Compare commits
111 Commits
@vercel/py
...
@vercel/py
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6700630feb | ||
|
|
34cd8b4144 | ||
|
|
ad0ed6d852 | ||
|
|
0bad09b47a | ||
|
|
5120689bf2 | ||
|
|
5a39fd9242 | ||
|
|
352cd00ef0 | ||
|
|
abfe817f86 | ||
|
|
ebb5e2b208 | ||
|
|
e34858d082 | ||
|
|
f03c947f91 | ||
|
|
0d13fe7e34 | ||
|
|
4afec9d373 | ||
|
|
09c85f63d2 | ||
|
|
9963965e9a | ||
|
|
f3ed279007 | ||
|
|
4fe489edad | ||
|
|
e2911aac0b | ||
|
|
f3cbc5d746 | ||
|
|
40df88b483 | ||
|
|
75c4f45b73 | ||
|
|
05a236f944 | ||
|
|
4b7383f521 | ||
|
|
c263c31e48 | ||
|
|
c80530f9b1 | ||
|
|
16fd4396ef | ||
|
|
4e7138f400 | ||
|
|
5273279cf1 | ||
|
|
37290039d6 | ||
|
|
b52d01f809 | ||
|
|
ffefaf82a1 | ||
|
|
6d8dbfc7d6 | ||
|
|
551cd7f688 | ||
|
|
2dfb6b45cd | ||
|
|
65ae2a289e | ||
|
|
72ea3532b1 | ||
|
|
78fac00823 | ||
|
|
9e255afa37 | ||
|
|
e4be68270f | ||
|
|
9c636dc1ba | ||
|
|
c98c9996bf | ||
|
|
0fcf172a10 | ||
|
|
99e5c4a6db | ||
|
|
b8269b0111 | ||
|
|
decac0fe3f | ||
|
|
591d1686d0 | ||
|
|
5e1d5c921c | ||
|
|
603b1256c6 | ||
|
|
6957c72828 | ||
|
|
9be3650cb7 | ||
|
|
6e1ee7a7d6 | ||
|
|
767ce2cff1 | ||
|
|
bb1d0ce1b7 | ||
|
|
31f79c7de1 | ||
|
|
4c230c8436 | ||
|
|
7941f5a104 | ||
|
|
5b931afbf3 | ||
|
|
15080364b8 | ||
|
|
47e3381c6d | ||
|
|
33aefdc029 | ||
|
|
30fe76a0cf | ||
|
|
97ef88dc28 | ||
|
|
f679098d7a | ||
|
|
2b57e12ad3 | ||
|
|
c4e94ad03f | ||
|
|
32afd67d29 | ||
|
|
7523e39f18 | ||
|
|
99f2f2f1ba | ||
|
|
63830d38ce | ||
|
|
f3428dd212 | ||
|
|
5eb8b16cbd | ||
|
|
226bf02be2 | ||
|
|
8505872f55 | ||
|
|
7db6436797 | ||
|
|
e2d76e9c92 | ||
|
|
337cb21d67 | ||
|
|
6bfff3e9eb | ||
|
|
ac5b259c11 | ||
|
|
bfc553db11 | ||
|
|
2b101d4692 | ||
|
|
3316f38cb4 | ||
|
|
7837387127 | ||
|
|
f478200dd3 | ||
|
|
c29de8206a | ||
|
|
a2df3b5463 | ||
|
|
73446e544a | ||
|
|
21ff4a58c3 | ||
|
|
2b9eb02b8c | ||
|
|
4ef4722460 | ||
|
|
be5308b137 | ||
|
|
08a83a94f8 | ||
|
|
543ffdfe5c | ||
|
|
c11527e904 | ||
|
|
d296064386 | ||
|
|
400a6c42bd | ||
|
|
71b3ded398 | ||
|
|
fc3fa61b59 | ||
|
|
1f98c4fee7 | ||
|
|
1cb5a91727 | ||
|
|
e8c7db59cf | ||
|
|
57b230e25f | ||
|
|
ab3fb25790 | ||
|
|
88d98f7497 | ||
|
|
90c1895949 | ||
|
|
46a1f3670b | ||
|
|
4b025fee92 | ||
|
|
dc8293dc13 | ||
|
|
78883dea23 | ||
|
|
b5b792e42f | ||
|
|
8993a3c4af | ||
|
|
57241aad81 |
25
.github/workflows/cron-update-next.yml
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
name: Cron Update Next
|
||||
|
||||
on:
|
||||
# Run every 4 hours https://crontab.guru/every-4-hours
|
||||
schedule:
|
||||
- cron: '0 */4 * * *'
|
||||
|
||||
jobs:
|
||||
create-pull-request:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
# 0 means fetch all commits so we can commit and push in the script below
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Create Pull Request
|
||||
uses: actions/github-script@v6
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
# See https://github.com/actions/github-script#run-a-separate-file-with-an-async-function
|
||||
with:
|
||||
script: |
|
||||
const script = require('./utils/update-next.js')
|
||||
await script({ github, context })
|
||||
4
.github/workflows/publish.yml
vendored
@@ -26,12 +26,12 @@ jobs:
|
||||
fi
|
||||
- name: Setup Go
|
||||
if: ${{ steps.check-release.outputs.IS_RELEASE == 'true' }}
|
||||
uses: actions/setup-go@v2
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: '1.13.15'
|
||||
- name: Setup Node
|
||||
if: ${{ steps.check-release.outputs.IS_RELEASE == 'true' }}
|
||||
uses: actions/setup-node@v2
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 14
|
||||
- name: Install
|
||||
|
||||
8
.github/workflows/test-integration-cli.yml
vendored
@@ -25,20 +25,20 @@ jobs:
|
||||
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
|
||||
- uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: '1.13.15'
|
||||
- uses: actions/setup-node@v2
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 100
|
||||
- run: git --version
|
||||
- run: git fetch origin main --depth=100
|
||||
- run: git fetch origin ${{ github.ref }} --depth=100
|
||||
- run: git diff origin/main...HEAD --name-only
|
||||
- run: yarn install --network-timeout 1000000
|
||||
- run: yarn install --network-timeout 1000000 --frozen-lockfile
|
||||
- run: yarn run build
|
||||
- run: yarn test-integration-cli
|
||||
env:
|
||||
|
||||
8
.github/workflows/test-unit.yml
vendored
@@ -25,20 +25,20 @@ jobs:
|
||||
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
|
||||
- uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: '1.13.15'
|
||||
- uses: actions/setup-node@v2
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 100
|
||||
- run: git --version
|
||||
- run: git fetch origin main --depth=100
|
||||
- run: git fetch origin ${{ github.ref }} --depth=100
|
||||
- run: git diff origin/main...HEAD --name-only
|
||||
- run: yarn install --network-timeout 1000000
|
||||
- run: yarn install --network-timeout 1000000 --frozen-lockfile
|
||||
- run: yarn run build
|
||||
- run: yarn run lint
|
||||
if: matrix.os == 'ubuntu-latest' && matrix.node == 14 # only run lint once
|
||||
|
||||
22
.github/workflows/test.yml
vendored
@@ -17,21 +17,28 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
tests: ${{ steps['set-tests'].outputs['tests'] }}
|
||||
dplUrl: ${{ steps.waitForTarball.outputs.url }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- run: git --version
|
||||
- run: git fetch origin main
|
||||
- uses: actions/setup-node@v2
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: 'yarn'
|
||||
- run: yarn install --network-timeout 1000000
|
||||
- run: yarn install --network-timeout 1000000 --frozen-lockfile
|
||||
- id: set-tests
|
||||
run: |
|
||||
TESTS_ARRAY=$(node utils/chunk-tests.js $SCRIPT_NAME)
|
||||
echo "Files to test:"
|
||||
echo "$TESTS_ARRAY"
|
||||
echo "::set-output name=tests::$TESTS_ARRAY"
|
||||
- uses: patrickedqvist/wait-for-vercel-preview@ae34b392ef30297f2b672f9afb3c329bde9bd487
|
||||
id: waitForTarball
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
max_timeout: 360
|
||||
check_interval: 5
|
||||
|
||||
test:
|
||||
timeout-minutes: 120
|
||||
@@ -51,13 +58,13 @@ jobs:
|
||||
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
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 2
|
||||
- uses: actions/setup-go@v2
|
||||
- uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: '1.13.15'
|
||||
- uses: actions/setup-node@v2
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: 'yarn'
|
||||
@@ -69,13 +76,14 @@ jobs:
|
||||
- run: yarn install --network-timeout 1000000
|
||||
|
||||
- name: Build ${{matrix.packageName}} and all its dependencies
|
||||
run: yarn turbo run build --cache-dir=".turbo" --scope=${{matrix.packageName}} --include-dependencies --no-deps
|
||||
run: node_modules/.bin/turbo run build --cache-dir=".turbo" --scope=${{matrix.packageName}} --include-dependencies --no-deps
|
||||
env:
|
||||
FORCE_COLOR: '1'
|
||||
- name: Test ${{matrix.packageName}}
|
||||
run: node_modules/.bin/turbo run test --cache-dir=".turbo" --scope=${{matrix.packageName}} --no-deps -- ${{ join(matrix.testPaths, ' ') }}
|
||||
shell: bash
|
||||
env:
|
||||
VERCEL_CLI_VERSION: ${{ needs.setup.outputs.dplUrl }}/tarballs/vercel.tgz
|
||||
VERCEL_TEAM_TOKEN: ${{ secrets.VERCEL_TEAM_TOKEN }}
|
||||
VERCEL_REGISTRATION_URL: ${{ secrets.VERCEL_REGISTRATION_URL }}
|
||||
FORCE_COLOR: '1'
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
# Runtime Developer Reference
|
||||
|
||||
The following page is a reference for how to create a Runtime by implementing
|
||||
the Runtime API interface.
|
||||
the Runtime API interface. It's a way to add support for a new programming language to Vercel.
|
||||
|
||||
> Note: If you're the author of a web framework, please use the [Build Output API](https://vercel.com/docs/build-output-api/v3) instead to make your framework compatible with Vercel.
|
||||
|
||||
A Runtime is an npm module that implements the following interface:
|
||||
|
||||
@@ -61,9 +63,6 @@ export async function build(options: BuildOptions) {
|
||||
const lambda = createLambda(/* … */);
|
||||
return {
|
||||
output: lambda,
|
||||
watch: [
|
||||
// Dependent files to trigger a rebuild in `vercel dev` go here…
|
||||
],
|
||||
routes: [
|
||||
// If your Runtime needs to define additional routing, define it here…
|
||||
],
|
||||
@@ -113,7 +112,8 @@ export async function shouldServe(options: ShouldServeOptions) {
|
||||
}
|
||||
```
|
||||
|
||||
If this function is not defined, Vercel CLI will use the [default implementation](https://github.com/vercel/vercel/blob/52994bfe26c5f4f179bdb49783ee57ce19334631/packages/now-build-utils/src/should-serve.ts).
|
||||
If this function is not defined, Vercel CLI will use the [default
|
||||
implementation](https://github.com/vercel/vercel/blob/52994bfe26c5f4f179bdb49783ee57ce19334631/packages/now-build-utils/src/should-serve.ts).
|
||||
|
||||
### `startDevServer()`
|
||||
|
||||
@@ -187,7 +187,8 @@ If you need to share state between those steps, use the filesystem.
|
||||
|
||||
### Directory and Cache Lifecycle
|
||||
|
||||
When a new build is created, we pre-populate the `workPath` supplied to `analyze` with the results of the `prepareCache` step of the previous build.
|
||||
When a new build is created, we pre-populate the `workPath` supplied to `analyze` with the results of the `prepareCache` step of the
|
||||
previous build.
|
||||
|
||||
The `analyze` step can modify that directory, and it will not be re-created when it's supplied to `build` and `prepareCache`.
|
||||
|
||||
@@ -195,6 +196,77 @@ The `analyze` step can modify that directory, and it will not be re-created when
|
||||
|
||||
The env and secrets specified by the user as `build.env` are passed to the Runtime process. This means you can access user env via `process.env` in Node.js.
|
||||
|
||||
### Supporting Large Environment
|
||||
|
||||
We provide the ability to support more than 4KB of environment (up to 64KB) by way of
|
||||
a Lambda runtime wrapper that is added to every Lambda function we create. These are
|
||||
supported by many of the existing Lambda runtimes, but custom runtimes may require
|
||||
additional work.
|
||||
|
||||
The following Lambda runtime families have built-in support for the runtime wrapper:
|
||||
|
||||
- `nodejs`
|
||||
- `python` (>= 3.8)
|
||||
- `ruby`
|
||||
- `java11`
|
||||
- `java8.al2` (not `java8`)
|
||||
- `dotnetcore`
|
||||
|
||||
If a custom runtime is based on one of these Lambda runtimes, large environment
|
||||
support will be available without further configuration. Custom runtimes based on
|
||||
other Lambda runtimes, including those that provide the runtime via `provided` and
|
||||
`provided.al2`, must implement runtime wrapper support and indicate it via the
|
||||
`supportsWrapper` flag when calling [`createLambda`](#createlambda()).
|
||||
|
||||
To add support for runtime wrappers to a custom runtime, first check the value of the
|
||||
`AWS_LAMBDA_EXEC_WRAPPER` environment variable in the bootstrap script. Its value is
|
||||
the path to the wrapper executable.
|
||||
|
||||
The wrapper must be passed the path to the runtime as well as any parameters that the
|
||||
runtime requires. This is most easily done in a small `bootstrap` script.
|
||||
|
||||
In this simple `bash` example, the runtime is called directly if
|
||||
`AWS_LAMBDA_EXEC_WRAPPER` has no value, otherwise the wrapper is called with the
|
||||
runtime command as parameters.
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
|
||||
exec $AWS_LAMBDA_EXEC_WRAPPER path/to/runtime param1 param2
|
||||
```
|
||||
|
||||
If the `bootstrap` file is not a launcher script, but the entrypoint of the runtime
|
||||
itself, replace the bootstrap process with the wrapper. Pass the path and parameters
|
||||
of the executing file, ensuring the `AWS_LAMBDA_EXEC_WRAPPER` environment variable is
|
||||
set to blank.
|
||||
|
||||
This `bash` example uses `exec` to replace the running bootstrap process with the
|
||||
wrapper, passing its own path and parameters.
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
|
||||
if [[ -n $AWS_LAMBDA_EXEC_WRAPPER ]]
|
||||
__WRAPPER=$AWS_LAMBDA_EXEC_WRAPPER
|
||||
AWS_LAMBDA_EXEC_WRAPPER=""
|
||||
exec $__WRAPPER "$0" "${@}"
|
||||
fi
|
||||
|
||||
# start the actual runtime functionality
|
||||
```
|
||||
|
||||
Note that unsetting the variable may not have the desired effect due to the way
|
||||
Lambda spawns runtime processes. It is better to explicitly set it to blank.
|
||||
|
||||
The best way to replace the existing bootstrap process is with the
|
||||
[`execve`](https://www.man7.org/linux/man-pages/man2/execve.2.html) syscall.
|
||||
This is achieved by using `exec` in `bash` to replace the running process with the wrapper,
|
||||
maintaining the same PID and environment.
|
||||
|
||||
Once support for runtime wrappers is included, ensure `supportsWrapper` is set to
|
||||
`true` in the call to [`createLambda`](#createlambda()). This will inform the build
|
||||
process to enable large environment support for this runtime.
|
||||
|
||||
### Utilities as peerDependencies
|
||||
|
||||
When you publish your Runtime to npm, make sure to not specify `@vercel/build-utils` (as seen below in the API definitions) as a dependency, but rather as part of `peerDependencies`.
|
||||
@@ -302,6 +374,7 @@ This is a [class](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refere
|
||||
- `handler: String` path to handler file and (optionally) a function name it exports
|
||||
- `runtime: LambdaRuntime` the name of the lambda runtime
|
||||
- `environment: Object` key-value map of handler-related (aside of those passed by user) environment variables
|
||||
- `supportsWrapper: Boolean` set to true to indicate that Lambda runtime wrappers are supported by this runtime
|
||||
|
||||
### `LambdaRuntime`
|
||||
|
||||
|
||||
@@ -35,6 +35,6 @@ For details on how to use Vercel, check out our [documentation](https://vercel.c
|
||||
|
||||
## Contributing
|
||||
|
||||
- [Code of Conduct](https://github.com/vercel/vercel/blob/main/.github/CODE_OF_CONDUCT.md)
|
||||
- [Contributing Guidelines](https://github.com/vercel/vercel/blob/main/.github/CONTRIBUTING.md)
|
||||
- [MIT License](https://github.com/vercel/vercel/blob/main/LICENSE)
|
||||
- [Code of Conduct](./.github/CODE_OF_CONDUCT.md)
|
||||
- [Contributing Guidelines](./.github/CONTRIBUTING.md)
|
||||
- [MIT License](./LICENSE)
|
||||
|
||||
@@ -5,8 +5,7 @@
|
||||
"description": "API for the vercel/vercel repo",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"//TODO": "We should add this pkg to yarn workspaces",
|
||||
"vercel-build": "cd .. && yarn install && yarn vercel-build"
|
||||
"//TODO": "We should add this pkg to yarn workspaces"
|
||||
},
|
||||
"dependencies": {
|
||||
"@sentry/node": "5.11.1",
|
||||
|
||||
1
examples/astro/.gitignore
vendored
@@ -18,3 +18,4 @@ pnpm-debug.log*
|
||||
|
||||
# macOS-specific files
|
||||
.DS_Store
|
||||
.vercel
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
README.md
|
||||
@@ -1,10 +1,16 @@
|
||||
# Welcome to [Astro](https://astro.build)
|
||||
# Astro
|
||||
|
||||
[](https://stackblitz.com/github/withastro/astro/tree/latest/examples/starter)
|
||||
This directory is a brief example of an [Astro](https://astro.build/) site that can be deployed to Vercel with zero configuration.
|
||||
|
||||
> 🧑🚀 **Seasoned astronaut?** Delete this file. Have fun!
|
||||
## Deploy Your Own
|
||||
|
||||
## 🚀 Project Structure
|
||||
Deploy your own Astro project with Vercel.
|
||||
|
||||
[](https://vercel.com/new/clone?repository-url=https://github.com/vercel/vercel/tree/main/examples/astro&template=astro)
|
||||
|
||||
_Live Example: https://astro-template.vercel.app_
|
||||
|
||||
## Project Structure
|
||||
|
||||
Inside of your Astro project, you'll see the following folders and files:
|
||||
|
||||
@@ -26,17 +32,15 @@ There's nothing special about `src/components/`, but that's where we like to put
|
||||
|
||||
Any static assets, like images, can be placed in the `public/` directory.
|
||||
|
||||
## 🧞 Commands
|
||||
## Commands
|
||||
|
||||
All commands are run from the root of the project, from a terminal:
|
||||
|
||||
| Command | Action |
|
||||
| :---------------- | :------------------------------------------- |
|
||||
| `npm install` | Installs dependencies |
|
||||
| `npm run dev` | Starts local dev server at `localhost:3000` |
|
||||
| `npm run build` | Build your production site to `./dist/` |
|
||||
| `npm run preview` | Preview your build locally, before deploying |
|
||||
|
||||
## 👀 Want to learn more?
|
||||
|
||||
Feel free to check [our documentation](https://github.com/withastro/astro) or jump into our [Discord server](https://astro.build/chat).
|
||||
| Command | Action |
|
||||
| :--------------------- | :------------------------------------------------- |
|
||||
| `npm install` | Installs dependencies |
|
||||
| `npm run dev` | Starts local dev server at `localhost:3000` |
|
||||
| `npm run build` | Build your production site to `./dist/` |
|
||||
| `npm run preview` | Preview your build locally, before deploying |
|
||||
| `npm run astro ...` | Run CLI commands like `astro add`, `astro preview` |
|
||||
| `npm run astro --help` | Get help using the Astro CLI |
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
{
|
||||
"name": "@example/basics",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "astro dev",
|
||||
"start": "astro dev",
|
||||
"build": "astro build",
|
||||
"preview": "astro preview"
|
||||
"preview": "astro preview",
|
||||
"astro": "astro"
|
||||
},
|
||||
"devDependencies": {
|
||||
"astro": "^1.0.0-beta.20"
|
||||
"astro": "^1.0.0-rc.8"
|
||||
}
|
||||
}
|
||||
|
||||
76
examples/astro/src/components/Card.astro
Normal file
@@ -0,0 +1,76 @@
|
||||
---
|
||||
export interface Props {
|
||||
title: string;
|
||||
body: string;
|
||||
href: string;
|
||||
}
|
||||
|
||||
const { href, title, body } = Astro.props as Props;
|
||||
---
|
||||
|
||||
<li class="link-card">
|
||||
<a href={href}>
|
||||
<h2>
|
||||
{title}
|
||||
<span>→</span>
|
||||
</h2>
|
||||
<p>
|
||||
{body}
|
||||
</p>
|
||||
</a>
|
||||
</li>
|
||||
<style>
|
||||
:root {
|
||||
--link-gradient: linear-gradient(45deg, #4f39fa, #da62c4 30%, var(--color-border) 60%);
|
||||
}
|
||||
|
||||
.link-card {
|
||||
list-style: none;
|
||||
display: flex;
|
||||
padding: 0.15rem;
|
||||
background-image: var(--link-gradient);
|
||||
background-size: 400%;
|
||||
border-radius: 0.5rem;
|
||||
background-position: 100%;
|
||||
transition: background-position 0.6s cubic-bezier(0.22, 1, 0.36, 1);
|
||||
}
|
||||
|
||||
.link-card > a {
|
||||
width: 100%;
|
||||
text-decoration: none;
|
||||
line-height: 1.4;
|
||||
padding: 1em 1.3em;
|
||||
border-radius: 0.35rem;
|
||||
color: var(--text-color);
|
||||
background-color: white;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin: 0;
|
||||
transition: color 0.6s cubic-bezier(0.22, 1, 0.36, 1);
|
||||
}
|
||||
|
||||
p {
|
||||
margin-top: 0.75rem;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
h2 span {
|
||||
display: inline-block;
|
||||
transition: transform 0.3s cubic-bezier(0.22, 1, 0.36, 1);
|
||||
}
|
||||
|
||||
.link-card:is(:hover, :focus-within) {
|
||||
background-position: 0;
|
||||
}
|
||||
|
||||
.link-card:is(:hover, :focus-within) h2 {
|
||||
color: #4f39fa;
|
||||
}
|
||||
|
||||
.link-card:is(:hover, :focus-within) h2 span {
|
||||
will-change: transform;
|
||||
transform: translateX(2px);
|
||||
}
|
||||
</style>
|
||||
@@ -1,55 +0,0 @@
|
||||
---
|
||||
export interface Props {
|
||||
title: string;
|
||||
}
|
||||
|
||||
const { title } = Astro.props as Props;
|
||||
---
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width">
|
||||
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
|
||||
<title>{title}</title>
|
||||
</head>
|
||||
<body>
|
||||
<slot />
|
||||
</body>
|
||||
</html>
|
||||
|
||||
<style>
|
||||
:root {
|
||||
--font-size-base: clamp(1rem, 0.34vw + 0.91rem, 1.19rem);
|
||||
--font-size-lg: clamp(1.2rem, 0.7vw + 1.2rem, 1.5rem);
|
||||
--font-size-xl: clamp(2.44rem, 2.38vw + 1.85rem, 3.75rem);
|
||||
|
||||
--color-text: hsl(12, 5%, 4%);
|
||||
--color-bg: hsl(10, 21%, 95%);
|
||||
}
|
||||
|
||||
html {
|
||||
font-family: system-ui, sans-serif;
|
||||
font-size: var(--font-size-base);
|
||||
color: var(--color-text);
|
||||
background-color: var(--color-bg);
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
:global(h1) {
|
||||
font-size: var(--font-size-xl);
|
||||
}
|
||||
|
||||
:global(h2) {
|
||||
font-size: var(--font-size-lg);
|
||||
}
|
||||
|
||||
:global(code) {
|
||||
font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono,
|
||||
Bitstream Vera Sans Mono, Courier New, monospace;
|
||||
}
|
||||
</style>
|
||||
1
examples/astro/src/env.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/// <reference types="astro/client" />
|
||||
56
examples/astro/src/layouts/Layout.astro
Normal file
@@ -0,0 +1,56 @@
|
||||
---
|
||||
export interface Props {
|
||||
title: string;
|
||||
}
|
||||
|
||||
const { title } = Astro.props as Props;
|
||||
---
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
|
||||
<meta name="generator" content={Astro.generator} />
|
||||
<title>{title}</title>
|
||||
</head>
|
||||
<body>
|
||||
<slot />
|
||||
</body>
|
||||
</html>
|
||||
<style>
|
||||
:root {
|
||||
--font-size-base: clamp(1rem, 0.34vw + 0.91rem, 1.19rem);
|
||||
--font-size-lg: clamp(1.2rem, 0.7vw + 1.2rem, 1.5rem);
|
||||
--font-size-xl: clamp(2.44rem, 2.38vw + 1.85rem, 3.75rem);
|
||||
|
||||
--color-text: hsl(12, 5%, 4%);
|
||||
--color-bg: hsl(10, 21%, 95%);
|
||||
--color-border: hsl(17, 24%, 90%);
|
||||
}
|
||||
|
||||
html {
|
||||
font-family: system-ui, sans-serif;
|
||||
font-size: var(--font-size-base);
|
||||
color: var(--color-text);
|
||||
background-color: var(--color-bg);
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
:global(h1) {
|
||||
font-size: var(--font-size-xl);
|
||||
}
|
||||
|
||||
:global(h2) {
|
||||
font-size: var(--font-size-lg);
|
||||
}
|
||||
|
||||
:global(code) {
|
||||
font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono,
|
||||
Bitstream Vera Sans Mono, Courier New, monospace;
|
||||
}
|
||||
</style>
|
||||
@@ -1,81 +1,52 @@
|
||||
---
|
||||
import Layout from '../components/Layout.astro';
|
||||
import Layout from '../layouts/Layout.astro';
|
||||
import Card from '../components/Card.astro';
|
||||
---
|
||||
|
||||
<Layout title="Welcome to Astro.">
|
||||
<main>
|
||||
<h1>Welcome to <span class="text-gradient">Astro</span></h1>
|
||||
<p class="instructions"><strong>Your first mission:</strong> tweak this message to try our hot module reloading. Check the <code>src/pages</code> directory!</p>
|
||||
<p class="instructions">
|
||||
Check out the <code>src/pages</code> directory to get started.<br />
|
||||
<strong>Code Challenge:</strong> Tweak the "Welcome to Astro" message above.
|
||||
</p>
|
||||
<ul role="list" class="link-card-grid">
|
||||
<li class="link-card">
|
||||
<a href="https://astro.build/integrations/">
|
||||
<h2>Integrations <span>→</span></h2>
|
||||
<p>Add component frameworks, Tailwind, Partytown, and more!</p>
|
||||
</a>
|
||||
</li>
|
||||
<li class="link-card">
|
||||
<a href="https://astro.build/themes/">
|
||||
<h2>Themes <span>→</span></h2>
|
||||
<p>Explore a galaxy of community-built starters.</p>
|
||||
</a>
|
||||
</li>
|
||||
<li class="link-card">
|
||||
<a href="https://docs.astro.build/">
|
||||
<h2>Docs <span>→</span></h2>
|
||||
<p>Learn our complete feature set and explore the API.</p>
|
||||
</a>
|
||||
</li>
|
||||
<li class="link-card">
|
||||
<a href="https://astro.build/chat/">
|
||||
<h2>Chat <span>→</span></h2>
|
||||
<p>
|
||||
Ask, contribute, and have fun on our community Discord
|
||||
<svg
|
||||
class="heart"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 512 512"
|
||||
width="16"
|
||||
height="16"
|
||||
fill="currentColor"
|
||||
>
|
||||
<title>heart</title>
|
||||
<path d="M256 448l-30.164-27.211C118.718 322.442 48 258.61 48 179.095 48 114.221 97.918 64 162.4 64c36.399 0 70.717 16.742 93.6 43.947C278.882 80.742 313.199 64 349.6 64 414.082 64 464 114.221 464 179.095c0 79.516-70.719 143.348-177.836 241.694L256 448z" />
|
||||
</svg>
|
||||
</p>
|
||||
</a>
|
||||
</li>
|
||||
<Card
|
||||
href="https://docs.astro.build/"
|
||||
title="Documentation"
|
||||
body="Learn how Astro works and explore the official API docs."
|
||||
/>
|
||||
<Card
|
||||
href="https://astro.build/integrations/"
|
||||
title="Integrations"
|
||||
body="Supercharge your project with new frameworks and libraries."
|
||||
/>
|
||||
<Card
|
||||
href="https://astro.build/themes/"
|
||||
title="Themes"
|
||||
body="Explore a galaxy of community-built starter themes."
|
||||
/>
|
||||
<Card
|
||||
href="https://astro.build/chat/"
|
||||
title="Chat"
|
||||
body="Come say hi to our amazing Discord community. ❤️"
|
||||
/>
|
||||
</ul>
|
||||
</main>
|
||||
</Layout>
|
||||
|
||||
<style>
|
||||
:root {
|
||||
--color-border: hsl(17, 24%, 90%);
|
||||
--astro-gradient: linear-gradient(0deg,#4F39FA, #DA62C4);
|
||||
--link-gradient: linear-gradient(45deg, #4F39FA, #DA62C4 30%, var(--color-border) 60%);
|
||||
--night-sky-gradient: linear-gradient(0deg, #392362 -33%, #431f69 10%, #30216b 50%, #1f1638 100%);
|
||||
--astro-gradient: linear-gradient(0deg, #4f39fa, #da62c4);
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin: 0;
|
||||
transition: color 0.6s cubic-bezier(0.22, 1, 0.36, 1);
|
||||
}
|
||||
|
||||
h2 span {
|
||||
display: inline-block;
|
||||
transition: transform 0.3s cubic-bezier(0.22, 1, 0.36, 1);
|
||||
}
|
||||
|
||||
code {
|
||||
font-size: 0.875em;
|
||||
border: 0.1em solid var(--color-border);
|
||||
border-radius: 4px;
|
||||
padding: 0.15em 0.25em;
|
||||
h1 {
|
||||
margin: 2rem 0;
|
||||
}
|
||||
|
||||
main {
|
||||
margin: auto;
|
||||
padding: 1em;
|
||||
padding: 1em;
|
||||
max-width: 60ch;
|
||||
}
|
||||
|
||||
@@ -83,7 +54,7 @@ import Layout from '../components/Layout.astro';
|
||||
font-weight: 900;
|
||||
background-image: var(--astro-gradient);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-size: 100% 200%;
|
||||
background-position-y: 100%;
|
||||
border-radius: 0.4rem;
|
||||
@@ -91,7 +62,8 @@ import Layout from '../components/Layout.astro';
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% {
|
||||
0%,
|
||||
100% {
|
||||
background-position-y: 0%;
|
||||
}
|
||||
50% {
|
||||
@@ -100,75 +72,25 @@ import Layout from '../components/Layout.astro';
|
||||
}
|
||||
|
||||
.instructions {
|
||||
line-height: 1.8;
|
||||
margin-bottom: 2rem;
|
||||
background-image: var(--night-sky-gradient);
|
||||
padding: 1.5rem;
|
||||
line-height: 1.6;
|
||||
margin: 1rem 0;
|
||||
background: #4f39fa;
|
||||
padding: 1rem;
|
||||
border-radius: 0.4rem;
|
||||
color: var(--color-bg);
|
||||
}
|
||||
|
||||
.instructions code {
|
||||
font-size: 0.875em;
|
||||
border: 0.1em solid var(--color-border);
|
||||
border-radius: 4px;
|
||||
padding: 0.15em 0.25em;
|
||||
}
|
||||
|
||||
.link-card-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(24ch, 1fr));
|
||||
gap: 1rem;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.link-card {
|
||||
list-style: none;
|
||||
display: flex;
|
||||
padding: 0.15rem;
|
||||
background-image: var(--link-gradient);
|
||||
background-size: 400%;
|
||||
border-radius: 0.5rem;
|
||||
background-position: 100%;
|
||||
transition: background-position 0.6s cubic-bezier(0.22, 1, 0.36, 1);
|
||||
}
|
||||
|
||||
.link-card > a {
|
||||
width: 100%;
|
||||
text-decoration: none;
|
||||
line-height: 1.4;
|
||||
padding: 1em 1.3em;
|
||||
border-radius: 0.35rem;
|
||||
color: var(--text-color);
|
||||
background-color: white;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.link-card:is(:hover, :focus-within) {
|
||||
background-position: 0;
|
||||
}
|
||||
|
||||
.link-card:is(:hover, :focus-within) h2 {
|
||||
color: #4F39FA;
|
||||
}
|
||||
|
||||
.link-card:is(:hover, :focus-within) h2 span {
|
||||
transform: translateX(2px);
|
||||
}
|
||||
|
||||
.heart {
|
||||
display: inline-block;
|
||||
color: #DA62C4;
|
||||
animation: heartbeat 3s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes heartbeat {
|
||||
0%,
|
||||
50%,
|
||||
100% {
|
||||
transform: scale(1);
|
||||
}
|
||||
5% {
|
||||
transform: scale(1.125);
|
||||
}
|
||||
10% {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
15% {
|
||||
transform: scale(1.25);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
// Enable top-level await, and other modern ESM features.
|
||||
"target": "ESNext",
|
||||
"module": "ESNext",
|
||||
// Enable node-style module resolution, for things like npm package imports.
|
||||
"moduleResolution": "node",
|
||||
// Enable JSON imports.
|
||||
"resolveJsonModule": true,
|
||||
// Enable stricter transpilation for better output.
|
||||
"isolatedModules": true,
|
||||
// Add type definitions for our Vite runtime.
|
||||
"types": ["vite/client"]
|
||||
}
|
||||
}
|
||||
@@ -15,5 +15,5 @@ _Live Example: https://docusaurus-2-template.vercel.app_
|
||||
To get started with Docusaurus on Vercel, you can use the [Docusaurus CLI](https://v2.docusaurus.io/docs/installation#scaffold-project-website) to initialize the project:
|
||||
|
||||
```shell
|
||||
$ npx @docusaurus/init@next init my-website classic
|
||||
npx create-docusaurus@latest my-website classic
|
||||
```
|
||||
|
||||
3
examples/docusaurus-2/babel.config.js
Normal file
@@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
presets: [require.resolve('@docusaurus/core/lib/babel/preset')],
|
||||
};
|
||||
@@ -1,10 +1,11 @@
|
||||
---
|
||||
id: hola
|
||||
title: Hola
|
||||
author: Gao Wei
|
||||
author_title: Docusaurus Core Team
|
||||
author_url: https://github.com/wgao19
|
||||
author_image_url: https://avatars1.githubusercontent.com/u/2055384?v=4
|
||||
slug: first-blog-post
|
||||
title: First Blog Post
|
||||
authors:
|
||||
name: Gao Wei
|
||||
title: Docusaurus Core Team
|
||||
url: https://github.com/wgao19
|
||||
image_url: https://github.com/wgao19.png
|
||||
tags: [hola, docusaurus]
|
||||
---
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
---
|
||||
id: hello-world
|
||||
title: Hello
|
||||
author: Endilie Yacop Sucipto
|
||||
author_title: Maintainer of Docusaurus
|
||||
author_url: https://github.com/endiliey
|
||||
author_image_url: https://avatars1.githubusercontent.com/u/17883920?s=460&v=4
|
||||
tags: [hello, docusaurus]
|
||||
---
|
||||
|
||||
Welcome to this blog. This blog is created with [**Docusaurus 2 alpha**](https://v2.docusaurus.io/).
|
||||
|
||||
<!--truncate-->
|
||||
|
||||
This is a test post.
|
||||
|
||||
A whole bunch of other information.
|
||||
44
examples/docusaurus-2/blog/2019-05-29-long-blog-post.md
Normal file
@@ -0,0 +1,44 @@
|
||||
---
|
||||
slug: long-blog-post
|
||||
title: Long Blog Post
|
||||
authors: endi
|
||||
tags: [hello, docusaurus]
|
||||
---
|
||||
|
||||
This is the summary of a very long blog post,
|
||||
|
||||
Use a `<!--` `truncate` `-->` comment to limit blog post size in the list view.
|
||||
|
||||
<!--truncate-->
|
||||
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
|
||||
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
|
||||
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
|
||||
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
|
||||
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
|
||||
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
|
||||
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
|
||||
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
|
||||
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
|
||||
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
|
||||
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
|
||||
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
|
||||
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
|
||||
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
|
||||
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
|
||||
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
|
||||
@@ -1,13 +0,0 @@
|
||||
---
|
||||
id: welcome
|
||||
title: Welcome
|
||||
author: Yangshun Tay
|
||||
author_title: Front End Engineer @ Facebook
|
||||
author_url: https://github.com/yangshun
|
||||
author_image_url: https://avatars0.githubusercontent.com/u/1315101?s=400&v=4
|
||||
tags: [facebook, hello, docusaurus]
|
||||
---
|
||||
|
||||
Blog features are powered by the blog plugin. Simply add files to the `blog` directory. It supports tags as well!
|
||||
|
||||
Delete the whole directory if you don't want the blog features. As simple as that!
|
||||
20
examples/docusaurus-2/blog/2021-08-01-mdx-blog-post.mdx
Normal file
@@ -0,0 +1,20 @@
|
||||
---
|
||||
slug: mdx-blog-post
|
||||
title: MDX Blog Post
|
||||
authors: [slorber]
|
||||
tags: [docusaurus]
|
||||
---
|
||||
|
||||
Blog posts support [Docusaurus Markdown features](https://docusaurus.io/docs/markdown-features), such as [MDX](https://mdxjs.com/).
|
||||
|
||||
:::tip
|
||||
|
||||
Use the power of React to create interactive blog posts.
|
||||
|
||||
```js
|
||||
<button onClick={() => alert('button clicked!')}>Click me!</button>
|
||||
```
|
||||
|
||||
<button onClick={() => alert('button clicked!')}>Click me!</button>
|
||||
|
||||
:::
|
||||
|
After Width: | Height: | Size: 94 KiB |
25
examples/docusaurus-2/blog/2021-08-26-welcome/index.md
Normal file
@@ -0,0 +1,25 @@
|
||||
---
|
||||
slug: welcome
|
||||
title: Welcome
|
||||
authors: [slorber, yangshun]
|
||||
tags: [facebook, hello, docusaurus]
|
||||
---
|
||||
|
||||
[Docusaurus blogging features](https://docusaurus.io/docs/blog) are powered by the [blog plugin](https://docusaurus.io/docs/api/plugins/@docusaurus/plugin-content-blog).
|
||||
|
||||
Simply add Markdown files (or folders) to the `blog` directory.
|
||||
|
||||
Regular blog authors can be added to `authors.yml`.
|
||||
|
||||
The blog post date can be extracted from filenames, such as:
|
||||
|
||||
- `2019-05-30-welcome.md`
|
||||
- `2019-05-30-welcome/index.md`
|
||||
|
||||
A blog post folder can be convenient to co-locate blog post images:
|
||||
|
||||

|
||||
|
||||
The blog supports tags as well!
|
||||
|
||||
**And if you don't want a blog**: just delete this directory, and use `blog: false` in your Docusaurus config.
|
||||
17
examples/docusaurus-2/blog/authors.yml
Normal file
@@ -0,0 +1,17 @@
|
||||
endi:
|
||||
name: Endilie Yacop Sucipto
|
||||
title: Maintainer of Docusaurus
|
||||
url: https://github.com/endiliey
|
||||
image_url: https://github.com/endiliey.png
|
||||
|
||||
yangshun:
|
||||
name: Yangshun Tay
|
||||
title: Front End Engineer @ Facebook
|
||||
url: https://github.com/yangshun
|
||||
image_url: https://github.com/yangshun.png
|
||||
|
||||
slorber:
|
||||
name: Sébastien Lorber
|
||||
title: Docusaurus maintainer
|
||||
url: https://sebastienlorber.com
|
||||
image_url: https://github.com/slorber.png
|
||||
@@ -1,202 +0,0 @@
|
||||
---
|
||||
id: doc1
|
||||
title: Style Guide
|
||||
sidebar_label: Style Guide
|
||||
---
|
||||
|
||||
You can write content using [GitHub-flavored Markdown syntax](https://github.github.com/gfm/).
|
||||
|
||||
## Markdown Syntax
|
||||
|
||||
To serve as an example page when styling markdown based Docusaurus sites.
|
||||
|
||||
## Headers
|
||||
|
||||
# H1 - Create the best documentation
|
||||
|
||||
## H2 - Create the best documentation
|
||||
|
||||
### H3 - Create the best documentation
|
||||
|
||||
#### H4 - Create the best documentation
|
||||
|
||||
##### H5 - Create the best documentation
|
||||
|
||||
###### H6 - Create the best documentation
|
||||
|
||||
---
|
||||
|
||||
## Emphasis
|
||||
|
||||
Emphasis, aka italics, with _asterisks_ or _underscores_.
|
||||
|
||||
Strong emphasis, aka bold, with **asterisks** or **underscores**.
|
||||
|
||||
Combined emphasis with **asterisks and _underscores_**.
|
||||
|
||||
Strikethrough uses two tildes. ~~Scratch this.~~
|
||||
|
||||
---
|
||||
|
||||
## Lists
|
||||
|
||||
1. First ordered list item
|
||||
1. Another item ⋅⋅\* Unordered sub-list.
|
||||
1. Actual numbers don't matter, just that it's a number ⋅⋅1. Ordered sub-list
|
||||
1. And another item.
|
||||
|
||||
⋅⋅⋅You can have properly indented paragraphs within list items. Notice the blank line above, and the leading spaces (at least one, but we'll use three here to also align the raw Markdown).
|
||||
|
||||
⋅⋅⋅To have a line break without a paragraph, you will need to use two trailing spaces.⋅⋅ ⋅⋅⋅Note that this line is separate, but within the same paragraph.⋅⋅ ⋅⋅⋅(This is contrary to the typical GFM line break behaviour, where trailing spaces are not required.)
|
||||
|
||||
- Unordered list can use asterisks
|
||||
|
||||
* Or minuses
|
||||
|
||||
- Or pluses
|
||||
|
||||
---
|
||||
|
||||
## Links
|
||||
|
||||
[I'm an inline-style link](https://www.google.com)
|
||||
|
||||
[I'm an inline-style link with title](https://www.google.com "Google's Homepage")
|
||||
|
||||
[I'm a reference-style link][arbitrary case-insensitive reference text]
|
||||
|
||||
[I'm a relative reference to a repository file](../blob/master/LICENSE)
|
||||
|
||||
[You can use numbers for reference-style link definitions][1]
|
||||
|
||||
Or leave it empty and use the [link text itself].
|
||||
|
||||
URLs and URLs in angle brackets will automatically get turned into links. http://www.example.com or <http://www.example.com> and sometimes example.com (but not on Github, for example).
|
||||
|
||||
Some text to show that the reference links can follow later.
|
||||
|
||||
[arbitrary case-insensitive reference text]: https://www.mozilla.org
|
||||
[1]: http://slashdot.org
|
||||
[link text itself]: http://www.reddit.com
|
||||
|
||||
---
|
||||
|
||||
## Images
|
||||
|
||||
Here's our logo (hover to see the title text):
|
||||
|
||||
Inline-style: 
|
||||
|
||||
Reference-style: ![alt text][logo]
|
||||
|
||||
[logo]: https://github.com/adam-p/markdown-here/raw/master/src/common/images/icon48.png 'Logo Title Text 2'
|
||||
|
||||
---
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
var s = 'JavaScript syntax highlighting';
|
||||
alert(s);
|
||||
```
|
||||
|
||||
```python
|
||||
s = "Python syntax highlighting"
|
||||
print(s)
|
||||
```
|
||||
|
||||
```
|
||||
No language indicated, so no syntax highlighting.
|
||||
But let's throw in a <b>tag</b>.
|
||||
```
|
||||
|
||||
```js {2}
|
||||
function highlightMe() {
|
||||
console.log('This line can be highlighted!');
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Tables
|
||||
|
||||
Colons can be used to align columns.
|
||||
|
||||
| Tables | Are | Cool |
|
||||
| ------------- | :-----------: | -----: |
|
||||
| col 3 is | right-aligned | \$1600 |
|
||||
| col 2 is | centered | \$12 |
|
||||
| zebra stripes | are neat | \$1 |
|
||||
|
||||
There must be at least 3 dashes separating each header cell. The outer pipes (|) are optional, and you don't need to make the raw Markdown line up prettily. You can also use inline Markdown.
|
||||
|
||||
| Markdown | Less | Pretty |
|
||||
| -------- | --------- | ---------- |
|
||||
| _Still_ | `renders` | **nicely** |
|
||||
| 1 | 2 | 3 |
|
||||
|
||||
---
|
||||
|
||||
## Blockquotes
|
||||
|
||||
> Blockquotes are very handy in email to emulate reply text. This line is part of the same quote.
|
||||
|
||||
Quote break.
|
||||
|
||||
> This is a very long line that will still be quoted properly when it wraps. Oh boy let's keep writing to make sure this is long enough to actually wrap for everyone. Oh, you can _put_ **Markdown** into a blockquote.
|
||||
|
||||
---
|
||||
|
||||
## Inline HTML
|
||||
|
||||
<dl>
|
||||
<dt>Definition list</dt>
|
||||
<dd>Is something people use sometimes.</dd>
|
||||
|
||||
<dt>Markdown in HTML</dt>
|
||||
<dd>Does *not* work **very** well. Use HTML <em>tags</em>.</dd>
|
||||
</dl>
|
||||
|
||||
---
|
||||
|
||||
## Line Breaks
|
||||
|
||||
Here's a line for us to start with.
|
||||
|
||||
This line is separated from the one above by two newlines, so it will be a _separate paragraph_.
|
||||
|
||||
This line is also a separate paragraph, but... This line is only separated by a single newline, so it's a separate line in the _same paragraph_.
|
||||
|
||||
---
|
||||
|
||||
## Admonitions
|
||||
|
||||
:::note
|
||||
|
||||
This is a note
|
||||
|
||||
:::
|
||||
|
||||
:::tip
|
||||
|
||||
This is a tip
|
||||
|
||||
:::
|
||||
|
||||
:::important
|
||||
|
||||
This is important
|
||||
|
||||
:::
|
||||
|
||||
:::caution
|
||||
|
||||
This is a caution
|
||||
|
||||
:::
|
||||
|
||||
:::warning
|
||||
|
||||
This is a warning
|
||||
|
||||
:::
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
id: doc2
|
||||
title: Document Number 2
|
||||
---
|
||||
|
||||
This is a link to [another document.](doc3.md) This is a link to an [external page.](http://www.example.com)
|
||||
@@ -1,14 +0,0 @@
|
||||
---
|
||||
id: doc3
|
||||
title: This is Document Number 3
|
||||
---
|
||||
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. In ac euismod odio, eu consequat dui. Nullam molestie consectetur risus id imperdiet. Proin sodales ornare turpis, non mollis massa ultricies id. Nam at nibh scelerisque, feugiat ante non, dapibus tortor. Vivamus volutpat diam quis tellus elementum bibendum. Praesent semper gravida velit quis aliquam. Etiam in cursus neque. Nam lectus ligula, malesuada et mauris a, bibendum faucibus mi. Phasellus ut interdum felis. Phasellus in odio pulvinar, porttitor urna eget, fringilla lectus. Aliquam sollicitudin est eros. Mauris consectetur quam vitae mauris interdum hendrerit. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|
||||
|
||||
Duis et egestas libero, imperdiet faucibus ipsum. Sed posuere eget urna vel feugiat. Vivamus a arcu sagittis, fermentum urna dapibus, congue lectus. Fusce vulputate porttitor nisl, ac cursus elit volutpat vitae. Nullam vitae ipsum egestas, convallis quam non, porta nibh. Morbi gravida erat nec neque bibendum, eu pellentesque velit posuere. Fusce aliquam erat eu massa eleifend tristique.
|
||||
|
||||
Sed consequat sollicitudin ipsum eget tempus. Integer a aliquet velit. In justo nibh, pellentesque non suscipit eget, gravida vel lacus. Donec odio ante, malesuada in massa quis, pharetra tristique ligula. Donec eros est, tristique eget finibus quis, semper non nisl. Vivamus et elit nec enim ornare placerat. Sed posuere odio a elit cursus sagittis.
|
||||
|
||||
Phasellus feugiat purus eu tortor ultrices finibus. Ut libero nibh, lobortis et libero nec, dapibus posuere eros. Sed sagittis euismod justo at consectetur. Nulla finibus libero placerat, cursus sapien at, eleifend ligula. Vivamus elit nisl, hendrerit ac nibh eu, ultrices tempus dui. Nam tellus neque, commodo non rhoncus eu, gravida in risus. Nullam id iaculis tortor.
|
||||
|
||||
Nullam at odio in sem varius tempor sit amet vel lorem. Etiam eu hendrerit nisl. Fusce nibh mauris, vulputate sit amet ex vitae, congue rhoncus nisl. Sed eget tellus purus. Nullam tempus commodo erat ut tristique. Cras accumsan massa sit amet justo consequat eleifend. Integer scelerisque vitae tellus id consectetur.
|
||||
47
examples/docusaurus-2/docs/intro.md
Normal file
@@ -0,0 +1,47 @@
|
||||
---
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
# Tutorial Intro
|
||||
|
||||
Let's discover **Docusaurus in less than 5 minutes**.
|
||||
|
||||
## Getting Started
|
||||
|
||||
Get started by **creating a new site**.
|
||||
|
||||
Or **try Docusaurus immediately** with **[docusaurus.new](https://docusaurus.new)**.
|
||||
|
||||
### What you'll need
|
||||
|
||||
- [Node.js](https://nodejs.org/en/download/) version 16.14 or above:
|
||||
- When installing Node.js, you are recommended to check all checkboxes related to dependencies.
|
||||
|
||||
## Generate a new site
|
||||
|
||||
Generate a new Docusaurus site using the **classic template**.
|
||||
|
||||
The classic template will automatically be added to your project after you run the command:
|
||||
|
||||
```bash
|
||||
npm init docusaurus@latest my-website classic
|
||||
```
|
||||
|
||||
You can type this command into Command Prompt, Powershell, Terminal, or any other integrated terminal of your code editor.
|
||||
|
||||
The command also installs all necessary dependencies you need to run Docusaurus.
|
||||
|
||||
## Start your site
|
||||
|
||||
Run the development server:
|
||||
|
||||
```bash
|
||||
cd my-website
|
||||
npm run start
|
||||
```
|
||||
|
||||
The `cd` command changes the directory you're working with. In order to work with your newly created Docusaurus site, you'll need to navigate the terminal there.
|
||||
|
||||
The `npm run start` command builds your website locally and serves it through a development server, ready for you to view at http://localhost:3000/.
|
||||
|
||||
Open `docs/intro.md` (this page) and edit some lines: the site **reloads automatically** and displays your changes.
|
||||
@@ -1,17 +0,0 @@
|
||||
---
|
||||
id: mdx
|
||||
title: Powered by MDX
|
||||
---
|
||||
|
||||
You can write JSX and use React components within your Markdown thanks to [MDX](https://mdxjs.com/).
|
||||
|
||||
export const Highlight = ({children, color}) => ( <span style={{
|
||||
backgroundColor: color,
|
||||
borderRadius: '2px',
|
||||
color: '#fff',
|
||||
padding: '0.2rem',
|
||||
}}> {children} </span> );
|
||||
|
||||
<Highlight color="#25c2a0">Docusaurus green</Highlight> and <Highlight color="#1877F2">Facebook blue</Highlight> are my favorite colors.
|
||||
|
||||
I can write **Markdown** alongside my _JSX_!
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"label": "Tutorial - Basics",
|
||||
"position": 2,
|
||||
"link": {
|
||||
"type": "generated-index",
|
||||
"description": "5 minutes to learn the most important Docusaurus concepts."
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
---
|
||||
sidebar_position: 6
|
||||
---
|
||||
|
||||
# Congratulations!
|
||||
|
||||
You have just learned the **basics of Docusaurus** and made some changes to the **initial template**.
|
||||
|
||||
Docusaurus has **much more to offer**!
|
||||
|
||||
Have **5 more minutes**? Take a look at **[versioning](../tutorial-extras/manage-docs-versions.md)** and **[i18n](../tutorial-extras/translate-your-site.md)**.
|
||||
|
||||
Anything **unclear** or **buggy** in this tutorial? [Please report it!](https://github.com/facebook/docusaurus/discussions/4610)
|
||||
|
||||
## What's next?
|
||||
|
||||
- Read the [official documentation](https://docusaurus.io/).
|
||||
- Add a custom [Design and Layout](https://docusaurus.io/docs/styling-layout)
|
||||
- Add a [search bar](https://docusaurus.io/docs/search)
|
||||
- Find inspirations in the [Docusaurus showcase](https://docusaurus.io/showcase)
|
||||
- Get involved in the [Docusaurus Community](https://docusaurus.io/community/support)
|
||||
@@ -0,0 +1,34 @@
|
||||
---
|
||||
sidebar_position: 3
|
||||
---
|
||||
|
||||
# Create a Blog Post
|
||||
|
||||
Docusaurus creates a **page for each blog post**, but also a **blog index page**, a **tag system**, an **RSS** feed...
|
||||
|
||||
## Create your first Post
|
||||
|
||||
Create a file at `blog/2021-02-28-greetings.md`:
|
||||
|
||||
```md title="blog/2021-02-28-greetings.md"
|
||||
---
|
||||
slug: greetings
|
||||
title: Greetings!
|
||||
authors:
|
||||
- name: Joel Marcey
|
||||
title: Co-creator of Docusaurus 1
|
||||
url: https://github.com/JoelMarcey
|
||||
image_url: https://github.com/JoelMarcey.png
|
||||
- name: Sébastien Lorber
|
||||
title: Docusaurus maintainer
|
||||
url: https://sebastienlorber.com
|
||||
image_url: https://github.com/slorber.png
|
||||
tags: [greetings]
|
||||
---
|
||||
|
||||
Congratulations, you have made your first post!
|
||||
|
||||
Feel free to play around and edit this post as much you like.
|
||||
```
|
||||
|
||||
A new blog post is now available at [http://localhost:3000/blog/greetings](http://localhost:3000/blog/greetings).
|
||||
@@ -0,0 +1,55 @@
|
||||
---
|
||||
sidebar_position: 2
|
||||
---
|
||||
|
||||
# Create a Document
|
||||
|
||||
Documents are **groups of pages** connected through:
|
||||
|
||||
- a **sidebar**
|
||||
- **previous/next navigation**
|
||||
- **versioning**
|
||||
|
||||
## Create your first Doc
|
||||
|
||||
Create a Markdown file at `docs/hello.md`:
|
||||
|
||||
```md title="docs/hello.md"
|
||||
# Hello
|
||||
|
||||
This is my **first Docusaurus document**!
|
||||
```
|
||||
|
||||
A new document is now available at [http://localhost:3000/docs/hello](http://localhost:3000/docs/hello).
|
||||
|
||||
## Configure the Sidebar
|
||||
|
||||
Docusaurus automatically **creates a sidebar** from the `docs` folder.
|
||||
|
||||
Add metadata to customize the sidebar label and position:
|
||||
|
||||
```md title="docs/hello.md" {1-4}
|
||||
---
|
||||
sidebar_label: 'Hi!'
|
||||
sidebar_position: 3
|
||||
---
|
||||
|
||||
# Hello
|
||||
|
||||
This is my **first Docusaurus document**!
|
||||
```
|
||||
|
||||
It is also possible to create your sidebar explicitly in `sidebars.js`:
|
||||
|
||||
```js title="sidebars.js"
|
||||
module.exports = {
|
||||
tutorialSidebar: [
|
||||
{
|
||||
type: 'category',
|
||||
label: 'Tutorial',
|
||||
// highlight-next-line
|
||||
items: ['hello'],
|
||||
},
|
||||
],
|
||||
};
|
||||
```
|
||||
43
examples/docusaurus-2/docs/tutorial-basics/create-a-page.md
Normal file
@@ -0,0 +1,43 @@
|
||||
---
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
# Create a Page
|
||||
|
||||
Add **Markdown or React** files to `src/pages` to create a **standalone page**:
|
||||
|
||||
- `src/pages/index.js` → `localhost:3000/`
|
||||
- `src/pages/foo.md` → `localhost:3000/foo`
|
||||
- `src/pages/foo/bar.js` → `localhost:3000/foo/bar`
|
||||
|
||||
## Create your first React Page
|
||||
|
||||
Create a file at `src/pages/my-react-page.js`:
|
||||
|
||||
```jsx title="src/pages/my-react-page.js"
|
||||
import React from 'react';
|
||||
import Layout from '@theme/Layout';
|
||||
|
||||
export default function MyReactPage() {
|
||||
return (
|
||||
<Layout>
|
||||
<h1>My React page</h1>
|
||||
<p>This is a React page</p>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
A new page is now available at [http://localhost:3000/my-react-page](http://localhost:3000/my-react-page).
|
||||
|
||||
## Create your first Markdown Page
|
||||
|
||||
Create a file at `src/pages/my-markdown-page.md`:
|
||||
|
||||
```mdx title="src/pages/my-markdown-page.md"
|
||||
# My Markdown page
|
||||
|
||||
This is a Markdown page
|
||||
```
|
||||
|
||||
A new page is now available at [http://localhost:3000/my-markdown-page](http://localhost:3000/my-markdown-page).
|
||||
@@ -0,0 +1,31 @@
|
||||
---
|
||||
sidebar_position: 5
|
||||
---
|
||||
|
||||
# Deploy your site
|
||||
|
||||
Docusaurus is a **static-site-generator** (also called **[Jamstack](https://jamstack.org/)**).
|
||||
|
||||
It builds your site as simple **static HTML, JavaScript and CSS files**.
|
||||
|
||||
## Build your site
|
||||
|
||||
Build your site **for production**:
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
The static files are generated in the `build` folder.
|
||||
|
||||
## Deploy your site
|
||||
|
||||
Test your production build locally:
|
||||
|
||||
```bash
|
||||
npm run serve
|
||||
```
|
||||
|
||||
The `build` folder is now served at [http://localhost:3000/](http://localhost:3000/).
|
||||
|
||||
You can now deploy the `build` folder **almost anywhere** easily, **for free** or very small cost (read the **[Deployment Guide](https://docusaurus.io/docs/deployment)**).
|
||||
146
examples/docusaurus-2/docs/tutorial-basics/markdown-features.mdx
Normal file
@@ -0,0 +1,146 @@
|
||||
---
|
||||
sidebar_position: 4
|
||||
---
|
||||
|
||||
# Markdown Features
|
||||
|
||||
Docusaurus supports **[Markdown](https://daringfireball.net/projects/markdown/syntax)** and a few **additional features**.
|
||||
|
||||
## Front Matter
|
||||
|
||||
Markdown documents have metadata at the top called [Front Matter](https://jekyllrb.com/docs/front-matter/):
|
||||
|
||||
```text title="my-doc.md"
|
||||
// highlight-start
|
||||
---
|
||||
id: my-doc-id
|
||||
title: My document title
|
||||
description: My document description
|
||||
slug: /my-custom-url
|
||||
---
|
||||
// highlight-end
|
||||
|
||||
## Markdown heading
|
||||
|
||||
Markdown text with [links](./hello.md)
|
||||
```
|
||||
|
||||
## Links
|
||||
|
||||
Regular Markdown links are supported, using url paths or relative file paths.
|
||||
|
||||
```md
|
||||
Let's see how to [Create a page](/create-a-page).
|
||||
```
|
||||
|
||||
```md
|
||||
Let's see how to [Create a page](./create-a-page.md).
|
||||
```
|
||||
|
||||
**Result:** Let's see how to [Create a page](./create-a-page.md).
|
||||
|
||||
## Images
|
||||
|
||||
Regular Markdown images are supported.
|
||||
|
||||
You can use absolute paths to reference images in the static directory (`static/img/docusaurus.png`):
|
||||
|
||||
```md
|
||||

|
||||
```
|
||||
|
||||

|
||||
|
||||
You can reference images relative to the current file as well, as shown in [the extra guides](../tutorial-extras/manage-docs-versions.md).
|
||||
|
||||
## Code Blocks
|
||||
|
||||
Markdown code blocks are supported with Syntax highlighting.
|
||||
|
||||
```jsx title="src/components/HelloDocusaurus.js"
|
||||
function HelloDocusaurus() {
|
||||
return (
|
||||
<h1>Hello, Docusaurus!</h1>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
```jsx title="src/components/HelloDocusaurus.js"
|
||||
function HelloDocusaurus() {
|
||||
return <h1>Hello, Docusaurus!</h1>;
|
||||
}
|
||||
```
|
||||
|
||||
## Admonitions
|
||||
|
||||
Docusaurus has a special syntax to create admonitions and callouts:
|
||||
|
||||
:::tip My tip
|
||||
|
||||
Use this awesome feature option
|
||||
|
||||
:::
|
||||
|
||||
:::danger Take care
|
||||
|
||||
This action is dangerous
|
||||
|
||||
:::
|
||||
|
||||
:::tip My tip
|
||||
|
||||
Use this awesome feature option
|
||||
|
||||
:::
|
||||
|
||||
:::danger Take care
|
||||
|
||||
This action is dangerous
|
||||
|
||||
:::
|
||||
|
||||
## MDX and React Components
|
||||
|
||||
[MDX](https://mdxjs.com/) can make your documentation more **interactive** and allows using any **React components inside Markdown**:
|
||||
|
||||
```jsx
|
||||
export const Highlight = ({children, color}) => (
|
||||
<span
|
||||
style={{
|
||||
backgroundColor: color,
|
||||
borderRadius: '20px',
|
||||
color: '#fff',
|
||||
padding: '10px',
|
||||
cursor: 'pointer',
|
||||
}}
|
||||
onClick={() => {
|
||||
alert(`You clicked the color ${color} with label ${children}`)
|
||||
}}>
|
||||
{children}
|
||||
</span>
|
||||
);
|
||||
|
||||
This is <Highlight color="#25c2a0">Docusaurus green</Highlight> !
|
||||
|
||||
This is <Highlight color="#1877F2">Facebook blue</Highlight> !
|
||||
```
|
||||
|
||||
export const Highlight = ({children, color}) => (
|
||||
<span
|
||||
style={{
|
||||
backgroundColor: color,
|
||||
borderRadius: '20px',
|
||||
color: '#fff',
|
||||
padding: '10px',
|
||||
cursor: 'pointer',
|
||||
}}
|
||||
onClick={() => {
|
||||
alert(`You clicked the color ${color} with label ${children}`);
|
||||
}}>
|
||||
{children}
|
||||
</span>
|
||||
);
|
||||
|
||||
This is <Highlight color="#25c2a0">Docusaurus green</Highlight> !
|
||||
|
||||
This is <Highlight color="#1877F2">Facebook blue</Highlight> !
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"label": "Tutorial - Extras",
|
||||
"position": 3,
|
||||
"link": {
|
||||
"type": "generated-index"
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 27 KiB |
@@ -0,0 +1,55 @@
|
||||
---
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
# Manage Docs Versions
|
||||
|
||||
Docusaurus can manage multiple versions of your docs.
|
||||
|
||||
## Create a docs version
|
||||
|
||||
Release a version 1.0 of your project:
|
||||
|
||||
```bash
|
||||
npm run docusaurus docs:version 1.0
|
||||
```
|
||||
|
||||
The `docs` folder is copied into `versioned_docs/version-1.0` and `versions.json` is created.
|
||||
|
||||
Your docs now have 2 versions:
|
||||
|
||||
- `1.0` at `http://localhost:3000/docs/` for the version 1.0 docs
|
||||
- `current` at `http://localhost:3000/docs/next/` for the **upcoming, unreleased docs**
|
||||
|
||||
## Add a Version Dropdown
|
||||
|
||||
To navigate seamlessly across versions, add a version dropdown.
|
||||
|
||||
Modify the `docusaurus.config.js` file:
|
||||
|
||||
```js title="docusaurus.config.js"
|
||||
module.exports = {
|
||||
themeConfig: {
|
||||
navbar: {
|
||||
items: [
|
||||
// highlight-start
|
||||
{
|
||||
type: 'docsVersionDropdown',
|
||||
},
|
||||
// highlight-end
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
The docs version dropdown appears in your navbar:
|
||||
|
||||

|
||||
|
||||
## Update an existing version
|
||||
|
||||
It is possible to edit versioned docs in their respective folder:
|
||||
|
||||
- `versioned_docs/version-1.0/hello.md` updates `http://localhost:3000/docs/hello`
|
||||
- `docs/hello.md` updates `http://localhost:3000/docs/next/hello`
|
||||
@@ -0,0 +1,88 @@
|
||||
---
|
||||
sidebar_position: 2
|
||||
---
|
||||
|
||||
# Translate your site
|
||||
|
||||
Let's translate `docs/intro.md` to French.
|
||||
|
||||
## Configure i18n
|
||||
|
||||
Modify `docusaurus.config.js` to add support for the `fr` locale:
|
||||
|
||||
```js title="docusaurus.config.js"
|
||||
module.exports = {
|
||||
i18n: {
|
||||
defaultLocale: 'en',
|
||||
locales: ['en', 'fr'],
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
## Translate a doc
|
||||
|
||||
Copy the `docs/intro.md` file to the `i18n/fr` folder:
|
||||
|
||||
```bash
|
||||
mkdir -p i18n/fr/docusaurus-plugin-content-docs/current/
|
||||
|
||||
cp docs/intro.md i18n/fr/docusaurus-plugin-content-docs/current/intro.md
|
||||
```
|
||||
|
||||
Translate `i18n/fr/docusaurus-plugin-content-docs/current/intro.md` in French.
|
||||
|
||||
## Start your localized site
|
||||
|
||||
Start your site on the French locale:
|
||||
|
||||
```bash
|
||||
npm run start -- --locale fr
|
||||
```
|
||||
|
||||
Your localized site is accessible at [http://localhost:3000/fr/](http://localhost:3000/fr/) and the `Getting Started` page is translated.
|
||||
|
||||
:::caution
|
||||
|
||||
In development, you can only use one locale at a same time.
|
||||
|
||||
:::
|
||||
|
||||
## Add a Locale Dropdown
|
||||
|
||||
To navigate seamlessly across languages, add a locale dropdown.
|
||||
|
||||
Modify the `docusaurus.config.js` file:
|
||||
|
||||
```js title="docusaurus.config.js"
|
||||
module.exports = {
|
||||
themeConfig: {
|
||||
navbar: {
|
||||
items: [
|
||||
// highlight-start
|
||||
{
|
||||
type: 'localeDropdown',
|
||||
},
|
||||
// highlight-end
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
The locale dropdown now appears in your navbar:
|
||||
|
||||

|
||||
|
||||
## Build your localized site
|
||||
|
||||
Build your site for a specific locale:
|
||||
|
||||
```bash
|
||||
npm run build -- --locale fr
|
||||
```
|
||||
|
||||
Or build your site to include all the locales at once:
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
@@ -1,103 +1,132 @@
|
||||
module.exports = {
|
||||
// @ts-check
|
||||
// Note: type annotations allow type checking and IDEs autocompletion
|
||||
|
||||
const lightCodeTheme = require('prism-react-renderer/themes/github');
|
||||
const darkCodeTheme = require('prism-react-renderer/themes/dracula');
|
||||
|
||||
/** @type {import('@docusaurus/types').Config} */
|
||||
const config = {
|
||||
title: 'My Site',
|
||||
tagline: 'The tagline of my site',
|
||||
tagline: 'Dinosaurs are cool',
|
||||
url: 'https://your-docusaurus-test-site.com',
|
||||
baseUrl: '/',
|
||||
onBrokenLinks: 'throw',
|
||||
onBrokenMarkdownLinks: 'warn',
|
||||
favicon: 'img/favicon.ico',
|
||||
|
||||
// GitHub pages deployment config.
|
||||
// If you aren't using GitHub pages, you don't need these.
|
||||
organizationName: 'facebook', // Usually your GitHub org/user name.
|
||||
projectName: 'docusaurus', // Usually your repo name.
|
||||
themeConfig: {
|
||||
navbar: {
|
||||
title: 'My Site',
|
||||
logo: {
|
||||
alt: 'My Site Logo',
|
||||
src: 'img/logo.svg',
|
||||
},
|
||||
links: [
|
||||
{
|
||||
to: 'docs/doc1',
|
||||
activeBasePath: 'docs',
|
||||
label: 'Docs',
|
||||
position: 'left',
|
||||
},
|
||||
{to: 'blog', label: 'Blog', position: 'left'},
|
||||
{
|
||||
href: 'https://github.com/facebook/docusaurus',
|
||||
label: 'GitHub',
|
||||
position: 'right',
|
||||
},
|
||||
],
|
||||
},
|
||||
footer: {
|
||||
style: 'dark',
|
||||
links: [
|
||||
{
|
||||
title: 'Docs',
|
||||
items: [
|
||||
{
|
||||
label: 'Style Guide',
|
||||
to: 'docs/doc1',
|
||||
},
|
||||
{
|
||||
label: 'Second Doc',
|
||||
to: 'docs/doc2',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Community',
|
||||
items: [
|
||||
{
|
||||
label: 'Stack Overflow',
|
||||
href: 'https://stackoverflow.com/questions/tagged/docusaurus',
|
||||
},
|
||||
{
|
||||
label: 'Discord',
|
||||
href: 'https://discordapp.com/invite/docusaurus',
|
||||
},
|
||||
{
|
||||
label: 'Twitter',
|
||||
href: 'https://twitter.com/docusaurus',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'More',
|
||||
items: [
|
||||
{
|
||||
label: 'Blog',
|
||||
to: 'blog',
|
||||
},
|
||||
{
|
||||
label: 'GitHub',
|
||||
href: 'https://github.com/facebook/docusaurus',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
copyright: `Copyright © ${new Date().getFullYear()} My Project, Inc. Built with Docusaurus.`,
|
||||
},
|
||||
|
||||
// Even if you don't use internalization, you can use this field to set useful
|
||||
// metadata like html lang. For example, if your site is Chinese, you may want
|
||||
// to replace "en" with "zh-Hans".
|
||||
i18n: {
|
||||
defaultLocale: 'en',
|
||||
locales: ['en'],
|
||||
},
|
||||
|
||||
presets: [
|
||||
[
|
||||
'@docusaurus/preset-classic',
|
||||
{
|
||||
'classic',
|
||||
/** @type {import('@docusaurus/preset-classic').Options} */
|
||||
({
|
||||
docs: {
|
||||
sidebarPath: require.resolve('./sidebars.js'),
|
||||
// Please change this to your repo.
|
||||
// Remove this to remove the "edit this page" links.
|
||||
editUrl:
|
||||
'https://github.com/facebook/docusaurus/edit/master/website/',
|
||||
'https://github.com/facebook/docusaurus/tree/main/packages/create-docusaurus/templates/shared/',
|
||||
},
|
||||
blog: {
|
||||
showReadingTime: true,
|
||||
// Please change this to your repo.
|
||||
// Remove this to remove the "edit this page" links.
|
||||
editUrl:
|
||||
'https://github.com/facebook/docusaurus/edit/master/website/blog/',
|
||||
'https://github.com/facebook/docusaurus/tree/main/packages/create-docusaurus/templates/shared/',
|
||||
},
|
||||
theme: {
|
||||
customCss: require.resolve('./src/css/custom.css'),
|
||||
},
|
||||
},
|
||||
}),
|
||||
],
|
||||
],
|
||||
|
||||
themeConfig:
|
||||
/** @type {import('@docusaurus/preset-classic').ThemeConfig} */
|
||||
({
|
||||
navbar: {
|
||||
title: 'My Site',
|
||||
logo: {
|
||||
alt: 'My Site Logo',
|
||||
src: 'img/logo.svg',
|
||||
},
|
||||
items: [
|
||||
{
|
||||
type: 'doc',
|
||||
docId: 'intro',
|
||||
position: 'left',
|
||||
label: 'Tutorial',
|
||||
},
|
||||
{to: '/blog', label: 'Blog', position: 'left'},
|
||||
{
|
||||
href: 'https://github.com/facebook/docusaurus',
|
||||
label: 'GitHub',
|
||||
position: 'right',
|
||||
},
|
||||
],
|
||||
},
|
||||
footer: {
|
||||
style: 'dark',
|
||||
links: [
|
||||
{
|
||||
title: 'Docs',
|
||||
items: [
|
||||
{
|
||||
label: 'Tutorial',
|
||||
to: '/docs/intro',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Community',
|
||||
items: [
|
||||
{
|
||||
label: 'Stack Overflow',
|
||||
href: 'https://stackoverflow.com/questions/tagged/docusaurus',
|
||||
},
|
||||
{
|
||||
label: 'Discord',
|
||||
href: 'https://discordapp.com/invite/docusaurus',
|
||||
},
|
||||
{
|
||||
label: 'Twitter',
|
||||
href: 'https://twitter.com/docusaurus',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'More',
|
||||
items: [
|
||||
{
|
||||
label: 'Blog',
|
||||
to: '/blog',
|
||||
},
|
||||
{
|
||||
label: 'GitHub',
|
||||
href: 'https://github.com/facebook/docusaurus',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
copyright: `Copyright © ${new Date().getFullYear()} My Project, Inc. Built with Docusaurus.`,
|
||||
},
|
||||
prism: {
|
||||
theme: lightCodeTheme,
|
||||
darkTheme: darkCodeTheme,
|
||||
},
|
||||
}),
|
||||
};
|
||||
|
||||
module.exports = config;
|
||||
|
||||
@@ -1,23 +1,31 @@
|
||||
{
|
||||
"name": "docusaurus-2",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"docusaurus": "docusaurus",
|
||||
"start": "docusaurus start",
|
||||
"build": "docusaurus build",
|
||||
"swizzle": "docusaurus swizzle",
|
||||
"deploy": "docusaurus deploy"
|
||||
"deploy": "docusaurus deploy",
|
||||
"clear": "docusaurus clear",
|
||||
"serve": "docusaurus serve",
|
||||
"write-translations": "docusaurus write-translations",
|
||||
"write-heading-ids": "docusaurus write-heading-ids"
|
||||
},
|
||||
"dependencies": {
|
||||
"@docusaurus/core": "^2.0.0-alpha.54",
|
||||
"@docusaurus/preset-classic": "^2.0.0-alpha.54",
|
||||
"classnames": "^2.2.6",
|
||||
"react": "^16.8.4",
|
||||
"react-dom": "^16.8.4"
|
||||
"@docusaurus/core": "2.0.1",
|
||||
"@docusaurus/preset-classic": "2.0.1",
|
||||
"@mdx-js/react": "^1.6.22",
|
||||
"clsx": "^1.2.1",
|
||||
"prism-react-renderer": "^1.3.5",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@docusaurus/module-type-aliases": "2.0.1"
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
">0.5%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
@@ -26,5 +34,8 @@
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.14"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,31 @@
|
||||
module.exports = {
|
||||
someSidebar: {
|
||||
Docusaurus: ['doc1', 'doc2', 'doc3'],
|
||||
Features: ['mdx'],
|
||||
},
|
||||
/**
|
||||
* Creating a sidebar enables you to:
|
||||
- create an ordered group of docs
|
||||
- render a sidebar for each doc of that group
|
||||
- provide next/previous navigation
|
||||
|
||||
The sidebars can be generated from the filesystem, or explicitly defined here.
|
||||
|
||||
Create as many sidebars as you want.
|
||||
*/
|
||||
|
||||
// @ts-check
|
||||
|
||||
/** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */
|
||||
const sidebars = {
|
||||
// By default, Docusaurus generates a sidebar from the docs folder structure
|
||||
tutorialSidebar: [{type: 'autogenerated', dirName: '.'}],
|
||||
|
||||
// But you can create a sidebar manually
|
||||
/*
|
||||
tutorialSidebar: [
|
||||
{
|
||||
type: 'category',
|
||||
label: 'Tutorial',
|
||||
items: ['hello'],
|
||||
},
|
||||
],
|
||||
*/
|
||||
};
|
||||
|
||||
module.exports = sidebars;
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
import React from 'react';
|
||||
import clsx from 'clsx';
|
||||
import styles from './styles.module.css';
|
||||
|
||||
const FeatureList = [
|
||||
{
|
||||
title: 'Easy to Use',
|
||||
Svg: require('@site/static/img/undraw_docusaurus_mountain.svg').default,
|
||||
description: (
|
||||
<>
|
||||
Docusaurus was designed from the ground up to be easily installed and
|
||||
used to get your website up and running quickly.
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Focus on What Matters',
|
||||
Svg: require('@site/static/img/undraw_docusaurus_tree.svg').default,
|
||||
description: (
|
||||
<>
|
||||
Docusaurus lets you focus on your docs, and we'll do the chores. Go
|
||||
ahead and move your docs into the <code>docs</code> directory.
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Powered by React',
|
||||
Svg: require('@site/static/img/undraw_docusaurus_react.svg').default,
|
||||
description: (
|
||||
<>
|
||||
Extend or customize your website layout by reusing React. Docusaurus can
|
||||
be extended while reusing the same header and footer.
|
||||
</>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
function Feature({Svg, title, description}) {
|
||||
return (
|
||||
<div className={clsx('col col--4')}>
|
||||
<div className="text--center">
|
||||
<Svg className={styles.featureSvg} role="img" />
|
||||
</div>
|
||||
<div className="text--center padding-horiz--md">
|
||||
<h3>{title}</h3>
|
||||
<p>{description}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function HomepageFeatures() {
|
||||
return (
|
||||
<section className={styles.features}>
|
||||
<div className="container">
|
||||
<div className="row">
|
||||
{FeatureList.map((props, idx) => (
|
||||
<Feature key={idx} {...props} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
.features {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 2rem 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.featureSvg {
|
||||
height: 200px;
|
||||
width: 200px;
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
/* stylelint-disable docusaurus/copyright-header */
|
||||
/**
|
||||
* Any CSS included here will be global. The classic template
|
||||
* bundles Infima by default. Infima is a CSS framework designed to
|
||||
@@ -7,19 +6,25 @@
|
||||
|
||||
/* You can override the default Infima variables here. */
|
||||
:root {
|
||||
--ifm-color-primary: #25c2a0;
|
||||
--ifm-color-primary-dark: rgb(33, 175, 144);
|
||||
--ifm-color-primary-darker: rgb(31, 165, 136);
|
||||
--ifm-color-primary-darkest: rgb(26, 136, 112);
|
||||
--ifm-color-primary-light: rgb(70, 203, 174);
|
||||
--ifm-color-primary-lighter: rgb(102, 212, 189);
|
||||
--ifm-color-primary-lightest: rgb(146, 224, 208);
|
||||
--ifm-color-primary: #2e8555;
|
||||
--ifm-color-primary-dark: #29784c;
|
||||
--ifm-color-primary-darker: #277148;
|
||||
--ifm-color-primary-darkest: #205d3b;
|
||||
--ifm-color-primary-light: #33925d;
|
||||
--ifm-color-primary-lighter: #359962;
|
||||
--ifm-color-primary-lightest: #3cad6e;
|
||||
--ifm-code-font-size: 95%;
|
||||
--docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.docusaurus-highlight-code-line {
|
||||
background-color: rgb(72, 77, 91);
|
||||
display: block;
|
||||
margin: 0 calc(-1 * var(--ifm-pre-padding));
|
||||
padding: 0 var(--ifm-pre-padding);
|
||||
/* For readability concerns, you should choose a lighter palette in dark mode. */
|
||||
[data-theme='dark'] {
|
||||
--ifm-color-primary: #25c2a0;
|
||||
--ifm-color-primary-dark: #21af90;
|
||||
--ifm-color-primary-darker: #1fa588;
|
||||
--ifm-color-primary-darkest: #1a8870;
|
||||
--ifm-color-primary-light: #29d5b0;
|
||||
--ifm-color-primary-lighter: #32d8b4;
|
||||
--ifm-color-primary-lightest: #4fddbf;
|
||||
--docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
@@ -1,97 +1,41 @@
|
||||
import React from 'react';
|
||||
import classnames from 'classnames';
|
||||
import Layout from '@theme/Layout';
|
||||
import clsx from 'clsx';
|
||||
import Link from '@docusaurus/Link';
|
||||
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
|
||||
import useBaseUrl from '@docusaurus/useBaseUrl';
|
||||
import styles from './styles.module.css';
|
||||
import Layout from '@theme/Layout';
|
||||
import HomepageFeatures from '@site/src/components/HomepageFeatures';
|
||||
|
||||
const features = [
|
||||
{
|
||||
title: <>Easy to Use</>,
|
||||
imageUrl: 'img/undraw_docusaurus_mountain.svg',
|
||||
description: (
|
||||
<>
|
||||
Docusaurus was designed from the ground up to be easily installed and
|
||||
used to get your website up and running quickly.
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: <>Focus on What Matters</>,
|
||||
imageUrl: 'img/undraw_docusaurus_tree.svg',
|
||||
description: (
|
||||
<>
|
||||
Docusaurus lets you focus on your docs, and we'll do the chores. Go
|
||||
ahead and move your docs into the <code>docs</code> directory.
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: <>Powered by React</>,
|
||||
imageUrl: 'img/undraw_docusaurus_react.svg',
|
||||
description: (
|
||||
<>
|
||||
Extend or customize your website layout by reusing React. Docusaurus can
|
||||
be extended while reusing the same header and footer.
|
||||
</>
|
||||
),
|
||||
},
|
||||
];
|
||||
import styles from './index.module.css';
|
||||
|
||||
function Feature({imageUrl, title, description}) {
|
||||
const imgUrl = useBaseUrl(imageUrl);
|
||||
function HomepageHeader() {
|
||||
const {siteConfig} = useDocusaurusContext();
|
||||
return (
|
||||
<div className={classnames('col col--4', styles.feature)}>
|
||||
{imgUrl && (
|
||||
<div className="text--center">
|
||||
<img className={styles.featureImage} src={imgUrl} alt={title} />
|
||||
<header className={clsx('hero hero--primary', styles.heroBanner)}>
|
||||
<div className="container">
|
||||
<h1 className="hero__title">{siteConfig.title}</h1>
|
||||
<p className="hero__subtitle">{siteConfig.tagline}</p>
|
||||
<div className={styles.buttons}>
|
||||
<Link
|
||||
className="button button--secondary button--lg"
|
||||
to="/docs/intro">
|
||||
Docusaurus Tutorial - 5min ⏱️
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
<h3>{title}</h3>
|
||||
<p>{description}</p>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
|
||||
function Home() {
|
||||
const context = useDocusaurusContext();
|
||||
const {siteConfig = {}} = context;
|
||||
export default function Home() {
|
||||
const {siteConfig} = useDocusaurusContext();
|
||||
return (
|
||||
<Layout
|
||||
title={`Hello from ${siteConfig.title}`}
|
||||
description="Description will go into a meta tag in <head />">
|
||||
<header className={classnames('hero hero--primary', styles.heroBanner)}>
|
||||
<div className="container">
|
||||
<h1 className="hero__title">{siteConfig.title}</h1>
|
||||
<p className="hero__subtitle">{siteConfig.tagline}</p>
|
||||
<div className={styles.buttons}>
|
||||
<Link
|
||||
className={classnames(
|
||||
'button button--outline button--secondary button--lg',
|
||||
styles.getStarted,
|
||||
)}
|
||||
to={useBaseUrl('docs/doc1')}>
|
||||
Get Started
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<HomepageHeader />
|
||||
<main>
|
||||
{features && features.length && (
|
||||
<section className={styles.features}>
|
||||
<div className="container">
|
||||
<div className="row">
|
||||
{features.map((props, idx) => (
|
||||
<Feature key={idx} {...props} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
<HomepageFeatures />
|
||||
</main>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
|
||||
export default Home;
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
/* stylelint-disable docusaurus/copyright-header */
|
||||
/**
|
||||
* CSS files with the .module.css suffix will be treated as CSS modules
|
||||
* and scoped locally.
|
||||
@@ -11,7 +10,7 @@
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 966px) {
|
||||
@media screen and (max-width: 996px) {
|
||||
.heroBanner {
|
||||
padding: 2rem;
|
||||
}
|
||||
@@ -22,15 +21,3 @@
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.features {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 2rem 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.featureImage {
|
||||
height: 200px;
|
||||
width: 200px;
|
||||
}
|
||||
7
examples/docusaurus-2/src/pages/markdown-page.md
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
title: Markdown page example
|
||||
---
|
||||
|
||||
# Markdown page example
|
||||
|
||||
You don't need React to write simple standalone pages.
|
||||
0
examples/docusaurus-2/static/.nojekyll
Normal file
BIN
examples/docusaurus-2/static/img/docusaurus.png
Normal file
|
After Width: | Height: | Size: 5.0 KiB |
|
Before Width: | Height: | Size: 766 B After Width: | Height: | Size: 3.5 KiB |
@@ -1,4 +1,5 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1088" height="687.962" viewBox="0 0 1088 687.962">
|
||||
<title>Easy to Use</title>
|
||||
<g id="Group_12" data-name="Group 12" transform="translate(-57 -56)">
|
||||
<g id="Group_11" data-name="Group 11" transform="translate(57 56)">
|
||||
<path id="Path_83" data-name="Path 83" d="M1017.81,560.461c-5.27,45.15-16.22,81.4-31.25,110.31-20,38.52-54.21,54.04-84.77,70.28a193.275,193.275,0,0,1-27.46,11.94c-55.61,19.3-117.85,14.18-166.74,3.99a657.282,657.282,0,0,0-104.09-13.16q-14.97-.675-29.97-.67c-15.42.02-293.07,5.29-360.67-131.57-16.69-33.76-28.13-75-32.24-125.27-11.63-142.12,52.29-235.46,134.74-296.47,155.97-115.41,369.76-110.57,523.43,7.88C941.15,276.621,1036.99,396.031,1017.81,560.461Z" transform="translate(-56 -106.019)" fill="#3f3d56"/>
|
||||
|
||||
|
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 31 KiB |
@@ -1,4 +1,5 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1041.277" height="554.141" viewBox="0 0 1041.277 554.141">
|
||||
<title>Powered by React</title>
|
||||
<g id="Group_24" data-name="Group 24" transform="translate(-440 -263)">
|
||||
<g id="Group_23" data-name="Group 23" transform="translate(439.989 262.965)">
|
||||
<path id="Path_299" data-name="Path 299" d="M1040.82,611.12q-1.74,3.75-3.47,7.4-2.7,5.67-5.33,11.12c-.78,1.61-1.56,3.19-2.32,4.77-8.6,17.57-16.63,33.11-23.45,45.89A73.21,73.21,0,0,1,942.44,719l-151.65,1.65h-1.6l-13,.14-11.12.12-34.1.37h-1.38l-17.36.19h-.53l-107,1.16-95.51,1-11.11.12-69,.75H429l-44.75.48h-.48l-141.5,1.53-42.33.46a87.991,87.991,0,0,1-10.79-.54h0c-1.22-.14-2.44-.3-3.65-.49a87.38,87.38,0,0,1-51.29-27.54C116,678.37,102.75,655,93.85,629.64q-1.93-5.49-3.6-11.12C59.44,514.37,97,380,164.6,290.08q4.25-5.64,8.64-11l.07-.08c20.79-25.52,44.1-46.84,68.93-62,44-26.91,92.75-34.49,140.7-11.9,40.57,19.12,78.45,28.11,115.17,30.55,3.71.24,7.42.42,11.11.53,84.23,2.65,163.17-27.7,255.87-47.29,3.69-.78,7.39-1.55,11.12-2.28,66.13-13.16,139.49-20.1,226.73-5.51a189.089,189.089,0,0,1,26.76,6.4q5.77,1.86,11.12,4c41.64,16.94,64.35,48.24,74,87.46q1.37,5.46,2.37,11.11C1134.3,384.41,1084.19,518.23,1040.82,611.12Z" transform="translate(-79.34 -172.91)" fill="#f2f2f2"/>
|
||||
|
||||
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
@@ -99,6 +99,7 @@
|
||||
"selector": "MemberExpression > Identifier[name='substr']"
|
||||
}
|
||||
],
|
||||
"no-dupe-keys": 2,
|
||||
"require-atomic-updates": 0,
|
||||
"@typescript-eslint/ban-ts-comment": 0,
|
||||
"@typescript-eslint/camelcase": 0,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@vercel/build-utils",
|
||||
"version": "5.0.5",
|
||||
"version": "5.3.2",
|
||||
"license": "MIT",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.js",
|
||||
@@ -14,8 +14,7 @@
|
||||
"build": "node build",
|
||||
"test": "jest --env node --verbose --runInBand --bail",
|
||||
"test-unit": "yarn test test/unit.*test.*",
|
||||
"test-integration-once": "yarn test test/integration.test.ts",
|
||||
"prepublishOnly": "node build"
|
||||
"test-integration-once": "yarn test test/integration.test.ts"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@iarna/toml": "2.2.3",
|
||||
@@ -31,7 +30,7 @@
|
||||
"@types/node-fetch": "^2.1.6",
|
||||
"@types/semver": "6.0.0",
|
||||
"@types/yazl": "2.4.2",
|
||||
"@vercel/ncc": "0.34.0",
|
||||
"@vercel/ncc": "0.24.0",
|
||||
"aggregate-error": "3.0.1",
|
||||
"async-retry": "1.2.3",
|
||||
"async-sema": "2.1.4",
|
||||
|
||||
@@ -27,9 +27,7 @@ async function prepareSymlinkTarget(
|
||||
}
|
||||
|
||||
if (file.type === 'FileRef' || file.type === 'FileBlob') {
|
||||
const targetPathBufferPromise = await streamToBuffer(
|
||||
await file.toStreamAsync()
|
||||
);
|
||||
const targetPathBufferPromise = streamToBuffer(await file.toStreamAsync());
|
||||
const [targetPathBuffer] = await Promise.all([
|
||||
targetPathBufferPromise,
|
||||
mkdirPromise,
|
||||
@@ -42,9 +40,15 @@ async function prepareSymlinkTarget(
|
||||
);
|
||||
}
|
||||
|
||||
async function downloadFile(file: File, fsPath: string): Promise<FileFsRef> {
|
||||
export async function downloadFile(
|
||||
file: File,
|
||||
fsPath: string
|
||||
): Promise<FileFsRef> {
|
||||
const { mode } = file;
|
||||
|
||||
// If the source is a symlink, try to create it instead of copying the file.
|
||||
// Note: creating symlinks on Windows requires admin priviliges or symlinks
|
||||
// enabled in the group policy. We may want to improve the error message.
|
||||
if (isSymbolicLink(mode)) {
|
||||
const target = await prepareSymlinkTarget(file, fsPath);
|
||||
|
||||
@@ -92,12 +96,28 @@ export default async function download(
|
||||
await removeFile(basePath, name);
|
||||
return;
|
||||
}
|
||||
|
||||
// If a file didn't change, do not re-download it.
|
||||
if (Array.isArray(filesChanged) && !filesChanged.includes(name)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Some builders resolve symlinks and return both
|
||||
// a file, node_modules/<symlink>/package.json, and
|
||||
// node_modules/<symlink>, a symlink.
|
||||
// Removing the file matches how the yazl lambda zip
|
||||
// behaves so we can use download() with `vercel build`.
|
||||
const parts = name.split('/');
|
||||
for (let i = 1; i < parts.length; i++) {
|
||||
const dir = parts.slice(0, i).join('/');
|
||||
const parent = files[dir];
|
||||
if (parent && isSymbolicLink(parent.mode)) {
|
||||
console.warn(
|
||||
`Warning: file "${name}" is within a symlinked directory "${dir}" and will be ignored`
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const file = files[name];
|
||||
const fsPath = path.join(basePath, name);
|
||||
|
||||
|
||||
@@ -8,17 +8,13 @@ import FileFsRef from '../file-fs-ref';
|
||||
|
||||
export type GlobOptions = vanillaGlob_.IOptions;
|
||||
|
||||
interface FsFiles {
|
||||
[filePath: string]: FileFsRef;
|
||||
}
|
||||
|
||||
const vanillaGlob = promisify(vanillaGlob_);
|
||||
|
||||
export default async function glob(
|
||||
pattern: string,
|
||||
opts: GlobOptions | string,
|
||||
mountpoint?: string
|
||||
): Promise<FsFiles> {
|
||||
): Promise<Record<string, FileFsRef>> {
|
||||
let options: GlobOptions;
|
||||
if (typeof opts === 'string') {
|
||||
options = { cwd: opts };
|
||||
@@ -36,10 +32,11 @@ export default async function glob(
|
||||
throw new Error(`basePath/cwd must be an absolute path (${options.cwd})`);
|
||||
}
|
||||
|
||||
const results: FsFiles = {};
|
||||
const results: Record<string, FileFsRef> = {};
|
||||
const statCache: Record<string, Stats> = {};
|
||||
|
||||
options.symlinks = {};
|
||||
options.statCache = {};
|
||||
options.statCache = statCache;
|
||||
options.stat = true;
|
||||
options.dot = true;
|
||||
|
||||
@@ -47,7 +44,7 @@ export default async function glob(
|
||||
|
||||
for (const relativePath of files) {
|
||||
const fsPath = normalizePath(path.join(options.cwd, relativePath));
|
||||
let stat: Stats = options.statCache[fsPath] as Stats;
|
||||
let stat = statCache[fsPath];
|
||||
assert(
|
||||
stat,
|
||||
`statCache does not contain value for ${relativePath} (resolved to ${fsPath})`
|
||||
|
||||
@@ -10,7 +10,7 @@ const allOptions = [
|
||||
major: 12,
|
||||
range: '12.x',
|
||||
runtime: 'nodejs12.x',
|
||||
discontinueDate: new Date('2022-08-09'),
|
||||
discontinueDate: new Date('2022-10-01'),
|
||||
},
|
||||
{
|
||||
major: 10,
|
||||
|
||||
32
packages/build-utils/src/get-prefixed-env-vars.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
type Envs = { [key: string]: string | undefined };
|
||||
|
||||
/**
|
||||
* Get the framework-specific prefixed System Environment Variables.
|
||||
* See https://vercel.com/docs/concepts/projects/environment-variables#system-environment-variables
|
||||
* @param envPrefix - Prefix, typically from `@vercel/frameworks`
|
||||
* @param envs - Environment Variables, typically from `process.env`
|
||||
*/
|
||||
export function getPrefixedEnvVars({
|
||||
envPrefix,
|
||||
envs,
|
||||
}: {
|
||||
envPrefix: string | undefined;
|
||||
envs: Envs;
|
||||
}): Envs {
|
||||
const vercelSystemEnvPrefix = 'VERCEL_';
|
||||
const newEnvs: Envs = {};
|
||||
if (envPrefix && envs.VERCEL_URL) {
|
||||
Object.keys(envs)
|
||||
.filter(key => key.startsWith(vercelSystemEnvPrefix))
|
||||
.forEach(key => {
|
||||
const newKey = `${envPrefix}${key}`;
|
||||
if (!(newKey in envs)) {
|
||||
newEnvs[newKey] = envs[key];
|
||||
}
|
||||
});
|
||||
// Tell turbo to exclude all Vercel System Env Vars
|
||||
// See https://github.com/vercel/turborepo/pull/1622
|
||||
newEnvs.TURBO_CI_VENDOR_ENV_KEY = `${envPrefix}${vercelSystemEnvPrefix}`;
|
||||
}
|
||||
return newEnvs;
|
||||
}
|
||||
@@ -4,7 +4,11 @@ import FileRef from './file-ref';
|
||||
import { Lambda, createLambda, getLambdaOptionsFromFunction } from './lambda';
|
||||
import { NodejsLambda } from './nodejs-lambda';
|
||||
import { Prerender } from './prerender';
|
||||
import download, { DownloadedFiles, isSymbolicLink } from './fs/download';
|
||||
import download, {
|
||||
downloadFile,
|
||||
DownloadedFiles,
|
||||
isSymbolicLink,
|
||||
} from './fs/download';
|
||||
import getWriteableDirectory from './fs/get-writable-directory';
|
||||
import glob, { GlobOptions } from './fs/glob';
|
||||
import rename from './fs/rename';
|
||||
@@ -36,6 +40,7 @@ import streamToBuffer from './fs/stream-to-buffer';
|
||||
import debug from './debug';
|
||||
import getIgnoreFilter from './get-ignore-filter';
|
||||
import { getPlatformEnv } from './get-platform-env';
|
||||
import { getPrefixedEnvVars } from './get-prefixed-env-vars';
|
||||
|
||||
export {
|
||||
FileBlob,
|
||||
@@ -46,6 +51,7 @@ export {
|
||||
createLambda,
|
||||
Prerender,
|
||||
download,
|
||||
downloadFile,
|
||||
DownloadedFiles,
|
||||
getWriteableDirectory,
|
||||
glob,
|
||||
@@ -71,6 +77,7 @@ export {
|
||||
getDiscontinuedNodeVersions,
|
||||
getSpawnOptions,
|
||||
getPlatformEnv,
|
||||
getPrefixedEnvVars,
|
||||
streamToBuffer,
|
||||
debug,
|
||||
isSymbolicLink,
|
||||
|
||||
@@ -22,6 +22,7 @@ export interface LambdaOptionsBase {
|
||||
allowQuery?: string[];
|
||||
regions?: string[];
|
||||
supportsMultiPayloads?: boolean;
|
||||
supportsWrapper?: boolean;
|
||||
}
|
||||
|
||||
export interface LambdaOptionsWithFiles extends LambdaOptionsBase {
|
||||
@@ -58,6 +59,7 @@ export class Lambda {
|
||||
*/
|
||||
zipBuffer?: Buffer;
|
||||
supportsMultiPayloads?: boolean;
|
||||
supportsWrapper?: boolean;
|
||||
|
||||
constructor(opts: LambdaOptions) {
|
||||
const {
|
||||
@@ -69,6 +71,7 @@ export class Lambda {
|
||||
allowQuery,
|
||||
regions,
|
||||
supportsMultiPayloads,
|
||||
supportsWrapper,
|
||||
} = opts;
|
||||
if ('files' in opts) {
|
||||
assert(typeof opts.files === 'object', '"files" must be an object');
|
||||
@@ -103,6 +106,13 @@ export class Lambda {
|
||||
);
|
||||
}
|
||||
|
||||
if (supportsWrapper !== undefined) {
|
||||
assert(
|
||||
typeof supportsWrapper === 'boolean',
|
||||
'"supportsWrapper" is not a boolean'
|
||||
);
|
||||
}
|
||||
|
||||
if (regions !== undefined) {
|
||||
assert(Array.isArray(regions), '"regions" is not an Array');
|
||||
assert(
|
||||
@@ -121,6 +131,7 @@ export class Lambda {
|
||||
this.regions = regions;
|
||||
this.zipBuffer = 'zipBuffer' in opts ? opts.zipBuffer : undefined;
|
||||
this.supportsMultiPayloads = supportsMultiPayloads;
|
||||
this.supportsWrapper = supportsWrapper;
|
||||
}
|
||||
|
||||
async createZip(): Promise<Buffer> {
|
||||
|
||||
20
packages/build-utils/test/integration.test.ts
vendored
@@ -1,22 +1,12 @@
|
||||
import path from 'path';
|
||||
import fs from 'fs-extra';
|
||||
import {
|
||||
packAndDeploy,
|
||||
testDeployment,
|
||||
// @ts-ignore
|
||||
} from '../../../test/lib/deployment/test-deployment';
|
||||
|
||||
jest.setTimeout(4 * 60 * 1000);
|
||||
|
||||
const builderUrl = '@canary';
|
||||
let buildUtilsUrl: string;
|
||||
|
||||
beforeAll(async () => {
|
||||
const buildUtilsPath = path.resolve(__dirname, '..');
|
||||
buildUtilsUrl = await packAndDeploy(buildUtilsPath);
|
||||
console.log('buildUtilsUrl', buildUtilsUrl);
|
||||
});
|
||||
|
||||
const fixturesPath = path.resolve(__dirname, 'fixtures');
|
||||
|
||||
// Fixtures that have separate tests and should be skipped in the loop
|
||||
@@ -42,10 +32,7 @@ for (const fixture of fs.readdirSync(fixturesPath)) {
|
||||
// eslint-disable-next-line no-loop-func
|
||||
it(`Should build "${fixture}"`, async () => {
|
||||
await expect(
|
||||
testDeployment(
|
||||
{ builderUrl, buildUtilsUrl },
|
||||
path.join(fixturesPath, fixture)
|
||||
)
|
||||
testDeployment(path.join(fixturesPath, fixture))
|
||||
).resolves.toBeDefined();
|
||||
});
|
||||
}
|
||||
@@ -68,10 +55,7 @@ for (const builder of buildersToTestWith) {
|
||||
// eslint-disable-next-line no-loop-func
|
||||
it(`Should build "${builder}/${fixture}"`, async () => {
|
||||
await expect(
|
||||
testDeployment(
|
||||
{ builderUrl, buildUtilsUrl },
|
||||
path.join(fixturesPath2, fixture)
|
||||
)
|
||||
testDeployment(path.join(fixturesPath2, fixture))
|
||||
).resolves.toBeDefined();
|
||||
});
|
||||
}
|
||||
|
||||
87
packages/build-utils/test/unit.get-prefixed-env-vars.test.ts
vendored
Normal file
@@ -0,0 +1,87 @@
|
||||
import { getPrefixedEnvVars } from '../src';
|
||||
|
||||
describe('Test `getPrefixedEnvVars()`', () => {
|
||||
const cases: Array<{
|
||||
name: string;
|
||||
args: Parameters<typeof getPrefixedEnvVars>[0];
|
||||
want: ReturnType<typeof getPrefixedEnvVars>;
|
||||
}> = [
|
||||
{
|
||||
name: 'should work with NEXT_PUBLIC_',
|
||||
args: {
|
||||
envPrefix: 'NEXT_PUBLIC_',
|
||||
envs: {
|
||||
VERCEL: '1',
|
||||
VERCEL_URL: 'example.vercel.sh',
|
||||
USER_ENV_VAR_NOT_VERCEL: 'example.com',
|
||||
FOO: 'bar',
|
||||
},
|
||||
},
|
||||
want: {
|
||||
NEXT_PUBLIC_VERCEL_URL: 'example.vercel.sh',
|
||||
TURBO_CI_VENDOR_ENV_KEY: 'NEXT_PUBLIC_VERCEL_',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'should work with GATSBY_',
|
||||
args: {
|
||||
envPrefix: 'GATSBY_',
|
||||
envs: {
|
||||
USER_ENV_VAR_NOT_VERCEL: 'example.com',
|
||||
FOO: 'bar',
|
||||
VERCEL_URL: 'example.vercel.sh',
|
||||
VERCEL_ENV: 'production',
|
||||
VERCEL_REGION: 'iad1',
|
||||
VERCEL_GIT_COMMIT_AUTHOR_LOGIN: 'rauchg',
|
||||
},
|
||||
},
|
||||
want: {
|
||||
GATSBY_VERCEL_URL: 'example.vercel.sh',
|
||||
GATSBY_VERCEL_ENV: 'production',
|
||||
GATSBY_VERCEL_REGION: 'iad1',
|
||||
GATSBY_VERCEL_GIT_COMMIT_AUTHOR_LOGIN: 'rauchg',
|
||||
TURBO_CI_VENDOR_ENV_KEY: 'GATSBY_VERCEL_',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'should not return anything if no system env vars detected',
|
||||
args: {
|
||||
envPrefix: 'GATSBY_',
|
||||
envs: {
|
||||
USER_ENV_VAR_NOT_VERCEL: 'example.com',
|
||||
FOO: 'bar',
|
||||
BLARG_VERCEL_THING: 'fake',
|
||||
},
|
||||
},
|
||||
want: {},
|
||||
},
|
||||
{
|
||||
name: 'should not return anything if envPrefix is empty string',
|
||||
args: {
|
||||
envPrefix: '',
|
||||
envs: {
|
||||
VERCEL: '1',
|
||||
VERCEL_URL: 'example.vercel.sh',
|
||||
},
|
||||
},
|
||||
want: {},
|
||||
},
|
||||
{
|
||||
name: 'should not return anything if envPrefix is undefined',
|
||||
args: {
|
||||
envPrefix: undefined,
|
||||
envs: {
|
||||
VERCEL: '1',
|
||||
VERCEL_URL: 'example.vercel.sh',
|
||||
},
|
||||
},
|
||||
want: {},
|
||||
},
|
||||
];
|
||||
|
||||
for (const { name, args, want } of cases) {
|
||||
it(name, () => {
|
||||
expect(getPrefixedEnvVars(args)).toEqual(want);
|
||||
});
|
||||
}
|
||||
});
|
||||
53
packages/build-utils/test/unit.test.ts
vendored
@@ -170,6 +170,53 @@ it('should create zip files with symlinks properly', async () => {
|
||||
assert(aStat.isFile());
|
||||
});
|
||||
|
||||
it('should download symlinks even with incorrect file', async () => {
|
||||
if (process.platform === 'win32') {
|
||||
console.log('Skipping test on windows');
|
||||
return;
|
||||
}
|
||||
const files = {
|
||||
'dir/file.txt': new FileBlob({
|
||||
mode: 33188,
|
||||
contentType: undefined,
|
||||
data: 'file text',
|
||||
}),
|
||||
linkdir: new FileBlob({
|
||||
mode: 41453,
|
||||
contentType: undefined,
|
||||
data: 'dir',
|
||||
}),
|
||||
'linkdir/file.txt': new FileBlob({
|
||||
mode: 33188,
|
||||
contentType: undefined,
|
||||
data: 'this file should be discarded',
|
||||
}),
|
||||
};
|
||||
|
||||
const outDir = path.join(__dirname, 'symlinks-out');
|
||||
await fs.remove(outDir);
|
||||
await fs.mkdirp(outDir);
|
||||
|
||||
await download(files, outDir);
|
||||
|
||||
const [dir, file, linkdir] = await Promise.all([
|
||||
fs.lstat(path.join(outDir, 'dir')),
|
||||
fs.lstat(path.join(outDir, 'dir/file.txt')),
|
||||
fs.lstat(path.join(outDir, 'linkdir')),
|
||||
]);
|
||||
expect(dir.isFile()).toBe(false);
|
||||
expect(dir.isSymbolicLink()).toBe(false);
|
||||
|
||||
expect(file.isFile()).toBe(true);
|
||||
expect(file.isSymbolicLink()).toBe(false);
|
||||
|
||||
expect(linkdir.isSymbolicLink()).toBe(true);
|
||||
|
||||
expect(warningMessages).toEqual([
|
||||
'Warning: file "linkdir/file.txt" is within a symlinked directory "linkdir" and will be ignored',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should only match supported node versions, otherwise throw an error', async () => {
|
||||
expect(await getSupportedNodeVersion('12.x', false)).toHaveProperty(
|
||||
'major',
|
||||
@@ -347,7 +394,7 @@ it('should get latest node version', async () => {
|
||||
it('should throw for discontinued versions', async () => {
|
||||
// Mock a future date so that Node 8 and 10 become discontinued
|
||||
const realDateNow = Date.now.bind(global.Date);
|
||||
global.Date.now = () => new Date('2022-09-01').getTime();
|
||||
global.Date.now = () => new Date('2022-10-15').getTime();
|
||||
|
||||
expect(getSupportedNodeVersion('8.10.x', false)).rejects.toThrow();
|
||||
expect(getSupportedNodeVersion('8.10.x', true)).rejects.toThrow();
|
||||
@@ -389,8 +436,8 @@ it('should warn for deprecated versions, soon to be discontinued', async () => {
|
||||
expect(warningMessages).toStrictEqual([
|
||||
'Error: Node.js version 10.x has reached End-of-Life. Deployments created on or after 2021-04-20 will fail to build. Please set "engines": { "node": "16.x" } in your `package.json` file to use Node.js 16.',
|
||||
'Error: Node.js version 10.x has reached End-of-Life. Deployments created on or after 2021-04-20 will fail to build. Please set Node.js Version to 16.x in your Project Settings to use Node.js 16.',
|
||||
'Error: Node.js version 12.x has reached End-of-Life. Deployments created on or after 2022-08-09 will fail to build. Please set "engines": { "node": "16.x" } in your `package.json` file to use Node.js 16.',
|
||||
'Error: Node.js version 12.x has reached End-of-Life. Deployments created on or after 2022-08-09 will fail to build. Please set Node.js Version to 16.x in your Project Settings to use Node.js 16.',
|
||||
'Error: Node.js version 12.x has reached End-of-Life. Deployments created on or after 2022-10-01 will fail to build. Please set "engines": { "node": "16.x" } in your `package.json` file to use Node.js 16.',
|
||||
'Error: Node.js version 12.x has reached End-of-Life. Deployments created on or after 2022-10-01 will fail to build. Please set Node.js Version to 16.x in your Project Settings to use Node.js 16.',
|
||||
]);
|
||||
|
||||
global.Date.now = realDateNow;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "vercel",
|
||||
"version": "27.3.0",
|
||||
"version": "28.1.4",
|
||||
"preferGlobal": true,
|
||||
"license": "Apache-2.0",
|
||||
"description": "The command-line interface for Vercel",
|
||||
@@ -16,7 +16,6 @@
|
||||
"test-unit": "yarn test test/unit/",
|
||||
"test-integration-cli": "rimraf test/fixtures/integration && ava test/integration.js --serial --fail-fast --verbose",
|
||||
"test-integration-dev": "yarn test test/dev/",
|
||||
"prepublishOnly": "yarn build",
|
||||
"coverage": "codecov",
|
||||
"build": "ts-node ./scripts/build.ts",
|
||||
"dev": "ts-node ./src/index.ts"
|
||||
@@ -42,16 +41,16 @@
|
||||
"node": ">= 14"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vercel/build-utils": "5.0.5",
|
||||
"@vercel/go": "2.0.9",
|
||||
"@vercel/hydrogen": "0.0.6",
|
||||
"@vercel/next": "3.1.9",
|
||||
"@vercel/node": "2.5.0",
|
||||
"@vercel/python": "3.1.1",
|
||||
"@vercel/redwood": "1.0.10",
|
||||
"@vercel/remix": "1.0.11",
|
||||
"@vercel/ruby": "1.3.17",
|
||||
"@vercel/static-build": "1.0.10",
|
||||
"@vercel/build-utils": "5.3.2",
|
||||
"@vercel/go": "2.2.2",
|
||||
"@vercel/hydrogen": "0.0.15",
|
||||
"@vercel/next": "3.1.21",
|
||||
"@vercel/node": "2.5.10",
|
||||
"@vercel/python": "3.1.11",
|
||||
"@vercel/redwood": "1.0.19",
|
||||
"@vercel/remix": "1.0.20",
|
||||
"@vercel/ruby": "1.3.28",
|
||||
"@vercel/static-build": "1.0.19",
|
||||
"update-notifier": "5.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -97,11 +96,11 @@
|
||||
"@types/which": "1.3.2",
|
||||
"@types/write-json-file": "2.2.1",
|
||||
"@types/yauzl-promise": "2.1.0",
|
||||
"@vercel/client": "12.1.4",
|
||||
"@vercel/frameworks": "1.1.1",
|
||||
"@vercel/fs-detectors": "2.0.1",
|
||||
"@vercel/client": "12.2.1",
|
||||
"@vercel/frameworks": "1.1.3",
|
||||
"@vercel/fs-detectors": "2.0.5",
|
||||
"@vercel/fun": "1.0.4",
|
||||
"@vercel/ncc": "0.34.0",
|
||||
"@vercel/ncc": "0.24.0",
|
||||
"@zeit/source-map-support": "0.6.2",
|
||||
"ajv": "6.12.2",
|
||||
"alpha-sort": "2.0.1",
|
||||
@@ -119,7 +118,6 @@
|
||||
"chokidar": "3.3.1",
|
||||
"codecov": "3.8.2",
|
||||
"cpy": "7.2.0",
|
||||
"credit-card": "3.0.1",
|
||||
"date-fns": "1.29.0",
|
||||
"debug": "3.1.0",
|
||||
"dot": "1.1.3",
|
||||
|
||||
@@ -37,6 +37,7 @@ const help = () => {
|
||||
)} Login token
|
||||
-S, --scope Set a custom scope
|
||||
-N, --next Show next page of results
|
||||
-y, --yes Skip the confirmation prompt when removing an alias
|
||||
|
||||
${chalk.dim('Examples:')}
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ export default async function rm(
|
||||
|
||||
const removeStamp = stamp();
|
||||
if (!opts['--yes'] && !(await confirmAliasRemove(client, alias))) {
|
||||
output.log('Aborted');
|
||||
output.log('Canceled');
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -220,7 +220,7 @@ function handleSetupDomainError<T>(
|
||||
}
|
||||
|
||||
if (error instanceof ERRORS.UserAborted) {
|
||||
output.error(`User aborted`);
|
||||
output.error(`User canceled.`);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,170 +0,0 @@
|
||||
import ansiEscapes from 'ansi-escapes';
|
||||
import chalk from 'chalk';
|
||||
import ccValidator from 'credit-card';
|
||||
import textInput from '../../util/input/text';
|
||||
import cardBrands from '../../util/billing/card-brands';
|
||||
import success from '../../util/output/success';
|
||||
import wait from '../../util/output/wait';
|
||||
import chars from '../../util/output/chars';
|
||||
import error from '../../util/output/error';
|
||||
|
||||
const expDateMiddleware = data => data;
|
||||
|
||||
export default async function ({ creditCards, clear = false, contextName }) {
|
||||
const state = {
|
||||
error: undefined,
|
||||
cardGroupLabel: `> ${chalk.bold(
|
||||
`Enter your card details for ${chalk.bold(contextName)}`
|
||||
)}`,
|
||||
|
||||
name: {
|
||||
label: 'Full Name'.padEnd(12),
|
||||
placeholder: 'John Appleseed',
|
||||
validateValue: data => data.trim().length > 0,
|
||||
},
|
||||
|
||||
cardNumber: {
|
||||
label: 'Number'.padEnd(12),
|
||||
mask: 'cc',
|
||||
placeholder: '#### #### #### ####',
|
||||
validateKeypress: (data, value) => /\d/.test(data) && value.length < 19,
|
||||
validateValue: data => {
|
||||
data = data.replace(/ /g, '');
|
||||
const type = ccValidator.determineCardType(data);
|
||||
if (!type) {
|
||||
return false;
|
||||
}
|
||||
return ccValidator.isValidCardNumber(data, type);
|
||||
},
|
||||
},
|
||||
|
||||
ccv: {
|
||||
label: 'CCV'.padEnd(12),
|
||||
mask: 'ccv',
|
||||
placeholder: '###',
|
||||
validateValue: data => {
|
||||
const brand = state.cardNumber.brand.toLowerCase();
|
||||
return ccValidator.doesCvvMatchType(data, brand);
|
||||
},
|
||||
},
|
||||
|
||||
expDate: {
|
||||
label: 'Exp. Date'.padEnd(12),
|
||||
mask: 'expDate',
|
||||
placeholder: 'mm / yyyy',
|
||||
middleware: expDateMiddleware,
|
||||
validateValue: data => !ccValidator.isExpired(...data.split(' / ')),
|
||||
},
|
||||
};
|
||||
|
||||
async function render() {
|
||||
for (const key in state) {
|
||||
if (!Object.hasOwnProperty.call(state, key)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const piece = state[key];
|
||||
|
||||
if (typeof piece === 'string') {
|
||||
console.log(piece);
|
||||
} else if (typeof piece === 'object') {
|
||||
let result;
|
||||
|
||||
try {
|
||||
/* eslint-disable no-await-in-loop */
|
||||
result = await textInput({
|
||||
label: `- ${piece.label}`,
|
||||
initialValue: piece.initialValue || piece.value,
|
||||
placeholder: piece.placeholder,
|
||||
mask: piece.mask,
|
||||
validateKeypress: piece.validateKeypress,
|
||||
validateValue: piece.validateValue,
|
||||
autoComplete: piece.autoComplete,
|
||||
});
|
||||
|
||||
piece.value = result;
|
||||
|
||||
if (key === 'cardNumber') {
|
||||
let brand = cardBrands[ccValidator.determineCardType(result)];
|
||||
piece.brand = brand;
|
||||
|
||||
if (brand === 'American Express') {
|
||||
state.ccv.placeholder = '#'.repeat(4);
|
||||
} else {
|
||||
state.ccv.placeholder = '#'.repeat(3);
|
||||
}
|
||||
|
||||
brand = chalk.cyan(`[${brand}]`);
|
||||
const masked = chalk.gray('#### '.repeat(3)) + result.split(' ')[3];
|
||||
process.stdout.write(
|
||||
`${chalk.cyan(chars.tick)} ${piece.label}${masked} ${brand}\n`
|
||||
);
|
||||
} else if (key === 'ccv') {
|
||||
process.stdout.write(
|
||||
`${chalk.cyan(chars.tick)} ${piece.label}${'*'.repeat(
|
||||
result.length
|
||||
)}\n`
|
||||
);
|
||||
} else if (key === 'expDate') {
|
||||
let text = result.split(' / ');
|
||||
text = text[0] + chalk.gray(' / ') + text[1];
|
||||
process.stdout.write(
|
||||
`${chalk.cyan(chars.tick)} ${piece.label}${text}\n`
|
||||
);
|
||||
} else {
|
||||
process.stdout.write(
|
||||
`${chalk.cyan(chars.tick)} ${piece.label}${result}\n`
|
||||
);
|
||||
}
|
||||
} catch (err) {
|
||||
if (err.message === 'USER_ABORT') {
|
||||
process.exit(1);
|
||||
} else {
|
||||
console.error(error(err));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(''); // New line
|
||||
const stopSpinner = wait(process.stderr, 'Saving card');
|
||||
|
||||
try {
|
||||
const res = await creditCards.add({
|
||||
name: state.name.value,
|
||||
cardNumber: state.cardNumber.value,
|
||||
ccv: state.ccv.value,
|
||||
expDate: state.expDate.value,
|
||||
});
|
||||
|
||||
stopSpinner();
|
||||
|
||||
if (clear) {
|
||||
const linesToClear = state.error ? 15 : 14;
|
||||
process.stdout.write(ansiEscapes.eraseLines(linesToClear));
|
||||
}
|
||||
|
||||
console.log(
|
||||
success(
|
||||
`${state.cardNumber.brand || state.cardNumber.card.brand} ending in ${
|
||||
res.last4 || res.card.last4
|
||||
} was added to ${chalk.bold(contextName)}`
|
||||
)
|
||||
);
|
||||
} catch (err) {
|
||||
stopSpinner();
|
||||
const linesToClear = state.error ? 15 : 14;
|
||||
process.stdout.write(ansiEscapes.eraseLines(linesToClear));
|
||||
state.error = `${chalk.red('> Error!')} ${
|
||||
err.message
|
||||
} Please make sure the info is correct`;
|
||||
await render();
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
await render();
|
||||
} catch (err) {
|
||||
console.erorr(err);
|
||||
}
|
||||
}
|
||||
@@ -1,353 +0,0 @@
|
||||
import chalk from 'chalk';
|
||||
import ms from 'ms';
|
||||
import plural from 'pluralize';
|
||||
import { error } from '../../util/error';
|
||||
import NowCreditCards from '../../util/credit-cards';
|
||||
import indent from '../../util/output/indent';
|
||||
import listInput from '../../util/input/list';
|
||||
import success from '../../util/output/success';
|
||||
import promptBool from '../../util/input/prompt-bool';
|
||||
import info from '../../util/output/info';
|
||||
import logo from '../../util/output/logo';
|
||||
import addBilling from './add';
|
||||
import exit from '../../util/exit';
|
||||
import getScope from '../../util/get-scope.ts';
|
||||
import { getPkgName } from '../../util/pkg-name.ts';
|
||||
import getArgs from '../../util/get-args.ts';
|
||||
import handleError from '../../util/handle-error.ts';
|
||||
|
||||
const help = () => {
|
||||
console.log(`
|
||||
${chalk.bold(`${logo} ${getPkgName()} billing`)} [options] <command>
|
||||
|
||||
${chalk.dim('Options:')}
|
||||
|
||||
ls Show all of your credit cards
|
||||
add Add a new credit card
|
||||
rm [id] Remove a credit card
|
||||
set-default [id] Make a credit card your default one
|
||||
|
||||
${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
|
||||
-d, --debug Debug mode [off]
|
||||
-t ${chalk.bold.underline('TOKEN')}, --token=${chalk.bold.underline(
|
||||
'TOKEN'
|
||||
)} Login token
|
||||
-S, --scope Set a custom scope
|
||||
|
||||
${chalk.dim('Examples:')}
|
||||
|
||||
${chalk.gray('–')} Add a new credit card (interactively)
|
||||
|
||||
${chalk.cyan(`$ ${getPkgName()} billing add`)}
|
||||
`);
|
||||
};
|
||||
|
||||
let argv;
|
||||
let subcommand;
|
||||
|
||||
export default async client => {
|
||||
try {
|
||||
argv = getArgs(client.argv.slice(2), {});
|
||||
} catch (error) {
|
||||
handleError(error);
|
||||
return 1;
|
||||
}
|
||||
|
||||
argv._ = argv._.slice(1);
|
||||
|
||||
subcommand = argv._[0];
|
||||
|
||||
if (argv['--help'] || !subcommand) {
|
||||
help();
|
||||
return 2;
|
||||
}
|
||||
|
||||
const {
|
||||
output,
|
||||
config: { currentTeam },
|
||||
} = client;
|
||||
|
||||
const start = new Date();
|
||||
const creditCards = new NowCreditCards({
|
||||
client,
|
||||
currentTeam,
|
||||
});
|
||||
|
||||
let contextName = null;
|
||||
|
||||
try {
|
||||
({ contextName } = await getScope(client));
|
||||
} catch (err) {
|
||||
if (err.code === 'NOT_AUTHORIZED' || err.code === 'TEAM_DELETED') {
|
||||
output.error(err.message);
|
||||
return 1;
|
||||
}
|
||||
|
||||
throw err;
|
||||
}
|
||||
|
||||
const args = argv._.slice(1);
|
||||
|
||||
switch (subcommand) {
|
||||
case 'ls':
|
||||
case 'list': {
|
||||
let cards;
|
||||
|
||||
try {
|
||||
cards = await creditCards.ls();
|
||||
} catch (err) {
|
||||
console.error(error(err.message));
|
||||
return 1;
|
||||
}
|
||||
|
||||
const text = cards.sources
|
||||
.map(source => {
|
||||
const _default =
|
||||
source.id === cards.defaultSource
|
||||
? ` ${chalk.bold('(default)')}`
|
||||
: '';
|
||||
const id = `${chalk.gray('-')} ${chalk.cyan(
|
||||
`ID: ${source.id}`
|
||||
)}${_default}`;
|
||||
const number = `${chalk.gray('#### ').repeat(3)}${
|
||||
source.last4 || source.card.last4
|
||||
}`;
|
||||
|
||||
return [
|
||||
id,
|
||||
indent(source.name || source.owner.name, 2),
|
||||
indent(`${source.brand || source.card.brand} ${number}`, 2),
|
||||
].join('\n');
|
||||
})
|
||||
.join('\n\n');
|
||||
|
||||
const elapsed = ms(new Date() - start);
|
||||
console.log(
|
||||
`> ${plural(
|
||||
'card',
|
||||
cards.sources.length,
|
||||
true
|
||||
)} found under ${chalk.bold(contextName)} ${chalk.gray(`[${elapsed}]`)}`
|
||||
);
|
||||
if (text) {
|
||||
console.log(`\n${text}\n`);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 'set-default': {
|
||||
if (args.length > 1) {
|
||||
console.error(error('Invalid number of arguments'));
|
||||
return 1;
|
||||
}
|
||||
|
||||
const start = new Date();
|
||||
|
||||
let cards;
|
||||
try {
|
||||
cards = await creditCards.ls();
|
||||
} catch (err) {
|
||||
console.error(error(err.message));
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (cards.sources.length === 0) {
|
||||
console.error(error('You have no credit cards to choose from'));
|
||||
return 0;
|
||||
}
|
||||
|
||||
let cardId = args[0];
|
||||
|
||||
if (cardId === undefined) {
|
||||
const elapsed = ms(new Date() - start);
|
||||
const message = `Selecting a new default payment card for ${chalk.bold(
|
||||
contextName
|
||||
)} ${chalk.gray(`[${elapsed}]`)}`;
|
||||
const choices = buildInquirerChoices(cards);
|
||||
|
||||
cardId = await listInput(client, {
|
||||
message,
|
||||
choices,
|
||||
separator: true,
|
||||
abort: 'end',
|
||||
});
|
||||
}
|
||||
|
||||
// Check if the provided cardId (in case the user
|
||||
// typed `vercel billing set-default <some-id>`) is valid
|
||||
if (cardId) {
|
||||
const label = `Are you sure that you to set this card as the default?`;
|
||||
const confirmation = await promptBool(label, {
|
||||
...client,
|
||||
trailing: '\n',
|
||||
});
|
||||
|
||||
if (!confirmation) {
|
||||
console.log(info('Aborted'));
|
||||
break;
|
||||
}
|
||||
|
||||
const start = new Date();
|
||||
await creditCards.setDefault(cardId);
|
||||
|
||||
const card = cards.sources.find(card => card.id === cardId);
|
||||
const elapsed = ms(new Date() - start);
|
||||
console.log(
|
||||
success(
|
||||
`${card.brand || card.card.brand} ending in ${
|
||||
card.last4 || card.card.last4
|
||||
} is now the default ${chalk.gray(`[${elapsed}]`)}`
|
||||
)
|
||||
);
|
||||
} else {
|
||||
console.log('No changes made');
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 'rm':
|
||||
case 'remove': {
|
||||
if (args.length > 1) {
|
||||
console.error(error('Invalid number of arguments'));
|
||||
return 1;
|
||||
}
|
||||
|
||||
const start = new Date();
|
||||
let cards;
|
||||
try {
|
||||
cards = await creditCards.ls();
|
||||
} catch (err) {
|
||||
console.error(error(err.message));
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (cards.sources.length === 0) {
|
||||
console.error(
|
||||
error(
|
||||
`You have no credit cards to choose from to delete under ${chalk.bold(
|
||||
contextName
|
||||
)}`
|
||||
)
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
|
||||
let cardId = args[0];
|
||||
|
||||
if (cardId === undefined) {
|
||||
const elapsed = ms(new Date() - start);
|
||||
const message = `Selecting a card to ${chalk.underline(
|
||||
'remove'
|
||||
)} under ${chalk.bold(contextName)} ${chalk.gray(`[${elapsed}]`)}`;
|
||||
const choices = buildInquirerChoices(cards);
|
||||
|
||||
cardId = await listInput(client, {
|
||||
message,
|
||||
choices,
|
||||
separator: true,
|
||||
abort: 'start',
|
||||
});
|
||||
}
|
||||
|
||||
// Shoud check if the provided cardId (in case the user
|
||||
// typed `vercel billing rm <some-id>`) is valid
|
||||
if (cardId) {
|
||||
const label = `Are you sure that you want to remove this card?`;
|
||||
const confirmation = await promptBool(label, client);
|
||||
if (!confirmation) {
|
||||
console.log('Aborted');
|
||||
break;
|
||||
}
|
||||
const start = new Date();
|
||||
await creditCards.rm(cardId);
|
||||
|
||||
const deletedCard = cards.sources.find(card => card.id === cardId);
|
||||
const remainingCards = cards.sources.filter(card => card.id !== cardId);
|
||||
|
||||
let text = `${deletedCard.brand || deletedCard.card.brand} ending in ${
|
||||
deletedCard.last4 || deletedCard.card.last4
|
||||
} was deleted`;
|
||||
// ${chalk.gray(`[${elapsed}]`)}
|
||||
|
||||
if (cardId === cards.defaultSource) {
|
||||
if (remainingCards.length === 0) {
|
||||
// The user deleted the last card in their account
|
||||
text += `\n${chalk.yellow('Warning!')} You have no default card`;
|
||||
} else {
|
||||
// We can't guess the current default card – let's ask the API
|
||||
const cards = await creditCards.ls();
|
||||
const newDefaultCard = cards.sources.find(
|
||||
card => card.id === cards.defaultCardId
|
||||
);
|
||||
|
||||
text += `\n${
|
||||
newDefaultCard.brand || newDefaultCard.card.brand
|
||||
} ending in ${
|
||||
newDefaultCard.last4 || newDefaultCard.card.last4
|
||||
} in now default for ${chalk.bold(contextName)}`;
|
||||
}
|
||||
}
|
||||
|
||||
const elapsed = ms(new Date() - start);
|
||||
text += ` ${chalk.gray(`[${elapsed}]`)}`;
|
||||
console.log(success(text));
|
||||
} else {
|
||||
console.log('No changes made');
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 'add': {
|
||||
await addBilling({
|
||||
creditCards,
|
||||
contextName,
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
console.error(
|
||||
error('Please specify a valid subcommand: ls | add | rm | set-default')
|
||||
);
|
||||
help();
|
||||
return 1;
|
||||
}
|
||||
|
||||
// This is required, otherwise we get those weird zlib errors
|
||||
return exit(0);
|
||||
};
|
||||
|
||||
// Builds a `choices` object that can be passesd to inquirer.prompt()
|
||||
function buildInquirerChoices(cards) {
|
||||
return cards.sources.map(source => {
|
||||
const _default =
|
||||
source.id === cards.defaultSource ? ` ${chalk.bold('(default)')}` : '';
|
||||
const id = `${chalk.cyan(`ID: ${source.id}`)}${_default}`;
|
||||
const number = `${chalk.gray('#### ').repeat(3)}${
|
||||
source.last4 || source.card.last4
|
||||
}`;
|
||||
const str = [
|
||||
id,
|
||||
indent(source.name || source.owner.name, 2),
|
||||
indent(`${source.brand || source.card.brand} ${number}`, 2),
|
||||
].join('\n');
|
||||
|
||||
return {
|
||||
name: str, // Will be displayed by Inquirer
|
||||
value: source.id, // Will be used to identify the answer
|
||||
short: source.id, // Will be displayed after the users answers
|
||||
};
|
||||
});
|
||||
}
|
||||
@@ -116,8 +116,6 @@ export default async function main(client: Client): Promise<number> {
|
||||
}
|
||||
}
|
||||
|
||||
const badDeploymentPromise = getDeployment(client, bad).catch(err => err);
|
||||
|
||||
good = normalizeURL(good);
|
||||
parsed = parse(good);
|
||||
if (!parsed.hostname) {
|
||||
@@ -138,8 +136,6 @@ export default async function main(client: Client): Promise<number> {
|
||||
);
|
||||
}
|
||||
|
||||
const goodDeploymentPromise = getDeployment(client, good).catch(err => err);
|
||||
|
||||
if (!subpath) {
|
||||
subpath = await prompt(
|
||||
client,
|
||||
@@ -148,10 +144,9 @@ export default async function main(client: Client): Promise<number> {
|
||||
}
|
||||
|
||||
output.spinner('Retrieving deployments…');
|
||||
const [badDeployment, goodDeployment] = await Promise.all([
|
||||
badDeploymentPromise,
|
||||
goodDeploymentPromise,
|
||||
]);
|
||||
|
||||
// `getDeployment` cannot be parallelized because it might prompt for login
|
||||
const badDeployment = await getDeployment(client, bad).catch(err => err);
|
||||
|
||||
if (badDeployment) {
|
||||
if (badDeployment instanceof Error) {
|
||||
@@ -165,7 +160,8 @@ export default async function main(client: Client): Promise<number> {
|
||||
return 1;
|
||||
}
|
||||
|
||||
const { projectId } = badDeployment;
|
||||
// `getDeployment` cannot be parallelized because it might prompt for login
|
||||
const goodDeployment = await getDeployment(client, good).catch(err => err);
|
||||
|
||||
if (goodDeployment) {
|
||||
if (goodDeployment instanceof Error) {
|
||||
@@ -181,6 +177,8 @@ export default async function main(client: Client): Promise<number> {
|
||||
return 1;
|
||||
}
|
||||
|
||||
const { projectId } = badDeployment;
|
||||
|
||||
if (projectId !== goodDeployment.projectId) {
|
||||
output.error(`Good and Bad deployments must be from the same Project`);
|
||||
return 1;
|
||||
|
||||
@@ -91,7 +91,7 @@ const help = () => {
|
||||
--output [path] Directory where built assets should be written to
|
||||
--prod Build a production deployment
|
||||
-d, --debug Debug mode [off]
|
||||
-y, --yes Skip the confirmation prompt
|
||||
-y, --yes Skip the confirmation prompt about pulling environment variables and project settings when not found locally
|
||||
|
||||
${chalk.dim('Examples:')}
|
||||
|
||||
@@ -157,7 +157,7 @@ export default async function main(client: Client): Promise<number> {
|
||||
client.output.print(
|
||||
`No Project Settings found locally. Run ${cli.getCommandName(
|
||||
'pull --yes'
|
||||
)} to retreive them.`
|
||||
)} to retrieve them.`
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
@@ -171,7 +171,7 @@ export default async function main(client: Client): Promise<number> {
|
||||
);
|
||||
}
|
||||
if (!confirmed) {
|
||||
client.output.print(`Aborted. No Project Settings retrieved.\n`);
|
||||
client.output.print(`Canceled. No Project Settings retrieved.\n`);
|
||||
return 0;
|
||||
}
|
||||
const { argv: originalArgv } = client;
|
||||
@@ -469,6 +469,8 @@ async function doBuild(
|
||||
)
|
||||
);
|
||||
} catch (err: any) {
|
||||
output.prettyError(err);
|
||||
|
||||
const writeConfigJsonPromise = fs.writeJSON(
|
||||
join(outputDir, 'config.json'),
|
||||
{ version: 3 },
|
||||
|
||||
@@ -9,33 +9,33 @@ export const help = () => `
|
||||
|
||||
${chalk.dim('Basic')}
|
||||
|
||||
billing Manages the account payment methods
|
||||
deploy [path] Performs a deployment ${chalk.bold(
|
||||
'(default)'
|
||||
)}
|
||||
dev Start a local development server
|
||||
env Manages the Environment Variables for your current Project
|
||||
git Manage Git provider repository for your current Project
|
||||
help [cmd] Displays complete help for [cmd]
|
||||
init [example] Initialize an example project
|
||||
ls | list [app] Lists deployments
|
||||
inspect [id] Displays information related to a deployment
|
||||
link [path] Link local directory to a Vercel Project
|
||||
ls | list [app] Lists deployments
|
||||
login [email] Logs into your account or creates a new one
|
||||
logout Logs out of your account
|
||||
pull [path] Pull your Project Settings from the cloud
|
||||
switch [scope] Switches between teams and your personal account
|
||||
help [cmd] Displays complete help for [cmd]
|
||||
|
||||
${chalk.dim('Advanced')}
|
||||
|
||||
rm | remove [id] Removes a deployment
|
||||
alias [cmd] Manages your domain aliases
|
||||
bisect Use binary search to find the deployment that introduced a bug
|
||||
domains [name] Manages your domain names
|
||||
projects Manages your Projects
|
||||
dns [name] Manages your DNS records
|
||||
certs [cmd] Manages your SSL certificates
|
||||
secrets [name] Manages your global Secrets, for use in Environment Variables
|
||||
dns [name] Manages your DNS records
|
||||
domains [name] Manages your domain names
|
||||
logs [url] Displays the logs for a deployment
|
||||
projects Manages your Projects
|
||||
rm | remove [id] Removes a deployment
|
||||
secrets [name] Manages your global Secrets, for use in Environment Variables
|
||||
teams Manages your teams
|
||||
whoami Shows the username of the currently logged in user
|
||||
|
||||
@@ -72,7 +72,7 @@ export const help = () => `
|
||||
-S, --scope Set a custom scope
|
||||
--regions Set default regions to enable the deployment on
|
||||
--prod Create a production deployment
|
||||
-c, --confirm Confirm default options and skip questions
|
||||
-y, --yes Skip questions when setting up new project using default scope and settings
|
||||
|
||||
${chalk.dim('Examples:')}
|
||||
|
||||
|
||||
@@ -3,7 +3,11 @@ import fs from 'fs-extra';
|
||||
import bytes from 'bytes';
|
||||
import chalk from 'chalk';
|
||||
import { join, resolve, basename } from 'path';
|
||||
import { fileNameSymbol, VercelConfig } from '@vercel/client';
|
||||
import {
|
||||
fileNameSymbol,
|
||||
VALID_ARCHIVE_FORMATS,
|
||||
VercelConfig,
|
||||
} from '@vercel/client';
|
||||
import code from '../../util/output/code';
|
||||
import highlight from '../../util/output/highlight';
|
||||
import { readLocalConfig } from '../../util/config/files';
|
||||
@@ -66,10 +70,11 @@ import { getDeploymentChecks } from '../../util/deploy/get-deployment-checks';
|
||||
import parseTarget from '../../util/deploy/parse-target';
|
||||
import getPrebuiltJson from '../../util/deploy/get-prebuilt-json';
|
||||
import { createGitMeta } from '../../util/create-git-meta';
|
||||
import { isValidArchive } from '../../util/deploy/validate-archive-format';
|
||||
import { parseEnv } from '../../util/parse-env';
|
||||
import { errorToString, isErrnoException, isError } from '../../util/is-error';
|
||||
|
||||
export default async (client: Client) => {
|
||||
export default async (client: Client): Promise<number> => {
|
||||
const { output } = client;
|
||||
|
||||
let argv = null;
|
||||
@@ -87,20 +92,28 @@ export default async (client: Client) => {
|
||||
'--regions': String,
|
||||
'--prebuilt': Boolean,
|
||||
'--prod': Boolean,
|
||||
'--confirm': Boolean,
|
||||
'--archive': String,
|
||||
'--yes': Boolean,
|
||||
'-f': '--force',
|
||||
'-p': '--public',
|
||||
'-e': '--env',
|
||||
'-b': '--build-env',
|
||||
'-m': '--meta',
|
||||
'-c': '--confirm',
|
||||
'-y': '--yes',
|
||||
|
||||
// deprecated
|
||||
'--name': String,
|
||||
'-n': '--name',
|
||||
'--no-clipboard': Boolean,
|
||||
'--target': String,
|
||||
'--confirm': Boolean,
|
||||
'-c': '--confirm',
|
||||
});
|
||||
|
||||
if ('--confirm' in argv) {
|
||||
output.warn('`--confirm` is deprecated, please use `--yes` instead');
|
||||
argv['--yes'] = argv['--confirm'];
|
||||
}
|
||||
} catch (error) {
|
||||
handleError(error);
|
||||
return 1;
|
||||
@@ -173,7 +186,7 @@ export default async (client: Client) => {
|
||||
}
|
||||
|
||||
const { path } = pathValidation;
|
||||
const autoConfirm = argv['--confirm'];
|
||||
const autoConfirm = argv['--yes'];
|
||||
|
||||
// deprecate --name
|
||||
if (argv['--name']) {
|
||||
@@ -254,6 +267,12 @@ export default async (client: Client) => {
|
||||
}
|
||||
}
|
||||
|
||||
const archive = argv['--archive'];
|
||||
if (typeof archive === 'string' && !isValidArchive(archive)) {
|
||||
output.error(`Format must be one of: ${VALID_ARCHIVE_FORMATS.join(', ')}`);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// retrieve `project` and `org` from .vercel
|
||||
const link = await getLinkedProject(client, path);
|
||||
|
||||
@@ -277,7 +296,7 @@ export default async (client: Client) => {
|
||||
));
|
||||
|
||||
if (!shouldStartSetup) {
|
||||
output.print(`Aborted. Project not set up.\n`);
|
||||
output.print(`Canceled. Project not set up.\n`);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -538,7 +557,8 @@ export default async (client: Client) => {
|
||||
createArgs,
|
||||
org,
|
||||
!project,
|
||||
path
|
||||
path,
|
||||
archive
|
||||
);
|
||||
|
||||
if (deployment.code === 'missing_project_settings') {
|
||||
@@ -911,4 +931,6 @@ const printDeploymentStatus = async (
|
||||
) + newline;
|
||||
output.print(message + link);
|
||||
}
|
||||
|
||||
return 0;
|
||||
};
|
||||
|
||||
@@ -17,7 +17,7 @@ import { OUTPUT_DIR } from '../../util/build/write-build-result';
|
||||
|
||||
type Options = {
|
||||
'--listen': string;
|
||||
'--confirm': boolean;
|
||||
'--yes': boolean;
|
||||
};
|
||||
|
||||
export default async function dev(
|
||||
@@ -38,7 +38,7 @@ export default async function dev(
|
||||
|
||||
if (link.status === 'not_linked' && !process.env.__VERCEL_SKIP_DEV_CMD) {
|
||||
link = await setupAndLink(client, cwd, {
|
||||
autoConfirm: opts['--confirm'],
|
||||
autoConfirm: opts['--yes'],
|
||||
successEmoji: 'link',
|
||||
setupMsg: 'Set up and develop',
|
||||
});
|
||||
@@ -54,14 +54,13 @@ export default async function dev(
|
||||
client.output.error(
|
||||
`Command ${getCommandName(
|
||||
'dev'
|
||||
)} requires confirmation. Use option ${param('--confirm')} to confirm.`
|
||||
)} requires confirmation. Use option ${param('--yes')} to confirm.`
|
||||
);
|
||||
}
|
||||
return link.exitCode;
|
||||
}
|
||||
|
||||
let devCommand: string | undefined;
|
||||
let frameworkSlug: string | undefined;
|
||||
let projectSettings: ProjectSettings | undefined;
|
||||
let projectEnvs: ProjectEnvVariable[] = [];
|
||||
let systemEnvValues: string[] = [];
|
||||
@@ -77,10 +76,6 @@ export default async function dev(
|
||||
const framework = frameworks.find(f => f.slug === project.framework);
|
||||
|
||||
if (framework) {
|
||||
if (framework.slug) {
|
||||
frameworkSlug = framework.slug;
|
||||
}
|
||||
|
||||
const defaults = framework.settings.devCommand.value;
|
||||
if (defaults) {
|
||||
devCommand = defaults;
|
||||
@@ -120,7 +115,6 @@ export default async function dev(
|
||||
const devServer = new DevServer(cwd, {
|
||||
output,
|
||||
devCommand,
|
||||
frameworkSlug,
|
||||
projectSettings,
|
||||
projectEnvs,
|
||||
systemEnvValues,
|
||||
|
||||
@@ -33,7 +33,7 @@ const help = () => {
|
||||
-d, --debug Debug mode [off]
|
||||
-l, --listen [uri] Specify a URI endpoint on which to listen [0.0.0.0:3000]
|
||||
-t, --token [token] Specify an Authorization Token
|
||||
--confirm Skip questions and use defaults when setting up a new project
|
||||
-y, --yes Skip questions when setting up new project using default scope and settings
|
||||
|
||||
${chalk.dim('Examples:')}
|
||||
|
||||
@@ -74,14 +74,22 @@ export default async function main(client: Client) {
|
||||
argv = getArgs(client.argv.slice(2), {
|
||||
'--listen': String,
|
||||
'-l': '--listen',
|
||||
'--confirm': Boolean,
|
||||
'--yes': Boolean,
|
||||
'-y': '--yes',
|
||||
|
||||
// Deprecated
|
||||
'--port': Number,
|
||||
'-p': '--port',
|
||||
'--confirm': Boolean,
|
||||
'-c': '--confirm',
|
||||
});
|
||||
args = getSubcommand(argv._.slice(1), COMMAND_CONFIG).args;
|
||||
|
||||
if ('--confirm' in argv) {
|
||||
output.warn('`--confirm` is deprecated, please use `--yes` instead');
|
||||
argv['--yes'] = argv['--confirm'];
|
||||
}
|
||||
|
||||
if ('--port' in argv) {
|
||||
output.warn('`--port` is deprecated, please use `--listen` instead');
|
||||
argv['--listen'] = String(argv['--port']);
|
||||
|
||||
@@ -37,7 +37,7 @@ export default async function add(
|
||||
const { domain, data: argData } = parsedParams;
|
||||
const data = await getDNSData(client, argData);
|
||||
if (!data) {
|
||||
output.log(`Aborted`);
|
||||
output.log(`Canceled`);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ export default async function rm(
|
||||
);
|
||||
|
||||
if (!yes) {
|
||||
output.error(`User aborted.`);
|
||||
output.error(`User canceled.`);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -116,9 +116,7 @@ export default async function buy(
|
||||
|
||||
if (buyResult instanceof ERRORS.SourceNotFound) {
|
||||
output.error(
|
||||
`Could not purchase domain. Please add a payment method using ${getCommandName(
|
||||
`billing add`
|
||||
)}.`
|
||||
`Could not purchase domain. Please add a payment method using the dashboard.`
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -45,6 +45,7 @@ const help = () => {
|
||||
)} Login token
|
||||
-S, --scope Set a custom scope
|
||||
-N, --next Show next page of results
|
||||
-y, --yes Skip the confirmation prompt when removing a domain
|
||||
|
||||
${chalk.dim('Examples:')}
|
||||
|
||||
@@ -92,6 +93,7 @@ export default async function main(client: Client) {
|
||||
'--force': Boolean,
|
||||
'--next': Number,
|
||||
'-N': '--next',
|
||||
'-y': '--yes',
|
||||
});
|
||||
} catch (error) {
|
||||
handleError(error);
|
||||
|
||||
@@ -68,7 +68,7 @@ export default async function move(
|
||||
client
|
||||
))
|
||||
) {
|
||||
output.log('Aborted');
|
||||
output.log('Canceled');
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -87,7 +87,7 @@ export default async function move(
|
||||
client
|
||||
))
|
||||
) {
|
||||
output.log('Aborted');
|
||||
output.log('Canceled');
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,7 +86,7 @@ export default async function rm(
|
||||
client
|
||||
))
|
||||
) {
|
||||
output.log('Aborted');
|
||||
output.log('Canceled');
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -224,7 +224,7 @@ async function removeDomain(
|
||||
!skipConfirmation &&
|
||||
!(await promptBool(`Remove conflicts associated with domain?`, client))
|
||||
) {
|
||||
output.log('Aborted');
|
||||
output.log('Canceled');
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -111,9 +111,7 @@ export default async function transferIn(
|
||||
|
||||
if (transferInResult instanceof ERRORS.SourceNotFound) {
|
||||
output.error(
|
||||
`Could not purchase domain. Please add a payment method using ${getCommandName(
|
||||
`billing add`
|
||||
)}.`
|
||||
`Could not purchase domain. Please add a payment method using the dashboard.`
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
|
||||
1
packages/cli/src/commands/env/index.ts
vendored
@@ -42,6 +42,7 @@ const help = () => {
|
||||
-t ${chalk.bold.underline('TOKEN')}, --token=${chalk.bold.underline(
|
||||
'TOKEN'
|
||||
)} Login token
|
||||
-y, --yes Skip the confirmation prompt when overwriting env file on pull or removing an env variable
|
||||
|
||||
${chalk.dim('Examples:')}
|
||||
|
||||
|
||||
22
packages/cli/src/commands/env/pull.ts
vendored
@@ -84,7 +84,7 @@ export default async function pull(
|
||||
false
|
||||
))
|
||||
) {
|
||||
output.log('Aborted');
|
||||
output.log('Canceled');
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -117,7 +117,12 @@ export default async function pull(
|
||||
if (exists) {
|
||||
oldEnv = await createEnvObject(fullPath, output);
|
||||
if (oldEnv) {
|
||||
deltaString = buildDeltaString(oldEnv, records);
|
||||
// Removes any double quotes from `records`, if they exist
|
||||
// We need this because double quotes are stripped from the local .env file,
|
||||
// but `records` is already in the form of a JSON object that doesn't filter
|
||||
// double quotes.
|
||||
const newEnv = JSON.parse(JSON.stringify(records).replace(/\\"/g, ''));
|
||||
deltaString = buildDeltaString(oldEnv, newEnv);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,6 +135,12 @@ export default async function pull(
|
||||
|
||||
await outputFile(fullPath, contents, 'utf8');
|
||||
|
||||
if (deltaString) {
|
||||
output.print('\n' + deltaString);
|
||||
} else if (oldEnv && exists) {
|
||||
output.log('No changes found.');
|
||||
}
|
||||
|
||||
output.print(
|
||||
`${prependEmoji(
|
||||
`${exists ? 'Updated' : 'Created'} ${chalk.bold(
|
||||
@@ -139,13 +150,6 @@ export default async function pull(
|
||||
)}\n`
|
||||
);
|
||||
|
||||
output.print('\n');
|
||||
if (deltaString) {
|
||||
output.print(deltaString);
|
||||
} else if (oldEnv && exists) {
|
||||
output.log('No changes found.');
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
7
packages/cli/src/commands/env/rm.ts
vendored
@@ -1,5 +1,4 @@
|
||||
import chalk from 'chalk';
|
||||
import inquirer from 'inquirer';
|
||||
import { Project } from '../../types';
|
||||
import { Output } from '../../util/output';
|
||||
import confirm from '../../util/input/confirm';
|
||||
@@ -45,7 +44,7 @@ export default async function rm(
|
||||
let [envName, envTarget, envGitBranch] = args;
|
||||
|
||||
while (!envName) {
|
||||
const { inputName } = await inquirer.prompt({
|
||||
const { inputName } = await client.prompt({
|
||||
type: 'input',
|
||||
name: 'inputName',
|
||||
message: `What’s the name of the variable?`,
|
||||
@@ -87,7 +86,7 @@ export default async function rm(
|
||||
}
|
||||
|
||||
while (envs.length > 1) {
|
||||
const { id } = await inquirer.prompt({
|
||||
const { id } = await client.prompt({
|
||||
name: 'id',
|
||||
type: 'list',
|
||||
message: `Remove ${envName} from which Environments?`,
|
||||
@@ -112,7 +111,7 @@ export default async function rm(
|
||||
false
|
||||
))
|
||||
) {
|
||||
output.log('Aborted');
|
||||
output.log('Canceled');
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,21 +1,54 @@
|
||||
import { Dictionary } from '@vercel/client';
|
||||
import chalk from 'chalk';
|
||||
import { join } from 'path';
|
||||
import { Org, Project } from '../../types';
|
||||
import { Org, Project, ProjectLinkData } from '../../types';
|
||||
import Client from '../../util/client';
|
||||
import { parseGitConfig, pluckRemoteUrls } from '../../util/create-git-meta';
|
||||
import confirm from '../../util/input/confirm';
|
||||
import list, { ListChoice } from '../../util/input/list';
|
||||
import { Output } from '../../util/output';
|
||||
import link from '../../util/output/link';
|
||||
import { getCommandName } from '../../util/pkg-name';
|
||||
import {
|
||||
connectGitProvider,
|
||||
disconnectGitProvider,
|
||||
formatProvider,
|
||||
RepoInfo,
|
||||
parseRepoUrl,
|
||||
} from '../../util/projects/connect-git-provider';
|
||||
printRemoteUrls,
|
||||
} from '../../util/git/connect-git-provider';
|
||||
import validatePaths from '../../util/validate-paths';
|
||||
|
||||
interface GitRepoCheckParams {
|
||||
client: Client;
|
||||
confirm: boolean;
|
||||
gitProviderLink?: ProjectLinkData;
|
||||
org: Org;
|
||||
gitOrg: string;
|
||||
project: Project;
|
||||
provider: string;
|
||||
repo: string;
|
||||
repoPath: string;
|
||||
}
|
||||
|
||||
interface ConnectArgParams {
|
||||
client: Client;
|
||||
org: Org;
|
||||
project: Project;
|
||||
confirm: boolean;
|
||||
repoInfo: RepoInfo;
|
||||
}
|
||||
|
||||
interface ConnectGitArgParams extends ConnectArgParams {
|
||||
gitConfig: Dictionary<any>;
|
||||
}
|
||||
|
||||
interface PromptConnectArgParams {
|
||||
client: Client;
|
||||
yes: boolean;
|
||||
repoInfo: RepoInfo;
|
||||
remoteUrls: Dictionary<string>;
|
||||
}
|
||||
|
||||
export default async function connect(
|
||||
client: Client,
|
||||
argv: any,
|
||||
@@ -24,9 +57,10 @@ export default async function connect(
|
||||
org: Org | undefined
|
||||
) {
|
||||
const { output } = client;
|
||||
const confirm = Boolean(argv['--confirm']);
|
||||
const confirm = Boolean(argv['--yes']);
|
||||
const repoArg = argv._[1];
|
||||
|
||||
if (args.length !== 0) {
|
||||
if (args.length > 1) {
|
||||
output.error(
|
||||
`Invalid number of arguments. Usage: ${chalk.cyan(
|
||||
`${getCommandName('project connect')}`
|
||||
@@ -36,7 +70,7 @@ export default async function connect(
|
||||
}
|
||||
if (!project || !org) {
|
||||
output.error(
|
||||
`Can't find \`org\` or \`project\`. Make sure your current directory is linked to a Vercel projet by running ${getCommandName(
|
||||
`Can't find \`org\` or \`project\`. Make sure your current directory is linked to a Vercel project by running ${getCommandName(
|
||||
'link'
|
||||
)}.`
|
||||
);
|
||||
@@ -57,9 +91,38 @@ export default async function connect(
|
||||
// get project from .git
|
||||
const gitConfigPath = join(path, '.git/config');
|
||||
const gitConfig = await parseGitConfig(gitConfigPath, output);
|
||||
|
||||
if (repoArg) {
|
||||
// parse repo arg
|
||||
const parsedUrlArg = parseRepoUrl(repoArg);
|
||||
if (!parsedUrlArg) {
|
||||
output.error(
|
||||
`Failed to parse URL "${repoArg}". Please ensure the URL is valid.`
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
if (gitConfig) {
|
||||
return await connectArgWithLocalGit({
|
||||
client,
|
||||
org,
|
||||
project,
|
||||
confirm,
|
||||
gitConfig,
|
||||
repoInfo: parsedUrlArg,
|
||||
});
|
||||
}
|
||||
return await connectArg({
|
||||
client,
|
||||
confirm,
|
||||
org,
|
||||
project,
|
||||
repoInfo: repoArg,
|
||||
});
|
||||
}
|
||||
|
||||
if (!gitConfig) {
|
||||
output.error(
|
||||
`No local git repo found. Run ${chalk.cyan(
|
||||
`No local Git repository found. Run ${chalk.cyan(
|
||||
'`git clone <url>`'
|
||||
)} to clone a remote Git repository first.`
|
||||
);
|
||||
@@ -78,7 +141,7 @@ export default async function connect(
|
||||
let remoteUrl: string;
|
||||
|
||||
if (Object.keys(remoteUrls).length > 1) {
|
||||
output.log(`Found multiple remote URLs.`);
|
||||
output.log('Found multiple remote URLs.');
|
||||
remoteUrl = await selectRemoteUrl(client, remoteUrls);
|
||||
} else {
|
||||
// If only one is found, get it — usually "origin"
|
||||
@@ -86,14 +149,14 @@ export default async function connect(
|
||||
}
|
||||
|
||||
if (remoteUrl === '') {
|
||||
output.log('Aborted.');
|
||||
output.log('Canceled');
|
||||
return 0;
|
||||
}
|
||||
|
||||
output.log(`Connecting Git remote: ${link(remoteUrl)}`);
|
||||
|
||||
const parsedUrl = parseRepoUrl(remoteUrl);
|
||||
if (!parsedUrl) {
|
||||
const repoInfo = parseRepoUrl(remoteUrl);
|
||||
if (!repoInfo) {
|
||||
output.error(
|
||||
`Failed to parse Git repo data from the following remote URL: ${link(
|
||||
remoteUrl
|
||||
@@ -101,10 +164,176 @@ export default async function connect(
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
const { provider, org: gitOrg, repo } = parsedUrl;
|
||||
const { provider, org: gitOrg, repo } = repoInfo;
|
||||
const repoPath = `${gitOrg}/${repo}`;
|
||||
let connectedRepoPath;
|
||||
|
||||
const checkAndConnect = await checkExistsAndConnect({
|
||||
client,
|
||||
confirm,
|
||||
org,
|
||||
project,
|
||||
gitProviderLink,
|
||||
provider,
|
||||
repoPath,
|
||||
gitOrg,
|
||||
repo,
|
||||
});
|
||||
if (typeof checkAndConnect === 'number') {
|
||||
return checkAndConnect;
|
||||
}
|
||||
|
||||
output.log(
|
||||
`Connected ${formatProvider(provider)} repository ${chalk.cyan(repoPath)}!`
|
||||
);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
async function connectArg({
|
||||
client,
|
||||
confirm,
|
||||
org,
|
||||
project,
|
||||
repoInfo,
|
||||
}: ConnectArgParams) {
|
||||
const { url: repoUrl } = repoInfo;
|
||||
client.output.log(`Connecting Git remote: ${link(repoUrl)}`);
|
||||
const parsedRepoArg = parseRepoUrl(repoUrl);
|
||||
if (!parsedRepoArg) {
|
||||
client.output.error(
|
||||
`Failed to parse URL "${repoUrl}". Please ensure the URL is valid.`
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
const { provider, org: gitOrg, repo } = parsedRepoArg;
|
||||
const repoPath = `${gitOrg}/${repo}`;
|
||||
const connect = await checkExistsAndConnect({
|
||||
client,
|
||||
confirm,
|
||||
org,
|
||||
project,
|
||||
gitProviderLink: project.link,
|
||||
provider,
|
||||
repoPath,
|
||||
gitOrg,
|
||||
repo,
|
||||
});
|
||||
if (typeof connect === 'number') {
|
||||
return connect;
|
||||
}
|
||||
client.output.log(
|
||||
`Connected ${formatProvider(provider)} repository ${chalk.cyan(repoPath)}!`
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
|
||||
async function connectArgWithLocalGit({
|
||||
client,
|
||||
org,
|
||||
project,
|
||||
confirm,
|
||||
gitConfig,
|
||||
repoInfo,
|
||||
}: ConnectGitArgParams) {
|
||||
const remoteUrls = pluckRemoteUrls(gitConfig);
|
||||
if (remoteUrls) {
|
||||
const shouldConnect = await promptConnectArg({
|
||||
client,
|
||||
yes: confirm,
|
||||
repoInfo,
|
||||
remoteUrls,
|
||||
});
|
||||
if (!shouldConnect) {
|
||||
return 1;
|
||||
}
|
||||
if (shouldConnect) {
|
||||
const { provider, org: gitOrg, repo, url: repoUrl } = repoInfo;
|
||||
const repoPath = `${gitOrg}/${repo}`;
|
||||
client.output.log(`Connecting Git remote: ${link(repoUrl)}`);
|
||||
const connect = await checkExistsAndConnect({
|
||||
client,
|
||||
confirm,
|
||||
org,
|
||||
project,
|
||||
gitProviderLink: project.link,
|
||||
provider,
|
||||
repoPath,
|
||||
gitOrg,
|
||||
repo,
|
||||
});
|
||||
if (typeof connect === 'number') {
|
||||
return connect;
|
||||
}
|
||||
client.output.log(
|
||||
`Connected ${formatProvider(provider)} repository ${chalk.cyan(
|
||||
repoPath
|
||||
)}!`
|
||||
);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
return await connectArg({ client, confirm, org, project, repoInfo });
|
||||
}
|
||||
|
||||
async function promptConnectArg({
|
||||
client,
|
||||
yes,
|
||||
repoInfo: repoInfoFromArg,
|
||||
remoteUrls,
|
||||
}: PromptConnectArgParams) {
|
||||
if (Object.keys(remoteUrls).length > 1) {
|
||||
client.output.log(
|
||||
'Found multiple Git repositories in your local Git config:'
|
||||
);
|
||||
printRemoteUrls(client.output, remoteUrls);
|
||||
} else {
|
||||
const url = Object.values(remoteUrls)[0];
|
||||
const repoInfoFromGitConfig = parseRepoUrl(url);
|
||||
if (!repoInfoFromGitConfig) {
|
||||
client.output.error(
|
||||
`Failed to parse URL "${url}". Please ensure the URL is valid.`
|
||||
);
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
JSON.stringify(repoInfoFromGitConfig) === JSON.stringify(repoInfoFromArg)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
client.output.log(
|
||||
`Found a repository in your local Git Config: ${chalk.cyan(
|
||||
Object.values(remoteUrls)[0]
|
||||
)}`
|
||||
);
|
||||
}
|
||||
|
||||
let shouldConnect = yes;
|
||||
if (!shouldConnect) {
|
||||
const { url: repoUrlFromArg } = repoInfoFromArg;
|
||||
shouldConnect = await confirm(
|
||||
client,
|
||||
`Do you still want to connect ${link(repoUrlFromArg)}?`,
|
||||
false
|
||||
);
|
||||
if (!shouldConnect) {
|
||||
client.output.log('Canceled. Repo not connected.');
|
||||
}
|
||||
}
|
||||
return shouldConnect;
|
||||
}
|
||||
|
||||
async function checkExistsAndConnect({
|
||||
client,
|
||||
confirm,
|
||||
org,
|
||||
project,
|
||||
gitProviderLink,
|
||||
provider,
|
||||
repoPath,
|
||||
gitOrg,
|
||||
repo,
|
||||
}: GitRepoCheckParams) {
|
||||
if (!gitProviderLink) {
|
||||
const connect = await connectGitProvider(
|
||||
client,
|
||||
@@ -120,14 +349,14 @@ export default async function connect(
|
||||
const connectedProvider = gitProviderLink.type;
|
||||
const connectedOrg = gitProviderLink.org;
|
||||
const connectedRepo = gitProviderLink.repo;
|
||||
connectedRepoPath = `${connectedOrg}/${connectedRepo}`;
|
||||
const connectedRepoPath = `${connectedOrg}/${connectedRepo}`;
|
||||
|
||||
const isSameRepo =
|
||||
connectedProvider === provider &&
|
||||
connectedOrg === gitOrg &&
|
||||
connectedRepo === repo;
|
||||
if (isSameRepo) {
|
||||
output.log(
|
||||
client.output.log(
|
||||
`${chalk.cyan(connectedRepoPath)} is already connected to your project.`
|
||||
);
|
||||
return 1;
|
||||
@@ -135,8 +364,8 @@ export default async function connect(
|
||||
|
||||
const shouldReplaceRepo = await confirmRepoConnect(
|
||||
client,
|
||||
output,
|
||||
confirm,
|
||||
connectedProvider,
|
||||
connectedRepoPath
|
||||
);
|
||||
if (!shouldReplaceRepo) {
|
||||
@@ -155,31 +384,27 @@ export default async function connect(
|
||||
return connect;
|
||||
}
|
||||
}
|
||||
|
||||
output.log(
|
||||
`Connected ${formatProvider(provider)} repository ${chalk.cyan(repoPath)}!`
|
||||
);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
async function confirmRepoConnect(
|
||||
client: Client,
|
||||
output: Output,
|
||||
yes: boolean,
|
||||
connectedProvider: string,
|
||||
connectedRepoPath: string
|
||||
) {
|
||||
let shouldReplaceProject = yes;
|
||||
if (!shouldReplaceProject) {
|
||||
shouldReplaceProject = await confirm(
|
||||
client,
|
||||
`Looks like you already have a repository connected: ${chalk.cyan(
|
||||
`Looks like you already have a ${formatProvider(
|
||||
connectedProvider
|
||||
)} repository connected: ${chalk.cyan(
|
||||
connectedRepoPath
|
||||
)}. Do you want to replace it?`,
|
||||
true
|
||||
);
|
||||
if (!shouldReplaceProject) {
|
||||
output.log(`Aborted. Repo not connected.`);
|
||||
client.output.log('Canceled. Repo not connected.');
|
||||
}
|
||||
}
|
||||
return shouldReplaceProject;
|
||||
@@ -187,7 +412,7 @@ async function confirmRepoConnect(
|
||||
|
||||
async function selectRemoteUrl(
|
||||
client: Client,
|
||||
remoteUrls: { [key: string]: string }
|
||||
remoteUrls: Dictionary<string>
|
||||
): Promise<string> {
|
||||
let choices: ListChoice[] = [];
|
||||
for (const [urlKey, urlValue] of Object.entries(remoteUrls)) {
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Org, Project } from '../../types';
|
||||
import Client from '../../util/client';
|
||||
import confirm from '../../util/input/confirm';
|
||||
import { getCommandName } from '../../util/pkg-name';
|
||||
import { disconnectGitProvider } from '../../util/projects/connect-git-provider';
|
||||
import { disconnectGitProvider } from '../../util/git/connect-git-provider';
|
||||
|
||||
export default async function disconnect(
|
||||
client: Client,
|
||||
@@ -43,7 +43,7 @@ export default async function disconnect(
|
||||
await disconnectGitProvider(client, org, project.id);
|
||||
output.log(`Disconnected ${chalk.cyan(`${linkOrg}/${repo}`)}.`);
|
||||
} else {
|
||||
output.log('Aborted.');
|
||||
output.log('Canceled');
|
||||
}
|
||||
} else {
|
||||
output.error(
|
||||
|
||||
@@ -16,21 +16,30 @@ const help = () => {
|
||||
|
||||
${chalk.dim('Commands:')}
|
||||
|
||||
connect Connect your Git config "origin" remote as a Git provider to your project
|
||||
disconnect Disconnect the Git provider repository from your project
|
||||
connect [url] Connect your Vercel Project to your Git repository or provide the remote URL to your Git repository
|
||||
disconnect Disconnect the Git provider repository from your project
|
||||
|
||||
${chalk.dim('Options:')}
|
||||
|
||||
-h, --help Output usage information
|
||||
-h, --help Output usage information
|
||||
-t ${chalk.bold.underline('TOKEN')}, --token=${chalk.bold.underline(
|
||||
'TOKEN'
|
||||
)} Login token
|
||||
)} Login token
|
||||
-y, --yes Skip questions when setting up new project using default scope and settings
|
||||
|
||||
${chalk.dim('Examples:')}
|
||||
|
||||
${chalk.gray('–')} Connect a Git provider repository
|
||||
${chalk.gray(
|
||||
'–'
|
||||
)} Connect your Vercel Project to your Git repository defined in your local .git config
|
||||
|
||||
${chalk.cyan(`$ ${getPkgName()} git connect`)}
|
||||
|
||||
${chalk.gray('–')} Connect your Vercel Project to a Git repository using the remote URL
|
||||
|
||||
${chalk.cyan(
|
||||
`$ ${getPkgName()} git connect https://github.com/user/repo.git`
|
||||
)}
|
||||
|
||||
${chalk.gray('–')} Disconnect the Git provider repository
|
||||
|
||||
@@ -49,7 +58,12 @@ export default async function main(client: Client) {
|
||||
|
||||
try {
|
||||
argv = getArgs(client.argv.slice(2), {
|
||||
'--confirm': Boolean,
|
||||
'--yes': Boolean,
|
||||
'-y': '--yes',
|
||||
|
||||
// deprecated
|
||||
'-c': '--yes',
|
||||
'--confirm': '--yes',
|
||||
});
|
||||
} catch (error) {
|
||||
handleError(error);
|
||||
@@ -64,7 +78,7 @@ export default async function main(client: Client) {
|
||||
argv._ = argv._.slice(1);
|
||||
subcommand = argv._[0];
|
||||
const args = argv._.slice(1);
|
||||
const confirm = Boolean(argv['--confirm']);
|
||||
const confirm = Boolean(argv['--yes']);
|
||||
const { output } = client;
|
||||
|
||||
let paths = [process.cwd()];
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
export default new Map([
|
||||
['alias', 'alias'],
|
||||
['aliases', 'alias'],
|
||||
['billing', 'billing'],
|
||||
['bisect', 'bisect'],
|
||||
['build', 'build'],
|
||||
['cc', 'billing'],
|
||||
['cert', 'certs'],
|
||||
['certs', 'certs'],
|
||||
['deploy', 'deploy'],
|
||||
@@ -36,6 +34,5 @@ export default new Map([
|
||||
['switch', 'teams'],
|
||||
['team', 'teams'],
|
||||
['teams', 'teams'],
|
||||
['update', 'update'],
|
||||
['whoami', 'whoami'],
|
||||
]);
|
||||
|
||||
@@ -53,7 +53,7 @@ export default async function init(
|
||||
);
|
||||
|
||||
if (!chosen) {
|
||||
output.log('Aborted');
|
||||
output.log('Canceled');
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||