Files
2026-10-04 00:30:09 +07:00

104 lines
6.7 KiB
JavaScript

import { spawnSync } from 'node:child_process';
import { packwizContext } from './build.mjs';
export const usage = `Usage:
./mcpack build [--config FILE] [--release]
./mcpack release VERSION [--config FILE]
./mcpack add <modrinth-slug|ID|URL|search> [--yes] [--config FILE]
./mcpack remove <mod> [--yes] [--config FILE]
./mcpack update <mod> [--yes] [--config FILE]
./mcpack update --all [--yes] [--config FILE]
./mcpack deploy [--artifact RELEASE_DIR] [--config FILE] [--ssh-config FILE] [--remote-node FILE]
./mcpack rollback [--backup ID --accept-progress-loss] [--config FILE] [--ssh-config FILE] [--remote-node FILE]
./mcpack rollback-pack VERSION [--confirm-compatible] [--config FILE] [--ssh-config FILE] [--remote-node FILE]
./mcpack backup [--target-release DIR] [--wait-timeout SECONDS]
./mcpack restore --backup SNAPSHOT_DIR --destination NEW_SERVER_ROOT
Mod commands edit packwiz source and retain packwiz prompts/output.
Development builds may be dirty; --release requires a clean Git worktree.`;
export function parseArgs(args) {
args = [...args];
const command = args.shift();
if (!['build', 'release', 'add', 'remove', 'update', 'backup', 'restore', 'deploy', 'rollback', 'rollback-pack'].includes(command)) throw new Error(usage);
const options = { command, configFile: 'platform/config.yaml', releaseOnly: false, yes: false, all: false };
const seen = new Set();
while (args.length) {
const argument = args.shift();
const option = argument === '-y' ? '--yes' : argument;
if (['--config', '--release', '--yes', '--all', '--target-release', '--wait-timeout', '--backup', '--destination', '--artifact', '--ssh-config', '--remote-node', '--confirm-compatible', '--accept-progress-loss'].includes(option)) {
if (seen.has(option)) throw new Error(`Repeated argument: ${option}`);
seen.add(option);
if (['--config', '--target-release', '--wait-timeout', '--backup', '--destination', '--artifact', '--ssh-config', '--remote-node'].includes(option)) {
const value = args.shift();
if (!value || value.startsWith('-') || /[\0\r\n]/.test(value)) throw new Error(`Missing or invalid value: ${option}`);
options[{'--config':'configFile','--target-release':'targetRelease','--wait-timeout':'waitTimeout','--backup':'backupDirectory','--destination':'destination','--artifact':'artifact','--ssh-config':'sshConfig','--remote-node':'remoteNode'}[option]] = value;
} else options[option === '--release' ? 'releaseOnly' : option.slice(2)] = true;
} else {
if (argument.startsWith('-') || options.mod || /[\0\r\n]/.test(argument)) throw new Error(`Unexpected argument: ${argument}`);
options.mod = argument;
}
}
if (command === 'rollback' || command === 'rollback-pack') {
if (options.yes || options.all || options.releaseOnly || options.artifact || options.targetRelease || options.waitTimeout || options.destination) throw new Error('Invalid rollback arguments');
if (command === 'rollback-pack') {
if (!options.mod || options.backupDirectory || options['accept-progress-loss']) throw new Error('rollback-pack requires a version and optional --confirm-compatible');
options.version = options.mod;
options.confirmCompatible = Boolean(options['confirm-compatible']);
} else {
if (options.mod || options['confirm-compatible'] || (options['accept-progress-loss'] && !options.backupDirectory)) throw new Error('rollback accepts only an optional snapshot ID and progress-loss acknowledgment');
options.backupId = options.backupDirectory;
options.acceptProgressLoss = Boolean(options['accept-progress-loss']);
}
return options;
}
if (options['confirm-compatible'] || options['accept-progress-loss']) throw new Error('Confirmation option is only for rollback');
if (command === 'deploy') {
if (options.mod || options.yes || options.all || options.releaseOnly || options.targetRelease || options.waitTimeout || options.backupDirectory || options.destination) throw new Error('deploy accepts only --artifact and --config');
return options;
}
if (options.artifact || options.sshConfig || options.remoteNode) throw new Error('--artifact is only valid for deploy');
const backupOptions = ['--target-release', '--wait-timeout', '--backup', '--destination'];
if (command === 'backup' || command === 'restore') {
if (options.mod || options.yes || options.all || options.releaseOnly || seen.has('--config')) throw new Error('Invalid backup/restore arguments');
if (command === 'backup') {
if (options.backupDirectory || options.destination) throw new Error('backup accepts only --target-release and --wait-timeout');
if (options.waitTimeout && (!/^[1-9]\d*$/.test(options.waitTimeout) || !Number.isSafeInteger(Number(options.waitTimeout)))) throw new Error('Invalid --wait-timeout');
options.waitTimeout = Number(options.waitTimeout ?? 120);
} else if (!options.backupDirectory || !options.destination || options.targetRelease || options.waitTimeout) throw new Error('restore requires --backup and --destination');
return options;
}
if (backupOptions.some(option => seen.has(option))) throw new Error('Backup/restore option used with another command');
if(command==='release'){
if(!options.mod||options.yes||options.all||options.releaseOnly)throw new Error('release requires VERSION and accepts only --config');
options.version=options.mod;return options;
}
if (command === 'build') {
if (options.mod || options.yes || options.all) throw new Error('build accepts only --config and --release');
} else {
if (options.releaseOnly) throw new Error('--release is only valid for build');
if (command !== 'update' && options.all) throw new Error('--all is only valid for update');
if (options.all && options.mod) throw new Error('Choose update <mod> or update --all');
if (!options.all && !options.mod) throw new Error(`${command} requires a mod`);
}
return options;
}
export function packwizArguments(options) {
const arguments_ = options.yes ? ['--yes'] : [];
if (options.command === 'add') {
arguments_.push('modrinth', 'add', options.mod);
} else if (options.command === 'remove') arguments_.push('remove', options.mod);
else if (options.command === 'update') arguments_.push('update', ...(options.all ? ['--all'] : [options.mod]));
else throw new Error('Expected a mod command');
return arguments_;
}
export function manageMod({ root, ...options }) {
const context = packwizContext({ root, configFile: options.configFile });
const result = spawnSync(context.executable, [...context.args, ...packwizArguments(options)], { cwd: context.source, stdio: 'inherit' });
if (result.error) throw result.error;
if (result.signal) throw new Error(`packwiz terminated by ${result.signal}`);
return result.status ?? 1;
}