diff --git a/.eslintrc.js b/.eslintrc.js index b879400..3084af5 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,7 +1,7 @@ // eslint-disable-next-line no-undef module.exports = { root: true, - ignorePatterns: ["dist", "examples"], + ignorePatterns: ["dist", "examples", "e2e"], plugins: ["@typescript-eslint"], extends: [ "eslint:recommended", diff --git a/.gitignore b/.gitignore index 5bcb6fe..dd22219 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ dist coverage node_modules +tsconfig.tsbuildinfo diff --git a/CHANGELOG.md b/CHANGELOG.md index 8367858..35fe013 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,4 +25,4 @@ This means replacing imports of `next/link` with `nextjs-routes/link` and `next/ +import { useRouter } from 'next/router' ``` -- Added windows support +- Added windows support. diff --git a/e2e/e2e.test.ts b/e2e/e2e.test.ts new file mode 100644 index 0000000..db5b433 --- /dev/null +++ b/e2e/e2e.test.ts @@ -0,0 +1,17 @@ +import { spawnSync } from "child_process"; + +function run(cmd: string) { + return spawnSync(cmd, { shell: true, encoding: "utf8" }); +} + +describe("e2e", () => { + process.chdir(__dirname); + + it.each(["node ../dist/cli.js", "yarn tsc --noEmit"])("%s", (cmd) => { + const result = run(cmd); + if (result.status !== 0) { + console.log(result.output); + } + expect(result.status).toEqual(0); + }); +}); diff --git a/e2e/nextjs-routes.d.ts b/e2e/nextjs-routes.d.ts new file mode 100644 index 0000000..3f17a7d --- /dev/null +++ b/e2e/nextjs-routes.d.ts @@ -0,0 +1,66 @@ +// THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.' +// Run `yarn nextjs-routes` to regenerate this file. +/* eslint-disable */ + +type Route = + | { pathname?: "/foos/[foo]"; query: Query<{ foo: string }> } + | { pathname?: "/"; query?: Query | undefined }; + +type Query = Params & { + [key: string]: string; +}; + +type Pathname = Exclude; + +type QueryForPathname = { + [K in Route as K["pathname"]]: Exclude; +}; + +declare module "next/link" { + import type { LinkProps as NextLinkProps } from "next/dist/client/link"; + import type { PropsWithChildren, MouseEventHandler } from "react"; + + export interface LinkProps extends Omit { + href: Route; + } + + declare function Link( + props: PropsWithChildren + ): DetailedReactHTMLElement< + { + onMouseEnter?: MouseEventHandler | undefined; + onClick: MouseEventHandler; + href?: string | undefined; + ref?: any; + }, + HTMLElement + >; + + export default Link; +} + +declare module "next/router" { + import type { NextRouter as Router } from "next/dist/client/router"; + export { RouterEvent } from "next/dist/client/router"; + + type TransitionOptions = Parameters[2]; + + export interface NextRouter

+ extends Omit { + pathname: P; + route: P; + query: QueryForPathname[P]; + push( + url: Route, + as?: string, + options?: TransitionOptions + ): Promise; + replace( + url: Route, + as?: string, + options?: TransitionOptions + ): Promise; + } + + export function useRouter

(): NextRouter

