@adobe/aem-cli 15.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +5262 -0
- package/CODE_OF_CONDUCT.md +74 -0
- package/CONTRIBUTING.md +64 -0
- package/LICENSE.txt +264 -0
- package/README.md +147 -0
- package/index.js +25 -0
- package/package.json +97 -0
- package/src/abstract-server.cmd.js +143 -0
- package/src/abstract.cmd.js +42 -0
- package/src/cli-util.js +74 -0
- package/src/cli.js +137 -0
- package/src/config/config-utils.js +49 -0
- package/src/fetch-utils.js +65 -0
- package/src/git-utils.js +265 -0
- package/src/hack.cmd.js +48 -0
- package/src/hack.js +50 -0
- package/src/import.cmd.js +103 -0
- package/src/import.js +110 -0
- package/src/log-common.js +132 -0
- package/src/md5.js +34 -0
- package/src/package.cjs +12 -0
- package/src/server/BaseProject.js +137 -0
- package/src/server/BaseServer.js +205 -0
- package/src/server/HeadHtmlSupport.js +180 -0
- package/src/server/HelixImportProject.js +30 -0
- package/src/server/HelixImportServer.js +207 -0
- package/src/server/HelixProject.js +130 -0
- package/src/server/HelixServer.js +120 -0
- package/src/server/Indexer.js +152 -0
- package/src/server/LiveReload.js +276 -0
- package/src/server/RequestContext.js +205 -0
- package/src/server/utils.js +457 -0
- package/src/up.cmd.js +171 -0
- package/src/up.js +120 -0
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2020 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
|
+
// eslint-disable-next-line max-classes-per-file
|
|
13
|
+
import fs from 'fs';
|
|
14
|
+
import chokidar from 'chokidar';
|
|
15
|
+
import WebSocket from 'faye-websocket';
|
|
16
|
+
import { EventEmitter } from 'events';
|
|
17
|
+
import { createRequire } from 'module';
|
|
18
|
+
|
|
19
|
+
const require = createRequire(import.meta.url);
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Client connection for the live reload server.
|
|
23
|
+
*/
|
|
24
|
+
class ClientConnection extends EventEmitter {
|
|
25
|
+
static nextId() {
|
|
26
|
+
ClientConnection.counter = (ClientConnection.counter || 0) + 1;
|
|
27
|
+
return `ws${ClientConnection.counter}`;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
constructor(req, socket, head) {
|
|
31
|
+
super();
|
|
32
|
+
this.id = ClientConnection.nextId();
|
|
33
|
+
this.ws = new WebSocket(req, socket, head);
|
|
34
|
+
this.ws.onmessage = this._onMessage.bind(this);
|
|
35
|
+
this.ws.onclose = this._onClose.bind(this);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
_onMessage(event) {
|
|
39
|
+
let data = {};
|
|
40
|
+
try {
|
|
41
|
+
data = JSON.parse(event.data);
|
|
42
|
+
} catch {
|
|
43
|
+
// ignore
|
|
44
|
+
}
|
|
45
|
+
switch (data.command) {
|
|
46
|
+
case 'hello':
|
|
47
|
+
return this._cmdHello(data);
|
|
48
|
+
case 'info':
|
|
49
|
+
return this._cmdInfo(data);
|
|
50
|
+
default:
|
|
51
|
+
return {};
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
_onClose(event) {
|
|
56
|
+
if (this.ws) {
|
|
57
|
+
this.ws.close();
|
|
58
|
+
this.ws = null;
|
|
59
|
+
}
|
|
60
|
+
this.emit('end', event);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
_cmdHello() {
|
|
64
|
+
this._send({
|
|
65
|
+
command: 'hello',
|
|
66
|
+
protocols: [
|
|
67
|
+
'http://livereload.com/protocols/official-7',
|
|
68
|
+
],
|
|
69
|
+
serverName: 'aem-simulator',
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
_cmdInfo(data) {
|
|
74
|
+
if (data) {
|
|
75
|
+
this.plugins = data.plugins;
|
|
76
|
+
this.url = data.url;
|
|
77
|
+
}
|
|
78
|
+
return { ...data || {}, id: this.id, url: this.url };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
_send(data) {
|
|
82
|
+
if (this.ws) {
|
|
83
|
+
this.ws.send(JSON.stringify(data));
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
sendReload(files) {
|
|
88
|
+
files.forEach((file) => {
|
|
89
|
+
this._send({
|
|
90
|
+
command: 'reload',
|
|
91
|
+
path: file,
|
|
92
|
+
liveCSS: true,
|
|
93
|
+
reloadMissingCSS: true,
|
|
94
|
+
liveImg: true,
|
|
95
|
+
});
|
|
96
|
+
}, this);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
sendAlert(message) {
|
|
100
|
+
this._send({
|
|
101
|
+
command: 'alert',
|
|
102
|
+
message,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
close() {
|
|
107
|
+
this._onClose({});
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Live reload file watcher and server.
|
|
113
|
+
*/
|
|
114
|
+
export default class LiveReload extends EventEmitter {
|
|
115
|
+
constructor(logger) {
|
|
116
|
+
super();
|
|
117
|
+
// file to request mapping
|
|
118
|
+
this._fileMapping = new Map();
|
|
119
|
+
// pending requests by request id
|
|
120
|
+
this._pending = new Map();
|
|
121
|
+
this._logger = logger;
|
|
122
|
+
|
|
123
|
+
// client connections
|
|
124
|
+
this._connections = {};
|
|
125
|
+
this._liveReloadJSPath = require.resolve('livereload-js/dist/livereload.js');
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
get log() {
|
|
129
|
+
return this._logger;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
startRequest(requestId, pathname) {
|
|
133
|
+
this._pending.set(requestId, {
|
|
134
|
+
pathname,
|
|
135
|
+
files: [],
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
endRequest(requestId) {
|
|
140
|
+
const info = this._pending.get(requestId);
|
|
141
|
+
if (!info) {
|
|
142
|
+
this.log.debug('unable to register accessed files. info does not exist: ', requestId);
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
this._pending.delete(requestId);
|
|
146
|
+
this.registerFiles(info.files, info.pathname);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
registerFiles(files, pathName) {
|
|
150
|
+
this._watcher.add(files);
|
|
151
|
+
files.forEach((file) => {
|
|
152
|
+
this._fileMapping.set(file, pathName);
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
registerFile(requestId, filePath) {
|
|
157
|
+
const info = this._pending.get(requestId);
|
|
158
|
+
if (info) {
|
|
159
|
+
info.files.push(filePath);
|
|
160
|
+
} else {
|
|
161
|
+
this.log.debug(`unable to register file ${filePath}. info for ${requestId} does not exit.`);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
async init(app, httpServer) {
|
|
166
|
+
this._server = httpServer;
|
|
167
|
+
app.get('/__internal__/livereload.js', this._serveLiveReload.bind(this));
|
|
168
|
+
httpServer.on('upgrade', this._onSvrUpgrade.bind(this));
|
|
169
|
+
httpServer.on('error', this._onSvrError.bind(this));
|
|
170
|
+
httpServer.on('close', this._onSvrClose.bind(this));
|
|
171
|
+
this._initWatcher();
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
_initWatcher() {
|
|
175
|
+
let timer = null;
|
|
176
|
+
let modifiedFiles = {};
|
|
177
|
+
|
|
178
|
+
this._watcher = chokidar.watch([], {
|
|
179
|
+
ignored: [/(.*\.swx|.*\.swp|.*~)/],
|
|
180
|
+
persistent: true,
|
|
181
|
+
ignoreInitial: true,
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
this._watcher.on('all', (eventType, file) => {
|
|
185
|
+
modifiedFiles[file] = true;
|
|
186
|
+
if (timer) {
|
|
187
|
+
clearTimeout(timer);
|
|
188
|
+
}
|
|
189
|
+
// debounce a bit in case several files are changed at once
|
|
190
|
+
timer = setTimeout(async () => {
|
|
191
|
+
timer = null;
|
|
192
|
+
// only proceed if watcher not closed.
|
|
193
|
+
if (this._watcher) {
|
|
194
|
+
const files = Object.keys(modifiedFiles);
|
|
195
|
+
modifiedFiles = {};
|
|
196
|
+
|
|
197
|
+
// inform clients
|
|
198
|
+
await this.changed(files);
|
|
199
|
+
}
|
|
200
|
+
}, 100);
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
async stop() {
|
|
205
|
+
if (this._watcher) {
|
|
206
|
+
await this._watcher.close();
|
|
207
|
+
delete this._watcher;
|
|
208
|
+
}
|
|
209
|
+
this._onSvrClose();
|
|
210
|
+
this.log.debug('livereload stopped.');
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
_onSvrUpgrade(req, socket, head) {
|
|
214
|
+
const cx = new ClientConnection(req, socket, head);
|
|
215
|
+
this._connections[cx.id] = cx;
|
|
216
|
+
|
|
217
|
+
socket.on('error', (e) => {
|
|
218
|
+
if (e.code === 'ECONNRESET' || e.code === 'EBADF') {
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
this._onSvrError(e);
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
cx.once('end', () => {
|
|
225
|
+
this.log.debug(`websocket connection closed ${cx.id} (url: ${cx.url})`);
|
|
226
|
+
delete this._connections[cx.id];
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
_onSvrClose() {
|
|
231
|
+
Object.values(this._connections).forEach((cx) => {
|
|
232
|
+
try {
|
|
233
|
+
cx.close();
|
|
234
|
+
} catch (e) {
|
|
235
|
+
this.log.error('error closing connection', e);
|
|
236
|
+
}
|
|
237
|
+
}, this);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
_onSvrError(e) {
|
|
241
|
+
this.log.error(e);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
_serveLiveReload(req, res) {
|
|
245
|
+
res.setHeader('content-type', 'application/javascript');
|
|
246
|
+
fs.createReadStream(this._liveReloadJSPath).pipe(res);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
async changed(files) {
|
|
250
|
+
this.log.debug(`changed files: ${files}`);
|
|
251
|
+
|
|
252
|
+
// map each file to the registered source
|
|
253
|
+
const sources = new Set();
|
|
254
|
+
files.forEach((file) => {
|
|
255
|
+
const mapping = this._fileMapping.get(file);
|
|
256
|
+
if (mapping) {
|
|
257
|
+
sources.add(mapping);
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
const modified = Array.from(sources);
|
|
261
|
+
await this.emit('modified', modified);
|
|
262
|
+
|
|
263
|
+
Object.values(this._connections).forEach((cx) => {
|
|
264
|
+
this.log.debug(`reloading client ${cx.id} (url: ${cx.url})`);
|
|
265
|
+
cx.sendReload(modified);
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
alert(message) {
|
|
270
|
+
this.log.debug(`alert: ${message}`);
|
|
271
|
+
Object.values(this._connections).forEach((cx) => {
|
|
272
|
+
this.log.debug(`alert client ${cx.id} (url: ${cx.url})`);
|
|
273
|
+
cx.sendAlert(message);
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
}
|
|
@@ -0,0 +1,205 @@
|
|
|
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
|
+
import { parse } from 'url';
|
|
13
|
+
import utils from './utils.js';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Context that is used during request handling.
|
|
17
|
+
*
|
|
18
|
+
* @type {module.RequestContext}
|
|
19
|
+
*/
|
|
20
|
+
export default class RequestContext {
|
|
21
|
+
constructor(request, cfg) {
|
|
22
|
+
// see https://github.com/nodejs/node/issues/36550
|
|
23
|
+
const req = {
|
|
24
|
+
...request,
|
|
25
|
+
};
|
|
26
|
+
const { url } = req;
|
|
27
|
+
this._cfg = cfg || {};
|
|
28
|
+
this._url = url;
|
|
29
|
+
const purl = parse(url);
|
|
30
|
+
this._path = purl.pathname || '/';
|
|
31
|
+
this._queryString = purl.search || '';
|
|
32
|
+
this._selector = '';
|
|
33
|
+
this._extension = '';
|
|
34
|
+
this._headers = req.headers || {};
|
|
35
|
+
this._method = req.method || 'GET';
|
|
36
|
+
this._params = req.query || {};
|
|
37
|
+
this._requestId = utils.randomChars(32);
|
|
38
|
+
this._logger = cfg.log;
|
|
39
|
+
|
|
40
|
+
if (req.body && Object.entries(req.body).length > 0) {
|
|
41
|
+
this._body = req.body;
|
|
42
|
+
}
|
|
43
|
+
const lastSlash = this._path.lastIndexOf('/');
|
|
44
|
+
if (lastSlash === this._path.length - 1) {
|
|
45
|
+
// directory request
|
|
46
|
+
const index = 'index.html';
|
|
47
|
+
// append index and remove multiple slashes
|
|
48
|
+
this._path = `${this._path}${index}`.replace(/\/+/g, '/');
|
|
49
|
+
}
|
|
50
|
+
const lastDot = this._path.lastIndexOf('.');
|
|
51
|
+
let relPath = lastDot >= 0 ? this._path.substring(0, lastDot) : this._path;
|
|
52
|
+
|
|
53
|
+
if (lastDot > lastSlash) {
|
|
54
|
+
this._extension = this._path.substring(lastDot + 1);
|
|
55
|
+
} else {
|
|
56
|
+
// append .html
|
|
57
|
+
this._extension = 'html';
|
|
58
|
+
this._path += '.html';
|
|
59
|
+
}
|
|
60
|
+
// check for selector
|
|
61
|
+
const selDot = relPath.lastIndexOf('.');
|
|
62
|
+
if (selDot > lastSlash) {
|
|
63
|
+
this._selector = relPath.substring(selDot + 1);
|
|
64
|
+
relPath = relPath.substring(0, selDot);
|
|
65
|
+
}
|
|
66
|
+
this._relPath = this._path;
|
|
67
|
+
|
|
68
|
+
// prepend any content repository path
|
|
69
|
+
const repoPath = '';
|
|
70
|
+
if (repoPath && repoPath !== '/') {
|
|
71
|
+
relPath = repoPath + relPath;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
this._resourcePath = relPath;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* the original request url
|
|
79
|
+
*/
|
|
80
|
+
get url() {
|
|
81
|
+
return this._url;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* the request path, including any directoryIndex mapping.
|
|
86
|
+
* @returns {*|string}
|
|
87
|
+
*/
|
|
88
|
+
get path() {
|
|
89
|
+
return this._path;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* The helix project configuration.
|
|
94
|
+
* @returns {HelixProject}
|
|
95
|
+
*/
|
|
96
|
+
get config() {
|
|
97
|
+
return this._cfg;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* The request body.
|
|
102
|
+
* @returns {Object}
|
|
103
|
+
*/
|
|
104
|
+
get body() {
|
|
105
|
+
return this._body;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* The path to the resource in the repository.
|
|
110
|
+
* @returns {string}
|
|
111
|
+
*/
|
|
112
|
+
get resourcePath() {
|
|
113
|
+
return this._resourcePath;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* The file extension of the request path.
|
|
118
|
+
* @returns {string|*}
|
|
119
|
+
*/
|
|
120
|
+
get extension() {
|
|
121
|
+
return this._extension;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* the selector of the request path.
|
|
126
|
+
* @returns {string|string}
|
|
127
|
+
*/
|
|
128
|
+
get selector() {
|
|
129
|
+
return this._selector;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* The client request headers.
|
|
134
|
+
* @returns {any | {}}
|
|
135
|
+
*/
|
|
136
|
+
get headers() {
|
|
137
|
+
return this._headers;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* The request method
|
|
142
|
+
* @returns {*|string}
|
|
143
|
+
*/
|
|
144
|
+
get method() {
|
|
145
|
+
return this._method;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* The request params (query)
|
|
150
|
+
* @returns {any | {}}
|
|
151
|
+
*/
|
|
152
|
+
get params() {
|
|
153
|
+
return this._params;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* The relative path. i.e. the request path without the mount path.
|
|
158
|
+
* @returns {*}
|
|
159
|
+
*/
|
|
160
|
+
get relPath() {
|
|
161
|
+
return this._relPath;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Returns the queryString of the url (including the ?)
|
|
166
|
+
* @returns {string}
|
|
167
|
+
*/
|
|
168
|
+
get queryString() {
|
|
169
|
+
return this._queryString;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* The request id.
|
|
174
|
+
* @returns {String}
|
|
175
|
+
*/
|
|
176
|
+
get requestId() {
|
|
177
|
+
return this._requestId;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* The logger;
|
|
182
|
+
* @returns {Logger}
|
|
183
|
+
*/
|
|
184
|
+
get log() {
|
|
185
|
+
return this._logger;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
get json() {
|
|
189
|
+
const o = {
|
|
190
|
+
url: this.url,
|
|
191
|
+
queryString: this.queryString,
|
|
192
|
+
resourcePath: this.resourcePath,
|
|
193
|
+
path: this.path,
|
|
194
|
+
selector: this.selector,
|
|
195
|
+
extension: this.extension,
|
|
196
|
+
method: this.method,
|
|
197
|
+
headers: this.headers,
|
|
198
|
+
params: this.params,
|
|
199
|
+
};
|
|
200
|
+
if (this.body) {
|
|
201
|
+
o.body = this.body;
|
|
202
|
+
}
|
|
203
|
+
return o;
|
|
204
|
+
}
|
|
205
|
+
}
|