@madewithremy/admin 0.1.46 → 0.1.48
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/CHANGELOG.md +18 -0
- package/dist/index.d.ts +137 -5
- package/dist/index.js +41 -0
- package/dist/index.js.map +1 -1
- package/dist/prod.js +207 -14
- package/package.json +1 -1
package/dist/prod.js
CHANGED
|
@@ -2138,6 +2138,29 @@ function query2(ctx, sql) {
|
|
|
2138
2138
|
{ queries: [{ sql }] }
|
|
2139
2139
|
);
|
|
2140
2140
|
}
|
|
2141
|
+
function history(ctx, params = {}) {
|
|
2142
|
+
return call(
|
|
2143
|
+
ctx,
|
|
2144
|
+
"GET",
|
|
2145
|
+
`/_internal/v2/apps/${ctx.appId}/db/history${qs({ limit: params.limit, cursor: params.cursor })}`
|
|
2146
|
+
);
|
|
2147
|
+
}
|
|
2148
|
+
function historyQuery(ctx, releaseId, sql) {
|
|
2149
|
+
return call(
|
|
2150
|
+
ctx,
|
|
2151
|
+
"POST",
|
|
2152
|
+
`/_internal/v2/apps/${ctx.appId}/db/history/query`,
|
|
2153
|
+
{ releaseId, queries: [{ sql }] }
|
|
2154
|
+
);
|
|
2155
|
+
}
|
|
2156
|
+
function historyAttach(ctx, releaseId, sql) {
|
|
2157
|
+
return call(
|
|
2158
|
+
ctx,
|
|
2159
|
+
"POST",
|
|
2160
|
+
`/_internal/v2/apps/${ctx.appId}/db/history/attach`,
|
|
2161
|
+
{ releaseId, queries: [{ sql }] }
|
|
2162
|
+
);
|
|
2163
|
+
}
|
|
2141
2164
|
|
|
2142
2165
|
// src/commands/db.ts
|
|
2143
2166
|
var dbSpecs = {
|
|
@@ -2150,11 +2173,46 @@ var dbSpecs = {
|
|
|
2150
2173
|
},
|
|
2151
2174
|
"db tables": {
|
|
2152
2175
|
usage: "Usage: remy-admin db tables"
|
|
2176
|
+
},
|
|
2177
|
+
"db history": {
|
|
2178
|
+
usage: "Usage: remy-admin db history [--limit 20] [--cursor <nextCursor>]",
|
|
2179
|
+
flags: {
|
|
2180
|
+
limit: { type: "number", min: 1, max: 100 },
|
|
2181
|
+
cursor: { type: "string" }
|
|
2182
|
+
}
|
|
2183
|
+
},
|
|
2184
|
+
// raw, as `db query` is: the SQL is taken verbatim.
|
|
2185
|
+
"db history query": {
|
|
2186
|
+
usage: "Usage: remy-admin db history query <releaseId> <sql>",
|
|
2187
|
+
raw: true,
|
|
2188
|
+
positionals: [
|
|
2189
|
+
{ name: "releaseId", required: true },
|
|
2190
|
+
{ name: "sql", required: true }
|
|
2191
|
+
]
|
|
2192
|
+
},
|
|
2193
|
+
"db history attach": {
|
|
2194
|
+
usage: "Usage: remy-admin db history attach <releaseId> <sql>",
|
|
2195
|
+
raw: true,
|
|
2196
|
+
positionals: [
|
|
2197
|
+
{ name: "releaseId", required: true },
|
|
2198
|
+
{ name: "sql", required: true }
|
|
2199
|
+
]
|
|
2153
2200
|
}
|
|
2154
2201
|
};
|
|
2155
2202
|
async function dbQuery(ctx, a) {
|
|
2156
2203
|
out(await query2(ctx, a.req("sql")));
|
|
2157
2204
|
}
|
|
2205
|
+
async function dbHistory(ctx, a) {
|
|
2206
|
+
out(
|
|
2207
|
+
await history(ctx, { limit: a.num("limit"), cursor: a.str("cursor") })
|
|
2208
|
+
);
|
|
2209
|
+
}
|
|
2210
|
+
async function dbHistoryQuery(ctx, a) {
|
|
2211
|
+
out(await historyQuery(ctx, a.req("releaseId"), a.req("sql")));
|
|
2212
|
+
}
|
|
2213
|
+
async function dbHistoryAttach(ctx, a) {
|
|
2214
|
+
out(await historyAttach(ctx, a.req("releaseId"), a.req("sql")));
|
|
2215
|
+
}
|
|
2158
2216
|
async function dbTables(ctx) {
|
|
2159
2217
|
out(
|
|
2160
2218
|
await query2(
|
|
@@ -2165,28 +2223,91 @@ async function dbTables(ctx) {
|
|
|
2165
2223
|
}
|
|
2166
2224
|
var dbHandlers = {
|
|
2167
2225
|
"db query": dbQuery,
|
|
2168
|
-
"db tables": dbTables
|
|
2226
|
+
"db tables": dbTables,
|
|
2227
|
+
"db history": dbHistory,
|
|
2228
|
+
"db history query": dbHistoryQuery,
|
|
2229
|
+
"db history attach": dbHistoryAttach
|
|
2169
2230
|
};
|
|
2170
|
-
var dbHelp = `remy-admin db \u2014 Query the production database.
|
|
2231
|
+
var dbHelp = `remy-admin db \u2014 Query the production database, and the copies of it past releases kept.
|
|
2171
2232
|
|
|
2172
2233
|
Subcommands:
|
|
2173
|
-
query
|
|
2174
|
-
tables
|
|
2234
|
+
query Execute a SQL query against the live release's database
|
|
2235
|
+
tables List all tables in the database
|
|
2236
|
+
history List past releases' databases, newest first, with when each was live
|
|
2237
|
+
history query Read a past release's database (read-only)
|
|
2238
|
+
history attach Run SQL against live with a past release's database attached as "history"
|
|
2175
2239
|
|
|
2176
2240
|
Usage:
|
|
2177
2241
|
remy-admin db <sql>
|
|
2178
2242
|
remy-admin db query <sql>
|
|
2179
2243
|
remy-admin db tables
|
|
2244
|
+
remy-admin db history [--limit 20] [--cursor <nextCursor>]
|
|
2245
|
+
remy-admin db history query <releaseId> <sql>
|
|
2246
|
+
remy-admin db history attach <releaseId> <sql>
|
|
2247
|
+
|
|
2248
|
+
Past releases:
|
|
2249
|
+
Every deploy copies the live database into the new release and keeps the
|
|
2250
|
+
release it replaces, database and all, exactly as it stood at that moment.
|
|
2251
|
+
So each entry 'db history' lists holds live as of its liveUntil: anything
|
|
2252
|
+
written after that is only in later copies and in live, and nothing that
|
|
2253
|
+
happened between two deploys is kept anywhere. The release live now is
|
|
2254
|
+
listed first (current: true); its database is live, so use 'db query'.
|
|
2255
|
+
|
|
2256
|
+
'db history query' reads one of those copies, and nothing can change it: a
|
|
2257
|
+
write is refused. Its schema is that release's, which may differ from
|
|
2258
|
+
live's: SELECT name FROM sqlite_master lists its tables, and
|
|
2259
|
+
PRAGMA table_info(<table>) a table's columns.
|
|
2260
|
+
|
|
2261
|
+
'db history attach' runs against live with the copy attached as the schema
|
|
2262
|
+
"history": history.<table> is the past table, and an unqualified name is
|
|
2263
|
+
live's. Writes to live are real, exactly as with 'db query', and a
|
|
2264
|
+
statement runs as one transaction. The attached copy is private to the
|
|
2265
|
+
call, so writing into it changes nothing anywhere.
|
|
2266
|
+
PRAGMA history.table_info(<table>) shows a past table's columns.
|
|
2267
|
+
|
|
2268
|
+
Recovering data is a merge, not a rollback. Live has moved on since the
|
|
2269
|
+
copy was made (rows added, edits people meant to make), so decide what
|
|
2270
|
+
comes back: which rows, which columns, and what happens to anything
|
|
2271
|
+
written since.
|
|
2272
|
+
- Find the copy that has what's missing: the windows say where to look,
|
|
2273
|
+
and a count or a few rows confirm it.
|
|
2274
|
+
- Compare it with live through attach (a join on id, a count of what
|
|
2275
|
+
differs) before changing anything.
|
|
2276
|
+
- Write the repair to restore exactly that. Columns emptied in live,
|
|
2277
|
+
filled back by id, leaving rows created since alone:
|
|
2278
|
+
UPDATE orders SET note = h.note, total = h.total
|
|
2279
|
+
FROM history.orders h
|
|
2280
|
+
WHERE h.id = orders.id AND orders.note IS NULL
|
|
2281
|
+
Rows deleted from live, put back:
|
|
2282
|
+
INSERT INTO orders (id, note, total)
|
|
2283
|
+
SELECT id, note, total FROM history.orders
|
|
2284
|
+
WHERE id NOT IN (SELECT id FROM orders)
|
|
2285
|
+
- Tell the user what will change and get their go-ahead before writing
|
|
2286
|
+
live, as with any change to production.
|
|
2287
|
+
If no copy covers the moment that matters, say so plainly rather than
|
|
2288
|
+
restoring from the nearest one.
|
|
2289
|
+
|
|
2290
|
+
Notes:
|
|
2291
|
+
- SQL is taken verbatim: 'db query' and the two history commands that take
|
|
2292
|
+
SQL don't parse flags, so a statement may safely begin with a '--'
|
|
2293
|
+
comment. Always quote the SQL.
|
|
2294
|
+
- The history commands use the database named as live's default.
|
|
2295
|
+
- The first history command on a copy fetches it, so it takes longer for a
|
|
2296
|
+
large database; the ones after it are quick.
|
|
2297
|
+
- Errors: release_not_found (no release of this app that was ever live has
|
|
2298
|
+
that id), release_is_live (it's the live release: use 'db query'),
|
|
2299
|
+
no_database (that release has no database of live's name), sql_error
|
|
2300
|
+
(the database refused the statement, a write to a past copy included;
|
|
2301
|
+
nothing in it was applied).
|
|
2180
2302
|
|
|
2181
2303
|
Examples:
|
|
2182
2304
|
remy-admin db tables
|
|
2183
2305
|
remy-admin db "SELECT * FROM users LIMIT 10"
|
|
2184
2306
|
remy-admin db "INSERT INTO categories (name) VALUES ('Electronics')"
|
|
2185
2307
|
remy-admin db query "SELECT * FROM users LIMIT 10"
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
-
|
|
2189
|
-
a statement may safely begin with a '--' comment. Always quote the SQL.`;
|
|
2308
|
+
remy-admin db history --limit 5
|
|
2309
|
+
remy-admin db history query <releaseId> "SELECT count(*) AS n FROM orders"
|
|
2310
|
+
remy-admin db history attach <releaseId> "SELECT count(*) AS emptied FROM orders o JOIN history.orders h ON h.id = o.id WHERE o.note IS NULL AND h.note IS NOT NULL"`;
|
|
2190
2311
|
|
|
2191
2312
|
// src/ops/secrets.ts
|
|
2192
2313
|
function list6(ctx) {
|
|
@@ -3544,8 +3665,9 @@ Notes:
|
|
|
3544
3665
|
of the store that holds objects; in a store holding both, a key is found
|
|
3545
3666
|
wherever it is, and ls asks which.
|
|
3546
3667
|
- Handing someone a large or sensitive file? Use a PRIVATE store + \`sign\`:
|
|
3547
|
-
the link is unguessable, expires (--ttl seconds, max 7 days
|
|
3548
|
-
|
|
3668
|
+
the link is unguessable, expires (--ttl seconds, max 7 days, or 3 hours
|
|
3669
|
+
where it's a storage presign; expiresAt says which), and the object stays
|
|
3670
|
+
deletable with \`rm\`. Never use the account media CDN
|
|
3549
3671
|
(\`mindstudio upload\`) for sensitive material \u2014 those URLs are public and
|
|
3550
3672
|
permanent.
|
|
3551
3673
|
- put: the key defaults to a content hash (sha256) of the bytes, so
|
|
@@ -8981,6 +9103,63 @@ Examples:
|
|
|
8981
9103
|
remy-admin cron list --runs 50
|
|
8982
9104
|
remy-admin cron run dailyDigest`;
|
|
8983
9105
|
|
|
9106
|
+
// src/commands/feedback.ts
|
|
9107
|
+
import fs7 from "fs";
|
|
9108
|
+
|
|
9109
|
+
// src/ops/feedback.ts
|
|
9110
|
+
function draft(ctx, message) {
|
|
9111
|
+
return call(
|
|
9112
|
+
ctx,
|
|
9113
|
+
"POST",
|
|
9114
|
+
`/_internal/v2/apps/${ctx.appId}/feedback-draft`,
|
|
9115
|
+
{ message }
|
|
9116
|
+
);
|
|
9117
|
+
}
|
|
9118
|
+
|
|
9119
|
+
// src/commands/feedback.ts
|
|
9120
|
+
var feedbackSpecs = {
|
|
9121
|
+
"feedback draft": {
|
|
9122
|
+
usage: "Usage: remy-admin feedback draft <message> (or --message - to read stdin)",
|
|
9123
|
+
positionals: [{ name: "message" }],
|
|
9124
|
+
flags: { message: { type: "string" } },
|
|
9125
|
+
requireAnyOf: {
|
|
9126
|
+
flags: ["message"],
|
|
9127
|
+
positionals: ["message"],
|
|
9128
|
+
message: "A message is required."
|
|
9129
|
+
}
|
|
9130
|
+
}
|
|
9131
|
+
};
|
|
9132
|
+
async function feedbackDraft(ctx, a) {
|
|
9133
|
+
const flag = a.str("message");
|
|
9134
|
+
const message = flag === "-" ? fs7.readFileSync(0, "utf-8") : flag ?? a.req("message");
|
|
9135
|
+
out(await draft(ctx, message));
|
|
9136
|
+
}
|
|
9137
|
+
var feedbackHandlers = {
|
|
9138
|
+
"feedback draft": feedbackDraft
|
|
9139
|
+
};
|
|
9140
|
+
var feedbackHelp = `remy-admin feedback \u2014 Report a problem with Remy itself to the Remy team.
|
|
9141
|
+
|
|
9142
|
+
Subcommands:
|
|
9143
|
+
draft Open Send Feedback in the user's editor with your message filled in
|
|
9144
|
+
|
|
9145
|
+
Usage:
|
|
9146
|
+
remy-admin feedback draft <message>
|
|
9147
|
+
remy-admin feedback draft --message - Read the message from stdin
|
|
9148
|
+
|
|
9149
|
+
Notes:
|
|
9150
|
+
- Nothing is sent by this command. The user sees Send Feedback open in their
|
|
9151
|
+
editor with your message in it, can edit it, and decides whether to send.
|
|
9152
|
+
- What goes with a sent report: your conversation, this sandbox's logs and the
|
|
9153
|
+
editor's state. The message itself is what the team reads first.
|
|
9154
|
+
- Only the user's own editors on this app open it, and only ones open now.
|
|
9155
|
+
Returns { status: "sent_to_editor" }.
|
|
9156
|
+
- Errors: feedback_not_configured (this deployment doesn't take feedback),
|
|
9157
|
+
missing_message, message_too_long (over 20,000 characters).
|
|
9158
|
+
|
|
9159
|
+
Examples:
|
|
9160
|
+
remy-admin feedback draft "Every save restarted the dev server twice, so previews lagged a full reload behind."
|
|
9161
|
+
cat note.md | remy-admin feedback draft --message -`;
|
|
9162
|
+
|
|
8984
9163
|
// src/ops/infra.ts
|
|
8985
9164
|
var DEFAULT_WAIT_TIMEOUT_MS4 = 10 * 60 * 1e3;
|
|
8986
9165
|
var POLL_MS5 = 3e3;
|
|
@@ -9688,6 +9867,7 @@ var SPECS = {
|
|
|
9688
9867
|
...settingsSpecs,
|
|
9689
9868
|
...emailSpecs,
|
|
9690
9869
|
...cronSpecs,
|
|
9870
|
+
...feedbackSpecs,
|
|
9691
9871
|
...infraSpecs,
|
|
9692
9872
|
...qaRecordingsSpecs
|
|
9693
9873
|
};
|
|
@@ -9722,6 +9902,7 @@ var HANDLERS = {
|
|
|
9722
9902
|
...settingsHandlers,
|
|
9723
9903
|
...emailHandlers,
|
|
9724
9904
|
...cronHandlers,
|
|
9905
|
+
...feedbackHandlers,
|
|
9725
9906
|
...infraHandlers,
|
|
9726
9907
|
...qaRecordingsHandlers
|
|
9727
9908
|
};
|
|
@@ -9750,12 +9931,13 @@ var GROUP_HELP = {
|
|
|
9750
9931
|
settings: settingsHelp,
|
|
9751
9932
|
email: emailHelp,
|
|
9752
9933
|
cron: cronHelp,
|
|
9934
|
+
feedback: feedbackHelp,
|
|
9753
9935
|
infra: infraHelp,
|
|
9754
9936
|
"qa-recordings": qaRecordingsHelp
|
|
9755
9937
|
};
|
|
9756
9938
|
|
|
9757
9939
|
// src/ctx.ts
|
|
9758
|
-
import
|
|
9940
|
+
import fs8 from "fs";
|
|
9759
9941
|
import path7 from "path";
|
|
9760
9942
|
|
|
9761
9943
|
// src/parseJsonConfig.ts
|
|
@@ -9788,7 +9970,7 @@ function loadWorkspaceAppId(workspaceDir = WORKSPACE_DIR) {
|
|
|
9788
9970
|
const manifestPath = path7.join(workspaceDir, "mindstudio.json");
|
|
9789
9971
|
let raw;
|
|
9790
9972
|
try {
|
|
9791
|
-
raw =
|
|
9973
|
+
raw = fs8.readFileSync(manifestPath, "utf-8");
|
|
9792
9974
|
} catch (err) {
|
|
9793
9975
|
if (err.code === "ENOENT") {
|
|
9794
9976
|
throw new Error(`mindstudio.json not found at ${manifestPath}`);
|
|
@@ -9819,7 +10001,7 @@ Commands:
|
|
|
9819
10001
|
diagnostics View the post-deploy Lighthouse audit (scores, issues, raw report)
|
|
9820
10002
|
domains Subdomain, custom domains, and buying a domain
|
|
9821
10003
|
users Manage app users and roles
|
|
9822
|
-
db Query the production database
|
|
10004
|
+
db Query the production database, and past releases' copies of it
|
|
9823
10005
|
secrets Manage app secrets (env vars)
|
|
9824
10006
|
methods List and invoke methods
|
|
9825
10007
|
data Sync databases between dev and live (lift-from-dev / lift-from-live)
|
|
@@ -9835,6 +10017,7 @@ Commands:
|
|
|
9835
10017
|
settings App settings \u2014 signup allowlist, test accounts, embed origins, toggles
|
|
9836
10018
|
email Email \u2014 delivery log, blast stats, unsubscribes, inbox, custom domains
|
|
9837
10019
|
cron Scheduled jobs \u2014 schedules, run history, manual trigger
|
|
10020
|
+
feedback Report a problem with Remy itself, for the user to review and send
|
|
9838
10021
|
infra Dedicated infrastructure \u2014 provision, hibernate, resize + destroy retrieval capacity
|
|
9839
10022
|
qa-recordings Browser-QA session replays \u2014 list them, render one as an mp4
|
|
9840
10023
|
|
|
@@ -9871,6 +10054,7 @@ var GROUPS = [
|
|
|
9871
10054
|
"settings",
|
|
9872
10055
|
"email",
|
|
9873
10056
|
"cron",
|
|
10057
|
+
"feedback",
|
|
9874
10058
|
"infra",
|
|
9875
10059
|
"qa-recordings"
|
|
9876
10060
|
];
|
|
@@ -9943,6 +10127,15 @@ ${guide}`;
|
|
|
9943
10127
|
}
|
|
9944
10128
|
return void 0;
|
|
9945
10129
|
}
|
|
10130
|
+
function dbWordsEnd(argv) {
|
|
10131
|
+
if (argv[1] === "query") {
|
|
10132
|
+
return 3;
|
|
10133
|
+
}
|
|
10134
|
+
if (argv[1] === "history") {
|
|
10135
|
+
return argv[2] === "query" || argv[2] === "attach" ? 4 : 3;
|
|
10136
|
+
}
|
|
10137
|
+
return 2;
|
|
10138
|
+
}
|
|
9946
10139
|
function resolveHelp(argv) {
|
|
9947
10140
|
const group = argv[0];
|
|
9948
10141
|
if (!group || group === "--help" || group === "-h") {
|
|
@@ -9955,7 +10148,7 @@ function resolveHelp(argv) {
|
|
|
9955
10148
|
if (argv.length === 1) {
|
|
9956
10149
|
return groupHelp;
|
|
9957
10150
|
}
|
|
9958
|
-
const scan = group === "db" ? argv.slice(1, argv
|
|
10151
|
+
const scan = group === "db" ? argv.slice(1, dbWordsEnd(argv)) : argv.slice(1);
|
|
9959
10152
|
if (!scan.some((t) => t === "--help" || t === "-h")) {
|
|
9960
10153
|
return void 0;
|
|
9961
10154
|
}
|