mirror of
https://github.com/fluencelabs/fluence-js.git
synced 2025-06-26 06:11:33 +00:00
feat(npm-aqua-compiler): create package (#401)
* Add npm-aqua-compiler package * Release new package * Remove noUncheckedIndexedAccess from tsconfig.json * Fix a test script * Fix length checks * Fix * Update error description * Try to choose a nicer err message * New import format and API * Fix error message * Improve test * Don't add empty string key when globalImports prop is empty * Fix exports
This commit is contained in:
115
packages/core/npm-aqua-compiler/src/imports.spec.ts
Normal file
115
packages/core/npm-aqua-compiler/src/imports.spec.ts
Normal file
@ -0,0 +1,115 @@
|
||||
/**
|
||||
* Copyright 2023 Fluence Labs Limited
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { join } from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
|
||||
import { assert, describe, expect, it } from "vitest";
|
||||
|
||||
import { gatherImportsFromNpm } from "./imports.js";
|
||||
|
||||
describe("imports", () => {
|
||||
/**
|
||||
* NOTE: This test expects that `npm i` is run
|
||||
* inside `./__test__/data/transitive-deps/project` folder
|
||||
*/
|
||||
it("should resolve transitive dependencies", async () => {
|
||||
const npmProjectDirPath = "./test/transitive-deps/project";
|
||||
const aquaToCompileDirPath = "./test/transitive-deps/aqua-project";
|
||||
const globalImports = ["./.fluence/aqua"];
|
||||
|
||||
const expectedResolution: Record<
|
||||
string,
|
||||
Record<string, string[] | string>
|
||||
> = {
|
||||
[aquaToCompileDirPath]: {
|
||||
"": globalImports,
|
||||
A: "./A",
|
||||
B: "./B",
|
||||
},
|
||||
"./A": {
|
||||
C: "./C",
|
||||
D: "./D",
|
||||
},
|
||||
"./B": {
|
||||
C: "./B/C",
|
||||
D: "./B/D",
|
||||
},
|
||||
"./C": {
|
||||
D: "./C/D",
|
||||
},
|
||||
"./B/C": {
|
||||
D: "./B/C/D",
|
||||
},
|
||||
};
|
||||
|
||||
const prefix = join(
|
||||
fileURLToPath(new URL("./", import.meta.url)),
|
||||
"..",
|
||||
"test",
|
||||
"transitive-deps",
|
||||
"project",
|
||||
);
|
||||
|
||||
const buildResolutionKey = (str: string) => {
|
||||
return (
|
||||
"./" +
|
||||
str
|
||||
.slice(prefix.length)
|
||||
.split("/node_modules/")
|
||||
.filter(Boolean)
|
||||
.join("/")
|
||||
);
|
||||
};
|
||||
|
||||
const imports = await gatherImportsFromNpm({
|
||||
npmProjectDirPath,
|
||||
aquaToCompileDirPath,
|
||||
globalImports,
|
||||
});
|
||||
|
||||
expect(Object.keys(imports).length).toBe(
|
||||
Object.keys(expectedResolution).length,
|
||||
);
|
||||
|
||||
Object.entries(imports).forEach(([key, value]) => {
|
||||
const resolutionKey =
|
||||
key === aquaToCompileDirPath ? key : buildResolutionKey(key);
|
||||
|
||||
const resolutionValues = expectedResolution[resolutionKey];
|
||||
|
||||
assert(resolutionValues);
|
||||
|
||||
expect(Object.keys(value).length).toBe(
|
||||
Object.keys(resolutionValues).length,
|
||||
);
|
||||
|
||||
for (const [dep, path] of Object.entries(value)) {
|
||||
if (Array.isArray(path)) {
|
||||
expect(dep).toBe("");
|
||||
expect(expectedResolution[resolutionKey]).toHaveProperty(dep, path);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
expect(expectedResolution[resolutionKey]).toHaveProperty(
|
||||
dep,
|
||||
buildResolutionKey(path),
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
92
packages/core/npm-aqua-compiler/src/imports.ts
Normal file
92
packages/core/npm-aqua-compiler/src/imports.ts
Normal file
@ -0,0 +1,92 @@
|
||||
/**
|
||||
* Copyright 2023 Fluence Labs Limited
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import Arborist from "@npmcli/arborist";
|
||||
import { breadth } from "treeverse";
|
||||
|
||||
export interface GatherImportsArg {
|
||||
npmProjectDirPath: string;
|
||||
aquaToCompileDirPath?: string; // Default: npmProjectDirPath
|
||||
globalImports?: string[];
|
||||
}
|
||||
|
||||
export type GatherImportsResult = Record<
|
||||
string,
|
||||
Record<string, string[] | string>
|
||||
>;
|
||||
|
||||
export async function gatherImportsFromNpm({
|
||||
npmProjectDirPath,
|
||||
aquaToCompileDirPath,
|
||||
globalImports = [],
|
||||
}: GatherImportsArg): Promise<GatherImportsResult> {
|
||||
const arborist = new Arborist({ path: npmProjectDirPath });
|
||||
const tree = await arborist.loadActual();
|
||||
|
||||
/**
|
||||
* Traverse dependency tree to construct map
|
||||
* (real path of a package) -> (real paths of its immediate dependencies)
|
||||
*/
|
||||
const result: GatherImportsResult = {};
|
||||
|
||||
breadth({
|
||||
tree,
|
||||
getChildren(node) {
|
||||
const deps: Arborist.Node[] = [];
|
||||
|
||||
for (const edge of node.edgesOut.values()) {
|
||||
// Skip dependencies that are not installed.
|
||||
|
||||
// Looks like Arborist type is incorrect here, so it's possible to have null here
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||
if (edge.to === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// NOTE: Any errors in edge are ignored.
|
||||
const dep = edge.to;
|
||||
|
||||
// Gather dependencies to traverse them.
|
||||
deps.push(dep);
|
||||
|
||||
// Root node should have top-level property pointed to aqua dependency folder
|
||||
if (node.isRoot) {
|
||||
const aquaDepPath = aquaToCompileDirPath ?? npmProjectDirPath;
|
||||
|
||||
result[aquaDepPath] = {
|
||||
...(result[aquaDepPath] ??
|
||||
(globalImports.length > 0
|
||||
? {
|
||||
"": globalImports,
|
||||
}
|
||||
: {})),
|
||||
[dep.name]: dep.realpath,
|
||||
};
|
||||
} else {
|
||||
// Gather dependencies real paths.
|
||||
result[node.realpath] = {
|
||||
...(result[node.realpath] ?? {}),
|
||||
[dep.name]: dep.realpath,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return deps;
|
||||
},
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
Reference in New Issue
Block a user