@extenshi/cli 0.21.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 +265 -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/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/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 +10 -0
- package/dist/page.d.ts.map +1 -1
- package/dist/page.js +32 -2
- package/dist/page.js.map +1 -1
- package/dist/pay-contract.d.ts +10 -3
- package/dist/pay-contract.d.ts.map +1 -1
- package/dist/pay-contract.js +9 -0
- 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/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
package/dist/icon.d.ts
CHANGED
|
@@ -32,6 +32,24 @@ export declare const sanitizeSvgSource: typeof scrubSvgMarkup;
|
|
|
32
32
|
export declare function displayNameFromFile(filePath: string): string;
|
|
33
33
|
/** Resolve the HTML destination: explicit --output wins, else auto-named in cwd. */
|
|
34
34
|
export declare function resolvePreviewPath(output: string | undefined, iconPath: string): string;
|
|
35
|
+
/** A validated icon/artwork source: sanitized SVG markup or signature-checked PNG bytes. */
|
|
36
|
+
export interface IconSource {
|
|
37
|
+
resolved: string;
|
|
38
|
+
isSvg: boolean;
|
|
39
|
+
/** PNG bytes (raster sources only). */
|
|
40
|
+
png?: Buffer;
|
|
41
|
+
/** Scrubbed SVG markup (SVG sources only). */
|
|
42
|
+
svgSource?: string;
|
|
43
|
+
dataUrl: string;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Read and validate an .svg / .png source. Shared by `icon preview` and
|
|
47
|
+
* `icon store-assets` so both refuse the same files for the same reasons.
|
|
48
|
+
*/
|
|
49
|
+
export declare function readIconSource(iconPath: string, limits?: {
|
|
50
|
+
svgBytes: number;
|
|
51
|
+
pngBytes: number;
|
|
52
|
+
}): IconSource;
|
|
35
53
|
/**
|
|
36
54
|
* Command body. Exported for unit tests (call with `open: false`).
|
|
37
55
|
* Throws plain Errors — runCommand() in cli.ts turns them into exit-1 output.
|
package/dist/icon.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"icon.d.ts","sourceRoot":"","sources":["../src/icon.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAMH,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AAM/C,MAAM,WAAW,qBAAqB;IACrC,yFAAyF;IACzF,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,qFAAqF;IACrF,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,oFAAoF;IACpF,IAAI,CAAC,EAAE,OAAO,CAAA;CACd;AAED;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB,uBAAiB,CAAA;AAE/C,oFAAoF;AACpF,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAK5D;AAED,oFAAoF;AACpF,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAIvF;AAmBD;;;GAGG;AACH,wBAAsB,cAAc,CACnC,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,qBAAqB,EAC3B,UAAU,EAAE,MAAM,GAChB,OAAO,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"icon.d.ts","sourceRoot":"","sources":["../src/icon.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAMH,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AAM/C,MAAM,WAAW,qBAAqB;IACrC,yFAAyF;IACzF,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,qFAAqF;IACrF,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,oFAAoF;IACpF,IAAI,CAAC,EAAE,OAAO,CAAA;CACd;AAED;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB,uBAAiB,CAAA;AAE/C,oFAAoF;AACpF,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAK5D;AAED,oFAAoF;AACpF,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAIvF;AAmBD,4FAA4F;AAC5F,MAAM,WAAW,UAAU;IAC1B,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,EAAE,OAAO,CAAA;IACd,uCAAuC;IACvC,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,8CAA8C;IAC9C,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,OAAO,EAAE,MAAM,CAAA;CACf;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAC7B,QAAQ,EAAE,MAAM,EAChB,MAAM;;;CAAuD,GAC3D,UAAU,CAqCZ;AAED;;;GAGG;AACH,wBAAsB,cAAc,CACnC,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,qBAAqB,EAC3B,UAAU,EAAE,MAAM,GAChB,OAAO,CAAC,IAAI,CAAC,CAqBf"}
|
package/dist/icon.js
CHANGED
|
@@ -59,10 +59,10 @@ function openInBrowser(target) {
|
|
|
59
59
|
}
|
|
60
60
|
}
|
|
61
61
|
/**
|
|
62
|
-
*
|
|
63
|
-
*
|
|
62
|
+
* Read and validate an .svg / .png source. Shared by `icon preview` and
|
|
63
|
+
* `icon store-assets` so both refuse the same files for the same reasons.
|
|
64
64
|
*/
|
|
65
|
-
export
|
|
65
|
+
export function readIconSource(iconPath, limits = { svgBytes: MAX_SVG_BYTES, pngBytes: MAX_PNG_BYTES }) {
|
|
66
66
|
const resolved = path.resolve(process.cwd(), iconPath);
|
|
67
67
|
if (!fs.existsSync(resolved)) {
|
|
68
68
|
throw new Error(`Icon file not found: ${resolved}`);
|
|
@@ -72,34 +72,43 @@ export async function runIconPreview(iconPath, opts, cliVersion) {
|
|
|
72
72
|
throw new Error(`Unsupported icon format "${ext || '(none)'}" — pass an .svg or .png file`);
|
|
73
73
|
}
|
|
74
74
|
const raw = fs.readFileSync(resolved);
|
|
75
|
-
let dataUrl;
|
|
76
|
-
let svgSource;
|
|
77
75
|
if (ext === '.svg') {
|
|
78
|
-
if (raw.length >
|
|
79
|
-
throw new Error(
|
|
76
|
+
if (raw.length > limits.svgBytes) {
|
|
77
|
+
throw new Error(`SVG is larger than ${Math.round(limits.svgBytes / 1024)} KB — extension icons should be tiny`);
|
|
80
78
|
}
|
|
81
79
|
const text = raw.toString('utf8');
|
|
82
80
|
if (!text.includes('<svg')) {
|
|
83
81
|
throw new Error('File does not contain an <svg> element');
|
|
84
82
|
}
|
|
85
|
-
svgSource = sanitizeSvgSource(text);
|
|
86
|
-
|
|
83
|
+
const svgSource = sanitizeSvgSource(text);
|
|
84
|
+
return {
|
|
85
|
+
resolved,
|
|
86
|
+
isSvg: true,
|
|
87
|
+
svgSource,
|
|
88
|
+
dataUrl: `data:image/svg+xml;base64,${Buffer.from(svgSource, 'utf8').toString('base64')}`,
|
|
89
|
+
};
|
|
87
90
|
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
throw new Error('File does not look like a valid PNG (bad signature)');
|
|
94
|
-
}
|
|
95
|
-
dataUrl = `data:image/png;base64,${raw.toString('base64')}`;
|
|
91
|
+
if (raw.length > limits.pngBytes) {
|
|
92
|
+
throw new Error(`PNG is larger than ${Math.round(limits.pngBytes / 1024 / 1024)} MB — extension icons should be tiny`);
|
|
93
|
+
}
|
|
94
|
+
if (!raw.subarray(0, 8).equals(PNG_SIGNATURE)) {
|
|
95
|
+
throw new Error('File does not look like a valid PNG (bad signature)');
|
|
96
96
|
}
|
|
97
|
+
return { resolved, isSvg: false, png: raw, dataUrl: `data:image/png;base64,${raw.toString('base64')}` };
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Command body. Exported for unit tests (call with `open: false`).
|
|
101
|
+
* Throws plain Errors — runCommand() in cli.ts turns them into exit-1 output.
|
|
102
|
+
*/
|
|
103
|
+
export async function runIconPreview(iconPath, opts, cliVersion) {
|
|
104
|
+
const source = readIconSource(iconPath);
|
|
105
|
+
const resolved = source.resolved;
|
|
97
106
|
const html = renderIconPreviewHtml({
|
|
98
107
|
name: opts.name?.trim() || displayNameFromFile(resolved),
|
|
99
108
|
fileName: path.basename(resolved),
|
|
100
|
-
dataUrl,
|
|
101
|
-
isSvg:
|
|
102
|
-
svgSource,
|
|
109
|
+
dataUrl: source.dataUrl,
|
|
110
|
+
isSvg: source.isSvg,
|
|
111
|
+
svgSource: source.svgSource,
|
|
103
112
|
cliVersion,
|
|
104
113
|
generatedAt: new Date(),
|
|
105
114
|
});
|
package/dist/icon.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"icon.js","sourceRoot":"","sources":["../src/icon.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAA;AAC1C,OAAO,KAAK,EAAE,MAAM,SAAS,CAAA;AAC7B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAA;AACjC,OAAO,EAAE,qBAAqB,EAAE,MAAM,mBAAmB,CAAA;AACzD,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AAE/C,MAAM,aAAa,GAAG,GAAG,GAAG,IAAI,CAAA;AAChC,MAAM,aAAa,GAAG,CAAC,GAAG,IAAI,GAAG,IAAI,CAAA;AACrC,MAAM,aAAa,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC,CAAA;AAWnF;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,cAAc,CAAA;AAE/C,oFAAoF;AACpF,MAAM,UAAU,mBAAmB,CAAC,QAAgB;IACnD,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,eAAe,EAAE,EAAE,CAAC,CAAA;IACjE,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;IACpD,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,cAAc,CAAA;IAC7C,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;AAC1E,CAAC;AAED,oFAAoF;AACpF,MAAM,UAAU,kBAAkB,CAAC,MAA0B,EAAE,QAAgB;IAC9E,IAAI,MAAM;QAAE,OAAO,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,MAAM,CAAC,CAAA;IACtD,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,eAAe,EAAE,EAAE,CAAC,CAAA;IACjE,OAAO,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,GAAG,IAAI,oBAAoB,CAAC,CAAA;AAChE,CAAC;AAED,iEAAiE;AACjE,SAAS,aAAa,CAAC,MAAc;IACpC,IAAI,CAAC;QACJ,MAAM,CAAC,OAAO,EAAE,IAAI,CAAC,GACpB,OAAO,CAAC,QAAQ,KAAK,QAAQ;YAC5B,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,MAAM,CAAC,CAAC;YACpB,CAAC,CAAC,OAAO,CAAC,QAAQ,KAAK,OAAO;gBAC7B,CAAC,CAAC,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,MAAM,CAAC,CAAC;gBACtC,CAAC,CAAC,CAAC,UAAU,EAAE,CAAC,MAAM,CAAC,CAAC,CAAA;QAC3B,MAAM,KAAK,GAAG,KAAK,CAAC,OAAiB,EAAE,IAAgB,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAA;QAC7F,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,GAAE,CAAC,CAAC,CAAA;QAC3B,KAAK,CAAC,KAAK,EAAE,CAAA;IACd,CAAC;IAAC,MAAM,CAAC;QACR,8DAA8D;IAC/D,CAAC;AACF,CAAC;
|
|
1
|
+
{"version":3,"file":"icon.js","sourceRoot":"","sources":["../src/icon.ts"],"names":[],"mappings":"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"}
|
package/dist/media.d.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `extenshi media upload <file…> --project <id>` — put a logo or screenshot in
|
|
3
|
+
* the project's PUBLIC media store and print its stable URL.
|
|
4
|
+
*
|
|
5
|
+
* Same store and same BFF procedure (`agentUploadProjectMedia`, hosted.write)
|
|
6
|
+
* as the MCP `upload_project_media` tool; the URLs are what `page publish`
|
|
7
|
+
* (and MCP `publish_landing_page`) accept for hosted screenshots and logos —
|
|
8
|
+
* `page publish --upload-local` calls this for every local screenshot path.
|
|
9
|
+
*
|
|
10
|
+
* Client-side, before a byte leaves the machine:
|
|
11
|
+
* - secret-looking file names are refused (same list as evidence attachments);
|
|
12
|
+
* - the content is sniffed — PNG/JPEG/WebP go as-is, anything else is refused;
|
|
13
|
+
* - an SVG is rasterized locally to PNG with the store-asset renderer (the
|
|
14
|
+
* public store never serves SVG, so there is no SVG script surface at all).
|
|
15
|
+
* The BFF then re-checks everything, strips metadata and applies the quota.
|
|
16
|
+
*/
|
|
17
|
+
export declare const MEDIA_MAX_BYTES: number;
|
|
18
|
+
export type MediaMime = 'image/png' | 'image/jpeg' | 'image/webp';
|
|
19
|
+
export declare function sniffMediaMime(bytes: Uint8Array): MediaMime | null;
|
|
20
|
+
export interface PreparedMedia {
|
|
21
|
+
file: string;
|
|
22
|
+
mime: MediaMime;
|
|
23
|
+
bytes: Buffer;
|
|
24
|
+
/** Set when an SVG was rasterized locally. */
|
|
25
|
+
rasterizedFrom?: 'svg';
|
|
26
|
+
}
|
|
27
|
+
/** Validate a local file and turn it into upload bytes. Exported for tests. */
|
|
28
|
+
export declare function prepareMediaFile(file: string, svgWidth?: number): Promise<PreparedMedia>;
|
|
29
|
+
export interface MediaUploadResult {
|
|
30
|
+
file: string;
|
|
31
|
+
url: string;
|
|
32
|
+
[key: string]: unknown;
|
|
33
|
+
}
|
|
34
|
+
/** Upload one prepared file. Shared by `media upload` and `page publish --upload-local`. */
|
|
35
|
+
export declare function uploadMediaFile(projectId: string, file: string, opts?: {
|
|
36
|
+
svgWidth?: number;
|
|
37
|
+
}): Promise<MediaUploadResult>;
|
|
38
|
+
export declare function runMediaUpload(files: string[], opts: {
|
|
39
|
+
project: string;
|
|
40
|
+
svgWidth?: string;
|
|
41
|
+
}): Promise<MediaUploadResult[]>;
|
|
42
|
+
//# sourceMappingURL=media.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"media.d.ts","sourceRoot":"","sources":["../src/media.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAYH,eAAO,MAAM,eAAe,QAAkB,CAAA;AAE9C,MAAM,MAAM,SAAS,GAAG,WAAW,GAAG,YAAY,GAAG,YAAY,CAAA;AAEjE,wBAAgB,cAAc,CAAC,KAAK,EAAE,UAAU,GAAG,SAAS,GAAG,IAAI,CAWlE;AAED,MAAM,WAAW,aAAa;IAC7B,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,SAAS,CAAA;IACf,KAAK,EAAE,MAAM,CAAA;IACb,8CAA8C;IAC9C,cAAc,CAAC,EAAE,KAAK,CAAA;CACtB;AAED,+EAA+E;AAC/E,wBAAsB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CA8B9F;AAID,MAAM,WAAW,iBAAiB;IACjC,IAAI,EAAE,MAAM,CAAA;IACZ,GAAG,EAAE,MAAM,CAAA;IACX,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CACtB;AAED,4FAA4F;AAC5F,wBAAsB,eAAe,CACpC,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,EACZ,IAAI,GAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAO,GAC9B,OAAO,CAAC,iBAAiB,CAAC,CAW5B;AAED,wBAAsB,cAAc,CACnC,KAAK,EAAE,MAAM,EAAE,EACf,IAAI,EAAE;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,GAC1C,OAAO,CAAC,iBAAiB,EAAE,CAAC,CAsB9B"}
|
package/dist/media.js
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `extenshi media upload <file…> --project <id>` — put a logo or screenshot in
|
|
3
|
+
* the project's PUBLIC media store and print its stable URL.
|
|
4
|
+
*
|
|
5
|
+
* Same store and same BFF procedure (`agentUploadProjectMedia`, hosted.write)
|
|
6
|
+
* as the MCP `upload_project_media` tool; the URLs are what `page publish`
|
|
7
|
+
* (and MCP `publish_landing_page`) accept for hosted screenshots and logos —
|
|
8
|
+
* `page publish --upload-local` calls this for every local screenshot path.
|
|
9
|
+
*
|
|
10
|
+
* Client-side, before a byte leaves the machine:
|
|
11
|
+
* - secret-looking file names are refused (same list as evidence attachments);
|
|
12
|
+
* - the content is sniffed — PNG/JPEG/WebP go as-is, anything else is refused;
|
|
13
|
+
* - an SVG is rasterized locally to PNG with the store-asset renderer (the
|
|
14
|
+
* public store never serves SVG, so there is no SVG script surface at all).
|
|
15
|
+
* The BFF then re-checks everything, strips metadata and applies the quota.
|
|
16
|
+
*/
|
|
17
|
+
import { readFile, stat } from 'node:fs/promises';
|
|
18
|
+
import path from 'node:path';
|
|
19
|
+
import { z } from 'zod';
|
|
20
|
+
import { loadConfig } from './config.js';
|
|
21
|
+
import { assertNotSecretLike } from './evidence-attachments.js';
|
|
22
|
+
import { InvalidInputError } from './expected-error.js';
|
|
23
|
+
import { readIconSource } from './icon.js';
|
|
24
|
+
import { projectRequest } from './project.js';
|
|
25
|
+
import { renderSvg, sourceInfo } from './store-assets.js';
|
|
26
|
+
export const MEDIA_MAX_BYTES = 2 * 1024 * 1024;
|
|
27
|
+
export function sniffMediaMime(bytes) {
|
|
28
|
+
const b = bytes;
|
|
29
|
+
if (b.length >= 8 && b[0] === 0x89 && b[1] === 0x50 && b[2] === 0x4e && b[3] === 0x47)
|
|
30
|
+
return 'image/png';
|
|
31
|
+
if (b.length >= 3 && b[0] === 0xff && b[1] === 0xd8 && b[2] === 0xff)
|
|
32
|
+
return 'image/jpeg';
|
|
33
|
+
if (b.length >= 12 &&
|
|
34
|
+
String.fromCharCode(b[0], b[1], b[2], b[3]) === 'RIFF' &&
|
|
35
|
+
String.fromCharCode(b[8], b[9], b[10], b[11]) === 'WEBP')
|
|
36
|
+
return 'image/webp';
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
/** Validate a local file and turn it into upload bytes. Exported for tests. */
|
|
40
|
+
export async function prepareMediaFile(file, svgWidth) {
|
|
41
|
+
assertNotSecretLike(file);
|
|
42
|
+
const resolved = path.resolve(process.cwd(), file);
|
|
43
|
+
const info = await stat(resolved).catch(() => null);
|
|
44
|
+
if (!info?.isFile())
|
|
45
|
+
throw new InvalidInputError(`File not found: ${file}`);
|
|
46
|
+
if (info.size > MEDIA_MAX_BYTES && !/\.svg$/i.test(file))
|
|
47
|
+
throw new InvalidInputError(`"${file}" is ${(info.size / 1024 / 1024).toFixed(1)} MB; the limit is 2 MB per image. Re-export it smaller.`);
|
|
48
|
+
const bytes = await readFile(resolved);
|
|
49
|
+
const mime = sniffMediaMime(bytes);
|
|
50
|
+
if (mime)
|
|
51
|
+
return { file, mime, bytes };
|
|
52
|
+
if (/\.svg$/i.test(file)) {
|
|
53
|
+
// readIconSource scrubs scripts/handlers; the output is a plain PNG.
|
|
54
|
+
const source = readIconSource(resolved, { svgBytes: MEDIA_MAX_BYTES, pngBytes: MEDIA_MAX_BYTES });
|
|
55
|
+
const { width, height } = await sourceInfo(source);
|
|
56
|
+
if (!width || !height)
|
|
57
|
+
throw new InvalidInputError(`"${file}" has no size (set width/height or a viewBox)`);
|
|
58
|
+
const target = Math.min(4096, Math.max(16, Math.round(svgWidth || 1024)));
|
|
59
|
+
const raster = await renderSvg(source.svgSource, target);
|
|
60
|
+
const png = Buffer.from(raster.png);
|
|
61
|
+
if (png.length > MEDIA_MAX_BYTES)
|
|
62
|
+
throw new InvalidInputError(`"${file}" rasterized to ${target}px is over 2 MB — pass a smaller --svg-width`);
|
|
63
|
+
return { file, mime: 'image/png', bytes: png, rasterizedFrom: 'svg' };
|
|
64
|
+
}
|
|
65
|
+
throw new InvalidInputError(`"${file}" is not a PNG, JPEG or WebP image (checked by content, not by name); .svg files are rasterized to PNG.`);
|
|
66
|
+
}
|
|
67
|
+
const projectIdSchema = z.string().uuid({ message: '--project must be a project UUID' });
|
|
68
|
+
/** Upload one prepared file. Shared by `media upload` and `page publish --upload-local`. */
|
|
69
|
+
export async function uploadMediaFile(projectId, file, opts = {}) {
|
|
70
|
+
const prepared = await prepareMediaFile(file, opts.svgWidth);
|
|
71
|
+
const result = (await projectRequest(await loadConfig(), 'agentUploadProjectMedia', { projectId, mime: prepared.mime, dataBase64: prepared.bytes.toString('base64') }, true));
|
|
72
|
+
if (typeof result.url !== 'string' || !/^https:\/\//.test(result.url))
|
|
73
|
+
throw new Error('The server did not return an https URL for the upload');
|
|
74
|
+
return { file, ...result, url: result.url, ...(prepared.rasterizedFrom ? { rasterizedFrom: 'svg' } : {}) };
|
|
75
|
+
}
|
|
76
|
+
export async function runMediaUpload(files, opts) {
|
|
77
|
+
const projectId = projectIdSchema.safeParse(opts.project);
|
|
78
|
+
if (!projectId.success)
|
|
79
|
+
throw new InvalidInputError(projectId.error.issues[0]?.message ?? 'Invalid --project');
|
|
80
|
+
const svgWidth = opts.svgWidth ? Number(opts.svgWidth) : undefined;
|
|
81
|
+
if (svgWidth !== undefined && !(Number.isInteger(svgWidth) && svgWidth >= 16 && svgWidth <= 4096))
|
|
82
|
+
throw new InvalidInputError('--svg-width must be an integer between 16 and 4096');
|
|
83
|
+
// Validate every file before uploading any, so a typo does not leave half a batch public.
|
|
84
|
+
for (const file of files)
|
|
85
|
+
await prepareMediaFile(file, svgWidth);
|
|
86
|
+
const results = [];
|
|
87
|
+
for (const file of files) {
|
|
88
|
+
const result = await uploadMediaFile(projectId.data, file, { svgWidth });
|
|
89
|
+
results.push(result);
|
|
90
|
+
process.stderr.write(`${file} → ${result.url}${result.deduplicated ? ' (already stored)' : ''}${result.rasterizedFrom ? ' (SVG rasterized to PNG)' : ''}\n`);
|
|
91
|
+
}
|
|
92
|
+
console.log(JSON.stringify(results, null, 2));
|
|
93
|
+
process.stderr.write('These URLs are public. Use them as screenshots[].url or logoUrl in landing.json, then extenshi page publish.\n');
|
|
94
|
+
return results;
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=media.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"media.js","sourceRoot":"","sources":["../src/media.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `extenshi package <dir> --browser chrome,edge,firefox` — one reproducible
|
|
3
|
+
* store package per browser from an unpacked build directory.
|
|
4
|
+
*
|
|
5
|
+
* REPRODUCIBLE means byte-identical for the same files on any machine, in any
|
|
6
|
+
* timezone, under any umask: Dojo release readiness binds every piece of
|
|
7
|
+
* evidence (tests, scan, installed smoke) to the package's SHA-256, so a CI
|
|
8
|
+
* build and a local build of one commit must agree to the byte. The archive is
|
|
9
|
+
* written by the SAME writer Dojo uses (`zip.ts`, a byte-identical copy of
|
|
10
|
+
* dojo/src/lib/tools/zip.ts) with the same choices as Dojo's extension archive:
|
|
11
|
+
* - entries sorted by path, manifest.json at the root, no directory entries;
|
|
12
|
+
* - every entry stamped 2000-01-01 00:00:00 (DOS fields carry no timezone, so
|
|
13
|
+
* the constant is built from LOCAL components — see REPRODUCIBLE_MTIME);
|
|
14
|
+
* - STORE (no compression): deflate output depends on the zlib build, which
|
|
15
|
+
* differs between Node releases, and reproducibility across machines is the
|
|
16
|
+
* point. Store uploads accept uncompressed archives;
|
|
17
|
+
* - "version made by" MS-DOS with zero external attributes, so no Unix mode
|
|
18
|
+
* bits, owners or extra fields — nothing machine-specific survives.
|
|
19
|
+
*
|
|
20
|
+
* The user's source directory is never modified. `--fix-war` and `--gecko-id`
|
|
21
|
+
* rewrite manifest.json in the PACKAGED copy only, and every rewrite is listed
|
|
22
|
+
* in the output (`changes`).
|
|
23
|
+
*/
|
|
24
|
+
export declare const PACKAGE_BROWSERS: readonly ["chrome", "edge", "firefox"];
|
|
25
|
+
export type PackageBrowser = (typeof PACKAGE_BROWSERS)[number];
|
|
26
|
+
/** Written next to the zips; `release prepare`, `test smoke`, `evidence ingest` read it directly. */
|
|
27
|
+
export declare const PACKAGES_MANIFEST_FILE = "extenshi-packages.json";
|
|
28
|
+
export declare const PACKAGES_MANIFEST_SCHEMA_VERSION = 1;
|
|
29
|
+
export interface PackageFile {
|
|
30
|
+
/** Posix path relative to the package root. */
|
|
31
|
+
path: string;
|
|
32
|
+
bytes: Uint8Array;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* `--exclude` pattern → matcher, gitignore-style: `*` stays inside one path
|
|
36
|
+
* segment, `**` crosses segments, a pattern without `/` matches a basename at any
|
|
37
|
+
* depth, a pattern with `/` is anchored at the package root, and a pattern naming
|
|
38
|
+
* a directory excludes everything under it.
|
|
39
|
+
*/
|
|
40
|
+
export declare function excludeMatcher(patterns: readonly string[]): (relative: string) => boolean;
|
|
41
|
+
/**
|
|
42
|
+
* A `web_accessible_resources` entry as the BROWSER matches it: `*` matches any
|
|
43
|
+
* run of characters INCLUDING `/` (Chromium's MatchPattern and Firefox's
|
|
44
|
+
* MatchGlob both work this way), `?` one character. Leading `/` is ignored.
|
|
45
|
+
*/
|
|
46
|
+
export declare function warGlobToRegExp(glob: string): RegExp;
|
|
47
|
+
export interface CollectedFiles {
|
|
48
|
+
files: PackageFile[];
|
|
49
|
+
/** Relative paths left out by --exclude (dot-entries and node_modules are not listed). */
|
|
50
|
+
excluded: string[];
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Read every file under `root`, sorted, skipping dot-entries (`.git`, `.github`,
|
|
54
|
+
* `.extenshi`, `.DS_Store`, …), `node_modules` and `skipDirs` (the output
|
|
55
|
+
* directory when it sits inside the source). Symlinks are refused: following one
|
|
56
|
+
* would pack bytes from outside the directory, and a link itself is not
|
|
57
|
+
* something every store unpacks the same way.
|
|
58
|
+
*/
|
|
59
|
+
export declare function collectFiles(root: string, options?: {
|
|
60
|
+
exclude?: readonly string[];
|
|
61
|
+
skipDirs?: readonly string[];
|
|
62
|
+
}): CollectedFiles;
|
|
63
|
+
type Json = Record<string, any>;
|
|
64
|
+
/**
|
|
65
|
+
* Every file the manifest names, which the browser loads or refuses to install
|
|
66
|
+
* without: background, pages, content scripts, icons, locale, rulesets.
|
|
67
|
+
* `web_accessible_resources` is not here — its entries are patterns, handled by
|
|
68
|
+
* the WAR check.
|
|
69
|
+
*/
|
|
70
|
+
export declare function manifestReferences(manifest: Json): string[];
|
|
71
|
+
export interface WarWildcard {
|
|
72
|
+
/** Index into the manifest's web_accessible_resources array. */
|
|
73
|
+
entry: number;
|
|
74
|
+
pattern: string;
|
|
75
|
+
}
|
|
76
|
+
export declare function warWildcards(manifest: Json): WarWildcard[];
|
|
77
|
+
/**
|
|
78
|
+
* Replace every wildcard in `web_accessible_resources` with the explicit list of
|
|
79
|
+
* packaged files it matches (as the browser would match it). Exact entries stay.
|
|
80
|
+
* manifest.json itself is never exposed. Returns human-readable change notes.
|
|
81
|
+
*/
|
|
82
|
+
export declare function expandWarWildcards(manifest: Json, packagedPaths: readonly string[]): string[];
|
|
83
|
+
/** `browser_specific_settings.gecko.id`, or the MV2 `applications.gecko.id` spelling. */
|
|
84
|
+
export declare function manifestGeckoId(manifest: Json): string | undefined;
|
|
85
|
+
export interface PackOptions {
|
|
86
|
+
browser: PackageBrowser;
|
|
87
|
+
/** Explicit Firefox add-on ID written into the packaged manifest. */
|
|
88
|
+
geckoId?: string;
|
|
89
|
+
/** Expand wildcard web_accessible_resources in the packaged manifest. */
|
|
90
|
+
fixWar?: boolean;
|
|
91
|
+
/** Bound Dojo project, used only to suggest its derived add-on ID in errors. */
|
|
92
|
+
projectId?: string;
|
|
93
|
+
}
|
|
94
|
+
export interface PackResult {
|
|
95
|
+
browser: PackageBrowser;
|
|
96
|
+
bytes: Uint8Array;
|
|
97
|
+
sha256: string;
|
|
98
|
+
entries: string[];
|
|
99
|
+
manifest: Json;
|
|
100
|
+
/** Rewrites applied to the PACKAGED manifest.json (never the source). */
|
|
101
|
+
changes: string[];
|
|
102
|
+
warnings: string[];
|
|
103
|
+
}
|
|
104
|
+
export declare function packExtension(source: readonly PackageFile[], options: PackOptions): PackResult;
|
|
105
|
+
/** `dyslexia-font-reading-ruler-chrome-0.1.0.zip`: browser in the name so the wrong upload is hard. */
|
|
106
|
+
export declare function packageFileName(name: string, browser: PackageBrowser, version: string): string;
|
|
107
|
+
/** Resolve `__MSG_key__` against the default locale, for the file name only. */
|
|
108
|
+
export declare function displayName(manifest: Json, files: readonly PackageFile[]): string;
|
|
109
|
+
export interface PackageEntry {
|
|
110
|
+
browser: PackageBrowser;
|
|
111
|
+
/** Zip file name, relative to the packages manifest. */
|
|
112
|
+
file: string;
|
|
113
|
+
sha256: string;
|
|
114
|
+
bytes: number;
|
|
115
|
+
entries: number;
|
|
116
|
+
name: string;
|
|
117
|
+
version: string;
|
|
118
|
+
/** hashMetadata(packaged manifest) — equals the digest `release prepare` records. */
|
|
119
|
+
manifestDigest: string;
|
|
120
|
+
geckoId?: string;
|
|
121
|
+
/** Source directory as given, relative to where the command ran. */
|
|
122
|
+
source: string;
|
|
123
|
+
changes: string[];
|
|
124
|
+
warnings: string[];
|
|
125
|
+
}
|
|
126
|
+
export interface PackagesManifest {
|
|
127
|
+
schemaVersion: typeof PACKAGES_MANIFEST_SCHEMA_VERSION;
|
|
128
|
+
tool: string;
|
|
129
|
+
packages: PackageEntry[];
|
|
130
|
+
}
|
|
131
|
+
export declare function isPackagesManifest(value: unknown): value is PackagesManifest;
|
|
132
|
+
/**
|
|
133
|
+
* `<artifact>` for `release prepare`, `test smoke` and `evidence ingest`: a zip,
|
|
134
|
+
* or the packages manifest `extenshi package` wrote (then `browser` picks the
|
|
135
|
+
* entry). The zip's bytes must still hash to what the manifest recorded — a
|
|
136
|
+
* package rebuilt or edited since then is refused rather than silently used.
|
|
137
|
+
*/
|
|
138
|
+
export declare function resolvePackagedArtifact(file: string, browser?: string): {
|
|
139
|
+
artifactPath: string;
|
|
140
|
+
entry?: PackageEntry;
|
|
141
|
+
};
|
|
142
|
+
/**
|
|
143
|
+
* Browsers a zip was packaged for, from the packages manifest next to it. Chrome
|
|
144
|
+
* and Edge built from one directory are byte-identical, so one zip can be both.
|
|
145
|
+
*/
|
|
146
|
+
export declare function packagedBrowsersFor(artifactPath: string, sha256: string): PackageBrowser[];
|
|
147
|
+
export interface RunPackageOptions {
|
|
148
|
+
/** Default build directory for every browser without its own. */
|
|
149
|
+
dir?: string;
|
|
150
|
+
dirs?: Partial<Record<PackageBrowser, string>>;
|
|
151
|
+
browsers: PackageBrowser[];
|
|
152
|
+
outDir: string;
|
|
153
|
+
exclude?: string[];
|
|
154
|
+
geckoId?: string;
|
|
155
|
+
fixWar?: boolean;
|
|
156
|
+
json?: boolean;
|
|
157
|
+
cliVersion: string;
|
|
158
|
+
cwd?: string;
|
|
159
|
+
}
|
|
160
|
+
export declare function parsePackageBrowsers(value: string | undefined): PackageBrowser[];
|
|
161
|
+
export declare function runPackage(options: RunPackageOptions): PackagesManifest;
|
|
162
|
+
export {};
|
|
163
|
+
//# sourceMappingURL=package.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"package.d.ts","sourceRoot":"","sources":["../src/package.ts"],"names":[],"mappings":"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"}
|