1
0
mirror of https://github.com/svg/svgo.git synced 2025-08-09 02:22:08 +03:00

Implement loadConfig utility (#1328)

Ref https://github.com/svg/svgo/issues/1327

Config file now can only be js. `svgo.config.js` is searched by default.
Otherwise any js module specified in `--config` cli flag.

Config loader is exposed in entry point as `loadConfig(configFile, cwd)`.
This commit is contained in:
Bogdan Chadkin
2021-02-16 19:11:13 +03:00
committed by GitHub
parent d273b26605
commit a6f14018ee
12 changed files with 142 additions and 29 deletions

54
lib/svgo-node.js Normal file
View File

@@ -0,0 +1,54 @@
'use strict';
const fs = require('fs');
const path = require('path');
const {
extendDefaultPlugins,
optimize,
createContentItem,
} = require('./svgo.js');
const importConfig = async configFile => {
try {
const config = require(configFile);
if (config == null || typeof config !== 'object' || Array.isArray(config)) {
throw Error(`Invalid config file "${configFile}"`);
}
return config;
} catch (error) {
if (error.code === 'MODULE_NOT_FOUND') {
return null;
}
throw error;
}
};
const loadConfig = async (configFile, cwd = process.cwd()) => {
if (configFile != null) {
if (path.isAbsolute(configFile)) {
return await importConfig(configFile);
} else {
return await importConfig(path.join(cwd, configFile));
}
}
let dir = cwd;
while (true) {
try {
const file = path.join(dir, "svgo.config.js");
const stats = await fs.promises.stat(file);
if (stats.isFile()) {
return await importConfig(file);
}
} catch {}
const parent = path.dirname(dir);
if (dir === parent) {
return null;
}
dir = parent;
}
};
exports.loadConfig = loadConfig;
exports.extendDefaultPlugins = extendDefaultPlugins;
exports.optimize = optimize;
exports.createContentItem = createContentItem;

View File

@@ -23,7 +23,10 @@ const { encodeSVGDatauri } = require('./svgo/tools.js');
exports.extendDefaultPlugins = extendDefaultPlugins;
const optimize = (svgstr, config = {}) => {
const optimize = (svgstr, config) => {
if (config == null) {
config = {};
}
if (typeof config !== 'object') {
throw Error('Config should be an object')
}

View File

@@ -4,9 +4,8 @@
const FS = require('fs');
const PATH = require('path');
const chalk = require('chalk');
const { optimize } = require('../svgo.js');
const { loadConfig, optimize } = require('../svgo-node.js');
const pluginsMap = require('../../plugins/plugins.js');
const YAML = require('js-yaml');
const PKG = require('../../package.json');
const { encodeSVGDatauri, decodeSVGDatauri } = require('./tools.js');
const regSVGFile = /\.svg$/;
@@ -50,7 +49,7 @@ module.exports = function makeProgram(program) {
.action(action);
}
function action(args, opts) {
async function action(args, opts) {
var input = opts.input || args;
var output = opts.output;
var config = {}
@@ -105,26 +104,13 @@ function action(args, opts) {
}
// --config
if (opts.config) {
var configPath = PATH.resolve(opts.config),
configData;
try {
// require() adds some weird output on YML files
configData = FS.readFileSync(configPath, 'utf8');
config = JSON.parse(configData);
} catch (err) {
if (err.code === 'ENOENT') {
return printErrorAndExit(`Error: couldn't find config file '${opts.config}'.`);
} else if (err.code === 'EISDIR') {
return printErrorAndExit(`Error: directory '${opts.config}' is not a config file.`);
}
config = YAML.safeLoad(configData);
config.__DIR = PATH.dirname(configPath); // will use it to resolve custom plugins defined via path
if (!config || Array.isArray(config)) {
return printErrorAndExit(`Error: invalid config file '${opts.config}'.`);
}
const loadedConfig = await loadConfig(opts.config);
if (loadedConfig != null) {
config = loadedConfig;
}
} catch (error) {
return printErrorAndExit(error.message);
}
// --quiet

8
package-lock.json generated
View File

@@ -537,6 +537,7 @@
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
"dev": true,
"requires": {
"sprintf-js": "~1.0.2"
}
@@ -992,7 +993,8 @@
"esprima": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
"dev": true
},
"estree-walker": {
"version": "2.0.2",
@@ -1510,6 +1512,7 @@
"version": "3.13.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz",
"integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==",
"dev": true,
"requires": {
"argparse": "^1.0.7",
"esprima": "^4.0.0"
@@ -2404,7 +2407,8 @@
"sprintf-js": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
"integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw="
"integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=",
"dev": true
},
"stable": {
"version": "0.1.8",

View File

@@ -38,7 +38,7 @@
"type": "git",
"url": "git://github.com/svg/svgo.git"
},
"main": "./lib/svgo.js",
"main": "./lib/svgo-node.js",
"bin": {
"svgo": "./bin/svgo"
},
@@ -59,7 +59,6 @@
"css-select-base-adapter": "^0.1.1",
"css-tree": "^1.1.2",
"csso": "^4.2.0",
"js-yaml": "^3.13.1",
"sax": "~1.2.4",
"stable": "^0.1.8"
},

View File

@@ -121,7 +121,7 @@ describe('coa', function() {
const stdin = require('mock-stdin').stdin();
setTimeout(() => { stdin.send(initialFile, 'ascii').end(); }, 0);
setTimeout(() => { stdin.send(initialFile, 'ascii').end(); }, 1000);
runProgram(['--input', '-', '--output', 'temp.svg', '--string', fs.readFileSync(svgPath, 'utf8'), '--quiet'])
.then(onComplete, onComplete);

View File

@@ -1,6 +1,8 @@
'use strict';
const path = require('path');
const { expect } = require('chai');
const { loadConfig } = require('../../lib/svgo-node.js');
const {
resolvePluginConfig,
extendDefaultPlugins
@@ -182,6 +184,66 @@ describe('config', function() {
expect(extendedPlugins[extendedPlugins.length - 1].name).to.equal('customPlugin');
});
});
describe('config', () => {
it('is loaded by absolute path', async () => {
const config = await loadConfig(
path.join(process.cwd(), './test/config/fixtures/one/two/config.js'),
);
expect(config).to.deep.equal({ plugins: [] });
});
it('is loaded by relative path to cwd', async () => {
const config = await loadConfig(
'one/two/config.js',
path.join(process.cwd(), './test/config/fixtures'),
);
expect(config).to.deep.equal({ plugins: [] });
});
it('is searched in cwd and up', async () => {
const config = await loadConfig(
null,
path.join(process.cwd(), './test/config/fixtures/one/two'),
);
expect(config).to.deep.equal({ plugins: [] });
});
it('gives null module does not exist', async () => {
const absoluteConfig = await loadConfig(
path.join(process.cwd(), './test/config/fixtures/config.js'),
);
expect(absoluteConfig).to.equal(null);
const searchedConfig = await loadConfig(
null,
path.join(process.cwd(), './test/config'),
);
expect(searchedConfig).to.equal(null);
});
it('is failed to load when module exports not an object', async () => {
try {
await loadConfig(
path.join(process.cwd(), './test/config/fixtures/invalid-null.js'),
);
expect.fail('Config is loaded successfully');
} catch (error) {
expect(error.message).to.match(/Invalid config file/);
}
try {
await loadConfig(
path.join(process.cwd(), './test/config/fixtures/invalid-array.js'),
);
expect.fail('Config is loaded successfully');
} catch (error) {
expect(error.message).to.match(/Invalid config file/);
}
try {
await loadConfig(
path.join(process.cwd(), './test/config/fixtures/invalid-string.js'),
);
expect.fail('Config is loaded successfully');
} catch (error) {
expect(error.message).to.match(/Invalid config file/);
}
});
});
});
function getPlugin(name, plugins) {

View File

@@ -0,0 +1 @@
module.exports = [];

View File

@@ -0,0 +1 @@
module.exports = null;

View File

@@ -0,0 +1 @@
module.exports = '';

View File

@@ -0,0 +1 @@
module.exports = { plugins: [] };

View File

@@ -0,0 +1 @@
module.exports = { plugins: [] };