Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion components/yaml.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
'use strict';

const exists = require('../utils/exists-sync');
const fs = require('fs');
const path = require('path');
const yaml = require('js-yaml');
Expand Down Expand Up @@ -43,7 +44,7 @@ const fileloader = {
if (!path.isAbsolute(input.file)) input.file = findFile(input.file, this.base);

// Otherwise check the path exists
return fs.existsSync(input.file);
return exists(input.file);
},
construct: function(data) {
// transform data
Expand Down
5 changes: 4 additions & 1 deletion hooks/lando-copy-v3-scripts.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@
const fs = require('fs');
const path = require('path');

const exists = require('../utils/exists-sync');

module.exports = async lando => {
return lando.Promise.map(lando.config.plugins, plugin => {
if (fs.existsSync(plugin.scripts)) {
// @NOTE: plugin.scripts is undefined for plugins that do not ship scripts
if (exists(plugin.scripts)) {
const confDir = path.join(lando.config.userConfRoot, 'scripts');
const dest = require('../utils/move-config')(plugin.scripts, confDir);
require('../utils/make-executable')(fs.readdirSync(dest), dest);
Expand Down
4 changes: 3 additions & 1 deletion hooks/lando-generate-tasks-cache.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,16 @@ const _ = require('lodash');
const fs = require('fs');
const path = require('path');

const exists = require('../utils/exists-sync');

module.exports = async lando => {
// load in legacy inits
await require('./lando-load-legacy-inits')(lando);

// build the cache
return lando.Promise.resolve(lando.config.plugins)
// Make sure the tasks dir exists
.filter(plugin => fs.existsSync(plugin.tasks))
.filter(plugin => exists(plugin.tasks))
// Get a list off full js files that exist in that dir
.map(plugin => _(fs.readdirSync(plugin.tasks))
.map(file => path.join(plugin.tasks, file))
Expand Down
10 changes: 7 additions & 3 deletions hooks/lando-load-legacy-inits.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,27 @@ const fs = require('fs');
const glob = require('glob');
const path = require('path');

// @NOTE: dirs are plucked off of the plugin objects so they are undefined for any plugin that
// does not have that particular dir, hence the permissive exists check
const exists = require('../utils/exists-sync');

// Helper to get init config
const getLegacyInitConfig = dirs => _(dirs)
.filter(dir => fs.existsSync(dir))
.filter(dir => exists(dir))
.flatMap(dir => glob.sync(path.join(dir, '*', 'init.js')))
.map(file => require(file))
.value();

// Helper to get init config
const getInitConfig = dirs => _(dirs)
.filter(dir => fs.existsSync(dir))
.filter(dir => exists(dir))
.flatMap(dir => fs.readdirSync(dir).map(file => path.join(dir, file)))
.map(file => require(file))
.value();

// Helper to get init source config
const getInitSourceConfig = dirs => _(dirs)
.filter(dir => fs.existsSync(dir))
.filter(dir => exists(dir))
.flatMap(dir => glob.sync(path.join(dir, '*.js')))
.map(file => require(file))
.flatMap(source => source.sources)
Expand Down
4 changes: 2 additions & 2 deletions lib/docker.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// Modules
const _ = require('lodash');
const Dockerode = require('dockerode');
const fs = require('fs');
const exists = require('../utils/exists-sync');
const Promise = require('./promise');

/*
Expand All @@ -16,7 +16,7 @@ const containerOpt = (container, method, message, opts = {}) => container[method
/*
* Helper to determine files exists in an array of files
*/
const srcExists = (files = []) => _.reduce(files, (exists, file) => fs.existsSync(file) || exists, false);
const srcExists = (files = []) => _.reduce(files, (found, file) => exists(file) || found, false);

/*
* Creates a new yaml instance.
Expand Down
4 changes: 2 additions & 2 deletions lib/engine.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

// Modules
const _ = require('lodash');
const fs = require('fs');
const exists = require('../utils/exists-sync');
const LandoDaemon = require('./daemon');
const Landerode = require('./docker');
const router = require('./router');
Expand All @@ -24,7 +24,7 @@ module.exports = class Engine {
run,
);
// Determine install status
this.composeInstalled = fs.existsSync(config.orchestratorBin);
this.composeInstalled = exists(config.orchestratorBin);
this.dockerInstalled = this.daemon.docker !== false;

// set the compose separator
Expand Down
5 changes: 3 additions & 2 deletions lib/lando.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
'use strict';

const _ = require('lodash');
const exists = require('../utils/exists-sync');
const fs = require('fs');
const glob = require('glob');
const path = require('path');
Expand Down Expand Up @@ -136,7 +137,7 @@ const bootstrapApp = lando => {
// start with legacy builder discovery
const legacyBuilders = _(['compose', 'types', 'services', 'recipes'])
.flatMap(type => _.map(lando.config.plugins, plugin => plugin[type]))
.filter(dir => fs.existsSync(dir))
.filter(dir => exists(dir))
.flatMap(dir => glob.sync(path.join(dir, '*', 'builder.js')))
.map(file => lando.factory.add(file).name)
.value();
Expand All @@ -145,7 +146,7 @@ const bootstrapApp = lando => {
// then move to legacy builders we can lazy load from builders
const legacyItems = _(['builders'])
.flatMap(type => _.map(lando.config.plugins, plugin => plugin[type]))
.filter(dir => fs.existsSync(dir))
.filter(dir => exists(dir))
.flatMap(dir => fs.readdirSync(dir).map(file => path.join(dir, file)))
.map(file => lando.factory.add(file))
.value();
Expand Down
1 change: 1 addition & 0 deletions lib/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ module.exports = {
// these are new and useful v4 thing
debugShim: (...args) => require('../utils/debug-shim')(...args),
downloadX: (...args) => require('../utils/download-x')(...args),
existsSync: (...args) => require('../utils/exists-sync')(...args),
getAxios: (...args) => require('../utils/get-axios')(...args),
getJsYaml: () => require('js-yaml'),
getLodash: () => require('lodash'),
Expand Down
71 changes: 71 additions & 0 deletions test/exists-sync.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
* Tests for utils/exists-sync.
* @file exists-sync.spec.js
*/

'use strict';

// Setup chai.
const chai = require('chai');
const expect = chai.expect;
chai.should();

const fs = require('fs');
const os = require('os');
const path = require('path');
const {execFileSync} = require('child_process');

// Get the module to test
const exists = require('../utils/exists-sync');

// node >=24 emits DEP0187 when fs.existsSync gets a non path-like arg
const isNode24 = Number(process.versions.node.split('.')[0]) >= 24;

// helper to run a snippet in a child process with deprecations promoted to throws
// @NOTE: node only emits a given deprecation once per process so we cannot reliably assert on
// this from inside the shared mocha process
const runStrict = code => execFileSync(process.execPath, ['--throw-deprecation', '-e', code], {
cwd: path.resolve(__dirname, '..'),
encoding: 'utf8',
stdio: ['ignore', 'pipe', 'pipe'],
});

describe('exists-sync', () => {
const realFile = path.join(os.tmpdir(), 'lando-exists-sync-test.txt');

before(() => fs.writeFileSync(realFile, 'lando'));
after(() => fs.rmSync(realFile, {force: true}));

it('should return false for non path-like values instead of throwing or warning', () => {
const invalids = [undefined, null, {}, {file: '/tmp'}, [], ['/tmp'], 42, true, false, NaN, () => {}];
for (const invalid of invalids) {
expect(exists(invalid), `expected false for ${String(invalid)}`).to.equal(false);
}
});

it('should behave like fs.existsSync for path-like values', () => {
expect(exists(realFile)).to.equal(true);
expect(exists(Buffer.from(realFile))).to.equal(true);
expect(exists(new URL(`file://${realFile}`))).to.equal(true);

const missing = path.join(os.tmpdir(), 'lando-exists-sync-nope.txt');
expect(exists(missing)).to.equal(false);
expect(exists(Buffer.from(missing))).to.equal(false);
expect(exists(new URL(`file://${missing}`))).to.equal(false);
});

it('should not emit a DEP0187 deprecation warning for invalid values', () => {
const code = `
const exists = require('./utils/exists-sync');
for (const bad of [undefined, null, {}, [], 42, true]) {
if (exists(bad) !== false) throw new Error('expected false for ' + String(bad));
}
`;
expect(() => runStrict(code)).to.not.throw();
});

// this guards the guard, if node ever stops warning here the test above stops proving anything
(isNode24 ? it : it.skip)('should be verifiably avoiding a real fs.existsSync deprecation', () => {
expect(() => runStrict(`require('fs').existsSync(undefined)`)).to.throw(/DeprecationWarning/);
});
});
21 changes: 21 additions & 0 deletions utils/exists-sync.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
'use strict';

const fs = require('fs');

/**
* Permissive fs.existsSync().
*
* Node >=24 emits a DEP0187 deprecation warning when fs.existsSync() is handed anything that is
* not a string, Buffer or URL. Lando has a bunch of call sites that are legitimately permissive
* eg they pluck optional keys off of plugin/config objects and just want a "is there a file
* there?" answer. This restores the pre-24 behavior of quietly returning false for those.
*
* @param {*} file - The thing to check, may be anything
* @return {boolean} Whether file is a path that exists
*/
module.exports = file => {
// bail on anything fs.existsSync() would warn about
if (typeof file !== 'string' && !Buffer.isBuffer(file) && !(file instanceof URL)) return false;
// otherwise defer to node
return fs.existsSync(file);
};
4 changes: 3 additions & 1 deletion utils/get-passphraseless-keys.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
'use strict';

const exists = require('./exists-sync');
const fs = require('fs');
const path = require('path');
const read = require('./read-file');
Expand Down Expand Up @@ -37,7 +38,8 @@ module.exports = (paths = []) => {

// now lets try to find all the private keys without passphrases
return paths
.filter(path => fs.existsSync(path))
// @NOTE: paths comes from user config so it can contain basically anything
.filter(path => exists(path))
.map(path => fs.statSync(path).isDirectory() ? getAllFiles(path) : path)
.flat(Number.POSITIVE_INFINITY)
.map(file => ({file, contents: read(file)}))
Expand Down
4 changes: 2 additions & 2 deletions utils/get-plugin-config.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
'use strict';

const fs = require('fs');
const exists = require('./exists-sync');
const merge = require('lodash/merge');
const read = require('./read-file');

module.exports = (file, config = {}) => {
// if config file exists then rebase config on top of it
if (fs.existsSync(file)) return merge({}, read(file), config);
if (exists(file)) return merge({}, read(file), config);
// otherwise return config alone
return config;
};
11 changes: 7 additions & 4 deletions utils/get-tasks.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ const _ = require('lodash');
const fs = require('fs');
const path = require('path');

// @NOTE: the various caches below are all optional config keys so they may be undefined
const exists = require('./exists-sync');

/*
* Paths to /
*/
Expand All @@ -21,7 +24,7 @@ const pathsToRoot = (startFrom = process.cwd()) => {
const getBsLevel = (config, command) => {
if (_.has(config, `tooling.${command}.level`)) return config.tooling[command].level;
else if (_.find(config.tooling, {id: command}).level) return _.find(config.tooling, {id: command}).level;
else return (!fs.existsSync(config.composeCache)) ? 'app' : 'engine';
else return (!exists(config.composeCache)) ? 'app' : 'engine';
};

/*
Expand Down Expand Up @@ -99,12 +102,12 @@ const engineRunner = (config, command) => (argv, lando) => {

module.exports = (config = {}, argv = {}, tasks = []) => {
// merge in recipe cache config first
if (fs.existsSync(config.recipeCache) && _.has(config, 'recipe')) {
if (exists(config.recipeCache) && _.has(config, 'recipe')) {
config = _.merge({}, JSON.parse(fs.readFileSync(config.recipeCache, {encoding: 'utf-8'})), config);
}

// If we have a tooling router lets rebase on that
if (fs.existsSync(config.toolingRouter)) {
if (exists(config.toolingRouter)) {
// Get the closest route
const closestRoute = _(loadCacheFile(config.toolingRouter))
.map(route => _.merge({}, route, {
Expand Down Expand Up @@ -153,7 +156,7 @@ module.exports = (config = {}, argv = {}, tasks = []) => {
const coreTasks = _(loadCacheFile(process.landoTaskCacheFile)).map(t => ([t.command, t])).fromPairs().value();

// mix in any relevant compose cache things
if (fs.existsSync(config.composeCache)) {
if (exists(config.composeCache)) {
try {
const composeCache = JSON.parse(fs.readFileSync(config.composeCache, {encoding: 'utf-8'}));

Expand Down
4 changes: 3 additions & 1 deletion utils/load-config-files.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ const fs = require('fs');
const path = require('path');
const yaml = require('../components/yaml');

const exists = require('./exists-sync');

/*
* @TODO
*/
Expand Down Expand Up @@ -49,7 +51,7 @@ const normalizePlugins = (plugins = [], baseDir = __dirname) => _(plugins)

module.exports = files => _(files)
// Filter the source out if it doesn't exist
.filter(source => fs.existsSync(source) || fs.existsSync(source.file))
.filter(source => exists(source) || exists(source.file))
// If the file is just a string lets map it to an object
.map(source => {
return _.isString(source) ? {file: source, data: yaml.load(fs.readFileSync(source)) || {}} : source;
Expand Down
4 changes: 2 additions & 2 deletions utils/load-file.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
'use strict';

const fs = require('fs');
const exists = require('./exists-sync');

module.exports = file => {
// if the file doesnt exist then return an empty object
if (!fs.existsSync(file)) return {};
if (!exists(file)) return {};
// otherwise load the file and return it
return require('./read-file')(file);
};
Expand Down
Loading