Skip to content
This repository was archived by the owner on Jun 29, 2021. It is now read-only.

Commit

Permalink
Merge pull request #71 from dolliejs/dev
Browse files Browse the repository at this point in the history
v2.5.0
lenconda authored Mar 2, 2021
2 parents 9cd52df + c3b9572 commit 49a2359
Showing 11 changed files with 398 additions and 205 deletions.
307 changes: 193 additions & 114 deletions package-lock.json

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@dollie/core",
"version": "2.4.0",
"version": "2.5.0",
"description": "Core engine for Dollie.js",
"homepage": "https://github.com/dolliejs/dollie-core#readme",
"author": {
@@ -50,6 +50,7 @@
"husky": "^5.1.1",
"jest": "^24.8.0",
"lint-staged": "^9.2.0",
"log-update": "^4.0.0",
"md5-file": "^5.0.0",
"prettier": "^2.2.1",
"rimraf": "^3.0.2",
@@ -72,7 +73,9 @@
"fs-extra": "^9.1.0",
"got": "^11.8.1",
"isbinaryfile": "^4.0.6",
"loading-indicator": "^2.0.0",
"lodash": "^4.17.20",
"log-symbols": "^4.0.0",
"memfs": "^3.2.0",
"minimatch": "^3.0.4",
"require-from-string": "^2.0.2",
29 changes: 20 additions & 9 deletions src/base.ts
Original file line number Diff line number Diff line change
@@ -121,7 +121,7 @@ class DollieBaseGenerator extends Generator {
* @type {DollieScaffold}
* @protected
*/
protected scaffold: DollieScaffold;
public scaffold: DollieScaffold;
/**
* the name to be shown as a prompt when CLI is initializing
* @type {string}
@@ -266,7 +266,7 @@ class DollieBaseGenerator extends Generator {
*/
await writeTempFiles(this.scaffold, this);
await writeCacheTable(this.scaffold, this);
const deletions = this.getDeletions();
const deletions = await this.getDeletions();
this.conflicts = this.getConflicts(deletions);
this.deleteCachedFiles(deletions);
writeToDestinationPath(this);
@@ -301,7 +301,7 @@ class DollieBaseGenerator extends Generator {
}
}

public end() {
public async end() {
for (const binaryFileRelativePath of Object.keys(this.binaryTable)) {
const binaryFileAbsolutePath = this.binaryTable[binaryFileRelativePath];
if (binaryFileAbsolutePath) {
@@ -314,16 +314,22 @@ class DollieBaseGenerator extends Generator {
* if there are items in `config.endScripts` options, then we should traverse
* there are two types for `config.endScripts` option: `string` and `Function`
*/
const endScripts = getComposedArrayValue<Function | string>(this.scaffold, 'endScripts') || [];
const endScripts = (await getComposedArrayValue(this.scaffold, 'endScripts', { allowNonString: true })) || [];

const execute = (cmd: string): string => {
if (typeof cmd !== 'string') { return; }
this.log(`Executing: ${cmd}`);
return Buffer.from(execSync(cmd)).toString();
};

for (const endScript of endScripts) {
/**
* if current end script value is a string, Dollie will recognize it as a
* normal shell command, and will invoke `child_process.execSync` to execute
* this script as a command
*/
if (typeof endScript === 'string') {
this.log.info(`Executing end script: \`${endScript}\``);
this.log(Buffer.from(execSync(endScript)).toString());
this.log(execute(endScript));
/**
* if current end script value is a function, Dollie will considering reading
* the code from it, and call it with `context`
@@ -332,7 +338,8 @@ class DollieBaseGenerator extends Generator {
} else if (typeof endScript === 'function') {
const endScriptSource = Function.prototype.toString.call(endScript);
const endScriptFunc = new Function(`return ${endScriptSource}`).call(null);
endScriptFunc({

const result = endScriptFunc({
fs: {
read: (pathname: string): string => {
return fs.readFileSync(this.destinationPath(pathname), { encoding: 'utf-8' });
@@ -354,6 +361,10 @@ class DollieBaseGenerator extends Generator {
scaffold: this.scaffold,
});

if (typeof result === 'string') {
this.log(execute(result));
}

if (this.conflicts.length > 0) {
this.log(
'There ' +
@@ -376,12 +387,12 @@ class DollieBaseGenerator extends Generator {
* traverse files in destination dir and get the deletion pathname
* @returns {Array<string>}
*/
protected getDeletions(): Array<string> {
protected async getDeletions(): Promise<Array<string>> {
/**
* if there are items in `config.files.delete` options, then we should traverse
* it and remove the items
*/
const deletionRegExps = getComposedArrayValue<string>(this.scaffold, 'files.delete') || [];
const deletionRegExps = await getComposedArrayValue(this.scaffold, 'files.delete') || [];
return Object.keys(this.cacheTable).filter((pathname) => {
return (isPathnameInConfig(pathname, deletionRegExps));
});
4 changes: 2 additions & 2 deletions src/generators/compose.ts
Original file line number Diff line number Diff line change
@@ -89,8 +89,8 @@ class DollieComposeGenerator extends DollieBaseGenerator {
super.install.call(this);
}

public end() {
super.end.call(this);
public async end() {
await super.end.call(this);
}
}

2 changes: 1 addition & 1 deletion src/generators/container.ts
Original file line number Diff line number Diff line change
@@ -43,7 +43,7 @@ class DollieContainerGenerator extends DollieComposeGenerator {
}

public async end() {
super.end.call(this);
await super.end.call(this);
const gitIgnoreFileContent = fs.readFileSync(this.destinationPath('.gitignore')) || '';
const matcher = new GitIgnoreMatcher(gitIgnoreFileContent.toString());
const acceptedFiles = await traverse(this.destinationPath(), matcher);
4 changes: 2 additions & 2 deletions src/generators/interactive.ts
Original file line number Diff line number Diff line change
@@ -264,8 +264,8 @@ class DollieInteractiveGenerator extends DollieBaseGenerator {
super.install.call(this);
}

public end() {
super.end.call(this);
public async end() {
await super.end.call(this);
}

protected getDestinationRoot() {
4 changes: 4 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -48,13 +48,15 @@ import {
DollieScaffoldNameParser,
DollieScaffoldProps,
FileAction,
FileItemsGenerator,
MergeBlock,
MergeResult,
PatchTableItem,
PatchTable,
Plugin,
PluginContext,
PluginFunction,
ScaffoldContextItem,
ScaffoldOriginServiceGenerator,
ScaffoldRepoDescription,
ScaffoldConfig,
@@ -139,13 +141,15 @@ export {
DollieScaffoldNameParser,
DollieScaffoldProps,
FileAction,
FileItemsGenerator,
MergeBlock,
MergeResult,
PatchTableItem,
PatchTable,
Plugin,
PluginContext,
PluginFunction,
ScaffoldContextItem,
ScaffoldOriginServiceGenerator,
ScaffoldRepoDescription,
ScaffoldConfig,
13 changes: 10 additions & 3 deletions src/interfaces.ts
Original file line number Diff line number Diff line change
@@ -29,12 +29,14 @@ export interface DollieBasicProps {
scaffold: string;
}

export type DollieScaffoldProps = Record<string, string>;
export type DollieScaffoldProps = Record<string, any>;

export type FileItemsGenerator = (context: Array<ScaffoldContextItem>) => string | Array<string>;

export interface DollieScaffoldFileConfiguration {
merge?: Array<string>;
add?: Array<string>;
delete?: Array<string>;
add?: Array<string | Function>;
delete?: Array<string | Function>;
}

export interface DollieScaffoldConfiguration {
@@ -51,6 +53,11 @@ export interface ComposedDollieScaffold {
props?: DollieScaffoldProps;
}

export interface ScaffoldContextItem {
scaffoldName: string;
props: object;
}

export interface DollieScaffold {
uuid: string;
scaffoldName: string;
22 changes: 14 additions & 8 deletions src/utils/diff.ts
Original file line number Diff line number Diff line change
@@ -9,6 +9,7 @@ import {
CacheTable,
DollieScaffoldFileConfiguration,
} from '../interfaces';
import { getComposedArrayValue } from './generator';
import { isPathnameInConfig } from './scaffold';

/**
@@ -235,26 +236,27 @@ const parseFileTextToMergeBlocks = (content: string): Array<MergeBlock> => {
/**
* parse scaffold tree, temp files of each scaffold and user's scaffold configuration
* and return an appropriate file action strategy
* @param {DollieScaffold} scaffold - scaffold configuration tree
* @param {DollieScaffold} currentScaffold - scaffold configuration tree
* @param {string} relativePathname
* @param {CacheTable} cacheTable
*
* @description `DIRECT` write file and replace content to cache table
* @description `MERGE` push diff between current file and the original file content to cache table
* @description `NIL` do nothing
*/
const checkFileAction = (
scaffold: DollieScaffold,
const checkFileAction = async (
currentScaffold: DollieScaffold,
scaffoldTree: DollieScaffold,
relativePathname: string,
cacheTable: CacheTable,
): FileAction => {
const scaffoldFilesConfig = _.get(scaffold, 'configuration.files') as DollieScaffoldFileConfiguration;
): Promise<FileAction> => {
const scaffoldFilesConfig = _.get(currentScaffold, 'configuration.files') as DollieScaffoldFileConfiguration;

/**
* if current scaffold does not have parent scaffold, which means it is the top-level scaffold
* so we just return `DIRECT`
*/
if (!scaffold.parent) {
if (!currentScaffold.parent) {
return 'DIRECT';
}

@@ -270,8 +272,12 @@ const checkFileAction = (
return cacheExistence ? 'DIRECT' : 'NIL';
}

const mergeConfig = _.get(scaffold, 'configuration.files.merge') || [];
const addConfig = _.get(scaffold, 'configuration.files.add') || [];
const mergeConfig = _.get(currentScaffold, 'configuration.files.merge') || [];
const addConfig = await getComposedArrayValue(currentScaffold, 'files.add', {
recursively: false,
scaffoldTree,
});
// console.log(scaffold.scaffoldName, addConfig);

/**
* if current file pathname matches `config.files.merge`, which means scaffold's author hope
195 changes: 130 additions & 65 deletions src/utils/generator.ts
Original file line number Diff line number Diff line change
@@ -5,7 +5,13 @@ import requireFromString from 'require-from-string';
import DollieBaseGenerator from '../base';
import traverse from './traverse';
import { downloadScaffold } from './download';
import { diff, checkFileAction, parseDiffToMergeBlocks, parseMergeBlocksToText, merge } from './diff';
import {
diff,
checkFileAction,
parseDiffToMergeBlocks,
parseMergeBlocksToText,
merge,
} from './diff';
import {
parseExtendScaffoldName,
parseFilePathname,
@@ -18,17 +24,25 @@ import {
DollieScaffold,
DollieScaffoldConfiguration,
DollieScaffoldProps,
ScaffoldContextItem,
ScaffoldRepoDescription,
} from '../interfaces';
import { ArgInvalidError } from '../errors';
import { isBinaryFileSync } from 'isbinaryfile';
import symbols from 'log-symbols';
import loading, { Preset } from 'loading-indicator';
import presets from 'loading-indicator/presets';

const loadingPresets = presets as Preset;

/**
* get extended props from parent scaffold
* @param {DollieScaffold} scaffold
* @returns {object}
*/
export const getExtendedPropsFromParentScaffold = (scaffold: DollieScaffold): Record<string, any> => {
export const getExtendedPropsFromParentScaffold = (
scaffold: DollieScaffold,
): Record<string, any> => {
if (!scaffold.parent) {
return {};
}
@@ -57,7 +71,10 @@ export const getExtendedPropsFromParentScaffold = (scaffold: DollieScaffold): Re
* it will ignore `.dollie.js`, and inject props into the files
* which contain `__template.` as their filename at the beginning
*/
export const writeTempFiles = async (scaffold: DollieScaffold, context: DollieBaseGenerator) => {
export const writeTempFiles = async (
scaffold: DollieScaffold,
context: DollieBaseGenerator,
) => {
const { TRAVERSE_IGNORE_REGEXP, TEMPLATE_FILE_PREFIX } = context.constants;
/**
* `context.appBasePath` usually is $HOME/.dollie/cache
@@ -114,8 +131,8 @@ export const writeTempFiles = async (scaffold: DollieScaffold, context: DollieBa
* if there are dependencies in current scaffold, then we should traverse the array
* and call `writeTempFiles` to process array items
*/
for (const dependence of scaffold.dependencies) {
await writeTempFiles(dependence, context);
for (const dependency of scaffold.dependencies) {
await writeTempFiles(dependency, context);
}
};

@@ -129,7 +146,10 @@ export const writeTempFiles = async (scaffold: DollieScaffold, context: DollieBa
* the files from each file in each temporary dir and use an appropriate action to write
* the file content into destination dir
*/
export const writeCacheTable = async (scaffold: DollieScaffold, context: DollieBaseGenerator) => {
export const writeCacheTable = async (
scaffold: DollieScaffold,
context: DollieBaseGenerator,
) => {
const { TRAVERSE_IGNORE_REGEXP, TEMPLATE_FILE_PREFIX } = context.constants;
/**
* it is mentioned as above
@@ -179,7 +199,7 @@ export const writeCacheTable = async (scaffold: DollieScaffold, context: DollieB
* 2. file content in destination dir on mem-fs which has the same name as current one
* 3. current file that will be written into destination dir
*/
const action = checkFileAction(scaffold, relativePathname, context.cacheTable);
const action = await checkFileAction(scaffold, context.scaffold, relativePathname, context.cacheTable);

switch (action) {
/**
@@ -229,8 +249,8 @@ export const writeCacheTable = async (scaffold: DollieScaffold, context: DollieB
* if there are dependencies in current scaffold, then we should traverse the array
* and call `writeCacheTable` to process array items
*/
for (const dependence of scaffold.dependencies) {
await writeCacheTable(dependence, context);
for (const dependency of scaffold.dependencies) {
await writeCacheTable(dependency, context);
}
};

@@ -300,7 +320,11 @@ export const parseScaffolds = async (
const { owner, name, checkout, origin } = repoDescription;
const parsedScaffoldName = `${owner}/${name}#${checkout}@${origin}`;

context.log.info(`Pulling scaffold from ${parsedScaffoldName}`);
const timer = loading.start(
`Pulling scaffold: ${parsedScaffoldName}`,
{ frames: loadingPresets.dots },
);

/**
* download scaffold from GitHub repository and count the duration
*/
@@ -314,8 +338,9 @@ export const parseScaffolds = async (
},
context,
);
context.log.info(`Template pulled in ${duration}ms`);
context.log.info(`Reading scaffold configuration from ${parsedScaffoldName}...`);

loading.stop(timer);
context.log(`${symbols.success} Scaffold ${parsedScaffoldName} pulled in ${duration}ms`);

let customScaffoldConfiguration: DollieScaffoldConfiguration;
const dollieJsConfigPathname = path.resolve(scaffoldDir, '.dollie.js');
@@ -412,20 +437,20 @@ export const parseScaffolds = async (
scaffold.props,
);
if (scaffold.dependencies && Array.isArray(scaffold.dependencies)) {
for (const dependence of scaffold.dependencies) {
if (!dependence.scaffoldName) {
for (const dependency of scaffold.dependencies) {
if (!dependency.scaffoldName) {
throw new ArgInvalidError([mode !== 'compose' ? 'scaffoldName' : 'scaffold_name']);
}

dependence.uuid = uuid();
dependency.uuid = uuid();
/**
* cause current scaffold is a dependency, so we should invoke `parseExtendScaffoldName`
* to parse the scaffold's name
*/
const description = parseExtendScaffoldName(dependence.scaffoldName);
const description = parseExtendScaffoldName(dependency.scaffoldName);
const { owner, name, checkout, origin } = description;
dependence.scaffoldName = `${owner}/${name}#${checkout}@${origin}`;
await parseScaffolds(dependence, context, scaffold, mode);
dependency.scaffoldName = `${owner}/${name}#${checkout}@${origin}`;
await parseScaffolds(dependency, context, scaffold, mode);
}
}
return;
@@ -462,35 +487,62 @@ export const parseScaffolds = async (
scaffoldProps,
(value, key) => context.isDependencyKeyRegistered(key) && value !== 'null',
);
for (const dependenceKey of Object.keys(dependencies)) {
let dependedScaffoldName = '';
const currentDependenceValue = dependencies[dependenceKey];
const match = _.get(/\:(.*)$/.exec(dependenceKey), 1);
for (const dependencyKey of Object.keys(dependencies)) {
let dependedScaffoldNames = [];
const currentDependencyValue = dependencies[dependencyKey];
const match = _.get(/\:(.*)$/.exec(dependencyKey), 1);
if (match) {
if (_.isBoolean(currentDependenceValue) && currentDependenceValue) {
dependedScaffoldName = match;
if (_.isBoolean(currentDependencyValue) && currentDependencyValue) {
dependedScaffoldNames.push(match);
} else { continue; }
} else {
dependedScaffoldName = currentDependenceValue;
if (typeof currentDependencyValue === 'string') {
dependedScaffoldNames.push(currentDependencyValue);
} else if (_.isArray(currentDependencyValue)) {
dependedScaffoldNames = dependedScaffoldNames.concat(
currentDependencyValue.filter((value) => typeof value === 'string'),
);
}
}
if (!dependedScaffoldNames || dependedScaffoldNames.length === 0) { continue; }
for (const scaffoldName of dependedScaffoldNames) {
const dependencyUuid = uuid();
const description = parseExtendScaffoldName(scaffoldName);
const { owner, name, checkout, origin } = description;
const currentDependency: DollieScaffold = {
uuid: dependencyUuid,
scaffoldName: `${owner}/${name}#${checkout}@${origin}`,
dependencies: [],
};
scaffold.dependencies.push(currentDependency);
await parseScaffolds(currentDependency, context, scaffold, mode);
}
if (!dependedScaffoldName) { continue; }
const dependenceUuid = uuid();
const description = parseExtendScaffoldName(dependedScaffoldName);
const { owner, name, checkout, origin } = description;
const currentDependence: DollieScaffold = {
uuid: dependenceUuid,
scaffoldName: `${owner}/${name}#${checkout}@${origin}`,
dependencies: [],
};
scaffold.dependencies.push(currentDependence);
await parseScaffolds(currentDependence, context, scaffold, mode);
}
};

export const getScaffoldContext = (scaffold: DollieScaffold): Array<ScaffoldContextItem> => {
if (!scaffold) { return null; }

const recursion = (scaffold: DollieScaffold) => {
let result: Array<ScaffoldContextItem> = [];
const { scaffoldName, props = {}, dependencies = [] } = scaffold;
const currentScaffold: ScaffoldContextItem = { scaffoldName, props };
result.push(currentScaffold);

for (const dependency of dependencies) {
result = result.concat(recursion(dependency));
}

return result;
};

return recursion(scaffold);
};

/**
* get configuration values from scaffold tree structure, compose recursively as
* an array and returns it
* @param {DollieScaffold} scaffold
* @param {DollieScaffold} currentScaffold
* @param {string} key
* @param {boolean} lazyMode
* @returns {Array<any>}
@@ -499,36 +551,49 @@ export const parseScaffolds = async (
* value, but we supposed to get all of the values and make a aggregation (something just like
* a flatten), for example: `installers`, `files.delete`, `endScripts` and so on
*/
export const getComposedArrayValue = <T>(scaffold: DollieScaffold, key: string, lazyMode = false): Array<T> => {
const recursion = (scaffold: DollieScaffold, key: string, lazyMode: boolean): Array<Array<T>> => {
let results = [_.get(scaffold.configuration, key)];
const dependencies = _.get(scaffold, 'dependencies') || [];
for (const dependence of dependencies) {
const dependenceResult = recursion(dependence, key, lazyMode);
results = results.concat(dependenceResult);
export const getComposedArrayValue = async (
currentScaffold: DollieScaffold,
key: string,
options: { allowNonString?: boolean, recursively?: boolean, scaffoldTree?: DollieScaffold } = {},
): Promise<Array<string>> => {
const {
allowNonString = false,
recursively = true,
scaffoldTree = currentScaffold,
} = options;

const recursion = async (currentScaffold: DollieScaffold, key: string): Promise<Array<string>> => {
const values = _.get(currentScaffold.configuration, key) || [];
const dependencies = _.get(currentScaffold, 'dependencies') || [];
let result = [];
const scaffoldContext = getScaffoldContext(scaffoldTree);

for (const value of values) {
if (typeof value === 'string') { result.push(value); }
else {
if (!allowNonString) {
if (_.isFunction(value)) {
const currentResult = await value.call(null, scaffoldContext);
if (typeof currentResult === 'string') {
result.push(currentResult);
} else if (_.isArray(currentResult)) {
result = result.concat(currentResult.filter((result => typeof result === 'string')));
}
result.push(await value.call(null, scaffoldContext));
}
} else { result.push(value); }
}
}
return results;
};

const resultItems = recursion(scaffold, key, lazyMode);

if (
lazyMode &&
resultItems.filter(
(result) => Array.isArray(result) && result.length === 0,
).length > 0
) {
return [];
}

if (resultItems.filter((item) => item === undefined).length === resultItems.length) {
return undefined;
}

return resultItems.reduce((result: Array<T>, currentResult) => {
if (Array.isArray(currentResult)) {
return result.concat(currentResult);
if (recursively) {
for (const dependency of dependencies) {
const dependencyResult = await recursion(dependency, key);
result = result.concat(dependencyResult);
}
}

return result;
}, [] as Array<T>);
};

return await recursion(currentScaffold, key);
};
18 changes: 18 additions & 0 deletions typings/loading-indicator.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
declare module 'loading-indicator' {
import logUpdate from 'log-update';

declare type PresetFrame = Array<string>;

declare type Preset = Record<'spinner' | 'circle' | 'dots' | 'bullets' | 'arrows' | 'clock', PresetFrame>;

declare type NodeJSIntervalTimer = ReturnType<typeof setInterval>;

declare interface Options {
delay?: number;
frames?: PresetFrame;
render?: typeof logUpdate | Function;
}

export function start(text?: string, options?: Options): NodeJSIntervalTimer;
export function stop(timer: NodeJSIntervalTimer, shouldKeepOutput?: boolean): void;
}

0 comments on commit 49a2359

Please sign in to comment.