Netlify adapter (#2879)

* Netlify adapter

* Remove package.json export that doesnt exist

* Fix out path

* Make netlifyFunctions the default

* Make the dist configurable

* Add an export for the functions

* Append of the file exists
This commit is contained in:
Matthew Phillips 2022-03-25 12:08:02 -04:00 committed by GitHub
parent df8fac50b9
commit 80034c6cbc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 290 additions and 36 deletions

View file

@ -0,0 +1,18 @@
---
'astro': patch
'@astrojs/netlify': patch
'@astrojs/node': patch
---
Netlify Adapter
This change adds a Netlify adapter that uses Netlify Functions. You can use it like so:
```js
import { defineConfig } from 'astro/config';
import netlify from '@astrojs/netlify';
export default defineConfig({
adapter: netlify()
});
```

View file

@ -15,6 +15,9 @@
"types": "./dist/types/@types/astro.d.ts", "types": "./dist/types/@types/astro.d.ts",
"typesVersions": { "typesVersions": {
"*": { "*": {
"app": [
"./dist/types/core/app/index"
],
"app/*": [ "app/*": [
"./dist/types/core/app/*" "./dist/types/core/app/*"
] ]

View file

@ -39,6 +39,9 @@ export interface CLIFlags {
} }
export interface BuildConfig { export interface BuildConfig {
client: URL;
server: URL;
serverEntry: string;
staticMode: boolean | undefined; staticMode: boolean | undefined;
} }
@ -617,6 +620,7 @@ export interface AstroAdapter {
name: string; name: string;
serverEntrypoint?: string; serverEntrypoint?: string;
exports?: string[]; exports?: string[];
args?: any;
} }
export interface EndpointOutput<Output extends Body = Body> { export interface EndpointOutput<Output extends Body = Body> {
@ -670,7 +674,7 @@ export interface AstroIntegration {
'astro:server:start'?: (options: { address: AddressInfo }) => void | Promise<void>; 'astro:server:start'?: (options: { address: AddressInfo }) => void | Promise<void>;
'astro:server:done'?: () => void | Promise<void>; 'astro:server:done'?: () => void | Promise<void>;
'astro:build:start'?: (options: { buildConfig: BuildConfig }) => void | Promise<void>; 'astro:build:start'?: (options: { buildConfig: BuildConfig }) => void | Promise<void>;
'astro:build:done'?: (options: { pages: { pathname: string }[]; dir: URL }) => void | Promise<void>; 'astro:build:done'?: (options: { pages: { pathname: string }[]; dir: URL; routes: RouteData[] }) => void | Promise<void>;
}; };
} }

View file

@ -27,3 +27,5 @@ export interface SSRManifest {
export type SerializedSSRManifest = Omit<SSRManifest, 'routes'> & { export type SerializedSSRManifest = Omit<SSRManifest, 'routes'> & {
routes: SerializedRouteInfo[]; routes: SerializedRouteInfo[];
}; };
export type AdapterCreateExports<T = any> = (manifest: SSRManifest, args?: T) => Record<string, any>;

View file

@ -1,5 +1,4 @@
import type { AstroConfig, RouteType } from '../../@types/astro'; import type { AstroConfig, RouteType } from '../../@types/astro';
import type { StaticBuildOptions } from './types';
import npath from 'path'; import npath from 'path';
import { appendForwardSlash } from '../../core/path.js'; import { appendForwardSlash } from '../../core/path.js';
@ -9,18 +8,6 @@ export function getOutRoot(astroConfig: AstroConfig): URL {
return new URL('./', astroConfig.dist); return new URL('./', astroConfig.dist);
} }
export function getServerRoot(astroConfig: AstroConfig): URL {
const rootFolder = getOutRoot(astroConfig);
const serverFolder = new URL('./server/', rootFolder);
return serverFolder;
}
export function getClientRoot(astroConfig: AstroConfig): URL {
const rootFolder = getOutRoot(astroConfig);
const serverFolder = new URL('./client/', rootFolder);
return serverFolder;
}
export function getOutFolder(astroConfig: AstroConfig, pathname: string, routeType: RouteType): URL { export function getOutFolder(astroConfig: AstroConfig, pathname: string, routeType: RouteType): URL {
const outRoot = getOutRoot(astroConfig); const outRoot = getOutRoot(astroConfig);

View file

@ -13,7 +13,7 @@ import { BEFORE_HYDRATION_SCRIPT_ID } from '../../vite-plugin-scripts/index.js';
import { call as callEndpoint } from '../endpoint/index.js'; import { call as callEndpoint } from '../endpoint/index.js';
import { render } from '../render/core.js'; import { render } from '../render/core.js';
import { createLinkStylesheetElementSet, createModuleScriptElementWithSrcSet } from '../render/ssr-element.js'; import { createLinkStylesheetElementSet, createModuleScriptElementWithSrcSet } from '../render/ssr-element.js';
import { getOutFile, getOutRoot, getOutFolder, getServerRoot } from './common.js'; import { getOutFile, getOutRoot, getOutFolder } from './common.js';
import { getPageDataByComponent, eachPageData } from './internal.js'; import { getPageDataByComponent, eachPageData } from './internal.js';
import { bgMagenta, black, cyan, dim, magenta } from 'kleur/colors'; import { bgMagenta, black, cyan, dim, magenta } from 'kleur/colors';
import { getTimeStat } from './util.js'; import { getTimeStat } from './util.js';
@ -70,8 +70,9 @@ export async function generatePages(result: RollupOutput, opts: StaticBuildOptio
info(opts.logging, null, `\n${bgMagenta(black(' generating static routes '))}\n`); info(opts.logging, null, `\n${bgMagenta(black(' generating static routes '))}\n`);
const ssr = !!opts.astroConfig._ctx.adapter?.serverEntrypoint; const ssr = !!opts.astroConfig._ctx.adapter?.serverEntrypoint;
const outFolder = ssr ? getServerRoot(opts.astroConfig) : getOutRoot(opts.astroConfig); const serverEntry = opts.buildConfig.serverEntry;
const ssrEntryURL = new URL(`./entry.mjs?time=${Date.now()}`, outFolder); const outFolder = ssr ? opts.buildConfig.server : opts.astroConfig.dist;
const ssrEntryURL = new URL('./' + serverEntry + `?time=${Date.now()}`, outFolder);
const ssrEntry = await import(ssrEntryURL.toString()); const ssrEntry = await import(ssrEntryURL.toString());
for (const pageData of eachPageData(internals)) { for (const pageData of eachPageData(internals)) {

View file

@ -7,7 +7,7 @@ import { apply as applyPolyfill } from '../polyfill.js';
import { performance } from 'perf_hooks'; import { performance } from 'perf_hooks';
import * as vite from 'vite'; import * as vite from 'vite';
import { createVite, ViteConfigWithSSR } from '../create-vite.js'; import { createVite, ViteConfigWithSSR } from '../create-vite.js';
import { debug, defaultLogOptions, info, levels, timerMessage, warn } from '../logger.js'; import { debug, defaultLogOptions, info, levels, timerMessage, warn, warnIfUsingExperimentalSSR } from '../logger.js';
import { createRouteManifest } from '../routing/index.js'; import { createRouteManifest } from '../routing/index.js';
import { generateSitemap } from '../render/sitemap.js'; import { generateSitemap } from '../render/sitemap.js';
import { collectPagesData } from './page-data.js'; import { collectPagesData } from './page-data.js';
@ -73,11 +73,17 @@ class AstroBuilder {
{ astroConfig: this.config, logging, mode: 'build' } { astroConfig: this.config, logging, mode: 'build' }
); );
await runHookConfigDone({ config: this.config }); await runHookConfigDone({ config: this.config });
warnIfUsingExperimentalSSR(logging, this.config);
this.viteConfig = viteConfig; this.viteConfig = viteConfig;
const viteServer = await vite.createServer(viteConfig); const viteServer = await vite.createServer(viteConfig);
this.viteServer = viteServer; this.viteServer = viteServer;
debug('build', timerMessage('Vite started', timer.viteStart)); debug('build', timerMessage('Vite started', timer.viteStart));
const buildConfig: BuildConfig = { staticMode: undefined }; const buildConfig: BuildConfig = {
client: new URL('./client/', this.config.dist),
server: new URL('./server/', this.config.dist),
serverEntry: 'entry.mjs',
staticMode: undefined
};
await runHookBuildStart({ config: this.config, buildConfig }); await runHookBuildStart({ config: this.config, buildConfig });
info(this.logging, 'build', 'Collecting page data...'); info(this.logging, 'build', 'Collecting page data...');
@ -167,7 +173,7 @@ class AstroBuilder {
// You're done! Time to clean up. // You're done! Time to clean up.
await viteServer.close(); await viteServer.close();
await runHookBuildDone({ config: this.config, pages: pageNames }); await runHookBuildDone({ config: this.config, pages: pageNames, routes: Object.values(allPages).map(pd => pd.route) });
if (logging.level && levels[logging.level] <= levels['info']) { if (logging.level && levels[logging.level] <= levels['info']) {
const buildMode = this.config.buildOptions.experimentalSsr ? 'ssr' : 'static'; const buildMode = this.config.buildOptions.experimentalSsr ? 'ssr' : 'static';

View file

@ -19,7 +19,6 @@ import { vitePluginSSR } from './vite-plugin-ssr.js';
import { vitePluginPages } from './vite-plugin-pages.js'; import { vitePluginPages } from './vite-plugin-pages.js';
import { generatePages } from './generate.js'; import { generatePages } from './generate.js';
import { trackPageData } from './internal.js'; import { trackPageData } from './internal.js';
import { getClientRoot, getServerRoot, getOutRoot } from './common.js';
import { isBuildingToSSR } from '../util.js'; import { isBuildingToSSR } from '../util.js';
import { getTimeStat } from './util.js'; import { getTimeStat } from './util.js';
@ -114,7 +113,7 @@ export async function staticBuild(opts: StaticBuildOptions) {
async function ssrBuild(opts: StaticBuildOptions, internals: BuildInternals, input: Set<string>) { async function ssrBuild(opts: StaticBuildOptions, internals: BuildInternals, input: Set<string>) {
const { astroConfig, viteConfig } = opts; const { astroConfig, viteConfig } = opts;
const ssr = astroConfig.buildOptions.experimentalSsr; const ssr = astroConfig.buildOptions.experimentalSsr;
const out = ssr ? getServerRoot(astroConfig) : getOutRoot(astroConfig); const out = ssr ? opts.buildConfig.server : astroConfig.dist;
// TODO: use vite.mergeConfig() here? // TODO: use vite.mergeConfig() here?
return await vite.build({ return await vite.build({
logLevel: 'error', logLevel: 'error',
@ -130,9 +129,10 @@ async function ssrBuild(opts: StaticBuildOptions, internals: BuildInternals, inp
input: [], input: [],
output: { output: {
format: 'esm', format: 'esm',
entryFileNames: 'entry.mjs', entryFileNames: opts.buildConfig.serverEntry,
chunkFileNames: 'chunks/chunk.[hash].mjs', chunkFileNames: 'chunks/chunk.[hash].mjs',
assetFileNames: 'assets/asset.[hash][extname]', assetFileNames: 'assets/asset.[hash][extname]',
inlineDynamicImports: true,
}, },
}, },
// must match an esbuild target // must match an esbuild target
@ -170,12 +170,11 @@ async function clientBuild(opts: StaticBuildOptions, internals: BuildInternals,
return null; return null;
} }
const out = astroConfig.buildOptions.experimentalSsr ? getClientRoot(astroConfig) : getOutRoot(astroConfig);
// TODO: use vite.mergeConfig() here? // TODO: use vite.mergeConfig() here?
info(opts.logging, null, `\n${bgGreen(black(' building resources '))}\n`); info(opts.logging, null, `\n${bgGreen(black(' building resources '))}\n`);
const out = isBuildingToSSR(astroConfig) ? opts.buildConfig.client : astroConfig.dist;
const buildResult = await vite.build({ const buildResult = await vite.build({
logLevel: 'info', logLevel: 'info',
mode: 'production', mode: 'production',
@ -229,9 +228,8 @@ async function cleanSsrOutput(opts: StaticBuildOptions) {
async function ssrMoveAssets(opts: StaticBuildOptions) { async function ssrMoveAssets(opts: StaticBuildOptions) {
info(opts.logging, 'build', 'Rearranging server assets...'); info(opts.logging, 'build', 'Rearranging server assets...');
const { astroConfig } = opts; const serverRoot = opts.buildConfig.staticMode ? opts.buildConfig.client : opts.buildConfig.server;
const serverRoot = getServerRoot(astroConfig); const clientRoot = opts.buildConfig.client;
const clientRoot = getClientRoot(astroConfig);
const serverAssets = new URL('./assets/', serverRoot); const serverAssets = new URL('./assets/', serverRoot);
const clientAssets = new URL('./assets/', clientRoot); const clientAssets = new URL('./assets/', clientRoot);
const files = await glob('assets/**/*', { const files = await glob('assets/**/*', {

View file

@ -35,23 +35,24 @@ const _manifest = Object.assign(_deserializeManifest('${manifestReplace}'), {
pageMap: _main.pageMap, pageMap: _main.pageMap,
renderers: _main.renderers renderers: _main.renderers
}); });
const _args = ${adapter.args ? JSON.stringify(adapter.args) : 'undefined'};
${ ${
adapter.exports adapter.exports
? `const _exports = adapter.createExports(_manifest); ? `const _exports = adapter.createExports(_manifest, _args);
${adapter.exports.map((name) => `export const ${name} = _exports['${name}'];`).join('\n')} ${adapter.exports.map((name) => `export const ${name} = _exports['${name}'];`).join('\n')}
` `
: '' : ''
} }
const _start = 'start'; const _start = 'start';
if(_start in adapter) { if(_start in adapter) {
adapter[_start](_manifest); adapter[_start](_manifest, _args);
}`; }`;
} }
return void 0; return void 0;
}, },
generateBundle(opts, bundle) { generateBundle(_opts, bundle) {
const manifest = buildManifest(buildOpts, internals); const manifest = buildManifest(buildOpts, internals);
for (const [_chunkName, chunk] of Object.entries(bundle)) { for (const [_chunkName, chunk] of Object.entries(bundle)) {

View file

@ -4,7 +4,7 @@ import * as vite from 'vite';
import type { AstroConfig } from '../../@types/astro'; import type { AstroConfig } from '../../@types/astro';
import { runHookConfigDone, runHookConfigSetup, runHookServerDone, runHookServerSetup, runHookServerStart } from '../../integrations/index.js'; import { runHookConfigDone, runHookConfigSetup, runHookServerDone, runHookServerSetup, runHookServerStart } from '../../integrations/index.js';
import { createVite } from '../create-vite.js'; import { createVite } from '../create-vite.js';
import { defaultLogOptions, info, LogOptions, warn } from '../logger.js'; import { defaultLogOptions, info, LogOptions, warn, warnIfUsingExperimentalSSR } from '../logger.js';
import * as msg from '../messages.js'; import * as msg from '../messages.js';
import { apply as applyPolyfill } from '../polyfill.js'; import { apply as applyPolyfill } from '../polyfill.js';
import { getResolvedHostForVite } from './util.js'; import { getResolvedHostForVite } from './util.js';
@ -32,6 +32,7 @@ export default async function dev(config: AstroConfig, options: DevOptions = { l
{ astroConfig: config, logging: options.logging, mode: 'dev' } { astroConfig: config, logging: options.logging, mode: 'dev' }
); );
await runHookConfigDone({ config }); await runHookConfigDone({ config });
warnIfUsingExperimentalSSR(options.logging, config);
const viteServer = await vite.createServer(viteConfig); const viteServer = await vite.createServer(viteConfig);
runHookServerSetup({ config, server: viteServer }); runHookServerSetup({ config, server: viteServer });
await viteServer.listen(config.devOptions.port); await viteServer.listen(config.devOptions.port);

View file

@ -1,3 +1,4 @@
import type { AstroConfig } from '../@types/astro';
import { bold, cyan, dim, red, yellow, reset } from 'kleur/colors'; import { bold, cyan, dim, red, yellow, reset } from 'kleur/colors';
import { performance } from 'perf_hooks'; import { performance } from 'perf_hooks';
import { Writable } from 'stream'; import { Writable } from 'stream';
@ -5,6 +6,7 @@ import stringWidth from 'string-width';
import * as readline from 'readline'; import * as readline from 'readline';
import debugPackage from 'debug'; import debugPackage from 'debug';
import { format as utilFormat } from 'util'; import { format as utilFormat } from 'util';
import { isBuildingToSSR } from './util.js';
type ConsoleStream = Writable & { type ConsoleStream = Writable & {
fd: 1 | 2; fd: 1 | 2;
@ -211,3 +213,12 @@ export function timerMessage(message: string, startTime: number = performance.no
let timeDisplay = timeDiff < 750 ? `${Math.round(timeDiff)}ms` : `${(timeDiff / 1000).toFixed(1)}s`; let timeDisplay = timeDiff < 750 ? `${Math.round(timeDiff)}ms` : `${(timeDiff / 1000).toFixed(1)}s`;
return `${message} ${dim(timeDisplay)}`; return `${message} ${dim(timeDisplay)}`;
} }
/**
* A warning that SSR is experimental. Remove when we can.
*/
export function warnIfUsingExperimentalSSR(opts: LogOptions, config: AstroConfig) {
if(isBuildingToSSR(config)) {
warn(opts, 'warning', bold(`Warning:`), ` SSR support is still experimental and subject to API changes. If using in production pin your dependencies to prevent accidental breakage.`);
}
}

View file

@ -1,6 +1,6 @@
import type { AddressInfo } from 'net'; import type { AddressInfo } from 'net';
import type { ViteDevServer } from 'vite'; import type { ViteDevServer } from 'vite';
import { AstroConfig, AstroRenderer, BuildConfig } from '../@types/astro.js'; import { AstroConfig, AstroRenderer, BuildConfig, RouteData } from '../@types/astro.js';
import { mergeConfig } from '../core/config.js'; import { mergeConfig } from '../core/config.js';
import ssgAdapter from '../adapter-ssg/index.js'; import ssgAdapter from '../adapter-ssg/index.js';
@ -91,10 +91,10 @@ export async function runHookBuildStart({ config, buildConfig }: { config: Astro
} }
} }
export async function runHookBuildDone({ config, pages }: { config: AstroConfig; pages: string[] }) { export async function runHookBuildDone({ config, pages, routes }: { config: AstroConfig; pages: string[], routes: RouteData[] }) {
for (const integration of config.integrations) { for (const integration of config.integrations) {
if (integration.hooks['astro:build:done']) { if (integration.hooks['astro:build:done']) {
await integration.hooks['astro:build:done']({ pages: pages.map((p) => ({ pathname: p })), dir: config.dist }); await integration.hooks['astro:build:done']({ pages: pages.map((p) => ({ pathname: p })), dir: config.dist, routes });
} }
} }
} }

View file

@ -0,0 +1,34 @@
{
"name": "@astrojs/netlify",
"description": "Deploy your site to Netlify",
"version": "0.0.1",
"type": "module",
"types": "./dist/index.d.ts",
"author": "withastro",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/withastro/astro.git",
"directory": "packages/integrations/netlify"
},
"bugs": "https://github.com/withastro/astro/issues",
"homepage": "https://astro.build",
"exports": {
".": "./dist/index.js",
"./functions": "./dist/integration-functions.js",
"./netlify-functions.js": "./dist/netlify-functions.js",
"./package.json": "./package.json"
},
"scripts": {
"build": "astro-scripts build \"src/**/*.ts\" && tsc",
"dev": "astro-scripts dev \"src/**/*.ts\""
},
"dependencies": {
"@astrojs/webapi": "^0.11.0"
},
"devDependencies": {
"@netlify/functions": "^1.0.0",
"astro": "workspace:*",
"astro-scripts": "workspace:*"
}
}

View file

@ -0,0 +1,44 @@
# @astrojs/netlify
Deploy your server-side rendered (SSR) Astro app to [Netlify](https://www.netlify.com/).
Use this adapter in your Astro configuration file:
```js
import { defineConfig } from 'astro/config';
import netlify from '@astrojs/netlify/functions';
export default defineConfig({
adapter: netlify()
});
```
After you build your site the `netlify/` folder will contain [Netlify Functions](https://docs.netlify.com/functions/overview/) in the `netlify/functions/` folder.
Now you can deploy!
```shell
netlify deploy
```
## Configuration
The output folder is configuration with the `dist` property when creating the adapter.
```js
import { defineConfig } from 'astro/config';
import netlify from '@astrojs/netlify/functions';
export default defineConfig({
adapter: netlify({
dist: new URL('./dist/', import.meta.url)
})
});
```
And then point to the dist in your `netlify.toml`:
```toml
[functions]
directory = "dist/functions"
```

View file

@ -0,0 +1,64 @@
import type { AstroAdapter, AstroIntegration, AstroConfig } from 'astro';
import fs from 'fs';
export function getAdapter(site: string | undefined): AstroAdapter {
return {
name: '@astrojs/netlify',
serverEntrypoint: '@astrojs/netlify/netlify-functions.js',
exports: ['handler'],
args: { site }
};
}
interface NetlifyFunctionsOptions {
dist?: URL;
}
function netlifyFunctions({ dist }: NetlifyFunctionsOptions = {}): AstroIntegration {
let _config: AstroConfig;
let entryFile: string;
return {
name: '@astrojs/netlify',
hooks: {
'astro:config:setup': ({ config }) => {
if(dist) {
config.dist = dist;
} else {
config.dist = new URL('./netlify/', config.projectRoot);
}
},
'astro:config:done': ({ config, setAdapter }) => {
setAdapter(getAdapter(config.buildOptions.site));
_config = config;
},
'astro:build:start': async({ buildConfig }) => {
entryFile = buildConfig.serverEntry.replace(/\.m?js/, '');
buildConfig.client = _config.dist;
buildConfig.server = new URL('./functions/', _config.dist);
},
'astro:build:done': async ({ routes, dir }) => {
const _redirectsURL = new URL('./_redirects', dir);
// Create the redirects file that is used for routing.
let _redirects = '';
for(const route of routes) {
if(route.pathname) {
_redirects += `
${route.pathname} /.netlify/functions/${entryFile} 200`
}
}
if(fs.existsSync(_redirects)) {
await fs.promises.appendFile(_redirectsURL, _redirects, 'utf-8');
} else {
await fs.promises.writeFile(_redirectsURL, _redirects, 'utf-8');
}
}
},
};
}
export {
netlifyFunctions,
netlifyFunctions as default
};

View file

@ -0,0 +1,3 @@
export {
netlifyFunctions as default
} from './index.js';

View file

@ -0,0 +1,43 @@
import { SSRManifest } from 'astro';
import type { Handler } from "@netlify/functions";
import { App } from 'astro/app';
import { polyfill } from '@astrojs/webapi';
polyfill(globalThis, {
exclude: 'window document',
});
interface Args {
site?: string;
}
export const createExports = (manifest: SSRManifest, args: Args) => {
const app = new App(manifest);
const site = new URL(args.site ?? `https://netlify.com`);
const handler: Handler = async (event) => {
const headers = new Headers(event.headers as any);
const request = new Request(new URL(event.path, site).toString(), {
method: event.httpMethod,
headers
});
if(!app.match(request)) {
return {
statusCode: 404,
body: 'Not found'
};
}
const response = await app.render(request);
const body = await response.text();
return {
statusCode: 200,
headers: Object.fromEntries(response.headers.entries()),
body
};
}
return { handler };
};

View file

@ -0,0 +1,10 @@
{
"extends": "../../../tsconfig.base.json",
"include": ["src"],
"compilerOptions": {
"allowJs": true,
"module": "ES2020",
"outDir": "./dist",
"target": "ES2020"
}
}

View file

@ -1177,6 +1177,19 @@ importers:
astro: link:../../astro astro: link:../../astro
astro-scripts: link:../../../scripts astro-scripts: link:../../../scripts
packages/integrations/netlify:
specifiers:
'@astrojs/webapi': ^0.11.0
'@netlify/functions': ^1.0.0
astro: workspace:*
astro-scripts: workspace:*
dependencies:
'@astrojs/webapi': link:../../webapi
devDependencies:
'@netlify/functions': 1.0.0
astro: link:../../astro
astro-scripts: link:../../../scripts
packages/integrations/node: packages/integrations/node:
specifiers: specifiers:
'@astrojs/webapi': ^0.11.0 '@astrojs/webapi': ^0.11.0
@ -3413,6 +3426,13 @@ packages:
vue: 3.2.31 vue: 3.2.31
dev: false dev: false
/@netlify/functions/1.0.0:
resolution: {integrity: sha512-7fnJv3vr8uyyyOYPChwoec6MjzsCw1CoRUO2DhQ1BD6bOyJRlD4DUaOOGlMILB2LCT8P24p5LexEGx8AJb7xdA==}
engines: {node: '>=8.3.0'}
dependencies:
is-promise: 4.0.0
dev: true
/@nodelib/fs.scandir/2.1.5: /@nodelib/fs.scandir/2.1.5:
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
engines: {node: '>= 8'} engines: {node: '>= 8'}
@ -7002,6 +7022,10 @@ packages:
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
dev: true dev: true
/is-promise/4.0.0:
resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==}
dev: true
/is-regex/1.1.4: /is-regex/1.1.4:
resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==} resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}