Compare commits

..

13 Commits

Author SHA1 Message Date
Sean Massa
6700630feb Publish Stable
- @vercel/build-utils@5.3.2
 - vercel@28.1.4
 - @vercel/client@12.2.1
 - @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
2022-08-24 09:09:02 -05:00
Sean Massa
34cd8b4144 [cli] revert git connect inside vc link (#8450) 2022-08-24 09:07:04 -05:00
Sean Massa
ad0ed6d852 Publish Stable
- vercel@28.1.3
 - @vercel/node@2.5.9
2022-08-23 16:58:19 -05:00
Gal Schlezinger
0bad09b47a [node] fix WebAssembly bindings evaluating to undefined (#8417)
This was broken since https://github.com/vercel/vercel/pull/8242, which accidentally pushed all WebAssembly modules to be nested under `global.wasmBindings` instead of being directly under the global scope.

### Related Issues

- This was broken since https://github.com/vercel/vercel/pull/8242

### 📋 Checklist

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

#### Tests

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

#### Code Review

- [ ] This PR has a concise title and thorough description useful to a reviewer
- [ ] Issue from task tracker has a link to this PR
2022-08-23 21:56:13 +00:00
Steven
5120689bf2 [tests] Fix update-next script (#8452)
Follow up to #8447 since exec returns a buffer, not a string.
2022-08-23 17:28:38 -04:00
Steven
5a39fd9242 [tests] Automatically update Next.js example periodically (#8447)
Automatically update Next.js example every 4 hours using `create-next-app`
2022-08-23 17:19:55 +00:00
Sean Massa
352cd00ef0 Publish Stable
- vercel@28.1.2
 - @vercel/go@2.2.1
2022-08-23 10:15:26 -05:00
Sean Massa
abfe817f86 [go] ignore bad go handlers and rename functions with more precision (#8422)
Co-authored-by: Steven <steven@ceriously.com>
2022-08-23 10:14:21 -05:00
Sean Massa
ebb5e2b208 Publish Stable
- vercel@28.1.1
 - @vercel/next@3.1.20
 - @vercel/python@3.1.10
2022-08-22 15:33:41 -05:00
JJ Kasper
e34858d082 [next] Add handling for app-paths-manifest (#8098)
This adds handling for the `app` outputs and adds initial tests to ensure it is working as expected. 

### Related Issues

x-ref: https://github.com/vercel/next.js/pull/38420

### 📋 Checklist

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

#### Tests

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

#### Code Review

- [ ] This PR has a concise title and thorough description useful to a reviewer
- [ ] Issue from task tracker has a link to this PR
2022-08-22 14:58:21 -05:00
Ikko Ashimine
f03c947f91 [python] fix typo in install.ts comment (#8442)
overriden -> overridden
2022-08-22 11:58:27 -04:00
JJ Kasper
0d13fe7e34 [next] Ensure non-static pages/500 is handled (#8438)
Related Issues
Previously we were only checking for a non-static version of pages/404 although this can also be the case for pages/500 so this ensures we match that handling and add a test case to prevent regression.
Fixes: slack thread
📋 Checklist

Tests

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

Code Review

 This PR has a concise title and thorough description useful to a reviewer
 Issue from task tracker has a link to this PR
2022-08-22 10:13:16 -05:00
JJ Kasper
4afec9d373 [next] Update middleware data routes to fix caching (#8431)
Related Issues
This fixes caches for data routes with middleware due to search params not matching between the non-data variant and the data variant. Additional tests have been added to ensure this is working as expected.
Fixes: vercel/next.js#39595
📋 Checklist

Tests

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

Code Review

 This PR has a concise title and thorough description useful to a reviewer
 Issue from task tracker has a link to this PR
2022-08-22 09:52:24 -05:00
110 changed files with 1077 additions and 837 deletions

25
.github/workflows/cron-update-next.yml vendored Normal file
View 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 })

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/build-utils",
"version": "5.3.1",
"version": "5.3.2",
"license": "MIT",
"main": "./dist/index.js",
"types": "./dist/index.d.js",

View File

@@ -335,7 +335,6 @@ export interface ProjectSettings {
directoryListing?: boolean;
gitForkProtection?: boolean;
commandForIgnoringBuildStep?: string | null;
skipGitConnectDuringLink?: boolean;
}
export interface BuilderV2 {

View File

@@ -1,6 +1,6 @@
{
"name": "vercel",
"version": "28.1.0",
"version": "28.1.4",
"preferGlobal": true,
"license": "Apache-2.0",
"description": "The command-line interface for Vercel",
@@ -41,16 +41,16 @@
"node": ">= 14"
},
"dependencies": {
"@vercel/build-utils": "5.3.1",
"@vercel/go": "2.2.0",
"@vercel/hydrogen": "0.0.14",
"@vercel/next": "3.1.19",
"@vercel/node": "2.5.8",
"@vercel/python": "3.1.9",
"@vercel/redwood": "1.0.18",
"@vercel/remix": "1.0.19",
"@vercel/ruby": "1.3.27",
"@vercel/static-build": "1.0.18",
"@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": {
@@ -96,7 +96,7 @@
"@types/which": "1.3.2",
"@types/write-json-file": "2.2.1",
"@types/yauzl-promise": "2.1.0",
"@vercel/client": "12.2.0",
"@vercel/client": "12.2.1",
"@vercel/frameworks": "1.1.3",
"@vercel/fs-detectors": "2.0.5",
"@vercel/fun": "1.0.4",

View File

@@ -58,9 +58,9 @@ export async function connectGitProvider(
(err.action === 'Install GitHub App' || err.code === 'repo_not_found')
) {
client.output.error(
`Failed to link ${chalk.cyan(
`Failed to connect ${chalk.cyan(
repo
)}. Make sure there aren't any typos and that you have access to the repository if it's private.`
)} to project. Make sure there aren't any typos and that you have access to the repository if it's private.`
);
} else if (apiError && err.action === 'Add a Login Connection') {
client.output.error(

View File

@@ -1,127 +0,0 @@
import { Dictionary } from '@vercel/client';
import { parseRepoUrl } from '../git/connect-git-provider';
import Client from '../client';
import { Org, Project, ProjectSettings } from '../../types';
import { handleOptions } from './handle-options';
import {
promptGitConnectMultipleUrls,
promptGitConnectSingleUrl,
} from './git-connect-prompts';
function getProjectSettings(project: Project): ProjectSettings {
return {
createdAt: project.createdAt,
framework: project.framework,
devCommand: project.devCommand,
installCommand: project.installCommand,
buildCommand: project.buildCommand,
outputDirectory: project.outputDirectory,
rootDirectory: project.rootDirectory,
directoryListing: project.directoryListing,
nodeVersion: project.nodeVersion,
skipGitConnectDuringLink: project.skipGitConnectDuringLink,
};
}
export async function addGitConnection(
client: Client,
org: Org,
project: Project,
remoteUrls: Dictionary<string>,
autoConfirm: Boolean,
settings?: ProjectSettings
): Promise<number | void> {
if (!settings) {
settings = getProjectSettings(project);
}
if (Object.keys(remoteUrls).length === 1) {
return addSingleGitRemote(
client,
org,
project,
remoteUrls,
settings || project,
autoConfirm
);
} else if (Object.keys(remoteUrls).length > 1 && !project.link) {
return addMultipleGitRemotes(
client,
org,
project,
remoteUrls,
settings || project,
autoConfirm
);
}
}
async function addSingleGitRemote(
client: Client,
org: Org,
project: Project,
remoteUrls: Dictionary<string>,
settings: ProjectSettings,
autoConfirm: Boolean
) {
const [remoteName, remoteUrl] = Object.entries(remoteUrls)[0];
const repoInfo = parseRepoUrl(remoteUrl);
if (!repoInfo) {
client.output.debug(`Could not parse repo url ${repoInfo}.`);
return 1;
}
const { org: parsedOrg, repo, provider } = repoInfo;
const alreadyLinked =
project.link &&
project.link.org === parsedOrg &&
project.link.repo === repo &&
project.link.type === provider;
if (alreadyLinked) {
client.output.debug('Project already linked. Skipping...');
return;
}
const replace =
project.link &&
(project.link.org !== parsedOrg ||
project.link.repo !== repo ||
project.link.type !== provider);
let shouldConnectOption: string | undefined;
if (autoConfirm) {
shouldConnectOption = 'yes';
} else {
shouldConnectOption = await promptGitConnectSingleUrl(
client,
project,
remoteName,
remoteUrl,
replace
);
}
return handleOptions(
shouldConnectOption,
client,
org,
project,
settings,
repoInfo
);
}
async function addMultipleGitRemotes(
client: Client,
org: Org,
project: Project,
remoteUrls: Dictionary<string>,
settings: ProjectSettings,
autoConfirm: Boolean
) {
let remoteUrl: string | undefined;
if (autoConfirm) {
remoteUrl = remoteUrls['origin'] || Object.values(remoteUrls)[0];
} else {
client.output.log('Found multiple Git remote URLs in Git config.');
remoteUrl = await promptGitConnectMultipleUrls(client, remoteUrls);
}
return handleOptions(remoteUrl, client, org, project, settings);
}

View File

@@ -1,86 +0,0 @@
import { Dictionary } from '@vercel/client';
import chalk from 'chalk';
import { Project } from '../../types';
import Client from '../client';
import { formatProvider } from '../git/connect-git-provider';
import list from '../input/list';
export async function promptGitConnectSingleUrl(
client: Client,
project: Project,
remoteName: string,
remoteUrl: string,
hasDiffConnectedProvider = false
) {
const { output } = client;
if (hasDiffConnectedProvider) {
const currentRepoPath = `${project.link!.org}/${project.link!.repo}`;
const currentProvider = project.link!.type;
output.print('\n');
output.log(
`Found Git remote URL ${chalk.cyan(
remoteUrl
)}, which is different from the connected ${formatProvider(
currentProvider
)} repository ${chalk.cyan(currentRepoPath)}.`
);
} else {
output.print('\n');
output.log(
`Found local Git remote "${remoteName}": ${chalk.cyan(remoteUrl)}`
);
}
return await list(client, {
message: hasDiffConnectedProvider
? 'Do you want to replace it?'
: `Do you want to connect "${remoteName}" to your Vercel project?`,
choices: [
{
name: 'Yes',
value: 'yes',
short: 'yes',
},
{
name: 'No',
value: 'no',
short: 'no',
},
{
name: 'Do not ask again for this project',
value: 'opt-out',
short: 'no (opt out)',
},
],
});
}
export async function promptGitConnectMultipleUrls(
client: Client,
remoteUrls: Dictionary<string>
) {
const staticOptions = [
{
name: 'No',
value: 'no',
short: 'no',
},
{
name: 'Do not ask again for this project',
value: 'opt-out',
short: 'no (opt out)',
},
];
let choices = [];
for (const url of Object.values(remoteUrls)) {
choices.push({
name: url,
value: url,
short: url,
});
}
choices = choices.concat(staticOptions);
return await list(client, {
message: 'Do you want to connect a Git repository to your Vercel project?',
choices,
});
}

View File

@@ -1,98 +0,0 @@
import chalk from 'chalk';
import { Org, Project, ProjectSettings } from '../../types';
import Client from '../client';
import {
connectGitProvider,
disconnectGitProvider,
formatProvider,
RepoInfo,
parseRepoUrl,
} from '../git/connect-git-provider';
import { Output } from '../output';
import { getCommandName } from '../pkg-name';
import updateProject from '../projects/update-project';
export async function handleOptions(
option: string,
client: Client,
org: Org,
project: Project,
settings: ProjectSettings,
repoInfo?: RepoInfo
) {
const { output } = client;
if (option === 'no') {
skip(output);
return;
} else if (option === 'opt-out') {
optOut(client, project, settings);
return;
} else if (option !== '') {
// Option is "yes" or a URL
// Ensure parsed url exists
if (!repoInfo) {
const _repoInfo = parseRepoUrl(option);
if (!_repoInfo) {
output.debug(`Could not parse repo url ${option}.`);
return 1;
}
repoInfo = _repoInfo;
}
return connect(client, org, project, repoInfo);
}
}
async function optOut(
client: Client,
project: Project,
settings: ProjectSettings
) {
settings.skipGitConnectDuringLink = true;
await updateProject(client, project.name, settings);
client.output
.log(`Opted out. You can re-enable this prompt by visiting the Settings > Git page on the
dashboard for this Project.`);
}
function skip(output: Output) {
output.log('Skipping...');
output.log(
`You can connect a Git repository in the future by running ${getCommandName(
'git connect'
)}.`
);
}
async function connect(
client: Client,
org: Org,
project: Project,
repoInfo: RepoInfo
): Promise<number | void> {
const { output } = client;
const { provider, org: parsedOrg, repo } = repoInfo;
const repoPath = `${parsedOrg}/${repo}`;
output.log('Connecting...');
if (project.link) {
await disconnectGitProvider(client, org, project.id);
}
const connect = await connectGitProvider(
client,
org,
project.id,
provider,
repoPath
);
if (connect !== 1) {
output.log(
`Connected ${formatProvider(provider)} repository ${chalk.cyan(
repoPath
)}!`
);
} else {
return connect;
}
}

View File

@@ -28,8 +28,6 @@ import { EmojiLabel } from '../emoji';
import createDeploy from '../deploy/create-deploy';
import Now, { CreateOptions } from '../index';
import { isAPIError } from '../errors-ts';
import { getRemoteUrls } from '../create-git-meta';
import { addGitConnection } from './add-git-connection';
export interface SetupAndLinkOptions {
forceDelete?: boolean;
@@ -130,20 +128,6 @@ export default async function setupAndLink(
} else {
const project = projectOrNewProjectName;
const remoteUrls = await getRemoteUrls(join(path, '.git/config'), output);
if (remoteUrls && !project.skipGitConnectDuringLink) {
const connectGit = await addGitConnection(
client,
org,
project,
remoteUrls,
autoConfirm
);
if (typeof connectGit === 'number') {
return { status: 'error', exitCode: connectGit };
}
}
await linkFolderToProject(
output,
path,
@@ -258,21 +242,6 @@ export default async function setupAndLink(
const project = await createProject(client, newProjectName);
const remoteUrls = await getRemoteUrls(join(path, '.git/config'), output);
if (remoteUrls) {
const connectGit = await addGitConnection(
client,
org,
project,
remoteUrls,
autoConfirm,
settings
);
if (typeof connectGit === 'number') {
return { status: 'error', exitCode: connectGit };
}
}
await updateProject(client, project.id, settings);
Object.assign(project, settings);

View File

@@ -0,0 +1,13 @@
import mod from '../increment.wasm?module';
export const config = { runtime: 'experimental-edge' };
const init$ = WebAssembly.instantiate(mod);
/** @param {Request} req */
export default async req => {
const givenNumber = Number(new URL(req.url).searchParams.get('number') || 0);
const { exports } = await init$;
const added = exports.add_one(givenNumber);
return new Response(`${givenNumber} + 1 = ${added}`);
};

Binary file not shown.

View File

@@ -53,6 +53,33 @@ test('[vercel dev] should support edge functions', async () => {
}
});
test('[vercel dev] edge functions support WebAssembly files', async () => {
const dir = fixture('edge-function');
const { dev, port, readyResolver } = await testFixture(dir, {
env: {
ENV_VAR_IN_EDGE: '1',
},
});
try {
await readyResolver;
for (const { number, result } of [
{ number: 1, result: 2 },
{ number: 2, result: 3 },
{ number: 12, result: 13 },
]) {
let res = await fetch(
`http://localhost:${port}/api/webassembly?number=${number}`
);
validateResponseHeaders(res);
await expect(res.text()).resolves.toEqual(`${number} + 1 = ${result}`);
}
} finally {
await dev.kill('SIGTERM');
}
});
test(
'[vercel dev] edge functions respond properly the same as production',
testFixtureStdio('edge-function', async (testPath: any) => {

View File

@@ -1,16 +0,0 @@
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
ignorecase = true
precomposeunicode = true
[remote "secondary"]
url = https://github.com/user2/repo2.git
fetch = +refs/heads/*:refs/remotes/secondary/*
[remote "origin"]
url = https://github.com/user/repo.git
fetch = +refs/heads/*:refs/remotes/origin/*
[remote "gitlab"]
url = https://gitlab.com/user/repo.git
fetch = +refs/heads/*:refs/remotes/gitlab/*

View File

@@ -1,2 +0,0 @@
!.vercel
.vercel

View File

@@ -1,16 +0,0 @@
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
ignorecase = true
precomposeunicode = true
[remote "origin"]
url = https://github.com/user/repo.git
fetch = +refs/heads/*:refs/remotes/origin/*
[remote "secondary"]
url = https://github.com/user2/repo2.git
fetch = +refs/heads/*:refs/remotes/secondary/*
[remote "gitlab"]
url = https://gitlab.com/user/repo.git
fetch = +refs/heads/*:refs/remotes/gitlab/*

View File

@@ -1,13 +0,0 @@
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
ignorecase = true
precomposeunicode = true
[remote "origin"]
url = https://github.com/user2/repo2.git
fetch = +refs/heads/*:refs/remotes/origin/*
[branch "master"]
remote = origin
merge = refs/heads/master

View File

@@ -1,2 +0,0 @@
!.vercel
.vercel

View File

@@ -1,13 +0,0 @@
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
ignorecase = true
precomposeunicode = true
[remote "origin"]
url = https://github.com/user/repo.git
fetch = +refs/heads/*:refs/remotes/origin/*
[branch "master"]
remote = origin
merge = refs/heads/master

View File

@@ -39,11 +39,6 @@ describe('git', () => {
await expect(client.stderr).toOutput('Found project');
client.stdin.write('y\n');
await expect(client.stderr).toOutput(
'Do you want to connect "origin" to your Vercel project?'
);
client.stdin.write('n\n');
await expect(client.stderr).toOutput(
`Connecting Git remote: https://github.com/user/repo.git`
);
@@ -295,7 +290,7 @@ describe('git', () => {
`Connecting Git remote: https://github.com/laksfj/asdgklsadkl`
);
await expect(client.stderr).toOutput(
`Failed to link laksfj/asdgklsadkl. Make sure there aren't any typos and that you have access to the repository if it's private.`
`Failed to connect laksfj/asdgklsadkl to project. Make sure there aren't any typos and that you have access to the repository if it's private.`
);
const exitCode = await gitPromise;

View File

@@ -1,357 +0,0 @@
import { join } from 'path';
import fs from 'fs-extra';
import link from '../../../src/commands/link';
import { useUser } from '../../mocks/user';
import { useTeams } from '../../mocks/team';
import {
defaultProject,
useUnknownProject,
useProject,
} from '../../mocks/project';
import { client } from '../../mocks/client';
import { useDeploymentMissingProjectSettings } from '../../mocks/deployment';
import { Project } from '../../../src/types';
describe('link', () => {
describe('git prompt', () => {
const originalCwd = process.cwd();
const fixture = (name: string) =>
join(__dirname, '../../fixtures/unit/link-connect-git', name);
it('should prompt to connect a new project with a single remote', async () => {
const cwd = fixture('single-remote');
try {
process.chdir(cwd);
await fs.rename(join(cwd, 'git'), join(cwd, '.git'));
useUser();
useUnknownProject();
useDeploymentMissingProjectSettings();
useTeams('team_dummy');
const linkPromise = link(client);
await expect(client.stderr).toOutput('Set up');
client.stdin.write('y\n');
await expect(client.stderr).toOutput('Which scope');
client.stdin.write('\r');
await expect(client.stderr).toOutput('Link to existing project?');
client.stdin.write('n\n');
await expect(client.stderr).toOutput('Whats your projects name?');
client.stdin.write('\r');
await expect(client.stderr).toOutput(
'In which directory is your code located?'
);
client.stdin.write('\r');
await expect(client.stderr).toOutput('Want to modify these settings?');
client.stdin.write('n\n');
await expect(client.stderr).toOutput(
'Found local Git remote "origin": https://github.com/user/repo.git'
);
await expect(client.stderr).toOutput(
'Do you want to connect "origin" to your Vercel project?'
);
client.stdin.write('\r');
await expect(client.stderr).toOutput(
'Connected GitHub repository user/repo!'
);
await expect(client.stderr).toOutput('Linked to');
await expect(linkPromise).resolves.toEqual(0);
} finally {
await fs.rename(join(cwd, '.git'), join(cwd, 'git'));
process.chdir(originalCwd);
}
});
it('should prompt to connect an existing project with a single remote to git', async () => {
const cwd = fixture('single-remote');
try {
process.chdir(cwd);
await fs.rename(join(cwd, 'git'), join(cwd, '.git'));
useUser();
useProject({
...defaultProject,
name: 'single-remote',
id: 'single-remote',
});
useTeams('team_dummy');
const linkPromise = link(client);
await expect(client.stderr).toOutput('Set up');
client.stdin.write('y\n');
await expect(client.stderr).toOutput('Which scope');
client.stdin.write('\r');
await expect(client.stderr).toOutput('Found project');
client.stdin.write('y\n');
await expect(client.stderr).toOutput(
'Found local Git remote "origin": https://github.com/user/repo.git'
);
await expect(client.stderr).toOutput(
'Do you want to connect "origin" to your Vercel project?'
);
client.stdin.write('\r');
await expect(client.stderr).toOutput(
'Connected GitHub repository user/repo!'
);
await expect(client.stderr).toOutput('Linked to');
await expect(linkPromise).resolves.toEqual(0);
} finally {
await fs.rename(join(cwd, '.git'), join(cwd, 'git'));
process.chdir(originalCwd);
}
});
it('should prompt to replace a connected repository if there is one remote', async () => {
const cwd = fixture('single-remote-existing-link');
try {
process.chdir(cwd);
await fs.rename(join(cwd, 'git'), join(cwd, '.git'));
useUser();
const project = useProject({
...defaultProject,
name: 'single-remote-existing-link',
id: 'single-remote-existing-link',
});
useTeams('team_dummy');
project.project.link = {
type: 'github',
org: 'user',
repo: 'repo',
repoId: 1010,
gitCredentialId: '',
sourceless: true,
createdAt: 1656109539791,
updatedAt: 1656109539791,
};
const linkPromise = link(client);
await expect(client.stderr).toOutput('Set up');
client.stdin.write('y\n');
await expect(client.stderr).toOutput('Which scope');
client.stdin.write('\r');
await expect(client.stderr).toOutput('Found project');
client.stdin.write('y\n');
await expect(client.stderr).toOutput(
`Found Git remote URL https://github.com/user2/repo2.git, which is different from the connected GitHub repository user/repo.`
);
await expect(client.stderr).toOutput('Do you want to replace it?');
client.stdin.write('\r');
await expect(client.stderr).toOutput(
'Connected GitHub repository user2/repo2!'
);
await expect(client.stderr).toOutput('Linked to');
await expect(linkPromise).resolves.toEqual(0);
} finally {
await fs.rename(join(cwd, '.git'), join(cwd, 'git'));
process.chdir(originalCwd);
}
});
it('should prompt to connect an existing project with multiple remotes', async () => {
const cwd = fixture('multiple-remotes');
try {
process.chdir(cwd);
await fs.rename(join(cwd, 'git'), join(cwd, '.git'));
useUser();
useProject({
...defaultProject,
name: 'multiple-remotes',
id: 'multiple-remotes',
});
useTeams('team_dummy');
const linkPromise = link(client);
await expect(client.stderr).toOutput('Set up');
client.stdin.write('y\n');
await expect(client.stderr).toOutput('Which scope');
client.stdin.write('\r');
await expect(client.stderr).toOutput('Found project');
client.stdin.write('y\n');
await expect(client.stderr).toOutput(
`> Do you want to connect a Git repository to your Vercel project?`
);
client.stdin.write('\r');
await expect(client.stderr).toOutput(
'Connected GitHub repository user/repo!'
);
await expect(client.stderr).toOutput('Linked to');
await expect(linkPromise).resolves.toEqual(0);
} finally {
await fs.rename(join(cwd, '.git'), join(cwd, 'git'));
process.chdir(originalCwd);
}
});
it('should not prompt to replace a connected repository if there is more than one remote', async () => {
const cwd = fixture('multiple-remotes');
try {
process.chdir(cwd);
await fs.rename(join(cwd, 'git'), join(cwd, '.git'));
useUser();
const project = useProject({
...defaultProject,
name: 'multiple-remotes',
id: 'multiple-remotes',
});
useTeams('team_dummy');
project.project.link = {
type: 'github',
org: 'user',
repo: 'repo',
repoId: 1010,
gitCredentialId: '',
sourceless: true,
createdAt: 1656109539791,
updatedAt: 1656109539791,
};
const linkPromise = link(client);
await expect(client.stderr).toOutput('Set up');
client.stdin.write('y\n');
await expect(client.stderr).toOutput('Which scope');
client.stdin.write('\r');
await expect(client.stderr).toOutput('Found project');
client.stdin.write('y\n');
expect(client.stderr).not.toOutput('Found multiple Git remote URLs');
await expect(client.stderr).toOutput('Linked to');
await expect(linkPromise).resolves.toEqual(0);
} finally {
await fs.rename(join(cwd, '.git'), join(cwd, 'git'));
process.chdir(originalCwd);
}
});
it('should set a project setting if user opts out', async () => {
const cwd = fixture('single-remote');
try {
process.chdir(cwd);
await fs.rename(join(cwd, 'git'), join(cwd, '.git'));
useUser();
useProject({
...defaultProject,
name: 'single-remote',
id: 'single-remote',
});
useTeams('team_dummy');
const linkPromise = link(client);
await expect(client.stderr).toOutput('Set up');
client.stdin.write('y\n');
await expect(client.stderr).toOutput('Which scope');
client.stdin.write('\r');
await expect(client.stderr).toOutput('Found project');
client.stdin.write('y\n');
await expect(client.stderr).toOutput(
'Found local Git remote "origin": https://github.com/user/repo.git'
);
await expect(client.stderr).toOutput(
'Do you want to connect "origin" to your Vercel project?'
);
client.stdin.write('\x1B[B'); // Down arrow
client.stdin.write('\x1B[B');
client.stdin.write('\r'); // Opt out
await expect(client.stderr).toOutput(`Opted out.`);
await expect(client.stderr).toOutput('Linked to');
await expect(linkPromise).resolves.toEqual(0);
const newProjectData: Project = await client.fetch(
`/v8/projects/single-remote`
);
expect(newProjectData.skipGitConnectDuringLink).toBeTruthy();
} finally {
await fs.rename(join(cwd, '.git'), join(cwd, 'git'));
process.chdir(originalCwd);
}
});
it('should not prompt to connect git if the project has skipGitConnectDuringLink property', async () => {
const cwd = fixture('single-remote');
try {
process.chdir(cwd);
await fs.rename(join(cwd, 'git'), join(cwd, '.git'));
useUser();
const project = useProject({
...defaultProject,
name: 'single-remote',
id: 'single-remote',
});
useTeams('team_dummy');
project.project.skipGitConnectDuringLink = true;
const linkPromise = link(client);
await expect(client.stderr).toOutput('Set up');
client.stdin.write('y\n');
await expect(client.stderr).toOutput('Which scope');
client.stdin.write('\r');
await expect(client.stderr).toOutput('Found project');
client.stdin.write('y\n');
expect(client.stderr).not.toOutput('Found local Git remote "origin"');
await expect(client.stderr).toOutput('Linked to');
await expect(linkPromise).resolves.toEqual(0);
} finally {
await fs.rename(join(cwd, '.git'), join(cwd, 'git'));
process.chdir(originalCwd);
}
});
it('should respect --yes', async () => {
const cwd = fixture('single-remote');
try {
process.chdir(cwd);
await fs.rename(join(cwd, 'git'), join(cwd, '.git'));
useUser();
useProject({
...defaultProject,
name: 'single-remote',
id: 'single-remote',
});
useTeams('team_dummy');
client.setArgv('--yes');
const linkPromise = link(client);
expect(client.stderr).not.toOutput('Do you want to connect "origin"');
await expect(client.stderr).toOutput('Linked to');
await expect(linkPromise).resolves.toEqual(0);
} finally {
await fs.rename(join(cwd, '.git'), join(cwd, 'git'));
process.chdir(originalCwd);
}
});
it('should respect --yes for multiple remotes when origin is not the first', async () => {
const cwd = fixture('multiple-remotes-prefer-origin');
try {
process.chdir(cwd);
await fs.rename(join(cwd, 'git'), join(cwd, '.git'));
useUser();
useProject({
...defaultProject,
name: 'multiple-remotes-prefer-origin',
id: 'multiple-remotes-prefer-origin',
});
useTeams('team_dummy');
client.setArgv('--yes');
const linkPromise = link(client);
expect(client.stderr).not.toOutput('Found multiple Git remote URLs');
await expect(client.stderr).toOutput(
'Connected GitHub repository user/repo'
);
await expect(linkPromise).resolves.toEqual(0);
} finally {
await fs.rename(join(cwd, '.git'), join(cwd, 'git'));
process.chdir(originalCwd);
}
});
});
});

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/client",
"version": "12.2.0",
"version": "12.2.1",
"main": "dist/index.js",
"typings": "dist/index.d.ts",
"homepage": "https://vercel.com",
@@ -43,7 +43,7 @@
]
},
"dependencies": {
"@vercel/build-utils": "5.3.1",
"@vercel/build-utils": "5.3.2",
"@vercel/routing-utils": "2.0.2",
"@zeit/fetch": "5.2.0",
"async-retry": "1.2.3",

20
packages/go/go.mod Normal file
View File

@@ -0,0 +1,20 @@
module main
go 1.18
// This file exists to allow debugging of Go files within this package,
// such as `util/analyze.go`.
// You can do this by creating a VS Code Launcher Configuration
// replacing `YOUR_VERCEL_DIR` with your vercel directory, like:
// {
// "name": "Debug Go",
// "type": "go",
// "request": "launch",
// "mode": "auto",
// "program": "${fileDirname}",
// "args": [
// "-modpath=YOUR_VERCEL_DIR/packages/go/test/fixtures/24-bad-handler/api/",
// "YOUR_VERCEL_DIR/packages/go/test/fixtures/24-bad-handler/api/index.go"
// ]
// }

View File

@@ -351,10 +351,7 @@ export async function build({
if (isGoModExist && isGoModInRootDir) {
debug('[mod-root] Write main file to ' + downloadPath);
await writeFile(
join(downloadPath, mainGoFileName),
mainModGoContents
);
await writeFile(join(downloadPath, mainGoFileName), mainModGoContents);
undoFileActions.push({
to: undefined, // delete
from: join(downloadPath, mainGoFileName),
@@ -534,8 +531,14 @@ export async function build({
async function renameHandlerFunction(fsPath: string, from: string, to: string) {
let fileContents = await readFile(fsPath, 'utf8');
const fromRegex = new RegExp(`\\b${from}\\b`, 'g');
fileContents = fileContents.replace(fromRegex, to);
// This regex has to walk a fine line where it replaces the most-likely occurrences
// of the handler's identifier without clobbering other syntax.
// Left-hand Side: A single space was chosen because it can catch `func Handler`
// as well as `var _ http.HandlerFunc = Index`.
// Right-hand Side: a word boundary was chosen because this can be an end of line
// or an open paren (as in `func Handler(`).
const fromRegex = new RegExp(String.raw` ${from}\b`, 'g');
fileContents = fileContents.replace(fromRegex, ` ${to}`);
await writeFile(fsPath, fileContents);
}

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/go",
"version": "2.2.0",
"version": "2.2.2",
"license": "MIT",
"main": "./dist/index",
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/go",
@@ -35,7 +35,7 @@
"@types/jest": "28.1.6",
"@types/node-fetch": "^2.3.0",
"@types/tar": "^4.0.0",
"@vercel/build-utils": "5.3.1",
"@vercel/build-utils": "5.3.2",
"@vercel/ncc": "0.24.0",
"async-retry": "1.3.1",
"execa": "^1.0.0",

View File

@@ -0,0 +1 @@
.vercel

View File

@@ -0,0 +1,34 @@
package handler
import (
"fmt"
"net/http"
)
type SampleDecoder struct {
}
type Servers struct {
path string
}
// the handler location logic looks for the first function that matches the proper signature;
// this test makes sure that the BadReceiverHandler is not found because it is a receiver function
// this handler will not be found because it is a receiver function
func (d *SampleDecoder) BadReceiverHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "from BadHandler")
}
// this handler can be delegated to without being renamed
func (s Servers) Handler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, s.path)
}
// this handler will be found because it has the correct function signature
func GoodHandler_api_bad_receiver_go(w http.ResponseWriter, r *http.Request) {
server := Servers{"some/path"}
// this occurence of "Handler" should not be renamed
server.Handler(w, r)
}

View File

@@ -0,0 +1,3 @@
module handler
go 1.13

View File

@@ -0,0 +1,8 @@
{
"probes": [
{
"path": "/api/bad-receiver",
"mustContain": "some/path"
}
]
}

View File

@@ -0,0 +1 @@
{}

View File

@@ -197,7 +197,9 @@ func main() {
// find a valid `http.HandlerFunc` handler function
params := rf[fn.Type.Params.Pos()-offset : fn.Type.Params.End()-offset]
validHandlerFunc := (strings.Contains(string(params), "http.ResponseWriter") &&
strings.Contains(string(params), "*http.Request") && len(fn.Type.Params.List) == 2)
strings.Contains(string(params), "*http.Request") &&
len(fn.Type.Params.List) == 2 &&
(fn.Recv == nil || len(fn.Recv.List) == 0))
if validHandlerFunc {
// we found the first exported function with `http.HandlerFunc`
@@ -223,7 +225,7 @@ func main() {
if fn.Name.IsExported() == true {
for _, param := range fn.Type.Params.List {
paramStr := fmt.Sprintf("%s", param.Type)
if strings.Contains(string(paramStr), "http ResponseWriter") && len(fn.Type.Params.List) == 2 {
if strings.Contains(string(paramStr), "http ResponseWriter") && len(fn.Type.Params.List) == 2 && (fn.Recv == nil || len(fn.Recv.List) == 0) {
analyzed := analyze{
PackageName: parsed.Name.Name,
FuncName: fn.Name.Name,

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/hydrogen",
"version": "0.0.14",
"version": "0.0.15",
"license": "MIT",
"main": "./dist/index.js",
"homepage": "https://vercel.com/docs",
@@ -21,7 +21,7 @@
"devDependencies": {
"@types/jest": "27.5.1",
"@types/node": "*",
"@vercel/build-utils": "5.3.1",
"@vercel/build-utils": "5.3.2",
"typescript": "4.6.4"
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/next",
"version": "3.1.19",
"version": "3.1.21",
"license": "MIT",
"main": "./dist/index",
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/next-js",
@@ -44,7 +44,7 @@
"@types/semver": "6.0.0",
"@types/text-table": "0.2.1",
"@types/webpack-sources": "3.2.0",
"@vercel/build-utils": "5.3.1",
"@vercel/build-utils": "5.3.2",
"@vercel/nft": "0.21.0",
"@vercel/routing-utils": "2.0.2",
"async-sema": "3.0.1",

View File

@@ -36,7 +36,14 @@ import { Sema } from 'async-sema';
// escape-string-regexp version must match Next.js version
import escapeStringRegexp from 'escape-string-regexp';
import findUp from 'find-up';
import { lstat, pathExists, readFile, remove, writeFile } from 'fs-extra';
import {
lstat,
pathExists,
readFile,
readJSON,
remove,
writeFile,
} from 'fs-extra';
import os from 'os';
import path from 'path';
import resolveFrom from 'resolve-from';
@@ -477,6 +484,14 @@ export const build: BuildV2 = async ({
: '/404'
]?.initialRevalidate === 'number';
const hasIsr500Page =
typeof prerenderManifest.staticRoutes[
routesManifest?.i18n
? // eslint-disable-next-line @typescript-eslint/no-non-null-asserted-optional-chain
path.join('/', routesManifest?.i18n!.defaultLocale!, '/500')
: '/500'
]?.initialRevalidate === 'number';
const wildcardConfig: BuildResult['wildcard'] =
routesManifest?.i18n?.domains && routesManifest.i18n.domains.length > 0
? routesManifest.i18n.domains.map(item => {
@@ -996,7 +1011,7 @@ export const build: BuildV2 = async ({
buildId,
'pages'
);
const pages = await getServerlessPages({
const { pages } = await getServerlessPages({
pagesDir,
entryPath,
outputDirectory,
@@ -1073,10 +1088,15 @@ export const build: BuildV2 = async ({
'pages'
);
const pages = await getServerlessPages({
const appPathRoutesManifest = await readJSON(
path.join(entryPath, outputDirectory, 'app-path-routes-manifest.json')
).catch(() => null);
const { pages, appPaths: lambdaAppPaths } = await getServerlessPages({
pagesDir,
entryPath,
outputDirectory,
appPathRoutesManifest,
});
const isApiPage = (page: string) =>
page
@@ -1265,10 +1285,12 @@ export const build: BuildV2 = async ({
config,
nextVersion,
trailingSlash,
appPathRoutesManifest,
dynamicPages,
canUsePreviewMode,
staticPages,
lambdaPages: pages,
lambdaAppPaths,
omittedPrerenderRoutes,
isCorrectLocaleAPIRoutes,
pagesDir,
@@ -1296,6 +1318,7 @@ export const build: BuildV2 = async ({
requiredServerFilesManifest,
privateOutputs,
hasIsr404Page,
hasIsr500Page,
});
}
@@ -2597,18 +2620,39 @@ async function getServerlessPages(params: {
pagesDir: string;
entryPath: string;
outputDirectory: string;
appPathRoutesManifest?: Record<string, string>;
}) {
const [pages, middlewareManifest] = await Promise.all([
const [pages, appPaths, middlewareManifest] = await Promise.all([
glob('**/!(_middleware).js', params.pagesDir),
params.appPathRoutesManifest
? glob('**/page.js', path.join(params.pagesDir, '../app'))
: Promise.resolve({}),
getMiddlewareManifest(params.entryPath, params.outputDirectory),
]);
const normalizedAppPaths: typeof appPaths = {};
if (params.appPathRoutesManifest) {
for (const [entry, normalizedEntry] of Object.entries(
params.appPathRoutesManifest
)) {
const normalizedPath = `${path.join('.', normalizedEntry)}.js`;
const globPath = `${path.join('.', entry)}.js`;
if (appPaths[globPath]) {
normalizedAppPaths[normalizedPath] = appPaths[globPath];
}
}
}
// Edge Functions do not consider as Serverless Functions
for (const edgeFunctionFile of Object.keys(
middlewareManifest?.functions ?? {}
)) {
delete pages[edgeFunctionFile.slice(1) + '.js'];
const edgePath = edgeFunctionFile.slice(1) + '.js';
delete normalizedAppPaths[edgePath];
delete pages[edgePath];
}
return pages;
return { pages, appPaths: normalizedAppPaths };
}

View File

@@ -41,6 +41,7 @@ import {
getNextServerPath,
getMiddlewareBundle,
getFilesMapFromReasons,
UnwrapPromise,
} from './utils';
import {
nodeFileTrace,
@@ -80,21 +81,25 @@ export async function serverBuild({
headers,
dataRoutes,
hasIsr404Page,
hasIsr500Page,
imagesManifest,
wildcardConfig,
routesManifest,
staticPages,
lambdaPages,
nextVersion,
lambdaAppPaths,
canUsePreviewMode,
trailingSlash,
prerenderManifest,
appPathRoutesManifest,
omittedPrerenderRoutes,
trailingSlashRedirects,
isCorrectLocaleAPIRoutes,
lambdaCompressedByteLimit,
requiredServerFilesManifest,
}: {
appPathRoutesManifest?: Record<string, string>;
dynamicPages: string[];
trailingSlash: boolean;
config: Config;
@@ -103,6 +108,7 @@ export async function serverBuild({
canUsePreviewMode: boolean;
omittedPrerenderRoutes: Set<string>;
staticPages: { [key: string]: FileFsRef };
lambdaAppPaths: { [key: string]: FileFsRef };
lambdaPages: { [key: string]: FileFsRef };
privateOutputs: { files: Files; routes: Route[] };
entryPath: string;
@@ -122,6 +128,7 @@ export async function serverBuild({
dataRoutes: Route[];
nextVersion: string;
hasIsr404Page: boolean;
hasIsr500Page: boolean;
trailingSlashRedirects: Route[];
routesManifest: RoutesManifest;
lambdaCompressedByteLimit: number;
@@ -130,6 +137,8 @@ export async function serverBuild({
prerenderManifest: NextPrerenderedRoutes;
requiredServerFilesManifest: NextRequiredServerFilesManifest;
}): Promise<BuildResult> {
lambdaPages = Object.assign({}, lambdaPages, lambdaAppPaths);
const lambdas: { [key: string]: Lambda } = {};
const prerenders: { [key: string]: Prerender } = {};
const lambdaPageKeys = Object.keys(lambdaPages);
@@ -139,6 +148,14 @@ export async function serverBuild({
nextVersion,
EMPTY_ALLOW_QUERY_FOR_PRERENDERED_VERSION
);
let appBuildTraces: UnwrapPromise<ReturnType<typeof glob>> = {};
let appDir: string | null = null;
if (appPathRoutesManifest) {
appDir = path.join(pagesDir, '../app');
appBuildTraces = await glob('**/*.js.nft.json', appDir);
}
const isCorrectNotFoundRoutes = semver.gte(
nextVersion,
CORRECT_NOT_FOUND_ROUTES_VERSION
@@ -531,9 +548,26 @@ export async function serverBuild({
const mergedPageKeys = [...nonApiPages, ...apiPages, ...internalPages];
const traceCache = {};
const getOriginalPagePath = (page: string) => {
let originalPagePath = page;
if (appDir && lambdaAppPaths[page]) {
const { fsPath } = lambdaAppPaths[page];
originalPagePath = path.relative(appDir, fsPath);
}
return originalPagePath;
};
const getBuildTraceFile = (page: string) => {
return (
pageBuildTraces[page + '.nft.json'] ||
appBuildTraces[page + '.nft.json']
);
};
const pathsToTrace: string[] = mergedPageKeys
.map(page => {
if (!pageBuildTraces[page + '.nft.json']) {
if (!getBuildTraceFile(page)) {
return lambdaPages[page].fsPath;
}
})
@@ -557,7 +591,8 @@ export async function serverBuild({
for (const page of mergedPageKeys) {
const tracedFiles: { [key: string]: FileFsRef } = {};
const pageBuildTrace = pageBuildTraces[page + '.nft.json'];
const originalPagePath = getOriginalPagePath(page);
const pageBuildTrace = getBuildTraceFile(originalPagePath);
let fileList: string[];
let reasons: NodeFileTraceReasons;
@@ -565,8 +600,38 @@ export async function serverBuild({
const { files } = JSON.parse(
await fs.readFile(pageBuildTrace.fsPath, 'utf8')
);
// TODO: this will be moved to a separate worker in the future
// although currently this is needed in the lambda
const isAppPath = appDir && lambdaAppPaths[page];
const serverComponentFile = isAppPath
? pageBuildTrace.fsPath.replace(
/\.js\.nft\.json$/,
'.__sc_client__.js'
)
: null;
if (serverComponentFile && (await fs.pathExists(serverComponentFile))) {
files.push(
path.relative(
path.dirname(pageBuildTrace.fsPath),
serverComponentFile
)
);
try {
const scTrace = JSON.parse(
await fs.readFile(`${serverComponentFile}.nft.json`, 'utf8')
);
scTrace.files.forEach((file: string) => files.push(file));
} catch (err) {
/* non-fatal for now */
}
}
fileList = [];
const pageDir = path.dirname(path.join(pagesDir, page));
const curPagesDir = isAppPath && appDir ? appDir : pagesDir;
const pageDir = path.dirname(path.join(curPagesDir, originalPagePath));
const normalizedBaseDir = `${baseDir}${
baseDir.endsWith('/') ? '' : '/'
}`;
@@ -1375,7 +1440,10 @@ export async function serverBuild({
route.src.replace(/(^\^|\$$)/g, '') + '.json$'
);
const { pathname } = new URL(route.dest || '/', 'http://n');
const { pathname, search } = new URL(
route.dest || '/',
'http://n'
);
let isPrerender = !!prerenders[path.join('./', pathname)];
if (routesManifest.i18n) {
@@ -1392,7 +1460,9 @@ export async function serverBuild({
}
if (isPrerender) {
route.dest = `/_next/data/${buildId}${pathname}.json`;
route.dest = `/_next/data/${buildId}${pathname}.json${
search || ''
}`;
}
return route;
})
@@ -1529,10 +1599,8 @@ export async function serverBuild({
},
]),
// static 500 page if present
...(!hasStatic500
? []
: i18n
// custom 500 page if present
...(i18n && (hasStatic500 || hasIsr500Page || lambdaPages['500.js'])
? [
{
src: `${path.join(
@@ -1544,6 +1612,7 @@ export async function serverBuild({
.join('|')})(/.*|$)`,
dest: path.join('/', entryDirectory, '/$nextLocale/500'),
status: 500,
caseSensitive: true,
},
{
src: path.join('/', entryDirectory, '.*'),
@@ -1558,7 +1627,15 @@ export async function serverBuild({
: [
{
src: path.join('/', entryDirectory, '.*'),
dest: path.join('/', entryDirectory, '/500'),
dest: path.join(
'/',
entryDirectory,
hasStatic500 ||
hasIsr500Page ||
lambdas[path.join(entryDirectory, '500')]
? '/500'
: '/_error'
),
status: 500,
},
]),

View File

@@ -2020,7 +2020,7 @@ export const onPrerenderRoute =
}
};
type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;
export type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;
export async function getStaticFiles(
entryPath: string,

View File

@@ -0,0 +1,7 @@
export default function AnotherPage(props) {
return (
<>
<p>hello from newroot/dashboard/another</p>
</>
);
}

View File

@@ -0,0 +1,18 @@
export async function getServerSideProps() {
return {
props: {
world: 'world',
},
};
}
export default function Root({ children, world }) {
return (
<html className="this-is-another-document-html">
<head>
<title>{`hello ${world}`}</title>
</head>
<body className="this-is-another-document-body">{children}</body>
</html>
);
}

View File

@@ -0,0 +1,7 @@
export default function ChangelogPage(props) {
return (
<>
<p>hello from app/dashboard/changelog</p>
</>
);
}

View File

@@ -0,0 +1,7 @@
export default function HelloPage(props) {
return (
<>
<p>hello from app/dashboard/rootonly/hello</p>
</>
);
}

View File

@@ -0,0 +1,18 @@
import { useState, useEffect } from 'react';
import style from './style.module.css';
import './style.css';
export default function ClientComponentRoute() {
const [count, setCount] = useState(0);
useEffect(() => {
setCount(1);
}, [count]);
return (
<>
<p className={style.red}>
hello from app/client-component-route. <b>count: {count}</b>
</p>
</>
);
}

View File

@@ -0,0 +1,3 @@
b {
color: blue;
}

View File

@@ -0,0 +1,3 @@
.red {
color: red;
}

View File

@@ -0,0 +1,18 @@
import { useState, useEffect } from 'react';
import styles from './style.module.css';
import './style.css';
export default function ClientNestedLayout({ children }) {
const [count, setCount] = useState(0);
useEffect(() => {
setCount(1);
}, []);
return (
<>
<h1 className={styles.red}>Client Nested. Count: {count}</h1>
<button onClick={() => setCount(count + 1)}>{count}</button>
{children}
</>
);
}

View File

@@ -0,0 +1,7 @@
export default function ClientPage() {
return (
<>
<p>hello from app/client-nested</p>
</>
);
}

View File

@@ -0,0 +1,3 @@
button {
color: red;
}

View File

@@ -0,0 +1,3 @@
.red {
color: red;
}

View File

@@ -0,0 +1,7 @@
export default function DeploymentsBreakdownPage(props) {
return (
<>
<p>hello from app/dashboard/(custom)/deployments/breakdown</p>
</>
);
}

View File

@@ -0,0 +1,8 @@
export default function CustomDashboardRootLayout({ children }) {
return (
<>
<h2>Custom dashboard</h2>
{children}
</>
);
}

View File

@@ -0,0 +1,15 @@
export async function getServerSideProps({ params }) {
return {
props: {
id: params.id,
},
};
}
export default function DeploymentsPage(props) {
return (
<>
<p>hello from app/dashboard/deployments/[id]. ID is: {props.id}</p>
</>
);
}

View File

@@ -0,0 +1,7 @@
export default function DeploymentsInfoPage(props) {
return (
<>
<p>hello from app/dashboard/deployments/info</p>
</>
);
}

View File

@@ -0,0 +1,16 @@
export function getServerSideProps() {
return {
props: {
message: 'hello',
},
};
}
export default function DeploymentsLayout({ message, children }) {
return (
<>
<h2>Deployments {message}</h2>
{children}
</>
);
}

View File

@@ -0,0 +1,7 @@
export default function LazyComponent() {
return (
<>
<p>hello from lazy</p>
</>
);
}

View File

@@ -0,0 +1,10 @@
import { ClientComponent } from './test.client.js';
export default function DashboardIndexPage() {
return (
<>
<p>hello from app/dashboard/index</p>
<ClientComponent />
</>
);
}

View File

@@ -0,0 +1,13 @@
import { useState, lazy } from 'react';
const Lazy = lazy(() => import('./lazy.client.js'));
export function ClientComponent() {
let [state] = useState('client');
return (
<>
<Lazy />
<p className="hi">hello from modern the {state}</p>
</>
);
}

View File

@@ -0,0 +1,7 @@
export default function IntegrationsPage(props) {
return (
<>
<p>hello from app/dashboard/integrations</p>
</>
);
}

View File

@@ -0,0 +1,8 @@
export default function DashboardLayout(props) {
return (
<>
<h1>Dashboard</h1>
{props.children}
</>
);
}

View File

@@ -0,0 +1,7 @@
export default function DashboardPage(props) {
return (
<>
<p>hello from app/dashboard</p>
</>
);
}

View File

@@ -0,0 +1,19 @@
export async function getServerSideProps({ params }) {
return {
props: {
params,
},
};
}
export default function IdLayout({ children, params }) {
return (
<>
<h3>
Id Layout. Params:{' '}
<span id="id-layout-params">{JSON.stringify(params)}</span>
</h3>
{children}
</>
);
}

View File

@@ -0,0 +1,19 @@
export async function getServerSideProps({ params }) {
return {
props: {
params,
},
};
}
export default function IdPage({ children, params }) {
return (
<>
<p>
Id Page. Params:{' '}
<span id="id-page-params">{JSON.stringify(params)}</span>
</p>
{children}
</>
);
}

View File

@@ -0,0 +1,19 @@
export async function getServerSideProps({ params }) {
return {
props: {
params,
},
};
}
export default function CategoryLayout({ children, params }) {
return (
<>
<h2>
Category Layout. Params:{' '}
<span id="category-layout-params">{JSON.stringify(params)}</span>{' '}
</h2>
{children}
</>
);
}

View File

@@ -0,0 +1,19 @@
export async function getServerSideProps({ params }) {
return {
props: {
params,
},
};
}
export default function DynamicLayout({ children, params }) {
return (
<>
<h1>
Dynamic Layout. Params:{' '}
<span id="dynamic-layout-params">{JSON.stringify(params)}</span>
</h1>
{children}
</>
);
}

View File

@@ -0,0 +1,18 @@
export async function getServerSideProps() {
return {
props: {
world: 'world',
},
};
}
export default function Root({ children, custom, world }) {
return (
<html className="this-is-the-document-html">
<head>
<title>{`hello ${world}`}</title>
</head>
<body className="this-is-the-document-body">{children}</body>
</html>
);
}

View File

@@ -0,0 +1,15 @@
export async function getServerSideProps({ params }) {
return {
props: {
id: params.id,
},
};
}
export default function DeploymentsPage(props) {
return (
<>
<p>hello from app/partial-match-[id]. ID is: {props.id}</p>
</>
);
}

View File

@@ -0,0 +1,7 @@
export default function SharedComponentRoute() {
return (
<>
<p>hello from app/shared-component-route</p>
</>
);
}

View File

@@ -0,0 +1,7 @@
export default function ShouldNotServeClientDotJs(props) {
return (
<>
<p>hello from app/should-not-serve-client</p>
</>
);
}

View File

@@ -0,0 +1,7 @@
export default function ShouldNotServeServerDotJs(props) {
return (
<>
<p>hello from app/should-not-serve-server</p>
</>
);
}

View File

@@ -0,0 +1,3 @@
export default function Loading() {
return <p id="loading-layout">Loading layout...</p>;
}

View File

@@ -0,0 +1,17 @@
export async function getServerSideProps() {
await new Promise(resolve => setTimeout(resolve, 1000));
return {
props: {
message: 'hello from slow layout',
},
};
}
export default function SlowLayout(props) {
return (
<>
<p id="slow-layout-message">{props.message}</p>
{props.children}
</>
);
}

View File

@@ -0,0 +1,3 @@
export default function Loading() {
return <p id="loading-page">Loading page...</p>;
}

View File

@@ -0,0 +1,12 @@
export async function getServerSideProps() {
await new Promise(resolve => setTimeout(resolve, 5000));
return {
props: {
message: 'hello from slow page',
},
};
}
export default function SlowPage(props) {
return <h1 id="slow-page-message">{props.message}</h1>;
}

View File

@@ -0,0 +1,3 @@
export default function Loading() {
return <p id="loading">Loading...</p>;
}

View File

@@ -0,0 +1,17 @@
export async function getServerSideProps() {
await new Promise(resolve => setTimeout(resolve, 5000));
return {
props: {
message: 'hello from slow layout',
},
};
}
export default function SlowLayout(props) {
return (
<>
<p id="slow-layout-message">{props.message}</p>
{props.children}
</>
);
}

View File

@@ -0,0 +1,3 @@
export default function Page() {
return <h1 id="page-message">Hello World</h1>;
}

View File

@@ -0,0 +1,3 @@
export default function Loading() {
return <p id="loading">Loading...</p>;
}

View File

@@ -0,0 +1,12 @@
export async function getServerSideProps() {
await new Promise(resolve => setTimeout(resolve, 5000));
return {
props: {
message: 'hello from slow page',
},
};
}
export default function SlowPage(props) {
return <h1 id="slow-page-message">{props.message}</h1>;
}

View File

@@ -0,0 +1,3 @@
export default function Page() {
return <p id="page">Page</p>;
}

View File

@@ -0,0 +1,12 @@
/* eslint-env jest */
const path = require('path');
const { deployAndTest } = require('../../utils');
const ctx = {};
describe(`${__dirname.split(path.sep).pop()}`, () => {
it('should deploy and pass probe checks', async () => {
const info = await deployAndTest(__dirname);
Object.assign(ctx, info);
});
});

View File

@@ -0,0 +1,8 @@
import { NextResponse } from 'next/server';
export function middleware(request) {
if (request.nextUrl.pathname === '/middleware-to-dashboard') {
// TODO: this does not copy __flight__ and __flight_router_state_tree__
return NextResponse.rewrite(new URL('/dashboard', request.url));
}
}

View File

@@ -0,0 +1,17 @@
module.exports = {
experimental: {
appDir: true,
runtime: 'nodejs',
serverComponents: true,
legacyBrowsers: false,
browsersListForSwc: true,
},
rewrites: async () => {
return [
{
source: '/rewritten-to-dashboard',
destination: '/dashboard',
},
];
},
};

View File

@@ -0,0 +1,7 @@
{
"dependencies": {
"next": "https://files-26yo0dy1b-ijjk-testing.vercel.app",
"react": "experimental",
"react-dom": "experimental"
}
}

View File

@@ -0,0 +1,7 @@
export default function Page(props) {
return (
<>
<p>hello from pages/blog/[slug]</p>
</>
);
}

View File

@@ -0,0 +1,9 @@
import Link from 'next/link';
export default function Page(props) {
return (
<>
<p>hello from pages/index</p>
<Link href="/dashboard">Dashboard</Link>
</>
);
}

View File

@@ -0,0 +1 @@
hello world

View File

@@ -0,0 +1,45 @@
{
"builds": [
{
"src": "package.json",
"use": "@vercel/next"
}
],
"probes": [
{
"path": "/dashboard",
"status": 200,
"mustContain": "hello from app/dashboard"
},
{
"path": "/dashboard/another",
"status": 200,
"mustContain": "hello from newroot/dashboard/another"
},
{
"path": "/dashboard/deployments/123",
"status": 200,
"mustContain": "hello from app/dashboard/deployments/[id]. ID is: <!-- -->123"
},
{
"path": "/",
"status": 200,
"mustContain": "hello from pages/index"
},
{
"path": "/blog/123",
"status": 200,
"mustContain": "hello from pages/blog/[slug]"
},
{
"path": "/dynamic/category-1/id-1",
"status": 200,
"mustContain": "{&quot;category&quot;:&quot;category-1&quot;,&quot;id&quot;:&quot;id-1&quot;}"
},
{
"path": "/dashboard/changelog",
"status": 200,
"mustContain": "hello from app/dashboard/changelog"
}
]
}

View File

@@ -0,0 +1,8 @@
const path = require('path');
const { deployAndTest } = require('../../utils');
describe(`${__dirname.split(path.sep).pop()}`, () => {
it('should deploy and pass probe checks', async () => {
await deployAndTest(__dirname);
});
});

View File

@@ -0,0 +1,5 @@
module.exports = {
generateBuildId() {
return 'testing-build-id';
},
};

View File

@@ -0,0 +1,10 @@
{
"scripts": {
"build": "next build"
},
"dependencies": {
"next": "latest",
"react": "latest",
"react-dom": "latest"
}
}

View File

@@ -0,0 +1,3 @@
export default function Error404(props) {
return <p>pages/404</p>;
}

View File

@@ -0,0 +1,3 @@
export default function Error500(props) {
return <p>pages/500</p>;
}

View File

@@ -0,0 +1,11 @@
import App from 'next/app';
function MyApp({ Component, pageProps }) {
return <Component {...pageProps} />;
}
MyApp.getInitialProps = async function (ctx) {
return App.getInitialProps(ctx);
};
export default MyApp;

View File

@@ -0,0 +1,18 @@
function Error(props) {
return (
<>
<p>pages/_error</p>
<p>{JSON.stringify(props)}</p>
</>
);
}
Error.getInitialProps = async function getInitialProps({ statusCode, err }) {
console.log('Error.getInitialProps', err);
return {
statusCode,
message: err?.message,
};
};
export default Error;

View File

@@ -0,0 +1,3 @@
export default function Page(props) {
return <p>index page</p>;
}

View File

@@ -0,0 +1,7 @@
export default function Page(props) {
return <p>pages/trigger-error</p>;
}
export function getServerSideProps() {
throw new Error('custom error');
}

View File

@@ -0,0 +1,19 @@
{
"probes": [
{
"path": "/",
"status": 200,
"mustContain": "index page"
},
{
"path": "/non-existent",
"status": 404,
"mustContain": "pages/404"
},
{
"path": "/trigger-error",
"status": 500,
"mustContain": "pages/500"
}
]
}

View File

@@ -1,8 +1,69 @@
const path = require('path');
const { deployAndTest } = require('../../utils');
const cheerio = require('cheerio');
const { deployAndTest, check } = require('../../utils');
const fetch = require('../../../../../test/lib/deployment/fetch-retry');
describe(`${__dirname.split(path.sep).pop()}`, () => {
let ctx = {};
it('should deploy and pass probe checks', async () => {
await deployAndTest(__dirname);
const info = await deployAndTest(__dirname);
Object.assign(ctx, info);
});
it('should revalidate content correctly for middleware rewrite', async () => {
const propsFromHtml = async () => {
let res = await fetch(`${ctx.deploymentUrl}/rewrite-to-another-site`);
let $ = cheerio.load(await res.text());
let props = JSON.parse($('#props').text());
return props;
};
let props = await propsFromHtml();
expect(isNaN(props.now)).toBe(false);
const { pageProps: data } = await fetch(
`${ctx.deploymentUrl}/_next/data/testing-build-id/rewrite-to-another-site.json`
).then(res => res.json());
expect(isNaN(data.now)).toBe(false);
const revalidateRes = await fetch(
`${ctx.deploymentUrl}/api/revalidate?urlPath=/_sites/test-revalidate`
);
expect(revalidateRes.status).toBe(200);
expect(await revalidateRes.json()).toEqual({ revalidated: true });
await check(async () => {
const newProps = await propsFromHtml();
console.log({ props, newProps });
if (isNaN(newProps.now)) {
throw new Error();
}
return newProps.now !== props.now
? 'success'
: JSON.stringify({
newProps,
props,
});
}, 'success');
await check(async () => {
const { pageProps: newData } = await fetch(
`${ctx.deploymentUrl}/_next/data/testing-build-id/rewrite-to-another-site.json`
).then(res => res.json());
console.log({ newData, data });
if (isNaN(newData.now)) {
throw new Error();
}
return newData.now !== data.now
? 'success'
: JSON.stringify({
newData,
data,
});
}, 'success');
});
});

View File

@@ -94,6 +94,13 @@ export function middleware(request) {
return NextResponse.rewrite(customUrl);
}
if (url.pathname === '/rewrite-to-another-site') {
const customUrl = new URL(url);
customUrl.pathname = '/_sites/test-revalidate';
console.log('rewriting to', customUrl.pathname, customUrl.href);
return NextResponse.rewrite(customUrl);
}
if (url.pathname === '/redirect-me-to-about') {
url.pathname = '/about';
url.searchParams.set('middleware', 'foo');

View File

@@ -2,7 +2,7 @@ export default function Page(props) {
return (
<>
<p>/_sites/[site]</p>
<p>{JSON.stringify(props)}</p>
<p id="props">{JSON.stringify(props)}</p>
</>
);
}

View File

@@ -0,0 +1,4 @@
export default async function handler(req, res) {
await res.revalidate(req.query.urlPath);
res.json({ revalidated: true });
}

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