; +} diff --git a/e2e/package.json b/e2e/package.json new file mode 100644 index 0000000..5a23373 --- /dev/null +++ b/e2e/package.json @@ -0,0 +1,14 @@ +{ + "name": "e2e", + "version": "1.0.0", + "main": "index.js", + "license": "MIT", + "dependencies": { + "next": "*", + "react": "*", + "react-dom": "*" + }, + "devDependencies": { + "typescript": "*" + } +} diff --git a/e2e/pages/foos/[foo].ts b/e2e/pages/foos/[foo].ts new file mode 100644 index 0000000..cb0ff5c --- /dev/null +++ b/e2e/pages/foos/[foo].ts @@ -0,0 +1 @@ +export {}; diff --git a/e2e/pages/index.ts b/e2e/pages/index.ts new file mode 100644 index 0000000..cb0ff5c --- /dev/null +++ b/e2e/pages/index.ts @@ -0,0 +1 @@ +export {}; diff --git a/e2e/tsconfig.json b/e2e/tsconfig.json new file mode 100644 index 0000000..99710e8 --- /dev/null +++ b/e2e/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "target": "es5", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "incremental": true + }, + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], + "exclude": ["node_modules"] +} diff --git a/e2e/typetest.tsx b/e2e/typetest.tsx new file mode 100644 index 0000000..787c28d --- /dev/null +++ b/e2e/typetest.tsx @@ -0,0 +1,85 @@ +import Link from "next/link"; +import { useRouter } from "next/router"; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-empty-function +function expectType(_value: T) {} + +// next/link + +// Path without dynamic segments +; +; +; +; + +// Path with dynamic segments +; +// @ts-expect-error missing 'foo' in query +; +// @ts-expect-error missing 'foo' in query +; +// @ts-expect-error missing 'foo' in query +; +; + +// Only change query for current page +; +; + +// next/router +const router = useRouter(); + +// pathname +expectType<"/" | "/foos/[foo]">(router.pathname); + +// route +expectType<"/" | "/foos/[foo]">(router.route); + +// query +expectType<{ foo: string; [key: string]: string } | { [key: string]: string }>( + router.query +); + +// push + +// Path without dynamic segments +router.push({ pathname: "/" }); +router.push({ pathname: "/", query: undefined }); +router.push({ pathname: "/", query: {} }); +router.push({ pathname: "/", query: { bar: "baz" } }); + +// Path with dynamic segments +router.push({ pathname: "/foos/[foo]", query: { foo: "baz" } }); +// @ts-expect-error missing 'foo' in query +router.push({ pathname: "/foos/[foo]", query: { bar: "baz" } }); +// @ts-expect-error missing 'foo' in query +router.push({ pathname: "/foos/[foo]", query: undefined }); +// @ts-expect-error missing 'foo' in query +router.push({ pathname: "/foos/[foo]", query: {} }); +router.push({ pathname: "/foos/[foo]", query: { foo: "baz", bar: "baz" } }); + +// Only change query for current page +router.push({ query: { bar: "baz" } }); +router.push({ query: { foo: "foo" } }); + +// replace + +// Path without dynamic segments +router.replace({ pathname: "/" }); +router.replace({ pathname: "/", query: undefined }); +router.replace({ pathname: "/", query: {} }); +router.replace({ pathname: "/", query: { bar: "baz" } }); + +// Path with dynamic segments +router.replace({ pathname: "/foos/[foo]", query: { foo: "baz" } }); +// @ts-expect-error missing 'foo' in query +router.replace({ pathname: "/foos/[foo]", query: { bar: "baz" } }); +// @ts-expect-error missing 'foo' in query +router.replace({ pathname: "/foos/[foo]", query: undefined }); +// @ts-expect-error missing 'foo' in query +router.replace({ pathname: "/foos/[foo]", query: {} }); +router.replace({ pathname: "/foos/[foo]", query: { foo: "baz", bar: "baz" } }); + +// Only change query for current page +router.replace({ query: { bar: "baz" } }); +router.replace({ query: { foo: "foo" } }); diff --git a/e2e/yarn.lock b/e2e/yarn.lock new file mode 100644 index 0000000..73c94be --- /dev/null +++ b/e2e/yarn.lock @@ -0,0 +1,189 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@next/env@12.2.0": + version "12.2.0" + resolved "https://registry.yarnpkg.com/@next/env/-/env-12.2.0.tgz#17ce2d9f5532b677829840037e06f208b7eed66b" + integrity sha512-/FCkDpL/8SodJEXvx/DYNlOD5ijTtkozf4PPulYPtkPOJaMPpBSOkzmsta4fnrnbdH6eZjbwbiXFdr6gSQCV4w== + +"@next/swc-android-arm-eabi@12.2.0": + version "12.2.0" + resolved "https://registry.yarnpkg.com/@next/swc-android-arm-eabi/-/swc-android-arm-eabi-12.2.0.tgz#f116756e668b267de84b76f068d267a12f18eb22" + integrity sha512-hbneH8DNRB2x0Nf5fPCYoL8a0osvdTCe4pvOc9Rv5CpDsoOlf8BWBs2OWpeP0U2BktGvIsuUhmISmdYYGyrvTw== + +"@next/swc-android-arm64@12.2.0": + version "12.2.0" + resolved "https://registry.yarnpkg.com/@next/swc-android-arm64/-/swc-android-arm64-12.2.0.tgz#cbd9e329cef386271d4e746c08416b5d69342c24" + integrity sha512-1eEk91JHjczcJomxJ8X0XaUeNcp5Lx1U2Ic7j15ouJ83oRX+3GIslOuabW2oPkSgXbHkThMClhirKpvG98kwZg== + +"@next/swc-darwin-arm64@12.2.0": + version "12.2.0" + resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-12.2.0.tgz#3473889157ba70b30ccdd4f59c46232d841744e2" + integrity sha512-x5U5gJd7ZvrEtTFnBld9O2bUlX8opu7mIQUqRzj7KeWzBwPhrIzTTsQXAiNqsaMuaRPvyHBVW/5d/6g6+89Y8g== + +"@next/swc-darwin-x64@12.2.0": + version "12.2.0" + resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-12.2.0.tgz#b25198c3ef4c906000af49e4787a757965f760bb" + integrity sha512-iwMNFsrAPjfedjKDv9AXPAV16PWIomP3qw/FfPaxkDVRbUls7BNdofBLzkQmqxqWh93WrawLwaqyXpJuAaiwJA== + +"@next/swc-freebsd-x64@12.2.0": + version "12.2.0" + resolved "https://registry.yarnpkg.com/@next/swc-freebsd-x64/-/swc-freebsd-x64-12.2.0.tgz#78e2213f8b703be0fef23a49507779b4a9842929" + integrity sha512-gRiAw8g3Akf6niTDLEm1Emfa7jXDjvaAj/crDO8hKASKA4Y1fS4kbi/tyWw5VtoFI4mUzRmCPmZ8eL0tBSG58A== + +"@next/swc-linux-arm-gnueabihf@12.2.0": + version "12.2.0" + resolved "https://registry.yarnpkg.com/@next/swc-linux-arm-gnueabihf/-/swc-linux-arm-gnueabihf-12.2.0.tgz#80a4baf0ba699357e7420e2dea998908dcef5055" + integrity sha512-/TJZkxaIpeEwnXh6A40trgwd40C5+LJroLUOEQwMOJdavLl62PjCA6dGl1pgooWLCIb5YdBQ0EG4ylzvLwS2+Q== + +"@next/swc-linux-arm64-gnu@12.2.0": + version "12.2.0" + resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-12.2.0.tgz#134a42ddea804d6bf04761607f774432c3126de6" + integrity sha512-++WAB4ElXCSOKG9H8r4ENF8EaV+w0QkrpjehmryFkQXmt5juVXz+nKDVlCRMwJU7A1O0Mie82XyEoOrf6Np1pA== + +"@next/swc-linux-arm64-musl@12.2.0": + version "12.2.0" + resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-12.2.0.tgz#c781ac642ad35e0578d8a8d19c638b0f31c1a334" + integrity sha512-XrqkHi/VglEn5zs2CYK6ofJGQySrd+Lr4YdmfJ7IhsCnMKkQY1ma9Hv5THwhZVof3e+6oFHrQ9bWrw9K4WTjFA== + +"@next/swc-linux-x64-gnu@12.2.0": + version "12.2.0" + resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-12.2.0.tgz#0e2235a59429eadd40ac8880aec18acdbc172a31" + integrity sha512-MyhHbAKVjpn065WzRbqpLu2krj4kHLi6RITQdD1ee+uxq9r2yg5Qe02l24NxKW+1/lkmpusl4Y5Lks7rBiJn4w== + +"@next/swc-linux-x64-musl@12.2.0": + version "12.2.0" + resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-12.2.0.tgz#b0a10db0d9e16f079429588a58f71fa3c3d46178" + integrity sha512-Tz1tJZ5egE0S/UqCd5V6ZPJsdSzv/8aa7FkwFmIJ9neLS8/00za+OY5pq470iZQbPrkTwpKzmfTTIPRVD5iqDg== + +"@next/swc-win32-arm64-msvc@12.2.0": + version "12.2.0" + resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-12.2.0.tgz#3063f850c9db7b774c69e9be74ad59986cf6fc34" + integrity sha512-0iRO/CPMCdCYUzuH6wXLnsfJX1ykBX4emOOvH0qIgtiZM0nVYbF8lkEyY2ph4XcsurpinS+ziWuYCXVqrOSqiw== + +"@next/swc-win32-ia32-msvc@12.2.0": + version "12.2.0" + resolved "https://registry.yarnpkg.com/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-12.2.0.tgz#001bbadf3d2cf006c4991f728d1d23e4d5c0e7cc" + integrity sha512-8A26RJVcJHwIKm8xo/qk2ePRquJ6WCI2keV2qOW/Qm+ZXrPXHMIWPYABae/nKN243YFBNyPiHytjX37VrcpUhg== + +"@next/swc-win32-x64-msvc@12.2.0": + version "12.2.0" + resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-12.2.0.tgz#9f66664f9122ca555b96a5f2fc6e2af677bf801b" + integrity sha512-OI14ozFLThEV3ey6jE47zrzSTV/6eIMsvbwozo+XfdWqOPwQ7X00YkRx4GVMKMC0rM44oGS2gmwMKYpe4EblnA== + +"@swc/helpers@0.4.2": + version "0.4.2" + resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.4.2.tgz#ed1f6997ffbc22396665d9ba74e2a5c0a2d782f8" + integrity sha512-556Az0VX7WR6UdoTn4htt/l3zPQ7bsQWK+HqdG4swV7beUCxo/BqmvbOpUkTIm/9ih86LIf1qsUnywNL3obGHw== + dependencies: + tslib "^2.4.0" + +caniuse-lite@^1.0.30001332: + version "1.0.30001363" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001363.tgz#26bec2d606924ba318235944e1193304ea7c4f15" + integrity sha512-HpQhpzTGGPVMnCjIomjt+jvyUu8vNFo3TaDiZ/RcoTrlOq/5+tC8zHdsbgFB6MxmaY+jCpsH09aD80Bb4Ow3Sg== + +"js-tokens@^3.0.0 || ^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +loose-envify@^1.1.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" + integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== + dependencies: + js-tokens "^3.0.0 || ^4.0.0" + +nanoid@^3.1.30: + version "3.3.4" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.4.tgz#730b67e3cd09e2deacf03c027c81c9d9dbc5e8ab" + integrity sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw== + +next@*: + version "12.2.0" + resolved "https://registry.yarnpkg.com/next/-/next-12.2.0.tgz#aef47cd96b602bc1307d1dcf9a1ee3e753845544" + integrity sha512-B4j7D3SHYopLYx6/Ark0fenwIar9tEaZZFAaxmKjgcMMexhVJzB3jt7X+6wcdXPPMeUD6r09weUtnDpjox/vIA== + dependencies: + "@next/env" "12.2.0" + "@swc/helpers" "0.4.2" + caniuse-lite "^1.0.30001332" + postcss "8.4.5" + styled-jsx "5.0.2" + use-sync-external-store "1.1.0" + optionalDependencies: + "@next/swc-android-arm-eabi" "12.2.0" + "@next/swc-android-arm64" "12.2.0" + "@next/swc-darwin-arm64" "12.2.0" + "@next/swc-darwin-x64" "12.2.0" + "@next/swc-freebsd-x64" "12.2.0" + "@next/swc-linux-arm-gnueabihf" "12.2.0" + "@next/swc-linux-arm64-gnu" "12.2.0" + "@next/swc-linux-arm64-musl" "12.2.0" + "@next/swc-linux-x64-gnu" "12.2.0" + "@next/swc-linux-x64-musl" "12.2.0" + "@next/swc-win32-arm64-msvc" "12.2.0" + "@next/swc-win32-ia32-msvc" "12.2.0" + "@next/swc-win32-x64-msvc" "12.2.0" + +picocolors@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" + integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== + +postcss@8.4.5: + version "8.4.5" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.5.tgz#bae665764dfd4c6fcc24dc0fdf7e7aa00cc77f95" + integrity sha512-jBDboWM8qpaqwkMwItqTQTiFikhs/67OYVvblFFTM7MrZjt6yMKd6r2kgXizEbTTljacm4NldIlZnhbjr84QYg== + dependencies: + nanoid "^3.1.30" + picocolors "^1.0.0" + source-map-js "^1.0.1" + +react-dom@*: + version "18.2.0" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.2.0.tgz#22aaf38708db2674ed9ada224ca4aa708d821e3d" + integrity sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g== + dependencies: + loose-envify "^1.1.0" + scheduler "^0.23.0" + +react@*: + version "18.2.0" + resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5" + integrity sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ== + dependencies: + loose-envify "^1.1.0" + +scheduler@^0.23.0: + version "0.23.0" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.23.0.tgz#ba8041afc3d30eb206a487b6b384002e4e61fdfe" + integrity sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw== + dependencies: + loose-envify "^1.1.0" + +source-map-js@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" + integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== + +styled-jsx@5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/styled-jsx/-/styled-jsx-5.0.2.tgz#ff230fd593b737e9e68b630a694d460425478729" + integrity sha512-LqPQrbBh3egD57NBcHET4qcgshPks+yblyhPlH2GY8oaDgKs8SK4C3dBh3oSJjgzJ3G5t1SYEZGHkP+QEpX9EQ== + +tslib@^2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.0.tgz#7cecaa7f073ce680a05847aa77be941098f36dc3" + integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ== + +typescript@*: + version "4.7.4" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.7.4.tgz#1a88596d1cf47d59507a1bcdfb5b9dfe4d488235" + integrity sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ== + +use-sync-external-store@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.1.0.tgz#3343c3fe7f7e404db70f8c687adf5c1652d34e82" + integrity sha512-SEnieB2FPKEVne66NpXPd1Np4R1lTNKfjuy3XdIoPQKYBAFdzbzSZlSn1KJZUiihQLQC5Znot4SBz1EOTBwQAQ== diff --git a/package.json b/package.json index 04bb3e7..a806fe6 100644 --- a/package.json +++ b/package.json @@ -11,13 +11,14 @@ "build": "yarn tsc", "build:watch": "yarn tsc --watch", "clean": "rm -rf dist", + "e2e:setup": "yarn package:build && (cd e2e && yarn install)", "lint": "yarn typecheck && prettier --check . && prettier-package-json --list-different '{,public.,example/}package.json' && eslint .", "lint:fix": "prettier --write '**/*.{ts,tsx,md,json}' && prettier-package-json --write '{,public.,example/}package.json' && eslint --fix .", "package:build": "yarn install && yarn build && yarn package:prune && yarn package:copy:files && chmod +x dist/cli.js", "package:copy:files": "cp ./LICENSE ./README.md dist/ && cp ./public.package.json dist/package.json", "package:prune": "find dist -name test.* | xargs rm -f", "prepare": "husky install", - "test": "yarn jest", + "test": "yarn e2e:setup && yarn jest", "test:ci": "yarn test --coverage", "typecheck": "yarn tsc --noEmit" }, @@ -26,10 +27,10 @@ "@babel/preset-env": "^7.18.6", "@babel/preset-typescript": "^7.18.6", "@types/jest": "^28.1.4", - "@types/node": "^18.0.1", - "@types/react": "^18.0.14", - "@typescript-eslint/eslint-plugin": "^5.30.4", - "@typescript-eslint/parser": "^5.30.4", + "@types/node": "^18.0.3", + "@types/react": "^18.0.15", + "@typescript-eslint/eslint-plugin": "^5.30.5", + "@typescript-eslint/parser": "^5.30.5", "babel-loader": "^8.2.5", "codecov": "^3.8.3", "eslint": "^8.19.0", diff --git a/public.package.json b/public.package.json index 1f7dabd..6a37db2 100644 --- a/public.package.json +++ b/public.package.json @@ -1,6 +1,6 @@ { "name": "nextjs-routes", - "version": "0.0.13", + "version": "0.0.14", "description": "Type safe routing for Next.js", "license": "MIT", "author": "Tate ", @@ -15,8 +15,7 @@ "sideEffects": false, "types": "index.d.ts", "peerDependencies": { - "next": "*", - "prettier": "^2.1.1" + "next": "*" }, "keywords": [ "link", @@ -27,8 +26,6 @@ "typescript" ], "exports": { - "./package.json": "./package.json", - "./router": "./router.js", - "./link": "./link.js" + "./package.json": "./package.json" } } diff --git a/src/__snapshots__/test.ts.snap b/src/__snapshots__/test.ts.snap index 187667c..f046d94 100644 --- a/src/__snapshots__/test.ts.snap +++ b/src/__snapshots__/test.ts.snap @@ -3,45 +3,44 @@ exports[`route generation typescript 1`] = ` "// THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.' // Run \`yarn nextjs-routes\` to regenerate this file. +/* eslint-disable */ type Route = - | { pathname: '/404'; query?: Query | undefined } - | { pathname: '/[foo]'; query: Query<{ foo: string; }> } - | { pathname: '/[foo]/[bar]/[baz]'; query: Query<{ foo: string; bar: string; baz: string; }> } - | { pathname: '/[foo]/bar/[baz]'; query: Query<{ foo: string; baz: string; }> } - | { pathname: '/[foo]/bar/[baz]/foo/[bar]'; query: Query<{ foo: string; baz: string; bar: string; }> } - | { pathname: '/[foo]/baz'; query: Query<{ foo: string; }> } - | { pathname: '/_debug/health-check'; query?: Query | undefined } - | { pathname: '/_error'; query?: Query | undefined } - | { pathname: '/api/[[...segments]]'; query?: Query | undefined } - | { pathname: '/api/[...segments]'; query: Query<{ segments: string[]; }> } - | { pathname: '/api/bar'; query?: Query | undefined } - | { pathname: '/foo/[slug]'; query: Query<{ slug: string; }> } - | { pathname: '/'; query?: Query | undefined } - | { pathname: '/not-found'; query?: Query | undefined } - | { pathname: '/settings/bars/[bar]'; query: Query<{ bar: string; }> } - | { pathname: '/settings/bars/[bar]/baz'; query: Query<{ bar: string; }> } - | { pathname: '/settings/foo'; query?: Query | undefined } - | { pathname: '/settings'; query?: Query | undefined } - -type Pathname = Route[\\"pathname\\"]; + | { pathname?: '/404'; query?: Query | undefined } + | { pathname?: '/[foo]'; query: Query<{ foo: string; }> } + | { pathname?: '/[foo]/[bar]/[baz]'; query: Query<{ foo: string;bar: string;baz: string; }> } + | { pathname?: '/[foo]/bar/[baz]'; query: Query<{ foo: string;baz: string; }> } + | { pathname?: '/[foo]/bar/[baz]/foo/[bar]'; query: Query<{ foo: string;baz: string;bar: string; }> } + | { pathname?: '/[foo]/baz'; query: Query<{ foo: string; }> } + | { pathname?: '/_debug/health-check'; query?: Query | undefined } + | { pathname?: '/_error'; query?: Query | undefined } + | { pathname?: '/api/[[...segments]]'; query?: Query | undefined } + | { pathname?: '/api/[...segments]'; query: Query<{ segments: string[]; }> } + | { pathname?: '/api/bar'; query?: Query | undefined } + | { pathname?: '/foo/[slug]'; query: Query<{ slug: string; }> } + | { pathname?: '/'; query?: Query | undefined } + | { pathname?: '/not-found'; query?: Query | undefined } + | { pathname?: '/settings/bars/[bar]'; query: Query<{ bar: string; }> } + | { pathname?: '/settings/bars/[bar]/baz'; query: Query<{ bar: string; }> } + | { pathname?: '/settings/foo'; query?: Query | undefined } + | { pathname?: '/settings'; query?: Query | undefined } type Query = Params & { [key: string]: string; } +type Pathname = Exclude; + type QueryForPathname = { - [K in Route as K[\\"pathname\\"]]: K[\\"query\\"] + [K in Route as K[\\"pathname\\"]]: Exclude; }; -type RouteOrQuery = Route | { query: Query } - declare module \\"next/link\\" { import type { LinkProps as NextLinkProps } from \\"next/dist/client/link\\"; import type { PropsWithChildren, MouseEventHandler } from \\"react\\"; export interface LinkProps extends Omit { - href: RouteOrQuery; + href: Route; } declare function Link( @@ -68,10 +67,10 @@ declare module \\"next/router\\" { export interface NextRouter

