@marina-cloud/cli 0.0.4 → 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 +26 -3
- package/dist/marina.mjs +2718 -435
- 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,64 +120,6 @@ import {
|
|
|
16
120
|
} from "node:fs";
|
|
17
121
|
import { homedir } from "node:os";
|
|
18
122
|
import { join } from "node:path";
|
|
19
|
-
|
|
20
|
-
// package.json
|
|
21
|
-
var package_default = {
|
|
22
|
-
name: "@marina-cloud/cli",
|
|
23
|
-
version: "0.0.4",
|
|
24
|
-
description: "Command-line client for Marina Cloud",
|
|
25
|
-
homepage: "https://github.com/marina-hq/marina#readme",
|
|
26
|
-
bugs: {
|
|
27
|
-
url: "https://github.com/marina-hq/marina/issues"
|
|
28
|
-
},
|
|
29
|
-
license: "Apache-2.0",
|
|
30
|
-
repository: {
|
|
31
|
-
type: "git",
|
|
32
|
-
url: "git+https://github.com/marina-hq/marina.git",
|
|
33
|
-
directory: "packages/cli"
|
|
34
|
-
},
|
|
35
|
-
bin: {
|
|
36
|
-
marina: "dist/marina.mjs"
|
|
37
|
-
},
|
|
38
|
-
files: [
|
|
39
|
-
"dist"
|
|
40
|
-
],
|
|
41
|
-
type: "module",
|
|
42
|
-
publishConfig: {
|
|
43
|
-
access: "public",
|
|
44
|
-
provenance: true
|
|
45
|
-
},
|
|
46
|
-
scripts: {
|
|
47
|
-
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',
|
|
48
|
-
typecheck: "tsc --noEmit",
|
|
49
|
-
test: "pnpm build && node --experimental-strip-types --test src/*.test.ts",
|
|
50
|
-
prepack: "pnpm build"
|
|
51
|
-
},
|
|
52
|
-
devDependencies: {
|
|
53
|
-
"@types/node": "^26.2.0",
|
|
54
|
-
esbuild: "^0.25.0",
|
|
55
|
-
fflate: "^0.8.2",
|
|
56
|
-
typescript: "^5.9.0"
|
|
57
|
-
},
|
|
58
|
-
engines: {
|
|
59
|
-
node: ">=22"
|
|
60
|
-
}
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
// src/identity.ts
|
|
64
|
-
var CLI_VERSION = package_default.version;
|
|
65
|
-
var cliRequestHeaders = () => ({
|
|
66
|
-
"user-agent": `marina-cli/${CLI_VERSION}`,
|
|
67
|
-
"x-marina-client": "cli",
|
|
68
|
-
"x-marina-client-version": CLI_VERSION
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
// src/config.ts
|
|
72
|
-
var MARINA_HOME = process.env.MARINA_HOME ?? join(homedir(), ".marina");
|
|
73
|
-
var PROFILE = join(MARINA_HOME, "profile");
|
|
74
|
-
var PRODUCTION_CONTROL_PLANE = "https://marina.cloud";
|
|
75
|
-
var CONTROL_PLANE_CACHE_MS = 60 * 60 * 1e3;
|
|
76
|
-
var resolvedControlPlane = null;
|
|
77
123
|
function normalizeApiUrl(value) {
|
|
78
124
|
const url = new URL(value);
|
|
79
125
|
if (url.protocol !== "https:" && url.protocol !== "http:") {
|
|
@@ -87,9 +133,6 @@ function normalizeApiUrl(value) {
|
|
|
87
133
|
}
|
|
88
134
|
return url.origin;
|
|
89
135
|
}
|
|
90
|
-
var controlPlaneHost = () => normalizeApiUrl(
|
|
91
|
-
process.env.MARINA_CONTROL_PLANE ?? readProfile().control_plane_host ?? PRODUCTION_CONTROL_PLANE
|
|
92
|
-
);
|
|
93
136
|
function cachedControlPlane(host = controlPlaneHost()) {
|
|
94
137
|
const cached = readProfile().control_plane;
|
|
95
138
|
if (!cached || cached.host !== host) return null;
|
|
@@ -101,15 +144,6 @@ function cachedControlPlane(host = controlPlaneHost()) {
|
|
|
101
144
|
return null;
|
|
102
145
|
}
|
|
103
146
|
}
|
|
104
|
-
var apiUrl = () => normalizeApiUrl(
|
|
105
|
-
process.env.MARINA_API ?? resolvedControlPlane?.api_url ?? cachedControlPlane()?.api_url ?? controlPlaneHost()
|
|
106
|
-
);
|
|
107
|
-
var dashboardUrl = () => {
|
|
108
|
-
if (process.env.MARINA_DASHBOARD) return normalizeApiUrl(process.env.MARINA_DASHBOARD);
|
|
109
|
-
return normalizeApiUrl(
|
|
110
|
-
resolvedControlPlane?.dashboard_url ?? cachedControlPlane()?.dashboard_url ?? controlPlaneHost()
|
|
111
|
-
);
|
|
112
|
-
};
|
|
113
147
|
async function resolveControlPlane() {
|
|
114
148
|
const host = controlPlaneHost();
|
|
115
149
|
const cached = cachedControlPlane(host);
|
|
@@ -161,7 +195,6 @@ async function resolveControlPlane() {
|
|
|
161
195
|
function latestCliVersion() {
|
|
162
196
|
return resolvedControlPlane?.latest_cli_version ?? cachedControlPlane()?.latest_cli_version ?? null;
|
|
163
197
|
}
|
|
164
|
-
var profilePath = () => PROFILE;
|
|
165
198
|
function readProfile() {
|
|
166
199
|
try {
|
|
167
200
|
return JSON.parse(readFileSync(PROFILE, "utf8"));
|
|
@@ -195,145 +228,2304 @@ function clearToken() {
|
|
|
195
228
|
}
|
|
196
229
|
return;
|
|
197
230
|
}
|
|
198
|
-
writeProfile(rest);
|
|
231
|
+
writeProfile(rest);
|
|
232
|
+
}
|
|
233
|
+
function readLink(dir) {
|
|
234
|
+
try {
|
|
235
|
+
return JSON.parse(readFileSync(join(dir, LINK), "utf8"));
|
|
236
|
+
} catch {
|
|
237
|
+
return null;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
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}`);
|
|
199
2247
|
}
|
|
200
|
-
|
|
201
|
-
|
|
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;
|
|
202
2254
|
try {
|
|
203
|
-
|
|
2255
|
+
value = JSON.parse(readFileSync6(path, "utf8"));
|
|
204
2256
|
} catch {
|
|
205
|
-
|
|
2257
|
+
fail("is not valid JSON");
|
|
206
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
|
+
};
|
|
207
2307
|
}
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
}
|
|
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
|
+
});
|
|
213
2316
|
|
|
214
|
-
// src/
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
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
|
+
};
|
|
223
2409
|
}
|
|
224
|
-
};
|
|
225
|
-
|
|
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) {
|
|
226
2420
|
const token = getToken();
|
|
227
|
-
if (!token)
|
|
228
|
-
|
|
229
|
-
...init,
|
|
230
|
-
headers: { ...cliRequestHeaders(), authorization: `Bearer ${token}`, ...init?.headers }
|
|
231
|
-
});
|
|
232
|
-
const body = await res.json().catch(() => ({}));
|
|
233
|
-
if (!res.ok) {
|
|
234
|
-
throw new ApiError(
|
|
235
|
-
body.error?.code ?? "error",
|
|
236
|
-
body.error?.message ?? `request failed (${String(res.status)})`,
|
|
237
|
-
res.status
|
|
238
|
-
);
|
|
2421
|
+
if (!token) {
|
|
2422
|
+
throw new Error("not signed in \u2014 run `marina setup` first");
|
|
239
2423
|
}
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
res = await fetch(`${apiUrl()}/cli/auth/exchange`, {
|
|
246
|
-
method: "POST",
|
|
247
|
-
headers: { ...cliRequestHeaders(), "content-type": "application/json" },
|
|
248
|
-
body: JSON.stringify({ code, code_verifier: codeVerifier }),
|
|
249
|
-
signal: AbortSignal.timeout(LOGIN_EXCHANGE_TIMEOUT_MS)
|
|
250
|
-
});
|
|
251
|
-
} catch (error) {
|
|
252
|
-
if (error.name === "TimeoutError") {
|
|
253
|
-
throw new ApiError(
|
|
254
|
-
"login_timeout",
|
|
255
|
-
"the login confirmation request timed out after 15 seconds \u2014 run `marina setup` to try again",
|
|
256
|
-
408
|
|
257
|
-
);
|
|
258
|
-
}
|
|
259
|
-
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");
|
|
260
2429
|
}
|
|
261
|
-
const
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
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
|
+
)
|
|
267
2441
|
);
|
|
268
2442
|
}
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
form.set("source", "cli");
|
|
280
|
-
if (app) form.set("app", app);
|
|
281
|
-
const res = await request("/v1/deploys", {
|
|
282
|
-
method: "POST",
|
|
283
|
-
body: form
|
|
284
|
-
});
|
|
285
|
-
return res.deploy;
|
|
286
|
-
}
|
|
287
|
-
async function pollDeploy(id, onProgress = () => void 0) {
|
|
288
|
-
for (; ; ) {
|
|
289
|
-
const { deploy: deploy2 } = await request(`/v1/deploys/${id}`);
|
|
290
|
-
onProgress(deploy2);
|
|
291
|
-
if (deploy2.status !== "queued" && deploy2.status !== "building") return deploy2;
|
|
292
|
-
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
|
+
);
|
|
293
2453
|
}
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
return res.versions;
|
|
308
|
-
}
|
|
309
|
-
async function listDeploys(app, limit = 10) {
|
|
310
|
-
const res = await request(
|
|
311
|
-
`/v1/apps/${encodeURIComponent(app)}/deploys?limit=${String(limit)}`
|
|
312
|
-
);
|
|
313
|
-
return res.deploys;
|
|
314
|
-
}
|
|
315
|
-
async function restoreVersion(versionId) {
|
|
316
|
-
const res = await request(`/v1/versions/${versionId}/restore`, {
|
|
317
|
-
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 }
|
|
318
2467
|
});
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
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
|
+
}
|
|
330
2483
|
});
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
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);
|
|
335
2504
|
});
|
|
336
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";
|
|
337
2529
|
|
|
338
2530
|
// ../../node_modules/.pnpm/fflate@0.8.3/node_modules/fflate/esm/index.mjs
|
|
339
2531
|
import { createRequire } from "module";
|
|
@@ -422,13 +2614,13 @@ var fdeb = new u8([
|
|
|
422
2614
|
var clim = new u8([16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15]);
|
|
423
2615
|
var freb = function(eb, start) {
|
|
424
2616
|
var b = new u16(31);
|
|
425
|
-
for (var
|
|
426
|
-
b[
|
|
2617
|
+
for (var i2 = 0; i2 < 31; ++i2) {
|
|
2618
|
+
b[i2] = start += 1 << eb[i2 - 1];
|
|
427
2619
|
}
|
|
428
2620
|
var r = new i32(b[30]);
|
|
429
|
-
for (var
|
|
430
|
-
for (var j = b[
|
|
431
|
-
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;
|
|
432
2624
|
}
|
|
433
2625
|
}
|
|
434
2626
|
return { b, r };
|
|
@@ -451,25 +2643,25 @@ var x;
|
|
|
451
2643
|
var i;
|
|
452
2644
|
var hMap = (function(cd, mb, r) {
|
|
453
2645
|
var s = cd.length;
|
|
454
|
-
var
|
|
2646
|
+
var i2 = 0;
|
|
455
2647
|
var l = new u16(mb);
|
|
456
|
-
for (;
|
|
457
|
-
if (cd[
|
|
458
|
-
++l[cd[
|
|
2648
|
+
for (; i2 < s; ++i2) {
|
|
2649
|
+
if (cd[i2])
|
|
2650
|
+
++l[cd[i2] - 1];
|
|
459
2651
|
}
|
|
460
2652
|
var le = new u16(mb);
|
|
461
|
-
for (
|
|
462
|
-
le[
|
|
2653
|
+
for (i2 = 1; i2 < mb; ++i2) {
|
|
2654
|
+
le[i2] = le[i2 - 1] + l[i2 - 1] << 1;
|
|
463
2655
|
}
|
|
464
2656
|
var co;
|
|
465
2657
|
if (r) {
|
|
466
2658
|
co = new u16(1 << mb);
|
|
467
2659
|
var rvb = 15 - mb;
|
|
468
|
-
for (
|
|
469
|
-
if (cd[
|
|
470
|
-
var sv =
|
|
471
|
-
var r_1 = mb - cd[
|
|
472
|
-
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;
|
|
473
2665
|
for (var m = v | (1 << r_1) - 1; v <= m; ++v) {
|
|
474
2666
|
co[rev[v] >> rvb] = sv;
|
|
475
2667
|
}
|
|
@@ -477,9 +2669,9 @@ var hMap = (function(cd, mb, r) {
|
|
|
477
2669
|
}
|
|
478
2670
|
} else {
|
|
479
2671
|
co = new u16(s);
|
|
480
|
-
for (
|
|
481
|
-
if (cd[
|
|
482
|
-
co[
|
|
2672
|
+
for (i2 = 0; i2 < s; ++i2) {
|
|
2673
|
+
if (cd[i2]) {
|
|
2674
|
+
co[i2] = rev[le[cd[i2] - 1]++] >> 15 - cd[i2];
|
|
483
2675
|
}
|
|
484
2676
|
}
|
|
485
2677
|
}
|
|
@@ -556,9 +2748,9 @@ var wbits16 = function(d, p, v) {
|
|
|
556
2748
|
};
|
|
557
2749
|
var hTree = function(d, mb) {
|
|
558
2750
|
var t = [];
|
|
559
|
-
for (var
|
|
560
|
-
if (d[
|
|
561
|
-
t.push({ s:
|
|
2751
|
+
for (var i2 = 0; i2 < d.length; ++i2) {
|
|
2752
|
+
if (d[i2])
|
|
2753
|
+
t.push({ s: i2, f: d[i2] });
|
|
562
2754
|
}
|
|
563
2755
|
var s = t.length;
|
|
564
2756
|
var t2 = t.slice();
|
|
@@ -573,28 +2765,28 @@ var hTree = function(d, mb) {
|
|
|
573
2765
|
return a.f - b.f;
|
|
574
2766
|
});
|
|
575
2767
|
t.push({ s: -1, f: 25001 });
|
|
576
|
-
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;
|
|
577
2769
|
t[0] = { s: -1, f: l.f + r.f, l, r };
|
|
578
2770
|
while (i1 != s - 1) {
|
|
579
|
-
l = t[t[i0].f < t[
|
|
580
|
-
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++];
|
|
581
2773
|
t[i1++] = { s: -1, f: l.f + r.f, l, r };
|
|
582
2774
|
}
|
|
583
2775
|
var maxSym = t2[0].s;
|
|
584
|
-
for (var
|
|
585
|
-
if (t2[
|
|
586
|
-
maxSym = t2[
|
|
2776
|
+
for (var i2 = 1; i2 < s; ++i2) {
|
|
2777
|
+
if (t2[i2].s > maxSym)
|
|
2778
|
+
maxSym = t2[i2].s;
|
|
587
2779
|
}
|
|
588
2780
|
var tr = new u16(maxSym + 1);
|
|
589
2781
|
var mbt = ln(t[i1 - 1], tr, 0);
|
|
590
2782
|
if (mbt > mb) {
|
|
591
|
-
var
|
|
2783
|
+
var i2 = 0, dt = 0;
|
|
592
2784
|
var lft = mbt - mb, cst = 1 << lft;
|
|
593
2785
|
t2.sort(function(a, b) {
|
|
594
2786
|
return tr[b.s] - tr[a.s] || a.f - b.f;
|
|
595
2787
|
});
|
|
596
|
-
for (;
|
|
597
|
-
var i2_1 = t2[
|
|
2788
|
+
for (; i2 < s; ++i2) {
|
|
2789
|
+
var i2_1 = t2[i2].s;
|
|
598
2790
|
if (tr[i2_1] > mb) {
|
|
599
2791
|
dt += cst - (1 << mbt - tr[i2_1]);
|
|
600
2792
|
tr[i2_1] = mb;
|
|
@@ -603,14 +2795,14 @@ var hTree = function(d, mb) {
|
|
|
603
2795
|
}
|
|
604
2796
|
dt >>= lft;
|
|
605
2797
|
while (dt > 0) {
|
|
606
|
-
var i2_2 = t2[
|
|
2798
|
+
var i2_2 = t2[i2].s;
|
|
607
2799
|
if (tr[i2_2] < mb)
|
|
608
2800
|
dt -= 1 << mb - tr[i2_2]++ - 1;
|
|
609
2801
|
else
|
|
610
|
-
++
|
|
2802
|
+
++i2;
|
|
611
2803
|
}
|
|
612
|
-
for (;
|
|
613
|
-
var i2_3 = t2[
|
|
2804
|
+
for (; i2 >= 0 && dt; --i2) {
|
|
2805
|
+
var i2_3 = t2[i2].s;
|
|
614
2806
|
if (tr[i2_3] == mb) {
|
|
615
2807
|
--tr[i2_3];
|
|
616
2808
|
++dt;
|
|
@@ -632,8 +2824,8 @@ var lc = function(c) {
|
|
|
632
2824
|
var w = function(v) {
|
|
633
2825
|
cl[cli++] = v;
|
|
634
2826
|
};
|
|
635
|
-
for (var
|
|
636
|
-
if (c[
|
|
2827
|
+
for (var i2 = 1; i2 <= s; ++i2) {
|
|
2828
|
+
if (c[i2] == cln && i2 != s)
|
|
637
2829
|
++cls;
|
|
638
2830
|
else {
|
|
639
2831
|
if (!cln && cls > 2) {
|
|
@@ -653,15 +2845,15 @@ var lc = function(c) {
|
|
|
653
2845
|
while (cls--)
|
|
654
2846
|
w(cln);
|
|
655
2847
|
cls = 1;
|
|
656
|
-
cln = c[
|
|
2848
|
+
cln = c[i2];
|
|
657
2849
|
}
|
|
658
2850
|
}
|
|
659
2851
|
return { c: cl.subarray(0, cli), n: s };
|
|
660
2852
|
};
|
|
661
2853
|
var clen = function(cf, cl) {
|
|
662
2854
|
var l = 0;
|
|
663
|
-
for (var
|
|
664
|
-
l += cf[
|
|
2855
|
+
for (var i2 = 0; i2 < cl.length; ++i2)
|
|
2856
|
+
l += cf[i2] * cl[i2];
|
|
665
2857
|
return l;
|
|
666
2858
|
};
|
|
667
2859
|
var wfblk = function(out, pos, dat) {
|
|
@@ -671,8 +2863,8 @@ var wfblk = function(out, pos, dat) {
|
|
|
671
2863
|
out[o + 1] = s >> 8;
|
|
672
2864
|
out[o + 2] = out[o] ^ 255;
|
|
673
2865
|
out[o + 3] = out[o + 1] ^ 255;
|
|
674
|
-
for (var
|
|
675
|
-
out[o +
|
|
2866
|
+
for (var i2 = 0; i2 < s; ++i2)
|
|
2867
|
+
out[o + i2 + 4] = dat[i2];
|
|
676
2868
|
return (o + 4 + s) * 8;
|
|
677
2869
|
};
|
|
678
2870
|
var wblk = function(dat, out, final, syms, lf, df, eb, li, bs, bl, p) {
|
|
@@ -683,10 +2875,10 @@ var wblk = function(dat, out, final, syms, lf, df, eb, li, bs, bl, p) {
|
|
|
683
2875
|
var _c = lc(dlt), lclt = _c.c, nlc = _c.n;
|
|
684
2876
|
var _d = lc(ddt), lcdt = _d.c, ndc = _d.n;
|
|
685
2877
|
var lcfreq = new u16(19);
|
|
686
|
-
for (var
|
|
687
|
-
++lcfreq[lclt[
|
|
688
|
-
for (var
|
|
689
|
-
++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];
|
|
690
2882
|
var _e = hTree(lcfreq, 7), lct = _e.t, mlcb = _e.l;
|
|
691
2883
|
var nlcc = 19;
|
|
692
2884
|
for (; nlcc > 4 && !lct[clim[nlcc - 1]]; --nlcc)
|
|
@@ -705,24 +2897,24 @@ var wblk = function(dat, out, final, syms, lf, df, eb, li, bs, bl, p) {
|
|
|
705
2897
|
wbits(out, p + 5, ndc - 1);
|
|
706
2898
|
wbits(out, p + 10, nlcc - 4);
|
|
707
2899
|
p += 14;
|
|
708
|
-
for (var
|
|
709
|
-
wbits(out, p + 3 *
|
|
2900
|
+
for (var i2 = 0; i2 < nlcc; ++i2)
|
|
2901
|
+
wbits(out, p + 3 * i2, lct[clim[i2]]);
|
|
710
2902
|
p += 3 * nlcc;
|
|
711
2903
|
var lcts = [lclt, lcdt];
|
|
712
2904
|
for (var it = 0; it < 2; ++it) {
|
|
713
2905
|
var clct = lcts[it];
|
|
714
|
-
for (var
|
|
715
|
-
var len = clct[
|
|
2906
|
+
for (var i2 = 0; i2 < clct.length; ++i2) {
|
|
2907
|
+
var len = clct[i2] & 31;
|
|
716
2908
|
wbits(out, p, llm[len]), p += lct[len];
|
|
717
2909
|
if (len > 15)
|
|
718
|
-
wbits(out, p, clct[
|
|
2910
|
+
wbits(out, p, clct[i2] >> 5 & 127), p += clct[i2] >> 12;
|
|
719
2911
|
}
|
|
720
2912
|
}
|
|
721
2913
|
} else {
|
|
722
2914
|
lm = flm, ll = flt, dm = fdm, dl = fdt;
|
|
723
2915
|
}
|
|
724
|
-
for (var
|
|
725
|
-
var sym = syms[
|
|
2916
|
+
for (var i2 = 0; i2 < li; ++i2) {
|
|
2917
|
+
var sym = syms[i2];
|
|
726
2918
|
if (sym > 255) {
|
|
727
2919
|
var len = sym >> 18 & 31;
|
|
728
2920
|
wbits16(out, p, lm[len + 257]), p += ll[len + 257];
|
|
@@ -755,36 +2947,36 @@ var dflt = function(dat, lvl, plvl, pre, post, st) {
|
|
|
755
2947
|
var msk_1 = (1 << plvl) - 1;
|
|
756
2948
|
var prev = st.p || new u16(32768), head = st.h || new u16(msk_1 + 1);
|
|
757
2949
|
var bs1_1 = Math.ceil(plvl / 3), bs2_1 = 2 * bs1_1;
|
|
758
|
-
var hsh = function(
|
|
759
|
-
return (dat[
|
|
2950
|
+
var hsh = function(i3) {
|
|
2951
|
+
return (dat[i3] ^ dat[i3 + 1] << bs1_1 ^ dat[i3 + 2] << bs2_1) & msk_1;
|
|
760
2952
|
};
|
|
761
2953
|
var syms = new i32(25e3);
|
|
762
2954
|
var lf = new u16(288), df = new u16(32);
|
|
763
|
-
var lc_1 = 0, eb = 0,
|
|
764
|
-
for (;
|
|
765
|
-
var hv = hsh(
|
|
766
|
-
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];
|
|
767
2959
|
prev[imod] = pimod;
|
|
768
2960
|
head[hv] = imod;
|
|
769
|
-
if (wi <=
|
|
770
|
-
var rem = s -
|
|
2961
|
+
if (wi <= i2) {
|
|
2962
|
+
var rem = s - i2;
|
|
771
2963
|
if ((lc_1 > 7e3 || li > 24576) && (rem > 423 || !lst)) {
|
|
772
|
-
pos = wblk(dat, w, 0, syms, lf, df, eb, li, bs,
|
|
773
|
-
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;
|
|
774
2966
|
for (var j = 0; j < 286; ++j)
|
|
775
2967
|
lf[j] = 0;
|
|
776
2968
|
for (var j = 0; j < 30; ++j)
|
|
777
2969
|
df[j] = 0;
|
|
778
2970
|
}
|
|
779
2971
|
var l = 2, d = 0, ch_1 = c, dif = imod - pimod & 32767;
|
|
780
|
-
if (rem > 2 && hv == hsh(
|
|
2972
|
+
if (rem > 2 && hv == hsh(i2 - dif)) {
|
|
781
2973
|
var maxn = Math.min(n, rem) - 1;
|
|
782
|
-
var maxd = Math.min(32767,
|
|
2974
|
+
var maxd = Math.min(32767, i2);
|
|
783
2975
|
var ml = Math.min(258, rem);
|
|
784
2976
|
while (dif <= maxd && --ch_1 && imod != pimod) {
|
|
785
|
-
if (dat[
|
|
2977
|
+
if (dat[i2 + l] == dat[i2 + l - dif]) {
|
|
786
2978
|
var nl = 0;
|
|
787
|
-
for (; nl < ml && dat[
|
|
2979
|
+
for (; nl < ml && dat[i2 + nl] == dat[i2 + nl - dif]; ++nl)
|
|
788
2980
|
;
|
|
789
2981
|
if (nl > l) {
|
|
790
2982
|
l = nl, d = dif;
|
|
@@ -793,7 +2985,7 @@ var dflt = function(dat, lvl, plvl, pre, post, st) {
|
|
|
793
2985
|
var mmd = Math.min(dif, nl - 2);
|
|
794
2986
|
var md = 0;
|
|
795
2987
|
for (var j = 0; j < mmd; ++j) {
|
|
796
|
-
var ti =
|
|
2988
|
+
var ti = i2 - dif + j & 32767;
|
|
797
2989
|
var pti = prev[ti];
|
|
798
2990
|
var cd = ti - pti & 32767;
|
|
799
2991
|
if (cd > md)
|
|
@@ -811,32 +3003,32 @@ var dflt = function(dat, lvl, plvl, pre, post, st) {
|
|
|
811
3003
|
eb += fleb[lin] + fdeb[din];
|
|
812
3004
|
++lf[257 + lin];
|
|
813
3005
|
++df[din];
|
|
814
|
-
wi =
|
|
3006
|
+
wi = i2 + l;
|
|
815
3007
|
++lc_1;
|
|
816
3008
|
} else {
|
|
817
|
-
syms[li++] = dat[
|
|
818
|
-
++lf[dat[
|
|
3009
|
+
syms[li++] = dat[i2];
|
|
3010
|
+
++lf[dat[i2]];
|
|
819
3011
|
}
|
|
820
3012
|
}
|
|
821
3013
|
}
|
|
822
|
-
for (
|
|
823
|
-
syms[li++] = dat[
|
|
824
|
-
++lf[dat[
|
|
3014
|
+
for (i2 = Math.max(i2, wi); i2 < s; ++i2) {
|
|
3015
|
+
syms[li++] = dat[i2];
|
|
3016
|
+
++lf[dat[i2]];
|
|
825
3017
|
}
|
|
826
|
-
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);
|
|
827
3019
|
if (!lst) {
|
|
828
3020
|
st.r = pos & 7 | w[pos / 8 | 0] << 3;
|
|
829
3021
|
pos -= 7;
|
|
830
|
-
st.h = head, st.p = prev, st.i =
|
|
3022
|
+
st.h = head, st.p = prev, st.i = i2, st.w = wi;
|
|
831
3023
|
}
|
|
832
3024
|
} else {
|
|
833
|
-
for (var
|
|
834
|
-
var e =
|
|
3025
|
+
for (var i2 = st.w || 0; i2 < s + lst; i2 += 65535) {
|
|
3026
|
+
var e = i2 + 65535;
|
|
835
3027
|
if (e >= s) {
|
|
836
3028
|
w[pos / 8 | 0] = lst;
|
|
837
3029
|
e = s;
|
|
838
3030
|
}
|
|
839
|
-
pos = wfblk(w, pos + 1, dat.subarray(
|
|
3031
|
+
pos = wfblk(w, pos + 1, dat.subarray(i2, e));
|
|
840
3032
|
}
|
|
841
3033
|
st.i = s;
|
|
842
3034
|
}
|
|
@@ -844,11 +3036,11 @@ var dflt = function(dat, lvl, plvl, pre, post, st) {
|
|
|
844
3036
|
};
|
|
845
3037
|
var crct = /* @__PURE__ */ (function() {
|
|
846
3038
|
var t = new Int32Array(256);
|
|
847
|
-
for (var
|
|
848
|
-
var c =
|
|
3039
|
+
for (var i2 = 0; i2 < 256; ++i2) {
|
|
3040
|
+
var c = i2, k = 9;
|
|
849
3041
|
while (--k)
|
|
850
3042
|
c = (c & 1 && -306674912) ^ c >>> 1;
|
|
851
|
-
t[
|
|
3043
|
+
t[i2] = c;
|
|
852
3044
|
}
|
|
853
3045
|
return t;
|
|
854
3046
|
})();
|
|
@@ -857,8 +3049,8 @@ var crc = function() {
|
|
|
857
3049
|
return {
|
|
858
3050
|
p: function(d) {
|
|
859
3051
|
var cr = c;
|
|
860
|
-
for (var
|
|
861
|
-
cr = crct[cr & 255 ^ d[
|
|
3052
|
+
for (var i2 = 0; i2 < d.length; ++i2)
|
|
3053
|
+
cr = crct[cr & 255 ^ d[i2]] ^ cr >>> 8;
|
|
862
3054
|
c = cr;
|
|
863
3055
|
},
|
|
864
3056
|
d: function() {
|
|
@@ -919,8 +3111,8 @@ try {
|
|
|
919
3111
|
function strToU8(str, latin1) {
|
|
920
3112
|
if (latin1) {
|
|
921
3113
|
var ar_1 = new u8(str.length);
|
|
922
|
-
for (var
|
|
923
|
-
ar_1[
|
|
3114
|
+
for (var i2 = 0; i2 < str.length; ++i2)
|
|
3115
|
+
ar_1[i2] = str.charCodeAt(i2);
|
|
924
3116
|
return ar_1;
|
|
925
3117
|
}
|
|
926
3118
|
if (te)
|
|
@@ -931,19 +3123,19 @@ function strToU8(str, latin1) {
|
|
|
931
3123
|
var w = function(v) {
|
|
932
3124
|
ar[ai++] = v;
|
|
933
3125
|
};
|
|
934
|
-
for (var
|
|
3126
|
+
for (var i2 = 0; i2 < l; ++i2) {
|
|
935
3127
|
if (ai + 5 > ar.length) {
|
|
936
|
-
var n = new u8(ai + 8 + (l -
|
|
3128
|
+
var n = new u8(ai + 8 + (l - i2 << 1));
|
|
937
3129
|
n.set(ar);
|
|
938
3130
|
ar = n;
|
|
939
3131
|
}
|
|
940
|
-
var c = str.charCodeAt(
|
|
3132
|
+
var c = str.charCodeAt(i2);
|
|
941
3133
|
if (c < 128 || latin1)
|
|
942
3134
|
w(c);
|
|
943
3135
|
else if (c < 2048)
|
|
944
3136
|
w(192 | c >> 6), w(128 | c & 63);
|
|
945
3137
|
else if (c > 55295 && c < 57344)
|
|
946
|
-
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);
|
|
947
3139
|
else
|
|
948
3140
|
w(224 | c >> 12), w(128 | c >> 6 & 63), w(128 | c & 63);
|
|
949
3141
|
}
|
|
@@ -1040,8 +3232,8 @@ function zipSync(data, opts) {
|
|
|
1040
3232
|
tot += 76 + 2 * (s + exl) + (ms || 0) + l;
|
|
1041
3233
|
}
|
|
1042
3234
|
var out = new u8(tot + 22), oe = o, cdl = tot - o;
|
|
1043
|
-
for (var
|
|
1044
|
-
var f = files[
|
|
3235
|
+
for (var i2 = 0; i2 < files.length; ++i2) {
|
|
3236
|
+
var f = files[i2];
|
|
1045
3237
|
wzh(out, f.o, f, f.f, f.u, f.c.length);
|
|
1046
3238
|
var badd = 30 + f.f.length + exfl(f.extra);
|
|
1047
3239
|
out.set(f.c, f.o + badd);
|
|
@@ -1535,8 +3727,7 @@ header {
|
|
|
1535
3727
|
var DEMO_MANIFEST = {
|
|
1536
3728
|
schema: 1,
|
|
1537
3729
|
name: "Hello Marina",
|
|
1538
|
-
icon: "\u26F5"
|
|
1539
|
-
type: "static"
|
|
3730
|
+
icon: "\u26F5"
|
|
1540
3731
|
};
|
|
1541
3732
|
|
|
1542
3733
|
// src/demo.ts
|
|
@@ -1561,20 +3752,24 @@ function packDemo() {
|
|
|
1561
3752
|
}
|
|
1562
3753
|
|
|
1563
3754
|
// src/login.ts
|
|
3755
|
+
init_config();
|
|
3756
|
+
init_api();
|
|
1564
3757
|
import { createHash, randomBytes } from "node:crypto";
|
|
1565
3758
|
import { spawn } from "node:child_process";
|
|
1566
3759
|
import { createServer } from "node:http";
|
|
1567
3760
|
import { hostname } from "node:os";
|
|
1568
|
-
var LOGIN_TIMEOUT_MS =
|
|
3761
|
+
var LOGIN_TIMEOUT_MS = 15 * 6e4;
|
|
1569
3762
|
var PROGRESS_INTERVAL_MS = 1e3;
|
|
1570
3763
|
var page = (title, message) => `<!doctype html>
|
|
1571
|
-
<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">
|
|
1572
3765
|
<title>${title}</title><style>
|
|
1573
|
-
|
|
1574
|
-
|
|
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)}
|
|
1575
3770
|
</style></head><body><main><h1>${title}</h1><p>${message}</p></main></body></html>`;
|
|
1576
|
-
var closeLoginServer = (server) => new Promise((
|
|
1577
|
-
server.close(() =>
|
|
3771
|
+
var closeLoginServer = (server) => new Promise((resolve4) => {
|
|
3772
|
+
server.close(() => resolve4());
|
|
1578
3773
|
server.closeAllConnections();
|
|
1579
3774
|
});
|
|
1580
3775
|
function openBrowser(url) {
|
|
@@ -1592,7 +3787,7 @@ function timeoutLabel(timeoutMs) {
|
|
|
1592
3787
|
return `${String(seconds)} second${seconds === 1 ? "" : "s"}`;
|
|
1593
3788
|
}
|
|
1594
3789
|
function waitForLoginCallback(server, expectedState, onProgress, timeoutMs = LOGIN_TIMEOUT_MS) {
|
|
1595
|
-
return new Promise((
|
|
3790
|
+
return new Promise((resolve4, reject) => {
|
|
1596
3791
|
const startedAt = Date.now();
|
|
1597
3792
|
const waiting = () => {
|
|
1598
3793
|
const elapsedMs = Date.now() - startedAt;
|
|
@@ -1649,7 +3844,7 @@ function waitForLoginCallback(server, expectedState, onProgress, timeoutMs = LOG
|
|
|
1649
3844
|
response.writeHead(200);
|
|
1650
3845
|
response.end(
|
|
1651
3846
|
page("Marina CLI is signed in", "You can close this window and return to the terminal."),
|
|
1652
|
-
() =>
|
|
3847
|
+
() => resolve4(code)
|
|
1653
3848
|
);
|
|
1654
3849
|
});
|
|
1655
3850
|
});
|
|
@@ -1659,9 +3854,9 @@ async function loginWithBrowser(onOpen, onProgress = () => void 0) {
|
|
|
1659
3854
|
const challenge = createHash("sha256").update(verifier).digest("base64url");
|
|
1660
3855
|
const state = randomBytes(32).toString("base64url");
|
|
1661
3856
|
const server = createServer();
|
|
1662
|
-
await new Promise((
|
|
3857
|
+
await new Promise((resolve4, reject) => {
|
|
1663
3858
|
server.once("error", reject);
|
|
1664
|
-
server.listen(0, "127.0.0.1",
|
|
3859
|
+
server.listen(0, "127.0.0.1", resolve4);
|
|
1665
3860
|
});
|
|
1666
3861
|
try {
|
|
1667
3862
|
const address = server.address();
|
|
@@ -1685,65 +3880,18 @@ async function loginWithBrowser(onOpen, onProgress = () => void 0) {
|
|
|
1685
3880
|
}
|
|
1686
3881
|
}
|
|
1687
3882
|
|
|
1688
|
-
// src/
|
|
1689
|
-
|
|
1690
|
-
var setJsonMode = (on) => {
|
|
1691
|
-
json = on;
|
|
1692
|
-
};
|
|
1693
|
-
var isJsonMode = () => json;
|
|
1694
|
-
var bold = (s) => `\x1B[1m${s}\x1B[22m`;
|
|
1695
|
-
var dim = (s) => `\x1B[2m${s}\x1B[22m`;
|
|
1696
|
-
var red = (s) => `\x1B[31m${s}\x1B[39m`;
|
|
1697
|
-
var green = (s) => `\x1B[32m${s}\x1B[39m`;
|
|
1698
|
-
function say(line = "") {
|
|
1699
|
-
if (json) console.error(line);
|
|
1700
|
-
else console.log(line);
|
|
1701
|
-
}
|
|
1702
|
-
function note(line) {
|
|
1703
|
-
console.error(line);
|
|
1704
|
-
}
|
|
1705
|
-
function createProgress() {
|
|
1706
|
-
let active = false;
|
|
1707
|
-
let lastPhase = null;
|
|
1708
|
-
let frame = 0;
|
|
1709
|
-
const frames = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
1710
|
-
const interactive = process.stderr.isTTY === true && !json;
|
|
1711
|
-
return {
|
|
1712
|
-
update(phase, line) {
|
|
1713
|
-
if (interactive) {
|
|
1714
|
-
process.stderr.write(`\r\x1B[2K${frames[frame++ % frames.length]} ${line}`);
|
|
1715
|
-
active = true;
|
|
1716
|
-
} else if (phase !== lastPhase) {
|
|
1717
|
-
console.error(json ? JSON.stringify({ type: "progress", phase, message: line }) : line);
|
|
1718
|
-
}
|
|
1719
|
-
lastPhase = phase;
|
|
1720
|
-
},
|
|
1721
|
-
clear() {
|
|
1722
|
-
if (active) process.stderr.write("\r\x1B[2K");
|
|
1723
|
-
active = false;
|
|
1724
|
-
}
|
|
1725
|
-
};
|
|
1726
|
-
}
|
|
1727
|
-
function result(payload) {
|
|
1728
|
-
if (json) console.log(JSON.stringify({ schema_version: 1, ok: true, ...payload }, null, 2));
|
|
1729
|
-
}
|
|
1730
|
-
function failure(code, message, extra = {}) {
|
|
1731
|
-
if (json)
|
|
1732
|
-
console.log(
|
|
1733
|
-
JSON.stringify({ schema_version: 1, ok: false, error: { code, message, ...extra } }, null, 2)
|
|
1734
|
-
);
|
|
1735
|
-
else {
|
|
1736
|
-
console.error(`${red(code)} ${message}`);
|
|
1737
|
-
}
|
|
1738
|
-
}
|
|
3883
|
+
// src/index.ts
|
|
3884
|
+
init_output();
|
|
1739
3885
|
|
|
1740
3886
|
// src/pack.ts
|
|
1741
|
-
import { lstatSync, readFileSync as readFileSync2, readdirSync } from "node:fs";
|
|
1742
|
-
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);
|
|
1743
3890
|
var EXCLUDED_DIRS = /* @__PURE__ */ new Set([
|
|
1744
3891
|
".git",
|
|
1745
3892
|
".hg",
|
|
1746
3893
|
".svn",
|
|
3894
|
+
".marina",
|
|
1747
3895
|
"node_modules",
|
|
1748
3896
|
"bower_components",
|
|
1749
3897
|
".venv",
|
|
@@ -1759,23 +3907,74 @@ var EXCLUDED_DIRS = /* @__PURE__ */ new Set([
|
|
|
1759
3907
|
"coverage",
|
|
1760
3908
|
".idea",
|
|
1761
3909
|
".vscode",
|
|
1762
|
-
".marina",
|
|
1763
3910
|
"__MACOSX"
|
|
1764
3911
|
]);
|
|
1765
3912
|
var EXCLUDED_FILES = /* @__PURE__ */ new Set([".DS_Store"]);
|
|
1766
3913
|
var MAX_TOTAL = 100 * 1024 * 1024;
|
|
1767
|
-
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
|
+
}
|
|
1768
3952
|
function pack(dir) {
|
|
1769
3953
|
const files = {};
|
|
1770
3954
|
const skippedSecrets = [];
|
|
1771
3955
|
let totalBytes = 0;
|
|
1772
|
-
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
|
+
}
|
|
1773
3969
|
for (const entry of readdirSync(current)) {
|
|
1774
3970
|
const full = join2(current, entry);
|
|
1775
3971
|
const stat = lstatSync(full);
|
|
1776
3972
|
if (stat.isSymbolicLink()) continue;
|
|
3973
|
+
const relativePath = relative(dir, full).replaceAll("\\", "/");
|
|
1777
3974
|
if (stat.isDirectory()) {
|
|
1778
|
-
if (!EXCLUDED_DIRS.has(entry)
|
|
3975
|
+
if (!EXCLUDED_DIRS.has(entry) && !ignoredBy(rules, relativePath, true)) {
|
|
3976
|
+
walk(full, rules);
|
|
3977
|
+
}
|
|
1779
3978
|
continue;
|
|
1780
3979
|
}
|
|
1781
3980
|
if (EXCLUDED_FILES.has(entry)) continue;
|
|
@@ -1783,27 +3982,28 @@ function pack(dir) {
|
|
|
1783
3982
|
skippedSecrets.push(relative(dir, full));
|
|
1784
3983
|
continue;
|
|
1785
3984
|
}
|
|
3985
|
+
if (ignoredBy(rules, relativePath, false)) continue;
|
|
1786
3986
|
totalBytes += stat.size;
|
|
1787
3987
|
if (totalBytes > MAX_TOTAL) {
|
|
1788
3988
|
throw new Error(
|
|
1789
3989
|
`this directory is over ${String(MAX_TOTAL / 1024 / 1024)} MB unpacked \u2014 trim it before deploying`
|
|
1790
3990
|
);
|
|
1791
3991
|
}
|
|
1792
|
-
files[
|
|
3992
|
+
files[relativePath] = readFileSync2(full);
|
|
1793
3993
|
}
|
|
1794
3994
|
};
|
|
1795
|
-
walk(dir);
|
|
3995
|
+
walk(dir, []);
|
|
1796
3996
|
const fileCount = Object.keys(files).length;
|
|
1797
3997
|
if (fileCount === 0) throw new Error("nothing to deploy \u2014 this directory is empty");
|
|
1798
3998
|
return { zip: zipSync(files), fileCount, totalBytes, skippedSecrets };
|
|
1799
3999
|
}
|
|
1800
4000
|
|
|
1801
4001
|
// src/manifest.ts
|
|
1802
|
-
import { existsSync as
|
|
1803
|
-
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";
|
|
1804
4004
|
function readManifest(dir) {
|
|
1805
4005
|
const path = join3(dir, "marina.json");
|
|
1806
|
-
if (!
|
|
4006
|
+
if (!existsSync3(path)) return null;
|
|
1807
4007
|
let value;
|
|
1808
4008
|
try {
|
|
1809
4009
|
value = JSON.parse(readFileSync3(path, "utf8"));
|
|
@@ -1824,7 +4024,7 @@ function readManifest(dir) {
|
|
|
1824
4024
|
throw new Error('marina.json "icon" must be an emoji');
|
|
1825
4025
|
}
|
|
1826
4026
|
if (manifest.type !== void 0 && manifest.type !== "static" && manifest.type !== "dynamic") {
|
|
1827
|
-
throw new Error('marina.json "type"
|
|
4027
|
+
throw new Error('marina.json "type" is obsolete; remove it and use "entrypoint" if needed');
|
|
1828
4028
|
}
|
|
1829
4029
|
return manifest;
|
|
1830
4030
|
}
|
|
@@ -1839,16 +4039,16 @@ function resolveAppName(dir, flag, manifest = readManifest(dir), linkedName) {
|
|
|
1839
4039
|
} catch {
|
|
1840
4040
|
}
|
|
1841
4041
|
if (linkedName?.trim()) return linkedName.trim();
|
|
1842
|
-
return
|
|
4042
|
+
return basename2(dir);
|
|
1843
4043
|
}
|
|
1844
4044
|
|
|
1845
4045
|
// src/skills.ts
|
|
1846
|
-
import { existsSync as
|
|
4046
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "node:fs";
|
|
1847
4047
|
import { homedir as homedir2 } from "node:os";
|
|
1848
4048
|
import { join as join4 } from "node:path";
|
|
1849
4049
|
|
|
1850
4050
|
// ../../skills/marina-deploy/SKILL.md
|
|
1851
|
-
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";
|
|
1852
4052
|
|
|
1853
4053
|
// ../../skills/marina-deploy/agents/openai.yaml
|
|
1854
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';
|
|
@@ -1870,7 +4070,7 @@ function targets(agent) {
|
|
|
1870
4070
|
}));
|
|
1871
4071
|
}
|
|
1872
4072
|
if (agent === "codex" || agent === "claude") return [{ agent, home: agentHomes[agent] }];
|
|
1873
|
-
return Object.entries(agentHomes).filter(([, home]) =>
|
|
4073
|
+
return Object.entries(agentHomes).filter(([, home]) => existsSync4(home)).map(([name, home]) => ({ agent: name, home }));
|
|
1874
4074
|
}
|
|
1875
4075
|
function install(target, update) {
|
|
1876
4076
|
const directory = join4(target.home, "skills", "marina-deploy");
|
|
@@ -1907,11 +4107,13 @@ function installSkills(agent) {
|
|
|
1907
4107
|
}
|
|
1908
4108
|
|
|
1909
4109
|
// src/update.ts
|
|
4110
|
+
init_package();
|
|
4111
|
+
init_config();
|
|
1910
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));
|
|
1911
4114
|
function isNewerVersion(candidate, current) {
|
|
1912
|
-
const
|
|
1913
|
-
const
|
|
1914
|
-
const right = parse(current);
|
|
4115
|
+
const left = parseVersion(candidate);
|
|
4116
|
+
const right = parseVersion(current);
|
|
1915
4117
|
if (left.some(Number.isNaN) || right.some(Number.isNaN)) return false;
|
|
1916
4118
|
for (let index = 0; index < Math.max(left.length, right.length); index += 1) {
|
|
1917
4119
|
const difference = (left[index] ?? 0) - (right[index] ?? 0);
|
|
@@ -1941,7 +4143,7 @@ async function availableUpdate() {
|
|
|
1941
4143
|
// src/index.ts
|
|
1942
4144
|
var HELP = `${bold("marina")} \u2014 deploy internal apps
|
|
1943
4145
|
|
|
1944
|
-
marina setup sign in
|
|
4146
|
+
marina setup sign in through Marina in your browser
|
|
1945
4147
|
--skills install the Marina deployment skill
|
|
1946
4148
|
--deploy-demo deploy Hello Marina after signing in
|
|
1947
4149
|
marina logout remove the saved credential
|
|
@@ -1954,11 +4156,17 @@ var HELP = `${bold("marina")} \u2014 deploy internal apps
|
|
|
1954
4156
|
--app <slug> deploy into an existing app
|
|
1955
4157
|
marina status [--app <slug>] what is live, and how the last deploy went
|
|
1956
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
|
|
1957
4164
|
marina versions [--app <slug>] version history
|
|
1958
4165
|
marina rollback [--to <hash>] publish a previous version again
|
|
1959
|
-
marina
|
|
1960
|
-
|
|
1961
|
-
|
|
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
|
|
1962
4170
|
marina list apps in your workspace
|
|
1963
4171
|
marina open open this project's app
|
|
1964
4172
|
|
|
@@ -1966,7 +4174,7 @@ var HELP = `${bold("marina")} \u2014 deploy internal apps
|
|
|
1966
4174
|
var ANSI_ESCAPE = new RegExp(`${String.fromCodePoint(27)}\\[[0-9;]*m`, "g");
|
|
1967
4175
|
var shouldCheckForUpdates = false;
|
|
1968
4176
|
function targetApp(flag) {
|
|
1969
|
-
const linked = readLink(
|
|
4177
|
+
const linked = readLink(resolve3("."))?.app;
|
|
1970
4178
|
const app = flag ?? linked;
|
|
1971
4179
|
if (!app) {
|
|
1972
4180
|
failure("no_app", "no app here \u2014 pass --app <slug>, or deploy from a linked project");
|
|
@@ -1975,6 +4183,15 @@ function targetApp(flag) {
|
|
|
1975
4183
|
return app;
|
|
1976
4184
|
}
|
|
1977
4185
|
var shortDigest = (digest) => digest?.slice(7, 14) ?? "\u2014";
|
|
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
|
+
}
|
|
1978
4195
|
async function deploy(dirArg, flags, emitResult = true) {
|
|
1979
4196
|
let dir = null;
|
|
1980
4197
|
let link = null;
|
|
@@ -1989,20 +4206,32 @@ async function deploy(dirArg, flags, emitResult = true) {
|
|
|
1989
4206
|
packed = packDemo();
|
|
1990
4207
|
name = flags.name?.trim() || DEMO_MANIFEST.name;
|
|
1991
4208
|
} else {
|
|
1992
|
-
dir =
|
|
4209
|
+
dir = resolve3(dirArg ?? ".");
|
|
1993
4210
|
link = readLink(dir);
|
|
1994
4211
|
target = flags.app ?? link?.app;
|
|
1995
4212
|
const manifest = readManifest(dir);
|
|
1996
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
|
+
}
|
|
1997
4225
|
packed = pack(dir);
|
|
1998
4226
|
}
|
|
1999
|
-
for (const secret of packed.skippedSecrets)
|
|
4227
|
+
for (const secret of packed.skippedSecrets)
|
|
4228
|
+
say(dim(`skipped ${secret} \u2014 credential files are never deployed`));
|
|
2000
4229
|
const progress = createProgress();
|
|
2001
4230
|
const size = `${String(packed.fileCount)} files, ${String(Math.round(packed.totalBytes / 1024))} KB`;
|
|
2002
4231
|
progress.update("uploading", `Uploading ${name} (${size})`);
|
|
2003
4232
|
let started;
|
|
2004
4233
|
try {
|
|
2005
|
-
started = await startDeploy(packed.zip, name, target);
|
|
4234
|
+
started = await startDeploy(packed.zip, name, target, link?.base_revision);
|
|
2006
4235
|
} catch (error) {
|
|
2007
4236
|
progress.clear();
|
|
2008
4237
|
if (error instanceof ApiError && error.code === "not_found" && link) {
|
|
@@ -2020,32 +4249,43 @@ async function deploy(dirArg, flags, emitResult = true) {
|
|
|
2020
4249
|
deployed = await pollDeploy(started.id, (current) => {
|
|
2021
4250
|
if (current.status !== "queued" && current.status !== "building") return;
|
|
2022
4251
|
const elapsed = Math.max(1, Math.round((Date.now() - deployedAt) / 1e3));
|
|
2023
|
-
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)`;
|
|
2024
4253
|
progress.update(current.status, message);
|
|
2025
4254
|
});
|
|
2026
4255
|
} finally {
|
|
2027
4256
|
progress.clear();
|
|
2028
4257
|
}
|
|
2029
4258
|
if (deployed.status === "refused" && deployed.refusal) {
|
|
2030
|
-
say(`${red("refused")} ${deployed.refusal.message}`);
|
|
2031
|
-
if (deployed.refusal.action) say(` ${deployed.refusal.action}`);
|
|
2032
|
-
if (deployed.build_log) {
|
|
2033
|
-
say(dim("---- build log (tail) ----"));
|
|
2034
|
-
say(dim(deployed.build_log.split("\n").slice(-20).join("\n")));
|
|
2035
|
-
}
|
|
2036
4259
|
failure("refused", deployed.refusal.message, {
|
|
4260
|
+
deploy_id: deployed.id,
|
|
2037
4261
|
refusal: deployed.refusal,
|
|
2038
|
-
build_log: deployed.build_log
|
|
4262
|
+
build_log: deployed.build_log,
|
|
4263
|
+
build_phase: deployed.build_phase,
|
|
4264
|
+
build_message: deployed.build_message
|
|
2039
4265
|
});
|
|
4266
|
+
if (!isJsonMode() && deployed.refusal.action) {
|
|
4267
|
+
note(` ${terminalSafeText(deployed.refusal.action)}`);
|
|
4268
|
+
}
|
|
4269
|
+
deployAttemptHint(deployed.id, deployed.build_log !== null);
|
|
2040
4270
|
process.exit(3);
|
|
2041
4271
|
}
|
|
2042
4272
|
if (deployed.status === "failed") {
|
|
2043
|
-
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);
|
|
2044
4280
|
process.exit(1);
|
|
2045
4281
|
}
|
|
2046
4282
|
const slug = deployed.app_slug ?? "";
|
|
2047
|
-
if (dir && (!link || link.app !== slug || link.name !== name)) {
|
|
2048
|
-
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
|
+
});
|
|
2049
4289
|
}
|
|
2050
4290
|
const published = deployed.version_state === "published";
|
|
2051
4291
|
say(
|
|
@@ -2067,11 +4307,13 @@ async function deploy(dirArg, flags, emitResult = true) {
|
|
|
2067
4307
|
const payload = {
|
|
2068
4308
|
command: "deploy",
|
|
2069
4309
|
status: "succeeded",
|
|
4310
|
+
deploy_id: deployed.id,
|
|
2070
4311
|
app: slug,
|
|
2071
4312
|
version: deployed.version_number,
|
|
2072
4313
|
state: deployed.version_state,
|
|
2073
4314
|
url: deployed.url,
|
|
2074
4315
|
version_url: deployed.version_url,
|
|
4316
|
+
build_log: deployed.build_log,
|
|
2075
4317
|
preparation: deployed.preparation,
|
|
2076
4318
|
live: published,
|
|
2077
4319
|
demo: dirArg === "demo"
|
|
@@ -2081,15 +4323,16 @@ async function deploy(dirArg, flags, emitResult = true) {
|
|
|
2081
4323
|
}
|
|
2082
4324
|
async function status(appFlag) {
|
|
2083
4325
|
const app = targetApp(appFlag);
|
|
2084
|
-
const [detail,
|
|
2085
|
-
const last =
|
|
4326
|
+
const [detail, history] = await Promise.all([getApp(app), listDeploys(app, 1)]);
|
|
4327
|
+
const last = history[0];
|
|
2086
4328
|
say(`${bold(detail.app.name)} ${dim(`(${detail.app.slug})`)}`);
|
|
2087
4329
|
say(` ${detail.app.current_version_id ? green(detail.app.status) : dim("nothing published")}`);
|
|
2088
4330
|
if (detail.app.current_version_id) say(` ${detail.url}`);
|
|
2089
4331
|
if (detail.app.archived_at) say(` ${red("archived")} \u2014 it isn't serving`);
|
|
2090
4332
|
if (last) {
|
|
2091
4333
|
const outcome = last.status === "succeeded" ? green("succeeded") : last.status === "queued" || last.status === "building" ? last.status : red(last.status);
|
|
2092
|
-
|
|
4334
|
+
const version = last.version_number == null ? "" : ` v${String(last.version_number)}`;
|
|
4335
|
+
say(` last deploy: ${outcome}${version}${last.refusal ? ` \u2014 ${last.refusal.message}` : ""}`);
|
|
2093
4336
|
}
|
|
2094
4337
|
result({
|
|
2095
4338
|
command: "status",
|
|
@@ -2102,17 +4345,55 @@ async function status(appFlag) {
|
|
|
2102
4345
|
last_deploy: last ?? null
|
|
2103
4346
|
});
|
|
2104
4347
|
}
|
|
2105
|
-
|
|
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
|
+
}
|
|
2106
4360
|
const app = targetApp(appFlag);
|
|
2107
4361
|
const rows = await listDeploys(app);
|
|
2108
4362
|
if (rows.length === 0) say("no deploys yet");
|
|
2109
4363
|
for (const row of rows) {
|
|
2110
4364
|
const mark = row.status === "succeeded" ? green("ok") : red(row.status);
|
|
2111
|
-
const why = row.refusal ? ` \u2014 ${row.refusal.message}` : row.error ? ` \u2014 ${row.error}` : "";
|
|
2112
|
-
|
|
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}`);
|
|
2113
4368
|
}
|
|
4369
|
+
const failedWithLog = rows.find((row) => row.status !== "succeeded" && row.build_log);
|
|
4370
|
+
if (failedWithLog) deployAttemptHint(failedWithLog.id, true);
|
|
2114
4371
|
result({ command: "deploys", app, deploys: rows });
|
|
2115
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
|
+
}
|
|
2116
4397
|
async function versions(appFlag) {
|
|
2117
4398
|
const app = targetApp(appFlag);
|
|
2118
4399
|
const [rows, detail] = await Promise.all([listVersions(app), getApp(app)]);
|
|
@@ -2154,40 +4435,6 @@ async function rollback(appFlag, to) {
|
|
|
2154
4435
|
url: detail.url
|
|
2155
4436
|
});
|
|
2156
4437
|
}
|
|
2157
|
-
async function secrets(appFlag, argv, plain) {
|
|
2158
|
-
const app = targetApp(appFlag);
|
|
2159
|
-
const [action, argument] = argv;
|
|
2160
|
-
if (action === "set") {
|
|
2161
|
-
const eq = argument?.indexOf("=") ?? -1;
|
|
2162
|
-
if (!argument || eq < 1) {
|
|
2163
|
-
failure("invalid_input", "usage: marina secrets set KEY=value");
|
|
2164
|
-
process.exit(1);
|
|
2165
|
-
}
|
|
2166
|
-
const key = argument.slice(0, eq);
|
|
2167
|
-
await setEnv(app, key, argument.slice(eq + 1), !plain);
|
|
2168
|
-
say(`${green("ok")} set ${bold(key)}${plain ? "" : dim(" (secret)")}`);
|
|
2169
|
-
result({ command: "secrets.set", app, key, kind: plain ? "plain" : "secret" });
|
|
2170
|
-
return;
|
|
2171
|
-
}
|
|
2172
|
-
if (action === "rm") {
|
|
2173
|
-
if (!argument) {
|
|
2174
|
-
failure("invalid_input", "usage: marina secrets rm KEY");
|
|
2175
|
-
process.exit(1);
|
|
2176
|
-
}
|
|
2177
|
-
await deleteEnv(app, argument);
|
|
2178
|
-
say(`${green("ok")} removed ${bold(argument)}`);
|
|
2179
|
-
result({ command: "secrets.rm", app, key: argument, removed: true });
|
|
2180
|
-
return;
|
|
2181
|
-
}
|
|
2182
|
-
const rows = await listEnv(app);
|
|
2183
|
-
if (rows.length === 0) say("nothing set");
|
|
2184
|
-
for (const row of rows) {
|
|
2185
|
-
say(
|
|
2186
|
-
`${bold(row.key)} ${row.kind === "secret" ? dim("secret \u2014 write-only") : row.value ?? ""}`
|
|
2187
|
-
);
|
|
2188
|
-
}
|
|
2189
|
-
result({ command: "secrets.list", app, env: rows });
|
|
2190
|
-
}
|
|
2191
4438
|
async function main() {
|
|
2192
4439
|
setJsonMode(process.argv.slice(2).includes("--json"));
|
|
2193
4440
|
const { positionals, values } = parseArgs({
|
|
@@ -2196,16 +4443,21 @@ async function main() {
|
|
|
2196
4443
|
name: { type: "string" },
|
|
2197
4444
|
app: { type: "string" },
|
|
2198
4445
|
to: { type: "string" },
|
|
2199
|
-
|
|
4446
|
+
level: { type: "string" },
|
|
4447
|
+
limit: { type: "string" },
|
|
4448
|
+
cursor: { type: "string" },
|
|
2200
4449
|
agent: { type: "string" },
|
|
4450
|
+
port: { type: "string" },
|
|
4451
|
+
schedules: { type: "boolean" },
|
|
4452
|
+
dir: { type: "string" },
|
|
2201
4453
|
skills: { type: "boolean" },
|
|
2202
4454
|
"deploy-demo": { type: "boolean" },
|
|
2203
4455
|
json: { type: "boolean" },
|
|
2204
4456
|
help: { type: "boolean", short: "h" }
|
|
2205
4457
|
}
|
|
2206
4458
|
});
|
|
2207
|
-
setJsonMode(values.json === true);
|
|
2208
4459
|
const command = positionals[0];
|
|
4460
|
+
setJsonMode(values.json === true || command === "logs");
|
|
2209
4461
|
if (values.help || !command) {
|
|
2210
4462
|
say(HELP);
|
|
2211
4463
|
result({ command: "help", usage: HELP.replaceAll(ANSI_ESCAPE, "") });
|
|
@@ -2276,6 +4528,20 @@ async function main() {
|
|
|
2276
4528
|
});
|
|
2277
4529
|
return;
|
|
2278
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
|
+
}
|
|
2279
4545
|
case "profile": {
|
|
2280
4546
|
const profile = readProfile();
|
|
2281
4547
|
const source = process.env.MARINA_TOKEN ? "environment" : profile.token ? "profile" : null;
|
|
@@ -2302,6 +4568,23 @@ async function main() {
|
|
|
2302
4568
|
result({ command: "skills.install", skills: installed });
|
|
2303
4569
|
return;
|
|
2304
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
|
+
}
|
|
2305
4588
|
case "deploy":
|
|
2306
4589
|
await deploy(positionals[1], values);
|
|
2307
4590
|
return;
|
|
@@ -2310,7 +4593,10 @@ async function main() {
|
|
|
2310
4593
|
return;
|
|
2311
4594
|
case "deploys":
|
|
2312
4595
|
case "deployments":
|
|
2313
|
-
await deploys(values.app);
|
|
4596
|
+
await deploys(values.app, positionals[1]);
|
|
4597
|
+
return;
|
|
4598
|
+
case "logs":
|
|
4599
|
+
await logs(values.app, values);
|
|
2314
4600
|
return;
|
|
2315
4601
|
case "versions":
|
|
2316
4602
|
await versions(values.app);
|
|
@@ -2318,9 +4604,6 @@ async function main() {
|
|
|
2318
4604
|
case "rollback":
|
|
2319
4605
|
await rollback(values.app, values.to);
|
|
2320
4606
|
return;
|
|
2321
|
-
case "secrets":
|
|
2322
|
-
await secrets(values.app, positionals.slice(1), values.plain === true);
|
|
2323
|
-
return;
|
|
2324
4607
|
case "list": {
|
|
2325
4608
|
const apps = await listApps();
|
|
2326
4609
|
if (apps.length === 0) say("no apps yet \u2014 `marina deploy` one");
|