@madewithremy/admin 0.1.40 → 0.1.41
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 +1 -1
- package/dist/index.d.ts +17 -11
- package/dist/index.js +56 -5
- package/dist/index.js.map +1 -1
- package/dist/prod.js +65 -13
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @madewithremy/admin
|
|
2
2
|
|
|
3
|
-
Admin SDK + CLI for managing production Remy apps: request logs, analytics, releases, users, secrets, database, files, email (including custom sending/receiving domains), scheduled jobs, QA replays, and more. Almost every operation wraps the same management API the dashboard uses, so anything answerable in the console is answerable here — from the terminal or from code. The exception is `qa-recordings export`, which
|
|
3
|
+
Admin SDK + CLI for managing production Remy apps: request logs, analytics, releases, users, secrets, database, files, email (including custom sending/receiving domains), scheduled jobs, QA replays, and more. Almost every operation wraps the same management API the dashboard uses, so anything answerable in the console is answerable here — from the terminal or from code. The exception is `qa-recordings export`, which the dev box that recorded the replay packages for the platform to render, and which therefore only runs inside a sandbox.
|
|
4
4
|
|
|
5
5
|
One surface, two skins: every CLI command (`remy-admin <group> <sub>`) is also a typed client method (`admin.<group>.<sub>()`). Both are thin layers over the same ops core.
|
|
6
6
|
|
package/dist/index.d.ts
CHANGED
|
@@ -7378,7 +7378,7 @@ declare namespace prerender {
|
|
|
7378
7378
|
* roles/http/routes/Apps/develop/recordings.ts
|
|
7379
7379
|
* customerApps/AppDevBoxes/recordings/sessions.ts
|
|
7380
7380
|
*
|
|
7381
|
-
* Export is the exception —
|
|
7381
|
+
* Export is the exception — the dev box packages it (see ../ops/qaRecordings).
|
|
7382
7382
|
*/
|
|
7383
7383
|
/** One document lifetime within a session: a page load and everything after it
|
|
7384
7384
|
* until the next one. Its lowest-seq chunk carries the rrweb FullSnapshot. */
|
|
@@ -7430,13 +7430,13 @@ interface QaRecordingsExportResult {
|
|
|
7430
7430
|
* own.
|
|
7431
7431
|
*
|
|
7432
7432
|
* `export` is the one operation in this SDK that does NOT go through the
|
|
7433
|
-
* management API.
|
|
7434
|
-
*
|
|
7435
|
-
*
|
|
7436
|
-
*
|
|
7437
|
-
*
|
|
7433
|
+
* management API. A replay names the app's own files as the dev box that
|
|
7434
|
+
* recorded it served them, signed in, so that box is what packages it for the
|
|
7435
|
+
* platform to render: it has the proxy those files come from and the browser
|
|
7436
|
+
* session they need. So this talks to the sandbox's local sidecar, and says so
|
|
7437
|
+
* plainly when there isn't one. (CLI-only paths are established here —
|
|
7438
7438
|
* `methods invoke --stream`, `jewels export --file` — but this is the first
|
|
7439
|
-
* that needs local
|
|
7439
|
+
* that needs the local box rather than local disk.)
|
|
7440
7440
|
*/
|
|
7441
7441
|
|
|
7442
7442
|
/** The app store QA replay chunks are written to (private). */
|
|
@@ -7468,13 +7468,19 @@ interface QaRecordingsExportParams {
|
|
|
7468
7468
|
access?: 'public' | 'private';
|
|
7469
7469
|
/** Milliseconds to wait for the render. */
|
|
7470
7470
|
timeoutMs?: number;
|
|
7471
|
+
/**
|
|
7472
|
+
* How far the render is, as it runs: a line when it moves to a new phase
|
|
7473
|
+
* and every 10% after, e.g. `Rendering the replay… 40%`.
|
|
7474
|
+
*/
|
|
7475
|
+
onProgress?: (message: string) => void;
|
|
7471
7476
|
}
|
|
7472
7477
|
/**
|
|
7473
|
-
* Render a window of a recording session to an mp4
|
|
7478
|
+
* Render a window of a recording session to an mp4.
|
|
7474
7479
|
*
|
|
7475
|
-
* Blocks for the whole render — the
|
|
7476
|
-
*
|
|
7477
|
-
* box, since
|
|
7480
|
+
* Blocks for the whole render — the box packages the replay, the platform
|
|
7481
|
+
* renders it, which takes minutes for a long clip — and answers with where
|
|
7482
|
+
* the video landed. Runs only on a Remy dev box, since the replay's own files
|
|
7483
|
+
* are served by the box that recorded it.
|
|
7478
7484
|
*
|
|
7479
7485
|
* @example
|
|
7480
7486
|
* const { url } = await admin.qaRecordings.exportVideo({
|
package/dist/index.js
CHANGED
|
@@ -2727,7 +2727,12 @@ function seqOf(key) {
|
|
|
2727
2727
|
);
|
|
2728
2728
|
return Number.isFinite(n) ? n : 0;
|
|
2729
2729
|
}
|
|
2730
|
-
var DEFAULT_EXPORT_TIMEOUT_MS =
|
|
2730
|
+
var DEFAULT_EXPORT_TIMEOUT_MS = 33 * 6e4;
|
|
2731
|
+
var EXPORT_PHASE_LABEL = {
|
|
2732
|
+
loading: "Preparing the replay",
|
|
2733
|
+
rendering: "Rendering the replay",
|
|
2734
|
+
encoding: "Encoding the video"
|
|
2735
|
+
};
|
|
2731
2736
|
async function exportVideo(_ctx, params) {
|
|
2732
2737
|
const body = {
|
|
2733
2738
|
recordingSessionId: params.sessionId,
|
|
@@ -2740,7 +2745,12 @@ async function exportVideo(_ctx, params) {
|
|
|
2740
2745
|
try {
|
|
2741
2746
|
res = await fetch(`${SIDECAR_URL}/export-recording`, {
|
|
2742
2747
|
method: "POST",
|
|
2743
|
-
|
|
2748
|
+
// A line per progress event, then the answer (the sidecar's
|
|
2749
|
+
// streamRecordingExport).
|
|
2750
|
+
headers: {
|
|
2751
|
+
"Content-Type": "application/json",
|
|
2752
|
+
Accept: "application/x-ndjson"
|
|
2753
|
+
},
|
|
2744
2754
|
body: JSON.stringify(body),
|
|
2745
2755
|
signal: AbortSignal.timeout(
|
|
2746
2756
|
params.timeoutMs ?? DEFAULT_EXPORT_TIMEOUT_MS
|
|
@@ -2751,12 +2761,53 @@ async function exportVideo(_ctx, params) {
|
|
|
2751
2761
|
`Could not reach the sandbox at ${SIDECAR_URL} (${err instanceof Error ? err.message : String(err)}). Rendering a replay needs the dev box that recorded it \u2014 this command only works inside a Remy sandbox.`
|
|
2752
2762
|
);
|
|
2753
2763
|
}
|
|
2754
|
-
|
|
2755
|
-
|
|
2764
|
+
if (!res.ok || !res.body) {
|
|
2765
|
+
const refused = await res.json().catch(() => ({}));
|
|
2756
2766
|
throw new Error(
|
|
2757
|
-
typeof
|
|
2767
|
+
typeof refused.error === "string" ? refused.error : `The sandbox refused the export (HTTP ${res.status}).`
|
|
2758
2768
|
);
|
|
2759
2769
|
}
|
|
2770
|
+
let payload = {};
|
|
2771
|
+
let shown = null;
|
|
2772
|
+
const decoder = new TextDecoder();
|
|
2773
|
+
let buffer = "";
|
|
2774
|
+
const read = (text) => {
|
|
2775
|
+
if (!text.trim()) {
|
|
2776
|
+
return;
|
|
2777
|
+
}
|
|
2778
|
+
let value;
|
|
2779
|
+
try {
|
|
2780
|
+
value = JSON.parse(text);
|
|
2781
|
+
} catch {
|
|
2782
|
+
return;
|
|
2783
|
+
}
|
|
2784
|
+
const progress = value.progress;
|
|
2785
|
+
if (!progress) {
|
|
2786
|
+
payload = value;
|
|
2787
|
+
return;
|
|
2788
|
+
}
|
|
2789
|
+
const phase = progress.phase ?? "rendering";
|
|
2790
|
+
const percent = Math.round(progress.percent ?? 0);
|
|
2791
|
+
const step = Math.floor(percent / 10);
|
|
2792
|
+
if (shown?.phase !== phase || step > shown.step) {
|
|
2793
|
+
shown = { phase, step };
|
|
2794
|
+
params.onProgress?.(
|
|
2795
|
+
`${EXPORT_PHASE_LABEL[phase] ?? EXPORT_PHASE_LABEL.rendering}\u2026 ${percent}%`
|
|
2796
|
+
);
|
|
2797
|
+
}
|
|
2798
|
+
};
|
|
2799
|
+
for await (const chunk of res.body) {
|
|
2800
|
+
buffer += decoder.decode(chunk, { stream: true });
|
|
2801
|
+
let end;
|
|
2802
|
+
while ((end = buffer.indexOf("\n")) !== -1) {
|
|
2803
|
+
read(buffer.slice(0, end));
|
|
2804
|
+
buffer = buffer.slice(end + 1);
|
|
2805
|
+
}
|
|
2806
|
+
}
|
|
2807
|
+
read(buffer);
|
|
2808
|
+
if (typeof payload.error === "string") {
|
|
2809
|
+
throw new Error(payload.error);
|
|
2810
|
+
}
|
|
2760
2811
|
const status2 = payload.export;
|
|
2761
2812
|
if (!status2) {
|
|
2762
2813
|
throw new Error(
|