@codyswann/lisa 3.64.1 → 3.64.2
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/cdk/package-lisa/package.lisa.json +12 -4
- package/dist/core/lisa.d.ts.map +1 -1
- package/dist/core/lisa.js +8 -1
- package/dist/core/lisa.js.map +1 -1
- package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
- package/dist/core/upstream-evidence-manifest.js +15 -7
- package/dist/core/upstream-evidence-manifest.js.map +1 -1
- package/dist/strategies/package-lisa-types.d.ts +32 -1
- package/dist/strategies/package-lisa-types.d.ts.map +1 -1
- package/dist/strategies/package-lisa.d.ts +17 -0
- package/dist/strategies/package-lisa.d.ts.map +1 -1
- package/dist/strategies/package-lisa.js +342 -13
- package/dist/strategies/package-lisa.js.map +1 -1
- package/expo/package-lisa/package.lisa.json +12 -2
- package/harper-fabric/package-lisa/package.lisa.json +38 -8
- package/nestjs/package-lisa/package.lisa.json +11 -3
- package/package.json +1 -1
- package/phaser/package-lisa/package.lisa.json +38 -8
- package/plugins/lisa/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-agy/plugin.json +1 -1
- package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-agy/plugin.json +1 -1
- package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-agy/plugin.json +1 -1
- package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-agy/plugin.json +1 -1
- package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-agy/plugin.json +1 -1
- package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-agy/plugin.json +1 -1
- package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-agy/plugin.json +1 -1
- package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-agy/plugin.json +1 -1
- package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-agy/plugin.json +1 -1
- package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
- package/typescript/package-lisa/package.lisa.json +38 -9
- package/ui/index.html +5 -2
|
@@ -42,6 +42,29 @@ export interface MergeSection {
|
|
|
42
42
|
export interface RemoveSection {
|
|
43
43
|
[key: string]: string[];
|
|
44
44
|
}
|
|
45
|
+
/**
|
|
46
|
+
* Adopt behavior: values Lisa itself previously wrote into a key it has since
|
|
47
|
+
* handed back to the host.
|
|
48
|
+
*
|
|
49
|
+
* A `force` key that a host must be able to EXTEND cannot stay forced — every
|
|
50
|
+
* apply would delete the extension. Moving it to `defaults` protects the
|
|
51
|
+
* extension but freezes every host that never customised it on whatever literal
|
|
52
|
+
* their last apply left behind, because `defaults` never overwrites.
|
|
53
|
+
*
|
|
54
|
+
* `adopt` is the bridge. Each entry lists the values Lisa is known to have
|
|
55
|
+
* written into that key itself. A host sitting on one of them has provably NOT
|
|
56
|
+
* customised it, so Lisa discards it and lets `defaults` install the current
|
|
57
|
+
* value; any other value is the host's own and is kept. Applied between force
|
|
58
|
+
* and defaults, so the default it clears the way for lands in the same apply.
|
|
59
|
+
*
|
|
60
|
+
* Each entry maps a package.json section (e.g. "scripts") to a map of key to
|
|
61
|
+
* the Lisa-authored values recognised for that key. The list is cumulative:
|
|
62
|
+
* whenever a governed value changes, the value being replaced stays in the list
|
|
63
|
+
* so a host that skipped a release is still recognised rather than warned at.
|
|
64
|
+
*/
|
|
65
|
+
export interface AdoptSection {
|
|
66
|
+
[key: string]: Record<string, string[]>;
|
|
67
|
+
}
|
|
45
68
|
/**
|
|
46
69
|
* Template structure for package.lisa.json files
|
|
47
70
|
* @remarks
|
|
@@ -49,6 +72,8 @@ export interface RemoveSection {
|
|
|
49
72
|
* - `defaults`: Sections where project's values take precedence if they exist
|
|
50
73
|
* - `merge`: Array sections that are concatenated and deduplicated
|
|
51
74
|
* - `remove`: Section keys Lisa deletes from the project (retired keys)
|
|
75
|
+
* - `adopt`: Section keys whose Lisa-authored values are reclaimed so
|
|
76
|
+
* `defaults` can install the current one
|
|
52
77
|
*
|
|
53
78
|
* When multiple package.lisa.json files are loaded from the inheritance chain (all → typescript → specific),
|
|
54
79
|
* they are merged with child types overriding parent types in each section.
|
|
@@ -74,6 +99,9 @@ export interface RemoveSection {
|
|
|
74
99
|
* },
|
|
75
100
|
* "remove": {
|
|
76
101
|
* "scripts": ["knip"]
|
|
102
|
+
* },
|
|
103
|
+
* "adopt": {
|
|
104
|
+
* "scripts": { "lint": ["eslint . --quiet"] }
|
|
77
105
|
* }
|
|
78
106
|
* }
|
|
79
107
|
* ```
|
|
@@ -87,9 +115,11 @@ export interface PackageLisaTemplate {
|
|
|
87
115
|
merge?: Record<string, unknown[]>;
|
|
88
116
|
/** Section keys Lisa deletes from the project (retired keys) */
|
|
89
117
|
remove?: Record<string, string[]>;
|
|
118
|
+
/** Section keys whose Lisa-authored values are reclaimed before defaults */
|
|
119
|
+
adopt?: Record<string, Record<string, string[]>>;
|
|
90
120
|
}
|
|
91
121
|
/**
|
|
92
|
-
* Merged template with resolved force/defaults/merge/remove sections
|
|
122
|
+
* Merged template with resolved force/defaults/merge/remove/adopt sections
|
|
93
123
|
* ready to be applied to a project's package.json
|
|
94
124
|
*/
|
|
95
125
|
export interface ResolvedPackageLisaTemplate extends PackageLisaTemplate {
|
|
@@ -97,5 +127,6 @@ export interface ResolvedPackageLisaTemplate extends PackageLisaTemplate {
|
|
|
97
127
|
defaults: Record<string, unknown>;
|
|
98
128
|
merge: Record<string, unknown[]>;
|
|
99
129
|
remove: Record<string, string[]>;
|
|
130
|
+
adopt: Record<string, Record<string, string[]>>;
|
|
100
131
|
}
|
|
101
132
|
//# sourceMappingURL=package-lisa-types.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"package-lisa-types.d.ts","sourceRoot":"","sources":["../../src/strategies/package-lisa-types.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC3B,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED;;;;;GAKG;AACH,MAAM,WAAW,eAAe;IAC9B,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC3B,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,EAAE,CAAC;CAC1B;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,aAAa;IAC5B,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;CACzB;AAED
|
|
1
|
+
{"version":3,"file":"package-lisa-types.d.ts","sourceRoot":"","sources":["../../src/strategies/package-lisa-types.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC3B,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED;;;;;GAKG;AACH,MAAM,WAAW,eAAe;IAC9B,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC3B,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,EAAE,CAAC;CAC1B;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,aAAa;IAC5B,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;CACzB;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,WAAW,YAAY;IAC3B,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;CACzC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AACH,MAAM,WAAW,mBAAmB;IAClC,uEAAuE;IACvE,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAEhC,0EAA0E;IAC1E,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAEnC,4DAA4D;IAC5D,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC,CAAC;IAElC,gEAAgE;IAChE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IAElC,4EAA4E;IAC5E,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC;CAClD;AAED;;;GAGG;AACH,MAAM,WAAW,2BAA4B,SAAQ,mBAAmB;IACtE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC,CAAC;IACjC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IACjC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC;CACjD"}
|
|
@@ -10,8 +10,15 @@ import type { ICopyStrategy, StrategyContext } from "./strategy.interface.js";
|
|
|
10
10
|
*
|
|
11
11
|
* Behavior is defined in package.lisa.json:
|
|
12
12
|
* - force: Lisa's values completely replace project's values
|
|
13
|
+
* - adopt: Lisa reclaims a key still holding a value Lisa itself wrote
|
|
13
14
|
* - defaults: Project's values preserved; Lisa's used only if missing
|
|
14
15
|
* - merge: Arrays concatenated and deduplicated
|
|
16
|
+
* - remove: Retired keys deleted from their section
|
|
17
|
+
*
|
|
18
|
+
* A governed script CI invokes is shipped as a pair — `lint:lisa` forced, and
|
|
19
|
+
* `lint` merely defaulted to invoke it — so the host owns the composition point
|
|
20
|
+
* and anything chained onto it survives an apply. See the reserved-base section
|
|
21
|
+
* of `wiki/documentation/specs/package-lisa-json.md`.
|
|
15
22
|
*
|
|
16
23
|
* Inheritance chain: all → typescript → specific types (expo, nestjs, cdk, npm-package)
|
|
17
24
|
* Child types override parent values in each section.
|
|
@@ -25,7 +32,15 @@ import type { ICopyStrategy, StrategyContext } from "./strategy.interface.js";
|
|
|
25
32
|
* - Keeps project's package.json 100% clean (no Lisa artifacts)
|
|
26
33
|
*/
|
|
27
34
|
export declare class PackageLisaStrategy implements ICopyStrategy {
|
|
35
|
+
private readonly readApplyingVersion;
|
|
28
36
|
readonly name: "package-lisa";
|
|
37
|
+
/**
|
|
38
|
+
* Build a strategy, optionally stating the version performing the apply.
|
|
39
|
+
* @param readApplyingVersion - Reports the Lisa version performing the apply.
|
|
40
|
+
* Injected rather than read inline so a spec can state a version outright
|
|
41
|
+
* instead of deriving its expectation from the code under test.
|
|
42
|
+
*/
|
|
43
|
+
constructor(readApplyingVersion?: () => string);
|
|
29
44
|
private readonly PACKAGE_JSON;
|
|
30
45
|
private readonly TSCONFIG_JSON;
|
|
31
46
|
private readonly APP_JSON;
|
|
@@ -223,6 +238,8 @@ export declare class PackageLisaStrategy implements ICopyStrategy {
|
|
|
223
238
|
* @param projectJson - Current project's package.json
|
|
224
239
|
* @param template - Merged package.lisa.json template
|
|
225
240
|
* @param fileName - Basename used in error messages
|
|
241
|
+
* @param restricted - True when the template was reduced to security pins,
|
|
242
|
+
* which is the postinstall path and Lisa's own repository
|
|
226
243
|
* @returns Modified package.json plus operator-visible notes
|
|
227
244
|
* @private
|
|
228
245
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"package-lisa.d.ts","sourceRoot":"","sources":["../../src/strategies/package-lisa.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,mBAAmB,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAE1E,OAAO,KAAK,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"package-lisa.d.ts","sourceRoot":"","sources":["../../src/strategies/package-lisa.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,mBAAmB,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAE1E,OAAO,KAAK,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AA8B9E;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH;;;;;;GAMG;AACH,qBAAa,mBAAoB,YAAW,aAAa;IAUrD,OAAO,CAAC,QAAQ,CAAC,mBAAmB;IATtC,QAAQ,CAAC,IAAI,EAAG,cAAc,CAAU;IAExC;;;;;OAKG;gBAEgB,mBAAmB,GAAE,MAAM,MAA0B;IAGxE,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAkB;IAC/C,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAmB;IACjD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAc;IACvC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAc;IACvC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAmB;IACjD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAc;IACvC,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAA0C;IAC5E,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAGhC;IAEF;;;;;;;OAOG;IACG,eAAe,CACnB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACpC,aAAa,EAAE,SAAS,WAAW,EAAE,EACrC,OAAO,EAAE,MAAM,EACf,gBAAgB,UAAQ,GACvB,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAmBnC;;;;;;;;;;;;;OAaG;IACG,KAAK,CACT,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,EAChB,YAAY,EAAE,MAAM,EACpB,OAAO,EAAE,eAAe,GACvB,OAAO,CAAC,mBAAmB,CAAC;IA8D/B;;;;;;;;;OASG;YACW,iBAAiB;IAmB/B;;;;;;;;;OASG;YACW,iBAAiB;IAwC/B;;;;;;;;OAQG;YACW,gBAAgB;IAkC9B;;;;;;;;;;;;;;;;;;;;;;;;;;OA0BG;IACH,OAAO,CAAC,sBAAsB;IAsB9B;;;;;;;;;OASG;IACH,OAAO,CAAC,wBAAwB;IAsChC;;;;;;OAMG;YACW,kBAAkB;IA0DhC;;;;;;OAMG;YACW,+BAA+B;IAkB7C;;;;;;OAMG;YACW,wBAAwB;IAgBtC;;;;;;OAMG;YACW,8BAA8B;IAmB5C;;;;;;OAMG;YACW,gBAAgB;IAW9B;;;;;;;;;OASG;YACW,qBAAqB;IA0CnC;;;;;;;;;;OAUG;IACH,OAAO,CAAC,mBAAmB;IAgB3B;;;;;;;;OAQG;IACH,OAAO,CAAC,cAAc;IAgBtB;;;;;;;OAOG;IACH,OAAO,CAAC,kBAAkB;IAmB1B;;;;;;;;;;;;;;;;OAgBG;IACH,OAAO,CAAC,aAAa;IAkDrB;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,mBAAmB;IAwB3B;;;;;;;OAOG;IACH,OAAO,CAAC,kBAAkB;IAsB1B;;;;;;;;OAQG;IACH,OAAO,CAAC,iBAAiB;CA2B1B"}
|
|
@@ -8,6 +8,7 @@ import { ensureParentDir } from "../utils/file-operations.js";
|
|
|
8
8
|
import { readJson, writeJson, deepMerge, readJsonOrNull, } from "../utils/json-utils.js";
|
|
9
9
|
import { JsonMergeError } from "../errors/index.js";
|
|
10
10
|
import { LISA_PACKAGE_NAME } from "../core/self-apply.js";
|
|
11
|
+
import { getPackageVersion } from "../cli/version.js";
|
|
11
12
|
/**
|
|
12
13
|
* @file package-lisa.ts
|
|
13
14
|
* @description Package.lisa.json strategy for governance-driven package.json management
|
|
@@ -18,8 +19,15 @@ import { LISA_PACKAGE_NAME } from "../core/self-apply.js";
|
|
|
18
19
|
*
|
|
19
20
|
* Behavior is defined in package.lisa.json:
|
|
20
21
|
* - force: Lisa's values completely replace project's values
|
|
22
|
+
* - adopt: Lisa reclaims a key still holding a value Lisa itself wrote
|
|
21
23
|
* - defaults: Project's values preserved; Lisa's used only if missing
|
|
22
24
|
* - merge: Arrays concatenated and deduplicated
|
|
25
|
+
* - remove: Retired keys deleted from their section
|
|
26
|
+
*
|
|
27
|
+
* A governed script CI invokes is shipped as a pair — `lint:lisa` forced, and
|
|
28
|
+
* `lint` merely defaulted to invoke it — so the host owns the composition point
|
|
29
|
+
* and anything chained onto it survives an apply. See the reserved-base section
|
|
30
|
+
* of `wiki/documentation/specs/package-lisa-json.md`.
|
|
23
31
|
*
|
|
24
32
|
* Inheritance chain: all → typescript → specific types (expo, nestjs, cdk, npm-package)
|
|
25
33
|
* Child types override parent values in each section.
|
|
@@ -33,7 +41,17 @@ import { LISA_PACKAGE_NAME } from "../core/self-apply.js";
|
|
|
33
41
|
* - Keeps project's package.json 100% clean (no Lisa artifacts)
|
|
34
42
|
*/
|
|
35
43
|
export class PackageLisaStrategy {
|
|
44
|
+
readApplyingVersion;
|
|
36
45
|
name = "package-lisa";
|
|
46
|
+
/**
|
|
47
|
+
* Build a strategy, optionally stating the version performing the apply.
|
|
48
|
+
* @param readApplyingVersion - Reports the Lisa version performing the apply.
|
|
49
|
+
* Injected rather than read inline so a spec can state a version outright
|
|
50
|
+
* instead of deriving its expectation from the code under test.
|
|
51
|
+
*/
|
|
52
|
+
constructor(readApplyingVersion = getPackageVersion) {
|
|
53
|
+
this.readApplyingVersion = readApplyingVersion;
|
|
54
|
+
}
|
|
37
55
|
PACKAGE_JSON = "package.json";
|
|
38
56
|
TSCONFIG_JSON = "tsconfig.json";
|
|
39
57
|
APP_JSON = "app.json";
|
|
@@ -52,10 +70,9 @@ export class PackageLisaStrategy {
|
|
|
52
70
|
*/
|
|
53
71
|
async planPackageJson(projectJson, detectedTypes, lisaDir, securityPinsOnly = false) {
|
|
54
72
|
const merged = await this.loadAndMergeTemplates(lisaDir, detectedTypes);
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
const forced = this.applyTemplate(projectJson, effective, this.PACKAGE_JSON);
|
|
73
|
+
const restricted = securityPinsOnly || projectJson.name === LISA_PACKAGE_NAME;
|
|
74
|
+
const effective = restricted ? this.restrictToSecurityPins(merged) : merged;
|
|
75
|
+
const forced = this.applyTemplate(projectJson, effective, this.PACKAGE_JSON, restricted);
|
|
59
76
|
const result = planSelfReferencingOverrideNormalization(forced.packageJson, this.PACKAGE_JSON);
|
|
60
77
|
assertManifestIsInstallable(result.packageJson, this.PACKAGE_JSON);
|
|
61
78
|
return result.packageJson;
|
|
@@ -190,10 +207,9 @@ export class PackageLisaStrategy {
|
|
|
190
207
|
// Get detected project types by analyzing the project structure
|
|
191
208
|
const detectedTypes = await this.detectProjectTypes(projectDir);
|
|
192
209
|
const merged = await this.loadAndMergeTemplates(lisaDir, detectedTypes);
|
|
193
|
-
const
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
const forced = this.applyTemplate(projectJson, effective, this.PACKAGE_JSON);
|
|
210
|
+
const restricted = securityPinsOnly || projectJson.name === LISA_PACKAGE_NAME;
|
|
211
|
+
const effective = restricted ? this.restrictToSecurityPins(merged) : merged;
|
|
212
|
+
const forced = this.applyTemplate(projectJson, effective, this.PACKAGE_JSON, restricted);
|
|
197
213
|
const plan = planSelfReferencingOverrideNormalization(forced.packageJson, this.PACKAGE_JSON);
|
|
198
214
|
assertManifestIsInstallable(plan.packageJson, this.PACKAGE_JSON);
|
|
199
215
|
return { ...plan, notes: [...forced.notes, ...plan.notes] };
|
|
@@ -235,7 +251,10 @@ export class PackageLisaStrategy {
|
|
|
235
251
|
}
|
|
236
252
|
this.includeBackingDirectDeps(template, force);
|
|
237
253
|
const remove = Object.fromEntries(OVERRIDE_SECTIONS.filter(section => template.remove[section] !== undefined).map(section => [section, template.remove[section]]));
|
|
238
|
-
|
|
254
|
+
// `adopt` reclaims a key so `defaults` can rewrite it, and `defaults` is
|
|
255
|
+
// dropped here — carrying it alone would delete a host script and put
|
|
256
|
+
// nothing back.
|
|
257
|
+
return { force, defaults: {}, merge: {}, remove, adopt: {} };
|
|
239
258
|
}
|
|
240
259
|
/**
|
|
241
260
|
* For every direct-dependency override in the restricted overrides/resolutions,
|
|
@@ -410,6 +429,7 @@ export class PackageLisaStrategy {
|
|
|
410
429
|
defaults: {},
|
|
411
430
|
merge: {},
|
|
412
431
|
remove: {},
|
|
432
|
+
adopt: {},
|
|
413
433
|
};
|
|
414
434
|
// Expand types to include parents (e.g., expo includes typescript)
|
|
415
435
|
const allTypes = this.expandTypeHierarchy(detectedTypes);
|
|
@@ -467,6 +487,7 @@ export class PackageLisaStrategy {
|
|
|
467
487
|
defaults: deepMerge(parent.defaults, child.defaults || {}),
|
|
468
488
|
merge: this.mergeMergeSections(parent.merge, child.merge || {}),
|
|
469
489
|
remove: this.mergeMergeSections(parent.remove, child.remove || {}),
|
|
490
|
+
adopt: mergeAdoptSections(parent.adopt, child.adopt || {}),
|
|
470
491
|
};
|
|
471
492
|
}
|
|
472
493
|
/**
|
|
@@ -503,21 +524,40 @@ export class PackageLisaStrategy {
|
|
|
503
524
|
* @param projectJson - Current project's package.json
|
|
504
525
|
* @param template - Merged package.lisa.json template
|
|
505
526
|
* @param fileName - Basename used in error messages
|
|
527
|
+
* @param restricted - True when the template was reduced to security pins,
|
|
528
|
+
* which is the postinstall path and Lisa's own repository
|
|
506
529
|
* @returns Modified package.json plus operator-visible notes
|
|
507
530
|
* @private
|
|
508
531
|
*/
|
|
509
|
-
applyTemplate(projectJson, template, fileName) {
|
|
532
|
+
applyTemplate(projectJson, template, fileName, restricted = false) {
|
|
510
533
|
// Phase 1: Apply force (Lisa's values completely replace project's), then
|
|
511
534
|
// restore any dependency pin the host had raised ABOVE Lisa's floor.
|
|
512
535
|
const afterForce = preserveHigherHostPins(projectJson, deepMerge(projectJson, template.force), fileName);
|
|
536
|
+
// Phase 1.5: Reclaim keys still carrying a value Lisa itself wrote, so the
|
|
537
|
+
// defaults phase can install the current one. A host value Lisa does not
|
|
538
|
+
// recognise as its own is left alone — that is the whole point.
|
|
539
|
+
const afterAdopt = applyAdoptSections(afterForce.packageJson, template);
|
|
513
540
|
// Phase 2: Apply defaults (project's values preserved, Lisa provides fallback)
|
|
514
|
-
const afterDefaults = deepMerge(template.defaults,
|
|
541
|
+
const afterDefaults = deepMerge(template.defaults, afterAdopt);
|
|
515
542
|
// Phase 3: Apply merge (concatenate and deduplicate arrays)
|
|
516
543
|
const afterMerge = this.applyMergeSections(afterDefaults, template.merge);
|
|
517
544
|
// Phase 4: Apply remove (delete retired keys from their sections)
|
|
545
|
+
const afterRemove = this.applyRemoveSections(afterMerge, template.remove);
|
|
546
|
+
// Phase 5: Make the host's pin name the version that wrote these templates.
|
|
547
|
+
// A restricted apply is the postinstall path, where the installed package
|
|
548
|
+
// IS the applying version, so there is nothing to reconcile — and rewriting
|
|
549
|
+
// the host's manifest from inside their `install` is not this phase's to do.
|
|
550
|
+
const pinned = restricted
|
|
551
|
+
? { packageJson: afterRemove, notes: [] }
|
|
552
|
+
: alignLisaPin(afterRemove, this.readApplyingVersion());
|
|
553
|
+
// Phase 6: Say out loud what the host lost, and which gates nothing runs.
|
|
518
554
|
return {
|
|
519
|
-
packageJson:
|
|
520
|
-
notes:
|
|
555
|
+
packageJson: pinned.packageJson,
|
|
556
|
+
notes: [
|
|
557
|
+
...afterForce.notes,
|
|
558
|
+
...pinned.notes,
|
|
559
|
+
...describeScriptChanges(projectJson, pinned.packageJson, template),
|
|
560
|
+
],
|
|
521
561
|
};
|
|
522
562
|
}
|
|
523
563
|
/**
|
|
@@ -598,6 +638,295 @@ export class PackageLisaStrategy {
|
|
|
598
638
|
return result;
|
|
599
639
|
}
|
|
600
640
|
}
|
|
641
|
+
/**
|
|
642
|
+
* Dependency sections a host may declare `@codyswann/lisa` in, most-specific
|
|
643
|
+
* first: a runtime dependency is the unusual choice, so finding one there means
|
|
644
|
+
* the host meant it.
|
|
645
|
+
*/
|
|
646
|
+
const LISA_PIN_SECTIONS = ["dependencies", "devDependencies"];
|
|
647
|
+
/** Where a pin goes on a host that does not have one yet. */
|
|
648
|
+
const DEFAULT_LISA_PIN_SECTION = "devDependencies";
|
|
649
|
+
/**
|
|
650
|
+
* Specs that name a LOCATION rather than a registry version.
|
|
651
|
+
* @remarks
|
|
652
|
+
* `file:` / `link:` / `portal:` / `workspace:` and the git forms all mean
|
|
653
|
+
* somebody is developing against a checkout instead of a release. Replacing one
|
|
654
|
+
* with a version number breaks that setup, so the apply reports the skew rather
|
|
655
|
+
* than resolving it — which is the branch the second acceptance scenario is
|
|
656
|
+
* about.
|
|
657
|
+
*/
|
|
658
|
+
const NON_REGISTRY_SPEC = /^(?:file|link|portal|workspace|git|git\+[a-z]+|github|https?|npm):/i;
|
|
659
|
+
/**
|
|
660
|
+
* Does this spec point at a location rather than name a registry version?
|
|
661
|
+
* @param spec - The version spec the host declared
|
|
662
|
+
* @returns True when the spec resolves outside the registry
|
|
663
|
+
*/
|
|
664
|
+
function isNonRegistrySpec(spec) {
|
|
665
|
+
return NON_REGISTRY_SPEC.test(spec);
|
|
666
|
+
}
|
|
667
|
+
/**
|
|
668
|
+
* Where the host declares its Lisa pin, and what it currently says.
|
|
669
|
+
* @param packageJson - The manifest as the merge phases left it
|
|
670
|
+
* @returns The section to write into and the spec already there, if any
|
|
671
|
+
*/
|
|
672
|
+
function locateLisaPin(packageJson) {
|
|
673
|
+
const declared = LISA_PIN_SECTIONS.map(section => ({
|
|
674
|
+
section,
|
|
675
|
+
current: asRecord(packageJson[section])[LISA_PACKAGE_NAME],
|
|
676
|
+
})).find(found => typeof found.current === "string");
|
|
677
|
+
return declared === undefined
|
|
678
|
+
? { section: DEFAULT_LISA_PIN_SECTION, current: undefined }
|
|
679
|
+
: { section: declared.section, current: declared.current };
|
|
680
|
+
}
|
|
681
|
+
/**
|
|
682
|
+
* Make the host's `@codyswann/lisa` pin name the version doing the applying.
|
|
683
|
+
* @remarks
|
|
684
|
+
* An apply writes templates that call into the package's own API, so the
|
|
685
|
+
* applied version and the INSTALLED version are two halves of one thing. When
|
|
686
|
+
* they drift, a config file calls an export the installed package does not have
|
|
687
|
+
* and every run of the tool that loads it dies at config load — while the apply
|
|
688
|
+
* itself reports success, and `postinstall`'s `[ -d dist/configs ] || tsc ||
|
|
689
|
+
* true` swallows the only local signal. The failure then surfaces at the next
|
|
690
|
+
* lint run, detached from the apply that caused it (#2953).
|
|
691
|
+
*
|
|
692
|
+
* A range is rewritten as readily as an exact pin, and deliberately so: a caret
|
|
693
|
+
* range ADMITS the applying version without requiring it, so a lockfile still
|
|
694
|
+
* resolving an older build produces exactly the skew this closes.
|
|
695
|
+
*
|
|
696
|
+
* Lisa applying to its own repository never reaches here: that path is
|
|
697
|
+
* restricted to security pins, and a package cannot depend on itself.
|
|
698
|
+
* @param packageJson - The manifest as the merge phases left it
|
|
699
|
+
* @param applyingVersion - Version of the Lisa performing this apply
|
|
700
|
+
* @returns The manifest with the pin aligned, plus operator-visible notes
|
|
701
|
+
*/
|
|
702
|
+
function alignLisaPin(packageJson, applyingVersion) {
|
|
703
|
+
const { section, current } = locateLisaPin(packageJson);
|
|
704
|
+
if (current === applyingVersion)
|
|
705
|
+
return { packageJson, notes: [] };
|
|
706
|
+
if (current !== undefined && isNonRegistrySpec(current)) {
|
|
707
|
+
return {
|
|
708
|
+
packageJson,
|
|
709
|
+
notes: [
|
|
710
|
+
`Left ${LISA_PACKAGE_NAME} at "${current}", which points at a local copy rather than a release, but this apply is ${applyingVersion}. If that copy is older, the files just written may call something it does not have and every lint run will fail before it checks anything.`,
|
|
711
|
+
],
|
|
712
|
+
};
|
|
713
|
+
}
|
|
714
|
+
const note = current === undefined
|
|
715
|
+
? `Added ${LISA_PACKAGE_NAME} ${applyingVersion} to ${section}. The files this apply just wrote come from ${applyingVersion} and call into it, so install it before your next lint run.`
|
|
716
|
+
: `Pinned ${LISA_PACKAGE_NAME} to ${applyingVersion}; it was ${current}. The files this apply just wrote come from ${applyingVersion} and call into it, so the two have to be the same version — install it before your next lint run.`;
|
|
717
|
+
return {
|
|
718
|
+
packageJson: {
|
|
719
|
+
...packageJson,
|
|
720
|
+
[section]: {
|
|
721
|
+
...asRecord(packageJson[section]),
|
|
722
|
+
[LISA_PACKAGE_NAME]: applyingVersion,
|
|
723
|
+
},
|
|
724
|
+
},
|
|
725
|
+
notes: [note],
|
|
726
|
+
};
|
|
727
|
+
}
|
|
728
|
+
/** The package.json section whose overwrites are reported to the operator. */
|
|
729
|
+
const SCRIPTS_SECTION = "scripts";
|
|
730
|
+
/**
|
|
731
|
+
* Suffix naming the Lisa-owned half of a split script.
|
|
732
|
+
*
|
|
733
|
+
* A governed gate is shipped as a PAIR: `lint:lisa` carries Lisa's own command
|
|
734
|
+
* and stays in `force`, so a host can neither delete nor weaken it; `lint` is
|
|
735
|
+
* only a `defaults` entry invoking it, so the host owns the composition point
|
|
736
|
+
* and anything chained onto it survives every apply.
|
|
737
|
+
*/
|
|
738
|
+
const RESERVED_BASE_SUFFIX = ":lisa";
|
|
739
|
+
/** How much of a script value an operator note quotes before eliding. */
|
|
740
|
+
const NOTE_VALUE_BUDGET = 140;
|
|
741
|
+
/**
|
|
742
|
+
* Merge two `adopt` sections, taking the UNION of the recognised values.
|
|
743
|
+
* @remarks
|
|
744
|
+
* Union, not child-overrides-parent as `force` and `defaults` use. Every entry
|
|
745
|
+
* is a value Lisa is known to have written, and a host may have taken any of
|
|
746
|
+
* them from any layer of the chain it has passed through. Dropping the parent's
|
|
747
|
+
* list would stop recognising a value Lisa really did author, and the cost of
|
|
748
|
+
* that is not cosmetic: the host gets warned that it customised something it
|
|
749
|
+
* never touched, and stops tracking the template.
|
|
750
|
+
* @param parent - Parent template's adopt section
|
|
751
|
+
* @param child - Child template's adopt section
|
|
752
|
+
* @returns Per-section, per-key union of the two, order-preserving and deduped
|
|
753
|
+
*/
|
|
754
|
+
function mergeAdoptSections(parent, child) {
|
|
755
|
+
const sections = new Set([...Object.keys(parent), ...Object.keys(child)]);
|
|
756
|
+
return Object.fromEntries(Array.from(sections).map(section => {
|
|
757
|
+
const parentKeys = parent[section] ?? {};
|
|
758
|
+
const childKeys = child[section] ?? {};
|
|
759
|
+
const keys = new Set([
|
|
760
|
+
...Object.keys(parentKeys),
|
|
761
|
+
...Object.keys(childKeys),
|
|
762
|
+
]);
|
|
763
|
+
return [
|
|
764
|
+
section,
|
|
765
|
+
Object.fromEntries(Array.from(keys).map(key => [
|
|
766
|
+
key,
|
|
767
|
+
Array.from(new Set([...(parentKeys[key] ?? []), ...(childKeys[key] ?? [])])),
|
|
768
|
+
])),
|
|
769
|
+
];
|
|
770
|
+
}));
|
|
771
|
+
}
|
|
772
|
+
/**
|
|
773
|
+
* Drop every key still carrying a value Lisa itself wrote.
|
|
774
|
+
* @remarks
|
|
775
|
+
* The deletion is what lets the `defaults` phase, which never overwrites, reach
|
|
776
|
+
* a key Lisa used to force. Nothing else is touched: a value absent from the
|
|
777
|
+
* adopt list is the host's own work by definition, and keeping it is the entire
|
|
778
|
+
* behaviour this exists to provide.
|
|
779
|
+
* @param packageJson - The document as the force phase left it
|
|
780
|
+
* @param template - Resolved template carrying the adopt section
|
|
781
|
+
* @returns The document with Lisa-authored values cleared
|
|
782
|
+
*/
|
|
783
|
+
function applyAdoptSections(packageJson, template) {
|
|
784
|
+
return Object.entries(template.adopt).reduce((document, [sectionName, recognised]) => {
|
|
785
|
+
const section = document[sectionName];
|
|
786
|
+
if (section === null ||
|
|
787
|
+
typeof section !== "object" ||
|
|
788
|
+
Array.isArray(section)) {
|
|
789
|
+
return document;
|
|
790
|
+
}
|
|
791
|
+
// A key the template also FORCES already holds Lisa's current value.
|
|
792
|
+
// Clearing it would delete what force just wrote and leave the key to
|
|
793
|
+
// whatever `defaults` happens to carry — so force wins, and adopt is a
|
|
794
|
+
// no-op there. Adopt only has meaning for a key Lisa has handed back.
|
|
795
|
+
const forcedHere = asRecord(asRecord(template.force)[sectionName]);
|
|
796
|
+
const entries = Object.entries(section);
|
|
797
|
+
const kept = entries.filter(([key, value]) => typeof value !== "string" ||
|
|
798
|
+
key in forcedHere ||
|
|
799
|
+
!(recognised[key] ?? []).includes(value));
|
|
800
|
+
if (kept.length === entries.length) {
|
|
801
|
+
return document;
|
|
802
|
+
}
|
|
803
|
+
return { ...document, [sectionName]: Object.fromEntries(kept) };
|
|
804
|
+
}, packageJson);
|
|
805
|
+
}
|
|
806
|
+
/**
|
|
807
|
+
* Quote a script value for an operator note without flooding the terminal.
|
|
808
|
+
* @param value - The script value being quoted
|
|
809
|
+
* @returns The value, elided past the note budget
|
|
810
|
+
*/
|
|
811
|
+
function quoteScript(value) {
|
|
812
|
+
return value.length <= NOTE_VALUE_BUDGET
|
|
813
|
+
? `"${value}"`
|
|
814
|
+
: `"${value.slice(0, NOTE_VALUE_BUDGET)}…"`;
|
|
815
|
+
}
|
|
816
|
+
/**
|
|
817
|
+
* Report what an apply did to the host's scripts, and what it left inert.
|
|
818
|
+
* @remarks
|
|
819
|
+
* The defect this answers was invisible rather than wrong-looking. One script
|
|
820
|
+
* value changed inside a `package.json` diff dominated by key reordering, and
|
|
821
|
+
* nothing said so, so five chained CI gates became dead code while the Lint
|
|
822
|
+
* check kept reporting green.
|
|
823
|
+
*
|
|
824
|
+
* Every key of the host's `scripts` is walked. Deliberately not a curated list
|
|
825
|
+
* of interesting names: the review that nearly shipped this defect compared a
|
|
826
|
+
* GUESSED subset and concluded "ordering only". A subset is not a method.
|
|
827
|
+
* @param projectJson - The host manifest as it was before the apply
|
|
828
|
+
* @param packageJson - The manifest the apply is about to write
|
|
829
|
+
* @param template - Resolved template, for the reserved-base pairing
|
|
830
|
+
* @returns Operator-readable lines, empty when nothing was lost
|
|
831
|
+
*/
|
|
832
|
+
function describeScriptChanges(projectJson, packageJson, template) {
|
|
833
|
+
const before = asRecord(projectJson[SCRIPTS_SECTION]);
|
|
834
|
+
const after = asRecord(packageJson[SCRIPTS_SECTION]);
|
|
835
|
+
const adopted = asRecord(template.adopt[SCRIPTS_SECTION]);
|
|
836
|
+
return [
|
|
837
|
+
...describeOverwrittenScripts(before, after, adopted),
|
|
838
|
+
...describeUnrunGates(after, template),
|
|
839
|
+
];
|
|
840
|
+
}
|
|
841
|
+
/**
|
|
842
|
+
* Was this host value one Lisa itself wrote into that key?
|
|
843
|
+
* @param adopted - The resolved adopt list for the scripts section
|
|
844
|
+
* @param name - Script name being reported on
|
|
845
|
+
* @param hostValue - The value the host carried before the apply
|
|
846
|
+
* @returns True when the value is Lisa's own rather than the host's work
|
|
847
|
+
*/
|
|
848
|
+
function isLisaAuthored(adopted, name, hostValue) {
|
|
849
|
+
const recognised = adopted[name];
|
|
850
|
+
return Array.isArray(recognised) && recognised.includes(hostValue);
|
|
851
|
+
}
|
|
852
|
+
/**
|
|
853
|
+
* Name every host script value this apply replaced or deleted.
|
|
854
|
+
* @remarks
|
|
855
|
+
* A value on the `adopt` list is Lisa's own, so replacing it discards nothing
|
|
856
|
+
* of the host's and must not be reported as a loss. That is not cosmetic: the
|
|
857
|
+
* split hands six gate names back at once, so loss-shaped wording there puts
|
|
858
|
+
* six false alarms in front of every operator on their first upgrade and a
|
|
859
|
+
* REAL loss stops standing out — the precise failure this change exists to
|
|
860
|
+
* end. Those keys get a handover line instead, because the operator does need
|
|
861
|
+
* to learn that the composition point is now theirs to extend.
|
|
862
|
+
* @param before - The host's scripts before the apply
|
|
863
|
+
* @param after - The scripts the apply is about to write
|
|
864
|
+
* @param adopted - Resolved adopt list, naming the values Lisa authored
|
|
865
|
+
* @returns One line per script whose host value did not survive
|
|
866
|
+
*/
|
|
867
|
+
function describeOverwrittenScripts(before, after, adopted) {
|
|
868
|
+
return Object.entries(before).flatMap(([name, hostValue]) => {
|
|
869
|
+
if (typeof hostValue !== "string")
|
|
870
|
+
return [];
|
|
871
|
+
const applied = after[name];
|
|
872
|
+
if (applied === hostValue)
|
|
873
|
+
return [];
|
|
874
|
+
if (applied === undefined) {
|
|
875
|
+
return [
|
|
876
|
+
`Removed scripts.${name}; it ran ${quoteScript(hostValue)}. Nothing in your project runs that any more.`,
|
|
877
|
+
];
|
|
878
|
+
}
|
|
879
|
+
if (typeof applied !== "string")
|
|
880
|
+
return [];
|
|
881
|
+
const base = `${name}${RESERVED_BASE_SUFFIX}`;
|
|
882
|
+
// The handover wording names the reserved base, so it is only truthful
|
|
883
|
+
// when the value being written actually invokes one.
|
|
884
|
+
if (isLisaAuthored(adopted, name, hostValue) && applied.includes(base)) {
|
|
885
|
+
return [
|
|
886
|
+
`Moved Lisa's ${name} checks into scripts.${base}; scripts.${name} now calls it, so anything you add there survives the next apply.`,
|
|
887
|
+
];
|
|
888
|
+
}
|
|
889
|
+
return [
|
|
890
|
+
`Replaced scripts.${name}: it ran ${quoteScript(hostValue)} and now runs ${quoteScript(applied)}.`,
|
|
891
|
+
];
|
|
892
|
+
});
|
|
893
|
+
}
|
|
894
|
+
/**
|
|
895
|
+
* Name every Lisa gate the host's own composition point does not run.
|
|
896
|
+
* @remarks
|
|
897
|
+
* `lint:lisa` being force-installed proves the gate EXISTS; it proves nothing
|
|
898
|
+
* about whether anything invokes it, and CI invokes `lint`. A host is free to
|
|
899
|
+
* decline a gate, but declining it silently is the failure mode this whole
|
|
900
|
+
* change is about, so the apply says which gate went unrun.
|
|
901
|
+
*
|
|
902
|
+
* A composition point that inlines Lisa's current base verbatim — the shape
|
|
903
|
+
* every host was left in before the split existed — does run the gate, so it is
|
|
904
|
+
* not warned about. It is nudged instead: an inlined copy stops tracking the
|
|
905
|
+
* template the next time the base changes.
|
|
906
|
+
* @param after - The scripts the apply is about to write
|
|
907
|
+
* @param template - Resolved template carrying the forced reserved bases
|
|
908
|
+
* @returns One line per gate nothing invokes, plus migration nudges
|
|
909
|
+
*/
|
|
910
|
+
function describeUnrunGates(after, template) {
|
|
911
|
+
const forcedScripts = asRecord(asRecord(template.force)[SCRIPTS_SECTION]);
|
|
912
|
+
return Object.keys(forcedScripts).flatMap(base => {
|
|
913
|
+
if (!base.endsWith(RESERVED_BASE_SUFFIX))
|
|
914
|
+
return [];
|
|
915
|
+
const composed = base.slice(0, -RESERVED_BASE_SUFFIX.length);
|
|
916
|
+
const hostValue = after[composed];
|
|
917
|
+
if (typeof hostValue !== "string" || hostValue.includes(base))
|
|
918
|
+
return [];
|
|
919
|
+
const baseValue = forcedScripts[base];
|
|
920
|
+
if (typeof baseValue === "string" && hostValue.includes(baseValue)) {
|
|
921
|
+
return [
|
|
922
|
+
`Kept your scripts.${composed}. It spells out Lisa's ${composed} checks instead of calling ${base}, so it will not pick up changes to them; run ${base} from it to stay current.`,
|
|
923
|
+
];
|
|
924
|
+
}
|
|
925
|
+
return [
|
|
926
|
+
`Kept your scripts.${composed}, but nothing invokes ${base}, so Lisa's ${composed} checks do not run. Add ${base} to scripts.${composed} to turn them back on.`,
|
|
927
|
+
];
|
|
928
|
+
});
|
|
929
|
+
}
|
|
601
930
|
/** package.json sections whose keys are treated as direct dependencies. */
|
|
602
931
|
const DIRECT_DEPENDENCY_SECTIONS = [
|
|
603
932
|
"dependencies",
|