exec-staged 0.2.2 → 0.3.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/dist/bin/cli.js CHANGED
File without changes
@@ -8,6 +8,7 @@ export declare class Stage {
8
8
  private readonly mergeStatus;
9
9
  private head?;
10
10
  private _gitDir?;
11
+ private needsRevert;
11
12
  private get gitDir();
12
13
  private get artifactsDir();
13
14
  private get patchPath();
package/dist/lib/stage.js CHANGED
@@ -4,6 +4,7 @@ import { spawn, spawnSync } from './spawn.js';
4
4
  import micromatch from 'micromatch';
5
5
  import fs from 'node:fs';
6
6
  import path from 'node:path';
7
+ import { deregisterExitHandler, registerExitHandler } from 'on-process-exit';
7
8
  import semver from 'semver';
8
9
  import parseArgsStringToArgv from 'string-argv';
9
10
  export class Stage {
@@ -14,6 +15,7 @@ export class Stage {
14
15
  mergeStatus = [];
15
16
  head;
16
17
  _gitDir;
18
+ needsRevert = false;
17
19
  get gitDir() {
18
20
  return (this._gitDir ??= this.git(['rev-parse', '--absolute-git-dir']));
19
21
  }
@@ -37,6 +39,13 @@ export class Stage {
37
39
  this.logger.debug(error);
38
40
  throw error;
39
41
  }
42
+ this.needsRevert = true;
43
+ const exitHandlerId = registerExitHandler(() => {
44
+ if (this.needsRevert) {
45
+ this.needsRevert = false;
46
+ this.revert();
47
+ }
48
+ });
40
49
  try {
41
50
  await this.run(tasks);
42
51
  this.merge();
@@ -44,6 +53,7 @@ export class Stage {
44
53
  catch (error) {
45
54
  this.logger.debug(error);
46
55
  try {
56
+ this.needsRevert = false;
47
57
  this.revert();
48
58
  }
49
59
  catch (error) {
@@ -51,6 +61,9 @@ export class Stage {
51
61
  }
52
62
  throw error;
53
63
  }
64
+ finally {
65
+ deregisterExitHandler(exitHandlerId);
66
+ }
54
67
  }
55
68
  check() {
56
69
  this.logger.log(stageLifecycleMessages.check);
@@ -70,15 +83,15 @@ export class Stage {
70
83
  this.logger.log('⚠️ Unsupported git version!');
71
84
  throw new Error('unsupported git version');
72
85
  }
73
- let gitRootDirectory;
86
+ let prefix;
74
87
  try {
75
- gitRootDirectory = this.git(['rev-parse', '--show-toplevel']).trim();
88
+ prefix = this.git(['rev-parse', '--show-prefix']).trim();
76
89
  }
77
90
  catch (error) {
78
91
  this.logger.log('⚠️ Not a git repository!');
79
92
  throw new Error('cwd is not a git repository');
80
93
  }
81
- if (gitRootDirectory !== this.cwd) {
94
+ if (prefix !== '') {
82
95
  this.logger.log('⚠️ Not in git root directory!');
83
96
  throw new Error('cwd is not a git repository root directory');
84
97
  }
@@ -237,16 +250,19 @@ export class Stage {
237
250
  '-m',
238
251
  STAGED_CHANGES_COMMIT_MESSAGE,
239
252
  ]);
240
- // apply patch containing unstaged changes
241
- this.git([
242
- 'apply',
243
- '--allow-empty',
244
- '--recount',
245
- '--unidiff-zero',
246
- '--whitespace=nowarn',
247
- '--3way',
248
- this.patchPath,
249
- ]);
253
+ // apply patch containing unstaged changes.
254
+ // `--allow-empty` could be used here, but it was not added to `git apply`
255
+ // until 2.35.0 (January 2022), so we skip the call when the patch is empty.
256
+ if (fs.statSync(this.patchPath).size > 0) {
257
+ this.git([
258
+ 'apply',
259
+ '--recount',
260
+ '--unidiff-zero',
261
+ '--whitespace=nowarn',
262
+ '--3way',
263
+ this.patchPath,
264
+ ]);
265
+ }
250
266
  // unstaged deletions are not included in the patch and must be handled
251
267
  // separately because the patch cannot be applied if such files are
252
268
  // modified by tasks; use force: true to handle cases where the file
@@ -268,6 +284,7 @@ export class Stage {
268
284
  this.logger.log('⚠️ Error restoring unstaged changes from stash!');
269
285
  throw error;
270
286
  }
287
+ this.needsRevert = false;
271
288
  }
272
289
  revert() {
273
290
  this.logger.log(stageLifecycleMessages.revert);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "exec-staged",
3
- "version": "0.2.2",
3
+ "version": "0.3.0",
4
4
  "description": "Run commands against the current git index",
5
5
  "keywords": [
6
6
  "git",
@@ -27,33 +27,36 @@
27
27
  "dist/",
28
28
  "src/"
29
29
  ],
30
+ "scripts": {
31
+ "build": "pnpm clean && tsc --build",
32
+ "clean": "rm -rf dist/",
33
+ "prepare": "husky",
34
+ "prepublishOnly": "pnpm build",
35
+ "test": "pnpm build && tsx --test --experimental-test-coverage"
36
+ },
30
37
  "dependencies": {
31
- "commander": "^14.0.0",
32
- "cosmiconfig": "^9.0.0",
33
- "env-paths": "^3.0.0",
34
- "execa": "^9.6.0",
38
+ "commander": "^15.0.0",
39
+ "cosmiconfig": "^9.0.2",
40
+ "env-paths": "^4.0.0",
41
+ "execa": "^9.6.1",
35
42
  "micromatch": "^4.0.8",
36
- "on-process-exit": "^1.0.2",
37
- "semver": "^7.7.2",
43
+ "on-process-exit": "^1.1.0",
44
+ "semver": "^7.8.5",
38
45
  "string-argv": "^0.3.2"
39
46
  },
40
47
  "devDependencies": {
41
- "@trivago/prettier-plugin-sort-imports": "^5.2.2",
42
- "@tsconfig/node22": "^22.0.2",
43
- "@types/micromatch": "^4.0.9",
44
- "@types/node": "^22.15.31",
45
- "@types/semver": "^7.7.0",
48
+ "@trivago/prettier-plugin-sort-imports": "^6.0.2",
49
+ "@tsconfig/node22": "^22.0.5",
50
+ "@types/micromatch": "^4.0.10",
51
+ "@types/node": "^22.20.0",
52
+ "@types/semver": "^7.7.1",
46
53
  "husky": "^9.1.7",
47
- "knip": "^5.60.2",
54
+ "knip": "^6.17.1",
48
55
  "lint-staged": "github:ItsNickBarry/lint-staged#knip",
49
- "prettier": "^3.5.3",
50
- "prettier-plugin-packagejson": "^2.5.15",
51
- "tsx": "^4.20.1",
52
- "typescript": "^5.8.3"
53
- },
54
- "scripts": {
55
- "build": "pnpm clean && tsc --build",
56
- "clean": "rm -rf dist/",
57
- "test": "pnpm build && tsx --test --experimental-test-coverage"
56
+ "prettier": "^3.8.4",
57
+ "prettier-plugin-packagejson": "^3.0.2",
58
+ "release-it": "^20.2.0",
59
+ "tsx": "^4.22.4",
60
+ "typescript": "^6.0.3"
58
61
  }
59
- }
62
+ }
package/src/lib/stage.ts CHANGED
@@ -12,6 +12,7 @@ import { spawn, spawnSync } from './spawn.js';
12
12
  import micromatch from 'micromatch';
13
13
  import fs from 'node:fs';
14
14
  import path from 'node:path';
15
+ import { deregisterExitHandler, registerExitHandler } from 'on-process-exit';
15
16
  import semver from 'semver';
16
17
  import parseArgsStringToArgv from 'string-argv';
17
18
 
@@ -23,6 +24,7 @@ export class Stage {
23
24
  private readonly mergeStatus: (typeof MERGE_FILES)[number][] = [];
24
25
  private head?: string;
25
26
  private _gitDir?: string;
27
+ private needsRevert: boolean = false;
26
28
 
27
29
  private get gitDir(): string {
28
30
  return (this._gitDir ??= this.git(['rev-parse', '--absolute-git-dir']));
@@ -51,6 +53,15 @@ export class Stage {
51
53
  throw error;
52
54
  }
53
55
 
56
+ this.needsRevert = true;
57
+
58
+ const exitHandlerId = registerExitHandler(() => {
59
+ if (this.needsRevert) {
60
+ this.needsRevert = false;
61
+ this.revert();
62
+ }
63
+ });
64
+
54
65
  try {
55
66
  await this.run(tasks);
56
67
  this.merge();
@@ -58,12 +69,15 @@ export class Stage {
58
69
  this.logger.debug(error);
59
70
 
60
71
  try {
72
+ this.needsRevert = false;
61
73
  this.revert();
62
74
  } catch (error) {
63
75
  this.logger.debug(error);
64
76
  }
65
77
 
66
78
  throw error;
79
+ } finally {
80
+ deregisterExitHandler(exitHandlerId);
67
81
  }
68
82
  }
69
83
 
@@ -90,16 +104,16 @@ export class Stage {
90
104
  throw new Error('unsupported git version');
91
105
  }
92
106
 
93
- let gitRootDirectory: string;
107
+ let prefix: string;
94
108
 
95
109
  try {
96
- gitRootDirectory = this.git(['rev-parse', '--show-toplevel']).trim();
110
+ prefix = this.git(['rev-parse', '--show-prefix']).trim();
97
111
  } catch (error) {
98
112
  this.logger.log('⚠️ Not a git repository!');
99
113
  throw new Error('cwd is not a git repository');
100
114
  }
101
115
 
102
- if (gitRootDirectory !== this.cwd) {
116
+ if (prefix !== '') {
103
117
  this.logger.log('⚠️ Not in git root directory!');
104
118
  throw new Error('cwd is not a git repository root directory');
105
119
  }
@@ -311,16 +325,19 @@ export class Stage {
311
325
  STAGED_CHANGES_COMMIT_MESSAGE,
312
326
  ]);
313
327
 
314
- // apply patch containing unstaged changes
315
- this.git([
316
- 'apply',
317
- '--allow-empty',
318
- '--recount',
319
- '--unidiff-zero',
320
- '--whitespace=nowarn',
321
- '--3way',
322
- this.patchPath,
323
- ]);
328
+ // apply patch containing unstaged changes.
329
+ // `--allow-empty` could be used here, but it was not added to `git apply`
330
+ // until 2.35.0 (January 2022), so we skip the call when the patch is empty.
331
+ if (fs.statSync(this.patchPath).size > 0) {
332
+ this.git([
333
+ 'apply',
334
+ '--recount',
335
+ '--unidiff-zero',
336
+ '--whitespace=nowarn',
337
+ '--3way',
338
+ this.patchPath,
339
+ ]);
340
+ }
324
341
 
325
342
  // unstaged deletions are not included in the patch and must be handled
326
343
  // separately because the patch cannot be applied if such files are
@@ -345,6 +362,8 @@ export class Stage {
345
362
  this.logger.log('⚠️ Error restoring unstaged changes from stash!');
346
363
  throw error;
347
364
  }
365
+
366
+ this.needsRevert = false;
348
367
  }
349
368
 
350
369
  protected revert() {