@ia-qa/self-healing 1.6.7 → 1.6.10
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 +111 -0
- package/TUTORIAL.md +128 -2
- package/assets/logoKawaiiGreenTiny.png +0 -0
- package/dist/aom.d.ts +8 -0
- package/dist/aom.js.map +1 -1
- package/dist/cli/args.js +4 -2
- package/dist/cli/args.js.map +1 -1
- package/dist/cli/audit.js +2 -8
- package/dist/cli/audit.js.map +1 -1
- package/dist/cli/diff.d.ts +25 -1
- package/dist/cli/diff.js +176 -20
- package/dist/cli/diff.js.map +1 -1
- package/dist/cli/emit.d.ts +71 -0
- package/dist/cli/emit.js +150 -0
- package/dist/cli/emit.js.map +1 -0
- package/dist/cli/fix.js +41 -3
- package/dist/cli/fix.js.map +1 -1
- package/dist/cli/history.d.ts +12 -0
- package/dist/cli/history.js +95 -0
- package/dist/cli/history.js.map +1 -0
- package/dist/cli/index.js +68 -3
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/ingest.js +5 -8
- package/dist/cli/ingest.js.map +1 -1
- package/dist/cli/init.js +32 -0
- package/dist/cli/init.js.map +1 -1
- package/dist/cli/map.d.ts +14 -1
- package/dist/cli/map.js +144 -33
- package/dist/cli/map.js.map +1 -1
- package/dist/cli/run.js +91 -8
- package/dist/cli/run.js.map +1 -1
- package/dist/cli/ui.d.ts +16 -0
- package/dist/cli/ui.js +294 -0
- package/dist/cli/ui.js.map +1 -0
- package/dist/cli-ai/index.js +3 -3
- package/dist/cli-ai/index.js.map +1 -1
- package/dist/config.d.ts +50 -0
- package/dist/config.js +63 -1
- package/dist/config.js.map +1 -1
- package/dist/evidence.d.ts +130 -0
- package/dist/evidence.js +232 -0
- package/dist/evidence.js.map +1 -0
- package/dist/fixEngine.d.ts +2 -0
- package/dist/fixEngine.js +7 -0
- package/dist/fixEngine.js.map +1 -1
- package/dist/history.d.ts +145 -0
- package/dist/history.js +230 -0
- package/dist/history.js.map +1 -0
- package/dist/htmlReport.d.ts +47 -0
- package/dist/htmlReport.js +103 -1
- package/dist/htmlReport.js.map +1 -1
- package/dist/ingest.d.ts +18 -0
- package/dist/ingest.js +0 -0
- package/dist/ingest.js.map +1 -1
- package/dist/junit.d.ts +53 -0
- package/dist/junit.js +150 -0
- package/dist/junit.js.map +1 -0
- package/dist/mcp/server.js +46 -5
- package/dist/mcp/server.js.map +1 -1
- package/dist/nameMask.d.ts +110 -0
- package/dist/nameMask.js +0 -0
- package/dist/nameMask.js.map +1 -0
- package/dist/ui/icon.d.ts +41 -0
- package/dist/ui/icon.js +115 -0
- package/dist/ui/icon.js.map +1 -0
- package/dist/ui/lock.d.ts +40 -0
- package/dist/ui/lock.js +138 -0
- package/dist/ui/lock.js.map +1 -0
- package/dist/ui/page.d.ts +1 -0
- package/dist/ui/page.js +657 -0
- package/dist/ui/page.js.map +1 -0
- package/dist/ui/registry.d.ts +62 -0
- package/dist/ui/registry.js +139 -0
- package/dist/ui/registry.js.map +1 -0
- package/dist/ui/server.d.ts +76 -0
- package/dist/ui/server.js +607 -0
- package/dist/ui/server.js.map +1 -0
- package/dist/ui/shortcut.d.ts +90 -0
- package/dist/ui/shortcut.js +176 -0
- package/dist/ui/shortcut.js.map +1 -0
- package/dist/update.d.ts +104 -0
- package/dist/update.js +297 -0
- package/dist/update.js.map +1 -0
- package/dist/volatile.d.ts +2 -0
- package/dist/volatile.js +2 -0
- package/dist/volatile.js.map +1 -1
- package/package.json +3 -2
- package/skills/ia-qa-heal/SKILL.md +37 -0
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { PageMapping } from './aom';
|
|
2
|
+
import { diffMappings } from './browser/match';
|
|
3
|
+
import { Usage } from './ingest';
|
|
4
|
+
/**
|
|
5
|
+
* Name masking — the declared-variable-label class.
|
|
6
|
+
*
|
|
7
|
+
* A label that carries a counter, a total or a clock ("🔌 Smithery 3.2k calls",
|
|
8
|
+
* "Cart (3)", "Step 2 of 5") changes on its own. The selector still resolves, so
|
|
9
|
+
* `diffMappings` calls it `renamed` and the verdict stays PASS — reported every
|
|
10
|
+
* single run, dismissed every single run. `nameHint` notices the pattern; this is
|
|
11
|
+
* the remedy it points at.
|
|
12
|
+
*
|
|
13
|
+
* **Why this exists rather than "just use `volatile`".** `volatile` was the only
|
|
14
|
+
* answer available, and it is the wrong size: it drops the element from the contract
|
|
15
|
+
* *entirely* — role, selector, href, coverage. The counter you wanted to ignore takes
|
|
16
|
+
* a real button out of the gate with it, and a selector break on that button then
|
|
17
|
+
* goes unnoticed forever. A mask is the conservative half: the element stays under
|
|
18
|
+
* contract on everything that identifies it, and only the varying field stops being
|
|
19
|
+
* compared. Both are user declarations — the doctrine `volatile.ts` states ("a
|
|
20
|
+
* declaration, not a heuristic") admits this one for exactly the same reason, at a
|
|
21
|
+
* granularity that costs less.
|
|
22
|
+
*
|
|
23
|
+
* **Applied at comparison, never at capture.** `map` and the run capture keep writing
|
|
24
|
+
* the raw label to disk, and this module masks in-memory copies right before the diff.
|
|
25
|
+
* Three things fall out of that: the contracts keep the evidence (the report, the
|
|
26
|
+
* crops and `_overview.md` still say what the page actually read), editing the config
|
|
27
|
+
* takes effect without re-mapping, and removing a mask cannot silently rewrite history.
|
|
28
|
+
*
|
|
29
|
+
* **It lives outside `src/browser/match.js`**, like `nameDrift.ts` and for the same
|
|
30
|
+
* two reasons: that file ships twice (npm + the zero-install web tool) and must stay
|
|
31
|
+
* dependency-free, and the web tool has no config.json to declare a mask in. No
|
|
32
|
+
* config ⇒ no masks ⇒ `diffWithMasks` is `diffMappings`, object for object.
|
|
33
|
+
*
|
|
34
|
+
* **Two refusals keep it from becoming a blindfold.** A mask is dropped, loudly, when
|
|
35
|
+
* - a label it would swallow is one the tests locate by name (`usage.names`). Masking
|
|
36
|
+
* that is the exact silent failure this package exists to kill: the gate goes green
|
|
37
|
+
* while the suite goes red. The user is told which label and which file:line.
|
|
38
|
+
* - applying it would leave two elements of the same role sharing one name, on either
|
|
39
|
+
* side of the diff. That is a locator coin flip — `check` reports the same shape as
|
|
40
|
+
* a finding — and it would turn a clean `healable` into `ambiguous`, i.e. make the
|
|
41
|
+
* gate *worse* than no mask at all.
|
|
42
|
+
*/
|
|
43
|
+
export interface NameMask {
|
|
44
|
+
/** The pattern as written in config.json — also the value masked labels collapse to. */
|
|
45
|
+
raw: string;
|
|
46
|
+
regex: RegExp;
|
|
47
|
+
}
|
|
48
|
+
export type MaskRefusalReason = 'referenced-by-tests' | 'collision';
|
|
49
|
+
export interface MaskRefusal {
|
|
50
|
+
pattern: string;
|
|
51
|
+
reason: MaskRefusalReason;
|
|
52
|
+
/** Human-readable specifics: which label, and where it is named. */
|
|
53
|
+
detail: string;
|
|
54
|
+
}
|
|
55
|
+
export interface MaskOutcome {
|
|
56
|
+
/** Patterns that took effect, with how many element labels each one collapsed. */
|
|
57
|
+
applied: Array<{
|
|
58
|
+
pattern: string;
|
|
59
|
+
count: number;
|
|
60
|
+
}>;
|
|
61
|
+
refused: MaskRefusal[];
|
|
62
|
+
}
|
|
63
|
+
/** Does the pattern keep anything a human could recognise the element by? */
|
|
64
|
+
export declare function hasStablePart(pattern: string): boolean;
|
|
65
|
+
/**
|
|
66
|
+
* Compile `config.nameMask`. Same glob dialect as `volatile` (`*` = anything,
|
|
67
|
+
* case-insensitive, whole-string) so there is one pattern language to learn, minus
|
|
68
|
+
* the field prefixes: a mask is about the accessible name and nothing else.
|
|
69
|
+
*/
|
|
70
|
+
export declare function parseNameMasks(patterns: string[] | undefined): NameMask[];
|
|
71
|
+
/**
|
|
72
|
+
* The project's masks, or none.
|
|
73
|
+
*
|
|
74
|
+
* Tolerant of **absence** and nothing else: the MCP server diffs mapping files that
|
|
75
|
+
* may belong to no project at all, and the web tool has no config.json — both must
|
|
76
|
+
* keep behaving exactly as they did, which "no config ⇒ no masks" gives for free.
|
|
77
|
+
*
|
|
78
|
+
* A config that *exists* and is malformed is rethrown. Swallowing that would undo the
|
|
79
|
+
* whole point of validating `nameMask` at load: the user would get a diff that quietly
|
|
80
|
+
* ignored their declaration — a mask that does nothing, reported as nothing — which is
|
|
81
|
+
* precisely the silent failure the validation exists to make impossible.
|
|
82
|
+
*/
|
|
83
|
+
export declare function loadNameMasks(): NameMask[];
|
|
84
|
+
export interface MaskedPair {
|
|
85
|
+
before: PageMapping;
|
|
86
|
+
after: PageMapping;
|
|
87
|
+
outcome: MaskOutcome;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Apply the surviving masks to in-memory copies of both mappings. The originals —
|
|
91
|
+
* and the files they were read from — are never touched.
|
|
92
|
+
*/
|
|
93
|
+
export declare function applyNameMasks(before: PageMapping, after: PageMapping, masks: NameMask[], usage: Usage | null): MaskedPair;
|
|
94
|
+
/**
|
|
95
|
+
* The single door every verdict goes through.
|
|
96
|
+
*
|
|
97
|
+
* Every path that produces a verdict must apply the same policy — a `diff` and an
|
|
98
|
+
* MCP `diff_mappings` disagreeing about the same two files is the divergence this
|
|
99
|
+
* project treats as a defect — so no caller outside this module calls `diffMappings`
|
|
100
|
+
* directly, and a test asserts that.
|
|
101
|
+
*
|
|
102
|
+
* With no masks configured this returns `diffMappings(before, after)` and nothing
|
|
103
|
+
* else: no copies, no extra field, byte-identical output for every project that never
|
|
104
|
+
* writes a `nameMask` line.
|
|
105
|
+
*/
|
|
106
|
+
export declare function diffWithMasks(before: PageMapping, after: PageMapping, masks: NameMask[], usage?: Usage | null): ReturnType<typeof diffMappings> & {
|
|
107
|
+
nameMask?: MaskOutcome;
|
|
108
|
+
};
|
|
109
|
+
/** Union of per-page outcomes, for a directory verdict. */
|
|
110
|
+
export declare function mergeMaskOutcomes(outcomes: Array<MaskOutcome | undefined>): MaskOutcome | undefined;
|
package/dist/nameMask.js
ADDED
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nameMask.js","sourceRoot":"","sources":["../src/nameMask.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The console's desktop icon.
|
|
3
|
+
*
|
|
4
|
+
* Windows will not take a PNG for a shortcut's `IconLocation` — it wants an
|
|
5
|
+
* `.ico`, an `.exe` or a `.dll`. Converting an image normally means a native
|
|
6
|
+
* encoder, which this package will not take on: `sharp`/`jimp` for one desktop
|
|
7
|
+
* icon would be a heavier dependency than the entire tool.
|
|
8
|
+
*
|
|
9
|
+
* It does not need one. Since Vista, an ICO entry may hold a **PNG verbatim**
|
|
10
|
+
* instead of a BMP, so the conversion is a 22-byte header wrapped around bytes
|
|
11
|
+
* we already have. No decoding, no re-encoding, no dependency, and the result
|
|
12
|
+
* is byte-identical every run.
|
|
13
|
+
*
|
|
14
|
+
* Layout, all little-endian:
|
|
15
|
+
*
|
|
16
|
+
* ICONDIR 6 bytes reserved=0, type=1 (icon), count
|
|
17
|
+
* ICONDIRENTRY 16 bytes w, h, palette=0, reserved=0, planes=1, bpp=32,
|
|
18
|
+
* byteLength, offset
|
|
19
|
+
* …the PNG file itself
|
|
20
|
+
*/
|
|
21
|
+
export declare const ICO_HEADER_BYTES = 22;
|
|
22
|
+
/** Width/height from a PNG's IHDR, which is always the first chunk. */
|
|
23
|
+
export declare function pngSize(png: Buffer): {
|
|
24
|
+
width: number;
|
|
25
|
+
height: number;
|
|
26
|
+
} | null;
|
|
27
|
+
/**
|
|
28
|
+
* Wrap a PNG in an ICO container. Returns null for anything that is not a PNG,
|
|
29
|
+
* rather than producing a file Windows would silently refuse to draw.
|
|
30
|
+
*
|
|
31
|
+
* A dimension of 256 is written as 0 — that is the format's own convention for
|
|
32
|
+
* "256", and it is why sizes above 256 cannot be expressed at all.
|
|
33
|
+
*/
|
|
34
|
+
export declare function pngToIco(png: Buffer): Buffer | null;
|
|
35
|
+
/**
|
|
36
|
+
* The brand logo shipped in the tarball. `assets/` must stay in package.json →
|
|
37
|
+
* `files` or this resolves to a path that only exists in the repo — the same
|
|
38
|
+
* trap `skills/` has (a test pins both).
|
|
39
|
+
*/
|
|
40
|
+
export declare function brandLogoPath(): string;
|
|
41
|
+
export declare function readBrandLogo(): Buffer | null;
|
package/dist/ui/icon.js
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.ICO_HEADER_BYTES = void 0;
|
|
37
|
+
exports.pngSize = pngSize;
|
|
38
|
+
exports.pngToIco = pngToIco;
|
|
39
|
+
exports.brandLogoPath = brandLogoPath;
|
|
40
|
+
exports.readBrandLogo = readBrandLogo;
|
|
41
|
+
const fs = __importStar(require("fs"));
|
|
42
|
+
const path = __importStar(require("path"));
|
|
43
|
+
/**
|
|
44
|
+
* The console's desktop icon.
|
|
45
|
+
*
|
|
46
|
+
* Windows will not take a PNG for a shortcut's `IconLocation` — it wants an
|
|
47
|
+
* `.ico`, an `.exe` or a `.dll`. Converting an image normally means a native
|
|
48
|
+
* encoder, which this package will not take on: `sharp`/`jimp` for one desktop
|
|
49
|
+
* icon would be a heavier dependency than the entire tool.
|
|
50
|
+
*
|
|
51
|
+
* It does not need one. Since Vista, an ICO entry may hold a **PNG verbatim**
|
|
52
|
+
* instead of a BMP, so the conversion is a 22-byte header wrapped around bytes
|
|
53
|
+
* we already have. No decoding, no re-encoding, no dependency, and the result
|
|
54
|
+
* is byte-identical every run.
|
|
55
|
+
*
|
|
56
|
+
* Layout, all little-endian:
|
|
57
|
+
*
|
|
58
|
+
* ICONDIR 6 bytes reserved=0, type=1 (icon), count
|
|
59
|
+
* ICONDIRENTRY 16 bytes w, h, palette=0, reserved=0, planes=1, bpp=32,
|
|
60
|
+
* byteLength, offset
|
|
61
|
+
* …the PNG file itself
|
|
62
|
+
*/
|
|
63
|
+
exports.ICO_HEADER_BYTES = 22;
|
|
64
|
+
/** Width/height from a PNG's IHDR, which is always the first chunk. */
|
|
65
|
+
function pngSize(png) {
|
|
66
|
+
const SIGNATURE = '89504e470d0a1a0a';
|
|
67
|
+
if (png.length < 24 || png.subarray(0, 8).toString('hex') !== SIGNATURE)
|
|
68
|
+
return null;
|
|
69
|
+
return { width: png.readUInt32BE(16), height: png.readUInt32BE(20) };
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Wrap a PNG in an ICO container. Returns null for anything that is not a PNG,
|
|
73
|
+
* rather than producing a file Windows would silently refuse to draw.
|
|
74
|
+
*
|
|
75
|
+
* A dimension of 256 is written as 0 — that is the format's own convention for
|
|
76
|
+
* "256", and it is why sizes above 256 cannot be expressed at all.
|
|
77
|
+
*/
|
|
78
|
+
function pngToIco(png) {
|
|
79
|
+
const size = pngSize(png);
|
|
80
|
+
if (!size)
|
|
81
|
+
return null;
|
|
82
|
+
if (size.width > 256 || size.height > 256)
|
|
83
|
+
return null;
|
|
84
|
+
const header = Buffer.alloc(exports.ICO_HEADER_BYTES);
|
|
85
|
+
header.writeUInt16LE(0, 0); // reserved
|
|
86
|
+
header.writeUInt16LE(1, 2); // type: icon
|
|
87
|
+
header.writeUInt16LE(1, 4); // one image
|
|
88
|
+
header.writeUInt8(size.width === 256 ? 0 : size.width, 6);
|
|
89
|
+
header.writeUInt8(size.height === 256 ? 0 : size.height, 7);
|
|
90
|
+
header.writeUInt8(0, 8); // palette colours: none, it is truecolour
|
|
91
|
+
header.writeUInt8(0, 9); // reserved
|
|
92
|
+
header.writeUInt16LE(1, 10); // colour planes
|
|
93
|
+
header.writeUInt16LE(32, 12); // bits per pixel
|
|
94
|
+
header.writeUInt32LE(png.length, 14);
|
|
95
|
+
header.writeUInt32LE(exports.ICO_HEADER_BYTES, 18);
|
|
96
|
+
return Buffer.concat([header, png]);
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* The brand logo shipped in the tarball. `assets/` must stay in package.json →
|
|
100
|
+
* `files` or this resolves to a path that only exists in the repo — the same
|
|
101
|
+
* trap `skills/` has (a test pins both).
|
|
102
|
+
*/
|
|
103
|
+
function brandLogoPath() {
|
|
104
|
+
// dist/ui/icon.js → package root
|
|
105
|
+
return path.join(__dirname, '..', '..', 'assets', 'logoKawaiiGreenTiny.png');
|
|
106
|
+
}
|
|
107
|
+
function readBrandLogo() {
|
|
108
|
+
try {
|
|
109
|
+
return fs.readFileSync(brandLogoPath());
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
//# sourceMappingURL=icon.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"icon.js","sourceRoot":"","sources":["../../src/ui/icon.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2BA,0BAIC;AASD,4BAkBC;AAOD,sCAGC;AAED,sCAMC;AA5ED,uCAAyB;AACzB,2CAA6B;AAE7B;;;;;;;;;;;;;;;;;;;GAmBG;AAEU,QAAA,gBAAgB,GAAG,EAAE,CAAC;AAEnC,uEAAuE;AACvE,SAAgB,OAAO,CAAC,GAAW;IACjC,MAAM,SAAS,GAAG,kBAAkB,CAAC;IACrC,IAAI,GAAG,CAAC,MAAM,GAAG,EAAE,IAAI,GAAG,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC;IACrF,OAAO,EAAE,KAAK,EAAE,GAAG,CAAC,YAAY,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,YAAY,CAAC,EAAE,CAAC,EAAE,CAAC;AACvE,CAAC;AAED;;;;;;GAMG;AACH,SAAgB,QAAQ,CAAC,GAAW;IAClC,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;IAC1B,IAAI,CAAC,IAAI;QAAE,OAAO,IAAI,CAAC;IACvB,IAAI,IAAI,CAAC,KAAK,GAAG,GAAG,IAAI,IAAI,CAAC,MAAM,GAAG,GAAG;QAAE,OAAO,IAAI,CAAC;IAEvD,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,wBAAgB,CAAC,CAAC;IAC9C,MAAM,CAAC,aAAa,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,WAAW;IACvC,MAAM,CAAC,aAAa,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,aAAa;IACzC,MAAM,CAAC,aAAa,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,YAAY;IACxC,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,KAAK,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;IAC1D,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,MAAM,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;IAC5D,MAAM,CAAC,UAAU,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,0CAA0C;IACnE,MAAM,CAAC,UAAU,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,WAAW;IACpC,MAAM,CAAC,aAAa,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,gBAAgB;IAC7C,MAAM,CAAC,aAAa,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC,iBAAiB;IAC/C,MAAM,CAAC,aAAa,CAAC,GAAG,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IACrC,MAAM,CAAC,aAAa,CAAC,wBAAgB,EAAE,EAAE,CAAC,CAAC;IAC3C,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC;AACtC,CAAC;AAED;;;;GAIG;AACH,SAAgB,aAAa;IAC3B,iCAAiC;IACjC,OAAO,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,yBAAyB,CAAC,CAAC;AAC/E,CAAC;AAED,SAAgB,aAAa;IAC3B,IAAI,CAAC;QACH,OAAO,EAAE,CAAC,YAAY,CAAC,aAAa,EAAE,CAAC,CAAC;IAC1C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Who is already serving this project's console — `.ia-qa/ui.lock`.
|
|
3
|
+
*
|
|
4
|
+
* The console is opened by clicking something, and people click twice. Without
|
|
5
|
+
* this, the second click either fails on a port collision or starts a *second*
|
|
6
|
+
* server with a *different* token, leaving an orphan process and a browser tab
|
|
7
|
+
* that will 401 the moment the first one is closed. Neither is explicable to
|
|
8
|
+
* the person who clicked.
|
|
9
|
+
*
|
|
10
|
+
* So the lock holds the port and the token, and a launcher that finds a live
|
|
11
|
+
* console simply opens it. The file is a hint, never an authority: it is
|
|
12
|
+
* verified by an actual request before being believed (a lock left behind by a
|
|
13
|
+
* killed process is the normal case, not the exception), and it lives next to
|
|
14
|
+
* the contracts because that is what it is scoped to — one console per project
|
|
15
|
+
* directory, not one per machine.
|
|
16
|
+
*
|
|
17
|
+
* It holds a session token, so it is a file to gitignore, like `_shots/`.
|
|
18
|
+
*/
|
|
19
|
+
export declare const UI_LOCK_FILENAME = "ui.lock";
|
|
20
|
+
export interface UiLock {
|
|
21
|
+
port: number;
|
|
22
|
+
token: string;
|
|
23
|
+
pid: number;
|
|
24
|
+
startedAt: string;
|
|
25
|
+
}
|
|
26
|
+
export declare function lockPath(cwd?: string): string;
|
|
27
|
+
export declare function writeLock(lock: UiLock): void;
|
|
28
|
+
export declare function clearLock(): void;
|
|
29
|
+
export declare function readLock(): UiLock | null;
|
|
30
|
+
/**
|
|
31
|
+
* Is the console named by the lock actually answering?
|
|
32
|
+
*
|
|
33
|
+
* Checked with a real request rather than by testing whether the pid exists: a
|
|
34
|
+
* pid can be reused, and what matters is not that *something* is alive but that
|
|
35
|
+
* it is a console that accepts this token. Short timeout — this runs before a
|
|
36
|
+
* human sees anything, so a stale lock must not cost them a visible pause.
|
|
37
|
+
*/
|
|
38
|
+
export declare function probe(lock: UiLock, timeoutMs?: number): Promise<boolean>;
|
|
39
|
+
/** The URL of a console already serving this project, or null. */
|
|
40
|
+
export declare function findRunningConsole(): Promise<string | null>;
|
package/dist/ui/lock.js
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.UI_LOCK_FILENAME = void 0;
|
|
37
|
+
exports.lockPath = lockPath;
|
|
38
|
+
exports.writeLock = writeLock;
|
|
39
|
+
exports.clearLock = clearLock;
|
|
40
|
+
exports.readLock = readLock;
|
|
41
|
+
exports.probe = probe;
|
|
42
|
+
exports.findRunningConsole = findRunningConsole;
|
|
43
|
+
const fs = __importStar(require("fs"));
|
|
44
|
+
const http = __importStar(require("http"));
|
|
45
|
+
const path = __importStar(require("path"));
|
|
46
|
+
const config_1 = require("../config");
|
|
47
|
+
/**
|
|
48
|
+
* Who is already serving this project's console — `.ia-qa/ui.lock`.
|
|
49
|
+
*
|
|
50
|
+
* The console is opened by clicking something, and people click twice. Without
|
|
51
|
+
* this, the second click either fails on a port collision or starts a *second*
|
|
52
|
+
* server with a *different* token, leaving an orphan process and a browser tab
|
|
53
|
+
* that will 401 the moment the first one is closed. Neither is explicable to
|
|
54
|
+
* the person who clicked.
|
|
55
|
+
*
|
|
56
|
+
* So the lock holds the port and the token, and a launcher that finds a live
|
|
57
|
+
* console simply opens it. The file is a hint, never an authority: it is
|
|
58
|
+
* verified by an actual request before being believed (a lock left behind by a
|
|
59
|
+
* killed process is the normal case, not the exception), and it lives next to
|
|
60
|
+
* the contracts because that is what it is scoped to — one console per project
|
|
61
|
+
* directory, not one per machine.
|
|
62
|
+
*
|
|
63
|
+
* It holds a session token, so it is a file to gitignore, like `_shots/`.
|
|
64
|
+
*/
|
|
65
|
+
exports.UI_LOCK_FILENAME = 'ui.lock';
|
|
66
|
+
function lockPath(cwd = (0, config_1.baseDir)()) {
|
|
67
|
+
return path.join(cwd, config_1.CONFIG_DIR, exports.UI_LOCK_FILENAME);
|
|
68
|
+
}
|
|
69
|
+
function writeLock(lock) {
|
|
70
|
+
try {
|
|
71
|
+
fs.mkdirSync(path.dirname(lockPath()), { recursive: true });
|
|
72
|
+
// 0600: it carries the session token, and the console it unlocks can edit
|
|
73
|
+
// test files. On Windows the mode is advisory, which is why the token is
|
|
74
|
+
// random per process and dies with it rather than being a stored secret.
|
|
75
|
+
fs.writeFileSync(lockPath(), JSON.stringify(lock), { encoding: 'utf8', mode: 0o600 });
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
/* a console that cannot write its lock still works; it just cannot be found */
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
function clearLock() {
|
|
82
|
+
try {
|
|
83
|
+
fs.unlinkSync(lockPath());
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
/* already gone */
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
function readLock() {
|
|
90
|
+
try {
|
|
91
|
+
const raw = JSON.parse(fs.readFileSync(lockPath(), 'utf8'));
|
|
92
|
+
if (!Number.isInteger(raw.port) || typeof raw.token !== 'string' || raw.token === '')
|
|
93
|
+
return null;
|
|
94
|
+
return raw;
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Is the console named by the lock actually answering?
|
|
102
|
+
*
|
|
103
|
+
* Checked with a real request rather than by testing whether the pid exists: a
|
|
104
|
+
* pid can be reused, and what matters is not that *something* is alive but that
|
|
105
|
+
* it is a console that accepts this token. Short timeout — this runs before a
|
|
106
|
+
* human sees anything, so a stale lock must not cost them a visible pause.
|
|
107
|
+
*/
|
|
108
|
+
function probe(lock, timeoutMs = 700) {
|
|
109
|
+
return new Promise((resolve) => {
|
|
110
|
+
const req = http.get({
|
|
111
|
+
host: '127.0.0.1',
|
|
112
|
+
port: lock.port,
|
|
113
|
+
path: `/api/state?t=${encodeURIComponent(lock.token)}`,
|
|
114
|
+
timeout: timeoutMs,
|
|
115
|
+
headers: { Host: `127.0.0.1:${lock.port}` },
|
|
116
|
+
}, (res) => {
|
|
117
|
+
res.resume();
|
|
118
|
+
resolve(res.statusCode === 200);
|
|
119
|
+
});
|
|
120
|
+
req.on('timeout', () => {
|
|
121
|
+
req.destroy();
|
|
122
|
+
resolve(false);
|
|
123
|
+
});
|
|
124
|
+
req.on('error', () => resolve(false));
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
/** The URL of a console already serving this project, or null. */
|
|
128
|
+
async function findRunningConsole() {
|
|
129
|
+
const lock = readLock();
|
|
130
|
+
if (!lock)
|
|
131
|
+
return null;
|
|
132
|
+
if (!(await probe(lock))) {
|
|
133
|
+
clearLock();
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
return `http://127.0.0.1:${lock.port}/?t=${lock.token}`;
|
|
137
|
+
}
|
|
138
|
+
//# sourceMappingURL=lock.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lock.js","sourceRoot":"","sources":["../../src/ui/lock.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function renderConsoleHtml(token: string): string;
|