Compare commits

...

9 Commits

Author SHA1 Message Date
Nathan Rajlich
c4ab0ebe9c Publish Canary
- vercel@24.0.1-canary.4
 - @vercel/node@1.13.1-canary.4
2022-03-03 13:49:02 -08:00
Nathan Rajlich
321f1232a1 [node] Move @vercel/node-bridge to dependencies (#7515)
Fixes issue introduced by #7436 when using `vc dev`. Since the dev-server script now imports `@vercel/node-bridge` directly, instead of copying the relevant files into the package, the bridge needs to be a regular dependency otherwise the import will fail.

```
Error: Cannot find module '@vercel/node-bridge/launcher.js'
Require stack:
- /usr/local/lib/node_modules/vercel/node_modules/@vercel/node/dist/dev-server.js
    at Function.Module._resolveFilename (internal/modules/cjs/loader.js:902:15)
    at Function.Module._load (internal/modules/cjs/loader.js:746:27)
    at Module.require (internal/modules/cjs/loader.js:974:19)
    at require (internal/modules/cjs/helpers.js:93:18)
    at Object.<anonymous> (/usr/local/lib/node_modules/vercel/node_modules/@vercel/node/dist/dev-server.js:79:23)
    at Module._compile (internal/modules/cjs/loader.js:1085:14)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1114:10)
    at Module.load (internal/modules/cjs/loader.js:950:32)
    at Function.Module._load (internal/modules/cjs/loader.js:790:12)
    at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:76:12)
Error! `node api/hello.js` failed with exit code 1
```
2022-03-03 21:04:33 +00:00
Nathan Rajlich
8a8203e149 Publish Canary
- @vercel/redwood@0.5.2-canary.1
2022-03-02 17:23:02 -08:00
Sean Massa
33527165e7 remove outdated comment (#7512) 2022-03-02 17:21:08 -08:00
Sean Massa
db10383bc8 [redwood] Add @vercel/redwood Builder (#7503) 2022-03-02 15:17:29 -08:00
Sean Massa
56960e506e [cli] enhance changelog accuracy and format (#7387)
* refactor and add test to changelog

* group changelog into areas

* remove revert and reverted commits from changelog

* make sure Revert commits that revert a commit in a previous changelog are not filtered

* run "yarn test" as part of "yarn test-unit"

* remove advanced syntax

* remove advanced syntax

* temp changelog

* remove temp changelog

* remove global jest config

* Delete symlink

* scope jest dir to just the test files we care about
2022-03-02 10:22:56 -08:00
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
232 changed files with 78065 additions and 479 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

@@ -56,7 +56,8 @@
"build": "turbo run build",
"vercel-build": "mkdir -p public && echo '<a href=\"https://vercel.com/import\">Import</a>' > public/output.html",
"pre-commit": "lint-staged",
"test-unit": "node utils/run.js test-unit",
"test": "jest --rootDir=\"test\" --testPathPattern=\"\\.test.js\"",
"test-unit": "yarn test && node utils/run.js test-unit",
"test-integration-cli": "node utils/run.js test-integration-cli",
"test-integration-once": "node utils/run.js test-integration-once",
"test-integration-dev": "node utils/run.js test-integration-dev",

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.4",
"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.4",
"@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.4",
"license": "MIT",
"main": "./dist/index",
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/node-js",
@@ -19,6 +19,7 @@
],
"dependencies": {
"@types/node": "*",
"@vercel/node-bridge": "2.1.2-canary.0",
"ts-node": "8.9.1",
"typescript": "4.3.4"
},
@@ -31,10 +32,9 @@
"@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",
"content-type": "1.0.4",
"cookie": "0.4.0",
"etag": "1.8.1",

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"

12
packages/redwood/build.js Normal file
View File

@@ -0,0 +1,12 @@
const execa = require('execa');
const { remove } = require('fs-extra');
async function main() {
await remove('dist');
await execa('tsc', [], { stdio: 'inherit' });
}
main().catch(err => {
console.error(err);
process.exit(1);
});

View File

@@ -0,0 +1,30 @@
{
"name": "@vercel/redwood",
"version": "0.5.2-canary.1",
"main": "./dist/index.js",
"license": "MIT",
"homepage": "https://vercel.com/docs",
"files": [
"dist"
],
"repository": {
"type": "git",
"url": "https://github.com/vercel/vercel.git",
"directory": "packages/redwood"
},
"scripts": {
"build": "node build.js",
"test-integration-once": "jest --env node --verbose --runInBand --bail",
"prepublishOnly": "node build.js"
},
"dependencies": {
"@vercel/nft": "0.17.5",
"semver": "6.1.1"
},
"devDependencies": {
"@types/aws-lambda": "8.10.19",
"@types/node": "*",
"@types/semver": "6.0.0",
"@vercel/build-utils": "2.14.1-canary.3"
}
}

View File

@@ -0,0 +1,259 @@
import { join, dirname, relative, parse as parsePath, sep } from 'path';
import { readFileSync, lstatSync } from 'fs';
import { intersects, validRange } from 'semver';
import {
Lambda,
Files,
download,
glob,
debug,
getNodeVersion,
getSpawnOptions,
runNpmInstall,
runPackageJsonScript,
execCommand,
File,
FileBlob,
FileFsRef,
PackageJson,
getLambdaOptionsFromFunction,
readConfigFile,
isSymbolicLink,
scanParentDirs,
NodejsLambda,
BuildV2,
PrepareCache,
} from '@vercel/build-utils';
import { nodeFileTrace } from '@vercel/nft';
interface RedwoodToml {
web: { port?: number; apiProxyPath?: string };
api: { port?: number };
browser: { open?: boolean };
}
export const version = 2;
export const build: BuildV2 = async ({
workPath,
files,
entrypoint,
meta = {},
config = {},
}) => {
await download(files, workPath, meta);
Object.keys(process.env)
.filter(key => key.startsWith('VERCEL_'))
.forEach(key => {
const newKey = `REDWOOD_ENV_${key}`;
if (!(newKey in process.env)) {
process.env[newKey] = process.env[key];
}
});
const { installCommand, buildCommand } = config;
const mountpoint = dirname(entrypoint);
const entrypointFsDirname = join(workPath, mountpoint);
const nodeVersion = await getNodeVersion(
entrypointFsDirname,
undefined,
config,
meta
);
const spawnOpts = getSpawnOptions(meta, nodeVersion);
if (typeof installCommand === 'string') {
if (installCommand.trim()) {
console.log(`Running "install" command: \`${installCommand}\`...`);
const { cliType, lockfileVersion } = await scanParentDirs(
entrypointFsDirname
);
const env: Record<string, string> = {
YARN_NODE_LINKER: 'node-modules',
...spawnOpts.env,
};
if (cliType === 'npm') {
if (
typeof lockfileVersion === 'number' &&
lockfileVersion >= 2 &&
(nodeVersion?.major || 0) < 16
) {
// Ensure that npm 7 is at the beginning of the `$PATH`
env.PATH = `/node16/bin-npm7:${env.PATH}`;
console.log('Detected `package-lock.json` generated by npm 7...');
}
}
await execCommand(installCommand, {
...spawnOpts,
env,
cwd: entrypointFsDirname,
});
} else {
console.log(`Skipping "install" command...`);
}
} else {
console.log('Installing dependencies...');
const installTime = Date.now();
await runNpmInstall(entrypointFsDirname, [], spawnOpts, meta, nodeVersion);
debug(`Install complete [${Date.now() - installTime}ms]`);
}
if (meta.isDev) {
throw new Error('Detected `@vercel/redwood` dev but this is not supported');
}
const pkg = await readConfigFile<PackageJson>(join(workPath, 'package.json'));
const toml = await readConfigFile<RedwoodToml>(
join(workPath, 'redwood.toml')
);
if (buildCommand) {
debug(`Executing build command "${buildCommand}"`);
await execCommand(buildCommand, {
...spawnOpts,
cwd: workPath,
});
} else if (hasScript('vercel-build', pkg)) {
debug(`Executing "yarn vercel-build"`);
await runPackageJsonScript(workPath, 'vercel-build', spawnOpts);
} else if (hasScript('build', pkg)) {
debug(`Executing "yarn build"`);
await runPackageJsonScript(workPath, 'build', spawnOpts);
} else {
const { devDependencies = {} } = pkg || {};
const versionRange = devDependencies['@redwoodjs/core'];
let cmd: string;
if (!versionRange || !validRange(versionRange)) {
console.log(
'WARNING: Unable to detect RedwoodJS version in package.json devDependencies'
);
cmd = 'yarn rw deploy vercel'; // Assume 0.25.0 and newer
} else if (intersects(versionRange, '<0.25.0')) {
// older than 0.25.0
cmd =
'yarn rw build && yarn rw db up --no-db-client --auto-approve && yarn rw dataMigrate up';
} else {
// 0.25.0 and newer
cmd = 'yarn rw deploy vercel';
}
await execCommand(cmd, {
...spawnOpts,
cwd: workPath,
});
}
const apiDir = toml?.web?.apiProxyPath?.replace(/^\//, '') ?? 'api';
const apiDistPath = join(workPath, 'api', 'dist', 'functions');
const webDistPath = join(workPath, 'web', 'dist');
const lambdaOutputs: { [filePath: string]: Lambda } = {};
const staticOutputs = await glob('**', webDistPath);
// Each file in the `functions` dir will become a lambda
const functionFiles = await glob('*.js', apiDistPath);
const sourceCache = new Map<string, string | Buffer | null>();
const fsCache = new Map<string, File>();
for (const [funcName, fileFsRef] of Object.entries(functionFiles)) {
const outputName = join(apiDir, parsePath(funcName).name); // remove `.js` extension
const absEntrypoint = fileFsRef.fsPath;
const relativeEntrypoint = relative(workPath, absEntrypoint);
const awsLambdaHandler = getAWSLambdaHandler(relativeEntrypoint, 'handler');
const sourceFile = relativeEntrypoint.replace('/dist/', '/src/');
const { fileList, esmFileList, warnings } = await nodeFileTrace(
[absEntrypoint],
{
base: workPath,
processCwd: workPath,
ts: true,
mixedModules: true,
ignore: config.excludeFiles,
async readFile(fsPath: string): Promise<Buffer | string | null> {
const relPath = relative(workPath, fsPath);
const cached = sourceCache.get(relPath);
if (cached) return cached.toString();
// null represents a not found
if (cached === null) return null;
try {
const source = readFileSync(fsPath);
const { mode } = lstatSync(fsPath);
let entry: File;
if (isSymbolicLink(mode)) {
entry = new FileFsRef({ fsPath, mode });
} else {
entry = new FileBlob({ data: source, mode });
}
fsCache.set(relPath, entry);
sourceCache.set(relPath, source);
return source.toString();
} catch (e: any) {
if (e.code === 'ENOENT' || e.code === 'EISDIR') {
sourceCache.set(relPath, null);
return null;
}
throw e;
}
},
}
);
for (const warning of warnings) {
if (warning?.stack) {
debug(warning.stack.replace('Error: ', 'Warning: '));
}
}
const lambdaFiles: Files = {};
const allFiles = [...fileList, ...esmFileList];
for (const filePath of allFiles) {
lambdaFiles[filePath] = await FileFsRef.fromFsPath({
fsPath: join(workPath, filePath),
});
}
lambdaFiles[relative(workPath, fileFsRef.fsPath)] = fileFsRef;
const { memory, maxDuration } = await getLambdaOptionsFromFunction({
sourceFile,
config,
});
const lambda = new NodejsLambda({
files: lambdaFiles,
handler: relativeEntrypoint,
runtime: nodeVersion.runtime,
memory,
maxDuration,
shouldAddHelpers: false,
shouldAddSourcemapSupport: false,
awsLambdaHandler,
});
lambdaOutputs[outputName] = lambda;
}
return {
output: { ...staticOutputs, ...lambdaOutputs },
routes: [{ handle: 'filesystem' }, { src: '/.*', dest: '/index.html' }],
};
};
function getAWSLambdaHandler(filePath: string, handlerName: string) {
const { dir, name } = parsePath(filePath);
return `${dir}${dir ? sep : ''}${name}.${handlerName}`;
}
function hasScript(scriptName: string, pkg: PackageJson | null) {
const scripts = (pkg && pkg.scripts) || {};
return typeof scripts[scriptName] === 'string';
}
export const prepareCache: PrepareCache = async ({ workPath }) => {
const cache = await glob('**/node_modules/**', workPath);
return cache;
};

View File

@@ -0,0 +1,7 @@
# These environment variables will be used by default if you do not create any
# yourself in .env. This file should be safe to check into your version control
# system. Any custom values should go in .env and .env should *not* be checked
# into version control.
DATABASE_URL=file:./dev.db
BINARY_TARGET=native

View File

@@ -0,0 +1,10 @@
.DS_Store
.env
.netlify
dev.db
dist
dist-babel
node_modules
yarn-error.log
.vercel

View File

@@ -0,0 +1 @@
lts/*

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020 Redwood
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,44 @@
# Redwood
> **HEADS UP:** RedwoodJS is _NOT_ ready for use in Production. It relies heavily on Prisma2, which is currently in testing with an expected production release coming soon. See status at ["Is Prisma2 Ready?"](https://isprisma2ready.com)
## Getting Started
- [Redwoodjs.com](https://redwoodjs.com): home to all things RedwoodJS.
- [Tutorial](https://redwoodjs.com/tutorial/welcome-to-redwood): getting started and complete overview guide.
- [Docs](https://redwoodjs.com/docs/introduction): using the Redwood Router, handling assets and files, list of command-line tools, and more.
- [Redwood Community](https://community.redwoodjs.com): get help, share tips and tricks, and collaborate on everything about RedwoodJS.
### Setup
We use Yarn as our package manager. To get the dependencies installed, just do this in the root directory:
```terminal
yarn install
```
### Fire it up
```terminal
yarn redwood dev
```
Your browser should open automatically to `http://localhost:8910` to see the web app. Lambda functions run on `http://localhost:8911` and are also proxied to `http://localhost:8910/api/functions/*`.
## Development
### Database
We're using [Prisma2](https://github.com/prisma/prisma2), a modern DB toolkit to query, migrate and model your database.
Prisma2 is [not ready for production](https://isprisma2ready.com) at the moment.
To create a development database:
```terminal
yarn redwood db up
```
This will read the schema definition in `api/prisma/schema.prisma` and generate a sqlite database in `api/prisma/dev.db`
If you've made changes to the schema run `yarn redwood db save` to generate a migration, and `yarn redwood db up` to apply the migration/ generate a new ORM client.

View File

@@ -0,0 +1 @@
module.exports = { extends: '../babel.config.js' }

View File

@@ -0,0 +1,9 @@
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"src/*": ["./src/*"]
}
},
"include": ["src/**/*"]
}

View File

@@ -0,0 +1,8 @@
{
"name": "api",
"version": "0.0.0",
"private": true,
"dependencies": {
"@redwoodjs/api": "0.15.0"
}
}

View File

@@ -0,0 +1,18 @@
datasource DS {
provider = "sqlite"
url = env("DATABASE_URL")
}
generator client {
provider = "prisma-client-js"
binaryTargets = env("BINARY_TARGET")
}
// Define your own datamodels here and run `yarn redwood db save` to create
// migrations for them.
// TODO: Please remove the following example:
model UserExample {
id Int @id @default(autoincrement())
email String @unique
name String?
}

View File

@@ -0,0 +1,26 @@
/* eslint-disable no-console */
const { PrismaClient } = require('@prisma/client')
const dotenv = require('dotenv')
dotenv.config()
const db = new PrismaClient()
async function main() {
// Seed data is database data that needs to exist for your app to run.
// Ideally this file should be idempotent: running it multiple times
// will result in the same database state (usually by checking for the
// existence of a record before trying to create it). For example:
//
// const existing = await db.user.findMany({ where: { email: 'admin@email.com' }})
// if (!existing.length) {
// await db.user.create({ data: { name: 'Admin', email: 'admin@email.com' }})
// }
console.info('No data to seed. See api/prisma/seeds.js for info.')
}
main()
.catch((e) => console.error(e))
.finally(async () => {
await db.disconnect()
})

View File

@@ -0,0 +1,19 @@
import {
createGraphQLHandler,
makeMergedSchema,
makeServices,
} from '@redwoodjs/api'
import importAll from '@redwoodjs/api/importAll.macro'
import { db } from 'src/lib/db'
const schemas = importAll('api', 'graphql')
const services = importAll('api', 'services')
export const handler = createGraphQLHandler({
schema: makeMergedSchema({
schemas,
services: makeServices({ services }),
}),
db,
})

View File

@@ -0,0 +1,9 @@
async function handler() {
return {
statusCode: 200,
headers: {},
body: `Memory is: ${process.env.AWS_LAMBDA_FUNCTION_MEMORY_SIZE}`,
}
}
module.exports = { handler }

View File

@@ -0,0 +1,26 @@
const {
promises: { access },
constants: { X_OK },
} = require('fs')
async function isExecutable(fsPath) {
console.log(`Testing is file is executable: ${fsPath}`)
try {
await access(fsPath, X_OK)
return true
} catch (e) {
console.error(e)
return e.message
}
}
async function handler() {
const isExec = await isExecutable(module.id)
return {
statusCode: 200,
headers: {},
body: `File is executable: ${isExec}`,
}
}
module.exports = { handler }

View File

@@ -0,0 +1,6 @@
// See https://github.com/prisma/prisma2/blob/master/docs/prisma-client-js/api.md#constructor
// for options.
import { PrismaClient } from '@prisma/client'
export const db = new PrismaClient()

View File

@@ -0,0 +1,3 @@
module.exports = {
presets: ['@redwoodjs/core/config/babel-preset'],
}

View File

@@ -0,0 +1,19 @@
{
"private": true,
"workspaces": {
"packages": [
"api",
"web"
]
},
"scripts": {
"build": "rw db up --no-db-client --auto-approve && rw build"
},
"devDependencies": {
"@redwoodjs/core": "0.15.0"
},
"engines": {
"node": ">=12",
"yarn": ">=1.15"
}
}

View File

@@ -0,0 +1,9 @@
// https://prettier.io/docs/en/options.html
module.exports = {
trailingComma: 'es5',
bracketSpacing: true,
tabWidth: 2,
semi: false,
singleQuote: true,
arrowParens: 'always',
}

View File

@@ -0,0 +1,7 @@
[web]
port = 8910
apiProxyPath = "/api"
[api]
port = 8911
[browser]
open = false

View File

@@ -0,0 +1,41 @@
{
"version": 2,
"builds": [
{
"src": "package.json",
"use": "@vercel/redwood",
"config": {
"zeroConfig": true,
"functions": {
"api/src/functions/memory.js": {
"memory": 128
}
}
}
}
],
"probes": [
{ "path": "/", "mustContain": "<div id=\"redwood-app\">" },
{ "path": "/about", "mustContain": "<div id=\"redwood-app\">" },
{
"path": "/api/graphql",
"headers": { "Accept": "text/html" },
"mustContain": "<title>GraphQL Playground</title>"
},
{
"path": "/api/graphql",
"method": "POST",
"headers": { "Accept": "application/json" },
"body": { "query": "{ redwood { version } }" },
"mustContain": "0.15.0"
},
{
"path": "/api/permission",
"mustContain": "File is executable: true"
},
{
"path": "/api/memory",
"mustContain": "Memory is: 128"
}
]
}

View File

@@ -0,0 +1 @@
module.exports = { extends: '../babel.config.js' }

View File

@@ -0,0 +1,10 @@
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"src/*": ["./src/*"]
},
"jsx": "preserve"
},
"include": ["src/**/*"]
}

