Compare commits

...

9 Commits

Author SHA1 Message Date
Andy Bitz
792ab38760 Publish Canary
- @vercel/build-utils@2.12.3-canary.28
 - vercel@23.1.3-canary.47
 - @vercel/client@10.2.3-canary.29
 - vercel-plugin-go@1.0.0-canary.13
 - vercel-plugin-node@1.12.2-canary.19
 - vercel-plugin-python@1.0.0-canary.14
 - vercel-plugin-ruby@1.0.0-canary.12
 - @vercel/ruby@1.2.8-canary.6
2021-12-01 15:32:05 +01:00
Leo Lamprecht
0bba3e76c1 Corrected dependency installation systems (#7088)
* Avoid unnecessary Gemfile installations

* Do not bundle `.output` or `.vercel` into Lambdas

* Use the same input hash format for all CLI Plugins and `vercel build`

* Fixed unit tests

* Fixed the unit tests again

* Fixed the unit tests

* Fixed all the tests

* Exclude useless files

* Consider `.vercelignore` and `.nowignore`

* Fixed error

* Reverted changes

* Deleted useless file

* Fixed tests

* Share input hash format with `vercel-plugin-node`

* Make output inspectable

* Fixed build error

* Extended comment

* Bump Ruby version

* Update Gemfiles

* Update bundles

* Fixed tests

Co-authored-by: Andy Bitz <artzbitz@gmail.com>
2021-12-01 15:31:27 +01:00
Andy Bitz
3d961ffbb9 Publish Canary
- vercel@23.1.3-canary.46
2021-11-30 19:25:13 +01:00
Andy
a3039f57bb [cli] Fix vc build with nested app dir (#7083)
* [cli] Fix `vc build` with nested app dir

* Set NEXT_PRIVATE_OUTPUT_TRACE_ROOT
2021-11-30 18:30:51 +01:00
Andy Bitz
5499fa9a04 Publish Canary
- @vercel/build-utils@2.12.3-canary.27
 - vercel@23.1.3-canary.45
 - @vercel/client@10.2.3-canary.28
 - vercel-plugin-go@1.0.0-canary.12
 - vercel-plugin-node@1.12.2-canary.18
 - vercel-plugin-python@1.0.0-canary.13
 - vercel-plugin-ruby@1.0.0-canary.11
2021-11-30 16:17:47 +01:00
Leo Lamprecht
b9fd64faff Stop passing functions and regions to Runtimes (#7085)
* Stop passing `functions` and `regions` to Runtimes

* Added required types back

* Removed `vercelConfig` from `vercel-plugin-node`

* Fixed unit test

* Fixed test
2021-11-30 16:16:53 +01:00
Andy Bitz
1202ff7b2b Publish Canary
- @vercel/build-utils@2.12.3-canary.26
 - vercel@23.1.3-canary.44
 - @vercel/client@10.2.3-canary.27
 - @vercel/frameworks@0.5.1-canary.16
 - vercel-plugin-go@1.0.0-canary.11
 - vercel-plugin-node@1.12.2-canary.17
 - vercel-plugin-python@1.0.0-canary.12
 - vercel-plugin-ruby@1.0.0-canary.10
 - @vercel/python@2.1.2-canary.1
 - @vercel/ruby@1.2.8-canary.5
2021-11-30 14:37:53 +01:00
Leo Lamprecht
abd9f019f1 Don't install Ruby or Python dependencies unnecessarily (#7084)
* Don't install Ruby or Python dependencies unnecessarily

* Less repeated code

* Cleaner code
2021-11-30 14:36:27 +01:00
Leo Lamprecht
edb5eead81 Speed up Remix Template (#7077)
* Replaced Remix Template

* Added npm lockfile for Remix Template

* Added npm lockfile for Next.js Template

* Added Remix logo
2021-11-29 16:01:23 +01:00
113 changed files with 24483 additions and 3395 deletions

15787
examples/nextjs/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -5,4 +5,4 @@ node_modules
.output .output
public/build public/build
api/build api/_build

View File

@@ -1,5 +1,5 @@
const { createRequestHandler } = require("@remix-run/vercel"); const { createRequestHandler } = require("@remix-run/vercel");
module.exports = createRequestHandler({ module.exports = createRequestHandler({
build: require("./build") build: require("./_build")
}); });

View File

@@ -1,120 +0,0 @@
/*
* You probably want to just delete this file; it's just for the demo pages.
*/
.remix-app {
display: flex;
flex-direction: column;
min-height: 100vh;
min-height: calc(100vh - env(safe-area-inset-bottom));
}
.remix-app > * {
width: 100%;
}
.remix-app__header {
padding-top: 1rem;
padding-bottom: 1rem;
border-bottom: 1px solid var(--color-border);
}
.remix-app__header-content {
display: flex;
justify-content: space-between;
align-items: center;
}
.remix-app__header-home-link {
width: 106px;
height: 30px;
color: var(--color-foreground);
}
.remix-app__header-nav ul {
list-style: none;
margin: 0;
display: flex;
align-items: center;
gap: 1.5em;
}
.remix-app__header-nav li {
font-weight: bold;
}
.remix-app__main {
flex: 1 1 100%;
}
.remix-app__footer {
padding-top: 1rem;
padding-bottom: 1rem;
border-top: 1px solid var(--color-border);
}
.remix-app__footer-content {
display: flex;
justify-content: center;
align-items: center;
}
.remix__page {
--gap: 1rem;
--space: 2rem;
display: grid;
grid-auto-rows: min-content;
gap: var(--gap);
padding-top: var(--space);
padding-bottom: var(--space);
}
@media print, screen and (min-width: 640px) {
.remix__page {
--gap: 2rem;
grid-auto-rows: unset;
grid-template-columns: repeat(2, 1fr);
}
}
@media screen and (min-width: 1024px) {
.remix__page {
--gap: 4rem;
}
}
.remix__page > main > :first-child {
margin-top: 0;
}
.remix__page > main > :last-child {
margin-bottom: 0;
}
.remix__page > aside {
margin: 0;
padding: 1.5ch 2ch;
border: solid 1px var(--color-border);
border-radius: 0.5rem;
}
.remix__page > aside > :first-child {
margin-top: 0;
}
.remix__page > aside > :last-child {
margin-bottom: 0;
}
.remix__form {
display: flex;
flex-direction: column;
gap: 1rem;
padding: 1rem;
border: 1px solid var(--color-border);
border-radius: 0.5rem;
}
.remix__form > * {
margin-top: 0;
margin-bottom: 0;
}

8345
examples/remix/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -9,15 +9,15 @@
"postinstall": "remix setup node" "postinstall": "remix setup node"
}, },
"dependencies": { "dependencies": {
"@remix-run/react": "^1.0.5", "@remix-run/react": "^1.0.6",
"react": "^17.0.2", "react": "^17.0.2",
"react-dom": "^17.0.2", "react-dom": "^17.0.2",
"remix": "^1.0.5", "remix": "^1.0.6",
"@remix-run/serve": "^1.0.5", "@remix-run/serve": "^1.0.6",
"@remix-run/vercel": "^1.0.5" "@remix-run/vercel": "^1.0.6"
}, },
"devDependencies": { "devDependencies": {
"@remix-run/dev": "^1.0.5", "@remix-run/dev": "^1.0.6",
"@types/react": "^17.0.24", "@types/react": "^17.0.24",
"@types/react-dom": "^17.0.9", "@types/react-dom": "^17.0.9",
"typescript": "^4.1.2" "typescript": "^4.1.2"

View File

@@ -5,5 +5,5 @@ module.exports = {
appDirectory: "app", appDirectory: "app",
browserBuildDirectory: "public/build", browserBuildDirectory: "public/build",
publicPath: "/build/", publicPath: "/build/",
serverBuildDirectory: "api/build" serverBuildDirectory: "api/_build"
}; };

View File

@@ -1,6 +1,6 @@
{ {
"name": "@vercel/build-utils", "name": "@vercel/build-utils",
"version": "2.12.3-canary.25", "version": "2.12.3-canary.28",
"license": "MIT", "license": "MIT",
"main": "./dist/index.js", "main": "./dist/index.js",
"types": "./dist/index.d.js", "types": "./dist/index.d.js",
@@ -30,7 +30,7 @@
"@types/node-fetch": "^2.1.6", "@types/node-fetch": "^2.1.6",
"@types/semver": "6.0.0", "@types/semver": "6.0.0",
"@types/yazl": "^2.4.1", "@types/yazl": "^2.4.1",
"@vercel/frameworks": "0.5.1-canary.15", "@vercel/frameworks": "0.5.1-canary.16",
"@vercel/ncc": "0.24.0", "@vercel/ncc": "0.24.0",
"aggregate-error": "3.0.1", "aggregate-error": "3.0.1",
"async-retry": "1.2.3", "async-retry": "1.2.3",

View File

@@ -2,55 +2,78 @@ import fs from 'fs-extra';
import { join, dirname, relative } from 'path'; import { join, dirname, relative } from 'path';
import glob from './fs/glob'; import glob from './fs/glob';
import { normalizePath } from './fs/normalize-path'; import { normalizePath } from './fs/normalize-path';
import { FILES_SYMBOL, getLambdaOptionsFromFunction, Lambda } from './lambda'; import { FILES_SYMBOL, Lambda } from './lambda';
import type FileBlob from './file-blob'; import type FileBlob from './file-blob';
import type { BuilderFunctions, BuildOptions, Files } from './types'; import type { BuildOptions, Files } from './types';
import minimatch from 'minimatch'; import { getIgnoreFilter } from '.';
/** /**
* Convert legacy Runtime to a Plugin. * Convert legacy Runtime to a Plugin.
* @param buildRuntime - a legacy build() function from a Runtime * @param buildRuntime - a legacy build() function from a Runtime
* @param packageName - the name of the package, for example `vercel-plugin-python`
* @param ext - the file extension, for example `.py` * @param ext - the file extension, for example `.py`
*/ */
export function convertRuntimeToPlugin( export function convertRuntimeToPlugin(
buildRuntime: (options: BuildOptions) => Promise<{ output: Lambda }>, buildRuntime: (options: BuildOptions) => Promise<{ output: Lambda }>,
packageName: string,
ext: string ext: string
) { ) {
// This `build()` signature should match `plugin.build()` signature in `vercel build`. // This `build()` signature should match `plugin.build()` signature in `vercel build`.
return async function build({ return async function build({ workPath }: { workPath: string }) {
vercelConfig,
workPath,
}: {
vercelConfig: {
functions?: BuilderFunctions;
regions?: string[];
};
workPath: string;
}) {
const opts = { cwd: workPath }; const opts = { cwd: workPath };
const files = await glob('**', opts); const files = await glob('**', opts);
delete files['vercel.json']; // Builders/Runtimes didn't have vercel.json
const entrypoints = await glob(`api/**/*${ext}`, opts); // `.output` was already created by the Build Command, so we have
// to ensure its contents don't get bundled into the Lambda. Similarily,
// we don't want to bundle anything from `.vercel` either. Lastly,
// Builders/Runtimes didn't have `vercel.json` or `now.json`.
const ignoredPaths = ['.output', '.vercel', 'vercel.json', 'now.json'];
// We also don't want to provide any files to Runtimes that were ignored
// through `.vercelignore` or `.nowignore`, because the Build Step does the same.
const ignoreFilter = await getIgnoreFilter(workPath);
// We're not passing this as an `ignore` filter to the `glob` function above,
// so that we can re-use exactly the same `getIgnoreFilter` method that the
// Build Step uses (literally the same code).
for (const file in files) {
const isNative = ignoredPaths.some(item => {
return file.startsWith(item);
});
if (isNative || ignoreFilter(file)) {
delete files[file];
}
}
const entrypointPattern = `api/**/*${ext}`;
const entrypoints = await glob(entrypointPattern, opts);
const pages: { [key: string]: any } = {}; const pages: { [key: string]: any } = {};
const { functions = {} } = vercelConfig; const pluginName = packageName.replace('vercel-plugin-', '');
const traceDir = join(workPath, '.output', 'runtime-traced-files');
const traceDir = join(
workPath,
`.output`,
`inputs`,
// Legacy Runtimes can only provide API Routes, so that's
// why we can use this prefix for all of them. Here, we have to
// make sure to not use a cryptic hash name, because people
// need to be able to easily inspect the output.
`api-routes-${pluginName}`
);
await fs.ensureDir(traceDir); await fs.ensureDir(traceDir);
for (const entrypoint of Object.keys(entrypoints)) { for (const entrypoint of Object.keys(entrypoints)) {
const key =
Object.keys(functions).find(
src => src === entrypoint || minimatch(entrypoint, src)
) || '';
const config = functions[key] || {};
const { output } = await buildRuntime({ const { output } = await buildRuntime({
files, files,
entrypoint, entrypoint,
workPath, workPath,
config: { config: {
zeroConfig: true, zeroConfig: true,
includeFiles: config.includeFiles, },
excludeFiles: config.excludeFiles, meta: {
avoidTopLevelInstall: true,
}, },
}); });
@@ -61,7 +84,6 @@ export function convertRuntimeToPlugin(
maxDuration: output.maxDuration, maxDuration: output.maxDuration,
environment: output.environment, environment: output.environment,
allowQuery: output.allowQuery, allowQuery: output.allowQuery,
//regions: output.regions,
}; };
// @ts-ignore This symbol is a private API // @ts-ignore This symbol is a private API
@@ -108,7 +130,7 @@ export function convertRuntimeToPlugin(
await fs.writeFile(nft, json); await fs.writeFile(nft, json);
} }
await updateFunctionsManifest({ vercelConfig, workPath, pages }); await updateFunctionsManifest({ workPath, pages });
}; };
} }
@@ -136,15 +158,12 @@ async function readJson(filePath: string): Promise<{ [key: string]: any }> {
/** /**
* If `.output/functions-manifest.json` exists, append to the pages * If `.output/functions-manifest.json` exists, append to the pages
* property. Otherwise write a new file. This will also read `vercel.json` * property. Otherwise write a new file.
* and apply relevant `functions` property config.
*/ */
export async function updateFunctionsManifest({ export async function updateFunctionsManifest({
vercelConfig,
workPath, workPath,
pages, pages,
}: { }: {
vercelConfig: { functions?: BuilderFunctions; regions?: string[] };
workPath: string; workPath: string;
pages: { [key: string]: any }; pages: { [key: string]: any };
}) { }) {
@@ -159,16 +178,7 @@ export async function updateFunctionsManifest({
if (!functionsManifest.pages) functionsManifest.pages = {}; if (!functionsManifest.pages) functionsManifest.pages = {};
for (const [pageKey, pageConfig] of Object.entries(pages)) { for (const [pageKey, pageConfig] of Object.entries(pages)) {
const fnConfig = await getLambdaOptionsFromFunction({ functionsManifest.pages[pageKey] = { ...pageConfig };
sourceFile: pageKey,
config: vercelConfig,
});
functionsManifest.pages[pageKey] = {
...pageConfig,
memory: fnConfig.memory || pageConfig.memory,
maxDuration: fnConfig.maxDuration || pageConfig.maxDuration,
regions: vercelConfig.regions || pageConfig.regions,
};
} }
await fs.writeFile(functionsManifestPath, JSON.stringify(functionsManifest)); await fs.writeFile(functionsManifestPath, JSON.stringify(functionsManifest));

View File

@@ -0,0 +1,84 @@
import path from 'path';
import fs from 'fs-extra';
import ignore from 'ignore';
interface CodedError extends Error {
code: string;
}
function isCodedError(error: unknown): error is CodedError {
return (
error !== null &&
error !== undefined &&
(error as CodedError).code !== undefined
);
}
function clearRelative(s: string) {
return s.replace(/(\n|^)\.\//g, '$1');
}
export default async function (
downloadPath: string,
rootDirectory?: string | undefined
) {
const readFile = async (p: string) => {
try {
return await fs.readFile(p, 'utf8');
} catch (error: any) {
if (
error.code === 'ENOENT' ||
(error instanceof Error && error.message.includes('ENOENT'))
) {
return undefined;
}
throw error;
}
};
const vercelIgnorePath = path.join(
downloadPath,
rootDirectory || '',
'.vercelignore'
);
const nowIgnorePath = path.join(
downloadPath,
rootDirectory || '',
'.nowignore'
);
const ignoreContents = [];
try {
ignoreContents.push(
...(
await Promise.all([readFile(vercelIgnorePath), readFile(nowIgnorePath)])
).filter(Boolean)
);
} catch (error) {
if (isCodedError(error) && error.code === 'ENOTDIR') {
console.log(`Warning: Cannot read ignore file from ${vercelIgnorePath}`);
} else {
throw error;
}
}
if (ignoreContents.length === 2) {
throw new Error(
'Cannot use both a `.vercelignore` and `.nowignore` file. Please delete the `.nowignore` file.'
);
}
if (ignoreContents.length === 0) {
return () => false;
}
const ignoreFilter: any = ignore().add(clearRelative(ignoreContents[0]!));
return function (p: string) {
// we should not ignore now.json and vercel.json if it asked to.
// we depend on these files for building the app with sourceless
if (p === 'now.json' || p === 'vercel.json') return false;
return ignoreFilter.test(p).ignored;
};
}

View File

@@ -1,3 +1,4 @@
import { createHash } from 'crypto';
import FileBlob from './file-blob'; import FileBlob from './file-blob';
import FileFsRef from './file-fs-ref'; import FileFsRef from './file-fs-ref';
import FileRef from './file-ref'; import FileRef from './file-ref';
@@ -33,6 +34,7 @@ import { NowBuildError } from './errors';
import streamToBuffer from './fs/stream-to-buffer'; import streamToBuffer from './fs/stream-to-buffer';
import shouldServe from './should-serve'; import shouldServe from './should-serve';
import debug from './debug'; import debug from './debug';
import getIgnoreFilter from './get-ignore-filter';
export { export {
FileBlob, FileBlob,
@@ -70,6 +72,7 @@ export {
isSymbolicLink, isSymbolicLink,
getLambdaOptionsFromFunction, getLambdaOptionsFromFunction,
scanParentDirs, scanParentDirs,
getIgnoreFilter,
}; };
export { export {
@@ -132,3 +135,11 @@ export const getPlatformEnv = (name: string): string | undefined => {
} }
return n; return n;
}; };
/**
* Helper function for generating file or directories names in `.output/inputs`
* for dependencies of files provided to the File System API.
*/
export const getInputHash = (source: Buffer | string): string => {
return createHash('sha1').update(source).digest('hex');
};

View File

@@ -58,6 +58,7 @@ export interface Meta {
filesRemoved?: string[]; filesRemoved?: string[];
env?: Env; env?: Env;
buildEnv?: Env; buildEnv?: Env;
avoidTopLevelInstall?: boolean;
} }
export interface AnalyzeOptions { export interface AnalyzeOptions {

View File

@@ -50,16 +50,21 @@ describe('convert-runtime-to-plugin', () => {
}; };
const lambdaFiles = await fsToJson(workPath); const lambdaFiles = await fsToJson(workPath);
const vercelConfig = JSON.parse(lambdaFiles['vercel.json']);
delete lambdaFiles['vercel.json']; delete lambdaFiles['vercel.json'];
const build = await convertRuntimeToPlugin(buildRuntime, '.py');
await build({ vercelConfig, workPath }); const ext = '.py';
const packageName = 'vercel-plugin-python';
const build = await convertRuntimeToPlugin(buildRuntime, packageName, ext);
await build({ workPath });
const output = await fsToJson(join(workPath, '.output')); const output = await fsToJson(join(workPath, '.output'));
expect(output).toMatchObject({ expect(output).toMatchObject({
'functions-manifest.json': expect.stringContaining('{'), 'functions-manifest.json': expect.stringContaining('{'),
'runtime-traced-files': lambdaFiles, inputs: {
'api-routes-python': lambdaFiles,
},
server: { server: {
pages: { pages: {
api: { api: {
@@ -82,7 +87,7 @@ describe('convert-runtime-to-plugin', () => {
pages: { pages: {
'api/index.py': lambdaOptions, 'api/index.py': lambdaOptions,
'api/users/get.py': lambdaOptions, 'api/users/get.py': lambdaOptions,
'api/users/post.py': { ...lambdaOptions, memory: 3008 }, 'api/users/post.py': { ...lambdaOptions, memory: 512 },
}, },
}); });
@@ -91,36 +96,35 @@ describe('convert-runtime-to-plugin', () => {
version: 1, version: 1,
files: [ files: [
{ {
input: '../../../../runtime-traced-files/api/db/[id].py', input: `../../../../inputs/api-routes-python/api/db/[id].py`,
output: 'api/db/[id].py', output: 'api/db/[id].py',
}, },
{ {
input: '../../../../runtime-traced-files/api/index.py', input: `../../../../inputs/api-routes-python/api/index.py`,
output: 'api/index.py', output: 'api/index.py',
}, },
{ {
input: input: `../../../../inputs/api-routes-python/api/project/[aid]/[bid]/index.py`,
'../../../../runtime-traced-files/api/project/[aid]/[bid]/index.py',
output: 'api/project/[aid]/[bid]/index.py', output: 'api/project/[aid]/[bid]/index.py',
}, },
{ {
input: '../../../../runtime-traced-files/api/users/get.py', input: `../../../../inputs/api-routes-python/api/users/get.py`,
output: 'api/users/get.py', output: 'api/users/get.py',
}, },
{ {
input: '../../../../runtime-traced-files/api/users/post.py', input: `../../../../inputs/api-routes-python/api/users/post.py`,
output: 'api/users/post.py', output: 'api/users/post.py',
}, },
{ {
input: '../../../../runtime-traced-files/file.txt', input: `../../../../inputs/api-routes-python/file.txt`,
output: 'file.txt', output: 'file.txt',
}, },
{ {
input: '../../../../runtime-traced-files/util/date.py', input: `../../../../inputs/api-routes-python/util/date.py`,
output: 'util/date.py', output: 'util/date.py',
}, },
{ {
input: '../../../../runtime-traced-files/util/math.py', input: `../../../../inputs/api-routes-python/util/math.py`,
output: 'util/math.py', output: 'util/math.py',
}, },
], ],
@@ -133,36 +137,35 @@ describe('convert-runtime-to-plugin', () => {
version: 1, version: 1,
files: [ files: [
{ {
input: '../../../../../runtime-traced-files/api/db/[id].py', input: `../../../../../inputs/api-routes-python/api/db/[id].py`,
output: 'api/db/[id].py', output: 'api/db/[id].py',
}, },
{ {
input: '../../../../../runtime-traced-files/api/index.py', input: `../../../../../inputs/api-routes-python/api/index.py`,
output: 'api/index.py', output: 'api/index.py',
}, },
{ {
input: input: `../../../../../inputs/api-routes-python/api/project/[aid]/[bid]/index.py`,
'../../../../../runtime-traced-files/api/project/[aid]/[bid]/index.py',
output: 'api/project/[aid]/[bid]/index.py', output: 'api/project/[aid]/[bid]/index.py',
}, },
{ {
input: '../../../../../runtime-traced-files/api/users/get.py', input: `../../../../../inputs/api-routes-python/api/users/get.py`,
output: 'api/users/get.py', output: 'api/users/get.py',
}, },
{ {
input: '../../../../../runtime-traced-files/api/users/post.py', input: `../../../../../inputs/api-routes-python/api/users/post.py`,
output: 'api/users/post.py', output: 'api/users/post.py',
}, },
{ {
input: '../../../../../runtime-traced-files/file.txt', input: `../../../../../inputs/api-routes-python/file.txt`,
output: 'file.txt', output: 'file.txt',
}, },
{ {
input: '../../../../../runtime-traced-files/util/date.py', input: `../../../../../inputs/api-routes-python/util/date.py`,
output: 'util/date.py', output: 'util/date.py',
}, },
{ {
input: '../../../../../runtime-traced-files/util/math.py', input: `../../../../../inputs/api-routes-python/util/math.py`,
output: 'util/math.py', output: 'util/math.py',
}, },
], ],
@@ -175,36 +178,35 @@ describe('convert-runtime-to-plugin', () => {
version: 1, version: 1,
files: [ files: [
{ {
input: '../../../../../runtime-traced-files/api/db/[id].py', input: `../../../../../inputs/api-routes-python/api/db/[id].py`,
output: 'api/db/[id].py', output: 'api/db/[id].py',
}, },
{ {
input: '../../../../../runtime-traced-files/api/index.py', input: `../../../../../inputs/api-routes-python/api/index.py`,
output: 'api/index.py', output: 'api/index.py',
}, },
{ {
input: input: `../../../../../inputs/api-routes-python/api/project/[aid]/[bid]/index.py`,
'../../../../../runtime-traced-files/api/project/[aid]/[bid]/index.py',
output: 'api/project/[aid]/[bid]/index.py', output: 'api/project/[aid]/[bid]/index.py',
}, },
{ {
input: '../../../../../runtime-traced-files/api/users/get.py', input: `../../../../../inputs/api-routes-python/api/users/get.py`,
output: 'api/users/get.py', output: 'api/users/get.py',
}, },
{ {
input: '../../../../../runtime-traced-files/api/users/post.py', input: `../../../../../inputs/api-routes-python/api/users/post.py`,
output: 'api/users/post.py', output: 'api/users/post.py',
}, },
{ {
input: '../../../../../runtime-traced-files/file.txt', input: `../../../../../inputs/api-routes-python/file.txt`,
output: 'file.txt', output: 'file.txt',
}, },
{ {
input: '../../../../../runtime-traced-files/util/date.py', input: `../../../../../inputs/api-routes-python/util/date.py`,
output: 'util/date.py', output: 'util/date.py',
}, },
{ {
input: '../../../../../runtime-traced-files/util/math.py', input: `../../../../../inputs/api-routes-python/util/math.py`,
output: 'util/math.py', output: 'util/math.py',
}, },
], ],

View File

@@ -1,6 +1,6 @@
{ {
"name": "vercel", "name": "vercel",
"version": "23.1.3-canary.43", "version": "23.1.3-canary.47",
"preferGlobal": true, "preferGlobal": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"description": "The command-line interface for Vercel", "description": "The command-line interface for Vercel",
@@ -43,14 +43,14 @@
"node": ">= 12" "node": ">= 12"
}, },
"dependencies": { "dependencies": {
"@vercel/build-utils": "2.12.3-canary.25", "@vercel/build-utils": "2.12.3-canary.28",
"@vercel/go": "1.2.4-canary.4", "@vercel/go": "1.2.4-canary.4",
"@vercel/node": "1.12.2-canary.7", "@vercel/node": "1.12.2-canary.7",
"@vercel/python": "2.1.2-canary.0", "@vercel/python": "2.1.2-canary.1",
"@vercel/ruby": "1.2.8-canary.4", "@vercel/ruby": "1.2.8-canary.6",
"update-notifier": "4.1.0", "update-notifier": "4.1.0",
"vercel-plugin-middleware": "0.0.0-canary.7", "vercel-plugin-middleware": "0.0.0-canary.7",
"vercel-plugin-node": "1.12.2-canary.16" "vercel-plugin-node": "1.12.2-canary.19"
}, },
"devDependencies": { "devDependencies": {
"@next/env": "11.1.2", "@next/env": "11.1.2",
@@ -90,7 +90,7 @@
"@types/update-notifier": "5.1.0", "@types/update-notifier": "5.1.0",
"@types/which": "1.3.2", "@types/which": "1.3.2",
"@types/write-json-file": "2.2.1", "@types/write-json-file": "2.2.1",
"@vercel/frameworks": "0.5.1-canary.15", "@vercel/frameworks": "0.5.1-canary.16",
"@vercel/ncc": "0.24.0", "@vercel/ncc": "0.24.0",
"@vercel/nft": "0.17.0", "@vercel/nft": "0.17.0",
"@zeit/fun": "0.11.2", "@zeit/fun": "0.11.2",

View File

@@ -298,6 +298,9 @@ export default async function main(client: Client) {
} }
} }
// Required for Next.js to produce the correct `.nft.json` files.
spawnOpts.env.NEXT_PRIVATE_OUTPUT_TRACE_ROOT = baseDir;
// Yarn v2 PnP mode may be activated, so force // Yarn v2 PnP mode may be activated, so force
// "node-modules" linker style // "node-modules" linker style
const env = { const env = {
@@ -621,16 +624,18 @@ export default async function main(client: Client) {
...requiredServerFilesJson, ...requiredServerFilesJson,
appDir: '.', appDir: '.',
files: requiredServerFilesJson.files.map((i: string) => { files: requiredServerFilesJson.files.map((i: string) => {
const originalPath = join(dirname(distDir), i); const originalPath = join(requiredServerFilesJson.appDir, i);
const relPath = join(OUTPUT_DIR, relative(distDir, originalPath)); const relPath = join(OUTPUT_DIR, relative(distDir, originalPath));
const absolutePath = join(cwd, relPath); const absolutePath = join(cwd, relPath);
const output = relative(baseDir, absolutePath); const output = relative(baseDir, absolutePath);
return { return relPath === output
input: relPath, ? relPath
output, : {
}; input: relPath,
output,
};
}), }),
}); });
} }

View File

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

View File

@@ -0,0 +1,6 @@
<svg viewBox="0 0 800 800" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M700 0H100C44.772 0 0 44.772 0 100v600c0 55.228 44.772 100 100 100h600c55.228 0 100-44.772 100-100V100C800 44.772 755.228 0 700 0Z" fill="#212121"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M587.947 527.768c4.254 54.65 4.254 80.268 4.254 108.232H465.756c0-6.091.109-11.663.219-17.313.342-17.564.699-35.88-2.147-72.868-3.761-54.152-27.08-66.185-69.957-66.185H195v-98.525h204.889c54.16 0 81.241-16.476 81.241-60.098 0-38.357-27.081-61.601-81.241-61.601H195V163h227.456C545.069 163 606 220.912 606 313.42c0 69.193-42.877 114.319-100.799 121.84 48.895 9.777 77.48 37.605 82.746 92.508Z" fill="#fff"/>
<path d="M195 636v-73.447h133.697c22.332 0 27.181 16.563 27.181 26.441V636H195Z" fill="#fff"/>
<path d="M194.5 636v.5h161.878v-47.506c0-5.006-1.226-11.734-5.315-17.224-4.108-5.515-11.059-9.717-22.366-9.717H194.5V636Z" stroke="#fff" stroke-opacity=".8"/>
</svg>

After

Width:  |  Height:  |  Size: 958 B

View File

@@ -1,6 +1,6 @@
{ {
"name": "@vercel/frameworks", "name": "@vercel/frameworks",
"version": "0.5.1-canary.15", "version": "0.5.1-canary.16",
"main": "./dist/frameworks.js", "main": "./dist/frameworks.js",
"types": "./dist/frameworks.d.ts", "types": "./dist/frameworks.d.ts",
"files": [ "files": [

View File

@@ -1,7 +1,7 @@
{ {
"private": false, "private": false,
"name": "vercel-plugin-go", "name": "vercel-plugin-go",
"version": "1.0.0-canary.10", "version": "1.0.0-canary.13",
"main": "dist/index.js", "main": "dist/index.js",
"license": "MIT", "license": "MIT",
"files": [ "files": [
@@ -17,7 +17,7 @@
"prepublishOnly": "tsc" "prepublishOnly": "tsc"
}, },
"dependencies": { "dependencies": {
"@vercel/build-utils": "2.12.3-canary.25", "@vercel/build-utils": "2.12.3-canary.28",
"@vercel/go": "1.2.4-canary.4" "@vercel/go": "1.2.4-canary.4"
}, },
"devDependencies": { "devDependencies": {

View File

@@ -1,6 +1,7 @@
import { convertRuntimeToPlugin } from '@vercel/build-utils'; import { convertRuntimeToPlugin } from '@vercel/build-utils';
import * as go from '@vercel/go'; import * as go from '@vercel/go';
import { name } from '../package.json';
export const build = convertRuntimeToPlugin(go.build, '.go'); export const build = convertRuntimeToPlugin(go.build, name, '.go');
export const startDevServer = go.startDevServer; export const startDevServer = go.startDevServer;

View File

@@ -12,6 +12,7 @@
"noUnusedParameters": true, "noUnusedParameters": true,
"outDir": "dist", "outDir": "dist",
"strict": true, "strict": true,
"target": "esnext" "target": "esnext",
"resolveJsonModule": true
} }
} }

View File

@@ -1,6 +1,6 @@
{ {
"name": "vercel-plugin-node", "name": "vercel-plugin-node",
"version": "1.12.2-canary.16", "version": "1.12.2-canary.19",
"license": "MIT", "license": "MIT",
"main": "./dist/index", "main": "./dist/index",
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/node-js", "homepage": "https://vercel.com/docs/runtimes#official-runtimes/node-js",
@@ -34,7 +34,7 @@
"@types/node-fetch": "2", "@types/node-fetch": "2",
"@types/test-listen": "1.1.0", "@types/test-listen": "1.1.0",
"@types/yazl": "2.4.2", "@types/yazl": "2.4.2",
"@vercel/build-utils": "2.12.3-canary.25", "@vercel/build-utils": "2.12.3-canary.28",
"@vercel/fun": "1.0.3", "@vercel/fun": "1.0.3",
"@vercel/ncc": "0.24.0", "@vercel/ncc": "0.24.0",
"@vercel/nft": "0.14.0", "@vercel/nft": "0.14.0",

View File

@@ -40,6 +40,7 @@ import {
walkParentDirs, walkParentDirs,
normalizePath, normalizePath,
runPackageJsonScript, runPackageJsonScript,
getInputHash,
} from '@vercel/build-utils'; } from '@vercel/build-utils';
import { FromSchema } from 'json-schema-to-ts'; import { FromSchema } from 'json-schema-to-ts';
import { getConfig, BaseFunctionConfigSchema } from '@vercel/static-config'; import { getConfig, BaseFunctionConfigSchema } from '@vercel/static-config';
@@ -47,8 +48,6 @@ import { AbortController } from 'abort-controller';
import { Register, register } from './typescript'; import { Register, register } from './typescript';
import { pageToRoute } from './router/page-to-route'; import { pageToRoute } from './router/page-to-route';
import { isDynamicRoute } from './router/is-dynamic'; import { isDynamicRoute } from './router/is-dynamic';
import crypto from 'crypto';
import type { VercelConfig } from '@vercel/client';
export { shouldServe }; export { shouldServe };
export { export {
@@ -380,13 +379,7 @@ function getAWSLambdaHandler(entrypoint: string, config: FunctionConfig) {
} }
// TODO NATE: turn this into a `@vercel/plugin-utils` helper function? // TODO NATE: turn this into a `@vercel/plugin-utils` helper function?
export async function build({ export async function build({ workPath }: { workPath: string }) {
vercelConfig,
workPath,
}: {
vercelConfig: VercelConfig;
workPath: string;
}) {
const project = new Project(); const project = new Project();
const entrypoints = await glob('api/**/*.[jt]s', workPath); const entrypoints = await glob('api/**/*.[jt]s', workPath);
const installedPaths = new Set<string>(); const installedPaths = new Set<string>();
@@ -415,7 +408,6 @@ export async function build({
} }
await buildEntrypoint({ await buildEntrypoint({
vercelConfig,
workPath, workPath,
entrypoint, entrypoint,
config, config,
@@ -425,23 +417,18 @@ export async function build({
} }
export async function buildEntrypoint({ export async function buildEntrypoint({
vercelConfig,
workPath, workPath,
entrypoint, entrypoint,
config, config,
installedPaths, installedPaths,
}: { }: {
vercelConfig: VercelConfig;
workPath: string; workPath: string;
entrypoint: string; entrypoint: string;
config: FunctionConfig; config: FunctionConfig;
installedPaths?: Set<string>; installedPaths?: Set<string>;
}) { }) {
// Unique hash that will be used as directory name for `.output`. // Unique hash that will be used as directory name for `.output`.
const entrypointHash = crypto const entrypointHash = 'api-routes-node-' + getInputHash(entrypoint);
.createHash('sha256')
.update(entrypoint)
.digest('hex');
const outputDirPath = join(workPath, '.output'); const outputDirPath = join(workPath, '.output');
const { dir, name } = parsePath(entrypoint); const { dir, name } = parsePath(entrypoint);
@@ -561,7 +548,7 @@ export async function buildEntrypoint({
runtime: nodeVersion.runtime, runtime: nodeVersion.runtime,
}, },
}; };
await updateFunctionsManifest({ vercelConfig, workPath, pages }); await updateFunctionsManifest({ workPath, pages });
// Update the `routes-mainifest.json` file with the wildcard route // Update the `routes-mainifest.json` file with the wildcard route
// when the entrypoint is dynamic (i.e. `/api/[id].ts`). // when the entrypoint is dynamic (i.e. `/api/[id].ts`).

View File

@@ -143,16 +143,7 @@ function withFixture<T>(
await runNpmInstall(fixture); await runNpmInstall(fixture);
} }
let vercelConfig = {}; await build({ workPath: fixture });
try {
vercelConfig = JSON.parse(
await fsp.readFile(path.join(fixture, 'vercel.json'), 'utf8')
);
} catch (e) {
// Consume error
}
await build({ vercelConfig, workPath: fixture });
try { try {
return await t({ fixture, fetch }); return await t({ fixture, fetch });

View File

@@ -1,7 +1,7 @@
{ {
"private": false, "private": false,
"name": "vercel-plugin-python", "name": "vercel-plugin-python",
"version": "1.0.0-canary.11", "version": "1.0.0-canary.14",
"main": "dist/index.js", "main": "dist/index.js",
"license": "MIT", "license": "MIT",
"files": [ "files": [
@@ -17,8 +17,8 @@
"prepublishOnly": "tsc" "prepublishOnly": "tsc"
}, },
"dependencies": { "dependencies": {
"@vercel/build-utils": "2.12.3-canary.25", "@vercel/build-utils": "2.12.3-canary.28",
"@vercel/python": "2.1.2-canary.0" "@vercel/python": "2.1.2-canary.1"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "*", "@types/node": "*",

View File

@@ -1,6 +1,7 @@
import { convertRuntimeToPlugin } from '@vercel/build-utils'; import { convertRuntimeToPlugin } from '@vercel/build-utils';
import * as python from '@vercel/python'; import * as python from '@vercel/python';
import { name } from '../package.json';
export const build = convertRuntimeToPlugin(python.build, '.py'); export const build = convertRuntimeToPlugin(python.build, name, '.py');
//export const startDevServer = python.startDevServer; //export const startDevServer = python.startDevServer;

View File

@@ -13,6 +13,7 @@
"outDir": "dist", "outDir": "dist",
"types": ["node"], "types": ["node"],
"strict": true, "strict": true,
"target": "esnext" "target": "esnext",
"resolveJsonModule": true
} }
} }

View File

@@ -1,7 +1,7 @@
{ {
"private": false, "private": false,
"name": "vercel-plugin-ruby", "name": "vercel-plugin-ruby",
"version": "1.0.0-canary.9", "version": "1.0.0-canary.12",
"main": "dist/index.js", "main": "dist/index.js",
"license": "MIT", "license": "MIT",
"files": [ "files": [
@@ -17,8 +17,8 @@
"prepublishOnly": "tsc" "prepublishOnly": "tsc"
}, },
"dependencies": { "dependencies": {
"@vercel/build-utils": "2.12.3-canary.25", "@vercel/build-utils": "2.12.3-canary.28",
"@vercel/ruby": "1.2.8-canary.4" "@vercel/ruby": "1.2.8-canary.6"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "*", "@types/node": "*",

View File

@@ -1,6 +1,7 @@
import { convertRuntimeToPlugin } from '@vercel/build-utils'; import { convertRuntimeToPlugin } from '@vercel/build-utils';
import * as ruby from '@vercel/ruby'; import * as ruby from '@vercel/ruby';
import { name } from '../package.json';
export const build = convertRuntimeToPlugin(ruby.build, '.rb'); export const build = convertRuntimeToPlugin(ruby.build, name, '.rb');
//export const startDevServer = ruby.startDevServer; //export const startDevServer = ruby.startDevServer;

View File

@@ -13,6 +13,7 @@
"outDir": "dist", "outDir": "dist",
"types": ["node"], "types": ["node"],
"strict": true, "strict": true,
"target": "esnext" "target": "esnext",
"resolveJsonModule": true
} }
} }

View File

@@ -1,6 +1,6 @@
{ {
"name": "@vercel/python", "name": "@vercel/python",
"version": "2.1.2-canary.0", "version": "2.1.2-canary.1",
"main": "./dist/index.js", "main": "./dist/index.js",
"license": "MIT", "license": "MIT",
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/python", "homepage": "https://vercel.com/docs/runtimes#official-runtimes/python",

View File

@@ -1,3 +1,4 @@
import { relative, basename } from 'path';
import execa from 'execa'; import execa from 'execa';
import { Meta, debug } from '@vercel/build-utils'; import { Meta, debug } from '@vercel/build-utils';
@@ -135,6 +136,18 @@ export async function installRequirementsFile({
meta, meta,
args = [], args = [],
}: InstallRequirementsFileArg) { }: InstallRequirementsFileArg) {
const fileAtRoot = relative(workPath, filePath) === basename(filePath);
// If the `requirements.txt` file is located in the Root Directory of the project and
// the new File System API is used (`avoidTopLevelInstall`), the Install Command
// will have already installed its dependencies, so we don't need to do it again.
if (meta.avoidTopLevelInstall && fileAtRoot) {
debug(
`Skipping requirements file installation, already installed by Install Command`
);
return;
}
if ( if (
meta.isDev && meta.isDev &&
(await areRequirementsInstalled(pythonPath, filePath, workPath)) (await areRequirementsInstalled(pythonPath, filePath, workPath))

View File

@@ -1,4 +1,4 @@
import { join, dirname } from 'path'; import { join, dirname, relative } from 'path';
import execa from 'execa'; import execa from 'execa';
import { import {
ensureDir, ensureDir,
@@ -85,10 +85,12 @@ export async function build({
}: BuildOptions) { }: BuildOptions) {
await download(files, workPath, meta); await download(files, workPath, meta);
const entrypointFsDirname = join(workPath, dirname(entrypoint)); const entrypointFsDirname = join(workPath, dirname(entrypoint));
const gemfileName = 'Gemfile';
const gemfilePath = await walkParentDirs({ const gemfilePath = await walkParentDirs({
base: workPath, base: workPath,
start: entrypointFsDirname, start: entrypointFsDirname,
filename: 'Gemfile', filename: gemfileName,
}); });
const gemfileContents = gemfilePath const gemfileContents = gemfilePath
? await readFile(gemfilePath, 'utf8') ? await readFile(gemfilePath, 'utf8')
@@ -130,15 +132,24 @@ export async function build({
'did not find a vendor directory but found a Gemfile, bundling gems...' 'did not find a vendor directory but found a Gemfile, bundling gems...'
); );
// try installing. this won't work if native extesions are required. const fileAtRoot = relative(workPath, gemfilePath) === gemfileName;
// if that's the case, gems should be vendored locally before deploying.
try { // If the `Gemfile` is located in the Root Directory of the project and
await bundleInstall(bundlerPath, bundleDir, gemfilePath); // the new File System API is used (`avoidTopLevelInstall`), the Install Command
} catch (err) { // will have already installed its dependencies, so we don't need to do it again.
debug( if (meta.avoidTopLevelInstall && fileAtRoot) {
'unable to build gems from Gemfile. vendor the gems locally with "bundle install --deployment" and retry.' debug('Skipping `bundle install` — already handled by Install Command');
); } else {
throw err; // try installing. this won't work if native extesions are required.
// if that's the case, gems should be vendored locally before deploying.
try {
await bundleInstall(bundlerPath, bundleDir, gemfilePath);
} catch (err) {
debug(
'unable to build gems from Gemfile. vendor the gems locally with "bundle install --deployment" and retry.'
);
throw err;
}
} }
} }
} else { } else {

View File

@@ -66,6 +66,23 @@ export async function installBundler(meta: Meta, gemfileContents: string) {
gemfileContents gemfileContents
); );
// If the new File System API is used (`avoidTopLevelInstall`), the Install Command
// will have already installed the dependencies, so we don't need to do it again.
if (meta.avoidTopLevelInstall) {
debug(
`Skipping bundler installation, already installed by Install Command`
);
return {
gemHome,
rubyPath,
gemPath,
vendorPath,
runtime,
bundlerPath: join(gemHome, 'bin', 'bundler'),
};
}
debug('installing bundler...'); debug('installing bundler...');
await execa(gemPath, ['install', 'bundler', '--no-document'], { await execa(gemPath, ['install', 'bundler', '--no-document'], {
stdio: 'pipe', stdio: 'pipe',

View File

@@ -1,7 +1,7 @@
{ {
"name": "@vercel/ruby", "name": "@vercel/ruby",
"author": "Nathan Cahill <nathan@nathancahill.com>", "author": "Nathan Cahill <nathan@nathancahill.com>",
"version": "1.2.8-canary.4", "version": "1.2.8-canary.6",
"license": "MIT", "license": "MIT",
"main": "./dist/index", "main": "./dist/index",
"homepage": "https://vercel.com/docs/runtimes#official-runtimes/ruby", "homepage": "https://vercel.com/docs/runtimes#official-runtimes/ruby",

View File

@@ -0,0 +1,2 @@
---
BUNDLE_PATH: "vendor/bundle"

View File

@@ -2,6 +2,6 @@
source "https://rubygems.org" source "https://rubygems.org"
ruby "~> 2.5.0" ruby "~> 2.7.0"
gem "cowsay", "~> 0.3.0" gem "cowsay", "~> 0.3.0"

View File

@@ -0,0 +1,16 @@
GEM
remote: https://rubygems.org/
specs:
cowsay (0.3.0)
PLATFORMS
x86_64-darwin-21
DEPENDENCIES
cowsay (~> 0.3.0)
RUBY VERSION
ruby 2.7.5p203
BUNDLED WITH
2.2.22

View File

@@ -1,6 +1,6 @@
{ {
"version": 2, "version": 2,
"builds": [{ "src": "index.rb", "use": "@vercel/ruby" }], "builds": [{ "src": "index.rb", "use": "@vercel/ruby" }],
"build": { "env": { "RUBY_VERSION": "2.5.x" } }, "build": { "env": { "RUBY_VERSION": "2.7.x" } },
"probes": [{ "path": "/", "mustContain": "gem:RANDOMNESS_PLACEHOLDER" }] "probes": [{ "path": "/", "mustContain": "gem:RANDOMNESS_PLACEHOLDER" }]
} }

View File

@@ -14,19 +14,17 @@ Gem::Specification.new do |s|
s.executables = ["cowsay".freeze] s.executables = ["cowsay".freeze]
s.files = ["bin/cowsay".freeze] s.files = ["bin/cowsay".freeze]
s.homepage = "https://github.com/moneydesktop/cowsay".freeze s.homepage = "https://github.com/moneydesktop/cowsay".freeze
s.rubygems_version = "3.0.3".freeze s.rubygems_version = "3.2.22".freeze
s.summary = "ASCII art avatars emote your messages".freeze s.summary = "ASCII art avatars emote your messages".freeze
s.installed_by_version = "3.0.3" if s.respond_to? :installed_by_version s.installed_by_version = "3.2.22" if s.respond_to? :installed_by_version
if s.respond_to? :specification_version then if s.respond_to? :specification_version then
s.specification_version = 4 s.specification_version = 4
end
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then if s.respond_to? :add_runtime_dependency then
s.add_development_dependency(%q<rake>.freeze, [">= 0"]) s.add_development_dependency(%q<rake>.freeze, [">= 0"])
else
s.add_dependency(%q<rake>.freeze, [">= 0"])
end
else else
s.add_dependency(%q<rake>.freeze, [">= 0"]) s.add_dependency(%q<rake>.freeze, [">= 0"])
end end

View File

@@ -0,0 +1,2 @@
---
BUNDLE_PATH: "vendor/bundle"

View File

@@ -1,7 +1,7 @@
{ {
"version": 2, "version": 2,
"builds": [{ "src": "project/index.rb", "use": "@vercel/ruby" }], "builds": [{ "src": "project/index.rb", "use": "@vercel/ruby" }],
"build": { "env": { "RUBY_VERSION": "2.5.x" } }, "build": { "env": { "RUBY_VERSION": "2.7.x" } },
"probes": [ "probes": [
{ "path": "/project/", "mustContain": "gem:RANDOMNESS_PLACEHOLDER" } { "path": "/project/", "mustContain": "gem:RANDOMNESS_PLACEHOLDER" }
] ]

View File

@@ -0,0 +1,2 @@
---
BUNDLE_PATH: "vendor/bundle"

View File

@@ -2,6 +2,6 @@
source "https://rubygems.org" source "https://rubygems.org"
ruby "~> 2.5.0" ruby "~> 2.7.0"
gem "cowsay", "~> 0.3.0" gem "cowsay", "~> 0.3.0"

View File

@@ -0,0 +1,16 @@
GEM
remote: https://rubygems.org/
specs:
cowsay (0.3.0)
PLATFORMS
x86_64-darwin-21
DEPENDENCIES
cowsay (~> 0.3.0)
RUBY VERSION
ruby 2.7.5p203
BUNDLED WITH
2.2.22

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