create-authup 1.0.0-beta.68 → 1.0.0-beta.70
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 +93 -51
- package/dist/index.mjs +3 -3
- package/dist/index.mjs.map +1 -1
- package/package.json +7 -7
package/README.md
CHANGED
|
@@ -1,58 +1,100 @@
|
|
|
1
|
-
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="https://raw.githubusercontent.com/authup/authup/master/.github/assets/logo.svg" alt="Authup logo" width="96" height="96" />
|
|
3
|
+
</p>
|
|
2
4
|
|
|
3
|
-
|
|
5
|
+
<h1 align="center">create-authup</h1>
|
|
4
6
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
one of the two.
|
|
7
|
+
<p align="center">
|
|
8
|
+
<b>Answer a few questions and get the deployment files for Authup: docker run, compose, Helm or bare metal.</b><br>
|
|
9
|
+
No hand-assembled env files, no copied compose snippets, no guessing which keys a release understands.
|
|
10
|
+
</p>
|
|
10
11
|
|
|
11
|
-
|
|
12
|
+
<p align="center">
|
|
13
|
+
<a href="https://www.npmjs.com/package/create-authup"><img src="https://img.shields.io/npm/v/create-authup" alt="npm version" /></a>
|
|
14
|
+
<a href="https://github.com/authup/authup/actions/workflows/main.yml"><img src="https://github.com/authup/authup/actions/workflows/main.yml/badge.svg" alt="CI" /></a>
|
|
15
|
+
<a href="./LICENSE"><img src="https://img.shields.io/badge/license-Apache--2.0-blue" alt="License: Apache-2.0" /></a>
|
|
16
|
+
</p>
|
|
17
|
+
|
|
18
|
+
<p align="center">
|
|
19
|
+
<a href="#installation"><b>Installation</b></a> · <a href="#usage">Usage</a> · <a href="#targets">Targets</a> · <a href="https://authup.org/guide/deployment/">Documentation</a>
|
|
20
|
+
</p>
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Why create-authup?
|
|
25
|
+
|
|
26
|
+
A first Authup deployment means picking the right environment variables for the image, wiring a database, deciding on Redis, and getting the details right that only show up later: a public URL that is also the OpenID Connect issuer, a proxy hop count so client addresses cannot be spoofed, secrets that should not be world-readable. The wizard asks for the decisions and writes the files.
|
|
27
|
+
|
|
28
|
+
| Concern | Without it | With create-authup |
|
|
29
|
+
|---|---|---|
|
|
30
|
+
| **Configuration keys** | Copied from docs that may describe another release | Its test suite checks every key it writes against the configuration schema and the environment variables of its own release |
|
|
31
|
+
| **Versions** | `latest`, or a tag you look up | The image tag and the `authup` dependency are pinned exactly to the wizard's own version |
|
|
32
|
+
| **Proxy trust** | Authup's default trusts every `X-Forwarded-For` hop | `TRUST_PROXY` (`core.trustProxy` on bare metal) is written as the number of proxies the deployment has, or `false`; the Helm values leave it to the chart |
|
|
33
|
+
| **Secrets** | An env file with the default `0644` mode | Files holding a password are written `0600`, also when overwritten |
|
|
34
|
+
| **Scale-out** | A split topology you assemble by hand | Worker and console services as their own containers, with Redis turned on and, for compose, an nginx in front |
|
|
35
|
+
|
|
36
|
+
## Installation
|
|
37
|
+
|
|
38
|
+
Nothing to install; run it with npm:
|
|
39
|
+
|
|
40
|
+
```shell
|
|
41
|
+
npm create authup@latest
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
This runs the `create-authup` binary in the current directory. Use `@latest` to scaffold the current release, since the version you run is the version the files pin. The wizard needs Node.js `^22.13.0 || ^23.5.0 || >=24.0.0` and has no runtime dependencies.
|
|
45
|
+
|
|
46
|
+
## Usage
|
|
47
|
+
|
|
48
|
+
```shell
|
|
49
|
+
npm create authup@latest
|
|
50
|
+
npm create authup@latest -- --force
|
|
51
|
+
npx create-authup --help
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
| Flag | Description |
|
|
55
|
+
|---|---|
|
|
56
|
+
| `--force` | Overwrite files that already exist. Without it the wizard lists the conflicts, writes nothing and exits with 1 |
|
|
57
|
+
| `--help` | Print the usage |
|
|
58
|
+
|
|
59
|
+
Any other flag or a positional argument is refused. A file name that exists as a directory is never written over, `--force` or not.
|
|
60
|
+
|
|
61
|
+
### Questions
|
|
62
|
+
|
|
63
|
+
Asked in this order, with the default in brackets; some are only asked when they apply:
|
|
64
|
+
|
|
65
|
+
1. **Deployment target**: `docker`, `compose`, `helm` or `bare-metal` [`compose`], by name or number.
|
|
66
|
+
2. **Public URL**: the address a browser reaches Authup at, which is the OpenID Connect issuer and the base of every console URL. No credentials, query or fragment; for `helm` no path either.
|
|
67
|
+
3. **cert-manager** (`helm` over https only): request the ingress certificate from cert-manager instead of expecting a `<hostname>-tls` secret [no].
|
|
68
|
+
4. **Database**: `postgres` or `mysql`, plus `sqlite` for `bare-metal` [`postgres`]. For `compose` and `helm` it asks whether to run the database alongside Authup [yes]; then only a password is asked. Otherwise: host (`127.0.0.1` on bare metal), port (5432 or 3306), username [`authup`], password and database name [`authup`].
|
|
69
|
+
5. **User registration** [no] and **password recovery** [no]. When either is on, the SMTP connection URL (`smtp://` or `smtps://`); with registration on, whether to verify email addresses [yes].
|
|
70
|
+
6. **Admin password**: required, 3 to 256 characters, and never `start123`, the provisioning default.
|
|
71
|
+
7. **Worker and consoles as separate services** (`compose` and `helm` only) [no, no].
|
|
72
|
+
8. **Redis** [no]: asked only when nothing is split, since a split turns it on. `compose` and `helm` use the bundled `redis://redis:6379`; `docker` and `bare-metal` ask for the URL.
|
|
73
|
+
|
|
74
|
+
Passwords are not echoed on a terminal. A value is refused when it holds a single quote together with a literal `\n` or `\r` sequence, because such a value has no `.env` representation that reads back unchanged.
|
|
75
|
+
|
|
76
|
+
## Targets
|
|
77
|
+
|
|
78
|
+
| Target | Files | Next steps the wizard prints |
|
|
12
79
|
|---|---|---|
|
|
13
80
|
| `docker` | `authup.env` | `docker run -d --name authup --restart unless-stopped --env-file authup.env -p <port>:3000 authup/authup:<version> start` |
|
|
14
|
-
| `compose` | `docker-compose.yml`, `.env
|
|
15
|
-
| `helm` | `values.yaml` | `helm repo add authup https://helm.authup.org`,
|
|
81
|
+
| `compose` | `docker-compose.yml`, `.env`, and `nginx.conf` with a console split | `docker compose up -d`, preceded by `docker compose run --rm authup migration run` with a worker split |
|
|
82
|
+
| `helm` | `values.yaml` | `helm repo add authup https://helm.authup.org`, `helm install authup authup/authup -f values.yaml` |
|
|
16
83
|
| `bare-metal` | `package.json`, `authup.yml`, `.env` | `npm install`, `npx authup config validate`, `npm start` |
|
|
17
84
|
|
|
18
|
-
The
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
worker and the
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
proxies the deployment knows about, or `false` when nothing is in front, since
|
|
35
|
-
authup's own default trusts every `X-Forwarded-For` hop. Every emitted file
|
|
36
|
-
carries commented placeholders for `TRUSTED_ORIGINS`, `SECRETS_ENCRYPTION_KEY`
|
|
37
|
-
and the configuration file mount, so the options a real deployment reaches for
|
|
38
|
-
next are one uncomment away.
|
|
39
|
-
|
|
40
|
-
Two flags: `--force` overwrites files that already exist (without it the
|
|
41
|
-
conflicts are listed, nothing is written and the process exits 1), `--help`
|
|
42
|
-
prints the usage.
|
|
43
|
-
|
|
44
|
-
Versions are pinned to the wizard's own version: the image tag and the
|
|
45
|
-
`authup` dependency are both exactly `pkg.version`, never `latest` and never a
|
|
46
|
-
range, so run `npm create authup@latest` to scaffold the current release. Helm
|
|
47
|
-
is the exception and names no tag at all: the chart's `appVersion` owns the
|
|
48
|
-
image, so the chart version is what selects the release there. An emitted
|
|
49
|
-
`authup.yml` is checked with `npx authup config validate`, and the emitted helm
|
|
50
|
-
values are validated in this package's test suite against the chart's own
|
|
51
|
-
`values.schema.json`, vendored under `test/fixtures/`.
|
|
52
|
-
|
|
53
|
-
The package holds zero runtime dependencies: prompts ride
|
|
54
|
-
`node:readline/promises`, flags `node:util.parseArgs`, and every emitted
|
|
55
|
-
document is a plain string template. It never runs a service.
|
|
56
|
-
|
|
57
|
-
The deployment guides describe each target in full:
|
|
58
|
-
https://authup.org/guide/deployment/
|
|
85
|
+
- **The port follows the public URL.** An explicit http port (`http://host:8080`) is dialed directly, so it is the port published or listened on. https or a default port means a reverse proxy owns the port, and Authup listens on 3000.
|
|
86
|
+
- **Every file carries commented placeholders** for `TRUSTED_ORIGINS`, `SECRETS_ENCRYPTION_KEY` and the configuration file mount.
|
|
87
|
+
- **`bare-metal` writes a `package.json`** named `authup-deployment` with `"start": "authup start"` and the `authup` dependency pinned to the wizard's version; the structure goes into `authup.yml` and the secrets into `.env`.
|
|
88
|
+
|
|
89
|
+
## Notes
|
|
90
|
+
|
|
91
|
+
- **The `docker` target refuses SQLite.** The image runs in production mode, which refuses the SQLite driver, so the database must exist already; only `bare-metal` offers SQLite.
|
|
92
|
+
- **The Helm values name no image tag.** The chart's version decides the release there, and the file states the chart version it needs (0.4.0 or later).
|
|
93
|
+
- **A split needs Redis.** With a worker or console split the console sign-in and the token blocklist cross replicas through the cache, so Redis is added and the wizard says so.
|
|
94
|
+
- **It only writes files.** Nothing is started; every file it writes runs the `authup` CLI or the `authup/authup` image.
|
|
95
|
+
|
|
96
|
+
## License
|
|
97
|
+
|
|
98
|
+
Made with 💚
|
|
99
|
+
|
|
100
|
+
Published under the [Apache 2.0 License](./LICENSE).
|
package/dist/index.mjs
CHANGED
|
@@ -327,7 +327,7 @@ function renderDocker(answers, version) {
|
|
|
327
327
|
}
|
|
328
328
|
//#endregion
|
|
329
329
|
//#region src/targets/helm.ts
|
|
330
|
-
const CHART_REQUIREMENT = "#
|
|
330
|
+
const CHART_REQUIREMENT = "# This file needs the authup helm chart 0.4.0 or later.";
|
|
331
331
|
const HELM_COMMANDS = ["helm repo add authup https://helm.authup.org", "helm install authup authup/authup -f values.yaml"];
|
|
332
332
|
function renderHelm(answers, _version) {
|
|
333
333
|
const { db } = answers;
|
|
@@ -337,7 +337,7 @@ function renderHelm(answers, _version) {
|
|
|
337
337
|
const lines = [
|
|
338
338
|
`# Written by npm create authup. Install with: ${HELM_COMMANDS.join(" && ")}`,
|
|
339
339
|
"# Secrets in this file (auth.adminPassword, the database password, smtp.connectionString) can move to the chart's existingSecret keys; see the chart README.",
|
|
340
|
-
|
|
340
|
+
CHART_REQUIREMENT,
|
|
341
341
|
"server:",
|
|
342
342
|
` publicUrl: ${quoteYaml(answers.publicUrl)}`,
|
|
343
343
|
...tls && !answers.tlsCertManager ? [` # TLS needs a secret named ${url.hostname}-tls in the release namespace, or set server.ingress.certManager: true.`] : [],
|
|
@@ -356,7 +356,7 @@ function renderHelm(answers, _version) {
|
|
|
356
356
|
if (answers.workerSplit) lines.push(" migration:", " enabled: true");
|
|
357
357
|
if (answers.consoleSplit) lines.push(" splitConsoles: true");
|
|
358
358
|
lines.push(" # Every downstream application origin; each may obtain a full-permission token.", " # trustedOrigins:", " # - https://app.example.com", " # An authup.yml the chart mounts, for the options that only the file can carry.", " # configuration: |", " # core:", " # passwordMinLength: 12");
|
|
359
|
-
|
|
359
|
+
lines.push("", "worker:", ` enabled: ${answers.workerSplit}`);
|
|
360
360
|
lines.push("", "database:", ` type: ${db.type}`, "");
|
|
361
361
|
for (const engine of ["postgresql", "mysql"]) {
|
|
362
362
|
const enabled = db.bundled && db.type === (engine === "postgresql" ? "postgres" : "mysql");
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":["DOCS_URL"],"sources":["../src/ask.ts","../src/constants.ts","../src/deployment.ts","../src/utils.ts","../src/prompts.ts","../src/targets/docker.ts","../src/targets/helm.ts","../src/targets/bare-metal.ts","../src/targets/compose.ts","../src/targets/index.ts","../src/version.ts","../src/index.ts"],"sourcesContent":["/*\n * Copyright (c) 2026.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nimport process from 'node:process';\nimport { createInterface } from 'node:readline/promises';\nimport { Writable } from 'node:stream';\nimport type { ReadlineAskContext, ReadlineAskResult } from './types.ts';\n\ntype PendingLine = {\n resolve: (line: string) => void,\n reject: (error: Error) => void\n};\n\nconst CLOSED_MESSAGE = 'Input ended before every question was answered.';\n\n/**\n * The readline seam. The streams are arguments rather than `process.stdin` / `process.stdout` reached for directly,\n * so the echo behaviour below is testable without a terminal: a spec passes a pair of in-memory streams claiming to\n * be one.\n */\nexport function createReadlineAsk(ctx: ReadlineAskContext = {}): ReadlineAskResult {\n const input = ctx.input || process.stdin;\n const target = ctx.output || process.stdout;\n\n // On a terminal readline echoes what is typed through the output stream itself, so muting that stream while a\n // secret's line is read is what keeps the password off the screen. Piped input takes none of this path.\n const terminal = Boolean(input.isTTY && target.isTTY);\n let muted = false;\n const output = new Writable({\n write(chunk, _encoding, callback) {\n if (!muted) {\n target.write(chunk);\n }\n callback();\n },\n });\n const rl = createInterface({\n input,\n output,\n terminal,\n });\n\n // readline emits every line of one stdin chunk synchronously, while the next question is only registered on a\n // later microtask, so `rl.question` drops all but the first answer of a piped script. The lines are buffered\n // instead, and a close with a question still open is an error rather than the silent exit 0 node answers with.\n const lines: string[] = [];\n let pending: PendingLine | undefined;\n let closed = false;\n\n rl.on('line', (line) => {\n if (pending) {\n const { resolve } = pending;\n pending = undefined;\n resolve(line);\n return;\n }\n\n lines.push(line);\n });\n\n rl.on('close', () => {\n closed = true;\n if (pending) {\n const { reject } = pending;\n pending = undefined;\n reject(new Error(CLOSED_MESSAGE));\n }\n });\n\n // Ctrl+C on a TTY reaches readline rather than the process; without this it closes the interface and reads as EOF.\n rl.on('SIGINT', () => process.exit(130));\n\n function readLine(): Promise<{ line: string, buffered: boolean }> {\n const buffered = lines.shift();\n if (buffered !== undefined) {\n return Promise.resolve({ line: buffered, buffered: true });\n }\n\n if (closed) {\n return Promise.reject(new Error(CLOSED_MESSAGE));\n }\n\n return new Promise((resolve, reject) => {\n pending = { resolve: (line) => resolve({ line, buffered: false }), reject };\n });\n }\n\n return {\n // The answer is handed over untrimmed: a secret keeps its whitespace, and each parser trims what it should.\n ask: async (question, fallback, options) => {\n rl.setPrompt(fallback === undefined ? `${question}: ` : `${question} [${fallback}]: `);\n rl.prompt();\n const secret = terminal && Boolean(options?.secret);\n muted = secret;\n try {\n const { line, buffered } = await readLine();\n if (buffered && terminal && !secret) {\n // a type-ahead answer never echoed after this prompt, so the next prompt would overwrite the line\n target.write(`${line}\\n`);\n }\n\n return fallback !== undefined && line.trim() === '' ? fallback : line;\n } finally {\n if (secret) {\n // the Enter was swallowed with the echo\n muted = false;\n target.write('\\n');\n }\n }\n },\n close: () => rl.close(),\n };\n}\n","/*\n * Copyright (c) 2026.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nimport type { Target } from './types.ts';\n\n/**\n * The emitted files that hold a password, written owner-only by `src/index.ts`. The compose file, `nginx.conf`,\n * `package.json` and `authup.yml` carry none: every secret rides an `.env`, or `values.yaml` for helm.\n * `test/unit/secret-files.spec.ts` holds the renderers to this list, so a new secret-bearing file cannot slip past it.\n */\nexport const SECRET_FILES = new Set(['.env', 'authup.env', 'values.yaml']);\n\nexport const TARGETS: readonly Target[] = ['docker', 'compose', 'helm', 'bare-metal'] as const;\n","/*\n * Copyright (c) 2026.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nimport type { Exposure } from './types.ts';\n\n/** The port the process listens on inside a container, and the default everywhere else. */\nexport const LISTEN_PORT = 3000;\n\n// A public url with an explicit port is dialed directly, so that port is the one to publish or to listen on. A\n// default port, or https, means a reverse proxy in front owns the port a browser dials and reaches the listener\n// on its own, so the default listen port stays. `http://localhost` with no port is read as a proxy too; the\n// operator who really serves port 80 directly edits the mapping.\nexport function describeExposure(publicUrl: string): Exposure {\n const url = new URL(publicUrl);\n const behindProxy = url.protocol === 'https:' || url.port === '';\n\n return {\n behindProxy,\n hostPort: behindProxy ? LISTEN_PORT : Number(url.port),\n };\n}\n\n// The value core.trustProxy / TRUST_PROXY takes: the number of proxy hops the deployment itself knows about\n// (the operator's proxy in front, plus the one the compose console split emits), or false when nothing is in front,\n// since authup's default trusts every hop and lets any direct client spoof its address.\nexport function trustProxyValue(exposure: Exposure, ownProxy = false): string {\n const hops = (exposure.behindProxy ? 1 : 0) + (ownProxy ? 1 : 0);\n\n return hops > 0 ? String(hops) : 'false';\n}\n","/*\n * Copyright (c) 2026.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\n// A JSON string is a valid YAML double-quoted scalar, which also defuses the Norway problem and a leading `*`.\nexport function quoteYaml(value: string): string {\n return JSON.stringify(value);\n}\n\nconst PLAIN = /^[A-Za-z0-9_@:/.\\-+=%?&,]*$/;\n\n// Whether a value survives the .env round trip at all. dotenv strips the surrounding quotes and, inside DOUBLE quotes,\n// turns a literal \\n or \\r into the control character itself, with no escape that survives (it never unescapes a\n// doubled backslash). A value carrying a single quote, which rules the single-quoted form out, together with one of\n// those two sequences therefore has no representation, and `askSecret` refuses it rather than corrupting a password.\nexport function isEnvRepresentable(value: string): boolean {\n return !value.includes('\\'') || !/\\\\[nr]/.test(value);\n}\n\n// For the .env dotenv reads (`authup start` loads it from cwd): a value is single-quoted when it carries no single\n// quote and written raw inside double quotes otherwise, which round-trips everything `isEnvRepresentable` admits.\nexport function quoteEnv(value: string): string {\n if (PLAIN.test(value)) {\n return value;\n }\n\n if (!value.includes('\\'')) {\n return `'${value}'`;\n }\n\n return `\"${value}\"`;\n}\n\n// For the .env compose-go reads: double quotes take backslash escapes and `$` interpolates unless doubled, while a\n// single-quoted `\\'` there is an escaped quote rather than a literal, so one double-quoted branch covers every value.\nexport function quoteComposeEnv(value: string): string {\n if (PLAIN.test(value)) {\n return value;\n }\n\n return `\"${escapeComposeInterpolation(value.replace(/[\\\\\"]/g, '\\\\$&'))}\"`;\n}\n\n// compose interpolates `$name` and `${name}` in every yaml value and .env value; `$$` is its literal dollar.\nexport function escapeComposeInterpolation(value: string): string {\n return value.replace(/\\$/g, '$$$$');\n}\n\nexport function indent(text: string, spaces: number): string {\n const prefix = ' '.repeat(spaces);\n\n return text\n .split('\\n')\n .map((line) => (line.length > 0 ? prefix + line : line))\n .join('\\n');\n}\n","/*\n * Copyright (c) 2026.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nimport { TARGETS } from './constants.ts';\nimport { isEnvRepresentable } from './utils.ts';\nimport type {\n Answers,\n Ask,\n AskOptions,\n Database,\n Target,\n} from './types.ts';\n\ntype Parsed<T> = { value: T } | { error: string };\n\nconst ENGINE_PORTS = { postgres: 5432, mysql: 3306 } as const;\nconst BUNDLED_REDIS_URL = 'redis://redis:6379';\nconst UNREPRESENTABLE = 'A single quote together with a \\\\n or \\\\r sequence has no .env representation. Choose another value.';\nconst REDIS_NOTE = 'Redis is required for a split deployment: the console sign-in and the token blocklist ride the cache.';\n\n// A refused answer re-asks with the reason on the line above, so the flow stays pure over `ask`.\nasync function askUntil<T>(\n ask: Ask,\n question: string,\n fallback: string | undefined,\n parse: (answer: string) => Parsed<T>,\n options?: AskOptions,\n): Promise<T> {\n let prefix = '';\n\n for (;;) {\n const parsed = parse(await ask(prefix + question, fallback, options));\n if ('value' in parsed) {\n return parsed.value;\n }\n\n prefix = `${parsed.error}\\n`;\n }\n}\n\nfunction askChoice<T extends string>(ask: Ask, question: string, options: readonly T[], fallback: T): Promise<T> {\n const listed = options.map((option, index) => `${index + 1}: ${option}`).join(', ');\n\n return askUntil<T>(ask, `${question} (${listed})`, fallback, (answer) => {\n const normalized = answer.trim().toLowerCase();\n if ((options as readonly string[]).includes(normalized)) {\n return { value: normalized as T };\n }\n\n const numbered = options[Number(normalized) - 1];\n if (numbered !== undefined) {\n return { value: numbered };\n }\n\n return { error: `Pick one of ${options.join(', ')}, by name or number.` };\n });\n}\n\nfunction askYesNo(ask: Ask, question: string, fallback: boolean): Promise<boolean> {\n return askUntil<boolean>(ask, `${question} (y/n)`, fallback ? 'y' : 'n', (answer) => {\n const normalized = answer.trim().toLowerCase();\n if (normalized === 'y' || normalized === 'yes') {\n return { value: true };\n }\n if (normalized === 'n' || normalized === 'no') {\n return { value: false };\n }\n\n return { error: 'Answer y or n.' };\n });\n}\n\nfunction askUrl(ask: Ask, question: string, protocols: readonly string[] = ['http:', 'https:'], fallback?: string): Promise<string> {\n return askUntil<string>(ask, question, fallback, (answer) => {\n const trimmed = answer.trim();\n try {\n // a non-special scheme parses `smtp:mail` with an empty host, which would land in the file as written\n const url = new URL(trimmed);\n if (protocols.includes(url.protocol) && url.hostname !== '') {\n return { value: trimmed };\n }\n } catch {\n // falls through to the refusal\n }\n\n return { error: `Enter an absolute URL using ${protocols.map((protocol) => `${protocol}//`).join(' or ')}.` };\n });\n}\n\n// A free-text answer that must not be blank; trimmed, unlike a secret.\nfunction askText(ask: Ask, question: string, fallback?: string): Promise<string> {\n return askUntil<string>(ask, question, fallback, (answer) => {\n const trimmed = answer.trim();\n if (trimmed === '') {\n return { error: 'A value is required.' };\n }\n\n // a host, user or database name reaches the same .env the secrets do\n return isEnvRepresentable(trimmed) ? { value: trimmed } : { error: UNREPRESENTABLE };\n });\n}\n\n// The issuer: no credentials, query or fragment (OIDC Core 3.1.2.1), and for the helm target no path either, since\n// the chart publishes authup at the origin root and refuses a console split under a sub-path.\nfunction askPublicUrl(ask: Ask, target: Target): Promise<string> {\n return askUntil<string>(\n ask,\n 'Public URL: the address a browser reaches authup at. It is the OIDC issuer, and every console url derives from it',\n undefined,\n (answer) => {\n const trimmed = answer.trim();\n let url: URL;\n try {\n url = new URL(trimmed);\n } catch {\n return { error: 'Enter an absolute URL using http:// or https://.' };\n }\n\n if (url.protocol !== 'http:' && url.protocol !== 'https:') {\n return { error: 'Enter an absolute URL using http:// or https://.' };\n }\n if (url.username || url.password || url.search || url.hash) {\n return { error: 'The public URL is the OIDC issuer: no credentials, query or fragment.' };\n }\n if (target === 'helm' && url.pathname !== '/') {\n return { error: 'The helm chart publishes authup at the origin root: enter the URL without a path.' };\n }\n\n return { value: trimmed };\n },\n );\n}\n\nfunction askPort(ask: Ask, question: string, fallback: number): Promise<number> {\n return askUntil<number>(ask, question, String(fallback), (answer) => {\n const port = Number(answer.trim());\n if (Number.isSafeInteger(port) && port >= 1 && port <= 65535) {\n return { value: port };\n }\n\n return { error: 'Enter a port between 1 and 65535.' };\n });\n}\n\nfunction askSecret(ask: Ask, question: string): Promise<string> {\n return askUntil<string>(ask, question, undefined, (answer) => {\n if (answer.trim() === '') {\n return { error: 'A value is required.' };\n }\n if (answer === 'start123') {\n return { error: 'start123 is the provisioning default the wizard refuses to emit. Choose another value.' };\n }\n // the bounds the config registry declares for userAdminPassword\n if (answer.length < 3 || answer.length > 256) {\n return { error: 'Use between 3 and 256 characters.' };\n }\n if (!isEnvRepresentable(answer)) {\n return { error: UNREPRESENTABLE };\n }\n\n return { value: answer };\n }, { secret: true });\n}\n\nasync function askDatabase(ask: Ask, target: Target): Promise<Database> {\n const options: readonly ('postgres' | 'mysql' | 'sqlite')[] = target === 'bare-metal' ?\n ['postgres', 'mysql', 'sqlite'] :\n ['postgres', 'mysql'];\n const type = await askChoice(ask, 'Database', options, 'postgres');\n if (type === 'sqlite') {\n return { type: 'better-sqlite3' };\n }\n\n const bundled = (target === 'compose' || target === 'helm') &&\n await askYesNo(ask, 'Run the database alongside authup?', true);\n if (bundled) {\n return {\n type,\n bundled: true,\n host: type,\n port: ENGINE_PORTS[type],\n username: 'authup',\n password: await askSecret(ask, 'Database password'),\n database: 'authup',\n };\n }\n\n // 127.0.0.1 is the operator's machine on bare metal and the container itself everywhere else\n const hostQuestion = target === 'bare-metal' ?\n 'Database host' :\n 'Database host (a name the container resolves; 127.0.0.1 would be the container itself)';\n\n return {\n type,\n bundled: false,\n host: await askText(ask, hostQuestion, target === 'bare-metal' ? '127.0.0.1' : undefined),\n port: await askPort(ask, 'Database port', ENGINE_PORTS[type]),\n username: await askText(ask, 'Database username', 'authup'),\n password: await askSecret(ask, 'Database password'),\n database: await askText(ask, 'Database name', 'authup'),\n };\n}\n\nexport async function collectAnswers(ask: Ask): Promise<{ answers: Answers, notes: string[] }> {\n const target = await askChoice(ask, 'Deployment target', TARGETS, 'compose');\n const publicUrl = await askPublicUrl(ask, target);\n const tlsCertManager = target === 'helm' && publicUrl.startsWith('https:') &&\n await askYesNo(ask, 'Request the TLS certificate from cert-manager? The alternative is a <hostname>-tls secret in the release namespace', false);\n const db = await askDatabase(ask, target);\n\n const registrationEnabled = await askYesNo(ask, 'Enable user registration?', false);\n const passwordRecoveryEnabled = await askYesNo(ask, 'Enable password recovery?', false);\n const smtp = registrationEnabled || passwordRecoveryEnabled ?\n { url: await askUrl(ask, 'SMTP connection URL (smtp(s)://user:pass@host:port)', ['smtp:', 'smtps:']) } :\n false;\n\n const emailVerificationEnabled = registrationEnabled && await askYesNo(ask, 'Verify email addresses on registration?', true);\n\n const adminPassword = await askSecret(ask, 'Admin password');\n\n const splittable = target === 'compose' || target === 'helm';\n const workerSplit = splittable && await askYesNo(ask, 'Run the background worker as a separate service?', false);\n const consoleSplit = splittable && await askYesNo(ask, 'Run the consoles as a separate service?', false);\n\n const notes: string[] = [];\n let redis: Answers['redis'] = false;\n if (workerSplit || consoleSplit) {\n redis = { url: BUNDLED_REDIS_URL };\n notes.push(REDIS_NOTE);\n } else if (await askYesNo(ask, 'Use redis?', false)) {\n redis = {\n url: splittable ?\n BUNDLED_REDIS_URL :\n await askUrl(\n ask,\n target === 'bare-metal' ? 'Redis URL' : 'Redis URL (a host the container resolves)',\n ['redis:', 'rediss:'],\n target === 'bare-metal' ? 'redis://127.0.0.1:6379' : undefined,\n ),\n };\n }\n\n return {\n answers: {\n target,\n publicUrl,\n db,\n redis,\n smtp,\n registrationEnabled,\n passwordRecoveryEnabled,\n emailVerificationEnabled,\n adminPassword,\n workerSplit,\n consoleSplit,\n tlsCertManager,\n },\n notes,\n };\n}\n","/*\n * Copyright (c) 2026.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nimport { describeExposure, trustProxyValue } from '../deployment.ts';\nimport type { Answers, Rendered } from '../types.ts';\n\nexport function dockerRunCommand(version: string, hostPort: number): string {\n return `docker run -d --name authup --restart unless-stopped --env-file authup.env -p ${hostPort}:3000 authup/authup:${version} start`;\n}\n\nexport function renderDocker(answers: Answers, version: string): Rendered {\n if (answers.db.type === 'better-sqlite3') {\n throw new Error('The docker image runs in production mode and refuses sqlite: configure a postgres or mysql database.');\n }\n\n if (answers.workerSplit || answers.consoleSplit) {\n throw new Error('The docker run target is one container: a worker or console split needs the compose or helm target.');\n }\n\n const exposure = describeExposure(answers.publicUrl);\n\n // `docker run --env-file` reads KEY=value verbatim and does no quote handling, so values are written raw rather than through quoteEnv.\n const lines = [\n `PUBLIC_URL=${answers.publicUrl}`,\n '# The proxies in front whose X-Forwarded-For is trusted, or false; authup trusts every hop by default.',\n `TRUST_PROXY=${trustProxyValue(exposure)}`,\n `DB_TYPE=${answers.db.type}`,\n `DB_HOST=${answers.db.host}`,\n `DB_PORT=${answers.db.port}`,\n `DB_USERNAME=${answers.db.username}`,\n `DB_PASSWORD=${answers.db.password}`,\n `DB_DATABASE=${answers.db.database}`,\n ];\n\n if (answers.redis) {\n lines.push(`REDIS=${answers.redis.url}`);\n }\n\n if (answers.smtp) {\n lines.push(`SMTP=${answers.smtp.url}`);\n }\n\n if (answers.registrationEnabled) {\n lines.push('REGISTRATION_ENABLED=true');\n }\n\n if (answers.passwordRecoveryEnabled) {\n lines.push('PASSWORD_RECOVERY_ENABLED=true');\n }\n\n if (answers.emailVerificationEnabled) {\n lines.push('EMAIL_VERIFICATION_ENABLED=true');\n }\n\n lines.push(\n `USER_ADMIN_PASSWORD=${answers.adminPassword}`,\n '# Every downstream application origin, comma-separated; each may obtain a full-permission token.',\n '# TRUSTED_ORIGINS=https://app.example.com',\n '# Wraps the realm key store at rest (base64, 32 bytes). Write-once: back it up.',\n '# SECRETS_ENCRYPTION_KEY=<base64 32 bytes>',\n );\n\n return {\n 'authup.env': [\n '# Environment of the authup container. It holds secrets: keep it out of version control.',\n `# ${dockerRunCommand(version, exposure.hostPort)}`,\n '# The database must exist already; the image runs in production mode and refuses sqlite.',\n '# A configuration file or a provisioning directory is mounted with',\n '# -v ./authup.yml:/etc/authup/authup.yml:ro -v ./provisioning:/etc/authup/provisioning:ro',\n '# https://authup.org/guide/deployment/docker',\n ...lines,\n '',\n ].join('\\n'),\n };\n}\n","/*\n * Copyright (c) 2026.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nimport type { Answers, Rendered } from '../types.ts';\nimport { quoteYaml } from '../utils.ts';\n\n// The chart version, not the authup version: it is what `helm install` selects, and 0.4.0 is the first that carries\n// worker.* and server.splitConsoles (0.3.0 rejects both through its values schema).\nexport const CHART_REQUIREMENT = '# The worker and the console split need the authup helm chart 0.4.0 or later.';\n\nexport const HELM_COMMANDS: string[] = [\n 'helm repo add authup https://helm.authup.org',\n 'helm install authup authup/authup -f values.yaml',\n];\n\nexport function renderHelm(answers: Answers, _version: string): Rendered {\n const { db } = answers;\n if (db.type === 'better-sqlite3') {\n throw new Error('The helm chart needs a server database (postgres or mysql); the image refuses sqlite.');\n }\n\n const url = new URL(answers.publicUrl);\n const tls = url.protocol === 'https:';\n const lines: string[] = [\n `# Written by npm create authup. Install with: ${HELM_COMMANDS.join(' && ')}`,\n '# Secrets in this file (auth.adminPassword, the database password, smtp.connectionString) can move to the chart\\'s existingSecret keys; see the chart README.',\n // worker.* and server.splitConsoles arrived with the chart that tracks v1.0.0-beta.64 (authup/helm PR #29); the\n // released 0.3.0 rejects both through its values schema, while a no-split document renders on either.\n ...(answers.workerSplit || answers.consoleSplit ? [CHART_REQUIREMENT] : []),\n 'server:',\n ` publicUrl: ${quoteYaml(answers.publicUrl)}`,\n ...(tls && !answers.tlsCertManager ?\n [` # TLS needs a secret named ${url.hostname}-tls in the release namespace, or set server.ingress.certManager: true.`] :\n []),\n ' ingress:',\n ' enabled: true',\n ` hostname: ${quoteYaml(url.hostname)}`,\n ` tls: ${tls}`,\n ...(tls && answers.tlsCertManager ? [' certManager: true'] : []),\n ];\n\n if (answers.registrationEnabled || answers.passwordRecoveryEnabled || answers.emailVerificationEnabled) {\n lines.push(' features:');\n if (answers.registrationEnabled) {\n lines.push(' registration: true');\n }\n if (answers.passwordRecoveryEnabled) {\n lines.push(' passwordRecovery: true');\n }\n if (answers.emailVerificationEnabled) {\n lines.push(' emailVerification: true');\n }\n }\n\n if (answers.workerSplit) {\n lines.push(' migration:', ' enabled: true');\n }\n\n if (answers.consoleSplit) {\n lines.push(' splitConsoles: true');\n }\n\n lines.push(\n ' # Every downstream application origin; each may obtain a full-permission token.',\n ' # trustedOrigins:',\n ' # - https://app.example.com',\n ' # An authup.yml the chart mounts, for the options that only the file can carry.',\n ' # configuration: |',\n ' # core:',\n ' # passwordMinLength: 12',\n );\n\n if (answers.workerSplit) {\n lines.push('', 'worker:', ' enabled: true');\n }\n\n lines.push(\n '',\n 'database:',\n ` type: ${db.type}`,\n '',\n );\n\n // The bundled engine takes the answered password; left empty, the chart would generate one and drop the answer.\n for (const engine of ['postgresql', 'mysql'] as const) {\n const enabled = db.bundled && db.type === (engine === 'postgresql' ? 'postgres' : 'mysql');\n lines.push(`${engine}:`, ` enabled: ${enabled}`);\n if (enabled) {\n lines.push(' auth:', ` password: ${quoteYaml(db.password)}`);\n }\n }\n\n if (!db.bundled) {\n // The chart schema types the port as a string (\"\" = engine default), so a bare integer fails helm's validation.\n lines.push(\n 'externalDatabase:',\n ` host: ${quoteYaml(db.host)}`,\n ` port: ${quoteYaml(String(db.port))}`,\n ` user: ${quoteYaml(db.username)}`,\n ` database: ${quoteYaml(db.database)}`,\n ` password: ${quoteYaml(db.password)}`,\n );\n }\n\n if (answers.redis) {\n lines.push(\n '',\n 'valkey:',\n ' enabled: true',\n '# For an external Redis set valkey.enabled to false and externalRedis.url to its redis:// URL instead.',\n );\n }\n\n lines.push(\n '',\n 'auth:',\n ` adminPassword: ${quoteYaml(answers.adminPassword)}`,\n ' # Wraps the realm key store at rest (base64, 32 bytes). Write-once: back it up.',\n ' # secretsEncryptionKey: <base64 32 bytes>',\n );\n\n if (answers.smtp) {\n lines.push('', 'smtp:', ` connectionString: ${quoteYaml(answers.smtp.url)}`);\n }\n\n return { 'values.yaml': `${lines.join('\\n')}\\n` };\n}\n","/*\n * Copyright (c) 2026.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nimport { LISTEN_PORT, describeExposure, trustProxyValue } from '../deployment.ts';\nimport type { Answers, Rendered } from '../types.ts';\nimport { quoteEnv, quoteYaml } from '../utils.ts';\n\nconst SCHEMA_COMMENT = '# yaml-language-server: $schema=https://authup.org/schema/config.json';\nconst DATABASE_DOCS_URL = 'https://authup.org/guide/deployment/configuration-server-core-database';\n\nexport function renderBareMetal(answers: Answers, version: string): Rendered {\n if (answers.workerSplit || answers.consoleSplit) {\n throw new Error('The bare-metal target runs one process: a worker or console split is a second process under a process manager, which the wizard does not write.');\n }\n\n return {\n 'package.json': renderPackageJson(version),\n 'authup.yml': renderConfig(answers),\n '.env': renderEnv(answers),\n };\n}\n\nfunction renderPackageJson(version: string): string {\n const manifest = {\n name: 'authup-deployment',\n private: true,\n type: 'module',\n scripts: { start: 'authup start' },\n // exact, like the image tag the container targets write: the wizard scaffolds one release, not a range\n dependencies: { authup: version },\n };\n\n return `${JSON.stringify(manifest, null, 4)}\\n`;\n}\n\nfunction renderConfig(answers: Answers): string {\n const { db } = answers;\n const lines = [\n SCHEMA_COMMENT,\n `publicUrl: ${quoteYaml(answers.publicUrl)}`,\n ];\n\n if (db.type === 'better-sqlite3') {\n lines.push(\n '# Production refuses sqlite, so this file keeps the development default of env.',\n `# That is fine for a local trial only: ${DATABASE_DOCS_URL}`,\n '',\n '# db.sqlite is written into this directory.',\n );\n } else {\n lines.push(\n 'env: production',\n '',\n // A `db` block here would be used AS IS: server-core never merges DB_* over it, so the password could not\n // stay in .env. The whole connection rides the DB_* variables there instead (the documented env form).\n '# The database connection is the DB_* set in .env.',\n );\n }\n\n if (answers.redis) {\n lines.push('', `redis: ${quoteYaml(answers.redis.url)}`);\n }\n\n if (answers.smtp) {\n lines.push('', '# The connection URL is SMTP in .env.', 'smtp: true');\n }\n\n lines.push(\n '',\n '# Every downstream application origin; each may obtain a full-permission token.',\n '# trustedOrigins:',\n '# - https://app.example.com',\n );\n\n // The process listens where the url says when nothing is in front; behind a proxy the default stays.\n const exposure = describeExposure(answers.publicUrl);\n lines.push(\n '',\n 'core:',\n ...(exposure.hostPort !== LISTEN_PORT ? [` port: ${exposure.hostPort}`] : []),\n ' # The proxies in front whose X-Forwarded-For is trusted, or false; authup trusts every hop by default.',\n ` trustProxy: ${trustProxyValue(exposure)}`,\n ...(answers.registrationEnabled ? [' registrationEnabled: true'] : []),\n ...(answers.passwordRecoveryEnabled ? [' passwordRecoveryEnabled: true'] : []),\n ...(answers.emailVerificationEnabled ? [' emailVerificationEnabled: true'] : []),\n );\n\n return `${lines.join('\\n')}\\n`;\n}\n\nfunction renderEnv(answers: Answers): string {\n const lines = ['# Secrets. authup start reads this file from the directory it is started in. Keep it out of version control.'];\n\n const { db } = answers;\n if (db.type !== 'better-sqlite3') {\n lines.push(\n `DB_TYPE=${db.type}`,\n `DB_HOST=${quoteEnv(db.host)}`,\n `DB_PORT=${db.port}`,\n `DB_USERNAME=${quoteEnv(db.username)}`,\n `DB_PASSWORD=${quoteEnv(db.password)}`,\n `DB_DATABASE=${quoteEnv(db.database)}`,\n );\n }\n\n lines.push(`USER_ADMIN_PASSWORD=${quoteEnv(answers.adminPassword)}`);\n\n if (answers.smtp) {\n lines.push(`SMTP=${quoteEnv(answers.smtp.url)}`);\n }\n\n lines.push(\n '# Wraps the realm key store at rest (base64, 32 bytes). Write-once: back it up.',\n '# SECRETS_ENCRYPTION_KEY=',\n );\n\n return `${lines.join('\\n')}\\n`;\n}\n","/*\n * Copyright (c) 2026.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nimport { describeExposure, trustProxyValue } from '../deployment.ts';\nimport type { Answers, Rendered } from '../types.ts';\nimport {\n escapeComposeInterpolation,\n indent,\n quoteComposeEnv,\n quoteYaml,\n} from '../utils.ts';\n\nconst DOCS_URL = 'https://authup.org/guide/deployment';\n\nconst NGINX_CONF = `# Written by npm create authup: the console split's routing, ${DOCS_URL}/console-replicas\n# TLS, if any, terminates in front of this proxy; X-Forwarded-Proto is passed through as the scheme it sees.\nupstream authup_api { server authup:3000; }\nupstream authup_auth_console { server authup-console:3020; }\nupstream authup_admin_console { server authup-console:3021; }\nupstream authup_account_console { server authup-console:3022; }\n\nserver {\n listen 80;\n # nginx listens on 80 inside the container while the published port is another one: a redirect it builds itself\n # (the trailing slash it adds to /console/<name>) must stay relative or it would send the browser to port 80.\n absolute_redirect off;\n\n # The sign-in pair stays on the API. Exact matches, so they win over the prefix rules below.\n location = /console/admin/login/start { proxy_pass http://authup_api; }\n location = /console/admin/callback { proxy_pass http://authup_api; }\n location = /console/account/login/start { proxy_pass http://authup_api; }\n location = /console/account/callback { proxy_pass http://authup_api; }\n\n location /console/auth/ {\n rewrite ^/console/auth(/.*)$ $1 break;\n proxy_set_header Host $host;\n proxy_set_header X-Forwarded-Proto $scheme;\n proxy_set_header X-Forwarded-Tls-Client-Cert \"\";\n proxy_pass http://authup_auth_console;\n }\n\n location /console/admin/ {\n rewrite ^/console/admin(/.*)$ $1 break;\n proxy_set_header Host $host;\n proxy_set_header X-Forwarded-Proto $scheme;\n proxy_set_header X-Forwarded-Tls-Client-Cert \"\";\n proxy_pass http://authup_admin_console;\n }\n\n location /console/account/ {\n rewrite ^/console/account(/.*)$ $1 break;\n proxy_set_header Host $host;\n proxy_set_header X-Forwarded-Proto $scheme;\n proxy_set_header X-Forwarded-Tls-Client-Cert \"\";\n proxy_pass http://authup_account_console;\n }\n\n location / {\n proxy_set_header Host $host;\n proxy_set_header X-Real-IP $remote_addr;\n proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n proxy_set_header X-Forwarded-Proto $scheme;\n proxy_set_header X-Forwarded-Tls-Client-Cert \"\";\n proxy_pass http://authup_api;\n }\n}\n`;\n\n\nconst ENGINES = {\n postgres: {\n image: 'postgres:17',\n port: 5432,\n dataPath: '/var/lib/postgresql/data',\n },\n mysql: {\n image: 'mysql:8.4',\n port: 3306,\n dataPath: '/var/lib/mysql',\n },\n} as const;\n\n// One list entry of a compose `environment` block; the whole KEY=value is one YAML scalar, so quoting it covers the\n// value, and an operator's `$` is doubled so compose does not interpolate it away.\nfunction env(key: string, value: string): string {\n return `- ${quoteYaml(`${key}=${escapeComposeInterpolation(value)}`)}`;\n}\n\n// The same entry carrying a deliberate `${VARIABLE}` reference into .env.\nfunction ref(key: string, variable: string): string {\n return `- ${quoteYaml(`${key}=\\${${variable}}`)}`;\n}\n\nfunction block(key: string, lines: string[]): string[] {\n return [`${key}:`, ...indent(lines.join('\\n'), 2).split('\\n')];\n}\n\nexport function renderCompose(answers: Answers, version: string): Rendered {\n const { db } = answers;\n if (db.type === 'better-sqlite3') {\n throw new Error('The compose target needs a server database (postgres or mysql): the image runs in production mode and refuses sqlite.');\n }\n\n const image = `image: authup/authup:${version}`;\n const restart = 'restart: unless-stopped';\n const engine = ENGINES[db.type];\n const exposure = describeExposure(answers.publicUrl);\n\n const dbEnv = [\n env('DB_TYPE', db.type),\n env('DB_HOST', db.bundled ? db.type : db.host),\n env('DB_PORT', String(db.bundled ? engine.port : db.port)),\n env('DB_USERNAME', db.username),\n ref('DB_PASSWORD', 'DB_PASSWORD'),\n env('DB_DATABASE', db.database),\n ];\n if (answers.redis) {\n dbEnv.push(env('REDIS', 'redis://redis:6379'));\n }\n\n const dependsOn: string[] = [];\n if (db.bundled) {\n // The one-off `migration run` and the services would otherwise race an engine still initializing its volume.\n dependsOn.push(`${db.type}:`, ' condition: service_healthy');\n }\n if (answers.redis) {\n dependsOn.push('redis:', ' condition: service_started');\n }\n const dependsOnBlock = dependsOn.length > 0 ? block('depends_on', dependsOn) : [];\n\n const authupEnv = [\n env('PUBLIC_URL', answers.publicUrl),\n '# the proxies in front whose X-Forwarded-For is trusted, or false; authup trusts every hop by default',\n env('TRUST_PROXY', trustProxyValue(exposure, answers.consoleSplit)),\n ...dbEnv,\n ref('USER_ADMIN_PASSWORD', 'USER_ADMIN_PASSWORD'),\n ];\n if (answers.smtp) {\n authupEnv.push(ref('SMTP', 'SMTP'));\n }\n if (answers.registrationEnabled) {\n authupEnv.push(env('REGISTRATION_ENABLED', 'true'));\n }\n if (answers.passwordRecoveryEnabled) {\n authupEnv.push(env('PASSWORD_RECOVERY_ENABLED', 'true'));\n }\n if (answers.emailVerificationEnabled) {\n authupEnv.push(env('EMAIL_VERIFICATION_ENABLED', 'true'));\n }\n authupEnv.push(\n '# every downstream application origin, comma-separated; each may obtain a full-permission token',\n '# - \"TRUSTED_ORIGINS=https://app.example.com\"',\n '# wraps the realm key store at rest (base64, 32 bytes); write-once, back it up',\n '# - \"SECRETS_ENCRYPTION_KEY=<base64 32 bytes>\"',\n );\n // A console-only split keeps one API process, which sweeps and migrates itself like a plain `start` does.\n if (answers.workerSplit) {\n authupEnv.push(\n '# the sweeps run in the worker below',\n env('WORKER_ENABLED', 'false'),\n '# run \"docker compose run --rm authup migration run\" once before starting',\n env('MIGRATION_ENABLED', 'false'),\n );\n }\n\n const services = block('authup', [\n image,\n restart,\n // under a console split the proxy below owns the published port\n ...(answers.consoleSplit ? [] : block('ports', [`- \"${exposure.hostPort}:3000\"`])),\n '# a configuration file and a provisioning directory are optional; mount them under /etc/authup',\n '# volumes:',\n '# - ./authup.yml:/etc/authup/authup.yml:ro',\n '# - ./provisioning:/etc/authup/provisioning:ro',\n ...dependsOnBlock,\n ...block('environment', authupEnv),\n `command: ${answers.consoleSplit ? 'start core' : 'start'}`,\n ]);\n\n if (db.bundled) {\n const credentials = db.type === 'postgres' ?\n [\n env('POSTGRES_USER', db.username),\n ref('POSTGRES_PASSWORD', 'DB_PASSWORD'),\n env('POSTGRES_DB', db.database),\n ] :\n [\n env('MYSQL_USER', db.username),\n ref('MYSQL_PASSWORD', 'DB_PASSWORD'),\n ref('MYSQL_ROOT_PASSWORD', 'DB_PASSWORD'),\n env('MYSQL_DATABASE', db.database),\n ];\n\n // Both official images run a temporary, network-less server while initializing an empty volume, so a probe\n // over TCP answers only once the real server is up (pg_isready over the socket would answer too early).\n const probe = db.type === 'postgres' ?\n `test: [\"CMD\", \"pg_isready\", \"-h\", \"localhost\", \"-U\", ${quoteYaml(escapeComposeInterpolation(db.username))}, \"-d\", ${quoteYaml(escapeComposeInterpolation(db.database))}]` :\n // mysqladmin ping exits 0 whenever the server takes the connection, credentials or not\n 'test: [\"CMD\", \"mysqladmin\", \"ping\", \"-h\", \"127.0.0.1\"]';\n\n services.push('', ...block(db.type, [\n `image: ${engine.image}`,\n restart,\n ...block('volumes', [`- ${db.type}_data:${engine.dataPath}`]),\n ...block('environment', credentials),\n ...block('healthcheck', [probe, 'interval: 5s', 'timeout: 5s', 'retries: 24']),\n ]));\n }\n\n if (answers.redis) {\n services.push('', ...block('redis', ['image: redis:7', restart]));\n }\n\n if (answers.workerSplit) {\n services.push(\n '',\n '# one instance is enough, whatever the API scales to',\n ...block('authup-worker', [\n image,\n restart,\n ...dependsOnBlock,\n // the worker needs no url of its own; without one the CLI derives it and warns on every start\n ...block('environment', [env('PUBLIC_URL', answers.publicUrl), ...dbEnv]),\n 'command: start worker',\n ]),\n );\n }\n\n if (answers.consoleSplit) {\n services.push(\n '',\n '# Two proxy rules, in this order: /console/<name>/login/start and',\n '# /console/<name>/callback (admin and account) go to the authup service;',\n '# everything else under /console/** goes here, with the /console/<name>',\n `# prefix stripped. See ${DOCS_URL}/console-replicas`,\n ...block('authup-console', [\n image,\n restart,\n ...block('environment', [\n env('PUBLIC_URL', answers.publicUrl),\n '# the auth console renders server-side and calls the API itself: reach it on this network',\n env('INTERNAL_URL', 'http://authup:3000'),\n ]),\n '# the image probes the API port, which this process never opens; the auth',\n '# console cannot be disabled, so it is always listening (3020 by default)',\n ...block('healthcheck', [\n 'test: [\"CMD\", \"wget\", \"--spider\", \"--proxy\", \"off\", \"http://127.0.0.1:3020/healthy\"]',\n 'interval: 10s',\n ]),\n 'command: start console',\n ]),\n '',\n '# The proxy owns the published port and carries the two console routing rules (nginx.conf).',\n ...block('nginx', [\n 'image: nginx:1.31-alpine',\n restart,\n ...block('ports', [`- \"${exposure.hostPort}:80\"`]),\n ...block('volumes', ['- ./nginx.conf:/etc/nginx/conf.d/default.conf:ro']),\n ...block('depends_on', ['authup:', ' condition: service_started', 'authup-console:', ' condition: service_started']),\n ]),\n );\n }\n\n const compose = [\n `# Written by npm create authup. Secrets live in .env next to this file: ${DOCS_URL}/docker-compose`,\n '',\n ];\n if (db.bundled) {\n compose.push(...block('volumes', [`${db.type}_data:`]), '');\n }\n compose.push(...block('services', services));\n\n const dotenv = [\n '# Secrets docker-compose.yml interpolates. Keep this file out of version control.',\n `DB_PASSWORD=${quoteComposeEnv(db.password)}`,\n `USER_ADMIN_PASSWORD=${quoteComposeEnv(answers.adminPassword)}`,\n ];\n if (answers.smtp) {\n dotenv.push(`SMTP=${quoteComposeEnv(answers.smtp.url)}`);\n }\n dotenv.push(\n '# Wraps the realm key store at rest (base64, 32 bytes). Write-once: back it up.',\n '# SECRETS_ENCRYPTION_KEY=',\n );\n\n return {\n 'docker-compose.yml': `${compose.join('\\n')}\\n`,\n '.env': `${dotenv.join('\\n')}\\n`,\n ...(answers.consoleSplit ? { 'nginx.conf': NGINX_CONF } : {}),\n };\n}\n","/*\n * Copyright (c) 2026.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nimport type { Renderer, Target } from '../types.ts';\nimport { renderBareMetal } from './bare-metal.ts';\nimport { renderCompose } from './compose.ts';\nimport { renderDocker } from './docker.ts';\nimport { renderHelm } from './helm.ts';\n\nexport const RENDERERS: Record<Target, Renderer> = {\n docker: renderDocker,\n compose: renderCompose,\n helm: renderHelm,\n 'bare-metal': renderBareMetal,\n};\n","/*\n * Copyright (c) 2026.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nimport { readFileSync } from 'node:fs';\n\n// dist/index.mjs and src/version.ts both sit one level below the package root, so one relative URL serves the built bin and vitest alike.\nexport const VERSION: string = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8')).version;\n","#!/usr/bin/env node\n\n/*\n * Copyright (c) 2026.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\n/* eslint-disable no-console */\n\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport process from 'node:process';\nimport { parseArgs } from 'node:util';\nimport { createReadlineAsk } from './ask.ts';\nimport { SECRET_FILES, TARGETS } from './constants.ts';\nimport { describeExposure } from './deployment.ts';\nimport { collectAnswers } from './prompts.ts';\nimport { dockerRunCommand } from './targets/docker.ts';\nimport { HELM_COMMANDS } from './targets/helm.ts';\nimport { RENDERERS } from './targets/index.ts';\nimport type { Answers, Target } from './types.ts';\nimport { VERSION } from './version.ts';\n\nconst DOCS_URL = 'https://authup.org/guide/deployment/';\n\nconst USAGE = `Usage: npx create-authup [--force] [--help]\n npm create authup -- [--force] [--help]\n\nWrites the deployment files for one authup installation into the current directory.\n\nTargets: ${TARGETS.join(', ')}\n\nFlags:\n --force overwrite files that already exist\n --help print this text`;\n\nfunction nextSteps(answers: Answers): string[] {\n const steps: Record<Target, string[]> = {\n docker: [\n dockerRunCommand(VERSION, describeExposure(answers.publicUrl).hostPort),\n 'The database must already exist: the image runs in production mode and refuses sqlite.',\n ],\n compose: [\n ...(answers.workerSplit ? ['docker compose run --rm authup migration run'] : []),\n 'docker compose up -d',\n ...(answers.consoleSplit ? ['The nginx service publishes the port; TLS, if any, terminates in front of it.'] : []),\n ],\n helm: [...HELM_COMMANDS],\n 'bare-metal': ['npm install', 'npx authup config validate', 'npm start'],\n };\n\n return steps[answers.target];\n}\n\nasync function main(): Promise<void> {\n const { values } = parseArgs({\n options: {\n force: { type: 'boolean', default: false },\n help: { type: 'boolean', default: false },\n },\n allowPositionals: false,\n });\n if (values.help) {\n console.log(USAGE);\n return;\n }\n\n console.log(`create-authup ${VERSION}`);\n console.log(`Files are written into the current directory (${process.cwd()}).`);\n\n const { ask, close } = createReadlineAsk();\n let collected: Awaited<ReturnType<typeof collectAnswers>>;\n try {\n collected = await collectAnswers(ask);\n } finally {\n close();\n }\n const { answers, notes } = collected;\n\n const rendered = RENDERERS[answers.target](answers, VERSION);\n const files = Object.keys(rendered);\n const directories = files.filter((name) => {\n try {\n return fs.statSync(path.join(process.cwd(), name)).isDirectory();\n } catch {\n return false;\n }\n });\n if (directories.length > 0) {\n console.error(`Cannot write over a directory: ${directories.join(', ')}.`);\n process.exit(1);\n }\n\n const existing = files.filter((name) => fs.existsSync(path.join(process.cwd(), name)));\n if (existing.length > 0 && !values.force) {\n console.error(`Refusing to overwrite: ${existing.join(', ')}. Re-run with --force.`);\n process.exit(1);\n }\n\n for (const name of files) {\n const target = path.join(process.cwd(), name);\n const secret = SECRET_FILES.has(name);\n fs.writeFileSync(target, rendered[name], secret ? { encoding: 'utf8', mode: 0o600 } : 'utf8');\n if (secret) {\n // writeFileSync's mode applies to a file it creates, so an overwrite keeps whatever the old one had\n fs.chmodSync(target, 0o600);\n }\n }\n\n for (const note of notes) {\n console.log(note);\n }\n console.log(`Written: ${files.join(', ')}`);\n console.log('Next steps:');\n for (const step of nextSteps(answers)) {\n console.log(` ${step}`);\n }\n console.log(`Docs: ${DOCS_URL}`);\n}\n\nmain().catch((e) => {\n console.error(e instanceof Error ? e.message : String(e));\n process.exit(1);\n});\n\n"],"mappings":";;;;;;;;AAiBA,MAAM,iBAAiB;;;;;;AAOvB,SAAgB,kBAAkB,MAA0B,CAAC,GAAsB;CAC/E,MAAM,QAAQ,IAAI,SAAS,QAAQ;CACnC,MAAM,SAAS,IAAI,UAAU,QAAQ;CAIrC,MAAM,WAAW,QAAQ,MAAM,SAAS,OAAO,KAAK;CACpD,IAAI,QAAQ;CACZ,MAAM,SAAS,IAAI,SAAS,EACxB,MAAM,OAAO,WAAW,UAAU;EAC9B,IAAI,CAAC,OACD,OAAO,MAAM,KAAK;EAEtB,SAAS;CACb,EACJ,CAAC;CACD,MAAM,KAAK,gBAAgB;EACvB;EACA;EACA;CACJ,CAAC;CAKD,MAAM,QAAkB,CAAC;CACzB,IAAI;CACJ,IAAI,SAAS;CAEb,GAAG,GAAG,SAAS,SAAS;EACpB,IAAI,SAAS;GACT,MAAM,EAAE,YAAY;GACpB,UAAU,KAAA;GACV,QAAQ,IAAI;GACZ;EACJ;EAEA,MAAM,KAAK,IAAI;CACnB,CAAC;CAED,GAAG,GAAG,eAAe;EACjB,SAAS;EACT,IAAI,SAAS;GACT,MAAM,EAAE,WAAW;GACnB,UAAU,KAAA;GACV,uBAAO,IAAI,MAAM,cAAc,CAAC;EACpC;CACJ,CAAC;CAGD,GAAG,GAAG,gBAAgB,QAAQ,KAAK,GAAG,CAAC;CAEvC,SAAS,WAAyD;EAC9D,MAAM,WAAW,MAAM,MAAM;EAC7B,IAAI,aAAa,KAAA,GACb,OAAO,QAAQ,QAAQ;GAAE,MAAM;GAAU,UAAU;EAAK,CAAC;EAG7D,IAAI,QACA,OAAO,QAAQ,uBAAO,IAAI,MAAM,cAAc,CAAC;EAGnD,OAAO,IAAI,SAAS,SAAS,WAAW;GACpC,UAAU;IAAE,UAAU,SAAS,QAAQ;KAAE;KAAM,UAAU;IAAM,CAAC;IAAG;GAAO;EAC9E,CAAC;CACL;CAEA,OAAO;EAEH,KAAK,OAAO,UAAU,UAAU,YAAY;GACxC,GAAG,UAAU,aAAa,KAAA,IAAY,GAAG,SAAS,MAAM,GAAG,SAAS,IAAI,SAAS,IAAI;GACrF,GAAG,OAAO;GACV,MAAM,SAAS,YAAY,QAAQ,SAAS,MAAM;GAClD,QAAQ;GACR,IAAI;IACA,MAAM,EAAE,MAAM,aAAa,MAAM,SAAS;IAC1C,IAAI,YAAY,YAAY,CAAC,QAEzB,OAAO,MAAM,GAAG,KAAK,GAAG;IAG5B,OAAO,aAAa,KAAA,KAAa,KAAK,KAAK,MAAM,KAAK,WAAW;GACrE,UAAU;IACN,IAAI,QAAQ;KAER,QAAQ;KACR,OAAO,MAAM,IAAI;IACrB;GACJ;EACJ;EACA,aAAa,GAAG,MAAM;CAC1B;AACJ;;;;;;;;ACtGA,MAAa,+BAAe,IAAI,IAAI;CAAC;CAAQ;CAAc;AAAa,CAAC;AAEzE,MAAa,UAA6B;CAAC;CAAU;CAAW;CAAQ;AAAY;;;;ACNpF,MAAa,cAAc;AAM3B,SAAgB,iBAAiB,WAA6B;CAC1D,MAAM,MAAM,IAAI,IAAI,SAAS;CAC7B,MAAM,cAAc,IAAI,aAAa,YAAY,IAAI,SAAS;CAE9D,OAAO;EACH;EACA,UAAU,cAAc,cAAc,OAAO,IAAI,IAAI;CACzD;AACJ;AAKA,SAAgB,gBAAgB,UAAoB,WAAW,OAAe;CAC1E,MAAM,QAAQ,SAAS,cAAc,IAAI,MAAM,WAAW,IAAI;CAE9D,OAAO,OAAO,IAAI,OAAO,IAAI,IAAI;AACrC;;;ACzBA,SAAgB,UAAU,OAAuB;CAC7C,OAAO,KAAK,UAAU,KAAK;AAC/B;AAEA,MAAM,QAAQ;AAMd,SAAgB,mBAAmB,OAAwB;CACvD,OAAO,CAAC,MAAM,SAAS,GAAI,KAAK,CAAC,SAAS,KAAK,KAAK;AACxD;AAIA,SAAgB,SAAS,OAAuB;CAC5C,IAAI,MAAM,KAAK,KAAK,GAChB,OAAO;CAGX,IAAI,CAAC,MAAM,SAAS,GAAI,GACpB,OAAO,IAAI,MAAM;CAGrB,OAAO,IAAI,MAAM;AACrB;AAIA,SAAgB,gBAAgB,OAAuB;CACnD,IAAI,MAAM,KAAK,KAAK,GAChB,OAAO;CAGX,OAAO,IAAI,2BAA2B,MAAM,QAAQ,UAAU,MAAM,CAAC,EAAE;AAC3E;AAGA,SAAgB,2BAA2B,OAAuB;CAC9D,OAAO,MAAM,QAAQ,OAAO,MAAM;AACtC;AAEA,SAAgB,OAAO,MAAc,QAAwB;CACzD,MAAM,SAAS,IAAI,OAAO,MAAM;CAEhC,OAAO,KACF,MAAM,IAAI,CAAC,CACX,KAAK,SAAU,KAAK,SAAS,IAAI,SAAS,OAAO,IAAK,CAAC,CACvD,KAAK,IAAI;AAClB;;;ACvCA,MAAM,eAAe;CAAE,UAAU;CAAM,OAAO;AAAK;AACnD,MAAM,oBAAoB;AAC1B,MAAM,kBAAkB;AACxB,MAAM,aAAa;AAGnB,eAAe,SACX,KACA,UACA,UACA,OACA,SACU;CACV,IAAI,SAAS;CAEb,SAAS;EACL,MAAM,SAAS,MAAM,MAAM,IAAI,SAAS,UAAU,UAAU,OAAO,CAAC;EACpE,IAAI,WAAW,QACX,OAAO,OAAO;EAGlB,SAAS,GAAG,OAAO,MAAM;CAC7B;AACJ;AAEA,SAAS,UAA4B,KAAU,UAAkB,SAAuB,UAAyB;CAG7G,OAAO,SAAY,KAAK,GAAG,SAAS,IAFrB,QAAQ,KAAK,QAAQ,UAAU,GAAG,QAAQ,EAAE,IAAI,QAAQ,CAAC,CAAC,KAAK,IAEjC,EAAE,IAAI,WAAW,WAAW;EACrE,MAAM,aAAa,OAAO,KAAK,CAAC,CAAC,YAAY;EAC7C,IAAK,QAA8B,SAAS,UAAU,GAClD,OAAO,EAAE,OAAO,WAAgB;EAGpC,MAAM,WAAW,QAAQ,OAAO,UAAU,IAAI;EAC9C,IAAI,aAAa,KAAA,GACb,OAAO,EAAE,OAAO,SAAS;EAG7B,OAAO,EAAE,OAAO,eAAe,QAAQ,KAAK,IAAI,EAAE,sBAAsB;CAC5E,CAAC;AACL;AAEA,SAAS,SAAS,KAAU,UAAkB,UAAqC;CAC/E,OAAO,SAAkB,KAAK,GAAG,SAAS,SAAS,WAAW,MAAM,MAAM,WAAW;EACjF,MAAM,aAAa,OAAO,KAAK,CAAC,CAAC,YAAY;EAC7C,IAAI,eAAe,OAAO,eAAe,OACrC,OAAO,EAAE,OAAO,KAAK;EAEzB,IAAI,eAAe,OAAO,eAAe,MACrC,OAAO,EAAE,OAAO,MAAM;EAG1B,OAAO,EAAE,OAAO,iBAAiB;CACrC,CAAC;AACL;AAEA,SAAS,OAAO,KAAU,UAAkB,YAA+B,CAAC,SAAS,QAAQ,GAAG,UAAoC;CAChI,OAAO,SAAiB,KAAK,UAAU,WAAW,WAAW;EACzD,MAAM,UAAU,OAAO,KAAK;EAC5B,IAAI;GAEA,MAAM,MAAM,IAAI,IAAI,OAAO;GAC3B,IAAI,UAAU,SAAS,IAAI,QAAQ,KAAK,IAAI,aAAa,IACrD,OAAO,EAAE,OAAO,QAAQ;EAEhC,QAAQ,CAER;EAEA,OAAO,EAAE,OAAO,+BAA+B,UAAU,KAAK,aAAa,GAAG,SAAS,GAAG,CAAC,CAAC,KAAK,MAAM,EAAE,GAAG;CAChH,CAAC;AACL;AAGA,SAAS,QAAQ,KAAU,UAAkB,UAAoC;CAC7E,OAAO,SAAiB,KAAK,UAAU,WAAW,WAAW;EACzD,MAAM,UAAU,OAAO,KAAK;EAC5B,IAAI,YAAY,IACZ,OAAO,EAAE,OAAO,uBAAuB;EAI3C,OAAO,mBAAmB,OAAO,IAAI,EAAE,OAAO,QAAQ,IAAI,EAAE,OAAO,gBAAgB;CACvF,CAAC;AACL;AAIA,SAAS,aAAa,KAAU,QAAiC;CAC7D,OAAO,SACH,KACA,qHACA,KAAA,IACC,WAAW;EACR,MAAM,UAAU,OAAO,KAAK;EAC5B,IAAI;EACJ,IAAI;GACA,MAAM,IAAI,IAAI,OAAO;EACzB,QAAQ;GACJ,OAAO,EAAE,OAAO,mDAAmD;EACvE;EAEA,IAAI,IAAI,aAAa,WAAW,IAAI,aAAa,UAC7C,OAAO,EAAE,OAAO,mDAAmD;EAEvE,IAAI,IAAI,YAAY,IAAI,YAAY,IAAI,UAAU,IAAI,MAClD,OAAO,EAAE,OAAO,wEAAwE;EAE5F,IAAI,WAAW,UAAU,IAAI,aAAa,KACtC,OAAO,EAAE,OAAO,oFAAoF;EAGxG,OAAO,EAAE,OAAO,QAAQ;CAC5B,CACJ;AACJ;AAEA,SAAS,QAAQ,KAAU,UAAkB,UAAmC;CAC5E,OAAO,SAAiB,KAAK,UAAU,OAAO,QAAQ,IAAI,WAAW;EACjE,MAAM,OAAO,OAAO,OAAO,KAAK,CAAC;EACjC,IAAI,OAAO,cAAc,IAAI,KAAK,QAAQ,KAAK,QAAQ,OACnD,OAAO,EAAE,OAAO,KAAK;EAGzB,OAAO,EAAE,OAAO,oCAAoC;CACxD,CAAC;AACL;AAEA,SAAS,UAAU,KAAU,UAAmC;CAC5D,OAAO,SAAiB,KAAK,UAAU,KAAA,IAAY,WAAW;EAC1D,IAAI,OAAO,KAAK,MAAM,IAClB,OAAO,EAAE,OAAO,uBAAuB;EAE3C,IAAI,WAAW,YACX,OAAO,EAAE,OAAO,yFAAyF;EAG7G,IAAI,OAAO,SAAS,KAAK,OAAO,SAAS,KACrC,OAAO,EAAE,OAAO,oCAAoC;EAExD,IAAI,CAAC,mBAAmB,MAAM,GAC1B,OAAO,EAAE,OAAO,gBAAgB;EAGpC,OAAO,EAAE,OAAO,OAAO;CAC3B,GAAG,EAAE,QAAQ,KAAK,CAAC;AACvB;AAEA,eAAe,YAAY,KAAU,QAAmC;CAIpE,MAAM,OAAO,MAAM,UAAU,KAAK,YAH4B,WAAW,eACrE;EAAC;EAAY;EAAS;CAAQ,IAC9B,CAAC,YAAY,OAAO,GAC+B,UAAU;CACjE,IAAI,SAAS,UACT,OAAO,EAAE,MAAM,iBAAiB;CAKpC,KAFiB,WAAW,aAAa,WAAW,WAChD,MAAM,SAAS,KAAK,sCAAsC,IAAI,GAE9D,OAAO;EACH;EACA,SAAS;EACT,MAAM;EACN,MAAM,aAAa;EACnB,UAAU;EACV,UAAU,MAAM,UAAU,KAAK,mBAAmB;EAClD,UAAU;CACd;CAQJ,OAAO;EACH;EACA,SAAS;EACT,MAAM,MAAM,QAAQ,KAPH,WAAW,eAC5B,kBACA,0FAKuC,WAAW,eAAe,cAAc,KAAA,CAAS;EACxF,MAAM,MAAM,QAAQ,KAAK,iBAAiB,aAAa,KAAK;EAC5D,UAAU,MAAM,QAAQ,KAAK,qBAAqB,QAAQ;EAC1D,UAAU,MAAM,UAAU,KAAK,mBAAmB;EAClD,UAAU,MAAM,QAAQ,KAAK,iBAAiB,QAAQ;CAC1D;AACJ;AAEA,eAAsB,eAAe,KAA0D;CAC3F,MAAM,SAAS,MAAM,UAAU,KAAK,qBAAqB,SAAS,SAAS;CAC3E,MAAM,YAAY,MAAM,aAAa,KAAK,MAAM;CAChD,MAAM,iBAAiB,WAAW,UAAU,UAAU,WAAW,QAAQ,KACrE,MAAM,SAAS,KAAK,sHAAsH,KAAK;CACnJ,MAAM,KAAK,MAAM,YAAY,KAAK,MAAM;CAExC,MAAM,sBAAsB,MAAM,SAAS,KAAK,6BAA6B,KAAK;CAClF,MAAM,0BAA0B,MAAM,SAAS,KAAK,6BAA6B,KAAK;CACtF,MAAM,OAAO,uBAAuB,0BAChC,EAAE,KAAK,MAAM,OAAO,KAAK,uDAAuD,CAAC,SAAS,QAAQ,CAAC,EAAE,IACrG;CAEJ,MAAM,2BAA2B,uBAAuB,MAAM,SAAS,KAAK,2CAA2C,IAAI;CAE3H,MAAM,gBAAgB,MAAM,UAAU,KAAK,gBAAgB;CAE3D,MAAM,aAAa,WAAW,aAAa,WAAW;CACtD,MAAM,cAAc,cAAc,MAAM,SAAS,KAAK,oDAAoD,KAAK;CAC/G,MAAM,eAAe,cAAc,MAAM,SAAS,KAAK,2CAA2C,KAAK;CAEvG,MAAM,QAAkB,CAAC;CACzB,IAAI,QAA0B;CAC9B,IAAI,eAAe,cAAc;EAC7B,QAAQ,EAAE,KAAK,kBAAkB;EACjC,MAAM,KAAK,UAAU;CACzB,OAAO,IAAI,MAAM,SAAS,KAAK,cAAc,KAAK,GAC9C,QAAQ,EACJ,KAAK,aACD,oBACA,MAAM,OACF,KACA,WAAW,eAAe,cAAc,6CACxC,CAAC,UAAU,SAAS,GACpB,WAAW,eAAe,2BAA2B,KAAA,CACzD,EACR;CAGJ,OAAO;EACH,SAAS;GACL;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;EACJ;EACA;CACJ;AACJ;;;AC7PA,SAAgB,iBAAiB,SAAiB,UAA0B;CACxE,OAAO,iFAAiF,SAAS,sBAAsB,QAAQ;AACnI;AAEA,SAAgB,aAAa,SAAkB,SAA2B;CACtE,IAAI,QAAQ,GAAG,SAAS,kBACpB,MAAM,IAAI,MAAM,sGAAsG;CAG1H,IAAI,QAAQ,eAAe,QAAQ,cAC/B,MAAM,IAAI,MAAM,qGAAqG;CAGzH,MAAM,WAAW,iBAAiB,QAAQ,SAAS;CAGnD,MAAM,QAAQ;EACV,cAAc,QAAQ;EACtB;EACA,eAAe,gBAAgB,QAAQ;EACvC,WAAW,QAAQ,GAAG;EACtB,WAAW,QAAQ,GAAG;EACtB,WAAW,QAAQ,GAAG;EACtB,eAAe,QAAQ,GAAG;EAC1B,eAAe,QAAQ,GAAG;EAC1B,eAAe,QAAQ,GAAG;CAC9B;CAEA,IAAI,QAAQ,OACR,MAAM,KAAK,SAAS,QAAQ,MAAM,KAAK;CAG3C,IAAI,QAAQ,MACR,MAAM,KAAK,QAAQ,QAAQ,KAAK,KAAK;CAGzC,IAAI,QAAQ,qBACR,MAAM,KAAK,2BAA2B;CAG1C,IAAI,QAAQ,yBACR,MAAM,KAAK,gCAAgC;CAG/C,IAAI,QAAQ,0BACR,MAAM,KAAK,iCAAiC;CAGhD,MAAM,KACF,uBAAuB,QAAQ,iBAC/B,oGACA,6CACA,mFACA,4CACJ;CAEA,OAAO,EACH,cAAc;EACV;EACA,KAAK,iBAAiB,SAAS,SAAS,QAAQ;EAChD;EACA;EACA;EACA;EACA,GAAG;EACH;CACJ,CAAC,CAAC,KAAK,IAAI,EACf;AACJ;;;AClEA,MAAa,oBAAoB;AAEjC,MAAa,gBAA0B,CACnC,gDACA,kDACJ;AAEA,SAAgB,WAAW,SAAkB,UAA4B;CACrE,MAAM,EAAE,OAAO;CACf,IAAI,GAAG,SAAS,kBACZ,MAAM,IAAI,MAAM,uFAAuF;CAG3G,MAAM,MAAM,IAAI,IAAI,QAAQ,SAAS;CACrC,MAAM,MAAM,IAAI,aAAa;CAC7B,MAAM,QAAkB;EACpB,iDAAiD,cAAc,KAAK,MAAM;EAC1E;EAGA,GAAI,QAAQ,eAAe,QAAQ,eAAe,CAAC,iBAAiB,IAAI,CAAC;EACzE;EACA,gBAAgB,UAAU,QAAQ,SAAS;EAC3C,GAAI,OAAO,CAAC,QAAQ,iBAChB,CAAC,gCAAgC,IAAI,SAAS,wEAAwE,IACtH,CAAC;EACL;EACA;EACA,iBAAiB,UAAU,IAAI,QAAQ;EACvC,YAAY;EACZ,GAAI,OAAO,QAAQ,iBAAiB,CAAC,uBAAuB,IAAI,CAAC;CACrE;CAEA,IAAI,QAAQ,uBAAuB,QAAQ,2BAA2B,QAAQ,0BAA0B;EACpG,MAAM,KAAK,aAAa;EACxB,IAAI,QAAQ,qBACR,MAAM,KAAK,wBAAwB;EAEvC,IAAI,QAAQ,yBACR,MAAM,KAAK,4BAA4B;EAE3C,IAAI,QAAQ,0BACR,MAAM,KAAK,6BAA6B;CAEhD;CAEA,IAAI,QAAQ,aACR,MAAM,KAAK,gBAAgB,mBAAmB;CAGlD,IAAI,QAAQ,cACR,MAAM,KAAK,uBAAuB;CAGtC,MAAM,KACF,qFACA,uBACA,mCACA,qFACA,wBACA,eACA,+BACJ;CAEA,IAAI,QAAQ,aACR,MAAM,KAAK,IAAI,WAAW,iBAAiB;CAG/C,MAAM,KACF,IACA,aACA,WAAW,GAAG,QACd,EACJ;CAGA,KAAK,MAAM,UAAU,CAAC,cAAc,OAAO,GAAY;EACnD,MAAM,UAAU,GAAG,WAAW,GAAG,UAAU,WAAW,eAAe,aAAa;EAClF,MAAM,KAAK,GAAG,OAAO,IAAI,cAAc,SAAS;EAChD,IAAI,SACA,MAAM,KAAK,WAAW,iBAAiB,UAAU,GAAG,QAAQ,GAAG;CAEvE;CAEA,IAAI,CAAC,GAAG,SAEJ,MAAM,KACF,qBACA,WAAW,UAAU,GAAG,IAAI,KAC5B,WAAW,UAAU,OAAO,GAAG,IAAI,CAAC,KACpC,WAAW,UAAU,GAAG,QAAQ,KAChC,eAAe,UAAU,GAAG,QAAQ,KACpC,eAAe,UAAU,GAAG,QAAQ,GACxC;CAGJ,IAAI,QAAQ,OACR,MAAM,KACF,IACA,WACA,mBACA,wGACJ;CAGJ,MAAM,KACF,IACA,SACA,oBAAoB,UAAU,QAAQ,aAAa,KACnD,qFACA,6CACJ;CAEA,IAAI,QAAQ,MACR,MAAM,KAAK,IAAI,SAAS,uBAAuB,UAAU,QAAQ,KAAK,GAAG,GAAG;CAGhF,OAAO,EAAE,eAAe,GAAG,MAAM,KAAK,IAAI,EAAE,IAAI;AACpD;;;ACvHA,MAAM,iBAAiB;AACvB,MAAM,oBAAoB;AAE1B,SAAgB,gBAAgB,SAAkB,SAA2B;CACzE,IAAI,QAAQ,eAAe,QAAQ,cAC/B,MAAM,IAAI,MAAM,iJAAiJ;CAGrK,OAAO;EACH,gBAAgB,kBAAkB,OAAO;EACzC,cAAc,aAAa,OAAO;EAClC,QAAQ,UAAU,OAAO;CAC7B;AACJ;AAEA,SAAS,kBAAkB,SAAyB;CAUhD,OAAO,GAAG,KAAK,UAAU;EARrB,MAAM;EACN,SAAS;EACT,MAAM;EACN,SAAS,EAAE,OAAO,eAAe;EAEjC,cAAc,EAAE,QAAQ,QAAQ;CAGJ,GAAG,MAAM,CAAC,EAAE;AAChD;AAEA,SAAS,aAAa,SAA0B;CAC5C,MAAM,EAAE,OAAO;CACf,MAAM,QAAQ,CACV,gBACA,cAAc,UAAU,QAAQ,SAAS,GAC7C;CAEA,IAAI,GAAG,SAAS,kBACZ,MAAM,KACF,mFACA,0CAA0C,qBAC1C,IACA,6CACJ;MAEA,MAAM,KACF,mBACA,IAGA,oDACJ;CAGJ,IAAI,QAAQ,OACR,MAAM,KAAK,IAAI,UAAU,UAAU,QAAQ,MAAM,GAAG,GAAG;CAG3D,IAAI,QAAQ,MACR,MAAM,KAAK,IAAI,yCAAyC,YAAY;CAGxE,MAAM,KACF,IACA,mFACA,qBACA,+BACJ;CAGA,MAAM,WAAW,iBAAiB,QAAQ,SAAS;CACnD,MAAM,KACF,IACA,SACA,GAAI,SAAS,aAAA,MAA2B,CAAC,WAAW,SAAS,UAAU,IAAI,CAAC,GAC5E,4GACA,iBAAiB,gBAAgB,QAAQ,KACzC,GAAI,QAAQ,sBAAsB,CAAC,6BAA6B,IAAI,CAAC,GACrE,GAAI,QAAQ,0BAA0B,CAAC,iCAAiC,IAAI,CAAC,GAC7E,GAAI,QAAQ,2BAA2B,CAAC,kCAAkC,IAAI,CAAC,CACnF;CAEA,OAAO,GAAG,MAAM,KAAK,IAAI,EAAE;AAC/B;AAEA,SAAS,UAAU,SAA0B;CACzC,MAAM,QAAQ,CAAC,8GAA8G;CAE7H,MAAM,EAAE,OAAO;CACf,IAAI,GAAG,SAAS,kBACZ,MAAM,KACF,WAAW,GAAG,QACd,WAAW,SAAS,GAAG,IAAI,KAC3B,WAAW,GAAG,QACd,eAAe,SAAS,GAAG,QAAQ,KACnC,eAAe,SAAS,GAAG,QAAQ,KACnC,eAAe,SAAS,GAAG,QAAQ,GACvC;CAGJ,MAAM,KAAK,uBAAuB,SAAS,QAAQ,aAAa,GAAG;CAEnE,IAAI,QAAQ,MACR,MAAM,KAAK,QAAQ,SAAS,QAAQ,KAAK,GAAG,GAAG;CAGnD,MAAM,KACF,mFACA,2BACJ;CAEA,OAAO,GAAG,MAAM,KAAK,IAAI,EAAE;AAC/B;;;ACzGA,MAAMA,aAAW;AAEjB,MAAM,aAAa,gEAAgEA,WAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAuD5F,MAAM,UAAU;CACZ,UAAU;EACN,OAAO;EACP,MAAM;EACN,UAAU;CACd;CACA,OAAO;EACH,OAAO;EACP,MAAM;EACN,UAAU;CACd;AACJ;AAIA,SAAS,IAAI,KAAa,OAAuB;CAC7C,OAAO,KAAK,UAAU,GAAG,IAAI,GAAG,2BAA2B,KAAK,GAAG;AACvE;AAGA,SAAS,IAAI,KAAa,UAA0B;CAChD,OAAO,KAAK,UAAU,GAAG,IAAI,MAAM,SAAS,EAAE;AAClD;AAEA,SAAS,MAAM,KAAa,OAA2B;CACnD,OAAO,CAAC,GAAG,IAAI,IAAI,GAAG,OAAO,MAAM,KAAK,IAAI,GAAG,CAAC,CAAC,CAAC,MAAM,IAAI,CAAC;AACjE;AAEA,SAAgB,cAAc,SAAkB,SAA2B;CACvE,MAAM,EAAE,OAAO;CACf,IAAI,GAAG,SAAS,kBACZ,MAAM,IAAI,MAAM,uHAAuH;CAG3I,MAAM,QAAQ,wBAAwB;CACtC,MAAM,UAAU;CAChB,MAAM,SAAS,QAAQ,GAAG;CAC1B,MAAM,WAAW,iBAAiB,QAAQ,SAAS;CAEnD,MAAM,QAAQ;EACV,IAAI,WAAW,GAAG,IAAI;EACtB,IAAI,WAAW,GAAG,UAAU,GAAG,OAAO,GAAG,IAAI;EAC7C,IAAI,WAAW,OAAO,GAAG,UAAU,OAAO,OAAO,GAAG,IAAI,CAAC;EACzD,IAAI,eAAe,GAAG,QAAQ;EAC9B,IAAI,eAAe,aAAa;EAChC,IAAI,eAAe,GAAG,QAAQ;CAClC;CACA,IAAI,QAAQ,OACR,MAAM,KAAK,IAAI,SAAS,oBAAoB,CAAC;CAGjD,MAAM,YAAsB,CAAC;CAC7B,IAAI,GAAG,SAEH,UAAU,KAAK,GAAG,GAAG,KAAK,IAAI,8BAA8B;CAEhE,IAAI,QAAQ,OACR,UAAU,KAAK,UAAU,8BAA8B;CAE3D,MAAM,iBAAiB,UAAU,SAAS,IAAI,MAAM,cAAc,SAAS,IAAI,CAAC;CAEhF,MAAM,YAAY;EACd,IAAI,cAAc,QAAQ,SAAS;EACnC;EACA,IAAI,eAAe,gBAAgB,UAAU,QAAQ,YAAY,CAAC;EAClE,GAAG;EACH,IAAI,uBAAuB,qBAAqB;CACpD;CACA,IAAI,QAAQ,MACR,UAAU,KAAK,IAAI,QAAQ,MAAM,CAAC;CAEtC,IAAI,QAAQ,qBACR,UAAU,KAAK,IAAI,wBAAwB,MAAM,CAAC;CAEtD,IAAI,QAAQ,yBACR,UAAU,KAAK,IAAI,6BAA6B,MAAM,CAAC;CAE3D,IAAI,QAAQ,0BACR,UAAU,KAAK,IAAI,8BAA8B,MAAM,CAAC;CAE5D,UAAU,KACN,mGACA,mDACA,kFACA,kDACJ;CAEA,IAAI,QAAQ,aACR,UAAU,KACN,wCACA,IAAI,kBAAkB,OAAO,GAC7B,+EACA,IAAI,qBAAqB,OAAO,CACpC;CAGJ,MAAM,WAAW,MAAM,UAAU;EAC7B;EACA;EAEA,GAAI,QAAQ,eAAe,CAAC,IAAI,MAAM,SAAS,CAAC,MAAM,SAAS,SAAS,OAAO,CAAC;EAChF;EACA;EACA;EACA;EACA,GAAG;EACH,GAAG,MAAM,eAAe,SAAS;EACjC,YAAY,QAAQ,eAAe,eAAe;CACtD,CAAC;CAED,IAAI,GAAG,SAAS;EACZ,MAAM,cAAc,GAAG,SAAS,aAC5B;GACI,IAAI,iBAAiB,GAAG,QAAQ;GAChC,IAAI,qBAAqB,aAAa;GACtC,IAAI,eAAe,GAAG,QAAQ;EAClC,IACA;GACI,IAAI,cAAc,GAAG,QAAQ;GAC7B,IAAI,kBAAkB,aAAa;GACnC,IAAI,uBAAuB,aAAa;GACxC,IAAI,kBAAkB,GAAG,QAAQ;EACrC;EAIJ,MAAM,QAAQ,GAAG,SAAS,aACtB,wDAAwD,UAAU,2BAA2B,GAAG,QAAQ,CAAC,EAAE,UAAU,UAAU,2BAA2B,GAAG,QAAQ,CAAC,EAAE,KAExK;EAEJ,SAAS,KAAK,IAAI,GAAG,MAAM,GAAG,MAAM;GAChC,UAAU,OAAO;GACjB;GACA,GAAG,MAAM,WAAW,CAAC,KAAK,GAAG,KAAK,QAAQ,OAAO,UAAU,CAAC;GAC5D,GAAG,MAAM,eAAe,WAAW;GACnC,GAAG,MAAM,eAAe;IAAC;IAAO;IAAgB;IAAe;GAAa,CAAC;EACjF,CAAC,CAAC;CACN;CAEA,IAAI,QAAQ,OACR,SAAS,KAAK,IAAI,GAAG,MAAM,SAAS,CAAC,kBAAkB,OAAO,CAAC,CAAC;CAGpE,IAAI,QAAQ,aACR,SAAS,KACL,IACA,wDACA,GAAG,MAAM,iBAAiB;EACtB;EACA;EACA,GAAG;EAEH,GAAG,MAAM,eAAe,CAAC,IAAI,cAAc,QAAQ,SAAS,GAAG,GAAG,KAAK,CAAC;EACxE;CACJ,CAAC,CACL;CAGJ,IAAI,QAAQ,cACR,SAAS,KACL,IACA,qEACA,4EACA,2EACA,0BAA0BA,WAAS,oBACnC,GAAG,MAAM,kBAAkB;EACvB;EACA;EACA,GAAG,MAAM,eAAe;GACpB,IAAI,cAAc,QAAQ,SAAS;GACnC;GACA,IAAI,gBAAgB,oBAAoB;EAC5C,CAAC;EACD;EACA;EACA,GAAG,MAAM,eAAe,CACpB,oGACA,eACJ,CAAC;EACD;CACJ,CAAC,GACD,IACA,+FACA,GAAG,MAAM,SAAS;EACd;EACA;EACA,GAAG,MAAM,SAAS,CAAC,MAAM,SAAS,SAAS,KAAK,CAAC;EACjD,GAAG,MAAM,WAAW,CAAC,kDAAkD,CAAC;EACxE,GAAG,MAAM,cAAc;GAAC;GAAW;GAAgC;GAAmB;EAA8B,CAAC;CACzH,CAAC,CACL;CAGJ,MAAM,UAAU,CACZ,2EAA2EA,WAAS,kBACpF,EACJ;CACA,IAAI,GAAG,SACH,QAAQ,KAAK,GAAG,MAAM,WAAW,CAAC,GAAG,GAAG,KAAK,OAAO,CAAC,GAAG,EAAE;CAE9D,QAAQ,KAAK,GAAG,MAAM,YAAY,QAAQ,CAAC;CAE3C,MAAM,SAAS;EACX;EACA,eAAe,gBAAgB,GAAG,QAAQ;EAC1C,uBAAuB,gBAAgB,QAAQ,aAAa;CAChE;CACA,IAAI,QAAQ,MACR,OAAO,KAAK,QAAQ,gBAAgB,QAAQ,KAAK,GAAG,GAAG;CAE3D,OAAO,KACH,mFACA,2BACJ;CAEA,OAAO;EACH,sBAAsB,GAAG,QAAQ,KAAK,IAAI,EAAE;EAC5C,QAAQ,GAAG,OAAO,KAAK,IAAI,EAAE;EAC7B,GAAI,QAAQ,eAAe,EAAE,cAAc,WAAW,IAAI,CAAC;CAC/D;AACJ;;;ACzRA,MAAa,YAAsC;CAC/C,QAAQ;CACR,SAAS;CACT,MAAM;CACN,cAAc;AAClB;;;ACRA,MAAa,UAAkB,KAAK,MAAM,aAAa,IAAI,IAAI,mBAAmB,YAAY,GAAG,GAAG,MAAM,CAAC,CAAC,CAAC;;;ACe7G,MAAM,WAAW;AAEjB,MAAM,QAAQ;;;;;WAKH,QAAQ,KAAK,IAAI,EAAE;;;;;AAM9B,SAAS,UAAU,SAA4B;CAe3C,OAAO;EAbH,QAAQ,CACJ,iBAAiB,SAAS,iBAAiB,QAAQ,SAAS,CAAC,CAAC,QAAQ,GACtE,wFACJ;EACA,SAAS;GACL,GAAI,QAAQ,cAAc,CAAC,8CAA8C,IAAI,CAAC;GAC9E;GACA,GAAI,QAAQ,eAAe,CAAC,+EAA+E,IAAI,CAAC;EACpH;EACA,MAAM,CAAC,GAAG,aAAa;EACvB,cAAc;GAAC;GAAe;GAA8B;EAAW;CAGhE,EAAE,QAAQ;AACzB;AAEA,eAAe,OAAsB;CACjC,MAAM,EAAE,WAAW,UAAU;EACzB,SAAS;GACL,OAAO;IAAE,MAAM;IAAW,SAAS;GAAM;GACzC,MAAM;IAAE,MAAM;IAAW,SAAS;GAAM;EAC5C;EACA,kBAAkB;CACtB,CAAC;CACD,IAAI,OAAO,MAAM;EACb,QAAQ,IAAI,KAAK;EACjB;CACJ;CAEA,QAAQ,IAAI,iBAAiB,SAAS;CACtC,QAAQ,IAAI,iDAAiD,QAAQ,IAAI,EAAE,GAAG;CAE9E,MAAM,EAAE,KAAK,UAAU,kBAAkB;CACzC,IAAI;CACJ,IAAI;EACA,YAAY,MAAM,eAAe,GAAG;CACxC,UAAU;EACN,MAAM;CACV;CACA,MAAM,EAAE,SAAS,UAAU;CAE3B,MAAM,WAAW,UAAU,QAAQ,OAAO,CAAC,SAAS,OAAO;CAC3D,MAAM,QAAQ,OAAO,KAAK,QAAQ;CAClC,MAAM,cAAc,MAAM,QAAQ,SAAS;EACvC,IAAI;GACA,OAAO,GAAG,SAAS,KAAK,KAAK,QAAQ,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,YAAY;EACnE,QAAQ;GACJ,OAAO;EACX;CACJ,CAAC;CACD,IAAI,YAAY,SAAS,GAAG;EACxB,QAAQ,MAAM,kCAAkC,YAAY,KAAK,IAAI,EAAE,EAAE;EACzE,QAAQ,KAAK,CAAC;CAClB;CAEA,MAAM,WAAW,MAAM,QAAQ,SAAS,GAAG,WAAW,KAAK,KAAK,QAAQ,IAAI,GAAG,IAAI,CAAC,CAAC;CACrF,IAAI,SAAS,SAAS,KAAK,CAAC,OAAO,OAAO;EACtC,QAAQ,MAAM,0BAA0B,SAAS,KAAK,IAAI,EAAE,uBAAuB;EACnF,QAAQ,KAAK,CAAC;CAClB;CAEA,KAAK,MAAM,QAAQ,OAAO;EACtB,MAAM,SAAS,KAAK,KAAK,QAAQ,IAAI,GAAG,IAAI;EAC5C,MAAM,SAAS,aAAa,IAAI,IAAI;EACpC,GAAG,cAAc,QAAQ,SAAS,OAAO,SAAS;GAAE,UAAU;GAAQ,MAAM;EAAM,IAAI,MAAM;EAC5F,IAAI,QAEA,GAAG,UAAU,QAAQ,GAAK;CAElC;CAEA,KAAK,MAAM,QAAQ,OACf,QAAQ,IAAI,IAAI;CAEpB,QAAQ,IAAI,YAAY,MAAM,KAAK,IAAI,GAAG;CAC1C,QAAQ,IAAI,aAAa;CACzB,KAAK,MAAM,QAAQ,UAAU,OAAO,GAChC,QAAQ,IAAI,KAAK,MAAM;CAE3B,QAAQ,IAAI,SAAS,UAAU;AACnC;AAEA,KAAK,CAAC,CAAC,OAAO,MAAM;CAChB,QAAQ,MAAM,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC,CAAC;CACxD,QAAQ,KAAK,CAAC;AAClB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["DOCS_URL"],"sources":["../src/ask.ts","../src/constants.ts","../src/deployment.ts","../src/utils.ts","../src/prompts.ts","../src/targets/docker.ts","../src/targets/helm.ts","../src/targets/bare-metal.ts","../src/targets/compose.ts","../src/targets/index.ts","../src/version.ts","../src/index.ts"],"sourcesContent":["/*\n * Copyright (c) 2026.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nimport process from 'node:process';\nimport { createInterface } from 'node:readline/promises';\nimport { Writable } from 'node:stream';\nimport type { ReadlineAskContext, ReadlineAskResult } from './types.ts';\n\ntype PendingLine = {\n resolve: (line: string) => void,\n reject: (error: Error) => void\n};\n\nconst CLOSED_MESSAGE = 'Input ended before every question was answered.';\n\n/**\n * The readline seam. The streams are arguments rather than `process.stdin` / `process.stdout` reached for directly,\n * so the echo behaviour below is testable without a terminal: a spec passes a pair of in-memory streams claiming to\n * be one.\n */\nexport function createReadlineAsk(ctx: ReadlineAskContext = {}): ReadlineAskResult {\n const input = ctx.input || process.stdin;\n const target = ctx.output || process.stdout;\n\n // On a terminal readline echoes what is typed through the output stream itself, so muting that stream while a\n // secret's line is read is what keeps the password off the screen. Piped input takes none of this path.\n const terminal = Boolean(input.isTTY && target.isTTY);\n let muted = false;\n const output = new Writable({\n write(chunk, _encoding, callback) {\n if (!muted) {\n target.write(chunk);\n }\n callback();\n },\n });\n const rl = createInterface({\n input,\n output,\n terminal,\n });\n\n // readline emits every line of one stdin chunk synchronously, while the next question is only registered on a\n // later microtask, so `rl.question` drops all but the first answer of a piped script. The lines are buffered\n // instead, and a close with a question still open is an error rather than the silent exit 0 node answers with.\n const lines: string[] = [];\n let pending: PendingLine | undefined;\n let closed = false;\n\n rl.on('line', (line) => {\n if (pending) {\n const { resolve } = pending;\n pending = undefined;\n resolve(line);\n return;\n }\n\n lines.push(line);\n });\n\n rl.on('close', () => {\n closed = true;\n if (pending) {\n const { reject } = pending;\n pending = undefined;\n reject(new Error(CLOSED_MESSAGE));\n }\n });\n\n // Ctrl+C on a TTY reaches readline rather than the process; without this it closes the interface and reads as EOF.\n rl.on('SIGINT', () => process.exit(130));\n\n function readLine(): Promise<{ line: string, buffered: boolean }> {\n const buffered = lines.shift();\n if (buffered !== undefined) {\n return Promise.resolve({ line: buffered, buffered: true });\n }\n\n if (closed) {\n return Promise.reject(new Error(CLOSED_MESSAGE));\n }\n\n return new Promise((resolve, reject) => {\n pending = { resolve: (line) => resolve({ line, buffered: false }), reject };\n });\n }\n\n return {\n // The answer is handed over untrimmed: a secret keeps its whitespace, and each parser trims what it should.\n ask: async (question, fallback, options) => {\n rl.setPrompt(fallback === undefined ? `${question}: ` : `${question} [${fallback}]: `);\n rl.prompt();\n const secret = terminal && Boolean(options?.secret);\n muted = secret;\n try {\n const { line, buffered } = await readLine();\n if (buffered && terminal && !secret) {\n // a type-ahead answer never echoed after this prompt, so the next prompt would overwrite the line\n target.write(`${line}\\n`);\n }\n\n return fallback !== undefined && line.trim() === '' ? fallback : line;\n } finally {\n if (secret) {\n // the Enter was swallowed with the echo\n muted = false;\n target.write('\\n');\n }\n }\n },\n close: () => rl.close(),\n };\n}\n","/*\n * Copyright (c) 2026.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nimport type { Target } from './types.ts';\n\n/**\n * The emitted files that hold a password, written owner-only by `src/index.ts`. The compose file, `nginx.conf`,\n * `package.json` and `authup.yml` carry none: every secret rides an `.env`, or `values.yaml` for helm.\n * `test/unit/secret-files.spec.ts` holds the renderers to this list, so a new secret-bearing file cannot slip past it.\n */\nexport const SECRET_FILES = new Set(['.env', 'authup.env', 'values.yaml']);\n\nexport const TARGETS: readonly Target[] = ['docker', 'compose', 'helm', 'bare-metal'] as const;\n","/*\n * Copyright (c) 2026.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nimport type { Exposure } from './types.ts';\n\n/** The port the process listens on inside a container, and the default everywhere else. */\nexport const LISTEN_PORT = 3000;\n\n// A public url with an explicit port is dialed directly, so that port is the one to publish or to listen on. A\n// default port, or https, means a reverse proxy in front owns the port a browser dials and reaches the listener\n// on its own, so the default listen port stays. `http://localhost` with no port is read as a proxy too; the\n// operator who really serves port 80 directly edits the mapping.\nexport function describeExposure(publicUrl: string): Exposure {\n const url = new URL(publicUrl);\n const behindProxy = url.protocol === 'https:' || url.port === '';\n\n return {\n behindProxy,\n hostPort: behindProxy ? LISTEN_PORT : Number(url.port),\n };\n}\n\n// The value core.trustProxy / TRUST_PROXY takes: the number of proxy hops the deployment itself knows about\n// (the operator's proxy in front, plus the one the compose console split emits), or false when nothing is in front,\n// since authup's default trusts every hop and lets any direct client spoof its address.\nexport function trustProxyValue(exposure: Exposure, ownProxy = false): string {\n const hops = (exposure.behindProxy ? 1 : 0) + (ownProxy ? 1 : 0);\n\n return hops > 0 ? String(hops) : 'false';\n}\n","/*\n * Copyright (c) 2026.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\n// A JSON string is a valid YAML double-quoted scalar, which also defuses the Norway problem and a leading `*`.\nexport function quoteYaml(value: string): string {\n return JSON.stringify(value);\n}\n\nconst PLAIN = /^[A-Za-z0-9_@:/.\\-+=%?&,]*$/;\n\n// Whether a value survives the .env round trip at all. dotenv strips the surrounding quotes and, inside DOUBLE quotes,\n// turns a literal \\n or \\r into the control character itself, with no escape that survives (it never unescapes a\n// doubled backslash). A value carrying a single quote, which rules the single-quoted form out, together with one of\n// those two sequences therefore has no representation, and `askSecret` refuses it rather than corrupting a password.\nexport function isEnvRepresentable(value: string): boolean {\n return !value.includes('\\'') || !/\\\\[nr]/.test(value);\n}\n\n// For the .env dotenv reads (`authup start` loads it from cwd): a value is single-quoted when it carries no single\n// quote and written raw inside double quotes otherwise, which round-trips everything `isEnvRepresentable` admits.\nexport function quoteEnv(value: string): string {\n if (PLAIN.test(value)) {\n return value;\n }\n\n if (!value.includes('\\'')) {\n return `'${value}'`;\n }\n\n return `\"${value}\"`;\n}\n\n// For the .env compose-go reads: double quotes take backslash escapes and `$` interpolates unless doubled, while a\n// single-quoted `\\'` there is an escaped quote rather than a literal, so one double-quoted branch covers every value.\nexport function quoteComposeEnv(value: string): string {\n if (PLAIN.test(value)) {\n return value;\n }\n\n return `\"${escapeComposeInterpolation(value.replace(/[\\\\\"]/g, '\\\\$&'))}\"`;\n}\n\n// compose interpolates `$name` and `${name}` in every yaml value and .env value; `$$` is its literal dollar.\nexport function escapeComposeInterpolation(value: string): string {\n return value.replace(/\\$/g, '$$$$');\n}\n\nexport function indent(text: string, spaces: number): string {\n const prefix = ' '.repeat(spaces);\n\n return text\n .split('\\n')\n .map((line) => (line.length > 0 ? prefix + line : line))\n .join('\\n');\n}\n","/*\n * Copyright (c) 2026.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nimport { TARGETS } from './constants.ts';\nimport { isEnvRepresentable } from './utils.ts';\nimport type {\n Answers,\n Ask,\n AskOptions,\n Database,\n Target,\n} from './types.ts';\n\ntype Parsed<T> = { value: T } | { error: string };\n\nconst ENGINE_PORTS = { postgres: 5432, mysql: 3306 } as const;\nconst BUNDLED_REDIS_URL = 'redis://redis:6379';\nconst UNREPRESENTABLE = 'A single quote together with a \\\\n or \\\\r sequence has no .env representation. Choose another value.';\nconst REDIS_NOTE = 'Redis is required for a split deployment: the console sign-in and the token blocklist ride the cache.';\n\n// A refused answer re-asks with the reason on the line above, so the flow stays pure over `ask`.\nasync function askUntil<T>(\n ask: Ask,\n question: string,\n fallback: string | undefined,\n parse: (answer: string) => Parsed<T>,\n options?: AskOptions,\n): Promise<T> {\n let prefix = '';\n\n for (;;) {\n const parsed = parse(await ask(prefix + question, fallback, options));\n if ('value' in parsed) {\n return parsed.value;\n }\n\n prefix = `${parsed.error}\\n`;\n }\n}\n\nfunction askChoice<T extends string>(ask: Ask, question: string, options: readonly T[], fallback: T): Promise<T> {\n const listed = options.map((option, index) => `${index + 1}: ${option}`).join(', ');\n\n return askUntil<T>(ask, `${question} (${listed})`, fallback, (answer) => {\n const normalized = answer.trim().toLowerCase();\n if ((options as readonly string[]).includes(normalized)) {\n return { value: normalized as T };\n }\n\n const numbered = options[Number(normalized) - 1];\n if (numbered !== undefined) {\n return { value: numbered };\n }\n\n return { error: `Pick one of ${options.join(', ')}, by name or number.` };\n });\n}\n\nfunction askYesNo(ask: Ask, question: string, fallback: boolean): Promise<boolean> {\n return askUntil<boolean>(ask, `${question} (y/n)`, fallback ? 'y' : 'n', (answer) => {\n const normalized = answer.trim().toLowerCase();\n if (normalized === 'y' || normalized === 'yes') {\n return { value: true };\n }\n if (normalized === 'n' || normalized === 'no') {\n return { value: false };\n }\n\n return { error: 'Answer y or n.' };\n });\n}\n\nfunction askUrl(ask: Ask, question: string, protocols: readonly string[] = ['http:', 'https:'], fallback?: string): Promise<string> {\n return askUntil<string>(ask, question, fallback, (answer) => {\n const trimmed = answer.trim();\n try {\n // a non-special scheme parses `smtp:mail` with an empty host, which would land in the file as written\n const url = new URL(trimmed);\n if (protocols.includes(url.protocol) && url.hostname !== '') {\n return { value: trimmed };\n }\n } catch {\n // falls through to the refusal\n }\n\n return { error: `Enter an absolute URL using ${protocols.map((protocol) => `${protocol}//`).join(' or ')}.` };\n });\n}\n\n// A free-text answer that must not be blank; trimmed, unlike a secret.\nfunction askText(ask: Ask, question: string, fallback?: string): Promise<string> {\n return askUntil<string>(ask, question, fallback, (answer) => {\n const trimmed = answer.trim();\n if (trimmed === '') {\n return { error: 'A value is required.' };\n }\n\n // a host, user or database name reaches the same .env the secrets do\n return isEnvRepresentable(trimmed) ? { value: trimmed } : { error: UNREPRESENTABLE };\n });\n}\n\n// The issuer: no credentials, query or fragment (OIDC Core 3.1.2.1), and for the helm target no path either, since\n// the chart publishes authup at the origin root and refuses a console split under a sub-path.\nfunction askPublicUrl(ask: Ask, target: Target): Promise<string> {\n return askUntil<string>(\n ask,\n 'Public URL: the address a browser reaches authup at. It is the OIDC issuer, and every console url derives from it',\n undefined,\n (answer) => {\n const trimmed = answer.trim();\n let url: URL;\n try {\n url = new URL(trimmed);\n } catch {\n return { error: 'Enter an absolute URL using http:// or https://.' };\n }\n\n if (url.protocol !== 'http:' && url.protocol !== 'https:') {\n return { error: 'Enter an absolute URL using http:// or https://.' };\n }\n if (url.username || url.password || url.search || url.hash) {\n return { error: 'The public URL is the OIDC issuer: no credentials, query or fragment.' };\n }\n if (target === 'helm' && url.pathname !== '/') {\n return { error: 'The helm chart publishes authup at the origin root: enter the URL without a path.' };\n }\n\n return { value: trimmed };\n },\n );\n}\n\nfunction askPort(ask: Ask, question: string, fallback: number): Promise<number> {\n return askUntil<number>(ask, question, String(fallback), (answer) => {\n const port = Number(answer.trim());\n if (Number.isSafeInteger(port) && port >= 1 && port <= 65535) {\n return { value: port };\n }\n\n return { error: 'Enter a port between 1 and 65535.' };\n });\n}\n\nfunction askSecret(ask: Ask, question: string): Promise<string> {\n return askUntil<string>(ask, question, undefined, (answer) => {\n if (answer.trim() === '') {\n return { error: 'A value is required.' };\n }\n if (answer === 'start123') {\n return { error: 'start123 is the provisioning default the wizard refuses to emit. Choose another value.' };\n }\n // the bounds the config registry declares for userAdminPassword\n if (answer.length < 3 || answer.length > 256) {\n return { error: 'Use between 3 and 256 characters.' };\n }\n if (!isEnvRepresentable(answer)) {\n return { error: UNREPRESENTABLE };\n }\n\n return { value: answer };\n }, { secret: true });\n}\n\nasync function askDatabase(ask: Ask, target: Target): Promise<Database> {\n const options: readonly ('postgres' | 'mysql' | 'sqlite')[] = target === 'bare-metal' ?\n ['postgres', 'mysql', 'sqlite'] :\n ['postgres', 'mysql'];\n const type = await askChoice(ask, 'Database', options, 'postgres');\n if (type === 'sqlite') {\n return { type: 'better-sqlite3' };\n }\n\n const bundled = (target === 'compose' || target === 'helm') &&\n await askYesNo(ask, 'Run the database alongside authup?', true);\n if (bundled) {\n return {\n type,\n bundled: true,\n host: type,\n port: ENGINE_PORTS[type],\n username: 'authup',\n password: await askSecret(ask, 'Database password'),\n database: 'authup',\n };\n }\n\n // 127.0.0.1 is the operator's machine on bare metal and the container itself everywhere else\n const hostQuestion = target === 'bare-metal' ?\n 'Database host' :\n 'Database host (a name the container resolves; 127.0.0.1 would be the container itself)';\n\n return {\n type,\n bundled: false,\n host: await askText(ask, hostQuestion, target === 'bare-metal' ? '127.0.0.1' : undefined),\n port: await askPort(ask, 'Database port', ENGINE_PORTS[type]),\n username: await askText(ask, 'Database username', 'authup'),\n password: await askSecret(ask, 'Database password'),\n database: await askText(ask, 'Database name', 'authup'),\n };\n}\n\nexport async function collectAnswers(ask: Ask): Promise<{ answers: Answers, notes: string[] }> {\n const target = await askChoice(ask, 'Deployment target', TARGETS, 'compose');\n const publicUrl = await askPublicUrl(ask, target);\n const tlsCertManager = target === 'helm' && publicUrl.startsWith('https:') &&\n await askYesNo(ask, 'Request the TLS certificate from cert-manager? The alternative is a <hostname>-tls secret in the release namespace', false);\n const db = await askDatabase(ask, target);\n\n const registrationEnabled = await askYesNo(ask, 'Enable user registration?', false);\n const passwordRecoveryEnabled = await askYesNo(ask, 'Enable password recovery?', false);\n const smtp = registrationEnabled || passwordRecoveryEnabled ?\n { url: await askUrl(ask, 'SMTP connection URL (smtp(s)://user:pass@host:port)', ['smtp:', 'smtps:']) } :\n false;\n\n const emailVerificationEnabled = registrationEnabled && await askYesNo(ask, 'Verify email addresses on registration?', true);\n\n const adminPassword = await askSecret(ask, 'Admin password');\n\n const splittable = target === 'compose' || target === 'helm';\n const workerSplit = splittable && await askYesNo(ask, 'Run the background worker as a separate service?', false);\n const consoleSplit = splittable && await askYesNo(ask, 'Run the consoles as a separate service?', false);\n\n const notes: string[] = [];\n let redis: Answers['redis'] = false;\n if (workerSplit || consoleSplit) {\n redis = { url: BUNDLED_REDIS_URL };\n notes.push(REDIS_NOTE);\n } else if (await askYesNo(ask, 'Use redis?', false)) {\n redis = {\n url: splittable ?\n BUNDLED_REDIS_URL :\n await askUrl(\n ask,\n target === 'bare-metal' ? 'Redis URL' : 'Redis URL (a host the container resolves)',\n ['redis:', 'rediss:'],\n target === 'bare-metal' ? 'redis://127.0.0.1:6379' : undefined,\n ),\n };\n }\n\n return {\n answers: {\n target,\n publicUrl,\n db,\n redis,\n smtp,\n registrationEnabled,\n passwordRecoveryEnabled,\n emailVerificationEnabled,\n adminPassword,\n workerSplit,\n consoleSplit,\n tlsCertManager,\n },\n notes,\n };\n}\n","/*\n * Copyright (c) 2026.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nimport { describeExposure, trustProxyValue } from '../deployment.ts';\nimport type { Answers, Rendered } from '../types.ts';\n\nexport function dockerRunCommand(version: string, hostPort: number): string {\n return `docker run -d --name authup --restart unless-stopped --env-file authup.env -p ${hostPort}:3000 authup/authup:${version} start`;\n}\n\nexport function renderDocker(answers: Answers, version: string): Rendered {\n if (answers.db.type === 'better-sqlite3') {\n throw new Error('The docker image runs in production mode and refuses sqlite: configure a postgres or mysql database.');\n }\n\n if (answers.workerSplit || answers.consoleSplit) {\n throw new Error('The docker run target is one container: a worker or console split needs the compose or helm target.');\n }\n\n const exposure = describeExposure(answers.publicUrl);\n\n // `docker run --env-file` reads KEY=value verbatim and does no quote handling, so values are written raw rather than through quoteEnv.\n const lines = [\n `PUBLIC_URL=${answers.publicUrl}`,\n '# The proxies in front whose X-Forwarded-For is trusted, or false; authup trusts every hop by default.',\n `TRUST_PROXY=${trustProxyValue(exposure)}`,\n `DB_TYPE=${answers.db.type}`,\n `DB_HOST=${answers.db.host}`,\n `DB_PORT=${answers.db.port}`,\n `DB_USERNAME=${answers.db.username}`,\n `DB_PASSWORD=${answers.db.password}`,\n `DB_DATABASE=${answers.db.database}`,\n ];\n\n if (answers.redis) {\n lines.push(`REDIS=${answers.redis.url}`);\n }\n\n if (answers.smtp) {\n lines.push(`SMTP=${answers.smtp.url}`);\n }\n\n if (answers.registrationEnabled) {\n lines.push('REGISTRATION_ENABLED=true');\n }\n\n if (answers.passwordRecoveryEnabled) {\n lines.push('PASSWORD_RECOVERY_ENABLED=true');\n }\n\n if (answers.emailVerificationEnabled) {\n lines.push('EMAIL_VERIFICATION_ENABLED=true');\n }\n\n lines.push(\n `USER_ADMIN_PASSWORD=${answers.adminPassword}`,\n '# Every downstream application origin, comma-separated; each may obtain a full-permission token.',\n '# TRUSTED_ORIGINS=https://app.example.com',\n '# Wraps the realm key store at rest (base64, 32 bytes). Write-once: back it up.',\n '# SECRETS_ENCRYPTION_KEY=<base64 32 bytes>',\n );\n\n return {\n 'authup.env': [\n '# Environment of the authup container. It holds secrets: keep it out of version control.',\n `# ${dockerRunCommand(version, exposure.hostPort)}`,\n '# The database must exist already; the image runs in production mode and refuses sqlite.',\n '# A configuration file or a provisioning directory is mounted with',\n '# -v ./authup.yml:/etc/authup/authup.yml:ro -v ./provisioning:/etc/authup/provisioning:ro',\n '# https://authup.org/guide/deployment/docker',\n ...lines,\n '',\n ].join('\\n'),\n };\n}\n","/*\n * Copyright (c) 2026.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nimport type { Answers, Rendered } from '../types.ts';\nimport { quoteYaml } from '../utils.ts';\n\n// The chart version, not the authup version: it is what `helm install` selects, and 0.4.0 is the first that carries\n// worker.* and server.splitConsoles (0.3.0 rejects both through its values schema). Every document names worker.enabled,\n// so every document needs it.\nexport const CHART_REQUIREMENT = '# This file needs the authup helm chart 0.4.0 or later.';\n\nexport const HELM_COMMANDS: string[] = [\n 'helm repo add authup https://helm.authup.org',\n 'helm install authup authup/authup -f values.yaml',\n];\n\nexport function renderHelm(answers: Answers, _version: string): Rendered {\n const { db } = answers;\n if (db.type === 'better-sqlite3') {\n throw new Error('The helm chart needs a server database (postgres or mysql); the image refuses sqlite.');\n }\n\n const url = new URL(answers.publicUrl);\n const tls = url.protocol === 'https:';\n const lines: string[] = [\n `# Written by npm create authup. Install with: ${HELM_COMMANDS.join(' && ')}`,\n '# Secrets in this file (auth.adminPassword, the database password, smtp.connectionString) can move to the chart\\'s existingSecret keys; see the chart README.',\n // worker.* arrived with the chart that tracks v1.0.0-beta.64 (authup/helm PR #29); the released 0.3.0 rejects it\n // through its values schema.\n CHART_REQUIREMENT,\n 'server:',\n ` publicUrl: ${quoteYaml(answers.publicUrl)}`,\n ...(tls && !answers.tlsCertManager ?\n [` # TLS needs a secret named ${url.hostname}-tls in the release namespace, or set server.ingress.certManager: true.`] :\n []),\n ' ingress:',\n ' enabled: true',\n ` hostname: ${quoteYaml(url.hostname)}`,\n ` tls: ${tls}`,\n ...(tls && answers.tlsCertManager ? [' certManager: true'] : []),\n ];\n\n if (answers.registrationEnabled || answers.passwordRecoveryEnabled || answers.emailVerificationEnabled) {\n lines.push(' features:');\n if (answers.registrationEnabled) {\n lines.push(' registration: true');\n }\n if (answers.passwordRecoveryEnabled) {\n lines.push(' passwordRecovery: true');\n }\n if (answers.emailVerificationEnabled) {\n lines.push(' emailVerification: true');\n }\n }\n\n if (answers.workerSplit) {\n lines.push(' migration:', ' enabled: true');\n }\n\n if (answers.consoleSplit) {\n lines.push(' splitConsoles: true');\n }\n\n lines.push(\n ' # Every downstream application origin; each may obtain a full-permission token.',\n ' # trustedOrigins:',\n ' # - https://app.example.com',\n ' # An authup.yml the chart mounts, for the options that only the file can carry.',\n ' # configuration: |',\n ' # core:',\n ' # passwordMinLength: 12',\n );\n\n // Always spelled out: the chart's default flipped to a dedicated worker in\n // 0.5.0, so leaving the key out would split a deployment that asked not to.\n lines.push('', 'worker:', ` enabled: ${answers.workerSplit}`);\n\n lines.push(\n '',\n 'database:',\n ` type: ${db.type}`,\n '',\n );\n\n // The bundled engine takes the answered password; left empty, the chart would generate one and drop the answer.\n for (const engine of ['postgresql', 'mysql'] as const) {\n const enabled = db.bundled && db.type === (engine === 'postgresql' ? 'postgres' : 'mysql');\n lines.push(`${engine}:`, ` enabled: ${enabled}`);\n if (enabled) {\n lines.push(' auth:', ` password: ${quoteYaml(db.password)}`);\n }\n }\n\n if (!db.bundled) {\n // The chart schema types the port as a string (\"\" = engine default), so a bare integer fails helm's validation.\n lines.push(\n 'externalDatabase:',\n ` host: ${quoteYaml(db.host)}`,\n ` port: ${quoteYaml(String(db.port))}`,\n ` user: ${quoteYaml(db.username)}`,\n ` database: ${quoteYaml(db.database)}`,\n ` password: ${quoteYaml(db.password)}`,\n );\n }\n\n if (answers.redis) {\n lines.push(\n '',\n 'valkey:',\n ' enabled: true',\n '# For an external Redis set valkey.enabled to false and externalRedis.url to its redis:// URL instead.',\n );\n }\n\n lines.push(\n '',\n 'auth:',\n ` adminPassword: ${quoteYaml(answers.adminPassword)}`,\n ' # Wraps the realm key store at rest (base64, 32 bytes). Write-once: back it up.',\n ' # secretsEncryptionKey: <base64 32 bytes>',\n );\n\n if (answers.smtp) {\n lines.push('', 'smtp:', ` connectionString: ${quoteYaml(answers.smtp.url)}`);\n }\n\n return { 'values.yaml': `${lines.join('\\n')}\\n` };\n}\n","/*\n * Copyright (c) 2026.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nimport { LISTEN_PORT, describeExposure, trustProxyValue } from '../deployment.ts';\nimport type { Answers, Rendered } from '../types.ts';\nimport { quoteEnv, quoteYaml } from '../utils.ts';\n\nconst SCHEMA_COMMENT = '# yaml-language-server: $schema=https://authup.org/schema/config.json';\nconst DATABASE_DOCS_URL = 'https://authup.org/guide/deployment/configuration-server-core-database';\n\nexport function renderBareMetal(answers: Answers, version: string): Rendered {\n if (answers.workerSplit || answers.consoleSplit) {\n throw new Error('The bare-metal target runs one process: a worker or console split is a second process under a process manager, which the wizard does not write.');\n }\n\n return {\n 'package.json': renderPackageJson(version),\n 'authup.yml': renderConfig(answers),\n '.env': renderEnv(answers),\n };\n}\n\nfunction renderPackageJson(version: string): string {\n const manifest = {\n name: 'authup-deployment',\n private: true,\n type: 'module',\n scripts: { start: 'authup start' },\n // exact, like the image tag the container targets write: the wizard scaffolds one release, not a range\n dependencies: { authup: version },\n };\n\n return `${JSON.stringify(manifest, null, 4)}\\n`;\n}\n\nfunction renderConfig(answers: Answers): string {\n const { db } = answers;\n const lines = [\n SCHEMA_COMMENT,\n `publicUrl: ${quoteYaml(answers.publicUrl)}`,\n ];\n\n if (db.type === 'better-sqlite3') {\n lines.push(\n '# Production refuses sqlite, so this file keeps the development default of env.',\n `# That is fine for a local trial only: ${DATABASE_DOCS_URL}`,\n '',\n '# db.sqlite is written into this directory.',\n );\n } else {\n lines.push(\n 'env: production',\n '',\n // A `db` block here would be used AS IS: server-core never merges DB_* over it, so the password could not\n // stay in .env. The whole connection rides the DB_* variables there instead (the documented env form).\n '# The database connection is the DB_* set in .env.',\n );\n }\n\n if (answers.redis) {\n lines.push('', `redis: ${quoteYaml(answers.redis.url)}`);\n }\n\n if (answers.smtp) {\n lines.push('', '# The connection URL is SMTP in .env.', 'smtp: true');\n }\n\n lines.push(\n '',\n '# Every downstream application origin; each may obtain a full-permission token.',\n '# trustedOrigins:',\n '# - https://app.example.com',\n );\n\n // The process listens where the url says when nothing is in front; behind a proxy the default stays.\n const exposure = describeExposure(answers.publicUrl);\n lines.push(\n '',\n 'core:',\n ...(exposure.hostPort !== LISTEN_PORT ? [` port: ${exposure.hostPort}`] : []),\n ' # The proxies in front whose X-Forwarded-For is trusted, or false; authup trusts every hop by default.',\n ` trustProxy: ${trustProxyValue(exposure)}`,\n ...(answers.registrationEnabled ? [' registrationEnabled: true'] : []),\n ...(answers.passwordRecoveryEnabled ? [' passwordRecoveryEnabled: true'] : []),\n ...(answers.emailVerificationEnabled ? [' emailVerificationEnabled: true'] : []),\n );\n\n return `${lines.join('\\n')}\\n`;\n}\n\nfunction renderEnv(answers: Answers): string {\n const lines = ['# Secrets. authup start reads this file from the directory it is started in. Keep it out of version control.'];\n\n const { db } = answers;\n if (db.type !== 'better-sqlite3') {\n lines.push(\n `DB_TYPE=${db.type}`,\n `DB_HOST=${quoteEnv(db.host)}`,\n `DB_PORT=${db.port}`,\n `DB_USERNAME=${quoteEnv(db.username)}`,\n `DB_PASSWORD=${quoteEnv(db.password)}`,\n `DB_DATABASE=${quoteEnv(db.database)}`,\n );\n }\n\n lines.push(`USER_ADMIN_PASSWORD=${quoteEnv(answers.adminPassword)}`);\n\n if (answers.smtp) {\n lines.push(`SMTP=${quoteEnv(answers.smtp.url)}`);\n }\n\n lines.push(\n '# Wraps the realm key store at rest (base64, 32 bytes). Write-once: back it up.',\n '# SECRETS_ENCRYPTION_KEY=',\n );\n\n return `${lines.join('\\n')}\\n`;\n}\n","/*\n * Copyright (c) 2026.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nimport { describeExposure, trustProxyValue } from '../deployment.ts';\nimport type { Answers, Rendered } from '../types.ts';\nimport {\n escapeComposeInterpolation,\n indent,\n quoteComposeEnv,\n quoteYaml,\n} from '../utils.ts';\n\nconst DOCS_URL = 'https://authup.org/guide/deployment';\n\nconst NGINX_CONF = `# Written by npm create authup: the console split's routing, ${DOCS_URL}/console-replicas\n# TLS, if any, terminates in front of this proxy; X-Forwarded-Proto is passed through as the scheme it sees.\nupstream authup_api { server authup:3000; }\nupstream authup_auth_console { server authup-console:3020; }\nupstream authup_admin_console { server authup-console:3021; }\nupstream authup_account_console { server authup-console:3022; }\n\nserver {\n listen 80;\n # nginx listens on 80 inside the container while the published port is another one: a redirect it builds itself\n # (the trailing slash it adds to /console/<name>) must stay relative or it would send the browser to port 80.\n absolute_redirect off;\n\n # The sign-in pair stays on the API. Exact matches, so they win over the prefix rules below.\n location = /console/admin/login/start { proxy_pass http://authup_api; }\n location = /console/admin/callback { proxy_pass http://authup_api; }\n location = /console/account/login/start { proxy_pass http://authup_api; }\n location = /console/account/callback { proxy_pass http://authup_api; }\n\n location /console/auth/ {\n rewrite ^/console/auth(/.*)$ $1 break;\n proxy_set_header Host $host;\n proxy_set_header X-Forwarded-Proto $scheme;\n proxy_set_header X-Forwarded-Tls-Client-Cert \"\";\n proxy_pass http://authup_auth_console;\n }\n\n location /console/admin/ {\n rewrite ^/console/admin(/.*)$ $1 break;\n proxy_set_header Host $host;\n proxy_set_header X-Forwarded-Proto $scheme;\n proxy_set_header X-Forwarded-Tls-Client-Cert \"\";\n proxy_pass http://authup_admin_console;\n }\n\n location /console/account/ {\n rewrite ^/console/account(/.*)$ $1 break;\n proxy_set_header Host $host;\n proxy_set_header X-Forwarded-Proto $scheme;\n proxy_set_header X-Forwarded-Tls-Client-Cert \"\";\n proxy_pass http://authup_account_console;\n }\n\n location / {\n proxy_set_header Host $host;\n proxy_set_header X-Real-IP $remote_addr;\n proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n proxy_set_header X-Forwarded-Proto $scheme;\n proxy_set_header X-Forwarded-Tls-Client-Cert \"\";\n proxy_pass http://authup_api;\n }\n}\n`;\n\n\nconst ENGINES = {\n postgres: {\n image: 'postgres:17',\n port: 5432,\n dataPath: '/var/lib/postgresql/data',\n },\n mysql: {\n image: 'mysql:8.4',\n port: 3306,\n dataPath: '/var/lib/mysql',\n },\n} as const;\n\n// One list entry of a compose `environment` block; the whole KEY=value is one YAML scalar, so quoting it covers the\n// value, and an operator's `$` is doubled so compose does not interpolate it away.\nfunction env(key: string, value: string): string {\n return `- ${quoteYaml(`${key}=${escapeComposeInterpolation(value)}`)}`;\n}\n\n// The same entry carrying a deliberate `${VARIABLE}` reference into .env.\nfunction ref(key: string, variable: string): string {\n return `- ${quoteYaml(`${key}=\\${${variable}}`)}`;\n}\n\nfunction block(key: string, lines: string[]): string[] {\n return [`${key}:`, ...indent(lines.join('\\n'), 2).split('\\n')];\n}\n\nexport function renderCompose(answers: Answers, version: string): Rendered {\n const { db } = answers;\n if (db.type === 'better-sqlite3') {\n throw new Error('The compose target needs a server database (postgres or mysql): the image runs in production mode and refuses sqlite.');\n }\n\n const image = `image: authup/authup:${version}`;\n const restart = 'restart: unless-stopped';\n const engine = ENGINES[db.type];\n const exposure = describeExposure(answers.publicUrl);\n\n const dbEnv = [\n env('DB_TYPE', db.type),\n env('DB_HOST', db.bundled ? db.type : db.host),\n env('DB_PORT', String(db.bundled ? engine.port : db.port)),\n env('DB_USERNAME', db.username),\n ref('DB_PASSWORD', 'DB_PASSWORD'),\n env('DB_DATABASE', db.database),\n ];\n if (answers.redis) {\n dbEnv.push(env('REDIS', 'redis://redis:6379'));\n }\n\n const dependsOn: string[] = [];\n if (db.bundled) {\n // The one-off `migration run` and the services would otherwise race an engine still initializing its volume.\n dependsOn.push(`${db.type}:`, ' condition: service_healthy');\n }\n if (answers.redis) {\n dependsOn.push('redis:', ' condition: service_started');\n }\n const dependsOnBlock = dependsOn.length > 0 ? block('depends_on', dependsOn) : [];\n\n const authupEnv = [\n env('PUBLIC_URL', answers.publicUrl),\n '# the proxies in front whose X-Forwarded-For is trusted, or false; authup trusts every hop by default',\n env('TRUST_PROXY', trustProxyValue(exposure, answers.consoleSplit)),\n ...dbEnv,\n ref('USER_ADMIN_PASSWORD', 'USER_ADMIN_PASSWORD'),\n ];\n if (answers.smtp) {\n authupEnv.push(ref('SMTP', 'SMTP'));\n }\n if (answers.registrationEnabled) {\n authupEnv.push(env('REGISTRATION_ENABLED', 'true'));\n }\n if (answers.passwordRecoveryEnabled) {\n authupEnv.push(env('PASSWORD_RECOVERY_ENABLED', 'true'));\n }\n if (answers.emailVerificationEnabled) {\n authupEnv.push(env('EMAIL_VERIFICATION_ENABLED', 'true'));\n }\n authupEnv.push(\n '# every downstream application origin, comma-separated; each may obtain a full-permission token',\n '# - \"TRUSTED_ORIGINS=https://app.example.com\"',\n '# wraps the realm key store at rest (base64, 32 bytes); write-once, back it up',\n '# - \"SECRETS_ENCRYPTION_KEY=<base64 32 bytes>\"',\n );\n // A console-only split keeps one API process, which sweeps and migrates itself like a plain `start` does.\n if (answers.workerSplit) {\n authupEnv.push(\n '# the sweeps run in the worker below',\n env('WORKER_ENABLED', 'false'),\n '# run \"docker compose run --rm authup migration run\" once before starting',\n env('MIGRATION_ENABLED', 'false'),\n );\n }\n\n const services = block('authup', [\n image,\n restart,\n // under a console split the proxy below owns the published port\n ...(answers.consoleSplit ? [] : block('ports', [`- \"${exposure.hostPort}:3000\"`])),\n '# a configuration file and a provisioning directory are optional; mount them under /etc/authup',\n '# volumes:',\n '# - ./authup.yml:/etc/authup/authup.yml:ro',\n '# - ./provisioning:/etc/authup/provisioning:ro',\n ...dependsOnBlock,\n ...block('environment', authupEnv),\n `command: ${answers.consoleSplit ? 'start core' : 'start'}`,\n ]);\n\n if (db.bundled) {\n const credentials = db.type === 'postgres' ?\n [\n env('POSTGRES_USER', db.username),\n ref('POSTGRES_PASSWORD', 'DB_PASSWORD'),\n env('POSTGRES_DB', db.database),\n ] :\n [\n env('MYSQL_USER', db.username),\n ref('MYSQL_PASSWORD', 'DB_PASSWORD'),\n ref('MYSQL_ROOT_PASSWORD', 'DB_PASSWORD'),\n env('MYSQL_DATABASE', db.database),\n ];\n\n // Both official images run a temporary, network-less server while initializing an empty volume, so a probe\n // over TCP answers only once the real server is up (pg_isready over the socket would answer too early).\n const probe = db.type === 'postgres' ?\n `test: [\"CMD\", \"pg_isready\", \"-h\", \"localhost\", \"-U\", ${quoteYaml(escapeComposeInterpolation(db.username))}, \"-d\", ${quoteYaml(escapeComposeInterpolation(db.database))}]` :\n // mysqladmin ping exits 0 whenever the server takes the connection, credentials or not\n 'test: [\"CMD\", \"mysqladmin\", \"ping\", \"-h\", \"127.0.0.1\"]';\n\n services.push('', ...block(db.type, [\n `image: ${engine.image}`,\n restart,\n ...block('volumes', [`- ${db.type}_data:${engine.dataPath}`]),\n ...block('environment', credentials),\n ...block('healthcheck', [probe, 'interval: 5s', 'timeout: 5s', 'retries: 24']),\n ]));\n }\n\n if (answers.redis) {\n services.push('', ...block('redis', ['image: redis:7', restart]));\n }\n\n if (answers.workerSplit) {\n services.push(\n '',\n '# one instance is enough, whatever the API scales to',\n ...block('authup-worker', [\n image,\n restart,\n ...dependsOnBlock,\n // the worker needs no url of its own; without one the CLI derives it and warns on every start\n ...block('environment', [env('PUBLIC_URL', answers.publicUrl), ...dbEnv]),\n 'command: start worker',\n ]),\n );\n }\n\n if (answers.consoleSplit) {\n services.push(\n '',\n '# Two proxy rules, in this order: /console/<name>/login/start and',\n '# /console/<name>/callback (admin and account) go to the authup service;',\n '# everything else under /console/** goes here, with the /console/<name>',\n `# prefix stripped. See ${DOCS_URL}/console-replicas`,\n ...block('authup-console', [\n image,\n restart,\n ...block('environment', [\n env('PUBLIC_URL', answers.publicUrl),\n '# the auth console renders server-side and calls the API itself: reach it on this network',\n env('INTERNAL_URL', 'http://authup:3000'),\n ]),\n '# the image probes the API port, which this process never opens; the auth',\n '# console cannot be disabled, so it is always listening (3020 by default)',\n ...block('healthcheck', [\n 'test: [\"CMD\", \"wget\", \"--spider\", \"--proxy\", \"off\", \"http://127.0.0.1:3020/healthy\"]',\n 'interval: 10s',\n ]),\n 'command: start console',\n ]),\n '',\n '# The proxy owns the published port and carries the two console routing rules (nginx.conf).',\n ...block('nginx', [\n 'image: nginx:1.31-alpine',\n restart,\n ...block('ports', [`- \"${exposure.hostPort}:80\"`]),\n ...block('volumes', ['- ./nginx.conf:/etc/nginx/conf.d/default.conf:ro']),\n ...block('depends_on', ['authup:', ' condition: service_started', 'authup-console:', ' condition: service_started']),\n ]),\n );\n }\n\n const compose = [\n `# Written by npm create authup. Secrets live in .env next to this file: ${DOCS_URL}/docker-compose`,\n '',\n ];\n if (db.bundled) {\n compose.push(...block('volumes', [`${db.type}_data:`]), '');\n }\n compose.push(...block('services', services));\n\n const dotenv = [\n '# Secrets docker-compose.yml interpolates. Keep this file out of version control.',\n `DB_PASSWORD=${quoteComposeEnv(db.password)}`,\n `USER_ADMIN_PASSWORD=${quoteComposeEnv(answers.adminPassword)}`,\n ];\n if (answers.smtp) {\n dotenv.push(`SMTP=${quoteComposeEnv(answers.smtp.url)}`);\n }\n dotenv.push(\n '# Wraps the realm key store at rest (base64, 32 bytes). Write-once: back it up.',\n '# SECRETS_ENCRYPTION_KEY=',\n );\n\n return {\n 'docker-compose.yml': `${compose.join('\\n')}\\n`,\n '.env': `${dotenv.join('\\n')}\\n`,\n ...(answers.consoleSplit ? { 'nginx.conf': NGINX_CONF } : {}),\n };\n}\n","/*\n * Copyright (c) 2026.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nimport type { Renderer, Target } from '../types.ts';\nimport { renderBareMetal } from './bare-metal.ts';\nimport { renderCompose } from './compose.ts';\nimport { renderDocker } from './docker.ts';\nimport { renderHelm } from './helm.ts';\n\nexport const RENDERERS: Record<Target, Renderer> = {\n docker: renderDocker,\n compose: renderCompose,\n helm: renderHelm,\n 'bare-metal': renderBareMetal,\n};\n","/*\n * Copyright (c) 2026.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nimport { readFileSync } from 'node:fs';\n\n// dist/index.mjs and src/version.ts both sit one level below the package root, so one relative URL serves the built bin and vitest alike.\nexport const VERSION: string = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8')).version;\n","#!/usr/bin/env node\n\n/*\n * Copyright (c) 2026.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\n/* eslint-disable no-console */\n\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport process from 'node:process';\nimport { parseArgs } from 'node:util';\nimport { createReadlineAsk } from './ask.ts';\nimport { SECRET_FILES, TARGETS } from './constants.ts';\nimport { describeExposure } from './deployment.ts';\nimport { collectAnswers } from './prompts.ts';\nimport { dockerRunCommand } from './targets/docker.ts';\nimport { HELM_COMMANDS } from './targets/helm.ts';\nimport { RENDERERS } from './targets/index.ts';\nimport type { Answers, Target } from './types.ts';\nimport { VERSION } from './version.ts';\n\nconst DOCS_URL = 'https://authup.org/guide/deployment/';\n\nconst USAGE = `Usage: npx create-authup [--force] [--help]\n npm create authup -- [--force] [--help]\n\nWrites the deployment files for one authup installation into the current directory.\n\nTargets: ${TARGETS.join(', ')}\n\nFlags:\n --force overwrite files that already exist\n --help print this text`;\n\nfunction nextSteps(answers: Answers): string[] {\n const steps: Record<Target, string[]> = {\n docker: [\n dockerRunCommand(VERSION, describeExposure(answers.publicUrl).hostPort),\n 'The database must already exist: the image runs in production mode and refuses sqlite.',\n ],\n compose: [\n ...(answers.workerSplit ? ['docker compose run --rm authup migration run'] : []),\n 'docker compose up -d',\n ...(answers.consoleSplit ? ['The nginx service publishes the port; TLS, if any, terminates in front of it.'] : []),\n ],\n helm: [...HELM_COMMANDS],\n 'bare-metal': ['npm install', 'npx authup config validate', 'npm start'],\n };\n\n return steps[answers.target];\n}\n\nasync function main(): Promise<void> {\n const { values } = parseArgs({\n options: {\n force: { type: 'boolean', default: false },\n help: { type: 'boolean', default: false },\n },\n allowPositionals: false,\n });\n if (values.help) {\n console.log(USAGE);\n return;\n }\n\n console.log(`create-authup ${VERSION}`);\n console.log(`Files are written into the current directory (${process.cwd()}).`);\n\n const { ask, close } = createReadlineAsk();\n let collected: Awaited<ReturnType<typeof collectAnswers>>;\n try {\n collected = await collectAnswers(ask);\n } finally {\n close();\n }\n const { answers, notes } = collected;\n\n const rendered = RENDERERS[answers.target](answers, VERSION);\n const files = Object.keys(rendered);\n const directories = files.filter((name) => {\n try {\n return fs.statSync(path.join(process.cwd(), name)).isDirectory();\n } catch {\n return false;\n }\n });\n if (directories.length > 0) {\n console.error(`Cannot write over a directory: ${directories.join(', ')}.`);\n process.exit(1);\n }\n\n const existing = files.filter((name) => fs.existsSync(path.join(process.cwd(), name)));\n if (existing.length > 0 && !values.force) {\n console.error(`Refusing to overwrite: ${existing.join(', ')}. Re-run with --force.`);\n process.exit(1);\n }\n\n for (const name of files) {\n const target = path.join(process.cwd(), name);\n const secret = SECRET_FILES.has(name);\n fs.writeFileSync(target, rendered[name], secret ? { encoding: 'utf8', mode: 0o600 } : 'utf8');\n if (secret) {\n // writeFileSync's mode applies to a file it creates, so an overwrite keeps whatever the old one had\n fs.chmodSync(target, 0o600);\n }\n }\n\n for (const note of notes) {\n console.log(note);\n }\n console.log(`Written: ${files.join(', ')}`);\n console.log('Next steps:');\n for (const step of nextSteps(answers)) {\n console.log(` ${step}`);\n }\n console.log(`Docs: ${DOCS_URL}`);\n}\n\nmain().catch((e) => {\n console.error(e instanceof Error ? e.message : String(e));\n process.exit(1);\n});\n\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-authup",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.0.0-beta.
|
|
5
|
-
"description": "
|
|
4
|
+
"version": "1.0.0-beta.70",
|
|
5
|
+
"description": "Answer a few questions and get the deployment files for Authup: docker run, compose, Helm or bare metal.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
7
7
|
"bin": {
|
|
8
8
|
"create-authup": "dist/index.mjs"
|
|
@@ -47,13 +47,13 @@
|
|
|
47
47
|
"homepage": "https://github.com/authup/authup#readme",
|
|
48
48
|
"dependencies": {},
|
|
49
49
|
"devDependencies": {
|
|
50
|
-
"@authup/server-config": "^1.0.0-beta.
|
|
51
|
-
"@authup/server-config-kit": "^1.0.0-beta.
|
|
52
|
-
"@types/node": "^26.6.
|
|
50
|
+
"@authup/server-config": "^1.0.0-beta.70",
|
|
51
|
+
"@authup/server-config-kit": "^1.0.0-beta.70",
|
|
52
|
+
"@types/node": "^26.6.5",
|
|
53
53
|
"ajv": "^8.20.0",
|
|
54
54
|
"cross-env": "^10.1.0",
|
|
55
|
-
"dotenv": "^18.0.
|
|
56
|
-
"vitest": "^
|
|
55
|
+
"dotenv": "^18.0.8",
|
|
56
|
+
"vitest": "^5.0.3",
|
|
57
57
|
"yaml": "^2.9.1"
|
|
58
58
|
},
|
|
59
59
|
"publishConfig": {
|