@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
@@ -28,7 +28,28 @@
28
28
  * {@link ANONYMIZE_PATTERNS} says exactly which. It cannot find personal data in
29
29
  * a column called `notes`, and this file says so rather than implying a
30
30
  * guarantee it cannot keep.
31
+ *
32
+ * What it does guarantee is that it never half-happens: {@link anonymizeCopy}
33
+ * runs the pass in one transaction, and when the pass fails it empties the
34
+ * tables it would have rewritten rather than leave their real values behind.
31
35
  */
36
+ import type { Client } from "pg";
37
+ /**
38
+ * How one kind of personal-looking column is overwritten.
39
+ *
40
+ * Two forms, because a constant is the most readable replacement and is wrong
41
+ * for exactly one kind of column: a unique one. Every `token_hash` becoming
42
+ * `'REDACTED'` is what `UNIQUE(token_hash)` refused on the second row of
43
+ * `rebase.refresh_tokens` — so on any project where someone had signed in twice.
44
+ */
45
+ export interface AnonymizeRule {
46
+ pattern: RegExp;
47
+ /** The value for a column no unique index covers, or null to always use the unique form. */
48
+ value: string | null;
49
+ /** The unique form is `prefix` + random hex + `suffix`, cut to fit the column. */
50
+ prefix: string;
51
+ suffix: string;
52
+ }
32
53
  /**
33
54
  * Column-name patterns the anonymizer overwrites.
34
55
  *
@@ -36,19 +57,32 @@
36
57
  * the same things. This is a reasonable-effort measure for making a local copy
37
58
  * less dangerous, and it is not a compliance control.
38
59
  */
39
- export declare const ANONYMIZE_PATTERNS: readonly {
40
- pattern: RegExp;
41
- replacement: string;
42
- }[];
60
+ export declare const ANONYMIZE_PATTERNS: readonly AnonymizeRule[];
43
61
  export declare function shouldAnonymize(columnName: string): boolean;
44
- export declare function replacementFor(columnName: string): string | null;
45
62
  /** A text-ish column the anonymizer can overwrite without a type error. */
46
63
  export interface ColumnRef {
47
64
  schema: string;
48
65
  table: string;
49
66
  column: string;
50
67
  dataType: string;
68
+ /** `character_maximum_length`: null or absent for an unbounded type. */
69
+ maxLength?: number | null;
70
+ /** Covered by a unique index — a primary key, a UNIQUE constraint, or a unique expression index. */
71
+ unique?: boolean;
51
72
  }
73
+ /**
74
+ * The SQL expression that overwrites one column, or null when no rule names it.
75
+ *
76
+ * Unique per row when an index makes the column unique, and always cut to the
77
+ * column's declared length — `zip char(5)` refused `'Redacted'` outright, and a
78
+ * failed statement now empties the tables rather than skip one. To fit a short
79
+ * unique column the prefix goes first, then the suffix, and the random part is
80
+ * kept to at least {@link MIN_UNIQUE_HEX} digits while there is room for them.
81
+ * A NULL stays NULL: an empty optional field is not personal data.
82
+ */
83
+ export declare function replacementFor(column: ColumnRef): string | null;
84
+ /** A double-quoted identifier, with any `"` in it doubled — a table called `order` or `a"b` still works. */
85
+ export declare function quoteIdentifier(name: string): string;
52
86
  /**
53
87
  * Anonymizable columns: name looks personal, and the type can hold the
54
88
  * replacement.
@@ -58,8 +92,55 @@ export interface ColumnRef {
58
92
  * whole pass on a technicality.
59
93
  */
60
94
  export declare function anonymizableColumns(columns: readonly ColumnRef[]): ColumnRef[];
95
+ /** `"schema"."table"` for each table the pass rewrites, sorted. */
96
+ export declare function anonymizedTables(columns: readonly ColumnRef[]): string[];
61
97
  /** `UPDATE` statements for one anonymization pass, in a stable order. */
62
98
  export declare function anonymizeStatements(columns: readonly ColumnRef[]): string[];
