flecto 4.3.0 → 4.4.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 +18 -1
- package/README.md +3 -3
- package/package.json +1 -1
- package/src/differ.js +35 -12
- package/src/positions.js +6 -3
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,22 @@ The format is based on [Keep a Changelog], and this project adheres to
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [4.4.0] - 2026-10-05
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- **Flag lists diff by flag name, so removing one arg is one removal**
|
|
15
|
+
([#238]). A real pull request that removed the first of eight container
|
|
16
|
+
`args` read as 7 `changed` and 1 `removed`, each pairing two unrelated flags.
|
|
17
|
+
It now reads as `removed args["--keytoolPath"]`. A list counts when every item
|
|
18
|
+
is `-x`, `--name` or `--name=value` with unique names; a repeated `--set`,
|
|
19
|
+
`["--port", "8080"]`, and `["-c", "echo hi"]` keep the position diff, and
|
|
20
|
+
`--no-array-id` turns it off. Paths change from `args[0]` to
|
|
21
|
+
`args["--name"]` for these lists. No built-in pack rule matches an `args`
|
|
22
|
+
index.
|
|
23
|
+
|
|
24
|
+
[#238]: https://github.com/myselfsiddharth/Flecto/issues/238
|
|
25
|
+
|
|
10
26
|
## [4.3.0] - 2026-10-05
|
|
11
27
|
|
|
12
28
|
### Fixed
|
|
@@ -1438,7 +1454,8 @@ fixed — those runs were never actually gated — but the failure is new.
|
|
|
1438
1454
|
- Misconfigured policy packs/plugins cause `watch` to exit non-zero instead of
|
|
1439
1455
|
continuing with no policies.
|
|
1440
1456
|
|
|
1441
|
-
[Unreleased]: https://github.com/myselfsiddharth/Flecto/compare/v4.
|
|
1457
|
+
[Unreleased]: https://github.com/myselfsiddharth/Flecto/compare/v4.4.0...HEAD
|
|
1458
|
+
[4.4.0]: https://github.com/myselfsiddharth/Flecto/compare/v4.3.0...v4.4.0
|
|
1442
1459
|
[4.3.0]: https://github.com/myselfsiddharth/Flecto/compare/v4.2.0...v4.3.0
|
|
1443
1460
|
[4.2.0]: https://github.com/myselfsiddharth/Flecto/compare/v4.1.1...v4.2.0
|
|
1444
1461
|
[4.1.1]: https://github.com/myselfsiddharth/Flecto/compare/v4.1.0...v4.1.1
|
package/README.md
CHANGED
|
@@ -77,7 +77,7 @@ aws_db_instance.main.#action
|
|
|
77
77
|
|
|
78
78
|
Flecto PR Risk is on the
|
|
79
79
|
[GitHub Marketplace](https://github.com/marketplace/actions/flecto-pr-risk), so
|
|
80
|
-
`myselfsiddharth/Flecto@v4.
|
|
80
|
+
`myselfsiddharth/Flecto@v4.3.0` is the whole reference.
|
|
81
81
|
|
|
82
82
|
**Terraform** — point it at the plan JSON:
|
|
83
83
|
|
|
@@ -91,7 +91,7 @@ steps:
|
|
|
91
91
|
- run: |
|
|
92
92
|
terraform plan -out=tf.plan
|
|
93
93
|
terraform show -json tf.plan > plan.json
|
|
94
|
-
- uses: myselfsiddharth/Flecto@v4.
|
|
94
|
+
- uses: myselfsiddharth/Flecto@v4.3.0
|
|
95
95
|
with:
|
|
96
96
|
terraform-plan: plan.json
|
|
97
97
|
fail-on: error
|
|
@@ -109,7 +109,7 @@ steps:
|
|
|
109
109
|
with:
|
|
110
110
|
fetch-depth: 0
|
|
111
111
|
- run: helm template ./chart > rendered.yaml
|
|
112
|
-
- uses: myselfsiddharth/Flecto@v4.
|
|
112
|
+
- uses: myselfsiddharth/Flecto@v4.3.0
|
|
113
113
|
with:
|
|
114
114
|
targets: rendered.yaml
|
|
115
115
|
policies: kubernetes
|
package/package.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"access": "public",
|
|
5
5
|
"provenance": true
|
|
6
6
|
},
|
|
7
|
-
"version": "4.
|
|
7
|
+
"version": "4.4.0",
|
|
8
8
|
"description": "Reads your Terraform plan and Kubernetes changes and posts a plain-English risk summary on every pull request, blocking the dangerous ones",
|
|
9
9
|
"license": "MIT",
|
|
10
10
|
"keywords": [
|
package/src/differ.js
CHANGED
|
@@ -364,24 +364,43 @@ function identityMap(items, idKey) {
|
|
|
364
364
|
// `KEY=VALUE`, or a bare `KEY` (Compose's pass-through from the host).
|
|
365
365
|
export const ASSIGNMENT_RE = /^([A-Za-z_][A-Za-z0-9_.-]*)(?:=|$)/;
|
|
366
366
|
|
|
367
|
+
// `-x`, `--name`, or `--name=value`: a command-line flag, keyed by its name.
|
|
368
|
+
export const FLAG_RE = /^(-{1,2}[A-Za-z0-9][A-Za-z0-9_.-]*)(?:=|$)/;
|
|
369
|
+
|
|
367
370
|
/**
|
|
368
|
-
*
|
|
371
|
+
* String-list shapes that are really maps, and how each is keyed.
|
|
369
372
|
*
|
|
370
373
|
* Compose writes `environment`, `labels` and `build.args` either as a map or as
|
|
371
|
-
* a list of
|
|
374
|
+
* a list of `KEY=VALUE`, and means the same thing by both. Diffed by position,
|
|
372
375
|
* one inserted variable turned every later line into a `changed` pairing two
|
|
373
|
-
* unrelated variables (#226)
|
|
374
|
-
*
|
|
376
|
+
* unrelated variables (#226); one removed container arg did the same to every
|
|
377
|
+
* later flag (#238). A list counts only when every item has the shape and keys
|
|
378
|
+
* are unique, so a repeated `--set` falls back to position.
|
|
379
|
+
*
|
|
380
|
+
* Assignments also need at least one `=`, so a plain word list such as
|
|
381
|
+
* `command: [python, app.py]` keeps its order-sensitive diff. A flag list does
|
|
382
|
+
* not: `[-v, --debug]` is already unambiguous, and `["--port", "8080"]` or
|
|
383
|
+
* `["-c", "echo hi"]` fail the shape and keep position.
|
|
384
|
+
* @type {Array<{ re: RegExp, needsValue: boolean }>}
|
|
385
|
+
*/
|
|
386
|
+
const KEYED_STRING_SHAPES = [
|
|
387
|
+
{ re: ASSIGNMENT_RE, needsValue: true },
|
|
388
|
+
{ re: FLAG_RE, needsValue: false },
|
|
389
|
+
];
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* Key a list of strings by the given shape, or return null when it is not one.
|
|
375
393
|
* @param {unknown[]} items
|
|
394
|
+
* @param {RegExp} re
|
|
376
395
|
* @returns {Map<string, { value: unknown, index: number }> | null}
|
|
377
396
|
*/
|
|
378
|
-
function
|
|
397
|
+
function keyedStringMap(items, re) {
|
|
379
398
|
/** @type {Map<string, { value: unknown, index: number }>} */
|
|
380
399
|
const map = new Map();
|
|
381
400
|
for (let i = 0; i < items.length; i++) {
|
|
382
401
|
const item = items[i];
|
|
383
402
|
if (typeof item !== 'string') return null;
|
|
384
|
-
const match =
|
|
403
|
+
const match = re.exec(item);
|
|
385
404
|
if (!match || map.has(match[1])) return null;
|
|
386
405
|
map.set(match[1], { value: item, index: i });
|
|
387
406
|
}
|
|
@@ -389,18 +408,22 @@ function assignmentMap(items) {
|
|
|
389
408
|
}
|
|
390
409
|
|
|
391
410
|
/**
|
|
392
|
-
* Both sides
|
|
411
|
+
* Both sides have the same keyed shape (see KEYED_STRING_SHAPES).
|
|
393
412
|
* @param {unknown[]} before
|
|
394
413
|
* @param {unknown[]} after
|
|
395
414
|
* @returns {{ beforeMap: Map<string, { value: unknown, index: number }>, afterMap: Map<string, { value: unknown, index: number }> } | null}
|
|
396
415
|
*/
|
|
397
416
|
function assignmentMaps(before, after) {
|
|
398
|
-
const beforeMap = assignmentMap(before);
|
|
399
|
-
const afterMap = beforeMap && assignmentMap(after);
|
|
400
|
-
if (!beforeMap || !afterMap) return null;
|
|
401
417
|
const assigns = (item) => typeof item === 'string' && item.includes('=');
|
|
402
|
-
|
|
403
|
-
|
|
418
|
+
for (const { re, needsValue } of KEYED_STRING_SHAPES) {
|
|
419
|
+
const beforeMap = keyedStringMap(before, re);
|
|
420
|
+
const afterMap = beforeMap && keyedStringMap(after, re);
|
|
421
|
+
if (!beforeMap || !afterMap) continue;
|
|
422
|
+
if (beforeMap.size === 0 && afterMap.size === 0) continue;
|
|
423
|
+
if (needsValue && !before.some(assigns) && !after.some(assigns)) continue;
|
|
424
|
+
return { beforeMap, afterMap };
|
|
425
|
+
}
|
|
426
|
+
return null;
|
|
404
427
|
}
|
|
405
428
|
|
|
406
429
|
/**
|
package/src/positions.js
CHANGED
|
@@ -3,7 +3,7 @@ import yaml from 'js-yaml';
|
|
|
3
3
|
|
|
4
4
|
import { isArmoredAgeFile } from './encrypted.js';
|
|
5
5
|
import { isEnvFilename, isIniFilename, stripJsonComments, yamlDocumentKeys } from './parser.js';
|
|
6
|
-
import { ASSIGNMENT_RE } from './differ.js';
|
|
6
|
+
import { ASSIGNMENT_RE, FLAG_RE } from './differ.js';
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Where a config path lives in the source text (#142).
|
|
@@ -248,10 +248,13 @@ function itemByIdentity(items, quoted, arrayIdKey) {
|
|
|
248
248
|
if (String(id) === target) matches.add(item);
|
|
249
249
|
}
|
|
250
250
|
}
|
|
251
|
-
// A `KEY=VALUE` list (Compose `environment`, `labels`) is keyed by KEY
|
|
251
|
+
// A `KEY=VALUE` list (Compose `environment`, `labels`) is keyed by KEY, and
|
|
252
|
+
// a flag list (container `args`) by flag name.
|
|
252
253
|
if (matches.size === 0 && !arrayIdKey) {
|
|
253
254
|
for (const item of items) {
|
|
254
|
-
if (typeof item.value
|
|
255
|
+
if (typeof item.value !== 'string') continue;
|
|
256
|
+
const key = ASSIGNMENT_RE.exec(item.value)?.[1] ?? FLAG_RE.exec(item.value)?.[1];
|
|
257
|
+
if (key === target) matches.add(item);
|
|
255
258
|
}
|
|
256
259
|
}
|
|
257
260
|
return matches.size === 1 ? [...matches][0] : null;
|