@roxyon/api-client 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +40 -0
- package/dist/index.cjs +856 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +543 -0
- package/dist/index.d.ts +543 -0
- package/dist/index.js +808 -0
- package/dist/index.js.map +1 -0
- package/package.json +43 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,856 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
AccountApi: () => AccountApi,
|
|
24
|
+
ApplicationsApi: () => ApplicationsApi,
|
|
25
|
+
AuthApi: () => AuthApi,
|
|
26
|
+
DEFAULT_APP: () => DEFAULT_APP,
|
|
27
|
+
DEFAULT_BAAS_URL: () => DEFAULT_BAAS_URL,
|
|
28
|
+
DEFAULT_CONSOLE_URL: () => DEFAULT_CONSOLE_URL,
|
|
29
|
+
DomainsApi: () => DomainsApi,
|
|
30
|
+
LUMEN_BUILD: () => LUMEN_BUILD,
|
|
31
|
+
RUNTIMES: () => RUNTIMES,
|
|
32
|
+
Roxyon: () => Roxyon,
|
|
33
|
+
RoxyonApiError: () => RoxyonApiError,
|
|
34
|
+
RoxyonClient: () => RoxyonClient,
|
|
35
|
+
SitesApi: () => SitesApi,
|
|
36
|
+
SubscriptionsApi: () => SubscriptionsApi,
|
|
37
|
+
TokensApi: () => TokensApi,
|
|
38
|
+
assertOk: () => assertOk,
|
|
39
|
+
envFromStored: () => envFromStored,
|
|
40
|
+
formatEnv: () => formatEnv,
|
|
41
|
+
isAuthError: () => isAuthError,
|
|
42
|
+
parseEnv: () => parseEnv,
|
|
43
|
+
rxError: () => rxError,
|
|
44
|
+
toQueryString: () => toQueryString
|
|
45
|
+
});
|
|
46
|
+
module.exports = __toCommonJS(index_exports);
|
|
47
|
+
|
|
48
|
+
// src/errors.ts
|
|
49
|
+
function rxError(r) {
|
|
50
|
+
if (r === null || r === void 0) return "No response from the server.";
|
|
51
|
+
if (typeof r !== "object") return "";
|
|
52
|
+
const body = r;
|
|
53
|
+
if (typeof body.error === "string" && body.error) return body.error;
|
|
54
|
+
const rows = body.results;
|
|
55
|
+
if (Array.isArray(rows)) {
|
|
56
|
+
for (const row of rows) {
|
|
57
|
+
if (row && typeof row === "object" && typeof row.error === "string") {
|
|
58
|
+
const msg = row.error;
|
|
59
|
+
if (msg) return msg;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return "";
|
|
64
|
+
}
|
|
65
|
+
var RoxyonApiError = class extends Error {
|
|
66
|
+
code;
|
|
67
|
+
type;
|
|
68
|
+
status;
|
|
69
|
+
body;
|
|
70
|
+
constructor(message, opts = {}) {
|
|
71
|
+
super(message);
|
|
72
|
+
this.name = "RoxyonApiError";
|
|
73
|
+
this.code = opts.code;
|
|
74
|
+
this.type = opts.type;
|
|
75
|
+
this.status = opts.status;
|
|
76
|
+
this.body = opts.body;
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
var AUTH_CODES = /* @__PURE__ */ new Set([101, 141, 209, 1060, 1063, 401]);
|
|
80
|
+
var AUTH_HINTS = [
|
|
81
|
+
"invalid session",
|
|
82
|
+
"session token",
|
|
83
|
+
"unauthorized",
|
|
84
|
+
"not authorized",
|
|
85
|
+
"invalid token",
|
|
86
|
+
"expired",
|
|
87
|
+
"log in again"
|
|
88
|
+
];
|
|
89
|
+
function isAuthError(err) {
|
|
90
|
+
if (err instanceof RoxyonApiError) {
|
|
91
|
+
if (err.status === 401 || err.status === 403) return true;
|
|
92
|
+
if (err.code !== void 0 && AUTH_CODES.has(err.code)) return true;
|
|
93
|
+
}
|
|
94
|
+
const msg = (err instanceof Error ? err.message : typeof err === "string" ? err : rxError(err)).toLowerCase();
|
|
95
|
+
return AUTH_HINTS.some((h) => msg.includes(h));
|
|
96
|
+
}
|
|
97
|
+
function erroringNode(r) {
|
|
98
|
+
if (!r || typeof r !== "object") return void 0;
|
|
99
|
+
const body = r;
|
|
100
|
+
if (typeof body.error === "string" && body.error) return body;
|
|
101
|
+
if (Array.isArray(body.results)) {
|
|
102
|
+
for (const row of body.results) {
|
|
103
|
+
if (row && typeof row === "object" && row.error) return row;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return void 0;
|
|
107
|
+
}
|
|
108
|
+
function assertOk(r) {
|
|
109
|
+
const msg = rxError(r);
|
|
110
|
+
if (msg) {
|
|
111
|
+
const node = erroringNode(r);
|
|
112
|
+
throw new RoxyonApiError(msg, { code: node?.code, type: node?.type, body: r });
|
|
113
|
+
}
|
|
114
|
+
return r;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// src/account.ts
|
|
118
|
+
var AccountApi = class {
|
|
119
|
+
constructor(client) {
|
|
120
|
+
this.client = client;
|
|
121
|
+
}
|
|
122
|
+
client;
|
|
123
|
+
cache;
|
|
124
|
+
context(opts = {}) {
|
|
125
|
+
if (!this.cache || opts.fresh) this.cache = this.fetch();
|
|
126
|
+
return this.cache;
|
|
127
|
+
}
|
|
128
|
+
async fetch() {
|
|
129
|
+
const r = await this.client.console(
|
|
130
|
+
"GET",
|
|
131
|
+
"/account/context",
|
|
132
|
+
{ tolerateHttpError: true }
|
|
133
|
+
);
|
|
134
|
+
if (!r?.ok || !r.user?.id) {
|
|
135
|
+
throw new RoxyonApiError(r?.error || "Could not load the account context.", { body: r });
|
|
136
|
+
}
|
|
137
|
+
return {
|
|
138
|
+
user: r.user,
|
|
139
|
+
scopes: r.scopes ?? [],
|
|
140
|
+
subscriptions: r.subscriptions ?? [],
|
|
141
|
+
domains: r.domains ?? []
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
/** `GET /account/apps` — the account's applications (list, PAT-safe). */
|
|
145
|
+
async apps() {
|
|
146
|
+
const r = await this.client.console("GET", "/account/apps", { tolerateHttpError: true });
|
|
147
|
+
if (!r?.ok) throw new RoxyonApiError(r?.error || "Could not list applications.", { body: r });
|
|
148
|
+
return r.applications ?? [];
|
|
149
|
+
}
|
|
150
|
+
/** One application by id, PAT-safe (filters {@link apps}). */
|
|
151
|
+
async getApp(id) {
|
|
152
|
+
return (await this.apps()).find((a) => a.id === id);
|
|
153
|
+
}
|
|
154
|
+
/** Pick the subscription to deploy against — the preferred one, else the only/first. */
|
|
155
|
+
async resolveSubscription(preferred) {
|
|
156
|
+
const { subscriptions } = await this.context();
|
|
157
|
+
if (subscriptions.length === 0) {
|
|
158
|
+
throw new RoxyonApiError("This account has no subscriptions.");
|
|
159
|
+
}
|
|
160
|
+
if (preferred) {
|
|
161
|
+
const hit = subscriptions.find((s) => s.id === preferred || s.name === preferred);
|
|
162
|
+
if (hit) return hit;
|
|
163
|
+
}
|
|
164
|
+
return subscriptions[0];
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
// src/applications.ts
|
|
169
|
+
var ApplicationsApi = class {
|
|
170
|
+
constructor(client) {
|
|
171
|
+
this.client = client;
|
|
172
|
+
}
|
|
173
|
+
client;
|
|
174
|
+
async list(subscriptionId) {
|
|
175
|
+
const r = await this.client.get("/Applications", {
|
|
176
|
+
fields: "objectId,Name,Status,DesiredState,SourcePath,Runtime,RuntimeVersion,Preset,ConfigRevision,AppliedRevision,LastError,Env,RepoUrl,Subscription",
|
|
177
|
+
limit: -1,
|
|
178
|
+
order: "Name",
|
|
179
|
+
where: { Subscription: subscriptionId }
|
|
180
|
+
});
|
|
181
|
+
return r.results ?? [];
|
|
182
|
+
}
|
|
183
|
+
async get(id) {
|
|
184
|
+
const r = await this.client.get("/Applications", {
|
|
185
|
+
fields: "objectId,Name,Status,DesiredState,SourcePath,Runtime,RuntimeVersion,Preset,ConfigRevision,AppliedRevision,LastError,Env,RepoUrl,Subscription",
|
|
186
|
+
limit: 1,
|
|
187
|
+
where: { objectId: id }
|
|
188
|
+
});
|
|
189
|
+
return r.results?.[0];
|
|
190
|
+
}
|
|
191
|
+
/** Create the application + its web process + a route, matching `createNew()`. */
|
|
192
|
+
async create(input) {
|
|
193
|
+
const appRes = assertOk(
|
|
194
|
+
await this.client.post("/Applications", {
|
|
195
|
+
Subscription: input.subscription,
|
|
196
|
+
Name: input.name,
|
|
197
|
+
Preset: input.preset,
|
|
198
|
+
SourcePath: input.sourcePath,
|
|
199
|
+
Runtime: input.runtime,
|
|
200
|
+
RuntimeVersion: input.runtimeVersion,
|
|
201
|
+
Env: JSON.stringify(input.env ?? {}),
|
|
202
|
+
Status: "pending",
|
|
203
|
+
DesiredState: "running",
|
|
204
|
+
ConfigRevision: 1,
|
|
205
|
+
AppliedRevision: 0,
|
|
206
|
+
LastError: ""
|
|
207
|
+
})
|
|
208
|
+
);
|
|
209
|
+
const appId = appRes.results?.[0]?.objectId;
|
|
210
|
+
if (!appId) throw new RoxyonApiError("No application id returned.", { body: appRes });
|
|
211
|
+
const procRes = assertOk(
|
|
212
|
+
await this.client.post("/ApplicationProcesses", {
|
|
213
|
+
Application: appId,
|
|
214
|
+
Type: "web",
|
|
215
|
+
Port: 0,
|
|
216
|
+
// allocated by the backend at deploy time
|
|
217
|
+
Command: input.command,
|
|
218
|
+
Status: "pending"
|
|
219
|
+
})
|
|
220
|
+
);
|
|
221
|
+
const procId = procRes.results?.[0]?.objectId ?? "";
|
|
222
|
+
const routeRes = assertOk(
|
|
223
|
+
await this.client.post("/ApplicationRoutes", {
|
|
224
|
+
Application: appId,
|
|
225
|
+
Domain: input.domainId,
|
|
226
|
+
Path: "/",
|
|
227
|
+
Enabled: input.public ? 1 : 0
|
|
228
|
+
})
|
|
229
|
+
);
|
|
230
|
+
const routeId = routeRes.results?.[0]?.objectId ?? "";
|
|
231
|
+
return { application: appId, process: procId, route: routeId };
|
|
232
|
+
}
|
|
233
|
+
// ---- console endpoints (session token or PAT) ----
|
|
234
|
+
/** Read an application's environment. `GET /applications/env`. */
|
|
235
|
+
async getEnv(applicationId) {
|
|
236
|
+
const r = await this.client.console("GET", "/applications/env", {
|
|
237
|
+
query: { application: applicationId },
|
|
238
|
+
tolerateHttpError: true
|
|
239
|
+
});
|
|
240
|
+
if (!r?.ok)
|
|
241
|
+
throw new RoxyonApiError(r?.error || "Could not read the environment.", { body: r });
|
|
242
|
+
return r.env ?? {};
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Merge `set` and delete `remove`, bumping `ConfigRevision`. `PORT`/`HOST` are
|
|
246
|
+
* ignored (platform-managed). `POST /applications/env`. Returns the new env.
|
|
247
|
+
*/
|
|
248
|
+
async setEnv(applicationId, changes) {
|
|
249
|
+
const r = await this.client.console("POST", "/applications/env", {
|
|
250
|
+
query: { application: applicationId },
|
|
251
|
+
body: { set: changes.set ?? {}, remove: changes.remove ?? [] },
|
|
252
|
+
tolerateHttpError: true
|
|
253
|
+
});
|
|
254
|
+
if (!r?.ok)
|
|
255
|
+
throw new RoxyonApiError(r?.error || "Could not update the environment.", { body: r });
|
|
256
|
+
return { env: r.env ?? {}, changed: r.changed ?? [], configRevision: r.configRevision ?? 0 };
|
|
257
|
+
}
|
|
258
|
+
/** Rebuild from the latest source and restart. `POST /applications/action`. */
|
|
259
|
+
async deploy(applicationId) {
|
|
260
|
+
const r = await this.client.console("POST", "/applications/action", {
|
|
261
|
+
body: { application: applicationId, action: "deploy" },
|
|
262
|
+
tolerateHttpError: true
|
|
263
|
+
});
|
|
264
|
+
if (r?.error) throw new RoxyonApiError(r.error);
|
|
265
|
+
}
|
|
266
|
+
/** Bounce the process only — no rebuild. `POST /applications/action`. */
|
|
267
|
+
async restart(applicationId) {
|
|
268
|
+
const r = await this.client.console("POST", "/applications/action", {
|
|
269
|
+
body: { application: applicationId, action: "restart" },
|
|
270
|
+
tolerateHttpError: true
|
|
271
|
+
});
|
|
272
|
+
if (r?.error) throw new RoxyonApiError(r.error);
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Tail the app's journal. `POST /applications/logs` returns one block per
|
|
276
|
+
* process (`{ type, unit, output }`); this flattens them to lines, each
|
|
277
|
+
* prefixed with the process type when there is more than one.
|
|
278
|
+
*/
|
|
279
|
+
async logs(applicationId, lines = 100) {
|
|
280
|
+
const r = await this.client.console("POST", "/applications/logs", {
|
|
281
|
+
body: { application: applicationId, lines },
|
|
282
|
+
tolerateHttpError: true
|
|
283
|
+
});
|
|
284
|
+
if (r?.error) throw new RoxyonApiError(r.error);
|
|
285
|
+
const procs = r?.processes ?? [];
|
|
286
|
+
const multi = procs.length > 1;
|
|
287
|
+
const out = [];
|
|
288
|
+
for (const p of procs) {
|
|
289
|
+
const body = (p.output ?? "").split("\n").filter(Boolean);
|
|
290
|
+
for (const line of body) out.push(multi ? `[${p.type ?? "web"}] ${line}` : line);
|
|
291
|
+
}
|
|
292
|
+
return out;
|
|
293
|
+
}
|
|
294
|
+
/** Connect a git remote for push-to-deploy. `POST /applications/repo/connect`. */
|
|
295
|
+
async repoConnect(applicationId, repoUrl, branch = "main") {
|
|
296
|
+
const r = await this.client.console("POST", "/applications/repo/connect", {
|
|
297
|
+
body: { application: applicationId, repoUrl, branch },
|
|
298
|
+
tolerateHttpError: true
|
|
299
|
+
});
|
|
300
|
+
if (r?.error) throw new RoxyonApiError(r.error);
|
|
301
|
+
return r;
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Upload a gzipped tarball of the project source. The endpoint lands it in the
|
|
305
|
+
* application's `SourcePath` and bumps `ConfigRevision` (the reconciler then
|
|
306
|
+
* builds it in a release dir exactly as for a git deploy).
|
|
307
|
+
*
|
|
308
|
+
* `POST /applications/deploy` — NEW endpoint (see `backend/` in this repo).
|
|
309
|
+
*/
|
|
310
|
+
async uploadSource(target, tarball) {
|
|
311
|
+
const query = typeof target === "string" ? { application: target } : {
|
|
312
|
+
host: target.host,
|
|
313
|
+
folder: target.folder ?? "",
|
|
314
|
+
runtime: target.runtime,
|
|
315
|
+
...target.runtimeVersion ? { runtimeVersion: target.runtimeVersion } : {},
|
|
316
|
+
...target.preset ? { preset: target.preset } : {},
|
|
317
|
+
...target.command ? { command: target.command } : {},
|
|
318
|
+
public: target.public === false ? "0" : "1"
|
|
319
|
+
};
|
|
320
|
+
const r = await this.client.console("POST", "/applications/deploy", {
|
|
321
|
+
query,
|
|
322
|
+
headers: { "content-type": "application/gzip" },
|
|
323
|
+
body: tarball,
|
|
324
|
+
tolerateHttpError: true
|
|
325
|
+
});
|
|
326
|
+
if (!r?.ok) throw new RoxyonApiError(r?.error || "Upload failed.", { body: r });
|
|
327
|
+
return r;
|
|
328
|
+
}
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
// src/auth.ts
|
|
332
|
+
var AuthApi = class {
|
|
333
|
+
constructor(client) {
|
|
334
|
+
this.client = client;
|
|
335
|
+
}
|
|
336
|
+
client;
|
|
337
|
+
/**
|
|
338
|
+
* Best-effort device/step-up precheck. `/_account/login-precheck` is a console
|
|
339
|
+
* cloud function that may be gated behind the REST key (not reachable from a
|
|
340
|
+
* public client) — in that case this resolves to `{}` and login falls back to
|
|
341
|
+
* `/Auth/login`, which is itself the security boundary and re-checks the
|
|
342
|
+
* device. Only a definitive `{ ok: false }` / `stepUpRequired` is acted on.
|
|
343
|
+
*/
|
|
344
|
+
async precheck(email, password, metadata = {}) {
|
|
345
|
+
try {
|
|
346
|
+
const r = await this.client.post(
|
|
347
|
+
"/_account/login-precheck",
|
|
348
|
+
{ Email: email, Password: password, metadata },
|
|
349
|
+
{ tolerateHttpError: true }
|
|
350
|
+
);
|
|
351
|
+
if (r?.code === 401 || r?.error && /rest api key/i.test(r.error)) return {};
|
|
352
|
+
return r ?? {};
|
|
353
|
+
} catch {
|
|
354
|
+
return {};
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* Full login. Returns a session token on success, or `stepUpRequired` with a
|
|
359
|
+
* `challengeId` when an OTP code is needed next.
|
|
360
|
+
*/
|
|
361
|
+
async login(email, password, metadata = {}) {
|
|
362
|
+
const pre = await this.precheck(email, password, metadata);
|
|
363
|
+
if (pre.ok === false && pre.error && !pre.stepUpRequired) {
|
|
364
|
+
throw new RoxyonApiError(pre.error, { type: "CredentialsError" });
|
|
365
|
+
}
|
|
366
|
+
if (pre.stepUpRequired) {
|
|
367
|
+
return { sessionToken: "", stepUpRequired: true, challengeId: pre.challengeId };
|
|
368
|
+
}
|
|
369
|
+
const grant = await this.client.post("/Auth/login", { Email: email, Password: password, metadata }, { tolerateHttpError: true });
|
|
370
|
+
if (grant?.stepUpRequired || grant?.challengeId && !grant.session_token) {
|
|
371
|
+
return { sessionToken: "", stepUpRequired: true, challengeId: grant.challengeId };
|
|
372
|
+
}
|
|
373
|
+
if (!grant?.session_token) {
|
|
374
|
+
throw new RoxyonApiError(grant?.error || "Wrong email or password.", {
|
|
375
|
+
type: "CredentialsError"
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
this.client.setSessionToken(grant.session_token);
|
|
379
|
+
return {
|
|
380
|
+
sessionToken: grant.session_token,
|
|
381
|
+
refreshToken: grant.refresh_token,
|
|
382
|
+
expiresIn: grant.expires_in
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
/** Complete an OTP step-up with the code the user received. */
|
|
386
|
+
async verifyOtp(challengeId, code) {
|
|
387
|
+
const grant = await this.client.post(
|
|
388
|
+
"/Auth/login/otp-verify",
|
|
389
|
+
{ challengeId, code },
|
|
390
|
+
{ tolerateHttpError: true }
|
|
391
|
+
);
|
|
392
|
+
if (grant?.status !== "authenticated" || !grant.session_token) {
|
|
393
|
+
throw new RoxyonApiError(grant?.error || "That code did not verify.", {
|
|
394
|
+
type: "OtpError",
|
|
395
|
+
body: grant
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
this.client.setSessionToken(grant.session_token);
|
|
399
|
+
return {
|
|
400
|
+
sessionToken: grant.session_token,
|
|
401
|
+
refreshToken: grant.refresh_token,
|
|
402
|
+
expiresIn: grant.expires_in
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
/** Start an OTP-only sign-in (no password). Returns a `challengeId`. */
|
|
406
|
+
async startOtp(email) {
|
|
407
|
+
const r = await this.client.post(
|
|
408
|
+
"/Auth/login/otp",
|
|
409
|
+
{ Email: email },
|
|
410
|
+
{ tolerateHttpError: true }
|
|
411
|
+
);
|
|
412
|
+
if (!r?.challengeId) {
|
|
413
|
+
throw new RoxyonApiError(r?.error || "Could not start OTP sign-in.", { body: r });
|
|
414
|
+
}
|
|
415
|
+
return { challengeId: r.challengeId };
|
|
416
|
+
}
|
|
417
|
+
/** The currently authenticated user. Requires a session token on the client. */
|
|
418
|
+
async me() {
|
|
419
|
+
const user = await this.client.post("/Auth/me", {});
|
|
420
|
+
if (!user?.objectId) {
|
|
421
|
+
throw new RoxyonApiError(user?.error || "Not signed in.", { status: 401, body: user });
|
|
422
|
+
}
|
|
423
|
+
return user;
|
|
424
|
+
}
|
|
425
|
+
async logout() {
|
|
426
|
+
await this.client.post("/Auth/logout", {}, { tolerateHttpError: true }).catch(() => void 0);
|
|
427
|
+
this.client.setSessionToken(void 0);
|
|
428
|
+
}
|
|
429
|
+
};
|
|
430
|
+
|
|
431
|
+
// src/constants.ts
|
|
432
|
+
var DEFAULT_BAAS_URL = "https://www.beaapis.com/1";
|
|
433
|
+
var DEFAULT_CONSOLE_URL = "https://console.roxyon.com";
|
|
434
|
+
var DEFAULT_APP = {
|
|
435
|
+
appId: "jAtp2zHGU3FbnrQWrToALFakd_vbiY0ywihn4Hj54lw",
|
|
436
|
+
javascriptKey: "w2Y9USRg0COtNe-fUqOaC1OOOlNCnV2BG8GZF80w4x8"
|
|
437
|
+
};
|
|
438
|
+
var H = {
|
|
439
|
+
appId: "x-bea-application-id",
|
|
440
|
+
jsKey: "x-bea-javascript-key",
|
|
441
|
+
restKey: "x-bea-authorization",
|
|
442
|
+
sessionToken: "x-bea-session-token",
|
|
443
|
+
accessToken: "x-bea-access-token",
|
|
444
|
+
refreshToken: "x-bea-refresh-token"
|
|
445
|
+
};
|
|
446
|
+
|
|
447
|
+
// src/query.ts
|
|
448
|
+
function encode(k, v) {
|
|
449
|
+
return `${encodeURIComponent(k)}=${encodeURIComponent(v)}`;
|
|
450
|
+
}
|
|
451
|
+
function walk(value, prefix, out) {
|
|
452
|
+
if (value === null || value === void 0) return;
|
|
453
|
+
if (Array.isArray(value)) {
|
|
454
|
+
value.forEach((item, i) => walk(item, `${prefix}[${i}]`, out));
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
if (typeof value === "object") {
|
|
458
|
+
for (const [key, child] of Object.entries(value)) {
|
|
459
|
+
if (key === "in" || key === "nin" || key === "notIn") {
|
|
460
|
+
const joined = Array.isArray(child) ? child.join(",") : String(child);
|
|
461
|
+
out.push(encode(`${prefix}[${key === "notIn" ? "nin" : key}]`, joined));
|
|
462
|
+
continue;
|
|
463
|
+
}
|
|
464
|
+
walk(child, `${prefix}[${key}]`, out);
|
|
465
|
+
}
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
out.push(encode(prefix, String(value)));
|
|
469
|
+
}
|
|
470
|
+
function toQueryString(query) {
|
|
471
|
+
if (!query) return "";
|
|
472
|
+
const out = [];
|
|
473
|
+
for (const [key, value] of Object.entries(query)) {
|
|
474
|
+
walk(value, key, out);
|
|
475
|
+
}
|
|
476
|
+
return out.join("&");
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// src/client.ts
|
|
480
|
+
var RoxyonClient = class {
|
|
481
|
+
baseUrl;
|
|
482
|
+
consoleUrl;
|
|
483
|
+
appId;
|
|
484
|
+
javascriptKey;
|
|
485
|
+
restKey;
|
|
486
|
+
_fetch;
|
|
487
|
+
sessionToken;
|
|
488
|
+
anon;
|
|
489
|
+
constructor(opts = {}) {
|
|
490
|
+
this.baseUrl = (opts.baseUrl ?? DEFAULT_BAAS_URL).replace(/\/+$/, "");
|
|
491
|
+
this.consoleUrl = (opts.consoleUrl ?? DEFAULT_CONSOLE_URL).replace(/\/+$/, "");
|
|
492
|
+
this.appId = opts.appId ?? DEFAULT_APP.appId;
|
|
493
|
+
this.javascriptKey = opts.javascriptKey ?? DEFAULT_APP.javascriptKey;
|
|
494
|
+
this.restKey = opts.restKey;
|
|
495
|
+
this.sessionToken = opts.sessionToken;
|
|
496
|
+
this._fetch = opts.fetch ?? globalThis.fetch;
|
|
497
|
+
if (typeof this._fetch !== "function") {
|
|
498
|
+
throw new Error("No fetch implementation available (need Node >= 20 or pass opts.fetch).");
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
getSessionToken() {
|
|
502
|
+
return this.sessionToken;
|
|
503
|
+
}
|
|
504
|
+
setSessionToken(token) {
|
|
505
|
+
this.sessionToken = token;
|
|
506
|
+
}
|
|
507
|
+
/** Mint (or reuse) an anonymous access token from the public app credentials. */
|
|
508
|
+
async anonToken() {
|
|
509
|
+
if (this.anon?._expiresAt && this.anon._expiresAt > Date.now() + 5e3) {
|
|
510
|
+
return this.anon;
|
|
511
|
+
}
|
|
512
|
+
const res = await this._fetch(`${this.baseUrl}/Auth`, {
|
|
513
|
+
method: "POST",
|
|
514
|
+
headers: {
|
|
515
|
+
"content-type": "application/json",
|
|
516
|
+
[H.appId]: this.appId,
|
|
517
|
+
[H.jsKey]: this.javascriptKey
|
|
518
|
+
},
|
|
519
|
+
body: JSON.stringify({ scope: "public" })
|
|
520
|
+
});
|
|
521
|
+
const json = await res.json().catch(() => ({}));
|
|
522
|
+
if (!json.access_token && !json.session_token) {
|
|
523
|
+
throw new RoxyonApiError("Could not obtain an anonymous BaaS token.", {
|
|
524
|
+
status: res.status,
|
|
525
|
+
body: json
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
json._expiresAt = Date.now() + (json.expires_in ? json.expires_in * 1e3 : 36e5);
|
|
529
|
+
this.anon = json;
|
|
530
|
+
return json;
|
|
531
|
+
}
|
|
532
|
+
/** A Personal Access Token (`roxp_…`) authenticates the console endpoints via
|
|
533
|
+
* `Authorization: Bearer`; it is not a BaaS session token. */
|
|
534
|
+
get isPat() {
|
|
535
|
+
return this.sessionToken?.startsWith("roxp_") ?? false;
|
|
536
|
+
}
|
|
537
|
+
async authHeaders(skipAuth, isConsole) {
|
|
538
|
+
const base = { [H.appId]: this.appId };
|
|
539
|
+
if (skipAuth) return base;
|
|
540
|
+
if (this.isPat) {
|
|
541
|
+
return isConsole ? { ...base, authorization: `Bearer ${this.sessionToken}` } : base;
|
|
542
|
+
}
|
|
543
|
+
if (this.sessionToken) return { ...base, [H.sessionToken]: this.sessionToken };
|
|
544
|
+
if (this.restKey) return { ...base, [H.restKey]: this.restKey };
|
|
545
|
+
const anon = await this.anonToken();
|
|
546
|
+
if (anon.session_token) return { ...base, [H.sessionToken]: anon.session_token };
|
|
547
|
+
return { ...base, [H.accessToken]: anon.access_token ?? "" };
|
|
548
|
+
}
|
|
549
|
+
/** A raw BaaS request. `path` is relative to {@link baseUrl} (leading `/`). */
|
|
550
|
+
async request(method, path, opts = {}) {
|
|
551
|
+
return this.rawRequest(this.baseUrl, method, path, opts);
|
|
552
|
+
}
|
|
553
|
+
/** A request against the console app endpoints ({@link consoleUrl}). */
|
|
554
|
+
async console(method, path, opts = {}) {
|
|
555
|
+
return this.rawRequest(this.consoleUrl, method, path, opts);
|
|
556
|
+
}
|
|
557
|
+
async rawRequest(root, method, path, opts) {
|
|
558
|
+
const qs = toQueryString(opts.query);
|
|
559
|
+
const url = `${root}${path.startsWith("/") ? path : `/${path}`}${qs ? `?${qs}` : ""}`;
|
|
560
|
+
const headers = {
|
|
561
|
+
...await this.authHeaders(opts.skipAuth ?? false, root === this.consoleUrl),
|
|
562
|
+
...opts.headers
|
|
563
|
+
};
|
|
564
|
+
const hasBody = method !== "GET" && method !== "HEAD" && opts.body !== void 0;
|
|
565
|
+
let body;
|
|
566
|
+
if (hasBody) {
|
|
567
|
+
if (typeof opts.body === "string" || opts.body instanceof Uint8Array || opts.body instanceof ArrayBuffer || typeof FormData !== "undefined" && opts.body instanceof FormData) {
|
|
568
|
+
body = opts.body;
|
|
569
|
+
} else {
|
|
570
|
+
headers["content-type"] = headers["content-type"] ?? "application/json";
|
|
571
|
+
body = JSON.stringify(opts.body);
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
let res;
|
|
575
|
+
try {
|
|
576
|
+
res = await this._fetch(url, { method, headers, body, signal: opts.signal });
|
|
577
|
+
} catch (cause) {
|
|
578
|
+
throw new RoxyonApiError(`Network error calling ${method} ${url}`, { body: cause });
|
|
579
|
+
}
|
|
580
|
+
const text = await res.text();
|
|
581
|
+
let json;
|
|
582
|
+
try {
|
|
583
|
+
json = text ? JSON.parse(text) : {};
|
|
584
|
+
} catch {
|
|
585
|
+
json = { error: text || `HTTP ${res.status}`, _raw: text };
|
|
586
|
+
}
|
|
587
|
+
if (!res.ok && !opts.tolerateHttpError) {
|
|
588
|
+
const msg = json && typeof json === "object" && "error" in json && json.error || `HTTP ${res.status} calling ${method} ${path}`;
|
|
589
|
+
throw new RoxyonApiError(String(msg), { status: res.status, body: json });
|
|
590
|
+
}
|
|
591
|
+
return json;
|
|
592
|
+
}
|
|
593
|
+
get(path, query, opts) {
|
|
594
|
+
return this.request("GET", path, { ...opts, query });
|
|
595
|
+
}
|
|
596
|
+
post(path, body, opts) {
|
|
597
|
+
return this.request("POST", path, { ...opts, body });
|
|
598
|
+
}
|
|
599
|
+
put(path, body, opts) {
|
|
600
|
+
return this.request("PUT", path, { ...opts, body });
|
|
601
|
+
}
|
|
602
|
+
del(path, body, opts) {
|
|
603
|
+
return this.request("DELETE", path, { ...opts, body });
|
|
604
|
+
}
|
|
605
|
+
};
|
|
606
|
+
|
|
607
|
+
// src/domains.ts
|
|
608
|
+
var DomainsApi = class {
|
|
609
|
+
constructor(client) {
|
|
610
|
+
this.client = client;
|
|
611
|
+
}
|
|
612
|
+
client;
|
|
613
|
+
async list(subscriptionId) {
|
|
614
|
+
const r = await this.client.get("/Domains", {
|
|
615
|
+
fields: "objectId,Name",
|
|
616
|
+
limit: -1,
|
|
617
|
+
order: "Name",
|
|
618
|
+
where: { Subscription: subscriptionId }
|
|
619
|
+
});
|
|
620
|
+
return r.results ?? [];
|
|
621
|
+
}
|
|
622
|
+
};
|
|
623
|
+
|
|
624
|
+
// src/sites.ts
|
|
625
|
+
var SitesApi = class {
|
|
626
|
+
constructor(client) {
|
|
627
|
+
this.client = client;
|
|
628
|
+
}
|
|
629
|
+
client;
|
|
630
|
+
async deploy(host, folder, tarball) {
|
|
631
|
+
const r = await this.client.console("POST", "/sites/deploy", {
|
|
632
|
+
query: { host, folder },
|
|
633
|
+
headers: { "content-type": "application/gzip" },
|
|
634
|
+
body: tarball,
|
|
635
|
+
tolerateHttpError: true
|
|
636
|
+
});
|
|
637
|
+
if (!r?.ok) throw new RoxyonApiError(r?.error || "Static deploy failed.", { body: r });
|
|
638
|
+
return r;
|
|
639
|
+
}
|
|
640
|
+
};
|
|
641
|
+
|
|
642
|
+
// src/subscriptions.ts
|
|
643
|
+
var SubscriptionsApi = class {
|
|
644
|
+
constructor(client) {
|
|
645
|
+
this.client = client;
|
|
646
|
+
}
|
|
647
|
+
client;
|
|
648
|
+
/** All subscriptions the signed-in user has a Privilege on. */
|
|
649
|
+
async list(userId) {
|
|
650
|
+
const r = await this.client.get("/Privileges", {
|
|
651
|
+
where: { User: userId },
|
|
652
|
+
fields: "objectId,Type,Subscription",
|
|
653
|
+
limit: -1,
|
|
654
|
+
include: [
|
|
655
|
+
{
|
|
656
|
+
className: "Subscriptions",
|
|
657
|
+
field: "Subscription",
|
|
658
|
+
fields: "objectId,Name,Status,Username,Node,Datacenter"
|
|
659
|
+
}
|
|
660
|
+
]
|
|
661
|
+
});
|
|
662
|
+
if (!Array.isArray(r.results)) {
|
|
663
|
+
throw new RoxyonApiError(r.error || "Could not load subscriptions.", {
|
|
664
|
+
status: 401,
|
|
665
|
+
body: r
|
|
666
|
+
});
|
|
667
|
+
}
|
|
668
|
+
const subs = [];
|
|
669
|
+
for (const priv of r.results) {
|
|
670
|
+
const sub = priv._Subscription?.results?.[0];
|
|
671
|
+
if (sub) subs.push(sub);
|
|
672
|
+
}
|
|
673
|
+
return subs;
|
|
674
|
+
}
|
|
675
|
+
/**
|
|
676
|
+
* Resolve the subscription to act on. Prefers an exact `objectId` or `Name`
|
|
677
|
+
* match against `preferred`; otherwise the first active subscription; otherwise
|
|
678
|
+
* the first one. Throws when the account has none.
|
|
679
|
+
*/
|
|
680
|
+
async resolve(userId, preferred) {
|
|
681
|
+
const subs = await this.list(userId);
|
|
682
|
+
if (subs.length === 0) {
|
|
683
|
+
throw new RoxyonApiError(
|
|
684
|
+
"This account has no active subscription \u2014 add a plan in the console first."
|
|
685
|
+
);
|
|
686
|
+
}
|
|
687
|
+
if (preferred) {
|
|
688
|
+
const hit = subs.find(
|
|
689
|
+
(s) => s.objectId === preferred || s.Name === preferred || s.Username === preferred
|
|
690
|
+
);
|
|
691
|
+
if (hit) return hit;
|
|
692
|
+
throw new RoxyonApiError(`No subscription matches "${preferred}".`);
|
|
693
|
+
}
|
|
694
|
+
return subs.find((s) => s.Status === "active") ?? subs[0];
|
|
695
|
+
}
|
|
696
|
+
};
|
|
697
|
+
|
|
698
|
+
// src/tokens.ts
|
|
699
|
+
var TokensApi = class {
|
|
700
|
+
constructor(client) {
|
|
701
|
+
this.client = client;
|
|
702
|
+
}
|
|
703
|
+
client;
|
|
704
|
+
async list() {
|
|
705
|
+
const r = await this.client.console(
|
|
706
|
+
"GET",
|
|
707
|
+
"/account/tokens",
|
|
708
|
+
{ tolerateHttpError: true }
|
|
709
|
+
);
|
|
710
|
+
if (!r?.ok) throw new RoxyonApiError(r?.error || "Could not list tokens.", { body: r });
|
|
711
|
+
return r.tokens ?? [];
|
|
712
|
+
}
|
|
713
|
+
async create(name, opts = {}) {
|
|
714
|
+
const r = await this.client.console(
|
|
715
|
+
"POST",
|
|
716
|
+
"/account/tokens",
|
|
717
|
+
{
|
|
718
|
+
body: {
|
|
719
|
+
name,
|
|
720
|
+
scopes: opts.scopes ?? ["deploy", "logs"],
|
|
721
|
+
...opts.expiresInDays ? { expiresInDays: opts.expiresInDays } : {}
|
|
722
|
+
},
|
|
723
|
+
tolerateHttpError: true
|
|
724
|
+
}
|
|
725
|
+
);
|
|
726
|
+
if (!r?.ok || !r.token) {
|
|
727
|
+
throw new RoxyonApiError(r?.error || "Could not create the token.", { body: r });
|
|
728
|
+
}
|
|
729
|
+
return { token: r.token, name: r.name, scopes: r.scopes, expiresAt: r.expiresAt ?? null };
|
|
730
|
+
}
|
|
731
|
+
async revoke(id) {
|
|
732
|
+
const r = await this.client.console(
|
|
733
|
+
"DELETE",
|
|
734
|
+
"/account/tokens",
|
|
735
|
+
{ query: { id }, tolerateHttpError: true }
|
|
736
|
+
);
|
|
737
|
+
if (!r?.ok) throw new RoxyonApiError(r?.error || "Could not revoke the token.", { body: r });
|
|
738
|
+
}
|
|
739
|
+
};
|
|
740
|
+
|
|
741
|
+
// src/env.ts
|
|
742
|
+
function parseEnv(text) {
|
|
743
|
+
const out = {};
|
|
744
|
+
for (let line of String(text || "").split(/\r?\n/)) {
|
|
745
|
+
line = line.trim();
|
|
746
|
+
if (!line || line.startsWith("#")) continue;
|
|
747
|
+
line = line.replace(/^export\s+/, "");
|
|
748
|
+
const eq = line.indexOf("=");
|
|
749
|
+
if (eq < 1) continue;
|
|
750
|
+
const k = line.slice(0, eq).trim();
|
|
751
|
+
let v = line.slice(eq + 1).trim();
|
|
752
|
+
if (v.length >= 2 && (v[0] === '"' || v[0] === "'") && v[v.length - 1] === v[0]) {
|
|
753
|
+
v = v.slice(1, -1);
|
|
754
|
+
}
|
|
755
|
+
if (/^[A-Za-z_][A-Za-z0-9_]*$/.test(k)) out[k] = v;
|
|
756
|
+
}
|
|
757
|
+
return out;
|
|
758
|
+
}
|
|
759
|
+
function formatEnv(obj) {
|
|
760
|
+
if (!obj || typeof obj !== "object") return "";
|
|
761
|
+
return Object.keys(obj).map((k) => `${k}=${obj[k] == null ? "" : String(obj[k])}`).join("\n");
|
|
762
|
+
}
|
|
763
|
+
function envFromStored(jsonStr) {
|
|
764
|
+
if (!jsonStr) return {};
|
|
765
|
+
try {
|
|
766
|
+
const obj = JSON.parse(jsonStr);
|
|
767
|
+
return obj && typeof obj === "object" ? obj : {};
|
|
768
|
+
} catch {
|
|
769
|
+
return {};
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
// src/runtimes.ts
|
|
774
|
+
var RUNTIMES = {
|
|
775
|
+
node: {
|
|
776
|
+
versions: ["23.11.1", "22.23.1", "20.20.2", "18.20.8"],
|
|
777
|
+
defaultVersion: "20.20.2",
|
|
778
|
+
presets: [
|
|
779
|
+
["nextjs", "Next.js"],
|
|
780
|
+
["nuxt", "Nuxt"],
|
|
781
|
+
["node", "No framework"]
|
|
782
|
+
],
|
|
783
|
+
command: "npm run start"
|
|
784
|
+
},
|
|
785
|
+
python: {
|
|
786
|
+
versions: ["3.11"],
|
|
787
|
+
defaultVersion: "3.11",
|
|
788
|
+
presets: [["python", "No framework"]],
|
|
789
|
+
command: "gunicorn app:app"
|
|
790
|
+
},
|
|
791
|
+
php: {
|
|
792
|
+
versions: ["8.4", "8.3", "8.2", "8.1", "7.4"],
|
|
793
|
+
defaultVersion: "8.4",
|
|
794
|
+
presets: [
|
|
795
|
+
["swoole", "Swoole"],
|
|
796
|
+
["php", "No framework"]
|
|
797
|
+
],
|
|
798
|
+
command: "php8.4 server.php"
|
|
799
|
+
}
|
|
800
|
+
};
|
|
801
|
+
var LUMEN_BUILD = {
|
|
802
|
+
command: "lm build --serverless",
|
|
803
|
+
/** Directory `lm build --serverless` writes to, relative to the project root. */
|
|
804
|
+
outDir: "dist"
|
|
805
|
+
};
|
|
806
|
+
|
|
807
|
+
// src/index.ts
|
|
808
|
+
var Roxyon = class {
|
|
809
|
+
client;
|
|
810
|
+
auth;
|
|
811
|
+
account;
|
|
812
|
+
tokens;
|
|
813
|
+
subscriptions;
|
|
814
|
+
domains;
|
|
815
|
+
applications;
|
|
816
|
+
sites;
|
|
817
|
+
constructor(options = {}) {
|
|
818
|
+
this.client = options instanceof RoxyonClient ? options : new RoxyonClient(options);
|
|
819
|
+
this.auth = new AuthApi(this.client);
|
|
820
|
+
this.account = new AccountApi(this.client);
|
|
821
|
+
this.tokens = new TokensApi(this.client);
|
|
822
|
+
this.subscriptions = new SubscriptionsApi(this.client);
|
|
823
|
+
this.domains = new DomainsApi(this.client);
|
|
824
|
+
this.applications = new ApplicationsApi(this.client);
|
|
825
|
+
this.sites = new SitesApi(this.client);
|
|
826
|
+
}
|
|
827
|
+
get sessionToken() {
|
|
828
|
+
return this.client.getSessionToken();
|
|
829
|
+
}
|
|
830
|
+
};
|
|
831
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
832
|
+
0 && (module.exports = {
|
|
833
|
+
AccountApi,
|
|
834
|
+
ApplicationsApi,
|
|
835
|
+
AuthApi,
|
|
836
|
+
DEFAULT_APP,
|
|
837
|
+
DEFAULT_BAAS_URL,
|
|
838
|
+
DEFAULT_CONSOLE_URL,
|
|
839
|
+
DomainsApi,
|
|
840
|
+
LUMEN_BUILD,
|
|
841
|
+
RUNTIMES,
|
|
842
|
+
Roxyon,
|
|
843
|
+
RoxyonApiError,
|
|
844
|
+
RoxyonClient,
|
|
845
|
+
SitesApi,
|
|
846
|
+
SubscriptionsApi,
|
|
847
|
+
TokensApi,
|
|
848
|
+
assertOk,
|
|
849
|
+
envFromStored,
|
|
850
|
+
formatEnv,
|
|
851
|
+
isAuthError,
|
|
852
|
+
parseEnv,
|
|
853
|
+
rxError,
|
|
854
|
+
toQueryString
|
|
855
|
+
});
|
|
856
|
+
//# sourceMappingURL=index.cjs.map
|