Skip to content

Commit

Permalink
feat: support workspaces
Browse files Browse the repository at this point in the history
  • Loading branch information
sergioramos committed May 27, 2020
1 parent f9766ee commit e7d27c3
Show file tree
Hide file tree
Showing 25 changed files with 44,514 additions and 135 deletions.
4 changes: 3 additions & 1 deletion .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ logfile
.nyc_output
coverage
.yarn/*
test/__fixtures__

.git/*
.DS_Store
Expand Down Expand Up @@ -70,4 +71,5 @@ _env*ac
*.lock
*.diff
*.hbs
*.pro
*.pro
*.gz
20 changes: 0 additions & 20 deletions example/package.json

This file was deleted.

251 changes: 203 additions & 48 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
const { existsSync, readFileSync, statAsync } = require('fs');
const archiver = require('archiver');
const { readFileSync, lstatSync, realpathSync, ...fs } = require('fs');
const { exists, stat: statAsync } = require('mz/fs');
const FileTrace = require('@zeit/node-file-trace');
const Flatten = require('lodash.flatten');
const { default: Find } = require('apr-find');
const Globby = require('globby');
const { default: Map } = require('apr-map');
const nanomatch = require('nanomatch');
const Parallel = require('apr-parallel');
const { basename, dirname, extname, relative, resolve, sep } = require('path');
const { basename, dirname, extname, sep } = require('path');
const { normalize, relative, resolve } = require('path');
const PathIsInside = require('path-is-inside');
const SortBy = require('lodash.sortby');
const ToPairs = require('lodash.topairs');
const { writeSync } = require('tempy');
const Uniq = require('lodash.uniq');

const zipService = require('serverless/lib/plugins/package/lib/zipService');
const packageService = require('serverless/lib/plugins/package/lib/packageService');
Expand Down Expand Up @@ -67,6 +75,7 @@ module.exports = class {
this.options = options;
this.servicePath = serverless.config.servicePath || '';
this.files = [];
this.syms = [];

if (options.package) {
this.packagePath = options.package;
Expand Down Expand Up @@ -110,7 +119,20 @@ module.exports = class {

async getFileContentAndStat(pathname) {
const fullpath = resolve(this.servicePath, pathname);
const cache = this.files.find(([src]) => src === fullpath);
const realpath = realpathSync(fullpath);
const cache = this.files.find(([src]) => src === realpath);

const prevSym = this.syms.find(({ target }) => {
return PathIsInside(fullpath, target);
});

if (prevSym) {
const { source, target, ...entry } = prevSym;
return [
entry,
await this.getFileContentAndStat(relative(this.servicePath, realpath)),
];
}

if (!Array.isArray(cache)) {
return zipService.getFileContentAndStat.call(this, pathname);
Expand All @@ -121,26 +143,143 @@ module.exports = class {
return zipService.getFileContentAndStat.call(this, pathname);
}

const isDiff = realpath !== fullpath;
const isInside = PathIsInside(realpath, this.servicePath);
const hasSymlink = isDiff && isInside;

if (hasSymlink) {
const symlink = fullpath.split(sep).reduceRight((memo, _, i, arr) => {
if (memo) {
return memo;
}

const partial = arr.slice(0, i).join(sep);
if (!PathIsInside(partial, this.servicePath)) {
return memo;
}

const lstat = lstatSync(partial);
if (!lstat.isSymbolicLink()) {
return memo;
}

const source = realpathSync(partial);
const slstat = lstatSync(source);
if (!slstat.isDirectory()) {
return memo;
}

return {
source: realpathSync(partial),
target: partial,
};
}, null);

if (symlink) {
const { source, target } = symlink;

const entry = {
stat: lstatSync(target),
filePath: relative(
this.servicePath,
resolve(this.servicePath, target),
),
data: Buffer.from(''),
type: 'symlink',
linkname: relative(dirname(target), source),
};

this.syms.push({ source, target, ...entry });
return entry;
}
}

const [data, stat] = await Promise.all([
// Get file contents and stat in parallel
zipService.getFileContent.call(this, transpiled),
statAsync(transpiled),
statAsync(pathname),
]);

const filePath = relative(
this.servicePath,
resolve(
this.servicePath,
dirname(pathname),
basename(pathname, extname(pathname)),
).concat('.js'),
);

return {
filePath,
data,
stat,
filePath: relative(
this.servicePath,
resolve(
this.servicePath,
dirname(pathname),
basename(pathname, extname(pathname)),
).concat('.js'),
),
};
}

// from: https://github.com/serverless/serverless/blob/f93b27bf684d9a14b1e67ec554a7719ca3628135/lib/plugins/package/lib/zipService.js#L65-L116
async zipFiles(files, zipFileName, prefix, filesToChmodPlusX) {
if (files.length === 0) {
throw new this.serverless.classes.Error('No files to package');
}

const zip = archiver.create('zip');
// Create artifact in temp path and move it to the package path (if any) later
const artifactFilePath = resolve(
this.serverless.config.servicePath,
'.serverless',
zipFileName,
);

this.serverless.utils.writeFileDir(artifactFilePath);
const output = fs.createWriteStream(artifactFilePath);

return new Promise((resolve, reject) => {
output.on('close', () => resolve(artifactFilePath));
output.on('error', (err) => reject(err));
zip.on('error', (err) => reject(err));

return output.on('open', async () => {
zip.pipe(output);

const normalizedFilesToChmodPlusX =
filesToChmodPlusX &&
Uniq(filesToChmodPlusX.map((file) => normalize(file)));

const contents = Flatten(
await Map(
Uniq(files.map((pathname) => normalize(pathname))),
(fullpath) => this.getFileContentAndStat(fullpath),
),
);

SortBy(contents, ['filePath']).forEach((file) => {
const { filePath, data, stat, ...rest } = file;
let { mode } = stat;

const name = filePath.slice(prefix ? `${prefix}${sep}`.length : 0);

if (
normalizedFilesToChmodPlusX &&
normalizedFilesToChmodPlusX.includes(name) &&
mode % 2 === 0
) {
mode += 1;
}

zip.append(data, {
name,
mode,
// necessary to get the same hash when zipping the same content
date: new Date(0),
...rest,
});
});

zip.finalize();
});
});
}

async resolveFilePathsFunction(fnName) {
const { service } = this.serverless;
const { package: pkg = {}, handler } = service.getFunction(fnName);
Expand All @@ -157,43 +296,57 @@ module.exports = class {
basename(handler, extname(handler)),
);

const entrypoint = ['.tsx', '.ts', '.js']
.map((ext) => entry.concat(ext))
.find((filename) => existsSync(filename));

const { fileList = [] } = await FileTrace([entrypoint], {
base: this.servicePath,
readFile: (path) => {
// eslint-disable-next-line block-scoped-var
if (!tsAvailable) {
return readFileSync(path, 'utf-8');
}
const entrypoint = await Find(
['.tsx', '.ts', '.js'].map((ext) => entry.concat(ext)),
(filename) => exists(filename),
);

if (!['.ts', '.tsx'].includes(extname(path))) {
return readFileSync(path, 'utf-8');
}
const handleFile = (pathname) => {
const realpath = realpathSync(pathname);
const cache = this.files.find(([src]) => src === realpath);

if (
!PathIsInside(path, this.servicePath) &&
path.split(sep).includes(['node_modules'])
) {
return readFileSync(path, 'utf-8');
if (Array.isArray(cache)) {
const [fullpath] = cache;
if (fullpath) {
return readFileSync(fullpath, 'utf-8');
}
}

// eslint-disable-next-line block-scoped-var
if (!tsAvailable) {
return readFileSync(pathname, 'utf-8');
}

if (!['.ts', '.tsx'].includes(extname(pathname))) {
return readFileSync(pathname, 'utf-8');
}

if (
!PathIsInside(pathname, this.servicePath) ||
pathname.split(sep).includes(['node_modules'])
) {
return readFileSync(pathname, 'utf-8');
}

// eslint-disable-next-line block-scoped-var
const { outputText } = transpileModule(sys.readFile(pathname), {
compilerOptions: getCompilerOptions(pathname, this.servicePath),
});

// eslint-disable-next-line block-scoped-var
const { outputText } = transpileModule(sys.readFile(path), {
compilerOptions: getCompilerOptions(path, this.servicePath),
});
this.files.push([
pathname,
writeSync(outputText, {
extension: '.js',
}),
]);

this.files.push([
path,
writeSync(outputText, {
extension: '.js',
}),
]);
return outputText;
};

return outputText;
},
const { fileList = [] } = await FileTrace([entrypoint], {
base: this.servicePath,
readLink: handleFile,
readFile: handleFile,
});

const patterns = excludes
Expand Down Expand Up @@ -231,12 +384,14 @@ module.exports = class {
.filter((r) => r[1] === true)
.map((r) => r[0]);

if (filePaths.length !== 0) {
return filePaths;
if (!filePaths.length) {
throw new this.serverless.classes.Error(
'No file matches include / exclude patterns',
);
}

throw new this.serverless.classes.Error(
'No file matches include / exclude patterns',
);
return SortBy(filePaths, (pathname) => {
return pathname.split(sep).includes('package.json');
});
}
};
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,15 @@
},
"dependencies": {
"@zeit/node-file-trace": "^0.6.1",
"apr-find": "^3.0.3",
"apr-for-each": "^3.0.3",
"apr-map": "^3.0.3",
"apr-parallel": "^3.0.3",
"common-shake": "^2.1.0",
"globby": "^11.0.0",
"lodash.sortby": "^4.7.0",
"lodash.topairs": "^4.3.0",
"lodash.uniq": "^4.5.0",
"mz": "^2.7.0",
"nanomatch": "^1.2.13",
"path-is-inside": "^1.0.2",
"tempy": "^0.5.0"
Expand Down
3 changes: 3 additions & 0 deletions test/__fixtures__/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"name": "trick-yarn-ws"
}

0 comments on commit e7d27c3

Please sign in to comment.