next-ws 0.1.5-next.78595a2 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -21,6 +21,8 @@ Next WS (`next-ws`) is an advanced Next.js **13** plugin designed to seamlessly
21
21
 
22
22
  It's important to note that this module can only be used when working with a server. Unfortunately, in serverless environments like Vercel, WebSocket servers cannot be used. Additionally, this module was built for the app directory and is incompatible with the older pages directory.
23
23
 
24
+ Next WS is still pre its 1.0 release, and as such, things may change. If you find any bugs or have any suggestions, please open an issue on the GitHub repository.
25
+
24
26
  This module is inspired by the now outdated `next-plugin-websocket`, if you are using an older version of Next.js, that module may work for you.
25
27
 
26
28
  ---
@@ -30,36 +32,37 @@ This module is inspired by the now outdated `next-plugin-websocket`, if you are
30
32
  - [🤔 About](#-about)
31
33
  - [🏓 Table of Contents](#-table-of-contents)
32
34
  - [📦 Installation](#-installation)
33
- - [🚀 Usage](#-api)
35
+ - [🚀 Usage](#-usage)
36
+ - [🚓 Verify Patch](#-verify-patch)
34
37
  - [🌀 Example](#-example)
35
- - [📁 Server](#-server)
36
- - [📁 Client](#-client)
37
-
38
+ - [📁 Server](#-server)
39
+ - [📁 Client](#-client)
40
+
38
41
  ---
39
42
 
40
43
  ## 📦 Installation
41
44
 
45
+ In order to setup a WebSocket server, Next WS needs to patch your local Next.js installation. Next WS provides a CLI command to do this for you, it will automatically detect your Next.js version and patch it accordingly, however a minimum version of Next.js 13.1.1 is required.
46
+
42
47
  ```sh
43
- npm install next-ws
44
- yarn add next-ws
45
- pnpm add next-ws
48
+ npx next-ws-cli patch
46
49
  ```
47
50
 
48
- Upon installation, Next WS will automatically patch your existing Next.js installation to add support for WebSockets in API routes in the app directory.
51
+ > If at any point your local Next.js installation is changed or updated you will need to re-run the patch command.
49
52
 
50
- <details>
51
- <summary><strong>Caveats</strong></summary>
53
+ Once the patch is complete, you will need to install the Next WS package into your project.
52
54
 
53
- As this module modifies the Next.js installation, if for any reason it changes (such as when you update Next.js), you will need to reinstall Next WS. And if you want to uninstall Next WS, you will need to reinstall Next.js.
54
- </details>
55
+ ```sh
56
+ npm install next-ws
57
+ ```
55
58
 
56
59
  ---
57
60
 
58
61
  ## 🚀 Usage
59
62
 
60
- Using Next WS is a breeze, requiring zero configuration. Simply export a `SOCKET` function from an API route. This function gets called whenever a client connects to the WebSocket server at the respective API path.
63
+ Using Next WS is a breeze, requiring zero configuration. Simply export a `SOCKET` function from any API route. This function gets called whenever a client connects to the WebSocket server at the respective API path.
61
64
 
62
- The `SOCKET` function receives two arguments: the WebSocket client and the HTTP request, which you can use to get the URL path, query parameters, and headers.
65
+ The `SOCKET` function receives three arguments: the WebSocket client, the HTTP request - which you can use to get the URL path, query parameters, and headers - and the WebSocket server that `next-ws` created.
63
66
 
64
67
  ```ts
65
68
  export function SOCKET(
@@ -73,6 +76,16 @@ export function SOCKET(
73
76
 
74
77
  With this straightforward setup, you can fully leverage the capabilities of Next WS and efficiently handle WebSocket connections within your Next.js application.
75
78
 
79
+ ### 🚓 Verify Patch
80
+
81
+ It is recommended to add the following code to the top level of your `next.config.js`.
82
+
83
+ This will verify that Next WS has been patched correctly, and throw an error if it has not. Preventing you from accidentally deploying a broken setup.
84
+
85
+ ```ts
86
+ require('next-ws/server').verifyPatch();
87
+ ```
88
+
76
89
  ---
77
90
 
78
91
  ## 🌀 Example
@@ -86,6 +99,7 @@ Create an API route anywhere within the app directory, and export a `SOCKET` fun
86
99
  export function SOCKET(
87
100
  client: import('ws').WebSocket,
88
101
  request: import('http').IncomingMessage,
102
+ server: import('ws').WebSocketServer,
89
103
  ) {
90
104
  console.log('A client connected!');
91
105
 
@@ -101,11 +115,9 @@ export function SOCKET(
101
115
 
102
116
  You are pretty much done at this point, you can now connect to the WebSocket server using the native WebSocket API in the browser.
103
117
 
104
- ---
105
-
106
118
  ### 📁 Client
107
119
 
108
- To make it easier to connect to your new WebSocker server, Next WS also provides some client-side utilities. These are not required, you can use the native WebSocket API if you prefer.
120
+ To make it easier to connect to your new WebSocker server, Next WS also provides some client-side utilities. These are completely optional, you can use your own implementation if you wish.
109
121
 
110
122
  ```tsx
111
123
  // layout.tsx
@@ -114,12 +126,45 @@ To make it easier to connect to your new WebSocker server, Next WS also provides
114
126
  import { WebSocketProvider } from 'next-ws/client';
115
127
 
116
128
  export default function Layout() {
117
- return <WebSocketProvider url="ws://localhost:3000/api/ws">
129
+ return <WebSocketProvider
130
+ url="ws://localhost:3000/api/ws"
131
+ // ... other props
132
+ >
118
133
  {...}
119
134
  </WebSocketProvider>;
120
135
  }
121
136
  ```
122
137
 
138
+ The following is the props interface for the `WebSocketProvider` component, containing all the available options.
139
+
140
+ ```ts
141
+ interface WebSocketProviderProps {
142
+ children: React.ReactNode;
143
+
144
+ /** The URL for the WebSocket to connect to. */
145
+ url: string;
146
+ /** The subprotocols to use. */
147
+ protocols?: string[] | string;
148
+ /** The binary type to use. */
149
+ binaryType?: BinaryType;
150
+
151
+ /** Whether to reconnect when the WebSocket closes. */
152
+ reconnect?: boolean;
153
+ /** The maximum number of times to reconnect. */
154
+ maxReconnectAttempts?: number;
155
+ /** The delay between reconnect attempts. */
156
+ reconnectDelay?: number;
157
+
158
+ // NOTE: You do not need to use these, they are only here for convenience
159
+ /** Event listener that is triggered when the WebSocket connection opens. */
160
+ onOpen?(this: WebSocket, event: Event): any;
161
+ /** Event listener that is triggered when the WebSocket connection closes. */
162
+ onClose?(this: WebSocket, event: CloseEvent): any;
163
+ /** Event listener that is triggered when an error occurs. */
164
+ onError?(this: WebSocket, event: Event): any;
165
+ }
166
+ ```
167
+
123
168
  ```tsx
124
169
  // page.tsx
125
170
  'use client';
@@ -129,7 +174,8 @@ import { useCallback, useEffect, useState } from 'react';
129
174
 
130
175
  export default function Page() {
131
176
  const ws = useWebSocket();
132
- // ^? WebSocket on the client, null on the server
177
+ // WebSocket instance only exists when connected
178
+ // It will be null when state is closed or connecting
133
179
 
134
180
  const [value, setValue] = useState('');
135
181
  const [message, setMessage] = useState<string | null>('');
@@ -142,7 +188,7 @@ export default function Page() {
142
188
  useEffect(() => {
143
189
  ws?.addEventListener('message', onMessage);
144
190
  return () => ws?.removeEventListener('message', onMessage);
145
- }, []);
191
+ }, [ws]);
146
192
 
147
193
  return <>
148
194
  <input
@@ -160,6 +206,6 @@ export default function Page() {
160
206
  ? 'Waiting for server to send a message...'
161
207
  : `Got message: ${message}`}
162
208
  </p>
163
- </>
209
+ </>;
164
210
  }
165
211
  ```
@@ -0,0 +1,24 @@
1
+ /// <reference types="react" />
2
+ export declare const WebSocketContext: import("react").Context<WebSocket | null>;
3
+ export interface WebSocketProviderProps {
4
+ children: React.ReactNode;
5
+ /** The URL for the WebSocket to connect to. */
6
+ url: string;
7
+ /** The subprotocols to use. */
8
+ protocols?: string[] | string;
9
+ /** The binary type to use. */
10
+ binaryType?: BinaryType;
11
+ }
12
+ /**
13
+ * Provides a WebSocket instance to its children via context,
14
+ * allowing for easy access to the WebSocket from anywhere in the app.
15
+ * @param props WebSocket parameters and children.
16
+ * @returns JSX Element
17
+ */
18
+ export declare function WebSocketProvider({ children, url, protocols, binaryType, }: WebSocketProviderProps): import("react/jsx-runtime").JSX.Element;
19
+ export declare const WebSocketConsumer: import("react").Consumer<WebSocket | null>;
20
+ /**
21
+ * Access the websocket from anywhere in the app, so long as it's wrapped in a WebSocketProvider.
22
+ * @returns WebSocket instance when connected, null when disconnected.
23
+ */
24
+ export declare function useWebSocket(): WebSocket | null;
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.useWebSocket = exports.WebSocketConsumer = exports.WebSocketProvider = exports.WebSocketContext = void 0;
4
+ const jsx_runtime_1 = require("react/jsx-runtime");
5
+ const react_1 = require("react");
6
+ exports.WebSocketContext = (0, react_1.createContext)(null);
7
+ exports.WebSocketContext.displayName = 'WebSocketContext';
8
+ /**
9
+ * Provides a WebSocket instance to its children via context,
10
+ * allowing for easy access to the WebSocket from anywhere in the app.
11
+ * @param props WebSocket parameters and children.
12
+ * @returns JSX Element
13
+ */
14
+ function WebSocketProvider({ children, url, protocols, binaryType, }) {
15
+ const isBrowser = typeof window !== 'undefined';
16
+ const setupClient = () => {
17
+ if (!isBrowser)
18
+ return null;
19
+ const client = new WebSocket(url, protocols);
20
+ if (binaryType)
21
+ client.binaryType = binaryType;
22
+ return client;
23
+ };
24
+ const instance = (0, react_1.useMemo)(setupClient, [isBrowser, url, protocols]);
25
+ (0, react_1.useEffect)(() => {
26
+ return () => instance?.close();
27
+ }, []);
28
+ return (0, jsx_runtime_1.jsx)(exports.WebSocketContext.Provider, { value: instance, children: children });
29
+ }
30
+ exports.WebSocketProvider = WebSocketProvider;
31
+ exports.WebSocketConsumer = exports.WebSocketContext.Consumer;
32
+ /**
33
+ * Access the websocket from anywhere in the app, so long as it's wrapped in a WebSocketProvider.
34
+ * @returns WebSocket instance when connected, null when disconnected.
35
+ */
36
+ function useWebSocket() {
37
+ const context = (0, react_1.useContext)(exports.WebSocketContext);
38
+ if (context === undefined)
39
+ throw new Error('useWebSocket must be used within a WebSocketProvider');
40
+ return context;
41
+ }
42
+ exports.useWebSocket = useWebSocket;
@@ -0,0 +1 @@
1
+ export * from './context';
@@ -0,0 +1,4 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const tslib_1 = require("tslib");
4
+ tslib_1.__exportStar(require("./context"), exports);
package/index.d.ts ADDED
@@ -0,0 +1 @@
1
+ export {};
package/index.js ADDED
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ throw new Error("next-ws cannot be directory imported, use 'next-ws/server' or 'next-ws/client' instead");
package/package.json CHANGED
@@ -1,75 +1,46 @@
1
1
  {
2
2
  "name": "next-ws",
3
- "version": "0.1.5-next.78595a2",
3
+ "version": "0.2.0",
4
4
  "description": "Add support for WebSockets in Next.js 13 app directory",
5
- "packageManager": "yarn@3.6.0",
5
+ "keywords": ["next", "websocket", "ws", "server", "client"],
6
6
  "license": "MIT",
7
- "keywords": [
8
- "next",
9
- "websocket",
10
- "ws",
11
- "server",
12
- "client"
13
- ],
14
7
  "homepage": "https://github.com/apteryxxyz/next-ws#readme",
15
8
  "repository": {
16
9
  "type": "git",
17
- "url": "git+https://github.com/apteryxxyz/next-ws.git"
10
+ "url": "git+https://github.com/apteryxxyz/next-ws.git",
11
+ "directory": "packages/core"
18
12
  },
19
13
  "bugs": {
20
14
  "url": "https://github.com/apteryxxyz/next-ws/issues"
21
15
  },
22
- "files": [
23
- "*.js",
24
- "*.d.ts"
25
- ],
16
+ "files": ["index.js", "index.d.ts", "client", "server"],
17
+ "main": "./index.js",
18
+ "types": "./index.d.ts",
26
19
  "scripts": {
27
- "build": "tsc",
28
- "clean": "rimraf client server common",
20
+ "build": "cp ../../README.md . && tsc",
21
+ "clean": "rimraf index.js index.d.ts client server README.md",
29
22
  "lint": "eslint --ext .ts,.tsx src",
30
- "format": "prettier --write src/**/*.{ts,tsx} && eslint --fix --ext .ts,.tsx src/",
31
- "postinstall": "node -e \"try{require('./common/patch.js')}catch{}\""
23
+ "format": "prettier --write src/**/*.{ts,tsx} && eslint --fix --ext .ts,.tsx src/"
32
24
  },
33
25
  "dependencies": {
34
- "@babel/generator": "^7.22.5",
35
- "@babel/parser": "^7.22.5",
36
- "@babel/template": "^7.22.5"
26
+ "ws": "^8.13.0"
37
27
  },
38
28
  "peerDependencies": {
39
- "next": "*",
40
- "react": "*",
41
- "ws": "*"
29
+ "next": ">=13.1.1",
30
+ "react": "*"
42
31
  },
43
32
  "devDependencies": {
44
- "@babel/types": "^7.22.4",
45
- "@rushstack/eslint-patch": "^1.3.0",
46
- "@types/babel__generator": "^7",
47
- "@types/babel__template": "^7",
48
33
  "@types/eslint": "^8",
49
- "@types/node": "^20.2.5",
50
34
  "@types/prettier": "^2",
51
35
  "@types/react": "^18",
36
+ "@types/react-dom": "^18",
52
37
  "@types/ws": "^8",
53
- "@typescript-eslint/eslint-plugin": "^5.59.8",
54
- "@typescript-eslint/parser": "^5.59.8",
55
- "eslint": "^8.41.0",
56
- "eslint-config-apteryx": "^2.1.7",
57
- "eslint-config-prettier": "^8.8.0",
58
- "eslint-plugin-import": "^2.27.5",
59
- "eslint-plugin-jsdoc": "^46.1.0",
60
- "eslint-plugin-n": "^16.0.0",
61
- "eslint-plugin-prettier": "^4.2.1",
62
- "eslint-plugin-promise": "^6.1.1",
63
- "eslint-plugin-sonarjs": "^0.19.0",
64
- "eslint-plugin-unicorn": "^47.0.0",
65
- "next": "^13.4.4",
38
+ "eslint": "^8.43.0",
39
+ "next": "^13.4.6",
66
40
  "prettier": "^2.8.8",
67
- "prettier-config-apteryx": "^2.1.0",
68
41
  "react": "^18.2.0",
42
+ "react-dom": "^18.2.0",
69
43
  "rimraf": "^5.0.1",
70
- "ts-config-apteryx": "^2.1.0",
71
- "typescript": "<5.1.0",
72
- "ws": "^8.13.0"
73
- },
74
- "prettier": "prettier-config-apteryx"
75
- }
44
+ "typescript": "<5.1.0"
45
+ }
46
+ }
@@ -0,0 +1,3 @@
1
+ export * from './utilities/patch';
2
+ export type { SocketHandler } from './utilities/next';
3
+ export * from './setup';
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const tslib_1 = require("tslib");
4
+ tslib_1.__exportStar(require("./utilities/patch"), exports);
5
+ tslib_1.__exportStar(require("./setup"), exports);
@@ -0,0 +1,3 @@
1
+ import NextNodeServer from 'next/dist/server/next-server';
2
+ export declare function setupWebSocketServer(nextServer: NextNodeServer): void;
3
+ export declare function hookNextNodeServer(this: NextNodeServer): void;
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.hookNextNodeServer = exports.setupWebSocketServer = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const node_path_1 = tslib_1.__importDefault(require("node:path"));
6
+ const Log = tslib_1.__importStar(require("next/dist/build/output/log"));
7
+ const ws_1 = require("ws");
8
+ const next_1 = require("./utilities/next");
9
+ function setupWebSocketServer(nextServer) {
10
+ const httpServer = (0, next_1.getHttpServer)(nextServer);
11
+ const wsServer = new ws_1.WebSocketServer({ noServer: true });
12
+ Log.ready('[next-ws] websocket server started successfully');
13
+ httpServer.on('upgrade', async (request, socket, head) => {
14
+ const url = new URL(request.url ?? '', 'http://next-ws');
15
+ const pathname = await (0, next_1.resolvePathname)(nextServer, url.pathname);
16
+ if (!pathname)
17
+ return;
18
+ const fsPathname = node_path_1.default
19
+ .join(pathname, 'route')
20
+ .replaceAll(node_path_1.default.sep, '/');
21
+ const pageModule = await (0, next_1.getPageModule)(nextServer, fsPathname);
22
+ if (!pageModule)
23
+ return Log.error(`[next-ws] could not find module for page ${pathname}`);
24
+ const socketHandler = pageModule?.routeModule?.userland?.SOCKET;
25
+ if (!socketHandler || typeof socketHandler !== 'function')
26
+ return Log.error(`[next-ws] could not find SOCKET handler for page ${pathname}`);
27
+ wsServer.handleUpgrade(request, socket, head, (client, request) => {
28
+ socketHandler(client, request, wsServer);
29
+ });
30
+ });
31
+ }
32
+ exports.setupWebSocketServer = setupWebSocketServer;
33
+ // Next WS versions below 0.2.0 used a different method of setup
34
+ // This remains for backwards compatibility, but may be removed in a future version
35
+ function hookNextNodeServer() {
36
+ setupWebSocketServer(this);
37
+ }
38
+ exports.hookNextNodeServer = hookNextNodeServer;
@@ -0,0 +1,31 @@
1
+ /// <reference types="node" />
2
+ import { Server } from 'node:http';
3
+ import NextNodeServer from 'next/dist/server/next-server';
4
+ /** A function that handles a WebSocket connection. */
5
+ export type SocketHandler = (
6
+ /** The WebSocket client that connected. */
7
+ client: import('ws').WebSocket,
8
+ /** The HTTP request that initiated the WebSocket connection. */
9
+ request: import('http').IncomingMessage,
10
+ /** The WebSocket server. */
11
+ server: import('ws').WebSocketServer) => any;
12
+ /**
13
+ * Get the http.Server instance from the NextNodeServer.
14
+ * @param nextServer The NextNodeServer instance.
15
+ * @returns The http.Server instance.
16
+ */
17
+ export declare function getHttpServer(nextServer: NextNodeServer): Server<typeof import("http").IncomingMessage, typeof import("http").ServerResponse>;
18
+ /**
19
+ * Resolve a pathname to a page.
20
+ * @param nextServer The NextNodeServer instance.
21
+ * @param pathname The pathname to resolve.
22
+ * @returns The resolved page, or null if the page could not be resolved.
23
+ */
24
+ export declare function resolvePathname(nextServer: NextNodeServer, pathname: string): Promise<string | null>;
25
+ /**
26
+ * Get the page module for a page.
27
+ * @param nextServer The NextNodeServer instance.
28
+ * @param filename The filename of the page.
29
+ * @returns The page module.
30
+ */
31
+ export declare function getPageModule(nextServer: NextNodeServer, filename: string): Promise<any>;
@@ -0,0 +1,61 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getPageModule = exports.resolvePathname = exports.getHttpServer = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const node_http_1 = require("node:http");
6
+ const next_server_1 = tslib_1.__importDefault(require("next/dist/server/next-server"));
7
+ const is_dynamic_1 = require("next/dist/shared/lib/router/utils/is-dynamic");
8
+ /**
9
+ * Get the http.Server instance from the NextNodeServer.
10
+ * @param nextServer The NextNodeServer instance.
11
+ * @returns The http.Server instance.
12
+ */
13
+ // prettier-ignore
14
+ function getHttpServer(nextServer) {
15
+ if (!nextServer || !(nextServer instanceof next_server_1.default))
16
+ throw new Error('Next WS is missing access to the NextNodeServer');
17
+ // @ts-expect-error serverOptions is protected
18
+ const httpServer = nextServer.serverOptions?.httpServer;
19
+ if (!httpServer || !(httpServer instanceof node_http_1.Server))
20
+ throw new Error('Next WS is missing access to the http.Server');
21
+ return httpServer;
22
+ }
23
+ exports.getHttpServer = getHttpServer;
24
+ /**
25
+ * Resolve a pathname to a page.
26
+ * @param nextServer The NextNodeServer instance.
27
+ * @param pathname The pathname to resolve.
28
+ * @returns The resolved page, or null if the page could not be resolved.
29
+ */
30
+ async function resolvePathname(nextServer, pathname) {
31
+ if (pathname.startsWith('/_next'))
32
+ return null;
33
+ // @ts-expect-error hasPage is protected
34
+ if (!(0, is_dynamic_1.isDynamicRoute)(pathname) && (await nextServer.hasPage(pathname)))
35
+ return pathname;
36
+ // @ts-expect-error dynamicRoutes is protected
37
+ for (const route of nextServer.dynamicRoutes ?? []) {
38
+ const params = route.match(pathname) || undefined;
39
+ if (params)
40
+ return route.page;
41
+ }
42
+ return null;
43
+ }
44
+ exports.resolvePathname = resolvePathname;
45
+ /**
46
+ * Get the page module for a page.
47
+ * @param nextServer The NextNodeServer instance.
48
+ * @param filename The filename of the page.
49
+ * @returns The page module.
50
+ */
51
+ async function getPageModule(nextServer, filename) {
52
+ // @ts-expect-error HotReloader is private
53
+ await nextServer.hotReloader.ensurePage({
54
+ page: filename,
55
+ clientOnly: false,
56
+ });
57
+ // @ts-expect-error getPagePath is protected
58
+ const builtPagePath = nextServer.getPagePath(filename);
59
+ return require(builtPagePath);
60
+ }
61
+ exports.getPageModule = getPageModule;
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Get the Next WS patch version and Next.js version.
3
+ * @returns The patch version and Next.js version if the patch has been applied, otherwise null.
4
+ */
5
+ export declare function getPatch(): {
6
+ patch: string;
7
+ version: string;
8
+ } | null;
9
+ /**
10
+ * Verify that the Next WS patch has been applied to Next.js.
11
+ * @returns The patch version and Next.js version if the patch has been applied, otherwise null.
12
+ */
13
+ export declare function verifyPatch(): void;
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.verifyPatch = exports.getPatch = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const fs = tslib_1.__importStar(require("node:fs"));
6
+ const path = tslib_1.__importStar(require("node:path"));
7
+ const workspace_1 = require("./workspace");
8
+ /**
9
+ * Get the Next WS patch version and Next.js version.
10
+ * @returns The patch version and Next.js version if the patch has been applied, otherwise null.
11
+ */
12
+ function getPatch() {
13
+ const location = path.join((0, workspace_1.findWorkspaceRoot)(), 'node_modules/next/.next-ws-trace.json');
14
+ try {
15
+ const content = fs.readFileSync(location, 'utf8');
16
+ return JSON.parse(content);
17
+ }
18
+ catch {
19
+ return null;
20
+ }
21
+ }
22
+ exports.getPatch = getPatch;
23
+ /**
24
+ * Verify that the Next WS patch has been applied to Next.js.
25
+ * @returns The patch version and Next.js version if the patch has been applied, otherwise null.
26
+ */
27
+ // prettier-ignore
28
+ function verifyPatch() {
29
+ const patch = getPatch();
30
+ if (!patch)
31
+ throw new Error('Next.js has not been patched to support Next WS, please run `npx next-ws-cli patch`');
32
+ const version = require('next/package.json').version.split('-')[0];
33
+ if (patch.version !== version)
34
+ throw new Error(`Next.js version mismatch, expected ${patch.version} but found ${version}`);
35
+ }
36
+ exports.verifyPatch = verifyPatch;
@@ -0,0 +1 @@
1
+ export declare function findWorkspaceRoot(initalPath?: string): string;
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.findWorkspaceRoot = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const fs = tslib_1.__importStar(require("node:fs"));
6
+ const path = tslib_1.__importStar(require("node:path"));
7
+ function findWorkspaceRoot(initalPath = process.cwd()) {
8
+ let currentPath = initalPath;
9
+ let lastPossiblePath = currentPath;
10
+ let isRoot = 'maybe';
11
+ const isCurrentPathRoot = () => {
12
+ const files = fs.readdirSync(currentPath);
13
+ const lockFiles = ['pnpm-lock.yaml', 'yarn.lock', 'package-lock.json'];
14
+ const hasLockFile = files.some(file => lockFiles.includes(file));
15
+ if (hasLockFile)
16
+ return 'true';
17
+ const packageJson = files.find(file => file === 'package.json');
18
+ if (packageJson) {
19
+ const packageContent = fs.readFileSync(path.resolve(currentPath, packageJson), 'utf8');
20
+ const packageObject = JSON.parse(packageContent);
21
+ if (packageObject.packageManager)
22
+ return 'true';
23
+ return 'maybe';
24
+ }
25
+ return 'false';
26
+ };
27
+ while (true) {
28
+ isRoot = isCurrentPathRoot();
29
+ const nextPath = path.resolve(currentPath, '..');
30
+ if (isRoot === 'true' || nextPath === currentPath)
31
+ break;
32
+ else if (isRoot === 'maybe')
33
+ lastPossiblePath = currentPath;
34
+ currentPath = nextPath;
35
+ }
36
+ return isRoot === 'true' ? currentPath : lastPossiblePath;
37
+ }
38
+ exports.findWorkspaceRoot = findWorkspaceRoot;