Core
A batteries-included step runner library, suitable for creating migration tooling, codemods, scaffolding CLIs, etc.
Install / Use
/learn @DubstepJS/CoreREADME
Dubstep
A batteries-included step runner library, suitable for creating migration tooling, codemods, scaffolding CLIs, etc.
Dubstep has utility functions for file system operations, Babel-based codemodding, Git operations and others.
License: MIT
Installation | Usage | API | Recipes | Motivation
Installation
yarn add @dubstep/core
Usage
import {
Stepper,
step,
gitClone,
findFiles,
withTextFile,
getRestorePoint,
removeFile,
createRestorePoint,
} from '@dubstep/core';
import inquirer from 'inquirer';
async function run() {
const state = {name: ''};
const stepper = new Stepper([
step('name', async () => {
state.name = await inquirer.prompt({message: 'Name:', type: 'input'});
}),
step('clone', async () => {
gitClone('some-scaffold-template.git', state.name);
}),
step('customize', async () => {
const files = await findFiles('**/*.js', f => /src/.test(f));
for (const file of files) {
withTextFile(file, text => text.replace(/{{name}}/g, state.name));
}
}),
]);
stepper
.run({from: await getRestorePoint(restoreFile)})
.then(() => removeFile(reportFile))
.catch(e => createRestorePoint(reportFile, e));
}
run();
API
All API entities are available as non-default import specifiers, e.g. import {Stepper} from '@dubstep/core';
Utilities can also be imported individually, e.g. import {findFiles} from '@dubstep/core/find-files';
Core
Stepper
import {Stepper} from '@dubstep/core';
class Stepper {
constructor(preset: Preset)
run(options: StepperOptions): Promise<any> // rejects w/ StepperError
on(type: 'progress', handler: StepperEventHandler)
off(type: 'progress', handler: StepperEventHandler)
}
type Preset = Array<Step>
type StepperOptions = ?{from: ?number, to: ?number}
type StepperEventHandler = ({index: number, total: number, step: string}) => void
A stepper can take a list of steps, run them in series and emit progress events.
step
import {step} from '@dubstep/core';
type step = (name: string, step: AsyncFunction) => Step;
type Step = {name: string, step: AsyncFunction};
type AsyncFunction = () => Promise<any>;
A step consists of a descriptive name and an async function.
StepperError
import {StepperError} from '@dubstep/core';
class StepperError extends Error {
constructor(error: Error, step: string, index: number),
step: string,
index: number,
message: string,
stack: string,
}
A stepper error indicates what step failed. It can be used for resuming execution via restore points.
Utilities
File system | Babel | Git | Restore points | Misc
File system
findFiles
import {findFiles} from '@dubstep/core';
type findFiles = (glob?: string, filter?: string => boolean) => Promise<Array<string>>;
Resolves to a list of file names that match glob and match the condition from the filter function. Respects .gitignore.
moveFile
import {moveFile} from '@dubstep/core';
type moveFile = (oldName: string, newName: string) => Promise<any>;
Moves an existing file or directory to the location specified by newName. If the file specified by oldName doesn't exist, it no-ops.
readFile
import {readFile} from '@dubstep/core';
type readFile = (file: string) => Promise<string>;
Reads the specified file into a UTF-8 string. If the file doesn't exist, the function throws a ENOENT error.
removeFile
import {removeFile} from '@dubstep/core';
type removeFile = (file: string) => Promise<any>;
Removes the specified file. If the file doesn't exist, it no-ops.
withIgnoreFile
import {withIgnoreFile} from '@dubstep/core';
type withIgnoreFile = (file: string, fn: IgnoreFileMutation) => Promise<any>;
type IgnoreFileMutation = (data: Array<string>) => Promise<?Array<string>>;
Opens a file, parses each line into a string, and calls fn with the array of lines. Then, writes the return value or the array back into the file.
If the file does not exist, fn is called with an empty array, and the file is created (including missing directories).
withJsFile
import {withJsFile} from '@dubstep/core';
type withJsFile = (file: string, fn: JsFileMutation, options: ParserOptions) => Promise<any>;
type JsFileMutation = (program: BabelPath, file: string) => Promise<any>;
type ParserOptions = ?{mode: ?('typescript' | 'flow')};
Opens a file, parses each line into a Babel BabelPath, and calls fn with BabelPath. Then, writes the modified AST back into the file.
If the file does not exist, fn is called with a empty program BabelPath, and the file is created (including missing directories).
See the Babel handbook for more information on BabelPath's API.
withJsFiles
import {withJsFiles} from '@dubstep/core';
type withJsFiles = (glob: string, fn: JsFileMutation, options: ParserOptions) => Promise<any>;
type JsFileMutation = (program: BabelPath, file: string) => Promise<any>;
type ParserOptions = ?{mode: ?('typescript' | 'flow')};
Runs withJsFile only on files that match glob.
See the Babel handbook for more information on BabelPath's API.
withJsonFile
import {withJsonFile} from '@dubstep/core';
type withJsonFile = (file: string, fn: JsonFileMutation) => Promise<any>;
type JsonFileMutation = (data: any) => Promise<any>;
Opens a file, parses each line into a Javascript data structure, and calls fn with it. Then, writes the return value or modified data structure back into the file.
If the file does not exist, fn is called with an empty object, and the file is created (including missing directories).
withTextFile
import {withTextFile} from '@dubstep/core';
type withTextFile = (file: string, fn: TextFileMutation) => Promise<any>;
type TextFileMutation = (data: string) => Promise<?string>;
Opens a file, parses each line into a string, and calls fn with it. Then, writes the return value back into the file.
If the file does not exist, fn is called with an empty string, and the file is created.
writeFile
import {writeFile} from '@dubstep/core';
type writeFile = (file: string, data: string) => Promise<any>;
Writes data to file. If the file doesn't exist, it's created (including missing directories)
Babel
ensureJsImports
import {ensureJsImports} from '@dubstep/core';
type ensureJsImports = (path: BabelPath, code: string, options: ParserOptions) => Array<Object<string, string>>;
type ParserOptions = ?{mode: ?('typescript' | 'flow')};
If an import declaration in code is missing in the program, it's added. If it's already present, specifiers are added if not present. Note that the BabelPath should be for a Program node, and that it is mutated in-place.
Returns a list of maps of specifier local names. The default specifier is bound to the key default.
If a specifier is already declared in path, but there's a conflicting specifier in code, the one in path is retained and returned in the output map. For example:
// default specifier is already declared as `a`, but trying to redeclare it as `foo`
ensureJsImports(parseJs(`import a from 'a';`), `import foo from 'a'`);
// > {default: 'a'};
A BabelPath can be obtained from withJsFile, withJsFiles or parseJs.
visitJsImport
import {visitJsImport} from '@dubstep/core';
type visitJsImport = (
path: BabelPath,
code: string,
handler: (importPath: BabelPath, refPaths: Array<BabelPath>) => void),
options: ParserOptions,
: void
type ParserOptions = ?{mode: ?('typescript' | 'flow')};
This function is useful when applying codemods to specific modules which requires modifying the ast surrounding specific modules and their usage. This module works robustly across various styles of importing. For example:
visitJsImport(
parseJs(`
import {a} from 'a';
a('test')
console.log(a);
`),
`import {a} from 'a';`,
(importPath, refPaths) => {
// importPath corresponds to the ImportDeclaration from 'a';
// refPaths is a list of BabelPaths corresponding to the usage of the a variable
}
);
hasImport
import {hasImport} from '@dubstep/core';
type hasImport = (path: BabelPath<Program>, code: string, options: ParserOptions) => boolean
type ParserOptions = ?{mode: ?('typescript' | 'flow')};
Checks if a given program node contains an import matching a string.
hasImport(
parseJs(`
import {a} from 'a';
console.log(a);
`),
`import {a} from 'a';`
); // true
collapseImports
import {collapseImports} from '@dubstep/core';
type collapseImports = (path: BabelPath<Program>) => BabelPath<Program>
This function collapses multiple import declarations with the same source into a single import statement by combining the specifiers. For example:
import A, {B} from 'a';
import {C, D} from 'a';
// =>
import A, {B, C, D} from 'a';
generateJs
import {generateJs} from '@dubstep/core';
type generateJs = (path: BabelPath) => string;
Converts a Program BabelPath into a Javascript code string.
A BabelPath can be obtained from withJsFile, withJsFiles or parseJs.
insertJsAfter
import {insertJsAfter} from '@dubstep/core';
type insertJsAfter = (path: Bab
