Skip to content
Merged
Show file tree
Hide file tree
Changes from 47 commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
71a6b6c
install `gittar`
lukeed Aug 19, 2017
d4ff638
remove flags: css,sass,less,dest,type
lukeed Aug 19, 2017
6c3c3aa
purge *css flag-related stuffs
lukeed Aug 19, 2017
b7f4cdc
extract yarn-vs-npm logic before printing
lukeed Aug 19, 2017
427b22e
replace ansi-codes with `chalk.green`
lukeed Aug 19, 2017
8ab7688
extract `pkgFile` path
lukeed Aug 19, 2017
6b878d4
use `argv.name` within pkg & manifest (Closes #326)
lukeed Aug 20, 2017
86a4f3b
extract an `error` helper
lukeed Aug 20, 2017
1ee0dfc
uninstall `recursive-copy`
lukeed Aug 20, 2017
5e4fdb0
extract `isDir` helper
lukeed Aug 20, 2017
a23765e
replace `copy` with `gittar.extract` - with filter
lukeed Aug 20, 2017
172ff54
move `target` + `exists` + `force` logic first;
lukeed Aug 20, 2017
f04532f
use `argv.dest` for final instructions
lukeed Aug 20, 2017
4dc79c0
fix typo
lukeed Aug 20, 2017
8daf606
export `setup` functions directly
lukeed Aug 20, 2017
eccca4f
remove debug-type stdout from `initGit`
lukeed Aug 20, 2017
15f1b6b
move common helpers to `util` file;
lukeed Aug 20, 2017
45b28c2
move `commandExists` —> `util.hasCommand`;
lukeed Aug 20, 2017
ce8d316
check if `yarn` bin exists on flag parsing;
lukeed Aug 20, 2017
60e4830
consolidate `setup.pkgScripts` function
lukeed Aug 20, 2017
34379f5
do not write a `gitignore` file
lukeed Aug 20, 2017
8dc0583
minor cleanup, warn if no `git` binary found
lukeed Aug 20, 2017
da46a8b
oops, fix regex tester
lukeed Aug 20, 2017
1e18c1a
don’t assume exit within `util.error`
lukeed Aug 20, 2017
c443554
use `warn` within watch command
lukeed Aug 20, 2017
39091a7
use `util.isDir` within serve command
lukeed Aug 20, 2017
8af1c30
remove `init` command publicly
lukeed Aug 20, 2017
dfec01b
Merge `master` into `templates`
lukeed Aug 20, 2017
c3430c7
alias “official” preact-cli templates by name
lukeed Aug 21, 2017
54b93ae
simplify `install` — install template deps
lukeed Aug 21, 2017
2f71fd3
move `trimLeft` —> `util.trim`
lukeed Aug 21, 2017
a18f968
remove `initialize` from setup;
lukeed Aug 21, 2017
2fd9522
simplify package-related `create`
lukeed Aug 21, 2017
6e1509f
only extract `/template/` filepaths
lukeed Aug 21, 2017
e9463b8
add extra TODO note
lukeed Aug 21, 2017
92fec7a
define `done` handler before export
lukeed Aug 22, 2017
6d0493b
update `test/subjects` names
lukeed Aug 22, 2017
32acd00
[MAJOR] tests refactor
lukeed Aug 23, 2017
1fb1133
move `serve.snapshot` —>`images/serve`
lukeed Aug 23, 2017
771f43c
add `deprecated` notice on `build.snapshot` file
lukeed Aug 23, 2017
1a994b7
remove unused `exists` helper
lukeed Aug 23, 2017
d7f748a
Merge branch 'master' into templates
reznord Aug 23, 2017
ca3b235
Fix tests/image/create.js test error
reznord Aug 23, 2017
85dfe85
Revert "Fix tests/image/create.js test error"
lukeed Aug 23, 2017
fb8f273
[Travis] only build `master` branch
lukeed Aug 23, 2017
b3380e0
remove `commands/init` file
lukeed Aug 23, 2017
f8f7ec4
add console statement for debug
lukeed Aug 23, 2017
1880a38
remove `dependency-install-loader`
lukeed Aug 26, 2017
bc7af44
update scripts path
lukeed Aug 26, 2017
0ed79d1
exit early if run `build` w/o installing deps;
lukeed Aug 26, 2017
faa8cad
always install deps for build subjects
lukeed Aug 26, 2017
741774e
remove `SKIP_INSTALL` global for Travis (temp)
lukeed Aug 26, 2017
7c1ad41
assume any template without `/` is official lookup
lukeed Sep 3, 2017
e7ee7f7
update tests, remove `full` alias
lukeed Sep 3, 2017
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ node_js:
- 6
- 8

branches:
only:
- master

cache:
npm: true
directories:
Expand All @@ -18,7 +22,7 @@ cache:

before_script:
- export LIGHTHOUSE_CHROMIUM_PATH=`which google-chrome-stable`
- if [ "$TRAVIS_BRANCH" = "master" ]; then export WITH_INSTALL=true; export WITH_LOG=true; fi
- if [ "$TRAVIS_BRANCH" = "master" ]; then export SKIP_INSTALL=true; export WITH_LOG=true; fi

script:
- npm run lint
Expand Down
6 changes: 0 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,12 +95,6 @@ $ preact init
$ preact create

--name Directory and package name for the new app.
--dest Directory to create the app within. [default: <name>]
--type A project template to start from.
[Options: "full", "root", "simple", "empty"] [default: "full"]
--less Pre-install LESS support. [boolean] [default: false]
--sass Pre-install SASS/SCSS support. [boolean] [default: false]
--stylus Pre-install STYLUS support. [boolean] [default: false]
--git Initialize version control using git. [boolean] [default: false]
--no-install Disables installing of dependencies. [boolean] [default: false]
--yarn Installs dependencies with yarn. [boolean] [default: false]
Expand Down
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"lint": "eslint src tests",
"pretest": "npm run -s build && rimraf ./tests/output",
"test": "jest",
"posttest": "rimraf ./tests/output",
"posttest": "rimraf tests/output",
"postinstall": "node -p 'require(\"./check.js\")()'",
"test:build": "babel-node src build --cwd examples/root",
"test:serve": "npm run -s test:build && babel-node src serve --port 3000 --cwd examples/root",
Expand Down Expand Up @@ -90,7 +90,6 @@
"eslint-plugin-react": "^7.0.1",
"html-looks-like": "^1.0.2",
"jest": "^20.0.4",
"lodash": "^4.17.4",
"ncp": "^2.0.0",
"uuid": "^3.0.1"
},
Expand Down Expand Up @@ -121,6 +120,7 @@
"file-loader": "^0.11.1",
"fs.promised": "^3.0.0",
"get-port": "^3.1.0",
"gittar": "^0.1.0",
"glob": "^7.1.2",
"html-webpack-exclude-assets-plugin": "0.0.5",
"html-webpack-plugin": "^2.28.0",
Expand All @@ -131,6 +131,7 @@
"less": "^2.7.2",
"less-loader": "^4.0.3",
"loader-utils": "^1.1.0",
"log-symbols": "^2.0.0",
"minimatch": "^3.0.3",
"mkdirp": "^0.5.1",
"offline-plugin": "^4.6.2",
Expand All @@ -144,7 +145,6 @@
"progress-bar-webpack-plugin": "^1.9.3",
"promise-polyfill": "^6.0.2",
"raw-loader": "^0.5.1",
"recursive-copy": "^2.0.6",
"require-relative": "^0.8.7",
"rimraf": "^2.6.1",
"script-ext-html-webpack-plugin": "^1.8.0",
Expand Down
252 changes: 96 additions & 156 deletions src/commands/create.js
Original file line number Diff line number Diff line change
@@ -1,68 +1,40 @@
import asyncCommand from '../lib/async-command';
import fs from 'fs.promised';
import copy from 'recursive-copy';
import glob from 'glob';
import mkdirp from 'mkdirp';
import { resolve } from 'path';
import ora from 'ora';
import chalk from 'chalk';
import inquirer from 'inquirer';
import path from 'path';
import { install, initialize, pkgScripts, initGit, trimLeft } from './../lib/setup';
import glob from 'glob';
import gittar from 'gittar';
import { green } from 'chalk';
import { prompt } from 'inquirer';
import asyncCommand from '../lib/async-command';
import { install, initGit, addScripts } from './../lib/setup';
import { isDir, hasCommand, error, trim, warn } from '../util';

