create-authhero 0.33.0 โ†’ 0.35.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -86,6 +86,41 @@ try {
86
86
  );
87
87
  }
88
88
 
89
+ // Copy admin UI files from @authhero/react-admin package
90
+ const adminSourceDir = path.join(
91
+ __dirname,
92
+ "node_modules",
93
+ "@authhero",
94
+ "react-admin",
95
+ "dist",
96
+ );
97
+
98
+ if (fs.existsSync(adminSourceDir)) {
99
+ console.log("๐Ÿ“ฆ Copying admin UI assets...");
100
+ const adminTargetDir = path.join(targetDir, "admin");
101
+ copyDirectory(adminSourceDir, adminTargetDir);
102
+
103
+ // Inject runtime config into index.html
104
+ // Uses window.location.origin so the admin UI automatically points to its own server
105
+ const adminIndexPath = path.join(adminSourceDir, "index.html");
106
+ const adminHtml = fs.readFileSync(adminIndexPath, "utf-8")
107
+ .replace(/src="\.\/assets\//g, 'src="/admin/assets/')
108
+ .replace(/href="\.\/assets\//g, 'href="/admin/assets/');
109
+ const configScript = `<script>window.__AUTHHERO_ADMIN_CONFIG__={domain:window.location.origin,basePath:"/admin"}</script>`;
110
+ const injectedHtml = adminHtml.replace("</head>", configScript + "\n</head>");
111
+
112
+ // Write injected HTML to CDN assets (for direct /admin/ access)
113
+ fs.writeFileSync(path.join(adminTargetDir, "index.html"), injectedHtml);
114
+
115
+ // Write as TS module for worker to import (for SPA fallback on deep links)
116
+ const srcDir = path.join(__dirname, "src");
117
+ fs.writeFileSync(
118
+ path.join(srcDir, "admin-index-html.ts"),
119
+ `export default ${JSON.stringify(injectedHtml)};\n`,
120
+ );
121
+ console.log("โœ… Admin UI assets copied and configured");
122
+ }
123
+
89
124
  console.log(`โœ… Assets copied to ${targetDir}`);
90
125
  } catch (error) {
91
126
  console.error("โŒ Error copying assets:", error.message);
@@ -1,18 +1,18 @@
1
1
  #!/usr/bin/env node
2
- import { Command as I } from "commander";
2
+ import { Command as P } from "commander";
3
3
  import m from "inquirer";
4
- import a from "fs";
5
- import l from "path";
4
+ import s from "fs";
5
+ import i from "path";
6
6
  import { spawn as E } from "child_process";
7
- const T = new I(), p = {
7
+ const D = new P(), p = {
8
8
  local: {
9
9
  name: "Local (SQLite)",
10
10
  description: "Local development setup with SQLite database - great for getting started",
11
11
  templateDir: "local",
12
- packageJson: (o, e, r, n) => {
13
- const t = n ? "workspace:*" : "latest";
12
+ packageJson: (n, e, r, a, o) => {
13
+ const t = a ? "workspace:*" : "latest";
14
14
  return {
15
- name: o,
15
+ name: n,
16
16
  version: "1.0.0",
17
17
  type: "module",
18
18
  scripts: {
@@ -23,6 +23,7 @@ const T = new I(), p = {
23
23
  },
24
24
  dependencies: {
25
25
  "@authhero/kysely-adapter": t,
26
+ ...o && { "@authhero/react-admin": t },
26
27
  "@authhero/widget": t,
27
28
  "@hono/swagger-ui": "^0.5.0",
28
29
  "@hono/zod-openapi": "^0.19.0",
@@ -48,10 +49,10 @@ const T = new I(), p = {
48
49
  name: "Cloudflare Workers (D1)",
49
50
  description: "Cloudflare Workers setup with D1 database",
50
51
  templateDir: "cloudflare",
51
- packageJson: (o, e, r, n) => {
52
- const t = n ? "workspace:*" : "latest";
52
+ packageJson: (n, e, r, a, o) => {
53
+ const t = a ? "workspace:*" : "latest";
53
54
  return {
54
- name: o,
55
+ name: n,
55
56
  version: "1.0.0",
56
57
  type: "module",
57
58
  scripts: {
@@ -71,6 +72,7 @@ const T = new I(), p = {
71
72
  dependencies: {
72
73
  "@authhero/drizzle": t,
73
74
  "@authhero/kysely-adapter": t,
75
+ ...o && { "@authhero/react-admin": t },
74
76
  "@authhero/widget": t,
75
77
  "@hono/swagger-ui": "^0.5.0",
76
78
  "@hono/zod-openapi": "^0.19.0",
@@ -96,10 +98,10 @@ const T = new I(), p = {
96
98
  name: "AWS SST (Lambda + DynamoDB)",
97
99
  description: "Serverless AWS deployment with Lambda, DynamoDB, and SST",
98
100
  templateDir: "aws-sst",
99
- packageJson: (o, e, r, n) => {
100
- const t = n ? "workspace:*" : "latest";
101
+ packageJson: (n, e, r, a, o) => {
102
+ const t = a ? "workspace:*" : "latest";
101
103
  return {
102
- name: o,
104
+ name: n,
103
105
  version: "1.0.0",
104
106
  type: "module",
105
107
  scripts: {
@@ -111,6 +113,7 @@ const T = new I(), p = {
111
113
  },
112
114
  dependencies: {
113
115
  "@authhero/aws": t,
116
+ ...o && { "@authhero/react-admin": t },
114
117
  "@authhero/widget": t,
115
118
  "@aws-sdk/client-dynamodb": "^3.0.0",
116
119
  "@aws-sdk/lib-dynamodb": "^3.0.0",
@@ -133,27 +136,28 @@ const T = new I(), p = {
133
136
  seedFile: "seed.ts"
134
137
  }
135
138
  };
136
- function N(o, e) {
137
- a.readdirSync(o).forEach((n) => {
138
- const t = l.join(o, n), s = l.join(e, n);
139
- a.lstatSync(t).isDirectory() ? (a.mkdirSync(s, { recursive: !0 }), N(t, s)) : a.copyFileSync(t, s);
139
+ function N(n, e) {
140
+ s.readdirSync(n).forEach((a) => {
141
+ const o = i.join(n, a), t = i.join(e, a);
142
+ s.lstatSync(o).isDirectory() ? (s.mkdirSync(t, { recursive: !0 }), N(o, t)) : s.copyFileSync(o, t);
140
143
  });
141
144
  }
142
- function P(o, e = !1, r = "authhero-local") {
143
- const n = o ? "control_plane" : "main", t = o ? "Control Plane" : "Main", s = [
145
+ function R(n, e = !1, r = "authhero-local", a) {
146
+ const o = n ? "control_plane" : "main", t = n ? "Control Plane" : "Main", c = [
144
147
  "https://manage.authhero.net/auth-callback",
145
148
  "https://local.authhero.net/auth-callback",
146
149
  "http://localhost:5173/auth-callback",
147
- "https://localhost:3000/auth-callback"
148
- ], i = e ? [
150
+ "https://localhost:3000/auth-callback",
151
+ ...a ? ["https://localhost:3000/admin/auth-callback"] : []
152
+ ], d = e ? [
149
153
  `https://localhost.emobix.co.uk:8443/test/a/${r}/callback`,
150
154
  `https://localhost:8443/test/a/${r}/callback`
151
- ] : [], g = [...s, ...i], f = [
155
+ ] : [], f = [...c, ...d], h = [
152
156
  "https://manage.authhero.net",
153
157
  "https://local.authhero.net",
154
158
  "http://localhost:5173",
155
159
  "https://localhost:3000"
156
- ], v = e ? ["https://localhost:8443/", "https://localhost.emobix.co.uk:8443/"] : [], w = [...f, ...v], A = e ? `
160
+ ], C = e ? ["https://localhost:8443/", "https://localhost.emobix.co.uk:8443/"] : [], y = [...h, ...C], S = e ? `
157
161
  // Create OpenID Conformance Suite test clients and user
158
162
  console.log("Creating conformance test clients and user...");
159
163
 
@@ -171,7 +175,7 @@ function P(o, e = !1, r = "authhero-local") {
171
175
  ];
172
176
 
173
177
  try {
174
- await adapters.clients.create("${n}", {
178
+ await adapters.clients.create("${o}", {
175
179
  client_id: "conformance-test",
176
180
  client_secret: "conformanceTestSecret123",
177
181
  name: "Conformance Test Client",
@@ -189,7 +193,7 @@ function P(o, e = !1, r = "authhero-local") {
189
193
  }
190
194
 
191
195
  try {
192
- await adapters.clients.create("${n}", {
196
+ await adapters.clients.create("${o}", {
193
197
  client_id: "conformance-test2",
194
198
  client_secret: "conformanceTestSecret456",
195
199
  name: "Conformance Test Client 2",
@@ -209,7 +213,7 @@ function P(o, e = !1, r = "authhero-local") {
209
213
  // Create a conformance test user with ALL OIDC profile claims populated
210
214
  // This is required for OIDCC-5.4 (VerifyScopesReturnedInUserInfoClaims) test
211
215
  try {
212
- await adapters.users.create("${n}", {
216
+ await adapters.users.create("${o}", {
213
217
  user_id: \`\${USERNAME_PASSWORD_PROVIDER}|conformance-user\`,
214
218
  email: "conformance@example.com",
215
219
  email_verified: true,
@@ -244,7 +248,7 @@ function P(o, e = !1, r = "authhero-local") {
244
248
  try {
245
249
  const bcrypt = await import("bcryptjs");
246
250
  const hashedPassword = await bcrypt.hash("ConformanceTest123!", 10);
247
- await adapters.passwords.create("${n}", {
251
+ await adapters.passwords.create("${o}", {
248
252
  user_id: \`\${USERNAME_PASSWORD_PROVIDER}|conformance-user\`,
249
253
  password: hashedPassword,
250
254
  });
@@ -276,34 +280,82 @@ async function main() {
276
280
  await seed(adapters, {
277
281
  adminUsername,
278
282
  adminPassword,
279
- tenantId: "${n}",
283
+ tenantId: "${o}",
280
284
  tenantName: "${t}",
281
- isControlPlane: ${o},
282
- callbacks: ${JSON.stringify(g)},
283
- allowedLogoutUrls: ${JSON.stringify(w)},
285
+ isControlPlane: ${!!n},${n ? `
286
+ clientId: "default_client",` : ""}
287
+ callbacks: ${JSON.stringify(f)},
288
+ allowedLogoutUrls: ${JSON.stringify(y)},
284
289
  });
285
- ${A}
290
+ ${S}
286
291
  await db.destroy();
287
292
  }
288
293
 
289
294
  main().catch(console.error);
290
295
  `;
291
296
  }
292
- function R(o) {
293
- return o ? `import { Context } from "hono";
297
+ function U(n, e) {
298
+ const r = e ? `import fs from "fs";
299
+ ` : "", a = e ? `
300
+ const adminDistPath = path.resolve(
301
+ __dirname,
302
+ "../node_modules/@authhero/react-admin/dist",
303
+ );
304
+ const adminIndexPath = path.join(adminDistPath, "index.html");
305
+ ` : "", o = e ? `
306
+ // Add admin UI handler if the package is installed
307
+ if (fs.existsSync(adminIndexPath)) {
308
+ const issuer =
309
+ process.env.ISSUER || \`https://localhost:\${process.env.PORT || 3000}/\`;
310
+ const rawHtml = fs.readFileSync(adminIndexPath, "utf-8")
311
+ .replace(/src="\\.\\//g, 'src="/admin/')
312
+ .replace(/href="\\.\\//g, 'href="/admin/');
313
+ const configJson = JSON.stringify({
314
+ domain: issuer.replace(/\\/$/, ""),${n ? `
315
+ clientId: CONTROL_PLANE_CLIENT_ID,` : ""}
316
+ basePath: "/admin",
317
+ }).replace(/</g, "\\\\u003c");
318
+ configWithHandlers.adminIndexHtml = rawHtml.replace(
319
+ "</head>",
320
+ \`<script>window.__AUTHHERO_ADMIN_CONFIG__=\${configJson};<\/script>\\n</head>\`,
321
+ );
322
+ configWithHandlers.adminHandler = serveStatic({
323
+ root: adminDistPath,
324
+ rewriteRequestPath: (p: string) => p.replace("/admin", ""),
325
+ });
326
+ }
327
+ ` : "";
328
+ return n ? `import { Context } from "hono";
294
329
  import { swaggerUI } from "@hono/swagger-ui";
295
330
  import { AuthHeroConfig, DataAdapters } from "authhero";
296
331
  import { serveStatic } from "@hono/node-server/serve-static";
297
332
  import { initMultiTenant } from "@authhero/multi-tenancy";
333
+ import path from "path";
334
+ ${r}import { fileURLToPath } from "url";
298
335
 
336
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
337
+
338
+ const widgetPath = path.resolve(
339
+ __dirname,
340
+ "../node_modules/@authhero/widget/dist/authhero-widget",
341
+ );
342
+ ${a}
299
343
  // Control plane configuration
300
344
  const CONTROL_PLANE_TENANT_ID = "control_plane";
301
345
  const CONTROL_PLANE_CLIENT_ID = "default_client";
302
346
 
303
347
  export default function createApp(config: AuthHeroConfig & { dataAdapter: DataAdapters }) {
348
+ const configWithHandlers: AuthHeroConfig & { dataAdapter: DataAdapters } = {
349
+ ...config,
350
+ widgetHandler: serveStatic({
351
+ root: widgetPath,
352
+ rewriteRequestPath: (p) => p.replace("/u/widget", ""),
353
+ }),
354
+ };
355
+ ${o}
304
356
  // Initialize multi-tenant AuthHero - syncs resource servers, roles, and connections by default
305
357
  const { app } = initMultiTenant({
306
- ...config,
358
+ ...configWithHandlers,
307
359
  controlPlane: {
308
360
  tenantId: CONTROL_PLANE_TENANT_ID,
309
361
  clientId: CONTROL_PLANE_CLIENT_ID,
@@ -328,23 +380,7 @@ export default function createApp(config: AuthHeroConfig & { dataAdapter: DataAd
328
380
  controlPlaneTenant: CONTROL_PLANE_TENANT_ID,
329
381
  });
330
382
  })
331
- .get("/docs", swaggerUI({ url: "/api/v2/spec" }))
332
- // Serve widget assets from @authhero/widget package
333
- .get(
334
- "/u/widget/*",
335
- serveStatic({
336
- root: "./node_modules/@authhero/widget/dist/authhero-widget",
337
- rewriteRequestPath: (path) => path.replace("/u/widget", ""),
338
- }),
339
- )
340
- // Serve static assets (CSS, JS) from authhero package
341
- .get(
342
- "/u/*",
343
- serveStatic({
344
- root: "./node_modules/authhero/dist/assets/u",
345
- rewriteRequestPath: (path) => path.replace("/u", ""),
346
- }),
347
- );
383
+ .get("/docs", swaggerUI({ url: "/api/v2/spec" }));
348
384
 
349
385
  return app;
350
386
  }
@@ -352,9 +388,26 @@ export default function createApp(config: AuthHeroConfig & { dataAdapter: DataAd
352
388
  import { AuthHeroConfig, init } from "authhero";
353
389
  import { swaggerUI } from "@hono/swagger-ui";
354
390
  import { serveStatic } from "@hono/node-server/serve-static";
391
+ import path from "path";
392
+ ${r}import { fileURLToPath } from "url";
355
393
 
394
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
395
+
396
+ const widgetPath = path.resolve(
397
+ __dirname,
398
+ "../node_modules/@authhero/widget/dist/authhero-widget",
399
+ );
400
+ ${a}
356
401
  export default function createApp(config: AuthHeroConfig) {
357
- const { app } = init(config);
402
+ const configWithHandlers: AuthHeroConfig = {
403
+ ...config,
404
+ widgetHandler: serveStatic({
405
+ root: widgetPath,
406
+ rewriteRequestPath: (p) => p.replace("/u/widget", ""),
407
+ }),
408
+ };
409
+ ${o}
410
+ const { app } = init(configWithHandlers);
358
411
 
359
412
  app
360
413
  .onError((err, ctx) => {
@@ -371,29 +424,13 @@ export default function createApp(config: AuthHeroConfig) {
371
424
  status: "running",
372
425
  });
373
426
  })
374
- .get("/docs", swaggerUI({ url: "/api/v2/spec" }))
375
- // Serve widget assets from @authhero/widget package
376
- .get(
377
- "/u/widget/*",
378
- serveStatic({
379
- root: "./node_modules/@authhero/widget/dist/authhero-widget",
380
- rewriteRequestPath: (path) => path.replace("/u/widget", ""),
381
- }),
382
- )
383
- // Serve static assets (CSS, JS) from authhero package
384
- .get(
385
- "/u/*",
386
- serveStatic({
387
- root: "./node_modules/authhero/dist/assets/u",
388
- rewriteRequestPath: (path) => path.replace("/u", ""),
389
- }),
390
- );
427
+ .get("/docs", swaggerUI({ url: "/api/v2/spec" }));
391
428
 
392
429
  return app;
393
430
  }
394
431
  `;
395
432
  }
396
- function O(o) {
433
+ function O(n) {
397
434
  return `import { D1Dialect } from "kysely-d1";
398
435
  import { Kysely } from "kysely";
399
436
  import createAdapters from "@authhero/kysely-adapter";
@@ -420,9 +457,9 @@ export default {
420
457
  adminUsername,
421
458
  adminPassword,
422
459
  issuer,
423
- tenantId: "${o ? "control_plane" : "main"}",
424
- tenantName: "${o ? "Control Plane" : "Main"}",
425
- isControlPlane: ${o},
460
+ tenantId: "${n ? "control_plane" : "main"}",
461
+ tenantName: "${n ? "Control Plane" : "Main"}",
462
+ isControlPlane: ${!!n},
426
463
  });
427
464
 
428
465
  return new Response(
@@ -453,12 +490,15 @@ export default {
453
490
  };
454
491
  `;
455
492
  }
456
- function U(o) {
457
- return o ? `import { Context } from "hono";
493
+ function L(n, e) {
494
+ const r = e ? `import adminIndexHtml from "./admin-index-html";
495
+ ` : "", a = e ? ` adminIndexHtml,
496
+ ` : "";
497
+ return n ? `import { Context } from "hono";
458
498
  import { swaggerUI } from "@hono/swagger-ui";
459
499
  import { AuthHeroConfig, DataAdapters } from "authhero";
460
500
  import { initMultiTenant } from "@authhero/multi-tenancy";
461
-
501
+ ${r}
462
502
  // Control plane configuration
463
503
  const CONTROL_PLANE_TENANT_ID = "control_plane";
464
504
  const CONTROL_PLANE_CLIENT_ID = "default_client";
@@ -467,7 +507,7 @@ export default function createApp(config: AuthHeroConfig & { dataAdapter: DataAd
467
507
  // Initialize multi-tenant AuthHero - syncs resource servers, roles, and connections by default
468
508
  const { app } = initMultiTenant({
469
509
  ...config,
470
- controlPlane: {
510
+ ${a} controlPlane: {
471
511
  tenantId: CONTROL_PLANE_TENANT_ID,
472
512
  clientId: CONTROL_PLANE_CLIENT_ID,
473
513
  },
@@ -499,9 +539,11 @@ export default function createApp(config: AuthHeroConfig & { dataAdapter: DataAd
499
539
  import { cors } from "hono/cors";
500
540
  import { AuthHeroConfig, init } from "authhero";
501
541
  import { swaggerUI } from "@hono/swagger-ui";
502
-
542
+ ${r}
503
543
  export default function createApp(config: AuthHeroConfig) {
504
- const { app } = init(config);
544
+ const { app } = init({
545
+ ...config,
546
+ ${a} });
505
547
 
506
548
  // Enable CORS for all origins in development
507
549
  app.use("*", cors({
@@ -533,8 +575,8 @@ export default function createApp(config: AuthHeroConfig) {
533
575
  }
534
576
  `;
535
577
  }
536
- function j(o) {
537
- return o ? `import { Context } from "hono";
578
+ function j(n) {
579
+ return n ? `import { Context } from "hono";
538
580
  import { swaggerUI } from "@hono/swagger-ui";
539
581
  import { AuthHeroConfig, DataAdapters } from "authhero";
540
582
  import { initMultiTenant } from "@authhero/multi-tenancy";
@@ -639,7 +681,7 @@ export default function createApp(config: AppConfig) {
639
681
  }
640
682
  `;
641
683
  }
642
- function L(o) {
684
+ function $(n) {
643
685
  return `import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
644
686
  import { DynamoDBDocumentClient } from "@aws-sdk/lib-dynamodb";
645
687
  import createAdapters from "@authhero/aws";
@@ -668,9 +710,9 @@ async function main() {
668
710
  await seed(adapters, {
669
711
  adminUsername,
670
712
  adminPassword,
671
- tenantId: "${o ? "control_plane" : "main"}",
672
- tenantName: "${o ? "Control Plane" : "Main"}",
673
- isControlPlane: ${o},
713
+ tenantId: "${n ? "control_plane" : "main"}",
714
+ tenantName: "${n ? "Control Plane" : "Main"}",
715
+ isControlPlane: ${!!n},
674
716
  });
675
717
 
676
718
  console.log("โœ… Database seeded successfully!");
@@ -679,24 +721,24 @@ async function main() {
679
721
  main().catch(console.error);
680
722
  `;
681
723
  }
682
- function $(o, e) {
683
- const r = l.join(o, "src");
684
- a.writeFileSync(
685
- l.join(r, "app.ts"),
724
+ function H(n, e) {
725
+ const r = i.join(n, "src");
726
+ s.writeFileSync(
727
+ i.join(r, "app.ts"),
686
728
  j(e)
687
- ), a.writeFileSync(
688
- l.join(r, "seed.ts"),
689
- L(e)
729
+ ), s.writeFileSync(
730
+ i.join(r, "seed.ts"),
731
+ $(e)
690
732
  );
691
733
  }
692
- function k() {
734
+ function I() {
693
735
  console.log("\\n" + "โ”€".repeat(50)), console.log("๐Ÿ” AuthHero deployed to AWS!"), console.log("๐Ÿ“š Check SST output for your API URL"), console.log(
694
736
  "๐Ÿš€ Open your server URL /setup to complete initial setup"
695
737
  ), console.log("๐ŸŒ Portal available at https://local.authhero.net"), console.log("โ”€".repeat(50) + "\\n");
696
738
  }
697
- function H(o) {
698
- const e = l.join(o, ".github", "workflows");
699
- a.mkdirSync(e, { recursive: !0 });
739
+ function M(n) {
740
+ const e = i.join(n, ".github", "workflows");
741
+ s.mkdirSync(e, { recursive: !0 });
700
742
  const r = `name: Unit tests
701
743
 
702
744
  on: push
@@ -718,7 +760,7 @@ jobs:
718
760
 
719
761
  - run: npm run type-check
720
762
  - run: npm test
721
- `, n = `name: Deploy to Dev
763
+ `, a = `name: Deploy to Dev
722
764
 
723
765
  on:
724
766
  push:
@@ -754,7 +796,7 @@ jobs:
754
796
  with:
755
797
  apiToken: \${{ secrets.CLOUDFLARE_API_TOKEN }}
756
798
  command: deploy
757
- `, t = `name: Deploy to Production
799
+ `, o = `name: Deploy to Production
758
800
 
759
801
  on:
760
802
  release:
@@ -783,9 +825,9 @@ jobs:
783
825
  apiToken: \${{ secrets.PROD_CLOUDFLARE_API_TOKEN }}
784
826
  command: deploy --env production
785
827
  `;
786
- a.writeFileSync(l.join(e, "unit-tests.yml"), r), a.writeFileSync(l.join(e, "deploy-dev.yml"), n), a.writeFileSync(l.join(e, "release.yml"), t), console.log("\\n๐Ÿ“ฆ GitHub CI workflows created!");
828
+ s.writeFileSync(i.join(e, "unit-tests.yml"), r), s.writeFileSync(i.join(e, "deploy-dev.yml"), a), s.writeFileSync(i.join(e, "release.yml"), o), console.log("\\n๐Ÿ“ฆ GitHub CI workflows created!");
787
829
  }
788
- function M(o) {
830
+ function F(n) {
789
831
  const e = {
790
832
  branches: ["main"],
791
833
  plugins: [
@@ -794,39 +836,39 @@ function M(o) {
794
836
  "@semantic-release/github"
795
837
  ]
796
838
  };
797
- a.writeFileSync(
798
- l.join(o, ".releaserc.json"),
839
+ s.writeFileSync(
840
+ i.join(n, ".releaserc.json"),
799
841
  JSON.stringify(e, null, 2)
800
842
  );
801
- const r = l.join(o, "package.json"), n = JSON.parse(a.readFileSync(r, "utf-8"));
802
- n.devDependencies = {
803
- ...n.devDependencies,
843
+ const r = i.join(n, "package.json"), a = JSON.parse(s.readFileSync(r, "utf-8"));
844
+ a.devDependencies = {
845
+ ...a.devDependencies,
804
846
  "semantic-release": "^24.0.0"
805
- }, n.scripts = {
806
- ...n.scripts,
847
+ }, a.scripts = {
848
+ ...a.scripts,
807
849
  test: 'echo "No tests yet"',
808
850
  "type-check": "tsc --noEmit"
809
- }, a.writeFileSync(r, JSON.stringify(n, null, 2));
851
+ }, s.writeFileSync(r, JSON.stringify(a, null, 2));
810
852
  }
811
- function S(o, e) {
812
- return new Promise((r, n) => {
813
- const t = E(o, [], {
853
+ function b(n, e) {
854
+ return new Promise((r, a) => {
855
+ const o = E(n, [], {
814
856
  cwd: e,
815
857
  shell: !0,
816
858
  stdio: "inherit"
817
859
  });
818
- t.on("close", (s) => {
819
- s === 0 ? r() : n(new Error(`Command failed with exit code ${s}`));
820
- }), t.on("error", n);
860
+ o.on("close", (t) => {
861
+ t === 0 ? r() : a(new Error(`Command failed with exit code ${t}`));
862
+ }), o.on("error", a);
821
863
  });
822
864
  }
823
- function F(o, e) {
824
- const r = l.join(o, "src");
825
- a.writeFileSync(
826
- l.join(r, "app.ts"),
827
- U(e)
828
- ), a.writeFileSync(
829
- l.join(r, "seed.ts"),
865
+ function W(n, e, r) {
866
+ const a = i.join(n, "src");
867
+ s.writeFileSync(
868
+ i.join(a, "app.ts"),
869
+ L(e, r)
870
+ ), s.writeFileSync(
871
+ i.join(a, "seed.ts"),
830
872
  O(e)
831
873
  );
832
874
  }
@@ -837,29 +879,29 @@ function x() {
837
879
  ), console.log("๐ŸŒ Portal available at https://local.authhero.net"), console.log("โ”€".repeat(50) + `
838
880
  `);
839
881
  }
840
- function D() {
882
+ function k() {
841
883
  console.log(`
842
884
  ` + "โ”€".repeat(50)), console.log("โœ… Self-signed certificates generated with openssl"), console.log("โš ๏ธ You may need to trust the certificate in your browser"), console.log("๐Ÿ” AuthHero server running at https://localhost:3000"), console.log("๐Ÿ“š API documentation available at https://localhost:3000/docs"), console.log(
843
885
  "๐Ÿš€ Open https://localhost:3000/setup to complete initial setup"
844
886
  ), console.log("๐ŸŒ Portal available at https://local.authhero.net"), console.log("โ”€".repeat(50) + `
845
887
  `);
846
888
  }
847
- T.version("1.0.0").description("Create a new AuthHero project").argument("[project-name]", "name of the project").option("-t, --template <type>", "template type: local or cloudflare").option(
889
+ D.version("1.0.0").description("Create a new AuthHero project").argument("[project-name]", "name of the project").option("-t, --template <type>", "template type: local or cloudflare").option(
848
890
  "--package-manager <pm>",
849
891
  "package manager to use: npm, yarn, pnpm, or bun"
850
- ).option("--multi-tenant", "enable multi-tenant mode").option("--skip-install", "skip installing dependencies").option("--skip-migrate", "skip running database migrations").option("--skip-start", "skip starting the development server").option("--github-ci", "include GitHub CI workflows with semantic versioning").option("--conformance", "add OpenID conformance suite test clients").option(
892
+ ).option("--multi-tenant", "enable multi-tenant mode").option("--admin-ui", "include admin UI at /admin").option("--skip-install", "skip installing dependencies").option("--skip-migrate", "skip running database migrations").option("--skip-start", "skip starting the development server").option("--github-ci", "include GitHub CI workflows with semantic versioning").option("--conformance", "add OpenID conformance suite test clients").option(
851
893
  "--conformance-alias <alias>",
852
894
  "alias for conformance suite (default: authhero-local)"
853
895
  ).option(
854
896
  "--workspace",
855
897
  "use workspace:* dependencies for local monorepo development"
856
- ).option("-y, --yes", "skip all prompts and use defaults/provided options").action(async (o, e) => {
898
+ ).option("-y, --yes", "skip all prompts and use defaults/provided options").action(async (n, e) => {
857
899
  const r = e.yes === !0;
858
900
  console.log(`
859
901
  ๐Ÿ” Welcome to AuthHero!
860
902
  `);
861
- let n = o;
862
- n || (r ? (n = "auth-server", console.log(`Using default project name: ${n}`)) : n = (await m.prompt([
903
+ let a = n;
904
+ a || (r ? (a = "auth-server", console.log(`Using default project name: ${a}`)) : a = (await m.prompt([
863
905
  {
864
906
  type: "input",
865
907
  name: "projectName",
@@ -868,10 +910,10 @@ T.version("1.0.0").description("Create a new AuthHero project").argument("[proje
868
910
  validate: (u) => u !== "" || "Project name cannot be empty"
869
911
  }
870
912
  ])).projectName);
871
- const t = l.join(process.cwd(), n);
872
- a.existsSync(t) && (console.error(`โŒ Project "${n}" already exists.`), process.exit(1));
873
- let s;
874
- e.template ? (["local", "cloudflare", "aws-sst"].includes(e.template) || (console.error(`โŒ Invalid template: ${e.template}`), console.error("Valid options: local, cloudflare, aws-sst"), process.exit(1)), s = e.template, console.log(`Using template: ${p[s].name}`)) : s = (await m.prompt([
913
+ const o = i.join(process.cwd(), a);
914
+ s.existsSync(o) && (console.error(`โŒ Project "${a}" already exists.`), process.exit(1));
915
+ let t;
916
+ e.template ? (["local", "cloudflare", "aws-sst"].includes(e.template) || (console.error(`โŒ Invalid template: ${e.template}`), console.error("Valid options: local, cloudflare, aws-sst"), process.exit(1)), t = e.template, console.log(`Using template: ${p[t].name}`)) : t = (await m.prompt([
875
917
  {
876
918
  type: "list",
877
919
  name: "setupType",
@@ -898,64 +940,73 @@ T.version("1.0.0").description("Create a new AuthHero project").argument("[proje
898
940
  ]
899
941
  }
900
942
  ])).setupType;
901
- let i;
902
- e.multiTenant !== void 0 ? (i = e.multiTenant, console.log(`Multi-tenant mode: ${i ? "enabled" : "disabled"}`)) : r ? i = !1 : i = (await m.prompt([
943
+ let c;
944
+ e.multiTenant !== void 0 ? c = e.multiTenant : r ? c = !1 : c = (await m.prompt([
903
945
  {
904
946
  type: "confirm",
905
947
  name: "multiTenant",
906
- message: `Would you like to enable multi-tenant mode?
907
- (Allows managing multiple tenants from a control plane)`,
948
+ message: "Would you like to enable multi-tenant mode?",
908
949
  default: !1
909
950
  }
910
- ])).multiTenant;
911
- const g = e.conformance || !1, f = e.conformanceAlias || "authhero-local";
912
- g && console.log(
913
- `OpenID Conformance Suite: enabled (alias: ${f})`
951
+ ])).multiTenant, c && console.log("Multi-tenant mode: enabled");
952
+ let d = !1;
953
+ (t === "local" || t === "cloudflare") && (e.adminUi !== void 0 ? d = e.adminUi : r ? d = !0 : d = (await m.prompt([
954
+ {
955
+ type: "confirm",
956
+ name: "adminUi",
957
+ message: "Would you like to include the admin UI at /admin?",
958
+ default: !0
959
+ }
960
+ ])).adminUi, d && console.log("Admin UI: enabled (available at /admin)"));
961
+ const f = e.conformance || !1, h = e.conformanceAlias || "authhero-local";
962
+ f && console.log(
963
+ `OpenID Conformance Suite: enabled (alias: ${h})`
914
964
  );
915
- const v = e.workspace || !1;
916
- v && console.log("Workspace mode: enabled (using workspace:* dependencies)");
917
- const w = p[s];
918
- a.mkdirSync(t, { recursive: !0 }), a.writeFileSync(
919
- l.join(t, "package.json"),
965
+ const C = e.workspace || !1;
966
+ C && console.log("Workspace mode: enabled (using workspace:* dependencies)");
967
+ const y = p[t];
968
+ s.mkdirSync(o, { recursive: !0 }), s.writeFileSync(
969
+ i.join(o, "package.json"),
920
970
  JSON.stringify(
921
- w.packageJson(n, i, g, v),
971
+ y.packageJson(a, c, f, C, d),
922
972
  null,
923
973
  2
924
974
  )
925
975
  );
926
- const A = w.templateDir, b = l.join(
976
+ const S = y.templateDir, _ = i.join(
927
977
  import.meta.url.replace("file://", "").replace("/create-authhero.js", ""),
928
- A
978
+ S
929
979
  );
930
- if (a.existsSync(b) ? N(b, t) : (console.error(`โŒ Template directory not found: ${b}`), process.exit(1)), s === "cloudflare" && F(t, i), s === "cloudflare") {
931
- const c = l.join(t, "wrangler.toml"), u = l.join(t, "wrangler.local.toml");
932
- a.existsSync(c) && a.copyFileSync(c, u);
933
- const d = l.join(t, ".dev.vars.example"), h = l.join(t, ".dev.vars");
934
- a.existsSync(d) && a.copyFileSync(d, h), console.log(
980
+ if (s.existsSync(_) ? N(_, o) : (console.error(`โŒ Template directory not found: ${_}`), process.exit(1)), t === "cloudflare" && W(o, c, d), t === "cloudflare") {
981
+ const l = i.join(o, "wrangler.toml"), u = i.join(o, "wrangler.local.toml");
982
+ s.existsSync(l) && s.copyFileSync(l, u);
983
+ const g = i.join(o, ".dev.vars.example"), w = i.join(o, ".dev.vars");
984
+ s.existsSync(g) && s.copyFileSync(g, w), console.log(
935
985
  "๐Ÿ“ Created wrangler.local.toml and .dev.vars for local development"
936
986
  );
937
987
  }
938
- let C = !1;
939
- if (s === "cloudflare" && (e.githubCi !== void 0 ? (C = e.githubCi, C && console.log("Including GitHub CI workflows with semantic versioning")) : r || (C = (await m.prompt([
988
+ let A = !1;
989
+ if (t === "cloudflare" && (e.githubCi !== void 0 ? (A = e.githubCi, A && console.log("Including GitHub CI workflows with semantic versioning")) : r || (A = (await m.prompt([
940
990
  {
941
991
  type: "confirm",
942
992
  name: "includeGithubCi",
943
993
  message: "Would you like to include GitHub CI with semantic versioning?",
944
994
  default: !1
945
995
  }
946
- ])).includeGithubCi), C && (H(t), M(t))), s === "local") {
947
- const c = P(
948
- i,
949
- g,
950
- f
996
+ ])).includeGithubCi), A && (M(o), F(o))), t === "local") {
997
+ const l = R(
998
+ c,
999
+ f,
1000
+ h,
1001
+ d
951
1002
  );
952
- a.writeFileSync(l.join(t, "src/seed.ts"), c);
953
- const u = R(i);
954
- a.writeFileSync(l.join(t, "src/app.ts"), u);
1003
+ s.writeFileSync(i.join(o, "src/seed.ts"), l);
1004
+ const u = U(c, d);
1005
+ s.writeFileSync(i.join(o, "src/app.ts"), u);
955
1006
  }
956
- if (s === "aws-sst" && $(t, i), g) {
957
- const c = {
958
- alias: f,
1007
+ if (t === "aws-sst" && H(o, c), f) {
1008
+ const l = {
1009
+ alias: h,
959
1010
  description: "AuthHero Conformance Test",
960
1011
  server: {
961
1012
  discoveryUrl: "http://host.docker.internal:3000/.well-known/openid-configuration"
@@ -972,32 +1023,32 @@ T.version("1.0.0").description("Create a new AuthHero project").argument("[proje
972
1023
  resourceUrl: "http://host.docker.internal:3000/userinfo"
973
1024
  }
974
1025
  };
975
- a.writeFileSync(
976
- l.join(t, "conformance-config.json"),
977
- JSON.stringify(c, null, 2)
1026
+ s.writeFileSync(
1027
+ i.join(o, "conformance-config.json"),
1028
+ JSON.stringify(l, null, 2)
978
1029
  ), console.log(
979
1030
  "๐Ÿ“ Created conformance-config.json for OpenID Conformance Suite"
980
1031
  );
981
1032
  }
982
- const _ = i ? "multi-tenant" : "single-tenant";
1033
+ const T = c ? "multi-tenant" : "single-tenant";
983
1034
  console.log(
984
1035
  `
985
- โœ… Project "${n}" has been created with ${w.name} (${_}) setup!
1036
+ โœ… Project "${a}" has been created with ${y.name} (${T}) setup!
986
1037
  `
987
1038
  );
988
- let y;
989
- if (e.skipInstall ? y = !1 : r ? y = !0 : y = (await m.prompt([
1039
+ let v;
1040
+ if (e.skipInstall ? v = !1 : r ? v = !0 : v = (await m.prompt([
990
1041
  {
991
1042
  type: "confirm",
992
1043
  name: "shouldInstall",
993
1044
  message: "Would you like to install dependencies now?",
994
1045
  default: !0
995
1046
  }
996
- ])).shouldInstall, y) {
997
- let c;
1047
+ ])).shouldInstall, v) {
1048
+ let l;
998
1049
  e.packageManager ? (["npm", "yarn", "pnpm", "bun"].includes(e.packageManager) || (console.error(
999
1050
  `โŒ Invalid package manager: ${e.packageManager}`
1000
- ), console.error("Valid options: npm, yarn, pnpm, bun"), process.exit(1)), c = e.packageManager) : r ? c = "pnpm" : c = (await m.prompt([
1051
+ ), console.error("Valid options: npm, yarn, pnpm, bun"), process.exit(1)), l = e.packageManager) : r ? l = "pnpm" : l = (await m.prompt([
1001
1052
  {
1002
1053
  type: "list",
1003
1054
  name: "packageManager",
@@ -1011,65 +1062,65 @@ T.version("1.0.0").description("Create a new AuthHero project").argument("[proje
1011
1062
  default: "pnpm"
1012
1063
  }
1013
1064
  ])).packageManager, console.log(`
1014
- ๐Ÿ“ฆ Installing dependencies with ${c}...
1065
+ ๐Ÿ“ฆ Installing dependencies with ${l}...
1015
1066
  `);
1016
1067
  try {
1017
- const u = c === "pnpm" ? "pnpm install --ignore-workspace" : `${c} install`;
1018
- if (await S(u, t), s === "local" && (console.log(`
1068
+ const u = l === "pnpm" ? "pnpm install --ignore-workspace" : `${l} install`;
1069
+ if (await b(u, o), t === "local" && (console.log(`
1019
1070
  ๐Ÿ”ง Building native modules...
1020
- `), await S("npm rebuild better-sqlite3", t)), console.log(`
1071
+ `), await b("npm rebuild better-sqlite3", o)), console.log(`
1021
1072
  โœ… Dependencies installed successfully!
1022
- `), (s === "local" || s === "cloudflare") && !e.skipMigrate) {
1023
- let h;
1024
- r ? h = !0 : h = (await m.prompt([
1073
+ `), (t === "local" || t === "cloudflare") && !e.skipMigrate) {
1074
+ let w;
1075
+ r ? w = !0 : w = (await m.prompt([
1025
1076
  {
1026
1077
  type: "confirm",
1027
1078
  name: "shouldMigrate",
1028
1079
  message: "Would you like to run database migrations?",
1029
1080
  default: !0
1030
1081
  }
1031
- ])).shouldMigrate, h && (console.log(`
1082
+ ])).shouldMigrate, w && (console.log(`
1032
1083
  ๐Ÿ”„ Running migrations...
1033
- `), await S(`${c} run migrate`, t));
1084
+ `), await b(`${l} run migrate`, o));
1034
1085
  }
1035
- let d;
1036
- e.skipStart || r ? d = !1 : d = (await m.prompt([
1086
+ let g;
1087
+ e.skipStart || r ? g = !1 : g = (await m.prompt([
1037
1088
  {
1038
1089
  type: "confirm",
1039
1090
  name: "shouldStart",
1040
1091
  message: "Would you like to start the development server?",
1041
1092
  default: !0
1042
1093
  }
1043
- ])).shouldStart, d && (s === "cloudflare" ? x() : s === "aws-sst" ? k() : D(), console.log(`๐Ÿš€ Starting development server...
1044
- `), await S(`${c} run dev`, t)), r && !d && (console.log(`
1094
+ ])).shouldStart, g && (t === "cloudflare" ? x() : t === "aws-sst" ? I() : k(), console.log(`๐Ÿš€ Starting development server...
1095
+ `), await b(`${l} run dev`, o)), r && !g && (console.log(`
1045
1096
  โœ… Setup complete!`), console.log(`
1046
- To start the development server:`), console.log(` cd ${n}`), console.log(" npm run dev"), s === "cloudflare" ? x() : s === "aws-sst" ? k() : D());
1097
+ To start the development server:`), console.log(` cd ${a}`), console.log(" npm run dev"), t === "cloudflare" ? x() : t === "aws-sst" ? I() : k());
1047
1098
  } catch (u) {
1048
1099
  console.error(`
1049
1100
  โŒ An error occurred:`, u), process.exit(1);
1050
1101
  }
1051
1102
  }
1052
- y || (console.log("Next steps:"), console.log(` cd ${n}`), s === "local" ? (console.log(" npm install"), console.log(" npm run migrate"), console.log(" npm run dev"), console.log(
1103
+ v || (console.log("Next steps:"), console.log(` cd ${a}`), t === "local" ? (console.log(" npm install"), console.log(" npm run migrate"), console.log(" npm run dev"), console.log(
1053
1104
  `
1054
1105
  Open https://localhost:3000/setup to complete initial setup`
1055
- )) : s === "cloudflare" ? (console.log(" npm install"), console.log(
1106
+ )) : t === "cloudflare" ? (console.log(" npm install"), console.log(
1056
1107
  " npm run migrate # or npm run db:migrate:remote for production"
1057
1108
  ), console.log(" npm run dev # or npm run dev:remote for production"), console.log(
1058
1109
  `
1059
1110
  Open https://localhost:3000/setup to complete initial setup`
1060
- )) : s === "aws-sst" && (console.log(" npm install"), console.log(" npm run dev # Deploys to AWS in development mode"), console.log(
1111
+ )) : t === "aws-sst" && (console.log(" npm install"), console.log(" npm run dev # Deploys to AWS in development mode"), console.log(
1061
1112
  `
1062
1113
  Open your server URL /setup to complete initial setup`
1063
1114
  )), console.log(`
1064
- Server will be available at: https://localhost:3000`), console.log("Portal available at: https://local.authhero.net"), g && (console.log(`
1115
+ Server will be available at: https://localhost:3000`), console.log("Portal available at: https://local.authhero.net"), f && (console.log(`
1065
1116
  ๐Ÿงช OpenID Conformance Suite Testing:`), console.log(
1066
1117
  " 1. Clone and start the conformance suite (if not already running):"
1067
1118
  ), console.log(
1068
1119
  " git clone https://gitlab.com/openid/conformance-suite.git"
1069
1120
  ), console.log(" cd conformance-suite && mvn clean package"), console.log(" docker-compose up -d"), console.log(" 2. Open https://localhost.emobix.co.uk:8443"), console.log(
1070
1121
  " 3. Create a test plan and use conformance-config.json for settings"
1071
- ), console.log(` 4. Use alias: ${f}`)), console.log(`
1122
+ ), console.log(` 4. Use alias: ${h}`)), console.log(`
1072
1123
  For more information, visit: https://authhero.net/docs
1073
1124
  `));
1074
1125
  });
1075
- T.parse(process.argv);
1126
+ D.parse(process.argv);
@@ -3,6 +3,7 @@ import { AuthHeroConfig, init } from "authhero";
3
3
  import { swaggerUI } from "@hono/swagger-ui";
4
4
  import { serveStatic } from "@hono/node-server/serve-static";
5
5
  import path from "path";
6
+ import fs from "fs";
6
7
  import { fileURLToPath } from "url";
7
8
 
8
9
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
@@ -13,9 +14,15 @@ const widgetPath = path.resolve(
13
14
  "../node_modules/@authhero/widget/dist/authhero-widget",
14
15
  );
15
16
 
17
+ const adminDistPath = path.resolve(
18
+ __dirname,
19
+ "../node_modules/@authhero/react-admin/dist",
20
+ );
21
+ const adminIndexPath = path.join(adminDistPath, "index.html");
22
+
16
23
  export default function createApp(config: AuthHeroConfig) {
17
- // Configure widget handler before init() to serve widget files at /u/widget/*
18
- const configWithWidget: AuthHeroConfig = {
24
+ // Configure widget and admin handlers before init()
25
+ const configWithHandlers: AuthHeroConfig = {
19
26
  ...config,
20
27
  widgetHandler: serveStatic({
21
28
  root: widgetPath,
@@ -23,7 +30,28 @@ export default function createApp(config: AuthHeroConfig) {
23
30
  }),
24
31
  };
25
32
 
26
- const { app } = init(configWithWidget);
33
+ // Add admin UI handler if the package is installed
34
+ if (fs.existsSync(adminIndexPath)) {
35
+ const issuer =
36
+ process.env.ISSUER || `https://localhost:${process.env.PORT || 3000}/`;
37
+ const rawHtml = fs.readFileSync(adminIndexPath, "utf-8")
38
+ .replace(/src="\.\/assets\//g, 'src="/admin/assets/')
39
+ .replace(/href="\.\/assets\//g, 'href="/admin/assets/');
40
+ const configJson = JSON.stringify({
41
+ domain: issuer.replace(/\/$/, ""),
42
+ basePath: "/admin",
43
+ }).replace(/</g, "\\u003c");
44
+ configWithHandlers.adminIndexHtml = rawHtml.replace(
45
+ "</head>",
46
+ `<script>window.__AUTHHERO_ADMIN_CONFIG__=${configJson};</script>\n</head>`,
47
+ );
48
+ configWithHandlers.adminHandler = serveStatic({
49
+ root: adminDistPath,
50
+ rewriteRequestPath: (p: string) => p.replace("/admin", ""),
51
+ });
52
+ }
53
+
54
+ const { app } = init(configWithHandlers);
27
55
 
28
56
  app
29
57
  .get("/", async (ctx: Context) => {
@@ -90,6 +90,7 @@ const app = createApp({
90
90
  "https://manage.authhero.net",
91
91
  "https://local.authhero.net",
92
92
  "http://localhost:5173",
93
+ "https://localhost:5173",
93
94
  ],
94
95
  });
95
96
 
package/package.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "type": "git",
6
6
  "url": "https://github.com/markusahlstrand/authhero"
7
7
  },
8
- "version": "0.33.0",
8
+ "version": "0.35.0",
9
9
  "type": "module",
10
10
  "main": "dist/create-authhero.js",
11
11
  "bin": {
@@ -29,6 +29,7 @@
29
29
  },
30
30
  "scripts": {
31
31
  "build": "tsc && vite build",
32
+ "dev": "pnpm build && rm -rf auth-server && node dist/create-authhero.js auth-server --workspace --skip-install --skip-start && pnpm -w install --force --filter auth-server... && pnpm --filter auth-server migrate && pnpm --filter auth-server dev",
32
33
  "start": "pnpm build && node dist/create-authhero.js"
33
34
  }
34
35
  }