aws-cdk-lib
Version:
Version 2 of the AWS Cloud Development Kit library
364 lines (289 loc) • 11.8 kB
JavaScript
'use strict';
/**
* @license
* Copyright 2020 Balena Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* ------------------------------------------------------------------------
*
* Copyright 2018 Zeit, Inc.
* Licensed under the MIT License. See file LICENSE.md for a full copy.
*
* ------------------------------------------------------------------------
*/
/**
* This module implements the [dockerignore
* spec](https://docs.docker.com/engine/reference/builder/#dockerignore-file),
* closely following Docker's (Moby) Golang implementation:
* https://github.com/moby/moby/blob/v19.03.8/builder/dockerignore/dockerignore.go
* https://github.com/moby/moby/blob/v19.03.8/pkg/fileutils/fileutils.go
* https://github.com/moby/moby/blob/v19.03.8/pkg/archive/archive.go#L825
*
* Something the spec is not clear about, but we discovered by reading source code
* and testing against the "docker build" command, is the handling of backslashes and
* forward slashes as path separators and escape characters in the .dockerignore file
* across platforms including Windows, Linux and macOS:
*
* * On Linux and macOS, only forward slashes can be used as path separators in the
* .dockerignore file, and the backslash works as an escape character.
* * On Windows, both forward slashes and backslashes are allowed as path separators
* in the .dockerignore file, and the backslash is not used as an escape character.
*
* This is consistent with how Windows works generally: both forward slashes and
* backslashes are accepted as path separators by the cmd.exe Command Prompt or
* PowerShell, and by library functions like the Golang filepath.Clean or the
* Node.js path.normalize.
*
* Similarly, path strings provided to the IgnoreBase.ignores() and IgnoreBase.filter()
* methods can use either forward slashes or backslashes as path separators on Windows,
* but only forward slashes are accepted as path separators on Linux and macOS.
*/
const path = require('path');
const factory = options => new IgnoreBase(options); // https://github.com/kaelzhang/node-ignore/blob/5.1.4/index.js#L538-L539
// Fixes typescript module import
factory.default = factory;
module.exports = factory;
function make_array(subject) {
return Array.isArray(subject) ? subject : [subject];
}
const REGEX_TRAILING_SLASH = /(?<=.)\/$/;
const REGEX_TRAILING_BACKSLASH = /(?<=.)\\$/;
const REGEX_TRAILING_PATH_SEP = path.sep === '\\' ? REGEX_TRAILING_BACKSLASH : REGEX_TRAILING_SLASH;
const KEY_IGNORE = typeof Symbol !== 'undefined' ? Symbol.for('dockerignore') : 'dockerignore'; // An implementation of Go's filepath.Clean
// https://golang.org/pkg/path/filepath/#Clean
// https://github.com/golang/go/blob/master/src/path/filepath/path.go
// Note that, like Go, on Windows this function converts forward slashes
// to backslashes.
function cleanPath(file) {
return path.normalize(file).replace(REGEX_TRAILING_PATH_SEP, '');
} // Javascript port of Golang's filepath.ToSlash
// https://golang.org/pkg/path/filepath/#ToSlash
// https://github.com/golang/go/blob/master/src/path/filepath/path.go
// Convert any OS-specific path separator to '/'. Backslash is converted
// to forward slash on Windows, but not on Linux/macOS.
// Note that both forward slashes and backslashes are valid path separators on
// Windows. As a result, code such as `pattern.split(path.sep).join('/')` fails
// on Windows when forward slashes are used as path separators.
function toSlash(file) {
if (path.sep === '/') {
return file;
}
return file.replace(/\\/g, '/');
} // Javascript port of Golang's filepath.FromSlash
// https://github.com/golang/go/blob/master/src/path/filepath/path.go
function fromSlash(file) {
if (path.sep === '/') {
return file;
}
return file.replace(/\//g, path.sep);
}
class IgnoreBase {
constructor({
// https://github.com/kaelzhang/node-ignore/blob/5.1.4/index.js#L372
ignorecase = true
} = {}) {
this._rules = [];
this._ignorecase = ignorecase;
this[KEY_IGNORE] = true;
this._initCache();
}
_initCache() {
this._cache = {};
} // @param {Array.<string>|string|Ignore} pattern
add(pattern) {
this._added = false;
if (typeof pattern === 'string') {
pattern = pattern.split(/\r?\n/g);
}
make_array(pattern).forEach(this._addPattern, this); // Some rules have just added to the ignore,
// making the behavior changed.
if (this._added) {
this._initCache();
}
return this;
} // legacy
addPattern(pattern) {
return this.add(pattern);
}
_addPattern(pattern) {
// https://github.com/kaelzhang/node-ignore/issues/32
if (pattern && pattern[KEY_IGNORE]) {
this._rules = this._rules.concat(pattern._rules);
this._added = true;
return;
}
if (this._checkPattern(pattern)) {
const rule = this._createRule(pattern.trim());
if (rule !== null) {
this._added = true;
this._rules.push(rule);
}
}
}
_checkPattern(pattern) {
// https://github.com/moby/moby/blob/v19.03.8/builder/dockerignore/dockerignore.go#L34-L40
return pattern && typeof pattern === 'string' && pattern.indexOf('#') !== 0 && pattern.trim() !== "";
}
filter(paths) {
return make_array(paths).filter(path => this._filter(path));
}
createFilter() {
return path => this._filter(path);
}
ignores(path) {
return !this._filter(path);
} // https://github.com/moby/moby/blob/v19.03.8/builder/dockerignore/dockerignore.go#L41-L53
// https://github.com/moby/moby/blob/v19.03.8/pkg/fileutils/fileutils.go#L29-L55
_createRule(pattern) {
const origin = pattern;
let negative = false; // > An optional prefix "!" which negates the pattern;
// https://github.com/moby/moby/blob/v19.03.8/builder/dockerignore/dockerignore.go#L43-L46
if (pattern[0] === '!') {
negative = true;
pattern = pattern.substring(1).trim();
} // https://github.com/moby/moby/blob/v19.03.8/builder/dockerignore/dockerignore.go#L47-L53
if (pattern.length > 0) {
pattern = cleanPath(pattern);
pattern = toSlash(pattern);
if (pattern.length > 1 && pattern[0] === '/') {
pattern = pattern.slice(1);
}
} // https://github.com/moby/moby/blob/v19.03.8/builder/dockerignore/dockerignore.go#L54-L55
if (negative) {
pattern = '!' + pattern;
} // https://github.com/moby/moby/blob/v19.03.8/pkg/fileutils/fileutils.go#L30
pattern = pattern.trim();
if (pattern === "") {
return null;
} // https://github.com/moby/moby/blob/v19.03.8/pkg/fileutils/fileutils.go#L34
// convert forward slashes to backslashes on Windows
pattern = cleanPath(pattern); // https://github.com/moby/moby/blob/v19.03.8/pkg/fileutils/fileutils.go#L36-L42
if (pattern[0] === '!') {
if (pattern.length === 1) {
return null;
}
negative = true;
pattern = pattern.substring(1);
} else {
negative = false;
}
return {
origin,
pattern,
// https://github.com/moby/moby/blob/v19.03.8/pkg/fileutils/fileutils.go#L54
dirs: pattern.split(path.sep),
negative
};
} // @returns `Boolean` true if the `path` is NOT ignored
_filter(path) {
if (!path) {
return false;
}
if (path in this._cache) {
return this._cache[path];
}
return this._cache[path] = this._test(path);
} // @returns {Boolean} true if a file is NOT ignored
// https://github.com/moby/moby/blob/v19.03.8/pkg/fileutils/fileutils.go#L62
_test(file) {
file = fromSlash(file); // equivalent to golang filepath.Dir() https://golang.org/src/path/filepath/path.go
const parentPath = cleanPath(path.dirname(file));
const parentPathDirs = parentPath.split(path.sep);
let matched = false;
this._rules.forEach(rule => {
let match = this._match(file, rule); // https://github.com/moby/moby/blob/v19.03.8/pkg/fileutils/fileutils.go#L80
if (!match && parentPath !== ".") {
// Check to see if the pattern matches one of our parent dirs.
if (rule.dirs.includes('**')) {
// Ah shucks! We have to test every possible parent path that has
// a number of dirs _n_ where
// `rule.dirs.filter(doubleStar).length <= _n_ <= parentPathDirs.length`
// since the ** can imply any number of directories including 0
for (let i = rule.dirs.filter(x => x !== '**').length; i <= parentPathDirs.length; i++) {
match = match || this._match(parentPathDirs.slice(0, i).join(path.sep), rule);
}
} else if (rule.dirs.length <= parentPathDirs.length) {
// https://github.com/moby/moby/blob/v19.03.8/pkg/fileutils/fileutils.go#L83
match = this._match(parentPathDirs.slice(0, rule.dirs.length).join(path.sep), rule);
}
}
if (match) {
matched = !rule.negative;
}
});
return !matched;
} // @returns {Boolean} true if a file is matched by a rule
_match(file, rule) {
return this._compile(rule).regexp.test(file);
} // https://github.com/moby/moby/blob/v19.03.8/pkg/fileutils/fileutils.go#L139
_compile(rule) {
if (rule.regexp) {
return rule;
}
let regStr = '^'; // Go through the pattern and convert it to a regexp.
let escapedSlash = path.sep === '\\' ? '\\\\' : path.sep;
for (let i = 0; i < rule.pattern.length; i++) {
const ch = rule.pattern[i];
if (ch === '*') {
if (rule.pattern[i + 1] === '*') {
// is some flavor of "**"
i++; // Treat **/ as ** so eat the "/"
if (rule.pattern[i + 1] === path.sep) {
i++;
}
if (rule.pattern[i + 1] === undefined) {
// is "**EOF" - to align with .gitignore just accept all
regStr += ".*";
} else {
// is "**"
// Note that this allows for any # of /'s (even 0) because
// the .* will eat everything, even /'s
regStr += `(.*${escapedSlash})?`;
}
} else {
// is "*" so map it to anything but "/"
regStr += `[^${escapedSlash}]*`;
}
} else if (ch === '?') {
// "?" is any char except "/"
regStr += `[^${escapedSlash}]`;
} else if (ch === '.' || ch === '$') {
// Escape some regexp special chars that have no meaning
// in golang's filepath.Match
regStr += `\\${ch}`;
} else if (ch === '\\') {
// escape next char. Note that a trailing \ in the pattern
// will be left alone (but need to escape it)
if (path.sep === '\\') {
// On windows map "\" to "\\", meaning an escaped backslash,
// and then just continue because filepath.Match on
// Windows doesn't allow escaping at all
regStr += escapedSlash;
continue;
}
if (rule.pattern[i + 1] !== undefined) {
regStr += '\\' + rule.pattern[i + 1];
i++;
} else {
regStr += '\\';
}
} else {
regStr += ch;
}
}
regStr += "$";
rule.regexp = new RegExp(regStr, this._ignorecase ? 'i' : '');
return rule;
}
}