@adobe/aem-cli 16.19.7 → 16.19.8

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 CHANGED
@@ -1,3 +1,10 @@
1
+ ## [16.19.8](https://github.com/adobe/helix-cli/compare/v16.19.7...v16.19.8) (2026-05-20)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * Add --preferPlainHtml flag ([#2728](https://github.com/adobe/helix-cli/issues/2728)) ([0ae790f](https://github.com/adobe/helix-cli/commit/0ae790fdc9ac5a36385ef2efa9e9f717c3321eaa))
7
+
1
8
  ## [16.19.7](https://github.com/adobe/helix-cli/compare/v16.19.6...v16.19.7) (2026-05-19)
2
9
 
3
10
 
package/README.md CHANGED
@@ -99,6 +99,12 @@ Plain HTML files contain only the main content and an optional metadata block. T
99
99
  - Merges in `head.html` content
100
100
  - The metadata block is removed from the rendered content and converted to meta tags in the `<head>`.
101
101
 
102
+ When a request resolves to `<stem>`, the CLI looks up `<stem>.html` first and falls back to `<stem>.plain.html`. Pass `--preferPlainHtml` to reverse this order — useful when you keep both files side-by-side and want the plain version to win:
103
+
104
+ ```
105
+ $ aem up --html-folder drafts --preferPlainHtml
106
+ ```
107
+
102
108
  ### setting up a self-signed cert for using https
103
109
 
104
110
  1. create the certificate
@@ -188,6 +194,7 @@ If present, `ALL_PROXY` is used as fallback if there is no other match.
188
194
  | `--forward-browser-logs` | `AEM_FORWARD_BROWSER_LOGS` | `false` | Forward browser console logs to terminal. |
189
195
  | `--html-folder` | `AEM_HTML_FOLDER` | undefined | Serve HTML files from folder without extensions. |
190
196
  | `--html-mount` | `AEM_HTML_MOUNT` | `/FOLDER` | URL path where html-folder files are served. |
197
+ | `--prefer-plain-html` | `AEM_PREFER_PLAIN_HTML` | `false` | Prefer `<stem>.plain.html` over `<stem>.html` when both exist. |
191
198
  | `--open` | `AEM_OPEN` | `/` | Open a browser window at specified path after server start. |
192
199
  | `--no-open` | `AEM_NO_OPEN` | `false` | Disable automatic opening of browser window. |
193
200
  | `--tls-key` | `AEM_TLS_KEY` | undefined | Path to .key file (for enabling TLS) |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adobe/aem-cli",
3
- "version": "16.19.7",
3
+ "version": "16.19.8",
4
4
  "description": "AEM CLI",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -99,6 +99,11 @@ export class HelixProject extends BaseProject {
99
99
  return this;
100
100
  }
101
101
 
102
+ withPreferPlainHtml(value) {
103
+ this._preferPlainHtml = value;
104
+ return this;
105
+ }
106
+
102
107
  get proxyUrl() {
103
108
  return this._proxyUrl;
104
109
  }
