Compare commits

...

41 Commits

Author SHA1 Message Date
Steven
ad436313e1 Publish Stable
- @vercel/build-utils@3.1.1
 - vercel@24.2.5
 - @vercel/client@11.0.4
 - @vercel/frameworks@1.0.0
 - @vercel/go@1.4.4
 - @vercel/next@2.9.0
 - @vercel/node@1.15.4
 - @vercel/python@2.3.4
 - @vercel/redwood@0.8.4
 - @vercel/remix@0.0.2
 - @vercel/ruby@1.3.7
 - @vercel/static-build@0.26.0
2022-05-31 10:44:36 -04:00
Lee Robinson
c414288b2f [examples] Update SvelteKit example with latest boilerplate (#7892)
Replaces https://github.com/vercel/vercel/pull/7674 with the latest SvelteKit boilerplate.
2022-05-30 07:57:46 +00:00
Steven
b07ff7431f [tests] Bump turbo to 1.2.14 (#7887)
This should fix the caching problem

https://github.com/vercel/turborepo/releases/tag/v1.2.14
2022-05-27 23:46:38 +00:00
Steven
79fde4475c [build-utils][go][next][redwood][static-build] Fix path delimiter on windows (#7881)
This PR fixes an issue where the path delimiter was incorrect for windows and caused yarn (and other global CLIs) to no longer be found.

https://github.com/vercel/vercel/runs/6602572000?check_suite_focus=true#step:13:1357
2022-05-27 22:59:28 +00:00
Nathan Rajlich
855197c699 [node] Remove mkdirp-promise dependency (#7878)
There's no need to use this module since Node.js has `recursive: true` since Node v10.

Co-authored-by: Steven <steven@ceriously.com>
2022-05-26 15:07:09 -04:00
Nathan Rajlich
fbd9080859 [tests] Add vc build integration test (#7876)
[cli] Add `vc build` integration test

Adds a `vc build` integration test to ensure the ncc'd CLI works as
expected to supplement the unit tests from #7869.

Co-authored-by: Steven <steven@ceriously.com>
2022-05-26 15:05:31 -04:00
Steven
b5c5b7b82c [tests] Fix tests with promises (#7880)
These tests are asynchronous and would sometimes fail if the second call started before the first call finished. This PR fixes the usage since the function returns a promise.
2022-05-26 10:28:49 -04:00
Gal Schlezinger
0a072ee850 Publish Canary
- vercel@24.2.5-canary.3
 - @vercel/next@2.8.67-canary.3
2022-05-26 09:20:12 +03:00
Dalpat Rathore
0b56caba45 [docs] Fix CONTRIBUTING.md grammar (#7575)
Fix: CONTRIBUTING.md grammar

Co-authored-by: Steven <steven@ceriously.com>
2022-05-25 17:44:41 -04:00
Steven
ab3db60824 [tests] Conditionally set env vars (#7874)
This PR is a follow up to #7873 which didn't work as intended
2022-05-25 17:36:20 -04:00
Steven
f2f2ff2c67 [tests] Skip env vars for forks (#7873)
This fixes an issue when an external contributor submits a PR and the remote cache is not accessible 

```
$ turbo run build
 ERROR No caches are enabled. You can try "turbo login", "turbo link", or ensuring you are not passing --remote-only to enable caching:  <nil>
• Packages in scope: @vercel/build-utils, @vercel/client, @vercel/frameworks, @vercel/go, @vercel/next, @vercel/node, @vercel/node-bridge, @vercel/python, @vercel/redwood, @vercel/remix, @vercel/routing-utils, @vercel/ruby, @vercel/static-build, @vercel/static-config, vercel
```
2022-05-25 16:54:35 -04:00
Gal Schlezinger
ba7dafff71 [next] Allow edge api endpoints in Next.js (#7855)
Co-authored-by: JJ Kasper <jj@jjsweb.site>
2022-05-25 15:19:14 +03:00
JJ Kasper
987fb4d4f7 [next] Ensure test-next-local is run in CI (#7868)
* Ensure test-next-local is run in CI

* update turbo config

* update timeout

* update flakey test
2022-05-24 14:22:40 -05:00
Nathan Rajlich
be74f79fa0 Publish Canary
- @vercel/build-utils@3.1.1-canary.2
 - vercel@24.2.5-canary.2
 - @vercel/client@11.0.4-canary.2
 - @vercel/go@1.4.4-canary.2
 - @vercel/next@2.8.67-canary.2
 - @vercel/node@1.15.4-canary.2
 - @vercel/python@2.3.4-canary.2
 - @vercel/redwood@0.8.4-canary.2
 - @vercel/remix@0.0.2-canary.2
 - @vercel/ruby@1.3.7-canary.2
 - @vercel/static-build@0.25.3-canary.2
2022-05-24 09:14:29 -07:00
Andrew Gadzik
86886e6b60 [build-utils] Add getWorkspaces helper function (#7775)
```ts
/*

  Example 1:
	
  my-repo
    |-- packages
    |    |-- api-1
    |    |-- api-2
    |-- package.json
    |-- pnpm-workspaces.yaml // packages: ["packages/*"]
	
  getWorkspaces("my-repo") => [
    {
      implementation: "pnpm",
      rootPath: "https://github.com/.../my-repo"
    }
  ]
  
  Example 2:
	
  my-repo
    |-- backend
    |    |-- api-1
    |    |-- api-2
    |    |-- package.json // workspaces: ["api-1", "api-2"]
    |-- frontend
    |    |-- nextjs-app
    |    |-- gatsby-app
    |    |-- package.json
    |    |-- pnpm-workspaces.yaml // packages: ["nextjs-app", "gatsby-app"]
	
  getWorkspaces("my-repo") => [
    {
      implementation: "yarn",
      rootPath: "https://github.com/.../my-repo/backend"
    }, 
    {
      implementation: "pnpm",
      rootPath: "https://github.com/.../my-repo/frontend"
    }
  ]

 */
type Workspace = {
  implementation: "yarn" | "npm" | "pnpm" 
  rootPath: string // the path to the root of the workspace
}

function getWorkspaces(fs: DetectorFilesystem): Workspace[]
```

### Related Issues

> Closes https://github.com/vercel/vercel/issues/7730

### 📋 Checklist

<!--
  Please keep your PR as a Draft until the checklist is complete
-->

#### Tests

- [x] The code changed/added as part of this PR has been covered with tests
- [x] All tests pass locally with `yarn test-unit`

#### Code Review

- [x] This PR has a concise title and thorough description useful to a reviewer
- [x] Issue from task tracker has a link to this PR
2022-05-24 14:28:32 +00:00
Nathan Rajlich
e8a9000137 [cli] Symlink @now/build-utils when installing builders in "vc build" (#7844)
To support legacy Builders like `@frontity/now` which still depends on the old `@now/build-utils` package name, create a symlink to `@vercel/build-utils` to make import the package work transparently.

Depends on #7842 being merged first.
2022-05-24 05:36:34 +00:00
Nathan Rajlich
75ea68d445 [cli] Convert DevServer "Unit" suite tests to "Dev" suite tests (#7845)
The `DevServer` class as it's currently implement is not really suitable for unit testing, since it calls `process.exit()` when shutting down the server. This causes Jest to exit prematurely, and prevents tests after these one from running. This was in fact hiding some failing tests. So these tests were ported over to be "Dev" suite tests which spawn a child process of `vc dev` rather than running them in-process, so that the `process.exit()` doesn't exit Jest.
2022-05-23 23:38:02 +00:00
Sean Massa
a5a990995c [tests] improve error message when a deploy fails (#7864)
improve error message
2022-05-23 17:02:29 -05:00
Sean Massa
cd7dcc6731 [tests] bump node version to 14 (#7863) 2022-05-23 16:00:03 -05:00
Sean Massa
c4bd462b85 [tests] update fixture path (#7862)
It looks like the fixture location was shifted and these references to it were not updated. The crashing `dev` tests were hiding these failures.
2022-05-23 20:31:24 +00:00
Steven
5fc266bd8a [next][node] Bump @vercel/nft to 0.19.1 (#7860)
Bump `@vercel/nft` to 0.19.1 https://github.com/vercel/nft/releases/tag/0.19.1
2022-05-23 17:30:29 +00:00
JJ Kasper
08b04d0bda Publish Canary
- @vercel/build-utils@3.1.1-canary.1
 - vercel@24.2.5-canary.1
 - @vercel/client@11.0.4-canary.1
 - @vercel/frameworks@0.9.2-canary.0
 - @vercel/go@1.4.4-canary.1
 - @vercel/next@2.8.67-canary.1
 - @vercel/node@1.15.4-canary.1
 - @vercel/python@2.3.4-canary.1
 - @vercel/redwood@0.8.4-canary.1
 - @vercel/remix@0.0.2-canary.1
 - @vercel/ruby@1.3.7-canary.1
 - @vercel/static-build@0.25.3-canary.1
2022-05-23 10:38:42 -05:00
Nathan Rajlich
92ae51c6a6 [examples] Add missing semi to "solidstart" vite config (#7858) 2022-05-22 19:24:45 -05:00
JJ Kasper
909c493d1e [next][build-utils] Update to re-use build result type from build-utils (#7857)
### Related Issues

We were previously using a custom type in Next.js for the build result although there is an existing type for this in the build-utils package which we can centralize around. 

x-ref: https://github.com/vercel/vercel/pull/7854#issuecomment-1133934764

### 📋 Checklist

<!--
  Please keep your PR as a Draft until the checklist is complete
-->

#### Tests

- [ ] The code changed/added as part of this PR has been covered with tests
- [ ] All tests pass locally with `yarn test-unit`

#### Code Review

- [ ] This PR has a concise title and thorough description useful to a reviewer
- [ ] Issue from task tracker has a link to this PR
2022-05-22 21:11:33 +00:00
Gal Schlezinger
98b54ff61f [next] Remove unused middleware definition (#7854)
Remove unused middleware definition in Next.js builder

Co-authored-by: JJ Kasper <jj@jjsweb.site>
2022-05-22 20:46:49 +03:00
JJ Kasper
3defd0b2fd [next] Fix middleware order and add e2e test (#7847)
Fix middleware order and add e2e test
2022-05-22 11:25:56 -05:00
Nathan Rajlich
5a7851a7f7 [tests] Revert "update CI to use node@14 and drop support for node@12 (#7819)" (#7853)
Revert "[tests] update CI to use node@14 and drop support for node@12 (#7819)"

This reverts commit 89b5aad367.
2022-05-21 20:11:08 -07:00
Nathan Rajlich
7f0caa7dec [examples] Add vercel.json file back to "remix" template for now (#7851)
This file was removed prematurely in #7831.
2022-05-21 20:03:06 -07:00
Sean Massa
89b5aad367 [tests] update CI to use node@14 and drop support for node@12 (#7819)
Node 12 is EOL and we're starting to reference packages that don't install on node lower than 14, such as nuxt@3 and remix. Let's update to 14 for now.
2022-05-21 05:20:43 +00:00
Nathan Rajlich
c13775ba07 [frameworks] Remove defaultHeaders / defaultRedirects / defaultRewrites / getFsOutputDir() (#7832)
These properties are leftover from the previous iteration of `vc build` / File System API v2, and are no longer in use.
2022-05-20 20:24:51 +00:00
Nathan Rajlich
cc3c1d1eeb [cli] Refactor vc build installBuilders() logic to not use a loop (#7842)
This will prevent the possibility of an infinite loop occurring when importing builders that throw a `MODULE_NOT_FOUND` error. Instead, we'll just try to import the builders, then run yarn if necessary, and then try to import the builders a second time when needed. If that fails then we'll throw the original error.
2022-05-20 19:53:54 +00:00
Sean Massa
3aa2fbbb53 [static-build] Support subset of Build Output API v2 (#7808)
* Revert "Revert "[static-build] Support subset of Build Output API v2" (#7803)"

This reverts commit dfb6ef949b.

* more specific v2 detection

* use nuxt@3.0.0-rc.3 to make sure the incident is resolved

* set test timeout to see if this works in CI

* update CI node version to 14

* add node_modules because it was taking too long to install in CI

* remove timeout

* remove node modules

* remove the nuxt@3 dependency

* finish update to node 14

* blank yarn.lock

* revert node version update

* fix revert

* remove newline
2022-05-20 12:27:14 -07:00
Nathan Rajlich
3a8b8aa03a [cli] Hard-code VERCEL: 1 env var for "vc build" (#7843)
This env var should normally come from the `.env` file that is created from `vc pull`, but let's just be extra sure and hard-code it directly in to `vc build` command as well.

SvelteKit `adapter-vercel` depends on this env var being set for its detection logic.
2022-05-20 18:53:55 +00:00
Nathan Rajlich
d008617c0b [frameworks] Use @vercel/remix Builder for "remix" preset (#7831)
Specify the `useRuntimes` property for the "remix" framework
to use the `@vercel/remix` Builder.

The `ENABLE_FILE_SYSTEM_API` env var is no longer necessary so the
`vercel.json` file from the Remix template has also been deleted.
2022-05-20 11:41:36 -05:00
Steven
8aecb99447 [cli] Recommend vc env instead of vc secrets (#6217)
We no longer need `vercel secrets` command. The former use cases can be solved with `vercel env`.

This adds a note recommending to use `vc env` as an alternative.

<img width="544" alt="Screen Shot 2021-08-11 at 1 38 18 PM" src="https://user-images.githubusercontent.com/71256/129101031-73e915dd-8253-4901-8e34-4b37d9516100.png">
<img width="565" alt="Screen Shot 2021-08-11 at 1 41 06 PM" src="https://user-images.githubusercontent.com/71256/129101036-2ed053dc-3de6-4fc1-8071-1bf9206323c9.png">
2022-05-20 15:43:20 +00:00
Andrew Gadzik
1793fa643f Add list of supported monorepo managers (#7760)
This file will mimic the structure of `vercel/frameworks/frameworks.ts` in the sense that it defines the list of supported monorepo managers.  These are the ones were targeting for the initial launch,

- Turborepo

```ts
export const monorepoManagers = [
  {
    name: "Turborepo",
    slug: "turbo",
    detectors: {
      every: [
        {
          path: 'turbo.json'
        },
      ],
    },
  },
]
```

### Related Issues

> Closes https://github.com/vercel/vercel/issues/7757

### 📋 Checklist

<!--
  Please keep your PR as a Draft until the checklist is complete
-->

#### Tests

- [x] The code changed/added as part of this PR has been covered with tests
- [x] All tests pass locally with `yarn test-unit`

#### Code Review

- [x] This PR has a concise title and thorough description useful to a reviewer
- [x] Issue from task tracker has a link to this PR
2022-05-20 15:20:50 +00:00
Nathan Rajlich
913683a8e6 [next] Use substring() instead of substr() (#7839)
Fixes linting warnings, i.e.:

> packages/next/src/server-build.ts#L858
>
> substr() is deprecated, use slice() or substring() instead
2022-05-20 14:58:39 +00:00
Nathan Rajlich
0d1dc23d0d [cli] Don't use tree-kill for shutting down startDevServer() process in vc dev (#7830)
Follow-up to #7824 since using `tree-kill` here seems to make a CLI test hang forever.
2022-05-19 18:16:09 -07:00
Nathan Rajlich
e829ce47c3 Publish Canary
- @vercel/build-utils@3.1.1-canary.0
 - vercel@24.2.5-canary.0
 - @vercel/client@11.0.4-canary.0
 - @vercel/go@1.4.4-canary.0
 - @vercel/next@2.8.67-canary.0
 - @vercel/node@1.15.4-canary.0
 - @vercel/python@2.3.4-canary.0
 - @vercel/redwood@0.8.4-canary.0
 - @vercel/remix@0.0.2-canary.0
 - @vercel/ruby@1.3.7-canary.0
 - @vercel/static-build@0.25.3-canary.0
2022-05-19 16:56:36 -07:00
Nathan Rajlich
474770e9b0 [remix] Add @vercel/remix Builder (#7814)
Adds a new Builder for the Remix framework. This Builder has support for a few different configurations of Remix including the "just the basics" Remix server as well as the "vercel" server build target configuration. If the project does not have the `@remix-run/vercel` runtime adapter as a dependency in package.json then this Builder will implicitly add it when generating the SSR serverless function.

In a follow-up PR, we can make this Builder be used in the frameworks configuration so that it is executed when `remix` framework is selected for the project, and we can update the remix example template in this repo to no longer require the `ENABLE_FILE_SYSTEM_API` env var.
2022-05-19 22:54:00 +00:00
Nathan Rajlich
e762d8d4e8 [cli] Add vercel build command (#7673)
This PR adds the latest iteration of the `vc build` command which invokes the Builders that a Project depends on locally and serializes the output into the `.vercel/output` directory.
2022-05-19 22:11:07 +00:00
240 changed files with 13715 additions and 3264 deletions

View File

@@ -23,7 +23,7 @@ Make sure all the tests pass before making changes.
## Verifying your change
Once you are done with your changes (we even suggest doing it along the way), make sure all the test still pass by running:
Once you are done with your changes (we even suggest doing it along the way), make sure all the tests still pass by running:
```
yarn test-unit
@@ -64,7 +64,7 @@ Integration tests create deployments to your Vercel account using the `test` pro
x-now-trace=iad1]
```
In such cases you can visit the URL of the failed deployment and append `/_logs` so see the build error. In the case above, that would be https://test-8ashcdlew.vercel.app/_logs
In such cases, you can visit the URL of the failed deployment and append `/_logs` to see the build error. In the case above, that would be https://test-8ashcdlew.vercel.app/_logs
The logs of this deployment will contain the actual error which may help you to understand what went wrong.
@@ -82,11 +82,11 @@ nodeFileTrace(['path/to/entrypoint.js'], {
.then(e => console.error(e));
```
When you run this script, you'll see all imported files. If anything file is missing, the bug is in [@vercel/nft](https://github.com/vercel/nft) and not the Builder.
When you run this script, you'll see all the imported files. If anything file is missing, the bug is in [@vercel/nft](https://github.com/vercel/nft) and not the Builder.
## Deploy a Builder with existing project
Sometimes you want to test changes to a Builder against an existing project, maybe with `vercel dev` or an actual deployment. You can avoid publishing every Builder change to npm by uploading the Builder as a tarball.
Sometimes you want to test changes to a Builder against an existing project, maybe with `vercel dev` or actual deployment. You can avoid publishing every Builder change to npm by uploading the Builder as a tarball.
1. Change directory to the desired Builder `cd ./packages/node`
2. Run `yarn build` to compile typescript and other build steps

View File

@@ -16,13 +16,15 @@ jobs:
fail-fast: false
matrix:
os: [ubuntu-latest]
node: [12]
node: [14]
runs-on: ${{ matrix.os }}
env:
TURBO_REMOTE_ONLY: true
TURBO_TEAM: vercel
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
steps:
- name: Conditionally set remote env
if: github.event.pull_request.head.repo.full_name == github.repository
run: |
echo "TURBO_REMOTE_ONLY=true" >> $GITHUB_ENV
echo "TURBO_TEAM=vercel" >> $GITHUB_ENV
echo "TURBO_TOKEN=${{ secrets.TURBO_TOKEN }}" >> $GITHUB_ENV
- uses: actions/setup-go@v2
with:
go-version: '1.13.15'

View File

@@ -16,13 +16,15 @@ jobs:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
node: [12]
node: [14]
runs-on: ${{ matrix.os }}
env:
TURBO_REMOTE_ONLY: true
TURBO_TEAM: vercel
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
steps:
- name: Conditionally set remote env
if: github.event.pull_request.head.repo.full_name == github.repository
run: |
echo "TURBO_REMOTE_ONLY=true" >> $GITHUB_ENV
echo "TURBO_TEAM=vercel" >> $GITHUB_ENV
echo "TURBO_TOKEN=${{ secrets.TURBO_TOKEN }}" >> $GITHUB_ENV
- uses: actions/setup-go@v2
with:
go-version: '1.13.15'
@@ -39,9 +41,9 @@ jobs:
- run: yarn install --network-timeout 1000000
- run: yarn run build
- run: yarn run lint
if: matrix.os == 'ubuntu-latest' && matrix.node == 12 # only run lint once
if: matrix.os == 'ubuntu-latest' && matrix.node == 14 # only run lint once
- run: yarn run test-unit
- run: yarn workspace vercel run coverage
if: matrix.os == 'ubuntu-latest' && matrix.node == 12 # only run coverage once
if: matrix.os == 'ubuntu-latest' && matrix.node == 14 # only run coverage once
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}

View File

@@ -38,10 +38,6 @@ jobs:
runs-on: ${{ matrix.runner }}
name: ${{matrix.scriptName}} (${{matrix.packageName}}, ${{matrix.chunkNumber}}, ${{ matrix.runner }})
if: ${{ needs.setup.outputs['tests'] != '[]' }}
env:
TURBO_REMOTE_ONLY: true
TURBO_TEAM: vercel
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
needs:
- setup
strategy:
@@ -49,6 +45,12 @@ jobs:
matrix:
include: ${{ fromJson(needs.setup.outputs['tests']) }}
steps:
- name: Conditionally set remote env
if: github.event.pull_request.head.repo.full_name == github.repository
run: |
echo "TURBO_REMOTE_ONLY=true" >> $GITHUB_ENV
echo "TURBO_TEAM=vercel" >> $GITHUB_ENV
echo "TURBO_TOKEN=${{ secrets.TURBO_TOKEN }}" >> $GITHUB_ENV
- uses: actions/checkout@v2
with:
fetch-depth: 2
@@ -59,11 +61,11 @@ jobs:
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'yarn'
- name: Install Hugo
if: matrix.runner == 'macos-latest'
run: curl -L -O https://github.com/gohugoio/hugo/releases/download/v0.56.0/hugo_0.56.0_macOS-64bit.tar.gz && tar -xzf hugo_0.56.0_macOS-64bit.tar.gz && mv ./hugo packages/cli/test/dev/fixtures/08-hugo/
- run: yarn install --network-timeout 1000000
- name: Build ${{matrix.packageName}} and all its dependencies

View File

@@ -2,6 +2,7 @@ dist
worker
.solid
.vercel
.output
# dependencies
/node_modules

View File

@@ -1,7 +1,7 @@
import { defineConfig } from "vite";
import solid from "solid-start";
import vercel from "solid-start-vercel"
import vercel from "solid-start-vercel";
export default defineConfig({
plugins: [solid({ adapter: vercel() })]
plugins: [solid({ adapter: vercel() })],
});

View File

@@ -7,3 +7,4 @@ node_modules
.env.*
!.env.example
.vercel
.output

View File

@@ -0,0 +1,13 @@
.DS_Store
node_modules
/build
/.svelte-kit
/package
.env
.env.*
!.env.example
# Ignore files for PNPM, NPM and YARN
pnpm-lock.yaml
package-lock.json
yarn.lock

View File

@@ -0,0 +1,6 @@
{
"useTabs": false,
"singleQuote": true,
"trailingComma": "none",
"printWidth": 100
}

View File

@@ -14,31 +14,29 @@ If you're seeing this, you've probably already done this step. Congrats!
```bash
# create a new project in the current directory
npm init svelte@next
npm init svelte
# create a new project in my-app
npm init svelte@next my-app
npm init svelte my-app
```
> Note: the `@next` is temporary
## Developing
Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
Once you've created a project and installed dependencies with `pnpm install`, start a development server:
```bash
npm run dev
pnpm run dev
# or start the server and open the app in a new browser tab
npm run dev -- --open
pnpm run dev -- --open
```
## Building
This uses the adapter-auto for SvelteKit, which detects Vercel and runs adapter-vercel on your behalf.
To create a production version of your app:
```bash
npm run build
pnpm run build
```
> You can preview the built app with `npm run preview`, regardless of whether you installed an adapter. This should _not_ be used to serve your app in production.
You can preview the production build with `npm run preview`.

View File

@@ -1,10 +1,13 @@
{
"extends": "./.svelte-kit/tsconfig.json",
"compilerOptions": {
"baseUrl": ".",
"paths": {
"$lib": ["src/lib"],
"$lib/*": ["src/lib/*"]
}
},
"include": ["src/**/*.d.ts", "src/**/*.js", "src/**/*.svelte"]
"allowJs": true,
"checkJs": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"sourceMap": true,
"strict": true
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,22 +1,29 @@
{
"private": true,
"name": "sveltekit",
"version": "0.0.1",
"scripts": {
"dev": "svelte-kit dev",
"build": "svelte-kit build",
"package": "svelte-kit package",
"preview": "svelte-kit preview"
"preview": "svelte-kit preview",
"prepare": "svelte-kit sync",
"check": "svelte-check --tsconfig ./jsconfig.json",
"check:watch": "svelte-check --tsconfig ./jsconfig.json --watch",
"lint": "prettier --check --plugin-search-dir=. .",
"format": "prettier --write --plugin-search-dir=. ."
},
"devDependencies": {
"@sveltejs/adapter-auto": "next",
"@sveltejs/kit": "next",
"svelte": "^3.46.0"
"@types/cookie": "^0.4.1",
"prettier": "^2.5.1",
"prettier-plugin-svelte": "^2.5.0",
"svelte": "^3.46.0",
"svelte-check": "^2.2.6",
"typescript": "~4.6.2"
},
"type": "module",
"dependencies": {
"@fontsource/fira-mono": "^4.5.0",
"@lukeed/uuid": "^2.0.0",
"cookie": "^0.4.1"
}
}

1633
examples/sveltekit/pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,107 +1,107 @@
@import '@fontsource/fira-mono';
:root {
font-family: Arial, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu,
Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
--font-mono: 'Fira Mono', monospace;
--pure-white: #ffffff;
--primary-color: #b9c6d2;
--secondary-color: #d0dde9;
--tertiary-color: #edf0f8;
--accent-color: #ff3e00;
--heading-color: rgba(0, 0, 0, 0.7);
--text-color: #444444;
--background-without-opacity: rgba(255, 255, 255, 0.7);
--column-width: 42rem;
--column-margin-top: 4rem;
font-family: Arial, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu,
Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
--font-mono: 'Fira Mono', monospace;
--pure-white: #ffffff;
--primary-color: #b9c6d2;
--secondary-color: #d0dde9;
--tertiary-color: #edf0f8;
--accent-color: #ff3e00;
--heading-color: rgba(0, 0, 0, 0.7);
--text-color: #444444;
--background-without-opacity: rgba(255, 255, 255, 0.7);
--column-width: 42rem;
--column-margin-top: 4rem;
}
body {
min-height: 100vh;
margin: 0;
background-color: var(--primary-color);
background: linear-gradient(
180deg,
var(--primary-color) 0%,
var(--secondary-color) 10.45%,
var(--tertiary-color) 41.35%
);
min-height: 100vh;
margin: 0;
background-color: var(--primary-color);
background: linear-gradient(
180deg,
var(--primary-color) 0%,
var(--secondary-color) 10.45%,
var(--tertiary-color) 41.35%
);
}
body::before {
content: '';
width: 80vw;
height: 100vh;
position: absolute;
top: 0;
left: 10vw;
z-index: -1;
background: radial-gradient(
50% 50% at 50% 50%,
var(--pure-white) 0%,
rgba(255, 255, 255, 0) 100%
);
opacity: 0.05;
content: '';
width: 80vw;
height: 100vh;
position: absolute;
top: 0;
left: 10vw;
z-index: -1;
background: radial-gradient(
50% 50% at 50% 50%,
var(--pure-white) 0%,
rgba(255, 255, 255, 0) 100%
);
opacity: 0.05;
}
#svelte {
min-height: 100vh;
display: flex;
flex-direction: column;
min-height: 100vh;
display: flex;
flex-direction: column;
}
h1,
h2,
p {
font-weight: 400;
color: var(--heading-color);
font-weight: 400;
color: var(--heading-color);
}
p {
line-height: 1.5;
line-height: 1.5;
}
a {
color: var(--accent-color);
text-decoration: none;
color: var(--accent-color);
text-decoration: none;
}
a:hover {
text-decoration: underline;
text-decoration: underline;
}
h1 {
font-size: 2rem;
text-align: center;
font-size: 2rem;
text-align: center;
}
h2 {
font-size: 1rem;
font-size: 1rem;
}
pre {
font-size: 16px;
font-family: var(--font-mono);
background-color: rgba(255, 255, 255, 0.45);
border-radius: 3px;
box-shadow: 2px 2px 6px rgb(255 255 255 / 25%);
padding: 0.5em;
overflow-x: auto;
color: var(--text-color);
font-size: 16px;
font-family: var(--font-mono);
background-color: rgba(255, 255, 255, 0.45);
border-radius: 3px;
box-shadow: 2px 2px 6px rgb(255 255 255 / 25%);
padding: 0.5em;
overflow-x: auto;
color: var(--text-color);
}
input,
button {
font-size: inherit;
font-family: inherit;
font-size: inherit;
font-family: inherit;
}
button:focus:not(:focus-visible) {
outline: none;
outline: none;
}
@media (min-width: 720px) {
h1 {
font-size: 2.4rem;
}
h1 {
font-size: 2.4rem;
}
}

15
examples/sveltekit/src/app.d.ts vendored Normal file
View File

@@ -0,0 +1,15 @@
/// <reference types="@sveltejs/kit" />
// See https://kit.svelte.dev/docs/types#app
// for information about these interfaces
declare namespace App {
interface Locals {
userid: string;
}
// interface Platform {}
// interface Session {}
// interface Stuff {}
}

View File

@@ -1,13 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="description" content="Svelte demo app" />
<link rel="icon" href="%svelte.assets%/favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
%svelte.head%
</head>
<body>
<div>%svelte.body%</div>
</body>
<head>
<meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
%sveltekit.head%
</head>
<body>
<div>%sveltekit.body%</div>
</body>
</html>

View File

@@ -1,23 +1,23 @@
import cookie from 'cookie';
import { v4 as uuid } from '@lukeed/uuid';
import * as cookie from 'cookie';
/** @type {import('@sveltejs/kit').Handle} */
export const handle = async ({ event, resolve }) => {
const cookies = cookie.parse(event.request.headers.get('cookie') || '');
event.locals.userid = cookies.userid || uuid();
const cookies = cookie.parse(event.request.headers.get('cookie') || '');
event.locals.userid = cookies['userid'] || crypto.randomUUID();
const response = await resolve(event);
const response = await resolve(event);
if (!cookies.userid) {
// if this is the first time the user has visited this app,
// set a cookie so that we recognise them when they return
response.headers.set(
'set-cookie',
cookie.serialize('userid', event.locals.userid, {
path: '/',
httpOnly: true
})
);
}
if (!cookies['userid']) {
// if this is the first time the user has visited this app,
// set a cookie so that we recognise them when they return
response.headers.set(
'set-cookie',
cookie.serialize('userid', event.locals.userid, {
path: '/',
httpOnly: true
})
);
}
return response;
return response;
};

View File

@@ -1,102 +1,107 @@
<script>
import { spring } from 'svelte/motion';
import { spring } from 'svelte/motion';
let count = 0;
let count = 0;
const displayed_count = spring();
$: displayed_count.set(count);
$: offset = modulo($displayed_count, 1);
const displayed_count = spring();
$: displayed_count.set(count);
$: offset = modulo($displayed_count, 1);
function modulo(n, m) {
// handle negative numbers
return ((n % m) + m) % m;
}
/**
* @param {number} n
* @param {number} m
*/
function modulo(n, m) {
// handle negative numbers
return ((n % m) + m) % m;
}
</script>
<div class="counter">
<button on:click={() => (count -= 1)} aria-label="Decrease the counter by one">
<svg aria-hidden="true" viewBox="0 0 1 1">
<path d="M0,0.5 L1,0.5" />
</svg>
</button>
<button on:click={() => (count -= 1)} aria-label="Decrease the counter by one">
<svg aria-hidden="true" viewBox="0 0 1 1">
<path d="M0,0.5 L1,0.5" />
</svg>
</button>
<div class="counter-viewport">
<div class="counter-digits" style="transform: translate(0, {100 * offset}%)">
<strong class="hidden" aria-hidden="true">{Math.floor($displayed_count + 1)}</strong>
<strong>{Math.floor($displayed_count)}</strong>
</div>
</div>
<div class="counter-viewport">
<div class="counter-digits" style="transform: translate(0, {100 * offset}%)">
<strong class="hidden" aria-hidden="true">{Math.floor($displayed_count + 1)}</strong>
<strong>{Math.floor($displayed_count)}</strong>
</div>
</div>
<button on:click={() => (count += 1)} aria-label="Increase the counter by one">
<svg aria-hidden="true" viewBox="0 0 1 1">
<path d="M0,0.5 L1,0.5 M0.5,0 L0.5,1" />
</svg>
</button>
<button on:click={() => (count += 1)} aria-label="Increase the counter by one">
<svg aria-hidden="true" viewBox="0 0 1 1">
<path d="M0,0.5 L1,0.5 M0.5,0 L0.5,1" />
</svg>
</button>
</div>
<style>
.counter {
display: flex;
border-top: 1px solid rgba(0, 0, 0, 0.1);
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
margin: 1rem 0;
}
.counter {
display: flex;
border-top: 1px solid rgba(0, 0, 0, 0.1);
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
margin: 1rem 0;
}
.counter button {
width: 2em;
padding: 0;
display: flex;
align-items: center;
justify-content: center;
border: 0;
background-color: transparent;
color: var(--text-color);
font-size: 2rem;
}
.counter button {
width: 2em;
padding: 0;
display: flex;
align-items: center;
justify-content: center;
border: 0;
background-color: transparent;
touch-action: manipulation;
color: var(--text-color);
font-size: 2rem;
}
.counter button:hover {
background-color: var(--secondary-color);
}
.counter button:hover {
background-color: var(--secondary-color);
}
svg {
width: 25%;
height: 25%;
}
svg {
width: 25%;
height: 25%;
}
path {
vector-effect: non-scaling-stroke;
stroke-width: 2px;
stroke: var(--text-color);
}
path {
vector-effect: non-scaling-stroke;
stroke-width: 2px;
stroke: var(--text-color);
}
.counter-viewport {
width: 8em;
height: 4em;
overflow: hidden;
text-align: center;
position: relative;
}
.counter-viewport {
width: 8em;
height: 4em;
overflow: hidden;
text-align: center;
position: relative;
}
.counter-viewport strong {
position: absolute;
display: flex;
width: 100%;
height: 100%;
font-weight: 400;
color: var(--accent-color);
font-size: 4rem;
align-items: center;
justify-content: center;
}
.counter-viewport strong {
position: absolute;
display: flex;
width: 100%;
height: 100%;
font-weight: 400;
color: var(--accent-color);
font-size: 4rem;
align-items: center;
justify-content: center;
}
.counter-digits {
position: absolute;
width: 100%;
height: 100%;
}
.counter-digits {
position: absolute;
width: 100%;
height: 100%;
}
.hidden {
top: -100%;
user-select: none;
}
.hidden {
top: -100%;
user-select: none;
}
</style>

View File

@@ -2,54 +2,81 @@ import { invalidate } from '$app/navigation';
// this action (https://svelte.dev/tutorial/actions) allows us to
// progressively enhance a <form> that already works without JS
/**
* @param {HTMLFormElement} form
* @param {{
* pending?: ({ data, form }: { data: FormData; form: HTMLFormElement }) => void;
* error?: ({
* data,
* form,
* response,
* error
* }: {
* data: FormData;
* form: HTMLFormElement;
* response: Response | null;
* error: Error | null;
* }) => void;
* result?: ({
* data,
* form,
* response
* }: {
* data: FormData;
* response: Response;
* form: HTMLFormElement;
* }) => void;
* }} [opts]
*/
export function enhance(form, { pending, error, result } = {}) {
let current_token;
let current_token;
async function handle_submit(e) {
const token = (current_token = {});
/** @param {SubmitEvent} e */
async function handle_submit(e) {
const token = (current_token = {});
e.preventDefault();
e.preventDefault();
const data = new FormData(form);
const data = new FormData(form);
if (pending) pending({ data, form });
if (pending) pending({ data, form });
try {
const response = await fetch(form.action, {
method: form.method,
headers: {
accept: 'application/json'
},
body: data
});
try {
const response = await fetch(form.action, {
method: form.method,
headers: {
accept: 'application/json'
},
body: data
});
if (token !== current_token) return;
if (token !== current_token) return;
if (response.ok) {
if (result) result({ data, form, response });
if (response.ok) {
if (result) result({ data, form, response });
const url = new URL(form.action);
url.search = url.hash = '';
invalidate(url.href);
} else if (error) {
error({ data, form, error: null, response });
} else {
console.error(await response.text());
}
} catch (e) {
if (error) {
error({ data, form, error: e, response: null });
} else {
throw e;
}
}
}
const url = new URL(form.action);
url.search = url.hash = '';
invalidate(url.href);
} else if (error) {
error({ data, form, error: null, response });
} else {
console.error(await response.text());
}
} catch (e) {
if (error && e instanceof Error) {
error({ data, form, error: e, response: null });
} else {
throw e;
}
}
}
form.addEventListener('submit', handle_submit);
form.addEventListener('submit', handle_submit);
return {
destroy() {
form.removeEventListener('submit', handle_submit);
}
};
return {
destroy() {
form.removeEventListener('submit', handle_submit);
}
};
}

View File

@@ -1,124 +1,124 @@
<script>
import { page } from '$app/stores';
import logo from './svelte-logo.svg';
import { page } from '$app/stores';
import logo from './svelte-logo.svg';
</script>
<header>
<div class="corner">
<a href="https://kit.svelte.dev">
<img src={logo} alt="SvelteKit" />
</a>
</div>
<div class="corner">
<a href="https://kit.svelte.dev">
<img src={logo} alt="SvelteKit" />
</a>
</div>
<nav>
<svg viewBox="0 0 2 3" aria-hidden="true">
<path d="M0,0 L1,2 C1.5,3 1.5,3 2,3 L2,0 Z" />
</svg>
<ul>
<li class:active={$page.url.pathname === '/'}><a sveltekit:prefetch href="/">Home</a></li>
<li class:active={$page.url.pathname === '/about'}>
<a sveltekit:prefetch href="/about">About</a>
</li>
<li class:active={$page.url.pathname === '/todos'}>
<a sveltekit:prefetch href="/todos">Todos</a>
</li>
</ul>
<svg viewBox="0 0 2 3" aria-hidden="true">
<path d="M0,0 L0,3 C0.5,3 0.5,3 1,2 L2,0 Z" />
</svg>
</nav>
<nav>
<svg viewBox="0 0 2 3" aria-hidden="true">
<path d="M0,0 L1,2 C1.5,3 1.5,3 2,3 L2,0 Z" />
</svg>
<ul>
<li class:active={$page.url.pathname === '/'}><a sveltekit:prefetch href="/">Home</a></li>
<li class:active={$page.url.pathname === '/about'}>
<a sveltekit:prefetch href="/about">About</a>
</li>
<li class:active={$page.url.pathname === '/todos'}>
<a sveltekit:prefetch href="/todos">Todos</a>
</li>
</ul>
<svg viewBox="0 0 2 3" aria-hidden="true">
<path d="M0,0 L0,3 C0.5,3 0.5,3 1,2 L2,0 Z" />
</svg>
</nav>
<div class="corner">
<!-- TODO put something else here? github link? -->
</div>
<div class="corner">
<!-- TODO put something else here? github link? -->
</div>
</header>
<style>
header {
display: flex;
justify-content: space-between;
}
header {
display: flex;
justify-content: space-between;
}
.corner {
width: 3em;
height: 3em;
}
.corner {
width: 3em;
height: 3em;
}
.corner a {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
}
.corner a {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
}
.corner img {
width: 2em;
height: 2em;
object-fit: contain;
}
.corner img {
width: 2em;
height: 2em;
object-fit: contain;
}
nav {
display: flex;
justify-content: center;
--background: rgba(255, 255, 255, 0.7);
}
nav {
display: flex;
justify-content: center;
--background: rgba(255, 255, 255, 0.7);
}
svg {
width: 2em;
height: 3em;
display: block;
}
svg {
width: 2em;
height: 3em;
display: block;
}
path {
fill: var(--background);
}
path {
fill: var(--background);
}
ul {
position: relative;
padding: 0;
margin: 0;
height: 3em;
display: flex;
justify-content: center;
align-items: center;
list-style: none;
background: var(--background);
background-size: contain;
}
ul {
position: relative;
padding: 0;
margin: 0;
height: 3em;
display: flex;
justify-content: center;
align-items: center;
list-style: none;
background: var(--background);
background-size: contain;
}
li {
position: relative;
height: 100%;
}
li {
position: relative;
height: 100%;
}
li.active::before {
--size: 6px;
content: '';
width: 0;
height: 0;
position: absolute;
top: 0;
left: calc(50% - var(--size));
border: var(--size) solid transparent;
border-top: var(--size) solid var(--accent-color);
}
li.active::before {
--size: 6px;
content: '';
width: 0;
height: 0;
position: absolute;
top: 0;
left: calc(50% - var(--size));
border: var(--size) solid transparent;
border-top: var(--size) solid var(--accent-color);
}
nav a {
display: flex;
height: 100%;
align-items: center;
padding: 0 1em;
color: var(--heading-color);
font-weight: 700;
font-size: 0.8rem;
text-transform: uppercase;
letter-spacing: 0.1em;
text-decoration: none;
transition: color 0.2s linear;
}
nav a {
display: flex;
height: 100%;
align-items: center;
padding: 0 1em;
color: var(--heading-color);
font-weight: 700;
font-size: 0.8rem;
text-transform: uppercase;
letter-spacing: 0.1em;
text-decoration: none;
transition: color 0.2s linear;
}
a:hover {
color: var(--accent-color);
}
a:hover {
color: var(--accent-color);
}
</style>

View File

@@ -1,45 +1,45 @@
<script>
import Header from '$lib/header/Header.svelte';
import '../app.css';
import Header from '$lib/header/Header.svelte';
import '../app.css';
</script>
<Header />
<main>
<slot />
<slot />
</main>
<footer>
<p>visit <a href="https://kit.svelte.dev">kit.svelte.dev</a> to learn SvelteKit</p>
<p>visit <a href="https://kit.svelte.dev">kit.svelte.dev</a> to learn SvelteKit</p>
</footer>
<style>
main {
flex: 1;
display: flex;
flex-direction: column;
padding: 1rem;
width: 100%;
max-width: 1024px;
margin: 0 auto;
box-sizing: border-box;
}
main {
flex: 1;
display: flex;
flex-direction: column;
padding: 1rem;
width: 100%;
max-width: 1024px;
margin: 0 auto;
box-sizing: border-box;
}
footer {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding: 40px;
}
footer {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding: 40px;
}
footer a {
font-weight: bold;
}
footer a {
font-weight: bold;
}
@media (min-width: 480px) {
footer {
padding: 40px 0;
}
}
@media (min-width: 480px) {
footer {
padding: 40px 0;
}
}
</style>

View File

@@ -1,50 +1,50 @@
<script context="module">
import { browser, dev } from '$app/env';
import { browser, dev } from '$app/env';
// we don't need any JS on this page, though we'll load
// it in dev so that we get hot module replacement...
export const hydrate = dev;
// we don't need any JS on this page, though we'll load
// it in dev so that we get hot module replacement...
export const hydrate = dev;
// ...but if the client-side router is already loaded
// (i.e. we came here from elsewhere in the app), use it
export const router = browser;
// ...but if the client-side router is already loaded
// (i.e. we came here from elsewhere in the app), use it
export const router = browser;
// since there's no dynamic data here, we can prerender
// it so that it gets served as a static asset in prod
export const prerender = true;
// since there's no dynamic data here, we can prerender
// it so that it gets served as a static asset in prod
export const prerender = true;
</script>
<svelte:head>
<title>About</title>
<title>About</title>
<meta name="description" content="About this app" />
</svelte:head>
<div class="content">
<h1>About this app</h1>
<h1>About this app</h1>
<p>
This is a <a href="https://kit.svelte.dev">SvelteKit</a> app. You can make your own by typing the
following into your command line and following the prompts:
</p>
<p>
This is a <a href="https://kit.svelte.dev">SvelteKit</a> app. You can make your own by typing the
following into your command line and following the prompts:
</p>
<!-- TODO lose the @next! -->
<pre>npm init svelte@next</pre>
<pre>npm init svelte</pre>
<p>
The page you're looking at is purely static HTML, with no client-side interactivity needed.
Because of that, we don't need to load any JavaScript. Try viewing the page's source, or opening
the devtools network panel and reloading.
</p>
<p>
The page you're looking at is purely static HTML, with no client-side interactivity needed.
Because of that, we don't need to load any JavaScript. Try viewing the page's source, or opening
the devtools network panel and reloading.
</p>
<p>
The <a href="/todos">TODOs</a> page illustrates SvelteKit's data loading and form handling. Try using
it with JavaScript disabled!
</p>
<p>
The <a href="/todos">TODOs</a> page illustrates SvelteKit's data loading and form handling. Try using
it with JavaScript disabled!
</p>
</div>
<style>
.content {
width: 100%;
max-width: var(--column-width);
margin: var(--column-margin-top) auto 0 auto;
}
.content {
width: 100%;
max-width: var(--column-width);
margin: var(--column-margin-top) auto 0 auto;
}
</style>

View File

@@ -1,59 +1,60 @@
<script context="module">
export const prerender = true;
export const prerender = true;
</script>
<script>
import Counter from '$lib/Counter.svelte';
import Counter from '$lib/Counter.svelte';
</script>
<svelte:head>
<title>Home</title>
<title>Home</title>
<meta name="description" content="Svelte demo app" />
</svelte:head>
<section>
<h1>
<div class="welcome">
<picture>
<source srcset="svelte-welcome.webp" type="image/webp" />
<img src="svelte-welcome.png" alt="Welcome" />
</picture>
</div>
<h1>
<div class="welcome">
<picture>
<source srcset="svelte-welcome.webp" type="image/webp" />
<img src="svelte-welcome.png" alt="Welcome" />
</picture>
</div>
to your new<br />SvelteKit app
</h1>
to your new<br />SvelteKit app
</h1>
<h2>
try editing <strong>src/routes/index.svelte</strong>
</h2>
<h2>
try editing <strong>src/routes/index.svelte</strong>
</h2>
<Counter />
<Counter />
</section>
<style>
section {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
flex: 1;
}
section {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
flex: 1;
}
h1 {
width: 100%;
}
h1 {
width: 100%;
}
.welcome {
position: relative;
width: 100%;
height: 0;
padding: 0 0 calc(100% * 495 / 2048) 0;
}
.welcome {
position: relative;
width: 100%;
height: 0;
padding: 0 0 calc(100% * 495 / 2048) 0;
}
.welcome img {
position: absolute;
width: 100%;
height: 100%;
top: 0;
display: block;
}
.welcome img {
position: absolute;
width: 100%;
height: 100%;
top: 0;
display: block;
}
</style>

View File

@@ -11,12 +11,17 @@
const base = 'https://api.svelte.dev';
/**
* @param {string} method
* @param {string} resource
* @param {Record<string, unknown>} [data]
*/
export function api(method, resource, data) {
return fetch(`${base}/${resource}`, {
method,
headers: {
'content-type': 'application/json'
},
body: data && JSON.stringify(data)
});
return fetch(`${base}/${resource}`, {
method,
headers: {
'content-type': 'application/json'
},
body: data && JSON.stringify(data)
});
}

View File

@@ -1,66 +1,70 @@
import { api } from './_api';
/** @type {import('./__types').RequestHandler} */
export const get = async ({ locals }) => {
// locals.userid comes from src/hooks.js
const response = await api('get', `todos/${locals.userid}`);
// locals.userid comes from src/hooks.js
const response = await api('get', `todos/${locals.userid}`);
if (response.status === 404) {
// user hasn't created a todo list.
// start with an empty array
return {
body: {
todos: []
}
};
}
if (response.status === 404) {
// user hasn't created a todo list.
// start with an empty array
return {
body: {
todos: []
}
};
}
if (response.status === 200) {
return {
body: {
todos: await response.json()
}
};
}
if (response.status === 200) {
return {
body: {
todos: await response.json()
}
};
}
return {
status: response.status
};
return {
status: response.status
};
};
/** @type {import('./index').RequestHandler} */
export const post = async ({ request, locals }) => {
const form = await request.formData();
const form = await request.formData();
await api('post', `todos/${locals.userid}`, {
text: form.get('text')
});
await api('post', `todos/${locals.userid}`, {
text: form.get('text')
});
return {};
return {};
};
// If the user has JavaScript disabled, the URL will change to
// include the method override unless we redirect back to /todos
const redirect = {
status: 303,
headers: {
location: '/todos'
}
status: 303,
headers: {
location: '/todos'
}
};
/** @type {import('./index').RequestHandler} */
export const patch = async ({ request, locals }) => {
const form = await request.formData();
const form = await request.formData();
await api('patch', `todos/${locals.userid}/${form.get('uid')}`, {
text: form.has('text') ? form.get('text') : undefined,
done: form.has('done') ? !!form.get('done') : undefined
});
await api('patch', `todos/${locals.userid}/${form.get('uid')}`, {
text: form.has('text') ? form.get('text') : undefined,
done: form.has('done') ? !!form.get('done') : undefined
});
return redirect;
return redirect;
};
/** @type {import('./index').RequestHandler} */
export const del = async ({ request, locals }) => {
const form = await request.formData();
const form = await request.formData();
await api('delete', `todos/${locals.userid}/${form.get('uid')}`);
await api('delete', `todos/${locals.userid}/${form.get('uid')}`);
return redirect;
return redirect;
};

View File

@@ -1,178 +1,190 @@
<script>
import { enhance } from '$lib/form';
import { scale } from 'svelte/transition';
import { flip } from 'svelte/animate';
import { enhance } from '$lib/form';
import { scale } from 'svelte/transition';
import { flip } from 'svelte/animate';
export let todos;
/**
* @typedef {{
* uid: string;
* created_at: Date;
* text: string;
* done: boolean;
* pending_delete: boolean;
* }} Todo
*/
/** @type {Todo[]} */
export let todos;
</script>
<svelte:head>
<title>Todos</title>
<title>Todos</title>
<meta name="description" content="A todo list app" />
</svelte:head>
<div class="todos">
<h1>Todos</h1>
<h1>Todos</h1>
<form
class="new"
action="/todos"
method="post"
use:enhance={{
result: async ({ form }) => {
form.reset();
}
}}
>
<input name="text" aria-label="Add todo" placeholder="+ tap to add a todo" />
</form>
<form
class="new"
action="/todos"
method="post"
use:enhance={{
result: async ({ form }) => {
form.reset();
}
}}
>
<input name="text" aria-label="Add todo" placeholder="+ tap to add a todo" />
</form>
{#each todos as todo (todo.uid)}
<div
class="todo"
class:done={todo.done}
transition:scale|local={{ start: 0.7 }}
animate:flip={{ duration: 200 }}
>
<form
action="/todos?_method=PATCH"
method="post"
use:enhance={{
pending: ({ data }) => {
todo.done = !!data.get('done');
}
}}
>
<input type="hidden" name="uid" value={todo.uid} />
<input type="hidden" name="done" value={todo.done ? '' : 'true'} />
<button class="toggle" aria-label="Mark todo as {todo.done ? 'not done' : 'done'}" />
</form>
{#each todos as todo (todo.uid)}
<div
class="todo"
class:done={todo.done}
transition:scale|local={{ start: 0.7 }}
animate:flip={{ duration: 200 }}
>
<form
action="/todos?_method=PATCH"
method="post"
use:enhance={{
pending: ({ data }) => {
todo.done = !!data.get('done');
}
}}
>
<input type="hidden" name="uid" value={todo.uid} />
<input type="hidden" name="done" value={todo.done ? '' : 'true'} />
<button class="toggle" aria-label="Mark todo as {todo.done ? 'not done' : 'done'}" />
</form>
<form class="text" action="/todos?_method=PATCH" method="post" use:enhance>
<input type="hidden" name="uid" value={todo.uid} />
<input aria-label="Edit todo" type="text" name="text" value={todo.text} />
<button class="save" aria-label="Save todo" />
</form>
<form class="text" action="/todos?_method=PATCH" method="post" use:enhance>
<input type="hidden" name="uid" value={todo.uid} />
<input aria-label="Edit todo" type="text" name="text" value={todo.text} />
<button class="save" aria-label="Save todo" />
</form>
<form
action="/todos?_method=DELETE"
method="post"
use:enhance={{
pending: () => (todo.pending_delete = true)
}}
>
<input type="hidden" name="uid" value={todo.uid} />
<button class="delete" aria-label="Delete todo" disabled={todo.pending_delete} />
</form>
</div>
{/each}
<form
action="/todos?_method=DELETE"
method="post"
use:enhance={{
pending: () => (todo.pending_delete = true)
}}
>
<input type="hidden" name="uid" value={todo.uid} />
<button class="delete" aria-label="Delete todo" disabled={todo.pending_delete} />
</form>
</div>
{/each}
</div>
<style>
.todos {
width: 100%;
max-width: var(--column-width);
margin: var(--column-margin-top) auto 0 auto;
line-height: 1;
}
.todos {
width: 100%;
max-width: var(--column-width);
margin: var(--column-margin-top) auto 0 auto;
line-height: 1;
}
.new {
margin: 0 0 0.5rem 0;
}
.new {
margin: 0 0 0.5rem 0;
}
input {
border: 1px solid transparent;
}
input {
border: 1px solid transparent;
}
input:focus-visible {
box-shadow: inset 1px 1px 6px rgba(0, 0, 0, 0.1);
border: 1px solid #ff3e00 !important;
outline: none;
}
input:focus-visible {
box-shadow: inset 1px 1px 6px rgba(0, 0, 0, 0.1);
border: 1px solid #ff3e00 !important;
outline: none;
}
.new input {
font-size: 28px;
width: 100%;
padding: 0.5em 1em 0.3em 1em;
box-sizing: border-box;
background: rgba(255, 255, 255, 0.05);
border-radius: 8px;
text-align: center;
}
.new input {
font-size: 28px;
width: 100%;
padding: 0.5em 1em 0.3em 1em;
box-sizing: border-box;
background: rgba(255, 255, 255, 0.05);
border-radius: 8px;
text-align: center;
}
.todo {
display: grid;
grid-template-columns: 2rem 1fr 2rem;
grid-gap: 0.5rem;
align-items: center;
margin: 0 0 0.5rem 0;
padding: 0.5rem;
background-color: white;
border-radius: 8px;
filter: drop-shadow(2px 4px 6px rgba(0, 0, 0, 0.1));
transform: translate(-1px, -1px);
transition: filter 0.2s, transform 0.2s;
}
.todo {
display: grid;
grid-template-columns: 2rem 1fr 2rem;
grid-gap: 0.5rem;
align-items: center;
margin: 0 0 0.5rem 0;
padding: 0.5rem;
background-color: white;
border-radius: 8px;
filter: drop-shadow(2px 4px 6px rgba(0, 0, 0, 0.1));
transform: translate(-1px, -1px);
transition: filter 0.2s, transform 0.2s;
}
.done {
transform: none;
opacity: 0.4;
filter: drop-shadow(0 0 1px rgba(0, 0, 0, 0.1));
}
.done {
transform: none;
opacity: 0.4;
filter: drop-shadow(0 0 1px rgba(0, 0, 0, 0.1));
}
form.text {
position: relative;
display: flex;
align-items: center;
flex: 1;
}
form.text {
position: relative;
display: flex;
align-items: center;
flex: 1;
}
.todo input {
flex: 1;
padding: 0.5em 2em 0.5em 0.8em;
border-radius: 3px;
}
.todo input {
flex: 1;
padding: 0.5em 2em 0.5em 0.8em;
border-radius: 3px;
}
.todo button {
width: 2em;
height: 2em;
border: none;
background-color: transparent;
background-position: 50% 50%;
background-repeat: no-repeat;
}
.todo button {
width: 2em;
height: 2em;
border: none;
background-color: transparent;
background-position: 50% 50%;
background-repeat: no-repeat;
}
button.toggle {
border: 1px solid rgba(0, 0, 0, 0.2);
border-radius: 50%;
box-sizing: border-box;
background-size: 1em auto;
}
button.toggle {
border: 1px solid rgba(0, 0, 0, 0.2);
border-radius: 50%;
box-sizing: border-box;
background-size: 1em auto;
}
.done .toggle {
background-image: url("data:image/svg+xml,%3Csvg width='22' height='16' viewBox='0 0 22 16' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M20.5 1.5L7.4375 14.5L1.5 8.5909' stroke='%23676778' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
}
.done .toggle {
background-image: url("data:image/svg+xml,%3Csvg width='22' height='16' viewBox='0 0 22 16' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M20.5 1.5L7.4375 14.5L1.5 8.5909' stroke='%23676778' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
}
.delete {
background-image: url("data:image/svg+xml,%3Csvg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M4.5 5V22H19.5V5H4.5Z' fill='%23676778' stroke='%23676778' stroke-width='1.5' stroke-linejoin='round'/%3E%3Cpath d='M10 10V16.5' stroke='white' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M14 10V16.5' stroke='white' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M2 5H22' stroke='%23676778' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M8 5L9.6445 2H14.3885L16 5H8Z' fill='%23676778' stroke='%23676778' stroke-width='1.5' stroke-linejoin='round'/%3E%3C/svg%3E%0A");
opacity: 0.2;
}
.delete {
background-image: url("data:image/svg+xml,%3Csvg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M4.5 5V22H19.5V5H4.5Z' fill='%23676778' stroke='%23676778' stroke-width='1.5' stroke-linejoin='round'/%3E%3Cpath d='M10 10V16.5' stroke='white' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M14 10V16.5' stroke='white' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M2 5H22' stroke='%23676778' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M8 5L9.6445 2H14.3885L16 5H8Z' fill='%23676778' stroke='%23676778' stroke-width='1.5' stroke-linejoin='round'/%3E%3C/svg%3E%0A");
opacity: 0.2;
}
.delete:hover,
.delete:focus {
transition: opacity 0.2s;
opacity: 1;
}
.delete:hover,
.delete:focus {
transition: opacity 0.2s;
opacity: 1;
}
.save {
position: absolute;
right: 0;
opacity: 0;
background-image: url("data:image/svg+xml,%3Csvg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M20.5 2H3.5C2.67158 2 2 2.67157 2 3.5V20.5C2 21.3284 2.67158 22 3.5 22H20.5C21.3284 22 22 21.3284 22 20.5V3.5C22 2.67157 21.3284 2 20.5 2Z' fill='%23676778' stroke='%23676778' stroke-width='1.5' stroke-linejoin='round'/%3E%3Cpath d='M17 2V11H7.5V2H17Z' fill='white' stroke='white' stroke-width='1.5' stroke-linejoin='round'/%3E%3Cpath d='M13.5 5.5V7.5' stroke='%23676778' stroke-width='1.5' stroke-linecap='round'/%3E%3Cpath d='M5.99844 2H18.4992' stroke='%23676778' stroke-width='1.5' stroke-linecap='round'/%3E%3C/svg%3E%0A");
}
.save {
position: absolute;
right: 0;
opacity: 0;
background-image: url("data:image/svg+xml,%3Csvg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M20.5 2H3.5C2.67158 2 2 2.67157 2 3.5V20.5C2 21.3284 2.67158 22 3.5 22H20.5C21.3284 22 22 21.3284 22 20.5V3.5C22 2.67157 21.3284 2 20.5 2Z' fill='%23676778' stroke='%23676778' stroke-width='1.5' stroke-linejoin='round'/%3E%3Cpath d='M17 2V11H7.5V2H17Z' fill='white' stroke='white' stroke-width='1.5' stroke-linejoin='round'/%3E%3Cpath d='M13.5 5.5V7.5' stroke='%23676778' stroke-width='1.5' stroke-linecap='round'/%3E%3Cpath d='M5.99844 2H18.4992' stroke='%23676778' stroke-width='1.5' stroke-linecap='round'/%3E%3C/svg%3E%0A");
}
.todo input:focus + .save,
.save:focus {
transition: opacity 0.2s;
opacity: 1;
}
.todo input:focus + .save,
.save:focus {
transition: opacity 0.2s;
opacity: 1;
}
</style>

View File

@@ -2,14 +2,14 @@ import adapter from '@sveltejs/adapter-auto';
/** @type {import('@sveltejs/kit').Config} */
const config = {
kit: {
adapter: adapter(),
kit: {
adapter: adapter(),
// Override http methods in the Todo forms
methodOverride: {
allowed: ['PATCH', 'DELETE']
}
}
// Override http methods in the Todo forms
methodOverride: {
allowed: ['PATCH', 'DELETE']
}
}
};
export default config;

View File

@@ -31,7 +31,7 @@
"prettier": "2.6.2",
"ts-eager": "2.0.2",
"ts-jest": "28.0.0-next.1",
"turbo": "1.2.9"
"turbo": "1.2.14"
},
"scripts": {
"lerna": "lerna",

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/build-utils",
"version": "3.1.0",
"version": "3.1.1",
"license": "MIT",
"main": "./dist/index.js",
"types": "./dist/index.d.js",
@@ -31,7 +31,7 @@
"@types/node-fetch": "^2.1.6",
"@types/semver": "6.0.0",
"@types/yazl": "2.4.2",
"@vercel/frameworks": "0.9.1",
"@vercel/frameworks": "1.0.0",
"@vercel/ncc": "0.24.0",
"aggregate-error": "3.0.1",
"async-retry": "1.2.3",

View File

@@ -1,12 +1,17 @@
import { Framework, FrameworkDetectionItem } from '@vercel/frameworks';
import { DetectorFilesystem } from './detectors/filesystem';
export interface DetectFrameworkOptions {
fs: DetectorFilesystem;
frameworkList: readonly Framework[];
interface BaseFramework {
slug: Framework['slug'];
detectors?: Framework['detectors'];
}
async function matches(fs: DetectorFilesystem, framework: Framework) {
export interface DetectFrameworkOptions {
fs: DetectorFilesystem;
frameworkList: readonly BaseFramework[];
}
async function matches(fs: DetectorFilesystem, framework: BaseFramework) {
const { detectors } = framework;
if (!detectors) {

View File

@@ -205,7 +205,9 @@ export function getSpawnOptions(
if (!meta.isDev) {
// Ensure that the selected Node version is at the beginning of the `$PATH`
opts.env.PATH = `/node${nodeVersion.major}/bin:${opts.env.PATH}`;
opts.env.PATH = `/node${nodeVersion.major}/bin${path.delimiter}${
opts.env.PATH || process.env.PATH
}`;
}
return opts;
@@ -433,13 +435,13 @@ export function getEnvForPackageManager({
(nodeVersion?.major || 0) < 16
) {
// Ensure that npm 7 is at the beginning of the `$PATH`
newEnv.PATH = `/node16/bin-npm7:${env.PATH}`;
newEnv.PATH = `/node16/bin-npm7${path.delimiter}${env.PATH}`;
console.log('Detected `package-lock.json` generated by npm 7...');
}
} else if (cliType === 'pnpm') {
if (typeof lockfileVersion === 'number' && lockfileVersion === 5.4) {
// Ensure that pnpm 7 is at the beginning of the `$PATH`
newEnv.PATH = `/pnpm7/node_modules/.bin:${env.PATH}`;
newEnv.PATH = `/pnpm7/node_modules/.bin${path.delimiter}${env.PATH}`;
console.log('Detected `pnpm-lock.yaml` generated by pnpm 7...');
}
} else {

View File

@@ -117,3 +117,5 @@ export const isStaticRuntime = (name?: string): boolean => {
};
export { workspaceManagers } from './workspaces/workspace-managers';
export { getWorkspaces } from './workspaces/get-workspaces';
export { monorepoManagers } from './monorepos/monorepo-managers';

View File

@@ -0,0 +1,36 @@
import type { Framework } from '@vercel/frameworks';
/**
* The supported list of monorepo managers.
*
* This list is designed to work with the @see {@link detectFramework} function.
*
* @example
* import { monorepoManagers as frameworkList } from '@vercel/build-utils'
* import { detectFramework } from '@vercel/build-utils'
*
* const fs = new GitDetectorFilesystem(...)
* detectFramwork({ fs, frameworkList }) // returns the 'slug' field if detected, otherwise null
*
*/
export const monorepoManagers: Array<
Omit<Framework, 'description' | 'logo' | 'settings' | 'getOutputDirName'>
> = [
{
name: 'Turborepo',
slug: 'turbo',
detectors: {
some: [
{
path: 'turbo.json',
},
{
path: 'package.json',
matchContent: '"turbo":\\s*{[^}]*.+[^}]*}',
},
],
},
},
];
export default monorepoManagers;

View File

@@ -327,6 +327,7 @@ export interface ProjectSettings {
buildCommand?: string | null;
outputDirectory?: string | null;
rootDirectory?: string | null;
nodeVersion?: string;
createdAt?: number;
autoExposeSystemEnvs?: boolean;
sourceFilesOutsideRootDirectory?: boolean;
@@ -351,11 +352,41 @@ export interface BuilderV3 {
type ImageFormat = 'image/avif' | 'image/webp';
export type RemotePattern = {
/**
* Must be `http` or `https`.
*/
protocol?: 'http' | 'https';
/**
* Can be literal or wildcard.
* Single `*` matches a single subdomain.
* Double `**` matches any number of subdomains.
*/
hostname: string;
/**
* Can be literal port such as `8080` or empty string
* meaning no port.
*/
port?: string;
/**
* Can be literal or wildcard.
* Single `*` matches a single path segment.
* Double `**` matches any number of path segments.
*/
pathname?: string;
};
export interface Images {
domains: string[];
remotePatterns?: RemotePattern[];
sizes: number[];
minimumCacheTTL?: number;
formats?: ImageFormat[];
dangerouslyAllowSVG?: boolean;
contentSecurityPolicy?: string;
}
/**

View File

@@ -0,0 +1,59 @@
import path from 'path';
import { DetectorFilesystem } from '../detectors/filesystem';
import { workspaceManagers } from './workspace-managers';
import { detectFramework as detectWorkspaceManagers } from '../detect-framework';
const MAX_DEPTH_TRAVERSE = 3;
const posixPath = path.posix;
export interface GetWorkspaceOptions {
fs: DetectorFilesystem;
depth?: number;
cwd?: string;
}
export type WorkspaceType = 'yarn' | 'pnpm' | 'npm';
export type Workspace = {
type: WorkspaceType;
rootPath: string;
};
export async function getWorkspaces({
fs,
depth = MAX_DEPTH_TRAVERSE,
cwd = '/',
}: GetWorkspaceOptions): Promise<Workspace[]> {
if (depth === 0) return [];
const workspaceType = await detectWorkspaceManagers({
fs,
frameworkList: workspaceManagers,
});
if (workspaceType === null) {
const directoryContents = await fs.readdir('./');
const childDirectories = directoryContents.filter(
stat => stat.type === 'dir'
);
return (
await Promise.all(
childDirectories.map(childDirectory =>
getWorkspaces({
fs: fs.chdir(childDirectory.path),
depth: depth - 1,
cwd: posixPath.join(cwd, childDirectory.path),
})
)
)
).flat();
}
return [
{
type: workspaceType as WorkspaceType,
rootPath: cwd,
},
];
}

View File

@@ -6,15 +6,16 @@ import type { Framework } from '@vercel/frameworks';
* This list is designed to work with the @see {@link detectFramework} function.
*
* @example
* import { workspaceManagers as frameworkList } from '@vercel/build-utils/workspaces'
* import { workspaceManagers as frameworkList } from '@vercel/build-utils'
* import { detectFramework } from '@vercel/build-utils'
*
* const fs = new GitDetectorFilesystem(...)
* detectFramwork({ fs, frameworkList }) // returns the 'slug' field if detected, otherwise null
*
* @todo Will be used by the detect-eligible-projects API endpoint for a given git url.
*/
export const workspaceManagers: Array<Framework> = [
export const workspaceManagers: Array<
Omit<Framework, 'description' | 'logo' | 'settings' | 'getOutputDirName'>
> = [
{
name: 'Yarn',
slug: 'yarn',
@@ -30,28 +31,6 @@ export const workspaceManagers: Array<Framework> = [
},
],
},
// unused props - needed for typescript
description: '',
logo: '',
settings: {
buildCommand: {
value: '',
placeholder: '',
},
devCommand: {
value: '',
placeholder: '',
},
installCommand: {
value: '',
placeholder: '',
},
outputDirectory: {
value: '',
placeholder: '',
},
},
getOutputDirName: () => Promise.resolve(''),
},
{
name: 'pnpm',
@@ -63,28 +42,6 @@ export const workspaceManagers: Array<Framework> = [
},
],
},
// unused props - needed for typescript
description: '',
logo: '',
settings: {
buildCommand: {
value: '',
placeholder: '',
},
devCommand: {
value: '',
placeholder: '',
},
installCommand: {
value: '',
placeholder: '',
},
outputDirectory: {
value: '',
placeholder: '',
},
},
getOutputDirName: () => Promise.resolve(''),
},
{
name: 'npm',
@@ -101,28 +58,6 @@ export const workspaceManagers: Array<Framework> = [
},
],
},
// unused props - needed for typescript
description: '',
logo: '',
settings: {
buildCommand: {
value: '',
placeholder: '',
},
devCommand: {
value: '',
placeholder: '',
},
installCommand: {
value: '',
placeholder: '',
},
outputDirectory: {
value: '',
placeholder: '',
},
},
getOutputDirName: () => Promise.resolve(''),
},
];

View File

@@ -17,5 +17,13 @@ checkPkgOrThrow('exeggcute');
// This is to satisfy `@vercel/static-build` which needs a `dist` directory.
const { exec } = require('exeggcute');
exec('mkdir dist', __dirname);
exec('echo "node-env:RANDOMNESS_PLACEHOLDER" > dist/index.html', __dirname);
exec('mkdir dist', __dirname)
.then(() => {
exec(
'echo "node-env:RANDOMNESS_PLACEHOLDER" > dist/index.html',
__dirname
).then(() => {
console.log('Success');
});
})
.catch(console.error);

View File

@@ -6,5 +6,10 @@ const b = require('./b');
a();
b();
exec('mkdir public', __dirname);
exec('echo "Hello, World!" > public/index.html', __dirname);
exec('mkdir public', __dirname)
.then(() => {
exec('echo "Hello, World!" > public/index.html', __dirname).then(() => {
console.log('Success');
});
})
.catch(console.error);

View File

@@ -0,0 +1,15 @@
{
"name": "a",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"debug": "^4.3.2"
}
}

View File

@@ -0,0 +1,15 @@
{
"name": "b",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"cowsay": "^1.5.0"
}
}

View File

@@ -0,0 +1,9 @@
{
"name": "21-npm-workspaces",
"version": "1.0.0",
"private": true,
"workspaces": [
"a",
"b"
]
}

View File

@@ -0,0 +1,20 @@
{
"$schema": "https://turborepo.org/schema.json",
"baseBranch": "origin/main",
"pipeline": {
"build": {
"dependsOn": ["^build"],
"outputs": [".next/**"]
},
"test": {
"dependsOn": ["^build"],
"outputs": []
},
"lint": {
"outputs": []
},
"dev": {
"cache": false
}
}
}

View File

@@ -0,0 +1,232 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
ansi-regex@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.1.tgz#123d6479e92ad45ad897d4054e3c7ca7db4944e1"
integrity sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==
ansi-regex@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304"
integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==
ansi-styles@^4.0.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937"
integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==
dependencies:
color-convert "^2.0.1"
camelcase@^5.0.0:
version "5.3.1"
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320"
integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==
cliui@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/cliui/-/cliui-6.0.0.tgz#511d702c0c4e41ca156d7d0e96021f23e13225b1"
integrity sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==
dependencies:
string-width "^4.2.0"
strip-ansi "^6.0.0"
wrap-ansi "^6.2.0"
color-convert@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3"
integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==
dependencies:
color-name "~1.1.4"
color-name@~1.1.4:
version "1.1.4"
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
cowsay@^1.5.0:
version "1.5.0"
resolved "https://registry.yarnpkg.com/cowsay/-/cowsay-1.5.0.tgz#4a2a453b8b59383c7d7a50e44d765c5de0bf615f"
integrity sha512-8Ipzr54Z8zROr/62C8f0PdhQcDusS05gKTS87xxdji8VbWefWly0k8BwGK7+VqamOrkv3eGsCkPtvlHzrhWsCA==
dependencies:
get-stdin "8.0.0"
string-width "~2.1.1"
strip-final-newline "2.0.0"
yargs "15.4.1"
debug@^4.3.2:
version "4.3.4"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
dependencies:
ms "2.1.2"
decamelize@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=
emoji-regex@^8.0.0:
version "8.0.0"
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37"
integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==
find-up@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19"
integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==
dependencies:
locate-path "^5.0.0"
path-exists "^4.0.0"
get-caller-file@^2.0.1:
version "2.0.5"
resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
get-stdin@8.0.0:
version "8.0.0"
resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-8.0.0.tgz#cbad6a73feb75f6eeb22ba9e01f89aa28aa97a53"
integrity sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg==
is-fullwidth-code-point@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f"
integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=
is-fullwidth-code-point@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d"
integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==
locate-path@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0"
integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==
dependencies:
p-locate "^4.1.0"
ms@2.1.2:
version "2.1.2"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
p-limit@^2.2.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1"
integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==
dependencies:
p-try "^2.0.0"
p-locate@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07"
integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==
dependencies:
p-limit "^2.2.0"
p-try@^2.0.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6"
integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==
path-exists@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3"
integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==
require-directory@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I=
require-main-filename@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b"
integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==
set-blocking@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc=
string-width@^4.1.0, string-width@^4.2.0:
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
dependencies:
emoji-regex "^8.0.0"
is-fullwidth-code-point "^3.0.0"
strip-ansi "^6.0.1"
string-width@~2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e"
integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==
dependencies:
is-fullwidth-code-point "^2.0.0"
strip-ansi "^4.0.0"
strip-ansi@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f"
integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8=
dependencies:
ansi-regex "^3.0.0"
strip-ansi@^6.0.0, strip-ansi@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
dependencies:
ansi-regex "^5.0.1"
strip-final-newline@2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad"
integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==
which-module@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a"
integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=
wrap-ansi@^6.2.0:
version "6.2.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53"
integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==
dependencies:
ansi-styles "^4.0.0"
string-width "^4.1.0"
strip-ansi "^6.0.0"
y18n@^4.0.0:
version "4.0.3"
resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.3.tgz#b5f259c82cd6e336921efd7bfd8bf560de9eeedf"
integrity sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==
yargs-parser@^18.1.2:
version "18.1.3"
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0"
integrity sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==
dependencies:
camelcase "^5.0.0"
decamelize "^1.2.0"
yargs@15.4.1:
version "15.4.1"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.4.1.tgz#0d87a16de01aee9d8bec2bfbf74f67851730f4f8"
integrity sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==
dependencies:
cliui "^6.0.0"
decamelize "^1.2.0"
find-up "^4.1.0"
get-caller-file "^2.0.1"
require-directory "^2.1.1"
require-main-filename "^2.0.0"
set-blocking "^2.0.0"
string-width "^4.2.0"
which-module "^2.0.0"
y18n "^4.0.0"
yargs-parser "^18.1.2"

View File

@@ -0,0 +1,5 @@
{
"name": "c",
"license": "MIT",
"version": "0.1.0"
}

View File

@@ -0,0 +1,8 @@
{
"name": "d",
"license": "MIT",
"version": "0.1.0",
"devDependencies": {
"once": "1.4.0"
}
}

View File

@@ -0,0 +1,6 @@
{
"private": true,
"name": "29-nested-workspaces-backend",
"license": "MIT",
"version": "1.0.0"
}

View File

@@ -0,0 +1,27 @@
lockfileVersion: 5.3
importers:
.:
specifiers: {}
c:
specifiers: {}
d:
specifiers:
once: 1.4.0
devDependencies:
once: 1.4.0
packages:
/once/1.4.0:
resolution: {integrity: sha1-WDsap3WWHUsROsF9nFC6753Xa9E=}
dependencies:
wrappy: 1.0.2
dev: true
/wrappy/1.0.2:
resolution: {integrity: sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=}
dev: true

View File

@@ -0,0 +1,3 @@
packages:
- 'c'
- 'd'

View File

@@ -0,0 +1,15 @@
{
"name": "a",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"debug": "^4.3.2"
}
}

View File

@@ -0,0 +1,15 @@
{
"name": "b",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"cowsay": "^1.5.0"
}
}

View File

@@ -0,0 +1,9 @@
{
"name": "29-nested-workspaces-frontend",
"version": "1.0.0",
"private": true,
"workspaces": [
"a",
"b"
]
}

View File

@@ -0,0 +1,232 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
ansi-regex@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.1.tgz#123d6479e92ad45ad897d4054e3c7ca7db4944e1"
integrity sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==
ansi-regex@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304"
integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==
ansi-styles@^4.0.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937"
integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==
dependencies:
color-convert "^2.0.1"
camelcase@^5.0.0:
version "5.3.1"
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320"
integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==
cliui@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/cliui/-/cliui-6.0.0.tgz#511d702c0c4e41ca156d7d0e96021f23e13225b1"
integrity sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==
dependencies:
string-width "^4.2.0"
strip-ansi "^6.0.0"
wrap-ansi "^6.2.0"
color-convert@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3"
integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==
dependencies:
color-name "~1.1.4"
color-name@~1.1.4:
version "1.1.4"
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
cowsay@^1.5.0:
version "1.5.0"
resolved "https://registry.yarnpkg.com/cowsay/-/cowsay-1.5.0.tgz#4a2a453b8b59383c7d7a50e44d765c5de0bf615f"
integrity sha512-8Ipzr54Z8zROr/62C8f0PdhQcDusS05gKTS87xxdji8VbWefWly0k8BwGK7+VqamOrkv3eGsCkPtvlHzrhWsCA==
dependencies:
get-stdin "8.0.0"
string-width "~2.1.1"
strip-final-newline "2.0.0"
yargs "15.4.1"
debug@^4.3.2:
version "4.3.4"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
dependencies:
ms "2.1.2"
decamelize@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=
emoji-regex@^8.0.0:
version "8.0.0"
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37"
integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==
find-up@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19"
integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==
dependencies:
locate-path "^5.0.0"
path-exists "^4.0.0"
get-caller-file@^2.0.1:
version "2.0.5"
resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
get-stdin@8.0.0:
version "8.0.0"
resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-8.0.0.tgz#cbad6a73feb75f6eeb22ba9e01f89aa28aa97a53"
integrity sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg==
is-fullwidth-code-point@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f"
integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=
is-fullwidth-code-point@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d"
integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==
locate-path@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0"
integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==
dependencies:
p-locate "^4.1.0"
ms@2.1.2:
version "2.1.2"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
p-limit@^2.2.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1"
integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==
dependencies:
p-try "^2.0.0"
p-locate@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07"
integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==
dependencies:
p-limit "^2.2.0"
p-try@^2.0.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6"
integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==
path-exists@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3"
integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==
require-directory@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I=
require-main-filename@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b"
integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==
set-blocking@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc=
string-width@^4.1.0, string-width@^4.2.0:
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
dependencies:
emoji-regex "^8.0.0"
is-fullwidth-code-point "^3.0.0"
strip-ansi "^6.0.1"
string-width@~2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e"
integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==
dependencies:
is-fullwidth-code-point "^2.0.0"
strip-ansi "^4.0.0"
strip-ansi@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f"
integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8=
dependencies:
ansi-regex "^3.0.0"
strip-ansi@^6.0.0, strip-ansi@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
dependencies:
ansi-regex "^5.0.1"
strip-final-newline@2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad"
integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==
which-module@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a"
integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=
wrap-ansi@^6.2.0:
version "6.2.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53"
integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==
dependencies:
ansi-styles "^4.0.0"
string-width "^4.1.0"
strip-ansi "^6.0.0"
y18n@^4.0.0:
version "4.0.3"
resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.3.tgz#b5f259c82cd6e336921efd7bfd8bf560de9eeedf"
integrity sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==
yargs-parser@^18.1.2:
version "18.1.3"
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0"
integrity sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==
dependencies:
camelcase "^5.0.0"
decamelize "^1.2.0"
yargs@15.4.1:
version "15.4.1"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.4.1.tgz#0d87a16de01aee9d8bec2bfbf74f67851730f4f8"
integrity sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==
dependencies:
cliui "^6.0.0"
decamelize "^1.2.0"
find-up "^4.1.0"
get-caller-file "^2.0.1"
require-directory "^2.1.1"
require-main-filename "^2.0.0"
set-blocking "^2.0.0"
string-width "^4.2.0"
which-module "^2.0.0"
y18n "^4.0.0"
yargs-parser "^18.1.2"

View File

@@ -0,0 +1,5 @@
{
"name": "c",
"license": "MIT",
"version": "0.1.0"
}

View File

@@ -0,0 +1,8 @@
{
"name": "d",
"license": "MIT",
"version": "0.1.0",
"devDependencies": {
"once": "1.4.0"
}
}

View File

@@ -0,0 +1,6 @@
{
"private": true,
"name": "30-double-nested-workspaces-packages-backend",
"license": "MIT",
"version": "1.0.0"
}

View File

@@ -0,0 +1,27 @@
lockfileVersion: 5.3
importers:
.:
specifiers: {}
c:
specifiers: {}
d:
specifiers:
once: 1.4.0
devDependencies:
once: 1.4.0
packages:
/once/1.4.0:
resolution: {integrity: sha1-WDsap3WWHUsROsF9nFC6753Xa9E=}
dependencies:
wrappy: 1.0.2
dev: true
/wrappy/1.0.2:
resolution: {integrity: sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=}
dev: true

View File

@@ -0,0 +1,3 @@
packages:
- 'c'
- 'd'

View File

@@ -0,0 +1,15 @@
{
"name": "a",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"debug": "^4.3.2"
}
}

View File

@@ -0,0 +1,15 @@
{
"name": "b",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"cowsay": "^1.5.0"
}
}

View File

@@ -0,0 +1,9 @@
{
"name": "30-double-nested-workspaces-packages-frontend",
"version": "1.0.0",
"private": true,
"workspaces": [
"a",
"b"
]
}

View File

@@ -0,0 +1,232 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
ansi-regex@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.1.tgz#123d6479e92ad45ad897d4054e3c7ca7db4944e1"
integrity sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==
ansi-regex@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304"
integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==
ansi-styles@^4.0.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937"
integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==
dependencies:
color-convert "^2.0.1"
camelcase@^5.0.0:
version "5.3.1"
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320"
integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==
cliui@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/cliui/-/cliui-6.0.0.tgz#511d702c0c4e41ca156d7d0e96021f23e13225b1"
integrity sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==
dependencies:
string-width "^4.2.0"
strip-ansi "^6.0.0"
wrap-ansi "^6.2.0"
color-convert@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3"
integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==
dependencies:
color-name "~1.1.4"
color-name@~1.1.4:
version "1.1.4"
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
cowsay@^1.5.0:
version "1.5.0"
resolved "https://registry.yarnpkg.com/cowsay/-/cowsay-1.5.0.tgz#4a2a453b8b59383c7d7a50e44d765c5de0bf615f"
integrity sha512-8Ipzr54Z8zROr/62C8f0PdhQcDusS05gKTS87xxdji8VbWefWly0k8BwGK7+VqamOrkv3eGsCkPtvlHzrhWsCA==
dependencies:
get-stdin "8.0.0"
string-width "~2.1.1"
strip-final-newline "2.0.0"
yargs "15.4.1"
debug@^4.3.2:
version "4.3.4"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
dependencies:
ms "2.1.2"
decamelize@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=
emoji-regex@^8.0.0:
version "8.0.0"
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37"
integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==
find-up@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19"
integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==
dependencies:
locate-path "^5.0.0"
path-exists "^4.0.0"
get-caller-file@^2.0.1:
version "2.0.5"
resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
get-stdin@8.0.0:
version "8.0.0"
resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-8.0.0.tgz#cbad6a73feb75f6eeb22ba9e01f89aa28aa97a53"
integrity sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg==
is-fullwidth-code-point@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f"
integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=
is-fullwidth-code-point@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d"
integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==
locate-path@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0"
integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==
dependencies:
p-locate "^4.1.0"
ms@2.1.2:
version "2.1.2"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
p-limit@^2.2.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1"
integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==
dependencies:
p-try "^2.0.0"
p-locate@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07"
integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==
dependencies:
p-limit "^2.2.0"
p-try@^2.0.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6"
integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==
path-exists@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3"
integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==
require-directory@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I=
require-main-filename@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b"
integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==
set-blocking@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc=
string-width@^4.1.0, string-width@^4.2.0:
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
dependencies:
emoji-regex "^8.0.0"
is-fullwidth-code-point "^3.0.0"
strip-ansi "^6.0.1"
string-width@~2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e"
integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==
dependencies:
is-fullwidth-code-point "^2.0.0"
strip-ansi "^4.0.0"
strip-ansi@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f"
integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8=
dependencies:
ansi-regex "^3.0.0"
strip-ansi@^6.0.0, strip-ansi@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
dependencies:
ansi-regex "^5.0.1"
strip-final-newline@2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad"
integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==
which-module@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a"
integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=
wrap-ansi@^6.2.0:
version "6.2.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53"
integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==
dependencies:
ansi-styles "^4.0.0"
string-width "^4.1.0"
strip-ansi "^6.0.0"
y18n@^4.0.0:
version "4.0.3"
resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.3.tgz#b5f259c82cd6e336921efd7bfd8bf560de9eeedf"
integrity sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==
yargs-parser@^18.1.2:
version "18.1.3"
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0"
integrity sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==
dependencies:
camelcase "^5.0.0"
decamelize "^1.2.0"
yargs@15.4.1:
version "15.4.1"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.4.1.tgz#0d87a16de01aee9d8bec2bfbf74f67851730f4f8"
integrity sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==
dependencies:
cliui "^6.0.0"
decamelize "^1.2.0"
find-up "^4.1.0"
get-caller-file "^2.0.1"
require-directory "^2.1.1"
require-main-filename "^2.0.0"
set-blocking "^2.0.0"
string-width "^4.2.0"
which-module "^2.0.0"
y18n "^4.0.0"
yargs-parser "^18.1.2"

View File

@@ -0,0 +1,15 @@
{
"name": "a",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"debug": "^4.3.2"
}
}

View File

@@ -0,0 +1,15 @@
{
"name": "b",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"cowsay": "^1.5.0"
}
}

View File

@@ -0,0 +1,34 @@
{
"name": "21-npm-workspaces",
"version": "1.0.0",
"private": true,
"workspaces": [
"a",
"b"
],
"turbo": {
"baseBranch": "origin/main",
"pipeline": {
"build": {
"dependsOn": [
"^build"
],
"outputs": [
".next/**"
]
},
"test": {
"dependsOn": [
"^build"
],
"outputs": []
},
"lint": {
"outputs": []
},
"dev": {
"cache": false
}
}
}
}

View File

@@ -0,0 +1,232 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
ansi-regex@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.1.tgz#123d6479e92ad45ad897d4054e3c7ca7db4944e1"
integrity sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==
ansi-regex@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304"
integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==
ansi-styles@^4.0.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937"
integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==
dependencies:
color-convert "^2.0.1"
camelcase@^5.0.0:
version "5.3.1"
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320"
integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==
cliui@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/cliui/-/cliui-6.0.0.tgz#511d702c0c4e41ca156d7d0e96021f23e13225b1"
integrity sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==
dependencies:
string-width "^4.2.0"
strip-ansi "^6.0.0"
wrap-ansi "^6.2.0"
color-convert@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3"
integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==
dependencies:
color-name "~1.1.4"
color-name@~1.1.4:
version "1.1.4"
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
cowsay@^1.5.0:
version "1.5.0"
resolved "https://registry.yarnpkg.com/cowsay/-/cowsay-1.5.0.tgz#4a2a453b8b59383c7d7a50e44d765c5de0bf615f"
integrity sha512-8Ipzr54Z8zROr/62C8f0PdhQcDusS05gKTS87xxdji8VbWefWly0k8BwGK7+VqamOrkv3eGsCkPtvlHzrhWsCA==
dependencies:
get-stdin "8.0.0"
string-width "~2.1.1"
strip-final-newline "2.0.0"
yargs "15.4.1"
debug@^4.3.2:
version "4.3.4"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
dependencies:
ms "2.1.2"
decamelize@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=
emoji-regex@^8.0.0:
version "8.0.0"
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37"
integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==
find-up@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19"
integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==
dependencies:
locate-path "^5.0.0"
path-exists "^4.0.0"
get-caller-file@^2.0.1:
version "2.0.5"
resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
get-stdin@8.0.0:
version "8.0.0"
resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-8.0.0.tgz#cbad6a73feb75f6eeb22ba9e01f89aa28aa97a53"
integrity sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg==
is-fullwidth-code-point@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f"
integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=
is-fullwidth-code-point@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d"
integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==
locate-path@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0"
integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==
dependencies:
p-locate "^4.1.0"
ms@2.1.2:
version "2.1.2"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
p-limit@^2.2.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1"
integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==
dependencies:
p-try "^2.0.0"
p-locate@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07"
integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==
dependencies:
p-limit "^2.2.0"
p-try@^2.0.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6"
integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==
path-exists@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3"
integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==
require-directory@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I=
require-main-filename@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b"
integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==
set-blocking@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc=
string-width@^4.1.0, string-width@^4.2.0:
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
dependencies:
emoji-regex "^8.0.0"
is-fullwidth-code-point "^3.0.0"
strip-ansi "^6.0.1"
string-width@~2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e"
integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==
dependencies:
is-fullwidth-code-point "^2.0.0"
strip-ansi "^4.0.0"
strip-ansi@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f"
integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8=
dependencies:
ansi-regex "^3.0.0"
strip-ansi@^6.0.0, strip-ansi@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
dependencies:
ansi-regex "^5.0.1"
strip-final-newline@2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad"
integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==
which-module@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a"
integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=
wrap-ansi@^6.2.0:
version "6.2.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53"
integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==
dependencies:
ansi-styles "^4.0.0"
string-width "^4.1.0"
strip-ansi "^6.0.0"
y18n@^4.0.0:
version "4.0.3"
resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.3.tgz#b5f259c82cd6e336921efd7bfd8bf560de9eeedf"
integrity sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==
yargs-parser@^18.1.2:
version "18.1.3"
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0"
integrity sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==
dependencies:
camelcase "^5.0.0"
decamelize "^1.2.0"
yargs@15.4.1:
version "15.4.1"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.4.1.tgz#0d87a16de01aee9d8bec2bfbf74f67851730f4f8"
integrity sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==
dependencies:
cliui "^6.0.0"
decamelize "^1.2.0"
find-up "^4.1.0"
get-caller-file "^2.0.1"
require-directory "^2.1.1"
require-main-filename "^2.0.0"
set-blocking "^2.0.0"
string-width "^4.2.0"
which-module "^2.0.0"
y18n "^4.0.0"
yargs-parser "^18.1.2"

View File

@@ -33,6 +33,10 @@ const skipFixtures: string[] = [
'21-npm-workspaces',
'23-pnpm-workspaces',
'27-yarn-workspaces',
'28-turborepo-with-yarn-workspaces',
'29-nested-workspaces',
'30-double-nested-workspaces',
'31-turborepo-in-package-json',
];
// eslint-disable-next-line no-restricted-syntax

View File

@@ -0,0 +1,28 @@
import path from 'path';
import { detectFramework } from '../src/detect-framework';
import monorepoManagers from '../src/monorepos/monorepo-managers';
import { FixtureFilesystem } from './utils/fixture-filesystem';
describe('monorepo-managers', () => {
describe.each([
['28-turborepo-with-yarn-workspaces', 'turbo'],
['31-turborepo-in-package-json', 'turbo'],
['22-pnpm', null],
])('with detectFramework', (fixturePath, frameworkSlug) => {
const testName = frameworkSlug
? `should detect a ${frameworkSlug} workspace for ${fixturePath}`
: `should not detect a monorepo manager for ${fixturePath}`;
it(testName, async () => {
const fixture = path.join(__dirname, 'fixtures', fixturePath);
const fs = new FixtureFilesystem(fixture);
const result = await detectFramework({
fs,
frameworkList: monorepoManagers,
});
expect(result).toBe(frameworkSlug);
});
});
});

View File

@@ -5,13 +5,13 @@ import { FixtureFilesystem } from './utils/fixture-filesystem';
describe('workspace-managers', () => {
describe.each([
['npm', '21-npm-workspaces'],
['pnpm', '23-pnpm-workspaces'],
['yarn', '27-yarn-workspaces'],
['yarn', '25-multiple-lock-files-yarn'],
['pnpm', '26-multiple-lock-files-pnpm'],
[null, '22-pnpm'],
])('with detectFramework', (frameworkSlug, fixturePath) => {
['21-npm-workspaces', 'npm'],
['23-pnpm-workspaces', 'pnpm'],
['27-yarn-workspaces', 'yarn'],
['25-multiple-lock-files-yarn', 'yarn'],
['26-multiple-lock-files-pnpm', 'pnpm'],
['22-pnpm', null],
])('with detectFramework', (fixturePath, frameworkSlug) => {
const testName = frameworkSlug
? `should detect a ${frameworkSlug} workspace for ${fixturePath}`
: `should not detect framework for ${fixturePath}`;

View File

@@ -1,4 +1,5 @@
import assert from 'assert';
import { delimiter } from 'path';
import { getEnvForPackageManager } from '../src';
describe('Test `getEnvForPackageManager()`', () => {
@@ -34,7 +35,7 @@ describe('Test `getEnvForPackageManager()`', () => {
},
want: {
FOO: 'bar',
PATH: `/node16/bin-npm7:foo`,
PATH: `/node16/bin-npm7${delimiter}foo`,
},
},
{
@@ -97,7 +98,7 @@ describe('Test `getEnvForPackageManager()`', () => {
},
want: {
FOO: 'bar',
PATH: '/pnpm7/node_modules/.bin:foo',
PATH: `/pnpm7/node_modules/.bin${delimiter}foo`,
},
},
{

View File

@@ -0,0 +1,43 @@
import path from 'path';
import { getWorkspaces, Workspace } from '../src/workspaces/get-workspaces';
import { FixtureFilesystem } from './utils/fixture-filesystem';
describe.each<[string, Workspace[]]>([
['21-npm-workspaces', [{ type: 'npm', rootPath: '/' }]],
['23-pnpm-workspaces', [{ type: 'pnpm', rootPath: '/' }]],
['27-yarn-workspaces', [{ type: 'yarn', rootPath: '/' }]],
['25-multiple-lock-files-yarn', [{ type: 'yarn', rootPath: '/' }]],
['26-multiple-lock-files-pnpm', [{ type: 'pnpm', rootPath: '/' }]],
[
'29-nested-workspaces',
[
{ type: 'pnpm', rootPath: '/backend' },
{ type: 'yarn', rootPath: '/frontend' },
],
],
[
'30-double-nested-workspaces',
[
{ type: 'pnpm', rootPath: '/packages/backend' },
{ type: 'yarn', rootPath: '/packages/frontend' },
],
],
['22-pnpm', []],
])('`getWorkspaces()`', (fixturePath, workspaces) => {
const expectedImplementations = workspaces.map(({ type }) => type);
const testName =
workspaces.length > 0
? `should detect ${expectedImplementations.join()} workspace${
expectedImplementations.length > 1 ? 's' : ''
} for ${fixturePath}`
: `should not detect any workspace for ${fixturePath}`;
it(testName, async () => {
const fixture = path.join(__dirname, 'fixtures', fixturePath);
const fs = new FixtureFilesystem(fixture);
const actualWorkspaces = await getWorkspaces({ fs });
expect(actualWorkspaces).toEqual(expect.arrayContaining(workspaces));
});
});

View File

@@ -1,6 +1,6 @@
{
"name": "vercel",
"version": "24.2.4",
"version": "24.2.5",
"preferGlobal": true,
"license": "Apache-2.0",
"description": "The command-line interface for Vercel",
@@ -43,11 +43,15 @@
"node": ">= 12"
},
"dependencies": {
"@vercel/build-utils": "3.1.0",
"@vercel/go": "1.4.3",
"@vercel/node": "1.15.3",
"@vercel/python": "2.3.3",
"@vercel/ruby": "1.3.6",
"@vercel/build-utils": "3.1.1",
"@vercel/go": "1.4.4",
"@vercel/next": "2.9.0",
"@vercel/node": "1.15.4",
"@vercel/python": "2.3.4",
"@vercel/redwood": "0.8.4",
"@vercel/remix": "0.0.2",
"@vercel/ruby": "1.3.7",
"@vercel/static-build": "0.26.0",
"update-notifier": "4.1.0"
},
"devDependencies": {
@@ -90,8 +94,9 @@
"@types/update-notifier": "5.1.0",
"@types/which": "1.3.2",
"@types/write-json-file": "2.2.1",
"@vercel/client": "11.0.3",
"@vercel/frameworks": "0.9.1",
"@types/yauzl-promise": "2.1.0",
"@vercel/client": "11.0.4",
"@vercel/frameworks": "1.0.0",
"@vercel/ncc": "0.24.0",
"@zeit/fun": "0.11.2",
"@zeit/source-map-support": "0.6.2",
@@ -167,7 +172,8 @@
"utility-types": "2.1.0",
"which": "2.0.2",
"write-json-file": "2.2.0",
"xdg-app-paths": "5.1.0"
"xdg-app-paths": "5.1.0",
"yauzl-promise": "2.1.3"
},
"jest": {
"preset": "ts-jest",

View File

@@ -0,0 +1,510 @@
import fs from 'fs-extra';
import chalk from 'chalk';
import dotenv from 'dotenv';
import { join, relative } from 'path';
import {
detectBuilders,
Files,
FileFsRef,
PackageJson,
BuildOptions,
Config,
Meta,
Builder,
BuildResultV2,
BuildResultV2Typical,
BuildResultV3,
} from '@vercel/build-utils';
import minimatch from 'minimatch';
import {
appendRoutesToPhase,
getTransformedRoutes,
mergeRoutes,
MergeRoutesProps,
Route,
} from '@vercel/routing-utils';
import { VercelConfig } from '@vercel/client';
import pull from './pull';
import { staticFiles as getFiles } from '../util/get-files';
import Client from '../util/client';
import getArgs from '../util/get-args';
import cmd from '../util/output/cmd';
import * as cli from '../util/pkg-name';
import cliPkg from '../util/pkg';
import readJSONFile from '../util/read-json-file';
import { CantParseJSONFile } from '../util/errors-ts';
import { readProjectSettings } from '../util/projects/project-settings';
import { VERCEL_DIR } from '../util/projects/link';
import confirm from '../util/input/confirm';
import { emoji, prependEmoji } from '../util/emoji';
import stamp from '../util/output/stamp';
import {
OUTPUT_DIR,
PathOverride,
writeBuildResult,
} from '../util/build/write-build-result';
import { importBuilders, BuilderWithPkg } from '../util/build/import-builders';
type BuildResult = BuildResultV2 | BuildResultV3;
const help = () => {
return console.log(`
${chalk.bold(`${cli.logo} ${cli.name} build`)}
${chalk.dim('Options:')}
-h, --help Output usage information
-A ${chalk.bold.underline('FILE')}, --local-config=${chalk.bold.underline(
'FILE'
)} Path to the local ${'`vercel.json`'} file
-Q ${chalk.bold.underline('DIR')}, --global-config=${chalk.bold.underline(
'DIR'
)} Path to the global ${'`.vercel`'} directory
--cwd [path] The current working directory
--prod Build a production deployment
-d, --debug Debug mode [off]
-y, --yes Skip the confirmation prompt
${chalk.dim('Examples:')}
${chalk.gray('')} Build the project
${chalk.cyan(`$ ${cli.name} build`)}
${chalk.cyan(`$ ${cli.name} build --cwd ./path-to-project`)}
`);
};
export default async function main(client: Client): Promise<number> {
const { output } = client;
// Ensure that `vc build` is not being invoked recursively
if (process.env.__VERCEL_BUILD_RUNNING) {
output.error(
`${cmd(
`${cli.name} build`
)} must not recursively invoke itself. Check the Build Command in the Project Settings or the ${cmd(
'build'
)} script in ${cmd('package.json')}`
);
output.error(
`Learn More: https://vercel.link/recursive-invocation-of-commands`
);
return 1;
} else {
process.env.__VERCEL_BUILD_RUNNING = '1';
}
// Parse CLI args
const argv = getArgs(client.argv.slice(2), {
'--cwd': String,
'--prod': Boolean,
});
if (argv['--help']) {
help();
return 2;
}
// Set the working directory if necessary
if (argv['--cwd']) {
process.chdir(argv['--cwd']);
}
const cwd = process.cwd();
// TODO: read project settings from the API, fall back to local `project.json` if that fails
// Read project settings, and pull them from Vercel if necessary
let project = await readProjectSettings(join(cwd, VERCEL_DIR));
const isTTY = process.stdin.isTTY;
while (!project?.settings) {
if (!isTTY) {
client.output.print(
`No Project Settings found locally. Run ${cli.getCommandName(
'pull --yes'
)} to retreive them.`
);
return 1;
}
const confirmed = await confirm(
`No Project Settings found locally. Run ${cli.getCommandName(
'pull'
)} for retrieving them?`,
true
);
if (!confirmed) {
client.output.print(`Aborted. No Project Settings retrieved.\n`);
return 0;
}
client.argv = [];
const result = await pull(client);
if (result !== 0) {
return result;
}
project = await readProjectSettings(join(cwd, VERCEL_DIR));
}
// Build `target` influences which environment variables will be used
const target = argv['--prod'] ? 'production' : 'preview';
// TODO: load env vars from the API, fall back to local files if that fails
const envPath = await checkExists([
join(cwd, VERCEL_DIR, `.env.${target}.local`),
join(cwd, `.env`),
]);
if (envPath) {
dotenv.config({ path: envPath, debug: client.output.isDebugEnabled() });
output.log(`Loaded env from "${relative(cwd, envPath)}"`);
}
// Some build processes use these env vars to platform detect Vercel
process.env.VERCEL = '1';
process.env.NOW_BUILDER = '1';
const workPath = join(cwd, project.settings.rootDirectory || '.');
// Load `package.json` and `vercel.json` files
const [pkg, vercelConfig] = await Promise.all([
readJSONFile<PackageJson>(join(workPath, 'package.json')),
readJSONFile<VercelConfig>(join(workPath, 'vercel.json')).then(
config => config || readJSONFile<VercelConfig>(join(workPath, 'now.json'))
),
]);
if (pkg instanceof CantParseJSONFile) throw pkg;
if (vercelConfig instanceof CantParseJSONFile) throw vercelConfig;
// Get a list of source files
const files = (await getFiles(workPath, client)).map(f =>
relative(workPath, f)
);
const routesResult = getTransformedRoutes({ nowConfig: vercelConfig || {} });
if (routesResult.error) {
output.prettyError(routesResult.error);
return 1;
}
if (vercelConfig?.builds && vercelConfig.functions) {
output.prettyError({
message:
'The `functions` property cannot be used in conjunction with the `builds` property. Please remove one of them.',
link: 'https://vercel.link/functions-and-builds',
});
return 1;
}
let builds = vercelConfig?.builds || [];
let zeroConfigRoutes: Route[] = [];
if (builds.length > 0) {
output.warn(
'Due to `builds` existing in your configuration file, the Build and Development Settings defined in your Project Settings will not apply. Learn More: https://vercel.link/unused-build-settings'
);
builds = builds.map(b => expandBuild(files, b)).flat();
} else {
// Zero config
// Detect the Vercel Builders that will need to be invoked
const detectedBuilders = await detectBuilders(files, pkg, {
...vercelConfig,
projectSettings: project.settings,
featHandleMiss: true,
});
if (detectedBuilders.errors && detectedBuilders.errors.length > 0) {
output.prettyError(detectedBuilders.errors[0]);
return 1;
}
for (const w of detectedBuilders.warnings) {
console.log(
`Warning: ${w.message} ${w.action || 'Learn More'}: ${w.link}`
);
}
if (detectedBuilders.builders) {
builds = detectedBuilders.builders;
} else {
builds = [{ src: '**', use: '@vercel/static' }];
}
zeroConfigRoutes.push(...(detectedBuilders.redirectRoutes || []));
zeroConfigRoutes.push(
...appendRoutesToPhase({
routes: [],
newRoutes: detectedBuilders.rewriteRoutes,
phase: 'filesystem',
})
);
zeroConfigRoutes = appendRoutesToPhase({
routes: zeroConfigRoutes,
newRoutes: detectedBuilders.errorRoutes,
phase: 'error',
});
zeroConfigRoutes.push(...(detectedBuilders.defaultRoutes || []));
}
const builderSpecs = new Set(builds.map(b => b.use));
let buildersWithPkgs: Map<string, BuilderWithPkg>;
try {
buildersWithPkgs = await importBuilders(builderSpecs, cwd, output);
} catch (err: any) {
output.prettyError(err);
return 1;
}
// Populate Files -> FileFsRef mapping
const filesMap: Files = {};
for (const path of files) {
const fsPath = join(workPath, path);
const { mode } = await fs.stat(fsPath);
filesMap[path] = new FileFsRef({ mode, fsPath });
}
// Delete output directory from potential previous build
await fs.remove(OUTPUT_DIR);
const buildStamp = stamp();
// Create fresh new output directory
await fs.mkdirp(OUTPUT_DIR);
const ops: Promise<Error | void>[] = [];
// Write the `detectedBuilders` result to output dir
ops.push(
fs.writeJSON(
join(OUTPUT_DIR, 'builds.json'),
{
'//': 'This file was generated by the `vercel build` command. It is not part of the Build Output API.',
target,
builds: builds.map(build => {
const builderWithPkg = buildersWithPkgs.get(build.use);
if (!builderWithPkg) {
throw new Error(`Failed to load Builder "${build.use}"`);
}
const { builder, pkg: builderPkg } = builderWithPkg;
return {
require: builderPkg.name,
apiVersion: builder.version,
...build,
};
}),
},
{
spaces: 2,
}
)
);
// The `meta` config property is re-used for each Builder
// invocation so that Builders can share state between
// subsequent entrypoint builds.
const meta: Meta = {
skipDownload: true,
cliVersion: cliPkg.version,
};
// Execute Builders for detected entrypoints
// TODO: parallelize builds
const buildResults: Map<Builder, BuildResult> = new Map();
const overrides: PathOverride[] = [];
for (const build of builds) {
if (typeof build.src !== 'string') continue;
const builderWithPkg = buildersWithPkgs.get(build.use);
if (!builderWithPkg) {
throw new Error(`Failed to load Builder "${build.use}"`);
}
const { builder, pkg: builderPkg } = builderWithPkg;
const buildConfig: Config = {
outputDirectory: project.settings.outputDirectory ?? undefined,
...build.config,
projectSettings: project.settings,
installCommand: project.settings.installCommand ?? undefined,
devCommand: project.settings.devCommand ?? undefined,
buildCommand: project.settings.buildCommand ?? undefined,
framework: project.settings.framework,
nodeVersion: project.settings.nodeVersion,
};
const repoRootPath = cwd === workPath ? undefined : cwd;
const buildOptions: BuildOptions = {
files: filesMap,
entrypoint: build.src,
workPath,
repoRootPath,
config: buildConfig,
meta,
};
output.debug(
`Building entrypoint "${build.src}" with "${builderPkg.name}"`
);
const buildResult = await builder.build(buildOptions);
// Store the build result to generate the final `config.json` after
// all builds have completed
buildResults.set(build, buildResult);
// Start flushing the file outputs to the filesystem asynchronously
ops.push(
writeBuildResult(
buildResult,
build,
builder,
builderPkg,
vercelConfig?.cleanUrls
).then(
override => {
if (override) overrides.push(override);
},
err => err
)
);
}
// Wait for filesystem operations to complete
// TODO render progress bar?
let hadError = false;
const errors = await Promise.all(ops);
for (const error of errors) {
if (error) {
hadError = true;
output.prettyError(error);
}
}
if (hadError) return 1;
// Merge existing `config.json` file into the one that will be produced
const configPath = join(OUTPUT_DIR, 'config.json');
// TODO: properly type
const existingConfig = await readJSONFile<any>(configPath);
if (existingConfig instanceof CantParseJSONFile) {
throw existingConfig;
}
if (existingConfig) {
if (existingConfig.overrides) {
overrides.push(existingConfig.overrides);
}
// Find the `Build` entry for this config file and update the build result
for (const [build, buildResult] of buildResults.entries()) {
if ('buildOutputPath' in buildResult) {
output.debug(`Using "config.json" for "${build.use}`);
buildResults.set(build, existingConfig);
break;
}
}
}
const builderRoutes: MergeRoutesProps['builds'] = Array.from(
buildResults.entries()
)
.filter(b => 'routes' in b[1] && Array.isArray(b[1].routes))
.map(b => {
return {
use: b[0].use,
entrypoint: b[0].src!,
routes: (b[1] as BuildResultV2Typical).routes,
};
});
if (zeroConfigRoutes.length) {
builderRoutes.unshift({
use: '@vercel/zero-config-routes',
entrypoint: '/',
routes: zeroConfigRoutes,
});
}
const mergedRoutes = mergeRoutes({
userRoutes: routesResult.routes,
builds: builderRoutes,
});
const mergedImages = mergeImages(buildResults.values());
const mergedWildcard = mergeWildcard(buildResults.values());
const mergedOverrides: Record<string, PathOverride> =
overrides.length > 0 ? Object.assign({}, ...overrides) : undefined;
// Write out the final `config.json` file based on the
// user configuration and Builder build results
// TODO: properly type
const config = {
version: 3,
routes: mergedRoutes,
images: mergedImages,
wildcard: mergedWildcard,
overrides: mergedOverrides,
};
await fs.writeJSON(join(OUTPUT_DIR, 'config.json'), config, { spaces: 2 });
output.print(
`${prependEmoji(
`Build Completed in ${chalk.bold(OUTPUT_DIR)} ${chalk.gray(
buildStamp()
)}`,
emoji('success')
)}\n`
);
return 0;
}
function expandBuild(files: string[], build: Builder): Builder[] {
if (!build.src) return [];
let pattern = build.src;
if (pattern[0] === '/') {
// Remove a leading slash so that the globbing is relative
// to `cwd` instead of the root of the filesystem.
pattern = pattern.substring(1);
}
const matches = files.filter(
name => name === pattern || minimatch(name, pattern, { dot: true })
);
return matches.map(m => {
return {
...build,
src: m,
};
});
}
function mergeImages(
buildResults: Iterable<BuildResult>
): BuildResultV2Typical['images'] {
let images: BuildResultV2Typical['images'] = undefined;
for (const result of buildResults) {
if ('images' in result && result.images) {
images = Object.assign({} || images, result.images);
}
}
return images;
}
function mergeWildcard(
buildResults: Iterable<BuildResult>
): BuildResultV2Typical['wildcard'] {
let wildcard: BuildResultV2Typical['wildcard'] = undefined;
for (const result of buildResults) {
if ('wildcard' in result && result.wildcard) {
if (!wildcard) wildcard = [];
wildcard.push(...result.wildcard);
}
}
return wildcard;
}
async function checkExists(paths: Iterable<string>) {
for (const path of paths) {
try {
await fs.stat(path);
return path;
} catch (err: any) {
if (err.code !== 'ENOENT') throw err;
}
}
}

View File

@@ -98,6 +98,12 @@ export default async function dev(
]);
}
// This is just for tests - can be removed once project settings
// are respected locally in `.vercel/project.json`
if (process.env.VERCEL_DEV_COMMAND) {
devCommand = process.env.VERCEL_DEV_COMMAND;
}
const devServer = new DevServer(cwd, {
output,
devCommand,

View File

@@ -3,6 +3,7 @@ export default new Map([
['aliases', 'alias'],
['billing', 'billing'],
['bisect', 'bisect'],
['build', 'build'],
['cc', 'billing'],
['cert', 'certs'],
['certs', 'certs'],

View File

@@ -15,6 +15,12 @@ import { getPkgName, getCommandName } from '../util/pkg-name.ts';
const help = () => {
console.log(`
${chalk.yellow(
`${chalk.bold('NOTE:')} The ${getCommandName(
'env'
)} command is recommended instead of ${getCommandName('secrets')}`
)}
${chalk.bold(`${logo} ${getPkgName()} secrets`)} [options] <command>
${chalk.dim('Commands:')}
@@ -64,7 +70,7 @@ const help = () => {
${chalk.gray('')} Paginate results, where ${chalk.dim(
'`1584722256178`'
)} is the time in milliseconds since the UNIX epoch.
)} is the time in milliseconds since the UNIX epoch
${chalk.cyan(`$ ${getPkgName()} secrets ls --next 1584722256178`)}
`);
@@ -130,8 +136,14 @@ async function run({ output, contextName, currentTeam, client }) {
const args = argv._.slice(1);
const start = Date.now();
const { 'test-warning': testWarningFlag } = argv;
const commandName = getCommandName('secret ' + subcommand);
if (subcommand === 'ls' || subcommand === 'list') {
output.note(
`The ${getCommandName(
'env ls'
)} command is recommended instead of ${commandName}\n`
);
if (args.length > 1) {
console.error(
error(
@@ -194,6 +206,11 @@ async function run({ output, contextName, currentTeam, client }) {
}
if (subcommand === 'rm' || subcommand === 'remove') {
output.note(
`The ${getCommandName(
'env rm'
)} command is recommended instead of ${commandName}\n`
);
if (args.length !== 1) {
console.error(
error(
@@ -236,6 +253,11 @@ async function run({ output, contextName, currentTeam, client }) {
}
if (subcommand === 'rename') {
output.note(
`The ${getCommandName('env rm')} and ${getCommandName(
'env add'
)} commands are recommended instead of ${commandName}\n`
);
if (args.length !== 2) {
console.error(
error(
@@ -259,6 +281,11 @@ async function run({ output, contextName, currentTeam, client }) {
}
if (subcommand === 'add' || subcommand === 'set') {
output.note(
`The ${getCommandName(
'env add'
)} command is recommended instead of ${commandName}\n`
);
if (args.length !== 2) {
console.error(
error(

View File

@@ -171,7 +171,7 @@ const main = async () => {
// * a subcommand (as in: `vercel ls`)
const targetOrSubcommand = argv._[2];
const betaCommands: string[] = [];
const betaCommands: string[] = ['build'];
if (betaCommands.includes(targetOrSubcommand)) {
console.log(
`${chalk.grey(
@@ -303,7 +303,14 @@ const main = async () => {
let authConfig = null;
const subcommandsWithoutToken = ['login', 'logout', 'help', 'init', 'update'];
const subcommandsWithoutToken = [
'login',
'logout',
'help',
'init',
'update',
'build',
];
if (authConfigExists) {
try {
@@ -524,6 +531,7 @@ const main = async () => {
typeof scope === 'string' &&
targetCommand !== 'login' &&
targetCommand !== 'dev' &&
targetCommand !== 'build' &&
!(targetCommand === 'teams' && argv._[3] !== 'invite')
) {
let user = null;
@@ -598,6 +606,9 @@ const main = async () => {
case 'bisect':
func = require('./commands/bisect').default;
break;
case 'build':
func = require('./commands/build').default;
break;
case 'certs':
func = require('./commands/certs').default;
break;

View File

@@ -251,9 +251,6 @@ export interface Project extends ProjectSettings {
updatedAt: number;
createdAt: number;
alias?: ProjectAliasTarget[];
devCommand?: string | null;
framework?: string | null;
rootDirectory?: string | null;
latestDeployments?: Partial<Deployment>[];
}

View File

@@ -0,0 +1,230 @@
import npa from 'npm-package-arg';
import { satisfies } from 'semver';
import { dirname, join } from 'path';
import { mkdirp, outputJSON, readJSON, symlink } from 'fs-extra';
import {
BuilderV2,
BuilderV3,
PackageJson,
spawnAsync,
} from '@vercel/build-utils';
import * as staticBuilder from './static-builder';
import { VERCEL_DIR } from '../projects/link';
import { Output } from '../output';
import readJSONFile from '../read-json-file';
import { CantParseJSONFile } from '../errors-ts';
export interface BuilderWithPkg {
path: string;
pkgPath: string;
builder: BuilderV2 | BuilderV3;
pkg: PackageJson;
}
type ResolveBuildersResult =
| { buildersToAdd: Set<string> }
| { builders: Map<string, BuilderWithPkg> };
/**
* Imports the specified Vercel Builders, installing any missing ones
* into `.vercel/builders` if necessary.
*/
export async function importBuilders(
builderSpecs: Set<string>,
cwd: string,
output: Output
): Promise<Map<string, BuilderWithPkg>> {
const buildersDir = join(cwd, VERCEL_DIR, 'builders');
let importResult = await resolveBuilders(buildersDir, builderSpecs, output);
if ('buildersToAdd' in importResult) {
const installResult = await installBuilders(
buildersDir,
importResult.buildersToAdd,
output
);
importResult = await resolveBuilders(
buildersDir,
builderSpecs,
output,
installResult.resolvedSpecs
);
if ('buildersToAdd' in importResult) {
throw new Error('Something went wrong!');
}
}
return importResult.builders;
}
export async function resolveBuilders(
buildersDir: string,
builderSpecs: Set<string>,
output: Output,
resolvedSpecs?: Map<string, string>
): Promise<ResolveBuildersResult> {
const builders = new Map<string, BuilderWithPkg>();
const buildersToAdd = new Set<string>();
for (const spec of builderSpecs) {
const resolvedSpec = resolvedSpecs?.get(spec) || spec;
const parsed = npa(resolvedSpec);
let { name } = parsed;
if (!name) {
// A URL was specified - will need to install it and resolve the
// proper package name from the written `package.json` file
buildersToAdd.add(spec);
continue;
}
if (name === '@vercel/static') {
// `@vercel/static` is a special-case built-in builder
builders.set(name, {
builder: staticBuilder,
pkg: { name },
path: '',
pkgPath: '',
});
continue;
}
try {
let pkgPath: string | undefined;
let builderPkg: PackageJson | undefined;
try {
// First try `.vercel/builders`. The package name should always be available
// at the top-level of `node_modules` since CLI is installing those directly.
pkgPath = join(buildersDir, 'node_modules', name, 'package.json');
builderPkg = await readJSON(pkgPath);
} catch (err: any) {
if (err?.code !== 'ENOENT') throw err;
// If `pkgPath` wasn't found in `.vercel/builders` then try as a CLI local
// dependency. `require.resolve()` will throw if the Builder is not a CLI
// dep, in which case we'll install it into `.vercel/builders`.
pkgPath = require.resolve(`${name}/package.json`, {
paths: [__dirname],
});
builderPkg = await readJSON(pkgPath);
}
if (!builderPkg || !pkgPath) {
throw new Error(`Failed to load \`package.json\` for "${name}"`);
}
if (typeof builderPkg.version !== 'string') {
throw new Error(
`\`package.json\` for "${name}" does not contain a "version" field`
);
}
if (parsed.type === 'version' && parsed.rawSpec !== builderPkg.version) {
// An explicit Builder version was specified but it does
// not match the version that is currently installed
output.debug(
`Installed version "${name}@${builderPkg.version}" does not match "${parsed.rawSpec}"`
);
buildersToAdd.add(spec);
continue;
}
if (
parsed.type === 'range' &&
!satisfies(builderPkg.version, parsed.rawSpec)
) {
// An explicit Builder range was specified but it is not
// compatible with the version that is currently installed
output.debug(
`Installed version "${name}@${builderPkg.version}" is not compatible with "${parsed.rawSpec}"`
);
buildersToAdd.add(spec);
continue;
}
// TODO: handle `parsed.type === 'tag'` ("latest" vs. anything else?)
const path = join(dirname(pkgPath), builderPkg.main || 'index.js');
const builder = require(path);
builders.set(spec, {
builder,
pkg: builderPkg,
path,
pkgPath,
});
output.debug(`Imported Builder "${name}" from "${dirname(pkgPath)}"`);
} catch (err: any) {
// `resolvedSpecs` is only passed into this function on the 2nd run,
// so if MODULE_NOT_FOUND happens in that case then we don't want to
// try to install again. Instead just pass through the error to the user
if (err.code === 'MODULE_NOT_FOUND' && !resolvedSpecs) {
output.debug(`Failed to import "${name}": ${err}`);
buildersToAdd.add(spec);
} else {
err.message = `Importing "${name}": ${err.message}`;
throw err;
}
}
}
// Add any Builders that are not yet present into `.vercel/builders`
if (buildersToAdd.size > 0) {
return { buildersToAdd };
}
return { builders };
}
async function installBuilders(
buildersDir: string,
buildersToAdd: Set<string>,
output: Output
) {
const resolvedSpecs = new Map<string, string>();
const buildersPkgPath = join(buildersDir, 'package.json');
try {
const emptyPkgJson = {
private: true,
license: 'UNLICENSED',
};
await outputJSON(buildersPkgPath, emptyPkgJson, {
flag: 'wx',
});
} catch (err: any) {
if (err.code !== 'EEXIST') throw err;
}
output.debug(`Installing Builders: ${Array.from(buildersToAdd).join(', ')}`);
await spawnAsync('yarn', ['add', '@vercel/build-utils', ...buildersToAdd], {
cwd: buildersDir,
});
// Symlink `@now/build-utils` -> `@vercel/build-utils` to support legacy Builders
const nowScopePath = join(buildersDir, 'node_modules/@now');
await mkdirp(nowScopePath);
await symlink('../@vercel/build-utils', join(nowScopePath, 'build-utils'));
// Cross-reference any builderSpecs from the saved `package.json` file,
// in case they were installed from a URL
const buildersPkg = await readJSONFile<PackageJson>(buildersPkgPath);
if (buildersPkg instanceof CantParseJSONFile) throw buildersPkg;
if (!buildersPkg) {
throw new Error(`Failed to load "${buildersPkgPath}"`);
}
for (const spec of buildersToAdd) {
for (const [name, version] of Object.entries(
buildersPkg.dependencies || {}
)) {
if (version === spec) {
output.debug(`Resolved Builder spec "${spec}" to name "${name}"`);
resolvedSpecs.set(spec, name);
}
}
}
return { resolvedSpecs };
}

View File

@@ -0,0 +1,41 @@
import minimatch from 'minimatch';
import { BuildV2, Files } from '@vercel/build-utils';
export const version = 2;
export const build: BuildV2 = async ({ entrypoint, files, config }) => {
const output: Files = {};
const outputDirectory = config.zeroConfig ? config.outputDirectory : '';
for (let [filename, fileFsRef] of Object.entries(files)) {
if (
filename.startsWith('.git/') ||
filename === 'vercel.json' ||
filename === 'now.json'
) {
continue;
}
if (
entrypoint &&
!(
entrypoint === filename ||
minimatch(filename, entrypoint, { dot: true })
)
) {
continue;
}
if (outputDirectory) {
const outputMatch = outputDirectory + '/';
if (filename.startsWith(outputMatch)) {
// static output files are moved to the root directory
filename = filename.slice(outputMatch.length);
}
}
output[filename] = fileFsRef;
}
return { output };
};

View File

@@ -0,0 +1,108 @@
/**
* Code modified from `extract-zip` to accept Buffer.
* https://github.com/maxogden/extract-zip/blob/master/index.js
* BSD-2 Copyright (c) 2014 Max Ogden and other contributors
*/
import path from 'path';
import pipe from 'promisepipe';
import * as fs from 'fs-extra';
import { streamToBuffer } from '@vercel/build-utils';
import { Entry, ZipFile, fromBuffer as zipFromBuffer } from 'yauzl-promise';
async function* createZipIterator(zipFile: ZipFile) {
let entry: Entry;
while ((entry = await zipFile.readEntry()) !== null) {
yield entry;
}
}
export async function unzip(buffer: Buffer, dir: string): Promise<void> {
const zipFile = await zipFromBuffer(buffer);
for await (const entry of createZipIterator(zipFile)) {
if (entry.fileName.startsWith('__MACOSX/')) continue;
try {
const destDir = path.dirname(path.join(dir, entry.fileName));
await fs.mkdirp(destDir);
const canonicalDestDir = await fs.realpath(destDir);
const relativeDestDir = path.relative(dir, canonicalDestDir);
if (relativeDestDir.split(path.sep).includes('..')) {
throw new Error(
`Out of bound path "${canonicalDestDir}" found while processing file ${entry.fileName}`
);
}
await extractEntry(zipFile, entry, dir);
} catch (err) {
await zipFile.close();
throw err;
}
}
}
async function extractEntry(
zipFile: ZipFile,
entry: Entry,
dir: string
): Promise<void> {
const dest = path.join(dir, entry.fileName);
// convert external file attr int into a fs stat mode int
const mode = (entry.externalFileAttributes >> 16) & 0xffff;
// check if it's a symlink or dir (using stat mode constants)
const IFMT = 61440;
const IFDIR = 16384;
const IFLNK = 40960;
const symlink = (mode & IFMT) === IFLNK;
let isDir = (mode & IFMT) === IFDIR;
// Failsafe, borrowed from jsZip
if (!isDir && entry.fileName.endsWith('/')) {
isDir = true;
}
// check for windows weird way of specifying a directory
// https://github.com/maxogden/extract-zip/issues/13#issuecomment-154494566
const madeBy = entry.versionMadeBy >> 8;
if (!isDir) isDir = madeBy === 0 && entry.externalFileAttributes === 16;
const procMode = getExtractedMode(mode, isDir) & 0o777;
// always ensure folders are created
const destDir = isDir ? dest : path.dirname(dest);
const mkdirOptions = { recursive: true };
if (isDir) {
// @ts-ignore
mkdirOptions.mode = procMode;
}
await fs.mkdir(destDir, mkdirOptions);
if (isDir) return;
const readStream = await zipFile.openReadStream(entry);
if (symlink) {
const link = await streamToBuffer(readStream);
await fs.symlink(link.toString('utf8'), dest);
} else {
await pipe(readStream, fs.createWriteStream(dest, { mode: procMode }));
}
}
function getExtractedMode(entryMode: number, isDir: boolean): number {
let mode = entryMode;
// Set defaults, if necessary
if (mode === 0) {
if (isDir) {
mode = 0o755;
} else {
mode = 0o644;
}
}
return mode;
}

View File

@@ -0,0 +1,373 @@
import fs from 'fs-extra';
import mimeTypes from 'mime-types';
import { basename, dirname, extname, join, relative, resolve } from 'path';
import {
Builder,
BuildResultV2,
BuildResultV3,
File,
FileFsRef,
BuilderV2,
BuilderV3,
Lambda,
PackageJson,
Prerender,
download,
EdgeFunction,
BuildResultBuildOutput,
} from '@vercel/build-utils';
import pipe from 'promisepipe';
import { unzip } from './unzip';
import { VERCEL_DIR } from '../projects/link';
export const OUTPUT_DIR = join(VERCEL_DIR, 'output');
export async function writeBuildResult(
buildResult: BuildResultV2 | BuildResultV3,
build: Builder,
builder: BuilderV2 | BuilderV3,
builderPkg: PackageJson,
cleanUrls?: boolean
) {
const { version } = builder;
if (version === 2) {
return writeBuildResultV2(buildResult as BuildResultV2, cleanUrls);
} else if (version === 3) {
return writeBuildResultV3(buildResult as BuildResultV3, build);
}
throw new Error(
`Unsupported Builder version \`${version}\` from "${builderPkg.name}"`
);
}
function isEdgeFunction(v: any): v is EdgeFunction {
return v?.type === 'EdgeFunction';
}
function isLambda(v: any): v is Lambda {
return v?.type === 'Lambda';
}
function isPrerender(v: any): v is Prerender {
return v?.type === 'Prerender';
}
function isFile(v: any): v is File {
const type = v?.type;
return type === 'FileRef' || type === 'FileFsRef' || type === 'FileBlob';
}
export interface PathOverride {
contentType?: string;
path?: string;
}
/**
* Writes the output from the `build()` return value of a v2 Builder to
* the filesystem.
*/
async function writeBuildResultV2(
buildResult: BuildResultV2,
cleanUrls?: boolean
) {
if ('buildOutputPath' in buildResult) {
await mergeBuilderOutput(buildResult);
return;
}
const lambdas = new Map<Lambda, string>();
const overrides: Record<string, PathOverride> = {};
for (const [path, output] of Object.entries(buildResult.output)) {
if (isLambda(output)) {
await writeLambda(output, path, lambdas);
} else if (isPrerender(output)) {
await writeLambda(output.lambda, path, lambdas);
// Write the fallback file alongside the Lambda directory
let fallback = output.fallback;
if (fallback) {
const ext = getFileExtension(fallback);
const fallbackName = `${path}.prerender-fallback${ext}`;
const fallbackPath = join(OUTPUT_DIR, 'functions', fallbackName);
const stream = fallback.toStream();
await pipe(
stream,
fs.createWriteStream(fallbackPath, { mode: fallback.mode })
);
fallback = new FileFsRef({
...output.fallback,
fsPath: basename(fallbackName),
});
}
const prerenderConfigPath = join(
OUTPUT_DIR,
'functions',
`${path}.prerender-config.json`
);
const prerenderConfig = {
...output,
lambda: undefined,
fallback,
};
await fs.writeJSON(prerenderConfigPath, prerenderConfig, { spaces: 2 });
} else if (isFile(output)) {
await writeStaticFile(output, path, overrides, cleanUrls);
} else if (isEdgeFunction(output)) {
await writeEdgeFunction(output, path);
} else {
throw new Error(
`Unsupported output type: "${(output as any).type}" for ${path}`
);
}
}
return Object.keys(overrides).length > 0 ? overrides : undefined;
}
/**
* Writes the output from the `build()` return value of a v3 Builder to
* the filesystem.
*/
async function writeBuildResultV3(buildResult: BuildResultV3, build: Builder) {
const { output } = buildResult;
if (isLambda(output)) {
const src = build.src!;
const ext = extname(src);
const path = build.config?.zeroConfig
? src.substring(0, src.length - ext.length)
: src;
await writeLambda(output, path);
} else {
throw new Error(
`Unsupported output type: "${(output as any).type}" for ${build.src}`
);
}
}
/**
* Writes a static `File` instance to the file system in the "static" directory.
* If the filename does not have a file extension then one attempts to be inferred
* from the extension of the `fsPath`.
*
* @param file The `File` instance to write
* @param path The URL path where the `File` can be accessed from
* @param overrides Record of override configuration when a File is renamed or has other metadata
*/
async function writeStaticFile(
file: File,
path: string,
overrides: Record<string, PathOverride>,
cleanUrls = false
) {
let fsPath = path;
let override: PathOverride | null = null;
// If the output path doesn't match the determined file extension of
// the File then add the extension. This is to help avoid conflicts
// where an output path matches a directory name of another output path
// (i.e. `blog` -> `blog.html` and `blog/hello` -> `blog/hello.html`)
const ext = getFileExtension(file);
if (ext && extname(path) !== ext) {
fsPath += ext;
if (!override) override = {};
override.path = path;
}
// If `cleanUrls` is true then remove the `.html` file extension
// for HTML files.
if (cleanUrls && path.endsWith('.html')) {
if (!override) override = {};
override.path = path.slice(0, -5);
}
// Ensure an explicit "content-type" on the `File` is returned in
// the final output asset.
if (file.contentType) {
if (!override) override = {};
override.contentType = file.contentType;
}
if (override) {
overrides[fsPath] = override;
}
const dest = join(OUTPUT_DIR, 'static', fsPath);
await fs.mkdirp(dirname(dest));
// TODO: handle (or skip) symlinks?
const stream = file.toStream();
await pipe(stream, fs.createWriteStream(dest, { mode: file.mode }));
}
/**
* Serializes the `EdgeFunction` instance to the file system.
*
* @param edgeFunction The `EdgeFunction` instance
* @param path The URL path where the `EdgeFunction` can be accessed from
*/
async function writeEdgeFunction(edgeFunction: EdgeFunction, path: string) {
const dest = join(OUTPUT_DIR, 'functions', `${path}.func`);
await fs.mkdirp(dest);
const ops: Promise<any>[] = [];
ops.push(download(edgeFunction.files, dest));
const config = {
runtime: 'edge',
...edgeFunction,
files: undefined,
type: undefined,
};
const configPath = join(dest, '.vc-config.json');
ops.push(
fs.writeJSON(configPath, config, {
spaces: 2,
})
);
await Promise.all(ops);
}
/**
* Writes the file references from the `Lambda` instance to the file system.
*
* @param lambda The `Lambda` instance
* @param path The URL path where the `Lambda` can be accessed from
* @param lambdas (optional) Map of `Lambda` instances that have previously been written
*/
async function writeLambda(
lambda: Lambda,
path: string,
lambdas?: Map<Lambda, string>
) {
const dest = join(OUTPUT_DIR, 'functions', `${path}.func`);
// If the `lambda` has already been written to the filesystem at a different
// location then create a symlink to the previous location instead of copying
// the files again.
const existingLambdaPath = lambdas?.get(lambda);
if (existingLambdaPath) {
const destDir = dirname(dest);
const targetDest = join(
OUTPUT_DIR,
'functions',
`${existingLambdaPath}.func`
);
const target = relative(destDir, targetDest);
await fs.mkdirp(destDir);
await fs.symlink(target, dest);
return;
}
lambdas?.set(lambda, path);
await fs.mkdirp(dest);
const ops: Promise<any>[] = [];
if (lambda.files) {
// `files` is defined
ops.push(download(lambda.files, dest));
} else if (lambda.zipBuffer) {
// Builders that use the deprecated `createLambda()` might only have `zipBuffer`
ops.push(unzip(lambda.zipBuffer, dest));
} else {
throw new Error('Malformed `Lambda` - no "files" present');
}
const config = {
...lambda,
type: undefined,
files: undefined,
zipBuffer: undefined,
};
const configPath = join(dest, '.vc-config.json');
ops.push(
fs.writeJSON(configPath, config, {
spaces: 2,
})
);
await Promise.all(ops);
// XXX: remove any `.vercel/builders` directories that were
// extracted into the `dest` dir. This is a temporary band-aid
// to make `vercel-php` work since it is inadvertently including
// `.vercel/builders` into the Lambda files due to glob syntax.
// See: https://github.com/juicyfx/vercel-php/pull/232
for await (const dir of findDirs('.vercel', dest)) {
const absDir = join(dest, dir);
const entries = await fs.readdir(absDir);
if (entries.includes('cache')) {
// Delete everything except for "cache"
await Promise.all(
entries
.filter(e => e !== 'cache')
.map(entry => fs.remove(join(absDir, entry)))
);
} else {
// Delete the entire `.vercel` directory
await fs.remove(absDir);
}
}
}
/**
* When the Root Directory setting is utilized, merge the contents of the
* `.vercel/output` directory that was specified by the Builder into the
* `vc build` output directory.
*/
async function mergeBuilderOutput(buildResult: BuildResultBuildOutput) {
const absOutputDir = resolve(OUTPUT_DIR);
if (absOutputDir === buildResult.buildOutputPath) {
// `.vercel/output` dir is already in the correct location,
// so no need to do anything
return;
}
await fs.copy(buildResult.buildOutputPath, OUTPUT_DIR);
}
/**
* Attempts to return the file extension (i.e. `.html`) from the given
* `File` instance, based on its actual filesystem path and/or the
* "content-type" of the File.
*/
function getFileExtension(file: File): string {
let ext = '';
if (file.type === 'FileFsRef') {
ext = extname(file.fsPath);
}
if (!ext && file.contentType) {
const e = mimeTypes.extension(file.contentType);
if (e) {
ext = `.${e}`;
}
}
return ext;
}
/**
* Creates an async iterator that scans a directory
* for sub-directories with the matching `name`.
*/
export async function* findDirs(
name: string,
dir: string,
root = dir
): AsyncIterable<string> {
let paths: string[];
try {
paths = await fs.readdir(dir);
} catch (err: any) {
if (err.code !== 'ENOENT') {
throw err;
}
paths = [];
}
for (const path of paths) {
const abs = join(dir, path);
const s = await fs.stat(abs);
if (s.isDirectory()) {
if (path === name) {
yield relative(root, abs);
} else {
yield* findDirs(name, abs, root);
}
}
}
}

View File

@@ -1040,7 +1040,12 @@ export default class DevServer {
const { debug } = this.output;
debug(`Killing builder dev server with PID ${pid}`);
this.devServerPids.delete(pid);
await treeKill(pid);
try {
process.kill(pid, 'SIGTERM');
debug(`Killed builder dev server with PID ${pid}`);
} catch (err) {
debug(`Failed to kill builder dev server with PID ${pid}: ${err}`);
}
}
async send404(

View File

@@ -2,6 +2,16 @@ import title from 'title';
import pkg from './pkg';
import cmd from './output/cmd';
/**
* The package name defined in the CLI's `package.json` file (`vercel`).
*/
export const name = pkg.name;
/**
* Unicode symbol used to represent the CLI.
*/
export const logo = '▲';
/**
* Returns the package name such as `vercel` or `now`.
*/

View File

@@ -5,12 +5,14 @@ import { join } from 'path';
export type ProjectLinkAndSettings = ProjectLink & {
settings: {
installCommand: Project['installCommand'];
buildCommand: Project['buildCommand'];
devCommand: Project['devCommand'];
outputDirectory: Project['outputDirectory'];
directoryListing: Project['directoryListing'];
rootDirectory: Project['rootDirectory'];
framework: Project['framework'];
nodeVersion: Project['nodeVersion'];
};
};
@@ -22,20 +24,22 @@ export async function writeProjectSettings(
project: Project,
org: Org
) {
const data = {
const projectLinkAndSettings: ProjectLinkAndSettings = {
projectId: project.id,
orgId: org.id,
settings: {
buildCommand: project.buildCommand,
devCommand: project.devCommand,
outputDirectory: project.outputDirectory,
directoryListing: project.directoryListing,
rootDirectory: project.rootDirectory,
framework: project.framework,
devCommand: project.devCommand,
installCommand: project.installCommand,
buildCommand: project.buildCommand,
outputDirectory: project.outputDirectory,
rootDirectory: project.rootDirectory,
directoryListing: project.directoryListing,
nodeVersion: project.nodeVersion,
},
};
const path = join(cwd, VERCEL_DIR, VERCEL_DIR_PROJECT);
return await outputJSON(path, data, {
return await outputJSON(path, projectLinkAndSettings, {
spaces: 2,
});
}

View File

@@ -0,0 +1 @@
export default () => <div>hello, this is the frontend</div>;

Some files were not shown because too many files have changed in this diff Show More