Compare commits
93 Commits
now@16.6.0
...
@now/pytho
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
502aad7c2b | ||
|
|
b49afb61a6 | ||
|
|
d380902ad3 | ||
|
|
ffaed62094 | ||
|
|
b0adeb68fe | ||
|
|
2372832654 | ||
|
|
e6a9586b7e | ||
|
|
9687415eed | ||
|
|
49b375ed6a | ||
|
|
906dea096e | ||
|
|
3225a83084 | ||
|
|
87794cfcc5 | ||
|
|
79ad0ce0c4 | ||
|
|
fda5987465 | ||
|
|
5cfdd5a6b2 | ||
|
|
bd6e0f9f93 | ||
|
|
aa8d002309 | ||
|
|
42ce9aca86 | ||
|
|
156f596189 | ||
|
|
8acfd5bf71 | ||
|
|
76c99ebb28 | ||
|
|
5fb119b99c | ||
|
|
99b766d9cb | ||
|
|
c207cf9b40 | ||
|
|
dd00ac4621 | ||
|
|
3d18a067a0 | ||
|
|
c48571a799 | ||
|
|
6eeb6983d9 | ||
|
|
aee33f040d | ||
|
|
b100677b3b | ||
|
|
9241b3ae2f | ||
|
|
1088da6871 | ||
|
|
f7b4dd4458 | ||
|
|
fb85b6b27a | ||
|
|
2e5e9b9a6f | ||
|
|
d3cc306e5b | ||
|
|
d6c6a2a271 | ||
|
|
6171a58ae3 | ||
|
|
5a6d1a135f | ||
|
|
68deab9007 | ||
|
|
d6114e2bef | ||
|
|
5fdc55f3fb | ||
|
|
751b166536 | ||
|
|
6ffc8d97f4 | ||
|
|
67a80d6b83 | ||
|
|
934cf772bc | ||
|
|
b01a24afdb | ||
|
|
0ad75b52bf | ||
|
|
050772e78a | ||
|
|
7c05dc1420 | ||
|
|
bdd25ac727 | ||
|
|
3a27328828 | ||
|
|
c076a5620f | ||
|
|
2bd8ef9eed | ||
|
|
500014f2fc | ||
|
|
17687e9bcd | ||
|
|
90354e9fe7 | ||
|
|
6236631beb | ||
|
|
75aefdddd6 | ||
|
|
566d82e873 | ||
|
|
44ae0b654e | ||
|
|
d8cfaae596 | ||
|
|
a40e0f21ee | ||
|
|
ac1f506c98 | ||
|
|
68d5bdcf3d | ||
|
|
beb51f8c67 | ||
|
|
b881cb7111 | ||
|
|
d83bc59257 | ||
|
|
5be9f297de | ||
|
|
51d440431e | ||
|
|
7cf061122c | ||
|
|
1254368505 | ||
|
|
9d4b830c5f | ||
|
|
37401b4363 | ||
|
|
10fe08e14f | ||
|
|
0ecdb35d50 | ||
|
|
caee8fe9ef | ||
|
|
7d92c27b2d | ||
|
|
701eabbaba | ||
|
|
e74a1b2d1a | ||
|
|
e087b02333 | ||
|
|
eea7f902b5 | ||
|
|
db7583201b | ||
|
|
023001a8b1 | ||
|
|
4ff8ab2435 | ||
|
|
d2cccbfce6 | ||
|
|
970e6c400c | ||
|
|
b4cb7345a1 | ||
|
|
7e75d8c1a3 | ||
|
|
a4ea551160 | ||
|
|
f56ad447a0 | ||
|
|
7656422057 | ||
|
|
afa2231add |
@@ -340,6 +340,10 @@ jobs:
|
||||
- run:
|
||||
name: Running Unit Tests
|
||||
command: yarn test-unit --clean false
|
||||
- persist_to_workspace:
|
||||
root: .
|
||||
paths:
|
||||
- packages/now-cli/.nyc_output
|
||||
|
||||
coverage:
|
||||
docker:
|
||||
@@ -349,12 +353,6 @@ jobs:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
at: .
|
||||
- run:
|
||||
name: Compiling `now dev` HTML error templates
|
||||
command: node packages/now-cli/scripts/compile-templates.js
|
||||
- run:
|
||||
name: Run unit tests
|
||||
command: yarn workspace now run test-unit
|
||||
- run:
|
||||
name: Run coverage report
|
||||
command: yarn workspace now run coverage
|
||||
|
||||
2
.gitignore
vendored
@@ -1,6 +1,7 @@
|
||||
node_modules
|
||||
package-lock.json
|
||||
dist
|
||||
.vscode
|
||||
npm-debug.log
|
||||
yarn-error.log
|
||||
.nyc_output
|
||||
@@ -18,3 +19,4 @@ packages/now-cli/test/dev/fixtures/08-hugo/hugo
|
||||
packages/now-cli/test/dev/fixtures/**/dist
|
||||
packages/now-cli/test/dev/fixtures/**/public
|
||||
packages/now-cli/test/fixtures/integration
|
||||
.DS_Store
|
||||
|
||||
@@ -6,7 +6,7 @@ A Runtime is an npm module that exposes a `build` function and optionally an `an
|
||||
Official Runtimes are published to [npmjs.com](https://npmjs.com) as a package and referenced in the `use` property of the `now.json` configuration file.
|
||||
However, the `use` property will work with any [npm install argument](https://docs.npmjs.com/cli/install) such as a git repo url which is useful for testing your Runtime.
|
||||
|
||||
See the [Runtimes Documentation](https://zeit.co/docs/v2/advanced/runtimes) to view example usage.
|
||||
See the [Runtimes Documentation](https://zeit.co/docs/runtimes) to view example usage.
|
||||
|
||||
## Runtime Exports
|
||||
|
||||
@@ -285,14 +285,13 @@ This is an abstract enumeration type that is implemented by one of the following
|
||||
|
||||
- `nodejs12.x`
|
||||
- `nodejs10.x`
|
||||
- `nodejs8.10`
|
||||
- `go1.x`
|
||||
- `java-1.8.0-openjdk`
|
||||
- `java11`
|
||||
- `python3.8`
|
||||
- `python3.6`
|
||||
- `python2.7`
|
||||
- `dotnetcore2.1`
|
||||
- `dotnetcore2.0`
|
||||
- `dotnetcore1.0`
|
||||
- `ruby2.5`
|
||||
- `provided`
|
||||
|
||||
## JavaScript API
|
||||
|
||||
|
||||
23
changelog.js
@@ -12,12 +12,31 @@ if (!commit) {
|
||||
throw new Error('Unable to find last publish commit');
|
||||
}
|
||||
|
||||
const log = execSync(`git log --pretty=format:"- %s [%an]" ${commit}...HEAD`)
|
||||
const log =
|
||||
execSync(`git log --pretty=format:"- %s [%an]" ${commit}...HEAD`)
|
||||
.toString()
|
||||
.trim()
|
||||
.split('\n')
|
||||
.filter(line => !line.startsWith('- Publish Canary '))
|
||||
.join('\n');
|
||||
.join('\n') || 'NO CHANGES DETECTED';
|
||||
|
||||
console.log(`Changes since the last Stable release (${commit.slice(0, 7)}):`);
|
||||
console.log(`\n${log}\n`);
|
||||
|
||||
const pkgs =
|
||||
Array.from(
|
||||
new Set(
|
||||
execSync(`git diff --name-only ${commit}...HEAD`)
|
||||
.toString()
|
||||
.trim()
|
||||
.split('\n')
|
||||
.filter(line => line.startsWith('packages/'))
|
||||
.map(line => line.split('/')[1])
|
||||
.map(pkgName => require(`./packages/${pkgName}/package.json`).name)
|
||||
)
|
||||
).join(',') || 'now';
|
||||
|
||||
console.log('To publish a stable release, execute the following:');
|
||||
console.log(
|
||||
`\ngit pull && lerna version --message 'Publish Stable' --exact --force-publish=${pkgs}\n`
|
||||
);
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
"scripts": {
|
||||
"lerna": "lerna",
|
||||
"bootstrap": "lerna bootstrap",
|
||||
"publish-stable": "git pull && lerna version --message 'Publish Stable' --exact",
|
||||
"publish-stable": "echo 'Run `yarn changelog` for instructions'",
|
||||
"publish-canary": "git pull && lerna version prerelease --preid canary --message 'Publish Canary' --exact",
|
||||
"publish-from-github": "./.circleci/publish.sh",
|
||||
"changelog": "node changelog.js",
|
||||
|
||||
249
packages/frameworks/frameworks.json
Normal file
@@ -0,0 +1,249 @@
|
||||
[
|
||||
{
|
||||
"name": "Next.js",
|
||||
"slug": "nextjs",
|
||||
"logo": "https://raw.githubusercontent.com/zeit/now/master/packages/frameworks/logos/next.svg",
|
||||
"tagline": "Next.js makes you productive with React instantly — whether you want to build static or dynamic sites. ",
|
||||
"website": "https://nextjs.org"
|
||||
},
|
||||
{
|
||||
"name": "Gatsby",
|
||||
"slug": "gatsby",
|
||||
"logo": "https://raw.githubusercontent.com/zeit/now/master/packages/frameworks/logos/gatsby.svg",
|
||||
"tagline": "Gatsby helps developers build blazing fast websites and apps with React.",
|
||||
"website": "https://gatsbyjs.org"
|
||||
},
|
||||
{
|
||||
"name": "Nuxt.js",
|
||||
"slug": "nuxtjs",
|
||||
"logo": "https://raw.githubusercontent.com/zeit/now/master/packages/frameworks/logos/nuxt.svg",
|
||||
"tagline": "Nuxt.js is the web comprehensive framework that lets you dream big with Vue.js.",
|
||||
"website": "https://nuxtjs.org"
|
||||
},
|
||||
{
|
||||
"name": "Create-React-App",
|
||||
"slug": "create-react-app",
|
||||
"logo": "https://raw.githubusercontent.com/zeit/now/master/packages/frameworks/logos/react.svg",
|
||||
"tagline": "Create React App allows you to get going with React in no time.",
|
||||
"website": "https://create-react-app.dev"
|
||||
},
|
||||
{
|
||||
"name": "Svelte",
|
||||
"slug": "svelte",
|
||||
"logo": "https://raw.githubusercontent.com/zeit/now/master/packages/frameworks/logos/svelte.svg",
|
||||
"tagline": "Svelte lets you write high performance reactive apps with significantly less boilerplate. ",
|
||||
"website": "https://svelte.dev"
|
||||
},
|
||||
{
|
||||
"name": "Vue.js",
|
||||
"slug": "vue",
|
||||
"logo": "https://raw.githubusercontent.com/zeit/now/master/packages/frameworks/logos/vue.svg",
|
||||
"tagline": "Vue.js is a versatile JavaScript framework that is as approachable as it is performant.",
|
||||
"website": "https://vuejs.org"
|
||||
},
|
||||
{
|
||||
"name": "Angular",
|
||||
"slug": "angular",
|
||||
"logo": "https://raw.githubusercontent.com/zeit/now/master/packages/frameworks/logos/angular.svg",
|
||||
"tagline": "Angular is a TypeScript-based cross-platform framework from Google.",
|
||||
"website": "https://angular.io"
|
||||
},
|
||||
{
|
||||
"name": "Hugo",
|
||||
"slug": "hugo",
|
||||
"logo": "https://raw.githubusercontent.com/zeit/now/master/packages/frameworks/logos/hugo.svg",
|
||||
"tagline": "Hugo is the world’s fastest framework for building websites, written in Go.",
|
||||
"website": "https://gohugo.io"
|
||||
},
|
||||
{
|
||||
"name": "Sapper",
|
||||
"slug": "sapper",
|
||||
"logo": "https://raw.githubusercontent.com/zeit/now/master/packages/frameworks/logos/svelte.svg",
|
||||
"tagline": "Sapper is a framework for building high-performance universal web apps with Svelte.",
|
||||
"website": "https://sapper.svelte.dev"
|
||||
},
|
||||
{
|
||||
"name": "UmiJS",
|
||||
"slug": "umijs",
|
||||
"logo": "https://raw.githubusercontent.com/zeit/now/master/packages/frameworks/logos/umi.svg",
|
||||
"tagline": "UmiJS is an extensible enterprise-level React application framework.",
|
||||
"website": "https://umijs.org"
|
||||
},
|
||||
{
|
||||
"name": "Vanilla",
|
||||
"slug": "vanilla",
|
||||
"logo": "https://raw.githubusercontent.com/zeit/now/master/packages/frameworks/logos/vanilla.svg",
|
||||
"tagline": "Love the original way of making websites?"
|
||||
},
|
||||
{
|
||||
"name": "Storybook",
|
||||
"slug": "storybook",
|
||||
"logo": "https://raw.githubusercontent.com/zeit/now/master/packages/frameworks/logos/storybook.svg",
|
||||
"tagline": "Storybook is an open source tool for developing UI components in isolation for React, Vue, and Angular.",
|
||||
"website": "https://storybook.js.org"
|
||||
},
|
||||
{
|
||||
"name": "Preact",
|
||||
"slug": "preact",
|
||||
"logo": "https://raw.githubusercontent.com/zeit/now/master/packages/frameworks/logos/preact.svg",
|
||||
"tagline": "Preact is a fast 3kB alternative to React with the same modern API.",
|
||||
"website": "https://preactjs.com"
|
||||
},
|
||||
{
|
||||
"name": "Docz",
|
||||
"slug": "docz",
|
||||
"logo": "https://raw.githubusercontent.com/zeit/now/master/packages/frameworks/logos/docz.svg",
|
||||
"tagline": "Docz makes it easy to write and publish beautiful interactive documentation for your code.",
|
||||
"website": "https://docz.site"
|
||||
},
|
||||
{
|
||||
"name": "mdx-deck",
|
||||
"slug": "mdx-deck",
|
||||
"tagline": "MDX Deck allows you to swiftly create React MDX-based presentation decks.",
|
||||
"website": "https://github.com/jxnblk/mdx-deck"
|
||||
},
|
||||
{
|
||||
"name": "Gridsome",
|
||||
"slug": "gridsome",
|
||||
"logo": "https://raw.githubusercontent.com/zeit/now/master/packages/frameworks/logos/gridsome.svg",
|
||||
"tagline": "Gridsome is a Vue.js-powered framework for building websites & apps that are fast by default."
|
||||
},
|
||||
{
|
||||
"name": "Aurelia",
|
||||
"slug": "aurelia",
|
||||
"logo": "https://raw.githubusercontent.com/zeit/now/master/packages/frameworks/logos/aurelia.svg",
|
||||
"tagline": "Aurelia is an all-in-one framework for building web, desktop, and mobile applications."
|
||||
},
|
||||
{
|
||||
"name": "Ember",
|
||||
"slug": "ember",
|
||||
"logo": "https://raw.githubusercontent.com/zeit/now/master/packages/frameworks/logos/ember.svg",
|
||||
"tagline": "Ember.js helps webapp developers be more productive out of the box."
|
||||
},
|
||||
{
|
||||
"name": "Docusaurus",
|
||||
"slug": "docusaurus",
|
||||
"logo": "https://raw.githubusercontent.com/zeit/now/master/packages/frameworks/logos/docusaurus.svg",
|
||||
"tagline": "Docusaurus makes it easy to maintain Open Source documentation websites."
|
||||
},
|
||||
{
|
||||
"name": "VuePress",
|
||||
"slug": "vuepress",
|
||||
"tagline": "VuePress is the performant way to create static sites with Vue.js."
|
||||
},
|
||||
{
|
||||
"name": "Charge.js",
|
||||
"slug": "charge",
|
||||
"logo": "https://raw.githubusercontent.com/zeit/now/master/packages/frameworks/logos/chargejs.svg",
|
||||
"tagline": "Charge is an opinionated, zero-config static site generator written in JavaScript."
|
||||
},
|
||||
{
|
||||
"name": "Riot.js",
|
||||
"slug": "riot",
|
||||
"tagline": "Riot.js lets you build user interfaces with custom tags using simple and enjoyable syntax."
|
||||
},
|
||||
{
|
||||
"name": "Jekyll",
|
||||
"slug": "jekyll",
|
||||
"logo": "https://raw.githubusercontent.com/zeit/now/master/packages/frameworks/logos/jekyll.svg",
|
||||
"tagline": "Jekyll makes it super easy to transform your plain text into static websites and blogs."
|
||||
},
|
||||
{
|
||||
"name": "Marko.js",
|
||||
"slug": "marko",
|
||||
"tagline": "Marko is a super fast UI library that makes building web apps fun."
|
||||
},
|
||||
{
|
||||
"name": "Hexo",
|
||||
"slug": "hexo",
|
||||
"logo": "https://raw.githubusercontent.com/zeit/now/master/packages/frameworks/logos/hexo.svg",
|
||||
"tagline": "Hexo is a fast, simple & powerful blog framework powered by Node.js."
|
||||
},
|
||||
{
|
||||
"name": "Mithril.js",
|
||||
"slug": "mithril",
|
||||
"logo": "https://raw.githubusercontent.com/zeit/now/master/packages/frameworks/logos/mithriljs.svg",
|
||||
"tagline": "Mithril is a lightweight modern web framework for that makes it easy to build SPAs."
|
||||
},
|
||||
{
|
||||
"name": "Metalsmith",
|
||||
"slug": "metalsmith",
|
||||
"tagline": "Metalsmith is an extremely simple, extendable static site generator."
|
||||
},
|
||||
{
|
||||
"name": "HyperApp",
|
||||
"slug": "hyperapp",
|
||||
"logo": "https://raw.githubusercontent.com/zeit/now/master/packages/frameworks/logos/hyperapp.svg",
|
||||
"tagline": "HyperApp is a low-footprint framework for building web interfaces without a learning curve."
|
||||
},
|
||||
{
|
||||
"name": "Polymer",
|
||||
"slug": "polymer",
|
||||
"logo": "https://raw.githubusercontent.com/zeit/now/master/packages/frameworks/logos/polymer.svg",
|
||||
"tagline": "Polymer is an open-source webapps library from Google, for building using Web Components."
|
||||
},
|
||||
{
|
||||
"name": "Brunch",
|
||||
"slug": "brunch",
|
||||
"logo": "https://raw.githubusercontent.com/zeit/now/master/packages/frameworks/logos/brunch.svg",
|
||||
"tagline": "Brunch is a fast and simple webapp build tool with seamless incremental compilation for rapid development."
|
||||
},
|
||||
{
|
||||
"name": "Saber",
|
||||
"slug": "saber",
|
||||
"logo": "https://raw.githubusercontent.com/zeit/now/master/packages/frameworks/logos/saber.svg",
|
||||
"tagline": "Saber is a framework for building static sites in Vue.js that supports data from any source."
|
||||
},
|
||||
{
|
||||
"name": "Eleventy",
|
||||
"slug": "eleventy",
|
||||
"logo": "https://raw.githubusercontent.com/zeit/now/master/packages/frameworks/logos/eleventy.svg",
|
||||
"tagline": "11ty is a simpler static site generator written in JavaScript, created to be an alternative to Jekyll."
|
||||
},
|
||||
{
|
||||
"name": "Zola",
|
||||
"slug": "zola",
|
||||
"logo": "https://raw.githubusercontent.com/zeit/now/master/packages/frameworks/logos/zola.svg",
|
||||
"tagline": "Zola is a one-stop static site engine for all of your static needs. "
|
||||
},
|
||||
{
|
||||
"name": "Pelican",
|
||||
"slug": "pelican",
|
||||
"tagline": "Pelican is a versatile static site generator, written in Python."
|
||||
},
|
||||
{
|
||||
"name": "MkDocs",
|
||||
"slug": "mkdocs",
|
||||
"logo": "https://raw.githubusercontent.com/zeit/now/master/packages/frameworks/logos/mkdocs.svg",
|
||||
"tagline": "MkDocs is a fast, simple and downright gorgeous static site generator that's geared towards building project documentation."
|
||||
},
|
||||
{
|
||||
"name": "Middleman",
|
||||
"slug": "middleman",
|
||||
"logo": "https://raw.githubusercontent.com/zeit/now/master/packages/frameworks/logos/middleman.svg",
|
||||
"tagline": "Middleman is a static site generator that uses all the shortcuts and tools in modern web development."
|
||||
},
|
||||
{
|
||||
"name": "Assemble",
|
||||
"slug": "assemble",
|
||||
"logo": "https://raw.githubusercontent.com/zeit/now/master/packages/frameworks/logos/assemble.svg",
|
||||
"tagline": "A static site generator for Grunt.js and Yeoman, Assemble makes it dead simple to build modular sites and blogs."
|
||||
},
|
||||
{
|
||||
"name": "Ionic React",
|
||||
"slug": "ionic-react",
|
||||
"logo": "https://raw.githubusercontent.com/zeit/now/master/packages/frameworks/logos/ionic-react.svg",
|
||||
"tagline": "Ionic React allows you to build mobile PWAs with React and the Ionic Framework."
|
||||
},
|
||||
{
|
||||
"name": "Stencil",
|
||||
"slug": "stencil",
|
||||
"logo": "https://raw.githubusercontent.com/zeit/now/master/packages/frameworks/logos/stencil.svg",
|
||||
"tagline": "Stencil is a powerful toolchain for building Progressive Web Apps and Design Systems."
|
||||
},
|
||||
{
|
||||
"name": "Foundation",
|
||||
"slug": "foundation",
|
||||
"tagline": "Foundation is the most advanced responsive front-end framework in the world."
|
||||
}
|
||||
]
|
||||
1
packages/frameworks/logos/angular.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg width="48" height="48" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M24 1L3 8.636l3.203 28.313L24 47l17.797-10.051L45 8.636 24 1z" fill="#DA0B36"/><path d="M24 1v5.106-.023V47l17.797-10.051L45 8.636 24 1z" fill="#C10933"/><path d="M24.022 6L11 36h4.855l2.618-6.713h11.054L32.145 36H37L24.022 6zm3.804 19.15H20.22l3.803-9.403 3.804 9.402z" fill="#fff"/></svg>
|
||||
|
After Width: | Height: | Size: 374 B |
4
packages/frameworks/logos/assemble.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 52 48">
|
||||
<path fill="#111" stroke="#190000" stroke-linecap="square" stroke-miterlimit="10" stroke-width=".5" d="M22.32 4.48h7.5l16.5 38.97h-8.55l-3.53-8.9H17.71l-3.63 8.75h-8.4L22.33 4.48h0zm3.53 9.68c.05.15 5.45 13.13 5.45 13.13H20.55s5.27-13.24 5.3-13.13z"/>
|
||||
<path fill="#fff" stroke="#190000" stroke-linecap="square" stroke-miterlimit="10" stroke-width=".5" d="M25.77 14l5.44 13.13H20.47S25.73 13.9 25.77 14z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 490 B |
61
packages/frameworks/logos/aurelia.svg
Normal file
@@ -0,0 +1,61 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" fill="none">
|
||||
<path fill="url(#paint0_linear)" d="M31.48 8.75l-4.44 2.99-4.58-6.9 4.44-2.99 4.58 6.9z"/>
|
||||
<path fill="url(#paint1_linear)" d="M35.09 29.48l7.58 11.45L33.64 47l-7.6-11.45-1.31-2 9.03-6.06 1.33 2z"/>
|
||||
<path fill="url(#paint2_linear)" d="M23.7 37.13l1.67 2.52-6.87 4.62-3-4.52 1.5-1 5.38-3.62 1.32 2z"/>
|
||||
<path fill="url(#paint3_linear)" d="M38.84 24.07l1.93-1.3 3 4.52-4.44 2.99-1.67-2.52 2.5-1.7-1.32-1.99zm-1.18 3.69l-1.33-2 2.51-1.69 1.33 2-2.51 1.69z"/>
|
||||
<path fill="url(#paint4_linear)" d="M7.7 24.72l-1.5 1.01-4.57-6.9 6.88-4.62 3.2 4.84-5.37 3.6 5.38-3.6 1.36 2.06-5.37 3.61z"/>
|
||||
<path fill="url(#paint5_linear)" d="M24.47 13.46l-9.04 6.07-1.37-2.06L6.55 6.13 15.58.06 23.1 11.4l1.37 2.06z"/>
|
||||
<path fill="url(#paint6_linear)" d="M29.55 10.05l-2.51 1.69-1.37-2.07-3.2-4.83 4.43-2.99 4.58 6.9-1.93 1.3z"/>
|
||||
<path fill="#714896" d="M18.33 40.74l-1.33-2 5.38-3.6 1.32 1.99-5.37 3.61z"/>
|
||||
<path fill="#6F4795" d="M37.66 27.76l-1.33-2 2.51-1.69 1.33 2-2.51 1.69z"/>
|
||||
<path fill="#88519F" d="M7.7 24.72l-1.36-2.06 5.38-3.61 1.36 2.06-5.37 3.61z"/>
|
||||
<path fill="#85509E" d="M27.04 11.74l-1.37-2.07L28.18 8l1.37 2.06-2.51 1.69z"/>
|
||||
<path fill="#8D166A" d="M35.09 29.48l-9.04 6.07-1.32-2 9.03-6.06 1.33 2z"/>
|
||||
<path fill="#A70D6F" d="M23.1 11.4l1.37 2.06-9.04 6.07-1.37-2.06 9.04-6.07z"/>
|
||||
<path fill="#9E61AD" d="M5.14 9.75l1.71 2.57-2.56 1.72-1.7-2.57 2.55-1.72z"/>
|
||||
<path fill="#8053A3" d="M14.36 40.6l1.7 2.57-2.55 1.72-1.71-2.57 2.56-1.72z"/>
|
||||
<path fill="url(#paint7_linear)" d="M7.63 43.19L.15 31.8l40-26.93 7.84 11.2L7.63 43.19z"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear" x1="-12.53" x2="47.68" y1="-9.38" y2="33.11" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#C06FBB"/>
|
||||
<stop offset="1" stop-color="#6E4D9B"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint1_linear" x1="37.82" x2="4.33" y1="43" y2="6.94" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#6E4D9B"/>
|
||||
<stop offset=".14" stop-color="#77327A"/>
|
||||
<stop offset=".29" stop-color="#B31777"/>
|
||||
<stop offset=".84" stop-color="#CD0F7E"/>
|
||||
<stop offset="1" stop-color="#ED2C89"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint2_linear" x1="-7.42" x2="35.06" y1="-28.24" y2="47.35" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#C06FBB"/>
|
||||
<stop offset="1" stop-color="#6E4D9B"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint3_linear" x1="-24.72" x2="44.39" y1="-12.24" y2="43.73" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#C06FBB"/>
|
||||
<stop offset="1" stop-color="#6E4D9B"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint4_linear" x1="-14.41" x2="49.9" y1="-12.4" y2="44.01" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#C06FBB"/>
|
||||
<stop offset="1" stop-color="#6E4D9B"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint5_linear" x1="40.79" x2="7.22" y1="43.89" y2="7.61" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#6E4D9B"/>
|
||||
<stop offset=".14" stop-color="#77327A"/>
|
||||
<stop offset=".29" stop-color="#B31777"/>
|
||||
<stop offset=".84" stop-color="#CD0F7E"/>
|
||||
<stop offset="1" stop-color="#ED2C89"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint6_linear" x1="-12.52" x2="48.68" y1="-11.86" y2="40.25" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#C06FBB"/>
|
||||
<stop offset="1" stop-color="#6E4D9B"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint7_linear" x1="5.65" x2="34.95" y1="39.23" y2="2.75" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#6E4D9B"/>
|
||||
<stop offset=".14" stop-color="#77327A"/>
|
||||
<stop offset=".53" stop-color="#B31777"/>
|
||||
<stop offset=".79" stop-color="#CD0F7E"/>
|
||||
<stop offset="1" stop-color="#ED2C89"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.7 KiB |
3
packages/frameworks/logos/brunch.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="49" height="48" fill="none">
|
||||
<path fill="#3F894A" d="M14.52 35.93a3.39 3.39 0 0 0-1.14-2.41 4.65 4.65 0 0 0-.1-.1l-.02-.01a7.6 7.6 0 0 1-2.14-5.4c0-3.08 1.37-11.87 1.67-11.87.23 0 .23 4.24.08 8.22-.1 2.77.3 3.86.96 3.86.88 0 1.02-2.79 1.11-5.92.08-2.57.3-6.14.48-6.16.28 0 .51 3.55.59 6.13.1 3.2.32 5.87 1.05 5.87.77 0 1-1.23.95-3.97-.07-3.34-.07-8.03.17-8.03.3 0 1.3 3.09 1.54 11.88a7.36 7.36 0 0 1-2.14 5.39h-.01l-.11.1a3.39 3.39 0 0 0-1.14 2.42 62.5 62.5 0 0 0-.13 4.62l6.78 6.78c.1-2.95.08-5.74-.05-7.84-.05-.79-.17-1.7-.3-2.43-.05-.23-.2-.44-.2-.63v-25.6c0-2.18 2.05-3.2 3.93 1.58 1.88 4.8 1.35 21.84 1.35 23.05 0 .26-.22.56-.59.86l-.4.29c-1.12.9-1.28 1.95-1.34 2.88-.14 2.26-.25 5.31-.28 8.51l9.34-9.34c-.02-1.1-.05-2.08-.1-2.9-.02-.32-.1-.64-.16-.98-.16-.95-1.35-1.74-2.03-2.43-1.33-1.33-2.19-3.58-2.19-6.11 0-4.11 2.28-9.32 5.1-9.32 2.8 0 5.08 5.2 5.08 9.32 0 2.53-.85 4.78-2.18 6.11-.69.7-1.87 1.48-2.04 2.43-.06.34-.14.66-.16.98l-.07 1.65 13.05-13.05L24.36 0 0 24.36 14.64 39c-.02-1.16-.06-2.2-.12-3.07z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
15
packages/frameworks/logos/chargejs.svg
Normal file
@@ -0,0 +1,15 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" fill="none">
|
||||
<path fill="url(#paint0_linear)" fill-rule="evenodd" d="M17.1 48l-16-9.4a1.87 1.87 0 0 1-.92-1.61l-.1-25.7A1.89 1.89 0 0 1 1 9.64L17.32.16l7.19 6.76 6.1-6.59 16.2 9.53a1.87 1.87 0 0 1 .93 1.6l.1 25.71a1.89 1.89 0 0 1-.92 1.63l-15.7 9.13-7.52-7.07-6.6 7.13V48z" clip-rule="evenodd" opacity=".9"/>
|
||||
<path fill="url(#paint1_linear)" fill-rule="evenodd" d="M44.44 13.39l.1 21.59a1.89 1.89 0 0 1-.92 1.63L25.27 47.29a1.85 1.85 0 0 1-1.87 0L4.9 36.38a1.87 1.87 0 0 1-.92-1.6l-.11-21.6a1.89 1.89 0 0 1 .93-1.63L23.15.88a1.85 1.85 0 0 1 1.87.01l18.5 10.89a1.87 1.87 0 0 1 .92 1.6v.01z" clip-rule="evenodd"/>
|
||||
<path fill="#fff" d="M32.14 19.09l-7.1 1.92.1-10.96-9.5 19.41 7.09-1.92-.11 10.96 9.52-19.41z"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear" x1="23.17" x2="31.2" y1="-3.24" y2="50.11" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#FE4386"/>
|
||||
<stop offset="1" stop-color="#FFBB94"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint1_linear" x1="24.59" x2="28.79" y1="2.21" y2="47.42" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#F8016B"/>
|
||||
<stop offset="1" stop-color="#FC716A"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
1
packages/frameworks/logos/docusaurus.svg
Normal file
|
After Width: | Height: | Size: 5.2 KiB |
11
packages/frameworks/logos/docz.svg
Normal file
@@ -0,0 +1,11 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="53" height="42" fill="none">
|
||||
<path fill="#E2D9C5" d="M17.92 4.81h31.96A4.3 4.3 0 0 0 45.52.44H13.56a4.3 4.3 0 0 1 4.36 4.37z"/>
|
||||
<path fill="#F2EBDA" d="M41.14 4.76A4.34 4.34 0 0 1 45.5.44H13.55a4.34 4.34 0 0 0-4.36 4.32v35.49h27.6c2.4 0 4.35-1.93 4.35-4.32V4.76z"/>
|
||||
<path fill="#E2D9C5" d="M40.44.44a4.36 4.36 0 0 0-4.37 4.35v31.38c0 2.4-1.96 4.35-4.38 4.35H37a4.36 4.36 0 0 0 4.37-4.35V4.8c0-2.4 1.96-4.35 4.38-4.35h-5.31z"/>
|
||||
<path fill="#E2D9C5" d="M32.53 35.92H.5c.03 2.68 1.97 4.6 4.37 4.6H36.9c-2.4 0-4.34-1.92-4.37-4.6z"/>
|
||||
<path fill="#DDA064" d="M30.19 28c.88-2.74 1.68-5.53 3.56-7.67 4.1-4.66 12.34-6.03 18.31-5.88a53 53 0 0 0-5.38 5.12c-2.12 2.28-6.74 5.3-8.96 5.99-3.46 1.06-5.06 1.24-7.53 2.44z"/>
|
||||
<path fill="#B57947" d="M30.19 28c.88-2.74 15.9-13.71 21.87-13.56a53.02 53.02 0 0 0-5.38 5.12c-2.12 2.28-6.74 5.32-8.96 6-3.46 1.06-5.06 1.23-7.53 2.44z"/>
|
||||
<path fill="#1F2D3D" d="M38.06 8.1c0-.37-.34-.66-.77-.66H20.9c-.42 0-.76.3-.76.65 0 .37.34.66.77.66h16.4c.42 0 .76-.3.76-.66zM13.94 8.75h2.75c.45 0 .81-.3.81-.66 0-.36-.36-.65-.81-.65h-2.75c-.45 0-.81.3-.81.65 0 .37.36.66.81.66zM13.9 31.06c-.43 0-.77.3-.77.66 0 .36.34.66.77.66h8.08c.42 0 .77-.3.77-.66 0-.36-.35-.66-.77-.66H13.9zM26.25 24.72c0-.36-.35-.66-.79-.66H13.91c-.43 0-.78.3-.78.66 0 .36.35.66.78.66h11.55c.44 0 .79-.3.79-.66zM31.94 16.84c0-.36-.36-.65-.8-.65H13.93c-.44 0-.8.3-.8.65 0 .37.36.66.8.66h17.23c.43 0 .79-.3.79-.66z"/>
|
||||
<path fill="#1F2D3D" d="M41.3 26.62a.77.77 0 0 0-.76.78v9.43c0 2-1.6 3.61-3.57 3.61h-.1c-2 0-3.54-1.66-3.56-3.88a.77.77 0 0 0-.77-.77H10.3V5.17c0-2 1.6-3.61 3.57-3.61h28.12l-.06.07-.08.09-.09.1-.07.08-.08.1-.07.1-.07.1-.07.1-.07.1-.06.1-.07.12-.06.1-.05.11-.06.12-.05.1c-.01.05-.03.08-.05.12l-.04.12-.05.12-.04.12-.04.11-.03.13-.04.12-.03.13-.02.12-.03.13-.02.12-.01.15-.02.1-.01.17v.1l-.01.26v6.91c0 .43.34.77.76.77.43 0 .77-.34.77-.77V6.2h7.91c.2 0 .4-.09.55-.23a.78.78 0 0 0 .22-.56C50.72 2.33 48.53 0 45.64 0H13.87a5.14 5.14 0 0 0-5.1 5.17v30.62h-8c-.2 0-.4.09-.55.23a.78.78 0 0 0-.22.56C.03 39.67 2.22 42 5.1 42h31.87c2.81 0 5.1-2.32 5.1-5.17V27.4a.77.77 0 0 0-.77-.78zm.81-21.99l.01-.07.02-.1.02-.07a3.53 3.53 0 0 1 .2-.65l.04-.07.04-.09.03-.06.04-.09.02-.03a3.72 3.72 0 0 1 .32-.48l.02-.02.08-.1.02-.02.1-.1v-.02l.1-.1.02-.01.1-.1h.01a3.46 3.46 0 0 1 1.85-.85v-.01l.16-.02H45.48l.08-.01h.09c1.76 0 3.16 1.28 3.49 3.09H42.1v-.02zm-37 35.81c-1.77 0-3.17-1.28-3.5-3.09h30.22a6 6 0 0 0 .03.24l.02.08a6 6 0 0 0 .03.15l.02.1.03.14.03.1a6.14 6.14 0 0 0 .18.56l.04.1.05.12.04.09.06.12.04.08.08.15.03.05.11.19c0 .02.02.03.03.04l.1.15a4.63 4.63 0 0 0 .13.18l.04.07.08.1.06.07a5.16 5.16 0 0 0 .15.17l.04.04H5.1z"/>
|
||||
<path fill="#1F2D3D" d="M52.94 14.35v-.03a.77.77 0 0 0-.04-.21v-.01-.01a.77.77 0 0 0-.05-.1v-.01a.78.78 0 0 0-.06-.08l-.01-.02a.78.78 0 0 0-.07-.07l-.01-.02a.8.8 0 0 0-.17-.12l-.02-.01a.79.79 0 0 0-.33-.08c-6.56-.17-15.11 1.4-19.44 6.29-1.89 2.14-2.76 4.86-3.6 7.49l-.22.64v.03l-1.33 4.67a.78.78 0 0 0 .76.99c.34 0 .65-.23.75-.57l1.23-4.33c1.5-.7 2.71-1.02 4.34-1.46.85-.23 1.81-.5 2.97-.85 2.52-.76 7.34-3.97 9.55-6.35a53.37 53.37 0 0 1 5.46-5.18.78.78 0 0 0 .15-.16l.02-.02a.77.77 0 0 0 .11-.32v-.08-.02zm-19.02 6.54c1.83-2.06 4.62-3.62 8.31-4.63 1-.27 2.04-.49 3.12-.67a60.73 60.73 0 0 0-6.83 3.95 62.64 62.64 0 0 0-6.81 5.16c.56-1.4 1.23-2.7 2.21-3.8zm12.12-1.82c-2.16 2.32-6.82 5.3-8.86 5.93-1.13.34-2.08.6-2.92.83-.75.2-1.44.38-2.12.6.3-.29.66-.61 1.08-.98 1.7-1.46 3.88-3.1 6.17-4.62 3.95-2.62 7.53-4.46 10.16-5.26a60.6 60.6 0 0 0-3.5 3.5z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.5 KiB |
1
packages/frameworks/logos/eleventy.svg
Normal file
|
After Width: | Height: | Size: 13 KiB |
1
packages/frameworks/logos/ember.svg
Normal file
|
After Width: | Height: | Size: 39 KiB |
1
packages/frameworks/logos/gatsby.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg width="48" height="48" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M42.857 24h-12v3.428h8.229c-1.2 5.143-4.972 9.43-9.943 11.143L9.429 18.857c2.057-6 7.885-10.286 14.571-10.286 5.143 0 9.771 2.572 12.686 6.515l2.571-2.229C35.83 8.23 30.343 5.143 24 5.143c-8.914 0-16.457 6.343-18.343 14.743l22.629 22.628c8.228-2.057 14.571-9.6 14.571-18.514zm-37.714.171c0 4.8 1.886 9.429 5.486 13.029 3.6 3.6 8.4 5.486 13.028 5.486L5.143 24.17z" fill="#fff"/><path d="M24 0C10.8 0 0 10.8 0 24s10.8 24 24 24 24-10.8 24-24S37.2 0 24 0zM10.629 37.371c-3.6-3.6-5.486-8.4-5.486-13.028l18.686 18.514c-4.8-.171-9.6-1.886-13.2-5.486zm17.485 4.972L5.657 19.886C7.543 11.486 15.086 5.143 24 5.143c6.343 0 11.829 3.086 15.257 7.714l-2.571 2.229C33.77 11.143 29.143 8.57 24 8.57c-6.686 0-12.343 4.286-14.571 10.286l19.714 19.714c4.971-1.714 8.743-6 9.943-11.142h-8.229V24h12c0 8.914-6.343 16.457-14.743 18.343z" fill="#639"/></svg>
|
||||
|
After Width: | Height: | Size: 921 B |
1
packages/frameworks/logos/gridsome.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg width="48" height="48" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M43.395 19.862c2.308-.11 4.35 1.68 4.55 3.99.863 11.174-9.364 23.692-23.775 24.092C12.025 48.244.013 38.521.013 23.82c0-2.317 1.915-4.195 4.225-4.195a4.19 4.19 0 0 1 4.184 4.195c0 9.67 7.776 15.928 15.542 15.737 9.56-.266 15.776-8.544 15.453-15.303a4.19 4.19 0 0 1 3.978-4.392z" fill="url(#paint0_linear)"/><path d="M32.944 24.145c0-2.372 1.935-4.295 4.321-4.295h6.298c2.387 0 4.382 1.923 4.382 4.295s-1.995 4.295-4.382 4.295h-6.298c-2.386 0-4.321-1.923-4.321-4.295zm-13.215.006a4.297 4.297 0 0 1 4.291-4.301 4.297 4.297 0 0 1 4.292 4.301 4.297 4.297 0 0 1-4.292 4.301 4.297 4.297 0 0 1-4.291-4.301z" fill="#00A672"/><path fill-rule="evenodd" clip-rule="evenodd" d="M28.193 4.055a4.19 4.19 0 0 1-4.006 4.365c-9.966.415-16.05 8.248-15.753 15.685.092 2.315-1.668 4.267-3.976 4.36-2.309.092-4.34-1.777-4.431-4.092C-.456 12.253 9.635.273 23.84.037a4.188 4.188 0 0 1 4.353 4.018z" fill="#00A672"/><defs><linearGradient id="paint0_linear" x1="24.005" y1="19.625" x2="24.005" y2="47.951" gradientUnits="userSpaceOnUse"><stop stop-color="#00583E"/><stop offset="1" stop-color="#00835C"/></linearGradient></defs></svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
1
packages/frameworks/logos/hexo.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg width="48" height="48" fill="none" xmlns="http://www.w3.org/2000/svg"><g clip-path="url(#clip0)"><path d="M23.829.005l-20.735 12 .002 23.964L23.878 47.92l20.736-12V11.97L23.829.004z" fill="#0E83CD"/><path d="M14.101 34.338l.002-10.362.002-10.363 1.882-1.101 1.804 1.067.019 4.235.02 4.234h12.05l.019-4.231.02-4.232 1.83-1.059 1.857 1.087V23.97l-.002 10.355-1.837 1.027-1.849-1.04-.02-4.21-.019-4.212H17.83l-.019 4.21.006 4.23-1.848 1.085-1.866-1.076z" fill="#fff"/></g><defs><clipPath id="clip0"><path fill="#fff" transform="translate(3)" d="M0 0H41.645V48H0z"/></clipPath></defs></svg>
|
||||
|
After Width: | Height: | Size: 591 B |
5
packages/frameworks/logos/hugo.svg
Normal file
|
After Width: | Height: | Size: 30 KiB |
4
packages/frameworks/logos/hyperapp.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" fill="none">
|
||||
<rect width="48" height="48" fill="#0080FF" rx="24"/>
|
||||
<path fill="#fff" d="M18.5386 21.6899h7.8153l1.8771 3.8487h-9.6924v9.6902h-3.8481v-9.6902H5v-3.8487h9.6905V12h3.8481v9.6899zm24.1057 13.5389h-4.5342L26.7809 12h4.5341l11.3293 23.2288z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 328 B |
4
packages/frameworks/logos/ionic-react.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 48 48">
|
||||
<rect width="48" height="48" fill="#4180FC" rx="24"/>
|
||||
<path fill="#fff" fill-rule="evenodd" d="M38.4327 17.0558l.1306.3047C39.5211 19.4721 40 21.6925 40 24c0 8.8163-7.1837 16-16 16S8 32.8163 8 24 15.1837 8 24 8c2.5905 0 5.0503.6095 7.3143 1.7415l.3047.1524-.2612.2177c-.653.5224-1.1537 1.1755-1.4802 1.9374l-.0871.2177-.1959-.0871C27.8313 11.3524 25.9592 10.917 24 10.917c-7.2272 0-13.083 5.8558-13.083 13.083S16.7728 37.083 24 37.083 37.083 31.2054 37.083 24c0-1.7197-.3265-3.4177-1.0014-5.0068l-.087-.2177.2176-.0871c.7619-.283 1.4585-.7619 2.0028-1.3714l.2177-.2612zm-3.962.3048c1.8395 0 3.3307-1.4912 3.3307-3.3306 0-1.8395-1.4912-3.3307-3.3307-3.3307-1.8394 0-3.3306 1.4912-3.3306 3.3307 0 1.8394 1.4912 3.3306 3.3306 3.3306zm-10.4708-.6531c-4.0272 0-7.2925 3.2653-7.2925 7.2926 0 4.0272 3.2653 7.2925 7.2925 7.2925 4.0272 0 7.2925-3.2653 7.2925-7.2925 0-4.0273-3.2653-7.2926-7.2925-7.2926z" clip-rule="evenodd"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1003 B |
19
packages/frameworks/logos/jekyll.svg
Normal file
@@ -0,0 +1,19 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" fill="none">
|
||||
<path fill="#333" d="M0 24a24 24 0 1 0 48 0 24 24 0 0 0-48 0z"/>
|
||||
<path fill="#000" d="M25.22 5.55a.52.52 0 0 0 0 .22s.23 1.56-.03 2.25l-11.35 29.5a4.22 4.22 0 0 0 7.88 3.03l11.35-29.5c.22-.57 1.48-1.7 1.48-1.7.07-.04.12-.1.15-.16v-.01l.02-.04c.3-.76-1.6-2.2-4.21-3.2-2.62-1-4.98-1.2-5.27-.45l-.02.04v.02z" opacity=".3"/>
|
||||
<path fill="url(#paint0_linear)" d="M24.9 5.3a.51.51 0 0 0-.01.22s.23 1.56-.03 2.25L13.5 37.27a4.22 4.22 0 0 0 7.88 3.03l11.35-29.5c.22-.58 1.49-1.7 1.49-1.7.06-.04.1-.1.14-.16v-.01l.03-.04c.29-.76-1.6-2.2-4.22-3.2-2.62-1-4.98-1.2-5.27-.45l-.01.04v.01z"/>
|
||||
<path fill="url(#paint1_linear)" d="M28.45 18.64L20.27 39.9a3.06 3.06 0 0 1-3.91 1.72 3.12 3.12 0 0 1-1.83-3.93l5.23-13.59s.65-1.05 1.93-1.8c1.29-.75 2.34-.6 3.77-1.22 1.42-.64 3-2.45 3-2.45z"/>
|
||||
<path fill="#333" d="M29.33 7.92c1.62.63 3.07.79 3.24.36.16-.43-1.02-1.28-2.65-1.9-1.62-.63-3.08-.8-3.24-.37-.17.43 1.02 1.28 2.65 1.91z"/>
|
||||
<path fill="#fff" d="M24.89 5.52s.23 1.57-.03 2.25L13.5 37.27a4.22 4.22 0 0 0 3.58 5.68 4.22 4.22 0 0 1-1.41-4.85L26.8 9.19s-1.52-1.42-1.91-3.67z" opacity=".3"/>
|
||||
<path fill="#fff" d="M21.24 24.87a.33.33 0 1 0 0-.65.33.33 0 0 0 0 .65zM23.4 27.42a.5.5 0 1 0 0-.99.5.5 0 0 0 0 1zM20.1 30.7a.93.93 0 1 0 0-1.87.93.93 0 0 0 0 1.86zM18.6 31.98a.5.5 0 1 0 0-1 .5.5 0 0 0 0 1zM20.57 35.49a.33.33 0 1 0 0-.66.33.33 0 0 0 0 .66z" opacity=".5"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear" x1="18.32" x2="27.81" y1="22.36" y2="26.01" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#919191"/>
|
||||
<stop offset="1" stop-color="#fff"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint1_linear" x1="18.08" x2="23.83" y1="28.47" y2="30.68" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#900"/>
|
||||
<stop offset="1" stop-color="#E80000"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
9
packages/frameworks/logos/middleman.svg
Normal file
@@ -0,0 +1,9 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 48 48">
|
||||
<rect width="48" height="48" fill="#FBC547" rx="24"/>
|
||||
<path fill="#000" fill-opacity=".45" d="M6 12.53L23.65 9v2.65L6 14.29v-1.76z"/>
|
||||
<path fill="#fff" d="M23.65 9l17.64 3.53v1.76l-17.64-2.64V9z"/>
|
||||
<path fill="#000" fill-opacity=".45" d="M6 15.88v16.24l3.97.44V22.4l3.97 7.33 4.15-7.33V33.8l4.67.62V13.6l-5.02.7-3.8 7.33-3.8-6.18-4.14.44z"/>
|
||||
<path fill="#fff" d="M41.3 15.88v16.24l-3.98.44V22.4l-3.97 7.33-4.14-7.33V33.8l-4.68.62V13.6l5.03.7 3.8 7.33 3.79-6.18 4.14.44z"/>
|
||||
<path fill="#000" fill-opacity=".45" d="M6 35.47L23.65 39v-2.65L6 33.71v1.76z"/>
|
||||
<path fill="#fff" d="M23.65 39l17.64-3.53v-1.76l-17.64 2.64V39z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 715 B |
4
packages/frameworks/logos/mithriljs.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" fill="none">
|
||||
<rect width="48" height="48" fill="#000" rx="24"/>
|
||||
<path fill="#fff" d="M36.109 17.5974C36.0934 11.1453 30.839 5.9 24.3831 5.9c-6.4659 0-11.7267 5.2609-11.7267 11.7268 0 .0202.0037.0394.0037.0596C9.2734 19.745 7 23.4594 7 27.7033 7 34.1701 12.2599 39.43 18.7267 39.43c2.0816 0 4.0338-.5516 5.7308-1.507 1.697.9554 3.6501 1.507 5.7316 1.507 6.4669 0 11.7268-5.2608 11.7268-11.7267 0-4.3054-2.3386-8.0666-5.8069-10.1059zM24.4575 35.4587c-2.122-1.5722-3.5849-3.9842-3.8731-6.7477 1.1931.4112 2.4679.6443 3.7987.6443 1.3896 0 2.7167-.2551 3.953-.7012-.2735 2.7874-1.742 5.2214-3.8786 6.8046zm-9.6406-16.5782c1.1959-.5314 2.5175-.8343 3.9098-.8343a9.5872 9.5872 0 0 1 3.8126.7875c-1.9403 1.6759-3.3307 3.9695-3.8502 6.5778-2.077-1.5253-3.5225-3.8547-3.8722-6.531zm9.5662 8.4043c-1.3372 0-2.612-.2735-3.7712-.7682.3313-2.6873 1.7704-5.0314 3.8456-6.5687 2.0586 1.5254 3.4895 3.8428 3.8364 6.5026-1.1968.5332-2.5185.8343-3.9108.8343zm1.9926-8.4521a9.6043 9.6043 0 0 1 3.8125-.7874c1.3373 0 2.6121.2735 3.7722.7682-.3267 2.6478-1.7282 4.9598-3.7529 6.4998-.5342-2.5689-1.9154-4.8248-3.8318-6.4806zm-1.9926-10.863c4.9855 0 9.1 3.7969 9.6057 8.6502-1.1931-.4102-2.4679-.6443-3.7997-.6443-2.0815 0-4.0346.5516-5.7316 1.5071-1.6961-.9555-3.6483-1.5071-5.7308-1.5071-1.3895 0-2.7167.2552-3.9529.7003.48-4.8799 4.6055-8.7062 9.6093-8.7062zm-5.6564 29.3916c-5.326 0-9.658-4.332-9.658-9.658 0-3.1371 1.5098-5.9207 3.8337-7.6866.6828 3.2867 2.7525 6.0639 5.5609 7.7169.0101 3.5308 1.5924 6.6935 4.0769 8.8393a9.5766 9.5766 0 0 1-3.8135.7884zm11.4624 0a9.587 9.587 0 0 1-3.8125-.7875c2.4918-2.1522 4.0787-5.3269 4.0787-8.8696 0-.0202-.0037-.0404-.0037-.0596 2.7599-1.6787 4.7809-4.4532 5.4316-7.7243 2.3982 1.7595 3.964 4.5881 3.964 7.783-.001 5.3251-4.333 9.658-9.6581 9.658z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
4
packages/frameworks/logos/mkdocs.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" fill="none">
|
||||
<circle cx="24" cy="24" r="24" fill="#2FA4E7"/>
|
||||
<path fill="#fff" d="M32.55 32.91V16h-3.07L24.1 29.23h-.2L18.52 16h-3.07v16.91h2.43V20.52h.17L23 32.61h2l4.95-12.09h.17v12.39h2.43z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 270 B |
1
packages/frameworks/logos/next.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg width="48" height="48" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M22.428.013c-.103.01-.431.042-.727.066C14.883.693 8.497 4.37 4.453 10.024A23.754 23.754 0 0 0 .216 20.51C.023 21.828 0 22.217 0 24.005c0 1.787.023 2.177.216 3.495 1.304 9.012 7.718 16.584 16.417 19.39 1.558.501 3.2.844 5.068 1.05.727.08 3.87.08 4.598 0 3.224-.356 5.954-1.154 8.648-2.529.412-.21.492-.267.436-.314-.038-.028-1.797-2.388-3.909-5.24l-3.838-5.184-4.809-7.117c-2.646-3.913-4.824-7.112-4.842-7.112-.019-.005-.038 3.157-.047 7.018-.014 6.76-.019 7.033-.103 7.192-.122.23-.216.324-.413.427-.15.075-.282.09-.99.09h-.812l-.216-.137a.878.878 0 0 1-.314-.342l-.099-.211.01-9.407.014-9.41.145-.184c.075-.098.235-.225.347-.286.193-.094.268-.103 1.08-.103.957 0 1.116.038 1.365.31.07.075 2.674 3.997 5.79 8.721s7.376 11.175 9.469 14.342l3.8 5.756.192-.127c1.704-1.107 3.505-2.683 4.932-4.325a23.888 23.888 0 0 0 5.65-12.268c.192-1.319.215-1.708.215-3.495 0-1.788-.023-2.177-.216-3.495-1.304-9.013-7.718-16.584-16.417-19.39C29.832.623 28.199.28 26.369.074c-.45-.047-3.551-.099-3.94-.061zm9.825 14.515a.947.947 0 0 1 .474.554c.038.122.047 2.73.038 8.608l-.014 8.436-1.488-2.28-1.492-2.28v-6.132c0-3.964.019-6.193.047-6.3a.957.957 0 0 1 .465-.592c.192-.098.262-.108 1-.108.694 0 .816.01.97.094z" fill="#000"/></svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
5
packages/frameworks/logos/nuxt.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg width="48" height="38" viewBox="0 0 48 38" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M14.7599 35.9199L14.6399 35.7999C14.3999 35.3199 14.3999 34.8399 14.3999 34.3599H2.99992L20.0399 4.11988L27.1199 16.9599L29.3999 15.2799L22.3199 2.43988C22.1999 2.19988 21.3599 0.879883 19.9199 0.879883C19.3199 0.879883 18.3599 1.11988 17.6399 2.43988L0.479919 33.0399C0.359919 33.2799 -0.360081 34.7199 0.359919 35.9199C0.599919 36.5199 1.31992 37.1199 2.87992 37.1199H17.2799C15.7199 37.1199 14.9999 36.5199 14.7599 35.9199Z" fill="#00C58E"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M47.3999 33.1598L33.5999 8.31977C33.3599 8.07977 32.6399 6.75977 31.1999 6.75977C30.5999 6.75977 29.7599 6.99977 28.9199 8.31977L27.1199 11.1998V16.9598L31.1999 9.87977L44.8799 34.3598H39.7199C39.7913 34.853 39.7075 35.3563 39.4799 35.7998V35.9198C38.7599 37.1198 37.1999 37.1198 36.9599 37.1198H45.1199C45.3599 37.1198 46.9199 37.1198 47.6399 35.9198C47.8799 35.3198 48.1199 34.3598 47.3999 33.1598Z" fill="#108775"/>
|
||||
<path d="M39.84 35.9199V35.7999L39.96 35.5599C40.08 35.1999 40.2 34.7199 40.08 34.3599L39.6 33.0399L28.8 14.08L27.24 11.2H27.12L25.56 14.08L14.64 33.0399L14.28 34.3599C14.1908 34.8922 14.275 35.4391 14.52 35.9199C14.88 36.5199 15.6 37.1199 17.04 37.1199H37.2C37.56 37.1199 39.12 37.1199 39.84 35.9199ZM27.12 16.96L37.08 34.3599H17.28L27.12 16.96Z" fill="#2F495E"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
1
packages/frameworks/logos/polymer.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg width="48" height="48" fill="none" xmlns="http://www.w3.org/2000/svg"><g clip-path="url(#clip0)"><path d="M28.8 40.371L9.6 7.113H19.2l19.201 33.258h-9.6z" fill="#FF4081"/><path d="M28.8 40.371l4.8-8.314 4.802 8.314H28.8z" fill="#fff" fill-opacity=".2"/><path d="M24 32.057l4.8 8.314 4.8-8.314H24z" fill="#fff" fill-opacity=".1"/><path d="M19.2 23.742l4.8 8.315 4.8-8.315h-9.6z" fill="#000" fill-opacity=".1"/><path d="M19.2 23.742l4.8-8.314 4.8 8.314h-9.6z" fill="#000" fill-opacity=".2"/><path d="M14.4 15.428l4.8 8.314 4.8-8.314h-9.6z" fill="#000" fill-opacity=".3"/><path d="M14.4 15.428l4.8-8.315 4.8 8.315h-9.6z" fill="#000" fill-opacity=".4"/><path d="M9.599 7.113l4.801 8.315 4.8-8.315H9.6z" fill="#000" fill-opacity=".5"/><path d="M9.599 40.371L-.002 23.742l4.8-8.314L19.2 40.37H9.599z" fill="#536DFE"/><path d="M9.599 40.371l4.801-8.314 4.8 8.314H9.6z" fill="#fff" fill-opacity=".2"/><path d="M4.799 32.057l4.8 8.314 4.801-8.314H4.8z" fill="#fff" fill-opacity=".1"/><path d="M-.002 23.742l4.8 8.315 4.8-8.315h-9.6z" fill="#000" fill-opacity=".1"/><path d="M-.002 23.742l4.8-8.314 4.8 8.314h-9.6z" fill="#000" fill-opacity=".2"/><path d="M9.599 23.742l-4.8-8.314 4.8-8.315 4.801 8.315L9.6 23.742z" fill="#303F9F"/><path d="M14.4 15.428L9.6 7.113l-4.8 8.315H14.4z" fill="#000" fill-opacity=".2"/><path d="M38.401 40.371l-4.8-8.314 4.8-8.315 4.8 8.315-4.8 8.314z" fill="#3F51B5"/><path d="M43.201 32.057l-4.8 8.314-4.8-8.314h9.6z" fill="#000" fill-opacity=".2"/><path d="M43.201 32.057L28.8 7.113h9.601l9.602 16.63-4.802 8.314z" fill="#7986CB"/><path d="M38.401 23.742l4.8 8.315 4.801-8.315h-9.6z" fill="#fff" fill-opacity=".2"/><path d="M38.401 23.742l4.8-8.314 4.801 8.314h-9.6z" fill="#fff" fill-opacity=".1"/><path d="M33.6 15.428l4.801-8.315 4.8 8.315h-9.6z" fill="#000" fill-opacity=".1"/><path d="M28.8 7.113l4.8 8.315 4.801-8.315H28.8z" fill="#000" fill-opacity=".2"/></g><defs><clipPath id="clip0"><path fill="#fff" transform="translate(0 7)" d="M0 0H48V33.375H0z"/></clipPath></defs></svg>
|
||||
|
After Width: | Height: | Size: 2.0 KiB |
1
packages/frameworks/logos/preact.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg width="48" height="48" fill="none" xmlns="http://www.w3.org/2000/svg"><g clip-path="url(#clip0)"><path d="M23.757 0l20.756 11.984V35.95L23.758 47.935 3 35.951V11.984L23.757 0z" fill="#673AB8"/><path d="M8.654 35.753c2.76 3.532 11.529.84 19.808-5.628S41.474 15.8 38.714 12.267s-11.529-.84-19.808 5.628S5.894 32.22 8.654 35.753zm1.178-.92c-.915-1.172-.515-3.472 1.229-6.366 1.836-3.048 4.952-6.413 8.766-9.393 3.814-2.98 7.833-5.19 11.235-6.234 3.23-.991 5.558-.823 6.473.348.916 1.172.516 3.471-1.228 6.366-1.836 3.048-4.952 6.413-8.766 9.393-3.814 2.98-7.833 5.19-11.235 6.233-3.23.992-5.558.824-6.474-.348z" fill="#fff"/><path d="M38.714 35.753c2.76-3.532-1.973-11.39-10.252-17.858-8.28-6.468-17.049-9.16-19.808-5.628-2.76 3.532 1.973 11.39 10.252 17.858 8.28 6.469 17.049 9.16 19.808 5.628zm-1.178-.92c-.916 1.17-3.244 1.339-6.474.347-3.402-1.044-7.42-3.254-11.235-6.233-3.814-2.98-6.93-6.345-8.766-9.393-1.744-2.895-2.144-5.194-1.229-6.366.916-1.171 3.243-1.34 6.474-.348 3.402 1.044 7.42 3.254 11.235 6.234 3.814 2.98 6.93 6.345 8.766 9.393 1.744 2.894 2.144 5.194 1.228 6.365z" fill="#fff"/><path d="M23.684 27.19a3.179 3.179 0 1 0 0-6.359 3.179 3.179 0 0 0 0 6.358z" fill="#fff"/></g><defs><clipPath id="clip0"><path fill="#fff" transform="translate(3)" d="M0 0H41.514V48H0z"/></clipPath></defs></svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
1
packages/frameworks/logos/react.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg width="48" height="48" fill="none" xmlns="http://www.w3.org/2000/svg"><g clip-path="url(#clip0)"><path d="M24 28.631a4.278 4.278 0 1 0 0-8.556 4.278 4.278 0 0 0 0 8.556z" fill="#61DAFB"/><path d="M24 33.118c12.678 0 22.956-3.924 22.956-8.765 0-4.84-10.278-8.765-22.956-8.765-12.679 0-22.957 3.924-22.957 8.765 0 4.841 10.278 8.765 22.957 8.765z" stroke="#61DAFB"/><path d="M16.409 28.736c6.34 10.98 14.877 17.918 19.07 15.498 4.191-2.42 2.451-13.284-3.888-24.264C25.25 8.99 16.714 2.053 12.52 4.473 8.33 6.892 10.07 17.756 16.41 28.736z" stroke="#61DAFB"/><path d="M16.409 19.97c-6.34 10.98-8.08 21.843-3.887 24.264 4.192 2.42 12.73-4.518 19.069-15.498 6.34-10.98 8.08-21.843 3.887-24.264-4.192-2.42-12.73 4.519-19.069 15.498z" stroke="#61DAFB"/></g><defs><clipPath id="clip0"><path fill="#fff" transform="translate(0 3)" d="M0 0H48V42.706H0z"/></clipPath></defs></svg>
|
||||
|
After Width: | Height: | Size: 874 B |
1
packages/frameworks/logos/saber.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg width="50" height="50" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M25 49c13.255 0 24-10.745 24-24S38.255 1 25 1 1 11.745 1 25s10.745 24 24 24z" fill="#00838F" stroke="#00838F" stroke-width=".308"/><path fill-rule="evenodd" clip-rule="evenodd" d="M19.355 28.86l6.064-.508 1.14 2.633-3.913 3.882-3.29 4.626.433 5.617c-3.218-.786-5.361-1.56-6.43-2.324-1.068-.763-2.215-1.861-3.441-3.293l5.745-6.843h2.514l1.178-3.79z" fill="#FBB526"/><path fill-rule="evenodd" clip-rule="evenodd" d="M25.36 7.404a2.993 2.993 0 0 0-1.916 5.292v4.045h3.95v-4.15a2.993 2.993 0 0 0-2.035-5.187zm-1.318 2.992a1.317 1.317 0 1 1 2.634 0 1.317 1.317 0 0 1-2.634 0z" fill="#fff"/><path d="M27.373 37.265l-2.953 2.83 1.16 1.21 2.083-1.997 1.866.978 5.01.68.225-1.661-4.718-.64-2.673-1.4z" fill="#fff"/><path fill-rule="evenodd" clip-rule="evenodd" d="M2.736 25C2.736 12.704 12.704 2.736 25 2.736S47.264 12.704 47.264 25 37.296 47.264 25 47.264 2.736 37.296 2.736 25zM25 4.41C13.63 4.411 4.41 13.63 4.41 25a20.51 20.51 0 0 0 5.1 13.565l5.418-7.22h3.086l.255-2.69 3.259-.618v-9.62h-3.352V16.74h14.364v1.675H29.31V30.52l2.17.222 1.761 2.328h1.629l5.653 5.457A20.51 20.51 0 0 0 45.59 25c0-11.37-9.218-20.589-20.59-20.589zm7.43 30.364l-.023-.03h-2.293v-1.676h1.024l-.556-.735-4.404-.45-.974 2.587-2.52 1.545-.875-1.43 2.03-1.243 1.225-3.257.847.086-.694-1.129-5.398 1.024-.63 6.639-2.977 2.581-1.098-1.266 2.47-2.142.271-2.86h-2.088l-5.083 6.777A20.52 20.52 0 0 0 25 45.59a20.52 20.52 0 0 0 14.37-5.846l-5.178-4.998h-1.723l-.04.03zm-7.909-7.306l-1.556.296v-9.349h4.908v11.748l-1.834-2.983-.082.016v-3.993h-1.436v4.265z" fill="#fff"/></svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
1
packages/frameworks/logos/sendgrid.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg width="48" height="48" fill="none" xmlns="http://www.w3.org/2000/svg"><circle cx="24" cy="24" r="24" fill="#2A4660"/><g clip-path="url(#clip0)"><path d="M28.187 28.006h-7.993V36h7.993v-7.994zm-7.993-7.994H12.2v7.994h7.994v-7.994z" fill="#99E1F4"/><path d="M20.194 28.006H12.2V36h7.994v-7.994z" fill="#1A82E2"/><path d="M36.2 20.012h-7.994v7.994H36.2v-7.994zM28.187 12h-7.993v7.994h7.993V12z" fill="#00B2E3"/><path d="M20.194 20.012v7.994h8.012v-7.994h-8.012z" fill="#009DD9"/><path d="M36.2 12h-7.994v7.994H36.2V12z" fill="#1A82E2"/></g><defs><clipPath id="clip0"><path fill="#fff" transform="translate(12 12)" d="M0 0H24V24H0z"/></clipPath></defs></svg>
|
||||
|
After Width: | Height: | Size: 659 B |
4
packages/frameworks/logos/stencil.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" fill="none">
|
||||
<circle cx="24" cy="24" r="24" fill="#000"/>
|
||||
<path fill="#fff" d="M20.27 29H32l-6.34 7H14l6.27-7zM40 21H15.45L9 27h24.55L40 21zM23.32 12H35l-6.3 7H17l6.32-7z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 248 B |
9
packages/frameworks/logos/storybook.svg
Normal file
@@ -0,0 +1,9 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="39" height="48" fill="none">
|
||||
<mask id="a" width="39" height="48" x="0" y="0" maskUnits="userSpaceOnUse">
|
||||
<path fill="#fff" d="M1.48 43.97L0 4.58A2.4 2.4 0 0 1 2.25 2.1L35.75 0a2.4 2.4 0 0 1 2.55 2.4v42.92a2.4 2.4 0 0 1-2.5 2.4L3.76 46.28a2.4 2.4 0 0 1-2.29-2.3z"/>
|
||||
</mask>
|
||||
<g mask="url(#a)">
|
||||
<path fill="#FF4785" d="M1.47 43.97L-.01 4.58A2.4 2.4 0 0 1 2.24 2.1L35.74 0a2.4 2.4 0 0 1 2.55 2.4v42.92a2.4 2.4 0 0 1-2.5 2.4L3.75 46.28a2.4 2.4 0 0 1-2.3-2.3z"/>
|
||||
<path fill="#fff" d="M28.26 5.87l.23-5.5L33.1 0l.2 5.68a.36.36 0 0 1-.58.29l-1.77-1.4-2.1 1.6a.36.36 0 0 1-.58-.3zm-5.88 12.12c0 .93 6.29.48 7.13-.17 0-6.36-3.41-9.7-9.66-9.7-6.24 0-9.74 3.4-9.74 8.48 0 8.86 11.96 9.03 11.96 13.87 0 1.35-.67 2.16-2.13 2.16-1.9 0-2.66-.97-2.57-4.28 0-.72-7.26-.94-7.48 0-.57 8.01 4.43 10.33 10.14 10.33 5.54 0 9.88-2.96 9.88-8.3 0-9.5-12.14-9.24-12.14-13.95 0-1.9 1.42-2.16 2.26-2.16.89 0 2.48.15 2.35 3.72z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 980 B |
1
packages/frameworks/logos/svelte.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg width="47" height="48" fill="none" xmlns="http://www.w3.org/2000/svg"><g clip-path="url(#clip0)"><path d="M39.792 6.09C35.507-.027 27.044-1.84 20.924 2.048L10.177 8.88a12.297 12.297 0 0 0-5.57 8.237 12.922 12.922 0 0 0 1.28 8.315 12.313 12.313 0 0 0-1.844 4.596c-.619 3.47.19 7.043 2.244 9.91 4.287 6.119 12.75 7.931 18.869 4.042l10.747-6.831a12.295 12.295 0 0 0 5.57-8.237 12.927 12.927 0 0 0-1.28-8.315A12.31 12.31 0 0 0 42.037 16c.619-3.47-.19-7.043-2.245-9.91" fill="#FF3E00"/><path d="M19.873 40.52a8.55 8.55 0 0 1-9.165-3.388 7.866 7.866 0 0 1-1.35-5.962c.062-.339.148-.674.257-1.001l.202-.616.55.404a13.881 13.881 0 0 0 4.206 2.095l.4.121-.037.398a2.4 2.4 0 0 0 .433 1.594 2.575 2.575 0 0 0 2.76 1.022 2.37 2.37 0 0 0 .66-.29l10.75-6.833a2.233 2.233 0 0 0 1.011-1.492 2.377 2.377 0 0 0-.407-1.797 2.577 2.577 0 0 0-2.76-1.022 2.37 2.37 0 0 0-.66.289L22.62 26.65c-.675.428-1.411.75-2.183.956a8.55 8.55 0 0 1-9.166-3.388 7.866 7.866 0 0 1-1.35-5.962 7.394 7.394 0 0 1 3.35-4.954l10.75-6.834a7.844 7.844 0 0 1 2.185-.957 8.55 8.55 0 0 1 9.165 3.388 7.866 7.866 0 0 1 1.35 5.962c-.062.34-.148.674-.256 1.001l-.203.616-.55-.403a13.87 13.87 0 0 0-4.206-2.096l-.4-.121.037-.398a2.404 2.404 0 0 0-.433-1.594 2.575 2.575 0 0 0-2.76-1.022 2.37 2.37 0 0 0-.66.29l-10.75 6.833a2.229 2.229 0 0 0-1.01 1.492c-.112.63.034 1.277.406 1.797a2.577 2.577 0 0 0 2.76 1.023c.234-.063.457-.16.661-.29l4.102-2.607a7.834 7.834 0 0 1 2.183-.957 8.55 8.55 0 0 1 9.165 3.388 7.865 7.865 0 0 1 1.35 5.962 7.398 7.398 0 0 1-3.35 4.955l-10.75 6.833a7.842 7.842 0 0 1-2.185.958" fill="#fff"/></g><defs><clipPath id="clip0"><path fill="#fff" transform="translate(3.84)" d="M0 0H38.4V46.08H0z"/></clipPath></defs></svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
1
packages/frameworks/logos/umi.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg width="48" height="48" fill="none" xmlns="http://www.w3.org/2000/svg"><circle cx="24" cy="24" r="24" fill="#1CA1FA"/><path d="M19.77 16.09h-2.625v11.086c0 3.656 2.613 6.234 6.843 6.234 4.254 0 6.856-2.578 6.856-6.234V16.09h-2.625v10.875c0 2.414-1.535 4.113-4.23 4.113-2.684 0-4.22-1.7-4.22-4.113V16.09z" fill="#fff"/></svg>
|
||||
|
After Width: | Height: | Size: 328 B |
4
packages/frameworks/logos/vanilla.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M0 24C0 37.2548 10.7452 48 24 48C37.2548 48 48 37.2548 48 24C48 10.7452 37.2548 0 24 0C10.7452 0 0 10.7452 0 24Z" fill="#F7DF1E"/>
|
||||
<path d="M27.6053 28.7181C28.5722 30.2968 29.8301 31.4571 32.0549 31.4571C33.9238 31.4571 35.1177 30.523 35.1177 29.2324C35.1177 27.6857 33.8911 27.1379 31.8339 26.2381L30.7063 25.7543C27.4514 24.3676 25.2891 22.6305 25.2891 18.9581C25.2891 15.5752 27.8667 13 31.8949 13C34.7627 13 36.8244 13.9981 38.3101 16.6114L34.7977 18.8667C34.0244 17.48 33.1901 16.9337 31.8949 16.9337C30.5737 16.9337 29.7364 17.7718 29.7364 18.8667C29.7364 20.2198 30.5745 20.7676 32.5097 21.6057L33.6373 22.0888C37.4697 23.7322 39.6335 25.4076 39.6335 29.1745C39.6335 33.2354 36.4434 35.4602 32.1592 35.4602C27.9703 35.4602 25.264 33.464 23.9398 30.8476L27.6053 28.7181ZM11.6716 29.109C12.3802 30.3661 13.0248 31.429 14.5745 31.429C16.0564 31.429 16.9912 30.8491 16.9912 28.5947V13.2575H21.5017V28.6556C21.5017 33.3261 18.7634 35.4518 14.7665 35.4518C11.155 35.4518 9.06362 33.5829 8 31.3318L11.6716 29.109Z" fill="black"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
1
packages/frameworks/logos/vue.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg width="48" height="48" fill="none" xmlns="http://www.w3.org/2000/svg"><g clip-path="url(#clip0)"><path d="M29.54 3L24 12.7 18.456 3H-.001l24 42L47.998 3H29.54z" fill="#41B883"/><path d="M29.54 3L24 12.7 18.456 3H9.599l14.4 25.2L38.398 3H29.54z" fill="#34495E"/></g><defs><clipPath id="clip0"><path fill="#fff" transform="translate(0 3)" d="M0 0H48V42H0z"/></clipPath></defs></svg>
|
||||
|
After Width: | Height: | Size: 385 B |
4
packages/frameworks/logos/zola.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" fill="none">
|
||||
<circle cx="24" cy="24" r="24" fill="#191919"/>
|
||||
<path fill="#fff" d="M17.65 33h12.7v-2.26H21v-.2l9.14-12.66V16.1h-12.2v2.26h8.9v.2l-9.18 12.66V33z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 237 B |
6
packages/frameworks/package.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"name": "@now/frameworks",
|
||||
"version": "0.0.2-canary.0",
|
||||
"main": "frameworks.json",
|
||||
"license": "UNLICENSED"
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@now/build-utils",
|
||||
"version": "1.1.0",
|
||||
"version": "1.2.1-canary.2",
|
||||
"license": "MIT",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.js",
|
||||
@@ -32,9 +32,9 @@
|
||||
"aggregate-error": "3.0.1",
|
||||
"async-retry": "1.2.3",
|
||||
"async-sema": "2.1.4",
|
||||
"boxen": "4.2.0",
|
||||
"cross-spawn": "6.0.5",
|
||||
"end-of-stream": "1.4.1",
|
||||
"execa": "^1.0.0",
|
||||
"fs-extra": "7.0.0",
|
||||
"glob": "7.1.3",
|
||||
"into-stream": "5.0.0",
|
||||
|
||||
432
packages/now-build-utils/src/detect-builders-legacy.ts
Normal file
@@ -0,0 +1,432 @@
|
||||
import minimatch from 'minimatch';
|
||||
import { valid as validSemver } from 'semver';
|
||||
import { PackageJson, Builder, Config, BuilderFunctions } from './types';
|
||||
|
||||
interface ErrorResponse {
|
||||
code: string;
|
||||
message: string;
|
||||
}
|
||||
|
||||
interface Options {
|
||||
tag?: 'canary' | 'latest' | string;
|
||||
functions?: BuilderFunctions;
|
||||
}
|
||||
|
||||
const src = 'package.json';
|
||||
const config: Config = { zeroConfig: true };
|
||||
|
||||
const MISSING_BUILD_SCRIPT_ERROR: ErrorResponse = {
|
||||
code: 'missing_build_script',
|
||||
message:
|
||||
'Your `package.json` file is missing a `build` property inside the `scripts` property.' +
|
||||
'\nMore details: https://zeit.co/docs/v2/platform/frequently-asked-questions#missing-build-script',
|
||||
};
|
||||
|
||||
// Static builders are special cased in `@now/static-build`
|
||||
function getBuilders({ tag }: Options = {}): Map<string, Builder> {
|
||||
const withTag = tag ? `@${tag}` : '';
|
||||
const config = { zeroConfig: true };
|
||||
|
||||
return new Map<string, Builder>([
|
||||
['next', { src, use: `@now/next${withTag}`, config }],
|
||||
]);
|
||||
}
|
||||
|
||||
// Must be a function to ensure that the returned
|
||||
// object won't be a reference
|
||||
function getApiBuilders({ tag }: Pick<Options, 'tag'> = {}): Builder[] {
|
||||
const withTag = tag ? `@${tag}` : '';
|
||||
const config = { zeroConfig: true };
|
||||
|
||||
return [
|
||||
{ src: 'api/**/*.js', use: `@now/node${withTag}`, config },
|
||||
{ src: 'api/**/*.ts', use: `@now/node${withTag}`, config },
|
||||
{ src: 'api/**/*.go', use: `@now/go${withTag}`, config },
|
||||
{ src: 'api/**/*.py', use: `@now/python${withTag}`, config },
|
||||
{ src: 'api/**/*.rb', use: `@now/ruby${withTag}`, config },
|
||||
];
|
||||
}
|
||||
|
||||
function hasPublicDirectory(files: string[]) {
|
||||
return files.some(name => name.startsWith('public/'));
|
||||
}
|
||||
|
||||
function hasBuildScript(pkg: PackageJson | undefined) {
|
||||
const { scripts = {} } = pkg || {};
|
||||
return Boolean(scripts && scripts['build']);
|
||||
}
|
||||
|
||||
function getApiFunctionBuilder(
|
||||
file: string,
|
||||
prevBuilder: Builder | undefined,
|
||||
{ functions = {} }: Pick<Options, 'functions'>
|
||||
) {
|
||||
const key = Object.keys(functions).find(
|
||||
k => file === k || minimatch(file, k)
|
||||
);
|
||||
const fn = key ? functions[key] : undefined;
|
||||
|
||||
if (!fn || (!fn.runtime && !prevBuilder)) {
|
||||
return prevBuilder;
|
||||
}
|
||||
|
||||
const src = (prevBuilder && prevBuilder.src) || file;
|
||||
const use = fn.runtime || (prevBuilder && prevBuilder.use);
|
||||
|
||||
const config: Config = { zeroConfig: true };
|
||||
|
||||
if (key) {
|
||||
Object.assign(config, {
|
||||
functions: {
|
||||
[key]: fn,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const { includeFiles, excludeFiles } = fn;
|
||||
|
||||
if (includeFiles) Object.assign(config, { includeFiles });
|
||||
if (excludeFiles) Object.assign(config, { excludeFiles });
|
||||
|
||||
return use ? { use, src, config } : prevBuilder;
|
||||
}
|
||||
|
||||
async function detectFrontBuilder(
|
||||
pkg: PackageJson,
|
||||
builders: Builder[],
|
||||
options: Options
|
||||
): Promise<Builder> {
|
||||
const { tag } = options;
|
||||
const withTag = tag ? `@${tag}` : '';
|
||||
for (const [dependency, builder] of getBuilders(options)) {
|
||||
const deps = Object.assign({}, pkg.dependencies, pkg.devDependencies);
|
||||
|
||||
// Return the builder when a dependency matches
|
||||
if (deps[dependency]) {
|
||||
if (options.functions) {
|
||||
Object.entries(options.functions).forEach(([key, func]) => {
|
||||
// When the builder is not used yet we'll use it for the frontend
|
||||
if (
|
||||
builders.every(
|
||||
b => !(b.config && b.config.functions && b.config.functions[key])
|
||||
)
|
||||
) {
|
||||
if (!builder.config) builder.config = {};
|
||||
if (!builder.config.functions) builder.config.functions = {};
|
||||
builder.config.functions[key] = { ...func };
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
|
||||
// By default we'll choose the `static-build` builder
|
||||
return { src, use: `@now/static-build${withTag}`, config };
|
||||
}
|
||||
|
||||
// Files that match a specific pattern will get ignored
|
||||
export function getIgnoreApiFilter(optionsOrBuilders: Options | Builder[]) {
|
||||
const possiblePatterns: string[] = getApiBuilders().map(b => b.src);
|
||||
|
||||
if (Array.isArray(optionsOrBuilders)) {
|
||||
optionsOrBuilders.forEach(({ src }) => possiblePatterns.push(src));
|
||||
} else if (optionsOrBuilders.functions) {
|
||||
Object.keys(optionsOrBuilders.functions).forEach(p =>
|
||||
possiblePatterns.push(p)
|
||||
);
|
||||
}
|
||||
|
||||
return (file: string) => {
|
||||
if (!file.startsWith('api/')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (file.includes('/.')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (file.includes('/_')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (file.endsWith('.d.ts')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (possiblePatterns.every(p => !(file === p || minimatch(file, p)))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
// We need to sort the file paths by alphabet to make
|
||||
// sure the routes stay in the same order e.g. for deduping
|
||||
export function sortFiles(fileA: string, fileB: string) {
|
||||
return fileA.localeCompare(fileB);
|
||||
}
|
||||
|
||||
async function detectApiBuilders(
|
||||
files: string[],
|
||||
options: Options
|
||||
): Promise<Builder[]> {
|
||||
const builds = files
|
||||
.sort(sortFiles)
|
||||
.filter(getIgnoreApiFilter(options))
|
||||
.map(file => {
|
||||
const apiBuilders = getApiBuilders(options);
|
||||
const apiBuilder = apiBuilders.find(b => minimatch(file, b.src));
|
||||
const fnBuilder = getApiFunctionBuilder(file, apiBuilder, options);
|
||||
return fnBuilder ? { ...fnBuilder, src: file } : null;
|
||||
});
|
||||
|
||||
return builds.filter(Boolean) as Builder[];
|
||||
}
|
||||
|
||||
// When a package has files that conflict with `/api` routes
|
||||
// e.g. Next.js pages/api we'll check it here and return an error.
|
||||
async function checkConflictingFiles(
|
||||
files: string[],
|
||||
builders: Builder[]
|
||||
): Promise<ErrorResponse | null> {
|
||||
// For Next.js
|
||||
if (builders.some(b => b.use.startsWith('@now/next'))) {
|
||||
const hasApiPages = files.some(file => file.startsWith('pages/api/'));
|
||||
const hasApiBuilders = builders.some(b => b.src.startsWith('api/'));
|
||||
|
||||
if (hasApiPages && hasApiBuilders) {
|
||||
return {
|
||||
code: 'conflicting_files',
|
||||
message:
|
||||
'It is not possible to use `api` and `pages/api` at the same time, please only use one option',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// When e.g. Next.js receives a `functions` property it has to make sure,
|
||||
// that it can handle those files, otherwise there are unused functions.
|
||||
async function checkUnusedFunctionsOnFrontendBuilder(
|
||||
files: string[],
|
||||
builder: Builder
|
||||
): Promise<ErrorResponse | null> {
|
||||
const { config: { functions = undefined } = {} } = builder;
|
||||
|
||||
if (!functions) return null;
|
||||
|
||||
if (builder.use.startsWith('@now/next')) {
|
||||
const matchingFiles = files.filter(file =>
|
||||
Object.keys(functions).some(key => file === key || minimatch(file, key))
|
||||
);
|
||||
|
||||
for (const matchedFile of matchingFiles) {
|
||||
if (
|
||||
!matchedFile.startsWith('src/pages/') &&
|
||||
!matchedFile.startsWith('pages/')
|
||||
) {
|
||||
return {
|
||||
code: 'unused_function',
|
||||
message: `The function for ${matchedFile} can't be handled by any builder`,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function validateFunctions(files: string[], { functions = {} }: Options) {
|
||||
const apiBuilders = getApiBuilders();
|
||||
|
||||
for (const [path, func] of Object.entries(functions)) {
|
||||
if (path.length > 256) {
|
||||
return {
|
||||
code: 'invalid_function_glob',
|
||||
message: 'Function globs must be less than 256 characters long.',
|
||||
};
|
||||
}
|
||||
|
||||
if (!func || typeof func !== 'object') {
|
||||
return {
|
||||
code: 'invalid_function',
|
||||
message: 'Function must be an object.',
|
||||
};
|
||||
}
|
||||
|
||||
if (Object.keys(func).length === 0) {
|
||||
return {
|
||||
code: 'invalid_function',
|
||||
message: 'Function must contain at least one property.',
|
||||
};
|
||||
}
|
||||
|
||||
if (
|
||||
func.maxDuration !== undefined &&
|
||||
(func.maxDuration < 1 ||
|
||||
func.maxDuration > 900 ||
|
||||
!Number.isInteger(func.maxDuration))
|
||||
) {
|
||||
return {
|
||||
code: 'invalid_function_duration',
|
||||
message: 'Functions must have a duration between 1 and 900.',
|
||||
};
|
||||
}
|
||||
|
||||
if (
|
||||
func.memory !== undefined &&
|
||||
(func.memory < 128 || func.memory > 3008 || func.memory % 64 !== 0)
|
||||
) {
|
||||
return {
|
||||
code: 'invalid_function_memory',
|
||||
message:
|
||||
'Functions must have a memory value between 128 and 3008 in steps of 64.',
|
||||
};
|
||||
}
|
||||
|
||||
if (path.startsWith('/')) {
|
||||
return {
|
||||
code: 'invalid_function_source',
|
||||
message: `The function path "${path}" is invalid. The path must be relative to your project root and therefore cannot start with a slash.`,
|
||||
};
|
||||
}
|
||||
|
||||
if (files.some(f => f === path || minimatch(f, path)) === false) {
|
||||
return {
|
||||
code: 'invalid_function_source',
|
||||
message: `No source file matched the function for ${path}.`,
|
||||
};
|
||||
}
|
||||
|
||||
if (func.runtime !== undefined) {
|
||||
const tag = `${func.runtime}`.split('@').pop();
|
||||
|
||||
if (!tag || !validSemver(tag)) {
|
||||
return {
|
||||
code: 'invalid_function_runtime',
|
||||
message:
|
||||
'Function Runtimes must have a valid version, for example `now-php@1.0.0`.',
|
||||
};
|
||||
}
|
||||
|
||||
if (
|
||||
apiBuilders.some(b => func.runtime && func.runtime.startsWith(b.use))
|
||||
) {
|
||||
return {
|
||||
code: 'invalid_function_runtime',
|
||||
message: `The function Runtime ${func.runtime} is not a Community Runtime and must not be specified.`,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (func.includeFiles !== undefined) {
|
||||
if (typeof func.includeFiles !== 'string') {
|
||||
return {
|
||||
code: 'invalid_function_property',
|
||||
message: `The property \`includeFiles\` must be a string.`,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (func.excludeFiles !== undefined) {
|
||||
if (typeof func.excludeFiles !== 'string') {
|
||||
return {
|
||||
code: 'invalid_function_property',
|
||||
message: `The property \`excludeFiles\` must be a string.`,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// When zero config is used we can call this function
|
||||
// to determine what builders to use
|
||||
export async function detectBuildersLegacy(
|
||||
files: string[],
|
||||
pkg?: PackageJson | undefined | null,
|
||||
options: Options = {}
|
||||
): Promise<{
|
||||
builders: Builder[] | null;
|
||||
errors: ErrorResponse[] | null;
|
||||
warnings: ErrorResponse[];
|
||||
}> {
|
||||
const errors: ErrorResponse[] = [];
|
||||
const warnings: ErrorResponse[] = [];
|
||||
|
||||
const functionError = validateFunctions(files, options);
|
||||
|
||||
if (functionError) {
|
||||
return {
|
||||
builders: null,
|
||||
errors: [functionError],
|
||||
warnings,
|
||||
};
|
||||
}
|
||||
|
||||
// Detect all builders for the `api` directory before anything else
|
||||
const builders = await detectApiBuilders(files, options);
|
||||
|
||||
if (pkg && hasBuildScript(pkg)) {
|
||||
const frontendBuilder = await detectFrontBuilder(pkg, builders, options);
|
||||
builders.push(frontendBuilder);
|
||||
|
||||
const conflictError = await checkConflictingFiles(files, builders);
|
||||
|
||||
if (conflictError) {
|
||||
warnings.push(conflictError);
|
||||
}
|
||||
|
||||
const unusedFunctionError = await checkUnusedFunctionsOnFrontendBuilder(
|
||||
files,
|
||||
frontendBuilder
|
||||
);
|
||||
|
||||
if (unusedFunctionError) {
|
||||
return {
|
||||
builders: null,
|
||||
errors: [unusedFunctionError],
|
||||
warnings,
|
||||
};
|
||||
}
|
||||
} else {
|
||||
if (pkg && builders.length === 0) {
|
||||
// We only show this error when there are no api builders
|
||||
// since the dependencies of the pkg could be used for those
|
||||
errors.push(MISSING_BUILD_SCRIPT_ERROR);
|
||||
return { errors, warnings, builders: null };
|
||||
}
|
||||
|
||||
// We allow a `public` directory
|
||||
// when there are no build steps
|
||||
if (hasPublicDirectory(files)) {
|
||||
builders.push({
|
||||
use: '@now/static',
|
||||
src: 'public/**/*',
|
||||
config,
|
||||
});
|
||||
} else if (
|
||||
builders.length > 0 &&
|
||||
files.some(f => !f.startsWith('api/') && f !== 'package.json')
|
||||
) {
|
||||
// Everything besides the api directory
|
||||
// and package.json can be served as static files
|
||||
builders.push({
|
||||
use: '@now/static',
|
||||
src: '!{api/**,package.json}',
|
||||
config,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
builders: builders.length ? builders : null,
|
||||
errors: errors.length ? errors : null,
|
||||
warnings,
|
||||
};
|
||||
}
|
||||
@@ -1,6 +1,12 @@
|
||||
import minimatch from 'minimatch';
|
||||
import { valid as validSemver } from 'semver';
|
||||
import { PackageJson, Builder, Config, BuilderFunctions } from './types';
|
||||
import {
|
||||
Builder,
|
||||
Config,
|
||||
BuilderFunctions,
|
||||
DetectorResult,
|
||||
DetectorOutput,
|
||||
} from './types';
|
||||
|
||||
interface ErrorResponse {
|
||||
code: string;
|
||||
@@ -12,26 +18,6 @@ interface Options {
|
||||
functions?: BuilderFunctions;
|
||||
}
|
||||
|
||||
const src = 'package.json';
|
||||
const config: Config = { zeroConfig: true };
|
||||
|
||||
const MISSING_BUILD_SCRIPT_ERROR: ErrorResponse = {
|
||||
code: 'missing_build_script',
|
||||
message:
|
||||
'Your `package.json` file is missing a `build` property inside the `script` property.' +
|
||||
'\nMore details: https://zeit.co/docs/v2/platform/frequently-asked-questions#missing-build-script',
|
||||
};
|
||||
|
||||
// Static builders are special cased in `@now/static-build`
|
||||
function getBuilders({ tag }: Options = {}): Map<string, Builder> {
|
||||
const withTag = tag ? `@${tag}` : '';
|
||||
const config = { zeroConfig: true };
|
||||
|
||||
return new Map<string, Builder>([
|
||||
['next', { src, use: `@now/next${withTag}`, config }],
|
||||
]);
|
||||
}
|
||||
|
||||
// Must be a function to ensure that the returned
|
||||
// object won't be a reference
|
||||
function getApiBuilders({ tag }: Pick<Options, 'tag'> = {}): Builder[] {
|
||||
@@ -47,13 +33,8 @@ function getApiBuilders({ tag }: Pick<Options, 'tag'> = {}): Builder[] {
|
||||
];
|
||||
}
|
||||
|
||||
function hasPublicDirectory(files: string[]) {
|
||||
return files.some(name => name.startsWith('public/'));
|
||||
}
|
||||
|
||||
function hasBuildScript(pkg: PackageJson | undefined) {
|
||||
const { scripts = {} } = pkg || {};
|
||||
return Boolean(scripts && scripts['build']);
|
||||
function hasDirectory(fileName: string, files: string[]) {
|
||||
return files.some(name => name.startsWith(`${fileName}/`));
|
||||
}
|
||||
|
||||
function getApiFunctionBuilder(
|
||||
@@ -91,39 +72,71 @@ function getApiFunctionBuilder(
|
||||
return use ? { use, src, config } : prevBuilder;
|
||||
}
|
||||
|
||||
async function detectFrontBuilder(
|
||||
pkg: PackageJson,
|
||||
function detectFrontBuilder(
|
||||
detectorResult: Partial<DetectorOutput>,
|
||||
builders: Builder[],
|
||||
files: string[],
|
||||
options: Options
|
||||
): Promise<Builder> {
|
||||
): Builder {
|
||||
const { tag } = options;
|
||||
const withTag = tag ? `@${tag}` : '';
|
||||
for (const [dependency, builder] of getBuilders(options)) {
|
||||
const deps = Object.assign({}, pkg.dependencies, pkg.devDependencies);
|
||||
|
||||
// Return the builder when a dependency matches
|
||||
if (deps[dependency]) {
|
||||
const {
|
||||
framework,
|
||||
buildCommand,
|
||||
outputDirectory,
|
||||
devCommand,
|
||||
} = detectorResult;
|
||||
|
||||
const frameworkSlug = framework ? framework.slug : null;
|
||||
|
||||
const config: Config = {
|
||||
zeroConfig: true,
|
||||
};
|
||||
|
||||
if (framework) {
|
||||
config.framework = framework;
|
||||
}
|
||||
|
||||
if (devCommand) {
|
||||
config.devCommand = devCommand;
|
||||
}
|
||||
|
||||
if (buildCommand) {
|
||||
config.buildCommand = buildCommand;
|
||||
}
|
||||
|
||||
if (outputDirectory) {
|
||||
config.outputDirectory = outputDirectory;
|
||||
}
|
||||
|
||||
// All unused functions will be used for the frontend
|
||||
if (options.functions) {
|
||||
Object.entries(options.functions).forEach(([key, func]) => {
|
||||
// When the builder is not used yet we'll use it for the frontend
|
||||
if (
|
||||
builders.every(
|
||||
b => !(b.config && b.config.functions && b.config.functions[key])
|
||||
)
|
||||
) {
|
||||
if (!builder.config) builder.config = {};
|
||||
if (!builder.config.functions) builder.config.functions = {};
|
||||
builder.config.functions[key] = { ...func };
|
||||
config.functions = config.functions || {};
|
||||
config.functions[key] = { ...func };
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return builder;
|
||||
if (frameworkSlug === 'next') {
|
||||
return { src: 'package.json', use: `@now/next${withTag}`, config };
|
||||
}
|
||||
|
||||
if (frameworkSlug === 'hugo') {
|
||||
const configFiles = new Set(['config.yaml', 'config.toml', 'config.json']);
|
||||
const source = files.find(file => configFiles.has(file));
|
||||
if (source) {
|
||||
return { src: source, use: `@now/static-build${withTag}`, config };
|
||||
}
|
||||
}
|
||||
|
||||
// By default we'll choose the `static-build` builder
|
||||
return { src, use: `@now/static-build${withTag}`, config };
|
||||
return { src: 'package.json', use: `@now/static-build${withTag}`, config };
|
||||
}
|
||||
|
||||
// Files that match a specific pattern will get ignored
|
||||
@@ -139,10 +152,6 @@ export function getIgnoreApiFilter(optionsOrBuilders: Options | Builder[]) {
|
||||
}
|
||||
|
||||
return (file: string) => {
|
||||
if (!file.startsWith('api/')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (file.includes('/.')) {
|
||||
return false;
|
||||
}
|
||||
@@ -169,10 +178,7 @@ export function sortFiles(fileA: string, fileB: string) {
|
||||
return fileA.localeCompare(fileB);
|
||||
}
|
||||
|
||||
async function detectApiBuilders(
|
||||
files: string[],
|
||||
options: Options
|
||||
): Promise<Builder[]> {
|
||||
function detectApiBuilders(files: string[], options: Options): Builder[] {
|
||||
const builds = files
|
||||
.sort(sortFiles)
|
||||
.filter(getIgnoreApiFilter(options))
|
||||
@@ -188,10 +194,10 @@ async function detectApiBuilders(
|
||||
|
||||
// When a package has files that conflict with `/api` routes
|
||||
// e.g. Next.js pages/api we'll check it here and return an error.
|
||||
async function checkConflictingFiles(
|
||||
function checkConflictingFiles(
|
||||
files: string[],
|
||||
builders: Builder[]
|
||||
): Promise<ErrorResponse | null> {
|
||||
): ErrorResponse | null {
|
||||
// For Next.js
|
||||
if (builders.some(b => b.use.startsWith('@now/next'))) {
|
||||
const hasApiPages = files.some(file => file.startsWith('pages/api/'));
|
||||
@@ -211,10 +217,10 @@ async function checkConflictingFiles(
|
||||
|
||||
// When e.g. Next.js receives a `functions` property it has to make sure,
|
||||
// that it can handle those files, otherwise there are unused functions.
|
||||
async function checkUnusedFunctionsOnFrontendBuilder(
|
||||
function checkUnusedFunctionsOnFrontendBuilder(
|
||||
files: string[],
|
||||
builder: Builder
|
||||
): Promise<ErrorResponse | null> {
|
||||
): ErrorResponse | null {
|
||||
const { config: { functions = undefined } = {} } = builder;
|
||||
|
||||
if (!functions) return null;
|
||||
@@ -231,7 +237,9 @@ async function checkUnusedFunctionsOnFrontendBuilder(
|
||||
) {
|
||||
return {
|
||||
code: 'unused_function',
|
||||
message: `The function for ${matchedFile} can't be handled by any builder`,
|
||||
message:
|
||||
`The function for "${matchedFile}" can't be handled by any runtime. ` +
|
||||
`Please provide one with the "runtime" option.`,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -241,8 +249,6 @@ async function checkUnusedFunctionsOnFrontendBuilder(
|
||||
}
|
||||
|
||||
function validateFunctions(files: string[], { functions = {} }: Options) {
|
||||
const apiBuilders = getApiBuilders();
|
||||
|
||||
for (const [path, func] of Object.entries(functions)) {
|
||||
if (path.length > 256) {
|
||||
return {
|
||||
@@ -312,15 +318,6 @@ function validateFunctions(files: string[], { functions = {} }: Options) {
|
||||
'Function Runtimes must have a valid version, for example `now-php@1.0.0`.',
|
||||
};
|
||||
}
|
||||
|
||||
if (
|
||||
apiBuilders.some(b => func.runtime && func.runtime.startsWith(b.use))
|
||||
) {
|
||||
return {
|
||||
code: 'invalid_function_runtime',
|
||||
message: `The function Runtime ${func.runtime} is not a Community Runtime and must not be specified.`,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (func.includeFiles !== undefined) {
|
||||
@@ -349,7 +346,7 @@ function validateFunctions(files: string[], { functions = {} }: Options) {
|
||||
// to determine what builders to use
|
||||
export async function detectBuilders(
|
||||
files: string[],
|
||||
pkg?: PackageJson | undefined | null,
|
||||
detectorResult: Partial<DetectorResult> | null = null,
|
||||
options: Options = {}
|
||||
): Promise<{
|
||||
builders: Builder[] | null;
|
||||
@@ -370,19 +367,24 @@ export async function detectBuilders(
|
||||
}
|
||||
|
||||
// Detect all builders for the `api` directory before anything else
|
||||
const builders = await detectApiBuilders(files, options);
|
||||
const builders = detectApiBuilders(files, options);
|
||||
|
||||
if (pkg && hasBuildScript(pkg)) {
|
||||
const frontendBuilder = await detectFrontBuilder(pkg, builders, options);
|
||||
if (detectorResult && detectorResult.buildCommand) {
|
||||
const frontendBuilder = detectFrontBuilder(
|
||||
detectorResult,
|
||||
builders,
|
||||
files,
|
||||
options
|
||||
);
|
||||
builders.push(frontendBuilder);
|
||||
|
||||
const conflictError = await checkConflictingFiles(files, builders);
|
||||
const conflictError = checkConflictingFiles(files, builders);
|
||||
|
||||
if (conflictError) {
|
||||
warnings.push(conflictError);
|
||||
}
|
||||
|
||||
const unusedFunctionError = await checkUnusedFunctionsOnFrontendBuilder(
|
||||
const unusedFunctionError = checkUnusedFunctionsOnFrontendBuilder(
|
||||
files,
|
||||
frontendBuilder
|
||||
);
|
||||
@@ -394,21 +396,23 @@ export async function detectBuilders(
|
||||
warnings,
|
||||
};
|
||||
}
|
||||
} else {
|
||||
if (pkg && builders.length === 0) {
|
||||
// We only show this error when there are no api builders
|
||||
// since the dependencies of the pkg could be used for those
|
||||
errors.push(MISSING_BUILD_SCRIPT_ERROR);
|
||||
return { errors, warnings, builders: null };
|
||||
}
|
||||
|
||||
// We allow a `public` directory
|
||||
// when there are no build steps
|
||||
if (hasPublicDirectory(files)) {
|
||||
} else if (
|
||||
detectorResult &&
|
||||
detectorResult.outputDirectory &&
|
||||
hasDirectory(detectorResult.outputDirectory, files)
|
||||
) {
|
||||
builders.push({
|
||||
use: '@now/static',
|
||||
src: [...detectorResult.outputDirectory.split('/'), '**', '*']
|
||||
.filter(Boolean)
|
||||
.join('/'),
|
||||
config: { zeroConfig: true },
|
||||
});
|
||||
} else if (hasDirectory('public', files)) {
|
||||
builders.push({
|
||||
use: '@now/static',
|
||||
src: 'public/**/*',
|
||||
config,
|
||||
config: { zeroConfig: true },
|
||||
});
|
||||
} else if (
|
||||
builders.length > 0 &&
|
||||
@@ -419,10 +423,9 @@ export async function detectBuilders(
|
||||
builders.push({
|
||||
use: '@now/static',
|
||||
src: '!{api/**,package.json}',
|
||||
config,
|
||||
config: { zeroConfig: true },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
builders: builders.length ? builders : null,
|
||||
|
||||
299
packages/now-build-utils/src/detect-routes-legacy.ts
Normal file
@@ -0,0 +1,299 @@
|
||||
import { parse as parsePath } from 'path';
|
||||
import { Route } from '@now/routing-utils';
|
||||
import { Builder } from './types';
|
||||
import { getIgnoreApiFilter, sortFiles } from './detect-builders-legacy';
|
||||
|
||||
function escapeName(name: string) {
|
||||
const special = '[]^$.|?*+()'.split('');
|
||||
|
||||
for (const char of special) {
|
||||
name = name.replace(new RegExp(`\\${char}`, 'g'), `\\${char}`);
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
function joinPath(...segments: string[]) {
|
||||
const joinedPath = segments.join('/');
|
||||
return joinedPath.replace(/\/{2,}/g, '/');
|
||||
}
|
||||
|
||||
function concatArrayOfText(texts: string[]): string {
|
||||
if (texts.length <= 2) {
|
||||
return texts.join(' and ');
|
||||
}
|
||||
|
||||
const last = texts.pop();
|
||||
return `${texts.join(', ')}, and ${last}`;
|
||||
}
|
||||
|
||||
// Takes a filename or foldername, strips the extension
|
||||
// gets the part between the "[]" brackets.
|
||||
// It will return `null` if there are no brackets
|
||||
// and therefore no segment.
|
||||
function getSegmentName(segment: string): string | null {
|
||||
const { name } = parsePath(segment);
|
||||
|
||||
if (name.startsWith('[') && name.endsWith(']')) {
|
||||
return name.slice(1, -1);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function createRouteFromPath(filePath: string): Route {
|
||||
const parts = filePath.split('/');
|
||||
|
||||
let counter = 1;
|
||||
const query: string[] = [];
|
||||
|
||||
const srcParts = parts.map((segment, index): string => {
|
||||
const name = getSegmentName(segment);
|
||||
const isLast = index === parts.length - 1;
|
||||
|
||||
if (name !== null) {
|
||||
// We can't use `URLSearchParams` because `$` would get escaped
|
||||
query.push(`${name}=$${counter++}`);
|
||||
return `([^\\/]+)`;
|
||||
} else if (isLast) {
|
||||
const { name: fileName, ext } = parsePath(segment);
|
||||
const isIndex = fileName === 'index';
|
||||
const prefix = isIndex ? '\\/' : '';
|
||||
|
||||
const names = [
|
||||
isIndex ? prefix : `${fileName}\\/`,
|
||||
prefix + escapeName(fileName),
|
||||
prefix + escapeName(fileName) + escapeName(ext),
|
||||
].filter(Boolean);
|
||||
|
||||
// Either filename with extension, filename without extension
|
||||
// or nothing when the filename is `index`
|
||||
return `(${names.join('|')})${isIndex ? '?' : ''}`;
|
||||
}
|
||||
|
||||
return segment;
|
||||
});
|
||||
|
||||
const { name: fileName } = parsePath(filePath);
|
||||
const isIndex = fileName === 'index';
|
||||
|
||||
const src = isIndex
|
||||
? `^/${srcParts.slice(0, -1).join('/')}${srcParts.slice(-1)[0]}$`
|
||||
: `^/${srcParts.join('/')}$`;
|
||||
|
||||
const dest = `/${filePath}${query.length ? '?' : ''}${query.join('&')}`;
|
||||
|
||||
return { src, dest };
|
||||
}
|
||||
|
||||
// Check if the path partially matches and has the same
|
||||
// name for the path segment at the same position
|
||||
function partiallyMatches(pathA: string, pathB: string): boolean {
|
||||
const partsA = pathA.split('/');
|
||||
const partsB = pathB.split('/');
|
||||
|
||||
const long = partsA.length > partsB.length ? partsA : partsB;
|
||||
const short = long === partsA ? partsB : partsA;
|
||||
|
||||
let index = 0;
|
||||
|
||||
for (const segmentShort of short) {
|
||||
const segmentLong = long[index];
|
||||
|
||||
const nameLong = getSegmentName(segmentLong);
|
||||
const nameShort = getSegmentName(segmentShort);
|
||||
|
||||
// If there are no segments or the paths differ we
|
||||
// return as they are not matching
|
||||
if (segmentShort !== segmentLong && (!nameLong || !nameShort)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (nameLong !== nameShort) {
|
||||
return true;
|
||||
}
|
||||
|
||||
index += 1;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Counts how often a path occurs when all placeholders
|
||||
// got resolved, so we can check if they have conflicts
|
||||
function pathOccurrences(filePath: string, files: string[]): string[] {
|
||||
const getAbsolutePath = (unresolvedPath: string): string => {
|
||||
const { dir, name } = parsePath(unresolvedPath);
|
||||
const parts = joinPath(dir, name).split('/');
|
||||
return parts.map(part => part.replace(/\[.*\]/, '1')).join('/');
|
||||
};
|
||||
|
||||
const currentAbsolutePath = getAbsolutePath(filePath);
|
||||
|
||||
return files.reduce((prev: string[], file: string): string[] => {
|
||||
const absolutePath = getAbsolutePath(file);
|
||||
|
||||
if (absolutePath === currentAbsolutePath) {
|
||||
prev.push(file);
|
||||
} else if (partiallyMatches(filePath, file)) {
|
||||
prev.push(file);
|
||||
}
|
||||
|
||||
return prev;
|
||||
}, []);
|
||||
}
|
||||
|
||||
// Checks if a placeholder with the same name is used
|
||||
// multiple times inside the same path
|
||||
function getConflictingSegment(filePath: string): string | null {
|
||||
const segments = new Set<string>();
|
||||
|
||||
for (const segment of filePath.split('/')) {
|
||||
const name = getSegmentName(segment);
|
||||
|
||||
if (name !== null && segments.has(name)) {
|
||||
return name;
|
||||
}
|
||||
|
||||
if (name) {
|
||||
segments.add(name);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function sortFilesBySegmentCount(fileA: string, fileB: string): number {
|
||||
const lengthA = fileA.split('/').length;
|
||||
const lengthB = fileB.split('/').length;
|
||||
|
||||
if (lengthA > lengthB) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (lengthA < lengthB) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Paths that have the same segment length but
|
||||
// less placeholders are preferred
|
||||
const countSegments = (prev: number, segment: string) =>
|
||||
getSegmentName(segment) ? prev + 1 : 0;
|
||||
const segmentLengthA = fileA.split('/').reduce(countSegments, 0);
|
||||
const segmentLengthB = fileB.split('/').reduce(countSegments, 0);
|
||||
|
||||
if (segmentLengthA > segmentLengthB) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (segmentLengthA < segmentLengthB) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
interface RoutesResult {
|
||||
defaultRoutes: Route[] | null;
|
||||
error: { [key: string]: string } | null;
|
||||
}
|
||||
|
||||
async function detectApiRoutes(
|
||||
files: string[],
|
||||
builders: Builder[]
|
||||
): Promise<RoutesResult> {
|
||||
if (!files || files.length === 0) {
|
||||
return { defaultRoutes: null, error: null };
|
||||
}
|
||||
|
||||
// The deepest routes need to be
|
||||
// the first ones to get handled
|
||||
const sortedFiles = files
|
||||
.filter(getIgnoreApiFilter(builders))
|
||||
.sort(sortFiles)
|
||||
.sort(sortFilesBySegmentCount);
|
||||
|
||||
const defaultRoutes: Route[] = [];
|
||||
|
||||
for (const file of sortedFiles) {
|
||||
// We only consider every file in the api directory
|
||||
// as we will strip extensions as well as resolving "[segments]"
|
||||
if (!file.startsWith('api/')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const conflictingSegment = getConflictingSegment(file);
|
||||
|
||||
if (conflictingSegment) {
|
||||
return {
|
||||
defaultRoutes: null,
|
||||
error: {
|
||||
code: 'conflicting_path_segment',
|
||||
message:
|
||||
`The segment "${conflictingSegment}" occurs more than ` +
|
||||
`one time in your path "${file}". Please make sure that ` +
|
||||
`every segment in a path is unique`,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const occurrences = pathOccurrences(file, sortedFiles).filter(
|
||||
name => name !== file
|
||||
);
|
||||
|
||||
if (occurrences.length > 0) {
|
||||
const messagePaths = concatArrayOfText(
|
||||
occurrences.map(name => `"${name}"`)
|
||||
);
|
||||
|
||||
return {
|
||||
defaultRoutes: null,
|
||||
error: {
|
||||
code: 'conflicting_file_path',
|
||||
message:
|
||||
`Two or more files have conflicting paths or names. ` +
|
||||
`Please make sure path segments and filenames, without their extension, are unique. ` +
|
||||
`The path "${file}" has conflicts with ${messagePaths}`,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
defaultRoutes.push(createRouteFromPath(file));
|
||||
}
|
||||
|
||||
// 404 Route to disable directory listing
|
||||
if (defaultRoutes.length) {
|
||||
defaultRoutes.push({
|
||||
status: 404,
|
||||
src: '/api(\\/.*)?$',
|
||||
});
|
||||
}
|
||||
|
||||
return { defaultRoutes, error: null };
|
||||
}
|
||||
|
||||
function hasPublicBuilder(builders: Builder[]): boolean {
|
||||
return builders.some(
|
||||
builder =>
|
||||
builder.use === '@now/static' &&
|
||||
builder.src === 'public/**/*' &&
|
||||
builder.config &&
|
||||
builder.config.zeroConfig === true
|
||||
);
|
||||
}
|
||||
|
||||
export async function detectRoutesLegacy(
|
||||
files: string[],
|
||||
builders: Builder[]
|
||||
): Promise<RoutesResult> {
|
||||
const routesResult = await detectApiRoutes(files, builders);
|
||||
|
||||
if (routesResult.defaultRoutes && hasPublicBuilder(builders)) {
|
||||
routesResult.defaultRoutes.push({
|
||||
src: '/(.*)',
|
||||
dest: '/public/$1',
|
||||
});
|
||||
}
|
||||
|
||||
return routesResult;
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import { parse as parsePath } from 'path';
|
||||
import { Route, Builder } from './types';
|
||||
import { Route } from '@now/routing-utils';
|
||||
import { Builder } from './types';
|
||||
import { getIgnoreApiFilter, sortFiles } from './detect-builders';
|
||||
|
||||
function escapeName(name: string) {
|
||||
@@ -199,7 +200,8 @@ interface RoutesResult {
|
||||
|
||||
async function detectApiRoutes(
|
||||
files: string[],
|
||||
builders: Builder[]
|
||||
builders: Builder[],
|
||||
featHandleMiss: boolean
|
||||
): Promise<RoutesResult> {
|
||||
if (!files || files.length === 0) {
|
||||
return { defaultRoutes: null, error: null };
|
||||
@@ -212,12 +214,15 @@ async function detectApiRoutes(
|
||||
.sort(sortFiles)
|
||||
.sort(sortFilesBySegmentCount);
|
||||
|
||||
const defaultRoutes: Route[] = [];
|
||||
let defaultRoutes: Route[] = [];
|
||||
|
||||
for (const file of sortedFiles) {
|
||||
// We only consider every file in the api directory
|
||||
// as we will strip extensions as well as resolving "[segments]"
|
||||
if (!file.startsWith('api/')) {
|
||||
if (
|
||||
!file.startsWith('api/') &&
|
||||
!builders.some(b => b.src === file && b.config!.functions)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -261,36 +266,63 @@ async function detectApiRoutes(
|
||||
}
|
||||
|
||||
// 404 Route to disable directory listing
|
||||
if (defaultRoutes.length) {
|
||||
if (defaultRoutes.length > 0) {
|
||||
if (featHandleMiss) {
|
||||
defaultRoutes = [
|
||||
{ handle: 'miss' },
|
||||
{
|
||||
src: '/api/(.+)\\.\\w+',
|
||||
dest: '/api/$1',
|
||||
check: true,
|
||||
},
|
||||
{
|
||||
status: 404,
|
||||
src: '/api(/.*)?$',
|
||||
continue: true,
|
||||
},
|
||||
];
|
||||
} else {
|
||||
defaultRoutes.push({
|
||||
status: 404,
|
||||
src: '/api(\\/.*)?$',
|
||||
src: '/api(/.*)?$',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return { defaultRoutes, error: null };
|
||||
}
|
||||
|
||||
function hasPublicBuilder(builders: Builder[]): boolean {
|
||||
return builders.some(
|
||||
function getPublicBuilder(builders: Builder[]): Builder | null {
|
||||
const builder = builders.find(
|
||||
builder =>
|
||||
builder.use === '@now/static' &&
|
||||
builder.src === 'public/**/*' &&
|
||||
/^.*\/\*\*\/\*$/.test(builder.src) &&
|
||||
builder.config &&
|
||||
builder.config.zeroConfig === true
|
||||
);
|
||||
|
||||
return builder || null;
|
||||
}
|
||||
|
||||
export function detectOutputDirectory(builders: Builder[]): string | null {
|
||||
// TODO: We eventually want to save the output directory to
|
||||
// builder.config.outputDirectory so it is only detected once
|
||||
const publicBuilder = getPublicBuilder(builders);
|
||||
return publicBuilder ? publicBuilder.src.replace('/**/*', '') : null;
|
||||
}
|
||||
|
||||
export async function detectRoutes(
|
||||
files: string[],
|
||||
builders: Builder[]
|
||||
builders: Builder[],
|
||||
featHandleMiss = false
|
||||
): Promise<RoutesResult> {
|
||||
const routesResult = await detectApiRoutes(files, builders);
|
||||
const routesResult = await detectApiRoutes(files, builders, featHandleMiss);
|
||||
const directory = detectOutputDirectory(builders);
|
||||
|
||||
if (routesResult.defaultRoutes && hasPublicBuilder(builders)) {
|
||||
if (routesResult.defaultRoutes && directory && !featHandleMiss) {
|
||||
routesResult.defaultRoutes.push({
|
||||
src: '/(.*)',
|
||||
dest: '/public/$1',
|
||||
dest: `/${directory}/$1`,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
import { DetectorParameters, DetectorResult } from '../types';
|
||||
|
||||
export default async function detectAngular({
|
||||
fs: { hasDependency },
|
||||
fs: { getPackageJsonBuildCommand, getDependencyVersion },
|
||||
}: DetectorParameters): Promise<DetectorResult> {
|
||||
const hasAngular = await hasDependency('@angular/cli');
|
||||
if (!hasAngular) return false;
|
||||
const version = await getDependencyVersion('@angular/cli');
|
||||
if (!version) return false;
|
||||
return {
|
||||
buildCommand: ['ng', 'build'],
|
||||
buildDirectory: 'dist',
|
||||
devCommand: ['ng', 'serve', '--port', '$PORT'],
|
||||
buildCommand: (await getPackageJsonBuildCommand()) || 'ng build',
|
||||
outputDirectory: 'dist',
|
||||
devCommand: 'ng serve --port $PORT',
|
||||
framework: {
|
||||
slug: '@angular/cli',
|
||||
version,
|
||||
},
|
||||
minNodeRange: '10.x',
|
||||
routes: [
|
||||
{
|
||||
|
||||
@@ -1,17 +1,22 @@
|
||||
import { DetectorParameters, DetectorResult } from '../types';
|
||||
|
||||
export default async function detectBrunch({
|
||||
fs: { hasDependency, exists },
|
||||
fs: { exists, getPackageJsonBuildCommand, getDependencyVersion },
|
||||
}: DetectorParameters): Promise<DetectorResult> {
|
||||
const hasBrunch = await hasDependency('brunch');
|
||||
if (!hasBrunch) return false;
|
||||
const version = await getDependencyVersion('brunch');
|
||||
if (!version) return false;
|
||||
|
||||
const hasConfig = await exists('brunch-config.js');
|
||||
if (!hasConfig) return false;
|
||||
|
||||
return {
|
||||
buildCommand: ['brunch', 'build', '--production'],
|
||||
buildDirectory: 'public',
|
||||
devCommand: ['brunch', 'watch', '--server', '--port', '$PORT'],
|
||||
buildCommand:
|
||||
(await getPackageJsonBuildCommand()) || 'brunch build --production',
|
||||
outputDirectory: 'public',
|
||||
devCommand: 'brunch watch --server --port $PORT',
|
||||
framework: {
|
||||
slug: 'brunch',
|
||||
version,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,17 +1,21 @@
|
||||
import { DetectorParameters, DetectorResult } from '../types';
|
||||
|
||||
export default async function detectCreateReactAppEjected({
|
||||
fs: { hasDependency },
|
||||
fs: { getDependencyVersion },
|
||||
}: DetectorParameters): Promise<DetectorResult> {
|
||||
const hasReactDevUtils = await hasDependency('react-dev-utils');
|
||||
if (!hasReactDevUtils) {
|
||||
const version = await getDependencyVersion('react-dev-utils');
|
||||
if (!version) {
|
||||
return false;
|
||||
}
|
||||
return {
|
||||
buildCommand: ['node', 'scripts/build.js'],
|
||||
buildDirectory: 'build',
|
||||
devCommand: ['node', 'scripts/start.js'],
|
||||
devEnv: { BROWSER: 'none' },
|
||||
buildCommand: 'node scripts/build.js',
|
||||
outputDirectory: 'build',
|
||||
devCommand: 'node scripts/start.js',
|
||||
framework: {
|
||||
slug: 'react-dev-utils',
|
||||
version,
|
||||
},
|
||||
devVariables: { BROWSER: 'none' },
|
||||
routes: [
|
||||
{
|
||||
src: '/static/(.*)',
|
||||
|
||||
@@ -1,17 +1,21 @@
|
||||
import { DetectorParameters, DetectorResult } from '../types';
|
||||
|
||||
export default async function detectCreateReactApp({
|
||||
fs: { hasDependency },
|
||||
fs: { getPackageJsonBuildCommand, getDependencyVersion },
|
||||
}: DetectorParameters): Promise<DetectorResult> {
|
||||
const hasReactScripts = await hasDependency('react-scripts');
|
||||
if (!hasReactScripts) {
|
||||
const version = await getDependencyVersion('react-scripts');
|
||||
if (!version) {
|
||||
return false;
|
||||
}
|
||||
return {
|
||||
buildCommand: ['react-scripts', 'build'],
|
||||
buildDirectory: 'build',
|
||||
devCommand: ['react-scripts', 'start'],
|
||||
devEnv: { BROWSER: 'none' },
|
||||
buildCommand: (await getPackageJsonBuildCommand()) || 'react-scripts build',
|
||||
outputDirectory: 'build',
|
||||
devCommand: 'react-scripts start',
|
||||
devVariables: { BROWSER: 'none' },
|
||||
framework: {
|
||||
slug: 'react-scripts',
|
||||
version,
|
||||
},
|
||||
routes: [
|
||||
{
|
||||
src: '/static/(.*)',
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
import { DetectorParameters, DetectorResult } from '../types';
|
||||
|
||||
export default async function detectDocusaurus({
|
||||
fs: { hasDependency },
|
||||
fs: { getPackageJsonBuildCommand, getDependencyVersion },
|
||||
}: DetectorParameters): Promise<DetectorResult> {
|
||||
const hasDocusaurus = await hasDependency('docusaurus');
|
||||
if (!hasDocusaurus) return false;
|
||||
const version = await getDependencyVersion('docusaurus');
|
||||
if (!version) return false;
|
||||
return {
|
||||
buildCommand: ['docusaurus-build'],
|
||||
buildDirectory: 'build',
|
||||
devCommand: ['docusaurus-start', '--port', '$PORT'],
|
||||
buildCommand: (await getPackageJsonBuildCommand()) || 'docusaurus-build',
|
||||
outputDirectory: 'build',
|
||||
devCommand: 'docusaurus-start --port $PORT',
|
||||
framework: {
|
||||
slug: 'docusaurus',
|
||||
version,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,20 +1,17 @@
|
||||
import { DetectorParameters, DetectorResult } from '../types';
|
||||
|
||||
export default async function detectEleventy({
|
||||
fs: { hasDependency },
|
||||
fs: { getDependencyVersion },
|
||||
}: DetectorParameters): Promise<DetectorResult> {
|
||||
const hasEleventy = await hasDependency('@11ty/eleventy');
|
||||
if (!hasEleventy) return false;
|
||||
const version = await getDependencyVersion('@11ty/eleventy');
|
||||
if (!version) return false;
|
||||
return {
|
||||
buildCommand: ['npx', '@11ty/eleventy'],
|
||||
buildDirectory: '_site',
|
||||
devCommand: [
|
||||
'npx',
|
||||
'@11ty/eleventy',
|
||||
'--serve',
|
||||
'--watch',
|
||||
'--port',
|
||||
'$PORT',
|
||||
],
|
||||
buildCommand: 'npx @11ty/eleventy',
|
||||
outputDirectory: '_site',
|
||||
devCommand: 'npx @11ty/eleventy --serve --watch --port $PORT',
|
||||
framework: {
|
||||
slug: '@11ty/eleventy',
|
||||
version,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
import { DetectorParameters, DetectorResult } from '../types';
|
||||
|
||||
export default async function detectEmber({
|
||||
fs: { hasDependency },
|
||||
fs: { getPackageJsonBuildCommand, getDependencyVersion },
|
||||
}: DetectorParameters): Promise<DetectorResult> {
|
||||
const hasEmber = await hasDependency('ember-cli');
|
||||
if (!hasEmber) return false;
|
||||
const version = await getDependencyVersion('ember-cli');
|
||||
if (!version) return false;
|
||||
return {
|
||||
buildCommand: ['ember', 'build'],
|
||||
buildDirectory: 'dist',
|
||||
devCommand: ['ember', 'serve', '--port', '$PORT'],
|
||||
buildCommand: (await getPackageJsonBuildCommand()) || 'ember build',
|
||||
outputDirectory: 'dist',
|
||||
devCommand: 'ember serve --port $PORT',
|
||||
framework: {
|
||||
slug: 'ember-cli',
|
||||
version,
|
||||
},
|
||||
routes: [
|
||||
{
|
||||
handle: 'filesystem',
|
||||
|
||||
@@ -102,6 +102,31 @@ export default abstract class DetectorFilesystem {
|
||||
const { dependencies = {}, devDependencies = {} } = pkg || {};
|
||||
return name in dependencies || name in devDependencies;
|
||||
};
|
||||
|
||||
public isNpm = async (): Promise<boolean> => {
|
||||
return this.exists('package-lock.json');
|
||||
};
|
||||
|
||||
public getPackageJsonCommand = async (
|
||||
name: string
|
||||
): Promise<string | null> => {
|
||||
const pkg = await this.readPackageJson();
|
||||
const { scripts = {} } = pkg || {};
|
||||
return scripts[name] || null;
|
||||
};
|
||||
|
||||
public getPackageJsonBuildCommand = async (): Promise<string | null> => {
|
||||
const buildCommand = (await this.isNpm())
|
||||
? 'npm run build'
|
||||
: 'yarn run build';
|
||||
return (await this.getPackageJsonCommand('build')) ? buildCommand : null;
|
||||
};
|
||||
|
||||
public getDependencyVersion = async (name: string): Promise<string> => {
|
||||
const pkg = await this.readPackageJson();
|
||||
const { dependencies = {}, devDependencies = {} } = pkg || {};
|
||||
return dependencies[name] || devDependencies[name];
|
||||
};
|
||||
}
|
||||
|
||||
async function nullEnoent<T>(p: Promise<T>): Promise<T | null> {
|
||||
|
||||
@@ -1,16 +1,20 @@
|
||||
import { DetectorParameters, DetectorResult } from '../types';
|
||||
|
||||
export default async function detectGatsby({
|
||||
fs: { hasDependency },
|
||||
fs: { getPackageJsonBuildCommand, getDependencyVersion },
|
||||
}: DetectorParameters): Promise<DetectorResult> {
|
||||
const hasGatsby = await hasDependency('gatsby');
|
||||
if (!hasGatsby) {
|
||||
const version = await getDependencyVersion('gatsby');
|
||||
if (!version) {
|
||||
return false;
|
||||
}
|
||||
return {
|
||||
buildCommand: ['gatsby', 'build'],
|
||||
buildDirectory: 'public',
|
||||
devCommand: ['gatsby', 'develop', '-p', '$PORT'],
|
||||
buildCommand: (await getPackageJsonBuildCommand()) || 'gatsby build',
|
||||
outputDirectory: 'public',
|
||||
devCommand: 'gatsby develop -p $PORT',
|
||||
framework: {
|
||||
slug: 'gatsby',
|
||||
version,
|
||||
},
|
||||
cachePattern: '.cache/**',
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import { DetectorParameters, DetectorResult } from '../types';
|
||||
|
||||
export default async function detectGenericNodeProject({
|
||||
fs: { isNpm, getPackageJsonCommand },
|
||||
}: DetectorParameters): Promise<DetectorResult> {
|
||||
const useNpm = await isNpm();
|
||||
const devCommand = await getPackageJsonCommand('dev');
|
||||
const buildCommand = await getPackageJsonCommand('build');
|
||||
|
||||
if (!buildCommand) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return {
|
||||
buildCommand: `${useNpm ? 'npm' : 'yarn'} run build`,
|
||||
devCommand: useNpm && devCommand ? `yarn run ${devCommand}` : undefined,
|
||||
outputDirectory: 'public',
|
||||
};
|
||||
}
|
||||
@@ -1,15 +1,19 @@
|
||||
import { DetectorParameters, DetectorResult } from '../types';
|
||||
|
||||
export default async function detectGridsome({
|
||||
fs: { hasDependency },
|
||||
fs: { getPackageJsonBuildCommand, getDependencyVersion },
|
||||
}: DetectorParameters): Promise<DetectorResult> {
|
||||
const hasGridsome = await hasDependency('gridsome');
|
||||
if (!hasGridsome) {
|
||||
const version = await getDependencyVersion('gridsome');
|
||||
if (!version) {
|
||||
return false;
|
||||
}
|
||||
return {
|
||||
buildCommand: ['gridsome', 'build'],
|
||||
buildDirectory: 'dist',
|
||||
devCommand: ['gridsome', 'develop', '-p', '$PORT'],
|
||||
buildCommand: (await getPackageJsonBuildCommand()) || 'gridsome build',
|
||||
outputDirectory: 'dist',
|
||||
devCommand: 'gridsome develop -p $PORT',
|
||||
framework: {
|
||||
slug: 'gridsom',
|
||||
version,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
import { DetectorParameters, DetectorResult } from '../types';
|
||||
|
||||
export default async function detectHexo({
|
||||
fs: { hasDependency },
|
||||
fs: { getPackageJsonBuildCommand, getDependencyVersion },
|
||||
}: DetectorParameters): Promise<DetectorResult> {
|
||||
const hasHexo = await hasDependency('hexo');
|
||||
if (!hasHexo) return false;
|
||||
const version = await getDependencyVersion('hexo');
|
||||
if (!version) return false;
|
||||
return {
|
||||
buildCommand: ['hexo', 'generate'],
|
||||
buildDirectory: 'public',
|
||||
devCommand: ['hexo', 'server', '--port', '$PORT'],
|
||||
buildCommand: (await getPackageJsonBuildCommand()) || 'hexo generate',
|
||||
outputDirectory: 'public',
|
||||
devCommand: 'hexo server --port $PORT',
|
||||
framework: {
|
||||
slug: 'hexo',
|
||||
version,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -19,8 +19,12 @@ export default async function detectHugo({
|
||||
return false;
|
||||
}
|
||||
return {
|
||||
buildCommand: ['hugo'],
|
||||
buildDirectory: config.publishDir || 'public',
|
||||
devCommand: ['hugo', 'server', '-D', '-w', '-p', '$PORT'],
|
||||
buildCommand: 'hugo',
|
||||
outputDirectory: config.publishDir || 'public',
|
||||
devCommand: 'hugo server -D -w -p $PORT',
|
||||
framework: {
|
||||
slug: 'hugo',
|
||||
version: 'latest',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import docusaurus from './docusaurus';
|
||||
import eleventy from './eleventy';
|
||||
import ember from './ember';
|
||||
import gatsby from './gatsby';
|
||||
import genericNodeProject from './generic-node-project';
|
||||
import gridsome from './gridsome';
|
||||
import hexo from './hexo';
|
||||
import hugo from './hugo';
|
||||
@@ -81,5 +82,8 @@ export async function detectDefaults(
|
||||
d = params.detectors || detectors;
|
||||
result = await firstTruthy(d.map(detector => detector(params)));
|
||||
}
|
||||
if (!result) {
|
||||
result = await genericNodeProject(params);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -15,16 +15,12 @@ export default async function detectJekyll({
|
||||
return false;
|
||||
}
|
||||
return {
|
||||
buildCommand: ['jekyll', 'build'],
|
||||
buildDirectory: config.destination || '_site',
|
||||
devCommand: [
|
||||
'bundle',
|
||||
'exec',
|
||||
'jekyll',
|
||||
'serve',
|
||||
'--watch',
|
||||
'--port',
|
||||
'$PORT',
|
||||
],
|
||||
buildCommand: 'jekyll build',
|
||||
outputDirectory: config.destination || '_site',
|
||||
devCommand: 'bundle exec jekyll serve --watch --port $PORT',
|
||||
framework: {
|
||||
slug: 'jekyll',
|
||||
version: 'latest',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -7,8 +7,12 @@ export default async function detectMiddleman({
|
||||
if (!hasConfig) return false;
|
||||
|
||||
return {
|
||||
buildCommand: ['bundle', 'exec', 'middleman', 'build'],
|
||||
buildDirectory: 'build',
|
||||
devCommand: ['bundle', 'exec', 'middleman', 'server', '-p', '$PORT'],
|
||||
buildCommand: 'bundle exec middleman build',
|
||||
outputDirectory: 'build',
|
||||
devCommand: 'bundle exec middleman server -p $PORT',
|
||||
framework: {
|
||||
slug: 'middleman',
|
||||
version: 'latest',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
import { DetectorParameters, DetectorResult } from '../types';
|
||||
|
||||
export default async function detectNext({
|
||||
fs: { hasDependency },
|
||||
fs: { getPackageJsonBuildCommand, getDependencyVersion },
|
||||
}: DetectorParameters): Promise<DetectorResult> {
|
||||
const hasNext = await hasDependency('next');
|
||||
if (!hasNext) return false;
|
||||
const version = await getDependencyVersion('next');
|
||||
if (!version) return false;
|
||||
return {
|
||||
buildCommand: ['next', 'build'],
|
||||
buildDirectory: 'build',
|
||||
devCommand: ['next', '-p', '$PORT'],
|
||||
buildCommand: (await getPackageJsonBuildCommand()) || 'next build',
|
||||
outputDirectory: '.next/static',
|
||||
devCommand: 'next -p $PORT',
|
||||
framework: {
|
||||
slug: 'next',
|
||||
version,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
import { DetectorParameters, DetectorResult } from '../types';
|
||||
|
||||
export default async function detectPolymer({
|
||||
fs: { hasDependency },
|
||||
fs: { getPackageJsonBuildCommand, getDependencyVersion },
|
||||
}: DetectorParameters): Promise<DetectorResult> {
|
||||
const hasPolymer = await hasDependency('polymer-cli');
|
||||
if (!hasPolymer) return false;
|
||||
const version = await getDependencyVersion('polymer-cli');
|
||||
if (!version) return false;
|
||||
return {
|
||||
buildCommand: ['polymer', 'build'],
|
||||
buildDirectory: 'build',
|
||||
devCommand: ['polymer', 'serve', '--port', '$PORT'],
|
||||
buildCommand: (await getPackageJsonBuildCommand()) || 'polymer build',
|
||||
outputDirectory: 'build',
|
||||
devCommand: 'polymer serve --port $PORT',
|
||||
framework: {
|
||||
slug: 'polymer-cli',
|
||||
version,
|
||||
},
|
||||
routes: [
|
||||
{
|
||||
handle: 'filesystem',
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
import { DetectorParameters, DetectorResult } from '../types';
|
||||
|
||||
export default async function detectPreact({
|
||||
fs: { hasDependency },
|
||||
fs: { getPackageJsonBuildCommand, getDependencyVersion },
|
||||
}: DetectorParameters): Promise<DetectorResult> {
|
||||
const hasPreact = await hasDependency('preact-cli');
|
||||
if (!hasPreact) return false;
|
||||
const version = await getDependencyVersion('preact-cli');
|
||||
if (!version) return false;
|
||||
return {
|
||||
buildCommand: ['preact', 'build'],
|
||||
buildDirectory: 'build',
|
||||
devCommand: ['preact', 'watch', '--port', '$PORT'],
|
||||
buildCommand: (await getPackageJsonBuildCommand()) || 'preact build',
|
||||
outputDirectory: 'build',
|
||||
devCommand: 'preact watch --port $PORT',
|
||||
framework: {
|
||||
slug: 'preact-cli',
|
||||
version,
|
||||
},
|
||||
routes: [
|
||||
{
|
||||
handle: 'filesystem',
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { DetectorParameters, DetectorResult } from '../types';
|
||||
|
||||
export default async function detectSaber({
|
||||
fs: { hasDependency },
|
||||
fs: { getPackageJsonBuildCommand, getDependencyVersion },
|
||||
}: DetectorParameters): Promise<DetectorResult> {
|
||||
const hasSaber = await hasDependency('saber');
|
||||
if (!hasSaber) return false;
|
||||
const version = await getDependencyVersion('saber');
|
||||
if (!version) return false;
|
||||
return {
|
||||
buildCommand: ['saber', 'build'],
|
||||
buildDirectory: 'public',
|
||||
devCommand: ['saber', '--port', '$PORT'],
|
||||
buildCommand: (await getPackageJsonBuildCommand()) || 'saber build',
|
||||
outputDirectory: 'public',
|
||||
devCommand: 'saber --port $PORT',
|
||||
routes: [
|
||||
{
|
||||
src: '/_saber/.*',
|
||||
@@ -23,5 +23,9 @@ export default async function detectSaber({
|
||||
dest: '404.html',
|
||||
},
|
||||
],
|
||||
framework: {
|
||||
slug: 'saber',
|
||||
version,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
import { DetectorParameters, DetectorResult } from '../types';
|
||||
|
||||
export default async function detectSapper({
|
||||
fs: { hasDependency },
|
||||
fs: { getPackageJsonBuildCommand, getDependencyVersion },
|
||||
}: DetectorParameters): Promise<DetectorResult> {
|
||||
const hasSapper = await hasDependency('sapper');
|
||||
if (!hasSapper) return false;
|
||||
const version = await getDependencyVersion('sapper');
|
||||
if (!version) return false;
|
||||
return {
|
||||
buildCommand: ['sapper', 'export'],
|
||||
buildDirectory: '__sapper__/export',
|
||||
devCommand: ['sapper', 'dev', '--port', '$PORT'],
|
||||
buildCommand: (await getPackageJsonBuildCommand()) || 'sapper export',
|
||||
outputDirectory: '__sapper__/export',
|
||||
devCommand: 'sapper dev --port $PORT',
|
||||
framework: {
|
||||
slug: 'sapper',
|
||||
version,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,22 +1,18 @@
|
||||
import { DetectorParameters, DetectorResult } from '../types';
|
||||
|
||||
export default async function detectStencil({
|
||||
fs: { hasDependency },
|
||||
fs: { getPackageJsonBuildCommand, getDependencyVersion },
|
||||
}: DetectorParameters): Promise<DetectorResult> {
|
||||
const hasStencil = await hasDependency('@stencil/core');
|
||||
if (!hasStencil) return false;
|
||||
const version = await getDependencyVersion('@stencil/core');
|
||||
if (!version) return false;
|
||||
return {
|
||||
buildCommand: ['stencil', 'build'],
|
||||
buildDirectory: 'www',
|
||||
devCommand: [
|
||||
'stencil',
|
||||
'build',
|
||||
'--dev',
|
||||
'--watch',
|
||||
'--serve',
|
||||
'--port',
|
||||
'$PORT',
|
||||
],
|
||||
buildCommand: (await getPackageJsonBuildCommand()) || 'stencil build',
|
||||
outputDirectory: 'www',
|
||||
devCommand: 'stencil build --dev --watch --serve --port $PORT',
|
||||
framework: {
|
||||
slug: '@stencil/core',
|
||||
version,
|
||||
},
|
||||
routes: [
|
||||
{
|
||||
handle: 'filesystem',
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
import { DetectorParameters, DetectorResult } from '../types';
|
||||
|
||||
export default async function detectSvelte({
|
||||
fs: { hasDependency },
|
||||
fs: { getPackageJsonBuildCommand, getDependencyVersion },
|
||||
}: DetectorParameters): Promise<DetectorResult> {
|
||||
const hasSvelte = await hasDependency('sirv-cli');
|
||||
if (!hasSvelte) return false;
|
||||
const version = await getDependencyVersion('sirv-cli');
|
||||
if (!version) return false;
|
||||
return {
|
||||
buildCommand: ['rollup', '-c'],
|
||||
buildDirectory: 'public',
|
||||
devCommand: ['sirv', 'public', '--single', '--dev', ' --port', '$PORT'],
|
||||
buildCommand: (await getPackageJsonBuildCommand()) || 'rollup -c',
|
||||
outputDirectory: 'public',
|
||||
devCommand: 'sirv public --single --dev --port $PORT',
|
||||
framework: {
|
||||
slug: 'sirv-cli',
|
||||
version,
|
||||
},
|
||||
routes: [
|
||||
{
|
||||
handle: 'filesystem',
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
import { DetectorParameters, DetectorResult } from '../types';
|
||||
|
||||
export default async function detectUmiJS({
|
||||
fs: { hasDependency },
|
||||
fs: { getPackageJsonBuildCommand, getDependencyVersion },
|
||||
}: DetectorParameters): Promise<DetectorResult> {
|
||||
const hasUmi = await hasDependency('umi');
|
||||
if (!hasUmi) return false;
|
||||
const version = await getDependencyVersion('umi');
|
||||
if (!version) return false;
|
||||
return {
|
||||
buildCommand: ['umi', 'build'],
|
||||
buildDirectory: 'dist',
|
||||
devCommand: ['umi', 'dev', '--port', '$PORT'],
|
||||
buildCommand: (await getPackageJsonBuildCommand()) || 'umi build',
|
||||
outputDirectory: 'dist',
|
||||
devCommand: 'umi dev --port $PORT',
|
||||
framework: {
|
||||
slug: 'umi',
|
||||
version,
|
||||
},
|
||||
routes: [
|
||||
{
|
||||
handle: 'filesystem',
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
import { DetectorParameters, DetectorResult } from '../types';
|
||||
|
||||
export default async function detectVue({
|
||||
fs: { hasDependency },
|
||||
fs: { getPackageJsonBuildCommand, getDependencyVersion },
|
||||
}: DetectorParameters): Promise<DetectorResult> {
|
||||
const hasVue = await hasDependency('@vue/cli-service');
|
||||
if (!hasVue) return false;
|
||||
const version = await getDependencyVersion('@vue/cli-service');
|
||||
if (!version) return false;
|
||||
return {
|
||||
buildCommand: ['vue-cli-service', 'build'],
|
||||
buildDirectory: 'dist',
|
||||
devCommand: ['vue-cli-service', 'serve', '--port', '$PORT'],
|
||||
buildCommand:
|
||||
(await getPackageJsonBuildCommand()) || 'vue-cli-service build',
|
||||
outputDirectory: 'dist',
|
||||
devCommand: 'vue-cli-service serve --port $PORT',
|
||||
framework: {
|
||||
slug: '@vue/cli-service',
|
||||
version,
|
||||
},
|
||||
routes: [
|
||||
{
|
||||
src: '^/[^/]*\\.(js|txt|ico|json)',
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import path from 'path';
|
||||
import debug from '../debug';
|
||||
import FileFsRef from '../file-fs-ref';
|
||||
import { File, Files, Meta } from '../types';
|
||||
import { remove, mkdirp, readlink, symlink } from 'fs-extra';
|
||||
@@ -39,8 +40,12 @@ export default async function download(
|
||||
basePath: string,
|
||||
meta?: Meta
|
||||
): Promise<DownloadedFiles> {
|
||||
const { isDev = false, skipDownload = false, filesChanged = null, filesRemoved = null } =
|
||||
meta || {};
|
||||
const {
|
||||
isDev = false,
|
||||
skipDownload = false,
|
||||
filesChanged = null,
|
||||
filesRemoved = null,
|
||||
} = meta || {};
|
||||
|
||||
if (isDev || skipDownload) {
|
||||
// In `now dev`, the `download()` function is a no-op because
|
||||
@@ -48,11 +53,14 @@ export default async function download(
|
||||
// source files are already available.
|
||||
return files as DownloadedFiles;
|
||||
}
|
||||
debug('Downloading deployment source files...');
|
||||
|
||||
const start = Date.now();
|
||||
const files2: DownloadedFiles = {};
|
||||
const filenames = Object.keys(files);
|
||||
|
||||
await Promise.all(
|
||||
Object.keys(files).map(async name => {
|
||||
filenames.map(async name => {
|
||||
// If the file does not exist anymore, remove it.
|
||||
if (Array.isArray(filesRemoved) && filesRemoved.includes(name)) {
|
||||
await removeFile(basePath, name);
|
||||
@@ -71,5 +79,8 @@ export default async function download(
|
||||
})
|
||||
);
|
||||
|
||||
const duration = Date.now() - start;
|
||||
debug(`Downloaded ${filenames.length} source files: ${duration}ms`);
|
||||
|
||||
return files2;
|
||||
}
|
||||
|
||||
@@ -1,13 +1,21 @@
|
||||
import { intersects } from 'semver';
|
||||
import boxen from 'boxen';
|
||||
import { NodeVersion } from '../types';
|
||||
import debug from '../debug';
|
||||
|
||||
const supportedOptions: NodeVersion[] = [
|
||||
const allOptions: NodeVersion[] = [
|
||||
{ major: 12, range: '12.x', runtime: 'nodejs12.x' },
|
||||
{ major: 10, range: '10.x', runtime: 'nodejs10.x' },
|
||||
{ major: 8, range: '8.10.x', runtime: 'nodejs8.10' },
|
||||
{
|
||||
major: 8,
|
||||
range: '8.10.x',
|
||||
runtime: 'nodejs8.10',
|
||||
discontinueDate: new Date('2020-01-06'),
|
||||
},
|
||||
];
|
||||
|
||||
const supportedOptions = allOptions.filter(o => !isDiscontinued(o));
|
||||
|
||||
// This version should match Fargate's default in the PATH
|
||||
// Today that is Node 8
|
||||
export const defaultSelection = supportedOptions.find(
|
||||
@@ -28,13 +36,14 @@ export async function getSupportedNodeVersion(
|
||||
);
|
||||
}
|
||||
} else {
|
||||
const found = supportedOptions.some(o => {
|
||||
const found = allOptions.some(o => {
|
||||
// the array is already in order so return the first
|
||||
// match which will be the newest version of node
|
||||
selection = o;
|
||||
return intersects(o.range, engineRange);
|
||||
});
|
||||
if (found) {
|
||||
const discontinued = isDiscontinued(selection);
|
||||
if (found && !discontinued) {
|
||||
if (!silent) {
|
||||
debug(
|
||||
'Found `engines` in `package.json`, selecting range: ' +
|
||||
@@ -42,15 +51,50 @@ export async function getSupportedNodeVersion(
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if (!silent) {
|
||||
throw new Error(
|
||||
'Found `engines` in `package.json` with an unsupported node range: ' +
|
||||
'Found `engines` in `package.json` with an unsupported Node.js version range: ' +
|
||||
engineRange +
|
||||
'\nPlease use one of the following supported ranges: ' +
|
||||
JSON.stringify(supportedOptions.map(o => o.range))
|
||||
JSON.stringify(supportedOptions.map(o => o.range)) +
|
||||
(discontinued
|
||||
? '\nThis change is the result of a decision made by an upstream infrastructure provider (AWS).' +
|
||||
'\nRead more: https://docs.aws.amazon.com/lambda/latest/dg/runtime-support-policy.html'
|
||||
: '')
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const { range, discontinueDate } = selection;
|
||||
if (discontinueDate && !isDiscontinued(selection)) {
|
||||
const d = discontinueDate.toISOString().split('T')[0];
|
||||
const validRanges = supportedOptions
|
||||
.filter(o => !o.discontinueDate)
|
||||
.map(o => o.range);
|
||||
const prevTerm = process.env.TERM;
|
||||
if (!prevTerm) {
|
||||
// workaround for https://github.com/sindresorhus/term-size/issues/13
|
||||
process.env.TERM = 'xterm';
|
||||
}
|
||||
console.warn(
|
||||
boxen(
|
||||
'NOTICE' +
|
||||
'\n' +
|
||||
`\nNode.js version ${range} has reached end-of-life.` +
|
||||
`\nAs a result, deployments created on or after ${d} will fail to build.` +
|
||||
'\nPlease use one of the following supported `engines` in `package.json`: ' +
|
||||
JSON.stringify(validRanges) +
|
||||
'\nThis change is the result of a decision made by an upstream infrastructure provider (AWS).' +
|
||||
'\nRead more: https://docs.aws.amazon.com/lambda/latest/dg/runtime-support-policy.html',
|
||||
{ padding: 1 }
|
||||
)
|
||||
);
|
||||
process.env.TERM = prevTerm;
|
||||
}
|
||||
|
||||
return selection;
|
||||
}
|
||||
|
||||
function isDiscontinued({ discontinueDate }: NodeVersion): boolean {
|
||||
const today = new Date();
|
||||
return discontinueDate !== undefined && discontinueDate <= today;
|
||||
}
|
||||
|
||||
@@ -39,6 +39,66 @@ export function spawnAsync(
|
||||
});
|
||||
}
|
||||
|
||||
export function execAsync(
|
||||
command: string,
|
||||
args: string[],
|
||||
opts: SpawnOptions = {}
|
||||
) {
|
||||
return new Promise<{ stdout: string; stderr: string; code: number }>(
|
||||
(resolve, reject) => {
|
||||
opts.stdio = 'pipe';
|
||||
|
||||
const stdoutList: Buffer[] = [];
|
||||
const stderrList: Buffer[] = [];
|
||||
|
||||
const child = spawn(command, args, opts);
|
||||
|
||||
child.stderr!.on('data', data => {
|
||||
stderrList.push(data);
|
||||
});
|
||||
|
||||
child.stdout!.on('data', data => {
|
||||
stdoutList.push(data);
|
||||
});
|
||||
|
||||
child.on('error', reject);
|
||||
child.on('close', (code, signal) => {
|
||||
if (code !== 0) {
|
||||
return reject(
|
||||
new Error(
|
||||
`Program "${command}" exited with non-zero exit code ${code} ${signal}.`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return resolve({
|
||||
code,
|
||||
stdout: Buffer.concat(stdoutList).toString(),
|
||||
stderr: Buffer.concat(stderrList).toString(),
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
export function spawnCommand(command: string, options: SpawnOptions = {}) {
|
||||
if (process.platform === 'win32') {
|
||||
return spawn('cmd.exe', ['/C', command], options);
|
||||
}
|
||||
|
||||
return spawn('sh', ['-c', command], options);
|
||||
}
|
||||
|
||||
export async function execCommand(command: string, options: SpawnOptions = {}) {
|
||||
if (process.platform === 'win32') {
|
||||
await spawnAsync('cmd.exe', ['/C', command], options);
|
||||
} else {
|
||||
await spawnAsync('sh', ['-c', command], options);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
async function chmodPlusX(fsPath: string) {
|
||||
const s = await fs.stat(fsPath);
|
||||
const newMode = s.mode | 64 | 8 | 1; // eslint-disable-line no-bitwise
|
||||
@@ -155,7 +215,7 @@ export async function runNpmInstall(
|
||||
commandArgs = args.filter(a => a !== '--prefer-offline');
|
||||
await spawnAsync(
|
||||
'npm',
|
||||
commandArgs.concat(['install', '--unsafe-perm']),
|
||||
commandArgs.concat(['install', '--no-audit', '--unsafe-perm']),
|
||||
opts
|
||||
);
|
||||
} else {
|
||||
|
||||
@@ -3,12 +3,15 @@ import FileFsRef from './file-fs-ref';
|
||||
import FileRef from './file-ref';
|
||||
import { Lambda, createLambda, getLambdaOptionsFromFunction } from './lambda';
|
||||
import { Prerender } from './prerender';
|
||||
import download, { DownloadedFiles } from './fs/download';
|
||||
import download, { DownloadedFiles, isSymbolicLink } from './fs/download';
|
||||
import getWriteableDirectory from './fs/get-writable-directory';
|
||||
import glob from './fs/glob';
|
||||
import rename from './fs/rename';
|
||||
import {
|
||||
execAsync,
|
||||
spawnAsync,
|
||||
execCommand,
|
||||
spawnCommand,
|
||||
installDependencies,
|
||||
runPackageJsonScript,
|
||||
runNpmInstall,
|
||||
@@ -21,7 +24,7 @@ import {
|
||||
import streamToBuffer from './fs/stream-to-buffer';
|
||||
import shouldServe from './should-serve';
|
||||
import { detectBuilders } from './detect-builders';
|
||||
import { detectRoutes } from './detect-routes';
|
||||
import { detectRoutes, detectOutputDirectory } from './detect-routes';
|
||||
import DetectorFilesystem from './detectors/filesystem';
|
||||
import debug from './debug';
|
||||
|
||||
@@ -38,9 +41,12 @@ export {
|
||||
getWriteableDirectory,
|
||||
glob,
|
||||
rename,
|
||||
execAsync,
|
||||
spawnAsync,
|
||||
installDependencies,
|
||||
runPackageJsonScript,
|
||||
execCommand,
|
||||
spawnCommand,
|
||||
runNpmInstall,
|
||||
runBundleInstall,
|
||||
runPipInstall,
|
||||
@@ -51,9 +57,15 @@ export {
|
||||
shouldServe,
|
||||
detectBuilders,
|
||||
detectRoutes,
|
||||
detectOutputDirectory,
|
||||
debug,
|
||||
isSymbolicLink,
|
||||
getLambdaOptionsFromFunction,
|
||||
};
|
||||
|
||||
export { detectBuildersLegacy } from './detect-builders-legacy';
|
||||
export { detectRoutesLegacy } from './detect-routes-legacy';
|
||||
|
||||
export { detectDefaults } from './detectors';
|
||||
export * from './schemas';
|
||||
export * from './types';
|
||||
|
||||
61
packages/now-build-utils/src/schemas.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
export const functionsSchema = {
|
||||
type: 'object',
|
||||
minProperties: 1,
|
||||
maxProperties: 50,
|
||||
additionalProperties: false,
|
||||
patternProperties: {
|
||||
'^.{1,256}$': {
|
||||
type: 'object',
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
runtime: {
|
||||
type: 'string',
|
||||
maxLength: 256,
|
||||
},
|
||||
memory: {
|
||||
// Number between 128 and 3008 in steps of 64
|
||||
enum: Object.keys(Array.from({ length: 50 }))
|
||||
.slice(2, 48)
|
||||
.map(x => Number(x) * 64),
|
||||
},
|
||||
maxDuration: {
|
||||
type: 'number',
|
||||
minimum: 1,
|
||||
maximum: 900,
|
||||
},
|
||||
includeFiles: {
|
||||
type: 'string',
|
||||
maxLength: 256,
|
||||
},
|
||||
excludeFiles: {
|
||||
type: 'string',
|
||||
maxLength: 256,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const buildsSchema = {
|
||||
type: 'array',
|
||||
minItems: 0,
|
||||
maxItems: 128,
|
||||
items: {
|
||||
type: 'object',
|
||||
additionalProperties: false,
|
||||
required: ['use'],
|
||||
properties: {
|
||||
src: {
|
||||
type: 'string',
|
||||
minLength: 1,
|
||||
maxLength: 4096,
|
||||
},
|
||||
use: {
|
||||
type: 'string',
|
||||
minLength: 3,
|
||||
maxLength: 256,
|
||||
},
|
||||
config: { type: 'object' },
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -1,6 +1,7 @@
|
||||
import FileRef from './file-ref';
|
||||
import FileFsRef from './file-fs-ref';
|
||||
import DetectorFilesystem from './detectors/filesystem';
|
||||
import { Route } from '@now/routing-utils';
|
||||
|
||||
export interface Env {
|
||||
[name: string]: string | undefined;
|
||||
@@ -21,18 +22,6 @@ export interface Files {
|
||||
[filePath: string]: File;
|
||||
}
|
||||
|
||||
export interface Route {
|
||||
src?: string;
|
||||
dest?: string;
|
||||
handle?: string;
|
||||
type?: string;
|
||||
headers?: {
|
||||
[key: string]: string;
|
||||
};
|
||||
continue?: boolean;
|
||||
status?: number;
|
||||
}
|
||||
|
||||
export interface Config {
|
||||
[key: string]:
|
||||
| string
|
||||
@@ -52,6 +41,13 @@ export interface Config {
|
||||
zeroConfig?: boolean;
|
||||
import?: { [key: string]: string };
|
||||
functions?: BuilderFunctions;
|
||||
outputDirectory?: string;
|
||||
buildCommand?: string;
|
||||
devCommand?: string;
|
||||
framework?: {
|
||||
slug: string;
|
||||
version: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface Meta {
|
||||
@@ -304,6 +300,7 @@ export interface NodeVersion {
|
||||
major: number;
|
||||
range: string;
|
||||
runtime: string;
|
||||
discontinueDate?: Date;
|
||||
}
|
||||
|
||||
export interface Builder {
|
||||
@@ -352,11 +349,11 @@ export interface DetectorParameters {
|
||||
}
|
||||
|
||||
export interface DetectorOutput {
|
||||
buildCommand: string[];
|
||||
buildDirectory: string;
|
||||
buildEnv?: Env;
|
||||
devCommand?: string[];
|
||||
devEnv?: Env;
|
||||
buildCommand: string;
|
||||
outputDirectory: string;
|
||||
buildVariables?: Env;
|
||||
devCommand?: string;
|
||||
devVariables?: Env;
|
||||
minNodeRange?: string;
|
||||
cachePattern?: string;
|
||||
routes?: Route[];
|
||||
@@ -365,6 +362,10 @@ export interface DetectorOutput {
|
||||
redirects?: NowRedirect[];
|
||||
headers?: NowHeader[];
|
||||
trailingSlash?: boolean;
|
||||
framework?: {
|
||||
slug: string;
|
||||
version: string;
|
||||
};
|
||||
}
|
||||
|
||||
export type DetectorResult = DetectorOutput | false;
|
||||
|
||||
@@ -4,13 +4,34 @@ const {
|
||||
packAndDeploy,
|
||||
testDeployment,
|
||||
} = require('../../../test/lib/deployment/test-deployment');
|
||||
const { glob, detectBuilders, detectRoutes } = require('../');
|
||||
const {
|
||||
glob,
|
||||
detectBuilders,
|
||||
detectRoutes,
|
||||
DetectorFilesystem,
|
||||
detectDefaults,
|
||||
} = require('../');
|
||||
|
||||
jest.setTimeout(4 * 60 * 1000);
|
||||
|
||||
const builderUrl = '@canary';
|
||||
let buildUtilsUrl;
|
||||
|
||||
class LocalFilesystem extends DetectorFilesystem {
|
||||
constructor(dir) {
|
||||
super();
|
||||
this.dir = dir;
|
||||
}
|
||||
|
||||
_exists(name) {
|
||||
return fs.pathExists(path.join(this.dir, name));
|
||||
}
|
||||
|
||||
_readFile(name) {
|
||||
return fs.readFile(path.join(this.dir, name));
|
||||
}
|
||||
}
|
||||
|
||||
beforeAll(async () => {
|
||||
const buildUtilsPath = path.resolve(__dirname, '..');
|
||||
buildUtilsUrl = await packAndDeploy(buildUtilsPath);
|
||||
@@ -67,7 +88,9 @@ for (const builder of buildersToTestWith) {
|
||||
|
||||
it('Test `detectBuilders` and `detectRoutes`', async () => {
|
||||
const fixture = path.join(__dirname, 'fixtures', '01-zero-config-api');
|
||||
const pkg = await fs.readJSON(path.join(fixture, 'package.json'));
|
||||
const detectorResult = await detectDefaults({
|
||||
fs: new LocalFilesystem(fixture),
|
||||
});
|
||||
const fileList = await glob('**', fixture);
|
||||
const files = Object.keys(fileList);
|
||||
|
||||
@@ -110,7 +133,7 @@ it('Test `detectBuilders` and `detectRoutes`', async () => {
|
||||
},
|
||||
];
|
||||
|
||||
const { builders } = await detectBuilders(files, pkg);
|
||||
const { builders } = await detectBuilders(files, detectorResult);
|
||||
const { defaultRoutes } = await detectRoutes(files, builders);
|
||||
|
||||
const nowConfig = { builds: builders, routes: defaultRoutes, probes };
|
||||
@@ -128,7 +151,9 @@ it('Test `detectBuilders` and `detectRoutes`', async () => {
|
||||
|
||||
it('Test `detectBuilders` and `detectRoutes` with `index` files', async () => {
|
||||
const fixture = path.join(__dirname, 'fixtures', '02-zero-config-api');
|
||||
const pkg = await fs.readJSON(path.join(fixture, 'package.json'));
|
||||
const detectorResult = await detectDefaults({
|
||||
fs: new LocalFilesystem(fixture),
|
||||
});
|
||||
const fileList = await glob('**', fixture);
|
||||
const files = Object.keys(fileList);
|
||||
|
||||
@@ -192,7 +217,7 @@ it('Test `detectBuilders` and `detectRoutes` with `index` files', async () => {
|
||||
},
|
||||
];
|
||||
|
||||
const { builders } = await detectBuilders(files, pkg);
|
||||
const { builders } = await detectBuilders(files, detectorResult);
|
||||
const { defaultRoutes } = await detectRoutes(files, builders);
|
||||
|
||||
const nowConfig = { builds: builders, routes: defaultRoutes, probes };
|
||||
|
||||
@@ -67,8 +67,8 @@ test('detectDefaults() - angular', async () => {
|
||||
const fs = new LocalFilesystem(dir);
|
||||
const result = await detectDefaults({ fs });
|
||||
if (!result) throw new Error('Expected result');
|
||||
assert.equal(result.buildDirectory, 'dist');
|
||||
assert.deepEqual(result.buildCommand, ['ng', 'build']);
|
||||
assert.equal(result.outputDirectory, 'dist');
|
||||
assert.deepEqual(result.buildCommand, 'yarn run build');
|
||||
});
|
||||
|
||||
test('detectDefaults() - brunch', async () => {
|
||||
@@ -76,8 +76,8 @@ test('detectDefaults() - brunch', async () => {
|
||||
const fs = new LocalFilesystem(dir);
|
||||
const result = await detectDefaults({ fs });
|
||||
if (!result) throw new Error('Expected result');
|
||||
assert.equal(result.buildDirectory, 'public');
|
||||
assert.deepEqual(result.buildCommand, ['brunch', 'build', '--production']);
|
||||
assert.equal(result.outputDirectory, 'public');
|
||||
assert.deepEqual(result.buildCommand, 'yarn run build');
|
||||
});
|
||||
|
||||
test('detectDefaults() - hugo', async () => {
|
||||
@@ -85,8 +85,8 @@ test('detectDefaults() - hugo', async () => {
|
||||
const fs = new LocalFilesystem(dir);
|
||||
const result = await detectDefaults({ fs });
|
||||
if (!result) throw new Error('Expected result');
|
||||
assert.equal(result.buildDirectory, 'public');
|
||||
assert.deepEqual(result.buildCommand, ['hugo']);
|
||||
assert.equal(result.outputDirectory, 'public');
|
||||
assert.deepEqual(result.buildCommand, 'hugo');
|
||||
});
|
||||
|
||||
test('detectDefaults() - jekyll', async () => {
|
||||
@@ -94,8 +94,8 @@ test('detectDefaults() - jekyll', async () => {
|
||||
const fs = new LocalFilesystem(dir);
|
||||
const result = await detectDefaults({ fs });
|
||||
if (!result) throw new Error('Expected result');
|
||||
assert.equal(result.buildDirectory, '_site');
|
||||
assert.deepEqual(result.buildCommand, ['jekyll', 'build']);
|
||||
assert.equal(result.outputDirectory, '_site');
|
||||
assert.deepEqual(result.buildCommand, 'jekyll build');
|
||||
});
|
||||
|
||||
test('detectDefaults() - middleman', async () => {
|
||||
@@ -103,11 +103,6 @@ test('detectDefaults() - middleman', async () => {
|
||||
const fs = new LocalFilesystem(dir);
|
||||
const result = await detectDefaults({ fs });
|
||||
if (!result) throw new Error('Expected result');
|
||||
assert.equal(result.buildDirectory, 'build');
|
||||
assert.deepEqual(result.buildCommand, [
|
||||
'bundle',
|
||||
'exec',
|
||||
'middleman',
|
||||
'build',
|
||||
]);
|
||||
assert.equal(result.outputDirectory, 'build');
|
||||
assert.deepEqual(result.buildCommand, 'bundle exec middleman build');
|
||||
});
|
||||
|
||||
598
packages/now-build-utils/test/unit.test.js
vendored
@@ -1,9 +1,14 @@
|
||||
const path = require('path');
|
||||
const fs = require('fs-extra');
|
||||
const execa = require('execa');
|
||||
const assert = require('assert');
|
||||
const { createZip } = require('../dist/lambda');
|
||||
const { glob, download, detectBuilders, detectRoutes } = require('../');
|
||||
const {
|
||||
glob,
|
||||
download,
|
||||
detectBuilders,
|
||||
detectRoutes,
|
||||
spawnAsync,
|
||||
} = require('../');
|
||||
const {
|
||||
getSupportedNodeVersion,
|
||||
defaultSelection,
|
||||
@@ -39,7 +44,7 @@ it('should create zip files with symlinks properly', async () => {
|
||||
await fs.mkdirp(outDir);
|
||||
|
||||
await fs.writeFile(outFile, await createZip(files));
|
||||
await execa('unzip', [outFile], { cwd: outDir });
|
||||
await spawnAsync('unzip', [outFile], { cwd: outDir });
|
||||
|
||||
const [linkStat, aStat] = await Promise.all([
|
||||
fs.lstat(path.join(outDir, 'link.txt')),
|
||||
@@ -110,42 +115,34 @@ it('should support require by path for legacy builders', () => {
|
||||
});
|
||||
|
||||
describe('Test `detectBuilders`', () => {
|
||||
it('package.json + no build', async () => {
|
||||
const pkg = { dependencies: { next: '9.0.0' } };
|
||||
it('package.json + no build command', async () => {
|
||||
const detected = { framework: { slug: 'next', version: '9.0.0' } };
|
||||
const files = ['package.json', 'pages/index.js', 'public/index.html'];
|
||||
const { builders, errors } = await detectBuilders(files, pkg);
|
||||
expect(builders).toBe(null);
|
||||
expect(errors.length).toBe(1);
|
||||
const { builders } = await detectBuilders(files, detected);
|
||||
expect(builders.length).toBe(1);
|
||||
expect(builders[0].src).toBe('public/**/*');
|
||||
expect(builders[0].use).toBe('@now/static');
|
||||
});
|
||||
|
||||
it('package.json + no build + next', async () => {
|
||||
const pkg = {
|
||||
scripts: { build: 'next build' },
|
||||
dependencies: { next: '9.0.0' },
|
||||
it('package.json + build command + next', async () => {
|
||||
const detected = {
|
||||
buildCommand: 'yarn build',
|
||||
framework: {
|
||||
slug: 'next',
|
||||
version: '9.0.0',
|
||||
},
|
||||
};
|
||||
const files = ['package.json', 'pages/index.js'];
|
||||
const { builders, errors } = await detectBuilders(files, pkg);
|
||||
const { builders, errors } = await detectBuilders(files, detected);
|
||||
expect(builders[0].use).toBe('@now/next');
|
||||
expect(errors).toBe(null);
|
||||
});
|
||||
|
||||
it('package.json + no build + next', async () => {
|
||||
const pkg = {
|
||||
scripts: { build: 'next build' },
|
||||
devDependencies: { next: '9.0.0' },
|
||||
};
|
||||
const files = ['package.json', 'pages/index.js'];
|
||||
const { builders, errors } = await detectBuilders(files, pkg);
|
||||
expect(builders[0].use).toBe('@now/next');
|
||||
expect(errors).toBe(null);
|
||||
});
|
||||
|
||||
it('package.json + no build', async () => {
|
||||
const pkg = {};
|
||||
it('no detectors + no build command', async () => {
|
||||
const files = ['package.json'];
|
||||
const { builders, errors } = await detectBuilders(files, pkg);
|
||||
const { builders, errors } = await detectBuilders(files, {});
|
||||
expect(builders).toBe(null);
|
||||
expect(errors.length).toBe(1);
|
||||
expect(errors).toBe(null);
|
||||
});
|
||||
|
||||
it('static file', async () => {
|
||||
@@ -155,7 +152,7 @@ describe('Test `detectBuilders`', () => {
|
||||
expect(errors).toBe(null);
|
||||
});
|
||||
|
||||
it('no package.json + public', async () => {
|
||||
it('no package.json + public + api', async () => {
|
||||
const files = ['api/users.js', 'public/index.html'];
|
||||
const { builders, errors } = await detectBuilders(files);
|
||||
expect(builders[1].use).toBe('@now/static');
|
||||
@@ -173,7 +170,7 @@ describe('Test `detectBuilders`', () => {
|
||||
expect(errors).toBe(null);
|
||||
});
|
||||
|
||||
it('package.json + no build + root + api', async () => {
|
||||
it('no package.json + no build command + root + api', async () => {
|
||||
const files = ['index.html', 'api/[endpoint].js', 'static/image.png'];
|
||||
const { builders, errors } = await detectBuilders(files);
|
||||
expect(builders[0].use).toBe('@now/node');
|
||||
@@ -198,13 +195,17 @@ describe('Test `detectBuilders`', () => {
|
||||
});
|
||||
|
||||
it('api + next + public', async () => {
|
||||
const pkg = {
|
||||
scripts: { build: 'next build' },
|
||||
devDependencies: { next: '9.0.0' },
|
||||
const detected = {
|
||||
buildCommand: 'yarn build',
|
||||
framework: {
|
||||
slug: 'next',
|
||||
version: '9.0.0',
|
||||
},
|
||||
};
|
||||
|
||||
const files = ['package.json', 'api/endpoint.js', 'public/index.html'];
|
||||
|
||||
const { builders } = await detectBuilders(files, pkg);
|
||||
const { builders } = await detectBuilders(files, detected);
|
||||
expect(builders[0].use).toBe('@now/node');
|
||||
expect(builders[0].src).toBe('api/endpoint.js');
|
||||
expect(builders[1].use).toBe('@now/next');
|
||||
@@ -213,13 +214,17 @@ describe('Test `detectBuilders`', () => {
|
||||
});
|
||||
|
||||
it('api + next + raw static', async () => {
|
||||
const pkg = {
|
||||
scripts: { build: 'next build' },
|
||||
devDependencies: { next: '9.0.0' },
|
||||
const detected = {
|
||||
buildCommand: 'yarn build',
|
||||
framework: {
|
||||
slug: 'next',
|
||||
version: '9.0.0',
|
||||
},
|
||||
};
|
||||
|
||||
const files = ['package.json', 'api/endpoint.js', 'index.html'];
|
||||
|
||||
const { builders } = await detectBuilders(files, pkg);
|
||||
const { builders } = await detectBuilders(files, detected);
|
||||
expect(builders[0].use).toBe('@now/node');
|
||||
expect(builders[0].src).toBe('api/endpoint.js');
|
||||
expect(builders[1].use).toBe('@now/next');
|
||||
@@ -263,61 +268,85 @@ describe('Test `detectBuilders`', () => {
|
||||
});
|
||||
|
||||
it('next + public', async () => {
|
||||
const pkg = {
|
||||
scripts: { build: 'next build' },
|
||||
devDependencies: { next: '9.0.0' },
|
||||
const detected = {
|
||||
buildCommand: 'yarn build',
|
||||
framework: {
|
||||
slug: 'next',
|
||||
version: '9.0.0',
|
||||
},
|
||||
};
|
||||
|
||||
const files = ['package.json', 'public/index.html', 'README.md'];
|
||||
|
||||
const { builders } = await detectBuilders(files, pkg);
|
||||
const { builders } = await detectBuilders(files, detected);
|
||||
expect(builders[0].use).toBe('@now/next');
|
||||
expect(builders[0].src).toBe('package.json');
|
||||
expect(builders.length).toBe(1);
|
||||
});
|
||||
|
||||
it('nuxt', async () => {
|
||||
const pkg = {
|
||||
scripts: { build: 'nuxt build' },
|
||||
dependencies: { nuxt: '2.8.1' },
|
||||
const detected = {
|
||||
buildCommand: 'yarn build',
|
||||
framework: {
|
||||
slug: '@vue/cli-service',
|
||||
version: '2.8.1',
|
||||
},
|
||||
};
|
||||
|
||||
const files = ['package.json', 'pages/index.js'];
|
||||
|
||||
const { builders } = await detectBuilders(files, pkg);
|
||||
const { builders } = await detectBuilders(files, detected);
|
||||
expect(builders[0].use).toBe('@now/static-build');
|
||||
expect(builders[0].src).toBe('package.json');
|
||||
expect(builders.length).toBe(1);
|
||||
});
|
||||
|
||||
it('nuxt + tag canary', async () => {
|
||||
const pkg = {
|
||||
scripts: { build: 'nuxt build' },
|
||||
dependencies: { nuxt: '2.8.1' },
|
||||
const detected = {
|
||||
buildCommand: 'yarn build',
|
||||
framework: {
|
||||
slug: '@vue/cli-service',
|
||||
version: '2.8.1',
|
||||
},
|
||||
};
|
||||
|
||||
const files = ['package.json', 'pages/index.js'];
|
||||
|
||||
const { builders } = await detectBuilders(files, pkg, { tag: 'canary' });
|
||||
const { builders } = await detectBuilders(files, detected, {
|
||||
tag: 'canary',
|
||||
});
|
||||
expect(builders[0].use).toBe('@now/static-build@canary');
|
||||
expect(builders[0].src).toBe('package.json');
|
||||
expect(builders.length).toBe(1);
|
||||
});
|
||||
|
||||
it('package.json with no build + api', async () => {
|
||||
const pkg = { dependencies: { next: '9.0.0' } };
|
||||
it('no build command + api', async () => {
|
||||
const detected = {
|
||||
framework: {
|
||||
slug: 'next',
|
||||
version: '9.0.0',
|
||||
},
|
||||
};
|
||||
const files = ['package.json', 'api/[endpoint].js'];
|
||||
|
||||
const { builders } = await detectBuilders(files, pkg);
|
||||
const { builders } = await detectBuilders(files, detected);
|
||||
expect(builders[0].use).toBe('@now/node');
|
||||
expect(builders[0].src).toBe('api/[endpoint].js');
|
||||
expect(builders.length).toBe(1);
|
||||
});
|
||||
|
||||
it('package.json with no build + public directory', async () => {
|
||||
const pkg = { dependencies: { next: '9.0.0' } };
|
||||
it('no build command + public directory', async () => {
|
||||
const detected = {
|
||||
framework: {
|
||||
slug: 'next',
|
||||
version: '9.0.0',
|
||||
},
|
||||
};
|
||||
const files = ['package.json', 'public/index.html'];
|
||||
|
||||
const { builders, errors } = await detectBuilders(files, pkg);
|
||||
expect(builders).toBe(null);
|
||||
expect(errors.length).toBe(1);
|
||||
const { builders, errors } = await detectBuilders(files, detected);
|
||||
expect(builders.length).toBe(1);
|
||||
expect(errors).toBe(null);
|
||||
});
|
||||
|
||||
it('no package.json + api', async () => {
|
||||
@@ -336,17 +365,23 @@ describe('Test `detectBuilders`', () => {
|
||||
});
|
||||
|
||||
it('package.json + api + canary', async () => {
|
||||
const pkg = {
|
||||
scripts: { build: 'next build' },
|
||||
dependencies: { next: '9.0.0' },
|
||||
const detected = {
|
||||
buildCommand: 'yarn build',
|
||||
framework: {
|
||||
slug: 'next',
|
||||
version: '9.0.0',
|
||||
},
|
||||
};
|
||||
|
||||
const files = [
|
||||
'pages/index.js',
|
||||
'api/[endpoint].js',
|
||||
'api/[endpoint]/[id].js',
|
||||
];
|
||||
|
||||
const { builders } = await detectBuilders(files, pkg, { tag: 'canary' });
|
||||
const { builders } = await detectBuilders(files, detected, {
|
||||
tag: 'canary',
|
||||
});
|
||||
expect(builders[0].use).toBe('@now/node@canary');
|
||||
expect(builders[1].use).toBe('@now/node@canary');
|
||||
expect(builders[2].use).toBe('@now/next@canary');
|
||||
@@ -354,17 +389,23 @@ describe('Test `detectBuilders`', () => {
|
||||
});
|
||||
|
||||
it('package.json + api + latest', async () => {
|
||||
const pkg = {
|
||||
scripts: { build: 'next build' },
|
||||
dependencies: { next: '9.0.0' },
|
||||
const detected = {
|
||||
buildCommand: 'yarn build',
|
||||
framework: {
|
||||
slug: 'next',
|
||||
version: '9.0.0',
|
||||
},
|
||||
};
|
||||
|
||||
const files = [
|
||||
'pages/index.js',
|
||||
'api/[endpoint].js',
|
||||
'api/[endpoint]/[id].js',
|
||||
];
|
||||
|
||||
const { builders } = await detectBuilders(files, pkg, { tag: 'latest' });
|
||||
const { builders } = await detectBuilders(files, detected, {
|
||||
tag: 'latest',
|
||||
});
|
||||
expect(builders[0].use).toBe('@now/node@latest');
|
||||
expect(builders[1].use).toBe('@now/node@latest');
|
||||
expect(builders[2].use).toBe('@now/next@latest');
|
||||
@@ -372,17 +413,21 @@ describe('Test `detectBuilders`', () => {
|
||||
});
|
||||
|
||||
it('package.json + api + random tag', async () => {
|
||||
const pkg = {
|
||||
scripts: { build: 'next build' },
|
||||
dependencies: { next: '9.0.0' },
|
||||
const detected = {
|
||||
buildCommand: 'yarn build',
|
||||
framework: {
|
||||
slug: 'next',
|
||||
version: '9.0.0',
|
||||
},
|
||||
};
|
||||
|
||||
const files = [
|
||||
'pages/index.js',
|
||||
'api/[endpoint].js',
|
||||
'api/[endpoint]/[id].js',
|
||||
];
|
||||
|
||||
const { builders } = await detectBuilders(files, pkg, { tag: 'haha' });
|
||||
const { builders } = await detectBuilders(files, detected, { tag: 'haha' });
|
||||
expect(builders[0].use).toBe('@now/node@haha');
|
||||
expect(builders[1].use).toBe('@now/node@haha');
|
||||
expect(builders[2].use).toBe('@now/next@haha');
|
||||
@@ -390,13 +435,20 @@ describe('Test `detectBuilders`', () => {
|
||||
});
|
||||
|
||||
it('next.js pages/api + api', async () => {
|
||||
const pkg = {
|
||||
scripts: { build: 'next build' },
|
||||
dependencies: { next: '9.0.0' },
|
||||
const detected = {
|
||||
buildCommand: 'yarn build',
|
||||
framework: {
|
||||
slug: 'next',
|
||||
version: '9.0.0',
|
||||
},
|
||||
};
|
||||
|
||||
const files = ['api/user.js', 'pages/api/user.js'];
|
||||
|
||||
const { warnings, errors, builders } = await detectBuilders(files, pkg);
|
||||
const { warnings, errors, builders } = await detectBuilders(
|
||||
files,
|
||||
detected
|
||||
);
|
||||
|
||||
expect(errors).toBe(null);
|
||||
expect(warnings[0]).toBeDefined();
|
||||
@@ -420,9 +472,12 @@ describe('Test `detectBuilders`', () => {
|
||||
});
|
||||
|
||||
it('functions with nextjs', async () => {
|
||||
const pkg = {
|
||||
scripts: { build: 'next build' },
|
||||
dependencies: { next: '9.0.0' },
|
||||
const detected = {
|
||||
buildCommand: 'yarn build',
|
||||
framework: {
|
||||
slug: 'next',
|
||||
version: '9.0.0',
|
||||
},
|
||||
};
|
||||
const functions = {
|
||||
'pages/api/teams/**': {
|
||||
@@ -435,7 +490,7 @@ describe('Test `detectBuilders`', () => {
|
||||
'pages/index.js',
|
||||
'pages/api/teams/members.ts',
|
||||
];
|
||||
const { builders, errors } = await detectBuilders(files, pkg, {
|
||||
const { builders, errors } = await detectBuilders(files, detected, {
|
||||
functions,
|
||||
});
|
||||
|
||||
@@ -446,6 +501,11 @@ describe('Test `detectBuilders`', () => {
|
||||
use: '@now/next',
|
||||
config: {
|
||||
zeroConfig: true,
|
||||
buildCommand: 'yarn build',
|
||||
framework: {
|
||||
slug: 'next',
|
||||
version: '9.0.0',
|
||||
},
|
||||
functions: {
|
||||
'pages/api/teams/**': {
|
||||
memory: 128,
|
||||
@@ -457,9 +517,12 @@ describe('Test `detectBuilders`', () => {
|
||||
});
|
||||
|
||||
it('extend with functions', async () => {
|
||||
const pkg = {
|
||||
scripts: { build: 'next build' },
|
||||
dependencies: { next: '9.0.0' },
|
||||
const detected = {
|
||||
buildCommand: 'yarn build',
|
||||
framework: {
|
||||
slug: 'next',
|
||||
version: '9.0.0',
|
||||
},
|
||||
};
|
||||
const functions = {
|
||||
'api/users/*.ts': {
|
||||
@@ -476,7 +539,7 @@ describe('Test `detectBuilders`', () => {
|
||||
'api/users/[id].ts',
|
||||
'api/teams/members.ts',
|
||||
];
|
||||
const { builders } = await detectBuilders(files, pkg, { functions });
|
||||
const { builders } = await detectBuilders(files, detected, { functions });
|
||||
|
||||
expect(builders.length).toBe(3);
|
||||
expect(builders[0]).toEqual({
|
||||
@@ -509,6 +572,11 @@ describe('Test `detectBuilders`', () => {
|
||||
use: '@now/next',
|
||||
config: {
|
||||
zeroConfig: true,
|
||||
buildCommand: 'yarn build',
|
||||
framework: {
|
||||
slug: 'next',
|
||||
version: '9.0.0',
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
@@ -607,31 +675,23 @@ describe('Test `detectBuilders`', () => {
|
||||
});
|
||||
|
||||
it('Do not allow functions that are not used by @now/next', async () => {
|
||||
const pkg = {
|
||||
scripts: { build: 'next build' },
|
||||
dependencies: { next: '9.0.0' },
|
||||
const detected = {
|
||||
buildCommand: 'yarn build',
|
||||
framework: {
|
||||
slug: 'next',
|
||||
version: '9.0.0',
|
||||
},
|
||||
};
|
||||
|
||||
const functions = { 'test.js': { memory: 1024 } };
|
||||
const files = ['pages/index.js', 'test.js'];
|
||||
|
||||
const { errors } = await detectBuilders(files, pkg, { functions });
|
||||
const { errors } = await detectBuilders(files, detected, { functions });
|
||||
|
||||
expect(errors).toBeDefined();
|
||||
expect(errors[0].code).toBe('unused_function');
|
||||
});
|
||||
|
||||
it('Do not allow function non Community Runtimes', async () => {
|
||||
const functions = {
|
||||
'api/test.js': { memory: 128, runtime: '@now/node@1.0.0' },
|
||||
};
|
||||
const files = ['api/test.js'];
|
||||
|
||||
const { errors } = await detectBuilders(files, null, { functions });
|
||||
|
||||
expect(errors).toBeDefined();
|
||||
expect(errors[0].code).toBe('invalid_function_runtime');
|
||||
});
|
||||
|
||||
it('Must include includeFiles config property', async () => {
|
||||
const functions = {
|
||||
'api/test.js': { includeFiles: 'text/include.txt' },
|
||||
@@ -731,6 +791,332 @@ describe('Test `detectBuilders`', () => {
|
||||
expect(errors).not.toBe(null);
|
||||
expect(errors[0].code).toBe('invalid_function_source');
|
||||
});
|
||||
|
||||
it('Custom static output directory', async () => {
|
||||
const detected = {
|
||||
outputDirectory: 'dist',
|
||||
};
|
||||
|
||||
const files = ['dist/index.html', 'dist/style.css'];
|
||||
|
||||
const { builders } = await detectBuilders(files, detected);
|
||||
|
||||
expect(builders.length).toBe(1);
|
||||
expect(builders[0].src).toBe('dist/**/*');
|
||||
expect(builders[0].use).toBe('@now/static');
|
||||
|
||||
const { defaultRoutes } = await detectRoutes(files, builders);
|
||||
|
||||
expect(defaultRoutes.length).toBe(1);
|
||||
expect(defaultRoutes[0].src).toBe('/(.*)');
|
||||
expect(defaultRoutes[0].dest).toBe('/dist/$1');
|
||||
});
|
||||
|
||||
it('Custom static output directory with api', async () => {
|
||||
const detected = {
|
||||
outputDirectory: 'output',
|
||||
};
|
||||
|
||||
const files = ['api/user.ts', 'output/index.html', 'output/style.css'];
|
||||
|
||||
const { builders } = await detectBuilders(files, detected);
|
||||
|
||||
expect(builders.length).toBe(2);
|
||||
expect(builders[1].src).toBe('output/**/*');
|
||||
expect(builders[1].use).toBe('@now/static');
|
||||
|
||||
const { defaultRoutes } = await detectRoutes(files, builders);
|
||||
|
||||
expect(defaultRoutes.length).toBe(3);
|
||||
expect(defaultRoutes[1].status).toBe(404);
|
||||
expect(defaultRoutes[2].src).toBe('/(.*)');
|
||||
expect(defaultRoutes[2].dest).toBe('/output/$1');
|
||||
});
|
||||
|
||||
it('Custom directory for Serverless Functions', async () => {
|
||||
const files = ['server/_lib/db.ts', 'server/user.ts', 'server/team.ts'];
|
||||
|
||||
const functions = {
|
||||
'server/**/*.ts': {
|
||||
memory: 128,
|
||||
runtime: '@now/node@1.2.1',
|
||||
},
|
||||
};
|
||||
|
||||
const { builders } = await detectBuilders(files, null, { functions });
|
||||
|
||||
expect(builders.length).toBe(3);
|
||||
expect(builders[0]).toEqual({
|
||||
use: '@now/node@1.2.1',
|
||||
src: 'server/team.ts',
|
||||
config: {
|
||||
zeroConfig: true,
|
||||
functions: {
|
||||
'server/**/*.ts': {
|
||||
memory: 128,
|
||||
runtime: '@now/node@1.2.1',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(builders[1]).toEqual({
|
||||
use: '@now/node@1.2.1',
|
||||
src: 'server/user.ts',
|
||||
config: {
|
||||
zeroConfig: true,
|
||||
functions: {
|
||||
'server/**/*.ts': {
|
||||
memory: 128,
|
||||
runtime: '@now/node@1.2.1',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
// This is expected, since only "api + full static" is supported
|
||||
// no other directory, so everything else will be deployed
|
||||
expect(builders[2].use).toBe('@now/static');
|
||||
|
||||
const { defaultRoutes } = await detectRoutes(files, builders);
|
||||
|
||||
expect(defaultRoutes.length).toBe(3);
|
||||
expect(defaultRoutes[0].dest).toBe('/server/team.ts');
|
||||
expect(defaultRoutes[0].src).toBe('^/server/(team\\/|team|team\\.ts)$');
|
||||
expect(defaultRoutes[1].dest).toBe('/server/user.ts');
|
||||
expect(defaultRoutes[1].src).toBe('^/server/(user\\/|user|user\\.ts)$');
|
||||
expect(defaultRoutes[2].status).toBe(404);
|
||||
});
|
||||
|
||||
it('Custom directory for Serverless Functions + Next.js', async () => {
|
||||
const detected = {
|
||||
buildCommand: 'yarn build',
|
||||
framework: {
|
||||
slug: 'next',
|
||||
version: '9.0.0',
|
||||
},
|
||||
};
|
||||
|
||||
const functions = {
|
||||
'server/**/*.ts': {
|
||||
runtime: '@now/node@1.2.1',
|
||||
},
|
||||
};
|
||||
|
||||
const files = ['package.json', 'pages/index.ts', 'server/user.ts'];
|
||||
|
||||
const { builders } = await detectBuilders(files, detected, { functions });
|
||||
|
||||
expect(builders.length).toBe(2);
|
||||
expect(builders[0]).toEqual({
|
||||
use: '@now/node@1.2.1',
|
||||
src: 'server/user.ts',
|
||||
config: {
|
||||
zeroConfig: true,
|
||||
functions,
|
||||
},
|
||||
});
|
||||
expect(builders[1]).toEqual({
|
||||
use: '@now/next',
|
||||
src: 'package.json',
|
||||
config: {
|
||||
zeroConfig: true,
|
||||
buildCommand: 'yarn build',
|
||||
framework: {
|
||||
slug: 'next',
|
||||
version: '9.0.0',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const { defaultRoutes } = await detectRoutes(files, builders);
|
||||
|
||||
expect(defaultRoutes.length).toBe(2);
|
||||
expect(defaultRoutes[0].dest).toBe('/server/user.ts');
|
||||
expect(defaultRoutes[0].src).toBe('^/server/(user\\/|user|user\\.ts)$');
|
||||
expect(defaultRoutes[1].status).toBe(404);
|
||||
});
|
||||
});
|
||||
|
||||
it('Test `detectRoutes` with `featHandleMiss=true`', async () => {
|
||||
const featHandleMiss = true;
|
||||
|
||||
const expectedRoutes = [
|
||||
{ handle: 'miss' },
|
||||
{
|
||||
src: '/api/(.+)\\.\\w+',
|
||||
dest: '/api/$1',
|
||||
check: true,
|
||||
},
|
||||
{
|
||||
status: 404,
|
||||
src: '/api(/.*)?$',
|
||||
continue: true,
|
||||
},
|
||||
];
|
||||
|
||||
{
|
||||
const files = ['api/user.go', 'api/team.js', 'api/package.json'];
|
||||
|
||||
const { builders } = await detectBuilders(files);
|
||||
const { defaultRoutes } = await detectRoutes(
|
||||
files,
|
||||
builders,
|
||||
featHandleMiss
|
||||
);
|
||||
expect(defaultRoutes).toStrictEqual(expectedRoutes);
|
||||
}
|
||||
|
||||
{
|
||||
const files = ['api/user.go', 'api/user.js'];
|
||||
|
||||
const { builders } = await detectBuilders(files);
|
||||
const { error } = await detectRoutes(files, builders, featHandleMiss);
|
||||
expect(error.code).toBe('conflicting_file_path');
|
||||
}
|
||||
|
||||
{
|
||||
const files = ['api/[user].go', 'api/[team]/[id].js'];
|
||||
|
||||
const { builders } = await detectBuilders(files);
|
||||
const { error } = await detectRoutes(files, builders, featHandleMiss);
|
||||
expect(error.code).toBe('conflicting_file_path');
|
||||
}
|
||||
|
||||
{
|
||||
const files = ['api/[team]/[team].js'];
|
||||
|
||||
const { builders } = await detectBuilders(files);
|
||||
const { error } = await detectRoutes(files, builders, featHandleMiss);
|
||||
expect(error.code).toBe('conflicting_path_segment');
|
||||
}
|
||||
|
||||
{
|
||||
const files = ['api/date/index.js', 'api/date/index.go'];
|
||||
|
||||
const { builders } = await detectBuilders(files);
|
||||
const { defaultRoutes, error } = await detectRoutes(
|
||||
files,
|
||||
builders,
|
||||
featHandleMiss
|
||||
);
|
||||
expect(defaultRoutes).toBe(null);
|
||||
expect(error.code).toBe('conflicting_file_path');
|
||||
}
|
||||
|
||||
{
|
||||
const files = ['api/[endpoint].js', 'api/[endpoint]/[id].js'];
|
||||
|
||||
const { builders } = await detectBuilders(files);
|
||||
const { defaultRoutes } = await detectRoutes(
|
||||
files,
|
||||
builders,
|
||||
featHandleMiss
|
||||
);
|
||||
expect(defaultRoutes).toStrictEqual(expectedRoutes);
|
||||
}
|
||||
|
||||
{
|
||||
const files = [
|
||||
'public/index.html',
|
||||
'api/[endpoint].js',
|
||||
'api/[endpoint]/[id].js',
|
||||
];
|
||||
|
||||
const { builders } = await detectBuilders(files);
|
||||
const { defaultRoutes } = await detectRoutes(
|
||||
files,
|
||||
builders,
|
||||
featHandleMiss
|
||||
);
|
||||
expect(defaultRoutes).toStrictEqual(expectedRoutes);
|
||||
}
|
||||
|
||||
{
|
||||
const detected = {
|
||||
buildCommand: 'yarn build',
|
||||
framework: {
|
||||
slug: 'next',
|
||||
version: '9.0.0',
|
||||
},
|
||||
};
|
||||
|
||||
const files = ['public/index.html', 'api/[endpoint].js'];
|
||||
|
||||
const { builders } = await detectBuilders(files, detected);
|
||||
const { defaultRoutes } = await detectRoutes(
|
||||
files,
|
||||
builders,
|
||||
featHandleMiss
|
||||
);
|
||||
expect(defaultRoutes).toStrictEqual(expectedRoutes);
|
||||
}
|
||||
|
||||
{
|
||||
const files = ['public/index.html'];
|
||||
|
||||
const { builders } = await detectBuilders(files);
|
||||
const { defaultRoutes } = await detectRoutes(
|
||||
files,
|
||||
builders,
|
||||
featHandleMiss
|
||||
);
|
||||
expect(defaultRoutes).toStrictEqual([]);
|
||||
}
|
||||
|
||||
{
|
||||
const files = ['api/date/index.js', 'api/date.js'];
|
||||
|
||||
const { builders } = await detectBuilders(files);
|
||||
const { defaultRoutes } = await detectRoutes(
|
||||
files,
|
||||
builders,
|
||||
featHandleMiss
|
||||
);
|
||||
expect(defaultRoutes).toStrictEqual(expectedRoutes);
|
||||
}
|
||||
|
||||
{
|
||||
const files = ['api/date.js', 'api/[date]/index.js'];
|
||||
|
||||
const { builders } = await detectBuilders(files);
|
||||
const { defaultRoutes } = await detectRoutes(
|
||||
files,
|
||||
builders,
|
||||
featHandleMiss
|
||||
);
|
||||
expect(defaultRoutes).toStrictEqual(expectedRoutes);
|
||||
}
|
||||
|
||||
{
|
||||
const files = [
|
||||
'api/index.ts',
|
||||
'api/index.d.ts',
|
||||
'api/users/index.ts',
|
||||
'api/users/index.d.ts',
|
||||
'api/food.ts',
|
||||
'api/ts/gold.ts',
|
||||
];
|
||||
const { builders } = await detectBuilders(files);
|
||||
const { defaultRoutes } = await detectRoutes(
|
||||
files,
|
||||
builders,
|
||||
featHandleMiss
|
||||
);
|
||||
expect(defaultRoutes).toStrictEqual(expectedRoutes);
|
||||
}
|
||||
|
||||
{
|
||||
// use a custom runtime
|
||||
const functions = { 'api/user.php': { runtime: 'now-php@0.0.5' } };
|
||||
const files = ['api/user.php'];
|
||||
|
||||
const { builders } = await detectBuilders(files, null, { functions });
|
||||
const { defaultRoutes } = await detectRoutes(
|
||||
files,
|
||||
builders,
|
||||
featHandleMiss
|
||||
);
|
||||
expect(defaultRoutes).toStrictEqual(expectedRoutes);
|
||||
}
|
||||
});
|
||||
|
||||
it('Test `detectRoutes`', async () => {
|
||||
@@ -797,23 +1183,27 @@ it('Test `detectRoutes`', async () => {
|
||||
const { builders } = await detectBuilders(files);
|
||||
const { defaultRoutes } = await detectRoutes(files, builders);
|
||||
expect(defaultRoutes[2].status).toBe(404);
|
||||
expect(defaultRoutes[2].src).toBe('/api(\\/.*)?$');
|
||||
expect(defaultRoutes[2].src).toBe('/api(/.*)?$');
|
||||
expect(defaultRoutes[3].src).toBe('/(.*)');
|
||||
expect(defaultRoutes[3].dest).toBe('/public/$1');
|
||||
expect(defaultRoutes.length).toBe(4);
|
||||
}
|
||||
|
||||
{
|
||||
const pkg = {
|
||||
scripts: { build: 'next build' },
|
||||
devDependencies: { next: '9.0.0' },
|
||||
const detected = {
|
||||
buildCommand: 'yarn build',
|
||||
framework: {
|
||||
slug: 'next',
|
||||
version: '9.0.0',
|
||||
},
|
||||
};
|
||||
|
||||
const files = ['public/index.html', 'api/[endpoint].js'];
|
||||
|
||||
const { builders } = await detectBuilders(files, pkg);
|
||||
const { builders } = await detectBuilders(files, detected);
|
||||
const { defaultRoutes } = await detectRoutes(files, builders);
|
||||
expect(defaultRoutes[1].status).toBe(404);
|
||||
expect(defaultRoutes[1].src).toBe('/api(\\/.*)?$');
|
||||
expect(defaultRoutes[1].src).toBe('/api(/.*)?$');
|
||||
expect(defaultRoutes.length).toBe(2);
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ const { mkdirp, copyFile } = require('fs-extra');
|
||||
|
||||
const {
|
||||
glob,
|
||||
debug,
|
||||
download,
|
||||
shouldServe,
|
||||
createLambda,
|
||||
@@ -14,7 +15,6 @@ exports.analyze = ({ files, entrypoint }) => files[entrypoint].digest;
|
||||
exports.version = 3;
|
||||
|
||||
exports.build = async ({ workPath, files, entrypoint, meta, config }) => {
|
||||
console.log('downloading files...');
|
||||
const outDir = await getWritableDirectory();
|
||||
|
||||
await download(files, workPath, meta);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@now/cgi",
|
||||
"version": "1.0.1-canary.0",
|
||||
"version": "1.0.1",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "now",
|
||||
"version": "16.6.0",
|
||||
"version": "16.7.1-canary.2",
|
||||
"preferGlobal": true,
|
||||
"license": "Apache-2.0",
|
||||
"description": "The command-line interface for Now",
|
||||
@@ -146,7 +146,6 @@
|
||||
"ora": "3.4.0",
|
||||
"pcre-to-regexp": "1.0.0",
|
||||
"pluralize": "7.0.0",
|
||||
"pre-commit": "1.2.2",
|
||||
"printf": "0.2.5",
|
||||
"progress": "2.0.3",
|
||||
"promisepipe": "3.0.0",
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
const fs = require('fs');
|
||||
const { promisify } = require('util');
|
||||
const { join, delimiter } = require('path');
|
||||
const { homedir } = require('os');
|
||||
|
||||
const stat = promisify(fs.stat);
|
||||
const unlink = promisify(fs.unlink);
|
||||
@@ -39,7 +40,16 @@ function isGlobal() {
|
||||
// See: https://git.io/fj4jD
|
||||
function getNowPath() {
|
||||
if (process.platform === 'win32') {
|
||||
const path = join(process.env.LOCALAPPDATA, 'now-cli', 'now.exe');
|
||||
const { LOCALAPPDATA, USERPROFILE, HOMEPATH } = process.env;
|
||||
const home = homedir() || USERPROFILE || HOMEPATH;
|
||||
let path;
|
||||
if (LOCALAPPDATA) {
|
||||
path = join(LOCALAPPDATA, 'now-cli', 'now.exe');
|
||||
} else if (home) {
|
||||
path = join(home, 'AppData', 'Local', 'now-cli', 'now.exe');
|
||||
} else {
|
||||
path = '';
|
||||
}
|
||||
return fs.existsSync(path) ? path : null;
|
||||
}
|
||||
|
||||
@@ -48,7 +58,7 @@ function getNowPath() {
|
||||
const paths = [
|
||||
join(process.env.HOME || '/', 'bin'),
|
||||
'/usr/local/bin',
|
||||
'/usr/bin'
|
||||
'/usr/bin',
|
||||
];
|
||||
|
||||
for (const basePath of paths) {
|
||||
|
||||
@@ -38,7 +38,6 @@ import {
|
||||
} from '../../util/errors-ts';
|
||||
import { SchemaValidationFailed } from '../../util/errors';
|
||||
import purchaseDomainIfAvailable from '../../util/domains/purchase-domain-if-available';
|
||||
import handleCertError from '../../util/certs/handle-cert-error';
|
||||
import isWildcardAlias from '../../util/alias/is-wildcard-alias';
|
||||
import shouldDeployDir from '../../util/deploy/should-deploy-dir';
|
||||
|
||||
@@ -294,15 +293,11 @@ export default async function main(
|
||||
parseEnv(argv['--env'])
|
||||
);
|
||||
|
||||
// Enable debug mode for builders
|
||||
const buildDebugEnv = debugEnabled ? { NOW_BUILDER_DEBUG: '1' } : {};
|
||||
|
||||
// Merge build env out of `build.env` from now.json, and `--build-env` args
|
||||
const deploymentBuildEnv = Object.assign(
|
||||
{},
|
||||
parseEnv(localConfig.build && localConfig.build.env),
|
||||
parseEnv(argv['--build-env']),
|
||||
buildDebugEnv
|
||||
parseEnv(argv['--build-env'])
|
||||
);
|
||||
|
||||
// If there's any undefined values, then inherit them from this process
|
||||
@@ -386,15 +381,13 @@ export default async function main(
|
||||
return 1;
|
||||
}
|
||||
|
||||
const deploymentResponse = handleCertError(
|
||||
output,
|
||||
await getDeploymentByIdOrHost(now, contextName, deployment.id, 'v10')
|
||||
const deploymentResponse = await getDeploymentByIdOrHost(
|
||||
now,
|
||||
contextName,
|
||||
deployment.id,
|
||||
'v10'
|
||||
);
|
||||
|
||||
if (deploymentResponse === 1) {
|
||||
return deploymentResponse;
|
||||
}
|
||||
|
||||
if (
|
||||
deploymentResponse instanceof DeploymentNotFound ||
|
||||
deploymentResponse instanceof DeploymentPermissionDenied ||
|
||||
@@ -404,10 +397,6 @@ export default async function main(
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (handleCertError(output, deployment) === 1) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (deployment === null) {
|
||||
error('Uploading failed. Please try again.');
|
||||
return 1;
|
||||
|
||||
@@ -70,7 +70,6 @@ import {
|
||||
InvalidRegionOrDCForScale,
|
||||
} from '../../util/errors';
|
||||
import { SchemaValidationFailed } from '../../util/errors';
|
||||
import handleCertError from '../../util/certs/handle-cert-error';
|
||||
import readPackage from '../../util/read-package';
|
||||
|
||||
interface Env {
|
||||
@@ -801,11 +800,6 @@ async function sync({
|
||||
createArgs
|
||||
);
|
||||
|
||||
const handledResult = handleCertError(output, deployment);
|
||||
if (handledResult === 1) {
|
||||
return handledResult;
|
||||
}
|
||||
|
||||
if (
|
||||
deployment instanceof DomainNotFound ||
|
||||
deployment instanceof NotDomainOwner ||
|
||||
|
||||
@@ -18,7 +18,7 @@ export default async function dev(
|
||||
output: Output
|
||||
) {
|
||||
output.dim(
|
||||
`Now CLI ${pkg.version} dev (beta) — https://zeit.co/feedback/dev`
|
||||
`Now CLI ${pkg.version} dev (beta) — https://zeit.co/feedback`
|
||||
);
|
||||
|
||||
const [dir = '.'] = args;
|
||||
|
||||
@@ -10,6 +10,7 @@ import formatDate from '../../util/format-date';
|
||||
import formatNSTable from '../../util/format-ns-table';
|
||||
import getDomainByName from '../../util/domains/get-domain-by-name';
|
||||
import getScope from '../../util/get-scope';
|
||||
import getDomainPrice from '../../util/domains/get-domain-price';
|
||||
|
||||
type Options = {
|
||||
'--debug': boolean;
|
||||
@@ -23,7 +24,7 @@ export default async function inspect(
|
||||
) {
|
||||
const {
|
||||
authConfig: { token },
|
||||
config
|
||||
config,
|
||||
} = ctx;
|
||||
const { currentTeam } = config;
|
||||
const { apiUrl } = ctx;
|
||||
@@ -61,7 +62,12 @@ export default async function inspect(
|
||||
}
|
||||
|
||||
output.debug(`Fetching domain info`);
|
||||
const domain = await getDomainByName(client, contextName, domainName);
|
||||
const [domain, renewalPrice] = await Promise.all([
|
||||
getDomainByName(client, contextName, domainName),
|
||||
getDomainPrice(client, domainName, 'renewal')
|
||||
.then(res => (res instanceof Error ? null : res.price))
|
||||
.catch(() => null),
|
||||
]);
|
||||
if (domain instanceof DomainNotFound) {
|
||||
output.error(
|
||||
`Domain not found by "${domainName}" under ${chalk.bold(contextName)}`
|
||||
@@ -104,24 +110,35 @@ export default async function inspect(
|
||||
` ${chalk.cyan('Bought At')}\t\t\t${formatDate(domain.boughtAt)}\n`
|
||||
);
|
||||
output.print(
|
||||
` ${chalk.cyan('Transferred At')}\t\t${formatDate(domain.transferredAt)}\n`
|
||||
` ${chalk.cyan('Transferred At')}\t\t${formatDate(
|
||||
domain.transferredAt
|
||||
)}\n`
|
||||
);
|
||||
output.print(
|
||||
` ${chalk.cyan('Expires At')}\t\t\t${formatDate(domain.expiresAt)}\n`
|
||||
);
|
||||
output.print(
|
||||
` ${chalk.cyan('NS Verified At')}\t\t${formatDate(domain.nsVerifiedAt)}\n`
|
||||
` ${chalk.cyan('NS Verified At')}\t\t${formatDate(
|
||||
domain.nsVerifiedAt
|
||||
)}\n`
|
||||
);
|
||||
output.print(
|
||||
` ${chalk.cyan('TXT Verified At')}\t\t${formatDate(domain.txtVerifiedAt)}\n`
|
||||
` ${chalk.cyan('TXT Verified At')}\t\t${formatDate(
|
||||
domain.txtVerifiedAt
|
||||
)}\n`
|
||||
);
|
||||
output.print(` ${chalk.cyan('CDN Enabled')}\t\t${true}\n`);
|
||||
if (renewalPrice && domain.boughtAt) {
|
||||
output.print(
|
||||
` ${chalk.cyan('Renewal Price')}\t\t$${renewalPrice} USD\n`
|
||||
);
|
||||
}
|
||||
output.print(` ${chalk.cyan('CDN Enabled')}\t\t\t${true}\n`);
|
||||
output.print('\n');
|
||||
|
||||
output.print(chalk.bold(' Nameservers\n\n'));
|
||||
output.print(
|
||||
`${formatNSTable(domain.intendedNameservers, domain.nameservers, {
|
||||
extraSpace: ' '
|
||||
extraSpace: ' ',
|
||||
})}\n`
|
||||
);
|
||||
output.print('\n');
|
||||
@@ -129,7 +146,7 @@ export default async function inspect(
|
||||
output.print(chalk.bold(' Verification Record\n\n'));
|
||||
output.print(
|
||||
`${dnsTable([['_now', 'TXT', domain.verificationRecord]], {
|
||||
extraSpace: ' '
|
||||
extraSpace: ' ',
|
||||
})}\n`
|
||||
);
|
||||
output.print('\n');
|
||||
|
||||
@@ -239,7 +239,7 @@ export default async function main(ctx) {
|
||||
return 1;
|
||||
}
|
||||
if (deployment.version === 2) {
|
||||
output.error('Cannot scale a deployment containing builds');
|
||||
output.error('Cannot scale a Now 2.0 deployment');
|
||||
now.close();
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -2,13 +2,7 @@ import { NowConfig } from './util/dev/types';
|
||||
|
||||
export type ThenArg<T> = T extends Promise<infer U> ? U : T;
|
||||
|
||||
export interface Config extends NowConfig {
|
||||
alias?: string[] | string;
|
||||
aliases?: string[] | string;
|
||||
name?: string;
|
||||
type?: string;
|
||||
scope?: string;
|
||||
}
|
||||
export type Config = NowConfig;
|
||||
|
||||
export interface NowContext {
|
||||
argv: string[];
|
||||
|
||||
@@ -6,12 +6,14 @@ import {
|
||||
createDeployment,
|
||||
createLegacyDeployment,
|
||||
DeploymentOptions,
|
||||
} from 'now-client/dist';
|
||||
NowClientOptions,
|
||||
} from 'now-client';
|
||||
import wait from '../output/wait';
|
||||
import { Output } from '../output';
|
||||
// @ts-ignore
|
||||
import Now from '../../util';
|
||||
import { NowConfig } from '../dev/types';
|
||||
import ua from '../ua';
|
||||
|
||||
export default async function processDeployment({
|
||||
now,
|
||||
@@ -21,9 +23,9 @@ export default async function processDeployment({
|
||||
requestBody,
|
||||
uploadStamp,
|
||||
deployStamp,
|
||||
legacy,
|
||||
env,
|
||||
isLegacy,
|
||||
quiet,
|
||||
force,
|
||||
nowConfig,
|
||||
}: {
|
||||
now: Now;
|
||||
@@ -33,27 +35,36 @@ export default async function processDeployment({
|
||||
requestBody: DeploymentOptions;
|
||||
uploadStamp: () => number;
|
||||
deployStamp: () => number;
|
||||
legacy: boolean;
|
||||
env: any;
|
||||
isLegacy: boolean;
|
||||
quiet: boolean;
|
||||
nowConfig?: NowConfig;
|
||||
force?: boolean;
|
||||
}) {
|
||||
const { warn, log, debug, note } = output;
|
||||
let bar: Progress | null = null;
|
||||
|
||||
const path0 = paths[0];
|
||||
const opts: DeploymentOptions = {
|
||||
...requestBody,
|
||||
debug: now._debug,
|
||||
const { env = {} } = requestBody;
|
||||
|
||||
const nowClientOptions: NowClientOptions = {
|
||||
teamId: now.currentTeam,
|
||||
apiUrl: now._apiUrl,
|
||||
token: now._token,
|
||||
debug: now._debug,
|
||||
userAgent: ua,
|
||||
path: paths[0],
|
||||
force,
|
||||
};
|
||||
|
||||
if (!legacy) {
|
||||
if (!isLegacy) {
|
||||
let queuedSpinner = null;
|
||||
let buildSpinner = null;
|
||||
let deploySpinner = null;
|
||||
|
||||
for await (const event of createDeployment(path0, opts, nowConfig)) {
|
||||
for await (const event of createDeployment(
|
||||
nowClientOptions,
|
||||
requestBody,
|
||||
nowConfig
|
||||
)) {
|
||||
if (event.type === 'hashes-calculated') {
|
||||
hashes = event.payload;
|
||||
}
|
||||
@@ -110,7 +121,7 @@ export default async function processDeployment({
|
||||
now._host = event.payload.url;
|
||||
|
||||
if (!quiet) {
|
||||
const version = legacy ? `${chalk.grey('[v1]')} ` : '';
|
||||
const version = isLegacy ? `${chalk.grey('[v1]')} ` : '';
|
||||
log(`https://${event.payload.url} ${version}${deployStamp()}`);
|
||||
} else {
|
||||
process.stdout.write(`https://${event.payload.url}`);
|
||||
@@ -176,7 +187,11 @@ export default async function processDeployment({
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for await (const event of createLegacyDeployment(path0, opts, nowConfig)) {
|
||||
for await (const event of createLegacyDeployment(
|
||||
nowClientOptions,
|
||||
requestBody,
|
||||
nowConfig
|
||||
)) {
|
||||
if (event.type === 'hashes-calculated') {
|
||||
hashes = event.payload;
|
||||
}
|
||||
@@ -224,7 +239,7 @@ export default async function processDeployment({
|
||||
now._host = event.payload.url;
|
||||
|
||||
if (!quiet) {
|
||||
const version = legacy ? `${chalk.grey('[v1]')} ` : '';
|
||||
const version = isLegacy ? `${chalk.grey('[v1]')} ` : '';
|
||||
log(`${event.payload.url} ${version}${deployStamp()}`);
|
||||
} else {
|
||||
process.stdout.write(`https://${event.payload.url}`);
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import chalk from 'chalk';
|
||||
import execa from 'execa';
|
||||
import semver from 'semver';
|
||||
import pipe from 'promisepipe';
|
||||
@@ -8,7 +7,6 @@ import { extract } from 'tar-fs';
|
||||
import { createHash } from 'crypto';
|
||||
import { createGunzip } from 'zlib';
|
||||
import { join, resolve } from 'path';
|
||||
import { funCacheDir } from '@zeit/fun';
|
||||
import { PackageJson } from '@now/build-utils';
|
||||
import XDGAppPaths from 'xdg-app-paths';
|
||||
import {
|
||||
@@ -17,7 +15,6 @@ import {
|
||||
readFile,
|
||||
readJSON,
|
||||
writeFile,
|
||||
remove,
|
||||
} from 'fs-extra';
|
||||
import pkg from '../../../package.json';
|
||||
|
||||
@@ -104,11 +101,7 @@ export async function prepareBuilderDir() {
|
||||
|
||||
if (!hasBundledBuilders(dependencies)) {
|
||||
const extractor = extract(builderDir);
|
||||
await pipe(
|
||||
createReadStream(bundledTarballPath),
|
||||
createGunzip(),
|
||||
extractor
|
||||
);
|
||||
await pipe(createReadStream(bundledTarballPath), createGunzip(), extractor);
|
||||
}
|
||||
|
||||
return builderDir;
|
||||
@@ -158,6 +151,14 @@ export function getBuildUtils(packages: string[]): string {
|
||||
return `@now/build-utils@${version}`;
|
||||
}
|
||||
|
||||
function parseVersionSafe(rawSpec: string) {
|
||||
try {
|
||||
return semver.parse(rawSpec);
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export function filterPackage(
|
||||
builderSpec: string,
|
||||
distTag: string,
|
||||
@@ -165,6 +166,17 @@ export function filterPackage(
|
||||
) {
|
||||
if (builderSpec in localBuilders) return false;
|
||||
const parsed = npa(builderSpec);
|
||||
const parsedVersion = parseVersionSafe(parsed.rawSpec);
|
||||
// skip install of already installed runtime
|
||||
if (
|
||||
parsed.name &&
|
||||
parsed.type === 'version' &&
|
||||
parsedVersion &&
|
||||
buildersPkg.dependencies &&
|
||||
parsedVersion.version == buildersPkg.dependencies[parsed.name]
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
parsed.name &&
|
||||
parsed.type === 'tag' &&
|
||||
|
||||
@@ -83,12 +83,6 @@ async function createBuildProcess(
|
||||
NOW_REGION: 'dev1',
|
||||
};
|
||||
|
||||
// Builders won't show debug logs by default.
|
||||
// The `NOW_BUILDER_DEBUG` env variable enables them.
|
||||
if (debugEnabled) {
|
||||
env.NOW_BUILDER_DEBUG = '1';
|
||||
}
|
||||
|
||||
const buildProcess = fork(modulePath, [], {
|
||||
cwd: workPath,
|
||||
env,
|
||||
|
||||
@@ -88,6 +88,17 @@ export default async function(
|
||||
continue;
|
||||
}
|
||||
|
||||
if (routeConfig.check && devServer) {
|
||||
const { pathname = '/' } = url.parse(destPath);
|
||||
const hasDestFile = await devServer.hasFilesystem(pathname);
|
||||
if (!hasDestFile) {
|
||||
// If the file is not found, `check: true` will
|
||||
// behave the same as `continue: true`
|
||||
reqPathname = destPath;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (isURL(destPath)) {
|
||||
found = {
|
||||
found: true,
|
||||
|
||||
@@ -22,6 +22,8 @@ import {
|
||||
PackageJson,
|
||||
detectBuilders,
|
||||
detectRoutes,
|
||||
detectDefaults,
|
||||
DetectorFilesystem,
|
||||
} from '@now/build-utils';
|
||||
|
||||
import { once } from '../once';
|
||||
@@ -100,6 +102,25 @@ function sortBuilders(buildA: Builder, buildB: Builder) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
class DevDetectorFilesystem extends DetectorFilesystem {
|
||||
private dir: string;
|
||||
private files: string[];
|
||||
|
||||
constructor(dir: string, files: string[]) {
|
||||
super();
|
||||
this.dir = dir;
|
||||
this.files = files;
|
||||
}
|
||||
|
||||
_exists(name: string): Promise<boolean> {
|
||||
return Promise.resolve(this.files.includes(name));
|
||||
}
|
||||
|
||||
_readFile(name: string): Promise<Buffer> {
|
||||
return fs.readFile(join(this.dir, name));
|
||||
}
|
||||
}
|
||||
|
||||
export default class DevServer {
|
||||
public cwd: string;
|
||||
public debug: boolean;
|
||||
@@ -477,8 +498,6 @@ export default class DevServer {
|
||||
return this.cachedNowConfig;
|
||||
}
|
||||
|
||||
const pkg = await this.getPackageJson();
|
||||
|
||||
// The default empty `now.json` is used to serve all files as static
|
||||
// when no `now.json` is present
|
||||
let config: NowConfig = this.cachedNowConfig || { version: 2 };
|
||||
@@ -526,10 +545,18 @@ export default class DevServer {
|
||||
|
||||
// no builds -> zero config
|
||||
if (!config.builds || config.builds.length === 0) {
|
||||
const { builders, warnings, errors } = await detectBuilders(files, pkg, {
|
||||
const detectorResult = await detectDefaults({
|
||||
fs: new DevDetectorFilesystem(this.cwd, files),
|
||||
});
|
||||
|
||||
const { builders, warnings, errors } = await detectBuilders(
|
||||
files,
|
||||
detectorResult,
|
||||
{
|
||||
tag: getDistTag(cliVersion) === 'canary' ? 'canary' : 'latest',
|
||||
functions: config.functions,
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
if (errors) {
|
||||
this.output.error(errors[0].message);
|
||||
@@ -575,29 +602,6 @@ export default class DevServer {
|
||||
return config;
|
||||
}
|
||||
|
||||
async getPackageJson(): Promise<PackageJson | null> {
|
||||
const pkgPath = join(this.cwd, 'package.json');
|
||||
let pkg: PackageJson | null = null;
|
||||
|
||||
this.output.debug('Reading `package.json` file');
|
||||
|
||||
try {
|
||||
pkg = JSON.parse(await fs.readFile(pkgPath, 'utf8'));
|
||||
} catch (err) {
|
||||
if (err.code === 'ENOENT') {
|
||||
this.output.debug('No `package.json` file present');
|
||||
} else if (err.name === 'SyntaxError') {
|
||||
this.output.warn(
|
||||
`There is a syntax error in the \`package.json\` file: ${err.message}`
|
||||
);
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
return pkg;
|
||||
}
|
||||
|
||||
async tryValidateOrExit(
|
||||
config: NowConfig,
|
||||
validate: (c: NowConfig) => string | null
|
||||
|
||||
@@ -9,9 +9,12 @@ import {
|
||||
PackageJson,
|
||||
BuilderFunctions,
|
||||
} from '@now/build-utils';
|
||||
import { NowConfig } from 'now-client';
|
||||
import { NowRedirect, NowRewrite, NowHeader, Route } from '@now/routing-utils';
|
||||
import { Output } from '../output';
|
||||
|
||||
export { NowConfig };
|
||||
|
||||
export interface DevServerOptions {
|
||||
output: Output;
|
||||
debug: boolean;
|
||||
@@ -31,24 +34,6 @@ export interface BuildMatch extends BuildConfig {
|
||||
|
||||
export type RouteConfig = Route;
|
||||
|
||||
export interface NowConfig {
|
||||
name?: string;
|
||||
version?: number;
|
||||
env?: EnvConfig;
|
||||
build?: {
|
||||
env?: EnvConfig;
|
||||
};
|
||||
builds?: BuildConfig[];
|
||||
routes?: RouteConfig[];
|
||||
files?: string[];
|
||||
cleanUrls?: boolean;
|
||||
rewrites?: NowRewrite[];
|
||||
redirects?: NowRedirect[];
|
||||
headers?: NowHeader[];
|
||||
trailingSlash?: boolean;
|
||||
functions?: BuilderFunctions;
|
||||
}
|
||||
|
||||
export interface HttpHandler {
|
||||
(req: http.IncomingMessage, res: http.ServerResponse): void;
|
||||
}
|
||||
|
||||
@@ -8,62 +8,10 @@ import {
|
||||
trailingSlashSchema,
|
||||
} from '@now/routing-utils';
|
||||
import { NowConfig } from './types';
|
||||
import { functionsSchema, buildsSchema } from '@now/build-utils';
|
||||
|
||||
const ajv = new Ajv();
|
||||
|
||||
const buildsSchema = {
|
||||
type: 'array',
|
||||
minItems: 0,
|
||||
maxItems: 128,
|
||||
items: {
|
||||
type: 'object',
|
||||
additionalProperties: false,
|
||||
required: ['use'],
|
||||
properties: {
|
||||
src: {
|
||||
type: 'string',
|
||||
minLength: 1,
|
||||
maxLength: 4096,
|
||||
},
|
||||
use: {
|
||||
type: 'string',
|
||||
minLength: 3,
|
||||
maxLength: 256,
|
||||
},
|
||||
config: { type: 'object' },
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const functionsSchema = {
|
||||
type: 'object',
|
||||
minProperties: 1,
|
||||
maxProperties: 50,
|
||||
additionalProperties: false,
|
||||
patternProperties: {
|
||||
'^.{1,256}$': {
|
||||
type: 'object',
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
runtime: {
|
||||
type: 'string',
|
||||
maxLength: 256,
|
||||
},
|
||||
memory: {
|
||||
enum: Object.keys(Array.from({ length: 50 }))
|
||||
.slice(2, 48)
|
||||
.map(x => Number(x) * 64),
|
||||
},
|
||||
maxDuration: {
|
||||
type: 'number',
|
||||
minimum: 1,
|
||||
maximum: 900,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const validateBuilds = ajv.compile(buildsSchema);
|
||||
const validateRoutes = ajv.compile(routesSchema);
|
||||
const validateCleanUrls = ajv.compile(cleanUrlsSchema);
|
||||
|
||||
@@ -1097,7 +1097,7 @@ export class LambdaSizeExceededError extends NowError<
|
||||
size
|
||||
).toLowerCase()}) exceeds the maximum size limit (${bytes(
|
||||
maxLambdaSize
|
||||
).toLowerCase()}). Learn more: https://zeit.co/docs/v2/deployments/concepts/lambdas/#maximum-bundle-size`,
|
||||
).toLowerCase()}).`,
|
||||
meta: { size, maxLambdaSize },
|
||||
});
|
||||
}
|
||||
|
||||
@@ -53,7 +53,6 @@ export default class Now extends EventEmitter {
|
||||
nowConfig = {},
|
||||
hasNowJson = false,
|
||||
sessionAffinity = 'random',
|
||||
atlas = false,
|
||||
|
||||
// Latest
|
||||
name,
|
||||
@@ -71,39 +70,21 @@ export default class Now extends EventEmitter {
|
||||
) {
|
||||
const opts = { output: this._output, hasNowJson };
|
||||
const { log, warn, debug } = this._output;
|
||||
const isBuilds = type === null;
|
||||
const isLegacy = type !== null;
|
||||
|
||||
let files = [];
|
||||
let hashes = {};
|
||||
const relatives = {};
|
||||
let engines;
|
||||
let deployment;
|
||||
let requestBody = {};
|
||||
|
||||
if (isBuilds) {
|
||||
requestBody = {
|
||||
token: this._token,
|
||||
teamId: this.currentTeam,
|
||||
env,
|
||||
build,
|
||||
public: wantsPublic || nowConfig.public,
|
||||
name,
|
||||
project,
|
||||
meta,
|
||||
regions,
|
||||
force: forceNew,
|
||||
};
|
||||
|
||||
if (target) {
|
||||
requestBody.target = target;
|
||||
}
|
||||
} else if (type === 'npm') {
|
||||
if (type === 'npm') {
|
||||
files = await getNpmFiles(paths[0], pkg, nowConfig, opts);
|
||||
|
||||
// A `start` or `now-start` npm script, or a `server.js` file
|
||||
// in the root directory of the deployment are required
|
||||
if (
|
||||
!isBuilds &&
|
||||
isLegacy &&
|
||||
!hasNpmStart(pkg) &&
|
||||
!hasFile(paths[0], files, 'server.js')
|
||||
) {
|
||||
@@ -139,30 +120,30 @@ export default class Now extends EventEmitter {
|
||||
|
||||
const uploadStamp = stamp();
|
||||
|
||||
if (isBuilds) {
|
||||
deployment = await processDeployment({
|
||||
now: this,
|
||||
output: this._output,
|
||||
hashes,
|
||||
paths,
|
||||
requestBody,
|
||||
uploadStamp,
|
||||
deployStamp,
|
||||
quiet,
|
||||
nowConfig,
|
||||
});
|
||||
} else {
|
||||
// Read `registry.npmjs.org` authToken from .npmrc
|
||||
let authToken;
|
||||
let requestBody = {
|
||||
...nowConfig,
|
||||
env,
|
||||
build,
|
||||
public: wantsPublic || nowConfig.public,
|
||||
name,
|
||||
project,
|
||||
meta,
|
||||
regions,
|
||||
target: target || undefined,
|
||||
};
|
||||
|
||||
if (type === 'npm' && forwardNpm) {
|
||||
authToken =
|
||||
(await readAuthToken(paths[0])) || (await readAuthToken(homedir()));
|
||||
}
|
||||
// Ignore specific items from Now.json
|
||||
delete requestBody.scope;
|
||||
delete requestBody.github;
|
||||
|
||||
if (isLegacy) {
|
||||
// Read `registry.npmjs.org` authToken from .npmrc
|
||||
const registryAuthToken =
|
||||
type === 'npm' && forwardNpm
|
||||
? (await readAuthToken(paths[0])) || (await readAuthToken(homedir()))
|
||||
: undefined;
|
||||
|
||||
requestBody = {
|
||||
token: this._token,
|
||||
teamId: this.currentTeam,
|
||||
env,
|
||||
build,
|
||||
meta,
|
||||
@@ -172,18 +153,17 @@ export default class Now extends EventEmitter {
|
||||
project,
|
||||
description,
|
||||
deploymentType: type,
|
||||
registryAuthToken: authToken,
|
||||
registryAuthToken,
|
||||
engines,
|
||||
scale,
|
||||
sessionAffinity,
|
||||
limits: nowConfig.limits,
|
||||
atlas,
|
||||
config: nowConfig,
|
||||
functions: nowConfig.functions,
|
||||
};
|
||||
}
|
||||
|
||||
deployment = await processDeployment({
|
||||
legacy: true,
|
||||
isLegacy,
|
||||
now: this,
|
||||
output: this._output,
|
||||
hashes,
|
||||
@@ -192,10 +172,9 @@ export default class Now extends EventEmitter {
|
||||
uploadStamp,
|
||||
deployStamp,
|
||||
quiet,
|
||||
env,
|
||||
nowConfig,
|
||||
force: forceNew,
|
||||
});
|
||||
}
|
||||
|
||||
// We report about files whose sizes are too big
|
||||
let missingVersion = false;
|
||||
@@ -228,7 +207,7 @@ export default class Now extends EventEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
if (!isBuilds && !quiet && type === 'npm' && deployment.nodeVersion) {
|
||||
if (isLegacy && !quiet && type === 'npm' && deployment.nodeVersion) {
|
||||
if (engines && engines.node && !missingVersion) {
|
||||
log(
|
||||
chalk`Using Node.js {bold ${deployment.nodeVersion}} (requested: {dim \`${engines.node}\`})`
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import chalk from 'chalk';
|
||||
import boxen from 'boxen';
|
||||
import { format } from 'util';
|
||||
import { Console } from 'console';
|
||||
|
||||
@@ -18,10 +19,32 @@ export default function createOutput({ debug: debugEnabled = false } = {}) {
|
||||
}
|
||||
|
||||
function warn(str: string, slug: string | null = null) {
|
||||
log(chalk`{yellow.bold WARN!} ${str}`);
|
||||
if (slug !== null) {
|
||||
log(`More details: https://err.sh/now/${slug}`);
|
||||
const prevTerm = process.env.TERM;
|
||||
|
||||
if (!prevTerm) {
|
||||
// workaround for https://github.com/sindresorhus/term-size/issues/13
|
||||
process.env.TERM = 'xterm';
|
||||
}
|
||||
|
||||
print(
|
||||
boxen(
|
||||
chalk.bold.yellow('WARN! ') +
|
||||
str +
|
||||
(slug ? `\nMore details: https://err.sh/now/${slug}` : ''),
|
||||
{
|
||||
padding: {
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
left: 1,
|
||||
right: 1,
|
||||
},
|
||||
borderColor: 'yellow',
|
||||
}
|
||||
)
|
||||
);
|
||||
print('\n');
|
||||
|
||||
process.env.TERM = prevTerm;
|
||||
}
|
||||
|
||||
function note(str: string) {
|
||||
@@ -59,7 +82,7 @@ export default function createOutput({ debug: debugEnabled = false } = {}) {
|
||||
_times: new Map(),
|
||||
log(a: string, ...args: string[]) {
|
||||
debug(format(a, ...args));
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
async function time(label: string, fn: Promise<any> | (() => Promise<any>)) {
|
||||
@@ -85,6 +108,6 @@ export default function createOutput({ debug: debugEnabled = false } = {}) {
|
||||
debug,
|
||||
dim,
|
||||
time,
|
||||
note
|
||||
note,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -58,9 +58,7 @@ export default async function preferV2Deployment({
|
||||
)} is missing a ${cmd('start')} script. ${INFO}`;
|
||||
}
|
||||
} else if (!pkg && !hasDockerfile) {
|
||||
return `Deploying to Now 2.0, because no ${highlight(
|
||||
'Dockerfile'
|
||||
)} was found. ${INFO}`;
|
||||
return `Deploying to Now 2.0 automatically. ${INFO}`;
|
||||
}
|
||||
|
||||
if (client && projectName) {
|
||||
|
||||