@rebasepro/cli 0.23.0 → 0.24.1-canary.g19596ef

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.
Files changed (186) hide show
  1. package/README.md +19 -3
  2. package/bin/rebase.js +4 -1
  3. package/dist/agent-setup-B31F00DC.js +324 -0
  4. package/dist/agent-setup-B31F00DC.js.map +1 -0
  5. package/dist/api-keys-Bl1I9q7M.js +525 -0
  6. package/dist/api-keys-Bl1I9q7M.js.map +1 -0
  7. package/dist/apps-CNgQJjg5.js +340 -0
  8. package/dist/apps-CNgQJjg5.js.map +1 -0
  9. package/dist/args-BnmsEXvu.js +109 -0
  10. package/dist/args-BnmsEXvu.js.map +1 -0
  11. package/dist/atlas-dev-database-D_LP_Woq.js +62 -0
  12. package/dist/atlas-dev-database-D_LP_Woq.js.map +1 -0
  13. package/dist/auth-aySHEF45.js +414 -0
  14. package/dist/auth-aySHEF45.js.map +1 -0
  15. package/dist/branch-pointer-DvSHBjjz.js +2 -0
  16. package/dist/build-DIHWIqO4.js +336 -0
  17. package/dist/build-DIHWIqO4.js.map +1 -0
  18. package/dist/bundle-CqURVN-K.js +1621 -0
  19. package/dist/bundle-CqURVN-K.js.map +1 -0
  20. package/dist/bundle.d.ts +12 -3
  21. package/dist/cli.d.ts +23 -2
  22. package/dist/cloud-DjByQlkW.js +9667 -0
  23. package/dist/cloud-DjByQlkW.js.map +1 -0
  24. package/dist/collection-drift-BKj_BoMq.js +106 -0
  25. package/dist/collection-drift-BKj_BoMq.js.map +1 -0
  26. package/dist/commands/__test_support__/parser-specs.d.ts +13 -0
  27. package/dist/commands/agent-setup.d.ts +116 -0
  28. package/dist/commands/api-keys.d.ts +86 -23
  29. package/dist/commands/apps.d.ts +17 -0
  30. package/dist/commands/auth.d.ts +7 -0
  31. package/dist/commands/build.d.ts +5 -1
  32. package/dist/commands/cloud/auth.d.ts +4 -3
  33. package/dist/commands/cloud/bundle-deploy.d.ts +45 -0
  34. package/dist/commands/cloud/context.d.ts +44 -5
  35. package/dist/commands/cloud/cron.d.ts +53 -0
  36. package/dist/commands/cloud/debug.d.ts +134 -0
  37. package/dist/commands/cloud/index.d.ts +89 -12
  38. package/dist/commands/cloud/token-capabilities.d.ts +76 -0
  39. package/dist/commands/cloud/tokens.d.ts +23 -0
  40. package/dist/commands/console.d.ts +12 -0
  41. package/dist/commands/db.d.ts +79 -25
  42. package/dist/commands/dev.d.ts +181 -13
  43. package/dist/commands/doctor.d.ts +5 -0
  44. package/dist/commands/generate_sdk.d.ts +29 -2
  45. package/dist/commands/init.d.ts +115 -14
  46. package/dist/commands/schema.d.ts +6 -0
  47. package/dist/commands/skills.d.ts +126 -7
  48. package/dist/commands/start.d.ts +44 -0
  49. package/dist/commands/storage.d.ts +46 -0
  50. package/dist/commands/upgrade.d.ts +1 -0
  51. package/dist/consent-ClRHmcxf.js +76 -0
  52. package/dist/consent-ClRHmcxf.js.map +1 -0
  53. package/dist/console-D6SJf8k_.js +102 -0
  54. package/dist/console-D6SJf8k_.js.map +1 -0
  55. package/dist/context-BqhRydBg.js +1234 -0
  56. package/dist/context-BqhRydBg.js.map +1 -0
  57. package/dist/{daemon-entry-LTFKtpHy.js → daemon-entry-Da-D3rmH.js} +3 -4
  58. package/dist/daemon-entry-Da-D3rmH.js.map +1 -0
  59. package/dist/daemon-pzsQwDHa.js +2 -0
  60. package/dist/db-B7RuNuaQ.js +1312 -0
  61. package/dist/db-B7RuNuaQ.js.map +1 -0
  62. package/dist/derive-Dl0z2OWi.js +484 -0
  63. package/dist/derive-Dl0z2OWi.js.map +1 -0
  64. package/dist/dev-CYr_WXan.js +1599 -0
  65. package/dist/dev-CYr_WXan.js.map +1 -0
  66. package/dist/dev-db/atlas-dev-database.d.ts +32 -0
  67. package/dist/dev-db/daemon-entry.d.ts +13 -0
  68. package/dist/dev-db/daemon.d.ts +10 -0
  69. package/dist/dev-db/last-dev-database.d.ts +41 -0
  70. package/dist/dev-db/prepare.d.ts +29 -3
  71. package/dist/dev-db/pull.d.ts +90 -5
  72. package/dist/dev-db/resolve.d.ts +26 -2
  73. package/dist/dev-preflight-B-LYyBf1.js +2 -0
  74. package/dist/dev-server-DG8Tt-uf.js +96 -0
  75. package/dist/dev-server-DG8Tt-uf.js.map +1 -0
  76. package/dist/doctor-BP8cZ-7F.js +771 -0
  77. package/dist/doctor-BP8cZ-7F.js.map +1 -0
  78. package/dist/eject-DizdoecV.js +335 -0
  79. package/dist/eject-DizdoecV.js.map +1 -0
  80. package/dist/fold-static-BC9CYicw.js +201 -0
  81. package/dist/fold-static-BC9CYicw.js.map +1 -0
  82. package/dist/fold-static.d.ts +26 -3
  83. package/dist/function-portability-BIvlvR9q.js +265 -0
  84. package/dist/function-portability-BIvlvR9q.js.map +1 -0
  85. package/dist/generate_sdk-BPqdJc3Q.js +489 -0
  86. package/dist/generate_sdk-BPqdJc3Q.js.map +1 -0
  87. package/dist/index.es.js +199 -22095
  88. package/dist/index.es.js.map +1 -1
  89. package/dist/init-Utstvh2Q.js +1149 -0
  90. package/dist/init-Utstvh2Q.js.map +1 -0
  91. package/dist/last-dev-database-B58g89uA.js +2 -0
  92. package/dist/manifest-DRp0UVTj.js +880 -0
  93. package/dist/manifest-DRp0UVTj.js.map +1 -0
  94. package/dist/manifest.d.ts +56 -4
  95. package/dist/normalize-imports-X0Tgze9y.js +107 -0
  96. package/dist/normalize-imports-X0Tgze9y.js.map +1 -0
  97. package/dist/package-manager-DSEOwTqM.js +193 -0
  98. package/dist/package-manager-DSEOwTqM.js.map +1 -0
  99. package/dist/path-flags-BFgG9V6y.js +91 -0
  100. package/dist/path-flags-BFgG9V6y.js.map +1 -0
  101. package/dist/precompress.d.ts +17 -0
  102. package/dist/prepare-CDN6lSpz.js +1236 -0
  103. package/dist/prepare-CDN6lSpz.js.map +1 -0
  104. package/dist/{project-3WCbrrfW.js → project-Wp2zoyeN.js} +34 -184
  105. package/dist/project-Wp2zoyeN.js.map +1 -0
  106. package/dist/pull-CrGYMEsy.js +458 -0
  107. package/dist/pull-CrGYMEsy.js.map +1 -0
  108. package/dist/resolve-B2O-wKKk.js +2 -0
  109. package/dist/resources-FD7AfZx8.js +183 -0
  110. package/dist/resources-FD7AfZx8.js.map +1 -0
  111. package/dist/schema-DMdr431S.js +125 -0
  112. package/dist/schema-DMdr431S.js.map +1 -0
  113. package/dist/skills-DTFibT1m.js +642 -0
  114. package/dist/skills-DTFibT1m.js.map +1 -0
  115. package/dist/start-DEa-hOBh.js +267 -0
  116. package/dist/start-DEa-hOBh.js.map +1 -0
  117. package/dist/{state-C59Elrnt.js → state-Do7ZJYkF.js} +49 -4
  118. package/dist/state-Do7ZJYkF.js.map +1 -0
  119. package/dist/status-DxnJTekv.js +352 -0
  120. package/dist/status-DxnJTekv.js.map +1 -0
  121. package/dist/storage-DyVq-f_F.js +303 -0
  122. package/dist/storage-DyVq-f_F.js.map +1 -0
  123. package/dist/telemetry/identity.d.ts +10 -1
  124. package/dist/telemetry-C8Uq8phb.js +129 -0
  125. package/dist/telemetry-C8Uq8phb.js.map +1 -0
  126. package/dist/telemetry-EMFRVRQi.js +351 -0
  127. package/dist/telemetry-EMFRVRQi.js.map +1 -0
  128. package/dist/unknown-command-DNwDcX3w.js +98 -0
  129. package/dist/unknown-command-DNwDcX3w.js.map +1 -0
  130. package/dist/upgrade-CtZtRkTE.js +1332 -0
  131. package/dist/upgrade-CtZtRkTE.js.map +1 -0
  132. package/dist/upgrade.d.ts +38 -5
  133. package/dist/utils/dev-preflight.d.ts +47 -5
  134. package/dist/utils/dev-server.d.ts +30 -0
  135. package/dist/utils/path-flags.d.ts +31 -0
  136. package/dist/utils/project.d.ts +16 -0
  137. package/dist/utils/temp-archive.d.ts +9 -0
  138. package/dist/version-Buyyo1ut.js +58 -0
  139. package/dist/version-Buyyo1ut.js.map +1 -0
  140. package/package.json +7 -7
  141. package/templates/eject/Dockerfile +4 -2
  142. package/templates/eject/backend/src/env.ts +1 -1
  143. package/templates/eject/backend/src/index.ts +42 -4
  144. package/templates/eject/docker-compose.custom.yml +8 -4
  145. package/templates/generated-schemas/blank.ts +40 -0
  146. package/templates/generated-schemas/blog.ts +127 -0
  147. package/templates/generated-schemas/ecommerce.ts +93 -0
  148. package/templates/overlays/baas/ai-instructions.md +1 -1
  149. package/templates/overlays/baas/config/package.json +1 -0
  150. package/templates/overlays/baas/config/storage.ts +5 -3
  151. package/templates/overlays/baas/package.json +1 -0
  152. package/templates/template/.cursorrules +1 -1
  153. package/templates/template/.env.example +20 -10
  154. package/templates/template/.github/copilot-instructions.md +1 -1
  155. package/templates/template/.mcp.json +2 -2
  156. package/templates/template/.windsurfrules +1 -1
  157. package/templates/template/AGENTS.md +1 -1
  158. package/templates/template/CLAUDE.md +1 -1
  159. package/templates/template/README.md +21 -15
  160. package/templates/template/ai-instructions.md +5 -4
  161. package/templates/template/backend/functions/hello.ts +8 -0
  162. package/templates/template/backend/package.json +1 -1
  163. package/templates/template/config/resources.ts +7 -6
  164. package/templates/template/config/storage.ts +5 -3
  165. package/templates/template/docker-compose.yml +15 -9
  166. package/templates/template/frontend/vite.config.ts +13 -3
  167. package/templates/template/gitignore +6 -1
  168. package/templates/template/package.json +5 -3
  169. package/templates/template/scripts/example.ts +6 -4
  170. package/templates/template/tsconfig.json +25 -0
  171. package/dist/branch-pointer-dBiClJ0p.js +0 -111
  172. package/dist/branch-pointer-dBiClJ0p.js.map +0 -1
  173. package/dist/constraints-DKGbNfUW.js +0 -48
  174. package/dist/constraints-DKGbNfUW.js.map +0 -1
  175. package/dist/daemon-DR9Tk0La.js +0 -355
  176. package/dist/daemon-DR9Tk0La.js.map +0 -1
  177. package/dist/daemon-entry-LTFKtpHy.js.map +0 -1
  178. package/dist/dev-preflight-CNLl4rdo.js +0 -367
  179. package/dist/dev-preflight-CNLl4rdo.js.map +0 -1
  180. package/dist/project-3WCbrrfW.js.map +0 -1
  181. package/dist/pull-Dywm5FWP.js +0 -232
  182. package/dist/pull-Dywm5FWP.js.map +0 -1
  183. package/dist/resolve-BAeRUBhg.js +0 -74
  184. package/dist/resolve-BAeRUBhg.js.map +0 -1
  185. package/dist/state-C59Elrnt.js.map +0 -1
  186. package/templates/template/backend/src/schema.generated.ts +0 -13
