codex-webapp 0.1.6 → 0.1.8

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.
@@ -0,0 +1,248 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { spawnSync } from "node:child_process";
3
+ import { fileURLToPath } from "node:url";
4
+ import path from "node:path";
5
+
6
+ const DEPENDENCY_FIELDS = [
7
+ "dependencies",
8
+ "devDependencies",
9
+ "optionalDependencies",
10
+ "peerDependencies",
11
+ "bundleDependencies",
12
+ "bundledDependencies",
13
+ ];
14
+
15
+ const PRIVATE_PACKAGE_NAMES = new Set([
16
+ "penso-render-envelope",
17
+ "@penso/penso-render-envelope",
18
+ "@penso-os/render-envelope",
19
+ ]);
20
+
21
+ const PRIVATE_PACKAGE_PREFIXES = [
22
+ "@penso/",
23
+ ];
24
+
25
+ const PRIVATE_SPEC_PATTERNS = [
26
+ { label: "GitHub dependency shorthand", pattern: /^github:/i },
27
+ { label: "Git dependency URL", pattern: /^(?:git\+|git:\/\/|ssh:\/\/)/i },
28
+ { label: "GitHub URL dependency", pattern: /(?:^|[/:@])github\.com[/:]/i },
29
+ { label: "GitHub package registry URL", pattern: /(?:^|[/:@])npm\.pkg\.github\.com[/:]/i },
30
+ { label: "local file dependency", pattern: /^file:/i },
31
+ { label: "local link dependency", pattern: /^link:/i },
32
+ { label: "workspace dependency", pattern: /^workspace:/i },
33
+ { label: "private package alias", pattern: /(?:^|:)@penso\//i },
34
+ { label: "private package alias", pattern: /(?:^|:)@penso-os\/render-envelope(?:$|@)/i },
35
+ { label: "private engine package", pattern: /(?:^|[/:@])penso-render-envelope(?:$|[#/:@])/i },
36
+ ];
37
+
38
+ const INSTALL_LIFECYCLE_SCRIPTS = new Set([
39
+ "preinstall",
40
+ "install",
41
+ "postinstall",
42
+ ]);
43
+
44
+ const PRIVATE_PACK_PATH_PATTERNS = [
45
+ /(?:^|\/)penso-render-envelope(?:\/|$)/i,
46
+ /(?:^|\/)\.env(?:\.|$)/i,
47
+ /(?:^|\/)\.npmrc$/i,
48
+ /(?:^|\/)(?:secret|secrets)(?:\/|$)/i,
49
+ /(?:^|\/)patent-claims?(?:\/|$)/i,
50
+ ];
51
+
52
+ function dependencyEntries(value) {
53
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
54
+ return [];
55
+ }
56
+ return Object.entries(value).map(([name, spec]) => [name, String(spec)]);
57
+ }
58
+
59
+ function isPrivatePackageName(name) {
60
+ return PRIVATE_PACKAGE_NAMES.has(name)
61
+ || PRIVATE_PACKAGE_PREFIXES.some((prefix) => name.startsWith(prefix));
62
+ }
63
+
64
+ function lockPackageName(packagePath) {
65
+ const marker = "node_modules/";
66
+ const index = packagePath.lastIndexOf(marker);
67
+ if (index === -1) {
68
+ return null;
69
+ }
70
+ const suffix = packagePath.slice(index + marker.length);
71
+ const parts = suffix.split("/");
72
+ if (parts[0]?.startsWith("@") && parts.length >= 2) {
73
+ return `${parts[0]}/${parts[1]}`;
74
+ }
75
+ return parts[0] || null;
76
+ }
77
+
78
+ export function findDependencyBoundaryViolations({ manifest, lockfile }) {
79
+ const violations = [];
80
+
81
+ checkManifestPolicy(manifest, violations);
82
+
83
+ for (const field of DEPENDENCY_FIELDS) {
84
+ for (const [name, spec] of dependencyEntries(manifest[field])) {
85
+ checkDependencyEntry({ source: `package.json ${field}`, name, spec, violations });
86
+ }
87
+ }
88
+
89
+ const rootPackage = lockfile?.packages?.[""];
90
+ if (rootPackage) {
91
+ for (const field of DEPENDENCY_FIELDS) {
92
+ for (const [name, spec] of dependencyEntries(rootPackage[field])) {
93
+ checkDependencyEntry({ source: `package-lock.json root ${field}`, name, spec, violations });
94
+ }
95
+ }
96
+ }
97
+
98
+ for (const [packagePath, packageMeta] of Object.entries(lockfile?.packages || {})) {
99
+ const name = lockPackageName(packagePath);
100
+ if (name && isPrivatePackageName(name)) {
101
+ violations.push(`${packagePath} uses private package name ${name}`);
102
+ }
103
+
104
+ if (packageMeta?.resolved) {
105
+ checkSpec({
106
+ source: `package-lock.json ${packagePath || "<root>"} resolved`,
107
+ name: name || packageMeta.name || "<unknown>",
108
+ spec: String(packageMeta.resolved),
109
+ violations,
110
+ });
111
+ }
112
+
113
+ for (const field of DEPENDENCY_FIELDS) {
114
+ for (const [depName, spec] of dependencyEntries(packageMeta?.[field])) {
115
+ checkDependencyEntry({
116
+ source: `package-lock.json ${packagePath || "<root>"} ${field}`,
117
+ name: depName,
118
+ spec,
119
+ violations,
120
+ });
121
+ }
122
+ }
123
+ }
124
+
125
+ for (const [name, meta] of Object.entries(lockfile?.dependencies || {})) {
126
+ if (isPrivatePackageName(name)) {
127
+ violations.push(`package-lock.json dependencies uses private package name ${name}`);
128
+ }
129
+ if (meta?.version) {
130
+ checkSpec({
131
+ source: `package-lock.json dependencies ${name} version`,
132
+ name,
133
+ spec: String(meta.version),
134
+ violations,
135
+ });
136
+ }
137
+ if (meta?.resolved) {
138
+ checkSpec({
139
+ source: `package-lock.json dependencies ${name} resolved`,
140
+ name,
141
+ spec: String(meta.resolved),
142
+ violations,
143
+ });
144
+ }
145
+ }
146
+
147
+ return violations;
148
+ }
149
+
150
+ export async function checkPublicPackageBoundary({
151
+ rootDir = process.cwd(),
152
+ runPackDryRun = true,
153
+ } = {}) {
154
+ const manifestPath = path.join(rootDir, "package.json");
155
+ const lockfilePath = path.join(rootDir, "package-lock.json");
156
+ const manifest = JSON.parse(await readFile(manifestPath, "utf8"));
157
+ const lockfile = JSON.parse(await readFile(lockfilePath, "utf8"));
158
+ const violations = findDependencyBoundaryViolations({ manifest, lockfile });
159
+
160
+ if (runPackDryRun) {
161
+ violations.push(...checkPackDryRun(rootDir));
162
+ }
163
+
164
+ return { violations };
165
+ }
166
+
167
+ export function checkPackEntries(files) {
168
+ const violations = [];
169
+ for (const file of files) {
170
+ const packPath = String(file.path || file);
171
+ for (const pattern of PRIVATE_PACK_PATH_PATTERNS) {
172
+ if (pattern.test(packPath)) {
173
+ violations.push(`npm pack would include private-boundary file path ${packPath}`);
174
+ }
175
+ }
176
+ }
177
+ return violations;
178
+ }
179
+
180
+ function checkManifestPolicy(manifest, violations) {
181
+ for (const scriptName of Object.keys(manifest.scripts || {})) {
182
+ if (INSTALL_LIFECYCLE_SCRIPTS.has(scriptName)) {
183
+ violations.push(`package.json scripts.${scriptName} is not allowed in the public package install path`);
184
+ }
185
+ }
186
+
187
+ const registry = manifest.publishConfig?.registry;
188
+ if (registry && !/^https:\/\/registry\.npmjs\.org\/?$/i.test(String(registry))) {
189
+ violations.push(`package.json publishConfig.registry is not public npm: ${registry}`);
190
+ }
191
+ }
192
+
193
+ function checkDependencyEntry({ source, name, spec, violations }) {
194
+ if (isPrivatePackageName(name)) {
195
+ violations.push(`${source} uses private package name ${name}`);
196
+ }
197
+ checkSpec({ source, name, spec, violations });
198
+ }
199
+
200
+ function checkSpec({ source, name, spec, violations }) {
201
+ for (const { label, pattern } of PRIVATE_SPEC_PATTERNS) {
202
+ if (pattern.test(spec)) {
203
+ violations.push(`${source} ${name} uses ${label}: ${spec}`);
204
+ }
205
+ }
206
+ if (/ resolved$/.test(source)
207
+ && /^https?:\/\//i.test(spec)
208
+ && !/^https:\/\/registry\.npmjs\.org\//i.test(spec)
209
+ ) {
210
+ violations.push(`${source} ${name} uses non-public-npm registry URL: ${spec}`);
211
+ }
212
+ }
213
+
214
+ function checkPackDryRun(rootDir) {
215
+ const result = spawnSync("npm", ["pack", "--dry-run", "--json", "--ignore-scripts"], {
216
+ cwd: rootDir,
217
+ encoding: "utf8",
218
+ });
219
+
220
+ if (result.status !== 0) {
221
+ return [`npm pack --dry-run failed: ${result.stderr || result.stdout}`.trim()];
222
+ }
223
+
224
+ let parsed;
225
+ try {
226
+ parsed = JSON.parse(result.stdout);
227
+ } catch (error) {
228
+ return [`npm pack --dry-run returned non-JSON output: ${error.message}`];
229
+ }
230
+
231
+ const files = parsed.flatMap((entry) => entry.files || []);
232
+ return checkPackEntries(files);
233
+ }
234
+
235
+ const isDirectRun = process.argv[1]
236
+ && fileURLToPath(import.meta.url) === path.resolve(process.argv[1]);
237
+
238
+ if (isDirectRun) {
239
+ const { violations } = await checkPublicPackageBoundary();
240
+ if (violations.length > 0) {
241
+ console.error("Public package boundary check failed:");
242
+ for (const violation of violations) {
243
+ console.error(`- ${violation}`);
244
+ }
245
+ process.exit(1);
246
+ }
247
+ console.log("Public package boundary check passed.");
248
+ }