@@ -152,6 +157,7 @@ export class HelixProject extends BaseProject {
152
157
  if (this._htmlFolder) {
153
158
  const mount = this._htmlMount || `/${this._htmlFolder}`;
154
159
  this._server.withHtmlFolder(this._htmlFolder, mount);
160
+ this._server.withPreferPlainHtml(this._preferPlainHtml);
155
161
  }
156
162
  await super.init();
157
163
  this._indexer = new Indexer()
@@ -26,6 +26,11 @@ import { transformContentMetadataHtml } from '../content/content-metadata-html.j
26
26
  const LOGIN_ROUTE = '/.aem/cli/login';
27
27
  const LOGIN_ACK_ROUTE = '/.aem/cli/login/ack';
28
28
 
29
+ // HTML folder candidate extensions, in lookup order.
30
+ // First entry takes precedence when multiple candidates exist on disk.
31
+ const HTML_FOLDER_EXTENSIONS = ['.html', '.plain.html'];
32
+ const HTML_FOLDER_EXTENSIONS_PREFER_PLAIN = [...HTML_FOLDER_EXTENSIONS].reverse();
33
+
29
34
  /**
30
35
  * @param {import('express').Request} req
31
36
  * @param {import('./RequestContext.js').default} ctx
@@ -92,6 +97,11 @@ export class HelixServer extends BaseServer {
92
97
  return this;
93
98
  }
94
99
 
100
+ withPreferPlainHtml(value) {
101
+ this._preferPlainHtml = value;
102
+ return this;
103
+ }
104
+
95
105
  get mountPrefix() {
96
106
  return this._mountPrefix;
97
107
  }
@@ -200,59 +210,54 @@ export class HelixServer extends BaseServer {
200
210
  }
201
211
 
202
212
  /**
203
- * Resolves which HTML file to serve from the HTML folder
213
+ * Resolves a candidate file inside the HTML folder, returning its absolute path
214
+ * if it exists and passes the security check, or null otherwise.
204
215
  * @param {string} relativePath path relative to HTML folder
205
- * @returns {Promise<{file: string, isPlain: boolean}|null>} resolved file info or null
216
+ * @returns {Promise<string|null>} absolute path, or null if missing/invalid
206
217
  */
207
- async resolveHtmlFolderFile(relativePath) {
208
- // Security check: prevent path traversal with /../ anywhere in the path
209
- if (relativePath.includes('/../')) {
210
- return null;
211
- }
212
-
213
- // Don't process if it already has an extension
214
- if (relativePath.includes('.')) {
215
- return null;
216
- }
217
-
218
- // Try .html first
219
- const htmlFile = path.resolve(
218
+ async resolveCandidate(relativePath) {
219
+ const file = path.resolve(
220
220
  this._project.directory,
221
221
  this._htmlFolder,
222
- `${relativePath}.html`,
222
+ relativePath,
223
223
  );
224
224
 
225
- if (!utils.validatePathSecurity(htmlFile, this._project.directory)) {
225
+ if (!utils.validatePathSecurity(file, this._project.directory)) {
226
226
  return null;
227
227
  }
228
228
 
229
229
  try {
230
- const stats = await lstat(htmlFile);
230
+ const stats = await lstat(file);
231
231
  if (stats.isFile()) {
232
- return { file: htmlFile, isPlain: false };
232
+ return file;
233
233
  }
234
234
  } catch (e) {
235
- // .html not found, try .plain.html
235
+ // not found
236
236
  }
237
+ return null;
238
+ }
237
239
 
238
- // Try .plain.html
239
- const plainHtmlFile = path.resolve(
240
- this._project.directory,
241
- this._htmlFolder,
242
- `${relativePath}.plain.html`,
243
- );
244
-
245
- if (!utils.validatePathSecurity(plainHtmlFile, this._project.directory)) {
240
+ /**
241
+ * Resolves which HTML file to serve from the HTML folder
242
+ * @param {string} relativePath path relative to HTML folder
243
+ * @returns {Promise<{file: string, isPlain: boolean}|null>} resolved file info or null
244
+ */
245
+ async resolveHtmlFolderFile(relativePath) {
246
+ // Don't process if it already has an extension
247
+ if (relativePath.includes('.')) {
246
248
  return null;
247
249
  }
248
250
 
249
- try {
250
- const stats = await lstat(plainHtmlFile);
251
- if (stats.isFile()) {
252
- return { file: plainHtmlFile, isPlain: true };
251
+ const candidates = this._preferPlainHtml
252
+ ? HTML_FOLDER_EXTENSIONS_PREFER_PLAIN
253
+ : HTML_FOLDER_EXTENSIONS;
254
+
255
+ for (const ext of candidates) {
256
+ // eslint-disable-next-line no-await-in-loop
257
+ const file = await this.resolveCandidate(`${relativePath}${ext}`);
258
+ if (file) {
259
+ return { file, isPlain: ext === '.plain.html' };
253
260
  }
254
- } catch (e) {
255
- // Neither exists
256
261
  }
257
262
 
258
263
  return null;
package/src/up.cmd.js CHANGED
@@ -65,6 +65,11 @@ export default class UpCommand extends AbstractServerCommand {
65
65
  return this;
66
66
  }
67
67
 
68
+ withPreferPlainHtml(value) {
69
+ this._preferPlainHtml = value;
70
+ return this;
71
+ }
72
+
68
73
  async doStop() {
69
74
  await super.doStop();
70
75
  if (this._watcher) {
@@ -118,7 +123,8 @@ export default class UpCommand extends AbstractServerCommand {
118
123
  .withSiteToken(this._siteToken)
119
124
  .withCookies(this._cookies)
120
125
  .withHtmlFolder(this._htmlFolder)
121
- .withHtmlMount(this._htmlMount);
126
+ .withHtmlMount(this._htmlMount)
127
+ .withPreferPlainHtml(this._preferPlainHtml);
122
128
 
123
129
  this.log.info(chalk`{yellow ___ ________ ___ __ __ v${pkgJson.version}}`);
124
130
  this.log.info(chalk`{yellow / | / ____/ |/ / _____(_)___ ___ __ __/ /___ _/ /_____ _____}`);
package/src/up.js CHANGED
@@ -125,6 +125,12 @@ export default function up() {
125
125
  describe: 'URL path where html-folder files are served (e.g., / for root). Defaults to /FOLDER.',
126
126
  type: 'string',
127
127
  })
128
+ .option('prefer-plain-html', {
129
+ alias: 'preferPlainHtml',
130
+ describe: 'When --html-folder is set, prefer <stem>.plain.html over <stem>.html if both exist.',
131
+ type: 'boolean',
132
+ default: false,
133
+ })
128
134
 
129
135
  .help();
130
136
  },
@@ -153,6 +159,7 @@ export default function up() {
153
159
  .withCookies(argv.cookies)
154
160
  .withHtmlFolder(argv.htmlFolder)
155
161
  .withHtmlMount(argv.htmlMount)
162
+ .withPreferPlainHtml(argv.preferPlainHtml)
156
163
  .run();
157
164
  },
158
165
  };