diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e9d6e90..985006d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,23 +1,76 @@ -name: release +name: Release on: - push: - tags: - - "v*.*.*" - - "v*.*.*-pre" workflow_dispatch: + inputs: + package: + description: "The published name of a single package to release" + type: choice + required: false + options: + - "all" + - "@nanoforge-dev/actions" + - "@nanoforge-dev/asset-manager" + - "@nanoforge-dev/common" + - "@nanoforge-dev/config" + - "@nanoforge-dev/core" + - "@nanoforge-dev/docgen" + - "@nanoforge-dev/ecs-client" + - "@nanoforge-dev/ecs-lib" + - "@nanoforge-dev/ecs-server" + - "@nanoforge-dev/graphics-2d" + - "@nanoforge-dev/input" + - "@nanoforge-dev/music" + - "@nanoforge-dev/network-client" + - "@nanoforge-dev/network-server" + - "@nanoforge-dev/sound" + - "@nanoforge-dev/utils-eslint-config" + - "@nanoforge-dev/utils-prettier-config" + exclude: + description: "Comma separated list of packages to exclude from release (if not depended upon)" + required: false + type: string + dry_run: + description: Perform a dry run? + type: boolean + default: false + +env: + EM_CACHE_FOLDER: "emsdk-cache" jobs: - release: + npm-publish: + name: npm publish runs-on: ubuntu-latest - name: Building stack + if: github.repository_owner == 'NanoForge-dev' steps: - - name: Checkout - uses: actions/checkout@v5 + - name: Checkout repository + uses: actions/checkout@v6 + + - name: Install Node.js v24 + uses: actions/setup-node@v6 + with: + node-version: 24 + package-manager-cache: false + registry-url: https://registry.npmjs.org/ + + - name: Install dependencies + uses: ./utils/actions/src/pnpm-install + + - name: "Install emscipten" + uses: "mymindstorm/setup-emsdk@v14" + with: + actions-cache-folder: ${{env.EM_CACHE_FOLDER}} + + - name: Build dependencies + run: pnpm run build - - name: Release - uses: softprops/action-gh-release@v2 - if: startsWith(github.ref, 'refs/tags/') + - name: Release packages + uses: ./utils/actions/src/release-packages with: - files: | - CHANGELOG.md + package: ${{ inputs.package }} + exclude: ${{ inputs.exclude }} + dry: ${{ inputs.dry_run }} + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.idea/betterCommentsSettings.xml b/.idea/betterCommentsSettings.xml new file mode 100644 index 0000000..4f152ed --- /dev/null +++ b/.idea/betterCommentsSettings.xml @@ -0,0 +1,31 @@ + + + + + + \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b0546b8..1988bfa 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -32,7 +32,7 @@ importers: version: 24.10.2 '@vitest/coverage-v8': specifier: ^4.0.15 - version: 4.0.15(vitest@4.0.15(@types/node@24.10.2)(jiti@2.6.1)(yaml@2.8.2)) + version: 4.0.15(vitest@4.0.15(@types/node@24.10.2)(jiti@2.6.1)(terser@5.44.1)(yaml@2.8.2)) eslint: specifier: ^9.39.1 version: 9.39.1(jiti@2.6.1) @@ -56,7 +56,7 @@ importers: version: 5.9.3 vitest: specifier: ^4.0.15 - version: 4.0.15(@types/node@24.10.2)(jiti@2.6.1)(yaml@2.8.2) + version: 4.0.15(@types/node@24.10.2)(jiti@2.6.1)(terser@5.44.1)(yaml@2.8.2) packages/asset-manager: dependencies: @@ -90,7 +90,7 @@ importers: version: 5.9.3 vitest: specifier: ^4.0.15 - version: 4.0.15(@types/node@24.10.2)(jiti@2.6.1)(yaml@2.8.2) + version: 4.0.15(@types/node@24.10.2)(jiti@2.6.1)(terser@5.44.1)(yaml@2.8.2) packages/common: devDependencies: @@ -240,7 +240,7 @@ importers: version: 5.9.3 vitest: specifier: ^4.0.15 - version: 4.0.15(@types/node@24.10.2)(jiti@2.6.1)(yaml@2.8.2) + version: 4.0.15(@types/node@24.10.2)(jiti@2.6.1)(terser@5.44.1)(yaml@2.8.2) packages/ecs-lib: dependencies: @@ -277,7 +277,7 @@ importers: version: 5.9.3 vitest: specifier: ^4.0.15 - version: 4.0.15(@types/node@24.10.2)(jiti@2.6.1)(yaml@2.8.2) + version: 4.0.15(@types/node@24.10.2)(jiti@2.6.1)(terser@5.44.1)(yaml@2.8.2) packages/ecs-server: dependencies: @@ -323,7 +323,7 @@ importers: version: 5.9.3 vitest: specifier: ^4.0.15 - version: 4.0.15(@types/node@24.10.2)(jiti@2.6.1)(yaml@2.8.2) + version: 4.0.15(@types/node@24.10.2)(jiti@2.6.1)(terser@5.44.1)(yaml@2.8.2) packages/graphics-2d: dependencies: @@ -360,7 +360,7 @@ importers: version: 5.9.3 vitest: specifier: ^4.0.15 - version: 4.0.15(@types/node@24.10.2)(jiti@2.6.1)(yaml@2.8.2) + version: 4.0.15(@types/node@24.10.2)(jiti@2.6.1)(terser@5.44.1)(yaml@2.8.2) packages/input: dependencies: @@ -535,6 +535,52 @@ importers: specifier: ^5.9.3 version: 5.9.3 + utils/actions: + dependencies: + '@actions/core': + specifier: ^2.0.1 + version: 2.0.1 + '@actions/github': + specifier: ^6.0.1 + version: 6.0.1 + commander: + specifier: ^14.0.2 + version: 14.0.2 + devDependencies: + '@favware/cliff-jumper': + specifier: ^6.0.0 + version: 6.0.0 + '@nanoforge-dev/utils-eslint-config': + specifier: workspace:^ + version: link:../eslint-config + '@nanoforge-dev/utils-prettier-config': + specifier: workspace:^ + version: link:../prettier-config + '@npm/types': + specifier: ^2.1.0 + version: 2.1.0 + '@trivago/prettier-plugin-sort-imports': + specifier: ^6.0.0 + version: 6.0.0(prettier@3.7.4) + '@types/bun': + specifier: ^1.3.5 + version: 1.3.5 + eslint: + specifier: ^9.39.1 + version: 9.39.1(jiti@2.6.1) + prettier: + specifier: ^3.6.2 + version: 3.7.4 + terser: + specifier: ^5.44.1 + version: 5.44.1 + tsup: + specifier: ^8.5.1 + version: 8.5.1(jiti@2.6.1)(postcss@8.5.6)(typescript@5.9.3)(yaml@2.8.2) + typescript: + specifier: ^5.9.3 + version: 5.9.3 + utils/eslint-config: dependencies: '@eslint/js': @@ -589,6 +635,24 @@ importers: packages: + '@actions/core@2.0.1': + resolution: {integrity: sha512-oBfqT3GwkvLlo1fjvhQLQxuwZCGTarTE5OuZ2Wg10hvhBj7LRIlF611WT4aZS6fDhO5ZKlY7lCAZTlpmyaHaeg==} + + '@actions/exec@2.0.0': + resolution: {integrity: sha512-k8ngrX2voJ/RIN6r9xB82NVqKpnMRtxDoiO+g3olkIUpQNqjArXrCQceduQZCQj3P3xm32pChRLqRrtXTlqhIw==} + + '@actions/github@6.0.1': + resolution: {integrity: sha512-xbZVcaqD4XnQAe35qSQqskb3SqIAfRyLBrHMd/8TuL7hJSz2QtbDwnNM8zWx4zO5l2fnGtseNE3MbEvD7BxVMw==} + + '@actions/http-client@2.2.3': + resolution: {integrity: sha512-mx8hyJi/hjFvbPokCg4uRd4ZX78t+YyRPtnKWwIl+RzNaVuFpQHfmlGVfsKEJN8LwTCvL+DfVgAM04XaHkm6bA==} + + '@actions/http-client@3.0.1': + resolution: {integrity: sha512-SbGS8c/vySbNO3kjFgSW77n83C4MQx/Yoe+b1hAdpuvfHxnkHzDq2pWljUpAA56Si1Gae/7zjeZsV0CYjmLo/w==} + + '@actions/io@2.0.0': + resolution: {integrity: sha512-Jv33IN09XLO+0HS79aaODsvIRyduiF7NY/F6LYeK5oeUmrsz7aFdRphQjFoESF4jS7lMauDOttKALcpapVDIAg==} + '@babel/code-frame@7.27.1': resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} engines: {node: '>=6.9.0'} @@ -1070,6 +1134,10 @@ packages: resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@fastify/busboy@2.1.1': + resolution: {integrity: sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==} + engines: {node: '>=14'} + '@favware/cliff-jumper@6.0.0': resolution: {integrity: sha512-9uXg/fGHFLh4AnG3HCtlrrrmDvUnmr5vrbs7H9pet3WlUCsGGGqeNT0bFb8LG0M0GatYUi9RM/F60p1yn2ndEA==} engines: {node: '>=v18'} @@ -1106,6 +1174,9 @@ packages: resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} engines: {node: '>=6.0.0'} + '@jridgewell/source-map@0.3.11': + resolution: {integrity: sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==} + '@jridgewell/sourcemap-codec@1.5.5': resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} @@ -1117,10 +1188,22 @@ packages: engines: {node: '>=18'} hasBin: true + '@npm/types@2.1.0': + resolution: {integrity: sha512-humQVe2BrWR7Yum5hGDYBnIPnnZJvKSOH/I4QN1ZL2bdb4c4zQHaHupEJ3cOkSJ07G3YfN793ptbNh196BWLgA==} + engines: {node: '>=18.6.0'} + + '@octokit/auth-token@4.0.0': + resolution: {integrity: sha512-tY/msAuJo6ARbK6SPIxZrPBms3xPbfwBrulZe0Wtr/DIY9lje2HeV1uoebShn6mx7SjCHif6EjMvoREj+gZ+SA==} + engines: {node: '>= 18'} + '@octokit/auth-token@5.1.2': resolution: {integrity: sha512-JcQDsBdg49Yky2w2ld20IHAlwr8d/d8N6NiOXbtuoPCqzbsiJgF633mVUw3x4mo0H5ypataQIX7SFu3yy44Mpw==} engines: {node: '>= 18'} + '@octokit/core@5.2.2': + resolution: {integrity: sha512-/g2d4sW9nUDJOMz3mabVQvOGhVa4e/BN/Um7yca9Bb2XTzPPnfTWHWQg+IsEYO7M3Vx+EXvaM/I2pJWIMun1bg==} + engines: {node: '>= 18'} + '@octokit/core@6.1.6': resolution: {integrity: sha512-kIU8SLQkYWGp3pVKiYzA5OSaNF5EE03P/R8zEmmrG6XwOg5oBjXyQVVIauQ0dgau4zYhpZEhJrvIYt6oM+zZZA==} engines: {node: '>= 18'} @@ -1129,27 +1212,67 @@ packages: resolution: {integrity: sha512-OlYOlZIsfEVZm5HCSR8aSg02T2lbUWOsCQoPKfTXJwDzcHQBrVBGdGXb89dv2Kw2ToZaRtudp8O3ZIYoaOjKlA==} engines: {node: '>= 18'} + '@octokit/endpoint@9.0.6': + resolution: {integrity: sha512-H1fNTMA57HbkFESSt3Y9+FBICv+0jFceJFPWDePYlR/iMGrwM5ph+Dd4XRQs+8X+PUFURLQgX9ChPfhJ/1uNQw==} + engines: {node: '>= 18'} + + '@octokit/graphql@7.1.1': + resolution: {integrity: sha512-3mkDltSfcDUoa176nlGoA32RGjeWjl3K7F/BwHwRMJUW/IteSa4bnSV8p2ThNkcIcZU2umkZWxwETSSCJf2Q7g==} + engines: {node: '>= 18'} + '@octokit/graphql@8.2.2': resolution: {integrity: sha512-Yi8hcoqsrXGdt0yObxbebHXFOiUA+2v3n53epuOg1QUgOB6c4XzvisBNVXJSl8RYA5KrDuSL2yq9Qmqe5N0ryA==} engines: {node: '>= 18'} + '@octokit/openapi-types@20.0.0': + resolution: {integrity: sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA==} + + '@octokit/openapi-types@24.2.0': + resolution: {integrity: sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==} + '@octokit/openapi-types@25.1.0': resolution: {integrity: sha512-idsIggNXUKkk0+BExUn1dQ92sfysJrje03Q0bv0e+KPLrvyqZF8MnBpFz8UNfYDwB3Ie7Z0TByjWfzxt7vseaA==} + '@octokit/plugin-paginate-rest@9.2.2': + resolution: {integrity: sha512-u3KYkGF7GcZnSD/3UP0S7K5XUFT2FkOQdcfXZGZQPGv3lm4F2Xbf71lvjldr8c1H3nNbF+33cLEkWYbokGWqiQ==} + engines: {node: '>= 18'} + peerDependencies: + '@octokit/core': '5' + + '@octokit/plugin-rest-endpoint-methods@10.4.1': + resolution: {integrity: sha512-xV1b+ceKV9KytQe3zCVqjg+8GTGfDYwaT1ATU5isiUyVtlVAO3HNdzpS4sr4GBx4hxQ46s7ITtZrAsxG22+rVg==} + engines: {node: '>= 18'} + peerDependencies: + '@octokit/core': '5' + '@octokit/plugin-retry@7.2.1': resolution: {integrity: sha512-wUc3gv0D6vNHpGxSaR3FlqJpTXGWgqmk607N9L3LvPL4QjaxDgX/1nY2mGpT37Khn+nlIXdljczkRnNdTTV3/A==} engines: {node: '>= 18'} peerDependencies: '@octokit/core': '>=6' + '@octokit/request-error@5.1.1': + resolution: {integrity: sha512-v9iyEQJH6ZntoENr9/yXxjuezh4My67CBSu9r6Ve/05Iu5gNgnisNWOsoJHTP6k0Rr0+HQIpnH+kyammu90q/g==} + engines: {node: '>= 18'} + '@octokit/request-error@6.1.8': resolution: {integrity: sha512-WEi/R0Jmq+IJKydWlKDmryPcmdYSVjL3ekaiEL1L9eo1sUnqMJ+grqmC9cjk7CA7+b2/T397tO5d8YLOH3qYpQ==} engines: {node: '>= 18'} + '@octokit/request@8.4.1': + resolution: {integrity: sha512-qnB2+SY3hkCmBxZsR/MPCybNmbJe4KAlfWErXq+rBKkQJlbjdJeS85VI9r8UqeLYLvnAenU8Q1okM/0MBsAGXw==} + engines: {node: '>= 18'} + '@octokit/request@9.2.4': resolution: {integrity: sha512-q8ybdytBmxa6KogWlNa818r0k1wlqzNC+yNkcQDECHvQo8Vmstrg18JwqJHdJdUiHD2sjlwBgSm9kHkOKe2iyA==} engines: {node: '>= 18'} + '@octokit/types@12.6.0': + resolution: {integrity: sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw==} + + '@octokit/types@13.10.0': + resolution: {integrity: sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==} + '@octokit/types@14.1.0': resolution: {integrity: sha512-1y6DgTy8Jomcpu33N+p5w58l6xyt55Ar2I91RPiIA0xCJBXyUAhXCcmZaDWSANiha7R9a6qJJ2CRomGPZ6f46g==} @@ -1304,6 +1427,9 @@ packages: svelte: optional: true + '@types/bun@1.3.5': + resolution: {integrity: sha512-RnygCqNrd3srIPEWBd5LFeUYG7plCoH2Yw9WaZGyNmdTEei+gWaHqydbaIRkIkcbXwhBT94q78QljxN0Sk838w==} + '@types/chai@5.2.3': resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} @@ -1498,6 +1624,9 @@ packages: balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + before-after-hook@2.2.3: + resolution: {integrity: sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==} + before-after-hook@3.0.2: resolution: {integrity: sha512-Nik3Sc0ncrMK4UUdXQmAnRtzmNQTAAXmXIopizwZ1W1t8QmfJj+zL4OA2I7XPTPW5z5TDqv4hRo/JzouDJnX3A==} @@ -1514,6 +1643,12 @@ packages: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} + buffer-from@1.1.2: + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + + bun-types@1.3.5: + resolution: {integrity: sha512-inmAYe2PFLs0SUbFOWSVD24sg1jFlMPxOjOSSCYqUgn4Hsc3rDc7dFvfVYjFPNHtov6kgUeulV4SxbuIV/stPw==} + bundle-require@5.1.0: resolution: {integrity: sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -1584,6 +1719,9 @@ packages: resolution: {integrity: sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==} engines: {node: '>=20'} + commander@2.20.3: + resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} + commander@4.1.1: resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} engines: {node: '>= 6'} @@ -1669,6 +1807,9 @@ packages: deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + deprecation@2.3.1: + resolution: {integrity: sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==} + detect-libc@2.1.2: resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} @@ -2272,6 +2413,9 @@ packages: obug@2.1.1: resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + onetime@7.0.0: resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==} engines: {node: '>=18'} @@ -2467,6 +2611,13 @@ packages: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} + source-map-support@0.5.21: + resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} + + source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + source-map@0.7.6: resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==} engines: {node: '>= 12'} @@ -2538,6 +2689,11 @@ packages: resolution: {integrity: sha512-7NyxrTE4Anh8km8iEy7o0QYPs+0JKBTj5ZaqHg6B39erLg0qYXN3BijtShwbsNSvQ+LN75+KV+C4QR/f6Gwnpg==} engines: {node: '>=18'} + terser@5.44.1: + resolution: {integrity: sha512-t/R3R/n0MSwnnazuPpPNVO60LX0SKL45pyl9YlvxIdkH0Of7D5qM2EVe+yASRIlY5pZ73nclYJfNANGWPwFDZw==} + engines: {node: '>=10'} + hasBin: true + text-extensions@2.4.0: resolution: {integrity: sha512-te/NtwBwfiNRLf9Ijqx3T0nlqZiQ2XrrtBvu+cLL8ZRrGkO0NHTug8MYFKyoSrv/sHTaSKfilUkizV6XhxMJ3g==} engines: {node: '>=8'} @@ -2612,6 +2768,10 @@ packages: typescript: optional: true + tunnel@0.0.6: + resolution: {integrity: sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==} + engines: {node: '>=0.6.11 <=0.7.0 || >=0.7.3'} + turbo-darwin-64@2.6.3: resolution: {integrity: sha512-BlJJDc1CQ7SK5Y5qnl7AzpkvKSnpkfPmnA+HeU/sgny3oHZckPV2776ebO2M33CYDSor7+8HQwaodY++IINhYg==} cpu: [x64] @@ -2668,6 +2828,10 @@ packages: undici-types@7.16.0: resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} + undici@5.29.0: + resolution: {integrity: sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==} + engines: {node: '>=14.0'} + unicorn-magic@0.1.0: resolution: {integrity: sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==} engines: {node: '>=18'} @@ -2676,6 +2840,9 @@ packages: resolution: {integrity: sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==} engines: {node: '>=18'} + universal-user-agent@6.0.1: + resolution: {integrity: sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ==} + universal-user-agent@7.0.3: resolution: {integrity: sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A==} @@ -2791,6 +2958,9 @@ packages: resolution: {integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==} engines: {node: '>=18'} + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + wrtc@0.4.7: resolution: {integrity: sha512-P6Hn7VT4lfSH49HxLHcHhDq+aFf/jd9dPY7lDHeFhZ22N3858EKuwm2jmnlPzpsRGEPaoF6XwkcxY5SYnt4f/g==} engines: {node: ^8.11.2 || >=10.0.0} @@ -2844,6 +3014,37 @@ packages: snapshots: + '@actions/core@2.0.1': + dependencies: + '@actions/exec': 2.0.0 + '@actions/http-client': 3.0.1 + + '@actions/exec@2.0.0': + dependencies: + '@actions/io': 2.0.0 + + '@actions/github@6.0.1': + dependencies: + '@actions/http-client': 2.2.3 + '@octokit/core': 5.2.2 + '@octokit/plugin-paginate-rest': 9.2.2(@octokit/core@5.2.2) + '@octokit/plugin-rest-endpoint-methods': 10.4.1(@octokit/core@5.2.2) + '@octokit/request': 8.4.1 + '@octokit/request-error': 5.1.1 + undici: 5.29.0 + + '@actions/http-client@2.2.3': + dependencies: + tunnel: 0.0.6 + undici: 5.29.0 + + '@actions/http-client@3.0.1': + dependencies: + tunnel: 0.0.6 + undici: 5.29.0 + + '@actions/io@2.0.0': {} + '@babel/code-frame@7.27.1': dependencies: '@babel/helper-validator-identifier': 7.28.5 @@ -3219,6 +3420,8 @@ snapshots: '@eslint/core': 0.17.0 levn: 0.4.1 + '@fastify/busboy@2.1.1': {} + '@favware/cliff-jumper@6.0.0': dependencies: '@favware/colorette-spinner': 1.0.1 @@ -3262,6 +3465,11 @@ snapshots: '@jridgewell/resolve-uri@3.1.2': {} + '@jridgewell/source-map@0.3.11': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + '@jridgewell/sourcemap-codec@1.5.5': {} '@jridgewell/trace-mapping@0.3.31': @@ -3282,8 +3490,22 @@ snapshots: - encoding - supports-color + '@npm/types@2.1.0': {} + + '@octokit/auth-token@4.0.0': {} + '@octokit/auth-token@5.1.2': {} + '@octokit/core@5.2.2': + dependencies: + '@octokit/auth-token': 4.0.0 + '@octokit/graphql': 7.1.1 + '@octokit/request': 8.4.1 + '@octokit/request-error': 5.1.1 + '@octokit/types': 13.10.0 + before-after-hook: 2.2.3 + universal-user-agent: 6.0.1 + '@octokit/core@6.1.6': dependencies: '@octokit/auth-token': 5.1.2 @@ -3299,14 +3521,39 @@ snapshots: '@octokit/types': 14.1.0 universal-user-agent: 7.0.3 + '@octokit/endpoint@9.0.6': + dependencies: + '@octokit/types': 13.10.0 + universal-user-agent: 6.0.1 + + '@octokit/graphql@7.1.1': + dependencies: + '@octokit/request': 8.4.1 + '@octokit/types': 13.10.0 + universal-user-agent: 6.0.1 + '@octokit/graphql@8.2.2': dependencies: '@octokit/request': 9.2.4 '@octokit/types': 14.1.0 universal-user-agent: 7.0.3 + '@octokit/openapi-types@20.0.0': {} + + '@octokit/openapi-types@24.2.0': {} + '@octokit/openapi-types@25.1.0': {} + '@octokit/plugin-paginate-rest@9.2.2(@octokit/core@5.2.2)': + dependencies: + '@octokit/core': 5.2.2 + '@octokit/types': 12.6.0 + + '@octokit/plugin-rest-endpoint-methods@10.4.1(@octokit/core@5.2.2)': + dependencies: + '@octokit/core': 5.2.2 + '@octokit/types': 12.6.0 + '@octokit/plugin-retry@7.2.1(@octokit/core@6.1.6)': dependencies: '@octokit/core': 6.1.6 @@ -3314,10 +3561,23 @@ snapshots: '@octokit/types': 14.1.0 bottleneck: 2.19.5 + '@octokit/request-error@5.1.1': + dependencies: + '@octokit/types': 13.10.0 + deprecation: 2.3.1 + once: 1.4.0 + '@octokit/request-error@6.1.8': dependencies: '@octokit/types': 14.1.0 + '@octokit/request@8.4.1': + dependencies: + '@octokit/endpoint': 9.0.6 + '@octokit/request-error': 5.1.1 + '@octokit/types': 13.10.0 + universal-user-agent: 6.0.1 + '@octokit/request@9.2.4': dependencies: '@octokit/endpoint': 10.1.4 @@ -3326,6 +3586,14 @@ snapshots: fast-content-type-parse: 2.0.1 universal-user-agent: 7.0.3 + '@octokit/types@12.6.0': + dependencies: + '@octokit/openapi-types': 20.0.0 + + '@octokit/types@13.10.0': + dependencies: + '@octokit/openapi-types': 24.2.0 + '@octokit/types@14.1.0': dependencies: '@octokit/openapi-types': 25.1.0 @@ -3422,6 +3690,10 @@ snapshots: transitivePeerDependencies: - supports-color + '@types/bun@1.3.5': + dependencies: + bun-types: 1.3.5 + '@types/chai@5.2.3': dependencies: '@types/deep-eql': 4.0.2 @@ -3545,7 +3817,7 @@ snapshots: '@typescript-eslint/types': 8.49.0 eslint-visitor-keys: 4.2.1 - '@vitest/coverage-v8@4.0.15(vitest@4.0.15(@types/node@24.10.2)(jiti@2.6.1)(yaml@2.8.2))': + '@vitest/coverage-v8@4.0.15(vitest@4.0.15(@types/node@24.10.2)(jiti@2.6.1)(terser@5.44.1)(yaml@2.8.2))': dependencies: '@bcoe/v8-coverage': 1.0.2 '@vitest/utils': 4.0.15 @@ -3558,7 +3830,7 @@ snapshots: obug: 2.1.1 std-env: 3.10.0 tinyrainbow: 3.0.3 - vitest: 4.0.15(@types/node@24.10.2)(jiti@2.6.1)(yaml@2.8.2) + vitest: 4.0.15(@types/node@24.10.2)(jiti@2.6.1)(terser@5.44.1)(yaml@2.8.2) transitivePeerDependencies: - supports-color @@ -3571,13 +3843,13 @@ snapshots: chai: 6.2.1 tinyrainbow: 3.0.3 - '@vitest/mocker@4.0.15(vite@7.2.7(@types/node@24.10.2)(jiti@2.6.1)(yaml@2.8.2))': + '@vitest/mocker@4.0.15(vite@7.2.7(@types/node@24.10.2)(jiti@2.6.1)(terser@5.44.1)(yaml@2.8.2))': dependencies: '@vitest/spy': 4.0.15 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 7.2.7(@types/node@24.10.2)(jiti@2.6.1)(yaml@2.8.2) + vite: 7.2.7(@types/node@24.10.2)(jiti@2.6.1)(terser@5.44.1)(yaml@2.8.2) '@vitest/pretty-format@4.0.15': dependencies: @@ -3660,6 +3932,8 @@ snapshots: balanced-match@1.0.2: {} + before-after-hook@2.2.3: {} + before-after-hook@3.0.2: {} bottleneck@2.19.5: {} @@ -3677,6 +3951,12 @@ snapshots: dependencies: fill-range: 7.1.1 + buffer-from@1.1.2: {} + + bun-types@1.3.5: + dependencies: + '@types/node': 24.10.2 + bundle-require@5.1.0(esbuild@0.27.1): dependencies: esbuild: 0.27.1 @@ -3736,6 +4016,8 @@ snapshots: commander@14.0.2: {} + commander@2.20.3: {} + commander@4.1.1: {} compare-func@2.0.0: @@ -3810,6 +4092,8 @@ snapshots: deep-is@0.1.4: {} + deprecation@2.3.1: {} + detect-libc@2.1.2: {} domexception@1.0.1: @@ -4415,6 +4699,10 @@ snapshots: obug@2.1.1: {} + once@1.4.0: + dependencies: + wrappy: 1.0.2 + onetime@7.0.0: dependencies: mimic-function: 5.0.1 @@ -4587,6 +4875,13 @@ snapshots: source-map-js@1.2.1: {} + source-map-support@0.5.21: + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + + source-map@0.6.1: {} + source-map@0.7.6: {} split2@4.2.0: {} @@ -4659,6 +4954,13 @@ snapshots: minizlib: 3.1.0 yallist: 5.0.0 + terser@5.44.1: + dependencies: + '@jridgewell/source-map': 0.3.11 + acorn: 8.15.0 + commander: 2.20.3 + source-map-support: 0.5.21 + text-extensions@2.4.0: {} thenify-all@1.6.0: @@ -4728,6 +5030,8 @@ snapshots: - tsx - yaml + tunnel@0.0.6: {} + turbo-darwin-64@2.6.3: optional: true @@ -4776,10 +5080,16 @@ snapshots: undici-types@7.16.0: {} + undici@5.29.0: + dependencies: + '@fastify/busboy': 2.1.1 + unicorn-magic@0.1.0: {} unicorn-magic@0.3.0: {} + universal-user-agent@6.0.1: {} + universal-user-agent@7.0.3: {} uri-js@4.4.1: @@ -4788,7 +5098,7 @@ snapshots: validator@13.15.23: {} - vite@7.2.7(@types/node@24.10.2)(jiti@2.6.1)(yaml@2.8.2): + vite@7.2.7(@types/node@24.10.2)(jiti@2.6.1)(terser@5.44.1)(yaml@2.8.2): dependencies: esbuild: 0.25.12 fdir: 6.5.0(picomatch@4.0.3) @@ -4800,12 +5110,13 @@ snapshots: '@types/node': 24.10.2 fsevents: 2.3.3 jiti: 2.6.1 + terser: 5.44.1 yaml: 2.8.2 - vitest@4.0.15(@types/node@24.10.2)(jiti@2.6.1)(yaml@2.8.2): + vitest@4.0.15(@types/node@24.10.2)(jiti@2.6.1)(terser@5.44.1)(yaml@2.8.2): dependencies: '@vitest/expect': 4.0.15 - '@vitest/mocker': 4.0.15(vite@7.2.7(@types/node@24.10.2)(jiti@2.6.1)(yaml@2.8.2)) + '@vitest/mocker': 4.0.15(vite@7.2.7(@types/node@24.10.2)(jiti@2.6.1)(terser@5.44.1)(yaml@2.8.2)) '@vitest/pretty-format': 4.0.15 '@vitest/runner': 4.0.15 '@vitest/snapshot': 4.0.15 @@ -4822,7 +5133,7 @@ snapshots: tinyexec: 1.0.2 tinyglobby: 0.2.15 tinyrainbow: 3.0.3 - vite: 7.2.7(@types/node@24.10.2)(jiti@2.6.1)(yaml@2.8.2) + vite: 7.2.7(@types/node@24.10.2)(jiti@2.6.1)(terser@5.44.1)(yaml@2.8.2) why-is-node-running: 2.3.0 optionalDependencies: '@types/node': 24.10.2 @@ -4872,6 +5183,8 @@ snapshots: string-width: 7.2.0 strip-ansi: 7.1.2 + wrappy@1.0.2: {} + wrtc@0.4.7: optionalDependencies: domexception: 1.0.1 diff --git a/utils/actions/.cliff-jumperrc.json b/utils/actions/.cliff-jumperrc.json new file mode 100644 index 0000000..b3bb770 --- /dev/null +++ b/utils/actions/.cliff-jumperrc.json @@ -0,0 +1,7 @@ +{ + "$schema": "https://raw.githubusercontent.com/favware/cliff-jumper/main/assets/cliff-jumper.schema.json", + "name": "actions", + "org": "nanoforge-dev", + "packagePath": "packages/actions", + "identifierBase": false +} diff --git a/utils/actions/.gitignore b/utils/actions/.gitignore new file mode 100644 index 0000000..f7652f8 --- /dev/null +++ b/utils/actions/.gitignore @@ -0,0 +1,231 @@ +### VisualStudioCode template +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +!.vscode/*.code-snippets + +# Local History for Visual Studio Code +.history/ + +# Built Visual Studio Code Extensions +*.vsix + +### JetBrains template +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# AWS User-specific +.idea/**/aws.xml + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/artifacts +# .idea/compiler.xml +# .idea/jarRepositories.xml +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# SonarLint plugin +.idea/sonarlint/ + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +### Node template +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# 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/) +.cache +.parcel-cache + +# Next.js build output +.next +out + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +# 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 +.cache + +# 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.* + +# Turbo +.turbo/ + +# Compiled files +src/**/*.js +src/**/*.d.ts diff --git a/utils/actions/.nvmrc b/utils/actions/.nvmrc new file mode 100644 index 0000000..c519bf5 --- /dev/null +++ b/utils/actions/.nvmrc @@ -0,0 +1 @@ +v24.11.0 diff --git a/utils/actions/.prettierignore b/utils/actions/.prettierignore new file mode 100644 index 0000000..64b127c --- /dev/null +++ b/utils/actions/.prettierignore @@ -0,0 +1,11 @@ +# Ignore files for PNPM, NPM and YARN +pnpm-lock.yaml +package-lock.json +yarn.lock +bun.lock + +.turbo/ +node_modules/ +dist/ +coverage/ +CHANGELOG.md diff --git a/utils/actions/LICENSE b/utils/actions/LICENSE new file mode 100644 index 0000000..4672486 --- /dev/null +++ b/utils/actions/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright © 2026 NanoForge + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/utils/actions/README.md b/utils/actions/README.md new file mode 100644 index 0000000..975bcd7 --- /dev/null +++ b/utils/actions/README.md @@ -0,0 +1,52 @@ +
+
+

