poe-code 3.0.392 → 3.0.393
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/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/skills.js +2875 -54
- package/dist/skills.js.map +7 -1
- package/package.json +1 -1
- package/packages/package-lint/dist/model.d.ts +9 -0
- package/packages/package-lint/dist/model.js +89 -2
- package/packages/package-lint/dist/rules/shipped-dist-deps-unresolvable.js +34 -1
- package/packages/package-lint/dist/source-imports.d.ts +2 -2
- package/packages/package-lint/dist/source-imports.js +28 -1
package/package.json
CHANGED
|
@@ -34,6 +34,7 @@ export interface PackageInfo {
|
|
|
34
34
|
bundledDependencies: string[];
|
|
35
35
|
repositoryDirectory: string | undefined;
|
|
36
36
|
ecosystem: Ecosystem;
|
|
37
|
+
main: string | undefined;
|
|
37
38
|
exports: unknown;
|
|
38
39
|
bin: Record<string, string>;
|
|
39
40
|
files: string[];
|
|
@@ -49,6 +50,11 @@ export interface BinTarget {
|
|
|
49
50
|
/** Owning package dir, e.g. "packages/superintendent". */
|
|
50
51
|
dir: string;
|
|
51
52
|
}
|
|
53
|
+
export interface RootEntryPoint {
|
|
54
|
+
kind: "main" | "export" | "bin";
|
|
55
|
+
name: string;
|
|
56
|
+
target: string;
|
|
57
|
+
}
|
|
52
58
|
export interface ReleaseWorkflow {
|
|
53
59
|
/** File name, e.g. "release-toolcraft.yml". */
|
|
54
60
|
file: string;
|
|
@@ -75,6 +81,9 @@ export interface WorkspaceModel {
|
|
|
75
81
|
binTargets: BinTarget[];
|
|
76
82
|
/** Real import graph of each package's `src`, keyed by package dir. */
|
|
77
83
|
sourceImports: SourceImportView;
|
|
84
|
+
/** Runtime imports of root package entrypoints that ship in the root tarball. */
|
|
85
|
+
shippedDistImports: SourceImportView;
|
|
86
|
+
rootEntryPoints: RootEntryPoint[];
|
|
78
87
|
/** Runtime file assets discovered from package source, keyed by package dir. */
|
|
79
88
|
runtimeFileAssets: RuntimeFileAssetView;
|
|
80
89
|
/** Files included by each package artifact, keyed by package dir. */
|
|
@@ -2,7 +2,7 @@ import path from "node:path";
|
|
|
2
2
|
import { parse as parseYaml } from "yaml";
|
|
3
3
|
import { createNpmPacklistProvider, loadPackageFileView } from "./packlist.js";
|
|
4
4
|
import { scanRuntimeFileAssets } from "./runtime-files.js";
|
|
5
|
-
import { scanSourceImports } from "./source-imports.js";
|
|
5
|
+
import { scanImportFiles, scanSourceImports } from "./source-imports.js";
|
|
6
6
|
function toPosix(p) {
|
|
7
7
|
return p.replaceAll("\\", "/");
|
|
8
8
|
}
|
|
@@ -100,6 +100,7 @@ async function loadPackage(fs, rootDir, relDir, isRoot) {
|
|
|
100
100
|
bundledDependencies: toStringArray(pkg.bundledDependencies ?? pkg.bundleDependencies),
|
|
101
101
|
repositoryDirectory: typeof repository?.directory === "string" ? repository.directory : undefined,
|
|
102
102
|
ecosystem,
|
|
103
|
+
main: typeof pkg.main === "string" ? pkg.main : undefined,
|
|
103
104
|
exports: pkg.exports,
|
|
104
105
|
bin: toBinRecord(pkg.bin, typeof pkg.name === "string" ? pkg.name : relDir),
|
|
105
106
|
files: Array.isArray(pkg.files)
|
|
@@ -133,6 +134,87 @@ function deriveShipped(root) {
|
|
|
133
134
|
}
|
|
134
135
|
return { shippedDirs, binTargets };
|
|
135
136
|
}
|
|
137
|
+
function normalizeRootTarget(target) {
|
|
138
|
+
const withoutDot = target.startsWith("./") ? target.slice(2) : target;
|
|
139
|
+
const normalized = toPosix(path.posix.normalize(toPosix(withoutDot)));
|
|
140
|
+
if (normalized.length === 0 ||
|
|
141
|
+
normalized === "." ||
|
|
142
|
+
normalized === ".." ||
|
|
143
|
+
normalized.startsWith("../") ||
|
|
144
|
+
path.posix.isAbsolute(normalized)) {
|
|
145
|
+
return undefined;
|
|
146
|
+
}
|
|
147
|
+
return normalized;
|
|
148
|
+
}
|
|
149
|
+
function exportImportTarget(value) {
|
|
150
|
+
if (typeof value === "string")
|
|
151
|
+
return normalizeRootTarget(value);
|
|
152
|
+
if (Array.isArray(value)) {
|
|
153
|
+
for (const item of value) {
|
|
154
|
+
const target = exportImportTarget(item);
|
|
155
|
+
if (target)
|
|
156
|
+
return target;
|
|
157
|
+
}
|
|
158
|
+
return undefined;
|
|
159
|
+
}
|
|
160
|
+
if (!value || typeof value !== "object")
|
|
161
|
+
return undefined;
|
|
162
|
+
const record = value;
|
|
163
|
+
for (const [key, raw] of Object.entries(record)) {
|
|
164
|
+
if (key === "types")
|
|
165
|
+
continue;
|
|
166
|
+
const target = exportImportTarget(raw);
|
|
167
|
+
if (target)
|
|
168
|
+
return target;
|
|
169
|
+
}
|
|
170
|
+
return undefined;
|
|
171
|
+
}
|
|
172
|
+
function isSubpathExportsMap(exportsField) {
|
|
173
|
+
if (!exportsField || typeof exportsField !== "object" || Array.isArray(exportsField)) {
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
return Object.keys(exportsField).some((key) => key.startsWith("."));
|
|
177
|
+
}
|
|
178
|
+
function deriveRootEntryPoints(root) {
|
|
179
|
+
const entryPoints = [];
|
|
180
|
+
const seen = new Set();
|
|
181
|
+
const add = (entry) => {
|
|
182
|
+
const key = `${entry.kind}:${entry.name}:${entry.target}`;
|
|
183
|
+
if (seen.has(key))
|
|
184
|
+
return;
|
|
185
|
+
seen.add(key);
|
|
186
|
+
entryPoints.push(entry);
|
|
187
|
+
};
|
|
188
|
+
if (root.main) {
|
|
189
|
+
const target = normalizeRootTarget(root.main);
|
|
190
|
+
if (target)
|
|
191
|
+
add({ kind: "main", name: ".", target });
|
|
192
|
+
}
|
|
193
|
+
if (isSubpathExportsMap(root.exports)) {
|
|
194
|
+
for (const [name, value] of Object.entries(root.exports)) {
|
|
195
|
+
const target = exportImportTarget(value);
|
|
196
|
+
if (target)
|
|
197
|
+
add({ kind: "export", name, target });
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
else {
|
|
201
|
+
const target = exportImportTarget(root.exports);
|
|
202
|
+
if (target)
|
|
203
|
+
add({ kind: "export", name: ".", target });
|
|
204
|
+
}
|
|
205
|
+
for (const [name, value] of Object.entries(root.bin)) {
|
|
206
|
+
const target = normalizeRootTarget(value);
|
|
207
|
+
if (target)
|
|
208
|
+
add({ kind: "bin", name, target });
|
|
209
|
+
}
|
|
210
|
+
return entryPoints.sort((a, b) => {
|
|
211
|
+
const byTarget = a.target.localeCompare(b.target);
|
|
212
|
+
if (byTarget !== 0)
|
|
213
|
+
return byTarget;
|
|
214
|
+
const byKind = a.kind.localeCompare(b.kind);
|
|
215
|
+
return byKind !== 0 ? byKind : a.name.localeCompare(b.name);
|
|
216
|
+
});
|
|
217
|
+
}
|
|
136
218
|
function dirFromArtifactPath(p) {
|
|
137
219
|
const parts = toPosix(p).split("/").filter(Boolean);
|
|
138
220
|
if (parts[parts.length - 1] === "dist")
|
|
@@ -274,6 +356,7 @@ export async function loadWorkspace(fs, rootDir, options = {}) {
|
|
|
274
356
|
for (const p of packages)
|
|
275
357
|
byDir.set(p.dir, p);
|
|
276
358
|
const { shippedDirs, binTargets } = deriveShipped(root);
|
|
359
|
+
const rootEntryPoints = deriveRootEntryPoints(root);
|
|
277
360
|
const workspaceNames = new Set(packages.map((p) => p.name));
|
|
278
361
|
const sourceImportPackages = packages.map((pkg) => ({
|
|
279
362
|
dir: pkg.dir,
|
|
@@ -284,8 +367,10 @@ export async function loadWorkspace(fs, rootDir, options = {}) {
|
|
|
284
367
|
root,
|
|
285
368
|
packages
|
|
286
369
|
};
|
|
287
|
-
const
|
|
370
|
+
const shippedDistEntryFiles = [...new Set(rootEntryPoints.map((entry) => entry.target))];
|
|
371
|
+
const [sourceImports, shippedDistImports, runtimeFileAssets, packageFiles] = await Promise.all([
|
|
288
372
|
scanSourceImports(fs, rootDir, sourceImportPackages),
|
|
373
|
+
scanImportFiles(fs, rootDir, shippedDistEntryFiles),
|
|
289
374
|
scanRuntimeFileAssets(fs, rootDir, runtimePackages),
|
|
290
375
|
loadPackageFileView(options.packlistProvider ?? createNpmPacklistProvider(fs), {
|
|
291
376
|
rootDir,
|
|
@@ -302,6 +387,8 @@ export async function loadWorkspace(fs, rootDir, options = {}) {
|
|
|
302
387
|
shippedDirs,
|
|
303
388
|
binTargets,
|
|
304
389
|
sourceImports,
|
|
390
|
+
shippedDistImports,
|
|
391
|
+
rootEntryPoints,
|
|
305
392
|
runtimeFileAssets,
|
|
306
393
|
packageFiles
|
|
307
394
|
};
|
|
@@ -9,6 +9,15 @@ function shippedPackageNames(model) {
|
|
|
9
9
|
}
|
|
10
10
|
return names;
|
|
11
11
|
}
|
|
12
|
+
function rootRuntimeDependencies(model) {
|
|
13
|
+
return new Set([
|
|
14
|
+
...Object.keys(model.root.dependencies),
|
|
15
|
+
...Object.keys(model.root.optionalDependencies)
|
|
16
|
+
]);
|
|
17
|
+
}
|
|
18
|
+
function entryPointLabel(entry) {
|
|
19
|
+
return `${entry.kind}:${entry.name}`;
|
|
20
|
+
}
|
|
12
21
|
/**
|
|
13
22
|
* Every runtime dependency of a shipped, tsc-emitted bin entry must resolve
|
|
14
23
|
* from the published `poe-code` tarball: it is in root `dependencies`, a Node
|
|
@@ -20,7 +29,31 @@ export const shippedDistDepsUnresolvable = {
|
|
|
20
29
|
run(model) {
|
|
21
30
|
const violations = [];
|
|
22
31
|
const shipped = shippedPackageNames(model);
|
|
23
|
-
const rootDeps =
|
|
32
|
+
const rootDeps = rootRuntimeDependencies(model);
|
|
33
|
+
for (const entry of model.rootEntryPoints) {
|
|
34
|
+
const unresolved = new Set();
|
|
35
|
+
for (const ref of model.shippedDistImports.get(entry.target) ?? []) {
|
|
36
|
+
if (ref.kind !== "bare" || ref.typeOnly || !ref.packageName)
|
|
37
|
+
continue;
|
|
38
|
+
if (ref.packageName === model.root.name)
|
|
39
|
+
continue;
|
|
40
|
+
if (rootDeps.has(ref.packageName) || isBuiltin(ref.packageName))
|
|
41
|
+
continue;
|
|
42
|
+
unresolved.add(ref.packageName);
|
|
43
|
+
}
|
|
44
|
+
if (unresolved.size === 0)
|
|
45
|
+
continue;
|
|
46
|
+
const names = [...unresolved].sort();
|
|
47
|
+
violations.push({
|
|
48
|
+
rule: id,
|
|
49
|
+
package: model.root.name,
|
|
50
|
+
severity: "error",
|
|
51
|
+
via: entryPointLabel(entry),
|
|
52
|
+
detail: { target: entry.target, unresolved: names },
|
|
53
|
+
message: "root package entrypoint imports bare names not in root dependencies (ERR_MODULE_NOT_FOUND on install)",
|
|
54
|
+
fix: `Bundle or rewrite ${entry.target}, or add each package to root "dependencies": ${names.join(", ")}.`
|
|
55
|
+
});
|
|
56
|
+
}
|
|
24
57
|
for (const bin of model.binTargets) {
|
|
25
58
|
const owner = model.byDir.get(bin.dir);
|
|
26
59
|
if (!owner)
|
|
@@ -17,7 +17,7 @@ export interface ImportRef {
|
|
|
17
17
|
}
|
|
18
18
|
/** Imports found in each workspace package, keyed by the package dir (posix). */
|
|
19
19
|
export type SourceImportView = Map<string, ImportRef[]>;
|
|
20
|
-
interface RawImport {
|
|
20
|
+
export interface RawImport {
|
|
21
21
|
specifier: string;
|
|
22
22
|
typeOnly: boolean;
|
|
23
23
|
}
|
|
@@ -33,4 +33,4 @@ export declare function scanSourceImports(fs: LintFs, rootDir: string, packages:
|
|
|
33
33
|
dir: string;
|
|
34
34
|
workspaceNames: ReadonlySet<string>;
|
|
35
35
|
}>): Promise<SourceImportView>;
|
|
36
|
-
export
|
|
36
|
+
export declare function scanImportFiles(fs: LintFs, rootDir: string, files: string[]): Promise<SourceImportView>;
|
|
@@ -14,8 +14,18 @@ function isTestFile(relFile) {
|
|
|
14
14
|
const segments = relFile.split("/");
|
|
15
15
|
return segments.some((s) => s === "test" || s === "tests" || s === "__tests__");
|
|
16
16
|
}
|
|
17
|
+
function scriptKind(fileName) {
|
|
18
|
+
if (fileName.endsWith(".tsx"))
|
|
19
|
+
return ts.ScriptKind.TSX;
|
|
20
|
+
if (fileName.endsWith(".jsx"))
|
|
21
|
+
return ts.ScriptKind.JSX;
|
|
22
|
+
if (fileName.endsWith(".js") || fileName.endsWith(".mjs") || fileName.endsWith(".cjs")) {
|
|
23
|
+
return ts.ScriptKind.JS;
|
|
24
|
+
}
|
|
25
|
+
return ts.ScriptKind.TS;
|
|
26
|
+
}
|
|
17
27
|
function extractImportsFromAst(text, fileName) {
|
|
18
|
-
const sourceFile = ts.createSourceFile(fileName, text, ts.ScriptTarget.Latest, false, fileName
|
|
28
|
+
const sourceFile = ts.createSourceFile(fileName, text, ts.ScriptTarget.Latest, false, scriptKind(fileName));
|
|
19
29
|
const out = [];
|
|
20
30
|
for (const statement of sourceFile.statements) {
|
|
21
31
|
if (ts.isImportDeclaration(statement)) {
|
|
@@ -176,3 +186,20 @@ export async function scanSourceImports(fs, rootDir, packages) {
|
|
|
176
186
|
}));
|
|
177
187
|
return view;
|
|
178
188
|
}
|
|
189
|
+
export async function scanImportFiles(fs, rootDir, files) {
|
|
190
|
+
const view = new Map();
|
|
191
|
+
await Promise.all(files.map(async (relFile) => {
|
|
192
|
+
const normalized = path.posix.normalize(toPosix(relFile));
|
|
193
|
+
const absFile = path.join(rootDir, normalized);
|
|
194
|
+
let text;
|
|
195
|
+
try {
|
|
196
|
+
text = await fs.readFile(absFile);
|
|
197
|
+
}
|
|
198
|
+
catch {
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
const refs = extractRelevantImports(text, absFile).map((raw) => classify(raw, rootDir, ".", absFile, normalized));
|
|
202
|
+
view.set(normalized, refs);
|
|
203
|
+
}));
|
|
204
|
+
return view;
|
|
205
|
+
}
|