Compare commits

..

3 Commits

Author SHA1 Message Date
Ethan Arrowood
a17f3a96ec Publish Canary
- @vercel/build-utils@2.14.1-canary.3
 - vercel@24.0.1-canary.3
 - @vercel/client@10.3.1-canary.3
 - @vercel/go@1.3.1-canary.3
 - @vercel/node@1.13.1-canary.3
 - @vercel/python@2.2.1-canary.3
 - @vercel/ruby@1.3.1-canary.3
2022-03-02 08:21:30 -07:00
Sam Ko
9ff86a896c [examples] Update todo functionality in the "sveltekit" example (#7506) 2022-03-01 15:50:04 -08:00
Jared Palmer
6ccb4354f9 Add support for PNPM (#6834)
Add support for `pnpm` as well as new fixtures for `pnpm` with and without workspaces

#### Tests

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

#### Code Review

- [x] This PR has a concise title and thorough description useful to a reviewer
- [x] Issue from task tracker has a link to this PR
2022-03-01 22:34:39 +00:00
31 changed files with 648 additions and 462 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,7 @@
{
"private": true,
"name": "sveltekit",
"version": "0.0.1",
"scripts": {
"dev": "svelte-kit dev",
"build": "svelte-kit build",
@@ -9,7 +11,7 @@
"devDependencies": {
"@sveltejs/adapter-auto": "next",
"@sveltejs/kit": "next",
"svelte": "^3.44.0"
"svelte": "^3.46.0"
},
"type": "module",
"dependencies": {

View File

@@ -8,6 +8,6 @@
%svelte.head%
</head>
<body>
<div id="svelte">%svelte.body%</div>
<div>%svelte.body%</div>
</body>
</html>

View File

@@ -1 +0,0 @@
/// <reference types="@sveltejs/kit" />

View File

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

View File

@@ -22,7 +22,7 @@
<div class="counter-viewport">
<div class="counter-digits" style="transform: translate(0, {100 * offset}%)">
<strong style="top: -100%" aria-hidden="true">{Math.floor($displayed_count + 1)}</strong>
<strong class="hidden" aria-hidden="true">{Math.floor($displayed_count + 1)}</strong>
<strong>{Math.floor($displayed_count)}</strong>
</div>
</div>
@@ -94,4 +94,9 @@
width: 100%;
height: 100%;
}
.hidden {
top: -100%;
user-select: none;
}
</style>

View File

@@ -1,6 +1,8 @@
import { invalidate } from '$app/navigation';
// this action (https://svelte.dev/tutorial/actions) allows us to
// progressively enhance a <form> that already works without JS
export function enhance(form, { pending, error, result }) {
export function enhance(form, { pending, error, result } = {}) {
let current_token;
async function handle_submit(e) {
@@ -8,31 +10,35 @@ export function enhance(form, { pending, error, result }) {
e.preventDefault();
const body = new FormData(form);
const data = new FormData(form);
if (pending) pending(body, form);
if (pending) pending({ data, form });
try {
const res = await fetch(form.action, {
const response = await fetch(form.action, {
method: form.method,
headers: {
accept: 'application/json'
},
body
body: data
});
if (token !== current_token) return;
if (res.ok) {
result(res, form);
if (response.ok) {
if (result) result({ data, form, response });
const url = new URL(form.action);
url.search = url.hash = '';
invalidate(url.href);
} else if (error) {
error(res, null, form);
error({ data, form, error: null, response });
} else {
console.error(await res.text());
console.error(await response.text());
}
} catch (e) {
if (error) {
error(null, e, form);
error({ data, form, error: e, response: null });
} else {
throw e;
}

View File

@@ -1,14 +0,0 @@
import { api } from './_api';
// PATCH /todos/:uid.json
export const patch = async (request) => {
return api(request, `todos/${request.locals.userid}/${request.params.uid}`, {
text: request.body.get('text'),
done: request.body.has('done') ? !!request.body.get('done') : undefined
});
};
// DELETE /todos/:uid.json
export const del = async (request) => {
return api(request, `todos/${request.locals.userid}/${request.params.uid}`);
};

View File

@@ -1,6 +1,6 @@
/*
This module is used by the /todos.json and /todos/[uid].json
endpoints to make calls to api.svelte.dev, which stores todos
This module is used by the /todos endpoint to
make calls to api.svelte.dev, which stores todos
for each user. The leading underscore indicates that this is
a private module, _not_ an endpoint — visiting /todos/_api
will net you a 404 response.
@@ -11,35 +11,12 @@
const base = 'https://api.svelte.dev';
export async function api(request, resource, data) {
// user must have a cookie set
if (!request.locals.userid) {
return { status: 401 };
}
const res = await fetch(`${base}/${resource}`, {
method: request.method,
export function api(method, resource, data) {
return fetch(`${base}/${resource}`, {
method,
headers: {
'content-type': 'application/json'
},
body: data && JSON.stringify(data)
});
// if the request came from a <form> submission, the browser's default
// behaviour is to show the URL corresponding to the form's "action"
// attribute. in those cases, we want to redirect them back to the
// /todos page, rather than showing the response
if (res.ok && request.method !== 'GET' && request.headers.accept !== 'application/json') {
return {
status: 303,
headers: {
location: '/todos'
}
};
}
return {
status: res.status,
body: await res.json()
};
}

View File

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

View File

@@ -1,28 +0,0 @@
import { api } from './_api';
// GET /todos.json
export const get = async (request) => {
// request.locals.userid comes from src/hooks.js
const response = await api(request, `todos/${request.locals.userid}`);
if (response.status === 404) {
// user hasn't created a todo list.
// start with an empty array
return { body: [] };
}
return response;
};
// POST /todos.json
export const post = async (request) => {
const response = await api(request, `todos/${request.locals.userid}`, {
// because index.svelte posts a FormData object,
// request.body is _also_ a (readonly) FormData
// object, which allows us to get form data
// with the `body.get(key)` method
text: request.body.get('text')
});
return response;
};

View File

@@ -1,40 +1,9 @@
<script context="module">
import { enhance } from '$lib/form';
// see https://kit.svelte.dev/docs#loading
export const load = async ({ fetch }) => {
const res = await fetch('/todos.json');
if (res.ok) {
const todos = await res.json();
return {
props: { todos }
};
}
const { message } = await res.json();
return {
error: new Error(message)
};
};
</script>
<script>
import { enhance } from '$lib/form';
import { scale } from 'svelte/transition';
import { flip } from 'svelte/animate';
export let todos;
async function patch(res) {
const todo = await res.json();
todos = todos.map((t) => {
if (t.uid === todo.uid) return todo;
return t;
});
}
</script>
<svelte:head>
@@ -46,13 +15,10 @@
<form
class="new"
action="/todos.json"
action="/todos"
method="post"
use:enhance={{
result: async (res, form) => {
const created = await res.json();
todos = [...todos, created];
result: async ({ form }) => {
form.reset();
}
}}
@@ -68,41 +34,33 @@
animate:flip={{ duration: 200 }}
>
<form
action="/todos/{todo.uid}.json?_method=PATCH"
action="/todos?_method=PATCH"
method="post"
use:enhance={{
pending: (data) => {
pending: ({ data }) => {
todo.done = !!data.get('done');
},
result: patch
}
}}
>
<input type="hidden" name="uid" value={todo.uid} />
<input type="hidden" name="done" value={todo.done ? '' : 'true'} />
<button class="toggle" aria-label="Mark todo as {todo.done ? 'not done' : 'done'}" />
</form>
<form
class="text"
action="/todos/{todo.uid}.json?_method=PATCH"
method="post"
use:enhance={{
result: patch
}}
>
<form class="text" action="/todos?_method=PATCH" method="post" use:enhance>
<input type="hidden" name="uid" value={todo.uid} />
<input aria-label="Edit todo" type="text" name="text" value={todo.text} />
<button class="save" aria-label="Save todo" />
</form>
<form
action="/todos/{todo.uid}.json?_method=DELETE"
action="/todos?_method=DELETE"
method="post"
use:enhance={{
pending: () => (todo.pending_delete = true),
result: () => {
todos = todos.filter((t) => t.uid !== todo.uid);
}
pending: () => (todo.pending_delete = true)
}}
>
<input type="hidden" name="uid" value={todo.uid} />
<button class="delete" aria-label="Delete todo" disabled={todo.pending_delete} />
</form>
</div>
@@ -158,7 +116,7 @@
.done {
transform: none;
opacity: 0.4;
filter: drop-shadow(0px 0px 1px rgba(0, 0, 0, 0.1));
filter: drop-shadow(0 0 1px rgba(0, 0, 0, 0.1));
}
form.text {

View File

@@ -5,8 +5,10 @@ const config = {
kit: {
adapter: adapter(),
// hydrate the <div id="svelte"> element in src/app.html
target: '#svelte'
// Override http methods in the Todo forms
methodOverride: {
allowed: ['PATCH', 'DELETE']
}
}
};

View File

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

View File

@@ -8,12 +8,13 @@ import { deprecate } from 'util';
import { NowBuildError } from '../errors';
import { Meta, PackageJson, NodeVersion, Config } from '../types';
import { getSupportedNodeVersion, getLatestNodeVersion } from './node-version';
import { readConfigFile } from './read-config-file';
export type CliType = 'yarn' | 'npm';
export type CliType = 'yarn' | 'npm' | 'pnpm';
export interface ScanParentDirsResult {
/**
* "yarn" or "npm", depending on the presence of lockfiles.
* "yarn", "npm", or "pnpm" depending on the presence of lockfiles.
*/
cliType: CliType;
/**
@@ -252,7 +253,7 @@ export async function scanParentDirs(
}
// eslint-disable-next-line no-await-in-loop
const [packageLockJson, hasYarnLock] = await Promise.all([
const [packageLockJson, hasYarnLock, pnpmLockYaml] = await Promise.all([
fs
.readJson(path.join(currentDestPath, 'package-lock.json'))
.catch(error => {
@@ -263,17 +264,26 @@ export async function scanParentDirs(
throw error;
}),
fs.pathExists(path.join(currentDestPath, 'yarn.lock')),
readConfigFile<{ lockfileVersion: number }>(
path.join(currentDestPath, 'pnpm-lock.yaml')
),
]);
if (packageLockJson && !hasYarnLock) {
if (packageLockJson && !hasYarnLock && !pnpmLockYaml) {
cliType = 'npm';
lockfileVersion = packageLockJson.lockfileVersion;
}
if (!packageLockJson && !hasYarnLock && pnpmLockYaml) {
cliType = 'pnpm';
// just ensure that it is read as a number and not a string
lockfileVersion = Number(pnpmLockYaml.lockfileVersion);
}
// Only stop iterating if a lockfile was found, because it's possible
// that the lockfile is in a higher path than where the `package.json`
// file was found.
if (packageLockJson || hasYarnLock) {
if (packageLockJson || hasYarnLock || pnpmLockYaml) {
break;
}
}
@@ -341,6 +351,13 @@ export async function runNpmInstall(
commandArgs = args
.filter(a => a !== '--prefer-offline')
.concat(['install', '--no-audit', '--unsafe-perm']);
} else if (cliType === 'pnpm') {
// PNPM's install command is similar to NPM's but without the audit nonsense
// @see options https://pnpm.io/cli/install
opts.prettyCommand = 'pnpm install';
commandArgs = args
.filter(a => a !== '--prefer-offline')
.concat(['install', '--unsafe-perm']);
} else {
opts.prettyCommand = 'yarn install';
commandArgs = ['install', ...args];

View File

@@ -0,0 +1,15 @@
{
"name": "22-pnpm",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"build": "mkdir -p public && (printf \"pnpm version: \" && pnpm -v) > public/index.txt"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"once": "^1.4.0"
}
}

View File

@@ -0,0 +1,19 @@
lockfileVersion: 5.3
specifiers:
once: ^1.4.0
dependencies:
once: 1.4.0
packages:
/once/1.4.0:
resolution: {integrity: sha1-WDsap3WWHUsROsF9nFC6753Xa9E=}
dependencies:
wrappy: 1.0.2
dev: false
/wrappy/1.0.2:
resolution: {integrity: sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=}
dev: false

View File

@@ -0,0 +1,5 @@
{
"version": 2,
"builds": [{ "src": "package.json", "use": "@vercel/static-build" }],
"probes": [{ "path": "/", "mustContain": "pnpm version: 6" }]
}

View File

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

View File

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

View File

@@ -0,0 +1,6 @@
{
"private": true,
"name": "23-pnpm-workspaces",
"license": "MIT",
"version": "1.0.0"
}

View File

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

View File

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

View File

@@ -31,6 +31,7 @@ const skipFixtures: string[] = [
'07-zero-config-jekyll',
'08-zero-config-middleman',
'21-npm-workspaces',
'23-pnpm-workspaces',
];
// eslint-disable-next-line no-restricted-syntax

View File

@@ -332,3 +332,17 @@ it('should detect npm Workspaces', async () => {
expect(result.cliType).toEqual('npm');
expect(result.lockfileVersion).toEqual(2);
});
it('should detect pnpm', async () => {
const fixture = path.join(__dirname, 'fixtures', '22-pnpm');
const result = await scanParentDirs(fixture);
expect(result.cliType).toEqual('pnpm');
expect(result.lockfileVersion).toEqual(5.3);
});
it('should detect pnpm Workspaces', async () => {
const fixture = path.join(__dirname, 'fixtures', '23-pnpm-workspaces/a');
const result = await scanParentDirs(fixture);
expect(result.cliType).toEqual('pnpm');
expect(result.lockfileVersion).toEqual(5.3);
});

View File

@@ -1,6 +1,6 @@
{
"name": "vercel",
"version": "24.0.1-canary.2",
"version": "24.0.1-canary.3",
"preferGlobal": true,
"license": "Apache-2.0",
"description": "The command-line interface for Vercel",
@@ -43,11 +43,11 @@
"node": ">= 12"
},
"dependencies": {
"@vercel/build-utils": "2.14.1-canary.2",
"@vercel/go": "1.3.1-canary.2",
"@vercel/node": "1.13.1-canary.2",
"@vercel/python": "2.2.1-canary.2",
"@vercel/ruby": "1.3.1-canary.2",
"@vercel/build-utils": "2.14.1-canary.3",
"@vercel/go": "1.3.1-canary.3",
"@vercel/node": "1.13.1-canary.3",
"@vercel/python": "2.2.1-canary.3",
"@vercel/ruby": "1.3.1-canary.3",
"update-notifier": "4.1.0"
},
"devDependencies": {
@@ -88,7 +88,7 @@
"@types/update-notifier": "5.1.0",
"@types/which": "1.3.2",
"@types/write-json-file": "2.2.1",
"@vercel/client": "10.3.1-canary.2",
"@vercel/client": "10.3.1-canary.3",
"@vercel/fetch-retry": "5.0.3",
"@vercel/frameworks": "0.6.1-canary.2",
"@vercel/ncc": "0.24.0",

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/client",
"version": "10.3.1-canary.2",
"version": "10.3.1-canary.3",
"main": "dist/index.js",
"typings": "dist/index.d.ts",
"homepage": "https://vercel.com",
@@ -40,7 +40,7 @@
]
},
"dependencies": {
"@vercel/build-utils": "2.14.1-canary.2",
"@vercel/build-utils": "2.14.1-canary.3",
"@zeit/fetch": "5.2.0",
"async-retry": "1.2.3",
"async-sema": "3.0.0",

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/go",
"version": "1.3.1-canary.2",
"version": "1.3.1-canary.3",
"license": "MIT",
"main": "./dist/index",
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/go",
@@ -24,7 +24,7 @@
"@types/fs-extra": "^5.0.5",
"@types/node-fetch": "^2.3.0",
"@types/tar": "^4.0.0",
"@vercel/build-utils": "2.14.1-canary.2",
"@vercel/build-utils": "2.14.1-canary.3",
"@vercel/ncc": "0.24.0",
"async-retry": "1.3.1",
"execa": "^1.0.0",

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/node",
"version": "1.13.1-canary.2",
"version": "1.13.1-canary.3",
"license": "MIT",
"main": "./dist/index",
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/node-js",
@@ -31,7 +31,7 @@
"@types/cookie": "0.3.3",
"@types/etag": "1.8.0",
"@types/test-listen": "1.1.0",
"@vercel/build-utils": "2.14.1-canary.2",
"@vercel/build-utils": "2.14.1-canary.3",
"@vercel/ncc": "0.24.0",
"@vercel/nft": "0.17.5",
"@vercel/node-bridge": "2.1.2-canary.0",

View File

@@ -1,6 +1,6 @@
{
"name": "@vercel/python",
"version": "2.2.1-canary.2",
"version": "2.2.1-canary.3",
"main": "./dist/index.js",
"license": "MIT",
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/python",
@@ -20,7 +20,7 @@
},
"devDependencies": {
"@types/execa": "^0.9.0",
"@vercel/build-utils": "2.14.1-canary.2",
"@vercel/build-utils": "2.14.1-canary.3",
"@vercel/ncc": "0.24.0",
"execa": "^1.0.0",
"typescript": "4.3.4"

View File

@@ -1,7 +1,7 @@
{
"name": "@vercel/ruby",
"author": "Nathan Cahill <nathan@nathancahill.com>",
"version": "1.3.1-canary.2",
"version": "1.3.1-canary.3",
"license": "MIT",
"main": "./dist/index",
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/ruby",
@@ -22,7 +22,7 @@
"devDependencies": {
"@types/fs-extra": "8.0.0",
"@types/semver": "6.0.0",
"@vercel/build-utils": "2.14.1-canary.2",
"@vercel/build-utils": "2.14.1-canary.3",
"@vercel/ncc": "0.24.0",
"execa": "2.0.4",
"fs-extra": "^7.0.1",