WIP: more wip

This commit is contained in:
Michael Bradley, Jr 2020-02-18 19:48:09 -06:00
parent 1931f78a2d
commit ea8966cc5c
8 changed files with 167 additions and 181 deletions

View File

@ -1,6 +1,6 @@
{
"name": "@embarklabs/create-react-dapp",
"version": "5.2.1",
"version": "5.2.2",
"author": "Michael Bradley <michaelsbradleyjr@gmail.com> (https://github.com/michaelsbradleyjr/)",
"description": "DApp creator utility that uses Create React App",
"homepage": "https://github.com/embarklabs/embark/tree/master/dapps/creators/react#readme",
@ -43,7 +43,7 @@
},
"dependencies": {
"embark-i18n": "^5.1.1",
"embark-init": "^5.2.1",
"embark-init": "^5.2.2",
"lodash.clonedeep": "4.5.0",
"source-map-support": "0.5.13"
},

View File

@ -10,12 +10,7 @@ const pkgJson = require(pkgJsonPath);
const version = pkgJson.version;
const creator = cloneDeep(creatorDefaults);
creator.extraHelp = () => {
console.log('');
console.log('EXTRA HELP SECTION FOR react-dapp');
};
creator.version = version;
delete creator.init.options.contractsOnly;
delete creator.init.options.simple;
delete creator.init.presets.contractsOnly;
@ -24,18 +19,18 @@ creator.init.presets.default = `${defaultPreset}@${pkgJson.devDependencies[defau
const demoPreset = '@embarklabs/dapps-presets-react-demo';
creator.init.presets.demo = `${demoPreset}@${pkgJson.devDependencies[demoPreset]}`;
const subcreator = 'create-react-app';
const subcreatorName = 'create-react-app';
creator.subcreator.abbrev = 'cra';
creator.subcreator.command = ['{{subcreator.package}}', [2], {abc:123}];
creator.subcreator.name = subcreator;
creator.subcreator.command = '{{subcreator.package}}';
creator.subcreator.name = subcreatorName;
creator.subcreator.package = '{{subcreator.name}}@{{subcreator.version}}';
creator.subcreator.version = pkgJson.indirect.dependencies[subcreator];
creator.subcreator.version = pkgJson.indirect.dependencies[subcreatorName];
// const subcreator = '@angular/cli';
// const subcreatorName = '@angular/cli';
// creator.subcreator.abbrev = 'ng';
// creator.subcreator.command = ['-p', '{{subcreator.package}}', 'ng', 'new'];
// creator.subcreator.name = subcreator;
// creator.subcreator.name = subcreatorName;
// creator.subcreator.package = '{{subcreator.name}}@{{subcreator.version}}';
// creator.subcreator.version = pkgJson.indirect.dependencies[subcreator];
// creator.subcreator.version = pkgJson.indirect.dependencies[subcreatorName];
export const main = makeCreatorMain(creator);

View File

@ -444,8 +444,8 @@ class Cmd {
async init() {
let reject, resolve;
let promise = new Promise((res, rej) => { resolve = res; reject = rej; });
initCli(program.command('init'), { reject, resolve });
try {
initCli(program.command('init'), { reject, resolve });
const code = await promise;
process.exit(code ?? 0);
} catch (error) {

View File

@ -1,6 +1,6 @@
{
"name": "embark-init",
"version": "5.2.1",
"version": "5.2.2",
"author": "Michael Bradley <michaelsbradleyjr@gmail.com> (https://github.com/michaelsbradleyjr/)",
"description": "DApp initializer utility",
"homepage": "https://github.com/embarklabs/embark/tree/master/packages/utils/init#readme",

View File

@ -6,7 +6,7 @@ import { __, setOrDetectLocale } from 'embark-i18n';
import minimist from 'minimist';
import { creatorDefaults } from './defaults';
import { main as initMain } from './index';
import { handleCliOptions as initHandleCliOptions } from './index';
import { makeCreatorCommanderOptions, replaceTokens, runCommand } from './util';
export { creatorDefaults };
@ -14,25 +14,20 @@ export { creatorDefaults };
const procArgv = process.argv.slice();
export function makeCreatorMain(creator = creatorDefaults) {
if (!Array.isArray(creator.subcreator.command)) {
creator.subcreator.command = [creator.subcreator.command];
}
async function handleCliOptions(
cliOptions,
{ argv, promise, reject, resolve } = {}
{ argv, program, promise, reject, resolve } = {}
) {
setOrDetectLocale(cliOptions.locale);
const npxCmd = process.platform === 'win32' ? 'npx.cmd': 'npx';
console.log();
console.log('hi from handleCliOptions in creator.js');
console.log();
// let error = new Error('bad stuff happened in creator.js');
// if (reject) return reject(error);
// throw error;
if (resolve) return resolve(19);
return 19;
if (resolve) return resolve(11);
return 11;
// should display command/s that will be run, similar to what the release
// script does, so it's easier to figure out what's happening and how
@ -60,6 +55,10 @@ export function makeCreatorMain(creator = creatorDefaults) {
// ^ should probably have an --init-prompts for the creator that can
// combine with `--yes` and `--`
// if (!Array.isArray(creator.subcreator.command)) {
// creator.subcreator.command = [creator.subcreator.command];
// }
// let subp = spawn(npxCmd, [
// ...creator.subcreator.command,
// ...creator.subcreator.options
@ -86,10 +85,13 @@ export function makeCreatorMain(creator = creatorDefaults) {
// process init.options and build an array that combines/overrides re:
// matching options spec'd in the creator's cli
const initCliArgs = [];
const initCliOptions = [];
// should report what final options get forwarded to embark-init
return initMain(null, initCliArgs, {promise, reject, resolve});
// report what final options get forwarded to embark-init
initHandleCliOptions(
initCliOptions,
{ argv, init: creator.init, program, promise, reject, resolve }
);
}
function cli(
@ -105,19 +107,12 @@ export function makeCreatorMain(creator = creatorDefaults) {
program = new Command();
program.version(creator.version);
}
program.description(
`Creates a new Embark dapp using ${creator.subcreator.name}`
);
program.description(`Creates a new Embark dapp using ${creator.subcreator.name}`);
program.usage(`[options] [-- [${creator.subcreator.abbrev}-options]]`);
// console.log('before replace:', require('util').inspect(creator, {depth: null}));
creator = replaceTokens(creator);
// console.log('after replace:', require('util').inspect(creator, {depth: null}));
makeCreatorCommanderOptions(creator).forEach(opt => {
program.option(...opt);
});
program.action((...args) => handleCliOptions(
args, { argv, promise, reject, resolve }
));
makeCreatorCommanderOptions(creator).forEach(opt => { program.option(...opt); });
program.action((...args) => handleCliOptions(args, { argv, program, promise, reject, resolve }));
program.on('--help', () => { if (creator.extraHelp) console.log('\n' + creator.extraHelp); });
return program;
}

View File

@ -10,6 +10,55 @@ const defaultPreset = '@embarklabs/dapps-presets-init-boilerplate';
const defaultInitContractsOnlyPreset = `${contractsOnlyPreset}@${pkgJson.devDependencies[contractsOnlyPreset]}`;
const defaultInitDefaultPreset = `${defaultPreset}@${pkgJson.devDependencies[defaultPreset]}`;
const defaultInitExtraHelp = (`
SIMPLE USAGE
$ {{init.embarkInit}} [options]
console.log('');
console.log('SIMPLE USAGE:');
console.log('');
console.log(' ', \`{{init.embarkInit}} [options]\`);
console.log('');
console.log(
'Initialization is performed in the current working directory;',
'will first run \`npm init\` if no package.json is present.'
);
console.log('');
console.log('WITH CREATOR:');
console.log('');
console.log(' ', \`{{init.embarkInit}} [creator] [options] [-- [extra-options]]\`);
console.log('');
console.log(
'A creator is a name such as "react-dapp".',
'Options to the left of the creator are ignored.',
'Any options to the right of "--" are passed to the underlying tool,',
'e.g. create-react-app, and will override conflicting options computed by the creator.'
);
console.log('');
console.log(
'Creator names are resolved to packages by prepending "@embarklabs/create-", then trying with "embark-", and finally using the bare name.'
);
console.log('');
console.log(
'Example: "react-dapp" resolves to "@embarklabs/create-react-dapp", and the following are equivalent:'
);
console.log('');
console.log(' ', \`{{init.embarkInit}} react-dapp mydapp [options] [-- [extra-options]]\`);
console.log('');
console.log(' ', \`npx @embarklabs/crete-react-dapp mydapp [options] [-- [extra-options]]\`);
console.log('');
console.log('To see the help output of a creator do:');
console.log('');
console.log(' ', \`{{init.embarkInit}} [creator] --help\`);
console.log('');
console.log('To see the help output of a creator\'s underlying tool do:');
console.log('');
console.log(' ', \`{{init.embarkInit}} [creator] -- --help\`);
`).trim();
const defaultInitOptions = {
contractsOnly: {
description: [
@ -68,6 +117,7 @@ const defaultInitOptions = {
};
export const initDefaults = {
extraHelp: defaultInitExtraHelp,
options: defaultInitOptions,
presets: {
contractsOnly: defaultInitContractsOnlyPreset,
@ -76,7 +126,9 @@ export const initDefaults = {
};
const defaultCreatorDemoPreset = null;
const defaultCreatorExtraHelp = null;
const defaultCreatorExtraHelp = (`
THIS IS GREAT STUFF
`).trim();
const defaultCreatorOptions = {
demo: {

View File

@ -16,21 +16,24 @@ const pkgJsonPath = join(__dirname, "..", "package.json");
const pkgJson = require(pkgJsonPath);
const version = pkgJson.version;
async function handleCliOptions(
export async function handleCliOptions(
cliOptions,
{ argv, init, promise, reject, resolve } = {}
{ argv, init, program, promise, reject, resolve } = {}
) {
setOrDetectLocale(cliOptions.locale);
const npxCmd = process.platform === 'win32' ? 'npx.cmd': 'npx';
console.log();
console.log('hi from handleCliOptions in embark-init');
console.log();
// let error = new Error('bad stuff happened in embark-init');
// if (reject) return reject(error);
// throw error;
// if (resolve) return resolve(19);
// return 19;
// console.log(require('util').inspect(cliOptions, {depth: null}));
// console.log();
console.log(require('util').inspect(cliOptions, {depth: null}));
if (resolve) return resolve(17);
return 17;
// should display command/s that will be run, similar to what the release
// script does, so it's easier to figure out what's happening and how options
@ -83,57 +86,11 @@ export function cli(
}
program.description('Initializes a project as an Embark dapp');
program.usage('[options] [creator] [creator-options] [-- [extra-options]]');
({init} = replaceTokens({init}));
init.embarkInit = program.parent ? 'embark init' : 'embark-init';
({ init } = replaceTokens({ init }));
makeInitCommanderOptions(init).forEach(opt => { program.option(...opt); });
program.action((...args) => handleCliOptions(
args, { argv, init, promise, reject, resolve }
));
program.on('--help', () => {
let embarkInit = 'embark-init';
if (program.parent) embarkInit = 'embark init';
console.log('');
console.log('SIMPLE USAGE:');
console.log('');
console.log(' ', `${embarkInit} [options]`);
console.log('');
console.log(
'Initialization is performed in the current working directory;',
'will first run \`npm init\` if no package.json is present.'
);
console.log('');
console.log('WITH CREATOR:');
console.log('');
console.log(' ', `${embarkInit} [creator] [options] [-- [extra-options]]`);
console.log('');
console.log(
'A creator is a name such as "react-dapp".',
'Options to the left of the creator are ignored.',
'Any options to the right of "--" are passed to the underlying tool,',
'e.g. create-react-app, and will override conflicting options computed by the creator.'
);
console.log('');
console.log(
'Creator names are resolved to packages by prepending "@embarklabs/create-", then trying with "embark-", and finally using the bare name.'
);
console.log('');
console.log(
'Example: "react-dapp" resolves to "@embarklabs/create-react-dapp", and the following are equivalent:'
);
console.log('');
console.log(' ', `${embarkInit} react-dapp mydapp [options] [-- [extra-options]]`);
console.log('');
console.log(' ', `npx @embarklabs/crete-react-dapp mydapp [options] [-- [extra-options]]`);
console.log('');
console.log('To see the help output of a creator do:');
console.log('');
console.log(' ', `${embarkInit} [creator] --help`);
console.log('');
console.log('To see the help output of a creator\'s underlying tool do:');
console.log('');
console.log(' ', `${embarkInit} [creator] -- --help`);
});
program.action((...args) => handleCliOptions(args, { argv, init, program, promise, reject, resolve }));
program.on('--help', () => { if (init.extraHelp) console.log('\n' + init.extraHelp); });
return program;
}

View File

@ -4,6 +4,9 @@ import isPlainObject from 'lodash.isplainobject';
import { creatorDefaults } from './defaults';
const ALL_TOKENS_REGEX = /\{\{([^}]+)\}\}/g;
const BARE_TOKEN_REGEX = /\{\{\s*(\S+)\s*\}\}/;
const cyan = (str) => chalk.cyan(str);
const log = (mark, str, which = 'log') => console[which](
mark, str.filter(s => !!s).join(` `)
@ -13,9 +16,18 @@ export const logInfo = (...str) => log(chalk.blue(``), str);
export const logSuccess = (...str) => log(chalk.green(``), str);
export const logWarning = (...str) => log(chalk.yellow('‼︎'), str);
export function makeCreatorCommanderOptions(creator) {
return makeInitCommanderOptions({
options: {
...creator.init.options,
...creator.options
}
});
}
export function makeInitCommanderOptions({options}) {
return Object.values(options)
.sort(({long: aLong}, {long: bLong}) => {
.sort(({long: aLong}, {long: bLong}) => {
if (aLong < bLong) return -1;
if (aLong > bLong) return 1;
return 0;
@ -33,30 +45,6 @@ export function makeInitCommanderOptions({options}) {
}, []);
}
export function makeCreatorCommanderOptions(creator) {
return makeInitCommanderOptions({
options: {
...creator.init.options,
...creator.options
}
});
}
// !!! needs refactor because intend to use spawn
export function runCommand(cmd, inherit = true, display) {
logInfo(`Running command ${cyan(display || cmd)}.`);
let out;
if (inherit) {
// execSyncInherit(cmd);
} else {
// out = execSync(cmd);
}
return out;
}
// NOTE: does not detect/handle recursive references, e.g. `{{foo}}` refers to
// `{{bar}}` and `{{bar}}` refers to `{{foo}}`, but assume for now that won't
// be a common pitfall
export function replaceTokens(obj) {
obj = cloneDeep(obj);
let dict = {};
@ -72,80 +60,58 @@ export function replaceTokens(obj) {
} else if (kind === 'array') {
nextLevel = `${level}[${key}]`;
}
if (typeof value === 'number') value = value.toString();
if (typeof value !== 'string') {
if (isPlainObject(value)) {
const nextKind = 'object';
entries.unshift(...(Object.entries(value).map(
([key, value]) => [key, value, nextLevel, nextKind]
)));
} else if (Array.isArray(value)) {
const nextKind = 'array';
entries.unshift(...(value.map(
(value, index) => [index, value, nextLevel, nextKind]
)));
}
if (isPlainObject(value)) {
const nextKind = 'object';
entries.unshift(...(Object.entries(value).map(
([key, value]) => [key, value, nextLevel, nextKind]
)));
continue;
} else if (Array.isArray(value)) {
const nextKind = 'array';
entries.unshift(...(value.map(
(value, index) => [index, value, nextLevel, nextKind]
)));
continue;
} else if (typeof value !== 'string') {
value = value.toString();
}
const token = nextLevel;
dict[token] = value;
}
const unknowns = [];
Object.values(dict).forEach(value => {
const matches = value.match(/\{\{([^}]+)\}\}/g);
if (!matches) return;
unknowns.push(...matches.map(
curly => curly.match(/\{\{\s*(\S+)\s*\}\}/)[1]
).filter(
token => !dict.hasOwnProperty(token)
));
});
if (unknowns.length) {
throw new Error(
`Unknown token${unknowns.length > 1 ? 's' : ''} ${unknowns.join(', ')}`
);
}
const keys = Object.keys(dict);
const dependencies = {};
const dependents = {};
while (keys.length) {
const key = keys.shift();
if (!dependencies[key]) dependencies[key] = new Set();
if (!dependents[key]) dependents[key] = new Set();
let unresolvedDep;
const matches = dict[key].match(/\{\{([^}]+)\}\}/g);
const matches = dict[key].match(ALL_TOKENS_REGEX);
if (matches) {
matches
.map(curly => curly.match(/\{\{\s*(\S+)\s*\}\}/)[1])
.map(curly => curly.match(BARE_TOKEN_REGEX)[1])
.forEach(token => {
if (!dependents[token]) dependents[token] = new Set();
dependents[token].add(key);
if (!dict.hasOwnProperty(token)) {
throw new Error(`Unknown token ${token}`);
}
dependencies[key].add(token);
});
[...dependencies[key]].forEach(dependency => {
if (dependents[dependency] && dependents[dependency].has(key)) {
if (dependencies[dependency]?.has(key)) {
throw new Error(
`circular dependency between tokens ${key} and ${dependency}`
);
}
if (dict[dependency].match(/\{\{([^}]+)\}\}/g)) {
if (dict[dependency].match(ALL_TOKENS_REGEX)) {
if (!unresolvedDep) {
unresolvedDep = true;
keys.push(key);
}
} else {
const token = `{{${dependency}}}`;
let value = dict[key];
let replaced;
while (true) {
replaced = value.replace(token, dict[dependency]);
if (replaced === value) break;
value = replaced;
}
dict[key] = value;
dict[key] = replaceTokenInString(
dict[key],
dependency,
dict[dependency]
);
}
});
}
@ -166,18 +132,39 @@ export function replaceTokens(obj) {
)));
continue;
} else if (typeof value === 'string') {
Object.entries(dict).forEach(([token, tokenVal]) => {
token = `{{${token}}}`;
let replaced;
while (true) {
replaced = value.replace(token, tokenVal);
if (replaced === value) break;
value = replaced;
}
context[key] = value;
});
const matches = value.match(ALL_TOKENS_REGEX);
if (matches) {
matches
.map(curly => curly.match(BARE_TOKEN_REGEX)[1])
.forEach(token => {
context[key] = replaceTokenInString(
value,
token,
dict[token]
);
});
}
}
}
return obj;
}
function replaceTokenInString(string, token, value) {
return string.replace(
new RegExp(`\\{\\{(\\s?)+${token}(\\s?)+\\}\\}`, 'g'),
value
);
}
// !!! needs refactor because intend to use spawn
export function runCommand(cmd, inherit = true, display) {
logInfo(`Running command ${cyan(display || cmd)}.`);
let out;
if (inherit) {
// execSyncInherit(cmd);
} else {
// out = execSync(cmd);
}
return out;
}