@marina-cloud/cli 0.0.3 → 0.0.5
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 +37 -7
- package/dist/marina.mjs +2822 -451
- package/package.json +10 -6
package/dist/marina.mjs
CHANGED
|
@@ -1,8 +1,112 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __esm = (fn, res, err2) => function __init() {
|
|
9
|
+
if (err2) throw err2[0];
|
|
10
|
+
try {
|
|
11
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
12
|
+
} catch (e) {
|
|
13
|
+
throw err2 = [e], e;
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
var __commonJS = (cb, mod) => function __require() {
|
|
17
|
+
try {
|
|
18
|
+
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
19
|
+
} catch (e) {
|
|
20
|
+
throw mod = 0, e;
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
var __export = (target, all) => {
|
|
24
|
+
for (var name in all)
|
|
25
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
26
|
+
};
|
|
27
|
+
var __copyProps = (to, from, except, desc) => {
|
|
28
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
29
|
+
for (let key of __getOwnPropNames(from))
|
|
30
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
31
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
32
|
+
}
|
|
33
|
+
return to;
|
|
34
|
+
};
|
|
35
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
36
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
37
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
38
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
39
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
40
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
41
|
+
mod
|
|
42
|
+
));
|
|
2
43
|
|
|
3
|
-
//
|
|
4
|
-
|
|
5
|
-
|
|
44
|
+
// package.json
|
|
45
|
+
var package_default;
|
|
46
|
+
var init_package = __esm({
|
|
47
|
+
"package.json"() {
|
|
48
|
+
package_default = {
|
|
49
|
+
name: "@marina-cloud/cli",
|
|
50
|
+
version: "0.0.5",
|
|
51
|
+
description: "Command-line client for Marina Cloud",
|
|
52
|
+
homepage: "https://github.com/marina-hq/marina#readme",
|
|
53
|
+
bugs: {
|
|
54
|
+
url: "https://github.com/marina-hq/marina/issues"
|
|
55
|
+
},
|
|
56
|
+
license: "Apache-2.0",
|
|
57
|
+
repository: {
|
|
58
|
+
type: "git",
|
|
59
|
+
url: "git+https://github.com/marina-hq/marina.git",
|
|
60
|
+
directory: "packages/cli"
|
|
61
|
+
},
|
|
62
|
+
bin: {
|
|
63
|
+
marina: "dist/marina.mjs"
|
|
64
|
+
},
|
|
65
|
+
files: [
|
|
66
|
+
"dist"
|
|
67
|
+
],
|
|
68
|
+
type: "module",
|
|
69
|
+
publishConfig: {
|
|
70
|
+
access: "public",
|
|
71
|
+
provenance: true
|
|
72
|
+
},
|
|
73
|
+
scripts: {
|
|
74
|
+
build: 'esbuild src/index.ts --bundle --platform=node --target=node22 --format=esm --loader:.md=text --loader:.yaml=text --loader:.html=text --loader:.css=text --loader:.txt=text --external:@electric-sql/pglite --external:esbuild --outfile=dist/marina.mjs --banner:js="#!/usr/bin/env node" && chmod +x dist/marina.mjs',
|
|
75
|
+
typecheck: "tsc --noEmit",
|
|
76
|
+
test: "pnpm build && node --experimental-strip-types --test src/*.test.ts",
|
|
77
|
+
prepack: "pnpm build"
|
|
78
|
+
},
|
|
79
|
+
dependencies: {
|
|
80
|
+
"@electric-sql/pglite": "^0.3.6",
|
|
81
|
+
esbuild: "^0.28.2",
|
|
82
|
+
ignore: "^7.0.6"
|
|
83
|
+
},
|
|
84
|
+
devDependencies: {
|
|
85
|
+
"@types/node": "26.2.0",
|
|
86
|
+
fflate: "^0.8.3",
|
|
87
|
+
typescript: "7.0.2"
|
|
88
|
+
},
|
|
89
|
+
engines: {
|
|
90
|
+
node: ">=22"
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
// src/identity.ts
|
|
97
|
+
var CLI_VERSION, cliRequestHeaders;
|
|
98
|
+
var init_identity = __esm({
|
|
99
|
+
"src/identity.ts"() {
|
|
100
|
+
"use strict";
|
|
101
|
+
init_package();
|
|
102
|
+
CLI_VERSION = package_default.version;
|
|
103
|
+
cliRequestHeaders = () => ({
|
|
104
|
+
"user-agent": `marina-cli/${CLI_VERSION}`,
|
|
105
|
+
"x-marina-client": "cli",
|
|
106
|
+
"x-marina-client-version": CLI_VERSION
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
});
|
|
6
110
|
|
|
7
111
|
// src/config.ts
|
|
8
112
|
import {
|
|
@@ -16,15 +120,81 @@ import {
|
|
|
16
120
|
} from "node:fs";
|
|
17
121
|
import { homedir } from "node:os";
|
|
18
122
|
import { join } from "node:path";
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
}
|
|
27
|
-
|
|
123
|
+
function normalizeApiUrl(value) {
|
|
124
|
+
const url = new URL(value);
|
|
125
|
+
if (url.protocol !== "https:" && url.protocol !== "http:") {
|
|
126
|
+
throw new Error("the Marina control plane must use http or https");
|
|
127
|
+
}
|
|
128
|
+
if (url.username || url.password || url.search || url.hash || url.pathname !== "/") {
|
|
129
|
+
throw new Error("the Marina control plane must be an origin without a path");
|
|
130
|
+
}
|
|
131
|
+
if (url.protocol === "http:" && url.hostname !== "localhost" && url.hostname !== "127.0.0.1" && url.hostname !== "[::1]") {
|
|
132
|
+
throw new Error("the Marina control plane must use https unless it is local");
|
|
133
|
+
}
|
|
134
|
+
return url.origin;
|
|
135
|
+
}
|
|
136
|
+
function cachedControlPlane(host = controlPlaneHost()) {
|
|
137
|
+
const cached = readProfile().control_plane;
|
|
138
|
+
if (!cached || cached.host !== host) return null;
|
|
139
|
+
try {
|
|
140
|
+
normalizeApiUrl(cached.api_url);
|
|
141
|
+
normalizeApiUrl(cached.dashboard_url);
|
|
142
|
+
return cached;
|
|
143
|
+
} catch {
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
async function resolveControlPlane() {
|
|
148
|
+
const host = controlPlaneHost();
|
|
149
|
+
const cached = cachedControlPlane(host);
|
|
150
|
+
const checkedAt = cached ? Date.parse(cached.checked_at) : Number.NaN;
|
|
151
|
+
if (cached && Number.isFinite(checkedAt) && Date.now() - checkedAt < CONTROL_PLANE_CACHE_MS) {
|
|
152
|
+
resolvedControlPlane = {
|
|
153
|
+
api_url: cached.api_url,
|
|
154
|
+
dashboard_url: cached.dashboard_url,
|
|
155
|
+
latest_cli_version: cached.latest_cli_version
|
|
156
|
+
};
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
if (process.env.MARINA_DISABLE_CONTROL_PLANE_DISCOVERY === "1") return;
|
|
160
|
+
try {
|
|
161
|
+
const response = await fetch(`${host}/.well-known/marina`, {
|
|
162
|
+
headers: { ...cliRequestHeaders(), accept: "application/json" },
|
|
163
|
+
signal: AbortSignal.timeout(3e3)
|
|
164
|
+
});
|
|
165
|
+
if (!response.ok)
|
|
166
|
+
throw new Error(`control-plane discovery failed (${String(response.status)})`);
|
|
167
|
+
const body = await response.json();
|
|
168
|
+
if (body.schema_version !== 1 || typeof body.api_url !== "string" || typeof body.dashboard_url !== "string") {
|
|
169
|
+
throw new Error("control-plane discovery returned an invalid response");
|
|
170
|
+
}
|
|
171
|
+
const discovered = {
|
|
172
|
+
api_url: normalizeApiUrl(body.api_url),
|
|
173
|
+
dashboard_url: normalizeApiUrl(body.dashboard_url),
|
|
174
|
+
latest_cli_version: typeof body.clients?.cli?.latest_version === "string" ? body.clients.cli.latest_version : null
|
|
175
|
+
};
|
|
176
|
+
resolvedControlPlane = discovered;
|
|
177
|
+
writeProfile({
|
|
178
|
+
...readProfile(),
|
|
179
|
+
control_plane: {
|
|
180
|
+
host,
|
|
181
|
+
checked_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
182
|
+
...discovered
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
} catch {
|
|
186
|
+
if (cached) {
|
|
187
|
+
resolvedControlPlane = {
|
|
188
|
+
api_url: cached.api_url,
|
|
189
|
+
dashboard_url: cached.dashboard_url,
|
|
190
|
+
latest_cli_version: cached.latest_cli_version
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
function latestCliVersion() {
|
|
196
|
+
return resolvedControlPlane?.latest_cli_version ?? cachedControlPlane()?.latest_cli_version ?? null;
|
|
197
|
+
}
|
|
28
198
|
function readProfile() {
|
|
29
199
|
try {
|
|
30
200
|
return JSON.parse(readFileSync(PROFILE, "utf8"));
|
|
@@ -60,7 +230,6 @@ function clearToken() {
|
|
|
60
230
|
}
|
|
61
231
|
writeProfile(rest);
|
|
62
232
|
}
|
|
63
|
-
var LINK = ".marina/project.json";
|
|
64
233
|
function readLink(dir) {
|
|
65
234
|
try {
|
|
66
235
|
return JSON.parse(readFileSync(join(dir, LINK), "utf8"));
|
|
@@ -68,135 +237,2295 @@ function readLink(dir) {
|
|
|
68
237
|
return null;
|
|
69
238
|
}
|
|
70
239
|
}
|
|
71
|
-
function writeLink(dir, link) {
|
|
72
|
-
mkdirSync(join(dir, ".marina"), { recursive: true });
|
|
73
|
-
writeFileSync(join(dir, LINK), `${JSON.stringify(link, null, 2)}
|
|
74
|
-
`);
|
|
75
|
-
}
|
|
240
|
+
function writeLink(dir, link) {
|
|
241
|
+
mkdirSync(join(dir, ".marina"), { recursive: true });
|
|
242
|
+
writeFileSync(join(dir, LINK), `${JSON.stringify(link, null, 2)}
|
|
243
|
+
`);
|
|
244
|
+
}
|
|
245
|
+
var MARINA_HOME, PROFILE, PRODUCTION_CONTROL_PLANE, CONTROL_PLANE_CACHE_MS, resolvedControlPlane, controlPlaneHost, apiUrl, dashboardUrl, profilePath, LINK;
|
|
246
|
+
var init_config = __esm({
|
|
247
|
+
"src/config.ts"() {
|
|
248
|
+
"use strict";
|
|
249
|
+
init_identity();
|
|
250
|
+
MARINA_HOME = process.env.MARINA_HOME ?? join(homedir(), ".marina");
|
|
251
|
+
PROFILE = join(MARINA_HOME, "profile");
|
|
252
|
+
PRODUCTION_CONTROL_PLANE = "https://marina.cloud";
|
|
253
|
+
CONTROL_PLANE_CACHE_MS = 60 * 60 * 1e3;
|
|
254
|
+
resolvedControlPlane = null;
|
|
255
|
+
controlPlaneHost = () => normalizeApiUrl(
|
|
256
|
+
process.env.MARINA_CONTROL_PLANE ?? readProfile().control_plane_host ?? PRODUCTION_CONTROL_PLANE
|
|
257
|
+
);
|
|
258
|
+
apiUrl = () => normalizeApiUrl(
|
|
259
|
+
process.env.MARINA_API ?? resolvedControlPlane?.api_url ?? cachedControlPlane()?.api_url ?? controlPlaneHost()
|
|
260
|
+
);
|
|
261
|
+
dashboardUrl = () => {
|
|
262
|
+
if (process.env.MARINA_DASHBOARD) return normalizeApiUrl(process.env.MARINA_DASHBOARD);
|
|
263
|
+
return normalizeApiUrl(
|
|
264
|
+
resolvedControlPlane?.dashboard_url ?? cachedControlPlane()?.dashboard_url ?? controlPlaneHost()
|
|
265
|
+
);
|
|
266
|
+
};
|
|
267
|
+
profilePath = () => PROFILE;
|
|
268
|
+
LINK = ".marina/project.json";
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
// src/api.ts
|
|
273
|
+
async function request(path, init) {
|
|
274
|
+
const token = getToken();
|
|
275
|
+
if (!token) throw new ApiError("unauthenticated", "not signed in \u2014 run `marina setup`", 401);
|
|
276
|
+
const res = await fetch(`${apiUrl()}${path}`, {
|
|
277
|
+
...init,
|
|
278
|
+
headers: { ...cliRequestHeaders(), authorization: `Bearer ${token}`, ...init?.headers }
|
|
279
|
+
});
|
|
280
|
+
const body = await res.json().catch(() => ({}));
|
|
281
|
+
if (!res.ok) {
|
|
282
|
+
throw new ApiError(
|
|
283
|
+
body.error?.code ?? "error",
|
|
284
|
+
body.error?.message ?? `request failed (${String(res.status)})`,
|
|
285
|
+
res.status
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
return body;
|
|
289
|
+
}
|
|
290
|
+
async function me() {
|
|
291
|
+
return request("/v1/me");
|
|
292
|
+
}
|
|
293
|
+
async function exchangeCliLogin(code, codeVerifier) {
|
|
294
|
+
let res;
|
|
295
|
+
try {
|
|
296
|
+
res = await fetch(`${apiUrl()}/cli/auth/exchange`, {
|
|
297
|
+
method: "POST",
|
|
298
|
+
headers: { ...cliRequestHeaders(), "content-type": "application/json" },
|
|
299
|
+
body: JSON.stringify({ code, code_verifier: codeVerifier }),
|
|
300
|
+
signal: AbortSignal.timeout(LOGIN_EXCHANGE_TIMEOUT_MS)
|
|
301
|
+
});
|
|
302
|
+
} catch (error) {
|
|
303
|
+
if (error.name === "TimeoutError") {
|
|
304
|
+
throw new ApiError(
|
|
305
|
+
"login_timeout",
|
|
306
|
+
"the login confirmation request timed out after 15 seconds \u2014 run `marina setup` to try again",
|
|
307
|
+
408
|
|
308
|
+
);
|
|
309
|
+
}
|
|
310
|
+
throw error;
|
|
311
|
+
}
|
|
312
|
+
const body = await res.json().catch(() => ({}));
|
|
313
|
+
if (!res.ok || !body.token) {
|
|
314
|
+
throw new ApiError(
|
|
315
|
+
body.error?.code ?? "login_failed",
|
|
316
|
+
body.error?.message ?? `login exchange failed (${String(res.status)})`,
|
|
317
|
+
res.status
|
|
318
|
+
);
|
|
319
|
+
}
|
|
320
|
+
return body.token;
|
|
321
|
+
}
|
|
322
|
+
async function startDeploy(zip, name, app, baseRevision) {
|
|
323
|
+
const form = new FormData();
|
|
324
|
+
form.set(
|
|
325
|
+
"code",
|
|
326
|
+
new Blob([new Uint8Array(zip).buffer], { type: "application/zip" }),
|
|
327
|
+
"upload.zip"
|
|
328
|
+
);
|
|
329
|
+
form.set("name", name);
|
|
330
|
+
form.set("source", "cli");
|
|
331
|
+
if (app) form.set("app", app);
|
|
332
|
+
if (baseRevision) form.set("base_revision", baseRevision);
|
|
333
|
+
const res = await request("/v1/deploys", {
|
|
334
|
+
method: "POST",
|
|
335
|
+
body: form
|
|
336
|
+
});
|
|
337
|
+
return res.deploy;
|
|
338
|
+
}
|
|
339
|
+
async function pollDeploy(id, onProgress = () => void 0) {
|
|
340
|
+
for (; ; ) {
|
|
341
|
+
const deploy2 = await getDeploy(id);
|
|
342
|
+
onProgress(deploy2);
|
|
343
|
+
if (deploy2.status !== "queued" && deploy2.status !== "building") return deploy2;
|
|
344
|
+
await new Promise((resolve4) => setTimeout(resolve4, 500));
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
async function getDeploy(id) {
|
|
348
|
+
const res = await request(`/v1/deploys/${encodeURIComponent(id)}`);
|
|
349
|
+
return res.deploy;
|
|
350
|
+
}
|
|
351
|
+
async function listApps() {
|
|
352
|
+
const res = await request("/v1/apps");
|
|
353
|
+
return res.apps;
|
|
354
|
+
}
|
|
355
|
+
async function getAppUrl(idOrSlug) {
|
|
356
|
+
return (await getApp(idOrSlug)).url;
|
|
357
|
+
}
|
|
358
|
+
async function listVersions(app) {
|
|
359
|
+
const res = await request(
|
|
360
|
+
`/v1/apps/${encodeURIComponent(app)}/versions`
|
|
361
|
+
);
|
|
362
|
+
return res.versions;
|
|
363
|
+
}
|
|
364
|
+
async function listDeploys(app, limit = 10) {
|
|
365
|
+
const res = await request(
|
|
366
|
+
`/v1/apps/${encodeURIComponent(app)}/deploys?limit=${String(limit)}`
|
|
367
|
+
);
|
|
368
|
+
return res.deploys;
|
|
369
|
+
}
|
|
370
|
+
async function listRuntimeLogs(app, options = {}) {
|
|
371
|
+
const query = new URLSearchParams();
|
|
372
|
+
query.set("limit", String(options.limit ?? 100));
|
|
373
|
+
if (options.cursor) query.set("cursor", options.cursor);
|
|
374
|
+
if (options.level) query.set("level", options.level);
|
|
375
|
+
const response = await request(
|
|
376
|
+
`/v1/apps/${encodeURIComponent(app)}/logs?${query.toString()}`
|
|
377
|
+
);
|
|
378
|
+
return { logs: response.logs, nextCursor: response.next_cursor };
|
|
379
|
+
}
|
|
380
|
+
async function restoreVersion(versionId) {
|
|
381
|
+
const res = await request(`/v1/versions/${versionId}/restore`, {
|
|
382
|
+
method: "POST"
|
|
383
|
+
});
|
|
384
|
+
return res.version;
|
|
385
|
+
}
|
|
386
|
+
var LOGIN_EXCHANGE_TIMEOUT_MS, ApiError, aiModels, getApp;
|
|
387
|
+
var init_api = __esm({
|
|
388
|
+
"src/api.ts"() {
|
|
389
|
+
"use strict";
|
|
390
|
+
init_config();
|
|
391
|
+
init_identity();
|
|
392
|
+
LOGIN_EXCHANGE_TIMEOUT_MS = 15e3;
|
|
393
|
+
ApiError = class extends Error {
|
|
394
|
+
code;
|
|
395
|
+
status;
|
|
396
|
+
constructor(code, message, status2) {
|
|
397
|
+
super(message);
|
|
398
|
+
this.code = code;
|
|
399
|
+
this.status = status2;
|
|
400
|
+
}
|
|
401
|
+
};
|
|
402
|
+
aiModels = () => request("/v1/ai/models");
|
|
403
|
+
getApp = (idOrSlug) => request(`/v1/apps/${encodeURIComponent(idOrSlug)}`);
|
|
404
|
+
}
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
// src/output.ts
|
|
408
|
+
function terminalSafeText(value) {
|
|
409
|
+
return Array.from(value, (character) => {
|
|
410
|
+
const code = character.codePointAt(0) ?? 0;
|
|
411
|
+
if (code >= 32 && (code < 127 || code > 159)) return character;
|
|
412
|
+
if (code === 9) return "\\t";
|
|
413
|
+
if (code === 10) return "\\n";
|
|
414
|
+
if (code === 13) return "\\r";
|
|
415
|
+
return `\\u{${code.toString(16).padStart(4, "0")}}`;
|
|
416
|
+
}).join("");
|
|
417
|
+
}
|
|
418
|
+
function terminalSafeJson(payload, space) {
|
|
419
|
+
return Array.from(JSON.stringify(payload, null, space), (character) => {
|
|
420
|
+
const code = character.codePointAt(0) ?? 0;
|
|
421
|
+
return code >= 127 && code <= 159 ? `\\u${code.toString(16).padStart(4, "0")}` : character;
|
|
422
|
+
}).join("");
|
|
423
|
+
}
|
|
424
|
+
function say(line = "") {
|
|
425
|
+
if (json) console.error(line);
|
|
426
|
+
else console.log(line);
|
|
427
|
+
}
|
|
428
|
+
function note(line) {
|
|
429
|
+
console.error(line);
|
|
430
|
+
}
|
|
431
|
+
function createProgress() {
|
|
432
|
+
let active = false;
|
|
433
|
+
let lastPhase = null;
|
|
434
|
+
let frame = 0;
|
|
435
|
+
const frames = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
436
|
+
const interactive = process.stderr.isTTY === true && !json;
|
|
437
|
+
return {
|
|
438
|
+
update(phase, line) {
|
|
439
|
+
if (interactive) {
|
|
440
|
+
process.stderr.write(`\r\x1B[2K${frames[frame++ % frames.length]} ${line}`);
|
|
441
|
+
active = true;
|
|
442
|
+
} else if (phase !== lastPhase) {
|
|
443
|
+
console.error(json ? terminalSafeJson({ type: "progress", phase, message: line }) : line);
|
|
444
|
+
}
|
|
445
|
+
lastPhase = phase;
|
|
446
|
+
},
|
|
447
|
+
clear() {
|
|
448
|
+
if (active) process.stderr.write("\r\x1B[2K");
|
|
449
|
+
active = false;
|
|
450
|
+
}
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
function result(payload) {
|
|
454
|
+
if (json) console.log(terminalSafeJson({ schema_version: 1, ok: true, ...payload }, 2));
|
|
455
|
+
}
|
|
456
|
+
function failure(code, message, extra = {}) {
|
|
457
|
+
if (json)
|
|
458
|
+
console.log(
|
|
459
|
+
terminalSafeJson({ schema_version: 1, ok: false, error: { code, message, ...extra } }, 2)
|
|
460
|
+
);
|
|
461
|
+
else {
|
|
462
|
+
console.error(`${red(terminalSafeText(code))} ${terminalSafeText(message)}`);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
var json, setJsonMode, isJsonMode, bold, dim, red, green;
|
|
466
|
+
var init_output = __esm({
|
|
467
|
+
"src/output.ts"() {
|
|
468
|
+
"use strict";
|
|
469
|
+
json = false;
|
|
470
|
+
setJsonMode = (on) => {
|
|
471
|
+
json = on;
|
|
472
|
+
};
|
|
473
|
+
isJsonMode = () => json;
|
|
474
|
+
bold = (s) => `\x1B[1m${s}\x1B[22m`;
|
|
475
|
+
dim = (s) => `\x1B[2m${s}\x1B[22m`;
|
|
476
|
+
red = (s) => `\x1B[31m${s}\x1B[39m`;
|
|
477
|
+
green = (s) => `\x1B[32m${s}\x1B[39m`;
|
|
478
|
+
}
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
// ../../node_modules/.pnpm/ignore@7.0.8/node_modules/ignore/index.js
|
|
482
|
+
var require_ignore = __commonJS({
|
|
483
|
+
"../../node_modules/.pnpm/ignore@7.0.8/node_modules/ignore/index.js"(exports, module) {
|
|
484
|
+
function makeArray(subject) {
|
|
485
|
+
return Array.isArray(subject) ? subject : [subject];
|
|
486
|
+
}
|
|
487
|
+
var UNDEFINED = void 0;
|
|
488
|
+
var EMPTY = "";
|
|
489
|
+
var SPACE = " ";
|
|
490
|
+
var ESCAPE = "\\";
|
|
491
|
+
var REGEX_LITERAL_SPECIAL = /[.*+?()[\]{}^$|\\/]/;
|
|
492
|
+
var REGEX_TEST_BLANK_LINE = /^ +$/;
|
|
493
|
+
var REGEX_INVALID_TRAILING_BACKSLASH = /(?:[^\\]|^)\\$/;
|
|
494
|
+
var REGEX_REPLACE_LEADING_EXCAPED_EXCLAMATION = /^\\!/;
|
|
495
|
+
var REGEX_REPLACE_LEADING_EXCAPED_HASH = /^\\#/;
|
|
496
|
+
var REGEX_SPLITALL_CRLF = /\r?\n/g;
|
|
497
|
+
var DOUBLE_SLASH = "//";
|
|
498
|
+
var SLASH_CODE = 47;
|
|
499
|
+
var DOT_CODE = 46;
|
|
500
|
+
var SLASH = "/";
|
|
501
|
+
var TMP_KEY_IGNORE = "node-ignore";
|
|
502
|
+
if (typeof Symbol !== "undefined") {
|
|
503
|
+
TMP_KEY_IGNORE = /* @__PURE__ */ Symbol.for("node-ignore");
|
|
504
|
+
}
|
|
505
|
+
var KEY_IGNORE = TMP_KEY_IGNORE;
|
|
506
|
+
var define = (object, key, value) => {
|
|
507
|
+
Object.defineProperty(object, key, { value });
|
|
508
|
+
return value;
|
|
509
|
+
};
|
|
510
|
+
var RETURN_FALSE = () => false;
|
|
511
|
+
var cleanRangeBackSlash = (slashes) => {
|
|
512
|
+
const { length } = slashes;
|
|
513
|
+
return slashes.slice(0, length - length % 2);
|
|
514
|
+
};
|
|
515
|
+
var POSIX_CLASSES = {
|
|
516
|
+
alnum: "0-9A-Za-z",
|
|
517
|
+
alpha: "A-Za-z",
|
|
518
|
+
blank: " \\t",
|
|
519
|
+
cntrl: "\\x00-\\x1f\\x7f",
|
|
520
|
+
digit: "0-9",
|
|
521
|
+
graph: "!-.0-~",
|
|
522
|
+
lower: "a-z",
|
|
523
|
+
print: " -.0-~",
|
|
524
|
+
punct: "!-.:-@\\[-`{-~",
|
|
525
|
+
// git's `sane-ctype.h` classifies \v and \f as control, not space,
|
|
526
|
+
// unlike C's `isspace`
|
|
527
|
+
space: " \\t\\n\\r",
|
|
528
|
+
upper: "A-Z",
|
|
529
|
+
xdigit: "0-9A-Fa-f"
|
|
530
|
+
};
|
|
531
|
+
var CLASS_MEMBERS_TO_ESCAPE = "\\]^-[";
|
|
532
|
+
var escapeMember = (char) => CLASS_MEMBERS_TO_ESCAPE.indexOf(char) < 0 ? char : ESCAPE + char;
|
|
533
|
+
var NON_SLASH = "(?!\\/)";
|
|
534
|
+
var classSource = (negated, body) => {
|
|
535
|
+
if (negated) {
|
|
536
|
+
return `[^\\/${body}]`;
|
|
537
|
+
}
|
|
538
|
+
const source = `[${body}]`;
|
|
539
|
+
return new RegExp(source).test("/") ? NON_SLASH + source : source;
|
|
540
|
+
};
|
|
541
|
+
var scanBracket = (pattern, start) => {
|
|
542
|
+
const { length } = pattern;
|
|
543
|
+
let index = start + 1;
|
|
544
|
+
let negated = EMPTY;
|
|
545
|
+
const lead = pattern[index];
|
|
546
|
+
if (lead === "!" || lead === "^") {
|
|
547
|
+
negated = "^";
|
|
548
|
+
index++;
|
|
549
|
+
}
|
|
550
|
+
let body = EMPTY;
|
|
551
|
+
let prev = EMPTY;
|
|
552
|
+
for (; ; ) {
|
|
553
|
+
const char = pattern[index];
|
|
554
|
+
if (char === UNDEFINED) {
|
|
555
|
+
return null;
|
|
556
|
+
}
|
|
557
|
+
if (char === ESCAPE) {
|
|
558
|
+
const escaped = pattern[index + 1];
|
|
559
|
+
if (escaped === UNDEFINED) {
|
|
560
|
+
return null;
|
|
561
|
+
}
|
|
562
|
+
body += escapeMember(escaped);
|
|
563
|
+
prev = escaped;
|
|
564
|
+
index++;
|
|
565
|
+
} else if (char === "-" && prev && index + 1 < length && pattern[index + 1] !== "]") {
|
|
566
|
+
index++;
|
|
567
|
+
let to = pattern[index];
|
|
568
|
+
if (to === ESCAPE) {
|
|
569
|
+
to = pattern[index += 1];
|
|
570
|
+
}
|
|
571
|
+
if (prev <= to) {
|
|
572
|
+
body += `-${escapeMember(to)}`;
|
|
573
|
+
}
|
|
574
|
+
prev = EMPTY;
|
|
575
|
+
} else if (char === "[" && pattern[index + 1] === ":") {
|
|
576
|
+
const nameStart = index + 2;
|
|
577
|
+
let end = nameStart;
|
|
578
|
+
while (end < length && pattern[end] !== "]") {
|
|
579
|
+
end++;
|
|
580
|
+
}
|
|
581
|
+
if (end === length) {
|
|
582
|
+
return null;
|
|
583
|
+
}
|
|
584
|
+
if (end > nameStart && pattern[end - 1] === ":") {
|
|
585
|
+
const expanded = POSIX_CLASSES[pattern.slice(nameStart, end - 1)];
|
|
586
|
+
if (expanded === UNDEFINED) {
|
|
587
|
+
return null;
|
|
588
|
+
}
|
|
589
|
+
body += expanded;
|
|
590
|
+
prev = EMPTY;
|
|
591
|
+
index = end;
|
|
592
|
+
} else {
|
|
593
|
+
body += escapeMember("[");
|
|
594
|
+
prev = "[";
|
|
595
|
+
index = nameStart - 2;
|
|
596
|
+
}
|
|
597
|
+
} else {
|
|
598
|
+
body += escapeMember(char);
|
|
599
|
+
prev = char;
|
|
600
|
+
}
|
|
601
|
+
index++;
|
|
602
|
+
if (pattern[index] === "]") {
|
|
603
|
+
return {
|
|
604
|
+
end: index,
|
|
605
|
+
source: classSource(negated, body)
|
|
606
|
+
};
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
};
|
|
610
|
+
var NEVER_MATCH = "[]";
|
|
611
|
+
var PLACEHOLDER = "\0";
|
|
612
|
+
var REGEX_RESTORE_PLACEHOLDER = new RegExp(
|
|
613
|
+
`${PLACEHOLDER}(\\d+)${PLACEHOLDER}`,
|
|
614
|
+
"g"
|
|
615
|
+
);
|
|
616
|
+
var TRAILING_WILDCARD = "\uE000";
|
|
617
|
+
var extractBrackets = (pattern) => {
|
|
618
|
+
const sources = [];
|
|
619
|
+
const hold = (source) => `${PLACEHOLDER}${sources.push(source) - 1}${PLACEHOLDER}`;
|
|
620
|
+
const { length } = pattern;
|
|
621
|
+
let out = EMPTY;
|
|
622
|
+
let index = 0;
|
|
623
|
+
while (index < length) {
|
|
624
|
+
const char = pattern[index];
|
|
625
|
+
if (char === ESCAPE) {
|
|
626
|
+
const escaped = pattern[index + 1];
|
|
627
|
+
if (escaped === "*" || escaped === "[" || escaped === SPACE || escaped === ESCAPE) {
|
|
628
|
+
out += pattern.slice(index, index + 2);
|
|
629
|
+
} else {
|
|
630
|
+
out += hold(
|
|
631
|
+
REGEX_LITERAL_SPECIAL.test(escaped) ? ESCAPE + escaped : escaped
|
|
632
|
+
);
|
|
633
|
+
}
|
|
634
|
+
index += 2;
|
|
635
|
+
} else if (char === PLACEHOLDER) {
|
|
636
|
+
out += hold(`[${PLACEHOLDER}]`);
|
|
637
|
+
index++;
|
|
638
|
+
} else if (char === "[") {
|
|
639
|
+
const scanned = scanBracket(pattern, index);
|
|
640
|
+
if (scanned === null) {
|
|
641
|
+
out += hold(NEVER_MATCH);
|
|
642
|
+
index = length;
|
|
643
|
+
} else {
|
|
644
|
+
out += hold(scanned.source);
|
|
645
|
+
index = scanned.end + 1;
|
|
646
|
+
}
|
|
647
|
+
} else {
|
|
648
|
+
out += char;
|
|
649
|
+
index++;
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
return {
|
|
653
|
+
source: out,
|
|
654
|
+
sources
|
|
655
|
+
};
|
|
656
|
+
};
|
|
657
|
+
var DIRECT = null;
|
|
658
|
+
var REGEX_INNER_SLASH = /\/(?!$)/;
|
|
659
|
+
var REPLACERS = [
|
|
660
|
+
[
|
|
661
|
+
// Remove BOM
|
|
662
|
+
// TODO:
|
|
663
|
+
// Other similar zero-width characters?
|
|
664
|
+
/^\uFEFF/,
|
|
665
|
+
() => EMPTY,
|
|
666
|
+
"\uFEFF"
|
|
667
|
+
],
|
|
668
|
+
[
|
|
669
|
+
// A trailing line terminator, left on when a whole file's contents are
|
|
670
|
+
// added as one pattern rather than split into lines. git never sees one
|
|
671
|
+
// -- it reads a `.gitignore` line by line -- so it is not part of the
|
|
672
|
+
// pattern and is dropped here, apart from the trailing-space trimming,
|
|
673
|
+
// which follows git in touching spaces and nothing else.
|
|
674
|
+
/[\r\n]+$/,
|
|
675
|
+
() => EMPTY
|
|
676
|
+
],
|
|
677
|
+
// > Trailing spaces are ignored unless they are quoted with backslash ("\")
|
|
678
|
+
[
|
|
679
|
+
// Only spaces, never tabs or other whitespace: git trims a trailing run
|
|
680
|
+
// of `' '` and nothing else (dir.c, `trim_trailing_spaces`, a single
|
|
681
|
+
// `case ' '`), so a pattern ending in a tab keeps it as a literal.
|
|
682
|
+
// (a\ ) -> (a )
|
|
683
|
+
// (a ) -> (a)
|
|
684
|
+
// (a ) -> (a)
|
|
685
|
+
// (a \ ) -> (a )
|
|
686
|
+
/((?:\\\\)*?)(\\? +)$/,
|
|
687
|
+
(_, m1, m2) => m1 + (m2.indexOf("\\") === 0 ? SPACE : EMPTY)
|
|
688
|
+
],
|
|
689
|
+
// Replace (\ ) with ' '
|
|
690
|
+
// Only a space: an escaped tab or other whitespace is already a literal by
|
|
691
|
+
// the time it reaches here, and a bare tab must be left as one, not turned
|
|
692
|
+
// into a space.
|
|
693
|
+
// (\ ) -> ' '
|
|
694
|
+
// (\\ ) -> '\\ '
|
|
695
|
+
// (\\\ ) -> '\\ '
|
|
696
|
+
[
|
|
697
|
+
/(\\+?) /g,
|
|
698
|
+
(_, m1) => {
|
|
699
|
+
const { length } = m1;
|
|
700
|
+
return m1.slice(0, length - length % 2) + SPACE;
|
|
701
|
+
}
|
|
702
|
+
],
|
|
703
|
+
// Escape metacharacters
|
|
704
|
+
// which is written down by users but means special for regular expressions.
|
|
705
|
+
// > There are 12 characters with special meanings:
|
|
706
|
+
// > - the backslash \,
|
|
707
|
+
// > - the caret ^,
|
|
708
|
+
// > - the dollar sign $,
|
|
709
|
+
// > - the period or dot .,
|
|
710
|
+
// > - the vertical bar or pipe symbol |,
|
|
711
|
+
// > - the question mark ?,
|
|
712
|
+
// > - the asterisk or star *,
|
|
713
|
+
// > - the plus sign +,
|
|
714
|
+
// > - the opening parenthesis (,
|
|
715
|
+
// > - the closing parenthesis ),
|
|
716
|
+
// > - and the opening square bracket [,
|
|
717
|
+
// > - the opening curly brace {,
|
|
718
|
+
// > These special characters are often called "metacharacters".
|
|
719
|
+
[
|
|
720
|
+
/[\\$.|*+(){^]/g,
|
|
721
|
+
(match) => `\\${match}`
|
|
722
|
+
],
|
|
723
|
+
[
|
|
724
|
+
// > a question mark (?) matches a single character
|
|
725
|
+
/(?!\\)\?/g,
|
|
726
|
+
() => "[^/]",
|
|
727
|
+
"?"
|
|
728
|
+
],
|
|
729
|
+
// leading slash
|
|
730
|
+
[
|
|
731
|
+
// > A leading slash matches the beginning of the pathname.
|
|
732
|
+
// > For example, "/*.c" matches "cat-file.c" but not "mozilla-sha1/sha1.c".
|
|
733
|
+
// A leading slash matches the beginning of the pathname
|
|
734
|
+
/^\//,
|
|
735
|
+
() => "^",
|
|
736
|
+
SLASH
|
|
737
|
+
],
|
|
738
|
+
// replace special metacharacter slash after the leading slash
|
|
739
|
+
[
|
|
740
|
+
/\//g,
|
|
741
|
+
() => "\\/",
|
|
742
|
+
SLASH
|
|
743
|
+
],
|
|
744
|
+
[
|
|
745
|
+
// > A leading "**" followed by a slash means match in all directories.
|
|
746
|
+
// > For example, "**/foo" matches file or directory "foo" anywhere,
|
|
747
|
+
// > the same as pattern "foo".
|
|
748
|
+
// > "**/foo/bar" matches file or directory "bar" anywhere that is directly
|
|
749
|
+
// > under directory "foo".
|
|
750
|
+
// Notice that the '*'s have been replaced as '\\*'
|
|
751
|
+
/^\^*(?:\\\*\\\*\\\/)+/,
|
|
752
|
+
// '**/foo' <-> 'foo'
|
|
753
|
+
() => "^(?:.*\\/)?",
|
|
754
|
+
"*"
|
|
755
|
+
],
|
|
756
|
+
// starting
|
|
757
|
+
[
|
|
758
|
+
// there will be no leading '/'
|
|
759
|
+
// (which has been replaced by section "leading slash")
|
|
760
|
+
// If starts with '**', adding a '^' to the regular expression also works
|
|
761
|
+
DIRECT,
|
|
762
|
+
(source, pattern) => {
|
|
763
|
+
if (!source || source[0] === "^") {
|
|
764
|
+
return source;
|
|
765
|
+
}
|
|
766
|
+
const anchor = !REGEX_INNER_SLASH.test(pattern) ? "(?:^|\\/)" : "^";
|
|
767
|
+
return anchor + source;
|
|
768
|
+
}
|
|
769
|
+
],
|
|
770
|
+
// two globstars
|
|
771
|
+
[
|
|
772
|
+
// Use lookahead assertions so that we could match more than one `'/**'`
|
|
773
|
+
/\\\/\\\*\\\*(?=\\\/|$)/g,
|
|
774
|
+
// Zero, one or several directories
|
|
775
|
+
// should not use '*', or it will be replaced by the next replacer
|
|
776
|
+
// Check if it is not the last `'/**'`
|
|
777
|
+
(_, index, str) => index + 6 < str.length ? str.slice(index + 6) === "\\/" ? "(?:\\/[^\\/]+)+" : "(?:\\/[^\\/]+)*" : "\\/.+",
|
|
778
|
+
"*"
|
|
779
|
+
],
|
|
780
|
+
// normal intermediate wildcards
|
|
781
|
+
[
|
|
782
|
+
// Never replace escaped '*'
|
|
783
|
+
// ignore rule '\*' will match the path '*'
|
|
784
|
+
// 'abc.*/' -> go
|
|
785
|
+
// 'abc.*' -> skip this rule,
|
|
786
|
+
// coz trailing single wildcard will be handed by [trailing wildcard]
|
|
787
|
+
/(^|[^\\]+)(\\\*)+(?=.+)/g,
|
|
788
|
+
// '*.js' matches '.js'
|
|
789
|
+
// '*.js' doesn't match 'abc'
|
|
790
|
+
(_, p1, p2) => {
|
|
791
|
+
const unescaped = p2.replace(/\\\*/g, "[^\\/]*");
|
|
792
|
+
return p1 + unescaped;
|
|
793
|
+
},
|
|
794
|
+
"*"
|
|
795
|
+
],
|
|
796
|
+
// trailing wildcard, held apart from a literal star
|
|
797
|
+
[
|
|
798
|
+
// The step above leaves a trailing `*` alone, so a single `\*` is all that
|
|
799
|
+
// can be left at the end here. Whether it is a wildcard or a literal
|
|
800
|
+
// turns on the backslashes the user put in front of it: the escaper has
|
|
801
|
+
// since doubled every one, so what stands here is those `2N` doubled
|
|
802
|
+
// backslashes and then the star's own escape. An even number of the
|
|
803
|
+
// original `N` leaves the star unescaped -- a wildcard -- and an odd
|
|
804
|
+
// number escapes it -- a literal. This runs while the two are still
|
|
805
|
+
// distinct, before the unescape steps below collapse the literal onto
|
|
806
|
+
// the very `\*` a wildcard leaves behind.
|
|
807
|
+
/(^|[^\\])((?:\\\\)*)\\\*$/,
|
|
808
|
+
(match, p1, p2) => (
|
|
809
|
+
// `p2` holds the doubled user backslashes; half of them is `N`.
|
|
810
|
+
p2.length / 2 % 2 === 0 ? p1 + p2 + TRAILING_WILDCARD : match
|
|
811
|
+
),
|
|
812
|
+
"*"
|
|
813
|
+
],
|
|
814
|
+
[
|
|
815
|
+
// unescape, revert step 3 except for back slash
|
|
816
|
+
// For example, if a user escape a '\\*',
|
|
817
|
+
// after step 3, the result will be '\\\\\\*'
|
|
818
|
+
/\\\\\\(?=[$.|*+(){^])/g,
|
|
819
|
+
() => ESCAPE,
|
|
820
|
+
ESCAPE + ESCAPE
|
|
821
|
+
],
|
|
822
|
+
[
|
|
823
|
+
// '\\\\' -> '\\'
|
|
824
|
+
/\\\\/g,
|
|
825
|
+
() => ESCAPE,
|
|
826
|
+
ESCAPE + ESCAPE
|
|
827
|
+
],
|
|
828
|
+
[
|
|
829
|
+
// Every real bracket expression -- POSIX classes included -- has already
|
|
830
|
+
// been held aside by `extractBrackets`, so the only `[` left in the
|
|
831
|
+
// pattern is an escaped, literal one.
|
|
832
|
+
// `\` is escaped by step 3
|
|
833
|
+
/\\\[([^\]/]*?)(\\*)($|\])/g,
|
|
834
|
+
// '\\[bar]' -> '\\\\[bar\\]'
|
|
835
|
+
(match, range, endEscape, close) => `\\[${range}${cleanRangeBackSlash(endEscape)}${close}`,
|
|
836
|
+
"["
|
|
837
|
+
],
|
|
838
|
+
// ending
|
|
839
|
+
[
|
|
840
|
+
// 'js' will not match 'js.'
|
|
841
|
+
// 'ab' will not match 'abc'
|
|
842
|
+
DIRECT,
|
|
843
|
+
// WTF!
|
|
844
|
+
// https://git-scm.com/docs/gitignore
|
|
845
|
+
// changes in [2.22.1](https://git-scm.com/docs/gitignore/2.22.1)
|
|
846
|
+
// which re-fixes #24, #38
|
|
847
|
+
// > If there is a separator at the end of the pattern then the pattern
|
|
848
|
+
// > will only match directories, otherwise the pattern can match both
|
|
849
|
+
// > files and directories.
|
|
850
|
+
// 'js*' will not match 'a.js'
|
|
851
|
+
// 'js/' will not match 'a.js'
|
|
852
|
+
// 'js' will match 'a.js' and 'a.js/'
|
|
853
|
+
(source) => {
|
|
854
|
+
const last = source[source.length - 1];
|
|
855
|
+
if (!last || last === TRAILING_WILDCARD) {
|
|
856
|
+
return source;
|
|
857
|
+
}
|
|
858
|
+
return last === SLASH ? `${source}$` : `${source}(?=$|\\/$)`;
|
|
859
|
+
}
|
|
860
|
+
]
|
|
861
|
+
];
|
|
862
|
+
var REGEX_REPLACE_TRAILING_WILDCARD = /(^|\\\/)?\uE000$/;
|
|
863
|
+
var MODE_IGNORE = "regex";
|
|
864
|
+
var MODE_CHECK_IGNORE = "checkRegex";
|
|
865
|
+
var UNDERSCORE = "_";
|
|
866
|
+
var TRAILING_WILD_CARD_REPLACERS = {
|
|
867
|
+
[MODE_IGNORE](_, p1) {
|
|
868
|
+
const prefix = p1 ? `${p1}[^/]+` : "[^/]*";
|
|
869
|
+
return `${prefix}(?=$|\\/$)`;
|
|
870
|
+
},
|
|
871
|
+
[MODE_CHECK_IGNORE](_, p1) {
|
|
872
|
+
const prefix = p1 ? `${p1}[^/]*` : "[^/]*";
|
|
873
|
+
return `${prefix}(?=$|\\/$)`;
|
|
874
|
+
}
|
|
875
|
+
};
|
|
876
|
+
var WILDCARD = "[^\\/]*";
|
|
877
|
+
var pinWildcards = (source) => {
|
|
878
|
+
if (source.indexOf(WILDCARD) < 0) {
|
|
879
|
+
return source;
|
|
880
|
+
}
|
|
881
|
+
const tokens = [];
|
|
882
|
+
const { length } = source;
|
|
883
|
+
let index = 0;
|
|
884
|
+
while (index < length) {
|
|
885
|
+
const char = source[index];
|
|
886
|
+
if (source.startsWith(WILDCARD, index)) {
|
|
887
|
+
tokens.push({ wildcard: true });
|
|
888
|
+
index += WILDCARD.length;
|
|
889
|
+
} else if (char === "[") {
|
|
890
|
+
let end = index + 1;
|
|
891
|
+
if (source[end] === "^") {
|
|
892
|
+
end++;
|
|
893
|
+
}
|
|
894
|
+
if (source[end] === "]") {
|
|
895
|
+
end++;
|
|
896
|
+
}
|
|
897
|
+
while (end < length && source[end] !== "]") {
|
|
898
|
+
end += source[end] === ESCAPE ? 2 : 1;
|
|
899
|
+
}
|
|
900
|
+
end++;
|
|
901
|
+
tokens.push({ single: source.slice(index, end) });
|
|
902
|
+
index = end;
|
|
903
|
+
} else if (char === ESCAPE) {
|
|
904
|
+
tokens.push({ single: source.slice(index, index + 2) });
|
|
905
|
+
index += 2;
|
|
906
|
+
} else if (char === "(") {
|
|
907
|
+
let depth = 0;
|
|
908
|
+
let end = index;
|
|
909
|
+
do {
|
|
910
|
+
if (source[end] === ESCAPE) {
|
|
911
|
+
end++;
|
|
912
|
+
} else if (source[end] === "(") {
|
|
913
|
+
depth++;
|
|
914
|
+
} else if (source[end] === ")") {
|
|
915
|
+
depth--;
|
|
916
|
+
}
|
|
917
|
+
end++;
|
|
918
|
+
} while (end < length && depth > 0);
|
|
919
|
+
if ("*+?".indexOf(source[end]) >= 0) {
|
|
920
|
+
end++;
|
|
921
|
+
}
|
|
922
|
+
tokens.push({ boundary: source.slice(index, end) });
|
|
923
|
+
index = end;
|
|
924
|
+
} else if (char === "^" || char === "$") {
|
|
925
|
+
tokens.push({ boundary: char });
|
|
926
|
+
index++;
|
|
927
|
+
} else {
|
|
928
|
+
tokens.push({ single: char });
|
|
929
|
+
index++;
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
let out = EMPTY;
|
|
933
|
+
let run2 = [];
|
|
934
|
+
const flush = () => {
|
|
935
|
+
let lastWildcard;
|
|
936
|
+
run2.forEach((token, at) => {
|
|
937
|
+
if (token.wildcard) {
|
|
938
|
+
lastWildcard = at;
|
|
939
|
+
}
|
|
940
|
+
});
|
|
941
|
+
run2.forEach((token, at) => {
|
|
942
|
+
if (!token.wildcard) {
|
|
943
|
+
out += token.single;
|
|
944
|
+
return;
|
|
945
|
+
}
|
|
946
|
+
out += at === lastWildcard ? WILDCARD : `(?:(?!${run2[at + 1].single})[^\\/])*`;
|
|
947
|
+
});
|
|
948
|
+
run2 = [];
|
|
949
|
+
};
|
|
950
|
+
tokens.forEach((token) => {
|
|
951
|
+
if (token.boundary === void 0) {
|
|
952
|
+
run2.push(token);
|
|
953
|
+
return;
|
|
954
|
+
}
|
|
955
|
+
flush();
|
|
956
|
+
out += token.boundary;
|
|
957
|
+
});
|
|
958
|
+
flush();
|
|
959
|
+
return out;
|
|
960
|
+
};
|
|
961
|
+
var makeRegexPrefix = (pattern) => {
|
|
962
|
+
const { source, sources } = extractBrackets(pattern);
|
|
963
|
+
const replaced = REPLACERS.reduce(
|
|
964
|
+
// A pass whose matcher finds nothing hands back the very string it was
|
|
965
|
+
// given, so asking first costs a search and saves a rewrite. Ten of the
|
|
966
|
+
// fifteen passes never fire for a typical .gitignore line, and between
|
|
967
|
+
// them they were 45% of this chain.
|
|
968
|
+
(prev, [matcher, replacer, required]) => {
|
|
969
|
+
if (matcher === DIRECT) {
|
|
970
|
+
return replacer(prev, pattern);
|
|
971
|
+
}
|
|
972
|
+
if (required !== UNDEFINED && prev.indexOf(required) < 0) {
|
|
973
|
+
return prev;
|
|
974
|
+
}
|
|
975
|
+
return matcher.test(prev) ? prev.replace(matcher, replacer.bind(pattern)) : prev;
|
|
976
|
+
},
|
|
977
|
+
source
|
|
978
|
+
);
|
|
979
|
+
return sources.length ? replaced.replace(
|
|
980
|
+
REGEX_RESTORE_PLACEHOLDER,
|
|
981
|
+
(match, index) => sources[index]
|
|
982
|
+
) : replaced;
|
|
983
|
+
};
|
|
984
|
+
var matchesBasename = (body) => {
|
|
985
|
+
const index = body.indexOf(SLASH);
|
|
986
|
+
return index < 0 || index === body.length - 1;
|
|
987
|
+
};
|
|
988
|
+
var basenameOf = (path) => {
|
|
989
|
+
const end = path.length - 1;
|
|
990
|
+
const index = path.lastIndexOf(
|
|
991
|
+
SLASH,
|
|
992
|
+
path[end] === SLASH ? end - 1 : end
|
|
993
|
+
);
|
|
994
|
+
return index < 0 ? path : path.slice(index + 1);
|
|
995
|
+
};
|
|
996
|
+
var parentOf = (path) => {
|
|
997
|
+
if (path.charCodeAt(0) === SLASH_CODE || path.indexOf(DOUBLE_SLASH) >= 0) {
|
|
998
|
+
const slices = path.split(SLASH).filter(Boolean);
|
|
999
|
+
slices.pop();
|
|
1000
|
+
return slices.length ? slices.join(SLASH) + SLASH : EMPTY;
|
|
1001
|
+
}
|
|
1002
|
+
const end = path.length - 1;
|
|
1003
|
+
const cut = path.lastIndexOf(
|
|
1004
|
+
SLASH,
|
|
1005
|
+
path.charCodeAt(end) === SLASH_CODE ? end - 1 : end
|
|
1006
|
+
);
|
|
1007
|
+
return cut < 0 ? EMPTY : path.slice(0, cut + 1);
|
|
1008
|
+
};
|
|
1009
|
+
var isString = (subject) => typeof subject === "string";
|
|
1010
|
+
var checkPattern = (pattern) => pattern && isString(pattern) && !REGEX_TEST_BLANK_LINE.test(pattern) && !REGEX_INVALID_TRAILING_BACKSLASH.test(pattern) && pattern.indexOf("#") !== 0;
|
|
1011
|
+
var splitPattern = (pattern) => pattern.split(REGEX_SPLITALL_CRLF).filter(Boolean);
|
|
1012
|
+
var IgnoreRule = class {
|
|
1013
|
+
constructor(pattern, mark, body, ignoreCase, negative, prefix) {
|
|
1014
|
+
this.pattern = pattern;
|
|
1015
|
+
this.mark = mark;
|
|
1016
|
+
this.negative = negative;
|
|
1017
|
+
define(this, "body", body);
|
|
1018
|
+
define(this, "ignoreCase", ignoreCase);
|
|
1019
|
+
define(this, "regexPrefix", prefix);
|
|
1020
|
+
}
|
|
1021
|
+
// Worked out on first use and kept behind an own property, the way `regex`
|
|
1022
|
+
// caches itself in `_regex`. Deciding it in the constructor instead would
|
|
1023
|
+
// add a fourth `defineProperty` to every rule ever built, which cost 4% of
|
|
1024
|
+
// every compile -- including the compiles of rules that are never matched
|
|
1025
|
+
// against anything.
|
|
1026
|
+
get _basenameOnly() {
|
|
1027
|
+
return define(this, "_basenameOnly", matchesBasename(this.body));
|
|
1028
|
+
}
|
|
1029
|
+
get regex() {
|
|
1030
|
+
const key = UNDERSCORE + MODE_IGNORE;
|
|
1031
|
+
if (this[key]) {
|
|
1032
|
+
return this[key];
|
|
1033
|
+
}
|
|
1034
|
+
return this._make(MODE_IGNORE, key);
|
|
1035
|
+
}
|
|
1036
|
+
get checkRegex() {
|
|
1037
|
+
const key = UNDERSCORE + MODE_CHECK_IGNORE;
|
|
1038
|
+
if (this[key]) {
|
|
1039
|
+
return this[key];
|
|
1040
|
+
}
|
|
1041
|
+
return this._make(MODE_CHECK_IGNORE, key);
|
|
1042
|
+
}
|
|
1043
|
+
_make(mode, key) {
|
|
1044
|
+
const str = pinWildcards(this.regexPrefix.replace(
|
|
1045
|
+
REGEX_REPLACE_TRAILING_WILDCARD,
|
|
1046
|
+
// It does not need to bind pattern
|
|
1047
|
+
TRAILING_WILD_CARD_REPLACERS[mode]
|
|
1048
|
+
));
|
|
1049
|
+
const regex = this.ignoreCase ? new RegExp(str, "i") : new RegExp(str);
|
|
1050
|
+
return define(this, key, regex);
|
|
1051
|
+
}
|
|
1052
|
+
};
|
|
1053
|
+
var createRule = ({
|
|
1054
|
+
pattern,
|
|
1055
|
+
mark
|
|
1056
|
+
}, ignoreCase) => {
|
|
1057
|
+
let negative = false;
|
|
1058
|
+
let body = pattern;
|
|
1059
|
+
if (body.indexOf("!") === 0) {
|
|
1060
|
+
negative = true;
|
|
1061
|
+
body = body.substr(1);
|
|
1062
|
+
}
|
|
1063
|
+
body = body.replace(REGEX_REPLACE_LEADING_EXCAPED_EXCLAMATION, "!").replace(REGEX_REPLACE_LEADING_EXCAPED_HASH, "#");
|
|
1064
|
+
const regexPrefix = makeRegexPrefix(body);
|
|
1065
|
+
return new IgnoreRule(
|
|
1066
|
+
pattern,
|
|
1067
|
+
mark,
|
|
1068
|
+
body,
|
|
1069
|
+
ignoreCase,
|
|
1070
|
+
negative,
|
|
1071
|
+
regexPrefix
|
|
1072
|
+
);
|
|
1073
|
+
};
|
|
1074
|
+
var RuleManager = class {
|
|
1075
|
+
constructor(ignoreCase) {
|
|
1076
|
+
this._ignoreCase = ignoreCase;
|
|
1077
|
+
this._rules = [];
|
|
1078
|
+
this._basenameCount = 0;
|
|
1079
|
+
}
|
|
1080
|
+
_add(pattern) {
|
|
1081
|
+
if (pattern && pattern[KEY_IGNORE]) {
|
|
1082
|
+
this._rules = this._rules.concat(pattern._rules._rules);
|
|
1083
|
+
this._basenameCount += pattern._rules._basenameCount;
|
|
1084
|
+
this._added = true;
|
|
1085
|
+
return;
|
|
1086
|
+
}
|
|
1087
|
+
if (isString(pattern)) {
|
|
1088
|
+
pattern = {
|
|
1089
|
+
pattern
|
|
1090
|
+
};
|
|
1091
|
+
}
|
|
1092
|
+
if (checkPattern(pattern.pattern)) {
|
|
1093
|
+
const rule = createRule(pattern, this._ignoreCase);
|
|
1094
|
+
this._added = true;
|
|
1095
|
+
this._rules.push(rule);
|
|
1096
|
+
if (matchesBasename(rule.body)) {
|
|
1097
|
+
this._basenameCount++;
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
// @param {Array<string> | string | Ignore} pattern
|
|
1102
|
+
add(pattern) {
|
|
1103
|
+
this._added = false;
|
|
1104
|
+
makeArray(
|
|
1105
|
+
isString(pattern) ? splitPattern(pattern) : pattern
|
|
1106
|
+
).forEach(this._add, this);
|
|
1107
|
+
return this._added;
|
|
1108
|
+
}
|
|
1109
|
+
// Test one single path without recursively checking parent directories
|
|
1110
|
+
//
|
|
1111
|
+
// - checkUnignored `boolean` whether should check if the path is unignored,
|
|
1112
|
+
// setting `checkUnignored` to `false` could reduce additional
|
|
1113
|
+
// path matching.
|
|
1114
|
+
// - check `string` either `MODE_IGNORE` or `MODE_CHECK_IGNORE`
|
|
1115
|
+
// @returns {TestResult} true if a file is ignored
|
|
1116
|
+
test(path, checkUnignored, mode) {
|
|
1117
|
+
let ignored = false;
|
|
1118
|
+
let unignored = false;
|
|
1119
|
+
let matchedRule;
|
|
1120
|
+
const rules = this._rules;
|
|
1121
|
+
const { length } = rules;
|
|
1122
|
+
const shortcut = this._basenameCount * 2 >= length;
|
|
1123
|
+
const basename3 = shortcut ? basenameOf(path) : path;
|
|
1124
|
+
for (let index = 0; index < length; index++) {
|
|
1125
|
+
const rule = rules[index];
|
|
1126
|
+
const { negative } = rule;
|
|
1127
|
+
const skip = unignored === negative && ignored !== unignored || negative && !ignored && !unignored && !checkUnignored;
|
|
1128
|
+
if (!skip && rule[mode].test(
|
|
1129
|
+
shortcut && rule._basenameOnly ? basename3 : path
|
|
1130
|
+
)) {
|
|
1131
|
+
ignored = !negative;
|
|
1132
|
+
unignored = negative;
|
|
1133
|
+
matchedRule = negative ? UNDEFINED : rule;
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
const ret = {
|
|
1137
|
+
ignored,
|
|
1138
|
+
unignored
|
|
1139
|
+
};
|
|
1140
|
+
if (matchedRule) {
|
|
1141
|
+
ret.rule = matchedRule;
|
|
1142
|
+
}
|
|
1143
|
+
return ret;
|
|
1144
|
+
}
|
|
1145
|
+
};
|
|
1146
|
+
var throwError = (message, Ctor) => {
|
|
1147
|
+
throw new Ctor(message);
|
|
1148
|
+
};
|
|
1149
|
+
var checkPath = (path, originalPath, doThrow) => {
|
|
1150
|
+
if (!isString(path)) {
|
|
1151
|
+
return doThrow(
|
|
1152
|
+
`path must be a string, but got \`${originalPath}\``,
|
|
1153
|
+
TypeError
|
|
1154
|
+
);
|
|
1155
|
+
}
|
|
1156
|
+
if (!path) {
|
|
1157
|
+
return doThrow(`path must not be empty`, TypeError);
|
|
1158
|
+
}
|
|
1159
|
+
if (checkPath.isNotRelative(path)) {
|
|
1160
|
+
const r = "`path.relative()`d";
|
|
1161
|
+
return doThrow(
|
|
1162
|
+
`path should be a ${r} string, but got "${originalPath}"`,
|
|
1163
|
+
RangeError
|
|
1164
|
+
);
|
|
1165
|
+
}
|
|
1166
|
+
return true;
|
|
1167
|
+
};
|
|
1168
|
+
var isNotRelative = (path) => {
|
|
1169
|
+
const first = path.charCodeAt(0);
|
|
1170
|
+
if (first === SLASH_CODE) {
|
|
1171
|
+
return true;
|
|
1172
|
+
}
|
|
1173
|
+
if (first !== DOT_CODE) {
|
|
1174
|
+
return false;
|
|
1175
|
+
}
|
|
1176
|
+
if (path.length === 1) {
|
|
1177
|
+
return true;
|
|
1178
|
+
}
|
|
1179
|
+
const second = path.charCodeAt(1);
|
|
1180
|
+
if (second === SLASH_CODE) {
|
|
1181
|
+
return true;
|
|
1182
|
+
}
|
|
1183
|
+
if (second !== DOT_CODE) {
|
|
1184
|
+
return false;
|
|
1185
|
+
}
|
|
1186
|
+
return path.length === 2 || path.charCodeAt(2) === SLASH_CODE;
|
|
1187
|
+
};
|
|
1188
|
+
checkPath.isNotRelative = isNotRelative;
|
|
1189
|
+
checkPath.convert = (p) => p;
|
|
1190
|
+
var Ignore = class {
|
|
1191
|
+
constructor({
|
|
1192
|
+
ignorecase = true,
|
|
1193
|
+
ignoreCase = ignorecase,
|
|
1194
|
+
allowRelativePaths = false
|
|
1195
|
+
} = {}) {
|
|
1196
|
+
define(this, KEY_IGNORE, true);
|
|
1197
|
+
this._rules = new RuleManager(ignoreCase);
|
|
1198
|
+
this._strictPathCheck = !allowRelativePaths;
|
|
1199
|
+
this._initCache();
|
|
1200
|
+
}
|
|
1201
|
+
_initCache() {
|
|
1202
|
+
this._ignoreCache = /* @__PURE__ */ Object.create(null);
|
|
1203
|
+
this._testCache = /* @__PURE__ */ Object.create(null);
|
|
1204
|
+
}
|
|
1205
|
+
add(pattern) {
|
|
1206
|
+
if (this._rules.add(pattern)) {
|
|
1207
|
+
this._initCache();
|
|
1208
|
+
}
|
|
1209
|
+
return this;
|
|
1210
|
+
}
|
|
1211
|
+
// legacy
|
|
1212
|
+
addPattern(pattern) {
|
|
1213
|
+
return this.add(pattern);
|
|
1214
|
+
}
|
|
1215
|
+
// @returns {TestResult}
|
|
1216
|
+
_test(originalPath, cache, checkUnignored) {
|
|
1217
|
+
const path = originalPath && checkPath.convert(originalPath);
|
|
1218
|
+
checkPath(
|
|
1219
|
+
path,
|
|
1220
|
+
originalPath,
|
|
1221
|
+
this._strictPathCheck ? throwError : RETURN_FALSE
|
|
1222
|
+
);
|
|
1223
|
+
return this._t(path, cache, checkUnignored);
|
|
1224
|
+
}
|
|
1225
|
+
checkIgnore(path) {
|
|
1226
|
+
if (path.charCodeAt(path.length - 1) !== SLASH_CODE) {
|
|
1227
|
+
return this.test(path);
|
|
1228
|
+
}
|
|
1229
|
+
const parentPath = parentOf(path);
|
|
1230
|
+
if (parentPath) {
|
|
1231
|
+
const parent = this._t(parentPath, this._testCache, true);
|
|
1232
|
+
if (parent.ignored) {
|
|
1233
|
+
return parent;
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1236
|
+
return this._rules.test(path, false, MODE_CHECK_IGNORE);
|
|
1237
|
+
}
|
|
1238
|
+
_t(path, cache, checkUnignored) {
|
|
1239
|
+
if (path in cache) {
|
|
1240
|
+
return cache[path];
|
|
1241
|
+
}
|
|
1242
|
+
const parentPath = parentOf(path);
|
|
1243
|
+
const parent = parentPath ? this._t(parentPath, cache, checkUnignored) : UNDEFINED;
|
|
1244
|
+
return cache[path] = parent && parent.ignored ? parent : this._rules.test(path, checkUnignored, MODE_IGNORE);
|
|
1245
|
+
}
|
|
1246
|
+
ignores(path) {
|
|
1247
|
+
return this._test(path, this._ignoreCache, false).ignored;
|
|
1248
|
+
}
|
|
1249
|
+
createFilter() {
|
|
1250
|
+
return (path) => !this.ignores(path);
|
|
1251
|
+
}
|
|
1252
|
+
filter(paths) {
|
|
1253
|
+
return makeArray(paths).filter(this.createFilter());
|
|
1254
|
+
}
|
|
1255
|
+
// @returns {TestResult}
|
|
1256
|
+
test(path) {
|
|
1257
|
+
return this._test(path, this._testCache, true);
|
|
1258
|
+
}
|
|
1259
|
+
};
|
|
1260
|
+
var factory = (options) => new Ignore(options);
|
|
1261
|
+
var isPathValid = (path) => checkPath(path && checkPath.convert(path), path, RETURN_FALSE);
|
|
1262
|
+
var setupWindows = () => {
|
|
1263
|
+
const makePosix = (str) => /^\\\\\?\\/.test(str) || /["<>|\u0000-\u001F]+/u.test(str) ? str : str.replace(/\\/g, "/");
|
|
1264
|
+
checkPath.convert = makePosix;
|
|
1265
|
+
const REGEX_TEST_WINDOWS_PATH_ABSOLUTE = /^[a-z]:\//i;
|
|
1266
|
+
checkPath.isNotRelative = (path) => REGEX_TEST_WINDOWS_PATH_ABSOLUTE.test(path) || isNotRelative(path);
|
|
1267
|
+
};
|
|
1268
|
+
if (
|
|
1269
|
+
// Detect `process` so that it can run in browsers.
|
|
1270
|
+
typeof process !== "undefined" && process.platform === "win32"
|
|
1271
|
+
) {
|
|
1272
|
+
setupWindows();
|
|
1273
|
+
}
|
|
1274
|
+
module.exports = factory;
|
|
1275
|
+
factory.default = factory;
|
|
1276
|
+
module.exports.isPathValid = isPathValid;
|
|
1277
|
+
define(module.exports, /* @__PURE__ */ Symbol.for("setupWindows"), setupWindows);
|
|
1278
|
+
}
|
|
1279
|
+
});
|
|
1280
|
+
|
|
1281
|
+
// src/dev/bridge.ts
|
|
1282
|
+
function mapErrorCode(code, status2) {
|
|
1283
|
+
if (code === "invalid_input") return "INVALID_INPUT";
|
|
1284
|
+
if (code === "not_found") return "NOT_FOUND";
|
|
1285
|
+
if (code === "conflict") return "CONFLICT";
|
|
1286
|
+
if (code === "forbidden") return "UNDECLARED";
|
|
1287
|
+
if (status2 === 401) return "UNDECLARED";
|
|
1288
|
+
return "UNAVAILABLE";
|
|
1289
|
+
}
|
|
1290
|
+
function devRuntimeBody(invoke, appId) {
|
|
1291
|
+
if (invoke.service === "ai") {
|
|
1292
|
+
return {
|
|
1293
|
+
service: "ai",
|
|
1294
|
+
args: invoke.input.args ?? {},
|
|
1295
|
+
...appId ? { app_id: appId } : {}
|
|
1296
|
+
};
|
|
1297
|
+
}
|
|
1298
|
+
if (invoke.service === "capabilities") {
|
|
1299
|
+
return {
|
|
1300
|
+
service: "capabilities",
|
|
1301
|
+
capability: invoke.input.capability,
|
|
1302
|
+
args: invoke.input.args ?? {},
|
|
1303
|
+
...invoke.input.requestId ? { request_id: invoke.input.requestId } : {}
|
|
1304
|
+
};
|
|
1305
|
+
}
|
|
1306
|
+
return {
|
|
1307
|
+
service: "connections",
|
|
1308
|
+
connector: invoke.input.connector,
|
|
1309
|
+
...invoke.input.connection ? { connection: invoke.input.connection } : {},
|
|
1310
|
+
operation: invoke.input.operation,
|
|
1311
|
+
args: invoke.input.args ?? {},
|
|
1312
|
+
...appId ? { app_id: appId } : {}
|
|
1313
|
+
};
|
|
1314
|
+
}
|
|
1315
|
+
async function bridgeInvoke(dependencies, invoke) {
|
|
1316
|
+
const fetcher = dependencies.fetcher ?? fetch;
|
|
1317
|
+
let response;
|
|
1318
|
+
try {
|
|
1319
|
+
response = await fetcher(`${dependencies.apiUrl}/v1/dev/runtime`, {
|
|
1320
|
+
method: "POST",
|
|
1321
|
+
headers: {
|
|
1322
|
+
authorization: `Bearer ${dependencies.token}`,
|
|
1323
|
+
"content-type": "application/json"
|
|
1324
|
+
},
|
|
1325
|
+
body: JSON.stringify(devRuntimeBody(invoke, dependencies.appId)),
|
|
1326
|
+
signal: AbortSignal.timeout(6e4)
|
|
1327
|
+
});
|
|
1328
|
+
} catch {
|
|
1329
|
+
throw new BridgeError({
|
|
1330
|
+
code: "UNAVAILABLE",
|
|
1331
|
+
message: "Marina is unreachable \u2014 bridged calls need a network connection",
|
|
1332
|
+
retryable: true
|
|
1333
|
+
});
|
|
1334
|
+
}
|
|
1335
|
+
let body;
|
|
1336
|
+
try {
|
|
1337
|
+
body = await response.json();
|
|
1338
|
+
} catch {
|
|
1339
|
+
throw new BridgeError({
|
|
1340
|
+
code: "UNAVAILABLE",
|
|
1341
|
+
message: "Marina returned an invalid response",
|
|
1342
|
+
retryable: true
|
|
1343
|
+
});
|
|
1344
|
+
}
|
|
1345
|
+
if (!response.ok) {
|
|
1346
|
+
const error = body.error ?? {};
|
|
1347
|
+
throw new BridgeError({
|
|
1348
|
+
code: mapErrorCode(error.code ?? "", response.status),
|
|
1349
|
+
message: error.message ?? `bridged call failed (${String(response.status)})`,
|
|
1350
|
+
retryable: response.status >= 500
|
|
1351
|
+
});
|
|
1352
|
+
}
|
|
1353
|
+
return body.value;
|
|
1354
|
+
}
|
|
1355
|
+
var BridgeError;
|
|
1356
|
+
var init_bridge = __esm({
|
|
1357
|
+
"src/dev/bridge.ts"() {
|
|
1358
|
+
"use strict";
|
|
1359
|
+
BridgeError = class extends Error {
|
|
1360
|
+
payload;
|
|
1361
|
+
constructor(payload) {
|
|
1362
|
+
super(payload.message);
|
|
1363
|
+
this.payload = payload;
|
|
1364
|
+
}
|
|
1365
|
+
};
|
|
1366
|
+
}
|
|
1367
|
+
});
|
|
1368
|
+
|
|
1369
|
+
// src/dev/binding.ts
|
|
1370
|
+
function undeclared(service, declaration) {
|
|
1371
|
+
return failure2(
|
|
1372
|
+
"UNDECLARED",
|
|
1373
|
+
`this app does not declare ${service} \u2014 add ${declaration} to marina.json`
|
|
1374
|
+
);
|
|
1375
|
+
}
|
|
1376
|
+
function createDevBinding(context) {
|
|
1377
|
+
return {
|
|
1378
|
+
async invoke(request2) {
|
|
1379
|
+
try {
|
|
1380
|
+
switch (request2.service) {
|
|
1381
|
+
case "storage": {
|
|
1382
|
+
if (!context.storage) return undeclared("storage", 'runtime.storage: "v1"');
|
|
1383
|
+
const input = request2.input;
|
|
1384
|
+
if (request2.operation === "put") return ok(context.storage.put(input));
|
|
1385
|
+
if (request2.operation === "get")
|
|
1386
|
+
return ok(context.storage.get(input.key));
|
|
1387
|
+
if (request2.operation === "delete") {
|
|
1388
|
+
context.storage.delete(input.key);
|
|
1389
|
+
return ok(null);
|
|
1390
|
+
}
|
|
1391
|
+
if (request2.operation === "list") return ok(context.storage.list(input));
|
|
1392
|
+
return failure2("INVALID_INPUT", `unsupported storage operation ${request2.operation}`);
|
|
1393
|
+
}
|
|
1394
|
+
case "db": {
|
|
1395
|
+
if (!context.database) return undeclared("the managed database", 'runtime.db: "v1"');
|
|
1396
|
+
if (request2.operation === "query") {
|
|
1397
|
+
const query = request2.input;
|
|
1398
|
+
return ok(await context.database.query(query.text, query.params));
|
|
1399
|
+
}
|
|
1400
|
+
if (request2.operation === "transaction") {
|
|
1401
|
+
const { queries } = request2.input;
|
|
1402
|
+
return ok(await context.database.transaction(queries));
|
|
1403
|
+
}
|
|
1404
|
+
return failure2("INVALID_INPUT", `unsupported db operation ${request2.operation}`);
|
|
1405
|
+
}
|
|
1406
|
+
case "ai": {
|
|
1407
|
+
if (!context.manifest.runtime.ai) return undeclared("ai", 'runtime.ai: "v1"');
|
|
1408
|
+
if (request2.operation !== "generate")
|
|
1409
|
+
return failure2("INVALID_INPUT", `unsupported ai operation ${request2.operation}`);
|
|
1410
|
+
return ok(
|
|
1411
|
+
await bridgeInvoke(context.bridge, {
|
|
1412
|
+
service: "ai",
|
|
1413
|
+
input: { args: request2.input }
|
|
1414
|
+
})
|
|
1415
|
+
);
|
|
1416
|
+
}
|
|
1417
|
+
case "jobs": {
|
|
1418
|
+
if (!context.jobs) return undeclared("background jobs", 'runtime.jobs: "v1"');
|
|
1419
|
+
if (request2.operation === "enqueue") {
|
|
1420
|
+
const input = request2.input;
|
|
1421
|
+
const status2 = context.jobs.enqueue(input);
|
|
1422
|
+
return ok({ id: status2.id, state: status2.state });
|
|
1423
|
+
}
|
|
1424
|
+
if (request2.operation === "get") {
|
|
1425
|
+
const { runId } = request2.input;
|
|
1426
|
+
const status2 = context.jobs.get(runId);
|
|
1427
|
+
return status2 ? ok(status2) : failure2("NOT_FOUND", "no such job run");
|
|
1428
|
+
}
|
|
1429
|
+
return failure2("INVALID_INPUT", `unsupported jobs operation ${request2.operation}`);
|
|
1430
|
+
}
|
|
1431
|
+
case "capabilities":
|
|
1432
|
+
case "connections":
|
|
1433
|
+
return ok(
|
|
1434
|
+
await bridgeInvoke(context.bridge, {
|
|
1435
|
+
service: request2.service,
|
|
1436
|
+
input: request2.input
|
|
1437
|
+
})
|
|
1438
|
+
);
|
|
1439
|
+
default:
|
|
1440
|
+
return failure2("UNDECLARED", `unsupported runtime service ${request2.service}`);
|
|
1441
|
+
}
|
|
1442
|
+
} catch (error) {
|
|
1443
|
+
if (error instanceof BridgeError) return { ok: false, error: error.payload };
|
|
1444
|
+
return failure2("INVALID_INPUT", error.message);
|
|
1445
|
+
}
|
|
1446
|
+
},
|
|
1447
|
+
async authorizeJob(request2) {
|
|
1448
|
+
return context.jobs ? context.jobs.authorizeJob(request2) : false;
|
|
1449
|
+
}
|
|
1450
|
+
};
|
|
1451
|
+
}
|
|
1452
|
+
var ok, failure2;
|
|
1453
|
+
var init_binding = __esm({
|
|
1454
|
+
"src/dev/binding.ts"() {
|
|
1455
|
+
"use strict";
|
|
1456
|
+
init_bridge();
|
|
1457
|
+
ok = (value) => ({ ok: true, value });
|
|
1458
|
+
failure2 = (code, message, retryable = false) => ({
|
|
1459
|
+
ok: false,
|
|
1460
|
+
error: { code, message, retryable }
|
|
1461
|
+
});
|
|
1462
|
+
}
|
|
1463
|
+
});
|
|
1464
|
+
|
|
1465
|
+
// src/dev/db.ts
|
|
1466
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync3, readdirSync as readdirSync2, readFileSync as readFileSync5 } from "node:fs";
|
|
1467
|
+
import { join as join5 } from "node:path";
|
|
1468
|
+
function normalizeValue(value) {
|
|
1469
|
+
if (value === null || value === void 0) return null;
|
|
1470
|
+
if (typeof value === "string" || typeof value === "boolean") return value;
|
|
1471
|
+
if (typeof value === "number") return Number.isFinite(value) ? value : String(value);
|
|
1472
|
+
if (typeof value === "bigint") {
|
|
1473
|
+
return value >= BigInt(Number.MIN_SAFE_INTEGER) && value <= BigInt(Number.MAX_SAFE_INTEGER) ? Number(value) : value.toString();
|
|
1474
|
+
}
|
|
1475
|
+
if (value instanceof Date) return value.toISOString();
|
|
1476
|
+
if (value instanceof Uint8Array) return Buffer.from(value).toString("base64");
|
|
1477
|
+
if (Array.isArray(value)) return value.map(normalizeValue);
|
|
1478
|
+
if (typeof value === "object") {
|
|
1479
|
+
return Object.fromEntries(
|
|
1480
|
+
Object.entries(value).map(([key, entry]) => [
|
|
1481
|
+
key,
|
|
1482
|
+
normalizeValue(entry)
|
|
1483
|
+
])
|
|
1484
|
+
);
|
|
1485
|
+
}
|
|
1486
|
+
return String(value);
|
|
1487
|
+
}
|
|
1488
|
+
function normalizeRow(row) {
|
|
1489
|
+
if (!row || typeof row !== "object") return {};
|
|
1490
|
+
return Object.fromEntries(
|
|
1491
|
+
Object.entries(row).map(([key, value]) => [
|
|
1492
|
+
key,
|
|
1493
|
+
normalizeValue(value)
|
|
1494
|
+
])
|
|
1495
|
+
);
|
|
1496
|
+
}
|
|
1497
|
+
var LocalDatabase;
|
|
1498
|
+
var init_db = __esm({
|
|
1499
|
+
"src/dev/db.ts"() {
|
|
1500
|
+
"use strict";
|
|
1501
|
+
LocalDatabase = class _LocalDatabase {
|
|
1502
|
+
db;
|
|
1503
|
+
constructor(db) {
|
|
1504
|
+
this.db = db;
|
|
1505
|
+
}
|
|
1506
|
+
static async open(dataDir) {
|
|
1507
|
+
let module;
|
|
1508
|
+
try {
|
|
1509
|
+
module = await import("@electric-sql/pglite");
|
|
1510
|
+
} catch {
|
|
1511
|
+
throw new Error(
|
|
1512
|
+
"the embedded Postgres could not load \u2014 reinstall the Marina CLI (@electric-sql/pglite is missing)"
|
|
1513
|
+
);
|
|
1514
|
+
}
|
|
1515
|
+
mkdirSync3(dataDir, { recursive: true });
|
|
1516
|
+
return new _LocalDatabase(new module.PGlite(dataDir));
|
|
1517
|
+
}
|
|
1518
|
+
/** Apply the app's marina/migrations in name order, once each — the same
|
|
1519
|
+
* files a deploy applies to the managed database. */
|
|
1520
|
+
async applyMigrations(projectDir) {
|
|
1521
|
+
const directory = join5(projectDir, "marina", "migrations");
|
|
1522
|
+
await this.db.exec(
|
|
1523
|
+
"create table if not exists marina_dev_migrations (name text primary key, applied_at timestamptz not null default now())"
|
|
1524
|
+
);
|
|
1525
|
+
if (!existsSync5(directory)) return [];
|
|
1526
|
+
const files = readdirSync2(directory).filter((file) => file.endsWith(".sql")).toSorted();
|
|
1527
|
+
const applied = [];
|
|
1528
|
+
for (const file of files) {
|
|
1529
|
+
const seen = await this.db.query("select 1 from marina_dev_migrations where name = $1", [
|
|
1530
|
+
file
|
|
1531
|
+
]);
|
|
1532
|
+
if (seen.rows.length > 0) continue;
|
|
1533
|
+
const source = readFileSync5(join5(directory, file), "utf8");
|
|
1534
|
+
await this.db.transaction(async (tx) => {
|
|
1535
|
+
await tx.exec(source);
|
|
1536
|
+
await tx.query("insert into marina_dev_migrations (name) values ($1)", [file]);
|
|
1537
|
+
});
|
|
1538
|
+
applied.push(file);
|
|
1539
|
+
}
|
|
1540
|
+
return applied;
|
|
1541
|
+
}
|
|
1542
|
+
async query(text, params) {
|
|
1543
|
+
const result2 = await this.db.query(text, params);
|
|
1544
|
+
const rows = result2.rows.map(normalizeRow);
|
|
1545
|
+
return { rows, rowCount: rows.length > 0 ? rows.length : result2.affectedRows ?? 0 };
|
|
1546
|
+
}
|
|
1547
|
+
async transaction(queries) {
|
|
1548
|
+
return this.db.transaction(async (tx) => {
|
|
1549
|
+
const results = [];
|
|
1550
|
+
for (const query of queries) {
|
|
1551
|
+
const result2 = await tx.query(query.text, query.params);
|
|
1552
|
+
const rows = result2.rows.map(normalizeRow);
|
|
1553
|
+
results.push({
|
|
1554
|
+
rows,
|
|
1555
|
+
rowCount: rows.length > 0 ? rows.length : result2.affectedRows ?? 0
|
|
1556
|
+
});
|
|
1557
|
+
}
|
|
1558
|
+
return results;
|
|
1559
|
+
});
|
|
1560
|
+
}
|
|
1561
|
+
};
|
|
1562
|
+
}
|
|
1563
|
+
});
|
|
1564
|
+
|
|
1565
|
+
// src/dev/chrome.ts
|
|
1566
|
+
function escapeHtml(value) {
|
|
1567
|
+
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """);
|
|
1568
|
+
}
|
|
1569
|
+
function devChromeSnippet(input) {
|
|
1570
|
+
return `<div id="__marina-dev-chrome" style="position:fixed;right:12px;bottom:12px;z-index:2147483647;display:flex;align-items:center;gap:8px;background:#121316;color:#e5e7ea;border:1px solid #2a2e34;border-radius:8px;padding:6px 12px;font:500 12px/1.4 ui-sans-serif,system-ui;box-shadow:0 4px 16px rgb(0 0 0 / 0.25)">
|
|
1571
|
+
<span style="width:8px;height:8px;border-radius:99px;background:#e0a24a"></span>
|
|
1572
|
+
<span>${escapeHtml(input.appName)} \xB7 local dev \xB7 ${escapeHtml(input.userLabel)}</span>
|
|
1573
|
+
</div>`;
|
|
1574
|
+
}
|
|
1575
|
+
function injectDevChrome(html, snippet) {
|
|
1576
|
+
const marker = /<\/body\s*>/i.exec(html);
|
|
1577
|
+
if (!marker) return html + snippet;
|
|
1578
|
+
return html.slice(0, marker.index) + snippet + html.slice(marker.index);
|
|
1579
|
+
}
|
|
1580
|
+
var init_chrome = __esm({
|
|
1581
|
+
"src/dev/chrome.ts"() {
|
|
1582
|
+
"use strict";
|
|
1583
|
+
}
|
|
1584
|
+
});
|
|
1585
|
+
|
|
1586
|
+
// src/dev/embedded-runtime.ts
|
|
1587
|
+
var DEV_RUNTIME_FILES;
|
|
1588
|
+
var init_embedded_runtime = __esm({
|
|
1589
|
+
"src/dev/embedded-runtime.ts"() {
|
|
1590
|
+
"use strict";
|
|
1591
|
+
DEV_RUNTIME_FILES = {
|
|
1592
|
+
"ai.ts": 'import type {\n AIGenerateInput,\n AIGenerateResult,\n AIMessage,\n MarinaRuntimeBinding,\n} from "./contract.js";\nimport {\n MAX_AI_INPUT_BYTES,\n MAX_AI_MESSAGE_BYTES,\n MAX_AI_MESSAGES,\n MAX_AI_OUTPUT_TOKENS,\n} from "./contract.js";\nimport { MarinaRuntimeError } from "./error.js";\nimport { invokeRuntime } from "./invoke.js";\n\nconst encoder = new TextEncoder();\nconst MESSAGE_ROLES = new Set(["system", "user", "assistant"]);\n\nfunction invalid(message: string): never {\n throw new MarinaRuntimeError({ code: "INVALID_INPUT", message, retryable: false });\n}\n\n/** Validate the portable chat contract before any provider or substrate sees\n * application content. The broker repeats the same bounded checks. */\nexport function assertAIGenerateInput(value: AIGenerateInput): AIGenerateInput {\n if (!value || typeof value !== "object" || Array.isArray(value))\n invalid("AI generation input must be an object");\n if (typeof value.model !== "string" || value.model.length === 0 || value.model.length > 200)\n invalid("AI model must be fast, smart, or a model id from Marina\'s model library");\n if (!Array.isArray(value.messages) || value.messages.length === 0)\n invalid("AI generation requires at least one message");\n if (value.messages.length > MAX_AI_MESSAGES)\n invalid(`AI generation may contain at most ${String(MAX_AI_MESSAGES)} messages`);\n\n let inputBytes = 0;\n const messages: AIMessage[] = value.messages.map((message) => {\n if (!message || typeof message !== "object" || Array.isArray(message))\n invalid("AI messages must be objects");\n if (typeof message.role !== "string" || !MESSAGE_ROLES.has(message.role))\n invalid("AI message role must be system, user, or assistant");\n if (typeof message.content !== "string" || message.content.length === 0)\n invalid("AI message content must not be empty");\n if (/\\p{Cc}/u.test(message.content.replaceAll("\\n", "").replaceAll("\\t", "")))\n invalid("AI message content contains unsupported control characters");\n const bytes = encoder.encode(message.content).byteLength;\n if (bytes > MAX_AI_MESSAGE_BYTES)\n invalid(`AI messages must not exceed ${String(MAX_AI_MESSAGE_BYTES)} bytes each`);\n inputBytes += bytes;\n return { role: message.role, content: message.content };\n });\n if (inputBytes > MAX_AI_INPUT_BYTES)\n invalid(`AI generation input must not exceed ${String(MAX_AI_INPUT_BYTES)} bytes`);\n\n if (\n value.maxOutputTokens !== undefined &&\n (!Number.isSafeInteger(value.maxOutputTokens) ||\n value.maxOutputTokens < 1 ||\n value.maxOutputTokens > MAX_AI_OUTPUT_TOKENS)\n )\n invalid(`AI output tokens must be between 1 and ${String(MAX_AI_OUTPUT_TOKENS)}`);\n\n return {\n model: value.model,\n messages,\n ...(value.maxOutputTokens === undefined ? {} : { maxOutputTokens: value.maxOutputTokens }),\n };\n}\n\nexport interface MarinaAI {\n /** Generate bounded text using a Marina-managed model class. Provider names,\n * credentials, routing, and quota state never enter the app contract. */\n generate(input: AIGenerateInput): Promise<AIGenerateResult>;\n}\n\nexport function createAI(binding: MarinaRuntimeBinding): MarinaAI {\n return {\n async generate(input) {\n return invokeRuntime(binding, "ai", "generate", assertAIGenerateInput(input));\n },\n };\n}\n',
|
|
1593
|
+
"capabilities.ts": 'import type { DatabaseValue, MarinaRuntimeBinding } from "./contract.js";\nimport { MAX_CAPABILITY_INPUT_BYTES, MAX_CAPABILITY_SESSION_BYTES } from "./contract.js";\nimport { MarinaRuntimeError } from "./error.js";\nimport { invokeRuntime } from "./invoke.js";\n\nconst encoder = new TextEncoder();\nconst CAPABILITY_NAME = /^[a-z][a-z0-9_]*(\\.[a-z][a-z0-9_]*)+$/;\nconst REQUEST_ID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;\n\nfunction invalid(message: string): never {\n throw new MarinaRuntimeError({ code: "INVALID_INPUT", message, retryable: false });\n}\n\nfunction capabilityArgs(value: unknown): DatabaseValue {\n let serialized: string | undefined;\n try {\n serialized = JSON.stringify(value);\n } catch {\n invalid("capability arguments must be JSON compatible");\n }\n if (serialized === undefined) invalid("capability arguments must be JSON compatible");\n if (encoder.encode(serialized).byteLength > MAX_CAPABILITY_INPUT_BYTES)\n invalid(`capability arguments must not exceed ${String(MAX_CAPABILITY_INPUT_BYTES)} bytes`);\n return value as DatabaseValue;\n}\n\nexport interface CapabilityInvokeOptions {\n /** Stable idempotency identity for edit-effect Capability retries. */\n requestId?: string;\n}\n\nexport interface MarinaCapabilities {\n invoke<Result = unknown>(\n name: string,\n args?: DatabaseValue,\n options?: CapabilityInvokeOptions,\n ): Promise<Result>;\n}\n\nexport function createCapabilities(\n binding: MarinaRuntimeBinding,\n session?: string,\n): MarinaCapabilities {\n return {\n invoke<Result>(name: string, args: DatabaseValue = {}, options: CapabilityInvokeOptions = {}) {\n if (!session) {\n throw new MarinaRuntimeError({\n code: "UNAVAILABLE",\n message: "Capability calls require an authenticated app request",\n retryable: false,\n });\n }\n if (encoder.encode(session).byteLength > MAX_CAPABILITY_SESSION_BYTES)\n invalid("invalid Capability session");\n if (typeof name !== "string" || name.length > 200 || !CAPABILITY_NAME.test(name))\n invalid("invalid Capability name");\n if (options.requestId !== undefined && !REQUEST_ID.test(options.requestId))\n invalid("invalid Capability request id");\n return invokeRuntime<Result>(binding, "capabilities", "invoke", {\n session,\n capability: name,\n args: capabilityArgs(args),\n ...(options.requestId ? { requestId: options.requestId } : {}),\n });\n },\n };\n}\n',
|
|
1594
|
+
"connections.ts": 'import type { DatabaseValue, MarinaRuntimeBinding } from "./contract.js";\nimport { MAX_CAPABILITY_INPUT_BYTES, MAX_CAPABILITY_SESSION_BYTES } from "./contract.js";\nimport { MarinaRuntimeError } from "./error.js";\nimport { invokeRuntime } from "./invoke.js";\n\nconst encoder = new TextEncoder();\nconst SEGMENT = /^[a-z][a-z0-9_]{0,39}$/;\nconst OPERATION = /^[a-z][a-z0-9_]*(\\.[a-z][a-z0-9_]*)+$/;\n\nfunction invalid(message: string): never {\n throw new MarinaRuntimeError({ code: "INVALID_INPUT", message, retryable: false });\n}\n\nexport interface ConnectionInvokeRequest {\n connector: string;\n /** Explicit connection id; optional when the app declares exactly one\n * connection of this connector. */\n connection?: string;\n operation: string;\n args?: DatabaseValue;\n}\n\nexport type MarinaConnectionOperationInput = { connection?: string } & Record<string, unknown>;\nexport type MarinaConnectionCall = (input?: MarinaConnectionOperationInput) => Promise<unknown>;\nexport type MarinaConnectionNamespace = MarinaConnectionCall & {\n readonly [segment: string]: MarinaConnectionNamespace;\n};\nexport type MarinaConnections = {\n invoke<Result = unknown>(request: ConnectionInvokeRequest): Promise<Result>;\n} & { readonly [connector: string]: MarinaConnectionNamespace };\n\nfunction boundedArgs(value: unknown): DatabaseValue {\n let serialized: string | undefined;\n try {\n serialized = JSON.stringify(value);\n } catch {\n invalid("connection operation input must be JSON compatible");\n }\n if (serialized === undefined) invalid("connection operation input must be JSON compatible");\n if (encoder.encode(serialized).byteLength > MAX_CAPABILITY_INPUT_BYTES)\n invalid(\n `connection operation input must not exceed ${String(MAX_CAPABILITY_INPUT_BYTES)} bytes`,\n );\n return value as DatabaseValue;\n}\n\n/**\n * `marina.connections.<connector>.<operation>(input)` \u2014 a dynamic path so the\n * runtime ships no connector catalog; the control plane owns validation and\n * authorization. `input.connection` names one of the app\'s declared\n * connections and is required only when the manifest declares several of the\n * same connector.\n */\nexport function createConnections(\n binding: MarinaRuntimeBinding,\n session?: string,\n): MarinaConnections {\n const invoke = <Result = unknown>(request: ConnectionInvokeRequest): Promise<Result> => {\n if (!session) {\n throw new MarinaRuntimeError({\n code: "UNAVAILABLE",\n message: "Connection calls require an authenticated app request",\n retryable: false,\n });\n }\n if (encoder.encode(session).byteLength > MAX_CAPABILITY_SESSION_BYTES)\n invalid("invalid connection session");\n if (typeof request.connector !== "string" || !SEGMENT.test(request.connector))\n invalid("invalid connector name");\n if (\n request.connection !== undefined &&\n (typeof request.connection !== "string" || !SEGMENT.test(request.connection))\n )\n invalid("invalid connection id");\n if (\n typeof request.operation !== "string" ||\n request.operation.length > 200 ||\n !OPERATION.test(request.operation)\n )\n invalid("invalid connection operation");\n return invokeRuntime<Result>(binding, "connections", "invoke", {\n session,\n connector: request.connector,\n ...(request.connection ? { connection: request.connection } : {}),\n operation: request.operation,\n args: boundedArgs(request.args ?? {}),\n });\n };\n\n const operationPath = (connector: string, segments: readonly string[]) => {\n const callable = (input: MarinaConnectionOperationInput = {}): Promise<unknown> => {\n if (segments.length < 2) invalid("call a connection operation such as orders.list");\n if (!input || typeof input !== "object" || Array.isArray(input))\n invalid("connection operation input must be an object");\n const { connection, ...args } = input;\n if (connection !== undefined && (typeof connection !== "string" || !SEGMENT.test(connection)))\n invalid("invalid connection id");\n return invoke({\n connector,\n ...(connection ? { connection } : {}),\n operation: segments.join("."),\n args: args as DatabaseValue,\n });\n };\n const path: MarinaConnectionNamespace = new Proxy(callable, {\n get(_target, property) {\n if (typeof property !== "string" || property === "then" || !SEGMENT.test(property))\n return undefined;\n return operationPath(connector, [...segments, property]);\n },\n }) as unknown as MarinaConnectionNamespace;\n return path;\n };\n\n return new Proxy(\n { invoke },\n {\n get(target, property) {\n if (property === "invoke") return target.invoke;\n if (typeof property !== "string" || property === "then" || !SEGMENT.test(property))\n return undefined;\n return operationPath(property, []);\n },\n },\n ) as MarinaConnections;\n}\n',
|
|
1595
|
+
"contract.ts": `/** The versioned provider-neutral protocol between an app and Marina. */
|
|
1596
|
+
export const MARINA_RUNTIME_PROTOCOL_VERSION = 1 as const;
|
|
1597
|
+
export const MAX_STORAGE_KEY_BYTES = 900;
|
|
1598
|
+
export const MAX_STORAGE_OBJECT_BYTES = 64 * 1024 * 1024;
|
|
1599
|
+
export const MAX_CAPABILITY_INPUT_BYTES = 256 * 1024;
|
|
1600
|
+
export const MAX_CAPABILITY_SESSION_BYTES = 4096;
|
|
1601
|
+
export const MAX_CAPABILITY_RESULT_BYTES = 32 * 1024 * 1024;
|
|
1602
|
+
export const MAX_DATABASE_QUERY_BYTES = 100 * 1024;
|
|
1603
|
+
export const MAX_DATABASE_PARAMETERS = 100;
|
|
1604
|
+
export const MAX_DATABASE_PARAMETER_BYTES = 1024 * 1024;
|
|
1605
|
+
export const MAX_DATABASE_TRANSACTION_QUERIES = 25;
|
|
1606
|
+
export const MAX_AI_MESSAGES = 64;
|
|
1607
|
+
export const MAX_AI_MESSAGE_BYTES = 32 * 1024;
|
|
1608
|
+
export const MAX_AI_INPUT_BYTES = 128 * 1024;
|
|
1609
|
+
export const MAX_AI_OUTPUT_TOKENS = 4096;
|
|
1610
|
+
export const MAX_JOB_INPUT_BYTES = 256 * 1024;
|
|
1611
|
+
export const MAX_JOB_VALUE_DEPTH = 32;
|
|
1612
|
+
export const MAX_JOB_INVOCATION_BYTES = MAX_JOB_INPUT_BYTES + 128 * 1024;
|
|
1613
|
+
export const MARINA_JOB_EXECUTION_PATH = "/.marina/runtime/jobs/execute";
|
|
1614
|
+
|
|
1615
|
+
/** Stable service identifiers carried by the broker protocol. */
|
|
1616
|
+
export type MarinaRuntimeService =
|
|
1617
|
+
| "storage"
|
|
1618
|
+
| "db"
|
|
1619
|
+
| "ai"
|
|
1620
|
+
| "jobs"
|
|
1621
|
+
| "capabilities"
|
|
1622
|
+
| "connections";
|
|
1623
|
+
|
|
1624
|
+
export type StorageOperation = "put" | "get" | "delete" | "list";
|
|
1625
|
+
|
|
1626
|
+
export interface MarinaRuntimeRequest {
|
|
1627
|
+
protocolVersion: typeof MARINA_RUNTIME_PROTOCOL_VERSION;
|
|
1628
|
+
/** Unique identity for this individual broker call. */
|
|
1629
|
+
requestId: string;
|
|
1630
|
+
service: MarinaRuntimeService;
|
|
1631
|
+
operation: string;
|
|
1632
|
+
input: unknown;
|
|
1633
|
+
}
|
|
1634
|
+
|
|
1635
|
+
export type MarinaRuntimeErrorCode =
|
|
1636
|
+
| "UNAVAILABLE"
|
|
1637
|
+
| "UNDECLARED"
|
|
1638
|
+
| "INVALID_INPUT"
|
|
1639
|
+
| "NOT_FOUND"
|
|
1640
|
+
| "QUOTA_EXCEEDED"
|
|
1641
|
+
| "CONFLICT"
|
|
1642
|
+
| "INTERNAL";
|
|
1643
|
+
|
|
1644
|
+
export interface MarinaRuntimeErrorPayload {
|
|
1645
|
+
code: MarinaRuntimeErrorCode;
|
|
1646
|
+
message: string;
|
|
1647
|
+
retryable: boolean;
|
|
1648
|
+
}
|
|
1649
|
+
|
|
1650
|
+
export type MarinaRuntimeResponse =
|
|
1651
|
+
| { ok: true; value: unknown }
|
|
1652
|
+
| { ok: false; error: MarinaRuntimeErrorPayload };
|
|
1653
|
+
|
|
1654
|
+
/**
|
|
1655
|
+
* The substrate supplies this object-capability. Applications never receive
|
|
1656
|
+
* the provider resources, credentials, or namespace identifiers behind it.
|
|
1657
|
+
*/
|
|
1658
|
+
export interface MarinaRuntimeBinding {
|
|
1659
|
+
invoke(request: MarinaRuntimeRequest): Promise<MarinaRuntimeResponse>;
|
|
1660
|
+
/** Internal substrate callback used only by defineApp's job adapter. The
|
|
1661
|
+
* application receives no signing key or provider dispatch primitive. */
|
|
1662
|
+
authorizeJob?(request: MarinaJobAuthorizationRequest): Promise<boolean>;
|
|
1663
|
+
}
|
|
1664
|
+
|
|
1665
|
+
export type JobValue = DatabaseValue;
|
|
1666
|
+
export type JobTrigger = "enqueue" | "schedule";
|
|
1667
|
+
export type JobState = "queued" | "running" | "succeeded" | "failed";
|
|
1668
|
+
|
|
1669
|
+
export interface JobEnqueueInput {
|
|
1670
|
+
name: string;
|
|
1671
|
+
input: JobValue;
|
|
1672
|
+
dedupeKey?: string;
|
|
1673
|
+
}
|
|
1674
|
+
|
|
1675
|
+
export interface JobEnqueueResult {
|
|
1676
|
+
id: string;
|
|
1677
|
+
state: JobState;
|
|
1678
|
+
}
|
|
1679
|
+
|
|
1680
|
+
export interface JobStatus {
|
|
1681
|
+
id: string;
|
|
1682
|
+
name: string;
|
|
1683
|
+
state: JobState;
|
|
1684
|
+
attempts: number;
|
|
1685
|
+
createdAt: string;
|
|
1686
|
+
startedAt?: string;
|
|
1687
|
+
finishedAt?: string;
|
|
1688
|
+
}
|
|
1689
|
+
|
|
1690
|
+
export interface MarinaJobInvocation {
|
|
1691
|
+
runId: string;
|
|
1692
|
+
workspaceId: string;
|
|
1693
|
+
appId: string;
|
|
1694
|
+
artifactDigest: string;
|
|
1695
|
+
environment: "development" | "preview" | "production";
|
|
1696
|
+
jobName: string;
|
|
1697
|
+
handler: string;
|
|
1698
|
+
input: JobValue;
|
|
1699
|
+
attempt: number;
|
|
1700
|
+
trigger: JobTrigger;
|
|
1701
|
+
scheduledFor?: string;
|
|
1702
|
+
/** Opaque, run-bound delegation consumed by the runtime adapter. It is not
|
|
1703
|
+
* exposed to the application handler or job context. */
|
|
1704
|
+
capabilitySession?: string;
|
|
1705
|
+
}
|
|
1706
|
+
|
|
1707
|
+
export interface MarinaJobAuthorizationRequest {
|
|
1708
|
+
token: string;
|
|
1709
|
+
body: string;
|
|
1710
|
+
}
|
|
1711
|
+
|
|
1712
|
+
export interface StoragePutInput {
|
|
1713
|
+
key: string;
|
|
1714
|
+
body: string | Uint8Array | ArrayBuffer;
|
|
1715
|
+
contentType?: string;
|
|
1716
|
+
metadata?: Record<string, string>;
|
|
1717
|
+
}
|
|
1718
|
+
|
|
1719
|
+
export interface StorageGetInput {
|
|
1720
|
+
key: string;
|
|
1721
|
+
}
|
|
1722
|
+
|
|
1723
|
+
export interface StorageDeleteInput {
|
|
1724
|
+
key: string;
|
|
1725
|
+
}
|
|
1726
|
+
|
|
1727
|
+
export interface StorageListInput {
|
|
1728
|
+
prefix?: string;
|
|
1729
|
+
cursor?: string;
|
|
1730
|
+
limit?: number;
|
|
1731
|
+
}
|
|
1732
|
+
|
|
1733
|
+
export interface StorageObjectMetadata {
|
|
1734
|
+
key: string;
|
|
1735
|
+
size: number;
|
|
1736
|
+
etag: string;
|
|
1737
|
+
uploadedAt: string;
|
|
1738
|
+
contentType?: string;
|
|
1739
|
+
metadata: Record<string, string>;
|
|
1740
|
+
}
|
|
1741
|
+
|
|
1742
|
+
export interface StorageObject extends StorageObjectMetadata {
|
|
1743
|
+
body: Uint8Array;
|
|
1744
|
+
}
|
|
1745
|
+
|
|
1746
|
+
export interface StorageListResult {
|
|
1747
|
+
objects: StorageObjectMetadata[];
|
|
1748
|
+
cursor?: string;
|
|
1749
|
+
truncated: boolean;
|
|
1750
|
+
}
|
|
1751
|
+
|
|
1752
|
+
/** JSON-compatible values cross the provider-neutral broker boundary. Postgres
|
|
1753
|
+
* dates, numerics, JSON, and arrays therefore keep deterministic wire shapes
|
|
1754
|
+
* across Workers, local development, and future substrates. */
|
|
1755
|
+
export type DatabaseValue =
|
|
1756
|
+
| null
|
|
1757
|
+
| boolean
|
|
1758
|
+
| number
|
|
1759
|
+
| string
|
|
1760
|
+
| DatabaseValue[]
|
|
1761
|
+
| { [key: string]: DatabaseValue };
|
|
1762
|
+
|
|
1763
|
+
export type DatabaseRow = Record<string, DatabaseValue>;
|
|
1764
|
+
|
|
1765
|
+
export interface DatabaseQuery {
|
|
1766
|
+
text: string;
|
|
1767
|
+
params: DatabaseValue[];
|
|
1768
|
+
}
|
|
1769
|
+
|
|
1770
|
+
export interface DatabaseResult<Row extends DatabaseRow = DatabaseRow> {
|
|
1771
|
+
rows: Row[];
|
|
1772
|
+
rowCount: number;
|
|
1773
|
+
}
|
|
1774
|
+
|
|
1775
|
+
export type AIModelClass = "fast" | "smart";
|
|
1776
|
+
/** A stable class alias, or the id of a model from Marina's own model
|
|
1777
|
+
* library. The broker validates explicit ids against the library it offers;
|
|
1778
|
+
* provider model identifiers are never part of the app contract. */
|
|
1779
|
+
export type AIModel = AIModelClass | (string & {});
|
|
1780
|
+
export type AIMessageRole = "system" | "user" | "assistant";
|
|
1781
|
+
|
|
1782
|
+
export interface AIMessage {
|
|
1783
|
+
role: AIMessageRole;
|
|
1784
|
+
content: string;
|
|
1785
|
+
}
|
|
1786
|
+
|
|
1787
|
+
export interface AIGenerateInput {
|
|
1788
|
+
model: AIModel;
|
|
1789
|
+
messages: AIMessage[];
|
|
1790
|
+
maxOutputTokens?: number;
|
|
1791
|
+
}
|
|
1792
|
+
|
|
1793
|
+
export interface AIGenerateResult {
|
|
1794
|
+
text: string;
|
|
1795
|
+
}
|
|
1796
|
+
|
|
1797
|
+
/** Detect top-level Postgres transaction-control statements while ignoring
|
|
1798
|
+
* quoted values, identifiers, dollar bodies, and nested comments. Marina owns
|
|
1799
|
+
* the operation transaction on every substrate. */
|
|
1800
|
+
export function containsDatabaseTransactionControl(source: string): boolean {
|
|
1801
|
+
let executable = "";
|
|
1802
|
+
let index = 0;
|
|
1803
|
+
let blockDepth = 0;
|
|
1804
|
+
let state: "normal" | "single" | "double" | "line" | "block" | "dollar" = "normal";
|
|
1805
|
+
let dollar = "";
|
|
1806
|
+
while (index < source.length) {
|
|
1807
|
+
const current = source[index] ?? "";
|
|
1808
|
+
const next = source[index + 1] ?? "";
|
|
1809
|
+
if (state === "normal") {
|
|
1810
|
+
if (current === "-" && next === "-") {
|
|
1811
|
+
state = "line";
|
|
1812
|
+
executable += " ";
|
|
1813
|
+
index += 2;
|
|
1814
|
+
continue;
|
|
1815
|
+
}
|
|
1816
|
+
if (current === "/" && next === "*") {
|
|
1817
|
+
state = "block";
|
|
1818
|
+
blockDepth = 1;
|
|
1819
|
+
executable += " ";
|
|
1820
|
+
index += 2;
|
|
1821
|
+
continue;
|
|
1822
|
+
}
|
|
1823
|
+
if (current === "'") state = "single";
|
|
1824
|
+
else if (current === '"') state = "double";
|
|
1825
|
+
else if (current === "$") {
|
|
1826
|
+
const opener = /^\\$[A-Za-z_][A-Za-z0-9_]*\\$|^\\$\\$/.exec(source.slice(index))?.[0];
|
|
1827
|
+
if (opener) {
|
|
1828
|
+
state = "dollar";
|
|
1829
|
+
dollar = opener;
|
|
1830
|
+
executable += " ".repeat(opener.length);
|
|
1831
|
+
index += opener.length;
|
|
1832
|
+
continue;
|
|
1833
|
+
}
|
|
1834
|
+
}
|
|
1835
|
+
executable += state === "normal" ? current : " ";
|
|
1836
|
+
index++;
|
|
1837
|
+
continue;
|
|
1838
|
+
}
|
|
1839
|
+
if (state === "line") {
|
|
1840
|
+
if (current === "\\n") {
|
|
1841
|
+
state = "normal";
|
|
1842
|
+
executable += "\\n";
|
|
1843
|
+
} else executable += " ";
|
|
1844
|
+
index++;
|
|
1845
|
+
continue;
|
|
1846
|
+
}
|
|
1847
|
+
if (state === "block") {
|
|
1848
|
+
if (current === "/" && next === "*") {
|
|
1849
|
+
blockDepth++;
|
|
1850
|
+
executable += " ";
|
|
1851
|
+
index += 2;
|
|
1852
|
+
} else if (current === "*" && next === "/") {
|
|
1853
|
+
blockDepth--;
|
|
1854
|
+
executable += " ";
|
|
1855
|
+
index += 2;
|
|
1856
|
+
if (blockDepth === 0) state = "normal";
|
|
1857
|
+
} else {
|
|
1858
|
+
executable += current === "\\n" ? "\\n" : " ";
|
|
1859
|
+
index++;
|
|
1860
|
+
}
|
|
1861
|
+
continue;
|
|
1862
|
+
}
|
|
1863
|
+
if (state === "dollar") {
|
|
1864
|
+
if (source.startsWith(dollar, index)) {
|
|
1865
|
+
executable += " ".repeat(dollar.length);
|
|
1866
|
+
index += dollar.length;
|
|
1867
|
+
state = "normal";
|
|
1868
|
+
} else {
|
|
1869
|
+
executable += current === "\\n" ? "\\n" : " ";
|
|
1870
|
+
index++;
|
|
1871
|
+
}
|
|
1872
|
+
continue;
|
|
1873
|
+
}
|
|
1874
|
+
if (state === "single" && current === "'" && next === "'") {
|
|
1875
|
+
executable += " ";
|
|
1876
|
+
index += 2;
|
|
1877
|
+
continue;
|
|
1878
|
+
}
|
|
1879
|
+
if (state === "double" && current === '"' && next === '"') {
|
|
1880
|
+
executable += " ";
|
|
1881
|
+
index += 2;
|
|
1882
|
+
continue;
|
|
1883
|
+
}
|
|
1884
|
+
const closes =
|
|
1885
|
+
(state === "single" && current === "'") || (state === "double" && current === '"');
|
|
1886
|
+
executable += current === "\\n" ? "\\n" : " ";
|
|
1887
|
+
index++;
|
|
1888
|
+
if (closes) state = "normal";
|
|
1889
|
+
}
|
|
1890
|
+
return /(?:^|;)\\s*(?:abort\\b|begin\\b|start\\s+transaction\\b|commit\\b|end\\b|rollback\\b|savepoint\\b|release\\s+savepoint\\b|prepare\\s+transaction\\b|set\\s+transaction\\b|set\\s+session\\s+characteristics\\s+as\\s+transaction\\b)/i.test(
|
|
1891
|
+
executable,
|
|
1892
|
+
);
|
|
1893
|
+
}
|
|
1894
|
+
|
|
1895
|
+
export function isRuntimeResponse(value: unknown): value is MarinaRuntimeResponse {
|
|
1896
|
+
if (!value || typeof value !== "object") return false;
|
|
1897
|
+
const response = value as Partial<MarinaRuntimeResponse>;
|
|
1898
|
+
if (response.ok === true) return Object.hasOwn(response, "value");
|
|
1899
|
+
if (response.ok !== false || !response.error || typeof response.error !== "object") return false;
|
|
1900
|
+
return (
|
|
1901
|
+
typeof response.error.code === "string" &&
|
|
1902
|
+
typeof response.error.message === "string" &&
|
|
1903
|
+
typeof response.error.retryable === "boolean"
|
|
1904
|
+
);
|
|
1905
|
+
}
|
|
1906
|
+
`,
|
|
1907
|
+
"database.ts": 'import type {\n DatabaseQuery,\n DatabaseResult,\n DatabaseRow,\n MarinaRuntimeBinding,\n} from "./contract.js";\nimport {\n containsDatabaseTransactionControl,\n MAX_DATABASE_PARAMETER_BYTES,\n MAX_DATABASE_PARAMETERS,\n MAX_DATABASE_QUERY_BYTES,\n MAX_DATABASE_TRANSACTION_QUERIES,\n} from "./contract.js";\nimport { MarinaRuntimeError } from "./error.js";\nimport { invokeRuntime } from "./invoke.js";\n\nconst encoder = new TextEncoder();\nconst MAX_VALUE_DEPTH = 32;\n\nfunction invalid(message: string): never {\n throw new MarinaRuntimeError({ code: "INVALID_INPUT", message, retryable: false });\n}\n\nfunction assertJsonValue(value: unknown, depth = 0, seen = new Set<object>()): void {\n if (depth > MAX_VALUE_DEPTH) invalid("database parameters are nested too deeply");\n if (\n value === null ||\n typeof value === "string" ||\n typeof value === "boolean" ||\n (typeof value === "number" && Number.isFinite(value))\n )\n return;\n if (!value || typeof value !== "object")\n invalid("database parameters must contain only JSON-compatible values");\n if (seen.has(value)) invalid("database parameters must not contain cycles");\n seen.add(value);\n if (Array.isArray(value)) {\n for (const item of value) assertJsonValue(item, depth + 1, seen);\n } else {\n const prototype = Object.getPrototypeOf(value);\n if (prototype !== Object.prototype && prototype !== null)\n invalid("database parameters must contain only plain JSON objects");\n for (const item of Object.values(value)) assertJsonValue(item, depth + 1, seen);\n }\n seen.delete(value);\n}\n\nfunction jsonBytes(value: unknown): number {\n assertJsonValue(value);\n let serialized: string;\n try {\n serialized = JSON.stringify(value);\n } catch {\n invalid("database parameters must be JSON serializable");\n }\n if (serialized === undefined) invalid("database parameters must be JSON serializable");\n return encoder.encode(serialized).byteLength;\n}\n\n/** Validate one provider-neutral parameterized Postgres query. Runtime SQL is\n * app-authored, but values remain separate so callers never need to interpolate\n * request data into SQL text. The broker repeats these checks at its boundary. */\nexport function assertDatabaseQuery(value: DatabaseQuery): DatabaseQuery {\n if (!value || typeof value !== "object" || Array.isArray(value))\n invalid("database query must be an object");\n if (typeof value.text !== "string" || value.text.trim().length === 0)\n invalid("database query text must not be empty");\n if (value.text.includes("\\0")) invalid("database query text must not contain null bytes");\n if (containsDatabaseTransactionControl(value.text))\n invalid("database transactions are managed by the Marina runtime");\n if (encoder.encode(value.text).byteLength > MAX_DATABASE_QUERY_BYTES)\n invalid(`database query text must not exceed ${String(MAX_DATABASE_QUERY_BYTES)} bytes`);\n if (!Array.isArray(value.params)) invalid("database query parameters must be an array");\n if (value.params.length > MAX_DATABASE_PARAMETERS)\n invalid(`database queries may contain at most ${String(MAX_DATABASE_PARAMETERS)} parameters`);\n if (jsonBytes(value.params) > MAX_DATABASE_PARAMETER_BYTES)\n invalid(\n `database query parameters must not exceed ${String(MAX_DATABASE_PARAMETER_BYTES)} bytes`,\n );\n return value;\n}\n\nexport interface MarinaDatabase {\n /** Execute one parameterized statement against this app\'s private schema. */\n query<Row extends DatabaseRow = DatabaseRow>(\n text: string,\n params?: DatabaseQuery["params"],\n ): Promise<DatabaseResult<Row>>;\n\n /** Execute a bounded list of statements atomically. Interactive transaction\n * callbacks are intentionally absent so the contract works across edge and\n * future non-Cloudflare substrates. */\n transaction<Row extends DatabaseRow = DatabaseRow>(\n queries: readonly DatabaseQuery[],\n ): Promise<DatabaseResult<Row>[]>;\n}\n\nexport function createDatabase(binding: MarinaRuntimeBinding): MarinaDatabase {\n return {\n async query(text, params = []) {\n const query = assertDatabaseQuery({ text, params });\n return invokeRuntime(binding, "db", "query", query);\n },\n\n async transaction(queries) {\n if (!Array.isArray(queries) || queries.length === 0)\n invalid("database transaction must contain at least one query");\n if (queries.length > MAX_DATABASE_TRANSACTION_QUERIES)\n invalid(\n `database transactions may contain at most ${String(MAX_DATABASE_TRANSACTION_QUERIES)} queries`,\n );\n const validated = queries.map((query) => assertDatabaseQuery(query));\n return invokeRuntime(binding, "db", "transaction", { queries: validated });\n },\n };\n}\n',
|
|
1908
|
+
"error.ts": 'import type { MarinaRuntimeErrorCode, MarinaRuntimeErrorPayload } from "./contract.js";\n\nexport class MarinaRuntimeError extends Error {\n readonly code: MarinaRuntimeErrorCode;\n readonly retryable: boolean;\n\n constructor(payload: MarinaRuntimeErrorPayload) {\n super(payload.message);\n this.name = "MarinaRuntimeError";\n this.code = payload.code;\n this.retryable = payload.retryable;\n }\n}\n',
|
|
1909
|
+
"index.ts": 'import {\n MARINA_JOB_EXECUTION_PATH,\n MAX_JOB_INVOCATION_BYTES,\n type MarinaRuntimeBinding,\n} from "./contract.js";\nimport { MarinaRuntimeError } from "./error.js";\nimport { createStorage, type MarinaStorage } from "./storage.js";\nimport { createDatabase, type MarinaDatabase } from "./database.js";\nimport { createAI, type MarinaAI } from "./ai.js";\nimport { createJobs, type MarinaJobs } from "./jobs.js";\nimport { createCapabilities, type MarinaCapabilities } from "./capabilities.js";\nimport { createConnections, type MarinaConnections } from "./connections.js";\n\nexport type {\n MarinaRuntimeBinding,\n DatabaseQuery,\n DatabaseResult,\n DatabaseRow,\n DatabaseValue,\n AIGenerateInput,\n AIGenerateResult,\n AIMessage,\n AIMessageRole,\n AIModel,\n AIModelClass,\n JobEnqueueResult,\n JobState,\n JobStatus,\n JobTrigger,\n JobValue,\n MarinaJobInvocation,\n MarinaRuntimeErrorCode,\n MarinaRuntimeErrorPayload,\n MarinaRuntimeRequest,\n MarinaRuntimeResponse,\n StorageListResult,\n StorageObject,\n StorageObjectMetadata,\n} from "./contract.js";\nexport {\n containsDatabaseTransactionControl,\n MARINA_RUNTIME_PROTOCOL_VERSION,\n MAX_STORAGE_KEY_BYTES,\n MAX_STORAGE_OBJECT_BYTES,\n MAX_CAPABILITY_INPUT_BYTES,\n MAX_CAPABILITY_RESULT_BYTES,\n MAX_CAPABILITY_SESSION_BYTES,\n MAX_DATABASE_PARAMETER_BYTES,\n MAX_DATABASE_PARAMETERS,\n MAX_DATABASE_QUERY_BYTES,\n MAX_DATABASE_TRANSACTION_QUERIES,\n MAX_AI_INPUT_BYTES,\n MAX_AI_MESSAGE_BYTES,\n MAX_AI_MESSAGES,\n MAX_AI_OUTPUT_TOKENS,\n MAX_JOB_INPUT_BYTES,\n MAX_JOB_INVOCATION_BYTES,\n MAX_JOB_VALUE_DEPTH,\n MARINA_JOB_EXECUTION_PATH,\n} from "./contract.js";\nexport { MarinaRuntimeError } from "./error.js";\nexport type { MarinaStorage, StorageListOptions, StoragePutOptions } from "./storage.js";\nexport type { MarinaDatabase } from "./database.js";\nexport { assertAIGenerateInput } from "./ai.js";\nexport type { MarinaAI } from "./ai.js";\nexport { assertJobName, assertJobValue } from "./jobs.js";\nexport type { JobEnqueueOptions, MarinaJobs } from "./jobs.js";\nexport type { CapabilityInvokeOptions, MarinaCapabilities } from "./capabilities.js";\nexport type {\n ConnectionInvokeRequest,\n MarinaConnectionNamespace,\n MarinaConnectionOperationInput,\n MarinaConnections,\n} from "./connections.js";\n\nexport interface MarinaRuntime {\n storage: MarinaStorage;\n db: MarinaDatabase;\n ai: MarinaAI;\n jobs: MarinaJobs;\n capabilities: MarinaCapabilities;\n connections: MarinaConnections;\n}\n\nexport function createMarinaRuntime(binding: MarinaRuntimeBinding): MarinaRuntime {\n return createRuntime(binding);\n}\n\nfunction createRuntime(binding: MarinaRuntimeBinding, capabilitySession?: string): MarinaRuntime {\n return {\n storage: createStorage(binding),\n db: createDatabase(binding),\n ai: createAI(binding),\n jobs: createJobs(binding),\n capabilities: createCapabilities(binding, capabilitySession),\n connections: createConnections(binding, capabilitySession),\n };\n}\n\nexport interface MarinaAppContext {\n waitUntil(promise: Promise<unknown>): void;\n}\n\nexport interface MarinaApp {\n fetch(\n request: Request,\n marina: MarinaRuntime,\n context: MarinaAppContext,\n ): Response | Promise<Response>;\n jobs?: Record<string, MarinaJobHandler>;\n}\n\nexport interface MarinaPlatformApp {\n fetch(\n request: Request,\n environment: unknown,\n context: MarinaAppContext,\n ): Response | Promise<Response>;\n}\n\nconst platformApps = new WeakSet<object>();\n\nexport interface MarinaJobContext extends MarinaAppContext {\n runId: string;\n attempt: number;\n trigger: import("./contract.js").JobTrigger;\n scheduledFor?: string;\n}\n\nexport type MarinaJobHandler = (\n input: import("./contract.js").JobValue,\n marina: MarinaRuntime,\n context: MarinaJobContext,\n) => void | Promise<void>;\n\nasync function readBoundedJobBody(request: Request): Promise<string | null> {\n const declared = request.headers.get("content-length");\n if (declared !== null) {\n const bytes = Number(declared);\n if (Number.isFinite(bytes) && bytes > MAX_JOB_INVOCATION_BYTES) return null;\n }\n if (!request.body) return "";\n const reader = request.body.getReader();\n const chunks: Uint8Array[] = [];\n let size = 0;\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n size += value.byteLength;\n if (size > MAX_JOB_INVOCATION_BYTES) {\n await reader.cancel().catch(() => undefined);\n return null;\n }\n chunks.push(value);\n }\n const bytes = new Uint8Array(size);\n let offset = 0;\n for (const chunk of chunks) {\n bytes.set(chunk, offset);\n offset += chunk.byteLength;\n }\n return new TextDecoder().decode(bytes);\n}\n\nasync function executeJobRequest(\n app: MarinaApp,\n request: Request,\n binding: MarinaRuntimeBinding,\n context: MarinaAppContext,\n): Promise<Response> {\n const token = request.headers.get("x-marina-job-token") ?? "";\n if (!binding.authorizeJob || !token) return new Response("Not found", { status: 404 });\n const body = await readBoundedJobBody(request);\n if (body === null) return new Response("Payload too large", { status: 413 });\n if (!(await binding.authorizeJob({ token, body })))\n return new Response("Not found", { status: 404 });\n let invocation: import("./contract.js").MarinaJobInvocation;\n try {\n invocation = JSON.parse(body) as import("./contract.js").MarinaJobInvocation;\n } catch {\n return new Response("Bad request", { status: 400 });\n }\n const handler = app.jobs?.[invocation.handler];\n if (typeof handler !== "function") return new Response("Job handler not found", { status: 404 });\n await handler(invocation.input, createRuntime(binding, invocation.capabilitySession), {\n ...context,\n runId: invocation.runId,\n attempt: invocation.attempt,\n trigger: invocation.trigger,\n ...(invocation.scheduledFor ? { scheduledFor: invocation.scheduledFor } : {}),\n });\n return new Response(null, { status: 204 });\n}\n\ninterface SubstrateEnvironment {\n MARINA?: unknown;\n}\n\nfunction runtimeBinding(environment: unknown): MarinaRuntimeBinding {\n const binding = (environment as SubstrateEnvironment | null)?.MARINA;\n if (!binding || typeof binding !== "object" || !("invoke" in binding)) {\n throw new MarinaRuntimeError({\n code: "UNAVAILABLE",\n message: "This app was not started with the Marina runtime binding",\n retryable: false,\n });\n }\n const invoke = (binding as { invoke?: unknown }).invoke;\n if (typeof invoke !== "function") {\n throw new MarinaRuntimeError({\n code: "UNAVAILABLE",\n message: "This app has an incompatible Marina runtime binding",\n retryable: false,\n });\n }\n return binding as MarinaRuntimeBinding;\n}\n\n/**\n * Define a portable Marina application. The returned adapter matches the\n * active substrate, while application code sees only Web APIs and Marina.\n */\nexport function defineApp(app: MarinaApp): MarinaPlatformApp {\n const adapter: MarinaPlatformApp = {\n fetch(request: Request, environment: unknown, context: MarinaAppContext) {\n const binding = runtimeBinding(environment);\n const url = new URL(request.url);\n if (request.method === "POST" && url.pathname === MARINA_JOB_EXECUTION_PATH) {\n return executeJobRequest(app, request, binding, context);\n }\n const capabilitySession = request.headers.get("x-platform-capability-session") ?? undefined;\n if (!capabilitySession) return app.fetch(request, createRuntime(binding), context);\n const headers = new Headers(request.headers);\n headers.delete("x-platform-capability-session");\n return app.fetch(\n new Request(request, { headers }),\n createRuntime(binding, capabilitySession),\n context,\n );\n },\n };\n platformApps.add(adapter);\n return adapter;\n}\n\n/** Internal build adapter. Application entrypoints may export the portable\n * app object directly; imports of defineApp remain compatible during the\n * transition without wrapping an already-adapted module twice. */\nexport function adaptApp(app: MarinaApp | MarinaPlatformApp): MarinaPlatformApp {\n if (!app || typeof app !== "object" || typeof app.fetch !== "function") {\n throw new TypeError("The Marina app entrypoint must export a fetch handler");\n }\n return platformApps.has(app) ? (app as MarinaPlatformApp) : defineApp(app as MarinaApp);\n}\n',
|
|
1910
|
+
"invoke.ts": 'import type {\n MarinaRuntimeBinding,\n MarinaRuntimeRequest,\n MarinaRuntimeService,\n} from "./contract.js";\nimport { MARINA_RUNTIME_PROTOCOL_VERSION, isRuntimeResponse } from "./contract.js";\nimport { MarinaRuntimeError } from "./error.js";\n\n/** Cross-service invocation behavior belongs to the Marina compatibility\n * layer. Individual capability clients supply only their stable service name,\n * operation, and validated provider-neutral input. */\nexport async function invokeRuntime<T>(\n binding: MarinaRuntimeBinding,\n service: MarinaRuntimeService,\n operation: MarinaRuntimeRequest["operation"],\n input: unknown,\n): Promise<T> {\n let response: unknown;\n try {\n response = await binding.invoke({\n protocolVersion: MARINA_RUNTIME_PROTOCOL_VERSION,\n requestId: crypto.randomUUID(),\n service,\n operation,\n input,\n });\n } catch (error) {\n if (error instanceof MarinaRuntimeError) throw error;\n throw new MarinaRuntimeError({\n code: "UNAVAILABLE",\n message: "Marina runtime is unavailable",\n retryable: true,\n });\n }\n if (!isRuntimeResponse(response)) {\n throw new MarinaRuntimeError({\n code: "INTERNAL",\n message: "Marina runtime returned an invalid response",\n retryable: false,\n });\n }\n if (!response.ok) throw new MarinaRuntimeError(response.error);\n return response.value as T;\n}\n',
|
|
1911
|
+
"jobs.ts": 'import type { JobEnqueueResult, JobStatus, JobValue, MarinaRuntimeBinding } from "./contract.js";\nimport { MAX_JOB_INPUT_BYTES, MAX_JOB_VALUE_DEPTH } from "./contract.js";\nimport { MarinaRuntimeError } from "./error.js";\nimport { invokeRuntime } from "./invoke.js";\n\nconst encoder = new TextEncoder();\nconst JOB_NAME = /^[A-Za-z0-9][A-Za-z0-9._-]{0,79}$/;\nconst RUN_ID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;\n\nfunction invalid(message: string): never {\n throw new MarinaRuntimeError({ code: "INVALID_INPUT", message, retryable: false });\n}\n\nexport function assertJobName(value: unknown): string {\n if (typeof value !== "string" || !JOB_NAME.test(value)) invalid("invalid job name");\n return value as string;\n}\n\nexport function assertJobValue(value: unknown): JobValue {\n const seen = new Set<object>();\n const visit = (current: unknown, depth: number): void => {\n if (depth > MAX_JOB_VALUE_DEPTH) invalid("job input is nested too deeply");\n if (current === null || typeof current === "string" || typeof current === "boolean") return;\n if (typeof current === "number") {\n if (!Number.isFinite(current)) invalid("job input numbers must be finite");\n return;\n }\n if (!current || typeof current !== "object") invalid("job input must be JSON compatible");\n if (seen.has(current as object)) invalid("job input must not contain cycles");\n seen.add(current as object);\n if (Array.isArray(current)) {\n for (const item of current) visit(item, depth + 1);\n } else {\n if (Object.getPrototypeOf(current) !== Object.prototype)\n invalid("job input must contain plain objects");\n for (const [key, item] of Object.entries(current)) {\n if (key === "__proto__" || key === "constructor" || key === "prototype")\n invalid("job input contains an unsafe key");\n visit(item, depth + 1);\n }\n }\n seen.delete(current as object);\n };\n visit(value, 0);\n let serialized: string;\n try {\n serialized = JSON.stringify(value);\n } catch {\n invalid("job input must be JSON compatible");\n }\n if (serialized === undefined || encoder.encode(serialized).byteLength > MAX_JOB_INPUT_BYTES)\n invalid(`job input must not exceed ${String(MAX_JOB_INPUT_BYTES)} bytes`);\n return value as JobValue;\n}\n\nexport interface JobEnqueueOptions {\n dedupeKey?: string;\n}\n\nexport interface MarinaJobs {\n enqueue(name: string, input: JobValue, options?: JobEnqueueOptions): Promise<JobEnqueueResult>;\n get(runId: string): Promise<JobStatus>;\n}\n\nexport function createJobs(binding: MarinaRuntimeBinding): MarinaJobs {\n return {\n enqueue(name, input, options = {}) {\n const dedupeKey = options.dedupeKey;\n if (\n dedupeKey !== undefined &&\n (typeof dedupeKey !== "string" || dedupeKey.length < 1 || dedupeKey.length > 200)\n )\n invalid("job dedupe key must be between 1 and 200 characters");\n return invokeRuntime(binding, "jobs", "enqueue", {\n name: assertJobName(name),\n input: assertJobValue(input),\n ...(dedupeKey === undefined ? {} : { dedupeKey }),\n });\n },\n get(runId) {\n if (!RUN_ID.test(runId)) invalid("invalid job run id");\n return invokeRuntime(binding, "jobs", "get", { runId });\n },\n };\n}\n',
|
|
1912
|
+
"storage.ts": 'import type {\n MarinaRuntimeBinding,\n StorageListInput,\n StorageListResult,\n StorageObject,\n StorageObjectMetadata,\n StoragePutInput,\n} from "./contract.js";\nimport { MAX_STORAGE_KEY_BYTES, MAX_STORAGE_OBJECT_BYTES } from "./contract.js";\nimport { MarinaRuntimeError } from "./error.js";\nimport { invokeRuntime } from "./invoke.js";\n\nconst MAX_CONTENT_TYPE_LENGTH = 200;\nconst MAX_METADATA_ENTRIES = 32;\nconst MAX_METADATA_KEY_LENGTH = 128;\nconst MAX_METADATA_VALUE_LENGTH = 1024;\nconst MAX_LIST_LIMIT = 1000;\nconst encoder = new TextEncoder();\n\nfunction invalid(message: string): never {\n throw new MarinaRuntimeError({ code: "INVALID_INPUT", message, retryable: false });\n}\n\n/** Validate a logical app key. The broker adds the organization/app prefix. */\nexport function assertStorageKey(\n key: string,\n options: { allowEmpty?: boolean; allowTrailingSlash?: boolean } = {},\n): string {\n if (typeof key !== "string") invalid("storage key must be a string");\n if (key.length === 0) {\n if (options.allowEmpty) return key;\n invalid("storage key must not be empty");\n }\n if (encoder.encode(key).length > MAX_STORAGE_KEY_BYTES)\n invalid(`storage key must not exceed ${String(MAX_STORAGE_KEY_BYTES)} bytes`);\n if (key.startsWith("/")) invalid("storage key must be relative");\n if (key.endsWith("/") && !options.allowTrailingSlash)\n invalid("storage key must identify an object, not a directory");\n if (key.includes("\\\\")) invalid("storage key must use forward slashes");\n if (/\\p{Cc}/u.test(key)) invalid("storage key must not contain control characters");\n const segments = key.endsWith("/") ? key.slice(0, -1).split("/") : key.split("/");\n if (segments.some((segment) => segment === "" || segment === "." || segment === ".."))\n invalid("storage key contains an invalid path segment");\n return key;\n}\n\nfunction validateMetadata(metadata: Record<string, string> | undefined): Record<string, string> {\n if (!metadata) return {};\n const entries = Object.entries(metadata);\n if (entries.length > MAX_METADATA_ENTRIES)\n invalid(`storage metadata may contain at most ${String(MAX_METADATA_ENTRIES)} entries`);\n const normalized: Record<string, string> = {};\n for (const [key, value] of entries) {\n if (!key || key.length > MAX_METADATA_KEY_LENGTH)\n invalid(`storage metadata keys must be 1-${String(MAX_METADATA_KEY_LENGTH)} characters`);\n if (typeof value !== "string" || value.length > MAX_METADATA_VALUE_LENGTH)\n invalid(\n `storage metadata values must be strings no longer than ${String(MAX_METADATA_VALUE_LENGTH)} characters`,\n );\n Object.defineProperty(normalized, key, {\n value,\n enumerable: true,\n configurable: true,\n writable: true,\n });\n }\n return normalized;\n}\n\nexport interface StoragePutOptions {\n contentType?: string;\n metadata?: Record<string, string>;\n}\n\nexport interface StorageListOptions {\n prefix?: string;\n cursor?: string;\n limit?: number;\n}\n\nexport interface MarinaStorage {\n put(\n key: string,\n body: StoragePutInput["body"],\n options?: StoragePutOptions,\n ): Promise<StorageObjectMetadata>;\n get(key: string): Promise<StorageObject | null>;\n delete(key: string): Promise<void>;\n list(options?: StorageListOptions): Promise<StorageListResult>;\n}\n\nexport function createStorage(binding: MarinaRuntimeBinding): MarinaStorage {\n return {\n async put(key, body, options = {}) {\n assertStorageKey(key);\n if (\n typeof body !== "string" &&\n !(body instanceof Uint8Array) &&\n !(body instanceof ArrayBuffer)\n )\n invalid("storage body must be a string, Uint8Array, or ArrayBuffer");\n const bodyBytes =\n typeof body === "string"\n ? encoder.encode(body).byteLength\n : body instanceof Uint8Array\n ? body.byteLength\n : body.byteLength;\n if (bodyBytes > MAX_STORAGE_OBJECT_BYTES) {\n throw new MarinaRuntimeError({\n code: "QUOTA_EXCEEDED",\n message: `storage objects may not exceed ${String(MAX_STORAGE_OBJECT_BYTES)} bytes`,\n retryable: false,\n });\n }\n if (options.contentType && options.contentType.length > MAX_CONTENT_TYPE_LENGTH)\n invalid(\n `storage content type must not exceed ${String(MAX_CONTENT_TYPE_LENGTH)} characters`,\n );\n return invokeRuntime<StorageObjectMetadata>(binding, "storage", "put", {\n key,\n body,\n ...(options.contentType ? { contentType: options.contentType } : {}),\n metadata: validateMetadata(options.metadata),\n } satisfies StoragePutInput);\n },\n\n async get(key) {\n assertStorageKey(key);\n return invokeRuntime<StorageObject | null>(binding, "storage", "get", { key });\n },\n\n async delete(key) {\n assertStorageKey(key);\n await invokeRuntime<null>(binding, "storage", "delete", { key });\n },\n\n async list(options = {}) {\n const input: StorageListInput = {};\n if (options.prefix !== undefined)\n input.prefix = assertStorageKey(options.prefix, {\n allowEmpty: true,\n allowTrailingSlash: true,\n });\n if (options.cursor !== undefined) {\n if (!options.cursor) invalid("storage cursor must not be empty");\n input.cursor = options.cursor;\n }\n if (options.limit !== undefined) {\n if (\n !Number.isSafeInteger(options.limit) ||\n options.limit < 1 ||\n options.limit > MAX_LIST_LIMIT\n )\n invalid(`storage list limit must be between 1 and ${String(MAX_LIST_LIMIT)}`);\n input.limit = options.limit;\n }\n return invokeRuntime<StorageListResult>(binding, "storage", "list", input);\n },\n };\n}\n'
|
|
1913
|
+
};
|
|
1914
|
+
}
|
|
1915
|
+
});
|
|
1916
|
+
|
|
1917
|
+
// src/dev/host.ts
|
|
1918
|
+
import { createServer as createServer2 } from "node:http";
|
|
1919
|
+
import { mkdirSync as mkdirSync4, watch, writeFileSync as writeFileSync3 } from "node:fs";
|
|
1920
|
+
import { dirname as dirname2, join as join6, resolve } from "node:path";
|
|
1921
|
+
import { pathToFileURL } from "node:url";
|
|
1922
|
+
import { Readable } from "node:stream";
|
|
1923
|
+
function entrypointSource(projectDir, entrypoint) {
|
|
1924
|
+
return [
|
|
1925
|
+
`import app from ${JSON.stringify(resolve(projectDir, entrypoint))};`,
|
|
1926
|
+
["import { adaptApp } ", ["fr", "om"].join(""), ' "./runtime/index.ts";'].join(""),
|
|
1927
|
+
"export default adaptApp(app);",
|
|
1928
|
+
""
|
|
1929
|
+
].join("\n");
|
|
1930
|
+
}
|
|
1931
|
+
async function bundle(options) {
|
|
1932
|
+
for (const [name, source] of Object.entries(DEV_RUNTIME_FILES)) {
|
|
1933
|
+
const path = join6(options.buildDir, "runtime", name);
|
|
1934
|
+
mkdirSync4(dirname2(path), { recursive: true });
|
|
1935
|
+
writeFileSync3(path, source);
|
|
1936
|
+
}
|
|
1937
|
+
const entry = join6(options.buildDir, "entrypoint.ts");
|
|
1938
|
+
writeFileSync3(entry, entrypointSource(options.projectDir, options.manifest.entrypoint));
|
|
1939
|
+
const outfile = join6(options.buildDir, "app.mjs");
|
|
1940
|
+
let esbuild;
|
|
1941
|
+
try {
|
|
1942
|
+
esbuild = await import("esbuild");
|
|
1943
|
+
} catch {
|
|
1944
|
+
throw new Error("esbuild could not load \u2014 reinstall the Marina CLI");
|
|
1945
|
+
}
|
|
1946
|
+
await esbuild.build({
|
|
1947
|
+
entryPoints: [entry],
|
|
1948
|
+
bundle: true,
|
|
1949
|
+
format: "esm",
|
|
1950
|
+
platform: "browser",
|
|
1951
|
+
conditions: ["workerd", "worker", "browser"],
|
|
1952
|
+
target: "es2022",
|
|
1953
|
+
external: ["cloudflare:*"],
|
|
1954
|
+
alias: {
|
|
1955
|
+
"@marina/runtime": join6(options.buildDir, "runtime", "index.ts"),
|
|
1956
|
+
"marina:runtime": join6(options.buildDir, "runtime", "index.ts")
|
|
1957
|
+
},
|
|
1958
|
+
absWorkingDir: options.projectDir,
|
|
1959
|
+
outfile,
|
|
1960
|
+
logLevel: "silent"
|
|
1961
|
+
});
|
|
1962
|
+
const module = await import(`${pathToFileURL(outfile).href}?v=${String(Date.now())}`);
|
|
1963
|
+
if (typeof module.default?.fetch !== "function") {
|
|
1964
|
+
throw new Error("the app entrypoint does not export a Marina app (use defineApp)");
|
|
1965
|
+
}
|
|
1966
|
+
return module.default;
|
|
1967
|
+
}
|
|
1968
|
+
function toRequest(req, port, identity) {
|
|
1969
|
+
const url = new URL(req.url ?? "/", `http://localhost:${String(port)}`);
|
|
1970
|
+
const headers = new Headers();
|
|
1971
|
+
for (const [name, value] of Object.entries(req.headers)) {
|
|
1972
|
+
if (typeof value === "string") headers.set(name, value);
|
|
1973
|
+
else if (Array.isArray(value)) headers.set(name, value.join(", "));
|
|
1974
|
+
}
|
|
1975
|
+
headers.set("x-platform-user-id", identity.userId);
|
|
1976
|
+
headers.set("x-platform-workspace-id", identity.workspaceId);
|
|
1977
|
+
headers.set("x-platform-capability-session", "marina-local-dev");
|
|
1978
|
+
const method = req.method ?? "GET";
|
|
1979
|
+
const body = method === "GET" || method === "HEAD" ? void 0 : Readable.toWeb(req);
|
|
1980
|
+
return new Request(url, {
|
|
1981
|
+
method,
|
|
1982
|
+
headers,
|
|
1983
|
+
...body ? { body, duplex: "half" } : {}
|
|
1984
|
+
});
|
|
1985
|
+
}
|
|
1986
|
+
async function writeResponse(response, res, chrome) {
|
|
1987
|
+
const headers = {};
|
|
1988
|
+
response.headers.forEach((value, name) => {
|
|
1989
|
+
headers[name] = value;
|
|
1990
|
+
});
|
|
1991
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
1992
|
+
if (contentType.includes("text/html")) {
|
|
1993
|
+
const html = injectDevChrome(await response.text(), chrome);
|
|
1994
|
+
delete headers["content-length"];
|
|
1995
|
+
res.writeHead(response.status, headers);
|
|
1996
|
+
res.end(html);
|
|
1997
|
+
return;
|
|
1998
|
+
}
|
|
1999
|
+
res.writeHead(response.status, headers);
|
|
2000
|
+
if (!response.body) {
|
|
2001
|
+
res.end();
|
|
2002
|
+
return;
|
|
2003
|
+
}
|
|
2004
|
+
const reader = response.body.getReader();
|
|
2005
|
+
for (; ; ) {
|
|
2006
|
+
const next = await reader.read();
|
|
2007
|
+
if (next.done) break;
|
|
2008
|
+
res.write(next.value);
|
|
2009
|
+
}
|
|
2010
|
+
res.end();
|
|
2011
|
+
}
|
|
2012
|
+
async function startDevHost(options) {
|
|
2013
|
+
mkdirSync4(options.buildDir, { recursive: true });
|
|
2014
|
+
let app = await bundle(options);
|
|
2015
|
+
const chrome = devChromeSnippet({
|
|
2016
|
+
appName: options.identity.appName,
|
|
2017
|
+
userLabel: options.identity.userLabel
|
|
2018
|
+
});
|
|
2019
|
+
const fetchApp = (request2) => Promise.resolve(
|
|
2020
|
+
app.fetch(request2, { MARINA: options.binding }, { waitUntil: () => void 0 })
|
|
2021
|
+
);
|
|
2022
|
+
const server = createServer2((req, res) => {
|
|
2023
|
+
fetchApp(toRequest(req, options.port, options.identity)).then((response) => writeResponse(response, res, chrome)).catch((error) => {
|
|
2024
|
+
res.writeHead(500, { "content-type": "text/plain" });
|
|
2025
|
+
res.end(`marina dev: ${error.message}`);
|
|
2026
|
+
});
|
|
2027
|
+
});
|
|
2028
|
+
await new Promise((ready, failed) => {
|
|
2029
|
+
server.once("error", failed);
|
|
2030
|
+
server.listen(options.port, () => {
|
|
2031
|
+
ready();
|
|
2032
|
+
});
|
|
2033
|
+
});
|
|
2034
|
+
let rebuildTimer = null;
|
|
2035
|
+
const watchers = [];
|
|
2036
|
+
const scheduleRebuild = () => {
|
|
2037
|
+
if (rebuildTimer) clearTimeout(rebuildTimer);
|
|
2038
|
+
rebuildTimer = setTimeout(() => {
|
|
2039
|
+
bundle(options).then((next) => {
|
|
2040
|
+
app = next;
|
|
2041
|
+
options.log("reloaded");
|
|
2042
|
+
}).catch((error) => {
|
|
2043
|
+
options.log(`build failed \u2014 still serving the previous build: ${error.message}`);
|
|
2044
|
+
});
|
|
2045
|
+
}, 150);
|
|
2046
|
+
};
|
|
2047
|
+
try {
|
|
2048
|
+
const watcher = watch(options.projectDir, { recursive: true }, (_event, file) => {
|
|
2049
|
+
const name = String(file ?? "");
|
|
2050
|
+
if (name.startsWith(".marina") || name.startsWith("node_modules") || name.startsWith(".git")) {
|
|
2051
|
+
return;
|
|
2052
|
+
}
|
|
2053
|
+
scheduleRebuild();
|
|
2054
|
+
});
|
|
2055
|
+
watchers.push(watcher);
|
|
2056
|
+
} catch {
|
|
2057
|
+
options.log("file watching is unavailable \u2014 restart marina dev to pick up changes");
|
|
2058
|
+
}
|
|
2059
|
+
return {
|
|
2060
|
+
port: options.port,
|
|
2061
|
+
fetchApp,
|
|
2062
|
+
rebuild: async () => {
|
|
2063
|
+
app = await bundle(options);
|
|
2064
|
+
},
|
|
2065
|
+
close: async () => {
|
|
2066
|
+
for (const watcher of watchers) watcher.close();
|
|
2067
|
+
if (rebuildTimer) clearTimeout(rebuildTimer);
|
|
2068
|
+
await new Promise((done) => {
|
|
2069
|
+
server.close(() => {
|
|
2070
|
+
done();
|
|
2071
|
+
});
|
|
2072
|
+
});
|
|
2073
|
+
}
|
|
2074
|
+
};
|
|
2075
|
+
}
|
|
2076
|
+
var init_host = __esm({
|
|
2077
|
+
"src/dev/host.ts"() {
|
|
2078
|
+
"use strict";
|
|
2079
|
+
init_chrome();
|
|
2080
|
+
init_embedded_runtime();
|
|
2081
|
+
}
|
|
2082
|
+
});
|
|
2083
|
+
|
|
2084
|
+
// src/dev/cron.ts
|
|
2085
|
+
function matchesField(field, value, minimum, maximum) {
|
|
2086
|
+
return field.split(",").some((part) => {
|
|
2087
|
+
const step = /^(.+)\/(\d+)$/.exec(part);
|
|
2088
|
+
if (step?.[1] !== void 0 && step[2] !== void 0) {
|
|
2089
|
+
const size = Number(step[2]);
|
|
2090
|
+
if (!Number.isInteger(size) || size < 1) return false;
|
|
2091
|
+
const base = step[1];
|
|
2092
|
+
const [start, end] = base === "*" ? [minimum, maximum] : /^(\d+)-(\d+)$/.exec(base)?.slice(1).map(Number) ?? [Number(base), maximum];
|
|
2093
|
+
if (start === void 0 || Number.isNaN(start)) return false;
|
|
2094
|
+
return value >= start && value <= (end ?? maximum) && (value - start) % size === 0;
|
|
2095
|
+
}
|
|
2096
|
+
if (part === "*") return true;
|
|
2097
|
+
const range = /^(\d+)-(\d+)$/.exec(part);
|
|
2098
|
+
if (range) return value >= Number(range[1]) && value <= Number(range[2]);
|
|
2099
|
+
return Number(part) === value;
|
|
2100
|
+
});
|
|
2101
|
+
}
|
|
2102
|
+
function cronMatches(schedule, at) {
|
|
2103
|
+
const fields = schedule.trim().split(/\s+/);
|
|
2104
|
+
if (fields.length !== 5) return false;
|
|
2105
|
+
const [minute, hour, dayOfMonth, month, dayOfWeek] = fields;
|
|
2106
|
+
return matchesField(minute, at.getUTCMinutes(), 0, 59) && matchesField(hour, at.getUTCHours(), 0, 23) && matchesField(dayOfMonth, at.getUTCDate(), 1, 31) && matchesField(month, at.getUTCMonth() + 1, 1, 12) && matchesField(dayOfWeek, at.getUTCDay(), 0, 6);
|
|
2107
|
+
}
|
|
2108
|
+
var init_cron = __esm({
|
|
2109
|
+
"src/dev/cron.ts"() {
|
|
2110
|
+
"use strict";
|
|
2111
|
+
}
|
|
2112
|
+
});
|
|
2113
|
+
|
|
2114
|
+
// src/dev/jobs.ts
|
|
2115
|
+
import { randomUUID } from "node:crypto";
|
|
2116
|
+
var JOB_EXECUTION_PATH, DevJobRunner;
|
|
2117
|
+
var init_jobs = __esm({
|
|
2118
|
+
"src/dev/jobs.ts"() {
|
|
2119
|
+
"use strict";
|
|
2120
|
+
init_cron();
|
|
2121
|
+
JOB_EXECUTION_PATH = "/.marina/runtime/jobs/execute";
|
|
2122
|
+
DevJobRunner = class {
|
|
2123
|
+
runs = /* @__PURE__ */ new Map();
|
|
2124
|
+
deduped = /* @__PURE__ */ new Map();
|
|
2125
|
+
token = randomUUID();
|
|
2126
|
+
fetchApp = null;
|
|
2127
|
+
timer = null;
|
|
2128
|
+
lastScheduledMinute = "";
|
|
2129
|
+
manifest;
|
|
2130
|
+
identity;
|
|
2131
|
+
log;
|
|
2132
|
+
constructor(manifest, identity, log) {
|
|
2133
|
+
this.manifest = manifest;
|
|
2134
|
+
this.identity = identity;
|
|
2135
|
+
this.log = log;
|
|
2136
|
+
}
|
|
2137
|
+
attachApp(fetchApp) {
|
|
2138
|
+
this.fetchApp = fetchApp;
|
|
2139
|
+
}
|
|
2140
|
+
authorizeJob(request2) {
|
|
2141
|
+
void request2.body;
|
|
2142
|
+
return request2.token === this.token;
|
|
2143
|
+
}
|
|
2144
|
+
enqueue(input) {
|
|
2145
|
+
const definition = this.manifest.jobs[input.name];
|
|
2146
|
+
if (!definition) {
|
|
2147
|
+
throw new Error(`job "${input.name}" is not defined in marina.json`);
|
|
2148
|
+
}
|
|
2149
|
+
if (input.dedupeKey) {
|
|
2150
|
+
const existing = this.deduped.get(`${input.name}:${input.dedupeKey}`);
|
|
2151
|
+
const run2 = existing ? this.runs.get(existing) : void 0;
|
|
2152
|
+
if (run2 && (run2.state === "queued" || run2.state === "running")) return run2;
|
|
2153
|
+
}
|
|
2154
|
+
const status2 = {
|
|
2155
|
+
id: randomUUID(),
|
|
2156
|
+
name: input.name,
|
|
2157
|
+
state: "queued",
|
|
2158
|
+
attempts: 0,
|
|
2159
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2160
|
+
};
|
|
2161
|
+
this.runs.set(status2.id, status2);
|
|
2162
|
+
if (input.dedupeKey) this.deduped.set(`${input.name}:${input.dedupeKey}`, status2.id);
|
|
2163
|
+
void this.execute(status2, definition.handler, input.input, "enqueue");
|
|
2164
|
+
return { ...status2 };
|
|
2165
|
+
}
|
|
2166
|
+
get(id) {
|
|
2167
|
+
const run2 = this.runs.get(id);
|
|
2168
|
+
return run2 ? { ...run2 } : null;
|
|
2169
|
+
}
|
|
2170
|
+
async execute(status2, handler, input, trigger, scheduledFor) {
|
|
2171
|
+
const fetchApp = this.fetchApp;
|
|
2172
|
+
if (!fetchApp) {
|
|
2173
|
+
status2.state = "failed";
|
|
2174
|
+
status2.finishedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2175
|
+
return;
|
|
2176
|
+
}
|
|
2177
|
+
status2.state = "running";
|
|
2178
|
+
status2.attempts += 1;
|
|
2179
|
+
status2.startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2180
|
+
const invocation = {
|
|
2181
|
+
runId: status2.id,
|
|
2182
|
+
workspaceId: this.identity.workspaceId,
|
|
2183
|
+
appId: this.identity.appId,
|
|
2184
|
+
artifactDigest: `sha256:${"0".repeat(64)}`,
|
|
2185
|
+
environment: "development",
|
|
2186
|
+
jobName: status2.name,
|
|
2187
|
+
handler,
|
|
2188
|
+
input,
|
|
2189
|
+
attempt: status2.attempts,
|
|
2190
|
+
trigger,
|
|
2191
|
+
...scheduledFor ? { scheduledFor } : {},
|
|
2192
|
+
capabilitySession: "marina-local-dev"
|
|
2193
|
+
};
|
|
2194
|
+
try {
|
|
2195
|
+
const response = await fetchApp(
|
|
2196
|
+
new Request(`http://marina.dev.local${JOB_EXECUTION_PATH}`, {
|
|
2197
|
+
method: "POST",
|
|
2198
|
+
headers: { "x-marina-job-token": this.token, "content-type": "application/json" },
|
|
2199
|
+
body: JSON.stringify(invocation)
|
|
2200
|
+
})
|
|
2201
|
+
);
|
|
2202
|
+
status2.state = response.ok ? "succeeded" : "failed";
|
|
2203
|
+
if (!response.ok) {
|
|
2204
|
+
this.log(`job ${status2.name} failed (${String(response.status)})`);
|
|
2205
|
+
}
|
|
2206
|
+
} catch (error) {
|
|
2207
|
+
status2.state = "failed";
|
|
2208
|
+
this.log(`job ${status2.name} threw: ${error.message}`);
|
|
2209
|
+
}
|
|
2210
|
+
status2.finishedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2211
|
+
}
|
|
2212
|
+
startSchedules() {
|
|
2213
|
+
if (this.timer) return;
|
|
2214
|
+
this.timer = setInterval(() => {
|
|
2215
|
+
const now = /* @__PURE__ */ new Date();
|
|
2216
|
+
const minute = now.toISOString().slice(0, 16);
|
|
2217
|
+
if (minute === this.lastScheduledMinute) return;
|
|
2218
|
+
this.lastScheduledMinute = minute;
|
|
2219
|
+
for (const [name, definition] of Object.entries(this.manifest.jobs)) {
|
|
2220
|
+
if (definition.schedule && cronMatches(definition.schedule, now)) {
|
|
2221
|
+
this.log(`schedule fired: ${name}`);
|
|
2222
|
+
try {
|
|
2223
|
+
const status2 = this.enqueue({ name, input: {} });
|
|
2224
|
+
const run2 = this.runs.get(status2.id);
|
|
2225
|
+
if (run2) run2.state = run2.state === "queued" ? "queued" : run2.state;
|
|
2226
|
+
} catch (error) {
|
|
2227
|
+
this.log(`schedule ${name} failed to enqueue: ${error.message}`);
|
|
2228
|
+
}
|
|
2229
|
+
}
|
|
2230
|
+
}
|
|
2231
|
+
}, 15e3);
|
|
2232
|
+
this.timer.unref?.();
|
|
2233
|
+
}
|
|
2234
|
+
stop() {
|
|
2235
|
+
if (this.timer) clearInterval(this.timer);
|
|
2236
|
+
this.timer = null;
|
|
2237
|
+
}
|
|
2238
|
+
};
|
|
2239
|
+
}
|
|
2240
|
+
});
|
|
2241
|
+
|
|
2242
|
+
// src/dev/manifest.ts
|
|
2243
|
+
import { existsSync as existsSync6, readFileSync as readFileSync6 } from "node:fs";
|
|
2244
|
+
import { join as join7 } from "node:path";
|
|
2245
|
+
function fail(message) {
|
|
2246
|
+
throw new Error(`marina.json: ${message}`);
|
|
2247
|
+
}
|
|
2248
|
+
function readDevManifest(dir) {
|
|
2249
|
+
const path = join7(dir, "marina.json");
|
|
2250
|
+
if (!existsSync6(path)) {
|
|
2251
|
+
fail("not found \u2014 marina dev runs from a project with a marina.json");
|
|
2252
|
+
}
|
|
2253
|
+
let value;
|
|
2254
|
+
try {
|
|
2255
|
+
value = JSON.parse(readFileSync6(path, "utf8"));
|
|
2256
|
+
} catch {
|
|
2257
|
+
fail("is not valid JSON");
|
|
2258
|
+
}
|
|
2259
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) fail("must contain an object");
|
|
2260
|
+
const manifest = value;
|
|
2261
|
+
if (typeof manifest.entrypoint !== "string" || manifest.entrypoint.length === 0) {
|
|
2262
|
+
fail('needs an "entrypoint" \u2014 static projects have no server to develop against');
|
|
2263
|
+
}
|
|
2264
|
+
const runtime = manifest.runtime ?? {};
|
|
2265
|
+
const capabilities = Array.isArray(manifest.capabilities) ? manifest.capabilities.filter((name) => typeof name === "string") : [];
|
|
2266
|
+
const connections = {};
|
|
2267
|
+
if (manifest.connections !== void 0) {
|
|
2268
|
+
if (!manifest.connections || typeof manifest.connections !== "object" || Array.isArray(manifest.connections)) {
|
|
2269
|
+
fail('"connections" must map connector names to declarations');
|
|
2270
|
+
}
|
|
2271
|
+
for (const [connector, declared] of Object.entries(manifest.connections)) {
|
|
2272
|
+
if (!CONNECTION_ID.test(connector)) fail(`"${connector}" is not a connector name`);
|
|
2273
|
+
if (!Array.isArray(declared)) fail(`connections.${connector} must be an array`);
|
|
2274
|
+
connections[connector] = declared.map((entry, index) => {
|
|
2275
|
+
const binding = entry;
|
|
2276
|
+
if (typeof binding.connection !== "string" || !CONNECTION_ID.test(binding.connection)) {
|
|
2277
|
+
fail(`connections.${connector}[${String(index)}] needs an explicit "connection" id`);
|
|
2278
|
+
}
|
|
2279
|
+
const operations = Array.isArray(binding.capabilities) ? binding.capabilities : [];
|
|
2280
|
+
if (!operations.every((op) => typeof op === "string" && NAME.test(op))) {
|
|
2281
|
+
fail(`connections.${connector}[${String(index)}] has an invalid operation name`);
|
|
2282
|
+
}
|
|
2283
|
+
return { connection: binding.connection, capabilities: operations };
|
|
2284
|
+
});
|
|
2285
|
+
}
|
|
2286
|
+
}
|
|
2287
|
+
const jobs = {};
|
|
2288
|
+
if (manifest.jobs && typeof manifest.jobs === "object" && !Array.isArray(manifest.jobs)) {
|
|
2289
|
+
for (const [name, definition] of Object.entries(manifest.jobs)) {
|
|
2290
|
+
const job = definition;
|
|
2291
|
+
if (typeof job.handler === "string") {
|
|
2292
|
+
jobs[name] = {
|
|
2293
|
+
handler: job.handler,
|
|
2294
|
+
...typeof job.schedule === "string" ? { schedule: job.schedule } : {}
|
|
2295
|
+
};
|
|
2296
|
+
}
|
|
2297
|
+
}
|
|
2298
|
+
}
|
|
2299
|
+
return {
|
|
2300
|
+
...typeof manifest.name === "string" ? { name: manifest.name } : {},
|
|
2301
|
+
entrypoint: manifest.entrypoint,
|
|
2302
|
+
runtime,
|
|
2303
|
+
capabilities,
|
|
2304
|
+
connections,
|
|
2305
|
+
jobs
|
|
2306
|
+
};
|
|
2307
|
+
}
|
|
2308
|
+
var CONNECTION_ID, NAME;
|
|
2309
|
+
var init_manifest = __esm({
|
|
2310
|
+
"src/dev/manifest.ts"() {
|
|
2311
|
+
"use strict";
|
|
2312
|
+
CONNECTION_ID = /^[a-z][a-z0-9_]{0,39}$/;
|
|
2313
|
+
NAME = /^[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)+$/;
|
|
2314
|
+
}
|
|
2315
|
+
});
|
|
76
2316
|
|
|
77
|
-
// src/
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
2317
|
+
// src/dev/storage.ts
|
|
2318
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
2319
|
+
import { mkdirSync as mkdirSync5, readdirSync as readdirSync3, readFileSync as readFileSync7, rmSync, statSync, writeFileSync as writeFileSync4 } from "node:fs";
|
|
2320
|
+
import { dirname as dirname3, join as join8, normalize, sep } from "node:path";
|
|
2321
|
+
var MAX_LIST_LIMIT, LocalStorage;
|
|
2322
|
+
var init_storage = __esm({
|
|
2323
|
+
"src/dev/storage.ts"() {
|
|
2324
|
+
"use strict";
|
|
2325
|
+
MAX_LIST_LIMIT = 1e3;
|
|
2326
|
+
LocalStorage = class {
|
|
2327
|
+
objects;
|
|
2328
|
+
sidecars;
|
|
2329
|
+
constructor(root) {
|
|
2330
|
+
this.objects = join8(root, "objects");
|
|
2331
|
+
this.sidecars = join8(root, "meta");
|
|
2332
|
+
mkdirSync5(this.objects, { recursive: true });
|
|
2333
|
+
mkdirSync5(this.sidecars, { recursive: true });
|
|
2334
|
+
}
|
|
2335
|
+
/** Keys were validated by the runtime client; this guards the filesystem
|
|
2336
|
+
* anyway so a hand-crafted invoke cannot escape the store. */
|
|
2337
|
+
objectPath(base, key) {
|
|
2338
|
+
const path = normalize(join8(base, key));
|
|
2339
|
+
if (path !== base && !path.startsWith(base + sep)) {
|
|
2340
|
+
throw new Error("storage key escapes the local store");
|
|
2341
|
+
}
|
|
2342
|
+
return path;
|
|
2343
|
+
}
|
|
2344
|
+
put(input) {
|
|
2345
|
+
const body = typeof input.body === "string" ? Buffer.from(input.body, "utf8") : input.body instanceof ArrayBuffer ? Buffer.from(input.body) : Buffer.from(input.body);
|
|
2346
|
+
const sidecar = {
|
|
2347
|
+
...input.contentType ? { contentType: input.contentType } : {},
|
|
2348
|
+
metadata: input.metadata ?? {},
|
|
2349
|
+
etag: createHash2("sha256").update(body).digest("hex").slice(0, 32),
|
|
2350
|
+
uploadedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2351
|
+
size: body.byteLength
|
|
2352
|
+
};
|
|
2353
|
+
const objectPath = this.objectPath(this.objects, input.key);
|
|
2354
|
+
mkdirSync5(dirname3(objectPath), { recursive: true });
|
|
2355
|
+
writeFileSync4(objectPath, body);
|
|
2356
|
+
const sidecarPath = this.objectPath(this.sidecars, `${input.key}.json`);
|
|
2357
|
+
mkdirSync5(dirname3(sidecarPath), { recursive: true });
|
|
2358
|
+
writeFileSync4(sidecarPath, JSON.stringify(sidecar));
|
|
2359
|
+
return { key: input.key, ...sidecar };
|
|
2360
|
+
}
|
|
2361
|
+
get(key) {
|
|
2362
|
+
const metadata = this.head(key);
|
|
2363
|
+
if (!metadata) return null;
|
|
2364
|
+
const body = readFileSync7(this.objectPath(this.objects, key));
|
|
2365
|
+
return { ...metadata, body: new Uint8Array(body) };
|
|
2366
|
+
}
|
|
2367
|
+
head(key) {
|
|
2368
|
+
try {
|
|
2369
|
+
const sidecar = JSON.parse(
|
|
2370
|
+
readFileSync7(this.objectPath(this.sidecars, `${key}.json`), "utf8")
|
|
2371
|
+
);
|
|
2372
|
+
return { key, ...sidecar };
|
|
2373
|
+
} catch {
|
|
2374
|
+
return null;
|
|
2375
|
+
}
|
|
2376
|
+
}
|
|
2377
|
+
delete(key) {
|
|
2378
|
+
rmSync(this.objectPath(this.objects, key), { force: true });
|
|
2379
|
+
rmSync(this.objectPath(this.sidecars, `${key}.json`), { force: true });
|
|
2380
|
+
}
|
|
2381
|
+
list(input = {}) {
|
|
2382
|
+
const limit = Math.min(input.limit ?? MAX_LIST_LIMIT, MAX_LIST_LIMIT);
|
|
2383
|
+
const keys = [];
|
|
2384
|
+
const walk = (dir, prefix) => {
|
|
2385
|
+
let entries;
|
|
2386
|
+
try {
|
|
2387
|
+
entries = readdirSync3(dir);
|
|
2388
|
+
} catch {
|
|
2389
|
+
return;
|
|
2390
|
+
}
|
|
2391
|
+
for (const entry of entries.toSorted()) {
|
|
2392
|
+
const path = join8(dir, entry);
|
|
2393
|
+
if (statSync(path).isDirectory()) walk(path, `${prefix}${entry}/`);
|
|
2394
|
+
else if (entry.endsWith(".json")) keys.push(`${prefix}${entry.slice(0, -5)}`);
|
|
2395
|
+
}
|
|
2396
|
+
};
|
|
2397
|
+
walk(this.sidecars, "");
|
|
2398
|
+
const filtered = keys.filter((key) => input.prefix ? key.startsWith(input.prefix) : true).filter((key) => input.cursor ? key > input.cursor : true);
|
|
2399
|
+
const page2 = filtered.slice(0, limit);
|
|
2400
|
+
const truncated = filtered.length > page2.length;
|
|
2401
|
+
const objects = page2.map((key) => this.head(key)).filter((object) => object !== null);
|
|
2402
|
+
return {
|
|
2403
|
+
objects,
|
|
2404
|
+
...truncated && page2.length > 0 ? { cursor: page2[page2.length - 1] } : {},
|
|
2405
|
+
truncated
|
|
2406
|
+
};
|
|
2407
|
+
}
|
|
2408
|
+
};
|
|
86
2409
|
}
|
|
87
|
-
};
|
|
88
|
-
|
|
2410
|
+
});
|
|
2411
|
+
|
|
2412
|
+
// src/dev/index.ts
|
|
2413
|
+
var dev_exports = {};
|
|
2414
|
+
__export(dev_exports, {
|
|
2415
|
+
runDev: () => runDev
|
|
2416
|
+
});
|
|
2417
|
+
import { existsSync as existsSync7 } from "node:fs";
|
|
2418
|
+
import { join as join9, resolve as resolve2 } from "node:path";
|
|
2419
|
+
async function runDev(options) {
|
|
89
2420
|
const token = getToken();
|
|
90
|
-
if (!token)
|
|
91
|
-
|
|
92
|
-
...init,
|
|
93
|
-
headers: { authorization: `Bearer ${token}`, ...init?.headers }
|
|
94
|
-
});
|
|
95
|
-
const body = await res.json().catch(() => ({}));
|
|
96
|
-
if (!res.ok) {
|
|
97
|
-
throw new ApiError(
|
|
98
|
-
body.error?.code ?? "error",
|
|
99
|
-
body.error?.message ?? `request failed (${String(res.status)})`,
|
|
100
|
-
res.status
|
|
101
|
-
);
|
|
2421
|
+
if (!token) {
|
|
2422
|
+
throw new Error("not signed in \u2014 run `marina setup` first");
|
|
102
2423
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
res = await fetch(`${apiUrl()}/cli/auth/exchange`, {
|
|
109
|
-
method: "POST",
|
|
110
|
-
headers: { "content-type": "application/json" },
|
|
111
|
-
body: JSON.stringify({ code, code_verifier: codeVerifier }),
|
|
112
|
-
signal: AbortSignal.timeout(LOGIN_EXCHANGE_TIMEOUT_MS)
|
|
113
|
-
});
|
|
114
|
-
} catch (error) {
|
|
115
|
-
if (error.name === "TimeoutError") {
|
|
116
|
-
throw new ApiError(
|
|
117
|
-
"login_timeout",
|
|
118
|
-
"the login confirmation request timed out after 15 seconds \u2014 run `marina setup` to try again",
|
|
119
|
-
408
|
|
120
|
-
);
|
|
121
|
-
}
|
|
122
|
-
throw error;
|
|
2424
|
+
const projectDir = resolve2(options.dir ?? ".");
|
|
2425
|
+
const manifest = readDevManifest(projectDir);
|
|
2426
|
+
const port = options.port ? Number(options.port) : DEFAULT_PORT;
|
|
2427
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535) {
|
|
2428
|
+
throw new Error("--port must be a number between 1 and 65535");
|
|
123
2429
|
}
|
|
124
|
-
const
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
2430
|
+
const identity = await me();
|
|
2431
|
+
const appName = manifest.name ?? "Marina app";
|
|
2432
|
+
say(`${bold("marina dev")} \xB7 ${appName}`);
|
|
2433
|
+
const declared = Object.entries(manifest.connections).flatMap(
|
|
2434
|
+
([connector, bindings]) => bindings.map((binding2) => `${connector}/${binding2.connection}`)
|
|
2435
|
+
);
|
|
2436
|
+
if (declared.length > 0) {
|
|
2437
|
+
say(
|
|
2438
|
+
dim(
|
|
2439
|
+
`connections: ${declared.join(", ")} \u2014 bridged to ${apiUrl()} as ${identity.user.email}; grants are checked per call`
|
|
2440
|
+
)
|
|
130
2441
|
);
|
|
131
2442
|
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
form.set("source", "cli");
|
|
143
|
-
if (app) form.set("app", app);
|
|
144
|
-
const res = await request("/v1/deploys", {
|
|
145
|
-
method: "POST",
|
|
146
|
-
body: form
|
|
147
|
-
});
|
|
148
|
-
return res.deploy;
|
|
149
|
-
}
|
|
150
|
-
async function pollDeploy(id, onProgress = () => void 0) {
|
|
151
|
-
for (; ; ) {
|
|
152
|
-
const { deploy: deploy2 } = await request(`/v1/deploys/${id}`);
|
|
153
|
-
onProgress(deploy2);
|
|
154
|
-
if (deploy2.status !== "queued" && deploy2.status !== "building") return deploy2;
|
|
155
|
-
await new Promise((resolve2) => setTimeout(resolve2, 500));
|
|
2443
|
+
const devDir = join9(projectDir, ".marina", "dev");
|
|
2444
|
+
const storage = manifest.runtime.storage === "v1" ? new LocalStorage(join9(devDir, "storage")) : null;
|
|
2445
|
+
let database = null;
|
|
2446
|
+
if (manifest.runtime.db === "v1") {
|
|
2447
|
+
database = await LocalDatabase.open(join9(devDir, "db"));
|
|
2448
|
+
const applied = await database.applyMigrations(projectDir);
|
|
2449
|
+
const total = existsSync7(join9(projectDir, "marina", "migrations")) ? "" : " (no marina/migrations yet)";
|
|
2450
|
+
say(
|
|
2451
|
+
`${green("ok")} db \u2014 embedded Postgres ready, ${String(applied.length)} migrations applied${total}`
|
|
2452
|
+
);
|
|
156
2453
|
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
return res.versions;
|
|
171
|
-
}
|
|
172
|
-
async function listDeploys(app, limit = 10) {
|
|
173
|
-
const res = await request(
|
|
174
|
-
`/v1/apps/${encodeURIComponent(app)}/deploys?limit=${String(limit)}`
|
|
175
|
-
);
|
|
176
|
-
return res.deploys;
|
|
177
|
-
}
|
|
178
|
-
async function restoreVersion(versionId) {
|
|
179
|
-
const res = await request(`/v1/versions/${versionId}/restore`, {
|
|
180
|
-
method: "POST"
|
|
2454
|
+
const jobs = manifest.runtime.jobs === "v1" ? new DevJobRunner(
|
|
2455
|
+
manifest,
|
|
2456
|
+
{ workspaceId: identity.workspace.id, appId: "local-dev" },
|
|
2457
|
+
(line) => {
|
|
2458
|
+
say(dim(line));
|
|
2459
|
+
}
|
|
2460
|
+
) : null;
|
|
2461
|
+
const binding = createDevBinding({
|
|
2462
|
+
manifest,
|
|
2463
|
+
storage,
|
|
2464
|
+
database,
|
|
2465
|
+
jobs,
|
|
2466
|
+
bridge: { apiUrl: apiUrl(), token }
|
|
181
2467
|
});
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
2468
|
+
const host = await startDevHost({
|
|
2469
|
+
projectDir,
|
|
2470
|
+
buildDir: join9(devDir, "build"),
|
|
2471
|
+
manifest,
|
|
2472
|
+
binding,
|
|
2473
|
+
port,
|
|
2474
|
+
identity: {
|
|
2475
|
+
userId: identity.user.id,
|
|
2476
|
+
workspaceId: identity.workspace.id,
|
|
2477
|
+
userLabel: identity.user.email,
|
|
2478
|
+
appName
|
|
2479
|
+
},
|
|
2480
|
+
log: (line) => {
|
|
2481
|
+
say(dim(line));
|
|
2482
|
+
}
|
|
193
2483
|
});
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
2484
|
+
jobs?.attachApp(host.fetchApp);
|
|
2485
|
+
if (jobs && options.schedules) {
|
|
2486
|
+
jobs.startSchedules();
|
|
2487
|
+
say(dim("schedules: on"));
|
|
2488
|
+
} else if (jobs && Object.values(manifest.jobs).some((job) => job.schedule)) {
|
|
2489
|
+
say(dim("schedules: off \u2014 pass --schedules to run them locally"));
|
|
2490
|
+
}
|
|
2491
|
+
if (manifest.runtime.ai === "v1") {
|
|
2492
|
+
say(dim("marina.ai: bridged to Marina; usage is metered"));
|
|
2493
|
+
}
|
|
2494
|
+
say(
|
|
2495
|
+
`${green("\u2192")} http://localhost:${String(port)} ${dim(`(signed in as ${identity.user.email})`)}`
|
|
2496
|
+
);
|
|
2497
|
+
await new Promise((stop) => {
|
|
2498
|
+
const shutdown = () => {
|
|
2499
|
+
jobs?.stop();
|
|
2500
|
+
void host.close().then(stop);
|
|
2501
|
+
};
|
|
2502
|
+
process.once("SIGINT", shutdown);
|
|
2503
|
+
process.once("SIGTERM", shutdown);
|
|
198
2504
|
});
|
|
199
2505
|
}
|
|
2506
|
+
var DEFAULT_PORT;
|
|
2507
|
+
var init_dev = __esm({
|
|
2508
|
+
"src/dev/index.ts"() {
|
|
2509
|
+
"use strict";
|
|
2510
|
+
init_api();
|
|
2511
|
+
init_config();
|
|
2512
|
+
init_output();
|
|
2513
|
+
init_binding();
|
|
2514
|
+
init_db();
|
|
2515
|
+
init_host();
|
|
2516
|
+
init_jobs();
|
|
2517
|
+
init_manifest();
|
|
2518
|
+
init_storage();
|
|
2519
|
+
DEFAULT_PORT = 5990;
|
|
2520
|
+
}
|
|
2521
|
+
});
|
|
2522
|
+
|
|
2523
|
+
// src/index.ts
|
|
2524
|
+
init_api();
|
|
2525
|
+
init_api();
|
|
2526
|
+
init_config();
|
|
2527
|
+
import { resolve as resolve3 } from "node:path";
|
|
2528
|
+
import { parseArgs } from "node:util";
|
|
200
2529
|
|
|
201
2530
|
// ../../node_modules/.pnpm/fflate@0.8.3/node_modules/fflate/esm/index.mjs
|
|
202
2531
|
import { createRequire } from "module";
|
|
@@ -285,13 +2614,13 @@ var fdeb = new u8([
|
|
|
285
2614
|
var clim = new u8([16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15]);
|
|
286
2615
|
var freb = function(eb, start) {
|
|
287
2616
|
var b = new u16(31);
|
|
288
|
-
for (var
|
|
289
|
-
b[
|
|
2617
|
+
for (var i2 = 0; i2 < 31; ++i2) {
|
|
2618
|
+
b[i2] = start += 1 << eb[i2 - 1];
|
|
290
2619
|
}
|
|
291
2620
|
var r = new i32(b[30]);
|
|
292
|
-
for (var
|
|
293
|
-
for (var j = b[
|
|
294
|
-
r[j] = j - b[
|
|
2621
|
+
for (var i2 = 1; i2 < 30; ++i2) {
|
|
2622
|
+
for (var j = b[i2]; j < b[i2 + 1]; ++j) {
|
|
2623
|
+
r[j] = j - b[i2] << 5 | i2;
|
|
295
2624
|
}
|
|
296
2625
|
}
|
|
297
2626
|
return { b, r };
|
|
@@ -314,25 +2643,25 @@ var x;
|
|
|
314
2643
|
var i;
|
|
315
2644
|
var hMap = (function(cd, mb, r) {
|
|
316
2645
|
var s = cd.length;
|
|
317
|
-
var
|
|
2646
|
+
var i2 = 0;
|
|
318
2647
|
var l = new u16(mb);
|
|
319
|
-
for (;
|
|
320
|
-
if (cd[
|
|
321
|
-
++l[cd[
|
|
2648
|
+
for (; i2 < s; ++i2) {
|
|
2649
|
+
if (cd[i2])
|
|
2650
|
+
++l[cd[i2] - 1];
|
|
322
2651
|
}
|
|
323
2652
|
var le = new u16(mb);
|
|
324
|
-
for (
|
|
325
|
-
le[
|
|
2653
|
+
for (i2 = 1; i2 < mb; ++i2) {
|
|
2654
|
+
le[i2] = le[i2 - 1] + l[i2 - 1] << 1;
|
|
326
2655
|
}
|
|
327
2656
|
var co;
|
|
328
2657
|
if (r) {
|
|
329
2658
|
co = new u16(1 << mb);
|
|
330
2659
|
var rvb = 15 - mb;
|
|
331
|
-
for (
|
|
332
|
-
if (cd[
|
|
333
|
-
var sv =
|
|
334
|
-
var r_1 = mb - cd[
|
|
335
|
-
var v = le[cd[
|
|
2660
|
+
for (i2 = 0; i2 < s; ++i2) {
|
|
2661
|
+
if (cd[i2]) {
|
|
2662
|
+
var sv = i2 << 4 | cd[i2];
|
|
2663
|
+
var r_1 = mb - cd[i2];
|
|
2664
|
+
var v = le[cd[i2] - 1]++ << r_1;
|
|
336
2665
|
for (var m = v | (1 << r_1) - 1; v <= m; ++v) {
|
|
337
2666
|
co[rev[v] >> rvb] = sv;
|
|
338
2667
|
}
|
|
@@ -340,9 +2669,9 @@ var hMap = (function(cd, mb, r) {
|
|
|
340
2669
|
}
|
|
341
2670
|
} else {
|
|
342
2671
|
co = new u16(s);
|
|
343
|
-
for (
|
|
344
|
-
if (cd[
|
|
345
|
-
co[
|
|
2672
|
+
for (i2 = 0; i2 < s; ++i2) {
|
|
2673
|
+
if (cd[i2]) {
|
|
2674
|
+
co[i2] = rev[le[cd[i2] - 1]++] >> 15 - cd[i2];
|
|
346
2675
|
}
|
|
347
2676
|
}
|
|
348
2677
|
}
|
|
@@ -419,9 +2748,9 @@ var wbits16 = function(d, p, v) {
|
|
|
419
2748
|
};
|
|
420
2749
|
var hTree = function(d, mb) {
|
|
421
2750
|
var t = [];
|
|
422
|
-
for (var
|
|
423
|
-
if (d[
|
|
424
|
-
t.push({ s:
|
|
2751
|
+
for (var i2 = 0; i2 < d.length; ++i2) {
|
|
2752
|
+
if (d[i2])
|
|
2753
|
+
t.push({ s: i2, f: d[i2] });
|
|
425
2754
|
}
|
|
426
2755
|
var s = t.length;
|
|
427
2756
|
var t2 = t.slice();
|
|
@@ -436,28 +2765,28 @@ var hTree = function(d, mb) {
|
|
|
436
2765
|
return a.f - b.f;
|
|
437
2766
|
});
|
|
438
2767
|
t.push({ s: -1, f: 25001 });
|
|
439
|
-
var l = t[0], r = t[1], i0 = 0, i1 = 1,
|
|
2768
|
+
var l = t[0], r = t[1], i0 = 0, i1 = 1, i22 = 2;
|
|
440
2769
|
t[0] = { s: -1, f: l.f + r.f, l, r };
|
|
441
2770
|
while (i1 != s - 1) {
|
|
442
|
-
l = t[t[i0].f < t[
|
|
443
|
-
r = t[i0 != i1 && t[i0].f < t[
|
|
2771
|
+
l = t[t[i0].f < t[i22].f ? i0++ : i22++];
|
|
2772
|
+
r = t[i0 != i1 && t[i0].f < t[i22].f ? i0++ : i22++];
|
|
444
2773
|
t[i1++] = { s: -1, f: l.f + r.f, l, r };
|
|
445
2774
|
}
|
|
446
2775
|
var maxSym = t2[0].s;
|
|
447
|
-
for (var
|
|
448
|
-
if (t2[
|
|
449
|
-
maxSym = t2[
|
|
2776
|
+
for (var i2 = 1; i2 < s; ++i2) {
|
|
2777
|
+
if (t2[i2].s > maxSym)
|
|
2778
|
+
maxSym = t2[i2].s;
|
|
450
2779
|
}
|
|
451
2780
|
var tr = new u16(maxSym + 1);
|
|
452
2781
|
var mbt = ln(t[i1 - 1], tr, 0);
|
|
453
2782
|
if (mbt > mb) {
|
|
454
|
-
var
|
|
2783
|
+
var i2 = 0, dt = 0;
|
|
455
2784
|
var lft = mbt - mb, cst = 1 << lft;
|
|
456
2785
|
t2.sort(function(a, b) {
|
|
457
2786
|
return tr[b.s] - tr[a.s] || a.f - b.f;
|
|
458
2787
|
});
|
|
459
|
-
for (;
|
|
460
|
-
var i2_1 = t2[
|
|
2788
|
+
for (; i2 < s; ++i2) {
|
|
2789
|
+
var i2_1 = t2[i2].s;
|
|
461
2790
|
if (tr[i2_1] > mb) {
|
|
462
2791
|
dt += cst - (1 << mbt - tr[i2_1]);
|
|
463
2792
|
tr[i2_1] = mb;
|
|
@@ -466,14 +2795,14 @@ var hTree = function(d, mb) {
|
|
|
466
2795
|
}
|
|
467
2796
|
dt >>= lft;
|
|
468
2797
|
while (dt > 0) {
|
|
469
|
-
var i2_2 = t2[
|
|
2798
|
+
var i2_2 = t2[i2].s;
|
|
470
2799
|
if (tr[i2_2] < mb)
|
|
471
2800
|
dt -= 1 << mb - tr[i2_2]++ - 1;
|
|
472
2801
|
else
|
|
473
|
-
++
|
|
2802
|
+
++i2;
|
|
474
2803
|
}
|
|
475
|
-
for (;
|
|
476
|
-
var i2_3 = t2[
|
|
2804
|
+
for (; i2 >= 0 && dt; --i2) {
|
|
2805
|
+
var i2_3 = t2[i2].s;
|
|
477
2806
|
if (tr[i2_3] == mb) {
|
|
478
2807
|
--tr[i2_3];
|
|
479
2808
|
++dt;
|
|
@@ -495,8 +2824,8 @@ var lc = function(c) {
|
|
|
495
2824
|
var w = function(v) {
|
|
496
2825
|
cl[cli++] = v;
|
|
497
2826
|
};
|
|
498
|
-
for (var
|
|
499
|
-
if (c[
|
|
2827
|
+
for (var i2 = 1; i2 <= s; ++i2) {
|
|
2828
|
+
if (c[i2] == cln && i2 != s)
|
|
500
2829
|
++cls;
|
|
501
2830
|
else {
|
|
502
2831
|
if (!cln && cls > 2) {
|
|
@@ -516,15 +2845,15 @@ var lc = function(c) {
|
|
|
516
2845
|
while (cls--)
|
|
517
2846
|
w(cln);
|
|
518
2847
|
cls = 1;
|
|
519
|
-
cln = c[
|
|
2848
|
+
cln = c[i2];
|
|
520
2849
|
}
|
|
521
2850
|
}
|
|
522
2851
|
return { c: cl.subarray(0, cli), n: s };
|
|
523
2852
|
};
|
|
524
2853
|
var clen = function(cf, cl) {
|
|
525
2854
|
var l = 0;
|
|
526
|
-
for (var
|
|
527
|
-
l += cf[
|
|
2855
|
+
for (var i2 = 0; i2 < cl.length; ++i2)
|
|
2856
|
+
l += cf[i2] * cl[i2];
|
|
528
2857
|
return l;
|
|
529
2858
|
};
|
|
530
2859
|
var wfblk = function(out, pos, dat) {
|
|
@@ -534,8 +2863,8 @@ var wfblk = function(out, pos, dat) {
|
|
|
534
2863
|
out[o + 1] = s >> 8;
|
|
535
2864
|
out[o + 2] = out[o] ^ 255;
|
|
536
2865
|
out[o + 3] = out[o + 1] ^ 255;
|
|
537
|
-
for (var
|
|
538
|
-
out[o +
|
|
2866
|
+
for (var i2 = 0; i2 < s; ++i2)
|
|
2867
|
+
out[o + i2 + 4] = dat[i2];
|
|
539
2868
|
return (o + 4 + s) * 8;
|
|
540
2869
|
};
|
|
541
2870
|
var wblk = function(dat, out, final, syms, lf, df, eb, li, bs, bl, p) {
|
|
@@ -546,10 +2875,10 @@ var wblk = function(dat, out, final, syms, lf, df, eb, li, bs, bl, p) {
|
|
|
546
2875
|
var _c = lc(dlt), lclt = _c.c, nlc = _c.n;
|
|
547
2876
|
var _d = lc(ddt), lcdt = _d.c, ndc = _d.n;
|
|
548
2877
|
var lcfreq = new u16(19);
|
|
549
|
-
for (var
|
|
550
|
-
++lcfreq[lclt[
|
|
551
|
-
for (var
|
|
552
|
-
++lcfreq[lcdt[
|
|
2878
|
+
for (var i2 = 0; i2 < lclt.length; ++i2)
|
|
2879
|
+
++lcfreq[lclt[i2] & 31];
|
|
2880
|
+
for (var i2 = 0; i2 < lcdt.length; ++i2)
|
|
2881
|
+
++lcfreq[lcdt[i2] & 31];
|
|
553
2882
|
var _e = hTree(lcfreq, 7), lct = _e.t, mlcb = _e.l;
|
|
554
2883
|
var nlcc = 19;
|
|
555
2884
|
for (; nlcc > 4 && !lct[clim[nlcc - 1]]; --nlcc)
|
|
@@ -568,24 +2897,24 @@ var wblk = function(dat, out, final, syms, lf, df, eb, li, bs, bl, p) {
|
|
|
568
2897
|
wbits(out, p + 5, ndc - 1);
|
|
569
2898
|
wbits(out, p + 10, nlcc - 4);
|
|
570
2899
|
p += 14;
|
|
571
|
-
for (var
|
|
572
|
-
wbits(out, p + 3 *
|
|
2900
|
+
for (var i2 = 0; i2 < nlcc; ++i2)
|
|
2901
|
+
wbits(out, p + 3 * i2, lct[clim[i2]]);
|
|
573
2902
|
p += 3 * nlcc;
|
|
574
2903
|
var lcts = [lclt, lcdt];
|
|
575
2904
|
for (var it = 0; it < 2; ++it) {
|
|
576
2905
|
var clct = lcts[it];
|
|
577
|
-
for (var
|
|
578
|
-
var len = clct[
|
|
2906
|
+
for (var i2 = 0; i2 < clct.length; ++i2) {
|
|
2907
|
+
var len = clct[i2] & 31;
|
|
579
2908
|
wbits(out, p, llm[len]), p += lct[len];
|
|
580
2909
|
if (len > 15)
|
|
581
|
-
wbits(out, p, clct[
|
|
2910
|
+
wbits(out, p, clct[i2] >> 5 & 127), p += clct[i2] >> 12;
|
|
582
2911
|
}
|
|
583
2912
|
}
|
|
584
2913
|
} else {
|
|
585
2914
|
lm = flm, ll = flt, dm = fdm, dl = fdt;
|
|
586
2915
|
}
|
|
587
|
-
for (var
|
|
588
|
-
var sym = syms[
|
|
2916
|
+
for (var i2 = 0; i2 < li; ++i2) {
|
|
2917
|
+
var sym = syms[i2];
|
|
589
2918
|
if (sym > 255) {
|
|
590
2919
|
var len = sym >> 18 & 31;
|
|
591
2920
|
wbits16(out, p, lm[len + 257]), p += ll[len + 257];
|
|
@@ -618,36 +2947,36 @@ var dflt = function(dat, lvl, plvl, pre, post, st) {
|
|
|
618
2947
|
var msk_1 = (1 << plvl) - 1;
|
|
619
2948
|
var prev = st.p || new u16(32768), head = st.h || new u16(msk_1 + 1);
|
|
620
2949
|
var bs1_1 = Math.ceil(plvl / 3), bs2_1 = 2 * bs1_1;
|
|
621
|
-
var hsh = function(
|
|
622
|
-
return (dat[
|
|
2950
|
+
var hsh = function(i3) {
|
|
2951
|
+
return (dat[i3] ^ dat[i3 + 1] << bs1_1 ^ dat[i3 + 2] << bs2_1) & msk_1;
|
|
623
2952
|
};
|
|
624
2953
|
var syms = new i32(25e3);
|
|
625
2954
|
var lf = new u16(288), df = new u16(32);
|
|
626
|
-
var lc_1 = 0, eb = 0,
|
|
627
|
-
for (;
|
|
628
|
-
var hv = hsh(
|
|
629
|
-
var imod =
|
|
2955
|
+
var lc_1 = 0, eb = 0, i2 = st.i || 0, li = 0, wi = st.w || 0, bs = 0;
|
|
2956
|
+
for (; i2 + 2 < s; ++i2) {
|
|
2957
|
+
var hv = hsh(i2);
|
|
2958
|
+
var imod = i2 & 32767, pimod = head[hv];
|
|
630
2959
|
prev[imod] = pimod;
|
|
631
2960
|
head[hv] = imod;
|
|
632
|
-
if (wi <=
|
|
633
|
-
var rem = s -
|
|
2961
|
+
if (wi <= i2) {
|
|
2962
|
+
var rem = s - i2;
|
|
634
2963
|
if ((lc_1 > 7e3 || li > 24576) && (rem > 423 || !lst)) {
|
|
635
|
-
pos = wblk(dat, w, 0, syms, lf, df, eb, li, bs,
|
|
636
|
-
li = lc_1 = eb = 0, bs =
|
|
2964
|
+
pos = wblk(dat, w, 0, syms, lf, df, eb, li, bs, i2 - bs, pos);
|
|
2965
|
+
li = lc_1 = eb = 0, bs = i2;
|
|
637
2966
|
for (var j = 0; j < 286; ++j)
|
|
638
2967
|
lf[j] = 0;
|
|
639
2968
|
for (var j = 0; j < 30; ++j)
|
|
640
2969
|
df[j] = 0;
|
|
641
2970
|
}
|
|
642
2971
|
var l = 2, d = 0, ch_1 = c, dif = imod - pimod & 32767;
|
|
643
|
-
if (rem > 2 && hv == hsh(
|
|
2972
|
+
if (rem > 2 && hv == hsh(i2 - dif)) {
|
|
644
2973
|
var maxn = Math.min(n, rem) - 1;
|
|
645
|
-
var maxd = Math.min(32767,
|
|
2974
|
+
var maxd = Math.min(32767, i2);
|
|
646
2975
|
var ml = Math.min(258, rem);
|
|
647
2976
|
while (dif <= maxd && --ch_1 && imod != pimod) {
|
|
648
|
-
if (dat[
|
|
2977
|
+
if (dat[i2 + l] == dat[i2 + l - dif]) {
|
|
649
2978
|
var nl = 0;
|
|
650
|
-
for (; nl < ml && dat[
|
|
2979
|
+
for (; nl < ml && dat[i2 + nl] == dat[i2 + nl - dif]; ++nl)
|
|
651
2980
|
;
|
|
652
2981
|
if (nl > l) {
|
|
653
2982
|
l = nl, d = dif;
|
|
@@ -656,7 +2985,7 @@ var dflt = function(dat, lvl, plvl, pre, post, st) {
|
|
|
656
2985
|
var mmd = Math.min(dif, nl - 2);
|
|
657
2986
|
var md = 0;
|
|
658
2987
|
for (var j = 0; j < mmd; ++j) {
|
|
659
|
-
var ti =
|
|
2988
|
+
var ti = i2 - dif + j & 32767;
|
|
660
2989
|
var pti = prev[ti];
|
|
661
2990
|
var cd = ti - pti & 32767;
|
|
662
2991
|
if (cd > md)
|
|
@@ -674,32 +3003,32 @@ var dflt = function(dat, lvl, plvl, pre, post, st) {
|
|
|
674
3003
|
eb += fleb[lin] + fdeb[din];
|
|
675
3004
|
++lf[257 + lin];
|
|
676
3005
|
++df[din];
|
|
677
|
-
wi =
|
|
3006
|
+
wi = i2 + l;
|
|
678
3007
|
++lc_1;
|
|
679
3008
|
} else {
|
|
680
|
-
syms[li++] = dat[
|
|
681
|
-
++lf[dat[
|
|
3009
|
+
syms[li++] = dat[i2];
|
|
3010
|
+
++lf[dat[i2]];
|
|
682
3011
|
}
|
|
683
3012
|
}
|
|
684
3013
|
}
|
|
685
|
-
for (
|
|
686
|
-
syms[li++] = dat[
|
|
687
|
-
++lf[dat[
|
|
3014
|
+
for (i2 = Math.max(i2, wi); i2 < s; ++i2) {
|
|
3015
|
+
syms[li++] = dat[i2];
|
|
3016
|
+
++lf[dat[i2]];
|
|
688
3017
|
}
|
|
689
|
-
pos = wblk(dat, w, lst, syms, lf, df, eb, li, bs,
|
|
3018
|
+
pos = wblk(dat, w, lst, syms, lf, df, eb, li, bs, i2 - bs, pos);
|
|
690
3019
|
if (!lst) {
|
|
691
3020
|
st.r = pos & 7 | w[pos / 8 | 0] << 3;
|
|
692
3021
|
pos -= 7;
|
|
693
|
-
st.h = head, st.p = prev, st.i =
|
|
3022
|
+
st.h = head, st.p = prev, st.i = i2, st.w = wi;
|
|
694
3023
|
}
|
|
695
3024
|
} else {
|
|
696
|
-
for (var
|
|
697
|
-
var e =
|
|
3025
|
+
for (var i2 = st.w || 0; i2 < s + lst; i2 += 65535) {
|
|
3026
|
+
var e = i2 + 65535;
|
|
698
3027
|
if (e >= s) {
|
|
699
3028
|
w[pos / 8 | 0] = lst;
|
|
700
3029
|
e = s;
|
|
701
3030
|
}
|
|
702
|
-
pos = wfblk(w, pos + 1, dat.subarray(
|
|
3031
|
+
pos = wfblk(w, pos + 1, dat.subarray(i2, e));
|
|
703
3032
|
}
|
|
704
3033
|
st.i = s;
|
|
705
3034
|
}
|
|
@@ -707,11 +3036,11 @@ var dflt = function(dat, lvl, plvl, pre, post, st) {
|
|
|
707
3036
|
};
|
|
708
3037
|
var crct = /* @__PURE__ */ (function() {
|
|
709
3038
|
var t = new Int32Array(256);
|
|
710
|
-
for (var
|
|
711
|
-
var c =
|
|
3039
|
+
for (var i2 = 0; i2 < 256; ++i2) {
|
|
3040
|
+
var c = i2, k = 9;
|
|
712
3041
|
while (--k)
|
|
713
3042
|
c = (c & 1 && -306674912) ^ c >>> 1;
|
|
714
|
-
t[
|
|
3043
|
+
t[i2] = c;
|
|
715
3044
|
}
|
|
716
3045
|
return t;
|
|
717
3046
|
})();
|
|
@@ -720,8 +3049,8 @@ var crc = function() {
|
|
|
720
3049
|
return {
|
|
721
3050
|
p: function(d) {
|
|
722
3051
|
var cr = c;
|
|
723
|
-
for (var
|
|
724
|
-
cr = crct[cr & 255 ^ d[
|
|
3052
|
+
for (var i2 = 0; i2 < d.length; ++i2)
|
|
3053
|
+
cr = crct[cr & 255 ^ d[i2]] ^ cr >>> 8;
|
|
725
3054
|
c = cr;
|
|
726
3055
|
},
|
|
727
3056
|
d: function() {
|
|
@@ -782,8 +3111,8 @@ try {
|
|
|
782
3111
|
function strToU8(str, latin1) {
|
|
783
3112
|
if (latin1) {
|
|
784
3113
|
var ar_1 = new u8(str.length);
|
|
785
|
-
for (var
|
|
786
|
-
ar_1[
|
|
3114
|
+
for (var i2 = 0; i2 < str.length; ++i2)
|
|
3115
|
+
ar_1[i2] = str.charCodeAt(i2);
|
|
787
3116
|
return ar_1;
|
|
788
3117
|
}
|
|
789
3118
|
if (te)
|
|
@@ -794,19 +3123,19 @@ function strToU8(str, latin1) {
|
|
|
794
3123
|
var w = function(v) {
|
|
795
3124
|
ar[ai++] = v;
|
|
796
3125
|
};
|
|
797
|
-
for (var
|
|
3126
|
+
for (var i2 = 0; i2 < l; ++i2) {
|
|
798
3127
|
if (ai + 5 > ar.length) {
|
|
799
|
-
var n = new u8(ai + 8 + (l -
|
|
3128
|
+
var n = new u8(ai + 8 + (l - i2 << 1));
|
|
800
3129
|
n.set(ar);
|
|
801
3130
|
ar = n;
|
|
802
3131
|
}
|
|
803
|
-
var c = str.charCodeAt(
|
|
3132
|
+
var c = str.charCodeAt(i2);
|
|
804
3133
|
if (c < 128 || latin1)
|
|
805
3134
|
w(c);
|
|
806
3135
|
else if (c < 2048)
|
|
807
3136
|
w(192 | c >> 6), w(128 | c & 63);
|
|
808
3137
|
else if (c > 55295 && c < 57344)
|
|
809
|
-
c = 65536 + (c & 1023 << 10) | str.charCodeAt(++
|
|
3138
|
+
c = 65536 + (c & 1023 << 10) | str.charCodeAt(++i2) & 1023, w(240 | c >> 18), w(128 | c >> 12 & 63), w(128 | c >> 6 & 63), w(128 | c & 63);
|
|
810
3139
|
else
|
|
811
3140
|
w(224 | c >> 12), w(128 | c >> 6 & 63), w(128 | c & 63);
|
|
812
3141
|
}
|
|
@@ -903,8 +3232,8 @@ function zipSync(data, opts) {
|
|
|
903
3232
|
tot += 76 + 2 * (s + exl) + (ms || 0) + l;
|
|
904
3233
|
}
|
|
905
3234
|
var out = new u8(tot + 22), oe = o, cdl = tot - o;
|
|
906
|
-
for (var
|
|
907
|
-
var f = files[
|
|
3235
|
+
for (var i2 = 0; i2 < files.length; ++i2) {
|
|
3236
|
+
var f = files[i2];
|
|
908
3237
|
wzh(out, f.o, f, f.f, f.u, f.c.length);
|
|
909
3238
|
var badd = 30 + f.f.length + exfl(f.extra);
|
|
910
3239
|
out.set(f.c, f.o + badd);
|
|
@@ -1398,8 +3727,7 @@ header {
|
|
|
1398
3727
|
var DEMO_MANIFEST = {
|
|
1399
3728
|
schema: 1,
|
|
1400
3729
|
name: "Hello Marina",
|
|
1401
|
-
icon: "\u26F5"
|
|
1402
|
-
type: "static"
|
|
3730
|
+
icon: "\u26F5"
|
|
1403
3731
|
};
|
|
1404
3732
|
|
|
1405
3733
|
// src/demo.ts
|
|
@@ -1424,20 +3752,24 @@ function packDemo() {
|
|
|
1424
3752
|
}
|
|
1425
3753
|
|
|
1426
3754
|
// src/login.ts
|
|
3755
|
+
init_config();
|
|
3756
|
+
init_api();
|
|
1427
3757
|
import { createHash, randomBytes } from "node:crypto";
|
|
1428
3758
|
import { spawn } from "node:child_process";
|
|
1429
3759
|
import { createServer } from "node:http";
|
|
1430
3760
|
import { hostname } from "node:os";
|
|
1431
|
-
var LOGIN_TIMEOUT_MS =
|
|
3761
|
+
var LOGIN_TIMEOUT_MS = 15 * 6e4;
|
|
1432
3762
|
var PROGRESS_INTERVAL_MS = 1e3;
|
|
1433
3763
|
var page = (title, message) => `<!doctype html>
|
|
1434
|
-
<html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width">
|
|
3764
|
+
<html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width"><meta name="color-scheme" content="light dark">
|
|
1435
3765
|
<title>${title}</title><style>
|
|
1436
|
-
|
|
1437
|
-
|
|
3766
|
+
:root{color-scheme:light;--text:#142238;--muted:#667085;--background:#faf9f7}
|
|
3767
|
+
@media(prefers-color-scheme:dark){:root{color-scheme:dark;--text:#f2f4f7;--muted:#98a2b3;--background:#101828}}
|
|
3768
|
+
body{margin:0;display:grid;place-items:center;min-height:100vh;font:15px/1.5 system-ui,sans-serif;color:var(--text);background:var(--background)}
|
|
3769
|
+
main{text-align:center;padding:32px}h1{font-size:22px;margin:0 0 6px}p{margin:0;color:var(--muted)}
|
|
1438
3770
|
</style></head><body><main><h1>${title}</h1><p>${message}</p></main></body></html>`;
|
|
1439
|
-
var closeLoginServer = (server) => new Promise((
|
|
1440
|
-
server.close(() =>
|
|
3771
|
+
var closeLoginServer = (server) => new Promise((resolve4) => {
|
|
3772
|
+
server.close(() => resolve4());
|
|
1441
3773
|
server.closeAllConnections();
|
|
1442
3774
|
});
|
|
1443
3775
|
function openBrowser(url) {
|
|
@@ -1455,7 +3787,7 @@ function timeoutLabel(timeoutMs) {
|
|
|
1455
3787
|
return `${String(seconds)} second${seconds === 1 ? "" : "s"}`;
|
|
1456
3788
|
}
|
|
1457
3789
|
function waitForLoginCallback(server, expectedState, onProgress, timeoutMs = LOGIN_TIMEOUT_MS) {
|
|
1458
|
-
return new Promise((
|
|
3790
|
+
return new Promise((resolve4, reject) => {
|
|
1459
3791
|
const startedAt = Date.now();
|
|
1460
3792
|
const waiting = () => {
|
|
1461
3793
|
const elapsedMs = Date.now() - startedAt;
|
|
@@ -1512,7 +3844,7 @@ function waitForLoginCallback(server, expectedState, onProgress, timeoutMs = LOG
|
|
|
1512
3844
|
response.writeHead(200);
|
|
1513
3845
|
response.end(
|
|
1514
3846
|
page("Marina CLI is signed in", "You can close this window and return to the terminal."),
|
|
1515
|
-
() =>
|
|
3847
|
+
() => resolve4(code)
|
|
1516
3848
|
);
|
|
1517
3849
|
});
|
|
1518
3850
|
});
|
|
@@ -1522,9 +3854,9 @@ async function loginWithBrowser(onOpen, onProgress = () => void 0) {
|
|
|
1522
3854
|
const challenge = createHash("sha256").update(verifier).digest("base64url");
|
|
1523
3855
|
const state = randomBytes(32).toString("base64url");
|
|
1524
3856
|
const server = createServer();
|
|
1525
|
-
await new Promise((
|
|
3857
|
+
await new Promise((resolve4, reject) => {
|
|
1526
3858
|
server.once("error", reject);
|
|
1527
|
-
server.listen(0, "127.0.0.1",
|
|
3859
|
+
server.listen(0, "127.0.0.1", resolve4);
|
|
1528
3860
|
});
|
|
1529
3861
|
try {
|
|
1530
3862
|
const address = server.address();
|
|
@@ -1548,65 +3880,18 @@ async function loginWithBrowser(onOpen, onProgress = () => void 0) {
|
|
|
1548
3880
|
}
|
|
1549
3881
|
}
|
|
1550
3882
|
|
|
1551
|
-
// src/
|
|
1552
|
-
|
|
1553
|
-
var setJsonMode = (on) => {
|
|
1554
|
-
json = on;
|
|
1555
|
-
};
|
|
1556
|
-
var isJsonMode = () => json;
|
|
1557
|
-
var bold = (s) => `\x1B[1m${s}\x1B[22m`;
|
|
1558
|
-
var dim = (s) => `\x1B[2m${s}\x1B[22m`;
|
|
1559
|
-
var red = (s) => `\x1B[31m${s}\x1B[39m`;
|
|
1560
|
-
var green = (s) => `\x1B[32m${s}\x1B[39m`;
|
|
1561
|
-
function say(line = "") {
|
|
1562
|
-
if (json) console.error(line);
|
|
1563
|
-
else console.log(line);
|
|
1564
|
-
}
|
|
1565
|
-
function note(line) {
|
|
1566
|
-
console.error(line);
|
|
1567
|
-
}
|
|
1568
|
-
function createProgress() {
|
|
1569
|
-
let active = false;
|
|
1570
|
-
let lastPhase = null;
|
|
1571
|
-
let frame = 0;
|
|
1572
|
-
const frames = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
1573
|
-
const interactive = process.stderr.isTTY === true && !json;
|
|
1574
|
-
return {
|
|
1575
|
-
update(phase, line) {
|
|
1576
|
-
if (interactive) {
|
|
1577
|
-
process.stderr.write(`\r\x1B[2K${frames[frame++ % frames.length]} ${line}`);
|
|
1578
|
-
active = true;
|
|
1579
|
-
} else if (phase !== lastPhase) {
|
|
1580
|
-
console.error(json ? JSON.stringify({ type: "progress", phase, message: line }) : line);
|
|
1581
|
-
}
|
|
1582
|
-
lastPhase = phase;
|
|
1583
|
-
},
|
|
1584
|
-
clear() {
|
|
1585
|
-
if (active) process.stderr.write("\r\x1B[2K");
|
|
1586
|
-
active = false;
|
|
1587
|
-
}
|
|
1588
|
-
};
|
|
1589
|
-
}
|
|
1590
|
-
function result(payload) {
|
|
1591
|
-
if (json) console.log(JSON.stringify({ schema_version: 1, ok: true, ...payload }, null, 2));
|
|
1592
|
-
}
|
|
1593
|
-
function failure(code, message, extra = {}) {
|
|
1594
|
-
if (json)
|
|
1595
|
-
console.log(
|
|
1596
|
-
JSON.stringify({ schema_version: 1, ok: false, error: { code, message, ...extra } }, null, 2)
|
|
1597
|
-
);
|
|
1598
|
-
else {
|
|
1599
|
-
console.error(`${red(code)} ${message}`);
|
|
1600
|
-
}
|
|
1601
|
-
}
|
|
3883
|
+
// src/index.ts
|
|
3884
|
+
init_output();
|
|
1602
3885
|
|
|
1603
3886
|
// src/pack.ts
|
|
1604
|
-
import { lstatSync, readFileSync as readFileSync2, readdirSync } from "node:fs";
|
|
1605
|
-
import { join as join2, relative } from "node:path";
|
|
3887
|
+
import { existsSync as existsSync2, lstatSync, readFileSync as readFileSync2, readdirSync, realpathSync } from "node:fs";
|
|
3888
|
+
import { basename, dirname, join as join2, relative } from "node:path";
|
|
3889
|
+
var import_ignore = __toESM(require_ignore(), 1);
|
|
1606
3890
|
var EXCLUDED_DIRS = /* @__PURE__ */ new Set([
|
|
1607
3891
|
".git",
|
|
1608
3892
|
".hg",
|
|
1609
3893
|
".svn",
|
|
3894
|
+
".marina",
|
|
1610
3895
|
"node_modules",
|
|
1611
3896
|
"bower_components",
|
|
1612
3897
|
".venv",
|
|
@@ -1622,23 +3907,74 @@ var EXCLUDED_DIRS = /* @__PURE__ */ new Set([
|
|
|
1622
3907
|
"coverage",
|
|
1623
3908
|
".idea",
|
|
1624
3909
|
".vscode",
|
|
1625
|
-
".marina",
|
|
1626
3910
|
"__MACOSX"
|
|
1627
3911
|
]);
|
|
1628
3912
|
var EXCLUDED_FILES = /* @__PURE__ */ new Set([".DS_Store"]);
|
|
1629
3913
|
var MAX_TOTAL = 100 * 1024 * 1024;
|
|
1630
|
-
var
|
|
3914
|
+
var OUTPUT_DIR_NAMES = /* @__PURE__ */ new Set(["dist", "build", "out", ".output"]);
|
|
3915
|
+
function buildOutputParent(dir) {
|
|
3916
|
+
let real;
|
|
3917
|
+
try {
|
|
3918
|
+
real = realpathSync(dir);
|
|
3919
|
+
} catch {
|
|
3920
|
+
return null;
|
|
3921
|
+
}
|
|
3922
|
+
if (!OUTPUT_DIR_NAMES.has(basename(dir)) && !OUTPUT_DIR_NAMES.has(basename(real))) return null;
|
|
3923
|
+
if (!existsSync2(join2(real, "index.html"))) return null;
|
|
3924
|
+
if (existsSync2(join2(real, "package.json")) || existsSync2(join2(real, "marina.json"))) return null;
|
|
3925
|
+
for (const parent of /* @__PURE__ */ new Set([dirname(real), dirname(dir)])) {
|
|
3926
|
+
if (parent === real || parent === dir) continue;
|
|
3927
|
+
if (declaresBuild(parent)) return parent;
|
|
3928
|
+
}
|
|
3929
|
+
return null;
|
|
3930
|
+
}
|
|
3931
|
+
function declaresBuild(parent) {
|
|
3932
|
+
try {
|
|
3933
|
+
const parsed = JSON.parse(readFileSync2(join2(parent, "package.json"), "utf8"));
|
|
3934
|
+
const build = parsed.scripts?.build;
|
|
3935
|
+
return typeof build === "string" && build.trim().length > 0;
|
|
3936
|
+
} catch {
|
|
3937
|
+
return false;
|
|
3938
|
+
}
|
|
3939
|
+
}
|
|
3940
|
+
var SECRET_FILE = /^(\.env(\.[^/]*)?|\.npmrc|.*\.pem|.*\.p12|.*\.pfx|id_rsa|id_dsa|id_ecdsa|id_ed25519|credentials\.json|service-account\.json)$/i;
|
|
3941
|
+
function ignoredBy(rules, path, directory) {
|
|
3942
|
+
let ignored = false;
|
|
3943
|
+
for (const rule of rules) {
|
|
3944
|
+
const scopedPath = rule.base === "" ? path : path.startsWith(`${rule.base}/`) ? path.slice(rule.base.length + 1) : void 0;
|
|
3945
|
+
if (!scopedPath) continue;
|
|
3946
|
+
const result2 = rule.matcher.test(directory ? `${scopedPath}/` : scopedPath);
|
|
3947
|
+
if (result2.ignored) ignored = true;
|
|
3948
|
+
if (result2.unignored) ignored = false;
|
|
3949
|
+
}
|
|
3950
|
+
return ignored;
|
|
3951
|
+
}
|
|
1631
3952
|
function pack(dir) {
|
|
1632
3953
|
const files = {};
|
|
1633
3954
|
const skippedSecrets = [];
|
|
1634
3955
|
let totalBytes = 0;
|
|
1635
|
-
const walk = (current) => {
|
|
3956
|
+
const walk = (current, inheritedRules) => {
|
|
3957
|
+
const currentRelative = relative(dir, current).replaceAll("\\", "/");
|
|
3958
|
+
const rules = [...inheritedRules];
|
|
3959
|
+
const gitignorePath = join2(current, ".gitignore");
|
|
3960
|
+
if (existsSync2(gitignorePath)) {
|
|
3961
|
+
const gitignoreStat = lstatSync(gitignorePath);
|
|
3962
|
+
if (gitignoreStat.isFile() && !gitignoreStat.isSymbolicLink()) {
|
|
3963
|
+
rules.push({
|
|
3964
|
+
base: currentRelative,
|
|
3965
|
+
matcher: (0, import_ignore.default)().add(readFileSync2(gitignorePath, "utf8"))
|
|
3966
|
+
});
|
|
3967
|
+
}
|
|
3968
|
+
}
|
|
1636
3969
|
for (const entry of readdirSync(current)) {
|
|
1637
3970
|
const full = join2(current, entry);
|
|
1638
3971
|
const stat = lstatSync(full);
|
|
1639
3972
|
if (stat.isSymbolicLink()) continue;
|
|
3973
|
+
const relativePath = relative(dir, full).replaceAll("\\", "/");
|
|
1640
3974
|
if (stat.isDirectory()) {
|
|
1641
|
-
if (!EXCLUDED_DIRS.has(entry)
|
|
3975
|
+
if (!EXCLUDED_DIRS.has(entry) && !ignoredBy(rules, relativePath, true)) {
|
|
3976
|
+
walk(full, rules);
|
|
3977
|
+
}
|
|
1642
3978
|
continue;
|
|
1643
3979
|
}
|
|
1644
3980
|
if (EXCLUDED_FILES.has(entry)) continue;
|
|
@@ -1646,27 +3982,28 @@ function pack(dir) {
|
|
|
1646
3982
|
skippedSecrets.push(relative(dir, full));
|
|
1647
3983
|
continue;
|
|
1648
3984
|
}
|
|
3985
|
+
if (ignoredBy(rules, relativePath, false)) continue;
|
|
1649
3986
|
totalBytes += stat.size;
|
|
1650
3987
|
if (totalBytes > MAX_TOTAL) {
|
|
1651
3988
|
throw new Error(
|
|
1652
3989
|
`this directory is over ${String(MAX_TOTAL / 1024 / 1024)} MB unpacked \u2014 trim it before deploying`
|
|
1653
3990
|
);
|
|
1654
3991
|
}
|
|
1655
|
-
files[
|
|
3992
|
+
files[relativePath] = readFileSync2(full);
|
|
1656
3993
|
}
|
|
1657
3994
|
};
|
|
1658
|
-
walk(dir);
|
|
3995
|
+
walk(dir, []);
|
|
1659
3996
|
const fileCount = Object.keys(files).length;
|
|
1660
3997
|
if (fileCount === 0) throw new Error("nothing to deploy \u2014 this directory is empty");
|
|
1661
3998
|
return { zip: zipSync(files), fileCount, totalBytes, skippedSecrets };
|
|
1662
3999
|
}
|
|
1663
4000
|
|
|
1664
4001
|
// src/manifest.ts
|
|
1665
|
-
import { existsSync as
|
|
1666
|
-
import { basename, join as join3 } from "node:path";
|
|
4002
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3 } from "node:fs";
|
|
4003
|
+
import { basename as basename2, join as join3 } from "node:path";
|
|
1667
4004
|
function readManifest(dir) {
|
|
1668
4005
|
const path = join3(dir, "marina.json");
|
|
1669
|
-
if (!
|
|
4006
|
+
if (!existsSync3(path)) return null;
|
|
1670
4007
|
let value;
|
|
1671
4008
|
try {
|
|
1672
4009
|
value = JSON.parse(readFileSync3(path, "utf8"));
|
|
@@ -1687,7 +4024,7 @@ function readManifest(dir) {
|
|
|
1687
4024
|
throw new Error('marina.json "icon" must be an emoji');
|
|
1688
4025
|
}
|
|
1689
4026
|
if (manifest.type !== void 0 && manifest.type !== "static" && manifest.type !== "dynamic") {
|
|
1690
|
-
throw new Error('marina.json "type"
|
|
4027
|
+
throw new Error('marina.json "type" is obsolete; remove it and use "entrypoint" if needed');
|
|
1691
4028
|
}
|
|
1692
4029
|
return manifest;
|
|
1693
4030
|
}
|
|
@@ -1702,16 +4039,16 @@ function resolveAppName(dir, flag, manifest = readManifest(dir), linkedName) {
|
|
|
1702
4039
|
} catch {
|
|
1703
4040
|
}
|
|
1704
4041
|
if (linkedName?.trim()) return linkedName.trim();
|
|
1705
|
-
return
|
|
4042
|
+
return basename2(dir);
|
|
1706
4043
|
}
|
|
1707
4044
|
|
|
1708
4045
|
// src/skills.ts
|
|
1709
|
-
import { existsSync as
|
|
4046
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "node:fs";
|
|
1710
4047
|
import { homedir as homedir2 } from "node:os";
|
|
1711
4048
|
import { join as join4 } from "node:path";
|
|
1712
4049
|
|
|
1713
4050
|
// ../../skills/marina-deploy/SKILL.md
|
|
1714
|
-
var SKILL_default =
|
|
4051
|
+
var SKILL_default = "---\nname: marina-deploy\ndescription: Build, deploy, inspect, and recover applications on Marina Cloud with the Marina CLI. Use when a user asks to publish a project to Marina, create a first Marina demo, check whether a Marina deployment is live, diagnose a failed or refused deploy, inspect versions, or roll an app back.\n---\n\n# Marina Deploy\n\nUse the Marina CLI to deploy the user's project. Run commands with `--json` and\nfollow the returned result until the app is live.\n\n## Deploy\n\n1. Inspect the project and run its relevant local checks.\n2. Install the CLI if needed: `npm install -g @marina-cloud/cli`.\n3. If unauthenticated, run `marina setup` and let the user finish browser sign-in.\n4. Run `marina deploy --json` from the project directory.\n5. Return the live URL only when the result has `ok: true`, `state: \"published\"`,\n `live: true`, and a non-null `url`.\n\nIf it returns a failure or refusal, use the returned message and build log to\nfix the project, verify it locally, and retry. Never bypass a refusal or\ndescribe an unpublished or failed version as live.\n\nMarina may recover an ambiguous or failed static build through its publishing\nagent. Inspect the returned `preparation` report and `build_log`: the agent can\nminimally repair and retry the build. Marina requires a successful build and\nmechanically verified output before publication.\n\nStatic source detection is deterministic: a non-empty `scripts.build` wins\nover root `index.html`; without a build script, root `index.html` is served\nas-is. Deploy the project root and let Marina build it. A finished output\ndirectory (for example, `marina deploy dist`) is refused when it belongs to a\nproject that declares a build; deploy that project's source instead.\n\nEvery accepted upload returns a `deploy_id`, including a first deploy refused\nbefore an app exists. Inspect that durable attempt with:\n\n```sh\nmarina deploys <deploy-id> --json\n```\n\nAn archive or security refusal may have a null `build_log` because no build was\nrun; the refusal and action are still preserved on the deploy attempt.\n\n## Develop locally\n\n`marina dev` runs the app on this machine with the production runtime\ncontract: local storage and an embedded Postgres (the app's\n`marina/migrations` apply on start), while `marina.capabilities` and\n`marina.connections` calls bridge to Marina under the signed-in user's\ndev-scoped grants. A denied bridged call names the missing grant \u2014 an\norganization admin adds it from the connection's Dev access control. Use\n`--port <port>` to change the listen port and `--schedules` to run scheduled\njobs locally. `marina.ai` bridges too; per-developer usage is metered.\n\n## Create a demo\n\nFor a first demo, run `marina deploy demo --json`. This deploys the bundled\nHello Marina collage app directly. Treat it like any other deploy: return its\nURL only after the result says it is published and live.\n\n## Inspect or recover\n\n```sh\nmarina status --app <slug> --json\nmarina deploys --app <slug> --json\nmarina deploys <deploy-id> --json\nmarina logs --app <slug>\nmarina versions --app <slug> --json\nmarina rollback --app <slug> --to <hash> --json\n```\n\n`marina logs` always returns structured JSON. Use it to inspect deployment\nevents, runtime invocations, console output, and exceptions. Follow\n`next_cursor` when older entries are needed.\n\nIdentify the intended published version before rolling back, then confirm the\nresult with `marina status --json`.\n";
|
|
1715
4052
|
|
|
1716
4053
|
// ../../skills/marina-deploy/agents/openai.yaml
|
|
1717
4054
|
var openai_default = 'interface:\n display_name: "Marina Deploy"\n short_description: "Build and deploy apps to Marina Cloud"\n default_prompt: "Use $marina-deploy to deploy this project to Marina Cloud."\n';
|
|
@@ -1733,7 +4070,7 @@ function targets(agent) {
|
|
|
1733
4070
|
}));
|
|
1734
4071
|
}
|
|
1735
4072
|
if (agent === "codex" || agent === "claude") return [{ agent, home: agentHomes[agent] }];
|
|
1736
|
-
return Object.entries(agentHomes).filter(([, home]) =>
|
|
4073
|
+
return Object.entries(agentHomes).filter(([, home]) => existsSync4(home)).map(([name, home]) => ({ agent: name, home }));
|
|
1737
4074
|
}
|
|
1738
4075
|
function install(target, update) {
|
|
1739
4076
|
const directory = join4(target.home, "skills", "marina-deploy");
|
|
@@ -1769,56 +4106,14 @@ function installSkills(agent) {
|
|
|
1769
4106
|
return selected.map((target) => install(target, true));
|
|
1770
4107
|
}
|
|
1771
4108
|
|
|
1772
|
-
// package.json
|
|
1773
|
-
var package_default = {
|
|
1774
|
-
name: "@marina-cloud/cli",
|
|
1775
|
-
version: "0.0.3",
|
|
1776
|
-
description: "Command-line client for Marina Cloud",
|
|
1777
|
-
homepage: "https://github.com/marina-hq/marina#readme",
|
|
1778
|
-
bugs: {
|
|
1779
|
-
url: "https://github.com/marina-hq/marina/issues"
|
|
1780
|
-
},
|
|
1781
|
-
license: "Apache-2.0",
|
|
1782
|
-
repository: {
|
|
1783
|
-
type: "git",
|
|
1784
|
-
url: "git+https://github.com/marina-hq/marina.git",
|
|
1785
|
-
directory: "packages/cli"
|
|
1786
|
-
},
|
|
1787
|
-
bin: {
|
|
1788
|
-
marina: "dist/marina.mjs"
|
|
1789
|
-
},
|
|
1790
|
-
files: [
|
|
1791
|
-
"dist"
|
|
1792
|
-
],
|
|
1793
|
-
type: "module",
|
|
1794
|
-
publishConfig: {
|
|
1795
|
-
access: "public",
|
|
1796
|
-
provenance: true
|
|
1797
|
-
},
|
|
1798
|
-
scripts: {
|
|
1799
|
-
build: 'esbuild src/index.ts --bundle --platform=node --target=node22 --format=esm --loader:.md=text --loader:.yaml=text --loader:.html=text --loader:.css=text --loader:.txt=text --outfile=dist/marina.mjs --banner:js="#!/usr/bin/env node" && chmod +x dist/marina.mjs',
|
|
1800
|
-
typecheck: "tsc --noEmit",
|
|
1801
|
-
test: "pnpm build && node --experimental-strip-types --test src/*.test.ts",
|
|
1802
|
-
prepack: "pnpm build"
|
|
1803
|
-
},
|
|
1804
|
-
devDependencies: {
|
|
1805
|
-
"@types/node": "^26.2.0",
|
|
1806
|
-
esbuild: "^0.25.0",
|
|
1807
|
-
fflate: "^0.8.2",
|
|
1808
|
-
typescript: "^5.9.0"
|
|
1809
|
-
},
|
|
1810
|
-
engines: {
|
|
1811
|
-
node: ">=22"
|
|
1812
|
-
}
|
|
1813
|
-
};
|
|
1814
|
-
|
|
1815
4109
|
// src/update.ts
|
|
1816
|
-
|
|
1817
|
-
|
|
4110
|
+
init_package();
|
|
4111
|
+
init_config();
|
|
4112
|
+
var NAG_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
4113
|
+
var parseVersion = (version) => version.replace(/^v/, "").split("-", 1)[0].split(".").map((part) => Number.parseInt(part, 10));
|
|
1818
4114
|
function isNewerVersion(candidate, current) {
|
|
1819
|
-
const
|
|
1820
|
-
const
|
|
1821
|
-
const right = parse(current);
|
|
4115
|
+
const left = parseVersion(candidate);
|
|
4116
|
+
const right = parseVersion(current);
|
|
1822
4117
|
if (left.some(Number.isNaN) || right.some(Number.isNaN)) return false;
|
|
1823
4118
|
for (let index = 0; index < Math.max(left.length, right.length); index += 1) {
|
|
1824
4119
|
const difference = (left[index] ?? 0) - (right[index] ?? 0);
|
|
@@ -1829,40 +4124,28 @@ function isNewerVersion(candidate, current) {
|
|
|
1829
4124
|
async function availableUpdate() {
|
|
1830
4125
|
if (process.env.MARINA_DISABLE_UPDATE_CHECK) return null;
|
|
1831
4126
|
const current = package_default.version;
|
|
4127
|
+
const latest = latestCliVersion();
|
|
4128
|
+
if (!latest || !isNewerVersion(latest, current)) return null;
|
|
1832
4129
|
const profile = readProfile();
|
|
1833
|
-
const
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
});
|
|
1841
|
-
if (response.ok) {
|
|
1842
|
-
const body = await response.json();
|
|
1843
|
-
if (body.version) {
|
|
1844
|
-
latest = body.version;
|
|
1845
|
-
writeProfile({
|
|
1846
|
-
...readProfile(),
|
|
1847
|
-
update: { checked_at: (/* @__PURE__ */ new Date()).toISOString(), latest }
|
|
1848
|
-
});
|
|
1849
|
-
}
|
|
1850
|
-
}
|
|
1851
|
-
} catch {
|
|
1852
|
-
}
|
|
1853
|
-
}
|
|
1854
|
-
return latest && isNewerVersion(latest, current) ? {
|
|
4130
|
+
const notifiedAt = profile.update?.notified_at ? Date.parse(profile.update.notified_at) : Number.NaN;
|
|
4131
|
+
if (Number.isFinite(notifiedAt) && Date.now() - notifiedAt < NAG_INTERVAL_MS) return null;
|
|
4132
|
+
writeProfile({
|
|
4133
|
+
...profile,
|
|
4134
|
+
update: { latest, notified_at: (/* @__PURE__ */ new Date()).toISOString() }
|
|
4135
|
+
});
|
|
4136
|
+
return {
|
|
1855
4137
|
current,
|
|
1856
4138
|
latest,
|
|
1857
4139
|
command: "npm install -g @marina-cloud/cli@latest"
|
|
1858
|
-
}
|
|
4140
|
+
};
|
|
1859
4141
|
}
|
|
1860
4142
|
|
|
1861
4143
|
// src/index.ts
|
|
1862
4144
|
var HELP = `${bold("marina")} \u2014 deploy internal apps
|
|
1863
4145
|
|
|
1864
|
-
marina setup sign in
|
|
1865
|
-
--
|
|
4146
|
+
marina setup sign in through Marina in your browser
|
|
4147
|
+
--skills install the Marina deployment skill
|
|
4148
|
+
--deploy-demo deploy Hello Marina after signing in
|
|
1866
4149
|
marina logout remove the saved credential
|
|
1867
4150
|
marina profile show where this CLI is signed in
|
|
1868
4151
|
marina skills install install or update the Marina deployment skill
|
|
@@ -1873,20 +4156,25 @@ var HELP = `${bold("marina")} \u2014 deploy internal apps
|
|
|
1873
4156
|
--app <slug> deploy into an existing app
|
|
1874
4157
|
marina status [--app <slug>] what is live, and how the last deploy went
|
|
1875
4158
|
marina deploys [--app <slug>] recent deploy attempts, including refusals
|
|
4159
|
+
marina deploys <deploy-id> inspect one attempt, even before an app exists
|
|
4160
|
+
marina logs [--app <slug>] structured JSON runtime invocations, console output, and errors
|
|
4161
|
+
--level <level> debug, info, log, warn, or error
|
|
4162
|
+
--limit <count> 1\u2013200 entries (default: 100)
|
|
4163
|
+
--cursor <cursor> continue from a prior result
|
|
1876
4164
|
marina versions [--app <slug>] version history
|
|
1877
4165
|
marina rollback [--to <hash>] publish a previous version again
|
|
1878
|
-
marina
|
|
1879
|
-
|
|
1880
|
-
|
|
4166
|
+
marina dev [dir] run this app locally against Marina
|
|
4167
|
+
--port <port> listen port (default: 5990)
|
|
4168
|
+
--schedules run scheduled jobs on their local timers
|
|
4169
|
+
marina ai models models marina.ai can generate with, and who pays
|
|
1881
4170
|
marina list apps in your workspace
|
|
1882
4171
|
marina open open this project's app
|
|
1883
4172
|
|
|
1884
|
-
${dim("--json machine-readable result on stdout, progress on stderr")}
|
|
1885
|
-
${dim(`API: ${apiUrl()} (override with MARINA_API)`)}`;
|
|
4173
|
+
${dim("--json machine-readable result on stdout, progress on stderr")}`;
|
|
1886
4174
|
var ANSI_ESCAPE = new RegExp(`${String.fromCodePoint(27)}\\[[0-9;]*m`, "g");
|
|
1887
4175
|
var shouldCheckForUpdates = false;
|
|
1888
4176
|
function targetApp(flag) {
|
|
1889
|
-
const linked = readLink(
|
|
4177
|
+
const linked = readLink(resolve3("."))?.app;
|
|
1890
4178
|
const app = flag ?? linked;
|
|
1891
4179
|
if (!app) {
|
|
1892
4180
|
failure("no_app", "no app here \u2014 pass --app <slug>, or deploy from a linked project");
|
|
@@ -1895,7 +4183,16 @@ function targetApp(flag) {
|
|
|
1895
4183
|
return app;
|
|
1896
4184
|
}
|
|
1897
4185
|
var shortDigest = (digest) => digest?.slice(7, 14) ?? "\u2014";
|
|
1898
|
-
|
|
4186
|
+
function deployAttemptHint(deployId, hasBuildLog) {
|
|
4187
|
+
if (isJsonMode()) return;
|
|
4188
|
+
const command = `marina deploys ${terminalSafeText(deployId)} --json`;
|
|
4189
|
+
note(
|
|
4190
|
+
dim(
|
|
4191
|
+
`${hasBuildLog ? "build log omitted from terminal; " : ""}retrieve structured JSON with: ${command}`
|
|
4192
|
+
)
|
|
4193
|
+
);
|
|
4194
|
+
}
|
|
4195
|
+
async function deploy(dirArg, flags, emitResult = true) {
|
|
1899
4196
|
let dir = null;
|
|
1900
4197
|
let link = null;
|
|
1901
4198
|
let target;
|
|
@@ -1909,20 +4206,32 @@ async function deploy(dirArg, flags) {
|
|
|
1909
4206
|
packed = packDemo();
|
|
1910
4207
|
name = flags.name?.trim() || DEMO_MANIFEST.name;
|
|
1911
4208
|
} else {
|
|
1912
|
-
dir =
|
|
4209
|
+
dir = resolve3(dirArg ?? ".");
|
|
1913
4210
|
link = readLink(dir);
|
|
1914
4211
|
target = flags.app ?? link?.app;
|
|
1915
4212
|
const manifest = readManifest(dir);
|
|
1916
4213
|
name = resolveAppName(dir, flags.name, manifest, link?.name);
|
|
4214
|
+
const builtFrom = buildOutputParent(dir);
|
|
4215
|
+
if (builtFrom) {
|
|
4216
|
+
const refusal = {
|
|
4217
|
+
code: "prebuilt_output",
|
|
4218
|
+
message: `${dir} looks like the build output of ${builtFrom}, not source.`,
|
|
4219
|
+
action: "Deploy the project root; Marina runs the build itself."
|
|
4220
|
+
};
|
|
4221
|
+
failure("refused", refusal.message, { refusal });
|
|
4222
|
+
if (!isJsonMode()) note(` ${terminalSafeText(refusal.action)}`);
|
|
4223
|
+
process.exit(3);
|
|
4224
|
+
}
|
|
1917
4225
|
packed = pack(dir);
|
|
1918
4226
|
}
|
|
1919
|
-
for (const secret of packed.skippedSecrets)
|
|
4227
|
+
for (const secret of packed.skippedSecrets)
|
|
4228
|
+
say(dim(`skipped ${secret} \u2014 credential files are never deployed`));
|
|
1920
4229
|
const progress = createProgress();
|
|
1921
4230
|
const size = `${String(packed.fileCount)} files, ${String(Math.round(packed.totalBytes / 1024))} KB`;
|
|
1922
4231
|
progress.update("uploading", `Uploading ${name} (${size})`);
|
|
1923
4232
|
let started;
|
|
1924
4233
|
try {
|
|
1925
|
-
started = await startDeploy(packed.zip, name, target);
|
|
4234
|
+
started = await startDeploy(packed.zip, name, target, link?.base_revision);
|
|
1926
4235
|
} catch (error) {
|
|
1927
4236
|
progress.clear();
|
|
1928
4237
|
if (error instanceof ApiError && error.code === "not_found" && link) {
|
|
@@ -1940,32 +4249,43 @@ async function deploy(dirArg, flags) {
|
|
|
1940
4249
|
deployed = await pollDeploy(started.id, (current) => {
|
|
1941
4250
|
if (current.status !== "queued" && current.status !== "building") return;
|
|
1942
4251
|
const elapsed = Math.max(1, Math.round((Date.now() - deployedAt) / 1e3));
|
|
1943
|
-
const message = current.status === "queued" ? `Waiting for a deploy worker (${String(elapsed)}s)` : `Building and verifying (${String(elapsed)}s)`;
|
|
4252
|
+
const message = current.build_message ? `${terminalSafeText(current.build_message)} (${String(elapsed)}s)` : current.status === "queued" ? `Waiting for a deploy worker (${String(elapsed)}s)` : `Building and verifying (${String(elapsed)}s)`;
|
|
1944
4253
|
progress.update(current.status, message);
|
|
1945
4254
|
});
|
|
1946
4255
|
} finally {
|
|
1947
4256
|
progress.clear();
|
|
1948
4257
|
}
|
|
1949
4258
|
if (deployed.status === "refused" && deployed.refusal) {
|
|
1950
|
-
say(`${red("refused")} ${deployed.refusal.message}`);
|
|
1951
|
-
if (deployed.refusal.action) say(` ${deployed.refusal.action}`);
|
|
1952
|
-
if (deployed.build_log) {
|
|
1953
|
-
say(dim("---- build log (tail) ----"));
|
|
1954
|
-
say(dim(deployed.build_log.split("\n").slice(-20).join("\n")));
|
|
1955
|
-
}
|
|
1956
4259
|
failure("refused", deployed.refusal.message, {
|
|
4260
|
+
deploy_id: deployed.id,
|
|
1957
4261
|
refusal: deployed.refusal,
|
|
1958
|
-
build_log: deployed.build_log
|
|
4262
|
+
build_log: deployed.build_log,
|
|
4263
|
+
build_phase: deployed.build_phase,
|
|
4264
|
+
build_message: deployed.build_message
|
|
1959
4265
|
});
|
|
4266
|
+
if (!isJsonMode() && deployed.refusal.action) {
|
|
4267
|
+
note(` ${terminalSafeText(deployed.refusal.action)}`);
|
|
4268
|
+
}
|
|
4269
|
+
deployAttemptHint(deployed.id, deployed.build_log !== null);
|
|
1960
4270
|
process.exit(3);
|
|
1961
4271
|
}
|
|
1962
4272
|
if (deployed.status === "failed") {
|
|
1963
|
-
failure("failed", deployed.error ?? "unknown error"
|
|
4273
|
+
failure("failed", deployed.error ?? "unknown error", {
|
|
4274
|
+
deploy_id: deployed.id,
|
|
4275
|
+
build_log: deployed.build_log,
|
|
4276
|
+
build_phase: deployed.build_phase,
|
|
4277
|
+
build_message: deployed.build_message
|
|
4278
|
+
});
|
|
4279
|
+
deployAttemptHint(deployed.id, deployed.build_log !== null);
|
|
1964
4280
|
process.exit(1);
|
|
1965
4281
|
}
|
|
1966
4282
|
const slug = deployed.app_slug ?? "";
|
|
1967
|
-
if (dir && (!link || link.app !== slug || link.name !== name)) {
|
|
1968
|
-
writeLink(dir, {
|
|
4283
|
+
if (dir && (!link || link.app !== slug || link.name !== name || link.base_revision !== (deployed.source_revision ?? void 0))) {
|
|
4284
|
+
writeLink(dir, {
|
|
4285
|
+
app: slug,
|
|
4286
|
+
name,
|
|
4287
|
+
...deployed.source_revision ? { base_revision: deployed.source_revision } : {}
|
|
4288
|
+
});
|
|
1969
4289
|
}
|
|
1970
4290
|
const published = deployed.version_state === "published";
|
|
1971
4291
|
say(
|
|
@@ -1984,30 +4304,35 @@ async function deploy(dirArg, flags) {
|
|
|
1984
4304
|
if (deployed.version_url) say(` try this version: ${deployed.version_url}`);
|
|
1985
4305
|
say(dim(" a publisher can approve it from the Marina inbox"));
|
|
1986
4306
|
}
|
|
1987
|
-
|
|
4307
|
+
const payload = {
|
|
1988
4308
|
command: "deploy",
|
|
1989
4309
|
status: "succeeded",
|
|
4310
|
+
deploy_id: deployed.id,
|
|
1990
4311
|
app: slug,
|
|
1991
4312
|
version: deployed.version_number,
|
|
1992
4313
|
state: deployed.version_state,
|
|
1993
4314
|
url: deployed.url,
|
|
1994
4315
|
version_url: deployed.version_url,
|
|
4316
|
+
build_log: deployed.build_log,
|
|
1995
4317
|
preparation: deployed.preparation,
|
|
1996
4318
|
live: published,
|
|
1997
4319
|
demo: dirArg === "demo"
|
|
1998
|
-
}
|
|
4320
|
+
};
|
|
4321
|
+
if (emitResult) result(payload);
|
|
4322
|
+
return payload;
|
|
1999
4323
|
}
|
|
2000
4324
|
async function status(appFlag) {
|
|
2001
4325
|
const app = targetApp(appFlag);
|
|
2002
|
-
const [detail,
|
|
2003
|
-
const last =
|
|
4326
|
+
const [detail, history] = await Promise.all([getApp(app), listDeploys(app, 1)]);
|
|
4327
|
+
const last = history[0];
|
|
2004
4328
|
say(`${bold(detail.app.name)} ${dim(`(${detail.app.slug})`)}`);
|
|
2005
4329
|
say(` ${detail.app.current_version_id ? green(detail.app.status) : dim("nothing published")}`);
|
|
2006
4330
|
if (detail.app.current_version_id) say(` ${detail.url}`);
|
|
2007
4331
|
if (detail.app.archived_at) say(` ${red("archived")} \u2014 it isn't serving`);
|
|
2008
4332
|
if (last) {
|
|
2009
4333
|
const outcome = last.status === "succeeded" ? green("succeeded") : last.status === "queued" || last.status === "building" ? last.status : red(last.status);
|
|
2010
|
-
|
|
4334
|
+
const version = last.version_number == null ? "" : ` v${String(last.version_number)}`;
|
|
4335
|
+
say(` last deploy: ${outcome}${version}${last.refusal ? ` \u2014 ${last.refusal.message}` : ""}`);
|
|
2011
4336
|
}
|
|
2012
4337
|
result({
|
|
2013
4338
|
command: "status",
|
|
@@ -2020,17 +4345,55 @@ async function status(appFlag) {
|
|
|
2020
4345
|
last_deploy: last ?? null
|
|
2021
4346
|
});
|
|
2022
4347
|
}
|
|
2023
|
-
|
|
4348
|
+
function deployOutcome(attempt) {
|
|
4349
|
+
return attempt.refusal?.message ?? attempt.error ?? attempt.status;
|
|
4350
|
+
}
|
|
4351
|
+
async function deploys(appFlag, deployId) {
|
|
4352
|
+
if (deployId) {
|
|
4353
|
+
const attempt = await getDeploy(deployId);
|
|
4354
|
+
const mark = attempt.status === "succeeded" ? green("ok") : red(attempt.status);
|
|
4355
|
+
say(`${mark} ${dim(attempt.id)} \u2014 ${terminalSafeText(deployOutcome(attempt))}`);
|
|
4356
|
+
if (attempt.build_log) deployAttemptHint(attempt.id, true);
|
|
4357
|
+
result({ command: "deploys.show", deploy: attempt });
|
|
4358
|
+
return;
|
|
4359
|
+
}
|
|
2024
4360
|
const app = targetApp(appFlag);
|
|
2025
4361
|
const rows = await listDeploys(app);
|
|
2026
4362
|
if (rows.length === 0) say("no deploys yet");
|
|
2027
4363
|
for (const row of rows) {
|
|
2028
4364
|
const mark = row.status === "succeeded" ? green("ok") : red(row.status);
|
|
2029
|
-
const why = row.refusal ? ` \u2014 ${row.refusal.message}` : row.error ? ` \u2014 ${row.error}` : "";
|
|
2030
|
-
|
|
4365
|
+
const why = row.refusal ? ` \u2014 ${terminalSafeText(row.refusal.message)}` : row.error ? ` \u2014 ${terminalSafeText(row.error)}` : "";
|
|
4366
|
+
const version = row.version_number == null ? "" : ` v${String(row.version_number)}`;
|
|
4367
|
+
say(`${mark}${version} ${dim(row.created_at)} ${dim(`via ${row.source}`)}${why}`);
|
|
2031
4368
|
}
|
|
4369
|
+
const failedWithLog = rows.find((row) => row.status !== "succeeded" && row.build_log);
|
|
4370
|
+
if (failedWithLog) deployAttemptHint(failedWithLog.id, true);
|
|
2032
4371
|
result({ command: "deploys", app, deploys: rows });
|
|
2033
4372
|
}
|
|
4373
|
+
var LOG_LEVELS = /* @__PURE__ */ new Set(["debug", "info", "log", "warn", "error"]);
|
|
4374
|
+
async function logs(appFlag, flags) {
|
|
4375
|
+
const app = targetApp(appFlag);
|
|
4376
|
+
const parsedLimit = flags.limit === void 0 ? 100 : Number(flags.limit);
|
|
4377
|
+
if (!Number.isInteger(parsedLimit) || parsedLimit < 1 || parsedLimit > 200) {
|
|
4378
|
+
failure("invalid_input", "--limit must be an integer from 1 to 200");
|
|
4379
|
+
process.exit(1);
|
|
4380
|
+
}
|
|
4381
|
+
if (flags.level && !LOG_LEVELS.has(flags.level)) {
|
|
4382
|
+
failure("invalid_input", "--level must be debug, info, log, warn, or error");
|
|
4383
|
+
process.exit(1);
|
|
4384
|
+
}
|
|
4385
|
+
const response = await listRuntimeLogs(app, {
|
|
4386
|
+
limit: parsedLimit,
|
|
4387
|
+
cursor: flags.cursor,
|
|
4388
|
+
level: flags.level
|
|
4389
|
+
});
|
|
4390
|
+
result({
|
|
4391
|
+
command: "logs",
|
|
4392
|
+
app,
|
|
4393
|
+
logs: response.logs,
|
|
4394
|
+
next_cursor: response.nextCursor
|
|
4395
|
+
});
|
|
4396
|
+
}
|
|
2034
4397
|
async function versions(appFlag) {
|
|
2035
4398
|
const app = targetApp(appFlag);
|
|
2036
4399
|
const [rows, detail] = await Promise.all([listVersions(app), getApp(app)]);
|
|
@@ -2072,70 +4435,40 @@ async function rollback(appFlag, to) {
|
|
|
2072
4435
|
url: detail.url
|
|
2073
4436
|
});
|
|
2074
4437
|
}
|
|
2075
|
-
async function secrets(appFlag, argv, plain) {
|
|
2076
|
-
const app = targetApp(appFlag);
|
|
2077
|
-
const [action, argument] = argv;
|
|
2078
|
-
if (action === "set") {
|
|
2079
|
-
const eq = argument?.indexOf("=") ?? -1;
|
|
2080
|
-
if (!argument || eq < 1) {
|
|
2081
|
-
failure("invalid_input", "usage: marina secrets set KEY=value");
|
|
2082
|
-
process.exit(1);
|
|
2083
|
-
}
|
|
2084
|
-
const key = argument.slice(0, eq);
|
|
2085
|
-
await setEnv(app, key, argument.slice(eq + 1), !plain);
|
|
2086
|
-
say(`${green("ok")} set ${bold(key)}${plain ? "" : dim(" (secret)")}`);
|
|
2087
|
-
result({ command: "secrets.set", app, key, kind: plain ? "plain" : "secret" });
|
|
2088
|
-
return;
|
|
2089
|
-
}
|
|
2090
|
-
if (action === "rm") {
|
|
2091
|
-
if (!argument) {
|
|
2092
|
-
failure("invalid_input", "usage: marina secrets rm KEY");
|
|
2093
|
-
process.exit(1);
|
|
2094
|
-
}
|
|
2095
|
-
await deleteEnv(app, argument);
|
|
2096
|
-
say(`${green("ok")} removed ${bold(argument)}`);
|
|
2097
|
-
result({ command: "secrets.rm", app, key: argument, removed: true });
|
|
2098
|
-
return;
|
|
2099
|
-
}
|
|
2100
|
-
const rows = await listEnv(app);
|
|
2101
|
-
if (rows.length === 0) say("nothing set");
|
|
2102
|
-
for (const row of rows) {
|
|
2103
|
-
say(
|
|
2104
|
-
`${bold(row.key)} ${row.kind === "secret" ? dim("secret \u2014 write-only") : row.value ?? ""}`
|
|
2105
|
-
);
|
|
2106
|
-
}
|
|
2107
|
-
result({ command: "secrets.list", app, env: rows });
|
|
2108
|
-
}
|
|
2109
4438
|
async function main() {
|
|
2110
4439
|
setJsonMode(process.argv.slice(2).includes("--json"));
|
|
2111
4440
|
const { positionals, values } = parseArgs({
|
|
2112
4441
|
allowPositionals: true,
|
|
2113
4442
|
options: {
|
|
2114
|
-
token: { type: "string" },
|
|
2115
4443
|
name: { type: "string" },
|
|
2116
4444
|
app: { type: "string" },
|
|
2117
4445
|
to: { type: "string" },
|
|
2118
|
-
|
|
4446
|
+
level: { type: "string" },
|
|
4447
|
+
limit: { type: "string" },
|
|
4448
|
+
cursor: { type: "string" },
|
|
2119
4449
|
agent: { type: "string" },
|
|
4450
|
+
port: { type: "string" },
|
|
4451
|
+
schedules: { type: "boolean" },
|
|
4452
|
+
dir: { type: "string" },
|
|
4453
|
+
skills: { type: "boolean" },
|
|
4454
|
+
"deploy-demo": { type: "boolean" },
|
|
2120
4455
|
json: { type: "boolean" },
|
|
2121
4456
|
help: { type: "boolean", short: "h" }
|
|
2122
4457
|
}
|
|
2123
4458
|
});
|
|
2124
|
-
setJsonMode(values.json === true);
|
|
2125
4459
|
const command = positionals[0];
|
|
4460
|
+
setJsonMode(values.json === true || command === "logs");
|
|
2126
4461
|
if (values.help || !command) {
|
|
2127
4462
|
say(HELP);
|
|
2128
4463
|
result({ command: "help", usage: HELP.replaceAll(ANSI_ESCAPE, "") });
|
|
2129
4464
|
return;
|
|
2130
4465
|
}
|
|
4466
|
+
await resolveControlPlane();
|
|
2131
4467
|
shouldCheckForUpdates = true;
|
|
2132
4468
|
switch (command) {
|
|
2133
4469
|
case "setup":
|
|
2134
4470
|
case "login": {
|
|
2135
|
-
|
|
2136
|
-
if (token) {
|
|
2137
|
-
saveToken(token);
|
|
2138
|
-
} else {
|
|
4471
|
+
if (!process.env.MARINA_TOKEN) {
|
|
2139
4472
|
let lastReportedRemaining = -1;
|
|
2140
4473
|
const signedIn = await loginWithBrowser(
|
|
2141
4474
|
(url) => {
|
|
@@ -2166,11 +4499,18 @@ async function main() {
|
|
|
2166
4499
|
saveToken(signedIn.token);
|
|
2167
4500
|
}
|
|
2168
4501
|
say(`${green("ok")} signed in ${dim(`(${apiUrl()})`)}`);
|
|
4502
|
+
const installed = values.skills ? installSkills(values.agent) : void 0;
|
|
4503
|
+
for (const skill of installed ?? []) {
|
|
4504
|
+
say(`${green("ok")} ${skill.status} Marina skill for ${skill.agent} ${dim(skill.path)}`);
|
|
4505
|
+
}
|
|
4506
|
+
const demo = values["deploy-demo"] ? await deploy("demo", { name: values.name }, false) : void 0;
|
|
2169
4507
|
result({
|
|
2170
4508
|
command: "setup",
|
|
2171
4509
|
signed_in: true,
|
|
2172
4510
|
api: apiUrl(),
|
|
2173
|
-
profile: profilePath()
|
|
4511
|
+
profile: profilePath(),
|
|
4512
|
+
...installed ? { skills: installed } : {},
|
|
4513
|
+
...demo ? { deploy: demo } : {}
|
|
2174
4514
|
});
|
|
2175
4515
|
return;
|
|
2176
4516
|
}
|
|
@@ -2188,6 +4528,20 @@ async function main() {
|
|
|
2188
4528
|
});
|
|
2189
4529
|
return;
|
|
2190
4530
|
}
|
|
4531
|
+
case "dev": {
|
|
4532
|
+
const { runDev: runDev2 } = await Promise.resolve().then(() => (init_dev(), dev_exports));
|
|
4533
|
+
try {
|
|
4534
|
+
await runDev2({
|
|
4535
|
+
dir: values.dir ?? positionals[1],
|
|
4536
|
+
port: values.port,
|
|
4537
|
+
schedules: values.schedules === true
|
|
4538
|
+
});
|
|
4539
|
+
} catch (error) {
|
|
4540
|
+
failure("dev_failed", error.message);
|
|
4541
|
+
process.exitCode = 1;
|
|
4542
|
+
}
|
|
4543
|
+
return;
|
|
4544
|
+
}
|
|
2191
4545
|
case "profile": {
|
|
2192
4546
|
const profile = readProfile();
|
|
2193
4547
|
const source = process.env.MARINA_TOKEN ? "environment" : profile.token ? "profile" : null;
|
|
@@ -2214,6 +4568,23 @@ async function main() {
|
|
|
2214
4568
|
result({ command: "skills.install", skills: installed });
|
|
2215
4569
|
return;
|
|
2216
4570
|
}
|
|
4571
|
+
case "ai": {
|
|
4572
|
+
if (positionals[1] !== "models") {
|
|
4573
|
+
failure("invalid_input", "usage: marina ai models");
|
|
4574
|
+
process.exit(1);
|
|
4575
|
+
}
|
|
4576
|
+
const view = await aiModels();
|
|
4577
|
+
say(`${bold("fast")} ${view.aliases.fast ?? dim("(broker unreachable)")}`);
|
|
4578
|
+
say(`${bold("smart")} ${view.aliases.smart ?? dim("(broker unreachable)")}`);
|
|
4579
|
+
if (view.models.length === 0) {
|
|
4580
|
+
say(dim("no library models offered on this deployment"));
|
|
4581
|
+
}
|
|
4582
|
+
for (const model of view.models) say(` ${model}`);
|
|
4583
|
+
const billing = view.billing === "workspace" ? "generations bill this workspace's OpenRouter account (BYOK)" : view.billing === "platform" ? "generations bill Marina's platform key \u2014 connect OpenRouter to use your own" : "no AI billing key: connect OpenRouter in Settings \u2192 Connections";
|
|
4584
|
+
say(dim(billing));
|
|
4585
|
+
result({ command: "ai.models", ...view });
|
|
4586
|
+
return;
|
|
4587
|
+
}
|
|
2217
4588
|
case "deploy":
|
|
2218
4589
|
await deploy(positionals[1], values);
|
|
2219
4590
|
return;
|
|
@@ -2222,7 +4593,10 @@ async function main() {
|
|
|
2222
4593
|
return;
|
|
2223
4594
|
case "deploys":
|
|
2224
4595
|
case "deployments":
|
|
2225
|
-
await deploys(values.app);
|
|
4596
|
+
await deploys(values.app, positionals[1]);
|
|
4597
|
+
return;
|
|
4598
|
+
case "logs":
|
|
4599
|
+
await logs(values.app, values);
|
|
2226
4600
|
return;
|
|
2227
4601
|
case "versions":
|
|
2228
4602
|
await versions(values.app);
|
|
@@ -2230,9 +4604,6 @@ async function main() {
|
|
|
2230
4604
|
case "rollback":
|
|
2231
4605
|
await rollback(values.app, values.to);
|
|
2232
4606
|
return;
|
|
2233
|
-
case "secrets":
|
|
2234
|
-
await secrets(values.app, positionals.slice(1), values.plain === true);
|
|
2235
|
-
return;
|
|
2236
4607
|
case "list": {
|
|
2237
4608
|
const apps = await listApps();
|
|
2238
4609
|
if (apps.length === 0) say("no apps yet \u2014 `marina deploy` one");
|
|
@@ -2266,8 +4637,8 @@ async function run() {
|
|
|
2266
4637
|
if (!shouldCheckForUpdates) return;
|
|
2267
4638
|
const update = await availableUpdate();
|
|
2268
4639
|
if (update) {
|
|
2269
|
-
const
|
|
2270
|
-
note(isJsonMode() ?
|
|
4640
|
+
const detail = `Marina CLI ${update.latest} is available (current ${update.current}) \u2014 ${update.command}`;
|
|
4641
|
+
note(isJsonMode() ? detail : `${bold("Update available:")} ${detail}`);
|
|
2271
4642
|
}
|
|
2272
4643
|
}
|
|
2273
4644
|
run().catch((error) => {
|