New Buildscript (#434)

/* Category: */
[INTERNAL]

/* Fixup for previous commits' bad syntax: */
[FIXUP]
[[fixes]]
sha = "4f966073890315ae0eb103b6011cdac7e6e960c0"
newTitle = "Update GregTech CEu to v2.7.4"
newBody = """
  [COMBINE]
  commits = ["bd58b9072f45d647734ae66168cbd27bf9b2f220"]
  [COMBINE]
  """

[[fixes]]
sha = "bd58b9072f45d647734ae66168cbd27bf9b2f220"
newTitle = "Update GT and Related Mods for 1.7"
newBody = """
  [EXPAND]
  [[messages]]
    messageTitle = \"Update GT to 2.7.4\"
    messageBody = \"\"\"
      [BREAKING]
      [DETAILS]
      details = [
        \\"**Lots of Recipes have been moved to the Assembly Line, and now require Assembly Line Research.**\\",
        \\"Adds Assembly Line Research\\",
        \\"Adds ME Hatches and Buses, for combining Multiblocks with AE Networks\\",
        \\"Adds EU Multiblock Power Storage\\",
        \\"Adds Multiblock Transformer and Laser Power Transfer\\",
        \\"Adds Long Distance Pipes\\",
        \\"Reworked & Improved Multiblock UIs\\",
        \\"Creating Waypoints in the Prospector\\",
        \\"Fixing many bugs\\",
        \\"And many more!\\"
      ]
      [DETAILS]
    \"\"\"
  [EXPAND]
  """

[[fixes]]
sha = "1a40bd87f9c648429059c77a90470e55e0e39c5e"
newTitle = "Update QB Jsons and Lang"
newBody = "[SKIP]"

[[fixes]]
sha = "bcae6f4eebf5616766edcc3e735df7cbf6276440"
newTitle = "Russian Content Tweaker Translation (#429)"
newBody = "[FEATURE]"

[[fixes]]
sha = "ca64e658083d5ff41f15ce37fe817842018031d3"
newTitle = "Update NAE2 to v1.3.1 (#424)"
newBody = "[BUG]"

[[fixes]]
sha = "1a35ae82d9830c5444c5634d93268e3b857f07bd"
newTitle = "Fix Description of The First Tier Two Circuits Quest"
newBody = "[QB]"

[[fixes]]
sha = "c66f5428818e02051b77d84fa01792bf2a6d9dcf"
newTitle = "Make Neeve Quest Ignore NBT"
newBody = "[QB]"

[[fixes]]
sha = "5b5d13710abe0f96c41ba4a3505969c0a98e23bc"
newTitle = "Update and Rewrite Mixer Quest"
newBody = "[QB]\n[HM]"

[[fixes]]
sha = "342d988e322a45e0d141227c9aa34ec8e53663e7"
newTitle = "Remove Mention of HV Batteries in Iron Quest"
newBody = "[QB]\n[HM]"

[[fixes]]
sha = "d037ee5f466cec066fac1d14a762a377426a032d"
newTitle = "Remove Reward for Bending Machine Quest"
newBody = "[QB]\n[HM]"

[[fixes]]
sha = "03d61c8d5fbaa0439fc823b5aef3a4859b17a444"
newTitle = "Move Multiblock Machine Previews Quest"
newBody = """
[QB]
[DETAILS]
details = [\"Moved From `The Beginning` to `Genesis`\"]
[DETAILS]
"""

[[fixes]]
sha = "a52b91d97ac6849fe74623e3cc08362eb3ad99fa"
newTitle = "Change Mold Quest Task from Ball Mold -> Rotor Mold"
newBody = "[QB]\n[HM]"

[[fixes]]
sha = "fcb48d4bc9f27058c6a2dcca7bf813f3d7f6a6d9"
newTitle = "Mention how to Mute Machines in Steam Machines Quest"
newBody = "[QB]\n[HM]"

[[fixes]]
sha = "f59eecad41745f65348da88a3f4c057300459782"
newTitle = "Adjust Steam Dynamo Quest"
newBody = "[QB]\n[HM]"

[[fixes]]
sha = "02eaf67bd168eb11a7a71f4d346eb591841d0af3"
newTitle = "Specify that Alloy Smelter is used to make Rubber Sheets"
newBody = "[QB]\n[HM]"

[[fixes]]
sha = "a988d2df30e4d01b973870cc85350962a9125f5c"
newTitle = "Fix Grammar Errors in 'From Ingots to Wires' Quest"
newBody = "[QB]\n[HM]"

[[fixes]]
sha = "8df9f6e3180323946cc6553cd0fb5e1e96f0ac03"
newTitle = "Add Molds Quest to Genesis, Make Glass Quest"
newBody = "[QB]\n[HM]"

[[fixes]]
sha = "5d51fda1c6740edc5021657f90f6ecd1de954d29"
newTitle = "Make Drawer Quest Accept Either Task"
newBody = "[QB]\n[HM]"

[[fixes]]
sha = "a02ef21fbae24df59e5fa9a5ef4640752baf835f"
newTitle = "New Data Textures (#419)"
newBody = "[FEATURE]"

[[fixes]]
sha = "758f7e704ca8b13031f7ca859e6bc9c0f0a9e786"
newTitle = "Fix DML Multiblock Recipes"
newBody = "[BUG]"

[[fixes]]
sha = "5671bca7fe7b685ac3495ef2497c3711a0baa2a0"
newTitle = "Improve Mac/Linux Pack Mode Switcher"
newBody = "[INTERNAL]"

[[fixes]]
sha = "5538fe4524dcb0b64a333b756bfeeec34e12661c"
newTitle = "Enable shufflemode on main menu (#414)"
newBody = "[FEATURE]"

[[fixes]]
sha = "644491a9f53c0ee300dbaf345f23e5b3f08b3c8a"
newTitle = "Release 1.6.1a"
newBody = "[NO CATEGORY]"
[FIXUP]

/* Description: */
TODO:
- [x] Changelog Generation
  - [x] Decomp Expand
  - [x] Decomp Details
  - [x] Add Commit Details to Mod Changes
  - [x] Allow changing of dir
  - [x] Catch parsing errors
- [x] Move to TOML instead of YAML
- [x] GHA Workflows
  - [x] Split workflows into called and callers (See https://docs.github.com/en/actions/using-workflows/reusing-workflows)
  - [x] Create workflow to generate Release Commit + Changelog, changelog in another branch
  - [x] Create workflow to grab Changelog from another branch, then deploy
  - [x] Create workflow to combine previous two
  - [x] Create workflow to test new commits' body syntax
  - [x] Split workflows into jobs
- [x] Cleanup Code
  - [x] Split code into multiple files 
- [x] Document Code (JS Docs)
- [x] CONTRIBUTING.md Documentation on workflows, and on templates
- [x] Remove changelog files

New TODO:
- [x] Make workflow to update modlist
- [x] Add modlist updating to release commit workflow, split into jobs
- [x] https://discord.com/channels/927050775073534012/1131899853052575785/1162897825173086299
- [ ] Update Documentation

/* Commits: */
* Create releasecommit.yml

* Update releasecommit.yml

* Update setup-node & various tasks to v3, improve .gitignore

* Working method to add version to Issue Templates

Produces weird artifacts...

* Revert "Working method to add version to Issue Templates"

This reverts commit f63480e2eba4b8ef5269ebc0a26cebd146fbe45e.

* WIP method to update release content, using templates

* Finish Release Commit Npx Gulp Task

* Update Files

* Allow for non-release commits

* Actually update version.txt

* Remove unneded env in updateqb.yml

* Fix releasecommit.yml

* Remove random patches config file transforming in `version.ts`

* Remove note about regex

* Add tag to release commits

* Allow changelog task to compare with any commit, seperate changelog task

* Add generation for CF changelog, begin detailed changelog

* Add new formatted commitList

* test change to CHANGELOG.md

* Fix HTML file formatting; test new MD file formatting

* Another HTML File formatting fix

* New formatting logic

* cleanup quest transform npx task

* Cleanup

* Progress

* Small Fixes

* Fix formatting

* Fix commits appearing twice

* Changelog Categories

* Remove Tags from Commit Messages

* New Storage System for ChangelogSections

* Fix Formatting

* Progress of version info for changed mods

* Allow external deps

* Cleanup Version,  Fix Crash, temp change compare to 1.5.2...1.6

* Cleanup code

* Cleanup Code, use templates instead of ifs

* Expand Decomp, fix some commits not being added

* Testing time

* What the changelog looks like

* Test

* Sort logs, with newest on top. Sort Commit Log

* Transform markdown entries into HTML at end

* Remove section matter npm package

* Test

* Update changelog

* Test nested Details

* update changelog files

* Begin Commit SHAs of Mod Changes Section

Just need to add support, in formatting funct, for multi commits

* Test new formatting

* Formatting Test

* Allow for multi commits in changelog messages

* Fix not showing multi commits

* Fix formatting

* formatting

* Add Performance Category

* Remove debug messages + date in changelog messages

* sort multi authors

* Allow for commits to go in multi categories

* Start Cleanup Code

* Allow changing of output dir

* More Cleanup + Create Post 1.7 Version

* Catch parsing errors of manifest.json

View the magical changelog, from the first file commit, to this commit!

* Catch errors in Expand Task, more cleanup

* Catch parsing errors in Details task

* Make UpdateQB Task not return an error if no changes

* Remove Comma Expressions

* Catch edge case parsing errors, improve error descriptions

* Cleanup parsing errors code, display original body

* TOML Test

* TOML

* Delete test.txt

* Update Post 1.7 Version

* Test no error qb task

* Test

* cleanup updateqb.yml

* Formatting

* Update Changelogs (after rebase to TOML syntax)

* Fix Deploy GH Task

* Remove uneeded code

* Allow for fetching/making changelog in build task

* Fix Changelog Fetching

* Change Changelog Output Dir to build root dir

* Add new features to Github Release Task

* Combine Version & GitHub_TAG

* Create GHA files, improve TS code (#446)

* Setup Action

* test

* test again

* Update releasecommit.yml

* Move setup.yml

* Update releasecommit.yml

* Update releasecommit.yml

* Update setup.yml

* Update releasecommit.yml

* Update deploygh.yml

* Delete .github/workflows/setup.yml

* Create build-pack.yml

* Update build-pack.yml

* test

* Update build-pack.yml

* more test

* Stupid me using download job for upload

* more testing...

* Update build-pack.yml

* Update build-pack.yml

* Update build-pack.yml

* Update build-pack.yml

* Create build-pack-manual.yml

* Move file

* Revert "Move file"

This reverts commit 506a523aed2d5d8af21094b943aa2b7e2b7ee688.

* Update build-pack-manual.yml

* Update build-pack-manual.yml

* Create deploy.yml

* Delete .github/workflows/deploygh.yml

* Rename deploy.yml to deploygh.yml

* Update deploygh.yml

* Create deploy.yml

* Update deploy.yml

* Update deploy.yml

* Update deploy.yml

* Update deploygh.yml

* Remove debug logging

* Update build-pack.yml

* Delete .github/workflows/build-pack-manual.yml

* Update deploygh.yml

* Update build-pack.yml

* Update deploy.yml

* Update deploygh.yml

* Update deploygh.yml

* Update build-pack.yml

* Update build-pack.yml

* Update deploy.yml

* Update build-pack.yml

* Update deploygh.yml

* Update deploy.yml

* Update releasecommit.yml

* Release 5.0

* Update releasecommit.yml

* Release 6.0

* Update Issue, Server and RP Config Files from Templates

* Update releasecommit.yml

* Release 7.0

* Add release title in changelog, remove more code

* Add link to full changelog, format release name

* Release 7.1

* Update deploy.yml

* Alpha Release 7.2

* Fix tags excaping, improve full changelog

* Release 7.3

* Add to CF Release, Cleanup Code

* Delete .github/workflows/deploycfrc.yml

* Update deploycf.yml

* Update deploy.yml

* Update gulpfile

* Alpha Release 8.0-alpha-1

* Update deploycf.yml

* Update deploycf.yml

* Fix Curseforge Changelog

* Beta Release 8.0-beta-2

* Update releasecommit.yml

* Rename build-pack.yml to buildpack.yml

* Update deploy.yml

* Add CF redirect

* Fix existing version detection

* Create releasedeploy.yml

* Update releasedeploy.yml

* Update releasedeploy.yml

* Release 8.0

* Update releasedeploy.yml

* Create test.yml

* Delete .github/workflows/test.yml

* Create makechangelog.yml

* Update makechangelog.yml

* Update and rename makechangelog.yml to createchangelog.yml

* Rename: MakeChangelog -> CreateChangelog

* Update createchangelog.yml

* Create releasechangelog.yml

* Update releasechangelog.yml

* Update createchangelog.yml

* Beta Release 9.0-beta-1

* Release 9.0

* Release 9.1

* Update releasechangelog.yml

* Alpha Release 9.1-alpha-1

* Remove extra versions

* Fix more changed version numbers

* Update Issue, Server and RP Config Files from Templates

* Update versions.txt

* Update Issue, Server and RP Config Files from Templates

* Update Post 1.7 Version

* Start writing more in CONTRIBUTING

* Add basic Table Of Contents structure, qb contributing

* test

* Fix wrong html tag

* Final Formatting

* Script contributing information + cleanup

* More Contribution Information

* Begin Maintainer Information

* Improve documentation, make submessages not include author/sha

* Finish Changelog Workflow Documentation

* Create structure

* Write section for create release commit workflow

* Provide a overview table of all keys

* Documentation on Deploy Task

* Artifacts information + start build pack documentation

* test new way to center elements

* Allow for Cutting Edge Build in build task

* Actually use release_type input in build task

* Finish Contributing information

* Remove changelog files

* JS documentation

* MMC zips + allow for build pack task to upload seperately

* test

* test selective artifact method, allow for selectively deploying

* Finish Buildscript (#457)

* mention contributing stuff in wiki

* Transform PR Tags in Message into links
This commit is contained in:
IntegerLimit 2023-10-20 11:00:55 +11:00 committed by GitHub
parent 4f96607389
commit fbd1584e7b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
72 changed files with 4183 additions and 787 deletions

View File

@ -1,3 +1,5 @@
# DO NOT EDIT THIS FILE! EDIT THE TEMPlATES INSTEAD!
# See https://github.com/Nomi-CEu/Nomi-CEu/wiki/Part-1:-Contributing-Information#section-5-template-information!
name: Bug Report
description: "Crashes or unintended behaviors arising from Nomi CEu's mods, configurations, or custom scripts."
labels: bug
@ -11,20 +13,20 @@ body:
label: Nomi CEu Version
description: The version of Nomi CEu you were using when this bug was encountered. If you do not know what it is, check the title of your instance window. If you do not see your version here, please update to the newest version of the pack, which currently is 1.6, or the newest alpha/beta, which currently is 1.6.1-beta-2.
options:
- "1.6.1a"
- "1.6.1-beta-4"
- "1.6.1-beta-3a"
- "1.6.1-beta-2"
- "1.6.1-alpha-1"
- "1.6"
- "1.5.2"
- "1.5.1"
- "1.5"
- "1.4.3"
- "1.4.2"
- "1.4.1a"
- "1.4"
- 1.6.1a
- 1.6.1-beta-4
- 1.6.1-beta-3a
- 1.6.1-beta-2
- 1.6.1-alpha-1
- 1.6
- 1.5.2
- 1.5.1
- 1.5
- 1.4.3
- 1.4.2
- 1.4.1a
- 1.4
validations:
required: true
- type: input

View File

@ -1,3 +1,5 @@
# DO NOT EDIT THIS FILE! EDIT THE TEMPlATES INSTEAD!
# See https://github.com/Nomi-CEu/Nomi-CEu/wiki/Part-1:-Contributing-Information#section-5-template-information!
name: Feature Request
description: Suggest an idea, including mod additions or addon scripts, for Nomi CEu.
labels: enhancement
@ -11,19 +13,20 @@ body:
label: Nomi CEu Version
description: The version of Nomi CEu you are using as the basis for this feature request. If you do not know what it is, check the title of your instance window. If you do not see your version here, please update to the newest version of the pack, which currently is 1.6, or the newest alpha/beta, which currently is 1.6.1-beta-2.
options:
- "1.6.1a"
- "1.6.1-beta-4"
- "1.6.1-beta-3a"
- "1.6.1-beta-2"
- "1.6.1-alpha-1"
- "1.6"
- "1.5.2"
- "1.5.1"
- "1.5"
- "1.4.3"
- "1.4.2"
- "1.4.1a"
- "1.4"
- 1.6.1a
- 1.6.1-beta-4
- 1.6.1-beta-3a
- 1.6.1-beta-2
- 1.6.1-alpha-1
- 1.6
- 1.5.2
- 1.5.1
- 1.5
- 1.4.3
- 1.4.2
- 1.4.1a
- 1.4
validations:
required: true
- type: textarea

181
.github/workflows/buildpack.yml vendored Normal file
View File

@ -0,0 +1,181 @@
name: Build Pack
on:
workflow_dispatch:
inputs:
tag:
description: Tag to Checkout and Release.
required: false
type: string
release_type:
description: The Release Type.
required: true
type: choice
default: 'Release'
options:
- 'Release'
- 'Beta Release'
- 'Alpha Release'
- 'Cutting Edge Build'
changelog_url:
description: Where to download the Changelog File from. See CONTRIBUTING.md for more information.
required: false
type: string
changelog_cf_url:
description: Where to download the CF Changelog File from. See CONTRIBUTING.md for more information.
required: false
type: string
changelog_branch:
description: Branch to download changelog Files from. See CONTRIBUTING.md for more information.
required: false
type: string
compare_tag:
description: Tag to compare to. See CONTRIBUTING.md for more information.
required: false
type: string
separate_upload:
description: Whether to uploaed each zip (Client, Server, Lang & MMC) and the changelogs seperately. If not set, will just upload all six files into one artifact (Built Pack).
required: true
type: boolean
workflow_call:
inputs:
tag:
description: Tag to Checkout and Release.
required: false
type: string
release_type:
description: The Release Type.
required: false
default: Release
type: string
changelog_url:
description: Where to download the Changelog File from. See CONTRIBUTING.md for more information.
required: false
type: string
changelog_cf_url:
description: Where to download the CF Changelog File from. See CONTRIBUTING.md for more information.
required: false
type: string
changelog_branch:
description: Branch to download changelog Files from. See CONTRIBUTING.md for more information.
required: false
type: string
compare_tag:
description: Tag to compare to. See CONTRIBUTING.md for more information.
required: false
type: string
separate_upload:
description: Whether to uploaed each zip (Client, Server & Lang) and the changelogs seperately. If not set, will just upload all five files into one artifact (Built Pack).
required: false
default: false
type: boolean
jobs:
build:
name: Build Pack (${{ inputs.tag }})
runs-on: ubuntu-latest
env:
GITHUB_TAG: ${{ inputs.tag }}
RELEASE_TYPE: ${{ inputs.release_type }}
steps:
- name: Checkout Ref
uses: actions/checkout@v4
with:
fetch-depth: 0
ref: ${{ inputs.tag }}
- name: Restore Cached Files
uses: actions/cache@v3
id: cache
with:
path: |
~/.npm
./.cache
./tools/node_modules
key: ${{ runner.os }}-bunny-${{ hashFiles('**/.cache', '**/package-lock.json', '**/manifest.json') }}
restore-keys: ${{ runner.os }}-bunny-
- name: Setup NodeJS v16
uses: actions/setup-node@v3
with:
node-version: "16"
check-latest: true
- name: Setup NPM Packages
working-directory: ./tools
run: npm ci
- name: Check Environmental Variables
working-directory: ./tools
run: npx gulp check
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CURSEFORGE_PROJECT_ID: ${{ secrets.CURSEFORGE_PROJECT_ID }}
CURSEFORGE_API_TOKEN: ${{ secrets.CURSEFORGE_API_TOKEN }}
CFCORE_API_TOKEN: ${{ secrets.CFCORE_API_TOKEN }}
- name: Build Pack
working-directory: ./tools
run: npx gulp buildAll
env:
CFCORE_API_TOKEN: ${{ secrets.CFCORE_API_TOKEN }}
CHANGELOG_BRANCH: ${{ inputs.changelog_branch }}
CHANGELOG_URL: ${{ inputs.changelog_url }}
CHANGELOG_CF_URL: ${{ inputs.changelog_cf_url }}
COMPARE_TAG: ${{ inputs.compare_tag }}
- name: Zip Pack
working-directory: ./tools
run: npx gulp zipAll
- name: Upload All Files
uses: actions/upload-artifact@v3
if: ${{ !inputs.separate_upload }}
with:
name: Built Pack
path: |
./build/*.zip
./build/*.md
if-no-files-found: error
- name: Upload Client Zip
uses: actions/upload-artifact@v3
if: ${{ inputs.separate_upload }}
with:
name: Client Zip
path: ./build/*-client.zip
if-no-files-found: error
- name: Upload Server Zip
uses: actions/upload-artifact@v3
if: ${{ inputs.separate_upload }}
with:
name: Server Zip
path: ./build/*-server.zip
if-no-files-found: error
- name: Upload Lang Zip
uses: actions/upload-artifact@v3
if: ${{ inputs.separate_upload }}
with:
name: Lang Zip
path: ./build/*-lang.zip
if-no-files-found: error
- name: Upload MMC Zip
uses: actions/upload-artifact@v3
if: ${{ inputs.separate_upload }}
with:
name: MMC Zip
path: ./build/*-mmc.zip
if-no-files-found: error
- name: Upload Changelogs
uses: actions/upload-artifact@v3
if: ${{ inputs.separate_upload }}
with:
name: Changelogs
path: ./build/*.md
if-no-files-found: error

118
.github/workflows/createchangelog.yml vendored Normal file
View File

@ -0,0 +1,118 @@
name: Create Changelog
on:
workflow_dispatch:
inputs:
tag:
description: Tag to checkout.
required: true
release_type:
description: Release Type
type: choice
required: true
default: 'Release'
options:
- 'Release'
- 'Beta Release'
- 'Alpha Release'
compare_tag:
description: Tag to compare against. If not set, will use the tag before `Tag`.
required: false
branch:
description: Branch to push changelog to. If not set, changelog will just be uploaded as an artifact.
required: false
test:
description: Whether to test commits. If true, then any parsing errors will throw an error.
required: false
type: boolean
default: false
workflow_call:
inputs:
tag:
description: Tag to make changelog at.
type: string
required: false
release_type:
description: Release Type.
type: string
required: true
compare_tag:
description: Tag to compare against.
type: string
required: false
branch:
description: Branch to push changelog to. If not set, will not push. Will still be uploaded as artifact.
type: string
required: false
test:
description: Whether to test commits. If true, then any parsing errors will throw an error.
required: false
type: boolean
default: false
jobs:
deploy:
name: Create Changelog (${{ inputs.tag }})
runs-on: ubuntu-latest
env:
GITHUB_TAG: ${{ inputs.tag }}
RELEASE_TYPE: ${{ inputs.release_type }}
permissions:
contents: write
steps:
- name: Checkout Repo
uses: actions/checkout@v4
with:
fetch-depth: 0
ref: ${{ inputs.tag }}
- name: Restore Cached Files
uses: actions/cache@v3
id: cache
with:
path: |
~/.npm
./.cache
./tools/node_modules
key: ${{ runner.os }}-bunny-${{ hashFiles('**/.cache', '**/package-lock.json', '**/manifest.json') }}
restore-keys: ${{ runner.os }}-bunny-
- name: Setup NodeJS v16
uses: actions/setup-node@v3
with:
node-version: "16"
check-latest: true
- name: Setup NPM Packages
working-directory: ./tools
run: npm ci
- name: Create Changelog
working-directory: ./tools
run: npx gulp createChangelog
env:
CFCORE_API_TOKEN: ${{ secrets.CFCORE_API_TOKEN }}
COMPARE_TAG: ${{ inputs.compare_tag }}
TEST_CHANGELOG: ${{ inputs.test }}
- name: Commit and Push Changelog
if: ${{ inputs.branch }}
uses: "stefanzweifel/git-auto-commit-action@v4"
id: "commit-template"
with:
commit_message: "Upload Changelogs for Release ${{ inputs.tag }}"
commit_author: "github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>"
branch: ${{ inputs.branch }}
create_branch: true
- name: Upload Changelog
uses: actions/upload-artifact@v3
with:
name: Changelogs
path: |
./CHANGELOG.md
./CHANGELOG_CF.md
if-no-files-found: error

80
.github/workflows/deploy.yml vendored Normal file
View File

@ -0,0 +1,80 @@
name: "Deploy to GitHub Releases & CurseForge"
on:
workflow_dispatch:
inputs:
tag:
description: Tag to Checkout and Release.
required: true
type: string
release_type:
description: The Release Type.
required: true
type: choice
default: 'Release'
options:
- 'Release'
- 'Beta Release'
- 'Alpha Release'
changelog_url:
description: Where to download the Changelog File from. See CONTRIBUTING.md for more information.
required: false
type: string
changelog_cf_url:
description: Where to download the CF Changelog File from. See CONTRIBUTING.md for more information.
required: false
type: string
changelog_branch:
description: Branch to download changelog Files from. See CONTRIBUTING.md for more information.
required: false
type: string
compare_tag:
description: Tag to compare to. See CONTRIBUTING.md for more information.
required: false
type: string
deploy_to_gh:
description: Whether to deploy to GitHub Releases.
required: true
type: boolean
default: true
deploy_to_cf:
description: Whether to deploy to CurseForge.
required: true
type: boolean
default: true
jobs:
build:
name: Build Pack (${{ inputs.tag }})
uses: ./.github/workflows/buildpack.yml
with:
tag: ${{ inputs.tag }}
release_type: ${{ inputs.release_type }}
changelog_url: ${{ inputs.changelog_url }}
changelog_cf_url: ${{ inputs.changelog_cf_url }}
changelog_branch: ${{ inputs.changelog_branch }}
compare_tag: ${{ inputs.compare_tag }}
secrets: inherit
deployGH:
name: Deploy to GitHub Releases (${{ inputs.tag }})
if: ${{ inputs.deploy_to_gh }}
needs: build
uses: ./.github/workflows/deploygh.yml
with:
tag: ${{ inputs.tag }}
release_type: ${{ inputs.release_type }}
secrets: inherit
permissions:
contents: write
deployCF:
name: Deploy to CurseForge (${{ inputs.tag }})
if: ${{ inputs.deploy_to_cf }}
needs: build
uses: ./.github/workflows/deploycf.yml
with:
tag: ${{ inputs.tag }}
release_type: ${{ inputs.release_type }}
secrets: inherit

View File

@ -1,30 +1,31 @@
name: Deploy to CurseForge
name: "[NOT CALLABLE] Deploy To CurseForge"
on:
workflow_dispatch:
workflow_call:
inputs:
tag:
description: 'Tag to checkout and deploy'
description: The Release Tag.
required: true
flavorTitle:
description: 'Flavor title (e. g. "Community Update")'
required: false
type: string
release_type:
description: The Release Type. This is the formatted version.
required: true
type: string
jobs:
deploy:
name: Deploy to CurseForge (${{ github.event.inputs.tag }})
deployCF:
name: Deploy to CurseForge (${{ inputs.tag }})
runs-on: ubuntu-latest
env:
GITHUB_TAG: ${{ github.event.inputs.tag }}
BUILD_FLAVOR_TITLE: ${{ github.event.inputs.flavorTitle }}
steps:
- name: "Checkout code"
uses: actions/checkout@v3
- name: Checkout Tag
uses: actions/checkout@v4
with:
fetch-depth: 0
ref: ${{ github.event.inputs.tag }}
ref: ${{ inputs.tag }}
- name: "Restore cached files"
- name: Restore Cached Files
uses: actions/cache@v3
id: cache
with:
@ -35,17 +36,17 @@ jobs:
key: ${{ runner.os }}-bunny-${{ hashFiles('**/.cache', '**/package-lock.json', '**/manifest.json') }}
restore-keys: ${{ runner.os }}-bunny-
- name: "Setup NodeJS v16"
uses: actions/setup-node@v2
- name: Setup NodeJS v16
uses: actions/setup-node@v3
with:
node-version: "16"
check-latest: true
- name: "Download NPM packages"
- name: Setup NPM Packages
working-directory: ./tools
run: npm ci
- name: "Check environmental variables"
- name: Check Environmental Variables
working-directory: ./tools
run: npx gulp check
env:
@ -54,25 +55,16 @@ jobs:
CURSEFORGE_API_TOKEN: ${{ secrets.CURSEFORGE_API_TOKEN }}
CFCORE_API_TOKEN: ${{ secrets.CFCORE_API_TOKEN }}
- name: "Build everything"
working-directory: ./tools
run: npx gulp buildAll
env:
CFCORE_API_TOKEN: ${{ secrets.CFCORE_API_TOKEN }}
- name: "Prune cache"
working-directory: ./tools
run: npx gulp pruneCache
env:
CFCORE_API_TOKEN: ${{ secrets.CFCORE_API_TOKEN }}
- name: "Zip everything"
working-directory: ./tools
run: npx gulp zipAll
- name: "Deploy to CurseForge"
- name: Download Built Artifacts
uses: actions/download-artifact@v3
with:
name: Built Pack
path: ./build/
- name: Deploy to CurseForge
env:
CURSEFORGE_PROJECT_ID: ${{ secrets.CURSEFORGE_PROJECT_ID }}
CURSEFORGE_API_TOKEN: ${{ secrets.CURSEFORGE_API_TOKEN }}
RELEASE_TYPE: ${{ inputs.release_type }}
working-directory: ./tools
run: npx gulp deployCurseForge

View File

@ -1,83 +0,0 @@
name: Deploy to CurseForge (RC)
on:
workflow_dispatch:
inputs:
branch:
description: 'Branch to checkout'
required: true
default: 'main'
version:
description: 'RC version to deploy (e. g. "v1.3")'
required: true
flavorTitle:
description: 'Flavor title (e. g. "Community Update")'
required: false
jobs:
deploy:
name: Deploy to CurseForge (RC) (${{ github.event.inputs.version }})
runs-on: ubuntu-latest
env:
GITHUB_BRANCH: ${{ github.event.inputs.branch }}
RC_VERSION: ${{ github.event.inputs.version }}
BUILD_FLAVOR_TITLE: ${{ github.event.inputs.flavorTitle }}
steps:
- name: "Checkout code"
uses: actions/checkout@v3
with:
fetch-depth: 0
ref: ${{ github.event.inputs.tag }}
- name: "Restore cached files"
uses: actions/cache@v2
id: cache
with:
path: |
~/.npm
./.cache
./tools/node_modules
key: ${{ runner.os }}-bunny-${{ hashFiles('**/.cache', '**/package-lock.json', '**/manifest.json') }}
restore-keys: ${{ runner.os }}-bunny-
- name: "Setup NodeJS v16"
uses: actions/setup-node@v2
with:
node-version: "16"
check-latest: true
- name: "Download NPM packages"
working-directory: ./tools
run: npm ci
- name: "Check environmental variables"
working-directory: ./tools
run: npx gulp check
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CURSEFORGE_PROJECT_ID: ${{ secrets.CURSEFORGE_PROJECT_ID }}
CURSEFORGE_API_TOKEN: ${{ secrets.CURSEFORGE_API_TOKEN }}
CFCORE_API_TOKEN: ${{ secrets.CFCORE_API_TOKEN }}
- name: "Build everything"
working-directory: ./tools
run: npx gulp buildAll
env:
CFCORE_API_TOKEN: ${{ secrets.CFCORE_API_TOKEN }}
- name: "Prune cache"
working-directory: ./tools
run: npx gulp pruneCache
env:
CFCORE_API_TOKEN: ${{ secrets.CFCORE_API_TOKEN }}
- name: "Zip everything"
working-directory: ./tools
run: npx gulp zipAll
- name: "Deploy to CurseForge"
env:
CURSEFORGE_PROJECT_ID: ${{ secrets.CURSEFORGE_PROJECT_ID }}
CURSEFORGE_API_TOKEN: ${{ secrets.CURSEFORGE_API_TOKEN }}
working-directory: ./tools
run: npx gulp deployCurseForgeBeta

View File

@ -1,31 +1,33 @@
name: Deploy to GitHub Releases
name: "[NOT CALLABLE] Deploy To Github Releases"
on:
workflow_dispatch:
workflow_call:
inputs:
tag:
description: 'Tag to checkout and deploy'
description: The Release Tag.
required: true
flavorTitle:
description: 'Flavor title (e. g. "Community Update")'
required: false
type: string
release_type:
description: The Release Type. This is the formatted version.
required: true
type: string
jobs:
deploy:
name: Deploy to GitHub Releases (${{ github.event.inputs.tag }})
deployGH:
name: Deploy to GitHub Releases (${{ inputs.tag }})
runs-on: ubuntu-latest
env:
GITHUB_TAG: ${{ github.event.inputs.tag }}
BUILD_FLAVOR_TITLE: ${{ github.event.inputs.flavorTitle }}
GITHUB_TAG: ${{ inputs.tag }}
permissions:
contents: write
steps:
- name: "Checkout code"
uses: actions/checkout@v3
- name: Checkout Tag
uses: actions/checkout@v4
with:
fetch-depth: 0
ref: ${{ github.event.inputs.tag }}
ref: ${{ inputs.tag }}
- name: "Restore cached files"
- name: Restore Cached Files
uses: actions/cache@v3
id: cache
with:
@ -36,17 +38,17 @@ jobs:
key: ${{ runner.os }}-bunny-${{ hashFiles('**/.cache', '**/package-lock.json', '**/manifest.json') }}
restore-keys: ${{ runner.os }}-bunny-
- name: "Setup NodeJS v16"
uses: actions/setup-node@v2
- name: Setup NodeJS v16
uses: actions/setup-node@v3
with:
node-version: "16"
check-latest: true
- name: "Download NPM packages"
- name: Setup NPM Packages
working-directory: ./tools
run: npm ci
- name: "Check environmental variables"
- name: Check Environmental Variables
working-directory: ./tools
run: npx gulp check
env:
@ -55,26 +57,17 @@ jobs:
CURSEFORGE_API_TOKEN: ${{ secrets.CURSEFORGE_API_TOKEN }}
CFCORE_API_TOKEN: ${{ secrets.CFCORE_API_TOKEN }}
- name: "Build everything"
working-directory: ./tools
run: npx gulp buildAll
env:
CFCORE_API_TOKEN: ${{ secrets.CFCORE_API_TOKEN }}
- name: Download Built Artifacts
uses: actions/download-artifact@v3
with:
name: Built Pack
path: ./build/
- name: "Prune cache"
working-directory: ./tools
run: npx gulp pruneCache
env:
CFCORE_API_TOKEN: ${{ secrets.CFCORE_API_TOKEN }}
- name: "Zip everything"
working-directory: ./tools
run: npx gulp zipAll
- name: "Deploy to GitHub Releases"
- name: Deploy to GitHub Releases
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CURSEFORGE_PROJECT_ID: ${{ secrets.CURSEFORGE_PROJECT_ID }}
CURSEFORGE_API_TOKEN: ${{ secrets.CURSEFORGE_API_TOKEN }}
RELEASE_TYPE: ${{ inputs.release_type }}
working-directory: ./tools
run: npx gulp deployReleases

88
.github/workflows/forkprbuildpack.yml vendored Normal file
View File

@ -0,0 +1,88 @@
# This workflow file tests PRs made from forks. Disable this if it is considered too much of a security risk (although many efforts have been taken to reduce risk)
# If workflow enabled, make sure to set the environment used to need a specific team (admin-devs), and default GITHUB_TOKEN perms to read!
# See https://securitylab.github.com/research/github-actions-preventing-pwn-requests/ for more information!
name: "[NOT CALLABLE] Fork PR Build Pack"
on:
pull_request_target:
# if a second commit is pushed quickly after the first, cancel the first one's build
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
jobs:
build:
# Only continue if we are in base Nomi-CEu Repo and pull request is from fork
if: "${{ github.repository_owner == 'Nomi-CEu' && github.event.pull_request.head.repo.owner.login != 'Nomi-CEu' }}"
name: Fork PR Build Pack
runs-on: ubuntu-latest
environment: fork-pr-build-pack
steps:
- name: Checkout Ref
uses: actions/checkout@v4
with:
fetch-depth: 0
ref: ${{ github.event.pull_request.head.sha }}
persist-credentials: false
# Don't use cache to prevent cache poisoning
- name: Setup NodeJS v16
uses: actions/setup-node@v3
with:
node-version: "16"
check-latest: true
- name: Setup NPM Packages
working-directory: ./tools
run: npm ci
- name: Build Pack
working-directory: ./tools
run: npx gulp buildAll
env:
CFCORE_API_TOKEN: ${{ secrets.CFCORE_API_TOKEN }}
- name: Zip Pack
working-directory: ./tools
run: npx gulp zipAll
- name: Upload Client Zip
uses: actions/upload-artifact@v3
with:
name: Client Zip
path: ./build/*-client.zip
if-no-files-found: error
- name: Upload Server Zip
uses: actions/upload-artifact@v3
with:
name: Server Zip
path: ./build/*-server.zip
if-no-files-found: error
- name: Upload Lang Zip
uses: actions/upload-artifact@v3
with:
name: Lang Zip
path: ./build/*-lang.zip
if-no-files-found: error
- name: Upload MMC Zip
uses: actions/upload-artifact@v3
with:
name: MMC Zip
path: ./build/*-mmc.zip
if-no-files-found: error
- name: Upload Changelogs
uses: actions/upload-artifact@v3
with:
name: Changelogs
path: ./build/*.md
if-no-files-found: error

View File

@ -17,11 +17,11 @@ jobs:
with:
access_token: ${{ secrets.GITHUB_TOKEN }}
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- run: git fetch --prune --unshallow --tags --force
- name: Restore cached files
uses: actions/cache@v2
uses: actions/cache@v3
id: cache
with:
path: |
@ -32,7 +32,7 @@ jobs:
restore-keys: ${{ runner.os }}-bunny-
- name: "Setup NodeJS v16"
uses: actions/setup-node@v2
uses: actions/setup-node@v3
with:
node-version: "16"
check-latest: true

47
.github/workflows/releasechangelog.yml vendored Normal file
View File

@ -0,0 +1,47 @@
name: Create Release Commit & Changelog
on:
workflow_dispatch:
inputs:
tag:
description: Tag to Create & Create Changelog At.
required: true
release_type:
description: Release Type.
type: choice
required: true
default: 'Release'
options:
- 'Release'
- 'Beta Release'
- 'Alpha Release'
compare_tag:
description: Tag to compare against.
required: false
branch:
description: Branch to push changelog to. If not set, changelog will just be uploaded as an artifact.
required: false
jobs:
releaseCommit:
name: Create Release Commit (${{ inputs.tag }})
uses: ./.github/workflows/releasecommit.yml
with:
version: ${{ inputs.tag }}
release_type: ${{ inputs.release_type }}
secrets: inherit
permissions:
contents: write
createChangelog:
name: Create Changelog (${{ inputs.tag }})
needs: releaseCommit
uses: ./.github/workflows/createchangelog.yml
with:
tag: ${{ inputs.tag }}
release_type: ${{ inputs.release_type }}
compare_tag: ${{ inputs.compare_tag }}
branch: ${{ inputs.branch }}
secrets: inherit
permissions:
contents: write

176
.github/workflows/releasecommit.yml vendored Normal file
View File

@ -0,0 +1,176 @@
name: Create Release Commit
on:
workflow_dispatch:
inputs:
version:
description: |
Release Version (aka 1.6.1a, 1.6.1-beta-3, 2.0, etc.).
Still important if not a release, and will be used to replace all version fields.
required: true
release_type:
description: 'Release Type. Will be ignored if not a release.'
type: choice
required: true
default: 'Release'
options:
- 'Release'
- 'Beta Release'
- 'Alpha Release'
is_release:
description: |
Whether this commit is a release. Usually, leave this set to true. See CONTRIBUTING.md for more details.
type: boolean
required: true
default: true
workflow_call:
inputs:
version:
description: |
Release Version (aka 1.6.1a, 1.6.1-beta-3, 2.0, etc.).
Still important if not a release, and will be used to replace all version fields.
type: string
required: true
release_type:
description: 'Release Type. Will be ignored if not a release.'
type: string
required: true
is_release:
description: |
Whether this commit is a release. See CONTRIBUTING.md for more details.
type: boolean
required: false
default: true
jobs:
# Release Commit Steps
createReleaseCommit:
name: Create Release Commit (${{ inputs.version }})
runs-on: ubuntu-latest
if: "${{ inputs.is_release }}"
env:
VERSION: "${{ inputs.version }}"
RELEASE_TYPE: "${{ inputs.release_type }}"
permissions:
contents: write
steps:
- name: Checkout Repo
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Restore Cached Files
uses: actions/cache@v3
id: cache
with:
path: |
~/.npm
./.cache
./tools/node_modules
key: ${{ runner.os }}-bunny-${{ hashFiles('**/.cache', '**/package-lock.json', '**/manifest.json') }}
restore-keys: ${{ runner.os }}-bunny-
- name: Setup NodeJS v16
uses: actions/setup-node@v3
with:
node-version: "16"
check-latest: true
- name: Setup NPM Packages
working-directory: ./tools
run: npm ci
- name: Check Environmental Variables
working-directory: ./tools
run: npx gulp check
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CURSEFORGE_PROJECT_ID: ${{ secrets.CURSEFORGE_PROJECT_ID }}
CURSEFORGE_API_TOKEN: ${{ secrets.CURSEFORGE_API_TOKEN }}
CFCORE_API_TOKEN: ${{ secrets.CFCORE_API_TOKEN }}
- name: Set Release Version
working-directory: ./tools
run: |
npx gulp addVersionAll
- name: Commit and Push Release Changes
uses: "stefanzweifel/git-auto-commit-action@v4"
id: "commit-release"
with:
commit_message: "${{ inputs.release_type }} ${{ inputs.version }}\n\n[NO CATEGORY]"
commit_author: "github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>"
tagging_message: "${{ inputs.version }}"
- name: Throw Error if No Release Changes were Detected
if: ${{ steps.commit-release.outputs.changes_detected == 'false' }}
run: |
echo "No Changes were Made."
exit 1
# Non-Release Commit Steps
updateFilesFromTemplate:
name: Update Files from Templates
runs-on: ubuntu-latest
if: "${{ !inputs.is_release }}"
env:
VERSION: "${{ inputs.version }}"
permissions:
contents: write
steps:
- name: Checkout Repo
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Restore Cached Files
uses: actions/cache@v3
id: cache
with:
path: |
~/.npm
./.cache
./tools/node_modules
key: ${{ runner.os }}-bunny-${{ hashFiles('**/.cache', '**/package-lock.json', '**/manifest.json') }}
restore-keys: ${{ runner.os }}-bunny-
- name: Setup NodeJS v16
uses: actions/setup-node@v3
with:
node-version: "16"
check-latest: true
- name: Setup NPM Packages
working-directory: ./tools
run: npm ci
- name: Check Environmental Variables
working-directory: ./tools
run: npx gulp check
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CURSEFORGE_PROJECT_ID: ${{ secrets.CURSEFORGE_PROJECT_ID }}
CURSEFORGE_API_TOKEN: ${{ secrets.CURSEFORGE_API_TOKEN }}
CFCORE_API_TOKEN: ${{ secrets.CFCORE_API_TOKEN }}
- name: Update Files from Templates
if: ${{ inputs.not_release }}
working-directory: ./tools
run: |
npx gulp updateTemplatesAll
- name: Commit and Push Template Changes
if: ${{ inputs.not_release }}
uses: "stefanzweifel/git-auto-commit-action@v4"
id: "commit-template"
with:
commit_message: "Update Issue, Server and RP Config Files from Templates\n\n[SKIP]"
commit_author: "github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>"
- name: Throw Error if No Template Changes were Detected
if: ${{ inputs.not_release && steps.commit-template.outputs.changes_detected == 'false' }}
run: |
echo "No Template Changes were detected. This may be an error."
exit 1

78
.github/workflows/releasedeploy.yml vendored Normal file
View File

@ -0,0 +1,78 @@
name: Release Commit & Deploy
on:
workflow_dispatch:
inputs:
tag:
description: Tag to Make and Release.
required: true
type: string
release_type:
description: The Release Type.
required: true
type: choice
default: 'Release'
options:
- 'Release'
- 'Beta Release'
- 'Alpha Release'
compare_tag:
description: Tag to compare to. See CONTRIBUTING.md for more information.
required: false
type: string
deploy_to_gh:
description: Whether to deploy to GitHub Releases.
required: true
type: boolean
default: true
deploy_to_cf:
description: Whether to deploy to CurseForge.
required: true
type: boolean
default: true
jobs:
releaseCommit:
name: Create Release Commit (${{ inputs.tag }})
uses: ./.github/workflows/releasecommit.yml
with:
version: ${{ inputs.tag }}
release_type: ${{ inputs.release_type }}
secrets: inherit
permissions:
contents: write
build:
name: Build Pack (${{ inputs.tag }})
needs: releaseCommit
uses: ./.github/workflows/buildpack.yml
with:
tag: ${{ inputs.tag }}
release_type: ${{ inputs.release_type }}
changelog_url: ${{ inputs.changelog_url }}
changelog_cf_url: ${{ inputs.changelog_cf_url }}
changelog_branch: ${{ inputs.changelog_branch }}
compare_tag: ${{ inputs.compare_tag }}
secrets: inherit
deployGH:
name: Deploy to GitHub Releases (${{ inputs.tag }})
if: ${{ inputs.deploy_to_gh }}
needs: build
uses: ./.github/workflows/deploygh.yml
with:
tag: ${{ inputs.tag }}
release_type: ${{ inputs.release_type }}
secrets: inherit
permissions:
contents: write
deployCF:
name: Deploy to CurseForge (${{ inputs.tag }})
if: ${{ inputs.deploy_to_cf }}
needs: build
uses: ./.github/workflows/deploycf.yml
with:
tag: ${{ inputs.tag }}
release_type: ${{ inputs.release_type }}
secrets: inherit

25
.github/workflows/testbuildpack.yml vendored Normal file
View File

@ -0,0 +1,25 @@
# Test Builds pack from pushes and pull requests in same repo.
name: "[NOT CALLABLE] Test Build Pack"
on:
push:
branches:
- main
- test_buildscript*
pull_request:
# if a second commit is pushed quickly after the first, cancel the first one's build
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
# Only allow runs from commits to Nomi-CEu Branches or from pull requests from Nomi-CEu repo
if: "${{ github.repository_owner == 'Nomi-CEu' && ( !github.event.pull_request || github.event.pull_request.head.repo.owner.login == 'Nomi-CEu' ) }}"
name: Test Build Pack
uses: ./.github/workflows/buildpack.yml
with:
separate_upload: true
secrets: inherit

20
.github/workflows/testcommits.yml vendored Normal file
View File

@ -0,0 +1,20 @@
name: Test Commit Syntax
on:
workflow_dispatch:
push:
branches:
- main
- test_buildscript*
jobs:
testCommits:
if: "${{ github.repository_owner == 'Nomi-CEu' }}"
name: Test Commits
uses: ./.github/workflows/createchangelog.yml
with:
release_type: "Cutting Edge Build"
test: true
secrets: inherit
permissions:
contents: write

View File

@ -14,21 +14,19 @@ on:
jobs:
updateQB:
if: "${{ github.repository_owner == 'Nomi-CEu' }}"
name: Update Quest Book Jsons and Lang (${{ github.event.inputs.tag }})
name: Update Quest Book Jsons and Lang (${{ inputs.tag }})
runs-on: ubuntu-latest
env:
GITHUB_BRANCH: ${{ github.event.inputs.branch }}
permissions:
contents: write
steps:
- name: "Checkout code"
uses: actions/checkout@v3
- name: Checkout Ref
uses: actions/checkout@v4
with:
fetch-depth: 0
ref: ${{ github.event.inputs.tag }}
ref: ${{ inputs.tag }}
- name: "Restore cached files"
- name: Restore Cached Files
uses: actions/cache@v3
id: cache
with:
@ -39,29 +37,27 @@ jobs:
key: ${{ runner.os }}-bunny-${{ hashFiles('**/.cache', '**/package-lock.json', '**/manifest.json') }}
restore-keys: ${{ runner.os }}-bunny-
- name: "Setup NodeJS v16"
uses: actions/setup-node@v2
- name: Setup NodeJS v16
uses: actions/setup-node@v3
with:
node-version: "16"
check-latest: true
- name: "Download NPM packages"
- name: Setup NPM Packages
working-directory: ./tools
run: npm ci
- name: "Transform QB"
- name: Transform QB
working-directory: ./tools
run: npx gulp transformQB
- name: "Commit and push QB Changes"
- name: Commit and push QB Changes
uses: "stefanzweifel/git-auto-commit-action@v4"
id: "commit"
with:
commit_message: "Update Quest Book Jsons and Lang\n\n[SKIP]"
commit_author: "github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>"
- name: "Throw Error if No Changes were Detected"
- name: Throw Error if No Changes were Detected
if: steps.commit.outputs.changes_detected == 'false'
run: |
echo "No QB Changes Detected. This may be intended, or an error."
exit 1
run: echo "No QB Changes Detected. This may be intended, or an error."

6
.gitignore vendored
View File

@ -3,7 +3,7 @@
overrides/config/brandon3055/ProjectIntelligence
overrides/config/brandon3055/ResourceCache
build
overrides/resources/minecraft/.DS_Store
overrides/resources/.DS_Store
.DS_Store
tools/node_modules
*.DS_Store
buildtools/build.bat
*.icloud

View File

@ -1,34 +0,0 @@
# Contributing
## More to come!
## How to contribute:
- Fork this repository
- Add commits
- PR it back!
## Quest Book Contributing
### Normal Mode:
Change the quest book `.json` located at `/overrides/config/betterquesting/saved_quests/NormalQuestsDev.json`.
### Expert Mode:
Change the quest book ``.json` located at `/overrides/config/betterquesting/saved_quests/ExpertQuestsDev.json`.
**Make sure when you change the files listed above! DO NOT change the lang file(s), or the main `.json` files, which contain translation keys.**
The only exception to this rule is with QB translations. Only change the lang file(s). Notes on how to do this are stated below.
Once the changes are committed to `main`, GH Actions will automatically update the main QB `.json` files, and the English QB lang file!
### Translations:
You can either make a resource pack, with a lang file located in `/questbook/lang`, or you can PR a lang file, placed in `/overrides/resources/questbook/lang`.
- Note that both Normal and Expert QB translations are located there.
- Each localization entry for each line or quest has a comment above, saying the quest/line id, and the mode it is related to.
- The localization entries for normal and expert are seperated by a comment.
- `line` notes above localization keys are for chapters.
- `db` notes above localization keys are for quests.
## Info for Maintainers:
Here are some basic notes. I hope that you were explained the basic gist of this project, but here are some finer details:
- If you want the QB Transform Workflow to run on your branch, that isn't `main`, prefix the branch with `test_buildscript`
- If you want the QB Transform Workflow to run on your fork, remove or comment out `if: "${{ github.repository_owner == 'Nomi-CEu' }}"` in `/.github/workflows/updateqb.yml` (in your fork)

View File

@ -23,6 +23,9 @@
- Many QoL features - Wiremill can produce any wire, Creative Tank no longer uses fluids, new creative chests and tanks from CEu, etc.
- And more...
## Contributing and Maintainer Information
This is available on our [wiki](https://github.com/Nomi-CEu/Nomi-CEu/wiki).
## Server Setup and Information
Details on how to setup a basic server, and some important server admin information, are listed [here.](https://github.com/Nomi-CEu/Nomi-CEu/blob/main/serverfiles/README.md)

View File

@ -1,3 +1,5 @@
# DO NOT EDIT THIS FILE! EDIT THE TEMPlATES INSTEAD!
# See https://github.com/Nomi-CEu/Nomi-CEu/wiki/Part-1:-Contributing-Information#section-5-template-information!
# Configuration file
##########################################################################################################
@ -110,7 +112,7 @@ client {
# The Minecraft window title.
# Default: Minecraft 1.12.2
S:title=Nomifactory CEu, Version 1.6.1a, Expert Mode)
S:title=Nomifactory CEu, v1.6.1a, Expert Mode)
}
}

View File

@ -1,3 +1,5 @@
# DO NOT EDIT THIS FILE! EDIT THE TEMPlATES INSTEAD!
# See https://github.com/Nomi-CEu/Nomi-CEu/wiki/Part-1:-Contributing-Information#section-5-template-information!
#Minecraft server properties
op-permission-level=4
level-name=world

View File

@ -1,3 +1,5 @@
# DO NOT EDIT THIS FILE! EDIT THE TEMPlATES INSTEAD!
# See https://github.com/Nomi-CEu/Nomi-CEu/wiki/Part-1:-Contributing-Information#section-5-template-information!
# Configuration file
##########################################################################################################
@ -110,7 +112,7 @@ client {
# The Minecraft window title.
# Default: Minecraft 1.12.2
S:title=Nomifactory CEu, Version 1.6.1a, Normal Mode)
S:title=Nomifactory CEu, v1.6.1a, Normal Mode)
}
}

View File

@ -1,3 +1,5 @@
# DO NOT EDIT THIS FILE! EDIT THE TEMPlATES INSTEAD!
# See https://github.com/Nomi-CEu/Nomi-CEu/wiki/Part-1:-Contributing-Information#section-5-template-information!
#Minecraft server properties
op-permission-level=4
level-name=world

View File

@ -1,3 +1,5 @@
# DO NOT EDIT THIS FILE! EDIT THE TEMPlATES INSTEAD!
# See https://github.com/Nomi-CEu/Nomi-CEu/wiki/Part-1:-Contributing-Information#section-5-template-information!
# Configuration file
##########################################################################################################

View File

@ -1,3 +1,5 @@
# DO NOT EDIT THIS FILE! EDIT THE TEMPlATES INSTEAD!
# See https://github.com/Nomi-CEu/Nomi-CEu/wiki/Part-1:-Contributing-Information#section-5-template-information!
#Minecraft server properties
op-permission-level=4
level-name=world

5
tools/.idea/.gitignore generated vendored Normal file
View File

@ -0,0 +1,5 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/

58
tools/.idea/codeStyles/Project.xml generated Normal file
View File

@ -0,0 +1,58 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<HTMLCodeStyleSettings>
<option name="HTML_SPACE_INSIDE_EMPTY_TAG" value="true" />
<option name="HTML_ENFORCE_QUOTES" value="true" />
</HTMLCodeStyleSettings>
<JSCodeStyleSettings version="0">
<option name="FORCE_SEMICOLON_STYLE" value="true" />
<option name="SPACE_BEFORE_FUNCTION_LEFT_PARENTH" value="false" />
<option name="FORCE_QUOTE_STYlE" value="true" />
<option name="ENFORCE_TRAILING_COMMA" value="Remove" />
<option name="SPACES_WITHIN_OBJECT_LITERAL_BRACES" value="true" />
<option name="SPACES_WITHIN_IMPORTS" value="true" />
</JSCodeStyleSettings>
<TypeScriptCodeStyleSettings version="0">
<option name="FORCE_SEMICOLON_STYLE" value="true" />
<option name="SPACE_BEFORE_FUNCTION_LEFT_PARENTH" value="false" />
<option name="FORCE_QUOTE_STYlE" value="true" />
<option name="ENFORCE_TRAILING_COMMA" value="Remove" />
<option name="SPACES_WITHIN_OBJECT_LITERAL_BRACES" value="true" />
<option name="SPACES_WITHIN_IMPORTS" value="true" />
</TypeScriptCodeStyleSettings>
<VueCodeStyleSettings>
<option name="INTERPOLATION_NEW_LINE_AFTER_START_DELIMITER" value="false" />
<option name="INTERPOLATION_NEW_LINE_BEFORE_END_DELIMITER" value="false" />
</VueCodeStyleSettings>
<codeStyleSettings language="HTML">
<option name="SOFT_MARGINS" value="80" />
<indentOptions>
<option name="INDENT_SIZE" value="2" />
<option name="CONTINUATION_INDENT_SIZE" value="2" />
<option name="TAB_SIZE" value="2" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="JavaScript">
<option name="SOFT_MARGINS" value="80" />
<indentOptions>
<option name="INDENT_SIZE" value="2" />
<option name="CONTINUATION_INDENT_SIZE" value="2" />
<option name="TAB_SIZE" value="2" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="TypeScript">
<option name="SOFT_MARGINS" value="80" />
<indentOptions>
<option name="INDENT_SIZE" value="2" />
<option name="CONTINUATION_INDENT_SIZE" value="2" />
<option name="TAB_SIZE" value="2" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="Vue">
<option name="SOFT_MARGINS" value="80" />
<indentOptions>
<option name="CONTINUATION_INDENT_SIZE" value="2" />
</indentOptions>
</codeStyleSettings>
</code_scheme>
</component>

View File

@ -0,0 +1,5 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
</state>
</component>

View File

@ -0,0 +1,8 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true">
<option name="useSeverityFromConfigFile" value="false" />
</inspection_tool>
</profile>
</component>

7
tools/.idea/jsLibraryMappings.xml generated Normal file
View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JavaScriptLibraryMappings">
<includedPredefinedLibrary name="Node.js Core" />
<excludedPredefinedLibrary name="HTML" />
</component>
</project>

8
tools/.idea/modules.xml generated Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/tools.iml" filepath="$PROJECT_DIR$/.idea/tools.iml" />
</modules>
</component>
</project>

12
tools/.idea/tools.iml generated Normal file
View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
<excludeFolder url="file://$MODULE_DIR$/temp" />
<excludeFolder url="file://$MODULE_DIR$/tmp" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

6
tools/.idea/vcs.xml generated Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
</component>
</project>

View File

@ -4,6 +4,7 @@ import manifest from "./../manifest.json";
import { ModpackManifest } from "./types/modpackManifest";
export const sharedDestDirectory = upath.join(buildConfig.buildDestinationDirectory, "shared");
export const modDestDirectory = upath.join(buildConfig.buildDestinationDirectory, "mods");
export const clientDestDirectory = upath.join(buildConfig.buildDestinationDirectory, "client");
export const mmcDestDirectory = upath.join(buildConfig.buildDestinationDirectory, "mmc");
export const serverDestDirectory = upath.join(buildConfig.buildDestinationDirectory, "server");
@ -13,3 +14,5 @@ export const modpackManifest = manifest as ModpackManifest;
export const overridesFolder = modpackManifest.overrides || "overrides";
export const configFolder = upath.join(overridesFolder, "config");
export const configOverridesFolder = upath.join(overridesFolder, "config-overrides");
export const rootDirectory = "..";
export const templatesFolder = "templates";

View File

@ -1,3 +1,5 @@
// noinspection JSUnusedGlobalSymbols
import * as gulp from "gulp";
import pruneCacheTask from "./tasks/misc/pruneCache";
@ -6,17 +8,37 @@ export const pruneCache = pruneCacheTask;
import * as quest from "./tasks/github/quest";
export const transformQB = quest.transformQuestBook;
import * as releaseCommit from "./tasks/misc/releaseCommit";
export const checkRelease = releaseCommit.check;
// Normal Tasks
export const addVersionIssue = gulp.series(checkRelease, releaseCommit.updateIssueTemplates);
export const addVersionRandomPatches = gulp.series(checkRelease, releaseCommit.updateRandomPatchesConfig);
export const addVersionServer = gulp.series(checkRelease, releaseCommit.updateServerProperties);
export const addVersionAll = gulp.series(checkRelease, releaseCommit.updateAll);
// Non Release Tasks
const setNotRelease = releaseCommit.setNotRelease;
export const updateTemplatesIssue = gulp.series(setNotRelease, addVersionIssue);
export const updateTemplatesRandomPatches = gulp.series(setNotRelease, addVersionRandomPatches);
export const updateTemplatesServer = gulp.series(setNotRelease, addVersionServer);
export const updateTemplatesAll = gulp.series(setNotRelease, addVersionAll);
import * as changelog from "./tasks/changelog/createChangelog";
export const createChangelog = changelog.createRootChangelog;
import sharedTasks from "./tasks/shared";
import clientTasks from "./tasks/client";
import serverTasks from "./tasks/server";
import langTasks from "./tasks/lang";
import mmcTasks from "./tasks/mmc";
import modTasks from "./tasks/misc/downloadMods";
export const buildClient = gulp.series(sharedTasks, clientTasks);
export const buildServer = gulp.series(sharedTasks, serverTasks);
export const buildServer = gulp.series(sharedTasks, modTasks, serverTasks);
export const buildLang = gulp.series(sharedTasks, langTasks);
export const buildAll = gulp.series(sharedTasks, gulp.series(clientTasks, serverTasks, langTasks));
export const buildMMC = gulp.series(sharedTasks, clientTasks, mmcTasks);
export const buildMMC = gulp.series(sharedTasks, modTasks, clientTasks, mmcTasks);
export const buildAll = gulp.series(sharedTasks, modTasks, gulp.series(clientTasks, langTasks, serverTasks, mmcTasks));
import checkTasks from "./tasks/checks";
export const check = gulp.series(checkTasks);
@ -25,13 +47,13 @@ import * as zip from "./tasks/misc/zip";
export const zipClient = zip.zipClient;
export const zipServer = zip.zipServer;
export const zipLang = zip.zipLang;
export const zipAll = zip.zipAll;
export const zipMMC = zip.zipMMC;
export const zipAll = zip.zipAll;
import * as gha from "./tasks/misc/gha";
export const makeArtifactNames = gha.makeArtifactNames;
export { deployCurseForge, deployCurseForgeBeta } from "./tasks/deploy/curseforge";
export { deployCurseForge } from "./tasks/deploy/curseforge";
import deployReleasesTask from "./tasks/deploy/releases";
export const deployReleases = deployReleasesTask;

263
tools/package-lock.json generated
View File

@ -9,7 +9,9 @@
"version": "1.2.2",
"license": "LGPL-3.0",
"dependencies": {
"md5": "^2.3.0"
"@egjs/list-differ": "^1.0.1",
"@ltd/j-toml": "^1.38.0",
"dedent-js": "^1.0.1"
},
"devDependencies": {
"@octokit/rest": "^18.3.5",
@ -34,10 +36,13 @@
"eslint-config-prettier": "^8.1.0",
"eslint-plugin-prettier": "^3.3.1",
"fancy-log": "^1.3.3",
"gray-matter": "^4.0.3",
"gulp": "^4.0.2",
"gulp-imagemin": "^7.1.0",
"gulp-rename": "^2.0.0",
"gulp-zip": "^5.1.0",
"marked": "^9.0.3",
"md5": "^2.3.0",
"merge-stream": "^2.0.0",
"mustache": "^4.1.0",
"png-to-jpeg": "^1.0.1",
@ -46,6 +51,7 @@
"requestretry": "^5.0.0",
"sanitize-filename": "^1.6.3",
"sha1": "^1.1.1",
"simple-git": "^3.19.1",
"ts-node": "^10.9.1",
"typescript": "^4.8.4",
"unzipper": "^0.10.11",
@ -167,6 +173,11 @@
"node": ">=12"
}
},
"node_modules/@egjs/list-differ": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@egjs/list-differ/-/list-differ-1.0.1.tgz",
"integrity": "sha512-OTFTDQcWS+1ZREOdCWuk5hCBgYO4OsD30lXcOCyVOAjXMhgL5rBRDnt/otb6Nz8CzU0L/igdcaQBDLWc4t9gvg=="
},
"node_modules/@eslint/eslintrc": {
"version": "0.4.3",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz",
@ -241,6 +252,26 @@
"@jridgewell/sourcemap-codec": "^1.4.10"
}
},
"node_modules/@kwsites/file-exists": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@kwsites/file-exists/-/file-exists-1.1.1.tgz",
"integrity": "sha512-m9/5YGR18lIwxSFDwfE3oA7bWuq9kdau6ugN4H2rJeyhFQZcG9AgSHkQtSD15a8WvTgfz9aikZMrKPHvbpqFiw==",
"dev": true,
"dependencies": {
"debug": "^4.1.1"
}
},
"node_modules/@kwsites/promise-deferred": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@kwsites/promise-deferred/-/promise-deferred-1.1.1.tgz",
"integrity": "sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw==",
"dev": true
},
"node_modules/@ltd/j-toml": {
"version": "1.38.0",
"resolved": "https://registry.npmjs.org/@ltd/j-toml/-/j-toml-1.38.0.tgz",
"integrity": "sha512-lYtBcmvHustHQtg4X7TXUu1Xa/tbLC3p2wLvgQI+fWVySguVZJF60Snxijw5EiohumxZbR10kWYFFebh1zotiw=="
},
"node_modules/@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@ -2234,6 +2265,7 @@
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz",
"integrity": "sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==",
"dev": true,
"engines": {
"node": "*"
}
@ -2754,6 +2786,7 @@
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz",
"integrity": "sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==",
"dev": true,
"engines": {
"node": "*"
}
@ -3102,6 +3135,11 @@
"node": ">=0.10.0"
}
},
"node_modules/dedent-js": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/dedent-js/-/dedent-js-1.0.1.tgz",
"integrity": "sha512-OUepMozQULMLUmhxS95Vudo0jb0UchLimi3+pQ2plj61Fcy8axbP9hbiD4Sz6DPqn6XG3kfmziVfQ1rSys5AJQ=="
},
"node_modules/deep-is": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
@ -5666,6 +5704,30 @@
"integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==",
"dev": true
},
"node_modules/gray-matter": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/gray-matter/-/gray-matter-4.0.3.tgz",
"integrity": "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==",
"dev": true,
"dependencies": {
"js-yaml": "^3.13.1",
"kind-of": "^6.0.2",
"section-matter": "^1.0.0",
"strip-bom-string": "^1.0.0"
},
"engines": {
"node": ">=6.0"
}
},
"node_modules/gray-matter/node_modules/kind-of": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
"integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/gulp": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/gulp/-/gulp-4.0.2.tgz",
@ -6423,7 +6485,8 @@
"node_modules/is-buffer": {
"version": "1.1.6",
"resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
"integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w=="
"integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==",
"dev": true
},
"node_modules/is-callable": {
"version": "1.2.7",
@ -7354,6 +7417,18 @@
"node": ">=0.10.0"
}
},
"node_modules/marked": {
"version": "9.0.3",
"resolved": "https://registry.npmjs.org/marked/-/marked-9.0.3.tgz",
"integrity": "sha512-pI/k4nzBG1PEq1J3XFEHxVvjicfjl8rgaMaqclouGSMPhk7Q3Ejb2ZRxx/ZQOcQ1909HzVoWCFYq6oLgtL4BpQ==",
"dev": true,
"bin": {
"marked": "bin/marked.js"
},
"engines": {
"node": ">= 16"
}
},
"node_modules/matchdep": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/matchdep/-/matchdep-2.0.0.tgz",
@ -7539,6 +7614,7 @@
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz",
"integrity": "sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==",
"dev": true,
"dependencies": {
"charenc": "0.0.2",
"crypt": "0.0.2",
@ -9403,6 +9479,49 @@
"dev": true,
"optional": true
},
"node_modules/section-matter": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz",
"integrity": "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==",
"dev": true,
"dependencies": {
"extend-shallow": "^2.0.1",
"kind-of": "^6.0.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/section-matter/node_modules/extend-shallow": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
"integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==",
"dev": true,
"dependencies": {
"is-extendable": "^0.1.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/section-matter/node_modules/is-extendable": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz",
"integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/section-matter/node_modules/kind-of": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
"integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/seek-bzip": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/seek-bzip/-/seek-bzip-1.0.6.tgz",
@ -9593,6 +9712,21 @@
"dev": true,
"optional": true
},
"node_modules/simple-git": {
"version": "3.19.1",
"resolved": "https://registry.npmjs.org/simple-git/-/simple-git-3.19.1.tgz",
"integrity": "sha512-Ck+rcjVaE1HotraRAS8u/+xgTvToTuoMkT9/l9lvuP5jftwnYUp6DwuJzsKErHgfyRk8IB8pqGHWEbM3tLgV1w==",
"dev": true,
"dependencies": {
"@kwsites/file-exists": "^1.1.1",
"@kwsites/promise-deferred": "^1.1.1",
"debug": "^4.3.4"
},
"funding": {
"type": "github",
"url": "https://github.com/steveukx/git-js?sponsor=1"
}
},
"node_modules/slash": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
@ -10242,6 +10376,15 @@
"node": ">=0.10.0"
}
},
"node_modules/strip-bom-string": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz",
"integrity": "sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/strip-dirs": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/strip-dirs/-/strip-dirs-2.1.0.tgz",
@ -11750,6 +11893,11 @@
"@jridgewell/trace-mapping": "0.3.9"
}
},
"@egjs/list-differ": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@egjs/list-differ/-/list-differ-1.0.1.tgz",
"integrity": "sha512-OTFTDQcWS+1ZREOdCWuk5hCBgYO4OsD30lXcOCyVOAjXMhgL5rBRDnt/otb6Nz8CzU0L/igdcaQBDLWc4t9gvg=="
},
"@eslint/eslintrc": {
"version": "0.4.3",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz",
@ -11814,6 +11962,26 @@
"@jridgewell/sourcemap-codec": "^1.4.10"
}
},
"@kwsites/file-exists": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@kwsites/file-exists/-/file-exists-1.1.1.tgz",
"integrity": "sha512-m9/5YGR18lIwxSFDwfE3oA7bWuq9kdau6ugN4H2rJeyhFQZcG9AgSHkQtSD15a8WvTgfz9aikZMrKPHvbpqFiw==",
"dev": true,
"requires": {
"debug": "^4.1.1"
}
},
"@kwsites/promise-deferred": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@kwsites/promise-deferred/-/promise-deferred-1.1.1.tgz",
"integrity": "sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw==",
"dev": true
},
"@ltd/j-toml": {
"version": "1.38.0",
"resolved": "https://registry.npmjs.org/@ltd/j-toml/-/j-toml-1.38.0.tgz",
"integrity": "sha512-lYtBcmvHustHQtg4X7TXUu1Xa/tbLC3p2wLvgQI+fWVySguVZJF60Snxijw5EiohumxZbR10kWYFFebh1zotiw=="
},
"@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@ -13415,7 +13583,8 @@
"charenc": {
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz",
"integrity": "sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA=="
"integrity": "sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==",
"dev": true
},
"chokidar": {
"version": "3.5.3",
@ -13841,7 +14010,8 @@
"crypt": {
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz",
"integrity": "sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow=="
"integrity": "sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==",
"dev": true
},
"css-select": {
"version": "2.1.0",
@ -14121,6 +14291,11 @@
}
}
},
"dedent-js": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/dedent-js/-/dedent-js-1.0.1.tgz",
"integrity": "sha512-OUepMozQULMLUmhxS95Vudo0jb0UchLimi3+pQ2plj61Fcy8axbP9hbiD4Sz6DPqn6XG3kfmziVfQ1rSys5AJQ=="
},
"deep-is": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
@ -16184,6 +16359,26 @@
"integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==",
"dev": true
},
"gray-matter": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/gray-matter/-/gray-matter-4.0.3.tgz",
"integrity": "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==",
"dev": true,
"requires": {
"js-yaml": "^3.13.1",
"kind-of": "^6.0.2",
"section-matter": "^1.0.0",
"strip-bom-string": "^1.0.0"
},
"dependencies": {
"kind-of": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
"integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==",
"dev": true
}
}
},
"gulp": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/gulp/-/gulp-4.0.2.tgz",
@ -16744,7 +16939,8 @@
"is-buffer": {
"version": "1.1.6",
"resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
"integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w=="
"integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==",
"dev": true
},
"is-callable": {
"version": "1.2.7",
@ -17459,6 +17655,12 @@
"object-visit": "^1.0.0"
}
},
"marked": {
"version": "9.0.3",
"resolved": "https://registry.npmjs.org/marked/-/marked-9.0.3.tgz",
"integrity": "sha512-pI/k4nzBG1PEq1J3XFEHxVvjicfjl8rgaMaqclouGSMPhk7Q3Ejb2ZRxx/ZQOcQ1909HzVoWCFYq6oLgtL4BpQ==",
"dev": true
},
"matchdep": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/matchdep/-/matchdep-2.0.0.tgz",
@ -17613,6 +17815,7 @@
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz",
"integrity": "sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==",
"dev": true,
"requires": {
"charenc": "0.0.2",
"crypt": "0.0.2",
@ -19028,6 +19231,39 @@
"dev": true,
"optional": true
},
"section-matter": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz",
"integrity": "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==",
"dev": true,
"requires": {
"extend-shallow": "^2.0.1",
"kind-of": "^6.0.0"
},
"dependencies": {
"extend-shallow": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
"integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==",
"dev": true,
"requires": {
"is-extendable": "^0.1.0"
}
},
"is-extendable": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz",
"integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==",
"dev": true
},
"kind-of": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
"integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==",
"dev": true
}
}
},
"seek-bzip": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/seek-bzip/-/seek-bzip-1.0.6.tgz",
@ -19176,6 +19412,17 @@
"dev": true,
"optional": true
},
"simple-git": {
"version": "3.19.1",
"resolved": "https://registry.npmjs.org/simple-git/-/simple-git-3.19.1.tgz",
"integrity": "sha512-Ck+rcjVaE1HotraRAS8u/+xgTvToTuoMkT9/l9lvuP5jftwnYUp6DwuJzsKErHgfyRk8IB8pqGHWEbM3tLgV1w==",
"dev": true,
"requires": {
"@kwsites/file-exists": "^1.1.1",
"@kwsites/promise-deferred": "^1.1.1",
"debug": "^4.3.4"
}
},
"slash": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
@ -19705,6 +19952,12 @@
"is-utf8": "^0.2.0"
}
},
"strip-bom-string": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz",
"integrity": "sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==",
"dev": true
},
"strip-dirs": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/strip-dirs/-/strip-dirs-2.1.0.tgz",

View File

@ -28,10 +28,13 @@
"eslint-config-prettier": "^8.1.0",
"eslint-plugin-prettier": "^3.3.1",
"fancy-log": "^1.3.3",
"gray-matter": "^4.0.3",
"gulp": "^4.0.2",
"gulp-imagemin": "^7.1.0",
"gulp-rename": "^2.0.0",
"gulp-zip": "^5.1.0",
"marked": "^9.0.3",
"md5": "^2.3.0",
"merge-stream": "^2.0.0",
"mustache": "^4.1.0",
"png-to-jpeg": "^1.0.1",
@ -40,12 +43,15 @@
"requestretry": "^5.0.0",
"sanitize-filename": "^1.6.3",
"sha1": "^1.1.1",
"simple-git": "^3.19.1",
"ts-node": "^10.9.1",
"typescript": "^4.8.4",
"unzipper": "^0.10.11",
"upath": "^2.0.1"
},
"dependencies": {
"md5": "^2.3.0"
"@egjs/list-differ": "^1.0.1",
"@ltd/j-toml": "^1.38.0",
"dedent-js": "^1.0.1"
}
}

View File

@ -0,0 +1,21 @@
import { categories } from "./definitions";
import { Category, ChangelogMessage, SubCategory } from "../../types/changelogTypes";
export function categoriesSetup(): void {
// Initialize Category Lists
categories.forEach((categoryKey) => {
initializeCategorySection(categoryKey);
});
}
/**
* Initializes the categorySection field of the categoryKey.
* @param category The Category to initialize the categorySection of.
*/
function initializeCategorySection(category: Category): void {
const categorySection = new Map<SubCategory, ChangelogMessage[]>();
category.subCategories.forEach((subCategory) => {
categorySection.set(subCategory, []);
});
category.changelogSection = categorySection;
}

View File

@ -0,0 +1,52 @@
import { Commit, FixUpInfo, InputReleaseType } from "../../types/changelogTypes";
import { getLastGitTag, isEnvVariableSet } from "../../util/util";
export default class ChangelogData {
since: string;
to: string;
releaseType: InputReleaseType;
isTest: boolean;
builder: string[];
commitList: Commit[];
commitFixes: Map<string, FixUpInfo>;
shaList: Set<string>;
// Map of a commit SHA to the commits which need to be added to its commit list.
combineList: Map<string, Commit[]>;
constructor() {
this.since = getLastGitTag();
this.to = "HEAD";
// If this is a tagged build, fetch the tag before last.
if (isEnvVariableSet("GITHUB_TAG")) {
this.since = getLastGitTag(process.env.GITHUB_TAG);
this.to = process.env.GITHUB_TAG;
}
// Get Release Type
this.releaseType = "Release";
if (isEnvVariableSet("RELEASE_TYPE")) this.releaseType = process.env.RELEASE_TYPE as InputReleaseType;
// See if current run is test
if (isEnvVariableSet("TEST_CHANGELOG")) {
try {
this.isTest = JSON.parse(process.env.TEST_CHANGELOG);
} catch (err) {
throw new Error("Test Changelog Env Variable set to Invalid Value.");
}
}
// Initialise Final Builder
this.builder = [];
// List of all commits to put into changelog commit section
this.commitList = [];
this.commitFixes = new Map<string, FixUpInfo>();
this.shaList = new Set<string>();
this.combineList = new Map<string, Commit[]>();
}
}

View File

@ -0,0 +1,62 @@
import fs from "fs";
import { rootDirectory } from "../../globals";
import upath from "upath";
import marked from "marked";
import buildConfig from "../../buildConfig";
import { categoriesSetup } from "./categoryManagement";
import ChangelogData from "./changelogData";
import { parsers } from "./definitions";
import parse from "./parser";
import { specialParserSetup } from "./specialParser";
import generateModChanges from "./generateModChanges";
import pushAll from "./pusher";
/**
* Generates a changelog based on environmental variables, and saves it a changelog data class.
*/
async function createChangelog(): Promise<ChangelogData> {
const data: ChangelogData = new ChangelogData();
changelogSetup(data);
for (const parser of parsers) {
await parse(data, parser);
}
await generateModChanges(data);
pushAll(data);
return data;
}
function changelogSetup(data: ChangelogData) {
categoriesSetup();
specialParserSetup(data);
}
/**
* Creates a changelog based on environment variables, and saves it to the root directory.
*/
export const createRootChangelog = async (): Promise<void> => {
// Make a changelog, and save it.
const builder = (await createChangelog()).builder;
// Write files.
await fs.promises.writeFile(upath.join(rootDirectory, "CHANGELOG.md"), builder.join("\n"));
return fs.promises.writeFile(upath.join(rootDirectory, "CHANGELOG_CF.md"), marked.parse(builder.join("\n")));
};
/**
* Creates a changelog based on environment variables, and saves it to the build directory.
*/
export const createBuildChangelog = async (): Promise<void> => {
// Make a changelog, and save it.
const builder = (await createChangelog()).builder;
// Write files.
await fs.promises.writeFile(upath.join(buildConfig.buildDestinationDirectory, "CHANGELOG.md"), builder.join("\n"));
return fs.promises.writeFile(
upath.join(buildConfig.buildDestinationDirectory, "CHANGELOG_CF.md"),
marked.parse(builder.join("\n")),
);
};

View File

@ -0,0 +1,219 @@
import { Category, Commit, Parser, SubCategory } from "../../types/changelogTypes";
import { modpackManifest } from "../../globals";
import { parseCommitBody } from "./parser";
import { parseFixUp } from "./specialParser";
/* Values */
export const defaultIndentation = "";
export const indentationLevel = " ";
/*
Link to the repo, with a slash at the end.
All URLs will be appended to this.
*/
export const repoLink = "https://github.com/Nomi-CEu/Nomi-CEu/";
/* Keys */
/* Special Handling Keys */
export const skipKey = "[SKIP]";
export const expandKey = "[EXPAND]";
export const expandList = "messages";
export const detailsKey = "[DETAILS]";
export const detailsList = "details";
export const noCategoryKey = "[NO CATEGORY]";
export const combineKey = "[COMBINE]";
export const combineList = "commits";
export const fixUpKey = "[FIXUP]";
export const fixUpList = "fixes";
/* Sub Category Keys */
// Mode Category Keys
const normalMode: SubCategory = { commitKey: "[NM]", keyName: "Normal Mode" };
const hardMode: SubCategory = { commitKey: "[HM]", keyName: "Hard Mode" };
/* Misc Sub Category Keys */
const qolChanges: SubCategory = { commitKey: "[QOL]", keyName: "Quality of Life" };
/* Set Sub Categories (Sub Categories that do not let any commit in) */
const bothModes: SubCategory = { keyName: "Both Modes" };
const modUpdates: SubCategory = { keyName: "Mod Updates" };
const modAdditions: SubCategory = { keyName: "Mod Additions" };
const modRemovals: SubCategory = { keyName: "Mod Removals" };
/* Default Sub Categories (Sub Categories that allow any commit in) */
const emptySubCategory: SubCategory = { commitKey: "", keyName: "" };
const other: SubCategory = { commitKey: "", keyName: "Other" };
/* Category Keys: */
const breakingCategory: Category = {
commitKey: "[BREAKING]",
categoryName: "Breaking Changes",
defaultSubCategory: emptySubCategory,
subCategories: [emptySubCategory],
};
const balancingCategory: Category = {
commitKey: "[BALANCING]",
categoryName: "Balancing Changes",
defaultSubCategory: bothModes,
subCategories: [bothModes, normalMode, hardMode],
};
const performanceCategory: Category = {
commitKey: "[PERFORMANCE]",
categoryName: "Performance Improvements",
defaultSubCategory: emptySubCategory,
subCategories: [emptySubCategory],
};
const featureCategory: Category = {
commitKey: "[FEATURE]",
categoryName: "Feature Additions",
defaultSubCategory: bothModes,
subCategories: [qolChanges, bothModes, normalMode, hardMode],
};
const questBookCategory: Category = {
commitKey: "[QB]",
categoryName: "Quest Book Changes",
defaultSubCategory: bothModes,
subCategories: [bothModes, normalMode, hardMode],
};
const bugCategory: Category = {
commitKey: "[BUG]",
categoryName: "Bug Fixes",
defaultSubCategory: bothModes,
subCategories: [bothModes, normalMode, hardMode],
};
const generalCategory: Category = {
commitKey: "[GENERAL]",
categoryName: "General Changes",
defaultSubCategory: other,
subCategories: [modUpdates, modAdditions, modRemovals, other],
};
const internalCategory: Category = {
commitKey: "[INTERNAL]",
categoryName: "Internal Changes",
defaultSubCategory: emptySubCategory,
subCategories: [emptySubCategory],
};
/**
* Category List
* <p>
* The order that the categories appear here will be the order that they appear in the changelog, and their priority.
*/
export const categories: Category[] = [
breakingCategory,
balancingCategory,
performanceCategory,
featureCategory,
questBookCategory,
bugCategory,
generalCategory,
internalCategory,
];
/* Parsing Util Methods */
const defaultSkipCallback = (_commit: Commit, _commitMessage: string, commitBody: string): boolean => {
if (!commitBody) return false;
return commitBody.includes(skipKey);
};
const defaultParsingCallback = async (
parser: Parser,
commit: Commit,
commitMessage: string,
commitBody: string,
): Promise<boolean> => {
if (!commitBody) return false;
return parseCommitBody(commitMessage, commitBody, commit, parser);
};
/* Parsing Categories */
const fixupParsing: Parser = {
skipCallback: () => false,
// No need to care about message/body, never parse expand/details commits
itemCallback: (_parser, commit) => parseFixUp(commit),
addCommitListCallback: () => false,
addSHACallback: () => false,
};
const overridesParsing: Parser = {
dirs: [modpackManifest.overrides],
skipCallback: defaultSkipCallback,
itemCallback: defaultParsingCallback,
leftOverCallback: (commit, commitMessage, _commitBody, subMessages) => {
generalCategory.changelogSection.get(generalCategory.defaultSubCategory).push({
commitMessage: commitMessage,
commitObject: commit,
subChangelogMessages: subMessages,
});
},
addCommitListCallback: () => true,
};
const manifestParsing: Parser = {
dirs: ["manifest.json"],
skipCallback: defaultSkipCallback,
itemCallback: defaultParsingCallback,
addCommitListCallback: () => true,
};
const finalParsing: Parser = {
skipCallback: defaultSkipCallback,
itemCallback: defaultParsingCallback,
addCommitListCallback: (_commit, parsed) => parsed,
};
/**
* Parsers
* <p>
* The order that the categories appear here will be the order that they are parsed in.<p>
* Note that unless `addSHA` of the category is set to false, a commit parsed in a previous category will not be allowed to be parsed in future categories,
* even if they fit in the dirs.
*/
export const parsers: Parser[] = [fixupParsing, overridesParsing, manifestParsing, finalParsing];
/* Parsing Information / Allocations for Mod Changes */
export type ModChangesType = "added" | "removed" | "updated";
/**
* An Allocation for mod changes categories to grab from.
*/
export interface ModChangesAllocation {
/**
* Category to put in.
*/
category: Category;
/**
* Sub category of the category to put in.
*/
subCategory: SubCategory;
/**
* The template to use.<p><p>
* Keys:<p>
* `{{{ modName }}}` replaced by mod name,<p>
* `{{{ oldVersion }}}` replaced by the old version (if applicable)<p>
* `{{{ newVersion }}}` replaced by the new version (if applicable)
*/
template: string;
}
export const modChangesAllocations: Record<ModChangesType, ModChangesAllocation> = {
added: {
category: generalCategory,
subCategory: modAdditions,
template: "{{ modName }}: *v{{ newVersion }}*",
},
updated: {
category: generalCategory,
subCategory: modUpdates,
template: "{{ modName }}: *v{{ oldVersion }} ⇥ v{{ newVersion }}*",
},
removed: {
category: generalCategory,
subCategory: modRemovals,
template: "{{ modName }}: *v{{ oldVersion }}*",
},
};

View File

@ -0,0 +1,177 @@
import { cleanupVersion, compareAndExpandManifestDependencies, getChangelog, getFileAtRevision } from "../../util/util";
import { ModpackManifest, ModpackManifestFile } from "../../types/modpackManifest";
import { Commit, ModChangeInfo } from "../../types/changelogTypes";
import ListDiffer, { DiffResult } from "@egjs/list-differ";
import dedent from "dedent-js";
import mustache from "mustache";
import { defaultIndentation, modChangesAllocations, repoLink } from "./definitions";
import ChangelogData from "./changelogData";
import { SpecialChangelogFormatting } from "../../types/changelogTypes";
/**
* Mod Changes special formatting
*/
const getModChangesFormatting: (commits: Commit[]) => SpecialChangelogFormatting<Commit[]> = (commits) => {
return {
formatting: (changelogMessage, commits) => {
const indentation = changelogMessage.indentation == undefined ? defaultIndentation : changelogMessage.indentation;
const message = changelogMessage.commitMessage.trim();
if (commits.length > 1) {
const authors: string[] = [];
const formattedCommits: string[] = [];
commits.forEach((commit) => {
if (!authors.includes(commit.author_name)) authors.push(commit.author_name);
formattedCommits.push(`[\`${commit.hash.substring(0, 7)}\`](${repoLink}commit/${commit.hash})`);
});
authors.sort();
return `${indentation}* ${message} - **${authors.join("**, **")}** (${formattedCommits.join(", ")})`;
}
const commit = commits[0];
const shortSHA = commit.hash.substring(0, 7);
const author = commit.author_name;
return `${indentation}* ${message} - **${author}** ([\`${shortSHA}\`](${repoLink}commit/${commit.hash}))`;
},
storage: commits,
} as SpecialChangelogFormatting<Commit[]>;
};
/**
* Pushes the mod changes, with their relative commits, to their respective sub categories in the specified category.
*/
export default async function generateModChanges(data: ChangelogData): Promise<void> {
const oldManifest: ModpackManifest = JSON.parse(getFileAtRevision("manifest.json", data.since));
const newManifest: ModpackManifest = JSON.parse(getFileAtRevision("manifest.json", data.to));
const comparisonResult = await compareAndExpandManifestDependencies(oldManifest, newManifest);
const commitList = await getChangelog(data.since, data.to, ["manifest.json"]);
const projectIDsToCommits: Map<number, Commit[]> = new Map();
commitList.forEach((commit) => {
const projectIDs = getChangedProjectIDs(commit.hash);
projectIDs.forEach((id) => {
if (projectIDsToCommits.has(id)) projectIDsToCommits.get(id).push(commit);
else projectIDsToCommits.set(id, [commit]);
});
});
[
{
allocation: modChangesAllocations.added,
list: comparisonResult.added,
},
{
allocation: modChangesAllocations.updated,
list: comparisonResult.modified,
},
{
allocation: modChangesAllocations.removed,
list: comparisonResult.removed,
},
].forEach((block) => {
if (block.list.length == 0) {
return;
}
const list = block.list
// Yeet invalid project names.
.filter((project) => !/project-\d*/.test(project.modName))
.sort()
.map((name) => name);
list.forEach((info) => {
let commits: Commit[] = undefined;
if (info.projectID && projectIDsToCommits.has(info.projectID)) {
commits = projectIDsToCommits.get(info.projectID);
}
block.allocation.category.changelogSection.get(block.allocation.subCategory).push({
commitMessage: getModChangeMessage(info, block.allocation.template),
specialFormatting: getModChangesFormatting(commits),
});
});
});
}
/**
* Returns the message, determined by the parameters below.
* @param info The mod change info, containing the mod name and versions.
* @param template The message template to replace in.
*/
function getModChangeMessage(info: ModChangeInfo, template: string): string {
const oldVersion = cleanupVersion(info.oldVersion);
const newVersion = cleanupVersion(info.newVersion);
// If not provided with either version, return just mod name
if (!oldVersion && !newVersion) return info.modName;
// Replace in template
return mustache.render(template, {
modName: info.modName,
oldVersion: oldVersion,
newVersion: newVersion,
});
}
/**
* Gets what project IDs, in manifest.json, a commit changed.
* @param SHA The sha of the commit
*/
function getChangedProjectIDs(SHA: string): number[] {
const change = getCommitChange(SHA);
const projectIDs: number[] = [];
if (!change || !change.diff) {
return projectIDs;
}
// Add all unique IDs from both diff lists
change.diff.added.forEach((index) => {
const id = change.newManifest.files[index].projectID;
if (!projectIDs.includes(id)) projectIDs.push(id);
});
change.diff.removed.forEach((index) => {
const id = change.oldManifest.files[index].projectID;
if (!projectIDs.includes(id)) projectIDs.push(id);
});
return projectIDs;
}
/**
* A storage of what parts of the 'manifest.json' file a commit changed.
*/
interface CommitChange {
diff: DiffResult<ModpackManifestFile>;
oldManifest: ModpackManifest;
newManifest: ModpackManifest;
}
/**
* Gets what parts of the 'manifest.json' file a commit changed.
* @param SHA The sha of the commit
*/
function getCommitChange(SHA: string): CommitChange {
let oldManifest: ModpackManifest, newManifest: ModpackManifest;
try {
oldManifest = JSON.parse(getFileAtRevision("manifest.json", `${SHA}^`)) as ModpackManifest;
newManifest = JSON.parse(getFileAtRevision("manifest.json", SHA)) as ModpackManifest;
} catch (e) {
console.error(dedent`
Failed to parse the manifest.json file at commit ${SHA} or the commit before!
Skipping...`);
return;
}
let result: DiffResult<ModpackManifestFile>;
if (oldManifest && newManifest) {
const differ = new ListDiffer(oldManifest.files, (e) => e.fileID);
result = differ.update(newManifest.files);
}
return {
diff: result,
oldManifest: oldManifest,
newManifest: newManifest,
};
}

