Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: dependency analysis #62

Merged
merged 3 commits into from
Aug 12, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
126 changes: 112 additions & 14 deletions api/async/publish.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,23 @@
*/

import { join, walk, SQSEvent, Context } from "../../deps.ts";
import { Build, Database } from "../../utils/database.ts";
import { Build, Database, BuildStats } from "../../utils/database.ts";
import { clone } from "../../utils/git.ts";
import {
uploadVersionMetaJson,
uploadVersionRaw,
uploadMetaJson,
getMeta,
getVersionMetaJson,
} from "../../utils/storage.ts";
import type { DirectoryListingFile } from "../../utils/types.ts";
import { asyncPool } from "../../utils/util.ts";

import { runDenoInfo } from "../../utils/deno.ts";
import type { Dep } from "../../utils/deno.ts";
const database = new Database(Deno.env.get("MONGO_URI")!);

const remoteURL = Deno.env.get("REMOTE_URL")!;

const MAX_FILE_SIZE = 2_500_000;

const decoder = new TextDecoder();
Expand All @@ -37,28 +41,50 @@ export async function handler(
if (build === null) {
throw new Error("Build does not exist!");
}

let stats: BuildStats | undefined = undefined;

switch (build.options.type) {
case "github":
try {
await publishGithub(build);
stats = await publishGithub(build);
} catch (err) {
console.log("error", err, err?.response);
await database.saveBuild({
...build,
status: "error",
message: err.message,
});
return;
}
break;
default:
throw new Error(`Unknown build type: ${build.options.type}`);
}

let message = "Published module.";

await analyzeDependencies(build).catch((err) => {
console.error("failed dependency analysis", build, err, err?.response);
message += " Failed to run dependency analysis.";
});

await database.saveBuild({
...build,
status: "success",
message: message,
stats,
});
}
}

async function publishGithub(
build: Build,
) {
): Promise<{
total_files: number;
skipped_due_to_size: string[];
total_size: number;
}> {
console.log(
`Publishing ${build.options.moduleName} at ${build.options.ref} from GitHub`,
);
Expand Down Expand Up @@ -182,18 +208,90 @@ async function publishGithub(
},
);

await database.saveBuild({
...build,
status: "success",
message: `Finished uploading`,
stats: {
total_files: directory.filter((f) => f.type === "file").length,
skipped_due_to_size: skippedFiles,
total_size: totalSize,
},
});
return {
total_files: directory.filter((f) => f.type === "file").length,
skipped_due_to_size: skippedFiles,
total_size: totalSize,
};
} finally {
// Remove checkout
await Deno.remove(clonePath, { recursive: true });
}
}

interface DependencyGraph {
nodes: {
[url: string]: {
imports: string[];
};
};
}

async function analyzeDependencies(build: Build): Promise<void> {
console.log(
`Analyzing dependencies for ${build.options.moduleName}@${build.options.version}`,
);
await database.saveBuild({
...build,
status: "analyzing_dependencies",
});

const { options: { moduleName, version } } = build;
const denoDir = await Deno.makeTempDir();
const prefix = remoteURL.replace("%m", moduleName).replace("%v", version);

const rawMeta = await getVersionMetaJson(moduleName, version, "meta.json");
if (!rawMeta) {
throw new Error("Invalid module");
}
const meta: { directory_listing: DirectoryListingFile[] } = JSON.parse(
decoder.decode(rawMeta),
);

const depsTrees = [];

for await (const file of meta.directory_listing) {
if (file.type !== "file") {
continue;
}
if (
!(file.path.endsWith(".js") || file.path.endsWith(".jsx") ||
file.path.endsWith(".ts") || file.path.endsWith(".tsx"))
) {
continue;
}
const entrypoint = join(
prefix,
file.path,
);
depsTrees.push(await runDenoInfo({ entrypoint, denoDir }));
}

const graph: DependencyGraph = { nodes: {} };

for (const dep of depsTrees) {
treeToGraph(graph, dep);
}

await Deno.remove(denoDir, { recursive: true });

await uploadVersionMetaJson(
build.options.moduleName,
build.options.version,
"deps.json",
{ graph },
);
}

function treeToGraph(graph: DependencyGraph, dep: Dep) {
const url = dep[0];
if (!graph.nodes[url]) {
graph.nodes[url] = { imports: [] };
}
dep[1].forEach((dep) => {
if (!graph.nodes[url].imports.includes(dep[0])) {
graph.nodes[url].imports.push(dep[0]);
}
});
dep[1].forEach((dep) => treeToGraph(graph, dep));
}
Loading