diff --git a/.changeset/itchy-donuts-love.md b/.changeset/itchy-donuts-love.md new file mode 100644 index 000000000..6bb94ff35 --- /dev/null +++ b/.changeset/itchy-donuts-love.md @@ -0,0 +1,23 @@ +--- +'@astrojs/netlify': minor +--- + +Adds `includedFiles` and `excludedFiles` configuration options. These allow extra files to be deployed in the SSR function bundle. + +When an Astro site using `server` or `hybrid` rendering is deployed to Netlify, the generated functions trace the server dependencies and include any that may be needed in SSR. However, sometimes you may want to include extra files that are not detected as dependencies, such as files that are loaded using `fs` functions. Also, you may sometimes want to specifically exclude dependencies that are bundled automatically. For example, you may have a Node module that includes a large binary. + +The `includedFiles` and `excludedFiles` options allow you specify these inclusions and exclusions as an array of file paths relative to the site root. Both options support glob patterns, so you can include/exclude multiple files at once. + +If you are specifying files using filesystem functions, resolve the path using `path.resolve()` or `process.cwd()`, which will give you the site root. At runtime, compiled source files will be in a different location and you cannot rely on relative file paths. + +```js +import netlify from '@astrojs/netlify'; +import { defineConfig } from 'astro/config'; + +export default defineConfig({ + output: 'server', + adapter: netlify({ + includedFiles: ['src/address-data/**/*.csv', 'src/include-this.txt'], + excludedFiles: ['node_modules/chonky-module/not-this-massive-file.mp4'], + }) +}); diff --git a/.changeset/shiny-geese-accept.md b/.changeset/shiny-geese-accept.md new file mode 100644 index 000000000..335084d24 --- /dev/null +++ b/.changeset/shiny-geese-accept.md @@ -0,0 +1,7 @@ +--- +'@astrojs/netlify': major +--- + +Changes SSR working directory to site root rather than repo root. + +During SSR, the working directory is now the site root, rather than the repo root. This change allows you to use paths in your SSR functions that match the paths used for `includedFiles`. diff --git a/.gitignore b/.gitignore index f4db98adb..a19112a00 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,4 @@ package-lock.json # exclude IntelliJ/WebStorm stuff .idea +.astro/ \ No newline at end of file diff --git a/packages/netlify/package.json b/packages/netlify/package.json index 25e68343c..d7aa0f751 100644 --- a/packages/netlify/package.json +++ b/packages/netlify/package.json @@ -25,6 +25,7 @@ "files": ["dist"], "scripts": { "build": "tsc", + "dev": "tsc --watch", "test-fn": "astro-scripts test \"test/functions/*.test.js\"", "test-static": "astro-scripts test \"test/static/*.test.js\"", "test": "pnpm run test-fn && pnpm run test-static", diff --git a/packages/netlify/src/index.ts b/packages/netlify/src/index.ts index a2515dce8..778a6e62c 100644 --- a/packages/netlify/src/index.ts +++ b/packages/netlify/src/index.ts @@ -1,15 +1,15 @@ import { randomUUID } from 'node:crypto'; import { appendFile, mkdir, readFile, writeFile } from 'node:fs/promises'; import type { IncomingMessage } from 'node:http'; -import { fileURLToPath } from 'node:url'; +import { fileURLToPath, pathToFileURL } from 'node:url'; import { emptyDir } from '@astrojs/internal-helpers/fs'; import { createRedirectsFromAstroRoutes } from '@astrojs/underscore-redirects'; import type { Context } from '@netlify/functions'; import type { AstroConfig, AstroIntegration, AstroIntegrationLogger, RouteData } from 'astro'; import { build } from 'esbuild'; +import glob from 'fast-glob'; import { copyDependenciesToFunction } from './lib/nft.js'; import type { Args } from './ssr-function.js'; - const { version: packageVersion } = JSON.parse( await readFile(new URL('../package.json', import.meta.url), 'utf8') ); @@ -185,6 +185,18 @@ export interface NetlifyIntegrationConfig { * @default {true} */ imageCDN?: boolean; + + /** + * Extra files to include in the SSR function bundle. Paths are relative to the project root. + * Glob patterns are supported. + */ + includedFiles?: string[]; + + /** + * Files to exclude from the SSR function bundle. Paths are relative to the project root. + * Glob patterns are supported. + */ + excludedFiles?: string[]; } export default function netlifyIntegration( @@ -237,18 +249,37 @@ export default function netlifyIntegration( } } + async function getFilesByGlob( + include: Array = [], + exclude: Array = [] + ): Promise> { + const files = await glob(include, { + cwd: fileURLToPath(rootDir), + absolute: true, + ignore: exclude, + }); + return files.map((file) => pathToFileURL(file)); + } + async function writeSSRFunction({ notFoundContent, logger, }: { notFoundContent?: string; logger: AstroIntegrationLogger }) { const entry = new URL('./entry.mjs', ssrBuildDir()); + const includedFiles = await getFilesByGlob( + integrationConfig?.includedFiles, + integrationConfig?.excludedFiles + ); + + const excludedFiles = await getFilesByGlob(integrationConfig?.excludedFiles); + const { handler } = await copyDependenciesToFunction( { entry, outDir: ssrOutputDir(), - includeFiles: [], - excludeFiles: [], + includeFiles: includedFiles, + excludeFiles: excludedFiles, logger, }, TRACE_CACHE diff --git a/packages/netlify/src/ssr-function.ts b/packages/netlify/src/ssr-function.ts index 22925ddaa..a7ee61fab 100644 --- a/packages/netlify/src/ssr-function.ts +++ b/packages/netlify/src/ssr-function.ts @@ -1,3 +1,4 @@ +import { fileURLToPath } from 'node:url'; import type { Context } from '@netlify/functions'; import type { SSRManifest } from 'astro'; import { App } from 'astro/app'; @@ -17,14 +18,31 @@ export interface Args { const clientAddressSymbol = Symbol.for('astro.clientAddress'); +function getSiteRoot() { + // The entrypoint is created in `.netlify/build`, so we can find the root by going one level up + const url = import.meta.url; + const index = url.lastIndexOf('.netlify/build/'); + return fileURLToPath(index !== -1 ? url.substring(0, index) : url); +} + export const createExports = (manifest: SSRManifest, { middlewareSecret }: Args) => { const app = new App(manifest); + const root = getSiteRoot(); + function createHandler(integrationConfig: { cacheOnDemandPages: boolean; notFoundContent?: string; }) { return async function handler(request: Request, context: Context) { + // Change working directory to the site root, rather than the repo root + // This allows relative paths to match those set in `includedFiles` in astro.config.mjs + try { + process.chdir(root); + } catch (err) { + console.warn('Failed to chdir to root', err); + } + const routeData = app.match(request); if (!routeData && typeof integrationConfig.notFoundContent !== 'undefined') { return new Response(integrationConfig.notFoundContent, { diff --git a/packages/netlify/test/functions/fixtures/excludes/astro.config.mjs b/packages/netlify/test/functions/fixtures/excludes/astro.config.mjs new file mode 100644 index 000000000..89614e49e --- /dev/null +++ b/packages/netlify/test/functions/fixtures/excludes/astro.config.mjs @@ -0,0 +1,11 @@ +import netlify from '@astrojs/netlify'; +import { defineConfig } from 'astro/config'; + +export default defineConfig({ + output: 'server', + adapter: netlify({ + // A long way to the root directory! + excludedFiles: ['../../../../../../node_modules/.pnpm/cowsay@*/**'], + }), + site: "http://example.com", +}); \ No newline at end of file diff --git a/packages/netlify/test/functions/fixtures/excludes/package.json b/packages/netlify/test/functions/fixtures/excludes/package.json new file mode 100644 index 000000000..625c6587d --- /dev/null +++ b/packages/netlify/test/functions/fixtures/excludes/package.json @@ -0,0 +1,14 @@ +{ + "name": "@test/netlify-excludes", + "version": "0.0.0", + "private": true, + "dependencies": { + "@astrojs/netlify": "workspace:", + "astro": "^4.15.1", + "cowsay": "1.6.0" + }, + "scripts": { + "build": "astro build", + "dev": "astro dev" + } +} \ No newline at end of file diff --git a/packages/netlify/test/functions/fixtures/excludes/src/env.d.ts b/packages/netlify/test/functions/fixtures/excludes/src/env.d.ts new file mode 100644 index 000000000..c13bd73c7 --- /dev/null +++ b/packages/netlify/test/functions/fixtures/excludes/src/env.d.ts @@ -0,0 +1,2 @@ +/// +/// \ No newline at end of file diff --git a/packages/netlify/test/functions/fixtures/excludes/src/pages/404.astro b/packages/netlify/test/functions/fixtures/excludes/src/pages/404.astro new file mode 100644 index 000000000..9049fa0fb --- /dev/null +++ b/packages/netlify/test/functions/fixtures/excludes/src/pages/404.astro @@ -0,0 +1,7 @@ +--- +export const prerender = false +const header = Astro.request.headers.get("x-test") +--- + +

This is my custom 404 page

+

x-test: {header}

\ No newline at end of file diff --git a/packages/netlify/test/functions/fixtures/excludes/src/pages/index.astro b/packages/netlify/test/functions/fixtures/excludes/src/pages/index.astro new file mode 100644 index 000000000..50f865b41 --- /dev/null +++ b/packages/netlify/test/functions/fixtures/excludes/src/pages/index.astro @@ -0,0 +1,18 @@ +--- + +async function moo() { + const cow = await import('cowsay') + return cow.say({ text: 'Moo!' }); +} + +if(Astro.url.searchParams.get('moo')) { + await moo(); +} + +--- + +Testing + +

Hi

+ + diff --git a/packages/netlify/test/functions/fixtures/includes/astro.config.mjs b/packages/netlify/test/functions/fixtures/includes/astro.config.mjs new file mode 100644 index 000000000..91c48472a --- /dev/null +++ b/packages/netlify/test/functions/fixtures/includes/astro.config.mjs @@ -0,0 +1,11 @@ +import netlify from '@astrojs/netlify'; +import { defineConfig } from 'astro/config'; + +export default defineConfig({ + output: 'server', + adapter: netlify({ + includedFiles: ['files/**/*.csv', 'files/include-this.txt'], + excludedFiles: ['files/subdirectory/not-this.csv'], + }), + site: "http://example.com", +}); \ No newline at end of file diff --git a/packages/netlify/test/functions/fixtures/includes/files/also-this.csv b/packages/netlify/test/functions/fixtures/includes/files/also-this.csv new file mode 100644 index 000000000..3fde4e202 --- /dev/null +++ b/packages/netlify/test/functions/fixtures/includes/files/also-this.csv @@ -0,0 +1 @@ +1,2,3 \ No newline at end of file diff --git a/packages/netlify/test/functions/fixtures/includes/files/include-this.txt b/packages/netlify/test/functions/fixtures/includes/files/include-this.txt new file mode 100644 index 000000000..b6fc4c620 --- /dev/null +++ b/packages/netlify/test/functions/fixtures/includes/files/include-this.txt @@ -0,0 +1 @@ +hello \ No newline at end of file diff --git a/packages/netlify/test/functions/fixtures/includes/files/subdirectory/and-this.csv b/packages/netlify/test/functions/fixtures/includes/files/subdirectory/and-this.csv new file mode 100644 index 000000000..3fde4e202 --- /dev/null +++ b/packages/netlify/test/functions/fixtures/includes/files/subdirectory/and-this.csv @@ -0,0 +1 @@ +1,2,3 \ No newline at end of file diff --git a/packages/netlify/test/functions/fixtures/includes/files/subdirectory/not-this.csv b/packages/netlify/test/functions/fixtures/includes/files/subdirectory/not-this.csv new file mode 100644 index 000000000..3fde4e202 --- /dev/null +++ b/packages/netlify/test/functions/fixtures/includes/files/subdirectory/not-this.csv @@ -0,0 +1 @@ +1,2,3 \ No newline at end of file diff --git a/packages/netlify/test/functions/fixtures/includes/files/subdirectory/or-this.txt b/packages/netlify/test/functions/fixtures/includes/files/subdirectory/or-this.txt new file mode 100644 index 000000000..b6fc4c620 --- /dev/null +++ b/packages/netlify/test/functions/fixtures/includes/files/subdirectory/or-this.txt @@ -0,0 +1 @@ +hello \ No newline at end of file diff --git a/packages/netlify/test/functions/fixtures/includes/netlify.toml b/packages/netlify/test/functions/fixtures/includes/netlify.toml new file mode 100644 index 000000000..2292ed35a --- /dev/null +++ b/packages/netlify/test/functions/fixtures/includes/netlify.toml @@ -0,0 +1,3 @@ +[build] +command = "pnpm run --filter @test/netlify-includes... build" +publish = "dist" diff --git a/packages/netlify/test/functions/fixtures/includes/package.json b/packages/netlify/test/functions/fixtures/includes/package.json new file mode 100644 index 000000000..e53f723ad --- /dev/null +++ b/packages/netlify/test/functions/fixtures/includes/package.json @@ -0,0 +1,14 @@ +{ + "name": "@test/netlify-includes", + "version": "0.0.0", + "private": true, + "dependencies": { + "@astrojs/netlify": "workspace:", + "astro": "^4.15.1", + "cowsay": "1.6.0" + }, + "scripts": { + "build": "astro build", + "dev": "astro dev" + } +} \ No newline at end of file diff --git a/packages/netlify/test/functions/fixtures/includes/src/env.d.ts b/packages/netlify/test/functions/fixtures/includes/src/env.d.ts new file mode 100644 index 000000000..c13bd73c7 --- /dev/null +++ b/packages/netlify/test/functions/fixtures/includes/src/env.d.ts @@ -0,0 +1,2 @@ +/// +/// \ No newline at end of file diff --git a/packages/netlify/test/functions/fixtures/includes/src/pages/404.astro b/packages/netlify/test/functions/fixtures/includes/src/pages/404.astro new file mode 100644 index 000000000..9049fa0fb --- /dev/null +++ b/packages/netlify/test/functions/fixtures/includes/src/pages/404.astro @@ -0,0 +1,7 @@ +--- +export const prerender = false +const header = Astro.request.headers.get("x-test") +--- + +

This is my custom 404 page

+

x-test: {header}

\ No newline at end of file diff --git a/packages/netlify/test/functions/fixtures/includes/src/pages/index.astro b/packages/netlify/test/functions/fixtures/includes/src/pages/index.astro new file mode 100644 index 000000000..0094b8b9a --- /dev/null +++ b/packages/netlify/test/functions/fixtures/includes/src/pages/index.astro @@ -0,0 +1,22 @@ +--- +import { promises as fs } from 'fs'; +const cwd = process.cwd(); +const file = await fs.readFile('files/include-this.txt', 'utf-8'); + +async function moo() { + const cow = await import('cowsay') + return cow.say({ text: 'Moo!' }); +} +if(Astro.url.searchParams.get('moo')) { + await moo(); +} + + +--- + +Testing + +

{file}

+

{cwd}

+ + diff --git a/packages/netlify/test/functions/includes.test.js b/packages/netlify/test/functions/includes.test.js new file mode 100644 index 000000000..a2e850744 --- /dev/null +++ b/packages/netlify/test/functions/includes.test.js @@ -0,0 +1,87 @@ +import * as assert from 'node:assert/strict'; +import { existsSync } from 'node:fs'; +import { before, describe, it } from 'node:test'; +import { fileURLToPath } from 'node:url'; +import { loadFixture } from '@astrojs/test-utils'; +import * as cheerio from 'cheerio'; + +describe( + 'Included files', + () => { + let fixture; + const root = new URL('./fixtures/includes/', import.meta.url); + const expectedCwd = new URL( + '.netlify/v1/functions/ssr/packages/netlify/test/functions/fixtures/includes/', + root + ); + + before(async () => { + fixture = await loadFixture({ root }); + await fixture.build(); + }); + + it('Includes files', async () => { + const filesRoot = new URL('./files/', expectedCwd); + const expectedFiles = ['include-this.txt', 'also-this.csv', 'subdirectory/and-this.csv']; + + for (const file of expectedFiles) { + assert.ok(existsSync(new URL(file, filesRoot)), `Expected file ${file} to exist`); + } + + const notExpectedFiles = ['subdirectory/not-this.csv', 'subdirectory/or-this.txt']; + + for (const file of notExpectedFiles) { + assert.ok(!existsSync(new URL(file, filesRoot)), `Expected file ${file} to not exist`); + } + }); + + it('Can load included files correctly', async () => { + const entryURL = new URL( + './fixtures/includes/.netlify/v1/functions/ssr/ssr.mjs', + import.meta.url + ); + const { default: handler } = await import(entryURL); + const resp = await handler(new Request('http://example.com/'), {}); + const html = await resp.text(); + const $ = cheerio.load(html); + assert.equal($('h1').text(), 'hello'); + assert.equal($('p').text(), fileURLToPath(expectedCwd).slice(0, -1)); + }); + + it('Includes traced node modules with symlinks', async () => { + const expected = new URL( + '.netlify/v1/functions/ssr/node_modules/.pnpm/cowsay@1.6.0/node_modules/cowsay/cows/happy-whale.cow', + root + ); + console.log(expected.href); + assert.ok(existsSync(expected, 'Expected excluded file to exist in default build')); + }); + }, + { + timeout: 120000, + } +); + +describe( + 'Excluded files', + () => { + let fixture; + const root = new URL('./fixtures/excludes/', import.meta.url); + + before(async () => { + fixture = await loadFixture({ root }); + await fixture.build(); + }); + + it('Excludes traced node modules', async () => { + const expected = new URL( + '.netlify/v1/functions/ssr/node_modules/.pnpm/cowsay@1.6.0/node_modules/cowsay/cows/happy-whale.cow', + root + ); + assert.ok(!existsSync(expected, 'Expected excluded file to not exist in build')); + }); + }, + { + timeout: 120000, + } +); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 73b6804a6..a41f7852f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -277,6 +277,30 @@ importers: specifier: 'workspace:' version: link:../../../.. + packages/netlify/test/functions/fixtures/excludes: + dependencies: + '@astrojs/netlify': + specifier: 'workspace:' + version: link:../../../.. + astro: + specifier: ^4.15.1 + version: 4.15.1(@types/node@22.4.1)(rollup@4.21.2)(typescript@5.5.4) + cowsay: + specifier: 1.6.0 + version: 1.6.0 + + packages/netlify/test/functions/fixtures/includes: + dependencies: + '@astrojs/netlify': + specifier: 'workspace:' + version: link:../../../.. + astro: + specifier: ^4.15.1 + version: 4.15.1(@types/node@22.4.1)(rollup@4.21.2)(typescript@5.5.4) + cowsay: + specifier: 1.6.0 + version: 1.6.0 + packages/netlify/test/functions/fixtures/middleware: dependencies: '@astrojs/netlify': @@ -2048,6 +2072,10 @@ packages: resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} engines: {node: '>=6'} + ansi-regex@3.0.1: + resolution: {integrity: sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==} + engines: {node: '>=4'} + ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} @@ -2194,6 +2222,10 @@ packages: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} + camelcase@5.3.1: + resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} + engines: {node: '>=6'} + camelcase@7.0.1: resolution: {integrity: sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw==} engines: {node: '>=14.16'} @@ -2269,6 +2301,9 @@ packages: resolution: {integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==} engines: {node: '>=6'} + cliui@6.0.0: + resolution: {integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==} + cliui@8.0.1: resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} engines: {node: '>=12'} @@ -2339,6 +2374,11 @@ packages: resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==} engines: {node: '>= 0.6'} + cowsay@1.6.0: + resolution: {integrity: sha512-8C4H1jdrgNusTQr3Yu4SCm+ZKsAlDFbpa0KS0Z3im8ueag+9pGOf3CrioruvmeaW/A5oqg9L0ar6qeftAh03jw==} + engines: {node: '>= 4'} + hasBin: true + cross-spawn@5.1.0: resolution: {integrity: sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==} @@ -2387,6 +2427,10 @@ packages: supports-color: optional: true + decamelize@1.2.0: + resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} + engines: {node: '>=0.10.0'} + decode-named-character-reference@1.0.2: resolution: {integrity: sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==} @@ -2782,6 +2826,10 @@ packages: get-source@2.0.12: resolution: {integrity: sha512-X5+4+iD+HoSeEED+uwrQ07BOQr0kEDFMVqqpBuI+RaZBpBpHCuXxo70bjar6f0b0u/DQJsJ7ssurpP0V60Az+w==} + get-stdin@8.0.0: + resolution: {integrity: sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg==} + engines: {node: '>=10'} + get-stream@8.0.1: resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==} engines: {node: '>=16'} @@ -2981,6 +3029,10 @@ packages: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} + is-fullwidth-code-point@2.0.0: + resolution: {integrity: sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==} + engines: {node: '>=4'} + is-fullwidth-code-point@3.0.0: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} engines: {node: '>=8'} @@ -3770,6 +3822,9 @@ packages: resolution: {integrity: sha512-X34iHADNbNDfr6OTStIAHWSAvvKQRYgLO6duASaVf7J2VA3lvmNYboAHOuLC2huav1IwgZJtyEcJCKVzFxOSMQ==} engines: {node: '>=8.6.0'} + require-main-filename@2.0.0: + resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==} + resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} @@ -3987,6 +4042,10 @@ packages: resolution: {integrity: sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw==} engines: {node: '>=4', npm: '>=6'} + string-width@2.1.1: + resolution: {integrity: sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==} + engines: {node: '>=4'} + string-width@4.2.3: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} engines: {node: '>=8'} @@ -4005,6 +4064,10 @@ packages: stringify-entities@4.0.3: resolution: {integrity: sha512-BP9nNHMhhfcMbiuQKCqMjhDP5yBCAxsPu4pHFFzJ6Alo9dZgY4VLDPutXqIjpRiMoKdp7Av85Gr73Q5uH9k7+g==} + strip-ansi@4.0.0: + resolution: {integrity: sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow==} + engines: {node: '>=4'} + strip-ansi@6.0.1: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} @@ -4021,6 +4084,10 @@ packages: resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} engines: {node: '>=4'} + strip-final-newline@2.0.0: + resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} + engines: {node: '>=6'} + strip-final-newline@3.0.0: resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} engines: {node: '>=12'} @@ -4453,6 +4520,9 @@ packages: whatwg-url@5.0.0: resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + which-module@2.0.1: + resolution: {integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==} + which-pm-runs@1.1.0: resolution: {integrity: sha512-n1brCuqClxfFfq/Rb0ICg9giSZqCS+pLtccdag6C2HyufBrh3fBOiy9nb6ggRMvWOVH5GrdJskj5iGTZNxd7SA==} engines: {node: '>=4'} @@ -4500,6 +4570,10 @@ packages: '@cloudflare/workers-types': optional: true + wrap-ansi@6.2.0: + resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} + engines: {node: '>=8'} + wrap-ansi@7.0.0: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} engines: {node: '>=10'} @@ -4526,6 +4600,9 @@ packages: xxhash-wasm@1.0.2: resolution: {integrity: sha512-ibF0Or+FivM9lNrg+HGJfVX8WJqgo+kCLDc4vx6xMeTce7Aj+DLttKbxxRR/gNLSAelRc1omAPlJ77N/Jem07A==} + y18n@4.0.3: + resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==} + y18n@5.0.8: resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} engines: {node: '>=10'} @@ -4552,10 +4629,18 @@ packages: engines: {node: '>= 14'} hasBin: true + yargs-parser@18.1.3: + resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==} + engines: {node: '>=6'} + yargs-parser@21.1.1: resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} engines: {node: '>=12'} + yargs@15.4.1: + resolution: {integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==} + engines: {node: '>=8'} + yargs@17.7.2: resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} engines: {node: '>=12'} @@ -6033,6 +6118,8 @@ snapshots: ansi-colors@4.1.3: {} + ansi-regex@3.0.1: {} + ansi-regex@5.0.1: {} ansi-regex@6.0.1: {} @@ -6436,6 +6523,8 @@ snapshots: callsites@3.1.0: {} + camelcase@5.3.1: {} + camelcase@7.0.1: {} caniuse-lite@1.0.30001643: {} @@ -6521,6 +6610,12 @@ snapshots: cli-spinners@2.9.2: {} + cliui@6.0.0: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 6.2.0 + cliui@8.0.1: dependencies: string-width: 4.2.3 @@ -6577,6 +6672,13 @@ snapshots: cookie@0.6.0: {} + cowsay@1.6.0: + dependencies: + get-stdin: 8.0.0 + string-width: 2.1.1 + strip-final-newline: 2.0.0 + yargs: 15.4.1 + cross-spawn@5.1.0: dependencies: lru-cache: 4.1.5 @@ -6617,6 +6719,8 @@ snapshots: dependencies: ms: 2.1.2 + decamelize@1.2.0: {} + decode-named-character-reference@1.0.2: dependencies: character-entities: 2.0.2 @@ -7083,6 +7187,8 @@ snapshots: data-uri-to-buffer: 2.0.2 source-map: 0.6.1 + get-stdin@8.0.0: {} + get-stream@8.0.1: {} github-slugger@2.0.0: {} @@ -7329,6 +7435,8 @@ snapshots: is-extglob@2.1.1: {} + is-fullwidth-code-point@2.0.0: {} + is-fullwidth-code-point@3.0.0: {} is-glob@4.0.3: @@ -8285,6 +8393,8 @@ snapshots: transitivePeerDependencies: - supports-color + require-main-filename@2.0.0: {} + resolve-from@4.0.0: {} resolve-from@5.0.0: {} @@ -8558,6 +8668,11 @@ snapshots: stoppable@1.1.0: {} + string-width@2.1.1: + dependencies: + is-fullwidth-code-point: 2.0.0 + strip-ansi: 4.0.0 + string-width@4.2.3: dependencies: emoji-regex: 8.0.0 @@ -8585,6 +8700,10 @@ snapshots: character-entities-html4: 2.1.0 character-entities-legacy: 3.0.0 + strip-ansi@4.0.0: + dependencies: + ansi-regex: 3.0.1 + strip-ansi@6.0.1: dependencies: ansi-regex: 5.0.1 @@ -8597,6 +8716,8 @@ snapshots: strip-bom@3.0.0: {} + strip-final-newline@2.0.0: {} + strip-final-newline@3.0.0: {} strip-json-comments@3.1.1: {} @@ -9020,6 +9141,8 @@ snapshots: tr46: 0.0.3 webidl-conversions: 3.0.1 + which-module@2.0.1: {} + which-pm-runs@1.1.0: {} which-pm@2.0.0: @@ -9085,6 +9208,12 @@ snapshots: - supports-color - utf-8-validate + wrap-ansi@6.2.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi@7.0.0: dependencies: ansi-styles: 4.3.0 @@ -9103,6 +9232,8 @@ snapshots: xxhash-wasm@1.0.2: {} + y18n@4.0.3: {} + y18n@5.0.8: {} yallist@2.1.2: {} @@ -9130,8 +9261,27 @@ snapshots: yaml@2.5.0: {} + yargs-parser@18.1.3: + dependencies: + camelcase: 5.3.1 + decamelize: 1.2.0 + yargs-parser@21.1.1: {} + yargs@15.4.1: + dependencies: + cliui: 6.0.0 + decamelize: 1.2.0 + find-up: 4.1.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + require-main-filename: 2.0.0 + set-blocking: 2.0.0 + string-width: 4.2.3 + which-module: 2.0.1 + y18n: 4.0.3 + yargs-parser: 18.1.3 + yargs@17.7.2: dependencies: cliui: 8.0.1