@devdogsuga/devtools 0.1.25 → 0.1.27
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 +72 -11
- package/bin/devtools.mjs +4 -4
- package/dist/cli-CZKkNHK5.js +6619 -0
- package/dist/failure-log-BBPGejQv.js +256 -0
- package/dist/launch.js +267 -281
- package/dist/local-env-D8HC2JK_.js +351 -0
- package/dist/local-env-DrBCXqD8.js +2 -0
- package/dist/menu-CQBsQRgH.js +1684 -0
- package/dist/peer-redirect-hooks.js +15 -0
- package/dist/peers-RBplwovA.js +229 -0
- package/dist/root-C2OmZDwD.js +94 -0
- package/dist/stack-Bx-55oi_.js +2 -0
- package/dist/stack-BynCgRVS.js +181 -0
- package/dist/supabase-project-0knM0mqd.js +426 -0
- package/dist/telemetry-C4foD8MD.js +249 -0
- package/dist/telemetry.js +2 -148
- package/package.json +10 -29
- package/bin/devtools-ci-bare.mjs +0 -48
- package/bin/devtools-ci.mjs +0 -31
- package/dist/args.d.ts +0 -26
- package/dist/args.js +0 -78
- package/dist/bws/bw.d.ts +0 -18
- package/dist/bws/bw.js +0 -70
- package/dist/bws/client.d.ts +0 -43
- package/dist/bws/client.js +0 -363
- package/dist/bws/environments.d.ts +0 -96
- package/dist/bws/environments.js +0 -111
- package/dist/bws/pace.d.ts +0 -25
- package/dist/bws/pace.js +0 -43
- package/dist/bws/retry.d.ts +0 -12
- package/dist/bws/retry.js +0 -39
- package/dist/bws/store.d.ts +0 -1
- package/dist/bws/store.js +0 -32
- package/dist/bws/token.d.ts +0 -31
- package/dist/bws/token.js +0 -88
- package/dist/bws/vault.d.ts +0 -29
- package/dist/bws/vault.js +0 -200
- package/dist/catalog.d.ts +0 -57
- package/dist/catalog.js +0 -57
- package/dist/cf/build.d.ts +0 -13
- package/dist/cf/build.js +0 -65
- package/dist/cf/commands.d.ts +0 -1
- package/dist/cf/commands.js +0 -172
- package/dist/cf/local-env.d.ts +0 -58
- package/dist/cf/local-env.js +0 -177
- package/dist/ci.d.ts +0 -1
- package/dist/ci.js +0 -328
- package/dist/cli.d.ts +0 -11
- package/dist/cli.js +0 -1094
- package/dist/commands.d.ts +0 -261
- package/dist/commands.js +0 -1318
- package/dist/completions.d.ts +0 -4
- package/dist/completions.js +0 -110
- package/dist/cron/commands.d.ts +0 -47
- package/dist/cron/commands.js +0 -372
- package/dist/cron/discovery.d.ts +0 -55
- package/dist/cron/discovery.js +0 -85
- package/dist/cron/schema.d.ts +0 -45
- package/dist/cron/schema.js +0 -35
- package/dist/db/config-push.d.ts +0 -8
- package/dist/db/config-push.js +0 -11
- package/dist/db/connection.d.ts +0 -79
- package/dist/db/connection.js +0 -114
- package/dist/db/exec.d.ts +0 -1
- package/dist/db/exec.js +0 -11
- package/dist/db/generate-types.d.ts +0 -1
- package/dist/db/generate-types.js +0 -4
- package/dist/db/generated-env.d.ts +0 -69
- package/dist/db/generated-env.js +0 -113
- package/dist/db/introspect.d.ts +0 -1
- package/dist/db/introspect.js +0 -122
- package/dist/db/local-env.d.ts +0 -65
- package/dist/db/local-env.js +0 -23
- package/dist/db/new-migration.d.ts +0 -8
- package/dist/db/new-migration.js +0 -69
- package/dist/db/pooler.d.ts +0 -23
- package/dist/db/pooler.js +0 -55
- package/dist/db/run.d.ts +0 -68
- package/dist/db/run.js +0 -156
- package/dist/db/seed-buckets.d.ts +0 -2
- package/dist/db/seed-buckets.js +0 -4
- package/dist/db/seed-production.d.ts +0 -8
- package/dist/db/seed-production.js +0 -79
- package/dist/db/seed-roles.d.ts +0 -7
- package/dist/db/seed-roles.js +0 -33
- package/dist/db/session-refresh.d.ts +0 -43
- package/dist/db/session-refresh.js +0 -40
- package/dist/deploy/migrations.d.ts +0 -20
- package/dist/deploy/migrations.js +0 -68
- package/dist/deploy/orphans.d.ts +0 -20
- package/dist/deploy/orphans.js +0 -177
- package/dist/deploy/preflight.d.ts +0 -53
- package/dist/deploy/preflight.js +0 -189
- package/dist/deploy/report.d.ts +0 -26
- package/dist/deploy/report.js +0 -70
- package/dist/deploy/require-planner.d.ts +0 -2
- package/dist/deploy/require-planner.js +0 -60
- package/dist/deploy/require-token.d.ts +0 -15
- package/dist/deploy/require-token.js +0 -78
- package/dist/deploy/secrets-file.d.ts +0 -17
- package/dist/deploy/secrets-file.js +0 -166
- package/dist/deploy/write-env.d.ts +0 -40
- package/dist/deploy/write-env.js +0 -395
- package/dist/docs/index-pages.d.ts +0 -38
- package/dist/docs/index-pages.js +0 -186
- package/dist/emails/commands.d.ts +0 -15
- package/dist/emails/commands.js +0 -130
- package/dist/emails/email-types.d.ts +0 -13
- package/dist/emails/email-types.js +0 -1
- package/dist/emails/fixtures.d.ts +0 -20
- package/dist/emails/fixtures.js +0 -20
- package/dist/env/audit.d.ts +0 -206
- package/dist/env/audit.js +0 -436
- package/dist/env/cloudflare.d.ts +0 -21
- package/dist/env/cloudflare.js +0 -61
- package/dist/env/commands.d.ts +0 -99
- package/dist/env/commands.js +0 -613
- package/dist/env/discovery.d.ts +0 -15
- package/dist/env/discovery.js +0 -239
- package/dist/env/document.d.ts +0 -119
- package/dist/env/document.js +0 -323
- package/dist/env/example.d.ts +0 -68
- package/dist/env/example.js +0 -605
- package/dist/env/selection.d.ts +0 -105
- package/dist/env/selection.js +0 -228
- package/dist/env-entry.d.ts +0 -49
- package/dist/env-entry.js +0 -204
- package/dist/environment-doctor.d.ts +0 -45
- package/dist/environment-doctor.js +0 -429
- package/dist/environment.d.ts +0 -92
- package/dist/environment.js +0 -185
- package/dist/fingerprint.d.ts +0 -10
- package/dist/fingerprint.js +0 -18
- package/dist/gen/campus-map.d.ts +0 -1
- package/dist/gen/campus-map.js +0 -473
- package/dist/gen/commands.d.ts +0 -1
- package/dist/gen/commands.js +0 -25
- package/dist/gen/hypno.d.ts +0 -1
- package/dist/gen/hypno.js +0 -134
- package/dist/gh/client.d.ts +0 -80
- package/dist/gh/client.js +0 -259
- package/dist/gh/environments.d.ts +0 -83
- package/dist/gh/environments.js +0 -178
- package/dist/gh/rulesets/actors.d.ts +0 -14
- package/dist/gh/rulesets/actors.js +0 -69
- package/dist/gh/rulesets/api.d.ts +0 -15
- package/dist/gh/rulesets/api.js +0 -104
- package/dist/gh/rulesets/commands.d.ts +0 -3
- package/dist/gh/rulesets/commands.js +0 -203
- package/dist/gh/rulesets/desired.d.ts +0 -112
- package/dist/gh/rulesets/desired.js +0 -271
- package/dist/gh/rulesets/diff.d.ts +0 -65
- package/dist/gh/rulesets/diff.js +0 -170
- package/dist/gh/rulesets/types.d.ts +0 -76
- package/dist/gh/rulesets/types.js +0 -13
- package/dist/gh/settings/api.d.ts +0 -40
- package/dist/gh/settings/api.js +0 -180
- package/dist/gh/settings/commands.d.ts +0 -1
- package/dist/gh/settings/commands.js +0 -254
- package/dist/gh/settings/desired.d.ts +0 -53
- package/dist/gh/settings/desired.js +0 -43
- package/dist/gh/settings/diff.d.ts +0 -57
- package/dist/gh/settings/diff.js +0 -161
- package/dist/gh/settings/types.d.ts +0 -92
- package/dist/gh/settings/types.js +0 -11
- package/dist/gh/settings/workflows.d.ts +0 -41
- package/dist/gh/settings/workflows.js +0 -128
- package/dist/grantRoot.d.ts +0 -42
- package/dist/grantRoot.js +0 -89
- package/dist/help.d.ts +0 -18
- package/dist/help.js +0 -169
- package/dist/images/commands.d.ts +0 -32
- package/dist/images/commands.js +0 -231
- package/dist/images/events.d.ts +0 -47
- package/dist/images/events.js +0 -116
- package/dist/images/graphics.d.ts +0 -70
- package/dist/images/graphics.js +0 -234
- package/dist/images/open-graph-types.d.ts +0 -75
- package/dist/images/open-graph-types.js +0 -1
- package/dist/images/prompts.d.ts +0 -34
- package/dist/images/prompts.js +0 -100
- package/dist/images/render.d.ts +0 -21
- package/dist/images/render.js +0 -61
- package/dist/images/select.d.ts +0 -64
- package/dist/images/select.js +0 -96
- package/dist/instance.d.ts +0 -69
- package/dist/instance.js +0 -102
- package/dist/invocation.d.ts +0 -49
- package/dist/invocation.js +0 -78
- package/dist/launch-ci.d.ts +0 -1
- package/dist/launch-ci.js +0 -90
- package/dist/launch.d.ts +0 -18
- package/dist/menu.d.ts +0 -43
- package/dist/menu.js +0 -300
- package/dist/moderation.d.ts +0 -70
- package/dist/moderation.js +0 -128
- package/dist/oauth/browser.d.ts +0 -1
- package/dist/oauth/browser.js +0 -13
- package/dist/oauth/config.d.ts +0 -24
- package/dist/oauth/config.js +0 -24
- package/dist/oauth/connect-url.d.ts +0 -17
- package/dist/oauth/connect-url.js +0 -23
- package/dist/oauth/db.d.ts +0 -37
- package/dist/oauth/db.js +0 -108
- package/dist/oauth/device.d.ts +0 -93
- package/dist/oauth/device.js +0 -198
- package/dist/oauth/discovery.d.ts +0 -28
- package/dist/oauth/discovery.js +0 -54
- package/dist/oauth/env-file.d.ts +0 -6
- package/dist/oauth/env-file.js +0 -35
- package/dist/oauth/exchange.d.ts +0 -43
- package/dist/oauth/exchange.js +0 -93
- package/dist/oauth/label.d.ts +0 -1
- package/dist/oauth/label.js +0 -25
- package/dist/oauth/loopback.d.ts +0 -52
- package/dist/oauth/loopback.js +0 -168
- package/dist/oauth/pkce.d.ts +0 -6
- package/dist/oauth/pkce.js +0 -34
- package/dist/oauth/target.d.ts +0 -54
- package/dist/oauth/target.js +0 -102
- package/dist/oauth/transport.d.ts +0 -41
- package/dist/oauth/transport.js +0 -32
- package/dist/oauth/wizard.d.ts +0 -40
- package/dist/oauth/wizard.js +0 -578
- package/dist/persona.d.ts +0 -18
- package/dist/persona.js +0 -221
- package/dist/pick.d.ts +0 -6
- package/dist/pick.js +0 -64
- package/dist/pipes.d.ts +0 -1
- package/dist/pipes.js +0 -25
- package/dist/planner/checks.d.ts +0 -26
- package/dist/planner/checks.js +0 -104
- package/dist/planner/commands.d.ts +0 -10
- package/dist/planner/commands.js +0 -367
- package/dist/planner/db.d.ts +0 -15
- package/dist/planner/db.js +0 -33
- package/dist/planner/role.d.ts +0 -33
- package/dist/planner/role.js +0 -68
- package/dist/repo/peer-redirect-hooks.d.ts +0 -16
- package/dist/repo/peer-redirect-hooks.js +0 -10
- package/dist/repo/peer-redirect.d.ts +0 -4
- package/dist/repo/peer-redirect.js +0 -26
- package/dist/repo/peers.d.ts +0 -39
- package/dist/repo/peers.js +0 -139
- package/dist/repo/resolve.d.ts +0 -41
- package/dist/repo/resolve.js +0 -152
- package/dist/repo/root.d.ts +0 -23
- package/dist/repo/root.js +0 -91
- package/dist/repo/source.d.ts +0 -14
- package/dist/repo/source.js +0 -54
- package/dist/repo/supabase-project.d.ts +0 -60
- package/dist/repo/supabase-project.js +0 -124
- package/dist/repo/tsx-loader.d.ts +0 -6
- package/dist/repo/tsx-loader.js +0 -41
- package/dist/run/pick.d.ts +0 -90
- package/dist/run/pick.js +0 -664
- package/dist/setup.d.ts +0 -1
- package/dist/setup.js +0 -280
- package/dist/stack.d.ts +0 -48
- package/dist/stack.js +0 -403
- package/dist/telemetry.d.ts +0 -59
- package/dist/tier.d.ts +0 -40
- package/dist/tier.js +0 -96
- package/dist/ui.d.ts +0 -40
- package/dist/ui.js +0 -71
- package/dist/version.d.ts +0 -7
- package/dist/version.js +0 -17
- package/dist/workers.d.ts +0 -5
- package/dist/workers.js +0 -33
- package/dist/workflows/commands.d.ts +0 -54
- package/dist/workflows/commands.js +0 -763
package/dist/oauth/wizard.js
DELETED
|
@@ -1,578 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Configures a Supabase project to accept "Sign in with DevDogs".
|
|
3
|
-
*
|
|
4
|
-
* Was `@devdogsuga/oauth-setup`, a package of its own with its own binary. It
|
|
5
|
-
* was separate because it was going to be published for sibling projects to
|
|
6
|
-
* install; nothing here is published, so that separation bought only a second
|
|
7
|
-
* `@clack/prompts` dependency and a second place to look for a CLI.
|
|
8
|
-
*
|
|
9
|
-
* Two axes, chosen independently:
|
|
10
|
-
*
|
|
11
|
-
* * TARGET — which Supabase project gets the custom provider: the one
|
|
12
|
-
* `supabase status` reports for this directory, or a hosted one
|
|
13
|
-
* (staging/production, or someone else's) named by URL + service-role
|
|
14
|
-
* key. See `target.ts`.
|
|
15
|
-
* * CONNECT METHOD — how the OAuth client's `client_id`/`client_secret`
|
|
16
|
-
* are obtained: the one-click flow (default, TASK-351) opens a browser
|
|
17
|
-
* at the platform's `/tools/oauth/connect`, waits on a loopback
|
|
18
|
-
* listener, and exchanges a PKCE-protected code for them; "Paste
|
|
19
|
-
* credentials instead" is the original manual flow, for a platform that
|
|
20
|
-
* is unreachable or a contributor who would rather not open a browser.
|
|
21
|
-
*/
|
|
22
|
-
import { confirm, log, note, password, select, spinner, text, } from "@clack/prompts";
|
|
23
|
-
import { DEFAULT_API_URL, ENV_KEYS, PROVIDER_IDENTIFIER, PROVIDER_NAME, WEBSITE_URL, } from "./config.js";
|
|
24
|
-
import { upsertEnvLocal } from "./env-file.js";
|
|
25
|
-
import { checkProvider, upsertDevDogsProvider } from "./db.js";
|
|
26
|
-
import { DiscoveryError, fetchIssuer } from "./discovery.js";
|
|
27
|
-
import { openBrowser } from "./browser.js";
|
|
28
|
-
import { codeChallengeFor, generateCodeVerifier, generateState, } from "./pkce.js";
|
|
29
|
-
import { CallbackDeniedError, CallbackMalformedError, CallbackTimeoutError, StateMismatchError, start as startLoopback, verifyState, } from "./loopback.js";
|
|
30
|
-
import { buildConnectUrl } from "./connect-url.js";
|
|
31
|
-
import { ExchangeError, exchangeCode, } from "./exchange.js";
|
|
32
|
-
import { DeviceError, PollError, pollForToken, requestDeviceCode, } from "./device.js";
|
|
33
|
-
import { decideTransport } from "./transport.js";
|
|
34
|
-
import { defaultLabel } from "./label.js";
|
|
35
|
-
import { HostedCredentialsMissingError, NoTierResolvedError, readHostedTargetFromEnvFiles, resolveLocalTarget, resolveHostedTargetInRepo, } from "./target.js";
|
|
36
|
-
import { loadEnvSession } from "../repo/peers.js";
|
|
37
|
-
import { discoverRepoRoot } from "../repo/root.js";
|
|
38
|
-
import { resolveTier } from "../tier.js";
|
|
39
|
-
import { bail, errorMessage, unwrap } from "../ui.js";
|
|
40
|
-
/** Runs `supabase status`, retrying if not running. */
|
|
41
|
-
async function detectLocalWithRetry(cwd) {
|
|
42
|
-
while (true) {
|
|
43
|
-
const s = spinner();
|
|
44
|
-
s.start("Detecting local Supabase");
|
|
45
|
-
try {
|
|
46
|
-
const target = await resolveLocalTarget(cwd);
|
|
47
|
-
s.stop(`Connected to ${target.apiUrl}`);
|
|
48
|
-
return target;
|
|
49
|
-
}
|
|
50
|
-
catch (err) {
|
|
51
|
-
s.stop("Could not detect local Supabase");
|
|
52
|
-
log.error(err instanceof Error ? err.message : String(err));
|
|
53
|
-
note("1. Make sure Docker is running\n" +
|
|
54
|
-
"2. Run `supabase start` in your project directory\n" +
|
|
55
|
-
"3. Confirm it's running with `supabase status`", "Troubleshooting");
|
|
56
|
-
const retry = unwrap(await confirm({ message: "Ready to retry?", initialValue: true }));
|
|
57
|
-
if (!retry)
|
|
58
|
-
bail("Run `supabase start` then try again.");
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
function requireUrl(v) {
|
|
63
|
-
if (!v)
|
|
64
|
-
return "Required";
|
|
65
|
-
try {
|
|
66
|
-
new URL(v);
|
|
67
|
-
return undefined;
|
|
68
|
-
}
|
|
69
|
-
catch {
|
|
70
|
-
return "Enter a valid URL";
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
/** An environment variable's value, with an empty one treated as unset. */
|
|
74
|
-
function nonEmptyEnv(key) {
|
|
75
|
-
const value = process.env[key];
|
|
76
|
-
return value === "" ? undefined : value;
|
|
77
|
-
}
|
|
78
|
-
function requireNonEmpty(v) {
|
|
79
|
-
return v?.trim() ? undefined : "Required";
|
|
80
|
-
}
|
|
81
|
-
/** A hosted target reached from INSIDE a DevDogsUGA checkout: asks which tier, lazily. */
|
|
82
|
-
async function chooseHostedTargetInRepo() {
|
|
83
|
-
try {
|
|
84
|
-
return await resolveHostedTargetInRepo(undefined, {
|
|
85
|
-
resolveTier: (tierArg, message) => resolveTier(tierArg, message, { label: "devtools oauth" }),
|
|
86
|
-
enterEnvironment: async (tier) => {
|
|
87
|
-
const envSession = await loadEnvSession();
|
|
88
|
-
// `override: true` — a DELIBERATE, later choice of tier here must
|
|
89
|
-
// win over the `development` the (envFree) launcher already
|
|
90
|
-
// entered before this command ever ran. See `target.ts`'s
|
|
91
|
-
// `RepoHostedTargetDeps.enterEnvironment`.
|
|
92
|
-
await envSession.enterEnvironment(tier, { override: true });
|
|
93
|
-
},
|
|
94
|
-
});
|
|
95
|
-
}
|
|
96
|
-
catch (err) {
|
|
97
|
-
if (err instanceof HostedCredentialsMissingError)
|
|
98
|
-
bail(err.message);
|
|
99
|
-
if (err instanceof NoTierResolvedError)
|
|
100
|
-
bail("No tier selected.");
|
|
101
|
-
throw err;
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
/** A hosted target reached from OUTSIDE any checkout: `.env.local`/`.env`, else a prompt. */
|
|
105
|
-
async function chooseHostedTargetFromEnvOrPrompt(cwd) {
|
|
106
|
-
const fromFiles = readHostedTargetFromEnvFiles(cwd);
|
|
107
|
-
if (fromFiles.apiUrl && fromFiles.serviceRoleKey) {
|
|
108
|
-
log.info("Using the Supabase URL and service-role key from .env.local/.env.");
|
|
109
|
-
return {
|
|
110
|
-
apiUrl: fromFiles.apiUrl,
|
|
111
|
-
serviceRoleKey: fromFiles.serviceRoleKey,
|
|
112
|
-
kind: "hosted",
|
|
113
|
-
};
|
|
114
|
-
}
|
|
115
|
-
const apiUrl = unwrap(await text({ message: "Supabase project URL", validate: requireUrl })).replace(/\/+$/, "");
|
|
116
|
-
const serviceRoleKey = unwrap(await password({
|
|
117
|
-
message: "Supabase service-role key",
|
|
118
|
-
validate: requireNonEmpty,
|
|
119
|
-
})).trim();
|
|
120
|
-
return { apiUrl, serviceRoleKey, kind: "hosted" };
|
|
121
|
-
}
|
|
122
|
-
/** Step 1: choose which Supabase project this run configures. */
|
|
123
|
-
async function chooseTarget(cwd) {
|
|
124
|
-
const insideRepo = discoverRepoRoot(cwd) !== null;
|
|
125
|
-
const kind = unwrap(await select({
|
|
126
|
-
message: "Which Supabase project are you configuring?",
|
|
127
|
-
options: [
|
|
128
|
-
{
|
|
129
|
-
value: "local",
|
|
130
|
-
label: "Local (this directory)",
|
|
131
|
-
hint: "supabase status",
|
|
132
|
-
},
|
|
133
|
-
{
|
|
134
|
-
value: "hosted",
|
|
135
|
-
label: "A hosted project",
|
|
136
|
-
hint: insideRepo
|
|
137
|
-
? "staging, production, or another project"
|
|
138
|
-
: "reads .env.local/.env, or asks",
|
|
139
|
-
},
|
|
140
|
-
],
|
|
141
|
-
}));
|
|
142
|
-
if (kind === "local")
|
|
143
|
-
return detectLocalWithRetry(cwd);
|
|
144
|
-
return insideRepo
|
|
145
|
-
? chooseHostedTargetInRepo()
|
|
146
|
-
: chooseHostedTargetFromEnvOrPrompt(cwd);
|
|
147
|
-
}
|
|
148
|
-
/** One human-readable line per `ExchangeError` kind. */
|
|
149
|
-
function describeExchangeError(err) {
|
|
150
|
-
switch (err.kind) {
|
|
151
|
-
case "invalid_grant":
|
|
152
|
-
return `The authorization code was rejected (${err.message}). Run the command again.`;
|
|
153
|
-
case "rate_limited":
|
|
154
|
-
return err.message;
|
|
155
|
-
case "network":
|
|
156
|
-
case "malformed":
|
|
157
|
-
case "invalid_request":
|
|
158
|
-
return err.message;
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
/** One human-readable line per `DeviceError` kind (see `device.ts`'s `requestDeviceCode`). */
|
|
162
|
-
function describeDeviceError(err) {
|
|
163
|
-
switch (err.kind) {
|
|
164
|
-
case "rate_limited":
|
|
165
|
-
case "network":
|
|
166
|
-
case "malformed":
|
|
167
|
-
case "invalid_request":
|
|
168
|
-
return err.message;
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
/** One human-readable line per `PollError` kind (see `device.ts`'s `pollForToken`). */
|
|
172
|
-
function describePollError(err) {
|
|
173
|
-
switch (err.kind) {
|
|
174
|
-
case "invalid_grant":
|
|
175
|
-
return `The device code was rejected (${err.message}). Run the command again.`;
|
|
176
|
-
case "access_denied":
|
|
177
|
-
case "expired_token":
|
|
178
|
-
case "invalid_request":
|
|
179
|
-
case "network":
|
|
180
|
-
case "malformed":
|
|
181
|
-
return err.message;
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
/**
|
|
185
|
-
* Step 2 (one-click branch, device transport): request a device code,
|
|
186
|
-
* show the user code and verification URL prominently, try to open a
|
|
187
|
-
* browser to it (failure is not fatal — the URL is already on screen), and
|
|
188
|
-
* poll until the platform reports the user approved it, denied it, or the
|
|
189
|
-
* code expired.
|
|
190
|
-
*/
|
|
191
|
-
async function connectByDevice(target, platformUrl, cwd, transportReason) {
|
|
192
|
-
const label = defaultLabel(cwd);
|
|
193
|
-
const callbackUri = `${target.apiUrl}/auth/v1/callback`;
|
|
194
|
-
log.info(`Using the device-code flow — ${transportReason}.`);
|
|
195
|
-
const requestSpinner = spinner();
|
|
196
|
-
requestSpinner.start("Requesting a device code");
|
|
197
|
-
let device;
|
|
198
|
-
try {
|
|
199
|
-
device = await requestDeviceCode({ platformUrl, label, callbackUri });
|
|
200
|
-
requestSpinner.stop("Got a device code");
|
|
201
|
-
}
|
|
202
|
-
catch (err) {
|
|
203
|
-
requestSpinner.stop("Could not request a device code");
|
|
204
|
-
if (err instanceof DeviceError)
|
|
205
|
-
bail(describeDeviceError(err));
|
|
206
|
-
throw err;
|
|
207
|
-
}
|
|
208
|
-
note(`Enter this code: ${device.userCode}\n` +
|
|
209
|
-
`At: ${device.verificationUri}\n\n` +
|
|
210
|
-
`Or open this URL directly:\n${device.verificationUriComplete}`, "Approve the connection");
|
|
211
|
-
log.info(`Opening ${device.verificationUriComplete}`);
|
|
212
|
-
openBrowser(device.verificationUriComplete);
|
|
213
|
-
const pollSpinner = spinner();
|
|
214
|
-
pollSpinner.start(`Waiting for you to enter ${device.userCode} and approve the connection`);
|
|
215
|
-
try {
|
|
216
|
-
const result = await pollForToken({
|
|
217
|
-
platformUrl,
|
|
218
|
-
deviceCode: device.deviceCode,
|
|
219
|
-
intervalSeconds: device.interval,
|
|
220
|
-
expiresInSeconds: device.expiresIn,
|
|
221
|
-
});
|
|
222
|
-
pollSpinner.stop("Approved");
|
|
223
|
-
return result;
|
|
224
|
-
}
|
|
225
|
-
catch (err) {
|
|
226
|
-
pollSpinner.stop("Connection not completed");
|
|
227
|
-
if (err instanceof PollError)
|
|
228
|
-
bail(describePollError(err));
|
|
229
|
-
throw err;
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
/**
|
|
233
|
-
* Step 2 (one-click branch): browser + loopback callback + exchange, given
|
|
234
|
-
* an ALREADY-STARTED `listener` — starting it is `connectViaOneClick`'s job,
|
|
235
|
-
* not this function's, so that the one failure that should fall back to the
|
|
236
|
-
* device flow (the listener failing to even start) is never confused with
|
|
237
|
-
* any failure in here, all of which are this flow's own and must propagate
|
|
238
|
-
* (or `bail()`) unchanged rather than triggering a fallback.
|
|
239
|
-
*
|
|
240
|
-
* Always closes the listener, on every exit path — success, denial,
|
|
241
|
-
* malformed callback, state mismatch, timeout, or an exchange error.
|
|
242
|
-
*/
|
|
243
|
-
async function connectOneClick(target, platformUrl, cwd, transportReason, listener) {
|
|
244
|
-
const verifier = generateCodeVerifier();
|
|
245
|
-
const challenge = codeChallengeFor(verifier);
|
|
246
|
-
const state = generateState();
|
|
247
|
-
const label = defaultLabel(cwd);
|
|
248
|
-
const callbackUri = `${target.apiUrl}/auth/v1/callback`;
|
|
249
|
-
log.info(`Using the loopback flow — ${transportReason}.`);
|
|
250
|
-
try {
|
|
251
|
-
const connectUrl = buildConnectUrl({
|
|
252
|
-
platformUrl,
|
|
253
|
-
redirectUri: listener.redirectUri,
|
|
254
|
-
codeChallenge: challenge,
|
|
255
|
-
state,
|
|
256
|
-
label,
|
|
257
|
-
callbackUri,
|
|
258
|
-
});
|
|
259
|
-
log.info(`Opening ${connectUrl}`);
|
|
260
|
-
openBrowser(connectUrl);
|
|
261
|
-
note(`If your browser did not open, visit this URL:\n${connectUrl}`, "Approve the connection");
|
|
262
|
-
const s = spinner();
|
|
263
|
-
s.start("Waiting for you to approve the connection in your browser");
|
|
264
|
-
let callback;
|
|
265
|
-
try {
|
|
266
|
-
callback = await listener.result;
|
|
267
|
-
}
|
|
268
|
-
catch (err) {
|
|
269
|
-
s.stop("Connection not completed");
|
|
270
|
-
if (err instanceof CallbackDeniedError) {
|
|
271
|
-
bail(err.errorCode === "access_denied"
|
|
272
|
-
? "Connection declined in the browser."
|
|
273
|
-
: `Connection failed: ${err.message}`);
|
|
274
|
-
}
|
|
275
|
-
if (err instanceof CallbackTimeoutError)
|
|
276
|
-
bail(err.message);
|
|
277
|
-
if (err instanceof CallbackMalformedError)
|
|
278
|
-
bail(err.message);
|
|
279
|
-
throw err;
|
|
280
|
-
}
|
|
281
|
-
s.stop("Approved");
|
|
282
|
-
let verified;
|
|
283
|
-
try {
|
|
284
|
-
verified = verifyState(state, callback);
|
|
285
|
-
}
|
|
286
|
-
catch (err) {
|
|
287
|
-
if (err instanceof StateMismatchError)
|
|
288
|
-
bail(err.message);
|
|
289
|
-
throw err;
|
|
290
|
-
}
|
|
291
|
-
const exchangeSpinner = spinner();
|
|
292
|
-
exchangeSpinner.start("Exchanging the authorization code");
|
|
293
|
-
try {
|
|
294
|
-
const result = await exchangeCode({
|
|
295
|
-
platformUrl,
|
|
296
|
-
code: verified.code,
|
|
297
|
-
codeVerifier: verifier,
|
|
298
|
-
});
|
|
299
|
-
exchangeSpinner.stop("Connected");
|
|
300
|
-
return result;
|
|
301
|
-
}
|
|
302
|
-
catch (err) {
|
|
303
|
-
exchangeSpinner.stop("Could not complete the exchange");
|
|
304
|
-
if (err instanceof ExchangeError)
|
|
305
|
-
bail(describeExchangeError(err));
|
|
306
|
-
throw err;
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
finally {
|
|
310
|
-
listener.close();
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
/**
|
|
314
|
-
* Step 2 (one-click branch): decides loopback vs. device (see `transport.ts`)
|
|
315
|
-
* and runs it.
|
|
316
|
-
*
|
|
317
|
-
* `transportOverride` is `--device`/`--loopback` (see `commands.ts`'s `oauth`
|
|
318
|
-
* node); `cli.ts` refuses passing both, so at most one is set here. Left
|
|
319
|
-
* undefined, the transport is auto-detected from the environment.
|
|
320
|
-
*
|
|
321
|
-
* `startLoopback()` is called HERE, not inside `connectOneClick`, and ONLY
|
|
322
|
-
* its failure falls back to device — every failure that can happen once the
|
|
323
|
-
* listener is actually up (denial, timeout, a bad exchange — all of which
|
|
324
|
-
* `connectOneClick` reports via `bail()`, or an unexpected rethrow) is that
|
|
325
|
-
* flow's own and must propagate unchanged, not be mistaken for "the listener
|
|
326
|
-
* never started" and silently rerouted to a different transport with a
|
|
327
|
-
* misleading warning. Loopback forced via `--loopback` never falls back at
|
|
328
|
-
* all: a start failure there is left to propagate as-is.
|
|
329
|
-
*/
|
|
330
|
-
export async function connectViaOneClick(target, platformUrl, cwd, transportOverride) {
|
|
331
|
-
const decision = decideTransport({
|
|
332
|
-
forceDevice: transportOverride === "device",
|
|
333
|
-
forceLoopback: transportOverride === "loopback",
|
|
334
|
-
});
|
|
335
|
-
if (decision.transport === "device") {
|
|
336
|
-
return connectByDevice(target, platformUrl, cwd, decision.reason);
|
|
337
|
-
}
|
|
338
|
-
let listener;
|
|
339
|
-
try {
|
|
340
|
-
listener = await startLoopback();
|
|
341
|
-
}
|
|
342
|
-
catch (err) {
|
|
343
|
-
if (transportOverride === "loopback")
|
|
344
|
-
throw err;
|
|
345
|
-
log.warn(`Could not start the local loopback listener (${errorMessage(err)}) — ` +
|
|
346
|
-
"falling back to the device-code flow.");
|
|
347
|
-
return connectByDevice(target, platformUrl, cwd, "the loopback listener failed to start");
|
|
348
|
-
}
|
|
349
|
-
return connectOneClick(target, platformUrl, cwd, decision.reason, listener);
|
|
350
|
-
}
|
|
351
|
-
/** Step 2 (paste branch): the original manual flow — client ID/secret against the DevDogs API. */
|
|
352
|
-
async function connectByPasting(baseUrlOverride) {
|
|
353
|
-
// Not prompted: contributors always want the canonical API. `--base-url` /
|
|
354
|
-
// `OAUTH_BASE_URL` stay as overrides for testing against another project.
|
|
355
|
-
const baseUrl = (baseUrlOverride ??
|
|
356
|
-
process.env[ENV_KEYS.baseUrl] ??
|
|
357
|
-
DEFAULT_API_URL).replace(/\/+$/, "");
|
|
358
|
-
let clientId = nonEmptyEnv(ENV_KEYS.clientId);
|
|
359
|
-
let clientSecret = nonEmptyEnv(ENV_KEYS.clientSecret);
|
|
360
|
-
if (clientId && clientSecret) {
|
|
361
|
-
const useSaved = unwrap(await confirm({
|
|
362
|
-
message: `Use saved credentials? (client ID: ${clientId})`,
|
|
363
|
-
initialValue: true,
|
|
364
|
-
}));
|
|
365
|
-
if (!useSaved) {
|
|
366
|
-
clientId = undefined;
|
|
367
|
-
clientSecret = undefined;
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
if (!clientId || !clientSecret) {
|
|
371
|
-
note(`1. Visit ${WEBSITE_URL}/tools/oauth\n` +
|
|
372
|
-
`2. Sign in — link your GitHub account if prompted\n` +
|
|
373
|
-
`3. Enable OAuth and copy your Client ID and Client Secret\n` +
|
|
374
|
-
` (the secret is shown once, immediately after you enable it)`, "Register an OAuth client");
|
|
375
|
-
clientId = unwrap(await text({
|
|
376
|
-
message: "Client ID",
|
|
377
|
-
validate: (v) => (v?.trim() ? undefined : "Required"),
|
|
378
|
-
})).trim();
|
|
379
|
-
clientSecret = unwrap(await password({
|
|
380
|
-
message: "Client Secret",
|
|
381
|
-
validate: (v) => (v?.trim() ? undefined : "Required"),
|
|
382
|
-
})).trim();
|
|
383
|
-
}
|
|
384
|
-
// Supabase Auth's own OIDC provider refuses to register a custom provider
|
|
385
|
-
// whose declared issuer disagrees with what ITS discovery document
|
|
386
|
-
// reports — and that need not be `${baseUrl}/auth/v1`; Supabase may
|
|
387
|
-
// advertise its project ref host there instead of the custom domain this
|
|
388
|
-
// ran against. Reading the issuer from discovery, rather than assuming
|
|
389
|
-
// it, is what keeps this correct either way — see `discovery.ts`'s
|
|
390
|
-
// header. (The one-click branch skips this entirely: the exchange
|
|
391
|
-
// response carries the issuer directly.)
|
|
392
|
-
const discoverySpinner = spinner();
|
|
393
|
-
discoverySpinner.start("Discovering issuer");
|
|
394
|
-
let issuer;
|
|
395
|
-
try {
|
|
396
|
-
issuer = await fetchIssuer(baseUrl);
|
|
397
|
-
discoverySpinner.stop(`Discovered issuer: ${issuer}`);
|
|
398
|
-
}
|
|
399
|
-
catch (err) {
|
|
400
|
-
discoverySpinner.stop("Could not discover the issuer");
|
|
401
|
-
if (err instanceof DiscoveryError)
|
|
402
|
-
bail(err.message);
|
|
403
|
-
throw err;
|
|
404
|
-
}
|
|
405
|
-
return { clientId, clientSecret, issuer };
|
|
406
|
-
}
|
|
407
|
-
/**
|
|
408
|
-
* The wizard. `baseUrlOverride`/`platformUrlOverride`, when given, replace
|
|
409
|
-
* the canonical API and platform URLs (used by "Paste credentials instead"
|
|
410
|
-
* and the one-click flow respectively). `transportOverride` (`--device`/
|
|
411
|
-
* `--loopback`) forces the one-click flow's transport — see
|
|
412
|
-
* `connectViaOneClick` — and is ignored on the "Paste credentials instead"
|
|
413
|
-
* branch, which has no transport of its own.
|
|
414
|
-
*
|
|
415
|
-
* Intro and outro are the caller's, so this composes into the devtools menu
|
|
416
|
-
* without drawing a second box inside the first.
|
|
417
|
-
*/
|
|
418
|
-
export async function runOAuthSetup(baseUrlOverride, platformUrlOverride, transportOverride,
|
|
419
|
-
/**
|
|
420
|
-
* A target already resolved by a caller — `setup.ts`'s hosted wizard,
|
|
421
|
-
* chaining in with the same project/service-role key a contributor just
|
|
422
|
-
* typed, so they are not asked to pick "hosted" and paste the key a
|
|
423
|
-
* second time. Skips Step 1 entirely when given.
|
|
424
|
-
*/
|
|
425
|
-
presetTarget) {
|
|
426
|
-
const cwd = process.cwd();
|
|
427
|
-
log.info('Configures a Supabase project to support "Sign in with DevDogs".');
|
|
428
|
-
// ── Step 1: Choose target ────────────────────────────────────────────────
|
|
429
|
-
const target = presetTarget ?? (await chooseTarget(cwd));
|
|
430
|
-
// ── Step 2: Connect ──────────────────────────────────────────────────────
|
|
431
|
-
const providerName = unwrap(await text({
|
|
432
|
-
message: "Provider display name",
|
|
433
|
-
initialValue: process.env[ENV_KEYS.providerName] ?? PROVIDER_NAME,
|
|
434
|
-
placeholder: PROVIDER_NAME,
|
|
435
|
-
}));
|
|
436
|
-
const connectMethod = unwrap(await select({
|
|
437
|
-
message: "How do you want to connect?",
|
|
438
|
-
options: [
|
|
439
|
-
{
|
|
440
|
-
value: "one-click",
|
|
441
|
-
label: "One-click connect",
|
|
442
|
-
hint: "opens your browser — recommended",
|
|
443
|
-
},
|
|
444
|
-
{
|
|
445
|
-
value: "paste",
|
|
446
|
-
label: "Paste credentials instead",
|
|
447
|
-
hint: "register an OAuth client on the website by hand",
|
|
448
|
-
},
|
|
449
|
-
],
|
|
450
|
-
}));
|
|
451
|
-
const usedOneClick = connectMethod === "one-click";
|
|
452
|
-
const { clientId, clientSecret, issuer } = usedOneClick
|
|
453
|
-
? await connectViaOneClick(target, platformUrlOverride ?? process.env[ENV_KEYS.platformUrl] ?? WEBSITE_URL, cwd, transportOverride)
|
|
454
|
-
: await connectByPasting(baseUrlOverride);
|
|
455
|
-
// ── Step 3: Configure — resolve the provider identifier, then upsert ──────
|
|
456
|
-
let identifier = PROVIDER_IDENTIFIER;
|
|
457
|
-
{
|
|
458
|
-
const s = spinner();
|
|
459
|
-
s.start(`Checking for existing ${identifier} provider`);
|
|
460
|
-
let existing;
|
|
461
|
-
try {
|
|
462
|
-
existing = await checkProvider(target, identifier);
|
|
463
|
-
s.stop(existing.exists
|
|
464
|
-
? `Found existing ${identifier} provider (${existing.name ?? "unnamed"})`
|
|
465
|
-
: `No existing ${identifier} provider — will create`);
|
|
466
|
-
}
|
|
467
|
-
catch (err) {
|
|
468
|
-
s.stop("Failed to check for existing provider");
|
|
469
|
-
throw err;
|
|
470
|
-
}
|
|
471
|
-
if (existing.exists) {
|
|
472
|
-
const action = unwrap(await select({
|
|
473
|
-
message: `Provider "${identifier}" already exists. What would you like to do?`,
|
|
474
|
-
options: [
|
|
475
|
-
{
|
|
476
|
-
value: "update",
|
|
477
|
-
label: "Update existing provider",
|
|
478
|
-
hint: "Overwrites client ID, secret, name, and issuer",
|
|
479
|
-
},
|
|
480
|
-
{
|
|
481
|
-
value: "new",
|
|
482
|
-
label: "Add a new provider with a different identifier",
|
|
483
|
-
hint: `e.g. custom:devdogsuga-staging`,
|
|
484
|
-
},
|
|
485
|
-
],
|
|
486
|
-
}));
|
|
487
|
-
if (action === "new") {
|
|
488
|
-
const suffix = unwrap(await text({
|
|
489
|
-
message: `New identifier suffix — will be registered as "custom:<suffix>"`,
|
|
490
|
-
placeholder: "devdogsuga-staging",
|
|
491
|
-
validate: (v) => {
|
|
492
|
-
if (!v?.trim())
|
|
493
|
-
return "Required";
|
|
494
|
-
if (/[^a-z0-9-]/.test(v.trim()))
|
|
495
|
-
return "Use only lowercase letters, numbers, and hyphens";
|
|
496
|
-
},
|
|
497
|
-
})).trim();
|
|
498
|
-
identifier = `custom:${suffix}`;
|
|
499
|
-
}
|
|
500
|
-
}
|
|
501
|
-
}
|
|
502
|
-
const s = spinner();
|
|
503
|
-
s.start(`Configuring ${identifier}`);
|
|
504
|
-
const row = await upsertDevDogsProvider(target, {
|
|
505
|
-
identifier,
|
|
506
|
-
name: providerName,
|
|
507
|
-
clientId,
|
|
508
|
-
clientSecret,
|
|
509
|
-
issuer,
|
|
510
|
-
});
|
|
511
|
-
s.stop(`Configured ${row.identifier} (issuer: ${row.issuer})`);
|
|
512
|
-
// ── Step 4: Finish — persist to .env.local, print next steps ─────────────
|
|
513
|
-
const envValues = {
|
|
514
|
-
[ENV_KEYS.providerName]: providerName,
|
|
515
|
-
[ENV_KEYS.clientId]: clientId,
|
|
516
|
-
// The secret key never rides in a value string that ships to a client —
|
|
517
|
-
// `.env.local` is a server-side/CLI file, never bundled, the same
|
|
518
|
-
// guarantee the original manual flow already carried; this branch adds
|
|
519
|
-
// nothing new to that boundary.
|
|
520
|
-
[ENV_KEYS.clientSecret]: clientSecret,
|
|
521
|
-
};
|
|
522
|
-
if (!usedOneClick) {
|
|
523
|
-
envValues[ENV_KEYS.baseUrl] =
|
|
524
|
-
baseUrlOverride ?? process.env[ENV_KEYS.baseUrl] ?? DEFAULT_API_URL;
|
|
525
|
-
}
|
|
526
|
-
upsertEnvLocal(cwd, envValues);
|
|
527
|
-
log.success("Credentials saved to .env.local");
|
|
528
|
-
const callbackUri = `${target.apiUrl}/auth/v1/callback`;
|
|
529
|
-
// The one-click flow already told the platform this callback URL as part
|
|
530
|
-
// of `/tools/oauth/connect` (`callback_uri`), so there is nothing left to
|
|
531
|
-
// register by hand. Only the manual "paste credentials" path — which
|
|
532
|
-
// created its OAuth client on the website, where redirect URIs are a
|
|
533
|
-
// separate step — still needs this.
|
|
534
|
-
if (!usedOneClick) {
|
|
535
|
-
const alreadyRegistered = unwrap(await confirm({
|
|
536
|
-
message: "Have you already registered your Supabase callback URL with DevDogs?",
|
|
537
|
-
initialValue: false,
|
|
538
|
-
}));
|
|
539
|
-
if (!alreadyRegistered) {
|
|
540
|
-
const keysUrl = `${WEBSITE_URL}/tools/oauth?add_redirect_uri=${encodeURIComponent(callbackUri)}`;
|
|
541
|
-
log.info(`Opening ${keysUrl}`);
|
|
542
|
-
openBrowser(keysUrl);
|
|
543
|
-
}
|
|
544
|
-
if (!alreadyRegistered) {
|
|
545
|
-
note(`Finish registering your callback URL in the browser that just opened:\n` +
|
|
546
|
-
` ${callbackUri}`, "One more step");
|
|
547
|
-
}
|
|
548
|
-
}
|
|
549
|
-
const redirectStep = target.kind === "hosted"
|
|
550
|
-
? [
|
|
551
|
-
`Add your app's localhost redirect URL in the dashboard:`,
|
|
552
|
-
` Auth -> URL Configuration -> Redirect URLs -> add http://localhost:<port>/**`,
|
|
553
|
-
` (schedule-builder is 3001, platform is 3000)`,
|
|
554
|
-
]
|
|
555
|
-
: [
|
|
556
|
-
`Make sure your project's supabase/config.toml allows your app callback:`,
|
|
557
|
-
` additional_redirect_urls = ["http://localhost:<port>/auth/callback"]`,
|
|
558
|
-
];
|
|
559
|
-
const nextSteps = [
|
|
560
|
-
...redirectStep,
|
|
561
|
-
``,
|
|
562
|
-
`Trigger sign-in from your app:`,
|
|
563
|
-
``,
|
|
564
|
-
` await supabase.auth.signInWithOAuth({`,
|
|
565
|
-
` provider: "${identifier}",`,
|
|
566
|
-
` options: { redirectTo: \`\${origin}/auth/callback\` },`,
|
|
567
|
-
` });`,
|
|
568
|
-
];
|
|
569
|
-
if (target.kind === "local") {
|
|
570
|
-
nextSteps.push(``,
|
|
571
|
-
// The provider row lives in `auth.custom_oauth_providers`, which a
|
|
572
|
-
// plain `supabase stop`/`supabase start` leaves alone — but `supabase
|
|
573
|
-
// db reset` and `supabase stop --no-backup` both erase it along with
|
|
574
|
-
// the rest of the database, silently turning this sign-in button off.
|
|
575
|
-
`Re-run \`devtools oauth\` after \`supabase db reset\` or \`supabase stop --no-backup\` —`, `either wipes this provider along with the rest of the local database.`);
|
|
576
|
-
}
|
|
577
|
-
note(nextSteps.join("\n"), "Next steps");
|
|
578
|
-
}
|
package/dist/persona.d.ts
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import type { DbConnection } from "./db/connection.js";
|
|
2
|
-
export declare const PERSONA_KINDS: readonly ["member", "moderator"];
|
|
3
|
-
export type PersonaKind = (typeof PERSONA_KINDS)[number];
|
|
4
|
-
export declare function isPersonaKind(value: string): value is PersonaKind;
|
|
5
|
-
/**
|
|
6
|
-
* Refuses outside development — a staging or production database has real
|
|
7
|
-
* members on it, and this command exists to populate one that does not.
|
|
8
|
-
* Exported so `cli.ts`'s `moderation check` (also development-only now, for
|
|
9
|
-
* the same reason) shares the exact refusal wording.
|
|
10
|
-
*/
|
|
11
|
-
export declare function refuseUnlessDevelopment(connection: DbConnection, label: string): boolean;
|
|
12
|
-
export interface CreatedPersona {
|
|
13
|
-
email: string;
|
|
14
|
-
password: string;
|
|
15
|
-
userId: string;
|
|
16
|
-
preferredName: string;
|
|
17
|
-
}
|
|
18
|
-
export declare function runPersona(argv: string[]): Promise<void>;
|