View File

@@ -0,0 +1,15 @@
{
"name": "web",
"version": "0.0.0",
"private": true,
"browserslist": [
"defaults"
],
"dependencies": {
"@redwoodjs/router": "0.15.0",
"@redwoodjs/web": "0.15.0",
"prop-types": "^15.7.2",
"react": "^16.13.1",
"react-dom": "^16.13.1"
}
}

View File

@@ -0,0 +1,45 @@
# Static Assets
Use this folder to add static files directly to your app. All included files and folders will be copied directly into the `/dist` folder (created when Webpack builds for production). They will also be available during development when you run `yarn rw dev`.
> Note: files will _not_ hot reload while the development server is running. You'll need to manually stop/start to access file changes.
### Example Use
A file like `favicon.png` will be copied to `/dist/favicon.png`. A folder containing a file such as `static-files/my-logo.jpg` will be copied to `/dist/static-files/my-logo.jpg`. These can be referenced in your code directly without any special handling, e.g.
```
<link rel="icon" type="image/png" href="/favicon.png" />
```
and
```
<img src="/static-files/my-logo.jpg"> alt="Logo" />
```
Behind the scenes, we are using Webpack's ["copy-webpack-plugin"](https://github.com/webpack-contrib/copy-webpack-plugin).
## Best Practices
Because assets in this folder are bypassing the javascript module system, **this folder should be used sparingly** for assets such as favicons, robots.txt, manifests, libraries incompatible with Webpack, etc.
In general, it's best to import files directly into a template, page, or component. This allows Webpack to include that file in the bundle, which ensures Webpack will correctly process and move assets into the distribution folder, providing error checks and correct paths along the way.
### Example Asset Import with Webpack
Instead of handling our logo image as a static file per the example above, we can do the following:
```
import React from "react"
import logo from "./my-logo.jpg"
function Header() {
return <img src={logo} alt="Logo" />
}
export default Header
```
Behind the scenes, we are using Webpack's ["file-loader"](https://webpack.js.org/loaders/file-loader/) and ["url-loader](https://webpack.js.org/loaders/url-loader/) (for files smaller than 10kb).

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -0,0 +1,2 @@
User-agent: *
Disallow:

