@adobe/aem-cli 16.19.1 → 16.19.3

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,17 @@
1
+ ## [16.19.3](https://github.com/adobe/helix-cli/compare/v16.19.2...v16.19.3) (2026-05-11)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * **deps:** update external fixes ([#2722](https://github.com/adobe/helix-cli/issues/2722)) ([aaf998f](https://github.com/adobe/helix-cli/commit/aaf998f1d130c4a36428a3277ea9d1a516cb6050))
7
+
8
+ ## [16.19.2](https://github.com/adobe/helix-cli/compare/v16.19.1...v16.19.2) (2026-05-11)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * **content:** correctness fixes for push, merge, and delete ([#2721](https://github.com/adobe/helix-cli/issues/2721)) ([efc6f11](https://github.com/adobe/helix-cli/commit/efc6f1138d55493dd546da300114df83ad006c5e))
14
+
1
15
  ## [16.19.1](https://github.com/adobe/helix-cli/compare/v16.19.0...v16.19.1) (2026-04-28)
2
16
 
3
17
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adobe/aem-cli",
3
- "version": "16.19.1",
3
+ "version": "16.19.3",
4
4
  "description": "AEM CLI",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -68,8 +68,8 @@
68
68
  "https-proxy-agent": "9.0.0",
69
69
  "ignore": "7.0.5",
70
70
  "ini": "6.0.0",
71
- "isomorphic-git": "1.37.5",
72
- "jose": "6.2.2",
71
+ "isomorphic-git": "1.37.6",
72
+ "jose": "6.2.3",
73
73
  "mime": "4.1.0",
74
74
  "livereload-js": "4.0.2",
75
75
  "node-diff3": "3.2.0",
@@ -95,7 +95,7 @@
95
95
  "eslint": "9.4.0",
96
96
  "esmock": "2.7.3",
97
97
  "husky": "9.1.7",
98
- "junit-report-builder": "5.1.1",
98
+ "junit-report-builder": "5.1.2",
99
99
  "lint-staged": "16.4.0",
100
100
  "mocha": "11.7.5",
101
101
  "mocha-multi-reporters": "1.5.1",
@@ -162,7 +162,8 @@ export class DaClient {
162
162
  }
163
163
 
164
164
  /**
165
- * Deletes a file or folder. Idempotent.
165
+ * Deletes a file or folder. Idempotent: 404 is treated as success.
166
+ * Throws on transport or server errors so callers don't silently treat them as success.
166
167
  */