99
+ /**
100
+ * Every column the pass could overwrite, with what decides how.
101
+ *
102
+ * Base tables only — a view has an `email` column too, and is not something to
103
+ * UPDATE. Generated columns are skipped for the same reason: `email_domain
104
+ * GENERATED ALWAYS AS (…)` matches by name and refuses any value but DEFAULT,
105
+ * and it follows the column it is generated from anyway. An extension type such
106
+ * as `citext` reports `USER-DEFINED`, so its own name is what is compared.
107
+ *
108
+ * `unique` comes from the indexes, through `pg_depend` as well as `indkey`: a
109
+ * unique index on `lower(email)` names no column in `indkey`, and is exactly
110
+ * what a constant replacement would violate.
111
+ */
112
+ export declare const ANONYMIZE_COLUMNS_SQL = "\nWITH unique_columns AS (\n SELECT DISTINCT i.indrelid AS relid, a.attname AS column_name\n FROM pg_index i\n JOIN pg_attribute a ON a.attrelid = i.indrelid AND a.attnum > 0 AND NOT a.attisdropped\n WHERE i.indisunique\n AND (a.attnum = ANY (i.indkey)\n OR EXISTS (SELECT 1 FROM pg_depend d\n WHERE d.classid = 'pg_class'::regclass AND d.objid = i.indexrelid\n AND d.refclassid = 'pg_class'::regclass AND d.refobjid = i.indrelid\n AND d.refobjsubid = a.attnum))\n)\nSELECT c.table_schema AS \"schema\",\n c.table_name AS \"table\",\n c.column_name AS \"column\",\n CASE WHEN c.data_type = 'USER-DEFINED' THEN c.udt_name ELSE c.data_type END AS \"dataType\",\n c.character_maximum_length AS \"maxLength\",\n (u.column_name IS NOT NULL) AS \"unique\"\n FROM information_schema.columns c\n JOIN information_schema.tables t ON t.table_schema = c.table_schema AND t.table_name = c.table_name\n LEFT JOIN unique_columns u\n ON u.relid = format('%I.%I', c.table_schema, c.table_name)::regclass AND u.column_name = c.column_name\n WHERE t.table_type = 'BASE TABLE'\n AND c.is_generated = 'NEVER'\n AND c.table_schema NOT IN ('pg_catalog', 'information_schema')";
113
+ /** How {@link anonymizeCopy} ended. */
114
+ export type AnonymizeOutcome = {
115
+ ok: true;
116
+ tables: string[];
117
+ } | {
118
+ ok: false;
119
+ /** Why the pass failed. Nothing it wrote was kept. */
120
+ error: string;
121
+ /** The tables emptied instead, so none of their real values are left. */
122
+ emptied: string[];
123
+ /** Why they could not be emptied, when they could not: the copy then holds real data. */
124
+ emptyError: string | null;
125
+ };
126
+ /**
127
+ * Overwrite the personal-looking columns of a restored copy, all or nothing.
128
+ *
129
+ * One transaction, so a failure part-way leaves no table half done — the old
130
+ * pass ran its UPDATEs one by one, so a failure on `rebase.refresh_tokens` left
131
+ * `public.authors` anonymized and `rebase.users` holding every real email.
132
+ *
133
+ * A rollback alone would be worse than that, though: it puts back the real
134
+ * value of every column, in a copy the developer asked to be anonymized. So on
135
+ * failure the tables the pass would have rewritten are emptied (`TRUNCATE …
136
+ * CASCADE`, which takes the rows that reference them too), and the outcome
137
+ * says which — or, if even that failed, says the copy holds real data, so the
138
+ * caller can say it louder.
139
+ *
140
+ * `row_security = off` makes a role that RLS would filter fail the pass instead
141
+ * of rewriting only the rows its policies show it, which looked like success.
142
+ */
143
+ export declare function anonymizeCopy(client: Client): Promise<AnonymizeOutcome>;
63
144
  /**
64
145
  * Schemas the restored copy needs the app role provisioned on.
65
146
  *
@@ -108,6 +189,10 @@ export interface PullPlan {
108
189
  * to continue past errors selectively rather than all-or-nothing.
109
190
  */
110
191
  export declare function dumpArgs(plan: PullPlan): string[];
