@adobe/aem-cli 16.11.3 → 16.12.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 +27 -0
- package/package.json +2 -2
- package/src/git-utils.js +124 -1
- package/src/up.cmd.js +24 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,30 @@
|
|
|
1
|
+
# [16.12.0](https://github.com/adobe/helix-cli/compare/v16.11.3...v16.12.0) (2025-09-16)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* address PR review comments ([a14cabc](https://github.com/adobe/helix-cli/commit/a14cabc8aaa21525ded5bd9b4c021baf87da07f3))
|
|
7
|
+
* address PR review comments for git worktree support ([4532cac](https://github.com/adobe/helix-cli/commit/4532cac0327e32b3aca045aa99e4279ba7b15d26)), closes [#2583](https://github.com/adobe/helix-cli/issues/2583)
|
|
8
|
+
* comprehensive test setup improvements for git submodules and worktrees ([2715b95](https://github.com/adobe/helix-cli/commit/2715b9578831544ed0b42e93fdd87b2922f6d953))
|
|
9
|
+
* ensure test directories exist before running git init ([c29542c](https://github.com/adobe/helix-cli/commit/c29542c5cd6c17e7a5e2691d7d4189c07a9d6914))
|
|
10
|
+
* handle case where initGit is called from within target directory ([ad4ccde](https://github.com/adobe/helix-cli/commit/ad4ccde34e97afafdeac0c51b3c8e3ddefee7e3f))
|
|
11
|
+
* handle Windows file locking in git worktree tests ([b271662](https://github.com/adobe/helix-cli/commit/b2716624e3fd28031d01b45cf86556d22e4e0f9f))
|
|
12
|
+
* improve initGit error handling and ensure directory creation ([76514d0](https://github.com/adobe/helix-cli/commit/76514d088bc21bc51a379d292ca42bdde280d184))
|
|
13
|
+
* increase Windows CI test timeout to 60 seconds ([149176a](https://github.com/adobe/helix-cli/commit/149176a2b642aee4efcb6e2d1de0a188cc789e12))
|
|
14
|
+
* inline git init to avoid directory change issues ([72fe216](https://github.com/adobe/helix-cli/commit/72fe216f1355369eb2d82bc4356aa4a3aef07cb4))
|
|
15
|
+
* prevent worktree name collisions in tests ([73b3cbf](https://github.com/adobe/helix-cli/commit/73b3cbfffcd6693163193f2031a344a2d2361d67))
|
|
16
|
+
* properly configure Windows CI tests with increased timeout ([59f3a61](https://github.com/adobe/helix-cli/commit/59f3a6175af1c236c34ad1a54639cf7700fbd230))
|
|
17
|
+
* resolve linting errors in test file ([ed31cca](https://github.com/adobe/helix-cli/commit/ed31cca0f82df047dca5e17b6ad6cba38f32f4c2))
|
|
18
|
+
* revert to using initGit helper for consistent test setup ([b517cb3](https://github.com/adobe/helix-cli/commit/b517cb3e2487b180b4b3cdaf2e3949c3984e461f))
|
|
19
|
+
* split statements to separate lines for linting ([068d91c](https://github.com/adobe/helix-cli/commit/068d91cfc36ef1cc6353ae9ad17b05de6e119734))
|
|
20
|
+
* split test runs to identify hanging test ([8a7dd86](https://github.com/adobe/helix-cli/commit/8a7dd862ca4024a25daac0b37105d9f468a113c3))
|
|
21
|
+
* use process.chdir instead of shell.cd for more reliable directory handling ([a31c327](https://github.com/adobe/helix-cli/commit/a31c327cb96add05d665ee9b00117d2be50150e4))
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
### Features
|
|
25
|
+
|
|
26
|
+
* add git worktree support with dynamic port selection ([cb763bd](https://github.com/adobe/helix-cli/commit/cb763bd004344d2a5361d675c8028efa2279ae1a))
|
|
27
|
+
|
|
1
28
|
## [16.11.3](https://github.com/adobe/helix-cli/compare/v16.11.2...v16.11.3) (2025-09-13)
|
|
2
29
|
|
|
3
30
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adobe/aem-cli",
|
|
3
|
-
"version": "16.
|
|
3
|
+
"version": "16.12.0",
|
|
4
4
|
"description": "AEM CLI",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"scripts": {
|
|
12
12
|
"check": "npm run lint && npm run test",
|
|
13
13
|
"test": "c8 mocha",
|
|
14
|
-
"test-ci-win": "
|
|
14
|
+
"test-ci-win": "mocha --reporter spec test -t 300000 --exit",
|
|
15
15
|
"lint": "eslint .",
|
|
16
16
|
"lint:fix": "eslint --fix .",
|
|
17
17
|
"semantic-release": "semantic-release",
|
package/src/git-utils.js
CHANGED
|
@@ -144,6 +144,24 @@ export default class GitUtils {
|
|
|
144
144
|
* @returns {Promise<string>} current branch or tag
|
|
145
145
|
*/
|
|
146
146
|
static async getBranch(dir, fallback = this.DEFAULT_BRANCH) {
|
|
147
|
+
// For worktrees, we need to get the actual git directory
|
|
148
|
+
const gitDir = await this.getGitDirectory(dir);
|
|
149
|
+
|
|
150
|
+
// If it's a worktree, try to get the branch from the worktree HEAD file
|
|
151
|
+
if (await this.isGitWorktree(dir)) {
|
|
152
|
+
try {
|
|
153
|
+
// Read the HEAD file in the worktree's git directory
|
|
154
|
+
const headPath = path.join(gitDir, 'HEAD');
|
|
155
|
+
const headContent = await fse.readFile(headPath, 'utf-8');
|
|
156
|
+
const match = headContent.match(/^ref: refs\/heads\/(.+)$/m);
|
|
157
|
+
if (match) {
|
|
158
|
+
return match[1].trim();
|
|
159
|
+
}
|
|
160
|
+
} catch (e) {
|
|
161
|
+
// Fall through to regular processing
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
147
165
|
// current commit sha
|
|
148
166
|
const rev = await git.resolveRef({ fs, dir, ref: 'HEAD' });
|
|
149
167
|
// reverse-lookup tag from commit sha
|
|
@@ -194,7 +212,20 @@ export default class GitUtils {
|
|
|
194
212
|
*/
|
|
195
213
|
static async getOrigin(dir) {
|
|
196
214
|
try {
|
|
197
|
-
|
|
215
|
+
// For worktrees, we need to use the main repository's config
|
|
216
|
+
let gitDir = dir;
|
|
217
|
+
if (await this.isGitWorktree(dir)) {
|
|
218
|
+
// Get the worktree's git directory
|
|
219
|
+
const worktreeGitDir = await this.getGitDirectory(dir);
|
|
220
|
+
// Extract the main repository path from the worktree git dir
|
|
221
|
+
// Worktree git dirs are like: /path/to/repo/.git/worktrees/worktree-name
|
|
222
|
+
const match = worktreeGitDir.match(/^(.+?)\/\.git\/worktrees\//);
|
|
223
|
+
if (match) {
|
|
224
|
+
[, gitDir] = match;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const rmt = (await git.listRemotes({ fs, dir: gitDir })).find((entry) => entry.remote === 'origin');
|
|
198
229
|
return typeof rmt === 'object' ? rmt.url : '';
|
|
199
230
|
} catch (e) {
|
|
200
231
|
// don't fail if directory is not a git repository
|
|
@@ -264,4 +295,96 @@ export default class GitUtils {
|
|
|
264
295
|
}))
|
|
265
296
|
.then((obj) => obj.object);
|
|
266
297
|
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Checks if the given directory is a git worktree.
|
|
301
|
+
*
|
|
302
|
+
* @param {string} dir working tree directory path
|
|
303
|
+
* @returns {Promise<boolean>} `true` if the directory is a git worktree
|
|
304
|
+
*/
|
|
305
|
+
static async isGitWorktree(dir) {
|
|
306
|
+
const gitPath = path.resolve(dir, '.git');
|
|
307
|
+
try {
|
|
308
|
+
// Read the file directly - if it's not a file, readFile will throw
|
|
309
|
+
const content = await fse.readFile(gitPath, 'utf-8');
|
|
310
|
+
return content.includes('/worktrees/');
|
|
311
|
+
} catch (e) {
|
|
312
|
+
// Either doesn't exist, is a directory, or can't be read
|
|
313
|
+
// ignore
|
|
314
|
+
}
|
|
315
|
+
return false;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Checks if the given directory is a git submodule.
|
|
320
|
+
*
|
|
321
|
+
* @param {string} dir working tree directory path
|
|
322
|
+
* @returns {Promise<boolean>} `true` if the directory is a git submodule
|
|
323
|
+
*/
|
|
324
|
+
static async isGitSubmodule(dir) {
|
|
325
|
+
const gitPath = path.resolve(dir, '.git');
|
|
326
|
+
try {
|
|
327
|
+
// Read the file directly - if it's not a file, readFile will throw
|
|
328
|
+
const content = await fse.readFile(gitPath, 'utf-8');
|
|
329
|
+
// Submodules have relative paths to .git/modules
|
|
330
|
+
return content.includes('/.git/modules/') || content.includes('\\.git\\modules\\');
|
|
331
|
+
} catch (e) {
|
|
332
|
+
// Either doesn't exist, is a directory, or can't be read
|
|
333
|
+
// ignore
|
|
334
|
+
}
|
|
335
|
+
return false;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Gets the actual git directory, resolving through worktree/submodule redirection.
|
|
340
|
+
*
|
|
341
|
+
* @param {string} dir working tree directory path
|
|
342
|
+
* @returns {Promise<string>} path to the actual git directory
|
|
343
|
+
*/
|
|
344
|
+
static async getGitDirectory(dir) {
|
|
345
|
+
const gitPath = path.resolve(dir, '.git');
|
|
346
|
+
try {
|
|
347
|
+
// Try to read as a file first (worktree/submodule case)
|
|
348
|
+
const content = await fse.readFile(gitPath, 'utf-8');
|
|
349
|
+
const match = content.match(/^gitdir: (.+)$/m);
|
|
350
|
+
if (match) {
|
|
351
|
+
const targetPath = match[1].trim();
|
|
352
|
+
// If it's a relative path, resolve it relative to the directory
|
|
353
|
+
if (!path.isAbsolute(targetPath)) {
|
|
354
|
+
return path.resolve(dir, targetPath);
|
|
355
|
+
}
|
|
356
|
+
return targetPath;
|
|
357
|
+
}
|
|
358
|
+
} catch (e) {
|
|
359
|
+
// If readFile fails, it's likely a directory - check if it exists
|
|
360
|
+
try {
|
|
361
|
+
const stat = await fse.lstat(gitPath);
|
|
362
|
+
if (stat.isDirectory()) {
|
|
363
|
+
return gitPath;
|
|
364
|
+
}
|
|
365
|
+
} catch (statErr) {
|
|
366
|
+
// ignore
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
return gitPath;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* Generates a deterministic port number based on branch name.
|
|
374
|
+
*
|
|
375
|
+
* @param {string} branchName the branch name to hash
|
|
376
|
+
* @param {number} basePort base port number (default: 3000)
|
|
377
|
+
* @param {number} range range of ports to use (default: 1000)
|
|
378
|
+
* @returns {number} port number between basePort and basePort + range - 1
|
|
379
|
+
*/
|
|
380
|
+
static hashBranchToPort(branchName, basePort = 3000, range = 1000) {
|
|
381
|
+
let hash = 0;
|
|
382
|
+
for (let i = 0; i < branchName.length; i += 1) {
|
|
383
|
+
// eslint-disable-next-line no-bitwise
|
|
384
|
+
hash = ((hash << 5) - hash) + branchName.charCodeAt(i);
|
|
385
|
+
// eslint-disable-next-line no-bitwise
|
|
386
|
+
hash &= hash; // Convert to 32bit integer
|
|
387
|
+
}
|
|
388
|
+
return basePort + (Math.abs(hash) % range);
|
|
389
|
+
}
|
|
267
390
|
}
|
package/src/up.cmd.js
CHANGED
|
@@ -64,7 +64,15 @@ export default class UpCommand extends AbstractServerCommand {
|
|
|
64
64
|
try {
|
|
65
65
|
const stat = await fse.lstat(path.resolve(this.directory, '.git'));
|
|
66
66
|
if (stat.isFile()) {
|
|
67
|
-
|
|
67
|
+
// Check if it's a submodule or a worktree
|
|
68
|
+
if (await GitUtils.isGitSubmodule(this.directory)) {
|
|
69
|
+
throw Error('git submodules are not supported.');
|
|
70
|
+
}
|
|
71
|
+
// Verify it's actually a worktree
|
|
72
|
+
if (!await GitUtils.isGitWorktree(this.directory)) {
|
|
73
|
+
throw Error('Unsupported git configuration: .git is a file but not a valid worktree or submodule.');
|
|
74
|
+
}
|
|
75
|
+
// It's a worktree - this is allowed
|
|
68
76
|
}
|
|
69
77
|
} catch (e) {
|
|
70
78
|
if (e.code === 'ENOENT') {
|
|
@@ -73,6 +81,15 @@ export default class UpCommand extends AbstractServerCommand {
|
|
|
73
81
|
throw e;
|
|
74
82
|
}
|
|
75
83
|
|
|
84
|
+
// Check if we're in a worktree and need to adjust the port
|
|
85
|
+
const isWorktree = await GitUtils.isGitWorktree(this.directory);
|
|
86
|
+
if (isWorktree && this._httpPort === 3000) {
|
|
87
|
+
// Only adjust port if using default port
|
|
88
|
+
const branch = await GitUtils.getBranch(this.directory);
|
|
89
|
+
this._httpPort = GitUtils.hashBranchToPort(branch);
|
|
90
|
+
this.log.info(chalk`Git worktree detected. Using port {cyan ${this._httpPort}} for branch {cyan ${branch}}`);
|
|
91
|
+
}
|
|
92
|
+
|
|
76
93
|
// init dev default file params
|
|
77
94
|
this._project = new HelixProject()
|
|
78
95
|
.withCwd(this.directory)
|
|
@@ -113,7 +130,7 @@ export default class UpCommand extends AbstractServerCommand {
|
|
|
113
130
|
|
|
114
131
|
try {
|
|
115
132
|
await this._project.init();
|
|
116
|
-
this.watchGit();
|
|
133
|
+
await this.watchGit();
|
|
117
134
|
} catch (e) {
|
|
118
135
|
throw Error(`Unable to start AEM: ${e.message}`);
|
|
119
136
|
}
|
|
@@ -155,10 +172,13 @@ export default class UpCommand extends AbstractServerCommand {
|
|
|
155
172
|
/**
|
|
156
173
|
* Watches the git repository for changes and restarts the server if necessary.
|
|
157
174
|
*/
|
|
158
|
-
watchGit() {
|
|
175
|
+
async watchGit() {
|
|
159
176
|
let timer = null;
|
|
160
177
|
|
|
161
|
-
|
|
178
|
+
// Resolve the actual git directory for worktrees
|
|
179
|
+
const gitDir = await GitUtils.getGitDirectory(this._project.directory);
|
|
180
|
+
|
|
181
|
+
this._watcher = chokidar.watch(gitDir, {
|
|
162
182
|
persistent: true,
|
|
163
183
|
ignoreInitial: true,
|
|
164
184
|
});
|