Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@travetto/manifest

Package Overview
Dependencies
Maintainers
1
Versions
69
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@travetto/manifest - npm Package Compare versions

Comparing version 4.0.0-rc.0 to 4.0.0-rc.1

src/types/common.ts

5

__index__.ts

@@ -11,2 +11,5 @@ /// <reference path="./src/global.d.ts" />

export * from './src/file';
export * from './src/types';
export * from './src/types/context';
export * from './src/types/package';
export * from './src/types/manifest';
export * from './src/types/common';

2

bin/context.d.ts

@@ -7,5 +7,5 @@ import type { ManifestContext } from '../src/types';

*/
function getManifestContext(folder?: string): Promise<ManifestContext>;
function getManifestContext(folder?: string): ManifestContext;
}
export = ManifestBootstrap;
// @ts-check
/**
* @typedef {import('../src/types').Package & { path:string }} Pkg
* @typedef {import('../src/types/package').Package & { path:string }} Pkg
* @typedef {Pkg & { mono: boolean, manager: 'yarn'|'npm', resolve: (file:string) => string}} Workspace
* @typedef {import('../src/types').ManifestContext} ManifestContext
* @typedef {import('../src/types/context').ManifestContext} ManifestContext
*/
import fs from 'node:fs/promises';
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
import path from 'node:path';

@@ -20,8 +20,10 @@ import { createRequire } from 'node:module';

