Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
356 changes: 355 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
[![Download on Modrinth](https://img.shields.io/badge/Download%20on-Modrinth-00AF5C?style=for-the-badge&logo=modrinth&logoColor=white)](https://modrinth.com/datapack/datalib)

---
> Current version: **v6.0.1**
> Current version: **v6.0.1-pre1**
---

> [!WARNING]
Expand Down Expand Up @@ -143,6 +143,360 @@ function datalib:core/lib/string/replace
```


## 💉 Injecting into Another Datapack

These methods **do not merge dataLib into your source datapack.** Your original datapack folder is left untouched. Each method reads a target datapack path, copies `datalib` in alongside it, patches a generated hook into `<namespace>:load`, and writes the result as a **new, separate zip** next to your original — producing two outputs:

- `dataLib-full.zip` — dataLib alone, unchanged (from `./gradlew zipFull`)
- `<target>-injected.zip` — your datapack + dataLib + the load hook, packaged together

None of the four methods below write to your source files. They only read from `datapacks/dataLib` (or `dataLib-full.zip`) and the target path you give them, and write to an output directory.

---

### Method 1 — GitHub Actions

Add this as a **reusable workflow** (`.github/workflows/inject.yml`) in your *own* datapack repo, or call it via `workflow_call` from `runtoolkit/dataLib-dp`. It checks out both repos, copies `datalib` into a scratch copy of your pack, patches the hook, and uploads the result as a build artifact — your repo's tracked files are never modified or committed back.

```yaml
name: Inject dataLib

on:
workflow_dispatch:
inputs:
target_namespace:
description: "Your datapack's namespace (e.g. mypack)"
required: true
datalib_ref:
description: "dataLib-dp ref/tag to inject"
default: "main"

jobs:
inject:
runs-on: ubuntu-latest
steps:
- name: Checkout target datapack
uses: actions/checkout@v6
with:
path: target

- name: Checkout dataLib-dp
uses: actions/checkout@v6
with:
repository: runtoolkit/dataLib-dp
ref: ${{ inputs.datalib_ref }}
path: datalib-src

- name: Build dataLib-full.zip
working-directory: datalib-src
run: |
chmod +x gradlew
./gradlew zipFull --no-daemon

- name: Assemble injected copy (scratch dir, source untouched)
env:
NS: ${{ inputs.target_namespace }}
run: |
set -euo pipefail
mkdir -p out/injected
cp -r target/. out/injected/
mkdir -p tmp_datalib
unzip -q datalib-src/build/dist/dataLib-full.zip -d tmp_datalib
cp -r tmp_datalib/data/datalib out/injected/data/

LOAD_FILE="out/injected/data/${NS}/function/load.mcfunction"
mkdir -p "$(dirname "$LOAD_FILE")"
if ! grep -q "loaded_datalib" "$LOAD_FILE" 2>/dev/null; then
cat <<EOF >> "$LOAD_FILE"

execute unless data storage datalib:engine {global:{loaded:1b}} run function ${NS}:load_datalib
EOF
fi

cat <<EOF > "out/injected/data/${NS}/function/load_datalib.mcfunction"
execute if data storage ${NS}:engine {loaded_datalib:1b} run return 0

function dl_load:load/yes
function dl_load:load/fork_no
tag @s add datalib.admin
scoreboard players set @s[tag=datalib.admin,type=minecraft:player] dl.perm_level 4

data modify storage ${NS}:engine loaded_datalib set value 1b
EOF

- name: Zip injected pack
run: cd out/injected && zip -qr ../../${{ inputs.target_namespace }}-injected.zip .

- uses: actions/upload-artifact@v7
with:
name: dataLib-full
path: datalib-src/build/dist/dataLib-full.zip
if-no-files-found: error

- uses: actions/upload-artifact@v7
with:
name: ${{ inputs.target_namespace }}-injected
path: ${{ inputs.target_namespace }}-injected.zip
if-no-files-found: error
```

Two artifacts are produced per run: the plain `dataLib-full.zip` and `<namespace>-injected.zip`. Neither is committed to any repo.

---

### Method 2 — Bash

```bash
#!/usr/bin/env bash
# inject-datalib.sh
# Usage: ./inject-datalib.sh <path-to-target-datapack> <target-namespace> [dataLib-full.zip]
set -euo pipefail

TARGET_DIR="${1:?target datapack path required}"
NAMESPACE="${2:?target namespace required}"
DATALIB_ZIP="${3:-dataLib-full.zip}"

if [ ! -d "$TARGET_DIR" ]; then
echo "error: target datapack not found: $TARGET_DIR" >&2
exit 1
fi
if [ ! -f "$DATALIB_ZIP" ]; then
echo "error: $DATALIB_ZIP not found. Run './gradlew zipFull' in dataLib-dp first." >&2
exit 1
fi

OUT_DIR="$(mktemp -d)"
trap 'rm -rf "$OUT_DIR"' EXIT

# Copy target into scratch — source datapack is never touched.
cp -r "$TARGET_DIR/." "$OUT_DIR/"

# Unpack dataLib and copy only its data/datalib folder in.
UNZIP_DIR="$(mktemp -d)"
unzip -q "$DATALIB_ZIP" -d "$UNZIP_DIR"
mkdir -p "$OUT_DIR/data"
cp -r "$UNZIP_DIR/data/datalib" "$OUT_DIR/data/"
rm -rf "$UNZIP_DIR"

# Patch the load hook.
LOAD_FILE="$OUT_DIR/data/$NAMESPACE/function/load.mcfunction"
mkdir -p "$(dirname "$LOAD_FILE")"
touch "$LOAD_FILE"
if ! grep -q "loaded_datalib" "$LOAD_FILE"; then
{
echo ""
echo "execute unless data storage datalib:engine {global:{loaded:1b}} run function ${NAMESPACE}:load_datalib"
} >> "$LOAD_FILE"
fi

cat > "$OUT_DIR/data/$NAMESPACE/function/load_datalib.mcfunction" <<EOF
execute if data storage ${NAMESPACE}:engine {loaded_datalib:1b} run return 0

function dl_load:load/yes
function dl_load:load/fork_no
tag @s add datalib.admin
scoreboard players set @s[tag=datalib.admin,type=minecraft:player] dl.perm_level 4

data modify storage ${NAMESPACE}:engine loaded_datalib set value 1b
EOF

OUT_ZIP="$(basename "$TARGET_DIR")-injected.zip"
( cd "$OUT_DIR" && zip -qr "$OLDPWD/$OUT_ZIP" . )

echo "Wrote $OUT_ZIP ($TARGET_DIR was not modified)"
```

---

### Method 3 — Python

```python
#!/usr/bin/env python3
"""
inject_datalib.py
Usage: python3 inject_datalib.py <target_dir> <namespace> [datalib_zip]
"""
import shutil
import sys
import tempfile
import zipfile
from pathlib import Path

LOAD_HOOK = (
"\nexecute unless data storage datalib:engine {{global:{{loaded:1b}}}} "
"run function {ns}:load_datalib\n"
)

LOAD_DATALIB_FN = """\
execute if data storage {ns}:engine {{loaded_datalib:1b}} run return 0

function dl_load:load/yes
function dl_load:load/fork_no
tag @s add datalib.admin
scoreboard players set @s[tag=datalib.admin,type=minecraft:player] dl.perm_level 4

data modify storage {ns}:engine loaded_datalib set value 1b
"""


def inject(target_dir: str, namespace: str, datalib_zip: str = "dataLib-full.zip") -> Path:
target = Path(target_dir)
if not target.is_dir():
raise SystemExit(f"error: target datapack not found: {target}")

datalib_zip_path = Path(datalib_zip)
if not datalib_zip_path.is_file():
raise SystemExit(
f"error: {datalib_zip_path} not found. "
"Run './gradlew zipFull' in dataLib-dp first."
)

with tempfile.TemporaryDirectory() as scratch_str:
scratch = Path(scratch_str)

# Copy target into scratch — source datapack is never touched.
injected = scratch / "injected"
shutil.copytree(target, injected)

# Unpack dataLib, copy only data/datalib in.
unzip_dir = scratch / "datalib_unzipped"
with zipfile.ZipFile(datalib_zip_path) as zf:
zf.extractall(unzip_dir)
dest_datalib = injected / "data" / "datalib"
if dest_datalib.exists():
shutil.rmtree(dest_datalib)
shutil.copytree(unzip_dir / "data" / "datalib", dest_datalib)

# Patch load hook.
fn_dir = injected / "data" / namespace / "function"
fn_dir.mkdir(parents=True, exist_ok=True)

load_file = fn_dir / "load.mcfunction"
existing = load_file.read_text() if load_file.exists() else ""
if "loaded_datalib" not in existing:
with load_file.open("a") as f:
f.write(LOAD_HOOK.format(ns=namespace))

(fn_dir / "load_datalib.mcfunction").write_text(
LOAD_DATALIB_FN.format(ns=namespace)
)

# Zip result next to cwd, source untouched.
out_zip = Path(f"{target.name}-injected.zip")
if out_zip.exists():
out_zip.unlink()
base_name = str(out_zip.with_suffix(""))
shutil.make_archive(base_name, "zip", injected)

print(f"Wrote {out_zip} ({target} was not modified)")
return out_zip


if __name__ == "__main__":
if len(sys.argv) < 3:
raise SystemExit(__doc__)
inject(sys.argv[1], sys.argv[2], sys.argv[3] if len(sys.argv) > 3 else "dataLib-full.zip")
```

---

### Method 4 — JavaScript (Node.js)

Requires `adm-zip` (`npm install adm-zip`).

```javascript
#!/usr/bin/env node
// inject-datalib.js
// Usage: node inject-datalib.js <targetDir> <namespace> [dataLibZip]

const fs = require("fs");
const path = require("path");
const os = require("os");
const AdmZip = require("adm-zip");

function copyDirSync(src, dest) {
fs.mkdirSync(dest, { recursive: true });
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
const srcPath = path.join(src, entry.name);
const destPath = path.join(dest, entry.name);
if (entry.isDirectory()) {
copyDirSync(srcPath, destPath);
} else {
fs.copyFileSync(srcPath, destPath);
}
}
}

function inject(targetDir, namespace, dataLibZip = "dataLib-full.zip") {
if (!fs.existsSync(targetDir) || !fs.statSync(targetDir).isDirectory()) {
throw new Error(`target datapack not found: ${targetDir}`);
}
if (!fs.existsSync(dataLibZip)) {
throw new Error(
`${dataLibZip} not found. Run './gradlew zipFull' in dataLib-dp first.`
);
}

const scratch = fs.mkdtempSync(path.join(os.tmpdir(), "datalib-inject-"));
const injectedDir = path.join(scratch, "injected");

// Copy target into scratch — source datapack is never touched.
copyDirSync(targetDir, injectedDir);

// Unpack dataLib, copy only data/datalib in.
const unzipDir = path.join(scratch, "datalib_unzipped");
new AdmZip(dataLibZip).extractAllTo(unzipDir, true);
const destDatalib = path.join(injectedDir, "data", "datalib");
if (fs.existsSync(destDatalib)) fs.rmSync(destDatalib, { recursive: true });
copyDirSync(path.join(unzipDir, "data", "datalib"), destDatalib);

// Patch load hook.
const fnDir = path.join(injectedDir, "data", namespace, "function");
fs.mkdirSync(fnDir, { recursive: true });

const loadFile = path.join(fnDir, "load.mcfunction");
const existing = fs.existsSync(loadFile) ? fs.readFileSync(loadFile, "utf8") : "";
if (!existing.includes("loaded_datalib")) {
fs.appendFileSync(
loadFile,
`\nexecute unless data storage datalib:engine {global:{loaded:1b}} run function ${namespace}:load_datalib\n`
);
}

fs.writeFileSync(
path.join(fnDir, "load_datalib.mcfunction"),
`execute if data storage ${namespace}:engine {loaded_datalib:1b} run return 0

function dl_load:load/yes
function dl_load:load/fork_no
tag @s add datalib.admin
scoreboard players set @s[tag=datalib.admin,type=minecraft:player] dl.perm_level 4

data modify storage ${namespace}:engine loaded_datalib set value 1b
`
);

// Zip result next to cwd, source untouched.
const outZip = `${path.basename(targetDir)}-injected.zip`;
const zip = new AdmZip();
zip.addLocalFolder(injectedDir);
zip.writeZip(outZip);

fs.rmSync(scratch, { recursive: true });
console.log(`Wrote ${outZip} (${targetDir} was not modified)`);
return outZip;
}

const [, , targetDir, namespace, dataLibZip] = process.argv;
if (!targetDir || !namespace) {
console.error("Usage: node inject-datalib.js <targetDir> <namespace> [dataLibZip]");
process.exit(1);
}
inject(targetDir, namespace, dataLibZip);
```

---

## 💬 Support

[![Issues](https://img.shields.io/github/issues/runtoolkit/dataLib-dp?style=for-the-badge)](https://github.com/runtoolkit/dataLib-dp/issues)
Expand Down
Loading