+ NanoForge +

+
+

+ npm version + npm downloads + Tests status + Last commit. +

+
+ +## About + +`@nanoforge-dev/actions` is library that contains all utils for nanoforge actions. + +Most of the sources of this package come from [discord.js][discordjs-source]. Spetial thanks to them ! + +## Installation + +**Node.js 24.11.0 or newer is required.** + +```sh +npm install @nanoforge-dev/actions +yarn add @nanoforge-dev/actions +pnpm add @nanoforge-dev/actions +bun add @nanoforge-dev/actions +``` + +## Links + +- [GitHub][source] +- [npm][npm] + +## Contributing + +Before creating an issue, please ensure that it hasn't already been reported/suggested, and double-check the +[documentation][documentation]. +See [the contribution guide][contributing] if you'd like to submit a PR. + +## Help + +If you don't understand something in the documentation, you are experiencing problems, or you just need a gentle nudge in the right direction, please don't hesitate to ask questions in [discussions][discussions]. + +[documentation]: https://github.com/NanoForge-dev/Engine +[discussions]: https://github.com/NanoForge-dev/Engine/discussions +[source]: https://github.com/NanoForge-dev/Engine/tree/main/packages/actions +[npm]: https://www.npmjs.com/package/@nanoforge-dev/actions +[contributing]: https://github.com/NanoForge-dev/Engine/blob/main/.github/CONTRIBUTING.md +[discordjs-source]: https://github.com/discordjs/discord.js diff --git a/utils/actions/cliff.toml b/utils/actions/cliff.toml new file mode 100644 index 0000000..21fe47a --- /dev/null +++ b/utils/actions/cliff.toml @@ -0,0 +1,79 @@ +[changelog] +header = """ +# Changelog + +All notable changes to this project will be documented in this file.\n +""" +body = """ +{%- macro remote_url() -%} + https://github.com/{{ remote.github.owner }}/{{ remote.github.repo }} +{%- endmacro -%} +{% if version %}\ + # [{{ version | trim_start_matches(pat="v") }}]\ + {% if previous %}\ + {% if previous.version %}\ + ({{ self::remote_url() }}/compare/{{ previous.version }}...{{ version }})\ + {% else %}\ + ({{ self::remote_url() }}/tree/{{ version }})\ + {% endif %}\ + {% endif %} \ + - ({{ timestamp | date(format="%Y-%m-%d") }}) +{% else %}\ + # [unreleased] +{% endif %}\ +{% for group, commits in commits | group_by(attribute="group") %} + ## {{ group | upper_first }} + {% for commit in commits %} + - {% if commit.scope %}\ + **{{commit.scope}}:** \ + {% endif %}\ + {{ commit.message | upper_first }} ([{{ commit.id | truncate(length=7, end="") }}]({{ self::remote_url() }}/commit/{{ commit.id }}))\ + {% if commit.github.username %} by @{{ commit.github.username }}{%- endif %}\ + {% if commit.breaking %}\ + {% for footer in commit.footers %}\ + {% if footer.breaking %}\ + \n{% raw %} {% endraw %}- **{{ footer.token }}{{ footer.separator }}** {{ footer.value }}\ + {% endif %}\ + {% endfor %}\ + {% endif %}\ + {% endfor %} +{% endfor %}\ +{% if github.contributors | filter(attribute="is_first_time", value=true) | length %}\ + \n### New Contributors\n + {% for contributor in github.contributors | filter(attribute="is_first_time", value=true) %}\ + - @{{ contributor.username }} made their first contribution in #{{ contributor.pr_number }} + {% endfor %}\ +{% endif %}\n +""" +trim = true +footer = "" + +[git] +conventional_commits = true +filter_unconventional = true +commit_parsers = [ + { message = "^feat", group = "Features"}, + { message = "^fix", group = "Bug Fixes"}, + { message = "^docs", group = "Documentation"}, + { message = "^perf", group = "Performance"}, + { message = "^refactor", group = "Refactor"}, + { message = "^types", group = "Typings"}, + { message = ".*deprecated", body = ".*deprecated", group = "Deprecation"}, + { message = "^revert", skip = true}, + { message = "^style", group = "Styling"}, + { message = "^test", group = "Testing"}, + { message = "^chore", skip = true}, + { message = "^ci", skip = true}, + { message = "^build", skip = true}, + { body = ".*security", group = "Security"}, +] +filter_commits = true +protect_breaking_commits = true +tag_pattern = "@nanoforge-dev/actions@[0-9]*" +ignore_tags = "" +topo_order = false +sort_commits = "newest" + +[remote.github] +owner = "NanoForge-dev" +repo = "Engine" diff --git a/utils/actions/eslint.config.js b/utils/actions/eslint.config.js new file mode 100644 index 0000000..62ec06d --- /dev/null +++ b/utils/actions/eslint.config.js @@ -0,0 +1,3 @@ +import config from "@nanoforge-dev/utils-eslint-config"; + +export default config; diff --git a/utils/actions/package.json b/utils/actions/package.json new file mode 100644 index 0000000..08883dd --- /dev/null +++ b/utils/actions/package.json @@ -0,0 +1,77 @@ +{ + "$schema": "https://json.schemastore.org/package.json", + "name": "@nanoforge-dev/actions", + "version": "1.0.0", + "description": "NanoForge Engine - Actions", + "keywords": [ + "nanoforge", + "game", + "engine" + ], + "homepage": "https://github.com/NanoForge-dev/Engine#readme", + "bugs": "https://github.com/NanoForge-dev/Engine/issues", + "license": "MIT", + "contributors": [ + "Bill ", + "Exelo ", + "Fexkoser ", + "Tchips " + ], + "files": [ + "dist" + ], + "exports": { + "./package.json": "./package.json" + }, + "type": "module", + "directories": { + "lib": "src" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/NanoForge-dev/Engine.git", + "directory": "packages/actions" + }, + "funding": "https://github.com/NanoForge-dev/Engine?sponsor", + "scripts": { + "build": "tsc --noEmit && tsup", + "lint": "prettier --check . && eslint --format=pretty src", + "format": "prettier --write . && eslint --fix --format=pretty src", + "prepack": "pnpm run build && pnpm run lint", + "changelog": "git cliff --prepend ./CHANGELOG.md -u -c ./cliff.toml -r ../../ --include-path 'packages/actions/*'", + "release": "cliff-jumper" + }, + "dependencies": { + "@actions/core": "^2.0.1", + "@actions/github": "^6.0.1", + "commander": "^14.0.2" + }, + "devDependencies": { + "@favware/cliff-jumper": "^6.0.0", + "@nanoforge-dev/utils-eslint-config": "workspace:^", + "@nanoforge-dev/utils-prettier-config": "workspace:^", + "@npm/types": "^2.1.0", + "@trivago/prettier-plugin-sort-imports": "^6.0.0", + "@types/bun": "^1.3.5", + "eslint": "^9.39.1", + "prettier": "^3.6.2", + "terser": "^5.44.1", + "tsup": "^8.5.1", + "typescript": "^5.9.3" + }, + "packageManager": "pnpm@10.22.0", + "engines": { + "node": "24.11.0" + }, + "publishConfig": { + "access": "public" + }, + "lint-staged": { + "**/*.ts": [ + "prettier --write" + ], + "src/**/*.ts": [ + "eslint --fix" + ] + } +} diff --git a/utils/actions/prettier.config.js b/utils/actions/prettier.config.js new file mode 100644 index 0000000..27d0e26 --- /dev/null +++ b/utils/actions/prettier.config.js @@ -0,0 +1,3 @@ +import config from "@nanoforge-dev/utils-prettier-config"; + +export default config; diff --git a/utils/actions/src/pnpm-install/action.yml b/utils/actions/src/pnpm-install/action.yml new file mode 100644 index 0000000..17996f5 --- /dev/null +++ b/utils/actions/src/pnpm-install/action.yml @@ -0,0 +1,42 @@ +name: "pnpm install" +description: "Run pnpm install with cache enabled" +runs: + using: "composite" + steps: + - name: Set up swap space + if: runner.os == 'Linux' + uses: pierotofy/set-swap-space@v1.0 + with: + swap-size-gb: 10 + + - uses: pnpm/action-setup@v4.1.0 + name: Install pnpm + with: + run_install: false + + - name: Expose pnpm config(s) through "$GITHUB_OUTPUT" + id: pnpm-config + shell: bash + run: | + echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT + + - name: Cache rotation keys + id: cache-rotation + shell: bash + run: | + echo "YEAR_MONTH=$(/bin/date -u "+%Y%m")" >> $GITHUB_OUTPUT + + - uses: actions/cache@v4 + name: Setup pnpm cache + with: + path: ${{ steps.pnpm-config.outputs.STORE_PATH }} + key: ${{ runner.os }}-pnpm-store-cache-${{ steps.cache-rotation.outputs.YEAR_MONTH }}-${{ hashFiles('**/pnpm-lock.yaml') }} + restore-keys: | + ${{ runner.os }}-pnpm-store-cache-${{ steps.cache-rotation.outputs.YEAR_MONTH }}- + + - name: Install dependencies + shell: bash + run: | + pnpm install --frozen-lockfile --prefer-offline --loglevel error + env: + HUSKY: "0" diff --git a/utils/actions/src/release-packages/action.yml b/utils/actions/src/release-packages/action.yml new file mode 100644 index 0000000..be41749 --- /dev/null +++ b/utils/actions/src/release-packages/action.yml @@ -0,0 +1,27 @@ +name: "Release Packages" +description: "Tags and releases any unreleased packages" +inputs: + dev: + description: "Releases development versions of packages (skips tagging and github releases)" + default: "false" + dry: + description: "Perform a dry run that skips publishing and outputs logs indicating what would have happened" + default: "false" + package: + description: "The published name of a single package to release" + exclude: + description: "Comma separated list of packages to exclude from release (if not depended upon)" + tag: + description: "The tag to use, generally a feature name" +runs: + using: composite + steps: + - uses: oven-sh/setup-bun@v2 + - run: bun $GITHUB_ACTION_PATH/index.ts + shell: bash + env: + INPUT_DEV: ${{ inputs.dev }} + INPUT_DRY: ${{ inputs.dry }} + INPUT_PACKAGE: ${{ inputs.package }} + INPUT_EXCLUDE: ${{ inputs.exclude }} + INPUT_TAG: ${{ inputs.tag }} diff --git a/utils/actions/src/release-packages/generate-release-tree.ts b/utils/actions/src/release-packages/generate-release-tree.ts new file mode 100644 index 0000000..c799911 --- /dev/null +++ b/utils/actions/src/release-packages/generate-release-tree.ts @@ -0,0 +1,242 @@ +import { info, warning } from "@actions/core"; +import type { PackageJSON, PackumentVersion } from "@npm/types"; +import { $, file, write } from "bun"; + +interface pnpmTreeDependency { + from: string; + path: string; + version: string; +} + +interface pnpmTree { + dependencies?: Record; + name?: string; + path: string; + private?: boolean; + unsavedDependencies?: Record; + version?: string; +} + +export interface ReleaseEntry { + changelog?: string; + dependsOn?: string[]; + name: string; + version: string; +} + +async function fetchDevVersion(pkg: string, tag: string) { + try { + const res = await fetch(`https://registry.npmjs.org/${pkg}/${tag}`); + if (!res.ok) return null; + const packument = (await res.json()) as PackumentVersion; + return packument.version; + } catch { + return null; + } +} + +async function getReleaseEntries(dry: boolean, devTag?: string) { + const releaseEntries: ReleaseEntry[] = []; + const packageList: pnpmTree[] = + await $`pnpm list --recursive --only-projects --filter {packages/\*} --filter {utils/\*} --prod --json`.json(); + + const commitHash = (await $`git rev-parse --short HEAD`.text()).trim(); + const timestamp = Math.round(Date.now() / 1_000); + + for (const pkg of packageList) { + // Don't release private packages ever (npm will error anyways) + if (pkg.private) continue; + // Just in case + if (!pkg.version || !pkg.name) continue; + + const release: ReleaseEntry = { + name: pkg.name, + version: pkg.version, + }; + + if (devTag) { + // Replace workspace dependencies with * to pin to associated dev versions + if (!dry) { + const pkgJsonString = await file(`${pkg.path}/package.json`).text(); + await write( + `${pkg.path}/package.json`, + pkgJsonString.replaceAll(/workspace:[\^~]/g, "workspace:*"), + ); + } + + const devVersion = await fetchDevVersion(pkg.name, devTag); + if (devVersion?.endsWith(commitHash)) { + // Write the currently released dev version so when pnpm publish runs on dependents they depend on the dev versions + if (dry) { + info(`[DRY] ${pkg.name}@${devVersion} already released. Editing package.json version.`); + } else { + const pkgJson = (await file(`${pkg.path}/package.json`).json()) as PackageJSON; + pkgJson.version = devVersion; + await write(`${pkg.path}/package.json`, JSON.stringify(pkgJson, null, "\t")); + } + + release.version = devVersion; + } else if (dry) { + info(`[DRY] Bumping ${pkg.name} via git-cliff.`); + release.version = `${pkg.version}.DRY-${devTag}.${timestamp}-${commitHash}`; + } else { + await $`pnpm --filter=${pkg.name} run release --preid "${devTag}.${timestamp}-${commitHash}" --skip-changelog`; + // Read again instead of parsing the output to be sure we're matching when checking against npm + const pkgJson = (await file(`${pkg.path}/package.json`).json()) as PackageJSON; + release.version = pkgJson.version; + } + } + // Only need changelog for releases published to github + else { + try { + // Find and parse changelog to post in github release + const changelogFile = await file(`${pkg.path}/CHANGELOG.md`).text(); + + let changelogLines: string[] = []; + let foundChangelog = false; + + for (const line of changelogFile.split("\n")) { + if (line.startsWith("# [")) { + if (foundChangelog) { + if (changelogLines.at(-1) === "") { + changelogLines = changelogLines.slice(2, -1); + } + + break; + } + + // Check changelog release version and assume no changelog if version does not match + if (!line.startsWith(`# [${release.name}@${release.version}]`)) { + break; + } + + foundChangelog = true; + } + + if (foundChangelog) { + changelogLines.push(line); + } + } + + release.changelog = changelogLines.join("\n"); + } catch (error) { + // Probably just no changelog file but log just in case + warning(`Error parsing changelog for ${pkg.name}, will use auto generated: ${error}`); + } + } + + if (pkg.dependencies) { + release.dependsOn = Object.keys(pkg.dependencies); + } + + releaseEntries.push(release); + } + + return releaseEntries; +} + +export async function generateReleaseTree( + dry: boolean, + devTag?: string, + packageName?: string, + exclude?: string[], +) { + let releaseEntries = await getReleaseEntries(dry, devTag); + // Try to early return if the package doesn't have deps + if (packageName && packageName !== "all") { + const releaseEntry = releaseEntries.find((entry) => entry.name === packageName); + if (!releaseEntry) { + throw new Error(`Package ${packageName} not releaseable`); + } + + if (!releaseEntry.dependsOn) { + return [[releaseEntry]]; + } + } + + // Generate the whole tree first, then prune if specified + const releaseTree: ReleaseEntry[][] = []; + const didRelease = new Set(); + + while (releaseEntries.length) { + const nextBranch: ReleaseEntry[] = []; + const unreleased: ReleaseEntry[] = []; + for (const entry of releaseEntries) { + if (!entry.dependsOn) { + nextBranch.push(entry); + continue; + } + + const allDepsReleased = entry.dependsOn.every((dep) => didRelease.has(dep)); + if (allDepsReleased) { + nextBranch.push(entry); + } else { + unreleased.push(entry); + } + } + + // Update didRelease in a second loop to avoid loop order issues + for (const release of nextBranch) { + didRelease.add(release.name); + } + + if (releaseEntries.length === unreleased.length) { + throw new Error( + `One or more packages have dependents that can't be released: ${unreleased.map((entry) => entry.name).join(",")}`, + ); + } + + releaseTree.push(nextBranch); + releaseEntries = unreleased; + } + + // Prune exclusions + if ((!packageName || packageName === "all") && Array.isArray(exclude) && exclude.length) { + const neededPackages = new Set(); + const excludedReleaseTree: ReleaseEntry[][] = []; + + for (const releaseBranch of releaseTree.reverse()) { + const newThisBranch: ReleaseEntry[] = []; + + for (const entry of releaseBranch) { + if (exclude.includes(entry.name) && !neededPackages.has(entry.name)) { + continue; + } + + newThisBranch.push(entry); + for (const dep of entry.dependsOn ?? []) { + neededPackages.add(dep); + } + } + + if (newThisBranch.length) excludedReleaseTree.unshift(newThisBranch); + } + + return excludedReleaseTree; + } + + if (!packageName || packageName === "all") { + return releaseTree; + } + + // Prune the tree for the specified package + const neededPackages = new Set([packageName]); + const packageReleaseTree: ReleaseEntry[][] = []; + + for (const releaseBranch of releaseTree.reverse()) { + const newThisBranch: ReleaseEntry[] = []; + + for (const entry of releaseBranch) { + if (neededPackages.has(entry.name)) { + newThisBranch.push(entry); + for (const dep of entry.dependsOn ?? []) { + neededPackages.add(dep); + } + } + } + + if (newThisBranch.length) packageReleaseTree.unshift(newThisBranch); + } + + return packageReleaseTree; +} diff --git a/utils/actions/src/release-packages/index.ts b/utils/actions/src/release-packages/index.ts new file mode 100644 index 0000000..0703feb --- /dev/null +++ b/utils/actions/src/release-packages/index.ts @@ -0,0 +1,118 @@ +import { endGroup, getBooleanInput, getInput, startGroup, summary } from "@actions/core"; +import { program } from "commander"; + +import { generateReleaseTree } from "./generate-release-tree"; +import { releasePackage } from "./release-package"; + +function npmPackageLink(packageName: string) { + return `https://npmjs.com/package/${packageName}` as const; +} + +const excludeInput = getInput("exclude"); +let dryInput = false; +let devInput = false; + +try { + devInput = getBooleanInput("dev"); +} catch { + // We're not running in actions +} + +try { + dryInput = getBooleanInput("dry"); +} catch { + // We're not running in actions or the input isn't set (cron) +} + +program + .name("release packages") + .description("releases monorepo packages with proper sequencing") + .argument("[package]", "release a specific package (and it's dependencies)", getInput("package")) + .option( + "-e, --exclude ", + "exclude specific packages from releasing (will still release if necessary for another package)", + excludeInput ? excludeInput.split(",") : [], + ) + .option("--dry", "skips actual publishing and outputs logs instead", dryInput) + .option("--dev", "publishes development versions and skips tagging / github releases", devInput) + .option("--tag ", 'tag to use for dev releases (defaults to "dev")', getInput("tag")) + .parse(); + +const { + exclude, + dry, + dev, + tag: inputTag, +} = program.opts<{ dev: boolean; dry: boolean; exclude: string[]; tag: string }>(); + +// All this because getInput('tag') will return empty string when not set :P +if (!dev && inputTag.length) { + throw new Error("The --tag option can only be used with --dev"); +} + +const tag = inputTag.length ? inputTag : dev ? "dev" : undefined; +const [packageName] = program.processedArgs as [string]; +const tree = await generateReleaseTree(dry, tag, packageName, exclude); + +interface ReleaseResult { + identifier: string; + url: string; +} + +const publishedPackages: ReleaseResult[] = []; +const skippedPackages: ReleaseResult[] = []; + +for (const branch of tree) { + startGroup(`Releasing ${branch.map((entry) => `${entry.name}@${entry.version}`).join(", ")}`); + + await Promise.all( + branch.map(async (release) => { + const published = await releasePackage(release, dry, tag); + const identifier = `${release.name}@${release.version}`; + + if (published) { + publishedPackages.push({ identifier, url: npmPackageLink(release.name) }); + } else { + skippedPackages.push({ identifier, url: npmPackageLink(release.name) }); + } + }), + ); + + endGroup(); +} + +const result = summary.addHeading("Release summary"); + +if (dry) { + result.addRaw("\n\n> [!NOTE]\n> This is a dry run.\n\n"); +} + +result.addHeading("Released", 2); + +if (publishedPackages.length === 0) { + result.addRaw("\n_None_\n\n"); +} else { + result.addRaw("\n"); + + for (const { identifier, url } of publishedPackages) { + result.addRaw(`- [${identifier}](${url})\n`); + } + + result.addRaw(`\n`); +} + +result.addHeading("Skipped", 2); + +if (skippedPackages.length === 0) { + result.addRaw("\n_None_\n\n"); +} else { + result.addRaw("\n"); + + for (const { identifier, url } of skippedPackages) { + result.addRaw(`- [${identifier}](${url})\n`); + } + + result.addRaw(`\n`); +} + +await result.write(); diff --git a/utils/actions/src/release-packages/release-package.ts b/utils/actions/src/release-packages/release-package.ts new file mode 100644 index 0000000..4f161ab --- /dev/null +++ b/utils/actions/src/release-packages/release-package.ts @@ -0,0 +1,86 @@ +import { info, warning } from "@actions/core"; +import { context, getOctokit } from "@actions/github"; +import { $ } from "bun"; +import process from "node:process"; + +import type { ReleaseEntry } from "./generate-release-tree"; + +let octokit: ReturnType | undefined; + +if (process.env.GITHUB_TOKEN) { + octokit = getOctokit(process.env.GITHUB_TOKEN); +} + +async function checkRegistry(release: ReleaseEntry) { + const res = await fetch(`https://registry.npmjs.org/${release.name}/${release.version}`); + return res.ok; +} + +async function gitTagAndRelease(release: ReleaseEntry, dry: boolean) { + const tagName = `${release.name}@${release.version}`; + + if (dry) { + info(`[DRY] Release would be "${tagName}", skipping release creation.`); + return; + } + + const commitHash = (await $`git rev-parse HEAD`.text()).trim(); + + try { + await octokit?.rest.repos.createRelease({ + ...context.repo, + tag_name: tagName, + target_commitish: commitHash, + name: tagName, + body: release.changelog ?? "", + generate_release_notes: release.changelog === undefined, + make_latest: "true", + }); + } catch (error) { + warning(`Failed to create github release: ${error}`); + } +} + +export async function releasePackage( + release: ReleaseEntry, + dry: boolean, + devTag?: string, + doGitRelease = !devTag, +) { + // Sanity check against the registry first + if (await checkRegistry(release)) { + info(`${release.name}@${release.version} already published, skipping.`); + return false; + } + + if (dry) { + info(`[DRY] Releasing ${release.name}@${release.version}`); + } else { + await $`pnpm --filter=${release.name} publish --provenance --no-git-checks ${devTag ? `--tag=${devTag}` : ""}`; + } + + // && !devTag just to be sure + if (doGitRelease && !devTag) await gitTagAndRelease(release, dry); + + if (dry) return true; + + const before = performance.now(); + + // Poll registry to ensure next publishes won't fail + await new Promise((resolve, reject) => { + const interval = setInterval(async () => { + if (await checkRegistry(release)) { + clearInterval(interval); + resolve(); + return; + } + + if (performance.now() > before + 5 * 60 * 1_000) { + clearInterval(interval); + reject(new Error(`Release for ${release.name} failed.`)); + } + }, 15_000); + }); + + return true; +} diff --git a/utils/actions/tsconfig.json b/utils/actions/tsconfig.json new file mode 100644 index 0000000..b8e2058 --- /dev/null +++ b/utils/actions/tsconfig.json @@ -0,0 +1,10 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig.json", + "extends": "../../tsconfig.json", + "compilerOptions": { + "types": ["node"], + "skipLibCheck": true + }, + "include": ["src/**/*.ts"], + "exclude": ["node_modules", "dist"] +} diff --git a/utils/actions/tsup.config.ts b/utils/actions/tsup.config.ts new file mode 100644 index 0000000..e2e7148 --- /dev/null +++ b/utils/actions/tsup.config.ts @@ -0,0 +1,11 @@ +import { createTsupConfig } from "../../tsup.config.js"; + +export default [ + createTsupConfig({ + entry: ["src/release-packages/index.ts"], + dts: false, + format: "esm", + minify: "terser", + target: "esnext", + }), +];