minimal m3 init

This commit is contained in:
Dinhero21 2024-09-10 17:40:45 -03:00
parent 350b3237ae
commit 79f2d3be61
8 changed files with 1079 additions and 4 deletions

View file

@ -35,6 +35,11 @@ export default tseslint.config(
'@typescript-eslint/no-misused-promises': 'error', '@typescript-eslint/no-misused-promises': 'error',
'object-shorthand': 'error', 'object-shorthand': 'error',
'@typescript-eslint/consistent-type-imports': 'error', '@typescript-eslint/consistent-type-imports': 'error',
'@typescript-eslint/explicit-function-return-type': [
'error',
{ allowExpressions: true },
],
'@typescript-eslint/strict-boolean-expressions': 'error',
}, },
languageOptions: { languageOptions: {
parserOptions: { parserOptions: {

872
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -23,12 +23,15 @@
} }
}, },
"dependencies": { "dependencies": {
"@inquirer/prompts": "^5.3.8",
"@types/text-table": "^0.2.5", "@types/text-table": "^0.2.5",
"chalk": "^5.3.0", "chalk": "^5.3.0",
"commander": "^12.1.0", "commander": "^12.1.0",
"listr2": "^8.2.4",
"minecraft-protocol": "^1.47.0", "minecraft-protocol": "^1.47.0",
"strip-ansi": "^7.1.0", "strip-ansi": "^7.1.0",
"text-table": "^0.2.0", "text-table": "^0.2.0",
"tmp-promise": "^3.0.3",
"zod": "^3.23.8" "zod": "^3.23.8"
}, },
"devDependencies": { "devDependencies": {

133
src/m3/command/init.ts Normal file
View file

@ -0,0 +1,133 @@
import { confirm, input } from '@inquirer/prompts';
import { Argument } from 'commander';
import { mkdir, writeFile } from 'fs/promises';
import type { ListrTask } from 'listr2';
import { Listr } from 'listr2';
import { resolve } from 'path';
import type { Writable } from 'stream';
import { exists } from '../../util/file.js';
import { run } from '../process.js';
import program, { moduleDir, modulesDir } from '../program.js';
import type { GlobalModuleData, LocalModuleData } from '../types.js';
program
.command('init')
.description('Initialize a new module')
.addArgument(new Argument('[name]', 'Name of the module').default(moduleDir))
.option('-f, --override', 'Overwrite existing module')
.option('--git', 'Initialize a new git repository')
.option('--npm', 'Initialize a new npm package')
.action(
async (
name: string | undefined,
options: { git?: boolean; npm?: boolean; override?: boolean },
) => {
name =
name ??
(await input({ message: 'Name of the module', default: moduleDir }));
const git =
options.git ??
(await confirm({
message: 'Initialize a new git repo?',
default: true,
}));
const npm =
options.npm ??
(await confirm({
message: 'Initialize a new npm package?',
default: true,
}));
await init({
override: options.override ?? false,
name,
git,
npm,
m3: true,
});
},
);
interface InitOptions {
override: boolean;
name: string;
git: boolean;
npm: boolean;
m3: boolean;
}
async function init(options: InitOptions): Promise<void> {
const directory = resolve(modulesDir, options.name);
if (!options.override && (await exists(directory))) {
throw new Error(`Module ${options.name} already exists`);
}
await mkdir(directory, { recursive: true });
const tasks: ListrTask[] = [];
if (options.git) {
tasks.push({
title: 'Initialize git',
task: async (_ctx, task) => {
await run(
'git',
['init'],
{ cwd: directory },
{ stdout: task.stdout() as Writable },
);
},
});
}
if (options.npm) {
tasks.push({
title: 'Initialize npm',
task: async (_ctx, task) => {
await run(
'npm',
['init', '-y'],
{ cwd: directory },
{ stdout: task.stdout() as Writable },
);
},
});
}
if (options.m3) {
tasks.push({
title: 'Initialize m3 json files',
task: async (_ctx, _task) => {
const global: GlobalModuleData = {
name: options.name,
dependencies: [],
};
const local: LocalModuleData = {
manual: true,
};
await writeFile(
resolve(directory, 'm3.global.json'),
JSON.stringify(global),
);
await writeFile(
resolve(directory, 'm3.local.json'),
JSON.stringify(local),
);
},
});
}
await new Listr(tasks, {
concurrent: true,
exitOnError: false,
}).run();
}

30
src/m3/override.ts Normal file
View file

@ -0,0 +1,30 @@
import { Command, Option } from 'commander';
import { override } from '../util/reflect.js';
// Please forgive me, I had to
// even if you extend Command, when chaining, it will return Command instead of the extended class
// thus, impossibilitating customization without monkey-patching
override(
Command.prototype,
'addOption',
(original) =>
function (this: Command, option: Option): Command {
const result = original.call(this, option);
if (
option.long &&
!option.required &&
!option.negate &&
!option.optional
) {
this.addOption(
new Option(option.long.replace(/^--/, '--no-')).hideHelp(),
);
}
return result;
},
);

30
src/m3/process.ts Normal file
View file

@ -0,0 +1,30 @@
import type { ChildProcess, SpawnOptions } from 'child_process';
import { spawn } from 'child_process';
import type { Readable, Writable } from 'stream';
export async function run(
command: string,
args: string[],
options: SpawnOptions,
stdio?: { stdin?: Readable; stdout?: Writable; stderr?: Writable },
): Promise<ChildProcess> {
const child = spawn(command, args, options);
if (stdio !== undefined) {
if (child.stdin !== null) stdio.stdin?.pipe(child.stdin);
if (stdio.stdout !== undefined) child.stdout?.pipe(stdio.stdout);
if (stdio.stderr !== null) stdio.stderr?.pipe(stdio.stderr);
}
await new Promise<void>((resolve, reject) => {
child.on('close', (code) => {
if (code === 0) {
resolve();
} else {
reject();
}
});
});
return child;
}

View file

@ -1,3 +1,5 @@
import './override.js';
import { Command } from 'commander'; import { Command } from 'commander';
import { basename, resolve } from 'path'; import { basename, resolve } from 'path';

8
src/util/reflect.ts Normal file
View file

@ -0,0 +1,8 @@
export function override<T, K extends keyof T>(
target: T,
key: K,
callback: (original: T[K]) => T[K],
): void {
const original = target[key];
target[key] = callback(original);
}