UNPKG

6.23 kBJavaScriptView Raw
1/*
2 * Copyright 2018 Adobe. All rights reserved.
3 * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License. You may obtain a copy
5 * of the License at http://www.apache.org/licenses/LICENSE-2.0
6 *
7 * Unless required by applicable law or agreed to in writing, software distributed under
8 * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9 * OF ANY KIND, either express or implied. See the License for the specific language
10 * governing permissions and limitations under the License.
11 */
12
13'use strict';
14
15const path = require('path');
16
17const fse = require('fs-extra');
18const chalk = require('chalk');
19const shell = require('shelljs');
20const glob = require('glob');
21
22const { getOrCreateLogger } = require('./log-common');
23
24const ANSI_REGEXP = RegExp([
25 '[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[a-zA-Z\\d]*)*)?\\u0007)',
26 '(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PRZcf-ntqry=><~]))',
27].join('|'), 'g');
28
29const FILENAME_MAPPING = {
30 _gitignore: '.gitignore',
31 _env: '.env',
32};
33
34function execAsync(cmd) {
35 return new Promise((resolve, reject) => {
36 shell.exec(cmd, (code, stdout, stderr) => {
37 if (code === 0) {
38 resolve(code);
39 } else {
40 reject(stderr);
41 }
42 });
43 });
44}
45
46class DemoCommand {
47 constructor(logger = getOrCreateLogger()) {
48 this._logger = logger;
49 this._name = '';
50 this._dir = process.cwd();
51 this._padding = 50;
52 this._type = 'simple';
53 }
54
55 /**
56 * Returns true if git is installed, otherwise false.
57 */
58 static gitInstalled() {
59 return !!shell.which('git');
60 }
61
62 /**
63 * Returns true if git `user.name` and `user.email` have been configured, otherwise false.
64 */
65 static gitConfigured() {
66 return !!(shell.exec('git config --get user.name').stdout && shell.exec('git config --get user.email').stdout);
67 }
68
69 withName(name) {
70 this._name = name;
71 return this;
72 }
73
74 withDirectory(dir) {
75 if (dir) {
76 this._dir = dir;
77 }
78 return this;
79 }
80
81 withType(type) {
82 this._type = type;
83 return this;
84 }
85
86 msg(txt) {
87 const dl = txt.length - txt.replace(ANSI_REGEXP, '').length;
88 this._logger.info(txt.padEnd(this._padding + dl, ' ') + chalk.green('[ok]'));
89 }
90
91 async initGitRepository(dir) {
92 const pwd = shell.pwd();
93 try {
94 shell.cd(dir);
95 await execAsync('git init -q');
96 await execAsync('git add -A');
97 // https://github.com/adobe/helix-cli/issues/280
98 // bypass pre-commit and commit-msg hooks when doing initial commit (-n,--no-verify)
99 await execAsync('git commit -q -n -m"Initial commit."');
100 this.msg(chalk.yellow('initializing git repository'));
101 } catch (e) {
102 throw Error(`Unable to initialize git repository: ${e}`);
103 } finally {
104 shell.cd(pwd);
105 }
106 }
107
108 async run() {
109 if (!this._name) {
110 throw new Error('init needs name.');
111 }
112 if (!this._dir) {
113 throw new Error('init needs directory.');
114 }
115
116 // git installed?
117 if (!DemoCommand.gitInstalled()) {
118 throw new Error(`
119It seems like Git has not yet been installed on this system.
120See https://git-scm.com/book/en/v2/Getting-Started-Installing-Git for more information.
121`);
122 }
123
124 // #181 cover edge case: make sure git is properly configured
125 if (!DemoCommand.gitConfigured()) {
126 throw new Error(`
127It seems like Git has not yet been setup on this system.
128See https://git-scm.com/book/en/v2/Getting-Started-First-Time-Git-Setup for more information.
129`);
130 }
131
132 this._padding = this._name.length + 45;
133 const projectDir = path.resolve(path.join(this._dir, this._name));
134 const relPath = path.relative(process.cwd(), projectDir);
135 if (await fse.pathExists(projectDir)) {
136 throw new Error(`cowardly rejecting to re-initialize project: ./${relPath}`);
137 }
138
139 try {
140 await fse.ensureDir(projectDir);
141 } catch (e) {
142 throw new Error(`Unable to create project directory: ${e}`);
143 }
144
145 const msgCreating = chalk.yellow('creating');
146 const msgRelPath = chalk.gray(relPath);
147 this.msg(`${msgCreating} ${msgRelPath}`);
148
149 const project = {
150 name: this._name,
151 };
152
153 function processFile(srcFile, dstFile, filter) {
154 if (filter) {
155 return fse.readFile(srcFile, 'utf8').then((text) => {
156 const result = text.replace(/{{\s*project\.name\s*}}/g, project.name);
157 return fse.outputFile(dstFile, result);
158 });
159 }
160 if (srcFile.startsWith('/__enclose_io_memfs__/')) {
161 // Temporary workaround for https://github.com/adobe/helix-cli/issues/654
162 // see also https://github.com/adobe/node-packer/issues/1
163
164 // When we're running inside the binary hlx executable (packaged helix-cli
165 // with embedded node runtime):
166
167 // avoid (directly or indirectly) calling fs.copyFile: it's not (yet)
168 // supported by node-packer
169 return fse.ensureDir(path.dirname(dstFile))
170 .then(() => fse.readFile(srcFile))
171 .then((content) => fse.writeFile(dstFile, content));
172 }
173 return fse.copy(srcFile, dstFile);
174 }
175
176 const root = path.resolve(__dirname, '..', 'demos', this._type);
177 const jobs = glob.sync('**', {
178 cwd: root,
179 absolute: false,
180 dot: true,
181 nodir: true,
182 }).map((f) => {
183 const srcFile = path.resolve(root, f);
184 const dstName = FILENAME_MAPPING[f] || f;
185 const dstFile = path.resolve(projectDir, dstName);
186 const filter = f === 'index.md' || f === 'README.md' || f === 'helix-config.yaml';
187 return processFile(srcFile, dstFile, filter).then(() => {
188 this.msg(`${msgCreating} ${msgRelPath}/${chalk.cyan(path.relative(projectDir, dstFile))}`);
189 });
190 });
191
192 await Promise.all(jobs);
193 await this.initGitRepository(projectDir);
194
195 this._logger.info(chalk`
196Project {cyan ${this._name}} initialized {green successfully} with a simple example.
197For more examples, clone or fork one from http://github.com/adobe/project-helix/.
198
199Next Step: start the development server and test the generated site with:
200{grey $ cd ${this._name}}
201{grey $ hlx up}`);
202 }
203}
204module.exports = DemoCommand;