View File

@@ -0,0 +1,22 @@
// In this file, all Page components from 'src/pages` are auto-imported. Nested
// directories are supported, and should be uppercase. Each subdirectory will be
// prepended onto the component name.
//
// Examples:
//
// 'src/pages/HomePage/HomePage.js' -> HomePage
// 'src/pages/Admin/BooksPage/BooksPage.js' -> AdminBooksPage
import { Router, Route } from '@redwoodjs/router'
const Routes = () => {
return (
<Router>
<Route path="/" page={HomePage} name="home" />
<Route path="/about" page={AboutPage} name="about" />
<Route notfound page={NotFoundPage} />
</Router>
)
}
export default Routes

View File

@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="icon" type="image/png" href="/favicon.png" />
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<div id="redwood-app"></div>
</body>
</html>

View File

@@ -0,0 +1,16 @@
import ReactDOM from 'react-dom'
import { RedwoodProvider, FatalErrorBoundary } from '@redwoodjs/web'
import FatalErrorPage from 'src/pages/FatalErrorPage'
import Routes from 'src/Routes'
import './index.css'
ReactDOM.render(
<FatalErrorBoundary page={FatalErrorPage}>
<RedwoodProvider>
<Routes />
</RedwoodProvider>
</FatalErrorBoundary>,
document.getElementById('redwood-app')
)

