@lasso-ai/cli 1.0.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/ARCHITECTURE.md +342 -0
- package/CHANGELOG.md +102 -0
- package/CODE_OF_CONDUCT.md +129 -0
- package/CONTRIBUTING.md +122 -0
- package/LICENSE +7 -0
- package/README.md +548 -0
- package/SECURITY.md +65 -0
- package/SUPPORT.md +31 -0
- package/dist/cli/agent.d.ts +61 -0
- package/dist/cli/agent.js +418 -0
- package/dist/cli/auth.d.ts +38 -0
- package/dist/cli/auth.js +162 -0
- package/dist/cli/bridge.d.ts +160 -0
- package/dist/cli/bridge.js +450 -0
- package/dist/cli/host/certificates.d.ts +12 -0
- package/dist/cli/host/certificates.js +52 -0
- package/dist/cli/host/client.d.ts +49 -0
- package/dist/cli/host/client.js +141 -0
- package/dist/cli/host/daemon.d.ts +50 -0
- package/dist/cli/host/daemon.js +368 -0
- package/dist/cli/host/dns.d.ts +80 -0
- package/dist/cli/host/dns.js +255 -0
- package/dist/cli/host/install.d.ts +32 -0
- package/dist/cli/host/install.js +177 -0
- package/dist/cli/host/next-host-entry.d.ts +1 -0
- package/dist/cli/host/next-host-entry.js +82 -0
- package/dist/cli/host/paths.d.ts +15 -0
- package/dist/cli/host/paths.js +55 -0
- package/dist/cli/host/registry.d.ts +31 -0
- package/dist/cli/host/registry.js +103 -0
- package/dist/cli/host/runtime.d.ts +23 -0
- package/dist/cli/host/runtime.js +134 -0
- package/dist/cli/host/vite-host-entry.d.ts +1 -0
- package/dist/cli/host/vite-host-entry.js +59 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +322 -0
- package/dist/cli/project.d.ts +56 -0
- package/dist/cli/project.js +298 -0
- package/dist/cli/server/next.d.ts +6 -0
- package/dist/cli/server/next.js +76 -0
- package/dist/cli/server/vite.d.ts +1 -0
- package/dist/cli/server/vite.js +51 -0
- package/dist/cli/utils/framework.d.ts +2 -0
- package/dist/cli/utils/framework.js +26 -0
- package/dist/overlay.js +20365 -0
- package/docs/PUBLISHING.md +63 -0
- package/package.json +106 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
export declare const LASSO_CONFIG_FILE = "lasso.config.json";
|
|
2
|
+
export interface CollabConfig {
|
|
3
|
+
projectId: string;
|
|
4
|
+
name: string;
|
|
5
|
+
version?: string;
|
|
6
|
+
realtimeUrl: string;
|
|
7
|
+
apiUrl: string;
|
|
8
|
+
registered: boolean;
|
|
9
|
+
registeredAt?: string;
|
|
10
|
+
claimed: boolean;
|
|
11
|
+
workspaceId?: string;
|
|
12
|
+
token?: string;
|
|
13
|
+
apiKey?: string;
|
|
14
|
+
}
|
|
15
|
+
export interface InitResult {
|
|
16
|
+
ok: boolean;
|
|
17
|
+
error?: string;
|
|
18
|
+
projectId?: string;
|
|
19
|
+
workspaceId?: string;
|
|
20
|
+
name?: string;
|
|
21
|
+
domain?: string;
|
|
22
|
+
}
|
|
23
|
+
export declare function readEnvFile(filename: string, cwd: string): Record<string, string>;
|
|
24
|
+
/** Reads .env / .env.local / .env.production from the project and common sub-roots. */
|
|
25
|
+
export declare function readProjectEnv(cwd: string): Record<string, string>;
|
|
26
|
+
/**
|
|
27
|
+
* The Lasso API key (`lss_live_…`) is the CLI credential. Lookup order:
|
|
28
|
+
* the environment (`LASSO_API_KEY` + `.env` files) first, then the credential
|
|
29
|
+
* stored by `lasso auth login`. It is NEVER written to lasso.config.json.
|
|
30
|
+
*/
|
|
31
|
+
export declare function resolveLassoApiKey(fileEnv: Record<string, string>): string;
|
|
32
|
+
/**
|
|
33
|
+
* The project identity that makes collaboration work. `lasso init` writes it;
|
|
34
|
+
* `lasso dev` reads it and validates it server-side against the API key's workspace.
|
|
35
|
+
* `domain` (the local `.lasso` Host domain) is optional and local-only.
|
|
36
|
+
*/
|
|
37
|
+
export declare function readProjectConfig(cwd: string): {
|
|
38
|
+
id: string;
|
|
39
|
+
source: string;
|
|
40
|
+
domain?: string;
|
|
41
|
+
} | null;
|
|
42
|
+
/**
|
|
43
|
+
* `lasso init` — creates (or re-registers) the project in the API key's workspace,
|
|
44
|
+
* writes `lasso.config.json` with the project id and its local `.lasso` Host domain,
|
|
45
|
+
* and registers the domain with Lasso Host (or the local registry if Host isn't
|
|
46
|
+
* running). Only `{ "id": "proj_…", "domain": "….lasso" }` is stored — the API key
|
|
47
|
+
* never touches this file.
|
|
48
|
+
*/
|
|
49
|
+
export declare function initProject(cwd: string, fileEnv: Record<string, string>): Promise<InitResult>;
|
|
50
|
+
/**
|
|
51
|
+
* `lasso dev` — consumes `lasso.config.json`: authenticates with the API key, lets the
|
|
52
|
+
* server resolve the key's workspace, validates the project belongs to it, and
|
|
53
|
+
* authenticates the project session. Returns null-enabled CollabConfig so realtime is
|
|
54
|
+
* safely degraded (no collaboration, local editing unaffected) on any failure.
|
|
55
|
+
*/
|
|
56
|
+
export declare function resolveCollabSession(cwd: string, fileEnv: Record<string, string>, projectId: string): Promise<CollabConfig>;
|
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.LASSO_CONFIG_FILE = void 0;
|
|
7
|
+
exports.readEnvFile = readEnvFile;
|
|
8
|
+
exports.readProjectEnv = readProjectEnv;
|
|
9
|
+
exports.resolveLassoApiKey = resolveLassoApiKey;
|
|
10
|
+
exports.readProjectConfig = readProjectConfig;
|
|
11
|
+
exports.initProject = initProject;
|
|
12
|
+
exports.resolveCollabSession = resolveCollabSession;
|
|
13
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
14
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
15
|
+
const node_readline_1 = __importDefault(require("node:readline"));
|
|
16
|
+
const node_child_process_1 = require("node:child_process");
|
|
17
|
+
const node_util_1 = require("node:util");
|
|
18
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
19
|
+
const framework_1 = require("./utils/framework");
|
|
20
|
+
const auth_1 = require("./auth");
|
|
21
|
+
const registry_1 = require("./host/registry");
|
|
22
|
+
const client_1 = require("./host/client");
|
|
23
|
+
const paths_1 = require("./host/paths");
|
|
24
|
+
const execFileAsync = (0, node_util_1.promisify)(node_child_process_1.execFile);
|
|
25
|
+
exports.LASSO_CONFIG_FILE = "lasso.config.json";
|
|
26
|
+
const PROJECT_STATE_FILE = ".lasso-project.json";
|
|
27
|
+
function readEnvFile(filename, cwd) {
|
|
28
|
+
try {
|
|
29
|
+
return node_fs_1.default.readFileSync(node_path_1.default.join(cwd, filename), "utf8").split(/\r?\n/).reduce((values, line) => {
|
|
30
|
+
const match = line.match(/^\s*(?:export\s+)?([A-Z0-9_]+)\s*=\s*(.*)\s*$/);
|
|
31
|
+
if (match) {
|
|
32
|
+
let value = match[2].trim();
|
|
33
|
+
if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'")))
|
|
34
|
+
value = value.slice(1, -1);
|
|
35
|
+
values[match[1]] = value.trim();
|
|
36
|
+
}
|
|
37
|
+
return values;
|
|
38
|
+
}, {});
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
return {};
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/** Reads .env / .env.local / .env.production from the project and common sub-roots. */
|
|
45
|
+
function readProjectEnv(cwd) {
|
|
46
|
+
const envRoots = [cwd, node_path_1.default.join(cwd, "web"), node_path_1.default.join(cwd, "server")];
|
|
47
|
+
return envRoots.reduce((values, root) => ({
|
|
48
|
+
...values,
|
|
49
|
+
...readEnvFile(".env", root),
|
|
50
|
+
...readEnvFile(".env.local", root),
|
|
51
|
+
...readEnvFile(".env.production", root),
|
|
52
|
+
}), {});
|
|
53
|
+
}
|
|
54
|
+
function readJson(filePath) {
|
|
55
|
+
try {
|
|
56
|
+
return JSON.parse(node_fs_1.default.readFileSync(filePath, "utf8"));
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function envFrom(fileEnv) {
|
|
63
|
+
const all = { ...process.env, ...fileEnv };
|
|
64
|
+
return all[`REALTIME_URL`] || all[`LASSO_REALTIME_URL`] || all[`NEXT_PUBLIC_REALTIME_URL`] || all[`API_URL`] || "";
|
|
65
|
+
}
|
|
66
|
+
function realtimeUrlFrom(fileEnv) {
|
|
67
|
+
const fromEnv = envFrom(fileEnv);
|
|
68
|
+
if (fromEnv)
|
|
69
|
+
return fromEnv;
|
|
70
|
+
const all = { ...process.env, ...fileEnv };
|
|
71
|
+
// If a local port override is explicitly set, prefer localhost.
|
|
72
|
+
if (all[`COLLAB_PORT`] || all[`REALTIME_PORT`]) {
|
|
73
|
+
const port = all[`COLLAB_PORT`] || all[`REALTIME_PORT`];
|
|
74
|
+
return `http://localhost:${port}`;
|
|
75
|
+
}
|
|
76
|
+
// Default to the hosted collab server; fall back to local dev server.
|
|
77
|
+
return `https://collab.lasso.byorello.space`;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* The Lasso API key (`lss_live_…`) is the CLI credential. Lookup order:
|
|
81
|
+
* the environment (`LASSO_API_KEY` + `.env` files) first, then the credential
|
|
82
|
+
* stored by `lasso auth login`. It is NEVER written to lasso.config.json.
|
|
83
|
+
*/
|
|
84
|
+
function resolveLassoApiKey(fileEnv) {
|
|
85
|
+
const fromEnv = (process.env.LASSO_API_KEY || fileEnv.LASSO_API_KEY || "").trim();
|
|
86
|
+
if (fromEnv)
|
|
87
|
+
return fromEnv;
|
|
88
|
+
const stored = (0, auth_1.loadCredentials)();
|
|
89
|
+
return stored?.apiKey || "";
|
|
90
|
+
}
|
|
91
|
+
function parseAuthor(author) {
|
|
92
|
+
if (typeof author === "string")
|
|
93
|
+
return author;
|
|
94
|
+
if (author && typeof author === "object") {
|
|
95
|
+
const parts = [author.name, author.email].filter(Boolean);
|
|
96
|
+
return parts.join(" ");
|
|
97
|
+
}
|
|
98
|
+
return "";
|
|
99
|
+
}
|
|
100
|
+
function parseRepository(repository) {
|
|
101
|
+
if (typeof repository === "string")
|
|
102
|
+
return repository;
|
|
103
|
+
if (repository && typeof repository === "object") {
|
|
104
|
+
const url = repository.url;
|
|
105
|
+
if (url)
|
|
106
|
+
return url.replace(/^git\+/, "");
|
|
107
|
+
}
|
|
108
|
+
return "";
|
|
109
|
+
}
|
|
110
|
+
function dependencyNames(pkg) {
|
|
111
|
+
const names = [];
|
|
112
|
+
for (const section of ["dependencies", "devDependencies", "peerDependencies"]) {
|
|
113
|
+
const map = pkg[section];
|
|
114
|
+
if (map && typeof map === "object")
|
|
115
|
+
names.push(...Object.keys(map));
|
|
116
|
+
}
|
|
117
|
+
return names.slice(0, 80);
|
|
118
|
+
}
|
|
119
|
+
async function detectGitRemote(cwd) {
|
|
120
|
+
try {
|
|
121
|
+
const result = await execFileAsync("git", ["remote", "get-url", "origin"], { cwd, maxBuffer: 1024 * 1024 });
|
|
122
|
+
return result.stdout.trim();
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
return "";
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* The project identity that makes collaboration work. `lasso init` writes it;
|
|
130
|
+
* `lasso dev` reads it and validates it server-side against the API key's workspace.
|
|
131
|
+
* `domain` (the local `.lasso` Host domain) is optional and local-only.
|
|
132
|
+
*/
|
|
133
|
+
function readProjectConfig(cwd) {
|
|
134
|
+
const direct = readJson(node_path_1.default.join(cwd, exports.LASSO_CONFIG_FILE));
|
|
135
|
+
const directId = direct && typeof direct.id === "string" ? direct.id.trim() : "";
|
|
136
|
+
if (directId) {
|
|
137
|
+
const domain = direct && typeof direct.domain === "string" ? direct.domain.trim() : undefined;
|
|
138
|
+
return { id: directId, source: exports.LASSO_CONFIG_FILE, domain: domain || undefined };
|
|
139
|
+
}
|
|
140
|
+
// Transitional: honor the legacy state file until a fresh `lasso init` is run.
|
|
141
|
+
const legacy = readJson(node_path_1.default.join(cwd, PROJECT_STATE_FILE));
|
|
142
|
+
const legacyId = legacy && typeof legacy.projectId === "string" ? legacy.projectId.trim() : "";
|
|
143
|
+
if (legacyId)
|
|
144
|
+
return { id: legacyId, source: PROJECT_STATE_FILE };
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
async function buildProjectMetadata(cwd, framework) {
|
|
148
|
+
const pkg = readJson(node_path_1.default.join(cwd, "package.json")) || {};
|
|
149
|
+
const name = typeof pkg.name === "string" && pkg.name ? pkg.name : node_path_1.default.basename(cwd) || "untitled";
|
|
150
|
+
const version = typeof pkg.version === "string" ? pkg.version : "";
|
|
151
|
+
const gitRemote = (typeof pkg.repository === "string" ? pkg.repository : parseRepository(pkg.repository)) || (await detectGitRemote(cwd));
|
|
152
|
+
const payload = {
|
|
153
|
+
name,
|
|
154
|
+
version,
|
|
155
|
+
description: typeof pkg.description === "string" ? pkg.description : "",
|
|
156
|
+
framework,
|
|
157
|
+
gitRemote,
|
|
158
|
+
repository: gitRemote,
|
|
159
|
+
author: parseAuthor(pkg.author),
|
|
160
|
+
homepage: typeof pkg.homepage === "string" ? pkg.homepage : "",
|
|
161
|
+
keywords: Array.isArray(pkg.keywords) ? pkg.keywords.slice(0, 40) : [],
|
|
162
|
+
packageInfo: {
|
|
163
|
+
name,
|
|
164
|
+
version,
|
|
165
|
+
description: typeof pkg.description === "string" ? pkg.description : "",
|
|
166
|
+
license: typeof pkg.license === "string" ? pkg.license : "",
|
|
167
|
+
type: typeof pkg.type === "string" ? pkg.type : "",
|
|
168
|
+
dependencies: dependencyNames(pkg),
|
|
169
|
+
},
|
|
170
|
+
};
|
|
171
|
+
return { name, version, gitRemote, payload };
|
|
172
|
+
}
|
|
173
|
+
function promptForApiKey() {
|
|
174
|
+
return new Promise((resolve) => {
|
|
175
|
+
const rl = node_readline_1.default.createInterface({ input: process.stdin, output: process.stdout });
|
|
176
|
+
rl.question(`No Lasso key found. Run ${chalk_1.default.cyan("npx lasso auth login")} to sign in, or paste an API key (create one in the dashboard: ${chalk_1.default.cyan("lss_live_…")}): `, (answer) => {
|
|
177
|
+
rl.close();
|
|
178
|
+
resolve(answer.trim() || null);
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* `lasso init` — creates (or re-registers) the project in the API key's workspace,
|
|
184
|
+
* writes `lasso.config.json` with the project id and its local `.lasso` Host domain,
|
|
185
|
+
* and registers the domain with Lasso Host (or the local registry if Host isn't
|
|
186
|
+
* running). Only `{ "id": "proj_…", "domain": "….lasso" }` is stored — the API key
|
|
187
|
+
* never touches this file.
|
|
188
|
+
*/
|
|
189
|
+
async function initProject(cwd, fileEnv) {
|
|
190
|
+
const realtimeUrl = realtimeUrlFrom(fileEnv).replace(/\/$/, "");
|
|
191
|
+
const apiUrl = `${realtimeUrl}/api/v1`;
|
|
192
|
+
const framework = (0, framework_1.detectFramework)(cwd);
|
|
193
|
+
const { name, payload } = await buildProjectMetadata(cwd, framework);
|
|
194
|
+
let apiKey = resolveLassoApiKey(fileEnv);
|
|
195
|
+
if (!apiKey)
|
|
196
|
+
apiKey = (await promptForApiKey()) || "";
|
|
197
|
+
if (!apiKey) {
|
|
198
|
+
return { ok: false, error: "No Lasso API key found. Set LASSO_API_KEY in your environment or paste it when prompted." };
|
|
199
|
+
}
|
|
200
|
+
try {
|
|
201
|
+
const response = await (0, auth_1.fetchWithTimeout)(`${apiUrl}/collab/projects/register`, {
|
|
202
|
+
method: "POST",
|
|
203
|
+
headers: {
|
|
204
|
+
"content-type": "application/json",
|
|
205
|
+
authorization: `Bearer ${apiKey}`,
|
|
206
|
+
},
|
|
207
|
+
body: JSON.stringify(payload),
|
|
208
|
+
});
|
|
209
|
+
const body = (await response.json().catch(() => ({})));
|
|
210
|
+
if (!response.ok) {
|
|
211
|
+
if (response.status === 401) {
|
|
212
|
+
return { ok: false, error: "Your Lasso API key is invalid or inactive. Create or reactivate one in the dashboard." };
|
|
213
|
+
}
|
|
214
|
+
if (response.status === 403) {
|
|
215
|
+
return { ok: false, error: "Your API key does not have access to a Lasso workspace." };
|
|
216
|
+
}
|
|
217
|
+
return { ok: false, error: body.message || `Project registration failed (${response.status}).` };
|
|
218
|
+
}
|
|
219
|
+
const projectId = body.project?.id;
|
|
220
|
+
if (!projectId) {
|
|
221
|
+
return { ok: false, error: "The server did not return a project id." };
|
|
222
|
+
}
|
|
223
|
+
const registry = (0, registry_1.loadRegistry)();
|
|
224
|
+
const existing = (0, registry_1.findByDirectory)(registry, cwd);
|
|
225
|
+
const domain = existing ? existing.domain : (0, registry_1.generateUniqueDomain)(cwd, registry);
|
|
226
|
+
await (0, client_1.registerWithHost)(domain, cwd, projectId, (0, paths_1.hostProxyPort)(fileEnv));
|
|
227
|
+
node_fs_1.default.writeFileSync(node_path_1.default.join(cwd, exports.LASSO_CONFIG_FILE), JSON.stringify({ id: projectId, domain }, null, 2) + "\n");
|
|
228
|
+
return {
|
|
229
|
+
ok: true,
|
|
230
|
+
projectId,
|
|
231
|
+
workspaceId: body.project?.workspaceId,
|
|
232
|
+
name: body.project?.name || name,
|
|
233
|
+
domain,
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
catch (error) {
|
|
237
|
+
const reason = error instanceof Error ? error.message : "request failed";
|
|
238
|
+
return { ok: false, error: `Could not reach the Lasso realtime server at ${realtimeUrl} (${reason}). Is it running?` };
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* `lasso dev` — consumes `lasso.config.json`: authenticates with the API key, lets the
|
|
243
|
+
* server resolve the key's workspace, validates the project belongs to it, and
|
|
244
|
+
* authenticates the project session. Returns null-enabled CollabConfig so realtime is
|
|
245
|
+
* safely degraded (no collaboration, local editing unaffected) on any failure.
|
|
246
|
+
*/
|
|
247
|
+
async function resolveCollabSession(cwd, fileEnv, projectId) {
|
|
248
|
+
const realtimeUrl = realtimeUrlFrom(fileEnv).replace(/\/$/, "");
|
|
249
|
+
const apiUrl = `${realtimeUrl}/api/v1`;
|
|
250
|
+
const config = {
|
|
251
|
+
projectId,
|
|
252
|
+
name: "",
|
|
253
|
+
version: "",
|
|
254
|
+
realtimeUrl,
|
|
255
|
+
apiUrl,
|
|
256
|
+
registered: false,
|
|
257
|
+
claimed: false,
|
|
258
|
+
};
|
|
259
|
+
const apiKey = resolveLassoApiKey(fileEnv);
|
|
260
|
+
if (!apiKey) {
|
|
261
|
+
console.warn(chalk_1.default.yellow("!") + " No Lasso credential found. Run " + chalk_1.default.cyan("npx lasso auth login") + " (or set LASSO_API_KEY) to enable realtime collaboration.");
|
|
262
|
+
return config;
|
|
263
|
+
}
|
|
264
|
+
try {
|
|
265
|
+
const response = await (0, auth_1.fetchWithTimeout)(`${apiUrl}/collab/projects/${encodeURIComponent(projectId)}/session`, {
|
|
266
|
+
method: "POST",
|
|
267
|
+
headers: { authorization: `Bearer ${apiKey}` },
|
|
268
|
+
});
|
|
269
|
+
if (!response.ok) {
|
|
270
|
+
const reason = response.status === 401
|
|
271
|
+
? "Your Lasso API key is invalid or inactive."
|
|
272
|
+
: response.status === 403
|
|
273
|
+
? "This project belongs to another workspace."
|
|
274
|
+
: `The server rejected the project session (${response.status}).`;
|
|
275
|
+
console.warn(chalk_1.default.yellow("!") + ` Realtime collaboration unavailable: ${reason}`);
|
|
276
|
+
return config;
|
|
277
|
+
}
|
|
278
|
+
const body = (await response.json());
|
|
279
|
+
config.name = body.project?.name || "";
|
|
280
|
+
config.version = body.project?.version || "";
|
|
281
|
+
config.workspaceId = body.project?.workspaceId || "";
|
|
282
|
+
config.token = body.token || "";
|
|
283
|
+
config.apiKey = apiKey;
|
|
284
|
+
config.registered = true;
|
|
285
|
+
config.claimed = Boolean(body.session?.id);
|
|
286
|
+
config.registeredAt = new Date().toISOString();
|
|
287
|
+
console.log(chalk_1.default.green("✓") +
|
|
288
|
+
" Project session opened " +
|
|
289
|
+
chalk_1.default.cyan(config.name || projectId) +
|
|
290
|
+
`${config.workspaceId ? ` · workspace ${config.workspaceId}` : ""}`);
|
|
291
|
+
return config;
|
|
292
|
+
}
|
|
293
|
+
catch (error) {
|
|
294
|
+
const reason = error instanceof Error ? error.message : "request failed";
|
|
295
|
+
console.warn(chalk_1.default.yellow("!") + ` Realtime collaboration unavailable: cannot reach ${realtimeUrl} (${reason}).`);
|
|
296
|
+
return config;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.startNextServer = startNextServer;
|
|
7
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
8
|
+
const node_http_1 = __importDefault(require("node:http"));
|
|
9
|
+
const http_proxy_1 = __importDefault(require("http-proxy"));
|
|
10
|
+
const node_fs_1 = require("node:fs");
|
|
11
|
+
const node_path_1 = require("node:path");
|
|
12
|
+
const node_child_process_1 = require("node:child_process");
|
|
13
|
+
const PUBLIC_PORT = 3000; // what the user opens in the browser
|
|
14
|
+
const INTERNAL_PORT = 3055; // where the real `next dev` actually runs
|
|
15
|
+
function serveOverlayBundle(res) {
|
|
16
|
+
const bundlePath = (0, node_path_1.resolve)(process.cwd(), "node_modules/lasso/dist/overlay.js");
|
|
17
|
+
// when running from within the Lasso repo itself (not yet published),
|
|
18
|
+
// this resolves relative to Lasso's own dist folder instead — adjust
|
|
19
|
+
// the path to point at your actual dist/overlay.js location for now.
|
|
20
|
+
res.setHeader("Content-Type", "application/javascript");
|
|
21
|
+
res.end((0, node_fs_1.readFileSync)(bundlePath, "utf-8"));
|
|
22
|
+
}
|
|
23
|
+
async function startNextServer(cwd) {
|
|
24
|
+
// 1. Spawn the user's own Next dev server on an internal port
|
|
25
|
+
const nextBin = (0, node_path_1.resolve)(cwd, "node_modules/.bin/next");
|
|
26
|
+
const nextProcess = (0, node_child_process_1.spawn)(nextBin, ["dev", "--port", String(INTERNAL_PORT)], {
|
|
27
|
+
cwd,
|
|
28
|
+
stdio: "inherit",
|
|
29
|
+
});
|
|
30
|
+
// 2. Proxy pointing at that internal port
|
|
31
|
+
const proxy = http_proxy_1.default.createProxyServer({
|
|
32
|
+
target: `http://localhost:${INTERNAL_PORT}`,
|
|
33
|
+
selfHandleResponse: true, // needed so we can rewrite HTML before it reaches the browser
|
|
34
|
+
ws: true,
|
|
35
|
+
});
|
|
36
|
+
proxy.on("proxyRes", (proxyRes, req, res) => {
|
|
37
|
+
const chunks = [];
|
|
38
|
+
proxyRes.on("data", (chunk) => chunks.push(chunk));
|
|
39
|
+
proxyRes.on("end", () => {
|
|
40
|
+
const body = Buffer.concat(chunks).toString("utf-8");
|
|
41
|
+
const contentType = proxyRes.headers["content-type"] || "";
|
|
42
|
+
res.writeHead(proxyRes.statusCode || 200, proxyRes.headers);
|
|
43
|
+
if (contentType.includes("text/html")) {
|
|
44
|
+
res.end(body.replace("</head>", `<script src="/__lasso/overlay.js?bridgePort=3056"></script></head>`));
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
res.end(body);
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
// 3. Our own HTTP server: serve the real overlay bundle directly,
|
|
52
|
+
// proxy everything else through to Next
|
|
53
|
+
const server = node_http_1.default.createServer((req, res) => {
|
|
54
|
+
if (req.url === "/__lasso/overlay.js") {
|
|
55
|
+
serveOverlayBundle(res);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
proxy.web(req, res, {}, (err) => {
|
|
59
|
+
console.error(chalk_1.default.red("Proxy error:"), err.message);
|
|
60
|
+
res.writeHead(502);
|
|
61
|
+
res.end("Bad gateway — is the Next.js dev server still starting up?");
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
// Forward WebSocket upgrades (Next's Fast Refresh) straight through —
|
|
65
|
+
// without this, HMR breaks the moment it's sitting behind a proxy.
|
|
66
|
+
server.on("upgrade", (req, socket, head) => {
|
|
67
|
+
if (req.url === "/__lasso/ws")
|
|
68
|
+
return; // let attachBridge's own listener handle this one
|
|
69
|
+
proxy.ws(req, socket, head);
|
|
70
|
+
});
|
|
71
|
+
server.listen(PUBLIC_PORT, () => {
|
|
72
|
+
console.log(chalk_1.default.green("✓") + " Next.js dev server running with Lasso attached");
|
|
73
|
+
console.log(` ${chalk_1.default.cyan(`http://localhost:${PUBLIC_PORT}`)}`);
|
|
74
|
+
});
|
|
75
|
+
return { nextProcess, server };
|
|
76
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function startViteServer(cwd: string): Promise<any>;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.startViteServer = startViteServer;
|
|
7
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
8
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
+
const node_path_2 = require("node:path");
|
|
10
|
+
const node_fs_1 = require("node:fs");
|
|
11
|
+
const node_url_1 = require("node:url");
|
|
12
|
+
const node_module_1 = require("node:module");
|
|
13
|
+
// const PLACEHOLDER_OVERLAY_JS = `
|
|
14
|
+
// console.log("[lasso] overlay placeholder loaded");
|
|
15
|
+
// alert("Lasso overlay injected successfully!");
|
|
16
|
+
// `;
|
|
17
|
+
function lassoInjectPlugin(bridgePort = 3056) {
|
|
18
|
+
return {
|
|
19
|
+
name: "lasso-inject",
|
|
20
|
+
transformIndexHtml(html) {
|
|
21
|
+
return html.replace("</head>", `<script src="/__lasso/overlay.js?bridgePort=${bridgePort}"></script></head>`);
|
|
22
|
+
},
|
|
23
|
+
configureServer(server) {
|
|
24
|
+
server.middlewares.use("/__lasso/overlay.js", (req, res) => {
|
|
25
|
+
const bundlePath = (0, node_path_2.resolve)(process.cwd(), "node_modules/lasso/dist/overlay.js");
|
|
26
|
+
res.setHeader("Content-Type", "application/javascript");
|
|
27
|
+
res.end((0, node_fs_1.readFileSync)(bundlePath, "utf-8"));
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
async function startViteServer(cwd) {
|
|
33
|
+
// Anchor resolution to the TARGET project, not Lasso's own file location —
|
|
34
|
+
// works from a CJS-compiled file, unlike import.meta.url.
|
|
35
|
+
const require = (0, node_module_1.createRequire)(node_path_1.default.join(cwd, "package.json"));
|
|
36
|
+
// Still only used to FIND the path.
|
|
37
|
+
const viteEntry = require.resolve("vite");
|
|
38
|
+
// Still must LOAD via dynamic import — Vite ships ESM-only,
|
|
39
|
+
// and require() can't load an ESM-only package regardless of
|
|
40
|
+
// how correctly it resolved the path.
|
|
41
|
+
const { createServer } = await import((0, node_url_1.pathToFileURL)(viteEntry).href);
|
|
42
|
+
const server = await createServer({
|
|
43
|
+
root: cwd,
|
|
44
|
+
server: { open: false },
|
|
45
|
+
plugins: [lassoInjectPlugin()],
|
|
46
|
+
});
|
|
47
|
+
await server.listen();
|
|
48
|
+
console.log(chalk_1.default.green("✓") + " Vite dev server running with Lasso attached");
|
|
49
|
+
server.printUrls();
|
|
50
|
+
return server;
|
|
51
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.detectFramework = detectFramework;
|
|
4
|
+
const node_fs_1 = require("node:fs");
|
|
5
|
+
const node_path_1 = require("node:path");
|
|
6
|
+
function detectFramework(cwd) {
|
|
7
|
+
const hasFile = (name) => (0, node_fs_1.existsSync)((0, node_path_1.resolve)(cwd, name));
|
|
8
|
+
if (hasFile("next.config.js") || hasFile("next.config.ts") || hasFile("next.config.mjs")) {
|
|
9
|
+
return "next";
|
|
10
|
+
}
|
|
11
|
+
if (hasFile("vite.config.ts") || hasFile("vite.config.js") || hasFile("vite.config.mjs")) {
|
|
12
|
+
return "vite";
|
|
13
|
+
}
|
|
14
|
+
// Fallback: check package.json deps in case there's no config file
|
|
15
|
+
// (e.g. a Vite project relying purely on defaults).
|
|
16
|
+
const pkgPath = (0, node_path_1.resolve)(cwd, "package.json");
|
|
17
|
+
if (hasFile("package.json")) {
|
|
18
|
+
const pkg = JSON.parse((0, node_fs_1.readFileSync)(pkgPath, "utf-8"));
|
|
19
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
20
|
+
if (deps.next)
|
|
21
|
+
return "next";
|
|
22
|
+
if (deps.vite)
|
|
23
|
+
return "vite";
|
|
24
|
+
}
|
|
25
|
+
return "unknown";
|
|
26
|
+
}
|