Compare commits

..

12 Commits

Author SHA1 Message Date
JJ Kasper
21337de7cd Publish Stable
- @now/next@2.5.4
2020-04-15 15:57:17 -05:00
JJ Kasper
a6686f9ff5 Publish Canary
- now@18.0.1-canary.1
 - @now/next@2.5.4-canary.0
2020-04-15 15:52:16 -05:00
JJ Kasper
65c621bd55 [now-next] Fix static 404 not being used in mono-repo set-up (#4083)
This fixes the static 404 page not being used when deploying in a mono-repo structure. Before we weren't taking into account the `entryDirectory` where we needed to causing us to deploy `_error` when we didn't need to

x-ref: https://github.com/zeit/now/discussions/4077#discussioncomment-4625
2020-04-15 20:29:12 +00:00
Luc
0827d3514d [now-cli] Remove custom error when fetching frameworks fail (#4081)
Fix PRODUCT-2364.

Use `Client` instead of `node-fetch` to retrieve frameworks list.
2020-04-15 17:30:48 +00:00
Steven
7bc5d9fb5b Publish Canary
- now@18.0.1-canary.0
2020-04-15 12:30:24 -04:00
Steven
c53106ecee [now-cli] Fix for RangeError invalid count value (#4074)
This PR fixes a [sentry error](https://sentry.io/organizations/zeithq/issues/1611628097/events/75ed28bc6aef42868721a0912875fdbd/) where `repeat()` is passed a negative number and throws [`RangeError: invalid count value`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Resulting_string_too_large).

This PR ensures `repeat()` is always passed 0 or greater.
2020-04-15 15:22:30 +00:00
Andy
91e4d18ab8 [now-cli] Proxy everything except Lambdas to the dev server (#4079) 2020-04-15 16:29:59 +02:00
Steven
33cd78b93a [now-cli] Fix undefined teams and cache result (#4071)
- Fix [sentry error](https://sentry.io/organizations/zeithq/issues/1610513448/events/12bde04e921442a4aae8b7b10a759ecb/) where the `teams` might be undefined.
- Fix regression from #3740 which accidentally removed caching `teams`.
- Fix superflous `console.time()` calls for the same `GET /teams` API call. See below:

```sh
> [debug] [2020-04-14T18:31:00.220Z] GET https://api.zeit.co/www/user
> [debug] [2020-04-14T18:31:00.533Z] GET https://api.zeit.co/www/user : 313.078ms
> [debug] [2020-04-14T18:31:00.543Z] GET https://api.zeit.co/teams
> [debug] [2020-04-14T18:31:00.799Z] GET https://api.zeit.co/teams : 255.459ms
> [debug] #1 GET /teams: 260.198ms
```
2020-04-14 19:35:53 +00:00
Dávid Lévai
a765d27e5a [examples] Fix typo in Gatsby example (#4063)
Co-authored-by: Steven <steven@ceriously.com>
2020-04-14 15:06:18 -04:00
Steven
e65ff4bfd5 [examples] Fix ionic-react missing public directory (#4070)
The `public` directory was missing from the `ionic-react` example because we were ignoring all `public` directories.

This PR adds the public directory back (it is copied from now-static-build test fixtures). I also updated `.gitignore` and `.gitattributes` to be a little more friendly to our test fixtures so this doesn't happen again.
2020-04-14 16:31:49 +00:00
Steven
30a4787390 [now-cli] Fix encoding domain names (#4068)
Fixes [sentry error](https://sentry.io/organizations/zeithq/issues/1611174358/events/c3455f32ce1743b58cc248d548538b21/) so that domain name is always encoded because this input comes from the user.
2020-04-14 16:00:04 +00:00
Naoyuki Kanezawa
36f6f1db77 remove query and all options from log (#4066) 2020-04-15 00:31:57 +09:00
46 changed files with 9478 additions and 141 deletions

3
.gitattributes vendored
View File

@@ -1,5 +1,8 @@
# Ignore test fixtures in GitHub Languages
# See https://github.com/github/linguist#vendored-code
examples/* linguist-vendored
utils/* linguist-vendored
test/* linguist-vendored
packages/*/test/* linguist-vendored
# Go build fails with Windows line endings.

4
.gitignore vendored
View File

@@ -23,5 +23,5 @@ packages/now-cli/test/fixtures/integration
test/lib/deployment/failed-page.txt
.DS_Store
.next
.env
public
/.env
/public

View File

@@ -8,7 +8,7 @@ module.exports = {
resolve: `gatsby-plugin-manifest`,
options: {
name: 'Gatsby + Node.js (TypeScript) API',
short_name: 'Gatbsy + Node.js (TypeScript)',
short_name: 'Gatsby + Node.js (TypeScript)',
start_url: '/',
icon: 'src/images/gatsby-icon.png',
},

Binary file not shown.

After

Width:  |  Height:  |  Size: 930 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

View File

@@ -0,0 +1 @@
<svg width="350" height="140" xmlns="http://www.w3.org/2000/svg" style="background:#f6f7f9"><g fill="none" fill-rule="evenodd"><path fill="#F04141" style="mix-blend-mode:multiply" d="M61.905-34.23l96.194 54.51-66.982 54.512L22 34.887z"/><circle fill="#10DC60" style="mix-blend-mode:multiply" cx="155.5" cy="135.5" r="57.5"/><path fill="#3880FF" style="mix-blend-mode:multiply" d="M208.538 9.513l84.417 15.392L223.93 93.93z"/><path fill="#FFCE00" style="mix-blend-mode:multiply" d="M268.625 106.557l46.332-26.75 46.332 26.75v53.5l-46.332 26.75-46.332-26.75z"/><circle fill="#7044FF" style="mix-blend-mode:multiply" cx="299.5" cy="9.5" r="38.5"/><rect fill="#11D3EA" style="mix-blend-mode:multiply" transform="rotate(-60 148.47 37.886)" x="143.372" y="-7.056" width="10.196" height="89.884" rx="5.098"/><path d="M-25.389 74.253l84.86 8.107c5.498.525 9.53 5.407 9.004 10.905a10 10 0 0 1-.057.477l-12.36 85.671a10.002 10.002 0 0 1-11.634 8.42l-86.351-15.226c-5.44-.959-9.07-6.145-8.112-11.584l13.851-78.551a10 10 0 0 1 10.799-8.219z" fill="#7044FF" style="mix-blend-mode:multiply"/><circle fill="#0CD1E8" style="mix-blend-mode:multiply" cx="273.5" cy="106.5" r="20.5"/></g></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1,30 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Ionic App</title>
<base href="/" />
<meta
name="viewport"
content="viewport-fit=cover, width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"
/>
<meta name="format-detection" content="telephone=no" />
<meta name="msapplication-tap-highlight" content="no" />
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<link rel="shortcut icon" type="image/png" href="%PUBLIC_URL%/assets/icon/favicon.png" />
<!-- add to homescreen for ios -->
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-title" content="Ionic App" />
<meta name="apple-mobile-web-app-status-bar-style" content="black" />
</head>
<body>
<div id="root"></div>
</body>
</html>

View File

@@ -0,0 +1,21 @@
{
"short_name": "Ionic App",
"name": "My Ionic App",
"icons": [
{
"src": "assets/icon/favicon.png",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "assets/icon/icon.png",
"type": "image/png",
"sizes": "512x512",
"purpose": "maskable"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#ffffff",
"background_color": "#ffffff"
}

View File

@@ -1,6 +1,6 @@
{
"name": "now",
"version": "18.0.0",
"version": "18.0.1-canary.1",
"preferGlobal": true,
"license": "Apache-2.0",
"description": "The command-line interface for Now",

View File

@@ -37,7 +37,7 @@ export default async function dev(
// retrieve dev command
const [link, frameworks] = await Promise.all([
getLinkedProject(output, client, cwd),
getFrameworks(),
getFrameworks(client),
]);
if (link.status === 'error') {

View File

@@ -155,11 +155,7 @@ export default async function main(ctx) {
type === STATIC
? null
: caught(
now.fetch(
`/v1/now/deployments/${encodeURIComponent(
finalId
)}/events?types=event`
)
now.fetch(`/v1/now/deployments/${encodeURIComponent(finalId)}/events`)
),
isBuilds ? now.fetch(buildsUrl) : { builds: [] },
]);

View File

@@ -4,7 +4,7 @@ import Now from '../util';
import createOutput from '../util/output';
import logo from '../util/output/logo';
import elapsed from '../util/output/elapsed.ts';
import { maybeURL, normalizeURL, parseInstanceURL } from '../util/url';
import { maybeURL, normalizeURL } from '../util/url';
import printEvents from '../util/events';
import Client from '../util/client.ts';
import getScope from '../util/get-scope.ts';
@@ -16,7 +16,6 @@ const help = () => {
${chalk.dim('Options:')}
-h, --help Output usage information
-a, --all Include access logs
-A ${chalk.bold.underline('FILE')}, --local-config=${chalk.bold.underline(
'FILE'
)} Path to the local ${'`now.json`'} file
@@ -28,9 +27,6 @@ const help = () => {
-n ${chalk.bold.underline(
'NUMBER'
)} Number of logs [100]
-q ${chalk.bold.underline('QUERY')}, --query=${chalk.bold.underline(
'QUERY'
)} Search query
-t ${chalk.bold.underline('TOKEN')}, --token=${chalk.bold.underline(
'TOKEN'
)} Login token
@@ -65,23 +61,18 @@ export default async function main(ctx) {
let apiUrl;
let head;
let limit;
let query;
let follow;
let types;
let outputMode;
let since;
let until;
let instanceId;
argv = mri(ctx.argv.slice(2), {
string: ['query', 'since', 'until', 'output'],
boolean: ['help', 'all', 'debug', 'head', 'follow'],
string: ['since', 'until', 'output'],
boolean: ['help', 'debug', 'head', 'follow'],
alias: {
help: 'h',
all: 'a',
debug: 'd',
query: 'q',
follow: 'f',
output: 'o',
},
@@ -121,7 +112,7 @@ export default async function main(ctx) {
return 1;
}
[deploymentIdOrURL, instanceId] = parseInstanceURL(normalizedURL);
deploymentIdOrURL = normalizedURL;
}
debug = argv.debug;
@@ -129,10 +120,8 @@ export default async function main(ctx) {
head = argv.head;
limit = typeof argv.n === 'number' ? argv.n : 100;
query = argv.query || '';
follow = argv.f;
if (follow) until = 0;
types = argv.all ? [] : ['command', 'stdout', 'stderr', 'exit'];
outputMode = argv.output in logPrinters ? argv.output : 'short';
const {
@@ -204,9 +193,6 @@ export default async function main(ctx) {
const findOpts1 = {
direction,
limit,
query,
types,
instanceId,
since,
until,
}; // no follow
@@ -236,9 +222,6 @@ export default async function main(ctx) {
const since2 = lastEvent ? lastEvent.date : Date.now();
const findOpts2 = {
direction: 'forward',
query,
types,
instanceId,
since: since2,
follow: true,
};

View File

@@ -11,11 +11,8 @@ async function getEventsStream(now, idOrHost, options) {
direction: options.direction,
follow: options.follow ? '1' : '',
format: options.format || 'lines',
instanceId: options.instanceId,
limit: options.limit,
q: options.query,
since: options.since,
types: (options.types || []).join(','),
until: options.until,
})}`
);

View File

@@ -1456,19 +1456,24 @@ export default class DevServer {
foundAsset = findAsset(match, requestPath, nowConfig);
}
if (!foundAsset) {
// if the dev command is started, proxy to it
if (this.devProcessPort) {
this.output.debug('Proxy to dev command server');
return proxyPass(
req,
res,
`http://localhost:${this.devProcessPort}`,
this.output,
false
);
}
// Proxy to the dev server:
// - when there is no asset
// - when the asset is not a Lambda (the dev server must take care of all static files)
if (
this.devProcessPort &&
(!foundAsset || (foundAsset && foundAsset.asset.type !== 'Lambda'))
) {
this.output.debug('Proxy to dev command server');
return proxyPass(
req,
res,
`http://localhost:${this.devProcessPort}`,
this.output,
false
);
}
if (!foundAsset) {
await this.send404(req, res, nowRequestId);
return;
}

View File

@@ -19,7 +19,7 @@ export default async function addDNSRecord(
) {
try {
const record = await client.fetch<Response>(
`/v3/domains/${domain}/records`,
`/v3/domains/${encodeURIComponent(domain)}/records`,
{
body: recordData,
method: 'POST',

View File

@@ -22,12 +22,15 @@ export default async function importZonefile(
const zonefile = readFileSync(resolve(zonefilePath), 'utf8');
try {
const res = await client.fetch<Response>(`/v3/domains/${domain}/records`, {
headers: { 'Content-Type': 'text/dns' },
body: zonefile,
method: 'PUT',
json: false,
});
const res = await client.fetch<Response>(
`/v3/domains/${encodeURIComponent(domain)}/records`,
{
headers: { 'Content-Type': 'text/dns' },
body: zonefile,
method: 'PUT',
json: false,
}
);
const { recordIds } = (await res.json()) as JSONResponse;
cancelWait();

View File

@@ -22,5 +22,7 @@ type Response = {
};
export default async function checkTransfer(client: Client, name: string) {
return client.fetch<Response>(`/v4/domains/${name}/registry`);
return client.fetch<Response>(
`/v4/domains/${encodeURIComponent(name)}/registry`
);
}

View File

@@ -18,7 +18,7 @@ async function getDomainByName(
);
try {
const { domain } = await client.fetch<Response>(
`/v4/domains/${domainName}`
`/v4/domains/${encodeURIComponent(domainName)}`
);
cancelWait();
return domain;

View File

@@ -13,10 +13,13 @@ export default async function moveOutDomain(
destination: string
) {
try {
return await client.fetch<Response>(`/v4/domains/${name}`, {
body: { op: 'move-out', destination },
method: 'PATCH',
});
return await client.fetch<Response>(
`/v4/domains/${encodeURIComponent(name)}`,
{
body: { op: 'move-out', destination },
method: 'PATCH',
}
);
} catch (error) {
if (error.code === 'forbidden') {
return new ERRORS.DomainPermissionDenied(name, contextName);

View File

@@ -7,7 +7,9 @@ export default async function removeDomainByName(
domain: string
) {
try {
return await now.fetch(`/v3/domains/${domain}`, { method: 'DELETE' });
return await now.fetch(`/v3/domains/${encodeURIComponent(domain)}`, {
method: 'DELETE',
});
} catch (error) {
if (error.code === 'not_found') {
return new ERRORS.DomainNotFound(domain);

View File

@@ -28,11 +28,8 @@ async function printEvents(
const q = qs.stringify({
direction: findOpts.direction,
limit: findOpts.limit,
q: findOpts.query,
types: (findOpts.types || []).join(','),
since: findOpts.since,
until: findOpts.until,
instanceId: findOpts.instanceId,
follow: findOpts.follow ? '1' : '',
format: 'lines',
});

View File

@@ -20,7 +20,7 @@ import strlen from './strlen';
export default function formatTable(
header: string[],
align: Array<'l' | 'r' | 'c' | '.'>,
blocks: { name: string, rows: string[][] }[],
blocks: { name: string; rows: string[][] }[],
hsep = ' '
) {
const nrCols = header.length;
@@ -50,8 +50,8 @@ export default function formatTable(
for (let j = 0; j < nrCols; j++) {
const col = `${row[j]}`;
const al = align[j] || 'l';
const pad =
padding[j] > 1 ? ' '.repeat(padding[j] * 8 - strlen(col)) : '';
const spaces = Math.max(padding[j] * 8 - strlen(col), 0);
const pad = ' '.repeat(spaces);
rows[i][j] = al === 'l' ? col + pad : pad + col;
}
}

View File

@@ -1,14 +1,6 @@
import fetch from 'node-fetch';
import { Framework } from '@now/frameworks';
import Client from './client';
export async function getFrameworks(): Promise<Framework[]> {
const res = await fetch('https://api.zeit.co/v1/frameworks');
if (!res.ok) {
throw new Error('Could not retrieve frameworks');
}
const json: Framework[] = await res.json();
return json;
export async function getFrameworks(client: Client) {
return await client.fetch<Framework[]>('/v1/frameworks');
}

View File

@@ -6,7 +6,7 @@ import NowTeams from './teams.js';
let teams: Team[] | undefined;
export default async function getTeams(client: Client) {
export default async function getTeams(client: Client): Promise<Team[]> {
if (teams) return teams;
try {
@@ -17,8 +17,8 @@ export default async function getTeams(client: Client) {
debug: client._debug,
});
const teams = (await teamClient.ls()).teams;
return teams as Team[];
teams = (await teamClient.ls()).teams;
return teams || [];
} catch (error) {
if (error instanceof APIError && error.status === 403) {
throw new InvalidToken();

View File

@@ -2,11 +2,7 @@ import Now from './index';
export default class Teams extends Now {
async create({ slug }) {
return this.retry(async (bail, attempt) => {
if (this._debug) {
console.time(`> [debug] #${attempt} POST /teams}`);
}
return this.retry(async bail => {
const res = await this._fetch(`/teams`, {
method: 'POST',
body: {
@@ -14,10 +10,6 @@ export default class Teams extends Now {
},
});
if (this._debug) {
console.timeEnd(`> [debug] #${attempt} POST /teams`);
}
if (res.status === 403) {
return bail(new Error('Unauthorized'));
}
@@ -40,11 +32,7 @@ export default class Teams extends Now {
}
async edit({ id, slug, name }) {
return this.retry(async (bail, attempt) => {
if (this._debug) {
console.time(`> [debug] #${attempt} PATCH /teams/${id}}`);
}
return this.retry(async bail => {
const payload = {};
if (name) {
payload.name = name;
@@ -58,10 +46,6 @@ export default class Teams extends Now {
body: payload,
});
if (this._debug) {
console.timeEnd(`> [debug] #${attempt} PATCH /teams/${id}`);
}
if (res.status === 403) {
return bail(new Error('Unauthorized'));
}
@@ -84,11 +68,7 @@ export default class Teams extends Now {
}
async inviteUser({ teamId, email }) {
return this.retry(async (bail, attempt) => {
if (this._debug) {
console.time(`> [debug] #${attempt} POST /teams/${teamId}/members}`);
}
return this.retry(async bail => {
const publicRes = await this._fetch(`/www/user/public?email=${email}`);
const { name, username } = await publicRes.json();
@@ -99,10 +79,6 @@ export default class Teams extends Now {
},
});
if (this._debug) {
console.timeEnd(`> [debug] #${attempt} POST /teams/${teamId}/members}`);
}
if (res.status === 403) {
return bail(new Error('Unauthorized'));
}
@@ -126,17 +102,9 @@ export default class Teams extends Now {
}
async ls() {
return this.retry(async (bail, attempt) => {
if (this._debug) {
console.time(`> [debug] #${attempt} GET /teams}`);
}
return this.retry(async bail => {
const res = await this._fetch(`/teams`);
if (this._debug) {
console.timeEnd(`> [debug] #${attempt} GET /teams`);
}
if (res.status === 403) {
const error = new Error('Unauthorized');
error.code = 'not_authorized';

View File

@@ -13,10 +13,3 @@ export const normalizeURL = u => {
return u;
};
export const parseInstanceURL = u => {
const m = /^(.+)-([a-z0-9]{24})(\.now\.sh)$/.exec(u);
const url = m ? m[1] + m[3] : u;
const instanceId = m ? m[2] : null;
return [url, instanceId];
};

View File

@@ -1,6 +1,6 @@
{
"name": "@now/next",
"version": "2.5.3",
"version": "2.5.4",
"license": "MIT",
"main": "./dist/index",
"homepage": "https://zeit.co/docs/runtimes#official-runtimes/next-js",

View File

@@ -569,7 +569,7 @@ export const build = async ({
},
// error handling
...(output['404']
...(output[path.join('./', entryDirectory, '404')]
? [
{ handle: 'error' } as Handler,
@@ -745,10 +745,10 @@ export const build = async ({
// this can be either 404.html in latest versions
// or _errors/404.html versions while this was experimental
static404Page =
staticPages['404'] && hasPages404
? '404'
: staticPages['_errors/404']
? '_errors/404'
staticPages[path.join(entryDirectory, '404')] && hasPages404
? path.join(entryDirectory, '404')
: staticPages[path.join(entryDirectory, '_errors/404')]
? path.join(entryDirectory, '_errors/404')
: undefined;
// > 1 because _error is a lambda but isn't used if a static 404 is available
@@ -1259,17 +1259,18 @@ export const build = async ({
{
src: path.join('/', entryDirectory, '.*'),
dest: path.join(
'/',
entryDirectory,
static404Page
? static404Page
: // if static 404 is not present but we have pages/404.js
// it is a lambda due to _app getInitialProps
hasPages404 && lambdas['404']
? '404'
: '_error'
),
// if static 404 is not present but we have pages/404.js
// it is a lambda due to _app getInitialProps
dest: static404Page
? path.join('/', static404Page)
: path.join(
'/',
entryDirectory,
hasPages404 &&
lambdas[path.join('./', entryDirectory, '404')]
? '404'
: '_error'
),
status: 404,
},
]),

View File

@@ -1,6 +1,6 @@
{
"dependencies": {
"next": "9.2.3-canary.4",
"next": "latest",
"react": "^16.8.6",
"react-dom": "^16.8.6"
}

View File

@@ -0,0 +1 @@
.now

View File

@@ -0,0 +1,36 @@
{
"version": 2,
"routes": [
{ "src": "/(.*)", "dest": "/packages/webapp/$1", "continue": true }
],
"builds": [
{
"src": "packages/webapp/next.config.js",
"use": "@now/next"
}
],
"probes": [
{
"path": "/",
"mustContain": "Hi"
},
{
"path": "/",
"responseHeaders": {
"x-now-cache": "HIT"
}
},
{
"path": "/non-existent",
"mustContain": "custom 404!!"
},
{
"path": "/non-existent",
"mustContain": "__next"
},
{
"path": "/non-existent",
"status": 404
}
]
}

View File

@@ -0,0 +1,7 @@
{
"workspaces": [
"packages/*"
],
"private": true,
"name": "mono-repo"
}

View File

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

View File

@@ -0,0 +1,9 @@
{
"name": "webapp",
"version": "0.0.1",
"dependencies": {
"next": "9.3.4",
"react": "^16.8.6",
"react-dom": "^16.8.6"
}
}

View File

@@ -0,0 +1 @@
export default () => 'custom 404!!';

View File

@@ -0,0 +1 @@
export default () => 'Hi';

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1 @@
.now

View File

@@ -0,0 +1,30 @@
{
"version": 2,
"routes": [
{ "src": "/(.*)", "dest": "/packages/webapp/$1", "continue": true }
],
"builds": [
{
"src": "packages/webapp/next.config.js",
"use": "@now/next"
}
],
"probes": [
{
"path": "/",
"mustContain": "Hi"
},
{
"path": "/non-existent",
"mustContain": "custom 404!!"
},
{
"path": "/non-existent",
"mustContain": "__next"
},
{
"path": "/non-existent",
"status": 404
}
]
}

View File

@@ -0,0 +1,7 @@
{
"workspaces": [
"packages/*"
],
"private": true,
"name": "mono-repo"
}

View File

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

View File

@@ -0,0 +1,9 @@
{
"name": "webapp",
"version": "0.0.1",
"dependencies": {
"next": "9.3.4",
"react": "^16.8.6",
"react-dom": "^16.8.6"
}
}

View File

@@ -0,0 +1 @@
export default () => 'custom 404!!';

View File

@@ -0,0 +1,12 @@
const App = ({ Component, pageProps }) => <Component {...pageProps} />;
App.getInitialProps = async ({ ctx, Component }) => {
let pageProps = {};
if (Component.getInitialProps) {
pageProps = await Component.getInitialProps(ctx);
}
return { pageProps };
};
export default App;

View File

@@ -0,0 +1 @@
export default () => 'Hi';

File diff suppressed because it is too large Load Diff