visual-remote 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -22,9 +22,18 @@ npmjs에서 전역으로 설치합니다.
22
22
 
23
23
  ```bash
24
24
  npm install --global visual-remote
25
+ visual init
26
+ visual dev
25
27
  visual doctor
26
28
  ```
27
29
 
30
+ 전역 설치 없이 실행하려면 대상 프로젝트에서 다음 두 명령만 사용합니다.
31
+
32
+ ```bash
33
+ npx --yes visual-remote@latest init
34
+ npx --yes visual-remote@latest dev
35
+ ```
36
+
28
37
  ## 빠른 시작
29
38
 
30
39
  저장소를 받은 뒤 NVM을 불러오고 고정된 Node.js 버전을 선택합니다.
@@ -101,8 +110,9 @@ Portr를 사용한다면 원래 개발 서버 포트가 아니라 브리지 Gate
101
110
 
102
111
  ## 개발 서버와 브리지를 함께 실행
103
112
 
104
- 저장소 루트에 `.visualdev/config.yaml`을 만듭니다. 명령은 문자열이 아니라
105
- 인자 배열로 작성합니다.
113
+ 대상 저장소 루트에서 `visual init`을 실행하면 패키지 매니저와 `dev` 스크립트를
114
+ 감지해 `.visualdev/config.yaml`을 생성합니다. 기존 파일은 덮어쓰지 않습니다.
115
+ 직접 작성할 때 명령은 셸 문자열이 아니라 인자 배열로 작성합니다.
106
116
 
107
117
  ```yaml
108
118
  version: 1
@@ -113,7 +123,7 @@ project:
113
123
 
114
124
  gateway:
115
125
  host: 0.0.0.0
116
- port: 10001
126
+ port: auto
117
127
  # publicUrl: https://visual.example.com
118
128
 
119
129
  upstream:
@@ -200,12 +210,14 @@ Overlay에서 `작업 보드 ↗`를 다시 눌러 새 세션을 엽니다. 연
200
210
  현재 제공하는 명령은 다음과 같습니다.
201
211
 
202
212
  ```bash
213
+ visual init
203
214
  visual attach --help
204
215
  visual dev --help
205
216
  visual status
206
217
  visual doctor
