From d40c3b82e3843af18dbc1a1330e0a07ef8e96dfb Mon Sep 17 00:00:00 2001 From: Moritz Ruth Date: Thu, 18 Aug 2022 14:15:31 +0200 Subject: [PATCH] Implement modrinth export --- package.json | 1 + pnpm-lock.yaml | 22 +++++++++ src/commands/init.ts | 5 ++- src/commands/modrinth/export.ts | 41 +++++++++++++++++ src/commands/modrinth/index.ts | 2 + src/files.ts | 1 + src/modrinth/exporting.ts | 60 +++++++++++++++++++++++++ src/pack.ts | 7 ++- test-pack/.gitignore | 2 +- test-pack/horizr.json | 1 + test-pack/src/client/resourcepacks/test | 1 + 11 files changed, 139 insertions(+), 4 deletions(-) create mode 100644 src/commands/modrinth/export.ts create mode 100644 src/modrinth/exporting.ts create mode 100644 test-pack/src/client/resourcepacks/test diff --git a/package.json b/package.json index 33a8538..2712e6f 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ ], "dependencies": { "@root/walk": "^1.1.0", + "@sindresorhus/slugify": "^2.1.0", "address": "^1.2.0", "commander": "^9.4.0", "dedent": "^0.7.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 789ba4a..19fa08e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2,6 +2,7 @@ lockfileVersion: 5.4 specifiers: '@root/walk': ^1.1.0 + '@sindresorhus/slugify': ^2.1.0 '@types/dedent': ^0.7.0 '@types/fs-extra': ^9.0.13 '@types/lodash-es': ^4.17.6 @@ -44,6 +45,7 @@ specifiers: dependencies: '@root/walk': 1.1.0 + '@sindresorhus/slugify': 2.1.0 address: 1.2.0 commander: 9.4.0 dedent: 0.7.0 @@ -166,6 +168,22 @@ packages: engines: {node: '>=14.16'} dev: false + /@sindresorhus/slugify/2.1.0: + resolution: {integrity: sha512-gU3Gdm/V167BmUwIn8APHZ3SeeRVRUSOdXxnt7Q/JkUHLXaaTA/prYmoRumwsSitJZWUDYMzDWdWgrOdvE8IRQ==} + engines: {node: '>=12'} + dependencies: + '@sindresorhus/transliterate': 1.5.0 + escape-string-regexp: 5.0.0 + dev: false + + /@sindresorhus/transliterate/1.5.0: + resolution: {integrity: sha512-/sfSkoNelLq5riqNRp5uBjHIKBi1MWZk9ubRT1WiBQuTfmDf7BeQkph2DJzRB83QagMPHk2VDjuvpy0VuwyzdA==} + engines: {node: '>=12'} + dependencies: + escape-string-regexp: 5.0.0 + lodash.deburr: 4.1.0 + dev: false + /@szmarczak/http-timer/5.0.1: resolution: {integrity: sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==} engines: {node: '>=14.16'} @@ -1162,6 +1180,10 @@ packages: resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==} dev: false + /lodash.deburr/4.1.0: + resolution: {integrity: sha512-m/M1U1f3ddMCs6Hq2tAsYThTBDaAKFDX3dwDo97GEYzamXi9SqUpjWi/Rrj/gf3X2n8ktwgZrlP1z6E3v/IExQ==} + dev: false + /log-symbols/5.1.0: resolution: {integrity: sha512-l0x2DvrW294C9uDCoQe1VSU4gf529FkSZ6leBl4TiqZH/e+0R7hSfHQBNut2mNygDgHwvYHfFLn6Oxb3VWj2rA==} engines: {node: '>=12'} diff --git a/src/commands/init.ts b/src/commands/init.ts index 654721e..b920948 100644 --- a/src/commands/init.ts +++ b/src/commands/init.ts @@ -7,6 +7,8 @@ import { fetchFabricMinecraftVersions, fetchFabricVersions } from "../fabricApi. import enquirer from "enquirer" import { PACK_MANIFEST_FILE_NAME, PACK_MANIFEST_FORMAT_VERSION, PackManifest } from "../files.js" import pathModule from "path" +import { EXPORTS_DIRECTORY_NAME } from "../pack.js" +import slugify from "@sindresorhus/slugify" export const initCommand = new Command("init") .argument("") @@ -62,6 +64,7 @@ export const initCommand = new Command("init") const file: PackManifest = { formatVersion: PACK_MANIFEST_FORMAT_VERSION, + slug: slugify(answers.name), meta: { name: answers.name, version: "1.0.0", @@ -76,7 +79,7 @@ export const initCommand = new Command("init") } await fs.writeJson(manifestFilePath.toString(), file, { spaces: 2 }) - await fs.writeFile(path.resolve(".gitignore").toString(), "/generated/") + await fs.writeFile(path.resolve(".gitignore").toString(), `/${EXPORTS_DIRECTORY_NAME}/`) output.println(kleur.green(`Successfully initialized pack in ${kleur.yellow(pathModule.normalize(pathString))}`)) }) diff --git a/src/commands/modrinth/export.ts b/src/commands/modrinth/export.ts new file mode 100644 index 0000000..d4989ac --- /dev/null +++ b/src/commands/modrinth/export.ts @@ -0,0 +1,41 @@ +import { Command } from "commander" +import { usePack } from "../../pack.js" +import { output } from "../../utils/output.js" +import fs from "fs-extra" +import { generateOutputDirectory } from "../../modrinth/exporting.js" +import kleur from "kleur" +import { zipDirectory } from "../../utils/zip.js" + +const EXPORT_OUTPUT_DIRECTORY_NAME = "modrinth" + +export const exportCommand = new Command("export") + .option("-s, --no-generate", "Skip regenerating the output directory.") + .option("-z, --no-zip", "Skip creating a zipped .mrpack file.") + .option("-c, --clean", "Remove the output directory afterwards.") + .action(async options => { + const pack = await usePack() + const outputDirectoryPath = pack.paths.exports.resolve(EXPORT_OUTPUT_DIRECTORY_NAME) + + const loader = output.startLoading("Exporting") + + if (options.generate) { + await output.withLoading(generateOutputDirectory(outputDirectoryPath), "Generating the output directory") + output.println(kleur.green(`Generated Modrinth pack directory.`)) + } + + if (options.zip) { + const fileName = `${pack.manifest.slug}-${pack.manifest.meta.version}.mrpack` + if (!(await fs.pathExists(outputDirectoryPath.toString()))) + output.failAndExit(`The ${kleur.yellow(EXPORT_OUTPUT_DIRECTORY_NAME)} export directory does not exist.\nRun the command without ${kleur.yellow("--no-generate")} to create it.`) + + await output.withLoading(zipDirectory(outputDirectoryPath, pack.paths.exports.resolve(fileName)), `Creating ${kleur.yellow(".mrpack")} file`) + output.println(kleur.green(`Created ${kleur.yellow(fileName)}`)) + } + + if (options.clean) { + await fs.remove(outputDirectoryPath.toString()) + output.println(kleur.green(`Removed the ${kleur.yellow(EXPORT_OUTPUT_DIRECTORY_NAME)} directory.`)) + } + + loader.stop() + }) diff --git a/src/commands/modrinth/index.ts b/src/commands/modrinth/index.ts index 2a31284..28660dd 100644 --- a/src/commands/modrinth/index.ts +++ b/src/commands/modrinth/index.ts @@ -3,10 +3,12 @@ import { activateCommand } from "./activate.js" import dedent from "dedent" import kleur from "kleur" import { openCommand } from "./open.js" +import { exportCommand } from "./export.js" export const modrinthCommand = new Command("modrinth") .alias("mr") .addCommand(activateCommand) + .addCommand(exportCommand) .addCommand(openCommand) .addHelpText("afterAll", dedent` \n${kleur.yellow("")} may be one of the following: diff --git a/src/files.ts b/src/files.ts index c669230..19c3681 100644 --- a/src/files.ts +++ b/src/files.ts @@ -49,6 +49,7 @@ export const PACK_MANIFEST_FILE_NAME = "horizr.json" export const horizrFileSchema = z.object({ formatVersion: z.literal(PACK_MANIFEST_FORMAT_VERSION), + slug: z.string(), meta: z.object({ name: z.string(), version: z.string(), diff --git a/src/modrinth/exporting.ts b/src/modrinth/exporting.ts new file mode 100644 index 0000000..5338717 --- /dev/null +++ b/src/modrinth/exporting.ts @@ -0,0 +1,60 @@ +import { AbsolutePath } from "../utils/path.js" +import { output } from "../utils/output.js" +import fs from "fs-extra" +import { Side, usePack } from "../pack.js" +import kleur from "kleur" + +const overridesDirectoryNameBySide: Record = { + client: "client-overrides", + server: "server-overrides", + universal: "overrides" +} + +export async function generateOutputDirectory(outputDirectoryPath: AbsolutePath) { + const pack = await usePack() + await fs.remove(outputDirectoryPath.toString()) + await fs.mkdirp(outputDirectoryPath.toString()) + + await fs.writeJson(outputDirectoryPath.resolve("modrinth.index.json").toString(), { + formatVersion: 1, + game: "minecraft", + versionId: pack.manifest.meta.version, + name: pack.manifest.meta.name, + summary: pack.manifest.meta.description, + dependencies: { + minecraft: pack.manifest.versions.minecraft, + "fabric-loader": pack.manifest.versions.fabric + }, + files: pack.metaFiles.map(metaFile => ({ + path: metaFile.effectivePath.toString(), + hashes: { + sha1: metaFile.content.version.hashes.sha1, + sha512: metaFile.content.version.hashes.sha512 + }, + env: { + client: metaFile.side === "client" || metaFile.side === "universal" ? "required" : "unsupported", + server: metaFile.side === "server" || metaFile.side === "universal" ? "required" : "unsupported" + }, + downloads: [ + metaFile.content.version.downloadUrl + ], + fileSize: metaFile.content.version.size + })) + }, { spaces: 2 }) + + let i = 0 + for (const staticSourceFile of pack.staticSourceFiles) { + i++ + const loader = output.startLoading(`Exporting static source file (${i}/${pack.staticSourceFiles.length}): ${kleur.yellow(staticSourceFile.relativePath.toString())}`) + const outputPath = outputDirectoryPath.resolve(overridesDirectoryNameBySide[staticSourceFile.side], staticSourceFile.effectivePath) + await fs.mkdirp(outputPath.parent().toString()) + await fs.copy(staticSourceFile.absolutePath.toString(), outputPath.toString()) + + // Workaround for https://github.com/PolyMC/PolyMC/issues/1060 + if (staticSourceFile.side === "client") { + await fs.mkdirp(outputDirectoryPath.resolve(overridesDirectoryNameBySide.universal, staticSourceFile.effectivePath).parent().toString()) + } + + loader.stop() + } +} diff --git a/src/pack.ts b/src/pack.ts index cbb5ba8..f540a2e 100644 --- a/src/pack.ts +++ b/src/pack.ts @@ -73,6 +73,8 @@ export interface Update { apply(): Promise } +export const EXPORTS_DIRECTORY_NAME = "exports" + let pack: Pack export async function usePack(): Promise { if (pack === undefined) { @@ -97,7 +99,7 @@ export async function usePack(): Promise { side: pathSegments[0] as Side, relativePath: relativePath, absolutePath: sourceDirectoryPath.resolve(relativePath), - effectivePath: RelativePath._createDirect(pathSegments.slice(1).join("/")), + effectivePath: RelativePath._createDirect(pathSegments.slice(1).join("/")) } if (relativePath.toString().endsWith("." + META_FILE_EXTENSION)) { @@ -107,6 +109,7 @@ export async function usePack(): Promise { const metaFile: MetaFile = { ...sourceFile, isStatic: false, + effectivePath: sourceFile.effectivePath.parent().joinedWith(content.version.fileName), content, fetchUpdates: source?.type === "modrinth" ? allowedReleaseChannels => fetchModrinthModUpdates(metaFile, source, allowedReleaseChannels, manifest.versions.minecraft) @@ -134,7 +137,7 @@ export async function usePack(): Promise { paths: { root: rootDirectoryPath, source: sourceDirectoryPath, - exports: rootDirectoryPath.resolve("exports") + exports: rootDirectoryPath.resolve(EXPORTS_DIRECTORY_NAME) }, manifest, metaFiles, diff --git a/test-pack/.gitignore b/test-pack/.gitignore index 9e0adcc..0f35b4f 100644 --- a/test-pack/.gitignore +++ b/test-pack/.gitignore @@ -1 +1 @@ -/generated/ +/exports/ diff --git a/test-pack/horizr.json b/test-pack/horizr.json index f08aee3..da06ef3 100644 --- a/test-pack/horizr.json +++ b/test-pack/horizr.json @@ -1,5 +1,6 @@ { "formatVersion": 1, + "slug": "test", "meta": { "name": "Test", "version": "1.0.0", diff --git a/test-pack/src/client/resourcepacks/test b/test-pack/src/client/resourcepacks/test new file mode 100644 index 0000000..d95f3ad --- /dev/null +++ b/test-pack/src/client/resourcepacks/test @@ -0,0 +1 @@ +content