|
1 | | -import { isUtf8 } from "node:buffer"; |
2 | | -import { |
3 | | - cpSync, |
4 | | - existsSync, |
5 | | - readFileSync, |
6 | | - readlinkSync, |
7 | | - readdirSync, |
8 | | - statSync, |
9 | | -} from "node:fs"; |
10 | | -import { |
11 | | - basename, |
12 | | - dirname, |
13 | | - isAbsolute, |
14 | | - join, |
15 | | - relative, |
16 | | - resolve, |
17 | | - sep, |
18 | | -} from "node:path"; |
| 1 | +import { cpSync } from "node:fs"; |
| 2 | +import { basename, join } from "node:path"; |
19 | 3 |
|
20 | 4 | import { |
21 | 5 | ROOT, |
22 | 6 | makeTemporaryDirectory, |
23 | 7 | removeTemporaryDirectory, |
24 | 8 | run, |
25 | 9 | } from "./lib.mjs"; |
26 | | - |
27 | | -const excluded = new Set([ |
28 | | - ".artifacts", |
29 | | - ".cache", |
30 | | - ".DS_Store", |
31 | | - ".git", |
32 | | - "coverage", |
33 | | - "dist", |
34 | | - "node_modules", |
35 | | -]); |
36 | | -const parentReferencePattern = |
37 | | - /(?:^|[\s`"'(=:[{])((?:\.\.[\\/])+[A-Za-z0-9@%_+.,~\\/-]+)/gm; |
38 | | -const absoluteLocalPathPatterns = [ |
39 | | - /(?:^|[\s`"'(=:[{])((?:file:\/\/\/(?:Users|home|private|root|Volumes|workspaces?)|\/(?:Users|home|private|root|Volumes|workspaces?))\/[A-Za-z0-9@%_+.,~/-]+)/gm, |
40 | | - /(?:^|[\s`"'(=:[{])((?:~\/|\$HOME\/|\$\{HOME\}\/)[A-Za-z0-9@%_+.,~/-]+)/gm, |
41 | | - /(?:^|[\s`"'(=:[{])([A-Za-z]:[\\/][^\s`"')\]}>;,]+)/gm, |
42 | | - /(?:^|[\s`"'(=:[{])(\\\\[A-Za-z0-9._-]+[\\/][^\s`"')\]}>;,]+)/gm, |
43 | | -]; |
44 | | -const privateArtifactPattern = /\b(?:SDK_PRD\.md|references\/)/g; |
45 | | -const privateWorkspacePathPattern = |
46 | | - /\b(?:cometapi-worksapce|cometapi-(?:python|go|cli)|comet-api-(?:backend|frontend|next))[\\/][A-Za-z0-9@%_+.~/-]{2,}/g; |
47 | | -const privateReferencesDirectory = ["references", ""].join("/"); |
| 10 | +import { |
| 11 | + collectStandaloneContentViolations, |
| 12 | + STANDALONE_CONTENT_EXCLUSIONS, |
| 13 | +} from "./standalone-content.mjs"; |
48 | 14 |
|
49 | 15 | function shouldCopy(source) { |
50 | | - return !excluded.has(basename(source)); |
51 | | -} |
52 | | - |
53 | | -function isInside(root, path) { |
54 | | - const pathFromRoot = relative(root, path); |
55 | | - return ( |
56 | | - pathFromRoot === "" || |
57 | | - (!isAbsolute(pathFromRoot) && |
58 | | - pathFromRoot !== ".." && |
59 | | - !pathFromRoot.startsWith(`..${sep}`)) |
60 | | - ); |
61 | | -} |
62 | | - |
63 | | -function scanTextFile(path, candidateRoot, violations) { |
64 | | - const bytes = readFileSync(path); |
65 | | - if (bytes.includes(0) || !isUtf8(bytes)) return; |
66 | | - |
67 | | - const contents = bytes.toString("utf8"); |
68 | | - const displayPath = relative(candidateRoot, path); |
69 | | - |
70 | | - for (const match of contents.matchAll(parentReferencePattern)) { |
71 | | - const reference = match[1]; |
72 | | - const resolvedReference = resolve( |
73 | | - dirname(path), |
74 | | - reference.replaceAll("\\", "/"), |
75 | | - ); |
76 | | - if (!isInside(candidateRoot, resolvedReference)) { |
77 | | - violations.push( |
78 | | - `${displayPath}: parent-relative path escapes the repository (${reference})`, |
79 | | - ); |
80 | | - } |
81 | | - } |
82 | | - |
83 | | - for (const pattern of absoluteLocalPathPatterns) { |
84 | | - for (const match of contents.matchAll(pattern)) { |
85 | | - violations.push( |
86 | | - `${displayPath}: absolute machine-local path is not standalone (${match[1]})`, |
87 | | - ); |
88 | | - } |
89 | | - } |
90 | | - |
91 | | - for (const match of contents.matchAll(privateArtifactPattern)) { |
92 | | - const reference = match[0]; |
93 | | - const rootEntry = reference.startsWith(privateReferencesDirectory) |
94 | | - ? "references" |
95 | | - : reference; |
96 | | - if (!existsSync(join(candidateRoot, rootEntry))) { |
97 | | - violations.push( |
98 | | - `${displayPath}: references non-repository private material (${reference})`, |
99 | | - ); |
100 | | - } |
101 | | - } |
102 | | - |
103 | | - for (const match of contents.matchAll(privateWorkspacePathPattern)) { |
104 | | - const reference = match[0]; |
105 | | - const rootEntry = reference.split(/[\\/]/, 1)[0]; |
106 | | - if (!existsSync(join(candidateRoot, rootEntry))) { |
107 | | - violations.push( |
108 | | - `${displayPath}: references a private workspace or sibling repository (${reference})`, |
109 | | - ); |
110 | | - } |
111 | | - } |
112 | | -} |
113 | | - |
114 | | -function scan(directory, candidateRoot, violations) { |
115 | | - for (const entry of readdirSync(directory, { withFileTypes: true })) { |
116 | | - if (entry.isDirectory() && excluded.has(entry.name)) continue; |
117 | | - const path = join(directory, entry.name); |
118 | | - if (entry.isDirectory()) { |
119 | | - scan(path, candidateRoot, violations); |
120 | | - continue; |
121 | | - } |
122 | | - if (entry.isSymbolicLink()) { |
123 | | - const target = readlinkSync(path); |
124 | | - const resolvedTarget = resolve(dirname(path), target); |
125 | | - if (!isInside(candidateRoot, resolvedTarget)) { |
126 | | - violations.push( |
127 | | - `${relative(candidateRoot, path)}: symbolic link escapes the repository (${target})`, |
128 | | - ); |
129 | | - continue; |
130 | | - } |
131 | | - if (!existsSync(resolvedTarget)) { |
132 | | - violations.push( |
133 | | - `${relative(candidateRoot, path)}: symbolic link target is missing (${target})`, |
134 | | - ); |
135 | | - continue; |
136 | | - } |
137 | | - if (statSync(path).isDirectory()) continue; |
138 | | - } |
139 | | - scanTextFile(path, candidateRoot, violations); |
140 | | - } |
| 16 | + return !STANDALONE_CONTENT_EXCLUSIONS.has(basename(source)); |
141 | 17 | } |
142 | 18 |
|
143 | 19 | const temporaryParent = makeTemporaryDirectory("cometapi-standalone-"); |
144 | 20 | const candidateRoot = join(temporaryParent, "cometapi-node"); |
145 | 21 |
|
146 | 22 | try { |
147 | 23 | cpSync(ROOT, candidateRoot, { filter: shouldCopy, recursive: true }); |
148 | | - const violations = []; |
149 | | - scan(candidateRoot, candidateRoot, violations); |
| 24 | + const violations = collectStandaloneContentViolations(candidateRoot); |
150 | 25 | if (violations.length > 0) { |
151 | 26 | throw new Error( |
152 | 27 | `Standalone repository scan found ${String(violations.length)} outside-root reference(s):\n- ${violations.join("\n- ")}`, |
|
0 commit comments