mirror of
https://github.com/LukeHagar/vercel.git
synced 2025-12-24 03:39:11 +00:00
Compare commits
43 Commits
@now/next@
...
@now/next@
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bd6e0f9f93 | ||
|
|
aa8d002309 | ||
|
|
42ce9aca86 | ||
|
|
156f596189 | ||
|
|
8acfd5bf71 | ||
|
|
76c99ebb28 | ||
|
|
5fb119b99c | ||
|
|
99b766d9cb | ||
|
|
c207cf9b40 | ||
|
|
dd00ac4621 | ||
|
|
3d18a067a0 | ||
|
|
c48571a799 | ||
|
|
6eeb6983d9 | ||
|
|
aee33f040d | ||
|
|
b100677b3b | ||
|
|
9241b3ae2f | ||
|
|
1088da6871 | ||
|
|
f7b4dd4458 | ||
|
|
fb85b6b27a | ||
|
|
2e5e9b9a6f | ||
|
|
d3cc306e5b | ||
|
|
d6c6a2a271 | ||
|
|
6171a58ae3 | ||
|
|
5a6d1a135f | ||
|
|
68deab9007 | ||
|
|
d6114e2bef | ||
|
|
5fdc55f3fb | ||
|
|
751b166536 | ||
|
|
6ffc8d97f4 | ||
|
|
67a80d6b83 | ||
|
|
934cf772bc | ||
|
|
b01a24afdb | ||
|
|
0ad75b52bf | ||
|
|
050772e78a | ||
|
|
7c05dc1420 | ||
|
|
bdd25ac727 | ||
|
|
3a27328828 | ||
|
|
c076a5620f | ||
|
|
2bd8ef9eed | ||
|
|
500014f2fc | ||
|
|
17687e9bcd | ||
|
|
90354e9fe7 | ||
|
|
6236631beb |
@@ -340,6 +340,10 @@ jobs:
|
||||
- run:
|
||||
name: Running Unit Tests
|
||||
command: yarn test-unit --clean false
|
||||
- persist_to_workspace:
|
||||
root: .
|
||||
paths:
|
||||
- packages/now-cli/.nyc_output
|
||||
|
||||
coverage:
|
||||
docker:
|
||||
@@ -349,12 +353,6 @@ jobs:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
at: .
|
||||
- run:
|
||||
name: Compiling `now dev` HTML error templates
|
||||
command: node packages/now-cli/scripts/compile-templates.js
|
||||
- run:
|
||||
name: Run unit tests
|
||||
command: yarn workspace now run test-unit
|
||||
- run:
|
||||
name: Run coverage report
|
||||
command: yarn workspace now run coverage
|
||||
|
||||
@@ -6,7 +6,7 @@ A Runtime is an npm module that exposes a `build` function and optionally an `an
|
||||
Official Runtimes are published to [npmjs.com](https://npmjs.com) as a package and referenced in the `use` property of the `now.json` configuration file.
|
||||
However, the `use` property will work with any [npm install argument](https://docs.npmjs.com/cli/install) such as a git repo url which is useful for testing your Runtime.
|
||||
|
||||
See the [Runtimes Documentation](https://zeit.co/docs/v2/advanced/runtimes) to view example usage.
|
||||
See the [Runtimes Documentation](https://zeit.co/docs/runtimes) to view example usage.
|
||||
|
||||
## Runtime Exports
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@now/build-utils",
|
||||
"version": "1.1.1",
|
||||
"version": "1.2.1-canary.0",
|
||||
"license": "MIT",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.js",
|
||||
@@ -32,9 +32,9 @@
|
||||
"aggregate-error": "3.0.1",
|
||||
"async-retry": "1.2.3",
|
||||
"async-sema": "2.1.4",
|
||||
"boxen": "4.2.0",
|
||||
"cross-spawn": "6.0.5",
|
||||
"end-of-stream": "1.4.1",
|
||||
"execa": "^1.0.0",
|
||||
"fs-extra": "7.0.0",
|
||||
"glob": "7.1.3",
|
||||
"into-stream": "5.0.0",
|
||||
|
||||
432
packages/now-build-utils/src/detect-builders-legacy.ts
Normal file
432
packages/now-build-utils/src/detect-builders-legacy.ts
Normal file
@@ -0,0 +1,432 @@
|
||||
import minimatch from 'minimatch';
|
||||
import { valid as validSemver } from 'semver';
|
||||
import { PackageJson, Builder, Config, BuilderFunctions } from './types';
|
||||
|
||||
interface ErrorResponse {
|
||||
code: string;
|
||||
message: string;
|
||||
}
|
||||
|
||||
interface Options {
|
||||
tag?: 'canary' | 'latest' | string;
|
||||
functions?: BuilderFunctions;
|
||||
}
|
||||
|
||||
const src = 'package.json';
|
||||
const config: Config = { zeroConfig: true };
|
||||
|
||||
const MISSING_BUILD_SCRIPT_ERROR: ErrorResponse = {
|
||||
code: 'missing_build_script',
|
||||
message:
|
||||
'Your `package.json` file is missing a `build` property inside the `scripts` property.' +
|
||||
'\nMore details: https://zeit.co/docs/v2/platform/frequently-asked-questions#missing-build-script',
|
||||
};
|
||||
|
||||
// Static builders are special cased in `@now/static-build`
|
||||
function getBuilders({ tag }: Options = {}): Map<string, Builder> {
|
||||
const withTag = tag ? `@${tag}` : '';
|
||||
const config = { zeroConfig: true };
|
||||
|
||||
return new Map<string, Builder>([
|
||||
['next', { src, use: `@now/next${withTag}`, config }],
|
||||
]);
|
||||
}
|
||||
|
||||
// Must be a function to ensure that the returned
|
||||
// object won't be a reference
|
||||
function getApiBuilders({ tag }: Pick<Options, 'tag'> = {}): Builder[] {
|
||||
const withTag = tag ? `@${tag}` : '';
|
||||
const config = { zeroConfig: true };
|
||||
|
||||
return [
|
||||
{ src: 'api/**/*.js', use: `@now/node${withTag}`, config },
|
||||
{ src: 'api/**/*.ts', use: `@now/node${withTag}`, config },
|
||||
{ src: 'api/**/*.go', use: `@now/go${withTag}`, config },
|
||||
{ src: 'api/**/*.py', use: `@now/python${withTag}`, config },
|
||||
{ src: 'api/**/*.rb', use: `@now/ruby${withTag}`, config },
|
||||
];
|
||||
}
|
||||
|
||||
function hasPublicDirectory(files: string[]) {
|
||||
return files.some(name => name.startsWith('public/'));
|
||||
}
|
||||
|
||||
function hasBuildScript(pkg: PackageJson | undefined) {
|
||||
const { scripts = {} } = pkg || {};
|
||||
return Boolean(scripts && scripts['build']);
|
||||
}
|
||||
|
||||
function getApiFunctionBuilder(
|
||||
file: string,
|
||||
prevBuilder: Builder | undefined,
|
||||
{ functions = {} }: Pick<Options, 'functions'>
|
||||
) {
|
||||
const key = Object.keys(functions).find(
|
||||
k => file === k || minimatch(file, k)
|
||||
);
|
||||
const fn = key ? functions[key] : undefined;
|
||||
|
||||
if (!fn || (!fn.runtime && !prevBuilder)) {
|
||||
return prevBuilder;
|
||||
}
|
||||
|
||||
const src = (prevBuilder && prevBuilder.src) || file;
|
||||
const use = fn.runtime || (prevBuilder && prevBuilder.use);
|
||||
|
||||
const config: Config = { zeroConfig: true };
|
||||
|
||||
if (key) {
|
||||
Object.assign(config, {
|
||||
functions: {
|
||||
[key]: fn,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const { includeFiles, excludeFiles } = fn;
|
||||
|
||||
if (includeFiles) Object.assign(config, { includeFiles });
|
||||
if (excludeFiles) Object.assign(config, { excludeFiles });
|
||||
|
||||
return use ? { use, src, config } : prevBuilder;
|
||||
}
|
||||
|
||||
async function detectFrontBuilder(
|
||||
pkg: PackageJson,
|
||||
builders: Builder[],
|
||||
options: Options
|
||||
): Promise<Builder> {
|
||||
const { tag } = options;
|
||||
const withTag = tag ? `@${tag}` : '';
|
||||
for (const [dependency, builder] of getBuilders(options)) {
|
||||
const deps = Object.assign({}, pkg.dependencies, pkg.devDependencies);
|
||||
|
||||
// Return the builder when a dependency matches
|
||||
if (deps[dependency]) {
|
||||
if (options.functions) {
|
||||
Object.entries(options.functions).forEach(([key, func]) => {
|
||||
// When the builder is not used yet we'll use it for the frontend
|
||||
if (
|
||||
builders.every(
|
||||
b => !(b.config && b.config.functions && b.config.functions[key])
|
||||
)
|
||||
) {
|
||||
if (!builder.config) builder.config = {};
|
||||
if (!builder.config.functions) builder.config.functions = {};
|
||||
builder.config.functions[key] = { ...func };
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
|
||||
// By default we'll choose the `static-build` builder
|
||||
return { src, use: `@now/static-build${withTag}`, config };
|
||||
}
|
||||
|
||||
// Files that match a specific pattern will get ignored
|
||||
export function getIgnoreApiFilter(optionsOrBuilders: Options | Builder[]) {
|
||||
const possiblePatterns: string[] = getApiBuilders().map(b => b.src);
|
||||
|
||||
if (Array.isArray(optionsOrBuilders)) {
|
||||
optionsOrBuilders.forEach(({ src }) => possiblePatterns.push(src));
|
||||
} else if (optionsOrBuilders.functions) {
|
||||
Object.keys(optionsOrBuilders.functions).forEach(p =>
|
||||
possiblePatterns.push(p)
|
||||
);
|
||||
}
|
||||
|
||||
return (file: string) => {
|
||||
if (!file.startsWith('api/')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (file.includes('/.')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (file.includes('/_')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (file.endsWith('.d.ts')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (possiblePatterns.every(p => !(file === p || minimatch(file, p)))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
// We need to sort the file paths by alphabet to make
|
||||
// sure the routes stay in the same order e.g. for deduping
|
||||
export function sortFiles(fileA: string, fileB: string) {
|
||||
return fileA.localeCompare(fileB);
|
||||
}
|
||||
|
||||
async function detectApiBuilders(
|
||||
files: string[],
|
||||
options: Options
|
||||
): Promise<Builder[]> {
|
||||
const builds = files
|
||||
.sort(sortFiles)
|
||||
.filter(getIgnoreApiFilter(options))
|
||||
.map(file => {
|
||||
const apiBuilders = getApiBuilders(options);
|
||||
const apiBuilder = apiBuilders.find(b => minimatch(file, b.src));
|
||||
const fnBuilder = getApiFunctionBuilder(file, apiBuilder, options);
|
||||
return fnBuilder ? { ...fnBuilder, src: file } : null;
|
||||
});
|
||||
|
||||
return builds.filter(Boolean) as Builder[];
|
||||
}
|
||||
|
||||
// When a package has files that conflict with `/api` routes
|
||||
// e.g. Next.js pages/api we'll check it here and return an error.
|
||||
async function checkConflictingFiles(
|
||||
files: string[],
|
||||
builders: Builder[]
|
||||
): Promise<ErrorResponse | null> {
|
||||
// For Next.js
|
||||
if (builders.some(b => b.use.startsWith('@now/next'))) {
|
||||
const hasApiPages = files.some(file => file.startsWith('pages/api/'));
|
||||
const hasApiBuilders = builders.some(b => b.src.startsWith('api/'));
|
||||
|
||||
if (hasApiPages && hasApiBuilders) {
|
||||
return {
|
||||
code: 'conflicting_files',
|
||||
message:
|
||||
'It is not possible to use `api` and `pages/api` at the same time, please only use one option',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// When e.g. Next.js receives a `functions` property it has to make sure,
|
||||
// that it can handle those files, otherwise there are unused functions.
|
||||
async function checkUnusedFunctionsOnFrontendBuilder(
|
||||
files: string[],
|
||||
builder: Builder
|
||||
): Promise<ErrorResponse | null> {
|
||||
const { config: { functions = undefined } = {} } = builder;
|
||||
|
||||
if (!functions) return null;
|
||||
|
||||
if (builder.use.startsWith('@now/next')) {
|
||||
const matchingFiles = files.filter(file =>
|
||||
Object.keys(functions).some(key => file === key || minimatch(file, key))
|
||||
);
|
||||
|
||||
for (const matchedFile of matchingFiles) {
|
||||
if (
|
||||
!matchedFile.startsWith('src/pages/') &&
|
||||
!matchedFile.startsWith('pages/')
|
||||
) {
|
||||
return {
|
||||
code: 'unused_function',
|
||||
message: `The function for ${matchedFile} can't be handled by any builder`,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function validateFunctions(files: string[], { functions = {} }: Options) {
|
||||
const apiBuilders = getApiBuilders();
|
||||
|
||||
for (const [path, func] of Object.entries(functions)) {
|
||||
if (path.length > 256) {
|
||||
return {
|
||||
code: 'invalid_function_glob',
|
||||
message: 'Function globs must be less than 256 characters long.',
|
||||
};
|
||||
}
|
||||
|
||||
if (!func || typeof func !== 'object') {
|
||||
return {
|
||||
code: 'invalid_function',
|
||||
message: 'Function must be an object.',
|
||||
};
|
||||
}
|
||||
|
||||
if (Object.keys(func).length === 0) {
|
||||
return {
|
||||
code: 'invalid_function',
|
||||
message: 'Function must contain at least one property.',
|
||||
};
|
||||
}
|
||||
|
||||
if (
|
||||
func.maxDuration !== undefined &&
|
||||
(func.maxDuration < 1 ||
|
||||
func.maxDuration > 900 ||
|
||||
!Number.isInteger(func.maxDuration))
|
||||
) {
|
||||
return {
|
||||
code: 'invalid_function_duration',
|
||||
message: 'Functions must have a duration between 1 and 900.',
|
||||
};
|
||||
}
|
||||
|
||||
if (
|
||||
func.memory !== undefined &&
|
||||
(func.memory < 128 || func.memory > 3008 || func.memory % 64 !== 0)
|
||||
) {
|
||||
return {
|
||||
code: 'invalid_function_memory',
|
||||
message:
|
||||
'Functions must have a memory value between 128 and 3008 in steps of 64.',
|
||||
};
|
||||
}
|
||||
|
||||
if (path.startsWith('/')) {
|
||||
return {
|
||||
code: 'invalid_function_source',
|
||||
message: `The function path "${path}" is invalid. The path must be relative to your project root and therefore cannot start with a slash.`,
|
||||
};
|
||||
}
|
||||
|
||||
if (files.some(f => f === path || minimatch(f, path)) === false) {
|
||||
return {
|
||||
code: 'invalid_function_source',
|
||||
message: `No source file matched the function for ${path}.`,
|
||||
};
|
||||
}
|
||||
|
||||
if (func.runtime !== undefined) {
|
||||
const tag = `${func.runtime}`.split('@').pop();
|
||||
|
||||
if (!tag || !validSemver(tag)) {
|
||||
return {
|
||||
code: 'invalid_function_runtime',
|
||||
message:
|
||||
'Function Runtimes must have a valid version, for example `now-php@1.0.0`.',
|
||||
};
|
||||
}
|
||||
|
||||
if (
|
||||
apiBuilders.some(b => func.runtime && func.runtime.startsWith(b.use))
|
||||
) {
|
||||
return {
|
||||
code: 'invalid_function_runtime',
|
||||
message: `The function Runtime ${func.runtime} is not a Community Runtime and must not be specified.`,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (func.includeFiles !== undefined) {
|
||||
if (typeof func.includeFiles !== 'string') {
|
||||
return {
|
||||
code: 'invalid_function_property',
|
||||
message: `The property \`includeFiles\` must be a string.`,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (func.excludeFiles !== undefined) {
|
||||
if (typeof func.excludeFiles !== 'string') {
|
||||
return {
|
||||
code: 'invalid_function_property',
|
||||
message: `The property \`excludeFiles\` must be a string.`,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// When zero config is used we can call this function
|
||||
// to determine what builders to use
|
||||
export async function detectBuildersLegacy(
|
||||
files: string[],
|
||||
pkg?: PackageJson | undefined | null,
|
||||
options: Options = {}
|
||||
): Promise<{
|
||||
builders: Builder[] | null;
|
||||
errors: ErrorResponse[] | null;
|
||||
warnings: ErrorResponse[];
|
||||
}> {
|
||||
const errors: ErrorResponse[] = [];
|
||||
const warnings: ErrorResponse[] = [];
|
||||
|
||||
const functionError = validateFunctions(files, options);
|
||||
|
||||
if (functionError) {
|
||||
return {
|
||||
builders: null,
|
||||
errors: [functionError],
|
||||
warnings,
|
||||
};
|
||||
}
|
||||
|
||||
// Detect all builders for the `api` directory before anything else
|
||||
const builders = await detectApiBuilders(files, options);
|
||||
|
||||
if (pkg && hasBuildScript(pkg)) {
|
||||
const frontendBuilder = await detectFrontBuilder(pkg, builders, options);
|
||||
builders.push(frontendBuilder);
|
||||
|
||||
const conflictError = await checkConflictingFiles(files, builders);
|
||||
|
||||
if (conflictError) {
|
||||
warnings.push(conflictError);
|
||||
}
|
||||
|
||||
const unusedFunctionError = await checkUnusedFunctionsOnFrontendBuilder(
|
||||
files,
|
||||
frontendBuilder
|
||||
);
|
||||
|
||||
if (unusedFunctionError) {
|
||||
return {
|
||||
builders: null,
|
||||
errors: [unusedFunctionError],
|
||||
warnings,
|
||||
};
|
||||
}
|
||||
} else {
|
||||
if (pkg && builders.length === 0) {
|
||||
// We only show this error when there are no api builders
|
||||
// since the dependencies of the pkg could be used for those
|
||||
errors.push(MISSING_BUILD_SCRIPT_ERROR);
|
||||
return { errors, warnings, builders: null };
|
||||
}
|
||||
|
||||
// We allow a `public` directory
|
||||
// when there are no build steps
|
||||
if (hasPublicDirectory(files)) {
|
||||
builders.push({
|
||||
use: '@now/static',
|
||||
src: 'public/**/*',
|
||||
config,
|
||||
});
|
||||
} else if (
|
||||
builders.length > 0 &&
|
||||
files.some(f => !f.startsWith('api/') && f !== 'package.json')
|
||||
) {
|
||||
// Everything besides the api directory
|
||||
// and package.json can be served as static files
|
||||
builders.push({
|
||||
use: '@now/static',
|
||||
src: '!{api/**,package.json}',
|
||||
config,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
builders: builders.length ? builders : null,
|
||||
errors: errors.length ? errors : null,
|
||||
warnings,
|
||||
};
|
||||
}
|
||||
@@ -1,6 +1,12 @@
|
||||
import minimatch from 'minimatch';
|
||||
import { valid as validSemver } from 'semver';
|
||||
import { PackageJson, Builder, Config, BuilderFunctions } from './types';
|
||||
import {
|
||||
Builder,
|
||||
Config,
|
||||
BuilderFunctions,
|
||||
DetectorResult,
|
||||
DetectorOutput,
|
||||
} from './types';
|
||||
|
||||
interface ErrorResponse {
|
||||
code: string;
|
||||
@@ -12,26 +18,6 @@ interface Options {
|
||||
functions?: BuilderFunctions;
|
||||
}
|
||||
|
||||
const src = 'package.json';
|
||||
const config: Config = { zeroConfig: true };
|
||||
|
||||
const MISSING_BUILD_SCRIPT_ERROR: ErrorResponse = {
|
||||
code: 'missing_build_script',
|
||||
message:
|
||||
'Your `package.json` file is missing a `build` property inside the `scripts` property.' +
|
||||
'\nMore details: https://zeit.co/docs/v2/platform/frequently-asked-questions#missing-build-script',
|
||||
};
|
||||
|
||||
// Static builders are special cased in `@now/static-build`
|
||||
function getBuilders({ tag }: Options = {}): Map<string, Builder> {
|
||||
const withTag = tag ? `@${tag}` : '';
|
||||
const config = { zeroConfig: true };
|
||||
|
||||
return new Map<string, Builder>([
|
||||
['next', { src, use: `@now/next${withTag}`, config }],
|
||||
]);
|
||||
}
|
||||
|
||||
// Must be a function to ensure that the returned
|
||||
// object won't be a reference
|
||||
function getApiBuilders({ tag }: Pick<Options, 'tag'> = {}): Builder[] {
|
||||
@@ -47,13 +33,8 @@ function getApiBuilders({ tag }: Pick<Options, 'tag'> = {}): Builder[] {
|
||||
];
|
||||
}
|
||||
|
||||
function hasPublicDirectory(files: string[]) {
|
||||
return files.some(name => name.startsWith('public/'));
|
||||
}
|
||||
|
||||
function hasBuildScript(pkg: PackageJson | undefined) {
|
||||
const { scripts = {} } = pkg || {};
|
||||
return Boolean(scripts && scripts['build']);
|
||||
function hasDirectory(fileName: string, files: string[]) {
|
||||
return files.some(name => name.startsWith(`${fileName}/`));
|
||||
}
|
||||
|
||||
function getApiFunctionBuilder(
|
||||
@@ -91,39 +72,71 @@ function getApiFunctionBuilder(
|
||||
return use ? { use, src, config } : prevBuilder;
|
||||
}
|
||||
|
||||
async function detectFrontBuilder(
|
||||
pkg: PackageJson,
|
||||
function detectFrontBuilder(
|
||||
detectorResult: Partial<DetectorOutput>,
|
||||
builders: Builder[],
|
||||
files: string[],
|
||||
options: Options
|
||||
): Promise<Builder> {
|
||||
): Builder {
|
||||
const { tag } = options;
|
||||
const withTag = tag ? `@${tag}` : '';
|
||||
for (const [dependency, builder] of getBuilders(options)) {
|
||||
const deps = Object.assign({}, pkg.dependencies, pkg.devDependencies);
|
||||
|
||||
// Return the builder when a dependency matches
|
||||
if (deps[dependency]) {
|
||||
if (options.functions) {
|
||||
Object.entries(options.functions).forEach(([key, func]) => {
|
||||
// When the builder is not used yet we'll use it for the frontend
|
||||
if (
|
||||
builders.every(
|
||||
b => !(b.config && b.config.functions && b.config.functions[key])
|
||||
)
|
||||
) {
|
||||
if (!builder.config) builder.config = {};
|
||||
if (!builder.config.functions) builder.config.functions = {};
|
||||
builder.config.functions[key] = { ...func };
|
||||
}
|
||||
});
|
||||
const {
|
||||
framework,
|
||||
buildCommand,
|
||||
outputDirectory,
|
||||
devCommand,
|
||||
} = detectorResult;
|
||||
|
||||
const frameworkSlug = framework ? framework.slug : null;
|
||||
|
||||
const config: Config = {
|
||||
zeroConfig: true,
|
||||
};
|
||||
|
||||
if (framework) {
|
||||
config.framework = framework;
|
||||
}
|
||||
|
||||
if (devCommand) {
|
||||
config.devCommand = devCommand;
|
||||
}
|
||||
|
||||
if (buildCommand) {
|
||||
config.buildCommand = buildCommand;
|
||||
}
|
||||
|
||||
if (outputDirectory) {
|
||||
config.outputDirectory = outputDirectory;
|
||||
}
|
||||
|
||||
// All unused functions will be used for the frontend
|
||||
if (options.functions) {
|
||||
Object.entries(options.functions).forEach(([key, func]) => {
|
||||
if (
|
||||
builders.every(
|
||||
b => !(b.config && b.config.functions && b.config.functions[key])
|
||||
)
|
||||
) {
|
||||
config.functions = config.functions || {};
|
||||
config.functions[key] = { ...func };
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return builder;
|
||||
if (frameworkSlug === 'next') {
|
||||
return { src: 'package.json', use: `@now/next${withTag}`, config };
|
||||
}
|
||||
|
||||
if (frameworkSlug === 'hugo') {
|
||||
const configFiles = new Set(['config.yaml', 'config.toml', 'config.json']);
|
||||
const source = files.find(file => configFiles.has(file));
|
||||
if (source) {
|
||||
return { src: source, use: `@now/static-build${withTag}`, config };
|
||||
}
|
||||
}
|
||||
|
||||
// By default we'll choose the `static-build` builder
|
||||
return { src, use: `@now/static-build${withTag}`, config };
|
||||
return { src: 'package.json', use: `@now/static-build${withTag}`, config };
|
||||
}
|
||||
|
||||
// Files that match a specific pattern will get ignored
|
||||
@@ -139,10 +152,6 @@ export function getIgnoreApiFilter(optionsOrBuilders: Options | Builder[]) {
|
||||
}
|
||||
|
||||
return (file: string) => {
|
||||
if (!file.startsWith('api/')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (file.includes('/.')) {
|
||||
return false;
|
||||
}
|
||||
@@ -169,10 +178,7 @@ export function sortFiles(fileA: string, fileB: string) {
|
||||
return fileA.localeCompare(fileB);
|
||||
}
|
||||
|
||||
async function detectApiBuilders(
|
||||
files: string[],
|
||||
options: Options
|
||||
): Promise<Builder[]> {
|
||||
function detectApiBuilders(files: string[], options: Options): Builder[] {
|
||||
const builds = files
|
||||
.sort(sortFiles)
|
||||
.filter(getIgnoreApiFilter(options))
|
||||
@@ -188,10 +194,10 @@ async function detectApiBuilders(
|
||||
|
||||
// When a package has files that conflict with `/api` routes
|
||||
// e.g. Next.js pages/api we'll check it here and return an error.
|
||||
async function checkConflictingFiles(
|
||||
function checkConflictingFiles(
|
||||
files: string[],
|
||||
builders: Builder[]
|
||||
): Promise<ErrorResponse | null> {
|
||||
): ErrorResponse | null {
|
||||
// For Next.js
|
||||
if (builders.some(b => b.use.startsWith('@now/next'))) {
|
||||
const hasApiPages = files.some(file => file.startsWith('pages/api/'));
|
||||
@@ -211,10 +217,10 @@ async function checkConflictingFiles(
|
||||
|
||||
// When e.g. Next.js receives a `functions` property it has to make sure,
|
||||
// that it can handle those files, otherwise there are unused functions.
|
||||
async function checkUnusedFunctionsOnFrontendBuilder(
|
||||
function checkUnusedFunctionsOnFrontendBuilder(
|
||||
files: string[],
|
||||
builder: Builder
|
||||
): Promise<ErrorResponse | null> {
|
||||
): ErrorResponse | null {
|
||||
const { config: { functions = undefined } = {} } = builder;
|
||||
|
||||
if (!functions) return null;
|
||||
@@ -231,7 +237,9 @@ async function checkUnusedFunctionsOnFrontendBuilder(
|
||||
) {
|
||||
return {
|
||||
code: 'unused_function',
|
||||
message: `The function for ${matchedFile} can't be handled by any builder`,
|
||||
message:
|
||||
`The function for "${matchedFile}" can't be handled by any runtime. ` +
|
||||
`Please provide one with the "runtime" option.`,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -241,8 +249,6 @@ async function checkUnusedFunctionsOnFrontendBuilder(
|
||||
}
|
||||
|
||||
function validateFunctions(files: string[], { functions = {} }: Options) {
|
||||
const apiBuilders = getApiBuilders();
|
||||
|
||||
for (const [path, func] of Object.entries(functions)) {
|
||||
if (path.length > 256) {
|
||||
return {
|
||||
@@ -312,15 +318,6 @@ function validateFunctions(files: string[], { functions = {} }: Options) {
|
||||
'Function Runtimes must have a valid version, for example `now-php@1.0.0`.',
|
||||
};
|
||||
}
|
||||
|
||||
if (
|
||||
apiBuilders.some(b => func.runtime && func.runtime.startsWith(b.use))
|
||||
) {
|
||||
return {
|
||||
code: 'invalid_function_runtime',
|
||||
message: `The function Runtime ${func.runtime} is not a Community Runtime and must not be specified.`,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (func.includeFiles !== undefined) {
|
||||
@@ -349,7 +346,7 @@ function validateFunctions(files: string[], { functions = {} }: Options) {
|
||||
// to determine what builders to use
|
||||
export async function detectBuilders(
|
||||
files: string[],
|
||||
pkg?: PackageJson | undefined | null,
|
||||
detectorResult: Partial<DetectorResult> | null = null,
|
||||
options: Options = {}
|
||||
): Promise<{
|
||||
builders: Builder[] | null;
|
||||
@@ -370,19 +367,24 @@ export async function detectBuilders(
|
||||
}
|
||||
|
||||
// Detect all builders for the `api` directory before anything else
|
||||
const builders = await detectApiBuilders(files, options);
|
||||
const builders = detectApiBuilders(files, options);
|
||||
|
||||
if (pkg && hasBuildScript(pkg)) {
|
||||
const frontendBuilder = await detectFrontBuilder(pkg, builders, options);
|
||||
if (detectorResult && detectorResult.buildCommand) {
|
||||
const frontendBuilder = detectFrontBuilder(
|
||||
detectorResult,
|
||||
builders,
|
||||
files,
|
||||
options
|
||||
);
|
||||
builders.push(frontendBuilder);
|
||||
|
||||
const conflictError = await checkConflictingFiles(files, builders);
|
||||
const conflictError = checkConflictingFiles(files, builders);
|
||||
|
||||
if (conflictError) {
|
||||
warnings.push(conflictError);
|
||||
}
|
||||
|
||||
const unusedFunctionError = await checkUnusedFunctionsOnFrontendBuilder(
|
||||
const unusedFunctionError = checkUnusedFunctionsOnFrontendBuilder(
|
||||
files,
|
||||
frontendBuilder
|
||||
);
|
||||
@@ -394,34 +396,35 @@ export async function detectBuilders(
|
||||
warnings,
|
||||
};
|
||||
}
|
||||
} else {
|
||||
if (pkg && builders.length === 0) {
|
||||
// We only show this error when there are no api builders
|
||||
// since the dependencies of the pkg could be used for those
|
||||
errors.push(MISSING_BUILD_SCRIPT_ERROR);
|
||||
return { errors, warnings, builders: null };
|
||||
}
|
||||
|
||||
// We allow a `public` directory
|
||||
// when there are no build steps
|
||||
if (hasPublicDirectory(files)) {
|
||||
builders.push({
|
||||
use: '@now/static',
|
||||
src: 'public/**/*',
|
||||
config,
|
||||
});
|
||||
} else if (
|
||||
builders.length > 0 &&
|
||||
files.some(f => !f.startsWith('api/') && f !== 'package.json')
|
||||
) {
|
||||
// Everything besides the api directory
|
||||
// and package.json can be served as static files
|
||||
builders.push({
|
||||
use: '@now/static',
|
||||
src: '!{api/**,package.json}',
|
||||
config,
|
||||
});
|
||||
}
|
||||
} else if (
|
||||
detectorResult &&
|
||||
detectorResult.outputDirectory &&
|
||||
hasDirectory(detectorResult.outputDirectory, files)
|
||||
) {
|
||||
builders.push({
|
||||
use: '@now/static',
|
||||
src: [...detectorResult.outputDirectory.split('/'), '**', '*']
|
||||
.filter(Boolean)
|
||||
.join('/'),
|
||||
config: { zeroConfig: true },
|
||||
});
|
||||
} else if (hasDirectory('public', files)) {
|
||||
builders.push({
|
||||
use: '@now/static',
|
||||
src: 'public/**/*',
|
||||
config: { zeroConfig: true },
|
||||
});
|
||||
} else if (
|
||||
builders.length > 0 &&
|
||||
files.some(f => !f.startsWith('api/') && f !== 'package.json')
|
||||
) {
|
||||
// Everything besides the api directory
|
||||
// and package.json can be served as static files
|
||||
builders.push({
|
||||
use: '@now/static',
|
||||
src: '!{api/**,package.json}',
|
||||
config: { zeroConfig: true },
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
298
packages/now-build-utils/src/detect-routes-legacy.ts
Normal file
298
packages/now-build-utils/src/detect-routes-legacy.ts
Normal file
@@ -0,0 +1,298 @@
|
||||
import { parse as parsePath } from 'path';
|
||||
import { Route, Builder } from './types';
|
||||
import { getIgnoreApiFilter, sortFiles } from './detect-builders-legacy';
|
||||
|
||||
function escapeName(name: string) {
|
||||
const special = '[]^$.|?*+()'.split('');
|
||||
|
||||
for (const char of special) {
|
||||
name = name.replace(new RegExp(`\\${char}`, 'g'), `\\${char}`);
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
function joinPath(...segments: string[]) {
|
||||
const joinedPath = segments.join('/');
|
||||
return joinedPath.replace(/\/{2,}/g, '/');
|
||||
}
|
||||
|
||||
function concatArrayOfText(texts: string[]): string {
|
||||
if (texts.length <= 2) {
|
||||
return texts.join(' and ');
|
||||
}
|
||||
|
||||
const last = texts.pop();
|
||||
return `${texts.join(', ')}, and ${last}`;
|
||||
}
|
||||
|
||||
// Takes a filename or foldername, strips the extension
|
||||
// gets the part between the "[]" brackets.
|
||||
// It will return `null` if there are no brackets
|
||||
// and therefore no segment.
|
||||
function getSegmentName(segment: string): string | null {
|
||||
const { name } = parsePath(segment);
|
||||
|
||||
if (name.startsWith('[') && name.endsWith(']')) {
|
||||
return name.slice(1, -1);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function createRouteFromPath(filePath: string): Route {
|
||||
const parts = filePath.split('/');
|
||||
|
||||
let counter = 1;
|
||||
const query: string[] = [];
|
||||
|
||||
const srcParts = parts.map((segment, index): string => {
|
||||
const name = getSegmentName(segment);
|
||||
const isLast = index === parts.length - 1;
|
||||
|
||||
if (name !== null) {
|
||||
// We can't use `URLSearchParams` because `$` would get escaped
|
||||
query.push(`${name}=$${counter++}`);
|
||||
return `([^\\/]+)`;
|
||||
} else if (isLast) {
|
||||
const { name: fileName, ext } = parsePath(segment);
|
||||
const isIndex = fileName === 'index';
|
||||
const prefix = isIndex ? '\\/' : '';
|
||||
|
||||
const names = [
|
||||
isIndex ? prefix : `${fileName}\\/`,
|
||||
prefix + escapeName(fileName),
|
||||
prefix + escapeName(fileName) + escapeName(ext),
|
||||
].filter(Boolean);
|
||||
|
||||
// Either filename with extension, filename without extension
|
||||
// or nothing when the filename is `index`
|
||||
return `(${names.join('|')})${isIndex ? '?' : ''}`;
|
||||
}
|
||||
|
||||
return segment;
|
||||
});
|
||||
|
||||
const { name: fileName } = parsePath(filePath);
|
||||
const isIndex = fileName === 'index';
|
||||
|
||||
const src = isIndex
|
||||
? `^/${srcParts.slice(0, -1).join('/')}${srcParts.slice(-1)[0]}$`
|
||||
: `^/${srcParts.join('/')}$`;
|
||||
|
||||
const dest = `/${filePath}${query.length ? '?' : ''}${query.join('&')}`;
|
||||
|
||||
return { src, dest };
|
||||
}
|
||||
|
||||
// Check if the path partially matches and has the same
|
||||
// name for the path segment at the same position
|
||||
function partiallyMatches(pathA: string, pathB: string): boolean {
|
||||
const partsA = pathA.split('/');
|
||||
const partsB = pathB.split('/');
|
||||
|
||||
const long = partsA.length > partsB.length ? partsA : partsB;
|
||||
const short = long === partsA ? partsB : partsA;
|
||||
|
||||
let index = 0;
|
||||
|
||||
for (const segmentShort of short) {
|
||||
const segmentLong = long[index];
|
||||
|
||||
const nameLong = getSegmentName(segmentLong);
|
||||
const nameShort = getSegmentName(segmentShort);
|
||||
|
||||
// If there are no segments or the paths differ we
|
||||
// return as they are not matching
|
||||
if (segmentShort !== segmentLong && (!nameLong || !nameShort)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (nameLong !== nameShort) {
|
||||
return true;
|
||||
}
|
||||
|
||||
index += 1;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Counts how often a path occurs when all placeholders
|
||||
// got resolved, so we can check if they have conflicts
|
||||
function pathOccurrences(filePath: string, files: string[]): string[] {
|
||||
const getAbsolutePath = (unresolvedPath: string): string => {
|
||||
const { dir, name } = parsePath(unresolvedPath);
|
||||
const parts = joinPath(dir, name).split('/');
|
||||
return parts.map(part => part.replace(/\[.*\]/, '1')).join('/');
|
||||
};
|
||||
|
||||
const currentAbsolutePath = getAbsolutePath(filePath);
|
||||
|
||||
return files.reduce((prev: string[], file: string): string[] => {
|
||||
const absolutePath = getAbsolutePath(file);
|
||||
|
||||
if (absolutePath === currentAbsolutePath) {
|
||||
prev.push(file);
|
||||
} else if (partiallyMatches(filePath, file)) {
|
||||
prev.push(file);
|
||||
}
|
||||
|
||||
return prev;
|
||||
}, []);
|
||||
}
|
||||
|
||||
// Checks if a placeholder with the same name is used
|
||||
// multiple times inside the same path
|
||||
function getConflictingSegment(filePath: string): string | null {
|
||||
const segments = new Set<string>();
|
||||
|
||||
for (const segment of filePath.split('/')) {
|
||||
const name = getSegmentName(segment);
|
||||
|
||||
if (name !== null && segments.has(name)) {
|
||||
return name;
|
||||
}
|
||||
|
||||
if (name) {
|
||||
segments.add(name);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function sortFilesBySegmentCount(fileA: string, fileB: string): number {
|
||||
const lengthA = fileA.split('/').length;
|
||||
const lengthB = fileB.split('/').length;
|
||||
|
||||
if (lengthA > lengthB) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (lengthA < lengthB) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Paths that have the same segment length but
|
||||
// less placeholders are preferred
|
||||
const countSegments = (prev: number, segment: string) =>
|
||||
getSegmentName(segment) ? prev + 1 : 0;
|
||||
const segmentLengthA = fileA.split('/').reduce(countSegments, 0);
|
||||
const segmentLengthB = fileB.split('/').reduce(countSegments, 0);
|
||||
|
||||
if (segmentLengthA > segmentLengthB) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (segmentLengthA < segmentLengthB) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
interface RoutesResult {
|
||||
defaultRoutes: Route[] | null;
|
||||
error: { [key: string]: string } | null;
|
||||
}
|
||||
|
||||
async function detectApiRoutes(
|
||||
files: string[],
|
||||
builders: Builder[]
|
||||
): Promise<RoutesResult> {
|
||||
if (!files || files.length === 0) {
|
||||
return { defaultRoutes: null, error: null };
|
||||
}
|
||||
|
||||
// The deepest routes need to be
|
||||
// the first ones to get handled
|
||||
const sortedFiles = files
|
||||
.filter(getIgnoreApiFilter(builders))
|
||||
.sort(sortFiles)
|
||||
.sort(sortFilesBySegmentCount);
|
||||
|
||||
const defaultRoutes: Route[] = [];
|
||||
|
||||
for (const file of sortedFiles) {
|
||||
// We only consider every file in the api directory
|
||||
// as we will strip extensions as well as resolving "[segments]"
|
||||
if (!file.startsWith('api/')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const conflictingSegment = getConflictingSegment(file);
|
||||
|
||||
if (conflictingSegment) {
|
||||
return {
|
||||
defaultRoutes: null,
|
||||
error: {
|
||||
code: 'conflicting_path_segment',
|
||||
message:
|
||||
`The segment "${conflictingSegment}" occurs more than ` +
|
||||
`one time in your path "${file}". Please make sure that ` +
|
||||
`every segment in a path is unique`,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const occurrences = pathOccurrences(file, sortedFiles).filter(
|
||||
name => name !== file
|
||||
);
|
||||
|
||||
if (occurrences.length > 0) {
|
||||
const messagePaths = concatArrayOfText(
|
||||
occurrences.map(name => `"${name}"`)
|
||||
);
|
||||
|
||||
return {
|
||||
defaultRoutes: null,
|
||||
error: {
|
||||
code: 'conflicting_file_path',
|
||||
message:
|
||||
`Two or more files have conflicting paths or names. ` +
|
||||
`Please make sure path segments and filenames, without their extension, are unique. ` +
|
||||
`The path "${file}" has conflicts with ${messagePaths}`,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
defaultRoutes.push(createRouteFromPath(file));
|
||||
}
|
||||
|
||||
// 404 Route to disable directory listing
|
||||
if (defaultRoutes.length) {
|
||||
defaultRoutes.push({
|
||||
status: 404,
|
||||
src: '/api(\\/.*)?$',
|
||||
});
|
||||
}
|
||||
|
||||
return { defaultRoutes, error: null };
|
||||
}
|
||||
|
||||
function hasPublicBuilder(builders: Builder[]): boolean {
|
||||
return builders.some(
|
||||
builder =>
|
||||
builder.use === '@now/static' &&
|
||||
builder.src === 'public/**/*' &&
|
||||
builder.config &&
|
||||
builder.config.zeroConfig === true
|
||||
);
|
||||
}
|
||||
|
||||
export async function detectRoutesLegacy(
|
||||
files: string[],
|
||||
builders: Builder[]
|
||||
): Promise<RoutesResult> {
|
||||
const routesResult = await detectApiRoutes(files, builders);
|
||||
|
||||
if (routesResult.defaultRoutes && hasPublicBuilder(builders)) {
|
||||
routesResult.defaultRoutes.push({
|
||||
src: '/(.*)',
|
||||
dest: '/public/$1',
|
||||
});
|
||||
}
|
||||
|
||||
return routesResult;
|
||||
}
|
||||
@@ -217,7 +217,10 @@ async function detectApiRoutes(
|
||||
for (const file of sortedFiles) {
|
||||
// We only consider every file in the api directory
|
||||
// as we will strip extensions as well as resolving "[segments]"
|
||||
if (!file.startsWith('api/')) {
|
||||
if (
|
||||
!file.startsWith('api/') &&
|
||||
!builders.some(b => b.src === file && b.config!.functions)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -271,14 +274,16 @@ async function detectApiRoutes(
|
||||
return { defaultRoutes, error: null };
|
||||
}
|
||||
|
||||
function hasPublicBuilder(builders: Builder[]): boolean {
|
||||
return builders.some(
|
||||
function getPublicBuilder(builders: Builder[]): Builder | null {
|
||||
const builder = builders.find(
|
||||
builder =>
|
||||
builder.use === '@now/static' &&
|
||||
builder.src === 'public/**/*' &&
|
||||
/^.*\/\*\*\/\*$/.test(builder.src) &&
|
||||
builder.config &&
|
||||
builder.config.zeroConfig === true
|
||||
);
|
||||
|
||||
return builder || null;
|
||||
}
|
||||
|
||||
export async function detectRoutes(
|
||||
@@ -286,11 +291,14 @@ export async function detectRoutes(
|
||||
builders: Builder[]
|
||||
): Promise<RoutesResult> {
|
||||
const routesResult = await detectApiRoutes(files, builders);
|
||||
const publicBuilder = getPublicBuilder(builders);
|
||||
|
||||
if (routesResult.defaultRoutes && publicBuilder) {
|
||||
const directory = publicBuilder.src.replace('/**/*', '');
|
||||
|
||||
if (routesResult.defaultRoutes && hasPublicBuilder(builders)) {
|
||||
routesResult.defaultRoutes.push({
|
||||
src: '/(.*)',
|
||||
dest: '/public/$1',
|
||||
dest: `/${directory}/$1`,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
import { DetectorParameters, DetectorResult } from '../types';
|
||||
|
||||
export default async function detectAngular({
|
||||
fs: { hasDependency },
|
||||
fs: { getPackageJsonBuildCommand, getDependencyVersion },
|
||||
}: DetectorParameters): Promise<DetectorResult> {
|
||||
const hasAngular = await hasDependency('@angular/cli');
|
||||
if (!hasAngular) return false;
|
||||
const version = await getDependencyVersion('@angular/cli');
|
||||
if (!version) return false;
|
||||
return {
|
||||
buildCommand: 'ng build',
|
||||
buildDirectory: 'dist',
|
||||
buildCommand: (await getPackageJsonBuildCommand()) || 'ng build',
|
||||
outputDirectory: 'dist',
|
||||
devCommand: 'ng serve --port $PORT',
|
||||
framework: {
|
||||
slug: '@angular/cli',
|
||||
version,
|
||||
},
|
||||
minNodeRange: '10.x',
|
||||
routes: [
|
||||
{
|
||||
|
||||
@@ -1,17 +1,22 @@
|
||||
import { DetectorParameters, DetectorResult } from '../types';
|
||||
|
||||
export default async function detectBrunch({
|
||||
fs: { hasDependency, exists },
|
||||
fs: { exists, getPackageJsonBuildCommand, getDependencyVersion },
|
||||
}: DetectorParameters): Promise<DetectorResult> {
|
||||
const hasBrunch = await hasDependency('brunch');
|
||||
if (!hasBrunch) return false;
|
||||
const version = await getDependencyVersion('brunch');
|
||||
if (!version) return false;
|
||||
|
||||
const hasConfig = await exists('brunch-config.js');
|
||||
if (!hasConfig) return false;
|
||||
|
||||
return {
|
||||
buildCommand: 'brunch build --production',
|
||||
buildDirectory: 'public',
|
||||
buildCommand:
|
||||
(await getPackageJsonBuildCommand()) || 'brunch build --production',
|
||||
outputDirectory: 'public',
|
||||
devCommand: 'brunch watch --server --port $PORT',
|
||||
framework: {
|
||||
slug: 'brunch',
|
||||
version,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,16 +1,20 @@
|
||||
import { DetectorParameters, DetectorResult } from '../types';
|
||||
|
||||
export default async function detectCreateReactAppEjected({
|
||||
fs: { hasDependency },
|
||||
fs: { getDependencyVersion },
|
||||
}: DetectorParameters): Promise<DetectorResult> {
|
||||
const hasReactDevUtils = await hasDependency('react-dev-utils');
|
||||
if (!hasReactDevUtils) {
|
||||
const version = await getDependencyVersion('react-dev-utils');
|
||||
if (!version) {
|
||||
return false;
|
||||
}
|
||||
return {
|
||||
buildCommand: 'node scripts/build.js',
|
||||
buildDirectory: 'build',
|
||||
outputDirectory: 'build',
|
||||
devCommand: 'node scripts/start.js',
|
||||
framework: {
|
||||
slug: 'react-dev-utils',
|
||||
version,
|
||||
},
|
||||
devVariables: { BROWSER: 'none' },
|
||||
routes: [
|
||||
{
|
||||
|
||||
@@ -1,17 +1,21 @@
|
||||
import { DetectorParameters, DetectorResult } from '../types';
|
||||
|
||||
export default async function detectCreateReactApp({
|
||||
fs: { hasDependency },
|
||||
fs: { getPackageJsonBuildCommand, getDependencyVersion },
|
||||
}: DetectorParameters): Promise<DetectorResult> {
|
||||
const hasReactScripts = await hasDependency('react-scripts');
|
||||
if (!hasReactScripts) {
|
||||
const version = await getDependencyVersion('react-scripts');
|
||||
if (!version) {
|
||||
return false;
|
||||
}
|
||||
return {
|
||||
buildCommand: 'react-scripts build',
|
||||
buildDirectory: 'build',
|
||||
buildCommand: (await getPackageJsonBuildCommand()) || 'react-scripts build',
|
||||
outputDirectory: 'build',
|
||||
devCommand: 'react-scripts start',
|
||||
devVariables: { BROWSER: 'none' },
|
||||
framework: {
|
||||
slug: 'react-scripts',
|
||||
version,
|
||||
},
|
||||
routes: [
|
||||
{
|
||||
src: '/static/(.*)',
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
import { DetectorParameters, DetectorResult } from '../types';
|
||||
|
||||
export default async function detectDocusaurus({
|
||||
fs: { hasDependency },
|
||||
fs: { getPackageJsonBuildCommand, getDependencyVersion },
|
||||
}: DetectorParameters): Promise<DetectorResult> {
|
||||
const hasDocusaurus = await hasDependency('docusaurus');
|
||||
if (!hasDocusaurus) return false;
|
||||
const version = await getDependencyVersion('docusaurus');
|
||||
if (!version) return false;
|
||||
return {
|
||||
buildCommand: 'docusaurus-build',
|
||||
buildDirectory: 'build',
|
||||
buildCommand: (await getPackageJsonBuildCommand()) || 'docusaurus-build',
|
||||
outputDirectory: 'build',
|
||||
devCommand: 'docusaurus-start --port $PORT',
|
||||
framework: {
|
||||
slug: 'docusaurus',
|
||||
version,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
import { DetectorParameters, DetectorResult } from '../types';
|
||||
|
||||
export default async function detectEleventy({
|
||||
fs: { hasDependency },
|
||||
fs: { getDependencyVersion },
|
||||
}: DetectorParameters): Promise<DetectorResult> {
|
||||
const hasEleventy = await hasDependency('@11ty/eleventy');
|
||||
if (!hasEleventy) return false;
|
||||
const version = await getDependencyVersion('@11ty/eleventy');
|
||||
if (!version) return false;
|
||||
return {
|
||||
buildCommand: 'npx @11ty/eleventy',
|
||||
buildDirectory: '_site',
|
||||
outputDirectory: '_site',
|
||||
devCommand: 'npx @11ty/eleventy --serve --watch --port $PORT',
|
||||
framework: {
|
||||
slug: '@11ty/eleventy',
|
||||
version,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
import { DetectorParameters, DetectorResult } from '../types';
|
||||
|
||||
export default async function detectEmber({
|
||||
fs: { hasDependency },
|
||||
fs: { getPackageJsonBuildCommand, getDependencyVersion },
|
||||
}: DetectorParameters): Promise<DetectorResult> {
|
||||
const hasEmber = await hasDependency('ember-cli');
|
||||
if (!hasEmber) return false;
|
||||
const version = await getDependencyVersion('ember-cli');
|
||||
if (!version) return false;
|
||||
return {
|
||||
buildCommand: 'ember build',
|
||||
buildDirectory: 'dist',
|
||||
buildCommand: (await getPackageJsonBuildCommand()) || 'ember build',
|
||||
outputDirectory: 'dist',
|
||||
devCommand: 'ember serve --port $PORT',
|
||||
framework: {
|
||||
slug: 'ember-cli',
|
||||
version,
|
||||
},
|
||||
routes: [
|
||||
{
|
||||
handle: 'filesystem',
|
||||
|
||||
@@ -102,6 +102,31 @@ export default abstract class DetectorFilesystem {
|
||||
const { dependencies = {}, devDependencies = {} } = pkg || {};
|
||||
return name in dependencies || name in devDependencies;
|
||||
};
|
||||
|
||||
public isNpm = async (): Promise<boolean> => {
|
||||
return this.exists('package-lock.json');
|
||||
};
|
||||
|
||||
public getPackageJsonCommand = async (
|
||||
name: string
|
||||
): Promise<string | null> => {
|
||||
const pkg = await this.readPackageJson();
|
||||
const { scripts = {} } = pkg || {};
|
||||
return scripts[name] || null;
|
||||
};
|
||||
|
||||
public getPackageJsonBuildCommand = async (): Promise<string | null> => {
|
||||
const buildCommand = (await this.isNpm())
|
||||
? 'npm run build'
|
||||
: 'yarn run build';
|
||||
return (await this.getPackageJsonCommand('build')) ? buildCommand : null;
|
||||
};
|
||||
|
||||
public getDependencyVersion = async (name: string): Promise<string> => {
|
||||
const pkg = await this.readPackageJson();
|
||||
const { dependencies = {}, devDependencies = {} } = pkg || {};
|
||||
return dependencies[name] || devDependencies[name];
|
||||
};
|
||||
}
|
||||
|
||||
async function nullEnoent<T>(p: Promise<T>): Promise<T | null> {
|
||||
|
||||
@@ -1,16 +1,20 @@
|
||||
import { DetectorParameters, DetectorResult } from '../types';
|
||||
|
||||
export default async function detectGatsby({
|
||||
fs: { hasDependency },
|
||||
fs: { getPackageJsonBuildCommand, getDependencyVersion },
|
||||
}: DetectorParameters): Promise<DetectorResult> {
|
||||
const hasGatsby = await hasDependency('gatsby');
|
||||
if (!hasGatsby) {
|
||||
const version = await getDependencyVersion('gatsby');
|
||||
if (!version) {
|
||||
return false;
|
||||
}
|
||||
return {
|
||||
buildCommand: 'gatsby build',
|
||||
buildDirectory: 'public',
|
||||
buildCommand: (await getPackageJsonBuildCommand()) || 'gatsby build',
|
||||
outputDirectory: 'public',
|
||||
devCommand: 'gatsby develop -p $PORT',
|
||||
framework: {
|
||||
slug: 'gatsby',
|
||||
version,
|
||||
},
|
||||
cachePattern: '.cache/**',
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import { DetectorParameters, DetectorResult } from '../types';
|
||||
|
||||
export default async function detectGenericNodeProject({
|
||||
fs: { isNpm, getPackageJsonCommand },
|
||||
}: DetectorParameters): Promise<DetectorResult> {
|
||||
const useNpm = await isNpm();
|
||||
const devCommand = await getPackageJsonCommand('dev');
|
||||
const buildCommand = await getPackageJsonCommand('build');
|
||||
|
||||
if (!buildCommand) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return {
|
||||
buildCommand: `${useNpm ? 'npm' : 'yarn'} run build`,
|
||||
devCommand: useNpm && devCommand ? `yarn run ${devCommand}` : undefined,
|
||||
outputDirectory: 'public',
|
||||
};
|
||||
}
|
||||
@@ -1,15 +1,19 @@
|
||||
import { DetectorParameters, DetectorResult } from '../types';
|
||||
|
||||
export default async function detectGridsome({
|
||||
fs: { hasDependency },
|
||||
fs: { getPackageJsonBuildCommand, getDependencyVersion },
|
||||
}: DetectorParameters): Promise<DetectorResult> {
|
||||
const hasGridsome = await hasDependency('gridsome');
|
||||
if (!hasGridsome) {
|
||||
const version = await getDependencyVersion('gridsome');
|
||||
if (!version) {
|
||||
return false;
|
||||
}
|
||||
return {
|
||||
buildCommand: 'gridsome build',
|
||||
buildDirectory: 'dist',
|
||||
buildCommand: (await getPackageJsonBuildCommand()) || 'gridsome build',
|
||||
outputDirectory: 'dist',
|
||||
devCommand: 'gridsome develop -p $PORT',
|
||||
framework: {
|
||||
slug: 'gridsom',
|
||||
version,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
import { DetectorParameters, DetectorResult } from '../types';
|
||||
|
||||
export default async function detectHexo({
|
||||
fs: { hasDependency },
|
||||
fs: { getPackageJsonBuildCommand, getDependencyVersion },
|
||||
}: DetectorParameters): Promise<DetectorResult> {
|
||||
const hasHexo = await hasDependency('hexo');
|
||||
if (!hasHexo) return false;
|
||||
const version = await getDependencyVersion('hexo');
|
||||
if (!version) return false;
|
||||
return {
|
||||
buildCommand: 'hexo generate',
|
||||
buildDirectory: 'public',
|
||||
buildCommand: (await getPackageJsonBuildCommand()) || 'hexo generate',
|
||||
outputDirectory: 'public',
|
||||
devCommand: 'hexo server --port $PORT',
|
||||
framework: {
|
||||
slug: 'hexo',
|
||||
version,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -20,7 +20,11 @@ export default async function detectHugo({
|
||||
}
|
||||
return {
|
||||
buildCommand: 'hugo',
|
||||
buildDirectory: config.publishDir || 'public',
|
||||
outputDirectory: config.publishDir || 'public',
|
||||
devCommand: 'hugo server -D -w -p $PORT',
|
||||
framework: {
|
||||
slug: 'hugo',
|
||||
version: 'latest',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import docusaurus from './docusaurus';
|
||||
import eleventy from './eleventy';
|
||||
import ember from './ember';
|
||||
import gatsby from './gatsby';
|
||||
import genericNodeProject from './generic-node-project';
|
||||
import gridsome from './gridsome';
|
||||
import hexo from './hexo';
|
||||
import hugo from './hugo';
|
||||
@@ -81,5 +82,8 @@ export async function detectDefaults(
|
||||
d = params.detectors || detectors;
|
||||
result = await firstTruthy(d.map(detector => detector(params)));
|
||||
}
|
||||
if (!result) {
|
||||
result = await genericNodeProject(params);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -16,7 +16,11 @@ export default async function detectJekyll({
|
||||
}
|
||||
return {
|
||||
buildCommand: 'jekyll build',
|
||||
buildDirectory: config.destination || '_site',
|
||||
outputDirectory: config.destination || '_site',
|
||||
devCommand: 'bundle exec jekyll serve --watch --port $PORT',
|
||||
framework: {
|
||||
slug: 'jekyll',
|
||||
version: 'latest',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -8,7 +8,11 @@ export default async function detectMiddleman({
|
||||
|
||||
return {
|
||||
buildCommand: 'bundle exec middleman build',
|
||||
buildDirectory: 'build',
|
||||
outputDirectory: 'build',
|
||||
devCommand: 'bundle exec middleman server -p $PORT',
|
||||
framework: {
|
||||
slug: 'middleman',
|
||||
version: 'latest',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
import { DetectorParameters, DetectorResult } from '../types';
|
||||
|
||||
export default async function detectNext({
|
||||
fs: { hasDependency },
|
||||
fs: { getPackageJsonBuildCommand, getDependencyVersion },
|
||||
}: DetectorParameters): Promise<DetectorResult> {
|
||||
const hasNext = await hasDependency('next');
|
||||
if (!hasNext) return false;
|
||||
const version = await getDependencyVersion('next');
|
||||
if (!version) return false;
|
||||
return {
|
||||
buildCommand: 'next build',
|
||||
buildDirectory: 'build',
|
||||
buildCommand: (await getPackageJsonBuildCommand()) || 'next build',
|
||||
outputDirectory: '.next/static',
|
||||
devCommand: 'next -p $PORT',
|
||||
framework: {
|
||||
slug: 'next',
|
||||
version,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
import { DetectorParameters, DetectorResult } from '../types';
|
||||
|
||||
export default async function detectPolymer({
|
||||
fs: { hasDependency },
|
||||
fs: { getPackageJsonBuildCommand, getDependencyVersion },
|
||||
}: DetectorParameters): Promise<DetectorResult> {
|
||||
const hasPolymer = await hasDependency('polymer-cli');
|
||||
if (!hasPolymer) return false;
|
||||
const version = await getDependencyVersion('polymer-cli');
|
||||
if (!version) return false;
|
||||
return {
|
||||
buildCommand: 'polymer build',
|
||||
buildDirectory: 'build',
|
||||
buildCommand: (await getPackageJsonBuildCommand()) || 'polymer build',
|
||||
outputDirectory: 'build',
|
||||
devCommand: 'polymer serve --port $PORT',
|
||||
framework: {
|
||||
slug: 'polymer-cli',
|
||||
version,
|
||||
},
|
||||
routes: [
|
||||
{
|
||||
handle: 'filesystem',
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
import { DetectorParameters, DetectorResult } from '../types';
|
||||
|
||||
export default async function detectPreact({
|
||||
fs: { hasDependency },
|
||||
fs: { getPackageJsonBuildCommand, getDependencyVersion },
|
||||
}: DetectorParameters): Promise<DetectorResult> {
|
||||
const hasPreact = await hasDependency('preact-cli');
|
||||
if (!hasPreact) return false;
|
||||
const version = await getDependencyVersion('preact-cli');
|
||||
if (!version) return false;
|
||||
return {
|
||||
buildCommand: 'preact build',
|
||||
buildDirectory: 'build',
|
||||
buildCommand: (await getPackageJsonBuildCommand()) || 'preact build',
|
||||
outputDirectory: 'build',
|
||||
devCommand: 'preact watch --port $PORT',
|
||||
framework: {
|
||||
slug: 'preact-cli',
|
||||
version,
|
||||
},
|
||||
routes: [
|
||||
{
|
||||
handle: 'filesystem',
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { DetectorParameters, DetectorResult } from '../types';
|
||||
|
||||
export default async function detectSaber({
|
||||
fs: { hasDependency },
|
||||
fs: { getPackageJsonBuildCommand, getDependencyVersion },
|
||||
}: DetectorParameters): Promise<DetectorResult> {
|
||||
const hasSaber = await hasDependency('saber');
|
||||
if (!hasSaber) return false;
|
||||
const version = await getDependencyVersion('saber');
|
||||
if (!version) return false;
|
||||
return {
|
||||
buildCommand: 'saber build',
|
||||
buildDirectory: 'public',
|
||||
buildCommand: (await getPackageJsonBuildCommand()) || 'saber build',
|
||||
outputDirectory: 'public',
|
||||
devCommand: 'saber --port $PORT',
|
||||
routes: [
|
||||
{
|
||||
@@ -23,5 +23,9 @@ export default async function detectSaber({
|
||||
dest: '404.html',
|
||||
},
|
||||
],
|
||||
framework: {
|
||||
slug: 'saber',
|
||||
version,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
import { DetectorParameters, DetectorResult } from '../types';
|
||||
|
||||
export default async function detectSapper({
|
||||
fs: { hasDependency },
|
||||
fs: { getPackageJsonBuildCommand, getDependencyVersion },
|
||||
}: DetectorParameters): Promise<DetectorResult> {
|
||||
const hasSapper = await hasDependency('sapper');
|
||||
if (!hasSapper) return false;
|
||||
const version = await getDependencyVersion('sapper');
|
||||
if (!version) return false;
|
||||
return {
|
||||
buildCommand: 'sapper export',
|
||||
buildDirectory: '__sapper__/export',
|
||||
buildCommand: (await getPackageJsonBuildCommand()) || 'sapper export',
|
||||
outputDirectory: '__sapper__/export',
|
||||
devCommand: 'sapper dev --port $PORT',
|
||||
framework: {
|
||||
slug: 'sapper',
|
||||
version,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
import { DetectorParameters, DetectorResult } from '../types';
|
||||
|
||||
export default async function detectStencil({
|
||||
fs: { hasDependency },
|
||||
fs: { getPackageJsonBuildCommand, getDependencyVersion },
|
||||
}: DetectorParameters): Promise<DetectorResult> {
|
||||
const hasStencil = await hasDependency('@stencil/core');
|
||||
if (!hasStencil) return false;
|
||||
const version = await getDependencyVersion('@stencil/core');
|
||||
if (!version) return false;
|
||||
return {
|
||||
buildCommand: 'stencil build',
|
||||
buildDirectory: 'www',
|
||||
buildCommand: (await getPackageJsonBuildCommand()) || 'stencil build',
|
||||
outputDirectory: 'www',
|
||||
devCommand: 'stencil build --dev --watch --serve --port $PORT',
|
||||
framework: {
|
||||
slug: '@stencil/core',
|
||||
version,
|
||||
},
|
||||
routes: [
|
||||
{
|
||||
handle: 'filesystem',
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
import { DetectorParameters, DetectorResult } from '../types';
|
||||
|
||||
export default async function detectSvelte({
|
||||
fs: { hasDependency },
|
||||
fs: { getPackageJsonBuildCommand, getDependencyVersion },
|
||||
}: DetectorParameters): Promise<DetectorResult> {
|
||||
const hasSvelte = await hasDependency('sirv-cli');
|
||||
if (!hasSvelte) return false;
|
||||
const version = await getDependencyVersion('sirv-cli');
|
||||
if (!version) return false;
|
||||
return {
|
||||
buildCommand: 'rollup -c',
|
||||
buildDirectory: 'public',
|
||||
buildCommand: (await getPackageJsonBuildCommand()) || 'rollup -c',
|
||||
outputDirectory: 'public',
|
||||
devCommand: 'sirv public --single --dev --port $PORT',
|
||||
framework: {
|
||||
slug: 'sirv-cli',
|
||||
version,
|
||||
},
|
||||
routes: [
|
||||
{
|
||||
handle: 'filesystem',
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
import { DetectorParameters, DetectorResult } from '../types';
|
||||
|
||||
export default async function detectUmiJS({
|
||||
fs: { hasDependency },
|
||||
fs: { getPackageJsonBuildCommand, getDependencyVersion },
|
||||
}: DetectorParameters): Promise<DetectorResult> {
|
||||
const hasUmi = await hasDependency('umi');
|
||||
if (!hasUmi) return false;
|
||||
const version = await getDependencyVersion('umi');
|
||||
if (!version) return false;
|
||||
return {
|
||||
buildCommand: 'umi build',
|
||||
buildDirectory: 'dist',
|
||||
buildCommand: (await getPackageJsonBuildCommand()) || 'umi build',
|
||||
outputDirectory: 'dist',
|
||||
devCommand: 'umi dev --port $PORT',
|
||||
framework: {
|
||||
slug: 'umi',
|
||||
version,
|
||||
},
|
||||
routes: [
|
||||
{
|
||||
handle: 'filesystem',
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
import { DetectorParameters, DetectorResult } from '../types';
|
||||
|
||||
export default async function detectVue({
|
||||
fs: { hasDependency },
|
||||
fs: { getPackageJsonBuildCommand, getDependencyVersion },
|
||||
}: DetectorParameters): Promise<DetectorResult> {
|
||||
const hasVue = await hasDependency('@vue/cli-service');
|
||||
if (!hasVue) return false;
|
||||
const version = await getDependencyVersion('@vue/cli-service');
|
||||
if (!version) return false;
|
||||
return {
|
||||
buildCommand: 'vue-cli-service build',
|
||||
buildDirectory: 'dist',
|
||||
buildCommand:
|
||||
(await getPackageJsonBuildCommand()) || 'vue-cli-service build',
|
||||
outputDirectory: 'dist',
|
||||
devCommand: 'vue-cli-service serve --port $PORT',
|
||||
framework: {
|
||||
slug: '@vue/cli-service',
|
||||
version,
|
||||
},
|
||||
routes: [
|
||||
{
|
||||
src: '^/[^/]*\\.(js|txt|ico|json)',
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import path from 'path';
|
||||
import debug from '../debug';
|
||||
import FileFsRef from '../file-fs-ref';
|
||||
import { File, Files, Meta } from '../types';
|
||||
import { remove, mkdirp, readlink, symlink } from 'fs-extra';
|
||||
@@ -39,8 +40,12 @@ export default async function download(
|
||||
basePath: string,
|
||||
meta?: Meta
|
||||
): Promise<DownloadedFiles> {
|
||||
const { isDev = false, skipDownload = false, filesChanged = null, filesRemoved = null } =
|
||||
meta || {};
|
||||
const {
|
||||
isDev = false,
|
||||
skipDownload = false,
|
||||
filesChanged = null,
|
||||
filesRemoved = null,
|
||||
} = meta || {};
|
||||
|
||||
if (isDev || skipDownload) {
|
||||
// In `now dev`, the `download()` function is a no-op because
|
||||
@@ -48,11 +53,14 @@ export default async function download(
|
||||
// source files are already available.
|
||||
return files as DownloadedFiles;
|
||||
}
|
||||
debug('Downloading deployment source files...');
|
||||
|
||||
const start = Date.now();
|
||||
const files2: DownloadedFiles = {};
|
||||
const filenames = Object.keys(files);
|
||||
|
||||
await Promise.all(
|
||||
Object.keys(files).map(async name => {
|
||||
filenames.map(async name => {
|
||||
// If the file does not exist anymore, remove it.
|
||||
if (Array.isArray(filesRemoved) && filesRemoved.includes(name)) {
|
||||
await removeFile(basePath, name);
|
||||
@@ -71,5 +79,8 @@ export default async function download(
|
||||
})
|
||||
);
|
||||
|
||||
const duration = Date.now() - start;
|
||||
debug(`Downloaded ${filenames.length} source files: ${duration}ms`);
|
||||
|
||||
return files2;
|
||||
}
|
||||
|
||||
@@ -1,13 +1,21 @@
|
||||
import { intersects } from 'semver';
|
||||
import boxen from 'boxen';
|
||||
import { NodeVersion } from '../types';
|
||||
import debug from '../debug';
|
||||
|
||||
const supportedOptions: NodeVersion[] = [
|
||||
const allOptions: NodeVersion[] = [
|
||||
{ major: 12, range: '12.x', runtime: 'nodejs12.x' },
|
||||
{ major: 10, range: '10.x', runtime: 'nodejs10.x' },
|
||||
{ major: 8, range: '8.10.x', runtime: 'nodejs8.10' },
|
||||
{
|
||||
major: 8,
|
||||
range: '8.10.x',
|
||||
runtime: 'nodejs8.10',
|
||||
discontinueDate: new Date('2020-01-06'),
|
||||
},
|
||||
];
|
||||
|
||||
const supportedOptions = allOptions.filter(o => !isDiscontinued(o));
|
||||
|
||||
// This version should match Fargate's default in the PATH
|
||||
// Today that is Node 8
|
||||
export const defaultSelection = supportedOptions.find(
|
||||
@@ -28,13 +36,14 @@ export async function getSupportedNodeVersion(
|
||||
);
|
||||
}
|
||||
} else {
|
||||
const found = supportedOptions.some(o => {
|
||||
const found = allOptions.some(o => {
|
||||
// the array is already in order so return the first
|
||||
// match which will be the newest version of node
|
||||
selection = o;
|
||||
return intersects(o.range, engineRange);
|
||||
});
|
||||
if (found) {
|
||||
const discontinued = isDiscontinued(selection);
|
||||
if (found && !discontinued) {
|
||||
if (!silent) {
|
||||
debug(
|
||||
'Found `engines` in `package.json`, selecting range: ' +
|
||||
@@ -42,15 +51,50 @@ export async function getSupportedNodeVersion(
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if (!silent) {
|
||||
throw new Error(
|
||||
'Found `engines` in `package.json` with an unsupported node range: ' +
|
||||
engineRange +
|
||||
'\nPlease use one of the following supported ranges: ' +
|
||||
JSON.stringify(supportedOptions.map(o => o.range))
|
||||
);
|
||||
}
|
||||
throw new Error(
|
||||
'Found `engines` in `package.json` with an unsupported Node.js version range: ' +
|
||||
engineRange +
|
||||
'\nPlease use one of the following supported ranges: ' +
|
||||
JSON.stringify(supportedOptions.map(o => o.range)) +
|
||||
(discontinued
|
||||
? '\nThis change is the result of a decision made by an upstream infrastructure provider (AWS).' +
|
||||
'\nRead more: https://docs.aws.amazon.com/lambda/latest/dg/runtime-support-policy.html'
|
||||
: '')
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const { range, discontinueDate } = selection;
|
||||
if (discontinueDate && !isDiscontinued(selection)) {
|
||||
const d = discontinueDate.toISOString().split('T')[0];
|
||||
const validRanges = supportedOptions
|
||||
.filter(o => !o.discontinueDate)
|
||||
.map(o => o.range);
|
||||
const prevTerm = process.env.TERM;
|
||||
if (!prevTerm) {
|
||||
// workaround for https://github.com/sindresorhus/term-size/issues/13
|
||||
process.env.TERM = 'xterm';
|
||||
}
|
||||
console.warn(
|
||||
boxen(
|
||||
'NOTICE' +
|
||||
'\n' +
|
||||
`\nNode.js version ${range} has reached end-of-life.` +
|
||||
`\nAs a result, deployments created on or after ${d} will fail to build.` +
|
||||
'\nPlease use one of the following supported `engines` in `package.json`: ' +
|
||||
JSON.stringify(validRanges) +
|
||||
'\nThis change is the result of a decision made by an upstream infrastructure provider (AWS).' +
|
||||
'\nRead more: https://docs.aws.amazon.com/lambda/latest/dg/runtime-support-policy.html',
|
||||
{ padding: 1 }
|
||||
)
|
||||
);
|
||||
process.env.TERM = prevTerm;
|
||||
}
|
||||
|
||||
return selection;
|
||||
}
|
||||
|
||||
function isDiscontinued({ discontinueDate }: NodeVersion): boolean {
|
||||
const today = new Date();
|
||||
return discontinueDate !== undefined && discontinueDate <= today;
|
||||
}
|
||||
|
||||
@@ -39,6 +39,66 @@ export function spawnAsync(
|
||||
});
|
||||
}
|
||||
|
||||
export function execAsync(
|
||||
command: string,
|
||||
args: string[],
|
||||
opts: SpawnOptions = {}
|
||||
) {
|
||||
return new Promise<{ stdout: string; stderr: string; code: number }>(
|
||||
(resolve, reject) => {
|
||||
opts.stdio = 'pipe';
|
||||
|
||||
const stdoutList: Buffer[] = [];
|
||||
const stderrList: Buffer[] = [];
|
||||
|
||||
const child = spawn(command, args, opts);
|
||||
|
||||
child.stderr!.on('data', data => {
|
||||
stderrList.push(data);
|
||||
});
|
||||
|
||||
child.stdout!.on('data', data => {
|
||||
stdoutList.push(data);
|
||||
});
|
||||
|
||||
child.on('error', reject);
|
||||
child.on('close', (code, signal) => {
|
||||
if (code !== 0) {
|
||||
return reject(
|
||||
new Error(
|
||||
`Program "${command}" exited with non-zero exit code ${code} ${signal}.`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return resolve({
|
||||
code,
|
||||
stdout: Buffer.concat(stdoutList).toString(),
|
||||
stderr: Buffer.concat(stderrList).toString(),
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
export function spawnCommand(command: string, options: SpawnOptions = {}) {
|
||||
if (process.platform === 'win32') {
|
||||
return spawn('cmd.exe', ['/C', command], options);
|
||||
}
|
||||
|
||||
return spawn('sh', ['-c', command], options);
|
||||
}
|
||||
|
||||
export async function execCommand(command: string, options: SpawnOptions = {}) {
|
||||
if (process.platform === 'win32') {
|
||||
await spawnAsync('cmd.exe', ['/C', command], options);
|
||||
} else {
|
||||
await spawnAsync('sh', ['-c', command], options);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
async function chmodPlusX(fsPath: string) {
|
||||
const s = await fs.stat(fsPath);
|
||||
const newMode = s.mode | 64 | 8 | 1; // eslint-disable-line no-bitwise
|
||||
|
||||
@@ -3,12 +3,15 @@ import FileFsRef from './file-fs-ref';
|
||||
import FileRef from './file-ref';
|
||||
import { Lambda, createLambda, getLambdaOptionsFromFunction } from './lambda';
|
||||
import { Prerender } from './prerender';
|
||||
import download, { DownloadedFiles } from './fs/download';
|
||||
import download, { DownloadedFiles, isSymbolicLink } from './fs/download';
|
||||
import getWriteableDirectory from './fs/get-writable-directory';
|
||||
import glob from './fs/glob';
|
||||
import rename from './fs/rename';
|
||||
import {
|
||||
execAsync,
|
||||
spawnAsync,
|
||||
execCommand,
|
||||
spawnCommand,
|
||||
installDependencies,
|
||||
runPackageJsonScript,
|
||||
runNpmInstall,
|
||||
@@ -38,9 +41,12 @@ export {
|
||||
getWriteableDirectory,
|
||||
glob,
|
||||
rename,
|
||||
execAsync,
|
||||
spawnAsync,
|
||||
installDependencies,
|
||||
runPackageJsonScript,
|
||||
execCommand,
|
||||
spawnCommand,
|
||||
runNpmInstall,
|
||||
runBundleInstall,
|
||||
runPipInstall,
|
||||
@@ -52,8 +58,13 @@ export {
|
||||
detectBuilders,
|
||||
detectRoutes,
|
||||
debug,
|
||||
isSymbolicLink,
|
||||
getLambdaOptionsFromFunction,
|
||||
};
|
||||
|
||||
export { detectBuildersLegacy } from './detect-builders-legacy';
|
||||
export { detectRoutesLegacy } from './detect-routes-legacy';
|
||||
|
||||
export { detectDefaults } from './detectors';
|
||||
export * from './schemas';
|
||||
export * from './types';
|
||||
|
||||
61
packages/now-build-utils/src/schemas.ts
Normal file
61
packages/now-build-utils/src/schemas.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
export const functionsSchema = {
|
||||
type: 'object',
|
||||
minProperties: 1,
|
||||
maxProperties: 50,
|
||||
additionalProperties: false,
|
||||
patternProperties: {
|
||||
'^.{1,256}$': {
|
||||
type: 'object',
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
runtime: {
|
||||
type: 'string',
|
||||
maxLength: 256,
|
||||
},
|
||||
memory: {
|
||||
// Number between 128 and 3008 in steps of 64
|
||||
enum: Object.keys(Array.from({ length: 50 }))
|
||||
.slice(2, 48)
|
||||
.map(x => Number(x) * 64),
|
||||
},
|
||||
maxDuration: {
|
||||
type: 'number',
|
||||
minimum: 1,
|
||||
maximum: 900,
|
||||
},
|
||||
includeFiles: {
|
||||
type: 'string',
|
||||
maxLength: 256,
|
||||
},
|
||||
excludeFiles: {
|
||||
type: 'string',
|
||||
maxLength: 256,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const buildsSchema = {
|
||||
type: 'array',
|
||||
minItems: 0,
|
||||
maxItems: 128,
|
||||
items: {
|
||||
type: 'object',
|
||||
additionalProperties: false,
|
||||
required: ['use'],
|
||||
properties: {
|
||||
src: {
|
||||
type: 'string',
|
||||
minLength: 1,
|
||||
maxLength: 4096,
|
||||
},
|
||||
use: {
|
||||
type: 'string',
|
||||
minLength: 3,
|
||||
maxLength: 256,
|
||||
},
|
||||
config: { type: 'object' },
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -52,6 +52,13 @@ export interface Config {
|
||||
zeroConfig?: boolean;
|
||||
import?: { [key: string]: string };
|
||||
functions?: BuilderFunctions;
|
||||
outputDirectory?: string;
|
||||
buildCommand?: string;
|
||||
devCommand?: string;
|
||||
framework?: {
|
||||
slug: string;
|
||||
version: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface Meta {
|
||||
@@ -304,6 +311,7 @@ export interface NodeVersion {
|
||||
major: number;
|
||||
range: string;
|
||||
runtime: string;
|
||||
discontinueDate?: Date;
|
||||
}
|
||||
|
||||
export interface Builder {
|
||||
@@ -353,7 +361,7 @@ export interface DetectorParameters {
|
||||
|
||||
export interface DetectorOutput {
|
||||
buildCommand: string;
|
||||
buildDirectory: string;
|
||||
outputDirectory: string;
|
||||
buildVariables?: Env;
|
||||
devCommand?: string;
|
||||
devVariables?: Env;
|
||||
@@ -365,6 +373,10 @@ export interface DetectorOutput {
|
||||
redirects?: NowRedirect[];
|
||||
headers?: NowHeader[];
|
||||
trailingSlash?: boolean;
|
||||
framework?: {
|
||||
slug: string;
|
||||
version: string;
|
||||
};
|
||||
}
|
||||
|
||||
export type DetectorResult = DetectorOutput | false;
|
||||
|
||||
@@ -4,13 +4,34 @@ const {
|
||||
packAndDeploy,
|
||||
testDeployment,
|
||||
} = require('../../../test/lib/deployment/test-deployment');
|
||||
const { glob, detectBuilders, detectRoutes } = require('../');
|
||||
const {
|
||||
glob,
|
||||
detectBuilders,
|
||||
detectRoutes,
|
||||
DetectorFilesystem,
|
||||
detectDefaults,
|
||||
} = require('../');
|
||||
|
||||
jest.setTimeout(4 * 60 * 1000);
|
||||
|
||||
const builderUrl = '@canary';
|
||||
let buildUtilsUrl;
|
||||
|
||||
class LocalFilesystem extends DetectorFilesystem {
|
||||
constructor(dir) {
|
||||
super();
|
||||
this.dir = dir;
|
||||
}
|
||||
|
||||
_exists(name) {
|
||||
return fs.pathExists(path.join(this.dir, name));
|
||||
}
|
||||
|
||||
_readFile(name) {
|
||||
return fs.readFile(path.join(this.dir, name));
|
||||
}
|
||||
}
|
||||
|
||||
beforeAll(async () => {
|
||||
const buildUtilsPath = path.resolve(__dirname, '..');
|
||||
buildUtilsUrl = await packAndDeploy(buildUtilsPath);
|
||||
@@ -67,7 +88,9 @@ for (const builder of buildersToTestWith) {
|
||||
|
||||
it('Test `detectBuilders` and `detectRoutes`', async () => {
|
||||
const fixture = path.join(__dirname, 'fixtures', '01-zero-config-api');
|
||||
const pkg = await fs.readJSON(path.join(fixture, 'package.json'));
|
||||
const detectorResult = await detectDefaults({
|
||||
fs: new LocalFilesystem(fixture),
|
||||
});
|
||||
const fileList = await glob('**', fixture);
|
||||
const files = Object.keys(fileList);
|
||||
|
||||
@@ -110,7 +133,7 @@ it('Test `detectBuilders` and `detectRoutes`', async () => {
|
||||
},
|
||||
];
|
||||
|
||||
const { builders } = await detectBuilders(files, pkg);
|
||||
const { builders } = await detectBuilders(files, detectorResult);
|
||||
const { defaultRoutes } = await detectRoutes(files, builders);
|
||||
|
||||
const nowConfig = { builds: builders, routes: defaultRoutes, probes };
|
||||
@@ -128,7 +151,9 @@ it('Test `detectBuilders` and `detectRoutes`', async () => {
|
||||
|
||||
it('Test `detectBuilders` and `detectRoutes` with `index` files', async () => {
|
||||
const fixture = path.join(__dirname, 'fixtures', '02-zero-config-api');
|
||||
const pkg = await fs.readJSON(path.join(fixture, 'package.json'));
|
||||
const detectorResult = await detectDefaults({
|
||||
fs: new LocalFilesystem(fixture),
|
||||
});
|
||||
const fileList = await glob('**', fixture);
|
||||
const files = Object.keys(fileList);
|
||||
|
||||
@@ -192,7 +217,7 @@ it('Test `detectBuilders` and `detectRoutes` with `index` files', async () => {
|
||||
},
|
||||
];
|
||||
|
||||
const { builders } = await detectBuilders(files, pkg);
|
||||
const { builders } = await detectBuilders(files, detectorResult);
|
||||
const { defaultRoutes } = await detectRoutes(files, builders);
|
||||
|
||||
const nowConfig = { builds: builders, routes: defaultRoutes, probes };
|
||||
|
||||
@@ -67,8 +67,8 @@ test('detectDefaults() - angular', async () => {
|
||||
const fs = new LocalFilesystem(dir);
|
||||
const result = await detectDefaults({ fs });
|
||||
if (!result) throw new Error('Expected result');
|
||||
assert.equal(result.buildDirectory, 'dist');
|
||||
assert.deepEqual(result.buildCommand, 'ng build');
|
||||
assert.equal(result.outputDirectory, 'dist');
|
||||
assert.deepEqual(result.buildCommand, 'yarn run build');
|
||||
});
|
||||
|
||||
test('detectDefaults() - brunch', async () => {
|
||||
@@ -76,8 +76,8 @@ test('detectDefaults() - brunch', async () => {
|
||||
const fs = new LocalFilesystem(dir);
|
||||
const result = await detectDefaults({ fs });
|
||||
if (!result) throw new Error('Expected result');
|
||||
assert.equal(result.buildDirectory, 'public');
|
||||
assert.deepEqual(result.buildCommand, 'brunch build --production');
|
||||
assert.equal(result.outputDirectory, 'public');
|
||||
assert.deepEqual(result.buildCommand, 'yarn run build');
|
||||
});
|
||||
|
||||
test('detectDefaults() - hugo', async () => {
|
||||
@@ -85,7 +85,7 @@ test('detectDefaults() - hugo', async () => {
|
||||
const fs = new LocalFilesystem(dir);
|
||||
const result = await detectDefaults({ fs });
|
||||
if (!result) throw new Error('Expected result');
|
||||
assert.equal(result.buildDirectory, 'public');
|
||||
assert.equal(result.outputDirectory, 'public');
|
||||
assert.deepEqual(result.buildCommand, 'hugo');
|
||||
});
|
||||
|
||||
@@ -94,7 +94,7 @@ test('detectDefaults() - jekyll', async () => {
|
||||
const fs = new LocalFilesystem(dir);
|
||||
const result = await detectDefaults({ fs });
|
||||
if (!result) throw new Error('Expected result');
|
||||
assert.equal(result.buildDirectory, '_site');
|
||||
assert.equal(result.outputDirectory, '_site');
|
||||
assert.deepEqual(result.buildCommand, 'jekyll build');
|
||||
});
|
||||
|
||||
@@ -103,6 +103,6 @@ test('detectDefaults() - middleman', async () => {
|
||||
const fs = new LocalFilesystem(dir);
|
||||
const result = await detectDefaults({ fs });
|
||||
if (!result) throw new Error('Expected result');
|
||||
assert.equal(result.buildDirectory, 'build');
|
||||
assert.equal(result.outputDirectory, 'build');
|
||||
assert.deepEqual(result.buildCommand, 'bundle exec middleman build');
|
||||
});
|
||||
|
||||
411
packages/now-build-utils/test/unit.test.js
vendored
411
packages/now-build-utils/test/unit.test.js
vendored
@@ -1,9 +1,14 @@
|
||||
const path = require('path');
|
||||
const fs = require('fs-extra');
|
||||
const execa = require('execa');
|
||||
const assert = require('assert');
|
||||
const { createZip } = require('../dist/lambda');
|
||||
const { glob, download, detectBuilders, detectRoutes } = require('../');
|
||||
const {
|
||||
glob,
|
||||
download,
|
||||
detectBuilders,
|
||||
detectRoutes,
|
||||
spawnAsync,
|
||||
} = require('../');
|
||||
const {
|
||||
getSupportedNodeVersion,
|
||||
defaultSelection,
|
||||
@@ -39,7 +44,7 @@ it('should create zip files with symlinks properly', async () => {
|
||||
await fs.mkdirp(outDir);
|
||||
|
||||
await fs.writeFile(outFile, await createZip(files));
|
||||
await execa('unzip', [outFile], { cwd: outDir });
|
||||
await spawnAsync('unzip', [outFile], { cwd: outDir });
|
||||
|
||||
const [linkStat, aStat] = await Promise.all([
|
||||
fs.lstat(path.join(outDir, 'link.txt')),
|
||||
@@ -110,42 +115,34 @@ it('should support require by path for legacy builders', () => {
|
||||
});
|
||||
|
||||
describe('Test `detectBuilders`', () => {
|
||||
it('package.json + no build', async () => {
|
||||
const pkg = { dependencies: { next: '9.0.0' } };
|
||||
it('package.json + no build command', async () => {
|
||||
const detected = { framework: { slug: 'next', version: '9.0.0' } };
|
||||
const files = ['package.json', 'pages/index.js', 'public/index.html'];
|
||||
const { builders, errors } = await detectBuilders(files, pkg);
|
||||
expect(builders).toBe(null);
|
||||
expect(errors.length).toBe(1);
|
||||
const { builders } = await detectBuilders(files, detected);
|
||||
expect(builders.length).toBe(1);
|
||||
expect(builders[0].src).toBe('public/**/*');
|
||||
expect(builders[0].use).toBe('@now/static');
|
||||
});
|
||||
|
||||
it('package.json + no build + next', async () => {
|
||||
const pkg = {
|
||||
scripts: { build: 'next build' },
|
||||
dependencies: { next: '9.0.0' },
|
||||
it('package.json + build command + next', async () => {
|
||||
const detected = {
|
||||
buildCommand: 'yarn build',
|
||||
framework: {
|
||||
slug: 'next',
|
||||
version: '9.0.0',
|
||||
},
|
||||
};
|
||||
const files = ['package.json', 'pages/index.js'];
|
||||
const { builders, errors } = await detectBuilders(files, pkg);
|
||||
const { builders, errors } = await detectBuilders(files, detected);
|
||||
expect(builders[0].use).toBe('@now/next');
|
||||
expect(errors).toBe(null);
|
||||
});
|
||||
|
||||
it('package.json + no build + next', async () => {
|
||||
const pkg = {
|
||||
scripts: { build: 'next build' },
|
||||
devDependencies: { next: '9.0.0' },
|
||||
};
|
||||
const files = ['package.json', 'pages/index.js'];
|
||||
const { builders, errors } = await detectBuilders(files, pkg);
|
||||
expect(builders[0].use).toBe('@now/next');
|
||||
expect(errors).toBe(null);
|
||||
});
|
||||
|
||||
it('package.json + no build', async () => {
|
||||
const pkg = {};
|
||||
it('no detectors + no build command', async () => {
|
||||
const files = ['package.json'];
|
||||
const { builders, errors } = await detectBuilders(files, pkg);
|
||||
const { builders, errors } = await detectBuilders(files, {});
|
||||
expect(builders).toBe(null);
|
||||
expect(errors.length).toBe(1);
|
||||
expect(errors).toBe(null);
|
||||
});
|
||||
|
||||
it('static file', async () => {
|
||||
@@ -155,7 +152,7 @@ describe('Test `detectBuilders`', () => {
|
||||
expect(errors).toBe(null);
|
||||
});
|
||||
|
||||
it('no package.json + public', async () => {
|
||||
it('no package.json + public + api', async () => {
|
||||
const files = ['api/users.js', 'public/index.html'];
|
||||
const { builders, errors } = await detectBuilders(files);
|
||||
expect(builders[1].use).toBe('@now/static');
|
||||
@@ -173,7 +170,7 @@ describe('Test `detectBuilders`', () => {
|
||||
expect(errors).toBe(null);
|
||||
});
|
||||
|
||||
it('package.json + no build + root + api', async () => {
|
||||
it('no package.json + no build command + root + api', async () => {
|
||||
const files = ['index.html', 'api/[endpoint].js', 'static/image.png'];
|
||||
const { builders, errors } = await detectBuilders(files);
|
||||
expect(builders[0].use).toBe('@now/node');
|
||||
@@ -198,13 +195,17 @@ describe('Test `detectBuilders`', () => {
|
||||
});
|
||||
|
||||
it('api + next + public', async () => {
|
||||
const pkg = {
|
||||
scripts: { build: 'next build' },
|
||||
devDependencies: { next: '9.0.0' },
|
||||
const detected = {
|
||||
buildCommand: 'yarn build',
|
||||
framework: {
|
||||
slug: 'next',
|
||||
version: '9.0.0',
|
||||
},
|
||||
};
|
||||
|
||||
const files = ['package.json', 'api/endpoint.js', 'public/index.html'];
|
||||
|
||||
const { builders } = await detectBuilders(files, pkg);
|
||||
const { builders } = await detectBuilders(files, detected);
|
||||
expect(builders[0].use).toBe('@now/node');
|
||||
expect(builders[0].src).toBe('api/endpoint.js');
|
||||
expect(builders[1].use).toBe('@now/next');
|
||||
@@ -213,13 +214,17 @@ describe('Test `detectBuilders`', () => {
|
||||
});
|
||||
|
||||
it('api + next + raw static', async () => {
|
||||
const pkg = {
|
||||
scripts: { build: 'next build' },
|
||||
devDependencies: { next: '9.0.0' },
|
||||
const detected = {
|
||||
buildCommand: 'yarn build',
|
||||
framework: {
|
||||
slug: 'next',
|
||||
version: '9.0.0',
|
||||
},
|
||||
};
|
||||
|
||||
const files = ['package.json', 'api/endpoint.js', 'index.html'];
|
||||
|
||||
const { builders } = await detectBuilders(files, pkg);
|
||||
const { builders } = await detectBuilders(files, detected);
|
||||
expect(builders[0].use).toBe('@now/node');
|
||||
expect(builders[0].src).toBe('api/endpoint.js');
|
||||
expect(builders[1].use).toBe('@now/next');
|
||||
@@ -263,61 +268,85 @@ describe('Test `detectBuilders`', () => {
|
||||
});
|
||||
|
||||
it('next + public', async () => {
|
||||
const pkg = {
|
||||
scripts: { build: 'next build' },
|
||||
devDependencies: { next: '9.0.0' },
|
||||
const detected = {
|
||||
buildCommand: 'yarn build',
|
||||
framework: {
|
||||
slug: 'next',
|
||||
version: '9.0.0',
|
||||
},
|
||||
};
|
||||
|
||||
const files = ['package.json', 'public/index.html', 'README.md'];
|
||||
|
||||
const { builders } = await detectBuilders(files, pkg);
|
||||
const { builders } = await detectBuilders(files, detected);
|
||||
expect(builders[0].use).toBe('@now/next');
|
||||
expect(builders[0].src).toBe('package.json');
|
||||
expect(builders.length).toBe(1);
|
||||
});
|
||||
|
||||
it('nuxt', async () => {
|
||||
const pkg = {
|
||||
scripts: { build: 'nuxt build' },
|
||||
dependencies: { nuxt: '2.8.1' },
|
||||
const detected = {
|
||||
buildCommand: 'yarn build',
|
||||
framework: {
|
||||
slug: '@vue/cli-service',
|
||||
version: '2.8.1',
|
||||
},
|
||||
};
|
||||
|
||||
const files = ['package.json', 'pages/index.js'];
|
||||
|
||||
const { builders } = await detectBuilders(files, pkg);
|
||||
const { builders } = await detectBuilders(files, detected);
|
||||
expect(builders[0].use).toBe('@now/static-build');
|
||||
expect(builders[0].src).toBe('package.json');
|
||||
expect(builders.length).toBe(1);
|
||||
});
|
||||
|
||||
it('nuxt + tag canary', async () => {
|
||||
const pkg = {
|
||||
scripts: { build: 'nuxt build' },
|
||||
dependencies: { nuxt: '2.8.1' },
|
||||
const detected = {
|
||||
buildCommand: 'yarn build',
|
||||
framework: {
|
||||
slug: '@vue/cli-service',
|
||||
version: '2.8.1',
|
||||
},
|
||||
};
|
||||
|
||||
const files = ['package.json', 'pages/index.js'];
|
||||
|
||||
const { builders } = await detectBuilders(files, pkg, { tag: 'canary' });
|
||||
const { builders } = await detectBuilders(files, detected, {
|
||||
tag: 'canary',
|
||||
});
|
||||
expect(builders[0].use).toBe('@now/static-build@canary');
|
||||
expect(builders[0].src).toBe('package.json');
|
||||
expect(builders.length).toBe(1);
|
||||
});
|
||||
|
||||
it('package.json with no build + api', async () => {
|
||||
const pkg = { dependencies: { next: '9.0.0' } };
|
||||
it('no build command + api', async () => {
|
||||
const detected = {
|
||||
framework: {
|
||||
slug: 'next',
|
||||
version: '9.0.0',
|
||||
},
|
||||
};
|
||||
const files = ['package.json', 'api/[endpoint].js'];
|
||||
|
||||
const { builders } = await detectBuilders(files, pkg);
|
||||
const { builders } = await detectBuilders(files, detected);
|
||||
expect(builders[0].use).toBe('@now/node');
|
||||
expect(builders[0].src).toBe('api/[endpoint].js');
|
||||
expect(builders.length).toBe(1);
|
||||
});
|
||||
|
||||
it('package.json with no build + public directory', async () => {
|
||||
const pkg = { dependencies: { next: '9.0.0' } };
|
||||
it('no build command + public directory', async () => {
|
||||
const detected = {
|
||||
framework: {
|
||||
slug: 'next',
|
||||
version: '9.0.0',
|
||||
},
|
||||
};
|
||||
const files = ['package.json', 'public/index.html'];
|
||||
|
||||
const { builders, errors } = await detectBuilders(files, pkg);
|
||||
expect(builders).toBe(null);
|
||||
expect(errors.length).toBe(1);
|
||||
const { builders, errors } = await detectBuilders(files, detected);
|
||||
expect(builders.length).toBe(1);
|
||||
expect(errors).toBe(null);
|
||||
});
|
||||
|
||||
it('no package.json + api', async () => {
|
||||
@@ -336,17 +365,23 @@ describe('Test `detectBuilders`', () => {
|
||||
});
|
||||
|
||||
it('package.json + api + canary', async () => {
|
||||
const pkg = {
|
||||
scripts: { build: 'next build' },
|
||||
dependencies: { next: '9.0.0' },
|
||||
const detected = {
|
||||
buildCommand: 'yarn build',
|
||||
framework: {
|
||||
slug: 'next',
|
||||
version: '9.0.0',
|
||||
},
|
||||
};
|
||||
|
||||
const files = [
|
||||
'pages/index.js',
|
||||
'api/[endpoint].js',
|
||||
'api/[endpoint]/[id].js',
|
||||
];
|
||||
|
||||
const { builders } = await detectBuilders(files, pkg, { tag: 'canary' });
|
||||
const { builders } = await detectBuilders(files, detected, {
|
||||
tag: 'canary',
|
||||
});
|
||||
expect(builders[0].use).toBe('@now/node@canary');
|
||||
expect(builders[1].use).toBe('@now/node@canary');
|
||||
expect(builders[2].use).toBe('@now/next@canary');
|
||||
@@ -354,17 +389,23 @@ describe('Test `detectBuilders`', () => {
|
||||
});
|
||||
|
||||
it('package.json + api + latest', async () => {
|
||||
const pkg = {
|
||||
scripts: { build: 'next build' },
|
||||
dependencies: { next: '9.0.0' },
|
||||
const detected = {
|
||||
buildCommand: 'yarn build',
|
||||
framework: {
|
||||
slug: 'next',
|
||||
version: '9.0.0',
|
||||
},
|
||||
};
|
||||
|
||||
const files = [
|
||||
'pages/index.js',
|
||||
'api/[endpoint].js',
|
||||
'api/[endpoint]/[id].js',
|
||||
];
|
||||
|
||||
const { builders } = await detectBuilders(files, pkg, { tag: 'latest' });
|
||||
const { builders } = await detectBuilders(files, detected, {
|
||||
tag: 'latest',
|
||||
});
|
||||
expect(builders[0].use).toBe('@now/node@latest');
|
||||
expect(builders[1].use).toBe('@now/node@latest');
|
||||
expect(builders[2].use).toBe('@now/next@latest');
|
||||
@@ -372,17 +413,21 @@ describe('Test `detectBuilders`', () => {
|
||||
});
|
||||
|
||||
it('package.json + api + random tag', async () => {
|
||||
const pkg = {
|
||||
scripts: { build: 'next build' },
|
||||
dependencies: { next: '9.0.0' },
|
||||
const detected = {
|
||||
buildCommand: 'yarn build',
|
||||
framework: {
|
||||
slug: 'next',
|
||||
version: '9.0.0',
|
||||
},
|
||||
};
|
||||
|
||||
const files = [
|
||||
'pages/index.js',
|
||||
'api/[endpoint].js',
|
||||
'api/[endpoint]/[id].js',
|
||||
];
|
||||
|
||||
const { builders } = await detectBuilders(files, pkg, { tag: 'haha' });
|
||||
const { builders } = await detectBuilders(files, detected, { tag: 'haha' });
|
||||
expect(builders[0].use).toBe('@now/node@haha');
|
||||
expect(builders[1].use).toBe('@now/node@haha');
|
||||
expect(builders[2].use).toBe('@now/next@haha');
|
||||
@@ -390,13 +435,20 @@ describe('Test `detectBuilders`', () => {
|
||||
});
|
||||
|
||||
it('next.js pages/api + api', async () => {
|
||||
const pkg = {
|
||||
scripts: { build: 'next build' },
|
||||
dependencies: { next: '9.0.0' },
|
||||
const detected = {
|
||||
buildCommand: 'yarn build',
|
||||
framework: {
|
||||
slug: 'next',
|
||||
version: '9.0.0',
|
||||
},
|
||||
};
|
||||
|
||||
const files = ['api/user.js', 'pages/api/user.js'];
|
||||
|
||||
const { warnings, errors, builders } = await detectBuilders(files, pkg);
|
||||
const { warnings, errors, builders } = await detectBuilders(
|
||||
files,
|
||||
detected
|
||||
);
|
||||
|
||||
expect(errors).toBe(null);
|
||||
expect(warnings[0]).toBeDefined();
|
||||
@@ -420,9 +472,12 @@ describe('Test `detectBuilders`', () => {
|
||||
});
|
||||
|
||||
it('functions with nextjs', async () => {
|
||||
const pkg = {
|
||||
scripts: { build: 'next build' },
|
||||
dependencies: { next: '9.0.0' },
|
||||
const detected = {
|
||||
buildCommand: 'yarn build',
|
||||
framework: {
|
||||
slug: 'next',
|
||||
version: '9.0.0',
|
||||
},
|
||||
};
|
||||
const functions = {
|
||||
'pages/api/teams/**': {
|
||||
@@ -435,7 +490,7 @@ describe('Test `detectBuilders`', () => {
|
||||
'pages/index.js',
|
||||
'pages/api/teams/members.ts',
|
||||
];
|
||||
const { builders, errors } = await detectBuilders(files, pkg, {
|
||||
const { builders, errors } = await detectBuilders(files, detected, {
|
||||
functions,
|
||||
});
|
||||
|
||||
@@ -446,6 +501,11 @@ describe('Test `detectBuilders`', () => {
|
||||
use: '@now/next',
|
||||
config: {
|
||||
zeroConfig: true,
|
||||
buildCommand: 'yarn build',
|
||||
framework: {
|
||||
slug: 'next',
|
||||
version: '9.0.0',
|
||||
},
|
||||
functions: {
|
||||
'pages/api/teams/**': {
|
||||
memory: 128,
|
||||
@@ -457,9 +517,12 @@ describe('Test `detectBuilders`', () => {
|
||||
});
|
||||
|
||||
it('extend with functions', async () => {
|
||||
const pkg = {
|
||||
scripts: { build: 'next build' },
|
||||
dependencies: { next: '9.0.0' },
|
||||
const detected = {
|
||||
buildCommand: 'yarn build',
|
||||
framework: {
|
||||
slug: 'next',
|
||||
version: '9.0.0',
|
||||
},
|
||||
};
|
||||
const functions = {
|
||||
'api/users/*.ts': {
|
||||
@@ -476,7 +539,7 @@ describe('Test `detectBuilders`', () => {
|
||||
'api/users/[id].ts',
|
||||
'api/teams/members.ts',
|
||||
];
|
||||
const { builders } = await detectBuilders(files, pkg, { functions });
|
||||
const { builders } = await detectBuilders(files, detected, { functions });
|
||||
|
||||
expect(builders.length).toBe(3);
|
||||
expect(builders[0]).toEqual({
|
||||
@@ -509,6 +572,11 @@ describe('Test `detectBuilders`', () => {
|
||||
use: '@now/next',
|
||||
config: {
|
||||
zeroConfig: true,
|
||||
buildCommand: 'yarn build',
|
||||
framework: {
|
||||
slug: 'next',
|
||||
version: '9.0.0',
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
@@ -607,31 +675,23 @@ describe('Test `detectBuilders`', () => {
|
||||
});
|
||||
|
||||
it('Do not allow functions that are not used by @now/next', async () => {
|
||||
const pkg = {
|
||||
scripts: { build: 'next build' },
|
||||
dependencies: { next: '9.0.0' },
|
||||
const detected = {
|
||||
buildCommand: 'yarn build',
|
||||
framework: {
|
||||
slug: 'next',
|
||||
version: '9.0.0',
|
||||
},
|
||||
};
|
||||
|
||||
const functions = { 'test.js': { memory: 1024 } };
|
||||
const files = ['pages/index.js', 'test.js'];
|
||||
|
||||
const { errors } = await detectBuilders(files, pkg, { functions });
|
||||
const { errors } = await detectBuilders(files, detected, { functions });
|
||||
|
||||
expect(errors).toBeDefined();
|
||||
expect(errors[0].code).toBe('unused_function');
|
||||
});
|
||||
|
||||
it('Do not allow function non Community Runtimes', async () => {
|
||||
const functions = {
|
||||
'api/test.js': { memory: 128, runtime: '@now/node@1.0.0' },
|
||||
};
|
||||
const files = ['api/test.js'];
|
||||
|
||||
const { errors } = await detectBuilders(files, null, { functions });
|
||||
|
||||
expect(errors).toBeDefined();
|
||||
expect(errors[0].code).toBe('invalid_function_runtime');
|
||||
});
|
||||
|
||||
it('Must include includeFiles config property', async () => {
|
||||
const functions = {
|
||||
'api/test.js': { includeFiles: 'text/include.txt' },
|
||||
@@ -731,6 +791,149 @@ describe('Test `detectBuilders`', () => {
|
||||
expect(errors).not.toBe(null);
|
||||
expect(errors[0].code).toBe('invalid_function_source');
|
||||
});
|
||||
|
||||
it('Custom static output directory', async () => {
|
||||
const detected = {
|
||||
outputDirectory: 'dist',
|
||||
};
|
||||
|
||||
const files = ['dist/index.html', 'dist/style.css'];
|
||||
|
||||
const { builders } = await detectBuilders(files, detected);
|
||||
|
||||
expect(builders.length).toBe(1);
|
||||
expect(builders[0].src).toBe('dist/**/*');
|
||||
expect(builders[0].use).toBe('@now/static');
|
||||
|
||||
const { defaultRoutes } = await detectRoutes(files, builders);
|
||||
|
||||
expect(defaultRoutes.length).toBe(1);
|
||||
expect(defaultRoutes[0].src).toBe('/(.*)');
|
||||
expect(defaultRoutes[0].dest).toBe('/dist/$1');
|
||||
});
|
||||
|
||||
it('Custom static output directory with api', async () => {
|
||||
const detected = {
|
||||
outputDirectory: 'output',
|
||||
};
|
||||
|
||||
const files = ['api/user.ts', 'output/index.html', 'output/style.css'];
|
||||
|
||||
const { builders } = await detectBuilders(files, detected);
|
||||
|
||||
expect(builders.length).toBe(2);
|
||||
expect(builders[1].src).toBe('output/**/*');
|
||||
expect(builders[1].use).toBe('@now/static');
|
||||
|
||||
const { defaultRoutes } = await detectRoutes(files, builders);
|
||||
|
||||
expect(defaultRoutes.length).toBe(3);
|
||||
expect(defaultRoutes[1].status).toBe(404);
|
||||
expect(defaultRoutes[2].src).toBe('/(.*)');
|
||||
expect(defaultRoutes[2].dest).toBe('/output/$1');
|
||||
});
|
||||
|
||||
it('Custom directory for Serverless Functions', async () => {
|
||||
const files = ['server/_lib/db.ts', 'server/user.ts', 'server/team.ts'];
|
||||
|
||||
const functions = {
|
||||
'server/**/*.ts': {
|
||||
memory: 128,
|
||||
runtime: '@now/node@1.2.1',
|
||||
},
|
||||
};
|
||||
|
||||
const { builders } = await detectBuilders(files, null, { functions });
|
||||
|
||||
expect(builders.length).toBe(3);
|
||||
expect(builders[0]).toEqual({
|
||||
use: '@now/node@1.2.1',
|
||||
src: 'server/team.ts',
|
||||
config: {
|
||||
zeroConfig: true,
|
||||
functions: {
|
||||
'server/**/*.ts': {
|
||||
memory: 128,
|
||||
runtime: '@now/node@1.2.1',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(builders[1]).toEqual({
|
||||
use: '@now/node@1.2.1',
|
||||
src: 'server/user.ts',
|
||||
config: {
|
||||
zeroConfig: true,
|
||||
functions: {
|
||||
'server/**/*.ts': {
|
||||
memory: 128,
|
||||
runtime: '@now/node@1.2.1',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
// This is expected, since only "api + full static" is supported
|
||||
// no other directory, so everything else will be deployed
|
||||
expect(builders[2].use).toBe('@now/static');
|
||||
|
||||
const { defaultRoutes } = await detectRoutes(files, builders);
|
||||
|
||||
expect(defaultRoutes.length).toBe(3);
|
||||
expect(defaultRoutes[0].dest).toBe('/server/team.ts');
|
||||
expect(defaultRoutes[0].src).toBe('^/server/(team\\/|team|team\\.ts)$');
|
||||
expect(defaultRoutes[1].dest).toBe('/server/user.ts');
|
||||
expect(defaultRoutes[1].src).toBe('^/server/(user\\/|user|user\\.ts)$');
|
||||
expect(defaultRoutes[2].status).toBe(404);
|
||||
});
|
||||
|
||||
it('Custom directory for Serverless Functions + Next.js', async () => {
|
||||
const detected = {
|
||||
buildCommand: 'yarn build',
|
||||
framework: {
|
||||
slug: 'next',
|
||||
version: '9.0.0',
|
||||
},
|
||||
};
|
||||
|
||||
const functions = {
|
||||
'server/**/*.ts': {
|
||||
runtime: '@now/node@1.2.1',
|
||||
},
|
||||
};
|
||||
|
||||
const files = ['package.json', 'pages/index.ts', 'server/user.ts'];
|
||||
|
||||
const { builders } = await detectBuilders(files, detected, { functions });
|
||||
|
||||
expect(builders.length).toBe(2);
|
||||
expect(builders[0]).toEqual({
|
||||
use: '@now/node@1.2.1',
|
||||
src: 'server/user.ts',
|
||||
config: {
|
||||
zeroConfig: true,
|
||||
functions,
|
||||
},
|
||||
});
|
||||
expect(builders[1]).toEqual({
|
||||
use: '@now/next',
|
||||
src: 'package.json',
|
||||
config: {
|
||||
zeroConfig: true,
|
||||
buildCommand: 'yarn build',
|
||||
framework: {
|
||||
slug: 'next',
|
||||
version: '9.0.0',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const { defaultRoutes } = await detectRoutes(files, builders);
|
||||
|
||||
expect(defaultRoutes.length).toBe(2);
|
||||
expect(defaultRoutes[0].dest).toBe('/server/user.ts');
|
||||
expect(defaultRoutes[0].src).toBe('^/server/(user\\/|user|user\\.ts)$');
|
||||
expect(defaultRoutes[1].status).toBe(404);
|
||||
});
|
||||
});
|
||||
|
||||
it('Test `detectRoutes`', async () => {
|
||||
@@ -804,13 +1007,17 @@ it('Test `detectRoutes`', async () => {
|
||||
}
|
||||
|
||||
{
|
||||
const pkg = {
|
||||
scripts: { build: 'next build' },
|
||||
devDependencies: { next: '9.0.0' },
|
||||
const detected = {
|
||||
buildCommand: 'yarn build',
|
||||
framework: {
|
||||
slug: 'next',
|
||||
version: '9.0.0',
|
||||
},
|
||||
};
|
||||
|
||||
const files = ['public/index.html', 'api/[endpoint].js'];
|
||||
|
||||
const { builders } = await detectBuilders(files, pkg);
|
||||
const { builders } = await detectBuilders(files, detected);
|
||||
const { defaultRoutes } = await detectRoutes(files, builders);
|
||||
expect(defaultRoutes[1].status).toBe(404);
|
||||
expect(defaultRoutes[1].src).toBe('/api(\\/.*)?$');
|
||||
|
||||
@@ -3,6 +3,7 @@ const { mkdirp, copyFile } = require('fs-extra');
|
||||
|
||||
const {
|
||||
glob,
|
||||
debug,
|
||||
download,
|
||||
shouldServe,
|
||||
createLambda,
|
||||
@@ -14,7 +15,6 @@ exports.analyze = ({ files, entrypoint }) => files[entrypoint].digest;
|
||||
exports.version = 3;
|
||||
|
||||
exports.build = async ({ workPath, files, entrypoint, meta, config }) => {
|
||||
console.log('downloading files...');
|
||||
const outDir = await getWritableDirectory();
|
||||
|
||||
await download(files, workPath, meta);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@now/cgi",
|
||||
"version": "1.0.1-canary.0",
|
||||
"version": "1.0.1",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "now",
|
||||
"version": "16.6.3",
|
||||
"version": "16.7.1-canary.0",
|
||||
"preferGlobal": true,
|
||||
"license": "Apache-2.0",
|
||||
"description": "The command-line interface for Now",
|
||||
@@ -146,7 +146,6 @@
|
||||
"ora": "3.4.0",
|
||||
"pcre-to-regexp": "1.0.0",
|
||||
"pluralize": "7.0.0",
|
||||
"pre-commit": "1.2.2",
|
||||
"printf": "0.2.5",
|
||||
"progress": "2.0.3",
|
||||
"promisepipe": "3.0.0",
|
||||
|
||||
@@ -38,7 +38,6 @@ import {
|
||||
} from '../../util/errors-ts';
|
||||
import { SchemaValidationFailed } from '../../util/errors';
|
||||
import purchaseDomainIfAvailable from '../../util/domains/purchase-domain-if-available';
|
||||
import handleCertError from '../../util/certs/handle-cert-error';
|
||||
import isWildcardAlias from '../../util/alias/is-wildcard-alias';
|
||||
import shouldDeployDir from '../../util/deploy/should-deploy-dir';
|
||||
|
||||
@@ -382,15 +381,13 @@ export default async function main(
|
||||
return 1;
|
||||
}
|
||||
|
||||
const deploymentResponse = handleCertError(
|
||||
output,
|
||||
await getDeploymentByIdOrHost(now, contextName, deployment.id, 'v10')
|
||||
const deploymentResponse = await getDeploymentByIdOrHost(
|
||||
now,
|
||||
contextName,
|
||||
deployment.id,
|
||||
'v10'
|
||||
);
|
||||
|
||||
if (deploymentResponse === 1) {
|
||||
return deploymentResponse;
|
||||
}
|
||||
|
||||
if (
|
||||
deploymentResponse instanceof DeploymentNotFound ||
|
||||
deploymentResponse instanceof DeploymentPermissionDenied ||
|
||||
@@ -400,10 +397,6 @@ export default async function main(
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (handleCertError(output, deployment) === 1) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (deployment === null) {
|
||||
error('Uploading failed. Please try again.');
|
||||
return 1;
|
||||
|
||||
@@ -70,7 +70,6 @@ import {
|
||||
InvalidRegionOrDCForScale,
|
||||
} from '../../util/errors';
|
||||
import { SchemaValidationFailed } from '../../util/errors';
|
||||
import handleCertError from '../../util/certs/handle-cert-error';
|
||||
import readPackage from '../../util/read-package';
|
||||
|
||||
interface Env {
|
||||
@@ -801,11 +800,6 @@ async function sync({
|
||||
createArgs
|
||||
);
|
||||
|
||||
const handledResult = handleCertError(output, deployment);
|
||||
if (handledResult === 1) {
|
||||
return handledResult;
|
||||
}
|
||||
|
||||
if (
|
||||
deployment instanceof DomainNotFound ||
|
||||
deployment instanceof NotDomainOwner ||
|
||||
|
||||
@@ -18,7 +18,7 @@ export default async function dev(
|
||||
output: Output
|
||||
) {
|
||||
output.dim(
|
||||
`Now CLI ${pkg.version} dev (beta) — https://zeit.co/feedback/dev`
|
||||
`Now CLI ${pkg.version} dev (beta) — https://zeit.co/feedback`
|
||||
);
|
||||
|
||||
const [dir = '.'] = args;
|
||||
|
||||
@@ -10,6 +10,7 @@ import formatDate from '../../util/format-date';
|
||||
import formatNSTable from '../../util/format-ns-table';
|
||||
import getDomainByName from '../../util/domains/get-domain-by-name';
|
||||
import getScope from '../../util/get-scope';
|
||||
import getDomainPrice from '../../util/domains/get-domain-price';
|
||||
|
||||
type Options = {
|
||||
'--debug': boolean;
|
||||
@@ -23,7 +24,7 @@ export default async function inspect(
|
||||
) {
|
||||
const {
|
||||
authConfig: { token },
|
||||
config
|
||||
config,
|
||||
} = ctx;
|
||||
const { currentTeam } = config;
|
||||
const { apiUrl } = ctx;
|
||||
@@ -61,7 +62,12 @@ export default async function inspect(
|
||||
}
|
||||
|
||||
output.debug(`Fetching domain info`);
|
||||
const domain = await getDomainByName(client, contextName, domainName);
|
||||
const [domain, renewalPrice] = await Promise.all([
|
||||
getDomainByName(client, contextName, domainName),
|
||||
getDomainPrice(client, domainName, 'renewal')
|
||||
.then(res => (res instanceof Error ? null : res.price))
|
||||
.catch(() => null),
|
||||
]);
|
||||
if (domain instanceof DomainNotFound) {
|
||||
output.error(
|
||||
`Domain not found by "${domainName}" under ${chalk.bold(contextName)}`
|
||||
@@ -104,24 +110,35 @@ export default async function inspect(
|
||||
` ${chalk.cyan('Bought At')}\t\t\t${formatDate(domain.boughtAt)}\n`
|
||||
);
|
||||
output.print(
|
||||
` ${chalk.cyan('Transferred At')}\t\t${formatDate(domain.transferredAt)}\n`
|
||||
` ${chalk.cyan('Transferred At')}\t\t${formatDate(
|
||||
domain.transferredAt
|
||||
)}\n`
|
||||
);
|
||||
output.print(
|
||||
` ${chalk.cyan('Expires At')}\t\t\t${formatDate(domain.expiresAt)}\n`
|
||||
);
|
||||
output.print(
|
||||
` ${chalk.cyan('NS Verified At')}\t\t${formatDate(domain.nsVerifiedAt)}\n`
|
||||
` ${chalk.cyan('NS Verified At')}\t\t${formatDate(
|
||||
domain.nsVerifiedAt
|
||||
)}\n`
|
||||
);
|
||||
output.print(
|
||||
` ${chalk.cyan('TXT Verified At')}\t\t${formatDate(domain.txtVerifiedAt)}\n`
|
||||
` ${chalk.cyan('TXT Verified At')}\t\t${formatDate(
|
||||
domain.txtVerifiedAt
|
||||
)}\n`
|
||||
);
|
||||
output.print(` ${chalk.cyan('CDN Enabled')}\t\t${true}\n`);
|
||||
if (renewalPrice && domain.boughtAt) {
|
||||
output.print(
|
||||
` ${chalk.cyan('Renewal Price')}\t\t$${renewalPrice} USD\n`
|
||||
);
|
||||
}
|
||||
output.print(` ${chalk.cyan('CDN Enabled')}\t\t\t${true}\n`);
|
||||
output.print('\n');
|
||||
|
||||
output.print(chalk.bold(' Nameservers\n\n'));
|
||||
output.print(
|
||||
`${formatNSTable(domain.intendedNameservers, domain.nameservers, {
|
||||
extraSpace: ' '
|
||||
extraSpace: ' ',
|
||||
})}\n`
|
||||
);
|
||||
output.print('\n');
|
||||
@@ -129,7 +146,7 @@ export default async function inspect(
|
||||
output.print(chalk.bold(' Verification Record\n\n'));
|
||||
output.print(
|
||||
`${dnsTable([['_now', 'TXT', domain.verificationRecord]], {
|
||||
extraSpace: ' '
|
||||
extraSpace: ' ',
|
||||
})}\n`
|
||||
);
|
||||
output.print('\n');
|
||||
|
||||
@@ -239,7 +239,7 @@ export default async function main(ctx) {
|
||||
return 1;
|
||||
}
|
||||
if (deployment.version === 2) {
|
||||
output.error('Cannot scale a deployment containing builds');
|
||||
output.error('Cannot scale a Now 2.0 deployment');
|
||||
now.close();
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import chalk from 'chalk';
|
||||
import execa from 'execa';
|
||||
import semver from 'semver';
|
||||
import pipe from 'promisepipe';
|
||||
@@ -8,7 +7,6 @@ import { extract } from 'tar-fs';
|
||||
import { createHash } from 'crypto';
|
||||
import { createGunzip } from 'zlib';
|
||||
import { join, resolve } from 'path';
|
||||
import { funCacheDir } from '@zeit/fun';
|
||||
import { PackageJson } from '@now/build-utils';
|
||||
import XDGAppPaths from 'xdg-app-paths';
|
||||
import {
|
||||
@@ -17,7 +15,6 @@ import {
|
||||
readFile,
|
||||
readJSON,
|
||||
writeFile,
|
||||
remove,
|
||||
} from 'fs-extra';
|
||||
import pkg from '../../../package.json';
|
||||
|
||||
@@ -104,11 +101,7 @@ export async function prepareBuilderDir() {
|
||||
|
||||
if (!hasBundledBuilders(dependencies)) {
|
||||
const extractor = extract(builderDir);
|
||||
await pipe(
|
||||
createReadStream(bundledTarballPath),
|
||||
createGunzip(),
|
||||
extractor
|
||||
);
|
||||
await pipe(createReadStream(bundledTarballPath), createGunzip(), extractor);
|
||||
}
|
||||
|
||||
return builderDir;
|
||||
|
||||
@@ -22,6 +22,8 @@ import {
|
||||
PackageJson,
|
||||
detectBuilders,
|
||||
detectRoutes,
|
||||
detectDefaults,
|
||||
DetectorFilesystem,
|
||||
} from '@now/build-utils';
|
||||
|
||||
import { once } from '../once';
|
||||
@@ -100,6 +102,25 @@ function sortBuilders(buildA: Builder, buildB: Builder) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
class DevDetectorFilesystem extends DetectorFilesystem {
|
||||
private dir: string;
|
||||
private files: string[];
|
||||
|
||||
constructor(dir: string, files: string[]) {
|
||||
super();
|
||||
this.dir = dir;
|
||||
this.files = files;
|
||||
}
|
||||
|
||||
_exists(name: string): Promise<boolean> {
|
||||
return Promise.resolve(this.files.includes(name));
|
||||
}
|
||||
|
||||
_readFile(name: string): Promise<Buffer> {
|
||||
return fs.readFile(join(this.dir, name));
|
||||
}
|
||||
}
|
||||
|
||||
export default class DevServer {
|
||||
public cwd: string;
|
||||
public debug: boolean;
|
||||
@@ -477,8 +498,6 @@ export default class DevServer {
|
||||
return this.cachedNowConfig;
|
||||
}
|
||||
|
||||
const pkg = await this.getPackageJson();
|
||||
|
||||
// The default empty `now.json` is used to serve all files as static
|
||||
// when no `now.json` is present
|
||||
let config: NowConfig = this.cachedNowConfig || { version: 2 };
|
||||
@@ -526,11 +545,19 @@ export default class DevServer {
|
||||
|
||||
// no builds -> zero config
|
||||
if (!config.builds || config.builds.length === 0) {
|
||||
const { builders, warnings, errors } = await detectBuilders(files, pkg, {
|
||||
tag: getDistTag(cliVersion) === 'canary' ? 'canary' : 'latest',
|
||||
functions: config.functions,
|
||||
const detectorResult = await detectDefaults({
|
||||
fs: new DevDetectorFilesystem(this.cwd, files),
|
||||
});
|
||||
|
||||
const { builders, warnings, errors } = await detectBuilders(
|
||||
files,
|
||||
detectorResult,
|
||||
{
|
||||
tag: getDistTag(cliVersion) === 'canary' ? 'canary' : 'latest',
|
||||
functions: config.functions,
|
||||
}
|
||||
);
|
||||
|
||||
if (errors) {
|
||||
this.output.error(errors[0].message);
|
||||
await this.exit();
|
||||
@@ -575,29 +602,6 @@ export default class DevServer {
|
||||
return config;
|
||||
}
|
||||
|
||||
async getPackageJson(): Promise<PackageJson | null> {
|
||||
const pkgPath = join(this.cwd, 'package.json');
|
||||
let pkg: PackageJson | null = null;
|
||||
|
||||
this.output.debug('Reading `package.json` file');
|
||||
|
||||
try {
|
||||
pkg = JSON.parse(await fs.readFile(pkgPath, 'utf8'));
|
||||
} catch (err) {
|
||||
if (err.code === 'ENOENT') {
|
||||
this.output.debug('No `package.json` file present');
|
||||
} else if (err.name === 'SyntaxError') {
|
||||
this.output.warn(
|
||||
`There is a syntax error in the \`package.json\` file: ${err.message}`
|
||||
);
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
return pkg;
|
||||
}
|
||||
|
||||
async tryValidateOrExit(
|
||||
config: NowConfig,
|
||||
validate: (c: NowConfig) => string | null
|
||||
|
||||
@@ -8,62 +8,10 @@ import {
|
||||
trailingSlashSchema,
|
||||
} from '@now/routing-utils';
|
||||
import { NowConfig } from './types';
|
||||
import { functionsSchema, buildsSchema } from '@now/build-utils';
|
||||
|
||||
const ajv = new Ajv();
|
||||
|
||||
const buildsSchema = {
|
||||
type: 'array',
|
||||
minItems: 0,
|
||||
maxItems: 128,
|
||||
items: {
|
||||
type: 'object',
|
||||
additionalProperties: false,
|
||||
required: ['use'],
|
||||
properties: {
|
||||
src: {
|
||||
type: 'string',
|
||||
minLength: 1,
|
||||
maxLength: 4096,
|
||||
},
|
||||
use: {
|
||||
type: 'string',
|
||||
minLength: 3,
|
||||
maxLength: 256,
|
||||
},
|
||||
config: { type: 'object' },
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const functionsSchema = {
|
||||
type: 'object',
|
||||
minProperties: 1,
|
||||
maxProperties: 50,
|
||||
additionalProperties: false,
|
||||
patternProperties: {
|
||||
'^.{1,256}$': {
|
||||
type: 'object',
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
runtime: {
|
||||
type: 'string',
|
||||
maxLength: 256,
|
||||
},
|
||||
memory: {
|
||||
enum: Object.keys(Array.from({ length: 50 }))
|
||||
.slice(2, 48)
|
||||
.map(x => Number(x) * 64),
|
||||
},
|
||||
maxDuration: {
|
||||
type: 'number',
|
||||
minimum: 1,
|
||||
maximum: 900,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const validateBuilds = ajv.compile(buildsSchema);
|
||||
const validateRoutes = ajv.compile(routesSchema);
|
||||
const validateCleanUrls = ajv.compile(cleanUrlsSchema);
|
||||
|
||||
@@ -1097,7 +1097,7 @@ export class LambdaSizeExceededError extends NowError<
|
||||
size
|
||||
).toLowerCase()}) exceeds the maximum size limit (${bytes(
|
||||
maxLambdaSize
|
||||
).toLowerCase()}). Learn more: https://zeit.co/docs/v2/deployments/concepts/lambdas/#maximum-bundle-size`,
|
||||
).toLowerCase()}).`,
|
||||
meta: { size, maxLambdaSize },
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import chalk from 'chalk';
|
||||
import boxen from 'boxen';
|
||||
import { format } from 'util';
|
||||
import { Console } from 'console';
|
||||
|
||||
@@ -18,10 +19,32 @@ export default function createOutput({ debug: debugEnabled = false } = {}) {
|
||||
}
|
||||
|
||||
function warn(str: string, slug: string | null = null) {
|
||||
log(chalk`{yellow.bold WARN!} ${str}`);
|
||||
if (slug !== null) {
|
||||
log(`More details: https://err.sh/now/${slug}`);
|
||||
const prevTerm = process.env.TERM;
|
||||
|
||||
if (!prevTerm) {
|
||||
// workaround for https://github.com/sindresorhus/term-size/issues/13
|
||||
process.env.TERM = 'xterm';
|
||||
}
|
||||
|
||||
print(
|
||||
boxen(
|
||||
chalk.bold.yellow('WARN! ') +
|
||||
str +
|
||||
(slug ? `\nMore details: https://err.sh/now/${slug}` : ''),
|
||||
{
|
||||
padding: {
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
left: 1,
|
||||
right: 1,
|
||||
},
|
||||
borderColor: 'yellow',
|
||||
}
|
||||
)
|
||||
);
|
||||
print('\n');
|
||||
|
||||
process.env.TERM = prevTerm;
|
||||
}
|
||||
|
||||
function note(str: string) {
|
||||
@@ -59,7 +82,7 @@ export default function createOutput({ debug: debugEnabled = false } = {}) {
|
||||
_times: new Map(),
|
||||
log(a: string, ...args: string[]) {
|
||||
debug(format(a, ...args));
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
async function time(label: string, fn: Promise<any> | (() => Promise<any>)) {
|
||||
@@ -85,6 +108,6 @@ export default function createOutput({ debug: debugEnabled = false } = {}) {
|
||||
debug,
|
||||
dim,
|
||||
time,
|
||||
note
|
||||
note,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -58,9 +58,7 @@ export default async function preferV2Deployment({
|
||||
)} is missing a ${cmd('start')} script. ${INFO}`;
|
||||
}
|
||||
} else if (!pkg && !hasDockerfile) {
|
||||
return `Deploying to Now 2.0, because no ${highlight(
|
||||
'Dockerfile'
|
||||
)} was found. ${INFO}`;
|
||||
return `Deploying to Now 2.0 automatically. ${INFO}`;
|
||||
}
|
||||
|
||||
if (client && projectName) {
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"scripts": {
|
||||
"build": "./hugo"
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
@@ -1238,7 +1238,7 @@ deep-extend@^0.6.0:
|
||||
resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac"
|
||||
integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==
|
||||
|
||||
define-properties@^1.1.2:
|
||||
define-properties@^1.1.2, define-properties@^1.1.3:
|
||||
version "1.1.3"
|
||||
resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1"
|
||||
integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==
|
||||
@@ -1660,7 +1660,7 @@ fsevents@^1.2.7:
|
||||
nan "^2.12.1"
|
||||
node-pre-gyp "^0.12.0"
|
||||
|
||||
function-bind@^1.0.2, function-bind@^1.1.0, function-bind@^1.1.1:
|
||||
function-bind@^1.1.0, function-bind@^1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
|
||||
integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==
|
||||
@@ -3305,21 +3305,21 @@ string-width@^1.0.1:
|
||||
is-fullwidth-code-point "^2.0.0"
|
||||
strip-ansi "^4.0.0"
|
||||
|
||||
string.prototype.trimleft@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/string.prototype.trimleft/-/string.prototype.trimleft-2.0.0.tgz#68b6aa8e162c6a80e76e3a8a0c2e747186e271ff"
|
||||
integrity sha1-aLaqjhYsaoDnbjqKDC50cYbicf8=
|
||||
string.prototype.trimleft@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/string.prototype.trimleft/-/string.prototype.trimleft-2.1.0.tgz#6cc47f0d7eb8d62b0f3701611715a3954591d634"
|
||||
integrity sha512-FJ6b7EgdKxxbDxc79cOlok6Afd++TTs5szo+zJTUyow3ycrRfJVE2pq3vcN53XexvKZu/DJMDfeI/qMiZTrjTw==
|
||||
dependencies:
|
||||
define-properties "^1.1.2"
|
||||
function-bind "^1.0.2"
|
||||
define-properties "^1.1.3"
|
||||
function-bind "^1.1.1"
|
||||
|
||||
string.prototype.trimright@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/string.prototype.trimright/-/string.prototype.trimright-2.0.0.tgz#ab4a56d802a01fbe7293e11e84f24dc8164661dd"
|
||||
integrity sha1-q0pW2AKgH75yk+EehPJNyBZGYd0=
|
||||
string.prototype.trimright@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/string.prototype.trimright/-/string.prototype.trimright-2.1.0.tgz#669d164be9df9b6f7559fa8e89945b168a5a6c58"
|
||||
integrity sha512-fXZTSV55dNBwv16uw+hh5jkghxSnc5oHq+5K/gXgizHwAvMetdAJlHqqoFC1FSDVPYWLkAKl2cxpUT41sV7nSg==
|
||||
dependencies:
|
||||
define-properties "^1.1.2"
|
||||
function-bind "^1.0.2"
|
||||
define-properties "^1.1.3"
|
||||
function-bind "^1.1.1"
|
||||
|
||||
string_decoder@~0.10.31, string_decoder@~0.10.x:
|
||||
version "0.10.31"
|
||||
|
||||
1
packages/now-cli/test/dev/fixtures/public-and-server-as-api/.gitignore
vendored
Normal file
1
packages/now-cli/test/dev/fixtures/public-and-server-as-api/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
!public
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"functions": {
|
||||
"server/**/*.js": {
|
||||
"runtime": "@now/node@1.2.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
This is content.
|
||||
@@ -0,0 +1,3 @@
|
||||
export default (req, res) => {
|
||||
res.end(`current hour: ${Math.floor(Date.now() / 10000)}`);
|
||||
};
|
||||
@@ -15,6 +15,11 @@ let port = 3000;
|
||||
const binaryPath = path.resolve(__dirname, `../../scripts/start.js`);
|
||||
const fixture = name => path.join('test', 'dev', 'fixtures', name);
|
||||
|
||||
// Adds Hugo to the PATH
|
||||
process.env.PATH = `${path.resolve(fixture('08-hugo'))}${path.delimiter}${
|
||||
process.env.PATH
|
||||
}`;
|
||||
|
||||
function fetchWithRetry(url, retries = 3, opts = {}) {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
@@ -128,17 +133,15 @@ function testFixtureStdio(directory, fn) {
|
||||
readyResolve = resolve;
|
||||
});
|
||||
|
||||
console.log(`> testing ${directory}`);
|
||||
dev = execa(binaryPath, ['dev', dir, '-l', port]);
|
||||
dev.stderr.on('data', async data => {
|
||||
output += data.toString();
|
||||
if (data.toString().includes('Ready! Available at')) {
|
||||
if (output.includes('Ready! Available at')) {
|
||||
readyResolve();
|
||||
}
|
||||
|
||||
if (
|
||||
data.toString().includes('Command failed') ||
|
||||
data.toString().includes('Error!')
|
||||
) {
|
||||
if (output.includes('Command failed') || output.includes('Error!')) {
|
||||
dev.kill('SIGTERM');
|
||||
console.log(output);
|
||||
process.exit(1);
|
||||
@@ -511,13 +514,12 @@ if (satisfies(process.version, '>= 6.9.0 <7.0.0 || >= 8.9.0')) {
|
||||
test(
|
||||
'[now dev] 06-gridsome',
|
||||
testFixtureStdio('06-gridsome', async (t, port) => {
|
||||
const result = fetch(`http://localhost:${port}`);
|
||||
const response = await result;
|
||||
const response = await fetch(`http://localhost:${port}`);
|
||||
|
||||
validateResponseHeaders(t, response);
|
||||
|
||||
const body = await response.text();
|
||||
t.regex(body, /Hello, world!/gm);
|
||||
t.regex(body, /<div id="app"><\/div>/gm);
|
||||
})
|
||||
);
|
||||
} else {
|
||||
@@ -529,8 +531,7 @@ if (satisfies(process.version, '>= 6.9.0 <7.0.0 || >= 8.9.0')) {
|
||||
test(
|
||||
'[now dev] 07-hexo-node',
|
||||
testFixtureStdio('07-hexo-node', async (t, port) => {
|
||||
const result = await fetchWithRetry(`http://localhost:${port}`, 180);
|
||||
const response = await result;
|
||||
const response = await fetchWithRetry(`http://localhost:${port}`, 180);
|
||||
|
||||
validateResponseHeaders(t, response);
|
||||
|
||||
@@ -542,8 +543,7 @@ test(
|
||||
test(
|
||||
'[now dev] 08-hugo',
|
||||
testFixtureStdio('08-hugo', async (t, port) => {
|
||||
const result = fetch(`http://localhost:${port}`);
|
||||
const response = await result;
|
||||
const response = await fetch(`http://localhost:${port}`);
|
||||
|
||||
validateResponseHeaders(t, response);
|
||||
|
||||
@@ -840,8 +840,7 @@ if (satisfies(process.version, '>= 8.10.0')) {
|
||||
test(
|
||||
'[now dev] 22-brunch',
|
||||
testFixtureStdio('22-brunch', async (t, port) => {
|
||||
const result = fetch(`http://localhost:${port}`);
|
||||
const response = await result;
|
||||
const response = await fetchWithRetry(`http://localhost:${port}`, 50);
|
||||
|
||||
validateResponseHeaders(t, response);
|
||||
|
||||
@@ -1114,8 +1113,7 @@ if (satisfies(process.version, '>= 8.9.0')) {
|
||||
// start `now dev` detached in child_process
|
||||
dev.unref();
|
||||
|
||||
const result = await fetchWithRetry(`http://localhost:${port}`, 80);
|
||||
const response = await result;
|
||||
const response = await fetchWithRetry(`http://localhost:${port}`, 80);
|
||||
|
||||
validateResponseHeaders(t, response);
|
||||
|
||||
@@ -1134,8 +1132,10 @@ if (satisfies(process.version, '>= 8.9.0')) {
|
||||
test(
|
||||
'[now dev] Use runtime from the functions property',
|
||||
testFixtureStdio('custom-runtime', async (t, port) => {
|
||||
const result = await fetchWithRetry(`http://localhost:${port}/api/user`, 3);
|
||||
const response = await result;
|
||||
const response = await fetchWithRetry(
|
||||
`http://localhost:${port}/api/user`,
|
||||
3
|
||||
);
|
||||
|
||||
validateResponseHeaders(t, response);
|
||||
|
||||
@@ -1143,3 +1143,20 @@ test(
|
||||
t.regex(body, /Hello, from Bash!/gm);
|
||||
})
|
||||
);
|
||||
|
||||
test(
|
||||
'[now dev] Use public with a custom Serverless Function in `server/date.js',
|
||||
testFixtureStdio('public-and-server-as-api', async (t, port) => {
|
||||
const response = await fetchWithRetry(
|
||||
`http://localhost:${port}/server/date`
|
||||
);
|
||||
|
||||
validateResponseHeaders(t, response);
|
||||
|
||||
t.is(response.status, 200);
|
||||
t.is(
|
||||
await response.text(),
|
||||
`current hour: ${Math.floor(Date.now() / 10000)}`
|
||||
);
|
||||
})
|
||||
);
|
||||
|
||||
@@ -466,10 +466,10 @@ CMD ["node", "index.js"]`,
|
||||
autoAlias: true,
|
||||
autoJobCancelation: true,
|
||||
enabled: true,
|
||||
silent: true
|
||||
}
|
||||
})
|
||||
}
|
||||
silent: true,
|
||||
},
|
||||
}),
|
||||
},
|
||||
};
|
||||
|
||||
for (const typeName of Object.keys(spec)) {
|
||||
|
||||
51
packages/now-cli/test/integration.js
vendored
51
packages/now-cli/test/integration.js
vendored
@@ -493,6 +493,35 @@ test('list the payment methods', async t => {
|
||||
t.true(stdout.startsWith(`> 0 cards found under ${contextName}`));
|
||||
});
|
||||
|
||||
test('domains inspect', async t => {
|
||||
const domainName = `inspect-${contextName}.org`;
|
||||
|
||||
const addRes = await execa(
|
||||
binaryPath,
|
||||
[`domains`, `add`, domainName, ...defaultArgs],
|
||||
{ reject: false }
|
||||
);
|
||||
t.is(addRes.exitCode, 0);
|
||||
|
||||
const { stderr, exitCode } = await execa(
|
||||
binaryPath,
|
||||
['domains', 'inspect', domainName, ...defaultArgs],
|
||||
{
|
||||
reject: false,
|
||||
}
|
||||
);
|
||||
|
||||
const rmRes = await execa(
|
||||
binaryPath,
|
||||
[`domains`, `rm`, domainName, ...defaultArgs],
|
||||
{ reject: false, input: 'y' }
|
||||
);
|
||||
t.is(rmRes.exitCode, 0);
|
||||
|
||||
t.is(exitCode, 0);
|
||||
t.true(!stderr.includes(`Renewal Price`));
|
||||
});
|
||||
|
||||
test('try to purchase a domain', async t => {
|
||||
const { stderr, stdout, exitCode } = await execa(
|
||||
binaryPath,
|
||||
@@ -767,7 +796,19 @@ test('create wildcard alias for deployment', async t => {
|
||||
t.true(stdout.startsWith(goal));
|
||||
|
||||
// Send a test request to the alias
|
||||
const response = await fetch(`https://test.${contextName}.now.sh`);
|
||||
// Retries to make sure we consider the time it takes to update
|
||||
const response = await retry(
|
||||
async () => {
|
||||
const response = await fetch(`https://test.${contextName}.now.sh`);
|
||||
|
||||
if (response.ok) {
|
||||
return response;
|
||||
}
|
||||
|
||||
throw new Error(`Error: Returned code ${response.status}`);
|
||||
},
|
||||
{ retries: 3 }
|
||||
);
|
||||
const content = await response.text();
|
||||
|
||||
t.true(response.ok);
|
||||
@@ -874,7 +915,7 @@ test('ensure we render a warning for deployments with no files', async t => {
|
||||
// Ensure the warning is printed
|
||||
t.true(
|
||||
stderr.includes(
|
||||
'> WARN! There are no files (or only files starting with a dot) inside your deployment.'
|
||||
'WARN! There are no files (or only files starting with a dot) inside your deployment.'
|
||||
)
|
||||
);
|
||||
|
||||
@@ -2040,10 +2081,10 @@ test('fail to deploy a Lambda with a specific runtime but without a locked versi
|
||||
});
|
||||
|
||||
test('ensure `github` and `scope` are not sent to the API', async t => {
|
||||
const directory = fixture('github-and-scope-config');
|
||||
const output = await execute([directory]);
|
||||
const directory = fixture('github-and-scope-config');
|
||||
const output = await execute([directory]);
|
||||
|
||||
t.is(output.exitCode, 0, formatOutput(output));
|
||||
t.is(output.exitCode, 0, formatOutput(output));
|
||||
});
|
||||
|
||||
test.after.always(async () => {
|
||||
|
||||
202
packages/now-cli/test/unit.js
vendored
202
packages/now-cli/test/unit.js
vendored
@@ -17,7 +17,7 @@ import getURL from './helpers/get-url';
|
||||
import {
|
||||
npm as getNpmFiles_,
|
||||
docker as getDockerFiles_,
|
||||
staticFiles as getStaticFiles_
|
||||
staticFiles as getStaticFiles_,
|
||||
} from '../src/util/get-files';
|
||||
import didYouMean from '../src/util/init/did-you-mean';
|
||||
import { isValidName } from '../src/util/is-valid-name';
|
||||
@@ -32,7 +32,7 @@ const fixture = name => join(prefix, name);
|
||||
const getNpmFiles = async dir => {
|
||||
const { pkg, nowConfig, hasNowJson } = await readMetadata(dir, {
|
||||
quiet: true,
|
||||
strict: false
|
||||
strict: false,
|
||||
});
|
||||
|
||||
return getNpmFiles_(dir, pkg, nowConfig, { hasNowJson, output });
|
||||
@@ -41,7 +41,7 @@ const getNpmFiles = async dir => {
|
||||
const getDockerFiles = async dir => {
|
||||
const { nowConfig, hasNowJson } = await readMetadata(dir, {
|
||||
quiet: true,
|
||||
strict: false
|
||||
strict: false,
|
||||
});
|
||||
|
||||
return getDockerFiles_(dir, nowConfig, { hasNowJson, output });
|
||||
@@ -51,7 +51,7 @@ const getStaticFiles = async (dir, isBuilds = false) => {
|
||||
const { nowConfig, hasNowJson } = await readMetadata(dir, {
|
||||
deploymentType: 'static',
|
||||
quiet: true,
|
||||
strict: false
|
||||
strict: false,
|
||||
});
|
||||
|
||||
return getStaticFiles_(dir, nowConfig, { hasNowJson, output, isBuilds });
|
||||
@@ -169,11 +169,9 @@ test('`now.files` overrides `.gitignore` in Static with custom config path', asy
|
||||
const path = 'now-json-static-gitignore-override';
|
||||
|
||||
// Simulate custom args passed by the user
|
||||
process.argv = [...process.argv, '--local-config', './now.json']
|
||||
process.argv = [...process.argv, '--local-config', './now.json'];
|
||||
|
||||
let files = await getStaticFiles(
|
||||
fixture(path)
|
||||
);
|
||||
let files = await getStaticFiles(fixture(path));
|
||||
|
||||
files = files.sort(alpha);
|
||||
|
||||
@@ -185,9 +183,7 @@ test('`now.files` overrides `.gitignore` in Static with custom config path', asy
|
||||
|
||||
test('`now.files` overrides `.gitignore` in Static', async t => {
|
||||
const path = 'now-json-static-gitignore-override';
|
||||
let files = await getStaticFiles(
|
||||
fixture(path)
|
||||
);
|
||||
let files = await getStaticFiles(fixture(path));
|
||||
files = files.sort(alpha);
|
||||
|
||||
t.is(files.length, 3);
|
||||
@@ -337,7 +333,7 @@ test('support docker', async t => {
|
||||
test('gets correct name of docker deployment', async t => {
|
||||
const { name, deploymentType } = await readMetadata(fixture('dockerfile'), {
|
||||
quiet: true,
|
||||
strict: false
|
||||
strict: false,
|
||||
});
|
||||
|
||||
t.is(deploymentType, 'docker');
|
||||
@@ -375,7 +371,7 @@ test('throw for unsupported `now.json` type property', async t => {
|
||||
try {
|
||||
await readMetadata(f, {
|
||||
quiet: true,
|
||||
strict: false
|
||||
strict: false,
|
||||
});
|
||||
} catch (err) {
|
||||
t.is(err.code, 'unsupported_deployment_type');
|
||||
@@ -387,7 +383,7 @@ test('support `now.json` files with package.json non quiet', async t => {
|
||||
const f = fixture('now-json-no-name');
|
||||
const { deploymentType } = await readMetadata(f, {
|
||||
quiet: false,
|
||||
strict: false
|
||||
strict: false,
|
||||
});
|
||||
|
||||
t.is(deploymentType, 'npm');
|
||||
@@ -404,7 +400,7 @@ test('support `now.json` files with package.json non quiet', async t => {
|
||||
test('support `now.json` files with package.json non quiet not specified', async t => {
|
||||
const f = fixture('now-json-no-name');
|
||||
const { deploymentType } = await readMetadata(f, {
|
||||
strict: false
|
||||
strict: false,
|
||||
});
|
||||
|
||||
t.is(deploymentType, 'npm');
|
||||
@@ -423,7 +419,7 @@ test('No commands in Dockerfile with automatic strictness', async t => {
|
||||
|
||||
try {
|
||||
await readMetadata(f, {
|
||||
quiet: true
|
||||
quiet: true,
|
||||
});
|
||||
} catch (err) {
|
||||
t.is(err.code, 'no_dockerfile_commands');
|
||||
@@ -437,7 +433,7 @@ test('No commands in Dockerfile', async t => {
|
||||
try {
|
||||
await readMetadata(f, {
|
||||
quiet: true,
|
||||
strict: true
|
||||
strict: true,
|
||||
});
|
||||
} catch (err) {
|
||||
t.is(err.code, 'no_dockerfile_commands');
|
||||
@@ -451,7 +447,7 @@ test('Missing Dockerfile for `docker` type', async t => {
|
||||
try {
|
||||
await readMetadata(f, {
|
||||
quiet: true,
|
||||
strict: true
|
||||
strict: true,
|
||||
});
|
||||
} catch (err) {
|
||||
t.is(err.code, 'dockerfile_missing');
|
||||
@@ -463,7 +459,7 @@ test('support `now.json` files with Dockerfile', async t => {
|
||||
const f = fixture('now-json-docker');
|
||||
const { deploymentType, nowConfig, hasNowJson } = await readMetadata(f, {
|
||||
quiet: true,
|
||||
strict: false
|
||||
strict: false,
|
||||
});
|
||||
t.is(deploymentType, 'docker');
|
||||
|
||||
@@ -479,7 +475,7 @@ test('load name from Dockerfile', async t => {
|
||||
const f = fixture('now-json-docker-name');
|
||||
const { deploymentType, name } = await readMetadata(f, {
|
||||
quiet: true,
|
||||
strict: false
|
||||
strict: false,
|
||||
});
|
||||
|
||||
t.is(deploymentType, 'docker');
|
||||
@@ -490,7 +486,7 @@ test('support `now.json` files with Dockerfile non quiet', async t => {
|
||||
const f = fixture('now-json-docker');
|
||||
const { deploymentType, nowConfig, hasNowJson } = await readMetadata(f, {
|
||||
quiet: false,
|
||||
strict: false
|
||||
strict: false,
|
||||
});
|
||||
t.is(deploymentType, 'docker');
|
||||
|
||||
@@ -507,7 +503,7 @@ test('throws when both `now.json` and `package.json:now` exist', async t => {
|
||||
try {
|
||||
await readMetadata(fixture('now-json-throws'), {
|
||||
quiet: true,
|
||||
strict: false
|
||||
strict: false,
|
||||
});
|
||||
} catch (err) {
|
||||
e = err;
|
||||
@@ -523,7 +519,7 @@ test('throws when `package.json` and `Dockerfile` exist', async t => {
|
||||
try {
|
||||
await readMetadata(fixture('multiple-manifests-throws'), {
|
||||
quiet: true,
|
||||
strict: false
|
||||
strict: false,
|
||||
});
|
||||
} catch (err) {
|
||||
e = err;
|
||||
@@ -536,7 +532,7 @@ test('support `package.json:now.type` to bypass multiple manifests error', async
|
||||
const f = fixture('type-in-package-now-with-dockerfile');
|
||||
const { type, nowConfig, hasNowJson } = await readMetadata(f, {
|
||||
quiet: true,
|
||||
strict: false
|
||||
strict: false,
|
||||
});
|
||||
t.is(type, 'npm');
|
||||
t.is(nowConfig.type, 'npm');
|
||||
@@ -544,16 +540,16 @@ test('support `package.json:now.type` to bypass multiple manifests error', async
|
||||
});
|
||||
|
||||
test('friendly error for malformed JSON', async t => {
|
||||
const err = await t.throwsAsync(
|
||||
() => readMetadata(fixture('json-syntax-error'), {
|
||||
const err = await t.throwsAsync(() =>
|
||||
readMetadata(fixture('json-syntax-error'), {
|
||||
quiet: true,
|
||||
strict: false
|
||||
strict: false,
|
||||
})
|
||||
);
|
||||
t.is(err.name, 'JSONError');
|
||||
t.is(
|
||||
err.message,
|
||||
'Unexpected token \'o\' at 2:5 in test/fixtures/unit/json-syntax-error/package.json\n oops\n ^'
|
||||
"Unexpected token 'o' at 2:5 in test/fixtures/unit/json-syntax-error/package.json\n oops\n ^"
|
||||
);
|
||||
});
|
||||
|
||||
@@ -597,7 +593,7 @@ test('`wait` utility does not invoke spinner before n miliseconds', async t => {
|
||||
const oraStub = sinon.stub().returns({
|
||||
color: '',
|
||||
start: () => {},
|
||||
stop: () => {}
|
||||
stop: () => {},
|
||||
});
|
||||
|
||||
const timeOut = 200;
|
||||
@@ -612,7 +608,7 @@ test('`wait` utility invokes spinner after n miliseconds', async t => {
|
||||
const oraStub = sinon.stub().returns({
|
||||
color: '',
|
||||
start: () => {},
|
||||
stop: () => {}
|
||||
stop: () => {},
|
||||
});
|
||||
|
||||
const timeOut = 200;
|
||||
@@ -635,7 +631,7 @@ test('`wait` utility does not invoke spinner when stopped before delay', async t
|
||||
const oraStub = sinon.stub().returns({
|
||||
color: '',
|
||||
start: () => {},
|
||||
stop: () => {}
|
||||
stop: () => {},
|
||||
});
|
||||
|
||||
const timeOut = 200;
|
||||
@@ -694,8 +690,8 @@ test('4xx response error as correct JSON', async t => {
|
||||
const fn = async (req, res) => {
|
||||
send(res, 400, {
|
||||
error: {
|
||||
message: 'The request is not correct'
|
||||
}
|
||||
message: 'The request is not correct',
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@@ -721,7 +717,7 @@ test('5xx response error as HTML', async t => {
|
||||
test('5xx response error with random JSON', async t => {
|
||||
const fn = async (req, res) => {
|
||||
send(res, 500, {
|
||||
wrong: 'property'
|
||||
wrong: 'property',
|
||||
});
|
||||
};
|
||||
|
||||
@@ -733,23 +729,27 @@ test('5xx response error with random JSON', async t => {
|
||||
});
|
||||
|
||||
test('getProjectName with argv - option 1', t => {
|
||||
const project = getProjectName({argv: {
|
||||
name: 'abc'
|
||||
}});
|
||||
const project = getProjectName({
|
||||
argv: {
|
||||
name: 'abc',
|
||||
},
|
||||
});
|
||||
t.is(project, 'abc');
|
||||
});
|
||||
|
||||
test('getProjectName with argv - option 2', t => {
|
||||
const project = getProjectName({argv: {
|
||||
'--name': 'abc'
|
||||
}});
|
||||
const project = getProjectName({
|
||||
argv: {
|
||||
'--name': 'abc',
|
||||
},
|
||||
});
|
||||
t.is(project, 'abc');
|
||||
});
|
||||
|
||||
test('getProjectName with now.json', t => {
|
||||
const project = getProjectName({
|
||||
argv: {},
|
||||
nowConfig: {name: 'abc'}
|
||||
nowConfig: { name: 'abc' },
|
||||
});
|
||||
t.is(project, 'abc');
|
||||
});
|
||||
@@ -758,7 +758,7 @@ test('getProjectName with a file', t => {
|
||||
const project = getProjectName({
|
||||
argv: {},
|
||||
nowConfig: {},
|
||||
isFile: true
|
||||
isFile: true,
|
||||
});
|
||||
t.is(project, 'files');
|
||||
});
|
||||
@@ -767,7 +767,7 @@ test('getProjectName with a multiple files', t => {
|
||||
const project = getProjectName({
|
||||
argv: {},
|
||||
nowConfig: {},
|
||||
paths: ['/tmp/aa/abc.png', '/tmp/aa/bbc.png']
|
||||
paths: ['/tmp/aa/abc.png', '/tmp/aa/bbc.png'],
|
||||
});
|
||||
t.is(project, 'files');
|
||||
});
|
||||
@@ -776,7 +776,7 @@ test('getProjectName with a directory', t => {
|
||||
const project = getProjectName({
|
||||
argv: {},
|
||||
nowConfig: {},
|
||||
paths: ['/tmp/aa']
|
||||
paths: ['/tmp/aa'],
|
||||
});
|
||||
t.is(project, 'aa');
|
||||
});
|
||||
@@ -797,8 +797,8 @@ test('4xx error message with proper message', async t => {
|
||||
const fn = async (req, res) => {
|
||||
send(res, 403, {
|
||||
error: {
|
||||
message: 'This is a test'
|
||||
}
|
||||
message: 'This is a test',
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@@ -813,8 +813,8 @@ test('5xx error message with proper message', async t => {
|
||||
const fn = async (req, res) => {
|
||||
send(res, 500, {
|
||||
error: {
|
||||
message: 'This is a test'
|
||||
}
|
||||
message: 'This is a test',
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@@ -842,8 +842,8 @@ test('4xx response error as correct JSON with more properties', async t => {
|
||||
send(res, 403, {
|
||||
error: {
|
||||
message: 'The request is not correct',
|
||||
additionalProperty: 'test'
|
||||
}
|
||||
additionalProperty: 'test',
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@@ -861,8 +861,8 @@ test('429 response error with retry header', async t => {
|
||||
|
||||
send(res, 429, {
|
||||
error: {
|
||||
message: 'You were rate limited'
|
||||
}
|
||||
message: 'You were rate limited',
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@@ -878,8 +878,8 @@ test('429 response error without retry header', async t => {
|
||||
const fn = async (req, res) => {
|
||||
send(res, 429, {
|
||||
error: {
|
||||
message: 'You were rate limited'
|
||||
}
|
||||
message: 'You were rate limited',
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@@ -891,8 +891,41 @@ test('429 response error without retry header', async t => {
|
||||
t.is(formatted.retryAfter, undefined);
|
||||
});
|
||||
|
||||
test('guess user\'s intention with custom didYouMean', async t => {
|
||||
const examples = ['apollo','create-react-app','docz','gatsby','go','gridsome','html-minifier','mdx-deck','monorepo','nextjs','nextjs-news','nextjs-static','node-server','nodejs','nodejs-canvas-partyparrot','nodejs-coffee','nodejs-express','nodejs-hapi','nodejs-koa','nodejs-koa-ts','nodejs-pdfkit','nuxt-static','optipng','php-7','puppeteer-screenshot','python','redirect','serverless-ssr-reddit','static','vue','vue-ssr','vuepress'];
|
||||
test("guess user's intention with custom didYouMean", async t => {
|
||||
const examples = [
|
||||
'apollo',
|
||||
'create-react-app',
|
||||
'docz',
|
||||
'gatsby',
|
||||
'go',
|
||||
'gridsome',
|
||||
'html-minifier',
|
||||
'mdx-deck',
|
||||
'monorepo',
|
||||
'nextjs',
|
||||
'nextjs-news',
|
||||
'nextjs-static',
|
||||
'node-server',
|
||||
'nodejs',
|
||||
'nodejs-canvas-partyparrot',
|
||||
'nodejs-coffee',
|
||||
'nodejs-express',
|
||||
'nodejs-hapi',
|
||||
'nodejs-koa',
|
||||
'nodejs-koa-ts',
|
||||
'nodejs-pdfkit',
|
||||
'nuxt-static',
|
||||
'optipng',
|
||||
'php-7',
|
||||
'puppeteer-screenshot',
|
||||
'python',
|
||||
'redirect',
|
||||
'serverless-ssr-reddit',
|
||||
'static',
|
||||
'vue',
|
||||
'vue-ssr',
|
||||
'vuepress',
|
||||
];
|
||||
|
||||
t.is(didYouMean('md', examples, 0.7), 'mdx-deck');
|
||||
t.is(didYouMean('koa', examples, 0.7), 'nodejs-koa');
|
||||
@@ -906,16 +939,26 @@ test('check platform version chanage with `preferV2Deployment`', async t => {
|
||||
const pkg = null;
|
||||
const hasDockerfile = false;
|
||||
const hasServerfile = false;
|
||||
const reason = await preferV2Deployment({ localConfig, pkg, hasDockerfile, hasServerfile });
|
||||
t.regex(reason, /Dockerfile/gm);
|
||||
const reason = await preferV2Deployment({
|
||||
localConfig,
|
||||
pkg,
|
||||
hasDockerfile,
|
||||
hasServerfile,
|
||||
});
|
||||
t.regex(reason, /Deploying to Now 2\.0 automatically/gm);
|
||||
}
|
||||
|
||||
{
|
||||
const localConfig = undefined;
|
||||
const pkg = { scripts: { 'start': 'echo hi' } };
|
||||
const pkg = { scripts: { start: 'echo hi' } };
|
||||
const hasDockerfile = false;
|
||||
const hasServerfile = false;
|
||||
const reason = await preferV2Deployment({ localConfig, pkg, hasDockerfile, hasServerfile });
|
||||
const reason = await preferV2Deployment({
|
||||
localConfig,
|
||||
pkg,
|
||||
hasDockerfile,
|
||||
hasServerfile,
|
||||
});
|
||||
t.is(reason, null);
|
||||
}
|
||||
|
||||
@@ -924,16 +967,26 @@ test('check platform version chanage with `preferV2Deployment`', async t => {
|
||||
const pkg = { scripts: { 'now-start': 'echo hi' } };
|
||||
const hasDockerfile = false;
|
||||
const hasServerfile = false;
|
||||
const reason = await preferV2Deployment({ localConfig, pkg, hasDockerfile, hasServerfile });
|
||||
const reason = await preferV2Deployment({
|
||||
localConfig,
|
||||
pkg,
|
||||
hasDockerfile,
|
||||
hasServerfile,
|
||||
});
|
||||
t.is(reason, null);
|
||||
}
|
||||
|
||||
{
|
||||
const localConfig = { 'version': 1 };
|
||||
const localConfig = { version: 1 };
|
||||
const pkg = null;
|
||||
const hasDockerfile = false;
|
||||
const hasServerfile = false;
|
||||
const reason = await preferV2Deployment({ localConfig, pkg, hasDockerfile, hasServerfile });
|
||||
const reason = await preferV2Deployment({
|
||||
localConfig,
|
||||
pkg,
|
||||
hasDockerfile,
|
||||
hasServerfile,
|
||||
});
|
||||
t.is(reason, null);
|
||||
}
|
||||
|
||||
@@ -942,16 +995,26 @@ test('check platform version chanage with `preferV2Deployment`', async t => {
|
||||
const pkg = null;
|
||||
const hasDockerfile = true;
|
||||
const hasServerfile = false;
|
||||
const reason = await preferV2Deployment({ localConfig, pkg, hasDockerfile, hasServerfile });
|
||||
const reason = await preferV2Deployment({
|
||||
localConfig,
|
||||
pkg,
|
||||
hasDockerfile,
|
||||
hasServerfile,
|
||||
});
|
||||
t.is(reason, null);
|
||||
}
|
||||
|
||||
{
|
||||
const localConfig = undefined;
|
||||
const pkg = { scripts: { 'build': 'echo hi' } };
|
||||
const pkg = { scripts: { build: 'echo hi' } };
|
||||
const hasDockerfile = false;
|
||||
const hasServerfile = false;
|
||||
const reason = await preferV2Deployment({ localConfig, pkg, hasDockerfile, hasServerfile });
|
||||
const reason = await preferV2Deployment({
|
||||
localConfig,
|
||||
pkg,
|
||||
hasDockerfile,
|
||||
hasServerfile,
|
||||
});
|
||||
t.regex(reason, /package\.json/gm);
|
||||
}
|
||||
|
||||
@@ -960,7 +1023,12 @@ test('check platform version chanage with `preferV2Deployment`', async t => {
|
||||
const pkg = null;
|
||||
const hasDockerfile = false;
|
||||
const hasServerfile = true;
|
||||
const reason = await preferV2Deployment({ localConfig, pkg, hasDockerfile, hasServerfile });
|
||||
const reason = await preferV2Deployment({
|
||||
localConfig,
|
||||
pkg,
|
||||
hasDockerfile,
|
||||
hasServerfile,
|
||||
});
|
||||
t.is(reason, null);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -70,7 +70,6 @@ Learn more: https://github.com/golang/go/wiki/Modules
|
||||
`);
|
||||
}
|
||||
|
||||
debug('Downloading user files...');
|
||||
const entrypointArr = entrypoint.split(sep);
|
||||
|
||||
// eslint-disable-next-line prefer-const
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@now/go",
|
||||
"version": "1.0.1-canary.0",
|
||||
"version": "1.0.1",
|
||||
"license": "MIT",
|
||||
"main": "./dist/index",
|
||||
"homepage": "https://zeit.co/docs/runtimes#official-runtimes/go",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@now/next",
|
||||
"version": "2.1.1",
|
||||
"version": "2.3.0",
|
||||
"license": "MIT",
|
||||
"main": "./dist/index",
|
||||
"homepage": "https://zeit.co/docs/runtimes#official-runtimes/next-js",
|
||||
|
||||
@@ -10,25 +10,15 @@ process.on('unhandledRejection', err => {
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
async function main(cwd: string) {
|
||||
const next = require(resolveFrom(cwd, 'next'));
|
||||
const app = next({ dev: true, dir: cwd });
|
||||
process.once('message', async ({ dir, runtimeEnv }) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const next = require(resolveFrom(dir, 'next'));
|
||||
const app = next({ dev: true, dir });
|
||||
const handler = app.getRequestHandler();
|
||||
|
||||
const openPort = await getPort({
|
||||
port: [5000, 4000],
|
||||
});
|
||||
|
||||
const [openPort] = await Promise.all([getPort(), app.prepare()]);
|
||||
const url = `http://localhost:${openPort}`;
|
||||
|
||||
// Prepare for incoming requests
|
||||
await app.prepare();
|
||||
|
||||
// The runtime env vars are passed in to `argv[2]`
|
||||
// as a base64-encoded JSON string
|
||||
const runtimeEnv = JSON.parse(
|
||||
Buffer.from(process.argv[2], 'base64').toString()
|
||||
);
|
||||
syncEnvVars(process.env, process.env, runtimeEnv);
|
||||
|
||||
createServer((req, res) => {
|
||||
@@ -39,6 +29,4 @@ async function main(cwd: string) {
|
||||
process.send(url);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
main(process.cwd());
|
||||
});
|
||||
|
||||
@@ -1,15 +1,3 @@
|
||||
import { ChildProcess, fork } from 'child_process';
|
||||
import {
|
||||
pathExists,
|
||||
readFile,
|
||||
unlink as unlinkFile,
|
||||
writeFile,
|
||||
} from 'fs-extra';
|
||||
import os from 'os';
|
||||
import path from 'path';
|
||||
import resolveFrom from 'resolve-from';
|
||||
import semver from 'semver';
|
||||
|
||||
import {
|
||||
BuildOptions,
|
||||
Config,
|
||||
@@ -19,6 +7,7 @@ import {
|
||||
FileBlob,
|
||||
FileFsRef,
|
||||
Files,
|
||||
getLambdaOptionsFromFunction,
|
||||
getNodeVersion,
|
||||
getSpawnOptions,
|
||||
glob,
|
||||
@@ -29,10 +18,24 @@ import {
|
||||
Route,
|
||||
runNpmInstall,
|
||||
runPackageJsonScript,
|
||||
getLambdaOptionsFromFunction,
|
||||
} from '@now/build-utils';
|
||||
import {
|
||||
convertRedirects,
|
||||
convertRewrites,
|
||||
} from '@now/routing-utils/dist/superstatic';
|
||||
import nodeFileTrace, { NodeFileTraceReasons } from '@zeit/node-file-trace';
|
||||
|
||||
import { ChildProcess, fork } from 'child_process';
|
||||
import {
|
||||
lstatSync,
|
||||
pathExists,
|
||||
readFile,
|
||||
unlink as unlinkFile,
|
||||
writeFile,
|
||||
} from 'fs-extra';
|
||||
import os from 'os';
|
||||
import path from 'path';
|
||||
import resolveFrom from 'resolve-from';
|
||||
import semver from 'semver';
|
||||
import createServerlessConfig from './create-serverless-config';
|
||||
import nextLegacyVersions from './legacy-versions';
|
||||
import {
|
||||
@@ -42,10 +45,14 @@ import {
|
||||
excludeFiles,
|
||||
ExperimentalTraceVersion,
|
||||
getDynamicRoutes,
|
||||
getExportIntent,
|
||||
getExportStatus,
|
||||
getNextConfig,
|
||||
getPathsInside,
|
||||
getPrerenderManifest,
|
||||
getRoutes,
|
||||
getRoutesManifest,
|
||||
getSourceFilePathFromPage,
|
||||
isDynamicRoute,
|
||||
normalizePackageJson,
|
||||
normalizePage,
|
||||
@@ -53,15 +60,8 @@ import {
|
||||
stringMap,
|
||||
syncEnvVars,
|
||||
validateEntrypoint,
|
||||
getSourceFilePathFromPage,
|
||||
getRoutesManifest,
|
||||
} from './utils';
|
||||
|
||||
import {
|
||||
convertRedirects,
|
||||
convertRewrites,
|
||||
} from '@now/routing-utils/dist/superstatic';
|
||||
|
||||
interface BuildParamsMeta {
|
||||
isDev: boolean | undefined;
|
||||
env?: EnvConfig;
|
||||
@@ -162,24 +162,20 @@ const name = '[@now/next]';
|
||||
const urls: stringMap = {};
|
||||
|
||||
function startDevServer(entryPath: string, runtimeEnv: EnvConfig) {
|
||||
// The runtime env vars are encoded and passed in as `argv[2]`, so that the
|
||||
// dev-server process can replace them onto `process.env` after the Next.js
|
||||
// "prepare" step
|
||||
const encodedEnv = Buffer.from(JSON.stringify(runtimeEnv)).toString('base64');
|
||||
|
||||
// `env` is omitted since that
|
||||
// makes it default to `process.env`
|
||||
const forked = fork(path.join(__dirname, 'dev-server.js'), [encodedEnv], {
|
||||
// `env` is omitted since that makes it default to `process.env`
|
||||
const forked = fork(path.join(__dirname, 'dev-server.js'), [], {
|
||||
cwd: entryPath,
|
||||
execArgv: [],
|
||||
});
|
||||
|
||||
const getUrl = () =>
|
||||
new Promise<string>((resolve, reject) => {
|
||||
forked.on('message', resolve);
|
||||
forked.on('error', reject);
|
||||
forked.once('message', resolve);
|
||||
forked.once('error', reject);
|
||||
});
|
||||
|
||||
forked.send({ dir: entryPath, runtimeEnv });
|
||||
|
||||
return { forked, getUrl };
|
||||
}
|
||||
|
||||
@@ -201,7 +197,6 @@ export const build = async ({
|
||||
const entryPath = path.join(workPath, entryDirectory);
|
||||
const dotNextStatic = path.join(entryPath, '.next/static');
|
||||
|
||||
debug(`${name} Downloading user files...`);
|
||||
await download(files, workPath, meta);
|
||||
|
||||
const pkg = await readPackageJson(entryPath);
|
||||
@@ -338,6 +333,122 @@ export const build = async ({
|
||||
env.NODE_OPTIONS = `--max_old_space_size=${memoryToConsume}`;
|
||||
await runPackageJsonScript(entryPath, shouldRunScript, { ...spawnOpts, env });
|
||||
|
||||
const routesManifest = await getRoutesManifest(entryPath, realNextVersion);
|
||||
const rewrites: Route[] = [];
|
||||
const redirects: Route[] = [];
|
||||
|
||||
if (routesManifest) {
|
||||
switch (routesManifest.version) {
|
||||
case 1: {
|
||||
redirects.push(...convertRedirects(routesManifest.redirects));
|
||||
rewrites.push(...convertRewrites(routesManifest.rewrites));
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
// update MIN_ROUTES_MANIFEST_VERSION in ./utils.ts
|
||||
throw new Error(
|
||||
'This version of `@now/next` does not support the version of Next.js you are trying to deploy.\n' +
|
||||
'Please upgrade your `@now/next` builder and try again. Contact support if this continues to happen.'
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const exportIntent = await getExportIntent(entryPath);
|
||||
const userExport = await getExportStatus(entryPath);
|
||||
|
||||
if (exportIntent || userExport) {
|
||||
const { trailingSlash = false } = exportIntent || {};
|
||||
|
||||
if (!userExport) {
|
||||
await writePackageJson(entryPath, {
|
||||
...pkg,
|
||||
scripts: {
|
||||
...pkg.scripts,
|
||||
'now-automatic-next-export': `next export --outdir "${path.resolve(
|
||||
entryPath,
|
||||
'out'
|
||||
)}"`,
|
||||
},
|
||||
});
|
||||
|
||||
await runPackageJsonScript(entryPath, 'now-automatic-next-export', {
|
||||
...spawnOpts,
|
||||
env,
|
||||
});
|
||||
}
|
||||
|
||||
const resultingExport = await getExportStatus(entryPath);
|
||||
if (!resultingExport) {
|
||||
throw new Error(
|
||||
'Exporting Next.js app failed. Please check your build logs and contact us if this continues.'
|
||||
);
|
||||
}
|
||||
|
||||
if (resultingExport.success !== true) {
|
||||
throw new Error(
|
||||
'Export of Next.js app failed. Please check your build logs.'
|
||||
);
|
||||
}
|
||||
|
||||
const outDirectory = resultingExport.outDirectory;
|
||||
|
||||
debug(`next export should use trailing slash: ${trailingSlash}`);
|
||||
|
||||
// This handles pages, `public/`, and `static/`.
|
||||
const filesAfterBuild = await glob('**', outDirectory);
|
||||
|
||||
const output: Files = { ...filesAfterBuild };
|
||||
|
||||
// Strip `.html` extensions from build output
|
||||
Object.entries(output)
|
||||
.filter(([name]) => name.endsWith('.html'))
|
||||
.forEach(([name, value]) => {
|
||||
const cleanName = name.slice(0, -5);
|
||||
delete output[name];
|
||||
output[cleanName] = value;
|
||||
if (value.type === 'FileBlob' || value.type === 'FileFsRef') {
|
||||
value.contentType = value.contentType || 'text/html; charset=utf-8';
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
output,
|
||||
routes: [
|
||||
// TODO: low priority: handle trailingSlash
|
||||
|
||||
// redirects take the highest priority
|
||||
...redirects,
|
||||
// Before we handle static files we need to set proper caching headers
|
||||
{
|
||||
// This ensures we only match known emitted-by-Next.js files and not
|
||||
// user-emitted files which may be missing a hash in their filename.
|
||||
src: path.join(
|
||||
'/',
|
||||
entryDirectory,
|
||||
'_next/static/(?:[^/]+/pages|chunks|runtime|css|media)/.+'
|
||||
),
|
||||
// Next.js assets contain a hash or entropy in their filenames, so they
|
||||
// are guaranteed to be unique and cacheable indefinitely.
|
||||
headers: { 'cache-control': 'public,max-age=31536000,immutable' },
|
||||
continue: true,
|
||||
},
|
||||
{
|
||||
src: path.join('/', entryDirectory, '_next(?!/data(?:/|$))(?:/.*)?'),
|
||||
},
|
||||
// Next.js pages, `static/` folder, reserved assets, and `public/`
|
||||
// folder
|
||||
{ handle: 'filesystem' },
|
||||
...rewrites,
|
||||
|
||||
// Dynamic routes
|
||||
// TODO: do we want to do this?: ...dynamicRoutes,
|
||||
],
|
||||
watch: [],
|
||||
childProcesses: [],
|
||||
};
|
||||
}
|
||||
|
||||
if (isLegacy) {
|
||||
debug('Running npm install --production...');
|
||||
await runNpmInstall(
|
||||
@@ -583,9 +694,11 @@ export const build = async ({
|
||||
// Initial files are manually added to the lambda later
|
||||
return;
|
||||
}
|
||||
const { mode } = lstatSync(path.join(workPath, file));
|
||||
|
||||
files[file] = new FileFsRef({
|
||||
fsPath: path.join(workPath, file),
|
||||
mode,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -821,8 +934,6 @@ export const build = async ({
|
||||
let dynamicPrefix = path.join('/', entryDirectory);
|
||||
dynamicPrefix = dynamicPrefix === '/' ? '' : dynamicPrefix;
|
||||
|
||||
const routesManifest = await getRoutesManifest(entryPath, realNextVersion);
|
||||
|
||||
const dynamicRoutes = await getDynamicRoutes(
|
||||
entryPath,
|
||||
entryDirectory,
|
||||
@@ -836,26 +947,6 @@ export const build = async ({
|
||||
})
|
||||
);
|
||||
|
||||
const rewrites: Route[] = [];
|
||||
const redirects: Route[] = [];
|
||||
|
||||
if (routesManifest) {
|
||||
switch (routesManifest.version) {
|
||||
case 1: {
|
||||
redirects.push(...convertRedirects(routesManifest.redirects));
|
||||
rewrites.push(...convertRewrites(routesManifest.rewrites));
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
// update MIN_ROUTES_MANIFEST_VERSION in ./utils.ts
|
||||
throw new Error(
|
||||
'This version of `@now/next` does not support the version of Next.js you are trying to deploy.\n' +
|
||||
'Please upgrade your `@now/next` builder and try again. Contact support if this continues to happen.'
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
output: {
|
||||
...publicDirectoryFiles,
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
streamToBuffer,
|
||||
Lambda,
|
||||
Route,
|
||||
isSymbolicLink,
|
||||
} from '@now/build-utils';
|
||||
|
||||
type stringMap = { [key: string]: string };
|
||||
@@ -340,6 +341,7 @@ export async function getRoutesManifest(
|
||||
);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const routesManifest: RoutesManifest = require(pathRoutesManifest);
|
||||
|
||||
return routesManifest;
|
||||
@@ -454,11 +456,20 @@ function syncEnvVars(base: EnvConfig, removeEnv: EnvConfig, addEnv: EnvConfig) {
|
||||
export const ExperimentalTraceVersion = `9.0.4-canary.1`;
|
||||
|
||||
export type PseudoLayer = {
|
||||
[fileName: string]: {
|
||||
crc32: number;
|
||||
compBuffer: Buffer;
|
||||
uncompressedSize: number;
|
||||
};
|
||||
[fileName: string]: PseudoFile | PseudoSymbolicLink;
|
||||
};
|
||||
|
||||
export type PseudoFile = {
|
||||
isSymlink: false;
|
||||
crc32: number;
|
||||
compBuffer: Buffer;
|
||||
uncompressedSize: number;
|
||||
};
|
||||
|
||||
export type PseudoSymbolicLink = {
|
||||
isSymlink: true;
|
||||
file: FileFsRef;
|
||||
symlinkTarget: string;
|
||||
};
|
||||
|
||||
const compressBuffer = (buf: Buffer): Promise<Buffer> => {
|
||||
@@ -481,13 +492,22 @@ export async function createPseudoLayer(files: {
|
||||
|
||||
for (const fileName of Object.keys(files)) {
|
||||
const file = files[fileName];
|
||||
const origBuffer = await streamToBuffer(file.toStream());
|
||||
const compBuffer = await compressBuffer(origBuffer);
|
||||
pseudoLayer[fileName] = {
|
||||
compBuffer,
|
||||
crc32: crc32.unsigned(origBuffer),
|
||||
uncompressedSize: origBuffer.byteLength,
|
||||
};
|
||||
|
||||
if (isSymbolicLink(file.mode)) {
|
||||
pseudoLayer[fileName] = {
|
||||
file,
|
||||
isSymlink: true,
|
||||
symlinkTarget: await fs.readlink(file.fsPath),
|
||||
} as PseudoSymbolicLink;
|
||||
} else {
|
||||
const origBuffer = await streamToBuffer(file.toStream());
|
||||
const compBuffer = await compressBuffer(origBuffer);
|
||||
pseudoLayer[fileName] = {
|
||||
compBuffer,
|
||||
crc32: crc32.unsigned(origBuffer),
|
||||
uncompressedSize: origBuffer.byteLength,
|
||||
} as PseudoFile;
|
||||
}
|
||||
}
|
||||
|
||||
return pseudoLayer;
|
||||
@@ -520,10 +540,31 @@ export async function createLambdaFromPseudoLayers({
|
||||
const zipFile = new ZipFile();
|
||||
const addedFiles = new Set();
|
||||
|
||||
const names = Object.keys(files).sort();
|
||||
const symlinkTargets = new Map<string, string>();
|
||||
|
||||
for (const name of names) {
|
||||
const file = files[name];
|
||||
if (file.mode && isSymbolicLink(file.mode) && file.type === 'FileFsRef') {
|
||||
const symlinkTarget = await fs.readlink((file as FileFsRef).fsPath);
|
||||
symlinkTargets.set(name, symlinkTarget);
|
||||
}
|
||||
}
|
||||
|
||||
// apply pseudo layers (already compressed objects)
|
||||
for (const layer of layers) {
|
||||
for (const seedKey of Object.keys(layer)) {
|
||||
const { compBuffer, crc32, uncompressedSize } = layer[seedKey];
|
||||
const item = layer[seedKey];
|
||||
|
||||
if (item.isSymlink) {
|
||||
const { symlinkTarget, file } = item;
|
||||
|
||||
zipFile.addBuffer(Buffer.from(symlinkTarget, 'utf8'), seedKey, {
|
||||
mode: file.mode,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
const { compBuffer, crc32, uncompressedSize } = item;
|
||||
|
||||
// @ts-ignore: `addDeflatedBuffer` is a valid function, but missing on the type
|
||||
zipFile.addDeflatedBuffer(compBuffer, seedKey, {
|
||||
@@ -539,8 +580,16 @@ export async function createLambdaFromPseudoLayers({
|
||||
// was already added in a pseudo layer
|
||||
if (addedFiles.has(fileName)) continue;
|
||||
const file = files[fileName];
|
||||
const fileBuffer = await streamToBuffer(file.toStream());
|
||||
zipFile.addBuffer(fileBuffer, fileName);
|
||||
const symlinkTarget = symlinkTargets.get(fileName);
|
||||
|
||||
if (typeof symlinkTarget === 'string') {
|
||||
zipFile.addBuffer(Buffer.from(symlinkTarget, 'utf8'), fileName, {
|
||||
mode: file.mode,
|
||||
});
|
||||
} else {
|
||||
const fileBuffer = await streamToBuffer(file.toStream());
|
||||
zipFile.addBuffer(fileBuffer, fileName);
|
||||
}
|
||||
}
|
||||
zipFile.end();
|
||||
|
||||
@@ -575,6 +624,73 @@ export type NextPrerenderedRoutes = {
|
||||
};
|
||||
};
|
||||
|
||||
export async function getExportIntent(
|
||||
entryPath: string
|
||||
): Promise<false | { trailingSlash: boolean }> {
|
||||
const pathExportMarker = path.join(entryPath, '.next', 'export-marker.json');
|
||||
const hasExportMarker: boolean = await fs
|
||||
.access(pathExportMarker, fs.constants.F_OK)
|
||||
.then(() => true)
|
||||
.catch(() => false);
|
||||
|
||||
if (!hasExportMarker) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const manifest: {
|
||||
version: 1;
|
||||
exportTrailingSlash: boolean;
|
||||
hasExportPathMap: boolean;
|
||||
} = JSON.parse(await fs.readFile(pathExportMarker, 'utf8'));
|
||||
|
||||
switch (manifest.version) {
|
||||
case 1: {
|
||||
if (manifest.hasExportPathMap !== true) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return { trailingSlash: manifest.exportTrailingSlash };
|
||||
}
|
||||
|
||||
default: {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function getExportStatus(
|
||||
entryPath: string
|
||||
): Promise<false | { success: boolean; outDirectory: string }> {
|
||||
const pathExportDetail = path.join(entryPath, '.next', 'export-detail.json');
|
||||
const hasExportDetail: boolean = await fs
|
||||
.access(pathExportDetail, fs.constants.F_OK)
|
||||
.then(() => true)
|
||||
.catch(() => false);
|
||||
|
||||
if (!hasExportDetail) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const manifest: {
|
||||
version: 1;
|
||||
success: boolean;
|
||||
outDirectory: string;
|
||||
} = JSON.parse(await fs.readFile(pathExportDetail, 'utf8'));
|
||||
|
||||
switch (manifest.version) {
|
||||
case 1: {
|
||||
return {
|
||||
success: !!manifest.success,
|
||||
outDirectory: manifest.outDirectory,
|
||||
};
|
||||
}
|
||||
|
||||
default: {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function getPrerenderManifest(
|
||||
entryPath: string
|
||||
): Promise<NextPrerenderedRoutes> {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"next": "^9.1.2-canary.8",
|
||||
"next": "^9.1.6-canary.1",
|
||||
"react": "^16.8.6",
|
||||
"react-dom": "^16.8.6"
|
||||
}
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import React from 'react';
|
||||
|
||||
// eslint-disable-next-line camelcase
|
||||
export async function unstable_getStaticParams() {
|
||||
export async function unstable_getStaticPaths () {
|
||||
return [
|
||||
'/blog/post-1/comment-1',
|
||||
{ post: 'post-2', comment: 'comment-2' },
|
||||
{ params: { post: 'post-2', comment: 'comment-2' } },
|
||||
'/blog/post-1337/comment-1337',
|
||||
];
|
||||
}
|
||||
|
||||
// eslint-disable-next-line camelcase
|
||||
export async function unstable_getStaticProps({ params }) {
|
||||
export async function unstable_getStaticProps ({ params }) {
|
||||
return {
|
||||
props: {
|
||||
post: params.post,
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import React from 'react'
|
||||
|
||||
// eslint-disable-next-line camelcase
|
||||
export async function unstable_getStaticParams () {
|
||||
export async function unstable_getStaticPaths () {
|
||||
return [
|
||||
'/blog/post-1',
|
||||
{ post: 'post-2' },
|
||||
{ params: { post: 'post-2' } },
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
// eslint-disable-next-line camelcase
|
||||
export async function unstable_getStaticProps ({ params }) {
|
||||
if (params.post === 'post-10') {
|
||||
|
||||
6
packages/now-next/test/fixtures/09-yarn-workspaces/.gitignore
vendored
Normal file
6
packages/now-next/test/fixtures/09-yarn-workspaces/.gitignore
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
node_modules
|
||||
.next
|
||||
.env
|
||||
tsconfig.tsbuildinfo
|
||||
.DS_Store
|
||||
*.log
|
||||
5
packages/now-next/test/fixtures/09-yarn-workspaces/lerna.json
vendored
Normal file
5
packages/now-next/test/fixtures/09-yarn-workspaces/lerna.json
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"packages": ["packages/*"],
|
||||
"npmClient": "yarn",
|
||||
"version": "0.0.0"
|
||||
}
|
||||
11
packages/now-next/test/fixtures/09-yarn-workspaces/now.json
vendored
Normal file
11
packages/now-next/test/fixtures/09-yarn-workspaces/now.json
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"version": 2,
|
||||
"builds": [{ "src": "packages/web/next.config.js", "use": "@now/next" }],
|
||||
"routes": [{ "src": "/(.*)", "dest": "/packages/web/$1", "continue": true }],
|
||||
"probes": [
|
||||
{
|
||||
"path": "/",
|
||||
"mustContain": "hello world <!-- -->6"
|
||||
}
|
||||
]
|
||||
}
|
||||
14
packages/now-next/test/fixtures/09-yarn-workspaces/package.json
vendored
Normal file
14
packages/now-next/test/fixtures/09-yarn-workspaces/package.json
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"private": true,
|
||||
"workspaces": {
|
||||
"packages": [
|
||||
"packages/*"
|
||||
]
|
||||
},
|
||||
"scripts": {
|
||||
"postinstall": "lerna run build --scope=@jimmy/common"
|
||||
},
|
||||
"devDependencies": {
|
||||
"lerna": "^3.19.0"
|
||||
}
|
||||
}
|
||||
1
packages/now-next/test/fixtures/09-yarn-workspaces/packages/common/dist/index.d.ts
vendored
Normal file
1
packages/now-next/test/fixtures/09-yarn-workspaces/packages/common/dist/index.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
export declare const add: (a: number, b: number) => number;
|
||||
6
packages/now-next/test/fixtures/09-yarn-workspaces/packages/common/dist/index.js
vendored
Normal file
6
packages/now-next/test/fixtures/09-yarn-workspaces/packages/common/dist/index.js
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
'use strict';
|
||||
Object.defineProperty(exports, '__esModule', { value: true });
|
||||
exports.add = (a, b) => {
|
||||
return a + b;
|
||||
};
|
||||
//# sourceMappingURL=index.js.map
|
||||
1
packages/now-next/test/fixtures/09-yarn-workspaces/packages/common/dist/index.js.map
vendored
Normal file
1
packages/now-next/test/fixtures/09-yarn-workspaces/packages/common/dist/index.js.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;AAAa,QAAA,GAAG,GAAG,CAAC,CAAS,EAAE,CAAS,EAAE,EAAE;IAC1C,OAAO,CAAC,GAAG,CAAC,CAAC;AACf,CAAC,CAAC"}
|
||||
14
packages/now-next/test/fixtures/09-yarn-workspaces/packages/common/package.json
vendored
Normal file
14
packages/now-next/test/fixtures/09-yarn-workspaces/packages/common/package.json
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "@jimmy/common",
|
||||
"version": "1.0.64",
|
||||
"types": "dist/index.d.ts",
|
||||
"main": "dist/index.js",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"watch": "tsc -w",
|
||||
"build": "tsc"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^3.7.3"
|
||||
}
|
||||
}
|
||||
3
packages/now-next/test/fixtures/09-yarn-workspaces/packages/common/src/index.ts
vendored
Normal file
3
packages/now-next/test/fixtures/09-yarn-workspaces/packages/common/src/index.ts
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
export const add = (a: number, b: number) => {
|
||||
return a + b;
|
||||
};
|
||||
32
packages/now-next/test/fixtures/09-yarn-workspaces/packages/common/tsconfig.json
vendored
Normal file
32
packages/now-next/test/fixtures/09-yarn-workspaces/packages/common/tsconfig.json
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"sourceMap": true,
|
||||
"removeComments": true,
|
||||
|
||||
"strict": true,
|
||||
"noImplicitAny": true,
|
||||
"strictNullChecks": true,
|
||||
"strictFunctionTypes": true,
|
||||
"noImplicitThis": true,
|
||||
"alwaysStrict": true,
|
||||
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"skipLibCheck": true,
|
||||
|
||||
"composite": true,
|
||||
"rootDir": "src",
|
||||
"outDir": "dist",
|
||||
"target": "es6",
|
||||
"module": "commonjs",
|
||||
"lib": ["dom", "es2017", "esnext.asynciterable", "es2017.object"],
|
||||
"declaration": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"resolveJsonModule": true
|
||||
},
|
||||
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.json"],
|
||||
"exclude": ["dist"]
|
||||
}
|
||||
2029
packages/now-next/test/fixtures/09-yarn-workspaces/packages/common/tsconfig.tsbuildinfo
vendored
Normal file
2029
packages/now-next/test/fixtures/09-yarn-workspaces/packages/common/tsconfig.tsbuildinfo
vendored
Normal file
File diff suppressed because it is too large
Load Diff
2
packages/now-next/test/fixtures/09-yarn-workspaces/packages/web/next-env.d.ts
vendored
Normal file
2
packages/now-next/test/fixtures/09-yarn-workspaces/packages/web/next-env.d.ts
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
/// <reference types="next" />
|
||||
/// <reference types="next/types/global" />
|
||||
1
packages/now-next/test/fixtures/09-yarn-workspaces/packages/web/next.config.js
vendored
Normal file
1
packages/now-next/test/fixtures/09-yarn-workspaces/packages/web/next.config.js
vendored
Normal file
@@ -0,0 +1 @@
|
||||
module.exports = {};
|
||||
24
packages/now-next/test/fixtures/09-yarn-workspaces/packages/web/package.json
vendored
Normal file
24
packages/now-next/test/fixtures/09-yarn-workspaces/packages/web/package.json
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"name": "@jimmy/web",
|
||||
"version": "1.0.67",
|
||||
"scripts": {
|
||||
"dev": "next",
|
||||
"build": "next build",
|
||||
"start": "next start"
|
||||
},
|
||||
"dependencies": {
|
||||
"@jimmy/common": "^1.0.64",
|
||||
"next": "^9.1.4",
|
||||
"react": "^16.12.0",
|
||||
"react-dom": "^16.12.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/plugin-syntax-class-properties": "^7.7.4",
|
||||
"@types/next": "^9.0.0",
|
||||
"@types/node": "^12.12.14",
|
||||
"@types/react": "^16.9.15",
|
||||
"@types/react-dom": "16.9.4",
|
||||
"typescript": "3.7.3"
|
||||
},
|
||||
"license": "ISC"
|
||||
}
|
||||
58
packages/now-next/test/fixtures/09-yarn-workspaces/packages/web/pages/_app.tsx
vendored
Normal file
58
packages/now-next/test/fixtures/09-yarn-workspaces/packages/web/pages/_app.tsx
vendored
Normal file
@@ -0,0 +1,58 @@
|
||||
import App from "next/app";
|
||||
import React from "react";
|
||||
|
||||
class MyApp extends App<any> {
|
||||
static async getInitialProps() {
|
||||
console.log("i am props");
|
||||
return { q: 5, pageProps: {} };
|
||||
}
|
||||
|
||||
render() {
|
||||
const { Component, pageProps } = this.props;
|
||||
|
||||
return (
|
||||
<>
|
||||
<div>yo</div>
|
||||
<Component {...pageProps} />
|
||||
<style jsx global>
|
||||
{`
|
||||
html {
|
||||
min-height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 40px;
|
||||
}
|
||||
|
||||
body,
|
||||
body > div {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
div,
|
||||
input,
|
||||
form,
|
||||
li,
|
||||
ul {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
`}
|
||||
</style>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default MyApp;
|
||||
5
packages/now-next/test/fixtures/09-yarn-workspaces/packages/web/pages/index.tsx
vendored
Normal file
5
packages/now-next/test/fixtures/09-yarn-workspaces/packages/web/pages/index.tsx
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
import { add } from "@jimmy/common";
|
||||
|
||||
export default () => {
|
||||
return <div>hello world {add(1, 5)}</div>;
|
||||
};
|
||||
26
packages/now-next/test/fixtures/09-yarn-workspaces/packages/web/tsconfig.json
vendored
Normal file
26
packages/now-next/test/fixtures/09-yarn-workspaces/packages/web/tsconfig.json
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "esnext",
|
||||
"module": "esnext",
|
||||
"jsx": "preserve",
|
||||
"lib": ["dom", "es2017"],
|
||||
"baseUrl": ".",
|
||||
"moduleResolution": "node",
|
||||
"strict": true,
|
||||
"allowJs": true,
|
||||
"noEmit": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"isolatedModules": true,
|
||||
"removeComments": false,
|
||||
"preserveConstEnums": true,
|
||||
"sourceMap": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"resolveJsonModule": true
|
||||
},
|
||||
"exclude": ["dist", ".next", "out", "next.config.js"],
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"]
|
||||
}
|
||||
8464
packages/now-next/test/fixtures/09-yarn-workspaces/yarn.lock
vendored
Normal file
8464
packages/now-next/test/fixtures/09-yarn-workspaces/yarn.lock
vendored
Normal file
File diff suppressed because it is too large
Load Diff
5
packages/now-next/test/fixtures/10-export-cache-headers/next.config.js
vendored
Normal file
5
packages/now-next/test/fixtures/10-export-cache-headers/next.config.js
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
module.exports = {
|
||||
generateBuildId() {
|
||||
return 'testing-build-id';
|
||||
},
|
||||
};
|
||||
16
packages/now-next/test/fixtures/10-export-cache-headers/now.json
vendored
Normal file
16
packages/now-next/test/fixtures/10-export-cache-headers/now.json
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"version": 2,
|
||||
"builds": [{ "src": "package.json", "use": "@now/next" }],
|
||||
"probes": [
|
||||
{
|
||||
"path": "/_next/static/testing-build-id/pages/index.js",
|
||||
"responseHeaders": {
|
||||
"cache-control": "public,max-age=31536000,immutable"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "/",
|
||||
"mustContain": "nextExport\":true"
|
||||
}
|
||||
]
|
||||
}
|
||||
10
packages/now-next/test/fixtures/10-export-cache-headers/package.json
vendored
Normal file
10
packages/now-next/test/fixtures/10-export-cache-headers/package.json
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"scripts": {
|
||||
"build": "next build && next export"
|
||||
},
|
||||
"dependencies": {
|
||||
"next": "canary",
|
||||
"react": "^16.8.6",
|
||||
"react-dom": "^16.8.6"
|
||||
}
|
||||
}
|
||||
5
packages/now-next/test/fixtures/10-export-cache-headers/pages/index.js
vendored
Normal file
5
packages/now-next/test/fixtures/10-export-cache-headers/pages/index.js
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
const Page = () => 'Hi';
|
||||
|
||||
Page.getInitialProps = () => ({ hello: 'world' });
|
||||
|
||||
export default Page;
|
||||
5
packages/now-next/test/fixtures/11-export-clean-urls/next.config.js
vendored
Normal file
5
packages/now-next/test/fixtures/11-export-clean-urls/next.config.js
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
module.exports = {
|
||||
generateBuildId() {
|
||||
return 'testing-build-id';
|
||||
},
|
||||
};
|
||||
18
packages/now-next/test/fixtures/11-export-clean-urls/now.json
vendored
Normal file
18
packages/now-next/test/fixtures/11-export-clean-urls/now.json
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"version": 2,
|
||||
"builds": [{ "src": "package.json", "use": "@now/next" }],
|
||||
"probes": [
|
||||
{
|
||||
"path": "/",
|
||||
"mustContain": "Hi There"
|
||||
},
|
||||
{
|
||||
"path": "/about",
|
||||
"mustContain": "Hi on About"
|
||||
},
|
||||
{
|
||||
"path": "/",
|
||||
"mustContain": "nextExport\":true"
|
||||
}
|
||||
]
|
||||
}
|
||||
10
packages/now-next/test/fixtures/11-export-clean-urls/package.json
vendored
Normal file
10
packages/now-next/test/fixtures/11-export-clean-urls/package.json
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"scripts": {
|
||||
"build": "next build && next export"
|
||||
},
|
||||
"dependencies": {
|
||||
"next": "canary",
|
||||
"react": "^16.8.6",
|
||||
"react-dom": "^16.8.6"
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user