207
218
  ```
208
219
 
220
+ - `init`: 패키지 매니저와 `dev` 스크립트를 감지해 기본 설정을 생성합니다.
209
221
  - `attach`: 이미 실행 중인 개발 서버 앞에 브리지를 연결합니다.
210
222
  - `dev`: 설정된 개발 서버와 브리지를 함께 실행합니다.
211
223
  - `status`: 현재 Git 작업 트리의 브리지 실행 상태를 확인합니다.
@@ -273,5 +285,5 @@ node --version
273
285
  ### 설정 파일 경고가 표시되는 경우
274
286
 
275
287
  기존 서버에 연결하는 `attach`는 설정 파일 없이도 기본값으로 실행할 수 있습니다.
276
- 브리지가 개발 서버를 직접 관리해야 한다면 `.visualdev/config.yaml`을 작성한
277
- `doctor`를 다시 실행합니다.
288
+ 브리지가 개발 서버를 직접 관리해야 한다면 `visual init`을 실행한 `doctor`를
289
+ 다시 실행합니다.
@@ -5174,6 +5174,134 @@ function formatDoctorChecks(checks) {
5174
5174
  }).join("\n");
5175
5175
  }
5176
5176
 
5177
+ // src/init.ts
5178
+ import { readFile as readFile5, mkdir as mkdir3, stat as stat3, writeFile as writeFile3 } from "node:fs/promises";
5179
+ import { basename as basename2, dirname as dirname3, join as join5 } from "node:path";
5180
+ import { stringify as stringifyYaml } from "yaml";
5181
+ function isMissingFile(error) {
5182
+ return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
5183
+ }
5184
+ async function fileExists2(path) {
5185
+ try {
5186
+ await stat3(path);
5187
+ return true;
5188
+ } catch (error) {
5189
+ if (isMissingFile(error)) return false;
5190
+ throw error;
5191
+ }
5192
+ }
5193
+ function packageManagerFromField(value) {
5194
+ if (typeof value !== "string") return void 0;
5195
+ const name = value.split("@", 1)[0];
5196
+ return name === "bun" || name === "npm" || name === "pnpm" || name === "yarn" ? name : void 0;
5197
+ }
5198
+ async function detectPackageManager(repoRoot, manifest) {
5199
+ const declared = packageManagerFromField(manifest.packageManager);
5200
+ if (declared !== void 0) return declared;
5201
+ const lockfiles = [
5202
+ ["pnpm", "pnpm-lock.yaml"],
5203
+ ["yarn", "yarn.lock"],
5204
+ ["bun", "bun.lock"],
5205
+ ["bun", "bun.lockb"],
5206
+ ["npm", "package-lock.json"]
5207
+ ];
5208
+ for (const [manager, filename] of lockfiles) {
5209
+ if (await fileExists2(join5(repoRoot, filename))) return manager;
5210
+ }
5211
+ return "npm";
5212
+ }
5213
+ function readDevScript(manifest) {
5214
+ if (typeof manifest.scripts !== "object" || manifest.scripts === null || !("dev" in manifest.scripts) || typeof manifest.scripts.dev !== "string" || manifest.scripts.dev.trim().length === 0) {
5215
+ throw new Error('visual init requires a non-empty "dev" script in package.json');
5216
+ }
5217
+ return manifest.scripts.dev.trim();
5218
+ }
5219
+ function devCommand(manager, script) {
5220
+ const command = manager === "pnpm" ? ["corepack", "pnpm", "run", "dev"] : manager === "yarn" ? ["corepack", "yarn", "run", "dev"] : manager === "bun" ? ["bun", "run", "dev"] : ["npm", "run", "dev"];
5221
+ const next = /(^|[\s;&|])next(?:\s|$)/.test(script);
5222
+ return [
5223
+ ...command,
5224
+ "--",
5225
+ next ? "--hostname" : "--host",
5226
+ "0.0.0.0",
5227
+ "--port",
5228
+ "{upstreamPort}"
5229
+ ];
5230
+ }
5231
+ async function readManifest(repoRoot) {
5232
+ const manifestPath = join5(repoRoot, "package.json");
5233
+ let source;
5234
+ try {
5235
+ source = await readFile5(manifestPath, "utf8");
5236
+ } catch (error) {
5237
+ if (isMissingFile(error)) {
5238
+ throw new Error("visual init requires package.json at the Git worktree root");
5239
+ }
5240
+ throw error;
5241
+ }
5242
+ let value;
5243
+ try {
5244
+ value = JSON.parse(source);
5245
+ } catch (error) {
5246
+ throw new Error(`Unable to parse ${manifestPath}`, { cause: error });
5247
+ }
5248
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
5249
+ throw new Error(`${manifestPath} must contain a JSON object`);
5250
+ }
5251
+ return value;
5252
+ }
5253
+ async function initializeVisualDev(dependencies = {}) {
5254
+ const repoRoot = await discoverGitWorktreeRoot(dependencies.cwd ?? process.cwd());
5255
+ const configPath = join5(repoRoot, CONFIG_PATH);
5256
+ if (await fileExists2(configPath)) {
5257
+ return { created: false, configPath };
5258
+ }
5259
+ const manifest = await readManifest(repoRoot);
5260
+ const devScript = readDevScript(manifest);
5261
+ const packageManager = await detectPackageManager(repoRoot, manifest);
5262
+ const document = {
5263
+ version: 1,
5264
+ project: {
5265
+ id: basename2(repoRoot),
5266
+ workspace: "."
5267
+ },
5268
+ gateway: {
5269
+ host: "0.0.0.0",
5270
+ port: "auto"
5271
+ },
5272
+ upstream: {
5273
+ port: "auto",
5274
+ command: devCommand(packageManager, devScript)
5275
+ }
5276
+ };
5277
+ await mkdir3(dirname3(configPath), { recursive: true });
5278
+ try {
5279
+ await writeFile3(configPath, stringifyYaml(document), {
5280
+ encoding: "utf8",
5281
+ flag: "wx"
5282
+ });
5283
+ } catch (error) {
5284
+ if (typeof error === "object" && error !== null && "code" in error && error.code === "EEXIST") {
5285
+ return { created: false, configPath };
5286
+ }
5287
+ throw error;
5288
+ }
5289
+ return { created: true, configPath, devScript, packageManager };
5290
+ }
5291
+ function formatInitResult(result) {
5292
+ if (!result.created) {
5293
+ return [
5294
+ `Already initialized: ${CONFIG_PATH}`,
5295
+ "Next: npx --yes visual-remote@latest dev"
5296
+ ].join("\n");
5297
+ }
5298
+ return [
5299
+ `Created: ${CONFIG_PATH}`,
5300
+ `Detected: ${result.packageManager} dev (${result.devScript})`,
5301
+ "Next: npx --yes visual-remote@latest dev"
5302
+ ].join("\n");
5303
+ }
5304
+
5177
5305
  // src/status.ts
5178
5306
  async function getBridgeStatus(dependencies = {}) {
5179
5307
  const repoRoot = await discoverGitWorktreeRoot(dependencies.cwd ?? process.cwd());
@@ -5224,7 +5352,11 @@ function setExitCode(dependencies, code) {
5224
5352
  }
5225
5353
  }
5226
5354
  function createCli(dependencies = {}) {
5227
- const program = new Command().name("visual").description("Visual Remote Dev Bridge").version("0.1.0");
5355
+ const program = new Command().name("visual").description("Visual Remote Dev Bridge").version("0.1.1");
5356
+ program.command("init").description("Create .visualdev/config.yaml for the current project").action(async () => {
5357
+ const result = await initializeVisualDev(dependencies);
5358
+ output(dependencies, formatInitResult(result));
5359
+ });
5228
5360
  program.command("attach").description("Attach the Bridge to an existing development server").requiredOption("--upstream <url>", "existing development server URL").option("--listen <port>", "gateway port (10001 or above)", parsePort).option("--host <host>", "gateway bind host (default: 0.0.0.0)").option("--public-url <url>", "public Gateway URL opened in the browser").action(
5229
5361
  async (options) => {
5230
5362
  const bridge = await startAttachBridge(options, dependencies);
@@ -5271,7 +5403,9 @@ export {
5271
5403
  formatBridgeStatus,
5272
5404
  formatBridgeSummary,
5273
5405
  formatDoctorChecks,
5406
+ formatInitResult,
5274
5407
  getBridgeStatus,
5408
+ initializeVisualDev,
5275
5409
  main,
5276
5410
  runBridgeUntilSignal,
5277
5411
  runDoctor,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "visual-remote",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Visual bridge from a running web UI to Codex in its Git worktree",
5
5
  "type": "module",
6
6
  "repository": {
@@ -43,10 +43,10 @@
43
43
  "typescript": "^7.0.2",
44
44
  "vitest": "^4.1.10",
45
45
  "@visual-remote/bridge-core": "0.1.0",
46
- "@visual-remote/gateway": "0.1.0",
47
- "@visual-remote/cli": "0.1.0",
46
+ "@visual-remote/protocol": "0.1.0",
48
47
  "@visual-remote/overlay": "0.1.0",
49
- "@visual-remote/protocol": "0.1.0"
48
+ "@visual-remote/cli": "0.1.0",
49
+ "@visual-remote/gateway": "0.1.0"
50
50
  },
51
51
  "scripts": {
52
52
  "build": "corepack pnpm run build:overlay && corepack pnpm run build:server",