View File

@@ -0,0 +1,45 @@
export default () => (
<main>
<style
dangerouslySetInnerHTML={{
__html: `
html, body {
margin: 0;
}
html * {
box-sizing: border-box;
}
main {
display: flex;
align-items: center;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif;
text-align: center;
background-color: #E2E8F0;
height: 100vh;
}
section {
background-color: white;
border-radius: 0.25rem;
width: 32rem;
padding: 1rem;
margin: 0 auto;
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06);
}
h1 {
font-size: 2rem;
margin: 0;
font-weight: 500;
line-height: 1;
color: #2D3748;
}
`,
}}
/>
<section>
<h1>
<span>About RedwoodJS</span>
</h1>
<p>{process.env.REDWOOD_ENV_VERCEL_URL}</p>
</section>
</main>
)

View File

@@ -0,0 +1,53 @@
// This page will be rendered when an error makes it all the way to the top of the
// application without being handled by a Javascript catch statement or React error
// boundary.
//
// You can modify this page as you wish, but it is important to keep things simple to
// avoid the possibility that it will cause its own error. If it does, Redwood will
// still render a generic error page, but your users will prefer something a bit more
// thoughtful. =)
export default () => (
<main>
<style
dangerouslySetInnerHTML={{
__html: `
html, body {
margin: 0;
}
html * {
box-sizing: border-box;
}
main {
display: flex;
align-items: center;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif;
text-align: center;
background-color: #E2E8F0;
height: 100vh;
}
section {
background-color: white;
border-radius: 0.25rem;
width: 32rem;
padding: 1rem;
margin: 0 auto;
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06);
}
h1 {
font-size: 2rem;
margin: 0;
font-weight: 500;
line-height: 1;
color: #2D3748;
}
`,
}}
/>
<section>
<h1>
<span>Something went wrong</span>
</h1>
</section>
</main>
)

