├── .editorconfig ├── .eslintrc.cjs ├── .gitattributes ├── .github ├── .kodiak.toml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ ├── fossa.yml │ ├── labeler.yml │ ├── pre-release.yml │ ├── release-please.yml │ └── workflow.yml ├── .gitignore ├── .prettierrc.json ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── assets └── logos │ ├── angular │ └── default.svg │ ├── assemble │ └── default.png │ ├── astro │ ├── dark.svg │ └── light.svg │ ├── blitz │ ├── dark.svg │ └── light.svg │ ├── brunch │ └── default.svg │ ├── cecil │ └── default.svg │ ├── create-react-app │ └── default.svg │ ├── docusaurus │ └── default.svg │ ├── eleventy │ └── default.svg │ ├── ember │ ├── dark.svg │ ├── default.svg │ └── light.svg │ ├── expo │ ├── dark.svg │ ├── default.svg │ └── light.svg │ ├── gatsby │ ├── dark.svg │ ├── default.svg │ └── light.svg │ ├── gridsome │ ├── dark.svg │ ├── default.svg │ └── light.svg │ ├── grunt │ └── default.svg │ ├── gulp │ └── default.svg │ ├── harp │ ├── default.svg │ └── light.svg │ ├── hexo │ └── default.svg │ ├── hugo │ └── default.svg │ ├── hydrogen │ └── default.svg │ ├── jekyll │ ├── dark.svg │ └── light.svg │ ├── metalsmith │ └── default.svg │ ├── middleman │ └── default.svg │ ├── nextjs │ ├── dark-logo.svg │ └── light-logo.svg │ ├── nuxt │ ├── nuxt-dark.svg │ ├── nuxt-default.svg │ └── nuxt-light.svg │ ├── parcel │ └── default.svg │ ├── phenomic │ └── default.svg │ ├── quasar │ └── default.svg │ ├── qwik │ └── default.svg │ ├── react-static │ └── default.png │ ├── redwoodjs │ └── default.svg │ ├── remix │ ├── dark.svg │ ├── default.svg │ └── light.svg │ ├── roots │ └── default.svg │ ├── sapper │ └── default.svg │ ├── solid-js │ ├── dark.svg │ └── default.svg │ ├── solid-start │ └── default.svg │ ├── stencil │ ├── dark.svg │ └── light.svg │ ├── svelte-kit │ └── default.svg │ ├── vite │ └── default.svg │ ├── vue │ └── default.svg │ ├── vuepress │ └── default.svg │ ├── wintersmith │ └── default.svg │ └── wmr │ └── default.svg ├── codecov.yml ├── commitlint.config.cjs ├── index.html ├── netlify.toml ├── package-lock.json ├── package.json ├── renovate.json5 ├── scripts └── transform_json.js ├── src ├── context.js ├── core.js ├── detect.js ├── dev.js ├── frameworks │ ├── angular.json │ ├── assemble.json │ ├── astro.json │ ├── blitz.json │ ├── brunch.json │ ├── cecil.json │ ├── create-react-app.json │ ├── docpad.json │ ├── docusaurus-v2.json │ ├── docusaurus.json │ ├── eleventy.json │ ├── ember.json │ ├── expo.json │ ├── gatsby.json │ ├── gridsome.json │ ├── grunt.json │ ├── gulp.json │ ├── harp.json │ ├── hexo.json │ ├── hugo.json │ ├── hydrogen.json │ ├── jekyll.json │ ├── main.js │ ├── metalsmith.json │ ├── middleman.json │ ├── next-nx.json │ ├── next.json │ ├── nuxt.json │ ├── nuxt3.json │ ├── parcel.json │ ├── phenomic.json │ ├── quasar-v0.17.json │ ├── quasar.json │ ├── qwik.json │ ├── react-static.json │ ├── redwoodjs.json │ ├── remix.json │ ├── roots.json │ ├── sapper.json │ ├── solid-js.json │ ├── solid-start.json │ ├── stencil.json │ ├── svelte-kit.json │ ├── svelte.json │ ├── vite.json │ ├── vue.json │ ├── vuepress.json │ ├── wintersmith.json │ ├── wmr.json │ └── zola.json ├── main.js ├── package.js ├── plugins.js └── run_script.js ├── test ├── detect.js ├── dev.js ├── fixtures │ ├── colon_scripts │ │ └── package.json │ ├── config_files │ │ ├── config.rb │ │ └── package.json │ ├── dependencies │ │ └── package.json │ ├── dev_command_scripts │ │ └── package.json │ ├── dev_dependencies │ │ └── package.json │ ├── empty │ │ ├── .gitkeep │ │ └── package.json │ ├── empty_dependencies │ │ ├── config.rb │ │ └── package.json │ ├── empty_scripts │ │ └── package.json │ ├── excluded_dependencies │ │ └── package.json │ ├── excluded_script │ │ └── package.json │ ├── invalid_dependencies │ │ └── package.json │ ├── invalid_package │ │ └── package.json │ ├── invalid_scripts │ │ └── package.json │ ├── monorepos │ │ ├── app1 │ │ │ └── package.json │ │ ├── app2 │ │ │ └── package.json │ │ └── node_modules │ │ │ └── next │ │ │ └── package.json │ ├── multiple │ │ ├── node_modules │ │ │ ├── @vue │ │ │ │ └── cli-service │ │ │ │ │ └── package.json │ │ │ └── vuepress │ │ │ │ └── package.json │ │ └── package.json │ ├── next-plugin │ │ └── package.json │ ├── no_package │ │ └── config.rb │ ├── package.json │ ├── parent_package │ │ └── package.json │ ├── scripts-order │ │ ├── build-first │ │ │ └── package.json │ │ ├── command-substring │ │ │ └── package.json │ │ ├── dev-first │ │ │ └── package.json │ │ ├── postfix-format │ │ │ └── package.json │ │ └── vite-framework │ │ │ └── package.json │ ├── several_dependencies │ │ └── package.json │ ├── simple │ │ ├── node_modules │ │ │ └── sapper │ │ │ │ └── package.json │ │ └── package.json │ ├── syntax_package │ │ └── package.json │ ├── use_scripts │ │ └── package.json │ └── yarn_scripts │ │ ├── package.json │ │ └── yarn.lock ├── frameworks.js ├── helpers │ └── main.js ├── main.js ├── options.js ├── package.js ├── run_script.js └── snapshots │ ├── main.js.md │ └── main.js.snap └── vite.config.js /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | max_line_length = 120 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | const { overrides } = require('@netlify/eslint-config-node/.eslintrc_esm.cjs') 2 | 3 | module.exports = { 4 | extends: '@netlify/eslint-config-node/.eslintrc_esm.cjs', 5 | rules: { 6 | 'import/no-unresolved': [2, { ignore: ['build/'] }], 7 | }, 8 | overrides: [...overrides], 9 | } 10 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | -------------------------------------------------------------------------------- /.github/.kodiak.toml: -------------------------------------------------------------------------------- 1 | version = 1 2 | 3 | [merge.automerge_dependencies] 4 | versions = ["minor", "patch"] 5 | usernames = ["renovate"] 6 | 7 | [approve] 8 | auto_approve_usernames = ["renovate"] -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: 'Please replace with a clear and descriptive title' 5 | labels: 'type: bug' 6 | assignees: '' 7 | --- 8 | 9 | Thanks for reporting this bug! 10 | 11 | Please search other issues to make sure this bug has not already been reported. 12 | 13 | Then fill in the sections below. 14 | 15 | **Describe the bug** 16 | 17 | A clear and concise description of what the bug is. 18 | 19 | **Configuration** 20 | 21 | Please enter the following command in a terminal and copy/paste its output: 22 | 23 | ```bash 24 | npx envinfo --system --binaries --npmPackages @netlify/framework-info 25 | ``` 26 | 27 | **Pull requests** 28 | 29 | Pull requests are welcome! If you would like to help us fix this bug, please check our 30 | [contributions guidelines](../blob/main/CONTRIBUTING.md). 31 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: 'Please replace with a clear and descriptive title' 5 | labels: 'type: feature' 6 | assignees: '' 7 | --- 8 | 9 | 14 | 15 | **Which problem is this feature request solving?** 16 | 17 | 20 | 21 | **Describe the solution you'd like** 22 | 23 | 26 | 27 | **Describe alternatives you've considered** 28 | 29 | 32 | 33 | **Can you submit a pull request?** 34 | 35 | Yes/No. 36 | 37 | 41 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 🎉 Thanks for submitting a pull request! 🎉 2 | 3 | #### Summary 4 | 5 | Fixes # 6 | 7 | 10 | 11 | --- 12 | 13 | For us to review and ship your PR efficiently, please perform the following steps: 14 | 15 | - [ ] Open a [bug/issue](https://github.com/netlify/framework-info/issues/new/choose) before writing your code 🧑‍💻. This 16 | ensures we can discuss the changes and get feedback from everyone that should be involved. If you\`re fixing a 17 | typo or something that\`s on fire 🔥 (e.g. incident related), you can skip this step. 18 | - [ ] Read the [contribution guidelines](../CONTRIBUTING.md) 📖. This ensures your code follows our style guide and 19 | passes our tests. 20 | - [ ] Update or add tests (if any source code was changed or added) 🧪 21 | - [ ] Update or add documentation (if features were changed or added) 📝 22 | - [ ] Make sure the status checks below are successful ✅ 23 | 24 | **A picture of a cute animal (not mandatory, but encouraged)** 25 | -------------------------------------------------------------------------------- /.github/workflows/fossa.yml: -------------------------------------------------------------------------------- 1 | name: Dependency License Scanning 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - chore/fossa-workflow 8 | 9 | defaults: 10 | run: 11 | shell: bash 12 | 13 | jobs: 14 | fossa: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Checkout 18 | uses: actions/checkout@v3 19 | - name: Download fossa cli 20 | run: |- 21 | mkdir -p $HOME/.local/bin 22 | curl https://raw.githubusercontent.com/fossas/fossa-cli/master/install.sh | bash -s -- -b $HOME/.local/bin 23 | echo "$HOME/.local/bin" >> $GITHUB_PATH 24 | 25 | - name: Fossa init 26 | run: fossa init 27 | - name: Set env 28 | run: echo "line_number=$(grep -n "project" .fossa.yml | cut -f1 -d:)" >> $GITHUB_ENV 29 | - name: Configuration 30 | run: |- 31 | sed -i "${line_number}s|.*| project: git@github.com:${GITHUB_REPOSITORY}.git|" .fossa.yml 32 | cat .fossa.yml 33 | - name: Upload dependencies 34 | run: fossa analyze --debug 35 | env: 36 | FOSSA_API_KEY: ${{ secrets.FOSSA_API_KEY }} 37 | -------------------------------------------------------------------------------- /.github/workflows/labeler.yml: -------------------------------------------------------------------------------- 1 | name: Label PR 2 | on: 3 | pull_request: 4 | types: [opened, edited] 5 | 6 | jobs: 7 | label-pr: 8 | if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == false 9 | runs-on: ubuntu-latest 10 | strategy: 11 | matrix: 12 | pr: 13 | [ 14 | { prefix: 'fix', type: 'bug' }, 15 | { prefix: 'chore', type: 'chore' }, 16 | { prefix: 'test', type: 'chore' }, 17 | { prefix: 'ci', type: 'chore' }, 18 | { prefix: 'feat', type: 'feature' }, 19 | { prefix: 'security', type: 'security' }, 20 | ] 21 | steps: 22 | - uses: netlify/pr-labeler-action@v1.1.0 23 | if: startsWith(github.event.pull_request.title, matrix.pr.prefix) 24 | with: 25 | token: '${{ secrets.GITHUB_TOKEN }}' 26 | label: 'type: ${{ matrix.pr.type }}' 27 | -------------------------------------------------------------------------------- /.github/workflows/pre-release.yml: -------------------------------------------------------------------------------- 1 | name: prerelease 2 | on: 3 | push: 4 | branches: 5 | # releases// 6 | - releases/*/* 7 | jobs: 8 | prerelease: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v3 12 | - uses: actions/setup-node@v3 13 | with: 14 | node-version: '*' 15 | cache: 'npm' 16 | check-latest: true 17 | registry-url: 'https://registry.npmjs.org' 18 | - name: Install core dependencies 19 | run: npm ci --no-audit 20 | - name: Build 21 | run: npm run build 22 | - name: Extract tag and version 23 | id: extract 24 | run: |- 25 | ref=${{ github.ref }} 26 | branch=${ref:11} 27 | tag_version=${branch:9} 28 | tag=${tag_version%/*} 29 | version=${tag_version##*/} 30 | echo "::set-output name=tag::${tag}" 31 | echo "::set-output name=version::${version}" 32 | - name: Log versions 33 | run: |- 34 | echo tag=${{ steps.extract.outputs.tag }} 35 | echo version=${{ steps.extract.outputs.version }} 36 | - name: Setup git user 37 | run: git config --global user.name github-actions 38 | - name: Setup git email 39 | run: git config --global user.email github-actions@github.com 40 | - name: Run npm version 41 | run: npm version ${{ steps.extract.outputs.version }}-${{ steps.extract.outputs.tag }} --allow-same-version 42 | - name: Push changes 43 | run: git push --follow-tags 44 | - name: Run npm publish 45 | run: npm publish --tag=${{ steps.extract.outputs.tag }} 46 | env: 47 | NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} 48 | -------------------------------------------------------------------------------- /.github/workflows/release-please.yml: -------------------------------------------------------------------------------- 1 | name: release-please 2 | on: 3 | push: 4 | branches: 5 | - main 6 | jobs: 7 | release-please: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: navikt/github-app-token-generator@a9cd374e271b8aef573b8c16ac46c44fb89b02db 11 | id: get-token 12 | with: 13 | private-key: ${{ secrets.TOKENS_PRIVATE_KEY }} 14 | app-id: ${{ secrets.TOKENS_APP_ID }} 15 | - uses: GoogleCloudPlatform/release-please-action@v3 16 | id: release 17 | with: 18 | token: ${{ steps.get-token.outputs.token }} 19 | release-type: node 20 | package-name: '@netlify/framework-info' 21 | - uses: actions/checkout@v3 22 | if: ${{ steps.release.outputs.release_created }} 23 | - uses: actions/setup-node@v3 24 | with: 25 | node-version: '*' 26 | cache: 'npm' 27 | check-latest: true 28 | registry-url: 'https://registry.npmjs.org' 29 | if: ${{ steps.release.outputs.release_created }} 30 | - run: npm publish 31 | if: ${{ steps.release.outputs.release_created }} 32 | env: 33 | NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} 34 | -------------------------------------------------------------------------------- /.github/workflows/workflow.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | on: 3 | # Ensure GitHub actions are not run twice for same commits 4 | push: 5 | branches: [main] 6 | tags: ['*'] 7 | pull_request: 8 | types: [opened, synchronize, reopened] 9 | jobs: 10 | build: 11 | strategy: 12 | matrix: 13 | os: [ubuntu-latest, macOS-latest, windows-latest] 14 | node-version: ['12.20.0', '*'] 15 | exclude: 16 | - os: macOS-latest 17 | node-version: '12.20.0' 18 | - os: windows-latest 19 | node-version: '12.20.0' 20 | fail-fast: false 21 | runs-on: ${{ matrix.os }} 22 | steps: 23 | - name: Git checkout 24 | uses: actions/checkout@v3 25 | - name: Node.js ${{ matrix.node-version }} 26 | uses: actions/setup-node@v3 27 | with: 28 | node-version: ${{ matrix.node-version }} 29 | cache: 'npm' 30 | check-latest: true 31 | - name: Install dependencies 32 | # Cypress has a postinstall script that doesn't work on older Node.js versions 33 | run: npm ci --no-audit --ignore-scripts 34 | - name: Build For All 35 | run: npm run build:json 36 | - name: Build For Browser 37 | run: npm run build:browser 38 | if: "${{ matrix.node-version == '*' }}" 39 | - name: Linting 40 | run: npm run format:ci 41 | if: "${{ matrix.node-version == '*' }}" 42 | - name: Tests 43 | run: npm run test:ci:ava 44 | - name: Get test coverage flags 45 | id: test-coverage-flags 46 | run: |- 47 | os=${{ matrix.os }} 48 | node=${{ matrix.node-version }} 49 | echo "::set-output name=os::${os/-latest/}" 50 | echo "::set-output name=node::node_${node//[.*]/}" 51 | shell: bash 52 | - uses: codecov/codecov-action@v2 53 | with: 54 | file: coverage/coverage-final.json 55 | flags: ${{ steps.test-coverage-flags.outputs.os }},${{ steps.test-coverage-flags.outputs.node }} 56 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | yarn-error.log 3 | coverage 4 | .eslintcache 5 | .vscode 6 | dist 7 | build 8 | cypress/videos 9 | cypress/screenshots 10 | cypress/support 11 | .DS_Store 12 | 13 | # Local Netlify folder 14 | .netlify 15 | 16 | # These are mock node_modules folder for testing purposes 17 | !test/fixtures/simple/node_modules 18 | !test/fixtures/multiple/node_modules 19 | !test/fixtures/monorepos/node_modules 20 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | "@netlify/eslint-config-node/.prettierrc.json" 2 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making 6 | participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, 7 | disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, 8 | religion, or sexual identity and orientation. 9 | 10 | ## Our Standards 11 | 12 | Examples of behavior that contributes to creating a positive environment include: 13 | 14 | - Using welcoming and inclusive language 15 | - Being respectful of differing viewpoints and experiences 16 | - Gracefully accepting constructive criticism 17 | - Focusing on what is best for the community 18 | - Showing empathy towards other community members 19 | 20 | Examples of unacceptable behavior by participants include: 21 | 22 | - The use of sexualized language or imagery and unwelcome sexual attention or advances 23 | - Trolling, insulting/derogatory comments, and personal or political attacks 24 | - Public or private harassment 25 | - Publishing others' private information, such as a physical or electronic address, without explicit permission 26 | - Other conduct which could reasonably be considered inappropriate in a professional setting 27 | 28 | ## Our Responsibilities 29 | 30 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take 31 | appropriate and fair corrective action in response to any instances of unacceptable behavior. 32 | 33 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, 34 | issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any 35 | contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 36 | 37 | ## Scope 38 | 39 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the 40 | project or its community. Examples of representing a project or community include using an official project e-mail 41 | address, posting via an official social media account, or acting as an appointed representative at an online or offline 42 | event. Representation of a project may be further defined and clarified by project maintainers. 43 | 44 | ## Enforcement 45 | 46 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at 47 | david@netlify.com. All complaints will be reviewed and investigated and will result in a response that is deemed 48 | necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to 49 | the reporter of an incident. Further details of specific enforcement policies may be posted separately. 50 | 51 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent 52 | repercussions as determined by other members of the project's leadership. 53 | 54 | ## Attribution 55 | 56 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at 57 | [http://contributor-covenant.org/version/1/4][version] 58 | 59 | [homepage]: http://contributor-covenant.org 60 | [version]: http://contributor-covenant.org/version/1/4/ 61 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # CONTRIBUTING 2 | 3 | Contributions are always welcome, no matter how large or small. Before contributing, please read the 4 | [code of conduct](CODE_OF_CONDUCT.md). 5 | 6 | ## Setup 7 | 8 | > Install Node.js + npm on your system: https://nodejs.org/en/download/ 9 | 10 | ```sh 11 | git clone git@github.com:netlify/framework-info.git 12 | cd framework-info 13 | npm install 14 | npm run build 15 | npm test 16 | ``` 17 | 18 | You can also use yarn. 19 | 20 | ## Testing 21 | 22 | The following things are tested for: 23 | 24 | - Dependencies (used an unused) 25 | - Linting 26 | 27 | ## Releasing 28 | 29 | Merge the release PR 30 | 31 | ### Creating a prerelease 32 | 33 | 1. Create a branch named `releases//` with the version and tag you'd like to release. 34 | 2. Push the branch to the repo. 35 | 36 | For example, a branch named `releases/rc/4.0.0` will create the version `v4.0.0-rc` and publish it under the `rc` tag. 37 | 38 | ## License 39 | 40 | By contributing to Netlify Node Client, you agree that your contributions will be licensed under its 41 | [MIT license](LICENSE). 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020 Netlify 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /assets/logos/angular/default.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 10 | 11 | 12 | 13 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /assets/logos/assemble/default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netlify/framework-info/db4604fc35bcea5dfdb502571ba257ea17d877ca/assets/logos/assemble/default.png -------------------------------------------------------------------------------- /assets/logos/astro/dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /assets/logos/astro/light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /assets/logos/cecil/default.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /assets/logos/docusaurus/default.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /assets/logos/eleventy/default.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /assets/logos/ember/dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /assets/logos/ember/default.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /assets/logos/ember/light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /assets/logos/expo/dark.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/logos/expo/default.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /assets/logos/expo/light.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/logos/gatsby/dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 9 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /assets/logos/gatsby/default.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 9 | 10 | 11 | 12 | 13 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /assets/logos/gatsby/light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /assets/logos/gridsome/dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /assets/logos/gridsome/default.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /assets/logos/gridsome/light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /assets/logos/harp/default.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /assets/logos/harp/light.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | Created by potrace 1.16, written by Peter Selinger 2001-2019 9 | 10 | 12 | 23 | 32 | 42 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /assets/logos/hexo/default.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/logos/hugo/default.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /assets/logos/hydrogen/default.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /assets/logos/metalsmith/default.svg: -------------------------------------------------------------------------------- 1 | Metalsmith logoimage/svg+xmlMetalsmith logoKevin Van LierdeIan Storm Taylor 2 | -------------------------------------------------------------------------------- /assets/logos/middleman/default.svg: -------------------------------------------------------------------------------- 1 | 10 | -------------------------------------------------------------------------------- /assets/logos/nextjs/dark-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /assets/logos/nextjs/light-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /assets/logos/nuxt/nuxt-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /assets/logos/nuxt/nuxt-default.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /assets/logos/nuxt/nuxt-light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /assets/logos/phenomic/default.svg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netlify/framework-info/db4604fc35bcea5dfdb502571ba257ea17d877ca/assets/logos/phenomic/default.svg -------------------------------------------------------------------------------- /assets/logos/quasar/default.svg: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /assets/logos/qwik/default.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /assets/logos/react-static/default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netlify/framework-info/db4604fc35bcea5dfdb502571ba257ea17d877ca/assets/logos/react-static/default.png -------------------------------------------------------------------------------- /assets/logos/redwoodjs/default.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/logos/remix/dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /assets/logos/remix/default.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /assets/logos/remix/light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /assets/logos/roots/default.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | logo 4 | Created with Sketch. 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /assets/logos/sapper/default.svg: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /assets/logos/solid-js/dark.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/logos/solid-js/default.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/logos/solid-start/default.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /assets/logos/stencil/dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /assets/logos/stencil/light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /assets/logos/svelte-kit/default.svg: -------------------------------------------------------------------------------- 1 | svelte-vertical 2 | -------------------------------------------------------------------------------- /assets/logos/vite/default.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /assets/logos/vue/default.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /assets/logos/vuepress/default.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | codecov: 2 | strict_yaml_branch: main 3 | coverage: 4 | range: [80, 100] 5 | parsers: 6 | javascript: 7 | enable_partials: true 8 | comment: false 9 | -------------------------------------------------------------------------------- /commitlint.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { extends: ['@commitlint/config-conventional'] } 2 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Framework Info 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /netlify.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | command = "npm run build" 3 | publish = "assets" 4 | 5 | [build.environment] 6 | NODE_VERSION = "14" 7 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@netlify/framework-info", 3 | "version": "9.5.3", 4 | "description": "Framework detection utility", 5 | "type": "module", 6 | "main": "./dist/index.umd.cjs", 7 | "exports": { 8 | "node": "./src/main.js", 9 | "default": "./dist/index.umd.cjs" 10 | }, 11 | "files": [ 12 | "build/*.js", 13 | "src/**/*.js", 14 | "dist/index.umd.cjs" 15 | ], 16 | "scripts": { 17 | "prepare": "husky install node_modules/@netlify/eslint-config-node/.husky/", 18 | "prepublishOnly": "npm ci && run-s build test", 19 | "build": "run-s build:*", 20 | "build:json": "node scripts/transform_json.js", 21 | "build:browser": "run-s build:browser:*", 22 | "build:browser:core": "vite build", 23 | "build:browser:site-root": "cpy index.html ../dist", 24 | "test": "npm run format && npm run test:dev", 25 | "format": "run-s format:check-fix:*", 26 | "format:ci": "run-s format:check:*", 27 | "format:check-fix:lint": "run-e format:check:lint format:fix:lint", 28 | "format:check:lint": "cross-env-shell eslint $npm_package_config_eslint", 29 | "format:fix:lint": "cross-env-shell eslint --fix $npm_package_config_eslint", 30 | "format:check-fix:prettier": "run-e format:check:prettier format:fix:prettier", 31 | "format:check:prettier": "cross-env-shell prettier --check $npm_package_config_prettier", 32 | "format:fix:prettier": "cross-env-shell prettier --write $npm_package_config_prettier", 33 | "test:dev": "run-s test:dev:*", 34 | "test:dev:ava": "ava", 35 | "test:ci": "run-s test:ci:*", 36 | "test:ci:ava": "c8 -r lcovonly -r text -r json ava" 37 | }, 38 | "keywords": [ 39 | "dependency-management", 40 | "continuous-integration", 41 | "deployment", 42 | "es6", 43 | "framework", 44 | "gulp", 45 | "javascript", 46 | "html", 47 | "library", 48 | "markdown", 49 | "nodejs", 50 | "npmjs", 51 | "package-manager", 52 | "webapp", 53 | "jamstack", 54 | "static-site-generator", 55 | "static-site", 56 | "gatsby", 57 | "hugo", 58 | "netlify" 59 | ], 60 | "author": "Netlify, Inc", 61 | "license": "MIT", 62 | "repository": "netlify/framework-info", 63 | "homepage": "https://github.com/netlify/framework-info", 64 | "bugs": { 65 | "url": "https://github.com/netlify/framework-info/issues" 66 | }, 67 | "config": { 68 | "eslint": "--ignore-path .gitignore --cache --format=codeframe --max-warnings=0 \"{src,scripts,test}/**/*.{cjs,mjs,js,jsx,html}\" --ignore-pattern \"test/fixtures/**/*\"", 69 | "prettier": "--ignore-path .gitignore --loglevel warn \"{src,scripts,test}/**/*.{cjs,mjs,js}\" \"*.{cjs,mjs,js,md,yml,json}\" \"!package-lock.json\" \"!CHANGELOG.md\"" 70 | }, 71 | "dependencies": { 72 | "ajv": "^8.0.0", 73 | "filter-obj": "^3.0.0", 74 | "find-up": "^6.3.0", 75 | "fs-extra": "^10.1.0", 76 | "is-plain-obj": "^4.0.0", 77 | "locate-path": "^7.0.0", 78 | "p-filter": "^3.0.0", 79 | "p-locate": "^6.0.0", 80 | "process": "^0.11.10", 81 | "read-pkg-up": "^9.0.0", 82 | "semver": "^7.3.4", 83 | "url": "^0.11.0" 84 | }, 85 | "devDependencies": { 86 | "@netlify/eslint-config-node": "^6.0.0", 87 | "ava": "^4.0.0", 88 | "babel-loader": "^8.2.2", 89 | "c8": "^7.11.0", 90 | "cpy": "^9.0.0", 91 | "cpy-cli": "^4.0.0", 92 | "del": "^6.0.0", 93 | "husky": "^7.0.4", 94 | "path-browserify": "^1.0.1", 95 | "rollup-plugin-node-polyfills": "^0.2.1", 96 | "test-each": "^4.0.0", 97 | "tmp-promise": "^3.0.2", 98 | "vite": "^3.1.6" 99 | }, 100 | "engines": { 101 | "node": "^14.14.0 || >=16.0.0" 102 | }, 103 | "ava": { 104 | "verbose": true, 105 | "workerThreads": false 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /renovate.json5: -------------------------------------------------------------------------------- 1 | { 2 | extends: ['github>netlify/renovate-config:esm'], 3 | ignorePresets: [':prHourlyLimit2'], 4 | semanticCommits: true, 5 | dependencyDashboard: true, 6 | automerge: true, 7 | packageRules: [], 8 | } 9 | -------------------------------------------------------------------------------- /scripts/transform_json.js: -------------------------------------------------------------------------------- 1 | import { promises as fs } from 'fs' 2 | import process from 'process' 3 | 4 | import { FRAMEWORK_NAMES } from '../src/frameworks/main.js' 5 | 6 | const FRAMEWORKS_DIR = new URL('../src/frameworks/', import.meta.url) 7 | const BUILD_DIR = new URL('../build/', import.meta.url) 8 | const FRAMEWORKS_BUILD = new URL('frameworks.js', BUILD_DIR) 9 | 10 | // We enforce frameworks to be written with JSON to ensure they remain logicless 11 | // which is simpler for contributors and avoid adding unnecessary logic. 12 | // However, Node.js does not support JSON imports without any experimental 13 | // flags. Also, not all browsers support it unless Webpack preprocesses it. 14 | // Therefore, we transform JSON to JavaScript files using at build time. 15 | const transformFrameworks = async function () { 16 | await fs.mkdir(BUILD_DIR, { recursive: true }) 17 | const frameworks = await Promise.all(FRAMEWORK_NAMES.map(transformFramework)) 18 | const fileContents = `${FRAMEWORKS_HEADER}${JSON.stringify(frameworks, null, 2)}` 19 | await fs.writeFile(FRAMEWORKS_BUILD, fileContents) 20 | } 21 | 22 | const updateLogoUrls = function (contents) { 23 | const updatedContents = contents 24 | const originalLogo = contents.logo 25 | if (originalLogo) { 26 | for (const [theme, urlPath] of Object.entries(originalLogo)) { 27 | updatedContents.logo[theme] = (process.env.DEPLOY_PRIME_URL || 'https://framework-info.netlify.app') + urlPath 28 | } 29 | } 30 | 31 | return updatedContents 32 | } 33 | 34 | const transformFramework = async function (frameworkName) { 35 | const frameworkUrl = new URL(`${frameworkName}.json`, FRAMEWORKS_DIR) 36 | const jsonContents = await fs.readFile(frameworkUrl) 37 | const contents = JSON.parse(jsonContents) 38 | 39 | const updatedContents = updateLogoUrls(contents) 40 | return updatedContents 41 | } 42 | 43 | const FRAMEWORKS_HEADER = `// This file is autogenerated at build time 44 | export const FRAMEWORKS = ` 45 | 46 | transformFrameworks() 47 | -------------------------------------------------------------------------------- /src/context.js: -------------------------------------------------------------------------------- 1 | import { cwd, version as nodejsVersion } from 'process' 2 | 3 | import isPlainObj from 'is-plain-obj' 4 | import { locatePath } from 'locate-path' 5 | import { readPackageUp } from 'read-pkg-up' 6 | 7 | export const getPackageJson = async (projectDir) => { 8 | try { 9 | const result = await readPackageUp({ cwd: projectDir, normalize: false }) 10 | if (result === undefined) { 11 | return {} 12 | } 13 | 14 | const { version, packageJson, path: packageJsonPath } = result 15 | 16 | if (!isPlainObj(packageJson)) { 17 | return { packageJsonPath } 18 | } 19 | 20 | return { version, packageJson, packageJsonPath } 21 | } catch { 22 | return {} 23 | } 24 | } 25 | 26 | export const getContext = async ({ projectDir = cwd(), nodeVersion = nodejsVersion } = {}) => { 27 | const { packageJson, packageJsonPath = projectDir } = await getPackageJson(projectDir) 28 | return { 29 | pathExists: async (path) => (await locatePath([path], { type: 'file', cwd: projectDir })) !== undefined, 30 | packageJson, 31 | packageJsonPath, 32 | nodeVersion, 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/core.js: -------------------------------------------------------------------------------- 1 | import pFilter from 'p-filter' 2 | 3 | import { FRAMEWORKS } from '../build/frameworks.js' 4 | 5 | import { usesFramework } from './detect.js' 6 | import { getDevCommands } from './dev.js' 7 | import { getPackageJsonContent } from './package.js' 8 | import { getPlugins } from './plugins.js' 9 | import { getRunScriptCommand } from './run_script.js' 10 | 11 | const getContext = (context) => { 12 | const { pathExists, packageJson, packageJsonPath = '.', nodeVersion } = context 13 | return { pathExists, packageJson, packageJsonPath, nodeVersion } 14 | } 15 | 16 | /** 17 | * @typedef {object} PollingStrategy 18 | * @property {'TCP'|'HTTP'} name - Name of the polling strategy. Possible names - TCP,HTTP 19 | */ 20 | 21 | /** 22 | * A callback to check if a path exists 23 | * @callback PathExists 24 | * @param {string} path 25 | * @returns {Promise} 26 | */ 27 | 28 | /** 29 | * @typedef {object} Context 30 | * @property {PathExists} pathExists - Checks if a path exists 31 | * @property {object} packageJson - Content of package.json 32 | * @property {string} [packageJsonPath='.'] - Path of package.json 33 | * @property {nodeVersion} [nodeVersion] - Node.js version of the runtime environment. Used to recommend Netlify build plugins 34 | */ 35 | 36 | /** 37 | * @typedef {object} Dev 38 | * @property {string} commands - Dev command. There might be several alternatives or empty 39 | * @property {number} port - Server port 40 | * @property {PollingStrategy[]} pollingStrategies - Dev Server polling strategies 41 | */ 42 | 43 | /** 44 | * @typedef {object} Build 45 | * @property {string} commands - Build command. There might be several alternatives 46 | * @property {string} directory - Relative path to the directory where files are built 47 | */ 48 | 49 | /** 50 | * @typedef {object} Framework 51 | * @property {string} id - framework id such as `"gatsby"` 52 | * @property {string} name - framework name such as `"Gatsby"` 53 | * @property {string} category - Category among `"static_site_generator"`, `"frontend_framework"` and `"build_tool"` 54 | * @property {Dev} dev - Information about the dev command 55 | * @property {Build} build - Information about the build command 56 | * @property {string} staticAssetsDirectory - Directory where the framework stores static assets. Can be `undefined` 57 | * @property {object} env - Environment variables that should be set when calling the dev command 58 | * @property {string[]} plugins - A list of recommend Netlify build plugins to install for the framework 59 | */ 60 | 61 | /** 62 | * Return all the frameworks used by a project. 63 | * 64 | * @param {Context} context - Context 65 | * 66 | * @returns {Promise} frameworks - Frameworks used by a project 67 | */ 68 | export const listFrameworks = async function (context) { 69 | const { pathExists, packageJson, packageJsonPath, nodeVersion } = getContext(context) 70 | const { npmDependencies, scripts, runScriptCommand } = await getProjectInfo({ 71 | pathExists, 72 | packageJson, 73 | packageJsonPath, 74 | }) 75 | const frameworks = await pFilter(FRAMEWORKS, (framework) => usesFramework(framework, { pathExists, npmDependencies })) 76 | const frameworkInfos = frameworks.map((framework) => 77 | getFrameworkInfo(framework, { scripts, runScriptCommand, nodeVersion }), 78 | ) 79 | return frameworkInfos 80 | } 81 | 82 | /** 83 | * Return whether a project uses a specific framework 84 | * 85 | * @param {string} frameworkId - Id such as `"gatsby"` 86 | * @param {Context} [context] - Context 87 | * 88 | * @returns {Promise} result - Whether the project uses this framework 89 | */ 90 | export const hasFramework = async function (frameworkId, context) { 91 | const framework = getFrameworkById(frameworkId) 92 | const { pathExists, packageJson, packageJsonPath } = getContext(context) 93 | const { npmDependencies } = await getProjectInfo({ pathExists, packageJson, packageJsonPath }) 94 | const result = await usesFramework(framework, { pathExists, npmDependencies }) 95 | return result 96 | } 97 | 98 | /** 99 | * Return some information about a framework used by a project. 100 | * 101 | * @param {string} frameworkId - Id such as `"gatsby"` 102 | * @param {Context} [context] - Context 103 | * 104 | * @returns {Promise} framework - Framework used by a project 105 | */ 106 | export const getFramework = async function (frameworkId, context) { 107 | const framework = getFrameworkById(frameworkId) 108 | const { pathExists, packageJson, packageJsonPath, nodeVersion } = getContext(context) 109 | const { scripts, runScriptCommand } = await getProjectInfo({ 110 | pathExists, 111 | packageJson, 112 | packageJsonPath, 113 | }) 114 | const frameworkInfo = getFrameworkInfo(framework, { scripts, runScriptCommand, nodeVersion }) 115 | return frameworkInfo 116 | } 117 | 118 | const getFrameworkById = function (frameworkId) { 119 | const framework = FRAMEWORKS.find(({ id }) => id === frameworkId) 120 | if (framework === undefined) { 121 | const frameworkIds = FRAMEWORKS.map((knownFramework) => getFrameworkId(knownFramework)) 122 | .sort() 123 | .join(', ') 124 | throw new Error(`Invalid framework "${frameworkId}". It should be one of: ${frameworkIds}`) 125 | } 126 | return framework 127 | } 128 | 129 | const getFrameworkId = function ({ id }) { 130 | return id 131 | } 132 | 133 | const getProjectInfo = async function ({ pathExists, packageJson, packageJsonPath }) { 134 | const { npmDependencies, scripts } = await getPackageJsonContent({ 135 | packageJson, 136 | }) 137 | const runScriptCommand = await getRunScriptCommand({ pathExists, packageJsonPath }) 138 | return { npmDependencies, scripts, runScriptCommand } 139 | } 140 | 141 | const getFrameworkInfo = function ( 142 | { 143 | id, 144 | name, 145 | detect, 146 | category, 147 | dev: { command: frameworkDevCommand, port, pollingStrategies }, 148 | build: { command: frameworkBuildCommand, directory }, 149 | staticAssetsDirectory, 150 | env, 151 | logo, 152 | plugins, 153 | }, 154 | { scripts, runScriptCommand, nodeVersion }, 155 | ) { 156 | const devCommands = getDevCommands({ frameworkDevCommand, scripts, runScriptCommand }) 157 | const recommendedPlugins = getPlugins(plugins, { nodeVersion }) 158 | return { 159 | id, 160 | name, 161 | package: { 162 | name: detect.npmDependencies[0], 163 | version: 'unknown', 164 | }, 165 | category, 166 | dev: { commands: devCommands, port, pollingStrategies }, 167 | build: { commands: [frameworkBuildCommand], directory }, 168 | staticAssetsDirectory, 169 | env, 170 | logo, 171 | plugins: recommendedPlugins, 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /src/detect.js: -------------------------------------------------------------------------------- 1 | import pLocate from 'p-locate' 2 | 3 | // Checks if the project is using a specific framework: 4 | // - if `framework.npmDependencies` is set, one of them must be present in the 5 | // `package.json` `dependencies|devDependencies` 6 | // - if `framework.excludedNpmDependencies` is set, none of them must be 7 | // present in the `package.json` `dependencies|devDependencies` 8 | // - if `framework.configFiles` is set, one of the files must exist 9 | export const usesFramework = async function ( 10 | { 11 | detect: { 12 | npmDependencies: frameworkNpmDependencies, 13 | excludedNpmDependencies: frameworkExcludedNpmDependencies, 14 | configFiles, 15 | }, 16 | }, 17 | { pathExists, npmDependencies }, 18 | ) { 19 | return ( 20 | usesNpmDependencies(frameworkNpmDependencies, npmDependencies) && 21 | lacksExcludedNpmDependencies(frameworkExcludedNpmDependencies, npmDependencies) && 22 | (await usesConfigFiles(configFiles, pathExists)) 23 | ) 24 | } 25 | 26 | const usesNpmDependencies = function (frameworkNpmDependencies, npmDependencies) { 27 | return ( 28 | frameworkNpmDependencies.length === 0 || 29 | frameworkNpmDependencies.some((frameworkNpmDependency) => npmDependencies.includes(frameworkNpmDependency)) 30 | ) 31 | } 32 | 33 | const lacksExcludedNpmDependencies = function (frameworkExcludedNpmDependencies, npmDependencies) { 34 | return frameworkExcludedNpmDependencies.every( 35 | (frameworkNpmDependency) => !npmDependencies.includes(frameworkNpmDependency), 36 | ) 37 | } 38 | 39 | const configExists = async (configFiles, pathExists) => { 40 | const exists = await pLocate(configFiles, (file) => pathExists(file)) 41 | return exists 42 | } 43 | 44 | const usesConfigFiles = async function (configFiles, pathExists) { 45 | return configFiles.length === 0 || (await configExists(configFiles, pathExists)) 46 | } 47 | -------------------------------------------------------------------------------- /src/dev.js: -------------------------------------------------------------------------------- 1 | // Retrieve framework's dev commands. 2 | // We use, in priority order: 3 | // - `package.json` `scripts` containing `framework.dev.command` 4 | // - `package.json` `scripts` whose names are among `NPM_DEV_SCRIPTS` 5 | // - `framework.dev.command` 6 | export const getDevCommands = function ({ frameworkDevCommand, scripts, runScriptCommand }) { 7 | if (frameworkDevCommand === undefined) { 8 | return [] 9 | } 10 | const scriptDevCommands = getScriptDevCommands(scripts, frameworkDevCommand).map( 11 | (scriptName) => `${runScriptCommand} ${scriptName}`, 12 | ) 13 | if (scriptDevCommands.length !== 0) { 14 | return scriptDevCommands 15 | } 16 | 17 | return [frameworkDevCommand] 18 | } 19 | 20 | const getScriptDevCommands = function (scripts, frameworkDevCommand) { 21 | const preferredScripts = getPreferredScripts(scripts, frameworkDevCommand) 22 | if (preferredScripts.length !== 0) { 23 | return preferredScripts 24 | } 25 | 26 | const devScripts = Object.keys(scripts).filter((script) => isNpmDevScript(script, scripts[script])) 27 | return devScripts.sort(scriptsSorter) 28 | } 29 | 30 | const getSortIndex = (index) => (index === -1 ? Number.MAX_SAFE_INTEGER : index) 31 | 32 | const scriptsSorter = (script1, script2) => { 33 | const index1 = NPM_DEV_SCRIPTS.findIndex((devScriptName) => matchesNpmWDevScript(script1, devScriptName)) 34 | const index2 = NPM_DEV_SCRIPTS.findIndex((devScriptName) => matchesNpmWDevScript(script2, devScriptName)) 35 | 36 | return getSortIndex(index1) - getSortIndex(index2) 37 | } 38 | 39 | const getPreferredScripts = function (scripts, frameworkDevCommand) { 40 | return Object.entries(scripts) 41 | .filter(([, scriptValue]) => scriptValue.includes(frameworkDevCommand)) 42 | .map((script) => getEntryKey(script)) 43 | .sort(scriptsSorter) 44 | } 45 | 46 | const getEntryKey = function ([key]) { 47 | return key 48 | } 49 | 50 | // Check if the npm script is likely to contain a dev command 51 | const isNpmDevScript = function (scriptName, scriptValue) { 52 | return NPM_DEV_SCRIPTS.some( 53 | (devScriptName) => matchesNpmWDevScript(scriptName, devScriptName) && !isExcludedScript(scriptValue), 54 | ) 55 | } 56 | 57 | // We also match script names like `docs:dev` 58 | const matchesNpmWDevScript = function (scriptName, devScriptName) { 59 | return scriptName === devScriptName || scriptName.endsWith(`:${devScriptName}`) 60 | } 61 | 62 | const NPM_DEV_SCRIPTS = ['dev', 'serve', 'develop', 'start', 'run', 'build', 'web'] 63 | 64 | const isExcludedScript = function (scriptValue) { 65 | return EXCLUDED_SCRIPTS.some((excluded) => scriptValue.includes(excluded)) 66 | } 67 | 68 | const EXCLUDED_SCRIPTS = ['netlify dev'] 69 | -------------------------------------------------------------------------------- /src/frameworks/angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "angular", 3 | "name": "Angular", 4 | "category": "frontend_framework", 5 | "detect": { 6 | "npmDependencies": ["@angular/cli"], 7 | "excludedNpmDependencies": [], 8 | "configFiles": ["angular.json"] 9 | }, 10 | "dev": { 11 | "command": "ng serve", 12 | "port": 4200, 13 | "pollingStrategies": [{ "name": "TCP" }, { "name": "HTTP" }] 14 | }, 15 | "build": { 16 | "command": "ng build --prod", 17 | "directory": "dist/" 18 | }, 19 | "logo": { 20 | "default": "/logos/angular/default.svg", 21 | "light": "/logos/angular/default.svg", 22 | "dark": "/logos/angular/default.svg" 23 | }, 24 | "env": {}, 25 | "plugins": [] 26 | } 27 | -------------------------------------------------------------------------------- /src/frameworks/assemble.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "assemble", 3 | "name": "Assemble", 4 | "category": "static_site_generator", 5 | "detect": { 6 | "npmDependencies": ["assemble"], 7 | "excludedNpmDependencies": [], 8 | "configFiles": [] 9 | }, 10 | "dev": {}, 11 | "build": { 12 | "command": "grunt build", 13 | "directory": "dist" 14 | }, 15 | "env": {}, 16 | "logo": { 17 | "default": "/logos/assemble/default.svg", 18 | "light": "/logos/assemble/default.svg", 19 | "dark": "/logos/assemble/default.svg" 20 | }, 21 | "plugins": [] 22 | } 23 | -------------------------------------------------------------------------------- /src/frameworks/astro.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "astro", 3 | "name": "Astro", 4 | "category": "static_site_generator", 5 | "detect": { 6 | "npmDependencies": ["astro"], 7 | "excludedNpmDependencies": [], 8 | "configFiles": ["astro.config.mjs"] 9 | }, 10 | "dev": { 11 | "command": "astro dev", 12 | "port": 3000, 13 | "pollingStrategies": [{ "name": "TCP" }, { "name": "HTTP" }] 14 | }, 15 | "build": { 16 | "command": "astro build", 17 | "directory": "dist" 18 | }, 19 | "staticAssetsDirectory": "public", 20 | "env": {}, 21 | "logo": { 22 | "default": "/logos/astro/light.svg", 23 | "light": "/logos/astro/light.svg", 24 | "dark": "/logos/astro/dark.svg" 25 | }, 26 | "plugins": [] 27 | } 28 | -------------------------------------------------------------------------------- /src/frameworks/blitz.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "blitz", 3 | "name": "Blitz.js", 4 | "category": "static_site_generator", 5 | "detect": { 6 | "npmDependencies": ["blitz"], 7 | "excludedNpmDependencies": [], 8 | "configFiles": [] 9 | }, 10 | "dev": { 11 | "command": "blitz dev", 12 | "port": 3000, 13 | "pollingStrategies": [{ "name": "TCP" }] 14 | }, 15 | "build": { 16 | "command": "blitz build", 17 | "directory": "out" 18 | }, 19 | "logo": { 20 | "default": "/logos/blitz/light.svg", 21 | "light": "/logos/blitz/light.svg", 22 | "dark": "/logos/blitz/dark.svg" 23 | }, 24 | "env": {}, 25 | "plugins": [] 26 | } 27 | -------------------------------------------------------------------------------- /src/frameworks/brunch.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "brunch", 3 | "name": "Brunch", 4 | "category": "build_tool", 5 | "detect": { 6 | "npmDependencies": ["brunch"], 7 | "excludedNpmDependencies": [], 8 | "configFiles": ["brunch-config.js"] 9 | }, 10 | "dev": { 11 | "command": "brunch watch --server", 12 | "port": 3333, 13 | "pollingStrategies": [{ "name": "TCP" }, { "name": "HTTP" }] 14 | }, 15 | "build": { 16 | "command": "brunch build", 17 | "directory": "public" 18 | }, 19 | "env": {}, 20 | "logo": { 21 | "default": "/logos/brunch/default.svg", 22 | "light": "/logos/brunch/default.svg", 23 | "dark": "/logos/brunch/default.svg" 24 | }, 25 | "plugins": [] 26 | } 27 | -------------------------------------------------------------------------------- /src/frameworks/cecil.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "cecil", 3 | "name": "Cecil", 4 | "category": "static_site_generator", 5 | "detect": { 6 | "npmDependencies": [], 7 | "excludedNpmDependencies": [], 8 | "configFiles": ["config.yml"] 9 | }, 10 | "dev": { 11 | "command": "cecil serve", 12 | "port": 8000, 13 | "pollingStrategies": [{ "name": "TCP" }, { "name": "HTTP" }] 14 | }, 15 | "build": { 16 | "command": "cecil build", 17 | "directory": "_site" 18 | }, 19 | "env": {}, 20 | "logo": { 21 | "default": "/logos/cecil/default.svg", 22 | "light": "/logos/cecil/default.svg", 23 | "dark": "/logos/cecil/default.svg" 24 | }, 25 | "plugins": [] 26 | } 27 | -------------------------------------------------------------------------------- /src/frameworks/create-react-app.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "create-react-app", 3 | "name": "Create React App", 4 | "category": "frontend_framework", 5 | "detect": { 6 | "npmDependencies": ["react-scripts"], 7 | "excludedNpmDependencies": [], 8 | "configFiles": [] 9 | }, 10 | "dev": { 11 | "command": "react-scripts start", 12 | "port": 3000, 13 | "pollingStrategies": [{ "name": "TCP" }, { "name": "HTTP" }] 14 | }, 15 | "build": { 16 | "command": "react-scripts build", 17 | "directory": "build" 18 | }, 19 | "staticAssetsDirectory": "public", 20 | "logo": { 21 | "default": "/logos/create-react-app/default.svg", 22 | "light": "/logos/create-react-app/default.svg", 23 | "dark": "/logos/create-react-app/default.svg" 24 | }, 25 | "env": { "BROWSER": "none", "PORT": "3000" }, 26 | "plugins": [] 27 | } 28 | -------------------------------------------------------------------------------- /src/frameworks/docpad.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "docpad", 3 | "name": "DocPad", 4 | "category": "static_site_generator", 5 | "detect": { 6 | "npmDependencies": ["docpad"], 7 | "excludedNpmDependencies": [], 8 | "configFiles": [] 9 | }, 10 | "dev": { 11 | "command": "docpad run", 12 | "port": 9778, 13 | "pollingStrategies": [{ "name": "TCP" }, { "name": "HTTP" }] 14 | }, 15 | "build": { 16 | "command": "docpad generate", 17 | "directory": "out" 18 | }, 19 | "env": {}, 20 | "plugins": [] 21 | } 22 | -------------------------------------------------------------------------------- /src/frameworks/docusaurus-v2.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "docusaurus-v2", 3 | "name": "Docusaurus 2", 4 | "category": "static_site_generator", 5 | "detect": { 6 | "npmDependencies": ["@docusaurus/core"], 7 | "excludedNpmDependencies": [], 8 | "configFiles": ["docusaurus.config.js"] 9 | }, 10 | "dev": { 11 | "command": "docusaurus start", 12 | "port": 3000, 13 | "pollingStrategies": [{ "name": "TCP" }, { "name": "HTTP" }] 14 | }, 15 | "build": { 16 | "command": "docusaurus build", 17 | "directory": "build" 18 | }, 19 | "staticAssetsDirectory": "static", 20 | "logo": { 21 | "default": "/logos/docusaurus/default.svg", 22 | "light": "/logos/docusaurus/default.svg", 23 | "dark": "/logos/docusaurus/default.svg" 24 | }, 25 | "env": { "BROWSER": "none" }, 26 | "plugins": [] 27 | } 28 | -------------------------------------------------------------------------------- /src/frameworks/docusaurus.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "docusaurus", 3 | "name": "Docusaurus", 4 | "category": "static_site_generator", 5 | "detect": { 6 | "npmDependencies": ["docusaurus"], 7 | "excludedNpmDependencies": [], 8 | "configFiles": ["siteConfig.js"] 9 | }, 10 | "dev": { 11 | "command": "docusaurus-start", 12 | "port": 3000, 13 | "pollingStrategies": [{ "name": "TCP" }, { "name": "HTTP" }] 14 | }, 15 | "build": { 16 | "command": "docusaurus-build", 17 | "directory": "build/" 18 | }, 19 | "staticAssetsDirectory": "static", 20 | "logo": { 21 | "default": "/logos/docusaurus/default.svg", 22 | "light": "/logos/docusaurus/default.svg", 23 | "dark": "/logos/docusaurus/default.svg" 24 | }, 25 | "env": { "BROWSER": "none" }, 26 | "plugins": [] 27 | } 28 | -------------------------------------------------------------------------------- /src/frameworks/eleventy.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "eleventy", 3 | "name": "Eleventy", 4 | "category": "static_site_generator", 5 | "detect": { 6 | "npmDependencies": ["@11ty/eleventy"], 7 | "excludedNpmDependencies": [], 8 | "configFiles": [".eleventy.js","eleventy.config.js","eleventy.config.cjs"] 9 | }, 10 | "dev": { 11 | "command": "eleventy --serve", 12 | "port": 8080, 13 | "pollingStrategies": [{ "name": "TCP" }, { "name": "HTTP" }] 14 | }, 15 | "build": { 16 | "command": "eleventy", 17 | "directory": "_site" 18 | }, 19 | "logo": { 20 | "default": "/logos/eleventy/default.svg", 21 | "light": "/logos/eleventy/default.svg", 22 | "dark": "/logos/eleventy/default.svg" 23 | }, 24 | "env": {}, 25 | "plugins": [] 26 | } 27 | -------------------------------------------------------------------------------- /src/frameworks/ember.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "ember", 3 | "name": "Ember.js", 4 | "category": "frontend_framework", 5 | "detect": { 6 | "npmDependencies": ["ember-cli"], 7 | "excludedNpmDependencies": [], 8 | "configFiles": ["ember-cli-build.js"] 9 | }, 10 | "dev": { 11 | "command": "ember serve", 12 | "port": 4200, 13 | "pollingStrategies": [{ "name": "TCP" }, { "name": "HTTP" }] 14 | }, 15 | "build": { 16 | "command": "ember build", 17 | "directory": "dist" 18 | }, 19 | "logo": { 20 | "default": "/logos/ember/default.svg", 21 | "light": "/logos/ember/light.svg", 22 | "dark": "/logos/ember/dark.svg" 23 | }, 24 | "env": {}, 25 | "plugins": [] 26 | } 27 | -------------------------------------------------------------------------------- /src/frameworks/expo.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "expo", 3 | "name": "Expo", 4 | "category": "frontend_framework", 5 | "detect": { 6 | "npmDependencies": ["expo"], 7 | "excludedNpmDependencies": [], 8 | "configFiles": ["app.json"] 9 | }, 10 | "dev": { 11 | "command": "expo start --web", 12 | "port": 19006, 13 | "pollingStrategies": [{ "name": "TCP" }, { "name": "HTTP" }] 14 | }, 15 | "build": { 16 | "command": "expo build:web", 17 | "directory": "web-build" 18 | }, 19 | "logo": { 20 | "default": "/logos/expo/default.svg", 21 | "light": "/logos/expo/light.svg", 22 | "dark": "/logos/expo/dark.svg" 23 | }, 24 | "env": {}, 25 | "plugins": [] 26 | } 27 | -------------------------------------------------------------------------------- /src/frameworks/gatsby.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "gatsby", 3 | "name": "Gatsby", 4 | "category": "static_site_generator", 5 | "detect": { 6 | "npmDependencies": ["gatsby"], 7 | "excludedNpmDependencies": [], 8 | "configFiles": ["gatsby-config.js"] 9 | }, 10 | "dev": { 11 | "command": "gatsby develop", 12 | "port": 8000, 13 | "pollingStrategies": [{ "name": "TCP" }, { "name": "HTTP" }] 14 | }, 15 | "build": { 16 | "command": "gatsby build", 17 | "directory": "public" 18 | }, 19 | "staticAssetsDirectory": "static", 20 | "env": { 21 | "GATSBY_LOGGER": "yurnalist", 22 | "GATSBY_PRECOMPILE_DEVELOP_FUNCTIONS": "true", 23 | "AWS_LAMBDA_JS_RUNTIME": "nodejs14.x", 24 | "NODE_VERSION": "14" 25 | }, 26 | "logo": { 27 | "default": "/logos/gatsby/default.svg", 28 | "light": "/logos/gatsby/light.svg", 29 | "dark": "/logos/gatsby/dark.svg" 30 | }, 31 | "plugins": [ 32 | { 33 | "packageName": "@netlify/plugin-gatsby", 34 | "condition": { "minNodeVersion": "12.13.0" } 35 | } 36 | ] 37 | } 38 | -------------------------------------------------------------------------------- /src/frameworks/gridsome.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "gridsome", 3 | "name": "Gridsome", 4 | "category": "static_site_generator", 5 | "detect": { 6 | "npmDependencies": ["gridsome"], 7 | "excludedNpmDependencies": [], 8 | "configFiles": ["gridsome.config.js"] 9 | }, 10 | "dev": { 11 | "command": "gridsome develop", 12 | "port": 8080, 13 | "pollingStrategies": [{ "name": "TCP" }, { "name": "HTTP" }] 14 | }, 15 | "build": { 16 | "command": "gridsome build", 17 | "directory": "dist" 18 | }, 19 | "logo": { 20 | "default": "/logos/gridsome/default.svg", 21 | "light": "/logos/gridsome/light.svg", 22 | "dark": "/logos/gridsome/dark.svg" 23 | }, 24 | "env": {}, 25 | "plugins": [] 26 | } 27 | -------------------------------------------------------------------------------- /src/frameworks/grunt.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "grunt", 3 | "name": "Grunt", 4 | "category": "build_tool", 5 | "detect": { 6 | "npmDependencies": ["grunt"], 7 | "excludedNpmDependencies": [], 8 | "configFiles": [] 9 | }, 10 | "dev": {}, 11 | "build": { 12 | "command": "grunt build", 13 | "directory": "dist" 14 | }, 15 | "logo": { 16 | "default": "/logos/grunt/default.svg", 17 | "light": "/logos/grunt/default.svg", 18 | "dark": "/logos/grunt/default.svg" 19 | }, 20 | "env": {}, 21 | "plugins": [] 22 | } 23 | -------------------------------------------------------------------------------- /src/frameworks/gulp.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "gulp", 3 | "name": "gulp.js", 4 | "category": "build_tool", 5 | "detect": { 6 | "npmDependencies": ["gulp"], 7 | "excludedNpmDependencies": [], 8 | "configFiles": [] 9 | }, 10 | "dev": {}, 11 | "build": { 12 | "command": "gulp build", 13 | "directory": "dist" 14 | }, 15 | "logo": { 16 | "default": "/logos/gulp/default.svg", 17 | "light": "/logos/gulp/default.svg", 18 | "dark": "/logos/gulp/default.svg" 19 | }, 20 | "env": {}, 21 | "plugins": [] 22 | } 23 | -------------------------------------------------------------------------------- /src/frameworks/harp.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "harp", 3 | "name": "Harp", 4 | "category": "static_site_generator", 5 | "detect": { 6 | "npmDependencies": ["harp"], 7 | "excludedNpmDependencies": [], 8 | "configFiles": [] 9 | }, 10 | "dev": { 11 | "command": "harp server", 12 | "port": 9000, 13 | "pollingStrategies": [{ "name": "TCP" }, { "name": "HTTP" }] 14 | }, 15 | "build": { 16 | "command": "harp compile", 17 | "directory": "www" 18 | }, 19 | "logo": { 20 | "default": "/logos/harp/default.svg", 21 | "light": "/logos/harp/light.svg", 22 | "dark": "/logos/harp/default.svg" 23 | }, 24 | "env": {}, 25 | "plugins": [] 26 | } 27 | -------------------------------------------------------------------------------- /src/frameworks/hexo.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "hexo", 3 | "name": "Hexo", 4 | "category": "static_site_generator", 5 | "detect": { 6 | "npmDependencies": ["hexo"], 7 | "excludedNpmDependencies": [], 8 | "configFiles": ["_config.yml"] 9 | }, 10 | "dev": { 11 | "command": "hexo server", 12 | "port": 4000, 13 | "pollingStrategies": [{ "name": "TCP" }, { "name": "HTTP" }] 14 | }, 15 | "build": { 16 | "command": "hexo generate", 17 | "directory": "public" 18 | }, 19 | "logo": { 20 | "default": "/logos/hexo/default.svg", 21 | "light": "/logos/hexo/light.svg", 22 | "dark": "/logos/hexo/dark.svg" 23 | }, 24 | "env": {}, 25 | "plugins": [] 26 | } 27 | -------------------------------------------------------------------------------- /src/frameworks/hugo.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "hugo", 3 | "name": "Hugo", 4 | "category": "static_site_generator", 5 | "detect": { 6 | "npmDependencies": [], 7 | "excludedNpmDependencies": [], 8 | "configFiles": ["config.toml", "config.yaml"] 9 | }, 10 | "dev": { 11 | "command": "hugo server -w", 12 | "port": 1313, 13 | "pollingStrategies": [{ "name": "TCP" }, { "name": "HTTP" }] 14 | }, 15 | "build": { 16 | "command": "hugo", 17 | "directory": "public" 18 | }, 19 | "logo": { 20 | "default": "/logos/hugo/default.svg", 21 | "light": "/logos/hugo/default.svg", 22 | "dark": "/logos/hugo/default.svg" 23 | }, 24 | "env": {}, 25 | "plugins": [] 26 | } 27 | -------------------------------------------------------------------------------- /src/frameworks/hydrogen.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "hydrogen", 3 | "name": "Hydrogen", 4 | "category": "static_site_generator", 5 | "detect": { 6 | "npmDependencies": ["@shopify/hydrogen"], 7 | "excludedNpmDependencies": [], 8 | "configFiles": [] 9 | }, 10 | "dev": { 11 | "command": "vite", 12 | "port": 3000, 13 | "pollingStrategies": [{ "name": "TCP" }] 14 | }, 15 | "build": { 16 | "command": "npm run build", 17 | "directory": "dist/client" 18 | }, 19 | "logo": { 20 | "default": "/logos/hydrogen/default.svg", 21 | "light": "/logos/hydrogen/default.svg", 22 | "dark": "/logos/hydrogen/default.svg" 23 | }, 24 | "env": {}, 25 | "plugins": [] 26 | } 27 | -------------------------------------------------------------------------------- /src/frameworks/jekyll.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "jekyll", 3 | "name": "Jekyll", 4 | "category": "static_site_generator", 5 | "detect": { 6 | "npmDependencies": [], 7 | "excludedNpmDependencies": [], 8 | "configFiles": ["_config.yml", "_config.yaml", "_config.toml"] 9 | }, 10 | "dev": { 11 | "command": "bundle exec jekyll serve -w", 12 | "port": 4000, 13 | "pollingStrategies": [{ "name": "TCP" }, { "name": "HTTP" }] 14 | }, 15 | "build": { 16 | "command": "bundle exec jekyll build", 17 | "directory": "_site" 18 | }, 19 | "logo": { 20 | "default": "/logos/jekyll/dark.svg", 21 | "light": "/logos/jekyll/light.svg", 22 | "dark": "/logos/jekyll/dark.svg" 23 | }, 24 | "env": {}, 25 | "plugins": [] 26 | } 27 | -------------------------------------------------------------------------------- /src/frameworks/main.js: -------------------------------------------------------------------------------- 1 | // We purposely order the following array to ensure the most relevant framework 2 | // is always first, if several frameworks are detected at once. 3 | // Therefore, we cannot use `fs.readdir()`. 4 | export const FRAMEWORK_NAMES = [ 5 | // Static site generators 6 | 'astro', 7 | 'docusaurus', 8 | 'docusaurus-v2', 9 | 'eleventy', 10 | 'gatsby', 11 | 'gridsome', 12 | 'hexo', 13 | 'hugo', 14 | 'hydrogen', 15 | 'jekyll', 16 | 'middleman', 17 | 'next-nx', 18 | 'next', 19 | 'blitz', 20 | 'nuxt', 21 | 'nuxt3', 22 | 'phenomic', 23 | 'qwik', 24 | 'react-static', 25 | 'redwoodjs', 26 | 'remix', 27 | 'solid-js', 28 | 'solid-start', 29 | 'stencil', 30 | 'vuepress', 31 | 'assemble', 32 | 'docpad', 33 | 'harp', 34 | 'metalsmith', 35 | 'roots', 36 | 'wintersmith', 37 | 'cecil', 38 | 'zola', 39 | 40 | // Front-end frameworks 41 | 'angular', 42 | 'create-react-app', 43 | 'ember', 44 | 'expo', 45 | 'quasar', 46 | 'quasar-v0.17', 47 | 'sapper', 48 | 'svelte', 49 | 'svelte-kit', 50 | 'vue', 51 | 52 | // Build tools 53 | 'brunch', 54 | 'parcel', 55 | 'grunt', 56 | 'gulp', 57 | 'vite', 58 | 'wmr', 59 | ] 60 | -------------------------------------------------------------------------------- /src/frameworks/metalsmith.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "metalsmith", 3 | "name": "Metalsmith", 4 | "category": "static_site_generator", 5 | "detect": { 6 | "npmDependencies": ["metalsmith"], 7 | "excludedNpmDependencies": [], 8 | "configFiles": [] 9 | }, 10 | "dev": {}, 11 | "build": { 12 | "command": "metalsmith", 13 | "directory": "build" 14 | }, 15 | "logo": { 16 | "default": "/logos/metalsmith/default.svg", 17 | "light": "/logos/metalsmith/default.svg", 18 | "dark": "/logos/metalsmith/default.svg" 19 | }, 20 | "env": {}, 21 | "plugins": [] 22 | } 23 | -------------------------------------------------------------------------------- /src/frameworks/middleman.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "middleman", 3 | "name": "Middleman", 4 | "category": "static_site_generator", 5 | "detect": { 6 | "npmDependencies": [], 7 | "excludedNpmDependencies": [], 8 | "configFiles": ["config.rb"] 9 | }, 10 | "dev": { 11 | "command": "bundle exec middleman server", 12 | "port": 4567, 13 | "pollingStrategies": [{ "name": "TCP" }, { "name": "HTTP" }] 14 | }, 15 | "build": { 16 | "command": "bundle exec middleman build", 17 | "directory": "build" 18 | }, 19 | "logo": { 20 | "default": "/logos/middleman/default.svg", 21 | "light": "/logos/middleman/default.svg", 22 | "dark": "/logos/middleman/default.svg" 23 | }, 24 | "env": {}, 25 | "plugins": [] 26 | } 27 | -------------------------------------------------------------------------------- /src/frameworks/next-nx.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "next-nx", 3 | "name": "Next.js with Nx", 4 | "category": "static_site_generator", 5 | "detect": { 6 | "npmDependencies": ["@nrwl/next"], 7 | "excludedNpmDependencies": [], 8 | "configFiles": [] 9 | }, 10 | "dev": { 11 | "command": "nx serve", 12 | "port": 4200, 13 | "pollingStrategies": [{ "name": "TCP" }, { "name": "HTTP" }] 14 | }, 15 | "build": { 16 | "command": "nx build", 17 | "directory": "dist/apps//.next" 18 | }, 19 | "env": {}, 20 | "plugins": [ 21 | { 22 | "packageName": "@netlify/plugin-nextjs", 23 | "condition": { "minNodeVersion": "10.13.0" } 24 | } 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /src/frameworks/next.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "next", 3 | "name": "Next.js", 4 | "category": "static_site_generator", 5 | "detect": { 6 | "npmDependencies": ["next"], 7 | "excludedNpmDependencies": ["@nrwl/next"], 8 | "configFiles": [] 9 | }, 10 | "dev": { 11 | "command": "next", 12 | "port": 3000, 13 | "pollingStrategies": [{ "name": "TCP" }] 14 | }, 15 | "build": { 16 | "command": "next build", 17 | "directory": ".next" 18 | }, 19 | "logo": { 20 | "default":"/logos/nextjs/light.svg", 21 | "light":"/logos/nextjs/light.svg", 22 | "dark":"/logos/nextjs/dark.svg" 23 | }, 24 | "env": {}, 25 | "plugins": [ 26 | { 27 | "packageName": "@netlify/plugin-nextjs", 28 | "condition": { "minNodeVersion": "10.13.0" } 29 | } 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /src/frameworks/nuxt.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "nuxt", 3 | "name": "Nuxt 2", 4 | "category": "static_site_generator", 5 | "detect": { 6 | "npmDependencies": ["nuxt", "nuxt-edge"], 7 | "excludedNpmDependencies": [], 8 | "configFiles": [] 9 | }, 10 | "dev": { 11 | "command": "nuxt", 12 | "port": 3000, 13 | "pollingStrategies": [{ "name": "TCP" }, { "name": "HTTP" }] 14 | }, 15 | "build": { 16 | "command": "nuxt generate", 17 | "directory": "dist" 18 | }, 19 | "logo": { 20 | "default": "/logos/nuxt/default.svg", 21 | "light": "/logos/nuxt/light.svg", 22 | "dark": "/logos/nuxt/dark.svg" 23 | }, 24 | "env": {}, 25 | "plugins": [] 26 | } 27 | -------------------------------------------------------------------------------- /src/frameworks/nuxt3.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "nuxt3", 3 | "name": "Nuxt 3", 4 | "category": "static_site_generator", 5 | "detect": { 6 | "npmDependencies": ["nuxt3"], 7 | "excludedNpmDependencies": [], 8 | "configFiles": [] 9 | }, 10 | "dev": { 11 | "command": "npm run dev", 12 | "port": 3000, 13 | "pollingStrategies": [{ "name": "TCP" }, { "name": "HTTP" }] 14 | }, 15 | "build": { 16 | "command": "npm run build", 17 | "directory": "dist" 18 | }, 19 | "env": { 20 | "AWS_LAMBDA_JS_RUNTIME": "nodejs14.x", 21 | "NODE_VERSION": "14" 22 | }, 23 | "logo": { 24 | "default": "/logos/nuxt/default.svg", 25 | "light": "/logos/nuxt/light.svg", 26 | "dark": "/logos/nuxt/dark.svg" 27 | }, 28 | "plugins": [] 29 | } 30 | -------------------------------------------------------------------------------- /src/frameworks/parcel.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "parcel", 3 | "name": "Parcel", 4 | "category": "build_tool", 5 | "detect": { 6 | "npmDependencies": ["parcel-bundler", "parcel"], 7 | "excludedNpmDependencies": [], 8 | "configFiles": [] 9 | }, 10 | "dev": { 11 | "command": "parcel", 12 | "port": 1234, 13 | "pollingStrategies": [{ "name": "TCP" }, { "name": "HTTP" }] 14 | }, 15 | "build": { 16 | "command": "parcel build", 17 | "directory": "dist" 18 | }, 19 | "logo": { 20 | "default": "/logos/parcel/default.svg", 21 | "light": "/logos/parcel/default.svg", 22 | "dark": "/logos/parcel/default.svg" 23 | }, 24 | "env": {}, 25 | "plugins": [] 26 | } 27 | -------------------------------------------------------------------------------- /src/frameworks/phenomic.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "phenomic", 3 | "name": "Phenomic", 4 | "category": "static_site_generator", 5 | "detect": { 6 | "npmDependencies": ["@phenomic/core"], 7 | "excludedNpmDependencies": [], 8 | "configFiles": [] 9 | }, 10 | "dev": { 11 | "command": "phenomic start", 12 | "port": 3333, 13 | "pollingStrategies": [{ "name": "TCP" }, { "name": "HTTP" }] 14 | }, 15 | "build": { 16 | "command": "phenomic build", 17 | "directory": "public" 18 | }, 19 | "logo": { 20 | "default": "/logos/phenomic/default.svg", 21 | "light": "/logos/phenomic/default.svg", 22 | "dark": "/logos/phenomic/default.svg" 23 | }, 24 | "env": {}, 25 | "plugins": [] 26 | } 27 | -------------------------------------------------------------------------------- /src/frameworks/quasar-v0.17.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "quasar-v0.17", 3 | "name": "Quasar", 4 | "category": "frontend_framework", 5 | "detect": { 6 | "npmDependencies": ["quasar-cli"], 7 | "excludedNpmDependencies": [], 8 | "configFiles": [] 9 | }, 10 | "dev": { 11 | "command": "quasar dev -p 8080", 12 | "port": 8080, 13 | "pollingStrategies": [{ "name": "TCP" }, { "name": "HTTP" }] 14 | }, 15 | "build": { 16 | "command": "quasar build", 17 | "directory": ".quasar" 18 | }, 19 | "logo": { 20 | "default": "/logos/quasar/default.svg", 21 | "light": "/logos/quasar/default.svg", 22 | "dark": "/logos/quasar/default.svg" 23 | }, 24 | "env": {}, 25 | "plugins": [] 26 | } 27 | -------------------------------------------------------------------------------- /src/frameworks/quasar.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "quasar", 3 | "name": "Quasar", 4 | "category": "frontend_framework", 5 | "detect": { 6 | "npmDependencies": ["@quasar/app"], 7 | "excludedNpmDependencies": [], 8 | "configFiles": [] 9 | }, 10 | "dev": { 11 | "command": "quasar dev -p 8081", 12 | "port": 8081, 13 | "pollingStrategies": [{ "name": "TCP" }, { "name": "HTTP" }] 14 | }, 15 | "build": { 16 | "command": "quasar build", 17 | "directory": "dist/spa" 18 | }, 19 | "logo": { 20 | "default": "/logos/quasar/default.svg", 21 | "light": "/logos/quasar/default.svg", 22 | "dark": "/logos/quasar/default.svg" 23 | }, 24 | "env": {}, 25 | "plugins": [] 26 | } 27 | -------------------------------------------------------------------------------- /src/frameworks/qwik.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "qwik", 3 | "name": "Qwik", 4 | "category": "static_site_generator", 5 | "detect": { 6 | "npmDependencies": ["@builder.io/qwik"], 7 | "excludedNpmDependencies": [], 8 | "configFiles": [] 9 | }, 10 | "dev": { 11 | "command": "vite", 12 | "port": 5173, 13 | "pollingStrategies": [{ "name": "TCP" }] 14 | }, 15 | "build": { 16 | "command": "npm run build", 17 | "directory": "dist" 18 | }, 19 | "logo": { 20 | "default": "/logos/qwik/default.svg", 21 | "light": "/logos/qwik/default.svg", 22 | "dark": "/logos/qwik/default.svg" 23 | }, 24 | "env": {}, 25 | "plugins": [] 26 | } 27 | -------------------------------------------------------------------------------- /src/frameworks/react-static.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "react-static", 3 | "name": "React Static", 4 | "category": "static_site_generator", 5 | "detect": { 6 | "npmDependencies": ["react-static"], 7 | "excludedNpmDependencies": [], 8 | "configFiles": ["static.config.js"] 9 | }, 10 | "dev": { 11 | "command": "react-static start", 12 | "port": 3000, 13 | "pollingStrategies": [{ "name": "TCP" }, { "name": "HTTP" }] 14 | }, 15 | "build": { 16 | "command": "react-static build", 17 | "directory": "dist" 18 | }, 19 | "logo": { 20 | "default": "/logos/react-static/default.png", 21 | "light": "/logos/react-static/default.png", 22 | "dark": "/logos/react-static/default.png" 23 | }, 24 | "env": {}, 25 | "plugins": [] 26 | } 27 | -------------------------------------------------------------------------------- /src/frameworks/redwoodjs.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "redwoodjs", 3 | "name": "RedwoodJS", 4 | "category": "static_site_generator", 5 | "detect": { 6 | "npmDependencies": [ 7 | "@redwoodjs/core" 8 | ], 9 | "excludedNpmDependencies": [], 10 | "configFiles": [ 11 | "redwood.toml" 12 | ] 13 | }, 14 | "dev": { 15 | "command": "yarn rw dev", 16 | "port": 8910, 17 | "pollingStrategies": [ 18 | { 19 | "name": "TCP" 20 | } 21 | ] 22 | }, 23 | "build": { 24 | "command": "rw deploy netlify", 25 | "directory": "web/dist" 26 | }, 27 | "staticAssetsDirectory": "public", 28 | "logo": { 29 | "default": "/logos/redwoodjs/default.svg", 30 | "light": "/logos/redwoodjs/default.svg", 31 | "dark": "/logos/redwoodjs/default.svg" 32 | }, 33 | "env": {}, 34 | "plugins": [] 35 | } 36 | -------------------------------------------------------------------------------- /src/frameworks/remix.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "remix", 3 | "name": "Remix", 4 | "category": "static_site_generator", 5 | "detect": { 6 | "npmDependencies": ["remix", "@remix-run/netlify", "@remix-run/netlify-edge"], 7 | "excludedNpmDependencies": [], 8 | "configFiles": ["remix.config.js"] 9 | }, 10 | "dev": { 11 | "command": "remix watch" 12 | }, 13 | "build": { 14 | "command": "remix build", 15 | "directory": "public" 16 | }, 17 | "logo": { 18 | "default": "/logos/remix/default.svg", 19 | "light": "/logos/remix/light.svg", 20 | "dark": "/logos/remix/dark.svg" 21 | }, 22 | "env": {}, 23 | "plugins": [] 24 | } 25 | -------------------------------------------------------------------------------- /src/frameworks/roots.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "roots", 3 | "name": "Roots", 4 | "category": "static_site_generator", 5 | "detect": { 6 | "npmDependencies": ["roots"], 7 | "excludedNpmDependencies": [], 8 | "configFiles": [] 9 | }, 10 | "dev": { 11 | "command": "roots watch", 12 | "port": 1111, 13 | "pollingStrategies": [{ "name": "TCP" }, { "name": "HTTP" }] 14 | }, 15 | "build": { 16 | "command": "roots compile", 17 | "directory": "public" 18 | }, 19 | "logo": { 20 | "default": "/logos/roots/default.svg", 21 | "light": "/logos/roots/default.svg", 22 | "dark": "/logos/roots/default.svg" 23 | }, 24 | "env": {}, 25 | "plugins": [] 26 | } 27 | -------------------------------------------------------------------------------- /src/frameworks/sapper.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "sapper", 3 | "name": "Sapper", 4 | "category": "frontend_framework", 5 | "detect": { 6 | "npmDependencies": ["sapper"], 7 | "excludedNpmDependencies": [], 8 | "configFiles": [] 9 | }, 10 | "dev": { 11 | "command": "sapper dev", 12 | "port": 3000, 13 | "pollingStrategies": [{ "name": "TCP" }, { "name": "HTTP" }] 14 | }, 15 | "build": { 16 | "command": "sapper export", 17 | "directory": "__sapper__/export" 18 | }, 19 | "logo": { 20 | "default": "/logos/sapper/default.svg", 21 | "light": "/logos/sapper/default.svg", 22 | "dark": "/logos/sapper/default.svg" 23 | }, 24 | "staticAssetsDirectory": "static", 25 | "env": {}, 26 | "plugins": [] 27 | } 28 | -------------------------------------------------------------------------------- /src/frameworks/solid-js.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "solid-js", 3 | "name": "SolidJS", 4 | "category": "static_site_generator", 5 | "detect": { 6 | "npmDependencies": ["solid-js"], 7 | "excludedNpmDependencies": ["solid-start"], 8 | "configFiles": [] 9 | }, 10 | "dev": { 11 | "command": "npm run dev", 12 | "port": 3000, 13 | "pollingStrategies": [{ "name": "TCP" }] 14 | }, 15 | "build": { 16 | "command": "npm run build", 17 | "directory": "netlify" 18 | }, 19 | "logo": { 20 | "default": "/logos/solid-js/default.svg", 21 | "light": "/logos/solid-js/default.svg", 22 | "dark": "/logos/solid-js/dark.svg" 23 | }, 24 | "env": {}, 25 | "plugins": [] 26 | } 27 | -------------------------------------------------------------------------------- /src/frameworks/solid-start.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "solid-start", 3 | "name": "Solid Start", 4 | "category": "static_site_generator", 5 | "detect": { 6 | "npmDependencies": ["solid-start"], 7 | "excludedNpmDependencies": [], 8 | "configFiles": [] 9 | }, 10 | "dev": { 11 | "command": "solid-start dev", 12 | "port": 3000, 13 | "pollingStrategies": [{ "name": "TCP" }] 14 | }, 15 | "build": { 16 | "command": "solid-start build", 17 | "directory": "netlify" 18 | }, 19 | "logo": { 20 | "default": "/logos/solid-start/default.svg", 21 | "light": "/logos/solid-start/default.svg", 22 | "dark": "/logos/solid-start/default.svg" 23 | }, 24 | "env": {}, 25 | "plugins": [] 26 | } 27 | -------------------------------------------------------------------------------- /src/frameworks/stencil.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "stencil", 3 | "name": "Stencil", 4 | "category": "static_site_generator", 5 | "detect": { 6 | "npmDependencies": ["@stencil/core"], 7 | "excludedNpmDependencies": [], 8 | "configFiles": ["stencil.config.ts"] 9 | }, 10 | "dev": { 11 | "command": "stencil build --dev --watch --serve", 12 | "port": 3333, 13 | "pollingStrategies": [{ "name": "TCP" }, { "name": "HTTP" }] 14 | }, 15 | "build": { 16 | "command": "stencil build", 17 | "directory": "www" 18 | }, 19 | "logo": { 20 | "default": "/logos/stencil/light.svg", 21 | "light": "/logos/stencil/light.svg", 22 | "dark": "/logos/stencil/dark.svg" 23 | }, 24 | "env": { "BROWSER": "none", "PORT": "3000" }, 25 | "plugins": [] 26 | } 27 | -------------------------------------------------------------------------------- /src/frameworks/svelte-kit.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "svelte-kit", 3 | "name": "SvelteKit", 4 | "category": "frontend_framework", 5 | "detect": { 6 | "npmDependencies": ["@sveltejs/kit"], 7 | "excludedNpmDependencies": [], 8 | "configFiles": [] 9 | }, 10 | "dev": { 11 | "command": "vite dev", 12 | "port": 5173, 13 | "pollingStrategies": [{ "name": "TCP" }, { "name": "HTTP" }] 14 | }, 15 | "build": { 16 | "command": "vite build", 17 | "directory": ".svelte-kit" 18 | }, 19 | "logo": { 20 | "default": "/logos/svelte-kit/default.svg", 21 | "light": "/logos/svelte-kit/default.svg", 22 | "dark": "/logos/svelte-kit/default.svg" 23 | }, 24 | "staticAssetsDirectory": "static", 25 | "env": {}, 26 | "plugins": [] 27 | } 28 | -------------------------------------------------------------------------------- /src/frameworks/svelte.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "svelte", 3 | "name": "Svelte", 4 | "category": "frontend_framework", 5 | "detect": { 6 | "npmDependencies": ["svelte"], 7 | "excludedNpmDependencies": ["sapper", "@sveltejs/kit"], 8 | "configFiles": [] 9 | }, 10 | "dev": { 11 | "command": "npm run dev", 12 | "port": 5000, 13 | "pollingStrategies": [{ "name": "TCP" }, { "name": "HTTP" }] 14 | }, 15 | "build": { 16 | "command": "npm run build", 17 | "directory": "public" 18 | }, 19 | "logo": { 20 | "default": "/logos/svelte-kit/default.svg", 21 | "light": "/logos/svelte-kit/default.svg", 22 | "dark": "/logos/svelte-kit/default.svg" 23 | }, 24 | "env": {}, 25 | "plugins": [] 26 | } 27 | -------------------------------------------------------------------------------- /src/frameworks/vite.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "vite", 3 | "name": "Vite", 4 | "category": "build_tool", 5 | "detect": { 6 | "npmDependencies": ["vite"], 7 | "excludedNpmDependencies": ["@shopify/hydrogen", "@builder.io/qwik", "solid-start", "solid-js", "@sveltejs/kit"], 8 | "configFiles": [] 9 | }, 10 | "dev": { 11 | "command": "vite", 12 | "port": 5173, 13 | "pollingStrategies": [{ "name": "TCP" }] 14 | }, 15 | "build": { 16 | "command": "vite build", 17 | "directory": "dist" 18 | }, 19 | "logo": { 20 | "default": "/logos/vite/default.svg", 21 | "light": "/logos/vite/default.svg", 22 | "dark": "/logos/vite/default.svg" 23 | }, 24 | "env": {}, 25 | "plugins": [] 26 | } 27 | -------------------------------------------------------------------------------- /src/frameworks/vue.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "vue", 3 | "name": "Vue.js", 4 | "category": "frontend_framework", 5 | "detect": { 6 | "npmDependencies": ["@vue/cli-service"], 7 | "excludedNpmDependencies": [], 8 | "configFiles": [] 9 | }, 10 | "dev": { 11 | "command": "vue-cli-service serve", 12 | "port": 8080, 13 | "pollingStrategies": [{ "name": "TCP" }, { "name": "HTTP" }] 14 | }, 15 | "build": { 16 | "command": "vue-cli-service build", 17 | "directory": "dist" 18 | }, 19 | "logo": { 20 | "default": "/logos/vue/default.svg", 21 | "light": "/logos/vue/default.svg", 22 | "dark": "/logos/vue/default.svg" 23 | }, 24 | "env": {}, 25 | "plugins": [] 26 | } 27 | -------------------------------------------------------------------------------- /src/frameworks/vuepress.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "vuepress", 3 | "name": "VuePress", 4 | "category": "static_site_generator", 5 | "detect": { 6 | "npmDependencies": ["vuepress"], 7 | "excludedNpmDependencies": [], 8 | "configFiles": [] 9 | }, 10 | "dev": { 11 | "command": "vuepress dev", 12 | "port": 8080, 13 | "pollingStrategies": [{ "name": "TCP" }, { "name": "HTTP" }] 14 | }, 15 | "build": { 16 | "command": "vuepress build", 17 | "directory": ".vuepress/dist" 18 | }, 19 | "logo": { 20 | "default": "/logos/vuepress/default.svg", 21 | "light": "/logos/vuepress/default.svg", 22 | "dark": "/logos/vuepress/default.svg" 23 | }, 24 | "env": {}, 25 | "plugins": [] 26 | } 27 | -------------------------------------------------------------------------------- /src/frameworks/wintersmith.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "wintersmith", 3 | "name": "Wintersmith", 4 | "category": "static_site_generator", 5 | "detect": { 6 | "npmDependencies": ["wintersmith"], 7 | "excludedNpmDependencies": [], 8 | "configFiles": ["config.json"] 9 | }, 10 | "dev": { 11 | "command": "wintersmith preview", 12 | "port": 8080, 13 | "pollingStrategies": [{ "name": "TCP" }, { "name": "HTTP" }] 14 | }, 15 | "build": { 16 | "command": "wintersmith build", 17 | "directory": "build" 18 | }, 19 | "logo": { 20 | "default": "/logos/wintersmith/default.svg", 21 | "light": "/logos/wintersmith/default.svg", 22 | "dark": "/logos/wintersmith/default.svg" 23 | }, 24 | "env": {}, 25 | "plugins": [] 26 | } 27 | -------------------------------------------------------------------------------- /src/frameworks/wmr.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "wmr", 3 | "name": "WMR", 4 | "category": "build_tool", 5 | "detect": { 6 | "npmDependencies": ["wmr"], 7 | "excludedNpmDependencies": [], 8 | "configFiles": [] 9 | }, 10 | "dev": { 11 | "command": "wmr", 12 | "port": 8080, 13 | "pollingStrategies": [{ "name": "TCP" }, { "name": "HTTP" }] 14 | }, 15 | "build": { 16 | "command": "wmr build", 17 | "directory": "dist" 18 | }, 19 | "logo": { 20 | "default": "/logos/wmr/default.svg", 21 | "light": "/logos/wmr/default.svg", 22 | "dark": "/logos/wmr/default.svg" 23 | }, 24 | "env": {}, 25 | "plugins": [] 26 | } 27 | -------------------------------------------------------------------------------- /src/frameworks/zola.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "zola", 3 | "name": "Zola", 4 | "category": "static_site_generator", 5 | "detect": { 6 | "npmDependencies": [], 7 | "excludedNpmDependencies": [], 8 | "configFiles": ["config.toml"] 9 | }, 10 | "dev": { 11 | "command": "zola serve", 12 | "port": 1111, 13 | "pollingStrategies": [{ "name": "TCP" }, { "name": "HTTP" }] 14 | }, 15 | "build": { 16 | "command": "zola build", 17 | "directory": "public" 18 | }, 19 | "env": {}, 20 | "plugins": [] 21 | } 22 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import { join } from 'path' 2 | import { cwd } from 'process' 3 | 4 | import { findUp } from 'find-up' 5 | 6 | import { getContext, getPackageJson } from './context.js' 7 | import { listFrameworks as list, hasFramework as has, getFramework as get } from './core.js' 8 | 9 | /** 10 | * @typedef {object} Options 11 | * @property {string} [projectDir=process.cwd()] - Project's directory 12 | * @property {string} [nodeVersion=process.version] - Node.js version of the runtime environment. Used to recommend Netlify build plugins 13 | */ 14 | 15 | /** 16 | * @typedef {object} Dev 17 | * @property {string} commands - Dev command. There might be several alternatives or empty 18 | * @property {number} port - Server port 19 | */ 20 | 21 | /** 22 | * @typedef {object} Build 23 | * @property {string} commands - Build command. There might be several alternatives 24 | * @property {string} directory - Relative path to the directory where files are built 25 | */ 26 | 27 | /** 28 | * @typedef {object} FrameworkPackage 29 | * @property {string} name - The name of the package. e.g: 'gatsby' 30 | * @property {string} version - The version of the installed package as found in the package.json. Is set to 'unknown' by default. 31 | */ 32 | 33 | /** 34 | * @typedef {object} Framework 35 | * @property {string} id - Id such as `"gatsby"` 36 | * @property {string} category - Category among `"static_site_generator"`, `"frontend_framework"` and `"build_tool"` 37 | * @property {FrameworkPackage} package - Information about the framework's underlying package 38 | * @property {Dev} dev - Information about the dev command 39 | * @property {Build} build - Information about the build command 40 | * @property {object} env - Environment variables that should be set when calling the dev command 41 | * @property {string[]} plugins - A list of recommend Netlify build plugins to install for the framework 42 | */ 43 | 44 | /** 45 | * Gets the version of the framework that is installed in a project. 46 | * 47 | * This cannot currently be used in the browser at this time, which is why it's defined 48 | * here rather than in `core.js` as part of the `getFrameworkInfo` method 49 | * 50 | * @param {string} projectDir - Project directory 51 | * @param {Framework} frameworkInfo - Information about the framework as detected by `getFrameworkInfo` 52 | * 53 | * @returns {Promise} 54 | */ 55 | const getFrameworkVersion = async (projectDir, frameworkInfo) => { 56 | if (!frameworkInfo.package || !frameworkInfo.package.name) { 57 | return frameworkInfo 58 | } 59 | 60 | const npmPackage = frameworkInfo.package.name 61 | 62 | // Get path of package.json for the installed framework. We need to traverse up the directories 63 | // in the event that the project uses something like npm workspaces, and the installed framework package 64 | // has been hoisted to the root directory of the project (which differs from the directory of the project/application being built) 65 | const installedFrameworkPath = await findUp(join('node_modules', npmPackage, 'package.json'), { cwd: projectDir }) 66 | const { packageJson } = await getPackageJson(installedFrameworkPath) 67 | 68 | return { 69 | ...frameworkInfo, 70 | package: { 71 | name: npmPackage, 72 | version: packageJson.version || 'unknown', 73 | }, 74 | } 75 | } 76 | 77 | /** 78 | * Return all the frameworks used by a project. 79 | * 80 | * @param {Options} options - Options 81 | * 82 | * @returns {Promise} frameworks - Frameworks used by a project 83 | */ 84 | export const listFrameworks = async function (opts) { 85 | const context = await getContext(opts) 86 | const frameworkList = await list(context) 87 | 88 | const projectDir = opts && opts.projectDir ? opts.projectDir : cwd() 89 | 90 | const settledPromises = await Promise.allSettled( 91 | frameworkList.map((framework) => getFrameworkVersion(projectDir, framework)), 92 | ) 93 | const updatedList = settledPromises.map((result) => { 94 | if (result.status === 'fulfilled') { 95 | return result.value 96 | } 97 | 98 | throw result.reason 99 | }) 100 | 101 | return updatedList 102 | } 103 | 104 | /** 105 | * Return whether a project uses a specific framework 106 | * 107 | * @param {string} frameworkId - Id such as `"gatsby"` 108 | * @param {Options} [options] - Context 109 | * 110 | * @returns {Promise} result - Whether the project uses this framework 111 | */ 112 | export const hasFramework = async function (frameworkId, options) { 113 | const context = await getContext(options) 114 | return has(frameworkId, context) 115 | } 116 | 117 | /** 118 | * Return some information about a framework used by a project. 119 | * 120 | * @param {string} frameworkId - Id such as `"gatsby"` 121 | * @param {Options} [options] - Context 122 | * 123 | * @returns {Promise} framework - Framework used by a project 124 | */ 125 | export const getFramework = async function (frameworkId, options) { 126 | const context = await getContext(options) 127 | return get(frameworkId, context) 128 | } 129 | -------------------------------------------------------------------------------- /src/package.js: -------------------------------------------------------------------------------- 1 | import filterObj from 'filter-obj' 2 | import isPlainObj from 'is-plain-obj' 3 | 4 | export const getPackageJsonContent = function ({ packageJson }) { 5 | if (packageJson === undefined) { 6 | return { npmDependencies: [], scripts: {} } 7 | } 8 | 9 | const npmDependencies = getNpmDependencies(packageJson) 10 | const scripts = getScripts(packageJson) 11 | return { npmDependencies, scripts } 12 | } 13 | 14 | // Retrieve `package.json` `dependencies` and `devDependencies` names 15 | const getNpmDependencies = function ({ dependencies, devDependencies }) { 16 | return [...getObjectKeys(dependencies), ...getObjectKeys(devDependencies)] 17 | } 18 | 19 | const getObjectKeys = function (value) { 20 | if (!isPlainObj(value)) { 21 | return [] 22 | } 23 | 24 | return Object.keys(value) 25 | } 26 | 27 | // Retrieve `package.json` `scripts` 28 | const getScripts = function ({ scripts }) { 29 | if (!isPlainObj(scripts)) { 30 | return {} 31 | } 32 | 33 | const scriptsA = filterObj(scripts, isValidScript) 34 | return scriptsA 35 | } 36 | 37 | const isValidScript = function (key, value) { 38 | return typeof value === 'string' 39 | } 40 | -------------------------------------------------------------------------------- /src/plugins.js: -------------------------------------------------------------------------------- 1 | import Ajv from 'ajv' 2 | import semver from 'semver' 3 | 4 | const MIN_NODE_VERSION_KEYWORD = { 5 | keyword: 'minNodeVersion', 6 | validate: (minNodeVersion, { nodeVersion }) => 7 | semver.valid(minNodeVersion) && semver.valid(nodeVersion) && semver.gte(nodeVersion, minNodeVersion), 8 | } 9 | 10 | const ajv = new Ajv({}) 11 | ajv.addKeyword(MIN_NODE_VERSION_KEYWORD) 12 | 13 | export const getPlugins = function (plugins, { nodeVersion }) { 14 | return plugins 15 | .filter(({ condition }) => ajv.validate(condition, { nodeVersion })) 16 | .map(({ packageName }) => packageName) 17 | } 18 | -------------------------------------------------------------------------------- /src/run_script.js: -------------------------------------------------------------------------------- 1 | import { dirname } from 'path' 2 | 3 | // Retrieve the command to run `package.json` `scripts` commands 4 | export const getRunScriptCommand = async function ({ pathExists, packageJsonPath }) { 5 | const yarnExists = await pathExists(`${dirname(packageJsonPath)}/yarn.lock`) 6 | if (yarnExists) { 7 | return 'yarn' 8 | } 9 | 10 | return 'npm run' 11 | } 12 | -------------------------------------------------------------------------------- /test/detect.js: -------------------------------------------------------------------------------- 1 | import { version as nodeVersion } from 'process' 2 | 3 | import test from 'ava' 4 | 5 | import { getFrameworks } from './helpers/main.js' 6 | 7 | test('Should detect dependencies', async (t) => { 8 | const frameworks = await getFrameworks('dependencies') 9 | t.is(frameworks.length, 1) 10 | }) 11 | 12 | test('Should detect devDependencies', async (t) => { 13 | const frameworks = await getFrameworks('dev_dependencies') 14 | t.is(frameworks.length, 1) 15 | }) 16 | 17 | test('Should ignore empty framework.npmDependencies', async (t) => { 18 | const frameworks = await getFrameworks('empty_dependencies') 19 | t.is(frameworks.length, 1) 20 | }) 21 | 22 | test('Should detect any of several framework.npmDependencies', async (t) => { 23 | const frameworks = await getFrameworks('several_dependencies') 24 | t.is(frameworks.length, 1) 25 | }) 26 | 27 | test('Should ignore if matching any framework.excludedNpmDependencies', async (t) => { 28 | const frameworks = await getFrameworks('excluded_dependencies') 29 | t.is(frameworks.length, 1) 30 | }) 31 | 32 | test('Should detect config files', async (t) => { 33 | const frameworks = await getFrameworks('config_files') 34 | t.is(frameworks.length, 1) 35 | }) 36 | 37 | if (nodeVersion !== 'v8.3.0') { 38 | test('Should detect Next.js plugin for Next.js if when Node version >= 10.13.0', async (t) => { 39 | const frameworks = await getFrameworks('next-plugin') 40 | t.is(frameworks[0].id, 'next') 41 | t.deepEqual(frameworks[0].plugins, ['@netlify/plugin-nextjs']) 42 | }) 43 | } 44 | 45 | if (nodeVersion === 'v8.3.0') { 46 | test('Should not detect Next.js plugin for Next.js if when Node version < 10.13.0', async (t) => { 47 | const frameworks = await getFrameworks('next-plugin') 48 | t.is(frameworks[0].id, 'next') 49 | t.is(frameworks[0].plugins.length, 0) 50 | }) 51 | } 52 | -------------------------------------------------------------------------------- /test/dev.js: -------------------------------------------------------------------------------- 1 | import test from 'ava' 2 | 3 | import { getFrameworks } from './helpers/main.js' 4 | 5 | test('Should use package scripts as dev command', async (t) => { 6 | const frameworks = await getFrameworks('use_scripts') 7 | t.is(frameworks.length, 1) 8 | t.deepEqual(frameworks[0].dev.commands, ['npm run dev', 'npm run start']) 9 | }) 10 | 11 | test('Should allow package scripts names with colons', async (t) => { 12 | const frameworks = await getFrameworks('colon_scripts') 13 | t.is(frameworks.length, 1) 14 | t.deepEqual(frameworks[0].dev.commands, ['npm run docs:dev']) 15 | }) 16 | 17 | test('Should only use package scripts if it includes framework.dev.command', async (t) => { 18 | const frameworks = await getFrameworks('dev_command_scripts') 19 | t.is(frameworks.length, 1) 20 | t.deepEqual(frameworks[0].dev.commands, ['npm run another']) 21 | }) 22 | 23 | test('Should default dev.commands to framework.dev.command', async (t) => { 24 | const frameworks = await getFrameworks('empty_scripts') 25 | t.is(frameworks.length, 1) 26 | t.deepEqual(frameworks[0].dev.commands, ['sapper dev']) 27 | }) 28 | 29 | test('Should sort scripts in the format *:', async (t) => { 30 | const frameworks = await getFrameworks('scripts-order/postfix-format') 31 | 32 | t.is(frameworks.length, 1) 33 | 34 | t.deepEqual(frameworks[0].dev.commands, ['npm run site:dev', 'npm run site:start', 'npm run site:build']) 35 | }) 36 | 37 | test('Should sort scripts when dev command is a substring of build command', async (t) => { 38 | const frameworks = await getFrameworks('scripts-order/command-substring') 39 | 40 | t.is(frameworks.length, 1) 41 | 42 | t.deepEqual(frameworks[0].dev.commands, ['npm run dev', 'npm run build']) 43 | }) 44 | 45 | test('Should prioritize dev over serve', async (t) => { 46 | const frameworks = await getFrameworks('scripts-order/vite-framework') 47 | 48 | t.is(frameworks.length, 1) 49 | 50 | t.deepEqual(frameworks[0].dev.commands, ['npm run dev', 'npm run serve', 'npm run build']) 51 | }) 52 | 53 | test(`Should exclude 'netlify dev' script`, async (t) => { 54 | const frameworks = await getFrameworks('excluded_script') 55 | 56 | t.is(frameworks.length, 1) 57 | 58 | t.deepEqual(frameworks[0].dev.commands, ['npm run build']) 59 | }) 60 | -------------------------------------------------------------------------------- /test/fixtures/colon_scripts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test", 3 | "version": "1.0.0", 4 | "dependencies": { 5 | "@quasar/app": "*" 6 | }, 7 | "scripts": { 8 | "docs:dev": "test" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /test/fixtures/config_files/config.rb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netlify/framework-info/db4604fc35bcea5dfdb502571ba257ea17d877ca/test/fixtures/config_files/config.rb -------------------------------------------------------------------------------- /test/fixtures/config_files/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test", 3 | "version": "1.0.0" 4 | } 5 | -------------------------------------------------------------------------------- /test/fixtures/dependencies/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test", 3 | "version": "1.0.0", 4 | "dependencies": { 5 | "sapper": "*", 6 | "other": "*" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /test/fixtures/dev_command_scripts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test", 3 | "version": "1.0.0", 4 | "dependencies": { 5 | "@quasar/app": "*" 6 | }, 7 | "scripts": { 8 | "another": "test quasar dev -p 8081 test", 9 | "other": "testThree", 10 | "start": "testTwo", 11 | "dev": "test" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /test/fixtures/dev_dependencies/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test", 3 | "version": "1.0.0", 4 | "devDependencies": { 5 | "sapper": "*", 6 | "other": "*" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /test/fixtures/empty/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netlify/framework-info/db4604fc35bcea5dfdb502571ba257ea17d877ca/test/fixtures/empty/.gitkeep -------------------------------------------------------------------------------- /test/fixtures/empty/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test", 3 | "version": "1.0.0" 4 | } 5 | -------------------------------------------------------------------------------- /test/fixtures/empty_dependencies/config.rb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netlify/framework-info/db4604fc35bcea5dfdb502571ba257ea17d877ca/test/fixtures/empty_dependencies/config.rb -------------------------------------------------------------------------------- /test/fixtures/empty_dependencies/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test", 3 | "version": "1.0.0" 4 | } 5 | -------------------------------------------------------------------------------- /test/fixtures/empty_scripts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test", 3 | "version": "1.0.0", 4 | "dependencies": { 5 | "sapper": "*" 6 | }, 7 | "scripts": {} 8 | } 9 | -------------------------------------------------------------------------------- /test/fixtures/excluded_dependencies/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test", 3 | "version": "1.0.0", 4 | "dependencies": { 5 | "sapper": "*", 6 | "svelte": "*" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /test/fixtures/excluded_script/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "create-app", 3 | "version": "1.0.0", 4 | "dependencies": { 5 | "react-scripts": "*" 6 | }, 7 | "scripts": { 8 | "start": "netlify dev", 9 | "build": "react-scripts build", 10 | "test": "react-scripts test", 11 | "eject": "react-scripts eject" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /test/fixtures/invalid_dependencies/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test", 3 | "version": "1.0.0", 4 | "dependencies": true, 5 | "devDependencies": { 6 | "sapper": "*" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /test/fixtures/invalid_package/package.json: -------------------------------------------------------------------------------- 1 | {{ 2 | -------------------------------------------------------------------------------- /test/fixtures/invalid_scripts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test", 3 | "version": "1.0.0", 4 | "dependencies": { 5 | "sapper": "*" 6 | }, 7 | "scripts": { 8 | "build": true 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /test/fixtures/monorepos/app1/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "app1", 3 | "version": "1.0.0", 4 | "dependencies": { 5 | "next":"*" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /test/fixtures/monorepos/app2/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "app2", 3 | "version": "2.0.0", 4 | "dependencies": { 5 | "next":"*" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /test/fixtures/monorepos/node_modules/next/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "3.2.1" 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/multiple/node_modules/@vue/cli-service/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.2.3" 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/multiple/node_modules/vuepress/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "4.5.6" 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/multiple/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test", 3 | "version": "1.0.0", 4 | "dependencies": { 5 | "@vue/cli-service": "*", 6 | "vuepress": "*" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /test/fixtures/next-plugin/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "my-next-app", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start" 9 | }, 10 | "dependencies": { 11 | "next": "10.0.5", 12 | "react": "17.0.1", 13 | "react-dom": "17.0.1" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /test/fixtures/no_package/config.rb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netlify/framework-info/db4604fc35bcea5dfdb502571ba257ea17d877ca/test/fixtures/no_package/config.rb -------------------------------------------------------------------------------- /test/fixtures/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "This is only here so fixtures never reach outside of this folder when finding package.json" 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/parent_package/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test", 3 | "version": "1.0.0", 4 | "dependencies": { 5 | "sapper": "*" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /test/fixtures/scripts-order/build-first/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "build": "rollup -c", 4 | "dev": "rollup -c -w", 5 | "start": "sirv public" 6 | }, 7 | "devDependencies": { 8 | "svelte": "^3.0.0" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /test/fixtures/scripts-order/command-substring/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "parcel-ste", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "license": "MIT", 6 | "dependencies": { 7 | "parcel-bundler": "^1.12.4" 8 | }, 9 | "scripts": { 10 | "build": "parcel build index.html", 11 | "dev": "parcel index.html" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /test/fixtures/scripts-order/dev-first/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "dev": "rollup -c -w", 4 | "build": "rollup -c", 5 | "start": "sirv public" 6 | }, 7 | "devDependencies": { 8 | "svelte": "^3.0.0" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /test/fixtures/scripts-order/postfix-format/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "site:build": "rollup -c", 4 | "site:start": "sirv public", 5 | "site:dev": "rollup -c -w" 6 | }, 7 | "devDependencies": { 8 | "svelte": "^3.0.0" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /test/fixtures/scripts-order/vite-framework/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vite-project", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "dev": "vite", 6 | "build": "vite build", 7 | "serve": "vite preview" 8 | }, 9 | "devDependencies": { 10 | "vite": "^2.1.5" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /test/fixtures/several_dependencies/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test", 3 | "version": "1.0.0", 4 | "dependencies": { 5 | "parcel": "*" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /test/fixtures/simple/node_modules/sapper/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "3.4.3" 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/simple/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test", 3 | "version": "1.0.0", 4 | "dependencies": { 5 | "sapper": "*" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /test/fixtures/syntax_package/package.json: -------------------------------------------------------------------------------- 1 | true 2 | -------------------------------------------------------------------------------- /test/fixtures/use_scripts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test", 3 | "version": "1.0.0", 4 | "dependencies": { 5 | "@quasar/app": "*" 6 | }, 7 | "scripts": { 8 | "other": "testThree", 9 | "start": "testTwo", 10 | "dev": "test" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /test/fixtures/yarn_scripts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test", 3 | "version": "1.0.0", 4 | "dependencies": { 5 | "@quasar/app": "*" 6 | }, 7 | "scripts": { 8 | "other": "testThree", 9 | "start": "testTwo", 10 | "dev": "test" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /test/fixtures/yarn_scripts/yarn.lock: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netlify/framework-info/db4604fc35bcea5dfdb502571ba257ea17d877ca/test/fixtures/yarn_scripts/yarn.lock -------------------------------------------------------------------------------- /test/frameworks.js: -------------------------------------------------------------------------------- 1 | import { promises as fs } from 'fs' 2 | import { extname } from 'path' 3 | 4 | import Ajv from 'ajv' 5 | import test from 'ava' 6 | import { each } from 'test-each' 7 | 8 | import { FRAMEWORKS } from '../build/frameworks.js' 9 | 10 | const FRAMEWORKS_DIR = new URL('../src/frameworks/', import.meta.url) 11 | 12 | const ajv = new Ajv({}) 13 | 14 | const validate = function (value, schema) { 15 | const isValid = ajv.validate(schema, value) 16 | 17 | if (isValid) { 18 | return true 19 | } 20 | 21 | return ajv.errorsText(ajv.errors, { separator: '\n' }) 22 | } 23 | 24 | const RELATIVE_PATH_SCHEMA = { 25 | type: 'string', 26 | minLength: 1, 27 | allOf: [{ not: { pattern: '^/' } }, { not: { pattern: '^\\.\\.?\\/' } }], 28 | } 29 | 30 | const COMMAND_SCHEMA = { 31 | type: 'string', 32 | minLength: 1, 33 | } 34 | 35 | const PLUGIN_SCHEMA = { 36 | type: 'object', 37 | required: ['packageName', 'condition'], 38 | additionalProperties: false, 39 | properties: { 40 | packageName: { type: 'string', minLength: 1 }, 41 | condition: { type: 'object' }, 42 | }, 43 | } 44 | 45 | const POLLING_STRATEGY_SCHEMA = { 46 | type: 'object', 47 | required: ['name'], 48 | additionalProperties: false, 49 | properties: { 50 | name: { enum: ['TCP', 'HTTP'] }, 51 | }, 52 | } 53 | 54 | const MAX_PORT = 65_535 55 | const FRAMEWORK_JSON_SCHEMA = { 56 | type: 'object', 57 | required: ['id', 'name', 'category', 'detect', 'dev', 'build', 'env'], 58 | additionalProperties: false, 59 | properties: { 60 | id: { type: 'string', pattern: '^[a-z\\d_]+', minLength: 1 }, 61 | name: { type: 'string', pattern: '^\\w+', minLength: 1 }, 62 | category: { 63 | type: 'string', 64 | enum: ['static_site_generator', 'frontend_framework', 'build_tool'], 65 | }, 66 | detect: { 67 | type: 'object', 68 | required: ['npmDependencies', 'excludedNpmDependencies', 'configFiles'], 69 | additionalProperties: false, 70 | properties: { 71 | npmDependencies: { 72 | type: 'array', 73 | items: { type: 'string', minLength: 1 }, 74 | }, 75 | excludedNpmDependencies: { 76 | type: 'array', 77 | items: { type: 'string', minLength: 1 }, 78 | }, 79 | configFiles: { 80 | type: 'array', 81 | items: RELATIVE_PATH_SCHEMA, 82 | }, 83 | }, 84 | }, 85 | dev: { 86 | oneOf: [ 87 | { 88 | type: 'object', 89 | required: ['command'], 90 | additionalProperties: false, 91 | properties: { 92 | command: COMMAND_SCHEMA, 93 | port: { type: 'integer', minimum: 1, maximum: MAX_PORT }, 94 | pollingStrategies: { 95 | type: 'array', 96 | items: POLLING_STRATEGY_SCHEMA, 97 | minItems: 1, 98 | uniqueItems: true, 99 | }, 100 | }, 101 | }, 102 | { type: 'object', additionalProperties: false, properties: {} }, 103 | ], 104 | }, 105 | build: { 106 | type: 'object', 107 | required: ['command', 'directory'], 108 | additionalProperties: false, 109 | properties: { 110 | command: COMMAND_SCHEMA, 111 | directory: RELATIVE_PATH_SCHEMA, 112 | }, 113 | }, 114 | staticAssetsDirectory: { 115 | type: 'string', 116 | }, 117 | env: { 118 | type: 'object', 119 | additionalProperties: { type: 'string' }, 120 | }, 121 | logo: { 122 | type: 'object', 123 | additionalProperties: false, 124 | properties: { 125 | default: { type: 'string' }, 126 | light: { type: 'string' }, 127 | dark: { type: 'string' }, 128 | }, 129 | }, 130 | plugins: { 131 | type: 'array', 132 | items: PLUGIN_SCHEMA, 133 | }, 134 | }, 135 | } 136 | 137 | each(FRAMEWORKS, (info, framework) => { 138 | test(`Framework "${framework.id}" should have a valid shape`, (t) => { 139 | t.is(validate(framework, FRAMEWORK_JSON_SCHEMA), true) 140 | }) 141 | }) 142 | 143 | test('each json file should be required in main.js FRAMEWORKS', async (t) => { 144 | const filenames = await fs.readdir(FRAMEWORKS_DIR) 145 | const jsonFiles = filenames.filter((filename) => extname(filename) === '.json') 146 | 147 | t.is(FRAMEWORKS.length, jsonFiles.length) 148 | FRAMEWORKS.forEach(({ id }) => { 149 | t.true(filenames.includes(`${id}.json`)) 150 | }) 151 | }) 152 | -------------------------------------------------------------------------------- /test/helpers/main.js: -------------------------------------------------------------------------------- 1 | import { fileURLToPath } from 'url' 2 | 3 | import { listFrameworks, getFramework as getFrameworkLib, hasFramework as hasFrameworkLib } from '../../src/main.js' 4 | 5 | export const FIXTURES_DIR = fileURLToPath(new URL('../fixtures', import.meta.url)) 6 | 7 | const getOptions = (fixtureName) => ({ projectDir: `${FIXTURES_DIR}/${fixtureName}` }) 8 | 9 | // Fire the main function with a specific fixture 10 | export const getFrameworks = function (fixtureName) { 11 | return listFrameworks(getOptions(fixtureName)) 12 | } 13 | 14 | export const getFramework = function (fixtureName, frameworkId) { 15 | return getFrameworkLib(frameworkId, getOptions(fixtureName)) 16 | } 17 | 18 | export const hasFramework = function (fixtureName, frameworkId) { 19 | return hasFrameworkLib(frameworkId, getOptions(fixtureName)) 20 | } 21 | -------------------------------------------------------------------------------- /test/main.js: -------------------------------------------------------------------------------- 1 | import test from 'ava' 2 | 3 | import { getFrameworks, getFramework, hasFramework } from './helpers/main.js' 4 | 5 | test('Should detect frameworks', async (t) => { 6 | const frameworks = await getFrameworks('simple') 7 | t.snapshot(frameworks) 8 | }) 9 | 10 | test('Should return an empty array when no framework is detected', async (t) => { 11 | const frameworks = await getFrameworks('empty') 12 | t.is(frameworks.length, 0) 13 | }) 14 | 15 | test('Should return several items when multiple frameworks are detected', async (t) => { 16 | const frameworks = await getFrameworks('multiple') 17 | t.is(frameworks.length, 2) 18 | }) 19 | 20 | test('Should return the version of each framework when multiple are detected', async (t) => { 21 | const frameworks = await getFrameworks('multiple') 22 | t.snapshot(frameworks) 23 | }) 24 | 25 | test('Should return the version of a framework that is not detected by npm package', async (t) => { 26 | const frameworks = await getFrameworks('no_package') 27 | t.snapshot(frameworks) 28 | }) 29 | 30 | test('Should return the version of the framework when the installed package is hoisted to the root project directory', async (t) => { 31 | const frameworks = await getFrameworks('monorepos/app1') 32 | t.snapshot(frameworks) 33 | }) 34 | 35 | test('Should allow getting a specific framework', async (t) => { 36 | const framework = await getFramework('simple', 'sapper') 37 | t.snapshot(framework) 38 | }) 39 | 40 | test('Should throw when passing an invalid framework', async (t) => { 41 | await t.throwsAsync(getFramework('simple', 'doesNotExist')) 42 | }) 43 | 44 | test('Should allow testing a specific framework', async (t) => { 45 | const trueResult = await hasFramework('simple', 'sapper') 46 | t.true(trueResult) 47 | 48 | const falseResult = await hasFramework('simple', 'nuxt') 49 | t.false(falseResult) 50 | }) 51 | 52 | test('Should throw when testing an invalid framework', async (t) => { 53 | await t.throwsAsync(hasFramework('simple', 'doesNotExist')) 54 | }) 55 | 56 | test('Should sort framework ids in invalid framework error message', async (t) => { 57 | const error = await t.throwsAsync(hasFramework('simple', 'doesNotExist')) 58 | 59 | // we don't use a hardcoded string here, since it will change when a new framework is added 60 | const [, frameworksFromMessage] = error.message.match(/It should be one of: (.+)/) 61 | const frameworksArray = frameworksFromMessage.split(', ') 62 | 63 | t.deepEqual(frameworksArray, [...frameworksArray].sort()) 64 | }) 65 | -------------------------------------------------------------------------------- /test/options.js: -------------------------------------------------------------------------------- 1 | import { cwd, chdir } from 'process' 2 | 3 | import test from 'ava' 4 | 5 | import { listFrameworks } from '../src/main.js' 6 | 7 | import { FIXTURES_DIR } from './helpers/main.js' 8 | 9 | test.serial('projectDir option defaults to process.cwd()', async (t) => { 10 | const oldCwd = cwd() 11 | chdir(`${FIXTURES_DIR}/simple`) 12 | try { 13 | const frameworks = await listFrameworks({}) 14 | t.is(frameworks.length, 1) 15 | } finally { 16 | chdir(oldCwd) 17 | } 18 | }) 19 | 20 | test.serial('Can trigger with no options', async (t) => { 21 | const oldCwd = cwd() 22 | chdir(`${FIXTURES_DIR}/simple`) 23 | try { 24 | const frameworks = await listFrameworks() 25 | t.is(frameworks.length, 1) 26 | } finally { 27 | chdir(oldCwd) 28 | } 29 | }) 30 | -------------------------------------------------------------------------------- /test/package.js: -------------------------------------------------------------------------------- 1 | import test from 'ava' 2 | import cpy from 'cpy' 3 | import del from 'del' 4 | import { dir as getTmpDir } from 'tmp-promise' 5 | 6 | import { listFrameworks } from '../src/main.js' 7 | 8 | import { getFrameworks, FIXTURES_DIR } from './helpers/main.js' 9 | 10 | test('Should detect package.json in parent directories', async (t) => { 11 | const frameworks = await getFrameworks('parent_package/parent') 12 | t.is(frameworks.length, 1) 13 | }) 14 | 15 | test('Should work without a package.json', async (t) => { 16 | const { path: tmpDir } = await getTmpDir() 17 | try { 18 | await cpy(`${FIXTURES_DIR}/no_package/**`, tmpDir) 19 | const frameworks = await listFrameworks({ projectDir: tmpDir }) 20 | t.is(frameworks.length, 1) 21 | } finally { 22 | del(tmpDir, { force: true }) 23 | } 24 | }) 25 | 26 | test('Should ignore invalid package.json', async (t) => { 27 | const frameworks = await getFrameworks('invalid_package') 28 | t.is(frameworks.length, 0) 29 | }) 30 | 31 | test('Should ignore package.json with a wrong syntax', async (t) => { 32 | const frameworks = await getFrameworks('syntax_package') 33 | t.is(frameworks.length, 0) 34 | }) 35 | 36 | test('Should ignore invalid package.json dependencies', async (t) => { 37 | const frameworks = await getFrameworks('invalid_dependencies') 38 | t.is(frameworks.length, 1) 39 | }) 40 | 41 | test('Should ignore invalid package.json scripts', async (t) => { 42 | const frameworks = await getFrameworks('invalid_scripts') 43 | t.is(frameworks.length, 1) 44 | }) 45 | 46 | test('Should ignore empty package.json scripts', async (t) => { 47 | const frameworks = await getFrameworks('empty_scripts') 48 | t.is(frameworks.length, 1) 49 | }) 50 | -------------------------------------------------------------------------------- /test/run_script.js: -------------------------------------------------------------------------------- 1 | import test from 'ava' 2 | 3 | import { getFrameworks } from './helpers/main.js' 4 | 5 | test('Should use Yarn when there is a yarn.lock', async (t) => { 6 | const frameworks = await getFrameworks('yarn_scripts') 7 | t.is(frameworks.length, 1) 8 | t.deepEqual(frameworks[0].dev.commands, ['yarn dev', 'yarn start']) 9 | }) 10 | -------------------------------------------------------------------------------- /test/snapshots/main.js.md: -------------------------------------------------------------------------------- 1 | # Snapshot report for `test/main.js` 2 | 3 | The actual snapshot is saved in `main.js.snap`. 4 | 5 | Generated by [AVA](https://avajs.dev). 6 | 7 | ## Should detect frameworks 8 | 9 | > Snapshot 1 10 | 11 | [ 12 | { 13 | build: { 14 | commands: [ 15 | 'sapper export', 16 | ], 17 | directory: '__sapper__/export', 18 | }, 19 | category: 'frontend_framework', 20 | dev: { 21 | commands: [ 22 | 'sapper dev', 23 | ], 24 | pollingStrategies: [ 25 | { 26 | name: 'TCP', 27 | }, 28 | { 29 | name: 'HTTP', 30 | }, 31 | ], 32 | port: 3000, 33 | }, 34 | env: {}, 35 | id: 'sapper', 36 | logo: { 37 | dark: 'https://framework-info.netlify.app/logos/sapper/default.svg', 38 | default: 'https://framework-info.netlify.app/logos/sapper/default.svg', 39 | light: 'https://framework-info.netlify.app/logos/sapper/default.svg', 40 | }, 41 | name: 'Sapper', 42 | package: { 43 | name: 'sapper', 44 | version: '3.4.3', 45 | }, 46 | plugins: [], 47 | staticAssetsDirectory: 'static', 48 | }, 49 | ] 50 | 51 | ## Should return the version of each framework when multiple are detected 52 | 53 | > Snapshot 1 54 | 55 | [ 56 | { 57 | build: { 58 | commands: [ 59 | 'vuepress build', 60 | ], 61 | directory: '.vuepress/dist', 62 | }, 63 | category: 'static_site_generator', 64 | dev: { 65 | commands: [ 66 | 'vuepress dev', 67 | ], 68 | pollingStrategies: [ 69 | { 70 | name: 'TCP', 71 | }, 72 | { 73 | name: 'HTTP', 74 | }, 75 | ], 76 | port: 8080, 77 | }, 78 | env: {}, 79 | id: 'vuepress', 80 | logo: { 81 | dark: 'https://framework-info.netlify.app/logos/vuepress/default.svg', 82 | default: 'https://framework-info.netlify.app/logos/vuepress/default.svg', 83 | light: 'https://framework-info.netlify.app/logos/vuepress/default.svg', 84 | }, 85 | name: 'VuePress', 86 | package: { 87 | name: 'vuepress', 88 | version: '4.5.6', 89 | }, 90 | plugins: [], 91 | staticAssetsDirectory: undefined, 92 | }, 93 | { 94 | build: { 95 | commands: [ 96 | 'vue-cli-service build', 97 | ], 98 | directory: 'dist', 99 | }, 100 | category: 'frontend_framework', 101 | dev: { 102 | commands: [ 103 | 'vue-cli-service serve', 104 | ], 105 | pollingStrategies: [ 106 | { 107 | name: 'TCP', 108 | }, 109 | { 110 | name: 'HTTP', 111 | }, 112 | ], 113 | port: 8080, 114 | }, 115 | env: {}, 116 | id: 'vue', 117 | logo: { 118 | dark: 'https://framework-info.netlify.app/logos/vue/default.svg', 119 | default: 'https://framework-info.netlify.app/logos/vue/default.svg', 120 | light: 'https://framework-info.netlify.app/logos/vue/default.svg', 121 | }, 122 | name: 'Vue.js', 123 | package: { 124 | name: '@vue/cli-service', 125 | version: '1.2.3', 126 | }, 127 | plugins: [], 128 | staticAssetsDirectory: undefined, 129 | }, 130 | ] 131 | 132 | ## Should return the version of a framework that is not detected by npm package 133 | 134 | > Snapshot 1 135 | 136 | [ 137 | { 138 | build: { 139 | commands: [ 140 | 'bundle exec middleman build', 141 | ], 142 | directory: 'build', 143 | }, 144 | category: 'static_site_generator', 145 | dev: { 146 | commands: [ 147 | 'bundle exec middleman server', 148 | ], 149 | pollingStrategies: [ 150 | { 151 | name: 'TCP', 152 | }, 153 | { 154 | name: 'HTTP', 155 | }, 156 | ], 157 | port: 4567, 158 | }, 159 | env: {}, 160 | id: 'middleman', 161 | logo: { 162 | dark: 'https://framework-info.netlify.app/logos/middleman/default.svg', 163 | default: 'https://framework-info.netlify.app/logos/middleman/default.svg', 164 | light: 'https://framework-info.netlify.app/logos/middleman/default.svg', 165 | }, 166 | name: 'Middleman', 167 | package: { 168 | name: undefined, 169 | version: 'unknown', 170 | }, 171 | plugins: [], 172 | staticAssetsDirectory: undefined, 173 | }, 174 | ] 175 | 176 | ## Should return the version of the framework when the installed package is hoisted to the root project directory 177 | 178 | > Snapshot 1 179 | 180 | [ 181 | { 182 | build: { 183 | commands: [ 184 | 'next build', 185 | ], 186 | directory: '.next', 187 | }, 188 | category: 'static_site_generator', 189 | dev: { 190 | commands: [ 191 | 'next', 192 | ], 193 | pollingStrategies: [ 194 | { 195 | name: 'TCP', 196 | }, 197 | ], 198 | port: 3000, 199 | }, 200 | env: {}, 201 | id: 'next', 202 | logo: { 203 | dark: 'https://framework-info.netlify.app/logos/nextjs/dark.svg', 204 | default: 'https://framework-info.netlify.app/logos/nextjs/light.svg', 205 | light: 'https://framework-info.netlify.app/logos/nextjs/light.svg', 206 | }, 207 | name: 'Next.js', 208 | package: { 209 | name: 'next', 210 | version: '3.2.1', 211 | }, 212 | plugins: [ 213 | '@netlify/plugin-nextjs', 214 | ], 215 | staticAssetsDirectory: undefined, 216 | }, 217 | ] 218 | 219 | ## Should allow getting a specific framework 220 | 221 | > Snapshot 1 222 | 223 | { 224 | build: { 225 | commands: [ 226 | 'sapper export', 227 | ], 228 | directory: '__sapper__/export', 229 | }, 230 | category: 'frontend_framework', 231 | dev: { 232 | commands: [ 233 | 'sapper dev', 234 | ], 235 | pollingStrategies: [ 236 | { 237 | name: 'TCP', 238 | }, 239 | { 240 | name: 'HTTP', 241 | }, 242 | ], 243 | port: 3000, 244 | }, 245 | env: {}, 246 | id: 'sapper', 247 | logo: { 248 | dark: 'https://framework-info.netlify.app/logos/sapper/default.svg', 249 | default: 'https://framework-info.netlify.app/logos/sapper/default.svg', 250 | light: 'https://framework-info.netlify.app/logos/sapper/default.svg', 251 | }, 252 | name: 'Sapper', 253 | package: { 254 | name: 'sapper', 255 | version: 'unknown', 256 | }, 257 | plugins: [], 258 | staticAssetsDirectory: 'static', 259 | } 260 | -------------------------------------------------------------------------------- /test/snapshots/main.js.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/netlify/framework-info/db4604fc35bcea5dfdb502571ba257ea17d877ca/test/snapshots/main.js.snap -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | import { fileURLToPath } from 'node:url' 2 | import nodePolyfills from 'rollup-plugin-node-polyfills' 3 | 4 | const CORE_FILE = fileURLToPath(new URL('src/core.js', import.meta.url)) 5 | 6 | /** @type {import('vite').UserConfig} */ 7 | export default { 8 | resolve: { 9 | alias: { 10 | path: 'rollup-plugin-node-polyfills/polyfills/path', 11 | }, 12 | }, 13 | build: { 14 | lib: { 15 | entry: CORE_FILE, 16 | name: 'frameworkInfo', 17 | fileName: 'index', 18 | }, 19 | sourcemap: true, 20 | rollupOptions: { 21 | plugins: [nodePolyfills()], 22 | }, 23 | }, 24 | } 25 | --------------------------------------------------------------------------------