@rebasepro/cli 0.12.1-canary.gf5f1d39 → 0.13.0

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.
@@ -0,0 +1,38 @@
1
+ import { TelemetryEventName } from "./payload";
2
+ /**
3
+ * Asking, and what the question looks like.
4
+ *
5
+ * ## Why the prompt comes *after* the work
6
+ *
7
+ * The first `rebase init` on a machine is the event most worth having, and it
8
+ * is the one where no consent exists yet. Asking before scaffolding puts a
9
+ * privacy negotiation in front of someone who has not yet seen the tool do
10
+ * anything — the worst possible moment, and a reliable way to get a reflexive
11
+ * no.
12
+ *
13
+ * So the question is asked once the project exists and the user has seen it
14
+ * work. The event's data is still in memory at that point, so nothing is lost
15
+ * by waiting, and — this is the part that matters — **nothing has been
16
+ * transmitted or written**. Declining leaves no id, no file, no record.
17
+ *
18
+ * ## Why the payload is shown rather than described
19
+ *
20
+ * "Anonymous usage data" is a phrase that has been used to mean almost
21
+ * anything. Printing the exact JSON costs four lines of output and replaces a
22
+ * claim the user has to take on faith with something they can read. It is also
23
+ * the same builder the sender uses, so it cannot drift into a comfortable
24
+ * fiction.
25
+ */
26
+ /** True when we may ask: no decision recorded, and nothing else forbids it. */
27
+ export declare function shouldPrompt(env?: NodeJS.ProcessEnv): boolean;
28
+ export declare function renderPreview(event: TelemetryEventName, properties: Record<string, unknown>): string;
29
+ /**
30
+ * Ask, record the answer, and report it.
31
+ *
32
+ * Never throws and never blocks a non-interactive run: `rebase init --yes` in
33
+ * CI must behave exactly as it does today, which means not asking and not
34
+ * sending.
35
+ */
36
+ export declare function promptForConsent(event: TelemetryEventName, properties: Record<string, unknown>): Promise<boolean>;
37
+ /** Human-readable current state, for `rebase telemetry status`. */
38
+ export declare function describeState(env?: NodeJS.ProcessEnv): string;
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Who is reporting, and where that is remembered.
3
+ *
4
+ * Two identifiers, because they answer two different questions and conflating
5
+ * them would make the answers wrong:
6
+ *
7
+ * - `machineId` — one developer's machine. Lives beside the cloud credentials
8
+ * in `~/.rebase/`, so it survives across every project they scaffold.
9
+ * - `projectId` — one *checkout*. Lives in the project's gitignored
10
+ * `.rebase/`, next to `cloud.json` and `state.json`.
11
+ *
12
+ * Without the split, a developer trying five templates in an afternoon reads as
13
+ * five installations. With it, that is one machine and five projects, which is
14
+ * the shape the funnel actually needs.
15
+ *
16
+ * ## Why `projectId` is not committed
17
+ *
18
+ * `rebase.json` is checked in, and a stable identifier sitting in a public
19
+ * repository is a correlation handle for anyone who finds it — it ties a named
20
+ * organisation to whatever that id did. `.rebase/` is gitignored, so the id
21
+ * stays on the machine that generated it. It also makes the unit honest: what
22
+ * is being counted is a developer working on a project, not the project.
23
+ *
24
+ * ## Why both are random
25
+ *
26
+ * Neither is derived from a hostname, a database name, an email or a machine
27
+ * fingerprint. Hashing any of those only *looks* anonymous — the space of real
28
+ * hostnames is small enough to brute-force a salted hash back to the original
29
+ * in minutes. A random UUID carries no information at all and does the same job.
30
+ */
31
+ /** Bumped when the stored shape changes in a way old CLIs cannot read. */
32
+ export declare const TELEMETRY_CONFIG_VERSION = 1;
33
+ export type TelemetryConfig = {
34
+ version: number;
35
+ /**
36
+ * `undefined` means never asked — which is *not* the same as declining, and
37
+ * is the only state in which anything may prompt.
38
+ */
39
+ enabled?: boolean;
40
+ machineId?: string;
41
+ /** When the choice was made, so a later release can tell stale consent from fresh. */
42
+ decidedAt?: string;
43
+ };
44
+ export declare function configPath(): string;
45
+ export declare function readConfig(): TelemetryConfig;
46
+ export declare function writeConfig(config: TelemetryConfig): void;
47
+ /**
48
+ * The machine's id, generating and persisting one on first use.
49
+ *
50
+ * Only called once consent exists — an id written before the user agreed would
51
+ * be a record we had no right to create, even unsent.
52
+ */
53
+ export declare function ensureMachineId(): string;
54
+ /**
55
+ * The checkout's id, generating one if this project has none.
56
+ *
57
+ * The directory is created when it is missing, but **only** where a
58
+ * `rebase.json` proves this really is a project root. An earlier version
59
+ * required `.rebase/` to already exist, on the assumption that `rebase init`
60
+ * created it — it does not. It is written only when a scaffold is linked to
61
+ * Rebase Cloud, so every self-hosted project reported no `projectId` at all,
62
+ * for ever. That silently broke the funnel this id exists for, on exactly the
63
+ * population the telemetry is meant to learn about.
64
+ *
65
+ * The `rebase.json` check is what keeps the fix from being "scatter `.rebase/`
66
+ * wherever a command happens to run": no manifest, no project, no directory,
67
+ * and the event simply reports no project.
68
+ */
69
+ export declare function ensureProjectId(projectRoot: string): string | undefined;
@@ -0,0 +1,72 @@
1
+ import { TelemetryEvent, TelemetryEventName } from "./payload";
2
+ export { TELEMETRY_SCHEMA_VERSION, bucket, durationBucket, errorClass, buildEvent, sanitize } from "./payload";
3
+ export type { TelemetryEvent, TelemetryEventName, TelemetryValue } from "./payload";
4
+ export { configPath, readConfig, writeConfig } from "./identity";
5
+ export type { TelemetryConfig } from "./identity";
6
+ export { readProjectPolicy } from "./project";
7
+ export type { ProjectTelemetryPolicy } from "./project";
8
+ /**
9
+ * Where events go. Overridable so a fork can point at its own collector, and so
10
+ * the tests never touch the network.
11
+ */
12
+ export declare const DEFAULT_TELEMETRY_ENDPOINT = "https://app.rebase.pro/api/functions/telemetry";
13
+ export declare function endpoint(): string;
14
+ /** Why nothing would be sent right now, or `null` when it would. */
15
+ export type SuppressionReason = "not_asked" | "declined" | "do_not_track" | "rebase_telemetry_disabled" | "ci" | "project_opt_out";
16
+ /**
17
+ * The one function that decides whether anything leaves the machine.
18
+ *
19
+ * Every path is a refusal except the last, which is the point: consent is
20
+ * opt-in, so the default answer at every branch — never asked, unreadable
21
+ * config, a set env var, a CI runner — is no.
22
+ *
23
+ * `DO_NOT_TRACK` is honoured because it is the cross-tool convention
24
+ * (consoledonottrack.com); a user who has set it globally has already answered
25
+ * this question and should not be asked again by us.
26
+ *
27
+ * `CI` is refused for a different reason: a build runner is not a person, and
28
+ * counting one is both useless and misleading. A single pipeline re-running on
29
+ * every push would otherwise outweigh every real developer in the data.
30
+ *
31
+ * A project's own `"telemetry": false` is checked *before* the machine setting,
32
+ * because it has to beat an individual opt-in to be worth anything — see
33
+ * project.ts on why the reverse is refused.
34
+ */
35
+ export declare function suppressionReason(env?: NodeJS.ProcessEnv, cwd?: string): SuppressionReason | null;
36
+ export declare function isEnabled(env?: NodeJS.ProcessEnv, cwd?: string): boolean;
37
+ /**
38
+ * Record the user's answer. `false` is final — nothing prompts again.
39
+ *
40
+ * Returns whether the choice could actually be persisted. A read-only or full
41
+ * home directory makes `writeConfig` throw, and the direction of that failure
42
+ * matters enormously: someone turning sharing **off** who sees a stack trace,
43
+ * or worse sees nothing, is left sharing. The caller is expected to say so and
44
+ * point at `REBASE_TELEMETRY_DISABLED`, which needs no disk.
45
+ */
46
+ export declare function setConsent(enabled: boolean): boolean;
47
+ /**
48
+ * Build the event that *would* be sent, without sending it.
49
+ *
50
+ * This is what `rebase telemetry show` prints, and it is deliberately the same
51
+ * function the sender uses — a preview assembled by separate code is a promise
52
+ * that drifts. Returns `null` when nothing would be sent, so the command can
53
+ * say why instead of showing a payload that is never going anywhere.
54
+ */
55
+ export declare function previewEvent(event: TelemetryEventName, properties?: Record<string, unknown>, projectRoot?: string): TelemetryEvent | null;
56
+ /**
57
+ * Send one event, or quietly do nothing.
58
+ *
59
+ * Three properties this must hold, in order of importance:
60
+ *
61
+ * 1. **It never throws.** Telemetry failing is not a reason for `rebase dev`
62
+ * to fail. Every error is swallowed.
63
+ * 2. **It never blocks meaningfully.** A two-second ceiling, after which the
64
+ * command carries on regardless — a collector having a bad day must not
65
+ * become the CLI hanging.
66
+ * 3. **It never sends without consent.** Enforced here rather than at the call
67
+ * sites, so a new call site cannot get it wrong.
68
+ */
69
+ export declare function recordEvent(event: TelemetryEventName, properties?: Record<string, unknown>, options?: {
70
+ projectRoot?: string;
71
+ timeoutMs?: number;
72
+ }): Promise<void>;
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Every field that may ever leave the machine, and nothing else.
3
+ *
4
+ * ## The rule this file exists to enforce
5
+ *
6
+ * **No value here may be free text.** Every property is either a number, a
7
+ * boolean, or a string drawn from a set enumerated in this file. That is not
8
+ * fussiness — free text is the mechanism by which telemetry leaks. An error
9
+ * message carries `/Users/francesco/rebase/...`, which is a real person's name.
10
+ * A collection name carries a customer's product vocabulary. A database URL
11
+ * carries a password. None of those are things anyone intends to send; they
12
+ * arrive by being interpolated into a string that a well-meaning field was
13
+ * happy to accept.
14
+ *
15
+ * So the payload is closed by construction. Adding a field means adding it
16
+ * here, where the review question — "can this ever contain something the user
17
+ * typed?" — is unavoidable.
18
+ *
19
+ * ## What is deliberately absent
20
+ *
21
+ * Project names, directory paths, collection or table names, hostnames,
22
+ * database URLs, error messages, stack traces, email addresses, exact row or
23
+ * user counts. Anything that would let a payload be traced to an organisation
24
+ * rather than to an anonymous id.
25
+ */
26
+ /**
27
+ * Bumped whenever a field is added, removed or changes meaning.
28
+ *
29
+ * Sent with every event so the receiving end can reject or migrate old shapes,
30
+ * and so `rebase telemetry show` can state which contract the user agreed to.
31
+ */
32
+ export declare const TELEMETRY_SCHEMA_VERSION = 1;
33
+ /** The events the CLI reports. Closed set — a name not listed cannot be sent. */
34
+ export type TelemetryEventName = "cli.init" | "cli.dev" | "cli.deploy" | "cli.schema_generate" | "cli.db_push" | "cli.error";
35
+ /** A single non-free-text value. */
36
+ export type TelemetryValue = string | number | boolean;
37
+ export type TelemetryEvent = {
38
+ schema: number;
39
+ event: TelemetryEventName;
40
+ /** Random per machine — see identity.ts on why it is not derived. */
41
+ machineId: string;
42
+ /** Random per checkout. Absent when the command ran outside a project. */
43
+ projectId?: string;
44
+ /** The CLI's own version, so old releases can be told apart. */
45
+ cliVersion: string;
46
+ /** Coarse platform facts, for the support matrix. */
47
+ nodeMajor: number;
48
+ platform: NodeJS.Platform;
49
+ arch: string;
50
+ /** UTC, second precision. Not a device clock reading anyone can fingerprint. */
51
+ at: string;
52
+ properties: Record<string, TelemetryValue>;
53
+ };
54
+ /**
55
+ * Buckets rather than exact counts.
56
+ *
57
+ * An exact figure is a surprisingly good fingerprint — "this install has 37
58
+ * collections" narrows the field a great deal when combined with a version and
59
+ * a driver. A bucket answers "roughly how big" without doing that.
60
+ */
61
+ export declare function bucket(value: number): string;
62
+ /** Duration in coarse bands — enough to see "slow", not enough to fingerprint. */
63
+ export declare function durationBucket(ms: number): string;
64
+ /**
65
+ * An error reduced to something safe to transmit.
66
+ *
67
+ * The message and the stack are discarded, always. What survives is the
68
+ * constructor name and, for the errors that carry one, a `code` — both of which
69
+ * come from the program rather than from anything the user typed or named.
70
+ * `EACCES` is useful and safe; "cannot write /Users/francesco/clients/acme" is
71
+ * neither.
72
+ */
73
+ export declare function errorClass(error: unknown): string;
74
+ export declare function sanitize(properties: Record<string, unknown>): Record<string, TelemetryValue>;
75
+ export declare function buildEvent(event: TelemetryEventName, properties: Record<string, unknown>, identity: {
76
+ machineId: string;
77
+ projectId?: string;
78
+ }): TelemetryEvent;
@@ -0,0 +1,34 @@
1
+ /**
2
+ * A repository's own policy on usage sharing.
3
+ *
4
+ * ## Why this is deliberately one-directional
5
+ *
6
+ * `rebase.json` is committed, so whatever it says applies to everyone who
7
+ * clones the repository — including people who have never seen our prompt. That
8
+ * makes it the right place for exactly one of the two answers:
9
+ *
10
+ * - **`false` is honoured**, and beats an individual's opt-in. An organisation
11
+ * restricting its own repository is setting policy for work done on its
12
+ * behalf, which is theirs to set. It is the same shape as a committed
13
+ * `.npmrc`.
14
+ * - **`true` is ignored**, and says so. It would be one developer answering a
15
+ * privacy question for every colleague who later clones the repo — consent
16
+ * by proxy, which is the precise thing opt-in exists to prevent.
17
+ *
18
+ * The asymmetry is the whole feature. A symmetric flag would be a worse version
19
+ * of the machine-level setting, and a quietly-obeyed `true` would be a way to
20
+ * enrol people without asking them.
21
+ *
22
+ * Read straight from the file rather than through `parseManifest`, for two
23
+ * reasons: the parser builds a typed object and drops keys it does not model,
24
+ * and a manifest too malformed to parse must still be able to switch telemetry
25
+ * off. A repository that says no should be obeyed even when it is broken.
26
+ */
27
+ export type ProjectTelemetryPolicy =
28
+ /** `"telemetry": false` — suppress, regardless of the machine setting. */
29
+ "opt_out"
30
+ /** `"telemetry": true` — refused; the machine setting still decides. */
31
+ | "ignored_opt_in"
32
+ /** No manifest, no key, or an unreadable one. */
33
+ | "unset";
34
+ export declare function readProjectPolicy(startDir?: string): ProjectTelemetryPolicy;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rebasepro/cli",
3
- "version": "0.12.1-canary.gf5f1d39",
3
+ "version": "0.13.0",
4
4
  "description": "Developer tools for Rebase projects",
