@oxy.so/doctor 1.1.1 → 1.2.0
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 -3
- package/package.json +1 -1
- package/src/checks.mjs +92 -13
package/README.md
CHANGED
|
@@ -15,9 +15,14 @@ bunx @oxy.so/doctor --ci --grace-days=7
|
|
|
15
15
|
```
|
|
16
16
|
|
|
17
17
|
It checks direct dependencies under the canonical `@oxy.so/*` scope against
|
|
18
|
-
npm, reports duplicate
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
npm, reports duplicate versions in `bun.lock`, and fails clearly when the
|
|
19
|
+
lockfile is missing.
|
|
20
|
+
|
|
21
|
+
It reads the manifests Bun installs from: the root `package.json` and the
|
|
22
|
+
workspaces its `workspaces` patterns match. A `catalog:` dependency is checked
|
|
23
|
+
where its range lives, the root's `workspaces.catalog` (or `catalogs.<name>`).
|
|
24
|
+
A `package.json` outside the workspaces, such as a frozen copy kept as audit
|
|
25
|
+
evidence, is not this repository's dependency state and is not read.
|
|
21
26
|
|
|
22
27
|
A range that doesn't include the newest published `@oxy.so/*` release is a
|
|
23
28
|
**warning** for 14 days after that release was published (`--grace-days=N`
|
package/package.json
CHANGED
package/src/checks.mjs
CHANGED
|
@@ -74,18 +74,85 @@ export async function findRepositoryRoot(start = process.cwd()) {
|
|
|
74
74
|
}
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
77
|
+
const IGNORED_DIRECTORIES = new Set([
|
|
78
|
+
'.git',
|
|
79
|
+
'.expo',
|
|
80
|
+
'.turbo',
|
|
81
|
+
'.worktrees',
|
|
82
|
+
'dist',
|
|
83
|
+
'lib',
|
|
84
|
+
'node_modules',
|
|
85
|
+
]);
|
|
86
|
+
|
|
87
|
+
async function childDirectories(directory) {
|
|
88
|
+
const entries = await readdir(directory, { withFileTypes: true }).catch(() => []);
|
|
89
|
+
return entries
|
|
90
|
+
.filter((entry) => entry.isDirectory() && !IGNORED_DIRECTORIES.has(entry.name))
|
|
91
|
+
.map((entry) => join(directory, entry.name));
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** Directories matching one Bun workspace pattern (`dir`, `dir/*`, `dir/**`). */
|
|
95
|
+
async function expandWorkspacePattern(directory, segments) {
|
|
96
|
+
if (segments.length === 0) return [directory];
|
|
97
|
+
const [segment, ...rest] = segments;
|
|
98
|
+
if (segment === '**') {
|
|
99
|
+
const here = await expandWorkspacePattern(directory, rest);
|
|
100
|
+
const deeper = await Promise.all(
|
|
101
|
+
(await childDirectories(directory)).map((child) => expandWorkspacePattern(child, segments)),
|
|
102
|
+
);
|
|
103
|
+
return [...here, ...deeper.flat()];
|
|
104
|
+
}
|
|
105
|
+
if (segment.includes('*')) {
|
|
106
|
+
const matcher = new RegExp(
|
|
107
|
+
`^${segment.replace(/[.+?^${}()|[\]\\]/g, '\\$&').replace(/\*/g, '.*')}$`,
|
|
108
|
+
);
|
|
109
|
+
const children = (await childDirectories(directory)).filter((child) =>
|
|
110
|
+
matcher.test(child.slice(directory.length + 1)),
|
|
111
|
+
);
|
|
112
|
+
return (await Promise.all(children.map((child) => expandWorkspacePattern(child, rest)))).flat();
|
|
113
|
+
}
|
|
114
|
+
return expandWorkspacePattern(join(directory, segment), rest);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* The manifests Bun installs from: the root and its workspaces. A package.json
|
|
119
|
+
* outside them (a frozen copy kept as audit evidence, a fixture) is not
|
|
120
|
+
* installed from this lockfile, so it is not this repository's dependency state.
|
|
121
|
+
*/
|
|
122
|
+
async function collectManifests(root, rootManifest) {
|
|
123
|
+
const workspaces = rootManifest.workspaces;
|
|
124
|
+
const patterns = Array.isArray(workspaces) ? workspaces : (workspaces?.packages ?? []);
|
|
125
|
+
const directories = new Set([root]);
|
|
126
|
+
const excluded = new Set();
|
|
127
|
+
for (const pattern of patterns) {
|
|
128
|
+
const negated = pattern.startsWith('!');
|
|
129
|
+
const segments = (negated ? pattern.slice(1) : pattern)
|
|
130
|
+
.replace(/^\.\//, '')
|
|
131
|
+
.split('/')
|
|
132
|
+
.filter(Boolean);
|
|
133
|
+
for (const directory of await expandWorkspacePattern(root, segments))
|
|
134
|
+
(negated ? excluded : directories).add(directory);
|
|
87
135
|
}
|
|
88
|
-
return
|
|
136
|
+
return [...directories]
|
|
137
|
+
.filter((directory) => !excluded.has(directory))
|
|
138
|
+
.map((directory) => join(directory, 'package.json'))
|
|
139
|
+
.filter((path) => existsSync(path))
|
|
140
|
+
.sort();
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** `catalog:` / `catalog:<name>` resolved against the root manifest's catalogs. */
|
|
144
|
+
function catalogEntries(rootManifest) {
|
|
145
|
+
const workspaces = Array.isArray(rootManifest.workspaces) ? {} : (rootManifest.workspaces ?? {});
|
|
146
|
+
const rows = [];
|
|
147
|
+
const add = (section, catalog) => {
|
|
148
|
+
for (const [name, range] of Object.entries(catalog ?? {})) rows.push({ section, name, range });
|
|
149
|
+
};
|
|
150
|
+
add('catalog', workspaces.catalog ?? rootManifest.catalog);
|
|
151
|
+
for (const [catalogName, catalog] of Object.entries(
|
|
152
|
+
workspaces.catalogs ?? rootManifest.catalogs ?? {},
|
|
153
|
+
))
|
|
154
|
+
add(`catalogs.${catalogName}`, catalog);
|
|
155
|
+
return rows;
|
|
89
156
|
}
|
|
90
157
|
|
|
91
158
|
/**
|
|
@@ -98,8 +165,15 @@ export async function inspectRepository(
|
|
|
98
165
|
fetchLatest,
|
|
99
166
|
{ graceDays = DEFAULT_GRACE_DAYS, now = new Date() } = {},
|
|
100
167
|
) {
|
|
101
|
-
const
|
|
168
|
+
const rootManifest = JSON.parse(await readFile(join(root, 'package.json'), 'utf8'));
|
|
169
|
+
const manifestPaths = await collectManifests(root, rootManifest);
|
|
102
170
|
const usages = new Map();
|
|
171
|
+
for (const { section, name, range } of catalogEntries(rootManifest)) {
|
|
172
|
+
if (!isFirstPartyPackage(name)) continue;
|
|
173
|
+
const rows = usages.get(name) ?? [];
|
|
174
|
+
rows.push({ manifest: 'package.json', section, range });
|
|
175
|
+
usages.set(name, rows);
|
|
176
|
+
}
|
|
103
177
|
for (const path of manifestPaths) {
|
|
104
178
|
const manifest = JSON.parse(await readFile(path, 'utf8'));
|
|
105
179
|
for (const section of [
|
|
@@ -109,7 +183,12 @@ export async function inspectRepository(
|
|
|
109
183
|
'optionalDependencies',
|
|
110
184
|
]) {
|
|
111
185
|
for (const [name, range] of Object.entries(manifest[section] ?? {})) {
|
|
112
|
-
if (
|
|
186
|
+
if (
|
|
187
|
+
!isFirstPartyPackage(name) ||
|
|
188
|
+
range.startsWith('workspace:') ||
|
|
189
|
+
range.startsWith('catalog:')
|
|
190
|
+
)
|
|
191
|
+
continue;
|
|
113
192
|
const rows = usages.get(name) ?? [];
|
|
114
193
|
rows.push({ manifest: path.slice(root.length + 1), section, range });
|
|
115
194
|
usages.set(name, rows);
|