create-authhero 0.22.0 → 0.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/aws-sst/README.md +142 -0
- package/dist/aws-sst/copy-assets.js +61 -0
- package/dist/aws-sst/src/index.ts +77 -0
- package/dist/aws-sst/src/types.ts +5 -0
- package/dist/aws-sst/sst.config.ts +82 -0
- package/dist/aws-sst/tsconfig.json +16 -0
- package/dist/create-authhero.js +311 -107
- package/package.json +1 -1
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# AuthHero - AWS SST (Lambda + DynamoDB)
|
|
2
|
+
|
|
3
|
+
A serverless AuthHero deployment using [SST](https://sst.dev) with AWS Lambda and DynamoDB.
|
|
4
|
+
|
|
5
|
+
## Prerequisites
|
|
6
|
+
|
|
7
|
+
- Node.js 18+
|
|
8
|
+
- AWS CLI configured with credentials
|
|
9
|
+
- An AWS account
|
|
10
|
+
|
|
11
|
+
## Setup
|
|
12
|
+
|
|
13
|
+
1. **Install dependencies**
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
2. **Start development mode**
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm run dev
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
This will:
|
|
26
|
+
- Deploy to your AWS account in development mode
|
|
27
|
+
- Create a DynamoDB table
|
|
28
|
+
- Start a Lambda function with live reloading
|
|
29
|
+
- Output your API URL
|
|
30
|
+
|
|
31
|
+
3. **Seed the database**
|
|
32
|
+
|
|
33
|
+
After the dev server starts, seed your database:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
# Set your admin credentials
|
|
37
|
+
export ADMIN_EMAIL=admin@example.com
|
|
38
|
+
export ADMIN_PASSWORD=your-secure-password
|
|
39
|
+
|
|
40
|
+
npm run seed
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Commands
|
|
44
|
+
|
|
45
|
+
| Command | Description |
|
|
46
|
+
|---------|-------------|
|
|
47
|
+
| `npm run dev` | Start SST in development mode |
|
|
48
|
+
| `npm run deploy` | Deploy to production |
|
|
49
|
+
| `npm run remove` | Remove all deployed resources |
|
|
50
|
+
| `npm run seed` | Seed the database with initial data |
|
|
51
|
+
|
|
52
|
+
## Project Structure
|
|
53
|
+
|
|
54
|
+
```text
|
|
55
|
+
├── sst.config.ts # SST configuration (Lambda, DynamoDB, API Gateway)
|
|
56
|
+
├── src/
|
|
57
|
+
│ ├── index.ts # Lambda handler
|
|
58
|
+
│ ├── app.ts # AuthHero app configuration
|
|
59
|
+
│ └── seed.ts # Database seeding script
|
|
60
|
+
├── copy-assets.js # Script to copy widget assets for Lambda
|
|
61
|
+
└── package.json
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Architecture
|
|
65
|
+
|
|
66
|
+
- **Lambda Function**: Runs the AuthHero Hono application
|
|
67
|
+
- **DynamoDB**: Single-table design for all AuthHero data
|
|
68
|
+
- **API Gateway**: HTTP API with custom domain support
|
|
69
|
+
- **S3 Bucket**: Serves widget assets (CSS, JS)
|
|
70
|
+
|
|
71
|
+
## Environment Variables
|
|
72
|
+
|
|
73
|
+
Set these in SST or AWS Systems Manager:
|
|
74
|
+
|
|
75
|
+
| Variable | Description |
|
|
76
|
+
|----------|-------------|
|
|
77
|
+
| `TABLE_NAME` | DynamoDB table name (auto-set by SST) |
|
|
78
|
+
| `WIDGET_URL` | URL to widget assets (auto-set by SST) |
|
|
79
|
+
| `ADMIN_EMAIL` | Admin user email (required for initial seeding) |
|
|
80
|
+
| `ADMIN_PASSWORD` | Admin user password (required for initial seeding) |
|
|
81
|
+
|
|
82
|
+
## Widget Assets
|
|
83
|
+
|
|
84
|
+
Widget assets are served from an S3 bucket with CloudFront. SST automatically:
|
|
85
|
+
1. Copies assets from `node_modules/authhero/dist/assets`
|
|
86
|
+
2. Uploads them to S3
|
|
87
|
+
3. Creates a CloudFront distribution
|
|
88
|
+
4. Sets the `WIDGET_URL` environment variable
|
|
89
|
+
|
|
90
|
+
## Custom Domain
|
|
91
|
+
|
|
92
|
+
To use a custom domain, update `sst.config.ts`:
|
|
93
|
+
|
|
94
|
+
```typescript
|
|
95
|
+
const api = new sst.aws.ApiGatewayV2("AuthHeroApi", {
|
|
96
|
+
domain: "auth.yourdomain.com",
|
|
97
|
+
});
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Production Deployment
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
npm run deploy -- --stage production
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Costs
|
|
107
|
+
|
|
108
|
+
Estimated monthly costs for moderate usage:
|
|
109
|
+
|
|
110
|
+
| Service | Free Tier | After Free Tier |
|
|
111
|
+
|---------|-----------|-----------------|
|
|
112
|
+
| Lambda | 1M requests/month | $0.20/1M requests |
|
|
113
|
+
| DynamoDB | 25 WCU/RCU | ~$0.25/1M requests |
|
|
114
|
+
| API Gateway | 1M requests/month | $1.00/1M requests |
|
|
115
|
+
| S3 + CloudFront | 1GB + 50GB | ~$5/month |
|
|
116
|
+
|
|
117
|
+
## Troubleshooting
|
|
118
|
+
|
|
119
|
+
### Lambda timeout
|
|
120
|
+
|
|
121
|
+
Increase timeout in `sst.config.ts`:
|
|
122
|
+
|
|
123
|
+
```typescript
|
|
124
|
+
new sst.aws.Function("AuthHero", {
|
|
125
|
+
timeout: "30 seconds",
|
|
126
|
+
});
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### DynamoDB throttling
|
|
130
|
+
|
|
131
|
+
Enable auto-scaling or increase provisioned capacity in `sst.config.ts`.
|
|
132
|
+
|
|
133
|
+
### Widget not loading
|
|
134
|
+
|
|
135
|
+
Ensure the S3 bucket and CloudFront are properly configured. Check browser console for CORS errors.
|
|
136
|
+
|
|
137
|
+
## Learn More
|
|
138
|
+
|
|
139
|
+
- [SST Documentation](https://sst.dev/docs)
|
|
140
|
+
- [AuthHero Documentation](https://authhero.net/docs)
|
|
141
|
+
- [AWS Lambda](https://docs.aws.amazon.com/lambda/)
|
|
142
|
+
- [DynamoDB](https://docs.aws.amazon.com/dynamodb/)
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Copy AuthHero widget assets for Lambda deployment
|
|
5
|
+
*
|
|
6
|
+
* This script copies widget assets from node_modules to dist/assets
|
|
7
|
+
* so they can be uploaded to S3 and served via CloudFront.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import fs from "fs";
|
|
11
|
+
import path from "path";
|
|
12
|
+
import { fileURLToPath } from "url";
|
|
13
|
+
|
|
14
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
15
|
+
const __dirname = path.dirname(__filename);
|
|
16
|
+
|
|
17
|
+
function copyDirectory(src, dest) {
|
|
18
|
+
if (!fs.existsSync(dest)) {
|
|
19
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
23
|
+
|
|
24
|
+
for (const entry of entries) {
|
|
25
|
+
const srcPath = path.join(src, entry.name);
|
|
26
|
+
const destPath = path.join(dest, entry.name);
|
|
27
|
+
|
|
28
|
+
if (entry.isDirectory()) {
|
|
29
|
+
copyDirectory(srcPath, destPath);
|
|
30
|
+
} else {
|
|
31
|
+
fs.copyFileSync(srcPath, destPath);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Source and destination paths
|
|
37
|
+
const authHeroAssets = path.join(__dirname, "node_modules/authhero/dist/assets");
|
|
38
|
+
const widgetSource = path.join(
|
|
39
|
+
__dirname,
|
|
40
|
+
"node_modules/@authhero/widget/dist/authhero-widget"
|
|
41
|
+
);
|
|
42
|
+
const targetDir = path.join(__dirname, "dist/assets");
|
|
43
|
+
const widgetTarget = path.join(targetDir, "u/widget");
|
|
44
|
+
|
|
45
|
+
// Copy authhero assets
|
|
46
|
+
if (fs.existsSync(authHeroAssets)) {
|
|
47
|
+
console.log("📦 Copying AuthHero assets...");
|
|
48
|
+
copyDirectory(authHeroAssets, targetDir);
|
|
49
|
+
} else {
|
|
50
|
+
console.log("⚠️ AuthHero assets not found at:", authHeroAssets);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Copy widget from @authhero/widget package
|
|
54
|
+
if (fs.existsSync(widgetSource)) {
|
|
55
|
+
console.log("📦 Copying widget assets...");
|
|
56
|
+
copyDirectory(widgetSource, widgetTarget);
|
|
57
|
+
} else {
|
|
58
|
+
console.log("⚠️ Widget assets not found at:", widgetSource);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
console.log("✅ Assets copied to dist/assets");
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { handle } from "hono/aws-lambda";
|
|
2
|
+
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
|
|
3
|
+
import { DynamoDBDocumentClient } from "@aws-sdk/lib-dynamodb";
|
|
4
|
+
import createAdapters from "@authhero/aws";
|
|
5
|
+
import createApp from "./app";
|
|
6
|
+
import type { APIGatewayProxyEventV2, Context } from "aws-lambda";
|
|
7
|
+
|
|
8
|
+
// Validate required environment variables
|
|
9
|
+
if (!process.env.TABLE_NAME) {
|
|
10
|
+
throw new Error("TABLE_NAME environment variable is required");
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// Initialize DynamoDB client outside handler for connection reuse
|
|
14
|
+
const client = new DynamoDBClient({});
|
|
15
|
+
const docClient = DynamoDBDocumentClient.from(client, {
|
|
16
|
+
marshallOptions: {
|
|
17
|
+
removeUndefinedValues: true,
|
|
18
|
+
},
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
// Create adapters - reused across invocations
|
|
22
|
+
const dataAdapter = createAdapters(docClient, {
|
|
23
|
+
tableName: process.env.TABLE_NAME,
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
export async function handler(event: APIGatewayProxyEventV2, context: Context) {
|
|
27
|
+
// Compute issuer from the request
|
|
28
|
+
const host = event.headers.host || event.requestContext.domainName;
|
|
29
|
+
const protocol = event.headers["x-forwarded-proto"] || "https";
|
|
30
|
+
const issuer = `${protocol}://${host}/`;
|
|
31
|
+
|
|
32
|
+
// Get origin for CORS
|
|
33
|
+
const origin = event.headers.origin || "";
|
|
34
|
+
|
|
35
|
+
// Widget URL from environment (set by SST)
|
|
36
|
+
const widgetUrl = process.env.WIDGET_URL || "";
|
|
37
|
+
|
|
38
|
+
// CORS configuration
|
|
39
|
+
// SECURITY: Configure ALLOWED_ORIGINS environment variable in production
|
|
40
|
+
// to restrict origins. Comma-separated list of allowed origins.
|
|
41
|
+
const allowedOrigins = process.env.ALLOWED_ORIGINS
|
|
42
|
+
? process.env.ALLOWED_ORIGINS.split(",").map((o) => o.trim())
|
|
43
|
+
: [
|
|
44
|
+
// WARNING: These localhost origins are for development only
|
|
45
|
+
// Remove or override via ALLOWED_ORIGINS env var in production
|
|
46
|
+
"http://localhost:5173",
|
|
47
|
+
"https://localhost:3000",
|
|
48
|
+
origin,
|
|
49
|
+
].filter(Boolean);
|
|
50
|
+
|
|
51
|
+
// Create app instance per request to avoid issuer contamination
|
|
52
|
+
// Lambda containers are reused, so we can't mutate process.env.ISSUER globally
|
|
53
|
+
const appWithIssuer = createApp({
|
|
54
|
+
dataAdapter,
|
|
55
|
+
allowedOrigins,
|
|
56
|
+
widgetUrl,
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
// Set issuer in a request-scoped way via middleware
|
|
60
|
+
appWithIssuer.use("*", async (c, next) => {
|
|
61
|
+
// Store issuer in context for this request
|
|
62
|
+
const originalIssuer = process.env.ISSUER;
|
|
63
|
+
process.env.ISSUER = issuer;
|
|
64
|
+
try {
|
|
65
|
+
await next();
|
|
66
|
+
} finally {
|
|
67
|
+
// Restore original value to prevent contamination
|
|
68
|
+
if (originalIssuer !== undefined) {
|
|
69
|
+
process.env.ISSUER = originalIssuer;
|
|
70
|
+
} else {
|
|
71
|
+
delete process.env.ISSUER;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
return handle(appWithIssuer)(event, context);
|
|
77
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/// <reference path="./.sst/platform/config.d.ts" />
|
|
2
|
+
|
|
3
|
+
export default $config({
|
|
4
|
+
app(input) {
|
|
5
|
+
return {
|
|
6
|
+
name: "authhero",
|
|
7
|
+
removal: input?.stage === "production" ? "retain" : "remove",
|
|
8
|
+
protect: ["production"].includes(input?.stage),
|
|
9
|
+
home: "aws",
|
|
10
|
+
};
|
|
11
|
+
},
|
|
12
|
+
async run() {
|
|
13
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
14
|
+
// DynamoDB Table - Single-table design for all AuthHero data
|
|
15
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
16
|
+
const table = new sst.aws.Dynamo("AuthHeroTable", {
|
|
17
|
+
fields: {
|
|
18
|
+
pk: "string",
|
|
19
|
+
sk: "string",
|
|
20
|
+
gsi1pk: "string",
|
|
21
|
+
gsi1sk: "string",
|
|
22
|
+
gsi2pk: "string",
|
|
23
|
+
gsi2sk: "string",
|
|
24
|
+
},
|
|
25
|
+
primaryIndex: { hashKey: "pk", rangeKey: "sk" },
|
|
26
|
+
globalIndexes: {
|
|
27
|
+
gsi1: { hashKey: "gsi1pk", rangeKey: "gsi1sk" },
|
|
28
|
+
gsi2: { hashKey: "gsi2pk", rangeKey: "gsi2sk" },
|
|
29
|
+
},
|
|
30
|
+
ttl: "expiresAt",
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
34
|
+
// S3 Bucket + CloudFront for Widget Assets
|
|
35
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
36
|
+
const assets = new sst.aws.StaticSite("WidgetAssets", {
|
|
37
|
+
path: "dist/assets",
|
|
38
|
+
build: {
|
|
39
|
+
command: "node copy-assets.js",
|
|
40
|
+
output: "dist/assets",
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
45
|
+
// Lambda Function
|
|
46
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
47
|
+
const api = new sst.aws.ApiGatewayV2("AuthHeroApi");
|
|
48
|
+
|
|
49
|
+
const authFunction = new sst.aws.Function("AuthHeroFunction", {
|
|
50
|
+
handler: "src/index.handler",
|
|
51
|
+
runtime: "nodejs20.x",
|
|
52
|
+
timeout: "30 seconds",
|
|
53
|
+
memory: "512 MB",
|
|
54
|
+
link: [table],
|
|
55
|
+
environment: {
|
|
56
|
+
TABLE_NAME: table.name,
|
|
57
|
+
WIDGET_URL: assets.url,
|
|
58
|
+
},
|
|
59
|
+
nodejs: {
|
|
60
|
+
install: ["@authhero/aws"],
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
api.route("$default", authFunction.arn);
|
|
65
|
+
|
|
66
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
67
|
+
// Optional: Custom Domain
|
|
68
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
69
|
+
// Uncomment and configure to use a custom domain:
|
|
70
|
+
//
|
|
71
|
+
// api.domain = {
|
|
72
|
+
// name: "auth.yourdomain.com",
|
|
73
|
+
// dns: sst.aws.dns(),
|
|
74
|
+
// };
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
api: api.url,
|
|
78
|
+
assets: assets.url,
|
|
79
|
+
table: table.name,
|
|
80
|
+
};
|
|
81
|
+
},
|
|
82
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "bundler",
|
|
6
|
+
"strict": true,
|
|
7
|
+
"esModuleInterop": true,
|
|
8
|
+
"skipLibCheck": true,
|
|
9
|
+
"forceConsistentCasingInFileNames": true,
|
|
10
|
+
"outDir": "dist",
|
|
11
|
+
"declaration": true,
|
|
12
|
+
"types": ["node"]
|
|
13
|
+
},
|
|
14
|
+
"include": ["src/**/*", "sst.config.ts"],
|
|
15
|
+
"exclude": ["node_modules", "dist"]
|
|
16
|
+
}
|
package/dist/create-authhero.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { Command as
|
|
3
|
-
import
|
|
2
|
+
import { Command as P } from "commander";
|
|
3
|
+
import u from "inquirer";
|
|
4
4
|
import s from "fs";
|
|
5
5
|
import i from "path";
|
|
6
|
-
import { spawn as
|
|
7
|
-
const
|
|
6
|
+
import { spawn as I } from "child_process";
|
|
7
|
+
const N = new P(), p = {
|
|
8
8
|
local: {
|
|
9
9
|
name: "Local (SQLite)",
|
|
10
10
|
description: "Local development setup with SQLite database - great for getting started",
|
|
@@ -83,15 +83,51 @@ const I = new T(), m = {
|
|
|
83
83
|
}
|
|
84
84
|
}),
|
|
85
85
|
seedFile: "seed.ts"
|
|
86
|
+
},
|
|
87
|
+
"aws-sst": {
|
|
88
|
+
name: "AWS SST (Lambda + DynamoDB)",
|
|
89
|
+
description: "Serverless AWS deployment with Lambda, DynamoDB, and SST",
|
|
90
|
+
templateDir: "aws-sst",
|
|
91
|
+
packageJson: (t, e) => ({
|
|
92
|
+
name: t,
|
|
93
|
+
version: "1.0.0",
|
|
94
|
+
type: "module",
|
|
95
|
+
scripts: {
|
|
96
|
+
dev: "sst dev",
|
|
97
|
+
deploy: "sst deploy --stage production",
|
|
98
|
+
remove: "sst remove",
|
|
99
|
+
seed: "npx tsx src/seed.ts",
|
|
100
|
+
"copy-assets": "node copy-assets.js"
|
|
101
|
+
},
|
|
102
|
+
dependencies: {
|
|
103
|
+
"@authhero/aws": "latest",
|
|
104
|
+
"@authhero/widget": "latest",
|
|
105
|
+
"@aws-sdk/client-dynamodb": "^3.0.0",
|
|
106
|
+
"@aws-sdk/lib-dynamodb": "^3.0.0",
|
|
107
|
+
"@hono/swagger-ui": "^0.5.0",
|
|
108
|
+
"@hono/zod-openapi": "^0.19.0",
|
|
109
|
+
authhero: "latest",
|
|
110
|
+
hono: "^4.6.0",
|
|
111
|
+
...e && { "@authhero/multi-tenancy": "latest" }
|
|
112
|
+
},
|
|
113
|
+
devDependencies: {
|
|
114
|
+
"@types/aws-lambda": "^8.10.0",
|
|
115
|
+
"@types/node": "^20.0.0",
|
|
116
|
+
sst: "^3.0.0",
|
|
117
|
+
tsx: "^4.0.0",
|
|
118
|
+
typescript: "^5.5.0"
|
|
119
|
+
}
|
|
120
|
+
}),
|
|
121
|
+
seedFile: "seed.ts"
|
|
86
122
|
}
|
|
87
123
|
};
|
|
88
|
-
function
|
|
89
|
-
s.readdirSync(t).forEach((
|
|
90
|
-
const n = i.join(t,
|
|
91
|
-
s.lstatSync(n).isDirectory() ? (s.mkdirSync(
|
|
124
|
+
function k(t, e) {
|
|
125
|
+
s.readdirSync(t).forEach((a) => {
|
|
126
|
+
const n = i.join(t, a), o = i.join(e, a);
|
|
127
|
+
s.lstatSync(n).isDirectory() ? (s.mkdirSync(o, { recursive: !0 }), k(n, o)) : s.copyFileSync(n, o);
|
|
92
128
|
});
|
|
93
129
|
}
|
|
94
|
-
function
|
|
130
|
+
function _(t) {
|
|
95
131
|
return `import { SqliteDialect, Kysely } from "kysely";
|
|
96
132
|
import Database from "better-sqlite3";
|
|
97
133
|
import createAdapters from "@authhero/kysely-adapter";
|
|
@@ -128,13 +164,12 @@ async function main() {
|
|
|
128
164
|
main().catch(console.error);
|
|
129
165
|
`;
|
|
130
166
|
}
|
|
131
|
-
function
|
|
167
|
+
function R(t) {
|
|
132
168
|
return t ? `import { Context } from "hono";
|
|
133
169
|
import { swaggerUI } from "@hono/swagger-ui";
|
|
134
|
-
import { AuthHeroConfig } from "authhero";
|
|
170
|
+
import { AuthHeroConfig, DataAdapters } from "authhero";
|
|
135
171
|
import { serveStatic } from "@hono/node-server/serve-static";
|
|
136
172
|
import { initMultiTenant } from "@authhero/multi-tenancy";
|
|
137
|
-
import { DataAdapters } from "@authhero/adapter-interfaces";
|
|
138
173
|
|
|
139
174
|
// Control plane tenant ID - the tenant that manages all other tenants
|
|
140
175
|
const CONTROL_PLANE_TENANT_ID = "control_plane";
|
|
@@ -229,7 +264,7 @@ export default function createApp(config: AuthHeroConfig) {
|
|
|
229
264
|
}
|
|
230
265
|
`;
|
|
231
266
|
}
|
|
232
|
-
function
|
|
267
|
+
function M(t) {
|
|
233
268
|
return `import { D1Dialect } from "kysely-d1";
|
|
234
269
|
import { Kysely } from "kysely";
|
|
235
270
|
import createAdapters from "@authhero/kysely-adapter";
|
|
@@ -302,12 +337,11 @@ export default {
|
|
|
302
337
|
};
|
|
303
338
|
`;
|
|
304
339
|
}
|
|
305
|
-
function
|
|
340
|
+
function j(t) {
|
|
306
341
|
return t ? `import { Context } from "hono";
|
|
307
342
|
import { swaggerUI } from "@hono/swagger-ui";
|
|
308
|
-
import { AuthHeroConfig } from "authhero";
|
|
343
|
+
import { AuthHeroConfig, DataAdapters } from "authhero";
|
|
309
344
|
import { initMultiTenant } from "@authhero/multi-tenancy";
|
|
310
|
-
import { DataAdapters } from "@authhero/adapter-interfaces";
|
|
311
345
|
|
|
312
346
|
// Control plane tenant ID - the tenant that manages all other tenants
|
|
313
347
|
const CONTROL_PLANE_TENANT_ID = "control_plane";
|
|
@@ -379,10 +413,171 @@ export default function createApp(config: AuthHeroConfig) {
|
|
|
379
413
|
}
|
|
380
414
|
`;
|
|
381
415
|
}
|
|
382
|
-
function
|
|
416
|
+
function L(t) {
|
|
417
|
+
return t ? `import { Context } from "hono";
|
|
418
|
+
import { swaggerUI } from "@hono/swagger-ui";
|
|
419
|
+
import { AuthHeroConfig, DataAdapters } from "authhero";
|
|
420
|
+
import { initMultiTenant } from "@authhero/multi-tenancy";
|
|
421
|
+
|
|
422
|
+
// Control plane tenant ID - the tenant that manages all other tenants
|
|
423
|
+
const CONTROL_PLANE_TENANT_ID = "control_plane";
|
|
424
|
+
|
|
425
|
+
interface AppConfig extends AuthHeroConfig {
|
|
426
|
+
dataAdapter: DataAdapters;
|
|
427
|
+
widgetUrl: string;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
export default function createApp(config: AppConfig) {
|
|
431
|
+
// Initialize multi-tenant AuthHero
|
|
432
|
+
const { app } = initMultiTenant({
|
|
433
|
+
...config,
|
|
434
|
+
controlPlaneTenantId: CONTROL_PLANE_TENANT_ID,
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
app
|
|
438
|
+
.onError((err, ctx) => {
|
|
439
|
+
if (err && typeof err === "object" && "getResponse" in err) {
|
|
440
|
+
return (err as { getResponse: () => Response }).getResponse();
|
|
441
|
+
}
|
|
442
|
+
console.error(err);
|
|
443
|
+
return ctx.text(err instanceof Error ? err.message : "Internal Server Error", 500);
|
|
444
|
+
})
|
|
445
|
+
.get("/", async (ctx: Context) => {
|
|
446
|
+
return ctx.json({
|
|
447
|
+
name: "AuthHero Multi-Tenant Server (AWS)",
|
|
448
|
+
version: "1.0.0",
|
|
449
|
+
status: "running",
|
|
450
|
+
docs: "/docs",
|
|
451
|
+
controlPlaneTenant: CONTROL_PLANE_TENANT_ID,
|
|
452
|
+
});
|
|
453
|
+
})
|
|
454
|
+
.get("/docs", swaggerUI({ url: "/api/v2/spec" }))
|
|
455
|
+
// Redirect widget requests to S3/CloudFront
|
|
456
|
+
.get("/u/widget/*", async (ctx) => {
|
|
457
|
+
const file = ctx.req.path.replace("/u/widget/", "");
|
|
458
|
+
return ctx.redirect(\`\${config.widgetUrl}/u/widget/\${file}\`);
|
|
459
|
+
})
|
|
460
|
+
.get("/u/*", async (ctx) => {
|
|
461
|
+
const file = ctx.req.path.replace("/u/", "");
|
|
462
|
+
return ctx.redirect(\`\${config.widgetUrl}/u/\${file}\`);
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
return app;
|
|
466
|
+
}
|
|
467
|
+
` : `import { Context } from "hono";
|
|
468
|
+
import { cors } from "hono/cors";
|
|
469
|
+
import { AuthHeroConfig, init, DataAdapters } from "authhero";
|
|
470
|
+
import { swaggerUI } from "@hono/swagger-ui";
|
|
471
|
+
|
|
472
|
+
interface AppConfig extends AuthHeroConfig {
|
|
473
|
+
dataAdapter: DataAdapters;
|
|
474
|
+
widgetUrl: string;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
export default function createApp(config: AppConfig) {
|
|
478
|
+
const { app } = init(config);
|
|
479
|
+
|
|
480
|
+
// Enable CORS for all origins in development
|
|
481
|
+
app.use("*", cors({
|
|
482
|
+
origin: (origin) => origin || "*",
|
|
483
|
+
allowMethods: ["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"],
|
|
484
|
+
allowHeaders: ["Content-Type", "Authorization", "Auth0-Client"],
|
|
485
|
+
exposeHeaders: ["Content-Length"],
|
|
486
|
+
credentials: true,
|
|
487
|
+
}));
|
|
488
|
+
|
|
489
|
+
app
|
|
490
|
+
.onError((err, ctx) => {
|
|
491
|
+
if (err && typeof err === "object" && "getResponse" in err) {
|
|
492
|
+
return (err as { getResponse: () => Response }).getResponse();
|
|
493
|
+
}
|
|
494
|
+
console.error(err);
|
|
495
|
+
return ctx.text(err instanceof Error ? err.message : "Internal Server Error", 500);
|
|
496
|
+
})
|
|
497
|
+
.get("/", async (ctx: Context) => {
|
|
498
|
+
return ctx.json({
|
|
499
|
+
name: "AuthHero Server (AWS)",
|
|
500
|
+
status: "running",
|
|
501
|
+
});
|
|
502
|
+
})
|
|
503
|
+
.get("/docs", swaggerUI({ url: "/api/v2/spec" }))
|
|
504
|
+
// Redirect widget requests to S3/CloudFront
|
|
505
|
+
.get("/u/widget/*", async (ctx) => {
|
|
506
|
+
const file = ctx.req.path.replace("/u/widget/", "");
|
|
507
|
+
return ctx.redirect(\`\${config.widgetUrl}/u/widget/\${file}\`);
|
|
508
|
+
})
|
|
509
|
+
.get("/u/*", async (ctx) => {
|
|
510
|
+
const file = ctx.req.path.replace("/u/", "");
|
|
511
|
+
return ctx.redirect(\`\${config.widgetUrl}/u/\${file}\`);
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
return app;
|
|
515
|
+
}
|
|
516
|
+
`;
|
|
517
|
+
}
|
|
518
|
+
function $(t) {
|
|
519
|
+
return `import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
|
|
520
|
+
import { DynamoDBDocumentClient } from "@aws-sdk/lib-dynamodb";
|
|
521
|
+
import createAdapters from "@authhero/aws";
|
|
522
|
+
import { seed } from "authhero";
|
|
523
|
+
|
|
524
|
+
async function main() {
|
|
525
|
+
const adminEmail = process.argv[2] || process.env.ADMIN_EMAIL;
|
|
526
|
+
const adminPassword = process.argv[3] || process.env.ADMIN_PASSWORD;
|
|
527
|
+
const tableName = process.argv[4] || process.env.TABLE_NAME;
|
|
528
|
+
|
|
529
|
+
if (!adminEmail || !adminPassword) {
|
|
530
|
+
console.error("Usage: npm run seed <email> <password>");
|
|
531
|
+
console.error(" or: ADMIN_EMAIL=... ADMIN_PASSWORD=... npm run seed");
|
|
532
|
+
process.exit(1);
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
if (!tableName) {
|
|
536
|
+
console.error("TABLE_NAME environment variable is required");
|
|
537
|
+
console.error("Run 'sst dev' first to get the table name from outputs");
|
|
538
|
+
process.exit(1);
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
const client = new DynamoDBClient({});
|
|
542
|
+
const docClient = DynamoDBDocumentClient.from(client, {
|
|
543
|
+
marshallOptions: {
|
|
544
|
+
removeUndefinedValues: true,
|
|
545
|
+
},
|
|
546
|
+
});
|
|
547
|
+
|
|
548
|
+
const adapters = createAdapters(docClient, { tableName });
|
|
549
|
+
|
|
550
|
+
await seed(adapters, {
|
|
551
|
+
adminEmail,
|
|
552
|
+
adminPassword,
|
|
553
|
+
tenantId: "${t ? "control_plane" : "main"}",
|
|
554
|
+
tenantName: "${t ? "Control Plane" : "Main"}",
|
|
555
|
+
isControlPlane: ${t},
|
|
556
|
+
});
|
|
557
|
+
|
|
558
|
+
console.log("✅ Database seeded successfully!");
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
main().catch(console.error);
|
|
562
|
+
`;
|
|
563
|
+
}
|
|
564
|
+
function O(t, e) {
|
|
565
|
+
const r = i.join(t, "src");
|
|
566
|
+
s.writeFileSync(
|
|
567
|
+
i.join(r, "app.ts"),
|
|
568
|
+
L(e)
|
|
569
|
+
), s.writeFileSync(
|
|
570
|
+
i.join(r, "seed.ts"),
|
|
571
|
+
$(e)
|
|
572
|
+
);
|
|
573
|
+
}
|
|
574
|
+
function C(t) {
|
|
575
|
+
console.log("\\n" + "─".repeat(50)), console.log("🔐 AuthHero deployed to AWS!"), console.log("📚 Check SST output for your API URL"), console.log("🌐 Portal available at https://local.authhero.net"), console.log(t ? "🏢 Multi-tenant mode enabled with control_plane tenant" : "🏠 Single-tenant mode with 'main' tenant"), console.log("─".repeat(50) + "\\n");
|
|
576
|
+
}
|
|
577
|
+
function H(t) {
|
|
383
578
|
const e = i.join(t, ".github", "workflows");
|
|
384
579
|
s.mkdirSync(e, { recursive: !0 });
|
|
385
|
-
const
|
|
580
|
+
const r = `name: Unit tests
|
|
386
581
|
|
|
387
582
|
on: push
|
|
388
583
|
|
|
@@ -403,7 +598,7 @@ jobs:
|
|
|
403
598
|
|
|
404
599
|
- run: npm run type-check
|
|
405
600
|
- run: npm test
|
|
406
|
-
`,
|
|
601
|
+
`, a = `name: Deploy to Dev
|
|
407
602
|
|
|
408
603
|
on:
|
|
409
604
|
push:
|
|
@@ -468,9 +663,9 @@ jobs:
|
|
|
468
663
|
apiToken: \${{ secrets.PROD_CLOUDFLARE_API_TOKEN }}
|
|
469
664
|
command: deploy --env production
|
|
470
665
|
`;
|
|
471
|
-
s.writeFileSync(i.join(e, "unit-tests.yml"),
|
|
666
|
+
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"), n), console.log("\\n📦 GitHub CI workflows created!");
|
|
472
667
|
}
|
|
473
|
-
function
|
|
668
|
+
function U(t) {
|
|
474
669
|
const e = {
|
|
475
670
|
branches: ["main"],
|
|
476
671
|
plugins: [
|
|
@@ -483,105 +678,111 @@ function $(t) {
|
|
|
483
678
|
i.join(t, ".releaserc.json"),
|
|
484
679
|
JSON.stringify(e, null, 2)
|
|
485
680
|
);
|
|
486
|
-
const
|
|
487
|
-
|
|
488
|
-
...
|
|
681
|
+
const r = i.join(t, "package.json"), a = JSON.parse(s.readFileSync(r, "utf-8"));
|
|
682
|
+
a.devDependencies = {
|
|
683
|
+
...a.devDependencies,
|
|
489
684
|
"semantic-release": "^24.0.0"
|
|
490
|
-
},
|
|
491
|
-
...
|
|
685
|
+
}, a.scripts = {
|
|
686
|
+
...a.scripts,
|
|
492
687
|
test: 'echo "No tests yet"',
|
|
493
688
|
"type-check": "tsc --noEmit"
|
|
494
|
-
}, s.writeFileSync(
|
|
689
|
+
}, s.writeFileSync(r, JSON.stringify(a, null, 2));
|
|
495
690
|
}
|
|
496
691
|
function v(t, e) {
|
|
497
|
-
return new Promise((
|
|
498
|
-
const n =
|
|
692
|
+
return new Promise((r, a) => {
|
|
693
|
+
const n = I(t, [], {
|
|
499
694
|
cwd: e,
|
|
500
695
|
shell: !0,
|
|
501
696
|
stdio: "inherit"
|
|
502
697
|
});
|
|
503
|
-
n.on("close", (
|
|
504
|
-
|
|
505
|
-
}), n.on("error",
|
|
698
|
+
n.on("close", (o) => {
|
|
699
|
+
o === 0 ? r() : a(new Error(`Command failed with exit code ${o}`));
|
|
700
|
+
}), n.on("error", a);
|
|
506
701
|
});
|
|
507
702
|
}
|
|
508
|
-
function
|
|
509
|
-
return new Promise((
|
|
510
|
-
const
|
|
703
|
+
function D(t, e, r) {
|
|
704
|
+
return new Promise((a, n) => {
|
|
705
|
+
const o = I(t, [], {
|
|
511
706
|
cwd: e,
|
|
512
707
|
shell: !0,
|
|
513
708
|
stdio: "inherit",
|
|
514
|
-
env: { ...process.env, ...
|
|
709
|
+
env: { ...process.env, ...r }
|
|
515
710
|
});
|
|
516
|
-
|
|
517
|
-
c === 0 ?
|
|
518
|
-
}),
|
|
711
|
+
o.on("close", (c) => {
|
|
712
|
+
c === 0 ? a() : n(new Error(`Command failed with exit code ${c}`));
|
|
713
|
+
}), o.on("error", n);
|
|
519
714
|
});
|
|
520
715
|
}
|
|
521
|
-
function
|
|
522
|
-
const
|
|
716
|
+
function F(t, e) {
|
|
717
|
+
const r = i.join(t, "src");
|
|
523
718
|
s.writeFileSync(
|
|
524
|
-
i.join(
|
|
525
|
-
|
|
719
|
+
i.join(r, "app.ts"),
|
|
720
|
+
j(e)
|
|
526
721
|
), s.writeFileSync(
|
|
527
|
-
i.join(
|
|
528
|
-
|
|
722
|
+
i.join(r, "seed.ts"),
|
|
723
|
+
M(e)
|
|
529
724
|
);
|
|
530
725
|
}
|
|
531
|
-
function
|
|
726
|
+
function x(t) {
|
|
532
727
|
console.log(`
|
|
533
728
|
` + "─".repeat(50)), console.log("🔐 AuthHero server running at https://localhost:3000"), console.log("📚 API documentation available at https://localhost:3000/docs"), console.log("🌐 Portal available at https://local.authhero.net"), console.log(t ? "🏢 Multi-tenant mode enabled with control_plane tenant" : "🏠 Single-tenant mode with 'main' tenant"), console.log("─".repeat(50) + `
|
|
534
729
|
`);
|
|
535
730
|
}
|
|
536
|
-
function
|
|
731
|
+
function b(t) {
|
|
537
732
|
console.log(`
|
|
538
733
|
` + "─".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("🌐 Portal available at https://local.authhero.net"), console.log(t ? "🏢 Multi-tenant mode enabled with control_plane tenant" : "🏠 Single-tenant mode with 'main' tenant"), console.log("─".repeat(50) + `
|
|
539
734
|
`);
|
|
540
735
|
}
|
|
541
|
-
|
|
736
|
+
N.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("-e, --email <email>", "admin email address").option("-p, --password <password>", "admin password (min 8 characters)").option(
|
|
542
737
|
"--package-manager <pm>",
|
|
543
738
|
"package manager to use: npm, yarn, pnpm, or bun"
|
|
544
739
|
).option("--multi-tenant", "enable multi-tenant mode").option("--skip-install", "skip installing dependencies").option("--skip-migrate", "skip running database migrations").option("--skip-seed", "skip seeding the database").option("--skip-start", "skip starting the development server").option("--github-ci", "include GitHub CI workflows with semantic versioning").option("-y, --yes", "skip all prompts and use defaults/provided options").action(async (t, e) => {
|
|
545
|
-
const
|
|
740
|
+
const r = e.yes === !0;
|
|
546
741
|
console.log(`
|
|
547
742
|
🔐 Welcome to AuthHero!
|
|
548
743
|
`);
|
|
549
|
-
let
|
|
550
|
-
|
|
744
|
+
let a = t;
|
|
745
|
+
a || (r ? (a = "auth-server", console.log(`Using default project name: ${a}`)) : a = (await u.prompt([
|
|
551
746
|
{
|
|
552
747
|
type: "input",
|
|
553
748
|
name: "projectName",
|
|
554
749
|
message: "Project name:",
|
|
555
750
|
default: "auth-server",
|
|
556
|
-
validate: (
|
|
751
|
+
validate: (d) => d !== "" || "Project name cannot be empty"
|
|
557
752
|
}
|
|
558
753
|
])).projectName);
|
|
559
|
-
const n = i.join(process.cwd(),
|
|
560
|
-
s.existsSync(n) && (console.error(`❌ Project "${
|
|
561
|
-
let
|
|
562
|
-
e.template ? (["local", "cloudflare"].includes(e.template) || (console.error(`❌ Invalid template: ${e.template}`), console.error("Valid options: local, cloudflare"), process.exit(1)),
|
|
754
|
+
const n = i.join(process.cwd(), a);
|
|
755
|
+
s.existsSync(n) && (console.error(`❌ Project "${a}" already exists.`), process.exit(1));
|
|
756
|
+
let o;
|
|
757
|
+
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)), o = e.template, console.log(`Using template: ${p[o].name}`)) : o = (await u.prompt([
|
|
563
758
|
{
|
|
564
759
|
type: "list",
|
|
565
760
|
name: "setupType",
|
|
566
761
|
message: "Select your setup type:",
|
|
567
762
|
choices: [
|
|
568
763
|
{
|
|
569
|
-
name: `${
|
|
570
|
-
${
|
|
764
|
+
name: `${p.local.name}
|
|
765
|
+
${p.local.description}`,
|
|
571
766
|
value: "local",
|
|
572
|
-
short:
|
|
767
|
+
short: p.local.name
|
|
573
768
|
},
|
|
574
769
|
{
|
|
575
|
-
name: `${
|
|
576
|
-
${
|
|
770
|
+
name: `${p.cloudflare.name}
|
|
771
|
+
${p.cloudflare.description}`,
|
|
577
772
|
value: "cloudflare",
|
|
578
|
-
short:
|
|
773
|
+
short: p.cloudflare.name
|
|
774
|
+
},
|
|
775
|
+
{
|
|
776
|
+
name: `${p["aws-sst"].name}
|
|
777
|
+
${p["aws-sst"].description}`,
|
|
778
|
+
value: "aws-sst",
|
|
779
|
+
short: p["aws-sst"].name
|
|
579
780
|
}
|
|
580
781
|
]
|
|
581
782
|
}
|
|
582
783
|
])).setupType;
|
|
583
784
|
let c;
|
|
584
|
-
e.multiTenant !== void 0 ? (c = e.multiTenant, console.log(`Multi-tenant mode: ${c ? "enabled" : "disabled"}`)) :
|
|
785
|
+
e.multiTenant !== void 0 ? (c = e.multiTenant, console.log(`Multi-tenant mode: ${c ? "enabled" : "disabled"}`)) : r ? c = !1 : c = (await u.prompt([
|
|
585
786
|
{
|
|
586
787
|
type: "confirm",
|
|
587
788
|
name: "multiTenant",
|
|
@@ -590,56 +791,57 @@ I.version("1.0.0").description("Create a new AuthHero project").argument("[proje
|
|
|
590
791
|
default: !1
|
|
591
792
|
}
|
|
592
793
|
])).multiTenant;
|
|
593
|
-
const
|
|
794
|
+
const A = p[o];
|
|
594
795
|
s.mkdirSync(n, { recursive: !0 }), s.writeFileSync(
|
|
595
796
|
i.join(n, "package.json"),
|
|
596
|
-
JSON.stringify(
|
|
797
|
+
JSON.stringify(A.packageJson(a, c), null, 2)
|
|
597
798
|
);
|
|
598
|
-
const
|
|
799
|
+
const E = A.templateDir, S = i.join(
|
|
599
800
|
import.meta.url.replace("file://", "").replace("/create-authhero.js", ""),
|
|
600
|
-
|
|
801
|
+
E
|
|
601
802
|
);
|
|
602
|
-
if (s.existsSync(
|
|
603
|
-
const l = i.join(n, "wrangler.toml"),
|
|
604
|
-
s.existsSync(l) && s.copyFileSync(l,
|
|
605
|
-
const
|
|
606
|
-
s.existsSync(
|
|
803
|
+
if (s.existsSync(S) ? k(S, n) : (console.error(`❌ Template directory not found: ${S}`), process.exit(1)), o === "cloudflare" && F(n, c), o === "cloudflare") {
|
|
804
|
+
const l = i.join(n, "wrangler.toml"), d = i.join(n, "wrangler.local.toml");
|
|
805
|
+
s.existsSync(l) && s.copyFileSync(l, d);
|
|
806
|
+
const m = i.join(n, ".dev.vars.example"), g = i.join(n, ".dev.vars");
|
|
807
|
+
s.existsSync(m) && s.copyFileSync(m, g), console.log(
|
|
607
808
|
"📁 Created wrangler.local.toml and .dev.vars for local development"
|
|
608
809
|
);
|
|
609
810
|
}
|
|
610
811
|
let w = !1;
|
|
611
|
-
if (
|
|
812
|
+
if (o === "cloudflare" && (e.githubCi !== void 0 ? (w = e.githubCi, w && console.log("Including GitHub CI workflows with semantic versioning")) : r || (w = (await u.prompt([
|
|
612
813
|
{
|
|
613
814
|
type: "confirm",
|
|
614
815
|
name: "includeGithubCi",
|
|
615
816
|
message: "Would you like to include GitHub CI with semantic versioning?",
|
|
616
817
|
default: !1
|
|
617
818
|
}
|
|
618
|
-
])).includeGithubCi), w && (
|
|
619
|
-
const l =
|
|
819
|
+
])).includeGithubCi), w && (H(n), U(n))), o === "local") {
|
|
820
|
+
const l = _(c);
|
|
620
821
|
s.writeFileSync(i.join(n, "src/seed.ts"), l);
|
|
621
|
-
const
|
|
622
|
-
s.writeFileSync(i.join(n, "src/app.ts"),
|
|
822
|
+
const d = R(c);
|
|
823
|
+
s.writeFileSync(i.join(n, "src/app.ts"), d);
|
|
623
824
|
}
|
|
624
|
-
|
|
825
|
+
o === "aws-sst" && O(n, c);
|
|
826
|
+
const T = c ? "multi-tenant" : "single-tenant";
|
|
625
827
|
console.log(
|
|
626
828
|
`
|
|
627
|
-
✅ Project "${
|
|
829
|
+
✅ Project "${a}" has been created with ${A.name} (${T}) setup!
|
|
628
830
|
`
|
|
629
831
|
);
|
|
630
|
-
let
|
|
631
|
-
if (e.skipInstall ?
|
|
832
|
+
let h;
|
|
833
|
+
if (e.skipInstall ? h = !1 : r ? h = !0 : h = (await u.prompt([
|
|
632
834
|
{
|
|
633
835
|
type: "confirm",
|
|
634
836
|
name: "shouldInstall",
|
|
635
837
|
message: "Would you like to install dependencies now?",
|
|
636
838
|
default: !0
|
|
637
839
|
}
|
|
638
|
-
])).shouldInstall,
|
|
840
|
+
])).shouldInstall, h) {
|
|
639
841
|
let l;
|
|
640
842
|
e.packageManager ? (["npm", "yarn", "pnpm", "bun"].includes(e.packageManager) || (console.error(
|
|
641
843
|
`❌ Invalid package manager: ${e.packageManager}`
|
|
642
|
-
), console.error("Valid options: npm, yarn, pnpm, bun"), process.exit(1)), l = e.packageManager) :
|
|
844
|
+
), console.error("Valid options: npm, yarn, pnpm, bun"), process.exit(1)), l = e.packageManager) : r ? l = "pnpm" : l = (await u.prompt([
|
|
643
845
|
{
|
|
644
846
|
type: "list",
|
|
645
847
|
name: "packageManager",
|
|
@@ -656,14 +858,14 @@ I.version("1.0.0").description("Create a new AuthHero project").argument("[proje
|
|
|
656
858
|
📦 Installing dependencies with ${l}...
|
|
657
859
|
`);
|
|
658
860
|
try {
|
|
659
|
-
const
|
|
660
|
-
if (await v(
|
|
861
|
+
const d = l === "pnpm" ? "pnpm install --ignore-workspace" : `${l} install`;
|
|
862
|
+
if (await v(d, n), o === "local" && (console.log(`
|
|
661
863
|
🔧 Building native modules...
|
|
662
864
|
`), await v("npm rebuild better-sqlite3", n)), console.log(`
|
|
663
865
|
✅ Dependencies installed successfully!
|
|
664
|
-
`),
|
|
866
|
+
`), o === "local" || o === "cloudflare") {
|
|
665
867
|
let g;
|
|
666
|
-
if (e.skipMigrate && e.skipSeed ? g = !1 :
|
|
868
|
+
if (e.skipMigrate && e.skipSeed ? g = !1 : r ? g = !e.skipMigrate || !e.skipSeed : g = (await u.prompt([
|
|
667
869
|
{
|
|
668
870
|
type: "confirm",
|
|
669
871
|
name: "shouldSetup",
|
|
@@ -671,11 +873,11 @@ I.version("1.0.0").description("Create a new AuthHero project").argument("[proje
|
|
|
671
873
|
default: !0
|
|
672
874
|
}
|
|
673
875
|
])).shouldSetup, g) {
|
|
674
|
-
let
|
|
675
|
-
e.email && e.password ? (/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(e.email) || (console.error("❌ Invalid email address provided"), process.exit(1)), e.password.length < 8 && (console.error("❌ Password must be at least 8 characters"), process.exit(1)),
|
|
876
|
+
let f;
|
|
877
|
+
e.email && e.password ? (/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(e.email) || (console.error("❌ Invalid email address provided"), process.exit(1)), e.password.length < 8 && (console.error("❌ Password must be at least 8 characters"), process.exit(1)), f = {
|
|
676
878
|
username: e.email,
|
|
677
879
|
password: e.password
|
|
678
|
-
}, console.log(`Using admin email: ${e.email}`)) :
|
|
880
|
+
}, console.log(`Using admin email: ${e.email}`)) : f = await u.prompt([
|
|
679
881
|
{
|
|
680
882
|
type: "input",
|
|
681
883
|
name: "username",
|
|
@@ -694,49 +896,51 @@ I.version("1.0.0").description("Create a new AuthHero project").argument("[proje
|
|
|
694
896
|
🔄 Running migrations...
|
|
695
897
|
`), await v(`${l} run migrate`, n)), e.skipSeed || (console.log(`
|
|
696
898
|
🌱 Seeding database...
|
|
697
|
-
`),
|
|
899
|
+
`), o === "local" ? await D(
|
|
698
900
|
`${l} run seed`,
|
|
699
901
|
n,
|
|
700
902
|
{
|
|
701
|
-
ADMIN_EMAIL:
|
|
702
|
-
ADMIN_PASSWORD:
|
|
903
|
+
ADMIN_EMAIL: f.username,
|
|
904
|
+
ADMIN_PASSWORD: f.password
|
|
703
905
|
}
|
|
704
|
-
) : await
|
|
906
|
+
) : await D(
|
|
705
907
|
`${l} run seed:local`,
|
|
706
908
|
n,
|
|
707
909
|
{
|
|
708
|
-
ADMIN_EMAIL:
|
|
709
|
-
ADMIN_PASSWORD:
|
|
910
|
+
ADMIN_EMAIL: f.username,
|
|
911
|
+
ADMIN_PASSWORD: f.password
|
|
710
912
|
}
|
|
711
913
|
));
|
|
712
914
|
}
|
|
713
915
|
}
|
|
714
|
-
let
|
|
715
|
-
e.skipStart ||
|
|
916
|
+
let m;
|
|
917
|
+
e.skipStart || r ? m = !1 : m = (await u.prompt([
|
|
716
918
|
{
|
|
717
919
|
type: "confirm",
|
|
718
920
|
name: "shouldStart",
|
|
719
921
|
message: "Would you like to start the development server?",
|
|
720
922
|
default: !0
|
|
721
923
|
}
|
|
722
|
-
])).shouldStart,
|
|
723
|
-
`), await v(`${l} run dev`, n)),
|
|
924
|
+
])).shouldStart, m && (o === "cloudflare" ? x(c) : o === "aws-sst" ? C(c) : b(c), console.log(`🚀 Starting development server...
|
|
925
|
+
`), await v(`${l} run dev`, n)), r && !m && (console.log(`
|
|
724
926
|
✅ Setup complete!`), console.log(`
|
|
725
|
-
To start the development server:`), console.log(` cd ${
|
|
726
|
-
} catch (
|
|
927
|
+
To start the development server:`), console.log(` cd ${a}`), console.log(" npm run dev"), o === "cloudflare" ? x(c) : o === "aws-sst" ? C(c) : b(c));
|
|
928
|
+
} catch (d) {
|
|
727
929
|
console.error(`
|
|
728
|
-
❌ An error occurred:`,
|
|
930
|
+
❌ An error occurred:`, d), process.exit(1);
|
|
729
931
|
}
|
|
730
932
|
}
|
|
731
|
-
|
|
933
|
+
h || (console.log("Next steps:"), console.log(` cd ${a}`), o === "local" ? (console.log(" npm install"), console.log(" npm run migrate"), console.log(
|
|
732
934
|
" ADMIN_EMAIL=admin@example.com ADMIN_PASSWORD=yourpassword npm run seed"
|
|
733
|
-
), console.log(" npm run dev")) :
|
|
935
|
+
), console.log(" npm run dev")) : o === "cloudflare" ? (console.log(" npm install"), console.log(
|
|
734
936
|
" npm run migrate # or npm run db:migrate:remote for production"
|
|
735
937
|
), console.log(
|
|
736
938
|
" ADMIN_EMAIL=admin@example.com ADMIN_PASSWORD=yourpassword npm run seed"
|
|
737
|
-
), console.log(" npm run dev # or npm run dev:remote for production")), console.log(
|
|
939
|
+
), console.log(" npm run dev # or npm run dev:remote for production")) : o === "aws-sst" && (console.log(" npm install"), console.log(" npm run dev # Deploys to AWS in development mode"), console.log(" # After deploy, get TABLE_NAME from output, then:"), console.log(
|
|
940
|
+
" TABLE_NAME=<your-table> ADMIN_EMAIL=admin@example.com ADMIN_PASSWORD=yourpassword npm run seed"
|
|
941
|
+
)), console.log(`
|
|
738
942
|
Server will be available at: https://localhost:3000`), console.log("Portal available at: https://local.authhero.net"), console.log(`
|
|
739
943
|
For more information, visit: https://authhero.net/docs
|
|
740
944
|
`));
|
|
741
945
|
});
|
|
742
|
-
|
|
946
|
+
N.parse(process.argv);
|