192
+ /** The environment `pg_dump` runs with: the source's password, if it has one. */
193
+ export declare function dumpEnv(plan: PullPlan): Record<string, string>;
194
+ /** The environment `pg_restore` runs with: the target's password, if it has one. */
195
+ export declare function restoreEnv(plan: PullPlan): Record<string, string>;
111
196
  /**
112
197
  * `pg_restore` arguments for the target.
113
198
  *
@@ -13,7 +13,7 @@
13
13
  * 2. `DATABASE_URL` in the shell environment
14
14
  * 3. the database branch this checkout is switched to
15
15
  * 4. `DATABASE_URL` in the project's `.env`
16
- * 5. `--docker`
16
+ * 5. `--docker`, or the last `rebase dev` having run with it
17
17
  * 6. the managed PGlite database
18
18
  *
19
19
  * An explicit connection string always wins. That is the whole point of the
@@ -22,6 +22,11 @@
22
22
  * with no cleverness in between. The managed database is what fills the vacuum
23
23
  * when nobody has said anything, and it is the only case where the CLI picks.
24
24
  *
25
+ * The second half of (5) is `rebase dev --docker` remembered, so that `rebase
26
+ * db url`, `db push` and the rest reach the database a dev server is on rather
27
+ * than start an empty PGlite beside it (`last-dev-database.ts`). It is exactly
28
+ * as strong as the flag it stands in for, and no stronger.
29
+ *
25
30
  * The branch at (3) is the one addition that is not an explicit connection
26
31
  * string, and it sits where it does deliberately. It has to outrank `.env` or
27
32
  * `rebase db branch switch` would silently do nothing on every project that
@@ -46,6 +51,8 @@ export type DevDatabaseSource =
46
51
  | "branch"
47
52
  /** `--docker`. */
48
53
  | "docker"
54
+ /** `--docker`, on the last `rebase dev` rather than this command line. */
55
+ | "last-dev"
49
56
  /** Nobody said anything, so the managed database fills in. */
50
57
  | "managed";
