aioengine 0.1.2 → 0.1.4
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/README.md +110 -18
- package/package.json +3 -2
- package/src/index.js +253 -14
package/README.md
CHANGED
|
@@ -4,21 +4,49 @@ AI change control for developers using Claude Code, Cursor, Codex, Copilot, and
|
|
|
4
4
|
|
|
5
5
|
aioengine helps you review AI-generated code before you trust it. It scans your repo for missing guardrails, checks changed files for risky edits, and flags when AI may have wandered outside the requested task.
|
|
6
6
|
|
|
7
|
+
## Quick start
|
|
8
|
+
|
|
9
|
+
Run aioengine in any JavaScript or TypeScript project:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npx aioengine check
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Then set up AI coding guardrails:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npx aioengine init
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
After your AI coding tool makes changes, review them before committing:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npx aioengine scope "update landing page headline"
|
|
25
|
+
npx aioengine review
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
In CI or pull request workflows:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
npx aioengine ci --task "update landing page headline"
|
|
32
|
+
```
|
|
33
|
+
|
|
7
34
|
## Commands
|
|
8
35
|
|
|
9
36
|
```bash
|
|
10
|
-
aioengine init
|
|
11
|
-
aioengine check
|
|
12
|
-
aioengine scope "add init command"
|
|
13
|
-
aioengine review
|
|
14
|
-
aioengine
|
|
37
|
+
npx aioengine init
|
|
38
|
+
npx aioengine check
|
|
39
|
+
npx aioengine scope "add init command"
|
|
40
|
+
npx aioengine review
|
|
41
|
+
npx aioengine ci --task "add init command"
|
|
42
|
+
npx aioengine rules
|
|
15
43
|
```
|
|
16
44
|
|
|
17
45
|
## Why aioengine exists
|
|
18
46
|
|
|
19
47
|
AI coding tools can move fast, but review becomes the bottleneck.
|
|
20
48
|
|
|
21
|
-
A simple prompt can lead to unexpected changes in sensitive files like auth, billing, database migrations, environment config, deployment settings, or
|
|
49
|
+
A simple prompt can lead to unexpected changes in sensitive files like auth, billing, database migrations, environment config, deployment settings, dependency files, or CI workflows.
|
|
22
50
|
|
|
23
51
|
aioengine helps answer:
|
|
24
52
|
|
|
@@ -26,12 +54,14 @@ aioengine helps answer:
|
|
|
26
54
|
- Did AI change files outside the task?
|
|
27
55
|
- Did AI add or modify dependencies?
|
|
28
56
|
- Does this repo have AI coding rules?
|
|
29
|
-
- What should I review before committing?
|
|
57
|
+
- What should I review before committing or merging?
|
|
30
58
|
|
|
31
59
|
## `aioengine init`
|
|
32
60
|
|
|
33
61
|
Sets up aioengine in your repo.
|
|
34
62
|
|
|
63
|
+
Creates missing files only. aioengine will not overwrite an existing `CLAUDE.md`.
|
|
64
|
+
|
|
35
65
|
Creates:
|
|
36
66
|
|
|
37
67
|
```txt
|
|
@@ -40,10 +70,16 @@ CLAUDE.md
|
|
|
40
70
|
.cursor/rules/aioengine.mdc
|
|
41
71
|
```
|
|
42
72
|
|
|
73
|
+
If `CLAUDE.md` already exists, aioengine leaves it untouched and saves suggested rules to:
|
|
74
|
+
|
|
75
|
+
```txt
|
|
76
|
+
.aioengine/suggested-claude-rules.md
|
|
77
|
+
```
|
|
78
|
+
|
|
43
79
|
Run:
|
|
44
80
|
|
|
45
81
|
```bash
|
|
46
|
-
aioengine init
|
|
82
|
+
npx aioengine init
|
|
47
83
|
```
|
|
48
84
|
|
|
49
85
|
## `aioengine check`
|
|
@@ -66,7 +102,7 @@ Checks for:
|
|
|
66
102
|
Run:
|
|
67
103
|
|
|
68
104
|
```bash
|
|
69
|
-
aioengine check
|
|
105
|
+
npx aioengine check
|
|
70
106
|
```
|
|
71
107
|
|
|
72
108
|
## `aioengine scope`
|
|
@@ -76,10 +112,10 @@ Checks whether changed files match the task you gave your AI coding tool.
|
|
|
76
112
|
Example:
|
|
77
113
|
|
|
78
114
|
```bash
|
|
79
|
-
aioengine scope "update landing page headline"
|
|
115
|
+
npx aioengine scope "update landing page headline"
|
|
80
116
|
```
|
|
81
117
|
|
|
82
|
-
If the task sounds like a UI change but AI modified billing, database, env, dependency, or deployment files, aioengine will flag possible scope drift.
|
|
118
|
+
If the task sounds like a UI change but AI modified billing, database, env, dependency, CLI, or deployment files, aioengine will flag possible scope drift.
|
|
83
119
|
|
|
84
120
|
## `aioengine review`
|
|
85
121
|
|
|
@@ -88,7 +124,7 @@ Reviews current uncommitted changes for risky files.
|
|
|
88
124
|
Run:
|
|
89
125
|
|
|
90
126
|
```bash
|
|
91
|
-
aioengine review
|
|
127
|
+
npx aioengine review
|
|
92
128
|
```
|
|
93
129
|
|
|
94
130
|
aioengine will flag changes to files that often deserve extra review, such as:
|
|
@@ -101,6 +137,52 @@ aioengine will flag changes to files that often deserve extra review, such as:
|
|
|
101
137
|
- dependency files
|
|
102
138
|
- GitHub workflow files
|
|
103
139
|
|
|
140
|
+
## `aioengine ci`
|
|
141
|
+
|
|
142
|
+
Runs aioengine checks in CI or pull request workflows.
|
|
143
|
+
|
|
144
|
+
Run:
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
npx aioengine ci --task "update landing page headline"
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
In GitHub Actions, aioengine will try to detect changed files from the pull request context. If a task is available from the PR title, event payload, or `AIOENGINE_TASK`, it can flag possible scope drift.
|
|
151
|
+
|
|
152
|
+
By default:
|
|
153
|
+
|
|
154
|
+
- possible scope drift fails the CI check
|
|
155
|
+
- risky files warn but do not fail the CI check
|
|
156
|
+
|
|
157
|
+
Example GitHub Actions step:
|
|
158
|
+
|
|
159
|
+
```yaml
|
|
160
|
+
- name: Run aioengine
|
|
161
|
+
run: npx aioengine ci
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
For more reliable PR diffs, use checkout with full history:
|
|
165
|
+
|
|
166
|
+
```yaml
|
|
167
|
+
- uses: actions/checkout@v4
|
|
168
|
+
with:
|
|
169
|
+
fetch-depth: 0
|
|
170
|
+
|
|
171
|
+
- name: Run aioengine
|
|
172
|
+
run: npx aioengine ci
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
You can also pass a task manually:
|
|
176
|
+
|
|
177
|
+
```yaml
|
|
178
|
+
- uses: actions/checkout@v4
|
|
179
|
+
with:
|
|
180
|
+
fetch-depth: 0
|
|
181
|
+
|
|
182
|
+
- name: Run aioengine
|
|
183
|
+
run: npx aioengine ci --task "update landing page headline"
|
|
184
|
+
```
|
|
185
|
+
|
|
104
186
|
## `aioengine rules`
|
|
105
187
|
|
|
106
188
|
Generates starter AI coding rules for Claude Code and Cursor.
|
|
@@ -108,26 +190,36 @@ Generates starter AI coding rules for Claude Code and Cursor.
|
|
|
108
190
|
Run:
|
|
109
191
|
|
|
110
192
|
```bash
|
|
111
|
-
aioengine rules
|
|
193
|
+
npx aioengine rules
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
Creates missing files only. aioengine will not overwrite an existing `CLAUDE.md`.
|
|
197
|
+
|
|
198
|
+
If `CLAUDE.md` already exists, suggested Claude rules are saved to:
|
|
199
|
+
|
|
200
|
+
```txt
|
|
201
|
+
.aioengine/suggested-claude-rules.md
|
|
112
202
|
```
|
|
113
203
|
|
|
114
|
-
This creates or skips:
|
|
204
|
+
This command creates or skips:
|
|
115
205
|
|
|
116
206
|
```txt
|
|
117
207
|
CLAUDE.md
|
|
118
208
|
.cursor/rules/aioengine.mdc
|
|
209
|
+
.aioengine/suggested-claude-rules.md
|
|
119
210
|
```
|
|
120
211
|
|
|
121
212
|
## Example workflow
|
|
122
213
|
|
|
123
214
|
```bash
|
|
124
|
-
aioengine init
|
|
125
|
-
aioengine check
|
|
215
|
+
npx aioengine init
|
|
216
|
+
npx aioengine check
|
|
126
217
|
|
|
127
218
|
# Ask Claude, Cursor, Codex, or another AI coding tool to make a change.
|
|
128
219
|
|
|
129
|
-
aioengine scope "update landing page headline"
|
|
130
|
-
aioengine review
|
|
220
|
+
npx aioengine scope "update landing page headline"
|
|
221
|
+
npx aioengine review
|
|
222
|
+
npx aioengine ci --task "update landing page headline"
|
|
131
223
|
```
|
|
132
224
|
|
|
133
225
|
## Current status
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aioengine",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "AI change control for developers using AI coding tools.",
|
|
5
5
|
"main": "./src/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
"review": "node ./src/index.js review",
|
|
10
10
|
"rules": "node ./src/index.js rules",
|
|
11
11
|
"scope": "node ./src/index.js scope",
|
|
12
|
-
"init": "node ./src/index.js init"
|
|
12
|
+
"init": "node ./src/index.js init",
|
|
13
|
+
"ci": "node ./src/index.js ci"
|
|
13
14
|
},
|
|
14
15
|
"keywords": [
|
|
15
16
|
"ai",
|
package/src/index.js
CHANGED
|
@@ -35,6 +35,12 @@ program
|
|
|
35
35
|
runReview();
|
|
36
36
|
});
|
|
37
37
|
|
|
38
|
+
program
|
|
39
|
+
.command("ci")
|
|
40
|
+
.description("Run aioengine review checks in CI and pull request workflows.")
|
|
41
|
+
.option("--task <task>", "Task description to compare changed files against")
|
|
42
|
+
.action((options) => runCi(options));
|
|
43
|
+
|
|
38
44
|
program
|
|
39
45
|
.command("scope")
|
|
40
46
|
.description("Check whether changed files match the requested task.")
|
|
@@ -62,7 +68,6 @@ function runInit() {
|
|
|
62
68
|
|
|
63
69
|
const aioengineDir = ".aioengine";
|
|
64
70
|
const configPath = path.join(aioengineDir, "config.json");
|
|
65
|
-
const claudePath = "CLAUDE.md";
|
|
66
71
|
const cursorDir = ".cursor/rules";
|
|
67
72
|
const cursorPath = path.join(cursorDir, "aioengine.mdc");
|
|
68
73
|
|
|
@@ -92,12 +97,7 @@ function runInit() {
|
|
|
92
97
|
skipped.push(configPath);
|
|
93
98
|
}
|
|
94
99
|
|
|
95
|
-
|
|
96
|
-
fs.writeFileSync(path.join(root, claudePath), getClaudeRules());
|
|
97
|
-
created.push(claudePath);
|
|
98
|
-
} else {
|
|
99
|
-
skipped.push(claudePath);
|
|
100
|
-
}
|
|
100
|
+
createClaudeRulesSafely(root, created, skipped);
|
|
101
101
|
|
|
102
102
|
if (!exists(cursorDir, root)) {
|
|
103
103
|
fs.mkdirSync(path.join(root, cursorDir), { recursive: true });
|
|
@@ -113,6 +113,14 @@ function runInit() {
|
|
|
113
113
|
printSection("Created", created, "green");
|
|
114
114
|
printSection("Skipped", skipped, "yellow");
|
|
115
115
|
|
|
116
|
+
if (skipped.includes("CLAUDE.md already exists")) {
|
|
117
|
+
console.log(
|
|
118
|
+
`\n${pc.dim(
|
|
119
|
+
"aioengine did not modify your existing CLAUDE.md. Suggested Claude rules were saved to .aioengine/suggested-claude-rules.md if that file did not already exist."
|
|
120
|
+
)}`
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
|
|
116
124
|
console.log(pc.bold("Next steps:"));
|
|
117
125
|
console.log(` 1. Run ${pc.cyan("aioengine check")}`);
|
|
118
126
|
console.log(` 2. Make or review AI-generated changes`);
|
|
@@ -273,6 +281,112 @@ function runReview() {
|
|
|
273
281
|
}
|
|
274
282
|
}
|
|
275
283
|
|
|
284
|
+
function runCi(options = {}) {
|
|
285
|
+
printHeader("aioengine CI");
|
|
286
|
+
|
|
287
|
+
const root = getProjectRoot();
|
|
288
|
+
|
|
289
|
+
if (!isInsideGitRepo()) {
|
|
290
|
+
console.log(
|
|
291
|
+
`${pc.red("✗")} No Git repo detected. aioengine ci must run inside a Git repository.`
|
|
292
|
+
);
|
|
293
|
+
process.exitCode = 1;
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const task = options.task || getCiTask();
|
|
298
|
+
const files = getCiChangedFiles(root);
|
|
299
|
+
|
|
300
|
+
console.log(`${pc.dim("Project:")} ${root}`);
|
|
301
|
+
|
|
302
|
+
if (isGitHubActions()) {
|
|
303
|
+
console.log(`${pc.dim("Environment:")} GitHub Actions`);
|
|
304
|
+
} else {
|
|
305
|
+
console.log(`${pc.dim("Environment:")} Local / unknown CI`);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
if (task) {
|
|
309
|
+
console.log(`${pc.dim("Task:")} ${task}`);
|
|
310
|
+
} else {
|
|
311
|
+
console.log(
|
|
312
|
+
`${pc.yellow("!")} No task detected. Scope checks will be less precise.`
|
|
313
|
+
);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
if (files.length === 0) {
|
|
317
|
+
console.log(pc.green("\nNo changed files found."));
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
const profile = task ? inferTaskProfile(task) : null;
|
|
322
|
+
const riskyFiles = files.filter(isRiskyFile);
|
|
323
|
+
const outOfScopeFiles = profile
|
|
324
|
+
? files.filter((file) => isProbablyOutOfScope(file, profile))
|
|
325
|
+
: [];
|
|
326
|
+
|
|
327
|
+
if (profile) {
|
|
328
|
+
console.log(`${pc.dim("Detected task type:")} ${profile.label}`);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
console.log(`${pc.dim("Changed files:")} ${files.length}\n`);
|
|
332
|
+
|
|
333
|
+
for (const file of files) {
|
|
334
|
+
const risky = riskyFiles.includes(file);
|
|
335
|
+
const outOfScope = outOfScopeFiles.includes(file);
|
|
336
|
+
|
|
337
|
+
if (outOfScope) {
|
|
338
|
+
console.log(` ${pc.red("✗")} ${file} ${pc.red("— possible scope drift")}`);
|
|
339
|
+
} else if (risky) {
|
|
340
|
+
console.log(` ${pc.yellow("!")} ${file} ${pc.yellow("— review carefully")}`);
|
|
341
|
+
} else {
|
|
342
|
+
console.log(` ${pc.green("✓")} ${file}`);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
const hasScopeDrift = outOfScopeFiles.length > 0;
|
|
347
|
+
const hasRiskyFiles = riskyFiles.length > 0;
|
|
348
|
+
|
|
349
|
+
if (hasScopeDrift) {
|
|
350
|
+
console.log(pc.yellow("\nCI review recommended"));
|
|
351
|
+
|
|
352
|
+
printSection(
|
|
353
|
+
"Possible scope drift",
|
|
354
|
+
outOfScopeFiles.map(
|
|
355
|
+
(file) => `Possible out-of-scope file changed: ${file}`
|
|
356
|
+
),
|
|
357
|
+
"warning"
|
|
358
|
+
);
|
|
359
|
+
|
|
360
|
+
if (hasRiskyFiles) {
|
|
361
|
+
printSection(
|
|
362
|
+
"Risky files",
|
|
363
|
+
riskyFiles.map((file) => `High-risk file changed: ${file}`),
|
|
364
|
+
"warning"
|
|
365
|
+
);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
console.log(
|
|
369
|
+
pc.dim(
|
|
370
|
+
"\nRecommendation: Review these changes before merging. aioengine is failing this CI check because possible scope drift was detected."
|
|
371
|
+
)
|
|
372
|
+
);
|
|
373
|
+
|
|
374
|
+
process.exitCode = 1;
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
if (hasRiskyFiles) {
|
|
379
|
+
console.log(
|
|
380
|
+
pc.yellow(
|
|
381
|
+
"\nRisky files were changed. aioengine is allowing this check to pass, but these files should receive extra human review."
|
|
382
|
+
)
|
|
383
|
+
);
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
console.log(pc.green("\nNo obvious AI change-control issues detected."));
|
|
388
|
+
}
|
|
389
|
+
|
|
276
390
|
function runScope(task) {
|
|
277
391
|
printHeader("aioengine Scope");
|
|
278
392
|
|
|
@@ -382,19 +496,13 @@ function runRules() {
|
|
|
382
496
|
|
|
383
497
|
const root = getProjectRoot();
|
|
384
498
|
|
|
385
|
-
const claudePath = "CLAUDE.md";
|
|
386
499
|
const cursorDir = ".cursor/rules";
|
|
387
500
|
const cursorPath = path.join(cursorDir, "aioengine.mdc");
|
|
388
501
|
|
|
389
502
|
const created = [];
|
|
390
503
|
const skipped = [];
|
|
391
504
|
|
|
392
|
-
|
|
393
|
-
fs.writeFileSync(path.join(root, claudePath), getClaudeRules());
|
|
394
|
-
created.push(claudePath);
|
|
395
|
-
} else {
|
|
396
|
-
skipped.push(claudePath);
|
|
397
|
-
}
|
|
505
|
+
createClaudeRulesSafely(root, created, skipped);
|
|
398
506
|
|
|
399
507
|
if (!exists(cursorDir, root)) {
|
|
400
508
|
fs.mkdirSync(path.join(root, cursorDir), { recursive: true });
|
|
@@ -410,6 +518,14 @@ function runRules() {
|
|
|
410
518
|
printSection("Created", created, "green");
|
|
411
519
|
printSection("Skipped", skipped, "yellow");
|
|
412
520
|
|
|
521
|
+
if (skipped.includes("CLAUDE.md already exists")) {
|
|
522
|
+
console.log(
|
|
523
|
+
`\n${pc.dim(
|
|
524
|
+
"aioengine did not modify your existing CLAUDE.md. Suggested Claude rules were saved to .aioengine/suggested-claude-rules.md if that file did not already exist."
|
|
525
|
+
)}`
|
|
526
|
+
);
|
|
527
|
+
}
|
|
528
|
+
|
|
413
529
|
console.log(pc.bold("Next step:"));
|
|
414
530
|
console.log(` Run ${pc.cyan("aioengine check")} again.`);
|
|
415
531
|
}
|
|
@@ -839,6 +955,37 @@ function getDefaultConfig() {
|
|
|
839
955
|
};
|
|
840
956
|
}
|
|
841
957
|
|
|
958
|
+
function createClaudeRulesSafely(root, created, skipped) {
|
|
959
|
+
const claudePath = path.join(root, "CLAUDE.md");
|
|
960
|
+
const aioengineDir = path.join(root, ".aioengine");
|
|
961
|
+
const suggestedPath = path.join(
|
|
962
|
+
root,
|
|
963
|
+
".aioengine",
|
|
964
|
+
"suggested-claude-rules.md"
|
|
965
|
+
);
|
|
966
|
+
|
|
967
|
+
if (!fs.existsSync(aioengineDir)) {
|
|
968
|
+
fs.mkdirSync(aioengineDir, { recursive: true });
|
|
969
|
+
created.push(".aioengine");
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
if (!fs.existsSync(claudePath)) {
|
|
973
|
+
fs.writeFileSync(claudePath, getClaudeRules(), "utf8");
|
|
974
|
+
created.push("CLAUDE.md");
|
|
975
|
+
return;
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
skipped.push("CLAUDE.md already exists");
|
|
979
|
+
|
|
980
|
+
if (!fs.existsSync(suggestedPath)) {
|
|
981
|
+
fs.writeFileSync(suggestedPath, getClaudeRules(), "utf8");
|
|
982
|
+
created.push(".aioengine/suggested-claude-rules.md");
|
|
983
|
+
return;
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
skipped.push(".aioengine/suggested-claude-rules.md already exists");
|
|
987
|
+
}
|
|
988
|
+
|
|
842
989
|
function getClaudeRules() {
|
|
843
990
|
return `# AI Coding Rules
|
|
844
991
|
|
|
@@ -897,6 +1044,98 @@ For UI-only tasks, avoid backend, API, database, and config changes.
|
|
|
897
1044
|
`;
|
|
898
1045
|
}
|
|
899
1046
|
|
|
1047
|
+
function isGitHubActions() {
|
|
1048
|
+
return process.env.GITHUB_ACTIONS === "true";
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
function getCiTask() {
|
|
1052
|
+
const explicitTask = process.env.AIOENGINE_TASK;
|
|
1053
|
+
|
|
1054
|
+
if (explicitTask) {
|
|
1055
|
+
return explicitTask;
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
const eventPath = process.env.GITHUB_EVENT_PATH;
|
|
1059
|
+
|
|
1060
|
+
if (!eventPath || !fs.existsSync(eventPath)) {
|
|
1061
|
+
return "";
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
try {
|
|
1065
|
+
const event = JSON.parse(fs.readFileSync(eventPath, "utf8"));
|
|
1066
|
+
|
|
1067
|
+
return (
|
|
1068
|
+
event.pull_request?.title ||
|
|
1069
|
+
event.issue?.title ||
|
|
1070
|
+
event.head_commit?.message ||
|
|
1071
|
+
""
|
|
1072
|
+
);
|
|
1073
|
+
} catch {
|
|
1074
|
+
return "";
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
function getCiChangedFiles(root) {
|
|
1079
|
+
if (isGitHubActions()) {
|
|
1080
|
+
const files = getGitHubActionsChangedFiles(root);
|
|
1081
|
+
|
|
1082
|
+
if (files.length > 0) {
|
|
1083
|
+
return files;
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
return getChangedFiles(root);
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
function getGitHubActionsChangedFiles(root) {
|
|
1091
|
+
const baseRef = process.env.GITHUB_BASE_REF;
|
|
1092
|
+
const beforeSha = process.env.GITHUB_EVENT_BEFORE;
|
|
1093
|
+
const currentSha = process.env.GITHUB_SHA || "HEAD";
|
|
1094
|
+
|
|
1095
|
+
try {
|
|
1096
|
+
if (baseRef) {
|
|
1097
|
+
try {
|
|
1098
|
+
execSync(`git fetch origin ${baseRef} --depth=1`, {
|
|
1099
|
+
cwd: root,
|
|
1100
|
+
stdio: "ignore",
|
|
1101
|
+
});
|
|
1102
|
+
} catch {
|
|
1103
|
+
// The workflow may already have enough history.
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
return uniqueFiles(
|
|
1107
|
+
execSync(`git diff --name-only origin/${baseRef}...HEAD`, {
|
|
1108
|
+
cwd: root,
|
|
1109
|
+
encoding: "utf8",
|
|
1110
|
+
})
|
|
1111
|
+
.split("\n")
|
|
1112
|
+
.map((file) => file.trim())
|
|
1113
|
+
.filter(Boolean)
|
|
1114
|
+
);
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
if (beforeSha && currentSha) {
|
|
1118
|
+
return uniqueFiles(
|
|
1119
|
+
execSync(`git diff --name-only ${beforeSha} ${currentSha}`, {
|
|
1120
|
+
cwd: root,
|
|
1121
|
+
encoding: "utf8",
|
|
1122
|
+
})
|
|
1123
|
+
.split("\n")
|
|
1124
|
+
.map((file) => file.trim())
|
|
1125
|
+
.filter(Boolean)
|
|
1126
|
+
);
|
|
1127
|
+
}
|
|
1128
|
+
} catch {
|
|
1129
|
+
return [];
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
return [];
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
function uniqueFiles(files) {
|
|
1136
|
+
return [...new Set(files)].sort();
|
|
1137
|
+
}
|
|
1138
|
+
|
|
900
1139
|
function getCliVersion() {
|
|
901
1140
|
try {
|
|
902
1141
|
const currentFile = fileURLToPath(import.meta.url);
|