@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,30 @@
|
|
|
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 { HelixImportServer } from './HelixImportServer.js';
|
|
13
|
+
import { BaseProject } from './BaseProject.js';
|
|
14
|
+
|
|
15
|
+
export class HelixImportProject extends BaseProject {
|
|
16
|
+
constructor() {
|
|
17
|
+
super(HelixImportServer);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async start() {
|
|
21
|
+
this.log.debug('Launching AEM import server for importing content...');
|
|
22
|
+
await super.start();
|
|
23
|
+
return this;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async doStop() {
|
|
27
|
+
this.log.debug('Stopping AEM import server..');
|
|
28
|
+
await super.doStop();
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,207 @@
|
|
|
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 { promisify } from 'util';
|
|
13
|
+
import path from 'path';
|
|
14
|
+
import { PassThrough } from 'stream';
|
|
15
|
+
import { getFetch, resetContext } from '../fetch-utils.js';
|
|
16
|
+
import utils from './utils.js';
|
|
17
|
+
import RequestContext from './RequestContext.js';
|
|
18
|
+
import { asyncHandler, BaseServer } from './BaseServer.js';
|
|
19
|
+
|
|
20
|
+
export class HelixImportServer extends BaseServer {
|
|
21
|
+
/**
|
|
22
|
+
* Proxy Mode route handler
|
|
23
|
+
* @param {Express.Request} req request
|
|
24
|
+
* @param {Express.Response} res response
|
|
25
|
+
*/
|
|
26
|
+
async handleToolsRequest(req, res) {
|
|
27
|
+
const sendFile = promisify(res.sendFile).bind(res);
|
|
28
|
+
const ctx = new RequestContext(req, this._project);
|
|
29
|
+
const { log } = this;
|
|
30
|
+
|
|
31
|
+
// try to serve static
|
|
32
|
+
try {
|
|
33
|
+
const filePath = path.join(this._project.directory, ctx.path);
|
|
34
|
+
if (path.relative(this._project.directory, filePath).startsWith('..')) {
|
|
35
|
+
log.info(`refuse to serve file outside the project directory: ${filePath}`);
|
|
36
|
+
res.status(403).send('');
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
log.debug('trying to serve local file', filePath);
|
|
40
|
+
await sendFile(filePath, {
|
|
41
|
+
dotfiles: 'allow',
|
|
42
|
+
headers: {
|
|
43
|
+
'access-control-allow-origin': '*',
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
return;
|
|
47
|
+
} catch (e) {
|
|
48
|
+
// not sure what to do yet here
|
|
49
|
+
// codecov:ignore:start
|
|
50
|
+
/* c8 ignore start */
|
|
51
|
+
log.debug(`Error while delivering resource ${ctx.path} - ${e.stack || e}`);
|
|
52
|
+
// codecov:ignore:end
|
|
53
|
+
/* c8 ignore end */
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
res.status(404).send(`Unknown path: ${ctx.path}`);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// eslint-disable-next-line class-methods-use-this
|
|
60
|
+
_makeProxyURL(reqUrl, base) {
|
|
61
|
+
const url = new URL(reqUrl, base);
|
|
62
|
+
url.searchParams.delete('host');
|
|
63
|
+
return url.toString();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async _doProxyRequest(ctx, url, host, req, res) {
|
|
67
|
+
ctx.log.debug(`Proxy ${req.method} request to ${url}`);
|
|
68
|
+
|
|
69
|
+
// POST requests have a body
|
|
70
|
+
const isBodyReq = !['GET', 'HEAD'].includes(req.method);
|
|
71
|
+
|
|
72
|
+
// do not cache POST requests
|
|
73
|
+
if (!isBodyReq && this._project.cacheDirectory) {
|
|
74
|
+
const cached = await utils.getFromCache(
|
|
75
|
+
url,
|
|
76
|
+
this._project.cacheDirectory,
|
|
77
|
+
ctx.log,
|
|
78
|
+
);
|
|
79
|
+
if (cached) {
|
|
80
|
+
res
|
|
81
|
+
.status(cached.status)
|
|
82
|
+
.set(cached.headers)
|
|
83
|
+
.cookie('hlx-proxyhost', host)
|
|
84
|
+
.send(cached.body);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
let body;
|
|
90
|
+
// pipe body if any
|
|
91
|
+
if (isBodyReq) {
|
|
92
|
+
body = new PassThrough();
|
|
93
|
+
req.pipe(body);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const headers = {
|
|
97
|
+
...req.headers,
|
|
98
|
+
};
|
|
99
|
+
delete headers.cookie;
|
|
100
|
+
delete headers.connection;
|
|
101
|
+
delete headers.host;
|
|
102
|
+
delete headers.referer;
|
|
103
|
+
|
|
104
|
+
const ret = await getFetch(true)(url, {
|
|
105
|
+
method: req.method,
|
|
106
|
+
headers,
|
|
107
|
+
cache: 'no-store',
|
|
108
|
+
redirect: 'manual',
|
|
109
|
+
body,
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
const contentType = ret.headers.get('content-type') || 'text/plain';
|
|
113
|
+
const level = utils.status2level(ret.status, true);
|
|
114
|
+
ctx.log[level](`Proxy ${req.method} request to ${url}: ${ret.status} (${contentType})`);
|
|
115
|
+
|
|
116
|
+
// because fetch decodes the response, we need to reset content encoding and length
|
|
117
|
+
const respHeaders = Object.fromEntries(ret.headers.entries());
|
|
118
|
+
delete respHeaders['content-encoding'];
|
|
119
|
+
delete respHeaders['content-length'];
|
|
120
|
+
|
|
121
|
+
// remove security "constraints"
|
|
122
|
+
delete respHeaders['x-frame-options'];
|
|
123
|
+
delete respHeaders['content-security-policy'];
|
|
124
|
+
respHeaders['access-control-allow-origin'] = '*';
|
|
125
|
+
delete respHeaders['set-cookie'];
|
|
126
|
+
|
|
127
|
+
if (respHeaders.location && !respHeaders.location.startsWith('/')) {
|
|
128
|
+
const u = new URL(respHeaders.location);
|
|
129
|
+
if (u.origin === host) {
|
|
130
|
+
respHeaders.location = u.pathname;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
let buffer = await ret.buffer();
|
|
135
|
+
if (contentType.includes('html') || contentType.includes('text')) {
|
|
136
|
+
buffer = utils.rewriteUrl(buffer, host);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (!isBodyReq && this._project.cacheDirectory) {
|
|
140
|
+
await utils.writeToCache(
|
|
141
|
+
url,
|
|
142
|
+
this._project.cacheDirectory,
|
|
143
|
+
{
|
|
144
|
+
body: buffer,
|
|
145
|
+
headers: respHeaders,
|
|
146
|
+
status: ret.status,
|
|
147
|
+
},
|
|
148
|
+
ctx.log,
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
res
|
|
153
|
+
.status(ret.status)
|
|
154
|
+
.set(respHeaders)
|
|
155
|
+
.cookie('hlx-proxyhost', host)
|
|
156
|
+
.send(buffer);
|
|
157
|
+
ret.body.pipe(res);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Proxy Mode route handler
|
|
162
|
+
* @param {Express.Request} req request
|
|
163
|
+
* @param {Express.Response} res response
|
|
164
|
+
*/
|
|
165
|
+
async handleProxyModeRequest(req, res) {
|
|
166
|
+
const ctx = new RequestContext(req, this._project);
|
|
167
|
+
const { log } = this;
|
|
168
|
+
|
|
169
|
+
let host = ctx.params?.host;
|
|
170
|
+
if (!host) {
|
|
171
|
+
// first call sets the cookie, next calls use the cookie
|
|
172
|
+
host = req.cookies['hlx-proxyhost'];
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (!host) {
|
|
176
|
+
res.status(403).send('Missing host parameter');
|
|
177
|
+
} else {
|
|
178
|
+
try {
|
|
179
|
+
host = new URL(host).origin;
|
|
180
|
+
const url = this._makeProxyURL(ctx.url, host);
|
|
181
|
+
await this._doProxyRequest(ctx, url, host, req, res);
|
|
182
|
+
// codecov:ignore:start
|
|
183
|
+
/* c8 ignore start */
|
|
184
|
+
} catch (err) {
|
|
185
|
+
log.error(`Failed to proxy AEM request ${ctx.path}: ${err.message}`);
|
|
186
|
+
res.status(502).send(`Failed to proxy AEM request: ${err.message}`);
|
|
187
|
+
}
|
|
188
|
+
// codecov:ignore:end
|
|
189
|
+
/* c8 ignore end */
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
this.emit('request', req, res, ctx);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
async setupApp() {
|
|
196
|
+
await super.setupApp();
|
|
197
|
+
this.app.get('/tools/*', asyncHandler(this.handleToolsRequest.bind(this)));
|
|
198
|
+
const handler = asyncHandler(this.handleProxyModeRequest.bind(this));
|
|
199
|
+
this.app.get('*', handler);
|
|
200
|
+
this.app.post('*', handler);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
async doStop() {
|
|
204
|
+
await super.doStop();
|
|
205
|
+
await resetContext();
|
|
206
|
+
}
|
|
207
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
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 { resolve } from 'path';
|
|
13
|
+
import { lstat } from 'fs/promises';
|
|
14
|
+
import { HelixServer } from './HelixServer.js';
|
|
15
|
+
import { BaseProject } from './BaseProject.js';
|
|
16
|
+
import HeadHtmlSupport from './HeadHtmlSupport.js';
|
|
17
|
+
import Indexer from './Indexer.js';
|
|
18
|
+
|
|
19
|
+
export class HelixProject extends BaseProject {
|
|
20
|
+
constructor() {
|
|
21
|
+
super(HelixServer);
|
|
22
|
+
this._proxyUrl = null;
|
|
23
|
+
this._headHtml = null;
|
|
24
|
+
this._indexer = null;
|
|
25
|
+
this._printIndex = false;
|
|
26
|
+
this._file404html = null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
withLiveReload(value) {
|
|
30
|
+
this._server.withLiveReload(value);
|
|
31
|
+
return this;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
withProxyUrl(value) {
|
|
35
|
+
this._proxyUrl = value;
|
|
36
|
+
return this;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
withPrintIndex(value) {
|
|
40
|
+
this._printIndex = value;
|
|
41
|
+
return this;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
get proxyUrl() {
|
|
45
|
+
return this._proxyUrl;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
get indexer() {
|
|
49
|
+
return this._indexer;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
get liveReload() {
|
|
53
|
+
// eslint-disable-next-line no-underscore-dangle
|
|
54
|
+
return this._server._liveReload;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
get file404html() {
|
|
58
|
+
return this._file404html;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
get headHtml() {
|
|
62
|
+
return this._headHtml;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async init() {
|
|
66
|
+
await super.init();
|
|
67
|
+
this._indexer = new Indexer()
|
|
68
|
+
.withLogger(this._logger)
|
|
69
|
+
.withCwd(this._cwd)
|
|
70
|
+
.withPrintIndex(this._printIndex);
|
|
71
|
+
return this;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async initHeadHtml() {
|
|
75
|
+
if (this.proxyUrl) {
|
|
76
|
+
this._headHtml = new HeadHtmlSupport({
|
|
77
|
+
directory: this.directory,
|
|
78
|
+
log: this.log,
|
|
79
|
+
proxyUrl: this.proxyUrl,
|
|
80
|
+
});
|
|
81
|
+
await this._headHtml.init();
|
|
82
|
+
|
|
83
|
+
// register local head in live-reload
|
|
84
|
+
if (this.liveReload) {
|
|
85
|
+
this.liveReload.registerFiles([this._headHtml.filePath], '/');
|
|
86
|
+
this.liveReload.on('modified', async (modified) => {
|
|
87
|
+
if (modified.indexOf('/') >= 0) {
|
|
88
|
+
await this._headHtml.loadLocal();
|
|
89
|
+
await this._headHtml.init();
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async init404Html() {
|
|
97
|
+
if (this.proxyUrl) {
|
|
98
|
+
this._file404html = resolve(this.directory, '404.html');
|
|
99
|
+
try {
|
|
100
|
+
await lstat(this._file404html);
|
|
101
|
+
this.log.debug('detected local 404.html');
|
|
102
|
+
if (this.liveReload) {
|
|
103
|
+
this.liveReload.registerFiles([this._file404html], '/');
|
|
104
|
+
}
|
|
105
|
+
} catch (e) {
|
|
106
|
+
this._file404html = null;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async start() {
|
|
112
|
+
this.log.debug('Launching AEM dev server...');
|
|
113
|
+
await super.start();
|
|
114
|
+
await this.initHeadHtml();
|
|
115
|
+
await this.init404Html();
|
|
116
|
+
if (this._indexer) {
|
|
117
|
+
await this._indexer.init();
|
|
118
|
+
}
|
|
119
|
+
return this;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async doStop() {
|
|
123
|
+
this.log.debug('Stopping AEM dev server...');
|
|
124
|
+
await super.doStop();
|
|
125
|
+
if (this._indexer) {
|
|
126
|
+
await this._indexer.close();
|
|
127
|
+
delete this._indexer;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
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 { promisify } from 'util';
|
|
13
|
+
import path from 'path';
|
|
14
|
+
import compression from 'compression';
|
|
15
|
+
import utils from './utils.js';
|
|
16
|
+
import RequestContext from './RequestContext.js';
|
|
17
|
+
import { asyncHandler, BaseServer } from './BaseServer.js';
|
|
18
|
+
import LiveReload from './LiveReload.js';
|
|
19
|
+
|
|
20
|
+
export class HelixServer extends BaseServer {
|
|
21
|
+
/**
|
|
22
|
+
* Creates a new HelixServer for the given project.
|
|
23
|
+
* @param {HelixProject} project
|
|
24
|
+
*/
|
|
25
|
+
constructor(project) {
|
|
26
|
+
super(project);
|
|
27
|
+
this._liveReload = null;
|
|
28
|
+
this._enableLiveReload = false;
|
|
29
|
+
this._app.use(compression());
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
withLiveReload(value) {
|
|
33
|
+
this._enableLiveReload = value;
|
|
34
|
+
return this;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Proxy Mode route handler
|
|
39
|
+
* @param {Express.Request} req request
|
|
40
|
+
* @param {Express.Response} res response
|
|
41
|
+
*/
|
|
42
|
+
async handleProxyModeRequest(req, res) {
|
|
43
|
+
const sendFile = promisify(res.sendFile).bind(res);
|
|
44
|
+
const ctx = new RequestContext(req, this._project);
|
|
45
|
+
const { log } = this;
|
|
46
|
+
const proxyUrl = new URL(this._project.proxyUrl);
|
|
47
|
+
|
|
48
|
+
const filePath = path.join(this._project.directory, ctx.path);
|
|
49
|
+
if (path.relative(this._project.directory, filePath).startsWith('..')) {
|
|
50
|
+
log.info(`refuse to serve file outside the project directory: ${filePath}`);
|
|
51
|
+
res.status(403).send('');
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const liveReload = this._liveReload;
|
|
56
|
+
if (liveReload) {
|
|
57
|
+
liveReload.startRequest(ctx.requestId, ctx.path);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// try to serve static
|
|
61
|
+
try {
|
|
62
|
+
log.debug('trying to serve local file', filePath);
|
|
63
|
+
await sendFile(filePath, {
|
|
64
|
+
dotfiles: 'allow',
|
|
65
|
+
headers: {
|
|
66
|
+
'access-control-allow-origin': '*',
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
if (liveReload) {
|
|
70
|
+
liveReload.registerFile(ctx.requestId, filePath);
|
|
71
|
+
}
|
|
72
|
+
return;
|
|
73
|
+
} catch (e) {
|
|
74
|
+
log.debug(`Error while delivering resource ${ctx.path} - ${e.stack || e}`);
|
|
75
|
+
} finally {
|
|
76
|
+
if (liveReload) {
|
|
77
|
+
liveReload.endRequest(ctx.requestId);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// use proxy
|
|
82
|
+
try {
|
|
83
|
+
const url = new URL(ctx.url, proxyUrl);
|
|
84
|
+
for (const [key, value] of proxyUrl.searchParams.entries()) {
|
|
85
|
+
url.searchParams.append(key, value);
|
|
86
|
+
}
|
|
87
|
+
await utils.proxyRequest(ctx, url.href, req, res, {
|
|
88
|
+
injectLiveReload: this._project.liveReload,
|
|
89
|
+
headHtml: this._project.headHtml,
|
|
90
|
+
indexer: this._project.indexer,
|
|
91
|
+
cacheDirectory: this._project.cacheDirectory,
|
|
92
|
+
file404html: this._project.file404html,
|
|
93
|
+
});
|
|
94
|
+
} catch (err) {
|
|
95
|
+
log.error(`Failed to proxy AEM request ${ctx.path}: ${err.message}`);
|
|
96
|
+
res.status(502).send(`Failed to proxy AEM request: ${err.message}`);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
this.emit('request', req, res, ctx);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async setupApp() {
|
|
103
|
+
await super.setupApp();
|
|
104
|
+
if (this._enableLiveReload) {
|
|
105
|
+
this._liveReload = new LiveReload(this.log);
|
|
106
|
+
await this._liveReload.init(this.app, this._server);
|
|
107
|
+
}
|
|
108
|
+
const handler = asyncHandler(this.handleProxyModeRequest.bind(this));
|
|
109
|
+
this.app.get('*', handler);
|
|
110
|
+
this.app.post('*', handler);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async doStop() {
|
|
114
|
+
await super.stop();
|
|
115
|
+
if (this._liveReload) {
|
|
116
|
+
await this._liveReload.stop();
|
|
117
|
+
delete this._liveReload;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2021 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 { resolve } from 'path';
|
|
13
|
+
import { IndexConfig } from '@adobe/helix-shared-config';
|
|
14
|
+
import { indexResource, contains } from '@adobe/helix-shared-indexer';
|
|
15
|
+
import chalk from 'chalk-template';
|
|
16
|
+
import chokidar from 'chokidar';
|
|
17
|
+
|
|
18
|
+
export default class Indexer {
|
|
19
|
+
constructor() {
|
|
20
|
+
this._cwd = process.cwd();
|
|
21
|
+
this._logger = null;
|
|
22
|
+
this._last = {};
|
|
23
|
+
this._index = null;
|
|
24
|
+
this._printIndex = false;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
withCwd(cwd) {
|
|
28
|
+
this._cwd = cwd;
|
|
29
|
+
return this;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
withLogger(logger) {
|
|
33
|
+
this._logger = logger;
|
|
34
|
+
return this;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
withPrintIndex(value) {
|
|
38
|
+
this._printIndex = value;
|
|
39
|
+
return this;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
get log() {
|
|
43
|
+
return this._logger;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async init() {
|
|
47
|
+
await this.loadIndex();
|
|
48
|
+
// eslint-disable-next-line no-underscore-dangle
|
|
49
|
+
this._watcher = chokidar.watch([resolve(this._cwd, this._index._name)], {
|
|
50
|
+
persistent: true,
|
|
51
|
+
ignoreInitial: true,
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
this._watcher.on('all', async () => {
|
|
55
|
+
await this.loadIndex();
|
|
56
|
+
await this.onIndexChanged();
|
|
57
|
+
});
|
|
58
|
+
return this;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async close() {
|
|
62
|
+
if (this._watcher) {
|
|
63
|
+
await this._watcher.close();
|
|
64
|
+
delete this._watcher;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async loadIndex() {
|
|
69
|
+
const { log } = this;
|
|
70
|
+
try {
|
|
71
|
+
this._index = await new IndexConfig()
|
|
72
|
+
.withDirectory(this._cwd)
|
|
73
|
+
.withLogger(log)
|
|
74
|
+
.init();
|
|
75
|
+
} catch (e) {
|
|
76
|
+
log.error(`Error in helix-query.yaml: ${e.message}`);
|
|
77
|
+
}
|
|
78
|
+
if (this._printIndex && this._index.indices.length === 0) {
|
|
79
|
+
log.warn(chalk`AEM CLI started with {gray --print-index} but no valid {gray helix-query.yaml} found.`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async onData(url, response) {
|
|
84
|
+
const { pathname } = new URL(url);
|
|
85
|
+
this._last = {
|
|
86
|
+
pathname,
|
|
87
|
+
response,
|
|
88
|
+
};
|
|
89
|
+
if (this._printIndex) {
|
|
90
|
+
await this.dump();
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async onIndexChanged() {
|
|
95
|
+
await this.loadIndex();
|
|
96
|
+
if (this._printIndex) {
|
|
97
|
+
await this.dump();
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async dump() {
|
|
102
|
+
const { log } = this;
|
|
103
|
+
const {
|
|
104
|
+
pathname,
|
|
105
|
+
response,
|
|
106
|
+
} = this._last;
|
|
107
|
+
if (!pathname) {
|
|
108
|
+
log.debug('No last path recorded, dump skipped.');
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
const records = await this.getRecords(pathname, response);
|
|
112
|
+
if (records) {
|
|
113
|
+
if (records.length) {
|
|
114
|
+
log.info(chalk`Index information for {blue ${pathname}}`);
|
|
115
|
+
} else {
|
|
116
|
+
log.info(chalk`No index information matches {blue ${pathname}}`);
|
|
117
|
+
}
|
|
118
|
+
for (const idx of records) {
|
|
119
|
+
log.info(chalk`Index: {yellow ${idx.name}}`);
|
|
120
|
+
const pad = Object.keys(idx.properties).reduce((p, c) => Math.max(p, c.length), 0);
|
|
121
|
+
Object.entries(idx.properties).forEach(([key, value]) => {
|
|
122
|
+
if (typeof value === 'number') {
|
|
123
|
+
// eslint-disable-next-line no-param-reassign
|
|
124
|
+
value = chalk`{yellow ${value}}`;
|
|
125
|
+
} else {
|
|
126
|
+
// eslint-disable-next-line no-param-reassign
|
|
127
|
+
value = JSON.stringify(value);
|
|
128
|
+
}
|
|
129
|
+
log.info(chalk` {gray ${key.padStart(pad)}}: ${value}`);
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async getRecords(pathname, response) {
|
|
136
|
+
if (!this._index) {
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
return this._index.indices
|
|
140
|
+
.map((config) => {
|
|
141
|
+
if (contains(config, pathname)) {
|
|
142
|
+
return {
|
|
143
|
+
name: config.name,
|
|
144
|
+
properties: indexResource(pathname, response, config, this.log),
|
|
145
|
+
};
|
|
146
|
+
} else {
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
})
|
|
150
|
+
.filter((r) => !!r);
|
|
151
|
+
}
|
|
152
|
+
}
|