@wenrwa/marketplace-cli 0.4.4 → 0.4.6
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/__tests__/bridge-activity-parser.test.d.ts +1 -0
- package/dist/__tests__/bridge-activity-parser.test.js +0 -0
- package/dist/__tests__/bridge-activity-tap.test.d.ts +1 -0
- package/dist/__tests__/bridge-activity-tap.test.js +178 -0
- package/dist/__tests__/bridge-api-client.test.d.ts +1 -0
- package/dist/__tests__/bridge-api-client.test.js +69 -0
- package/dist/__tests__/bridge-browser-driver.test.d.ts +1 -0
- package/dist/__tests__/bridge-browser-driver.test.js +379 -0
- package/dist/__tests__/bridge-browser-frames.test.d.ts +1 -0
- package/dist/__tests__/bridge-browser-frames.test.js +166 -0
- package/dist/__tests__/bridge-browser-idle.test.d.ts +1 -0
- package/dist/__tests__/bridge-browser-idle.test.js +259 -0
- package/dist/__tests__/bridge-browser-relay.test.d.ts +1 -0
- package/dist/__tests__/bridge-browser-relay.test.js +670 -0
- package/dist/__tests__/bridge-bundle-installer.test.d.ts +1 -0
- package/dist/__tests__/bridge-bundle-installer.test.js +126 -0
- package/dist/__tests__/bridge-cdp-client.test.d.ts +1 -0
- package/dist/__tests__/bridge-cdp-client.test.js +133 -0
- package/dist/__tests__/bridge-chromium-launcher.test.d.ts +1 -0
- package/dist/__tests__/bridge-chromium-launcher.test.js +202 -0
- package/dist/__tests__/bridge-command-browser-options.test.d.ts +1 -0
- package/dist/__tests__/bridge-command-browser-options.test.js +37 -0
- package/dist/__tests__/bridge-config-mcp.test.js +4 -0
- package/dist/__tests__/bridge-context-manifest-frame.test.d.ts +1 -0
- package/dist/__tests__/bridge-context-manifest-frame.test.js +125 -0
- package/dist/__tests__/bridge-context-writer.test.d.ts +1 -0
- package/dist/__tests__/bridge-context-writer.test.js +161 -0
- package/dist/__tests__/bridge-device-registration.test.d.ts +1 -0
- package/dist/__tests__/bridge-device-registration.test.js +33 -0
- package/dist/__tests__/bridge-folder-picker-runner.test.d.ts +1 -0
- package/dist/__tests__/bridge-folder-picker-runner.test.js +37 -0
- package/dist/__tests__/bridge-folder-picker.test.d.ts +1 -0
- package/dist/__tests__/bridge-folder-picker.test.js +66 -0
- package/dist/__tests__/bridge-folder-registry.test.d.ts +1 -0
- package/dist/__tests__/bridge-folder-registry.test.js +107 -0
- package/dist/__tests__/bridge-frame-pacer.test.d.ts +1 -0
- package/dist/__tests__/bridge-frame-pacer.test.js +102 -0
- package/dist/__tests__/bridge-install-bundle-frame.test.d.ts +1 -0
- package/dist/__tests__/bridge-install-bundle-frame.test.js +107 -0
- package/dist/__tests__/bridge-local-add-core.test.d.ts +1 -0
- package/dist/__tests__/bridge-local-add-core.test.js +48 -0
- package/dist/__tests__/bridge-local-path-validation-fs.test.d.ts +1 -0
- package/dist/__tests__/bridge-local-path-validation-fs.test.js +88 -0
- package/dist/__tests__/bridge-local-path-validation.test.d.ts +1 -0
- package/dist/__tests__/bridge-local-path-validation.test.js +45 -0
- package/dist/__tests__/bridge-login-status.test.d.ts +1 -0
- package/dist/__tests__/bridge-login-status.test.js +60 -0
- package/dist/__tests__/bridge-relay-client.test.d.ts +1 -0
- package/dist/__tests__/bridge-relay-client.test.js +283 -0
- package/dist/__tests__/bridge-relay-mode.test.d.ts +1 -0
- package/dist/__tests__/bridge-relay-mode.test.js +33 -0
- package/dist/__tests__/bridge-relay-pucc14.test.d.ts +1 -0
- package/dist/__tests__/bridge-relay-pucc14.test.js +230 -0
- package/dist/__tests__/bridge-server-local-endpoints.test.d.ts +1 -0
- package/dist/__tests__/bridge-server-local-endpoints.test.js +197 -0
- package/dist/__tests__/bridge-server.test.js +22 -0
- package/dist/__tests__/bridge-workspace-multirepo.test.d.ts +1 -0
- package/dist/__tests__/bridge-workspace-multirepo.test.js +317 -0
- package/dist/__tests__/devices.test.d.ts +1 -0
- package/dist/__tests__/devices.test.js +66 -0
- package/dist/__tests__/local.test.d.ts +1 -0
- package/dist/__tests__/local.test.js +147 -0
- package/dist/bridge/activityParser.d.ts +53 -0
- package/dist/bridge/activityParser.js +202 -0
- package/dist/bridge/apiClient.d.ts +56 -0
- package/dist/bridge/apiClient.js +50 -0
- package/dist/bridge/browserDriver.d.ts +98 -0
- package/dist/bridge/browserDriver.js +265 -0
- package/dist/bridge/browserFrames.d.ts +182 -0
- package/dist/bridge/browserFrames.js +179 -0
- package/dist/bridge/browserIdle.d.ts +75 -0
- package/dist/bridge/browserIdle.js +120 -0
- package/dist/bridge/bundleInstaller.d.ts +31 -0
- package/dist/bridge/bundleInstaller.js +127 -0
- package/dist/bridge/cdpClient.d.ts +47 -0
- package/dist/bridge/cdpClient.js +102 -0
- package/dist/bridge/chromiumLauncher.d.ts +45 -0
- package/dist/bridge/chromiumLauncher.js +113 -0
- package/dist/bridge/config.d.ts +3 -0
- package/dist/bridge/contextWriter.d.ts +31 -0
- package/dist/bridge/contextWriter.js +139 -0
- package/dist/bridge/deviceRegistration.d.ts +28 -0
- package/dist/bridge/deviceRegistration.js +19 -0
- package/dist/bridge/folderPicker.d.ts +34 -0
- package/dist/bridge/folderPicker.js +36 -0
- package/dist/bridge/folderPickerRunner.d.ts +3 -0
- package/dist/bridge/folderPickerRunner.js +25 -0
- package/dist/bridge/folderRegistry.d.ts +19 -0
- package/dist/bridge/folderRegistry.js +100 -0
- package/dist/bridge/framePacer.d.ts +61 -0
- package/dist/bridge/framePacer.js +79 -0
- package/dist/bridge/localAdd.d.ts +28 -0
- package/dist/bridge/localAdd.js +67 -0
- package/dist/bridge/localPathValidation.d.ts +20 -0
- package/dist/bridge/localPathValidation.js +58 -0
- package/dist/bridge/localPathValidationFs.d.ts +2 -0
- package/dist/bridge/localPathValidationFs.js +98 -0
- package/dist/bridge/loginStatus.d.ts +38 -0
- package/dist/bridge/loginStatus.js +56 -0
- package/dist/bridge/realPty.js +6 -2
- package/dist/bridge/relayClient.d.ts +166 -0
- package/dist/bridge/relayClient.js +517 -0
- package/dist/bridge/repoCheckout.d.ts +31 -0
- package/dist/bridge/repoCheckout.js +18 -0
- package/dist/bridge/server.d.ts +33 -0
- package/dist/bridge/server.js +98 -10
- package/dist/bridge/sessionManager.d.ts +18 -0
- package/dist/bridge/sessionManager.js +15 -1
- package/dist/commands/bridge.d.ts +21 -0
- package/dist/commands/bridge.js +194 -1
- package/dist/commands/devices.d.ts +9 -0
- package/dist/commands/devices.js +53 -0
- package/dist/commands/local.d.ts +9 -0
- package/dist/commands/local.js +141 -0
- package/dist/index.js +7 -1
- package/package.json +1 -1
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lws-05 — OS-native folder picker (PRD D3b) behind POST /local/pick-folder.
|
|
3
|
+
* `pickerCommandFor` is a pure command table — easy to test per platform
|
|
4
|
+
* without spawning anything. `pickFolder` runs it with an injected runner
|
|
5
|
+
* (real implementation in commands/bridge.ts uses child_process.execFile);
|
|
6
|
+
* tests never shell out to osascript/PowerShell/zenity.
|
|
7
|
+
*
|
|
8
|
+
* "One picker at a time" (server-level, not spawn-level) is enforced by the
|
|
9
|
+
* HTTP route in server.ts, which gates concurrent /local/pick-folder calls —
|
|
10
|
+
* this module only knows how to run a single pick.
|
|
11
|
+
*/
|
|
12
|
+
export interface PickerCommand {
|
|
13
|
+
cmd: string;
|
|
14
|
+
args: string[];
|
|
15
|
+
}
|
|
16
|
+
export declare function pickerCommandFor(platform: string, opts: {
|
|
17
|
+
hasZenity: boolean;
|
|
18
|
+
}): PickerCommand | null;
|
|
19
|
+
export type PickFolderRunner = (cmd: string, args: string[]) => Promise<{
|
|
20
|
+
stdout: string;
|
|
21
|
+
code: number;
|
|
22
|
+
}>;
|
|
23
|
+
export type PickFolderResult = {
|
|
24
|
+
path: string;
|
|
25
|
+
} | {
|
|
26
|
+
cancelled: true;
|
|
27
|
+
} | {
|
|
28
|
+
unsupported: true;
|
|
29
|
+
};
|
|
30
|
+
export declare function pickFolder(opts: {
|
|
31
|
+
platform: string;
|
|
32
|
+
hasZenity: boolean;
|
|
33
|
+
run: PickFolderRunner;
|
|
34
|
+
}): Promise<PickFolderResult>;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.pickerCommandFor = pickerCommandFor;
|
|
4
|
+
exports.pickFolder = pickFolder;
|
|
5
|
+
function pickerCommandFor(platform, opts) {
|
|
6
|
+
if (platform === 'darwin') {
|
|
7
|
+
return { cmd: 'osascript', args: ['-e', 'POSIX path of (choose folder)'] };
|
|
8
|
+
}
|
|
9
|
+
if (platform === 'win32') {
|
|
10
|
+
const script = "Add-Type -AssemblyName System.Windows.Forms; " +
|
|
11
|
+
"$f = New-Object System.Windows.Forms.FolderBrowserDialog; " +
|
|
12
|
+
"if ($f.ShowDialog() -eq 'OK') { Write-Output $f.SelectedPath }";
|
|
13
|
+
return { cmd: 'powershell.exe', args: ['-NoProfile', '-Command', script] };
|
|
14
|
+
}
|
|
15
|
+
if (platform === 'linux' && opts.hasZenity) {
|
|
16
|
+
return { cmd: 'zenity', args: ['--file-selection', '--directory'] };
|
|
17
|
+
}
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
async function pickFolder(opts) {
|
|
21
|
+
const command = pickerCommandFor(opts.platform, { hasZenity: opts.hasZenity });
|
|
22
|
+
if (!command)
|
|
23
|
+
return { unsupported: true };
|
|
24
|
+
try {
|
|
25
|
+
const { stdout } = await opts.run(command.cmd, command.args);
|
|
26
|
+
const path = stdout.trim();
|
|
27
|
+
if (!path)
|
|
28
|
+
return { cancelled: true };
|
|
29
|
+
return { path };
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
// Non-zero exit (user hit Cancel) surfaces as a rejected promise from
|
|
33
|
+
// every platform's runner — treated as a plain cancel, not an error.
|
|
34
|
+
return { cancelled: true };
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.execFileRunner = void 0;
|
|
4
|
+
exports.hasZenityInstalled = hasZenityInstalled;
|
|
5
|
+
/**
|
|
6
|
+
* lws-05 — real execFile-backed PickFolderRunner + zenity presence probe,
|
|
7
|
+
* wired into commands/bridge.ts's POST /local/pick-folder handler. Kept
|
|
8
|
+
* separate from folderPicker.ts (the pure command table + result logic) so
|
|
9
|
+
* that module never touches child_process directly and stays test-friendly.
|
|
10
|
+
*/
|
|
11
|
+
const child_process_1 = require("child_process");
|
|
12
|
+
const execFileRunner = (cmd, args) => new Promise((resolve, reject) => {
|
|
13
|
+
(0, child_process_1.execFile)(cmd, args, (err, stdout) => {
|
|
14
|
+
if (err)
|
|
15
|
+
reject(err);
|
|
16
|
+
else
|
|
17
|
+
resolve({ stdout: stdout ?? '', code: 0 });
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
exports.execFileRunner = execFileRunner;
|
|
21
|
+
function hasZenityInstalled() {
|
|
22
|
+
return new Promise((resolve) => {
|
|
23
|
+
(0, child_process_1.execFile)('which', ['zenity'], (err) => resolve(!err));
|
|
24
|
+
});
|
|
25
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export interface FolderEntry {
|
|
2
|
+
workspaceId: string;
|
|
3
|
+
name: string;
|
|
4
|
+
addedAt: string;
|
|
5
|
+
}
|
|
6
|
+
/** Keyed by absolute path. */
|
|
7
|
+
export interface FolderRegistry {
|
|
8
|
+
v: 1;
|
|
9
|
+
entries: Record<string, FolderEntry>;
|
|
10
|
+
}
|
|
11
|
+
export declare function loadFolderRegistry(dir?: string): FolderRegistry;
|
|
12
|
+
export declare function saveFolderRegistry(registry: FolderRegistry, dir?: string): void;
|
|
13
|
+
export declare function upsertEntry(registry: FolderRegistry, absPath: string, entry: FolderEntry): FolderRegistry;
|
|
14
|
+
export declare function removeEntryByPath(registry: FolderRegistry, absPath: string): FolderRegistry;
|
|
15
|
+
export declare function removeEntryByWorkspaceId(registry: FolderRegistry, workspaceId: string): FolderRegistry;
|
|
16
|
+
export declare function findEntryByPath(registry: FolderRegistry, absPath: string): FolderEntry | null;
|
|
17
|
+
export declare function findEntryByWorkspaceId(registry: FolderRegistry, workspaceId: string): {
|
|
18
|
+
path: string;
|
|
19
|
+
} & FolderEntry | null;
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.loadFolderRegistry = loadFolderRegistry;
|
|
37
|
+
exports.saveFolderRegistry = saveFolderRegistry;
|
|
38
|
+
exports.upsertEntry = upsertEntry;
|
|
39
|
+
exports.removeEntryByPath = removeEntryByPath;
|
|
40
|
+
exports.removeEntryByWorkspaceId = removeEntryByWorkspaceId;
|
|
41
|
+
exports.findEntryByPath = findEntryByPath;
|
|
42
|
+
exports.findEntryByWorkspaceId = findEntryByWorkspaceId;
|
|
43
|
+
/**
|
|
44
|
+
* lws-03 — folder→workspace registry. Backs `wen-market local add/list/
|
|
45
|
+
* remove` and the discovery advertisement (server.ts). Lives alongside the
|
|
46
|
+
* bridge's other dotfiles under ~/.wen-market/ rather than a second dotdir,
|
|
47
|
+
* matching config.ts's convention. File-backed so it survives daemon
|
|
48
|
+
* restart; pure ops below operate on an in-memory snapshot so add/remove
|
|
49
|
+
* logic is unit-testable without touching disk.
|
|
50
|
+
*/
|
|
51
|
+
const fs = __importStar(require("fs"));
|
|
52
|
+
const os = __importStar(require("os"));
|
|
53
|
+
const path = __importStar(require("path"));
|
|
54
|
+
const DEFAULT_DIR = path.join(os.homedir(), '.wen-market');
|
|
55
|
+
const REGISTRY_NAME = 'local-workspaces.json';
|
|
56
|
+
function ensureDir(dir) {
|
|
57
|
+
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
58
|
+
}
|
|
59
|
+
function loadFolderRegistry(dir = DEFAULT_DIR) {
|
|
60
|
+
try {
|
|
61
|
+
const parsed = JSON.parse(fs.readFileSync(path.join(dir, REGISTRY_NAME), 'utf-8'));
|
|
62
|
+
if (parsed && typeof parsed === 'object' && parsed.entries)
|
|
63
|
+
return parsed;
|
|
64
|
+
return { v: 1, entries: {} };
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
return { v: 1, entries: {} };
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
function saveFolderRegistry(registry, dir = DEFAULT_DIR) {
|
|
71
|
+
ensureDir(dir);
|
|
72
|
+
fs.writeFileSync(path.join(dir, REGISTRY_NAME), JSON.stringify(registry, null, 2) + '\n', { mode: 0o600 });
|
|
73
|
+
}
|
|
74
|
+
// ── Pure in-memory ops (no I/O — easy to unit test, easy to compose) ──────
|
|
75
|
+
function upsertEntry(registry, absPath, entry) {
|
|
76
|
+
return { ...registry, entries: { ...registry.entries, [absPath]: entry } };
|
|
77
|
+
}
|
|
78
|
+
function removeEntryByPath(registry, absPath) {
|
|
79
|
+
const entries = { ...registry.entries };
|
|
80
|
+
delete entries[absPath];
|
|
81
|
+
return { ...registry, entries };
|
|
82
|
+
}
|
|
83
|
+
function removeEntryByWorkspaceId(registry, workspaceId) {
|
|
84
|
+
const entries = {};
|
|
85
|
+
for (const [p, e] of Object.entries(registry.entries)) {
|
|
86
|
+
if (e.workspaceId !== workspaceId)
|
|
87
|
+
entries[p] = e;
|
|
88
|
+
}
|
|
89
|
+
return { ...registry, entries };
|
|
90
|
+
}
|
|
91
|
+
function findEntryByPath(registry, absPath) {
|
|
92
|
+
return registry.entries[absPath] ?? null;
|
|
93
|
+
}
|
|
94
|
+
function findEntryByWorkspaceId(registry, workspaceId) {
|
|
95
|
+
for (const [p, e] of Object.entries(registry.entries)) {
|
|
96
|
+
if (e.workspaceId === workspaceId)
|
|
97
|
+
return { path: p, ...e };
|
|
98
|
+
}
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cbs-01 — keep-newest frame pacer (pure, no I/O).
|
|
3
|
+
*
|
|
4
|
+
* Backpressure policy for the cloud browser session's `browser-frame`
|
|
5
|
+
* stream (docs/prd/cloud-browser-session.md, "Backpressure" decision): the
|
|
6
|
+
* relay has no message-size limit and no built-in backpressure, so an
|
|
7
|
+
* unbounded queue in front of a slow consumer (bad wifi, a stalled pane) is
|
|
8
|
+
* how memory balloons. This pacer enforces "at most ONE undelivered frame
|
|
9
|
+
* per session, newest wins":
|
|
10
|
+
*
|
|
11
|
+
* - The first pushed frame (nothing in flight) is delivered immediately.
|
|
12
|
+
* - A frame pushed while one is already in flight (awaiting ack) is held
|
|
13
|
+
* in a SINGLE queue slot, not appended to a list.
|
|
14
|
+
* - A frame pushed while the queue slot is already occupied REPLACES it —
|
|
15
|
+
* the replaced (older, now-stale) frame is counted as dropped and
|
|
16
|
+
* discarded. The queue can never hold more than one frame, so memory
|
|
17
|
+
* use is O(1) regardless of producer rate or consumer slowness.
|
|
18
|
+
* - The queued frame (if any) is delivered exactly when the consumer
|
|
19
|
+
* acks the in-flight one — delivery is entirely ack-gated, never
|
|
20
|
+
* time-gated.
|
|
21
|
+
*
|
|
22
|
+
* This module owns NO timers and no cadence — target ~10fps (per the PRD)
|
|
23
|
+
* is the PRODUCER's concern (e.g. the CDP screencast ack loop calling
|
|
24
|
+
* push() at the capture rate). The pacer only decides deliver-now vs.
|
|
25
|
+
* queue-and-maybe-drop; it never sleeps, polls, or schedules anything.
|
|
26
|
+
*
|
|
27
|
+
* Input events (`browser-input`) do NOT go through this pacer — see the
|
|
28
|
+
* drop rule in browserFrames.ts. This module is frame-type-agnostic (`F`
|
|
29
|
+
* is generic) precisely so it can't accidentally be reused for input.
|
|
30
|
+
*/
|
|
31
|
+
export interface FramePacerStats {
|
|
32
|
+
/** Frames replaced in the queue slot before ever being delivered. */
|
|
33
|
+
droppedCount: number;
|
|
34
|
+
/** Frames actually handed to onDeliver. */
|
|
35
|
+
deliveredCount: number;
|
|
36
|
+
}
|
|
37
|
+
export interface FramePacerOptions<F> {
|
|
38
|
+
/**
|
|
39
|
+
* Called synchronously the moment the pacer decides to deliver a frame —
|
|
40
|
+
* either immediately inside push() (nothing in flight) or immediately
|
|
41
|
+
* inside ack() (a queued frame was waiting). Must not throw for frames
|
|
42
|
+
* the pacer already validated as deliverable; any error propagates to
|
|
43
|
+
* the caller of push()/ack() (the pacer does no error swallowing of its
|
|
44
|
+
* own — that's the producer/consumer's call).
|
|
45
|
+
*/
|
|
46
|
+
onDeliver: (frame: F) => void;
|
|
47
|
+
}
|
|
48
|
+
export interface FramePacer<F> extends FramePacerStats {
|
|
49
|
+
/** Producer calls this once per captured frame. */
|
|
50
|
+
push(frame: F): void;
|
|
51
|
+
/** Consumer calls this once it has finished handling the last delivered
|
|
52
|
+
* frame. If a frame is queued, it is delivered now (synchronously,
|
|
53
|
+
* awaiting the NEXT ack) and the queue slot empties. A no-op if nothing
|
|
54
|
+
* is queued, and a safe no-op after close(). */
|
|
55
|
+
ack(): void;
|
|
56
|
+
/** Ends the session: discards any queued frame (uncounted — it's not a
|
|
57
|
+
* "replacement", the session is simply over) and makes every further
|
|
58
|
+
* push()/ack() call a safe no-op. Idempotent. */
|
|
59
|
+
close(): void;
|
|
60
|
+
}
|
|
61
|
+
export declare function createFramePacer<F>(opts: FramePacerOptions<F>): FramePacer<F>;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* cbs-01 — keep-newest frame pacer (pure, no I/O).
|
|
4
|
+
*
|
|
5
|
+
* Backpressure policy for the cloud browser session's `browser-frame`
|
|
6
|
+
* stream (docs/prd/cloud-browser-session.md, "Backpressure" decision): the
|
|
7
|
+
* relay has no message-size limit and no built-in backpressure, so an
|
|
8
|
+
* unbounded queue in front of a slow consumer (bad wifi, a stalled pane) is
|
|
9
|
+
* how memory balloons. This pacer enforces "at most ONE undelivered frame
|
|
10
|
+
* per session, newest wins":
|
|
11
|
+
*
|
|
12
|
+
* - The first pushed frame (nothing in flight) is delivered immediately.
|
|
13
|
+
* - A frame pushed while one is already in flight (awaiting ack) is held
|
|
14
|
+
* in a SINGLE queue slot, not appended to a list.
|
|
15
|
+
* - A frame pushed while the queue slot is already occupied REPLACES it —
|
|
16
|
+
* the replaced (older, now-stale) frame is counted as dropped and
|
|
17
|
+
* discarded. The queue can never hold more than one frame, so memory
|
|
18
|
+
* use is O(1) regardless of producer rate or consumer slowness.
|
|
19
|
+
* - The queued frame (if any) is delivered exactly when the consumer
|
|
20
|
+
* acks the in-flight one — delivery is entirely ack-gated, never
|
|
21
|
+
* time-gated.
|
|
22
|
+
*
|
|
23
|
+
* This module owns NO timers and no cadence — target ~10fps (per the PRD)
|
|
24
|
+
* is the PRODUCER's concern (e.g. the CDP screencast ack loop calling
|
|
25
|
+
* push() at the capture rate). The pacer only decides deliver-now vs.
|
|
26
|
+
* queue-and-maybe-drop; it never sleeps, polls, or schedules anything.
|
|
27
|
+
*
|
|
28
|
+
* Input events (`browser-input`) do NOT go through this pacer — see the
|
|
29
|
+
* drop rule in browserFrames.ts. This module is frame-type-agnostic (`F`
|
|
30
|
+
* is generic) precisely so it can't accidentally be reused for input.
|
|
31
|
+
*/
|
|
32
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
33
|
+
exports.createFramePacer = createFramePacer;
|
|
34
|
+
function createFramePacer(opts) {
|
|
35
|
+
let awaitingAck = false;
|
|
36
|
+
let queued = null;
|
|
37
|
+
let closed = false;
|
|
38
|
+
let droppedCount = 0;
|
|
39
|
+
let deliveredCount = 0;
|
|
40
|
+
const deliver = (frame) => {
|
|
41
|
+
awaitingAck = true;
|
|
42
|
+
deliveredCount += 1;
|
|
43
|
+
opts.onDeliver(frame);
|
|
44
|
+
};
|
|
45
|
+
return {
|
|
46
|
+
get droppedCount() {
|
|
47
|
+
return droppedCount;
|
|
48
|
+
},
|
|
49
|
+
get deliveredCount() {
|
|
50
|
+
return deliveredCount;
|
|
51
|
+
},
|
|
52
|
+
push(frame) {
|
|
53
|
+
if (closed)
|
|
54
|
+
return;
|
|
55
|
+
if (!awaitingAck) {
|
|
56
|
+
deliver(frame);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
if (queued !== null)
|
|
60
|
+
droppedCount += 1; // replacing an already-queued frame
|
|
61
|
+
queued = { frame };
|
|
62
|
+
},
|
|
63
|
+
ack() {
|
|
64
|
+
if (closed)
|
|
65
|
+
return;
|
|
66
|
+
awaitingAck = false;
|
|
67
|
+
if (queued !== null) {
|
|
68
|
+
const next = queued.frame;
|
|
69
|
+
queued = null;
|
|
70
|
+
deliver(next);
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
close() {
|
|
74
|
+
closed = true;
|
|
75
|
+
queued = null;
|
|
76
|
+
awaitingAck = false;
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { FolderRegistry } from './folderRegistry';
|
|
2
|
+
export interface LocalAddCoreDeps {
|
|
3
|
+
loadRegistry: () => FolderRegistry;
|
|
4
|
+
saveRegistry: (registry: FolderRegistry) => void;
|
|
5
|
+
createWorkspace: (name: string) => Promise<{
|
|
6
|
+
workspace: {
|
|
7
|
+
id: string;
|
|
8
|
+
name: string;
|
|
9
|
+
};
|
|
10
|
+
}>;
|
|
11
|
+
}
|
|
12
|
+
export type LocalAddCoreResult = {
|
|
13
|
+
status: 'created';
|
|
14
|
+
workspace: {
|
|
15
|
+
id: string;
|
|
16
|
+
name: string;
|
|
17
|
+
};
|
|
18
|
+
} | {
|
|
19
|
+
status: 'existing';
|
|
20
|
+
workspace: {
|
|
21
|
+
id: string;
|
|
22
|
+
name: string;
|
|
23
|
+
};
|
|
24
|
+
} | {
|
|
25
|
+
status: 'error';
|
|
26
|
+
message: string;
|
|
27
|
+
};
|
|
28
|
+
export declare function addLocalWorkspaceCore(absPath: string, nameOverride: string | undefined, deps: LocalAddCoreDeps): Promise<LocalAddCoreResult>;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.addLocalWorkspaceCore = addLocalWorkspaceCore;
|
|
37
|
+
/**
|
|
38
|
+
* lws-05 — bridge-side idempotent add core, behind POST /local/add. Mirrors
|
|
39
|
+
* `wen-market local add`'s behavior (commands/local.ts) so the arena
|
|
40
|
+
* create-modal's Browse/typed-path attach mechanisms (PRD D3b/D3c) end at the
|
|
41
|
+
* exact same registered-workspace outcome as the CLI mechanism (D3a) —
|
|
42
|
+
* deps-injected so server.ts can wire it without this module knowing about
|
|
43
|
+
* fs, network, or the WorkspaceRecord shape server.ts already has.
|
|
44
|
+
*/
|
|
45
|
+
const path = __importStar(require("path"));
|
|
46
|
+
const folderRegistry_1 = require("./folderRegistry");
|
|
47
|
+
async function addLocalWorkspaceCore(absPath, nameOverride, deps) {
|
|
48
|
+
const registry = deps.loadRegistry();
|
|
49
|
+
const existing = (0, folderRegistry_1.findEntryByPath)(registry, absPath);
|
|
50
|
+
if (existing) {
|
|
51
|
+
return { status: 'existing', workspace: { id: existing.workspaceId, name: existing.name } };
|
|
52
|
+
}
|
|
53
|
+
const name = nameOverride || path.basename(absPath);
|
|
54
|
+
try {
|
|
55
|
+
const result = await deps.createWorkspace(name);
|
|
56
|
+
const updated = (0, folderRegistry_1.upsertEntry)(registry, absPath, {
|
|
57
|
+
workspaceId: result.workspace.id,
|
|
58
|
+
name: result.workspace.name,
|
|
59
|
+
addedAt: new Date().toISOString(),
|
|
60
|
+
});
|
|
61
|
+
deps.saveRegistry(updated);
|
|
62
|
+
return { status: 'created', workspace: result.workspace };
|
|
63
|
+
}
|
|
64
|
+
catch (err) {
|
|
65
|
+
return { status: 'error', message: err?.message || String(err) };
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export interface LocalPathDeps {
|
|
2
|
+
exists: (absPath: string) => boolean;
|
|
3
|
+
isDirectory: (absPath: string) => boolean;
|
|
4
|
+
/** Best-effort readability probe (e.g. fs.accessSync with R_OK). */
|
|
5
|
+
readable: (absPath: string) => boolean;
|
|
6
|
+
/** Counts files under the folder (implementation may cap/skip heavy dirs
|
|
7
|
+
* like node_modules/.git — this is a quick echo, not the D5 tree scan). */
|
|
8
|
+
countFiles: (absPath: string) => number;
|
|
9
|
+
isGitRepo: (absPath: string) => boolean;
|
|
10
|
+
}
|
|
11
|
+
export type ValidatePathResult = {
|
|
12
|
+
ok: true;
|
|
13
|
+
name: string;
|
|
14
|
+
fileCount: number;
|
|
15
|
+
isGitRepo: boolean;
|
|
16
|
+
} | {
|
|
17
|
+
ok: false;
|
|
18
|
+
error: 'not-exists' | 'not-dir' | 'unreadable';
|
|
19
|
+
};
|
|
20
|
+
export declare function validateLocalPath(absPath: string, deps: LocalPathDeps): Promise<ValidatePathResult>;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.validateLocalPath = validateLocalPath;
|
|
37
|
+
/**
|
|
38
|
+
* lws-05 — POST /local/validate-path. Backs the create-modal's typed-path
|
|
39
|
+
* fallback (PRD D3c): echoes "my-app — 214 files, git repo detected" (or a
|
|
40
|
+
* typed error) before the user confirms. Pure logic, fs fully injected so
|
|
41
|
+
* the endpoint handler (server.ts) is unit-testable without real disk I/O —
|
|
42
|
+
* the real implementation (wired in commands/bridge.ts) uses node:fs/path.
|
|
43
|
+
*/
|
|
44
|
+
const path = __importStar(require("path"));
|
|
45
|
+
async function validateLocalPath(absPath, deps) {
|
|
46
|
+
if (!deps.exists(absPath))
|
|
47
|
+
return { ok: false, error: 'not-exists' };
|
|
48
|
+
if (!deps.isDirectory(absPath))
|
|
49
|
+
return { ok: false, error: 'not-dir' };
|
|
50
|
+
if (!deps.readable(absPath))
|
|
51
|
+
return { ok: false, error: 'unreadable' };
|
|
52
|
+
return {
|
|
53
|
+
ok: true,
|
|
54
|
+
name: path.basename(absPath),
|
|
55
|
+
fileCount: deps.countFiles(absPath),
|
|
56
|
+
isGitRepo: deps.isGitRepo(absPath),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.realLocalPathDeps = realLocalPathDeps;
|
|
37
|
+
/**
|
|
38
|
+
* lws-05 — real fs-backed LocalPathDeps for POST /local/validate-path
|
|
39
|
+
* (commands/bridge.ts wiring). File count is a quick echo for the
|
|
40
|
+
* create-modal ("my-app — 214 files, git repo detected"), NOT the D5 tree
|
|
41
|
+
* scan — it skips node_modules/.git and hard-caps to keep the endpoint fast
|
|
42
|
+
* on huge folders.
|
|
43
|
+
*/
|
|
44
|
+
const fs = __importStar(require("fs"));
|
|
45
|
+
const path = __importStar(require("path"));
|
|
46
|
+
const SKIP_DIRS = new Set(['node_modules', '.git']);
|
|
47
|
+
const COUNT_CAP = 20000;
|
|
48
|
+
function countFiles(root) {
|
|
49
|
+
let count = 0;
|
|
50
|
+
const stack = [root];
|
|
51
|
+
while (stack.length > 0 && count < COUNT_CAP) {
|
|
52
|
+
const dir = stack.pop();
|
|
53
|
+
let entries;
|
|
54
|
+
try {
|
|
55
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
for (const entry of entries) {
|
|
61
|
+
if (count >= COUNT_CAP)
|
|
62
|
+
break;
|
|
63
|
+
if (entry.isDirectory()) {
|
|
64
|
+
if (SKIP_DIRS.has(entry.name))
|
|
65
|
+
continue;
|
|
66
|
+
stack.push(path.join(dir, entry.name));
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
count++;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return count;
|
|
74
|
+
}
|
|
75
|
+
function realLocalPathDeps() {
|
|
76
|
+
return {
|
|
77
|
+
exists: (p) => fs.existsSync(p),
|
|
78
|
+
isDirectory: (p) => {
|
|
79
|
+
try {
|
|
80
|
+
return fs.statSync(p).isDirectory();
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
readable: (p) => {
|
|
87
|
+
try {
|
|
88
|
+
fs.accessSync(p, fs.constants.R_OK);
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
countFiles,
|
|
96
|
+
isGitRepo: (p) => fs.existsSync(path.join(p, '.git')),
|
|
97
|
+
};
|
|
98
|
+
}
|