Skip to content

Commit e67aa9d

Browse files
committed
feat(tarball): implement new EntryFilesAnalyser API
1 parent 466c16e commit e67aa9d

File tree

3 files changed

+157
-40
lines changed

3 files changed

+157
-40
lines changed

workspaces/scanner/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ export async function verify(
9797
...NPM_TOKEN, registry: getLocalRegistryURL(), cache: `${os.homedir()}/.npm`
9898
});
9999

100-
const scanResult = await tarball.scanPackage(dest, packageName);
100+
const scanResult = await tarball.scanPackage(dest);
101101

102102
return scanResult;
103103
}
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
// Import Node.js Dependencies
2+
import os from "node:os";
3+
import path from "node:path";
4+
5+
// Import Third-party Dependencies
6+
import pacote from "pacote";
7+
import * as conformance from "@nodesecure/conformance";
8+
import { ManifestManager } from "@nodesecure/mama";
9+
import {
10+
EntryFilesAnalyser,
11+
AstAnalyser,
12+
type Warning,
13+
type Dependency
14+
} from "@nodesecure/js-x-ray";
15+
16+
// Import Internal Dependencies
17+
import {
18+
getTarballComposition
19+
} from "../utils/index.js";
20+
21+
// CONSTANTS
22+
const kNpmToken = typeof process.env.NODE_SECURE_TOKEN === "string" ?
23+
{ token: process.env.NODE_SECURE_TOKEN } :
24+
{};
25+
26+
export interface NpmTarballExtractOptions {
27+
registry?: string;
28+
}
29+
30+
export class TarballExtractor {
31+
static JS_EXTENSIONS = new Set([".js", ".mjs", ".cjs"]);
32+
33+
public manifest: ManifestManager;
34+
public archiveLocation: string;
35+
36+
constructor(
37+
archiveLocation: string,
38+
mama: ManifestManager
39+
) {
40+
this.archiveLocation = archiveLocation;
41+
this.manifest = mama;
42+
}
43+
44+
async scan() {
45+
const [
46+
composition,
47+
spdx
48+
] = await Promise.all([
49+
getTarballComposition(this.archiveLocation),
50+
conformance.extractLicenses(this.archiveLocation)
51+
]);
52+
53+
return {
54+
spdx,
55+
composition
56+
};
57+
}
58+
59+
async runJavaScriptSast(
60+
JSFiles: string[]
61+
) {
62+
const dependencies: Record<string, Record<string, Dependency>> = Object.create(null);
63+
const minified: string[] = [];
64+
const warnings: Warning[] = [];
65+
66+
const entries = [...this.manifest.getEntryFiles()]
67+
.filter((entryFile) => TarballExtractor.JS_EXTENSIONS.has(path.extname(entryFile)));
68+
69+
if (entries.length > 0) {
70+
const efa = new EntryFilesAnalyser();
71+
for await (const fileReport of efa.analyse(entries)) {
72+
warnings.push(
73+
...fileReport.warnings.map((warning) => {
74+
return { ...warning, file: fileReport.file };
75+
})
76+
);
77+
78+
if (fileReport.ok) {
79+
dependencies[fileReport.file] = Object.fromEntries(
80+
fileReport.dependencies
81+
);
82+
fileReport.isMinified && minified.push(fileReport.file);
83+
}
84+
}
85+
}
86+
else {
87+
const { name, type = "script" } = this.manifest.document;
88+
89+
for (const file of JSFiles) {
90+
const result = await new AstAnalyser().analyseFile(
91+
path.join(this.archiveLocation, file),
92+
{
93+
packageName: name,
94+
module: type === "module"
95+
}
96+
);
97+
98+
warnings.push(
99+
...result.warnings.map((curr) => Object.assign({}, curr, { file }))
100+
);
101+
if (result.ok) {
102+
dependencies[file] = Object.fromEntries(result.dependencies);
103+
if (result.isMinified) {
104+
minified.push(file);
105+
}
106+
}
107+
}
108+
}
109+
110+
return {
111+
dependencies,
112+
warnings,
113+
minified
114+
};
115+
}
116+
117+
static async fromNpm(
118+
location: string,
119+
spec: string,
120+
options: NpmTarballExtractOptions = {}
121+
) {
122+
const { registry } = options;
123+
124+
await pacote.extract(spec, location, {
125+
...kNpmToken,
126+
registry,
127+
cache: `${os.homedir()}/.npm`
128+
});
129+
130+
return this.fromFileSystem(location);
131+
}
132+
133+
static async fromFileSystem(
134+
location: string
135+
): Promise<TarballExtractor> {
136+
const mama = await ManifestManager.fromPackageJSON(location);
137+
138+
return new TarballExtractor(location, mama);
139+
}
140+
}