const TEMPLATES = {
full: 'examples/full',
empty: 'examples/empty',
root: 'examples/root',
simple: 'examples/simple'
full: 'preactjs-templates/default',
default: 'preactjs-templates/default',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why both full & default? Since they do the same thing.

Instead we can just add default & simple from templates.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should add simple. @developit requested in Slack that we make default an alias of full

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was just thinking "principle of least surprise" here, this makes it work quite similarly to how it does in 1.0.

// simple: 'examples/simple',
// empty: 'examples/empty',
// root: 'examples/root',
};

export default asyncCommand({
command: 'create <name> [dest]',
command: 'create <template> <dest>',

desc: 'Create a new application.',

builder: {
name: {
description: 'directory and package name for the new app'
},
dest: {
description: 'Directory to create the app within',
defaultDescription: '<name>'
description: 'The application\'s name'
},
force: {
description: 'Force option to create the directory for the new app',
default: false
},
type: {
description: 'A project template to start from',
choices: [
'full',
'root',
'simple',
'empty'
],
default: 'full'
},
yarn: {
description: "Use 'yarn' instead of 'npm'",
type: 'boolean',
default: false
},
less: {
description: 'Pre-install LESS support',
type: 'boolean',
default: false
},
sass: {
description: 'Pre-install SASS/SCSS support',
type: 'boolean',
default: false
},
stylus: {
description: 'Pre-install STYLUS support',
type: 'boolean',
default: false
},
git: {
description: 'Initialize version control using git',
type: 'boolean',
Expand All @@ -76,153 +48,121 @@ export default asyncCommand({
},

async handler(argv) {
let template = TEMPLATES[argv.type];

if (!template) {
throw Error(`Unknown app template "${argv.type}".`);
}

let target = path.resolve(process.cwd(), argv.dest || argv.name);
let isYarn = argv.yarn && hasCommand('yarn');
let cwd = argv.cwd ? resolve(argv.cwd) : process.cwd();
let target = argv.dest && resolve(cwd, argv.dest);
let exists = target && isDir(target);

if (target) {
if (exists && !argv.force) {
return error('Refusing to overwrite current directory! Please specify a different destination or use the `--force` flag', 1);
}

let exists = false;
try {
exists = (await fs.stat(target)).isDirectory();
}
catch (err) {}

if (exists && argv.force) {
const question = {
type: 'confirm',
name: 'enableForce',
message: `You are using '--force'. Do you wish to continue?`,
default: false,
};

let { enableForce } = await inquirer.prompt(question);

if (enableForce) {
process.stdout.write('Initializing project in the current directory...\n');
} else {
process.stderr.write(chalk.red('Error: Cannot initialize in the current directory\n'));
process.exit(1);
if (exists && argv.force) {
let { enableForce } = await prompt({
type: 'confirm',
name: 'enableForce',
message: `You are using '--force'. Do you wish to continue?`,
default: false
});

if (enableForce) {
process.stdout.write('Initializing project in the current directory...\n');
} else {
return error('Refusing to overwrite current directory!', 1);
}
}
} else {
// TODO: interactive
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@developit Should we really do this? This make it more complex for us to handle.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's save this discussion for a future PR. I want to break it up instead of making this (even more) massive.

}

if (exists && !argv.force) {
process.stderr.write(chalk.red('Error: Cannot initialize in the current directory, please specify a different destination or use --force\n'));
process.exit(1);
}
let repo = TEMPLATES[argv.template] || argv.template;

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be reasonable here to do something like: "if repo has no /, assume preactjs-templates/${repo}"? That way any template we add to the preactjs-templates org becomes a default option.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure. Or just include new official templates (should not be often!) as patch releases.

// Attempt to fetch the `template`
let archive = await gittar.fetch(repo).catch(err => {
err = err || { message:'An error occured while fetching template.' };
return error(err.code === 404 ? `Could not find repostory: ${repo}` : err.message, 1);
});

let spinner = ora({
text: 'Creating project',
color: 'magenta'
}).start();

if (!exists) {
await Promise.promisify(mkdirp)(target);
}

await copy(
path.resolve(__dirname, '../..', template),
target,
{ filter: ['**/*', '!build'] }
);

spinner.text = 'Initializing project';

await initialize(argv.yarn, target);
// Extract files from `archive` to `target`
await gittar.extract(archive, target, {
strip: 2,
filter(path) {
// TODO: remove `/build/` ??
// TODO: read & respond to meta/hooks
return path.includes('/template/') && !path.includes('/build/');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we don't find templates/ folder, we can throw an error saying that you need a specific folder structure or sth like that.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. Let's do that in another PR.

return error(`No \`template\` directory found within ${ repo }!`);

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could even detect template/ and if it doesn't exist, assume the template is at the root. I'm fine with either, and a subsequent PR seems fine.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we need to force a template structure. When things become too flexible they become less clear and harder to debug.

}
});

let pkg = JSON.parse(await fs.readFile(path.resolve(target, 'package.json')));
spinner.text = 'Parsing `package.json` file';

pkg.scripts = await pkgScripts(argv.yarn, pkg);
// Validate user's `package.json` file
let pkgData;
let pkgFile = resolve(target, 'package.json');

try {
await fs.stat(path.resolve(target, 'src'));
}
catch (err) {
pkg.scripts.test = pkg.scripts.test.replace('src', '.');
if (pkgFile) {
pkgData = JSON.parse(await fs.readFile(pkgFile));
// Write default "scripts" if none found
pkgData.scripts = pkgData.scripts || (await addScripts(pkgData, target, isYarn));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe instead of making scripts a wholesale override, we could merge our scripts into the template's?

pkgData.scripts = {
  ...(pkgData.scripts || {}),
  ...(await addScripts(pkgData, target, isYarn))
};

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we could merge our scripts into the template's?

I don't think there is a necessary for that since templates will have scripts defined in the package.json file (includes our scripts too)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just sucks if the cli wants to change script down the line 😋

} else {
warn('Could not locate `package.json` file!');
}

pkg.eslintConfig = {
extends: 'eslint-config-synacor'
};
if (argv.name) {
spinner.text = 'Updating `name` within `package.json` file';
// Update `package.json` key
pkgData && (pkgData.name = argv.name);
// Find a `manifest.json`; use the first match, if any
let files = await Promise.promisify(glob)(target + '/**/manifest.json');
let manifest = files[0] && JSON.parse(await fs.readFile(files[0]));
if (manifest) {
spinner.text = 'Updating `name` within `manifest.json` file';
manifest.name = manifest.short_name = argv.name;
// Write changes to `manifest.json`
await fs.writeFile(files[0], JSON.stringify(manifest, null, 2));
if (argv.name.length > 12) {
// @see https://developer.chrome.com/extensions/manifest/name#short_name
process.stdout.write('\n');
warn('Your `short_name` should be fewer than 12 characters.');
}
}
}

await fs.writeFile(path.resolve(target, 'package.json'), JSON.stringify(pkg, null, 2));
if (pkgData) {
// Assume changes were made ¯\_(ツ)_/¯
await fs.writeFile(pkgFile, JSON.stringify(pkgData, null, 2));
}

if (argv.install) {
spinner.text = 'Installing dev dependencies';

await install(argv.yarn, target, [
'preact-cli',
'if-env',
'eslint',
'eslint-config-synacor',

// install sass setup if --sass
...(argv.sass ? [
'node-sass',
'sass-loader'
] : []),

// install less setup if --less
...(argv.less ? [
'less',
'less-loader'
] : []),

// install stylus if --stylus
...(argv.stylus ? [
'stylus',
'stylus-loader'
] : [])
], 'dev');

spinner.text = 'Installing dependencies';

await install(argv.yarn, target, [
'preact',
'preact-compat',
'preact-router'
]);
await install(target, isYarn);
}

spinner.succeed('Done!\n');

if (argv.less || argv.sass || argv.stylus) {
let extension;

if (argv.less) extension = '.less';
if (argv.sass) extension = '.scss';
if (argv.stylus) extension = '.styl';

const cssFiles = await Promise.promisify(glob)(`${target}/**/*.css`, {
ignore: [
`${target}/build/**`,
`${target}/node_modules/**`
]
});

const changeExtension = fileName => fs.rename(fileName, fileName.replace(/.css$/, extension));

await Promise.all(cssFiles.map(changeExtension));
}

if (argv.git) {
await initGit(target);
}

return trimLeft(`
let pfx = isYarn ? 'yarn' : 'npm run';

return trim(`
To get started, cd into the new directory:
\u001b[32mcd ${path.relative(process.cwd(), target)}\u001b[39m
${ green('cd ' + argv.dest) }

To start a development live-reload server:
\u001b[32m${argv.yarn === true ? 'yarn start' : 'npm start'}\u001b[39m
${ green(pfx + ' start') }

To create a production build (in ./build):
\u001b[32m${argv.yarn === true ? 'yarn build' : 'npm run build'}\u001b[39m
${ green(pfx + ' build') }

To start a production HTTP/2 server:
\u001b[32m${argv.yarn === true ? 'yarn serve' : 'npm run serve'}\u001b[39m
${ green(pfx + ' serve') }
`) + '\n';
}
});
Loading