qcobjects-cli
Version:
qcobjects cli command line tool
202 lines (178 loc) • 6.42 kB
JavaScript
const esbuild = require("esbuild");
const alias = require("esbuild-plugin-alias");
const path = require("node:path");
const { readFileSync, writeFileSync } = require("node:fs");
const fs = require("node:fs/promises");
const glob = require("glob");
// Organized external packages into categories
const externalPackages = {
node: [
"fs", "path", "os", "util", "events", "stream", "http", "https",
"crypto", "zlib", "buffer", "url", "querystring", "child_process",
"cluster", "dgram", "dns", "net", "readline", "repl", "tls", "tty",
"vm", "worker_threads"
],
qcobjects: ["qcobjects", "qcobjects-sdk"]
};
// Flatten external packages for quick lookup
const allExternalPackages = [
...externalPackages.node.map(pkg => `node:${pkg}`),
...externalPackages.qcobjects
];
// Function to detect and add the extension
const nameToExtension = (name, ext, settings) => {
const isPackage = name => !name.startsWith(".") && !name.startsWith("/") && !name.includes("/");
const hasExtension = /\.[^/\\]+$/.test(name);
const isExternalPackage = allExternalPackages.includes(name);
if (!hasExtension && !isPackage(name) && !isExternalPackage) {
name += ext;
}
return name;
};
// Function to add extensions to import/export/require statements
const addExtensions = (filePath, toExt, settings) => {
const content = readFileSync(filePath, 'utf8');
const patterns = [
[/(from\s+['"])(.*?)(['"])/g, '$1$2$3'],
[/(import\s+['"])(.*?)(['"])/g, '$1$2$3'],
[/(export\s+['"])(.*?)(['"])/g, '$1$2$3'],
[/(require\s*\(\s*['"])(.*?)(['"]\s*\))/g, '$1$2$3']
];
const updatedContent = patterns.reduce((content, [pattern, replacement]) => {
return content.replace(pattern, (match, p1, p2, p3) => {
return `${p1}${nameToExtension(p2, toExt, settings)}${p3}`;
});
}, content);
writeFileSync(filePath, updatedContent, 'utf8');
};
// Common build settings
const baseSettings = {
entryPoints: glob.sync('src/**/*.ts').map(file => path.resolve(file)),
bundle: false,
target: ["node22"],
tsconfig: "tsconfig.json",
globalName: "global",
minify: false,
keepNames: true,
sourcemap: true,
splitting: false,
chunkNames: "chunks/[name]-[hash]",
plugins: [
{
name: 'transform-qcobjects-imports',
setup(build) {
build.onResolve({ filter: /^(qcobjects|qcobjects-sdk)$/ }, args => ({
path: path.resolve(args.path),
namespace: args.kind === 'dynamic-import' ? 'qcobjects-transform' : undefined,
external: args.kind !== 'dynamic-import'
}));
build.onResolve({ filter: /.*/, namespace: 'file' }, args => ({
external: args.kind === 'dynamic-import',
path: path.resolve(args.path)
}));
build.onLoad({ filter: /.*/, namespace: 'qcobjects-transform' }, args => ({
contents: `module.exports = __toESM(require("${path.resolve(args.path)}"), true);`,
loader: 'js'
}));
}
},
alias({
"types": path.resolve(__dirname, "src/types/global/index.d.ts")
})
]
};
// Build settings for different formats
const buildConfigs = {
esm: {
...baseSettings,
outdir: "public/esm",
format: "esm",
platform: "browser",
outExtension: { ".js": ".mjs" },
plugins: [
...baseSettings.plugins,
{
name: 'transform-requires',
setup(build) {
build.onEnd(() => {
glob.sync('public/esm/**/*.mjs').forEach(file => {
let content = readFileSync(file, 'utf8');
content = content
.replace(/const\s+{([^}]+)}\s*=\s*require\(['"]([^'"]+)['"]\)/g, 'import { $1 } from "$2"')
.replace(/const\s+([^=]+)\s*=\s*require\(['"]([^'"]+)['"]\)/g, 'import $1 from "$2"');
// Convert await import(var) → JSON.parse(fs.readFileSync(var, "utf8"))
// for variables referencing paths ending in .json/.jsonp/.md/.mdc/.text/.txt
const extensionsToConvert = ['json', 'jsonp', 'md', 'mdc', 'text', 'txt'];
const extPattern = extensionsToConvert.join('|');
const varDeclRegex = new RegExp(
`(?:const|let|var)\\s+(\\w+)\\s*=[^;]*?\\.(?:${extPattern})["'\`][^;]*;`,
'g'
);
const jsonVars = new Set();
let match;
while ((match = varDeclRegex.exec(content)) !== null) {
jsonVars.add(match[1]);
}
if (jsonVars.size > 0) {
const importRegex = new RegExp(
`await import\\((${[...jsonVars].join('|')})\\)`,
'g'
);
content = content.replace(importRegex, 'JSON.parse(fs.readFileSync($1, "utf8"))');
}
writeFileSync(file, content, 'utf8');
});
});
}
}
]
}
};
// Utility functions
const logError = (e) => { console.error(e); process.exit(1); };
const logDebug = (e) => { console.debug(e); };
const copyDir = async (source, dest, exclude = []) => {
source = path.resolve(source);
dest = path.resolve(dest);
const dname = path.basename(source);
if (exclude.includes(dname)) return;
try {
const stat = await fs.stat(source);
if (!stat.isDirectory()) return;
await fs.mkdir(dest, { recursive: true });
const paths = await fs.readdir(source, { withFileTypes: true });
await Promise.all(paths.map(async item => {
const sourcePath = path.resolve(source, item.name);
const destPath = path.resolve(dest, item.name);
if (item.isFile() && !exclude.includes(item.name)) {
logDebug(`[publish:static] Copying ${sourcePath} to ${destPath}`);
await fs.copyFile(sourcePath, destPath);
} else if (item.isDirectory()) {
await copyDir(sourcePath, destPath, exclude);
}
}));
} catch (error) {
logError(error);
}
};
// Main execution
(async () => {
try {
// Copy templates
const templateDirs = [
"./build/templates",
"./public/cjs/templates",
"./public/esm/templates",
"./public/browser/templates"
];
await Promise.all(templateDirs.map(dir =>
copyDir("./src/templates", dir, [])
));
// Run builds in parallel
await Promise.all([
esbuild.build(buildConfigs.esm)
]);
} catch (error) {
logError(error);
}
})();