@extenshi/cli 0.20.0 → 0.22.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/README.md +4 -0
- package/dist/amo-source-detect.d.ts +18 -0
- package/dist/amo-source-detect.d.ts.map +1 -0
- package/dist/amo-source-detect.js +41 -0
- package/dist/amo-source-detect.js.map +1 -0
- package/dist/amo-source.d.ts +41 -0
- package/dist/amo-source.d.ts.map +1 -0
- package/dist/amo-source.js +240 -0
- package/dist/amo-source.js.map +1 -0
- package/dist/amowebstore-md.d.ts +38 -0
- package/dist/amowebstore-md.d.ts.map +1 -0
- package/dist/amowebstore-md.js +118 -0
- package/dist/amowebstore-md.js.map +1 -0
- package/dist/auto-evidence.d.ts +100 -0
- package/dist/auto-evidence.d.ts.map +1 -0
- package/dist/auto-evidence.js +197 -0
- package/dist/auto-evidence.js.map +1 -0
- package/dist/cli.js +320 -8
- package/dist/cli.js.map +1 -1
- package/dist/edgewebstore-md.d.ts +26 -0
- package/dist/edgewebstore-md.d.ts.map +1 -0
- package/dist/edgewebstore-md.js +95 -0
- package/dist/edgewebstore-md.js.map +1 -0
- package/dist/evidence-ingest.d.ts +60 -0
- package/dist/evidence-ingest.d.ts.map +1 -0
- package/dist/evidence-ingest.js +141 -0
- package/dist/evidence-ingest.js.map +1 -0
- package/dist/expected-error.d.ts +9 -0
- package/dist/expected-error.d.ts.map +1 -1
- package/dist/expected-error.js +16 -1
- package/dist/expected-error.js.map +1 -1
- package/dist/firefox-rdp.d.ts +55 -0
- package/dist/firefox-rdp.d.ts.map +1 -0
- package/dist/firefox-rdp.js +203 -0
- package/dist/firefox-rdp.js.map +1 -0
- package/dist/gecko-id.d.ts +55 -0
- package/dist/gecko-id.d.ts.map +1 -0
- package/dist/gecko-id.js +132 -0
- package/dist/gecko-id.js.map +1 -0
- package/dist/generate-listing.d.ts +19 -8
- package/dist/generate-listing.d.ts.map +1 -1
- package/dist/generate-listing.js +66 -18
- package/dist/generate-listing.js.map +1 -1
- package/dist/icon.d.ts +18 -0
- package/dist/icon.d.ts.map +1 -1
- package/dist/icon.js +29 -20
- package/dist/icon.js.map +1 -1
- package/dist/json-ld.d.ts +89 -0
- package/dist/json-ld.d.ts.map +1 -0
- package/dist/json-ld.js +118 -0
- package/dist/json-ld.js.map +1 -0
- package/dist/landing-page.d.ts +123 -0
- package/dist/landing-page.d.ts.map +1 -0
- package/dist/landing-page.js +405 -0
- package/dist/landing-page.js.map +1 -0
- package/dist/media.d.ts +42 -0
- package/dist/media.d.ts.map +1 -0
- package/dist/media.js +96 -0
- package/dist/media.js.map +1 -0
- package/dist/package.d.ts +163 -0
- package/dist/package.d.ts.map +1 -0
- package/dist/package.js +500 -0
- package/dist/package.js.map +1 -0
- package/dist/page.d.ts +141 -0
- package/dist/page.d.ts.map +1 -0
- package/dist/page.js +227 -0
- package/dist/page.js.map +1 -0
- package/dist/pay-contract.d.ts +20 -13
- package/dist/pay-contract.d.ts.map +1 -1
- package/dist/pay-contract.js +20 -11
- package/dist/pay-contract.js.map +1 -1
- package/dist/pay-test-cycle.d.ts +40 -0
- package/dist/pay-test-cycle.d.ts.map +1 -0
- package/dist/pay-test-cycle.js +207 -0
- package/dist/pay-test-cycle.js.map +1 -0
- package/dist/pay.d.ts +1 -1
- package/dist/pay.d.ts.map +1 -1
- package/dist/pay.js +1 -0
- package/dist/pay.js.map +1 -1
- package/dist/project.d.ts +39 -1
- package/dist/project.d.ts.map +1 -1
- package/dist/project.js +231 -20
- package/dist/project.js.map +1 -1
- package/dist/publish.d.ts +33 -0
- package/dist/publish.d.ts.map +1 -1
- package/dist/publish.js +113 -2
- package/dist/publish.js.map +1 -1
- package/dist/review-checks.d.ts +13 -0
- package/dist/review-checks.d.ts.map +1 -1
- package/dist/review-checks.js.map +1 -1
- package/dist/review-risk.d.ts +10 -1
- package/dist/review-risk.d.ts.map +1 -1
- package/dist/review-risk.js +1 -0
- package/dist/review-risk.js.map +1 -1
- package/dist/scan.d.ts +3 -2
- package/dist/scan.d.ts.map +1 -1
- package/dist/scan.js +4 -2
- package/dist/scan.js.map +1 -1
- package/dist/screenshots-scenes.d.ts +1383 -0
- package/dist/screenshots-scenes.d.ts.map +1 -0
- package/dist/screenshots-scenes.js +276 -0
- package/dist/screenshots-scenes.js.map +1 -0
- package/dist/screenshots.d.ts +145 -0
- package/dist/screenshots.d.ts.map +1 -0
- package/dist/screenshots.js +557 -0
- package/dist/screenshots.js.map +1 -0
- package/dist/smoke.d.ts +81 -0
- package/dist/smoke.d.ts.map +1 -0
- package/dist/smoke.js +491 -0
- package/dist/smoke.js.map +1 -0
- package/dist/store-assets.d.ts +139 -0
- package/dist/store-assets.d.ts.map +1 -0
- package/dist/store-assets.js +582 -0
- package/dist/store-assets.js.map +1 -0
- package/dist/telemetry.d.ts.map +1 -1
- package/dist/telemetry.js +3 -0
- package/dist/telemetry.js.map +1 -1
- package/dist/zip.d.ts +57 -0
- package/dist/zip.d.ts.map +1 -0
- package/dist/zip.js +134 -0
- package/dist/zip.js.map +1 -0
- package/package.json +4 -3
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EDGE.md — Microsoft Edge Add-ons listing draft (`extenshi generate-listing --store edge`).
|
|
3
|
+
*
|
|
4
|
+
* Shares the Name / Short description / Description / Single purpose headings
|
|
5
|
+
* with CHROMEWEBSTORE.md on purpose: `review-risk --listing EDGE.md` parses
|
|
6
|
+
* it through the exact same `listingDraftFromChromeWebStoreMd` heading
|
|
7
|
+
* lookup, so nothing in listing.ts needs to know EDGE.md exists.
|
|
8
|
+
*
|
|
9
|
+
* Field names, limits and step order come from Microsoft's own publish guide:
|
|
10
|
+
* https://learn.microsoft.com/en-us/microsoft-edge/extensions/publish/publish-extension
|
|
11
|
+
* (Store logo 300×300 recommended / 128×128 minimum; description 250–10,000
|
|
12
|
+
* chars; search terms ≤7 terms / ≤30 chars each / ≤21 words total; privacy
|
|
13
|
+
* policy URL and permission justifications live on the Privacy step).
|
|
14
|
+
*/
|
|
15
|
+
export const EDGE_FILENAME = 'EDGE.md';
|
|
16
|
+
export function renderEdgeMd(input) {
|
|
17
|
+
const loc = input.listing?.locales.find((l) => l.locale === input.listing?.defaultLocale) ?? input.listing?.locales[0];
|
|
18
|
+
const justifications = input.justifications ?? [];
|
|
19
|
+
const justBlock = justifications.length === 0
|
|
20
|
+
? '_No sensitive permissions need a Privacy-page justification yet._'
|
|
21
|
+
: justifications.map((j) => `### ${j.permission}\n\n${j.prompt}`).join('\n\n');
|
|
22
|
+
return `# EDGE.md
|
|
23
|
+
|
|
24
|
+
Microsoft Edge Add-ons listing draft. This is the same listing draft
|
|
25
|
+
\`review-risk --listing\` already accepts as JSON or CHROMEWEBSTORE.md — run:
|
|
26
|
+
|
|
27
|
+
\`npx @extenshi/cli@latest review-risk --listing EDGE.md\`
|
|
28
|
+
|
|
29
|
+
Do not paste this into Partner Center automatically. Fill the dashboard by hand.
|
|
30
|
+
Submission guide: https://learn.microsoft.com/en-us/microsoft-edge/extensions/publish/publish-extension
|
|
31
|
+
|
|
32
|
+
## Name
|
|
33
|
+
|
|
34
|
+
${loc?.name ?? ''}
|
|
35
|
+
|
|
36
|
+
## Short description
|
|
37
|
+
|
|
38
|
+
${loc?.shortDescription ?? ''}
|
|
39
|
+
|
|
40
|
+
## Description
|
|
41
|
+
|
|
42
|
+
${loc?.description ?? ''}
|
|
43
|
+
|
|
44
|
+
_Edge requires 250–10,000 characters per language; the manifest-derived short
|
|
45
|
+
description above is usually too short on its own — expand it or fill via
|
|
46
|
+
\`--listing\`._
|
|
47
|
+
|
|
48
|
+
## Category
|
|
49
|
+
|
|
50
|
+
_Pick one from Partner Center — required for every submission._
|
|
51
|
+
|
|
52
|
+
## Store logo
|
|
53
|
+
|
|
54
|
+
_1:1 aspect ratio, 300×300 recommended, 128×128 minimum, one per language._
|
|
55
|
+
|
|
56
|
+
## Promotional assets
|
|
57
|
+
|
|
58
|
+
- Small promotional tile (optional): 440×280
|
|
59
|
+
- Large promotional tile (optional): 1400×560
|
|
60
|
+
- Screenshots (optional, up to 6): 640×480 or 1280×800
|
|
61
|
+
|
|
62
|
+
## Search terms
|
|
63
|
+
|
|
64
|
+
_Up to 7 terms, 30 characters each, 21 words total. Optional but improves discovery._
|
|
65
|
+
|
|
66
|
+
## Support
|
|
67
|
+
|
|
68
|
+
- Website URL: _optional — must be your own site, not the store listing itself_
|
|
69
|
+
- Support contact: _email or support URL_
|
|
70
|
+
- Mature content: _No, unless the extension has mature content_
|
|
71
|
+
|
|
72
|
+
## Single purpose
|
|
73
|
+
|
|
74
|
+
${input.singlePurpose ?? loc?.shortDescription ?? loc?.name ?? ''}
|
|
75
|
+
|
|
76
|
+
## Permission justifications
|
|
77
|
+
|
|
78
|
+
${justBlock}
|
|
79
|
+
|
|
80
|
+
## Privacy policy
|
|
81
|
+
|
|
82
|
+
${input.needsPrivacyPolicy
|
|
83
|
+
? '**Required** — the manifest requests permissions Edge treats as accessing personal information. Set the Privacy Policy URL on the Privacy page.'
|
|
84
|
+
: 'Not required by the requested permissions alone — re-check if the extension collects data another way (e.g. through a remote service).'}
|
|
85
|
+
|
|
86
|
+
## Notes for certification
|
|
87
|
+
|
|
88
|
+
_Test accounts, steps to reach gated/paid features, anything a certification tester needs to exercise the extension._
|
|
89
|
+
|
|
90
|
+
## Version history
|
|
91
|
+
|
|
92
|
+
_Record each upload: version, what changed, why._
|
|
93
|
+
`;
|
|
94
|
+
}
|
|
95
|
+
//# sourceMappingURL=edgewebstore-md.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"edgewebstore-md.js","sourceRoot":"","sources":["../src/edgewebstore-md.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAIH,MAAM,CAAC,MAAM,aAAa,GAAG,SAAS,CAAA;AAEtC,MAAM,UAAU,YAAY,CAAC,KAK5B;IACA,MAAM,GAAG,GACR,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,KAAK,CAAC,OAAO,EAAE,aAAa,CAAC,IAAI,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,CAAA;IAC3G,MAAM,cAAc,GAAG,KAAK,CAAC,cAAc,IAAI,EAAE,CAAA;IACjD,MAAM,SAAS,GACd,cAAc,CAAC,MAAM,KAAK,CAAC;QAC1B,CAAC,CAAC,mEAAmE;QACrE,CAAC,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC,UAAU,OAAO,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;IAEhF,OAAO;;;;;;;;;;;;EAYN,GAAG,EAAE,IAAI,IAAI,EAAE;;;;EAIf,GAAG,EAAE,gBAAgB,IAAI,EAAE;;;;EAI3B,GAAG,EAAE,WAAW,IAAI,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAgCtB,KAAK,CAAC,aAAa,IAAI,GAAG,EAAE,gBAAgB,IAAI,GAAG,EAAE,IAAI,IAAI,EAAE;;;;EAI/D,SAAS;;;;EAKV,KAAK,CAAC,kBAAkB;QACvB,CAAC,CAAC,iJAAiJ;QACnJ,CAAC,CAAC,wIACJ;;;;;;;;;CASC,CAAA;AACD,CAAC"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `extenshi evidence ingest <zip|extenshi-packages.json>` — post CI evidence from
|
|
3
|
+
* GitHub Actions, the same request tools/extenshi-evidence-action makes.
|
|
4
|
+
*
|
|
5
|
+
* Why the CLI carries it: that action lives in the platform monorepo, which is
|
|
6
|
+
* private, and the public mirror does not publish it — `uses:` of it 404s in a
|
|
7
|
+
* developer's repository. The CLI is public on npm, so the scaffold's evidence
|
|
8
|
+
* workflow calls this instead. The payload, idempotency key and signature are
|
|
9
|
+
* the action's byte for byte; evidence-ingest.test.ts runs the action's
|
|
10
|
+
* main.mjs next to this function against one local server and compares.
|
|
11
|
+
*
|
|
12
|
+
* Nothing here can mint trust: the server verifies through the GitHub API that
|
|
13
|
+
* the run exists in the project's bound repository, at this commit, with this
|
|
14
|
+
* conclusion, and has COMPLETED — so this must run from a `workflow_run`
|
|
15
|
+
* follow-up, not inside the run it attests. Source `ci` is written only there.
|
|
16
|
+
*/
|
|
17
|
+
import type { Command } from 'commander';
|
|
18
|
+
/** RFC 4122 §4.3 uuidv5 — the action's derivation, so retries replay the same record. */
|
|
19
|
+
export declare function uuidv5(name: string, namespace?: string): string;
|
|
20
|
+
export interface IngestInput {
|
|
21
|
+
projectId: string;
|
|
22
|
+
artifactBytes: Uint8Array;
|
|
23
|
+
secret: string;
|
|
24
|
+
repository: string;
|
|
25
|
+
commit: string;
|
|
26
|
+
runId: number;
|
|
27
|
+
runAttempt: number;
|
|
28
|
+
kind: string;
|
|
29
|
+
browser: string;
|
|
30
|
+
locale?: string;
|
|
31
|
+
conclusion: string;
|
|
32
|
+
serverUrl?: string;
|
|
33
|
+
}
|
|
34
|
+
/** The exact body + headers the action sends. `timestamp` is injectable for tests. */
|
|
35
|
+
export declare function buildIngestRequest(input: IngestInput, timestamp?: string): {
|
|
36
|
+
artifactDigest: string;
|
|
37
|
+
body: string;
|
|
38
|
+
headers: {
|
|
39
|
+
'content-type': string;
|
|
40
|
+
'x-extenshi-timestamp': string;
|
|
41
|
+
'x-extenshi-signature': string;
|
|
42
|
+
};
|
|
43
|
+
};
|
|
44
|
+
export interface IngestCommandOptions {
|
|
45
|
+
project: string;
|
|
46
|
+
kind: string;
|
|
47
|
+
browser: string;
|
|
48
|
+
locale?: string;
|
|
49
|
+
conclusion?: string;
|
|
50
|
+
runId?: string;
|
|
51
|
+
commit?: string;
|
|
52
|
+
bffUrl?: string;
|
|
53
|
+
}
|
|
54
|
+
export declare function runEvidenceIngest(artifact: string, options: IngestCommandOptions, env?: NodeJS.ProcessEnv): Promise<{
|
|
55
|
+
artifactDigest: string;
|
|
56
|
+
status: number;
|
|
57
|
+
answer: string;
|
|
58
|
+
}>;
|
|
59
|
+
export declare function registerEvidenceIngest(program: Command, run: (name: string, fn: () => Promise<void>) => Promise<void>): void;
|
|
60
|
+
//# sourceMappingURL=evidence-ingest.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"evidence-ingest.d.ts","sourceRoot":"","sources":["../src/evidence-ingest.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAIH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAiBxC,yFAAyF;AACzF,wBAAgB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,SAAgB,GAAG,MAAM,CAMtE;AAED,MAAM,WAAW,WAAW;IAC3B,SAAS,EAAE,MAAM,CAAA;IACjB,aAAa,EAAE,UAAU,CAAA;IACzB,MAAM,EAAE,MAAM,CAAA;IACd,UAAU,EAAE,MAAM,CAAA;IAClB,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,MAAM,CAAA;IACb,UAAU,EAAE,MAAM,CAAA;IAClB,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,UAAU,EAAE,MAAM,CAAA;IAClB,SAAS,CAAC,EAAE,MAAM,CAAA;CAClB;AAED,sFAAsF;AACtF,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,WAAW,EAAE,SAAS,SAAwB;;;;;;;;EAiCvF;AAED,MAAM,WAAW,oBAAoB;IACpC,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,MAAM,CAAC,EAAE,MAAM,CAAA;CACf;AAED,wBAAsB,iBAAiB,CACtC,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,oBAAoB,EAC7B,GAAG,GAAE,MAAM,CAAC,UAAwB,GAClC,OAAO,CAAC;IAAE,cAAc,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAkDrE;AAED,wBAAgB,sBAAsB,CACrC,OAAO,EAAE,OAAO,EAChB,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,QA4B7D"}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `extenshi evidence ingest <zip|extenshi-packages.json>` — post CI evidence from
|
|
3
|
+
* GitHub Actions, the same request tools/extenshi-evidence-action makes.
|
|
4
|
+
*
|
|
5
|
+
* Why the CLI carries it: that action lives in the platform monorepo, which is
|
|
6
|
+
* private, and the public mirror does not publish it — `uses:` of it 404s in a
|
|
7
|
+
* developer's repository. The CLI is public on npm, so the scaffold's evidence
|
|
8
|
+
* workflow calls this instead. The payload, idempotency key and signature are
|
|
9
|
+
* the action's byte for byte; evidence-ingest.test.ts runs the action's
|
|
10
|
+
* main.mjs next to this function against one local server and compares.
|
|
11
|
+
*
|
|
12
|
+
* Nothing here can mint trust: the server verifies through the GitHub API that
|
|
13
|
+
* the run exists in the project's bound repository, at this commit, with this
|
|
14
|
+
* conclusion, and has COMPLETED — so this must run from a `workflow_run`
|
|
15
|
+
* follow-up, not inside the run it attests. Source `ci` is written only there.
|
|
16
|
+
*/
|
|
17
|
+
import { createHash, createHmac } from 'node:crypto';
|
|
18
|
+
import fs from 'node:fs';
|
|
19
|
+
import { InvalidInputError } from './expected-error.js';
|
|
20
|
+
import { resolvePackagedArtifact } from './package.js';
|
|
21
|
+
const CI_KINDS = ['tests', 'scan', 'installed'];
|
|
22
|
+
const CI_BROWSERS = ['chrome', 'firefox', 'edge'];
|
|
23
|
+
const CI_CONCLUSIONS = [
|
|
24
|
+
'success',
|
|
25
|
+
'failure',
|
|
26
|
+
'cancelled',
|
|
27
|
+
'timed_out',
|
|
28
|
+
'skipped',
|
|
29
|
+
'neutral',
|
|
30
|
+
'startup_failure',
|
|
31
|
+
];
|
|
32
|
+
const URL_NAMESPACE = '6ba7b810-9dad-11d1-80b4-00c04fd430c8';
|
|
33
|
+
/** RFC 4122 §4.3 uuidv5 — the action's derivation, so retries replay the same record. */
|
|
34
|
+
export function uuidv5(name, namespace = URL_NAMESPACE) {
|
|
35
|
+
const hash = createHash('sha1').update(namespace.replace(/-/g, ''), 'hex').update(name).digest();
|
|
36
|
+
hash[6] = (hash[6] & 0x0f) | 0x50;
|
|
37
|
+
hash[8] = (hash[8] & 0x3f) | 0x80;
|
|
38
|
+
const hex = hash.toString('hex');
|
|
39
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20, 32)}`;
|
|
40
|
+
}
|
|
41
|
+
/** The exact body + headers the action sends. `timestamp` is injectable for tests. */
|
|
42
|
+
export function buildIngestRequest(input, timestamp = Date.now().toString()) {
|
|
43
|
+
const artifactDigest = createHash('sha256').update(input.artifactBytes).digest('hex');
|
|
44
|
+
const locale = input.locale ?? '';
|
|
45
|
+
const idempotencyKey = uuidv5(`${input.repository}:${input.runId}:${input.runAttempt}:${input.kind}:${input.browser}:${locale}:${artifactDigest}`);
|
|
46
|
+
const serverUrl = input.serverUrl ?? 'https://github.com';
|
|
47
|
+
const body = JSON.stringify({
|
|
48
|
+
projectId: input.projectId,
|
|
49
|
+
idempotencyKey,
|
|
50
|
+
repo: input.repository,
|
|
51
|
+
commit: input.commit,
|
|
52
|
+
artifactDigest,
|
|
53
|
+
kind: input.kind,
|
|
54
|
+
browser: input.browser,
|
|
55
|
+
...(locale ? { locale } : {}),
|
|
56
|
+
conclusion: input.conclusion,
|
|
57
|
+
runId: input.runId,
|
|
58
|
+
runUrl: `${serverUrl}/${input.repository}/actions/runs/${input.runId}`,
|
|
59
|
+
reports: [],
|
|
60
|
+
});
|
|
61
|
+
// The server stores only sha256(secret) and verifies with that hash.
|
|
62
|
+
const key = createHash('sha256').update(input.secret, 'utf8').digest('hex');
|
|
63
|
+
const signature = createHmac('sha256', key).update(`${timestamp}.${body}`).digest('hex');
|
|
64
|
+
return {
|
|
65
|
+
artifactDigest,
|
|
66
|
+
body,
|
|
67
|
+
headers: {
|
|
68
|
+
'content-type': 'application/json',
|
|
69
|
+
'x-extenshi-timestamp': timestamp,
|
|
70
|
+
'x-extenshi-signature': `sha256=${signature}`,
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
export async function runEvidenceIngest(artifact, options, env = process.env) {
|
|
75
|
+
if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(options.project))
|
|
76
|
+
throw new InvalidInputError('--project must be the Dojo project UUID');
|
|
77
|
+
if (!CI_KINDS.includes(options.kind))
|
|
78
|
+
throw new InvalidInputError(`--kind must be one of: ${CI_KINDS.join(', ')} (CI attests the package it built)`);
|
|
79
|
+
if (!CI_BROWSERS.includes(options.browser))
|
|
80
|
+
throw new InvalidInputError(`--browser must be one of: ${CI_BROWSERS.join(', ')}`);
|
|
81
|
+
const conclusion = options.conclusion ?? 'success';
|
|
82
|
+
if (!CI_CONCLUSIONS.includes(conclusion))
|
|
83
|
+
throw new InvalidInputError(`--conclusion must be one of: ${CI_CONCLUSIONS.join(', ')}`);
|
|
84
|
+
// A flag would put the secret in the process list and shell history.
|
|
85
|
+
const secret = env.EXTENSHI_EVIDENCE_SECRET;
|
|
86
|
+
if (!secret)
|
|
87
|
+
throw new InvalidInputError('Set EXTENSHI_EVIDENCE_SECRET (the project CI ingest secret) in the environment.');
|
|
88
|
+
const repository = env.GITHUB_REPOSITORY;
|
|
89
|
+
const commit = options.commit || env.GITHUB_SHA;
|
|
90
|
+
const runId = Number(options.runId || env.GITHUB_RUN_ID);
|
|
91
|
+
if (!repository || !commit || !Number.isInteger(runId) || runId <= 0)
|
|
92
|
+
throw new InvalidInputError('GITHUB_REPOSITORY, the commit and the run id are required — run inside GitHub Actions, and from a workflow_run follow-up pass --run-id/--commit of the completed run.');
|
|
93
|
+
const { artifactPath } = resolvePackagedArtifact(artifact, options.browser);
|
|
94
|
+
const request = buildIngestRequest({
|
|
95
|
+
projectId: options.project,
|
|
96
|
+
artifactBytes: fs.readFileSync(artifactPath),
|
|
97
|
+
secret,
|
|
98
|
+
repository,
|
|
99
|
+
commit,
|
|
100
|
+
runId,
|
|
101
|
+
runAttempt: Number(env.GITHUB_RUN_ATTEMPT || '1'),
|
|
102
|
+
kind: options.kind,
|
|
103
|
+
browser: options.browser,
|
|
104
|
+
locale: options.locale,
|
|
105
|
+
conclusion,
|
|
106
|
+
serverUrl: env.GITHUB_SERVER_URL,
|
|
107
|
+
});
|
|
108
|
+
const bffUrl = (options.bffUrl ?? env.EXTENSHI_BFF_URL ?? 'https://bff.extenshi.io').replace(/\/$/, '');
|
|
109
|
+
const response = await fetch(`${bffUrl}/projects/${options.project}/evidence/ingest`, {
|
|
110
|
+
method: 'POST',
|
|
111
|
+
headers: request.headers,
|
|
112
|
+
body: request.body,
|
|
113
|
+
signal: AbortSignal.timeout(30_000),
|
|
114
|
+
});
|
|
115
|
+
const answer = await response.text().catch(() => '');
|
|
116
|
+
if (!response.ok)
|
|
117
|
+
throw new Error(`ingest rejected (${response.status}): ${answer.slice(0, 500)}`);
|
|
118
|
+
return { artifactDigest: request.artifactDigest, status: response.status, answer };
|
|
119
|
+
}
|
|
120
|
+
export function registerEvidenceIngest(program, run) {
|
|
121
|
+
const evidence = program.commands.find((c) => c.name() === 'evidence') ?? program.command('evidence');
|
|
122
|
+
evidence
|
|
123
|
+
.command('ingest <artifact>')
|
|
124
|
+
.description('CI only: attest a COMPLETED GitHub Actions run for this package (zip or extenshi-packages.json) — the ' +
|
|
125
|
+
'tools/extenshi-evidence-action request, signed with EXTENSHI_EVIDENCE_SECRET. Run it from a workflow_run follow-up.')
|
|
126
|
+
.requiredOption('--project <id>', 'Dojo project UUID')
|
|
127
|
+
.requiredOption('--kind <kind>', 'tests | scan | installed')
|
|
128
|
+
.requiredOption('--browser <browser>', 'chrome | firefox | edge')
|
|
129
|
+
.option('--locale <locale>', 'Optional locale the evidence is scoped to')
|
|
130
|
+
.option('--conclusion <conclusion>', 'Run conclusion to attest (the server cross-checks it)', 'success')
|
|
131
|
+
.option('--run-id <id>', 'The completed run to attest (default GITHUB_RUN_ID — rejected while in progress)')
|
|
132
|
+
.option('--commit <sha>', 'The built commit (default GITHUB_SHA)')
|
|
133
|
+
.option('--bff-url <url>', 'Extenshi BFF base URL', undefined)
|
|
134
|
+
.action(async (artifact, options) => {
|
|
135
|
+
await run('evidence ingest', async () => {
|
|
136
|
+
const result = await runEvidenceIngest(artifact, options);
|
|
137
|
+
console.log(`extenshi-evidence: recorded ${options.kind} for ${result.artifactDigest.slice(0, 12)}… — ${result.answer.slice(0, 300)}`);
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
//# sourceMappingURL=evidence-ingest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"evidence-ingest.js","sourceRoot":"","sources":["../src/evidence-ingest.ts"],"names":[],"mappings":"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"}
|
package/dist/expected-error.d.ts
CHANGED
|
@@ -24,6 +24,15 @@
|
|
|
24
24
|
* command surfaces to the user, never code exceptions.
|
|
25
25
|
*/
|
|
26
26
|
export declare const EXPECTED_ERROR_KINDS: Set<string>;
|
|
27
|
+
/**
|
|
28
|
+
* A problem with a file or flag the user supplied (a malformed config, a
|
|
29
|
+
* refused logo, a non-https URL) — `invalid_input`, reported with a non-zero
|
|
30
|
+
* exit but never as an exception. Recognised by `name` in telemetry.ts's
|
|
31
|
+
* classifier, so that module needs no import from here.
|
|
32
|
+
*/
|
|
33
|
+
export declare class InvalidInputError extends Error {
|
|
34
|
+
name: string;
|
|
35
|
+
}
|
|
27
36
|
/**
|
|
28
37
|
* True when a thrown error is an expected, user-facing condition rather than a
|
|
29
38
|
* bug worth an exception report. `kind` defaults to classifying `err`; pass one
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"expected-error.d.ts","sourceRoot":"","sources":["../src/expected-error.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAIH;;;;;;;;GAQG;AACH,eAAO,MAAM,oBAAoB,
|
|
1
|
+
{"version":3,"file":"expected-error.d.ts","sourceRoot":"","sources":["../src/expected-error.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAIH;;;;;;;;GAQG;AACH,eAAO,MAAM,oBAAoB,aAM/B,CAAA;AAEF;;;;;GAKG;AACH,qBAAa,iBAAkB,SAAQ,KAAK;IAC3C,IAAI,SAAsB;CAC1B;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,GAAE,MAA2B,GAAG,OAAO,CAExF"}
|
package/dist/expected-error.js
CHANGED
|
@@ -24,7 +24,22 @@ import { classifyError } from './telemetry.js';
|
|
|
24
24
|
* `rate_limit` — asked to slow down. All four are actionable conditions the
|
|
25
25
|
* command surfaces to the user, never code exceptions.
|
|
26
26
|
*/
|
|
27
|
-
export const EXPECTED_ERROR_KINDS = new Set([
|
|
27
|
+
export const EXPECTED_ERROR_KINDS = new Set([
|
|
28
|
+
'invalid_artifact',
|
|
29
|
+
'invalid_input',
|
|
30
|
+
'auth',
|
|
31
|
+
'quota',
|
|
32
|
+
'rate_limit',
|
|
33
|
+
]);
|
|
34
|
+
/**
|
|
35
|
+
* A problem with a file or flag the user supplied (a malformed config, a
|
|
36
|
+
* refused logo, a non-https URL) — `invalid_input`, reported with a non-zero
|
|
37
|
+
* exit but never as an exception. Recognised by `name` in telemetry.ts's
|
|
38
|
+
* classifier, so that module needs no import from here.
|
|
39
|
+
*/
|
|
40
|
+
export class InvalidInputError extends Error {
|
|
41
|
+
name = 'InvalidInputError';
|
|
42
|
+
}
|
|
28
43
|
/**
|
|
29
44
|
* True when a thrown error is an expected, user-facing condition rather than a
|
|
30
45
|
* bug worth an exception report. `kind` defaults to classifying `err`; pass one
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"expected-error.js","sourceRoot":"","sources":["../src/expected-error.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAE9C;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,IAAI,GAAG,CAAC,
|
|
1
|
+
{"version":3,"file":"expected-error.js","sourceRoot":"","sources":["../src/expected-error.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAE9C;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,IAAI,GAAG,CAAC;IAC3C,kBAAkB;IAClB,eAAe;IACf,MAAM;IACN,OAAO;IACP,YAAY;CACZ,CAAC,CAAA;AAEF;;;;;GAKG;AACH,MAAM,OAAO,iBAAkB,SAAQ,KAAK;IAC3C,IAAI,GAAG,mBAAmB,CAAA;CAC1B;AAED;;;;;GAKG;AACH,MAAM,UAAU,eAAe,CAAC,GAAY,EAAE,OAAe,aAAa,CAAC,GAAG,CAAC;IAC9E,OAAO,oBAAoB,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;AACtC,CAAC"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal Firefox Remote Debugging Protocol client — just enough to install a
|
|
3
|
+
* package as a TEMPORARY add-on and watch it, the way `web-ext run` and
|
|
4
|
+
* about:debugging do. No dependency: RDP is length-prefixed JSON over TCP
|
|
5
|
+
* (`<byteLength>:<json>`), and every request is answered by one packet `from`
|
|
6
|
+
* the actor it was sent `to`.
|
|
7
|
+
*
|
|
8
|
+
* Why RDP and not Playwright/Marionette: Playwright's Firefox cannot install
|
|
9
|
+
* add-ons, and Marionette cannot script privileged moz-extension pages — but
|
|
10
|
+
* the devtools server can install the exact .zip, report its manifest warnings
|
|
11
|
+
* and background status, and stream every error the add-on's documents raise.
|
|
12
|
+
*/
|
|
13
|
+
import { type ChildProcess } from 'node:child_process';
|
|
14
|
+
type Packet = Record<string, any>;
|
|
15
|
+
export declare class RdpClient {
|
|
16
|
+
private readonly socket;
|
|
17
|
+
private buffer;
|
|
18
|
+
private pending;
|
|
19
|
+
private listeners;
|
|
20
|
+
private constructor();
|
|
21
|
+
/** Connect, retrying while Firefox starts; resolves with the client and the server's hello packet. */
|
|
22
|
+
static connect(port: number, timeoutMs: number): Promise<{
|
|
23
|
+
client: RdpClient;
|
|
24
|
+
hello: Packet;
|
|
25
|
+
}>;
|
|
26
|
+
private onData;
|
|
27
|
+
private dispatch;
|
|
28
|
+
on(listener: (p: Packet) => void): void;
|
|
29
|
+
/** The next unsolicited packet matching `predicate`. */
|
|
30
|
+
next(predicate: (p: Packet) => boolean, timeoutMs: number): Promise<Packet>;
|
|
31
|
+
request(to: string, type: string, extra?: Packet, timeoutMs?: number): Promise<Packet>;
|
|
32
|
+
close(): void;
|
|
33
|
+
}
|
|
34
|
+
/** A Firefox binary, or null. Order: explicit path, FIREFOX_BIN, the usual install locations, PATH. */
|
|
35
|
+
export declare function findFirefox(explicit?: string, env?: NodeJS.ProcessEnv): string | null;
|
|
36
|
+
export interface LaunchedFirefox {
|
|
37
|
+
client: RdpClient;
|
|
38
|
+
hello: Packet;
|
|
39
|
+
process: ChildProcess;
|
|
40
|
+
profile: string;
|
|
41
|
+
close(): Promise<void>;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Start a disposable headless Firefox with the devtools server on a free port.
|
|
45
|
+
* `uuids` pins the moz-extension:// host per add-on id, so extension pages have
|
|
46
|
+
* a known URL before the add-on is installed.
|
|
47
|
+
*/
|
|
48
|
+
export declare function launchFirefox(options: {
|
|
49
|
+
binary: string;
|
|
50
|
+
uuids: Record<string, string>;
|
|
51
|
+
headless: boolean;
|
|
52
|
+
timeoutMs: number;
|
|
53
|
+
}): Promise<LaunchedFirefox>;
|
|
54
|
+
export {};
|
|
55
|
+
//# sourceMappingURL=firefox-rdp.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"firefox-rdp.d.ts","sourceRoot":"","sources":["../src/firefox-rdp.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,KAAK,YAAY,EAAS,MAAM,oBAAoB,CAAA;AAM7D,KAAK,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;AAEjC,qBAAa,SAAS;IAKD,OAAO,CAAC,QAAQ,CAAC,MAAM;IAJ3C,OAAO,CAAC,MAAM,CAAkB;IAChC,OAAO,CAAC,OAAO,CAAyF;IACxG,OAAO,CAAC,SAAS,CAAiC;IAElD,OAAO;IAOP,sGAAsG;WACzF,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,MAAM,EAAE,SAAS,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;IAqBpG,OAAO,CAAC,MAAM;IAmBd,OAAO,CAAC,QAAQ;IAWhB,EAAE,CAAC,QAAQ,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI;IAIvC,wDAAwD;IACxD,IAAI,CAAC,SAAS,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,OAAO,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAa3E,OAAO,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,GAAE,MAAW,EAAE,SAAS,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC;IAoB1F,KAAK,IAAI,IAAI;CAGb;AAED,uGAAuG;AACvG,wBAAgB,WAAW,CAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,GAAG,IAAI,CAgBlG;AAeD,MAAM,WAAW,eAAe;IAC/B,MAAM,EAAE,SAAS,CAAA;IACjB,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,YAAY,CAAA;IACrB,OAAO,EAAE,MAAM,CAAA;IACf,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;CACtB;AAED;;;;GAIG;AACH,wBAAsB,aAAa,CAAC,OAAO,EAAE;IAC5C,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC7B,QAAQ,EAAE,OAAO,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;CACjB,GAAG,OAAO,CAAC,eAAe,CAAC,CAiD3B"}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal Firefox Remote Debugging Protocol client — just enough to install a
|
|
3
|
+
* package as a TEMPORARY add-on and watch it, the way `web-ext run` and
|
|
4
|
+
* about:debugging do. No dependency: RDP is length-prefixed JSON over TCP
|
|
5
|
+
* (`<byteLength>:<json>`), and every request is answered by one packet `from`
|
|
6
|
+
* the actor it was sent `to`.
|
|
7
|
+
*
|
|
8
|
+
* Why RDP and not Playwright/Marionette: Playwright's Firefox cannot install
|
|
9
|
+
* add-ons, and Marionette cannot script privileged moz-extension pages — but
|
|
10
|
+
* the devtools server can install the exact .zip, report its manifest warnings
|
|
11
|
+
* and background status, and stream every error the add-on's documents raise.
|
|
12
|
+
*/
|
|
13
|
+
import { spawn } from 'node:child_process';
|
|
14
|
+
import fs from 'node:fs';
|
|
15
|
+
import net from 'node:net';
|
|
16
|
+
import os from 'node:os';
|
|
17
|
+
import path from 'node:path';
|
|
18
|
+
export class RdpClient {
|
|
19
|
+
socket;
|
|
20
|
+
buffer = Buffer.alloc(0);
|
|
21
|
+
pending = [];
|
|
22
|
+
listeners = [];
|
|
23
|
+
constructor(socket) {
|
|
24
|
+
this.socket = socket;
|
|
25
|
+
socket.on('data', (chunk) => this.onData(chunk));
|
|
26
|
+
socket.on('close', () => {
|
|
27
|
+
for (const w of this.pending.splice(0))
|
|
28
|
+
w.reject(new Error('Firefox closed the debugging connection'));
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
/** Connect, retrying while Firefox starts; resolves with the client and the server's hello packet. */
|
|
32
|
+
static async connect(port, timeoutMs) {
|
|
33
|
+
const deadline = Date.now() + timeoutMs;
|
|
34
|
+
for (;;) {
|
|
35
|
+
try {
|
|
36
|
+
const socket = await new Promise((resolve, reject) => {
|
|
37
|
+
const s = net.connect(port, '127.0.0.1', () => resolve(s));
|
|
38
|
+
s.once('error', reject);
|
|
39
|
+
});
|
|
40
|
+
const client = new RdpClient(socket);
|
|
41
|
+
const hello = await client.next((p) => p.from === 'root' && 'applicationType' in p, 5_000);
|
|
42
|
+
return { client, hello };
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
if (Date.now() > deadline)
|
|
46
|
+
throw new Error(`Firefox did not open its debugging port within ${timeoutMs} ms (${error.message})`);
|
|
47
|
+
await new Promise((r) => setTimeout(r, 250));
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
onData(chunk) {
|
|
52
|
+
this.buffer = Buffer.concat([this.buffer, chunk]);
|
|
53
|
+
for (;;) {
|
|
54
|
+
const colon = this.buffer.indexOf(0x3a);
|
|
55
|
+
if (colon < 0)
|
|
56
|
+
return;
|
|
57
|
+
const length = Number(this.buffer.subarray(0, colon).toString());
|
|
58
|
+
if (!Number.isFinite(length)) {
|
|
59
|
+
this.socket.destroy(new Error('malformed RDP packet'));
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
if (this.buffer.length < colon + 1 + length)
|
|
63
|
+
return;
|
|
64
|
+
const packet = JSON.parse(this.buffer.subarray(colon + 1, colon + 1 + length).toString('utf8'));
|
|
65
|
+
this.buffer = this.buffer.subarray(colon + 1 + length);
|
|
66
|
+
this.dispatch(packet);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
dispatch(packet) {
|
|
70
|
+
// Replies carry no `type` (or carry `error`); unsolicited events carry a type.
|
|
71
|
+
const isReply = packet.error !== undefined || packet.type === undefined;
|
|
72
|
+
const waiter = isReply ? this.pending.findIndex((w) => w.actor === packet.from) : -1;
|
|
73
|
+
if (waiter >= 0) {
|
|
74
|
+
this.pending.splice(waiter, 1)[0].resolve(packet);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
for (const listener of this.listeners)
|
|
78
|
+
listener(packet);
|
|
79
|
+
}
|
|
80
|
+
on(listener) {
|
|
81
|
+
this.listeners.push(listener);
|
|
82
|
+
}
|
|
83
|
+
/** The next unsolicited packet matching `predicate`. */
|
|
84
|
+
next(predicate, timeoutMs) {
|
|
85
|
+
return new Promise((resolve, reject) => {
|
|
86
|
+
const timer = setTimeout(() => reject(new Error('timed out waiting for Firefox')), timeoutMs);
|
|
87
|
+
const listener = (p) => {
|
|
88
|
+
if (!predicate(p))
|
|
89
|
+
return;
|
|
90
|
+
clearTimeout(timer);
|
|
91
|
+
this.listeners = this.listeners.filter((l) => l !== listener);
|
|
92
|
+
resolve(p);
|
|
93
|
+
};
|
|
94
|
+
this.listeners.push(listener);
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
request(to, type, extra = {}, timeoutMs = 20_000) {
|
|
98
|
+
return new Promise((resolve, reject) => {
|
|
99
|
+
const timer = setTimeout(() => reject(new Error(`Firefox did not answer ${type}`)), timeoutMs);
|
|
100
|
+
this.pending.push({
|
|
101
|
+
actor: to,
|
|
102
|
+
resolve: (p) => {
|
|
103
|
+
clearTimeout(timer);
|
|
104
|
+
resolve(p);
|
|
105
|
+
},
|
|
106
|
+
reject: (e) => {
|
|
107
|
+
clearTimeout(timer);
|
|
108
|
+
reject(e);
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
const body = Buffer.from(JSON.stringify({ to, type, ...extra }), 'utf8');
|
|
112
|
+
this.socket.write(`${body.length}:`);
|
|
113
|
+
this.socket.write(body);
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
close() {
|
|
117
|
+
this.socket.destroy();
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
/** A Firefox binary, or null. Order: explicit path, FIREFOX_BIN, the usual install locations, PATH. */
|
|
121
|
+
export function findFirefox(explicit, env = process.env) {
|
|
122
|
+
const candidates = [explicit, env.FIREFOX_BIN];
|
|
123
|
+
if (process.platform === 'darwin')
|
|
124
|
+
candidates.push('/Applications/Firefox.app/Contents/MacOS/firefox', '/Applications/Firefox Developer Edition.app/Contents/MacOS/firefox', '/Applications/Firefox Nightly.app/Contents/MacOS/firefox');
|
|
125
|
+
else if (process.platform === 'win32')
|
|
126
|
+
for (const base of [env.ProgramFiles, env['ProgramFiles(x86)']])
|
|
127
|
+
if (base)
|
|
128
|
+
candidates.push(path.join(base, 'Mozilla Firefox', 'firefox.exe'));
|
|
129
|
+
else
|
|
130
|
+
candidates.push('/usr/bin/firefox', '/usr/lib/firefox/firefox', '/usr/local/bin/firefox');
|
|
131
|
+
for (const dir of (env.PATH ?? '').split(path.delimiter))
|
|
132
|
+
if (dir)
|
|
133
|
+
candidates.push(path.join(dir, process.platform === 'win32' ? 'firefox.exe' : 'firefox'));
|
|
134
|
+
for (const candidate of candidates)
|
|
135
|
+
if (candidate && fs.existsSync(candidate))
|
|
136
|
+
return candidate;
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
async function freePort() {
|
|
140
|
+
return new Promise((resolve, reject) => {
|
|
141
|
+
const server = net.createServer();
|
|
142
|
+
server.once('error', reject);
|
|
143
|
+
server.listen(0, '127.0.0.1', () => {
|
|
144
|
+
const address = server.address();
|
|
145
|
+
server.close(() => typeof address === 'object' && address ? resolve(address.port) : reject(new Error('no port')));
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Start a disposable headless Firefox with the devtools server on a free port.
|
|
151
|
+
* `uuids` pins the moz-extension:// host per add-on id, so extension pages have
|
|
152
|
+
* a known URL before the add-on is installed.
|
|
153
|
+
*/
|
|
154
|
+
export async function launchFirefox(options) {
|
|
155
|
+
const profile = fs.mkdtempSync(path.join(os.tmpdir(), 'extenshi-smoke-firefox-'));
|
|
156
|
+
const prefs = {
|
|
157
|
+
'devtools.debugger.remote-enabled': true,
|
|
158
|
+
'devtools.debugger.prompt-connection': false,
|
|
159
|
+
'devtools.chrome.enabled': true,
|
|
160
|
+
'browser.shell.checkDefaultBrowser': false,
|
|
161
|
+
'browser.startup.homepage_override.mstone': 'ignore',
|
|
162
|
+
'browser.aboutwelcome.enabled': false,
|
|
163
|
+
'datareporting.policy.dataSubmissionEnabled': false,
|
|
164
|
+
'toolkit.telemetry.reportingpolicy.firstRun': false,
|
|
165
|
+
'app.update.enabled': false,
|
|
166
|
+
'extensions.webextensions.uuids': JSON.stringify(options.uuids),
|
|
167
|
+
};
|
|
168
|
+
fs.writeFileSync(path.join(profile, 'user.js'), Object.entries(prefs)
|
|
169
|
+
.map(([key, value]) => `user_pref(${JSON.stringify(key)}, ${JSON.stringify(value)});`)
|
|
170
|
+
.join('\n'));
|
|
171
|
+
const port = await freePort();
|
|
172
|
+
const args = ['-no-remote', '-profile', profile, '-start-debugger-server', String(port)];
|
|
173
|
+
if (options.headless)
|
|
174
|
+
args.unshift('-headless');
|
|
175
|
+
const child = spawn(options.binary, args, {
|
|
176
|
+
stdio: 'ignore',
|
|
177
|
+
env: { ...process.env, ...(options.headless ? { MOZ_HEADLESS: '1' } : {}) },
|
|
178
|
+
});
|
|
179
|
+
const exited = new Promise((resolve) => child.once('exit', () => resolve()));
|
|
180
|
+
const close = async () => {
|
|
181
|
+
child.kill();
|
|
182
|
+
await Promise.race([exited, new Promise((r) => setTimeout(r, 5_000))]);
|
|
183
|
+
fs.rmSync(profile, { recursive: true, force: true });
|
|
184
|
+
};
|
|
185
|
+
try {
|
|
186
|
+
const { client, hello } = await RdpClient.connect(port, options.timeoutMs);
|
|
187
|
+
return {
|
|
188
|
+
client,
|
|
189
|
+
hello,
|
|
190
|
+
process: child,
|
|
191
|
+
profile,
|
|
192
|
+
close: async () => {
|
|
193
|
+
client.close();
|
|
194
|
+
await close();
|
|
195
|
+
},
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
catch (error) {
|
|
199
|
+
await close();
|
|
200
|
+
throw error;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
//# sourceMappingURL=firefox-rdp.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"firefox-rdp.js","sourceRoot":"","sources":["../src/firefox-rdp.ts"],"names":[],"mappings":"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"}
|