extends Omit { pathname: P; route: P; - query: Exclude - push(url: RouteOrQuery, as?: string, options?: TransitionOptions): Promise; + query: QueryForPathname[P]; + push(url: Route, as?: string, options?: TransitionOptions): Promise; replace( - url: RouteOrQuery, + url: Route, as?: string, options?: TransitionOptions ): Promise; diff --git a/src/utils.ts b/src/utils.ts index efd4305..0830f5b 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -103,7 +103,7 @@ function getQueryInterface( return _exhaust; } } - res += "; "; + res += ";"; }); res += " }"; return [res, requiredKeys]; @@ -113,37 +113,36 @@ export function generate(routes: Route[]): string { return `\ // THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.' // Run \`yarn nextjs-routes\` to regenerate this file. +/* eslint-disable */ type Route = | ${routes .map((route) => { const [query, requiredKeys] = getQueryInterface(route.query); if (requiredKeys > 0) { - return `{ pathname: '${route.pathname}'; query: Query<${query}> }`; + return `{ pathname?: '${route.pathname}'; query: Query<${query}> }`; } else { - return `{ pathname: '${route.pathname}'; query?: Query | undefined }`; + return `{ pathname?: '${route.pathname}'; query?: Query | undefined }`; } }) .join("\n | ")} -type Pathname = Route["pathname"]; - type Query = Params & { [key: string]: string; } +type Pathname = Exclude; + type QueryForPathname = { - [K in Route as K["pathname"]]: K["query"] + [K in Route as K["pathname"]]: Exclude; }; -type RouteOrQuery = Route | { query: Query } - declare module "next/link" { import type { LinkProps as NextLinkProps } from "next/dist/client/link"; import type { PropsWithChildren, MouseEventHandler } from "react"; export interface LinkProps extends Omit { - href: RouteOrQuery; + href: Route; } declare function Link( @@ -170,10 +169,10 @@ declare module "next/router" { export interface NextRouter

extends Omit { pathname: P; route: P; - query: Exclude - push(url: RouteOrQuery, as?: string, options?: TransitionOptions): Promise; + query: QueryForPathname[P]; + push(url: Route, as?: string, options?: TransitionOptions): Promise; replace( - url: RouteOrQuery, + url: Route, as?: string, options?: TransitionOptions ): Promise; diff --git a/yarn.lock b/yarn.lock index 7b26cee..d703f22 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1198,9 +1198,9 @@ "@jridgewell/trace-mapping" "^0.3.9" "@jridgewell/resolve-uri@^3.0.3": - version "3.0.8" - resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.0.8.tgz#687cc2bbf243f4e9a868ecf2262318e2658873a1" - integrity sha512-YK5G9LaddzGbcucK4c8h5tWFmMPBvRZ/uyWmN1/SbBdIvqGUdWGkJ5BAaccgs6XbzVLsqbPJrBSFwKv3kT9i7w== + version "3.1.0" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78" + integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w== "@jridgewell/set-array@^1.0.0", "@jridgewell/set-array@^1.0.1": version "1.1.2" @@ -1414,10 +1414,10 @@ resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3" integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ== -"@types/node@*", "@types/node@^18.0.1": - version "18.0.1" - resolved "https://registry.yarnpkg.com/@types/node/-/node-18.0.1.tgz#e91bd73239b338557a84d1f67f7b9e0f25643870" - integrity sha512-CmR8+Tsy95hhwtZBKJBs0/FFq4XX7sDZHlGGf+0q+BRZfMbOTkzkj0AFAuTyXbObDIoanaBBW0+KEW+m3N16Wg== +"@types/node@*", "@types/node@^18.0.3": + version "18.0.3" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.0.3.tgz#463fc47f13ec0688a33aec75d078a0541a447199" + integrity sha512-HzNRZtp4eepNitP+BD6k2L6DROIDG4Q0fm4x+dwfsr6LGmROENnok75VGw40628xf+iR24WeMFcHuuBDUAzzsQ== "@types/parse-author@^2.0.0": version "2.0.1" @@ -1439,10 +1439,10 @@ resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.5.tgz#5f19d2b85a98e9558036f6a3cacc8819420f05cf" integrity sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w== -"@types/react@^18.0.14": - version "18.0.14" - resolved "https://registry.yarnpkg.com/@types/react/-/react-18.0.14.tgz#e016616ffff51dba01b04945610fe3671fdbe06d" - integrity sha512-x4gGuASSiWmo0xjDLpm5mPb52syZHJx02VKbqUKdLmKtAwIh63XClGsiTI1K6DO5q7ox4xAsQrU+Gl3+gGXF9Q== +"@types/react@^18.0.15": + version "18.0.15" + resolved "https://registry.yarnpkg.com/@types/react/-/react-18.0.15.tgz#d355644c26832dc27f3e6cbf0c4f4603fc4ab7fe" + integrity sha512-iz3BtLuIYH1uWdsv6wXYdhozhqj20oD4/Hk2DNXIn1kFsmp9x8d9QB6FnPhfkbhd2PgEONt9Q1x/ebkwjfFLow== dependencies: "@types/prop-types" "*" "@types/scheduler" "*" @@ -1470,14 +1470,14 @@ dependencies: "@types/yargs-parser" "*" -"@typescript-eslint/eslint-plugin@^5.30.4": - version "5.30.4" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.30.4.tgz#a46c8c0ab755a936cb63786a6222876ce51675e4" - integrity sha512-xjujQISAIa4HAaos8fcMZXmqkuZqMx6icdxkI88jMM/eNe4J8AuTLYnLK+zdm0mBYLyctdFf//UE4/xFCcQzYQ== +"@typescript-eslint/eslint-plugin@^5.30.5": + version "5.30.5" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.30.5.tgz#e9a0afd6eb3b1d663db91cf1e7bc7584d394503d" + integrity sha512-lftkqRoBvc28VFXEoRgyZuztyVUQ04JvUnATSPtIRFAccbXTWL6DEtXGYMcbg998kXw1NLUJm7rTQ9eUt+q6Ig== dependencies: - "@typescript-eslint/scope-manager" "5.30.4" - "@typescript-eslint/type-utils" "5.30.4" - "@typescript-eslint/utils" "5.30.4" + "@typescript-eslint/scope-manager" "5.30.5" + "@typescript-eslint/type-utils" "5.30.5" + "@typescript-eslint/utils" "5.30.5" debug "^4.3.4" functional-red-black-tree "^1.0.1" ignore "^5.2.0" @@ -1485,69 +1485,69 @@ semver "^7.3.7" tsutils "^3.21.0" -"@typescript-eslint/parser@^5.30.4": - version "5.30.4" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.30.4.tgz#659411e8700b22c8d5400798ef24838425bf4567" - integrity sha512-/ge1HtU63wVoED4VnlU2o+FPFmi017bPYpeSrCmd8Ycsti4VSxXrmcpXXm7JpI4GT0Aa7qviabv1PEp6L5bboQ== +"@typescript-eslint/parser@^5.30.5": + version "5.30.5" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.30.5.tgz#f667c34e4e4c299d98281246c9b1e68c03a92522" + integrity sha512-zj251pcPXI8GO9NDKWWmygP6+UjwWmrdf9qMW/L/uQJBM/0XbU2inxe5io/234y/RCvwpKEYjZ6c1YrXERkK4Q== dependencies: - "@typescript-eslint/scope-manager" "5.30.4" - "@typescript-eslint/types" "5.30.4" - "@typescript-eslint/typescript-estree" "5.30.4" + "@typescript-eslint/scope-manager" "5.30.5" + "@typescript-eslint/types" "5.30.5" + "@typescript-eslint/typescript-estree" "5.30.5" debug "^4.3.4" -"@typescript-eslint/scope-manager@5.30.4": - version "5.30.4" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.30.4.tgz#8140efd2bc12d41d74e8af23872a89f3edbe552e" - integrity sha512-DNzlQwGSiGefz71JwaHrpcaAX3zYkEcy8uVuan3YMKOa6qeW/y+7SaD8KIsIAruASwq6P+U4BjWBWtM2O+mwBQ== +"@typescript-eslint/scope-manager@5.30.5": + version "5.30.5" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.30.5.tgz#7f90b9d6800552c856a5f3644f5e55dd1469d964" + integrity sha512-NJ6F+YHHFT/30isRe2UTmIGGAiXKckCyMnIV58cE3JkHmaD6e5zyEYm5hBDv0Wbin+IC0T1FWJpD3YqHUG/Ydg== dependencies: - "@typescript-eslint/types" "5.30.4" - "@typescript-eslint/visitor-keys" "5.30.4" + "@typescript-eslint/types" "5.30.5" + "@typescript-eslint/visitor-keys" "5.30.5" -"@typescript-eslint/type-utils@5.30.4": - version "5.30.4" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.30.4.tgz#00ff19073cd01f7d27e9af49ce08d6a69f1e4f01" - integrity sha512-55cf1dZviwwv+unDB+mF8vZkfta5muTK6bppPvenWWCD7slZZ0DEsXUjZerqy7Rq8s3J4SXdg4rMIY8ngCtTmA== +"@typescript-eslint/type-utils@5.30.5": + version "5.30.5" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.30.5.tgz#7a9656f360b4b1daea635c4621dab053d08bf8a9" + integrity sha512-k9+ejlv1GgwN1nN7XjVtyCgE0BTzhzT1YsQF0rv4Vfj2U9xnslBgMYYvcEYAFVdvhuEscELJsB7lDkN7WusErw== dependencies: - "@typescript-eslint/utils" "5.30.4" + "@typescript-eslint/utils" "5.30.5" debug "^4.3.4" tsutils "^3.21.0" -"@typescript-eslint/types@5.30.4": - version "5.30.4" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.30.4.tgz#3bc99eca8ba3fcfd6a21480e216b09dab81c3999" - integrity sha512-NTEvqc+Vvu8Q6JeAKryHk2eqLKqsr2St3xhIjhOjQv5wQUBhaTuix4WOSacqj0ONWfKVU12Eug3LEAB95GBkMA== +"@typescript-eslint/types@5.30.5": + version "5.30.5" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.30.5.tgz#36a0c05a72af3623cdf9ee8b81ea743b7de75a98" + integrity sha512-kZ80w/M2AvsbRvOr3PjaNh6qEW1LFqs2pLdo2s5R38B2HYXG8Z0PP48/4+j1QHJFL3ssHIbJ4odPRS8PlHrFfw== -"@typescript-eslint/typescript-estree@5.30.4": - version "5.30.4" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.30.4.tgz#ac4be8a2f8fb1f1c3b346d5992a36163121ddb3f" - integrity sha512-V4VnEs6/J9/nNizaA12IeU4SAeEYaiKr7XndLNfV5+3zZSB4hIu6EhHJixTKhvIqA+EEHgBl6re8pivBMLLO1w== +"@typescript-eslint/typescript-estree@5.30.5": + version "5.30.5" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.30.5.tgz#c520e4eba20551c4ec76af8d344a42eb6c9767bb" + integrity sha512-qGTc7QZC801kbYjAr4AgdOfnokpwStqyhSbiQvqGBLixniAKyH+ib2qXIVo4P9NgGzwyfD9I0nlJN7D91E1VpQ== dependencies: - "@typescript-eslint/types" "5.30.4" - "@typescript-eslint/visitor-keys" "5.30.4" + "@typescript-eslint/types" "5.30.5" + "@typescript-eslint/visitor-keys" "5.30.5" debug "^4.3.4" globby "^11.1.0" is-glob "^4.0.3" semver "^7.3.7" tsutils "^3.21.0" -"@typescript-eslint/utils@5.30.4": - version "5.30.4" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.30.4.tgz#07a2b7ce80b2527ea506829f190591b76c70ba9f" - integrity sha512-a+GQrJzOUhn4WT1mUumXDyam+22Oo4c5K/jnZ+6r/4WTQF3q8e4CsC9PLHb4SnOClzOqo/5GLZWvkE1aa5UGKQ== +"@typescript-eslint/utils@5.30.5": + version "5.30.5" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.30.5.tgz#3999cbd06baad31b9e60d084f20714d1b2776765" + integrity sha512-o4SSUH9IkuA7AYIfAvatldovurqTAHrfzPApOZvdUq01hHojZojCFXx06D/aFpKCgWbMPRdJBWAC3sWp3itwTA== dependencies: "@types/json-schema" "^7.0.9" - "@typescript-eslint/scope-manager" "5.30.4" - "@typescript-eslint/types" "5.30.4" - "@typescript-eslint/typescript-estree" "5.30.4" + "@typescript-eslint/scope-manager" "5.30.5" + "@typescript-eslint/types" "5.30.5" + "@typescript-eslint/typescript-estree" "5.30.5" eslint-scope "^5.1.1" eslint-utils "^3.0.0" -"@typescript-eslint/visitor-keys@5.30.4": - version "5.30.4" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.30.4.tgz#b4969df1a440cc999d4bb7f7b7932dce05537089" - integrity sha512-ulKGse3mruSc8x6l8ORSc6+1ORyJzKmZeIaRTu/WpaF/jx3vHvEn5XZUKF9XaVg2710mFmTAUlLcLYLPp/Zf/Q== +"@typescript-eslint/visitor-keys@5.30.5": + version "5.30.5" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.30.5.tgz#d4bb969202019d5d5d849a0aaedc7370cc044b14" + integrity sha512-D+xtGo9HUMELzWIUqcQc0p2PO4NyvTrgIOK/VnSH083+8sq0tiLozNRKuLarwHYGRuA6TVBQSuuLwJUDWd3aaA== dependencies: - "@typescript-eslint/types" "5.30.4" + "@typescript-eslint/types" "5.30.5" eslint-visitor-keys "^3.3.0" acorn-jsx@^5.3.2: @@ -2020,9 +2020,9 @@ doctrine@^3.0.0: esutils "^2.0.2" electron-to-chromium@^1.4.172: - version "1.4.177" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.177.tgz#b6a4436eb788ca732556cd69f384b8a3c82118c5" - integrity sha512-FYPir3NSBEGexSZUEeht81oVhHfLFl6mhUKSkjHN/iB/TwEIt/WHQrqVGfTLN5gQxwJCQkIJBe05eOXjI7omgg== + version "1.4.181" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.181.tgz#4568379c1493ee95342b13bb2279cf4d207f2c3a" + integrity sha512-T6JzLj+9J6CdPgI7jXLcq5Ao2qyFhEBfWFUExxRRVFdVYAU86SfcLX3Zw6p1HzvngR+M8C4q6eDkQ5flEr1RnA== emittery@^0.10.2: version "0.10.2" @@ -2400,9 +2400,9 @@ globals@^11.1.0: integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== globals@^13.15.0: - version "13.15.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-13.15.0.tgz#38113218c907d2f7e98658af246cef8b77e90bac" - integrity sha512-bpzcOlgDhMG070Av0Vy5Owklpv1I6+j96GhUI7Rh7IzDCKLzboflLrrfqMu8NquDbiR4EOQk7XzJwqVJxicxog== + version "13.16.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-13.16.0.tgz#9be4aca28f311aaeb974ea54978ebbb5e35ce46a" + integrity sha512-A1lrQfpNF+McdPOnnFqY3kSN0AFTy485bTi1bkLk4mVPODIUEcSfhHgRqA+QdXPksrSTTztYXx37NFV+GpGk3Q== dependencies: type-fest "^0.20.2"