View File

@@ -0,0 +1,44 @@
export default () => (
<main>
<style
dangerouslySetInnerHTML={{
__html: `
html, body {
margin: 0;
}
html * {
box-sizing: border-box;
}
main {
display: flex;
align-items: center;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif;
text-align: center;
background-color: #E2E8F0;
height: 100vh;
}
section {
background-color: white;
border-radius: 0.25rem;
width: 32rem;
padding: 1rem;
margin: 0 auto;
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06);
}
h1 {
font-size: 2rem;
margin: 0;
font-weight: 500;
line-height: 1;
color: #2D3748;
}
`,
}}
/>
<section>
<h1>
<span>Welcome to RedwoodJS!</span>
</h1>
</section>
</main>
)

View File

@@ -0,0 +1,44 @@
export default () => (
<main>
<style
dangerouslySetInnerHTML={{
__html: `
html, body {
margin: 0;
}
html * {
box-sizing: border-box;
}
main {
display: flex;
align-items: center;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif;
text-align: center;
background-color: #E2E8F0;
height: 100vh;
}
section {
background-color: white;
border-radius: 0.25rem;
width: 32rem;
padding: 1rem;
margin: 0 auto;
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06);
}
h1 {
font-size: 2rem;
margin: 0;
font-weight: 500;
line-height: 1;
color: #2D3748;
}
`,
}}
/>
<section>
<h1>
<span>404 Page Not Found</span>
</h1>
</section>
</main>
)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,7 @@
# These environment variables will be used by default if you do not create any
# yourself in .env. This file should be safe to check into your version control
# system. Any custom values should go in .env and .env should *not* be checked
# into version control.
DATABASE_URL=file:./dev.db
BINARY_TARGET=native

