@rebasepro/cli 0.12.1-canary.gdd7172e → 0.12.1-canary.gddbd489
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/cloud/context.d.ts +25 -0
- package/dist/commands/cloud/projects.d.ts +32 -1
- package/dist/index.es.js +214 -41
- package/dist/index.es.js.map +1 -1
- package/dist/telemetry/identity.d.ts +11 -3
- package/dist/telemetry/index.d.ts +19 -5
- package/dist/telemetry/payload.d.ts +0 -8
- package/dist/telemetry/project.d.ts +34 -0
- package/package.json +7 -7
|
@@ -24,7 +24,32 @@ export declare function requireClient(rawArgs: string[]): Promise<{
|
|
|
24
24
|
client: CloudClient;
|
|
25
25
|
url: string;
|
|
26
26
|
}>;
|
|
27
|
+
/** One place a deploy can actually land, as the control plane describes it. */
|
|
28
|
+
export interface DeployTarget {
|
|
29
|
+
clusterId?: string | null;
|
|
30
|
+
provider: string;
|
|
31
|
+
region?: string;
|
|
32
|
+
label?: string;
|
|
33
|
+
baseDomain?: string;
|
|
34
|
+
}
|
|
35
|
+
export interface PlatformConfig {
|
|
36
|
+
tenantBaseDomain?: string;
|
|
37
|
+
deployTargets?: DeployTarget[];
|
|
38
|
+
}
|
|
39
|
+
export declare function fetchPlatformConfig(client: CloudClient, url: string): Promise<PlatformConfig | undefined>;
|
|
40
|
+
/**
|
|
41
|
+
* The base domain tenant projects are served at, derived from the same
|
|
42
|
+
* TENANT_BASE_DOMAIN the ingress and the console read (see
|
|
43
|
+
* saas/backend/src/utils/tenant-domain.ts).
|
|
44
|
+
*/
|
|
27
45
|
export declare function fetchTenantBaseDomain(client: CloudClient, url: string): Promise<string | undefined>;
|
|
46
|
+
/**
|
|
47
|
+
* The infrastructure a deploy for this control plane would ACTUALLY use, in the
|
|
48
|
+
* resolver's own preference order (saas/backend/src/k8s/resolve.ts).
|
|
49
|
+
*
|
|
50
|
+
* @returns the targets, or `undefined` when the control plane cannot say.
|
|
51
|
+
*/
|
|
52
|
+
export declare function fetchDeployTargets(client: CloudClient, url: string): Promise<DeployTarget[] | undefined>;
|
|
28
53
|
/**
|
|
29
54
|
* Public host for a project — `<subdomain>.<base>`, or the bare subdomain when
|
|
30
55
|
* the base domain is unknown.
|
|
@@ -1,5 +1,36 @@
|
|
|
1
|
-
import { type CloudClient } from "./context";
|
|
1
|
+
import { type DeployTarget, type CloudClient } from "./context";
|
|
2
2
|
export declare function listProjects(rawArgs: string[]): Promise<void>;
|
|
3
|
+
/**
|
|
4
|
+
* Where this project says it runs.
|
|
5
|
+
*
|
|
6
|
+
* `provider`/`region` are a *request*: no code downstream reads them to pick a
|
|
7
|
+
* deploy target — that comes from the project's cluster record or the ambient
|
|
8
|
+
* in-cluster context (saas/backend/src/k8s/resolve.ts). So a wrong value here is
|
|
9
|
+
* never contradicted by a failure; it just sits in the record. The CLI used to
|
|
10
|
+
* default to `hetzner`/`nbg1` unconditionally, which is how projects running on
|
|
11
|
+
* our GKE cluster came to describe themselves as Hetzner in the console — and
|
|
12
|
+
* `provider` is half the Stripe compute lookup key (`compute_<provider>_<vmSize>`),
|
|
13
|
+
* so that is a mispricing, not a cosmetic slip.
|
|
14
|
+
*
|
|
15
|
+
* The control plane already publishes the infrastructure that actually exists,
|
|
16
|
+
* and the console's create wizard reads it. Ask the same question here.
|
|
17
|
+
*
|
|
18
|
+
* Exported for tests: the decision is pure, so it can be pinned without a
|
|
19
|
+
* control plane. The fetching and the exit live in `resolveRequestedTarget`.
|
|
20
|
+
*
|
|
21
|
+
* @param requested `--provider`, if the caller named one. An explicit flag wins:
|
|
22
|
+
* it is the caller stating intent, and `deploy` corrects the record anyway.
|
|
23
|
+
* @param targets What the control plane says exists, or `undefined` when it
|
|
24
|
+
* cannot say — an older deployment with no `platform-config`, or a failed
|
|
25
|
+
* request. That is different from an empty list, which is a control plane
|
|
26
|
+
* stating it has no infrastructure at all.
|
|
27
|
+
* @returns the target to record, or `null` when the control plane answered that
|
|
28
|
+
* there is none.
|
|
29
|
+
*/
|
|
30
|
+
export declare function chooseRequestedTarget(requested: string | undefined, targets: DeployTarget[] | undefined): {
|
|
31
|
+
provider: string;
|
|
32
|
+
region?: string;
|
|
33
|
+
} | null;
|
|
3
34
|
export declare function createProject(rawArgs: string[]): Promise<void>;
|
|
4
35
|
export declare function projectInfo(rawArgs: string[], projectRef: string): Promise<void>;
|
|
5
36
|
export declare function deleteProject(rawArgs: string[], projectRef: string): Promise<void>;
|
package/dist/index.es.js
CHANGED
|
@@ -528,33 +528,51 @@ async function requireClient(rawArgs) {
|
|
|
528
528
|
};
|
|
529
529
|
}
|
|
530
530
|
/**
|
|
531
|
-
* The
|
|
532
|
-
* plane (`platform-config`, which derives it from the same TENANT_BASE_DOMAIN
|
|
533
|
-
* the ingress and the console read — see saas/backend/src/utils/tenant-domain.ts).
|
|
531
|
+
* The control plane's public, non-secret self-description (`platform-config`).
|
|
534
532
|
*
|
|
535
|
-
*
|
|
536
|
-
* serves tenants at `rebase.website
|
|
537
|
-
*
|
|
538
|
-
*
|
|
533
|
+
* None of it is knowable from the CLI side: it is per-deployment configuration —
|
|
534
|
+
* production serves tenants at `rebase.website` on GKE, a dev control plane at
|
|
535
|
+
* `localhost` on Docker. Guessing produced two separate lies: a congratulation
|
|
536
|
+
* URL that resolved nowhere near the app, and a `provider` the project does not
|
|
537
|
+
* run on (see `createProject`).
|
|
539
538
|
*
|
|
540
539
|
* Cached per host for the process: it is fixed for a control plane's lifetime,
|
|
541
540
|
* and `projects list` formats one host per row off a single fetch.
|
|
542
541
|
*
|
|
543
|
-
* @returns the
|
|
542
|
+
* @returns the config, or `undefined` if the control plane doesn't serve
|
|
544
543
|
* `platform-config` (an older deployment) or the request failed. A failure is
|
|
545
|
-
* cached too —
|
|
546
|
-
*
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
544
|
+
* cached too — a short-lived CLI should not retry once per row. Note the
|
|
545
|
+
* distinction callers rely on: `undefined` means "this control plane cannot
|
|
546
|
+
* tell us", whereas `deployTargets: []` is a control plane stating that it has
|
|
547
|
+
* no infrastructure configured.
|
|
548
|
+
*/
|
|
549
|
+
var platformConfigCache = /* @__PURE__ */ new Map();
|
|
550
|
+
function fetchPlatformConfig(client, url) {
|
|
551
|
+
let pending = platformConfigCache.get(url);
|
|
551
552
|
if (!pending) {
|
|
552
|
-
pending = client.functions.invoke("platform-config", void 0, { method: "GET" }).then((cfg) => cfg
|
|
553
|
-
|
|
553
|
+
pending = client.functions.invoke("platform-config", void 0, { method: "GET" }).then((cfg) => cfg ?? void 0).catch(() => void 0);
|
|
554
|
+
platformConfigCache.set(url, pending);
|
|
554
555
|
}
|
|
555
556
|
return pending;
|
|
556
557
|
}
|
|
557
558
|
/**
|
|
559
|
+
* The base domain tenant projects are served at, derived from the same
|
|
560
|
+
* TENANT_BASE_DOMAIN the ingress and the console read (see
|
|
561
|
+
* saas/backend/src/utils/tenant-domain.ts).
|
|
562
|
+
*/
|
|
563
|
+
function fetchTenantBaseDomain(client, url) {
|
|
564
|
+
return fetchPlatformConfig(client, url).then((cfg) => cfg?.tenantBaseDomain?.trim() || void 0);
|
|
565
|
+
}
|
|
566
|
+
/**
|
|
567
|
+
* The infrastructure a deploy for this control plane would ACTUALLY use, in the
|
|
568
|
+
* resolver's own preference order (saas/backend/src/k8s/resolve.ts).
|
|
569
|
+
*
|
|
570
|
+
* @returns the targets, or `undefined` when the control plane cannot say.
|
|
571
|
+
*/
|
|
572
|
+
function fetchDeployTargets(client, url) {
|
|
573
|
+
return fetchPlatformConfig(client, url).then((cfg) => Array.isArray(cfg?.deployTargets) ? cfg.deployTargets : void 0);
|
|
574
|
+
}
|
|
575
|
+
/**
|
|
558
576
|
* Public host for a project — `<subdomain>.<base>`, or the bare subdomain when
|
|
559
577
|
* the base domain is unknown.
|
|
560
578
|
*
|
|
@@ -904,10 +922,26 @@ function durationBucket(ms) {
|
|
|
904
922
|
* turning into an incident. Strings are capped at 64 characters because no
|
|
905
923
|
* legitimate enumerated value is longer, and a path or a message always is.
|
|
906
924
|
*/
|
|
925
|
+
/**
|
|
926
|
+
* Key names that shadow an `Object.prototype` member.
|
|
927
|
+
*
|
|
928
|
+
* `__proto__` is already excluded by the pattern (it starts with an
|
|
929
|
+
* underscore), and assigning any of these to an object literal shadows rather
|
|
930
|
+
* than pollutes — so nothing is exploitable here. They are refused because a
|
|
931
|
+
* stored property called `constructor` is a trap for every consumer downstream
|
|
932
|
+
* that reaches for `properties.constructor` and gets a string.
|
|
933
|
+
*/
|
|
934
|
+
var RESERVED_KEYS = /* @__PURE__ */ new Set([
|
|
935
|
+
"constructor",
|
|
936
|
+
"prototype",
|
|
937
|
+
"hasownproperty",
|
|
938
|
+
"tostring",
|
|
939
|
+
"valueof"
|
|
940
|
+
]);
|
|
907
941
|
function sanitize(properties) {
|
|
908
942
|
const out = {};
|
|
909
943
|
for (const [key, value] of Object.entries(properties)) {
|
|
910
|
-
if (!/^[a-z][a-z0-9_]{0,31}$/.test(key)) continue;
|
|
944
|
+
if (!/^[a-z][a-z0-9_]{0,31}$/.test(key) || RESERVED_KEYS.has(key.toLowerCase())) continue;
|
|
911
945
|
if (typeof value === "boolean" || typeof value === "number" && Number.isFinite(value)) out[key] = value;
|
|
912
946
|
else if (typeof value === "string" && value.length > 0 && value.length <= 64) {
|
|
913
947
|
if (!/[/\\@:\s]/.test(value)) out[key] = value;
|
|
@@ -982,16 +1016,29 @@ function ensureMachineId() {
|
|
|
982
1016
|
/**
|
|
983
1017
|
* The checkout's id, generating one if this project has none.
|
|
984
1018
|
*
|
|
985
|
-
*
|
|
986
|
-
*
|
|
987
|
-
*
|
|
1019
|
+
* The directory is created when it is missing, but **only** where a
|
|
1020
|
+
* `rebase.json` proves this really is a project root. An earlier version
|
|
1021
|
+
* required `.rebase/` to already exist, on the assumption that `rebase init`
|
|
1022
|
+
* created it — it does not. It is written only when a scaffold is linked to
|
|
1023
|
+
* Rebase Cloud, so every self-hosted project reported no `projectId` at all,
|
|
1024
|
+
* for ever. That silently broke the funnel this id exists for, on exactly the
|
|
1025
|
+
* population the telemetry is meant to learn about.
|
|
1026
|
+
*
|
|
1027
|
+
* The `rebase.json` check is what keeps the fix from being "scatter `.rebase/`
|
|
1028
|
+
* wherever a command happens to run": no manifest, no project, no directory,
|
|
1029
|
+
* and the event simply reports no project.
|
|
988
1030
|
*/
|
|
989
1031
|
function ensureProjectId(projectRoot) {
|
|
1032
|
+
if (!fs.existsSync(path.join(projectRoot, "rebase.json"))) return void 0;
|
|
990
1033
|
const dir = path.join(projectRoot, ".rebase");
|
|
991
|
-
|
|
1034
|
+
try {
|
|
1035
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
1036
|
+
} catch {
|
|
1037
|
+
return;
|
|
1038
|
+
}
|
|
992
1039
|
const file = path.join(dir, "state.json");
|
|
993
1040
|
let state = {};
|
|
994
|
-
try {
|
|
1041
|
+
if (fs.existsSync(file)) try {
|
|
995
1042
|
const raw = JSON.parse(fs.readFileSync(file, "utf-8"));
|
|
996
1043
|
if (raw && typeof raw === "object") state = raw;
|
|
997
1044
|
} catch {
|
|
@@ -1009,6 +1056,22 @@ function ensureProjectId(projectRoot) {
|
|
|
1009
1056
|
}
|
|
1010
1057
|
return projectId;
|
|
1011
1058
|
}
|
|
1059
|
+
//#endregion
|
|
1060
|
+
//#region src/telemetry/project.ts
|
|
1061
|
+
function readProjectPolicy(startDir = process.cwd()) {
|
|
1062
|
+
try {
|
|
1063
|
+
const root = findProjectRoot(startDir);
|
|
1064
|
+
if (!root) return "unset";
|
|
1065
|
+
const file = path.join(root, MANIFEST_FILENAME);
|
|
1066
|
+
if (!fs.existsSync(file)) return "unset";
|
|
1067
|
+
const raw = fs.readFileSync(file, "utf-8");
|
|
1068
|
+
const match = /"telemetry"\s*:\s*(true|false)/.exec(raw);
|
|
1069
|
+
if (!match) return "unset";
|
|
1070
|
+
return match[1] === "false" ? "opt_out" : "ignored_opt_in";
|
|
1071
|
+
} catch {
|
|
1072
|
+
return "unset";
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1012
1075
|
function endpoint() {
|
|
1013
1076
|
return process.env.REBASE_TELEMETRY_ENDPOINT?.trim() || "https://app.rebase.pro/api/functions/telemetry";
|
|
1014
1077
|
}
|
|
@@ -1026,29 +1089,47 @@ function endpoint() {
|
|
|
1026
1089
|
* `CI` is refused for a different reason: a build runner is not a person, and
|
|
1027
1090
|
* counting one is both useless and misleading. A single pipeline re-running on
|
|
1028
1091
|
* every push would otherwise outweigh every real developer in the data.
|
|
1092
|
+
*
|
|
1093
|
+
* A project's own `"telemetry": false` is checked *before* the machine setting,
|
|
1094
|
+
* because it has to beat an individual opt-in to be worth anything — see
|
|
1095
|
+
* project.ts on why the reverse is refused.
|
|
1029
1096
|
*/
|
|
1030
|
-
function suppressionReason(env = process.env) {
|
|
1097
|
+
function suppressionReason(env = process.env, cwd = process.cwd()) {
|
|
1031
1098
|
if (env.DO_NOT_TRACK && env.DO_NOT_TRACK !== "0") return "do_not_track";
|
|
1032
1099
|
if (env.REBASE_TELEMETRY_DISABLED && env.REBASE_TELEMETRY_DISABLED !== "0") return "rebase_telemetry_disabled";
|
|
1033
1100
|
if (env.CI && env.CI !== "false") return "ci";
|
|
1101
|
+
if (readProjectPolicy(cwd) === "opt_out") return "project_opt_out";
|
|
1034
1102
|
const config = readConfig();
|
|
1035
1103
|
if (config.enabled === void 0) return "not_asked";
|
|
1036
1104
|
if (!config.enabled) return "declined";
|
|
1037
1105
|
return null;
|
|
1038
1106
|
}
|
|
1039
|
-
function isEnabled(env = process.env) {
|
|
1040
|
-
return suppressionReason(env) === null;
|
|
1107
|
+
function isEnabled(env = process.env, cwd = process.cwd()) {
|
|
1108
|
+
return suppressionReason(env, cwd) === null;
|
|
1041
1109
|
}
|
|
1042
|
-
/**
|
|
1110
|
+
/**
|
|
1111
|
+
* Record the user's answer. `false` is final — nothing prompts again.
|
|
1112
|
+
*
|
|
1113
|
+
* Returns whether the choice could actually be persisted. A read-only or full
|
|
1114
|
+
* home directory makes `writeConfig` throw, and the direction of that failure
|
|
1115
|
+
* matters enormously: someone turning sharing **off** who sees a stack trace,
|
|
1116
|
+
* or worse sees nothing, is left sharing. The caller is expected to say so and
|
|
1117
|
+
* point at `REBASE_TELEMETRY_DISABLED`, which needs no disk.
|
|
1118
|
+
*/
|
|
1043
1119
|
function setConsent(enabled) {
|
|
1044
1120
|
const config = readConfig();
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1121
|
+
try {
|
|
1122
|
+
writeConfig({
|
|
1123
|
+
...config,
|
|
1124
|
+
enabled,
|
|
1125
|
+
decidedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1126
|
+
machineId: enabled ? config.machineId ?? void 0 : void 0
|
|
1127
|
+
});
|
|
1128
|
+
if (enabled) ensureMachineId();
|
|
1129
|
+
return true;
|
|
1130
|
+
} catch {
|
|
1131
|
+
return false;
|
|
1132
|
+
}
|
|
1052
1133
|
}
|
|
1053
1134
|
/**
|
|
1054
1135
|
* Build the event that *would* be sent, without sending it.
|
|
@@ -1081,7 +1162,7 @@ function previewEvent(event, properties = {}, projectRoot) {
|
|
|
1081
1162
|
*/
|
|
1082
1163
|
async function recordEvent(event, properties = {}, options = {}) {
|
|
1083
1164
|
try {
|
|
1084
|
-
if (!isEnabled()) return;
|
|
1165
|
+
if (!isEnabled(process.env, options.projectRoot ?? process.cwd())) return;
|
|
1085
1166
|
const payload = buildEvent(event, properties, {
|
|
1086
1167
|
machineId: ensureMachineId(),
|
|
1087
1168
|
projectId: options.projectRoot ? ensureProjectId(options.projectRoot) : void 0
|
|
@@ -1185,6 +1266,7 @@ function describeState(env = process.env) {
|
|
|
1185
1266
|
case "do_not_track": return `${chalk.gray("disabled")} — the ${chalk.cyan("DO_NOT_TRACK")} environment variable is set`;
|
|
1186
1267
|
case "rebase_telemetry_disabled": return `${chalk.gray("disabled")} — the ${chalk.cyan("REBASE_TELEMETRY_DISABLED")} environment variable is set`;
|
|
1187
1268
|
case "ci": return `${chalk.gray("disabled")} — this looks like CI (${chalk.cyan("CI")} is set), which is never counted`;
|
|
1269
|
+
case "project_opt_out": return `${chalk.gray("disabled")} — this project's ${chalk.cyan("rebase.json")} sets ${chalk.cyan("\"telemetry\": false")}`;
|
|
1188
1270
|
}
|
|
1189
1271
|
}
|
|
1190
1272
|
//#endregion
|
|
@@ -1877,7 +1959,11 @@ async function replacePlaceholders(options) {
|
|
|
1877
1959
|
const prereleasePins = [...unreleased].filter(([, version]) => version === "latest" || version.includes("-"));
|
|
1878
1960
|
if (cliIsStable && prereleasePins.length > 0) {
|
|
1879
1961
|
const lines = prereleasePins.map(([name, version]) => ` ${name} → ${version}`).join("\n");
|
|
1880
|
-
throw new Error(`Rebase ${cliVersion} is not fully published to npm.\n\nThese packages have no ${cliVersion} release, so the newest thing on the\nregistry is a prerelease:\n\n${lines}\n\nScaffolding would pin those alongside the ${cliVersion} packages and produce\nan app that cannot install or run. That is a release gap in Rebase itself
|
|
1962
|
+
throw new Error(`Rebase ${cliVersion} is not fully published to npm.\n\nThese packages have no ${cliVersion} release, so the newest thing on the\nregistry is a prerelease:\n\n${lines}\n\nScaffolding would pin those alongside the ${cliVersion} packages and produce\nan app that cannot install or run. That is a release gap in Rebase itself —
|
|
1963
|
+
not a problem with your machine, your network, or your package manager.
|
|
1964
|
+
|
|
1965
|
+
Stopped before writing dependency versions or installing anything. The
|
|
1966
|
+
project directory ${path.basename(options.targetDirectory)}/ was created and is safe to delete.\nPlease report this with the list above.`);
|
|
1881
1967
|
}
|
|
1882
1968
|
for (const [fullPath, originalContent] of fileContents.entries()) {
|
|
1883
1969
|
let content = originalContent.replace(/\{\{PROJECT_NAME\}\}/g, options.projectName);
|
|
@@ -2281,6 +2367,7 @@ async function schemaCommand(subcommand, rawArgs) {
|
|
|
2281
2367
|
return;
|
|
2282
2368
|
}
|
|
2283
2369
|
const projectRoot = requireProjectRoot();
|
|
2370
|
+
recordEvent("cli.schema_generate", { subcommand: subcommand ?? "none" }, { projectRoot });
|
|
2284
2371
|
const backendDir = requireBackendDir(projectRoot);
|
|
2285
2372
|
const activePlugin = getActiveBackendPlugin(backendDir);
|
|
2286
2373
|
if (!activePlugin) {
|
|
@@ -2349,6 +2436,7 @@ async function dbCommand(subcommand, rawArgs) {
|
|
|
2349
2436
|
return;
|
|
2350
2437
|
}
|
|
2351
2438
|
const projectRoot = requireProjectRoot();
|
|
2439
|
+
recordEvent("cli.db_push", { subcommand: subcommand ?? "none" }, { projectRoot });
|
|
2352
2440
|
const backendDir = requireBackendDir(projectRoot);
|
|
2353
2441
|
const activePlugin = getActiveBackendPlugin(backendDir);
|
|
2354
2442
|
if (!activePlugin) {
|
|
@@ -4194,7 +4282,7 @@ async function buildBundle(options) {
|
|
|
4194
4282
|
console.log(chalk.yellow(` ⚠ ${unusedEntry} is not the bundle's entry point — it is not compiled or shipped.`));
|
|
4195
4283
|
console.log(chalk.dim(` The runtime boots the bundle itself and mounts ${compiled}.`));
|
|
4196
4284
|
console.log(chalk.dim(` Routes defined there will not exist once deployed: move them to ${paths.functions}/,`));
|
|
4197
|
-
console.log(chalk.dim(
|
|
4285
|
+
console.log(chalk.dim(" or run `rebase eject` to make this file the entrypoint and own the image."));
|
|
4198
4286
|
}
|
|
4199
4287
|
log(options, chalk.dim(` compiling ${includes.length} source group(s) → ${path.relative(projectRoot, outDir)}/`));
|
|
4200
4288
|
cleanOutDir(projectRoot, outDir);
|
|
@@ -5976,12 +6064,21 @@ async function telemetryCommand(rawArgs) {
|
|
|
5976
6064
|
printPayload();
|
|
5977
6065
|
return;
|
|
5978
6066
|
case "enable":
|
|
5979
|
-
setConsent(true)
|
|
6067
|
+
if (!setConsent(true)) {
|
|
6068
|
+
console.error(chalk.red(`Could not write ${configPath()} — sharing was not enabled.`));
|
|
6069
|
+
process.exitCode = 1;
|
|
6070
|
+
return;
|
|
6071
|
+
}
|
|
5980
6072
|
console.log(chalk.green("Anonymous usage sharing enabled."));
|
|
5981
6073
|
console.log(chalk.gray(`Inspect what gets sent with ${chalk.cyan("rebase telemetry show")}.`));
|
|
5982
6074
|
return;
|
|
5983
6075
|
case "disable":
|
|
5984
|
-
setConsent(false)
|
|
6076
|
+
if (!setConsent(false)) {
|
|
6077
|
+
console.error(chalk.red(`Could not write ${configPath()} — sharing is still ON.`));
|
|
6078
|
+
console.error(chalk.yellow(`Set ${chalk.cyan("REBASE_TELEMETRY_DISABLED=1")} in your environment instead; it needs no file.`));
|
|
6079
|
+
process.exitCode = 1;
|
|
6080
|
+
return;
|
|
6081
|
+
}
|
|
5985
6082
|
console.log(chalk.gray("Anonymous usage sharing disabled. Nothing further will be sent."));
|
|
5986
6083
|
return;
|
|
5987
6084
|
default:
|
|
@@ -5992,6 +6089,12 @@ async function telemetryCommand(rawArgs) {
|
|
|
5992
6089
|
function printStatus() {
|
|
5993
6090
|
console.log("");
|
|
5994
6091
|
console.log(` Status: ${describeState()}`);
|
|
6092
|
+
if (readProjectPolicy() === "ignored_opt_in") {
|
|
6093
|
+
console.log("");
|
|
6094
|
+
console.log(chalk.yellow(" Note: this project's rebase.json sets \"telemetry\": true, which is ignored."));
|
|
6095
|
+
console.log(chalk.gray(" A committed file cannot consent on behalf of everyone who clones it."));
|
|
6096
|
+
console.log(chalk.gray(` Only "telemetry": false is honoured there. Use ${chalk.cyan("rebase telemetry enable")}.`));
|
|
6097
|
+
}
|
|
5995
6098
|
console.log(` Endpoint: ${chalk.gray(endpoint())}`);
|
|
5996
6099
|
console.log(` Config: ${chalk.gray(configPath())}`);
|
|
5997
6100
|
console.log("");
|
|
@@ -6028,6 +6131,11 @@ ${chalk.bold("Commands")}
|
|
|
6028
6131
|
${chalk.blue("enable")} Start sharing
|
|
6029
6132
|
${chalk.blue("disable")} Stop sharing, permanently
|
|
6030
6133
|
|
|
6134
|
+
${chalk.bold("Project policy")}
|
|
6135
|
+
${chalk.blue("\"telemetry\": false")} in ${chalk.blue("rebase.json")} disables sharing for everyone who
|
|
6136
|
+
clones the repository, overriding each developer's own choice.
|
|
6137
|
+
${chalk.gray("\"telemetry\": true is ignored — a committed file cannot consent for others.")}
|
|
6138
|
+
|
|
6031
6139
|
${chalk.bold("Environment")}
|
|
6032
6140
|
${chalk.blue("DO_NOT_TRACK")} Set to disable, across every tool that honours it
|
|
6033
6141
|
${chalk.blue("REBASE_TELEMETRY_DISABLED")} Set to disable Rebase specifically
|
|
@@ -6330,6 +6438,54 @@ function providerDefaults(provider) {
|
|
|
6330
6438
|
};
|
|
6331
6439
|
}
|
|
6332
6440
|
}
|
|
6441
|
+
/**
|
|
6442
|
+
* Where this project says it runs.
|
|
6443
|
+
*
|
|
6444
|
+
* `provider`/`region` are a *request*: no code downstream reads them to pick a
|
|
6445
|
+
* deploy target — that comes from the project's cluster record or the ambient
|
|
6446
|
+
* in-cluster context (saas/backend/src/k8s/resolve.ts). So a wrong value here is
|
|
6447
|
+
* never contradicted by a failure; it just sits in the record. The CLI used to
|
|
6448
|
+
* default to `hetzner`/`nbg1` unconditionally, which is how projects running on
|
|
6449
|
+
* our GKE cluster came to describe themselves as Hetzner in the console — and
|
|
6450
|
+
* `provider` is half the Stripe compute lookup key (`compute_<provider>_<vmSize>`),
|
|
6451
|
+
* so that is a mispricing, not a cosmetic slip.
|
|
6452
|
+
*
|
|
6453
|
+
* The control plane already publishes the infrastructure that actually exists,
|
|
6454
|
+
* and the console's create wizard reads it. Ask the same question here.
|
|
6455
|
+
*
|
|
6456
|
+
* Exported for tests: the decision is pure, so it can be pinned without a
|
|
6457
|
+
* control plane. The fetching and the exit live in `resolveRequestedTarget`.
|
|
6458
|
+
*
|
|
6459
|
+
* @param requested `--provider`, if the caller named one. An explicit flag wins:
|
|
6460
|
+
* it is the caller stating intent, and `deploy` corrects the record anyway.
|
|
6461
|
+
* @param targets What the control plane says exists, or `undefined` when it
|
|
6462
|
+
* cannot say — an older deployment with no `platform-config`, or a failed
|
|
6463
|
+
* request. That is different from an empty list, which is a control plane
|
|
6464
|
+
* stating it has no infrastructure at all.
|
|
6465
|
+
* @returns the target to record, or `null` when the control plane answered that
|
|
6466
|
+
* there is none.
|
|
6467
|
+
*/
|
|
6468
|
+
function chooseRequestedTarget(requested, targets) {
|
|
6469
|
+
if (requested) return {
|
|
6470
|
+
provider: requested,
|
|
6471
|
+
region: void 0
|
|
6472
|
+
};
|
|
6473
|
+
if (!targets) return {
|
|
6474
|
+
provider: "hetzner",
|
|
6475
|
+
region: void 0
|
|
6476
|
+
};
|
|
6477
|
+
if (targets.length === 0) return null;
|
|
6478
|
+
const [target] = targets;
|
|
6479
|
+
return {
|
|
6480
|
+
provider: target.provider,
|
|
6481
|
+
region: target.region?.trim() || void 0
|
|
6482
|
+
};
|
|
6483
|
+
}
|
|
6484
|
+
async function resolveRequestedTarget(client, url, requested) {
|
|
6485
|
+
const chosen = chooseRequestedTarget(requested, await fetchDeployTargets(client, url));
|
|
6486
|
+
if (!chosen) fail("This control plane has no deploy targets configured.", `Register a cluster, or pass ${chalk.bold("--provider")} and ${chalk.bold("--region")} to record one anyway.`);
|
|
6487
|
+
return chosen;
|
|
6488
|
+
}
|
|
6333
6489
|
async function createProject(rawArgs) {
|
|
6334
6490
|
const args = arg({
|
|
6335
6491
|
"--name": String,
|
|
@@ -6365,9 +6521,10 @@ async function createProject(rawArgs) {
|
|
|
6365
6521
|
const subdomain = (args["--subdomain"] || a.subdomain || "").trim().toLowerCase();
|
|
6366
6522
|
const gitRepoUrl = (args["--repo"] || a.repo || "").trim();
|
|
6367
6523
|
const gitBranch = (args["--branch"] || a.branch || "main").trim();
|
|
6368
|
-
const
|
|
6524
|
+
const target = await resolveRequestedTarget(client, url, (args["--provider"] || a.provider)?.trim() || void 0);
|
|
6525
|
+
const provider = target.provider;
|
|
6369
6526
|
const defaults = providerDefaults(provider);
|
|
6370
|
-
const region = (args["--region"] || defaults.region).trim();
|
|
6527
|
+
const region = (args["--region"] || target.region || defaults.region).trim();
|
|
6371
6528
|
const vmSize = (args["--vm-size"] || defaults.vmSize).trim();
|
|
6372
6529
|
if (!name || !subdomain) fail("Name and subdomain are required.");
|
|
6373
6530
|
try {
|
|
@@ -10717,7 +10874,23 @@ ${chalk.green.bold("Options")}
|
|
|
10717
10874
|
${chalk.blue("--help, -h")} Show this help message
|
|
10718
10875
|
|
|
10719
10876
|
${chalk.gray("Documentation: https://rebase.pro/docs")}
|
|
10720
|
-
`);
|
|
10877
|
+
${telemetryNotice()}`);
|
|
10878
|
+
}
|
|
10879
|
+
/**
|
|
10880
|
+
* One line about usage sharing, in the global help.
|
|
10881
|
+
*
|
|
10882
|
+
* Every other tool that collects anything prints a first-run notice. Ours asks
|
|
10883
|
+
* at the end of `rebase init` — but someone who installs the CLI and never runs
|
|
10884
|
+
* `init`, or who joins a project someone else scaffolded, would otherwise never
|
|
10885
|
+
* learn the subsystem exists. This is the cheapest place to close that: the
|
|
10886
|
+
* help is what an unfamiliar user reads first.
|
|
10887
|
+
*
|
|
10888
|
+
* It states the current setting rather than a generic sentence, so it is also
|
|
10889
|
+
* the fastest answer to "is this thing on?".
|
|
10890
|
+
*/
|
|
10891
|
+
function telemetryNotice() {
|
|
10892
|
+
const sharing = isEnabled();
|
|
10893
|
+
return chalk.gray(`Usage sharing: ${sharing ? "on" : "off"} — ${chalk.cyan("rebase telemetry")} to inspect or change\n`);
|
|
10721
10894
|
}
|
|
10722
10895
|
//#endregion
|
|
10723
10896
|
export { CURRENT_RUNTIME_RANGE, DEFAULT_BUNDLE_DIR, DEFAULT_CONFIG_DIR, DEFAULT_CRONS_DIR, DEFAULT_FUNCTIONS_DIR, DEFAULT_SCHEMA_FILE, DEV_PORT_FILENAME, MANIFEST_FILENAME, ManifestError, TEMPLATE_PLACEHOLDER_FILES, appsCommand, assessManagedCompatibility, authCommand, buildBundle, buildCommand, buildInitQuestions, buildStaticBundle, buildableApps, cloudCommand, collectDeclaredDependencies, configureEnvFile, createRebaseApp, dbCommand, detectFrameworkDepDrift, detectNativeDependencies, detectPackageManager, detectStorageAuthorize, devCommand, doctorCommand, ejectCommand, entry, findBackendApp, findBackendDir, findEnvFile, findFrontendDir, findProjectRoot, findUnusedServerEntry, foldStaticIntoBundle, formatCdTarget, generateSdkCommand, getActiveBackendPlugin, getPMCommands, getProjectPort, isIdentifierLike, isPnpmAvailable, loadManifest, manifestExists, manifestPath, normalizeEsmSpecifiers, pnpmAvailabilityFromProbe, positionals, printInitHelp, requireBackendDir, requireProjectRoot, resetPnpmAvailabilityCache, resolveBackendPaths, resolveCliVersion, resolveExampleBaseUrl, resolveLocalBin, resolvePluginCliScript, resolveStartPort, resolveTsx, schemaCommand, startCommand, synthesizeManifest, validateManifest, validateProjectName, validateTsxInstallation, writeManifest };
|