@metabase/rde 0.1.12 → 0.1.13
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/README.md +15 -10
- package/dist/cli.mjs +416 -178
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -35,19 +35,22 @@ The wizard shows its checklist first (`✓` done, `○` to do, `–` not applica
|
|
|
35
35
|
○ Start Metabase
|
|
36
36
|
○ Create the admin user
|
|
37
37
|
○ Connect the Metabase CLI
|
|
38
|
+
○ Enable transforms
|
|
38
39
|
○ Connect a sample database
|
|
39
40
|
○ Activate your license
|
|
40
|
-
○ Connect your databases
|
|
41
41
|
– Connect a git repository
|
|
42
|
+
○ Connect your databases
|
|
42
43
|
○ Summary
|
|
43
44
|
│
|
|
44
45
|
● Running npm install -g @metabase/cli@alpha-release-65-integration-branch
|
|
45
46
|
```
|
|
46
47
|
|
|
47
|
-
The steps up to the license run without a question, save one: `mb` is installed with the package manager that installed `rde`, the skill goes to the coding agents you pick from the ones found on the machine (at a terminal; without one, to every agent found), Metabase runs in Docker when Docker is there (the jar with your Java otherwise), on the first free port from 23000 up that no other container or instance claims, with an admin user whose password is generated and kept in `~/.rde`, `mb` is logged in under its `default` profile (the one question here comes when `default` already points at another Metabase), and a Postgres sample database comes up in a second container, connected to Metabase as a place transforms can write. Each of those choices is printed as it is made and can be overridden with a flag. The prompts that remain are the license token (Enter skips it),
|
|
48
|
+
The steps up to the license run without a question, save one: `mb` is installed with the package manager that installed `rde`, the skill goes to the coding agents you pick from the ones found on the machine (at a terminal; without one, to every agent found), Metabase runs in Docker when Docker is there (the jar with your Java otherwise), on the first free port from 23000 up that no other container or instance claims, with an admin user whose password is generated and kept in `~/.rde`, `mb` is logged in under its `default` profile (the one question here comes when `default` already points at another Metabase), and a Postgres sample database comes up in a second container, connected to Metabase as a place transforms can write. Each of those choices is printed as it is made and can be overridden with a flag. The prompts that remain are the license token (Enter skips it), with a license that includes remote sync where the git repository lives and which remote it uses, and your own databases (Enter skips them).
|
|
48
49
|
|
|
49
50
|
The databases step asks once. At a terminal, without `--yes` or `--skip-databases`, and while none of your own databases is connected, it lists what is connected (the sample database) and asks for a connection string: paste one and `rde` reads the engine from it (or asks which, when the string fits several, or shows the engine list when it fits none), then shows the connection for review, tests it, adds it, and offers another. Enter skips it, and the skip is remembered, so the next `rde init` does not ask again; `rde init --only databases` and `rde db add` still do. With `--yes` or without a terminal it asks nothing and the sample database stands in; `--database` connects yours ahead of time.
|
|
50
51
|
|
|
52
|
+
It runs after the git step because content imported from a repository names its databases, and Metabase only finds them by name. Every database the content names that this instance lacks becomes a stub: a placeholder with that name and no connection, which keeps the content importable and which Metabase leaves out of its own database lists. When there are stubs, the step asks for them instead of the connection string: a checklist of their names, none picked, where you pick the few you work on (a repository can name dozens, most of them someone else's or out of reach from your machine). Each one picked asks for its connection string (Enter goes to the engine list, whose last choice leaves that one unconnected), then review, test, and connect: the stub becomes the real database under the same name and id, so the content on it works and its sync starts. Picking none skips the step like Enter does. The ones left over are named at the end of the step and in the summary, and `rde db add` and `rde db list` show them until they are connected. A database named ahead of time (`--database`, an answers file, `rde db add --url --name`) whose name is a stub's connects that stub too, never a second database of the same name.
|
|
53
|
+
|
|
51
54
|
The git step reads `–` until a license with remote sync is active; it is checked again when its turn comes, so a run that activates a license connects the repository in the same pass.
|
|
52
55
|
|
|
53
56
|
| Step | What it does |
|
|
@@ -59,10 +62,11 @@ The git step reads `–` until a license with remote sync is active; it is check
|
|
|
59
62
|
| Start Metabase | Starts the container or the jar and waits for `/api/health`, showing Metabase's own progress |
|
|
60
63
|
| Create the admin user | `admin@metabase.local` with a generated password that `rde credentials` shows, or your own with `--admin prompted`, see [Credentials](#credentials) |
|
|
61
64
|
| Connect the Metabase CLI | Creates an API key named `rde` and logs `mb` in with it under the profile `default`, so plain `mb` uses the instance; when `default` already points at another Metabase it asks first, see [The `mb` profile](#the-mb-profile); a key Metabase rejects is replaced |
|
|
62
|
-
|
|
|
65
|
+
| Enable transforms | Turns transforms on, unless someone already set them either way: turned off in the admin settings, they stay off on every later `rde init`, and the admin settings turn them back on |
|
|
66
|
+
| Connect a sample database | Runs Metabase's Postgres sample data (`metabase/qa-databases:postgres-sample-15`) in a container named `rde-sampledb-default` on the first free port from 25432 (or one Docker picks, when another program or a concurrent `rde init` takes that port first), and connects it as `Sample Database`. It replaces the H2 sample database Metabase would otherwise bundle, which cannot be a transform target. Needs Docker, so a jar instance on a machine without Docker has no sample database |
|
|
63
67
|
| Activate your license | Optional. Paste a token, or press Enter to skip; remote sync needs one with the `remote-sync` feature |
|
|
64
|
-
| Connect
|
|
65
|
-
| Connect a
|
|
68
|
+
| Connect a git repository | Only with a license. Initializes or reuses a repository and points Metabase's remote sync at it, with transforms included in the sync (the Transforms row under Collections to sync), and imports the content it already holds |
|
|
69
|
+
| Connect your databases | At a terminal, until you connect one of your own or skip it: pick the databases the imported content uses from a checklist, or, with none, paste a connection string (Enter skips); review, test, add; repeat. `--database` names them ahead of time; with `--yes` or without a terminal nothing is asked. `rde db add` starts from the same checklist, then the engine list, where the fields can also be filled one by one |
|
|
66
70
|
| Summary | URL, login email, the `mb` command for the instance, databases, repository, and the command that opens each detected agent |
|
|
67
71
|
|
|
68
72
|
The second run on the same machine detects every step as done and prints the summary only:
|
|
@@ -76,10 +80,11 @@ The second run on the same machine detects every step as done and prints the sum
|
|
|
76
80
|
✓ Start Metabase
|
|
77
81
|
✓ Create the admin user
|
|
78
82
|
✓ Connect the Metabase CLI
|
|
83
|
+
✓ Enable transforms
|
|
79
84
|
✓ Connect a sample database
|
|
80
85
|
– Activate your license
|
|
81
|
-
✓ Connect your databases
|
|
82
86
|
– Connect a git repository
|
|
87
|
+
✓ Connect your databases
|
|
83
88
|
○ Summary
|
|
84
89
|
│
|
|
85
90
|
◇ Your environment ─────────────────────────────────────────────────────────────────╮
|
|
@@ -167,8 +172,8 @@ Docker is used whenever it is available: the image is self-contained, and the co
|
|
|
167
172
|
| `rde credentials` | The Metabase URL, admin email, and password; `--json` for scripts and agents; `--api-key` for the API key alone |
|
|
168
173
|
| `rde logs` | The container's log, or the jar's `metabase.log`; `-f` keeps streaming |
|
|
169
174
|
| `rde open` | Opens the instance in the browser |
|
|
170
|
-
| `rde db add` | The database wizard: pick the engine, paste a connection string or fill the fields, review, test, add. `rde db add --url <connection string> [--name <display name>]` asks nothing: it reads the engine from the string (`--engine` for one that fits several), tests it and adds it, which is how a script or a coding agent connects a database without a terminal; `--url -` reads the string from stdin |
|
|
171
|
-
| `rde db list` | The connected databases; `--json` for scripts; a bare `rde db` does the same |
|
|
175
|
+
| `rde db add` | The database wizard: pick the stubs to connect when the content uses databases not connected yet, otherwise (or after them) pick the engine, paste a connection string or fill the fields, review, test, add. `rde db add --url <connection string> [--name <display name>]` asks nothing: it reads the engine from the string (`--engine` for one that fits several), tests it and adds it, which is how a script or a coding agent connects a database without a terminal; `--url -` reads the string from stdin; a `--name` that is a stub's connects that stub |
|
|
176
|
+
| `rde db list` | The connected databases and the stubs still waiting for a connection (`stub` in `--json`); `--json` for scripts; a bare `rde db` does the same |
|
|
172
177
|
| `rde doctor` | One table of checks with a fix hint per failure; `--fix` re-runs the init steps behind them; `--json` for scripts and the rde skill (every row's `id`, `status`, `detail`, `hint`, and `fix`: the `rde` command that repairs it, or null when you have to act); exit 1 while anything fails |
|
|
173
178
|
| `rde upgrade` | Moves the instance to a newer Metabase after backing up its data; see [Upgrading](#upgrading) |
|
|
174
179
|
| `rde uninstall` | Removes the instance, its data, and the `mb` profile; see [Resetting](#resetting) |
|
|
@@ -191,12 +196,12 @@ mb git-sync status
|
|
|
191
196
|
|
|
192
197
|
## The git repository
|
|
193
198
|
|
|
194
|
-
With a license that includes remote sync, the
|
|
199
|
+
With a license that includes remote sync, the step after the license connects a git repository. You choose where Metabase pushes, then where the working directory lives (here, another directory, or an existing repository):
|
|
195
200
|
|
|
196
201
|
- **Local remote inside `~/.rde`** (default): a bare repository at `instances/default/data/content.git`, mounted into the container and reached as `file:///rde/content.git`. No account, no token. Your working directory gets it as `origin` (or as `rde-remote` when `origin` already points elsewhere).
|
|
197
202
|
- **Hosted remote**: a GitHub, GitLab, or Bitbucket `https://` URL plus a token with read and write access. `rde` tests the connection through Metabase before saving anything, then clones the repository into the working directory (an existing repository gets the URL as `origin` instead); an empty hosted repository gets the first commit pushed to it. The token is handed to Metabase and, for the clone and that first push, to git in its environment; it never lands in the clone's config, the remote URL, or a credential helper, so a later `git pull` uses your own git credentials.
|
|
198
203
|
|
|
199
|
-
A fresh repository gets a first commit with a `README.md` and a `.gitignore` that keeps out what a data app built in it must not push: `.env`, `.env.local`, `.env.*.local`, `node_modules/`, `.vite/`, the rde skill's `.scratch/`, and `.DS_Store` (a data app's built `dist/` stays tracked, since that bundle is what Metabase serves); a `.gitignore` already there keeps its lines and gets the missing ones, and a repository that already has commits is left as it is. A repository that already holds Metabase content is imported on the spot. Afterwards:
|
|
204
|
+
A fresh repository gets a first commit with a `README.md` and a `.gitignore` that keeps out what a data app built in it must not push: `.env`, `.env.local`, `.env.*.local`, `node_modules/`, `.vite/`, the rde skill's `.scratch/`, and `.DS_Store` (a data app's built `dist/` stays tracked, since that bundle is what Metabase serves); a `.gitignore` already there keeps its lines and gets the missing ones, and a repository that already has commits is left as it is. A repository that already holds Metabase content is imported on the spot; each database it names that this instance lacks becomes a stub, which the databases step right after offers to connect. Afterwards:
|
|
200
205
|
|
|
201
206
|
```sh
|
|
202
207
|
cd <your working directory>
|
package/dist/cli.mjs
CHANGED
|
@@ -240,10 +240,11 @@ const StepId = z.enum([
|
|
|
240
240
|
"start-metabase",
|
|
241
241
|
"admin-user",
|
|
242
242
|
"api-key",
|
|
243
|
+
"transforms",
|
|
243
244
|
"sample-database",
|
|
244
245
|
"license",
|
|
245
|
-
"databases",
|
|
246
246
|
"git-sync",
|
|
247
|
+
"databases",
|
|
247
248
|
"summary"
|
|
248
249
|
]);
|
|
249
250
|
const GlobalConfig = z.object({
|
|
@@ -1037,8 +1038,12 @@ const Database = z.object({
|
|
|
1037
1038
|
id: z.int(),
|
|
1038
1039
|
name: z.string(),
|
|
1039
1040
|
engine: z.string(),
|
|
1040
|
-
initial_sync_status: InitialSyncStatus
|
|
1041
|
+
initial_sync_status: InitialSyncStatus,
|
|
1042
|
+
is_stub: z.boolean().optional()
|
|
1041
1043
|
}).loose();
|
|
1044
|
+
function isStub(database) {
|
|
1045
|
+
return database.is_stub === true;
|
|
1046
|
+
}
|
|
1042
1047
|
const DatabaseList = z.object({
|
|
1043
1048
|
data: z.array(Database),
|
|
1044
1049
|
total: z.int()
|
|
@@ -1072,6 +1077,17 @@ function databaseResource(http) {
|
|
|
1072
1077
|
async function list() {
|
|
1073
1078
|
return http.requestJson("GET", "/api/database", { schema: DatabaseList });
|
|
1074
1079
|
}
|
|
1080
|
+
/** GET /api/database?include_stubs=true */
|
|
1081
|
+
async function listWithStubs() {
|
|
1082
|
+
return http.requestJson("GET", "/api/database?include_stubs=true", { schema: DatabaseList });
|
|
1083
|
+
}
|
|
1084
|
+
/** PUT /api/database/:id — on a stub, new connection details make it a real database */
|
|
1085
|
+
async function connect(id, connection) {
|
|
1086
|
+
return http.requestJson("PUT", `/api/database/${id}`, {
|
|
1087
|
+
body: connection,
|
|
1088
|
+
schema: Database
|
|
1089
|
+
});
|
|
1090
|
+
}
|
|
1075
1091
|
/** GET /api/database/:id */
|
|
1076
1092
|
async function get(id) {
|
|
1077
1093
|
return http.requestJson("GET", `/api/database/${id}`, { schema: Database });
|
|
@@ -1080,6 +1096,8 @@ function databaseResource(http) {
|
|
|
1080
1096
|
validate,
|
|
1081
1097
|
create,
|
|
1082
1098
|
list,
|
|
1099
|
+
listWithStubs,
|
|
1100
|
+
connect,
|
|
1083
1101
|
get
|
|
1084
1102
|
};
|
|
1085
1103
|
}
|
|
@@ -1132,6 +1150,13 @@ function remoteSyncResource(http) {
|
|
|
1132
1150
|
schema: RemoteSyncSettingsResult
|
|
1133
1151
|
});
|
|
1134
1152
|
}
|
|
1153
|
+
/** PUT /api/ee/remote-sync/settings with only `remote-sync-transforms`, which skips the repository check. */
|
|
1154
|
+
async function syncTransforms() {
|
|
1155
|
+
return http.requestJson("PUT", "/api/ee/remote-sync/settings", {
|
|
1156
|
+
body: { "remote-sync-transforms": true },
|
|
1157
|
+
schema: RemoteSyncSettingsResult
|
|
1158
|
+
});
|
|
1159
|
+
}
|
|
1135
1160
|
/** POST /api/ee/remote-sync/test-connection; an empty body tests the saved url and token. */
|
|
1136
1161
|
async function testConnection(connection = {}) {
|
|
1137
1162
|
return http.requestJson("POST", "/api/ee/remote-sync/test-connection", {
|
|
@@ -1145,6 +1170,7 @@ function remoteSyncResource(http) {
|
|
|
1145
1170
|
}
|
|
1146
1171
|
return {
|
|
1147
1172
|
updateSettings,
|
|
1173
|
+
syncTransforms,
|
|
1148
1174
|
testConnection,
|
|
1149
1175
|
currentTask
|
|
1150
1176
|
};
|
|
@@ -1154,6 +1180,33 @@ function remoteSyncResource(http) {
|
|
|
1154
1180
|
const Session = z.object({ id: z.string() }).loose();
|
|
1155
1181
|
//#endregion
|
|
1156
1182
|
//#region src/metabase/domain/session-properties.ts
|
|
1183
|
+
const SessionProperties = z.object({
|
|
1184
|
+
"setup-token": z.string().nullable(),
|
|
1185
|
+
"has-user-setup": z.boolean(),
|
|
1186
|
+
version: z.object({ tag: z.string() }).loose(),
|
|
1187
|
+
"token-features": z.record(z.string(), z.boolean())
|
|
1188
|
+
}).loose();
|
|
1189
|
+
//#endregion
|
|
1190
|
+
//#region src/metabase/resources/session.ts
|
|
1191
|
+
function sessionResource(http) {
|
|
1192
|
+
/** GET /api/session/properties */
|
|
1193
|
+
async function properties() {
|
|
1194
|
+
return http.requestJson("GET", "/api/session/properties", { schema: SessionProperties });
|
|
1195
|
+
}
|
|
1196
|
+
/** POST /api/session */
|
|
1197
|
+
async function create(credentials) {
|
|
1198
|
+
return http.requestJson("POST", "/api/session", {
|
|
1199
|
+
body: credentials,
|
|
1200
|
+
schema: Session
|
|
1201
|
+
});
|
|
1202
|
+
}
|
|
1203
|
+
return {
|
|
1204
|
+
properties,
|
|
1205
|
+
create
|
|
1206
|
+
};
|
|
1207
|
+
}
|
|
1208
|
+
//#endregion
|
|
1209
|
+
//#region src/metabase/domain/engine.ts
|
|
1157
1210
|
const EngineFieldOption = z.object({
|
|
1158
1211
|
name: z.string(),
|
|
1159
1212
|
value: z.unknown()
|
|
@@ -1179,36 +1232,16 @@ const Engine = z.object({
|
|
|
1179
1232
|
"superseded-by": z.string().nullable(),
|
|
1180
1233
|
"creatable?": z.boolean().optional()
|
|
1181
1234
|
}).loose();
|
|
1182
|
-
const
|
|
1183
|
-
"setup-token": z.string().nullable(),
|
|
1184
|
-
"has-user-setup": z.boolean(),
|
|
1185
|
-
version: z.object({ tag: z.string() }).loose(),
|
|
1186
|
-
"token-features": z.record(z.string(), z.boolean()),
|
|
1187
|
-
engines: z.record(z.string(), Engine)
|
|
1188
|
-
}).loose();
|
|
1235
|
+
const Engines = z.record(z.string(), Engine);
|
|
1189
1236
|
//#endregion
|
|
1190
|
-
//#region src/metabase/
|
|
1191
|
-
|
|
1192
|
-
/** GET /api/session/properties */
|
|
1193
|
-
async function properties() {
|
|
1194
|
-
return http.requestJson("GET", "/api/session/properties", { schema: SessionProperties });
|
|
1195
|
-
}
|
|
1196
|
-
/** POST /api/session */
|
|
1197
|
-
async function create(credentials) {
|
|
1198
|
-
return http.requestJson("POST", "/api/session", {
|
|
1199
|
-
body: credentials,
|
|
1200
|
-
schema: Session
|
|
1201
|
-
});
|
|
1202
|
-
}
|
|
1203
|
-
return {
|
|
1204
|
-
properties,
|
|
1205
|
-
create
|
|
1206
|
-
};
|
|
1207
|
-
}
|
|
1237
|
+
//#region src/metabase/domain/setting.ts
|
|
1238
|
+
const BooleanSetting = z.boolean();
|
|
1208
1239
|
//#endregion
|
|
1209
1240
|
//#region src/metabase/resources/setting.ts
|
|
1210
1241
|
const PREMIUM_EMBEDDING_TOKEN_SETTING = "premium-embedding-token";
|
|
1242
|
+
const ENGINES_SETTING = "engines";
|
|
1211
1243
|
const TRANSFORMS_ENABLED_SETTING = "transforms-enabled";
|
|
1244
|
+
const TRANSFORMS_SETUP_COMPLETE_SETTING = "transforms-setup-complete";
|
|
1212
1245
|
function settingResource(http) {
|
|
1213
1246
|
/** PUT /api/setting/premium-embedding-token */
|
|
1214
1247
|
async function setPremiumEmbeddingToken(value) {
|
|
@@ -1218,9 +1251,19 @@ function settingResource(http) {
|
|
|
1218
1251
|
async function enableTransforms() {
|
|
1219
1252
|
await http.requestNoContent("PUT", `/api/setting/${TRANSFORMS_ENABLED_SETTING}`, { body: { value: true } });
|
|
1220
1253
|
}
|
|
1254
|
+
/** GET /api/setting/transforms-setup-complete */
|
|
1255
|
+
async function transformsDecided() {
|
|
1256
|
+
return http.requestJson("GET", `/api/setting/${TRANSFORMS_SETUP_COMPLETE_SETTING}`, { schema: BooleanSetting });
|
|
1257
|
+
}
|
|
1258
|
+
/** GET /api/setting/engines; admins only, which the API key's user is. */
|
|
1259
|
+
async function engines() {
|
|
1260
|
+
return http.requestJson("GET", `/api/setting/${ENGINES_SETTING}`, { schema: Engines });
|
|
1261
|
+
}
|
|
1221
1262
|
return {
|
|
1222
1263
|
setPremiumEmbeddingToken,
|
|
1223
|
-
enableTransforms
|
|
1264
|
+
enableTransforms,
|
|
1265
|
+
transformsDecided,
|
|
1266
|
+
engines
|
|
1224
1267
|
};
|
|
1225
1268
|
}
|
|
1226
1269
|
//#endregion
|
|
@@ -2443,13 +2486,6 @@ const PLUGINS_DIR = "plugins";
|
|
|
2443
2486
|
const METABASE_STOP_GRACE_MS = 3e4;
|
|
2444
2487
|
const DB_DIR = "metabase.db";
|
|
2445
2488
|
const DB_FILE = "metabase.db";
|
|
2446
|
-
const STAGING_STORE_ENV = {
|
|
2447
|
-
MB_RUN_MODE: "e2e",
|
|
2448
|
-
MB_STORE_USE_STAGING: "true"
|
|
2449
|
-
};
|
|
2450
|
-
const ALLOW_LOCAL_WAREHOUSES_ENV = { MB_WAREHOUSE_ALLOWED_NETWORKS: "allow-all" };
|
|
2451
|
-
const NO_H2_SAMPLE_ENV = { MB_LOAD_SAMPLE_CONTENT: "false" };
|
|
2452
|
-
const NO_AUDIT_DB_ENV = { MB_INSTALL_ANALYTICS_DATABASE: "false" };
|
|
2453
2489
|
function instanceUrl(port) {
|
|
2454
2490
|
return `http://localhost:${port}`;
|
|
2455
2491
|
}
|
|
@@ -2458,10 +2494,11 @@ function serverEnv(options) {
|
|
|
2458
2494
|
MB_DB_FILE: options.dbFile,
|
|
2459
2495
|
MB_PLUGINS_DIR: options.pluginsDir,
|
|
2460
2496
|
MB_SITE_URL: instanceUrl(options.port),
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2497
|
+
MB_RUN_MODE: "e2e",
|
|
2498
|
+
MB_STORE_USE_STAGING: "true",
|
|
2499
|
+
MB_WAREHOUSE_ALLOWED_NETWORKS: "allow-all",
|
|
2500
|
+
MB_LOAD_SAMPLE_CONTENT: "false",
|
|
2501
|
+
MB_INSTALL_ANALYTICS_DATABASE: "false"
|
|
2465
2502
|
};
|
|
2466
2503
|
}
|
|
2467
2504
|
function jettyEnv(port) {
|
|
@@ -3018,12 +3055,12 @@ async function portClaims(state, runtime) {
|
|
|
3018
3055
|
}
|
|
3019
3056
|
//#endregion
|
|
3020
3057
|
//#region src/steps/admin-user.ts
|
|
3021
|
-
const STEP_ID$
|
|
3058
|
+
const STEP_ID$10 = "admin-user";
|
|
3022
3059
|
const ADMIN_USER_KEYS = {
|
|
3023
|
-
email: `${STEP_ID$
|
|
3024
|
-
password: `${STEP_ID$
|
|
3025
|
-
firstName: `${STEP_ID$
|
|
3026
|
-
lastName: `${STEP_ID$
|
|
3060
|
+
email: `${STEP_ID$10}.email`,
|
|
3061
|
+
password: `${STEP_ID$10}.password`,
|
|
3062
|
+
firstName: `${STEP_ID$10}.first-name`,
|
|
3063
|
+
lastName: `${STEP_ID$10}.last-name`
|
|
3027
3064
|
};
|
|
3028
3065
|
const GENERATED_ADMIN_EMAIL = "admin@metabase.local";
|
|
3029
3066
|
const GENERATED_PASSWORD_BYTES = 24;
|
|
@@ -3038,7 +3075,7 @@ function generatePassword(random) {
|
|
|
3038
3075
|
if (DIGIT.test(candidate)) return candidate;
|
|
3039
3076
|
}
|
|
3040
3077
|
}
|
|
3041
|
-
function hostDeps$
|
|
3078
|
+
function hostDeps$14(ctx) {
|
|
3042
3079
|
return {
|
|
3043
3080
|
client: (baseUrl, auth) => createMetabaseClient(baseUrl, auth, ctx.signal),
|
|
3044
3081
|
instanceRunning: () => isInstanceRunning(ctx.state, hostRuntimeDeps(ctx.state.name, ctx.signal)),
|
|
@@ -3176,9 +3213,9 @@ async function recordExistingAdmin(ctx, variant, client, url) {
|
|
|
3176
3213
|
ctx.ui.success(`Admin user ${email} recorded`);
|
|
3177
3214
|
return "completed";
|
|
3178
3215
|
}
|
|
3179
|
-
function adminUserStep(options, deps = hostDeps$
|
|
3216
|
+
function adminUserStep(options, deps = hostDeps$14) {
|
|
3180
3217
|
return {
|
|
3181
|
-
id: STEP_ID$
|
|
3218
|
+
id: STEP_ID$10,
|
|
3182
3219
|
title: "Create the admin user",
|
|
3183
3220
|
redoHint: "An instance keeps the admin it was set up with; `rde credentials` shows it.",
|
|
3184
3221
|
async detect(ctx) {
|
|
@@ -3559,14 +3596,14 @@ function mbTool(runner) {
|
|
|
3559
3596
|
}
|
|
3560
3597
|
//#endregion
|
|
3561
3598
|
//#region src/steps/api-key.ts
|
|
3562
|
-
const STEP_ID$
|
|
3599
|
+
const STEP_ID$9 = "api-key";
|
|
3563
3600
|
const API_KEY_KEYS = {
|
|
3564
|
-
email: `${STEP_ID$
|
|
3565
|
-
password: `${STEP_ID$
|
|
3566
|
-
profile: `${STEP_ID$
|
|
3601
|
+
email: `${STEP_ID$9}.email`,
|
|
3602
|
+
password: `${STEP_ID$9}.password`,
|
|
3603
|
+
profile: `${STEP_ID$9}.profile`
|
|
3567
3604
|
};
|
|
3568
3605
|
const ADMINISTRATORS_GROUP_NAME = "Administrators";
|
|
3569
|
-
function hostDeps$
|
|
3606
|
+
function hostDeps$13(ctx) {
|
|
3570
3607
|
return {
|
|
3571
3608
|
client: (baseUrl, auth) => createMetabaseClient(baseUrl, auth, ctx.signal),
|
|
3572
3609
|
runner: processRunner(ctx.signal)
|
|
@@ -3685,9 +3722,9 @@ function usageNote(profile) {
|
|
|
3685
3722
|
if (profile === "default") return `${mbCommand(profile)} <command> uses this instance`;
|
|
3686
3723
|
return `${mbCommand(profile)} <command>\nMB_PROFILE=${profile} for agents and scripts`;
|
|
3687
3724
|
}
|
|
3688
|
-
function apiKeyStep(deps = hostDeps$
|
|
3725
|
+
function apiKeyStep(deps = hostDeps$13) {
|
|
3689
3726
|
return {
|
|
3690
|
-
id: STEP_ID$
|
|
3727
|
+
id: STEP_ID$9,
|
|
3691
3728
|
title: "Connect the Metabase CLI",
|
|
3692
3729
|
redoHint: "`rde status` shows the mb profile it uses.",
|
|
3693
3730
|
async detect(ctx) {
|
|
@@ -4646,20 +4683,29 @@ function syncWithHint(database) {
|
|
|
4646
4683
|
function existingDatabaseLines(databases) {
|
|
4647
4684
|
return databases.map((db) => `${db.name} (${db.engine}, ${syncWithHint(db)})`);
|
|
4648
4685
|
}
|
|
4686
|
+
function unconnectedStubsLine(stubs) {
|
|
4687
|
+
return `Not connected yet, used by your content: ${stubs.map((stub) => stub.name).join(", ")}; \`rde db add\` connects one`;
|
|
4688
|
+
}
|
|
4689
|
+
function listingStatus(entry) {
|
|
4690
|
+
if (entry.stub) return "not connected, your content uses it; `rde db add` connects it";
|
|
4691
|
+
return entry.initialSyncStatus === null ? "missing on the server" : syncLabel(entry.initialSyncStatus);
|
|
4692
|
+
}
|
|
4649
4693
|
function databaseListingLines(listing) {
|
|
4650
4694
|
if (listing.length === 0) return ["No databases connected yet; run `rde db add`"];
|
|
4651
4695
|
return listing.map((entry) => {
|
|
4652
|
-
const
|
|
4653
|
-
return `${entry.name} ${
|
|
4696
|
+
const engine = entry.stub ? "?" : entry.engine;
|
|
4697
|
+
return `${entry.name} ${engine} id ${entry.id} ${listingStatus(entry)}`;
|
|
4654
4698
|
});
|
|
4655
4699
|
}
|
|
4656
4700
|
//#endregion
|
|
4657
4701
|
//#region src/steps/databases/flow.ts
|
|
4658
4702
|
const DATABASE_KEYS = {
|
|
4659
4703
|
connection: "databases.connection",
|
|
4660
|
-
skip: "databases.skip"
|
|
4704
|
+
skip: "databases.skip",
|
|
4705
|
+
stubs: "databases.stubs"
|
|
4661
4706
|
};
|
|
4662
4707
|
const DATABASE_KEY_NAMES = {
|
|
4708
|
+
connection: "connection",
|
|
4663
4709
|
detectedEngine: "detected-engine",
|
|
4664
4710
|
engine: "engine",
|
|
4665
4711
|
mode: "mode",
|
|
@@ -4703,11 +4749,13 @@ const RSA_KEY_UNAVAILABLE_ERROR = "RSA public key is not available";
|
|
|
4703
4749
|
const KEY_RETRIEVAL_NOTE = "allowPublicKeyRetrieval=true is added: MySQL 8 needs it to log in to a local server without SSL; to leave it out, paste a connection string with allowPublicKeyRetrieval=false instead";
|
|
4704
4750
|
const KEY_RETRIEVAL_HINT = "MySQL 8 without SSL needs allowPublicKeyRetrieval=true: go back and add ?allowPublicKeyRetrieval=true to the connection string, or turn on SSL";
|
|
4705
4751
|
const CONNECTION_PLACEHOLDER = "leave empty to skip";
|
|
4752
|
+
const STUB_CONNECTION_PLACEHOLDER = "leave empty to pick the engine";
|
|
4706
4753
|
const START_SCREEN = {
|
|
4707
4754
|
engine: "pick-engine",
|
|
4708
4755
|
"connection-string": "connection-string"
|
|
4709
4756
|
};
|
|
4710
4757
|
const FRESH$1 = {
|
|
4758
|
+
target: null,
|
|
4711
4759
|
engine: null,
|
|
4712
4760
|
uri: null,
|
|
4713
4761
|
pasted: null,
|
|
@@ -4717,6 +4765,35 @@ const FRESH$1 = {
|
|
|
4717
4765
|
name: null,
|
|
4718
4766
|
fieldErrors: null
|
|
4719
4767
|
};
|
|
4768
|
+
function targeting(stub) {
|
|
4769
|
+
return {
|
|
4770
|
+
...FRESH$1,
|
|
4771
|
+
target: stub,
|
|
4772
|
+
name: stub.name
|
|
4773
|
+
};
|
|
4774
|
+
}
|
|
4775
|
+
function restart(model) {
|
|
4776
|
+
return model.target === null ? FRESH$1 : targeting(model.target);
|
|
4777
|
+
}
|
|
4778
|
+
function nextStub(run, model) {
|
|
4779
|
+
const stub = run.queue.shift();
|
|
4780
|
+
if (stub !== void 0) return {
|
|
4781
|
+
type: "next",
|
|
4782
|
+
to: "connection-string",
|
|
4783
|
+
model: targeting(stub)
|
|
4784
|
+
};
|
|
4785
|
+
return run.created.length > 0 ? {
|
|
4786
|
+
type: "next",
|
|
4787
|
+
to: "another",
|
|
4788
|
+
model
|
|
4789
|
+
} : {
|
|
4790
|
+
type: "done",
|
|
4791
|
+
model
|
|
4792
|
+
};
|
|
4793
|
+
}
|
|
4794
|
+
function stubNamed(run, name) {
|
|
4795
|
+
return run.stubs.find((stub) => stub.name === name) ?? null;
|
|
4796
|
+
}
|
|
4720
4797
|
function ordinalOf(run) {
|
|
4721
4798
|
return run.created.length + 1;
|
|
4722
4799
|
}
|
|
@@ -4786,20 +4863,60 @@ async function pickDetectedEngine(run, candidates) {
|
|
|
4786
4863
|
if (entry === void 0) throw new Error(`engine ${picked} was not offered`);
|
|
4787
4864
|
return entry;
|
|
4788
4865
|
}
|
|
4866
|
+
function pickStubsScreen(run) {
|
|
4867
|
+
return async (model) => {
|
|
4868
|
+
const picked = await run.deps.prompts.multiselect({
|
|
4869
|
+
key: DATABASE_KEYS.stubs,
|
|
4870
|
+
message: "Your content uses these databases, not connected yet. Pick the ones to connect now (space picks, Enter goes on; the rest wait for `rde db add`)",
|
|
4871
|
+
choices: run.stubs.map((stub) => ({
|
|
4872
|
+
value: stub.id,
|
|
4873
|
+
label: stub.name
|
|
4874
|
+
})),
|
|
4875
|
+
required: false
|
|
4876
|
+
});
|
|
4877
|
+
if (picked.length > 0) {
|
|
4878
|
+
run.queue = run.stubs.filter((stub) => picked.includes(stub.id));
|
|
4879
|
+
return nextStub(run, model);
|
|
4880
|
+
}
|
|
4881
|
+
if (run.deps.start === "connection-string" && run.created.length === 0) return {
|
|
4882
|
+
type: "done",
|
|
4883
|
+
model
|
|
4884
|
+
};
|
|
4885
|
+
return {
|
|
4886
|
+
type: "next",
|
|
4887
|
+
to: "pick-engine",
|
|
4888
|
+
model: FRESH$1
|
|
4889
|
+
};
|
|
4890
|
+
};
|
|
4891
|
+
}
|
|
4892
|
+
function connectionQuestion(run, target) {
|
|
4893
|
+
if (target === null) return {
|
|
4894
|
+
key: DATABASE_KEYS.connection,
|
|
4895
|
+
message: "Connection string of your database (Enter to skip; `rde db add` connects one later)",
|
|
4896
|
+
placeholder: CONNECTION_PLACEHOLDER
|
|
4897
|
+
};
|
|
4898
|
+
return {
|
|
4899
|
+
key: databaseKey(ordinalOf(run), "connection"),
|
|
4900
|
+
message: `Connection string of ${target.name} (Enter to pick its engine instead)`,
|
|
4901
|
+
placeholder: STUB_CONNECTION_PLACEHOLDER
|
|
4902
|
+
};
|
|
4903
|
+
}
|
|
4789
4904
|
function connectionStringScreen(run) {
|
|
4790
4905
|
return async (model) => {
|
|
4791
4906
|
let draft = model.uri;
|
|
4792
4907
|
for (;;) {
|
|
4793
4908
|
const uri = (await run.deps.prompts.text({
|
|
4794
|
-
|
|
4795
|
-
message: "Connection string of your database (Enter to skip; `rde db add` connects one later)",
|
|
4796
|
-
placeholder: CONNECTION_PLACEHOLDER,
|
|
4909
|
+
...connectionQuestion(run, model.target),
|
|
4797
4910
|
...draft !== null && { initialValue: draft }
|
|
4798
4911
|
})).trim();
|
|
4799
4912
|
draft = uri;
|
|
4800
|
-
if (uri.length === 0) return {
|
|
4913
|
+
if (uri.length === 0) return model.target === null ? {
|
|
4801
4914
|
type: "done",
|
|
4802
4915
|
model
|
|
4916
|
+
} : {
|
|
4917
|
+
type: "next",
|
|
4918
|
+
to: "pick-engine",
|
|
4919
|
+
model: restart(model)
|
|
4803
4920
|
};
|
|
4804
4921
|
const candidates = detectEngine(uri).map((key) => run.deps.catalog.entry(key)).filter(isCreatableEntry);
|
|
4805
4922
|
const [first, ...others] = candidates;
|
|
@@ -4808,7 +4925,7 @@ function connectionStringScreen(run) {
|
|
|
4808
4925
|
return {
|
|
4809
4926
|
type: "next",
|
|
4810
4927
|
to: "pick-engine",
|
|
4811
|
-
model:
|
|
4928
|
+
model: restart(model)
|
|
4812
4929
|
};
|
|
4813
4930
|
}
|
|
4814
4931
|
const engine = others.length === 0 ? first : await pickDetectedEngine(run, candidates);
|
|
@@ -4822,7 +4939,7 @@ function connectionStringScreen(run) {
|
|
|
4822
4939
|
return {
|
|
4823
4940
|
type: "next",
|
|
4824
4941
|
to: "pick-engine",
|
|
4825
|
-
model:
|
|
4942
|
+
model: restart(model)
|
|
4826
4943
|
};
|
|
4827
4944
|
}
|
|
4828
4945
|
return toReview(run, model, {
|
|
@@ -4832,12 +4949,16 @@ function connectionStringScreen(run) {
|
|
|
4832
4949
|
}
|
|
4833
4950
|
};
|
|
4834
4951
|
}
|
|
4952
|
+
function finishLabel(run, model) {
|
|
4953
|
+
if (model.target !== null) return `Leave ${model.target.name} unconnected`;
|
|
4954
|
+
return run.deps.existingCount + run.created.length > 0 ? "Finish" : "Skip for now";
|
|
4955
|
+
}
|
|
4835
4956
|
function pickEngineScreen(run) {
|
|
4836
4957
|
return async (model) => {
|
|
4837
4958
|
const total = run.deps.existingCount + run.created.length;
|
|
4838
4959
|
const finishChoice = {
|
|
4839
4960
|
value: FINISH,
|
|
4840
|
-
label:
|
|
4961
|
+
label: finishLabel(run, model)
|
|
4841
4962
|
};
|
|
4842
4963
|
for (;;) {
|
|
4843
4964
|
const picked = await run.deps.prompts.select({
|
|
@@ -4846,6 +4967,7 @@ function pickEngineScreen(run) {
|
|
|
4846
4967
|
choices: [...run.deps.catalog.list().map(engineChoice), finishChoice],
|
|
4847
4968
|
...model.engine !== null && { initialValue: model.engine.key }
|
|
4848
4969
|
});
|
|
4970
|
+
if (picked === "finish" && model.target !== null) return nextStub(run, model);
|
|
4849
4971
|
if (picked === "finish") {
|
|
4850
4972
|
if (total > 0 || await confirmSkip(run)) return {
|
|
4851
4973
|
type: "done",
|
|
@@ -4863,7 +4985,7 @@ function pickEngineScreen(run) {
|
|
|
4863
4985
|
type: "next",
|
|
4864
4986
|
to: "paste-uri",
|
|
4865
4987
|
model: model.engine?.key === entry.key ? model : {
|
|
4866
|
-
...
|
|
4988
|
+
...restart(model),
|
|
4867
4989
|
engine: entry
|
|
4868
4990
|
}
|
|
4869
4991
|
};
|
|
@@ -4894,6 +5016,10 @@ async function chooseAfterParseError(run, engine, uri, error) {
|
|
|
4894
5016
|
if (entry === void 0) throw new Error(`engine ${picked} was not offered`);
|
|
4895
5017
|
return entry;
|
|
4896
5018
|
}
|
|
5019
|
+
function reviewName(model, sameString) {
|
|
5020
|
+
if (model.target !== null) return model.target.name;
|
|
5021
|
+
return sameString ? model.name : null;
|
|
5022
|
+
}
|
|
4897
5023
|
function toReview(run, model, connection) {
|
|
4898
5024
|
const mapped = mapDatabaseValues(connection.parsed);
|
|
4899
5025
|
const sameString = model.uri === connection.parsed.uri;
|
|
@@ -4901,13 +5027,14 @@ function toReview(run, model, connection) {
|
|
|
4901
5027
|
type: "next",
|
|
4902
5028
|
to: "review",
|
|
4903
5029
|
model: {
|
|
5030
|
+
target: model.target,
|
|
4904
5031
|
engine: connection.engine,
|
|
4905
5032
|
uri: connection.parsed.uri,
|
|
4906
5033
|
pasted: mapped.details,
|
|
4907
5034
|
details: mapped.details,
|
|
4908
5035
|
failure: null,
|
|
4909
5036
|
suggestedName: defaultDisplayName(mapped, connection.parsed, connection.engine),
|
|
4910
|
-
name:
|
|
5037
|
+
name: reviewName(model, sameString),
|
|
4911
5038
|
fieldErrors: null
|
|
4912
5039
|
}
|
|
4913
5040
|
};
|
|
@@ -5050,6 +5177,33 @@ async function fixReportedFields(run, fields, details, errors) {
|
|
|
5050
5177
|
}
|
|
5051
5178
|
return current;
|
|
5052
5179
|
}
|
|
5180
|
+
function reviewQuestion(target, name) {
|
|
5181
|
+
const edit = {
|
|
5182
|
+
value: "edit",
|
|
5183
|
+
label: "Edit a field"
|
|
5184
|
+
};
|
|
5185
|
+
if (target !== null) return {
|
|
5186
|
+
message: `Connect ${name}?`,
|
|
5187
|
+
choices: [{
|
|
5188
|
+
value: "test",
|
|
5189
|
+
label: "Test and connect"
|
|
5190
|
+
}, edit]
|
|
5191
|
+
};
|
|
5192
|
+
return {
|
|
5193
|
+
message: `Add ${name}?`,
|
|
5194
|
+
choices: [
|
|
5195
|
+
{
|
|
5196
|
+
value: "test",
|
|
5197
|
+
label: "Test and add"
|
|
5198
|
+
},
|
|
5199
|
+
edit,
|
|
5200
|
+
{
|
|
5201
|
+
value: "rename",
|
|
5202
|
+
label: "Change the display name"
|
|
5203
|
+
}
|
|
5204
|
+
]
|
|
5205
|
+
};
|
|
5206
|
+
}
|
|
5053
5207
|
function reviewScreen(run) {
|
|
5054
5208
|
const review = reviewAttempt(run);
|
|
5055
5209
|
return (model) => model.failure === null ? review(model) : orFailure(model.failure, () => review({
|
|
@@ -5075,21 +5229,7 @@ function reviewAttempt(run) {
|
|
|
5075
5229
|
if (name === null) name = await promptName(run, current, chosen.engine);
|
|
5076
5230
|
const action = await run.deps.prompts.selectOrBack({
|
|
5077
5231
|
key: databaseKey(ordinalOf(run), "action"),
|
|
5078
|
-
|
|
5079
|
-
choices: [
|
|
5080
|
-
{
|
|
5081
|
-
value: "test",
|
|
5082
|
-
label: "Test and add"
|
|
5083
|
-
},
|
|
5084
|
-
{
|
|
5085
|
-
value: "edit",
|
|
5086
|
-
label: "Edit a field"
|
|
5087
|
-
},
|
|
5088
|
-
{
|
|
5089
|
-
value: "rename",
|
|
5090
|
-
label: "Change the display name"
|
|
5091
|
-
}
|
|
5092
|
-
]
|
|
5232
|
+
...reviewQuestion(model.target, name)
|
|
5093
5233
|
});
|
|
5094
5234
|
if (action === BACK) return { type: "back" };
|
|
5095
5235
|
if (action === "edit") {
|
|
@@ -5168,26 +5308,44 @@ function testScreen(run) {
|
|
|
5168
5308
|
function isRejectedDetails(error) {
|
|
5169
5309
|
return error instanceof MetabaseApiError && error.status === BAD_REQUEST_STATUS$1;
|
|
5170
5310
|
}
|
|
5311
|
+
function commitFor(run, stub) {
|
|
5312
|
+
if (stub === null) return {
|
|
5313
|
+
verb: "add",
|
|
5314
|
+
progress: "Adding",
|
|
5315
|
+
past: "Added",
|
|
5316
|
+
send: (request) => run.deps.database.create(request)
|
|
5317
|
+
};
|
|
5318
|
+
return {
|
|
5319
|
+
verb: "connect",
|
|
5320
|
+
progress: "Connecting",
|
|
5321
|
+
past: "Connected",
|
|
5322
|
+
send: ({ engine, details }) => run.deps.database.connect(stub.id, {
|
|
5323
|
+
engine,
|
|
5324
|
+
details
|
|
5325
|
+
})
|
|
5326
|
+
};
|
|
5327
|
+
}
|
|
5171
5328
|
function createScreen(run) {
|
|
5172
5329
|
return async (model) => {
|
|
5173
5330
|
const chosen = requireEngine(run, model);
|
|
5174
5331
|
if (model.name === null) throw new Error("no display name before create");
|
|
5175
|
-
const
|
|
5332
|
+
const commit = commitFor(run, model.target ?? stubNamed(run, model.name));
|
|
5333
|
+
const spinner = run.deps.ui.spinner(`${commit.progress} ${model.name}…`);
|
|
5176
5334
|
let database;
|
|
5177
5335
|
try {
|
|
5178
|
-
database = await
|
|
5336
|
+
database = await commit.send({
|
|
5179
5337
|
name: model.name,
|
|
5180
5338
|
engine: chosen.engine.key,
|
|
5181
5339
|
details: sentDetails(run, chosen, model.details).details
|
|
5182
5340
|
});
|
|
5183
5341
|
} catch (error) {
|
|
5184
5342
|
if (!isRejectedDetails(error)) {
|
|
5185
|
-
spinner.fail(`Could not
|
|
5343
|
+
spinner.fail(`Could not ${commit.verb} ${model.name}`);
|
|
5186
5344
|
throw error;
|
|
5187
5345
|
}
|
|
5188
5346
|
spinner.fail(error.message);
|
|
5189
5347
|
keyRetrievalHint(run, chosen, error.message);
|
|
5190
|
-
const failure = `Metabase could not
|
|
5348
|
+
const failure = `Metabase could not ${commit.verb} ${model.name}: ${error.message}`;
|
|
5191
5349
|
return {
|
|
5192
5350
|
type: "next",
|
|
5193
5351
|
to: "review",
|
|
@@ -5199,14 +5357,15 @@ function createScreen(run) {
|
|
|
5199
5357
|
};
|
|
5200
5358
|
}
|
|
5201
5359
|
run.created.push(database);
|
|
5202
|
-
|
|
5360
|
+
run.stubs = run.stubs.filter((candidate) => candidate.id !== database.id);
|
|
5361
|
+
spinner.stop(`${commit.past} ${database.name}. Initial sync is running in the background; \`rde status\` shows progress.`);
|
|
5203
5362
|
run.added.push(addedDatabase(chosen, model, model.name));
|
|
5204
5363
|
await run.deps.onCreated(database);
|
|
5205
|
-
return {
|
|
5364
|
+
return model.target === null ? {
|
|
5206
5365
|
type: "next",
|
|
5207
5366
|
to: "another",
|
|
5208
5367
|
model
|
|
5209
|
-
};
|
|
5368
|
+
} : nextStub(run, model);
|
|
5210
5369
|
};
|
|
5211
5370
|
}
|
|
5212
5371
|
function anotherScreen(run) {
|
|
@@ -5227,18 +5386,24 @@ function anotherScreen(run) {
|
|
|
5227
5386
|
};
|
|
5228
5387
|
return {
|
|
5229
5388
|
type: "next",
|
|
5230
|
-
to: "pick-engine",
|
|
5389
|
+
to: offersStubs(run) ? "pick-stubs" : "pick-engine",
|
|
5231
5390
|
model: FRESH$1
|
|
5232
5391
|
};
|
|
5233
5392
|
};
|
|
5234
5393
|
}
|
|
5394
|
+
function offersStubs(run) {
|
|
5395
|
+
return run.deps.offerStubs && run.stubs.length > 0;
|
|
5396
|
+
}
|
|
5235
5397
|
async function runDatabaseFlow(deps) {
|
|
5236
5398
|
const run = {
|
|
5237
5399
|
deps,
|
|
5238
5400
|
created: [],
|
|
5239
|
-
added: []
|
|
5401
|
+
added: [],
|
|
5402
|
+
stubs: [...deps.stubs],
|
|
5403
|
+
queue: []
|
|
5240
5404
|
};
|
|
5241
5405
|
await runFlow({
|
|
5406
|
+
"pick-stubs": pickStubsScreen(run),
|
|
5242
5407
|
"connection-string": connectionStringScreen(run),
|
|
5243
5408
|
"pick-engine": pickEngineScreen(run),
|
|
5244
5409
|
"paste-uri": pasteUriScreen(run),
|
|
@@ -5246,10 +5411,11 @@ async function runDatabaseFlow(deps) {
|
|
|
5246
5411
|
test: testScreen(run),
|
|
5247
5412
|
create: createScreen(run),
|
|
5248
5413
|
another: anotherScreen(run)
|
|
5249
|
-
}, START_SCREEN[deps.start], FRESH$1);
|
|
5414
|
+
}, offersStubs(run) ? "pick-stubs" : START_SCREEN[deps.start], FRESH$1);
|
|
5250
5415
|
return {
|
|
5251
5416
|
created: run.created,
|
|
5252
|
-
added: run.added
|
|
5417
|
+
added: run.added,
|
|
5418
|
+
stubsLeft: run.stubs
|
|
5253
5419
|
};
|
|
5254
5420
|
}
|
|
5255
5421
|
//#endregion
|
|
@@ -5305,8 +5471,9 @@ function gitTool(runner) {
|
|
|
5305
5471
|
}
|
|
5306
5472
|
async function isRepo(dir) {
|
|
5307
5473
|
if (!await directoryExists(dir)) return false;
|
|
5308
|
-
const result = await runner.run(GIT, ["rev-parse", "--
|
|
5309
|
-
|
|
5474
|
+
const result = await runner.run(GIT, ["rev-parse", "--show-toplevel"], { cwd: dir });
|
|
5475
|
+
if (result.exitCode !== 0) return false;
|
|
5476
|
+
return result.stdout.trim() === await promises.realpath(dir);
|
|
5310
5477
|
}
|
|
5311
5478
|
async function initBare(path, options) {
|
|
5312
5479
|
await git([
|
|
@@ -5419,13 +5586,13 @@ function gitTool(runner) {
|
|
|
5419
5586
|
}
|
|
5420
5587
|
//#endregion
|
|
5421
5588
|
//#region src/steps/git-sync.ts
|
|
5422
|
-
const STEP_ID$
|
|
5589
|
+
const STEP_ID$8 = "git-sync";
|
|
5423
5590
|
const GIT_SYNC_KEYS = {
|
|
5424
|
-
location: `${STEP_ID$
|
|
5425
|
-
path: `${STEP_ID$
|
|
5426
|
-
remote: `${STEP_ID$
|
|
5427
|
-
url: `${STEP_ID$
|
|
5428
|
-
token: `${STEP_ID$
|
|
5591
|
+
location: `${STEP_ID$8}.location`,
|
|
5592
|
+
path: `${STEP_ID$8}.path`,
|
|
5593
|
+
remote: `${STEP_ID$8}.remote`,
|
|
5594
|
+
url: `${STEP_ID$8}.url`,
|
|
5595
|
+
token: `${STEP_ID$8}.token`
|
|
5429
5596
|
};
|
|
5430
5597
|
const BARE_REPO_NAME = "content.git";
|
|
5431
5598
|
const DEFAULT_BRANCH = "main";
|
|
@@ -5455,7 +5622,11 @@ const TERMINAL_TASK_STATUSES = /* @__PURE__ */ new Set([
|
|
|
5455
5622
|
"timed-out",
|
|
5456
5623
|
"conflict"
|
|
5457
5624
|
]);
|
|
5458
|
-
|
|
5625
|
+
const SYNC_MODE_SETTINGS = {
|
|
5626
|
+
"remote-sync-type": "read-write",
|
|
5627
|
+
"remote-sync-transforms": true
|
|
5628
|
+
};
|
|
5629
|
+
function hostDeps$12(ctx) {
|
|
5459
5630
|
return {
|
|
5460
5631
|
runner: processRunner(ctx.signal),
|
|
5461
5632
|
client: (baseUrl, auth) => createMetabaseClient(baseUrl, auth, ctx.signal),
|
|
@@ -5746,7 +5917,7 @@ async function configureLocalBare(run, runtime, workdir) {
|
|
|
5746
5917
|
},
|
|
5747
5918
|
settings: {
|
|
5748
5919
|
"remote-sync-url": bareUrlFor(runtime, barePath),
|
|
5749
|
-
|
|
5920
|
+
...SYNC_MODE_SETTINGS,
|
|
5750
5921
|
"remote-sync-branch": prepared.branch
|
|
5751
5922
|
}
|
|
5752
5923
|
};
|
|
@@ -5789,7 +5960,7 @@ async function configureHosted(run, workdir, url) {
|
|
|
5789
5960
|
settings: {
|
|
5790
5961
|
"remote-sync-url": url,
|
|
5791
5962
|
"remote-sync-token": token,
|
|
5792
|
-
|
|
5963
|
+
...SYNC_MODE_SETTINGS,
|
|
5793
5964
|
"remote-sync-branch": prepared.branch
|
|
5794
5965
|
}
|
|
5795
5966
|
};
|
|
@@ -5862,6 +6033,7 @@ async function importExistingContent(run, gitRemote) {
|
|
|
5862
6033
|
}
|
|
5863
6034
|
spinner.stop("Import finished");
|
|
5864
6035
|
reportImport(run, outcome);
|
|
6036
|
+
await run.client.remoteSync.syncTransforms();
|
|
5865
6037
|
}
|
|
5866
6038
|
const SHELL_SAFE = /^[\w./-]+$/;
|
|
5867
6039
|
function shellWord(value) {
|
|
@@ -5922,15 +6094,15 @@ async function runGitSync(ctx, deps) {
|
|
|
5922
6094
|
ctx.ui.note(usageLines$1(ctx.state, remote.gitRemote).join("\n"), "Working with the repository");
|
|
5923
6095
|
return "completed";
|
|
5924
6096
|
}
|
|
5925
|
-
function gitSyncStep(deps = hostDeps$
|
|
6097
|
+
function gitSyncStep(deps = hostDeps$12) {
|
|
5926
6098
|
return {
|
|
5927
|
-
id: STEP_ID$
|
|
6099
|
+
id: STEP_ID$8,
|
|
5928
6100
|
title: "Connect a git repository",
|
|
5929
6101
|
redoHint: "`rde init --only git-sync` with it points Metabase's remote sync at that repository instead.",
|
|
5930
6102
|
async detect(ctx) {
|
|
5931
6103
|
if (!hasRemoteSync(ctx.state.license)) return "not-applicable";
|
|
5932
|
-
if (ctx.state.steps[STEP_ID$
|
|
5933
|
-
if (ctx.state.gitRemote === null || ctx.state.steps[STEP_ID$
|
|
6104
|
+
if (ctx.state.steps[STEP_ID$8]?.status === "skipped") return "not-applicable";
|
|
6105
|
+
if (ctx.state.gitRemote === null || ctx.state.steps[STEP_ID$8]?.status !== "done") return "todo";
|
|
5934
6106
|
try {
|
|
5935
6107
|
return (await mbTool(deps(ctx).runner).gitSyncStatus(ctx.state.mbProfile)).branch === null ? "todo" : "done";
|
|
5936
6108
|
} catch (error) {
|
|
@@ -6071,12 +6243,12 @@ function skillsCli(runner) {
|
|
|
6071
6243
|
}
|
|
6072
6244
|
//#endregion
|
|
6073
6245
|
//#region src/steps/agents-skills.ts
|
|
6074
|
-
const STEP_ID$
|
|
6075
|
-
const AGENTS_SKILLS_KEYS = { agents: `${STEP_ID$
|
|
6246
|
+
const STEP_ID$7 = "agents-skills";
|
|
6247
|
+
const AGENTS_SKILLS_KEYS = { agents: `${STEP_ID$7}.agents` };
|
|
6076
6248
|
const MECHANICS_NOTE = "The rde skill's mechanics come from `mb skills get <name>` at runtime, so nothing else needs installing.";
|
|
6077
6249
|
const REQUIRED_SKILL_NAMES = [...DATA_APP_SKILL_NAMES, ...RDE_SOURCE_SKILL_NAMES];
|
|
6078
6250
|
const DETECT_AGENTS = { kind: "detected" };
|
|
6079
|
-
function hostDeps$
|
|
6251
|
+
function hostDeps$11(ctx) {
|
|
6080
6252
|
return {
|
|
6081
6253
|
runner: processRunner(ctx.signal),
|
|
6082
6254
|
probe: hostAgentProbe(),
|
|
@@ -6165,19 +6337,19 @@ async function selectAgents(ctx, all, options) {
|
|
|
6165
6337
|
function skipLine(all, source) {
|
|
6166
6338
|
switch (source) {
|
|
6167
6339
|
case "override":
|
|
6168
|
-
case "chosen": return `Skipping the skills; run \`rde init --only ${STEP_ID$
|
|
6169
|
-
case "detected": return `No coding agent found (${displayNames(all)}); run \`rde init --only ${STEP_ID$
|
|
6340
|
+
case "chosen": return `Skipping the skills; run \`rde init --only ${STEP_ID$7}\` to install it later`;
|
|
6341
|
+
case "detected": return `No coding agent found (${displayNames(all)}); run \`rde init --only ${STEP_ID$7} --agents <name>\` once one is installed`;
|
|
6170
6342
|
}
|
|
6171
6343
|
}
|
|
6172
|
-
function agentsSkillsStep(options, deps = hostDeps$
|
|
6344
|
+
function agentsSkillsStep(options, deps = hostDeps$11) {
|
|
6173
6345
|
return {
|
|
6174
|
-
id: STEP_ID$
|
|
6346
|
+
id: STEP_ID$7,
|
|
6175
6347
|
title: "Install the rde and data-app skills for your coding agents",
|
|
6176
6348
|
redoHint: "`rde init --only agents-skills` with it installs the skills for those agents.",
|
|
6177
6349
|
async detect(ctx) {
|
|
6178
6350
|
const resolved = deps(ctx);
|
|
6179
6351
|
const detected = detectedOnly(await detectAgents(resolved.probe));
|
|
6180
|
-
if (detected.length === 0) return ctx.state.steps[STEP_ID$
|
|
6352
|
+
if (detected.length === 0) return ctx.state.steps[STEP_ID$7]?.status === "skipped" ? "not-applicable" : "todo";
|
|
6181
6353
|
return missingSkills(await skillsCli(resolved.runner).list(), detected).length === 0 ? "done" : "todo";
|
|
6182
6354
|
},
|
|
6183
6355
|
async run(ctx) {
|
|
@@ -6217,7 +6389,7 @@ function agentsSkillsStep(options, deps = hostDeps$10) {
|
|
|
6217
6389
|
}
|
|
6218
6390
|
//#endregion
|
|
6219
6391
|
//#region src/steps/databases.ts
|
|
6220
|
-
const STEP_ID$
|
|
6392
|
+
const STEP_ID$6 = "databases";
|
|
6221
6393
|
const SAMPLE_STANDS_IN = { request: "sample-stands-in" };
|
|
6222
6394
|
function hostDatabasesDeps(signal) {
|
|
6223
6395
|
return {
|
|
@@ -6226,7 +6398,7 @@ function hostDatabasesDeps(signal) {
|
|
|
6226
6398
|
now: () => /* @__PURE__ */ new Date()
|
|
6227
6399
|
};
|
|
6228
6400
|
}
|
|
6229
|
-
function isNotServingYet$
|
|
6401
|
+
function isNotServingYet$2(error) {
|
|
6230
6402
|
return error instanceof MetabaseUnreachableError || error instanceof MetabaseInitializingError;
|
|
6231
6403
|
}
|
|
6232
6404
|
function requireApiKey$2(state, apiKey) {
|
|
@@ -6255,14 +6427,15 @@ function authenticatedClient(state, apiKey, deps) {
|
|
|
6255
6427
|
});
|
|
6256
6428
|
}
|
|
6257
6429
|
async function listDatabases(state, apiKey, deps) {
|
|
6258
|
-
const listed = await authenticatedClient(state, apiKey, deps).database.
|
|
6430
|
+
const listed = await authenticatedClient(state, apiKey, deps).database.listWithStubs();
|
|
6259
6431
|
const records = new Map(state.databases.map((record) => [record.id, record]));
|
|
6260
6432
|
const onServer = listed.data.map((database) => ({
|
|
6261
6433
|
id: database.id,
|
|
6262
6434
|
name: database.name,
|
|
6263
6435
|
engine: database.engine,
|
|
6264
6436
|
initialSyncStatus: database.initial_sync_status,
|
|
6265
|
-
createdAt: records.get(database.id)?.createdAt ?? null
|
|
6437
|
+
createdAt: records.get(database.id)?.createdAt ?? null,
|
|
6438
|
+
stub: isStub(database)
|
|
6266
6439
|
}));
|
|
6267
6440
|
const serverIds = new Set(listed.data.map((database) => database.id));
|
|
6268
6441
|
const missing = state.databases.filter((record) => !serverIds.has(record.id)).map((record) => ({
|
|
@@ -6270,24 +6443,27 @@ async function listDatabases(state, apiKey, deps) {
|
|
|
6270
6443
|
name: record.name,
|
|
6271
6444
|
engine: record.engine,
|
|
6272
6445
|
initialSyncStatus: null,
|
|
6273
|
-
createdAt: record.createdAt
|
|
6446
|
+
createdAt: record.createdAt,
|
|
6447
|
+
stub: false
|
|
6274
6448
|
}));
|
|
6275
6449
|
return [...onServer, ...missing];
|
|
6276
6450
|
}
|
|
6277
|
-
async function connectDatabases(ctx, deps,
|
|
6451
|
+
async function connectDatabases(ctx, deps, opening) {
|
|
6278
6452
|
const configured = requireConfiguredRuntime(ctx.state);
|
|
6279
6453
|
const client = authenticatedClient(ctx.state, ctx.secrets.apiKey, deps);
|
|
6280
|
-
const
|
|
6281
|
-
const
|
|
6282
|
-
|
|
6454
|
+
const engines = await client.setting.engines();
|
|
6455
|
+
const listed = await client.database.listWithStubs();
|
|
6456
|
+
const existing = listed.data.filter((database) => !isStub(database));
|
|
6457
|
+
if (existing.length > 0) ctx.ui.note(existingDatabaseLines(existing).join("\n"), "Connected databases");
|
|
6283
6458
|
const result = await runDatabaseFlow({
|
|
6284
|
-
|
|
6459
|
+
...opening,
|
|
6460
|
+
stubs: listed.data.filter(isStub),
|
|
6285
6461
|
prompts: ctx.prompts,
|
|
6286
6462
|
ui: ctx.ui,
|
|
6287
|
-
catalog: engineCatalog(
|
|
6463
|
+
catalog: engineCatalog(engines),
|
|
6288
6464
|
hostAlias: hostAliasOf(configured.runtime),
|
|
6289
6465
|
database: client.database,
|
|
6290
|
-
existingCount: existing.
|
|
6466
|
+
existingCount: existing.length,
|
|
6291
6467
|
readTextFile: deps.readTextFile,
|
|
6292
6468
|
onCreated: async (database) => {
|
|
6293
6469
|
const record = toRecord$1(database, deps.now().toISOString());
|
|
@@ -6297,27 +6473,34 @@ async function connectDatabases(ctx, deps, start) {
|
|
|
6297
6473
|
});
|
|
6298
6474
|
}
|
|
6299
6475
|
});
|
|
6476
|
+
if (result.stubsLeft.length > 0) ctx.ui.info(unconnectedStubsLine(result.stubsLeft));
|
|
6300
6477
|
return {
|
|
6301
|
-
existing: existing.
|
|
6478
|
+
existing: existing.length,
|
|
6302
6479
|
created: result.created.length,
|
|
6303
6480
|
added: result.added
|
|
6304
6481
|
};
|
|
6305
6482
|
}
|
|
6306
|
-
async function addDatabases(ctx, deps) {
|
|
6307
|
-
const connected = await connectDatabases(ctx, deps,
|
|
6483
|
+
async function addDatabases(ctx, deps, options) {
|
|
6484
|
+
const connected = await connectDatabases(ctx, deps, {
|
|
6485
|
+
start: "engine",
|
|
6486
|
+
...options
|
|
6487
|
+
});
|
|
6308
6488
|
return {
|
|
6309
6489
|
outcome: connected.existing + connected.created > 0 ? "completed" : "skipped",
|
|
6310
6490
|
result: { addedDatabases: connected.added }
|
|
6311
6491
|
};
|
|
6312
6492
|
}
|
|
6313
6493
|
async function askForOwnDatabase(ctx, deps) {
|
|
6314
|
-
const connected = await connectDatabases(ctx, deps,
|
|
6494
|
+
const connected = await connectDatabases(ctx, deps, {
|
|
6495
|
+
start: "connection-string",
|
|
6496
|
+
offerStubs: true
|
|
6497
|
+
});
|
|
6315
6498
|
return {
|
|
6316
6499
|
outcome: connected.created > 0 ? "completed" : "skipped",
|
|
6317
6500
|
result: { addedDatabases: connected.added }
|
|
6318
6501
|
};
|
|
6319
6502
|
}
|
|
6320
|
-
function hostDeps$
|
|
6503
|
+
function hostDeps$10(ctx) {
|
|
6321
6504
|
return {
|
|
6322
6505
|
...hostDatabasesDeps(ctx.signal),
|
|
6323
6506
|
instanceRunning: () => isInstanceRunning(ctx.state, hostRuntimeDeps(ctx.state.name, ctx.signal))
|
|
@@ -6327,15 +6510,15 @@ function sampleDatabaseId(state) {
|
|
|
6327
6510
|
return state.sampleDatabase === null ? null : state.sampleDatabase.databaseId;
|
|
6328
6511
|
}
|
|
6329
6512
|
function skippedBefore(state) {
|
|
6330
|
-
return state.steps[STEP_ID$
|
|
6513
|
+
return state.steps[STEP_ID$6]?.status === "skipped";
|
|
6331
6514
|
}
|
|
6332
6515
|
function ownDatabaseIds(state) {
|
|
6333
6516
|
const sampleId = sampleDatabaseId(state);
|
|
6334
6517
|
return new Set(state.databases.filter((record) => record.id !== sampleId).map((record) => record.id));
|
|
6335
6518
|
}
|
|
6336
|
-
function databasesStep(options, deps = hostDeps$
|
|
6519
|
+
function databasesStep(options, deps = hostDeps$10) {
|
|
6337
6520
|
return {
|
|
6338
|
-
id: STEP_ID$
|
|
6521
|
+
id: STEP_ID$6,
|
|
6339
6522
|
title: "Connect your databases",
|
|
6340
6523
|
redoHint: "Connect another with `rde db add --url <connection string>`.",
|
|
6341
6524
|
async detect(ctx) {
|
|
@@ -6358,14 +6541,14 @@ function databasesStep(options, deps = hostDeps$9) {
|
|
|
6358
6541
|
try {
|
|
6359
6542
|
return (await client.database.list()).data.some((database) => isOwn(database.id)) ? "done" : "todo";
|
|
6360
6543
|
} catch (error) {
|
|
6361
|
-
if (isNotServingYet$
|
|
6544
|
+
if (isNotServingYet$2(error) || isRejectedCredential(error)) return "todo";
|
|
6362
6545
|
throw error;
|
|
6363
6546
|
}
|
|
6364
6547
|
},
|
|
6365
6548
|
async run(ctx) {
|
|
6366
6549
|
switch (options.request) {
|
|
6367
6550
|
case "ask-once": return askForOwnDatabase(ctx, deps(ctx));
|
|
6368
|
-
case "named": return addDatabases(ctx, deps(ctx));
|
|
6551
|
+
case "named": return addDatabases(ctx, deps(ctx), { offerStubs: false });
|
|
6369
6552
|
case "sample-stands-in": return { outcome: "skipped" };
|
|
6370
6553
|
}
|
|
6371
6554
|
}
|
|
@@ -6373,7 +6556,7 @@ function databasesStep(options, deps = hostDeps$9) {
|
|
|
6373
6556
|
}
|
|
6374
6557
|
//#endregion
|
|
6375
6558
|
//#region src/steps/install-mb.ts
|
|
6376
|
-
const STEP_ID$
|
|
6559
|
+
const STEP_ID$5 = "install-mb";
|
|
6377
6560
|
const MIN_MB_VERSION = "0.3.0";
|
|
6378
6561
|
const MB_RANGE = `>=${MIN_MB_VERSION}`;
|
|
6379
6562
|
function installedWith(scriptPath) {
|
|
@@ -6382,7 +6565,7 @@ function installedWith(scriptPath) {
|
|
|
6382
6565
|
function rdeInstalledWith() {
|
|
6383
6566
|
return installedWith(process.argv[1]);
|
|
6384
6567
|
}
|
|
6385
|
-
function hostDeps$
|
|
6568
|
+
function hostDeps$9(ctx) {
|
|
6386
6569
|
return {
|
|
6387
6570
|
runner: processRunner(ctx.signal),
|
|
6388
6571
|
installedWith: rdeInstalledWith(),
|
|
@@ -6498,9 +6681,9 @@ async function stillWrong(install, after, deps) {
|
|
|
6498
6681
|
`${stillWrongAdvice(diagnosis)}.`
|
|
6499
6682
|
].join(" "));
|
|
6500
6683
|
}
|
|
6501
|
-
function installMbStep(deps = hostDeps$
|
|
6684
|
+
function installMbStep(deps = hostDeps$9) {
|
|
6502
6685
|
return {
|
|
6503
|
-
id: STEP_ID$
|
|
6686
|
+
id: STEP_ID$5,
|
|
6504
6687
|
title: "Install the Metabase CLI",
|
|
6505
6688
|
async detect(ctx) {
|
|
6506
6689
|
const found = await inspectMb(deps(ctx).runner);
|
|
@@ -6526,11 +6709,11 @@ function installMbStep(deps = hostDeps$8) {
|
|
|
6526
6709
|
}
|
|
6527
6710
|
//#endregion
|
|
6528
6711
|
//#region src/steps/license.ts
|
|
6529
|
-
const STEP_ID$
|
|
6530
|
-
const LICENSE_KEYS = { token: `${STEP_ID$
|
|
6712
|
+
const STEP_ID$4 = "license";
|
|
6713
|
+
const LICENSE_KEYS = { token: `${STEP_ID$4}.token` };
|
|
6531
6714
|
const TOKEN_PATTERN = /^[0-9a-f]{64}$/i;
|
|
6532
6715
|
const NOT_FOUND_STATUS$2 = 404;
|
|
6533
|
-
function hostDeps$
|
|
6716
|
+
function hostDeps$8(ctx) {
|
|
6534
6717
|
return {
|
|
6535
6718
|
client: (baseUrl, auth) => createMetabaseClient(baseUrl, auth, ctx.signal),
|
|
6536
6719
|
runner: processRunner(ctx.signal),
|
|
@@ -6553,13 +6736,13 @@ function activeLabel(status) {
|
|
|
6553
6736
|
const plan = status["plan-alias"];
|
|
6554
6737
|
return plan === void 0 ? "License active" : `License active (plan ${plan})`;
|
|
6555
6738
|
}
|
|
6556
|
-
function licenseStep(deps = hostDeps$
|
|
6739
|
+
function licenseStep(deps = hostDeps$8) {
|
|
6557
6740
|
return {
|
|
6558
|
-
id: STEP_ID$
|
|
6741
|
+
id: STEP_ID$4,
|
|
6559
6742
|
title: "Activate your license",
|
|
6560
6743
|
redoHint: "`rde init --from license` with it activates the license and runs the steps after it again.",
|
|
6561
6744
|
async detect(ctx) {
|
|
6562
|
-
if (ctx.state.steps[STEP_ID$
|
|
6745
|
+
if (ctx.state.steps[STEP_ID$4]?.status === "skipped") return "not-applicable";
|
|
6563
6746
|
const configured = configuredRuntime(ctx.state);
|
|
6564
6747
|
if (configured === null || ctx.secrets.apiKey === null) return "todo";
|
|
6565
6748
|
const resolved = deps(ctx);
|
|
@@ -6650,7 +6833,7 @@ const PLATFORMS = {
|
|
|
6650
6833
|
javaHint: `Install Java 21 or newer with your distribution's package manager (openjdk-21-jre) or from https://adoptium.net`
|
|
6651
6834
|
}
|
|
6652
6835
|
};
|
|
6653
|
-
function hostDeps$
|
|
6836
|
+
function hostDeps$7(ctx) {
|
|
6654
6837
|
const runner = processRunner(ctx.signal);
|
|
6655
6838
|
return {
|
|
6656
6839
|
platform: process.platform,
|
|
@@ -6772,7 +6955,7 @@ function checkLine(check) {
|
|
|
6772
6955
|
function checkLines(checks) {
|
|
6773
6956
|
return checks.map(checkLine);
|
|
6774
6957
|
}
|
|
6775
|
-
function preflightStep(deps = hostDeps$
|
|
6958
|
+
function preflightStep(deps = hostDeps$7) {
|
|
6776
6959
|
return {
|
|
6777
6960
|
id: "preflight",
|
|
6778
6961
|
title: "Check this machine",
|
|
@@ -6791,7 +6974,7 @@ function preflightStep(deps = hostDeps$6) {
|
|
|
6791
6974
|
}
|
|
6792
6975
|
//#endregion
|
|
6793
6976
|
//#region src/steps/runtime.ts
|
|
6794
|
-
const STEP_ID$
|
|
6977
|
+
const STEP_ID$3 = "runtime";
|
|
6795
6978
|
const MIN_PORT = 1;
|
|
6796
6979
|
const MAX_PORT = 65535;
|
|
6797
6980
|
const PORT_PATTERN = /^\d+$/;
|
|
@@ -6805,7 +6988,7 @@ const NO_RUNTIME_FLAGS = {
|
|
|
6805
6988
|
jar: null,
|
|
6806
6989
|
port: null
|
|
6807
6990
|
};
|
|
6808
|
-
function hostDeps$
|
|
6991
|
+
function hostDeps$6(ctx) {
|
|
6809
6992
|
return {
|
|
6810
6993
|
runtime: hostRuntimeDeps(ctx.state.name, ctx.signal),
|
|
6811
6994
|
releases: releaseLookup(ctx.signal)
|
|
@@ -6934,9 +7117,9 @@ async function persistedRuntime(ctx, options, deps) {
|
|
|
6934
7117
|
});
|
|
6935
7118
|
return chosen;
|
|
6936
7119
|
}
|
|
6937
|
-
function runtimeStep(options, deps = hostDeps$
|
|
7120
|
+
function runtimeStep(options, deps = hostDeps$6) {
|
|
6938
7121
|
return {
|
|
6939
|
-
id: STEP_ID$
|
|
7122
|
+
id: STEP_ID$3,
|
|
6940
7123
|
title: "Install Metabase",
|
|
6941
7124
|
redoHint: "Move it with `rde start --port`, or change its Metabase with `rde upgrade --to`.",
|
|
6942
7125
|
async detect(ctx) {
|
|
@@ -6962,11 +7145,11 @@ function runtimeStep(options, deps = hostDeps$5) {
|
|
|
6962
7145
|
}
|
|
6963
7146
|
//#endregion
|
|
6964
7147
|
//#region src/steps/sample-database.ts
|
|
6965
|
-
const STEP_ID$
|
|
7148
|
+
const STEP_ID$2 = "sample-database";
|
|
6966
7149
|
const SAMPLE_DATABASE_DISPLAY_NAME = "Sample Database";
|
|
6967
7150
|
const POSTGRES_ENGINE = "postgres";
|
|
6968
7151
|
const NOT_FOUND_STATUS$1 = 404;
|
|
6969
|
-
function hostDeps$
|
|
7152
|
+
function hostDeps$5(ctx) {
|
|
6970
7153
|
return {
|
|
6971
7154
|
runtime: hostRuntimeDeps(ctx.state.name, ctx.signal),
|
|
6972
7155
|
client: (baseUrl, auth) => createMetabaseClient(baseUrl, auth, ctx.signal),
|
|
@@ -6974,7 +7157,7 @@ function hostDeps$4(ctx) {
|
|
|
6974
7157
|
now: () => /* @__PURE__ */ new Date()
|
|
6975
7158
|
};
|
|
6976
7159
|
}
|
|
6977
|
-
function isNotServingYet(error) {
|
|
7160
|
+
function isNotServingYet$1(error) {
|
|
6978
7161
|
return error instanceof MetabaseUnreachableError || error instanceof MetabaseInitializingError;
|
|
6979
7162
|
}
|
|
6980
7163
|
function isNotFound$2(error) {
|
|
@@ -7117,11 +7300,11 @@ async function connectDatabase(ctx, choice, configured, client, deps) {
|
|
|
7117
7300
|
return database;
|
|
7118
7301
|
}
|
|
7119
7302
|
function usageLines(record) {
|
|
7120
|
-
return [`psql "${sampleDatabaseUrl(record.port)}"`, "Transforms
|
|
7303
|
+
return [`psql "${sampleDatabaseUrl(record.port)}"`, "Transforms can write to this database."].join("\n");
|
|
7121
7304
|
}
|
|
7122
|
-
function sampleDatabaseStep(deps = hostDeps$
|
|
7305
|
+
function sampleDatabaseStep(deps = hostDeps$5) {
|
|
7123
7306
|
return {
|
|
7124
|
-
id: STEP_ID$
|
|
7307
|
+
id: STEP_ID$2,
|
|
7125
7308
|
title: "Connect a sample database",
|
|
7126
7309
|
async detect(ctx) {
|
|
7127
7310
|
const resolved = deps(ctx);
|
|
@@ -7137,7 +7320,7 @@ function sampleDatabaseStep(deps = hostDeps$4) {
|
|
|
7137
7320
|
try {
|
|
7138
7321
|
return await recordedDatabase(record, client) === null ? "todo" : "done";
|
|
7139
7322
|
} catch (error) {
|
|
7140
|
-
if (isNotServingYet(error) || isRejectedCredential(error)) return "todo";
|
|
7323
|
+
if (isNotServingYet$1(error) || isRejectedCredential(error)) return "todo";
|
|
7141
7324
|
throw error;
|
|
7142
7325
|
}
|
|
7143
7326
|
},
|
|
@@ -7149,16 +7332,13 @@ function sampleDatabaseStep(deps = hostDeps$4) {
|
|
|
7149
7332
|
if (!docker.available) throw new ConfigError(`the sample database runs in Docker, which is not available: ${docker.reason}`);
|
|
7150
7333
|
const persisted = await persistedRecord(ctx, resolved);
|
|
7151
7334
|
const record = await installAndStart(ctx, sampleDatabaseContainer(persisted.record, resolved.runtime.docker, resolved.runtime.rdeHome), persisted.record, resolved);
|
|
7152
|
-
|
|
7335
|
+
await connectDatabase(ctx, {
|
|
7153
7336
|
...persisted,
|
|
7154
7337
|
record
|
|
7155
|
-
}
|
|
7156
|
-
const client = resolved.client(configured.url, {
|
|
7338
|
+
}, configured, resolved.client(configured.url, {
|
|
7157
7339
|
kind: "api-key",
|
|
7158
7340
|
key: apiKey
|
|
7159
|
-
});
|
|
7160
|
-
await connectDatabase(ctx, choice, configured, client, resolved);
|
|
7161
|
-
await client.setting.enableTransforms();
|
|
7341
|
+
}), resolved);
|
|
7162
7342
|
ctx.ui.note(usageLines(record), "Sample database");
|
|
7163
7343
|
return { outcome: "completed" };
|
|
7164
7344
|
}
|
|
@@ -7169,7 +7349,7 @@ const DEFAULT_HEALTH_TIMING = { timeoutMs: 3e5 };
|
|
|
7169
7349
|
//#region src/steps/start-metabase.ts
|
|
7170
7350
|
const LOG_TAIL_ON_TIMEOUT = 30;
|
|
7171
7351
|
const PERCENT = 100;
|
|
7172
|
-
function hostDeps$
|
|
7352
|
+
function hostDeps$4(ctx) {
|
|
7173
7353
|
return {
|
|
7174
7354
|
runtime: hostRuntimeDeps(ctx.state.name, ctx.signal),
|
|
7175
7355
|
client: (baseUrl, auth) => createMetabaseClient(baseUrl, auth, ctx.signal),
|
|
@@ -7213,7 +7393,7 @@ async function startRuntime(ctx, runtime, deps) {
|
|
|
7213
7393
|
throw error;
|
|
7214
7394
|
}
|
|
7215
7395
|
}
|
|
7216
|
-
function startMetabaseStep(deps = hostDeps$
|
|
7396
|
+
function startMetabaseStep(deps = hostDeps$4) {
|
|
7217
7397
|
return {
|
|
7218
7398
|
id: "start-metabase",
|
|
7219
7399
|
title: "Start Metabase",
|
|
@@ -7273,9 +7453,9 @@ function startMetabaseStep(deps = hostDeps$3) {
|
|
|
7273
7453
|
}
|
|
7274
7454
|
//#endregion
|
|
7275
7455
|
//#region src/steps/summary.ts
|
|
7276
|
-
const STEP_ID = "summary";
|
|
7456
|
+
const STEP_ID$1 = "summary";
|
|
7277
7457
|
const GIT_SYNC_STEP_ID = "git-sync";
|
|
7278
|
-
function hostDeps$
|
|
7458
|
+
function hostDeps$3(ctx) {
|
|
7279
7459
|
return {
|
|
7280
7460
|
client: (baseUrl, auth) => createMetabaseClient(baseUrl, auth, ctx.signal),
|
|
7281
7461
|
runner: processRunner(ctx.signal),
|
|
@@ -7299,14 +7479,21 @@ async function databaseLines$1(state, apiKey, deps) {
|
|
|
7299
7479
|
databases = (await deps.client(state.url, {
|
|
7300
7480
|
kind: "api-key",
|
|
7301
7481
|
key: apiKey
|
|
7302
|
-
}).database.
|
|
7482
|
+
}).database.listWithStubs()).data;
|
|
7303
7483
|
} catch (error) {
|
|
7304
7484
|
if (isNotServing$1(error)) return ["Databases: Metabase is not running; `rde start`"];
|
|
7305
7485
|
if (isRejectedCredential(error)) return ["Databases: Metabase rejects the stored API key; `rde init --only api-key`"];
|
|
7306
7486
|
throw error;
|
|
7307
7487
|
}
|
|
7308
|
-
|
|
7309
|
-
|
|
7488
|
+
const connected = databases.filter((database) => !isStub(database));
|
|
7489
|
+
const stubs = databases.filter(isStub);
|
|
7490
|
+
const stubLines = stubs.length === 0 ? [] : [unconnectedStubsLine(stubs)];
|
|
7491
|
+
if (connected.length === 0) return ["Databases: none connected yet; `rde db add`", ...stubLines];
|
|
7492
|
+
return [
|
|
7493
|
+
"Databases:",
|
|
7494
|
+
...existingDatabaseLines(connected).map((line) => ` ${line}`),
|
|
7495
|
+
...stubLines
|
|
7496
|
+
];
|
|
7310
7497
|
}
|
|
7311
7498
|
function remoteLine(remote) {
|
|
7312
7499
|
switch (remote.kind) {
|
|
@@ -7353,9 +7540,9 @@ async function summaryLines(state, apiKey, deps) {
|
|
|
7353
7540
|
lines.push("Lifecycle: rde status (--json for scripts), rde stop, rde start");
|
|
7354
7541
|
return lines;
|
|
7355
7542
|
}
|
|
7356
|
-
function summaryStep(deps = hostDeps$
|
|
7543
|
+
function summaryStep(deps = hostDeps$3) {
|
|
7357
7544
|
return {
|
|
7358
|
-
id: STEP_ID,
|
|
7545
|
+
id: STEP_ID$1,
|
|
7359
7546
|
title: "Summary",
|
|
7360
7547
|
async detect() {
|
|
7361
7548
|
return "todo";
|
|
@@ -7368,6 +7555,55 @@ function summaryStep(deps = hostDeps$2) {
|
|
|
7368
7555
|
};
|
|
7369
7556
|
}
|
|
7370
7557
|
//#endregion
|
|
7558
|
+
//#region src/steps/transforms.ts
|
|
7559
|
+
const STEP_ID = "transforms";
|
|
7560
|
+
function hostDeps$2(ctx) {
|
|
7561
|
+
return {
|
|
7562
|
+
client: (baseUrl, auth) => createMetabaseClient(baseUrl, auth, ctx.signal),
|
|
7563
|
+
instanceRunning: () => isInstanceRunning(ctx.state, hostRuntimeDeps(ctx.state.name, ctx.signal))
|
|
7564
|
+
};
|
|
7565
|
+
}
|
|
7566
|
+
function isNotServingYet(error) {
|
|
7567
|
+
return error instanceof MetabaseUnreachableError || error instanceof MetabaseInitializingError;
|
|
7568
|
+
}
|
|
7569
|
+
function transformsStep(deps = hostDeps$2) {
|
|
7570
|
+
return {
|
|
7571
|
+
id: STEP_ID,
|
|
7572
|
+
title: "Enable transforms",
|
|
7573
|
+
async detect(ctx) {
|
|
7574
|
+
const configured = configuredRuntime(ctx.state);
|
|
7575
|
+
if (configured === null || ctx.secrets.apiKey === null) return "todo";
|
|
7576
|
+
const resolved = deps(ctx);
|
|
7577
|
+
if (!await resolved.instanceRunning()) return "todo";
|
|
7578
|
+
const client = resolved.client(configured.url, {
|
|
7579
|
+
kind: "api-key",
|
|
7580
|
+
key: ctx.secrets.apiKey
|
|
7581
|
+
});
|
|
7582
|
+
try {
|
|
7583
|
+
return await client.setting.transformsDecided() ? "done" : "todo";
|
|
7584
|
+
} catch (error) {
|
|
7585
|
+
if (isNotServingYet(error) || isRejectedCredential(error)) return "todo";
|
|
7586
|
+
throw error;
|
|
7587
|
+
}
|
|
7588
|
+
},
|
|
7589
|
+
async run(ctx) {
|
|
7590
|
+
const configured = requireConfiguredRuntime(ctx.state);
|
|
7591
|
+
if (ctx.secrets.apiKey === null) throw new ConfigError("no API key yet; the api-key step comes first");
|
|
7592
|
+
const client = deps(ctx).client(configured.url, {
|
|
7593
|
+
kind: "api-key",
|
|
7594
|
+
key: ctx.secrets.apiKey
|
|
7595
|
+
});
|
|
7596
|
+
if (await client.setting.transformsDecided()) {
|
|
7597
|
+
ctx.ui.info("Transforms are already set in Metabase; leaving them as they are");
|
|
7598
|
+
return { outcome: "completed" };
|
|
7599
|
+
}
|
|
7600
|
+
await client.setting.enableTransforms();
|
|
7601
|
+
ctx.ui.success("Transforms enabled");
|
|
7602
|
+
return { outcome: "completed" };
|
|
7603
|
+
}
|
|
7604
|
+
};
|
|
7605
|
+
}
|
|
7606
|
+
//#endregion
|
|
7371
7607
|
//#region src/steps/index.ts
|
|
7372
7608
|
const DEFAULT_STEP_OPTIONS = {
|
|
7373
7609
|
runtime: NO_RUNTIME_FLAGS,
|
|
@@ -7383,6 +7619,7 @@ const STEP_BY_ID = {
|
|
|
7383
7619
|
"start-metabase": () => startMetabaseStep(),
|
|
7384
7620
|
"admin-user": (options) => adminUserStep(options.admin),
|
|
7385
7621
|
"api-key": () => apiKeyStep(),
|
|
7622
|
+
transforms: () => transformsStep(),
|
|
7386
7623
|
"sample-database": () => sampleDatabaseStep(),
|
|
7387
7624
|
license: () => licenseStep(),
|
|
7388
7625
|
databases: (options) => databasesStep(options.databases),
|
|
@@ -8318,7 +8555,7 @@ const dbAdd = defineRdeCommand({
|
|
|
8318
8555
|
await addDatabases(await createWizardContext(name, {
|
|
8319
8556
|
signal: interruptSignal,
|
|
8320
8557
|
prompts
|
|
8321
|
-
}), hostDatabasesDeps(interruptSignal));
|
|
8558
|
+
}), hostDatabasesDeps(interruptSignal), { offerStubs: args.url === void 0 });
|
|
8322
8559
|
}
|
|
8323
8560
|
});
|
|
8324
8561
|
//#endregion
|
|
@@ -9360,7 +9597,8 @@ function applyDatabaseRecord(draft, record) {
|
|
|
9360
9597
|
const value = stringOf(record.answer);
|
|
9361
9598
|
const skippedConnection = record.key === DATABASE_KEYS.connection && value?.trim() === "";
|
|
9362
9599
|
const finished = parseDatabaseKey(record.key)?.name === "engine" && value === "finish";
|
|
9363
|
-
|
|
9600
|
+
const pickedNoStub = record.key === DATABASE_KEYS.stubs && record.answer.kind === "multiselect" && record.answer.values.length === 0;
|
|
9601
|
+
if (skippedConnection || finished || pickedNoStub) draft.databasesFinished = true;
|
|
9364
9602
|
}
|
|
9365
9603
|
function applyAdminRecord(draft, key, value) {
|
|
9366
9604
|
if (key === ADMIN_USER_KEYS.email && value !== null) admin(draft).email = value;
|
|
@@ -10737,7 +10975,7 @@ const upgradeCommand = defineRdeCommand({
|
|
|
10737
10975
|
const main = defineCommand({
|
|
10738
10976
|
meta: {
|
|
10739
10977
|
name: "rde",
|
|
10740
|
-
version: "0.1.
|
|
10978
|
+
version: "0.1.13",
|
|
10741
10979
|
description: "Prepares a local machine for data-engineering work with Metabase and coding agents"
|
|
10742
10980
|
},
|
|
10743
10981
|
subCommands: {
|