View File

@@ -0,0 +1,10 @@
.idea
.DS_Store
.env
.netlify
dev.db
dist
dist-babel
node_modules
yarn-error.log
web/public/mockServiceWorker.js

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020 Redwood
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,54 @@
# Redwood
> **HEADS UP:** RedwoodJS is _NOT_ ready for use in Production. It relies heavily on Prisma2, which is currently in testing with an expected production release coming soon. See status at ["Is Prisma2 Ready?"](https://isprisma2ready.com)
## Getting Started
- [Redwoodjs.com](https://redwoodjs.com): home to all things RedwoodJS.
- [Tutorial](https://redwoodjs.com/tutorial/welcome-to-redwood): getting started and complete overview guide.
- [Docs](https://redwoodjs.com/docs/introduction): using the Redwood Router, handling assets and files, list of command-line tools, and more.
- [Redwood Community](https://community.redwoodjs.com): get help, share tips and tricks, and collaborate on everything about RedwoodJS.
### Setup
We use Yarn as our package manager. To get the dependencies installed, just do this in the root directory:
```terminal
yarn install
```
### Fire it up
```terminal
yarn redwood dev
```
Your browser should open automatically to `http://localhost:8910` to see the web app. Lambda functions run on `http://localhost:8911` and are also proxied to `http://localhost:8910/api/functions/*`.
### Updating Redwood
Redwood comes with a helpful command to update itself and its dependencies. Why not try a new and improved version today?
> :point_right: IMPORTANT: Skipping versions when upgrading is not recommended and will likely cause problems. Do read through all [Release Notes](https://github.com/redwoodjs/redwood/releases) between your current version and the latest version. Each minor release will likely require you to implement breaking change fixes and apply manual code modifications.
```terminal
yarn rw upgrade
```
## Development
### Database
We're using [Prisma2](https://github.com/prisma/prisma2), a modern DB toolkit to query, migrate and model your database.
Prisma2 is [not ready for production](https://isprisma2ready.com) at the moment.
To create a development database:
```terminal
yarn redwood db up
```
This will read the schema definition in `api/prisma/schema.prisma` and generate a sqlite database in `api/prisma/dev.db`
If you've made changes to the schema run `yarn redwood db save` to generate a migration, and `yarn redwood db up` to apply the migration/ generate a new ORM client.

View File

@@ -0,0 +1 @@
module.exports = { extends: '../babel.config.js' }

View File

@@ -0,0 +1,6 @@
const { getConfig } = require('@redwoodjs/core')
const config = getConfig({ type: 'jest', target: 'node' })
config.displayName.name = 'api'
module.exports = config

View File

@@ -0,0 +1,9 @@
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"src/*": ["./src/*"]
}
},
"include": ["src/**/*"]
}

View File

@@ -0,0 +1,8 @@
{
"name": "api",
"version": "0.0.0",
"private": true,
"dependencies": {
"@redwoodjs/api": "^0.15.3"
}
}

View File

@@ -0,0 +1,18 @@
datasource DS {
provider = "sqlite"
url = env("DATABASE_URL")
}
generator client {
provider = "prisma-client-js"
binaryTargets = "native"
}
// Define your own datamodels here and run `yarn redwood db save` to create
// migrations for them.
// TODO: Please remove the following example:
model UserExample {
id Int @id @default(autoincrement())
email String @unique
name String?
}

View File

@@ -0,0 +1,26 @@
/* eslint-disable no-console */
const { PrismaClient } = require('@prisma/client')
const dotenv = require('dotenv')
dotenv.config()
const db = new PrismaClient()
async function main() {
// Seed data is database data that needs to exist for your app to run.
// Ideally this file should be idempotent: running it multiple times
// will result in the same database state (usually by checking for the
// existence of a record before trying to create it). For example:
//
// const existing = await db.user.findMany({ where: { email: 'admin@email.com' }})
// if (!existing.length) {
// await db.user.create({ data: { name: 'Admin', email: 'admin@email.com' }})
// }
console.info('No data to seed. See api/prisma/seeds.js for info.')
}
main()
.catch((e) => console.error(e))
.finally(async () => {
await db.disconnect()
})

View File

@@ -0,0 +1,17 @@
import {
createGraphQLHandler,
makeMergedSchema,
makeServices,
} from '@redwoodjs/api'
import schemas from 'src/graphql/**/*.{js,ts}'
import services from 'src/services/**/*.{js,ts}'
import { db } from 'src/lib/db'
export const handler = createGraphQLHandler({
schema: makeMergedSchema({
schemas,
services: makeServices({ services }),
}),
db,
})

View File

@@ -0,0 +1,6 @@
// See https://www.prisma.io/docs/reference/tools-and-interfaces/prisma-client/constructor
// for options.
import { PrismaClient } from '@prisma/client'
export const db = new PrismaClient()

View File

@@ -0,0 +1,3 @@
module.exports = {
presets: ['@redwoodjs/core/config/babel-preset'],
}

View File

@@ -0,0 +1,17 @@
{
"private": true,
"workspaces": {
"packages": [
"api",
"web"
]
},
"devDependencies": {
"@redwoodjs/core": "^0.15.3",
"netlify-plugin-prisma-provider": "^0.3.0"
},
"engines": {
"node": ">=12",
"yarn": ">=1.15"
}
}

View File

@@ -0,0 +1,9 @@
// https://prettier.io/docs/en/options.html
module.exports = {
trailingComma: 'es5',
bracketSpacing: true,
tabWidth: 2,
semi: false,
singleQuote: true,
arrowParens: 'always',
}

View File

@@ -0,0 +1,7 @@
[web]
port = 8910
apiProxyPath = "/api"
[api]
port = 8911
[browser]
open = false

View File

@@ -0,0 +1,20 @@
{
"version": 2,
"builds": [{ "src": "package.json", "use": "@vercel/redwood" }],
"probes": [
{ "path": "/", "mustContain": "<div id=\"redwood-app\">" },
{ "path": "/about", "mustContain": "<div id=\"redwood-app\">" },
{
"path": "/api/graphql",
"headers": { "Accept": "text/html" },
"mustContain": "<title>GraphQL Playground</title>"
},
{
"path": "/api/graphql",
"method": "POST",
"headers": { "Accept": "application/json" },
"body": { "query": "{ redwood { version } }" },
"mustContain": "0.15.3"
}
]
}

View File

@@ -0,0 +1 @@
module.exports = { extends: '../babel.config.js' }

View File

@@ -0,0 +1,6 @@
const { getConfig } = require('@redwoodjs/core')
const config = getConfig({ type: 'jest', target: 'browser' })
config.displayName.name = 'web'
module.exports = config

View File

@@ -0,0 +1,10 @@
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"src/*": ["./src/*"]
},
"jsx": "preserve"
},
"include": ["src/**/*"]
}

View File

@@ -0,0 +1,15 @@
{
"name": "web",
"version": "0.0.0",
"private": true,
"browserslist": [
"defaults"
],
"dependencies": {
"@redwoodjs/router": "^0.15.3",
"@redwoodjs/web": "^0.15.3",
"prop-types": "^15.7.2",
"react": "^16.13.1",
"react-dom": "^16.13.1"
}
}

View File

@@ -0,0 +1,45 @@
# Static Assets
Use this folder to add static files directly to your app. All included files and folders will be copied directly into the `/dist` folder (created when Webpack builds for production). They will also be available during development when you run `yarn rw dev`.
> Note: files will _not_ hot reload while the development server is running. You'll need to manually stop/start to access file changes.
### Example Use
A file like `favicon.png` will be copied to `/dist/favicon.png`. A folder containing a file such as `static-files/my-logo.jpg` will be copied to `/dist/static-files/my-logo.jpg`. These can be referenced in your code directly without any special handling, e.g.
```
<link rel="icon" type="image/png" href="/favicon.png" />
```
and
```
<img src="/static-files/my-logo.jpg"> alt="Logo" />
```
Behind the scenes, we are using Webpack's ["copy-webpack-plugin"](https://github.com/webpack-contrib/copy-webpack-plugin).
## Best Practices
Because assets in this folder are bypassing the javascript module system, **this folder should be used sparingly** for assets such as favicons, robots.txt, manifests, libraries incompatible with Webpack, etc.
In general, it's best to import files directly into a template, page, or component. This allows Webpack to include that file in the bundle, which ensures Webpack will correctly process and move assets into the distribution folder, providing error checks and correct paths along the way.
### Example Asset Import with Webpack
Instead of handling our logo image as a static file per the example above, we can do the following:
```
import React from "react"
import logo from "./my-logo.jpg"
function Header() {
return <img src={logo} alt="Logo" />
}
export default Header
```
Behind the scenes, we are using Webpack's ["file-loader"](https://webpack.js.org/loaders/file-loader/) and ["url-loader](https://webpack.js.org/loaders/url-loader/) (for files smaller than 10kb).

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -0,0 +1,2 @@
User-agent: *
Disallow:

View File

@@ -0,0 +1,20 @@
// In this file, all Page components from 'src/pages` are auto-imported. Nested
// directories are supported, and should be uppercase. Each subdirectory will be
// prepended onto the component name.
//
// Examples:
//
// 'src/pages/HomePage/HomePage.js' -> HomePage
// 'src/pages/Admin/BooksPage/BooksPage.js' -> AdminBooksPage
import { Router, Route } from '@redwoodjs/router'
const Routes = () => {
return (
<Router>
<Route notfound page={NotFoundPage} />
</Router>
)
}
export default Routes

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