Skip to content

Commit

Permalink
Changed to cross-platform build-system
Browse files Browse the repository at this point in the history
  • Loading branch information
Jacob Schneider authored and Jacob Schneider committed Sep 13, 2024
1 parent 5bf6106 commit 147365a
Show file tree
Hide file tree
Showing 7 changed files with 315 additions and 47 deletions.
57 changes: 57 additions & 0 deletions bin/build.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import * as fs from 'node:fs/promises';
import State from '@j-cake/jcake-utils/state';
import { iterSync } from '@j-cake/jcake-utils/iter';
import * as Format from '@j-cake/jcake-utils/args';
import chalk from 'chalk';

import log from './log.js';
import Path from './path.js';
import * as comp from './components.js';

export const config = new State({
logLevel: 'info',
force: false,

root: new Path(process.cwd()),
out: new Path(process.cwd()).concat('build'),

components: []
});

export default async function main(argv) {
const logLevel = Format.oneOf(Object.keys(log), false);

for (const { current: i, skip: next } of iterSync.peekable(argv))
if (i == '--log-level')
config.setState({ logLevel: logLevel(next()) });

else if (i == '-f' || i == '--force')
config.setState({ force: true });

else if (i == '-o' || i == '--out')
config.setState({ out: new Path(next()) });

else
config.setState(prev => ({ components: [...prev.components, i] }));

log.debug(config.get());

await fs.mkdir(config.get().out.path, { recursive: true });

for (const component of config.get().components)
if (component in components)
await components[component]()
.then(status => log.info(`${chalk.grey(component)}: Done`));
}

export const components = {
"build:plugin": () => comp.build_plugin(),
"build:package.json": () => comp.build_package_json(),
"build:manifest.json": () => comp.build_manifest_json(),
"build:style.css": () => comp.build_style_css(),

"phony:install": () => comp.phony_install(),
"phony:all": () => Promise.all(Object.entries(components)
.filter(([comp, _]) => comp.startsWith("build:"))
.map(([_, fn]) => fn())),
}
89 changes: 89 additions & 0 deletions bin/components.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import * as fs from 'node:fs/promises';
import * as fss from 'node:fs';
import * as cp from 'node:child_process';
import esbuild from 'esbuild';

import { config } from './build.js';
import log from './log.js';
import { has_changed } from './util.js';

const is_source = path => path.startsWith(config.get().root.join("src"));

export async function build_plugin() {
if (!await has_changed({
glob: path => is_source(path),
dependents: [config.get().out.join("main.js")]
}))
return log.verbose("Skipping Rebuild");

await esbuild.build({
entryPoints: ["src/main.ts"],
bundle: true,
sourcemap: true,
platform: 'node',
format: 'cjs',
external: ['electron', 'obsidian'],
outdir: config.get().out.path
});
}

export async function build_package_json() {
if (!await has_changed({
glob: path => is_source(path),
dependents: [config.get().out.join("package.json")]
}))
return log.verbose("Skipping Rebuild");

const jq = cp.spawn('jq', ['-r', '. * .deploy * {deploy:null} | with_entries(select(.value |. != null))']);

fss.createReadStream(config.get().root.join("package.json").path)
.pipe(jq.stdin);

jq.stdout.pipe(fss.createWriteStream(config.get().out.join("package.json").path), 'utf8');

await new Promise((ok, err) => jq.on("exit", code => code == 0 ? ok() : err(code)));
}

export async function build_manifest_json() {
if (!await has_changed({
glob: path => is_source(path),
dependents: [config.get().out.join("manifest.json")]
}))
return log.verbose("Skipping Rebuild");

const jq = cp.spawn('jq', ['-r', '.']);

fss.createReadStream(config.get().root.join("manifest.json").path)
.pipe(jq.stdin);

jq.stdout.pipe(fss.createWriteStream(config.get().out.join("manifest.json").path), 'utf8');

await new Promise((ok, err) => jq.on("exit", code => code == 0 ? ok() : err(code)));
}