workspaces/tarball/src/tarball.ts

Lines changed: 16 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import os from "node:os";
44

55
// Import Third-party Dependencies
66
import {
7-
AstAnalyser,
87
type Warning,
98
type Dependency
109
} from "@nodesecure/js-x-ray";
@@ -19,6 +18,7 @@ import {
1918
analyzeDependencies,
2019
booleanToFlags
2120
} from "./utils/index.js";
21+
import { TarballExtractor } from "./class/TarballExtractor.class.js";
2222
import * as warnings from "./warnings.js";
2323
import * as sast from "./sast/index.js";
2424

@@ -58,7 +58,6 @@ const NPM_TOKEN = typeof process.env.NODE_SECURE_TOKEN === "string" ?
5858
{};
5959

6060
const kNativeCodeExtensions = new Set([".gyp", ".c", ".cpp", ".node", ".so", ".h"]);
61-
const kJsExtname = new Set([".js", ".mjs", ".cjs"]);
6261

6362
export interface scanDirOrArchiveOptions {
6463
ref: DependencyRef;
@@ -122,7 +121,7 @@ export async function scanDirOrArchive(
122121
const scannedFiles = await sast.scanManyFiles(composition.files, dest, name);
123122

124123
ref.warnings.push(...scannedFiles.flatMap((row) => row.warnings));
125-
if (/^0(\.\d+)*$/.test(version)) {
124+
if (mama.hasZeroSemver) {
126125
ref.warnings.push(warnings.getSemVerWarning(version));
127126
}
128127

@@ -189,46 +188,24 @@ export interface ScannedPackageResult {
189188
}
190189

191190
export async function scanPackage(
192-
dest: string,
193-
packageName?: string
191+
dest: string
194192
): Promise<ScannedPackageResult> {
195-
const [
196-
mama,
193+
const extractor = await TarballExtractor.fromFileSystem(dest);
194+
195+
const {
197196
composition,
198197
spdx
199-
] = await Promise.all([
200-
ManifestManager.fromPackageJSON(dest),
201-
getTarballComposition(dest),
202-
conformance.extractLicenses(dest)
203-
]);
204-
const { type = "script" } = mama.document;
205-
206-
// Search for runtime dependencies
207-
const dependencies: Record<string, Record<string, Dependency>> = Object.create(null);
208-
const minified: string[] = [];
209-
const warnings: Warning[] = [];
210-
211-
const JSFiles = composition.files
212-
.filter((name) => kJsExtname.has(path.extname(name)));
213-
for (const file of JSFiles) {
214-
const result = await new AstAnalyser().analyseFile(
215-
path.join(dest, file),
216-
{
217-
packageName: packageName ?? mama.document.name,
218-
module: type === "module"
219-
}
220-
);
198+
} = await extractor.scan();
221199

222-
warnings.push(
223-
...result.warnings.map((curr) => Object.assign({}, curr, { file }))
224-
);
225-
if (result.ok) {
226-
dependencies[file] = Object.fromEntries(result.dependencies);
227-
if (result.isMinified) {
228-
minified.push(file);
229-
}
230-
}
231-
}
200+
const {
201+
dependencies,
202+
warnings,
203+
minified
204+
} = await extractor.runJavaScriptSast(
205+
composition.files.filter(
206+
(name) => TarballExtractor.JS_EXTENSIONS.has(path.extname(name))
207+
)
208+
);
232209

233210
return {
234211
files: {

0 commit comments

Comments
 (0)