* @param {string} dir
* @returns {Promise<Pkg|undefined>}
* @returns {Pkg|undefined}
*/
async function $readPackage(dir) {
function $readPackage(dir) {
dir = dir.endsWith('.json') ? path.dirname(dir) : dir;
return await fs.readFile(path.resolve(dir, 'package.json'), 'utf8')
.then(v => ({ ...JSON.parse(v), path: path.resolve(dir) }), () => undefined);
try {
const v = readFileSync(path.resolve(dir, 'package.json'), 'utf8');
return ({ ...JSON.parse(v), path: path.resolve(dir) });
} catch { }
}

@@ -32,9 +34,9 @@

* @param {string} dir
* @return {Promise<Pkg>}
* @return {Pkg}
*/
async function $findPackage(dir) {
function $findPackage(dir) {
let prev;
let pkg, curr = path.resolve(dir);
while (!pkg && curr !== prev) {
pkg = await $readPackage(curr);
pkg = $readPackage(curr);
[prev, curr] = [curr, path.dirname(curr)];

@@ -51,5 +53,5 @@ }

* Get workspace root
* @return {Promise<Workspace>}
* @return {Workspace}
*/
async function $resolveWorkspace(base = process.cwd()) {
function $resolveWorkspace(base = process.cwd()) {
if (base in WS_ROOT) { return WS_ROOT[base]; }

@@ -63,6 +65,6 @@ let folder = base;

[prev, prevPkg] = [folder, pkg];
pkg = await $readPackage(folder) ?? pkg;
pkg = $readPackage(folder) ?? pkg;
if (
(pkg && (!!pkg.workspaces || !!pkg.travetto?.build?.isolated)) || // if we have a monorepo root, or we are isolated
await fs.stat(path.resolve(folder, '.git')).catch(() => { }) // we made it to the source repo root
existsSync(path.resolve(folder, '.git')) // we made it to the source repo root
) {

@@ -82,3 +84,3 @@ break;

type: pkg.type,
manager: await fs.stat(path.resolve(pkg.path, 'yarn.lock')).catch(() => { }) ? 'yarn' : 'npm',
manager: existsSync(path.resolve(pkg.path, 'yarn.lock')) ? 'yarn' : 'npm',
resolve: createRequire(`${pkg.path}/node_modules`).resolve.bind(null),

@@ -92,11 +94,12 @@ mono: !!pkg.workspaces || (!pkg.travetto?.build?.isolated && !!prevPkg) // Workspaces or nested projects

* @param {Workspace} ws
* @param {string} toolFolder
*/
async function $getCompilerUrl(ws) {
const file = path.resolve(ws.path, TOOL_FOLDER, 'build.compilerUrl');
function $getCompilerUrl(ws, toolFolder) {
const file = path.resolve(ws.path, toolFolder, 'build.compilerUrl');
// eslint-disable-next-line no-bitwise
const port = (Math.abs([...file].reduce((a, b) => (a * 33) ^ b.charCodeAt(0), 5381)) % 29000) + 20000;
const out = `http://localhost:${port}`;
try { await fs.stat(file); } catch {
await fs.mkdir(path.dirname(file), { recursive: true });
await fs.writeFile(file, out, 'utf8');
if (!existsSync(file)) {
mkdirSync(path.dirname(file), { recursive: true });
writeFileSync(file, out, 'utf8');
}

@@ -111,3 +114,3 @@ return out;

*/
async function $resolveModule(workspace, folder) {
function $resolveModule(workspace, folder) {
let mod;

@@ -117,4 +120,7 @@ if (!folder && process.env.TRV_MODULE) {

if (/[.](t|j)sx?$/.test(mod)) { // Rewrite from file to module
process.env.TRV_MODULE = mod = await $findPackage(path.dirname(mod))
.then(v => v.name, () => '');
try {
process.env.TRV_MODULE = mod = $findPackage(path.dirname(mod)).name;
} catch {
process.env.TRV_MODULE = mod = '';
}
}

@@ -127,3 +133,3 @@ }

} catch {
const workspacePkg = await $readPackage(workspace.path);
const workspacePkg = $readPackage(workspace.path);
if (workspacePkg?.name === mod) {

@@ -143,8 +149,9 @@ folder = workspace.path;

* @param {string} [folder]
* @return {Promise<ManifestContext>}
* @return {ManifestContext}
*/
export async function getManifestContext(folder) {
const workspace = await $resolveWorkspace(folder);
const mod = await $resolveModule(workspace, folder);
export function getManifestContext(folder) {
const workspace = $resolveWorkspace(folder);
const mod = $resolveModule(workspace, folder);
const build = workspace.travetto?.build ?? {};
const toolFolder = build.toolFolder ?? TOOL_FOLDER;

@@ -157,9 +164,11 @@ return {

manager: workspace.manager,
type: workspace.type ?? 'commonjs'
type: workspace.type ?? 'commonjs',
defaultEnv: workspace.travetto?.defaultEnv ?? 'local'
},
build: {
compilerFolder: build.compilerFolder ?? COMPILER_FOLDER,
compilerUrl: build.compilerUrl ?? await $getCompilerUrl(workspace),
compilerUrl: build.compilerUrl ?? $getCompilerUrl(workspace, toolFolder),
compilerModuleFolder: path.dirname(workspace.resolve('@travetto/compiler/package.json')).replace(`${workspace.path}/`, ''),
outputFolder: build.outputFolder ?? OUTPUT_FOLDER,
toolFolder
},

@@ -166,0 +175,0 @@ main: {

{
"name": "@travetto/manifest",
"version": "4.0.0-rc.0",
"version": "4.0.0-rc.1",
"description": "Support for project indexing, manifesting, along with file watching",

@@ -5,0 +5,0 @@ "keywords": [

@@ -33,3 +33,3 @@ <!-- This file was generated by @travetto/doc and should not be modified directly -->

## Manifest Delta
During the compilation process, it is helpful to know how the output content differs from the manifest, which is produced from the source input. The [ManifestDeltaUtil](https://github.com/travetto/travetto/tree/main/module/manifest/src/delta.ts#L20) provides the functionality for a given manifest, and will produce a stream of changes grouped by module. This is the primary input into the [Compiler](https://github.com/travetto/travetto/tree/main/module/compiler#readme "The compiler infrastructure for the Travetto framework")'s incremental behavior to know when a file has changed and needs to be recompiled.
During the compilation process, it is helpful to know how the output content differs from the manifest, which is produced from the source input. The [ManifestDeltaUtil](https://github.com/travetto/travetto/tree/main/module/manifest/src/delta.ts#L21) provides the functionality for a given manifest, and will produce a stream of changes grouped by module. This is the primary input into the [Compiler](https://github.com/travetto/travetto/tree/main/module/compiler#readme "The compiler infrastructure for the Travetto framework")'s incremental behavior to know when a file has changed and needs to be recompiled.

@@ -39,3 +39,3 @@ ## Class and Function Metadata

`Ⲑid` is used heavily throughout the framework for determining which classes are owned by the framework, and being able to lookup the needed data from the [RuntimeIndex](https://github.com/travetto/travetto/tree/main/module/manifest/src/runtime.ts#L11) using the `getFunctionMetadata` method.
`Ⲑid` is used heavily throughout the framework for determining which classes are owned by the framework, and being able to lookup the needed data from the [RuntimeIndex](https://github.com/travetto/travetto/tree/main/module/manifest/src/runtime.ts#L14) using the `getFunctionMetadata` method.

@@ -102,3 +102,4 @@ **Code: Test Class**

"manager": "npm",
"type": "commonjs"
"type": "commonjs",
"defaultEnv": "local"
},

@@ -109,3 +110,4 @@ "build": {

"compilerModuleFolder": "module/compiler",
"outputFolder": ".trv/output"
"outputFolder": ".trv/output",
"toolFolder": ".trv/tool"
},

@@ -121,5 +123,6 @@ "main": {

"main": true,
"prod": true,
"name": "@travetto/manifest",
"version": "x.x.x",
"local": true,
"workspace": true,
"internal": false,

@@ -130,3 +133,2 @@ "sourceFolder": "module/manifest",

"parents": [],
"prod": true,
"files": {

@@ -169,4 +171,7 @@ "$root": [

[ "src/runtime.ts", "ts", 1868155200000 ],
[ "src/types.ts", "ts", 1868155200000 ],
[ "src/util.ts", "ts", 1868155200000 ]
[ "src/util.ts", "ts", 1868155200000 ],
[ "src/types/common.ts", "ts", 1868155200000 ],
[ "src/types/context.ts", "ts", 1868155200000 ],
[ "src/types/manifest.ts", "ts", 1868155200000 ],
[ "src/types/package.ts", "ts", 1868155200000 ]
],

@@ -173,0 +178,0 @@ "bin": [

@@ -1,10 +0,10 @@

import {
ManifestContext, ManifestModule, ManifestModuleCore, ManifestModuleFile,
ManifestModuleFileType, ManifestModuleFolderType, ManifestRoot
} from './types';
import fs from 'node:fs/promises';
import { ManifestModuleUtil } from './module';
import { ManifestFileUtil } from './file';
import { path } from './path';
import type { ManifestModule, ManifestModuleCore, ManifestModuleFile, ManifestRoot } from './types/manifest';
import type { ManifestModuleFileType, ManifestModuleFolderType } from './types/common';
import type { ManifestContext } from './types/context';
type DeltaEventType = 'added' | 'changed' | 'removed' | 'missing' | 'dirty';

@@ -15,3 +15,4 @@ type DeltaModule = ManifestModuleCore & { files: Record<string, ManifestModuleFile> };

const VALID_SOURCE_FOLDERS = new Set<ManifestModuleFolderType>(['bin', 'src', 'test', 'support', '$index', '$package', 'doc']);
const VALID_SOURCE_TYPE = new Set<ManifestModuleFileType>(['js', 'ts', 'package-json']);
const VALID_OUTPUT_TYPE = new Set<ManifestModuleFileType>(['js', 'ts', 'package-json']);
const VALID_SOURCE_TYPE = new Set<ManifestModuleFileType>([...VALID_OUTPUT_TYPE, 'typings']);

@@ -41,3 +42,3 @@ /**

const type = ManifestModuleUtil.getFileType(x);
return type === 'ts' || type === 'typings' || type === 'js' || type === 'package-json';
return VALID_SOURCE_TYPE.has(type);
})

@@ -50,3 +51,3 @@ .map(x => ManifestModuleUtil.sourceToBlankExt(x.replace(`${root}/`, '')))

const [, , leftTs] = left.files[el];
const stat = await ManifestFileUtil.statFile(output);
const stat = await fs.stat(output).catch(() => undefined);
right.delete(ManifestModuleUtil.sourceToBlankExt(el));

@@ -83,3 +84,3 @@

for (const [name, type, date] of m.files?.[key] ?? []) {
if (VALID_SOURCE_TYPE.has(type)) {
if (VALID_OUTPUT_TYPE.has(type)) {
out[name] = [name, type, date];

@@ -86,0 +87,0 @@ }

import { PackageUtil } from './package';
import { path } from './path';
import { ManifestContext, ManifestModuleRole, PackageVisitor, PackageVisitReq, Package, ManifestDepCore } from './types';
export type ModuleDep = ManifestDepCore & {
pkg: Package;
mainLike?: boolean;
sourcePath: string;
childSet: Set<string>;
parentSet: Set<string>;
roleSet: Set<ManifestModuleRole>;
topLevel?: boolean;
};
import type { Package, PackageDepType, PackageVisitReq, PackageVisitor } from './types/package';
import type { ManifestContext } from './types/context';
import type { PackageModule } from './types/manifest';
type CreateOpts = Partial<Pick<PackageModule, 'main' | 'workspace' | 'prod'>> & { roleRoot?: boolean };
/**
* Used for walking dependencies for collecting modules for the manifest
*/
export class ModuleDependencyVisitor implements PackageVisitor<ModuleDep> {
export class PackageModuleVisitor implements PackageVisitor<PackageModule> {

@@ -24,34 +19,33 @@ constructor(public ctx: ManifestContext) {

#mainLikeModules = new Set<string>();
#mainSourcePath: string;
#cache: Record<string, PackageModule> = {};
#workspaceModules: Map<string, string>;
/**
* Main source path for searching
*/
get rootPath(): string {
return this.#mainSourcePath;
}
/**
* Initialize visitor, and provide global dependencies
*/
async init(req: PackageVisitReq<ModuleDep>): Promise<PackageVisitReq<ModuleDep>[]> {
const pkg = PackageUtil.readPackage(req.sourcePath);
const workspacePkg = PackageUtil.readPackage(this.ctx.workspace.path);
const workspaceModules = pkg.workspaces?.length ? (await PackageUtil.resolveWorkspaces(this.ctx, req.sourcePath)) : [];
async init(): Promise<Iterable<PackageVisitReq<PackageModule>>> {
const mainPkg = PackageUtil.readPackage(this.#mainSourcePath);
const mainReq = this.create(mainPkg, { main: true, workspace: true, roleRoot: true, prod: true });
const globals = [mainReq];
this.#workspaceModules = new Map(
(await PackageUtil.resolveWorkspaces(this.ctx)).map(x => [x.name, x.path])
);
this.#mainLikeModules = new Set([
pkg.name,
...Object.entries(pkg.travetto?.build?.withModules ?? []).filter(x => x[1] === 'main').map(x => x[0]),
// Add workspace folders, for tests and docs
...workspaceModules.map(x => x.name)
]);
// Treat all workspace modules as main modules
if (this.ctx.workspace.mono && !this.ctx.main.folder) {
for (const [, loc] of this.#workspaceModules) {
const depPkg = PackageUtil.readPackage(loc);
globals.push(this.create(depPkg, { main: true, workspace: true, roleRoot: true }));
}
} else {
// If we have 'withModules' at workspace root
const root = PackageUtil.readPackage(this.ctx.workspace.path);
for (const [name, type] of Object.entries(root.travetto?.build?.withModules ?? {})) {
const depPkg = PackageUtil.readPackage(PackageUtil.resolvePackagePath(name));
globals.push(this.create(depPkg, { main: type === 'main', workspace: true }));
}
}
const globals = Object.keys(workspacePkg.travetto?.build?.withModules ?? [])
.map(name => PackageUtil.packageReq<ModuleDep>(PackageUtil.resolvePackagePath(name), name in (workspacePkg.dependencies ?? {}), true));
const workspaceModuleDeps = workspaceModules
.map(entry => PackageUtil.packageReq<ModuleDep>(path.resolve(req.sourcePath, entry.sourcePath), false, true));
return [...globals, ...workspaceModuleDeps];
return globals.map((x, i) => i === 0 ? x : { ...x, parent: mainReq.value });
}

@@ -62,25 +56,29 @@

*/
valid(req: PackageVisitReq<ModuleDep>): boolean {
return req.sourcePath === this.#mainSourcePath || (
(!!req.pkg.travetto || req.pkg.private === true || !req.sourcePath.includes('node_modules'))
);
valid({ value: node }: PackageVisitReq<PackageModule>): boolean {
return node.workspace || !!node.state.travetto; // Workspace or travetto module
}
/**
* Create dependency from request
* Build a package module
*/
create(req: PackageVisitReq<ModuleDep>): ModuleDep {
const { pkg, sourcePath } = req;
const { name, version } = pkg;
const main = name === this.ctx.main.name;
const mainLike = main || this.#mainLikeModules.has(name);
const internal = pkg.private === true;
const local = internal || mainLike || !sourcePath.includes('node_modules');
const dep: ModuleDep = {
name, version, sourcePath, main, mainLike, local, internal, pkg: req.pkg,
parentSet: new Set([]), childSet: new Set([]), roleSet: new Set([]), prod: req.prod, topLevel: req.topLevel
create(pkg: Package, { main, workspace, prod = false, roleRoot = false }: CreateOpts = {}): PackageVisitReq<PackageModule> {
const sourcePath = PackageUtil.getPackagePath(pkg);
const value = this.#cache[sourcePath] ??= {
main,
prod,
name: pkg.name,
version: pkg.version,
workspace: workspace ?? this.#workspaceModules.has(pkg.name),
internal: pkg.private === true,
sourceFolder: sourcePath === this.ctx.workspace.path ? '' : sourcePath.replace(`${this.ctx.workspace.path}/`, ''),
outputFolder: `node_modules/${pkg.name}`,
state: {
childSet: new Set(), parentSet: new Set(), roleSet: new Set(), roleRoot,
travetto: pkg.travetto, prodDeps: new Set(Object.keys(pkg.dependencies ?? {}))
}
};
return dep;
const deps: PackageDepType[] = ['dependencies', ...(value.main ? ['devDependencies'] as const : [])];
const children = Object.fromEntries(deps.flatMap(x => Object.entries(pkg[x] ?? {})));
return { pkg, value, children };
}

@@ -91,7 +89,7 @@

*/
visit(req: PackageVisitReq<ModuleDep>, dep: ModuleDep): void {
const { parent } = req;
if (parent && dep.name !== this.ctx.main.name) {
dep.parentSet.add(parent.name);
parent.childSet.add(dep.name);
visit({ value: mod, parent }: PackageVisitReq<PackageModule>): void {
if (mod.name === this.ctx.main.name) { return; } // Skip root
if (parent) {
mod.state.parentSet.add(parent.name);
parent.state.childSet.add(mod.name);
}

@@ -103,23 +101,17 @@ }

*/
complete(deps: Set<ModuleDep>): Set<ModuleDep> {
const mapping = new Map<string, { parent: Set<string>, child: Set<string>, el: ModuleDep }>();
for (const el of deps) {
mapping.set(el.name, { parent: new Set(el.parentSet), child: new Set(el.childSet), el });
}
async complete(mods: Iterable<PackageModule>): Promise<PackageModule[]> {
const mapping = new Map([...mods].map(el => [el.name, { parent: new Set(el.state.parentSet), el }]));
const main = mapping.get(this.ctx.main.name)!;
// Visit all direct dependencies and mark
for (const { el } of mapping.values()) {
if (!main.child.has(el.name)) { // Not a direct descendent
el.prod = false;
// All first-level dependencies should have role filled in (for propagation)
for (const dep of [...mods].filter(x => x.state.roleRoot)) {
dep.state.roleSet.clear(); // Ensure the roleRoot is empty
for (const c of dep.state.childSet) { // Visit children
const cDep = mapping.get(c)!.el;
if (cDep.state.roleRoot) { continue; }
// Set roles for all top level modules
cDep.state.roleSet = new Set(cDep.state.travetto?.roles ?? ['std']);
}
if (main.child.has(el.name) || (el.topLevel && el !== main.el)) { // Direct descendant
el.roleSet = new Set(el.pkg.travetto?.roles ?? []);
if (!el.roleSet.size) {
el.roleSet.add('std');
}
}
}
// Visit all nodes
while (mapping.size > 0) {

@@ -130,11 +122,16 @@ const toProcess = [...mapping.values()].filter(x => x.parent.size === 0);

}
// Propagate
for (const { el, child } of toProcess) {
for (const c of child.keys()) {
const { el: cDep, parent } = mapping.get(c)!;
parent.delete(el.name); // Remove from child
for (const role of el.roleSet) {
cDep.roleSet.add(role);
// Propagate to children
for (const { el } of toProcess) {
for (const c of el.state.childSet) {
const child = mapping.get(c);
if (!child) { continue; }
child.parent.delete(el.name);
// Propagate roles from parent to child
if (!child.el.state.roleRoot) {
for (const role of el.state.roleSet) {
child.el.state.roleSet.add(role);
}
}
cDep.prod ||= el.prod; // Allow prod to trickle down as needed
// Allow prod to trickle down as needed
child.el.prod ||= (el.prod && el.state.prodDeps.has(c));
}

@@ -148,8 +145,9 @@ }

// Color parent as final step
main.el.prod = true;
main.el.roleSet.add('std');
// Mark as standard at the end
for (const dep of [...mods].filter(x => x.state.roleRoot)) {
dep.state.roleSet = new Set(['std']);
}
return deps;
return [...mods].sort((a, b) => a.name.localeCompare(b.name));
}
}

@@ -6,3 +6,2 @@ import os from 'node:os';

import { path } from './path';
import type { ManifestContext } from './types';

@@ -20,3 +19,3 @@ export class ManifestFileUtil {

await fs.copyFile(temp, file);
fs.unlink(temp);
fs.unlink(temp); // Don't wait for completion
return file;

@@ -38,21 +37,2 @@ }

}
/**
* Stat file
*/
static statFile(file: string): Promise<{ mtimeMs: number, ctimeMs: number } | undefined> {
return fs.stat(file).catch(() => undefined);
}
/**
* Resolve tool path for usage
*/
static toolPath(ctx: ManifestContext | { manifest: ManifestContext }, rel: string, moduleSpecific = false): string {
ctx = 'manifest' in ctx ? ctx.manifest : ctx;
const parts = [rel];
if (moduleSpecific) {
parts.unshift('node_modules', ctx.main.name);
}
return path.resolve(ctx.workspace.path, '.trv/tool', ...parts);
}
}

@@ -0,35 +1,10 @@

import { existsSync } from 'node:fs';
import { ManifestModuleUtil } from './module';
import { path } from './path';
import {
ManifestModule, ManifestModuleCore, ManifestModuleFile,
ManifestModuleFileType, ManifestModuleFolderType, ManifestModuleRole, ManifestRoot
} from './types';
import { ManifestUtil } from './util';
export type FindConfig = {
folder?: (folder: ManifestModuleFolderType) => boolean;
module?: (module: IndexedModule) => boolean;
file?: (file: IndexedFile) => boolean;
sourceOnly?: boolean;
};
import type { ManifestModuleFolderType } from './types/common';
import type { ManifestModule, ManifestRoot, ManifestModuleFile, IndexedModule, IndexedFile, FindConfig } from './types/manifest';
export type IndexedFile = {
id: string;
import: string;
module: string;
sourceFile: string;
outputFile: string;
relativeFile: string;
role: ManifestModuleRole;
type: ManifestModuleFileType;
};
export type IndexedModule = ManifestModuleCore & {
sourcePath: string;
outputPath: string;
files: Record<ManifestModuleFolderType, IndexedFile[]>;
};
const TypedObject: {

@@ -46,3 +21,3 @@ keys<T = unknown, K extends keyof T = keyof T>(o: T): K[];

#manifestFile: string;
#arbitraryLookup?: (parts: string[]) => ManifestModule | undefined;
#manifest: ManifestRoot;

@@ -73,10 +48,4 @@ #modules: IndexedModule[];

get manifestFile(): string {
return this.#manifestFile;
}
init(manifestInput: string): void {
const { manifest, file } = ManifestUtil.readManifestSync(manifestInput);
this.#manifest = manifest;
this.#manifestFile = file;
this.#manifest = ManifestUtil.readManifestSync(manifestInput);
this.#outputRoot = path.resolve(this.#manifest.workspace.path, this.#manifest.build.outputFolder);

@@ -109,2 +78,3 @@ this.#index();

this.#sourceToEntry.clear();
this.#arbitraryLookup = undefined;

@@ -116,2 +86,3 @@ this.#modules = Object.values(this.#manifest.modules)

sourcePath: path.resolve(this.#manifest.workspace.path, m.sourceFolder),
children: new Set(),
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions

@@ -135,2 +106,9 @@ files: Object.fromEntries(

this.#modulesByFolder = Object.fromEntries(this.#modules.map(x => [x.sourceFolder, x]));
// Store child information
for (const mod of this.#modules) {
for (const p of mod.parents) {
this.#modulesByName[p]?.children.add(mod.name);
}
}
}

@@ -146,7 +124,7 @@

/**
* Get all local modules
* Get all workspace modules
* @returns
*/
getLocalModules(): IndexedModule[] {
return this.#modules.filter(x => x.local);
getWorkspaceModules(): IndexedModule[] {
return this.#modules.filter(x => x.workspace);
}

@@ -246,8 +224,5 @@

*/
getModuleList(mode: 'local' | 'all', exprList: string = ''): Set<string> {
getModuleList(mode: 'workspace' | 'all', exprList: string = ''): Set<string> {
const allMods = Object.keys(this.#manifest.modules);
const active = new Set<string>(
mode === 'local' ? this.getLocalModules().map(x => x.name) :
(mode === 'all' ? allMods : [])
);
const active = new Set<string>(mode === 'workspace' ? this.getWorkspaceModules().map(x => x.name) : allMods);

@@ -267,19 +242,36 @@ for (const expr of exprList.split(/\s*,\s*/g)) {

/**
* Get all modules (transitively) that depend on this module
* Get all modules, parents or children, (transitively) of the provided root, in a DFS fashion
*/
getDependentModules(root: IndexedModule): Set<IndexedModule> {
getDependentModules(root: IndexedModule | string, field: 'parents' | 'children'): IndexedModule[] {
const seen = new Set<string>();
const out = new Set<IndexedModule>();
const toProcess = [root.name];
const out: IndexedModule[] = [];
const toProcess = [typeof root === 'string' ? root : root.name];
while (toProcess.length) {
const next = toProcess.shift()!;
if (seen.has(next)) {
continue;
if (!seen.has(next)) {
seen.add(next);
const mod = this.getModule(next)!;
toProcess.push(...mod[field]);
if (next !== this.#manifest.main.name) { // Do not include self
out.push(mod);
}
}
const mod = this.getModule(next)!;
toProcess.push(...mod.parents);
out.add(mod);
}
return out;
}
/**
* Find the module for an arbitrary source file, if it falls under a given workspace module
*/
findModuleForArbitraryFile(file: string): ManifestModule | undefined {
const base = this.#manifest.workspace.path;
const lookup = this.#arbitraryLookup ??= ManifestUtil.lookupTrie(
Object.values(this.#manifest.modules),
x => x.sourceFolder.split('/'),
sub =>
!existsSync(path.resolve(base, ...sub, 'package.json')) &&
!existsSync(path.resolve(base, ...sub, '.git'))
);
return lookup(file.replace(`${base}/`, '').split('/'));
}
}
import fs from 'node:fs/promises';
import { path } from './path';
import {
ManifestContext,
ManifestModule, ManifestModuleFile, ManifestModuleFileType,
ManifestModuleFolderType,
ManifestModuleRole
} from './types';
import { ModuleDep, ModuleDependencyVisitor } from './dependencies';
import { PackageUtil } from './package';
import { PackageModuleVisitor } from './dependencies';
import type { ManifestModuleFileType, ManifestModuleRole, ManifestModuleFolderType } from './types/common';
import type { ManifestModuleFile, ManifestModule, PackageModule } from './types/manifest';
import type { ManifestContext } from './types/context';
const EXT_MAPPING: Record<string, ManifestModuleFileType> = {

@@ -50,5 +48,6 @@ '.js': 'js',

*/
static async scanFolder(folder: string, mainLike = false): Promise<string[]> {
if (!mainLike && folder in this.#scanCache) {
return this.#scanCache[folder];
static async scanFolder(folder: string, full = false): Promise<string[]> {
const key = `${folder}|${full}`;
if (key in this.#scanCache) {
return this.#scanCache[key];
}

@@ -77,3 +76,3 @@

for (const sub of await fs.readdir(top)) {
const valid = !sub.startsWith('.') && (depth > 0 || mainLike);
const valid = !sub.startsWith('.') && (depth > 0 || full);
const stat = await fs.stat(`${top}/${sub}`);

@@ -92,7 +91,3 @@ if (stat.isFile()) {

if (!mainLike) {
this.#scanCache[folder] = out;
}
return out;
return this.#scanCache[key] = out;
}

@@ -183,8 +178,9 @@

*/
static async describeModule(ctx: ManifestContext, dep: ModuleDep): Promise<ManifestModule> {
const { main, mainLike, local, name, version, sourcePath, roleSet, prod, parentSet, internal } = dep;
static async describeModule(ctx: ManifestContext, mod: PackageModule): Promise<ManifestModule> {
const { state, ...rest } = mod;
const sourcePath = path.resolve(ctx.workspace.path, rest.sourceFolder);
const files: ManifestModule['files'] = {};
for (const file of await this.scanFolder(sourcePath, mainLike)) {
for (const file of await this.scanFolder(sourcePath, rest.main)) {
// Group by top folder

@@ -197,16 +193,8 @@ const moduleFile = file.replace(`${sourcePath}/`, '');

// Refine non-main source, remove anything in root that is source (doesn't include $index)
if (!mainLike) {
files.$root = files.$root?.filter(([file, type]) => type !== 'ts');
}
const roles = [...roleSet ?? []].sort();
const parents = [...parentSet].sort();
const outputFolder = `node_modules/${name}`;
const sourceFolder = sourcePath === ctx.workspace.path ? '' : sourcePath.replace(`${ctx.workspace.path}/`, '');
const res: ManifestModule = {
main, name, version, local, internal, sourceFolder, outputFolder, roles, parents, prod, files
return {
...rest,
roles: [...state.roleSet].sort(),
parents: [...state.parentSet].sort(),
files
};
return res;
}

@@ -218,6 +206,4 @@

static async produceModules(ctx: ManifestContext): Promise<Record<string, ManifestModule>> {
const visitor = new ModuleDependencyVisitor(ctx);
const declared = await PackageUtil.visitPackages(visitor);
const sorted = [...declared].sort((a, b) => a.name.localeCompare(b.name));
const modules = await Promise.all(sorted.map(x => this.describeModule(ctx, x)));
const pkgs = await PackageUtil.visitPackages(new PackageModuleVisitor(ctx));
const modules = await Promise.all([...pkgs].map(x => this.describeModule(ctx, x)));
return Object.fromEntries(modules.map(m => [m.name, m]));

@@ -224,0 +210,0 @@ }

import { createRequire } from 'node:module';
import { execSync } from 'node:child_process';
import type { ManifestContext, NodePackageManager, Package, PackageVisitor, PackageVisitReq, PackageWorkspaceEntry } from './types';
import { path } from './path';
import { ManifestFileUtil } from './file';
import { PackagePath, type Package, type PackageVisitor, type PackageWorkspaceEntry } from './types/package';
import type { ManifestContext } from './types/context';
import type { NodePackageManager } from './types/common';
/**

@@ -34,5 +37,5 @@ * Utilities for querying, traversing and reading package.json files.

*/
static resolveVersionPath(rootPath: string, ver: string): string | undefined {
static resolveVersionPath(root: Package, ver: string): string | undefined {
if (ver.startsWith('file:')) {
return path.resolve(rootPath, ver.replace('file:', ''));
return path.resolve(this.getPackagePath(root), ver.replace('file:', ''));
} else {

@@ -59,27 +62,2 @@ return;

/**
* Build a package visit req
*/
static packageReq<T>(sourcePath: string, prod: boolean, topLevel?: boolean): PackageVisitReq<T> {
return { pkg: this.readPackage(sourcePath), sourcePath, prod, topLevel };
}
/**
* Extract all dependencies from a package
*/
static getAllDependencies<T = unknown>(modulePath: string, local: boolean): PackageVisitReq<T>[] {
const pkg = this.readPackage(modulePath);
const children: Record<string, PackageVisitReq<T>> = {};
for (const [deps, prod] of [
[pkg.dependencies, true],
...(local ? [[pkg.devDependencies, false] as const] : []),
] as const) {
for (const [name, version] of Object.entries(deps ?? {})) {
const depPath = this.resolveVersionPath(modulePath, version) ?? this.resolvePackagePath(name);
children[`${name}#${version}`] = this.packageReq<T>(depPath, prod, false);
}
}
return Object.values(children).sort((a, b) => a.pkg.name.localeCompare(b.pkg.name));
}
/**
* Read a package.json from a given folder

@@ -97,2 +75,3 @@ */

res[PackagePath] = modulePath;
return res;

@@ -102,6 +81,6 @@ }

/**
* import a package.json from a given module name
* Get the package path
*/
static importPackage(moduleName: string): Package {
return this.readPackage(this.resolvePackagePath(moduleName));
static getPackagePath(pkg: Package): string {
return pkg[PackagePath];
}

@@ -112,38 +91,29 @@

*/
static async visitPackages<T>(visitor: PackageVisitor<T>): Promise<Set<T>> {
static async visitPackages<T>(visitor: PackageVisitor<T>): Promise<Iterable<T>> {
const seen = new Set<T>();
const queue = [...await visitor.init()];
const root = this.packageReq<T>(visitor.rootPath, false, true);
const seen = new Map<string, T>();
const queue: PackageVisitReq<T>[] = [...await visitor.init?.(root) ?? [], root];
const out = new Set<T>();
while (queue.length) {
const req = queue.pop();
const node = queue.shift()!; // Visit initial set first
if (!req || (visitor.valid && !visitor.valid(req))) {
if (!visitor.valid(node)) {
continue;
}
const key = req.sourcePath;
if (seen.has(key)) {
await visitor.visit?.(req, seen.get(key)!);
visitor.visit(node);
if (seen.has(node.value)) {
continue;
} else {
const dep = await visitor.create(req);
out.add(dep);
await visitor.visit?.(req, dep);
seen.set(key, dep);
const children = this.getAllDependencies<T>(
req.sourcePath,
// We consider a module local if its not in the node_modules
!req.sourcePath.includes('node_modules') && (
// And its the root or we are in a monorepo
root.sourcePath === req.sourcePath ||
!!root.pkg.workspaces
)
);
queue.push(...children.map(x => ({ ...x, parent: dep })));
seen.add(node.value);
}
const children = Object.entries(node.children)
.map(([n, v]) => this.readPackage(this.resolveVersionPath(node.pkg, v) ?? this.resolvePackagePath(n)))
.map(pkg => ({ ...visitor.create(pkg), parent: node.value }));
queue.push(...children);
}
return (await visitor.complete?.(out)) ?? out;
return await visitor.complete(seen);
}

@@ -154,28 +124,19 @@

*/
static async resolveWorkspaces(ctx: ManifestContext, rootPath: string): Promise<PackageWorkspaceEntry[]> {
if (!this.#workspaces[rootPath]) {
const cache = path.resolve(ctx.workspace.path, ctx.build.outputFolder, 'workspaces.json');
try {
return await ManifestFileUtil.readAsJson(cache);
} catch (err) {
static async resolveWorkspaces(ctx: ManifestContext): Promise<PackageWorkspaceEntry[]> {
const rootPath = ctx.workspace.path;
const cache = path.resolve(rootPath, ctx.build.outputFolder, 'workspaces.json');
return this.#workspaces[rootPath] ??= await ManifestFileUtil.readAsJson<PackageWorkspaceEntry[]>(cache)
.catch(async () => {
let out: PackageWorkspaceEntry[];
switch (ctx.workspace.manager) {
case 'yarn':
case 'npm': {
const res = await this.#exec<{ location: string, name: string }[]>(rootPath, 'npm query .workspace');
out = res.map(d => ({ sourcePath: d.location, name: d.name }));
out = res.map(d => ({ path: path.resolve(ctx.workspace.path, d.location), name: d.name }));
break;
}
case 'yarn': {
const res = await this.#exec<Record<string, { location: string }>>(rootPath, 'npm query .workspace');
out = Object.entries(res).map(([name, { location }]) => ({ sourcePath: location, name }));
break;
}
}
this.#workspaces[rootPath] = out;
await ManifestFileUtil.bufferedFileWrite(cache, out);
}
}
return this.#workspaces[rootPath];
return out;
});
}

@@ -182,0 +143,0 @@

import { path } from './path';
import { IndexedModule, ManifestIndex } from './manifest-index';
import { FunctionMetadata, ManifestContext, ManifestModule } from './types';
import { ManifestIndex } from './manifest-index';
import type { FunctionMetadata } from './types/common';
import type { IndexedModule, ManifestModule } from './types/manifest';
import type { ManifestContext } from './types/context';
const METADATA = Symbol.for('@travetto/manifest:metadata');

@@ -125,3 +128,17 @@ type Metadated = { [METADATA]: FunctionMetadata };

return path.resolve(RuntimeIndex.manifest.workspace.path, ...rel);
},
/**
* Produce a workspace path for tooling, with '@' being replaced by node_module/name folder
* @param rel The relative path
*/
toolPath(...rel: string[]): string {
rel = rel.flatMap(x => x === '@' ? ['node_modules', RuntimeIndex.manifest.main.name] : [x]);
return path.resolve(RuntimeIndex.manifest.workspace.path, RuntimeIndex.manifest.build.toolFolder, ...rel);
},
/**
* Are we running from a mono-root?
*/
get monoRoot(): boolean {
return !!RuntimeIndex.manifest.workspace.mono && !RuntimeIndex.manifest.main.folder;
}
}, ['main', 'workspace']);
import { path } from './path';
import { ManifestContext, ManifestRoot } from './types';
import { ManifestModuleUtil } from './module';

@@ -7,2 +6,5 @@ import { ManifestFileUtil } from './file';

import type { ManifestContext } from './types/context';
import type { ManifestRoot } from './types/manifest';
const MANIFEST_FILE = 'manifest.json';

@@ -19,5 +21,7 @@

return {
generated: Date.now(),
workspace: ctx.workspace,
build: ctx.build,
main: ctx.main,
modules: await ManifestModuleUtil.produceModules(ctx),
generated: Date.now(),
...ctx
};

@@ -27,13 +31,17 @@ }

/**
* Produce a manifest location given a current context and a module name
*/
static getManifestLocation(ctx: ManifestContext, module?: string): string {
return path.resolve(ctx.workspace.path, ctx.build.outputFolder, 'node_modules', module ?? ctx.workspace.name);
}
/**
* Produce a production manifest from a given manifest
*/
static createProductionManifest(manifest: ManifestRoot): ManifestRoot {
const prodModules = Object.values(manifest.modules).filter(x => x.prod);
const prodModNames = new Set([...prodModules.map(x => x.name)]);
return {
...manifest,
// If in prod mode, only include std modules
modules: Object.fromEntries(
Object.values(manifest.modules)
.filter(x => x.prod)
.map(m => [m.name, m])
),
generated: manifest.generated,
workspace: manifest.workspace,
build: {

@@ -43,3 +51,9 @@ ...manifest.build,

outputFolder: '$$PRODUCTION$$',
}
},
main: manifest.main,
modules: Object.fromEntries(
prodModules.map(m => [m.name, Object.assign(m, {
parents: m.parents.filter(x => prodModNames.has(x))
})])
),
};

@@ -54,3 +68,3 @@ }

*/
static readManifestSync(file: string): { manifest: ManifestRoot, file: string } {
static readManifestSync(file: string): ManifestRoot {
file = path.resolve(file);

@@ -66,3 +80,3 @@ if (!file.endsWith('.json')) {

}
return { manifest, file };
return manifest;
}

@@ -98,3 +112,4 @@

return ctx.workspace.mono ? {
...ctx,
workspace: ctx.workspace,
build: ctx.build,
main: {

@@ -116,3 +131,4 @@ name: ctx.workspace.name,

return {
...ctx,
workspace: ctx.workspace,
build: ctx.build,
main: {

@@ -126,2 +142,41 @@ name: pkg.name,

}
/**
* Efficient lookup for path-based graphs
*/
static lookupTrie<T>(
inputs: T[], getPath: (v: T) => string[], validateUnknown?: (pth: string[]) => boolean
): (pth: string[]) => T | undefined {
type TrieNode = { value?: T, subs: Record<string, TrieNode> };
const root: TrieNode = { subs: {} };
for (const item of inputs) {
const pth = getPath(item);
let node = root;
for (const sub of pth) {
if (sub) {
node = node.subs[sub] ??= { subs: {} };
}
}
node.value = item;
}
return pth => {
let node = root;
let value = node.value;
let i = 0;
for (const sub of pth) {
i += 1;
if (node) {
node = node.subs[sub];
value = node?.value ?? value;
} else if (validateUnknown && !node && !validateUnknown(pth.slice(0, i))) {
value = undefined;
break;
}
}
return value;
};
}
}
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc