mirror of
https://github.com/Nostalgica-Reverie/Content-Monorepo.git
synced 2026-05-09 00:24:15 +00:00
Merge branch 'main' of https://git.nostalgica.net/Lasting-Legacy/Lasting-Legacy-Monorepo
This commit is contained in:
@@ -35,12 +35,12 @@ jobs:
|
||||
SHORT_SHA=$(git rev-parse --short HEAD)
|
||||
echo "short_sha=$SHORT_SHA" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Cache Builder Binary
|
||||
- name: Cache Builder Binaries
|
||||
id: cache-builder
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ./builder-bin
|
||||
key: builder-v1-${{ runner.os }}-${{ hashFiles('src/actions/builder/**/*.rs', 'src/actions/builder/Cargo.toml', 'src/actions/builder/Cargo.lock') }}
|
||||
key: builder-v2-${{ runner.os }}-${{ hashFiles('src/actions/builder/**/*.rs', 'src/actions/builder/Cargo.toml', 'src/actions/builder/Cargo.lock') }}
|
||||
|
||||
- name: Install Rust
|
||||
if: steps.cache-builder.outputs.cache-hit != 'true'
|
||||
@@ -52,12 +52,15 @@ jobs:
|
||||
with:
|
||||
workspaces: "src/actions/builder -> target"
|
||||
|
||||
- name: Build Builder
|
||||
- name: Build Binaries
|
||||
if: steps.cache-builder.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
cargo build --release --manifest-path src/actions/builder/Cargo.toml --bin builder
|
||||
cargo build --release \
|
||||
--manifest-path src/actions/builder/Cargo.toml \
|
||||
--bin builder --bin minify-json
|
||||
mkdir -p ./builder-bin
|
||||
cp src/actions/builder/target/release/builder ./builder-bin/builder
|
||||
cp src/actions/builder/target/release/minify-json ./builder-bin/minify-json
|
||||
|
||||
- name: Cache Go Binaries
|
||||
id: cache-go
|
||||
@@ -88,11 +91,71 @@ jobs:
|
||||
restore-keys: |
|
||||
packwiz-cache-${{ runner.os }}-
|
||||
|
||||
- name: Minify JSON configs
|
||||
run: |
|
||||
set -eu
|
||||
chmod +x ./builder-bin/minify-json
|
||||
|
||||
if [ -d datapacks ]; then
|
||||
for pack_dir in datapacks/*/; do
|
||||
[ -d "$pack_dir" ] || continue
|
||||
TARGET="${pack_dir}content"
|
||||
if [ -d "$TARGET" ]; then
|
||||
cp -r "$TARGET" "${TARGET}.original"
|
||||
./builder-bin/minify-json "$TARGET"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
if [ -d modpacks ]; then
|
||||
for pack_dir in modpacks/*/; do
|
||||
[ -d "$pack_dir" ] || continue
|
||||
for subdir in "$pack_dir"*-mr "$pack_dir"*-cf; do
|
||||
[ -d "$subdir" ] || continue
|
||||
CONFIG_DIR="$subdir/config"
|
||||
if [ -d "$CONFIG_DIR" ]; then
|
||||
cp -r "$CONFIG_DIR" "${CONFIG_DIR}.original"
|
||||
./builder-bin/minify-json "$CONFIG_DIR"
|
||||
fi
|
||||
done
|
||||
done
|
||||
fi
|
||||
|
||||
- name: Run Build
|
||||
run: |
|
||||
chmod +x ./builder-bin/builder
|
||||
./builder-bin/builder "${{ steps.meta.outputs.short_sha }}"
|
||||
|
||||
- name: Restore JSON sources
|
||||
if: always()
|
||||
run: |
|
||||
set -eu
|
||||
|
||||
if [ -d datapacks ]; then
|
||||
for pack_dir in datapacks/*/; do
|
||||
[ -d "$pack_dir" ] || continue
|
||||
TARGET="${pack_dir}content"
|
||||
if [ -d "${TARGET}.original" ]; then
|
||||
rm -rf "$TARGET"
|
||||
mv "${TARGET}.original" "$TARGET"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
if [ -d modpacks ]; then
|
||||
for pack_dir in modpacks/*/; do
|
||||
[ -d "$pack_dir" ] || continue
|
||||
for subdir in "$pack_dir"*-mr "$pack_dir"*-cf; do
|
||||
[ -d "$subdir" ] || continue
|
||||
CONFIG_DIR="$subdir/config"
|
||||
if [ -d "${CONFIG_DIR}.original" ]; then
|
||||
rm -rf "$CONFIG_DIR"
|
||||
mv "${CONFIG_DIR}.original" "$CONFIG_DIR"
|
||||
fi
|
||||
done
|
||||
done
|
||||
fi
|
||||
|
||||
- name: Upload
|
||||
uses: https://code.forgejo.org/actions/upload-artifact@v3
|
||||
with:
|
||||
|
||||
@@ -73,13 +73,18 @@ jobs:
|
||||
modpacks
|
||||
datapacks
|
||||
src/actions/publish
|
||||
src/actions/builder
|
||||
tools/changelog
|
||||
tools/manifest
|
||||
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: Validate Manifest
|
||||
run: npx tsx tools/manifest/validate.ts "${{ matrix.manifest }}"
|
||||
|
||||
- name: Generate Changelog
|
||||
id: changelog
|
||||
run: npx tsx tools/changelog/generate-changelog.ts "${{ matrix.manifest }}"
|
||||
@@ -89,18 +94,31 @@ jobs:
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ./publisher-bin
|
||||
key: publisher-v1-${{ runner.os }}-${{ hashFiles('src/actions/publish/**/*.rs', 'src/actions/publish/Cargo.toml', 'src/actions/publish/Cargo.lock') }}
|
||||
key: publisher-v2-${{ runner.os }}-${{ hashFiles('src/actions/publish/**/*.rs', 'src/actions/publish/Cargo.toml', 'src/actions/publish/Cargo.lock') }}
|
||||
|
||||
- name: Cache Minify-JSON Binary
|
||||
id: cache-minify
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ./builder-bin
|
||||
key: builder-v2-${{ runner.os }}-${{ hashFiles('src/actions/builder/**/*.rs', 'src/actions/builder/Cargo.toml', 'src/actions/builder/Cargo.lock') }}
|
||||
|
||||
- name: Install Rust
|
||||
if: steps.cache-publisher.outputs.cache-hit != 'true'
|
||||
if: steps.cache-publisher.outputs.cache-hit != 'true' || steps.cache-minify.outputs.cache-hit != 'true'
|
||||
uses: https://github.com/dtolnay/rust-toolchain@stable
|
||||
|
||||
- name: Rust Cache
|
||||
- name: Rust Cache (publish)
|
||||
if: steps.cache-publisher.outputs.cache-hit != 'true'
|
||||
uses: https://github.com/Swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces: "src/actions/publish -> target"
|
||||
|
||||
- name: Rust Cache (builder)
|
||||
if: steps.cache-minify.outputs.cache-hit != 'true'
|
||||
uses: https://github.com/Swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces: "src/actions/builder -> target"
|
||||
|
||||
- name: Build Publisher
|
||||
if: steps.cache-publisher.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
@@ -108,6 +126,13 @@ jobs:
|
||||
mkdir -p ./publisher-bin
|
||||
cp src/actions/publish/target/release/publish ./publisher-bin/publish
|
||||
|
||||
- name: Build minify-json
|
||||
if: steps.cache-minify.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
cargo build --release --manifest-path src/actions/builder/Cargo.toml --bin minify-json
|
||||
mkdir -p ./builder-bin
|
||||
cp src/actions/builder/target/release/minify-json ./builder-bin/minify-json
|
||||
|
||||
- name: Cache Packwiz Binaries
|
||||
id: cache-go
|
||||
uses: actions/cache@v4
|
||||
@@ -137,12 +162,61 @@ jobs:
|
||||
restore-keys: |
|
||||
packwiz-cache-${{ runner.os }}-
|
||||
|
||||
- name: Minify JSON configs
|
||||
run: |
|
||||
set -eu
|
||||
chmod +x ./builder-bin/minify-json
|
||||
|
||||
MANIFEST='${{ matrix.manifest }}'
|
||||
PACK_DIR="$(dirname "$MANIFEST")"
|
||||
|
||||
if [[ "$MANIFEST" == datapacks/* ]]; then
|
||||
TARGET="${PACK_DIR}/content"
|
||||
if [ -d "$TARGET" ]; then
|
||||
cp -r "$TARGET" "${TARGET}.original"
|
||||
./builder-bin/minify-json "$TARGET"
|
||||
fi
|
||||
elif [[ "$MANIFEST" == modpacks/* ]]; then
|
||||
for subdir in "$PACK_DIR"/*-mr "$PACK_DIR"/*-cf; do
|
||||
[ -d "$subdir" ] || continue
|
||||
CONFIG_DIR="$subdir/config"
|
||||
if [ -d "$CONFIG_DIR" ]; then
|
||||
cp -r "$CONFIG_DIR" "${CONFIG_DIR}.original"
|
||||
./builder-bin/minify-json "$CONFIG_DIR"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
- name: Run Publisher
|
||||
id: meta
|
||||
run: |
|
||||
chmod +x ./publisher-bin/publish
|
||||
./publisher-bin/publish "${{ matrix.manifest }}"
|
||||
|
||||
- name: Restore JSON sources
|
||||
if: always()
|
||||
run: |
|
||||
set -eu
|
||||
MANIFEST='${{ matrix.manifest }}'
|
||||
PACK_DIR="$(dirname "$MANIFEST")"
|
||||
|
||||
if [[ "$MANIFEST" == datapacks/* ]]; then
|
||||
TARGET="${PACK_DIR}/content"
|
||||
if [ -d "${TARGET}.original" ]; then
|
||||
rm -rf "$TARGET"
|
||||
mv "${TARGET}.original" "$TARGET"
|
||||
fi
|
||||
elif [[ "$MANIFEST" == modpacks/* ]]; then
|
||||
for subdir in "$PACK_DIR"/*-mr "$PACK_DIR"/*-cf; do
|
||||
[ -d "$subdir" ] || continue
|
||||
CONFIG_DIR="$subdir/config"
|
||||
if [ -d "${CONFIG_DIR}.original" ]; then
|
||||
rm -rf "$CONFIG_DIR"
|
||||
mv "${CONFIG_DIR}.original" "$CONFIG_DIR"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
- name: Upload to Platforms
|
||||
if: "steps.meta.outputs.mr_id != '' || steps.meta.outputs.cf_id != ''"
|
||||
uses: https://github.com/Kir-Antipov/mc-publish@v3.3
|
||||
@@ -155,6 +229,7 @@ jobs:
|
||||
curseforge-files: "${{ github.workspace }}/${{ steps.meta.outputs.path }}/artifacts/*.zip"
|
||||
name: "${{ steps.meta.outputs.name }}"
|
||||
version: "${{ steps.meta.outputs.ver }}"
|
||||
version-type: ${{ steps.meta.outputs.release_type }}
|
||||
changelog: "${{ steps.changelog.outputs.notes }}"
|
||||
loaders: ${{ steps.meta.outputs.type == 'modpack' && steps.meta.outputs.loader || 'minecraft' }}
|
||||
game-versions: "${{ steps.meta.outputs.mc }}"
|
||||
@@ -1,9 +1,12 @@
|
||||
{
|
||||
"$schema": "../../tools/manifest/schema.json",
|
||||
"id": "rc-plus",
|
||||
"name": "Re-Console Plus",
|
||||
"type": "modpack",
|
||||
"version": "26.04.7",
|
||||
"loader": "fabric",
|
||||
"mc_version": "1.21.10",
|
||||
"version": "26.04.8",
|
||||
"release_type": "release",
|
||||
"modrinth_id": "legacy-minecraft",
|
||||
"curseforge_id": "re-console"
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,12 @@
|
||||
{
|
||||
"$schema": "../../tools/manifest/schema.json",
|
||||
"id": "simply",
|
||||
"name": "Simply Legacy",
|
||||
"type": "modpack",
|
||||
"version": "26.04.5",
|
||||
"loader": "fabric",
|
||||
"mc_version": "1.21.10",
|
||||
"version": "26.04.6",
|
||||
"release_type": "release",
|
||||
"modrinth_id": "simply-legacy",
|
||||
"curseforge_id": "simply-legacy"
|
||||
}
|
||||
}
|
||||
@@ -13,3 +13,7 @@ zip = "2.2"
|
||||
[[bin]]
|
||||
name = "builder"
|
||||
path = "builder.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "minify-json"
|
||||
path = "minify-json.rs"
|
||||
84
src/actions/builder/minify-json.rs
Normal file
84
src/actions/builder/minify-json.rs
Normal file
@@ -0,0 +1,84 @@
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use std::{
|
||||
env,
|
||||
fs,
|
||||
path::Path,
|
||||
sync::atomic::{AtomicU64, Ordering},
|
||||
};
|
||||
use walkdir::WalkDir;
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let args: Vec<String> = env::args().collect();
|
||||
let root = args
|
||||
.get(1)
|
||||
.ok_or_else(|| anyhow!("usage: minify-json <directory>"))?;
|
||||
|
||||
let root_path = Path::new(root);
|
||||
if !root_path.is_dir() {
|
||||
return Err(anyhow!("not a directory: {root}"));
|
||||
}
|
||||
|
||||
let bytes_before = AtomicU64::new(0);
|
||||
let bytes_after = AtomicU64::new(0);
|
||||
let mut processed = 0usize;
|
||||
let mut skipped = 0usize;
|
||||
|
||||
for entry in WalkDir::new(root_path) {
|
||||
let entry = entry.context("walkdir error")?;
|
||||
let path = entry.path();
|
||||
if !path.is_file() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let ext = path.extension().and_then(|s| s.to_str()).unwrap_or("");
|
||||
if ext != "json" && ext != "mcmeta" {
|
||||
continue;
|
||||
}
|
||||
|
||||
match minify_file(path) {
|
||||
Ok((before, after)) => {
|
||||
bytes_before.fetch_add(before, Ordering::Relaxed);
|
||||
bytes_after.fetch_add(after, Ordering::Relaxed);
|
||||
processed += 1;
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("warning: skipped {}: {e}", path.display());
|
||||
skipped += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let before = bytes_before.load(Ordering::Relaxed);
|
||||
let after = bytes_after.load(Ordering::Relaxed);
|
||||
let saved = before.saturating_sub(after);
|
||||
let pct = if before > 0 {
|
||||
(saved as f64 / before as f64) * 100.0
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
|
||||
println!(
|
||||
"minified {processed} file(s), skipped {skipped}, saved {saved} bytes ({pct:.1}%)"
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn minify_file(path: &Path) -> Result<(u64, u64)> {
|
||||
let content = fs::read_to_string(path)
|
||||
.with_context(|| format!("read failed: {}", path.display()))?;
|
||||
let before = content.len() as u64;
|
||||
|
||||
let value: serde_json::Value = serde_json::from_str(&content)
|
||||
.with_context(|| format!("parse failed: {}", path.display()))?;
|
||||
|
||||
let minified = serde_json::to_string(&value)
|
||||
.with_context(|| format!("serialize failed: {}", path.display()))?;
|
||||
let after = minified.len() as u64;
|
||||
|
||||
if after < before {
|
||||
fs::write(path, minified)
|
||||
.with_context(|| format!("write failed: {}", path.display()))?;
|
||||
}
|
||||
|
||||
Ok((before, after))
|
||||
}
|
||||
@@ -16,8 +16,6 @@ enum Platform {
|
||||
}
|
||||
|
||||
impl Platform {
|
||||
const ALL: [Platform; 2] = [Platform::Modrinth, Platform::Curseforge];
|
||||
|
||||
fn short(self) -> &'static str {
|
||||
match self {
|
||||
Platform::Modrinth => "mr",
|
||||
@@ -60,10 +58,15 @@ fn main() -> Result<()> {
|
||||
let p_ver = required_str(&manifest, "version")?;
|
||||
let mc_ver = required_str(&manifest, "mc_version")?;
|
||||
let p_type = required_str(&manifest, "type")?;
|
||||
let loader = manifest["loader"].as_str().unwrap_or("fabric");
|
||||
let loader = required_str(&manifest, "loader")?;
|
||||
let release_type = required_str(&manifest, "release_type")?;
|
||||
let mr_id = manifest["modrinth_id"].as_str().unwrap_or("");
|
||||
let cf_id = manifest["curseforge_id"].as_str().unwrap_or("");
|
||||
|
||||
if mr_id.is_empty() && cf_id.is_empty() {
|
||||
bail!("manifest must set at least one of modrinth_id or curseforge_id");
|
||||
}
|
||||
|
||||
let workspace = env::var("GITHUB_WORKSPACE").unwrap_or_else(|_| ".".into());
|
||||
let artifacts_dir = Path::new(&workspace).join(p_dir).join("artifacts");
|
||||
if artifacts_dir.exists() {
|
||||
@@ -76,7 +79,7 @@ fn main() -> Result<()> {
|
||||
println!("::group::Building artifacts for {raw_name}");
|
||||
|
||||
match p_type {
|
||||
"modpack" => build_modpack(p_dir, &artifacts_dir, &p_name, mc_ver, p_ver, loader)?,
|
||||
"modpack" => build_modpack(p_dir, &artifacts_dir, &p_name, mc_ver, p_ver, loader, mr_id, cf_id)?,
|
||||
"datapack" => build_datapack(p_dir, &artifacts_dir, &manifest, p_ver)?,
|
||||
other => bail!("unsupported pack type: {other}"),
|
||||
}
|
||||
@@ -91,6 +94,7 @@ fn main() -> Result<()> {
|
||||
mc_ver,
|
||||
p_type,
|
||||
loader,
|
||||
release_type,
|
||||
p_dir,
|
||||
})?;
|
||||
|
||||
@@ -110,11 +114,16 @@ fn build_modpack(
|
||||
mc_ver: &str,
|
||||
p_ver: &str,
|
||||
loader: &str,
|
||||
mr_id: &str,
|
||||
cf_id: &str,
|
||||
) -> Result<()> {
|
||||
let filename_base = format!("{p_name}-{mc_ver}-{loader}-{p_ver}");
|
||||
|
||||
let mut jobs: Vec<(Platform, PathBuf)> = Vec::new();
|
||||
for platform in Platform::ALL {
|
||||
for (platform, id) in [(Platform::Modrinth, mr_id), (Platform::Curseforge, cf_id)] {
|
||||
if id.is_empty() {
|
||||
continue;
|
||||
}
|
||||
let target_folder = format!("{mc_ver}-{}", platform.short());
|
||||
let target_path = p_dir.join(&target_folder);
|
||||
if target_path.exists() {
|
||||
@@ -129,7 +138,7 @@ fn build_modpack(
|
||||
}
|
||||
|
||||
if jobs.is_empty() {
|
||||
bail!("no platform folders (mc_ver-mr / mc_ver-cf) found");
|
||||
bail!("no platform folders (mc_ver-mr / mc_ver-cf) found matching manifest");
|
||||
}
|
||||
|
||||
let mut handles = Vec::new();
|
||||
@@ -211,6 +220,7 @@ struct OutputData<'a> {
|
||||
mc_ver: &'a str,
|
||||
p_type: &'a str,
|
||||
loader: &'a str,
|
||||
release_type: &'a str,
|
||||
p_dir: &'a Path,
|
||||
}
|
||||
|
||||
@@ -230,6 +240,7 @@ fn write_outputs(d: OutputData) -> Result<()> {
|
||||
writeln!(f, "mc={}", d.mc_ver)?;
|
||||
writeln!(f, "type={}", d.p_type)?;
|
||||
writeln!(f, "loader={}", d.loader)?;
|
||||
writeln!(f, "release_type={}", d.release_type)?;
|
||||
writeln!(f, "path={}", d.p_dir.display())?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
use somnus_core::lint_changed_files;
|
||||
use somnus_core::linter::lint_changed_files;
|
||||
|
||||
const WATCHED_PREFIXES: &[&str] = &["modpacks/", "datapacks/"];
|
||||
const WATCHED_EXTS: &[&str] = &[".json", ".mcmeta"];
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use somnus_core::lint_changed_files;
|
||||
use somnus_core::linter::lint_changed_files;
|
||||
|
||||
const WATCHED_PREFIXES: &[&str] = &["modpacks/", "datapacks/"];
|
||||
|
||||
|
||||
57
tools/manifest/schema.json
Normal file
57
tools/manifest/schema.json
Normal file
@@ -0,0 +1,57 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "Pack Manifest",
|
||||
"description": "Defines a publishable pack in the monorepo",
|
||||
"type": "object",
|
||||
"required": ["id", "name", "type", "loader", "mc_version", "version", "release_type"],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"$schema": {
|
||||
"type": "string",
|
||||
"description": "Path to this schema file."
|
||||
},
|
||||
"id": {
|
||||
"type": "string",
|
||||
"description": "Matches the directory name",
|
||||
"minLength": 1
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "Human-readable pack name shown on Modrinth/CurseForge. Ie. Re-Console Plus",
|
||||
"minLength": 1
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"description": "Pack type. 'modpack' exports via packwiz, 'datapack' zips the content directory",
|
||||
"enum": ["modpack", "datapack"]
|
||||
},
|
||||
"loader": {
|
||||
"type": "string",
|
||||
"description": "Mod loader this pack targets (e.g., 'fabric', 'neoforge', 'forge', 'quilt')",
|
||||
"minLength": 1
|
||||
},
|
||||
"mc_version": {
|
||||
"type": "string",
|
||||
"description": "Target Minecraft version (e.g., '1.21.10')",
|
||||
"minLength": 1
|
||||
},
|
||||
"version": {
|
||||
"type": "string",
|
||||
"description": "Pack version.",
|
||||
"minLength": 1
|
||||
},
|
||||
"release_type": {
|
||||
"type": "string",
|
||||
"description": "Release channel",
|
||||
"enum": ["release", "beta", "alpha"]
|
||||
},
|
||||
"modrinth_id": {
|
||||
"type": "string",
|
||||
"description": "Modrinth project ID or slug (e.g., 'legacy-minecraft'). Leave empty if not publishing to Modrinth"
|
||||
},
|
||||
"curseforge_id": {
|
||||
"type": "string",
|
||||
"description": "CurseForge project slug or numeric ID (e.g., 're-console'). Leave empty if not publishing to CurseForge"
|
||||
}
|
||||
}
|
||||
}
|
||||
112
tools/manifest/validate.ts
Normal file
112
tools/manifest/validate.ts
Normal file
@@ -0,0 +1,112 @@
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
interface Manifest {
|
||||
id: string;
|
||||
name: string;
|
||||
type: 'modpack' | 'datapack';
|
||||
loader: string;
|
||||
mc_version: string;
|
||||
version: string;
|
||||
release_type: 'release' | 'beta' | 'alpha';
|
||||
modrinth_id?: string;
|
||||
curseforge_id?: string;
|
||||
}
|
||||
|
||||
function fail(msg: string): never {
|
||||
console.error(`::error::${msg}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
function warn(msg: string): void {
|
||||
console.warn(`::warning::${msg}`);
|
||||
}
|
||||
|
||||
function validate(manifestPath: string): void {
|
||||
if (!fs.existsSync(manifestPath)) {
|
||||
fail(`manifest not found: ${manifestPath}`);
|
||||
}
|
||||
|
||||
let manifest: Manifest;
|
||||
try {
|
||||
const raw = fs.readFileSync(manifestPath, 'utf-8');
|
||||
manifest = JSON.parse(raw);
|
||||
} catch (e) {
|
||||
fail(`failed to parse ${manifestPath}: ${e instanceof Error ? e.message : e}`);
|
||||
}
|
||||
|
||||
const required = [
|
||||
'id', 'name', 'type', 'loader', 'mc_version', 'version', 'release_type',
|
||||
] as const;
|
||||
for (const field of required) {
|
||||
const v = manifest[field];
|
||||
if (v === undefined || v === null || v === '') {
|
||||
fail(`manifest missing required field: ${field}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (!['modpack', 'datapack'].includes(manifest.type)) {
|
||||
fail(`invalid 'type': ${manifest.type} (must be 'modpack' or 'datapack')`);
|
||||
}
|
||||
if (!['release', 'beta', 'alpha'].includes(manifest.release_type)) {
|
||||
fail(`invalid 'release_type': ${manifest.release_type} (must be 'release', 'beta', or 'alpha')`);
|
||||
}
|
||||
|
||||
const hasMr = manifest.modrinth_id && manifest.modrinth_id.trim() !== '';
|
||||
const hasCf = manifest.curseforge_id && manifest.curseforge_id.trim() !== '';
|
||||
if (!hasMr && !hasCf) {
|
||||
fail('manifest must set at least one of modrinth_id or curseforge_id');
|
||||
}
|
||||
|
||||
const packDir = path.dirname(manifestPath);
|
||||
|
||||
const changelogPath = path.join(packDir, 'changelog.md');
|
||||
if (!fs.existsSync(changelogPath)) {
|
||||
fail(`changelog.md is missing at ${changelogPath}`);
|
||||
}
|
||||
const changelog = fs.readFileSync(changelogPath, 'utf-8').trim();
|
||||
if (changelog === '') {
|
||||
fail(`changelog.md is empty at ${changelogPath}`);
|
||||
}
|
||||
const contentLines = changelog.split('\n').filter(l => l.trim() && !l.trim().startsWith('#'));
|
||||
if (contentLines.length === 0) {
|
||||
fail(`changelog.md has headers but no content at ${changelogPath}`);
|
||||
}
|
||||
|
||||
if (manifest.type === 'modpack') {
|
||||
const mr = path.join(packDir, `${manifest.mc_version}-mr`);
|
||||
const cf = path.join(packDir, `${manifest.mc_version}-cf`);
|
||||
|
||||
if (hasMr && !fs.existsSync(mr)) {
|
||||
fail(`modrinth_id is set but ${mr} does not exist`);
|
||||
}
|
||||
if (hasCf && !fs.existsSync(cf)) {
|
||||
fail(`curseforge_id is set but ${cf} does not exist`);
|
||||
}
|
||||
if (fs.existsSync(mr) && !hasMr) {
|
||||
warn(`${mr} exists but modrinth_id is not set`);
|
||||
}
|
||||
if (fs.existsSync(cf) && !hasCf) {
|
||||
warn(`${cf} exists but curseforge_id is not set`);
|
||||
}
|
||||
}
|
||||
|
||||
if (manifest.type === 'datapack') {
|
||||
const content = path.join(packDir, 'content');
|
||||
if (!fs.existsSync(content)) {
|
||||
fail(`datapack content directory missing: ${content}`);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`${manifest.id} ${manifest.version} (${manifest.release_type}) — manifest OK`);
|
||||
}
|
||||
|
||||
const args = process.argv.slice(2);
|
||||
if (args.length === 0) {
|
||||
console.error('usage: tsx validate.ts <path/to/manifest.json> [more manifests...]');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
for (const manifestPath of args) {
|
||||
validate(manifestPath);
|
||||
}
|
||||
Reference in New Issue
Block a user