51
58
  export type DevDatabase = {
@@ -57,7 +64,7 @@ export type DevDatabase = {
57
64
  branch?: string;
58
65
  } | {
59
66
  kind: "docker";
60
- source: "docker";
67
+ source: Extract<DevDatabaseSource, "docker" | "last-dev">;
61
68
  /**
62
69
  * The compose `db` service's connection string, derived by the caller.
63
70
  *
@@ -75,6 +82,12 @@ export interface ResolveDevDatabaseInput {
75
82
  flagUrl?: string | null;
76
83
  /** `--docker`, if given. */
77
84
  flagDocker?: boolean;
85
+ /**
86
+ * Whether the last `rebase dev` ran with `--docker`. Followed only while
87
+ * the compose file still names a database, since there is none to go back
88
+ * to otherwise.
89
+ */
90
+ lastDevDocker?: boolean;
78
91
  /** The shell environment. Only `DATABASE_URL` is read. */
79
92
  env?: Record<string, string | undefined>;
80
93
  /** Parsed `.env` from the project root. Only `DATABASE_URL` is read. */
@@ -99,5 +112,16 @@ export interface ResolveDevDatabaseInput {
99
112
  } | null;
100
113
  }
101
114
  export declare function resolveDevDatabase(input?: ResolveDevDatabaseInput): DevDatabase;
115
+ /**
116
+ * The line to print when `--docker` was typed and a connection string outranked
117
+ * it, or null when it was not typed or was what this command used.
118
+ *
119
+ * The order stays as documented: asking for Docker says how to get a database,
120
+ * and a `DATABASE_URL` that already names one wins. But a flag on this command
121
+ * line that changes nothing without a word reads as a flag that worked. Only the
122
+ * typed flag is reported — the last `rebase dev --docker` standing in for it is
123
+ * a default, and losing to `.env` is what a default does.
124
+ */
125
+ export declare function outrankedDockerFlagNotice(database: DevDatabase, flagDocker: boolean | undefined): string | null;
102
126
  /** One line for the startup banner, naming both the database and why. */
103
127
  export declare function describeDevDatabase(database: DevDatabase): string;
@@ -0,0 +1,2 @@
1
+ import { D as ensureDevDatabase } from "./prepare-CDN6lSpz.js";
2
+ export { ensureDevDatabase };
@@ -0,0 +1,96 @@
1
+ import { c as findProjectRoot } from "./project-Wp2zoyeN.js";
2
+ import fs from "fs";
3
+ import path from "path";
4
+ //#region src/utils/dev-server.ts
5
+ /**
6
+ * Where `rebase dev` serves a project's backend.
7
+ *
8
+ * Not the `PORT` in `.env`: `rebase dev` overrides it with a port derived from
9
+ * the project's path, so two projects on one machine do not collide, and that
10
+ * `PORT` is what `rebase start` listens on. A command that talks to the backend
11
+ * and gets no answer has to be able to say both — its hint named only `.env`'s
12
+ * 3001, for a project whose dev server had been on 3732.
13
+ */
14
+ /**
15
+ * The port `rebase dev` derives from the project root path.
16
+ * Range: 3001–3999 (avoids privileged ports and common services).
17
+ * Two different project directories will almost always get different ports.
18
+ */
19
+ function getProjectPort(projectRoot) {
20
+ let hash = 0;
21
+ for (let i = 0; i < projectRoot.length; i++) hash = (hash << 5) - hash + projectRoot.charCodeAt(i) | 0;
22
+ return 3001 + Math.abs(hash) % 999;
23
+ }
24
+ /** A TCP port out of a string or a number, or null. */
25
+ function portOf(raw) {
26
+ const port = typeof raw === "number" ? raw : typeof raw === "string" ? Number(raw.trim()) : NaN;
27
+ return Number.isInteger(port) && port > 0 && port < 65536 ? port : null;
28
+ }
29
+ function readText(file) {
30
+ try {
31
+ return fs.readFileSync(file, "utf-8").trim();
32
+ } catch {
33
+ return null;
34
+ }
35
+ }
36
+ /**
37
+ * The URL `rebase dev` serves this project on, and how that is known:
38
+ *
39
+ * - `running` — the URL a running dev wrote to `.rebase-dev-url`;
40
+ * - `recorded` — the port the last run left in `.rebase/state.json` or
41
+ * `.rebase-dev-port` (both outlive a dev that did not shut down cleanly);
42
+ * - `derived` — nothing is recorded, so the port it derives for this path.
43
+ *
44
+ * Asked only to describe where dev is; a `PORT` in the shell would move the
45
+ * next run, and is not something a stopped dev left behind.
46
+ */
47
+ function devServerUrl(projectRoot) {
48
+ const running = readText(path.join(projectRoot, ".rebase-dev-url"));
49
+ if (running) return {
50
+ url: running,
51
+ source: "running"
52
+ };
53
+ const state = readText(path.join(projectRoot, ".rebase", "state.json"));
54
+ if (state) try {
55
+ const parsed = JSON.parse(state);
56
+ const port = typeof parsed === "object" && parsed !== null && "port" in parsed ? portOf(parsed.port) : null;
57
+ if (port !== null) return {
58
+ url: `http://localhost:${port}`,
59
+ source: "recorded"
60
+ };
61
+ } catch {}
62
+ const portFile = portOf(readText(path.join(projectRoot, ".rebase-dev-port"))?.split(/\s+/)[0]);
63
+ if (portFile !== null) return {
64
+ url: `http://localhost:${portFile}`,
65
+ source: "recorded"
66
+ };
67
+ return {
68
+ url: `http://localhost:${getProjectPort(projectRoot)}`,
69
+ source: "derived"
70
+ };
71
+ }
72
+ /**
73
+ * What to say when nothing answered at `baseUrl`.
74
+ *
75
+ * The first line is the question it always asked. The second is added when
76
+ * `rebase dev` serves this project somewhere else, because then `baseUrl` is
77
+ * almost always `.env`'s `PORT`, and a reader who just stopped `rebase dev`
78
+ * reads the number as the one it was using.
79
+ */
80
+ function serverUnreachableHint(baseUrl, projectRoot = findProjectRoot()) {
81
+ const lines = [` Is the Rebase server running at ${baseUrl}? Start it with \`rebase dev\`, or set REBASE_BASE_URL in .env.`];
82
+ if (!projectRoot) return lines;
83
+ const dev = devServerUrl(projectRoot);
84
+ let same = false;
85
+ try {
86
+ same = new URL(dev.url).origin === new URL(baseUrl).origin;
87
+ } catch {}
88
+ if (same) return lines;
89
+ const why = dev.source === "derived" ? "the port it picks for this project" : "the port it last used";
90
+ lines.push(` \`rebase dev\` serves this project at ${dev.url} (${why}), and this command uses that address while it runs.`);
91
+ return lines;
92
+ }
93
+ //#endregion
94
+ export { getProjectPort as n, serverUnreachableHint as r, devServerUrl as t };
95
+
96
+ //# sourceMappingURL=dev-server-DG8Tt-uf.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dev-server-DG8Tt-uf.js","names":[],"sources":["../src/utils/dev-server.ts"],"sourcesContent":["/**\n * Where `rebase dev` serves a project's backend.\n *\n * Not the `PORT` in `.env`: `rebase dev` overrides it with a port derived from\n * the project's path, so two projects on one machine do not collide, and that\n * `PORT` is what `rebase start` listens on. A command that talks to the backend\n * and gets no answer has to be able to say both — its hint named only `.env`'s\n * 3001, for a project whose dev server had been on 3732.\n */\nimport fs from \"fs\";\nimport path from \"path\";\n\nimport { findProjectRoot } from \"./project\";\n\n/**\n * The port `rebase dev` derives from the project root path.\n * Range: 3001–3999 (avoids privileged ports and common services).\n * Two different project directories will almost always get different ports.\n */\nexport function getProjectPort(projectRoot: string): number {\n let hash = 0;\n for (let i = 0; i < projectRoot.length; i++) {\n hash = ((hash << 5) - hash + projectRoot.charCodeAt(i)) | 0;\n }\n return 3001 + (Math.abs(hash) % 999);\n}\n\n/** A TCP port out of a string or a number, or null. */\nfunction portOf(raw: unknown): number | null {\n const port = typeof raw === \"number\" ? raw : typeof raw === \"string\" ? Number(raw.trim()) : Number.NaN;\n return Number.isInteger(port) && port > 0 && port < 65536 ? port : null;\n}\n\nfunction readText(file: string): string | null {\n try {\n return fs.readFileSync(file, \"utf-8\").trim();\n } catch {\n return null;\n }\n}\n\n/**\n * The URL `rebase dev` serves this project on, and how that is known:\n *\n * - `running` — the URL a running dev wrote to `.rebase-dev-url`;\n * - `recorded` — the port the last run left in `.rebase/state.json` or\n * `.rebase-dev-port` (both outlive a dev that did not shut down cleanly);\n * - `derived` — nothing is recorded, so the port it derives for this path.\n *\n * Asked only to describe where dev is; a `PORT` in the shell would move the\n * next run, and is not something a stopped dev left behind.\n */\nexport function devServerUrl(projectRoot: string): { url: string; source: \"running\" | \"recorded\" | \"derived\" } {\n const running = readText(path.join(projectRoot, \".rebase-dev-url\"));\n if (running) return { url: running, source: \"running\" };\n\n const state = readText(path.join(projectRoot, \".rebase\", \"state.json\"));\n if (state) {\n try {\n const parsed: unknown = JSON.parse(state);\n const port = typeof parsed === \"object\" && parsed !== null && \"port\" in parsed ? portOf(parsed.port) : null;\n if (port !== null) return { url: `http://localhost:${port}`, source: \"recorded\" };\n } catch { /* fall through to the port file */ }\n }\n\n const portFile = portOf(readText(path.join(projectRoot, \".rebase-dev-port\"))?.split(/\\s+/)[0]);\n if (portFile !== null) return { url: `http://localhost:${portFile}`, source: \"recorded\" };\n\n return { url: `http://localhost:${getProjectPort(projectRoot)}`, source: \"derived\" };\n}\n\n/**\n * What to say when nothing answered at `baseUrl`.\n *\n * The first line is the question it always asked. The second is added when\n * `rebase dev` serves this project somewhere else, because then `baseUrl` is\n * almost always `.env`'s `PORT`, and a reader who just stopped `rebase dev`\n * reads the number as the one it was using.\n */\nexport function serverUnreachableHint(baseUrl: string, projectRoot: string | null = findProjectRoot()): string[] {\n const lines = [` Is the Rebase server running at ${baseUrl}? Start it with \\`rebase dev\\`, or set REBASE_BASE_URL in .env.`];\n if (!projectRoot) return lines;\n\n const dev = devServerUrl(projectRoot);\n let same = false;\n try {\n same = new URL(dev.url).origin === new URL(baseUrl).origin;\n } catch {\n // An unparseable URL on either side is not the same address.\n }\n if (same) return lines;\n\n const why = dev.source === \"derived\" ? \"the port it picks for this project\" : \"the port it last used\";\n lines.push(` \\`rebase dev\\` serves this project at ${dev.url} (${why}), and this command uses that address while it runs.`);\n\n return lines;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAmBA,SAAgB,eAAe,aAA6B;CACxD,IAAI,OAAO;CACX,KAAK,IAAI,IAAI,GAAG,IAAI,YAAY,QAAQ,KACpC,QAAS,QAAQ,KAAK,OAAO,YAAY,WAAW,CAAC,IAAK;CAE9D,OAAO,OAAQ,KAAK,IAAI,IAAI,IAAI;AACpC;;AAGA,SAAS,OAAO,KAA6B;CACzC,MAAM,OAAO,OAAO,QAAQ,WAAW,MAAM,OAAO,QAAQ,WAAW,OAAO,IAAI,KAAK,CAAC,IAAI;CAC5F,OAAO,OAAO,UAAU,IAAI,KAAK,OAAO,KAAK,OAAO,QAAQ,OAAO;AACvE;AAEA,SAAS,SAAS,MAA6B;CAC3C,IAAI;EACA,OAAO,GAAG,aAAa,MAAM,OAAO,CAAC,CAAC,KAAK;CAC/C,QAAQ;EACJ,OAAO;CACX;AACJ;;;;;;;;;;;;AAaA,SAAgB,aAAa,aAAkF;CAC3G,MAAM,UAAU,SAAS,KAAK,KAAK,aAAa,iBAAiB,CAAC;CAClE,IAAI,SAAS,OAAO;EAAE,KAAK;EAAS,QAAQ;CAAU;CAEtD,MAAM,QAAQ,SAAS,KAAK,KAAK,aAAa,WAAW,YAAY,CAAC;CACtE,IAAI,OACA,IAAI;EACA,MAAM,SAAkB,KAAK,MAAM,KAAK;EACxC,MAAM,OAAO,OAAO,WAAW,YAAY,WAAW,QAAQ,UAAU,SAAS,OAAO,OAAO,IAAI,IAAI;EACvG,IAAI,SAAS,MAAM,OAAO;GAAE,KAAK,oBAAoB;GAAQ,QAAQ;EAAW;CACpF,QAAQ,CAAsC;CAGlD,MAAM,WAAW,OAAO,SAAS,KAAK,KAAK,aAAa,kBAAkB,CAAC,CAAC,EAAE,MAAM,KAAK,CAAC,CAAC,EAAE;CAC7F,IAAI,aAAa,MAAM,OAAO;EAAE,KAAK,oBAAoB;EAAY,QAAQ;CAAW;CAExF,OAAO;EAAE,KAAK,oBAAoB,eAAe,WAAW;EAAK,QAAQ;CAAU;AACvF;;;;;;;;;AAUA,SAAgB,sBAAsB,SAAiB,cAA6B,gBAAgB,GAAa;CAC7G,MAAM,QAAQ,CAAC,qCAAqC,QAAQ,gEAAgE;CAC5H,IAAI,CAAC,aAAa,OAAO;CAEzB,MAAM,MAAM,aAAa,WAAW;CACpC,IAAI,OAAO;CACX,IAAI;EACA,OAAO,IAAI,IAAI,IAAI,GAAG,CAAC,CAAC,WAAW,IAAI,IAAI,OAAO,CAAC,CAAC;CACxD,QAAQ,CAER;CACA,IAAI,MAAM,OAAO;CAEjB,MAAM,MAAM,IAAI,WAAW,YAAY,uCAAuC;CAC9E,MAAM,KAAK,2CAA2C,IAAI,IAAI,IAAI,IAAI,qDAAqD;CAE3H,OAAO;AACX"}