@reticlehq/vite-plugin 2.12.0 → 2.13.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/dist/announce.d.ts +16 -0
- package/dist/announce.js +60 -0
- package/dist/discover-port.js +3 -3
- package/dist/index.cjs +140 -29
- package/dist/index.d.cts +34 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.js +51 -1
- package/dist/state-home.d.ts +1 -0
- package/dist/state-home.js +19 -0
- package/package.json +5 -5
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type DevServerEntry } from '@reticlehq/core';
|
|
2
|
+
/** The filesystem this needs, injected so the writer is unit-testable without a real home. */
|
|
3
|
+
export interface AnnounceIo {
|
|
4
|
+
mkdir: (dir: string) => void;
|
|
5
|
+
writeFile: (path: string, data: string) => void;
|
|
6
|
+
removeFile: (path: string) => void;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Publish the entry; returns the cleanup that withdraws it.
|
|
10
|
+
*
|
|
11
|
+
* The cleanup is idempotent because it is called from shutdown paths — `close`, `SIGINT`, process
|
|
12
|
+
* exit — which fire more than once and race each other. A stale entry is handled on the read side
|
|
13
|
+
* by a liveness check (`liveDevServers`), so a missed cleanup degrades rather than misleads; a
|
|
14
|
+
* throw inside a signal handler does not degrade, it takes the shutdown with it.
|
|
15
|
+
*/
|
|
16
|
+
export declare function announceDevServer(entry: DevServerEntry, home?: string, io?: AnnounceIo): () => void;
|
package/dist/announce.js
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Announce this dev server to `~/.reticle`, so setup can be diagnosed from facts rather than guesses.
|
|
3
|
+
*
|
|
4
|
+
* The mirror of `discover-port.ts`: that reads the daemon's `daemon-<port>.json` to find a bridge,
|
|
5
|
+
* this writes `devserver-<port>.json` so the bridge — and `reticle init` — can see that an
|
|
6
|
+
* instrumented dev server exists at all.
|
|
7
|
+
*
|
|
8
|
+
* Why this is the right half to add, rather than teaching `init` to run the dev command: the script
|
|
9
|
+
* name, the package manager, the port and the framework are all things a user or their agent can
|
|
10
|
+
* change, and a setup step that hardcodes any of them breaks on exactly the project that needed it.
|
|
11
|
+
* This plugin is ALREADY inside the dev server when it boots. It knows the port because it is being
|
|
12
|
+
* served on it, and it knows the URL because Vite reports it. Nothing here is assumed.
|
|
13
|
+
*
|
|
14
|
+
* Writing this file proves one specific thing and no more: the plugin is loaded in the process that
|
|
15
|
+
* is actually running. That is the fact nobody could observe, and the commonest setup failure — a
|
|
16
|
+
* plugin added to a config the running dev server already read — is precisely its absence.
|
|
17
|
+
*
|
|
18
|
+
* Best-effort throughout. This is a diagnostic, and a diagnostic that can break the dev server it
|
|
19
|
+
* reports on is worse than no diagnostic at all.
|
|
20
|
+
*/
|
|
21
|
+
import { mkdirSync, rmSync, writeFileSync } from 'node:fs';
|
|
22
|
+
import { join } from 'node:path';
|
|
23
|
+
import { devServerRegistryFileName } from '@reticlehq/core';
|
|
24
|
+
import { stateHome } from './state-home.js';
|
|
25
|
+
const JSON_INDENT = 2;
|
|
26
|
+
const nodeIo = {
|
|
27
|
+
mkdir: (dir) => void mkdirSync(dir, { recursive: true }),
|
|
28
|
+
writeFile: (path, data) => void writeFileSync(path, data),
|
|
29
|
+
removeFile: (path) => void rmSync(path, { force: true }),
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Publish the entry; returns the cleanup that withdraws it.
|
|
33
|
+
*
|
|
34
|
+
* The cleanup is idempotent because it is called from shutdown paths — `close`, `SIGINT`, process
|
|
35
|
+
* exit — which fire more than once and race each other. A stale entry is handled on the read side
|
|
36
|
+
* by a liveness check (`liveDevServers`), so a missed cleanup degrades rather than misleads; a
|
|
37
|
+
* throw inside a signal handler does not degrade, it takes the shutdown with it.
|
|
38
|
+
*/
|
|
39
|
+
export function announceDevServer(entry, home = stateHome(), io = nodeIo) {
|
|
40
|
+
const path = join(home, devServerRegistryFileName(entry.port));
|
|
41
|
+
try {
|
|
42
|
+
io.mkdir(home);
|
|
43
|
+
io.writeFile(path, `${JSON.stringify(entry, null, JSON_INDENT)}\n`);
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
// No home directory, a read-only filesystem, a sandbox. The app still serves; we just cannot say so.
|
|
47
|
+
}
|
|
48
|
+
let withdrawn = false;
|
|
49
|
+
return () => {
|
|
50
|
+
if (withdrawn)
|
|
51
|
+
return;
|
|
52
|
+
withdrawn = true;
|
|
53
|
+
try {
|
|
54
|
+
io.removeFile(path);
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
// Already gone, or never written. The reader's liveness check covers what we cannot remove.
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
}
|
package/dist/discover-port.js
CHANGED
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
* projectId, drop dead daemons — is the pure `pickDaemonPort` in core; this file is just the fs plumbing.
|
|
6
6
|
*/
|
|
7
7
|
import { readdirSync, readFileSync } from 'node:fs';
|
|
8
|
-
import { homedir } from 'node:os';
|
|
9
8
|
import { join } from 'node:path';
|
|
10
|
-
import { daemonRegistryPort, DaemonRegistryEntrySchema, pickDaemonPort,
|
|
9
|
+
import { daemonRegistryPort, DaemonRegistryEntrySchema, pickDaemonPort, } from '@reticlehq/core';
|
|
10
|
+
import { stateHome } from './state-home.js';
|
|
11
11
|
/** process.kill(pid, 0) throws iff the process is gone — the same liveness probe the daemon uses. */
|
|
12
12
|
function isAlive(pid) {
|
|
13
13
|
try {
|
|
@@ -23,7 +23,7 @@ function isAlive(pid) {
|
|
|
23
23
|
* to the default port — never auto-connects to a mismatched daemon). `home` and `alive` are injectable
|
|
24
24
|
* so the selection is unit-tested without a real ~/.reticle or real processes.
|
|
25
25
|
*/
|
|
26
|
-
export function discoverDaemonPort(projectId, home =
|
|
26
|
+
export function discoverDaemonPort(projectId, home = stateHome(), alive = isAlive) {
|
|
27
27
|
const entries = [];
|
|
28
28
|
let files;
|
|
29
29
|
try {
|
package/dist/index.cjs
CHANGED
|
@@ -44,7 +44,7 @@ __export(index_exports, {
|
|
|
44
44
|
reticle: () => reticle
|
|
45
45
|
});
|
|
46
46
|
module.exports = __toCommonJS(index_exports);
|
|
47
|
-
var
|
|
47
|
+
var import_node_fs6 = require("node:fs");
|
|
48
48
|
|
|
49
49
|
// src/missing-token.ts
|
|
50
50
|
function missingTokenWarning(token) {
|
|
@@ -78,8 +78,8 @@ function ensurePairingToken(dir) {
|
|
|
78
78
|
|
|
79
79
|
// src/index.ts
|
|
80
80
|
var import_node_os2 = require("node:os");
|
|
81
|
-
var
|
|
82
|
-
var
|
|
81
|
+
var import_node_path8 = require("node:path");
|
|
82
|
+
var import_core6 = require("@babel/core");
|
|
83
83
|
var import_babel_plugin = __toESM(require("@reticlehq/babel-plugin"), 1);
|
|
84
84
|
|
|
85
85
|
// ../core/dist/constants.js
|
|
@@ -243,6 +243,24 @@ var ReticleDir = {
|
|
|
243
243
|
AMBIENT_FILE: "ambient.json",
|
|
244
244
|
/** per-flow flake ledger — replay outcomes that decide intermittent-failure quarantine. */
|
|
245
245
|
FLAKE_FILE: "flake.json",
|
|
246
|
+
/**
|
|
247
|
+
* the project's cloud binding — .reticle/cloud.json, written by `reticle link`. Git-checked and
|
|
248
|
+
* non-secret: the project id, the API origin, and where its dashboard lives. The KEY lives in
|
|
249
|
+
* ~/.reticle/credentials.json instead, because that one must never reach a repository.
|
|
250
|
+
*/
|
|
251
|
+
CLOUD_LINK_FILE: "cloud.json",
|
|
252
|
+
/**
|
|
253
|
+
* local sync bookkeeping — .reticle/cloud-state.json. The pull cursor, when each half last ran,
|
|
254
|
+
* and the last error. NOT git-checked: it describes this machine's conversation with the server,
|
|
255
|
+
* and committing one machine's cursor would make every other machine skip what it had not seen.
|
|
256
|
+
*/
|
|
257
|
+
CLOUD_STATE_FILE: "cloud-state.json",
|
|
258
|
+
/**
|
|
259
|
+
* triage decisions pulled BACK from the dashboard — .reticle/issues.json. What a human said about
|
|
260
|
+
* a defect ("resolved", "not a bug"), so the HUD stops showing it and the next run does not
|
|
261
|
+
* re-report it as though nobody had looked.
|
|
262
|
+
*/
|
|
263
|
+
ISSUES_FILE: "issues.json",
|
|
246
264
|
/** Per-flow assertion tiers recorded on each PASSING replay — the gate's anti-downgrade baseline. */
|
|
247
265
|
TIERS_FILE: "assertion-tiers.json",
|
|
248
266
|
/**
|
|
@@ -4453,6 +4471,35 @@ function pickDaemonPort(entries, projectId, isAlive2) {
|
|
|
4453
4471
|
return matches[0] ?? null;
|
|
4454
4472
|
}
|
|
4455
4473
|
|
|
4474
|
+
// ../core/dist/dev-server-registry.js
|
|
4475
|
+
var DEV_SERVER_PREFIX = "devserver-";
|
|
4476
|
+
var DEV_SERVER_SUFFIX = ".json";
|
|
4477
|
+
function devServerRegistryFileName(port) {
|
|
4478
|
+
return `${DEV_SERVER_PREFIX}${String(port)}${DEV_SERVER_SUFFIX}`;
|
|
4479
|
+
}
|
|
4480
|
+
var DevServerEntrySchema = external_exports.object({
|
|
4481
|
+
port: external_exports.number(),
|
|
4482
|
+
pid: external_exports.number(),
|
|
4483
|
+
/** The project directory the dev server is serving — how a monorepo tells its apps apart. */
|
|
4484
|
+
root: external_exports.string(),
|
|
4485
|
+
/** Where the app is actually served, as the dev server itself reports it. Never assembled here. */
|
|
4486
|
+
url: external_exports.string(),
|
|
4487
|
+
/**
|
|
4488
|
+
* The SDK version in the bundle, so a skew can be named rather than guessed at.
|
|
4489
|
+
*
|
|
4490
|
+
* Optional, and ABSENT rather than empty when it cannot be resolved. `""` reads as "the version is
|
|
4491
|
+
* empty"; a missing field reads as "not known", which is the true statement — and the difference
|
|
4492
|
+
* matters to the one reader who exists for this field, a skew diagnosis.
|
|
4493
|
+
*/
|
|
4494
|
+
sdkVersion: external_exports.string().min(1).optional(),
|
|
4495
|
+
startedAt: external_exports.number(),
|
|
4496
|
+
/**
|
|
4497
|
+
* Optional because an app can be running before `init` has ever named it — which is exactly the
|
|
4498
|
+
* state this signal has to be able to describe.
|
|
4499
|
+
*/
|
|
4500
|
+
projectId: external_exports.string().optional()
|
|
4501
|
+
});
|
|
4502
|
+
|
|
4456
4503
|
// src/project-id.ts
|
|
4457
4504
|
var import_node_crypto2 = require("node:crypto");
|
|
4458
4505
|
var import_node_path2 = require("node:path");
|
|
@@ -4490,8 +4537,17 @@ function resolveProjectId(explicit, cwd, readPkgName = readNearestPackageName) {
|
|
|
4490
4537
|
|
|
4491
4538
|
// src/discover-port.ts
|
|
4492
4539
|
var import_node_fs3 = require("node:fs");
|
|
4540
|
+
var import_node_path4 = require("node:path");
|
|
4541
|
+
|
|
4542
|
+
// src/state-home.ts
|
|
4493
4543
|
var import_node_os = require("node:os");
|
|
4494
4544
|
var import_node_path3 = require("node:path");
|
|
4545
|
+
function stateHome(env = process.env) {
|
|
4546
|
+
const override = env[ReticleEnv.STATE_DIR];
|
|
4547
|
+
return override !== void 0 && override.length > 0 ? override : (0, import_node_path3.join)((0, import_node_os.homedir)(), ReticleDir.ROOT);
|
|
4548
|
+
}
|
|
4549
|
+
|
|
4550
|
+
// src/discover-port.ts
|
|
4495
4551
|
function isAlive(pid) {
|
|
4496
4552
|
try {
|
|
4497
4553
|
process.kill(pid, 0);
|
|
@@ -4500,7 +4556,7 @@ function isAlive(pid) {
|
|
|
4500
4556
|
return false;
|
|
4501
4557
|
}
|
|
4502
4558
|
}
|
|
4503
|
-
function discoverDaemonPort(projectId, home = (
|
|
4559
|
+
function discoverDaemonPort(projectId, home = stateHome(), alive = isAlive) {
|
|
4504
4560
|
const entries = [];
|
|
4505
4561
|
let files;
|
|
4506
4562
|
try {
|
|
@@ -4512,7 +4568,7 @@ function discoverDaemonPort(projectId, home = (0, import_node_path3.join)((0, im
|
|
|
4512
4568
|
if (null === daemonRegistryPort(file)) continue;
|
|
4513
4569
|
try {
|
|
4514
4570
|
const parsed = DaemonRegistryEntrySchema.safeParse(
|
|
4515
|
-
JSON.parse((0, import_node_fs3.readFileSync)((0,
|
|
4571
|
+
JSON.parse((0, import_node_fs3.readFileSync)((0, import_node_path4.join)(home, file), "utf8"))
|
|
4516
4572
|
);
|
|
4517
4573
|
if (parsed.success) entries.push(parsed.data);
|
|
4518
4574
|
} catch {
|
|
@@ -4521,9 +4577,37 @@ function discoverDaemonPort(projectId, home = (0, import_node_path3.join)((0, im
|
|
|
4521
4577
|
return pickDaemonPort(entries, projectId, alive) ?? void 0;
|
|
4522
4578
|
}
|
|
4523
4579
|
|
|
4580
|
+
// src/announce.ts
|
|
4581
|
+
var import_node_fs4 = require("node:fs");
|
|
4582
|
+
var import_node_path5 = require("node:path");
|
|
4583
|
+
var JSON_INDENT = 2;
|
|
4584
|
+
var nodeIo = {
|
|
4585
|
+
mkdir: (dir) => void (0, import_node_fs4.mkdirSync)(dir, { recursive: true }),
|
|
4586
|
+
writeFile: (path, data) => void (0, import_node_fs4.writeFileSync)(path, data),
|
|
4587
|
+
removeFile: (path) => void (0, import_node_fs4.rmSync)(path, { force: true })
|
|
4588
|
+
};
|
|
4589
|
+
function announceDevServer(entry, home = stateHome(), io = nodeIo) {
|
|
4590
|
+
const path = (0, import_node_path5.join)(home, devServerRegistryFileName(entry.port));
|
|
4591
|
+
try {
|
|
4592
|
+
io.mkdir(home);
|
|
4593
|
+
io.writeFile(path, `${JSON.stringify(entry, null, JSON_INDENT)}
|
|
4594
|
+
`);
|
|
4595
|
+
} catch {
|
|
4596
|
+
}
|
|
4597
|
+
let withdrawn = false;
|
|
4598
|
+
return () => {
|
|
4599
|
+
if (withdrawn) return;
|
|
4600
|
+
withdrawn = true;
|
|
4601
|
+
try {
|
|
4602
|
+
io.removeFile(path);
|
|
4603
|
+
} catch {
|
|
4604
|
+
}
|
|
4605
|
+
};
|
|
4606
|
+
}
|
|
4607
|
+
|
|
4524
4608
|
// src/svelte-source.ts
|
|
4525
4609
|
var import_node_module = require("node:module");
|
|
4526
|
-
var
|
|
4610
|
+
var import_node_path6 = require("node:path");
|
|
4527
4611
|
var SVELTE_FILE = /\.svelte$/;
|
|
4528
4612
|
var HOST_ELEMENT_TYPES = /* @__PURE__ */ new Set(["RegularElement", "Element"]);
|
|
4529
4613
|
var PARENT_KEY = "parent";
|
|
@@ -4552,7 +4636,7 @@ function offsetToLineColumn(source, offset) {
|
|
|
4552
4636
|
return { line, column: offset - lineStart };
|
|
4553
4637
|
}
|
|
4554
4638
|
function sourcePathFor(id, cwd = process.cwd()) {
|
|
4555
|
-
return (0,
|
|
4639
|
+
return (0, import_node_path6.relative)(cwd, id).replace(/\\/g, "/");
|
|
4556
4640
|
}
|
|
4557
4641
|
function isElementNode(value) {
|
|
4558
4642
|
if (null === value || typeof value !== "object") return false;
|
|
@@ -4606,25 +4690,25 @@ function stampSvelte(code, id, load = defaultLoadCompiler) {
|
|
|
4606
4690
|
}
|
|
4607
4691
|
|
|
4608
4692
|
// src/installed.ts
|
|
4609
|
-
var
|
|
4610
|
-
var
|
|
4693
|
+
var import_node_fs5 = require("node:fs");
|
|
4694
|
+
var import_node_path7 = require("node:path");
|
|
4611
4695
|
var import_node_module2 = require("node:module");
|
|
4612
4696
|
var RETICLE_PACKAGE = "@reticlehq/react";
|
|
4613
4697
|
var MANIFEST_SEARCH_DEPTH = 5;
|
|
4614
4698
|
function requireFromApp(from) {
|
|
4615
|
-
const absoluteFrom = (0,
|
|
4616
|
-
return (0, import_node_module2.createRequire)((0,
|
|
4699
|
+
const absoluteFrom = (0, import_node_path7.isAbsolute)(from) ? from : (0, import_node_path7.resolve)(process.cwd(), from);
|
|
4700
|
+
return (0, import_node_module2.createRequire)((0, import_node_path7.join)(absoluteFrom, "package.json"));
|
|
4617
4701
|
}
|
|
4618
4702
|
function packageDirOf(specifier, from) {
|
|
4619
4703
|
try {
|
|
4620
|
-
let dir = (0,
|
|
4704
|
+
let dir = (0, import_node_path7.dirname)(requireFromApp(from).resolve(specifier));
|
|
4621
4705
|
for (let up = 0; up < MANIFEST_SEARCH_DEPTH; up++) {
|
|
4622
|
-
const candidate = (0,
|
|
4623
|
-
if ((0,
|
|
4624
|
-
const parsed = JSON.parse((0,
|
|
4706
|
+
const candidate = (0, import_node_path7.join)(dir, "package.json");
|
|
4707
|
+
if ((0, import_node_fs5.existsSync)(candidate)) {
|
|
4708
|
+
const parsed = JSON.parse((0, import_node_fs5.readFileSync)(candidate, "utf8"));
|
|
4625
4709
|
if (parsed.name === specifier) return dir;
|
|
4626
4710
|
}
|
|
4627
|
-
const parent = (0,
|
|
4711
|
+
const parent = (0, import_node_path7.dirname)(dir);
|
|
4628
4712
|
if (parent === dir) break;
|
|
4629
4713
|
dir = parent;
|
|
4630
4714
|
}
|
|
@@ -4649,14 +4733,14 @@ function sdkPackageVersion(from = process.cwd()) {
|
|
|
4649
4733
|
} catch {
|
|
4650
4734
|
}
|
|
4651
4735
|
try {
|
|
4652
|
-
let dir = (0,
|
|
4736
|
+
let dir = (0, import_node_path7.dirname)(require_.resolve(RETICLE_PACKAGE));
|
|
4653
4737
|
for (let up = 0; up < MANIFEST_SEARCH_DEPTH; up++) {
|
|
4654
|
-
const candidate = (0,
|
|
4655
|
-
if ((0,
|
|
4656
|
-
const parsed = JSON.parse((0,
|
|
4738
|
+
const candidate = (0, import_node_path7.join)(dir, "package.json");
|
|
4739
|
+
if ((0, import_node_fs5.existsSync)(candidate)) {
|
|
4740
|
+
const parsed = JSON.parse((0, import_node_fs5.readFileSync)(candidate, "utf8"));
|
|
4657
4741
|
if ("string" === typeof parsed.version) return parsed.version;
|
|
4658
4742
|
}
|
|
4659
|
-
const parent = (0,
|
|
4743
|
+
const parent = (0, import_node_path7.dirname)(dir);
|
|
4660
4744
|
if (parent === dir) break;
|
|
4661
4745
|
dir = parent;
|
|
4662
4746
|
}
|
|
@@ -4667,7 +4751,7 @@ function sdkPackageVersion(from = process.cwd()) {
|
|
|
4667
4751
|
function sdkBuildFingerprint(from = process.cwd()) {
|
|
4668
4752
|
try {
|
|
4669
4753
|
const entry = requireFromApp(from).resolve(RETICLE_PACKAGE);
|
|
4670
|
-
const { size, mtimeMs } = (0,
|
|
4754
|
+
const { size, mtimeMs } = (0, import_node_fs5.statSync)(entry);
|
|
4671
4755
|
return `${String(size)}-${String(Math.trunc(mtimeMs))}`;
|
|
4672
4756
|
} catch {
|
|
4673
4757
|
return "unknown";
|
|
@@ -4753,7 +4837,7 @@ function shouldStampSvelte(id) {
|
|
|
4753
4837
|
return clean !== null && SVELTE_FILE.test(clean);
|
|
4754
4838
|
}
|
|
4755
4839
|
function stamp(code, id) {
|
|
4756
|
-
const out = (0,
|
|
4840
|
+
const out = (0, import_core6.transformSync)(code, {
|
|
4757
4841
|
filename: id,
|
|
4758
4842
|
plugins: [import_babel_plugin.default],
|
|
4759
4843
|
parserOpts: { plugins: ["jsx", "typescript"] },
|
|
@@ -4769,7 +4853,7 @@ function stamp(code, id) {
|
|
|
4769
4853
|
}
|
|
4770
4854
|
function readPairingToken() {
|
|
4771
4855
|
const override = process.env[ReticleEnv.PAIRING_TOKEN_DIR];
|
|
4772
|
-
const dir = override !== void 0 && override.length > 0 ? override : (0,
|
|
4856
|
+
const dir = override !== void 0 && override.length > 0 ? override : (0, import_node_path8.join)((0, import_node_os2.homedir)(), ReticleDir.ROOT);
|
|
4773
4857
|
return ensurePairingToken(dir);
|
|
4774
4858
|
}
|
|
4775
4859
|
var tokenWarned = false;
|
|
@@ -4796,6 +4880,9 @@ function connectArgs(options) {
|
|
|
4796
4880
|
if (true === options.captureNetworkBodies || "1" === process.env["VITE_RETICLE_CAPTURE_BODIES"]) {
|
|
4797
4881
|
args["captureNetworkBodies"] = true;
|
|
4798
4882
|
}
|
|
4883
|
+
if (true === options.exposePresenter || "1" === process.env["VITE_RETICLE_EXPOSE_PRESENTER"]) {
|
|
4884
|
+
args["exposePresenter"] = true;
|
|
4885
|
+
}
|
|
4799
4886
|
if (true === options.allowNonLocalhost || "1" === process.env["VITE_RETICLE_ALLOW_NON_LOCALHOST"]) {
|
|
4800
4887
|
args["allowNonLocalhost"] = true;
|
|
4801
4888
|
}
|
|
@@ -4815,9 +4902,9 @@ function findDevModule(root, exists) {
|
|
|
4815
4902
|
}
|
|
4816
4903
|
function installedSdk(appRoot, canResolve = (dep) => null !== resolvableChain([dep], appRoot)) {
|
|
4817
4904
|
try {
|
|
4818
|
-
const pkgPath = (0,
|
|
4819
|
-
if ((0,
|
|
4820
|
-
const pkg = JSON.parse((0,
|
|
4905
|
+
const pkgPath = (0, import_node_path8.join)(appRoot, "package.json");
|
|
4906
|
+
if ((0, import_node_fs6.existsSync)(pkgPath)) {
|
|
4907
|
+
const pkg = JSON.parse((0, import_node_fs6.readFileSync)(pkgPath, "utf8"));
|
|
4821
4908
|
const deps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
|
|
4822
4909
|
if (deps[RETICLE_SENSOR] !== void 0 && deps[RETICLE_PACKAGE2] === void 0) {
|
|
4823
4910
|
return { specifier: RETICLE_SENSOR, usesInstall: false };
|
|
@@ -4870,10 +4957,10 @@ function reticle(options = {}) {
|
|
|
4870
4957
|
const sdkVersion = withToken.sdkVersion ?? sdkPackageVersion(appRoot);
|
|
4871
4958
|
return { ...withToken, root: appRoot, sdkVersion };
|
|
4872
4959
|
};
|
|
4873
|
-
const currentConnectSource = () => connectModuleSource(resolveLazy(), root === void 0 ? null : findDevModule(root,
|
|
4960
|
+
const currentConnectSource = () => connectModuleSource(resolveLazy(), root === void 0 ? null : findDevModule(root, import_node_fs6.existsSync));
|
|
4874
4961
|
let lastServedConnectSource;
|
|
4875
4962
|
let connectChanges = 0;
|
|
4876
|
-
const notInjectedMessage = () => `[${RETICLE_VITE_PLUGIN_NAME}] could not inject reticle.connect(): the HTML entry module was never matched, so this app carries no instrumentation and will never connect. Check that index.html references your entry with a <script type="module" src="...">, or pass \`inject: false\` and call reticle.connect() yourself.`;
|
|
4963
|
+
const notInjectedMessage = () => `[${RETICLE_VITE_PLUGIN_NAME}] could not inject reticle.connect(): the HTML entry module was never matched, so this app carries no instrumentation and will never connect. Check that index.html references your entry with a <script type="module" src="...">, or pass \`inject: false\` and call reticle.connect({ token: __RETICLE_TOKEN__ }) yourself. The plugin still inlines that define; a connect without it is refused.`;
|
|
4877
4964
|
const unconfirmedInjectionMessage = () => `[${RETICLE_VITE_PLUGIN_NAME}] could not confirm reticle.connect() was injected: the HTML entry module was not transformed this session. That is expected when Vite served it from its transform cache. If the app does not appear in \`reticle status\`, restart the dev server with \`--force\` to bypass the cache, then check that index.html references your entry with a <script type="module" src="...">.`;
|
|
4878
4965
|
const checkInjected = () => {
|
|
4879
4966
|
if (!desktop || !inject || injected) return;
|
|
@@ -5036,6 +5123,30 @@ ${code}`;
|
|
|
5036
5123
|
*/
|
|
5037
5124
|
configureServer(server) {
|
|
5038
5125
|
if (!inject) return;
|
|
5126
|
+
const announce = () => {
|
|
5127
|
+
const address = server.httpServer?.address();
|
|
5128
|
+
const port = "object" === typeof address && null !== address && void 0 !== address ? address.port : void 0;
|
|
5129
|
+
if (void 0 === port) return;
|
|
5130
|
+
const opts = resolveLazy();
|
|
5131
|
+
const withdraw = announceDevServer({
|
|
5132
|
+
port,
|
|
5133
|
+
pid: process.pid,
|
|
5134
|
+
root: opts.root ?? process.cwd(),
|
|
5135
|
+
url: server.resolvedUrls?.local[0] ?? `http://localhost:${String(port)}/`,
|
|
5136
|
+
...opts.sdkVersion === void 0 || 0 === opts.sdkVersion.length ? {} : { sdkVersion: opts.sdkVersion },
|
|
5137
|
+
startedAt: Date.now(),
|
|
5138
|
+
...opts.projectId === void 0 ? {} : { projectId: opts.projectId }
|
|
5139
|
+
});
|
|
5140
|
+
server.httpServer?.once("close", withdraw);
|
|
5141
|
+
process.once("exit", withdraw);
|
|
5142
|
+
process.once("SIGINT", withdraw);
|
|
5143
|
+
process.once("SIGTERM", withdraw);
|
|
5144
|
+
};
|
|
5145
|
+
if (null === server.httpServer?.address() || void 0 === server.httpServer?.address()) {
|
|
5146
|
+
server.httpServer?.once("listening", announce);
|
|
5147
|
+
} else {
|
|
5148
|
+
announce();
|
|
5149
|
+
}
|
|
5039
5150
|
server.middlewares.use((req, _res, next) => {
|
|
5040
5151
|
if ((req.url ?? "").split("?")[0] === RETICLE_CONNECT_MODULE) {
|
|
5041
5152
|
if (currentConnectSource() !== lastServedConnectSource) {
|
package/dist/index.d.cts
CHANGED
|
@@ -84,6 +84,20 @@ export interface ReticleVitePluginOptions {
|
|
|
84
84
|
* session without editing vite.config.
|
|
85
85
|
*/
|
|
86
86
|
captureNetworkBodies?: boolean;
|
|
87
|
+
/**
|
|
88
|
+
* Make Reticle's OWN presenter visible to snapshots and queries. CONTRIBUTORS ONLY.
|
|
89
|
+
*
|
|
90
|
+
* Reachable here for the same reason `captureNetworkBodies` is: the plugin is the only `connect()`
|
|
91
|
+
* most apps ever have, so an SDK option the plugin cannot pass is an option that does not exist.
|
|
92
|
+
*
|
|
93
|
+
* The presenter is hidden from every tool by design — an agent that can drive Reticle's own
|
|
94
|
+
* interface can fabricate its own impact report. The cost is that a HUD change is the only kind of
|
|
95
|
+
* change Reticle cannot be used to check. This is the hatch for that one case, and the app reports
|
|
96
|
+
* it in its capabilities so a verdict drawn with it open is never mistaken for an ordinary one.
|
|
97
|
+
*
|
|
98
|
+
* Also settable as `VITE_RETICLE_EXPOSE_PRESENTER=1`.
|
|
99
|
+
*/
|
|
100
|
+
exposePresenter?: boolean;
|
|
87
101
|
/**
|
|
88
102
|
* Let Reticle run when the page or the bridge is not on localhost.
|
|
89
103
|
*
|
|
@@ -171,6 +185,26 @@ export interface ReticleVitePlugin {
|
|
|
171
185
|
* plugin never imports, the same way the Svelte compiler and Playwright are handled elsewhere.
|
|
172
186
|
*/
|
|
173
187
|
export interface ViteDevServerLike {
|
|
188
|
+
/**
|
|
189
|
+
* Vite's own HTTP server, for the port it ACTUALLY bound and the moment it bound it. Optional and
|
|
190
|
+
* nullable because middleware mode has none — and because a structural stand-in that demands more
|
|
191
|
+
* than Vite guarantees stops being assignable, which red-builds every typechecked config.
|
|
192
|
+
*/
|
|
193
|
+
httpServer?: {
|
|
194
|
+
once(event: string, listener: () => void): unknown;
|
|
195
|
+
address(): string | {
|
|
196
|
+
port: number;
|
|
197
|
+
} | null;
|
|
198
|
+
} | null;
|
|
199
|
+
/**
|
|
200
|
+
* The URLs Vite prints on boot. Read rather than assembled: host, protocol and base are all
|
|
201
|
+
* configurable, so composing a URL here would be a guess about the one thing the dev server can
|
|
202
|
+
* simply be asked.
|
|
203
|
+
*/
|
|
204
|
+
resolvedUrls?: {
|
|
205
|
+
local: string[];
|
|
206
|
+
network: string[];
|
|
207
|
+
} | null;
|
|
174
208
|
middlewares: {
|
|
175
209
|
use(handler: (req: {
|
|
176
210
|
url?: string | undefined;
|
package/dist/index.d.ts
CHANGED
|
@@ -84,6 +84,20 @@ export interface ReticleVitePluginOptions {
|
|
|
84
84
|
* session without editing vite.config.
|
|
85
85
|
*/
|
|
86
86
|
captureNetworkBodies?: boolean;
|
|
87
|
+
/**
|
|
88
|
+
* Make Reticle's OWN presenter visible to snapshots and queries. CONTRIBUTORS ONLY.
|
|
89
|
+
*
|
|
90
|
+
* Reachable here for the same reason `captureNetworkBodies` is: the plugin is the only `connect()`
|
|
91
|
+
* most apps ever have, so an SDK option the plugin cannot pass is an option that does not exist.
|
|
92
|
+
*
|
|
93
|
+
* The presenter is hidden from every tool by design — an agent that can drive Reticle's own
|
|
94
|
+
* interface can fabricate its own impact report. The cost is that a HUD change is the only kind of
|
|
95
|
+
* change Reticle cannot be used to check. This is the hatch for that one case, and the app reports
|
|
96
|
+
* it in its capabilities so a verdict drawn with it open is never mistaken for an ordinary one.
|
|
97
|
+
*
|
|
98
|
+
* Also settable as `VITE_RETICLE_EXPOSE_PRESENTER=1`.
|
|
99
|
+
*/
|
|
100
|
+
exposePresenter?: boolean;
|
|
87
101
|
/**
|
|
88
102
|
* Let Reticle run when the page or the bridge is not on localhost.
|
|
89
103
|
*
|
|
@@ -171,6 +185,26 @@ export interface ReticleVitePlugin {
|
|
|
171
185
|
* plugin never imports, the same way the Svelte compiler and Playwright are handled elsewhere.
|
|
172
186
|
*/
|
|
173
187
|
export interface ViteDevServerLike {
|
|
188
|
+
/**
|
|
189
|
+
* Vite's own HTTP server, for the port it ACTUALLY bound and the moment it bound it. Optional and
|
|
190
|
+
* nullable because middleware mode has none — and because a structural stand-in that demands more
|
|
191
|
+
* than Vite guarantees stops being assignable, which red-builds every typechecked config.
|
|
192
|
+
*/
|
|
193
|
+
httpServer?: {
|
|
194
|
+
once(event: string, listener: () => void): unknown;
|
|
195
|
+
address(): string | {
|
|
196
|
+
port: number;
|
|
197
|
+
} | null;
|
|
198
|
+
} | null;
|
|
199
|
+
/**
|
|
200
|
+
* The URLs Vite prints on boot. Read rather than assembled: host, protocol and base are all
|
|
201
|
+
* configurable, so composing a URL here would be a guess about the one thing the dev server can
|
|
202
|
+
* simply be asked.
|
|
203
|
+
*/
|
|
204
|
+
resolvedUrls?: {
|
|
205
|
+
local: string[];
|
|
206
|
+
network: string[];
|
|
207
|
+
} | null;
|
|
174
208
|
middlewares: {
|
|
175
209
|
use(handler: (req: {
|
|
176
210
|
url?: string | undefined;
|
package/dist/index.js
CHANGED
|
@@ -8,6 +8,7 @@ import reticleSource from '@reticlehq/babel-plugin';
|
|
|
8
8
|
import { RETICLE_DEFAULT_PORT, RETICLE_RENDER_PREHOOK, bridgeWsUrl, ReticleDir, ReticleEnv, RETICLE_ROOT_GLOBAL, RETICLE_SDK_VERSION_GLOBAL, } from '@reticlehq/core';
|
|
9
9
|
import { resolveProjectId } from './project-id.js';
|
|
10
10
|
import { discoverDaemonPort } from './discover-port.js';
|
|
11
|
+
import { announceDevServer } from './announce.js';
|
|
11
12
|
import { SVELTE_FILE, stampSvelte } from './svelte-source.js';
|
|
12
13
|
import { resolvableChain, sdkPackageVersion, sdkBuildFingerprint, viteMajor, optimizerOptionsKey, optimizerOptions, } from './installed.js';
|
|
13
14
|
export const RETICLE_VITE_PLUGIN_NAME = 'reticle';
|
|
@@ -206,6 +207,10 @@ function connectArgs(options) {
|
|
|
206
207
|
if (true === options.captureNetworkBodies || '1' === process.env['VITE_RETICLE_CAPTURE_BODIES']) {
|
|
207
208
|
args['captureNetworkBodies'] = true;
|
|
208
209
|
}
|
|
210
|
+
// Same shape, same reason. Off unless asked for, in a config or for one session.
|
|
211
|
+
if (true === options.exposePresenter || '1' === process.env['VITE_RETICLE_EXPOSE_PRESENTER']) {
|
|
212
|
+
args['exposePresenter'] = true;
|
|
213
|
+
}
|
|
209
214
|
// Same shape, same reason: without it an app that cannot be served on localhost has no way to
|
|
210
215
|
// reach the SDK option at all. The pairing token still applies — see the option's docstring.
|
|
211
216
|
if (true === options.allowNonLocalhost ||
|
|
@@ -375,7 +380,8 @@ export function reticle(options = {}) {
|
|
|
375
380
|
const notInjectedMessage = () => `[${RETICLE_VITE_PLUGIN_NAME}] could not inject reticle.connect(): the HTML entry module was ` +
|
|
376
381
|
'never matched, so this app carries no instrumentation and will never connect. Check that ' +
|
|
377
382
|
'index.html references your entry with a <script type="module" src="...">, or pass ' +
|
|
378
|
-
'`inject: false` and call reticle.connect() yourself.'
|
|
383
|
+
'`inject: false` and call reticle.connect({ token: __RETICLE_TOKEN__ }) yourself. The plugin ' +
|
|
384
|
+
'still inlines that define; a connect without it is refused.';
|
|
379
385
|
/**
|
|
380
386
|
* The DEV message, which must be weaker — and this is the whole reason the two are separate.
|
|
381
387
|
*
|
|
@@ -572,6 +578,50 @@ export function reticle(options = {}) {
|
|
|
572
578
|
configureServer(server) {
|
|
573
579
|
if (!inject)
|
|
574
580
|
return;
|
|
581
|
+
// Tell `~/.reticle` this dev server exists, the moment it is actually listening.
|
|
582
|
+
//
|
|
583
|
+
// This is the one fact nobody outside this process could observe: the plugin is loaded in the
|
|
584
|
+
// dev server that is RUNNING, not merely present in a config file on disk. Its absence is the
|
|
585
|
+
// commonest setup failure there is — a plugin added to a config the running server already
|
|
586
|
+
// read — and until now that failure was indistinguishable from every other one.
|
|
587
|
+
//
|
|
588
|
+
// Deliberately reads the port and URL off the server rather than composing them. The port may
|
|
589
|
+
// be `strictPort: false` and have moved, the host and base are configurable, and every one of
|
|
590
|
+
// those is something the user can change under us.
|
|
591
|
+
const announce = () => {
|
|
592
|
+
const address = server.httpServer?.address();
|
|
593
|
+
const port = 'object' === typeof address && null !== address && undefined !== address
|
|
594
|
+
? address.port
|
|
595
|
+
: undefined;
|
|
596
|
+
if (undefined === port)
|
|
597
|
+
return;
|
|
598
|
+
const opts = resolveLazy();
|
|
599
|
+
const withdraw = announceDevServer({
|
|
600
|
+
port,
|
|
601
|
+
pid: process.pid,
|
|
602
|
+
root: opts.root ?? process.cwd(),
|
|
603
|
+
url: server.resolvedUrls?.local[0] ?? `http://localhost:${String(port)}/`,
|
|
604
|
+
...(opts.sdkVersion === undefined || 0 === opts.sdkVersion.length
|
|
605
|
+
? {}
|
|
606
|
+
: { sdkVersion: opts.sdkVersion }),
|
|
607
|
+
startedAt: Date.now(),
|
|
608
|
+
...(opts.projectId === undefined ? {} : { projectId: opts.projectId }),
|
|
609
|
+
});
|
|
610
|
+
server.httpServer?.once('close', withdraw);
|
|
611
|
+
// `close` does not fire on Ctrl-C, which is how a dev server usually dies. The read side
|
|
612
|
+
// checks liveness anyway, so a missed withdrawal degrades rather than lies — these just
|
|
613
|
+
// keep the directory tidy in the cases we can catch.
|
|
614
|
+
process.once('exit', withdraw);
|
|
615
|
+
process.once('SIGINT', withdraw);
|
|
616
|
+
process.once('SIGTERM', withdraw);
|
|
617
|
+
};
|
|
618
|
+
// Already bound in some setups (middleware mode, a restart), not yet in the common one.
|
|
619
|
+
if (null === server.httpServer?.address() || undefined === server.httpServer?.address()) {
|
|
620
|
+
server.httpServer?.once('listening', announce);
|
|
621
|
+
}
|
|
622
|
+
else {
|
|
623
|
+
announce();
|
|
624
|
+
}
|
|
575
625
|
server.middlewares.use((req, _res, next) => {
|
|
576
626
|
if ((req.url ?? '').split('?')[0] === RETICLE_CONNECT_MODULE) {
|
|
577
627
|
if (currentConnectSource() !== lastServedConnectSource) {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function stateHome(env?: NodeJS.ProcessEnv): string;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where Reticle keeps machine state — the one answer, for both halves of discovery.
|
|
3
|
+
*
|
|
4
|
+
* `RETICLE_STATE_DIR` already exists and already relocates the daemon's pidfiles, logs and discovery
|
|
5
|
+
* registry (a read-only `$HOME` — sandboxed agent, locked-down Windows profile, container — makes
|
|
6
|
+
* the default unwritable). The plugin's reader did not honour it: with the variable set, the daemon
|
|
7
|
+
* wrote its registry to one directory while `discoverDaemonPort` looked in another, so discovery
|
|
8
|
+
* silently found nothing and every app fell back to the default port. Exactly the class of split
|
|
9
|
+
* this file exists to prevent — two halves of one mechanism with two ideas of where it lives.
|
|
10
|
+
*/
|
|
11
|
+
import { homedir } from 'node:os';
|
|
12
|
+
import { join } from 'node:path';
|
|
13
|
+
import { ReticleDir, ReticleEnv } from '@reticlehq/core';
|
|
14
|
+
export function stateHome(env = process.env) {
|
|
15
|
+
const override = env[ReticleEnv.STATE_DIR];
|
|
16
|
+
return override !== undefined && override.length > 0
|
|
17
|
+
? override
|
|
18
|
+
: join(homedir(), ReticleDir.ROOT);
|
|
19
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reticlehq/vite-plugin",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.13.0",
|
|
4
4
|
"description": "Vite plugin for Reticle: dev-only source-map stamping plus auto-injected reticle.connect(). apply:'serve' guarantees it never ships to production.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -41,14 +41,14 @@
|
|
|
41
41
|
],
|
|
42
42
|
"dependencies": {
|
|
43
43
|
"@babel/core": "^7.26.0",
|
|
44
|
-
"@reticlehq/
|
|
45
|
-
"@reticlehq/
|
|
44
|
+
"@reticlehq/core": "2.13.0",
|
|
45
|
+
"@reticlehq/babel-plugin": "2.13.0"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
48
|
"@types/babel__core": "^7.20.5",
|
|
49
|
-
"svelte": "^5.56.
|
|
49
|
+
"svelte": "^5.56.10",
|
|
50
50
|
"vite": "^8",
|
|
51
|
-
"esbuild": "^0.28.
|
|
51
|
+
"esbuild": "^0.28.2"
|
|
52
52
|
},
|
|
53
53
|
"peerDependencies": {
|
|
54
54
|
"vite": ">=4"
|