@hyper.authio/cli 0.0.0-stage → 0.11.0
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 +68 -2
- package/dist/cli.js +497 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.cjs +514 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +264 -0
- package/dist/index.d.ts +264 -0
- package/dist/index.js +489 -0
- package/dist/index.js.map +1 -0
- package/package.json +53 -4
package/dist/index.js
ADDED
|
@@ -0,0 +1,489 @@
|
|
|
1
|
+
import { spawn } from 'child_process';
|
|
2
|
+
import { createInterface } from 'readline';
|
|
3
|
+
import { existsSync, readFileSync, mkdirSync, writeFileSync, appendFileSync, chmodSync, statSync } from 'fs';
|
|
4
|
+
import { join, sep, dirname, resolve, normalize, isAbsolute } from 'path';
|
|
5
|
+
import { randomBytes } from 'crypto';
|
|
6
|
+
|
|
7
|
+
// src/errors.ts
|
|
8
|
+
var AuthioCliError = class extends Error {
|
|
9
|
+
/** What went wrong, stable across versions. */
|
|
10
|
+
code;
|
|
11
|
+
/** The process exit code the CLI ends with. */
|
|
12
|
+
exitCode;
|
|
13
|
+
constructor(code, message, options) {
|
|
14
|
+
super(message, options);
|
|
15
|
+
this.name = "AuthioCliError";
|
|
16
|
+
this.code = code;
|
|
17
|
+
this.exitCode = options?.exitCode ?? 1;
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
// src/args.ts
|
|
22
|
+
var DEFAULT_API = "https://authio.api.hyperio.tech";
|
|
23
|
+
var VALUE_OPTIONS = /* @__PURE__ */ new Set(["--ticket", "--verification", "--api", "--cwd"]);
|
|
24
|
+
var FLAG_OPTIONS = /* @__PURE__ */ new Set(["--dry-run", "--yes", "-y", "--wait", "--help", "-h", "--version", "-v"]);
|
|
25
|
+
function parseArguments(argv) {
|
|
26
|
+
const values = /* @__PURE__ */ new Map();
|
|
27
|
+
const flags = /* @__PURE__ */ new Set();
|
|
28
|
+
const positional = [];
|
|
29
|
+
for (let index = 0; index < argv.length; index++) {
|
|
30
|
+
const current = argv[index];
|
|
31
|
+
if (!current.startsWith("-")) {
|
|
32
|
+
positional.push(current);
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
const [name, inline] = current.includes("=") ? [current.slice(0, current.indexOf("=")), current.slice(current.indexOf("=") + 1)] : [current, void 0];
|
|
36
|
+
if (VALUE_OPTIONS.has(name)) {
|
|
37
|
+
const value = inline ?? argv[++index];
|
|
38
|
+
if (value === void 0 || value.startsWith("--")) throw new AuthioCliError("invalid_arguments", `${name} needs a value.`);
|
|
39
|
+
values.set(name, value);
|
|
40
|
+
} else if (FLAG_OPTIONS.has(name) && inline === void 0) {
|
|
41
|
+
flags.add(name);
|
|
42
|
+
} else {
|
|
43
|
+
throw new AuthioCliError("invalid_arguments", `Unknown option ${current}. Run "authio --help" to see the options.`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
const named = positional[0];
|
|
47
|
+
const command = flags.has("--help") || flags.has("-h") ? "help" : flags.has("--version") || flags.has("-v") ? "version" : named === void 0 ? "help" : named;
|
|
48
|
+
if (command !== "init" && command !== "verify" && command !== "help" && command !== "version")
|
|
49
|
+
throw new AuthioCliError("invalid_arguments", `Unknown command "${command}". Use "init" or "verify".`);
|
|
50
|
+
return {
|
|
51
|
+
command,
|
|
52
|
+
ticket: values.get("--ticket"),
|
|
53
|
+
verification: values.get("--verification"),
|
|
54
|
+
api: values.get("--api"),
|
|
55
|
+
cwd: values.get("--cwd"),
|
|
56
|
+
dryRun: flags.has("--dry-run"),
|
|
57
|
+
yes: flags.has("--yes") || flags.has("-y"),
|
|
58
|
+
wait: flags.has("--wait")
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
function apiAddress(api) {
|
|
62
|
+
const address = (api ?? DEFAULT_API).trim().replace(/\/+$/, "");
|
|
63
|
+
if (!/^https?:\/\/[^/\s]+/i.test(address)) throw new AuthioCliError("invalid_arguments", `--api must be an http or https address, not "${api}".`);
|
|
64
|
+
return address;
|
|
65
|
+
}
|
|
66
|
+
var HELP = `Authio CLI
|
|
67
|
+
|
|
68
|
+
Usage
|
|
69
|
+
npx @hyper.authio/cli init --ticket ait_... Apply a client's integration plan to this project
|
|
70
|
+
npx @hyper.authio/cli verify [--wait] Check whether the first sign-in has arrived
|
|
71
|
+
|
|
72
|
+
Options
|
|
73
|
+
--ticket <code> The one-time code from the client's guide in the console (or AUTHIO_TICKET)
|
|
74
|
+
--api <url> The Authio API address (default ${DEFAULT_API})
|
|
75
|
+
--cwd <dir> The project folder (default: the current folder)
|
|
76
|
+
--dry-run Show the plan without writing anything (the code is still used up)
|
|
77
|
+
--yes, -y Answer yes to every question; required without an interactive terminal
|
|
78
|
+
--verification <code> The verification code (default: the one init saved in .authio/verification)
|
|
79
|
+
--wait With verify: keep checking until the first sign-in arrives
|
|
80
|
+
|
|
81
|
+
The CLI never overwrites a file, never commits and never sends your code anywhere.`;
|
|
82
|
+
function ask(question, hidden) {
|
|
83
|
+
const input = process.stdin;
|
|
84
|
+
const output = process.stdout;
|
|
85
|
+
if (!hidden || !input.isTTY) {
|
|
86
|
+
const lines = createInterface({ input, output });
|
|
87
|
+
return new Promise(
|
|
88
|
+
(resolve2) => lines.question(question, (answer) => {
|
|
89
|
+
lines.close();
|
|
90
|
+
resolve2(answer);
|
|
91
|
+
})
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
return new Promise((resolve2, reject) => {
|
|
95
|
+
let value = "";
|
|
96
|
+
output.write(question);
|
|
97
|
+
input.setRawMode(true);
|
|
98
|
+
input.resume();
|
|
99
|
+
input.setEncoding("utf8");
|
|
100
|
+
const finish = () => {
|
|
101
|
+
input.setRawMode(false);
|
|
102
|
+
input.pause();
|
|
103
|
+
input.removeListener("data", onData);
|
|
104
|
+
output.write("\n");
|
|
105
|
+
};
|
|
106
|
+
const onData = (chunk) => {
|
|
107
|
+
for (const character of chunk) {
|
|
108
|
+
if (character === "\r" || character === "\n") {
|
|
109
|
+
finish();
|
|
110
|
+
resolve2(value);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
if (character === "") {
|
|
114
|
+
finish();
|
|
115
|
+
reject(new Error("Cancelled."));
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
if (character === "\x7F" || character === "\b") value = value.slice(0, -1);
|
|
119
|
+
else if (character >= " ") value += character;
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
input.on("data", onData);
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
function windowsCommandLine(command, args) {
|
|
126
|
+
return [command, ...args.map((arg) => `"${arg.replace(/"/g, '""')}"`)].join(" ");
|
|
127
|
+
}
|
|
128
|
+
function createTerminalIo() {
|
|
129
|
+
return {
|
|
130
|
+
fetch: globalThis.fetch.bind(globalThis),
|
|
131
|
+
print: (line) => process.stdout.write(`${line}
|
|
132
|
+
`),
|
|
133
|
+
confirm: async (question, defaultYes) => {
|
|
134
|
+
const answer = (await ask(`${question} ${defaultYes ? "[Y/n]" : "[y/N]"} `, false)).trim().toLowerCase();
|
|
135
|
+
return answer === "" ? defaultYes : answer === "y" || answer === "yes";
|
|
136
|
+
},
|
|
137
|
+
promptSecret: (question) => ask(question, true),
|
|
138
|
+
run: (command, args, cwd) => new Promise((resolve2) => {
|
|
139
|
+
const child = process.platform === "win32" ? spawn(windowsCommandLine(command, args), { cwd, stdio: "inherit", shell: true }) : spawn(command, [...args], { cwd, stdio: "inherit" });
|
|
140
|
+
child.on("error", () => resolve2(127));
|
|
141
|
+
child.on("close", (code) => resolve2(code ?? 1));
|
|
142
|
+
}),
|
|
143
|
+
isInteractive: Boolean(process.stdin.isTTY && process.stdout.isTTY),
|
|
144
|
+
now: () => Date.now(),
|
|
145
|
+
sleep: (milliseconds) => new Promise((resolve2) => setTimeout(resolve2, milliseconds))
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
var isDirectory = (path) => existsSync(path) && statSync(path).isDirectory();
|
|
149
|
+
function majorOf(range) {
|
|
150
|
+
const match = /(\d+)/.exec(range ?? "");
|
|
151
|
+
return match ? Number(match[1]) : void 0;
|
|
152
|
+
}
|
|
153
|
+
function detectProject(root) {
|
|
154
|
+
const manifestPath = join(root, "package.json");
|
|
155
|
+
if (!existsSync(manifestPath))
|
|
156
|
+
throw new AuthioCliError("project_not_found", `No package.json in ${root}. Run the command in your Next.js project folder, or pass --cwd.`);
|
|
157
|
+
let manifest;
|
|
158
|
+
try {
|
|
159
|
+
manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
|
|
160
|
+
} catch (cause) {
|
|
161
|
+
throw new AuthioCliError("project_not_found", `${manifestPath} could not be read as JSON.`, { cause });
|
|
162
|
+
}
|
|
163
|
+
const dependencies = { ...manifest.devDependencies, ...manifest.dependencies };
|
|
164
|
+
const nextMajor = majorOf(dependencies.next);
|
|
165
|
+
if (nextMajor === void 0)
|
|
166
|
+
throw new AuthioCliError("stack_not_detected", "This project does not depend on next. The plan this CLI applies is for Next.js with the App Router and Auth.js v5; for another stack, follow the client's guide in the console.");
|
|
167
|
+
const nextAuthMajor = majorOf(dependencies["next-auth"]);
|
|
168
|
+
if (nextAuthMajor !== void 0 && nextAuthMajor < 5)
|
|
169
|
+
throw new AuthioCliError("nextauth_v4_unsupported", `This project uses next-auth ${dependencies["next-auth"]}. The plan is written for Auth.js v5; follow the NextAuth v4 recipe in the client's guide instead.`);
|
|
170
|
+
const srcDir = isDirectory(join(root, "src", "app"));
|
|
171
|
+
const appDir = srcDir || isDirectory(join(root, "app"));
|
|
172
|
+
if (!appDir) {
|
|
173
|
+
const pages = isDirectory(join(root, "pages")) || isDirectory(join(root, "src", "pages"));
|
|
174
|
+
throw new AuthioCliError(
|
|
175
|
+
pages ? "pages_router_unsupported" : "stack_not_detected",
|
|
176
|
+
pages ? "This project uses the pages router. The plan is written for the App Router; follow the client's guide in the console instead." : "No app/ or src/app/ folder was found. Create the App Router folder first, or run the command in the app's folder."
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
const packageManager = existsSync(join(root, "pnpm-lock.yaml")) ? "pnpm" : existsSync(join(root, "yarn.lock")) ? "yarn" : existsSync(join(root, "bun.lockb")) || existsSync(join(root, "bun.lock")) ? "bun" : "npm";
|
|
180
|
+
const port = /(?:^|\s)(?:-p|--port)(?:\s+|=)(\d{2,5})(?:\s|$)/.exec(manifest.scripts?.dev ?? "");
|
|
181
|
+
return { root, nextMajor, srcDir, packageManager, devPort: port ? Number(port[1]) : 3e3 };
|
|
182
|
+
}
|
|
183
|
+
var SUPPORTED_PLAN_VERSIONS = [1];
|
|
184
|
+
function normalizePlan(plan) {
|
|
185
|
+
const lower = (value, fallback) => value ? String(value).toLowerCase() : fallback;
|
|
186
|
+
return {
|
|
187
|
+
...plan,
|
|
188
|
+
packages: plan.packages ?? [],
|
|
189
|
+
redirectUris: plan.redirectUris ?? [],
|
|
190
|
+
files: (plan.files ?? []).map((file) => ({ ...file, root: lower(file.root, "project"), content: file.content ?? "" })),
|
|
191
|
+
environment: (plan.environment ?? []).map((variable) => ({
|
|
192
|
+
...variable,
|
|
193
|
+
source: lower(variable.source, "secret")
|
|
194
|
+
}))
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
function assertPlanVersion(plan) {
|
|
198
|
+
if (!SUPPORTED_PLAN_VERSIONS.includes(plan.planVersion))
|
|
199
|
+
throw new AuthioCliError(
|
|
200
|
+
"plan_version_unsupported",
|
|
201
|
+
`This plan is version ${plan.planVersion}, which this CLI does not know. Run "npx @hyper.authio/cli@latest" with a new code.`
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
function placePath(project, file) {
|
|
205
|
+
const relative = normalize(file.path).replace(/\\/g, "/");
|
|
206
|
+
if (!relative || isAbsolute(file.path) || /^[a-zA-Z]:/.test(file.path) || relative.split("/").includes(".."))
|
|
207
|
+
throw new AuthioCliError("plan_path_invalid", `The plan names a path outside the project ("${file.path}"); nothing was written.`);
|
|
208
|
+
return file.root === "source" && project.srcDir ? `src/${relative}` : relative;
|
|
209
|
+
}
|
|
210
|
+
function readEnvKeys(path) {
|
|
211
|
+
if (!existsSync(path)) return /* @__PURE__ */ new Set();
|
|
212
|
+
const keys = /* @__PURE__ */ new Set();
|
|
213
|
+
for (const line of readFileSync(path, "utf8").split(/\r?\n/)) {
|
|
214
|
+
const match = /^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=/.exec(line);
|
|
215
|
+
if (match) keys.add(match[1]);
|
|
216
|
+
}
|
|
217
|
+
return keys;
|
|
218
|
+
}
|
|
219
|
+
var LOCAL_ORIGIN_VARIABLE = "AUTH_URL";
|
|
220
|
+
var LOCAL_ORIGIN_COMMENT = "Where this app runs on this machine; set the public address where it is deployed.";
|
|
221
|
+
function localOrigin(project) {
|
|
222
|
+
return `http://localhost:${project.devPort}`;
|
|
223
|
+
}
|
|
224
|
+
function planChanges(project, plan) {
|
|
225
|
+
const files = plan.files.map((file) => {
|
|
226
|
+
const path = placePath(project, file);
|
|
227
|
+
const target = join(project.root, ...path.split("/"));
|
|
228
|
+
if (!target.startsWith(project.root + sep) && target !== project.root)
|
|
229
|
+
throw new AuthioCliError("plan_path_invalid", `The plan names a path outside the project ("${file.path}"); nothing was written.`);
|
|
230
|
+
return { path, target, content: file.content, action: existsSync(target) ? "merge" : "create" };
|
|
231
|
+
});
|
|
232
|
+
const existing = readEnvKeys(join(project.root, ".env.local"));
|
|
233
|
+
const localRedirect = `${localOrigin(project)}${plan.callbackPath}`;
|
|
234
|
+
return {
|
|
235
|
+
files,
|
|
236
|
+
environment: plan.environment.filter((variable) => !existing.has(variable.name)).map((variable) => variable.name === LOCAL_ORIGIN_VARIABLE && variable.source === "plan" ? { ...variable, value: localOrigin(project), comment: LOCAL_ORIGIN_COMMENT } : variable),
|
|
237
|
+
keptEnvironment: plan.environment.filter((variable) => existing.has(variable.name)).map((variable) => variable.name),
|
|
238
|
+
localRedirect,
|
|
239
|
+
redirectRegistered: plan.redirectUris.some((uri) => uri.replace(/\/+$/, "") === localRedirect)
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
var IGNORED_PATHS = [".env.local", ".authio/"];
|
|
243
|
+
function patternCovers(pattern, path) {
|
|
244
|
+
const trimmed = pattern.trim();
|
|
245
|
+
if (!trimmed || trimmed.startsWith("#") || trimmed.startsWith("!")) return false;
|
|
246
|
+
const bare = trimmed.replace(/^\//, "");
|
|
247
|
+
const wantsDirectory = path.endsWith("/");
|
|
248
|
+
const name = path.replace(/\/$/, "");
|
|
249
|
+
if (bare.endsWith("/") && !wantsDirectory) return false;
|
|
250
|
+
const glob = bare.replace(/\/$/, "");
|
|
251
|
+
if (glob.includes("/")) return false;
|
|
252
|
+
const expression = new RegExp(`^${glob.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, "[^/]*").replace(/\?/g, "[^/]")}$`);
|
|
253
|
+
return expression.test(name);
|
|
254
|
+
}
|
|
255
|
+
function uncoveredPaths(path) {
|
|
256
|
+
const patterns = existsSync(path) ? readFileSync(path, "utf8").split(/\r?\n/) : [];
|
|
257
|
+
return IGNORED_PATHS.filter((ignored) => !patterns.some((pattern) => patternCovers(pattern, ignored)));
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// src/api.ts
|
|
261
|
+
async function post(fetcher, api, path, body, invalidCode) {
|
|
262
|
+
const url = `${api}${path}`;
|
|
263
|
+
let response;
|
|
264
|
+
try {
|
|
265
|
+
response = await fetcher(url, {
|
|
266
|
+
method: "POST",
|
|
267
|
+
headers: { "content-type": "application/json", accept: "application/json" },
|
|
268
|
+
body: JSON.stringify(body)
|
|
269
|
+
});
|
|
270
|
+
} catch (cause) {
|
|
271
|
+
throw new AuthioCliError("api_unreachable", `Could not reach Authio at ${api}. Check the address (--api) and your connection.`, { cause });
|
|
272
|
+
}
|
|
273
|
+
let envelope = {};
|
|
274
|
+
try {
|
|
275
|
+
envelope = await response.json();
|
|
276
|
+
} catch {
|
|
277
|
+
envelope = {};
|
|
278
|
+
}
|
|
279
|
+
if (response.status === 401)
|
|
280
|
+
throw new AuthioCliError(
|
|
281
|
+
invalidCode,
|
|
282
|
+
invalidCode === "ticket_invalid" ? "This code was already used or has expired. Generate another command in the client's guide in the Authio console." : "This verification has expired. The last step of the client's guide in the console still shows the first sign-in."
|
|
283
|
+
);
|
|
284
|
+
if (response.status === 429) throw new AuthioCliError("rate_limited", "Authio is refusing more attempts for now. Wait a minute and try again.");
|
|
285
|
+
if (!response.ok || envelope.data === void 0) {
|
|
286
|
+
const detail = envelope.notifications?.map((notification) => notification.message).filter(Boolean).join(" ");
|
|
287
|
+
throw new AuthioCliError("api_error", `Authio answered ${response.status}${envelope.errorCode ? ` (${envelope.errorCode})` : ""}${detail ? `: ${detail}` : "."}`);
|
|
288
|
+
}
|
|
289
|
+
return envelope.data;
|
|
290
|
+
}
|
|
291
|
+
async function redeemTicket(fetcher, api, ticket, nextMajor) {
|
|
292
|
+
const redemption = await post(fetcher, api, "/api/v1/integration-plans/redeem", { ticket, nextMajor }, "ticket_invalid");
|
|
293
|
+
return { ...redemption, plan: normalizePlan(redemption.plan) };
|
|
294
|
+
}
|
|
295
|
+
function checkVerification(fetcher, api, verification) {
|
|
296
|
+
return post(fetcher, api, "/api/v1/integration-plans/verify", { verification }, "verification_invalid");
|
|
297
|
+
}
|
|
298
|
+
var VERIFICATION_FILE = join(".authio", "verification");
|
|
299
|
+
function installCommand(manager, specs) {
|
|
300
|
+
return manager === "npm" ? ["npm", ["install", ...specs]] : [manager, ["add", ...specs]];
|
|
301
|
+
}
|
|
302
|
+
function envLine(name, value) {
|
|
303
|
+
return value.includes('"') ? `${name}='${value}'` : `${name}="${value}"`;
|
|
304
|
+
}
|
|
305
|
+
function describe(io, project, plan, changes) {
|
|
306
|
+
io.print("");
|
|
307
|
+
io.print(`Authio plan for client "${plan.client}" in realm "${plan.realm}" (${plan.stack})`);
|
|
308
|
+
io.print(`Project: Next.js ${project.nextMajor}${project.srcDir ? ", src/ folder" : ""}, ${project.packageManager}`);
|
|
309
|
+
io.print("");
|
|
310
|
+
for (const file of changes.files) {
|
|
311
|
+
io.print(file.action === "create" ? ` + ${file.path}` : ` ! ${file.path} already exists: left as it is, merge it by hand`);
|
|
312
|
+
for (const line of file.content.split(/\r?\n/)) io.print(` ${line}`);
|
|
313
|
+
}
|
|
314
|
+
io.print("");
|
|
315
|
+
io.print(" .env.local");
|
|
316
|
+
for (const variable of changes.environment) {
|
|
317
|
+
const how = variable.source === "secret" ? "asked for, hidden" : variable.source === "generate" ? "generated on this machine" : variable.value ?? "";
|
|
318
|
+
io.print(` + ${variable.name} ${variable.source === "plan" ? `= ${how}` : `(${how})`}`);
|
|
319
|
+
}
|
|
320
|
+
for (const name of changes.keptEnvironment) io.print(` = ${name} (already set, kept)`);
|
|
321
|
+
if (plan.packages.length > 0) io.print(`
|
|
322
|
+
Packages: ${plan.packages.map((item) => `${item.name}@${item.version}`).join(" ")}`);
|
|
323
|
+
if (!changes.redirectRegistered)
|
|
324
|
+
io.print(`
|
|
325
|
+
Warning: the client does not register ${changes.localRedirect}. Sign-in will fail until you add it as a redirect URI on the client in the Authio console.`);
|
|
326
|
+
if (!plan.clientActive) io.print("\n Warning: this client is inactive or in quarantine in Authio, so sign-in is refused until it is active again.");
|
|
327
|
+
io.print("");
|
|
328
|
+
}
|
|
329
|
+
async function valueOf(io, variable, emptySecrets) {
|
|
330
|
+
if (variable.source === "plan") return variable.value ?? "";
|
|
331
|
+
if (variable.source === "generate") return randomBytes(32).toString("base64");
|
|
332
|
+
if (!io.isInteractive) {
|
|
333
|
+
emptySecrets.push(variable.name);
|
|
334
|
+
return "";
|
|
335
|
+
}
|
|
336
|
+
const typed = (await io.promptSecret(`${variable.name}${variable.comment ? ` (${variable.comment})` : ""}, hidden, Enter to leave empty: `)).trim();
|
|
337
|
+
if (!typed) emptySecrets.push(variable.name);
|
|
338
|
+
return typed;
|
|
339
|
+
}
|
|
340
|
+
async function runInit(options, io) {
|
|
341
|
+
const result = { exitCode: 0, created: [], toMerge: [], addedEnvironment: [], emptySecrets: [], plan: void 0 };
|
|
342
|
+
const ticket = options.ticket?.trim();
|
|
343
|
+
if (!ticket)
|
|
344
|
+
throw new AuthioCliError("ticket_missing", "No code given. Open the client's guide in the Authio console, choose Next.js \xB7 Auth.js v5 and press Generate command.");
|
|
345
|
+
const project = detectProject(options.cwd);
|
|
346
|
+
if (!options.dryRun && !options.yes && !io.isInteractive)
|
|
347
|
+
throw new AuthioCliError("confirmation_required", "There is no terminal to confirm in. Run with --yes to apply the plan, or --dry-run to see it first.");
|
|
348
|
+
const redemption = await redeemTicket(io.fetch, options.api, ticket, project.nextMajor);
|
|
349
|
+
const plan = redemption.plan;
|
|
350
|
+
result.plan = plan;
|
|
351
|
+
assertPlanVersion(plan);
|
|
352
|
+
const changes = planChanges(project, plan);
|
|
353
|
+
result.toMerge = changes.files.filter((file) => file.action === "merge").map((file) => file.path);
|
|
354
|
+
describe(io, project, plan, changes);
|
|
355
|
+
if (options.dryRun) {
|
|
356
|
+
io.print("Dry run: nothing was written. The code is used up; generate another command to apply the plan.");
|
|
357
|
+
return result;
|
|
358
|
+
}
|
|
359
|
+
const creating = changes.files.filter((file) => file.action === "create");
|
|
360
|
+
const agreed = options.yes || await io.confirm(`Create ${creating.length} file(s) and add ${changes.environment.length} variable(s) to .env.local?`, true);
|
|
361
|
+
if (!agreed) {
|
|
362
|
+
io.print("Nothing was written.");
|
|
363
|
+
result.exitCode = 2;
|
|
364
|
+
return result;
|
|
365
|
+
}
|
|
366
|
+
const lines = [];
|
|
367
|
+
for (const variable of changes.environment) {
|
|
368
|
+
const value = await valueOf(io, variable, result.emptySecrets);
|
|
369
|
+
if (variable.comment) lines.push(`# ${variable.comment}`);
|
|
370
|
+
lines.push(envLine(variable.name, value));
|
|
371
|
+
result.addedEnvironment.push(variable.name);
|
|
372
|
+
}
|
|
373
|
+
for (const file of creating) {
|
|
374
|
+
mkdirSync(dirname(file.target), { recursive: true });
|
|
375
|
+
writeFileSync(file.target, file.content.endsWith("\n") ? file.content : `${file.content}
|
|
376
|
+
`, { flag: "wx" });
|
|
377
|
+
result.created.push(file.path);
|
|
378
|
+
}
|
|
379
|
+
if (lines.length > 0) {
|
|
380
|
+
const envPath = join(project.root, ".env.local");
|
|
381
|
+
const current = existsSync(envPath) ? readFileSync(envPath, "utf8") : "";
|
|
382
|
+
const separator = current && !current.endsWith("\n") ? "\n" : "";
|
|
383
|
+
appendFileSync(envPath, `${separator}${current ? "\n" : ""}# Added by @hyper.authio/cli for the Authio client "${plan.client}"
|
|
384
|
+
${lines.join("\n")}
|
|
385
|
+
`);
|
|
386
|
+
}
|
|
387
|
+
const gitignorePath = join(project.root, ".gitignore");
|
|
388
|
+
const missing = uncoveredPaths(gitignorePath);
|
|
389
|
+
if (missing.length > 0 && (options.yes || await io.confirm(`Add ${missing.join(" and ")} to .gitignore so they are never committed?`, true))) {
|
|
390
|
+
const current = existsSync(gitignorePath) ? readFileSync(gitignorePath, "utf8") : "";
|
|
391
|
+
appendFileSync(gitignorePath, `${current && !current.endsWith("\n") ? "\n" : ""}${missing.join("\n")}
|
|
392
|
+
`);
|
|
393
|
+
} else if (missing.length > 0) {
|
|
394
|
+
io.print(`Warning: ${missing.join(" and ")} ${missing.length > 1 ? "are" : "is"} not in .gitignore. Never commit ${IGNORED_PATHS.join(" or ")}.`);
|
|
395
|
+
}
|
|
396
|
+
const verificationPath = join(project.root, VERIFICATION_FILE);
|
|
397
|
+
mkdirSync(dirname(verificationPath), { recursive: true });
|
|
398
|
+
writeFileSync(verificationPath, JSON.stringify({ api: options.api, verification: redemption.verification, expiresAt: redemption.verificationExpiresAt }), {
|
|
399
|
+
mode: 384
|
|
400
|
+
});
|
|
401
|
+
try {
|
|
402
|
+
chmodSync(verificationPath, 384);
|
|
403
|
+
} catch {
|
|
404
|
+
}
|
|
405
|
+
if (plan.packages.length > 0) {
|
|
406
|
+
const [command, args] = installCommand(project.packageManager, plan.packages.map((item) => `${item.name}@${item.version}`));
|
|
407
|
+
const install = options.yes || await io.confirm(`Install ${plan.packages.map((item) => item.name).join(", ")} with ${command}?`, true);
|
|
408
|
+
if (install) {
|
|
409
|
+
const code = await io.run(command, args, project.root);
|
|
410
|
+
if (code !== 0) io.print(`The install failed (exit ${code}). Run it yourself: ${command} ${args.join(" ")}`);
|
|
411
|
+
} else {
|
|
412
|
+
io.print(`Install them when you are ready: ${command} ${args.join(" ")}`);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
io.print("");
|
|
416
|
+
io.print(`Created: ${result.created.length > 0 ? result.created.join(", ") : "nothing"}`);
|
|
417
|
+
if (result.toMerge.length > 0) io.print(`Merge by hand: ${result.toMerge.join(", ")} (the plan's version is printed above)`);
|
|
418
|
+
if (result.emptySecrets.length > 0) io.print(`Fill in .env.local: ${result.emptySecrets.join(", ")}`);
|
|
419
|
+
io.print(`Next: start the app, sign in, then run "npx @hyper.authio/cli verify".`);
|
|
420
|
+
return result;
|
|
421
|
+
}
|
|
422
|
+
var VERIFY_INTERVAL_MS = 3e3;
|
|
423
|
+
function readSaved(cwd) {
|
|
424
|
+
const path = join(cwd, VERIFICATION_FILE);
|
|
425
|
+
if (!existsSync(path)) return {};
|
|
426
|
+
try {
|
|
427
|
+
return JSON.parse(readFileSync(path, "utf8"));
|
|
428
|
+
} catch {
|
|
429
|
+
return {};
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
async function runVerify(options, io) {
|
|
433
|
+
const saved = readSaved(options.cwd);
|
|
434
|
+
const verification = options.verification ?? saved.verification;
|
|
435
|
+
const api = options.api ?? saved.api ?? options.defaultApi;
|
|
436
|
+
if (!verification)
|
|
437
|
+
throw new AuthioCliError("verification_missing", `No verification found. Run "authio init" first, or pass --verification.`);
|
|
438
|
+
const expiresAt = saved.expiresAt && !options.verification ? Date.parse(saved.expiresAt) : Number.NaN;
|
|
439
|
+
if (options.wait) io.print("Waiting for the first sign-in through the app (Ctrl+C to stop)\u2026");
|
|
440
|
+
for (; ; ) {
|
|
441
|
+
const answer = await checkVerification(io.fetch, api, verification);
|
|
442
|
+
if (answer.signedIn) {
|
|
443
|
+
io.print(`Signed in: the first sign-in arrived${answer.firstSignInAt ? ` at ${answer.firstSignInAt}` : ""}. The integration works.`);
|
|
444
|
+
return { exitCode: 0, firstSignInAt: answer.firstSignInAt };
|
|
445
|
+
}
|
|
446
|
+
if (!options.wait || !Number.isNaN(expiresAt) && io.now() + VERIFY_INTERVAL_MS >= expiresAt) {
|
|
447
|
+
io.print("No sign-in has arrived yet. Start the app, sign in, and run this again (or add --wait).");
|
|
448
|
+
return { exitCode: 2, firstSignInAt: null };
|
|
449
|
+
}
|
|
450
|
+
await io.sleep(VERIFY_INTERVAL_MS);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
async function main(argv, io, environment, version) {
|
|
454
|
+
try {
|
|
455
|
+
const args = parseArguments(argv);
|
|
456
|
+
const cwd = resolve(args.cwd ?? ".");
|
|
457
|
+
if (args.command === "help") {
|
|
458
|
+
io.print(HELP);
|
|
459
|
+
return 0;
|
|
460
|
+
}
|
|
461
|
+
if (args.command === "version") {
|
|
462
|
+
io.print(version);
|
|
463
|
+
return 0;
|
|
464
|
+
}
|
|
465
|
+
if (args.command === "init") {
|
|
466
|
+
const result2 = await runInit(
|
|
467
|
+
{ ticket: args.ticket ?? environment.AUTHIO_TICKET, api: apiAddress(args.api), cwd, dryRun: args.dryRun, yes: args.yes },
|
|
468
|
+
io
|
|
469
|
+
);
|
|
470
|
+
return result2.exitCode;
|
|
471
|
+
}
|
|
472
|
+
const result = await runVerify(
|
|
473
|
+
{ verification: args.verification, api: args.api ? apiAddress(args.api) : void 0, defaultApi: DEFAULT_API, cwd, wait: args.wait },
|
|
474
|
+
io
|
|
475
|
+
);
|
|
476
|
+
return result.exitCode;
|
|
477
|
+
} catch (error) {
|
|
478
|
+
if (error instanceof AuthioCliError) {
|
|
479
|
+
io.print(`Error (${error.code}): ${error.message}`);
|
|
480
|
+
return error.exitCode;
|
|
481
|
+
}
|
|
482
|
+
io.print(error instanceof Error ? error.stack ?? error.message : String(error));
|
|
483
|
+
return 1;
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
export { AuthioCliError, DEFAULT_API, IGNORED_PATHS, LOCAL_ORIGIN_VARIABLE, SUPPORTED_PLAN_VERSIONS, VERIFICATION_FILE, VERIFY_INTERVAL_MS, apiAddress, assertPlanVersion, checkVerification, createTerminalIo, detectProject, installCommand, localOrigin, main, normalizePlan, parseArguments, planChanges, readEnvKeys, redeemTicket, runInit, runVerify, uncoveredPaths, windowsCommandLine };
|
|
488
|
+
//# sourceMappingURL=index.js.map
|
|
489
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/errors.ts","../src/args.ts","../src/io.ts","../src/project.ts","../src/plan.ts","../src/gitignore.ts","../src/api.ts","../src/init.ts","../src/verify.ts","../src/main.ts"],"names":["resolve","existsSync","readFileSync","join","result"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Why the CLI stopped. Each one is a sentence the person can act on; the code is stable for scripts and agents.\n */\nexport type AuthioCliErrorCode =\n | \"ticket_missing\"\n | \"ticket_invalid\"\n | \"verification_missing\"\n | \"verification_invalid\"\n | \"api_unreachable\"\n | \"api_error\"\n | \"rate_limited\"\n | \"plan_version_unsupported\"\n | \"plan_path_invalid\"\n | \"project_not_found\"\n | \"stack_not_detected\"\n | \"pages_router_unsupported\"\n | \"nextauth_v4_unsupported\"\n | \"confirmation_required\"\n | \"invalid_arguments\";\n\n/**\n * An error the CLI raises on purpose: printed as its message, never as a stack, and mapped to exit code 1.\n */\nexport class AuthioCliError extends Error {\n /** What went wrong, stable across versions. */\n readonly code: AuthioCliErrorCode;\n /** The process exit code the CLI ends with. */\n readonly exitCode: number;\n\n constructor(code: AuthioCliErrorCode, message: string, options?: { cause?: unknown; exitCode?: number }) {\n super(message, options);\n this.name = \"AuthioCliError\";\n this.code = code;\n this.exitCode = options?.exitCode ?? 1;\n }\n}\n","import { AuthioCliError } from \"./errors.js\";\n\n/** The API address the CLI talks to when `--api` is not given: the shared Authio platform. */\nexport const DEFAULT_API = \"https://authio.api.hyperio.tech\";\n\n/** The command line, read. */\nexport interface CliArguments {\n /** `init`, `verify`, `help` or `version`. */\n command: \"init\" | \"verify\" | \"help\" | \"version\";\n ticket: string | undefined;\n verification: string | undefined;\n api: string | undefined;\n cwd: string | undefined;\n dryRun: boolean;\n yes: boolean;\n wait: boolean;\n}\n\nconst VALUE_OPTIONS = new Set([\"--ticket\", \"--verification\", \"--api\", \"--cwd\"]);\nconst FLAG_OPTIONS = new Set([\"--dry-run\", \"--yes\", \"-y\", \"--wait\", \"--help\", \"-h\", \"--version\", \"-v\"]);\n\n/**\n * Reads `process.argv.slice(2)`. Accepts `--name value` and `--name=value`; refuses anything it does not know, so a\n * typo never silently turns into the default.\n */\nexport function parseArguments(argv: readonly string[]): CliArguments {\n const values = new Map<string, string>();\n const flags = new Set<string>();\n const positional: string[] = [];\n\n for (let index = 0; index < argv.length; index++) {\n const current = argv[index]!;\n\n if (!current.startsWith(\"-\")) {\n positional.push(current);\n continue;\n }\n\n const [name, inline] = current.includes(\"=\") ? [current.slice(0, current.indexOf(\"=\")), current.slice(current.indexOf(\"=\") + 1)] : [current, undefined];\n\n if (VALUE_OPTIONS.has(name)) {\n const value = inline ?? argv[++index];\n\n if (value === undefined || value.startsWith(\"--\")) throw new AuthioCliError(\"invalid_arguments\", `${name} needs a value.`);\n\n values.set(name, value);\n } else if (FLAG_OPTIONS.has(name) && inline === undefined) {\n flags.add(name);\n } else {\n throw new AuthioCliError(\"invalid_arguments\", `Unknown option ${current}. Run \"authio --help\" to see the options.`);\n }\n }\n\n const named = positional[0];\n const command =\n flags.has(\"--help\") || flags.has(\"-h\")\n ? \"help\"\n : flags.has(\"--version\") || flags.has(\"-v\")\n ? \"version\"\n : named === undefined\n ? \"help\"\n : named;\n\n if (command !== \"init\" && command !== \"verify\" && command !== \"help\" && command !== \"version\")\n throw new AuthioCliError(\"invalid_arguments\", `Unknown command \"${command}\". Use \"init\" or \"verify\".`);\n\n return {\n command,\n ticket: values.get(\"--ticket\"),\n verification: values.get(\"--verification\"),\n api: values.get(\"--api\"),\n cwd: values.get(\"--cwd\"),\n dryRun: flags.has(\"--dry-run\"),\n yes: flags.has(\"--yes\") || flags.has(\"-y\"),\n wait: flags.has(\"--wait\"),\n };\n}\n\n/** The API address with no trailing slash, refusing anything that is not http(s). */\nexport function apiAddress(api: string | undefined): string {\n const address = (api ?? DEFAULT_API).trim().replace(/\\/+$/, \"\");\n\n if (!/^https?:\\/\\/[^/\\s]+/i.test(address)) throw new AuthioCliError(\"invalid_arguments\", `--api must be an http or https address, not \"${api}\".`);\n\n return address;\n}\n\n/** What `authio --help` prints. */\nexport const HELP = `Authio CLI\n\nUsage\n npx @hyper.authio/cli init --ticket ait_... Apply a client's integration plan to this project\n npx @hyper.authio/cli verify [--wait] Check whether the first sign-in has arrived\n\nOptions\n --ticket <code> The one-time code from the client's guide in the console (or AUTHIO_TICKET)\n --api <url> The Authio API address (default ${DEFAULT_API})\n --cwd <dir> The project folder (default: the current folder)\n --dry-run Show the plan without writing anything (the code is still used up)\n --yes, -y Answer yes to every question; required without an interactive terminal\n --verification <code> The verification code (default: the one init saved in .authio/verification)\n --wait With verify: keep checking until the first sign-in arrives\n\nThe CLI never overwrites a file, never commits and never sends your code anywhere.`;\n","import { spawn } from \"node:child_process\";\nimport { createInterface } from \"node:readline\";\n\n/**\n * Everything the commands do outside their own logic: the network, the terminal and running a program. Injected, so\n * every decision is testable without either.\n */\nexport interface CliIo {\n /** The `fetch` to call Authio with. */\n fetch: typeof fetch;\n /** Prints one line. */\n print(line: string): void;\n /** Asks a yes/no question; `defaultYes` is the answer to an empty line. */\n confirm(question: string, defaultYes: boolean): Promise<boolean>;\n /** Asks for a value without echoing what is typed. */\n promptSecret(question: string): Promise<string>;\n /** Runs a program in `cwd`, inheriting the terminal; resolves with its exit code. */\n run(command: string, args: readonly string[], cwd: string): Promise<number>;\n /** Whether a person can answer questions (stdin and stdout are a terminal). */\n isInteractive: boolean;\n /** The clock, in milliseconds. */\n now(): number;\n /** Waits. */\n sleep(milliseconds: number): Promise<void>;\n}\n\n/**\n * Reads one line from stdin. With `hidden`, the terminal is put in raw mode and nothing typed is echoed; backspace\n * edits, Enter ends and Ctrl+C aborts.\n */\nfunction ask(question: string, hidden: boolean): Promise<string> {\n const input = process.stdin;\n const output = process.stdout;\n\n if (!hidden || !input.isTTY) {\n const lines = createInterface({ input, output });\n\n return new Promise((resolve) =>\n lines.question(question, (answer) => {\n lines.close();\n resolve(answer);\n }),\n );\n }\n\n return new Promise((resolve, reject) => {\n let value = \"\";\n\n output.write(question);\n input.setRawMode(true);\n input.resume();\n input.setEncoding(\"utf8\");\n\n const finish = () => {\n input.setRawMode(false);\n input.pause();\n input.removeListener(\"data\", onData);\n output.write(\"\\n\");\n };\n\n const onData = (chunk: string) => {\n for (const character of chunk) {\n if (character === \"\\r\" || character === \"\\n\") {\n finish();\n resolve(value);\n return;\n }\n\n if (character === \"\\u0003\") {\n finish();\n reject(new Error(\"Cancelled.\"));\n return;\n }\n\n if (character === \"\\u007f\" || character === \"\\b\") value = value.slice(0, -1);\n else if (character >= \" \") value += character;\n }\n };\n\n input.on(\"data\", onData);\n });\n}\n\n/**\n * The command line `cmd.exe` runs for a program and its arguments, each argument in double quotes. Windows needs a shell\n * to start `npm.cmd` and friends, and handing `spawn` one string (rather than arguments with `shell: true`) is what\n * Node accepts without DEP0190. Inside quotes `cmd.exe` keeps `^`, so `pkg@^6.0.2` keeps its range.\n */\nexport function windowsCommandLine(command: string, args: readonly string[]): string {\n return [command, ...args.map((arg) => `\"${arg.replace(/\"/g, '\"\"')}\"`)].join(\" \");\n}\n\n/** The real terminal, network and process. */\nexport function createTerminalIo(): CliIo {\n return {\n fetch: globalThis.fetch.bind(globalThis),\n print: (line) => process.stdout.write(`${line}\\n`),\n confirm: async (question, defaultYes) => {\n const answer = (await ask(`${question} ${defaultYes ? \"[Y/n]\" : \"[y/N]\"} `, false)).trim().toLowerCase();\n\n return answer === \"\" ? defaultYes : answer === \"y\" || answer === \"yes\";\n },\n promptSecret: (question) => ask(question, true),\n run: (command, args, cwd) =>\n new Promise((resolve) => {\n const child =\n process.platform === \"win32\"\n ? spawn(windowsCommandLine(command, args), { cwd, stdio: \"inherit\", shell: true })\n : spawn(command, [...args], { cwd, stdio: \"inherit\" });\n\n child.on(\"error\", () => resolve(127));\n child.on(\"close\", (code) => resolve(code ?? 1));\n }),\n isInteractive: Boolean(process.stdin.isTTY && process.stdout.isTTY),\n now: () => Date.now(),\n sleep: (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds)),\n };\n}\n","import { existsSync, readFileSync, statSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { AuthioCliError } from \"./errors.js\";\n\n/** The package managers the CLI installs with, named after their lockfile. */\nexport type PackageManager = \"npm\" | \"pnpm\" | \"yarn\" | \"bun\";\n\n/** What the CLI read from the project, without the network. */\nexport interface DetectedProject {\n /** The project folder. */\n root: string;\n /** The major of the `next` dependency. */\n nextMajor: number;\n /** Whether the app lives under `src/` (`src/app`), where Next.js then expects `auth.ts` and `proxy.ts` too. */\n srcDir: boolean;\n /** The package manager, from the lockfile; `npm` when there is none. */\n packageManager: PackageManager;\n /** The port the `dev` script serves on: `-p N`/`--port N`, else 3000. */\n devPort: number;\n}\n\ninterface PackageJson {\n dependencies?: Record<string, string>;\n devDependencies?: Record<string, string>;\n scripts?: Record<string, string>;\n}\n\nconst isDirectory = (path: string) => existsSync(path) && statSync(path).isDirectory();\n\n/**\n * The major a semver range starts at (`^16.1.0` → 16, `16.x` → 16, `latest` → undefined).\n *\n * @internal\n */\nexport function majorOf(range: string | undefined): number | undefined {\n const match = /(\\d+)/.exec(range ?? \"\");\n\n return match ? Number(match[1]) : undefined;\n}\n\n/**\n * Reads the project the plan is applied to and stops, before any network call, when it is not one the plan fits: no\n * `package.json`, no `next`, the pages router only, or NextAuth v4 already installed.\n */\nexport function detectProject(root: string): DetectedProject {\n const manifestPath = join(root, \"package.json\");\n\n if (!existsSync(manifestPath))\n throw new AuthioCliError(\"project_not_found\", `No package.json in ${root}. Run the command in your Next.js project folder, or pass --cwd.`);\n\n let manifest: PackageJson;\n\n try {\n manifest = JSON.parse(readFileSync(manifestPath, \"utf8\")) as PackageJson;\n } catch (cause) {\n throw new AuthioCliError(\"project_not_found\", `${manifestPath} could not be read as JSON.`, { cause });\n }\n\n const dependencies = { ...manifest.devDependencies, ...manifest.dependencies };\n const nextMajor = majorOf(dependencies.next);\n\n if (nextMajor === undefined)\n throw new AuthioCliError(\"stack_not_detected\", \"This project does not depend on next. The plan this CLI applies is for Next.js with the App Router and Auth.js v5; for another stack, follow the client's guide in the console.\");\n\n const nextAuthMajor = majorOf(dependencies[\"next-auth\"]);\n\n if (nextAuthMajor !== undefined && nextAuthMajor < 5)\n throw new AuthioCliError(\"nextauth_v4_unsupported\", `This project uses next-auth ${dependencies[\"next-auth\"]}. The plan is written for Auth.js v5; follow the NextAuth v4 recipe in the client's guide instead.`);\n\n const srcDir = isDirectory(join(root, \"src\", \"app\"));\n const appDir = srcDir || isDirectory(join(root, \"app\"));\n\n if (!appDir) {\n const pages = isDirectory(join(root, \"pages\")) || isDirectory(join(root, \"src\", \"pages\"));\n\n throw new AuthioCliError(\n pages ? \"pages_router_unsupported\" : \"stack_not_detected\",\n pages\n ? \"This project uses the pages router. The plan is written for the App Router; follow the client's guide in the console instead.\"\n : \"No app/ or src/app/ folder was found. Create the App Router folder first, or run the command in the app's folder.\",\n );\n }\n\n const packageManager: PackageManager = existsSync(join(root, \"pnpm-lock.yaml\"))\n ? \"pnpm\"\n : existsSync(join(root, \"yarn.lock\"))\n ? \"yarn\"\n : existsSync(join(root, \"bun.lockb\")) || existsSync(join(root, \"bun.lock\"))\n ? \"bun\"\n : \"npm\";\n\n const port = /(?:^|\\s)(?:-p|--port)(?:\\s+|=)(\\d{2,5})(?:\\s|$)/.exec(manifest.scripts?.dev ?? \"\");\n\n return { root, nextMajor, srcDir, packageManager, devPort: port ? Number(port[1]) : 3000 };\n}\n","import { existsSync, readFileSync } from \"node:fs\";\nimport { isAbsolute, join, normalize, sep } from \"node:path\";\nimport { AuthioCliError } from \"./errors.js\";\nimport type { DetectedProject } from \"./project.js\";\n\n/** The plan versions this CLI knows how to apply. */\nexport const SUPPORTED_PLAN_VERSIONS: readonly number[] = [1];\n\n/** A package the plan installs. */\nexport interface IntegrationPackage {\n name: string;\n version: string;\n}\n\n/** A file the plan writes; `source` files go under `src/` when the project has it. */\nexport interface IntegrationFile {\n path: string;\n root: \"source\" | \"project\";\n content: string;\n}\n\n/** An environment variable: its value comes in the plan, is typed by the person, or is generated on the machine. */\nexport interface IntegrationEnvironmentVariable {\n name: string;\n value?: string | null;\n source: \"plan\" | \"secret\" | \"generate\";\n comment?: string | null;\n}\n\n/** The integration plan Authio hands out for one client and one stack. It never carries the client secret. */\nexport interface IntegrationPlan {\n stack: string;\n planVersion: number;\n issuer: string;\n realm: string;\n client: string;\n clientActive: boolean;\n packages: IntegrationPackage[];\n files: IntegrationFile[];\n environment: IntegrationEnvironmentVariable[];\n redirectUris: string[];\n callbackPath: string;\n}\n\n/** One file of the plan, placed in the project. */\nexport interface PlannedFile {\n /** The path relative to the project, with forward slashes. */\n path: string;\n /** The absolute path. */\n target: string;\n content: string;\n /** `create` when the file does not exist; `merge` when it does and is left alone for the person to merge. */\n action: \"create\" | \"merge\";\n}\n\n/** What applying the plan would do to this project. */\nexport interface PlannedChanges {\n files: PlannedFile[];\n /** The variables `.env.local` does not have yet; the ones it has are kept. */\n environment: IntegrationEnvironmentVariable[];\n /** The variables `.env.local` already has, kept as they are. */\n keptEnvironment: string[];\n /** The redirect the app will use locally. */\n localRedirect: string;\n /** Whether that redirect is registered on the client. */\n redirectRegistered: boolean;\n}\n\n/**\n * Reads the plan as the API sends it. The API writes enum members by name (`Source`, `Plan`), the way every Authio\n * response does, and leaves empty lists as `null`; the CLI compares in lower case and always has lists, so both\n * spellings of a value apply the same.\n */\nexport function normalizePlan(plan: IntegrationPlan): IntegrationPlan {\n const lower = <T extends string>(value: T | string | null | undefined, fallback: T): T => (value ? (String(value).toLowerCase() as T) : fallback);\n\n return {\n ...plan,\n packages: plan.packages ?? [],\n redirectUris: plan.redirectUris ?? [],\n files: (plan.files ?? []).map((file) => ({ ...file, root: lower<IntegrationFile[\"root\"]>(file.root, \"project\"), content: file.content ?? \"\" })),\n environment: (plan.environment ?? []).map((variable) => ({\n ...variable,\n source: lower<IntegrationEnvironmentVariable[\"source\"]>(variable.source, \"secret\"),\n })),\n };\n}\n\n/** Refuses a plan this CLI does not know how to apply, before anything is written. */\nexport function assertPlanVersion(plan: IntegrationPlan): void {\n if (!SUPPORTED_PLAN_VERSIONS.includes(plan.planVersion))\n throw new AuthioCliError(\n \"plan_version_unsupported\",\n `This plan is version ${plan.planVersion}, which this CLI does not know. Run \"npx @hyper.authio/cli@latest\" with a new code.`,\n );\n}\n\n/**\n * Places a plan path inside the project, refusing an absolute path or one that climbs out of it. The plan comes from\n * Authio, but nothing outside the project folder is ever written.\n *\n * @internal\n */\nexport function placePath(project: DetectedProject, file: IntegrationFile): string {\n const relative = normalize(file.path).replace(/\\\\/g, \"/\");\n\n if (!relative || isAbsolute(file.path) || /^[a-zA-Z]:/.test(file.path) || relative.split(\"/\").includes(\"..\"))\n throw new AuthioCliError(\"plan_path_invalid\", `The plan names a path outside the project (\"${file.path}\"); nothing was written.`);\n\n return file.root === \"source\" && project.srcDir ? `src/${relative}` : relative;\n}\n\n/** The keys an env file already defines. */\nexport function readEnvKeys(path: string): Set<string> {\n if (!existsSync(path)) return new Set();\n\n const keys = new Set<string>();\n\n for (const line of readFileSync(path, \"utf8\").split(/\\r?\\n/)) {\n const match = /^\\s*(?:export\\s+)?([A-Za-z_][A-Za-z0-9_]*)\\s*=/.exec(line);\n\n if (match) keys.add(match[1]!);\n }\n\n return keys;\n}\n\n/** The variable Auth.js builds its own addresses from: in `.env.local`, where the app runs on this machine. */\nexport const LOCAL_ORIGIN_VARIABLE = \"AUTH_URL\";\n\nconst LOCAL_ORIGIN_COMMENT = \"Where this app runs on this machine; set the public address where it is deployed.\";\n\n/** The address the app is served from on this machine, from the `dev` script's port. */\nexport function localOrigin(project: DetectedProject): string {\n return `http://localhost:${project.devPort}`;\n}\n\n/**\n * Works out, without writing, what applying the plan does to this project.\n *\n * `.env.local` is the development environment, so `AUTH_URL` there is the local address the `dev` script serves on,\n * not the address the plan derives from the client's first redirect (often the production one): with that, Auth.js would\n * send every sign-in on this machine back to the deployed app.\n */\nexport function planChanges(project: DetectedProject, plan: IntegrationPlan): PlannedChanges {\n const files = plan.files.map((file): PlannedFile => {\n const path = placePath(project, file);\n const target = join(project.root, ...path.split(\"/\"));\n\n if (!target.startsWith(project.root + sep) && target !== project.root)\n throw new AuthioCliError(\"plan_path_invalid\", `The plan names a path outside the project (\"${file.path}\"); nothing was written.`);\n\n return { path, target, content: file.content, action: existsSync(target) ? \"merge\" : \"create\" };\n });\n\n const existing = readEnvKeys(join(project.root, \".env.local\"));\n const localRedirect = `${localOrigin(project)}${plan.callbackPath}`;\n\n return {\n files,\n environment: plan.environment\n .filter((variable) => !existing.has(variable.name))\n .map((variable) => (variable.name === LOCAL_ORIGIN_VARIABLE && variable.source === \"plan\" ? { ...variable, value: localOrigin(project), comment: LOCAL_ORIGIN_COMMENT } : variable)),\n keptEnvironment: plan.environment.filter((variable) => existing.has(variable.name)).map((variable) => variable.name),\n localRedirect,\n redirectRegistered: plan.redirectUris.some((uri) => uri.replace(/\\/+$/, \"\") === localRedirect),\n };\n}\n","import { existsSync, readFileSync } from \"node:fs\";\n\n/** The paths the CLI writes that must never be committed. */\nexport const IGNORED_PATHS = [\".env.local\", \".authio/\"] as const;\n\n/**\n * Turns one `.gitignore` pattern into a test for a top-level path. Covers what projects actually write for these two\n * paths (`*`, a leading `/`, a trailing `/`); a negation or a nested pattern counts as not covering.\n *\n * @internal\n */\nfunction patternCovers(pattern: string, path: string): boolean {\n const trimmed = pattern.trim();\n\n if (!trimmed || trimmed.startsWith(\"#\") || trimmed.startsWith(\"!\")) return false;\n\n const bare = trimmed.replace(/^\\//, \"\");\n const wantsDirectory = path.endsWith(\"/\");\n const name = path.replace(/\\/$/, \"\");\n\n if (bare.endsWith(\"/\") && !wantsDirectory) return false;\n\n const glob = bare.replace(/\\/$/, \"\");\n\n if (glob.includes(\"/\")) return false;\n\n const expression = new RegExp(`^${glob.replace(/[.+^${}()|[\\]\\\\]/g, \"\\\\$&\").replace(/\\*/g, \"[^/]*\").replace(/\\?/g, \"[^/]\")}$`);\n\n return expression.test(name);\n}\n\n/** The paths of {@link IGNORED_PATHS} the `.gitignore` at `path` does not cover yet. */\nexport function uncoveredPaths(path: string): string[] {\n const patterns = existsSync(path) ? readFileSync(path, \"utf8\").split(/\\r?\\n/) : [];\n\n return IGNORED_PATHS.filter((ignored) => !patterns.some((pattern) => patternCovers(pattern, ignored)));\n}\n","import { AuthioCliError } from \"./errors.js\";\nimport { normalizePlan, type IntegrationPlan } from \"./plan.js\";\n\n/** What redeeming a code answers: the plan, and the code that later confirms the first sign-in. */\nexport interface IntegrationRedemption {\n plan: IntegrationPlan;\n verification: string;\n verificationExpiresAt: string;\n}\n\n/** Whether the client has had a successful sign-in since the code was redeemed. */\nexport interface IntegrationVerification {\n signedIn: boolean;\n firstSignInAt: string | null;\n}\n\ninterface ApiEnvelope<T> {\n data?: T;\n errorCode?: string;\n notifications?: Array<{ message?: string }>;\n}\n\n/**\n * Posts to one of the two anonymous integration routes and reads Authio's `ApiResult` envelope. The codes travel in the\n * body, never in the URL, so no proxy log records them.\n */\nasync function post<T>(fetcher: typeof fetch, api: string, path: string, body: unknown, invalidCode: \"ticket_invalid\" | \"verification_invalid\"): Promise<T> {\n const url = `${api}${path}`;\n let response: Response;\n\n try {\n response = await fetcher(url, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\", accept: \"application/json\" },\n body: JSON.stringify(body),\n });\n } catch (cause) {\n throw new AuthioCliError(\"api_unreachable\", `Could not reach Authio at ${api}. Check the address (--api) and your connection.`, { cause });\n }\n\n let envelope: ApiEnvelope<T> = {};\n\n try {\n envelope = (await response.json()) as ApiEnvelope<T>;\n } catch {\n envelope = {};\n }\n\n if (response.status === 401)\n throw new AuthioCliError(\n invalidCode,\n invalidCode === \"ticket_invalid\"\n ? \"This code was already used or has expired. Generate another command in the client's guide in the Authio console.\"\n : \"This verification has expired. The last step of the client's guide in the console still shows the first sign-in.\",\n );\n\n if (response.status === 429) throw new AuthioCliError(\"rate_limited\", \"Authio is refusing more attempts for now. Wait a minute and try again.\");\n\n if (!response.ok || envelope.data === undefined) {\n const detail = envelope.notifications?.map((notification) => notification.message).filter(Boolean).join(\" \");\n\n throw new AuthioCliError(\"api_error\", `Authio answered ${response.status}${envelope.errorCode ? ` (${envelope.errorCode})` : \"\"}${detail ? `: ${detail}` : \".\"}`);\n }\n\n return envelope.data;\n}\n\n/** Exchanges the one-time code for the plan. The code is used up, whatever happens next. */\nexport async function redeemTicket(fetcher: typeof fetch, api: string, ticket: string, nextMajor: number): Promise<IntegrationRedemption> {\n const redemption = await post<IntegrationRedemption>(fetcher, api, \"/api/v1/integration-plans/redeem\", { ticket, nextMajor }, \"ticket_invalid\");\n\n return { ...redemption, plan: normalizePlan(redemption.plan) };\n}\n\n/** Asks whether the first sign-in has arrived. The verification code may be used until it expires. */\nexport function checkVerification(fetcher: typeof fetch, api: string, verification: string): Promise<IntegrationVerification> {\n return post<IntegrationVerification>(fetcher, api, \"/api/v1/integration-plans/verify\", { verification }, \"verification_invalid\");\n}\n","import { randomBytes } from \"node:crypto\";\nimport { appendFileSync, chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { redeemTicket } from \"./api.js\";\nimport { AuthioCliError } from \"./errors.js\";\nimport { IGNORED_PATHS, uncoveredPaths } from \"./gitignore.js\";\nimport type { CliIo } from \"./io.js\";\nimport { assertPlanVersion, planChanges, type IntegrationEnvironmentVariable, type IntegrationPlan, type PlannedChanges } from \"./plan.js\";\nimport { detectProject, type DetectedProject, type PackageManager } from \"./project.js\";\n\n/** The options of `authio init`. */\nexport interface InitOptions {\n /** The one-time code from the client's guide. */\n ticket: string | undefined;\n /** The API address, already normalised. */\n api: string;\n /** The project folder. */\n cwd: string;\n /** Show the plan without writing, installing or asking for secrets. */\n dryRun: boolean;\n /** Answer yes to every question. */\n yes: boolean;\n}\n\n/** What `authio init` did. */\nexport interface InitResult {\n /** 0 when the plan was applied (or shown, with `dryRun`), 2 when the person declined. */\n exitCode: number;\n /** The files created, relative to the project. */\n created: string[];\n /** The files that already existed and are left for the person to merge. */\n toMerge: string[];\n /** The variables added to `.env.local`. */\n addedEnvironment: string[];\n /** The `secret` variables written empty, for the person to fill. */\n emptySecrets: string[];\n /** The plan, as redeemed. */\n plan: IntegrationPlan | undefined;\n}\n\n/** The file `verify` reads the verification from. */\nexport const VERIFICATION_FILE = join(\".authio\", \"verification\");\n\n/** The command that installs `specs` with `manager`. */\nexport function installCommand(manager: PackageManager, specs: readonly string[]): [string, string[]] {\n return manager === \"npm\" ? [\"npm\", [\"install\", ...specs]] : [manager, [\"add\", ...specs]];\n}\n\n/** One `.env` line: double-quoted, or single-quoted when the value holds a double quote. */\nfunction envLine(name: string, value: string): string {\n return value.includes('\"') ? `${name}='${value}'` : `${name}=\"${value}\"`;\n}\n\nfunction describe(io: CliIo, project: DetectedProject, plan: IntegrationPlan, changes: PlannedChanges): void {\n io.print(\"\");\n io.print(`Authio plan for client \"${plan.client}\" in realm \"${plan.realm}\" (${plan.stack})`);\n io.print(`Project: Next.js ${project.nextMajor}${project.srcDir ? \", src/ folder\" : \"\"}, ${project.packageManager}`);\n io.print(\"\");\n\n for (const file of changes.files) {\n io.print(file.action === \"create\" ? ` + ${file.path}` : ` ! ${file.path} already exists: left as it is, merge it by hand`);\n for (const line of file.content.split(/\\r?\\n/)) io.print(` ${line}`);\n }\n\n io.print(\"\");\n io.print(\" .env.local\");\n\n for (const variable of changes.environment) {\n const how = variable.source === \"secret\" ? \"asked for, hidden\" : variable.source === \"generate\" ? \"generated on this machine\" : variable.value ?? \"\";\n\n io.print(` + ${variable.name} ${variable.source === \"plan\" ? `= ${how}` : `(${how})`}`);\n }\n\n for (const name of changes.keptEnvironment) io.print(` = ${name} (already set, kept)`);\n\n if (plan.packages.length > 0) io.print(`\\n Packages: ${plan.packages.map((item) => `${item.name}@${item.version}`).join(\" \")}`);\n\n if (!changes.redirectRegistered)\n io.print(`\\n Warning: the client does not register ${changes.localRedirect}. Sign-in will fail until you add it as a redirect URI on the client in the Authio console.`);\n\n if (!plan.clientActive) io.print(\"\\n Warning: this client is inactive or in quarantine in Authio, so sign-in is refused until it is active again.\");\n\n io.print(\"\");\n}\n\nasync function valueOf(io: CliIo, variable: IntegrationEnvironmentVariable, emptySecrets: string[]): Promise<string> {\n if (variable.source === \"plan\") return variable.value ?? \"\";\n\n if (variable.source === \"generate\") return randomBytes(32).toString(\"base64\");\n\n if (!io.isInteractive) {\n emptySecrets.push(variable.name);\n return \"\";\n }\n\n const typed = (await io.promptSecret(`${variable.name}${variable.comment ? ` (${variable.comment})` : \"\"}, hidden, Enter to leave empty: `)).trim();\n\n if (!typed) emptySecrets.push(variable.name);\n\n return typed;\n}\n\n/**\n * `authio init`: reads the project, exchanges the code for the plan and applies it, never overwriting a file, never\n * committing, asking before writing and for each secret in a hidden prompt.\n */\nexport async function runInit(options: InitOptions, io: CliIo): Promise<InitResult> {\n const result: InitResult = { exitCode: 0, created: [], toMerge: [], addedEnvironment: [], emptySecrets: [], plan: undefined };\n const ticket = options.ticket?.trim();\n\n if (!ticket)\n throw new AuthioCliError(\"ticket_missing\", \"No code given. Open the client's guide in the Authio console, choose Next.js · Auth.js v5 and press Generate command.\");\n\n const project = detectProject(options.cwd);\n\n if (!options.dryRun && !options.yes && !io.isInteractive)\n throw new AuthioCliError(\"confirmation_required\", \"There is no terminal to confirm in. Run with --yes to apply the plan, or --dry-run to see it first.\");\n\n const redemption = await redeemTicket(io.fetch, options.api, ticket, project.nextMajor);\n const plan = redemption.plan;\n\n result.plan = plan;\n assertPlanVersion(plan);\n\n const changes = planChanges(project, plan);\n\n result.toMerge = changes.files.filter((file) => file.action === \"merge\").map((file) => file.path);\n describe(io, project, plan, changes);\n\n if (options.dryRun) {\n io.print(\"Dry run: nothing was written. The code is used up; generate another command to apply the plan.\");\n return result;\n }\n\n const creating = changes.files.filter((file) => file.action === \"create\");\n const agreed = options.yes || (await io.confirm(`Create ${creating.length} file(s) and add ${changes.environment.length} variable(s) to .env.local?`, true));\n\n if (!agreed) {\n io.print(\"Nothing was written.\");\n result.exitCode = 2;\n return result;\n }\n\n const lines: string[] = [];\n\n for (const variable of changes.environment) {\n const value = await valueOf(io, variable, result.emptySecrets);\n\n if (variable.comment) lines.push(`# ${variable.comment}`);\n lines.push(envLine(variable.name, value));\n result.addedEnvironment.push(variable.name);\n }\n\n for (const file of creating) {\n mkdirSync(dirname(file.target), { recursive: true });\n writeFileSync(file.target, file.content.endsWith(\"\\n\") ? file.content : `${file.content}\\n`, { flag: \"wx\" });\n result.created.push(file.path);\n }\n\n if (lines.length > 0) {\n const envPath = join(project.root, \".env.local\");\n const current = existsSync(envPath) ? readFileSync(envPath, \"utf8\") : \"\";\n const separator = current && !current.endsWith(\"\\n\") ? \"\\n\" : \"\";\n\n appendFileSync(envPath, `${separator}${current ? \"\\n\" : \"\"}# Added by @hyper.authio/cli for the Authio client \"${plan.client}\"\\n${lines.join(\"\\n\")}\\n`);\n }\n\n const gitignorePath = join(project.root, \".gitignore\");\n const missing = uncoveredPaths(gitignorePath);\n\n if (missing.length > 0 && (options.yes || (await io.confirm(`Add ${missing.join(\" and \")} to .gitignore so they are never committed?`, true)))) {\n const current = existsSync(gitignorePath) ? readFileSync(gitignorePath, \"utf8\") : \"\";\n\n appendFileSync(gitignorePath, `${current && !current.endsWith(\"\\n\") ? \"\\n\" : \"\"}${missing.join(\"\\n\")}\\n`);\n } else if (missing.length > 0) {\n io.print(`Warning: ${missing.join(\" and \")} ${missing.length > 1 ? \"are\" : \"is\"} not in .gitignore. Never commit ${IGNORED_PATHS.join(\" or \")}.`);\n }\n\n const verificationPath = join(project.root, VERIFICATION_FILE);\n\n mkdirSync(dirname(verificationPath), { recursive: true });\n writeFileSync(verificationPath, JSON.stringify({ api: options.api, verification: redemption.verification, expiresAt: redemption.verificationExpiresAt }), {\n mode: 0o600,\n });\n\n try {\n chmodSync(verificationPath, 0o600);\n } catch {\n // Some file systems (FAT, network shares on Windows) have no POSIX modes; the file is written either way.\n }\n\n if (plan.packages.length > 0) {\n const [command, args] = installCommand(project.packageManager, plan.packages.map((item) => `${item.name}@${item.version}`));\n const install = options.yes || (await io.confirm(`Install ${plan.packages.map((item) => item.name).join(\", \")} with ${command}?`, true));\n\n if (install) {\n const code = await io.run(command, args, project.root);\n\n if (code !== 0) io.print(`The install failed (exit ${code}). Run it yourself: ${command} ${args.join(\" \")}`);\n } else {\n io.print(`Install them when you are ready: ${command} ${args.join(\" \")}`);\n }\n }\n\n io.print(\"\");\n io.print(`Created: ${result.created.length > 0 ? result.created.join(\", \") : \"nothing\"}`);\n if (result.toMerge.length > 0) io.print(`Merge by hand: ${result.toMerge.join(\", \")} (the plan's version is printed above)`);\n if (result.emptySecrets.length > 0) io.print(`Fill in .env.local: ${result.emptySecrets.join(\", \")}`);\n io.print(`Next: start the app, sign in, then run \"npx @hyper.authio/cli verify\".`);\n\n return result;\n}\n","import { existsSync, readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { checkVerification } from \"./api.js\";\nimport { AuthioCliError } from \"./errors.js\";\nimport { VERIFICATION_FILE } from \"./init.js\";\nimport type { CliIo } from \"./io.js\";\n\n/** The options of `authio verify`. */\nexport interface VerifyOptions {\n /** The verification code; when absent, the one `init` saved in `.authio/verification`. */\n verification: string | undefined;\n /** The API address given on the command line, already normalised; the saved one wins when it is absent. */\n api: string | undefined;\n /** The address used when neither is given. */\n defaultApi: string;\n /** The project folder. */\n cwd: string;\n /** Keep checking until the first sign-in arrives or the verification expires. */\n wait: boolean;\n}\n\n/** What `authio verify` found. */\nexport interface VerifyResult {\n /** 0 when the first sign-in arrived, 2 when it has not yet. */\n exitCode: number;\n firstSignInAt: string | null;\n}\n\n/** How often `--wait` asks again. */\nexport const VERIFY_INTERVAL_MS = 3000;\n\ninterface SavedVerification {\n api?: string;\n verification?: string;\n expiresAt?: string;\n}\n\nfunction readSaved(cwd: string): SavedVerification {\n const path = join(cwd, VERIFICATION_FILE);\n\n if (!existsSync(path)) return {};\n\n try {\n return JSON.parse(readFileSync(path, \"utf8\")) as SavedVerification;\n } catch {\n return {};\n }\n}\n\n/**\n * `authio verify`: asks Authio whether the client has had a successful sign-in since `init` redeemed the code. With\n * `wait`, asks every three seconds until it has or the verification expires.\n */\nexport async function runVerify(options: VerifyOptions, io: CliIo): Promise<VerifyResult> {\n const saved = readSaved(options.cwd);\n const verification = options.verification ?? saved.verification;\n const api = options.api ?? saved.api ?? options.defaultApi;\n\n if (!verification)\n throw new AuthioCliError(\"verification_missing\", `No verification found. Run \"authio init\" first, or pass --verification.`);\n\n const expiresAt = saved.expiresAt && !options.verification ? Date.parse(saved.expiresAt) : Number.NaN;\n\n if (options.wait) io.print(\"Waiting for the first sign-in through the app (Ctrl+C to stop)…\");\n\n for (;;) {\n const answer = await checkVerification(io.fetch, api, verification);\n\n if (answer.signedIn) {\n io.print(`Signed in: the first sign-in arrived${answer.firstSignInAt ? ` at ${answer.firstSignInAt}` : \"\"}. The integration works.`);\n return { exitCode: 0, firstSignInAt: answer.firstSignInAt };\n }\n\n if (!options.wait || (!Number.isNaN(expiresAt) && io.now() + VERIFY_INTERVAL_MS >= expiresAt)) {\n io.print(\"No sign-in has arrived yet. Start the app, sign in, and run this again (or add --wait).\");\n return { exitCode: 2, firstSignInAt: null };\n }\n\n await io.sleep(VERIFY_INTERVAL_MS);\n }\n}\n","import { resolve } from \"node:path\";\nimport { DEFAULT_API, HELP, apiAddress, parseArguments } from \"./args.js\";\nimport { AuthioCliError } from \"./errors.js\";\nimport { runInit } from \"./init.js\";\nimport type { CliIo } from \"./io.js\";\nimport { runVerify } from \"./verify.js\";\n\n/**\n * Runs the CLI with `argv` (without the node and script paths) and answers the exit code. Errors the CLI raises on\n * purpose are printed as their message; anything else is printed with its stack, since it is a bug.\n */\nexport async function main(argv: readonly string[], io: CliIo, environment: Record<string, string | undefined>, version: string): Promise<number> {\n try {\n const args = parseArguments(argv);\n const cwd = resolve(args.cwd ?? \".\");\n\n if (args.command === \"help\") {\n io.print(HELP);\n return 0;\n }\n\n if (args.command === \"version\") {\n io.print(version);\n return 0;\n }\n\n if (args.command === \"init\") {\n const result = await runInit(\n { ticket: args.ticket ?? environment.AUTHIO_TICKET, api: apiAddress(args.api), cwd, dryRun: args.dryRun, yes: args.yes },\n io,\n );\n\n return result.exitCode;\n }\n\n const result = await runVerify(\n { verification: args.verification, api: args.api ? apiAddress(args.api) : undefined, defaultApi: DEFAULT_API, cwd, wait: args.wait },\n io,\n );\n\n return result.exitCode;\n } catch (error) {\n if (error instanceof AuthioCliError) {\n io.print(`Error (${error.code}): ${error.message}`);\n return error.exitCode;\n }\n\n io.print(error instanceof Error ? (error.stack ?? error.message) : String(error));\n return 1;\n }\n}\n"]}
|