View File

@ -0,0 +1,117 @@
import { Category, Commit, Parser, SubCategory } from "../../types/changelogTypes";
import { categories, combineKey, defaultIndentation, detailsKey, expandKey, noCategoryKey } from "./definitions";
import { parseCombine, parseDetails, parseExpand } from "./specialParser";
import { getChangelog } from "../../util/util";
import ChangelogData from "./changelogData";
export default async function parseParser(data: ChangelogData, parser: Parser): Promise<void> {
const commits = await getChangelog(data.since, data.to, parser.dirs);
for (const commit of commits) {
if (data.shaList.has(commit.hash)) continue;
if (data.commitFixes.has(commit.hash)) {
const fixUpInfo = data.commitFixes.get(commit.hash);
commit.message = fixUpInfo.newTitle;
commit.body = fixUpInfo.newBody;
}
if (parser.skipCallback(commit, commit.message, commit.body)) {
if (!parser.addSHACallback || parser.addSHACallback(commit, true)) data.shaList.add(commit.hash);
continue;
}
const parsed = await parser.itemCallback(parser, commit, commit.message, commit.body);
if (!parsed && parser.leftOverCallback) parser.leftOverCallback(commit, commit.message, commit.body, []);
if (!parser.addSHACallback || parser.addSHACallback(commit, parsed)) data.shaList.add(commit.hash);
if (parser.addCommitListCallback(commit, parsed)) data.commitList.push(commit);
}
}
/**
* Parses a commit body.
* @param commitMessage The commit message to put into the changelog.
* @param commitBody The commit body to parse with.
* @param commitObject The commit object.
* @param parser The parser object to use for parse expand/details.
* @return parsed Returns true if contains parsing keys, false if not.
*/
export async function parseCommitBody(
commitMessage: string,
commitBody: string,
commitObject: Commit,
parser: Parser,
): Promise<boolean> {
if (commitBody.includes(expandKey)) {
await parseExpand(commitBody, commitObject, parser);
return true;
}
if (commitBody.includes(detailsKey)) {
await parseDetails(commitMessage, commitBody, commitObject, parser);
return true;
}
if (commitBody.includes(noCategoryKey)) {
return true;
}
if (commitBody.includes(combineKey)) {
await parseCombine(commitBody, commitObject);
return true;
}
return sortCommit(commitMessage, commitBody, commitObject);
}
/**
* Adds the (commit) message to its correct category. Does not parse special effect tags.
* @param message The message to add
* @param commitBody The body to use to sort
* @param commit The commit object to grab date, author and SHA from
* @param indentation The indentation of the message, if needed. Defaults to "".
* @return added If the commit message was added to a category
*/
function sortCommit(message: string, commitBody: string, commit: Commit, indentation = defaultIndentation): boolean {
const sortedCategories: Category[] = findCategories(commitBody);
if (sortedCategories.length === 0) return false;
sortedCategories.forEach((category) => {
const subCategory = findSubCategory(commitBody, category);
category.changelogSection.get(subCategory).push({
commitMessage: message,
commitObject: commit,
indentation: indentation,
});
});
return true;
}
/**
* Finds the categories that a commit fits in.
* @param commitBody The commit body to sort with
* @return categoryList The categories that the commit belongs in. Return undefined if no category specified via keys.
*/
export function findCategories(commitBody: string): Category[] | undefined {
const sortedCategories: Category[] = [];
for (const category of categories) {
if (category.commitKey !== undefined) {
if (commitBody.includes(category.commitKey)) {
sortedCategories.push(category);
}
}
}
return sortedCategories;
}
/**
* Finds the correct Sub Category a commit should go in. Must be given the Category first!
*/
export function findSubCategory(commitBody: string, category: Category): SubCategory {
for (const subCategory of category.subCategories) {
if (subCategory.commitKey !== undefined) {
if (commitBody.includes(subCategory.commitKey)) {
return subCategory;
}
}
}
return category.defaultSubCategory;
}