5
5
  "main": "./dist/index.es.js",
6
6
  "module": "./dist/index.es.js",
@@ -31,20 +31,20 @@
31
31
  "execa": "^9.6.1",
32
32
  "inquirer": "14.0.2",
33
33
  "jiti": "^2.7.0",
34
- "@rebasepro/agent-skills": "0.12.1-canary.gf5f1d39",
35
- "@rebasepro/client": "0.12.1-canary.gf5f1d39",
36
- "@rebasepro/server": "0.12.1-canary.gf5f1d39",
37
- "@rebasepro/codegen": "0.12.1-canary.gf5f1d39",
38
- "@rebasepro/types": "0.12.1-canary.gf5f1d39",
39
- "@rebasepro/server-postgres": "0.12.1-canary.gf5f1d39"
34
+ "@rebasepro/agent-skills": "0.13.0",
35
+ "@rebasepro/client": "0.13.0",
36
+ "@rebasepro/codegen": "0.13.0",
37
+ "@rebasepro/types": "0.13.0",
38
+ "@rebasepro/server": "0.13.0",
39
+ "@rebasepro/server-postgres": "0.13.0"
40
40
  },
41
41
  "devDependencies": {
42
- "@types/node": "^25.9.3",
42
+ "@types/node": "^26.1.2",
43
43
  "@types/pg": "^8.20.0",
44
- "pg": "^8.21.0",
44
+ "pg": "^8.22.0",
45
45
  "typescript": "^6.0.3",
46
- "vite": "^8.0.16",
47
- "vitest": "4.1.8"
46
+ "vite": "^8.1.5",
47
+ "vitest": "4.1.10"
48
48
  },