167
168
  async deleteSource(org, repo, daPath) {
168
169
  const url = `${DA_ADMIN}/source/${org}/${repo}${daPath}`;
@@ -173,7 +174,10 @@ export class DaClient {
173
174
  if (res.status === 401) {
174
175
  throw new Error('Unauthorized: invalid or missing token');
175
176
  }
176
- return res.ok || res.status === 204;
177
+ if (res.ok || res.status === 204 || res.status === 404) {
178
+ return true;
179
+ }
180
+ throw new Error(`DELETE failed for ${daPath}: ${res.status} ${res.statusText}`);
177
181
  }
178
182
 
179
183
  /**
@@ -96,7 +96,7 @@ export default class MergeCommand {
96
96
  return;
97
97
  }
98
98
 
99
- const token = await getValidToken(log, this._token, contentDir);
99
+ const token = await getValidToken(log, this._token, this._dir);
100
100
  const client = new DaClient(token);
101
101
 
102
102
  // Get the HEAD commit to read base blobs
@@ -108,16 +108,16 @@ export default class PushCommand {
108
108
  * @param {string} repo
109
109
  * @param {string} contentDir
110
110
  * @param {string[]} targets
111
- * @returns {Promise<{ pushed: number, errors: number, successfullyPushed: Set<string> }>}
111
+ * @returns {Promise<{ pushed: number, errors: number }>}
112
112
  */
113
113
  async _uploadFiles(client, org, repo, contentDir, targets) {
114
114
  const { log } = this;
115
115
  let pushed = 0;
116
116
  let errors = 0;
117
- const successfullyPushed = new Set();
118
117
 
118
+ // processQueue consumes its input array via shift(); copy so the caller's list survives.
119
119
  const results = await processQueue(
120
- targets,
120
+ [...targets],
121
121
  async (daPath) => {
122
122
  const localPath = path.join(contentDir, ...daPath.split('/').filter(Boolean));
123
123
  const ext = daPath.split('.').pop();
@@ -125,7 +125,7 @@ export default class PushCommand {
125
125
  const buffer = await fse.readFile(localPath);
126
126
  await client.putSource(org, repo, daPath, buffer, getContentType(ext));
127
127
  log.info(` ✓ ${daPath}`);
128
- return { ok: true, daPath };
128
+ return { ok: true };
129
129
  } catch (err) {
130
130
  log.warn(` ✗ ${daPath}: ${err.message}`);
131
131
  return { ok: false };
@@ -136,13 +136,12 @@ export default class PushCommand {
136
136
  for (const r of results) {
137
137
  if (r.ok) {
138
138
  pushed += 1;
139
- successfullyPushed.add(r.daPath);
140
139
  } else {
141
140
  errors += 1;
142
141
  }
143
142
  }
144
143
 
145
- return { pushed, errors, successfullyPushed };
144
+ return { pushed, errors };
146
145
  }
147
146
 
148
147
  /**
@@ -151,21 +150,21 @@ export default class PushCommand {
151
150
  * @param {string} org
152
151
  * @param {string} repo
153
152
  * @param {string[]} deleted
154
- * @returns {Promise<{ pushed: number, errors: number, successfullyDeleted: Set<string> }>}
153
+ * @returns {Promise<{ pushed: number, errors: number }>}
155
154
  */
156
155
  async _deleteFiles(client, org, repo, deleted) {
157
156
  const { log } = this;
158
157
  let pushed = 0;
159
158
  let errors = 0;
160
- const successfullyDeleted = new Set();
161
159
 
160
+ // processQueue consumes its input array via shift(); copy so the caller's list survives.
162
161
  const results = await processQueue(
163
- deleted,
162
+ [...deleted],
164
163
  async (daPath) => {
165
164
  try {
166
165
  await client.deleteSource(org, repo, daPath);
167
166
  log.info(` ✓ deleted ${daPath}`);
168
- return { ok: true, daPath };
167
+ return { ok: true };
169
168
  } catch (err) {
170
169
  log.warn(` ✗ ${daPath}: ${err.message}`);
171
170
  return { ok: false };
@@ -176,13 +175,12 @@ export default class PushCommand {
176
175
  for (const r of results) {
177
176
  if (r.ok) {
178
177
  pushed += 1;
179
- successfullyDeleted.add(r.daPath);
180
178
  } else {
181
179
  errors += 1;
182
180
  }
183
181
  }
184
182
 
185
- return { pushed, errors, successfullyDeleted };
183
+ return { pushed, errors };
186
184
  }
187
185
 
188
186
  async run() {
@@ -207,12 +205,21 @@ export default class PushCommand {
207
205
  const syncedOid = await resolveSyncedOid(fs, contentDir);
208
206
  const lastSyncTime = await getCommitCommitterTimeMs(fs, contentDir, syncedOid);
209
207
 
210
- let { added, modified, deleted } = await diffCommitTrees(fs, contentDir, syncedOid, headOid);
208
+ const fullChanges = await diffCommitTrees(fs, contentDir, syncedOid, headOid);
209
+
210
+ const scope = this._pushPath ? this._pushPath.replace(/\/+$/, '') : null;
211
+ const inScope = (daPath) => scope === null
212
+ || daPath === scope
213
+ || daPath.startsWith(`${scope}/`);
214
+ const added = fullChanges.added.filter(inScope);
215
+ const modified = fullChanges.modified.filter(inScope);
216
+ const deleted = fullChanges.deleted.filter(inScope);
211
217
 
212
- const inScope = (daPath) => !this._pushPath || daPath.startsWith(this._pushPath);
213
- added = added.filter(inScope);
214
- modified = modified.filter(inScope);
215
- deleted = deleted.filter(inScope);
218
+ const fullCount = fullChanges.added.length
219
+ + fullChanges.modified.length
220
+ + fullChanges.deleted.length;
221
+ const scopeCount = added.length + modified.length + deleted.length;
222
+ const scopeIsComplete = fullCount === scopeCount;
216
223
 
217
224
  if (added.length === 0 && modified.length === 0 && deleted.length === 0) {
218
225
  log.info('Nothing to push. No commits ahead of the last da.live sync.');
@@ -261,27 +268,27 @@ export default class PushCommand {
261
268
  return;
262
269
  }
263
270
 
264
- const putTargets = [...added, ...modified];
265
271
  const {
266
272
  pushed: putPushed,
267
273
  errors: putErrors,
268
- successfullyPushed,
269
- } = await this._uploadFiles(client, org, repo, contentDir, putTargets);
274
+ } = await this._uploadFiles(client, org, repo, contentDir, [...added, ...modified]);
270
275
 
271
276
  const {
272
277
  pushed: deletePushed,
273
278
  errors: deleteErrors,
274
- successfullyDeleted,
275
279
  } = await this._deleteFiles(client, org, repo, deleted);
276
280
 
277
281
  const pushed = putPushed + deletePushed;
278
282
  const pushErrors = putErrors + deleteErrors;
283
+ const allOk = pushErrors === 0;
279
284
 
280
- const allPutsOk = putTargets.every((p) => successfullyPushed.has(p));
281
- const allDeletesOk = deleted.every((p) => successfullyDeleted.has(p));
282
-
283
- if (allPutsOk && allDeletesOk) {
285
+ if (allOk && scopeIsComplete) {
284
286
  await writeSyncedRef(fs, contentDir, headOid);
287
+ } else if (allOk) {
288
+ log.info(
289
+ '\n--path filter held back other changes; sync ref not advanced. '
290
+ + 'Run \'aem content push\' (without --path) to fully sync.',
291
+ );
285
292
  } else {
286
293
  log.warn('\nSync ref not updated: fix errors and push again to finish syncing this commit.');
287
294
  }