exec-staged 0.1.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/LICENSE +19 -0
- package/README.md +177 -0
- package/dist/bin/cli.d.ts +2 -0
- package/dist/bin/cli.js +19 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/lib/config.d.ts +5 -0
- package/dist/lib/config.js +47 -0
- package/dist/lib/constants.d.ts +13 -0
- package/dist/lib/constants.js +17 -0
- package/dist/lib/exec_staged.d.ts +2 -0
- package/dist/lib/exec_staged.js +13 -0
- package/dist/lib/logger.d.ts +7 -0
- package/dist/lib/logger.js +23 -0
- package/dist/lib/spawn.d.ts +30 -0
- package/dist/lib/spawn.js +37 -0
- package/dist/lib/stage.d.ts +24 -0
- package/dist/lib/stage.js +300 -0
- package/dist/types.d.ts +11 -0
- package/dist/types.js +1 -0
- package/package.json +59 -0
- package/src/bin/cli.ts +26 -0
- package/src/index.ts +3 -0
- package/src/lib/config.ts +71 -0
- package/src/lib/constants.ts +24 -0
- package/src/lib/exec_staged.ts +19 -0
- package/src/lib/logger.ts +31 -0
- package/src/lib/spawn.ts +41 -0
- package/src/lib/stage.ts +376 -0
- package/src/types.ts +17 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
Copyright (c) 2025 Nick Barry
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
5
|
+
in the Software without restriction, including without limitation the rights
|
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
8
|
+
furnished to do so, subject to the following conditions:
|
|
9
|
+
|
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
|
11
|
+
all copies or substantial portions of the Software.
|
|
12
|
+
|
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
19
|
+
THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
# Exec Staged
|
|
2
|
+
|
|
3
|
+
Run commands against files staged in git, ignoring unstaged changes and untracked files.
|
|
4
|
+
|
|
5
|
+
### Use Cases
|
|
6
|
+
|
|
7
|
+
- 🧵 Lint new changes before commit.
|
|
8
|
+
- 🧪 Test changes in isolation before commit.
|
|
9
|
+
- ✨ ???
|
|
10
|
+
|
|
11
|
+
### How it Works
|
|
12
|
+
|
|
13
|
+
1. Unstaged changes and untracked files are hidden in a git stash. This inludes unstaged deletions, which are temporarily restored.
|
|
14
|
+
2. User-configured tasks are run, with staged files passed in according to configuration.
|
|
15
|
+
3. Any changes made by tasks are added to the git index. This includes additions and deletions.
|
|
16
|
+
4. The stashed changes are restored.
|
|
17
|
+
5. If any step fails, the initial state is fully restored.
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
|
|
21
|
+
Install from npm, using your preferred package manager:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm install --save-dev exec-staged
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Usage
|
|
28
|
+
|
|
29
|
+
### Run from the CLI
|
|
30
|
+
|
|
31
|
+
If an `exec-staged` configuration file is present, it will be loaded by the executable:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
npx exec-staged
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
If no configuration is present, the executable can still run tasks passed as arguments:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
npx exec-staged "npm test"
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Run in a Script
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
import { execStaged } from 'exec-staged';
|
|
47
|
+
|
|
48
|
+
const cwd = process.cwd();
|
|
49
|
+
const tasks = [`npm test`];
|
|
50
|
+
const options = { quiet: true };
|
|
51
|
+
|
|
52
|
+
const result = await execStaged(cwd, tasks, options);
|
|
53
|
+
|
|
54
|
+
if (!result) {
|
|
55
|
+
throw new Error('exec-staged task failed');
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Run in a Pre-Commit Hook
|
|
60
|
+
|
|
61
|
+
[Husky](https://github.com/typicode/husky) is recommended for handling pre-commit hooks.
|
|
62
|
+
|
|
63
|
+
Install Husky:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
npm install --save-dev husky
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Add a hook to `.husky/pre-commit`:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
#!/bin/sh
|
|
73
|
+
npx exec-staged
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Run husky:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
npx husky
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Add a `package.json` script to run husky whenever your repository is cloned:
|
|
83
|
+
|
|
84
|
+
```json
|
|
85
|
+
{
|
|
86
|
+
"scripts": {
|
|
87
|
+
"prepare": "husky"
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
`exec-staged` will do nothing unless configured. See configuration information below.
|
|
93
|
+
|
|
94
|
+
## Configuration
|
|
95
|
+
|
|
96
|
+
`exec-staged` configuration consists of a list of commands to execute against the stage. Each command may be formatted as a plain `string`, or as an object containing additional attributes.
|
|
97
|
+
|
|
98
|
+
All [Cosmiconfig-compatible](https://www.npmjs.com/package/cosmiconfig#searchplaces) configuration files are supported.
|
|
99
|
+
|
|
100
|
+
Here is an example configuration:
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
// exec-staged.config.ts
|
|
104
|
+
import type { ExecStagedUserConfig } from 'exec-staged/types';
|
|
105
|
+
|
|
106
|
+
const config: ExecStagedUserConfig = [
|
|
107
|
+
'knip',
|
|
108
|
+
'knip --production',
|
|
109
|
+
{ task: 'prettier --write $FILES', glob: '*.{js,ts,json,md}' },
|
|
110
|
+
];
|
|
111
|
+
|
|
112
|
+
export default config;
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Plain commands are run every time, as-is:
|
|
116
|
+
|
|
117
|
+
<!-- prettier-ignore-start -->
|
|
118
|
+
```typescript
|
|
119
|
+
'knip --production'
|
|
120
|
+
```
|
|
121
|
+
<!-- prettier-ignore-end -->
|
|
122
|
+
|
|
123
|
+
Commands which include the `$FILES` token are only run if staged files are found, and those files are interpolated into the command in place of the token.
|
|
124
|
+
|
|
125
|
+
<!-- prettier-ignore-start -->
|
|
126
|
+
```typescript
|
|
127
|
+
'prettier --write $FILES'
|
|
128
|
+
// => prettier --write new_file.js modified_file.js
|
|
129
|
+
```
|
|
130
|
+
<!-- prettier-ignore-end -->
|
|
131
|
+
|
|
132
|
+
File filtering can be customized.
|
|
133
|
+
|
|
134
|
+
To filter files by name, add a `glob` filter (defaults to `'*'`):
|
|
135
|
+
|
|
136
|
+
```typescript
|
|
137
|
+
{ task: 'prettier --write $FILES', glob: '*.{js,ts,json,md}' }
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
To filter files by git status, add a `diff` filter (defaults to `'ACMR'`; see [here](https://git-scm.com/docs/git-status#_short_format)):
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
{ task: 'prettier --write $FILES', diff: 'A' }
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Defining `diff` or `glob` on a task that does not include the `$FILES` token has no effect:
|
|
147
|
+
|
|
148
|
+
```typescript
|
|
149
|
+
{ task: 'knip', diff: 'NO EFFECT', glob: 'NO EFFECT' }
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## Safety Features
|
|
153
|
+
|
|
154
|
+
Before running any potentially destructive scripts, `exec-staged` stores all outstanding changes, including untracked files, in a backup stash. If any task fails, or if `exec-staged` is interrupted by an end-process signal (such as via <kbd>Ctrl + C</kbd>), the repository's original state is restored using this stash. Avoid running any tasks that interact with git, especially those that make commits or modify the stash.
|
|
155
|
+
|
|
156
|
+
### Recovery
|
|
157
|
+
|
|
158
|
+
If `exec-staged` fails to exit safely, such as due to power loss or if its process is killed via `SIGKILL`, its backup stash should still be present.
|
|
159
|
+
|
|
160
|
+
To verify, run `git log` and look for a stash with the message `💾 exec-staged backup stash`. It should be the most recent stash. If it isn't, one of your tasks probably created a stash for some reason. This is very unlikely. Remove any such stashes before proceeding.
|
|
161
|
+
|
|
162
|
+
`exec-staged` also creates a short-lived temporary commit with the message `💾 exec-staged staged changes`. If it's present, it can be removed with `git reset --hard HEAD~1`.
|
|
163
|
+
|
|
164
|
+
The following commands should return your repository to its original state:
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
git add -A
|
|
168
|
+
git reset --hard HEAD
|
|
169
|
+
git stash pop --index
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
To prevent data loss, `exec-staged` will not run if a stash or commit from a previous run is present.
|
|
173
|
+
|
|
174
|
+
## See Also
|
|
175
|
+
|
|
176
|
+
- [`lint-staged`](https://github.com/lint-staged/lint-staged): the inspiration for `exec-staged`.
|
|
177
|
+
- [`knip`](https://github.com/webpro-nl/knip): a linter that analyzes interactions between files, which is outside of the designed scope of `lint-staged`.
|
package/dist/bin/cli.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import pkg from '../../package.json' with { type: 'json' };
|
|
3
|
+
import { loadConfig } from '../lib/config.js';
|
|
4
|
+
import { execStaged } from '../lib/exec_staged.js';
|
|
5
|
+
import { program } from 'commander';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
program.name(pkg.name).version(pkg.version).description(pkg.description);
|
|
8
|
+
program.option('--quiet', 'suppress output');
|
|
9
|
+
program.option('--cwd <cwd>', 'directory in which to run');
|
|
10
|
+
program.argument('[tasks...]');
|
|
11
|
+
program.parse(process.argv);
|
|
12
|
+
const options = program.opts();
|
|
13
|
+
const args = program.args;
|
|
14
|
+
const cwd = path.resolve(options.cwd ?? '');
|
|
15
|
+
const tasks = args.length ? args : await loadConfig(cwd);
|
|
16
|
+
const result = await execStaged(cwd, tasks, options);
|
|
17
|
+
if (!result) {
|
|
18
|
+
process.exitCode ||= 1;
|
|
19
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { ExecStagedConfig, ExecStagedUserConfig } from '../types.js';
|
|
2
|
+
export declare const loadConfig: (cwd: string) => Promise<ExecStagedUserConfig>;
|
|
3
|
+
export declare const resolveConfig: (userConfig: ExecStagedUserConfig) => ExecStagedConfig;
|
|
4
|
+
/** @internal */
|
|
5
|
+
export declare const validateUserConfig: (userConfig: ExecStagedUserConfig) => void;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import pkg from '../../package.json' with { type: 'json' };
|
|
2
|
+
import { DEFAULT_CONFIG_ENTRY } from './constants.js';
|
|
3
|
+
import { cosmiconfig } from 'cosmiconfig';
|
|
4
|
+
export const loadConfig = async (cwd) => {
|
|
5
|
+
const configResult = await cosmiconfig(pkg.name).search(cwd);
|
|
6
|
+
if (configResult) {
|
|
7
|
+
const { config, filepath } = configResult;
|
|
8
|
+
console.log(`Config loaded from ${filepath}`);
|
|
9
|
+
validateUserConfig(config);
|
|
10
|
+
return config;
|
|
11
|
+
}
|
|
12
|
+
else {
|
|
13
|
+
console.log('No config found');
|
|
14
|
+
return [];
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
export const resolveConfig = (userConfig) => {
|
|
18
|
+
return userConfig.map((entry) => ({
|
|
19
|
+
...DEFAULT_CONFIG_ENTRY,
|
|
20
|
+
...(typeof entry === 'string' ? { task: entry } : entry),
|
|
21
|
+
}));
|
|
22
|
+
};
|
|
23
|
+
/** @internal */
|
|
24
|
+
export const validateUserConfig = (userConfig) => {
|
|
25
|
+
if (!isValidUserConfig(userConfig)) {
|
|
26
|
+
throw new Error('invalid config');
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
const isValidUserConfig = (userConfig) => {
|
|
30
|
+
return (Array.isArray(userConfig) &&
|
|
31
|
+
userConfig.every((userConfigEntry) => isValidUserConfigEntry(userConfigEntry)));
|
|
32
|
+
};
|
|
33
|
+
const isValidUserConfigEntry = (userConfigEntry) => {
|
|
34
|
+
if (typeof userConfigEntry === 'string')
|
|
35
|
+
return true;
|
|
36
|
+
if (typeof userConfigEntry !== 'object')
|
|
37
|
+
return false;
|
|
38
|
+
if (typeof userConfigEntry.task !== 'string')
|
|
39
|
+
return false;
|
|
40
|
+
if (typeof userConfigEntry.diff !== 'string' &&
|
|
41
|
+
typeof userConfigEntry.diff !== 'undefined')
|
|
42
|
+
return false;
|
|
43
|
+
if (typeof userConfigEntry.glob !== 'string' &&
|
|
44
|
+
typeof userConfigEntry.glob !== 'undefined')
|
|
45
|
+
return false;
|
|
46
|
+
return true;
|
|
47
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ExecStagedConfigEntry } from '../types.js';
|
|
2
|
+
export declare const DEFAULT_CONFIG_ENTRY: Omit<ExecStagedConfigEntry, 'task'>;
|
|
3
|
+
export declare const MERGE_FILES: readonly ["MERGE_HEAD", "MERGE_MODE", "MERGE_MSG"];
|
|
4
|
+
export declare const BACKUP_STASH_MESSAGE: string;
|
|
5
|
+
export declare const STAGED_CHANGES_COMMIT_MESSAGE: string;
|
|
6
|
+
export declare const INTERPOLATION_IDENTIFIER = "$FILES";
|
|
7
|
+
export declare const stageLifecycleMessages: {
|
|
8
|
+
check: string;
|
|
9
|
+
prepare: string;
|
|
10
|
+
run: string;
|
|
11
|
+
merge: string;
|
|
12
|
+
revert: string;
|
|
13
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import pkg from '../../package.json' with { type: 'json' };
|
|
2
|
+
export const DEFAULT_CONFIG_ENTRY = {
|
|
3
|
+
glob: '*',
|
|
4
|
+
diff: 'ACMR',
|
|
5
|
+
};
|
|
6
|
+
export const MERGE_FILES = ['MERGE_HEAD', 'MERGE_MODE', 'MERGE_MSG'];
|
|
7
|
+
export const BACKUP_STASH_MESSAGE = `💾 ${pkg.name} backup stash`;
|
|
8
|
+
export const STAGED_CHANGES_COMMIT_MESSAGE = `💾 ${pkg.name} staged changes`;
|
|
9
|
+
export const INTERPOLATION_IDENTIFIER = '$FILES';
|
|
10
|
+
const PREFIX = '➡️ ';
|
|
11
|
+
export const stageLifecycleMessages = {
|
|
12
|
+
check: `${PREFIX}Checking environment...`,
|
|
13
|
+
prepare: `${PREFIX}Preparing repository...`,
|
|
14
|
+
run: `${PREFIX}Running tasks...`,
|
|
15
|
+
merge: `${PREFIX}Merging new changes with saved state...`,
|
|
16
|
+
revert: `${PREFIX}Reverting to saved state...`,
|
|
17
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { resolveConfig } from './config.js';
|
|
2
|
+
import { Stage } from './stage.js';
|
|
3
|
+
export const execStaged = async (cwd, tasks, options = {}) => {
|
|
4
|
+
const stage = new Stage(cwd, options);
|
|
5
|
+
try {
|
|
6
|
+
await stage.exec(resolveConfig(tasks));
|
|
7
|
+
return true;
|
|
8
|
+
}
|
|
9
|
+
catch (error) {
|
|
10
|
+
console.log(`🪲 Log saved to: ${stage.logger.outFile}`);
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import pkg from '../../package.json' with { type: 'json' };
|
|
2
|
+
import envPaths from 'env-paths';
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
export class Logger {
|
|
6
|
+
outFile;
|
|
7
|
+
quiet;
|
|
8
|
+
constructor(quiet = false) {
|
|
9
|
+
this.quiet = quiet;
|
|
10
|
+
this.outFile = path.resolve(envPaths(pkg.name).temp, `debug-${new Date().getTime().toString()}-${crypto.randomUUID()}.txt`);
|
|
11
|
+
fs.mkdirSync(path.dirname(this.outFile), { recursive: true });
|
|
12
|
+
this.debug(`${pkg.name} log: ${new Date().toLocaleString()}`);
|
|
13
|
+
}
|
|
14
|
+
log(...params) {
|
|
15
|
+
this.debug(...params);
|
|
16
|
+
if (!this.quiet) {
|
|
17
|
+
console.log(...params);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
debug(...params) {
|
|
21
|
+
fs.appendFileSync(this.outFile, params.join('\n') + '\n');
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Spawn a child process asynchronously using the `execa` package.
|
|
3
|
+
* This is used to run `exec-staged` tasks. The `execa` package is
|
|
4
|
+
* required because it provides the `preferLocal` option.
|
|
5
|
+
*
|
|
6
|
+
* Child processes are configured to be killed if the main process is stopped.
|
|
7
|
+
*
|
|
8
|
+
* @param cwd Directory where command should be executed.
|
|
9
|
+
* @param args Command string or array of command tokens.
|
|
10
|
+
* @throws ExecaError
|
|
11
|
+
* @returns Execa `Result` promise.
|
|
12
|
+
*/
|
|
13
|
+
export declare const spawn: (cwd: string, args: string[]) => Promise<import("execa").Result<{
|
|
14
|
+
cwd: string;
|
|
15
|
+
preferLocal: true;
|
|
16
|
+
stdout: ("pipe" | "inherit")[];
|
|
17
|
+
}>>;
|
|
18
|
+
/**
|
|
19
|
+
* Spawn a child process synchronously using the `execa` package.
|
|
20
|
+
* This is used to run `git` commands. A synchronous API is required because
|
|
21
|
+
* cleanup operations may be run via `on-process-exit`.
|
|
22
|
+
*
|
|
23
|
+
* @param cwd Directory where command should be executed.
|
|
24
|
+
* @param args Command string or array of command tokens.
|
|
25
|
+
* @throws ExecaSyncError
|
|
26
|
+
* @returns Execa `SyncResult`.
|
|
27
|
+
*/
|
|
28
|
+
export declare const spawnSync: (cwd: string, args: string[]) => import("execa").SyncResult<{
|
|
29
|
+
cwd: string;
|
|
30
|
+
}>;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { execa, execaSync } from 'execa';
|
|
2
|
+
import { registerExitHandler, deregisterExitHandler } from 'on-process-exit';
|
|
3
|
+
/**
|
|
4
|
+
* Spawn a child process asynchronously using the `execa` package.
|
|
5
|
+
* This is used to run `exec-staged` tasks. The `execa` package is
|
|
6
|
+
* required because it provides the `preferLocal` option.
|
|
7
|
+
*
|
|
8
|
+
* Child processes are configured to be killed if the main process is stopped.
|
|
9
|
+
*
|
|
10
|
+
* @param cwd Directory where command should be executed.
|
|
11
|
+
* @param args Command string or array of command tokens.
|
|
12
|
+
* @throws ExecaError
|
|
13
|
+
* @returns Execa `Result` promise.
|
|
14
|
+
*/
|
|
15
|
+
export const spawn = async (cwd, args) => {
|
|
16
|
+
const subprocess = execa({
|
|
17
|
+
cwd,
|
|
18
|
+
preferLocal: true,
|
|
19
|
+
stdout: ['pipe', 'inherit'],
|
|
20
|
+
})(args[0], args.slice(1));
|
|
21
|
+
const id = registerExitHandler(() => subprocess.kill());
|
|
22
|
+
subprocess.once('close', () => deregisterExitHandler(id));
|
|
23
|
+
return subprocess;
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Spawn a child process synchronously using the `execa` package.
|
|
27
|
+
* This is used to run `git` commands. A synchronous API is required because
|
|
28
|
+
* cleanup operations may be run via `on-process-exit`.
|
|
29
|
+
*
|
|
30
|
+
* @param cwd Directory where command should be executed.
|
|
31
|
+
* @param args Command string or array of command tokens.
|
|
32
|
+
* @throws ExecaSyncError
|
|
33
|
+
* @returns Execa `SyncResult`.
|
|
34
|
+
*/
|
|
35
|
+
export const spawnSync = (cwd, args) => {
|
|
36
|
+
return execaSync({ cwd })(args[0], args.slice(1));
|
|
37
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { ExecStagedConfig, StageOptions } from '../types.js';
|
|
2
|
+
import { Logger } from './logger.js';
|
|
3
|
+
export declare class Stage {
|
|
4
|
+
readonly logger: Logger;
|
|
5
|
+
protected readonly cwd: string;
|
|
6
|
+
protected stashed: boolean;
|
|
7
|
+
private readonly status;
|
|
8
|
+
private readonly mergeStatus;
|
|
9
|
+
private head?;
|
|
10
|
+
private patchPath?;
|
|
11
|
+
private gitDir?;
|
|
12
|
+
constructor(cwd: string, options?: StageOptions);
|
|
13
|
+
exec(tasks: ExecStagedConfig): Promise<void>;
|
|
14
|
+
protected check(): void;
|
|
15
|
+
protected prepare(): void;
|
|
16
|
+
protected run(tasks: ExecStagedConfig): Promise<void>;
|
|
17
|
+
protected merge(): void;
|
|
18
|
+
protected revert(): void;
|
|
19
|
+
protected git(args: string[]): string;
|
|
20
|
+
private backupMergeStatus;
|
|
21
|
+
private restoreMergeStatus;
|
|
22
|
+
private indexOfBackupStash;
|
|
23
|
+
private findBackupStash;
|
|
24
|
+
}
|