49
49
  "files": [
50
50
  "bin/",
@@ -11,8 +11,8 @@
11
11
  "@rebasepro/server-postgres": "workspace:*",
12
12
  "@rebasepro/types": "workspace:*",
13
13
  "drizzle-orm": "^0.45.2",
14
- "hono": "^4.12.10",
15
- "@hono/node-server": "^1.19.12",
14
+ "hono": "^4.12.27",
15
+ "@hono/node-server": "^2.0.12",
16
16
  "pg": "^8.11.3",
17
17
  "ws": "^8.16.0",
18
18
  "dotenv": "^16.0.0",
@@ -17,12 +17,11 @@
17
17
  "generate:sdk": "rebase generate-sdk",
18
18
  "skills:install": "rebase skills install",
19
19
  "example": "tsx scripts/example.ts",
20
- "deploy": "rebase build && rebase start"
20
+ "deploy": "rebase cloud deploy"
21
21
  },
22
22
  "devDependencies": {
23
23
  "@rebasepro/cli": "workspace:*",
24
24
  "@rebasepro/types": "workspace:*",
25
- "concurrently": "^8.2.2",
26
25
  "tsx": "^4.20.6",
27
26
  "typescript": "^5.9.2"
28
27
  },
@@ -13,8 +13,8 @@
13
13
  "@rebasepro/server-postgres": "workspace:*",
