Skip to content

Commit

Permalink
Misc website and template improvements (#68)
Browse files Browse the repository at this point in the history
* Misc website and template improvements

* Add more options to the cli, currently a beta so hidden

* Enable datagen by default

* Support 1.20.4
  • Loading branch information
modmuss50 authored Dec 6, 2023
1 parent acbf90b commit f37ed3e
Show file tree
Hide file tree
Showing 9 changed files with 171 additions and 50 deletions.
137 changes: 120 additions & 17 deletions cli/commands/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,33 +20,74 @@ const KOTLIN_ADVANCED_OPTION = "Kotlin Programming Language";
const DATAGEN_ADVANCED_OPTION = "Data Generation";
const SPLIT_ADVANCED_OPTION = "Split client and common sources";

const ADVANCED_OPTIONS: Map<string, string> = new Map([
["kotlin", KOTLIN_ADVANCED_OPTION],
["datagen", DATAGEN_ADVANCED_OPTION],
["splitSources", SPLIT_ADVANCED_OPTION],
]);

interface CliOptions {
defaultOptions?: true;
name?: string;
modid?: string;
packageName?: string;
version?: string;
option?: (string | true)[];
}

const optionArg = {
conflicts: ["defaultOptions"],
// TODO hidden for now, as these are in beta and may change.
hidden: true,
};

export function initCommand() {
return new Command()
.name("init")
.description("Generate a new fabric project")
.option("-y, --defaultOptions", "Generate a mod with default options")
.option("-n, --name <name:string>", "The name of the mod", optionArg)
.option("-m, --modid <modid:string>", "The modid of the mod", optionArg)
.option(
"-p, --packageName <packageName:string>",
"The package name of the mod",
optionArg,
)
.option(
"-v, --version <version:string>",
"The minecraft version",
optionArg,
)
.option(
"-o, --option [advancedOption:string]",
"Specify an advanced option, one of" +
Object.keys(ADVANCED_OPTIONS).join(","),
{
...optionArg,
collect: true,
},
)
.arguments("[dir:file]")
.action(async ({ defaultOptions }, dir: string | undefined) => {
await generate(defaultOptions == true, dir);
.action(async (options, dir: string | undefined) => {
await generate(options, dir);
});
}

async function generate(
useDefaultOptions: boolean,
cli: CliOptions,
outputDirName: string | undefined,
) {
const outputDir = await getAndPrepareOutputDir(outputDirName);

const isTargetEmpty = await utils.isDirEmpty(outputDir);
if (!isTargetEmpty) {
console.error(error("The target directory must be empty"));
Deno.exit(1);
fatalError("The target directory must be empty");
}

const config =
await (useDefaultOptions
await (cli.defaultOptions
? defaultOptions(path.basename(outputDir))
: promptUser(path.basename(outputDir)));
: promptUser(path.basename(outputDir), cli));

const options: generator.Options = {
config,
Expand Down Expand Up @@ -80,17 +121,20 @@ async function getAndPrepareOutputDir(

async function promptUser(
startingName: string,
cli: CliOptions,
): Promise<generator.Configuration> {
// Store a promise for now, so the request can be made while taking the other inputs.
const minecraftVersionsPromise = generator.getTemplateGameVersions();

const modName: string = await Input.prompt({
validateCliOptions(cli);

const modName: string = cli.name ?? await Input.prompt({
message: "Choose a name",
default: startingName,
minLength: 2,
});

const modId: string = await Input.prompt({
const modId: string = cli.modid ?? await Input.prompt({
message: "Choose a unique modid",
default: generator.nameToModId(modName),
minLength: 2,
Expand All @@ -105,20 +149,59 @@ async function promptUser(
},
});

const packageName: string = await Input.prompt({
const packageName: string = cli.packageName ?? await Input.prompt({
message: "Choose a package name",
default: modId,
default: generator.formatPackageName(modId),
transform: (value) => {
return generator.formatPackageName(value);
},
validate: (value) => {
const errors = generator.computePackageNameErrors(value);

if (errors.length == 0) {
return true;
}

return errors.join(", ");
},
});

const minecraftVersion: string = await Select.prompt({
message: "Select the minecraft version",
options: (await minecraftVersionsPromise).map((v) => v.version),
const minecraftVersions = await minecraftVersionsPromise;
let minecraftVersion: string;

if (cli.version != undefined) {
minecraftVersion = cli.version;

if (!minecraftVersions.map((v) => v.version).includes(minecraftVersion)) {
fatalError(`The minecraft version ${minecraftVersion} does not exist.`);
}
} else {
minecraftVersion = await Select.prompt({
message: "Select the minecraft version",
options: minecraftVersions.map((v) => v.version),
});
}

const cliOptions = cli.option?.map((o): string => {
if (o === true) {
fatalError("Advanced options must be specified with a value");
return "unreachable";
}

const option = o as string;

if (!ADVANCED_OPTIONS.has(option)) {
fatalError(
`Unknown option ${o} must be one of: ${
Array.from(ADVANCED_OPTIONS.keys()).join(", ")
}`,
);
}

return ADVANCED_OPTIONS.get(option)!;
});

const advancedOptions = await Checkbox.prompt({
const advancedOptions = cliOptions ?? await Checkbox.prompt({
message: "Advanced options",
options: getAdancedOptions(minecraftVersion),
});
Expand All @@ -134,6 +217,22 @@ async function promptUser(
};
}

function validateCliOptions(cli: CliOptions) {
if (cli.modid != undefined) {
const errors = generator.computeCustomModIdErrors(cli.modid);
if (errors != undefined) {
fatalError(errors.join(", "));
}
}

if (cli.packageName != undefined) {
const errors = generator.computePackageNameErrors(cli.packageName);
if (errors.length > 0) {
fatalError(errors.join(", "));
}
}
}

async function defaultOptions(
startingName: string,
): Promise<generator.Configuration> {
Expand Down Expand Up @@ -240,8 +339,12 @@ async function requestPermissions(outputDir: string) {
const status = await Deno.permissions.request(permission);

if (status.state != "granted") {
console.error(error("Permission not granted"));
Deno.exit(1);
fatalError("Permission not granted");
}
}
}

function fatalError(message: string) {
console.error(error(message));
Deno.exit(1);
}
2 changes: 1 addition & 1 deletion cli/main.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/usr/bin/env -S deno run --allow-net
#!/usr/bin/env -S deno run -A

// @deno-types="../scripts/dist/fabric-template-generator.d.ts"
import * as generator from "../scripts/dist/fabric-template-generator.js";
Expand Down
3 changes: 2 additions & 1 deletion scripts/src/lib.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './lib/Api';
export * from './lib/template/template';
export * from './lib/template/minecraft';
export * from './lib/template/minecraft';
export * from './lib/template/java';
2 changes: 2 additions & 0 deletions scripts/src/lib/Api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,8 @@ export function isApiVersionvalidForMcVersion(apiVersion: string, mcVersion: str
branch = "1.20.2"
} else if (mcVersion.startsWith("1.20.3")) {
branch = "1.20.3"
} else if (mcVersion.startsWith("1.20.4")) {
branch = "1.20.4"
} else if (mcVersion.startsWith("21w")) {
branch = "1.18"
} else if (mcVersion.startsWith("20w")) {
Expand Down
20 changes: 13 additions & 7 deletions scripts/src/lib/Template.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import DownloadIcon from "./DownloadIcon.svelte";
import { getTemplateGameVersions } from "./template/template";
import { minecraftSupportsDataGen, minecraftSupportsSplitSources, computeCustomModIdErrors, sharedModIdChecks, formatPackageName, nameToModId} from "./template/minecraft";
import { computePackageNameErrors } from "./template/java"
let minecraftVersion: string;
let projectName = "Template Mod";
Expand All @@ -30,6 +31,7 @@
$: modIdErrors = computeModIdErrors(modid);
$: customIdErrors = computeCustomModIdErrors(customModId);
$: packageNameErrors = computePackageNameErrors(packageName);
function computeModIdErrors(id: string | undefined) : string[] | undefined {
if (id === undefined) {
Expand All @@ -40,7 +42,7 @@
}
async function generate() {
if (modIdErrors !== undefined || (customModId !== undefined && customIdErrors !== undefined)) {
if (modIdErrors !== undefined || (customModId !== undefined && customIdErrors !== undefined) || packageNameErrors.length > 0) {
return;
}
Expand Down Expand Up @@ -112,11 +114,11 @@
<input id="project-name" bind:value={projectName} on:keyup={doFormatProjectName} />

{#if modIdErrors != undefined}
{#each modIdErrors as error}
<li style="color: red">{error}</li>
{/each}
<br>
{/if}
{#each modIdErrors as error}
<li style="color: red">{error}</li>
{/each}
<br>
{/if}
</div>

{#if customModId != undefined}
Expand All @@ -143,6 +145,10 @@
should be unique to you. If you are unsure about this use <code>name.modid</code>.
</p>
<input id="package-name" on:keyup={doFormatPackageName} bind:value={packageName} />

{#each packageNameErrors as error}
<li style="color: red">{error}</li>
{/each}
</div>

<div class="form-line">
Expand Down Expand Up @@ -213,7 +219,7 @@
</a>
{:else}
<a
class="button primary download-button"
class="button primary large download-button"
href={""}
on:click|preventDefault={generate}
>
Expand Down
3 changes: 2 additions & 1 deletion scripts/src/lib/Versions.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
let gameVersions = getGameVersions().then((versions) => {
minecraftVersion = versions.find((v) => v.stable)!.version
return versions.map((v) => v.version);
const latestVersion = versions[0];
return versions.filter((v) => v.stable || v == latestVersion).map((v) => v.version);
});
const loaderVersions = getLoaderVersions().then((versions) => {
Expand Down
21 changes: 21 additions & 0 deletions scripts/src/lib/template/java.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,25 @@ export function getJavaVersion(minecraftVersion: string): JavaVersion {
}

return JAVA_17;
}

const JAVA_PACKAGE_REGEX = /^[a-zA-Z]+(\.[a-zA-Z][a-zA-Z0-9]*)*$/;
const RESERVED_PACKAGE_PREFIXES = ["net.minecraft.", "com.mojang.", "net.fabricmc.", "java."];

export function computePackageNameErrors(packageName: string): string[] {
let errorList : string[] = [];

if (!JAVA_PACKAGE_REGEX.test(packageName)) {
errorList.push("Package name is not a valid Java package name!");
}

for (let prefix of RESERVED_PACKAGE_PREFIXES) {
if (packageName.toLowerCase().startsWith(prefix)) {
errorList.push(`Package name starts with '${prefix}', which is reserved!`);
} else if (packageName.toLowerCase() + "." == prefix) {
errorList.push(`Package name is '${prefix}', which is reserved!`);
}
}

return errorList;
}
4 changes: 4 additions & 0 deletions scripts/src/lib/template/minecraft.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ export function sharedModIdChecks(id: string, isId: boolean): string[] | undefin
errorList.push(`${type} has more than 64 characters!`);
}

if (id.toLocaleLowerCase().startsWith("fabric")) {
errorList.push("Mod id starts with 'fabric', which is generally reserved for Fabric itself.")
}

return errorList.length === 0 ? undefined : errorList;
}

Expand Down
29 changes: 6 additions & 23 deletions scripts/src/lib/template/templates/gradle/groovy/build.gradle.eta
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ repositories {
// See https://docs.gradle.org/current/userguide/declaring_repositories.html
// for more information about repositories.
}
<% if (it.dataGeneration || it.splitSources) { %>
<% if (it.splitSources) { %>
loom {
<% if (it.splitSources) { %> splitEnvironmentSourceSets()

Expand All @@ -30,30 +30,13 @@ loom {
sourceSet sourceSets.client
}
}
<% } %><% if (it.dataGeneration) { %> runs {
// This adds a new gradle task that runs the datagen API: "gradlew runDatagen"
datagen {
inherit server
name "Data Generation"
vmArg "-Dfabric-api.datagen"
vmArg "-Dfabric-api.datagen.output-dir=${file("src/main/generated")}"
vmArg "-Dfabric-api.datagen.modid=<%= it.modid %>"

runDir "build/datagen"
}
}<% } %>
<% } %>
}
<% } %><% if (it.dataGeneration) { %>
// Add the generated resources to the main source set
sourceSets {
main {
resources {
srcDirs += [
'src/main/generated'
]
}
}
}<% } %>
fabricApi {
configureDataGeneration()
}
<% } %>
dependencies {
// To change the versions see the gradle.properties file
minecraft "com.mojang:minecraft:${project.minecraft_version}"
Expand Down

0 comments on commit f37ed3e

Please sign in to comment.