diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 131707b..818ba7b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -37,7 +37,7 @@ jobs: run: npm run typecheck - name: Run tests - run: npm test + run: npm run test:ci build: name: Build check diff --git a/.github/workflows/claude-code-review.yml b/.github/workflows/claude-code-review.yml index 205b0fe..81170e8 100644 --- a/.github/workflows/claude-code-review.yml +++ b/.github/workflows/claude-code-review.yml @@ -54,4 +54,3 @@ jobs: # See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md # or https://docs.claude.com/en/docs/claude-code/cli-reference for available options claude_args: '--allowed-tools "Bash(gh issue view:*),Bash(gh search:*),Bash(gh issue list:*),Bash(gh pr comment:*),Bash(gh pr diff:*),Bash(gh pr view:*),Bash(gh pr list:*)"' - diff --git a/.github/workflows/claude.yml b/.github/workflows/claude.yml index 412cef9..d2d6008 100644 --- a/.github/workflows/claude.yml +++ b/.github/workflows/claude.yml @@ -47,4 +47,3 @@ jobs: # See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md # or https://docs.claude.com/en/docs/claude-code/cli-reference for available options # claude_args: '--allowed-tools Bash(gh pr:*)' - diff --git a/.prettierrc.json b/.prettierrc.json index 8763cc0..2e2cffd 100644 --- a/.prettierrc.json +++ b/.prettierrc.json @@ -6,9 +6,7 @@ "semi": false, "tabWidth": 2, "trailingComma": "none", - "plugins": [ - "@ianvs/prettier-plugin-sort-imports" - ], + "plugins": ["@ianvs/prettier-plugin-sort-imports"], "importOrder": [ "", "^react", diff --git a/CLAUDE.md b/CLAUDE.md index b3fb166..3ffe2cf 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -57,4 +57,3 @@ Check out @README - ESLint is configured to ignore unused variables/parameters prefixed with underscore - No build step is currently configured - this is a TypeScript source project - Tests directory exists but no test framework is currently set up - diff --git a/README.md b/README.md index 3035d7b..23478f4 100644 --- a/README.md +++ b/README.md @@ -71,6 +71,17 @@ Uninstall package: await ipm.uninstall('package-name') ``` +### `publish(opts: { dryrun?: boolean; path?: string })` + +Publish a package to the registry. It will use `cwd` to locate the package to publish. + +- `dryrun`: If true, simulates the publish process without actually publishing. Default is `false`. +- `path`: Path to the package directory. If not provided, it uses the current working directory. + +```ts +await ipm.publish({ dryrun: true, path: './my-package' }) +``` + ### `registry.getPackageInfo(name: string): Promise` Get a package from the registry: diff --git a/jest.config.js b/jest.config.js index 53ab422..a1a17bf 100644 --- a/jest.config.js +++ b/jest.config.js @@ -19,9 +19,7 @@ export default { } ] }, - transformIgnorePatterns: [ - 'node_modules/(?!@inkdropapp/logger)' - ], + transformIgnorePatterns: ['node_modules/(?!@inkdropapp/logger)'], testMatch: [ '**/tests/**/*.test.ts', // Exclude real installation test from normal runs @@ -30,4 +28,3 @@ export default { ], collectCoverageFrom: ['src/**/*.ts', '!src/**/*.d.ts'] } - diff --git a/package-lock.json b/package-lock.json index 9cdd36d..8a446cf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@inkdropapp/ipm", - "version": "0.0.3", + "version": "1.0.0-beta.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@inkdropapp/ipm", - "version": "0.0.3", + "version": "1.0.0-beta.4", "license": "UNLICENSED", "dependencies": { "@inkdropapp/logger": "^4.0.0", @@ -22,11 +22,12 @@ "@types/node": "^24.10.0", "@types/semver": "^7.7.1", "@types/tar": "^6.1.13", + "dotenv": "^17.2.3", "eslint": "^9.39.1", "jest": "^30.2.0", "prettier": "^3.6.2", "ts-jest": "^29.4.5", - "tsup": "^8.5.0", + "tsup": "^8.5.1", "typescript": "^5.9.3", "typescript-eslint": "^8.46.3" } @@ -596,9 +597,9 @@ } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.5.tgz", - "integrity": "sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==", + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.0.tgz", + "integrity": "sha512-KuZrd2hRjz01y5JK9mEBSD3Vj3mbCvemhT466rSuJYeE/hjuBrHfjjcjMdTm/sz7au+++sdbJZJmuBwQLuw68A==", "cpu": [ "ppc64" ], @@ -613,9 +614,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.5.tgz", - "integrity": "sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA==", + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.0.tgz", + "integrity": "sha512-j67aezrPNYWJEOHUNLPj9maeJte7uSMM6gMoxfPC9hOg8N02JuQi/T7ewumf4tNvJadFkvLZMlAq73b9uwdMyQ==", "cpu": [ "arm" ], @@ -630,9 +631,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.5.tgz", - "integrity": "sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg==", + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.0.tgz", + "integrity": "sha512-CC3vt4+1xZrs97/PKDkl0yN7w8edvU2vZvAFGD16n9F0Cvniy5qvzRXjfO1l94efczkkQE6g1x0i73Qf5uthOQ==", "cpu": [ "arm64" ], @@ -647,9 +648,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.5.tgz", - "integrity": "sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw==", + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.0.tgz", + "integrity": "sha512-wurMkF1nmQajBO1+0CJmcN17U4BP6GqNSROP8t0X/Jiw2ltYGLHpEksp9MpoBqkrFR3kv2/te6Sha26k3+yZ9Q==", "cpu": [ "x64" ], @@ -664,9 +665,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.5.tgz", - "integrity": "sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ==", + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.0.tgz", + "integrity": "sha512-uJOQKYCcHhg07DL7i8MzjvS2LaP7W7Pn/7uA0B5S1EnqAirJtbyw4yC5jQ5qcFjHK9l6o/MX9QisBg12kNkdHg==", "cpu": [ "arm64" ], @@ -681,9 +682,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.5.tgz", - "integrity": "sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ==", + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.0.tgz", + "integrity": "sha512-8mG6arH3yB/4ZXiEnXof5MK72dE6zM9cDvUcPtxhUZsDjESl9JipZYW60C3JGreKCEP+p8P/72r69m4AZGJd5g==", "cpu": [ "x64" ], @@ -698,9 +699,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.5.tgz", - "integrity": "sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw==", + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.0.tgz", + "integrity": "sha512-9FHtyO988CwNMMOE3YIeci+UV+x5Zy8fI2qHNpsEtSF83YPBmE8UWmfYAQg6Ux7Gsmd4FejZqnEUZCMGaNQHQw==", "cpu": [ "arm64" ], @@ -715,9 +716,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.5.tgz", - "integrity": "sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw==", + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.0.tgz", + "integrity": "sha512-zCMeMXI4HS/tXvJz8vWGexpZj2YVtRAihHLk1imZj4efx1BQzN76YFeKqlDr3bUWI26wHwLWPd3rwh6pe4EV7g==", "cpu": [ "x64" ], @@ -732,9 +733,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.5.tgz", - "integrity": "sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw==", + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.0.tgz", + "integrity": "sha512-t76XLQDpxgmq2cNXKTVEB7O7YMb42atj2Re2Haf45HkaUpjM2J0UuJZDuaGbPbamzZ7bawyGFUkodL+zcE+jvQ==", "cpu": [ "arm" ], @@ -749,9 +750,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.5.tgz", - "integrity": "sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg==", + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.0.tgz", + "integrity": "sha512-AS18v0V+vZiLJyi/4LphvBE+OIX682Pu7ZYNsdUHyUKSoRwdnOsMf6FDekwoAFKej14WAkOef3zAORJgAtXnlQ==", "cpu": [ "arm64" ], @@ -766,9 +767,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.5.tgz", - "integrity": "sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA==", + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.0.tgz", + "integrity": "sha512-Mz1jxqm/kfgKkc/KLHC5qIujMvnnarD9ra1cEcrs7qshTUSksPihGrWHVG5+osAIQ68577Zpww7SGapmzSt4Nw==", "cpu": [ "ia32" ], @@ -783,9 +784,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.5.tgz", - "integrity": "sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg==", + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.0.tgz", + "integrity": "sha512-QbEREjdJeIreIAbdG2hLU1yXm1uu+LTdzoq1KCo4G4pFOLlvIspBm36QrQOar9LFduavoWX2msNFAAAY9j4BDg==", "cpu": [ "loong64" ], @@ -800,9 +801,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.5.tgz", - "integrity": "sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg==", + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.0.tgz", + "integrity": "sha512-sJz3zRNe4tO2wxvDpH/HYJilb6+2YJxo/ZNbVdtFiKDufzWq4JmKAiHy9iGoLjAV7r/W32VgaHGkk35cUXlNOg==", "cpu": [ "mips64el" ], @@ -817,9 +818,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.5.tgz", - "integrity": "sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ==", + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.0.tgz", + "integrity": "sha512-z9N10FBD0DCS2dmSABDBb5TLAyF1/ydVb+N4pi88T45efQ/w4ohr/F/QYCkxDPnkhkp6AIpIcQKQ8F0ANoA2JA==", "cpu": [ "ppc64" ], @@ -834,9 +835,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.5.tgz", - "integrity": "sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA==", + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.0.tgz", + "integrity": "sha512-pQdyAIZ0BWIC5GyvVFn5awDiO14TkT/19FTmFcPdDec94KJ1uZcmFs21Fo8auMXzD4Tt+diXu1LW1gHus9fhFQ==", "cpu": [ "riscv64" ], @@ -851,9 +852,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.5.tgz", - "integrity": "sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ==", + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.0.tgz", + "integrity": "sha512-hPlRWR4eIDDEci953RI1BLZitgi5uqcsjKMxwYfmi4LcwyWo2IcRP+lThVnKjNtk90pLS8nKdroXYOqW+QQH+w==", "cpu": [ "s390x" ], @@ -868,9 +869,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.5.tgz", - "integrity": "sha512-uhj8N2obKTE6pSZ+aMUbqq+1nXxNjZIIjCjGLfsWvVpy7gKCOL6rsY1MhRh9zLtUtAI7vpgLMK6DxjO8Qm9lJw==", + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.0.tgz", + "integrity": "sha512-1hBWx4OUJE2cab++aVZ7pObD6s+DK4mPGpemtnAORBvb5l/g5xFGk0vc0PjSkrDs0XaXj9yyob3d14XqvnQ4gw==", "cpu": [ "x64" ], @@ -885,9 +886,9 @@ } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.5.tgz", - "integrity": "sha512-pwHtMP9viAy1oHPvgxtOv+OkduK5ugofNTVDilIzBLpoWAM16r7b/mxBvfpuQDpRQFMfuVr5aLcn4yveGvBZvw==", + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.0.tgz", + "integrity": "sha512-6m0sfQfxfQfy1qRuecMkJlf1cIzTOgyaeXaiVaaki8/v+WB+U4hc6ik15ZW6TAllRlg/WuQXxWj1jx6C+dfy3w==", "cpu": [ "arm64" ], @@ -902,9 +903,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.5.tgz", - "integrity": "sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ==", + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.0.tgz", + "integrity": "sha512-xbbOdfn06FtcJ9d0ShxxvSn2iUsGd/lgPIO2V3VZIPDbEaIj1/3nBBe1AwuEZKXVXkMmpr6LUAgMkLD/4D2PPA==", "cpu": [ "x64" ], @@ -919,9 +920,9 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.5.tgz", - "integrity": "sha512-7A208+uQKgTxHd0G0uqZO8UjK2R0DDb4fDmERtARjSHWxqMTye4Erz4zZafx7Di9Cv+lNHYuncAkiGFySoD+Mw==", + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.0.tgz", + "integrity": "sha512-fWgqR8uNbCQ/GGv0yhzttj6sU/9Z5/Sv/VGU3F5OuXK6J6SlriONKrQ7tNlwBrJZXRYk5jUhuWvF7GYzGguBZQ==", "cpu": [ "arm64" ], @@ -936,9 +937,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.5.tgz", - "integrity": "sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg==", + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.0.tgz", + "integrity": "sha512-aCwlRdSNMNxkGGqQajMUza6uXzR/U0dIl1QmLjPtRbLOx3Gy3otfFu/VjATy4yQzo9yFDGTxYDo1FfAD9oRD2A==", "cpu": [ "x64" ], @@ -952,10 +953,27 @@ "node": ">=18" } }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.0.tgz", + "integrity": "sha512-nyvsBccxNAsNYz2jVFYwEGuRRomqZ149A39SHWk4hV0jWxKM0hjBPm3AmdxcbHiFLbBSwG6SbpIcUbXjgyECfA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@esbuild/sunos-x64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.5.tgz", - "integrity": "sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA==", + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.0.tgz", + "integrity": "sha512-Q1KY1iJafM+UX6CFEL+F4HRTgygmEW568YMqDA5UV97AuZSm21b7SXIrRJDwXWPzr8MGr75fUZPV67FdtMHlHA==", "cpu": [ "x64" ], @@ -970,9 +988,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.5.tgz", - "integrity": "sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw==", + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.0.tgz", + "integrity": "sha512-W1eyGNi6d+8kOmZIwi/EDjrL9nxQIQ0MiGqe/AWc6+IaHloxHSGoeRgDRKHFISThLmsewZ5nHFvGFWdBYlgKPg==", "cpu": [ "arm64" ], @@ -987,9 +1005,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.5.tgz", - "integrity": "sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ==", + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.0.tgz", + "integrity": "sha512-30z1aKL9h22kQhilnYkORFYt+3wp7yZsHWus+wSKAJR8JtdfI76LJ4SBdMsCopTR3z/ORqVu5L1vtnHZWVj4cQ==", "cpu": [ "ia32" ], @@ -1004,9 +1022,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.5.tgz", - "integrity": "sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g==", + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.0.tgz", + "integrity": "sha512-aIitBcjQeyOhMTImhLZmtxfdOcuNRpwlPNmlFKPcHQYPhEssw75Cl1TSXJXpMkzaua9FUetx/4OQKq7eJul5Cg==", "cpu": [ "x64" ], @@ -3649,6 +3667,19 @@ "node": ">=8" } }, + "node_modules/dotenv": { + "version": "17.2.3", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz", + "integrity": "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -3753,9 +3784,9 @@ } }, "node_modules/esbuild": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.5.tgz", - "integrity": "sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ==", + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.0.tgz", + "integrity": "sha512-jd0f4NHbD6cALCyGElNpGAOtWxSq46l9X/sWB0Nzd5er4Kz2YTm+Vl0qKFT9KUJvD8+fiO8AvoHhFvEatfVixA==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -3766,31 +3797,32 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.5", - "@esbuild/android-arm": "0.25.5", - "@esbuild/android-arm64": "0.25.5", - "@esbuild/android-x64": "0.25.5", - "@esbuild/darwin-arm64": "0.25.5", - "@esbuild/darwin-x64": "0.25.5", - "@esbuild/freebsd-arm64": "0.25.5", - "@esbuild/freebsd-x64": "0.25.5", - "@esbuild/linux-arm": "0.25.5", - "@esbuild/linux-arm64": "0.25.5", - "@esbuild/linux-ia32": "0.25.5", - "@esbuild/linux-loong64": "0.25.5", - "@esbuild/linux-mips64el": "0.25.5", - "@esbuild/linux-ppc64": "0.25.5", - "@esbuild/linux-riscv64": "0.25.5", - "@esbuild/linux-s390x": "0.25.5", - "@esbuild/linux-x64": "0.25.5", - "@esbuild/netbsd-arm64": "0.25.5", - "@esbuild/netbsd-x64": "0.25.5", - "@esbuild/openbsd-arm64": "0.25.5", - "@esbuild/openbsd-x64": "0.25.5", - "@esbuild/sunos-x64": "0.25.5", - "@esbuild/win32-arm64": "0.25.5", - "@esbuild/win32-ia32": "0.25.5", - "@esbuild/win32-x64": "0.25.5" + "@esbuild/aix-ppc64": "0.27.0", + "@esbuild/android-arm": "0.27.0", + "@esbuild/android-arm64": "0.27.0", + "@esbuild/android-x64": "0.27.0", + "@esbuild/darwin-arm64": "0.27.0", + "@esbuild/darwin-x64": "0.27.0", + "@esbuild/freebsd-arm64": "0.27.0", + "@esbuild/freebsd-x64": "0.27.0", + "@esbuild/linux-arm": "0.27.0", + "@esbuild/linux-arm64": "0.27.0", + "@esbuild/linux-ia32": "0.27.0", + "@esbuild/linux-loong64": "0.27.0", + "@esbuild/linux-mips64el": "0.27.0", + "@esbuild/linux-ppc64": "0.27.0", + "@esbuild/linux-riscv64": "0.27.0", + "@esbuild/linux-s390x": "0.27.0", + "@esbuild/linux-x64": "0.27.0", + "@esbuild/netbsd-arm64": "0.27.0", + "@esbuild/netbsd-x64": "0.27.0", + "@esbuild/openbsd-arm64": "0.27.0", + "@esbuild/openbsd-x64": "0.27.0", + "@esbuild/openharmony-arm64": "0.27.0", + "@esbuild/sunos-x64": "0.27.0", + "@esbuild/win32-arm64": "0.27.0", + "@esbuild/win32-ia32": "0.27.0", + "@esbuild/win32-x64": "0.27.0" } }, "node_modules/escalade": { @@ -5555,13 +5587,6 @@ "dev": true, "license": "MIT" }, - "node_modules/lodash.sortby": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", - "integrity": "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==", - "dev": true, - "license": "MIT" - }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -6924,16 +6949,6 @@ "node": ">=8.0" } }, - "node_modules/tr46": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", - "integrity": "sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==", - "dev": true, - "license": "MIT", - "dependencies": { - "punycode": "^2.1.0" - } - }, "node_modules/tree-kill": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", @@ -7039,9 +7054,9 @@ "optional": true }, "node_modules/tsup": { - "version": "8.5.0", - "resolved": "https://registry.npmjs.org/tsup/-/tsup-8.5.0.tgz", - "integrity": "sha512-VmBp77lWNQq6PfuMqCHD3xWl22vEoWsKajkF8t+yMBawlUS8JzEI+vOVMeuNZIuMML8qXRizFKi9oD5glKQVcQ==", + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/tsup/-/tsup-8.5.1.tgz", + "integrity": "sha512-xtgkqwdhpKWr3tKPmCkvYmS9xnQK3m3XgxZHwSUjvfTjp7YfXe5tT3GgWi0F2N+ZSMsOeWeZFh7ZZFg5iPhing==", "dev": true, "license": "MIT", "dependencies": { @@ -7050,14 +7065,14 @@ "chokidar": "^4.0.3", "consola": "^3.4.0", "debug": "^4.4.0", - "esbuild": "^0.25.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.8.0-beta.0", + "source-map": "^0.7.6", "sucrase": "^3.35.0", "tinyexec": "^0.3.2", "tinyglobby": "^0.2.11", @@ -7102,16 +7117,13 @@ } }, "node_modules/tsup/node_modules/source-map": { - "version": "0.8.0-beta.0", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.8.0-beta.0.tgz", - "integrity": "sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==", + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", + "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", "dev": true, "license": "BSD-3-Clause", - "dependencies": { - "whatwg-url": "^7.0.0" - }, "engines": { - "node": ">= 8" + "node": ">= 12" } }, "node_modules/type-check": { @@ -7317,25 +7329,6 @@ "makeerror": "1.0.12" } }, - "node_modules/webidl-conversions": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", - "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", - "dev": true, - "license": "BSD-2-Clause" - }, - "node_modules/whatwg-url": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz", - "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==", - "dev": true, - "license": "MIT", - "dependencies": { - "lodash.sortby": "^4.7.0", - "tr46": "^1.0.1", - "webidl-conversions": "^4.0.2" - } - }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/package.json b/package.json index 72cb091..705881f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@inkdropapp/ipm", - "version": "0.0.3", + "version": "1.0.0-beta.4", "type": "module", "description": "Inkdrop Package Manager", "main": "lib/index.js", @@ -11,6 +11,7 @@ "prepublishOnly": "npm run clean && npm run build", "test": "jest", "test:watch": "jest --watch", + "test:ci": "jest --testPathIgnorePatterns=\"command-publish.test.ts\"", "test:real-install": "jest tests/real-install.test.ts --testMatch=\"**/tests/real-install.test.ts\"", "test:real-outdated": "jest tests/real-get-outdated.test.ts --testMatch=\"**/tests/real-get-outdated.test.ts\"", "format": "prettier --write .", @@ -45,11 +46,12 @@ "@types/node": "^24.10.0", "@types/semver": "^7.7.1", "@types/tar": "^6.1.13", + "dotenv": "^17.2.3", "eslint": "^9.39.1", "jest": "^30.2.0", "prettier": "^3.6.2", "ts-jest": "^29.4.5", - "tsup": "^8.5.0", + "tsup": "^8.5.1", "typescript": "^5.9.3", "typescript-eslint": "^8.46.3" } diff --git a/src/commands/get-installed.ts b/src/commands/get-installed.ts index 9047dee..be27a7c 100644 --- a/src/commands/get-installed.ts +++ b/src/commands/get-installed.ts @@ -12,12 +12,14 @@ export class CommandGetInstalled { return await this.getInstalledPackages(packagesDir) } - private async getInstalledPackages(packagesDir: string): Promise { + private async getInstalledPackages( + packagesDir: string + ): Promise { const installedPackages: PackageMetadata[] = [] try { const packageNames = await readdir(packagesDir) - + for (const packageName of packageNames) { const packageDir = path.join(packagesDir, packageName) const packageJsonPath = path.join(packageDir, 'package.json') @@ -27,7 +29,10 @@ export class CommandGetInstalled { const packageJson: PackageMetadata = JSON.parse(packageJsonContent) installedPackages.push(packageJson) } catch (error) { - logger.warn(`Warning: Could not read package.json for ${packageName}:`, error) + logger.warn( + `Warning: Could not read package.json for ${packageName}:`, + error + ) } } } catch (error) { @@ -39,4 +44,4 @@ export class CommandGetInstalled { return installedPackages } -} \ No newline at end of file +} diff --git a/src/commands/get-outdated.ts b/src/commands/get-outdated.ts index 6ad0c00..a450f52 100644 --- a/src/commands/get-outdated.ts +++ b/src/commands/get-outdated.ts @@ -20,7 +20,7 @@ export class CommandGetOutdated { try { const installedPkgs = await this.getInstalledPackages(packagesDir) - + for (const pkg of installedPkgs) { try { const packageInfo = await this.registry.getPackageInfo(pkg.name) @@ -37,7 +37,10 @@ export class CommandGetOutdated { }) } } catch (error) { - logger.warn(`Warning: Could not check updates for ${pkg.name}:`, error) + logger.warn( + `Warning: Could not check updates for ${pkg.name}:`, + error + ) } } } catch (error) { @@ -48,12 +51,14 @@ export class CommandGetOutdated { return outdatedPackages } - private async getInstalledPackages(packagesDir: string): Promise { + private async getInstalledPackages( + packagesDir: string + ): Promise { const installedPackages: PackageMetadata[] = [] try { const packageNames = await readdir(packagesDir) - + for (const packageName of packageNames) { const packageDir = path.join(packagesDir, packageName) const packageJsonPath = path.join(packageDir, 'package.json') @@ -63,7 +68,10 @@ export class CommandGetOutdated { const packageJson: PackageMetadata = JSON.parse(packageJsonContent) installedPackages.push(packageJson) } catch (error) { - logger.warn(`Warning: Could not read package.json for ${packageName}:`, error) + logger.warn( + `Warning: Could not read package.json for ${packageName}:`, + error + ) } } } catch (error) { @@ -75,4 +83,4 @@ export class CommandGetOutdated { return installedPackages } -} \ No newline at end of file +} diff --git a/src/commands/publish.ts b/src/commands/publish.ts new file mode 100644 index 0000000..1c5de3d --- /dev/null +++ b/src/commands/publish.ts @@ -0,0 +1,260 @@ +import { rm } from 'fs/promises' +import * as fs from 'fs/promises' +import path from 'path' +import axios, { isAxiosError } from 'axios' +import FormData from 'form-data' +import * as tar from 'tar' +import { PACKAGE_MAX_SIZE } from '../consts' +import { Environment } from '../environment' +import { logger } from '../logger' +import { IPMRegistry } from '../registry' +import type { AxiosInstance } from 'axios' + +/** + * Command to publish a new Inkdrop package + */ +export class CommandPublish { + apiClient: AxiosInstance + + constructor( + public env: Environment, + public registry: IPMRegistry + ) { + this.apiClient = this.getAxiosInstance() + } + + private getAxiosInstance() { + // Get local HTTP server config with auth + const accessKey = this.env.getInkdropAccessKey() + if (!accessKey) { + throw new Error('The access key is not configured.') + } + + const apiBaseUrl = this.env.getInkdropApiUrl() + return axios.create({ + baseURL: `${apiBaseUrl}/v2/packages`, + auth: { + username: accessKey.accessKeyId, + password: accessKey.secretAccessKey + }, + headers: { + 'X-API-KEY': '1' + } + }) + } + + async run(opts: { dryrun?: boolean; path?: string }): Promise { + const { dryrun = false, path: packagePath } = opts + const repoDir = packagePath || process.cwd() + + try { + const { default: pkg } = await import( + path.join(repoDir, 'package.json'), + { with: { type: 'json' } } + ) + const repository = this.getRepositoryId(pkg) + + logger.info(`Publishing ${pkg.name}@${pkg.version}...`) + + // Step 1 - Validate the package metadata + await this.validatePackageContents(pkg, repoDir) + + // Step 2 - Create a tarball file of the package + const { filePath } = await this.createTarball(pkg, repoDir) + + // Step 3 - Upload the tarball to the Inkdrop package registry via the local http server + await this.uploadTarball(pkg, filePath, repository!, dryrun) + + // Step 4 - Clean up the created tarball file + await rm(filePath, { force: true }) + logger.info('Cleaned up temporary tarball') + + logger.info(`Successfully published ${pkg.name}@${pkg.version}`) + return true + } catch (error) { + logger.error(`Failed to publish the package:`, error) + throw error + } + } + + getRepositoryId(pack: any = {}): string | null { + const repository = pack.repository?.url ?? pack.repository + + if (!repository || typeof repository !== 'string') { + return null + } + + try { + const repoPath = new URL(repository.replace(/\.git$/, '')).pathname + const [owner, name] = repoPath.split('/').slice(-2) + + if (name && owner) { + return `${owner}/${name}` + } + } catch { + return null + } + + return null + } + + async validatePackageContents(pkg: any, _repoDir: string) { + try { + // Validate required fields + if (!pkg.name || typeof pkg.name !== 'string') { + throw new Error('package.json must have a valid "name" field') + } + + if (!pkg.version || typeof pkg.version !== 'string') { + throw new Error('package.json must have a valid "version" field') + } + + // Validate repository field + const repository = this.getRepositoryId(pkg) + if (!repository) { + throw new Error( + 'package.json must have a valid "repository" field (e.g., https://github.com/owner/repo)' + ) + } + + // Validate Inkdrop engine compatibility + if (!pkg.engines?.inkdrop) { + logger.warn( + 'Warning: package.json does not specify "engines.inkdrop" field. This package may not be compatible with all Inkdrop versions.' + ) + } + + logger.debug('Package validation passed') + } catch (error) { + if ((error as NodeJS.ErrnoException).code === 'ENOENT') { + throw new Error('package.json not found in the current directory') + } + throw error + } + } + + async createTarball(pkg: any, repoDir: string) { + const tempDir = path.join(this.env.getCacheDirectory(), 'publish') + await fs.mkdir(tempDir, { recursive: true }) + + const tarballPath = path.join(tempDir, `${pkg.name}-${pkg.version}.tar.gz`) + + // Files to exclude from the tarball + const excludePatterns = [ + 'node_modules', + '.git', + '.github', + '.vscode', + '.DS_Store', + '*.log', + '.npm', + '.eslint*', + '.env', + '.env.local', + '.env.*.local', + 'coverage', + '.nyc_output', + 'tmp', + 'temp', + '*.tgz', + '*.tar.gz' + ] + + await tar.create( + { + gzip: true, + file: tarballPath, + cwd: repoDir, + filter: path => { + // Check if path matches any exclude pattern + for (const pattern of excludePatterns) { + if (pattern.includes('*')) { + // Simple glob pattern matching + const regex = new RegExp('^' + pattern.replace(/\*/g, '.*') + '$') + if (regex.test(path)) { + return false + } + } else if (path === pattern || path.startsWith(`${pattern}/`)) { + return false + } + } + return true + } + }, + ['.'] + ) + + // Check file size (max 30MB) + const stats = await fs.stat(tarballPath) + + if (stats.size > PACKAGE_MAX_SIZE) { + await rm(tarballPath, { force: true }) + throw new Error( + `Package tarball size (${(stats.size / 1024 / 1024).toFixed(2)}MB) exceeds the 30MB limit` + ) + } + + logger.debug(`Created tarball: ${tarballPath}`) + + return { + filePath: tarballPath + } + } + + private async uploadTarball( + pkg: any, + filePath: string, + repositoryId: string, + dryrun: boolean = false + ) { + // Check if the package already exists + let isNewPackage = true + try { + const checkResponse = await this.registry.getPackageInfo(pkg.name) + isNewPackage = !checkResponse + } catch (error: any) { + if (error.response?.status !== 404) { + // If it's not a 404, something else went wrong + logger.warn('Could not check if package exists:', error.message) + } + // 404 means package doesn't exist, so it's a new package + } + + // Prepare form data + const form = new FormData() + form.append('tarball', await fs.readFile(filePath), { + filename: path.basename(filePath), + contentType: 'application/gzip' + }) + + if (isNewPackage) { + form.append('repository', repositoryId) + form.append('dryrun', dryrun ? 'true' : 'false') + } + + // Determine the endpoint + const endpoint = isNewPackage ? `` : `/${pkg.name}/versions` + + logger.info(`Uploading to ${endpoint}...`) + + try { + const response = await this.apiClient.post(endpoint, form, { + headers: { + ...form.getHeaders() + }, + maxBodyLength: Infinity, + maxContentLength: Infinity + }) + + logger.info('Upload successful!') + return response.data + } catch (error: any) { + if (isAxiosError(error) && error.response) { + throw new Error( + `Upload failed: ${error.response.status} - ${JSON.stringify(error.response.data)}` + ) + } + throw error + } + } +} diff --git a/src/commands/update.ts b/src/commands/update.ts index 9c1a316..b841bd0 100644 --- a/src/commands/update.ts +++ b/src/commands/update.ts @@ -1,10 +1,10 @@ import { readFile } from 'fs/promises' import path from 'path' import semver from 'semver' -import { CommandInstall } from './install' import { logger } from '../logger' import { PackageMetadata } from '../types' import { getLatestCompatibleVersion } from '../utils' +import { CommandInstall } from './install' export class CommandUpdate extends CommandInstall { async run(name: string, version?: string): Promise { @@ -51,7 +51,9 @@ export class CommandUpdate extends CommandInstall { // Check if current version is newer than target (downgrade case) if (semver.gt(currentVersion, targetVersion)) { - logger.info(`Downgrading ${name} from ${currentVersion} to ${targetVersion}`) + logger.info( + `Downgrading ${name} from ${currentVersion} to ${targetVersion}` + ) } else { logger.info(`Updating ${name} from ${currentVersion} to ${targetVersion}`) } @@ -59,4 +61,4 @@ export class CommandUpdate extends CommandInstall { // Proceed with installation (which will replace the existing version) return super.run(name, version) } -} \ No newline at end of file +} diff --git a/src/consts.ts b/src/consts.ts new file mode 100644 index 0000000..03fe8d5 --- /dev/null +++ b/src/consts.ts @@ -0,0 +1 @@ +export const PACKAGE_MAX_SIZE = 1024 * 1024 * 30 // 30 MB diff --git a/src/environment.ts b/src/environment.ts index 432b024..20e8871 100644 --- a/src/environment.ts +++ b/src/environment.ts @@ -18,7 +18,7 @@ export class Environment { * The path to the directory where Inkdrop stores its data, configuration, cache, and packages. */ getInkdropDirectory() { - const pathToHome = this.options.appHomePath || process.env.INKDROP_HOME + const pathToHome = process.env.INKDROP_HOME || this.options.appHomePath return pathToHome != null ? pathToHome : path.join(this.getAppDataPath(), 'inkdrop') @@ -47,12 +47,25 @@ export class Environment { } getInkdropPackagesUrl() { - const value = this.options.packagesUrl || process.env.INKDROP_PACKAGES_URL + const value = process.env.INKDROP_PACKAGES_URL || this.options.packagesUrl return value != null ? value : `${this.getInkdropApiUrl()}/packages` } getInkdropApiUrl() { - const value = this.options.apiUrl || process.env.INKDROP_API_URL - return value != null ? value : 'https://api.inkdrop.app/v1' + const value = process.env.INKDROP_API_URL || this.options.apiUrl + return value != null ? value : 'https://api.inkdrop.app' + } + + getInkdropAccessKey() { + const accessKeyId = + process.env.INKDROP_ACCESS_KEY_ID || this.options.accessKeyId + const secretAccessKey = + process.env.INKDROP_SECRET_ACCESS_KEY || this.options.secretAccessKey + return accessKeyId && secretAccessKey + ? { + accessKeyId, + secretAccessKey + } + : null } } diff --git a/src/index.ts b/src/index.ts index 5bc6fa3..1517d6a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,7 @@ import { CommandGetInstalled } from './commands/get-installed' import { CommandGetOutdated } from './commands/get-outdated' import { CommandInstall } from './commands/install' +import { CommandPublish } from './commands/publish' import { CommandUninstall } from './commands/uninstall' import { CommandUpdate } from './commands/update' import { Environment } from './environment' @@ -16,7 +17,9 @@ export class IPM { installedInkdropVersion: string constructor(public options: IPMOptions) { - this.installedInkdropVersion = normalizeVersion(options.appVersion) + this.installedInkdropVersion = normalizeVersion( + process.env.INKDROP_VERSION || options.appVersion + ) this.env = new Environment(options) this.registry = new IPMRegistry(this.env.getInkdropApiUrl()) } @@ -57,4 +60,11 @@ export class IPM { const command = new CommandGetInstalled(this.env) return await command.run() } + + async publish( + opts: { dryrun?: boolean } = { dryrun: false } + ): Promise { + const command = new CommandPublish(this.env, this.registry) + return await command.run(opts) + } } diff --git a/src/types.ts b/src/types.ts index 0f15a2f..3a98198 100644 --- a/src/types.ts +++ b/src/types.ts @@ -13,6 +13,8 @@ export interface IPMOptions { resourcePath?: string apiUrl?: string packagesUrl?: string + accessKeyId?: string + secretAccessKey?: string } export type PackageInfo = { diff --git a/tests/command-get-outdated.test.ts b/tests/command-get-outdated.test.ts index a8c54ac..017d5db 100644 --- a/tests/command-get-outdated.test.ts +++ b/tests/command-get-outdated.test.ts @@ -1,6 +1,6 @@ -import { jest } from '@jest/globals' import { readdir, readFile } from 'fs/promises' import path from 'path' +import { jest } from '@jest/globals' import { CommandGetOutdated } from '../src/commands/get-outdated' import { Environment } from '../src/environment' import { logger } from '../src/logger' @@ -60,7 +60,8 @@ const mockRegistryPackage1: PackageInfo = { inkdrop: '>=5.0.0' }, dist: { - tarball: 'https://api.inkdrop.app/packages/test-package-1/-/test-package-1-1.0.0.tgz' + tarball: + 'https://api.inkdrop.app/packages/test-package-1/-/test-package-1-1.0.0.tgz' } }, '1.5.0': { @@ -73,7 +74,8 @@ const mockRegistryPackage1: PackageInfo = { inkdrop: '>=5.0.0' }, dist: { - tarball: 'https://api.inkdrop.app/packages/test-package-1/-/test-package-1-1.5.0.tgz' + tarball: + 'https://api.inkdrop.app/packages/test-package-1/-/test-package-1-1.5.0.tgz' } } } @@ -107,7 +109,8 @@ const mockRegistryPackage2: PackageInfo = { inkdrop: '>=5.0.0' }, dist: { - tarball: 'https://api.inkdrop.app/packages/test-package-2/-/test-package-2-2.0.0.tgz' + tarball: + 'https://api.inkdrop.app/packages/test-package-2/-/test-package-2-2.0.0.tgz' } } } @@ -127,16 +130,24 @@ describe('CommandGetOutdated', () => { // Create mock environment mockEnvironment = new Environment({ appVersion: testInkdropVersion }) - jest.spyOn(mockEnvironment, 'getInkdropDirectory').mockReturnValue(testInkdropDir) + jest + .spyOn(mockEnvironment, 'getInkdropDirectory') + .mockReturnValue(testInkdropDir) // Create mock registry - mockRegistry = new IPMRegistry('http://test-api.inkdrop.app') as jest.Mocked - + mockRegistry = new IPMRegistry( + 'http://test-api.inkdrop.app' + ) as jest.Mocked + // Reset registry mock methods mockRegistry.getPackageInfo = jest.fn() // Create command instance - command = new CommandGetOutdated(testInkdropVersion, mockEnvironment, mockRegistry) + command = new CommandGetOutdated( + testInkdropVersion, + mockEnvironment, + mockRegistry + ) // Default mock behaviors mockedReaddir.mockResolvedValue([]) @@ -179,8 +190,11 @@ describe('CommandGetOutdated', () => { describe('with outdated packages', () => { beforeEach(() => { // Mock installed packages - mockedReaddir.mockResolvedValue(['test-package-1', 'test-package-2'] as any) - + mockedReaddir.mockResolvedValue([ + 'test-package-1', + 'test-package-2' + ] as any) + // Mock package.json files mockedReadFile .mockResolvedValueOnce(JSON.stringify(mockInstalledPackage1)) @@ -211,8 +225,12 @@ describe('CommandGetOutdated', () => { path.join(testPackagesDir, 'test-package-2', 'package.json'), 'utf8' ) - expect(mockRegistry.getPackageInfo).toHaveBeenCalledWith('test-package-1') - expect(mockRegistry.getPackageInfo).toHaveBeenCalledWith('test-package-2') + expect(mockRegistry.getPackageInfo).toHaveBeenCalledWith( + 'test-package-1' + ) + expect(mockRegistry.getPackageInfo).toHaveBeenCalledWith( + 'test-package-2' + ) }) }) @@ -220,9 +238,11 @@ describe('CommandGetOutdated', () => { beforeEach(() => { // Mock installed packages mockedReaddir.mockResolvedValue(['test-package-2'] as any) - + // Mock package.json file - mockedReadFile.mockResolvedValueOnce(JSON.stringify(mockInstalledPackage2)) + mockedReadFile.mockResolvedValueOnce( + JSON.stringify(mockInstalledPackage2) + ) // Mock registry response - same version as installed mockRegistry.getPackageInfo.mockResolvedValueOnce(mockRegistryPackage2) @@ -232,7 +252,9 @@ describe('CommandGetOutdated', () => { const result = await command.run() expect(result).toEqual([]) - expect(mockRegistry.getPackageInfo).toHaveBeenCalledWith('test-package-2') + expect(mockRegistry.getPackageInfo).toHaveBeenCalledWith( + 'test-package-2' + ) }) }) @@ -245,15 +267,21 @@ describe('CommandGetOutdated', () => { it('should handle registry errors gracefully and continue with other packages', async () => { mockedReaddir.mockResolvedValue(['test-package-1'] as any) - mockedReadFile.mockResolvedValueOnce(JSON.stringify(mockInstalledPackage1)) - + mockedReadFile.mockResolvedValueOnce( + JSON.stringify(mockInstalledPackage1) + ) + // Mock registry error for first package - mockRegistry.getPackageInfo.mockRejectedValueOnce(new Error('Registry error')) + mockRegistry.getPackageInfo.mockRejectedValueOnce( + new Error('Registry error') + ) const result = await command.run() expect(result).toEqual([]) - expect(mockRegistry.getPackageInfo).toHaveBeenCalledWith('test-package-1') + expect(mockRegistry.getPackageInfo).toHaveBeenCalledWith( + 'test-package-1' + ) expect(mockedLogger.warn).toHaveBeenCalledWith( 'Warning: Could not check updates for test-package-1:', expect.any(Error) @@ -263,7 +291,7 @@ describe('CommandGetOutdated', () => { it('should handle invalid package.json files gracefully', async () => { // Reset registry mock for this test mockRegistry.getPackageInfo.mockReset() - + mockedReaddir.mockResolvedValue(['invalid-package'] as any) mockedReadFile.mockResolvedValueOnce('invalid json') @@ -281,7 +309,7 @@ describe('CommandGetOutdated', () => { it('should handle missing package.json files gracefully', async () => { // Reset registry mock for this test mockRegistry.getPackageInfo.mockReset() - + mockedReaddir.mockResolvedValue(['missing-package'] as any) const error = new Error('ENOENT') as NodeJS.ErrnoException error.code = 'ENOENT' @@ -333,15 +361,21 @@ describe('CommandGetOutdated', () => { beforeEach(() => { mockedReaddir.mockResolvedValue(['test-package-1'] as any) - mockedReadFile.mockResolvedValueOnce(JSON.stringify(mockInstalledPackage1)) - mockRegistry.getPackageInfo.mockResolvedValueOnce(mockIncompatiblePackage) + mockedReadFile.mockResolvedValueOnce( + JSON.stringify(mockInstalledPackage1) + ) + mockRegistry.getPackageInfo.mockResolvedValueOnce( + mockIncompatiblePackage + ) }) it('should not include packages with no compatible versions', async () => { const result = await command.run() expect(result).toEqual([]) - expect(mockRegistry.getPackageInfo).toHaveBeenCalledWith('test-package-1') + expect(mockRegistry.getPackageInfo).toHaveBeenCalledWith( + 'test-package-1' + ) }) }) }) @@ -353,4 +387,4 @@ describe('CommandGetOutdated', () => { expect(command.registry).toBe(mockRegistry) }) }) -}) \ No newline at end of file +}) diff --git a/tests/command-install.test.ts b/tests/command-install.test.ts index 6643c7e..96a3e91 100644 --- a/tests/command-install.test.ts +++ b/tests/command-install.test.ts @@ -265,7 +265,10 @@ describe('CommandInstall', () => { } } - const result = getLatestCompatibleVersion(mockPackageInfo, testInkdropVersion) + const result = getLatestCompatibleVersion( + mockPackageInfo, + testInkdropVersion + ) expect(result).toBeNull() }) }) diff --git a/tests/command-publish.test.ts b/tests/command-publish.test.ts new file mode 100644 index 0000000..63e7433 --- /dev/null +++ b/tests/command-publish.test.ts @@ -0,0 +1,180 @@ +import './env' +import { rm } from 'fs/promises' +import path from 'path' +import { CommandPublish } from '../src/commands/publish' +import { Environment } from '../src/environment' +import { IPMRegistry } from '../src/registry' + +describe('CommandPublish', () => { + let command: CommandPublish + let environment: Environment + let registry: IPMRegistry + const fixtureDir = path.join(__dirname, 'fixtures', 'plugin-valid') + + beforeEach(() => { + environment = new Environment({ + appVersion: '6.0.0' + }) + registry = new IPMRegistry(environment.getInkdropApiUrl()) + command = new CommandPublish(environment, registry) + }) + + describe('validation', () => { + let originalCwd: string + + beforeAll(() => { + originalCwd = process.cwd() + }) + + beforeEach(() => { + // Change to fixture directory for testing + process.chdir(fixtureDir) + }) + + afterEach(() => { + // Restore original cwd + if (originalCwd) { + process.chdir(originalCwd) + } + }) + + describe('getRepositoryId', () => { + it('should extract repository ID from valid repository URL', () => { + const pkg = { + repository: 'https://github.com/inkdropapp/test-plugin.git' + } + const repoId = command.getRepositoryId(pkg) + + expect(repoId).toBe('inkdropapp/test-plugin') + }) + + it('should extract repository ID from repository object', () => { + const pkg = { + repository: { + type: 'git', + url: 'git+https://github.com/inkdropapp/test-plugin.git' + } + } + const repoId = command.getRepositoryId(pkg) + + expect(repoId).toBe('inkdropapp/test-plugin') + }) + + it('should handle repository URL without .git extension', () => { + const pkg = { + repository: 'https://github.com/inkdropapp/test-plugin' + } + const repoId = command.getRepositoryId(pkg) + + expect(repoId).toBe('inkdropapp/test-plugin') + }) + + it('should return null for missing repository', () => { + const pkg = { name: 'test', version: '1.0.0' } + const repoId = command.getRepositoryId(pkg) + + expect(repoId).toBeNull() + }) + + it('should return null for invalid repository format', () => { + const pkg = { repository: 'not-a-valid-url' } + const repoId = command.getRepositoryId(pkg) + + expect(repoId).toBeNull() + }) + }) + + describe('validatePackageContents', () => { + it('should pass validation for valid package', async () => { + const pkg = { + name: 'test-package', + version: '1.0.0', + repository: 'https://github.com/test/test.git' + } + await command.validatePackageContents(pkg, fixtureDir) + }) + + it('should warn if engines.inkdrop is not specified', async () => { + const pkg = { + name: 'test-package', + version: '1.0.0', + repository: 'https://github.com/test/test.git' + } + await command.validatePackageContents(pkg, fixtureDir) + }) + + it('should throw if package name is missing', async () => { + const pkg = { + version: '1.0.0', + repository: 'https://github.com/test/test.git' + } + + await expect( + command.validatePackageContents(pkg, fixtureDir) + ).rejects.toThrow('package.json must have a valid "name" field') + }) + + it('should throw if package version is missing', async () => { + const pkg = { + name: 'test-package', + repository: 'https://github.com/test/test.git' + } + + await expect( + command.validatePackageContents(pkg, fixtureDir) + ).rejects.toThrow('package.json must have a valid "version" field') + }) + }) + }) + + describe('tarball creation', () => { + let originalCwd: string + + beforeAll(() => { + originalCwd = process.cwd() + }) + + beforeEach(() => { + process.chdir(fixtureDir) + }) + + afterEach(() => { + if (originalCwd) { + process.chdir(originalCwd) + } + }) + + it('should create tarball and log size', async () => { + const pkg = { name: 'plugin-valid', version: '1.0.0' } + const result = await command.createTarball(pkg, fixtureDir) + + expect(result.filePath).toContain('plugin-valid-1.0.0.tar.gz') + + await rm(result.filePath) + }) + }) + + describe('publishing', () => { + beforeEach(() => { + process.chdir(fixtureDir) + }) + + it('should publish package in dry run mode', async () => { + // Use plugin-valid fixture + + const result = await command.run({ dryrun: true }) + expect(result).toBe(true) + }) + }) + + // + // describe('cleanup', () => { + // it('should define cleanup in the run method flow', () => { + // // Verify that the run method includes cleanup step + // const runMethod = command.run.toString() + // + // expect(runMethod).toContain('rm') + // expect(runMethod).toContain('Cleaned up temporary tarball') + // }) + // }) +}) diff --git a/tests/command-uninstall.test.ts b/tests/command-uninstall.test.ts index 2516b80..fc9a48d 100644 --- a/tests/command-uninstall.test.ts +++ b/tests/command-uninstall.test.ts @@ -1,6 +1,6 @@ -import { jest } from '@jest/globals' import { access, rm } from 'fs/promises' import path from 'path' +import { jest } from '@jest/globals' import { CommandUninstall } from '../src/commands/uninstall' import { Environment } from '../src/environment' import { logger } from '../src/logger' @@ -25,7 +25,9 @@ describe('CommandUninstall', () => { // Create mock environment mockEnvironment = new Environment({ appVersion: '5.0.0' }) - jest.spyOn(mockEnvironment, 'getInkdropDirectory').mockReturnValue(testInkdropDir) + jest + .spyOn(mockEnvironment, 'getInkdropDirectory') + .mockReturnValue(testInkdropDir) // Create command instance command = new CommandUninstall(mockEnvironment) @@ -51,8 +53,12 @@ describe('CommandUninstall', () => { recursive: true, force: true }) - expect(mockedLogger.info).toHaveBeenCalledWith(`Uninstalling ${testPackageName}...`) - expect(mockedLogger.info).toHaveBeenCalledWith(`Successfully uninstalled ${testPackageName}`) + expect(mockedLogger.info).toHaveBeenCalledWith( + `Uninstalling ${testPackageName}...` + ) + expect(mockedLogger.info).toHaveBeenCalledWith( + `Successfully uninstalled ${testPackageName}` + ) expect(mockedLogger.error).not.toHaveBeenCalled() }) @@ -105,14 +111,18 @@ describe('CommandUninstall', () => { const removeError = new Error('Permission denied') mockedRm.mockRejectedValueOnce(removeError) - await expect(command.run(testPackageName)).rejects.toThrow('Permission denied') + await expect(command.run(testPackageName)).rejects.toThrow( + 'Permission denied' + ) expect(mockedAccess).toHaveBeenCalledWith(testPackageDir) expect(mockedRm).toHaveBeenCalledWith(testPackageDir, { recursive: true, force: true }) - expect(mockedLogger.info).toHaveBeenCalledWith(`Uninstalling ${testPackageName}...`) + expect(mockedLogger.info).toHaveBeenCalledWith( + `Uninstalling ${testPackageName}...` + ) expect(mockedLogger.error).toHaveBeenCalledWith( `Failed to uninstall ${testPackageName}:`, removeError @@ -145,8 +155,14 @@ describe('CommandUninstall', () => { it('should log uninstall start and success messages', async () => { await command.run(testPackageName) - expect(mockedLogger.info).toHaveBeenNthCalledWith(1, `Uninstalling ${testPackageName}...`) - expect(mockedLogger.info).toHaveBeenNthCalledWith(2, `Successfully uninstalled ${testPackageName}`) + expect(mockedLogger.info).toHaveBeenNthCalledWith( + 1, + `Uninstalling ${testPackageName}...` + ) + expect(mockedLogger.info).toHaveBeenNthCalledWith( + 2, + `Successfully uninstalled ${testPackageName}` + ) }) it('should log error messages when operations fail', async () => { @@ -185,10 +201,12 @@ describe('CommandUninstall', () => { it('should properly detect non-existing packages', async () => { // mockedAccess already configured to reject by default - await expect(command.run(testPackageName)).rejects.toThrow('is not installed') + await expect(command.run(testPackageName)).rejects.toThrow( + 'is not installed' + ) expect(mockedAccess).toHaveBeenCalledWith(testPackageDir) expect(mockedRm).not.toHaveBeenCalled() }) }) -}) \ No newline at end of file +}) diff --git a/tests/command-update.test.ts b/tests/command-update.test.ts index 129dce0..6290ba7 100644 --- a/tests/command-update.test.ts +++ b/tests/command-update.test.ts @@ -4,7 +4,7 @@ import { CommandUpdate } from '../src/commands/update' import { Environment } from '../src/environment' import { logger } from '../src/logger' import { IPMRegistry } from '../src/registry' -import { PackageInfo, PackageMetadata } from '../src/types' +import { PackageInfo } from '../src/types' jest.mock('fs/promises') jest.mock('../src/logger') @@ -42,7 +42,8 @@ const mockPackageInfo: PackageInfo = { inkdrop: '^5.0.0' }, dist: { - tarball: 'https://registry.test.com/test-package/-/test-package-1.0.0.tgz' + tarball: + 'https://registry.test.com/test-package/-/test-package-1.0.0.tgz' } }, '1.5.0': { @@ -55,7 +56,8 @@ const mockPackageInfo: PackageInfo = { inkdrop: '^5.0.0' }, dist: { - tarball: 'https://registry.test.com/test-package/-/test-package-1.5.0.tgz' + tarball: + 'https://registry.test.com/test-package/-/test-package-1.5.0.tgz' } }, '2.0.0': { @@ -68,7 +70,8 @@ const mockPackageInfo: PackageInfo = { inkdrop: '^5.0.0' }, dist: { - tarball: 'https://registry.test.com/test-package/-/test-package-2.0.0.tgz' + tarball: + 'https://registry.test.com/test-package/-/test-package-2.0.0.tgz' } } } @@ -241,7 +244,8 @@ describe('CommandUpdate', () => { repository: 'test/test-package', engines: { inkdrop: '^6.0.0' }, // Incompatible with test version 5.0.0 dist: { - tarball: 'https://registry.test.com/test-package/-/test-package-1.0.0.tgz' + tarball: + 'https://registry.test.com/test-package/-/test-package-1.0.0.tgz' } } } @@ -312,4 +316,3 @@ describe('CommandUpdate', () => { }) }) }) - diff --git a/tests/env.ts b/tests/env.ts new file mode 100644 index 0000000..5a8f1f0 --- /dev/null +++ b/tests/env.ts @@ -0,0 +1,3 @@ +import dotenv from 'dotenv' + +dotenv.config() diff --git a/tests/fixtures/plugin-valid/README.md b/tests/fixtures/plugin-valid/README.md new file mode 100644 index 0000000..c79fa6c --- /dev/null +++ b/tests/fixtures/plugin-valid/README.md @@ -0,0 +1,3 @@ +# Valid Plugin Example + +Hello, world! diff --git a/tests/fixtures/plugin-valid/index.js b/tests/fixtures/plugin-valid/index.js new file mode 100644 index 0000000..3d46f95 --- /dev/null +++ b/tests/fixtures/plugin-valid/index.js @@ -0,0 +1,4 @@ +module.exports = { + activate() {}, + deactivate() {} +} diff --git a/tests/fixtures/plugin-valid/package.json b/tests/fixtures/plugin-valid/package.json new file mode 100644 index 0000000..a870962 --- /dev/null +++ b/tests/fixtures/plugin-valid/package.json @@ -0,0 +1,20 @@ +{ + "name": "plugin-valid", + "version": "1.0.0", + "description": "A valid plugin fixture", + "homepage": "https://github.com/inkdropapp/inkdrop-vim#readme", + "bugs": { + "url": "https://github.com/inkdropapp/inkdrop-vim/issues" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/inkdropapp/inkdrop-vim.git" + }, + "license": "MIT", + "author": "Takuya Matsuyama", + "type": "commonjs", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + } +} diff --git a/tests/real-get-outdated.test.ts b/tests/real-get-outdated.test.ts index b132c46..a2f8137 100644 --- a/tests/real-get-outdated.test.ts +++ b/tests/real-get-outdated.test.ts @@ -1,5 +1,5 @@ -import { CommandInstall } from '../src/commands/install' import { CommandGetOutdated } from '../src/commands/get-outdated' +import { CommandInstall } from '../src/commands/install' import { Environment } from '../src/environment' import { IPMRegistry } from '../src/registry' @@ -11,7 +11,7 @@ describe('Real Get Outdated Test', () => { appHomePath: './tmp' // Use ./tmp directory }) const realRegistry = new IPMRegistry('https://api.inkdrop.app') - + console.log('šŸ” Starting real getOutdated test with math package...') // First, ensure we have the math package installed for testing @@ -20,7 +20,7 @@ describe('Real Get Outdated Test', () => { realEnvironment, realRegistry ) - + const realCommandGetOutdated = new CommandGetOutdated( '5.9.0', realEnvironment, @@ -33,9 +33,9 @@ describe('Real Get Outdated Test', () => { // Check if math package is already installed const packageDir = path.default.join('./tmp', 'packages', 'math') const packageJsonPath = path.default.join(packageDir, 'package.json') - + let currentVersion = '' - + try { await fs.access(packageJsonPath) const packageJsonContent = await fs.readFile(packageJsonPath, 'utf8') @@ -45,7 +45,7 @@ describe('Real Get Outdated Test', () => { } catch { console.log('šŸ“¦ Math package not found, installing it first...') await realCommandInstall.run('math') - + // Read the version after installation const packageJsonContent = await fs.readFile(packageJsonPath, 'utf8') const packageJson = JSON.parse(packageJsonContent) @@ -61,17 +61,17 @@ describe('Real Get Outdated Test', () => { // Check if math package is in the outdated list const mathOutdated = outdatedPackages.find(pkg => pkg.name === 'math') - + if (mathOutdated) { console.log(`šŸ“ˆ Math package is outdated:`) console.log(` Current version: ${mathOutdated.version}`) console.log(` Latest version: ${mathOutdated.latestVersion}`) - + expect(mathOutdated.name).toBe('math') expect(mathOutdated.version).toBeDefined() expect(mathOutdated.latestVersion).toBeDefined() expect(mathOutdated.version).not.toBe(mathOutdated.latestVersion) - + // Validate version format expect(mathOutdated.version).toMatch(/^\d+\.\d+\.\d+/) expect(mathOutdated.latestVersion).toMatch(/^\d+\.\d+\.\d+/) @@ -81,7 +81,7 @@ describe('Real Get Outdated Test', () => { // Test that the function returns an array expect(Array.isArray(outdatedPackages)).toBe(true) - + // Log all outdated packages for debugging if (outdatedPackages.length > 0) { console.log('\nšŸ“‹ All outdated packages:') @@ -112,7 +112,7 @@ describe('Real Get Outdated Test', () => { appHomePath: './tmp-empty-test' // Use a directory that doesn't exist }) const realRegistry = new IPMRegistry('https://api.inkdrop.app') - + const realCommandGetOutdated = new CommandGetOutdated( '5.9.0', emptyEnvironment, @@ -136,7 +136,7 @@ describe('Real Get Outdated Test', () => { appHomePath: './tmp-outdated-test' }) const realRegistry = new IPMRegistry('https://api.inkdrop.app') - + const realCommandGetOutdated = new CommandGetOutdated( '5.9.0', realEnvironment, @@ -151,7 +151,7 @@ describe('Real Get Outdated Test', () => { // Create the packages directory structure const packagesDir = path.default.join('./tmp-outdated-test', 'packages') const mathPackageDir = path.default.join(packagesDir, 'math') - + await fs.mkdir(mathPackageDir, { recursive: true }) // Create a fake outdated package.json for math package @@ -159,7 +159,8 @@ describe('Real Get Outdated Test', () => { name: 'math', version: '1.0.0', // Much older version than current main: './lib/index', - description: 'Add math syntax support to the editor and the preview (outdated test version)', + description: + 'Add math syntax support to the editor and the preview (outdated test version)', repository: 'https://github.com/inkdropapp/inkdrop-math', engines: { inkdrop: '>=5.0.0' @@ -167,9 +168,14 @@ describe('Real Get Outdated Test', () => { } const packageJsonPath = path.default.join(mathPackageDir, 'package.json') - await fs.writeFile(packageJsonPath, JSON.stringify(outdatedPackageJson, null, 2)) + await fs.writeFile( + packageJsonPath, + JSON.stringify(outdatedPackageJson, null, 2) + ) - console.log(`šŸ“¦ Created simulated outdated math package (v${outdatedPackageJson.version})`) + console.log( + `šŸ“¦ Created simulated outdated math package (v${outdatedPackageJson.version})` + ) // Now test getOutdated const outdatedPackages = await realCommandGetOutdated.run() @@ -178,12 +184,12 @@ describe('Real Get Outdated Test', () => { // Math should definitely be outdated since we set it to v1.0.0 const mathOutdated = outdatedPackages.find(pkg => pkg.name === 'math') - + expect(mathOutdated).toBeDefined() expect(mathOutdated!.name).toBe('math') expect(mathOutdated!.version).toBe('1.0.0') expect(mathOutdated!.latestVersion).toBeDefined() - + console.log(`šŸ“ˆ Confirmed math package is outdated:`) console.log(` Current version: ${mathOutdated!.version}`) console.log(` Latest version: ${mathOutdated!.latestVersion}`) @@ -198,4 +204,4 @@ describe('Real Get Outdated Test', () => { console.log('āœ… Simulated outdated scenario test completed successfully') }, 60000) -}) \ No newline at end of file +}) diff --git a/tests/real-install.test.ts b/tests/real-install.test.ts index 975580f..46b4111 100644 --- a/tests/real-install.test.ts +++ b/tests/real-install.test.ts @@ -84,4 +84,3 @@ describe('Real Installation Test', () => { } }, 60000) // 60 second timeout for real API call and file operations }) - diff --git a/tests/registry.test.ts b/tests/registry.test.ts index 8a3a311..5c8ddac 100644 --- a/tests/registry.test.ts +++ b/tests/registry.test.ts @@ -329,4 +329,3 @@ describe('IPMRegistry', () => { }) }) }) - diff --git a/tsconfig.json b/tsconfig.json index 3dbb852..6dac070 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -14,7 +14,6 @@ /* Emit */ "declaration": true, "outDir": "./lib", - "rootDir": "./src", "noEmit": false, /* Linting */ "strict": true, @@ -24,6 +23,6 @@ "noFallthroughCasesInSwitch": true, "baseUrl": "./" /* Base directory to resolve non-absolute module names. */ }, - "include": ["src"], - "exclude": ["**/*.test.ts", "tests/**/*", "lib/**/*"] + "include": ["src", "tests"], + "exclude": ["lib/**/*"] } diff --git a/tsup.config.ts b/tsup.config.ts index 9b508e4..a29be93 100644 --- a/tsup.config.ts +++ b/tsup.config.ts @@ -8,8 +8,10 @@ export default defineConfig({ clean: true, outDir: 'lib', external: Object.keys(pkg.dependencies || {}), - target: 'es2023', + target: 'esnext', minify: false, - sourcemap: false + sourcemap: false, + banner: { + js: `import { createRequire } from 'module';const require = createRequire(import.meta.url);` + } }) -