diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2ca5c5d..dd6d808 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,6 +25,11 @@ jobs: with: go-version-file: go.mod + - name: Install Bun + uses: oven-sh/setup-bun@v2 + with: + bun-version: '1.2.20' + - name: Verify formatting shell: bash run: | @@ -39,3 +44,9 @@ jobs: - name: Build CLI run: go build ./cmd/metorial + + - name: Install npm workspace dependencies + run: bun install --frozen-lockfile + + - name: Build npm packages + run: bun run build:npm diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ca01800..33e8d63 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -18,6 +18,7 @@ jobs: CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} CLOUDFLARE_PAGES_PROJECT: 'metorial-cli' GITHUB_TOKEN: ${{ github.token }} + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} HOMEBREW_TAP_GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} SCOOP_BUCKET_GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} CHOCOLATEY_API_KEY: ${{ secrets.CHOCOLATEY_API_KEY }} @@ -35,11 +36,20 @@ jobs: with: go-version-file: go.mod + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + registry-url: https://registry.npmjs.org + - name: Install Bun uses: oven-sh/setup-bun@v2 with: bun-version: '1.2.20' + - name: Install npm workspace dependencies + run: bun install --frozen-lockfile + - name: Resolve GoReleaser skip flags shell: bash run: | @@ -75,6 +85,15 @@ jobs: version: '~> v2' args: release --clean ${{ env.GORELEASER_SKIP_ARGS }} + - name: Set npm package versions + run: bun run set-npm-version -- "${GITHUB_REF_NAME#v}" + + - name: Build npm packages + run: bun run build:npm + + - name: Publish npm packages + run: bun run publish:npm + - name: Build hosted installer site run: bun ./scripts/build-public.ts diff --git a/.gitignore b/.gitignore index d6fd2b1..1b94725 100644 --- a/.gitignore +++ b/.gitignore @@ -28,6 +28,8 @@ go.work.sum dist public .metorial +node_modules +npm/*/dist npm/node_modules metorial diff --git a/bun.lock b/bun.lock new file mode 100644 index 0000000..823fd89 --- /dev/null +++ b/bun.lock @@ -0,0 +1,282 @@ +{ + "lockfileVersion": 1, + "workspaces": { + "": { + "name": "@metorial/cli-workspace", + "devDependencies": { + "@types/node": "^24.5.2", + "tsup": "^8.5.0", + "typescript": "^5.9.2", + }, + }, + "npm/cli": { + "name": "@metorial/cli", + "version": "0.0.0-dev", + "bin": { + "metorial": "./dist/bin.cjs", + }, + "dependencies": { + "@metorial/cli-core": "workspace:*", + }, + }, + "npm/cli-core": { + "name": "@metorial/cli-core", + "version": "0.0.0-dev", + "dependencies": { + "extract-zip": "^2.0.1", + "tar": "^7.4.3", + }, + }, + "npm/create": { + "name": "create-metorial", + "version": "0.0.0-dev", + "bin": { + "create-metorial": "./dist/bin.cjs", + }, + "dependencies": { + "@metorial/cli-core": "workspace:*", + }, + }, + }, + "packages": { + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.7", "", { "os": "aix", "cpu": "ppc64" }, "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg=="], + + "@esbuild/android-arm": ["@esbuild/android-arm@0.27.7", "", { "os": "android", "cpu": "arm" }, "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ=="], + + "@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.7", "", { "os": "android", "cpu": "arm64" }, "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ=="], + + "@esbuild/android-x64": ["@esbuild/android-x64@0.27.7", "", { "os": "android", "cpu": "x64" }, "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg=="], + + "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.7", "", { "os": "darwin", "cpu": "arm64" }, "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw=="], + + "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.7", "", { "os": "darwin", "cpu": "x64" }, "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ=="], + + "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.7", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w=="], + + "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.7", "", { "os": "freebsd", "cpu": "x64" }, "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ=="], + + "@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.7", "", { "os": "linux", "cpu": "arm" }, "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA=="], + + "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.7", "", { "os": "linux", "cpu": "arm64" }, "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A=="], + + "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.7", "", { "os": "linux", "cpu": "ia32" }, "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg=="], + + "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.7", "", { "os": "linux", "cpu": "none" }, "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q=="], + + "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.7", "", { "os": "linux", "cpu": "none" }, "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw=="], + + "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.7", "", { "os": "linux", "cpu": "ppc64" }, "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ=="], + + "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.7", "", { "os": "linux", "cpu": "none" }, "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ=="], + + "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.7", "", { "os": "linux", "cpu": "s390x" }, "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw=="], + + "@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.7", "", { "os": "linux", "cpu": "x64" }, "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA=="], + + "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.7", "", { "os": "none", "cpu": "arm64" }, "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w=="], + + "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.7", "", { "os": "none", "cpu": "x64" }, "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw=="], + + "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.7", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A=="], + + "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.7", "", { "os": "openbsd", "cpu": "x64" }, "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg=="], + + "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.7", "", { "os": "none", "cpu": "arm64" }, "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw=="], + + "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.7", "", { "os": "sunos", "cpu": "x64" }, "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA=="], + + "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.7", "", { "os": "win32", "cpu": "arm64" }, "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA=="], + + "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.7", "", { "os": "win32", "cpu": "ia32" }, "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw=="], + + "@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.7", "", { "os": "win32", "cpu": "x64" }, "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg=="], + + "@isaacs/fs-minipass": ["@isaacs/fs-minipass@4.0.1", "", { "dependencies": { "minipass": "^7.0.4" } }, "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w=="], + + "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="], + + "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], + + "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="], + + "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], + + "@metorial/cli": ["@metorial/cli@workspace:npm/cli"], + + "@metorial/cli-core": ["@metorial/cli-core@workspace:npm/cli-core"], + + "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.60.1", "", { "os": "android", "cpu": "arm" }, "sha512-d6FinEBLdIiK+1uACUttJKfgZREXrF0Qc2SmLII7W2AD8FfiZ9Wjd+rD/iRuf5s5dWrr1GgwXCvPqOuDquOowA=="], + + "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.60.1", "", { "os": "android", "cpu": "arm64" }, "sha512-YjG/EwIDvvYI1YvYbHvDz/BYHtkY4ygUIXHnTdLhG+hKIQFBiosfWiACWortsKPKU/+dUwQQCKQM3qrDe8c9BA=="], + + "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.60.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-mjCpF7GmkRtSJwon+Rq1N8+pI+8l7w5g9Z3vWj4T7abguC4Czwi3Yu/pFaLvA3TTeMVjnu3ctigusqWUfjZzvw=="], + + "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.60.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-haZ7hJ1JT4e9hqkoT9R/19XW2QKqjfJVv+i5AGg57S+nLk9lQnJ1F/eZloRO3o9Scy9CM3wQ9l+dkXtcBgN5Ew=="], + + "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.60.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-czw90wpQq3ZsAVBlinZjAYTKduOjTywlG7fEeWKUA7oCmpA8xdTkxZZlwNJKWqILlq0wehoZcJYfBvOyhPTQ6w=="], + + "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.60.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-KVB2rqsxTHuBtfOeySEyzEOB7ltlB/ux38iu2rBQzkjbwRVlkhAGIEDiiYnO2kFOkJp+Z7pUXKyrRRFuFUKt+g=="], + + "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.60.1", "", { "os": "linux", "cpu": "arm" }, "sha512-L+34Qqil+v5uC0zEubW7uByo78WOCIrBvci69E7sFASRl0X7b/MB6Cqd1lky/CtcSVTydWa2WZwFuWexjS5o6g=="], + + "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.60.1", "", { "os": "linux", "cpu": "arm" }, "sha512-n83O8rt4v34hgFzlkb1ycniJh7IR5RCIqt6mz1VRJD6pmhRi0CXdmfnLu9dIUS6buzh60IvACM842Ffb3xd6Gg=="], + + "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.60.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-Nql7sTeAzhTAja3QXeAI48+/+GjBJ+QmAH13snn0AJSNL50JsDqotyudHyMbO2RbJkskbMbFJfIJKWA6R1LCJQ=="], + + "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.60.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-+pUymDhd0ys9GcKZPPWlFiZ67sTWV5UU6zOJat02M1+PiuSGDziyRuI/pPue3hoUwm2uGfxdL+trT6Z9rxnlMA=="], + + "@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.60.1", "", { "os": "linux", "cpu": "none" }, "sha512-VSvgvQeIcsEvY4bKDHEDWcpW4Yw7BtlKG1GUT4FzBUlEKQK0rWHYBqQt6Fm2taXS+1bXvJT6kICu5ZwqKCnvlQ=="], + + "@rollup/rollup-linux-loong64-musl": ["@rollup/rollup-linux-loong64-musl@4.60.1", "", { "os": "linux", "cpu": "none" }, "sha512-4LqhUomJqwe641gsPp6xLfhqWMbQV04KtPp7/dIp0nzPxAkNY1AbwL5W0MQpcalLYk07vaW9Kp1PBhdpZYYcEw=="], + + "@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.60.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-tLQQ9aPvkBxOc/EUT6j3pyeMD6Hb8QF2BTBnCQWP/uu1lhc9AIrIjKnLYMEroIz/JvtGYgI9dF3AxHZNaEH0rw=="], + + "@rollup/rollup-linux-ppc64-musl": ["@rollup/rollup-linux-ppc64-musl@4.60.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-RMxFhJwc9fSXP6PqmAz4cbv3kAyvD1etJFjTx4ONqFP9DkTkXsAMU4v3Vyc5BgzC+anz7nS/9tp4obsKfqkDHg=="], + + "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.60.1", "", { "os": "linux", "cpu": "none" }, "sha512-QKgFl+Yc1eEk6MmOBfRHYF6lTxiiiV3/z/BRrbSiW2I7AFTXoBFvdMEyglohPj//2mZS4hDOqeB0H1ACh3sBbg=="], + + "@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.60.1", "", { "os": "linux", "cpu": "none" }, "sha512-RAjXjP/8c6ZtzatZcA1RaQr6O1TRhzC+adn8YZDnChliZHviqIjmvFwHcxi4JKPSDAt6Uhf/7vqcBzQJy0PDJg=="], + + "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.60.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-wcuocpaOlaL1COBYiA89O6yfjlp3RwKDeTIA0hM7OpmhR1Bjo9j31G1uQVpDlTvwxGn2nQs65fBFL5UFd76FcQ=="], + + "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.60.1", "", { "os": "linux", "cpu": "x64" }, "sha512-77PpsFQUCOiZR9+LQEFg9GClyfkNXj1MP6wRnzYs0EeWbPcHs02AXu4xuUbM1zhwn3wqaizle3AEYg5aeoohhg=="], + + "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.60.1", "", { "os": "linux", "cpu": "x64" }, "sha512-5cIATbk5vynAjqqmyBjlciMJl1+R/CwX9oLk/EyiFXDWd95KpHdrOJT//rnUl4cUcskrd0jCCw3wpZnhIHdD9w=="], + + "@rollup/rollup-openbsd-x64": ["@rollup/rollup-openbsd-x64@4.60.1", "", { "os": "openbsd", "cpu": "x64" }, "sha512-cl0w09WsCi17mcmWqqglez9Gk8isgeWvoUZ3WiJFYSR3zjBQc2J5/ihSjpl+VLjPqjQ/1hJRcqBfLjssREQILw=="], + + "@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.60.1", "", { "os": "none", "cpu": "arm64" }, "sha512-4Cv23ZrONRbNtbZa37mLSueXUCtN7MXccChtKpUnQNgF010rjrjfHx3QxkS2PI7LqGT5xXyYs1a7LbzAwT0iCA=="], + + "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.60.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-i1okWYkA4FJICtr7KpYzFpRTHgy5jdDbZiWfvny21iIKky5YExiDXP+zbXzm3dUcFpkEeYNHgQ5fuG236JPq0g=="], + + "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.60.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-u09m3CuwLzShA0EYKMNiFgcjjzwqtUMLmuCJLeZWjjOYA3IT2Di09KaxGBTP9xVztWyIWjVdsB2E9goMjZvTQg=="], + + "@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.60.1", "", { "os": "win32", "cpu": "x64" }, "sha512-k+600V9Zl1CM7eZxJgMyTUzmrmhB/0XZnF4pRypKAlAgxmedUA+1v9R+XOFv56W4SlHEzfeMtzujLJD22Uz5zg=="], + + "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.60.1", "", { "os": "win32", "cpu": "x64" }, "sha512-lWMnixq/QzxyhTV6NjQJ4SFo1J6PvOX8vUx5Wb4bBPsEb+8xZ89Bz6kOXpfXj9ak9AHTQVQzlgzBEc1SyM27xQ=="], + + "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], + + "@types/node": ["@types/node@24.12.2", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g=="], + + "@types/yauzl": ["@types/yauzl@2.10.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q=="], + + "acorn": ["acorn@8.16.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="], + + "any-promise": ["any-promise@1.3.0", "", {}, "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A=="], + + "buffer-crc32": ["buffer-crc32@0.2.13", "", {}, "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ=="], + + "bundle-require": ["bundle-require@5.1.0", "", { "dependencies": { "load-tsconfig": "^0.2.3" }, "peerDependencies": { "esbuild": ">=0.18" } }, "sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA=="], + + "cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="], + + "chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="], + + "chownr": ["chownr@3.0.0", "", {}, "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g=="], + + "commander": ["commander@4.1.1", "", {}, "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA=="], + + "confbox": ["confbox@0.1.8", "", {}, "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w=="], + + "consola": ["consola@3.4.2", "", {}, "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA=="], + + "create-metorial": ["create-metorial@workspace:npm/create"], + + "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + + "end-of-stream": ["end-of-stream@1.4.5", "", { "dependencies": { "once": "^1.4.0" } }, "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg=="], + + "esbuild": ["esbuild@0.27.7", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.7", "@esbuild/android-arm": "0.27.7", "@esbuild/android-arm64": "0.27.7", "@esbuild/android-x64": "0.27.7", "@esbuild/darwin-arm64": "0.27.7", "@esbuild/darwin-x64": "0.27.7", "@esbuild/freebsd-arm64": "0.27.7", "@esbuild/freebsd-x64": "0.27.7", "@esbuild/linux-arm": "0.27.7", "@esbuild/linux-arm64": "0.27.7", "@esbuild/linux-ia32": "0.27.7", "@esbuild/linux-loong64": "0.27.7", "@esbuild/linux-mips64el": "0.27.7", "@esbuild/linux-ppc64": "0.27.7", "@esbuild/linux-riscv64": "0.27.7", "@esbuild/linux-s390x": "0.27.7", "@esbuild/linux-x64": "0.27.7", "@esbuild/netbsd-arm64": "0.27.7", "@esbuild/netbsd-x64": "0.27.7", "@esbuild/openbsd-arm64": "0.27.7", "@esbuild/openbsd-x64": "0.27.7", "@esbuild/openharmony-arm64": "0.27.7", "@esbuild/sunos-x64": "0.27.7", "@esbuild/win32-arm64": "0.27.7", "@esbuild/win32-ia32": "0.27.7", "@esbuild/win32-x64": "0.27.7" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w=="], + + "extract-zip": ["extract-zip@2.0.1", "", { "dependencies": { "debug": "^4.1.1", "get-stream": "^5.1.0", "yauzl": "^2.10.0" }, "optionalDependencies": { "@types/yauzl": "^2.9.1" }, "bin": { "extract-zip": "cli.js" } }, "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg=="], + + "fd-slicer": ["fd-slicer@1.1.0", "", { "dependencies": { "pend": "~1.2.0" } }, "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g=="], + + "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], + + "fix-dts-default-cjs-exports": ["fix-dts-default-cjs-exports@1.0.1", "", { "dependencies": { "magic-string": "^0.30.17", "mlly": "^1.7.4", "rollup": "^4.34.8" } }, "sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg=="], + + "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + + "get-stream": ["get-stream@5.2.0", "", { "dependencies": { "pump": "^3.0.0" } }, "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA=="], + + "joycon": ["joycon@3.1.1", "", {}, "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw=="], + + "lilconfig": ["lilconfig@3.1.3", "", {}, "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw=="], + + "lines-and-columns": ["lines-and-columns@1.2.4", "", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="], + + "load-tsconfig": ["load-tsconfig@0.2.5", "", {}, "sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg=="], + + "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], + + "minipass": ["minipass@7.1.3", "", {}, "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A=="], + + "minizlib": ["minizlib@3.1.0", "", { "dependencies": { "minipass": "^7.1.2" } }, "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw=="], + + "mlly": ["mlly@1.8.2", "", { "dependencies": { "acorn": "^8.16.0", "pathe": "^2.0.3", "pkg-types": "^1.3.1", "ufo": "^1.6.3" } }, "sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA=="], + + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + + "mz": ["mz@2.7.0", "", { "dependencies": { "any-promise": "^1.0.0", "object-assign": "^4.0.1", "thenify-all": "^1.0.0" } }, "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q=="], + + "object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="], + + "once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="], + + "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], + + "pend": ["pend@1.2.0", "", {}, "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg=="], + + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], + + "picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="], + + "pirates": ["pirates@4.0.7", "", {}, "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA=="], + + "pkg-types": ["pkg-types@1.3.1", "", { "dependencies": { "confbox": "^0.1.8", "mlly": "^1.7.4", "pathe": "^2.0.1" } }, "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ=="], + + "postcss-load-config": ["postcss-load-config@6.0.1", "", { "dependencies": { "lilconfig": "^3.1.1" }, "peerDependencies": { "jiti": ">=1.21.0", "postcss": ">=8.0.9", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["jiti", "postcss", "tsx", "yaml"] }, "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g=="], + + "pump": ["pump@3.0.4", "", { "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA=="], + + "readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="], + + "resolve-from": ["resolve-from@5.0.0", "", {}, "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw=="], + + "rollup": ["rollup@4.60.1", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.60.1", "@rollup/rollup-android-arm64": "4.60.1", "@rollup/rollup-darwin-arm64": "4.60.1", "@rollup/rollup-darwin-x64": "4.60.1", "@rollup/rollup-freebsd-arm64": "4.60.1", "@rollup/rollup-freebsd-x64": "4.60.1", "@rollup/rollup-linux-arm-gnueabihf": "4.60.1", "@rollup/rollup-linux-arm-musleabihf": "4.60.1", "@rollup/rollup-linux-arm64-gnu": "4.60.1", "@rollup/rollup-linux-arm64-musl": "4.60.1", "@rollup/rollup-linux-loong64-gnu": "4.60.1", "@rollup/rollup-linux-loong64-musl": "4.60.1", "@rollup/rollup-linux-ppc64-gnu": "4.60.1", "@rollup/rollup-linux-ppc64-musl": "4.60.1", "@rollup/rollup-linux-riscv64-gnu": "4.60.1", "@rollup/rollup-linux-riscv64-musl": "4.60.1", "@rollup/rollup-linux-s390x-gnu": "4.60.1", "@rollup/rollup-linux-x64-gnu": "4.60.1", "@rollup/rollup-linux-x64-musl": "4.60.1", "@rollup/rollup-openbsd-x64": "4.60.1", "@rollup/rollup-openharmony-arm64": "4.60.1", "@rollup/rollup-win32-arm64-msvc": "4.60.1", "@rollup/rollup-win32-ia32-msvc": "4.60.1", "@rollup/rollup-win32-x64-gnu": "4.60.1", "@rollup/rollup-win32-x64-msvc": "4.60.1", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-VmtB2rFU/GroZ4oL8+ZqXgSA38O6GR8KSIvWmEFv63pQ0G6KaBH9s07PO8XTXP4vI+3UJUEypOfjkGfmSBBR0w=="], + + "source-map": ["source-map@0.7.6", "", {}, "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ=="], + + "sucrase": ["sucrase@3.35.1", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.2", "commander": "^4.0.0", "lines-and-columns": "^1.1.6", "mz": "^2.7.0", "pirates": "^4.0.1", "tinyglobby": "^0.2.11", "ts-interface-checker": "^0.1.9" }, "bin": { "sucrase": "bin/sucrase", "sucrase-node": "bin/sucrase-node" } }, "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw=="], + + "tar": ["tar@7.5.13", "", { "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", "minipass": "^7.1.2", "minizlib": "^3.1.0", "yallist": "^5.0.0" } }, "sha512-tOG/7GyXpFevhXVh8jOPJrmtRpOTsYqUIkVdVooZYJS/z8WhfQUX8RJILmeuJNinGAMSu1veBr4asSHFt5/hng=="], + + "thenify": ["thenify@3.3.1", "", { "dependencies": { "any-promise": "^1.0.0" } }, "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw=="], + + "thenify-all": ["thenify-all@1.6.0", "", { "dependencies": { "thenify": ">= 3.1.0 < 4" } }, "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA=="], + + "tinyexec": ["tinyexec@0.3.2", "", {}, "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA=="], + + "tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="], + + "tree-kill": ["tree-kill@1.2.2", "", { "bin": { "tree-kill": "cli.js" } }, "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A=="], + + "ts-interface-checker": ["ts-interface-checker@0.1.13", "", {}, "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA=="], + + "tsup": ["tsup@8.5.1", "", { "dependencies": { "bundle-require": "^5.1.0", "cac": "^6.7.14", "chokidar": "^4.0.3", "consola": "^3.4.0", "debug": "^4.4.0", "esbuild": "^0.27.0", "fix-dts-default-cjs-exports": "^1.0.0", "joycon": "^3.1.1", "picocolors": "^1.1.1", "postcss-load-config": "^6.0.1", "resolve-from": "^5.0.0", "rollup": "^4.34.8", "source-map": "^0.7.6", "sucrase": "^3.35.0", "tinyexec": "^0.3.2", "tinyglobby": "^0.2.11", "tree-kill": "^1.2.2" }, "peerDependencies": { "@microsoft/api-extractor": "^7.36.0", "@swc/core": "^1", "postcss": "^8.4.12", "typescript": ">=4.5.0" }, "optionalPeers": ["@microsoft/api-extractor", "@swc/core", "postcss", "typescript"], "bin": { "tsup": "dist/cli-default.js", "tsup-node": "dist/cli-node.js" } }, "sha512-xtgkqwdhpKWr3tKPmCkvYmS9xnQK3m3XgxZHwSUjvfTjp7YfXe5tT3GgWi0F2N+ZSMsOeWeZFh7ZZFg5iPhing=="], + + "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], + + "ufo": ["ufo@1.6.3", "", {}, "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q=="], + + "undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], + + "wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="], + + "yallist": ["yallist@5.0.0", "", {}, "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw=="], + + "yauzl": ["yauzl@2.10.0", "", { "dependencies": { "buffer-crc32": "~0.2.3", "fd-slicer": "~1.1.0" } }, "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g=="], + } +} diff --git a/internal/commands/resources/command_test.go b/internal/commands/resources/command_test.go index b022fcb..92bee1d 100644 --- a/internal/commands/resources/command_test.go +++ b/internal/commands/resources/command_test.go @@ -54,8 +54,8 @@ func TestRootHelpSeparatesResourceCommands(t *testing.T) { if !strings.Contains(output, "Commands:\n") { t.Fatalf("help output missing Commands section:\n%s", output) } - if !strings.Contains(output, "Resource Commands:\n") { - t.Fatalf("help output missing Resource Commands section:\n%s", output) + if !strings.Contains(output, "Resource Admin Commands:\n") { + t.Fatalf("help output missing Resource Admin Commands section:\n%s", output) } if strings.Contains(output, "Commands:\n providers") { t.Fatalf("providers unexpectedly listed in general Commands section:\n%s", output) diff --git a/internal/commandutil/help.go b/internal/commandutil/help.go index 571f084..20c59a0 100644 --- a/internal/commandutil/help.go +++ b/internal/commandutil/help.go @@ -86,7 +86,7 @@ func HelpTemplate() string { {{helpHeading "Integrations:"}} {{renderIntegrationSection .Commands}}{{end}}{{if hasCommandCategory .Commands "resource"}} -{{helpHeading "Resource Commands:"}} +{{helpHeading "Resource Admin Commands:"}} {{renderCommandSection .Commands "resource"}}{{end}}{{end}}{{if commandAnnotation . "metorial:arguments"}} {{helpHeading "Arguments:"}} @@ -116,7 +116,7 @@ func UsageTemplate() string { {{helpHeading "Integrations:"}} {{renderIntegrationSection .Commands}}{{end}}{{if hasCommandCategory .Commands "resource"}} -{{helpHeading "Resource Commands:"}} +{{helpHeading "Resource Admin Commands:"}} {{renderCommandSection .Commands "resource"}}{{end}}{{end}}{{if .HasAvailableLocalFlags}} {{helpHeading "Flags:"}} diff --git a/npm/cli-core/package.json b/npm/cli-core/package.json new file mode 100644 index 0000000..406abad --- /dev/null +++ b/npm/cli-core/package.json @@ -0,0 +1,21 @@ +{ + "name": "@metorial/cli-core", + "version": "0.0.0-dev", + "description": "Runtime helpers for downloading and invoking the Metorial CLI", + "license": "MIT", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "files": [ + "dist" + ], + "publishConfig": { + "access": "public" + }, + "scripts": { + "build": "tsup --config tsup.config.ts" + }, + "dependencies": { + "extract-zip": "^2.0.1", + "tar": "^7.4.3" + } +} diff --git a/npm/cli-core/src/index.ts b/npm/cli-core/src/index.ts new file mode 100644 index 0000000..cc28834 --- /dev/null +++ b/npm/cli-core/src/index.ts @@ -0,0 +1,300 @@ +import extractZip from 'extract-zip'; +import { spawn } from 'node:child_process'; +import { createHash } from 'node:crypto'; +import { + access, + chmod, + copyFile, + mkdir, + readFile, + readdir, + rm, + writeFile +} from 'node:fs/promises'; +import os from 'node:os'; +import path from 'node:path'; +import * as tar from 'tar'; +import packageJson from '../package.json'; + +export type OutputTransform = (value: string) => string; + +export type RunCLIOptions = { + cwd?: string; + env?: NodeJS.ProcessEnv; + version?: string; + stdoutTransform?: OutputTransform; + stderrTransform?: OutputTransform; +}; + +type PlatformSpec = { + archiveName: string; + binaryName: string; +}; + +let DEFAULT_BASE_URL = 'https://cli.metorial.com'; + +export async function resolveCLIPath(version?: string) { + let tag = normalizeVersion( + version || process.env.METORIAL_CLI_VERSION || packageJson.version + ); + let spec = getPlatformSpec(tag); + let installDir = path.join(getCacheRoot(), 'versions', tag); + let binaryPath = path.join(installDir, spec.binaryName); + + if (await exists(binaryPath)) { + return binaryPath; + } + + await downloadAndInstall({ tag, spec, installDir, binaryPath }); + return binaryPath; +} + +export async function runCLI(args: string[], options: RunCLIOptions = {}) { + let binaryPath = await resolveCLIPath(options.version); + + if (!options.stdoutTransform && !options.stderrTransform) { + return await spawnWithInheritedOutput(binaryPath, args, options); + } + + return await spawnWithBufferedOutput(binaryPath, args, options); +} + +export async function runCLIAndExit(args: string[], options: RunCLIOptions = {}) { + let exitCode = await runCLI(args, options); + process.exit(exitCode); +} + +export function replaceOutput(searchValue: string, replaceValue: string): OutputTransform { + return value => value.replaceAll(searchValue, replaceValue); +} + +async function downloadAndInstall(input: { + tag: string; + spec: PlatformSpec; + installDir: string; + binaryPath: string; +}) { + let tempDir = await makeTempDir(path.join(getCacheRoot(), 'tmp')); + let archivePath = path.join(tempDir, input.spec.archiveName); + let checksumsPath = path.join(tempDir, 'checksums.txt'); + let extractDir = path.join(tempDir, 'extract'); + let baseUrl = `${(process.env.METORIAL_CLI_BASE_URL || DEFAULT_BASE_URL).replace(/\/$/, '')}/metorial-cli/${input.tag}`; + + try { + await mkdir(extractDir, { recursive: true }); + + await downloadFile(`${baseUrl}/${input.spec.archiveName}`, archivePath); + await downloadFile(`${baseUrl}/checksums.txt`, checksumsPath); + await verifyChecksum(checksumsPath, input.spec.archiveName, archivePath); + await extractArchive(archivePath, extractDir); + + let extractedBinary = await findBinary(extractDir, input.spec.binaryName); + await mkdir(input.installDir, { recursive: true }); + await copyFile(extractedBinary, input.binaryPath); + + if (process.platform !== 'win32') { + await chmod(input.binaryPath, 0o755); + } + } finally { + await rm(tempDir, { recursive: true, force: true }); + } +} + +async function spawnWithInheritedOutput( + binaryPath: string, + args: string[], + options: RunCLIOptions +) { + return await new Promise((resolve, reject) => { + let child = spawn(binaryPath, args, { + cwd: options.cwd, + env: { ...process.env, ...options.env }, + stdio: 'inherit' + }); + + child.on('error', reject); + child.on('close', code => resolve(code ?? 1)); + }); +} + +async function spawnWithBufferedOutput( + binaryPath: string, + args: string[], + options: RunCLIOptions +) { + return await new Promise((resolve, reject) => { + let stdout = ''; + let stderr = ''; + let child = spawn(binaryPath, args, { + cwd: options.cwd, + env: { ...process.env, ...options.env }, + stdio: ['inherit', 'pipe', 'pipe'] + }); + + child.stdout?.setEncoding('utf8'); + child.stderr?.setEncoding('utf8'); + + child.stdout?.on('data', chunk => { + stdout += String(chunk); + }); + child.stderr?.on('data', chunk => { + stderr += String(chunk); + }); + + child.on('error', reject); + child.on('close', code => { + process.stdout.write(options.stdoutTransform ? options.stdoutTransform(stdout) : stdout); + process.stderr.write(options.stderrTransform ? options.stderrTransform(stderr) : stderr); + resolve(code ?? 1); + }); + }); +} + +async function downloadFile(url: string, destinationPath: string) { + let response = await fetch(url, { + headers: { + 'User-Agent': 'metorial-cli-npm', + Accept: '*/*' + } + }); + + if (!response.ok || !response.body) { + throw new Error(`Failed to download ${url}: ${response.status} ${response.statusText}`); + } + + await writeFile(destinationPath, Buffer.from(await response.arrayBuffer())); +} + +async function verifyChecksum( + checksumsPath: string, + archiveName: string, + archivePath: string +) { + let checksums = await readFile(checksumsPath, 'utf8'); + let expected = checksums + .split('\n') + .map(line => line.trim().split(/\s+/)) + .find(parts => parts.length >= 2 && parts[1] === archiveName)?.[0]; + + if (!expected) { + throw new Error(`Checksum for ${archiveName} not found`); + } + + let archiveBuffer = await readFile(archivePath); + let actual = createHash('sha256').update(archiveBuffer).digest('hex'); + + if (actual !== expected) { + throw new Error(`Checksum verification failed for ${archiveName}`); + } +} + +async function extractArchive(archivePath: string, extractDir: string) { + if (archivePath.endsWith('.zip')) { + await extractZip(archivePath, { dir: extractDir }); + return; + } + + await tar.x({ + file: archivePath, + cwd: extractDir + }); +} + +async function findBinary(rootDir: string, binaryName: string): Promise { + let directPath = path.join(rootDir, binaryName); + if (await exists(directPath)) { + return directPath; + } + + return await walkForBinary(rootDir, binaryName); +} + +function getCacheRoot() { + if (process.env.METORIAL_CLI_CACHE_DIR) { + return process.env.METORIAL_CLI_CACHE_DIR; + } + + return path.join(os.homedir(), '.metorial', 'cli-npm'); +} + +function getPlatformSpec(tag: string): PlatformSpec { + let goos = resolveGoOS(); + let goarch = resolveGoArch(); + let extension = goos === 'windows' ? 'zip' : 'tar.gz'; + let binaryName = goos === 'windows' ? 'metorial.exe' : 'metorial'; + + return { + archiveName: `metorial_${tag.replace(/^v/, '')}_${goos}_${goarch}.${extension}`, + binaryName + }; +} + +function resolveGoOS() { + switch (process.platform) { + case 'darwin': + return 'darwin'; + case 'linux': + return 'linux'; + case 'win32': + return 'windows'; + default: + throw new Error(`Unsupported platform: ${process.platform}`); + } +} + +function resolveGoArch() { + switch (process.arch) { + case 'x64': + return 'amd64'; + case 'arm64': + return 'arm64'; + default: + throw new Error(`Unsupported architecture: ${process.arch}`); + } +} + +function normalizeVersion(value: string) { + if (!value.trim()) { + throw new Error('Unable to resolve a CLI version'); + } + + return value.startsWith('v') ? value : `v${value}`; +} + +async function makeTempDir(parentDir: string) { + let suffix = `${process.pid}-${Date.now()}-${Math.random().toString(16).slice(2)}`; + let tempDir = path.join(parentDir, suffix); + await mkdir(tempDir, { recursive: true }); + return tempDir; +} + +async function exists(filePath: string) { + try { + await access(filePath); + return true; + } catch { + return false; + } +} + +async function walkForBinary(rootDir: string, binaryName: string): Promise { + let entries = await readdir(rootDir, { withFileTypes: true }); + + for (let entry of entries) { + let entryPath = path.join(rootDir, entry.name); + + if (entry.isFile() && entry.name === binaryName) { + return entryPath; + } + + if (entry.isDirectory()) { + try { + return await walkForBinary(entryPath, binaryName); + } catch { + continue; + } + } + } + + throw new Error(`Downloaded archive did not contain ${binaryName}`); +} diff --git a/npm/cli-core/tsconfig.json b/npm/cli-core/tsconfig.json new file mode 100644 index 0000000..89a0d93 --- /dev/null +++ b/npm/cli-core/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "../../tsconfig.base.json", + "include": [ + "src/**/*.ts" + ] +} diff --git a/npm/cli-core/tsup.config.ts b/npm/cli-core/tsup.config.ts new file mode 100644 index 0000000..5075081 --- /dev/null +++ b/npm/cli-core/tsup.config.ts @@ -0,0 +1,11 @@ +import { defineConfig } from 'tsup'; + +export default defineConfig({ + entry: ['src/index.ts'], + format: ['cjs'], + dts: true, + bundle: true, + clean: true, + target: 'node20', + outDir: 'dist' +}); diff --git a/npm/cli/package.json b/npm/cli/package.json new file mode 100644 index 0000000..6b34dc2 --- /dev/null +++ b/npm/cli/package.json @@ -0,0 +1,23 @@ +{ + "name": "@metorial/cli", + "version": "0.0.0-dev", + "description": "npm wrapper for the Metorial CLI", + "license": "MIT", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "bin": { + "metorial": "./dist/bin.cjs" + }, + "files": [ + "dist" + ], + "publishConfig": { + "access": "public" + }, + "scripts": { + "build": "tsup --config tsup.config.ts" + }, + "dependencies": { + "@metorial/cli-core": "workspace:*" + } +} diff --git a/npm/cli/src/bin.ts b/npm/cli/src/bin.ts new file mode 100644 index 0000000..b5fb871 --- /dev/null +++ b/npm/cli/src/bin.ts @@ -0,0 +1,3 @@ +import { runCLIAndExit } from '@metorial/cli-core'; + +void runCLIAndExit(process.argv.slice(2)); diff --git a/npm/cli/src/index.ts b/npm/cli/src/index.ts new file mode 100644 index 0000000..ed89254 --- /dev/null +++ b/npm/cli/src/index.ts @@ -0,0 +1 @@ +export { replaceOutput, resolveCLIPath, runCLI, runCLIAndExit } from '@metorial/cli-core'; diff --git a/npm/cli/tsconfig.json b/npm/cli/tsconfig.json new file mode 100644 index 0000000..89a0d93 --- /dev/null +++ b/npm/cli/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "../../tsconfig.base.json", + "include": [ + "src/**/*.ts" + ] +} diff --git a/npm/cli/tsup.config.ts b/npm/cli/tsup.config.ts new file mode 100644 index 0000000..cd980b5 --- /dev/null +++ b/npm/cli/tsup.config.ts @@ -0,0 +1,29 @@ +import { defineConfig } from 'tsup'; + +export default defineConfig([ + { + entry: ['src/index.ts'], + format: ['cjs'], + dts: true, + bundle: true, + clean: true, + target: 'node20', + outDir: 'dist' + }, + { + entry: ['src/bin.ts'], + format: ['cjs'], + bundle: true, + clean: false, + target: 'node20', + outDir: 'dist', + banner: { + js: '#!/usr/bin/env node' + }, + outExtension() { + return { + js: '.cjs' + }; + } + } +]); diff --git a/npm/create/package.json b/npm/create/package.json new file mode 100644 index 0000000..676ec80 --- /dev/null +++ b/npm/create/package.json @@ -0,0 +1,21 @@ +{ + "name": "create-metorial", + "version": "0.0.0-dev", + "description": "npm create entrypoint for Metorial examples", + "license": "MIT", + "bin": { + "create-metorial": "./dist/bin.cjs" + }, + "files": [ + "dist" + ], + "publishConfig": { + "access": "public" + }, + "scripts": { + "build": "tsup --config tsup.config.ts" + }, + "dependencies": { + "@metorial/cli-core": "workspace:*" + } +} diff --git a/npm/create/src/bin.ts b/npm/create/src/bin.ts new file mode 100644 index 0000000..0ef8442 --- /dev/null +++ b/npm/create/src/bin.ts @@ -0,0 +1,10 @@ +import { replaceOutput, runCLIAndExit } from '@metorial/cli-core'; + +let args = process.argv.slice(2); +let cliArgs = args.length === 0 ? ['example', 'list'] : ['example', 'create', ...args]; +let transform = replaceOutput('metorial example create', 'npm create'); + +void runCLIAndExit(cliArgs, { + stdoutTransform: transform, + stderrTransform: transform +}); diff --git a/npm/create/tsconfig.json b/npm/create/tsconfig.json new file mode 100644 index 0000000..89a0d93 --- /dev/null +++ b/npm/create/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "../../tsconfig.base.json", + "include": [ + "src/**/*.ts" + ] +} diff --git a/npm/create/tsup.config.ts b/npm/create/tsup.config.ts new file mode 100644 index 0000000..1edca03 --- /dev/null +++ b/npm/create/tsup.config.ts @@ -0,0 +1,18 @@ +import { defineConfig } from 'tsup'; + +export default defineConfig({ + entry: ['src/bin.ts'], + format: ['cjs'], + bundle: true, + clean: true, + target: 'node20', + outDir: 'dist', + banner: { + js: '#!/usr/bin/env node' + }, + outExtension() { + return { + js: '.cjs' + }; + } +}); diff --git a/package.json b/package.json new file mode 100644 index 0000000..720e3cd --- /dev/null +++ b/package.json @@ -0,0 +1,18 @@ +{ + "name": "@metorial/cli-workspace", + "private": true, + "packageManager": "bun@1.2.20", + "workspaces": [ + "npm/*" + ], + "scripts": { + "build:npm": "bun run --cwd npm/cli-core build && bun run --cwd npm/cli build && bun run --cwd npm/create build", + "set-npm-version": "bun ./scripts/set-npm-version.ts", + "publish:npm": "bun ./scripts/publish-npm.ts" + }, + "devDependencies": { + "@types/node": "^24.5.2", + "tsup": "^8.5.0", + "typescript": "^5.9.2" + } +} diff --git a/scripts/build-public.ts b/scripts/build-public.ts index 8bc7935..6c7171b 100644 --- a/scripts/build-public.ts +++ b/scripts/build-public.ts @@ -97,9 +97,15 @@ for (let release of releases) { await mkdir(path.join(publicDir, 'metorial-cli'), { recursive: true }); await writeFile(path.join(publicDir, 'metorial-cli', 'latest'), `${latestRelease.tag_name}\n`); -await writeFile(path.join(publicDir, 'metorial-cli', 'releases.json'), JSON.stringify(mirroredReleases, null, 2) + '\n'); +await writeFile( + path.join(publicDir, 'metorial-cli', 'releases.json'), + JSON.stringify(mirroredReleases, null, 2) + '\n' +); await copyFile(installTemplatePath, path.join(publicDir, 'install.sh')); -await writeFile(path.join(publicDir, 'index.html'), renderIndex(latestRelease, mirroredReleases)); +await writeFile( + path.join(publicDir, 'index.html'), + renderIndex(latestRelease, mirroredReleases) +); async function getAllReleases(): Promise { let releases: GitHubRelease[] = []; @@ -167,123 +173,14 @@ function buildHeaders() { } function renderIndex(latestRelease: GitHubRelease, releases: MirroredRelease[]) { - let releaseList = releases - .map(release => { - let assetList = release.assets - .map(asset => `
  • ${escapeHtml(asset.name)}
  • `) - .join(''); - - return ` -
    -

    ${escapeHtml(release.tag)}

    -

    Published ${escapeHtml(formatDate(release.published_at))}

    -

    View GitHub release notes

    -
      ${assetList}
    -
    - `; - }) - .join('\n'); - return ` Metorial CLI - + - -
    -

    Metorial CLI

    -

    Hosted release artifacts and installer for the official Metorial CLI.

    -
    -

    Latest release: ${escapeHtml(latestRelease.tag_name)}

    -
    curl -fsSL https://cli.metorial.com/install.sh | bash
    -
    - ${releaseList} -
    - `; } - -function formatDate(value: string | null) { - if (!value) { - return 'unknown date'; - } - - return new Intl.DateTimeFormat('en-US', { - year: 'numeric', - month: 'long', - day: 'numeric' - }).format(new Date(value)); -} - -function escapeHtml(value: string) { - return String(value) - .replaceAll('&', '&') - .replaceAll('<', '<') - .replaceAll('>', '>') - .replaceAll('"', '"') - .replaceAll("'", '''); -} diff --git a/scripts/publish-npm.ts b/scripts/publish-npm.ts new file mode 100644 index 0000000..0c8ce6e --- /dev/null +++ b/scripts/publish-npm.ts @@ -0,0 +1,23 @@ +#!/usr/bin/env bun + +import { execFileSync } from 'node:child_process'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +let currentFile = fileURLToPath(import.meta.url); +let scriptsDir = path.dirname(currentFile); +let rootDir = path.resolve(scriptsDir, '..'); + +let publishOrder = ['npm/cli-core', 'npm/cli', 'npm/create']; + +for (let packagePath of publishOrder) { + execFileSync( + 'npm', + ['publish', '--access', 'public'], + { + cwd: path.join(rootDir, packagePath), + stdio: 'inherit', + env: process.env + } + ); +} diff --git a/scripts/set-npm-version.ts b/scripts/set-npm-version.ts new file mode 100644 index 0000000..23d8da7 --- /dev/null +++ b/scripts/set-npm-version.ts @@ -0,0 +1,51 @@ +#!/usr/bin/env bun + +import { readdir, readFile, writeFile } from 'node:fs/promises'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +let currentFile = fileURLToPath(import.meta.url); +let scriptsDir = path.dirname(currentFile); +let rootDir = path.resolve(scriptsDir, '..'); +let packagesDir = path.join(rootDir, 'npm'); +let requestedVersion = (process.argv[2] || process.env.RELEASE_VERSION || process.env.VERSION || '').replace(/^v/, ''); + +if (!requestedVersion) { + throw new Error('Provide a version argument or set RELEASE_VERSION/VERSION'); +} + +let packageDirs = (await readdir(packagesDir, { withFileTypes: true })) + .filter(entry => entry.isDirectory()) + .map(entry => path.join(packagesDir, entry.name)); + +let packageNames = new Map(); + +for (let packageDir of packageDirs) { + let packageJsonPath = path.join(packageDir, 'package.json'); + let packageJson = JSON.parse(await readFile(packageJsonPath, 'utf8')); + packageNames.set(packageJson.name, packageJsonPath); +} + +for (let packageDir of packageDirs) { + let packageJsonPath = path.join(packageDir, 'package.json'); + let packageJson = JSON.parse(await readFile(packageJsonPath, 'utf8')); + + packageJson.version = requestedVersion; + + for (let field of ['dependencies', 'devDependencies', 'peerDependencies', 'optionalDependencies'] as const) { + let values = packageJson[field]; + if (!values) { + continue; + } + + for (let dependencyName of Object.keys(values)) { + if (!packageNames.has(dependencyName)) { + continue; + } + + values[dependencyName] = requestedVersion; + } + } + + await writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n'); +} diff --git a/templates/install.sh b/templates/install.sh index d311c6e..6bf2662 100755 --- a/templates/install.sh +++ b/templates/install.sh @@ -195,7 +195,7 @@ ensure_path_in_shell_rc() { main() { need curl - printf 'Welcome to the Metorial CLI!\n' + printf 'Welcome to the \033[1;34mMetorial CLI\033[0m!\n' os='' os="$(detect_os)" @@ -227,11 +227,9 @@ main() { install "$extract_dir/metorial" "$install_dir/metorial" stop_spinner - printf '\rSuccessfully installed Metorial CLI (%s)\n' "$version" + printf '\rSuccessfully installed \033[1;34mMetorial CLI\033[0m (%s)\n' "$version" printf "Get started by running 'metorial'\n" ensure_path_in_shell_rc "$install_dir" - - "${install_dir}/metorial" version } main "$@" diff --git a/tsconfig.base.json b/tsconfig.base.json new file mode 100644 index 0000000..0431fe1 --- /dev/null +++ b/tsconfig.base.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "Bundler", + "types": [ + "node" + ], + "strict": true, + "declaration": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "skipLibCheck": true, + "resolveJsonModule": true + } +}