14
14
  "@rebasepro/types": "workspace:*",
15
15
  "drizzle-orm": "^0.45.2",
16
- "hono": "^4.12.25",
17
- "@hono/node-server": "^1.19.12",
16
+ "hono": "^4.12.27",
17
+ "@hono/node-server": "^2.0.12",
18
18
  "pg": "^8.11.3",
19
19
  "ws": "^8.16.0",
20
20
  "dotenv": "^16.0.0",
@@ -8,7 +8,15 @@ export const collections = [postsCollection, authorsCollection, tagsCollection,
8
8
 
9
9
  /**
10
10
  * Applied to any collection in this directory that declares no
11
- * `securityRules` of its own: anyone can read, only admins can write.
11
+ * `securityRules` of its own: every signed-in user reads every row, only
12
+ * admins write.
13
+ *
14
+ * `access: "public"` is about ROWS, not about who may call the API — it means
15
+ * "no row filter", not "no login". A request with no token is still answered
16
+ * 401 by the API before RLS is ever consulted. To serve readers who are not
17
+ * signed in (a public website reading this backend), set `AUTH_REQUIRE=false`
18
+ * in the backend's environment; access then rests entirely on these rules,
19
+ * which is what they are for.
12
20
  *
13
21
  * These live here, next to the collections, because `rebase db push`
14
22
  * generates the Postgres policies from these files — that is what actually
@@ -15,10 +15,10 @@
15
15
  "{{PROJECT_NAME}}-config": "*",
16
16
  "react": "^19.0.0",
17
17
  "react-dom": "^19.0.0",
18
- "react-router": "^7.0.0",
19
- "react-router-dom": "^7.0.0",
18
+ "react-router": "^8.3.0",
20
19
  "react-compiler-runtime": "^1.0.0",
21
- "@fontsource/rubik": "^5.2.5"
20
+ "@fontsource-variable/inter": "^5.3.0",
21
+ "@fontsource-variable/instrument-sans": "^5.3.0"
22
22
  },
23
23
  "scripts": {
24
24
  "dev": "vite --mode development",
@@ -1,7 +1,8 @@
1
1
  import React from "react";
2
2
 
3
3
  import "@fontsource/jetbrains-mono";
4
- import "@fontsource/rubik";
4
+ import "@fontsource-variable/inter";
5
+ import "@fontsource-variable/instrument-sans";
5
6
 
6
7
  import { Rebase, RebaseAuth, useRebaseAuthController } from "@rebasepro/app";
7
8
  import { RebaseAdmin, RebaseShell } from "@rebasepro/admin";
@@ -1,6 +1,7 @@
1
1
  import React from "react";
2
2
  import ReactDOM from "react-dom/client";
3
- import { createBrowserRouter, RouterProvider } from "react-router-dom";
3
+ import { createBrowserRouter } from "react-router";
4
+ import { RouterProvider } from "react-router/dom";
4
5
  import { App } from "./App";
5
6
  import "./index.css";
6
7
 
@@ -22,7 +22,6 @@ export default defineConfig({
22
22
  "react",
23
23
  "react-dom",
24
24
  "react-router",
25
- "react-router-dom",
26
25
  "@remix-run/router"
27
26
  ]
28
27
  },
@@ -20,12 +20,11 @@
20
20
  "schema:generate": "rebase schema generate --collections ../config/collections",
21
21
  "generate:sdk": "rebase generate-sdk",
22
22
  "skills:install": "rebase skills install",
23
- "deploy": "rebase build && rebase start"
23
+ "deploy": "rebase cloud deploy"
24
24
  },
25
25
  "devDependencies": {
26
26
  "@rebasepro/cli": "workspace:*",
27
27
  "@rebasepro/types": "workspace:*",
28
- "concurrently": "^8.2.2",
29
28
  "typescript": "^5.9.2"
30
29
  },
31
30
  "engines": {