@revyse/next 0.0.0-stage → 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 +18 -2
- package/dist/client.cjs +11 -0
- package/dist/client.js +9 -0
- package/dist/index.cjs +233 -0
- package/dist/index.d.cts +87 -0
- package/dist/index.d.ts +87 -0
- package/dist/index.js +185 -0
- package/dist/loader.cjs +15914 -0
- package/dist/loader.js +15879 -0
- package/package.json +55 -3
package/README.md
CHANGED
|
@@ -1,3 +1,19 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @revyse/next
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Next.js integration for Revyse: in `next dev` only, starts the local agent, tags elements with
|
|
4
|
+
their source location and injects the Revyse overlay, so there is no separate `revyse dev` to
|
|
5
|
+
run. A production build is untouched; a test proves it.
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
// next.config.ts
|
|
9
|
+
import { withRevyse } from "@revyse/next";
|
|
10
|
+
|
|
11
|
+
export default withRevyse({/* your config */});
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
`npx revyse init` makes this edit for you as a diff you approve. Works with the App Router
|
|
15
|
+
and the Pages Router, webpack and Turbopack, Next 14 to 16.
|
|
16
|
+
|
|
17
|
+
## License
|
|
18
|
+
|
|
19
|
+
Proprietary (`UNLICENSED`). Use is governed by the Revyse terms: https://revyse.dev/terms
|
package/dist/client.cjs
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
// src/client.ts
|
|
4
|
+
var url = decodeURIComponent(__resourceQuery.slice(1));
|
|
5
|
+
if (typeof document !== "undefined" && url && !document.querySelector(`script[src="${url}"]`)) {
|
|
6
|
+
const script = document.createElement("script");
|
|
7
|
+
script.type = "module";
|
|
8
|
+
script.async = true;
|
|
9
|
+
script.src = url;
|
|
10
|
+
document.head.append(script);
|
|
11
|
+
}
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// src/client.ts
|
|
2
|
+
var url = decodeURIComponent(__resourceQuery.slice(1));
|
|
3
|
+
if (typeof document !== "undefined" && url && !document.querySelector(`script[src="${url}"]`)) {
|
|
4
|
+
const script = document.createElement("script");
|
|
5
|
+
script.type = "module";
|
|
6
|
+
script.async = true;
|
|
7
|
+
script.src = url;
|
|
8
|
+
document.head.append(script);
|
|
9
|
+
}
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var src_exports = {};
|
|
32
|
+
__export(src_exports, {
|
|
33
|
+
DEFAULT_AGENT_PORT: () => DEFAULT_AGENT_PORT,
|
|
34
|
+
DEFAULT_APP_URL: () => DEFAULT_APP_URL,
|
|
35
|
+
LOCATIONS_DIR: () => LOCATIONS_DIR,
|
|
36
|
+
configuredAgentPort: () => configuredAgentPort,
|
|
37
|
+
ensureLocalAgent: () => ensureLocalAgent,
|
|
38
|
+
liveLoaderScript: () => liveLoaderScript,
|
|
39
|
+
pagesRouterOf: () => pagesRouterOf,
|
|
40
|
+
withRevyse: () => withRevyse
|
|
41
|
+
});
|
|
42
|
+
module.exports = __toCommonJS(src_exports);
|
|
43
|
+
|
|
44
|
+
// ../../node_modules/.pnpm/tsup@9.0.0_jiti@2.7.0_postcss@8.5.31_tsx@4.23.15_typescript@6.0.3/node_modules/tsup/assets/cjs_shims.js
|
|
45
|
+
var getImportMetaUrl = () => typeof document === "undefined" ? new URL(`file:${__filename}`).href : document.currentScript && document.currentScript.tagName.toUpperCase() === "SCRIPT" ? document.currentScript.src : new URL("main.js", document.baseURI).href;
|
|
46
|
+
var importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
|
|
47
|
+
|
|
48
|
+
// src/index.ts
|
|
49
|
+
var import_node_fs = require("fs");
|
|
50
|
+
var import_node_module = require("module");
|
|
51
|
+
var import_node_path = __toESM(require("path"), 1);
|
|
52
|
+
var LOCATIONS_DIR = import_node_path.default.join("revyse", "locations");
|
|
53
|
+
var DEFAULT_AGENT_PORT = 4545;
|
|
54
|
+
var DEFAULT_APP_URL = "https://revyse.dev";
|
|
55
|
+
var PHASE_DEVELOPMENT_SERVER = "phase-development-server";
|
|
56
|
+
var PHASE_PRODUCTION_BUILD = "phase-production-build";
|
|
57
|
+
var require_ = (0, import_node_module.createRequire)(importMetaUrl);
|
|
58
|
+
function configuredAgentPort(root) {
|
|
59
|
+
try {
|
|
60
|
+
const config = JSON.parse((0, import_node_fs.readFileSync)(import_node_path.default.join(root, "revyse.config.json"), "utf8"));
|
|
61
|
+
return typeof config.agentPort === "number" ? config.agentPort : void 0;
|
|
62
|
+
} catch {
|
|
63
|
+
return void 0;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
var say = (msg) => console.log(`[revyse] ${msg}`);
|
|
67
|
+
var warn = (msg) => console.warn(`[revyse] ${msg}`);
|
|
68
|
+
var agentStart;
|
|
69
|
+
function ensureLocalAgent(root, load = () => import("revyse/agent")) {
|
|
70
|
+
if (process.env.REVYSE_AGENT === "off") return Promise.resolve();
|
|
71
|
+
agentStart ??= startAgent(root, load).catch(
|
|
72
|
+
(err) => warn(
|
|
73
|
+
`Couldn't start Revyse (${err.message}). Check that \`revyse\` is installed as a dev dependency, or run \`npx revyse dev\`.`
|
|
74
|
+
)
|
|
75
|
+
);
|
|
76
|
+
return agentStart;
|
|
77
|
+
}
|
|
78
|
+
async function startAgent(root, load) {
|
|
79
|
+
if (!(0, import_node_fs.existsSync)(import_node_path.default.join(root, "revyse.config.json"))) {
|
|
80
|
+
warn("This app isn't set up for Revyse yet: run `npx revyse init`.");
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
const { startLocalAgent } = await load();
|
|
84
|
+
const started = await startLocalAgent({ root, log: say });
|
|
85
|
+
if (started.status === "running") return;
|
|
86
|
+
if (started.status === "taken") {
|
|
87
|
+
warn(
|
|
88
|
+
started.root ? `Port ${started.port} is used by Revyse for ${started.root}, so the overlay here would talk to that project. Set "agentPort" in revyse.config.json to another port, such as ${started.port + 1}.` : `Port ${started.port} is in use by another program. Set "agentPort" in revyse.config.json to a free port.`
|
|
89
|
+
);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
say(
|
|
93
|
+
started.agent.connected ? "Ready: open your app and click the Revyse button." : "Ready, but this project isn't connected to Revyse yet: run `npx revyse init` to connect it."
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
function pagesRouterOf(root) {
|
|
97
|
+
for (const dir of ["pages", "src/pages"]) {
|
|
98
|
+
if (!(0, import_node_fs.existsSync)(import_node_path.default.join(root, dir))) continue;
|
|
99
|
+
const hasDocument = ["tsx", "jsx", "ts", "js"].some(
|
|
100
|
+
(ext) => (0, import_node_fs.existsSync)(import_node_path.default.join(root, dir, `_document.${ext}`))
|
|
101
|
+
);
|
|
102
|
+
return { dir, hasDocument };
|
|
103
|
+
}
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
var withImport = (entry, add) => {
|
|
107
|
+
const list = (v) => Array.isArray(v) ? v : [v];
|
|
108
|
+
if (typeof entry === "object" && !Array.isArray(entry))
|
|
109
|
+
return { ...entry, import: [...list(entry.import), add] };
|
|
110
|
+
return [...list(entry), add];
|
|
111
|
+
};
|
|
112
|
+
function liveLoaderScript(live) {
|
|
113
|
+
if (!/^pk_live_[0-9a-f]{32}$/.test(live.projectKey))
|
|
114
|
+
throw new Error(
|
|
115
|
+
"[revyse] live.projectKey must be the project's public key (pk_live_\u2026) from the Revyse project page"
|
|
116
|
+
);
|
|
117
|
+
const appUrl = live.appUrl ?? process.env.REVYSE_APP_URL ?? DEFAULT_APP_URL;
|
|
118
|
+
return { src: `${new URL(appUrl).origin}/live.js`, projectKey: live.projectKey };
|
|
119
|
+
}
|
|
120
|
+
function withRevyse(config = {}, options = {}) {
|
|
121
|
+
return async (phase, ctx) => {
|
|
122
|
+
const resolved = typeof config === "function" ? await config(phase, ctx) : config;
|
|
123
|
+
if (phase === PHASE_PRODUCTION_BUILD && options.live) {
|
|
124
|
+
return withLiveLoader(resolved, options.projectRoot ?? process.cwd(), options.live);
|
|
125
|
+
}
|
|
126
|
+
if (phase !== PHASE_DEVELOPMENT_SERVER || process.env.NODE_ENV === "production") {
|
|
127
|
+
return resolved;
|
|
128
|
+
}
|
|
129
|
+
const projectRoot = options.projectRoot ?? process.cwd();
|
|
130
|
+
const agentPort = options.agentPort ?? configuredAgentPort(projectRoot) ?? DEFAULT_AGENT_PORT;
|
|
131
|
+
const overlayScriptUrl = `http://127.0.0.1:${agentPort}/overlay.js`;
|
|
132
|
+
void ensureLocalAgent(projectRoot);
|
|
133
|
+
const loader = {
|
|
134
|
+
loader: require_.resolve("@revyse/next/loader"),
|
|
135
|
+
options: { projectRoot, overlayScriptUrl }
|
|
136
|
+
};
|
|
137
|
+
const pages = pagesRouterOf(projectRoot);
|
|
138
|
+
if (pages && !pages.hasDocument && process.env.TURBOPACK) {
|
|
139
|
+
console.warn(
|
|
140
|
+
`[revyse] ${pages.dir}/ has no custom _document, so with Turbopack Revyse can't load its overlay on Pages Router pages. Add ${pages.dir}/_document.tsx (Next's default document is fine), or run next dev with --webpack.`
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
const rule = {
|
|
144
|
+
loaders: [loader],
|
|
145
|
+
// Development only, and never third-party code.
|
|
146
|
+
condition: { all: ["development", { not: "foreign" }] }
|
|
147
|
+
};
|
|
148
|
+
const userWebpack = resolved.webpack;
|
|
149
|
+
return {
|
|
150
|
+
...resolved,
|
|
151
|
+
turbopack: {
|
|
152
|
+
...resolved.turbopack,
|
|
153
|
+
rules: { ...resolved.turbopack?.rules, "*.tsx": rule, "*.jsx": rule }
|
|
154
|
+
},
|
|
155
|
+
webpack(webpackConfig, context) {
|
|
156
|
+
const out = userWebpack ? userWebpack(webpackConfig, context) : webpackConfig;
|
|
157
|
+
if (context.dev) {
|
|
158
|
+
out.module.rules.unshift({
|
|
159
|
+
test: /\.(t|j)sx$/,
|
|
160
|
+
exclude: /node_modules/,
|
|
161
|
+
enforce: "pre",
|
|
162
|
+
use: [loader]
|
|
163
|
+
});
|
|
164
|
+
if (pages && !context.isServer) {
|
|
165
|
+
const client = `${require_.resolve("@revyse/next/client")}?${encodeURIComponent(overlayScriptUrl)}`;
|
|
166
|
+
const original = out.entry;
|
|
167
|
+
out.entry = async () => {
|
|
168
|
+
const entries = await (typeof original === "function" ? original() : original);
|
|
169
|
+
if (entries.main) entries.main = withImport(entries.main, client);
|
|
170
|
+
return entries;
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return out;
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
function withLiveLoader(resolved, projectRoot, live) {
|
|
180
|
+
const token = process.env.REVYSE_BUILD_TOKEN;
|
|
181
|
+
if (live.locations && !token?.startsWith("rvb_"))
|
|
182
|
+
console.warn(
|
|
183
|
+
"[revyse] live.locations is on but REVYSE_BUILD_TOKEN isn't set, so this build has no exact locations. Create a build token on the project's Live site tab and set it where the site builds."
|
|
184
|
+
);
|
|
185
|
+
const locations = live.locations && token?.startsWith("rvb_") ? {
|
|
186
|
+
token,
|
|
187
|
+
manifestDir: import_node_path.default.join(projectRoot, resolved.distDir ?? ".next", LOCATIONS_DIR)
|
|
188
|
+
} : void 0;
|
|
189
|
+
const loader = {
|
|
190
|
+
loader: require_.resolve("@revyse/next/loader"),
|
|
191
|
+
options: {
|
|
192
|
+
projectRoot,
|
|
193
|
+
liveScript: liveLoaderScript(live),
|
|
194
|
+
...locations ? { locations } : {}
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
const pages = pagesRouterOf(projectRoot);
|
|
198
|
+
if (pages && !pages.hasDocument) {
|
|
199
|
+
console.warn(
|
|
200
|
+
`[revyse] ${pages.dir}/ has no custom _document, so the live overlay's loader can't be added to Pages Router pages. Add ${pages.dir}/_document.tsx (Next's default document is fine).`
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
const rule = { loaders: [loader], condition: { not: "foreign" } };
|
|
204
|
+
const userWebpack = resolved.webpack;
|
|
205
|
+
return {
|
|
206
|
+
...resolved,
|
|
207
|
+
turbopack: {
|
|
208
|
+
...resolved.turbopack,
|
|
209
|
+
rules: { ...resolved.turbopack?.rules, "*.tsx": rule, "*.jsx": rule }
|
|
210
|
+
},
|
|
211
|
+
webpack(webpackConfig, context) {
|
|
212
|
+
const out = userWebpack ? userWebpack(webpackConfig, context) : webpackConfig;
|
|
213
|
+
out.module.rules.unshift({
|
|
214
|
+
test: /\.(t|j)sx$/,
|
|
215
|
+
exclude: /node_modules/,
|
|
216
|
+
enforce: "pre",
|
|
217
|
+
use: [loader]
|
|
218
|
+
});
|
|
219
|
+
return out;
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
224
|
+
0 && (module.exports = {
|
|
225
|
+
DEFAULT_AGENT_PORT,
|
|
226
|
+
DEFAULT_APP_URL,
|
|
227
|
+
LOCATIONS_DIR,
|
|
228
|
+
configuredAgentPort,
|
|
229
|
+
ensureLocalAgent,
|
|
230
|
+
liveLoaderScript,
|
|
231
|
+
pagesRouterOf,
|
|
232
|
+
withRevyse
|
|
233
|
+
});
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { NextConfig } from 'next';
|
|
2
|
+
|
|
3
|
+
interface RevyseNextOptions {
|
|
4
|
+
/** Port of the local Revyse agent; defaults to `agentPort` in `revyse.config.json`, then 4545. */
|
|
5
|
+
agentPort?: number;
|
|
6
|
+
/** Project root; defaults to the directory Next runs in. */
|
|
7
|
+
projectRoot?: string;
|
|
8
|
+
/**
|
|
9
|
+
* The live overlay on the deployed site (ADR 0032): production builds get one `async`
|
|
10
|
+
* script tag for the Revyse loader, carrying the project's public key from the project page.
|
|
11
|
+
* The loader is inert for visitors; members activate it from Revyse. Without this option a
|
|
12
|
+
* production build contains no Revyse code at all.
|
|
13
|
+
*/
|
|
14
|
+
live?: {
|
|
15
|
+
/** The project's public key (`pk_live_…`), shown on the project page. */
|
|
16
|
+
projectKey: string;
|
|
17
|
+
/** The Revyse app serving the loader; defaults to `REVYSE_APP_URL` or https://revyse.dev. */
|
|
18
|
+
appUrl?: string;
|
|
19
|
+
/**
|
|
20
|
+
* Exact locations (W8, ADR 0032 §6): elements get opaque `data-rv` ids keyed with the
|
|
21
|
+
* project's build token (`REVYSE_BUILD_TOKEN` in the build environment), and the build
|
|
22
|
+
* writes the manifest (id → file:line:col) under its output directory for
|
|
23
|
+
* `revyse live upload`. No path is in the pages. Without the token the build goes on
|
|
24
|
+
* without locations, and says so.
|
|
25
|
+
*/
|
|
26
|
+
locations?: boolean;
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
/** Where a build with exact locations writes its manifest, inside Next's output directory. */
|
|
30
|
+
declare const LOCATIONS_DIR: string;
|
|
31
|
+
declare const DEFAULT_AGENT_PORT = 4545;
|
|
32
|
+
declare const DEFAULT_APP_URL = "https://revyse.dev";
|
|
33
|
+
type ConfigInput = NextConfig | ((phase: string, ctx: {
|
|
34
|
+
defaultConfig: NextConfig;
|
|
35
|
+
}) => NextConfig | Promise<NextConfig>);
|
|
36
|
+
/** `agentPort` from the project's `revyse.config.json`, if it has one. */
|
|
37
|
+
declare function configuredAgentPort(root: string): number | undefined;
|
|
38
|
+
/** What `withRevyse()` uses from `revyse/agent` (the CLI package's local agent). */
|
|
39
|
+
interface AgentModule {
|
|
40
|
+
startLocalAgent(opts: {
|
|
41
|
+
root: string;
|
|
42
|
+
log: (msg: string) => void;
|
|
43
|
+
}): Promise<{
|
|
44
|
+
status: "started";
|
|
45
|
+
agent: {
|
|
46
|
+
url: string;
|
|
47
|
+
connected: boolean;
|
|
48
|
+
};
|
|
49
|
+
} | {
|
|
50
|
+
status: "running";
|
|
51
|
+
port: number;
|
|
52
|
+
} | {
|
|
53
|
+
status: "taken";
|
|
54
|
+
port: number;
|
|
55
|
+
root: string | null;
|
|
56
|
+
}>;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Starts the local agent inside `next dev` (L14, ADR 0041), so the overlay works without
|
|
60
|
+
* `revyse dev`. Next 15 runs the config in its CLI process and its server process: whichever
|
|
61
|
+
* comes second finds this project's agent on the port and leaves it be. `REVYSE_AGENT=off`
|
|
62
|
+
* turns it off; `load` is for tests.
|
|
63
|
+
*/
|
|
64
|
+
declare function ensureLocalAgent(root: string, load?: () => Promise<AgentModule>): Promise<void>;
|
|
65
|
+
/** The Pages Router directory and whether it has a custom `_document`, if the app has one. */
|
|
66
|
+
declare function pagesRouterOf(root: string): {
|
|
67
|
+
dir: string;
|
|
68
|
+
hasDocument: boolean;
|
|
69
|
+
} | null;
|
|
70
|
+
/** The loader tag for a production build with the `live` option (ADR 0032 §1). */
|
|
71
|
+
declare function liveLoaderScript(live: NonNullable<RevyseNextOptions["live"]>): {
|
|
72
|
+
src: string;
|
|
73
|
+
projectKey: string;
|
|
74
|
+
};
|
|
75
|
+
/**
|
|
76
|
+
* Wraps a Next.js config. In `next dev` it starts the local agent (L14) and adds a JSX loader
|
|
77
|
+
* (webpack and Turbopack) that tags host elements with their source location and loads the
|
|
78
|
+
* Revyse overlay. In every other phase
|
|
79
|
+
* — including `next build` — the config is returned untouched, so nothing ships to production,
|
|
80
|
+
* with one opt-in exception: `live` adds the live overlay's loader tag to production builds.
|
|
81
|
+
*/
|
|
82
|
+
declare function withRevyse(config?: ConfigInput, options?: RevyseNextOptions): (phase: string, ctx: {
|
|
83
|
+
defaultConfig: NextConfig;
|
|
84
|
+
}) => Promise<NextConfig>;
|
|
85
|
+
|
|
86
|
+
export { DEFAULT_AGENT_PORT, DEFAULT_APP_URL, LOCATIONS_DIR, configuredAgentPort, ensureLocalAgent, liveLoaderScript, pagesRouterOf, withRevyse };
|
|
87
|
+
export type { AgentModule, RevyseNextOptions };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { NextConfig } from 'next';
|
|
2
|
+
|
|
3
|
+
interface RevyseNextOptions {
|
|
4
|
+
/** Port of the local Revyse agent; defaults to `agentPort` in `revyse.config.json`, then 4545. */
|
|
5
|
+
agentPort?: number;
|
|
6
|
+
/** Project root; defaults to the directory Next runs in. */
|
|
7
|
+
projectRoot?: string;
|
|
8
|
+
/**
|
|
9
|
+
* The live overlay on the deployed site (ADR 0032): production builds get one `async`
|
|
10
|
+
* script tag for the Revyse loader, carrying the project's public key from the project page.
|
|
11
|
+
* The loader is inert for visitors; members activate it from Revyse. Without this option a
|
|
12
|
+
* production build contains no Revyse code at all.
|
|
13
|
+
*/
|
|
14
|
+
live?: {
|
|
15
|
+
/** The project's public key (`pk_live_…`), shown on the project page. */
|
|
16
|
+
projectKey: string;
|
|
17
|
+
/** The Revyse app serving the loader; defaults to `REVYSE_APP_URL` or https://revyse.dev. */
|
|
18
|
+
appUrl?: string;
|
|
19
|
+
/**
|
|
20
|
+
* Exact locations (W8, ADR 0032 §6): elements get opaque `data-rv` ids keyed with the
|
|
21
|
+
* project's build token (`REVYSE_BUILD_TOKEN` in the build environment), and the build
|
|
22
|
+
* writes the manifest (id → file:line:col) under its output directory for
|
|
23
|
+
* `revyse live upload`. No path is in the pages. Without the token the build goes on
|
|
24
|
+
* without locations, and says so.
|
|
25
|
+
*/
|
|
26
|
+
locations?: boolean;
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
/** Where a build with exact locations writes its manifest, inside Next's output directory. */
|
|
30
|
+
declare const LOCATIONS_DIR: string;
|
|
31
|
+
declare const DEFAULT_AGENT_PORT = 4545;
|
|
32
|
+
declare const DEFAULT_APP_URL = "https://revyse.dev";
|
|
33
|
+
type ConfigInput = NextConfig | ((phase: string, ctx: {
|
|
34
|
+
defaultConfig: NextConfig;
|
|
35
|
+
}) => NextConfig | Promise<NextConfig>);
|
|
36
|
+
/** `agentPort` from the project's `revyse.config.json`, if it has one. */
|
|
37
|
+
declare function configuredAgentPort(root: string): number | undefined;
|
|
38
|
+
/** What `withRevyse()` uses from `revyse/agent` (the CLI package's local agent). */
|
|
39
|
+
interface AgentModule {
|
|
40
|
+
startLocalAgent(opts: {
|
|
41
|
+
root: string;
|
|
42
|
+
log: (msg: string) => void;
|
|
43
|
+
}): Promise<{
|
|
44
|
+
status: "started";
|
|
45
|
+
agent: {
|
|
46
|
+
url: string;
|
|
47
|
+
connected: boolean;
|
|
48
|
+
};
|
|
49
|
+
} | {
|
|
50
|
+
status: "running";
|
|
51
|
+
port: number;
|
|
52
|
+
} | {
|
|
53
|
+
status: "taken";
|
|
54
|
+
port: number;
|
|
55
|
+
root: string | null;
|
|
56
|
+
}>;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Starts the local agent inside `next dev` (L14, ADR 0041), so the overlay works without
|
|
60
|
+
* `revyse dev`. Next 15 runs the config in its CLI process and its server process: whichever
|
|
61
|
+
* comes second finds this project's agent on the port and leaves it be. `REVYSE_AGENT=off`
|
|
62
|
+
* turns it off; `load` is for tests.
|
|
63
|
+
*/
|
|
64
|
+
declare function ensureLocalAgent(root: string, load?: () => Promise<AgentModule>): Promise<void>;
|
|
65
|
+
/** The Pages Router directory and whether it has a custom `_document`, if the app has one. */
|
|
66
|
+
declare function pagesRouterOf(root: string): {
|
|
67
|
+
dir: string;
|
|
68
|
+
hasDocument: boolean;
|
|
69
|
+
} | null;
|
|
70
|
+
/** The loader tag for a production build with the `live` option (ADR 0032 §1). */
|
|
71
|
+
declare function liveLoaderScript(live: NonNullable<RevyseNextOptions["live"]>): {
|
|
72
|
+
src: string;
|
|
73
|
+
projectKey: string;
|
|
74
|
+
};
|
|
75
|
+
/**
|
|
76
|
+
* Wraps a Next.js config. In `next dev` it starts the local agent (L14) and adds a JSX loader
|
|
77
|
+
* (webpack and Turbopack) that tags host elements with their source location and loads the
|
|
78
|
+
* Revyse overlay. In every other phase
|
|
79
|
+
* — including `next build` — the config is returned untouched, so nothing ships to production,
|
|
80
|
+
* with one opt-in exception: `live` adds the live overlay's loader tag to production builds.
|
|
81
|
+
*/
|
|
82
|
+
declare function withRevyse(config?: ConfigInput, options?: RevyseNextOptions): (phase: string, ctx: {
|
|
83
|
+
defaultConfig: NextConfig;
|
|
84
|
+
}) => Promise<NextConfig>;
|
|
85
|
+
|
|
86
|
+
export { DEFAULT_AGENT_PORT, DEFAULT_APP_URL, LOCATIONS_DIR, configuredAgentPort, ensureLocalAgent, liveLoaderScript, pagesRouterOf, withRevyse };
|
|
87
|
+
export type { AgentModule, RevyseNextOptions };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import { existsSync, readFileSync } from "fs";
|
|
3
|
+
import { createRequire } from "module";
|
|
4
|
+
import path from "path";
|
|
5
|
+
var LOCATIONS_DIR = path.join("revyse", "locations");
|
|
6
|
+
var DEFAULT_AGENT_PORT = 4545;
|
|
7
|
+
var DEFAULT_APP_URL = "https://revyse.dev";
|
|
8
|
+
var PHASE_DEVELOPMENT_SERVER = "phase-development-server";
|
|
9
|
+
var PHASE_PRODUCTION_BUILD = "phase-production-build";
|
|
10
|
+
var require_ = createRequire(import.meta.url);
|
|
11
|
+
function configuredAgentPort(root) {
|
|
12
|
+
try {
|
|
13
|
+
const config = JSON.parse(readFileSync(path.join(root, "revyse.config.json"), "utf8"));
|
|
14
|
+
return typeof config.agentPort === "number" ? config.agentPort : void 0;
|
|
15
|
+
} catch {
|
|
16
|
+
return void 0;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
var say = (msg) => console.log(`[revyse] ${msg}`);
|
|
20
|
+
var warn = (msg) => console.warn(`[revyse] ${msg}`);
|
|
21
|
+
var agentStart;
|
|
22
|
+
function ensureLocalAgent(root, load = () => import("revyse/agent")) {
|
|
23
|
+
if (process.env.REVYSE_AGENT === "off") return Promise.resolve();
|
|
24
|
+
agentStart ??= startAgent(root, load).catch(
|
|
25
|
+
(err) => warn(
|
|
26
|
+
`Couldn't start Revyse (${err.message}). Check that \`revyse\` is installed as a dev dependency, or run \`npx revyse dev\`.`
|
|
27
|
+
)
|
|
28
|
+
);
|
|
29
|
+
return agentStart;
|
|
30
|
+
}
|
|
31
|
+
async function startAgent(root, load) {
|
|
32
|
+
if (!existsSync(path.join(root, "revyse.config.json"))) {
|
|
33
|
+
warn("This app isn't set up for Revyse yet: run `npx revyse init`.");
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
const { startLocalAgent } = await load();
|
|
37
|
+
const started = await startLocalAgent({ root, log: say });
|
|
38
|
+
if (started.status === "running") return;
|
|
39
|
+
if (started.status === "taken") {
|
|
40
|
+
warn(
|
|
41
|
+
started.root ? `Port ${started.port} is used by Revyse for ${started.root}, so the overlay here would talk to that project. Set "agentPort" in revyse.config.json to another port, such as ${started.port + 1}.` : `Port ${started.port} is in use by another program. Set "agentPort" in revyse.config.json to a free port.`
|
|
42
|
+
);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
say(
|
|
46
|
+
started.agent.connected ? "Ready: open your app and click the Revyse button." : "Ready, but this project isn't connected to Revyse yet: run `npx revyse init` to connect it."
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
function pagesRouterOf(root) {
|
|
50
|
+
for (const dir of ["pages", "src/pages"]) {
|
|
51
|
+
if (!existsSync(path.join(root, dir))) continue;
|
|
52
|
+
const hasDocument = ["tsx", "jsx", "ts", "js"].some(
|
|
53
|
+
(ext) => existsSync(path.join(root, dir, `_document.${ext}`))
|
|
54
|
+
);
|
|
55
|
+
return { dir, hasDocument };
|
|
56
|
+
}
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
var withImport = (entry, add) => {
|
|
60
|
+
const list = (v) => Array.isArray(v) ? v : [v];
|
|
61
|
+
if (typeof entry === "object" && !Array.isArray(entry))
|
|
62
|
+
return { ...entry, import: [...list(entry.import), add] };
|
|
63
|
+
return [...list(entry), add];
|
|
64
|
+
};
|
|
65
|
+
function liveLoaderScript(live) {
|
|
66
|
+
if (!/^pk_live_[0-9a-f]{32}$/.test(live.projectKey))
|
|
67
|
+
throw new Error(
|
|
68
|
+
"[revyse] live.projectKey must be the project's public key (pk_live_\u2026) from the Revyse project page"
|
|
69
|
+
);
|
|
70
|
+
const appUrl = live.appUrl ?? process.env.REVYSE_APP_URL ?? DEFAULT_APP_URL;
|
|
71
|
+
return { src: `${new URL(appUrl).origin}/live.js`, projectKey: live.projectKey };
|
|
72
|
+
}
|
|
73
|
+
function withRevyse(config = {}, options = {}) {
|
|
74
|
+
return async (phase, ctx) => {
|
|
75
|
+
const resolved = typeof config === "function" ? await config(phase, ctx) : config;
|
|
76
|
+
if (phase === PHASE_PRODUCTION_BUILD && options.live) {
|
|
77
|
+
return withLiveLoader(resolved, options.projectRoot ?? process.cwd(), options.live);
|
|
78
|
+
}
|
|
79
|
+
if (phase !== PHASE_DEVELOPMENT_SERVER || process.env.NODE_ENV === "production") {
|
|
80
|
+
return resolved;
|
|
81
|
+
}
|
|
82
|
+
const projectRoot = options.projectRoot ?? process.cwd();
|
|
83
|
+
const agentPort = options.agentPort ?? configuredAgentPort(projectRoot) ?? DEFAULT_AGENT_PORT;
|
|
84
|
+
const overlayScriptUrl = `http://127.0.0.1:${agentPort}/overlay.js`;
|
|
85
|
+
void ensureLocalAgent(projectRoot);
|
|
86
|
+
const loader = {
|
|
87
|
+
loader: require_.resolve("@revyse/next/loader"),
|
|
88
|
+
options: { projectRoot, overlayScriptUrl }
|
|
89
|
+
};
|
|
90
|
+
const pages = pagesRouterOf(projectRoot);
|
|
91
|
+
if (pages && !pages.hasDocument && process.env.TURBOPACK) {
|
|
92
|
+
console.warn(
|
|
93
|
+
`[revyse] ${pages.dir}/ has no custom _document, so with Turbopack Revyse can't load its overlay on Pages Router pages. Add ${pages.dir}/_document.tsx (Next's default document is fine), or run next dev with --webpack.`
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
const rule = {
|
|
97
|
+
loaders: [loader],
|
|
98
|
+
// Development only, and never third-party code.
|
|
99
|
+
condition: { all: ["development", { not: "foreign" }] }
|
|
100
|
+
};
|
|
101
|
+
const userWebpack = resolved.webpack;
|
|
102
|
+
return {
|
|
103
|
+
...resolved,
|
|
104
|
+
turbopack: {
|
|
105
|
+
...resolved.turbopack,
|
|
106
|
+
rules: { ...resolved.turbopack?.rules, "*.tsx": rule, "*.jsx": rule }
|
|
107
|
+
},
|
|
108
|
+
webpack(webpackConfig, context) {
|
|
109
|
+
const out = userWebpack ? userWebpack(webpackConfig, context) : webpackConfig;
|
|
110
|
+
if (context.dev) {
|
|
111
|
+
out.module.rules.unshift({
|
|
112
|
+
test: /\.(t|j)sx$/,
|
|
113
|
+
exclude: /node_modules/,
|
|
114
|
+
enforce: "pre",
|
|
115
|
+
use: [loader]
|
|
116
|
+
});
|
|
117
|
+
if (pages && !context.isServer) {
|
|
118
|
+
const client = `${require_.resolve("@revyse/next/client")}?${encodeURIComponent(overlayScriptUrl)}`;
|
|
119
|
+
const original = out.entry;
|
|
120
|
+
out.entry = async () => {
|
|
121
|
+
const entries = await (typeof original === "function" ? original() : original);
|
|
122
|
+
if (entries.main) entries.main = withImport(entries.main, client);
|
|
123
|
+
return entries;
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return out;
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
function withLiveLoader(resolved, projectRoot, live) {
|
|
133
|
+
const token = process.env.REVYSE_BUILD_TOKEN;
|
|
134
|
+
if (live.locations && !token?.startsWith("rvb_"))
|
|
135
|
+
console.warn(
|
|
136
|
+
"[revyse] live.locations is on but REVYSE_BUILD_TOKEN isn't set, so this build has no exact locations. Create a build token on the project's Live site tab and set it where the site builds."
|
|
137
|
+
);
|
|
138
|
+
const locations = live.locations && token?.startsWith("rvb_") ? {
|
|
139
|
+
token,
|
|
140
|
+
manifestDir: path.join(projectRoot, resolved.distDir ?? ".next", LOCATIONS_DIR)
|
|
141
|
+
} : void 0;
|
|
142
|
+
const loader = {
|
|
143
|
+
loader: require_.resolve("@revyse/next/loader"),
|
|
144
|
+
options: {
|
|
145
|
+
projectRoot,
|
|
146
|
+
liveScript: liveLoaderScript(live),
|
|
147
|
+
...locations ? { locations } : {}
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
const pages = pagesRouterOf(projectRoot);
|
|
151
|
+
if (pages && !pages.hasDocument) {
|
|
152
|
+
console.warn(
|
|
153
|
+
`[revyse] ${pages.dir}/ has no custom _document, so the live overlay's loader can't be added to Pages Router pages. Add ${pages.dir}/_document.tsx (Next's default document is fine).`
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
const rule = { loaders: [loader], condition: { not: "foreign" } };
|
|
157
|
+
const userWebpack = resolved.webpack;
|
|
158
|
+
return {
|
|
159
|
+
...resolved,
|
|
160
|
+
turbopack: {
|
|
161
|
+
...resolved.turbopack,
|
|
162
|
+
rules: { ...resolved.turbopack?.rules, "*.tsx": rule, "*.jsx": rule }
|
|
163
|
+
},
|
|
164
|
+
webpack(webpackConfig, context) {
|
|
165
|
+
const out = userWebpack ? userWebpack(webpackConfig, context) : webpackConfig;
|
|
166
|
+
out.module.rules.unshift({
|
|
167
|
+
test: /\.(t|j)sx$/,
|
|
168
|
+
exclude: /node_modules/,
|
|
169
|
+
enforce: "pre",
|
|
170
|
+
use: [loader]
|
|
171
|
+
});
|
|
172
|
+
return out;
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
export {
|
|
177
|
+
DEFAULT_AGENT_PORT,
|
|
178
|
+
DEFAULT_APP_URL,
|
|
179
|
+
LOCATIONS_DIR,
|
|
180
|
+
configuredAgentPort,
|
|
181
|
+
ensureLocalAgent,
|
|
182
|
+
liveLoaderScript,
|
|
183
|
+
pagesRouterOf,
|
|
184
|
+
withRevyse
|
|
185
|
+
};
|