Repository: tatethurston/nextjs-routes
Pull request #189 (2024-06-16 00:07:21)

fix app route generation on windows

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/tatethurston/nextjs-routes/pull/189?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Tate+Thurston) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 97.77%. Comparing base [(`49e3ae3`)](https://app.codecov.io/gh/tatethurston/nextjs-routes/commit/49e3ae3371a6594d2abcffd96af32654849c3502?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Tate+Thurston) to head [(`669be55`)](https://app.codecov.io/gh/tatethurston/nextjs-routes/commit/669be55c49a5713924ea3ae3f480aef8b2dfefd0?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Tate+Thurston).
> Report is 1 commits behind head on main.


<details><summary>Additional details and impacted files</summary>


```diff
@@           Coverage Diff           @@
##             main     #189   +/-   ##
=======================================
  Coverage   97.77%   97.77%           
=======================================
  Files           3        3           
  Lines         135      135           
  Branches       42       42           
=======================================
  Hits          132      132           
  Misses          3        3           
```



</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/tatethurston/nextjs-routes/pull/189?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Tate+Thurston).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Tate+Thurston).


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ec703b2..14cd1b4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,9 @@
 # Changelog
 
+## 2.2.1
+
+- Fix route generation on Windows. See [#187](https://github.com/tatethurston/nextjs-routes/issues/187). Thanks @AkanoCA!
+
 ## 2.2.0
 
 - Add `trailingSlash` option to `route`. See [#168](https://github.com/tatethurston/nextjs-routes/issues/168)
diff --git a/examples/app/@types/nextjs-routes.d.ts b/examples/app/@types/nextjs-routes.d.ts
index e858c6b..377030a 100644
--- a/examples/app/@types/nextjs-routes.d.ts
+++ b/examples/app/@types/nextjs-routes.d.ts
@@ -1,6 +1,6 @@
 // THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
 // This file will be automatically regenerated when your Next.js server is running.
-// nextjs-routes version: 2.2.0
+// nextjs-routes version: 2.2.1
 /* eslint-disable */
 
 // prettier-ignore
diff --git a/examples/cjs/types/nextjs-routes.d.ts b/examples/cjs/types/nextjs-routes.d.ts
index bfb88f1..ea309bc 100644
--- a/examples/cjs/types/nextjs-routes.d.ts
+++ b/examples/cjs/types/nextjs-routes.d.ts
@@ -1,6 +1,6 @@
 // THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
 // This file will be automatically regenerated when your Next.js server is running.
-// nextjs-routes version: 2.2.0
+// nextjs-routes version: 2.2.1
 /* eslint-disable */
 
 // prettier-ignore
diff --git a/examples/intl/@types/nextjs-routes.d.ts b/examples/intl/@types/nextjs-routes.d.ts
index 9386071..6348fc5 100644
--- a/examples/intl/@types/nextjs-routes.d.ts
+++ b/examples/intl/@types/nextjs-routes.d.ts
@@ -1,6 +1,6 @@
 // THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
 // This file will be automatically regenerated when your Next.js server is running.
-// nextjs-routes version: 2.2.0
+// nextjs-routes version: 2.2.1
 /* eslint-disable */
 
 // prettier-ignore
diff --git a/examples/typescript/types/nextjs-routes.d.ts b/examples/typescript/types/nextjs-routes.d.ts
index a51eed6..848ef54 100644
--- a/examples/typescript/types/nextjs-routes.d.ts
+++ b/examples/typescript/types/nextjs-routes.d.ts
@@ -1,6 +1,6 @@
 // THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
 // This file will be automatically regenerated when your Next.js server is running.
-// nextjs-routes version: 2.2.0
+// nextjs-routes version: 2.2.1
 /* eslint-disable */
 
 // prettier-ignore
diff --git a/packages/nextjs-routes/package.json b/packages/nextjs-routes/package.json
index 0608c6d..e3d0037 100644
--- a/packages/nextjs-routes/package.json
+++ b/packages/nextjs-routes/package.json
@@ -1,6 +1,6 @@
 {
   "name": "nextjs-routes",
-  "version": "2.2.0",
+  "version": "2.2.1",
   "description": "Type safe routing for Next.js",
   "license": "MIT",
   "author": "Tate <tatethurston@gmail.com>",
diff --git a/packages/nextjs-routes/src/core.ts b/packages/nextjs-routes/src/core.ts
index 01e7363..010f9c6 100644
--- a/packages/nextjs-routes/src/core.ts
+++ b/packages/nextjs-routes/src/core.ts
@@ -7,7 +7,7 @@ import { findFiles, getAppDirectory, getPagesDirectory } from "./utils.js";
 // by node 17+
 // import pkg from "../package.json" assert { type: "json" };
 const pkg = {
-  version: "2.2.0",
+  version: "2.2.1",
 };
 
 type QueryType = "dynamic" | "catch-all" | "optional-catch-all";
@@ -363,14 +363,14 @@ export function getAppRoutes(files: string[], opts: Opts): string[] {
       .map((file) =>
         // transform filepath to url path
         file
-          .split('/')
+          .split("/")
           // remove named groups
           .filter(
             (segment) => !(segment.startsWith("(") && segment.endsWith(")")),
           )
           // remove page
           .filter((file) => !APP_DIRECTORY_ROUTABLE.includes(parse(file).name))
-          .join('/'),
+          .join("/"),
       )
       // handle index page
       .map((file) => (file === "" ? "/" : file))


Test patch:
diff --git a/packages/e2e/@types/nextjs-routes.d.ts b/packages/e2e/@types/nextjs-routes.d.ts
index 0b5f3a2..81cafed 100644
--- a/packages/e2e/@types/nextjs-routes.d.ts
+++ b/packages/e2e/@types/nextjs-routes.d.ts
@@ -1,6 +1,6 @@
 // THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
 // This file will be automatically regenerated when your Next.js server is running.
-// nextjs-routes version: 2.2.0
+// nextjs-routes version: 2.2.1
 /* eslint-disable */
 
 // prettier-ignore
diff --git a/packages/nextjs-routes/src/__snapshots__/core.test.ts.snap b/packages/nextjs-routes/src/__snapshots__/core.test.ts.snap
index 1cb1322..423472a 100644
--- a/packages/nextjs-routes/src/__snapshots__/core.test.ts.snap
+++ b/packages/nextjs-routes/src/__snapshots__/core.test.ts.snap
@@ -1,12 +1,12 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
-exports[`route generation app directory (experimental) 1`] = `
+exports[`route generation app directory (experimental) generates routes 1`] = `
 [
   [
     "@types/nextjs-routes.d.ts",
     "// THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
 // This file will be automatically regenerated when your Next.js server is running.
-// nextjs-routes version: 2.2.0
+// nextjs-routes version: 2.2.1
 /* eslint-disable */
 
 // prettier-ignore
@@ -184,13 +184,191 @@ declare module "next/router" {
 ]
 `;
 
+exports[`route generation app directory (experimental) handles windows paths 1`] = `
+[
+  [
+    "@types/nextjs-routes.d.ts",
+    "// THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
+// This file will be automatically regenerated when your Next.js server is running.
+// nextjs-routes version: 2.2.1
+/* eslint-disable */
+
+// prettier-ignore
+declare module "nextjs-routes" {
+  import type {
+    GetServerSidePropsContext as NextGetServerSidePropsContext,
+    GetServerSidePropsResult as NextGetServerSidePropsResult
+  } from "next";
+
+  export type Route =
+    | DynamicRoute<"/[foo]/bar", { "foo": string }>;
+
+  interface StaticRoute<Pathname> {
+    pathname: Pathname;
+    query?: Query | undefined;
+    hash?: string | null | undefined;
+  }
+
+  interface DynamicRoute<Pathname, Parameters> {
+    pathname: Pathname;
+    query: Parameters & Query;
+    hash?: string | null | undefined;
+  }
+
+  interface Query {
+    [key: string]: string | string[] | undefined;
+  };
+
+  export type RoutedQuery<P extends Route["pathname"]> = Extract<
+    Route,
+    { pathname: P }
+  >["query"];
+
+  export type Locale = undefined;
+
+  /**
+   * A typesafe utility function for generating paths in your application.
+   *
+   * route({ pathname: "/foos/[foo]", query: { foo: "bar" }}) will produce "/foos/bar".
+   */
+  export declare function route(r: Route): string;
+
+  /**
+   * Nearly identical to GetServerSidePropsContext from next, but further narrows
+   * types based on nextjs-route's route data.
+   */
+  export type GetServerSidePropsContext<
+    Pathname extends Route["pathname"] = Route["pathname"],
+    Preview extends NextGetServerSidePropsContext["previewData"] = NextGetServerSidePropsContext["previewData"]
+  > = Omit<NextGetServerSidePropsContext, 'params' | 'query' | 'defaultLocale' | 'locale' | 'locales'> & {
+    params: Extract<Route, { pathname: Pathname }>["query"];
+    query: Query;
+    defaultLocale?: undefined;
+    locale?: Locale;
+    locales?: undefined;
+  };
+
+  /**
+   * Nearly identical to GetServerSideProps from next, but further narrows
+   * types based on nextjs-route's route data.
+   */
+  export type GetServerSideProps<
+    Props extends { [key: string]: any } = { [key: string]: any },
+    Pathname extends Route["pathname"] = Route["pathname"],
+    Preview extends NextGetServerSideProps["previewData"] = NextGetServerSideProps["previewData"]
+  > = (
+    context: GetServerSidePropsContext<Pathname, Preview>
+  ) => Promise<NextGetServerSidePropsResult<Props>>
+}
+
+// prettier-ignore
+declare module "next/link" {
+  import type { Route } from "nextjs-routes";
+  import type { LinkProps as NextLinkProps } from "next/dist/client/link";
+  import type {
+    AnchorHTMLAttributes,
+    DetailedReactHTMLElement,
+    MouseEventHandler,
+    PropsWithChildren,
+  } from "react";
+  export * from "next/dist/client/link";
+
+  type StaticRoute = Exclude<Route, { query: any }>["pathname"];
+
+  export interface LinkProps
+    extends Omit<NextLinkProps, "href" | "locale">,
+      AnchorHTMLAttributes<HTMLAnchorElement> {
+    href: Route | StaticRoute | Omit<Route, "pathname">
+    locale?: false;
+  }
+
+  type LinkReactElement = DetailedReactHTMLElement<
+    {
+      onMouseEnter?: MouseEventHandler<Element> | undefined;
+      onClick: MouseEventHandler;
+      href?: string | undefined;
+      ref?: any;
+    },
+    HTMLElement
+  >;
+
+  declare function Link(props: PropsWithChildren<LinkProps>): LinkReactElement;
+
+  export default Link;
+}
+
+// prettier-ignore
+declare module "next/router" {
+  import type { Locale, Route, RoutedQuery } from "nextjs-routes";
+  import type { NextRouter as Router } from "next/dist/client/router";
+  export * from "next/dist/client/router";
+  export { default } from "next/dist/client/router";
+
+  type NextTransitionOptions = NonNullable<Parameters<Router["push"]>[2]>;
+  type StaticRoute = Exclude<Route, { query: any }>["pathname"];
+
+  interface TransitionOptions extends Omit<NextTransitionOptions, "locale"> {
+    locale?: false;
+  }
+
+  type PathnameAndQuery<Pathname> = Required<
+    Pick<Extract<Route, { pathname: Pathname }>, "pathname" | "query">
+  >;
+
+  type AutomaticStaticOptimizedQuery<PaQ> = Omit<PaQ, "query"> & {
+    query: Partial<PaQ["query"]>;
+  };
+
+  type BaseRouter<PaQ> =
+    | ({ isReady: false } & AutomaticStaticOptimizedQuery<PaQ>)
+    | ({ isReady: true } & PaQ);
+
+  export type NextRouter<P extends Route["pathname"] = Route["pathname"]> =
+    BaseRouter<PathnameAndQuery<P>> &
+      Omit<
+        Router,
+        | "defaultLocale"
+        | "domainLocales"
+        | "isReady"
+        | "locale"
+        | "locales"
+        | "pathname"
+        | "push"
+        | "query"
+        | "replace"
+        | "route"
+      > & {
+        defaultLocale?: undefined;
+        domainLocales?: undefined;
+        locale?: Locale;
+        locales?: undefined;
+        push(
+          url: Route | StaticRoute | Omit<Route, "pathname">,
+          as?: string,
+          options?: TransitionOptions
+        ): Promise<boolean>;
+        replace(
+          url: Route | StaticRoute | Omit<Route, "pathname">,
+          as?: string,
+          options?: TransitionOptions
+        ): Promise<boolean>;
+        route: P;
+      };
+
+  export function useRouter<P extends Route["pathname"]>(): NextRouter<P>;
+}
+",
+  ],
+]
+`;
+
 exports[`route generation configuration i18n 1`] = `
 [
   [
     "@types/nextjs-routes.d.ts",
     "// THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
 // This file will be automatically regenerated when your Next.js server is running.
-// nextjs-routes version: 2.2.0
+// nextjs-routes version: 2.2.1
 /* eslint-disable */
 
 // prettier-ignore
@@ -392,7 +570,7 @@ exports[`route generation configuration outDir 1`] = `
     "src/nextjs-routes.d.ts",
     "// THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
 // This file will be automatically regenerated when your Next.js server is running.
-// nextjs-routes version: 2.2.0
+// nextjs-routes version: 2.2.1
 /* eslint-disable */
 
 // prettier-ignore
@@ -570,7 +748,7 @@ exports[`route generation configuration pageExtensions configured 1`] = `
     "@types/nextjs-routes.d.ts",
     "// THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
 // This file will be automatically regenerated when your Next.js server is running.
-// nextjs-routes version: 2.2.0
+// nextjs-routes version: 2.2.1
 /* eslint-disable */
 
 // prettier-ignore
@@ -750,7 +928,7 @@ exports[`route generation configuration pageExtensions default 1`] = `
     "@types/nextjs-routes.d.ts",
     "// THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
 // This file will be automatically regenerated when your Next.js server is running.
-// nextjs-routes version: 2.2.0
+// nextjs-routes version: 2.2.1
 /* eslint-disable */
 
 // prettier-ignore
@@ -928,7 +1106,7 @@ exports[`route generation dedupes 1`] = `
     "@types/nextjs-routes.d.ts",
     "// THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
 // This file will be automatically regenerated when your Next.js server is running.
-// nextjs-routes version: 2.2.0
+// nextjs-routes version: 2.2.1
 /* eslint-disable */
 
 // prettier-ignore
@@ -1106,7 +1284,7 @@ exports[`route generation no routes 1`] = `
     "@types/nextjs-routes.d.ts",
     "// THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
 // This file will be automatically regenerated when your Next.js server is running.
-// nextjs-routes version: 2.2.0
+// nextjs-routes version: 2.2.1
 /* eslint-disable */
 
 // prettier-ignore
@@ -1284,7 +1462,7 @@ exports[`route generation transforms windows paths 1`] = `
     "@types/nextjs-routes.d.ts",
     "// THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
 // This file will be automatically regenerated when your Next.js server is running.
-// nextjs-routes version: 2.2.0
+// nextjs-routes version: 2.2.1
 /* eslint-disable */
 
 // prettier-ignore
@@ -1462,7 +1640,7 @@ exports[`route generation typescript 1`] = `
     "@types/nextjs-routes.d.ts",
     "// THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
 // This file will be automatically regenerated when your Next.js server is running.
-// nextjs-routes version: 2.2.0
+// nextjs-routes version: 2.2.1
 /* eslint-disable */
 
 // prettier-ignore
diff --git a/packages/nextjs-routes/src/core.test.ts b/packages/nextjs-routes/src/core.test.ts
index cb6539c..25d808e 100644
--- a/packages/nextjs-routes/src/core.test.ts
+++ b/packages/nextjs-routes/src/core.test.ts
@@ -79,23 +79,36 @@ describe("route generation", () => {
     expect(writeFileSyncMock.mock.calls).toMatchSnapshot();
   });
 
-  it("app directory (experimental)", () => {
-    // getAppRoutes
-    existsSyncMock
-      .mockImplementationOnce(() => false)
-      .mockImplementationOnce(() => false)
-      .mockImplementationOnce(() => true);
-    findFilesMock.mockReturnValueOnce([
-      "app/page.ts",
-      "app/bar/page.ts",
-      "app/foo/page.tsx",
-      "app/foobar/page.js",
-      "app/barbaz/page.jsx",
-      "app/baz/route.js",
-      "app/foobaz/route.ts",
-    ]);
-    writeNextJSRoutes({});
-    expect(writeFileSyncMock.mock.calls).toMatchSnapshot();
+  describe("app directory (experimental)", () => {
+    it("generates routes", () => {
+      // getAppRoutes
+      existsSyncMock
+        .mockImplementationOnce(() => false)
+        .mockImplementationOnce(() => false)
+        .mockImplementationOnce(() => true);
+      findFilesMock.mockReturnValueOnce([
+        "app/page.ts",
+        "app/bar/page.ts",
+        "app/foo/page.tsx",
+        "app/foobar/page.js",
+        "app/barbaz/page.jsx",
+        "app/baz/route.js",
+        "app/foobaz/route.ts",
+      ]);
+      writeNextJSRoutes({});
+      expect(writeFileSyncMock.mock.calls).toMatchSnapshot();
+    });
+
+    it("handles windows paths", () => {
+      // get AppRoutes
+      existsSyncMock
+        .mockImplementationOnce(() => false)
+        .mockImplementationOnce(() => false)
+        .mockImplementationOnce(() => true);
+      findFilesMock.mockReturnValueOnce(["app\\[foo]\\bar\\page.ts"]);
+      writeNextJSRoutes({});
+      expect(writeFileSyncMock.mock.calls).toMatchSnapshot();
+    });
   });
 
   describe("configuration", () => {