@@ -0,0 +1,303 @@
1
+ import { d as readEnvFile, p as requireProjectRoot } from "./project-Wp2zoyeN.js";
2
+ import { n as parseCommandArgs, r as wantsHelp, t as UsageError } from "./args-BnmsEXvu.js";
3
+ import { t as unknownCommand } from "./unknown-command-DNwDcX3w.js";
4
+ import { t as cliUserAgent } from "./version-Buyyo1ut.js";
5
+ import { describeFailure, resolveBaseUrl } from "./api-keys-Bl1I9q7M.js";
6
+ import chalk from "chalk";
7
+ import { RebaseApiError } from "@rebasepro/client";
8
+ import readline from "node:readline/promises";
9
+ //#region src/commands/storage.ts
10
+ /**
11
+ * CLI command: rebase storage <action>
12
+ *
13
+ * Whole-store maintenance through the running backend's `/api/admin/storage`,
14
+ * authenticated with the service key from `.env`.
15
+ *
16
+ * Subcommands:
17
+ * orphans — Stored files no row names; `--delete` deletes them
18
+ * move — Copy a folder of files to another storage source, same keys
19
+ */
20
+ /** Everything the switch below dispatches, for the did-you-mean. */
21
+ var STORAGE_SUBCOMMANDS = ["orphans", "move"];
22
+ /** The flags `rebase storage orphans` takes. */
23
+ var ORPHANS_FLAGS = {
24
+ "--source": String,
25
+ "--older-than": Number,
26
+ "--delete": Boolean,
27
+ "--yes": Boolean,
28
+ "-y": "--yes",
29
+ "--json": Boolean
30
+ };
31
+ /** The flags `rebase storage move` takes. */
32
+ var MOVE_FLAGS = {
33
+ "--from": String,
34
+ "--to": String,
35
+ "--prefix": String,
36
+ "--delete-source": Boolean,
37
+ "--json": Boolean
38
+ };
39
+ /** The project's backend, holding the service key — the `admin` role, every scope. */
40
+ function connect() {
41
+ const projectRoot = requireProjectRoot();
42
+ const env = readEnvFile(projectRoot);
43
+ const baseUrl = resolveBaseUrl(env, projectRoot).replace(/\/+$/, "");
44
+ const serviceKey = env.SERVICE_KEY || env.REBASE_SERVICE_KEY;
45
+ if (!serviceKey) {
46
+ console.error(chalk.red("✗ REBASE_SERVICE_KEY not found in .env — required for storage maintenance."));
47
+ process.exit(1);
48
+ }
49
+ return {
50
+ baseUrl,
51
+ async request(method, path, body) {
52
+ let response;
53
+ try {
54
+ response = await fetch(`${baseUrl}/api/admin/storage${path}`, {
55
+ method,
56
+ headers: {
57
+ "Authorization": `Bearer ${serviceKey}`,
58
+ "Accept": "application/json",
59
+ "User-Agent": cliUserAgent(),
60
+ ...body === void 0 ? {} : { "Content-Type": "application/json" }
61
+ },
62
+ ...body === void 0 ? {} : { body: JSON.stringify(body) }
63
+ });
64
+ } catch (error) {
65
+ throw new RebaseApiError(error instanceof Error ? error.message : String(error), {
66
+ status: 0,
67
+ code: "NETWORK_ERROR"
68
+ });
69
+ }
70
+ const answer = await response.json().catch(() => void 0);
71
+ if (!response.ok) {
72
+ const error = answer && typeof answer === "object" && "error" in answer && answer.error && typeof answer.error === "object" ? answer.error : void 0;
73
+ throw new RebaseApiError(typeof error?.message === "string" ? error.message : `The server answered ${response.status}.`, {
74
+ status: response.status,
75
+ code: typeof error?.code === "string" ? error.code : void 0,
76
+ details: error?.details
77
+ });
78
+ }
79
+ return answer.data;
80
+ }
81
+ };
82
+ }
83
+ function fail(action, error, baseUrl) {
84
+ for (const line of describeFailure(action, error, baseUrl)) console.error(line.startsWith("✗") ? chalk.red(line) : chalk.gray(line));
85
+ if (error instanceof RebaseApiError && error.status === 404 && error.code !== "UNKNOWN_STORAGE_SOURCE") console.error(chalk.gray(" This backend has no storage maintenance routes: they arrived in 0.25, and need storage configured."));
86
+ process.exit(1);
87
+ }
88
+ /** Bytes, as a person reads them. */
89
+ function formatBytes(bytes) {
90
+ if (bytes < 1024) return `${bytes} B`;
91
+ const units = [
92
+ "KB",
93
+ "MB",
94
+ "GB",
95
+ "TB"
96
+ ];
97
+ let value = bytes / 1024;
98
+ let unit = 0;
99
+ while (value >= 1024 && unit < units.length - 1) {
100
+ value /= 1024;
101
+ unit++;
102
+ }
103
+ return `${value.toFixed(value < 10 ? 1 : 0)} ${units[unit]}`;
104
+ }
105
+ /**
106
+ * The lines that describe an orphan scan. Exported so its tests can read them
107
+ * without a server.
108
+ */
109
+ function describeOrphanReport(report, olderThanHours) {
110
+ const lines = [];
111
+ const bytes = report.orphans.reduce((sum, orphan) => sum + (orphan.size ?? 0), 0);
112
+ lines.push(` ${report.objects} file(s) in source "${report.storageId}", ${report.scanned.length} collection(s) read.`);
113
+ if (report.orphans.length === 0) lines.push(` No file older than ${olderThanHours}h is unused.`);
114
+ else {
115
+ lines.push(` ${report.orphans.length} file(s) no row names, ${formatBytes(bytes)}:`);
116
+ for (const orphan of report.orphans) lines.push(` ${orphan.key}${orphan.size !== void 0 ? ` ${formatBytes(orphan.size)}` : ""}${orphan.updated ? ` ${orphan.updated}` : ""}`);
117
+ }
118
+ if (report.recent > 0) lines.push(` ${report.recent} more no row names are newer than ${olderThanHours}h, or of unknown age, and are left out: an upload can be saved before the row that names it.`);
119
+ for (const entry of report.unscanned) lines.push(` ⚠ ${entry.collection} could not be read (${entry.reason}): a file only it names is listed above.`);
120
+ return lines;
121
+ }
122
+ async function confirm(question) {
123
+ const prompt = readline.createInterface({
124
+ input: process.stdin,
125
+ output: process.stdout
126
+ });
127
+ try {
128
+ return /^y(es)?$/i.test((await prompt.question(question)).trim());
129
+ } finally {
130
+ prompt.close();
131
+ }
132
+ }
133
+ async function orphans(rawArgs) {
134
+ const { flags } = parseCommandArgs({
135
+ spec: ORPHANS_FLAGS,
136
+ rawArgs,
137
+ commandWords: 2,
138
+ command: "storage orphans",
139
+ maxPositionals: 0
140
+ });
141
+ const olderThan = flags["--older-than"] ?? 24;
142
+ if (!Number.isFinite(olderThan) || olderThan < 0) throw new UsageError("--older-than takes a number of hours, 0 or more.");
143
+ const query = new URLSearchParams({ olderThanHours: String(olderThan) });
144
+ if (flags["--source"]) query.set("storageId", flags["--source"]);
145
+ const backend = connect();
146
+ let report;
147
+ try {
148
+ report = await backend.request("GET", `/orphans?${query}`);
149
+ } catch (error) {
150
+ fail("Failed to scan storage", error, backend.baseUrl);
151
+ }
152
+ if (!flags["--delete"]) {
153
+ if (flags["--json"]) {
154
+ console.log(JSON.stringify(report, null, 2));
155
+ return;
156
+ }
157
+ console.log("");
158
+ for (const line of describeOrphanReport(report, olderThan)) console.log(line);
159
+ if (report.orphans.length > 0) {
160
+ console.log("");
161
+ console.log(chalk.gray(" Nothing was deleted. Run again with --delete to delete them."));
162
+ }
163
+ console.log("");
164
+ return;
165
+ }
166
+ if (!flags["--json"]) {
167
+ console.log("");
168
+ for (const line of describeOrphanReport(report, olderThan)) console.log(line);
169
+ console.log("");
170
+ }
171
+ if (report.orphans.length === 0) return;
172
+ if (report.unscanned.length > 0) {
173
+ console.error(chalk.red("✗ Not deleting: some collections could not be read, so a file they name would look unused."));
174
+ process.exit(1);
175
+ }
176
+ if (!flags["--yes"]) {
177
+ if (!process.stdin.isTTY) throw new UsageError("--delete deletes files for good; pass --yes to confirm when there is no terminal to ask.");
178
+ if (!await confirm(` Delete these ${report.orphans.length} file(s) for good? [y/N] `)) {
179
+ console.log(chalk.gray(" Nothing was deleted."));
180
+ return;
181
+ }
182
+ }
183
+ let result;
184
+ try {
185
+ result = await backend.request("DELETE", `/orphans?${query}`);
186
+ } catch (error) {
187
+ fail("Failed to delete unused files", error, backend.baseUrl);
188
+ }
189
+ if (flags["--json"]) console.log(JSON.stringify(result, null, 2));
190
+ else {
191
+ console.log(chalk.green(` ✓ Deleted ${result.deleted.length} file(s).`));
192
+ for (const failure of result.failed) console.error(chalk.red(` ✗ ${failure.key}: ${failure.error}`));
193
+ console.log("");
194
+ }
195
+ if (result.failed.length > 0) process.exitCode = 1;
196
+ }
197
+ async function move(rawArgs) {
198
+ const { flags } = parseCommandArgs({
199
+ spec: MOVE_FLAGS,
200
+ rawArgs,
201
+ commandWords: 2,
202
+ command: "storage move",
203
+ maxPositionals: 0
204
+ });
205
+ const from = flags["--from"];
206
+ const to = flags["--to"];
207
+ if (!from && !to) throw new UsageError("Name the sources: --from <source> --to <source>. The default source is \"(default)\", and either may be left out for it.");
208
+ const backend = connect();
209
+ const counts = {
210
+ copied: 0,
211
+ exists: 0,
212
+ failed: 0,
213
+ deleted: 0
214
+ };
215
+ const failures = [];
216
+ let cursor;
217
+ do {
218
+ let page;
219
+ try {
220
+ page = await backend.request("POST", "/move", {
221
+ from,
222
+ to,
223
+ prefix: flags["--prefix"],
224
+ cursor,
225
+ deleteSource: flags["--delete-source"] === true
226
+ });
227
+ } catch (error) {
228
+ fail("Failed to move files", error, backend.baseUrl);
229
+ }
230
+ for (const object of page.moved) {
231
+ counts[object.outcome]++;
232
+ if (object.deleted) counts.deleted++;
233
+ if (object.outcome === "failed") failures.push(object);
234
+ }
235
+ if (!flags["--json"] && process.stdout.isTTY) process.stdout.write(`\r ${counts.copied} copied, ${counts.exists} already there, ${counts.failed} failed…`);
236
+ cursor = page.cursor;
237
+ } while (cursor);
238
+ if (flags["--json"]) console.log(JSON.stringify({
239
+ ...counts,
240
+ failures
241
+ }, null, 2));
242
+ else {
243
+ if (process.stdout.isTTY) process.stdout.write("\n");
244
+ console.log(chalk.green(` ✓ ${counts.copied} copied, ${counts.exists} already there${flags["--delete-source"] ? `, ${counts.deleted} deleted from the source` : ""}.`));
245
+ for (const failure of failures) console.error(chalk.red(` ✗ ${failure.key}: ${failure.error ?? "failed"}`));
246
+ if (counts.copied + counts.exists > 0 && !flags["--delete-source"]) console.log(chalk.gray(" The source keeps its copies. Point the property at the new source, then run again with --delete-source."));
247
+ }
248
+ if (failures.length > 0) process.exitCode = 1;
249
+ }
250
+ async function storageCommand(subcommand, rawArgs) {
251
+ if (!subcommand || subcommand === "--help" || wantsHelp(rawArgs)) {
252
+ printStorageHelp();
253
+ return;
254
+ }
255
+ switch (subcommand) {
256
+ case "orphans":
257
+ await orphans(rawArgs);
258
+ break;
259
+ case "move":
260
+ await move(rawArgs);
261
+ break;
262
+ default: unknownCommand(subcommand, STORAGE_SUBCOMMANDS, "storage");
263
+ }
264
+ }
265
+ function printStorageHelp() {
266
+ console.log(`
267
+ ${chalk.bold("rebase storage")} — Maintain stored files, through the running backend
268
+
269
+ ${chalk.green.bold("Usage")}
270
+ rebase storage ${chalk.blue("<command>")} [options]
271
+
272
+ ${chalk.green.bold("Commands")}
273
+ ${chalk.blue.bold("orphans")} List the stored files no row names
274
+ ${chalk.blue.bold("move")} Copy a folder of files to another storage source, same keys
275
+
276
+ ${chalk.green.bold("orphans Options")}
277
+ ${chalk.blue("--source")} Storage source key ${chalk.gray("(default: the default source)")}
278
+ ${chalk.blue("--older-than")} Hours a file must be old to count ${chalk.gray("(default: 24)")}
279
+ ${chalk.blue("--delete")} Delete them, after asking
280
+ ${chalk.blue("--yes, -y")} Do not ask ${chalk.gray("(required with --delete when there is no terminal)")}
281
+ ${chalk.blue("--json")} Print the report as JSON
282
+
283
+ ${chalk.green.bold("move Options")}
284
+ ${chalk.blue("--from")} Source to copy from ${chalk.gray("(default: the default source)")}
285
+ ${chalk.blue("--to")} Source to copy to ${chalk.gray("(default: the default source)")}
286
+ ${chalk.blue("--prefix")} Folder to copy ${chalk.gray("(default: everything)")}
287
+ ${chalk.blue("--delete-source")} Delete each file from the source once the target holds it
288
+ ${chalk.blue("--json")} Print the counts as JSON
289
+
290
+ ${chalk.green.bold("Notes")}
291
+ A file is unused when no string in any row of any collection names it —
292
+ a key, a URL, a ${chalk.blue("rebase-storage:")} reference in text — whatever property
293
+ or source holds it. Files newer than ${chalk.blue("--older-than")} are left out: the admin
294
+ panel uploads a file before the row that names it is saved. Files your own
295
+ tables name, outside collections, are not seen: check before deleting.
296
+
297
+ Both read ${chalk.blue("REBASE_SERVICE_KEY")} from .env and need the backend running.
298
+ `);
299
+ }
300
+ //#endregion
301
+ export { STORAGE_SUBCOMMANDS, storageCommand };
302
+
303
+ //# sourceMappingURL=storage-DyVq-f_F.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storage-DyVq-f_F.js","names":[],"sources":["../src/commands/storage.ts"],"sourcesContent":["/**\n * CLI command: rebase storage <action>\n *\n * Whole-store maintenance through the running backend's `/api/admin/storage`,\n * authenticated with the service key from `.env`.\n *\n * Subcommands:\n * orphans — Stored files no row names; `--delete` deletes them\n * move — Copy a folder of files to another storage source, same keys\n */\nimport chalk from \"chalk\";\nimport readline from \"node:readline/promises\";\nimport { RebaseApiError } from \"@rebasepro/client\";\nimport { requireProjectRoot, readEnvFile } from \"../utils/project\";\nimport { parseCommandArgs, UsageError, wantsHelp } from \"../utils/args\";\nimport { unknownCommand } from \"../utils/unknown-command\";\nimport { cliUserAgent } from \"../utils/version\";\nimport { describeFailure, resolveBaseUrl } from \"./api-keys\";\n\n/** Everything the switch below dispatches, for the did-you-mean. */\nexport const STORAGE_SUBCOMMANDS = [\"orphans\", \"move\"] as const;\n\n/** The flags `rebase storage orphans` takes. */\nexport const ORPHANS_FLAGS = {\n \"--source\": String,\n \"--older-than\": Number,\n \"--delete\": Boolean,\n \"--yes\": Boolean,\n \"-y\": \"--yes\",\n \"--json\": Boolean\n} as const;\n\n/** The flags `rebase storage move` takes. */\nexport const MOVE_FLAGS = {\n \"--from\": String,\n \"--to\": String,\n \"--prefix\": String,\n \"--delete-source\": Boolean,\n \"--json\": Boolean\n} as const;\n\n/** One stored object, as the backend lists it. */\ninterface StoredObjectEntry {\n key: string;\n size?: number;\n updated?: string;\n}\n\n/** What `GET /admin/storage/orphans` answers. */\nexport interface OrphanReport {\n storageId: string;\n objects: number;\n orphans: StoredObjectEntry[];\n recent: number;\n scanned: string[];\n unscanned: { collection: string; reason: string }[];\n}\n\ninterface MovedObject {\n key: string;\n outcome: \"copied\" | \"exists\" | \"failed\";\n deleted?: boolean;\n error?: string;\n}\n\ninterface Backend {\n baseUrl: string;\n request<T>(method: string, path: string, body?: unknown): Promise<T>;\n}\n\n/** The project's backend, holding the service key — the `admin` role, every scope. */\nfunction connect(): Backend {\n const projectRoot = requireProjectRoot();\n const env = readEnvFile(projectRoot);\n const baseUrl = resolveBaseUrl(env, projectRoot).replace(/\\/+$/, \"\");\n const serviceKey = env.SERVICE_KEY || env.REBASE_SERVICE_KEY;\n if (!serviceKey) {\n console.error(chalk.red(\"✗ REBASE_SERVICE_KEY not found in .env — required for storage maintenance.\"));\n process.exit(1);\n }\n return {\n baseUrl,\n async request<T>(method: string, path: string, body?: unknown): Promise<T> {\n let response: Response;\n try {\n response = await fetch(`${baseUrl}/api/admin/storage${path}`, {\n method,\n headers: {\n \"Authorization\": `Bearer ${serviceKey}`,\n \"Accept\": \"application/json\",\n \"User-Agent\": cliUserAgent(),\n ...(body === undefined ? {} : { \"Content-Type\": \"application/json\" })\n },\n ...(body === undefined ? {} : { body: JSON.stringify(body) })\n });\n } catch (error: unknown) {\n throw new RebaseApiError(error instanceof Error ? error.message : String(error), { status: 0, code: \"NETWORK_ERROR\" });\n }\n const answer: unknown = await response.json().catch(() => undefined);\n if (!response.ok) {\n const error = answer && typeof answer === \"object\" && \"error\" in answer && answer.error && typeof answer.error === \"object\"\n ? answer.error as { message?: unknown; code?: unknown; details?: unknown }\n : undefined;\n throw new RebaseApiError(\n typeof error?.message === \"string\" ? error.message : `The server answered ${response.status}.`,\n { status: response.status, code: typeof error?.code === \"string\" ? error.code : undefined, details: error?.details }\n );\n }\n return (answer as { data: T }).data;\n }\n };\n}\n\nfunction fail(action: string, error: unknown, baseUrl: string): never {\n for (const line of describeFailure(action, error, baseUrl)) {\n console.error(line.startsWith(\"✗\") ? chalk.red(line) : chalk.gray(line));\n }\n if (error instanceof RebaseApiError && error.status === 404 && error.code !== \"UNKNOWN_STORAGE_SOURCE\") {\n console.error(chalk.gray(\" This backend has no storage maintenance routes: they arrived in 0.25, and need storage configured.\"));\n }\n process.exit(1);\n}\n\n/** Bytes, as a person reads them. */\nexport function formatBytes(bytes: number): string {\n if (bytes < 1024) return `${bytes} B`;\n const units = [\"KB\", \"MB\", \"GB\", \"TB\"];\n let value = bytes / 1024;\n let unit = 0;\n while (value >= 1024 && unit < units.length - 1) {\n value /= 1024;\n unit++;\n }\n return `${value.toFixed(value < 10 ? 1 : 0)} ${units[unit]}`;\n}\n\n/**\n * The lines that describe an orphan scan. Exported so its tests can read them\n * without a server.\n */\nexport function describeOrphanReport(report: OrphanReport, olderThanHours: number): string[] {\n const lines: string[] = [];\n const bytes = report.orphans.reduce((sum, orphan) => sum + (orphan.size ?? 0), 0);\n lines.push(` ${report.objects} file(s) in source \"${report.storageId}\", ${report.scanned.length} collection(s) read.`);\n if (report.orphans.length === 0) {\n lines.push(\" No file older than \" + `${olderThanHours}h is unused.`);\n } else {\n lines.push(` ${report.orphans.length} file(s) no row names, ${formatBytes(bytes)}:`);\n for (const orphan of report.orphans) {\n lines.push(` ${orphan.key}${orphan.size !== undefined ? ` ${formatBytes(orphan.size)}` : \"\"}${orphan.updated ? ` ${orphan.updated}` : \"\"}`);\n }\n }\n if (report.recent > 0) {\n lines.push(` ${report.recent} more no row names are newer than ${olderThanHours}h, or of unknown age, and are left out: ` +\n \"an upload can be saved before the row that names it.\");\n }\n for (const entry of report.unscanned) {\n lines.push(` ⚠ ${entry.collection} could not be read (${entry.reason}): a file only it names is listed above.`);\n }\n return lines;\n}\n\nasync function confirm(question: string): Promise<boolean> {\n const prompt = readline.createInterface({ input: process.stdin, output: process.stdout });\n try {\n return /^y(es)?$/i.test((await prompt.question(question)).trim());\n } finally {\n prompt.close();\n }\n}\n\nasync function orphans(rawArgs: string[]): Promise<void> {\n const { flags } = parseCommandArgs({ spec: ORPHANS_FLAGS, rawArgs, commandWords: 2, command: \"storage orphans\", maxPositionals: 0 });\n const olderThan = flags[\"--older-than\"] ?? 24;\n if (!Number.isFinite(olderThan) || olderThan < 0) {\n throw new UsageError(\"--older-than takes a number of hours, 0 or more.\");\n }\n const query = new URLSearchParams({ olderThanHours: String(olderThan) });\n if (flags[\"--source\"]) query.set(\"storageId\", flags[\"--source\"]);\n const backend = connect();\n\n let report: OrphanReport;\n try {\n report = await backend.request<OrphanReport>(\"GET\", `/orphans?${query}`);\n } catch (error: unknown) {\n fail(\"Failed to scan storage\", error, backend.baseUrl);\n }\n\n if (!flags[\"--delete\"]) {\n if (flags[\"--json\"]) {\n console.log(JSON.stringify(report, null, 2));\n return;\n }\n console.log(\"\");\n for (const line of describeOrphanReport(report, olderThan)) console.log(line);\n if (report.orphans.length > 0) {\n console.log(\"\");\n console.log(chalk.gray(\" Nothing was deleted. Run again with --delete to delete them.\"));\n }\n console.log(\"\");\n return;\n }\n\n if (!flags[\"--json\"]) {\n console.log(\"\");\n for (const line of describeOrphanReport(report, olderThan)) console.log(line);\n console.log(\"\");\n }\n if (report.orphans.length === 0) return;\n if (report.unscanned.length > 0) {\n console.error(chalk.red(\"✗ Not deleting: some collections could not be read, so a file they name would look unused.\"));\n process.exit(1);\n }\n if (!flags[\"--yes\"]) {\n if (!process.stdin.isTTY) {\n throw new UsageError(\"--delete deletes files for good; pass --yes to confirm when there is no terminal to ask.\");\n }\n if (!await confirm(` Delete these ${report.orphans.length} file(s) for good? [y/N] `)) {\n console.log(chalk.gray(\" Nothing was deleted.\"));\n return;\n }\n }\n\n let result: { deleted: string[]; failed: { key: string; error: string }[] };\n try {\n // The server finds them again before it deletes: a file a row has\n // named since the list was printed is not deleted.\n result = await backend.request(\"DELETE\", `/orphans?${query}`);\n } catch (error: unknown) {\n fail(\"Failed to delete unused files\", error, backend.baseUrl);\n }\n if (flags[\"--json\"]) {\n console.log(JSON.stringify(result, null, 2));\n } else {\n console.log(chalk.green(` ✓ Deleted ${result.deleted.length} file(s).`));\n for (const failure of result.failed) console.error(chalk.red(` ✗ ${failure.key}: ${failure.error}`));\n console.log(\"\");\n }\n if (result.failed.length > 0) process.exitCode = 1;\n}\n\nasync function move(rawArgs: string[]): Promise<void> {\n const { flags } = parseCommandArgs({ spec: MOVE_FLAGS, rawArgs, commandWords: 2, command: \"storage move\", maxPositionals: 0 });\n const from = flags[\"--from\"];\n const to = flags[\"--to\"];\n if (!from && !to) {\n throw new UsageError(\"Name the sources: --from <source> --to <source>. The default source is \\\"(default)\\\", and either may be left out for it.\");\n }\n const backend = connect();\n const counts = { copied: 0, exists: 0, failed: 0, deleted: 0 };\n const failures: MovedObject[] = [];\n let cursor: string | undefined;\n do {\n let page: { moved: MovedObject[]; cursor?: string };\n try {\n page = await backend.request(\"POST\", \"/move\", {\n from,\n to,\n prefix: flags[\"--prefix\"],\n cursor,\n deleteSource: flags[\"--delete-source\"] === true\n });\n } catch (error: unknown) {\n fail(\"Failed to move files\", error, backend.baseUrl);\n }\n for (const object of page.moved) {\n counts[object.outcome]++;\n if (object.deleted) counts.deleted++;\n if (object.outcome === \"failed\") failures.push(object);\n }\n if (!flags[\"--json\"] && process.stdout.isTTY) {\n process.stdout.write(`\\r ${counts.copied} copied, ${counts.exists} already there, ${counts.failed} failed…`);\n }\n cursor = page.cursor;\n } while (cursor);\n\n if (flags[\"--json\"]) {\n console.log(JSON.stringify({ ...counts, failures }, null, 2));\n } else {\n if (process.stdout.isTTY) process.stdout.write(\"\\n\");\n console.log(chalk.green(` ✓ ${counts.copied} copied, ${counts.exists} already there` +\n `${flags[\"--delete-source\"] ? `, ${counts.deleted} deleted from the source` : \"\"}.`));\n for (const failure of failures) console.error(chalk.red(` ✗ ${failure.key}: ${failure.error ?? \"failed\"}`));\n if (counts.copied + counts.exists > 0 && !flags[\"--delete-source\"]) {\n console.log(chalk.gray(\" The source keeps its copies. Point the property at the new source, then run again with --delete-source.\"));\n }\n }\n if (failures.length > 0) process.exitCode = 1;\n}\n\nexport async function storageCommand(subcommand: string | undefined, rawArgs: string[]): Promise<void> {\n if (!subcommand || subcommand === \"--help\" || wantsHelp(rawArgs)) {\n printStorageHelp();\n return;\n }\n switch (subcommand) {\n case \"orphans\":\n await orphans(rawArgs);\n break;\n case \"move\":\n await move(rawArgs);\n break;\n default:\n unknownCommand(subcommand, STORAGE_SUBCOMMANDS, \"storage\");\n }\n}\n\nfunction printStorageHelp(): void {\n console.log(`\n${chalk.bold(\"rebase storage\")} — Maintain stored files, through the running backend\n\n${chalk.green.bold(\"Usage\")}\n rebase storage ${chalk.blue(\"<command>\")} [options]\n\n${chalk.green.bold(\"Commands\")}\n ${chalk.blue.bold(\"orphans\")} List the stored files no row names\n ${chalk.blue.bold(\"move\")} Copy a folder of files to another storage source, same keys\n\n${chalk.green.bold(\"orphans Options\")}\n ${chalk.blue(\"--source\")} Storage source key ${chalk.gray(\"(default: the default source)\")}\n ${chalk.blue(\"--older-than\")} Hours a file must be old to count ${chalk.gray(\"(default: 24)\")}\n ${chalk.blue(\"--delete\")} Delete them, after asking\n ${chalk.blue(\"--yes, -y\")} Do not ask ${chalk.gray(\"(required with --delete when there is no terminal)\")}\n ${chalk.blue(\"--json\")} Print the report as JSON\n\n${chalk.green.bold(\"move Options\")}\n ${chalk.blue(\"--from\")} Source to copy from ${chalk.gray(\"(default: the default source)\")}\n ${chalk.blue(\"--to\")} Source to copy to ${chalk.gray(\"(default: the default source)\")}\n ${chalk.blue(\"--prefix\")} Folder to copy ${chalk.gray(\"(default: everything)\")}\n ${chalk.blue(\"--delete-source\")} Delete each file from the source once the target holds it\n ${chalk.blue(\"--json\")} Print the counts as JSON\n\n${chalk.green.bold(\"Notes\")}\n A file is unused when no string in any row of any collection names it —\n a key, a URL, a ${chalk.blue(\"rebase-storage:\")} reference in text — whatever property\n or source holds it. Files newer than ${chalk.blue(\"--older-than\")} are left out: the admin\n panel uploads a file before the row that names it is saved. Files your own\n tables name, outside collections, are not seen: check before deleting.\n\n Both read ${chalk.blue(\"REBASE_SERVICE_KEY\")} from .env and need the backend running.\n`);\n}\n"],"mappings":"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"}
@@ -7,7 +7,8 @@
7
7
  * - `machineId` — one developer's machine. Lives beside the cloud credentials
