@plugjs/plug
Version:
PlugJS Build System ===================
223 lines • 8.33 kB
JavaScript
import { statSync } from 'node:fs';
import { createRequire } from 'node:module';
import { dirname, extname, isAbsolute, join, normalize, relative, resolve, sep } from 'node:path';
import { fileURLToPath, pathToFileURL } from 'node:url';
import { assert } from "./asserts.js";
/* ========================================================================== *
* PATH FUNCTIONS *
* ========================================================================== */
/** Resolve a path into an {@link AbsolutePath} */
export function resolveAbsolutePath(directory, ...paths) {
assertAbsolutePath(directory);
return resolve(directory, ...paths);
}
/**
* Resolve a path as a relative path to the directory specified, returning the
* relative child path or `undefined` if the specified path was not a child.
*
* The `path` specified here _could_ also be another {@link AbsolutePath}
* therefore something like this will work:
*
* ```
* resolveRelativeChildPath('/foo', '/foo/bar')
* // will yield `bar`
* ```
*/
export function resolveRelativeChildPath(directory, ...paths) {
const abs = resolveAbsolutePath(directory, ...paths);
const rel = relative(directory, abs);
return (isAbsolute(rel) || (rel === '..') || rel.startsWith(`..${sep}`)) ? undefined : rel;
}
/**
* Asserts that a path is a relative path to the directory specified, failing
* the build if it's not (see also {@link resolveRelativeChildPath}).
*/
export function assertRelativeChildPath(directory, ...paths) {
const relative = resolveRelativeChildPath(directory, ...paths);
assert(relative, `Path "${join(...paths)}" not relative to "${directory}"`);
return relative;
}
/** Checks that the specified path is an {@link AbsolutePath} */
export function isAbsolutePath(path) {
return isAbsolute(path);
}
/** Asserts that the specified path is an {@link AbsolutePath} */
export function assertAbsolutePath(p) {
assert(isAbsolute(p), `Path "${p}" not absolute`);
}
/** Return the {@link AbsolutePath} parent of another */
export function getAbsoluteParent(path) {
assertAbsolutePath(path);
return dirname(path);
}
/**
* Return the {@link process.cwd() | current working directory} as an
* {@link AbsolutePath}.
*/
export function getCurrentWorkingDirectory() {
const cwd = process.cwd();
assertAbsolutePath(cwd);
return cwd;
}
/**
* Return the _common_ path amongst all specified paths.
*
* While the first `path` _must_ be an {@link AbsolutePath}, all other `paths`
* can be _relative_ and will be resolved against the first `path`.
*/
export function commonPath(path, ...paths) {
assertAbsolutePath(path);
// Here the first path will be split into its components
// on win => [ 'C:', 'Windows', 'System32' ]
// on unx => [ '', 'usr'
const components = normalize(path).split(sep);
let length = components.length;
for (const current of paths) {
const absolute = resolveAbsolutePath(path, current);
const parts = absolute.split(sep);
for (let i = 0; i < length; i++) {
if (components[i] !== parts[i]) {
length = i;
break;
}
}
assert(length, 'No common ancestors amongst paths');
}
const common = components.slice(0, length).join(sep);
assertAbsolutePath(common);
return common;
}
/* ========================================================================== *
* MODULE RESOLUTION FUNCTIONS *
* ========================================================================== */
/** Resolve a file URL or a path to an {@link AbsolutePath} */
function resolveFilename(fileurl) {
const file = fileurl.startsWith('file:') ? fileURLToPath(fileurl) : fileurl;
assertAbsolutePath(file);
return file;
}
/**
* Asserts that `fileurl` (a path or a `file:...` URL) resolves to an
* existing file.
*/
export function filenameFromUrl(fileurl) {
const file = resolveFilename(fileurl);
assert(resolveFile(file), `Unable to resolve "${fileurl}" as a file`);
return file;
}
/**
* Asserts that the _parent_ path of `fileurl` (a path or a `file:...` URL)
* resolves to an existing directory.
*/
export function dirnameFromUrl(fileurl) {
const dir = getAbsoluteParent(resolveFilename(fileurl));
assert(resolveDirectory(dir), `Unable to resolve "${fileurl}" as a directory`);
return dir;
}
/**
* Return the absolute path of of `fileurl` (a path or a `file:...` URL) or
* (if further `paths` are specified) the absolute path of a file relative to
* it.
*
* If no `paths` are specified, this will simply return the {@link AbsolutePath}
* of `fileurl`:
*
* ```ts
* const thisFile = requireFilename(import.meta.url)
* // if we write this in "/foo/bar/baz.(ts|js|cjs|mjs)"
* // `thisFile` will now be "/foo/bar/baz.(ts|js|cjs|mjs)"
* ```
*
* If further `paths` are specified, those will be resolved as relative paths
* to the original `fileurl` so we can easily write something like this:
*
* ```ts
* const dataFile = requireFilename(import.meta.url, 'data.json')
* // if we write this in "/foo/bar/baz.(ts|js|cjs|mjs)"
* // `dataFile` will now be "/foo/bar/data.json"
* ```
*/
export function requireFilename(fileurl, ...paths) {
const file = resolveFilename(fileurl);
/* No paths? Return the file! */
if (!paths.length)
return file;
/* Resolve any paths, relative to the file */
const directory = getAbsoluteParent(file);
return resolveAbsolutePath(directory, ...paths);
}
/**
* Return the absolute path of a file which can be _required_ or _imported_
* by Node (or forked to via `child_process.fork`).
*
* This leverages {@link requireFilename} to figure out the starting point where
* to look for files, and will _try_ to match the same extension of `fileurl`
* (so, `.ts` for `ts-node`, `.mjs` for ESM modules, ...).
*
* Extension matching is performed that the same instruction can be used in
* various scenarions (e.g. `.ts` when running dynamically, `.js`, `.mjs` or
* `.cjs` after transpilation)
*/
export function requireResolve(fileurl, module) {
const file = resolveFilename(fileurl);
// We do our custom resolution _only_ for local (./foo.bar) files...
if (module.match(/^\.\.?\//)) {
// If we import "../foo.ext" from "/a/b/c/bar.ts" we need to check:
// * /a/b/foo.ext
// * /a/b/foo.ext.ts
// * /a/b/foo.ext/index.ts
// ... then delegate to the standard "require.resolve(...)"
const url = pathToFileURL(file);
const ext = extname(file);
const checks = [`${module}`, `${module}${ext}`, `${module}/index${ext}`];
for (const check of checks) {
const resolved = fileURLToPath(new URL(check, url));
if (resolveFile(resolved)) {
module = check;
break;
}
}
}
const require = createRequire(file);
const required = require.resolve(module);
assertAbsolutePath(required);
return required;
}
/* ========================================================================== *
* FILE CHECKING FUNCTIONS *
* ========================================================================== */
/**
* Resolves the specified path as an {@link AbsolutePath} and checks it is a
* _file_, returning `undefined` if it is not.
*/
export function resolveFile(path, ...paths) {
const file = resolveAbsolutePath(path, ...paths);
try {
const stat = statSync(file);
if (stat.isFile())
return file;
}
catch (error) {
if (error.code !== 'ENOENT')
throw error;
}
return undefined;
}
/**
* Resolves the specified path as an {@link AbsolutePath} and checks it is a
* _directory_, returning `undefined` if it is not.
*/
export function resolveDirectory(path, ...paths) {
const directory = resolveAbsolutePath(path, ...paths);
try {
const stat = statSync(directory);
if (stat.isDirectory())
return directory;
}
catch (error) {
if (error.code !== 'ENOENT')
throw error;
}
return undefined;
}
//# sourceMappingURL=paths.js.map