yeoman-generator
Version:
Rails-inspired generator system that provides scaffolding for your apps
225 lines (224 loc) • 11.5 kB
JavaScript
/* eslint max-params: [1, 6] */
import assert from 'node:assert';
import { splitPathOptions } from '../util/path-options.js';
/**
* A replacer is a function, an array or null, so a plain object in its position is the options object.
*/
const isWriteJSONOptions = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
const writeJSONOptionsToPositional = ({ replacer, space, ...options }) => [
replacer,
space,
options,
];
/**
* Merge the generator's `editorMetadata` into mem-fs-editor options, an explicit `metadata` option takes precedence.
*/
function withEditorMetadata(generator, options) {
const { editorMetadata } = generator;
if (!editorMetadata) {
return options;
}
if (options && 'isFile' in options) {
// Deprecated `write(filepath, contents, stat)` signature.
return { stat: options, metadata: editorMetadata };
}
return { ...options, metadata: { ...editorMetadata, ...options?.metadata } };
}
export class FsMixin {
fs;
readTemplate(...args) {
const [filepath, options, ...remaining] = args;
const [pathOptions, readOptions] = splitPathOptions(options);
return this.fs.read(this.templatePath(filepath, pathOptions), readOptions, ...remaining);
}
/**
* Copy file from templates folder to destination folder.
* mem-fs-editor method's shortcut, for more information see [mem-fs-editor]{@link https://github.com/SBoudrias/mem-fs-editor}.
* Shortcut for this.fs!.copy(this.templatePath(from), this.destinationPath(to))
*/
copyTemplate(...args) {
const [from, to, options, ...remaining] = args;
const [pathOptions, copyOptions] = splitPathOptions(options);
return this.fs.copy(from, this.destinationPath(to, pathOptions), withEditorMetadata(this, { fromBasePath: this.templatePath(), ...copyOptions }), ...remaining);
}
/**
* Copy file from templates folder to destination folder.
* mem-fs-editor method's shortcut, for more information see [mem-fs-editor]{@link https://github.com/SBoudrias/mem-fs-editor}.
* Shortcut for this.fs!.copy(this.templatePath(from), this.destinationPath(to))
*/
async copyTemplateAsync(...args) {
const [from, to, options, ...remaining] = args;
const [pathOptions, copyOptions] = splitPathOptions(options);
const templatePath = (filepath) => this.templatePath(filepath, pathOptions);
return this.fs.copyAsync(Array.isArray(from) ? from.map(filepath => templatePath(filepath)) : templatePath(from), this.destinationPath(to, pathOptions), withEditorMetadata(this, copyOptions), ...remaining);
}
readDestination(...args) {
const [filepath, options, ...remaining] = args;
const [pathOptions, readOptions] = splitPathOptions(options);
return this.fs.read(this.destinationPath(filepath, pathOptions), readOptions, ...remaining);
}
readDestinationJSON(...args) {
const [filepath, defaults, options, ...remaining] = args;
return this.fs.readJSON(this.destinationPath(filepath, options ?? {}), defaults, options, ...remaining);
}
/**
* Write file to destination folder
* mem-fs-editor method's shortcut, for more information see [mem-fs-editor]{@link https://github.com/SBoudrias/mem-fs-editor}.
* Shortcut for this.fs!.write(this.destinationPath(filepath)).
*/
writeDestination(...args) {
const [filepath, contents, options, ...remaining] = args;
const [pathOptions, writeOptions] = splitPathOptions(options);
return this.fs.write(this.destinationPath(filepath, pathOptions), contents, withEditorMetadata(this, writeOptions), ...remaining);
}
writeDestinationJSON(...args) {
const [filepath, contents, ...rest] = args;
// Normalize the `writeDestinationJSON(filepath, contents, options)` form to the positional one.
const [replacer, space, options, ...remaining] = isWriteJSONOptions(rest[0])
? writeJSONOptionsToPositional(rest[0])
: rest;
const [pathOptions, writeOptions] = splitPathOptions(options);
return this.fs.writeJSON(this.destinationPath(filepath, pathOptions), contents, replacer, space, withEditorMetadata(this, writeOptions), ...remaining);
}
/**
* Delete file from destination folder
* mem-fs-editor method's shortcut, for more information see [mem-fs-editor]{@link https://github.com/SBoudrias/mem-fs-editor}.
* Shortcut for this.fs!.delete(this.destinationPath(filepath)).
*/
deleteDestination(...args) {
const [paths, options, ...remaining] = args;
const [pathOptions, deleteOptions] = splitPathOptions(options);
const destinationPath = (filepath) => this.destinationPath(filepath, pathOptions);
return this.fs.delete(Array.isArray(paths) ? paths.map(filepath => destinationPath(filepath)) : destinationPath(paths), deleteOptions, ...remaining);
}
/**
* Copy file from destination folder to another destination folder.
* mem-fs-editor method's shortcut, for more information see [mem-fs-editor]{@link https://github.com/SBoudrias/mem-fs-editor}.
* Shortcut for this.fs!.copy(this.destinationPath(from), this.destinationPath(to)).
*/
copyDestination(...args) {
const [from, to, options, ...remaining] = args;
const [pathOptions, copyOptions] = splitPathOptions(options);
return this.fs.copy(from, this.destinationPath(to, pathOptions), withEditorMetadata(this, { fromBasePath: this.destinationPath(), ...copyOptions }), ...remaining);
}
/**
* Move file from destination folder to another destination folder.
* mem-fs-editor method's shortcut, for more information see [mem-fs-editor]{@link https://github.com/SBoudrias/mem-fs-editor}.
* Shortcut for this.fs!.move(this.destinationPath(from), this.destinationPath(to)).
*/
moveDestination(...args) {
const [from, to, options, ...remaining] = args;
const [pathOptions, moveOptions] = splitPathOptions(options);
return this.fs.move(this.destinationPath(from, pathOptions), this.destinationPath(to, pathOptions), { fromBasePath: this.destinationPath(), ...moveOptions }, ...remaining);
}
/**
* Exists file on destination folder.
* mem-fs-editor method's shortcut, for more information see [mem-fs-editor]{@link https://github.com/SBoudrias/mem-fs-editor}.
* Shortcut for this.fs!.exists(this.destinationPath(filepath)).
*/
existsDestination(...args) {
const [filepath, options, ...remaining] = args;
return this.fs.exists(this.destinationPath(filepath, options ?? {}), options, ...remaining);
}
renderTemplate(source = '', destination = source, templateData, copyOptions, compatOptions) {
if (compatOptions || 'context' in (copyOptions ?? {})) {
copyOptions = { ...compatOptions, transformOptions: copyOptions };
}
if (templateData === undefined || typeof templateData === 'string') {
templateData = this._templateData(templateData);
}
const [pathOptions, copyTplOptions] = splitPathOptions(copyOptions);
source = Array.isArray(source) ? source : [source];
const templatePath = this.templatePath(...source, pathOptions);
destination = Array.isArray(destination) ? destination : [destination];
const destinationPath = this.destinationPath(...destination, pathOptions);
this.fs.copyTpl(templatePath, destinationPath, templateData, withEditorMetadata(this, {
fromBasePath: this.templatePath(),
...copyTplOptions,
transformOptions: {
context: this,
...copyTplOptions?.transformOptions,
},
}));
}
async renderTemplateAsync(source = '', destination = source, templateData, copyOptions, compatOptions) {
if (compatOptions || 'context' in (copyOptions ?? {})) {
copyOptions = { ...compatOptions, transformOptions: copyOptions };
}
if (templateData === undefined || typeof templateData === 'string') {
templateData = this._templateData(templateData);
}
const [pathOptions, copyTplOptions] = splitPathOptions(copyOptions);
source = Array.isArray(source) ? source : [source];
const templatePath = this.templatePath(...source, pathOptions);
destination = Array.isArray(destination) ? destination : [destination];
const destinationPath = this.destinationPath(...destination, pathOptions);
return this.fs.copyTplAsync(templatePath, destinationPath, templateData, withEditorMetadata(this, {
fromBasePath: this.templatePath(),
...copyTplOptions,
transformOptions: {
context: this,
...copyTplOptions?.transformOptions,
},
}));
}
/**
* Copy templates from templates folder to the destination.
*/
renderTemplates(templates, templateData) {
assert.ok(Array.isArray(templates), 'Templates must an array');
if (templateData === undefined || typeof templateData === 'string') {
templateData = this._templateData(templateData);
}
for (const template of templates) {
const { templateData: eachData = templateData, source, destination } = template;
if (!template.when || template.when(eachData, this)) {
this.renderTemplate(source, destination, eachData, {
fromBasePath: this.templatePath(),
...template.copyOptions,
});
}
}
}
/**
* Copy templates from templates folder to the destination.
*
* @param templates - template file, relative to templatePath(), or absolute inside the source root (outside when the `allowTemplatesOutsideRoot` feature is enabled, inside the generator package when the `allowPackageTemplates` feature is enabled).
* @param templateData - ejs data
*/
async renderTemplatesAsync(templates, templateData) {
assert.ok(Array.isArray(templates), 'Templates must an array');
if (templateData === undefined || typeof templateData === 'string') {
templateData = this._templateData(templateData);
}
return Promise.all(templates.map(async (template) => {
const { templateData: eachData = templateData, source, destination } = template;
if (!template.when || template.when(eachData, this)) {
return this.renderTemplateAsync(source, destination, eachData, {
fromBasePath: this.templatePath(),
...template.copyOptions,
});
}
return;
}));
}
/**
* Utility method to get a formatted data for templates.
*
* @param path - path to the storage key.
* @return data to be passed to the templates.
*/
_templateData(path) {
if (path) {
return this.config.getPath(path);
}
const allConfig = this.config.getAll();
if (this.generatorConfig) {
Object.assign(allConfig, this.generatorConfig.getAll());
}
if (this.instanceConfig) {
Object.assign(allConfig, this.instanceConfig.getAll());
}
return allConfig;
}
}