8
8
  * in `~/.rebase/`, so it survives across every project they scaffold.
9
9
  * - `projectId` — one *checkout*. Lives in the project's gitignored
10
- * `.rebase/`, next to `cloud.json` and `state.json`.
10
+ * `.rebase/`, in a file of its own, `telemetry-project.json`, so it lasts
11
+ * as long as the checkout does.
11
12
  *
12
13
  * Without the split, a developer trying five templates in an afternoon reads as
13
14
  * five installations. With it, that is one machine and five projects, which is
@@ -51,6 +52,8 @@ export declare function writeConfig(config: TelemetryConfig): void;
51
52
  * be a record we had no right to create, even unsent.
52
53
  */
53
54
  export declare function ensureMachineId(): string;
55
+ /** Where a checkout keeps its id, under the project's gitignored `.rebase/`. */
56
+ export declare const PROJECT_ID_FILE = "telemetry-project.json";
54
57
  /**
55
58
  * The checkout's id, generating one if this project has none.
56
59
  *
@@ -65,5 +68,11 @@ export declare function ensureMachineId(): string;
65
68
  * The `rebase.json` check is what keeps the fix from being "scatter `.rebase/`
66
69
  * wherever a command happens to run": no manifest, no project, no directory,
67
70
  * and the event simply reports no project.
71
+ *
72
+ * A file of its own, written by nothing else. The id used to be a key in
73
+ * `.rebase/state.json`, which is the dev server's runtime file: rewritten
74
+ * from scratch on every start and deleted on every shutdown. So every
75
+ * restart of `rebase dev` minted a new id, and one checkout read as a new
76
+ * project each time — the error this split exists to prevent, by another way.
68
77
  */
