visual-remote 0.1.3 → 0.3.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 +56 -53
- package/apps/cli/dist/index.js +421 -75
- package/apps/cli/dist/next-client.js +11 -0
- package/apps/cli/dist/next.js +5057 -0
- package/apps/cli/dist/vite.js +5007 -0
- package/apps/cli/next-client.d.ts +1 -0
- package/apps/cli/next.d.ts +22 -0
- package/apps/cli/vite.d.ts +11 -0
- package/package.json +35 -4
package/apps/cli/dist/index.js
CHANGED
|
@@ -1439,8 +1439,8 @@ var CodexAdapter = class {
|
|
|
1439
1439
|
};
|
|
1440
1440
|
|
|
1441
1441
|
// ../../packages/bridge-core/src/config/loader.ts
|
|
1442
|
-
import { readFile, realpath } from "node:fs/promises";
|
|
1443
|
-
import { basename, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
1442
|
+
import { readFile, realpath, stat as stat2 } from "node:fs/promises";
|
|
1443
|
+
import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
1444
1444
|
import { parse as parseYaml } from "yaml";
|
|
1445
1445
|
import { ZodError } from "zod";
|
|
1446
1446
|
|
|
@@ -1627,11 +1627,42 @@ function isWithinRoot(root, candidate) {
|
|
|
1627
1627
|
const pathFromRoot = relative(root, candidate);
|
|
1628
1628
|
return pathFromRoot === "" || !pathFromRoot.startsWith(`..${sep}`) && pathFromRoot !== "..";
|
|
1629
1629
|
}
|
|
1630
|
+
async function configExists(root) {
|
|
1631
|
+
try {
|
|
1632
|
+
await stat2(join(root, CONFIG_PATH));
|
|
1633
|
+
return true;
|
|
1634
|
+
} catch (error) {
|
|
1635
|
+
if (typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT") {
|
|
1636
|
+
return false;
|
|
1637
|
+
}
|
|
1638
|
+
throw error;
|
|
1639
|
+
}
|
|
1640
|
+
}
|
|
1641
|
+
async function discoverVisualDevConfigRoot(startDirectory, repositoryRoot) {
|
|
1642
|
+
const repoRoot = await realpath(repositoryRoot);
|
|
1643
|
+
let cursor = await realpath(startDirectory);
|
|
1644
|
+
if (!isWithinRoot(repoRoot, cursor)) {
|
|
1645
|
+
throw new VisualDevConfigError(`Project directory is outside Git worktree: ${cursor}`);
|
|
1646
|
+
}
|
|
1647
|
+
while (true) {
|
|
1648
|
+
if (await configExists(cursor)) return cursor;
|
|
1649
|
+
if (cursor === repoRoot) return repoRoot;
|
|
1650
|
+
const parent = dirname(cursor);
|
|
1651
|
+
if (parent === cursor || !isWithinRoot(repoRoot, parent)) return repoRoot;
|
|
1652
|
+
cursor = parent;
|
|
1653
|
+
}
|
|
1654
|
+
}
|
|
1630
1655
|
async function loadVisualDevConfig(repositoryRoot, options = {}) {
|
|
1631
1656
|
const repoRoot = await realpath(repositoryRoot);
|
|
1632
|
-
const
|
|
1633
|
-
|
|
1634
|
-
|
|
1657
|
+
const configRoot = await realpath(options.configRoot ?? repoRoot);
|
|
1658
|
+
if (!isWithinRoot(repoRoot, configRoot)) {
|
|
1659
|
+
throw new VisualDevConfigError(
|
|
1660
|
+
`Config directory is outside Git worktree: ${configRoot}`
|
|
1661
|
+
);
|
|
1662
|
+
}
|
|
1663
|
+
const configPath = join(configRoot, CONFIG_PATH);
|
|
1664
|
+
const localConfigPath = join(configRoot, LOCAL_CONFIG_PATH);
|
|
1665
|
+
const projectId = basename(configRoot);
|
|
1635
1666
|
const baseDocument = await readYamlMapping(configPath, options.requireConfig ?? false);
|
|
1636
1667
|
const localDocument = await readYamlMapping(localConfigPath, false);
|
|
1637
1668
|
const loadedFiles = [];
|
|
@@ -1658,7 +1689,7 @@ async function loadVisualDevConfig(repositoryRoot, options = {}) {
|
|
|
1658
1689
|
filePath: localDocument === void 0 ? configPath : localConfigPath
|
|
1659
1690
|
});
|
|
1660
1691
|
}
|
|
1661
|
-
const unresolvedWorkspace = isAbsolute(config.project.workspace) ? config.project.workspace : resolve(
|
|
1692
|
+
const unresolvedWorkspace = isAbsolute(config.project.workspace) ? config.project.workspace : resolve(configRoot, config.project.workspace);
|
|
1662
1693
|
let workspaceRoot;
|
|
1663
1694
|
try {
|
|
1664
1695
|
workspaceRoot = await realpath(unresolvedWorkspace);
|
|
@@ -1677,6 +1708,7 @@ async function loadVisualDevConfig(repositoryRoot, options = {}) {
|
|
|
1677
1708
|
return {
|
|
1678
1709
|
config,
|
|
1679
1710
|
repoRoot,
|
|
1711
|
+
configRoot,
|
|
1680
1712
|
workspaceRoot,
|
|
1681
1713
|
configPath,
|
|
1682
1714
|
localConfigPath,
|
|
@@ -1964,7 +1996,7 @@ import {
|
|
|
1964
1996
|
rm
|
|
1965
1997
|
} from "node:fs/promises";
|
|
1966
1998
|
import { tmpdir } from "node:os";
|
|
1967
|
-
import { dirname, relative as relative3, resolve as resolve4, sep as sep3 } from "node:path";
|
|
1999
|
+
import { dirname as dirname2, relative as relative3, resolve as resolve4, sep as sep3 } from "node:path";
|
|
1968
2000
|
var SNAPSHOT_ENV = {
|
|
1969
2001
|
GIT_AUTHOR_NAME: "Visual Bridge",
|
|
1970
2002
|
GIT_AUTHOR_EMAIL: "visual-bridge@localhost",
|
|
@@ -2317,7 +2349,7 @@ var GitTransactionManager = class _GitTransactionManager {
|
|
|
2317
2349
|
} catch (error) {
|
|
2318
2350
|
if (error.code !== "ENOENT") throw error;
|
|
2319
2351
|
}
|
|
2320
|
-
await this.#removeEmptyParents(
|
|
2352
|
+
await this.#removeEmptyParents(dirname2(absolute));
|
|
2321
2353
|
}
|
|
2322
2354
|
async #removeEmptyParents(start) {
|
|
2323
2355
|
let cursor = start;
|
|
@@ -2327,13 +2359,13 @@ var GitTransactionManager = class _GitTransactionManager {
|
|
|
2327
2359
|
} catch (error) {
|
|
2328
2360
|
const code = error.code;
|
|
2329
2361
|
if (code === "ENOENT") {
|
|
2330
|
-
cursor =
|
|
2362
|
+
cursor = dirname2(cursor);
|
|
2331
2363
|
continue;
|
|
2332
2364
|
}
|
|
2333
2365
|
if (code === "ENOTEMPTY" || code === "EEXIST") return;
|
|
2334
2366
|
throw error;
|
|
2335
2367
|
}
|
|
2336
|
-
cursor =
|
|
2368
|
+
cursor = dirname2(cursor);
|
|
2337
2369
|
}
|
|
2338
2370
|
}
|
|
2339
2371
|
async #assertSnapshotRef(ref) {
|
|
@@ -2353,7 +2385,7 @@ var GitTransactionManager = class _GitTransactionManager {
|
|
|
2353
2385
|
// ../../packages/bridge-core/src/storage/sqlite-task-store.ts
|
|
2354
2386
|
import { mkdirSync } from "node:fs";
|
|
2355
2387
|
import { createRequire } from "node:module";
|
|
2356
|
-
import { dirname as
|
|
2388
|
+
import { dirname as dirname3 } from "node:path";
|
|
2357
2389
|
var requireNodeBuiltin = createRequire(import.meta.url);
|
|
2358
2390
|
var optional = (value) => value ?? void 0;
|
|
2359
2391
|
function parseJson(value, fallback) {
|
|
@@ -2445,7 +2477,7 @@ function taskParams(task) {
|
|
|
2445
2477
|
var SqliteTaskStore = class {
|
|
2446
2478
|
#db;
|
|
2447
2479
|
constructor(filename) {
|
|
2448
|
-
if (filename !== ":memory:") mkdirSync(
|
|
2480
|
+
if (filename !== ":memory:") mkdirSync(dirname3(filename), { recursive: true, mode: 448 });
|
|
2449
2481
|
const { DatabaseSync } = requireNodeBuiltin("node:sqlite");
|
|
2450
2482
|
this.#db = new DatabaseSync(filename);
|
|
2451
2483
|
this.#db.exec(`
|
|
@@ -4589,7 +4621,9 @@ ${output2}` : ""}`
|
|
|
4589
4621
|
|
|
4590
4622
|
// ../../packages/bridge-core/src/bridge/default-control-service.ts
|
|
4591
4623
|
async function createDefaultControlService(context, environment = process.env) {
|
|
4592
|
-
const loaded = await loadVisualDevConfig(context.repoRoot
|
|
4624
|
+
const loaded = await loadVisualDevConfig(context.repoRoot, {
|
|
4625
|
+
...context.configRoot === void 0 ? {} : { configRoot: context.configRoot }
|
|
4626
|
+
});
|
|
4593
4627
|
if (loaded.config.agent.adapter !== "codex") {
|
|
4594
4628
|
throw new Error(
|
|
4595
4629
|
`Agent adapter ${loaded.config.agent.adapter} is not implemented in this MVP build`
|
|
@@ -4882,6 +4916,7 @@ async function startBridgeCore(options, dependencies) {
|
|
|
4882
4916
|
mode: options.mode,
|
|
4883
4917
|
projectId: loadedConfig.config.project.id,
|
|
4884
4918
|
repoRoot: loadedConfig.repoRoot,
|
|
4919
|
+
configRoot: loadedConfig.configRoot,
|
|
4885
4920
|
workspaceRoot: loadedConfig.workspaceRoot,
|
|
4886
4921
|
upstreamUrl: options.upstreamUrl
|
|
4887
4922
|
};
|
|
@@ -4951,8 +4986,10 @@ async function startBridgeCore(options, dependencies) {
|
|
|
4951
4986
|
}
|
|
4952
4987
|
}
|
|
4953
4988
|
async function startAttachBridge(options, dependencies = {}) {
|
|
4954
|
-
const
|
|
4955
|
-
const
|
|
4989
|
+
const cwd = dependencies.cwd ?? process.cwd();
|
|
4990
|
+
const repoRoot = await discoverGitWorktreeRoot(cwd);
|
|
4991
|
+
const configRoot = await discoverVisualDevConfigRoot(cwd, repoRoot);
|
|
4992
|
+
const loadedConfig = await loadVisualDevConfig(repoRoot, { configRoot });
|
|
4956
4993
|
return await startBridgeCore(
|
|
4957
4994
|
{
|
|
4958
4995
|
mode: "attach",
|
|
@@ -4966,8 +5003,13 @@ async function startAttachBridge(options, dependencies = {}) {
|
|
|
4966
5003
|
);
|
|
4967
5004
|
}
|
|
4968
5005
|
async function startManagedBridge(options = {}, dependencies = {}) {
|
|
4969
|
-
const
|
|
4970
|
-
const
|
|
5006
|
+
const cwd = dependencies.cwd ?? process.cwd();
|
|
5007
|
+
const repoRoot = await discoverGitWorktreeRoot(cwd);
|
|
5008
|
+
const configRoot = await discoverVisualDevConfigRoot(cwd, repoRoot);
|
|
5009
|
+
const loadedConfig = await loadVisualDevConfig(repoRoot, {
|
|
5010
|
+
requireConfig: true,
|
|
5011
|
+
configRoot
|
|
5012
|
+
});
|
|
4971
5013
|
const command = loadedConfig.config.upstream.command;
|
|
4972
5014
|
if (command === void 0) {
|
|
4973
5015
|
throw new Error("visual dev requires upstream.command in .visualdev/config.yaml");
|
|
@@ -5076,14 +5118,14 @@ function formatBridgeSummary(bridge) {
|
|
|
5076
5118
|
|
|
5077
5119
|
// src/doctor.ts
|
|
5078
5120
|
import { constants } from "node:fs";
|
|
5079
|
-
import { access as access2, stat as
|
|
5080
|
-
import { delimiter, isAbsolute as isAbsolute5, join as join4, resolve as resolve7 } from "node:path";
|
|
5121
|
+
import { access as access2, stat as stat3 } from "node:fs/promises";
|
|
5122
|
+
import { delimiter, isAbsolute as isAbsolute5, join as join4, relative as relative6, resolve as resolve7 } from "node:path";
|
|
5081
5123
|
import { execFile as execFile2 } from "node:child_process";
|
|
5082
5124
|
import { promisify as promisify2 } from "node:util";
|
|
5083
5125
|
var execFileAsync2 = promisify2(execFile2);
|
|
5084
5126
|
async function fileExists(path) {
|
|
5085
5127
|
try {
|
|
5086
|
-
await
|
|
5128
|
+
await stat3(path);
|
|
5087
5129
|
return true;
|
|
5088
5130
|
} catch (error) {
|
|
5089
5131
|
if (typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT") {
|
|
@@ -5131,14 +5173,19 @@ async function runDoctor(dependencies = {}) {
|
|
|
5131
5173
|
return checks;
|
|
5132
5174
|
}
|
|
5133
5175
|
try {
|
|
5134
|
-
const
|
|
5176
|
+
const configRoot = await discoverVisualDevConfigRoot(
|
|
5177
|
+
dependencies.cwd ?? process.cwd(),
|
|
5178
|
+
repoRoot
|
|
5179
|
+
);
|
|
5180
|
+
const loaded = await loadVisualDevConfig(repoRoot, { configRoot });
|
|
5135
5181
|
checks.push({
|
|
5136
5182
|
name: "config",
|
|
5137
5183
|
status: loaded.loadedFiles.length === 0 ? "warning" : "pass",
|
|
5138
5184
|
message: loaded.loadedFiles.length === 0 ? "No .visualdev/config.yaml; attach defaults are available." : loaded.loadedFiles.join(", ")
|
|
5139
5185
|
});
|
|
5140
5186
|
if (await fileExists(loaded.localConfigPath)) {
|
|
5141
|
-
const
|
|
5187
|
+
const localConfigRelativePath = relative6(repoRoot, loaded.localConfigPath);
|
|
5188
|
+
const ignored = await isIgnored(repoRoot, localConfigRelativePath);
|
|
5142
5189
|
checks.push({
|
|
5143
5190
|
name: "local-config-ignore",
|
|
5144
5191
|
status: ignored ? "pass" : "warning",
|
|
@@ -5176,15 +5223,37 @@ function formatDoctorChecks(checks) {
|
|
|
5176
5223
|
}
|
|
5177
5224
|
|
|
5178
5225
|
// src/init.ts
|
|
5179
|
-
import {
|
|
5180
|
-
import {
|
|
5226
|
+
import { spawn as spawn6 } from "node:child_process";
|
|
5227
|
+
import { readFile as readFile5, mkdir as mkdir3, realpath as realpath10, stat as stat4, writeFile as writeFile3 } from "node:fs/promises";
|
|
5228
|
+
import { basename as basename2, dirname as dirname4, join as join5, relative as relative7 } from "node:path";
|
|
5181
5229
|
import { stringify as stringifyYaml } from "yaml";
|
|
5230
|
+
var VITE_CONFIG_FILES = [
|
|
5231
|
+
"vite.config.ts",
|
|
5232
|
+
"vite.config.mts",
|
|
5233
|
+
"vite.config.js",
|
|
5234
|
+
"vite.config.mjs"
|
|
5235
|
+
];
|
|
5236
|
+
var VITE_IMPORT = 'import { visualRemote } from "visual-remote/vite";';
|
|
5237
|
+
var NEXT_CONFIG_FILES = [
|
|
5238
|
+
"next.config.ts",
|
|
5239
|
+
"next.config.mjs",
|
|
5240
|
+
"next.config.js"
|
|
5241
|
+
];
|
|
5242
|
+
var NEXT_ESM_IMPORT = 'import { withVisualRemote } from "visual-remote/next";';
|
|
5243
|
+
var NEXT_CJS_IMPORT = 'const { withVisualRemote } = require("visual-remote/next");';
|
|
5244
|
+
var NEXT_CLIENT_MODULE = "visual-remote/next/client";
|
|
5245
|
+
var NEXT_CLIENT_BOOTSTRAP = [
|
|
5246
|
+
'if (process.env.NODE_ENV === "development") {',
|
|
5247
|
+
` void import("${NEXT_CLIENT_MODULE}");`,
|
|
5248
|
+
"}",
|
|
5249
|
+
""
|
|
5250
|
+
].join("\n");
|
|
5182
5251
|
function isMissingFile(error) {
|
|
5183
5252
|
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
5184
5253
|
}
|
|
5185
5254
|
async function fileExists2(path) {
|
|
5186
5255
|
try {
|
|
5187
|
-
await
|
|
5256
|
+
await stat4(path);
|
|
5188
5257
|
return true;
|
|
5189
5258
|
} catch (error) {
|
|
5190
5259
|
if (isMissingFile(error)) return false;
|
|
@@ -5196,7 +5265,7 @@ function packageManagerFromField(value) {
|
|
|
5196
5265
|
const name = value.split("@", 1)[0];
|
|
5197
5266
|
return name === "bun" || name === "npm" || name === "pnpm" || name === "yarn" ? name : void 0;
|
|
5198
5267
|
}
|
|
5199
|
-
async function detectPackageManager(repoRoot, manifest) {
|
|
5268
|
+
async function detectPackageManager(projectRoot, repoRoot, manifest) {
|
|
5200
5269
|
const declared = packageManagerFromField(manifest.packageManager);
|
|
5201
5270
|
if (declared !== void 0) return declared;
|
|
5202
5271
|
const lockfiles = [
|
|
@@ -5206,8 +5275,10 @@ async function detectPackageManager(repoRoot, manifest) {
|
|
|
5206
5275
|
["bun", "bun.lockb"],
|
|
5207
5276
|
["npm", "package-lock.json"]
|
|
5208
5277
|
];
|
|
5209
|
-
for (const [
|
|
5210
|
-
|
|
5278
|
+
for (const root of projectRoot === repoRoot ? [projectRoot] : [projectRoot, repoRoot]) {
|
|
5279
|
+
for (const [manager, filename] of lockfiles) {
|
|
5280
|
+
if (await fileExists2(join5(root, filename))) return manager;
|
|
5281
|
+
}
|
|
5211
5282
|
}
|
|
5212
5283
|
return "npm";
|
|
5213
5284
|
}
|
|
@@ -5217,6 +5288,26 @@ function readDevScript(manifest) {
|
|
|
5217
5288
|
}
|
|
5218
5289
|
return manifest.scripts.dev.trim();
|
|
5219
5290
|
}
|
|
5291
|
+
function packageRecord(value) {
|
|
5292
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
|
|
5293
|
+
}
|
|
5294
|
+
function isViteProject(manifest, devScript) {
|
|
5295
|
+
const packages = {
|
|
5296
|
+
...packageRecord(manifest.dependencies),
|
|
5297
|
+
...packageRecord(manifest.devDependencies)
|
|
5298
|
+
};
|
|
5299
|
+
return "vite" in packages || /(^|[\s;&|])vite(?:\s|$)/.test(devScript);
|
|
5300
|
+
}
|
|
5301
|
+
function isNextProject(manifest, devScript) {
|
|
5302
|
+
const packages = {
|
|
5303
|
+
...packageRecord(manifest.dependencies),
|
|
5304
|
+
...packageRecord(manifest.devDependencies)
|
|
5305
|
+
};
|
|
5306
|
+
return "next" in packages || /(^|[\s;&|])next(?:\s|$)/.test(devScript);
|
|
5307
|
+
}
|
|
5308
|
+
function hasVisualRemote(manifest) {
|
|
5309
|
+
return "visual-remote" in packageRecord(manifest.dependencies) || "visual-remote" in packageRecord(manifest.devDependencies);
|
|
5310
|
+
}
|
|
5220
5311
|
function devCommand(manager, script) {
|
|
5221
5312
|
const command = manager === "pnpm" ? ["corepack", "pnpm", "run", "dev"] : manager === "yarn" ? ["corepack", "yarn", "run", "dev"] : manager === "bun" ? ["bun", "run", "dev"] : ["npm", "run", "dev"];
|
|
5222
5313
|
const next = /(^|[\s;&|])next(?:\s|$)/.test(script);
|
|
@@ -5229,14 +5320,14 @@ function devCommand(manager, script) {
|
|
|
5229
5320
|
"{upstreamPort}"
|
|
5230
5321
|
];
|
|
5231
5322
|
}
|
|
5232
|
-
async function readManifest(
|
|
5233
|
-
const manifestPath = join5(
|
|
5323
|
+
async function readManifest(projectRoot) {
|
|
5324
|
+
const manifestPath = join5(projectRoot, "package.json");
|
|
5234
5325
|
let source;
|
|
5235
5326
|
try {
|
|
5236
5327
|
source = await readFile5(manifestPath, "utf8");
|
|
5237
5328
|
} catch (error) {
|
|
5238
5329
|
if (isMissingFile(error)) {
|
|
5239
|
-
throw new Error("visual init requires package.json
|
|
5330
|
+
throw new Error("visual init requires package.json in the current directory");
|
|
5240
5331
|
}
|
|
5241
5332
|
throw error;
|
|
5242
5333
|
}
|
|
@@ -5251,56 +5342,309 @@ async function readManifest(repoRoot) {
|
|
|
5251
5342
|
}
|
|
5252
5343
|
return value;
|
|
5253
5344
|
}
|
|
5254
|
-
async function
|
|
5255
|
-
const
|
|
5256
|
-
|
|
5257
|
-
|
|
5258
|
-
return { created: false, configPath };
|
|
5345
|
+
async function findViteConfig(projectRoot) {
|
|
5346
|
+
for (const filename of VITE_CONFIG_FILES) {
|
|
5347
|
+
const candidate = join5(projectRoot, filename);
|
|
5348
|
+
if (await fileExists2(candidate)) return candidate;
|
|
5259
5349
|
}
|
|
5260
|
-
|
|
5261
|
-
|
|
5262
|
-
|
|
5263
|
-
|
|
5264
|
-
|
|
5265
|
-
|
|
5266
|
-
|
|
5267
|
-
|
|
5268
|
-
|
|
5269
|
-
|
|
5270
|
-
|
|
5271
|
-
|
|
5272
|
-
|
|
5273
|
-
|
|
5274
|
-
|
|
5275
|
-
|
|
5350
|
+
throw new Error(
|
|
5351
|
+
"visual init found Vite but could not find vite.config.ts, .mts, .js, or .mjs"
|
|
5352
|
+
);
|
|
5353
|
+
}
|
|
5354
|
+
async function findNextConfig(projectRoot) {
|
|
5355
|
+
for (const filename of NEXT_CONFIG_FILES) {
|
|
5356
|
+
const candidate = join5(projectRoot, filename);
|
|
5357
|
+
if (await fileExists2(candidate)) return candidate;
|
|
5358
|
+
}
|
|
5359
|
+
return join5(projectRoot, "next.config.mjs");
|
|
5360
|
+
}
|
|
5361
|
+
function insertViteImport(source) {
|
|
5362
|
+
const lines = source.split("\n");
|
|
5363
|
+
let index = 0;
|
|
5364
|
+
while (lines[index]?.startsWith("///")) index += 1;
|
|
5365
|
+
lines.splice(index, 0, VITE_IMPORT);
|
|
5366
|
+
return lines.join("\n");
|
|
5367
|
+
}
|
|
5368
|
+
function transformViteConfig(source) {
|
|
5369
|
+
if (source.includes("visual-remote/vite") && /\bvisualRemote\s*\(/.test(source)) {
|
|
5370
|
+
return source;
|
|
5371
|
+
}
|
|
5372
|
+
let transformed = source.includes("visual-remote/vite") ? source : insertViteImport(source);
|
|
5373
|
+
const pluginsPattern = /(\bplugins\s*:\s*\[)/;
|
|
5374
|
+
if (pluginsPattern.test(transformed)) {
|
|
5375
|
+
return transformed.replace(
|
|
5376
|
+
pluginsPattern,
|
|
5377
|
+
(match, _prefix, offset, fullSource) => `${match}visualRemote(),${fullSource[offset + match.length] === "\n" ? "" : " "}`
|
|
5378
|
+
);
|
|
5379
|
+
}
|
|
5380
|
+
const objectConfigPattern = /(defineConfig\s*\(\s*\{)/;
|
|
5381
|
+
if (objectConfigPattern.test(transformed)) {
|
|
5382
|
+
return transformed.replace(
|
|
5383
|
+
objectConfigPattern,
|
|
5384
|
+
"$1\n plugins: [visualRemote()],"
|
|
5385
|
+
);
|
|
5386
|
+
}
|
|
5387
|
+
throw new Error(
|
|
5388
|
+
"visual init could not add visualRemote() to the Vite plugins array"
|
|
5389
|
+
);
|
|
5390
|
+
}
|
|
5391
|
+
async function configureVite(configPath) {
|
|
5392
|
+
const source = await readFile5(configPath, "utf8");
|
|
5393
|
+
const transformed = transformViteConfig(source);
|
|
5394
|
+
if (transformed === source) return false;
|
|
5395
|
+
await writeFile3(configPath, transformed, "utf8");
|
|
5396
|
+
return true;
|
|
5397
|
+
}
|
|
5398
|
+
function expressionEnd(source, expressionStart) {
|
|
5399
|
+
let roundDepth = 0;
|
|
5400
|
+
let squareDepth = 0;
|
|
5401
|
+
let curlyDepth = 0;
|
|
5402
|
+
let quote;
|
|
5403
|
+
let lineComment = false;
|
|
5404
|
+
let blockComment = false;
|
|
5405
|
+
for (let index = expressionStart; index < source.length; index += 1) {
|
|
5406
|
+
const character = source[index];
|
|
5407
|
+
const nextCharacter = source[index + 1];
|
|
5408
|
+
if (lineComment) {
|
|
5409
|
+
if (character === "\n") lineComment = false;
|
|
5410
|
+
continue;
|
|
5276
5411
|
}
|
|
5277
|
-
|
|
5278
|
-
|
|
5412
|
+
if (blockComment) {
|
|
5413
|
+
if (character === "*" && nextCharacter === "/") {
|
|
5414
|
+
blockComment = false;
|
|
5415
|
+
index += 1;
|
|
5416
|
+
}
|
|
5417
|
+
continue;
|
|
5418
|
+
}
|
|
5419
|
+
if (quote !== void 0) {
|
|
5420
|
+
if (character === "\\") {
|
|
5421
|
+
index += 1;
|
|
5422
|
+
} else if (character === quote) {
|
|
5423
|
+
quote = void 0;
|
|
5424
|
+
}
|
|
5425
|
+
continue;
|
|
5426
|
+
}
|
|
5427
|
+
if (character === "/" && nextCharacter === "/") {
|
|
5428
|
+
lineComment = true;
|
|
5429
|
+
index += 1;
|
|
5430
|
+
continue;
|
|
5431
|
+
}
|
|
5432
|
+
if (character === "/" && nextCharacter === "*") {
|
|
5433
|
+
blockComment = true;
|
|
5434
|
+
index += 1;
|
|
5435
|
+
continue;
|
|
5436
|
+
}
|
|
5437
|
+
if (character === "'" || character === '"' || character === "`") {
|
|
5438
|
+
quote = character;
|
|
5439
|
+
continue;
|
|
5440
|
+
}
|
|
5441
|
+
if (character === "(") roundDepth += 1;
|
|
5442
|
+
if (character === ")") roundDepth -= 1;
|
|
5443
|
+
if (character === "[") squareDepth += 1;
|
|
5444
|
+
if (character === "]") squareDepth -= 1;
|
|
5445
|
+
if (character === "{") curlyDepth += 1;
|
|
5446
|
+
if (character === "}") curlyDepth -= 1;
|
|
5447
|
+
if (character === ";" && roundDepth === 0 && squareDepth === 0 && curlyDepth === 0) {
|
|
5448
|
+
return index;
|
|
5449
|
+
}
|
|
5450
|
+
}
|
|
5451
|
+
return source.length;
|
|
5452
|
+
}
|
|
5453
|
+
function wrapConfigExpression(source, assignmentPattern) {
|
|
5454
|
+
const assignment = assignmentPattern.exec(source);
|
|
5455
|
+
if (assignment?.index === void 0) {
|
|
5456
|
+
throw new Error("visual init could not find the default Next.js config export");
|
|
5457
|
+
}
|
|
5458
|
+
let start = assignment.index + assignment[0].length;
|
|
5459
|
+
while (/\s/.test(source[start] ?? "")) start += 1;
|
|
5460
|
+
const end = expressionEnd(source, start);
|
|
5461
|
+
const rawExpression = source.slice(start, end);
|
|
5462
|
+
const trailingWhitespace = rawExpression.match(/\s*$/)?.[0] ?? "";
|
|
5463
|
+
const expression = rawExpression.slice(0, rawExpression.length - trailingWhitespace.length);
|
|
5464
|
+
if (expression.length === 0) {
|
|
5465
|
+
throw new Error("visual init found an empty Next.js config export");
|
|
5466
|
+
}
|
|
5467
|
+
return `${source.slice(0, start)}withVisualRemote(${expression})${trailingWhitespace}${source.slice(end)}`;
|
|
5468
|
+
}
|
|
5469
|
+
function transformNextConfig(source) {
|
|
5470
|
+
if (source.includes("visual-remote/next") && /\bwithVisualRemote\s*\(/.test(source)) {
|
|
5471
|
+
return source;
|
|
5472
|
+
}
|
|
5473
|
+
if (/\bmodule\.exports\s*=/.test(source)) {
|
|
5474
|
+
const wrapped2 = wrapConfigExpression(source, /\bmodule\.exports\s*=/);
|
|
5475
|
+
return wrapped2.includes(NEXT_CJS_IMPORT) ? wrapped2 : `${NEXT_CJS_IMPORT}
|
|
5476
|
+
${wrapped2}`;
|
|
5477
|
+
}
|
|
5478
|
+
const wrapped = wrapConfigExpression(source, /\bexport\s+default\b/);
|
|
5479
|
+
return wrapped.includes(NEXT_ESM_IMPORT) ? wrapped : `${NEXT_ESM_IMPORT}
|
|
5480
|
+
${wrapped}`;
|
|
5481
|
+
}
|
|
5482
|
+
async function configureNext(configPath) {
|
|
5483
|
+
let source;
|
|
5279
5484
|
try {
|
|
5280
|
-
await
|
|
5281
|
-
encoding: "utf8",
|
|
5282
|
-
flag: "wx"
|
|
5283
|
-
});
|
|
5485
|
+
source = await readFile5(configPath, "utf8");
|
|
5284
5486
|
} catch (error) {
|
|
5285
|
-
if (
|
|
5286
|
-
|
|
5487
|
+
if (!isMissingFile(error)) throw error;
|
|
5488
|
+
source = "export default {};\n";
|
|
5489
|
+
}
|
|
5490
|
+
const transformed = transformNextConfig(source);
|
|
5491
|
+
if (transformed === source) return false;
|
|
5492
|
+
await writeFile3(configPath, transformed, "utf8");
|
|
5493
|
+
return true;
|
|
5494
|
+
}
|
|
5495
|
+
async function findNextClientPath(projectRoot) {
|
|
5496
|
+
const candidates = [
|
|
5497
|
+
join5(projectRoot, "instrumentation-client.ts"),
|
|
5498
|
+
join5(projectRoot, "instrumentation-client.js"),
|
|
5499
|
+
join5(projectRoot, "src", "instrumentation-client.ts"),
|
|
5500
|
+
join5(projectRoot, "src", "instrumentation-client.js")
|
|
5501
|
+
];
|
|
5502
|
+
for (const candidate of candidates) {
|
|
5503
|
+
if (await fileExists2(candidate)) return candidate;
|
|
5504
|
+
}
|
|
5505
|
+
const sourceRoot = await fileExists2(join5(projectRoot, "src")) ? join5(projectRoot, "src") : projectRoot;
|
|
5506
|
+
const extension = await fileExists2(join5(projectRoot, "tsconfig.json")) ? "ts" : "js";
|
|
5507
|
+
return join5(sourceRoot, `instrumentation-client.${extension}`);
|
|
5508
|
+
}
|
|
5509
|
+
async function configureNextClient(clientPath) {
|
|
5510
|
+
let source = "";
|
|
5511
|
+
try {
|
|
5512
|
+
source = await readFile5(clientPath, "utf8");
|
|
5513
|
+
} catch (error) {
|
|
5514
|
+
if (!isMissingFile(error)) throw error;
|
|
5515
|
+
}
|
|
5516
|
+
if (source.includes(NEXT_CLIENT_MODULE)) return false;
|
|
5517
|
+
const separator = source.length === 0 || source.endsWith("\n") ? "" : "\n";
|
|
5518
|
+
await mkdir3(dirname4(clientPath), { recursive: true });
|
|
5519
|
+
await writeFile3(clientPath, `${source}${separator}${NEXT_CLIENT_BOOTSTRAP}`, "utf8");
|
|
5520
|
+
return true;
|
|
5521
|
+
}
|
|
5522
|
+
function installCommand(request) {
|
|
5523
|
+
if (request.packageManager === "pnpm") {
|
|
5524
|
+
return {
|
|
5525
|
+
command: "corepack",
|
|
5526
|
+
args: ["pnpm", "add", "--save-dev", request.packageSpec]
|
|
5527
|
+
};
|
|
5528
|
+
}
|
|
5529
|
+
if (request.packageManager === "yarn") {
|
|
5530
|
+
return {
|
|
5531
|
+
command: "corepack",
|
|
5532
|
+
args: ["yarn", "add", "--dev", request.packageSpec]
|
|
5533
|
+
};
|
|
5534
|
+
}
|
|
5535
|
+
if (request.packageManager === "bun") {
|
|
5536
|
+
return { command: "bun", args: ["add", "--dev", request.packageSpec] };
|
|
5537
|
+
}
|
|
5538
|
+
return {
|
|
5539
|
+
command: "npm",
|
|
5540
|
+
args: ["install", "--save-dev", request.packageSpec]
|
|
5541
|
+
};
|
|
5542
|
+
}
|
|
5543
|
+
async function installPackage(request) {
|
|
5544
|
+
const { command, args } = installCommand(request);
|
|
5545
|
+
await new Promise((resolvePromise, reject) => {
|
|
5546
|
+
const child = spawn6(command, args, {
|
|
5547
|
+
cwd: request.cwd,
|
|
5548
|
+
env: process.env,
|
|
5549
|
+
stdio: "inherit",
|
|
5550
|
+
windowsHide: true
|
|
5551
|
+
});
|
|
5552
|
+
child.once("error", reject);
|
|
5553
|
+
child.once("exit", (code, signal) => {
|
|
5554
|
+
if (code === 0) {
|
|
5555
|
+
resolvePromise();
|
|
5556
|
+
return;
|
|
5557
|
+
}
|
|
5558
|
+
reject(
|
|
5559
|
+
new Error(
|
|
5560
|
+
`${command} ${args.join(" ")} failed${signal === null ? ` with exit code ${code ?? "unknown"}` : ` with ${signal}`}`
|
|
5561
|
+
)
|
|
5562
|
+
);
|
|
5563
|
+
});
|
|
5564
|
+
});
|
|
5565
|
+
}
|
|
5566
|
+
async function initializeVisualDev(dependencies = {}) {
|
|
5567
|
+
const projectRoot = await realpath10(dependencies.cwd ?? process.cwd());
|
|
5568
|
+
const repoRoot = await discoverGitWorktreeRoot(projectRoot);
|
|
5569
|
+
const manifest = await readManifest(projectRoot);
|
|
5570
|
+
const devScript = readDevScript(manifest);
|
|
5571
|
+
const framework = isViteProject(manifest, devScript) ? "vite" : isNextProject(manifest, devScript) ? "next" : void 0;
|
|
5572
|
+
if (framework === void 0) {
|
|
5573
|
+
throw new Error("visual init currently supports Vite and Next.js projects");
|
|
5574
|
+
}
|
|
5575
|
+
const packageManager = await detectPackageManager(projectRoot, repoRoot, manifest);
|
|
5576
|
+
const integrationPath = framework === "vite" ? await findViteConfig(projectRoot) : await findNextConfig(projectRoot);
|
|
5577
|
+
const packageInstalled = !hasVisualRemote(manifest);
|
|
5578
|
+
if (packageInstalled) {
|
|
5579
|
+
await (dependencies.installPackage ?? installPackage)({
|
|
5580
|
+
cwd: projectRoot,
|
|
5581
|
+
packageManager,
|
|
5582
|
+
packageSpec: "visual-remote@latest"
|
|
5583
|
+
});
|
|
5584
|
+
}
|
|
5585
|
+
const integrationChanged = framework === "vite" ? await configureVite(integrationPath) : await configureNext(integrationPath);
|
|
5586
|
+
const clientPath = framework === "next" ? await findNextClientPath(projectRoot) : void 0;
|
|
5587
|
+
const clientChanged = clientPath === void 0 ? void 0 : await configureNextClient(clientPath);
|
|
5588
|
+
const configPath = join5(projectRoot, CONFIG_PATH);
|
|
5589
|
+
const created = !await fileExists2(configPath);
|
|
5590
|
+
if (created) {
|
|
5591
|
+
const document = {
|
|
5592
|
+
version: 1,
|
|
5593
|
+
project: {
|
|
5594
|
+
id: basename2(projectRoot),
|
|
5595
|
+
workspace: "."
|
|
5596
|
+
},
|
|
5597
|
+
gateway: {
|
|
5598
|
+
host: "0.0.0.0",
|
|
5599
|
+
port: "auto"
|
|
5600
|
+
},
|
|
5601
|
+
upstream: {
|
|
5602
|
+
port: "auto",
|
|
5603
|
+
command: devCommand(packageManager, devScript)
|
|
5604
|
+
}
|
|
5605
|
+
};
|
|
5606
|
+
await mkdir3(dirname4(configPath), { recursive: true });
|
|
5607
|
+
try {
|
|
5608
|
+
await writeFile3(configPath, stringifyYaml(document), {
|
|
5609
|
+
encoding: "utf8",
|
|
5610
|
+
flag: "wx"
|
|
5611
|
+
});
|
|
5612
|
+
} catch (error) {
|
|
5613
|
+
if (!isMissingFile(error) && (typeof error !== "object" || error === null || !("code" in error) || error.code !== "EEXIST")) {
|
|
5614
|
+
throw error;
|
|
5615
|
+
}
|
|
5287
5616
|
}
|
|
5288
|
-
throw error;
|
|
5289
5617
|
}
|
|
5290
|
-
return {
|
|
5618
|
+
return {
|
|
5619
|
+
framework,
|
|
5620
|
+
created,
|
|
5621
|
+
configPath,
|
|
5622
|
+
devScript,
|
|
5623
|
+
packageManager,
|
|
5624
|
+
integrationPath,
|
|
5625
|
+
integrationChanged,
|
|
5626
|
+
...clientPath === void 0 || clientChanged === void 0 ? {} : { clientPath, clientChanged },
|
|
5627
|
+
packageInstalled
|
|
5628
|
+
};
|
|
5291
5629
|
}
|
|
5292
5630
|
function formatInitResult(result) {
|
|
5293
|
-
|
|
5294
|
-
|
|
5295
|
-
|
|
5296
|
-
|
|
5297
|
-
|
|
5631
|
+
const configLabel = result.created ? "Created" : "Existing";
|
|
5632
|
+
const integrationLabel = result.integrationChanged ? "Configured" : "Existing";
|
|
5633
|
+
const rows = [
|
|
5634
|
+
`Framework: ${result.framework === "next" ? "Next.js" : "Vite"}`,
|
|
5635
|
+
`${configLabel}: ${relative7(process.cwd(), result.configPath) || CONFIG_PATH}`,
|
|
5636
|
+
`${integrationLabel}: ${relative7(process.cwd(), result.integrationPath)}`
|
|
5637
|
+
];
|
|
5638
|
+
if (result.clientPath !== void 0) {
|
|
5639
|
+
rows.push(
|
|
5640
|
+
`${result.clientChanged ? "Configured" : "Existing"}: ${relative7(process.cwd(), result.clientPath)}`
|
|
5641
|
+
);
|
|
5298
5642
|
}
|
|
5299
|
-
|
|
5300
|
-
|
|
5301
|
-
|
|
5302
|
-
|
|
5303
|
-
|
|
5643
|
+
rows.push(
|
|
5644
|
+
result.packageInstalled ? "Installed: visual-remote@latest" : "Installed: visual-remote",
|
|
5645
|
+
"Next: Start the app normally and open its original URL."
|
|
5646
|
+
);
|
|
5647
|
+
return rows.join("\n");
|
|
5304
5648
|
}
|
|
5305
5649
|
|
|
5306
5650
|
// src/status.ts
|
|
@@ -5353,8 +5697,8 @@ function setExitCode(dependencies, code) {
|
|
|
5353
5697
|
}
|
|
5354
5698
|
}
|
|
5355
5699
|
function createCli(dependencies = {}) {
|
|
5356
|
-
const program = new Command().name("visual").description("Visual Remote Dev Bridge").version("0.
|
|
5357
|
-
program.command("init").description("
|
|
5700
|
+
const program = new Command().name("visual").description("Visual Remote Dev Bridge").version("0.3.0");
|
|
5701
|
+
program.command("init").description("Configure Visual Remote for the current Vite or Next.js project").action(async () => {
|
|
5358
5702
|
const result = await initializeVisualDev(dependencies);
|
|
5359
5703
|
output(dependencies, formatInitResult(result));
|
|
5360
5704
|
});
|
|
@@ -5432,5 +5776,7 @@ export {
|
|
|
5432
5776
|
runBridgeUntilSignal,
|
|
5433
5777
|
runDoctor,
|
|
5434
5778
|
startAttachBridge,
|
|
5435
|
-
startManagedBridge
|
|
5779
|
+
startManagedBridge,
|
|
5780
|
+
transformNextConfig,
|
|
5781
|
+
transformViteConfig
|
|
5436
5782
|
};
|