fix(ci): package locally verified source without remote tests #8

Merged
dmg merged 1 commits from fs-build-02-package-locally-verified-source into main 2026-10-08 02:19:43 +00:00
5 changed files with 136 additions and 7 deletions

No files matched your search

+3 -2
View File
@@ -43,8 +43,9 @@ jobs:
run: |
npx -y -p @commitlint/cli -p @commitlint/config-conventional \
commitlint --from "$BASE_SHA" --to "$HEAD_SHA" --extends @commitlint/config-conventional
- name: Build and test
run: ./gradlew clean check jar
# Full clean checks are required locally for this exact candidate before integration.
- name: Compile and package
run: ./gradlew clean jar
- name: Upload development artifact
uses: actions/upload-artifact@ff15f0306b3f739f7b6fd43fb5d26cd321bd4de5 # v3
with:
+8 -4
View File
@@ -39,8 +39,9 @@ jobs:
with:
node-version: 20
cache: npm
- name: Build and test before release
run: ./gradlew clean check jar
# Full clean checks are required locally for this exact candidate before integration.
- name: Compile and package before release
run: ./gradlew clean jar
- name: Capture previous tag
id: previous
run: |
@@ -86,11 +87,14 @@ jobs:
else
printf 'created=false\n' >> "$GITHUB_OUTPUT"
fi
- name: Verify versioned plugin and release tooling
- name: Package versioned plugin and compile release tooling
if: steps.release.outputs.created == 'true'
env:
VERSION: ${{ steps.release.outputs.version }}
run: ./gradlew clean check jar -PreleaseVersion="$VERSION"
run: |
./gradlew clean jar -PreleaseVersion="$VERSION"
npm ci --ignore-scripts --no-audit --no-fund
npm run build
- name: Upload release workflow artifact
if: steps.release.outputs.created == 'true'
uses: actions/upload-artifact@ff15f0306b3f739f7b6fd43fb5d26cd321bd4de5 # v3
+14 -1
View File
@@ -16,6 +16,19 @@ Fantasy Stick is the proposed transferable-character RPG plugin for Social Minec
./gradlew check jar -PreleaseVersion=0.1.0
```
The full local suite remains mandatory before integrating a candidate. Retain passing reports against its exact Git tree and unchanged verification inputs; changes require renewed verification. This requirement is not satisfied by a packaging-only CI pass.
For packaging without running tests:
```sh
./gradlew clean jar
./gradlew clean jar -PreleaseVersion=0.1.0
npm ci --ignore-scripts --no-audit --no-fund
npm run build
```
`npm run build` compiles the TypeScript publication tools without executing tests; `npm test` still runs the complete tooling suite.
`check` runs Java packaging tests, TypeScript release-client/credential-helper tests, and a native compatibility test against the checksum-verified Purpur runtime. Native preparation downloads and patches the declared runtime and loads vanilla registries/components **without starting a listening server or creating a gameplay world**. The item tests decode signed written books with Minecraft's real codec, without a plugin instance or YAML lookup. A clean build repeats that preparation. Dependencies and runtime downloads require network access.
Build output is `build/libs/purpur-fantasy-stick-<version>.jar`. Server APIs, tests, credentials, and Node tooling are not bundled in the plugin. Runtime identity is `FantasyStick`, entrypoint `games.dmg.fantasystick.FantasyStickPlugin`, data folder `FantasyStick`, namespace `fantasystick`. Startup registers the manual crafting controller and automated-Crafter guard before the character-sheet recipe. Equipment sessions and permission-gated administration are registered; spell execution remains absent.
@@ -213,7 +226,7 @@ Automated party tests exercise actual packaged listeners, native book custom-cli
## CI and publication
Gitea CI verifies pushes/PRs; main pushes run semantic-release and can publish immutable versioned JARs. Source checkout is anonymous from the public repository; external action revisions are pinned to full commit SHAs. CI and actual release publication must both be verified for the exact source commit before any deployment preparation. The operator has provisioned `RELEASE_TOKEN` and the bootstrap is pushed. Do not infer successful CI, publication or credential validity from the secret name alone; record actual exact-commit workflow and artifact results in the canonical wiki.
Gitea CI compiles/packages pushes and PRs without rerunning test suites; main pushes run semantic-release, package the exact version, compile publication tooling and can publish immutable versioned JARs. Complete local test proof for the exact candidate remains required before integration; verify that the fetched merge tree equals the locally tested tree. Remote success is packaging/publication evidence, not remote test evidence. This temporary separation does not delete tests, alter their limits, waive explicit CI-test criteria, or clear security findings. Source checkout is anonymous from the public repository; external action revisions are pinned to full commit SHAs. CI and actual release publication must both be verified for the exact source commit before any deployment preparation. The operator has provisioned `RELEASE_TOKEN` and the bootstrap is pushed. Do not infer successful CI, publication or credential validity from the secret name alone; record actual exact-commit workflow and artifact results in the canonical wiki.
The release credential must be a repository-write credential authorized for `dmg/purpur-fantasy-stick`. Provision it through the approved protected-file/stdin workflow; never paste it into chat, commit it, or supply it as a command argument.
+1
View File
@@ -5,6 +5,7 @@
"type": "module",
"engines": { "node": ">=20" },
"scripts": {
"build": "tsc",
"test": "rm -rf build/release-tools && tsc && node --test build/release-tools/*.test.js"
},
"devDependencies": {
+110
View File
@@ -0,0 +1,110 @@
import assert from "node:assert/strict";
import { spawnSync } from "node:child_process";
import { mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync, existsSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { test } from "node:test";
// Execute the actual checked-in run blocks against strict external build-tool ports.
// Real Gradle packaging is verified separately on the final candidate; these ports prove wiring.
function buildCommands(workflow: string): string[] {
const lines = readFileSync(`.gitea/workflows/${workflow}`, "utf8").split("\n");
const commands: string[] = [];
for (let i = 0; i < lines.length; i++) {
const match = /^ {8}run: (.+)$/.exec(lines[i]!);
if (!match) continue;
let command = match[1]!;
if (command === "|") {
const body: string[] = [];
while (i + 1 < lines.length && /^ {10}/.test(lines[i + 1]!)) body.push(lines[++i]!.slice(10));
command = body.join("\n");
}
if (command.includes("./gradlew")) commands.push(command);
}
return commands;
}
function fixture() {
const root = mkdtempSync(join(tmpdir(), "fantasy-packaging-port-"));
mkdirSync(join(root, "bin"));
writeFileSync(join(root, "gradlew"), `#!/bin/sh
set -eu
version=0.1.0-SNAPSHOT
printf 'gradle %s\\n' "$*" >> "$CALLS"
if [ "$FAIL_PACKAGE" = true ]; then exit 87; fi
for arg do
case "$arg" in
clean|jar) ;;
-PreleaseVersion=*) version="\${arg#-PreleaseVersion=}" ;;
*) printf 'Unexpected task: %s\\n' "$arg" >&2; exit 91 ;;
esac
done
mkdir -p build/libs build/distributions
printf 'external packaging port\\n' > "build/libs/purpur-fantasy-stick-$version.jar"
printf 'external resource-pack port\\n' > "build/distributions/purpur-fantasy-stick-$version-resource-pack.zip"
`, { mode: 0o700 });
writeFileSync(join(root, "bin/npm"), `#!/bin/sh
set -eu
printf 'npm %s\\n' "$*" >> "$CALLS"
case "$*" in
'ci --ignore-scripts --no-audit --no-fund') ;;
'run build')
[ "$BUILD_SCRIPT" = tsc ] || { printf 'Missing compile-only release-tool script\\n' >&2; exit 92; }
mkdir -p build/release-tools
printf 'external TypeScript compiler port\\n' > build/release-tools/publish-release.js
;;
*) printf 'Unexpected npm command: %s\\n' "$*" >&2; exit 93 ;;
esac
`, { mode: 0o700 });
const metadata = JSON.parse(readFileSync("package.json", "utf8")) as { scripts: Record<string, string> };
return {
root,
run(command: string, failPackage = false) {
return spawnSync("/bin/sh", ["-eu", "-c", command], {
cwd: root, encoding: "utf8", timeout: 10_000,
env: { PATH: `${join(root, "bin")}:/usr/bin:/bin`, CALLS: join(root, "calls"), VERSION: "3.2.1",
BUILD_SCRIPT: metadata.scripts.build ?? "", FAIL_PACKAGE: String(failPackage) },
});
},
close() { rmSync(root, { recursive: true, force: true }); },
};
}
test("CI packages its development artifact without dispatching verification tasks", () => {
const commands = buildCommands("ci.yml");
assert.equal(commands.length, 1);
const port = fixture();
try {
const result = port.run(commands[0]!);
assert.equal(result.status, 0, result.stderr);
assert.ok(existsSync(join(port.root, "build/libs/purpur-fantasy-stick-0.1.0-SNAPSHOT.jar")));
assert.equal(readFileSync(join(port.root, "calls"), "utf8"), "gradle clean jar\n");
} finally { port.close(); }
});
test("release packages its exact version and compiles publication tools without running suites", () => {
const commands = buildCommands("release.yml");
assert.equal(commands.length, 2);
const port = fixture();
try {
for (const command of commands) {
const result = port.run(command);
assert.equal(result.status, 0, result.stderr);
}
assert.ok(existsSync(join(port.root, "build/libs/purpur-fantasy-stick-3.2.1.jar")));
assert.ok(existsSync(join(port.root, "build/distributions/purpur-fantasy-stick-3.2.1-resource-pack.zip")));
assert.ok(existsSync(join(port.root, "build/release-tools/publish-release.js")));
assert.equal(readFileSync(join(port.root, "calls"), "utf8"),
"gradle clean jar\ngradle clean jar -PreleaseVersion=3.2.1\nnpm ci --ignore-scripts --no-audit --no-fund\nnpm run build\n");
} finally { port.close(); }
});
test("failed versioned packaging stops before preparing publication tools", () => {
const port = fixture();
try {
const result = port.run(buildCommands("release.yml")[1]!, true);
assert.equal(result.status, 87);
assert.equal(readFileSync(join(port.root, "calls"), "utf8"), "gradle clean jar -PreleaseVersion=3.2.1\n");
assert.ok(!existsSync(join(port.root, "build/release-tools/publish-release.js")));
} finally { port.close(); }
});