69
78
  export declare function ensureProjectId(projectRoot: string): string | undefined;
@@ -0,0 +1,129 @@
1
+ import { a as setConsent, d as configPath, f as readConfig, r as previewEvent, s as readProjectPolicy, t as endpoint } from "./telemetry-EMFRVRQi.js";
2
+ import { n as parseCommandArgs, r as wantsHelp } from "./args-BnmsEXvu.js";
3
+ import { t as unknownCommand } from "./unknown-command-DNwDcX3w.js";
4
+ import { r as renderPreview, t as describeState } from "./consent-ClRHmcxf.js";
5
+ import chalk from "chalk";
6
+ //#region src/commands/telemetry.ts
7
+ /**
8
+ * `rebase telemetry` — the command that makes the rest of it inspectable.
9
+ *
10
+ * The whole subsystem asks for trust it cannot otherwise earn, and the cheapest
11
+ * way to earn it is to stop describing the payload and print it. `show` runs
12
+ * the same builder the sender uses, so what appears here is what would go, not
13
+ * a documentation comment that quietly fell out of date two releases ago.
14
+ */
15
+ /** Everything the switch below dispatches, for the did-you-mean. */
16
+ var TELEMETRY_SUBCOMMANDS = [
17
+ "status",
18
+ "show",
19
+ "enable",
20
+ "disable"
21
+ ];
22
+ async function telemetryCommand(rawArgs) {
23
+ if (wantsHelp(rawArgs)) {
24
+ printHelp();
25
+ return;
26
+ }
27
+ const { positionals } = parseCommandArgs({
28
+ spec: {},
29
+ rawArgs,
30
+ commandWords: 1,
31
+ command: "telemetry",
32
+ maxPositionals: 1
33
+ });
34
+ const subcommand = positionals[0];
35
+ switch (subcommand) {
36
+ case "status":
37
+ case void 0:
38
+ printStatus();
39
+ return;
40
+ case "show":
41
+ printPayload();
42
+ return;
43
+ case "enable":
44
+ if (!setConsent(true)) {
45
+ console.error(chalk.red(`Could not write ${configPath()} — sharing was not enabled.`));
46
+ process.exitCode = 1;
47
+ return;
48
+ }
49
+ console.log(chalk.green("Anonymous usage sharing enabled."));
50
+ console.log(chalk.gray(`Inspect what gets sent with ${chalk.cyan("rebase telemetry show")}.`));
51
+ return;
52
+ case "disable":
53
+ if (!setConsent(false)) {
54
+ console.error(chalk.red(`Could not write ${configPath()} — sharing is still ON.`));
55
+ console.error(chalk.yellow(`Set ${chalk.cyan("REBASE_TELEMETRY_DISABLED=1")} in your environment instead; it needs no file.`));
56
+ process.exitCode = 1;
57
+ return;
58
+ }
59
+ console.log(chalk.gray("Anonymous usage sharing disabled. Nothing further will be sent."));
60
+ return;
61
+ default: unknownCommand(subcommand, TELEMETRY_SUBCOMMANDS, "telemetry");
62
+ }
63
+ }
64
+ function printStatus() {
65
+ console.log("");
66
+ console.log(` Status: ${describeState()}`);
67
+ if (readProjectPolicy() === "ignored_opt_in") {
68
+ console.log("");
69
+ console.log(chalk.yellow(" Note: this project's rebase.json sets \"telemetry\": true, which is ignored."));
70
+ console.log(chalk.gray(" A committed file cannot consent on behalf of everyone who clones it."));
71
+ console.log(chalk.gray(` Only "telemetry": false is honoured there. Use ${chalk.cyan("rebase telemetry enable")}.`));
72
+ }
73
+ console.log(` Endpoint: ${chalk.gray(endpoint())}`);
74
+ console.log(` Config: ${chalk.gray(configPath())}`);
75
+ console.log("");
76
+ console.log(chalk.gray(` ${chalk.cyan("rebase telemetry show")} prints the exact payload.`));
77
+ console.log("");
78
+ }
79
+ function printPayload() {
80
+ if (readConfig().enabled !== true) {
81
+ console.log("");
82
+ console.log(` ${describeState()}`);
83
+ console.log("");
84
+ console.log(chalk.gray(" Nothing is being sent. This is what WOULD be sent if you said yes:"));
85
+ console.log("");
86
+ console.log(renderPreview("cli.dev", { first_run: false }).split("\n").map((l) => " " + l).join("\n"));
87
+ console.log("");
88
+ console.log(chalk.gray(` Start sharing with ${chalk.cyan("rebase telemetry enable")}.`));
89
+ console.log("");
90
+ return;
91
+ }
92
+ const event = previewEvent("cli.dev", { first_run: false }, process.cwd());
93
+ console.log("");
94
+ console.log(chalk.gray(" Sent to ") + chalk.gray(endpoint()) + chalk.gray(", for example:"));
95
+ console.log("");
96
+ console.log(JSON.stringify(event, null, 2).split("\n").map((l) => " " + l).join("\n"));
97
+ console.log("");
98
+ console.log(chalk.gray(" Both ids are random. Neither is derived from your machine, your"));
99
+ console.log(chalk.gray(" hostname, your project name or anything you have typed."));
100
+ console.log("");
101
+ }
102
+ function printHelp() {
103
+ console.log(`
104
+ ${chalk.bold("rebase telemetry")} — anonymous usage sharing (off until you say yes; ${chalk.cyan("rebase init")} asks)
105
+
106
+ ${chalk.bold("Commands")}
107
+ ${chalk.blue("status")} Whether anything is being shared, and why ${chalk.gray("(default)")}
108
+ ${chalk.blue("show")} Print the exact payload that would be sent
109
+ ${chalk.blue("enable")} Start sharing
110
+ ${chalk.blue("disable")} Stop sharing, permanently
111
+
112
+ ${chalk.bold("Project policy")}
113
+ ${chalk.blue("\"telemetry\": false")} in ${chalk.blue("rebase.json")} disables sharing for everyone who
114
+ clones the repository, overriding each developer's own choice.
115
+ ${chalk.gray("\"telemetry\": true is ignored — a committed file cannot consent for others.")}
116
+
117
+ ${chalk.bold("Environment")}
118
+ ${chalk.blue("DO_NOT_TRACK")} Set to disable, across every tool that honours it
119
+ ${chalk.blue("REBASE_TELEMETRY_DISABLED")} Set to disable Rebase specifically
120
+ ${chalk.blue("REBASE_TELEMETRY_ENDPOINT")} Send elsewhere — your own collector, for instance
121
+
122
+ ${chalk.gray("Never shared: project names, paths, collection or table names, database")}
123
+ ${chalk.gray("URLs, hostnames, error messages, stack traces, or exact record counts.")}
124
+ `);
125
+ }
126
+ //#endregion
127
+ export { TELEMETRY_SUBCOMMANDS, telemetryCommand };
128
+
129
+ //# sourceMappingURL=telemetry-C8Uq8phb.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"telemetry-C8Uq8phb.js","names":[],"sources":["../src/commands/telemetry.ts"],"sourcesContent":["import chalk from \"chalk\";\n\nimport { describeState, renderPreview } from \"../telemetry/consent\";\nimport { configPath, endpoint, previewEvent, readConfig, readProjectPolicy, setConsent } from \"../telemetry\";\nimport { parseCommandArgs, wantsHelp } from \"../utils/args\";\nimport { unknownCommand } from \"../utils/unknown-command\";\n\n/**\n * `rebase telemetry` — the command that makes the rest of it inspectable.\n *\n * The whole subsystem asks for trust it cannot otherwise earn, and the cheapest\n * way to earn it is to stop describing the payload and print it. `show` runs\n * the same builder the sender uses, so what appears here is what would go, not\n * a documentation comment that quietly fell out of date two releases ago.\n */\n/** Everything the switch below dispatches, for the did-you-mean. */\nexport const TELEMETRY_SUBCOMMANDS = [\"status\", \"show\", \"enable\", \"disable\"] as const;\n\nexport async function telemetryCommand(rawArgs: string[]): Promise<void> {\n // Checked before parsing, because `--help` is answered by the dispatcher\n // and never by a handler. It used to be checked after a `filter(a =>\n // !a.startsWith(\"-\"))` that stripped every flag off the line, so `rebase\n // telemetry --help` reached `case undefined` and printed the status page.\n if (wantsHelp(rawArgs)) {\n printHelp();\n return;\n }\n\n // Strictly, like every other family. That `filter` was not \"be lenient\": it\n // deleted every `-`-prefixed token before dispatch, so `rebase telemetry\n // --frobnicate` printed the status page and exited 0, and a mistyped\n // `--disable` looked exactly like asking for the status. `parseCommandArgs`\n // is the CLI's one rule for an undeclared flag, and the comment at the top\n // of `utils/args.ts` is why: guessing that an unknown flag was meant as\n // something else is the unsafe branch.\n const { positionals } = parseCommandArgs({\n spec: {},\n rawArgs,\n commandWords: 1,\n command: \"telemetry\",\n maxPositionals: 1\n });\n const subcommand = positionals[0];\n\n switch (subcommand) {\n case \"status\":\n case undefined:\n printStatus();\n return;\n case \"show\":\n printPayload();\n return;\n case \"enable\":\n if (!setConsent(true)) {\n console.error(chalk.red(`Could not write ${configPath()} — sharing was not enabled.`));\n process.exitCode = 1;\n return;\n }\n console.log(chalk.green(\"Anonymous usage sharing enabled.\"));\n console.log(chalk.gray(`Inspect what gets sent with ${chalk.cyan(\"rebase telemetry show\")}.`));\n return;\n case \"disable\":\n if (!setConsent(false)) {\n // The failure direction that matters. Someone turning sharing\n // off must never be left believing it worked — so this is loud,\n // non-zero, and points at the switch that needs no disk.\n console.error(chalk.red(`Could not write ${configPath()} — sharing is still ON.`));\n console.error(chalk.yellow(`Set ${chalk.cyan(\"REBASE_TELEMETRY_DISABLED=1\")} in your environment instead; it needs no file.`));\n process.exitCode = 1;\n return;\n }\n console.log(chalk.gray(\"Anonymous usage sharing disabled. Nothing further will be sent.\"));\n return;\n default:\n // Printing the help and setting exit 1 said nothing about *why*:\n // the page that came back looked like the page `rebase telemetry\n // --help` prints, and the only difference was an exit code nobody\n // reads at a prompt.\n unknownCommand(subcommand, TELEMETRY_SUBCOMMANDS, \"telemetry\");\n }\n}\n\nfunction printStatus(): void {\n console.log(\"\");\n console.log(` Status: ${describeState()}`);\n if (readProjectPolicy() === \"ignored_opt_in\") {\n // Said out loud rather than silently ignored: a maintainer who wrote\n // `\"telemetry\": true` believes they turned something on, and leaving\n // them to believe it is worse than refusing.\n console.log(\"\");\n console.log(chalk.yellow(\" Note: this project's rebase.json sets \\\"telemetry\\\": true, which is ignored.\"));\n console.log(chalk.gray(\" A committed file cannot consent on behalf of everyone who clones it.\"));\n console.log(chalk.gray(` Only \"telemetry\": false is honoured there. Use ${chalk.cyan(\"rebase telemetry enable\")}.`));\n }\n console.log(` Endpoint: ${chalk.gray(endpoint())}`);\n console.log(` Config: ${chalk.gray(configPath())}`);\n console.log(\"\");\n console.log(chalk.gray(` ${chalk.cyan(\"rebase telemetry show\")} prints the exact payload.`));\n console.log(\"\");\n}\n\nfunction printPayload(): void {\n const config = readConfig();\n if (config.enabled !== true) {\n // Deliberately still prints one. The consent prompt points here so the\n // question can stay short, and someone deciding whether to say yes is\n // exactly the person who most needs to see the payload — telling them\n // to enable sharing first, in order to find out what sharing sends,\n // is the wrong way round.\n console.log(\"\");\n console.log(` ${describeState()}`);\n console.log(\"\");\n console.log(chalk.gray(\" Nothing is being sent. This is what WOULD be sent if you said yes:\"));\n console.log(\"\");\n console.log(renderPreview(\"cli.dev\", { first_run: false }).split(\"\\n\").map((l) => \" \" + l).join(\"\\n\"));\n console.log(\"\");\n console.log(chalk.gray(` Start sharing with ${chalk.cyan(\"rebase telemetry enable\")}.`));\n console.log(\"\");\n return;\n }\n\n // A representative event rather than a synthetic one: `cli.dev` is the\n // event that fires most often, so it is the one worth being sure about.\n const event = previewEvent(\"cli.dev\", { first_run: false }, process.cwd());\n console.log(\"\");\n console.log(chalk.gray(\" Sent to \") + chalk.gray(endpoint()) + chalk.gray(\", for example:\"));\n console.log(\"\");\n console.log(JSON.stringify(event, null, 2).split(\"\\n\").map((l) => \" \" + l).join(\"\\n\"));\n console.log(\"\");\n console.log(chalk.gray(\" Both ids are random. Neither is derived from your machine, your\"));\n console.log(chalk.gray(\" hostname, your project name or anything you have typed.\"));\n console.log(\"\");\n}\n\nfunction printHelp(): void {\n console.log(`\n${chalk.bold(\"rebase telemetry\")} — anonymous usage sharing (off until you say yes; ${chalk.cyan(\"rebase init\")} asks)\n\n${chalk.bold(\"Commands\")}\n ${chalk.blue(\"status\")} Whether anything is being shared, and why ${chalk.gray(\"(default)\")}\n ${chalk.blue(\"show\")} Print the exact payload that would be sent\n ${chalk.blue(\"enable\")} Start sharing\n ${chalk.blue(\"disable\")} Stop sharing, permanently\n\n${chalk.bold(\"Project policy\")}\n ${chalk.blue('\"telemetry\": false')} in ${chalk.blue(\"rebase.json\")} disables sharing for everyone who\n clones the repository, overriding each developer's own choice.\n ${chalk.gray('\"telemetry\": true is ignored — a committed file cannot consent for others.')}\n\n${chalk.bold(\"Environment\")}\n ${chalk.blue(\"DO_NOT_TRACK\")} Set to disable, across every tool that honours it\n ${chalk.blue(\"REBASE_TELEMETRY_DISABLED\")} Set to disable Rebase specifically\n ${chalk.blue(\"REBASE_TELEMETRY_ENDPOINT\")} Send elsewhere — your own collector, for instance\n\n${chalk.gray(\"Never shared: project names, paths, collection or table names, database\")}\n${chalk.gray(\"URLs, hostnames, error messages, stack traces, or exact record counts.\")}\n`);\n}\n"],"mappings":";;;;;;;;;;;;;;;AAgBA,IAAa,wBAAwB;CAAC;CAAU;CAAQ;CAAU;AAAS;AAE3E,eAAsB,iBAAiB,SAAkC;CAKrE,IAAI,UAAU,OAAO,GAAG;EACpB,UAAU;EACV;CACJ;CASA,MAAM,EAAE,gBAAgB,iBAAiB;EACrC,MAAM,CAAC;EACP;EACA,cAAc;EACd,SAAS;EACT,gBAAgB;CACpB,CAAC;CACD,MAAM,aAAa,YAAY;CAE/B,QAAQ,YAAR;EACI,KAAK;EACL,KAAK,KAAA;GACD,YAAY;GACZ;EACJ,KAAK;GACD,aAAa;GACb;EACJ,KAAK;GACD,IAAI,CAAC,WAAW,IAAI,GAAG;IACnB,QAAQ,MAAM,MAAM,IAAI,mBAAmB,WAAW,EAAE,4BAA4B,CAAC;IACrF,QAAQ,WAAW;IACnB;GACJ;GACA,QAAQ,IAAI,MAAM,MAAM,kCAAkC,CAAC;GAC3D,QAAQ,IAAI,MAAM,KAAK,+BAA+B,MAAM,KAAK,uBAAuB,EAAE,EAAE,CAAC;GAC7F;EACJ,KAAK;GACD,IAAI,CAAC,WAAW,KAAK,GAAG;IAIpB,QAAQ,MAAM,MAAM,IAAI,mBAAmB,WAAW,EAAE,wBAAwB,CAAC;IACjF,QAAQ,MAAM,MAAM,OAAO,OAAO,MAAM,KAAK,6BAA6B,EAAE,gDAAgD,CAAC;IAC7H,QAAQ,WAAW;IACnB;GACJ;GACA,QAAQ,IAAI,MAAM,KAAK,iEAAiE,CAAC;GACzF;EACJ,SAKI,eAAe,YAAY,uBAAuB,WAAW;CACrE;AACJ;AAEA,SAAS,cAAoB;CACzB,QAAQ,IAAI,EAAE;CACd,QAAQ,IAAI,eAAe,cAAc,GAAG;CAC5C,IAAI,kBAAkB,MAAM,kBAAkB;EAI1C,QAAQ,IAAI,EAAE;EACd,QAAQ,IAAI,MAAM,OAAO,gFAAgF,CAAC;EAC1G,QAAQ,IAAI,MAAM,KAAK,8EAA8E,CAAC;EACtG,QAAQ,IAAI,MAAM,KAAK,0DAA0D,MAAM,KAAK,yBAAyB,EAAE,EAAE,CAAC;CAC9H;CACA,QAAQ,IAAI,eAAe,MAAM,KAAK,SAAS,CAAC,GAAG;CACnD,QAAQ,IAAI,eAAe,MAAM,KAAK,WAAW,CAAC,GAAG;CACrD,QAAQ,IAAI,EAAE;CACd,QAAQ,IAAI,MAAM,KAAK,KAAK,MAAM,KAAK,uBAAuB,EAAE,2BAA2B,CAAC;CAC5F,QAAQ,IAAI,EAAE;AAClB;AAEA,SAAS,eAAqB;CAE1B,IADe,WACX,CAAA,CAAO,YAAY,MAAM;EAMzB,QAAQ,IAAI,EAAE;EACd,QAAQ,IAAI,KAAK,cAAc,GAAG;EAClC,QAAQ,IAAI,EAAE;EACd,QAAQ,IAAI,MAAM,KAAK,sEAAsE,CAAC;EAC9F,QAAQ,IAAI,EAAE;EACd,QAAQ,IAAI,cAAc,WAAW,EAAE,WAAW,MAAM,CAAC,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,KAAK,MAAM,OAAO,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC;EACtG,QAAQ,IAAI,EAAE;EACd,QAAQ,IAAI,MAAM,KAAK,wBAAwB,MAAM,KAAK,yBAAyB,EAAE,EAAE,CAAC;EACxF,QAAQ,IAAI,EAAE;EACd;CACJ;CAIA,MAAM,QAAQ,aAAa,WAAW,EAAE,WAAW,MAAM,GAAG,QAAQ,IAAI,CAAC;CACzE,QAAQ,IAAI,EAAE;CACd,QAAQ,IAAI,MAAM,KAAK,YAAY,IAAI,MAAM,KAAK,SAAS,CAAC,IAAI,MAAM,KAAK,gBAAgB,CAAC;CAC5F,QAAQ,IAAI,EAAE;CACd,QAAQ,IAAI,KAAK,UAAU,OAAO,MAAM,CAAC,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,KAAK,MAAM,OAAO,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC;CACtF,QAAQ,IAAI,EAAE;CACd,QAAQ,IAAI,MAAM,KAAK,mEAAmE,CAAC;CAC3F,QAAQ,IAAI,MAAM,KAAK,2DAA2D,CAAC;CACnF,QAAQ,IAAI,EAAE;AAClB;AAEA,SAAS,YAAkB;CACvB,QAAQ,IAAI;EACd,MAAM,KAAK,kBAAkB,EAAE,qDAAqD,MAAM,KAAK,aAAa,EAAE;;EAE9G,MAAM,KAAK,UAAU,EAAE;IACrB,MAAM,KAAK,QAAQ,EAAE,iDAAiD,MAAM,KAAK,WAAW,EAAE;IAC9F,MAAM,KAAK,MAAM,EAAE;IACnB,MAAM,KAAK,QAAQ,EAAE;IACrB,MAAM,KAAK,SAAS,EAAE;;EAExB,MAAM,KAAK,gBAAgB,EAAE;IAC3B,MAAM,KAAK,sBAAoB,EAAE,MAAM,MAAM,KAAK,aAAa,EAAE;;IAEjE,MAAM,KAAK,8EAA4E,EAAE;;EAE3F,MAAM,KAAK,aAAa,EAAE;IACxB,MAAM,KAAK,cAAc,EAAE;IAC3B,MAAM,KAAK,2BAA2B,EAAE;IACxC,MAAM,KAAK,2BAA2B,EAAE;;EAE1C,MAAM,KAAK,yEAAyE,EAAE;EACtF,MAAM,KAAK,wEAAwE,EAAE;CACtF;AACD"}