View File

@ -0,0 +1,193 @@
import ChangelogData from "./changelogData";
import { categories, defaultIndentation } from "./definitions";
import { Category, ChangelogMessage, Commit } from "../../types/changelogTypes";
import { repoLink } from "./definitions";
let data: ChangelogData;
export default function pushAll(inputData: ChangelogData): void {
data = inputData;
// Push the titles.
// Center Align is replaced by the correct center align style in the respective deployments.
// Must be triple bracketed, to make mustache not html escape it.
if (data.releaseType === "Cutting Edge Build") {
const date = new Date().toLocaleDateString("en-us", {
year: "numeric",
month: "short",
day: "numeric",
hour12: true,
hour: "numeric",
minute: "numeric",
});
// noinspection HtmlUnknownAttribute
data.builder.push(`<h1 {{{ CENTER_ALIGN }}}>${data.releaseType} (${date})</h1>`, "");
} else {
// noinspection HtmlUnknownAttribute
data.builder.push(`<h1 {{{ CENTER_ALIGN }}}>${data.releaseType} ${data.to}</h1>`, "");
}
data.builder.push("{{{ CF_REDIRECT }}}", "");
data.builder.push(`# Changes Since ${data.since}`, "");
// Push Sections of Changelog
categories.forEach((category) => {
pushCategory(category);
});
// Push the commit log
if (data.commitList) {
sortCommitList(data.commitList, (commit) => commit);
data.builder.push("## Commits");
data.commitList.forEach((commit) => {
data.builder.push(formatCommit(commit));
});
}
// Check if the builder only contains the title.
if (data.builder.length <= 3) {
data.builder.push("");
data.builder.push("There haven't been any changes.");
}
// Push link
data.builder.push(
"",
`**Full Changelog**: [\`${data.since}...${data.to}\`](${repoLink}compare/${data.since}...${data.to})`,
);
}
/**
* Pushes a given category to the builders.
*/
function pushCategory(category: Category) {
const categoryLog: string[] = [];
let hasValues = false;
// Push All Sub Categories
category.subCategories.forEach((subCategory) => {
// Loop through key list instead of map to produce correct order
const list = category.changelogSection.get(subCategory);
if (list && list.length != 0) {
// Push Key Name (only pushes if Key Name is not "")
if (subCategory.keyName) {
categoryLog.push(`### ${subCategory.keyName}:`);
}
// Sort Log
sortCommitList(
list,
(message) => message.commitObject,
(a, b) => a.commitMessage.localeCompare(b.commitMessage),
);
// Push Log
list.forEach((changelogMessage) => {
categoryLog.push(formatChangelogMessage(changelogMessage));
// Push Sub Messages
if (changelogMessage.subChangelogMessages) {
changelogMessage.subChangelogMessages.forEach((subMessage) => {
categoryLog.push(formatChangelogMessage(subMessage, true));
});
}
});
categoryLog.push("");
hasValues = true;
}
});
if (hasValues) {
// Push Title
data.builder.push(`## ${category.categoryName}:`);
// Push previously made log
data.builder.push(...categoryLog);
}
}
/**
* Sorts a list that contains commit data
* @param list A list of type T that contains commit data
* @param transform A function to turn each element of type T into an element of type Commit
* @param backup A backup sort, to call when either element does not have a commit object, or when the commit objects' times are the same. Optional, if not set, will just return 0 (equal).
*/
function sortCommitList<T>(list: T[], transform: (obj: T) => Commit | undefined, backup?: (a: T, b: T) => number) {
list.sort((a, b): number => {
const commitA = transform(a);
const commitB = transform(b);
if (!commitA || !commitB) {
// If either commit is undefined
if (backup) return backup(a, b);
return 0;
}
const dateA = new Date(commitA.date);
const dateB = new Date(commitB.date);
// This is reversed, so the newest commits go on top
if (dateB.getTime() - dateA.getTime() !== 0) return dateB.getTime() - dateA.getTime();
if (backup) return backup(a, b);
return commitA.message.localeCompare(commitB.message);
});
}
/**
* Formats a Changelog Message
* @param changelogMessage The message to format.
* @param subMessage Whether this message is a subMessage (used in details). Set to true to make it a subMessage (different parsing). Defaults to false.
* @return string Formatted Changelog Message
*/
function formatChangelogMessage(changelogMessage: ChangelogMessage, subMessage = false): string {
if (changelogMessage.specialFormatting)
return changelogMessage.specialFormatting.formatting(changelogMessage, changelogMessage.specialFormatting.storage);
const indentation = changelogMessage.indentation == undefined ? defaultIndentation : changelogMessage.indentation;
let message = changelogMessage.commitMessage.trim();
// Transform PR tags into a link.
if (message.match(/(#\d+)/g)) {
const matched = message.match(/\(#\d+\)/g);
matched.forEach((match) => {
// Extract digits
const digits = match.match(/\d+/g);
message = message.replace(match, `([#${digits}](${repoLink}pull/${digits}))`);
});
}
if (changelogMessage.commitObject && !subMessage) {
if (data.combineList.has(changelogMessage.commitObject.hash)) {
const commits = data.combineList.get(changelogMessage.commitObject.hash);
commits.unshift(changelogMessage.commitObject);
const formattedCommits: string[] = [];
const authors: string[] = [];
const processedSHAs: Set<string> = new Set<string>();
commits.forEach((commit) => {
if (processedSHAs.has(commit.hash)) return;
if (!authors.includes(commit.author_name)) authors.push(commit.author_name);
formattedCommits.push(`[\`${commit.hash.substring(0, 7)}\`](${repoLink}commit/${commit.hash})`);
processedSHAs.add(commit.hash);
});
authors.sort();
return `${indentation}* ${message} - **${authors.join("**, **")}** (${formattedCommits.join(", ")})`;
}
const commit = changelogMessage.commitObject;
const shortSHA = commit.hash.substring(0, 7);
const author = commit.author_name;
return `${indentation}* ${message} - **${author}** ([\`${shortSHA}\`](${repoLink}commit/${commit.hash}))`;
}
return `${indentation}* ${message}`;
}
/**
* Returns a formatted commit
*/
function formatCommit(commit: Commit): string {
const date = new Date(commit.date).toLocaleDateString("en-us", { year: "numeric", month: "short", day: "numeric" });
const formattedCommit = `${commit.message} - **${commit.author_name}** (${date})`;
const shortSHA = commit.hash.substring(0, 7);
return `* [\`${shortSHA}\`](${repoLink}commit/${commit.hash}): ${formattedCommit}`;
}

View File

@ -0,0 +1,262 @@
import { ChangelogMessage, Commit, ExpandedMessage, FixUpInfo, Parser } from "../../types/changelogTypes";
import dedent from "dedent-js";
import matter, { GrayMatterFile } from "gray-matter";
import toml from "@ltd/j-toml";
import {
combineKey,
combineList,
detailsKey,
detailsList,
expandKey,
expandList,
fixUpKey,
fixUpList,
indentationLevel,
} from "./definitions";
import { findCategories, findSubCategory } from "./parser";
import ChangelogData from "./changelogData";
let data: ChangelogData;
export function specialParserSetup(inputData: ChangelogData): void {
data = inputData;
}
/**
* Parses a commit with 'Fixup'.
*/
export async function parseFixUp(commit: Commit): Promise<boolean> {
if (!commit.body || !commit.body.includes(fixUpKey)) return false;
await parse(
commit.body,
commit,
fixUpKey,
fixUpList,
(item: FixUpInfo) => item.sha && item.newTitle,
(item) => {
// Only override if no other overrides, from newer commits, set
if (!data.commitFixes.has(item.sha)) data.commitFixes.set(item.sha, item);
},
(matter) => {
// Must override, even if newer commits specified changes, as need to remove fixup data
data.commitFixes.set(commit.hash, {
sha: commit.hash,
newTitle: commit.message,
newBody: commit.body.replace(matter.matter.trim(), ""),
});
},
);
return true;
}
/**
* Parses a commit with 'expand'.
*/
export async function parseExpand(commitBody: string, commitObject: Commit, parser: Parser): Promise<void> {
await parse(
commitBody,
commitObject,
expandKey,
expandList,
(item: ExpandedMessage) => item.messageTitle,
async (item) => {
const title = dedent(item.messageTitle);
if (item.messageBody) {
const body = dedent(item.messageBody).trim();
if (!(await parser.itemCallback(parser, commitObject, title, body))) {
if (parser.leftOverCallback) {
parser.leftOverCallback(commitObject, title, body);
}
}
} else {
if (parser.leftOverCallback) {
parser.leftOverCallback(commitObject, title);
}
}
},
);
}
/**
* Parses a commit with 'details'.
*/
export async function parseDetails(
commitMessage: string,
commitBody: string,
commitObject: Commit,
parser: Parser,
): Promise<void> {
const sortedCategories = findCategories(commitBody);
const subMessages = await expandDetailsLevel(commitBody, commitObject);
if (sortedCategories.length === 0) {
if (parser.leftOverCallback) {
parser.leftOverCallback(commitObject, commitMessage, commitBody, subMessages);
}
} else {
sortedCategories.forEach((category) => {
const subCategory = findSubCategory(commitBody, category);
category.changelogSection.get(subCategory).push({
commitMessage: commitMessage,
commitObject: commitObject,
subChangelogMessages: subMessages,
});
});
}
}
/**
* Parses a 'level' of Details.
*/
async function expandDetailsLevel(
commitBody: string,
commitObject: Commit,
indentation = indentationLevel,
): Promise<ChangelogMessage[]> {
const result: ChangelogMessage[] = [];
await parse(
commitBody,
commitObject,
detailsKey,
detailsList,
(item: string) => item,
async (item) => {
item = dedent(item).trim();
if (item.includes(detailsKey)) {
result.push(...(await expandDetailsLevel(item, commitObject, `${indentation}${indentationLevel}`)));
} else {
result.push({ commitMessage: item, commitObject: commitObject, indentation: indentation });
}
},
);
return result;
}
/**
* Parses a commit with 'combine'.
*/
export async function parseCombine(commitBody: string, commitObject: Commit): Promise<void> {
await parse(
commitBody,
commitObject,
combineKey,
combineList,
(item: string) => item,
(item: string) => {
if (!data.combineList.has(item)) data.combineList.set(item, []);
data.combineList.get(item).push(commitObject);
},
);
}
/**
* Parse TOML in a commit body to produce a list.
* @param commitBody The body to parse
* @param commitObject The commit object to grab messages from, and to determine error messages.
* @param delimiter The delimiters, surrounding the TOML.
* @param listKey The key of the list to parse.
* @param emptyCheck The check to see if an item in the list is invalid.
* @param perItemCallback The callback to perform on each item in the list.
* @param matterCallback An optional callback to perform on the matter.
*/
async function parse<T>(
commitBody: string,
commitObject: Commit,
delimiter: string,
listKey: string,
emptyCheck: (item: T) => string,
perItemCallback: (item: T) => void,
matterCallback?: (matter: GrayMatterFile<string>) => void,
): Promise<void> {
let messages: T[];
let endMessage = "Skipping...";
if (data.isTest) {
endMessage = dedent`
Try checking the TOML syntax in https://www.toml-lint.com/, checking the object tree in https://www.convertsimple.com/convert-toml-to-json/, checking syntax in https://toml.io/en/v1.0.0, and looking through https://github.com/Nomi-CEu/Nomi-CEu/blob/main/CONTRIBUTING.md!
Also check that you have surrounded the TOML in '${delimiter}'!`;
}
try {
// Remove everything before first delimiter in body
const list = commitBody.split(delimiter);
list.shift();
const body = `${delimiter} ${list.join(delimiter)}`;
// Parse
const parseResult = matter(body, {
delimiters: delimiter,
engines: {
toml: (input): Record<string, unknown> => {
return toml.parse(input, "\n");
},
},
language: "toml",
});
if (matterCallback) matterCallback(parseResult);
messages = parseResult.data[listKey];
} catch (e) {
console.error(dedent`
Failed parsing TOML in body:
\`\`\`
${commitBody}\`\`\`
of commit object ${commitObject.hash} (${commitObject.message}).
This could be because of invalid syntax, or because the Message List (key: '${listKey}') is not an array.`);
if (commitObject.body && commitBody !== commitObject.body) {
console.error(dedent`
Original Body:
\`\`\`
${commitObject.body}\`\`\``);
}
console.error(`\n${endMessage}\n`);
if (data.isTest) throw e;
return;
}
if (!messages || !Array.isArray(messages) || messages.length === 0) {
console.error(dedent`
List (key: '${listKey}') in body:
\`\`\`
${commitBody}\`\`\`
of commit object ${commitObject.hash} (${commitObject.message}) is empty, not a list, or does not exist.`);
if (commitObject.body && commitBody !== commitObject.body) {
console.error(dedent`
Original Body:
\`\`\`
${commitObject.body}\`\`\``);
}
console.error(`${endMessage}\n`);
if (data.isTest) throw new Error("Failed Parsing Message List. See Above.");
return;
}
for (let i = 0; i < messages.length; i++) {
const item = messages[i];
if (!emptyCheck(item)) {
console.error(dedent`
Missing Requirements for entry ${i + 1} in body:
\`\`\`
${commitBody}\`\`\`
of commit object ${commitObject.hash} (${commitObject.message}).`);
if (commitObject.body && commitBody !== commitObject.body) {
console.error(dedent`
Original Body:
\`\`\`
${commitObject.body}\`\`\``);
}
console.error(`${endMessage}\n`);
if (data.isTest) throw new Error("Bad Entry. See Above.");
continue;
}
perItemCallback(item);
}
}

View File

@ -3,13 +3,14 @@ import { clientDestDirectory, modpackManifest, overridesFolder, sharedDestDirect
import fs from "fs";
import upath from "upath";
import buildConfig from "../../buildConfig";
import { fetchProjectsBulk } from "../../util/curseForgeAPI";
import log from "fancy-log";
import rename from "gulp-rename";
import imagemin from "gulp-imagemin";
import pngToJpeg from "png-to-jpeg";
import { MainMenuConfig } from "../../types/mainMenuConfig";
import del from "del";
import { createModList, ModFileInfo } from "../misc/createModList";
import dedent from "dedent-js";
import { cleanupVersion } from "../../util/util";
async function clientCleanUp() {
return del(upath.join(clientDestDirectory, "*"), { force: true });
@ -77,7 +78,9 @@ function copyClientUpdateNotes() {
* Copies the changelog file.
*/
function copyClientChangelog() {
return gulp.src(upath.join(sharedDestDirectory, "CHANGELOG.md")).pipe(gulp.dest(clientDestDirectory));
return gulp
.src(upath.join(buildConfig.buildDestinationDirectory, "CHANGELOG.md"))
.pipe(gulp.dest(clientDestDirectory));
}
/**
@ -93,30 +96,119 @@ function copyClientOverrides() {
* Fetches mod links and builds modlist.html.
*/
async function fetchModList() {
log("Fetching mod infos...");
const modList = await createModList();
// Fetch project/addon infos.
const modInfos = await fetchProjectsBulk(modpackManifest.files.map((mod) => mod.projectID));
const formattedModList = dedent`
<!DOCTYPE html>
<html lang="en">
<head>
<title>Nomi-CEu Mod Information</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel = "icon" href = "https://github.com/Nomi-CEu/Nomi-CEu/assets/103940576/672808a8-0ad0-4d07-809e-08336a928909" type = "image/x-icon">
<style>
caption {
font-size: 30px;
color: #fff;
font-family: "Comic Sans MS", "sans-serif";
font-weight: 700;
border-radius: 10px;
border-collapse: collapse;
background-color: #795B97;
}
table {
border-collapse: collapse;
width: 100%;
color: #333;
font-family: "Comic Sans MS", "sans-serif";
font-size: 13px;
text-align: left;
border-radius: 10px;
overflow: hidden;
box-shadow: 0 0 20px rgba(0, 0, 0, 0.1);
}
table th {
background-color: #9973BD;
color: #fff;
font-family: "Comic Sans MS", "sans-serif";
font-weight: bold;
padding: 10px;
text-transform: uppercase;
letter-spacing: 1px;
border-top: 1px solid #fff;
border-bottom: 1px solid #ccc;
font-size: 15px;
}
table tr:nth-child(even) td {
background-color: #E7E7E7;
}
table tr:hover td {
background-color: rgba(160, 210, 235, 0.5);
}
table td {
background-color: #fff;
padding: 10px;
border-bottom: 1px solid #ccc;
font-weight: bold;
}
table tr:last-of-type {
border-bottom: 2px solid #9973BD;
}
.redCross {
color: #ff0000;
font-size: 20px;
}
.greenTick {
color: #32cd32;
font-size: 20px;
}
</style>
</head>
<body>
<table>
<caption>Nomi-CEu Mod List (${modList.length} Mods):</caption>
<tr>
<th>Mod Name</th>
<th>Mod Version</th>
<th>Client?</th>
<th>Server?</th>
<th>Author(s)</th>
</tr>
${formatModList(modList)}
</table>
</body>
</html>
`;
log(`Fetched ${modInfos.length} mod infos`);
return fs.promises.writeFile(upath.join(clientDestDirectory, "modlist.html"), formattedModList);
}
// Create modlist.html
const output = [
"<ul>\r\n",
...modInfos
// Sort mods by their project IDs.
.sort((a, b) => a.id - b.id)
/**
* Formats the mod list.
*/
function formatModList(modList: ModFileInfo[]): string {
const output: string[] = [];
modList.forEach((modFile) => {
output.push(dedent`
<tr>
<td><a href="${modFile.modInfo.links.websiteUrl}">${modFile.modInfo.name}</a></td>
<td><a href="${modFile.fileInfo.downloadUrl}">v${cleanupVersion(modFile.fileInfo.displayName)}</a></td>
${getTickCross(modFile.inClient)}
${getTickCross(modFile.inServer)}
<td>${modFile.modInfo.authors.map((author) => `<a href=${author.url}>${author.name}</a>`).join(", ")}</td>
</tr>
`);
});
return output.join("\n");
}
// Create a <li> node for each mod.
.map((modInfo) => {
return `\t<li><a href="${modInfo.websiteUrl}">${modInfo.name || "Unknown"} (by ${modInfo.authors
.map((author) => author.name || "Someone")
.join(", ")})</a></li>\r\n`;
}),
"</ul>",
];
return fs.promises.writeFile(upath.join(clientDestDirectory, "modlist.html"), output.join(""));
/**
* Gets the tick/cross used in the mod list.
*/
function getTickCross(bool: boolean): string {
if (bool) {
return '<td class="greenTick">&#10004;</td>';
}
return '<td class="redCross">&#10006;</td>';
}
const bgImageNamespace = "minecraft";
@ -134,7 +226,7 @@ async function compressMainMenuImages() {
await new Promise((resolve) => {
gulp
.src(upath.join(sharedDestDirectory, overridesFolder, bgImagePathReal, "**/*"))
.pipe(imagemin([pngToJpeg({ quality: 80 })]))
.pipe(imagemin([pngToJpeg({ quality: buildConfig.screenshotsQuality })]))
.pipe(
rename((f) => {
// xd

View File

@ -1,4 +1,4 @@
import { modpackManifest, sharedDestDirectory } from "../../globals";
import { modpackManifest } from "../../globals";
import request from "requestretry";
import fs from "fs";
@ -7,55 +7,13 @@ import upath from "upath";
import buildConfig from "../../buildConfig";
import { makeArtifactNameBody } from "../../util/util";
import sanitize from "sanitize-filename";
import mustache from "mustache";
import { DeployReleaseType, inputToDeployReleaseTypes } from "../../types/changelogTypes";
const CURSEFORGE_LEGACY_ENDPOINT = "https://minecraft.curseforge.com/";
const variablesToCheck = ["CURSEFORGE_API_TOKEN", "CURSEFORGE_PROJECT_ID"];
interface CFUploadOptions {
releaseType?: "release" | "beta";
}
/**
* Uploads beta artifacts to CurseForge.
*/
export async function deployCurseForgeBeta(): Promise<void> {
/**
* Obligatory variable check.
*/
["RC_VERSION", ...variablesToCheck].forEach((vari) => {
if (!process.env[vari]) {
throw new Error(`Environmental variable ${vari} is unset.`);
}
});
const version = process.env.RC_VERSION;
const flavorTitle = process.env.BUILD_FLAVOR_TITLE;
const displayName = [modpackManifest.name, [version.replace(/^v/, ""), "Release Candidate"].join(" "), flavorTitle]
.filter(Boolean)
.join(" - ");
const files = [
{
name: sanitize((makeArtifactNameBody(modpackManifest.name) + "-client.zip").toLowerCase()),
displayName: displayName,
},
{
name: sanitize((makeArtifactNameBody(modpackManifest.name) + "-server.zip").toLowerCase()),
displayName: `${displayName} Server`,
},
];
/**
* Obligatory file check.
*/
await upload(files, {
releaseType: "beta",
});
}
async function upload(files: { name: string; displayName: string }[], opts?: CFUploadOptions) {
opts = opts || {};
const variablesToCheck = ["CURSEFORGE_API_TOKEN", "CURSEFORGE_PROJECT_ID", "RELEASE_TYPE"];
async function upload(files: { name: string; displayName: string }[]) {
files.forEach((file) => {
const path = upath.join(buildConfig.buildDestinationDirectory, file.name);
if (!fs.existsSync(path)) {
@ -64,10 +22,14 @@ async function upload(files: { name: string; displayName: string }[], opts?: CFU
});
// Since we've built everything beforehand, the changelog must be available in the shared directory.
const changelog = await (await fs.promises.readFile(upath.join(sharedDestDirectory, "CHANGELOG.md")))
.toString()
.replace(/\n/g, " \n")
.replace(/\n\*/g, "\n•");
let changelog = (
await fs.promises.readFile(upath.join(buildConfig.buildDestinationDirectory, "CHANGELOG_CF.md"))
).toString();
changelog = mustache.render(changelog, {
CENTER_ALIGN: 'style="text-align: center;"',
CF_REDIRECT: `<h4 style="text-align: center;">Looks way better <a href="https://github.com/Nomi-CEu/Nomi-CEu/releases/tag/${process.env.GITHUB_TAG}">here.</a></h4>`,
});
const tokenHeaders = {
"X-Api-Token": process.env.CURSEFORGE_API_TOKEN,
@ -97,6 +59,8 @@ async function upload(files: { name: string; displayName: string }[], opts?: CFU
let clientFileID: number | null;
const releaseType: DeployReleaseType = inputToDeployReleaseTypes[process.env.RELEASE_TYPE];
// Upload artifacts.
for (const file of files) {
const options = {
@ -109,9 +73,9 @@ async function upload(files: { name: string; displayName: string }[], opts?: CFU
formData: {
metadata: JSON.stringify({
changelog: changelog,
changelogType: "markdown",
releaseType: opts.releaseType || "release",
parentFileID: clientFileID,
changelogType: "html",
releaseType: releaseType ? releaseType.cfReleaseType : "release",
parentFileID: clientFileID ? clientFileID : undefined,
gameVersions: clientFileID ? undefined : [version.id],
displayName: file.displayName,
}),
@ -148,9 +112,7 @@ export async function deployCurseForge(): Promise<void> {
}
});
const tag = process.env.GITHUB_TAG;
const flavorTitle = process.env.BUILD_FLAVOR_TITLE;
const displayName = [modpackManifest.name, tag.replace(/^v/, ""), flavorTitle].filter(Boolean).join(" - ");
const displayName = process.env.GITHUB_TAG;
const files = [
{
@ -159,9 +121,9 @@ export async function deployCurseForge(): Promise<void> {
},
{
name: sanitize((makeArtifactNameBody(modpackManifest.name) + "-server.zip").toLowerCase()),
displayName: `${displayName} Server`,
displayName: `${displayName}-server`,
},
];
upload(files);
await upload(files);
}

View File

@ -1,4 +1,4 @@
import { modpackManifest, sharedDestDirectory } from "../../globals";
import { modpackManifest } from "../../globals";
import fs from "fs";
import upath from "upath";
@ -7,8 +7,10 @@ import { makeArtifactNameBody } from "../../util/util";
import Bluebird from "bluebird";
import { Octokit } from "@octokit/rest";
import sanitize from "sanitize-filename";
import mustache from "mustache";
import { DeployReleaseType, inputToDeployReleaseTypes } from "../../types/changelogTypes";
const variablesToCheck = ["GITHUB_TAG", "GITHUB_TOKEN", "GITHUB_REPOSITORY"];
const variablesToCheck = ["GITHUB_TAG", "GITHUB_TOKEN", "GITHUB_REPOSITORY", "RELEASE_TYPE"];
/**
* Uploads build artifacts to GitHub Releases.
@ -24,7 +26,7 @@ async function deployReleases(): Promise<void> {
});
const body = makeArtifactNameBody(modpackManifest.name);
const files = ["client", "server", "lang"].map((file) => sanitize(`${body}-${file}.zip`.toLowerCase()));
const files = ["client", "server", "lang", "mmc"].map((file) => sanitize(`${body}-${file}.zip`.toLowerCase()));
/**
* Obligatory file check.
@ -51,16 +53,21 @@ async function deployReleases(): Promise<void> {
};
const tag = process.env.GITHUB_TAG;
const flavorTitle = process.env.BUILD_FLAVOR_TITLE;
const releaseType: DeployReleaseType = inputToDeployReleaseTypes[process.env.RELEASE_TYPE];
const preRelease = releaseType ? releaseType.isPreRelease : false;
// Since we've built everything beforehand, the changelog must be available in the shared directory.
const changelog = await (await fs.promises.readFile(upath.join(sharedDestDirectory, "CHANGELOG.md"))).toString();
// Since we've grabbed, or built, everything beforehand, the Changelog file should be in the build dir
let changelog = (
await fs.promises.readFile(upath.join(buildConfig.buildDestinationDirectory, "CHANGELOG.md"))
).toString();
changelog = mustache.render(changelog, { CENTER_ALIGN: 'align="center"', CF_REDIRECT: "" });
// Create a release.
const release = await octokit.repos.createRelease({
tag_name: tag || "latest-dev-preview",
prerelease: !tag,
name: [modpackManifest.name, tag.replace(/^v/, ""), flavorTitle].filter(Boolean).join(" - "),
prerelease: preRelease,
name: tag || "latest-dev-preview",
body: changelog,
...repo,
});

View File

@ -105,7 +105,7 @@ export async function transformQuestBook(): Promise<void> {
// Source Quest Book File Locations
const questPathNormalDev = upath.join(sharedQBDefaults, "saved_quests", "NormalQuestsDev.json");
const questPathExpertDev = upath.join(sharedQBDefaults, "saved_quests", "ExpertQuestsDev.json");
// Quest Book Objects
const questBookNormal: QuestBook = JSON.parse((await fs.promises.readFile(questPathNormalDev)).toString());
const questBookExpert: QuestBook = JSON.parse((await fs.promises.readFile(questPathExpertDev)).toString());
@ -113,31 +113,27 @@ export async function transformQuestBook(): Promise<void> {
// Quest Book Paths
const questPathNormalDefault = upath.join(sharedQBDefaults, "DefaultQuests.json");
const questPathNormalOverride = upath.join(sharedConfigOverrides, "normal", "betterquesting", "DefaultQuests.json");
const questPathExpertDefault = upath.join(sharedQBDefaults, "saved_quests", "ExpertQuests.json");
const questPathExpertOverride = upath.join(sharedConfigOverrides, "expert", "betterquesting", "DefaultQuests.json");
// Quest Lang Location
const questLangLocation = upath.join(buildConfig.buildSourceDirectory, overridesFolder, langFileLocation);
// Traverse through the quest book and rewrite titles/descriptions.
// Extract title/desc pairs into a lang file.
const lines: string[] = [];
lines.push("Normal Quest Lang Entries:",
"",
);
lines.push("Normal Quest Lang Entries:", "");
// Normal Mode Quest lines.
transformKeyPairs(questBookNormal["questLines:9"], "normal", "line", lines);
// Normal Mode Quests themselves.
transformKeyPairs(questBookNormal["questDatabase:9"], "normal", "db", lines);
lines.push("Expert Quest Lang Entries:",
"",
);
lines.push("Expert Quest Lang Entries:", "");
// Expert Mode Quest lines.
transformKeyPairs(questBookExpert["questLines:9"], "expert", "line", lines);
@ -151,13 +147,11 @@ export async function transformQuestBook(): Promise<void> {
// Strip useless metadata.
stripUselessMetadata(questBookNormal);
stripUselessMetadata(questBookExpert);
// Write QB files.
fs.promises.writeFile(questPathNormalDefault, JSON.stringify(questBookNormal, null, 2));
fs.promises.writeFile(questPathNormalOverride, JSON.stringify(questBookNormal, null, 2));
fs.promises.writeFile(questPathExpertDefault, JSON.stringify(questBookExpert, null, 2));
fs.promises.writeFile(questPathExpertOverride, JSON.stringify(questBookExpert, null, 2));
await fs.promises.writeFile(questPathNormalDefault, JSON.stringify(questBookNormal, null, 2));
await fs.promises.writeFile(questPathNormalOverride, JSON.stringify(questBookNormal, null, 2));
await fs.promises.writeFile(questPathExpertDefault, JSON.stringify(questBookExpert, null, 2));
return await fs.promises.writeFile(questPathExpertOverride, JSON.stringify(questBookExpert, null, 2));
}

View File

@ -0,0 +1,69 @@
import log from "fancy-log";
import { fetchFileInfo, fetchFilesBulk, fetchProject, fetchProjectsBulk } from "../../util/curseForgeAPI";
import { modpackManifest } from "../../globals";
import { checkGitTag, getFileAtRevision } from "../../util/util";
import { ModpackManifest } from "../../types/modpackManifest";
import { CurseForgeFileInfo, CurseForgeModInfo } from "../../types/curseForge";
export interface ModFileInfo {
modInfo: CurseForgeModInfo;
fileInfo: CurseForgeFileInfo;
inClient: boolean;
inServer: boolean;
}
/**
* Fetches mod links and builds a modlist.
*/
export async function createModList(tag = ""): Promise<ModFileInfo[]> {
log("Fetching mod & file infos...");
let manifest: ModpackManifest = modpackManifest;
if (tag) {
checkGitTag(tag);
manifest = JSON.parse(getFileAtRevision("manifest.json", tag));
}
manifest.files.sort((a, b) => a.projectID - b.projectID);
// Fetch mod/addon & file infos, discard result. Further calls will hit cache.
await fetchProjectsBulk(manifest.files.map((mod) => mod.projectID));
await fetchFilesBulk(
// Use this instead of referencing the original array, as .sort sorts the input array
[...manifest.files]
.sort((a, b) => a.fileID - b.fileID)
.map((mod) => {
return { projectID: mod.projectID, fileID: mod.fileID };
}),
);
log("Fetched Infos. Creating modlist...");
// Create modlist
const output: ModFileInfo[] = [];
for (const file of manifest.files) {
const itemModInfo = await fetchProject(file.projectID);
const itemFileInfo = await fetchFileInfo(file.projectID, file.fileID);
let itemInClient = false;
let itemInServer = false;
if (file.sides) {
if (file.sides.includes("client")) itemInClient = true;
if (file.sides.includes("server")) itemInServer = true;
} else {
itemInClient = true;
itemInServer = true;
}
output.push({
modInfo: itemModInfo,
fileInfo: itemFileInfo,
inClient: itemInClient,
inServer: itemInServer,
});
}
return output;
}

View File

@ -0,0 +1,50 @@
import { modDestDirectory, modpackManifest } from "../../globals";
import { fetchMods } from "../../util/curseForgeAPI";
import upath from "upath";
import fs from "fs";
import log from "fancy-log";
import del from "del";
import gulp from "gulp";
async function modCleanUp() {
return del(upath.join(modDestDirectory, "*"), { force: true });
}
/**
* Checks and creates all necessary directories so we can download the mods safely.
*/
async function createModDirs() {
// This also makes the base dir, as it is recursive.
if (!fs.existsSync(upath.join(modDestDirectory, "client"))) {
await fs.promises.mkdir(upath.join(modDestDirectory, "client"), { recursive: true });
}
if (!fs.existsSync(upath.join(modDestDirectory, "server"))) {
await fs.promises.mkdir(upath.join(modDestDirectory, "server"), { recursive: true });
}
}
/**
* Downloads mods according to manifest.json and checks hashes.
*/
export async function downloadMods(): Promise<void> {
log("Fetching Shared Mods...");
await fetchMods(
modpackManifest.files.filter((f) => !f.sides),
modDestDirectory,
);
log("Fetching Client Mods...");
await fetchMods(
modpackManifest.files.filter((f) => f.sides && f.sides.includes("client")),
upath.join(modDestDirectory, "client"),
);
log("Fetching Server Mods...");
await fetchMods(
modpackManifest.files.filter((f) => f.sides && f.sides.includes("server")),
upath.join(modDestDirectory, "server"),
);
}
export default gulp.series(modCleanUp, createModDirs, downloadMods);

View File

@ -0,0 +1,207 @@
import fs from "fs";
import upath from "upath";
import { configFolder, configOverridesFolder, rootDirectory, templatesFolder } from "../../globals";
import mustache from "mustache";
import gulp from "gulp";
import dedent from "dedent-js";
import { checkEnvironmentalVariables } from "../../util/util";
// This updates all the files, for a release.
// IF DEBUGGING:
// Change debug value to true
// Change version to a string
const debug = false;
const version: string = process.env.VERSION;
// If it is not a release, and thus no changes to versions need to be made.
// This occurs when the files are to be updated from the templates outside of a release.
// Optional variable to set.
let notRelease = false;
/**
* Checks if env variable are set, creates versions.txt if file does not exist, and checks if new version already exists in versions.txt.
*/
export async function check(): Promise<void> {
if (!debug) {
checkEnvironmentalVariables(["VERSION"]);
}
const versionsFilePath: string = upath.join(templatesFolder, "versions.txt");
if (notRelease) {
console.log("Detected that this is not a release commit.");
console.log("Version info will not change, but the files will be updated from the template.");
await checkNotRelease(versionsFilePath);
} else {
console.log("Detected that this is a release commit.");
await checkRelease(versionsFilePath);
}
}
/**
* Sets this workflow as a non-release.
*/
export async function setNotRelease(): Promise<void> {
notRelease = true;
}
// Checks for non-release commits
async function checkNotRelease(versionsFilePath: string) {
// Check if versions.txt exists
if (!fs.existsSync(versionsFilePath)) {
console.error(
`Version.txt does not exist. Creating empty file, and adding ${version} to it. This may be an error.`,
);
// Create Versions.txt, with version
await fs.promises.writeFile(versionsFilePath, ` - ${version}`);
} else {
// Check for duplicate entries
let versionList = await fs.promises.readFile(versionsFilePath, "utf8");
// No Duplicate Key
if (!versionList.includes(version)) {
console.error(`Version is not in version.txt. Adding ${version} to version.txt. This may be an error.`);
versionList = ` - ${version}\n${versionList}`;
await fs.promises.writeFile(versionsFilePath, versionList);
}
}
}
// Checks for release Commits
async function checkRelease(versionsFilePath: string) {
// Check if versions.txt exists
if (!fs.existsSync(versionsFilePath)) {
console.error("Version.txt does not exist. Creating empty file. This may be an error.");
// Create Versions.txt
fs.closeSync(fs.openSync(versionsFilePath, "w"));
} else {
// Check for duplicate entries
const versionList = await fs.promises.readFile(versionsFilePath, "utf8");
// Duplicate Key
if (versionList.includes(`${version}\n`)) {
throw new Error("Version already exists in version.txt. Exiting...");
}
}
}
/**
* @param readPath The filepath to read from. (Template)
* @param writePaths The filepaths to write to.
* @param replacementObject A record, of type string to type unknown, containing the keys, and replacement for those keys
* <p>
* <p>
* A warning not to edit the file will also be added to the start of the file.
*/
async function modifyFile(readPath: string, writePaths: string[], replacementObject: Record<string, unknown>) {
// Read the file content
const data: string = await fs.promises.readFile(readPath, "utf8");
// Moustache Render
let modifiedData: string = mustache.render(data, replacementObject);
// Add warning to not edit file
modifiedData = dedent`# DO NOT EDIT THIS FILE! EDIT THE TEMPlATES INSTEAD!
# See https://github.com/Nomi-CEu/Nomi-CEu/wiki/Part-1:-Contributing-Information#section-5-template-information!
${modifiedData}`;
// Write the modified content back to the file
for (const filename of writePaths) {
await fs.promises.writeFile(filename, modifiedData, "utf8");
}
}
export async function updateIssueTemplates(): Promise<void> {
// Filenames
const fileNames: string[] = ["001-bug-report.yml", "002-feature-request.yml"];
const versionsFilePath: string = upath.join(templatesFolder, "versions.txt");
let versionList: string = await fs.promises.readFile(versionsFilePath, "utf8");
if (!notRelease) {
// Add new version to list, with indent
versionList = ` - ${version}\n${versionList}`;
}
// Replacement Object
const replacementObject: Record<string, unknown> = {
versions: versionList,
};
// Write updated Version List
await fs.promises.writeFile(versionsFilePath, versionList);
const issueTemplatesFolder: string = upath.join(rootDirectory, ".github", "ISSUE_TEMPLATE");
// Write to issue templates
for (const fileName of fileNames) {
const readPath = upath.join(templatesFolder, fileName);
const writePath = upath.join(issueTemplatesFolder, fileName);
await modifyFile(readPath, [writePath], replacementObject);
}
}
export async function updateRandomPatchesConfig(): Promise<void> {
// Filename & paths
const fileName = "randompatches.cfg";
const readPath: string = upath.join(templatesFolder, fileName);
const writePathsNormal: string[] = [
upath.join(rootDirectory, configFolder, fileName),
upath.join(rootDirectory, configOverridesFolder, "normal", fileName),
];
// Replacement object
const replacementObject: Record<string, unknown> = {
version: version,
mode: "Normal",
};
// Modify Normal File
await modifyFile(readPath, writePathsNormal, replacementObject);
// Change values for Expert Config
replacementObject["mode"] = "Expert";
const writePathExpert = upath.join(rootDirectory, configOverridesFolder, "expert", fileName);
// Modify Expert File
await modifyFile(readPath, [writePathExpert], replacementObject);
}
export async function updateServerProperties(): Promise<void> {
// File name of the output files
const fileName = "server.properties";
// File name of the Normal Template File
const fileNameNormal = "server_normal.properties";
// File name of the Expert Template File
const fileNameExpert = "server_expert.properties";
// Replacement Object
const replacementObject: Record<string, unknown> = {
version: version,
};
// Read and Write paths for normal
const readPathNormal: string = upath.join(templatesFolder, fileNameNormal);
const writePathsNormal: string[] = [
upath.join(rootDirectory, "serverfiles", fileName),
upath.join(rootDirectory, configOverridesFolder, "normal", fileName),
];
// Modify Normal File
await modifyFile(readPathNormal, writePathsNormal, replacementObject);
// Read and Write paths for expert
const readPathExpert: string = upath.join(templatesFolder, fileNameExpert);
const writePathExpert: string = upath.join(rootDirectory, configOverridesFolder, "expert", fileName);
// Modify Expert File
await modifyFile(readPathExpert, [writePathExpert], replacementObject);
}
export const updateAll = gulp.series(updateIssueTemplates, updateRandomPatchesConfig, updateServerProperties);

View File

@ -43,4 +43,4 @@ export const zipClient = makeZipper(clientDestDirectory, "Client");
export const zipLang = makeZipper(langDestDirectory, "Lang");
export const zipMMC = makeZipper(mmcDestDirectory, "MMC");
export const zipAll = gulp.series(zipServer, zipClient, zipLang);
export const zipAll = gulp.series(zipServer, zipClient, zipLang, zipMMC);

View File

@ -1,8 +1,10 @@
import { clientDestDirectory, mmcDestDirectory, modpackManifest } from "../../globals";
import { clientDestDirectory, mmcDestDirectory, modDestDirectory, modpackManifest } from "../../globals";
import { fetchMods } from "../../util/curseForgeAPI";
import * as upath from "upath";
import { series, src, symlink } from "gulp";
import * as fs from "fs";
import gulp from "gulp";
import buildConfig from "../../buildConfig";
async function mmcCleanUp(cb) {
if (fs.existsSync(mmcDestDirectory)) {
@ -13,7 +15,7 @@ async function mmcCleanUp(cb) {
}
/**
* Checks and creates all necessary directories so we can build the client safely.
* Checks and creates all necessary directories so we can build the MMC zip safely.
*/
async function createMMCDirs(cb) {
if (!fs.existsSync(mmcDestDirectory)) {
@ -23,6 +25,28 @@ async function createMMCDirs(cb) {
cb();
}
/**
* Copies the update notes file.
*/
function copyMMCUpdateNotes() {
return gulp.src("../UPDATENOTES.md", { allowEmpty: true }).pipe(gulp.dest(mmcDestDirectory));
}
/**
* Copies the license file.
*/
async function copyMMCLicense() {
return gulp.src("../LICENSE.md").pipe(gulp.dest(mmcDestDirectory));
}
/**
* Copies the changelog file.
*/
function copyMMCChangelog() {
return gulp.src(upath.join(buildConfig.buildDestinationDirectory, "CHANGELOG.md")).pipe(gulp.dest(mmcDestDirectory));
}
/**
* Copies modpack overrides.
*/
@ -34,7 +58,7 @@ function copyOverrides() {
}
/**
* Copies modpack overrides.
* Renames copied overrides to '.minecraft'.
*/
async function renameOverrides() {
await fs.promises.rename(upath.join(mmcDestDirectory, "overrides"), upath.join(mmcDestDirectory, ".minecraft"));
@ -42,13 +66,13 @@ async function renameOverrides() {
}
/**
* Downloads client mods according to manifest.json and checks hashes.
* Copies client & shared mods.
*/
async function fetchModJars() {
return fetchMods(
modpackManifest.files.filter((f) => !f.sides || f.sides.includes("client")),
upath.join(mmcDestDirectory, ".minecraft"),
);
async function copyMMCModJars() {
return src([upath.join(modDestDirectory, "*"), upath.join(modDestDirectory, "client", "*")], {
nodir: true,
resolveSymlinks: false,
}).pipe(symlink(upath.join(mmcDestDirectory, ".minecraft", "mods")));
}
async function createMMCConfig() {
@ -114,5 +138,5 @@ export default series(
renameOverrides,
createMMCConfig,
createMMCManifest,
fetchModJars,
copyMMCModJars,
);

View File

@ -10,7 +10,13 @@ import Bluebird from "bluebird";
import { ForgeProfile } from "../../types/forgeProfile";
import { FileDef } from "../../types/fileDef";
import { downloadOrRetrieveFileDef, getVersionManifest, libraryToPath, relative } from "../../util/util";
import { modpackManifest, serverDestDirectory, sharedDestDirectory } from "../../globals";
import {
mmcDestDirectory,
modDestDirectory,
modpackManifest,
serverDestDirectory,
sharedDestDirectory
} from "../../globals";
import del from "del";
import { VersionManifest } from "../../types/versionManifest";
import { fetchMods } from "../../util/curseForgeAPI";
@ -25,7 +31,7 @@ async function serverCleanUp() {
}
/**
* Checks and creates all necessary directories so we can build the client safely.
* Checks and creates all necessary directories so we can build the server safely.
*/
async function createServerDirs() {
if (!fs.existsSync(serverDestDirectory)) {
@ -175,13 +181,13 @@ async function downloadMinecraftServer() {
}
/**
* Downloads mods according to manifest.json and checks hashes.
* Copies server & shared mods.
*/
async function downloadMods() {
return fetchMods(
modpackManifest.files.filter((f) => !f.sides || f.sides.includes("server")),
serverDestDirectory,
);
async function copyServerMods() {
return src([upath.join(modDestDirectory, "*"), upath.join(modDestDirectory, "server", "*")], {
nodir: true,
resolveSymlinks: false,
}).pipe(symlink(upath.join(serverDestDirectory, "mods")));
}
/**
@ -218,7 +224,7 @@ function copyServerUpdateNotes() {
* Copies the changelog file.
*/
function copyServerChangelog() {
return src(upath.join(sharedDestDirectory, "CHANGELOG.md")).pipe(dest(serverDestDirectory));
return src(upath.join(buildConfig.buildDestinationDirectory, "CHANGELOG.md")).pipe(dest(serverDestDirectory));
}
/**
@ -259,7 +265,7 @@ export default gulp.series([
createServerDirs,
downloadForge,
downloadMinecraftServer,
downloadMods,
copyServerMods,
copyServerOverrides,
copyServerfiles,
copyServerLicense,

View File

@ -2,18 +2,11 @@ import fs from "fs";
import gulp from "gulp";
import upath from "upath";
import buildConfig from "../../buildConfig";
import { modpackManifest, overridesFolder, sharedDestDirectory, tempDirectory } from "../../globals";
import { modDestDirectory, modpackManifest, overridesFolder, sharedDestDirectory, tempDirectory } from "../../globals";
import del from "del";
import { FileDef } from "../../types/fileDef";
import Bluebird from "bluebird";
import {
compareAndExpandManifestDependencies,
downloadOrRetrieveFileDef,
getChangeLog,
getFileAtRevision,
getLastGitTag,
relative,
} from "../../util/util";
import { downloadFileDef, downloadOrRetrieveFileDef, isEnvVariableSet, relative } from "../../util/util";
async function sharedCleanUp() {
await del(upath.join(sharedDestDirectory, "*"), { force: true });
@ -51,7 +44,7 @@ async function copyOverrides() {
async function fetchExternalDependencies() {
const dependencies = modpackManifest.externalDependencies;
if (dependencies) {
const destDirectory = upath.join(sharedDestDirectory, overridesFolder, "mods");
const destDirectory = upath.join(modDestDirectory, "mods");
if (!fs.existsSync(destDirectory)) {
await fs.promises.mkdir(destDirectory, { recursive: true });
@ -88,90 +81,62 @@ async function fetchExternalDependencies() {
}
/**
* Generates a changelog based on environmental variables.
* Either fetches the Changelog File, or makes one.
*/
async function makeChangelog() {
let since = getLastGitTag(),
to: string;
// If this is a tagged build, fetch the tag before last.
if (process.env.GITHUB_TAG) {
since = getLastGitTag(process.env.GITHUB_TAG);
to = process.env.GITHUB_TAG;
async function fetchOrMakeChangelog() {
if (isEnvVariableSet("CHANGELOG_URL") && isEnvVariableSet("CHANGELOG_CF_URL")) {
console.log("Using Changelog Files from URL.");
await downloadChangelogs(process.env.CHANGELOG_URL, process.env.CHANGELOG_CF_URL);
return;
}
// Back-compat in case this crap is still around.
else if (since == "latest-dev-preview") {
since = getLastGitTag(since);
}
const old = JSON.parse(getFileAtRevision("manifest.json", since)) as ModpackManifest;
const current = modpackManifest;
const commitList = getChangeLog(since, to, [upath.join("..", modpackManifest.overrides), "manifest.json"]);
const builder: string[] = [];
// If the UPDATENOTES.md file is present, prepend it verbatim.
if (fs.existsSync("../UPDATENOTES.md")) {
builder.push((await fs.promises.readFile("../UPDATENOTES.md")).toString());
}
// Push the title.
builder.push(`# Changes since ${since}`);
const comparisonResult = await compareAndExpandManifestDependencies(old, current);
// Push mod update blocks.
[
{
name: "## New mods",
list: comparisonResult.added,
},
{
name: "## Updated mods",
list: comparisonResult.modified,
},
{
name: "## Removed mods",
list: comparisonResult.removed,
},
].forEach((block) => {
if (block.list.length == 0) {
return;
}
builder.push("");
builder.push(block.name);
builder.push(
...block.list
// Yeet invalid project names.
.filter((project) => !/project-\d*/.test(project))
.sort()
.map((name) => `* ${name}`),
if (isEnvVariableSet("CHANGELOG_BRANCH")) {
console.log("Using Changelog Files from Branch.");
const url = "https://raw.githubusercontent.com/Nomi-CEu/Nomi-CEu/{{ branch }}/{{ filename }}";
await downloadChangelogs(
mustache.render(url, { branch: process.env.CHANGELOG_BRANCH, filename: "CHANGELOG.md" }),
mustache.render(url, { branch: process.env.CHANGELOG_BRNACH, filename: "CHANGELOG_CF.md" }),
);
});
// Push the changelog itself.
if (commitList) {
builder.push("");
builder.push("## Commits");
builder.push(commitList);
return;
}
// Check if the builder only contains the title.
if (builder.length == 1) {
builder.push("");
builder.push("There haven't been any changes.");
}
return fs.promises.writeFile(upath.join(sharedDestDirectory, "CHANGELOG.md"), builder.join("\n"));
console.log("Creating Changelog Files.");
await createBuildChangelog();
}
import transforms from "./transforms";
import { ModpackManifest } from "../../types/modpackManifest";
async function downloadChangelogs(changelogURL: string, changelogCFURL: string) {
const changelog = await downloadFileDef({ url: changelogURL });
const changelogCF = await downloadFileDef({ url: changelogCFURL });
await writeToChangelog(changelog, "CHANGELOG.md", changelogURL);
await writeToChangelog(changelogCF, "CHANGELOG_CF.md", changelogCFURL);
}
async function writeToChangelog(buffer: Buffer, changelogFile: string, url: string) {
let handle: fs.promises.FileHandle;
try {
handle = await fs.promises.open(upath.join(buildConfig.buildDestinationDirectory, changelogFile), "w");
await handle.write(buffer);
await handle.close();
} catch (err) {
if (handle && (await handle.stat()).isFile()) {
log(`Couldn't download changelog from URL ${url}, cleaning up...`);
await handle.close();
}
throw err;
}
}
import transformVersion from "./transformVersion";
import { createBuildChangelog } from "../changelog/createChangelog";
import mustache from "mustache";
import log from "fancy-log";
export default gulp.series(
sharedCleanUp,
createSharedDirs,
copyOverrides,
makeChangelog,
fetchOrMakeChangelog,
fetchExternalDependencies,
...transforms,
transformVersion,
);

View File

@ -0,0 +1,25 @@
import { modpackManifest } from "../../globals";
/**
* Transform the version field of manifest.json.
*/
export default async function transformManifestVersion(): Promise<void> {
// We're building a tag.
if (process.env.GITHUB_TAG) {
modpackManifest.version = process.env.GITHUB_TAG.replace(/^v/, "");
}
// If SHA is provided and the build isn't tagged, append both the branch and short SHA.
else if (process.env.GITHUB_SHA && process.env.GITHUB_REF && process.env.GITHUB_REF.startsWith("refs/heads/")) {
const shortCommit = process.env.GITHUB_SHA.substring(0, 7);
const branch = /refs\/heads\/(.+)/.exec(process.env.GITHUB_REF)?.[1];
if (!branch) {
throw new Error(`Invalid git ref: ${process.env.GITHUB_REF}`);
}
modpackManifest.version = `${branch}-${shortCommit}`;
} else {
modpackManifest.version = "manual-build";
}
modpackManifest.name = "";
}

View File

@ -1,4 +0,0 @@
import scannable from "./scannable";
import version from "./version";
export default [scannable, version];

View File

@ -1,28 +0,0 @@
import { overridesFolder, sharedDestDirectory } from "../../../globals";
import upath from "upath";
import fs from "fs";
const scannableConfigFile = "config/scannable.cfg";
/**
* Transform the scannable config.
* Trim excess newlines and remove comments.
*/
export default async function transformScannable(): Promise<void> {
const configPath = upath.join(sharedDestDirectory, overridesFolder, scannableConfigFile);
const contents = (await fs.promises.readFile(configPath))
.toString()
// Match arrays (S:array < ... >)
.replace(/([ \t]+)(S:\w+) <([^>]+)>/g, (_, g0, g1, g2) => {
const body = g2
.replace(/#[^\r\n]+/gm, "") // Comments
.replace(/^\s+$/gm, "") // Trailing whitespaces
.replace(/[\r\n]{2,}/gm, "\n"); // Extra newlines
return g0 + g1 + " <" + body + (body ? "" : "\n") + g0 + " >";
});
return fs.promises.writeFile(configPath, contents);
}

View File

@ -1,64 +0,0 @@
import fs from "fs";
import upath from "path";
import mustache from "mustache";
import { modpackManifest, overridesFolder, sharedDestDirectory } from "../../../globals";
const randomPatchesConfigFile = "config/randompatches.cfg";
/**
* Transform the version field of manifest.json.
*/
export default async function transformManifestVersion(): Promise<void> {
let versionTitle;
// We're building a tag.
if (process.env.GITHUB_TAG) {
const flavorTitle = process.env.BUILD_FLAVOR_TITLE;
const tag = process.env.GITHUB_TAG.replace(/^v/, "");
versionTitle = [modpackManifest.name, tag, flavorTitle].filter(Boolean).join(" - ");
modpackManifest.version = tag;
}
// If we're building a release candidate, transform it appropriately.
else if (process.env.RC_VERSION) {
const rcVer = process.env.RC_VERSION;
const flavorTitle = process.env.BUILD_FLAVOR_TITLE;
const tag = rcVer.replace(/^v/, "");
versionTitle = [modpackManifest.name, [tag, "Release Candidate"].join(" "), flavorTitle]
.filter(Boolean)
.join(" - ");
modpackManifest.version = rcVer;
// modpackManifest.version = [rcVer, "rc"].join("-"); // No need for rc at end of name
}
// If SHA is provided and the build isn't tagged, append both the branch and short SHA.
else if (process.env.GITHUB_SHA && process.env.GITHUB_REF && process.env.GITHUB_REF.startsWith("refs/heads/")) {
const shortCommit = process.env.GITHUB_SHA.substr(0, 7);
const branch = /refs\/heads\/(.+)/.exec(process.env.GITHUB_REF)?.[1];
if (!branch) {
throw new Error(`Invalid git ref: ${process.env.GITHUB_REF}`);
}
versionTitle = `${modpackManifest.name} (${branch} branch, ${shortCommit})`;
modpackManifest.version = `${branch}-${shortCommit}`;
} else {
versionTitle = `${modpackManifest.name} (manual build)`;
modpackManifest.version = "manual-build";
}
modpackManifest.name = versionTitle;
const randomPatchesConfigFilePath = upath.join(sharedDestDirectory, overridesFolder, randomPatchesConfigFile);
const randomPatchesFile = (await fs.promises.readFile(randomPatchesConfigFilePath)).toString();
return fs.promises.writeFile(
randomPatchesConfigFilePath,
mustache.render(randomPatchesFile, {
title: versionTitle,
}),
);
}

View File

@ -0,0 +1,103 @@
name: Bug Report
description: "Crashes or unintended behaviors arising from Nomi CEu's mods, configurations, or custom scripts."
labels: bug
body:
- type: markdown
attributes:
value: "Note: If you need general tech support for things like server configuration, or general game support (how to I make this?), Discord is a better venue. Please open an issue only if there is a clear bug with the pack or if you have been asked to by one of the Discord staff."
- type: dropdown
id: version
attributes:
label: Nomi CEu Version
description: The version of Nomi CEu you were using when this bug was encountered. If you do not know what it is, check the title of your instance window. If you do not see your version here, please update to the newest version of the pack, which currently is 1.6, or the newest alpha/beta, which currently is 1.6.1-beta-2.
options:
{{versions}}
validations:
required: true
- type: input
id: launcher
attributes:
label: Launcher
description: "What launcher you were using when you experienced this issue. If you were using the CurseForge Launcher, please try to see if the issue occurs on a different launcher."
placeholder: "Example: Prism Launcher"
validations:
required: true
- type: textarea
id: changed
attributes:
label: Configurations or Mods Changed
description: Any changed configs, and removed or added mods. Make sure to also include the version of any mods you have added. If you have not changed any configurations, and not removed or added any mods, please leave this field blank.
placeholder: |
Example:
Changed Loliasm Config: B:onDemandAnimatedTextures to false.
Added AE2 Fluid Crafting Rework: Version 2.4.18-r.
validations:
required: false
- type: input
id: environment
attributes:
label: Environment
description: "How you were playing on the world. Typical answers include: Singleplayer, Open to LAN, Forge Server, Sponge Server, or Mohist Server."
placeholder: "Example: Singleplayer"
validations:
required: true
- type: dropdown
id: mode
attributes:
label: Mode
description: "What pack mode were you using when you came across this error?"
options:
- "Normal Mode"
- "Expert Mode"
- "Both Modes"
- "N/A"
validations:
required: true
- type: textarea
id: problem
attributes:
label: What Happened
description: What happened, of which you believe is a bug. Attach screenshots here as necessary.
placeholder: "Example: Produced one X but Y was not consumed."
validations:
required: true
- type: textarea
id: expected
attributes:
label: Expected Behavior
description: What you expected to happen. Attach screenshots here as necessary.
placeholder: "Example: Expected to produce X by consuming Y."
validations:
required: true
- type: textarea
id: reproduction
attributes:
label: Reproduction Steps
description: "How do you trigger this bug? Please walk us through it step by step."
placeholder: |
1.
2.
3.
...
validations:
required: true
- type: textarea
id: logs
attributes:
label: Logs
description: "If your client crashed as a result of this bug, please upload the generated crash log. This is found in your launcher's Nomi CEu instance, in a folder called `crash-reports`. If there was no crash, but instead an error screen, please upload your `latest.log`, found in a folder called `logs`. If there was a script error, please upload your `crafttweaker.log`, found in your base instance folder, instead. Otherwise, please leave this field blank."
placeholder: "You can upload into a external site like paste-bin, and send the link, or just drag the file into this text field."
validations:
required: false
- type: textarea
id: additional-info
attributes:
label: Additional Information
description: Any additional information you wish to provide. Please add anything which did not fit into the other sections here.
placeholder: "Example: This is likely caused by X because..."
validations:
required: false
- type: markdown
attributes:
value: Thank you for taking the time to fill out this bug report.

View File

@ -0,0 +1,62 @@
name: Feature Request
description: Suggest an idea, including mod additions or addon scripts, for Nomi CEu.
labels: enhancement
body:
- type: markdown
attributes:
value: "Note: balancing ideas should use this template, but only for changes. However, if its a problem, not a change, like `X material always runs out`, please use the `Bug Report` Template."
- type: dropdown
id: version
attributes:
label: Nomi CEu Version
description: The version of Nomi CEu you are using as the basis for this feature request. If you do not know what it is, check the title of your instance window. If you do not see your version here, please update to the newest version of the pack, which currently is 1.6, or the newest alpha/beta, which currently is 1.6.1-beta-2.
options:
{{versions}}
validations:
required: true
- type: textarea
id: problem
attributes:
label: Related Problem
description: If the feature you wish to change is related to a problem, please desscribe it. Leave this field blank if it is not related to a problem.
placeholder: "Example: I'm always frustrated when..."
validations:
required: false
- type: textarea
id: solution
attributes:
label: Your Solution
description: Describe the solution you would like to have happen.
placeholder: "Example: If I could..."
validations:
required: true
- type: textarea
id: work
attributes:
label: How Will Your Solution Work
description: |
Describe how the solution will fix the problem, or add to the pack.
If you are suggesting a mod, please describe what the mod does, and how it will add to the pack.
placeholder: "Example: If X was done, than Y will get a use, and..."
validations:
required: true
- type: textarea
id: alternatives
attributes:
label: "Alternatives You've Considered"
description: "What alternatives have you considered that might help solve the problem? If you aren't sure, or this is not related to a problem, please leave this field blank."
placeholder: "Example: Otherwise, we can also do Z..."
validations:
required: true
- type: textarea
id: additional-info
attributes:
label: Additional Information
description: "Add any other context or screenshots about the feature request here. If you are suggesting a mod, please also add a link to the mod's CurseForge page."
placeholder: "Example: This main motive for this idea is because X..."
validations:
required: false
- type: markdown
attributes:
value: Thank you for taking the time to fill out this feature request.

View File

@ -0,0 +1,272 @@
# Configuration file
##########################################################################################################
# boats
#--------------------------------------------------------------------------------------------------------#
# Options related to boats.
##########################################################################################################
boats {
# Whether to patch EntityBoat.
# Default: true
B:patchEntityBoat=true
# Prevents underwater boat passengers from being ejected after 60 ticks (3 seconds).
# Default: false
B:preventUnderwaterBoatPassengerEjection=false
# The buoyancy of boats when they are under flowing water.
# The vanilla default is -0.0007.
# Min: -1.7976931348623157E308
# Max: 1.7976931348623157E308
# Default: 0.023
D:underwaterBoatBuoyancy=0.023
}
##########################################################################################################
# client
#--------------------------------------------------------------------------------------------------------#
# Options related to client-sided features.
##########################################################################################################
client {
# Adds a separate keybind for dismounting.
# Default: true
B:dismountKeybind=false
# Speeds up language switching.
# Default: true
B:fastLanguageSwitch=true
# Forces Minecraft to show the title screen after disconnecting rather than the Multiplayer or Realms menu.
# Default: false
B:forceTitleScreenOnDisconnect=true
# The framerate limit slider step size.
# If this is set to 10.0, vanilla behavior is not changed.
# Min: 4.9E-324
# Max: 260.0
# Default: 1.0
D:framerateLimitSliderStepSize=1.0
# Whether to fix the player model occasionally disappearing when flying with elytra in a straight line in third-person mode.
# Default: true
B:invisiblePlayerModelFix=true
# Whether to add the Toggle Narrator keybind to the controls.
# Default: true
B:narratorKeybind=true
# Set this to false to disable the Minecraft class patches (the Toggle Narrator keybind and custom window title/icon).
# Default: true
B:patchMinecraftClass=true
# Set this to false to force disable the "force title screen on disconnect" patch.
# Default: true
B:patchTitleScreenOnDisconnect=true
# Whether to apply the potion glint patch so that the potion glowing effect can be toggled.
# Default: true
B:patchPotionGlint=false
# Whether to remove the glowing effect from potions.
# Default: false
B:removePotionGlint=false
# Backports the smooth eye level change animations from Minecraft 1.13 and newer.
# Default: true
B:patchSmoothEyeLevelChanges=true
# Whether smooth eye level change animations should be enabled.
# Default: true
B:smoothEyeLevelChanges=true
# Enables the /rpreloadclient command.
# Default: true
B:rpreloadclient=true
##########################################################################################################
# window
#--------------------------------------------------------------------------------------------------------#
# Options related to the Minecraft window.
##########################################################################################################
window {
# The path to the 16x16 Minecraft window icon.
# Leave this and the 32x32 icon blank to use the default icon.
# Default:
S:icon16=resources/modpack/textures/logo/256x.png
# The path to the 32x32 Minecraft window icon.
# Leave this and the 16x16 icon blank to use the default icon.
# Default:
S:icon32=resources/modpack/textures/logo/256x.png
# The path to the 256x256 window icon which is used on Mac OS X.
# Leave this, the 16x16 icon and the 32x32 icon blank to use the default icon.
# Default:
S:icon256=resources/modpack/textures/logo/256x.png
# The Minecraft window title.
# Default: Minecraft 1.12.2
S:title=Nomifactory CEu, v{{version}}, {{mode}} Mode)
}
}
##########################################################################################################
# misc
#--------------------------------------------------------------------------------------------------------#
# Options that don't fit into any other categories.
##########################################################################################################
misc {
# Whether to prevent the observer from emitting a signal when it is placed.
# This fixes MC-109832.
# Default: true
B:disableObserverSignalOnPlace=true
# Whether to fix dismount positions being too high.
# This fixes MC-3328 and MC-111726.
# Default: true
B:dismountPositionFix=true
# Fixes the End portal and End gateway break particle textures and improves End portal rendering.
# Default: true
B:endPortalTweaks=true
# Whether to patch WorldServer to prevent a "TickNextTick list out of synch" IllegalStateException.
# Default: true
B:fixTickNextTickListOutOfSynch=true
# Fixes MC-2025.
# More information can be found here: https://www.reddit.com/r/Mojira/comments/8pgd4q/final_and_proper_fix_to_mc2025_simple_reliable/
# Default: true
B:mc2025Fix=true
# Fixes MC-64836, which causes non-player entities to be allowed to control minecarts using their AI.
# Default: true
B:minecartAIFix=true
# Fixes MC-5694, which causes fast mining to sometimes only destroy blocks client-side only.
# Default: true
B:miningGhostBlocksFix=true
# Fixes MC-10369 (server-side particle spawning not creating particles for clients) and MC-93826 (breeding hearts only showing once instead of all of the time an animal can breed).
# Default: true
B:particleFixes=true
# Set this to false to disable the NetHandlerPlayServer patches (the speed limits and disconnect timeouts).
# Default: true
B:patchNetHandlerPlayServer=true
# Whether to patch the packet size limit.
# Default: true
B:patchPacketSizeLimit=false
# The packet size limit.
# The vanilla limit is 2097152.
# Min: 257
# Max: 2147483647
# Default: 16777216
I:packetSizeLimit=16777216
# Fixes MC-54026, which causes blocks attached to slime blocks in some circumstances to create ghost blocks if a piston pushes the slime block.
# Default: true
B:pistonGhostBlocksFix=true
# Fixes MC-11944, which allows players to replace End portals, End gateways and Nether portals using buckets.
# Default: true
B:portalBucketReplacementFix=true
# Enables the portal bucket replacement fix for Nether portals.
# Default: false
B:portalBucketReplacementFixForNetherPortals=false
# Fixes MC-129057, which prevents ingredients with NBT data from being transferred to the crafting grid when a recipe is clicked in the recipe book.
# Default: true
B:recipeBookNBTFix=true
# Enables the /rpreload command.
# Default: true
B:rpreload=true
# Fixes player skull stacking.
# Default: true
B:skullStackingFix=true
# Whether skull stacking requires the same textures or just the same player profile.
# Default: true
B:skullStackingRequiresSameTextures=true
}
##########################################################################################################
# speedLimits
#--------------------------------------------------------------------------------------------------------#
# Options related to the movement speed limits.
##########################################################################################################
speedLimits {
# The maximum player speed.
# The vanilla default is 100.0.
# Min: 1.0
# Max: 3.4028234663852886E38
# Default: 1000000.0
D:maxPlayerSpeed=1000000.0
# The maximum player elytra speed.
# The vanilla default is 300.0.
# Min: 1.0
# Max: 3.4028234663852886E38
# Default: 1000000.0
D:maxPlayerElytraSpeed=1000000.0
# The maximum player vehicle speed.
# The vanilla default is 100.0.
# Min: 1.0
# Max: 1.7976931348623157E308
# Default: 1000000.0
D:maxPlayerVehicleSpeed=1000000.0
}
##########################################################################################################
# timeouts
#--------------------------------------------------------------------------------------------------------#
# Options related to the disconnect timeouts.
##########################################################################################################
timeouts {
# The interval at which the server sends the KeepAlive packet.
# Min: 1
# Max: 2147483647
# Default: 15
I:keepAlivePacketInterval=15
# The login timeout in ticks.
# Min: 1
# Max: 2147483647
# Default: 1800
I:loginTimeout=1800
# Whether to apply the login timeout.
# Default: true
B:patchLoginTimeout=true
# The read timeout in seconds.
# This is the time it takes for a player to be disconnected after not responding to a KeepAlive packet.
# This value is automatically rounded up to a product of keepAlivePacketInterval.
# Min: 1
# Max: 2147483647
# Default: 90
I:readTimeout=90
# Whether to patch NetworkManager to apply the client-sided read timeout.
# Default: true
B:patchNetworkManager=true
}

View File

@ -0,0 +1,32 @@
#Minecraft server properties
op-permission-level=4
level-name=world
allow-flight=true
prevent-proxy-connections=false
server-port=25565
max-world-size=29999984
level-seed=
force-gamemode=false
server-ip=
network-compression-threshold=256
max-build-height=256
spawn-npcs=true
white-list=false
spawn-animals=true
hardcore=false
snooper-enabled=true
resource-pack-sha1=
online-mode=true
resource-pack=
pvp=true
difficulty=0
enable-command-block=false
gamemode=0
player-idle-timeout=0
max-players=20
spawn-monsters=true
view-distance=10
generate-structures=true
motd=Nomi CEu Server, v{{version}}, Expert Mode
level-type=lostcities
generator-settings=

View File

@ -0,0 +1,32 @@
#Minecraft server properties
op-permission-level=4
level-name=world
allow-flight=true
prevent-proxy-connections=false
server-port=25565
max-world-size=29999984
level-seed=
force-gamemode=false
server-ip=
network-compression-threshold=256
max-build-height=256
spawn-npcs=true
white-list=false
spawn-animals=true
hardcore=false
snooper-enabled=true
resource-pack-sha1=
online-mode=true
resource-pack=
pvp=true
difficulty=1
enable-command-block=false
gamemode=0
player-idle-timeout=0
max-players=20
spawn-monsters=true
view-distance=10
generate-structures=true
motd=Nomi CEu Server, v{{version}}, Normal Mode
level-type=lostcities
generator-settings=

View File

@ -0,0 +1,13 @@
- 1.6.1a
- 1.6.1-beta-4
- 1.6.1-beta-3a
- 1.6.1-beta-2
- 1.6.1-alpha-1
- 1.6
- 1.5.2
- 1.5.1
- 1.5
- 1.4.3
- 1.4.2
- 1.4.1a
- 1.4

View File

@ -0,0 +1,242 @@
export interface Commit {
hash: string;
date: string;
message: string;
refs: string;
body: string;
author_name: string;
author_email: string;
}
/**
* A Changelog Category.
*/
export interface Category {
/**
* Commit Key: The key used in the commit's body.
* <p>
* Optional. If not set, then commits cannot be added to this category during the parse commit task.
* Can still be added manually.
*/
commitKey?: string;
/**
* Key Name: The title of this Category in the changelog.
* <p>
* Can be set to "" to have no title.
*/
categoryName: string;
/**
* Changelog Section: The changelog section map that the key should push to.
* <p>
* Will be initialized later, if put into categoryKeys.
*/
changelogSection?: Map<SubCategory, ChangelogMessage[]>;
/**
* Default Sub Category. Any commits not placed into other sub-categories will be placed in here.
* <p>
* Should be a Sub Category added to subCategoryKeys, as otherwise the category would not appear in the changelog.
* <p>
* This can also be done with a SubCategoryKey placed at the end, with the commitKey set to `""`.
* However, this is useful for places where the Default Sub Category should not be at the end.
* <p>
* This is also needed for certain parsing operations.
*/
defaultSubCategory: SubCategory;
/**
* Sub Category Keys: The list of sub-category keys.
* <p>
* Commits being added can only be in one sub-category, and the priority will be in the order provided.
* Furthermore, the order provided will also be the order the commits appear in.
* <p>
* The last item on this list should have the `commitKey` set to "", to allow any commits not put into previous sub categories in, otherwise they would be ignored.
* However, this can also be done by setting the defaultSubCategory.
*/
subCategories: SubCategory[];
}
/**
* A Sub Category.
*/
export interface SubCategory {
/**
* Commit Key: The key used in the commit's body.
* <p>
* This can be set to "" to allow any commit in.
* <p>
* Optional. If not set, then no commit will be allowed in during the parse commit task.
* Can still be added to by DefaultSubCategory, or manually.
*/
commitKey?: string;
/**
* Key Name: The key to be used in the changelogSection. Also will be the title of this subCategory in the changelog.
* <p>
* Can be set to "" to have no title.
*/
keyName: string;
}
/**
* A Changelog Message Object.
*/
export interface ChangelogMessage {
/**
* Commit Message
*/
commitMessage: string;
/**
* Commit Object
* <p>
* Provides the Commit SHA, the Commit Author, and the Commit Date.
*/
commitObject?: Commit;
/**
* Sub Changelog Messages
*/
subChangelogMessages?: ChangelogMessage[];
/**
* Indentation
* <p>
* Optional. Defaults to "".
*/
indentation?: string;
/**
* If this changelog message is special. This is special formatting for it.
*/
specialFormatting?: SpecialChangelogFormatting<unknown>;
}
/**
* A special changelog message object, for special formatting.
*/
export interface SpecialChangelogFormatting<T> {
/**
* Formatting Function
*/
formatting: (message: ChangelogMessage, storage?: T) => string;
/**
* Storage
*/
storage: T;
}
/**
* A parsing category, which defines parsing rules and callbacks for different dirs.
*/
export interface Parser {
/**
* Dirs to parse. If not set, will just parse commit list of all changes.
*/
dirs?: string[];
/**
* Callback to determine whether a commit should be skipped.
* <p>
* If skipped, then all further parsing for the commit will stop. This condition does not include commits which are in the sha list, they are automatically skipped.
* <p>
* Expanded Commits from parseExpand go here too!<p><p>
* commit: The commit object.<p>
* commitMessage: The message of the commit.<p>
* commitBody: The body of the commit. Might be undefined.<p>
* return: True to skip, false to not.
*/
skipCallback: (commit: Commit, commitMessage: string, commitBody?: string) => boolean;
/**
* Callback per item.
* <p>
* Expanded Commits from parseExpand go here too!<p><p>
* parser: This parser object, for convenience of use when calling parseCommitBody.
* commit: The commit object.
* commitMessage: The message of the commit.<p>
* commitBody: The body of the commit. Might be undefined.<p>
* return: True if parsing was successful, false if not.
*/
itemCallback: (parser: Parser, commit: Commit, commitMessage: string, commitBody?: string) => Promise<boolean>;
/**
* The callback to perform on any commits, which did not pass parsing. If not set, no callback will be performed, and those commits will be discarded.
* <p>
* Expanded Commits from parseExpand and parseDetails go here too!<p><p>
* commit: The commit object.<p>
* commitMessage: The message of the commit.<p>
* commitBody: The body of the commit. Might be undefined.<p>
* subMessages: Any sub-messages, coming from parseDetails. Might be undefined.
*/
leftOverCallback?: (
commit: Commit,
commitMessage: string,
commitBody?: string,
subMessages?: ChangelogMessage[],
) => void;
/**
* Callback to determine whether to add the sha of that commit into the sha list, forbidding further parsing of it.
* <p>
* If not set, will just add SHA of every commit included in `dirs`.<p><p>
* commit: The commit object.<p>
* parsed: If parsing was successful. This is also true if the commit was skipped.<p>
* return: True if to add sha, false if to not.<p>
*/
addSHACallback?: (commit: Commit, parsed: boolean) => boolean;
/**
* Callback to determine whether or not the commit should be added to the commit list.
* <p><p>
* commit: The commit to determine.<p>
* parsed: If parsing was successful.<p>
* return: True if to add, false if not.
*/
addCommitListCallback: (commit: Commit, parsed: boolean) => boolean;
}
export interface ModChangeInfo {
modName: string;
projectID?: number;
oldVersion?: string;
newVersion?: string;
}
export interface ExpandedMessage {
messageTitle: string;
messageBody?: string;
}
export interface FixUpInfo {
sha: string;
newTitle: string;
newBody?: string;
}
export type InputReleaseType = "Release" | "Beta Release" | "Alpha Release" | "Cutting Edge Build";
export interface DeployReleaseType {
isPreRelease: boolean;
cfReleaseType: "release" | "beta" | "alpha";
}
// Cutting Edge Build is not needed here, as this type is only used for deploying, and not building.
export const inputToDeployReleaseTypes: Record<InputReleaseType, DeployReleaseType> = {
Release: {
isPreRelease: false,
cfReleaseType: "release",
},
"Beta Release": {
isPreRelease: true,
cfReleaseType: "beta",
},
"Alpha Release": {
isPreRelease: true,
cfReleaseType: "alpha",
},
"Cutting Edge Build": undefined,
};

View File

@ -1,154 +1,121 @@
interface Author {
interface CurseForgeAuthor {
id: number;
name: string;
url: string;
projectId: number;
id: number;
projectTitleId?: number;
projectTitleTitle: string;
userId: number;
twitchId?: number;
}
interface Attachment {
id: number;
projectId: number;
description: string;
isDefault: boolean;
thumbnailUrl: string;
title: string;
url: string;
status: number;
}
export interface CurseForgeFileInfo {
id: number;
gameId: number;
modId: number;
isAvailable: boolean;
displayName: string;
fileName: string;
fileDate: Date;
fileLength: number;
releaseType: number;
fileStatus: number;
hashes: CurseForgeHash[];
fileDate: Date;
fileLength: number;
downloadCount: number;
fileSizeOnDisk: number | undefined;
downloadUrl: string;
isAlternate: boolean;
alternateFileId: number;
dependencies: Dependency[];
isAvailable: boolean;
modules: Module[];
gameVersion: string[];
sortableGameVersion: SortableGameVersion[];
hasInstallScript: boolean;
isCompatibleWithClient: boolean;
categorySectionPackageType: number;
restrictProjectFileAccess: number;
projectStatus: number;
renderCacheId: number;
packageFingerprintId: number;
gameVersionDateReleased: Date;
gameVersionMappingId: number;
gameVersionId: number;
gameId: number;
isServerPack: boolean;
gameVersions: string[];
sortableGameVersions: CurseForgeSortableGameVersion[];
dependencies: CurseForgeDependency[];
exposeAsAlternative: boolean | undefined;
parentProjectFileId: number | undefined;
alternateFileId: number | undefined;
isServerPack: boolean | undefined;
serverPackFileId: number | undefined;
isEarlyAccessContent: boolean | undefined;
earlyAccessEndDate: Date | undefined;
fileFingerprint: number;
modules: CurseForgeModule[];
}
interface Category {
categoryId: number;
name: string;
url: string;
avatarUrl: string;
parentId: number;
rootId: number;
projectId: number;
avatarId: number;
gameId: number;
}
interface CategorySection {
interface CurseForgeCategory {
id: number;
gameId: number;
name: string;
packageType: number;
path: string;
initialInclusionPattern: string;
gameCategoryId: number;
slug: string;
url: string;
iconUrl: string;
dateModified: Date;
isClass: boolean;
classId: number;
parentCategoryId: number;
}
interface GameVersionLatestFile {
interface CurseForgeFileIndex {
gameVersion: string;
projectFileId: number;
projectFileName: string;
fileType: number;
fileId: number;
fileName: string;
releaseType: number;
gameVersionTypeId: number;
modLoader: number;
}
interface CurseForgeLinkInfo {
websiteUrl: string;
wikiUrl: string;
issuesUrl: string;
sourceUrl: string;
}
interface CurseForgeAsset {
id: number;
modID: number;
title: string;
description: string;
thumbnailUrl: string;
url: string;
}
export interface CurseForgeModInfo {
id: number;
gameId: number;
name: string;
authors: Author[];
attachments: Attachment[];
websiteUrl: string;
gameId: number;
summary: string;
defaultFileId: number;
downloadCount: number;
latestFiles: CurseForgeFileInfo[];
categories: Category[];
status: number;
primaryCategoryId: number;
categorySection: CategorySection;
slug: string;
gameVersionLatestFiles: GameVersionLatestFile[];
isFeatured: boolean;
popularityScore: number;
gamePopularityRank: number;
primaryLanguage: string;
gameSlug: string;
gameName: string;
portalName: string;
dateModified: Date;
dateCreated: Date;
dateReleased: Date;
isAvailable: boolean;
isExperiemental: boolean;
}
export interface CurseForgeFetchedFileInfo {
id: number;
gameId: number;
modId: number;
isAvailable: boolean;
displayName: string;
fileName: string;
releaseType: number;
fileStatus: number;
hashes?: Hash[];
fileDate: Date;
fileLength: number;
links: CurseForgeLinkInfo;
summary: string;
status: number;
downloadCount: number;
downloadUrl?: string;
gameVersions: string[];
sortableGameVersions: SortableGameVersion[];
dependencies: Dependency[];
alternateFileId: number;
isServerPack: boolean;
fileFingerprint: number;
modules: Module[];
isFeatured: boolean;
primaryCategoryId: number;
categories: CurseForgeCategory[];
classId: number;
authors: CurseForgeAuthor[];
logo: CurseForgeAsset;
screenshots: CurseForgeAsset[];
mainFileId: number;
latestFiles: CurseForgeFileInfo[];
latestFilesIndexes: CurseForgeFileIndex[];
latestEarlyAccessFilesIndexes: CurseForgeFileIndex[];
dateCreated: Date;
dateModified: Date;
dateReleased: Date;
allowModDistribution: boolean;
gamePopularityRank: number;
isAvailable: boolean;
thumbsUpCount: number;
}
interface Dependency {
interface CurseForgeDependency {
modId: number;
relationType: number;
}
interface Hash {
interface CurseForgeHash {
value: string;
algo: number;
}
interface Module {
interface CurseForgeModule {
name: string;
fingerprint: number;
}
interface SortableGameVersion {
interface CurseForgeSortableGameVersion {
gameVersionName: string;
gameVersionPadded: string;
gameVersion: string;

View File

@ -17,5 +17,6 @@
"buildDestinationDirectory": "../build",
"buildSourceDirectory": "../",
"nightlyHookAvatar": "",
"nightlyHookName": ""
}
"nightlyHookName": "",
"screenshotsQuality": 90
}

View File

@ -1,5 +1,5 @@
import bluebird from "bluebird";
import { CurseForgeFetchedFileInfo, CurseForgeModInfo as CurseForgeProject } from "../types/curseForge";
import { CurseForgeFileInfo, CurseForgeModInfo as CurseForgeProject } from "../types/curseForge";
import log from "fancy-log";
import request from "requestretry";
import { ModpackManifestFile } from "../types/modpackManifest";
@ -43,22 +43,20 @@ export async function fetchProject(toFetch: number): Promise<CurseForgeProject>
throw new Error(`Failed to fetch project ${toFetch}`);
}
if (project) {
curseForgeProjectCache[toFetch] = project;
}
curseForgeProjectCache[toFetch] = project;
return project;
}
const fetchedFileInfoCache: { [key: string]: CurseForgeFetchedFileInfo } = {};
export async function fetchFileInfo(projectID: number, fileID: number): Promise<CurseForgeFetchedFileInfo> {
const fetchedFileInfoCache: { [key: string]: CurseForgeFileInfo } = {};
export async function fetchFileInfo(projectID: number, fileID: number): Promise<CurseForgeFileInfo> {
const slug = `${projectID}/${fileID}`;
if (fetchedFileInfoCache[slug]) {
return fetchedFileInfoCache[slug];
}
const fileInfo: CurseForgeFetchedFileInfo = (
const fileInfo: CurseForgeFileInfo = (
await request({
uri: `${buildConfig.cfCoreApiEndpoint}/v1/mods/${projectID}/files/${fileID}`,
json: true,
@ -73,17 +71,126 @@ export async function fetchFileInfo(projectID: number, fileID: number): Promise<
throw new Error(`Failed to download file ${projectID}/file/${fileID}`);
}
if (fileInfo) {
fetchedFileInfoCache[slug] = fileInfo;
fetchedFileInfoCache[slug] = fileInfo;
if (!fileInfo.downloadUrl) {
const fid = `${Math.floor(fileInfo.id / 1000)}/${fileInfo.id % 1000}`;
return fileInfo;
}
fileInfo.downloadUrl = `https://edge.forgecdn.net/files/${fid}/${fileInfo.fileName}`;
export interface ProjectToFileId {
projectID: number;
fileID: number;
}
/**
* Fetches multiple CurseForge files.
* Falls back to fetchFileInfo in case it's impossible to bulk-fetch some files.
*
* @param toFetch List of Project IDs to File IDs, to fetch.
* @returns CurseForge file infos.
*/
export async function fetchFilesBulk(toFetch: ProjectToFileId[]): Promise<CurseForgeFileInfo[]> {
const fileInfos: CurseForgeFileInfo[] = [];
// Map of file ids not fetched (project ID to file ID)
const unfetched: ProjectToFileId[] = [];
// Determine projects that have been fetched already.
toFetch.forEach((file) => {
const slug = `${file.projectID}/${file.fileID}`;
const cached = fetchedFileInfoCache[slug];
if (cached) fileInfos.push(cached);
else unfetched.push(file);
});
// Sort list (reduces risk of duplicate entries)
unfetched.sort((a, b) => a.fileID - b.fileID);
if (unfetched.length > 0) {
// Augment the array of known files with new info.
const fetched: CurseForgeFileInfo[] = (
await request.post({
uri: `${buildConfig.cfCoreApiEndpoint}/v1/mods/files`,
json: {
fileIds: unfetched.map((file) => file.fileID),
},
fullResponse: false,
maxAttempts: 5,
headers: {
"X-Api-Key": getCurseForgeToken(),
},
})
)?.data;
if (!fetched) {
throw new Error(
`Failed to bulk-fetch files:\n${unfetched
.map((file) => `File ${file.fileID} of mod ${file.projectID},`)
.join("\n")}`,
);
}
// Remove duplicate entries (Batch Fetch sometimes returns duplicate inputs... for some reason)
if (fetched.length > unfetched.length) {
// Can't directly use Set, as Set compares object ref, not object data
const uniqueFileIDs: number[] = [];
fetched.forEach((file) => {
if (!uniqueFileIDs.includes(file.id)) {
fileInfos.push(file);
uniqueFileIDs.push(file.id);
}
});
} else {
fileInfos.push(...fetched);
}
// Cache fetched stuff.
fetched.forEach((info) => {
fetchedFileInfoCache[`${info.modId}/${info.id}`] = info;
});
// In case we haven't received the proper amount of mod infos,
// try requesting them individually.
if (fileInfos.length < toFetch.length) {
// Set of fetched fileIDs.
const fileInfoIDs: Set<number> = new Set(
fileInfos.map((file) => {
return file.id;
}),
);
const toFetchMissing = [...new Set(toFetch.filter((x) => !fileInfoIDs.has(x.fileID)))];
if (toFetchMissing.length > 0) {
log.warn(
`Couldn't fetch next project IDs in bulk:\n${toFetchMissing
.map((file) => `File ${file.fileID} of mod ${file.projectID},`)
.join("\n")}`,
);
// Try fetching files individually, in case they've been deleted.
let count = 0;
const missingFileInfos: CurseForgeFileInfo[] = await bluebird.map(toFetchMissing, async (file) => {
log.info(
`Fetching file ${file.fileID} of mod ${file.projectID} directly... (${++count} / ${toFetchMissing.length})`,
);
try {
// In case something fails to download; catch, rewrite, rethrow.
return await fetchFileInfo(file.projectID, file.fileID);
} catch (err) {
err.message = `Couldn't fetch file ${file.fileID} of mod ${file.projectID}. ${
err.message || "Unknown error"
}`;
throw err;
}
});
// The code above is expected to throw and terminate the further execution,
// so we can just do this.
fileInfos.push(...missingFileInfos);
}
}
}
return fileInfo;
return fileInfos;
}
/**
@ -136,11 +243,11 @@ export async function fetchProjectsBulk(toFetch: number[]): Promise<CurseForgePr
// In case we haven't received the proper amount of mod infos,
// try requesting them individually.
if (unfetched.length !== toFetch.length) {
if (modInfos.length !== toFetch.length) {
const modInfoIDs = new Set(modInfos.map((mi) => mi.id));
const toFetchMissing = [...new Set(toFetch.filter((x) => !modInfoIDs.has(x)))];
log.warn(`Couldn't fetch next project IDs in bulk: ${toFetchMissing.join(", ")}`);
log.warn(`Couldn't fetch some project IDs in bulk: ${toFetchMissing.join(", ")}`);
// Try fetching mods individually, in case they've been deleted.
let count = 0;
@ -165,19 +272,20 @@ export async function fetchProjectsBulk(toFetch: number[]): Promise<CurseForgePr
return modInfos;
}
/**
* Downloads mods from the manifest.
* @param toFetch The files to fetch
* @param destination The dir to put all the mods in. The mods will go into that dir, and not into a sub dir!
*/
export async function fetchMods(toFetch: ModpackManifestFile[], destination: string): Promise<void[]> {
if (toFetch.length > 0) {
log(`Fetching ${toFetch.length} mods...`);
const modsPath = upath.join(destination, "mods");
await fs.promises.mkdir(modsPath, { recursive: true });
let fetched = 0;
return Bluebird.map(
toFetch,
async (file) => {
const fileInfo = await fetchFileInfo(file.projectID, file.fileID);
const fileDef: FileDef = {
url: fileInfo.downloadUrl,
};
@ -199,7 +307,7 @@ export async function fetchMods(toFetch: ModpackManifestFile[], destination: str
log(`Fetched ${upath.basename(fileDef.url)} from cache... (${fetched} / ${toFetch.length})`);
}
const dest = upath.join(destination, "mods", fileInfo.fileName);
const dest = upath.join(destination, fileInfo.fileName);
await fs.promises.symlink(relative(dest, modFile.cachePath), dest);
},

View File

@ -4,19 +4,25 @@ import fs from "fs";
import buildConfig from "../buildConfig";
import upath from "upath";
import requestretry from "requestretry";
import request from "requestretry";
import http from "http";
import { compareBufferToHashDef } from "./hashes";
import { execSync } from "child_process";
import { ModpackManifest, ModpackManifestFile, ExternalDependency } from "../types/modpackManifest";
import { fetchProject, fetchProjectsBulk } from "./curseForgeAPI";
import { ExternalDependency, ModpackManifest, ModpackManifestFile } from "../types/modpackManifest";
import { fetchFileInfo, fetchProject, fetchProjectsBulk } from "./curseForgeAPI";
import Bluebird from "bluebird";
import { VersionManifest } from "../types/versionManifest";
import { VersionsManifest } from "../types/versionsManifest";
import request from "requestretry";
import log from "fancy-log";
import { pathspec, SimpleGit, simpleGit } from "simple-git";
import { Commit, ModChangeInfo } from "../types/changelogTypes";
import { rootDirectory } from "../globals";
const LIBRARY_REG = /^(.+?):(.+?):(.+?)$/;
// Make git commands run in root dir
const git: SimpleGit = simpleGit(rootDirectory);
/**
* Parses the library name into path following the standard package naming convention.
*
@ -29,9 +35,7 @@ export const libraryToPath = (library: string): string => {
const name = parsedLibrary[2];
const version = parsedLibrary[3];
const newURL = `${pkg}/${name}/${version}/${name}-${version}`;
return newURL;
return `${pkg}/${name}/${version}/${name}-${version}`;
}
};
@ -40,12 +44,31 @@ export const libraryToPath = (library: string): string => {
*/
export const checkEnvironmentalVariables = (vars: string[]): void => {
vars.forEach((vari) => {
if (!process.env[vari] || process.env[vari] == "") {
if (!isEnvVariableSet(vari)) {
throw new Error(`Environmental variable ${vari} is unset.`);
}
});
};
/**
* Returns true if given variable set, false otherwise.
*/
export const isEnvVariableSet = (env: string): boolean => {
return process.env[env] && process.env[env] != "";
};
/**
* Check if given git tag exists. Throws otherwise.
*/
export const checkGitTag = (tag: string): void => {
// The below command returns an empty buffer if the given tag does not exist.
const tagBuffer = execSync(`git tag --list ${tag}`);
if (!tagBuffer || tagBuffer.toString().trim() != tag) {
throw new Error(`Tag ${tag} could not be found.`);
}
};
export enum RetrievedFileDefReason {
Downloaded,
CacheHit,
@ -61,6 +84,8 @@ export interface RetrievedFileDef {
*
* Internally hashes the URL of the provided FileDef and looks it up in the cache directory.
* In case of no cache hit, downloads the file and stores within the cache directory for later use.
* <p>
* @param fileDef The file def to download or retrieve.
*/
export async function downloadOrRetrieveFileDef(fileDef: FileDef): Promise<RetrievedFileDef> {
const fileNameSha = sha1(fileDef.url);
@ -98,37 +123,7 @@ export async function downloadOrRetrieveFileDef(fileDef: FileDef): Promise<Retri
try {
handle = await fs.promises.open(cachedFilePath, "w");
let hashFailed = false;
const retryStrategy = (err: Error, response: http.IncomingMessage, body: unknown) => {
// Verify hashes.
if (!err && fileDef.hashes && body) {
const success = fileDef.hashes.every((hashDef) => {
return compareBufferToHashDef(body as Buffer, hashDef);
});
if (!success) {
if (hashFailed) {
throw new Error(`Couldn't verify checksums of ${upath.basename(fileDef.url)}`);
}
hashFailed = true;
return true;
}
}
return requestretry.RetryStrategies.HTTPOrNetworkError(err, response, body);
};
const data: Buffer = Buffer.from(
await requestretry({
url: fileDef.url,
fullResponse: false,
encoding: null,
retryStrategy: retryStrategy,
maxAttempts: 5,
}),
);
await handle.write(data);
await handle.write(await downloadFileDef(fileDef));
await handle.close();
return {
@ -147,6 +142,44 @@ export async function downloadOrRetrieveFileDef(fileDef: FileDef): Promise<Retri
}
}
/**
* Similar to downloadOrRetrieveFileDef, but does not check cache.
*/
export async function downloadFileDef(fileDef: FileDef): Promise<Buffer> {
let hashFailed = false;
const retryStrategy = (err: Error, response: http.IncomingMessage, body: unknown) => {
if (response.statusCode === 404) {
throw new Error(`URL ${fileDef.url} returned status 404.`);
}
// Verify hashes.
if (!err && fileDef.hashes && body) {
const success = fileDef.hashes.every((hashDef) => {
return compareBufferToHashDef(body as Buffer, hashDef);
});
if (!success) {
if (hashFailed) {
throw new Error(`Couldn't verify checksums of ${upath.basename(fileDef.url)}`);
}
hashFailed = true;
return true;
}
}
return requestretry.RetryStrategies.HTTPOrNetworkError(err, response, body);
};
return Buffer.from(
await requestretry({
url: fileDef.url,
fullResponse: false,
encoding: null,
retryStrategy: retryStrategy,
maxAttempts: 5,
}),
);
}
/**
* Returns artifact name body depending on environment variables.
* Mostly intended to be called by CI/CD.
@ -156,13 +189,9 @@ export function makeArtifactNameBody(baseName: string): string {
if (process.env.GITHUB_TAG) {
return `${baseName}-${process.env.GITHUB_TAG}`;
}
// RC.
else if (process.env.RC_VERSION) {
return `${baseName}-${process.env.RC_VERSION.replace(/^v/, "")}`;
}
// If SHA is provided and the build isn't tagged, append both the branch and short SHA.
else if (process.env.GITHUB_SHA && process.env.GITHUB_REF && process.env.GITHUB_REF.startsWith("refs/heads/")) {
const shortCommit = process.env.GITHUB_SHA.substr(0, 7);
const shortCommit = process.env.GITHUB_SHA.substring(0, 7);
const branch = /refs\/heads\/(.+)/.exec(process.env.GITHUB_REF);
return `${baseName}-${branch[1]}-${shortCommit}`;
} else {
@ -171,12 +200,18 @@ export function makeArtifactNameBody(baseName: string): string {
}
/**
* Fetches the last tag known to Git using the current branch.
* @param {string | nil} before Tag to get the tag before.
* Returns the COMPARE_TAG env if set, else fetches the last tag known to Git using the current branch.
* @param before Tag to get the tag before.
* @returns string Git tag.
* @throws
*/
export function getLastGitTag(before?: string): string {
if (isEnvVariableSet("COMPARE_TAG")) {
checkGitTag(process.env["COMPARE_TAG"]);
return process.env["COMPARE_TAG"];
}
if (before) {
before = `"${before}^"`;
}
@ -188,40 +223,46 @@ export function getLastGitTag(before?: string): string {
/**
* Generates a changelog based on the two provided Git refs.
* @param {string} since Lower boundary Git ref.
* @param {string} to Upper boundary Git ref.
* @param {string[]} dirs Optional scopes.
* @param since Lower boundary Git ref.
* @param to Upper boundary Git ref.
* @param dirs Optional scopes. These are of the perspective of the root dir.
* @returns changelog Object Array of Changelog
*/
export function getChangeLog(since = "HEAD", to = "HEAD", dirs: string[] = undefined): string {
const command = [
"git log",
"--no-merges",
'--date="format:%d %b %Y"',
'--pretty="* %s - **%an** (%ad)"',
`${since}..${to}`,
];
export async function getChangelog(since = "HEAD", to = "HEAD", dirs: string[] = undefined): Promise<Commit[]> {
const options: string[] = ["--no-merges", `${since}..${to}`];
if (dirs) {
command.push("--", dirs.join(" -- "));
dirs.forEach((dir) => {
options.push(pathspec(dir));
});
}
return execSync(command.join(" ")).toString().trim();
const commitList: Commit[] = [];
await git.log(options, (err, output) => {
if (err) {
console.error(err);
throw new Error();
}
// Cannot simply set commitList as output.all as is read only, must do this
output.all.forEach((commit) => commitList.push(commit));
});
return commitList;
}
/**
* Generates a changelog based on the two provided Git refs.
* @param {string} since Lower boundary Git ref.
* @param {string} to Upper boundary Git ref.
* @param {string[]} dirs Optional scopes.
* Gets the file at a certain point in time.
* @param path The path to the file
* @param revision The git ref point. Can also be a commit SHA
*/
export function getFileAtRevision(path: string, revision = "HEAD"): string {
return execSync(`git show ${revision}:"${path}"`).toString().trim();
}
export interface ManifestFileListComparisonResult {
removed: string[];
modified: string[];
added: string[];
removed: ModChangeInfo[];
modified: ModChangeInfo[];
added: ModChangeInfo[];
}
export async function compareAndExpandManifestDependencies(
@ -229,18 +270,18 @@ export async function compareAndExpandManifestDependencies(
newFiles: ModpackManifest,
): Promise<ManifestFileListComparisonResult> {
// Map inputs for efficient joining.
const oldFileMap: { [key: number]: ModpackManifestFile } = oldFiles.files.reduce(
(map, file) => ((map[file.projectID] = file), map),
{},
);
const newFileMap: { [key: number]: ModpackManifestFile } = newFiles.files.reduce(
(map, file) => ((map[file.projectID] = file), map),
{},
);
const oldFileMap: { [key: number]: ModpackManifestFile } = oldFiles.files.reduce((map, file) => {
map[file.projectID] = file;
return map;
}, {});
const newFileMap: { [key: number]: ModpackManifestFile } = newFiles.files.reduce((map, file) => {
map[file.projectID] = file;
return map;
}, {});
const removed: string[] = [],
modified: string[] = [],
added: string[] = [];
const removed: ModChangeInfo[] = [],
modified: ModChangeInfo[] = [],
added: ModChangeInfo[] = [];
// Create a distinct map of project IDs.
const projectIDs = Array.from(
@ -259,15 +300,28 @@ export async function compareAndExpandManifestDependencies(
// Doesn't exist in new, but exists in old. Removed. Left outer join.
if (!newFileInfo && oldFileInfo) {
removed.push((await fetchProject(oldFileInfo.projectID)).name);
removed.push({
modName: (await fetchProject(oldFileInfo.projectID)).name,
projectID: projectID,
oldVersion: (await fetchFileInfo(oldFileInfo.projectID, oldFileInfo.fileID)).displayName,
});
}
// Doesn't exist in old, but exists in new. Added. Right outer join.
else if (newFileMap[projectID] && !oldFileMap[projectID]) {
added.push((await fetchProject(newFileInfo.projectID)).name);
added.push({
modName: (await fetchProject(newFileInfo.projectID)).name,
projectID: projectID,
newVersion: (await fetchFileInfo(newFileInfo.projectID, newFileInfo.fileID)).displayName,
});
}
// Exists in both. Modified? Inner join.
else if (oldFileInfo.fileID != newFileInfo.fileID) {
modified.push((await fetchProject(newFileInfo.projectID)).name);
modified.push({
modName: (await fetchProject(newFileInfo.projectID)).name,
projectID: projectID,
oldVersion: (await fetchFileInfo(newFileInfo.projectID, oldFileInfo.fileID)).displayName,
newVersion: (await fetchFileInfo(newFileInfo.projectID, newFileInfo.fileID)).displayName,
});
}
},
{ concurrency: buildConfig.downloaderConcurrency },
@ -275,11 +329,17 @@ export async function compareAndExpandManifestDependencies(
// Compare external dependencies the same way.
const oldExternalMap: { [key: string]: ExternalDependency } = (oldFiles.externalDependencies || []).reduce(
(map, file) => ((map[file.name] = file), map),
(map, file) => {
map[file.name] = file;
return map;
},
{},
);
const newExternalMap: { [key: string]: ExternalDependency } = (newFiles.externalDependencies || []).reduce(
(map, file) => ((map[file.name] = file), map),
(map, file) => {
map[file.name] = file;
return map;
},
{},
);
@ -290,21 +350,21 @@ export async function compareAndExpandManifestDependencies(
]),
);
externalNames.forEach(async (name) => {
externalNames.forEach((name) => {
const oldDep = oldExternalMap[name];
const newDep = newExternalMap[name];
// Doesn't exist in new, but exists in old. Removed. Left outer join.
if (!newDep && oldDep) {
removed.push(oldDep.name);
removed.push({ modName: oldDep.name });
}
// Doesn't exist in old, but exists in new. Added. Right outer join.
else if (newDep && !oldDep) {
added.push(newDep.name);
added.push({ modName: newDep.name });
}
// Exists in both. Modified? Inner join.
else if (oldDep.url != newDep.url || oldDep.name != newDep.name) {
modified.push(newDep.name);
modified.push({ modName: newDep.name });
}
});
@ -338,17 +398,12 @@ export async function getVersionManifest(minecraftVersion: string): Promise<Vers
return null;
}
/**
* Fetch the version manifest file.
*/
const versionManifest: VersionManifest = await request({
return request({
uri: version.url,
json: true,
fullResponse: false,
maxAttempts: 5,
});
return versionManifest;
}
/**
@ -367,3 +422,14 @@ export function relative(from: string, to: string): string {
return upath.join(...Array(broken[0].length - 1).fill(".."), ...broken[1]);
}
/**
* Cleans up a file's display name, and returns the version. Works for all tested mods!
* @param version The filename/version to cleanup.
*/
export function cleanupVersion(version: string): string {
if (!version) return "";
version = version.replace(/1\.12\.2|1\.12|\.jar/g, "");
const list = version.match(/[\d+.?]+/g);
return list[list.length - 1];
}