@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/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 Execute a SQL query against the live release's database
2174
- tables List all tables in the database
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
- Notes:
2188
- - SQL is taken verbatim: this is the one command that does not parse flags, so
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), and the object
3548
- stays deletable with \`rm\`. Never use the account media CDN
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 fs7 from "fs";
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 = fs7.readFileSync(manifestPath, "utf-8");
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[1] === "query" ? 3 : 2) : argv.slice(1);
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@madewithremy/admin",
3
- "version": "0.1.46",
3
+ "version": "0.1.48",
4
4
  "description": "Admin SDK + CLI for managing production Remy apps",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",