webstore-lint 1.0.30 → 1.0.32
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/bin/webstore-lint.mjs +11 -1
- package/package.json +2 -2
- package/src/privacy.mjs +12 -12
package/README.md
CHANGED
|
@@ -64,6 +64,10 @@ git clone https://github.com/Circadian-agent/chrome-extension-rejection-lint
|
|
|
64
64
|
node chrome-extension-rejection-lint/bin/webstore-lint.mjs ./my-extension
|
|
65
65
|
```
|
|
66
66
|
|
|
67
|
+
Pass one extension directory per command. If you maintain several extensions,
|
|
68
|
+
run the command separately for each one. Multiple directories, including those
|
|
69
|
+
expanded from a shell wildcard, are rejected before any checks run.
|
|
70
|
+
|
|
67
71
|
## In GitHub Actions
|
|
68
72
|
|
|
69
73
|
```yaml
|
|
@@ -343,6 +347,10 @@ What it reports:
|
|
|
343
347
|
It checks *reachability*. It does not judge whether your policy is adequate,
|
|
344
348
|
which is the thing a reviewer actually reads it for.
|
|
345
349
|
|
|
350
|
+
The request has a ten-second deadline, including downloading the page body.
|
|
351
|
+
A timeout or interrupted download produces a network warning, with no verdict
|
|
352
|
+
on the unread page's content.
|
|
353
|
+
|
|
346
354
|
## What it cannot do
|
|
347
355
|
|
|
348
356
|
It reads your package. It cannot see your store listing, your screenshots or your
|
package/bin/webstore-lint.mjs
CHANGED
|
@@ -70,7 +70,8 @@ for (const f of VALUED) {
|
|
|
70
70
|
const i = args.indexOf(f);
|
|
71
71
|
if (i >= 0 && args[i + 1] && !args[i + 1].startsWith("--")) consumed.add(i + 1);
|
|
72
72
|
}
|
|
73
|
-
const
|
|
73
|
+
const targets = args.filter((a, i) => !a.startsWith("--") && !consumed.has(i));
|
|
74
|
+
const target = targets[0];
|
|
74
75
|
const policyUrl = valueOf("--privacy-policy");
|
|
75
76
|
|
|
76
77
|
if (flags.has("--help") || (!target && !flags.has("--policy"))) {
|
|
@@ -89,6 +90,7 @@ if (flags.has("--help") || (!target && !flags.has("--policy"))) {
|
|
|
89
90
|
webstore-lint --policy list the policy data this build carries
|
|
90
91
|
|
|
91
92
|
Use --permissions, --privacy-policy and --policy in separate commands.
|
|
93
|
+
Check one extension directory per command.
|
|
92
94
|
|
|
93
95
|
The one-liner, no install needed:
|
|
94
96
|
npx webstore-lint <extension-directory>
|
|
@@ -102,6 +104,14 @@ not affiliated with Google. A clean run is not a promise of approval.`);
|
|
|
102
104
|
process.exit(flags.has("--help") ? 0 : 2);
|
|
103
105
|
}
|
|
104
106
|
|
|
107
|
+
// A second directory (including a shell-expanded glob) must not be silently
|
|
108
|
+
// ignored: the first extension can pass while the unexamined one would fail.
|
|
109
|
+
if (targets.length > 1) {
|
|
110
|
+
console.error("Pass only one extension directory per command. No checks were run.");
|
|
111
|
+
console.error("Run webstore-lint separately for each extension directory.");
|
|
112
|
+
process.exit(2);
|
|
113
|
+
}
|
|
114
|
+
|
|
105
115
|
// These branches produce different reports and return early. Combining them
|
|
106
116
|
// used to exit successfully after silently skipping an explicitly requested
|
|
107
117
|
// check, including a broken privacy URL. Reject before producing any report.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "webstore-lint",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.32",
|
|
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"
|
|
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"
|
|
42
42
|
},
|
|
43
43
|
"repository": {
|
|
44
44
|
"type": "git",
|
package/src/privacy.mjs
CHANGED
|
@@ -118,6 +118,7 @@ export async function checkPolicyUrl(url, { timeoutMs = 10000, fetchImpl = fetch
|
|
|
118
118
|
const ctl = new AbortController();
|
|
119
119
|
const timer = setTimeout(() => ctl.abort(), timeoutMs);
|
|
120
120
|
let res;
|
|
121
|
+
let body;
|
|
121
122
|
try {
|
|
122
123
|
res = await fetchImpl(u.href, {
|
|
123
124
|
signal: ctl.signal,
|
|
@@ -129,24 +130,30 @@ export async function checkPolicyUrl(url, { timeoutMs = 10000, fetchImpl = fetch
|
|
|
129
130
|
"user-agent": "webstore-lint (privacy policy reachability check)",
|
|
130
131
|
},
|
|
131
132
|
});
|
|
133
|
+
// fetch resolves at headers. Keep the deadline active until the body is
|
|
134
|
+
// complete too, or a stalled page can hang the CLI and Action indefinitely.
|
|
135
|
+
if (res.ok) body = await res.text();
|
|
136
|
+
else ctl.abort(); // The status is enough evidence; do not download its body.
|
|
132
137
|
} catch (e) {
|
|
133
|
-
clearTimeout(timer);
|
|
134
138
|
// COULD NOT LOOK IS NOT THE SAME FACT AS NOT THERE. A DNS failure on the
|
|
135
139
|
// developer's own machine, an offline CI runner or a proxy is not evidence
|
|
136
140
|
// about the URL, so this warns and says which it is rather than failing.
|
|
137
|
-
const aborted = e.name === "AbortError";
|
|
141
|
+
const aborted = ctl.signal.aborted || e.name === "AbortError";
|
|
138
142
|
return [...findings, f(
|
|
139
143
|
"warn",
|
|
140
144
|
aborted
|
|
141
|
-
? `The privacy policy URL did not
|
|
142
|
-
:
|
|
145
|
+
? `The privacy policy URL did not finish loading within ${timeoutMs / 1000}s`
|
|
146
|
+
: res
|
|
147
|
+
? `The privacy policy response could not be read from here: ${e.message}`
|
|
148
|
+
: `The privacy policy URL could not be reached from here: ${e.message}`,
|
|
143
149
|
"This is not the same as the address being broken. It may be your network, a proxy, or an offline " +
|
|
144
150
|
"runner. Open it yourself in a private browser window before you trust either answer. " +
|
|
145
151
|
"A reviewer will be doing exactly that.",
|
|
146
152
|
[{ file: "--privacy-policy", text: url }],
|
|
147
153
|
)];
|
|
154
|
+
} finally {
|
|
155
|
+
clearTimeout(timer);
|
|
148
156
|
}
|
|
149
|
-
clearTimeout(timer);
|
|
150
157
|
|
|
151
158
|
const finalUrl = res.url || u.href;
|
|
152
159
|
const redirected = finalUrl.replace(/\/$/, "") !== u.href.replace(/\/$/, "");
|
|
@@ -163,13 +170,6 @@ export async function checkPolicyUrl(url, { timeoutMs = 10000, fetchImpl = fetch
|
|
|
163
170
|
)];
|
|
164
171
|
}
|
|
165
172
|
|
|
166
|
-
let body = "";
|
|
167
|
-
try {
|
|
168
|
-
body = await res.text();
|
|
169
|
-
} catch {
|
|
170
|
-
body = "";
|
|
171
|
-
}
|
|
172
|
-
|
|
173
173
|
// Strip tags and scripts before counting words, or a single-page app's bundle
|
|
174
174
|
// reads as a wall of text that mentions everything.
|
|
175
175
|
const text = body
|