settld 0.2.3 → 0.2.4
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/package.json +1 -1
- package/scripts/setup/login.mjs +44 -16
- package/scripts/setup/onboard.mjs +20 -15
package/package.json
CHANGED
package/scripts/setup/login.mjs
CHANGED
|
@@ -39,6 +39,7 @@ function readArgValue(argv, index, rawArg) {
|
|
|
39
39
|
function parseArgs(argv) {
|
|
40
40
|
const out = {
|
|
41
41
|
baseUrl: "https://api.settld.work",
|
|
42
|
+
baseUrlProvided: false,
|
|
42
43
|
tenantId: "",
|
|
43
44
|
email: "",
|
|
44
45
|
company: "",
|
|
@@ -64,6 +65,7 @@ function parseArgs(argv) {
|
|
|
64
65
|
if (arg === "--base-url" || arg.startsWith("--base-url=")) {
|
|
65
66
|
const parsed = readArgValue(argv, i, arg);
|
|
66
67
|
out.baseUrl = parsed.value;
|
|
68
|
+
out.baseUrlProvided = true;
|
|
67
69
|
i = parsed.nextIndex;
|
|
68
70
|
continue;
|
|
69
71
|
}
|
|
@@ -149,6 +151,20 @@ async function requestJson(url, { method, body, headers = {}, fetchImpl = fetch
|
|
|
149
151
|
return { res, text, json };
|
|
150
152
|
}
|
|
151
153
|
|
|
154
|
+
function responseCode({ json }) {
|
|
155
|
+
const direct = typeof json?.code === "string" ? json.code : "";
|
|
156
|
+
if (direct) return direct;
|
|
157
|
+
const error = typeof json?.error === "string" ? json.error : "";
|
|
158
|
+
return error ? error.toUpperCase() : "";
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function responseMessage({ json, text }, fallback = "unknown error") {
|
|
162
|
+
if (typeof json?.message === "string" && json.message.trim()) return json.message.trim();
|
|
163
|
+
if (typeof json?.error === "string" && json.error.trim()) return json.error.trim();
|
|
164
|
+
const raw = String(text ?? "").trim();
|
|
165
|
+
return raw || fallback;
|
|
166
|
+
}
|
|
167
|
+
|
|
152
168
|
async function promptLine(rl, label, { defaultValue = "", required = true } = {}) {
|
|
153
169
|
const suffix = defaultValue ? ` [${defaultValue}]` : "";
|
|
154
170
|
const value = String(await rl.question(`${label}${suffix}: `) ?? "").trim() || String(defaultValue ?? "").trim();
|
|
@@ -190,7 +206,9 @@ export async function runLogin({
|
|
|
190
206
|
const rl = interactive ? createInterface({ input: stdin, output: stdout }) : null;
|
|
191
207
|
try {
|
|
192
208
|
if (interactive) {
|
|
193
|
-
|
|
209
|
+
if (!args.baseUrlProvided) {
|
|
210
|
+
state.baseUrl = await promptLine(rl, "Settld base URL", { defaultValue: state.baseUrl || "https://api.settld.work" });
|
|
211
|
+
}
|
|
194
212
|
state.tenantId = await promptLine(rl, "Tenant ID (optional, leave blank to create new)", {
|
|
195
213
|
defaultValue: state.tenantId,
|
|
196
214
|
required: false
|
|
@@ -205,7 +223,20 @@ export async function runLogin({
|
|
|
205
223
|
if (!state.email) throw new Error("email is required");
|
|
206
224
|
if (!state.tenantId && !state.company) throw new Error("company is required when tenant ID is omitted");
|
|
207
225
|
|
|
226
|
+
const requestTenantOtp = async (tenantId) => {
|
|
227
|
+
const otpRequest = await requestJson(`${baseUrl}/v1/tenants/${encodeURIComponent(String(tenantId ?? ""))}/buyer/login/otp`, {
|
|
228
|
+
method: "POST",
|
|
229
|
+
body: { email: state.email },
|
|
230
|
+
fetchImpl
|
|
231
|
+
});
|
|
232
|
+
if (!otpRequest.res.ok) {
|
|
233
|
+
const message = responseMessage(otpRequest);
|
|
234
|
+
throw new Error(`otp request failed (${otpRequest.res.status}): ${message}`);
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
|
|
208
238
|
let tenantId = state.tenantId;
|
|
239
|
+
let otpAlreadyIssued = false;
|
|
209
240
|
if (!tenantId) {
|
|
210
241
|
const signup = await requestJson(`${baseUrl}/v1/public/signup`, {
|
|
211
242
|
method: "POST",
|
|
@@ -213,27 +244,24 @@ export async function runLogin({
|
|
|
213
244
|
fetchImpl
|
|
214
245
|
});
|
|
215
246
|
if (!signup.res.ok) {
|
|
216
|
-
const code =
|
|
217
|
-
const message =
|
|
247
|
+
const code = responseCode(signup);
|
|
248
|
+
const message = responseMessage(signup);
|
|
218
249
|
if (code === "SIGNUP_DISABLED") {
|
|
219
250
|
throw new Error("Public signup is disabled for this environment. Use an existing tenant ID or bootstrap key flow.");
|
|
220
251
|
}
|
|
221
|
-
|
|
252
|
+
if (signup.res.status === 403 && code === "FORBIDDEN") {
|
|
253
|
+
throw new Error(
|
|
254
|
+
"Public signup is unavailable on this base URL. Retry with --tenant-id <existing_tenant>, or in `settld setup` choose `Generate during setup` and provide an onboarding bootstrap API key."
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
throw new Error(`public signup failed (${signup.res.status}): ${message}`);
|
|
222
258
|
}
|
|
223
259
|
tenantId = String(signup.json?.tenantId ?? "").trim();
|
|
224
260
|
if (!tenantId) throw new Error("public signup response missing tenantId");
|
|
261
|
+
otpAlreadyIssued = Boolean(signup.json?.otpIssued);
|
|
225
262
|
if (interactive) stdout.write(`Created tenant: ${tenantId}\n`);
|
|
226
|
-
} else {
|
|
227
|
-
const otpRequest = await requestJson(`${baseUrl}/v1/tenants/${encodeURIComponent(tenantId)}/buyer/login/otp`, {
|
|
228
|
-
method: "POST",
|
|
229
|
-
body: { email: state.email },
|
|
230
|
-
fetchImpl
|
|
231
|
-
});
|
|
232
|
-
if (!otpRequest.res.ok) {
|
|
233
|
-
const message = typeof otpRequest.json?.message === "string" ? otpRequest.json.message : otpRequest.text;
|
|
234
|
-
throw new Error(`otp request failed (${otpRequest.res.status}): ${message || "unknown error"}`);
|
|
235
|
-
}
|
|
236
263
|
}
|
|
264
|
+
if (!otpAlreadyIssued) await requestTenantOtp(tenantId);
|
|
237
265
|
|
|
238
266
|
if (!state.otp && interactive) {
|
|
239
267
|
state.otp = await promptLine(rl, "OTP code", { required: true });
|
|
@@ -246,8 +274,8 @@ export async function runLogin({
|
|
|
246
274
|
fetchImpl
|
|
247
275
|
});
|
|
248
276
|
if (!login.res.ok) {
|
|
249
|
-
const message =
|
|
250
|
-
throw new Error(`login failed (${login.res.status}): ${message
|
|
277
|
+
const message = responseMessage(login);
|
|
278
|
+
throw new Error(`login failed (${login.res.status}): ${message}`);
|
|
251
279
|
}
|
|
252
280
|
const setCookie = login.res.headers.get("set-cookie") ?? "";
|
|
253
281
|
const cookie = cookieHeaderFromSetCookie(setCookie);
|
|
@@ -1361,21 +1361,26 @@ async function resolveRuntimeConfig({
|
|
|
1361
1361
|
{ defaultValue: canUseSavedSession ? "session" : "login", color }
|
|
1362
1362
|
);
|
|
1363
1363
|
if (keyMode === "login") {
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
savedSession
|
|
1377
|
-
|
|
1378
|
-
|
|
1364
|
+
try {
|
|
1365
|
+
await runLoginImpl({
|
|
1366
|
+
argv: ["--base-url", out.baseUrl, "--session-file", out.sessionFile],
|
|
1367
|
+
stdin,
|
|
1368
|
+
stdout,
|
|
1369
|
+
fetchImpl
|
|
1370
|
+
});
|
|
1371
|
+
const refreshedSession = await readSavedSessionImpl({ sessionPath: out.sessionFile });
|
|
1372
|
+
if (!refreshedSession) throw new Error("login did not produce a saved session");
|
|
1373
|
+
out.baseUrl = String(refreshedSession.baseUrl ?? out.baseUrl).trim() || out.baseUrl;
|
|
1374
|
+
out.tenantId = String(refreshedSession.tenantId ?? out.tenantId).trim();
|
|
1375
|
+
out.sessionCookie = String(refreshedSession.cookie ?? out.sessionCookie).trim();
|
|
1376
|
+
if (savedSession) {
|
|
1377
|
+
savedSession.baseUrl = refreshedSession.baseUrl;
|
|
1378
|
+
savedSession.tenantId = refreshedSession.tenantId;
|
|
1379
|
+
savedSession.cookie = refreshedSession.cookie;
|
|
1380
|
+
}
|
|
1381
|
+
} catch (err) {
|
|
1382
|
+
stdout.write(`Login failed: ${err?.message ?? "unknown error"}\n`);
|
|
1383
|
+
stdout.write("Choose `Generate during setup` if your deployment does not expose public signup/login.\n");
|
|
1379
1384
|
}
|
|
1380
1385
|
continue;
|
|
1381
1386
|
}
|