@wenrwa/marketplace-cli 0.8.8 → 0.8.9
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-env-names.test.d.ts +1 -0
- package/dist/__tests__/bridge-env-names.test.js +243 -0
- package/dist/__tests__/bridge-preview-checkout.test.d.ts +1 -0
- package/dist/__tests__/bridge-preview-checkout.test.js +294 -0
- package/dist/__tests__/bridge-preview-forwarder.test.d.ts +1 -0
- package/dist/__tests__/bridge-preview-forwarder.test.js +465 -0
- package/dist/__tests__/bridge-preview-frames.test.d.ts +1 -0
- package/dist/__tests__/bridge-preview-frames.test.js +165 -0
- package/dist/bridge/envNames.d.ts +26 -0
- package/dist/bridge/envNames.js +114 -0
- package/dist/bridge/previewCheckout.d.ts +136 -0
- package/dist/bridge/previewCheckout.js +677 -0
- package/dist/bridge/previewForwarder.d.ts +116 -0
- package/dist/bridge/previewForwarder.js +323 -0
- package/dist/bridge/previewFrames.d.ts +63 -0
- package/dist/bridge/previewFrames.js +59 -0
- package/dist/bridge/relayClient.d.ts +15 -0
- package/dist/bridge/relayClient.js +36 -0
- package/dist/bridge/server.d.ts +7 -0
- package/dist/bridge/server.js +40 -1
- package/dist/commands/bridge.js +31 -0
- package/package.json +1 -1
|
@@ -0,0 +1,114 @@
|
|
|
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.MAX_ENV_NAMES = exports.ENV_NAMES_CAPABILITY = void 0;
|
|
37
|
+
exports.readEnvNames = readEnvNames;
|
|
38
|
+
exports.handleEnvNamesFrame = handleEnvNamesFrame;
|
|
39
|
+
/**
|
|
40
|
+
* tp-02 (docs/prd/team-preview.md story 39) — the NAMES in a workspace's
|
|
41
|
+
* `.env`, never the values.
|
|
42
|
+
*
|
|
43
|
+
* Workspace settings shows a "Keys" list so a person can check what they have
|
|
44
|
+
* saved on their own machine. Values never leave the machine: the file is
|
|
45
|
+
* parsed here with the SAME parser terminals load it with (repoEnv.ts), the
|
|
46
|
+
* values are dropped on the spot, and only the names go into any reply.
|
|
47
|
+
*
|
|
48
|
+
* Both lanes share this module so they cannot drift:
|
|
49
|
+
* - hosted: the relay's `env-names` frame (relayClient.ts) → `env-names-result`
|
|
50
|
+
* - local: GET /local-env-names/:workspaceId on the loopback bridge (server.ts)
|
|
51
|
+
*
|
|
52
|
+
* tp-14 reuses the same reply to warn the owner about browser-visible names
|
|
53
|
+
* (NEXT_PUBLIC_* etc.) before sharing a preview.
|
|
54
|
+
*
|
|
55
|
+
* Never logs. Never throws.
|
|
56
|
+
*/
|
|
57
|
+
const path = __importStar(require("path"));
|
|
58
|
+
const repoEnv_1 = require("./repoEnv");
|
|
59
|
+
exports.ENV_NAMES_CAPABILITY = 'env-names';
|
|
60
|
+
/** More than this is not a hand-kept key file; the list is capped, never a value leak. */
|
|
61
|
+
exports.MAX_ENV_NAMES = 500;
|
|
62
|
+
/**
|
|
63
|
+
* Names in `<root>/.env`, sorted, deduplicated. `{ok:true, names:[]}` when
|
|
64
|
+
* there is no `.env` (nothing saved yet). A symlinked `.env` is not followed.
|
|
65
|
+
*/
|
|
66
|
+
function readEnvNames(root, deps = repoEnv_1.realRepoEnvDeps) {
|
|
67
|
+
if (!root)
|
|
68
|
+
return { ok: false, reason: 'no-folder' };
|
|
69
|
+
const p = path.join(root, repoEnv_1.DOTENV_FILENAME);
|
|
70
|
+
let text;
|
|
71
|
+
try {
|
|
72
|
+
const st = deps.lstat(p);
|
|
73
|
+
if (!st)
|
|
74
|
+
return { ok: true, names: [] };
|
|
75
|
+
if (!st.isFile())
|
|
76
|
+
return { ok: false, reason: 'not-a-file' };
|
|
77
|
+
if (st.size > repoEnv_1.MAX_DOTENV_BYTES)
|
|
78
|
+
return { ok: false, reason: 'too-large' };
|
|
79
|
+
text = deps.readFile(p);
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
return { ok: false, reason: 'unreadable' };
|
|
83
|
+
}
|
|
84
|
+
// The values are parsed here and go out of scope with this function.
|
|
85
|
+
const names = Object.keys((0, repoEnv_1.parseDotenv)(text)).sort().slice(0, exports.MAX_ENV_NAMES);
|
|
86
|
+
return { ok: true, names };
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* The relay's `env-names` frame → its `env-names-result` reply. `requestId`
|
|
90
|
+
* is echoed so the backend can match concurrent reads. The reply is built
|
|
91
|
+
* from the names array alone, so no value can ride along.
|
|
92
|
+
*/
|
|
93
|
+
function handleEnvNamesFrame(frame, resolveRoot, deps = repoEnv_1.realRepoEnvDeps) {
|
|
94
|
+
const requestId = typeof frame.requestId === 'string' ? frame.requestId : undefined;
|
|
95
|
+
const workspaceId = typeof frame.workspaceId === 'string' ? frame.workspaceId : undefined;
|
|
96
|
+
const head = {
|
|
97
|
+
t: 'env-names-result',
|
|
98
|
+
...(requestId !== undefined ? { requestId } : {}),
|
|
99
|
+
...(workspaceId !== undefined ? { workspaceId } : {}),
|
|
100
|
+
};
|
|
101
|
+
if (!workspaceId)
|
|
102
|
+
return { ...head, ok: false, reason: 'no-folder' };
|
|
103
|
+
let root;
|
|
104
|
+
try {
|
|
105
|
+
root = resolveRoot(workspaceId);
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
root = null;
|
|
109
|
+
}
|
|
110
|
+
const result = readEnvNames(root, deps);
|
|
111
|
+
return result.ok
|
|
112
|
+
? { ...head, ok: true, names: [...result.names] }
|
|
113
|
+
: { ...head, ok: false, reason: result.reason };
|
|
114
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
export type PreviewMode = 'built' | 'dev';
|
|
2
|
+
export type PreviewStatus = 'stopped'
|
|
3
|
+
/** An update is fetching / installing / building right now. */
|
|
4
|
+
| 'building' | 'running'
|
|
5
|
+
/** The newest sha failed to install or build. The last good one (if any) still serves. */
|
|
6
|
+
| 'build_failed'
|
|
7
|
+
/** The newest sha built but never answered on the port. The last good one (if any) still serves. */
|
|
8
|
+
| 'start_failed'
|
|
9
|
+
/** Nothing could be attempted (see `reason`). */
|
|
10
|
+
| 'failed';
|
|
11
|
+
export type PreviewFailReason = 'busy_other_workspace' | 'no_repo_url' | 'git_failed' | 'no_package_json' | 'no_start_script' | 'port_in_use'
|
|
12
|
+
/** The app listened beyond 127.0.0.1 and could not be forced to loopback. */
|
|
13
|
+
| 'unsupported_bind';
|
|
14
|
+
export interface PreviewResult {
|
|
15
|
+
workspaceId: string;
|
|
16
|
+
status: PreviewStatus;
|
|
17
|
+
mode: PreviewMode;
|
|
18
|
+
/** The sha this request tried to bring up (absent when it never got that far). */
|
|
19
|
+
sha?: string;
|
|
20
|
+
/** The sha serving on the preview port right now. */
|
|
21
|
+
runningSha?: string;
|
|
22
|
+
/** The newest sha that built successfully. */
|
|
23
|
+
builtSha?: string;
|
|
24
|
+
reason?: PreviewFailReason;
|
|
25
|
+
/** One short, masked sentence for the owner. Never a log dump. */
|
|
26
|
+
message?: string;
|
|
27
|
+
}
|
|
28
|
+
export interface PreviewGitCredential {
|
|
29
|
+
token: string;
|
|
30
|
+
repoFullName?: string;
|
|
31
|
+
}
|
|
32
|
+
export interface PreviewRequest {
|
|
33
|
+
workspaceId: string;
|
|
34
|
+
repoUrl?: string;
|
|
35
|
+
gitCredential?: PreviewGitCredential;
|
|
36
|
+
mode?: PreviewMode;
|
|
37
|
+
}
|
|
38
|
+
export interface PreviewRunnerOpts {
|
|
39
|
+
/** Root holding every workspace's preview slots, e.g. /data/previews. */
|
|
40
|
+
previewsBaseDir: string;
|
|
41
|
+
/** The owner's working copy for a workspace — read for its `.env` ONLY. */
|
|
42
|
+
workingCopyDir: (workspaceId: string) => string;
|
|
43
|
+
/** The one fixed preview port (tp-06's forwarder targets it). */
|
|
44
|
+
port: number;
|
|
45
|
+
/** The bridge's env; only PASSTHROUGH_ENV names are taken from it. Default process.env. */
|
|
46
|
+
baseEnv?: Record<string, string | undefined>;
|
|
47
|
+
/** How long a started server gets to answer on the port. */
|
|
48
|
+
readyTimeoutMs?: {
|
|
49
|
+
built: number;
|
|
50
|
+
dev: number;
|
|
51
|
+
};
|
|
52
|
+
probeIntervalMs?: number;
|
|
53
|
+
/** Bound on each install / build step. */
|
|
54
|
+
stepTimeoutMs?: number;
|
|
55
|
+
/** SIGTERM → SIGKILL grace when stopping a process group. */
|
|
56
|
+
killGraceMs?: number;
|
|
57
|
+
/** This machine's non-loopback addresses (the bind check). Default: os.networkInterfaces(). */
|
|
58
|
+
externalAddresses?: () => string[];
|
|
59
|
+
}
|
|
60
|
+
/** The only names a preview process takes from the bridge's own env. */
|
|
61
|
+
/** The only address a preview app may listen on. */
|
|
62
|
+
export declare const PREVIEW_BIND_HOST = "127.0.0.1";
|
|
63
|
+
export declare const PREVIEW_PASSTHROUGH_ENV: readonly ["PATH", "HOME", "USER", "LANG", "LC_ALL", "TZ", "TMPDIR", "SHELL"];
|
|
64
|
+
/**
|
|
65
|
+
* The preview env: allowlisted process names < the owner's repo `.env`
|
|
66
|
+
* (reserved names already removed by loadRepoEnv) < the fixed PORT.
|
|
67
|
+
* Pure, so the "bridge secrets never reach the preview" rule is testable.
|
|
68
|
+
*/
|
|
69
|
+
export declare function buildPreviewEnv(baseEnv: Record<string, string | undefined>, repoEnv: Record<string, string>, port: number): Record<string, string>;
|
|
70
|
+
/**
|
|
71
|
+
* Extra args that pin a known framework's server to loopback, appended after
|
|
72
|
+
* `--` to `npm run <script>`. Only for scripts whose command IS that
|
|
73
|
+
* framework's server — appending flags to an arbitrary script could break it.
|
|
74
|
+
* Anything else relies on HOST/HOSTNAME and the runtime bind check.
|
|
75
|
+
*/
|
|
76
|
+
export declare function loopbackArgs(scriptCommand: string): string[];
|
|
77
|
+
/** Non-loopback addresses of this machine (IPv4 + IPv6, link-local scoped). */
|
|
78
|
+
export declare function machineExternalAddresses(): string[];
|
|
79
|
+
/** True when ANY non-loopback address answers on the port, i.e. the app is exposed. */
|
|
80
|
+
export declare function listensBeyondLoopback(port: number, addresses: string[], timeoutMs?: number): Promise<boolean>;
|
|
81
|
+
/** `ref: refs/heads/main\tHEAD` → `main`; null when the remote names no HEAD. */
|
|
82
|
+
export declare function parseSymrefHead(lsRemoteOutput: string): string | null;
|
|
83
|
+
/** Which install command a checkout wants, by lockfile. */
|
|
84
|
+
export declare function installCommand(files: (name: string) => boolean): [string, string[]];
|
|
85
|
+
export declare class PreviewRunner {
|
|
86
|
+
private readonly opts;
|
|
87
|
+
private readonly states;
|
|
88
|
+
/** One port, so one operation at a time across the whole box. */
|
|
89
|
+
private chain;
|
|
90
|
+
private orphanSwept;
|
|
91
|
+
constructor(opts: PreviewRunnerOpts);
|
|
92
|
+
/** Bring the preview up. Already serving this workspace in this mode → answered as-is. */
|
|
93
|
+
start(req: PreviewRequest): Promise<PreviewResult>;
|
|
94
|
+
/** Fetch the default branch again and, when it moved, rebuild and swap. */
|
|
95
|
+
update(req: PreviewRequest): Promise<PreviewResult>;
|
|
96
|
+
stop(req: {
|
|
97
|
+
workspaceId: string;
|
|
98
|
+
}): Promise<PreviewResult>;
|
|
99
|
+
/** The masked tail of this workspace's preview log. Owner-scoped by the caller. */
|
|
100
|
+
logs(req: {
|
|
101
|
+
workspaceId: string;
|
|
102
|
+
}): {
|
|
103
|
+
log: string;
|
|
104
|
+
truncated: boolean;
|
|
105
|
+
};
|
|
106
|
+
status(workspaceId: string): PreviewResult;
|
|
107
|
+
/** Stop every preview this runner started (bridge shutdown). Exact PIDs only. */
|
|
108
|
+
shutdown(): Promise<void>;
|
|
109
|
+
private serialize;
|
|
110
|
+
private snapshot;
|
|
111
|
+
private wsDir;
|
|
112
|
+
private logPath;
|
|
113
|
+
private pidfilePath;
|
|
114
|
+
private clearPidfile;
|
|
115
|
+
/**
|
|
116
|
+
* A bridge restart loses `states` but not the detached server it started,
|
|
117
|
+
* which would keep the port. Stop it by the exact PID this module recorded —
|
|
118
|
+
* only if that PID still leads a process group we would have created.
|
|
119
|
+
*/
|
|
120
|
+
private sweepOrphans;
|
|
121
|
+
private otherServingWorkspace;
|
|
122
|
+
private deploy;
|
|
123
|
+
/** Bring the previous slot back after a failed swap, if there was one. */
|
|
124
|
+
private restoreSlot;
|
|
125
|
+
/** The request's own verdict, plus what is actually serving now. */
|
|
126
|
+
private withServing;
|
|
127
|
+
private fail;
|
|
128
|
+
private syncSlot;
|
|
129
|
+
private runStep;
|
|
130
|
+
private spawnServer;
|
|
131
|
+
private waitReady;
|
|
132
|
+
/** Stop the process GROUP this module spawned — by its exact leader PID. */
|
|
133
|
+
private killChild;
|
|
134
|
+
}
|
|
135
|
+
/** Any HTTP answer on 127.0.0.1:port counts as "up". */
|
|
136
|
+
export declare function probeOnce(port: number, timeoutMs?: number): Promise<boolean>;
|