create-web-config
Version:
A CLI to help you get started building modern web applications.
823 lines (712 loc) • 19.7 kB
JavaScript
;
function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }
var program = require('commander');
var colors = require('colors');
var fsExtra = require('fs-extra');
var path = require('path');
var prompts = _interopDefault(require('prompts'));
var child_process = require('child_process');
var ora = _interopDefault(require('ora'));
var name = "create-web-config";
var version = "1.0.10";
var description = "A CLI to help you get started building modern web applications.";
var author = "Andreas Mehlsen";
var main = "index.cjs.js";
var module$1 = "index.esm.js";
var bugs = {
url: "https://github.com/andreasbm/create-web-config/issues"
};
var homepage = "https://github.com/andreasbm/create-web-config#readme";
var repository = {
type: "git",
url: "git+https://github.com/andreasbm/create-web-config.git"
};
var keywords = ["configuration", "webapp", "custom", "elements", "web", "plugins", "lit-element", "component", "rollup", "lit-html", "template", "tslint", "typescript", "postcss", "browserslist", "scss", "minifying", "build", "tsconfig"];
var scripts = {
b: "rollup -c rollup.config.ts && npm run postbuild",
readme: "readme generate",
postbuild: "node post-build.js",
postversion: "npm run readme && npm run b",
"publish:patch": "np patch --contents=dist --no-cleanup",
"publish:minor": "np minor --contents=dist --no-cleanup",
"publish:major": "np major --contents=dist --no-cleanup",
ncu: "ncu -u -a && npm update && npm install"
};
var bin = {
"create-web-config": "index.cjs.js",
"web-config": "index.cjs.js"
};
var dependencies = {
colors: "^1.4.0",
commander: "^4.0.0",
"fs-extra": "^8.1.0",
ora: "^4.0.2",
path: "^0.12.7",
prompts: "^2.2.1"
};
var devDependencies = {
"@appnest/readme": "^1.2.4",
"@types/fs-extra": "^8.0.1",
"@types/prompts": "^2.0.2",
"@wessberg/rollup-plugin-ts": "^1.1.73",
rollup: "^1.26.3",
"rollup-plugin-commonjs": "^10.1.0",
"rollup-plugin-json": "^4.0.0",
"rollup-plugin-node-resolve": "^5.2.0"
};
var contributors = [{
name: "Andreas Mehlsen",
url: "https://twitter.com/andreasmehlsen",
img: "https://avatars1.githubusercontent.com/u/6267397?s=460&v=4"
}];
var license = "MIT";
var pkg = {
name: name,
version: version,
description: description,
author: author,
main: main,
module: module$1,
bugs: bugs,
homepage: homepage,
repository: repository,
keywords: keywords,
scripts: scripts,
bin: bin,
dependencies: dependencies,
devDependencies: devDependencies,
contributors: contributors,
license: license
};
const NPM_ID = `@appnest/web-config`;
const LIT_HOME_PAGE_FOLDER_NAME = `pages/home`;
const DIST_FOLDER_NAME = `dist`;
const SRC_FOLDER_NAME = `src`;
const names = {
MAIN_TS: `main.ts`,
INDEX_HTML: `index.html`,
MAIN_SCSS: `main.scss`,
ROLLUP_CONFIG_TS: `rollup.config.ts`,
ES_LINT_JSON: `.eslintrc.json`,
ES_LINT_IGNORE: `.eslintignore`,
TS_CONFIG_JSON: `tsconfig.json`,
KARMA_CONFIG_JS: `karma.conf.js`,
PACKAGE_JSON: `package.json`,
TYPINGS_D_TS: `typings.d.ts`,
BROWSERSLISTRC: `.browserslistrc`,
GITIGNORE: `.gitignore`,
ASSETS: `assets`,
MANIFEST_JSON: `manifest.json`,
ROBOTS_TXT: `robots.txt`,
HOME_ELEMENT_TS: `home-element.ts`,
HOME_ELEMENT_TEST_TS: `home-element.test.ts`,
HOME_ELEMENT_SCSS: `home-element.scss`,
README_MD: `README.md`,
SERVICE_WORKER_JS: "sw.js"
};
const browsersListTemplate = config => `last 2 Chrome versions
last 2 Safari versions
last 2 Firefox versions`;
const eslintIgnoreTemplate = config => `/node_modules/
/dist/`;
const gitignoreTemplate = config => `# See http://help.github.com/ignore-files/ for more about ignoring files.
.DS_Store
ec2-user-key-pair.pem
/tmp
env.json
package-lock.json
# compiled output
/dist
# dependencies
/node_modules
/functions/node_modules
# IDEs and editors
/.idea
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# IDE - VSCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
# misc
/.sass-cache
/connect.lock
/coverage/*
/libpeerconnection.log
npm-debug.log
testem.log
logfile
# e2e
/e2e/*.js
/e2e/*.map
#System Files
.DS_Store
Thumbs.db
dump.rdb
/compiled/
/.idea/
/.cache/
/.rpt2_cache/
/.vscode/
*.log
/logs/
npm-debug.log*
/lib-cov/
/coverage/
/.nyc_output/
/.grunt/
*.7z
*.dmg
*.gz
*.iso
*.jar
*.rar
*.tar
*.zip
.tgz
.env
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
*.pem
*.p12
*.crt
*.csr
/node_modules/
/dist/
/documentation/`;
const homeElementScssTemplate = config => `:host {
color: red;
}`;
const homeElementTestTemplate = config => `import "./home-element";
import HomeElement from "./home-element";
describe("home-element", () => {
let {expect} = chai;
let $elem: HomeElement;
let $container: HTMLElement;
before(() => {
$container = document.createElement("div");
document.body.appendChild($container);
});
beforeEach(async () => {
$container.innerHTML = \`<home-element></home-element>\`;
await window.customElements.whenDefined("home-element");
$elem = $container.querySelector<HomeElement>("home-element")!;
});
after(() => $container.remove());
it("should be able to be stamped into the DOM", () => {
expect($elem).to.exist;
});
});`;
const homeElementTsTemplate = config => `import { customElement, html, LitElement, unsafeCSS } from "lit-element";
import css from "./home-element.scss";
import "weightless/button";
@customElement("home-element")
export default class HomeElement extends LitElement {
static styles = [unsafeCSS(css)];
render () {
return html\`
<wl-button>Welcome</wl-button>
\`;
}
}
declare global {
interface HTMLElementTagNameMap {
"home-element": HomeElement;
}
}`;
const indexTemplate = ({
dir,
lit
}) => `<!DOCTYPE html>
<html lang="en">
<head>
<base href="/">
<title>${dir}</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#ffffff">
<meta name="description" content="This project was generated using the 'npm init web-config' command">
<link rel="manifest" href="/assets/manifest.json">
</head>
<body>
<p>${dir}</p>
${lit ? `<router-slot></router-slot>` : ""}
<noscript>
<p>Please enable Javascript in your browser.</p>
</noscript>
</body>
</html>`;
const karmaTemplate = ({
src
}) => `const {defaultResolvePlugins, defaultKarmaConfig} = require("@appnest/web-config");
module.exports = (config) => {
config.set({
...defaultKarmaConfig({
rollupPlugins: defaultResolvePlugins()
}),
basePath: "${src}",
logLevel: config.LOG_INFO
});
};`;
const mainScssTemplate = config => `html { font-size: 14px; }`;
const registerSwTemplate = config => `// Register the service worker
if ("serviceWorker" in navigator) {
navigator.serviceWorker.register("/${names.SERVICE_WORKER_JS}").then(res => {
console.log(\`Service worker registered\`, res);
});
}`;
const mainTsLitTemplate = config => `import "main.scss";
import "@appnest/web-router";
import {RouterSlot} from "@appnest/web-router";
customElements.whenDefined("router-slot").then(async () => {
const routerSlot = document.querySelector<RouterSlot>("router-slot")!;
await routerSlot.add([
{
path: "home",
component: () => import("./${LIT_HOME_PAGE_FOLDER_NAME}/home-element")
},
{
path: "**",
redirectTo: "home"
}
]);
});`;
const mainTsDetaulTemplate = config => `import "main.scss";`;
const mainTsTemplate = config => {
const {
lit,
sw
} = config;
return `${lit ? mainTsLitTemplate(config) : mainTsDetaulTemplate(config)}${sw ? `
${registerSwTemplate(config)}` : ""}`;
};
const manifestTemplate = ({
dir
}) => `{
"name": "${dir}",
"short_name": "${dir}",
"theme_color": "#ffffff",
"background_color": "#ffffff",
"display": "standalone",
"start_url": "/",
"lang": "en-US",
"icons": [
{
"src": "https://raw.githubusercontent.com/andreasbm/weightless/master/assets/www/android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "https://raw.githubusercontent.com/andreasbm/weightless/master/assets/www/android-chrome-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
]
}`;
const packageTemplate = ({
dir
}) => `{
${dir != "" ? `"name": "${dir}",` : ""}
"scripts": {
"b:dev": "rollup -c rollup.config.ts --environment NODE_ENV:dev",
"b:prod": "rollup -c rollup.config.ts --environment NODE_ENV:prod",
"s:dev": "rollup -c rollup.config.ts --watch --environment NODE_ENV:dev",
"s:prod": "rollup -c rollup.config.ts --watch --environment NODE_ENV:prod",
"s": "npm run s:dev",
"test": "karma start karma.conf.js"
}
}`;
const readmeTemplate = ({
dir
}) => `# ${dir}
This project was built using the [create-web-config](https://github.com/andreasbm/create-web-config) CLI.
## Usage
* Run \`npm run s\` to serve your project.
* Run \`npm run b:dev\` to build your project for development.
* Run \`npm run b:prod\` to build your project for production.
* Run \`npm run test\` to test the application.`;
const robotsTemplate = config => `User-agent: *
Allow: /`;
const rollupConfigTemplate = ({
sw,
dist,
src,
lit
}) => `import {resolve, join} from "path";
import {
defaultOutputConfig,
defaultPlugins,
defaultProdPlugins,
defaultServePlugins,
isProd,
isServe${sw ? `,
workbox` : ""}
} from "${NPM_ID}";
const folders = {
dist: resolve(__dirname, "${dist}"),
src: resolve(__dirname, "${src}"),
src_assets: resolve(__dirname, "${src}/${names.ASSETS}"),
dist_assets: resolve(__dirname, "${dist}/${names.ASSETS}")
};
const files = {
main: join(folders.src, "${names.MAIN_TS}"),
src_index: join(folders.src, "${names.INDEX_HTML}"),
src_robots: join(folders.src, "${names.ROBOTS_TXT}"),
dist_index: join(folders.dist, "${names.INDEX_HTML}"),
dist_robots: join(folders.dist, "${names.ROBOTS_TXT}")${sw ? `,
dist_service_worker: join(folders.dist, "${names.SERVICE_WORKER_JS}")` : ""}
};
export default {
input: {
main: files.main
},
output: [
defaultOutputConfig({
dir: folders.dist,
format: "esm"
})
],
plugins: [
...defaultPlugins({
cleanConfig: {
targets: [
folders.dist
]
},
copyConfig: {
resources: [
[files.src_robots, files.dist_robots],
[folders.src_assets, folders.dist_assets]
]
},
htmlTemplateConfig: {
${lit ? `polyfillConfig: {
features: ["es", "template", "shadow-dom", "custom-elements"]
},` : ""}
template: files.src_index,
target: files.dist_index,
include: /main(-.*)?\\.js$/
},
importStylesConfig: {
globals: ["${names.MAIN_SCSS}"]
}
}),
// Serve
...(isServe ? defaultServePlugins({
dist: folders.dist
}) : []),
// Production
...(isProd ? defaultProdPlugins({
dist: folders.dist,
budgetConfig: {
sizes: {
".js": 1024 * 170,
".jpg": 1024 * 400
}
}
}) : [])${sw ? `,
// Service worker
workbox({
mode: "generateSW",
workboxConfig: {
globDirectory: folders.dist,
swDest: files.dist_service_worker,
globPatterns: [ \`**/*.{js,png,html,css}\`]
}
})` : ""}
],
treeshake: isProd,
context: "window"
}`;
const tsconfigTemplate = config => `{
"extends": "./node_modules/@appnest/web-config/tsconfig.json"
}`;
const eslintTemplate = config => `{
"extends": "./node_modules/@appnest/web-config/eslint.js"
}`;
const typingsTemplate = config => `/// <reference path="node_modules/@appnest/web-config/typings.d.ts" />`;
/**
* Install dependencies.
* @param deps
* @param development
* @param dir
*/
async function install(deps, {
development = false,
dir = ""
}) {
await run(`cd ${path.resolve(process.cwd(), dir)} && npm i ${deps.join(" ")} ${development ? `-D` : ""}`);
}
/**
* Writes a file to the correct path.
* @param name
* @param content
* @param config
*/
function writeFile(name, content, config) {
const path$1 = path.join(path.resolve(process.cwd(), config.dir), name); // Check if the file exists and if we should then abort
if (!config.overwrite && fsExtra.existsSync(path$1)) {
return;
}
console.log(colors.green(`✔ Creating "${name}"`)); // Check if the command is dry.
if (config.dry) {
console.log(content);
return;
}
fsExtra.outputFileSync(path$1, content);
}
/**
* Creates a directory.
* @param dir
* @param config
*/
function createDirectory(dir, config) {
console.log(colors.green(`✔ Creating directory "${dir}"`)); // Check if the command is dry.
if (config.dry) {
return;
}
const path$1 = path.resolve(process.cwd(), path.join(config.dir, dir));
fsExtra.mkdirpSync(path$1);
}
/**
* Runs a command.
* @param cmd
*/
function run(cmd) {
return new Promise((res, rej) => {
child_process.exec(cmd, error => {
if (error !== null) {
return rej(error);
}
res();
});
});
}
/**
* Install dependencies.
* @param dry
* @param lit
* @param dir
*/
async function installDependencies({
dry,
lit,
dir
}) {
const spinner = ora(colors.green(`︎Installing dependencies...`)).start();
function finish() {
spinner.succeed(colors.green(`Finished installing dependencies`));
} // Check if the command is dry
if (dry) {
finish();
return;
} // Run the command
try {
await install(["@appnest/web-config"], {
dir,
development: true
}); // Install lit related dependencies
if (lit) {
await install(["@appnest/web-router", "lit-element", "weightless"], {
dir
});
}
finish();
} catch (err) {
spinner.warn(`Could not install dependencies: ${err.message}`);
}
}
/**
* Asks the user for input and returns a configuration object for the command.
* @param options
*/
async function getNewCommandConfig(options) {
const {
dir
} = options;
let overwrite = true;
if (fsExtra.existsSync(path.resolve(process.cwd(), dir))) {
const input = await prompts({
type: "confirm",
name: "overwrite",
message: `The directory "${dir}" already exists. Do you want to overwrite existing files?`,
initial: true
}, {
onCancel: () => {
process.exit(1);
}
});
overwrite = input.overwrite;
}
return {
overwrite,
...options
};
}
/**
* Setup rollup.config.ts
* @param config
*/
function setupRollupConfig(config) {
const content = rollupConfigTemplate(config);
writeFile(names.ROLLUP_CONFIG_TS, content, config);
}
/**
* Setup tslint.json
* @param config
*/
function setupEslint(config) {
const content = eslintTemplate(config);
writeFile(names.ES_LINT_JSON, content, config);
}
/**
* Setup .eslintignore
* @param config
*/
function setupEslintIgnore(config) {
const content = eslintIgnoreTemplate(config);
writeFile(names.ES_LINT_IGNORE, content, config);
}
/**
* Setup tsconfig.json
* @param config
*/
function setupTsconfig(config) {
const content = tsconfigTemplate(config);
writeFile(names.TS_CONFIG_JSON, content, config);
}
/**
* Setup .browserslistrc
* @param config
*/
function setupBrowserslist(config) {
const content = browsersListTemplate(config);
writeFile(names.BROWSERSLISTRC, content, config);
} // Step 6 - Setup karma.conf.js
function setupKarma(config) {
const content = karmaTemplate(config);
writeFile(names.KARMA_CONFIG_JS, content, config);
}
/**
* Add start and build scripts to package.json
* @param config
*/
function setupScripts(config) {
const content = packageTemplate(config);
writeFile(names.PACKAGE_JSON, content, config);
}
/**
* Setup typings
* @param config
*/
function setupTypings(config) {
const content = typingsTemplate(config);
writeFile(names.TYPINGS_D_TS, content, config);
}
/**
* Setup gitignore
* @param config
*/
function setupGitIgnore(config) {
const content = gitignoreTemplate(config);
writeFile(names.GITIGNORE, content, config);
}
/**
* Setup base files.
*/
function setupBaseFiles(config) {
const {
lit,
src
} = config;
const mainScssContent = mainScssTemplate(config);
const readmeMdContent = readmeTemplate(config);
const robotsTxtContent = robotsTemplate(config);
const indexContent = indexTemplate(config);
const manifestJsonContent = manifestTemplate(config);
const mainTsContent = mainTsTemplate(config);
createDirectory(path.join(src, names.ASSETS), config);
writeFile(path.join(src, names.ASSETS, names.MANIFEST_JSON), manifestJsonContent, config);
writeFile(path.join(src, names.MAIN_TS), mainTsContent, config);
writeFile(path.join(src, names.MAIN_SCSS), mainScssContent, config);
writeFile(path.join(src, names.INDEX_HTML), indexContent, config);
writeFile(path.join(src, names.ROBOTS_TXT), robotsTxtContent, config);
writeFile(names.README_MD, readmeMdContent, config); // Write the lit specific files if necessary
if (lit) {
const homeElementTsContent = homeElementTsTemplate(config);
const homeElementScssContent = homeElementScssTemplate(config);
const homeElementTsTestContent = homeElementTestTemplate(config);
writeFile(path.join(src, names.MAIN_TS), mainTsContent, config);
writeFile(path.join(src, LIT_HOME_PAGE_FOLDER_NAME, names.HOME_ELEMENT_TS), homeElementTsContent, config);
writeFile(path.join(src, LIT_HOME_PAGE_FOLDER_NAME, names.HOME_ELEMENT_SCSS), homeElementScssContent, config);
writeFile(path.join(src, LIT_HOME_PAGE_FOLDER_NAME, names.HOME_ELEMENT_TEST_TS), homeElementTsTestContent, config);
}
}
/**
* Executes the new command.
* @param options
*/
async function newCommand(options) {
const {
dir,
install
} = options;
const config = await getNewCommandConfig(options);
setupRollupConfig(config);
setupEslint(config);
setupEslintIgnore(config);
setupTsconfig(config);
setupBrowserslist(config);
setupKarma(config);
setupScripts(config);
setupTypings(config);
setupGitIgnore(config);
setupBaseFiles(config); // Only install if specified
if (install) {
await installDependencies(config);
} // Tell the user that everything worked!
console.log(colors.green(`✔ Finished creating project in "${path.resolve(process.cwd(), dir)}" 🎉`));
console.log(`What's next?
→ Run "${colors.green("npm run s")}" to serve your project.
→ Run "${colors.green("npm run b:dev")}" to build your project for development.
→ Run "${colors.green("npm run b:prod")}" to build your project for production.`);
}
program.version(pkg.version);
program.command("new <dir>").description("Setup a new project from scratch.").option(`--dry`, `Runs the command without writing any files.`).option(`--lit`, `Adds lit-element and various webapp related libraries to the setup.`).option(`--no-install`, `Doesn't install node_modules.`).option(`--sw`, `Adds a service worker to the setup.`).option(`--src <string>`, `Name of the folder with the transpiled output`, SRC_FOLDER_NAME).option(`--dist <string>`, `Name of the folder with the source code`, DIST_FOLDER_NAME).action((dir, cmd) => {
const {
dry,
lit,
install,
sw,
src,
dist
} = cmd;
newCommand({
dir,
dry,
lit,
install,
sw,
src,
dist
}).then();
}); // Do some error handling
const userArgs = process.argv.slice(2);
if (userArgs.length === 0) {
program.help();
} // Handle unknown commands
program.on("command:*", () => {
console.error(`Invalid command: ${userArgs.join(" ")}\nSee --help for a list of available commands.`);
process.exit(1);
}); // Parse the input
program.parse(process.argv);