@metabase/rde 0.1.8 → 0.1.10
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 +6 -32
- package/dist/cli.mjs +433 -263
- package/package.json +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -1600,6 +1600,10 @@ async function run$1(signal, command, args, options) {
|
|
|
1600
1600
|
"pipe"
|
|
1601
1601
|
],
|
|
1602
1602
|
...options.cwd !== void 0 && { cwd: options.cwd },
|
|
1603
|
+
...options.addedEnv !== void 0 && { env: {
|
|
1604
|
+
...hostEnvironment(),
|
|
1605
|
+
...options.addedEnv
|
|
1606
|
+
} },
|
|
1603
1607
|
signal: combined,
|
|
1604
1608
|
killSignal: "SIGKILL"
|
|
1605
1609
|
});
|
|
@@ -2118,9 +2122,9 @@ function adminUserStep(options, deps = hostDeps$12) {
|
|
|
2118
2122
|
const client = resolved.client(configured.url, NO_AUTH);
|
|
2119
2123
|
const properties = await client.session.properties();
|
|
2120
2124
|
if (properties["has-user-setup"]) {
|
|
2121
|
-
if (ctx.state.admin === null) return recordExistingAdmin(ctx, options.variant, client, configured.url);
|
|
2125
|
+
if (ctx.state.admin === null) return { outcome: await recordExistingAdmin(ctx, options.variant, client, configured.url) };
|
|
2122
2126
|
ctx.ui.info("Metabase already has an admin user");
|
|
2123
|
-
return "completed";
|
|
2127
|
+
return { outcome: "completed" };
|
|
2124
2128
|
}
|
|
2125
2129
|
const token = properties["setup-token"];
|
|
2126
2130
|
if (token === null) throw new ConfigError("Metabase has no admin user yet but offers no setup token");
|
|
@@ -2159,7 +2163,7 @@ function adminUserStep(options, deps = hostDeps$12) {
|
|
|
2159
2163
|
}
|
|
2160
2164
|
});
|
|
2161
2165
|
ctx.ui.success(`Admin user ${user.email} created`);
|
|
2162
|
-
return "completed";
|
|
2166
|
+
return { outcome: "completed" };
|
|
2163
2167
|
}
|
|
2164
2168
|
};
|
|
2165
2169
|
}
|
|
@@ -2635,7 +2639,7 @@ function apiKeyStep(deps = hostDeps$11) {
|
|
|
2635
2639
|
if (entry === void 0) throw new ConfigError(`\`mb auth list\` does not show profile ${profile} after login`);
|
|
2636
2640
|
if (entry.status !== "ok") throw new ConfigError(`mb profile ${profile} reports ${entry.status} after login`);
|
|
2637
2641
|
ctx.ui.note(usageNote(profile), "Metabase CLI connected");
|
|
2638
|
-
return "completed";
|
|
2642
|
+
return { outcome: "completed" };
|
|
2639
2643
|
}
|
|
2640
2644
|
};
|
|
2641
2645
|
}
|
|
@@ -2822,12 +2826,18 @@ const MAPPERS = {
|
|
|
2822
2826
|
...leftoverOptions(parsed, ["db", "warehouse"])
|
|
2823
2827
|
});
|
|
2824
2828
|
},
|
|
2825
|
-
sparksql: (parsed) =>
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2829
|
+
sparksql: (parsed) => {
|
|
2830
|
+
const user = paramIgnoringCase(parsed, ["user"]);
|
|
2831
|
+
const password = paramIgnoringCase(parsed, ["password"]);
|
|
2832
|
+
return build(parsed.database, {
|
|
2833
|
+
host: parsed.host ?? param(parsed, "Server"),
|
|
2834
|
+
port: portNumber(parsed.port),
|
|
2835
|
+
dbname: parsed.database,
|
|
2836
|
+
user: user?.value ?? null,
|
|
2837
|
+
password: password?.value ?? null,
|
|
2838
|
+
...leftoverOptions(parsed, ["Server", ...keysOf(user, password)], "jdbc-flags")
|
|
2839
|
+
});
|
|
2840
|
+
},
|
|
2831
2841
|
sqlite: (parsed) => build(null, { db: parsed.path }),
|
|
2832
2842
|
sqlserver: (parsed) => {
|
|
2833
2843
|
const database = parsed.database ?? param(parsed, "databaseName");
|
|
@@ -2874,9 +2884,12 @@ const MAPPERS = {
|
|
|
2874
2884
|
...leftoverOptions(parsed, ["user", "password"])
|
|
2875
2885
|
})
|
|
2876
2886
|
};
|
|
2887
|
+
const DRUID_SCHEME = /url=(?<scheme>https?:\/\/)/i;
|
|
2888
|
+
const DRUID_DEFAULT_SCHEME = "http://";
|
|
2877
2889
|
function mapDruid(parsed) {
|
|
2890
|
+
const scheme = DRUID_SCHEME.exec(parsed.uri)?.groups?.["scheme"] ?? DRUID_DEFAULT_SCHEME;
|
|
2878
2891
|
return build(null, {
|
|
2879
|
-
host: parsed.host
|
|
2892
|
+
host: parsed.host === null ? null : `${scheme}${parsed.host}`,
|
|
2880
2893
|
port: portNumber(parsed.port)
|
|
2881
2894
|
});
|
|
2882
2895
|
}
|
|
@@ -3266,6 +3279,43 @@ function withoutUnasked(fields, details) {
|
|
|
3266
3279
|
function promptableFields(fields, details) {
|
|
3267
3280
|
return visibleFields(fields, details).filter(isPromptable);
|
|
3268
3281
|
}
|
|
3282
|
+
const SECRET_KINDS$1 = /* @__PURE__ */ new Set(["password", "file"]);
|
|
3283
|
+
function answerOf(field, details) {
|
|
3284
|
+
const kind = promptKindFor(field);
|
|
3285
|
+
const current = details[field.name];
|
|
3286
|
+
if (kind === "boolean") {
|
|
3287
|
+
const value = typeof current === "boolean" ? current : Boolean(field.default);
|
|
3288
|
+
return {
|
|
3289
|
+
name: field.name,
|
|
3290
|
+
value,
|
|
3291
|
+
secret: false
|
|
3292
|
+
};
|
|
3293
|
+
}
|
|
3294
|
+
if (kind === "select") {
|
|
3295
|
+
const value = current ?? field.default;
|
|
3296
|
+
return value === void 0 || value === null ? null : {
|
|
3297
|
+
name: field.name,
|
|
3298
|
+
value: String(value),
|
|
3299
|
+
secret: false
|
|
3300
|
+
};
|
|
3301
|
+
}
|
|
3302
|
+
if (current === void 0 || current === "") return {
|
|
3303
|
+
name: field.name,
|
|
3304
|
+
value: "",
|
|
3305
|
+
secret: false
|
|
3306
|
+
};
|
|
3307
|
+
return {
|
|
3308
|
+
name: field.name,
|
|
3309
|
+
value: current,
|
|
3310
|
+
secret: SECRET_KINDS$1.has(kind)
|
|
3311
|
+
};
|
|
3312
|
+
}
|
|
3313
|
+
function answeredFields(fields, details) {
|
|
3314
|
+
return promptableFields(fields, details).flatMap((field) => {
|
|
3315
|
+
const answer = answerOf(field, details);
|
|
3316
|
+
return answer === null ? [] : [answer];
|
|
3317
|
+
});
|
|
3318
|
+
}
|
|
3269
3319
|
async function fillRequiredFields(deps, fields, details) {
|
|
3270
3320
|
let current = details;
|
|
3271
3321
|
const asked = /* @__PURE__ */ new Set();
|
|
@@ -3525,18 +3575,45 @@ function databaseListingLines(listing) {
|
|
|
3525
3575
|
//#region src/steps/databases/flow.ts
|
|
3526
3576
|
const DATABASE_KEYS = {
|
|
3527
3577
|
connection: "databases.connection",
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
|
|
3532
|
-
|
|
3533
|
-
|
|
3534
|
-
|
|
3535
|
-
|
|
3536
|
-
|
|
3537
|
-
|
|
3538
|
-
|
|
3578
|
+
skip: "databases.skip"
|
|
3579
|
+
};
|
|
3580
|
+
const DATABASE_KEY_NAMES = {
|
|
3581
|
+
detectedEngine: "detected-engine",
|
|
3582
|
+
engine: "engine",
|
|
3583
|
+
mode: "mode",
|
|
3584
|
+
uri: "uri",
|
|
3585
|
+
name: "name",
|
|
3586
|
+
action: "action",
|
|
3587
|
+
editField: "edit-field",
|
|
3588
|
+
testAction: "test-action",
|
|
3589
|
+
switchEngine: "switch-engine",
|
|
3590
|
+
another: "another"
|
|
3539
3591
|
};
|
|
3592
|
+
function sameDetails(left, right) {
|
|
3593
|
+
return [.../* @__PURE__ */ new Set([...Object.keys(left), ...Object.keys(right)])].every((key) => left[key] === right[key]);
|
|
3594
|
+
}
|
|
3595
|
+
function addedDatabase(chosen, model, name) {
|
|
3596
|
+
const pastedAsIs = model.uri !== null && model.pasted !== null && sameDetails(model.details, model.pasted);
|
|
3597
|
+
return {
|
|
3598
|
+
engine: chosen.engine.key,
|
|
3599
|
+
name,
|
|
3600
|
+
uri: pastedAsIs ? model.uri : null,
|
|
3601
|
+
fields: pastedAsIs ? [] : answeredFields(chosen.fields, model.details)
|
|
3602
|
+
};
|
|
3603
|
+
}
|
|
3604
|
+
function databaseKey(ordinal, name) {
|
|
3605
|
+
return `databases.${ordinal}.${DATABASE_KEY_NAMES[name]}`;
|
|
3606
|
+
}
|
|
3607
|
+
const DATABASE_KEY = /^databases\.(?<ordinal>\d+)\.(?<name>[a-z-]+)$/;
|
|
3608
|
+
function parseDatabaseKey(key) {
|
|
3609
|
+
const groups = DATABASE_KEY.exec(key)?.groups;
|
|
3610
|
+
const ordinal = groups?.["ordinal"];
|
|
3611
|
+
const name = groups?.["name"];
|
|
3612
|
+
return ordinal === void 0 || name === void 0 ? null : {
|
|
3613
|
+
ordinal: Number(ordinal),
|
|
3614
|
+
name
|
|
3615
|
+
};
|
|
3616
|
+
}
|
|
3540
3617
|
const FINISH = "finish";
|
|
3541
3618
|
const RETRY = "try-again";
|
|
3542
3619
|
const BAD_REQUEST_STATUS$1 = 400;
|
|
@@ -3558,13 +3635,16 @@ const FRESH$1 = {
|
|
|
3558
3635
|
name: null,
|
|
3559
3636
|
fieldErrors: null
|
|
3560
3637
|
};
|
|
3638
|
+
function ordinalOf(run) {
|
|
3639
|
+
return run.created.length + 1;
|
|
3640
|
+
}
|
|
3561
3641
|
function fieldDeps(run) {
|
|
3562
3642
|
const { prompts, ui, readTextFile } = run.deps;
|
|
3563
3643
|
return {
|
|
3564
3644
|
prompts,
|
|
3565
3645
|
ui,
|
|
3566
3646
|
readTextFile,
|
|
3567
|
-
ordinal: run
|
|
3647
|
+
ordinal: ordinalOf(run)
|
|
3568
3648
|
};
|
|
3569
3649
|
}
|
|
3570
3650
|
async function orFailure(failure, ask) {
|
|
@@ -3612,7 +3692,7 @@ function isCreatableEntry(entry) {
|
|
|
3612
3692
|
}
|
|
3613
3693
|
async function pickDetectedEngine(run, candidates) {
|
|
3614
3694
|
const picked = await run.deps.prompts.selectOrBack({
|
|
3615
|
-
key:
|
|
3695
|
+
key: databaseKey(ordinalOf(run), "detectedEngine"),
|
|
3616
3696
|
message: "Which database is it?",
|
|
3617
3697
|
choices: candidates.map((entry) => ({
|
|
3618
3698
|
value: entry.key,
|
|
@@ -3679,7 +3759,7 @@ function pickEngineScreen(run) {
|
|
|
3679
3759
|
};
|
|
3680
3760
|
for (;;) {
|
|
3681
3761
|
const picked = await run.deps.prompts.select({
|
|
3682
|
-
key:
|
|
3762
|
+
key: databaseKey(ordinalOf(run), "engine"),
|
|
3683
3763
|
message: "Which database engine?",
|
|
3684
3764
|
choices: [...run.deps.catalog.list().map(engineChoice), finishChoice],
|
|
3685
3765
|
...model.engine !== null && { initialValue: model.engine.key }
|
|
@@ -3720,7 +3800,7 @@ async function chooseAfterParseError(run, engine, uri, error) {
|
|
|
3720
3800
|
hint: "this looks like one of its connection strings"
|
|
3721
3801
|
}));
|
|
3722
3802
|
const picked = await orFailure(error.message, () => run.deps.prompts.selectOrBack({
|
|
3723
|
-
key:
|
|
3803
|
+
key: databaseKey(ordinalOf(run), "switchEngine"),
|
|
3724
3804
|
message: "What now?",
|
|
3725
3805
|
choices: [...switches, {
|
|
3726
3806
|
value: RETRY,
|
|
@@ -3774,7 +3854,7 @@ function pasteUriScreen(run) {
|
|
|
3774
3854
|
for (;;) {
|
|
3775
3855
|
if (engine.exampleUri === null) return manualEntry(run, model, engine);
|
|
3776
3856
|
const mode = await run.deps.prompts.selectOrBack({
|
|
3777
|
-
key:
|
|
3857
|
+
key: databaseKey(ordinalOf(run), "mode"),
|
|
3778
3858
|
message: `How do you want to connect to ${engine.displayName}?`,
|
|
3779
3859
|
choices: [{
|
|
3780
3860
|
value: "paste",
|
|
@@ -3787,7 +3867,7 @@ function pasteUriScreen(run) {
|
|
|
3787
3867
|
if (mode === BACK) return { type: "back" };
|
|
3788
3868
|
if (mode === "manual") return manualEntry(run, model, engine);
|
|
3789
3869
|
const uri = await run.deps.prompts.textOrBack({
|
|
3790
|
-
key:
|
|
3870
|
+
key: databaseKey(ordinalOf(run), "uri"),
|
|
3791
3871
|
message: "Connection string",
|
|
3792
3872
|
placeholder: engine.exampleUri,
|
|
3793
3873
|
...draft !== null && { initialValue: draft }
|
|
@@ -3852,7 +3932,7 @@ function reviewNote(run, chosen, model) {
|
|
|
3852
3932
|
async function promptName(run, model, engine) {
|
|
3853
3933
|
const suggested = model.name ?? model.suggestedName ?? engine.displayName;
|
|
3854
3934
|
return run.deps.prompts.text({
|
|
3855
|
-
key:
|
|
3935
|
+
key: databaseKey(ordinalOf(run), "name"),
|
|
3856
3936
|
message: "Display name",
|
|
3857
3937
|
defaultValue: suggested,
|
|
3858
3938
|
placeholder: suggested
|
|
@@ -3869,7 +3949,7 @@ async function editOneField(run, fields, details) {
|
|
|
3869
3949
|
};
|
|
3870
3950
|
});
|
|
3871
3951
|
const picked = await run.deps.prompts.selectOrBack({
|
|
3872
|
-
key:
|
|
3952
|
+
key: databaseKey(ordinalOf(run), "editField"),
|
|
3873
3953
|
message: "Which field?",
|
|
3874
3954
|
choices
|
|
3875
3955
|
});
|
|
@@ -3912,7 +3992,7 @@ function reviewAttempt(run) {
|
|
|
3912
3992
|
reviewNote(run, chosen, current);
|
|
3913
3993
|
if (name === null) name = await promptName(run, current, chosen.engine);
|
|
3914
3994
|
const action = await run.deps.prompts.selectOrBack({
|
|
3915
|
-
key:
|
|
3995
|
+
key: databaseKey(ordinalOf(run), "action"),
|
|
3916
3996
|
message: `Add ${name}?`,
|
|
3917
3997
|
choices: [
|
|
3918
3998
|
{
|
|
@@ -3977,7 +4057,7 @@ function testScreen(run) {
|
|
|
3977
4057
|
const lines = fieldErrorLines(chosen.fields, errors);
|
|
3978
4058
|
if (lines.length > 0) run.deps.ui.warn(lines.join("\n"));
|
|
3979
4059
|
const action = await orFailure(failure, () => run.deps.prompts.selectOrBack({
|
|
3980
|
-
key:
|
|
4060
|
+
key: databaseKey(ordinalOf(run), "testAction"),
|
|
3981
4061
|
message: "What now?",
|
|
3982
4062
|
choices: [{
|
|
3983
4063
|
value: "fix",
|
|
@@ -4038,6 +4118,7 @@ function createScreen(run) {
|
|
|
4038
4118
|
}
|
|
4039
4119
|
run.created.push(database);
|
|
4040
4120
|
spinner.stop(`Added ${database.name}. Initial sync is running in the background; \`rde status\` shows progress.`);
|
|
4121
|
+
run.added.push(addedDatabase(chosen, model, model.name));
|
|
4041
4122
|
await run.deps.onCreated(database);
|
|
4042
4123
|
return {
|
|
4043
4124
|
type: "next",
|
|
@@ -4049,7 +4130,7 @@ function createScreen(run) {
|
|
|
4049
4130
|
function anotherScreen(run) {
|
|
4050
4131
|
return async (model) => {
|
|
4051
4132
|
if (await run.deps.prompts.select({
|
|
4052
|
-
key:
|
|
4133
|
+
key: databaseKey(run.created.length, "another"),
|
|
4053
4134
|
message: "Anything else?",
|
|
4054
4135
|
choices: [{
|
|
4055
4136
|
value: "another",
|
|
@@ -4072,7 +4153,8 @@ function anotherScreen(run) {
|
|
|
4072
4153
|
async function runDatabaseFlow(deps) {
|
|
4073
4154
|
const run = {
|
|
4074
4155
|
deps,
|
|
4075
|
-
created: []
|
|
4156
|
+
created: [],
|
|
4157
|
+
added: []
|
|
4076
4158
|
};
|
|
4077
4159
|
await runFlow({
|
|
4078
4160
|
"connection-string": connectionStringScreen(run),
|
|
@@ -4083,7 +4165,10 @@ async function runDatabaseFlow(deps) {
|
|
|
4083
4165
|
create: createScreen(run),
|
|
4084
4166
|
another: anotherScreen(run)
|
|
4085
4167
|
}, START_SCREEN[deps.start], FRESH$1);
|
|
4086
|
-
return {
|
|
4168
|
+
return {
|
|
4169
|
+
created: run.created,
|
|
4170
|
+
added: run.added
|
|
4171
|
+
};
|
|
4087
4172
|
}
|
|
4088
4173
|
//#endregion
|
|
4089
4174
|
//#region src/tools/docker.ts
|
|
@@ -4131,9 +4216,9 @@ function createArgs(options) {
|
|
|
4131
4216
|
"--label",
|
|
4132
4217
|
`${HOME_LABEL}=${options.home}`,
|
|
4133
4218
|
"-p",
|
|
4134
|
-
`${LOOPBACK_HOST}:${options.port.host}:${options.port.container}`
|
|
4219
|
+
`${LOOPBACK_HOST}:${options.port.host ?? ""}:${options.port.container}`
|
|
4135
4220
|
];
|
|
4136
|
-
if (options.port.alsoOn !== null) args.push("-p", `${options.port.alsoOn}:${options.port.host}:${options.port.container}`);
|
|
4221
|
+
if (options.port.alsoOn !== null) args.push("-p", `${options.port.alsoOn}:${options.port.host ?? ""}:${options.port.container}`);
|
|
4137
4222
|
if (options.mount !== null) args.push("-v", `${options.mount.source}:${options.mount.target}`);
|
|
4138
4223
|
for (const [name, value] of Object.entries(options.env)) args.push("-e", `${name}=${value}`);
|
|
4139
4224
|
if (options.hostUser !== null) {
|
|
@@ -4244,6 +4329,33 @@ function dockerTool(runner) {
|
|
|
4244
4329
|
async function containerRemove(name) {
|
|
4245
4330
|
await docker(["rm", name]);
|
|
4246
4331
|
}
|
|
4332
|
+
async function containerForceRemove(name) {
|
|
4333
|
+
await docker([
|
|
4334
|
+
"rm",
|
|
4335
|
+
"-f",
|
|
4336
|
+
name
|
|
4337
|
+
]);
|
|
4338
|
+
}
|
|
4339
|
+
async function containerLoopbackPort(name, containerPort) {
|
|
4340
|
+
const args = [
|
|
4341
|
+
"port",
|
|
4342
|
+
name,
|
|
4343
|
+
`${containerPort}/tcp`
|
|
4344
|
+
];
|
|
4345
|
+
const result = await docker(args);
|
|
4346
|
+
const prefix = `${LOOPBACK_HOST}:`;
|
|
4347
|
+
const line = result.stdout.split("\n").find((entry) => entry.startsWith(prefix));
|
|
4348
|
+
if (line !== void 0) {
|
|
4349
|
+
const port = Number.parseInt(line.slice(prefix.length), 10);
|
|
4350
|
+
if (Number.isInteger(port)) return port;
|
|
4351
|
+
}
|
|
4352
|
+
throw new ExternalToolError({
|
|
4353
|
+
tool: DOCKER,
|
|
4354
|
+
argv: args,
|
|
4355
|
+
exitCode: 0,
|
|
4356
|
+
stderrTail: `no port published on ${LOOPBACK_HOST} in: ${result.stdout.trim()}`
|
|
4357
|
+
});
|
|
4358
|
+
}
|
|
4247
4359
|
async function containerExecSucceeds(name, command) {
|
|
4248
4360
|
return (await runner.run(DOCKER, [
|
|
4249
4361
|
"exec",
|
|
@@ -4270,6 +4382,8 @@ function dockerTool(runner) {
|
|
|
4270
4382
|
containerStart,
|
|
4271
4383
|
containerStop,
|
|
4272
4384
|
containerRemove,
|
|
4385
|
+
containerForceRemove,
|
|
4386
|
+
containerLoopbackPort,
|
|
4273
4387
|
containerExecSucceeds,
|
|
4274
4388
|
containerLogs
|
|
4275
4389
|
};
|
|
@@ -4294,6 +4408,21 @@ const METABASE_CONTENT_DIRS = [
|
|
|
4294
4408
|
"snippets",
|
|
4295
4409
|
"transforms"
|
|
4296
4410
|
];
|
|
4411
|
+
const TOKEN_USERNAMES = { "bitbucket.org": "x-token-auth" };
|
|
4412
|
+
const DEFAULT_TOKEN_USERNAME = "x-access-token";
|
|
4413
|
+
function credentialEnv(credentials) {
|
|
4414
|
+
const { origin, hostname } = new URL(credentials.url);
|
|
4415
|
+
const username = TOKEN_USERNAMES[hostname] ?? DEFAULT_TOKEN_USERNAME;
|
|
4416
|
+
const basic = Buffer.from(`${username}:${credentials.token}`).toString("base64");
|
|
4417
|
+
return {
|
|
4418
|
+
GIT_CONFIG_COUNT: "2",
|
|
4419
|
+
GIT_CONFIG_KEY_0: `http.${origin}/.extraHeader`,
|
|
4420
|
+
GIT_CONFIG_VALUE_0: `Authorization: Basic ${basic}`,
|
|
4421
|
+
GIT_CONFIG_KEY_1: "credential.helper",
|
|
4422
|
+
GIT_CONFIG_VALUE_1: "",
|
|
4423
|
+
GIT_TERMINAL_PROMPT: "0"
|
|
4424
|
+
};
|
|
4425
|
+
}
|
|
4297
4426
|
async function directoryExists(path) {
|
|
4298
4427
|
try {
|
|
4299
4428
|
return (await promises.stat(path)).isDirectory();
|
|
@@ -4302,8 +4431,8 @@ async function directoryExists(path) {
|
|
|
4302
4431
|
}
|
|
4303
4432
|
}
|
|
4304
4433
|
function gitTool(runner) {
|
|
4305
|
-
async function git(args,
|
|
4306
|
-
const result = await runner.run(GIT, args,
|
|
4434
|
+
async function git(args, options = {}) {
|
|
4435
|
+
const result = await runner.run(GIT, args, options);
|
|
4307
4436
|
if (result.exitCode !== 0) throw toolFailure(GIT, args, result);
|
|
4308
4437
|
return result;
|
|
4309
4438
|
}
|
|
@@ -4349,7 +4478,7 @@ function gitTool(runner) {
|
|
|
4349
4478
|
return result.exitCode === 0 && result.stdout.trim().length > 0;
|
|
4350
4479
|
}
|
|
4351
4480
|
async function commitAll(dir, message) {
|
|
4352
|
-
await git(["add", "--all"], dir);
|
|
4481
|
+
await git(["add", "--all"], { cwd: dir });
|
|
4353
4482
|
await git([
|
|
4354
4483
|
...await hasIdentity(dir) ? [] : [
|
|
4355
4484
|
"-c",
|
|
@@ -4360,7 +4489,7 @@ function gitTool(runner) {
|
|
|
4360
4489
|
"commit",
|
|
4361
4490
|
"--message",
|
|
4362
4491
|
message
|
|
4363
|
-
], dir);
|
|
4492
|
+
], { cwd: dir });
|
|
4364
4493
|
}
|
|
4365
4494
|
async function addRemote(dir, name, url) {
|
|
4366
4495
|
await git([
|
|
@@ -4368,22 +4497,33 @@ function gitTool(runner) {
|
|
|
4368
4497
|
"add",
|
|
4369
4498
|
name,
|
|
4370
4499
|
url
|
|
4371
|
-
], dir);
|
|
4500
|
+
], { cwd: dir });
|
|
4501
|
+
}
|
|
4502
|
+
async function clone(credentials, dir) {
|
|
4503
|
+
await git([
|
|
4504
|
+
"clone",
|
|
4505
|
+
"--quiet",
|
|
4506
|
+
credentials.url,
|
|
4507
|
+
dir
|
|
4508
|
+
], { addedEnv: credentialEnv(credentials) });
|
|
4372
4509
|
}
|
|
4373
|
-
async function push(dir, remote, branch) {
|
|
4510
|
+
async function push(dir, remote, branch, credentials) {
|
|
4374
4511
|
await git([
|
|
4375
4512
|
"push",
|
|
4376
4513
|
"--set-upstream",
|
|
4377
4514
|
remote,
|
|
4378
4515
|
branch
|
|
4379
|
-
],
|
|
4516
|
+
], {
|
|
4517
|
+
cwd: dir,
|
|
4518
|
+
...credentials !== void 0 && { addedEnv: credentialEnv(credentials) }
|
|
4519
|
+
});
|
|
4380
4520
|
}
|
|
4381
4521
|
async function currentBranch(dir) {
|
|
4382
4522
|
return (await git([
|
|
4383
4523
|
"rev-parse",
|
|
4384
4524
|
"--abbrev-ref",
|
|
4385
4525
|
"HEAD"
|
|
4386
|
-
], dir)).stdout.trim();
|
|
4526
|
+
], { cwd: dir })).stdout.trim();
|
|
4387
4527
|
}
|
|
4388
4528
|
async function remoteUrl(dir, name) {
|
|
4389
4529
|
const result = await runner.run(GIT, [
|
|
@@ -4406,6 +4546,7 @@ function gitTool(runner) {
|
|
|
4406
4546
|
hasCommits,
|
|
4407
4547
|
commitAll,
|
|
4408
4548
|
addRemote,
|
|
4549
|
+
clone,
|
|
4409
4550
|
push,
|
|
4410
4551
|
currentBranch,
|
|
4411
4552
|
remoteUrl,
|
|
@@ -4459,10 +4600,11 @@ function hostDeps$10(ctx) {
|
|
|
4459
4600
|
taskPollIntervalMs: TASK_POLL_INTERVAL_MS
|
|
4460
4601
|
};
|
|
4461
4602
|
}
|
|
4603
|
+
const SKIP_GIT_SYNC = "skip";
|
|
4462
4604
|
const FRESH = {
|
|
4463
|
-
workdir: null,
|
|
4464
4605
|
remoteKind: null,
|
|
4465
|
-
hostedUrl: null
|
|
4606
|
+
hostedUrl: null,
|
|
4607
|
+
workdir: null
|
|
4466
4608
|
};
|
|
4467
4609
|
function expandHome(path) {
|
|
4468
4610
|
return path.startsWith(HOME_PREFIX) ? join(homedir(), path.slice(2)) : path;
|
|
@@ -4478,6 +4620,10 @@ async function directoryState(path) {
|
|
|
4478
4620
|
if (!stat.isDirectory()) return "file";
|
|
4479
4621
|
return (await promises.readdir(path)).length === 0 ? "empty" : "populated";
|
|
4480
4622
|
}
|
|
4623
|
+
async function isCloneTarget(path) {
|
|
4624
|
+
const state = await directoryState(path);
|
|
4625
|
+
return state === "missing" || state === "empty";
|
|
4626
|
+
}
|
|
4481
4627
|
async function initTargetProblem(run, path) {
|
|
4482
4628
|
switch (await directoryState(path)) {
|
|
4483
4629
|
case "missing":
|
|
@@ -4488,78 +4634,132 @@ async function initTargetProblem(run, path) {
|
|
|
4488
4634
|
case "populated": return await run.git.isRepo(path) ? null : `${path} is not empty and not a git repository; pick an empty directory or a repository`;
|
|
4489
4635
|
}
|
|
4490
4636
|
}
|
|
4637
|
+
async function cloneTargetProblem(path) {
|
|
4638
|
+
if (await isCloneTarget(path)) return null;
|
|
4639
|
+
return await directoryState(path) === "file" ? `${path} is not a directory` : `${path} is not empty; pick an empty directory, or use an existing repository`;
|
|
4640
|
+
}
|
|
4491
4641
|
async function existingRepoProblem(run, path) {
|
|
4492
4642
|
return await run.git.isRepo(path) ? null : `${path} is not a git repository`;
|
|
4493
4643
|
}
|
|
4494
|
-
const
|
|
4495
|
-
|
|
4496
|
-
|
|
4644
|
+
const FRESH_WORKDIRS = {
|
|
4645
|
+
"local-bare": {
|
|
4646
|
+
here: "Initialize a repository here",
|
|
4647
|
+
elsewhere: "Initialize in another directory",
|
|
4648
|
+
pathMessage: "Directory for the repository (created if missing)",
|
|
4649
|
+
problem: initTargetProblem
|
|
4650
|
+
},
|
|
4651
|
+
hosted: {
|
|
4652
|
+
here: "Clone the repository here",
|
|
4653
|
+
elsewhere: "Clone into another directory",
|
|
4654
|
+
pathMessage: "Directory to clone into (created if missing)",
|
|
4655
|
+
problem: (_run, path) => cloneTargetProblem(path)
|
|
4656
|
+
}
|
|
4497
4657
|
};
|
|
4498
|
-
async function askPath(run,
|
|
4658
|
+
async function askPath(run, question) {
|
|
4499
4659
|
for (;;) {
|
|
4500
4660
|
const typed = await run.ctx.prompts.textOrBack({
|
|
4501
4661
|
key: GIT_SYNC_KEYS.path,
|
|
4502
|
-
message:
|
|
4503
|
-
...model.workdir !== null && { initialValue: model.workdir }
|
|
4662
|
+
message: question.message
|
|
4504
4663
|
});
|
|
4505
4664
|
if (typed === BACK) return BACK;
|
|
4506
4665
|
const path = resolve(run.deps.cwd, expandHome(typed));
|
|
4507
|
-
const problem =
|
|
4666
|
+
const problem = await question.problem(path);
|
|
4508
4667
|
if (problem === null) return path;
|
|
4509
4668
|
run.ctx.ui.warn(problem);
|
|
4510
4669
|
}
|
|
4511
4670
|
}
|
|
4671
|
+
function remoteScreen(run) {
|
|
4672
|
+
return async (model) => {
|
|
4673
|
+
const choice = await run.ctx.prompts.select({
|
|
4674
|
+
key: GIT_SYNC_KEYS.remote,
|
|
4675
|
+
message: "Where should Metabase push the content?",
|
|
4676
|
+
choices: [
|
|
4677
|
+
{
|
|
4678
|
+
value: "local-bare",
|
|
4679
|
+
label: "Local remote inside ~/.rde",
|
|
4680
|
+
hint: "no account needed, recommended"
|
|
4681
|
+
},
|
|
4682
|
+
{
|
|
4683
|
+
value: "hosted",
|
|
4684
|
+
label: "Hosted remote (GitHub, GitLab, Bitbucket…)",
|
|
4685
|
+
hint: "needs a token"
|
|
4686
|
+
},
|
|
4687
|
+
{
|
|
4688
|
+
value: SKIP_GIT_SYNC,
|
|
4689
|
+
label: "Skip for now",
|
|
4690
|
+
hint: "rde init --only git-sync sets it up later"
|
|
4691
|
+
}
|
|
4692
|
+
],
|
|
4693
|
+
...model.remoteKind !== null && { initialValue: model.remoteKind }
|
|
4694
|
+
});
|
|
4695
|
+
if (choice === "skip") return {
|
|
4696
|
+
type: "done",
|
|
4697
|
+
model: FRESH
|
|
4698
|
+
};
|
|
4699
|
+
const next = {
|
|
4700
|
+
...model,
|
|
4701
|
+
remoteKind: choice
|
|
4702
|
+
};
|
|
4703
|
+
return {
|
|
4704
|
+
type: "next",
|
|
4705
|
+
to: choice === "hosted" ? "hosted" : "workdir",
|
|
4706
|
+
model: next
|
|
4707
|
+
};
|
|
4708
|
+
};
|
|
4709
|
+
}
|
|
4512
4710
|
function workdirScreen(run) {
|
|
4513
4711
|
return async (model) => {
|
|
4712
|
+
if (model.remoteKind === null) throw new Error("no remote before the workdir screen");
|
|
4713
|
+
const fresh = FRESH_WORKDIRS[model.remoteKind];
|
|
4714
|
+
const questions = {
|
|
4715
|
+
new: {
|
|
4716
|
+
message: fresh.pathMessage,
|
|
4717
|
+
problem: (path) => fresh.problem(run, path)
|
|
4718
|
+
},
|
|
4719
|
+
existing: {
|
|
4720
|
+
message: "Path of the existing repository",
|
|
4721
|
+
problem: (path) => existingRepoProblem(run, path)
|
|
4722
|
+
}
|
|
4723
|
+
};
|
|
4514
4724
|
for (;;) {
|
|
4515
|
-
const choice = await run.ctx.prompts.
|
|
4725
|
+
const choice = await run.ctx.prompts.selectOrBack({
|
|
4516
4726
|
key: GIT_SYNC_KEYS.location,
|
|
4517
4727
|
message: "Where should the Metabase content repository live?",
|
|
4518
4728
|
choices: [
|
|
4519
4729
|
{
|
|
4520
4730
|
value: "here",
|
|
4521
|
-
label:
|
|
4731
|
+
label: fresh.here,
|
|
4522
4732
|
hint: run.deps.cwd
|
|
4523
4733
|
},
|
|
4524
4734
|
{
|
|
4525
4735
|
value: "new",
|
|
4526
|
-
label:
|
|
4736
|
+
label: fresh.elsewhere
|
|
4527
4737
|
},
|
|
4528
4738
|
{
|
|
4529
4739
|
value: "existing",
|
|
4530
4740
|
label: "Use an existing repository"
|
|
4531
|
-
},
|
|
4532
|
-
{
|
|
4533
|
-
value: "skip",
|
|
4534
|
-
label: "Skip for now",
|
|
4535
|
-
hint: "rde init --only git-sync sets it up later"
|
|
4536
4741
|
}
|
|
4537
4742
|
]
|
|
4538
4743
|
});
|
|
4539
|
-
if (choice ===
|
|
4540
|
-
type: "done",
|
|
4541
|
-
model: FRESH
|
|
4542
|
-
};
|
|
4744
|
+
if (choice === BACK) return { type: "back" };
|
|
4543
4745
|
if (choice === "here") {
|
|
4544
|
-
const problem = await
|
|
4746
|
+
const problem = await fresh.problem(run, run.deps.cwd);
|
|
4545
4747
|
if (problem !== null) {
|
|
4546
4748
|
run.ctx.ui.warn(problem);
|
|
4547
4749
|
continue;
|
|
4548
4750
|
}
|
|
4549
4751
|
return {
|
|
4550
|
-
type: "
|
|
4551
|
-
to: "remote",
|
|
4752
|
+
type: "done",
|
|
4552
4753
|
model: {
|
|
4553
4754
|
...model,
|
|
4554
4755
|
workdir: run.deps.cwd
|
|
4555
4756
|
}
|
|
4556
4757
|
};
|
|
4557
4758
|
}
|
|
4558
|
-
const path = await askPath(run,
|
|
4759
|
+
const path = await askPath(run, questions[choice]);
|
|
4559
4760
|
if (path === BACK) continue;
|
|
4560
4761
|
return {
|
|
4561
|
-
type: "
|
|
4562
|
-
to: "remote",
|
|
4762
|
+
type: "done",
|
|
4563
4763
|
model: {
|
|
4564
4764
|
...model,
|
|
4565
4765
|
workdir: path
|
|
@@ -4568,37 +4768,6 @@ function workdirScreen(run) {
|
|
|
4568
4768
|
}
|
|
4569
4769
|
};
|
|
4570
4770
|
}
|
|
4571
|
-
function remoteScreen(run) {
|
|
4572
|
-
return async (model) => {
|
|
4573
|
-
const kind = await run.ctx.prompts.selectOrBack({
|
|
4574
|
-
key: GIT_SYNC_KEYS.remote,
|
|
4575
|
-
message: "Where should Metabase push the content?",
|
|
4576
|
-
choices: [{
|
|
4577
|
-
value: "local-bare",
|
|
4578
|
-
label: "Local remote inside ~/.rde",
|
|
4579
|
-
hint: "no account needed, recommended"
|
|
4580
|
-
}, {
|
|
4581
|
-
value: "hosted",
|
|
4582
|
-
label: "Hosted remote (GitHub, GitLab, Bitbucket…)",
|
|
4583
|
-
hint: "needs a token"
|
|
4584
|
-
}],
|
|
4585
|
-
...model.remoteKind !== null && { initialValue: model.remoteKind }
|
|
4586
|
-
});
|
|
4587
|
-
if (kind === BACK) return { type: "back" };
|
|
4588
|
-
const next = {
|
|
4589
|
-
...model,
|
|
4590
|
-
remoteKind: kind
|
|
4591
|
-
};
|
|
4592
|
-
return kind === "hosted" ? {
|
|
4593
|
-
type: "next",
|
|
4594
|
-
to: "hosted",
|
|
4595
|
-
model: next
|
|
4596
|
-
} : {
|
|
4597
|
-
type: "done",
|
|
4598
|
-
model: next
|
|
4599
|
-
};
|
|
4600
|
-
};
|
|
4601
|
-
}
|
|
4602
4771
|
function validateHttpsUrl(value) {
|
|
4603
4772
|
return value.length === 0 || value.startsWith(HTTPS_PREFIX) ? void 0 : `The repository URL must start with ${HTTPS_PREFIX}`;
|
|
4604
4773
|
}
|
|
@@ -4639,7 +4808,8 @@ function hostedScreen(run) {
|
|
|
4639
4808
|
spinner.stop("Metabase can reach the repository");
|
|
4640
4809
|
run.ctx.session.gitToken.set(token);
|
|
4641
4810
|
return {
|
|
4642
|
-
type: "
|
|
4811
|
+
type: "next",
|
|
4812
|
+
to: "workdir",
|
|
4643
4813
|
model: {
|
|
4644
4814
|
...model,
|
|
4645
4815
|
hostedUrl: url
|
|
@@ -4706,9 +4876,34 @@ async function configureLocalBare(run, runtime, workdir) {
|
|
|
4706
4876
|
}
|
|
4707
4877
|
};
|
|
4708
4878
|
}
|
|
4709
|
-
async function
|
|
4879
|
+
async function cloneHosted(run, workdir, credentials) {
|
|
4880
|
+
const spinner = run.ctx.ui.spinner(`Cloning ${credentials.url}…`);
|
|
4881
|
+
try {
|
|
4882
|
+
await run.git.clone(credentials, workdir);
|
|
4883
|
+
} catch (error) {
|
|
4884
|
+
spinner.fail("Could not clone the repository");
|
|
4885
|
+
throw error;
|
|
4886
|
+
}
|
|
4887
|
+
spinner.stop(`Cloned into ${workdir}`);
|
|
4888
|
+
if (await run.git.hasCommits(workdir)) return {
|
|
4889
|
+
workdir,
|
|
4890
|
+
branch: await run.git.currentBranch(workdir)
|
|
4891
|
+
};
|
|
4892
|
+
const prepared = await prepareWorkdir(run, workdir);
|
|
4893
|
+
await run.git.push(workdir, ORIGIN, prepared.branch, credentials);
|
|
4894
|
+
return prepared;
|
|
4895
|
+
}
|
|
4896
|
+
async function adoptRepository(run, workdir, url) {
|
|
4710
4897
|
const prepared = await prepareWorkdir(run, workdir);
|
|
4711
4898
|
await ensureRemote(run, workdir, url);
|
|
4899
|
+
return prepared;
|
|
4900
|
+
}
|
|
4901
|
+
async function configureHosted(run, workdir, url) {
|
|
4902
|
+
const token = run.ctx.session.gitToken.get();
|
|
4903
|
+
const prepared = await isCloneTarget(workdir) ? await cloneHosted(run, workdir, {
|
|
4904
|
+
url,
|
|
4905
|
+
token
|
|
4906
|
+
}) : await adoptRepository(run, workdir, url);
|
|
4712
4907
|
return {
|
|
4713
4908
|
gitRemote: {
|
|
4714
4909
|
kind: "hosted",
|
|
@@ -4718,7 +4913,7 @@ async function configureHosted(run, workdir, url) {
|
|
|
4718
4913
|
},
|
|
4719
4914
|
settings: {
|
|
4720
4915
|
"remote-sync-url": url,
|
|
4721
|
-
"remote-sync-token":
|
|
4916
|
+
"remote-sync-token": token,
|
|
4722
4917
|
"remote-sync-type": "read-write",
|
|
4723
4918
|
"remote-sync-branch": prepared.branch
|
|
4724
4919
|
}
|
|
@@ -4836,10 +5031,10 @@ async function runGitSync(ctx, deps) {
|
|
|
4836
5031
|
})
|
|
4837
5032
|
};
|
|
4838
5033
|
const flow = await runFlow({
|
|
4839
|
-
workdir: workdirScreen(run),
|
|
4840
5034
|
remote: remoteScreen(run),
|
|
4841
|
-
hosted: hostedScreen(run)
|
|
4842
|
-
|
|
5035
|
+
hosted: hostedScreen(run),
|
|
5036
|
+
workdir: workdirScreen(run)
|
|
5037
|
+
}, "remote", FRESH);
|
|
4843
5038
|
if (flow.type === "exited" || !isChosen(flow.model)) {
|
|
4844
5039
|
ctx.ui.info("Skipping remote sync; run `rde init --only git-sync` to set it up later");
|
|
4845
5040
|
return "skipped";
|
|
@@ -4865,8 +5060,8 @@ function gitSyncStep(deps = hostDeps$10) {
|
|
|
4865
5060
|
throw error;
|
|
4866
5061
|
}
|
|
4867
5062
|
},
|
|
4868
|
-
run(ctx) {
|
|
4869
|
-
return runGitSync(ctx, deps(ctx));
|
|
5063
|
+
async run(ctx) {
|
|
5064
|
+
return { outcome: await runGitSync(ctx, deps(ctx)) };
|
|
4870
5065
|
}
|
|
4871
5066
|
};
|
|
4872
5067
|
}
|
|
@@ -4874,7 +5069,7 @@ function gitSyncStep(deps = hostDeps$10) {
|
|
|
4874
5069
|
//#region src/tools/skills-cli.ts
|
|
4875
5070
|
const NPX = "npx";
|
|
4876
5071
|
const SKILLS_PACKAGE = "skills@latest";
|
|
4877
|
-
const DEFAULT_RDE_SKILL_SOURCE = "https://github.com/metabase/agent-skills/tree/
|
|
5072
|
+
const DEFAULT_RDE_SKILL_SOURCE = "https://github.com/metabase/agent-skills/tree/main";
|
|
4878
5073
|
const RDE_SOURCE_SKILL_NAMES = ["rde", "rde-data-apps"];
|
|
4879
5074
|
const DATA_APP_SKILLS_SOURCE = "metabase/metabase/skills#master";
|
|
4880
5075
|
const DATA_APP_SKILL_NAMES = [
|
|
@@ -5112,7 +5307,7 @@ function agentsSkillsStep(options, deps = hostDeps$9) {
|
|
|
5112
5307
|
const selection = await selectAgents(ctx, all, options);
|
|
5113
5308
|
if (selection.agents.length === 0) {
|
|
5114
5309
|
ctx.ui.info(skipLine(all, selection.source));
|
|
5115
|
-
return "skipped";
|
|
5310
|
+
return { outcome: "skipped" };
|
|
5116
5311
|
}
|
|
5117
5312
|
const skills = skillsCli(resolved.runner);
|
|
5118
5313
|
const agents = selection.agents.map((agent) => agent.skillsCliName);
|
|
@@ -5124,7 +5319,7 @@ function agentsSkillsStep(options, deps = hostDeps$9) {
|
|
|
5124
5319
|
agents,
|
|
5125
5320
|
output: ctx.ui.childOutput
|
|
5126
5321
|
});
|
|
5127
|
-
const from = resolved.skillSource === "https://github.com/metabase/agent-skills/tree/
|
|
5322
|
+
const from = resolved.skillSource === "https://github.com/metabase/agent-skills/tree/main" ? "" : ` from ${resolved.skillSource}`;
|
|
5128
5323
|
ctx.ui.info(`Installing the rde skills ${forWhom}${from}`);
|
|
5129
5324
|
await skills.add({
|
|
5130
5325
|
source: resolved.skillSource,
|
|
@@ -5137,7 +5332,7 @@ function agentsSkillsStep(options, deps = hostDeps$9) {
|
|
|
5137
5332
|
if (missing.length > 0) throw new ConfigError(`the skills installer finished but \`skills list\` does not show ${missing.join(", ")}`);
|
|
5138
5333
|
ctx.ui.note(installedLines(requiredEntries(installed), selection.agents), "Skills installed");
|
|
5139
5334
|
ctx.ui.info(MECHANICS_NOTE);
|
|
5140
|
-
return "completed";
|
|
5335
|
+
return { outcome: "completed" };
|
|
5141
5336
|
}
|
|
5142
5337
|
};
|
|
5143
5338
|
}
|
|
@@ -5225,15 +5420,23 @@ async function connectDatabases(ctx, deps, start) {
|
|
|
5225
5420
|
});
|
|
5226
5421
|
return {
|
|
5227
5422
|
existing: existing.data.length,
|
|
5228
|
-
created: result.created.length
|
|
5423
|
+
created: result.created.length,
|
|
5424
|
+
added: result.added
|
|
5229
5425
|
};
|
|
5230
5426
|
}
|
|
5231
5427
|
async function addDatabases(ctx, deps) {
|
|
5232
5428
|
const connected = await connectDatabases(ctx, deps, "engine");
|
|
5233
|
-
return
|
|
5429
|
+
return {
|
|
5430
|
+
outcome: connected.existing + connected.created > 0 ? "completed" : "skipped",
|
|
5431
|
+
result: { addedDatabases: connected.added }
|
|
5432
|
+
};
|
|
5234
5433
|
}
|
|
5235
5434
|
async function askForOwnDatabase(ctx, deps) {
|
|
5236
|
-
|
|
5435
|
+
const connected = await connectDatabases(ctx, deps, "connection-string");
|
|
5436
|
+
return {
|
|
5437
|
+
outcome: connected.created > 0 ? "completed" : "skipped",
|
|
5438
|
+
result: { addedDatabases: connected.added }
|
|
5439
|
+
};
|
|
5237
5440
|
}
|
|
5238
5441
|
function hostDeps$8(ctx) {
|
|
5239
5442
|
return hostDatabasesDeps(ctx.signal);
|
|
@@ -5274,9 +5477,12 @@ function databasesStep(options, deps = hostDeps$8) {
|
|
|
5274
5477
|
throw error;
|
|
5275
5478
|
}
|
|
5276
5479
|
},
|
|
5277
|
-
run(ctx) {
|
|
5278
|
-
|
|
5279
|
-
|
|
5480
|
+
async run(ctx) {
|
|
5481
|
+
switch (options.request) {
|
|
5482
|
+
case "ask-once": return askForOwnDatabase(ctx, deps(ctx));
|
|
5483
|
+
case "named": return addDatabases(ctx, deps(ctx));
|
|
5484
|
+
case "sample-stands-in": return { outcome: "skipped" };
|
|
5485
|
+
}
|
|
5280
5486
|
}
|
|
5281
5487
|
};
|
|
5282
5488
|
}
|
|
@@ -5420,7 +5626,7 @@ function installMbStep(deps = hostDeps$7) {
|
|
|
5420
5626
|
const before = await inspectMb(resolved.runner);
|
|
5421
5627
|
if (before.installed && before.satisfies) {
|
|
5422
5628
|
ctx.ui.success(`mb ${before.version} is installed`);
|
|
5423
|
-
return "completed";
|
|
5629
|
+
return { outcome: "completed" };
|
|
5424
5630
|
}
|
|
5425
5631
|
if (before.installed) ctx.ui.warn(`mb ${before.version} is installed; ${MIN_MB_VERSION} or newer is required`);
|
|
5426
5632
|
const install = mbInstallCommand(resolved.installedWith);
|
|
@@ -5429,7 +5635,7 @@ function installMbStep(deps = hostDeps$7) {
|
|
|
5429
5635
|
const after = await inspectMb(resolved.runner);
|
|
5430
5636
|
if (!after.installed || !after.satisfies) throw await stillWrong(install, after, resolved);
|
|
5431
5637
|
ctx.ui.success(`mb ${after.version} is installed`);
|
|
5432
|
-
return "completed";
|
|
5638
|
+
return { outcome: "completed" };
|
|
5433
5639
|
}
|
|
5434
5640
|
};
|
|
5435
5641
|
}
|
|
@@ -5487,7 +5693,7 @@ function licenseStep(deps = hostDeps$6) {
|
|
|
5487
5693
|
});
|
|
5488
5694
|
if (token.length === 0) {
|
|
5489
5695
|
ctx.ui.info("Skipping the license; remote sync needs one. Run `rde init --from license` to add it later");
|
|
5490
|
-
return "skipped";
|
|
5696
|
+
return { outcome: "skipped" };
|
|
5491
5697
|
}
|
|
5492
5698
|
ctx.session.licenseToken.set(token);
|
|
5493
5699
|
const client = deps(ctx).client(configured.url, {
|
|
@@ -5518,7 +5724,7 @@ function licenseStep(deps = hostDeps$6) {
|
|
|
5518
5724
|
});
|
|
5519
5725
|
if (status.features.includes("remote-sync")) ctx.ui.info("Remote sync is included; the git step will set it up");
|
|
5520
5726
|
else ctx.ui.warn("This license does not include remote sync; the git step will be skipped");
|
|
5521
|
-
return "completed";
|
|
5727
|
+
return { outcome: "completed" };
|
|
5522
5728
|
}
|
|
5523
5729
|
};
|
|
5524
5730
|
}
|
|
@@ -5742,7 +5948,7 @@ function preflightStep(deps = hostDeps$5) {
|
|
|
5742
5948
|
const checks = await runChecks(resolved, resolved.platform);
|
|
5743
5949
|
ctx.ui.note(checkLines(checks).join("\n"), "This machine");
|
|
5744
5950
|
if (checks.some(blocks)) throw new ConfigError("Fix the items above and run rde init again");
|
|
5745
|
-
return "completed";
|
|
5951
|
+
return { outcome: "completed" };
|
|
5746
5952
|
}
|
|
5747
5953
|
};
|
|
5748
5954
|
}
|
|
@@ -6355,7 +6561,7 @@ function runtimeStep(options, deps = hostDeps$4) {
|
|
|
6355
6561
|
throw error;
|
|
6356
6562
|
}
|
|
6357
6563
|
spinner.stop(`Metabase installed on port ${chosen.port}`);
|
|
6358
|
-
return "completed";
|
|
6564
|
+
return { outcome: "completed" };
|
|
6359
6565
|
}
|
|
6360
6566
|
};
|
|
6361
6567
|
}
|
|
@@ -6391,13 +6597,13 @@ function sampleDatabaseUrl(port) {
|
|
|
6391
6597
|
function publishesOnLoopback$1(inspection, port) {
|
|
6392
6598
|
return inspection.ports.some((mapping) => mapping.container === POSTGRES_PORT && mapping.host === port && mapping.hostIp === "127.0.0.1");
|
|
6393
6599
|
}
|
|
6394
|
-
function createOptions(state, home, alsoOn) {
|
|
6600
|
+
function createOptions(state, home, hostPort, alsoOn) {
|
|
6395
6601
|
return {
|
|
6396
6602
|
name: state.containerName,
|
|
6397
6603
|
home,
|
|
6398
6604
|
image: state.image,
|
|
6399
6605
|
port: {
|
|
6400
|
-
host:
|
|
6606
|
+
host: hostPort,
|
|
6401
6607
|
container: POSTGRES_PORT,
|
|
6402
6608
|
alsoOn
|
|
6403
6609
|
},
|
|
@@ -6407,6 +6613,9 @@ function createOptions(state, home, alsoOn) {
|
|
|
6407
6613
|
hostUser: null
|
|
6408
6614
|
};
|
|
6409
6615
|
}
|
|
6616
|
+
function requireOwnContainer(name, existing, home) {
|
|
6617
|
+
if (existing.home !== null && existing.home !== home) throw new ConfigError(`container ${name} belongs to the rde home ${existing.home} and is left alone`);
|
|
6618
|
+
}
|
|
6410
6619
|
function sampleDatabaseContainer(state, docker, home) {
|
|
6411
6620
|
const name = state.containerName;
|
|
6412
6621
|
function conflict(reason) {
|
|
@@ -6416,11 +6625,12 @@ function sampleDatabaseContainer(state, docker, home) {
|
|
|
6416
6625
|
const existing = await docker.containerInspect(name);
|
|
6417
6626
|
if (existing.exists) {
|
|
6418
6627
|
if (existing.image !== state.image) throw conflict(`with image ${existing.image}, not ${state.image}`);
|
|
6419
|
-
if (
|
|
6420
|
-
|
|
6628
|
+
if (publishesOnLoopback$1(existing, state.port)) return;
|
|
6629
|
+
requireOwnContainer(name, existing, home);
|
|
6630
|
+
await docker.containerForceRemove(name);
|
|
6421
6631
|
}
|
|
6422
6632
|
await docker.imagePull(state.image, { onLine: options.onProgress });
|
|
6423
|
-
await docker.containerCreate(createOptions(state, home, options.alsoOn));
|
|
6633
|
+
await docker.containerCreate(createOptions(state, home, state.port, options.alsoOn));
|
|
6424
6634
|
}
|
|
6425
6635
|
async function start() {
|
|
6426
6636
|
const existing = await docker.containerInspect(name);
|
|
@@ -6470,7 +6680,22 @@ function extraAddress(inspection) {
|
|
|
6470
6680
|
const extra = inspection.ports.find((mapping) => mapping.container === POSTGRES_PORT && mapping.hostIp !== "127.0.0.1");
|
|
6471
6681
|
return extra === void 0 ? null : extra.hostIp;
|
|
6472
6682
|
}
|
|
6473
|
-
async function
|
|
6683
|
+
async function startOnDockerPort(state, docker, home, alsoOn) {
|
|
6684
|
+
const name = state.containerName;
|
|
6685
|
+
await docker.containerForceRemove(name);
|
|
6686
|
+
await docker.containerCreate(createOptions(state, home, null, null));
|
|
6687
|
+
await docker.containerStart(name);
|
|
6688
|
+
const port = await docker.containerLoopbackPort(name, POSTGRES_PORT);
|
|
6689
|
+
await docker.containerForceRemove(name);
|
|
6690
|
+
const pinned = {
|
|
6691
|
+
...state,
|
|
6692
|
+
port
|
|
6693
|
+
};
|
|
6694
|
+
await docker.containerCreate(createOptions(pinned, home, port, alsoOn));
|
|
6695
|
+
await docker.containerStart(name);
|
|
6696
|
+
return pinned;
|
|
6697
|
+
}
|
|
6698
|
+
async function startOnFreePort(state, docker, home) {
|
|
6474
6699
|
try {
|
|
6475
6700
|
return {
|
|
6476
6701
|
outcome: await sampleDatabaseContainer(state, docker, home).start(),
|
|
@@ -6480,18 +6705,10 @@ async function startOnFreePort(state, docker, ports, home) {
|
|
|
6480
6705
|
if (!isPortTaken(error)) throw error;
|
|
6481
6706
|
}
|
|
6482
6707
|
const existing = await docker.containerInspect(state.containerName);
|
|
6483
|
-
if (existing.exists
|
|
6484
|
-
const moved = {
|
|
6485
|
-
...state,
|
|
6486
|
-
port: await ports.findFree(SAMPLE_DATABASE_PORT)
|
|
6487
|
-
};
|
|
6488
|
-
const alsoOn = existing.exists ? extraAddress(existing) : null;
|
|
6489
|
-
await docker.containerRemove(state.containerName);
|
|
6490
|
-
await docker.containerCreate(createOptions(moved, home, alsoOn));
|
|
6491
|
-
await docker.containerStart(moved.containerName);
|
|
6708
|
+
if (existing.exists) requireOwnContainer(state.containerName, existing, home);
|
|
6492
6709
|
return {
|
|
6493
6710
|
outcome: "started",
|
|
6494
|
-
state:
|
|
6711
|
+
state: await startOnDockerPort(state, docker, home, existing.exists ? extraAddress(existing) : null)
|
|
6495
6712
|
};
|
|
6496
6713
|
}
|
|
6497
6714
|
//#endregion
|
|
@@ -6585,7 +6802,7 @@ async function installAndStart(ctx, sidecar, installed, deps) {
|
|
|
6585
6802
|
throw error;
|
|
6586
6803
|
}
|
|
6587
6804
|
installing.stop(`Sample database container ${installed.containerName} on port ${installed.port}`);
|
|
6588
|
-
const started = await startOnFreePort(installed, deps.runtime.docker, deps.
|
|
6805
|
+
const started = await startOnFreePort(installed, deps.runtime.docker, deps.runtime.rdeHome);
|
|
6589
6806
|
const record = started.state.port === installed.port ? installed : await recordSampleDatabaseMove(ctx, installed, started.state);
|
|
6590
6807
|
const waiting = ctx.ui.spinner("Waiting for Postgres to accept connections…");
|
|
6591
6808
|
try {
|
|
@@ -6694,7 +6911,7 @@ function sampleDatabaseStep(deps = hostDeps$3) {
|
|
|
6694
6911
|
await connectDatabase(ctx, choice, configured, client, resolved);
|
|
6695
6912
|
await client.setting.enableTransforms();
|
|
6696
6913
|
ctx.ui.note(usageLines(record), "Sample database");
|
|
6697
|
-
return "completed";
|
|
6914
|
+
return { outcome: "completed" };
|
|
6698
6915
|
}
|
|
6699
6916
|
};
|
|
6700
6917
|
}
|
|
@@ -6706,7 +6923,6 @@ const PERCENT = 100;
|
|
|
6706
6923
|
function hostDeps$2(ctx) {
|
|
6707
6924
|
return {
|
|
6708
6925
|
runtime: hostRuntimeDeps(ctx.state.name, ctx.signal),
|
|
6709
|
-
ports: hostPortProbe,
|
|
6710
6926
|
client: (baseUrl, auth) => createMetabaseClient(baseUrl, auth, ctx.signal),
|
|
6711
6927
|
timing: DEFAULT_HEALTH_TIMING
|
|
6712
6928
|
};
|
|
@@ -6723,7 +6939,7 @@ function progressLabel(progress) {
|
|
|
6723
6939
|
async function startSampleDatabase(ctx, deps) {
|
|
6724
6940
|
const record = ctx.state.sampleDatabase;
|
|
6725
6941
|
if (record === null) return;
|
|
6726
|
-
const started = await startOnFreePort(record, deps.runtime.docker, deps.
|
|
6942
|
+
const started = await startOnFreePort(record, deps.runtime.docker, deps.runtime.rdeHome);
|
|
6727
6943
|
if (started.state.port !== record.port) await recordSampleDatabaseMove(ctx, record, started.state);
|
|
6728
6944
|
switch (started.outcome) {
|
|
6729
6945
|
case "missing":
|
|
@@ -6783,7 +6999,7 @@ function startMetabaseStep(deps = hostDeps$2) {
|
|
|
6783
6999
|
version
|
|
6784
7000
|
});
|
|
6785
7001
|
spinner.stop(`Metabase ${version} is running at ${configured.url}`);
|
|
6786
|
-
return "completed";
|
|
7002
|
+
return { outcome: "completed" };
|
|
6787
7003
|
}
|
|
6788
7004
|
};
|
|
6789
7005
|
}
|
|
@@ -6879,7 +7095,7 @@ function summaryStep(deps = hostDeps$1) {
|
|
|
6879
7095
|
async run(ctx) {
|
|
6880
7096
|
const lines = await summaryLines(ctx.state, ctx.secrets.apiKey, deps(ctx));
|
|
6881
7097
|
ctx.ui.note(lines.join("\n"), "Your environment");
|
|
6882
|
-
return "completed";
|
|
7098
|
+
return { outcome: "completed" };
|
|
6883
7099
|
}
|
|
6884
7100
|
};
|
|
6885
7101
|
}
|
|
@@ -7016,16 +7232,16 @@ function fieldAnswer(value) {
|
|
|
7016
7232
|
return text$1(String(value));
|
|
7017
7233
|
}
|
|
7018
7234
|
function addDatabase(map, database, ordinal, last) {
|
|
7019
|
-
add(map,
|
|
7235
|
+
add(map, databaseKey(ordinal, "engine"), select$1(database.engine));
|
|
7020
7236
|
if (database.uri !== void 0) {
|
|
7021
|
-
add(map,
|
|
7022
|
-
add(map,
|
|
7237
|
+
add(map, databaseKey(ordinal, "mode"), select$1("paste"));
|
|
7238
|
+
add(map, databaseKey(ordinal, "uri"), text$1(database.uri));
|
|
7023
7239
|
}
|
|
7024
|
-
if (database.uri === void 0 && EXAMPLE_URIS[database.engine] !== void 0) add(map,
|
|
7240
|
+
if (database.uri === void 0 && EXAMPLE_URIS[database.engine] !== void 0) add(map, databaseKey(ordinal, "mode"), select$1("manual"));
|
|
7025
7241
|
if (database.fields !== void 0) for (const [name, value] of Object.entries(database.fields)) add(map, fieldKey(ordinal, name), fieldAnswer(value));
|
|
7026
|
-
add(map,
|
|
7027
|
-
add(map,
|
|
7028
|
-
add(map,
|
|
7242
|
+
add(map, databaseKey(ordinal, "name"), text$1(database.name ?? ACCEPT_DEFAULT));
|
|
7243
|
+
add(map, databaseKey(ordinal, "action"), select$1("test"));
|
|
7244
|
+
add(map, databaseKey(ordinal, "another"), select$1(last ? "finish" : "another"));
|
|
7029
7245
|
}
|
|
7030
7246
|
function addAdmin(map, admin) {
|
|
7031
7247
|
if (admin.variant !== "prompted") return;
|
|
@@ -7083,17 +7299,12 @@ function keyedAnswers(answers) {
|
|
|
7083
7299
|
const license = answers.license;
|
|
7084
7300
|
if (license !== void 0) add(map, LICENSE_KEYS.token, password$1(license.token ?? SKIP_LICENSE));
|
|
7085
7301
|
const databases = answers.databases;
|
|
7086
|
-
if (databases !== void 0) {
|
|
7087
|
-
|
|
7088
|
-
|
|
7089
|
-
add(map, DATABASE_KEYS.skip, confirm$1(true));
|
|
7090
|
-
}
|
|
7091
|
-
databases.forEach((database, index) => {
|
|
7092
|
-
addDatabase(map, database, index + 1, index === databases.length - 1);
|
|
7093
|
-
});
|
|
7094
|
-
}
|
|
7302
|
+
if (databases !== void 0) databases.forEach((database, index) => {
|
|
7303
|
+
addDatabase(map, database, index + 1, index === databases.length - 1);
|
|
7304
|
+
});
|
|
7095
7305
|
const git = answers["git-sync"];
|
|
7096
|
-
if (git
|
|
7306
|
+
if (git?.location === "skip") add(map, GIT_SYNC_KEYS.remote, select$1(SKIP_GIT_SYNC));
|
|
7307
|
+
else if (git !== void 0) {
|
|
7097
7308
|
add(map, GIT_SYNC_KEYS.location, select$1(git.location));
|
|
7098
7309
|
if (git.path !== void 0) add(map, GIT_SYNC_KEYS.path, text$1(git.path));
|
|
7099
7310
|
if (git.remote !== void 0) add(map, GIT_SYNC_KEYS.remote, select$1(git.remote));
|
|
@@ -7137,18 +7348,26 @@ const FLAG_FOR_KEY$1 = {
|
|
|
7137
7348
|
[API_KEY_KEYS.password]: "--admin-password",
|
|
7138
7349
|
[API_KEY_KEYS.profile]: "--mb-profile",
|
|
7139
7350
|
[LICENSE_KEYS.token]: "--license-token or --skip-license",
|
|
7140
|
-
[DATABASE_KEYS.engine]: "--database or --skip-databases",
|
|
7141
|
-
[DATABASE_KEYS.uri]: "--database",
|
|
7142
7351
|
[DATABASE_KEYS.skip]: "--skip-databases",
|
|
7143
|
-
[GIT_SYNC_KEYS.location]: "--git-dir
|
|
7352
|
+
[GIT_SYNC_KEYS.location]: "--git-dir",
|
|
7144
7353
|
[GIT_SYNC_KEYS.path]: "--git-dir",
|
|
7145
|
-
[GIT_SYNC_KEYS.remote]: "--git-remote",
|
|
7354
|
+
[GIT_SYNC_KEYS.remote]: "--git-remote or --skip-git",
|
|
7146
7355
|
[GIT_SYNC_KEYS.url]: "--git-url",
|
|
7147
7356
|
[GIT_SYNC_KEYS.token]: "--git-token"
|
|
7148
7357
|
};
|
|
7358
|
+
const DATABASE_FLAG_FOR_NAME = {
|
|
7359
|
+
engine: "--database or --skip-databases",
|
|
7360
|
+
uri: "--database"
|
|
7361
|
+
};
|
|
7149
7362
|
function howToAnswer(key) {
|
|
7150
7363
|
const field = parseFieldKey(key);
|
|
7151
7364
|
if (field !== null) return `set "${field.name}" under "fields" of database ${field.ordinal} in the --answers file (for a pasted database, in its connection string)`;
|
|
7365
|
+
const database = parseDatabaseKey(key);
|
|
7366
|
+
if (database !== null) {
|
|
7367
|
+
const flag = DATABASE_FLAG_FOR_NAME[database.name];
|
|
7368
|
+
const inFile = `set database ${database.ordinal} in the --answers file`;
|
|
7369
|
+
return flag === void 0 ? inFile : `pass ${flag}, or ${inFile}`;
|
|
7370
|
+
}
|
|
7152
7371
|
const flag = FLAG_FOR_KEY$1[key];
|
|
7153
7372
|
const inFile = `set "${key}" in the --answers file`;
|
|
7154
7373
|
return flag === void 0 ? inFile : `pass ${flag}, or ${inFile}`;
|
|
@@ -7741,17 +7960,30 @@ function requireKnownStep(steps, id, flag) {
|
|
|
7741
7960
|
if (id === void 0) return;
|
|
7742
7961
|
if (!steps.some((step) => step.id === id)) throw new ConfigError(`${flag} ${id} is not one of the steps: ${steps.map((step) => step.id).join(", ")}`);
|
|
7743
7962
|
}
|
|
7963
|
+
async function detectOne(step, ctx, options, fromHere) {
|
|
7964
|
+
if (step.id === (options.only ?? options.from)) return {
|
|
7965
|
+
step,
|
|
7966
|
+
status: "todo",
|
|
7967
|
+
forced: true
|
|
7968
|
+
};
|
|
7969
|
+
const status = await step.detect(ctx);
|
|
7970
|
+
if (options.only === void 0 && fromHere && status !== "not-applicable") return {
|
|
7971
|
+
step,
|
|
7972
|
+
status: "todo",
|
|
7973
|
+
forced: true
|
|
7974
|
+
};
|
|
7975
|
+
return {
|
|
7976
|
+
step,
|
|
7977
|
+
status,
|
|
7978
|
+
forced: false
|
|
7979
|
+
};
|
|
7980
|
+
}
|
|
7744
7981
|
async function detectAll(steps, ctx, options) {
|
|
7745
7982
|
const detected = [];
|
|
7746
7983
|
let fromHere = false;
|
|
7747
7984
|
for (const step of steps) {
|
|
7748
7985
|
if (step.id === options.from) fromHere = true;
|
|
7749
|
-
|
|
7750
|
-
detected.push({
|
|
7751
|
-
step,
|
|
7752
|
-
status: forced ? "todo" : await step.detect(ctx),
|
|
7753
|
-
forced
|
|
7754
|
-
});
|
|
7986
|
+
detected.push(await detectOne(step, ctx, options, fromHere));
|
|
7755
7987
|
}
|
|
7756
7988
|
return detected;
|
|
7757
7989
|
}
|
|
@@ -7790,13 +8022,16 @@ async function runWizard(steps, ctx, options = {}) {
|
|
|
7790
8022
|
title: step.title,
|
|
7791
8023
|
status
|
|
7792
8024
|
})));
|
|
8025
|
+
const results = [];
|
|
7793
8026
|
for (const entry of detected) try {
|
|
7794
8027
|
if (!await shouldRun(entry, ctx, options)) continue;
|
|
7795
|
-
const outcome = await entry.step.run(ctx);
|
|
8028
|
+
const { outcome, result } = await entry.step.run(ctx);
|
|
8029
|
+
if (result !== void 0) results.push(result);
|
|
7796
8030
|
await ctx.saveState(withStepRecord(ctx.state, entry.step.id, recordFor(outcome, now())));
|
|
7797
8031
|
} catch (error) {
|
|
7798
8032
|
throw failureAt(entry.step, error);
|
|
7799
8033
|
}
|
|
8034
|
+
return results;
|
|
7800
8035
|
}
|
|
7801
8036
|
//#endregion
|
|
7802
8037
|
//#region src/steps/doctor.ts
|
|
@@ -8690,73 +8925,11 @@ function isLocation(value) {
|
|
|
8690
8925
|
function isRemote(value) {
|
|
8691
8926
|
return value === "local-bare" || value === "hosted";
|
|
8692
8927
|
}
|
|
8693
|
-
function applyConnectionRecord(draft, value) {
|
|
8694
|
-
draft.databases = draft.databases.filter((database) => !database.pasted);
|
|
8695
|
-
if (value === null) return;
|
|
8696
|
-
if (value.trim().length === 0) {
|
|
8697
|
-
draft.databasesFinished = true;
|
|
8698
|
-
return;
|
|
8699
|
-
}
|
|
8700
|
-
draft.databases.push({
|
|
8701
|
-
engine: null,
|
|
8702
|
-
uri: value.trim(),
|
|
8703
|
-
fields: {},
|
|
8704
|
-
name: null,
|
|
8705
|
-
pasted: true,
|
|
8706
|
-
added: false
|
|
8707
|
-
});
|
|
8708
|
-
}
|
|
8709
|
-
function applyEngineRecord(draft, value) {
|
|
8710
|
-
if (draft.databases.at(-1)?.added === false) draft.databases.pop();
|
|
8711
|
-
if (value === "finish") draft.databasesFinished = true;
|
|
8712
|
-
else if (value !== null) draft.databases.push({
|
|
8713
|
-
engine: value,
|
|
8714
|
-
uri: null,
|
|
8715
|
-
fields: {},
|
|
8716
|
-
name: null,
|
|
8717
|
-
pasted: false,
|
|
8718
|
-
added: false
|
|
8719
|
-
});
|
|
8720
|
-
}
|
|
8721
8928
|
function applyDatabaseRecord(draft, record) {
|
|
8722
8929
|
const value = stringOf(record.answer);
|
|
8723
|
-
|
|
8724
|
-
|
|
8725
|
-
|
|
8726
|
-
}
|
|
8727
|
-
if (record.key === DATABASE_KEYS.engine) {
|
|
8728
|
-
applyEngineRecord(draft, value);
|
|
8729
|
-
return;
|
|
8730
|
-
}
|
|
8731
|
-
const database = draft.databases.at(-1);
|
|
8732
|
-
if (database === void 0) return;
|
|
8733
|
-
const switched = record.key === DATABASE_KEYS.switchEngine && value !== "try-again";
|
|
8734
|
-
if ((record.key === DATABASE_KEYS.detectedEngine || switched) && value !== null) {
|
|
8735
|
-
database.engine = value;
|
|
8736
|
-
return;
|
|
8737
|
-
}
|
|
8738
|
-
if (record.key === DATABASE_KEYS.another) {
|
|
8739
|
-
database.added = true;
|
|
8740
|
-
return;
|
|
8741
|
-
}
|
|
8742
|
-
if (record.key === DATABASE_KEYS.mode) {
|
|
8743
|
-
database.fields = {};
|
|
8744
|
-
if (value === "manual") database.uri = null;
|
|
8745
|
-
return;
|
|
8746
|
-
}
|
|
8747
|
-
if (record.key === DATABASE_KEYS.uri && value !== null) {
|
|
8748
|
-
database.uri = value;
|
|
8749
|
-
return;
|
|
8750
|
-
}
|
|
8751
|
-
if (record.key === DATABASE_KEYS.name && value !== null && value.length > 0) {
|
|
8752
|
-
database.name = value;
|
|
8753
|
-
return;
|
|
8754
|
-
}
|
|
8755
|
-
const fieldName = parseFieldKey(record.key)?.name;
|
|
8756
|
-
if (fieldName === void 0) return;
|
|
8757
|
-
if (record.answer.kind === "confirm") database.fields[fieldName] = record.answer.value;
|
|
8758
|
-
else if (record.answer.kind === "password") database.fields[fieldName] = envReference(databaseEnv(draft.databases.length - 1, fieldName));
|
|
8759
|
-
else if (value !== null) database.fields[fieldName] = value;
|
|
8930
|
+
const skippedConnection = record.key === DATABASE_KEYS.connection && value?.trim() === "";
|
|
8931
|
+
const finished = parseDatabaseKey(record.key)?.name === "engine" && value === "finish";
|
|
8932
|
+
if (skippedConnection || finished) draft.databasesFinished = true;
|
|
8760
8933
|
}
|
|
8761
8934
|
function applyAdminRecord(draft, key, value) {
|
|
8762
8935
|
if (key === ADMIN_USER_KEYS.email && value !== null) admin(draft).email = value;
|
|
@@ -8765,7 +8938,8 @@ function applyAdminRecord(draft, key, value) {
|
|
|
8765
8938
|
else if (key === ADMIN_USER_KEYS.lastName && value !== null && value.length > 0) admin(draft)["last-name"] = value;
|
|
8766
8939
|
}
|
|
8767
8940
|
function applyGitRecord(draft, key, value) {
|
|
8768
|
-
if (key === GIT_SYNC_KEYS.
|
|
8941
|
+
if (key === GIT_SYNC_KEYS.remote && value === "skip") draft.answers["git-sync"] = { location: SKIP_GIT_SYNC };
|
|
8942
|
+
else if (key === GIT_SYNC_KEYS.location && value !== null && isLocation(value)) gitSync(draft).location = value;
|
|
8769
8943
|
else if (key === GIT_SYNC_KEYS.path && value !== null) gitSync(draft).path = value;
|
|
8770
8944
|
else if (key === GIT_SYNC_KEYS.remote && value !== null && isRemote(value)) gitSync(draft).remote = value;
|
|
8771
8945
|
else if (key === GIT_SYNC_KEYS.url && value !== null) gitSync(draft).url = value;
|
|
@@ -8793,30 +8967,26 @@ function writableUri(uri, index) {
|
|
|
8793
8967
|
const trimmed = uri.trim();
|
|
8794
8968
|
return URI_PASSWORDS.some((pattern) => pattern.test(trimmed)) ? envReference(databaseEnv(index, "uri")) : trimmed;
|
|
8795
8969
|
}
|
|
8796
|
-
function
|
|
8797
|
-
if (
|
|
8798
|
-
|
|
8799
|
-
|
|
8800
|
-
|
|
8801
|
-
|
|
8802
|
-
const
|
|
8803
|
-
const engine = engineOf(database);
|
|
8804
|
-
if (!database.added || database.uri === null && !hasFields || engine === null) return null;
|
|
8970
|
+
function databaseEntry(added, index) {
|
|
8971
|
+
if (added.uri !== null) return {
|
|
8972
|
+
engine: added.engine,
|
|
8973
|
+
uri: writableUri(added.uri, index),
|
|
8974
|
+
name: added.name
|
|
8975
|
+
};
|
|
8976
|
+
const fields = added.fields.map(({ name, value, secret }) => [name, secret ? envReference(databaseEnv(index, name)) : value]);
|
|
8805
8977
|
return {
|
|
8806
|
-
engine,
|
|
8807
|
-
|
|
8808
|
-
|
|
8809
|
-
...database.name !== null && { name: database.name }
|
|
8978
|
+
engine: added.engine,
|
|
8979
|
+
fields: Object.fromEntries(fields),
|
|
8980
|
+
name: added.name
|
|
8810
8981
|
};
|
|
8811
8982
|
}
|
|
8812
|
-
function answersFromRecords(records, overrides) {
|
|
8983
|
+
function answersFromRecords(records, overrides, added) {
|
|
8813
8984
|
const draft = {
|
|
8814
8985
|
answers: {},
|
|
8815
|
-
databases: [],
|
|
8816
8986
|
databasesFinished: false
|
|
8817
8987
|
};
|
|
8818
8988
|
for (const record of records) applyRecord(draft, record);
|
|
8819
|
-
const databases =
|
|
8989
|
+
const databases = added.map(databaseEntry);
|
|
8820
8990
|
if (databases.length > 0 || draft.databasesFinished) draft.answers.databases = databases;
|
|
8821
8991
|
return {
|
|
8822
8992
|
...draft.answers,
|
|
@@ -9063,9 +9233,10 @@ const init = defineRdeCommand({
|
|
|
9063
9233
|
prompts: recording?.prompts ?? answering
|
|
9064
9234
|
});
|
|
9065
9235
|
ctx.ui.intro("rde init");
|
|
9066
|
-
await runWizard(initSteps(stepOptionsFrom(answers, { interactive: stdinIsTty() && !runOptions.yes })), ctx, options);
|
|
9236
|
+
const results = await runWizard(initSteps(stepOptionsFrom(answers, { interactive: stdinIsTty() && !runOptions.yes })), ctx, options);
|
|
9067
9237
|
if (recording !== null && printTo !== void 0) {
|
|
9068
|
-
|
|
9238
|
+
const added = results.flatMap((result) => result.addedDatabases);
|
|
9239
|
+
await writeAnswersFile(printTo, answersFromRecords(recording.records, answers, added));
|
|
9069
9240
|
ctx.ui.info(`Answers written to ${printTo}`);
|
|
9070
9241
|
}
|
|
9071
9242
|
ctx.ui.outro("rde init finished");
|
|
@@ -9771,7 +9942,6 @@ const RELEASE_TAG = /^v?[01]\.(\d+\.\d+(?:\.\d+)?)/;
|
|
|
9771
9942
|
function hostDeps(ctx) {
|
|
9772
9943
|
return {
|
|
9773
9944
|
runtime: hostRuntimeDeps(ctx.state.name, ctx.signal),
|
|
9774
|
-
ports: hostPortProbe,
|
|
9775
9945
|
client: (baseUrl, auth) => createMetabaseClient(baseUrl, auth, ctx.signal),
|
|
9776
9946
|
timing: DEFAULT_HEALTH_TIMING,
|
|
9777
9947
|
releases: releaseLookup(ctx.signal),
|
|
@@ -10033,7 +10203,7 @@ const upgradeCommand = defineRdeCommand({
|
|
|
10033
10203
|
const main = defineCommand({
|
|
10034
10204
|
meta: {
|
|
10035
10205
|
name: "rde",
|
|
10036
|
-
version: "0.1.
|
|
10206
|
+
version: "0.1.10",
|
|
10037
10207
|
description: "Prepares a local machine for data-engineering work with Metabase and coding agents"
|
|
10038
10208
|
},
|
|
10039
10209
|
subCommands: {
|