export async function build_style_css() {
if (!await has_changed({
glob: path => is_source(path),
dependents: [config.get().out.join("styles.css")]
}))
return log.verbose("Skipping Rebuild");

await esbuild.build({
entryPoints: ["styles.css"],
bundle: true,
sourcemap: true,
outdir: config.get().out.path
});
}

export async function phony_install() {
const pkg = await fs.readFile(config.get().root.join("package.json"))
.then(pkg => JSON.parse(pkg).name);

const install = config.get().vault.join(".obsidian/plugins").join(pkg).path;

await fs.mkdir(install, { recursive: true });

for await (const file of config.get().out.readdir())
await fs.copyFile(file.path, install);
}
39 changes: 39 additions & 0 deletions bin/log.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import util from 'node:util';
import chalk from 'chalk';

import { config } from './build.js';

export const stripAnsi = str => str.replace(/[\u001b\u009b][[()#;?]*(?:\d{1,4}(?:;\d{0,4})*)?[\dA-ORZcf-nqry=><]/g, '');
export const centre = (text, width) => {
const colourless = stripAnsi(text);
const pad = Math.floor((width - colourless.length) / 2);
return `${' '.repeat(pad)}${text}${' '.repeat(pad)}`.padStart(width, ' ');
}
export function stdout(tag, ...msg) {
const log = msg
.map(i => ['string', 'number', 'bigint', 'boolean'].includes(typeof i) ? i : util.inspect(i, false, null, true))
.join(' ')
.split('\n')
.map((i, a) => `${a ? centre('\u2502', stripAnsi(tag).length) : tag} ${i}\n`);

for (const i of log)
process.stdout.write(i);
}

export function stderr(tag, ...msg) {
const log = msg
.map(i => ['string', 'number', 'bigint', 'boolean'].includes(typeof i) ? i : util.inspect(i, false, null, true))
.join(' ')
.split('\n')
.map((i, a) => `${a ? centre('\u2502', stripAnsi(tag).length) : tag} ${i}\n`);

for (const i of log)
process.stderr.write(i);
}

export default {
err: (...arg) => void (['err', 'info', 'verbose', 'debug'].includes(config.get().logLevel) && stderr(chalk.grey(`[${chalk.red('Error')}]`), ...arg)),
info: (...arg) => void (['info', 'verbose', 'debug'].includes(config.get().logLevel) && stdout(chalk.grey(`[${chalk.blue('Info')}]`), ...arg)),
verbose: (...arg) => void (['verbose', 'debug'].includes(config.get().logLevel) && stdout(chalk.grey(`[${chalk.yellow('Verbose')}]`), ...arg)),
debug: (...arg) => void (['debug'].includes(config.get().logLevel) && stdout(chalk.grey(`[${chalk.cyan('Debug')}]`), ...arg))
}
97 changes: 97 additions & 0 deletions bin/path.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import * as fs from 'node:fs/promises';
import * as fss from 'node:fs';
import * as path from 'node:path';

import log from './log.js';

export default class Path {
constructor(str) {
if (str instanceof fss.Dirent)
this.path = path.join(str.parentPath, str.name);
else if (str instanceof Path)
this.path = str.path;
else
this.path = path.isAbsolute(str) ? str : path.join(process.cwd(), str);
}

concat(...paths) {
this.path = path.join(this.path, ...paths.map(i => i instanceof Path ? i.path : i));
return this;
}

join(...paths) {
return new Path(path.join(this.path, ...paths.map(i => i instanceof Path ? i.path : i)));
}

ext() {
return this.path.split(path.sep).pop()?.split('.').pop()?.toLowerCase() ?? '';
}

async *readdir(recursive = true) {
const files = await fs.readdir(this.path, { recursive, withFileTypes: true })

for (const dirent of files)
yield new Path(dirent);

// for await (const dir of await fs.readdir(config.get().root.path, { recursive: true, withFileTypes: true }))
// if (dir.isFile())
// log.info(new Path(dir));
}

async mtime() {
return await fs.stat(this.path).then(stat => stat.mtime);
}

async isFile() {
return await fs.stat(this.path).then(stat => stat.isFile());
}

async isDir() {
return await fs.stat(this.path).then(stat => stat.isDirectory());
}

async isBlockdev() {
return await fs.stat(this.path).then(stat => stat.isBlockDevice());
}

async isChardev() {
return await fs.stat(this.path).then(stat => stat.isCharacterDevice());
}

async isPipe() {
return await fs.stat(this.path).then(stat => stat.isFIFO() || stat.isSocket());
}

async isFifo() {
return await fs.stat(this.path).then(stat => stat.isFIFO());
}

async isSocket() {
return await fs.stat(this.path).then(stat => stat.isSocket());
}

async isSymlink() {
return await fs.stat(this.path).then(stat => stat.isSymbolicLink());
}

replaceBase(base, newBase) {
if (this.path.startsWith(base.path))
return newBase.join(this.path.slice(base.path.length));

else
throw {
err: 'Could not substitute path',
reason: `Path does not start with '${base.path}'`,
path: this
};
}

parent() {
return new Path(this.path.split(path.sep).slice(0, -1).join(path.sep));
}

startsWith(base) {
const chunks = this.path.split(path.sep);
return base.path.split(path.sep).every(i => chunks.shift() == i);
}
}
30 changes: 30 additions & 0 deletions bin/util.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { config } from './build.js';

const file_tree = async function() {
const files = [];

if (files.length == 0)
for await (const path of config.get().root.readdir())
files.push(path);

return files;
}

export async function* get_files(glob) {
for (const path of await file_tree())
if (await glob(path))
yield path;
}

export async function has_changed(glob) {
if (config.get().force)
return true;

const mtimes = await Promise.all(glob.dependents.map(i => i.mtime().catch(_ => new Date(0))));

for await (const file of get_files(glob.glob))
if (await Promise.race(mtimes.map(async i => await file.mtime() > i)))
return true;

return false;
}
38 changes: 0 additions & 38 deletions makefile.json5

This file was deleted.

12 changes: 3 additions & 9 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,21 +14,15 @@
"chrono-node": "https://github.com/wanasit/chrono.git"
},
"scripts": {
"build:plugin": "esbuild src/main.ts --outdir=build --bundle --sourcemap --platform=node --format=cjs --external:obsidian --external:electron",
"build:package.json": "cat package.json | jq -r '. * .deploy * {deploy:null} | with_entries(select(.value |. != null))' > build/package.json",
"build:manifest.json": "cat manifest.json | jq -r '.' > build/manifest.json",
"build:styles.css": "esbuild styles.css --outdir=build --bundle",
"build:hotreload": "echo hotreload > build/.hotreload",
"phony:rebuild": "cat package.json | jq -r '.scripts | keys_unsorted[] | select(. | startswith(\"build:\"))' | xargs -d \\\\n -I {} $npm_execpath run {}",
"phony:install": "mkdir -p \"$vault_dir/.obsidian/plugins/$(cat package.json | jq -r '.name')\"; cp -ra build/. \"$vault_dir/.obsidian/plugins/$(cat package.json | jq -r '.name')\"",
"phony:clean": "rm -rf build target node_modules *lock* *yarn* *pnpm*"
"build": "node -e \"await import('./bin/build.js').then(script => script.default(process.argv.slice(1)))\" --"
},
"devDependencies": {
"@j-cake/mkjson": "latest",
"@types/luxon": "^3.4.2",
"@types/luxon": "latest",
"@types/node": "latest",
"@types/react": "latest",
"@types/react-dom": "latest",
"chalk": "latest",
"electron": "latest",
"esbuild": "latest",
"obsidian": "latest",
Expand Down

0 comments on commit 147365a

Please sign in to comment.