fix some issues; migrate to bun'
This commit is contained in:
parent
cf3d5c6d6f
commit
54293ac3a1
56 changed files with 1360 additions and 764 deletions
38
automation/build/buildBanner.ts
Normal file
38
automation/build/buildBanner.ts
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
import manifest from '../../manifest.json' assert { type: 'json' };
|
||||
|
||||
export function getBuildBanner(buildType: string, getVersion: (version: string) => string) {
|
||||
return `/*
|
||||
-------------------------------------------
|
||||
${manifest.name} - ${buildType}
|
||||
-------------------------------------------
|
||||
By: ${manifest.author} (${manifest.authorUrl})
|
||||
Time: ${new Date().toUTCString()}
|
||||
Version: ${getVersion(manifest.version)}
|
||||
-------------------------------------------
|
||||
THIS IS A GENERATED/BUNDLED FILE BY ESBUILD
|
||||
if you want to view the source, please visit the github repository of this plugin
|
||||
-------------------------------------------
|
||||
MIT License
|
||||
|
||||
Copyright (c) ${new Date().getFullYear()} ${manifest.author}
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
`;
|
||||
}
|
||||
57
automation/build/esbuild.config.ts
Normal file
57
automation/build/esbuild.config.ts
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
import builtins from 'builtin-modules';
|
||||
import esbuild from 'esbuild';
|
||||
import esbuildSvelte from 'esbuild-svelte';
|
||||
import sveltePreprocess from 'svelte-preprocess';
|
||||
import { getBuildBanner } from 'build/buildBanner';
|
||||
|
||||
const banner = getBuildBanner('Release Build', version => version);
|
||||
|
||||
const build = await esbuild.build({
|
||||
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: false,
|
||||
treeShaking: true,
|
||||
outfile: 'main.js',
|
||||
minify: true,
|
||||
metafile: true,
|
||||
define: {
|
||||
MB_GLOBAL_CONFIG_DEV_BUILD: 'false',
|
||||
},
|
||||
plugins: [
|
||||
esbuildSvelte({
|
||||
compilerOptions: { css: 'injected', dev: false, sveltePath: 'svelte' },
|
||||
preprocess: sveltePreprocess(),
|
||||
filterWarnings: warning => {
|
||||
// we don't want warnings from node modules that we can do nothing about
|
||||
return !warning.filename?.includes('node_modules');
|
||||
},
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
const file = Bun.file('meta.txt');
|
||||
await Bun.write(file, JSON.stringify(build.metafile, null, '\t'));
|
||||
|
||||
process.exit(0);
|
||||
65
automation/build/esbuild.dev.config.ts
Normal file
65
automation/build/esbuild.dev.config.ts
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
import esbuild from 'esbuild';
|
||||
import copy from 'esbuild-plugin-copy-watch';
|
||||
import esbuildSvelte from 'esbuild-svelte';
|
||||
import sveltePreprocess from 'svelte-preprocess';
|
||||
import manifest from '../../manifest.json' assert { type: 'json' };
|
||||
import { getBuildBanner } from 'build/buildBanner';
|
||||
|
||||
const banner = getBuildBanner('Dev Build', _ => 'Dev Build');
|
||||
|
||||
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',
|
||||
],
|
||||
format: 'cjs',
|
||||
target: 'es2018',
|
||||
logLevel: 'info',
|
||||
sourcemap: 'inline',
|
||||
treeShaking: true,
|
||||
outdir: `exampleVault/.obsidian/plugins/${manifest.id}/`,
|
||||
outbase: 'src',
|
||||
define: {
|
||||
MB_GLOBAL_CONFIG_DEV_BUILD: 'true',
|
||||
},
|
||||
plugins: [
|
||||
copy({
|
||||
paths: [
|
||||
{
|
||||
from: './styles.css',
|
||||
to: '',
|
||||
},
|
||||
{
|
||||
from: './manifest.json',
|
||||
to: '',
|
||||
},
|
||||
],
|
||||
}),
|
||||
esbuildSvelte({
|
||||
compilerOptions: { css: 'injected', dev: true, sveltePath: 'svelte' },
|
||||
preprocess: sveltePreprocess(),
|
||||
filterWarnings: warning => {
|
||||
// we don't want warnings from node modules that we can do nothing about
|
||||
return !warning.filename?.includes('node_modules');
|
||||
},
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
await context.watch();
|
||||
5
automation/config.json
Normal file
5
automation/config.json
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"devBranch": "master",
|
||||
"releaseBranch": "release",
|
||||
"github": "https://github.com/mProjectsCode/obsidian-media-db-plugin"
|
||||
}
|
||||
171
automation/release.ts
Normal file
171
automation/release.ts
Normal file
|
|
@ -0,0 +1,171 @@
|
|||
import { UserError } from 'utils/utils';
|
||||
import { CanaryVersion, Version, getIncrementOptions, parseVersion, stringifyVersion } from 'utils/versionUtils';
|
||||
import config from './config.json';
|
||||
import { $choice as $choice, $confirm, $seq, CMD_FMT, Verboseness } from 'utils/shellUtils';
|
||||
|
||||
async function runPreconditions(): Promise<void> {
|
||||
// run preconditions
|
||||
await $seq(
|
||||
[`bun run format`, `bun run lint:fix`, `bun run test`],
|
||||
(cmd: string) => {
|
||||
throw new UserError(`precondition "${cmd}" failed`);
|
||||
},
|
||||
() => {},
|
||||
undefined,
|
||||
Verboseness.VERBOSE,
|
||||
);
|
||||
|
||||
// add changed files
|
||||
await $seq(
|
||||
[`git add .`],
|
||||
() => {
|
||||
throw new UserError('failed to add preconditions changes to git');
|
||||
},
|
||||
() => {},
|
||||
undefined,
|
||||
Verboseness.NORMAL,
|
||||
);
|
||||
|
||||
// check if there were any changes
|
||||
let changesToCommit = false;
|
||||
await $seq(
|
||||
[`git diff --quiet`, `git diff --cached --quiet`],
|
||||
() => {
|
||||
changesToCommit = true;
|
||||
},
|
||||
() => {},
|
||||
undefined,
|
||||
Verboseness.QUITET,
|
||||
);
|
||||
|
||||
// if there were any changes, commit them
|
||||
if (changesToCommit) {
|
||||
await $seq(
|
||||
[`git commit -m "[auto] run release preconditions"`],
|
||||
() => {
|
||||
throw new UserError('failed to add preconditions changes to git');
|
||||
},
|
||||
() => {},
|
||||
undefined,
|
||||
Verboseness.NORMAL,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async function run() {
|
||||
console.log('looking for untracked changes ...');
|
||||
|
||||
// check for any uncommited files and exit if there are any
|
||||
await $seq(
|
||||
[`git add .`, `git diff --quiet`, `git diff --cached --quiet`, `git checkout ${config.devBranch}`],
|
||||
() => {
|
||||
throw new UserError('there are still untracked changes');
|
||||
},
|
||||
() => {},
|
||||
undefined,
|
||||
Verboseness.QUITET,
|
||||
);
|
||||
|
||||
console.log('\nrunning preconditions ...\n');
|
||||
|
||||
await runPreconditions();
|
||||
|
||||
console.log('\nbumping versions ...\n');
|
||||
|
||||
const manifestFile = Bun.file('./manifest.json');
|
||||
const manifest = await manifestFile.json();
|
||||
|
||||
const versionString: string = manifest.version;
|
||||
const currentVersion: Version = parseVersion(versionString);
|
||||
const currentVersionString = stringifyVersion(currentVersion);
|
||||
|
||||
const versionIncrementOptions = getIncrementOptions(currentVersion);
|
||||
|
||||
const selectedIndex = await $choice(
|
||||
`Current version "${currentVersionString}". Select new version`,
|
||||
versionIncrementOptions.map(x => stringifyVersion(x)),
|
||||
);
|
||||
const newVersion = versionIncrementOptions[selectedIndex];
|
||||
const newVersionString = stringifyVersion(newVersion);
|
||||
|
||||
console.log('');
|
||||
|
||||
await $confirm(`Version will be updated "${currentVersionString}" -> "${newVersionString}". Are you sure`, () => {
|
||||
throw new UserError('user canceled script');
|
||||
});
|
||||
|
||||
if (!(newVersion instanceof CanaryVersion)) {
|
||||
manifest.version = newVersionString;
|
||||
}
|
||||
|
||||
await Bun.write(manifestFile, JSON.stringify(manifest, null, '\t'));
|
||||
|
||||
const betaManifest = structuredClone(manifest);
|
||||
betaManifest.version = newVersionString;
|
||||
|
||||
const betaManifestFile = Bun.file('./manifest-beta.json');
|
||||
await Bun.write(betaManifestFile, JSON.stringify(betaManifest, null, '\t'));
|
||||
|
||||
if (!(newVersion instanceof CanaryVersion)) {
|
||||
const versionsFile = Bun.file('./versions.json');
|
||||
const versionsJson = await versionsFile.json();
|
||||
|
||||
versionsJson[newVersionString] = manifest.minAppVersion;
|
||||
|
||||
await Bun.write(versionsFile, JSON.stringify(versionsJson, null, '\t'));
|
||||
|
||||
const packageFile = Bun.file('./package.json');
|
||||
const packageJson = await packageFile.json();
|
||||
|
||||
packageJson.version = newVersionString;
|
||||
|
||||
await Bun.write(packageFile, JSON.stringify(packageJson, null, '\t'));
|
||||
}
|
||||
|
||||
await $seq(
|
||||
[`bun run format`, `git add .`, `git commit -m "[auto] bump version to \`${newVersionString}\`"`],
|
||||
() => {
|
||||
throw new UserError('failed to add preconditions changes to git');
|
||||
},
|
||||
() => {},
|
||||
undefined,
|
||||
Verboseness.NORMAL,
|
||||
);
|
||||
|
||||
console.log('\ncreating release tag ...\n');
|
||||
|
||||
await $seq(
|
||||
[
|
||||
`git checkout ${config.releaseBranch}`,
|
||||
`git merge ${config.devBranch} --commit -m "[auto] merge \`${newVersionString}\` release commit"`,
|
||||
`git push origin ${config.releaseBranch}`,
|
||||
`git tag -a ${newVersionString} -m "release version ${newVersionString}"`,
|
||||
`git push origin ${newVersionString}`,
|
||||
`git checkout ${config.devBranch}`,
|
||||
`git merge ${config.releaseBranch}`,
|
||||
`git push origin ${config.devBranch}`,
|
||||
],
|
||||
() => {
|
||||
throw new UserError('failed to merge or create tag');
|
||||
},
|
||||
() => {},
|
||||
undefined,
|
||||
Verboseness.NORMAL,
|
||||
);
|
||||
|
||||
console.log('');
|
||||
|
||||
console.log(`${CMD_FMT.BgGreen}done${CMD_FMT.Reset}`);
|
||||
console.log(`${config.github}`);
|
||||
console.log(`${config.github}/releases/tag/${newVersionString}`);
|
||||
}
|
||||
|
||||
try {
|
||||
await run();
|
||||
} catch (e) {
|
||||
if (e instanceof UserError) {
|
||||
console.error(e.message);
|
||||
} else {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
160
automation/stats.ts
Normal file
160
automation/stats.ts
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
import * as fs from 'fs';
|
||||
|
||||
interface Stat {
|
||||
fileType: string;
|
||||
count: number;
|
||||
lines: number;
|
||||
}
|
||||
|
||||
abstract class StatsBase {
|
||||
parent: StatsBase | undefined;
|
||||
path: string;
|
||||
name: string;
|
||||
stats: Stat[];
|
||||
|
||||
constructor(parent: StatsBase | undefined, path: string, name: string, stats: Stat[]) {
|
||||
this.parent = parent;
|
||||
|
||||
this.path = path;
|
||||
this.name = name;
|
||||
this.stats = stats;
|
||||
}
|
||||
|
||||
abstract addChild(child: StatsBase): void;
|
||||
|
||||
abstract mergeStats(stats: Stat[]): void;
|
||||
|
||||
abstract print(depth: number, lastChildArr: boolean[]): void;
|
||||
|
||||
abstract sort(): void;
|
||||
|
||||
getPrefix(depth: number, lastChildArr: boolean[]): string {
|
||||
let prefix = '';
|
||||
for (let i = 0; i < depth; i++) {
|
||||
prefix += lastChildArr[i] ? ' ' : '│ ';
|
||||
}
|
||||
|
||||
if (lastChildArr.at(-1)) {
|
||||
prefix += '└─ ';
|
||||
} else {
|
||||
prefix += '├─ ';
|
||||
}
|
||||
|
||||
return prefix;
|
||||
}
|
||||
}
|
||||
|
||||
class FolderStats extends StatsBase {
|
||||
children: StatsBase[];
|
||||
|
||||
constructor(parent: StatsBase | undefined, path: string, name: string) {
|
||||
super(parent, path, name, []);
|
||||
|
||||
this.children = [];
|
||||
}
|
||||
|
||||
addChild(child: StatsBase) {
|
||||
this.children.push(child);
|
||||
this.mergeStats(child.stats);
|
||||
}
|
||||
|
||||
mergeStats(stats: Stat[]): void {
|
||||
// console.log(this, stats);
|
||||
for (const stat of stats) {
|
||||
const existingStat = this.stats.find(s => s.fileType === stat.fileType);
|
||||
if (existingStat) {
|
||||
existingStat.count += stat.count;
|
||||
existingStat.lines += stat.lines;
|
||||
} else {
|
||||
this.stats.push(structuredClone(stat));
|
||||
}
|
||||
}
|
||||
|
||||
this.parent?.mergeStats(stats);
|
||||
}
|
||||
|
||||
print(depth: number, lastChildArr: boolean[]): void {
|
||||
console.log(
|
||||
`${this.getPrefix(depth, lastChildArr)}${this.name} | ${this.stats.reduce((acc, s) => acc + s.count, 0)} files | ${this.stats.reduce((acc, s) => acc + s.lines, 0)} lines`,
|
||||
);
|
||||
for (let i = 0; i < this.children.length; i++) {
|
||||
const child = this.children[i];
|
||||
child.print(depth + 1, [...lastChildArr, i === this.children.length - 1]);
|
||||
}
|
||||
}
|
||||
|
||||
sort(): void {
|
||||
this.children.sort((a, b) => {
|
||||
if (a instanceof FolderStats && b instanceof FileStats) {
|
||||
return 1;
|
||||
} else if (a instanceof FileStats && b instanceof FolderStats) {
|
||||
return -1;
|
||||
} else {
|
||||
return a.name.localeCompare(b.name);
|
||||
}
|
||||
});
|
||||
this.children.forEach(c => c.sort());
|
||||
}
|
||||
}
|
||||
|
||||
class FileStats extends StatsBase {
|
||||
constructor(parent: StatsBase, path: string, name: string, stats: Stat[]) {
|
||||
super(parent, path, name, stats);
|
||||
}
|
||||
|
||||
addChild(_child: StatsBase): void {
|
||||
throw new Error('Cannot add child to file');
|
||||
}
|
||||
|
||||
mergeStats(_stats: Stat[]): void {
|
||||
throw new Error('Cannot merge stats to file');
|
||||
}
|
||||
|
||||
print(depth: number, lastChildArr: boolean[]): void {
|
||||
console.log(`${this.getPrefix(depth, lastChildArr)}${this.name} | ${this.stats[0].lines} lines`);
|
||||
}
|
||||
|
||||
sort(): void {}
|
||||
}
|
||||
|
||||
function collectStats() {
|
||||
const root = new FolderStats(undefined, './src', 'src');
|
||||
const ignore = ['node_modules', 'extraTypes', 'bun.lockb'];
|
||||
|
||||
const todo: FolderStats[] = [root];
|
||||
|
||||
while (todo.length > 0) {
|
||||
const current = todo.pop()!;
|
||||
const children = fs.readdirSync(current.path, { withFileTypes: true });
|
||||
|
||||
for (const child of children) {
|
||||
if (ignore.includes(child.name)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (child.isDirectory()) {
|
||||
const folder = new FolderStats(current, `${current.path}/${child.name}`, child.name);
|
||||
current.addChild(folder);
|
||||
|
||||
todo.push(folder);
|
||||
} else {
|
||||
const content = fs.readFileSync(`${current.path}/${child.name}`, 'utf-8');
|
||||
|
||||
const file = new FileStats(current, `${current.path}/${child.name}`, child.name, [
|
||||
{
|
||||
fileType: child.name.split('.').splice(1).join('.'),
|
||||
count: 1,
|
||||
lines: content.split('\n').length,
|
||||
},
|
||||
]);
|
||||
current.addChild(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
root.sort();
|
||||
|
||||
root.print(0, [true]);
|
||||
}
|
||||
|
||||
collectStats();
|
||||
20
automation/tsconfig.json
Normal file
20
automation/tsconfig.json
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"module": "ESNext",
|
||||
"target": "ESNext",
|
||||
"allowJs": true,
|
||||
"noImplicitAny": true,
|
||||
"strict": true,
|
||||
"strictNullChecks": true,
|
||||
"noImplicitReturns": true,
|
||||
"moduleResolution": "node",
|
||||
"importHelpers": true,
|
||||
"isolatedModules": true,
|
||||
"lib": ["DOM", "ES5", "ES6", "ES7", "Es2021"],
|
||||
"types": ["bun-types"],
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"resolveJsonModule": true
|
||||
},
|
||||
"include": ["**/*.ts"]
|
||||
}
|
||||
168
automation/utils/shellUtils.ts
Normal file
168
automation/utils/shellUtils.ts
Normal file
|
|
@ -0,0 +1,168 @@
|
|||
import { Subprocess } from 'bun';
|
||||
import stringArgv from 'string-argv';
|
||||
|
||||
export enum Verboseness {
|
||||
QUITET,
|
||||
NORMAL,
|
||||
VERBOSE,
|
||||
}
|
||||
|
||||
function exec(c: string, cwd?: string): Subprocess<'ignore', 'pipe', 'inherit'> {
|
||||
return Bun.spawn(stringArgv(c), { cwd: cwd });
|
||||
}
|
||||
|
||||
export async function $(cmd: string, cwd?: string | undefined, verboseness: Verboseness = Verboseness.NORMAL): Promise<{ stdout: string; stderr: string; exit: number }> {
|
||||
if (verboseness === Verboseness.NORMAL || verboseness === Verboseness.VERBOSE) {
|
||||
if (cwd !== undefined) {
|
||||
console.log(`\n${CMD_FMT.Bright}running${CMD_FMT.Reset} in ${cwd} - ${cmd}\n`);
|
||||
} else {
|
||||
console.log(`\n${CMD_FMT.Bright}running${CMD_FMT.Reset} - ${cmd}\n`);
|
||||
}
|
||||
}
|
||||
|
||||
const proc = exec(cmd, cwd);
|
||||
const stdout = await new Response(proc.stdout).text();
|
||||
const stderr = await new Response(proc.stderr).text();
|
||||
|
||||
if (verboseness === Verboseness.VERBOSE) {
|
||||
if (stdout !== '') {
|
||||
console.log(
|
||||
stdout
|
||||
.split('\n')
|
||||
.map(x => `${CMD_FMT.FgGray}>${CMD_FMT.Reset} ${x}\n`)
|
||||
.join(''),
|
||||
);
|
||||
}
|
||||
|
||||
if (stderr !== '') {
|
||||
console.log(
|
||||
stderr
|
||||
.split('\n')
|
||||
.map(x => `${CMD_FMT.FgRed}>${CMD_FMT.Reset} ${x}\n`)
|
||||
.join(''),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const exit = await proc.exited;
|
||||
|
||||
if (verboseness === Verboseness.NORMAL || verboseness === Verboseness.VERBOSE) {
|
||||
if (exit === 0) {
|
||||
console.log(`${CMD_FMT.FgGreen}success${CMD_FMT.Reset} - ${cmd}\n`);
|
||||
} else {
|
||||
console.log(`${CMD_FMT.FgRed}fail${CMD_FMT.Reset} - ${cmd} - code ${exit}\n`);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
stdout,
|
||||
stderr,
|
||||
exit,
|
||||
};
|
||||
}
|
||||
|
||||
export async function $seq(
|
||||
cmds: string[],
|
||||
onError: (cmd: string, index: number) => void,
|
||||
onSuccess: () => void,
|
||||
cwd?: string | undefined,
|
||||
verboseness: Verboseness = Verboseness.NORMAL,
|
||||
): Promise<void> {
|
||||
const results = [];
|
||||
for (let i = 0; i < cmds.length; i += 1) {
|
||||
const cmd = cmds[i];
|
||||
const result = await $(cmd, cwd, verboseness);
|
||||
|
||||
if (result.exit !== 0) {
|
||||
onError(cmd, i);
|
||||
return;
|
||||
}
|
||||
|
||||
results.push(result);
|
||||
}
|
||||
onSuccess();
|
||||
}
|
||||
|
||||
export async function $input(message: string): Promise<string> {
|
||||
console.write(`${message} `);
|
||||
const stdin = Bun.stdin.stream();
|
||||
const reader = stdin.getReader();
|
||||
const chunk = await reader.read();
|
||||
reader.releaseLock();
|
||||
const text = Buffer.from(chunk.value ?? '').toString();
|
||||
return text.trim();
|
||||
}
|
||||
|
||||
export async function $choice(message: string, options: string[]): Promise<number> {
|
||||
console.log(`${message} `);
|
||||
|
||||
let optionNumbers = new Map<string, number>();
|
||||
|
||||
for (let i = 0; i < options.length; i++) {
|
||||
const option = options[i];
|
||||
console.log(`[${i}] ${option}`);
|
||||
|
||||
optionNumbers.set(i.toString(), i);
|
||||
}
|
||||
|
||||
let ret: undefined | number = undefined;
|
||||
|
||||
while (ret === undefined) {
|
||||
const selectedStr = await $input(`Select [${[...optionNumbers.keys()].join('/')}]:`);
|
||||
|
||||
ret = optionNumbers.get(selectedStr);
|
||||
|
||||
if (ret === undefined) {
|
||||
console.log(`${CMD_FMT.FgRed}invalid selection, please select a valid option${CMD_FMT.Reset}`);
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
export async function $confirm(message: string, onReject: () => void): Promise<void> {
|
||||
while (true) {
|
||||
const answer = await $input(`${message} [Y/N]?`);
|
||||
|
||||
if (answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (answer.toLowerCase() === 'n' || answer.toLowerCase() === 'no') {
|
||||
onReject();
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`${CMD_FMT.FgRed}invalid selection, please select a valid option${CMD_FMT.Reset}`);
|
||||
}
|
||||
}
|
||||
|
||||
export const CMD_FMT = {
|
||||
Reset: '\x1b[0m',
|
||||
Bright: '\x1b[1m',
|
||||
Dim: '\x1b[2m',
|
||||
Underscore: '\x1b[4m',
|
||||
Blink: '\x1b[5m',
|
||||
Reverse: '\x1b[7m',
|
||||
Hidden: '\x1b[8m',
|
||||
|
||||
FgBlack: '\x1b[30m',
|
||||
FgRed: '\x1b[31m',
|
||||
FgGreen: '\x1b[32m',
|
||||
FgYellow: '\x1b[33m',
|
||||
FgBlue: '\x1b[34m',
|
||||
FgMagenta: '\x1b[35m',
|
||||
FgCyan: '\x1b[36m',
|
||||
FgWhite: '\x1b[37m',
|
||||
FgGray: '\x1b[90m',
|
||||
|
||||
BgBlack: '\x1b[40m',
|
||||
BgRed: '\x1b[41m',
|
||||
BgGreen: '\x1b[42m',
|
||||
BgYellow: '\x1b[43m',
|
||||
BgBlue: '\x1b[44m',
|
||||
BgMagenta: '\x1b[45m',
|
||||
BgCyan: '\x1b[46m',
|
||||
BgWhite: '\x1b[47m',
|
||||
BgGray: '\x1b[100m',
|
||||
};
|
||||
6
automation/utils/utils.ts
Normal file
6
automation/utils/utils.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
export class UserError extends Error {}
|
||||
|
||||
export interface ProjectConfig {
|
||||
corePackages: string[];
|
||||
packages: string[];
|
||||
}
|
||||
108
automation/utils/versionUtils.ts
Normal file
108
automation/utils/versionUtils.ts
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
import { Parser } from '@lemons_dev/parsinom/lib/Parser';
|
||||
import { P_UTILS } from '@lemons_dev/parsinom/lib/ParserUtils';
|
||||
import { P } from '@lemons_dev/parsinom/lib/ParsiNOM';
|
||||
import Moment from 'moment';
|
||||
import { UserError } from 'utils/utils';
|
||||
|
||||
export class Version {
|
||||
major: number;
|
||||
minor: number;
|
||||
patch: number;
|
||||
|
||||
constructor(major: number, minor: number, patch: number) {
|
||||
this.major = major;
|
||||
this.minor = minor;
|
||||
this.patch = patch;
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
return `${this.major}.${this.minor}.${this.patch}`;
|
||||
}
|
||||
}
|
||||
|
||||
export class CanaryVersion extends Version {
|
||||
canary: string;
|
||||
|
||||
constructor(major: number, minor: number, patch: number, canary: string) {
|
||||
super(major, minor, patch);
|
||||
this.canary = canary;
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
return `${super.toString()}-canary.${this.canary}`;
|
||||
}
|
||||
}
|
||||
|
||||
const numberParser: Parser<number> = P_UTILS.digits()
|
||||
.map(x => Number.parseInt(x))
|
||||
.chain(x => {
|
||||
if (Number.isNaN(x)) {
|
||||
return P.fail('a number');
|
||||
} else {
|
||||
return P.succeed(x);
|
||||
}
|
||||
});
|
||||
|
||||
const canaryParser: Parser<string> = P.sequenceMap(
|
||||
(_, c1, c2, c3) => {
|
||||
return c1 + c2 + c3;
|
||||
},
|
||||
P.string('-canary.'),
|
||||
P_UTILS.digit()
|
||||
.repeat(8, 8)
|
||||
.map(x => x.join('')),
|
||||
P.string('T'),
|
||||
P_UTILS.digit()
|
||||
.repeat(6, 6)
|
||||
.map(x => x.join('')),
|
||||
);
|
||||
|
||||
export const versionParser: Parser<Version> = P.or(
|
||||
P.sequenceMap(
|
||||
(major, _1, minor, _2, patch) => {
|
||||
return new Version(major, minor, patch);
|
||||
},
|
||||
numberParser,
|
||||
P.string('.'),
|
||||
numberParser,
|
||||
P.string('.'),
|
||||
numberParser,
|
||||
P_UTILS.eof(),
|
||||
),
|
||||
P.sequenceMap(
|
||||
(major, _1, minor, _2, patch, canary) => {
|
||||
return new CanaryVersion(major, minor, patch, canary);
|
||||
},
|
||||
numberParser,
|
||||
P.string('.'),
|
||||
numberParser,
|
||||
P.string('.'),
|
||||
numberParser,
|
||||
canaryParser,
|
||||
P_UTILS.eof(),
|
||||
),
|
||||
);
|
||||
|
||||
export function parseVersion(str: string): Version {
|
||||
const parserRes = versionParser.tryParse(str);
|
||||
if (parserRes.success) {
|
||||
return parserRes.value;
|
||||
} else {
|
||||
throw new UserError(`failed to parse manifest version "${str}"`);
|
||||
}
|
||||
}
|
||||
|
||||
export function stringifyVersion(version: Version): string {
|
||||
return version.toString();
|
||||
}
|
||||
|
||||
export function getIncrementOptions(version: Version): [Version, Version, Version, CanaryVersion] {
|
||||
const moment = Moment();
|
||||
const canary = moment.utcOffset(0).format('YYYYMMDDTHHmmss');
|
||||
return [
|
||||
new Version(version.major + 1, 0, 0),
|
||||
new Version(version.major, version.minor + 1, 0),
|
||||
new Version(version.major, version.minor, version.patch + 1),
|
||||
new CanaryVersion(version.major, version.minor, version.patch, canary),
|
||||
];
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue