Skip to content

Commit f6e3aff

Browse files
Publish libraries on smithed (Gamemode4Dev#889)
* Publish libraries to a folder in release Standalone library build, with needed lantern load files, saved to the release directory to be uploaded to smithed * Version() support for rich comparison operators * Modify release script for libraries * Auto patch tracking for libraries * Update manifest.py * Release libraries with module release plugin * Start to version permalinking * Complete past-version permalinking * Fix lantern-load inclusion * beet-release.yaml plugins for LL * Library Licenses and Readmes * Version number appears in lib.zip pack.mcmeta * Rewrite lib_brewing README - actually offers explanations for things - restructures "Additional Uses" section * Simple typos * potion tracking clarification - adds example functions to show how to tag potions and how to execute from landed potions * custom crafters example - add loot table for example recipe - switch example to 1 wool -> 3 string to explain that rounding down is necessary * Port over example-use packs * Add smithed pack ids * Simple library pack icons Simple inkscape-bitmap-traced icons. Do not conform with the GM4 color map and should be adjusted in the future. I blindly used the colorize filter to achieve this effect * Copy READMEs to the generated directory --------- Co-authored-by: BluePsychoRanger <[email protected]>
1 parent b1b6eec commit f6e3aff

File tree

123 files changed

+3033
-274
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

123 files changed

+3033
-274
lines changed

beet-release.yaml

+23-1
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,34 @@ pipeline:
1616
meta: {copy_files: {data_pack: {LICENSE.md: "../LICENSE.md"}}}
1717
- gm4.plugins.readme_generator
1818
- gm4.plugins.write_mcmeta
19+
- gm4.plugins.output.release
20+
meta:
21+
mecha:
22+
formatting:
23+
layout: preserve
24+
nbt_compact: True
25+
- broadcast: 'lib_*'
26+
extend: 'beet.yaml'
27+
pipeline:
28+
- beet.contrib.lantern_load.base_data_pack
29+
- gm4.plugins.versioning.isolated_library
30+
- gm4.plugins.manifest.write_credits
31+
- require: [beet.contrib.copy_files]
32+
meta:
33+
copy_files:
34+
data_pack:
35+
LICENSE.md: "LICENSE.md"
36+
README.md: "README.md"
37+
pack.png: "pack.png"
38+
- gm4.plugins.module.default_pack_icon
39+
- gm4.plugins.readme_generator.libraries
40+
- gm4.plugins.write_mcmeta
41+
- gm4.plugins.output.release
1942
meta:
2043
mecha:
2144
formatting:
2245
layout: preserve
2346
nbt_compact: True
24-
2547
meta:
2648
autosave:
2749
link: false

gm4/plugins/manifest.py

+46-42
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,8 @@ class ManifestCacheModel(BaseModel):
6565
class ManifestFileModel(BaseModel):
6666
"""describes the structure of the meta.json saved to disk"""
6767
last_commit: str
68-
modules: list[ManifestModuleModel]
68+
modules: list[ManifestModuleModel] # straight list for website backward compat
69+
libraries: dict[str, ManifestModuleModel]
6970
contributors: Any
7071

7172

@@ -136,59 +137,63 @@ def update_patch(ctx: Context):
136137
logger = parent_logger.getChild("update_patch")
137138
skin_cache = JsonFile(source_path="gm4/skin_cache.json").data
138139

139-
modules = ManifestCacheModel.parse_obj(ctx.cache["gm4_manifest"].json).modules
140+
manifest_cache = ManifestCacheModel.parse_obj(ctx.cache["gm4_manifest"].json)
140141

141142
if manifest_file.exists():
142143
manifest = ManifestFileModel.parse_obj(json.loads(manifest_file.read_text()))
143144
last_commit = manifest.last_commit
144145
released_modules: dict[str, ManifestModuleModel] = {m.id:m for m in manifest.modules if m.version}
146+
released_modules |= manifest.libraries
145147
else:
146148
logger.debug("No existing meta.json manifest file was located")
147149
last_commit = None
148150
released_modules = {}
149151

150-
for id in modules:
151-
module = modules[id]
152-
released = released_modules.get(id, None)
153-
154-
publish_date = released.publish_date if released else None
155-
module.publish_date = publish_date or datetime.datetime.now().date().isoformat()
156-
157-
deps = _traverse_includes(id) | {"base"}
158-
deps_dirs = [element for sublist in [[f"{d}/data", f"{d}/*py"] for d in deps] for element in sublist]
159-
160-
# add watches to skins this module uses from other modules. NOTE this could be done in a more extendable way in the future, rather than "hardcoded"
161-
skin_dep_dirs: list[str] = []
162-
for skin_ref in skin_cache["nonnative_references"].get(id, []):
163-
d = skin_cache["skins"][skin_ref]["parent_module"]
164-
ns, path = skin_ref.split(":")
165-
skin_dep_dirs.append(f"{d}/data/{ns}/skins/{path}.png")
166-
167-
watch_dirs = deps_dirs+skin_dep_dirs
168-
169-
diff = run(["git", "diff", last_commit, "--shortstat", "--", f"{id}/data", f"{id}/*.py"] + watch_dirs) if last_commit else True
170-
171-
if not diff and released:
172-
# No changes were made, keep the same patch version
173-
module.version = released.version
174-
elif not released:
175-
# First release
176-
module.version = module.version.replace("X", "0")
177-
logger.debug(f"First release of {id}")
178-
else:
179-
# Changes were made, bump the patch
180-
version = Version(module.version)
181-
last_ver = Version(released.version)
152+
for packs in (manifest_cache.modules, manifest_cache.libraries):
153+
for id in packs:
154+
pack = packs[id]
155+
released = released_modules.get(id, None)
156+
157+
publish_date = released.publish_date if released else None
158+
pack.publish_date = publish_date or datetime.datetime.now().date().isoformat()
159+
160+
deps = _traverse_includes(id)
161+
if packs is manifest_cache.modules:
162+
deps |= {"base"} # scan the base directory if this is a module
163+
deps_dirs = [element for sublist in [[f"{d}/data", f"{d}/*py"] for d in deps] for element in sublist]
164+
165+
# add watches to skins this module uses from other modules. NOTE this could be done in a more extendable way in the future, rather than "hardcoded"
166+
skin_dep_dirs: list[str] = []
167+
for skin_ref in skin_cache["nonnative_references"].get(id, []):
168+
d = skin_cache["skins"][skin_ref]["parent_module"]
169+
ns, path = skin_ref.split(":")
170+
skin_dep_dirs.append(f"{d}/data/{ns}/skins/{path}.png")
182171

183-
if version.minor > last_ver.minor or version.major > last_ver.major: # type: ignore
184-
version.patch = 0
172+
watch_dirs = deps_dirs+skin_dep_dirs
173+
174+
diff = run(["git", "diff", last_commit, "--shortstat", "--", f"{id}/data", f"{id}/*.py"] + watch_dirs) if last_commit else True
175+
176+
if not diff and released:
177+
# No changes were made, keep the same patch version
178+
pack.version = released.version
179+
elif not released:
180+
# First release
181+
pack.version = pack.version.replace("X", "0")
182+
logger.debug(f"First release of {id}")
185183
else:
186-
version.patch = last_ver.patch + 1 # type: ignore
187-
logger.info(f"Updating {id} patch to {version.patch}")
184+
# Changes were made, bump the patch
185+
version = Version(pack.version)
186+
last_ver = Version(released.version)
187+
188+
if version.minor > last_ver.minor or version.major > last_ver.major: # type: ignore
189+
version.patch = 0
190+
else:
191+
version.patch = last_ver.patch + 1 # type: ignore
192+
logger.info(f"Updating {id} patch to {version.patch}")
188193

189-
module.version = str(version)
194+
pack.version = str(version)
190195

191-
ctx.cache["gm4_manifest"].json["modules"] = {id:m.dict() for id, m in modules.items()}
196+
ctx.cache["gm4_manifest"].json = manifest_cache.dict()
192197

193198
@cache
194199
def _traverse_includes(project_id: str) -> set[str]:
@@ -213,7 +218,6 @@ def write_meta(ctx: Context):
213218
manifest_file = release_dir / "meta.json"
214219
manifest = ctx.cache["gm4_manifest"].json.copy()
215220
manifest["modules"] = list(manifest["modules"].values()) # convert modules dict down to list for backwards compatability
216-
manifest.pop("libraries")
217221
manifest.pop("base")
218222
manifest_file.write_text(json.dumps(manifest, indent=2))
219223

@@ -260,7 +264,7 @@ def write_updates(ctx: Context):
260264
if init is None:
261265
return
262266

263-
manifest =ManifestCacheModel.parse_obj(ctx.cache["gm4_manifest"].json)
267+
manifest = ManifestCacheModel.parse_obj(ctx.cache["gm4_manifest"].json)
264268
modules = manifest.modules
265269

266270
score = f"{ctx.project_id.removeprefix('gm4_')} gm4_modules"

gm4/plugins/output.py

+69-50
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
import requests
77
import shutil
88
import logging
9-
from gm4.utils import run
9+
from gm4.utils import run, Version, NoneAttribute
10+
import gm4.plugins.manifest # for ManifestCacheModel; a runtime circular dependency
1011

1112
parent_logger = logging.getLogger("gm4.output")
1213

@@ -19,9 +20,11 @@
1920

2021
class ModrinthConfig(PluginOptions):
2122
project_id: Optional[str]
23+
minecraft: list[str] = SUPPORTED_GAME_VERSIONS
2224

2325
class SmithedConfig(PluginOptions):
2426
pack_id: Optional[str]
27+
minecraft: list[str] = SUPPORTED_GAME_VERSIONS
2528

2629
class PMCConfig(PluginOptions):
2730
uid: Optional[int]
@@ -55,7 +58,10 @@ def release(ctx: Context):
5558
"""
5659
version_dir = os.getenv("VERSION", "1.20")
5760
release_dir = Path("release") / version_dir
58-
file_name = f"{ctx.project_id}_{version_dir.replace('.', '_')}.zip"
61+
62+
corrected_project_id = stem if (stem:=ctx.directory.stem).startswith("lib") else ctx.project_id
63+
64+
file_name = f"{corrected_project_id}_{version_dir.replace('.', '_')}.zip"
5965

6066
yield # wait for exit phase, after other plugins cleanup
6167

@@ -70,37 +76,36 @@ def release(ctx: Context):
7076
pack_icon_dir = generated_dir / "pack_icons"
7177
os.makedirs(pack_icon_dir, exist_ok=True)
7278
if "pack.png" in ctx.data.extra:
73-
ctx.data.extra["pack.png"].dump(pack_icon_dir, f"{ctx.project_id}.png")
79+
ctx.data.extra["pack.png"].dump(pack_icon_dir, f"{corrected_project_id}.png")
7480

7581
smithed_readme_dir = generated_dir / "smithed_readmes"
7682
os.makedirs(smithed_readme_dir, exist_ok=True)
7783
if "smithed_readme" in ctx.meta:
78-
ctx.meta['smithed_readme'].dump(smithed_readme_dir, f"{ctx.project_id}.md")
84+
ctx.meta['smithed_readme'].dump(smithed_readme_dir, f"{corrected_project_id}.md")
7985

8086
# publish to download platforms
8187
publish_modrinth(ctx, release_dir, file_name)
82-
publish_smithed(ctx, release_dir, file_name)
88+
publish_smithed(ctx, file_name)
89+
8390

8491
def publish_modrinth(ctx: Context, release_dir: Path, file_name: str):
8592
'''Attempts to publish pack to modrinth'''
86-
modrinth = ctx.meta.get("modrinth", None)
93+
opts = ctx.validate("modrinth", ModrinthConfig)
8794
auth_token = os.getenv(MODRINTH_AUTH_KEY, None)
8895
logger = parent_logger.getChild(f"modrinth.{ctx.project_id}")
89-
if modrinth and auth_token:
90-
modrinth_id = modrinth["project_id"]
91-
96+
if opts.project_id and auth_token:
9297
# update page description
93-
res = requests.get(f"{MODRINTH_API}/project/{modrinth_id}", headers={'Authorization': auth_token, 'User-Agent': USER_AGENT})
98+
res = requests.get(f"{MODRINTH_API}/project/{opts.project_id}", headers={'Authorization': auth_token, 'User-Agent': USER_AGENT})
9499
if not (200 <= res.status_code < 300):
95100
if res.status_code == 404:
96-
logger.warning(f"Cannot edit description of modrinth project {modrinth_id} as it doesn't exist.")
101+
logger.warning(f"Cannot edit description of modrinth project {opts.project_id} as it doesn't exist.")
97102
else:
98103
logger.warning(f"Failed to get project: {res.status_code} {res.text}")
99104
return
100105
existing_readme = res.json()["body"]
101106
if existing_readme != (d:=ctx.meta['modrinth_readme'].text):
102107
logger.debug("Readme and modrinth-page content differ. Updating webpage body")
103-
res = requests.patch(f"{MODRINTH_API}/project/{modrinth_id}", headers={'Authorization': auth_token, 'User-Agent': USER_AGENT}, json={"body": d})
108+
res = requests.patch(f"{MODRINTH_API}/project/{opts.project_id}", headers={'Authorization': auth_token, 'User-Agent': USER_AGENT}, json={"body": d})
104109
if not (200 <= res.status_code < 300):
105110
logger.warning(f"Failed to update description: {res.status_code} {res.text}")
106111
logger.info(f"Successfully updated description of {ctx.project_name}")
@@ -112,16 +117,16 @@ def publish_modrinth(ctx: Context, release_dir: Path, file_name: str):
112117
logger.warning("Full version number not available in ctx.meta. Skipping publishing")
113118
return
114119

115-
res = requests.get(f"{MODRINTH_API}/project/{modrinth_id}/version", headers={'Authorization': auth_token, 'User-Agent': USER_AGENT})
120+
res = requests.get(f"{MODRINTH_API}/project/{opts.project_id}/version", headers={'Authorization': auth_token, 'User-Agent': USER_AGENT})
116121
if not (200 <= res.status_code < 300):
117122
if res.status_code == 404:
118-
logger.warning(f"Cannot publish to modrinth project {modrinth_id} as it doesn't exist.")
123+
logger.warning(f"Cannot publish to modrinth project {opts.project_id} as it doesn't exist.")
119124
else:
120125
logger.warning(f"Failed to get project versions: {res.status_code} {res.text}")
121126
return
122127
project_data = res.json()
123128
matching_version = next((v for v in project_data if v["version_number"] == str(version)), None)
124-
game_versions = modrinth.get("minecraft", SUPPORTED_GAME_VERSIONS)
129+
game_versions = opts.minecraft
125130
if matching_version is not None: # patch version already exists
126131
# update mc versions if necessary
127132
if not set(matching_version["game_versions"]) == set(game_versions):
@@ -148,7 +153,7 @@ def publish_modrinth(ctx: Context, release_dir: Path, file_name: str):
148153
"version_type": "release",
149154
"loaders": ["datapack"],
150155
"featured": False,
151-
"project_id": modrinth_id,
156+
"project_id": opts.project_id,
152157
"file_parts": [file_name],
153158
}),
154159
file_name: file_bytes,
@@ -158,54 +163,58 @@ def publish_modrinth(ctx: Context, release_dir: Path, file_name: str):
158163
return
159164
logger.info(f"Successfully published {res.json()['name']}")
160165

161-
def publish_smithed(ctx: Context, release_dir: Path, file_name: str):
166+
def publish_smithed(ctx: Context, file_name: str):
162167
"""Attempts to publish pack to smithed"""
163-
smithed = ctx.meta.get("smithed", None)
168+
opts = ctx.validate("smithed", SmithedConfig)
164169
auth_token = os.getenv(SMITHED_AUTH_KEY, None)
165170
logger = parent_logger.getChild(f"smithed.{ctx.project_id}")
166171
mc_version_dir = os.getenv("VERSION", "1.20")
167-
if smithed and auth_token and ctx.project_version:
168-
version = ctx.cache["gm4_manifest"].json["modules"].get(ctx.project_id, {}).get("version", None)
169-
smithed_id = smithed["pack_id"]
172+
manifest = gm4.plugins.manifest.ManifestCacheModel.parse_obj(ctx.cache["gm4_manifest"].json)
173+
project_id = stem if (stem:=ctx.directory.stem).startswith("lib") else ctx.project_id
174+
175+
if opts.pack_id and auth_token:
176+
version = (manifest.modules|manifest.libraries).get(project_id, NoneAttribute()).version or ""
170177

171178
# get project data and existing versions
172-
res = requests.get(f"{SMITHED_API}/packs/{smithed_id}")
179+
res = requests.get(f"{SMITHED_API}/packs/{opts.pack_id}")
173180
if not (200 <= res.status_code < 300):
174181
if res.status_code == 404:
175-
logger.warning(f"Cannot publish to smithed project {smithed_id} as it doesn't exist.")
182+
logger.warning(f"Cannot publish to smithed project {opts.pack_id} as it doesn't exist.")
176183
else:
177184
logger.warning(f"Failed to get project: {res.status_code} {res.text}")
178185
return
179186

180187
project_data = res.json()
181-
188+
182189
# update description and pack image
183190
# ensures they point to the most up-to-date mc version branch
184-
project_display = project_data["display"]
185-
current_icon = f"https://raw.githubusercontent.com/Gamemode4Dev/GM4_Datapacks/release/{mc_version_dir}/generated/pack_icons/{ctx.project_id}.png"
186-
current_readme = f"https://raw.githubusercontent.com/Gamemode4Dev/GM4_Datapacks/release/{mc_version_dir}/generated/smithed_readmes/{ctx.project_id}.md"
187-
188-
if project_display["icon"] != current_icon or project_display["webPage"] != current_readme:
189-
logger.debug("Pack Icon or Readme hyperlink is incorrect. Updating project")
190-
res = requests.patch(f"{SMITHED_API}/packs/{smithed_id}", params={'token': auth_token},
191-
json={"data": {
192-
"display": {
193-
"icon": current_icon,
194-
"webPage": current_readme,
195-
},
196-
}})
197-
if not (200 <= res.status_code < 300):
198-
logger.warning(f"Failed to update descripion: {res.status_code} {res.text}")
199-
logger.info(f"{ctx.project_name} {res.text}")
200-
201191
project_versions = project_data["versions"]
192+
newest_version = sorted([Version(v["name"]) for v in project_versions])[-1]
193+
if Version(version) > newest_version: # only update the description if we're not patching an old version
194+
project_display = project_data["display"]
195+
current_icon = f"https://raw.githubusercontent.com/Gamemode4Dev/GM4_Datapacks/release/{mc_version_dir}/generated/pack_icons/{project_id}.png"
196+
current_readme = f"https://raw.githubusercontent.com/Gamemode4Dev/GM4_Datapacks/release/{mc_version_dir}/generated/smithed_readmes/{project_id}.md"
197+
198+
if project_display["icon"] != current_icon or project_display["webPage"] != current_readme:
199+
logger.debug("Pack Icon or Readme hyperlink is incorrect. Updating project")
200+
res = requests.patch(f"{SMITHED_API}/packs/{opts.pack_id}", params={'token': auth_token},
201+
json={"data": {
202+
"display": {
203+
"icon": current_icon,
204+
"webPage": current_readme,
205+
},
206+
}})
207+
if not (200 <= res.status_code < 300):
208+
logger.warning(f"Failed to update descripion: {res.status_code} {res.text}")
209+
logger.info(f"{ctx.project_name} {res.text}")
210+
202211
matching_version = next((v for v in project_versions if v["name"] == str(version)), None)
203-
game_versions = smithed.get("minecraft", SUPPORTED_GAME_VERSIONS)
212+
game_versions = opts.minecraft
204213
if matching_version is not None: # patch version already exists
205214
# update MC version if necessary
206215
if not set(matching_version["supports"]) == set(game_versions):
207216
logger.debug("Additional MC version support has been added to an existing patch version. Updating existing smithed version data")
208-
res = requests.patch(f"{SMITHED_API}/packs/{smithed_id}/versions/{matching_version['name']}", params={'token': auth_token}, json={
217+
res = requests.patch(f"{SMITHED_API}/packs/{opts.pack_id}/versions/{matching_version['name']}", params={'token': auth_token}, json={
209218
"data": {
210219
"supports": game_versions
211220
}
@@ -214,17 +223,27 @@ def publish_smithed(ctx: Context, release_dir: Path, file_name: str):
214223
logger.warning(f"Failed to patch project versions: {res.status_code} {res.text}")
215224
return
216225

217-
# remove other existing versions for that mc version
218-
mc_version_matching_version = (v["name"] for v in project_versions if set(v['supports']) & set(game_versions))
219-
for v in mc_version_matching_version:
220-
res = requests.delete(f"{SMITHED_API}/packs/{smithed_id}/versions/{v}", params={'token': auth_token})
226+
# permalink previous version (in that MC version) to the git history
227+
commit_hash = run("cd release && git log -1 --format=%H")
228+
matching_mc_versions = sorted((Version(v["name"]) for v in project_versions if set(v['supports']) & set(game_versions)))
229+
prior_version_in_mc_version = matching_mc_versions[-1] if len(matching_mc_versions) > 0 else None # newest version number, with any MC overlap
230+
prior_url: str = next((v["downloads"]["datapack"] for v in project_versions if Version(v["name"]) == prior_version_in_mc_version), "")
231+
if "https://github.com/Gamemode4Dev/GM4_Datapacks/blob/" not in prior_url and prior_version_in_mc_version:
232+
res = requests.patch(f"{SMITHED_API}/packs/{opts.pack_id}/versions/{prior_version_in_mc_version}", params={'token': auth_token}, json={
233+
"data":{
234+
"downloads": {
235+
"datapack": f"https://github.com/Gamemode4Dev/GM4_Datapacks/blob/{commit_hash}/{mc_version_dir}/{file_name}?raw=true",
236+
"resourcepack": ""
237+
}
238+
}
239+
})
221240
if not (200 <= res.status_code < 300):
222-
logger.warning(f"Failed to delete {ctx.project_name} version {v}: {res.status_code} {res.text}")
241+
logger.warning(f"Failed to permalink {project_id} version {prior_version_in_mc_version}: {res.status_code} {res.text}")
223242
else:
224-
logger.info(f"{ctx.project_name} {res.text}")
243+
logger.info(f"Permalinked {project_id} {prior_version_in_mc_version} to git history: {res.text}")
225244

226245
# post new version
227-
res = requests.post(f"{SMITHED_API}/packs/{smithed_id}/versions",
246+
res = requests.post(f"{SMITHED_API}/packs/{opts.pack_id}/versions",
228247
params={'token': auth_token, 'version': version},
229248
json={"data":{
230249
"downloads":{

0 commit comments

Comments
 (0)