/**
* @file src/plugins/ts/dtsbundle.js
* @copyright @spmhome @_2025
* @author Scott Meesseman @spmeesseman
*//** */
// Object.defineProperty(exports, "__esModule", { value: true });
const os = require("os");
const { join, sep } = require("path");
const { wpwVersion } = require("../../utils/utils");
const { SpmhMessageUtils } = require("@spmhome/log-utils");
const { apply, pickNot, isFunction, isRegExp, pluralize } = require("@spmhome/type-utils");
const {
createDirAsync, deleteFileSync, dirname, existsSync, filename, isDirectory, isDirectoryEmpty, isFile,
findFilesSync, findExPath, findExPathSync, forwardSlash, findFiles, readFileAsync, replaceInFile, relativePath,
relativePathEx, resolvePath, writeFileAsync
} = require("@spmhome/cmn-utils");
// const mkdirp = require("mkdirp");
// const detectIndent = require("detect-indent");
const dtsExp = /\.d\.ts$/;
const bomOptExp = /^\uFEFF?/;
const externalExp = /^([ \t]*declare module )(['"])(.+?)(\2[ \t]*{?.*)$/;
const importExp = /^([ \t]*(?:export )?(?:import .+? )= require\()(['"])(.+?)(\2\);.*)$/;
// eslint-disable-next-line stylistic/max-len
const importEs6Exp = /^([ \t]*(?:export|import) ?(?:(?:\* (?:as [^ ,]+)?)|.*)?,? ?(?:[^ ,]+ ?,?)(?:\{(?:[^ ,]+ ?,?)*\})? ?from )(['"])([^ ,]+)(\2;.*)$/;
const referenceTagExp = /^[ \t]*\/\/\/[ \t]*<reference[ \t]+path=(["'])(.*?)\1?[ \t]*\/>.*$/;
const identifierExp = /^\w+(?:[.-]\w+)*$/;
const fileExp = /^([./].*|.:.*)$/;
const privateExp = /^[ \t]*(?:static )?private (?:static )?/;
const publicExp = /^([ \t]*)(static |)(public |)(static |)(.*)/;
class WpwTypeDeclarationsBundler
{
/**
* @private
* @type {WpwBuild}
*/
build;
/**
* @private
* @type {string}
*/
exportName;
/**
* @type {string[]}
*/
externalTypings = [];
/**
* @type {any[]}
*/
globalExternalImports = [];
/**
* @private
* @type {string}
*/
infoProp;
/**
* @private
* @type {WpwLogger}
*/
logger;
/**
* @private
* @type {WpwPlugin}
*/
plugin;
/**
* @param {WpwPlugin} plugin
* @param {string} infoProp
*/
constructor(plugin, infoProp)
{
this.plugin = plugin;
this.build = plugin.build;
this.logger = plugin.build.logger;
this.infoProp = infoProp || plugin.optionsKey;
}
/**
* @param {WpwTypesDtsBundleOptions} options
* @returns {WpwTypesDtsBundleOptions}
*/
applyOptions(options)
{
const _baseDir = (() => {
const baseDir = this.optValue(options.baseDir, dirname(options.main));
return this.allFiles && !options.baseDir ? baseDir.substr(0, baseDir.length - 2) : baseDir;
})();
this.exportName = options.name;
this.globalExternalImports = [];
this.main = this.allFiles ? "*.d.ts" : options.main;
this.out = this.optValue(options.out, this.exportName + ".d.ts").replace(/\//g, sep);
this.newline = this.optValue(options.newline, os.EOL);
this.indent = this.optValue(options.indent, " ");
this.outputAsModuleFolder = this.optValue(options.outputAsModuleFolder, false);
this.prefix = this.optValue(options.prefix, "");
this.separator = this.optValue(options.separator, "/");
this.externals = this.optValue(options.externals, false);
this.exclude = this.optValue(options.exclude, null);
this.removeSource = this.optValue(options.removeSource, false);
this.referenceExternals = this.optValue(options.referenceExternals, false);
this.emitOnIncludedFileNotFound = this.optValue(options.emitOnIncludedFileNotFound, false);
this.emitOnNoIncludedFileNotFound = this.optValue(options.emitOnNoIncludedFileNotFound, false);
this._headerPath = this.optValue(options.headerPath, null);
this.headerText = this.optValue(options.headerText, "");
this.comments = false;
this.allFiles = this.stringEndsWith(options.main, "**/*.d.ts");
this.verbose = this.optValue(options.verbose, false);
this.baseDir = resolvePath(_baseDir);
this.mainFile = this.allFiles ? resolvePath(this.baseDir, "**/*.d.ts") :
resolvePath(this.main.replace(/\//g, sep));
this.outFile = this.calcOutFilePath(this.out, this.baseDir);
this.headerData = "// generated by webpack-wrap v" + wpwVersion() + this.newline;
this.headerPath = this._headerPath && this._headerPath !== "none" ?
resolvePath(this._headerPath.replace(/\//g, sep)) : this._headerPath;
return options;
}
/**
* @param {string} [pad]
* @returns {Promise<Exclude<WpwPluginTaskResult, boolean>>} absolute path to type declarations bundle
* @throws {WpwError}
*/
async bundle(pad = " ")
{
/** @type {Exclude<WpwPluginTaskResult, boolean>} */
let result;
const l = this.logger.write("bundle type declarations", 1, pad),
params = this.getParams();
try
{ const opts = this.applyOptions(await this.getOptions(params, pad + " "));
if (!opts){
throw (this.build.lastError || new Error("unable to create type declarations bundle"));
}
this.sourceTypings = (await findFiles("**/*.d.ts", { cwd: this.baseDir }))
.map((file) => resolvePath(this.baseDir, file));
result = await this._bundle(opts, pad + " ");
// await replaceInFile(params.outFileAbs, opts.name, this.build.pkgJson.name);
await replaceInFile(result.paths[0], opts.name, this.build.pkgJson.name);
result.srcpaths = this.sourceTypings;
}
catch (e)
{ throw SpmhMessageUtils.isSpmh(e) ? e : this.plugin.addMessage({
exception: e,
code: SpmhMessageUtils.Code.ERROR_TYPES_BUNDLE_FAILED,
message: "unable to create type declarations bundle"
}, true);
}
l.ok("bundle type declarations", 1, pad);
return result;
}
/**
* @param {WpwTypesDtsBundleOptions} options
* @param {string} [pad]
* @returns {Promise<Exclude<WpwPluginTaskResult, boolean>>}
*/
async _bundle(options, pad)
{
/*
assert(typeof options === "object" && options, "options must be an object");
const main = this.allFiles ? "*.d.ts" : options.main;
assert.ok(main, 'option "main" must be defined');
assert.ok(this.exportName, 'option "name" must be defined');
assert(typeof this.newline === "string", 'option "newline" must be a string');
assert(typeof this.indent === "string", 'option "indent" must be a string');
assert(typeof this.prefix === "string", 'option "prefix" must be a string');
assert(this.separator.length > 0, 'option "separator" must have non-zero length');
*/
this.logger.write("begin constructing bundle file content", 1, pad);
if (!this.allFiles) {
// assert(existsSync(this.mainFile), " main does not exist: " + this.mainFile);
}
if (this.headerPath)
{ if (this.headerPath === "none") {
this.headerData = "";
}
else {
// assert(existsSync(this.headerPath), "header does not exist: " + this.headerPath);
this.headerData = await readFileAsync(this.headerPath) + this.headerData;
}
}
else if (this.headerText)
{
this.headerData = "/*" + this.headerText + "*/\n";
}
let isExclude;
if (isFunction(this.exclude)) {
isExclude = this.exclude;
}
else if (isRegExp(this.exclude)) {
isExclude = (file) => this.exclude.test(file);
}
else { isExclude = () => { return false; }; }
if (this.allFiles)
{
let mainFileContent_1 = "";
this.logger.write("create temporally main file", 1, pad);
this.sourceTypings.forEach((file) =>
{
const generatedLine = "export * from './" +
relativePath(this.baseDir, file.substring(0, file.length - 5)).replace(sep, "/") + "';";
this.logger.write(generatedLine, 4, pad);
mainFileContent_1 += generatedLine + "\n";
});
this.mainFile = resolvePath(this.baseDir, "dts-bundle.tmp." + this.exportName + ".d.ts");
await writeFileAsync(this.mainFile, mainFileContent_1);
}
this.logger.write("find typings", 1, pad);
this.logger.write(" source typings (will be included in output if actually used)", 4, pad);
this.sourceTypings.forEach((file) => this.logger.write(file, 4, pad + " "));
this.logger.write(" excluded typings (will always be excluded from output)", 4, pad);
this.sourceTypings.forEach((file) => this.logger.write(file, 4, pad + " "));
const fileMap = Object.create(null),
queue = [ this.mainFile ];
let mainParse,
queueSeen = Object.create(null);
this.logger.write(`parse ${queue.length} main ${pluralize("file", queue.length)}`, 1, pad);
while (queue.length > 0)
{
const target = queue.shift();
if (queueSeen[target]) {
continue;
}
queueSeen[target] = true;
const parse = await this.parseFile(target, pad + " ");
if (!mainParse) {
mainParse = parse;
}
fileMap[parse.file] = parse;
this.pushUniqueArr(queue, parse.refs, parse.relativeImports);
}
this.logger.write(`parsed ${queue.length} asset ${pluralize("path", queue.length)}`, 4, pad);
const exportMap = Object.create(null),
fileMapKeys = Object.keys(fileMap);
if (fileMapKeys.length > 0)
{
this.logger.write(`map ${fileMapKeys.length} ${pluralize("export", fileMapKeys.length)}`, 1, pad);
Object.keys(fileMap).forEach((file) =>
{
const parse = fileMap[file];
parse.exports.forEach((name) =>
{
// assert(!(name in exportMap), " already processed export for " + name);
exportMap[name] = parse;
this.logger.value(` export ${name}`, parse.file, 4, pad);
});
});
}
const excludedTypings = [],
usedTypings = [],
externalDependencies = [],
queue_1 = [ mainParse ];
queueSeen = Object.create(null);
this.logger.write("dump queue:", 4, pad);
this.logger.write(queue_1, 4, pad + " ");
this.logger.write("determine imported / included typings", 1, pad);
while (queue_1.length > 0)
{
const parse = queue_1.shift();
if (queueSeen[parse.file]) {
continue;
}
queueSeen[parse.file] = true;
usedTypings.push(parse);
let aLen = parse.externalImports.length;
if (aLen > 0)
{
this.logger.write(` process ${aLen} external ${pluralize("import", aLen)}`, 3, pad);
parse.externalImports.forEach((name) =>
{
const p = exportMap[name];
if (!this.externals)
{
this.logger.value(" exclude external", name, 3, pad);
this.pushUnique(externalDependencies, !p ? name : p.file);
return;
}
if (isExclude(relativePath(this.baseDir, p.file)))
{
this.logger.value(" exclude external filter", name, 3, pad);
this.pushUnique(excludedTypings, p.file);
return;
}
this.logger.value(" include external", name, 4, pad);
// assert(p, name);
queue_1.push(p);
});
}
aLen = parse.relativeImports.length;
if (aLen > 0)
{
this.logger.write(` process ${aLen} relative ${pluralize("import", aLen)}`, 3, pad);
parse.relativeImports.forEach((file) =>
{
const p = fileMap[file];
if (isExclude(relativePath(this.baseDir, p.file)))
{
this.logger.value(" excluded by internal filter", file, 3, pad);
this.pushUnique(excludedTypings, p.file);
return;
}
this.logger.value(" import relative file", file, 5, pad);
// assert(p, file);
queue_1.push(p);
});
}
}
if (usedTypings.length > 0)
{
this.logger.write("rewrite global external modules", 1, pad);
usedTypings.forEach((parse) =>
{
parse.relativeRef.forEach((line) =>
{
line.modified = this.replaceExternal(line.original, this.getLibName);
this.logger.write(` transform ~ ${line.original} ==>`, 5, pad);
this.logger.write(` ${line.modified}`, 5, pad);
});
parse.importLineRef.forEach((line) =>
{ if (this.outputAsModuleFolder)
{
this.logger.write(` line '${line.original}' was skipped`, 5, pad);
line.skip = true;
return;
}
if (importExp.test(line.original)) {
line.modified = this.replaceImportExport(line.original, this.getLibName);
}
else {
line.modified = this.replaceImportExportEs6(line.original, this.getLibName);
}
this.logger.write(" line transform applied", 5, pad);
this.logger.value(" original line", line.original, 5, pad);
this.logger.value(" modified line", line.modified, 5, pad);
});
});
this.logger.write("completed rewrite of global external modules", 4, pad);
}
this.logger.write("build output", 1, pad);
let content = this.headerData;
if (externalDependencies.length > 0)
{
content += "// Dependencies for this module:" + this.newline;
externalDependencies.forEach((file) =>
{ if (this.referenceExternals) {
content += this.formatReference(relativePath(this.baseDir, file).replace(/\\/g, "/")) + this.newline;
}
else {
content += "// " + relativePath(this.baseDir, file).replace(/\\/g, "/") + this.newline;
}
});
}
if (this.globalExternalImports.length > 0) {
content += `${this.newline}${this.globalExternalImports.join(this.newline) + this.newline}`;
}
content += this.newline;
content += usedTypings.filter((parse) =>
{
parse.lines = parse.lines.filter((line) => line.skip !== true);
return (parse.lines.length > 0);
})
.map((parse) =>
{
if (this.inSourceTypings(parse.file))
{ return this.formatModule(
parse.file,
parse.lines.map((line) => this.getIndenter(parse.indent, this.indent)(line))
);
}
return parse.lines.map((line) => this.getIndenter(parse.indent, this.indent)(line))
.join(this.newline) + this.newline;
})
.join(this.newline) + this.newline;
if (this.removeSource)
{
this.logger.write(" remove source type declaration files", 2, pad);
this.sourceTypings
.filter((p) => p !== this.outFile && dtsExp.test(p) && isFile(p))
.forEach((p) => {
this.logger.write(" " + p, 4, pad);
deleteFileSync(p); }
);
}
const inUsed = (file) => usedTypings.filter((parse) => parse.file === file).length !== 0;
const notFound = Object.values(fileMap).filter((p) => !p.fileExists);
if (notFound.length > 0)
{
this.logger.write(` ${notFound.length} files were not found:`, 1, pad);
notFound.forEach((parse, i) =>
{
if (inUsed(parse.file))
{
this.logger.error(` (${i - 1}) ${parse.file} [used][fatal]`, pad);
this.plugin.addMessage({
code: SpmhMessageUtils.Code.ERROR_RESOURCE_MISSING,
message: `unable to read included declarations file ${parse.file} [not found]`
});
}
else {
this.logger.warn(` (${i - 1})${parse.file} [not_used][non_fatal]`, pad);
this.plugin.addMessage({
code: SpmhMessageUtils.Code.WARNING_RESOURCE_MISSING,
message: `unable to read un-included declarations file ${parse.file} [not found]`
});
}
});
}
if ((this.build.errorCount === 0 || this.emitOnIncludedFileNotFound) &&
(this.build.warnings.length === 0 || this.emitOnNoIncludedFileNotFound))
{
const outDir = dirname(this.outFile);
this.logger.write(` persist bundle content '${filename(this.outFile)}'`, 2, pad);
this.logger.value(" directory", outDir, 2, pad);
if (!existsSync(outDir)) {
await createDirAsync(outDir);
}
await writeFileAsync(this.outFile, content);
}
else
{ return void this.plugin.addMessage({
code: SpmhMessageUtils.Code.ERROR_RESOURCE_MISSING,
message: `could not find ${notFound.length} included files [included::${this.build.errorCount}]` +
`[not_included::[${this.build.warnings.length}]]`,
suggest: "if unable to resolve the issue, try setting the ignoreIncludedNotFound and / " +
"ignoreNotIncludedFileNotFound options"
});
}
if (this.logger.level >= 3)
{
this.logger.write(" used source typings:", 4, pad);
this.sourceTypings.filter((p) => inUsed(p)).forEach((p) => this.logger.write(p, 4, " "));
this.logger.write(" unused source typings:", 4, pad);
this.sourceTypings.filter((p) => !inUsed(p)).forEach((p) => this.logger.write(p, 4, " "));
this.logger.write(" excluded typings:", 4, pad);
excludedTypings.forEach((p) => this.logger.write(p, 3, " "));
this.logger.write(" used external typings:", 4, pad);
this.externalTypings.filter((p) => inUsed(p)).forEach((p) => this.logger.write(p, 4, " "));
this.logger.write(" unused external typings:", 4, pad);
this.externalTypings.filter((p) => !inUsed(p)).forEach((p) => this.logger.write(p, 4, " "));
this.logger.write(" external dependencies:", 4, pad);
externalDependencies.forEach((p) => this.logger.write(p, 4, " "));
}
const bundleEmitPath = join(this.build.getDistPath(), relativePath(this.main, this.outFile));
this.logger.ok(`created type declarations bundle @ '${bundleEmitPath}'`, 1, pad);
if (this.allFiles) {
deleteFileSync(this.mainFile);
}
return {
glob: "*.d.ts", immutable: false, paths: [ this.outFile ], srcpaths: this.sourceTypings, tag: this.infoProp
};
}
/**
* @private
* @param {WpwBuild} build
* @param {WebpackCompilation} compilation
* @param {string} [outputDir] abs path
* @returns {string}
*/
getDtsEntryFile(build, compilation, outputDir)
{
const entryOptions = /** @type {WebpackEntryOptions} */(compilation.entries.get(build.name)?.options),
entryName = /** @type {string} */(entryOptions.name),
extRgx = new RegExp(`\\.(?:${build.source.ext}|[cm]?[jt]s|d\\.ts)`);
outputDir = resolvePath(outputDir || compilation.compiler.outputPath);
let dtsEntryFile = join(outputDir, `${entryName}.${build.source.ext.replace(extRgx, ".d.ts")}`);
if (!existsSync(dtsEntryFile))
{
dtsEntryFile = findExPathSync([
`${build.type}.d.ts")}`, `${build.name}.d.ts`, "index.d.ts", "types.d.ts"
], outputDir, true);
if (!dtsEntryFile || !existsSync(dtsEntryFile))
{
const assetName = /** @type {string} */(compilation.getAsset(entryName)?.name);
if (assetName)
{
dtsEntryFile = resolvePath(outputDir, assetName.replace(extRgx, "d.ts"));
if (!existsSync(dtsEntryFile) && existsSync(join(outputDir, "bin")))
{
dtsEntryFile = findFilesSync(
"**/*{index,types,typings}.d.ts", { cwd: join(outputDir, "bin"), maxDepth: 3 }
)[0];
}
}
}
}
if (!dtsEntryFile || !existsSync(dtsEntryFile))
{
build.addMessage({
code: SpmhMessageUtils.Code.ERROR_TYPES_FAILED,
message: "types build: failed to find entry file for bundle"
});
return "index.d.ts";
}
return relativePathEx(outputDir, dtsEntryFile, { psx: true });
}
/**
* @private
* @param {WpwTypesBundleParams} params
* @param {string} [pad]
* @returns {Promise<WpwTypesDtsBundleOptions>}
*/
async getOptions(params, pad = " ")
{
const l = this.logger;
let main = params.bundleOptions.main;
const out = params.outFileAbs,
baseDir = params.baseDir,
verbose = this.build.log.level >= 4 || !!params.bundleOptions.verbose,
name = `${this.build.pkgJson.name}-${this.build.name}`.replace(/\//g, "-").replace(/@/g, "");
l.write("construct 'dts-bundle' options object", 1, pad);
l.value(" is file mode", params.isFileMode, 1, pad);
l.value(" base directory", params.baseDir, 1, pad);
l.value(" temporary namespace", name, 1, pad);
l.write(" validate entry point specified by option 'main'", 1, pad);
l.value(" rc-configured current value", main, 1, pad);
if (main)
{
if (main === "entry" || params.isFileMode)
{
main = this.getDtsEntryFile(this.build, this.plugin.compilation, params.dtsBuildDir);
}
else if (main !== "entry" && !params.isFileMode && isDirectory(main))
{
main = main.replace(/\/\*[*/]+$/, "");
}
if (!main || !existsSync(resolvePath(params.baseDirProj, main)))
{
l.write(" option 'main' could not be validated using the rc-configured value", 1, pad);
}
}
if (!main || (!main.endsWith("*.d.ts") && !existsSync(resolvePath(params.baseDirProj, main))))
{
l.write(" attempt to auto-populate option value", 1, pad);
if (params.isFileMode)
{
main = await findExPath([ "index.d.ts", "types.d.ts" ], params.dtsBuildDir, true);
}
if (!main && !(await isDirectoryEmpty(params.dtsBuildDir)))
{
l.write(` check v_build directory '${params.dtsBuildDir}' for emptiness`, 1, pad);
l.write(" not empty, set option 'main' to temp to v_build directory", 1, pad);
if (params.isFileMode)
{
params.isFileMode = false;
this.plugin.addMessage({
code: SpmhMessageUtils.Code.WARNING_OPTIONS_INVALID,
message: "auto-switching to 'directory' mode with option 'directoryRecurse' set",
detail: "switching to 'directory' mode was done because the build directory was not empty, it is " +
"however recommended to fix the issue in the wpwrc configuration file",
suggest: "specify a valid path to the input entry point file with rc option 'types.bundle.main', " +
"or try 'directory' mode with/without the 'directoryRecurse' options set"
});
}
main = params.dtsBuildDir;
}
}
if (!main || (!main.endsWith("*.d.ts") && !existsSync(resolvePath(params.baseDirProj, main))))
{
l.write(" option 'main' could not be auto-configured [step_3]", 1, pad);
l.value(" final attempted value (raw)", main, 1, pad);
l.value(" final attempted value (rtesolved)", resolvePath(params.baseDirProj, main), 1, pad);
l.write("exhausted all auto-configurable possibilities, unable to proceed", 1, pad);
return void this.plugin.addMessage({
code: SpmhMessageUtils.Code.ERROR_TYPES_BUNDLE_FAILED,
message: "unable to locate an entry file",
suggest: "specify a valid path to the input entry point file with rc option 'types.bundle.main', " +
"or try 'directory' mode with/without the 'directoryRecurse' options set"
});
}
if (!params.isFileMode)
{
l.write(" directory mode enabled, apply applicable glob pattern", 1, pad);
l.value(" is recursive mode", params.bundleOptions.mode, 1, pad);
main = forwardSlash(main).replace(/\/\*[*/]+$/, "") +
(params.bundleOptions.mode === "directoryRecurse" ? "/**/*.d.ts" : "/*.d.ts");
}
l.ok("constructed 'dts-bundle' options object", 1, pad);
return apply(
apply({ verbose, outputAsModuleFolder: false }, pickNot(params.bundleOptions, "enabled")),
{ main, name, baseDir, out }
);
};
/**
* @returns {WpwTypesBundleParams}
*/
getParams()
{
const build = this.build,
baseDirProj = build.getBasePath(),
distDirProj = build.getDistPath(),
baseDistDir = build.getDistPath({ build: "app "}),
distDirRel = relativePath(baseDistDir, distDirProj),
dtsDistDir = build.virtualEntry.dirDist,
dtsBuildDir = build.virtualEntry.dirBuild,
bundleOptions = /** @type {IWpwPluginConfigTypesBundle} */(build.options.types?.bundle || {}),
dtsBundleFilename = bundleOptions.out || join(distDirRel, build.name + ".d.ts"),
outFileAbs = join(dtsDistDir, distDirRel, dtsBundleFilename),
distFileAbs = join(distDirProj, distDirRel, dtsBundleFilename);
return {
build, baseDir: dtsBuildDir, distDir: dtsDistDir,
distDirProj, baseDirProj, baseDistDir,
distDirRel, dtsDistDir, dtsBuildDir, outFileAbs,
bundleOptions, dtsBundleFilename, distFileAbs,
dtsOutDirRelToBuild: relativePath(dtsBuildDir, dtsDistDir),
dtsDistDirRelToProj: relativePath(baseDirProj, distDirProj),
dtsBuildDirRelToProj: relativePath(baseDirProj, dtsBuildDir),
outFileRelToBuild: relativePath(dtsBuildDir, outFileAbs),
distFileRelToProj: relativePath(baseDirProj, distFileAbs),
distFileRel: relativePath(distDirProj, distFileAbs),
isFileMode: bundleOptions.mode === "file"
};
};
/**
* @param {WpwTypesDtsBundleOptions} opts
* @param {*} params
* @param {string} pad
* @returns {WpwTypesDtsBundleOptions}
*/
printOptions(opts, params, pad)
{
const l = this.logger;
l.object(params, "finalized input/output parameters", "params", 2, pad + " ");
l.write("finalized options:", 1, pad);
l.value(" export name", this.exportName, 1, pad);
l.value(" comments", this.comments ? "yes" : "no", 1, pad);
l.value(" transformed mainFile", this.mainFile, 1, pad);
l.value(" transformed outFile", this.outFile, 1, pad);
l.object(opts, null, "options", 1, pad + " ");
return opts;
}
/**
* @param {Buffer} data
* @param {WpwTypesDtsBundleOptions} cfg
* @param {WpwBuild} build
* @returns {Buffer}
*/
transform(data, cfg, build)
{
return Buffer.from(data.toString().replace(new RegExp(cfg.name, "g"), build.pkgJson.name));
}
/**
* @private
* @param {string} file
* @returns {boolean}
*/
inExternalTypings = (file) => { return this.externalTypings.includes(file); };
inSourceTypings(file)
{
return this.sourceTypings.includes(file) || this.sourceTypings.includes(join(file, "index.d.ts"));
}
stringEndsWith(str, suffix)
{
return str.indexOf(suffix, str.length - suffix.length);
}
stringStartsWith(str, prefix)
{
return str.slice(0, prefix.length) === prefix;
}
calcOutFilePath(out, baseDir)
{
return !this.stringStartsWith(out, "~" + sep) ? resolvePath(baseDir, out) : resolvePath(".", out.substr(2));
}
getModName(file)
{
return relativePath(this.baseDir, dirname(file) + sep + filename(file).replace(/\.d\.ts$/, ""));
}
getExpName(file)
{
return file === this.mainFile ? this.exportName : this.getExpNameRaw(file);
}
getExpNameRaw(file)
{
return this.prefix + this.exportName + this.separator + this.cleanupName(this.getModName(file));
}
getLibName(ref)
{
return this.getExpNameRaw(this.mainFile) + this.separator + this.prefix + this.separator + ref;
}
cleanupName(name)
{
return name.replace(/\.\./g, "--").replace(/[\\/]/g, this.separator);
}
mergeModulesLines(lines)
{
const i = (this.outputAsModuleFolder ? "" : this.indent);
return (lines.length === 0 ? "" : i + lines.join(this.newline + i)) + this.newline;
}
formatModule(file, lines)
{
let out = "";
if (this.outputAsModuleFolder) {
return this.mergeModulesLines(lines);
}
out += "declare module '" + this.getExpName(file) + "' {" + this.newline;
out += this.mergeModulesLines(lines);
out += "}" + this.newline;
return out;
}
async parseFile(file, pad)
{
const name = this.getModName(file);
this.logger.value("parse " + name, file, 4, pad);
const res = {
file, name, indent: this.indent, exp: this.getExpName(file),
refs: [], externalImports: [], relativeImports: [], exports: [],
lines: [], fileExists: true, importLineRef: [], relativeRef: []
};
if (!existsSync(file))
{
this.logger.warn(` unable to read file '${file}' [not found]`, 1);
res.fileExists = false;
return res;
}
if (isDirectory(file)) {
file = join(file, "index.d.ts");
}
const code = (await readFileAsync(file)).replace(bomOptExp, "").replace(/\s*$/, "");
// res.indent = detectIndent(code) || indent;
let multiComment = [],
queuedJSDoc,
inBlockComment = false;
const popBlock = () =>
{ if (multiComment.length > 0)
{ if (/^[ \t]*\/\*\*/.test(multiComment[0]))
{
queuedJSDoc = multiComment;
}
else if (this.comments) {
multiComment.forEach((line) => res.lines.push({ original: line }));
}
multiComment = [];
}
inBlockComment = false;
};
const popJSDoc = () =>
{ if (queuedJSDoc)
{ queuedJSDoc.forEach((line) =>
{ const match = line.match(/^([ \t]*)(\*.*)/);
if (match) {
res.lines.push({ original: match[1] + " " + match[2] });
}
else {
res.lines.push({ original: line });
}
});
queuedJSDoc = null;
}
};
code.split(/\r?\n/g).forEach((line) =>
{
let match;
if (/^[((=====)(=*)) \t]*\*+\//.test(line))
{
multiComment.push(line);
popBlock();
return;
}
if (/^[ \t]*\/\*/.test(line))
{
multiComment.push(line);
inBlockComment = true;
if (/\*+\/[ \t]*$/.test(line)) {
popBlock();
}
return;
}
if (inBlockComment)
{
multiComment.push(line);
return;
}
if (/^\s*$/.test(line))
{
res.lines.push({ original: "" });
return;
}
if (/^\/\/\//.test(line))
{
const ref = this.extractReference(line);
if (ref)
{
const refPath = resolvePath(dirname(file), ref);
if (this.inSourceTypings(refPath))
{
this.logger.write(` reference source typing ${ref} (${refPath})`, 2, pad);
}
else
{ const relPath = relativePath(this.baseDir, refPath).replace(/\\/g, "/");
this.logger.write(` reference external typing ${ref} (${refPath}) (relative: ${relPath})`, 2, pad);
if (!this.inExternalTypings(refPath)) {
this.externalTypings.push(refPath);
}
}
this.pushUnique(res.refs, refPath);
return;
}
}
if (/^\/\//.test(line))
{
if (this.comments) {
res.lines.push({ original: line });
}
return;
}
if (privateExp.test(line))
{
queuedJSDoc = null;
return;
}
popJSDoc();
if ((line.indexOf("from") >= 0 && (match = line.match(importEs6Exp))) ||
(line.indexOf("require") >= 0 && (match = line.match(importExp))))
{
const // _ = match[0],
lead = match[1], quote = match[2], moduleName = match[3], trail = match[4];
// assert(moduleName);
const impPath = resolvePath(dirname(file), moduleName);
if (fileExp.test(moduleName))
{
const modLine = { original: lead + quote + this.getExpName(impPath) + trail };
res.lines.push(modLine);
let full = resolvePath(dirname(file), impPath);
if (!existsSync(full) || existsSync(full + ".d.ts")) {
full += ".d.ts";
}
this.logger.write(" import relative " + moduleName + "(" + full + ")", 5, pad);
this.pushUnique(res.relativeImports, full);
res.importLineRef.push(modLine);
}
else
{ const modLine = { original: line };
this.logger.write(" import external " + moduleName, 3, pad);
this.pushUnique(res.externalImports, moduleName);
if (this.externals) {
res.importLineRef.push(modLine);
}
if (!this.outputAsModuleFolder) {
res.lines.push(modLine);
}
else {
this.pushUnique(this.globalExternalImports, line);
}
}
}
else if ((match = line.match(externalExp)) !== null)
{
const // _ = match[0],
// declareModule = match[1],
// lead = match[2],
moduleName = match[3];
// trail = match[4];
// assert(moduleName);
this.logger.write(" declare " + moduleName, 1, pad);
this.pushUnique(res.exports, moduleName);
const modLine = { original: line };
res.relativeRef.push(modLine);
res.lines.push(modLine);
}
else
{ if ((match = line.match(publicExp)) !== null)
{
const // _ = match[0],
sp = match[1], static1 = match[2],
// pub = match[3],
static2 = match[4], ident = match[5];
line = sp + static1 + static2 + ident;
}
if (this.inSourceTypings(file))
{
res.lines.push({ original: line.replace(/^(export )?declare /g, "$1") });
}
else {
res.lines.push({ original: line });
}
}
});
return res;
}
pushUnique(arr, value)
{
if (arr.indexOf(value) < 0) {
arr.push(value);
}
return arr;
}
pushUniqueArr(arr)
{
const values = [];
for (let _i = 1; _i < arguments.length; _i++) {
values[_i - 1] = arguments[_i];
}
values.forEach((vs) => vs.forEach((v) => this.pushUnique(arr, v)));
return arr;
}
/**
* @param {string} file
* @returns {string}
*/
formatReference(file) { return '/// <reference path="' + file.replace(/\\/g, "/") + '" />'; }
extractReference(tag)
{
const match = tag.match(referenceTagExp);
return match ? match[2] : null;
}
replaceImportExport(line, replacer)
{
const match = line.match(importExp);
if (match)
{ // assert(match[4]);
if (identifierExp.test(match[3])) {
return match[1] + match[2] + replacer(match[3]) + match[4];
}
}
return line;
}
replaceImportExportEs6(line, replacer)
{
if (line.indexOf("from") < 0) {
return line;
}
const match = line.match(importEs6Exp);
if (match)
{ // assert(match[4]);
if (identifierExp.test(match[3])) {
return match[1] + match[2] + replacer(match[3]) + match[4];
}
}
return line;
}
replaceExternal(line, replacer)
{
const match = line.match(externalExp);
if (match)
{ const // _ = match[0],
declareModule = match[1], beforeIndent = match[2],
moduleName = match[3], afterIdent = match[4];
// assert(afterIdent);
if (identifierExp.test(moduleName)) {
return declareModule + beforeIndent + replacer(moduleName) + afterIdent;
}
}
return line;
}
getIndenter(actual, use)
{
if (actual === use || !actual)
{
return (line) => line.modified || line.original;
}
return ((line) => (
line.modified || line.original).replace(new RegExp("^" + actual + "+", "g"),
(match) => match.split(actual).join(use)
));
}
optValue(passed, def)
{
return typeof passed === "undefined" ? def : passed;
}
}
module.exports = WpwTypeDeclarationsBundler;