104 lines
6.7 KiB
JavaScript
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;
|
|
}
|