## 前提
以下がインストールされている。
- [[Bun]]
- [[Jest]]
以下を利用する。
- [[Jest]]
- [[husky]]
- [[Chokidar]] ([[ホットリロード]]用)
## 手順
### インストールコマンド
```console
rm -rf package-lock.json node_moules
bun add -D @types/bun husky@latest chokidar esbuild@latest esbuild-jest@latest
```
### tsconfig.json
`types`と`include`に追加。
```json
{
"compilerOptions": {
"types": ["@types/bun"]
},
"include": ["**/*.ts", "**/*.mts"]
}
```
> [!note]
> `include`の`"**/*.mts"`を追加しないと、[[Top-Level await]]などが[[IDE]]上でエラーになってしまう。
### package.json
```json
"scripts": {
"dev": "bun esbuild.config.mjs",
"build": "tsc -noEmit -skipLibCheck && bun esbuild.config.mjs production",
"prepare": "husky install",
"pre:push": "tsc -noEmit -skipLibCheck && bun run test",
"test": "jest",
"ci": "bun install && bun run build && bun run test",
"release": "bun ci && bun version-bump.mts ${VERSION} && git add package.json manifest-beta.json manifest.json versions.json bun.lockb && git commit -m ${VERSION} && git tag ${VERSION} && git push --tags && git push"
}
```
### esbuild.config.mts
```ts
import fs from "fs";
import path from "path";
import builtins from "builtin-modules";
import chokidar from "chokidar";
import esbuild from "esbuild";
import process from "process";
// TODO: 自分のVaultパスを設定する
const VAULT_DIR = "/mnt/c/Users/syoum/work/minerva";
const PLUGIN_DIR_NAME = "mobile-first-daily-interface";
const FILES = ["main.js", "manifest.json", "styles.css"];
// ---
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[2] === "production";
const context = await esbuild.context({
banner: {
js: banner,
},
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,
],
format: "cjs",
target: "es2018",
logLevel: "info",
sourcemap: prod ? false : "inline",
treeShaking: true,
outfile: "main.js",
});
if (prod) {
await context.rebuild();
process.exit(0);
} else {
await context.watch();
const pluginDir = path.join(
VAULT_DIR,
".obsidian/plugins",
PLUGIN_DIR_NAME,
);
console.log(`📁 Creating ${pluginDir} (if not existed)`);
fs.mkdirSync(pluginDir, { recursive: true });
const hotreloadPath = path.join(pluginDir, ".hotreload", "");
console.log(`🌶️ Creating a ${hotreloadPath}`);
fs.writeFileSync(hotreloadPath, "");
const watcher = chokidar.watch(FILES, { persistent: true });
watcher
.on("add", (p) => {
console.log(`♨️ ${p} is added`);
fs.copyFileSync(p, path.join(pluginDir, p));
})
.on("change", (p) => {
console.log(`♨️ ${p} is changed`);
fs.copyFileSync(p, path.join(pluginDir, p));
});
}
```
### version-bump.mts
```ts
import { readFileSync, writeFileSync } from "fs";
import { exit } from "process";
function updateVersion(version: string) {
const packageJson = JSON.parse(readFileSync("package.json", "utf8"));
packageJson.version = version;
writeFileSync("package.json", JSON.stringify(packageJson, null, " "));
const manifestBeta = JSON.parse(readFileSync("manifest-beta.json", "utf8"));
manifestBeta.version = version;
writeFileSync("manifest-beta.json", JSON.stringify(manifestBeta, null, " "));
if (version.includes("beta")) {
return;
}
const manifest = JSON.parse(readFileSync("manifest.json", "utf8"));
const { minAppVersion } = manifest;
manifest.version = version;
writeFileSync("manifest.json", JSON.stringify(manifest, null, " "));
// update versions.json with target version and minAppVersion from manifest.json
const versions = JSON.parse(readFileSync("versions.json", "utf8"));
versions[version] = minAppVersion;
writeFileSync("versions.json", JSON.stringify(versions, null, " "));
}
const [_1, _2, version] = Bun.argv;
if (!version) {
console.error("Required: ${version} (ex: bun version 1.2.3)");
exit(1);
}
if (!Boolean(version.match(/\d+\.\d+\.\d+/))) {
console.error("The version is not valid (ex: bun version 1.2.3)");
exit(1);
}
updateVersion(version);
```
### GitHub Actions
`.github/workflows/test.yaml`
```yaml
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v1
- run: bun ci
```
`.github/workflows/release.yaml`
```yaml
jobs:
release:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v1
- run: bun ci
- name: Create Release
id: create_release
uses: softprops/action-gh-release@v2
with:
draft: true
files: |
main.js
styles.css
manifest.json
manifest-beta.json
```