






















@@ -0,0 +1,120 @@
1+import fs from "node:fs";
2+import path from "node:path";
3+const SOURCE_ROOTS = ["src", "extensions"];
4+const DEFAULT_SKIPPED_DIR_NAMES = new Set(["node_modules", "dist", "coverage", ".generated"]);
5+6+function isCodeFile(filePath) {
7+if (filePath.endsWith(".d.ts")) {
8+return false;
9+}
10+return /\.(?:[cm]?ts|[cm]?js|tsx|jsx)$/u.test(filePath);
11+}
12+13+function collectFilesSync(rootDir, options) {
14+const skipDirNames = options.skipDirNames ?? DEFAULT_SKIPPED_DIR_NAMES;
15+const files = [];
16+const stack = [rootDir];
17+18+while (stack.length > 0) {
19+const current = stack.pop();
20+if (!current) {
21+continue;
22+}
23+let entries = [];
24+try {
25+entries = fs.readdirSync(current, { withFileTypes: true });
26+} catch {
27+continue;
28+}
29+for (const entry of entries) {
30+const fullPath = path.join(current, entry.name);
31+if (entry.isDirectory()) {
32+if (!skipDirNames.has(entry.name)) {
33+stack.push(fullPath);
34+}
35+continue;
36+}
37+if (entry.isFile() && options.includeFile(fullPath)) {
38+files.push(fullPath);
39+}
40+}
41+}
42+43+return files;
44+}
45+46+function toPosixPath(filePath) {
47+return filePath.replaceAll("\\", "/");
48+}
49+50+const FORBIDDEN_HTTP2_MODULES = new Set(["node:http2", "http2"]);
51+const ALLOWED_PRODUCTION_FILES = new Set(["src/infra/push-apns-http2.ts"]);
52+53+function isTestFile(relativePath) {
54+return (
55+/(?:^|\/)(?:test|test-fixtures)\//u.test(relativePath) ||
56+/\.test\.[cm]?[jt]sx?$/u.test(relativePath)
57+);
58+}
59+60+function lineNumberForOffset(content, offset) {
61+return content.slice(0, offset).split(/\r?\n/u).length;
62+}
63+64+function collectHttp2ImportOffenders(filePath) {
65+const relativePath = toPosixPath(path.relative(process.cwd(), filePath));
66+if (ALLOWED_PRODUCTION_FILES.has(relativePath) || isTestFile(relativePath)) {
67+return [];
68+}
69+70+const content = fs.readFileSync(filePath, "utf8");
71+const offenders = [];
72+const patterns = [
73+/\bimport\s+(?:type\s+)?[\s\S]*?\bfrom\s*["']([^"']+)["']/gu,
74+/\bexport\s+(?:type\s+)?[\s\S]*?\bfrom\s*["']([^"']+)["']/gu,
75+/\bimport\s*\(\s*["']([^"']+)["']\s*\)/gu,
76+/\brequire\s*\(\s*["']([^"']+)["']\s*\)/gu,
77+];
78+79+for (const pattern of patterns) {
80+for (const match of content.matchAll(pattern)) {
81+const specifier = match[1];
82+if (specifier && FORBIDDEN_HTTP2_MODULES.has(specifier)) {
83+offenders.push({
84+file: relativePath,
85+line: lineNumberForOffset(content, match.index ?? 0),
86+ specifier,
87+});
88+}
89+}
90+}
91+92+return offenders;
93+}
94+95+function collectSourceFiles() {
96+return SOURCE_ROOTS.flatMap((root) =>
97+collectFilesSync(path.join(process.cwd(), root), {
98+includeFile: isCodeFile,
99+}),
100+);
101+}
102+103+function main() {
104+const offenders = collectSourceFiles().flatMap(collectHttp2ImportOffenders);
105+if (offenders.length === 0) {
106+console.log("OK: raw node:http2 imports stay behind the APNs proxy wrapper.");
107+return;
108+}
109+110+console.error("Raw node:http2 imports are only allowed in src/infra/push-apns-http2.ts.");
111+for (const offender of offenders.toSorted(
112+(a, b) => a.file.localeCompare(b.file) || a.line - b.line,
113+)) {
114+console.error(`- ${offender.file}:${offender.line} imports ${offender.specifier}`);
115+}
116+console.error("Use connectApnsHttp2Session() so APNs HTTP/2 honors managed proxy policy.");
117+process.exit(1);
118+}
119+120+main();
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。