1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 | 'use strict';
|
14 |
|
15 | const path = require('path');
|
16 |
|
17 | const fse = require('fs-extra');
|
18 | const chalk = require('chalk');
|
19 | const shell = require('shelljs');
|
20 | const glob = require('glob');
|
21 |
|
22 | const { getOrCreateLogger } = require('./log-common');
|
23 |
|
24 | const 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 |
|
29 | const FILENAME_MAPPING = {
|
30 | _gitignore: '.gitignore',
|
31 | _env: '.env',
|
32 | };
|
33 |
|
34 | function 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 |
|
46 | class 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 |
|
57 |
|
58 | static gitInstalled() {
|
59 | return !!shell.which('git');
|
60 | }
|
61 |
|
62 | |
63 |
|
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 |
|
98 |
|
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 |
|
117 | if (!DemoCommand.gitInstalled()) {
|
118 | throw new Error(`
|
119 | It seems like Git has not yet been installed on this system.
|
120 | See https://git-scm.com/book/en/v2/Getting-Started-Installing-Git for more information.
|
121 | `);
|
122 | }
|
123 |
|
124 |
|
125 | if (!DemoCommand.gitConfigured()) {
|
126 | throw new Error(`
|
127 | It seems like Git has not yet been setup on this system.
|
128 | See 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 |
|
162 |
|
163 |
|
164 |
|
165 |
|
166 |
|
167 |
|
168 |
|
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`
|
196 | Project {cyan ${this._name}} initialized {green successfully} with a simple example.
|
197 | For more examples, clone or fork one from http://github.com/adobe/project-helix/.
|
198 |
|
199 | Next Step: start the development server and test the generated site with:
|
200 | {grey $ cd ${this._name}}
|
201 | {grey $ hlx up}`);
|
202 | }
|
203 | }
|
204 | module.exports = DemoCommand;
|