@ember/optional-features
Version:
The default blueprint for ember-cli addons.
235 lines (202 loc) • 5.67 kB
JavaScript
/* eslint-disable no-console */
;
const VersionChecker = require('ember-cli-version-checker');
const fs = require('fs');
const path = require('path');
const { styleText } = require('node:util');
const strip = require('../utils').strip;
const getConfigPath = require('../utils').getConfigPath;
const { mkdirSync } = require('node:fs');
const FEATURES = require('../features');
const USAGE_MESSAGE = strip`
Usage:
To list all available features, run ${styleText(
'bold',
'ember feature:list'
)}.
To enable a feature, run ${styleText(
'bold',
'ember feature:enable some-feature'
)}.
To disable a feature, run ${styleText(
'bold',
'ember feature:disable some-feature'
)}.
`;
const SHARED = {
// TODO: promisify the sync code below
_isFeatureAvailable(feature) {
let checker = new VersionChecker(this.project).for('ember-source');
return checker.gte(`${feature.since}-beta.1`);
},
_ensureConfigFile() {
let configPath = getConfigPath(this.project);
try {
return this.project.resolveSync(configPath);
} catch (err) {
if (err.code !== 'MODULE_NOT_FOUND') {
throw err;
}
}
mkdirSync(path.join(this.project.root, path.dirname(configPath)), {
recursive: true,
});
fs.writeFileSync(configPath, '{}', { encoding: 'UTF-8' });
return configPath;
},
_setFeature: async function (name, value, shouldRunCodemod) {
let feature = FEATURES[name];
if (feature === undefined) {
console.log(
styleText(
'red',
`Error: ${styleText('bold', name)} is not a valid feature.\n`
)
);
return LIST_FEATURES.run.apply(this);
}
let configPath = this._ensureConfigFile();
let configJSON = JSON.parse(
fs.readFileSync(configPath, { encoding: 'UTF-8' })
);
if (!this._isFeatureAvailable(feature)) {
console.log(
styleText(
'red',
`Error: ${styleText('bold', name)} is only available in Ember ${
feature.since
} or above.`
)
);
return;
}
if (typeof feature.callback === 'function') {
await feature.callback(this.project, value, shouldRunCodemod);
}
let config = {};
Object.keys(FEATURES).forEach((feature) => {
if (feature === name) {
config[feature] = value;
} else if (configJSON[feature] !== undefined) {
config[feature] = configJSON[feature];
}
});
fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n', {
encoding: 'UTF-8',
});
let state = value ? 'Enabled' : 'Disabled';
console.log(
styleText(
'green',
`${state} ${styleText('bold', name)}. Be sure to commit ${styleText(
'underline',
'config/optional-features.json'
)} to source control!`
)
);
},
};
const USAGE = Object.assign(
{
name: 'feature',
description: 'Prints the USAGE.',
works: 'insideProject',
run() {
console.log(USAGE_MESSAGE);
},
},
SHARED
);
/* This forces strip`` to start counting the indentaiton */
const INDENT_START = '';
const LIST_FEATURES = Object.assign(
{
name: 'feature:list',
description: 'List all available features.',
works: 'insideProject',
run() {
console.log(USAGE_MESSAGE);
console.log('Available features:');
let hasFeatures = false;
Object.keys(FEATURES).forEach((key) => {
let feature = FEATURES[key];
if (this._isFeatureAvailable(feature)) {
console.log(strip`
${INDENT_START}
${styleText('bold', key)} ${styleText(
'cyan',
`(Default: ${feature.default})`
)}
${feature.description}
${styleText(
'gray',
`More information: ${styleText('underline', feature.url)}`
)}`);
hasFeatures = true;
}
});
if (hasFeatures) {
console.log();
} else {
console.log(
styleText(
'gray',
strip`
${INDENT_START}
No optional features available for your current Ember version.
`
)
);
}
},
},
SHARED
);
const ENABLE_FEATURE = Object.assign(
{
name: 'feature:enable',
description: 'Enable feature.',
works: 'insideProject',
availableOptions: [
{
name: 'run-codemod',
type: Boolean,
description: 'run any associated codemods without prompting',
// intentionally not setting a default, when the value is undefined the
// command will prompt the user
},
],
anonymousOptions: ['<feature-name>'],
run(commandOptions, args) {
return this._setFeature(args[0], true, commandOptions.runCodemod);
},
},
SHARED
);
const DISABLE_FEATURE = Object.assign(
{
name: 'feature:disable',
description: 'Disable feature.',
works: 'insideProject',
availableOptions: [
{
name: 'run-codemod',
type: Boolean,
description: 'run any associated codemods without prompting',
// intentionally not setting a default, when the value is undefined the
// command will prompt the user
},
],
anonymousOptions: ['<feature-name>'],
run(commandOptions, args) {
return this._setFeature(args[0], false, commandOptions.runCodemod);
},
},
SHARED
);
module.exports = {
feature: USAGE,
'feature:list': LIST_FEATURES,
'feature:enable': ENABLE_FEATURE,
'feature:disable': DISABLE_FEATURE,
};