diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ac5f1f3..3900386 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -19,7 +19,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Build the Docker image - run: docker build . --file Dockerfile.tcli --tag ghcr.io/greentf/tcli:latest + run: docker build . --file Dockerfile.tcli --tag ghcr.io/greentf/tcli:bun - name: Authenticate with GHCR if: ${{ github.event_name == 'release' || github.event.inputs.push == 'true' }} uses: docker/login-action@v2.1.0 @@ -29,4 +29,4 @@ jobs: password: ${{ secrets.GHCR_TOKEN }} - name: Push container if: ${{ github.event_name == 'release' || github.event.inputs.push == 'true' }} - run: docker push ghcr.io/greentf/tcli:latest + run: docker push ghcr.io/greentf/tcli:bun diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9b1ee42 --- /dev/null +++ b/.gitignore @@ -0,0 +1,175 @@ +# Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore + +# Logs + +logs +_.log +npm-debug.log_ +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Caches + +.cache + +# Diagnostic reports (https://nodejs.org/api/report.html) + +report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json + +# Runtime data + +pids +_.pid +_.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover + +lib-cov + +# Coverage directory used by tools like istanbul + +coverage +*.lcov + +# nyc test coverage + +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) + +.grunt + +# Bower dependency directory (https://bower.io/) + +bower_components + +# node-waf configuration + +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) + +build/Release + +# Dependency directories + +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) + +web_modules/ + +# TypeScript cache + +*.tsbuildinfo + +# Optional npm cache directory + +.npm + +# Optional eslint cache + +.eslintcache + +# Optional stylelint cache + +.stylelintcache + +# Microbundle cache + +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history + +.node_repl_history + +# Output of 'npm pack' + +*.tgz + +# Yarn Integrity file + +.yarn-integrity + +# dotenv environment variable files + +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) + +.parcel-cache + +# Next.js build output + +.next +out + +# Nuxt.js build / generate output + +.nuxt +dist + +# Gatsby files + +# Comment in the public line in if your project uses Gatsby and not Next.js + +# https://nextjs.org/blog/next-9-1#public-directory-support + +# public + +# vuepress build output + +.vuepress/dist + +# vuepress v2.x temp and cache directory + +.temp + +# Docusaurus cache and generated files + +.docusaurus + +# Serverless directories + +.serverless/ + +# FuseBox cache + +.fusebox/ + +# DynamoDB Local files + +.dynamodb/ + +# TernJS port file + +.tern-port + +# Stores VSCode versions used for testing VSCode extensions + +.vscode-test + +# yarn v2 + +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* + +# IntelliJ based IDEs +.idea + +# Finder (MacOS) folder config +.DS_Store diff --git a/Dockerfile b/Dockerfile index dc10b28..86b5719 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,11 +1,8 @@ -FROM denoland/deno as cache -ENV DENO_DIR=/var/tmp/deno_cache -COPY ./cfg_edit.js /cfg_edit.js -RUN deno cache /cfg_edit.js -FROM ghcr.io/greentf/tcli -ENV DENO_DIR=/var/tmp/deno_cache -COPY --from=cache ${DENO_DIR} ${DENO_DIR} +FROM ghcr.io/greentf/tcli:bun COPY ./entrypoint.sh /entrypoint.sh -COPY ./cfg_edit.js /cfg_edit.js +COPY ./index.ts /index.ts +COPY ./package.json /package.json +COPY ./bun.lockb /bun.lockb +RUN ["bun", "install", "--frozen-lockfile"] RUN ["chmod", "+x", "/entrypoint.sh"] ENTRYPOINT ["/entrypoint.sh"] diff --git a/Dockerfile.tcli b/Dockerfile.tcli index 49c6e34..37041ef 100644 --- a/Dockerfile.tcli +++ b/Dockerfile.tcli @@ -1,4 +1,4 @@ -FROM denoland/deno +FROM oven/bun ARG VERSION=0.2.4 RUN apt-get update && apt-get install -y wget && rm -rf /var/lib/apt/lists/* RUN wget -O tcli.tar.gz https://github.com/thunderstore-io/thunderstore-cli/releases/download/${VERSION}/tcli-${VERSION}-linux-x64.tar.gz diff --git a/bun.lockb b/bun.lockb new file mode 100644 index 0000000..d5472be Binary files /dev/null and b/bun.lockb differ diff --git a/cfg_edit.js b/cfg_edit.js deleted file mode 100644 index 74b2dee..0000000 --- a/cfg_edit.js +++ /dev/null @@ -1,77 +0,0 @@ -import * as TOML from "npm:@aduh95/toml@0.4.2"; - -//Read in thunderstore.toml -const tstore = TOML.parse(await Deno.readTextFile("./thunderstore.toml")); - -const namespace = Deno.env.get("TS_NAMESPACE"); -const name = Deno.env.get("TS_NAME"); -const version = Deno.env.get("TS_VERSION").replace(/v/g, ""); -const desc = Deno.env.get("TS_DESC").substring(0, 256); //truncate overlong descriptions -const homepage = Deno.env.get("TS_WEBSITE"); -const categories = Deno.env.get("TS_CATEGORIES").replace(/\s/g, ","); //support comma and new-line delimiters -const deps = Deno.env.get("TS_DEPS").replace(/\n/g, " "); -const community = Deno.env.get("TS_COMMUNITY"); -const nsfw = Deno.env.get("TS_NSFW"); -const wrap = Deno.env.get("TS_WRAP"); -const repo = Deno.env.get("TS_REPO"); - -const re = /([a-zA-Z_0-9]*-[a-zA-Z_0-9]*)[\-@](\d+\.\d+\.\d+)/; - -//these should be set already but we're rewriting the whole file anyways -tstore.package.namespace = namespace; -tstore.package.name = name; -tstore.package.versionNumber = version; -tstore.package.description = desc; - -tstore.publish.communities = [community]; -tstore.build.copy[0].target = wrap; -tstore.package.dependencies = {}; - -tstore.publish.repository = repo ?? "https://thunderstore.io"; - -//check for optional inputs -if (homepage && homepage !== "") { - tstore.package.websiteUrl = homepage; -} else { - tstore.package.websiteUrl = `${Deno.env.get( - "GITHUB_SERVER_URL" - )}/${Deno.env.get("GITHUB_REPOSITORY")}`; -} - -if (nsfw && nsfw === "true") { - tstore.package.containsNsfwContent = true; -} - -if (categories && categories !== "") { - console.log(`::debug::Parsing categories: ${categories}`); - tstore.publish.categories[community] = categories - .split(",") - .filter((e) => e) //only keep truthy elements - .map((e) => e.toLowerCase().trim()); -} - -if (deps && deps !== "") { - console.log("::debug::Parsing dependencies: ", deps.split(" ")); - const p = {}; - for (let d of deps.split(" ")) { - if (!d) { - console.log("::warn::Empty dependency", d); - continue; - } - - const parts = d.match(re); - if (parts) { - console.log("::debug::Parts:", parts); - p[parts[1]] = parts[2]; - } else { - console.log("::error::Malformed dependency at ", d); - Deno.exit(1); - } - } - - console.log("::debug:: Built depencendies:", p); - tstore.package.dependencies = p; -} - -//write config file back to disk -Deno.writeTextFile("./thunderstore.toml", TOML.stringify(tstore)); diff --git a/entrypoint.sh b/entrypoint.sh index 0198178..573099c 100755 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -1,97 +1,2 @@ #!/bin/bash -function setup() { - echo "::group::Set up environment" - - # If the TS_PATH var is set and not empty - if [ -n "$TS_PATH" ]; then - echo "::debug::TS_PATH found" - p=$(echo $TS_PATH | sed 's:$/*::') #trim any trailing '/' - else - echo "::debug::TS_PATH not set" - p="." - fi - - mkdir -p "/dist" - - # Move files to the dist directory for the tcli - echo "Move files from $p to /dist" - mv $p/* /dist - - # Move the README if it exists - if [ -e "/dist/README.md" ]; then - echo "Move README" - mv "/dist/README.md" "/" - elif [ -n "$TS_README" ]; then - echo "Download README from $TS_README" - wget -O "/README.md" "$TS_README" - fi - - if [ -e "/dist/icon.png" ]; then - echo "Move icon" - mv "/dist/icon.png" "/" - elif [ -n "$TS_ICON" ]; then - echo "Download icon from $TS_ICON" - wget -O "/icon.png" "$TS_ICON" - fi - - - echo "::endgroup::" - -} -function configure(){ - cd "/" - - echo "::group::Configure tcli" - - #tcli usage based off of https://github.com/R2Northstar/Northstar/blob/d8ad8f12f8bca1e8de96f5d7163f71997d487218/.github/workflows/build.yml#L132-L192 - echo "Init tcli config" - tcli init --package-name ${TS_NAME} --package-namespace ${TS_NAMESPACE} --package-version ${TS_VERSION#v} - - deno run --allow-net --allow-env --allow-read --allow-write cfg_edit.js - - echo "Done config edit" - echo - echo "::debug::$(cat thunderstore.toml)" - echo "::endgroup::" -} - - -function publish() { - if [ -n "$TS_DEV" ]; then - repo="https://thunderstore.dev" - elif [ -n "$TS_REPO" ]; then - repo="https://thunderstore.io" - else - repo="$TS_REPO" - fi - - # skip the build if there is a prebuilt package provided - if [ -n "$TS_FILE" ]; then - echo "::group::Publish package" - echo "Publish to $repo" - file="dist/$TS_FILE" - else - echo "::group::Build and publish" - tcli build - echo "Publish to $repo" - file="build/*.zip" - fi - - out=$(tcli publish --repository ${repo} --file ${file}) #capture the output to get the URL - # A bad response from the server doesn't exit with a non-zero status code - if [[ $? -ne 0 ]]; then - echo "::error::$(echo ${out} | grep -Eo ERROR:.*)" - exit 1 - fi - echo "url=$(echo ${out} | grep -Eo "https.*")" >> $GITHUB_OUTPUT - echo "Done!" - echo "::endgroup::" -} - -set -e -setup -configure -set +e # Turn this off so we can see the output from tcli publish -publish - - +bun run index.ts diff --git a/index.ts b/index.ts new file mode 100644 index 0000000..3aa59df --- /dev/null +++ b/index.ts @@ -0,0 +1,182 @@ +import TOML from "smol-toml"; +import fs from "node:fs/promises"; +import p from "node:path"; + +const namespace = Bun.env.TS_NAMESPACE; +const name = Bun.env.TS_NAME; +const version = (Bun.env.TS_VERSION ?? "0.0.0").replace(/v/g, ""); +const desc = (Bun.env.TS_DESC ?? "").substring(0, 256); //truncate overlong descriptions +const homepage = Bun.env.TS_WEBSITE; +const categories = (Bun.env.TS_CATEGORIES ?? "").replace(/\s/g, ","); //support comma and new-line delimiters +const deps = (Bun.env.TS_DEPS ?? "").replace(/\n/g, " "); +const community = Bun.env.TS_COMMUNITY; +const nsfw = Bun.env.TS_NSFW; +const wrap = Bun.env.TS_WRAP; +const repo = Bun.env.TS_REPO ?? "https://thunderstore.io"; +const target_repo = Bun.env.TS_DEV?.toLowerCase() === "true" ? "https://thunderstore.dev" : repo; + +const publish = async (target: string, file: string | undefined) => { + console.log("::group::Publish package"); + const args = file ? ["--file", file] : []; + const pub = Bun.spawnSync(["tcli", "publish", "--repository", target, ...args], { + env: {...Bun.env} + }); + const out = pub.stdout.toString(); + + console.log(out); + if (pub.exitCode !== 0 || out.toLowerCase().includes("error")) { + console.log("::error::Tcli encountered an error while publishing"); + console.log(`::error::${pub.stderr}`); + } + + const url = out.match(/https.*/g)?.[0]; + + if (url && Bun.env.GITHUB_OUTPUT) { + Bun.write(Bun.env.GITHUB_OUTPUT, `url=${url}`); + } else { + console.log("::error::Unable to write url to output") + console.log(url) + } + + console.log("Done!"); + console.log("::endgroup::"); +} + +// Skip everything if a prebuilt file is provided +if(Bun.env.TS_FILE) { + console.log("Publishing prebuild file"); + + await publish(target_repo, Bun.env.TS_FILE); + process.exit(0); +} + + +// Move files to where they're expected +console.log("::group::Set up environment"); +const path = p.normalize(Bun.env.TS_PATH ? Bun.env.TS_PATH : "./*"); +console.log("Moving files from", path, "to /dist"); + +// Create the dist dir +await fs.mkdir("/dist"); +Bun.spawnSync(["mv", path, "/dist"]); + +// The readme and icon need to be in the root though + +if (!Bun.env.TS_README && await Bun.file("/dist/README.md").exists()) { + Bun.spawnSync(["mv", "/dist/README.md", "/README.md"]); +} else if (Bun.env.TS_README) { + const readme = await fetch(Bun.env.TS_README); + await Bun.write("/README.md", readme); +} else { + console.log("::error::README.md doesn't exist and the readme input is unset"); +} + +if (!Bun.env.TS_ICON && await Bun.file("/dist/icon.png").exists()) { + Bun.spawnSync(["mv", "/dist/icon.png", "/icon.png"]); +} else if (Bun.env.TS_ICON) { + const icon = await fetch(Bun.env.TS_ICON); + await Bun.write("/icon.png", icon); +} else { + console.log("::error::icon.png doesn't exist and the icon input is unset"); +} + +console.log("::endgroup::"); + +// Configure tcli and build the pacakge if needed + +console.log("::group::Configure tcli"); +process.chdir("/"); + +console.log("Init tcli config"); +const config = Bun.spawnSync(["tcli", "init", "--package-name", name ?? "", "--package-namespace", namespace ?? "", "--package-version", version], { + env: {...Bun.env}, +}); +console.log(config.stdout.toString()); +if (config.exitCode !== 0) { + console.log("::error::Failed to init tcli"); + console.log(`::error::${config.stderr.toString()}`); + process.exit(1); +} + +// Bun-ified cfg_edit.js + +// Read in thunderstore.toml +// Bun actually supports importing TOML natively but can't currently write it +const tstore = TOML.parse(await Bun.file("./thunderstore.toml").text()); + +const re = /([a-zA-Z_0-9]*-[a-zA-Z_0-9]*)[\-@](\d+\.\d+\.\d+)/; + +//these should be set already but we're rewriting the whole file anyways +tstore.package = { + namespace: namespace ?? "", + name: name ?? "", + versionNumber: version, + description: desc, + dependencies: {} +}; + +tstore.publish = {} +if (community) + tstore.publish.communities = [community]; + +tstore.build = { + copy: [{ + target: wrap ?? "" + }] +}; + +tstore.publish.repository = repo ?? "https://thunderstore.io"; + +//check for optional inputs +if (homepage && homepage !== "") { + tstore.package.websiteUrl = homepage; +} else { + tstore.package.websiteUrl = `${Bun.env.GITHUB_SERVER_URL}/${Bun.env.GITHUB_REPOSITORY}`; +} + +if (nsfw && nsfw === "true") { + tstore.package.containsNsfwContent = true; +} + +if (categories && categories !== "") { + console.log(`::debug::Parsing categories: ${categories}`); + tstore.publish.categories = {}; + tstore.publish.categories[community ?? ""] = categories + .split(",") + .filter((e) => e) //only keep truthy elements + .map((e) => e.toLowerCase().trim()); +} + +if (deps && deps !== "") { + console.log("::debug::Parsing dependencies: ", deps.split(" ")); + const p: Record = {}; + for (let d of deps.split(" ")) { + if (!d) { + console.log("::warn::Empty dependency", d); + continue; + } + + const parts = d.match(re); + if (parts) { + console.log("::debug::Parts:", parts); + p[parts[1]] = parts[2]; + } else { + console.log("::error::Malformed dependency at ", d); + process.exit(1); + } + } + + console.log("::debug:: Built depencendies:", p); + tstore.package.dependencies = p; +} + +//write config file back to disk +await Bun.write("./thunderstore.toml", TOML.stringify(tstore)); + +console.log("Done config edit"); +console.log(`\n${TOML.stringify(tstore)}`); +console.log("::endgroup::"); + +// Build and publish package +await publish(target_repo, undefined); + diff --git a/package.json b/package.json new file mode 100644 index 0000000..bf8dd24 --- /dev/null +++ b/package.json @@ -0,0 +1,14 @@ +{ + "name": "upload-thunderstore-package", + "module": "index.ts", + "type": "module", + "devDependencies": { + "@types/bun": "latest" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "dependencies": { + "smol-toml": "^1.3.0" + } +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..238655f --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + // Enable latest features + "lib": ["ESNext", "DOM"], + "target": "ESNext", + "module": "ESNext", + "moduleDetection": "force", + "jsx": "react-jsx", + "allowJs": true, + + // Bundler mode + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "noEmit": true, + + // Best practices + "strict": true, + "skipLibCheck": true, + "noFallthroughCasesInSwitch": true, + + // Some stricter flags (disabled by default) + "noUnusedLocals": false, + "noUnusedParameters": false, + "noPropertyAccessFromIndexSignature": false + } +}