@fluixi/cli 0.1.0-alpha.82 → 0.1.0-alpha.83
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 +21 -3
- package/dist/add.cjs +590 -0
- package/dist/add.d.ts +10 -0
- package/dist/add.d.ts.map +1 -0
- package/dist/add.js +107 -0
- package/dist/add.mjs +562 -0
- package/dist/check.cjs +102 -0
- package/dist/check.d.ts +13 -0
- package/dist/check.d.ts.map +1 -0
- package/dist/check.js +88 -0
- package/dist/check.mjs +76 -0
- package/dist/cli.cjs +977 -78
- package/dist/cli.js +44 -2
- package/dist/cli.mjs +976 -77
- package/dist/doctor.cjs +119 -0
- package/dist/doctor.d.ts +13 -0
- package/dist/doctor.d.ts.map +1 -0
- package/dist/doctor.js +114 -0
- package/dist/doctor.mjs +92 -0
- package/dist/generate.cjs +614 -65
- package/dist/generate.d.ts.map +1 -1
- package/dist/generate.js +118 -48
- package/dist/generate.mjs +619 -64
- package/dist/index.cjs +1 -1
- package/dist/index.mjs +1 -1
- package/dist/report.cjs +54 -0
- package/dist/report.d.ts +5 -0
- package/dist/report.d.ts.map +1 -0
- package/dist/report.js +31 -0
- package/dist/report.mjs +28 -0
- package/dist/scaffold/auth.cjs +422 -0
- package/dist/scaffold/auth.d.ts +39 -0
- package/dist/scaffold/auth.d.ts.map +1 -0
- package/dist/scaffold/auth.js +137 -0
- package/dist/scaffold/auth.mjs +389 -0
- package/dist/scaffold/edit.cjs +153 -0
- package/dist/scaffold/edit.d.ts +35 -0
- package/dist/scaffold/edit.d.ts.map +1 -0
- package/dist/scaffold/edit.js +140 -0
- package/dist/scaffold/edit.mjs +124 -0
- package/dist/scaffold/index.cjs +610 -0
- package/dist/scaffold/index.d.ts +10 -0
- package/dist/scaffold/index.d.ts.map +1 -0
- package/dist/scaffold/index.js +9 -0
- package/dist/scaffold/index.mjs +569 -0
- package/dist/scaffold/prompt.cjs +108 -0
- package/dist/scaffold/prompt.d.ts +9 -0
- package/dist/scaffold/prompt.d.ts.map +1 -0
- package/dist/scaffold/prompt.js +95 -0
- package/dist/scaffold/prompt.mjs +81 -0
- package/dist/scaffold/templates.cjs +274 -0
- package/dist/scaffold/templates.d.ts +26 -0
- package/dist/scaffold/templates.d.ts.map +1 -0
- package/dist/scaffold/templates.js +216 -0
- package/dist/scaffold/templates.mjs +229 -0
- package/dist/tsconfig.lib.tsbuildinfo +1 -1
- package/package.json +19 -3
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The interactive prompts create-fluixi and `fluixi add` share: a text answer and an
|
|
3
|
+
* arrow-key choice, drawn in the clack-style gutter. Zero dependencies, hand-rolled ANSI.
|
|
4
|
+
*/
|
|
5
|
+
import { createInterface } from 'node:readline/promises';
|
|
6
|
+
import { emitKeypressEvents } from 'node:readline';
|
|
7
|
+
import { stdin as input, stdout as output, exit, env } from 'node:process';
|
|
8
|
+
const COLOR = env.NO_COLOR || env.FORCE_COLOR === '0'
|
|
9
|
+
? false
|
|
10
|
+
: (!!output.isTTY || !!env.FORCE_COLOR) && env.TERM !== 'dumb';
|
|
11
|
+
const sgr = (o, x) => (s) => (COLOR ? `\x1b[${o}m${s}\x1b[${x}m` : `${s}`);
|
|
12
|
+
const c = { bold: sgr(1, 22), green: sgr(32, 39), cyan: sgr(36, 39), gray: sgr(90, 39) };
|
|
13
|
+
export const BAR = c.gray('│');
|
|
14
|
+
export async function askText(label, fallback) {
|
|
15
|
+
output.write(`${c.green('◇')} ${c.bold(label)} ${c.gray(`(${fallback})`)}\n`);
|
|
16
|
+
const rl = createInterface({ input, output });
|
|
17
|
+
const answer = (await rl.question(`${BAR} `)).trim();
|
|
18
|
+
rl.close();
|
|
19
|
+
output.write(`${BAR}\n`);
|
|
20
|
+
return answer || fallback;
|
|
21
|
+
}
|
|
22
|
+
export function askSelect(label, choices) {
|
|
23
|
+
return new Promise((done) => {
|
|
24
|
+
let idx = 0;
|
|
25
|
+
output.write(`${c.green('◆')} ${c.bold(label)}\n`);
|
|
26
|
+
const draw = (first) => {
|
|
27
|
+
if (!first)
|
|
28
|
+
output.write(`\x1b[${choices.length}A`);
|
|
29
|
+
for (let i = 0; i < choices.length; i++) {
|
|
30
|
+
const on = i === idx;
|
|
31
|
+
const dot = on ? c.green('●') : c.gray('○');
|
|
32
|
+
const name = on ? c.cyan(c.bold(choices[i].label)) : choices[i].label;
|
|
33
|
+
output.write(`\x1b[2K${BAR} ${dot} ${name} ${c.gray(choices[i].hint)}\n`);
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
draw(true);
|
|
37
|
+
emitKeypressEvents(input);
|
|
38
|
+
if (input.isTTY)
|
|
39
|
+
input.setRawMode(true);
|
|
40
|
+
const onKey = (_s, k) => {
|
|
41
|
+
if (k.name === 'up' || k.name === 'k') {
|
|
42
|
+
idx = (idx - 1 + choices.length) % choices.length;
|
|
43
|
+
draw(false);
|
|
44
|
+
}
|
|
45
|
+
else if (k.name === 'down' || k.name === 'j') {
|
|
46
|
+
idx = (idx + 1) % choices.length;
|
|
47
|
+
draw(false);
|
|
48
|
+
}
|
|
49
|
+
// Both names. A terminal sends `\r` and readline calls that `return`; a pty or a
|
|
50
|
+
// piped answer sends `\n`, which it calls `enter`. Accepting only the first left
|
|
51
|
+
// the prompt waiting for a key that never came.
|
|
52
|
+
else if (k.name === 'return' || k.name === 'enter') {
|
|
53
|
+
finish();
|
|
54
|
+
done(choices[idx].value);
|
|
55
|
+
}
|
|
56
|
+
else if (k.name === 'c' && k.ctrl) {
|
|
57
|
+
finish();
|
|
58
|
+
output.write('\n');
|
|
59
|
+
exit(1);
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
// Enter on a closed stream still means "take what is highlighted". Without this a
|
|
63
|
+
// piped answer (`printf 'app\n\n\n' | create-fluixi`) drew the list and stopped.
|
|
64
|
+
const onEnd = () => { finish(); done(choices[idx].value); };
|
|
65
|
+
const finish = () => {
|
|
66
|
+
input.off('keypress', onKey);
|
|
67
|
+
input.off('end', onEnd);
|
|
68
|
+
input.off('close', onEnd);
|
|
69
|
+
if (input.isTTY)
|
|
70
|
+
input.setRawMode(false);
|
|
71
|
+
// Pause it again or the live stdin handle keeps the event loop alive and the CLI
|
|
72
|
+
// never exits.
|
|
73
|
+
input.pause();
|
|
74
|
+
output.write(`${BAR}\n`);
|
|
75
|
+
};
|
|
76
|
+
// Already at EOF, so no event is coming: a fully buffered answer
|
|
77
|
+
// (`printf 'app\n\n\n' | create-fluixi`) is consumed by the name prompt and the
|
|
78
|
+
// stream ends before this one is set up. Take the highlighted value now.
|
|
79
|
+
if (input.readableEnded) {
|
|
80
|
+
finish();
|
|
81
|
+
done(choices[idx].value);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
input.on('keypress', onKey);
|
|
85
|
+
input.on('end', onEnd);
|
|
86
|
+
input.on('close', onEnd);
|
|
87
|
+
// The reason this prompt used to go nowhere. `askText` closes its readline
|
|
88
|
+
// interface, which leaves stdin paused, and `emitKeypressEvents` only wires the
|
|
89
|
+
// stream up the first time it is called. Attaching a listener to a paused stream
|
|
90
|
+
// reads nothing, so no key ever arrived: the list drew with the first option
|
|
91
|
+
// highlighted, the event loop drained, and the process exited 0 having created
|
|
92
|
+
// nothing.
|
|
93
|
+
input.resume();
|
|
94
|
+
});
|
|
95
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
// src/scaffold/prompt.ts
|
|
2
|
+
import { createInterface } from "node:readline/promises";
|
|
3
|
+
import { emitKeypressEvents } from "node:readline";
|
|
4
|
+
import { stdin as input, stdout as output, exit, env } from "node:process";
|
|
5
|
+
var COLOR = env.NO_COLOR || env.FORCE_COLOR === "0" ? false : (!!output.isTTY || !!env.FORCE_COLOR) && env.TERM !== "dumb";
|
|
6
|
+
var sgr = (o, x) => (s) => COLOR ? `\x1B[${o}m${s}\x1B[${x}m` : `${s}`;
|
|
7
|
+
var c = { bold: sgr(1, 22), green: sgr(32, 39), cyan: sgr(36, 39), gray: sgr(90, 39) };
|
|
8
|
+
var BAR = c.gray("│");
|
|
9
|
+
async function askText(label, fallback) {
|
|
10
|
+
output.write(`${c.green("◇")} ${c.bold(label)} ${c.gray(`(${fallback})`)}
|
|
11
|
+
`);
|
|
12
|
+
const rl = createInterface({ input, output });
|
|
13
|
+
const answer = (await rl.question(`${BAR} `)).trim();
|
|
14
|
+
rl.close();
|
|
15
|
+
output.write(`${BAR}
|
|
16
|
+
`);
|
|
17
|
+
return answer || fallback;
|
|
18
|
+
}
|
|
19
|
+
function askSelect(label, choices) {
|
|
20
|
+
return new Promise((done) => {
|
|
21
|
+
let idx = 0;
|
|
22
|
+
output.write(`${c.green("◆")} ${c.bold(label)}
|
|
23
|
+
`);
|
|
24
|
+
const draw = (first) => {
|
|
25
|
+
if (!first) output.write(`\x1B[${choices.length}A`);
|
|
26
|
+
for (let i = 0; i < choices.length; i++) {
|
|
27
|
+
const on = i === idx;
|
|
28
|
+
const dot = on ? c.green("●") : c.gray("○");
|
|
29
|
+
const name = on ? c.cyan(c.bold(choices[i].label)) : choices[i].label;
|
|
30
|
+
output.write(`\x1B[2K${BAR} ${dot} ${name} ${c.gray(choices[i].hint)}
|
|
31
|
+
`);
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
draw(true);
|
|
35
|
+
emitKeypressEvents(input);
|
|
36
|
+
if (input.isTTY) input.setRawMode(true);
|
|
37
|
+
const onKey = (_s, k) => {
|
|
38
|
+
if (k.name === "up" || k.name === "k") {
|
|
39
|
+
idx = (idx - 1 + choices.length) % choices.length;
|
|
40
|
+
draw(false);
|
|
41
|
+
} else if (k.name === "down" || k.name === "j") {
|
|
42
|
+
idx = (idx + 1) % choices.length;
|
|
43
|
+
draw(false);
|
|
44
|
+
} else if (k.name === "return" || k.name === "enter") {
|
|
45
|
+
finish();
|
|
46
|
+
done(choices[idx].value);
|
|
47
|
+
} else if (k.name === "c" && k.ctrl) {
|
|
48
|
+
finish();
|
|
49
|
+
output.write("\n");
|
|
50
|
+
exit(1);
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
const onEnd = () => {
|
|
54
|
+
finish();
|
|
55
|
+
done(choices[idx].value);
|
|
56
|
+
};
|
|
57
|
+
const finish = () => {
|
|
58
|
+
input.off("keypress", onKey);
|
|
59
|
+
input.off("end", onEnd);
|
|
60
|
+
input.off("close", onEnd);
|
|
61
|
+
if (input.isTTY) input.setRawMode(false);
|
|
62
|
+
input.pause();
|
|
63
|
+
output.write(`${BAR}
|
|
64
|
+
`);
|
|
65
|
+
};
|
|
66
|
+
if (input.readableEnded) {
|
|
67
|
+
finish();
|
|
68
|
+
done(choices[idx].value);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
input.on("keypress", onKey);
|
|
72
|
+
input.on("end", onEnd);
|
|
73
|
+
input.on("close", onEnd);
|
|
74
|
+
input.resume();
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
export {
|
|
78
|
+
BAR,
|
|
79
|
+
askSelect,
|
|
80
|
+
askText
|
|
81
|
+
};
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/scaffold/templates.ts
|
|
21
|
+
var templates_exports = {};
|
|
22
|
+
__export(templates_exports, {
|
|
23
|
+
AUTH_READER: () => AUTH_READER,
|
|
24
|
+
PROVIDERS: () => PROVIDERS,
|
|
25
|
+
PROVIDER_VARS: () => PROVIDER_VARS,
|
|
26
|
+
SERVER_ONLY: () => SERVER_ONLY,
|
|
27
|
+
apiRoute: () => apiRoute,
|
|
28
|
+
authEnvConfig: () => authEnvConfig,
|
|
29
|
+
authServerFile: () => authServerFile,
|
|
30
|
+
authServiceFile: () => authServiceFile,
|
|
31
|
+
betterAuthClient: () => betterAuthClient,
|
|
32
|
+
betterAuthConfig: () => betterAuthConfig,
|
|
33
|
+
betterAuthMiddleware: () => betterAuthMiddleware,
|
|
34
|
+
betterAuthRoute: () => betterAuthRoute,
|
|
35
|
+
callbackRoute: () => callbackRoute,
|
|
36
|
+
camel: () => camel,
|
|
37
|
+
envConfig: () => envConfig,
|
|
38
|
+
guardedApiRoute: () => guardedApiRoute,
|
|
39
|
+
loginRoute: () => loginRoute,
|
|
40
|
+
moduleFile: () => moduleFile,
|
|
41
|
+
serverFn: () => serverFn,
|
|
42
|
+
serviceFile: () => serviceFile,
|
|
43
|
+
verifyServer: () => verifyServer
|
|
44
|
+
});
|
|
45
|
+
module.exports = __toCommonJS(templates_exports);
|
|
46
|
+
var camel = (name) => name.toLowerCase().replace(/_+([a-z0-9])/g, (_, ch) => ch.toUpperCase());
|
|
47
|
+
var envConfig = (names) => {
|
|
48
|
+
const fields = names.length ? names.map((n) => ` ${camel(n)}: '${n}',`).join("\n") : ` // apiUrl: 'API_URL',`;
|
|
49
|
+
return `import { pickEnv } from '@fluixi/start/env';
|
|
50
|
+
|
|
51
|
+
// Every missing variable is reported at once, when this module loads. A field that may be
|
|
52
|
+
// unset takes { key, optional: true } or { key, default }; parse: asNumber and friends convert.
|
|
53
|
+
export const config = pickEnv({
|
|
54
|
+
${fields}
|
|
55
|
+
});
|
|
56
|
+
`;
|
|
57
|
+
};
|
|
58
|
+
var moduleFile = (Name, name) => `import { defineModule } from '@fluixi/start/app';
|
|
59
|
+
|
|
60
|
+
export const ${Name}Module = defineModule({
|
|
61
|
+
name: '${name}',
|
|
62
|
+
imports: [],
|
|
63
|
+
providers: [],
|
|
64
|
+
});
|
|
65
|
+
`;
|
|
66
|
+
var serviceFile = (Name, name, server) => `${server ? `// Server only: the browser build refuses a page that imports this file.
|
|
67
|
+
` : ""}import { injectionToken } from '@fluixi/start/di';
|
|
68
|
+
import { defineModule } from '@fluixi/start/app';
|
|
69
|
+
|
|
70
|
+
export class ${Name}Service {}
|
|
71
|
+
|
|
72
|
+
export const ${Name}Token = injectionToken<${Name}Service>('${Name}');
|
|
73
|
+
|
|
74
|
+
export const ${Name}Module = defineModule({
|
|
75
|
+
name: '${name}',
|
|
76
|
+
providers: [{ provide: ${Name}Token, useFactory: () => new ${Name}Service() }],
|
|
77
|
+
});
|
|
78
|
+
`;
|
|
79
|
+
var apiRoute = () => `export function GET(request: Request) {
|
|
80
|
+
return Response.json({ path: new URL(request.url).pathname });
|
|
81
|
+
}
|
|
82
|
+
`;
|
|
83
|
+
var guardedApiRoute = (guardImport, role) => `import { guard } from '${guardImport}';
|
|
84
|
+
|
|
85
|
+
export const GET = guard(
|
|
86
|
+
async (request, claims) => {
|
|
87
|
+
return Response.json({ sub: claims.sub });
|
|
88
|
+
},
|
|
89
|
+
{ role: '${role}' },
|
|
90
|
+
);
|
|
91
|
+
`;
|
|
92
|
+
var verifyServer = () => `import { createTokenVerifier, createApiGuard } from '@fluixi/oauth2/verify';
|
|
93
|
+
import { auth } from '../config/env.js';
|
|
94
|
+
|
|
95
|
+
// Checks a bearer token's signature, issuer, audience and expiry against the provider.
|
|
96
|
+
export const verify = createTokenVerifier(auth.provider.verifier(auth.clientId));
|
|
97
|
+
|
|
98
|
+
// Answers 401 without a valid token and 403 without the role.
|
|
99
|
+
export const guard = createApiGuard({ verify, roles: auth.provider.roles });
|
|
100
|
+
`;
|
|
101
|
+
var serverFn = (name) => `export async function ${name}(input: { id: string }) {
|
|
102
|
+
'use server';
|
|
103
|
+
// Runs on the server only. The browser calls it over RPC, so input and result are plain data.
|
|
104
|
+
return { id: input.id };
|
|
105
|
+
}
|
|
106
|
+
`;
|
|
107
|
+
var PROVIDERS = ["oidc", "keycloak", "auth0", "okta", "google", "dex", "oauth2"];
|
|
108
|
+
var SERVER_ONLY = {
|
|
109
|
+
google: "Google answers a token request without a client secret with `client_secret is missing`",
|
|
110
|
+
dex: "Dex sends no CORS headers on discovery or the token endpoint"
|
|
111
|
+
};
|
|
112
|
+
var PROVIDER_VARS = {
|
|
113
|
+
oidc: { AUTH_ISSUER: "https://id.example.com" },
|
|
114
|
+
keycloak: { AUTH_BASE_URL: "http://localhost:8080", AUTH_REALM: "master" },
|
|
115
|
+
auth0: { AUTH_DOMAIN: "example.eu.auth0.com", AUTH_AUDIENCE: "https://api.example.com" },
|
|
116
|
+
okta: { AUTH_DOMAIN: "example.okta.com" },
|
|
117
|
+
google: {},
|
|
118
|
+
dex: { AUTH_BASE_URL: "http://localhost:5556/dex" },
|
|
119
|
+
oauth2: {
|
|
120
|
+
AUTH_ISSUER: "https://id.example.com",
|
|
121
|
+
AUTH_AUTHORIZATION_ENDPOINT: "https://id.example.com/authorize",
|
|
122
|
+
AUTH_TOKEN_ENDPOINT: "https://id.example.com/token"
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
var AUTH_READER = `// Provider, issuer, client and token mode from the AUTH_* variables in env.public. Moving to
|
|
126
|
+
// another provider is a change to .env.
|
|
127
|
+
export const auth = authFromEnv(publicEnv(), { clientPrefix: '' });
|
|
128
|
+
`;
|
|
129
|
+
var authEnvConfig = () => `import { publicEnv } from '@fluixi/start/env';
|
|
130
|
+
import { authFromEnv } from '@fluixi/oauth2/env';
|
|
131
|
+
|
|
132
|
+
${AUTH_READER}`;
|
|
133
|
+
var authServiceFile = (tokens) => `import { createAuthService, authModule } from '@fluixi/oauth2';
|
|
134
|
+
import { env } from '@fluixi/start/env';
|
|
135
|
+
${tokens === "browser" ? `import { auth as authEnv } from '../config/env.js';
|
|
136
|
+
` : ""}
|
|
137
|
+
export interface User {
|
|
138
|
+
sub: string;
|
|
139
|
+
email?: string;
|
|
140
|
+
name?: string;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const appUrl = env('APP_URL') ?? 'http://localhost:3000';
|
|
144
|
+
|
|
145
|
+
export const auth = createAuthService<User>({
|
|
146
|
+
${tokens === "browser" ? ` config: {
|
|
147
|
+
...authEnv.client,
|
|
148
|
+
tokens: 'browser',
|
|
149
|
+
redirectUri: \`\${appUrl}/callback\`,
|
|
150
|
+
postLogoutRedirectUri: \`\${appUrl}/login\`,
|
|
151
|
+
},` : ` // The tokens stay on the server behind an httpOnly cookie. The browser calls the routes
|
|
152
|
+
// auth.server.ts mounts under basePath and never sees a token.
|
|
153
|
+
config: {
|
|
154
|
+
tokens: 'server',
|
|
155
|
+
redirectUri: \`\${appUrl}/auth/callback\`,
|
|
156
|
+
postLogoutRedirectUri: \`\${appUrl}/login\`,
|
|
157
|
+
basePath: '/auth',
|
|
158
|
+
},`}
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
export const AuthModule = authModule(auth);
|
|
162
|
+
export const { protectedRoute, Protect, Can, useUser } = auth;
|
|
163
|
+
`;
|
|
164
|
+
var authServerFile = () => `import { authServerModule } from '@fluixi/oauth2/server';
|
|
165
|
+
import { publicEnv, serverEnv } from '@fluixi/start/env';
|
|
166
|
+
import { auth } from '../config/env.js';
|
|
167
|
+
|
|
168
|
+
// A function: serverEnv with required throws when a secret is unset, and it is only read
|
|
169
|
+
// when app.config.server.ts registers this.
|
|
170
|
+
export function authServer() {
|
|
171
|
+
const appUrl = publicEnv().APP_URL ?? 'http://localhost:3000';
|
|
172
|
+
return authServerModule({
|
|
173
|
+
...auth.client,
|
|
174
|
+
clientSecret: serverEnv('OIDC_CLIENT_SECRET', { required: true }),
|
|
175
|
+
cookieSecret: serverEnv('SESSION_SECRET', { required: true }),
|
|
176
|
+
redirectUri: \`\${appUrl}/auth/callback\`,
|
|
177
|
+
defaultReturnTo: '/',
|
|
178
|
+
secureCookie: appUrl.startsWith('https://'),
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
`;
|
|
182
|
+
var callbackRoute = (html) => html ? `import { html } from '@fluixi/core';
|
|
183
|
+
import { OAuthCallback } from '@fluixi/oauth2/callback';
|
|
184
|
+
|
|
185
|
+
export default OAuthCallback({
|
|
186
|
+
pending: () => html\`<p>Signing you in</p>\`,
|
|
187
|
+
failed: (reason) => html\`<p>\${reason}</p><a href="/login">Try again</a>\`,
|
|
188
|
+
});
|
|
189
|
+
` : `import { OAuthCallback } from '@fluixi/oauth2/callback';
|
|
190
|
+
|
|
191
|
+
export default OAuthCallback({
|
|
192
|
+
pending: () => <p>Signing you in</p>,
|
|
193
|
+
failed: (reason) => (
|
|
194
|
+
<>
|
|
195
|
+
<p>{reason}</p>
|
|
196
|
+
<a href="/login">Try again</a>
|
|
197
|
+
</>
|
|
198
|
+
),
|
|
199
|
+
});
|
|
200
|
+
`;
|
|
201
|
+
var loginRoute = (html) => html ? `import { html } from '@fluixi/core';
|
|
202
|
+
import { auth } from '../services/auth.js';
|
|
203
|
+
|
|
204
|
+
export default function Login() {
|
|
205
|
+
return html\`<button @click=\${() => auth.signIn({ returnTo: '/' })}>Sign in</button>\`;
|
|
206
|
+
}
|
|
207
|
+
` : `import { auth } from '../services/auth.js';
|
|
208
|
+
|
|
209
|
+
export default function Login() {
|
|
210
|
+
return <button onClick={() => auth.signIn({ returnTo: '/' })}>Sign in</button>;
|
|
211
|
+
}
|
|
212
|
+
`;
|
|
213
|
+
var betterAuthConfig = () => `import { betterAuth } from 'better-auth';
|
|
214
|
+
import { defineAuth } from '@fluixi/auth/server';
|
|
215
|
+
|
|
216
|
+
export type AppUser = { id: string; email: string; name?: string; role?: string };
|
|
217
|
+
|
|
218
|
+
// Better Auth owns the audited primitives; defineAuth registers it with @fluixi/auth.
|
|
219
|
+
export const auth = defineAuth<AppUser>(
|
|
220
|
+
betterAuth({
|
|
221
|
+
// database: <your adapter>,
|
|
222
|
+
emailAndPassword: { enabled: true },
|
|
223
|
+
// socialProviders: { google: { … }, github: { … } },
|
|
224
|
+
} as Parameters<typeof betterAuth>[0]),
|
|
225
|
+
);
|
|
226
|
+
`;
|
|
227
|
+
var betterAuthClient = () => `import { createAuthClient } from 'better-auth/client';
|
|
228
|
+
import { createAuthHooks } from '@fluixi/auth/client';
|
|
229
|
+
import type { AppUser } from './auth.config.js';
|
|
230
|
+
|
|
231
|
+
const client = createAuthClient({ baseURL: '/api/auth' });
|
|
232
|
+
|
|
233
|
+
// Or, to reuse an existing reactive session: createAuthHooks({ session: yourSessionStore }).
|
|
234
|
+
export const { useUser, useSession, useAuth, useSignIn, useSignOut, protectedRoute } =
|
|
235
|
+
createAuthHooks<AppUser>({ client, loginPath: '/login' });
|
|
236
|
+
`;
|
|
237
|
+
var betterAuthMiddleware = () => `import { defineMiddleware } from '@fluixi/start';
|
|
238
|
+
import { authMiddleware } from '@fluixi/auth/server';
|
|
239
|
+
import './auth.config.js';
|
|
240
|
+
|
|
241
|
+
export default defineMiddleware([authMiddleware()]);
|
|
242
|
+
`;
|
|
243
|
+
var betterAuthRoute = () => `import { mountAuthRoutes } from '@fluixi/auth/server';
|
|
244
|
+
import '../../auth.config.js';
|
|
245
|
+
|
|
246
|
+
const handler = mountAuthRoutes();
|
|
247
|
+
export const GET = handler;
|
|
248
|
+
export const POST = handler;
|
|
249
|
+
export default handler;
|
|
250
|
+
`;
|
|
251
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
252
|
+
0 && (module.exports = {
|
|
253
|
+
AUTH_READER,
|
|
254
|
+
PROVIDERS,
|
|
255
|
+
PROVIDER_VARS,
|
|
256
|
+
SERVER_ONLY,
|
|
257
|
+
apiRoute,
|
|
258
|
+
authEnvConfig,
|
|
259
|
+
authServerFile,
|
|
260
|
+
authServiceFile,
|
|
261
|
+
betterAuthClient,
|
|
262
|
+
betterAuthConfig,
|
|
263
|
+
betterAuthMiddleware,
|
|
264
|
+
betterAuthRoute,
|
|
265
|
+
callbackRoute,
|
|
266
|
+
camel,
|
|
267
|
+
envConfig,
|
|
268
|
+
guardedApiRoute,
|
|
269
|
+
loginRoute,
|
|
270
|
+
moduleFile,
|
|
271
|
+
serverFn,
|
|
272
|
+
serviceFile,
|
|
273
|
+
verifyServer
|
|
274
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/** What the env, module, service, api, server-fn and oauth2 generators write. */
|
|
2
|
+
export declare const camel: (name: string) => string;
|
|
3
|
+
export declare const envConfig: (names: string[]) => string;
|
|
4
|
+
export declare const moduleFile: (Name: string, name: string) => string;
|
|
5
|
+
export declare const serviceFile: (Name: string, name: string, server: boolean) => string;
|
|
6
|
+
export declare const apiRoute: () => string;
|
|
7
|
+
export declare const guardedApiRoute: (guardImport: string, role: string) => string;
|
|
8
|
+
export declare const verifyServer: () => string;
|
|
9
|
+
export declare const serverFn: (name: string) => string;
|
|
10
|
+
export declare const PROVIDERS: readonly ["oidc", "keycloak", "auth0", "okta", "google", "dex", "oauth2"];
|
|
11
|
+
export type ProviderName = (typeof PROVIDERS)[number];
|
|
12
|
+
/** Providers that refuse a public client, so tokens must stay on the server. */
|
|
13
|
+
export declare const SERVER_ONLY: Partial<Record<ProviderName, string>>;
|
|
14
|
+
/** The variables each preset needs beyond the common ones, with a placeholder. */
|
|
15
|
+
export declare const PROVIDER_VARS: Record<ProviderName, Record<string, string>>;
|
|
16
|
+
export declare const AUTH_READER = "// Provider, issuer, client and token mode from the AUTH_* variables in env.public. Moving to\n// another provider is a change to .env.\nexport const auth = authFromEnv(publicEnv(), { clientPrefix: '' });\n";
|
|
17
|
+
export declare const authEnvConfig: () => string;
|
|
18
|
+
export declare const authServiceFile: (tokens: "browser" | "server") => string;
|
|
19
|
+
export declare const authServerFile: () => string;
|
|
20
|
+
export declare const callbackRoute: (html: boolean) => "import { html } from '@fluixi/core';\nimport { OAuthCallback } from '@fluixi/oauth2/callback';\n\nexport default OAuthCallback({\n pending: () => html`<p>Signing you in</p>`,\n failed: (reason) => html`<p>${reason}</p><a href=\"/login\">Try again</a>`,\n});\n" | "import { OAuthCallback } from '@fluixi/oauth2/callback';\n\nexport default OAuthCallback({\n pending: () => <p>Signing you in</p>,\n failed: (reason) => (\n <>\n <p>{reason}</p>\n <a href=\"/login\">Try again</a>\n </>\n ),\n});\n";
|
|
21
|
+
export declare const loginRoute: (html: boolean) => "import { html } from '@fluixi/core';\nimport { auth } from '../services/auth.js';\n\nexport default function Login() {\n return html`<button @click=${() => auth.signIn({ returnTo: '/' })}>Sign in</button>`;\n}\n" | "import { auth } from '../services/auth.js';\n\nexport default function Login() {\n return <button onClick={() => auth.signIn({ returnTo: '/' })}>Sign in</button>;\n}\n";
|
|
22
|
+
export declare const betterAuthConfig: () => string;
|
|
23
|
+
export declare const betterAuthClient: () => string;
|
|
24
|
+
export declare const betterAuthMiddleware: () => string;
|
|
25
|
+
export declare const betterAuthRoute: () => string;
|
|
26
|
+
//# sourceMappingURL=templates.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"templates.d.ts","sourceRoot":"","sources":["../../src/scaffold/templates.ts"],"names":[],"mappings":"AAAA,iFAAiF;AAEjF,eAAO,MAAM,KAAK,GAAI,MAAM,MAAM,WACgD,CAAC;AAEnF,eAAO,MAAM,SAAS,GAAI,OAAO,MAAM,EAAE,WAYxC,CAAC;AAEF,eAAO,MAAM,UAAU,GAAI,MAAM,MAAM,EAAE,MAAM,MAAM,WAOpD,CAAC;AAEF,eAAO,MAAM,WAAW,GAAI,MAAM,MAAM,EAAE,MAAM,MAAM,EAAE,QAAQ,OAAO,WAatE,CAAC;AAEF,eAAO,MAAM,QAAQ,cAGpB,CAAC;AAEF,eAAO,MAAM,eAAe,GAAI,aAAa,MAAM,EAAE,MAAM,MAAM,WAQhE,CAAC;AAEF,eAAO,MAAM,YAAY,cAQxB,CAAC;AAEF,eAAO,MAAM,QAAQ,GAAI,MAAM,MAAM,WAKpC,CAAC;AAIF,eAAO,MAAM,SAAS,2EAA4E,CAAC;AACnG,MAAM,MAAM,YAAY,GAAG,CAAC,OAAO,SAAS,CAAC,CAAC,MAAM,CAAC,CAAC;AAEtD,gFAAgF;AAChF,eAAO,MAAM,WAAW,EAAE,OAAO,CAAC,MAAM,CAAC,YAAY,EAAE,MAAM,CAAC,CAG7D,CAAC;AAEF,kFAAkF;AAClF,eAAO,MAAM,aAAa,EAAE,MAAM,CAAC,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAYtE,CAAC;AAEF,eAAO,MAAM,WAAW,mNAGvB,CAAC;AAEF,eAAO,MAAM,aAAa,cAGX,CAAC;AAEhB,eAAO,MAAM,eAAe,GAAI,QAAQ,SAAS,GAAG,QAAQ,WAiC3D,CAAC;AAEF,eAAO,MAAM,cAAc,cAiB1B,CAAC;AAEF,eAAO,MAAM,aAAa,GAAI,MAAM,OAAO,0gBAqB1C,CAAC;AAEF,eAAO,MAAM,UAAU,GAAI,MAAM,OAAO,wYAcvC,CAAC;AAIF,eAAO,MAAM,gBAAgB,cAc5B,CAAC;AAEF,eAAO,MAAM,gBAAgB,cAU5B,CAAC;AAEF,eAAO,MAAM,oBAAoB,cAMhC,CAAC;AAEF,eAAO,MAAM,eAAe,cAQ3B,CAAC"}
|