@adobe/aem-cli 16.12.0 → 16.13.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 +31 -0
- package/CONTRIBUTING.md +14 -0
- package/README.md +35 -0
- package/eslint.config.js +2 -0
- package/package.json +7 -4
- package/packages/browser-injectables/README.md +66 -0
- package/packages/browser-injectables/eslint.config.js +76 -0
- package/packages/browser-injectables/package-lock.json +6990 -0
- package/packages/browser-injectables/package.json +26 -0
- package/packages/browser-injectables/scripts/copy-vendor.js +28 -0
- package/packages/browser-injectables/src/console-interceptor.js +81 -0
- package/packages/browser-injectables/web-test-runner.config.js +60 -0
- package/src/server/HelixProject.js +5 -0
- package/src/server/HelixServer.js +11 -0
- package/src/server/LiveReload.js +40 -3
- package/src/server/utils.js +16 -0
- package/src/up.cmd.js +6 -0
- package/src/up.js +8 -1
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@adobe/aem-cli-browser-injectables",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"description": "Browser injectable scripts for AEM CLI development server",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"lint": "eslint .",
|
|
9
|
+
"test": "web-test-runner",
|
|
10
|
+
"test:watch": "web-test-runner --watch",
|
|
11
|
+
"postinstall": "npm run copy-vendor && npx playwright install",
|
|
12
|
+
"copy-vendor": "node scripts/copy-vendor.js"
|
|
13
|
+
},
|
|
14
|
+
"devDependencies": {
|
|
15
|
+
"@adobe/eslint-config-helix": "^3.0.9",
|
|
16
|
+
"@eslint/config-helpers": "0.3.0",
|
|
17
|
+
"@esm-bundle/chai": "4.3.4-fix.0",
|
|
18
|
+
"@web/test-runner": "0.20.2",
|
|
19
|
+
"@web/test-runner-playwright": "0.11.1",
|
|
20
|
+
"@web/test-runner-junit-reporter": "0.8.0",
|
|
21
|
+
"@web/test-runner-mocha": "0.9.0"
|
|
22
|
+
},
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"livereload-js": "4.0.2"
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2025 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
|
+
import fs from 'fs';
|
|
14
|
+
import path from 'path';
|
|
15
|
+
import { fileURLToPath } from 'url';
|
|
16
|
+
|
|
17
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
18
|
+
|
|
19
|
+
// Copy livereload.js from node_modules to vendor directory
|
|
20
|
+
const source = path.join(__dirname, '../node_modules/livereload-js/dist/livereload.js');
|
|
21
|
+
const dest = path.join(__dirname, '../vendor/livereload.js');
|
|
22
|
+
|
|
23
|
+
// Create vendor directory if it doesn't exist
|
|
24
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
25
|
+
|
|
26
|
+
// Copy the file
|
|
27
|
+
fs.copyFileSync(source, dest);
|
|
28
|
+
console.log('Copied livereload.js to vendor/');
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2025 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
|
+
/**
|
|
14
|
+
* Browser Console Interceptor
|
|
15
|
+
* Intercepts console methods and forwards them via WebSocket to the server
|
|
16
|
+
* This script is self-contained and browser-compatible (no module system)
|
|
17
|
+
*/
|
|
18
|
+
(function iife() {
|
|
19
|
+
// Wait for LiveReload connection
|
|
20
|
+
var checkInterval = setInterval(function checkLiveReload() {
|
|
21
|
+
if (window.LiveReload && window.LiveReload.connector && window.LiveReload.connector.socket) {
|
|
22
|
+
clearInterval(checkInterval);
|
|
23
|
+
|
|
24
|
+
// Store original console methods
|
|
25
|
+
var originalConsole = {
|
|
26
|
+
log: console.log,
|
|
27
|
+
error: console.error,
|
|
28
|
+
warn: console.warn,
|
|
29
|
+
info: console.info,
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
// Helper to safely serialize arguments
|
|
33
|
+
function serializeArgs(args) {
|
|
34
|
+
return Array.from(args).map(function mapArg(arg) {
|
|
35
|
+
try {
|
|
36
|
+
if (arg instanceof Error) {
|
|
37
|
+
return { type: 'Error', message: arg.message, stack: arg.stack };
|
|
38
|
+
}
|
|
39
|
+
return JSON.parse(JSON.stringify(arg));
|
|
40
|
+
} catch (e) {
|
|
41
|
+
return String(arg);
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Get current file location
|
|
47
|
+
function getLocation() {
|
|
48
|
+
try {
|
|
49
|
+
var stack = new Error().stack;
|
|
50
|
+
var match = stack.match(/at.*?((https?:\/\/[^\s]+?):(\d+):(\d+))/);
|
|
51
|
+
if (match) {
|
|
52
|
+
return { url: match[2], line: match[3] };
|
|
53
|
+
}
|
|
54
|
+
} catch (e) {
|
|
55
|
+
// Ignore error when getting stack
|
|
56
|
+
}
|
|
57
|
+
return { url: window.location.href, line: 0 };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Intercept console methods
|
|
61
|
+
['log', 'error', 'warn', 'info'].forEach(function interceptLevel(level) {
|
|
62
|
+
console[level] = function interceptedConsole() {
|
|
63
|
+
// Call original method
|
|
64
|
+
originalConsole[level].apply(console, arguments);
|
|
65
|
+
|
|
66
|
+
// Forward to server if connected
|
|
67
|
+
if (window.LiveReload.connector.socket.readyState === 1) {
|
|
68
|
+
var location = getLocation();
|
|
69
|
+
window.LiveReload.connector.socket.send(JSON.stringify({
|
|
70
|
+
command: 'log',
|
|
71
|
+
level: level,
|
|
72
|
+
args: serializeArgs(arguments),
|
|
73
|
+
url: location.url,
|
|
74
|
+
line: location.line,
|
|
75
|
+
}));
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
}, 100);
|
|
81
|
+
}());
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2025 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
|
+
import { defaultReporter } from '@web/test-runner';
|
|
14
|
+
import { junitReporter } from '@web/test-runner-junit-reporter';
|
|
15
|
+
import { playwrightLauncher } from '@web/test-runner-playwright';
|
|
16
|
+
|
|
17
|
+
export default {
|
|
18
|
+
nodeResolve: true,
|
|
19
|
+
coverage: true,
|
|
20
|
+
reporters: [
|
|
21
|
+
defaultReporter(),
|
|
22
|
+
junitReporter({
|
|
23
|
+
outputPath: 'junit/browser-test-results.xml',
|
|
24
|
+
}),
|
|
25
|
+
],
|
|
26
|
+
testFramework: {
|
|
27
|
+
type: 'mocha',
|
|
28
|
+
config: {
|
|
29
|
+
timeout: 20000, // 20 seconds for browser tests
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
browsers: [
|
|
33
|
+
playwrightLauncher({ product: 'chromium' }),
|
|
34
|
+
playwrightLauncher({ product: 'firefox' }),
|
|
35
|
+
playwrightLauncher({ product: 'webkit' }),
|
|
36
|
+
],
|
|
37
|
+
coverageConfig: {
|
|
38
|
+
report: true,
|
|
39
|
+
reportDir: 'coverage-browser',
|
|
40
|
+
exclude: [
|
|
41
|
+
'test/**',
|
|
42
|
+
'node_modules/**',
|
|
43
|
+
'**/*.test.js',
|
|
44
|
+
'scripts/**',
|
|
45
|
+
],
|
|
46
|
+
},
|
|
47
|
+
files: [
|
|
48
|
+
'test/**/*.test.html',
|
|
49
|
+
'test/**/*.test.js',
|
|
50
|
+
],
|
|
51
|
+
middleware: [
|
|
52
|
+
// Serve livereload.js from node_modules
|
|
53
|
+
async function serveLiveReload(context, next) {
|
|
54
|
+
if (context.url === '/__internal__/livereload.js') {
|
|
55
|
+
context.url = '/vendor/livereload.js';
|
|
56
|
+
}
|
|
57
|
+
await next();
|
|
58
|
+
},
|
|
59
|
+
],
|
|
60
|
+
};
|
|
@@ -32,6 +32,11 @@ export class HelixProject extends BaseProject {
|
|
|
32
32
|
return this;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
withForwardBrowserLogs(value) {
|
|
36
|
+
this._server.withForwardBrowserLogs(value);
|
|
37
|
+
return this;
|
|
38
|
+
}
|
|
39
|
+
|
|
35
40
|
withSiteToken(value) {
|
|
36
41
|
this.siteToken = value;
|
|
37
42
|
this._server.withSiteToken(value);
|
|
@@ -32,6 +32,7 @@ export class HelixServer extends BaseServer {
|
|
|
32
32
|
super(project);
|
|
33
33
|
this._liveReload = null;
|
|
34
34
|
this._enableLiveReload = false;
|
|
35
|
+
this._forwardBrowserLogs = false;
|
|
35
36
|
this._app.use(compression());
|
|
36
37
|
this._autoLogin = true;
|
|
37
38
|
this._cookies = false;
|
|
@@ -42,6 +43,15 @@ export class HelixServer extends BaseServer {
|
|
|
42
43
|
return this;
|
|
43
44
|
}
|
|
44
45
|
|
|
46
|
+
withForwardBrowserLogs(value) {
|
|
47
|
+
this._forwardBrowserLogs = value;
|
|
48
|
+
return this;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
get forwardBrowserLogs() {
|
|
52
|
+
return this._forwardBrowserLogs;
|
|
53
|
+
}
|
|
54
|
+
|
|
45
55
|
withSiteToken(value) {
|
|
46
56
|
this._siteToken = value;
|
|
47
57
|
return this;
|
|
@@ -225,6 +235,7 @@ export class HelixServer extends BaseServer {
|
|
|
225
235
|
await super.setupApp();
|
|
226
236
|
if (this._enableLiveReload) {
|
|
227
237
|
this._liveReload = new LiveReload(this.log);
|
|
238
|
+
this._liveReload.withForwardBrowserLogs(this._forwardBrowserLogs);
|
|
228
239
|
await this._liveReload.init(this.app, this._server);
|
|
229
240
|
}
|
|
230
241
|
|
package/src/server/LiveReload.js
CHANGED
|
@@ -11,10 +11,10 @@
|
|
|
11
11
|
*/
|
|
12
12
|
// eslint-disable-next-line max-classes-per-file
|
|
13
13
|
import fs from 'fs';
|
|
14
|
+
import { createRequire } from 'module';
|
|
14
15
|
import chokidar from 'chokidar';
|
|
15
16
|
import WebSocket from 'faye-websocket';
|
|
16
17
|
import { EventEmitter } from 'events';
|
|
17
|
-
import { createRequire } from 'module';
|
|
18
18
|
|
|
19
19
|
const require = createRequire(import.meta.url);
|
|
20
20
|
|
|
@@ -27,9 +27,10 @@ class ClientConnection extends EventEmitter {
|
|
|
27
27
|
return `ws${ClientConnection.counter}`;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
constructor(req, socket, head) {
|
|
30
|
+
constructor(req, socket, head, logger) {
|
|
31
31
|
super();
|
|
32
32
|
this.id = ClientConnection.nextId();
|
|
33
|
+
this.log = logger;
|
|
33
34
|
this.ws = new WebSocket(req, socket, head);
|
|
34
35
|
this.ws.onmessage = this._onMessage.bind(this);
|
|
35
36
|
this.ws.onclose = this._onClose.bind(this);
|
|
@@ -47,6 +48,8 @@ class ClientConnection extends EventEmitter {
|
|
|
47
48
|
return this._cmdHello(data);
|
|
48
49
|
case 'info':
|
|
49
50
|
return this._cmdInfo(data);
|
|
51
|
+
case 'log':
|
|
52
|
+
return this._cmdLog(data);
|
|
50
53
|
default:
|
|
51
54
|
return {};
|
|
52
55
|
}
|
|
@@ -78,6 +81,30 @@ class ClientConnection extends EventEmitter {
|
|
|
78
81
|
return { ...data || {}, id: this.id, url: this.url };
|
|
79
82
|
}
|
|
80
83
|
|
|
84
|
+
_cmdLog(data) {
|
|
85
|
+
const {
|
|
86
|
+
level = 'log', args = [], url = 'unknown', line,
|
|
87
|
+
} = data;
|
|
88
|
+
const timestamp = new Date().toISOString();
|
|
89
|
+
const location = line ? `${url}:${line}` : url;
|
|
90
|
+
|
|
91
|
+
// Format browser logs distinctively
|
|
92
|
+
const prefix = `[Browser:${level}] ${timestamp} ${location}`;
|
|
93
|
+
|
|
94
|
+
// Serialize args safely
|
|
95
|
+
const message = args.map((arg) => {
|
|
96
|
+
try {
|
|
97
|
+
return typeof arg === 'object' ? JSON.stringify(arg, null, 2) : String(arg);
|
|
98
|
+
} catch (e) {
|
|
99
|
+
return '[Circular or Complex Object]';
|
|
100
|
+
}
|
|
101
|
+
}).join(' ');
|
|
102
|
+
|
|
103
|
+
// Use appropriate log level
|
|
104
|
+
const logMethod = this.log[level] || this.log.info;
|
|
105
|
+
logMethod(`${prefix} ${message}`);
|
|
106
|
+
}
|
|
107
|
+
|
|
81
108
|
_send(data) {
|
|
82
109
|
if (this.ws) {
|
|
83
110
|
this.ws.send(JSON.stringify(data));
|
|
@@ -123,12 +150,22 @@ export default class LiveReload extends EventEmitter {
|
|
|
123
150
|
// client connections
|
|
124
151
|
this._connections = {};
|
|
125
152
|
this._liveReloadJSPath = require.resolve('livereload-js/dist/livereload.js');
|
|
153
|
+
this._forwardBrowserLogs = false;
|
|
126
154
|
}
|
|
127
155
|
|
|
128
156
|
get log() {
|
|
129
157
|
return this._logger;
|
|
130
158
|
}
|
|
131
159
|
|
|
160
|
+
withForwardBrowserLogs(value) {
|
|
161
|
+
this._forwardBrowserLogs = value;
|
|
162
|
+
return this;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
get forwardBrowserLogs() {
|
|
166
|
+
return this._forwardBrowserLogs;
|
|
167
|
+
}
|
|
168
|
+
|
|
132
169
|
startRequest(requestId, pathname) {
|
|
133
170
|
this._pending.set(requestId, {
|
|
134
171
|
pathname,
|
|
@@ -211,7 +248,7 @@ export default class LiveReload extends EventEmitter {
|
|
|
211
248
|
}
|
|
212
249
|
|
|
213
250
|
_onSvrUpgrade(req, socket, head) {
|
|
214
|
-
const cx = new ClientConnection(req, socket, head);
|
|
251
|
+
const cx = new ClientConnection(req, socket, head, this.log);
|
|
215
252
|
this._connections[cx.id] = cx;
|
|
216
253
|
|
|
217
254
|
socket.on('error', (e) => {
|
package/src/server/utils.js
CHANGED
|
@@ -14,9 +14,19 @@ import crypto from 'crypto';
|
|
|
14
14
|
import path from 'path';
|
|
15
15
|
import { Socket } from 'net';
|
|
16
16
|
import { PassThrough } from 'stream';
|
|
17
|
+
import { readFileSync } from 'fs';
|
|
18
|
+
import { fileURLToPath } from 'url';
|
|
17
19
|
import cookie from 'cookie';
|
|
18
20
|
import { getFetch } from '../fetch-utils.js';
|
|
19
21
|
|
|
22
|
+
// Load console interceptor script at startup
|
|
23
|
+
// eslint-disable-next-line no-underscore-dangle
|
|
24
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
25
|
+
const CONSOLE_INTERCEPTOR = readFileSync(
|
|
26
|
+
path.join(__dirname, '../../packages/browser-injectables/src/console-interceptor.js'),
|
|
27
|
+
'utf-8',
|
|
28
|
+
);
|
|
29
|
+
|
|
20
30
|
const utils = {
|
|
21
31
|
status2level(status, debug3xx) {
|
|
22
32
|
if (status < 300) {
|
|
@@ -104,6 +114,12 @@ window.LiveReloadOptions = {
|
|
|
104
114
|
newbody += `<script${nonce}>window.LiveReloadOptions={port:${server.port},host:location.hostname,https:${server.scheme === 'https'}};</script>`;
|
|
105
115
|
}
|
|
106
116
|
newbody += `<script${nonce} src="/__internal__/livereload.js"></script>`;
|
|
117
|
+
|
|
118
|
+
// Inject console interceptor if browser log forwarding is enabled
|
|
119
|
+
if (server.forwardBrowserLogs) {
|
|
120
|
+
newbody += `<script${nonce}>${CONSOLE_INTERCEPTOR}</script>`;
|
|
121
|
+
}
|
|
122
|
+
|
|
107
123
|
newbody += body.substring(index);
|
|
108
124
|
return newbody;
|
|
109
125
|
}
|
package/src/up.cmd.js
CHANGED
|
@@ -24,6 +24,11 @@ export default class UpCommand extends AbstractServerCommand {
|
|
|
24
24
|
return this;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
withForwardBrowserLogs(value) {
|
|
28
|
+
this._forwardBrowserLogs = value;
|
|
29
|
+
return this;
|
|
30
|
+
}
|
|
31
|
+
|
|
27
32
|
withUrl(value) {
|
|
28
33
|
this._originalUrl = value;
|
|
29
34
|
return this;
|
|
@@ -94,6 +99,7 @@ export default class UpCommand extends AbstractServerCommand {
|
|
|
94
99
|
this._project = new HelixProject()
|
|
95
100
|
.withCwd(this.directory)
|
|
96
101
|
.withLiveReload(this._liveReload)
|
|
102
|
+
.withForwardBrowserLogs(this._forwardBrowserLogs)
|
|
97
103
|
.withLogger(this._logger)
|
|
98
104
|
.withKill(this._kill)
|
|
99
105
|
.withPrintIndex(this._printIndex)
|
package/src/up.js
CHANGED
|
@@ -97,7 +97,7 @@ export default function up() {
|
|
|
97
97
|
describe: 'Path to local folder to cache the responses (note: this is an alpha feature, it may be removed without notice)',
|
|
98
98
|
type: 'string',
|
|
99
99
|
})
|
|
100
|
-
.group(['url', 'livereload', 'no-livereload', 'open', 'no-open', 'print-index', 'cache'], 'AEM Options')
|
|
100
|
+
.group(['url', 'livereload', 'no-livereload', 'open', 'no-open', 'print-index', 'cache', 'forward-browser-logs'], 'AEM Options')
|
|
101
101
|
.option('allow-insecure', {
|
|
102
102
|
alias: 'allowInsecure',
|
|
103
103
|
describe: 'Whether to allow insecure requests to the server',
|
|
@@ -109,6 +109,12 @@ export default function up() {
|
|
|
109
109
|
type: 'boolean',
|
|
110
110
|
default: false,
|
|
111
111
|
})
|
|
112
|
+
.option('forward-browser-logs', {
|
|
113
|
+
alias: 'forwardBrowserLogs',
|
|
114
|
+
describe: 'Forward browser console logs to terminal',
|
|
115
|
+
type: 'boolean',
|
|
116
|
+
default: false,
|
|
117
|
+
})
|
|
112
118
|
|
|
113
119
|
.help();
|
|
114
120
|
},
|
|
@@ -127,6 +133,7 @@ export default function up() {
|
|
|
127
133
|
.withOpen(path.basename(argv.$0) === 'aem' ? argv.open : false)
|
|
128
134
|
.withTLS(argv.tlsKey, argv.tlsCert)
|
|
129
135
|
.withLiveReload(argv.livereload)
|
|
136
|
+
.withForwardBrowserLogs(argv.forwardBrowserLogs)
|
|
130
137
|
.withSiteToken(argv.siteToken || await getSiteTokenFromFile())
|
|
131
138
|
.withUrl(argv.url)
|
|
132
139
|
.withPrintIndex(argv.printIndex)
|