[redwood] Fix file permissions and add support for build script (#4999)

Some files require execution privileges, such as Prisma, so we must preserve the file mode.

We also want redwood to behave the same as other frameworks and use `yarn build` if available.
This commit is contained in:
Steven
2020-08-10 17:20:13 -04:00
committed by GitHub
parent 2f0ea24552
commit edb31fb412
6 changed files with 68 additions and 16 deletions

View File

@@ -11,6 +11,7 @@ import {
getNodeVersion, getNodeVersion,
getSpawnOptions, getSpawnOptions,
runNpmInstall, runNpmInstall,
runPackageJsonScript,
execCommand, execCommand,
FileBlob, FileBlob,
FileFsRef, FileFsRef,
@@ -54,10 +55,6 @@ export async function build({
meta meta
); );
const {
buildCommand = 'yarn rw db up --no-db-client --auto-approve && yarn rw build',
} = config;
if (meta.isDev) { if (meta.isDev) {
debug('Detected @vercel/redwood dev, returning routes...'); debug('Detected @vercel/redwood dev, returning routes...');
@@ -79,10 +76,27 @@ export async function build({
} }
debug('Running build command...'); debug('Running build command...');
await execCommand(buildCommand, { const { buildCommand } = config;
const found =
typeof buildCommand === 'string'
? await execCommand(buildCommand, {
...spawnOpts, ...spawnOpts,
cwd: workPath, cwd: workPath,
}); })
: await runPackageJsonScript(
workPath,
['vercel-build', 'build'],
spawnOpts
);
if (!found) {
throw new Error(
`Missing required "${
buildCommand || 'vercel-build'
}" script in "${entrypoint}"`
);
}
const apiDistPath = join(workPath, 'api', 'dist', 'functions'); const apiDistPath = join(workPath, 'api', 'dist', 'functions');
const webDistPath = join(workPath, 'web', 'dist'); const webDistPath = join(workPath, 'web', 'dist');
@@ -119,9 +133,11 @@ export async function build({
}), }),
}; };
dependencies.forEach(fsPath => { for (const fsPath of dependencies) {
lambdaFiles[relative(workPath, fsPath)] = new FileFsRef({ fsPath }); lambdaFiles[relative(workPath, fsPath)] = await FileFsRef.fromFsPath({
fsPath,
}); });
}
lambdaFiles[relative(workPath, fileFsRef.fsPath)] = fileFsRef; lambdaFiles[relative(workPath, fileFsRef.fsPath)] = fileFsRef;

View File

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

View File

@@ -6,6 +6,9 @@
"web" "web"
] ]
}, },
"scripts": {
"build": "rw db up --no-db-client --auto-approve && rw build"
},
"devDependencies": { "devDependencies": {
"@redwoodjs/core": "0.15.0" "@redwoodjs/core": "0.15.0"
}, },

View File

@@ -15,6 +15,10 @@
"headers": { "Accept": "application/json" }, "headers": { "Accept": "application/json" },
"body": { "query": "{ redwood { version } }" }, "body": { "query": "{ redwood { version } }" },
"mustContain": "0.15.0" "mustContain": "0.15.0"
},
{
"path": "/api/permission",
"mustContain": "File is executable: true"
} }
] ]
} }

View File

@@ -3,6 +3,7 @@ const { createHash } = require('crypto');
const path = require('path'); const path = require('path');
const _fetch = require('node-fetch'); const _fetch = require('node-fetch');
const fetch = require('./fetch-retry.js'); const fetch = require('./fetch-retry.js');
const fileModeSymbol = Symbol('fileMode');
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms)); const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
async function nowDeploy(bodies, randomness, uploadNowJson) { async function nowDeploy(bodies, randomness, uploadNowJson) {
@@ -14,7 +15,9 @@ async function nowDeploy(bodies, randomness, uploadNowJson) {
sha: digestOfFile(bodies[n]), sha: digestOfFile(bodies[n]),
size: bodies[n].length, size: bodies[n].length,
file: n, file: n,
mode: path.extname(n) === '.sh' ? 0o100755 : 0o100644, mode:
bodies[n][fileModeSymbol] ||
(path.extname(n) === '.sh' ? 0o100755 : 0o100644),
})); }));
const { FORCE_BUILD_IN_REGION, NOW_DEBUG, VERCEL_DEBUG } = process.env; const { FORCE_BUILD_IN_REGION, NOW_DEBUG, VERCEL_DEBUG } = process.env;
@@ -80,9 +83,7 @@ async function nowDeploy(bodies, randomness, uploadNowJson) {
} }
function digestOfFile(body) { function digestOfFile(body) {
return createHash('sha1') return createHash('sha1').update(body).digest('hex');
.update(body)
.digest('hex');
} }
async function filePost(body, digest) { async function filePost(body, digest) {
@@ -247,4 +248,5 @@ module.exports = {
fetchWithAuth, fetchWithAuth,
nowDeploy, nowDeploy,
fetchTokenWithRetry, fetchTokenWithRetry,
fileModeSymbol,
}; };

View File

@@ -6,7 +6,7 @@ const glob = require('util').promisify(require('glob'));
const path = require('path'); const path = require('path');
const { spawn } = require('child_process'); const { spawn } = require('child_process');
const fetch = require('./fetch-retry.js'); const fetch = require('./fetch-retry.js');
const { nowDeploy } = require('./now-deploy.js'); const { nowDeploy, fileModeSymbol } = require('./now-deploy.js');
async function packAndDeploy(builderPath) { async function packAndDeploy(builderPath) {
await spawnAsync('npm', ['--loglevel', 'warn', 'pack'], { await spawnAsync('npm', ['--loglevel', 'warn', 'pack'], {
@@ -37,6 +37,7 @@ async function testDeployment(
const bodies = globResult.reduce((b, f) => { const bodies = globResult.reduce((b, f) => {
const r = path.relative(fixturePath, f); const r = path.relative(fixturePath, f);
b[r] = fs.readFileSync(f); b[r] = fs.readFileSync(f);
b[r][fileModeSymbol] = fs.statSync(f).mode;
return b; return b;
}, {}); }, {});