diff --git a/.changeset/brave-rockets-worry.md b/.changeset/brave-rockets-worry.md new file mode 100644 index 00000000..d92b4f2d --- /dev/null +++ b/.changeset/brave-rockets-worry.md @@ -0,0 +1,5 @@ +--- +"astro-integration-kit": patch +--- + +Updates imports from `node:path` to `pathe` diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 00000000..b850cf1f --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,25 @@ +name: Tests + +on: [pull_request] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Setup PNPM + run: corepack enable && pnpm -v + + - name: Setup Node + uses: actions/setup-node@v3 + with: + node-version: 18.19.0 + cache: pnpm + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Test + run: pnpm test \ No newline at end of file diff --git a/.gitignore b/.gitignore index 76add878..a2f6af8b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ node_modules -dist \ No newline at end of file +dist +.TMP_* \ No newline at end of file diff --git a/package.json b/package.json index 7411a5c4..d3d04fdd 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,8 @@ "changeset": "changeset", "release": "node scripts/release.mjs", "lint": "biome check .", - "lint:fix": "biome check --apply ." + "lint:fix": "biome check --apply .", + "test": "pnpm --filter astro-integration-kit test" }, "devDependencies": { "@biomejs/biome": "1.5.2", diff --git a/package/README.md b/package/README.md index 954d37a9..75186666 100644 --- a/package/README.md +++ b/package/README.md @@ -25,6 +25,14 @@ Start the playground: pnpm playground:dev ``` +Testing is using Vitest and all tests are co-located with the module they are testing. + +To run all the tests: + +```bash +pnpm test +``` + You can now edit files in `package`. Please note that making changes to those files may require restarting the playground dev server. ## Licensing diff --git a/package/package.json b/package/package.json index 5f432160..11dbc65b 100644 --- a/package/package.json +++ b/package/package.json @@ -23,7 +23,7 @@ ".": "./src/index.ts" }, "scripts": { - "build": "tsc && vite build" + "test": "vitest" }, "type": "module", "peerDependencies": { @@ -32,7 +32,8 @@ "devDependencies": { "@types/node": "^20.11.0", "typescript": "^5.3.3", - "vite": "^5.0.11" + "vite": "^5.0.11", + "vitest": "^1.2.1" }, "dependencies": { "defu": "^6.1.4", diff --git a/package/src/types.ts b/package/src/types.ts index f4f3120c..3592faae 100644 --- a/package/src/types.ts +++ b/package/src/types.ts @@ -86,11 +86,12 @@ export interface ExtendedHooks { * hasIntegration("@astrojs/tailwind") * ``` */ - hasIntegration: (...params: ( - | [name: string] - | [name: string, position: "before" | "after"] - | [name: string, position: "before" | "after", relativeTo: string] - )) => boolean; + hasIntegration: ( + ...params: + | [name: string] + | [name: string, position: "before" | "after"] + | [name: string, position: "before" | "after", relativeTo: string] + ) => boolean; /** * In development, will reload the Astro dev server if any files within * the integration directory has changed. diff --git a/package/src/utils/add-dts.ts b/package/src/utils/add-dts.ts index 35a8faec..2e01ed09 100644 --- a/package/src/utils/add-dts.ts +++ b/package/src/utils/add-dts.ts @@ -1,4 +1,4 @@ -import { readFileSync, mkdirSync, writeFileSync } from "node:fs"; +import { mkdirSync, readFileSync, writeFileSync } from "node:fs"; import { dirname, relative } from "node:path"; import { fileURLToPath } from "node:url"; import type { AstroIntegrationLogger } from "astro"; diff --git a/package/src/utils/define-integration.ts b/package/src/utils/define-integration.ts index 00f88580..4d6f477d 100644 --- a/package/src/utils/define-integration.ts +++ b/package/src/utils/define-integration.ts @@ -1,9 +1,9 @@ import type { AstroIntegration } from "astro"; import { defu } from "defu"; import type { ExtendedHooks } from "../types.js"; -import { addVirtualImport } from "../utils/add-virtual-import.js"; -import { addVitePlugin } from "../utils/add-vite-plugin.js"; import { addDts } from "./add-dts.js"; +import { addVirtualImport } from "./add-virtual-import.js"; +import { addVitePlugin } from "./add-vite-plugin.js"; import { hasIntegration } from "./has-integration.js"; import { watchIntegration } from "./watch-integration.js"; @@ -69,7 +69,11 @@ export const defineIntegration = < }), addVitePlugin: (plugin) => addVitePlugin({ plugin, updateConfig: params.updateConfig }), - hasIntegration: (_name: string, position?: "before" | "after", relativeTo?: string) => + hasIntegration: ( + _name: string, + position?: "before" | "after", + relativeTo?: string, + ) => hasIntegration({ name: _name, // When `relativeTo` is not set get positions relative the current integration. diff --git a/package/src/utils/has-integration.ts b/package/src/utils/has-integration.ts index 467585a0..ef253947 100644 --- a/package/src/utils/has-integration.ts +++ b/package/src/utils/has-integration.ts @@ -1,15 +1,13 @@ import type { HookParameters } from "astro"; import { AstroError } from "astro/errors"; -type HasIntegrationParams = - & ( - | { position?: undefined, relativeTo?: string } - | { position: "before" | "after", relativeTo: string } - ) - & { - name: string; - config: HookParameters<"astro:config:setup">["config"]; - } +type HasIntegrationParams = ( + | { position?: undefined; relativeTo?: string } + | { position: "before" | "after"; relativeTo: string } +) & { + name: string; + config: HookParameters<"astro:config:setup">["config"]; +}; /** * Checks whether an integration is already installed. @@ -43,7 +41,9 @@ export const hasIntegration = ({ relativeTo, config, }: HasIntegrationParams): boolean => { - const integrationPosition = config.integrations.findIndex((integration) => integration.name === name); + const integrationPosition = config.integrations.findIndex( + (integration) => integration.name === name, + ); // Integration is not installed if (integrationPosition === -1) return false; @@ -51,17 +51,22 @@ export const hasIntegration = ({ // Not a relative check, the integration is present. if (position === undefined) return true; - if (relativeTo === undefined) throw new AstroError( - 'Cannot perform a relative integration check without a relative reference.', - 'Pass `relativeTo` on your call to `hasIntegration` or remove the `position` option.', - ); + if (relativeTo === undefined) + throw new AstroError( + "Cannot perform a relative integration check without a relative reference.", + "Pass `relativeTo` on your call to `hasIntegration` or remove the `position` option.", + ); - const otherPosition = config.integrations.findIndex((integration) => integration.name === relativeTo); + const otherPosition = config.integrations.findIndex( + (integration) => integration.name === relativeTo, + ); - if (otherPosition === -1) throw new AstroError('Cannot check relative position against an absent integration.'); + if (otherPosition === -1) + throw new AstroError( + "Cannot check relative position against an absent integration.", + ); return position === "before" ? integrationPosition < otherPosition : integrationPosition > otherPosition; }; - diff --git a/package/src/utils/watch-integration.ts b/package/src/utils/watch-integration.ts index f1e9e858..d9ccade7 100644 --- a/package/src/utils/watch-integration.ts +++ b/package/src/utils/watch-integration.ts @@ -1,6 +1,6 @@ import { readdirSync, statSync } from "node:fs"; -import { join, relative, resolve } from "node:path"; import type { HookParameters } from "astro"; +import { join, relative, resolve } from "pathe"; const getFilesRecursively = (dir: string, baseDir = dir) => { const files = readdirSync(dir); diff --git a/package/tests/unit/add-dts.spec.ts b/package/tests/unit/add-dts.spec.ts new file mode 100644 index 00000000..64524cce --- /dev/null +++ b/package/tests/unit/add-dts.spec.ts @@ -0,0 +1,140 @@ +import { mkdirSync, readFileSync, rmSync, writeFileSync } from "fs"; +import { fileURLToPath } from "url"; +import type { AstroIntegrationLogger } from "astro"; +import { dirname } from "pathe"; +import { afterAll, beforeAll, describe, expect, test, vi } from "vitest"; +import { addDts } from "../../src/utils/add-dts.js"; +import { createResolver } from "../../src/utils/create-resolver.js"; + +const tempFolderName = ".TMP_ADDDTS/"; +const { resolve } = createResolver(import.meta.url); +const { resolve: tempFolderResolver } = createResolver(resolve(tempFolderName)); +const envDtsPath = resolve(`${tempFolderName}/env.d.ts`); + +const createTempFiles = () => { + mkdirSync(resolve(tempFolderName)); + + writeFileSync(envDtsPath, `/// `, { + encoding: "utf-8", + }); +}; + +const deleteTempFiles = () => { + rmSync(resolve(tempFolderName), { + recursive: true, + force: true, + }); +}; + +describe("addDts", () => { + beforeAll(() => { + createTempFiles(); + }); + + afterAll(() => { + deleteTempFiles(); + }); + + test("Should run", () => { + const dtsFileName = "TEST"; + const dtsFileContent = 'declare module "my-integration" {}'; + const root = new URL(tempFolderName, import.meta.url); + const srcDir = new URL(tempFolderName, import.meta.url); + const logger = { + info: vi.fn(), + } as unknown as AstroIntegrationLogger; + + expect(() => + addDts({ + name: dtsFileName, + content: dtsFileContent, + logger, + root, + srcDir, + }), + ).not.toThrow(); + }); + + test("Should update the env.d.ts (double quotes)", () => { + const dtsFileName = "TEST"; + const dtsFileContent = 'declare module "my-integration" {}'; + const root = new URL(tempFolderName, import.meta.url); + const srcDir = new URL(tempFolderName, import.meta.url); + const logger = { + info: vi.fn(), + } as unknown as AstroIntegrationLogger; + + const expectedEnvDtsContent = `/// \n/// `; + + addDts({ + name: dtsFileName, + content: dtsFileContent, + logger, + root, + srcDir, + }); + + const fileContents = readFileSync(envDtsPath, { + encoding: "utf-8", + }); + + expect(fileContents).toEqual(expectedEnvDtsContent); + }); + + test("Should update the env.d.ts (single quotes)", () => { + const dtsFileName = "TEST"; + const dtsFileContent = 'declare module "my-integration" {}'; + const root = new URL(tempFolderName, import.meta.url); + const srcDir = new URL(tempFolderName, import.meta.url); + const logger = { + info: vi.fn(), + } as unknown as AstroIntegrationLogger; + + const expectedEnvDtsContent = `/// \n/// `; + + writeFileSync(envDtsPath, `/// `, { + encoding: "utf-8", + }); + + addDts({ + name: dtsFileName, + content: dtsFileContent, + logger, + root, + srcDir, + }); + + const fileContents = readFileSync(envDtsPath, { + encoding: "utf-8", + }); + + expect(fileContents).toEqual(expectedEnvDtsContent); + }); + + test("Should create the virtual file", () => { + const dtsFileName = "TEST"; + const dtsFileContent = 'declare module "my-integration" {}'; + const root = new URL(tempFolderName, import.meta.url); + const srcDir = new URL(tempFolderName, import.meta.url); + const logger = { + info: vi.fn(), + } as unknown as AstroIntegrationLogger; + + addDts({ + name: dtsFileName, + content: dtsFileContent, + logger, + root, + srcDir, + }); + + const fileContents = readFileSync( + tempFolderResolver(`.astro/${dtsFileName}.d.ts`), + { + encoding: "utf-8", + }, + ); + + expect(fileContents).toEqual(dtsFileContent); + }); +}); diff --git a/package/tests/unit/add-virtual-import.spec.ts b/package/tests/unit/add-virtual-import.spec.ts new file mode 100644 index 00000000..444fa631 --- /dev/null +++ b/package/tests/unit/add-virtual-import.spec.ts @@ -0,0 +1,61 @@ +import { type Mock, afterEach, describe, expect, test, vi } from "vitest"; +import { addVirtualImport } from "../../src/utils/add-virtual-import.js"; +import { addVitePlugin } from "../../src/utils/add-vite-plugin.js"; + +vi.mock("../../src/utils/add-vite-plugin.js"); + +const pluginNameStub = (name: T): `vite-plugin-${T}` => + `vite-plugin-${name}`; + +describe("add-virtual-import", () => { + const name = "test-module"; + const content = "export default {}"; + + afterEach(() => { + vi.clearAllMocks(); + }); + + test("It should call `addVitePlugin`", () => { + const updateConfig = vi.fn(); + + addVirtualImport({ + name, + content, + updateConfig, + }); + + expect(addVitePlugin).toHaveBeenCalled(); + }); + + test("`addVitePlugin` should get called with the correct plugin name", () => { + const updateConfig = vi.fn(); + + addVirtualImport({ + name, + content, + updateConfig, + }); + + const expectedName = pluginNameStub(name); + + const { plugin } = (addVitePlugin as Mock).mock.lastCall[0]; + + expect(plugin.name).toEqual(expectedName); + }); + + test("Virtual module should resolve correct name", () => { + const updateConfig = vi.fn(); + + addVirtualImport({ + name, + content, + updateConfig, + }); + + const { plugin } = (addVitePlugin as Mock).mock.lastCall[0]; + + const resolvedVirtualModuleId = plugin.resolveId(name); + + expect(resolvedVirtualModuleId).toEqual(`\0${name}`); + }); +}); diff --git a/package/tests/unit/add-vite-plugin.spec.ts b/package/tests/unit/add-vite-plugin.spec.ts new file mode 100644 index 00000000..7a6ff9ac --- /dev/null +++ b/package/tests/unit/add-vite-plugin.spec.ts @@ -0,0 +1,69 @@ +import type { Plugin } from "vite"; +import { describe, expect, test, vi } from "vitest"; +import { addVitePlugin } from "../../src/utils/add-vite-plugin.js"; + +describe("addVitePlugin", () => { + test("Should run", () => { + const updateConfig = vi.fn(); + + expect(() => + addVitePlugin({ + plugin: null, + updateConfig, + }), + ).not.toThrow(); + }); + + test("Should call updateConfig", () => { + const updateConfig = vi.fn(); + + addVitePlugin({ + plugin: null, + updateConfig, + }); + + expect(updateConfig).toHaveBeenCalled(); + }); + + test("Should add vite plugin", () => { + let plugin: Plugin; + const pluginName = "test-plugin"; + + const updateConfig = vi.fn((config) => { + plugin = config.vite.plugins[0]; + }); + + const expectedPlugin = { + name: pluginName, + }; + + addVitePlugin({ + plugin: expectedPlugin, + updateConfig, + }); + + // @ts-ignore + expect(plugin).toBeDefined(); + }); + + test("Plugin name should match", () => { + let plugin: Plugin; + const pluginName = "test-plugin"; + + const updateConfig = vi.fn((config) => { + plugin = config.vite.plugins[0]; + }); + + const expectedPlugin = { + name: pluginName, + }; + + addVitePlugin({ + plugin: expectedPlugin, + updateConfig, + }); + + // @ts-ignore + expect(plugin.name).toBe(pluginName); + }); +}); diff --git a/package/tests/unit/create-resolver.spec.ts b/package/tests/unit/create-resolver.spec.ts new file mode 100644 index 00000000..ab33e3bb --- /dev/null +++ b/package/tests/unit/create-resolver.spec.ts @@ -0,0 +1,28 @@ +import { dirname } from "pathe"; +import { describe, expect, test } from "vitest"; +import { createResolver } from "../../src/utils/create-resolver.js"; + +describe("createResolver", () => { + const directory = dirname(import.meta.url).replace("file:///", ""); + + test("Should return an object with a `resolve` method", () => { + const resolver = createResolver(import.meta.url); + + expect(resolver.resolve).toBeDefined(); + }); + + test("`resolve()` should return the `import.meta.url`", () => { + const resolver = createResolver(import.meta.url); + + expect(resolver.resolve()).toEqual(directory); + }); + + test("`resolve('./index.astro)` should return the `{directory}/index.astro", () => { + const fileName = "index.astro"; + const resolver = createResolver(import.meta.url); + + expect(resolver.resolve(`./${fileName}`)).toEqual( + `${directory}/${fileName}`, + ); + }); +}); diff --git a/package/tests/unit/define-integration.spec.ts b/package/tests/unit/define-integration.spec.ts new file mode 100644 index 00000000..c7393fc1 --- /dev/null +++ b/package/tests/unit/define-integration.spec.ts @@ -0,0 +1,363 @@ +import type { + AstroConfig, + AstroIntegrationLogger, + HookParameters, +} from "astro"; +import { type Mock, afterEach, describe, expect, test, vi } from "vitest"; +import type { ExtendedHooks } from "../../src/types.js"; +import { defineIntegration } from "../../src/utils/define-integration.js"; + +import { addDts as mockAddDts } from "../../src/utils/add-dts.js"; +import { addVirtualImport as mockAddVirtualImport } from "../../src/utils/add-virtual-import.js"; +import { addVitePlugin as mockAddVitePlugin } from "../../src/utils/add-vite-plugin.js"; +import { hasIntegration as mockHasIntegration } from "../../src/utils/has-integration.js"; +import { watchIntegration as mockWatchIntegration } from "../../src/utils/watch-integration.js"; + +vi.mock("../../src/utils/add-virtual-import.js"); +vi.mock("../../src/utils/add-vite-plugin.js"); +vi.mock("../../src/utils/add-dts.js"); +vi.mock("../../src/utils/has-integration.js"); +vi.mock("../../src/utils/watch-integration.js"); + +const astroConfigSetupParamsStub = ( + params?: HookParameters<"astro:config:setup">, +): HookParameters<"astro:config:setup"> => ({ + logger: vi.fn() as unknown as AstroIntegrationLogger, + addClientDirective: vi.fn(), + addDevToolbarApp: vi.fn(), + addMiddleware: vi.fn(), + addRenderer: vi.fn(), + addWatchFile: vi.fn(), + command: "dev", + injectRoute: vi.fn(), + injectScript: vi.fn(), + isRestart: false, + updateConfig: vi.fn(), + addDevOverlayPlugin: vi.fn(), + config: {} as unknown as AstroConfig, + ...(params || {}), +}); + +describe("defineIntegration", () => { + afterEach(() => { + vi.resetAllMocks(); + }); + + test("Should run", () => { + const name = "my-integration"; + const setup = () => ({}) as ExtendedHooks; + + expect(() => + defineIntegration({ + name, + setup, + }), + ).not.toThrow(); + }); + + test("Setup should get called", () => { + const name = "my-integration"; + const defaults = { foo: "bar" }; + const setup = vi.fn(() => { + return {} as ExtendedHooks; + }); + + defineIntegration({ + name, + defaults, + setup, + })({ ...defaults }); + + expect(setup).toBeCalled(); + }); + + test("Setup should get called with correct name", () => { + const name = "my-integration"; + const defaults = { foo: "bar" }; + const setup = vi.fn(() => { + return {} as ExtendedHooks; + }); + + defineIntegration({ + name, + defaults, + setup, + })({ ...defaults }); + + const callArgs = setup.mock.lastCall?.[0]; + + expect(callArgs?.name).toBe(name); + }); + + test("Setup should get called with default args", () => { + const name = "my-integration"; + const defaults = { foo: "bar" }; + const setup = vi.fn(() => { + return {} as ExtendedHooks; + }); + + defineIntegration({ + name, + defaults, + setup, + })({}); + + const callArgs = setup.mock.lastCall?.[0]; + + expect(callArgs?.options).toEqual(defaults); + }); + + test("Setup should get called with overwritten args", () => { + const name = "my-integration"; + const defaults = { foo: "bar" }; + const setup = vi.fn(() => { + return {} as ExtendedHooks; + }); + + const expectedOptions = { + ...defaults, + foo: "baz", + }; + + defineIntegration({ + name, + defaults, + setup, + })({ + ...expectedOptions, + }); + + const callArgs = setup.mock.lastCall?.[0]; + + expect(callArgs?.options).toEqual(expectedOptions); + }); + + test("Integration should have correct name", () => { + const name = "my-integration"; + const setup = vi.fn(() => { + return {} as ExtendedHooks; + }); + + const integration = defineIntegration({ + name, + setup, + })({}); + + expect(integration.name).toBe(name); + }); + + describe("astro:config:setup", () => { + describe("addDts", () => { + test("Should pass the correct name", () => { + const name = "my-integration"; + const dtsName = `virtual:${name}`; + const dtsContent = "declare module {}"; + + const setup = (): ExtendedHooks => { + return { + "astro:config:setup": ({ addDts }) => { + addDts({ + name: dtsName, + content: dtsContent, + }); + }, + }; + }; + + const integration = defineIntegration({ + name, + setup, + })({}); + + const params = astroConfigSetupParamsStub(); + + integration.hooks["astro:config:setup"]?.(params); + + const dtsCallArgs = (mockAddDts as Mock).mock.lastCall[0]; + + expect(dtsCallArgs.name).toBe(dtsName); + }); + + test("Should pass the correct content", () => { + const name = "my-integration"; + const dtsName = `virtual:${name}`; + const dtsContent = "declare module {}"; + + const setup = (): ExtendedHooks => { + return { + "astro:config:setup": ({ addDts }) => { + addDts({ + name: dtsName, + content: dtsContent, + }); + }, + }; + }; + + const integration = defineIntegration({ + name, + setup, + })({}); + + const params = astroConfigSetupParamsStub(); + + integration.hooks["astro:config:setup"]?.(params); + + const dtsCallArgs = (mockAddDts as Mock).mock.lastCall[0]; + + expect(dtsCallArgs.content).toBe(dtsContent); + }); + }); + + describe("addVirtualImport", () => { + test("Should pass the correct name", () => { + const name = "my-integration"; + const virtualImportName = `virtual:${name}`; + const content = "declare module {}"; + + const setup = (): ExtendedHooks => { + return { + "astro:config:setup": ({ addVirtualImport }) => { + addVirtualImport({ + name: virtualImportName, + content: content, + }); + }, + }; + }; + + const integration = defineIntegration({ + name, + setup, + })({}); + + const params = astroConfigSetupParamsStub(); + + integration.hooks["astro:config:setup"]?.(params); + + const addVirtualImportCallArgs = (mockAddVirtualImport as Mock).mock + .lastCall[0]; + + expect(addVirtualImportCallArgs.name).toBe(virtualImportName); + }); + + test("Should pass the correct content", () => { + const name = "my-integration"; + const virtualImportName = `virtual:${name}`; + const content = "declare module {}"; + + const setup = (): ExtendedHooks => { + return { + "astro:config:setup": ({ addVirtualImport }) => { + addVirtualImport({ + name: virtualImportName, + content: content, + }); + }, + }; + }; + + const integration = defineIntegration({ + name, + setup, + })({}); + + const params = astroConfigSetupParamsStub(); + + integration.hooks["astro:config:setup"]?.(params); + + const addVirtualImportCallArgs = (mockAddVirtualImport as Mock).mock + .lastCall[0]; + + expect(addVirtualImportCallArgs.content).toBe(content); + }); + }); + + describe("addVitePlugin", () => { + test("Should pass the correct plugin name", () => { + const name = "my-integration"; + const plugin = { + name: "vite-plugin-my-integration", + }; + + const setup = (): ExtendedHooks => { + return { + "astro:config:setup": ({ addVitePlugin }) => { + addVitePlugin(plugin); + }, + }; + }; + + const integration = defineIntegration({ + name, + setup, + })({}); + + const params = astroConfigSetupParamsStub(); + + integration.hooks["astro:config:setup"]?.(params); + + const addVitePluginCallArgs = (mockAddVitePlugin as Mock).mock + .lastCall[0]; + + expect(addVitePluginCallArgs.plugin.name).toBe(plugin.name); + }); + }); + + describe("hasIntegration", () => { + test("Should pass the correct name", () => { + const name = "my-integration"; + const integrationName = "@astrojs/tailwind"; + + const setup = (): ExtendedHooks => { + return { + "astro:config:setup": ({ hasIntegration }) => { + hasIntegration(integrationName); + }, + }; + }; + + const integration = defineIntegration({ + name, + setup, + })({}); + + const params = astroConfigSetupParamsStub(); + + integration.hooks["astro:config:setup"]?.(params); + + const callArgs = (mockHasIntegration as Mock).mock.lastCall[0]; + + expect(callArgs.name).toBe(integrationName); + }); + }); + + describe("watchIntegration", () => { + test("Should pass the correct name", () => { + const name = "my-integration"; + const dir = "./src"; + + const setup = (): ExtendedHooks => { + return { + "astro:config:setup": ({ watchIntegration }) => { + watchIntegration(dir); + }, + }; + }; + + const integration = defineIntegration({ + name, + setup, + })({}); + + const params = astroConfigSetupParamsStub(); + + integration.hooks["astro:config:setup"]?.(params); + + const callArgs = (mockWatchIntegration as Mock).mock.lastCall[0]; + + expect(callArgs.dir).toBe(dir); + }); + }); + }); +}); diff --git a/package/tests/unit/watch-integration.spec.ts b/package/tests/unit/watch-integration.spec.ts new file mode 100644 index 00000000..fef59aa1 --- /dev/null +++ b/package/tests/unit/watch-integration.spec.ts @@ -0,0 +1,185 @@ +import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs"; +import { dirname, normalize } from "pathe"; +import type { Plugin } from "vite"; +import { afterAll, beforeAll, describe, expect, test, vi } from "vitest"; +import { createResolver } from "../../src/utils/create-resolver.js"; +import { watchIntegration } from "../../src/utils/watch-integration.js"; + +const tempFolderName = ".TMP_WATCHINTEGRATION"; + +const tempPaths = [ + `${tempFolderName}/text.txt`, + `${tempFolderName}/folderA/text.txt`, + `${tempFolderName}/folderA/FolderB/text.txt`, + `${tempFolderName}/folderA/FolderB/FolderC/text.txt`, +]; + +const { resolve } = createResolver(import.meta.url); + +const createTempFiles = (paths: Array) => { + for (const path of paths) { + const absolutePath = resolve(path); + const directory = dirname(absolutePath); + + if (!existsSync(directory)) { + mkdirSync(directory, { + recursive: true, + }); + } + + writeFileSync(absolutePath, "hello", { + encoding: "utf-8", + }); + } +}; + +const deleteTempFiles = () => { + rmSync(resolve(tempFolderName), { + recursive: true, + force: true, + }); +}; + +describe("watchIntegration", () => { + const command = "dev"; + + beforeAll(() => { + createTempFiles(tempPaths); + }); + + afterAll(() => { + deleteTempFiles(); + }); + + test("Should run", () => { + const addWatchFile = vi.fn(); + const updateConfig = vi.fn(); + + expect(() => + watchIntegration({ + dir: resolve(tempFolderName), + addWatchFile, + updateConfig, + command, + }), + ).not.toThrow(); + }); + + test("Should call updateConfig", () => { + const addWatchFile = vi.fn(); + const updateConfig = vi.fn(); + + watchIntegration({ + dir: resolve(tempFolderName), + addWatchFile, + updateConfig, + command, + }); + + expect(updateConfig).toBeCalled(); + }); + + test("Should call updateConfig once", () => { + const addWatchFile = vi.fn(); + const updateConfig = vi.fn(); + + watchIntegration({ + dir: resolve(tempFolderName), + addWatchFile, + updateConfig, + command, + }); + + expect(updateConfig).toBeCalledTimes(1); + }); + + test("Should call addWatchFile", () => { + const addWatchFile = vi.fn(); + const updateConfig = vi.fn(); + + watchIntegration({ + dir: resolve(tempFolderName), + addWatchFile, + updateConfig, + command, + }); + + expect(addWatchFile).toBeCalled(); + }); + + test("Should call addWatchFile for each path (count)", () => { + const addWatchFile = vi.fn(); + const updateConfig = vi.fn(); + + watchIntegration({ + dir: resolve(tempFolderName), + addWatchFile, + updateConfig, + command, + }); + + const calls = addWatchFile.mock.calls.flatMap((entry) => entry[0]); + + expect(calls.length).toEqual(tempPaths.length); + }); + + test("Should call addWatchFile for each path (path check)", () => { + const addWatchFile = vi.fn(); + const updateConfig = vi.fn(); + + watchIntegration({ + dir: resolve(tempFolderName), + addWatchFile, + updateConfig, + command, + }); + + const calls = addWatchFile.mock.calls.flatMap((entry) => + normalize(entry[0]), + ); + + const allPathsPresent = tempPaths.every((path) => { + const resolvedPath = normalize(resolve(path)); + + return calls.includes(resolvedPath); + }); + + expect(allPathsPresent).toBeTruthy(); + }); + + test("Should create a vite plugin", () => { + let plugin: Plugin; + const addWatchFile = vi.fn(); + const updateConfig = vi.fn((config) => { + plugin = config.vite.plugins[0]; + }); + + watchIntegration({ + dir: resolve(tempFolderName), + addWatchFile, + updateConfig, + command, + }); + + // @ts-ignore - TS can't figure out that plugin _will_ actually be defined here + expect(plugin).toBeDefined(); + }); + + test("Should create a vite plugin (check name)", () => { + let plugin: Plugin; + const addWatchFile = vi.fn(); + const updateConfig = vi.fn((config) => { + plugin = config.vite.plugins[0]; + }); + + watchIntegration({ + dir: resolve(tempFolderName), + addWatchFile, + updateConfig, + command, + }); + + // @ts-ignore - TS can't figure out that plugin _will_ actually be defined here + expect(plugin.name).toBeDefined(); + }); +}); diff --git a/package/tests/utils.ts b/package/tests/utils.ts new file mode 100644 index 00000000..1a505409 --- /dev/null +++ b/package/tests/utils.ts @@ -0,0 +1,6 @@ +import type { HookParameters } from "astro"; +import type { HookName } from "../src/internal"; + +export const mockHookParams = ( + hookParameters: Partial>, +) => hookParameters; diff --git a/package/vitest.config.ts b/package/vitest.config.ts new file mode 100644 index 00000000..d9b69fd0 --- /dev/null +++ b/package/vitest.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + include: ["./tests/unit/**/*.spec.ts"], + }, +}); diff --git a/playground/astro.config.mjs b/playground/astro.config.mjs index 25815fa6..b29b89c7 100644 --- a/playground/astro.config.mjs +++ b/playground/astro.config.mjs @@ -7,7 +7,7 @@ export default defineConfig({ integrations: [ tailwind(), testIntegration({ name: "ced" }), - { name: 'integration-a', hooks: {} }, - { name: 'integration-b', hooks: {} }, + { name: "integration-a", hooks: {} }, + { name: "integration-b", hooks: {} }, ], }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d2c8b124..c495cdf3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -55,6 +55,9 @@ importers: vite: specifier: ^5.0.11 version: 5.0.11(@types/node@20.11.0) + vitest: + specifier: ^1.2.1 + version: 1.2.1(@types/node@20.11.0) playground: dependencies: @@ -1044,6 +1047,13 @@ packages: wrap-ansi-cjs: /wrap-ansi@7.0.0 dev: false + /@jest/schemas@29.6.3: + resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@sinclair/typebox': 0.27.8 + dev: true + /@jridgewell/gen-mapping@0.3.3: resolution: {integrity: sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==} engines: {node: '>=6.0.0'} @@ -1283,6 +1293,10 @@ packages: requiresBuild: true optional: true + /@sinclair/typebox@0.27.8: + resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} + dev: true + /@types/acorn@4.0.6: resolution: {integrity: sha512-veQTnWP+1D/xbxVrPC3zHnCZRjSrKfhbMUlEA43iMZLu7EsnTtkJklIuwrCPbOi8YkvDQAiW05VQQFvvz9oieQ==} dependencies: @@ -1412,6 +1426,45 @@ packages: resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} dev: false + /@vitest/expect@1.2.1: + resolution: {integrity: sha512-/bqGXcHfyKgFWYwIgFr1QYDaR9e64pRKxgBNWNXPefPFRhgm+K3+a/dS0cUGEreWngets3dlr8w8SBRw2fCfFQ==} + dependencies: + '@vitest/spy': 1.2.1 + '@vitest/utils': 1.2.1 + chai: 4.4.1 + dev: true + + /@vitest/runner@1.2.1: + resolution: {integrity: sha512-zc2dP5LQpzNzbpaBt7OeYAvmIsRS1KpZQw4G3WM/yqSV1cQKNKwLGmnm79GyZZjMhQGlRcSFMImLjZaUQvNVZQ==} + dependencies: + '@vitest/utils': 1.2.1 + p-limit: 5.0.0 + pathe: 1.1.2 + dev: true + + /@vitest/snapshot@1.2.1: + resolution: {integrity: sha512-Tmp/IcYEemKaqAYCS08sh0vORLJkMr0NRV76Gl8sHGxXT5151cITJCET20063wk0Yr/1koQ6dnmP6eEqezmd/Q==} + dependencies: + magic-string: 0.30.5 + pathe: 1.1.2 + pretty-format: 29.7.0 + dev: true + + /@vitest/spy@1.2.1: + resolution: {integrity: sha512-vG3a/b7INKH7L49Lbp0IWrG6sw9j4waWAucwnksPB1r1FTJgV7nkBByd9ufzu6VWya/QTvQW4V9FShZbZIB2UQ==} + dependencies: + tinyspy: 2.2.0 + dev: true + + /@vitest/utils@1.2.1: + resolution: {integrity: sha512-bsH6WVZYe/J2v3+81M5LDU8kW76xWObKIURpPrOXm2pjBniBu2MERI/XP60GpS4PHU3jyK50LUutOwrx4CyHUg==} + dependencies: + diff-sequences: 29.6.3 + estree-walker: 3.0.3 + loupe: 2.3.7 + pretty-format: 29.7.0 + dev: true + /@volar/kit@1.11.1(typescript@5.3.3): resolution: {integrity: sha512-nqO+Hl9f1ygOK/3M7Hpnw0lhKvuMFhh823nilStpkTmm5WfrUnE+4WaQkb3dC6LM3TZq74j2m88yxRC+Z3sZZw==} peerDependencies: @@ -1490,11 +1543,15 @@ packages: acorn: 8.11.3 dev: false + /acorn-walk@8.3.2: + resolution: {integrity: sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==} + engines: {node: '>=0.4.0'} + dev: true + /acorn@8.11.3: resolution: {integrity: sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==} engines: {node: '>=0.4.0'} hasBin: true - dev: false /ansi-align@3.0.1: resolution: {integrity: sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==} @@ -1528,6 +1585,11 @@ packages: dependencies: color-convert: 2.0.1 + /ansi-styles@5.2.0: + resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} + engines: {node: '>=10'} + dev: true + /ansi-styles@6.2.1: resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} engines: {node: '>=12'} @@ -1607,6 +1669,10 @@ packages: engines: {node: '>=0.10.0'} dev: true + /assertion-error@1.1.0: + resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==} + dev: true + /astring@1.8.6: resolution: {integrity: sha512-ISvCdHdlTDlH5IpxQJIex7BWBywFWgjJSVdwst+/iQCoEYnyOaQ95+X1JGshuBjGp6nxKUy1jMgE3zPqN7fQdg==} hasBin: true @@ -1935,6 +2001,11 @@ packages: ieee754: 1.2.1 dev: false + /cac@6.7.14: + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} + engines: {node: '>=8'} + dev: true + /call-bind@1.0.5: resolution: {integrity: sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==} dependencies: @@ -1975,6 +2046,19 @@ packages: resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} dev: false + /chai@4.4.1: + resolution: {integrity: sha512-13sOfMv2+DWduEU+/xbun3LScLoqN17nBeTLUsmDfKdoiC1fr0n9PU4guu4AhRcOVFk/sW8LyZWHuhWtQZiF+g==} + engines: {node: '>=4'} + dependencies: + assertion-error: 1.1.0 + check-error: 1.0.3 + deep-eql: 4.1.3 + get-func-name: 2.0.2 + loupe: 2.3.7 + pathval: 1.1.1 + type-detect: 4.0.8 + dev: true + /chalk@2.4.2: resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} engines: {node: '>=4'} @@ -2016,6 +2100,12 @@ packages: resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==} dev: true + /check-error@1.0.3: + resolution: {integrity: sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==} + dependencies: + get-func-name: 2.0.2 + dev: true + /chokidar@3.5.3: resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==} engines: {node: '>= 8.10.0'} @@ -2164,7 +2254,6 @@ packages: path-key: 3.1.1 shebang-command: 2.0.0 which: 2.0.2 - dev: false /css-selector-parser@3.0.4: resolution: {integrity: sha512-pnmS1dbKsz6KA4EW4BznyPL2xxkNDRg62hcD0v8g6DEw2W7hxOln5M953jsp9hmw5Dg57S6o/A8GOn37mbAgcQ==} @@ -2230,7 +2319,6 @@ packages: optional: true dependencies: ms: 2.1.2 - dev: false /decamelize-keys@1.1.1: resolution: {integrity: sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==} @@ -2259,6 +2347,13 @@ packages: mimic-response: 3.1.0 dev: false + /deep-eql@4.1.3: + resolution: {integrity: sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==} + engines: {node: '>=6'} + dependencies: + type-detect: 4.0.8 + dev: true + /deep-extend@0.6.0: resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} engines: {node: '>=4.0.0'} @@ -2330,6 +2425,11 @@ packages: resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} dev: false + /diff-sequences@29.6.3: + resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dev: true + /diff@5.1.0: resolution: {integrity: sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==} engines: {node: '>=0.3.1'} @@ -2567,7 +2667,6 @@ packages: resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} dependencies: '@types/estree': 1.0.5 - dev: false /eventemitter3@5.0.1: resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==} @@ -2586,7 +2685,6 @@ packages: onetime: 6.0.0 signal-exit: 4.1.0 strip-final-newline: 3.0.0 - dev: false /expand-template@2.0.3: resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} @@ -2758,6 +2856,10 @@ packages: engines: {node: '>=18'} dev: false + /get-func-name@2.0.2: + resolution: {integrity: sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==} + dev: true + /get-intrinsic@1.2.2: resolution: {integrity: sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==} dependencies: @@ -2770,7 +2872,6 @@ packages: /get-stream@8.0.1: resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==} engines: {node: '>=16'} - dev: false /get-symbol-description@1.0.0: resolution: {integrity: sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==} @@ -3181,7 +3282,6 @@ packages: /human-signals@5.0.0: resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==} engines: {node: '>=16.17.0'} - dev: false /iconv-lite@0.4.24: resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} @@ -3398,7 +3498,6 @@ packages: /is-stream@3.0.0: resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - dev: false /is-string@1.0.7: resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==} @@ -3509,6 +3608,10 @@ packages: resolution: {integrity: sha512-H8jvkz1O50L3dMZCsLqiuB2tA7muqbSg1AtGEkN0leAqGjsUzDJir3Zwr02BhqdcITPg3ei3mZ+HjMocAknhhg==} dev: true + /jsonc-parser@3.2.1: + resolution: {integrity: sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==} + dev: true + /jsonfile@4.0.0: resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} optionalDependencies: @@ -3550,6 +3653,14 @@ packages: pify: 4.0.1 strip-bom: 3.0.0 + /local-pkg@0.5.0: + resolution: {integrity: sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==} + engines: {node: '>=14'} + dependencies: + mlly: 1.5.0 + pkg-types: 1.0.3 + dev: true + /locate-path@5.0.0: resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} engines: {node: '>=8'} @@ -3582,6 +3693,12 @@ packages: resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} dev: false + /loupe@2.3.7: + resolution: {integrity: sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==} + dependencies: + get-func-name: 2.0.2 + dev: true + /lru-cache@10.1.0: resolution: {integrity: sha512-/1clY/ui8CzjKFyjdvwPWJUYKiFVXG2I2cY0ssG7h4+hwk+XOIX7ZSG9Q7TW8TW3Kp3BUSqgFWBLgL4PJ+Blag==} engines: {node: 14 || >=16.14} @@ -3611,7 +3728,6 @@ packages: engines: {node: '>=12'} dependencies: '@jridgewell/sourcemap-codec': 1.4.15 - dev: false /map-obj@1.0.1: resolution: {integrity: sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==} @@ -3868,7 +3984,6 @@ packages: /merge-stream@2.0.0: resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} - dev: false /merge2@1.4.1: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} @@ -4241,7 +4356,6 @@ packages: /mimic-fn@4.0.0: resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} engines: {node: '>=12'} - dev: false /mimic-response@3.1.0: resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} @@ -4290,13 +4404,21 @@ packages: requiresBuild: true dev: false + /mlly@1.5.0: + resolution: {integrity: sha512-NPVQvAY1xr1QoVeG0cy8yUYC7FQcOx6evl/RjT1wL5FvzPnzOysoqB/jmx/DhssT2dYa8nxECLAaFI/+gVLhDQ==} + dependencies: + acorn: 8.11.3 + pathe: 1.1.2 + pkg-types: 1.0.3 + ufo: 1.3.2 + dev: true + /ms@2.0.0: resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} dev: false /ms@2.1.2: resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} - dev: false /ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -4386,7 +4508,6 @@ packages: engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} dependencies: path-key: 4.0.0 - dev: false /nth-check@2.1.1: resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} @@ -4442,7 +4563,6 @@ packages: engines: {node: '>=12'} dependencies: mimic-fn: 4.0.0 - dev: false /ora@7.0.1: resolution: {integrity: sha512-0TUxTiFJWv+JnjWm4o9yvuskpEJLXTcng8MJuKd+SzAzp2o+OP3HWqNhB4OdJRt1Vsd9/mR0oyaEYlOnL7XIRw==} @@ -4492,7 +4612,6 @@ packages: engines: {node: '>=18'} dependencies: yocto-queue: 1.0.0 - dev: false /p-locate@4.1.0: resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} @@ -4591,12 +4710,10 @@ packages: /path-key@3.1.1: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} - dev: false /path-key@4.0.0: resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} engines: {node: '>=12'} - dev: false /path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} @@ -4620,7 +4737,10 @@ packages: /pathe@1.1.2: resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} - dev: false + + /pathval@1.1.1: + resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==} + dev: true /periscopic@3.1.0: resolution: {integrity: sha512-vKiQ8RRtkl9P+r/+oefh25C3fhybptkHKCZSPlcXiJux2tJF55GnEj3BVn4A5gKfq9NWWXXrxkHBwVPUfH0opw==} @@ -4657,6 +4777,14 @@ packages: dependencies: find-up: 4.1.0 + /pkg-types@1.0.3: + resolution: {integrity: sha512-nN7pYi0AQqJnoLPC9eHFQ8AcyaixBUOwvqc5TDnIKCMEE6I0y8P7OKA7fPexsXGCGxQDl/cmrLAp26LhcwxZ4A==} + dependencies: + jsonc-parser: 3.2.1 + mlly: 1.5.0 + pathe: 1.1.2 + dev: true + /postcss-import@15.1.0(postcss@8.4.33): resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==} engines: {node: '>=14.0.0'} @@ -4761,6 +4889,15 @@ packages: hasBin: true dev: true + /pretty-format@29.7.0: + resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/schemas': 29.6.3 + ansi-styles: 5.2.0 + react-is: 18.2.0 + dev: true + /prismjs@1.29.0: resolution: {integrity: sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==} engines: {node: '>=6'} @@ -4824,6 +4961,10 @@ packages: strip-json-comments: 2.0.1 dev: false + /react-is@18.2.0: + resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==} + dev: true + /read-cache@1.0.0: resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==} dependencies: @@ -5216,7 +5357,6 @@ packages: engines: {node: '>=8'} dependencies: shebang-regex: 3.0.0 - dev: false /shebang-regex@1.0.0: resolution: {integrity: sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==} @@ -5226,7 +5366,6 @@ packages: /shebang-regex@3.0.0: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} - dev: false /shikiji-core@0.9.19: resolution: {integrity: sha512-AFJu/vcNT21t0e6YrfadZ+9q86gvPum6iywRyt1OtIPjPFe25RQnYJyxHQPMLKCCWA992TPxmEmbNcOZCAJclw==} @@ -5258,13 +5397,16 @@ packages: object-inspect: 1.13.1 dev: true + /siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + dev: true + /signal-exit@3.0.7: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} /signal-exit@4.1.0: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} - dev: false /simple-concat@1.0.1: resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==} @@ -5365,6 +5507,14 @@ packages: /sprintf-js@1.0.3: resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} + /stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + dev: true + + /std-env@3.7.0: + resolution: {integrity: sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==} + dev: true + /stdin-discarder@0.1.0: resolution: {integrity: sha512-xhV7w8S+bUwlPTb4bAOUQhv8/cSS5offJuX8GQGq32ONF0ZtDWKfkdomM3HMRA+LhX6um/FZ0COqlwsjD53LeQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -5493,7 +5643,6 @@ packages: /strip-final-newline@3.0.0: resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} engines: {node: '>=12'} - dev: false /strip-indent@3.0.0: resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} @@ -5508,6 +5657,12 @@ packages: requiresBuild: true dev: false + /strip-literal@1.3.0: + resolution: {integrity: sha512-PugKzOsyXpArk0yWmUwqOZecSO0GH0bPoctLcqNDH9J04pVW3lflYE0ujElBGTloevcxF5MofAOZ7C5l2b+wLg==} + dependencies: + acorn: 8.11.3 + dev: true + /style-to-object@0.4.4: resolution: {integrity: sha512-HYNoHZa2GorYNyqiCaBgsxvcJIn7OHq6inEga+E6Ke3m5JkoqpQbnFssk4jwe+K7AhGa2fcha4wSOf1Kn01dMg==} dependencies: @@ -5640,6 +5795,20 @@ packages: any-promise: 1.3.0 dev: false + /tinybench@2.6.0: + resolution: {integrity: sha512-N8hW3PG/3aOoZAN5V/NSAEDz0ZixDSSt5b/a05iqtpgfLWMSVuCo7w0k2vVvEjdrIoeGqZzweX2WlyioNIHchA==} + dev: true + + /tinypool@0.8.2: + resolution: {integrity: sha512-SUszKYe5wgsxnNOVlBYO6IC+8VGWdVGZWAqUxp3UErNBtptZvWbwyUOyzNL59zigz2rCA92QiL3wvG+JDSdJdQ==} + engines: {node: '>=14.0.0'} + dev: true + + /tinyspy@2.2.0: + resolution: {integrity: sha512-d2eda04AN/cPOR89F7Xv5bK/jrQEhmcLFe6HFldoeO9AJtps+fqEnh486vnT/8y4bw38pSyxDcTCAq+Ks2aJTg==} + engines: {node: '>=14.0.0'} + dev: true + /tmp@0.0.33: resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==} engines: {node: '>=0.6.0'} @@ -5709,6 +5878,11 @@ packages: safe-buffer: 5.2.1 dev: false + /type-detect@4.0.8: + resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} + engines: {node: '>=4'} + dev: true + /type-fest@0.13.1: resolution: {integrity: sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==} engines: {node: '>=10'} @@ -5782,6 +5956,10 @@ packages: engines: {node: '>=14.17'} hasBin: true + /ufo@1.3.2: + resolution: {integrity: sha512-o+ORpgGwaYQXgqGDwd+hkS4PuZ3QnmqMMxRuajK/a38L6fTpcE5GPIfrf+L/KemFzfUpeUQc1rRS1iDBozvnFA==} + dev: true + /unbox-primitive@1.0.2: resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==} dependencies: @@ -5994,6 +6172,27 @@ packages: vfile-message: 4.0.2 dev: false + /vite-node@1.2.1(@types/node@20.11.0): + resolution: {integrity: sha512-fNzHmQUSOY+y30naohBvSW7pPn/xn3Ib/uqm+5wAJQJiqQsU0NBR78XdRJb04l4bOFKjpTWld0XAfkKlrDbySg==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + dependencies: + cac: 6.7.14 + debug: 4.3.4 + pathe: 1.1.2 + picocolors: 1.0.0 + vite: 5.0.11(@types/node@20.11.0) + transitivePeerDependencies: + - '@types/node' + - less + - lightningcss + - sass + - stylus + - sugarss + - supports-color + - terser + dev: true + /vite@5.0.11(@types/node@20.11.0): resolution: {integrity: sha512-XBMnDjZcNAw/G1gEiskiM1v6yzM4GE5aMGvhWTlHAYYhxb7S3/V1s3m2LDHa8Vh6yIWYYB0iJwsEaS523c4oYA==} engines: {node: ^18.0.0 || >=20.0.0} @@ -6040,6 +6239,63 @@ packages: vite: 5.0.11(@types/node@20.11.0) dev: false + /vitest@1.2.1(@types/node@20.11.0): + resolution: {integrity: sha512-TRph8N8rnSDa5M2wKWJCMnztCZS9cDcgVTQ6tsTFTG/odHJ4l5yNVqvbeDJYJRZ6is3uxaEpFs8LL6QM+YFSdA==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@types/node': ^18.0.0 || >=20.0.0 + '@vitest/browser': ^1.0.0 + '@vitest/ui': ^1.0.0 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@types/node': + optional: true + '@vitest/browser': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + dependencies: + '@types/node': 20.11.0 + '@vitest/expect': 1.2.1 + '@vitest/runner': 1.2.1 + '@vitest/snapshot': 1.2.1 + '@vitest/spy': 1.2.1 + '@vitest/utils': 1.2.1 + acorn-walk: 8.3.2 + cac: 6.7.14 + chai: 4.4.1 + debug: 4.3.4 + execa: 8.0.1 + local-pkg: 0.5.0 + magic-string: 0.30.5 + pathe: 1.1.2 + picocolors: 1.0.0 + std-env: 3.7.0 + strip-literal: 1.3.0 + tinybench: 2.6.0 + tinypool: 0.8.2 + vite: 5.0.11(@types/node@20.11.0) + vite-node: 1.2.1(@types/node@20.11.0) + why-is-node-running: 2.2.2 + transitivePeerDependencies: + - less + - lightningcss + - sass + - stylus + - sugarss + - supports-color + - terser + dev: true + /volar-service-css@0.0.17(@volar/language-service@1.11.1): resolution: {integrity: sha512-bEDJykygMzn2+a9ud6KwZZLli9eqarxApAXZuf2CqJJh6Trw1elmbBCo9SlPfqMrIhpFnwV0Sa+Xoc9x5WPeGw==} peerDependencies: @@ -6248,7 +6504,15 @@ packages: hasBin: true dependencies: isexe: 2.0.0 - dev: false + + /why-is-node-running@2.2.2: + resolution: {integrity: sha512-6tSwToZxTOcotxHeA+qGCq1mVzKR3CwcJGmVcY+QE8SHy6TnpFnh8PAvPNHYr7EcuVeG0QSMxtYCuO1ta/G/oA==} + engines: {node: '>=8'} + hasBin: true + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + dev: true /widest-line@4.0.1: resolution: {integrity: sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig==} @@ -6362,7 +6626,6 @@ packages: /yocto-queue@1.0.0: resolution: {integrity: sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==} engines: {node: '>=12.20'} - dev: false /zod@3.22.4: resolution: {integrity: sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==}