@kernocal/validate-store 0.1.1

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 ADDED
@@ -0,0 +1,43 @@
1
+ # wxt-validate-store
2
+
3
+ WXT module for validating Chrome Web Store and Edge Add-ons store listing data before publishing.
4
+
5
+ ## Features
6
+
7
+ - Validates image assets (screenshots, promo tiles, icons) exist and have correct dimensions
8
+ - Validates Chrome permission justifications match manifest permissions
9
+ - Validates Edge search term limits (count, word count, character length)
10
+ - Generates a `details.txt` export with all store listing data to copy/paste easier
11
+
12
+ ## Usage
13
+
14
+ Install the package:
15
+
16
+ ```sh
17
+ pnpm i -D @kernocal/wxt-validate-store
18
+ ```
19
+
20
+ Add the module to `wxt.config.ts`:
21
+
22
+ ```ts
23
+ export default defineConfig({
24
+ modules: ['@kernocal/wxt-validate-store'],
25
+ });
26
+ ```
27
+
28
+ ## Configuration
29
+
30
+ ```ts
31
+ export default defineConfig({
32
+ modules: ['@kernocal/wxt-validate-store'],
33
+ validateStore: {
34
+ enabled: true,
35
+ storeDataSrc: './store/storeData',
36
+ storeExportsDir: './store/exports',
37
+ },
38
+ });
39
+ ```
40
+
41
+ ## Store Data
42
+
43
+ Your `storeData` module should export a `storeData` object conforming to the `StoreDetails` type. See the exported types for the full schema.
@@ -0,0 +1,71 @@
1
+ import * as wxt0 from "wxt";
2
+
3
+ //#region src/types.d.ts
4
+ type ChromeCategory = 'Communication' | 'Developer Tools' | 'Education' | 'Tools' | 'Workflow & Planning' | 'Art & Design' | 'Entertainment' | 'Games' | 'Household' | 'Just for Fun' | 'News & Weather' | 'Shopping' | 'Social Networking' | 'Travel' | 'Well-being' | 'Accessibility' | 'Functionality & UI' | 'Privacy & Security';
5
+ type EdgeCategory = 'Accessibility' | 'Blogging' | 'Communication' | 'Developer tools' | 'Entertainment' | 'News & weather' | 'Photos' | 'Productivity' | 'Search tools' | 'Shopping' | 'Social' | 'Sports';
6
+ interface ImageAsset<W extends number = number, H extends number = number> {
7
+ path: string;
8
+ width: W;
9
+ height: H;
10
+ }
11
+ type Screenshot = ImageAsset<1280, 800> | ImageAsset<640, 400>;
12
+ interface CommonDetails {
13
+ privacyPolicyUrl: string;
14
+ screenshots: [Screenshot, ...Screenshot[]];
15
+ smallPromoTile: ImageAsset<440, 280>;
16
+ largePromoTile: ImageAsset<1400, 560> | null;
17
+ promoVideoUrl: string | null;
18
+ websiteUrl: string;
19
+ matureContent: boolean;
20
+ locales: Record<string, {
21
+ description: string;
22
+ }>;
23
+ }
24
+ interface ChromeDetails {
25
+ category: ChromeCategory;
26
+ icon: ImageAsset<128, 128>;
27
+ officialUrl: string | null;
28
+ supportUrl: string;
29
+ singlePurpose: string;
30
+ permissionJustifications: Record<string, string>;
31
+ remoteCode: boolean;
32
+ }
33
+ interface EdgeDetails {
34
+ category: EdgeCategory;
35
+ icon: ImageAsset<300, 300>;
36
+ searchTerms: string[];
37
+ supportContact: string;
38
+ collectsPersonalData: boolean;
39
+ }
40
+ interface StoreDetails {
41
+ common: CommonDetails;
42
+ chrome: ChromeDetails;
43
+ edge: EdgeDetails;
44
+ }
45
+ //#endregion
46
+ //#region src/index.d.ts
47
+ declare const _default: wxt0.WxtModule<ValidateStoreOptions>;
48
+ interface ValidateStoreOptions {
49
+ /**
50
+ * Enable publish validation
51
+ *
52
+ * @default true
53
+ */
54
+ enabled?: boolean;
55
+ /**
56
+ * Path to the store data module (without extension), resolved from root
57
+ *
58
+ * @default '<root>/store/storeData'
59
+ */
60
+ storeDataSrc?: string;
61
+ /**
62
+ * Directory for store exports (screenshots, icons, details.txt output)
63
+ *
64
+ * @default '<root>/store/exports'
65
+ */
66
+ storeExportsDir?: string;
67
+ }
68
+ sideEffect();
69
+
70
+ //#endregion
71
+ export { ChromeCategory, ChromeDetails, CommonDetails, EdgeCategory, EdgeDetails, ImageAsset, StoreDetails, type ValidateStoreOptions, _default as default };
package/dist/index.js ADDED
@@ -0,0 +1,128 @@
1
+ import "wxt";
2
+ import { defineWxtModule } from "wxt/modules";
3
+ import { resolve } from "node:path";
4
+ import { access, mkdir, writeFile } from "node:fs/promises";
5
+ import defu from "defu";
6
+ import sharp from "sharp";
7
+ //#region src/util.ts
8
+ const WHITESPACE_RE = /\s+/;
9
+ async function validateImageAsset(asset, label, issues, srcDir) {
10
+ const fullPath = resolve(srcDir, asset.path);
11
+ try {
12
+ await access(fullPath);
13
+ } catch {
14
+ issues.push(`${label}: file not found — ${asset.path}`);
15
+ return;
16
+ }
17
+ if (!asset.path.endsWith(".png")) {
18
+ issues.push(`${label}: expected .png format — ${asset.path}`);
19
+ return;
20
+ }
21
+ const { width, height } = await sharp(fullPath).metadata();
22
+ if (width !== asset.width) issues.push(`${label}: expected width ${asset.width}, got ${width}`);
23
+ if (height !== asset.height) issues.push(`${label}: expected height ${asset.height}, got ${height}`);
24
+ }
25
+ async function validateChrome(chrome, manifestPermissions, issues, srcDir) {
26
+ await validateImageAsset(chrome.icon, "chrome.icon", issues, srcDir);
27
+ const justified = Object.keys(chrome.permissionJustifications);
28
+ for (const perm of manifestPermissions) if (!justified.includes(perm)) issues.push(`Permission "${perm}" in manifest but missing justification in storeData`);
29
+ for (const perm of justified) if (!manifestPermissions.includes(perm)) issues.push(`Justification for "${perm}" but not in manifest permissions`);
30
+ }
31
+ async function validateEdge(edge, issues, srcDir) {
32
+ await validateImageAsset(edge.icon, "edge.icon", issues, srcDir);
33
+ const { searchTerms } = edge;
34
+ const totalWords = searchTerms.reduce((sum, term) => sum + term.split(WHITESPACE_RE).length, 0);
35
+ if (totalWords > 21) issues.push(`Edge search terms: max 21 words total, got ${totalWords}`);
36
+ if (searchTerms.length > 7) issues.push(`Edge search terms: max 7 terms, got ${searchTerms.length}`);
37
+ for (const term of searchTerms) if (term.length > 30) issues.push(`Edge search term "${term}" exceeds 30 char limit (${term.length})`);
38
+ }
39
+ function buildDetailsText(data) {
40
+ return [
41
+ "=== Common ===",
42
+ "",
43
+ ...Object.entries(data.common.locales || {}).flatMap(([locale, info]) => [
44
+ `Description (${locale}):`,
45
+ info.description,
46
+ ""
47
+ ]),
48
+ "Privacy Policy URL:",
49
+ data.common.privacyPolicyUrl,
50
+ "",
51
+ "Website URL:",
52
+ data.common.websiteUrl,
53
+ "",
54
+ "Mature Content:",
55
+ `${data.common.matureContent}`,
56
+ "",
57
+ "=== Chrome ===",
58
+ "",
59
+ "Category:",
60
+ data.chrome.category,
61
+ "",
62
+ "Official URL:",
63
+ data.chrome.officialUrl ?? "(none)",
64
+ "",
65
+ "Support URL:",
66
+ data.chrome.supportUrl,
67
+ "",
68
+ "Single Purpose:",
69
+ data.chrome.singlePurpose,
70
+ "",
71
+ "Remote Code:",
72
+ `${data.chrome.remoteCode}`,
73
+ "",
74
+ "Permission Justifications:",
75
+ ...Object.entries(data.chrome.permissionJustifications).flatMap(([k, v]) => [
76
+ ` ${k}:`,
77
+ ` ${v}`,
78
+ ""
79
+ ]),
80
+ "",
81
+ "=== Edge ===",
82
+ "",
83
+ "Category:",
84
+ data.edge.category,
85
+ "",
86
+ "Search Terms:",
87
+ ...data.edge.searchTerms,
88
+ "",
89
+ "Support Contact:",
90
+ data.edge.supportContact,
91
+ "",
92
+ "Collects Personal Data:",
93
+ `${data.edge.collectsPersonalData}`
94
+ ].join("\n");
95
+ }
96
+ //#endregion
97
+ //#region src/index.ts
98
+ var src_default = defineWxtModule({
99
+ name: "validateStore",
100
+ configKey: "validateStore",
101
+ async setup(wxt, options) {
102
+ const parsedOptions = defu(options, {
103
+ enabled: true,
104
+ storeDataSrc: resolve(wxt.config.root, "store/storeData"),
105
+ storeExportsDir: resolve(wxt.config.root, "store/exports")
106
+ });
107
+ if (!parsedOptions.enabled) return wxt.logger.warn("`[validate-store]` module disabled");
108
+ const { storeData } = await import(parsedOptions.storeDataSrc);
109
+ wxt.hook("build:manifestGenerated", async (_wxt, manifest) => {
110
+ if (_wxt.config.mode === "development") return;
111
+ const issues = [];
112
+ const { root } = _wxt.config;
113
+ const { screenshots, largePromoTile, smallPromoTile } = storeData.common;
114
+ for (const [i, screenshot] of screenshots.entries()) await validateImageAsset(screenshot, `screenshots[${i}]`, issues, root);
115
+ if (screenshots.length > 5) issues.push(`Max 5 screenshots allowed, got ${screenshots.length}`);
116
+ await validateImageAsset(smallPromoTile, "smallPromoTile", issues, root);
117
+ if (largePromoTile) await validateImageAsset(largePromoTile, "largePromoTile", issues, root);
118
+ await validateChrome(storeData.chrome, manifest.permissions ?? [], issues, root);
119
+ await validateEdge(storeData.edge, issues, root);
120
+ if (issues.length > 0) for (const issue of issues) _wxt.logger.warn(`\`[validate-store]\` ${issue}`);
121
+ await mkdir(parsedOptions.storeExportsDir, { recursive: true });
122
+ await writeFile(resolve(parsedOptions.storeExportsDir, "details.txt"), buildDetailsText(storeData), "utf-8");
123
+ _wxt.logger.info("`[validate-store]` details.txt generated");
124
+ });
125
+ }
126
+ });
127
+ //#endregion
128
+ export { src_default as default };
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "@kernocal/validate-store",
3
+ "description": "WXT module for validating Chrome and Edge store listing data before publishing",
4
+ "repository": {
5
+ "type": "git",
6
+ "url": "git+https://github.com/Kernocal/validate-store.git"
7
+ },
8
+ "homepage": "https://github.com/Kernocal/validate-store#readme",
9
+ "keywords": [
10
+ "wxt-module"
11
+ ],
12
+ "license": "MIT",
13
+ "version": "0.1.1",
14
+ "type": "module",
15
+ "module": "./dist/index.js",
16
+ "types": "./dist/index.d.ts",
17
+ "exports": {
18
+ ".": {
19
+ "types": "./dist/index.d.ts",
20
+ "default": "./dist/index.js"
21
+ }
22
+ },
23
+ "publishConfig": {
24
+ "access": "public"
25
+ },
26
+ "files": [
27
+ "dist"
28
+ ],
29
+ "peerDependencies": {
30
+ "wxt": ">=0.19.0"
31
+ },
32
+ "dependencies": {
33
+ "defu": "^6.1.4",
34
+ "sharp": "^0.34.5"
35
+ },
36
+ "devDependencies": {
37
+ "tsdown": "^0.12.4",
38
+ "typescript": "^5.9.3"
39
+ },
40
+ "scripts": {
41
+ "build": "tsdown"
42
+ }
43
+ }