insta-toc
Version:
Simultaneously generate, update, and maintain a table of contents for your notes in real time.
212 lines (184 loc) • 7.76 kB
JavaScript
import builtins from "builtin-modules";
import esbuild from "esbuild";
import esbuildSvelte from "esbuild-svelte";
import { copyFileSync, existsSync, readFileSync, statSync, writeFileSync } from "fs";
import path from "path";
import process from "process";
import config from "./svelte.config.js";
const banner = `/*
THIS IS A GENERATED/BUNDLED FILE BY ESBUILD
if you want to view the source, please visit the github repository of this plugin
*/
`;
const prod = process.argv.includes("production");
const shouldLog = process.argv.includes("logger");
let logs = [];
const __dirname = process.cwd();
// Load the package.json file
const packageJsonPath = path.join(__dirname, "package.json");
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
const manifestJsonPath = path.join(__dirname, "manifest.json");
const manifestJson = JSON.parse(readFileSync(manifestJsonPath, "utf-8"));
const dataJsonPath = path.join(__dirname, "data.json");
if (!existsSync(dataJsonPath)) {
writeFileSync(dataJsonPath, "{}", "utf-8");
}
const dataJson = JSON.parse(readFileSync(dataJsonPath, "utf-8"));
// Retrieve the name of the package
const packageName = packageJson.name;
const packageVersion = packageJson.version;
const packageMain = prod ? "dist/build/main.js" : "dist/dev/main.js";
packageJson.main = packageMain;
writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 4), "utf-8");
logs.push(`Package Name: ${packageName}`);
logs.push(`Package Version: ${packageVersion}`);
logs.push(`Set main.js directory to ${packageJson.main}`);
const { pluginRoot, vaultRoot } = {
pluginRoot: __dirname,
vaultRoot: decodeURI(__dirname.replace(/\/\.obsidian.*/, ""))
};
logs.push(`pluginRoot: ${pluginRoot}\nvaultRoot: ${vaultRoot}`);
const vaultName = vaultRoot.split("/").pop();
logs.push(`vaultName: ${vaultName}`);
const runtimeStylesheetPath = path.join(__dirname, "styles.css");
const bareStylesheetImportPattern = /@import\s+["'](?![./])[A-Za-z@][^"']*["'];?/;
const unsupportedBuildDirectivePattern = /@(plugin|config|theme|utility|variant|source|reference)\b/;
function ensureRuntimeStylesheetIsBrowserReady(filePath) {
const stylesheet = readFileSync(filePath, "utf-8");
const forbiddenDirectives = [ bareStylesheetImportPattern, unsupportedBuildDirectivePattern ];
if (!forbiddenDirectives.some((pattern) => pattern.test(stylesheet))) {
return;
}
throw new Error(
`The shipped runtime stylesheet (${
path.basename(filePath)
}) must contain browser-ready CSS only. Remove package imports or build-time directives before releasing the plugin.`
);
}
ensureRuntimeStylesheetIsBrowserReady(runtimeStylesheetPath);
const sourcePath = path.resolve(`${pluginRoot}/${packageMain}`);
const targetPath = path.resolve(`${pluginRoot}/main.js`);
logs.push(`Source Path: ${sourcePath}`);
logs.push(`Target Path: ${targetPath}`);
const context = await esbuild
.context({
banner: { js: banner },
logLevel: "warning",
plugins: [
esbuildSvelte({
preprocess: config.preprocess,
compilerOptions: { ...config.compilerOptions, sourcemap: prod ? false : "inline" },
moduleCompilerOptions: { dev: !prod, experimental: { async: true }, rootDir: `${__dirname}/src/svelte` }
// esbuildTsTransformOptions: {}
}),
{
name: "copy-main-js",
setup(build) {
/** @type {Map<string, number>} filePath -> mtimeMs */
const inputMtimes = new Map();
let isFirstBuild = true;
build.onStart(() => {
if (isFirstBuild) return;
// Detect which file triggered the rebuild
let changedFile = null;
for (const [ filePath, lastMtime ] of inputMtimes) {
try {
const { mtimeMs } = statSync(filePath);
if (mtimeMs > lastMtime) {
changedFile = filePath;
break;
}
}
catch {
// File may have been deleted — that itself is a change
changedFile = filePath;
break;
}
}
const changeInfo = changedFile ? ` (change: "${path.relative(__dirname, changedFile)}")` : "";
console.log(`[watch] build started${changeInfo}`);
});
build.onEnd((result) => {
if (result.errors.length === 0) {
copyFileSync(sourcePath, targetPath);
if (shouldLog) {
console.log(`Copied: ${path.basename(sourcePath)} -> ${packageMain}`);
console.log("[watch] build finished");
}
// Snapshot input file mtimes for next rebuild's change detection
if (result.metafile) {
inputMtimes.clear();
for (const filePath of Object.keys(result.metafile.inputs)) {
const absPath = path.resolve(__dirname, filePath);
try {
inputMtimes.set(absPath, statSync(absPath).mtimeMs);
}
catch {
// skip files that can't be stat'd
}
}
}
}
else {
console.log("[watch] build skipped");
}
isFirstBuild = false;
});
}
}
],
entryPoints: [ "src/main.ts" ],
bundle: true,
external: [
"obsidian",
"electron",
"@codemirror/autocomplete",
"@codemirror/collab",
"@codemirror/commands",
"@codemirror/language",
"@codemirror/lint",
"@codemirror/search",
"@codemirror/state",
"@codemirror/view",
"@lezer/common",
"@lezer/highlight",
"@lezer/lr",
...builtins
],
platform: "node",
format: "cjs",
target: "es2022",
absWorkingDir: __dirname,
sourcemap: prod ? false : "inline",
treeShaking: true,
metafile: !prod,
outfile: packageMain,
minify: prod,
mainFields: [ "svelte", "browser", "module", "main" ],
conditions: [ "svelte", "browser" ]
})
.catch((error) => {
console.error(error);
process.exit(1);
});
function copyMainJs() {
try {
copyFileSync(sourcePath, targetPath);
logs.push(`Copied file: ${sourcePath} -> ${targetPath}`);
logs = logs.join("\n");
if (shouldLog) console.log(logs);
}
catch (error) {
console.error(`Error copying main.js: ${error}\nLogs:\n${logs.join("\n")}`);
process.exit(1);
}
}
if (prod) {
await context.rebuild();
copyMainJs();
await context.dispose();
}
else {
if (shouldLog) console.log(logs.join("\n"));
await context.watch();
}