webstore-lint 1.0.34 → 1.0.35
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 +8 -0
- package/package.json +2 -2
- package/src/rules.mjs +50 -4
- package/src/scan.mjs +1 -1
package/README.md
CHANGED
|
@@ -74,6 +74,14 @@ Run it on a copy of the built extension containing regular files to check that
|
|
|
74
74
|
code. A linked `manifest.json` makes the package unreadable. The Action fails on
|
|
75
75
|
that case at every threshold; other skipped links trigger `fail-on: warn`.
|
|
76
76
|
|
|
77
|
+
The tool also warns when `content_scripts` names an unsupported JavaScript or
|
|
78
|
+
stylesheet file type. Chrome skips the entire entry, including its other files.
|
|
79
|
+
Use built `.js` or `.mjs` scripts and `.css` stylesheets, then reload the unpacked
|
|
80
|
+
extension to verify it runs. `.user.js` is supported, and Chrome accepts `.scss`
|
|
81
|
+
for compatibility without compiling Sass. This loading warning has no policy
|
|
82
|
+
codename and triggers the Action's `fail-on: warn` threshold. See
|
|
83
|
+
[Chromium's file-type validation](https://chromium.googlesource.com/chromium/src/+/main/extensions/common/utils/content_script_utils.cc).
|
|
84
|
+
|
|
77
85
|
## In GitHub Actions
|
|
78
86
|
|
|
79
87
|
```yaml
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "webstore-lint",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.35",
|
|
4
4
|
"description": "Check an unpacked Chrome extension against the Chrome Web Store program policies before you submit it. Every finding cites Google's own notification ID and verbatim policy text. Covers the 1 August 2026 policy updates.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"chrome-extension",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"node": ">=18"
|
|
39
39
|
},
|
|
40
40
|
"scripts": {
|
|
41
|
-
"test": "node test/lint.test.mjs && node test/action.test.mjs && node --test test/permissions-output.test.mjs test/cli-modes.test.mjs test/privacy-timeout.test.mjs test/symlinks.test.mjs"
|
|
41
|
+
"test": "node test/lint.test.mjs && node test/action.test.mjs && node --test test/permissions-output.test.mjs test/cli-modes.test.mjs test/privacy-timeout.test.mjs test/symlinks.test.mjs test/content-script-types.test.mjs"
|
|
42
42
|
},
|
|
43
43
|
"repository": {
|
|
44
44
|
"type": "git",
|
package/src/rules.mjs
CHANGED
|
@@ -128,6 +128,30 @@ function bareOf(files) {
|
|
|
128
128
|
// reads text files, so an icon that is present would be invisible to the check
|
|
129
129
|
// and would report as missing on every package in the world.
|
|
130
130
|
const norm = (p) => String(p).split(/[\\/]/).join("/").replace(/^\.\//, "").replace(/[?#].*$/, "");
|
|
131
|
+
|
|
132
|
+
// Chromium validates content-script types from the filename, before reading
|
|
133
|
+
// the resource. A bad type causes the whole content_scripts entry to be skipped.
|
|
134
|
+
// SCSS is explicitly allowed for compatibility; this does not compile Sass.
|
|
135
|
+
// Verified against extensions/common/utils/content_script_utils.cc and
|
|
136
|
+
// net/base/mime_util.cc in Chromium, plus browser probes on 10 October 2026.
|
|
137
|
+
const supportedContentFile = (path, type) =>
|
|
138
|
+
(type === "js" ? /\.(?:js|mjs)$/i : /\.(?:css|scss)$/i).test(path);
|
|
139
|
+
|
|
140
|
+
function invalidContentFiles(manifest) {
|
|
141
|
+
const out = [];
|
|
142
|
+
const scripts = Array.isArray(manifest?.content_scripts) ? manifest.content_scripts : [];
|
|
143
|
+
for (const [index, script] of scripts.entries()) {
|
|
144
|
+
for (const type of ["js", "css"]) {
|
|
145
|
+
for (const path of Array.isArray(script?.[type]) ? script[type] : []) {
|
|
146
|
+
if (typeof path === "string" && !supportedContentFile(path, type)) {
|
|
147
|
+
out.push({ path, key: `content_scripts[${index}].${type}` });
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return out;
|
|
153
|
+
}
|
|
154
|
+
|
|
131
155
|
function declaredFiles(manifest) {
|
|
132
156
|
const m = manifest || {};
|
|
133
157
|
const out = [];
|
|
@@ -136,8 +160,10 @@ function declaredFiles(manifest) {
|
|
|
136
160
|
add(m.background?.page, "background.page");
|
|
137
161
|
for (const s of m.background?.scripts || []) add(s, "background.scripts");
|
|
138
162
|
for (const cs of m.content_scripts || []) {
|
|
139
|
-
|
|
140
|
-
|
|
163
|
+
// Unsupported types get their own loading warning. They may exist even
|
|
164
|
+
// when the scanner does not read that suffix, so do not call them missing.
|
|
165
|
+
for (const j of cs.js || []) if (supportedContentFile(j, "js")) add(j, "content_scripts[].js");
|
|
166
|
+
for (const c of cs.css || []) if (supportedContentFile(c, "css")) add(c, "content_scripts[].css");
|
|
141
167
|
}
|
|
142
168
|
add(m.action?.default_popup, "action.default_popup");
|
|
143
169
|
add(m.browser_action?.default_popup, "browser_action.default_popup");
|
|
@@ -167,6 +193,23 @@ function missingDeclaredOf(manifest, files, skipped = []) {
|
|
|
167
193
|
|
|
168
194
|
export const RULES = [
|
|
169
195
|
|
|
196
|
+
{
|
|
197
|
+
id: "content-script-file-types",
|
|
198
|
+
run({ manifest }) {
|
|
199
|
+
const invalid = invalidContentFiles(manifest);
|
|
200
|
+
if (!invalid.length) return [];
|
|
201
|
+
return [finding({
|
|
202
|
+
severity: "warn",
|
|
203
|
+
title: `${invalid.length} content-script resource(s) have an unsupported file type`,
|
|
204
|
+
detail: "Chrome skips an entire content_scripts entry when any of its js or css filenames has an unsupported file type. "
|
|
205
|
+
+ "Point js at built .js or .mjs files, and css at .css files, then reload the unpacked extension to verify it runs. "
|
|
206
|
+
+ "The .user.js suffix is supported. Chrome also accepts .scss for compatibility, but does not compile Sass. "
|
|
207
|
+
+ "This is a loading check, not a Chrome Web Store policy violation or a reason to delete permissions.",
|
|
208
|
+
evidence: invalid.map(d => ({ file: "manifest.json", line: 1, text: `${d.key}: ${d.path}` })),
|
|
209
|
+
})];
|
|
210
|
+
},
|
|
211
|
+
},
|
|
212
|
+
|
|
170
213
|
{
|
|
171
214
|
id: "symbolic-links",
|
|
172
215
|
run({ skipped = [] }) {
|
|
@@ -1227,8 +1270,11 @@ export const RULES = [
|
|
|
1227
1270
|
// names dist/background.js and we were linting a folder of seven JSON
|
|
1228
1271
|
// files - "never used" there is a claim about nothing at all.
|
|
1229
1272
|
const gone = missingDeclaredOf(manifest, files, skipped);
|
|
1230
|
-
|
|
1231
|
-
|
|
1273
|
+
const invalidTypes = invalidContentFiles(manifest);
|
|
1274
|
+
if (unread.length || minified.length || bare.length || gone.length || invalidTypes.length) {
|
|
1275
|
+
const why = invalidTypes.length
|
|
1276
|
+
? `a content_scripts entry has an unsupported file type (${invalidTypes[0].path}); fix that loading error before deciding whether these permissions are unused`
|
|
1277
|
+
: gone.length
|
|
1232
1278
|
? `manifest.json names ${gone.length} file(s) that are not in this directory (${gone[0].key}: ${gone[0].path}), so the code that uses these permissions is not here to be read`
|
|
1233
1279
|
: unread.length
|
|
1234
1280
|
? `${unread.length} code file(s) in this package were not read (${unread[0].path}: ${unread[0].why}), so this scan did not see all of your code`
|
package/src/scan.mjs
CHANGED
|
@@ -100,7 +100,7 @@ export function buildHint(root, exists = existsSync) {
|
|
|
100
100
|
const MAX_BYTES = 2 * 1024 * 1024;
|
|
101
101
|
const TEXT_EXT = new Set([
|
|
102
102
|
".js", ".mjs", ".cjs", ".ts", ".jsx", ".tsx", ".json", ".html", ".htm",
|
|
103
|
-
".css", ".txt", ".md", ".webmanifest",
|
|
103
|
+
".css", ".scss", ".txt", ".md", ".webmanifest",
|
|
104
104
|
]);
|
|
105
105
|
|
|
106
106
|
// A link may name a file or a whole directory. Its descendants are unknown,
|