create-authhero 0.23.0 → 0.25.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 +332 -114
- 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
|
|
4
|
-
import
|
|
2
|
+
import { Command as k } from "commander";
|
|
3
|
+
import u from "inquirer";
|
|
4
|
+
import a from "fs";
|
|
5
5
|
import i from "path";
|
|
6
|
-
import { spawn as
|
|
7
|
-
const
|
|
6
|
+
import { spawn as N } from "child_process";
|
|
7
|
+
const b = new k(), 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
|
-
|
|
90
|
-
const n = i.join(t,
|
|
91
|
-
|
|
124
|
+
function E(t, e) {
|
|
125
|
+
a.readdirSync(t).forEach((s) => {
|
|
126
|
+
const n = i.join(t, s), o = i.join(e, s);
|
|
127
|
+
a.lstatSync(n).isDirectory() ? (a.mkdirSync(o, { recursive: !0 }), E(n, o)) : a.copyFileSync(n, o);
|
|
92
128
|
});
|
|
93
129
|
}
|
|
94
|
-
function
|
|
130
|
+
function P(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,21 +164,25 @@ 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
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
173
|
|
|
138
|
-
// Control plane
|
|
174
|
+
// Control plane configuration
|
|
139
175
|
const CONTROL_PLANE_TENANT_ID = "control_plane";
|
|
176
|
+
const CONTROL_PLANE_CLIENT_ID = "default_client";
|
|
140
177
|
|
|
141
178
|
export default function createApp(config: AuthHeroConfig & { dataAdapter: DataAdapters }) {
|
|
142
179
|
// Initialize multi-tenant AuthHero - syncs resource servers, roles, and connections by default
|
|
143
180
|
const { app } = initMultiTenant({
|
|
144
181
|
...config,
|
|
145
|
-
|
|
182
|
+
controlPlane: {
|
|
183
|
+
tenantId: CONTROL_PLANE_TENANT_ID,
|
|
184
|
+
clientId: CONTROL_PLANE_CLIENT_ID,
|
|
185
|
+
},
|
|
146
186
|
});
|
|
147
187
|
|
|
148
188
|
app
|
|
@@ -228,7 +268,7 @@ export default function createApp(config: AuthHeroConfig) {
|
|
|
228
268
|
}
|
|
229
269
|
`;
|
|
230
270
|
}
|
|
231
|
-
function
|
|
271
|
+
function L(t) {
|
|
232
272
|
return `import { D1Dialect } from "kysely-d1";
|
|
233
273
|
import { Kysely } from "kysely";
|
|
234
274
|
import createAdapters from "@authhero/kysely-adapter";
|
|
@@ -301,20 +341,24 @@ export default {
|
|
|
301
341
|
};
|
|
302
342
|
`;
|
|
303
343
|
}
|
|
304
|
-
function
|
|
344
|
+
function M(t) {
|
|
305
345
|
return t ? `import { Context } from "hono";
|
|
306
346
|
import { swaggerUI } from "@hono/swagger-ui";
|
|
307
347
|
import { AuthHeroConfig, DataAdapters } from "authhero";
|
|
308
348
|
import { initMultiTenant } from "@authhero/multi-tenancy";
|
|
309
349
|
|
|
310
|
-
// Control plane
|
|
350
|
+
// Control plane configuration
|
|
311
351
|
const CONTROL_PLANE_TENANT_ID = "control_plane";
|
|
352
|
+
const CONTROL_PLANE_CLIENT_ID = "default_client";
|
|
312
353
|
|
|
313
354
|
export default function createApp(config: AuthHeroConfig & { dataAdapter: DataAdapters }) {
|
|
314
355
|
// Initialize multi-tenant AuthHero - syncs resource servers, roles, and connections by default
|
|
315
356
|
const { app } = initMultiTenant({
|
|
316
357
|
...config,
|
|
317
|
-
|
|
358
|
+
controlPlane: {
|
|
359
|
+
tenantId: CONTROL_PLANE_TENANT_ID,
|
|
360
|
+
clientId: CONTROL_PLANE_CLIENT_ID,
|
|
361
|
+
},
|
|
318
362
|
});
|
|
319
363
|
|
|
320
364
|
app
|
|
@@ -377,10 +421,175 @@ export default function createApp(config: AuthHeroConfig) {
|
|
|
377
421
|
}
|
|
378
422
|
`;
|
|
379
423
|
}
|
|
380
|
-
function
|
|
424
|
+
function j(t) {
|
|
425
|
+
return t ? `import { Context } from "hono";
|
|
426
|
+
import { swaggerUI } from "@hono/swagger-ui";
|
|
427
|
+
import { AuthHeroConfig, DataAdapters } from "authhero";
|
|
428
|
+
import { initMultiTenant } from "@authhero/multi-tenancy";
|
|
429
|
+
|
|
430
|
+
// Control plane configuration
|
|
431
|
+
const CONTROL_PLANE_TENANT_ID = "control_plane";
|
|
432
|
+
const CONTROL_PLANE_CLIENT_ID = "default_client";
|
|
433
|
+
|
|
434
|
+
interface AppConfig extends AuthHeroConfig {
|
|
435
|
+
dataAdapter: DataAdapters;
|
|
436
|
+
widgetUrl: string;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
export default function createApp(config: AppConfig) {
|
|
440
|
+
// Initialize multi-tenant AuthHero
|
|
441
|
+
const { app } = initMultiTenant({
|
|
442
|
+
...config,
|
|
443
|
+
controlPlane: {
|
|
444
|
+
tenantId: CONTROL_PLANE_TENANT_ID,
|
|
445
|
+
clientId: CONTROL_PLANE_CLIENT_ID,
|
|
446
|
+
},
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
app
|
|
450
|
+
.onError((err, ctx) => {
|
|
451
|
+
if (err && typeof err === "object" && "getResponse" in err) {
|
|
452
|
+
return (err as { getResponse: () => Response }).getResponse();
|
|
453
|
+
}
|
|
454
|
+
console.error(err);
|
|
455
|
+
return ctx.text(err instanceof Error ? err.message : "Internal Server Error", 500);
|
|
456
|
+
})
|
|
457
|
+
.get("/", async (ctx: Context) => {
|
|
458
|
+
return ctx.json({
|
|
459
|
+
name: "AuthHero Multi-Tenant Server (AWS)",
|
|
460
|
+
version: "1.0.0",
|
|
461
|
+
status: "running",
|
|
462
|
+
docs: "/docs",
|
|
463
|
+
controlPlaneTenant: CONTROL_PLANE_TENANT_ID,
|
|
464
|
+
});
|
|
465
|
+
})
|
|
466
|
+
.get("/docs", swaggerUI({ url: "/api/v2/spec" }))
|
|
467
|
+
// Redirect widget requests to S3/CloudFront
|
|
468
|
+
.get("/u/widget/*", async (ctx) => {
|
|
469
|
+
const file = ctx.req.path.replace("/u/widget/", "");
|
|
470
|
+
return ctx.redirect(\`\${config.widgetUrl}/u/widget/\${file}\`);
|
|
471
|
+
})
|
|
472
|
+
.get("/u/*", async (ctx) => {
|
|
473
|
+
const file = ctx.req.path.replace("/u/", "");
|
|
474
|
+
return ctx.redirect(\`\${config.widgetUrl}/u/\${file}\`);
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
return app;
|
|
478
|
+
}
|
|
479
|
+
` : `import { Context } from "hono";
|
|
480
|
+
import { cors } from "hono/cors";
|
|
481
|
+
import { AuthHeroConfig, init, DataAdapters } from "authhero";
|
|
482
|
+
import { swaggerUI } from "@hono/swagger-ui";
|
|
483
|
+
|
|
484
|
+
interface AppConfig extends AuthHeroConfig {
|
|
485
|
+
dataAdapter: DataAdapters;
|
|
486
|
+
widgetUrl: string;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
export default function createApp(config: AppConfig) {
|
|
490
|
+
const { app } = init(config);
|
|
491
|
+
|
|
492
|
+
// Enable CORS for all origins in development
|
|
493
|
+
app.use("*", cors({
|
|
494
|
+
origin: (origin) => origin || "*",
|
|
495
|
+
allowMethods: ["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"],
|
|
496
|
+
allowHeaders: ["Content-Type", "Authorization", "Auth0-Client"],
|
|
497
|
+
exposeHeaders: ["Content-Length"],
|
|
498
|
+
credentials: true,
|
|
499
|
+
}));
|
|
500
|
+
|
|
501
|
+
app
|
|
502
|
+
.onError((err, ctx) => {
|
|
503
|
+
if (err && typeof err === "object" && "getResponse" in err) {
|
|
504
|
+
return (err as { getResponse: () => Response }).getResponse();
|
|
505
|
+
}
|
|
506
|
+
console.error(err);
|
|
507
|
+
return ctx.text(err instanceof Error ? err.message : "Internal Server Error", 500);
|
|
508
|
+
})
|
|
509
|
+
.get("/", async (ctx: Context) => {
|
|
510
|
+
return ctx.json({
|
|
511
|
+
name: "AuthHero Server (AWS)",
|
|
512
|
+
status: "running",
|
|
513
|
+
});
|
|
514
|
+
})
|
|
515
|
+
.get("/docs", swaggerUI({ url: "/api/v2/spec" }))
|
|
516
|
+
// Redirect widget requests to S3/CloudFront
|
|
517
|
+
.get("/u/widget/*", async (ctx) => {
|
|
518
|
+
const file = ctx.req.path.replace("/u/widget/", "");
|
|
519
|
+
return ctx.redirect(\`\${config.widgetUrl}/u/widget/\${file}\`);
|
|
520
|
+
})
|
|
521
|
+
.get("/u/*", async (ctx) => {
|
|
522
|
+
const file = ctx.req.path.replace("/u/", "");
|
|
523
|
+
return ctx.redirect(\`\${config.widgetUrl}/u/\${file}\`);
|
|
524
|
+
});
|
|
525
|
+
|
|
526
|
+
return app;
|
|
527
|
+
}
|
|
528
|
+
`;
|
|
529
|
+
}
|
|
530
|
+
function O(t) {
|
|
531
|
+
return `import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
|
|
532
|
+
import { DynamoDBDocumentClient } from "@aws-sdk/lib-dynamodb";
|
|
533
|
+
import createAdapters from "@authhero/aws";
|
|
534
|
+
import { seed } from "authhero";
|
|
535
|
+
|
|
536
|
+
async function main() {
|
|
537
|
+
const adminEmail = process.argv[2] || process.env.ADMIN_EMAIL;
|
|
538
|
+
const adminPassword = process.argv[3] || process.env.ADMIN_PASSWORD;
|
|
539
|
+
const tableName = process.argv[4] || process.env.TABLE_NAME;
|
|
540
|
+
|
|
541
|
+
if (!adminEmail || !adminPassword) {
|
|
542
|
+
console.error("Usage: npm run seed <email> <password>");
|
|
543
|
+
console.error(" or: ADMIN_EMAIL=... ADMIN_PASSWORD=... npm run seed");
|
|
544
|
+
process.exit(1);
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
if (!tableName) {
|
|
548
|
+
console.error("TABLE_NAME environment variable is required");
|
|
549
|
+
console.error("Run 'sst dev' first to get the table name from outputs");
|
|
550
|
+
process.exit(1);
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
const client = new DynamoDBClient({});
|
|
554
|
+
const docClient = DynamoDBDocumentClient.from(client, {
|
|
555
|
+
marshallOptions: {
|
|
556
|
+
removeUndefinedValues: true,
|
|
557
|
+
},
|
|
558
|
+
});
|
|
559
|
+
|
|
560
|
+
const adapters = createAdapters(docClient, { tableName });
|
|
561
|
+
|
|
562
|
+
await seed(adapters, {
|
|
563
|
+
adminEmail,
|
|
564
|
+
adminPassword,
|
|
565
|
+
tenantId: "${t ? "control_plane" : "main"}",
|
|
566
|
+
tenantName: "${t ? "Control Plane" : "Main"}",
|
|
567
|
+
isControlPlane: ${t},
|
|
568
|
+
});
|
|
569
|
+
|
|
570
|
+
console.log("✅ Database seeded successfully!");
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
main().catch(console.error);
|
|
574
|
+
`;
|
|
575
|
+
}
|
|
576
|
+
function $(t, e) {
|
|
577
|
+
const r = i.join(t, "src");
|
|
578
|
+
a.writeFileSync(
|
|
579
|
+
i.join(r, "app.ts"),
|
|
580
|
+
j(e)
|
|
581
|
+
), a.writeFileSync(
|
|
582
|
+
i.join(r, "seed.ts"),
|
|
583
|
+
O(e)
|
|
584
|
+
);
|
|
585
|
+
}
|
|
586
|
+
function C(t) {
|
|
587
|
+
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");
|
|
588
|
+
}
|
|
589
|
+
function H(t) {
|
|
381
590
|
const e = i.join(t, ".github", "workflows");
|
|
382
|
-
|
|
383
|
-
const
|
|
591
|
+
a.mkdirSync(e, { recursive: !0 });
|
|
592
|
+
const r = `name: Unit tests
|
|
384
593
|
|
|
385
594
|
on: push
|
|
386
595
|
|
|
@@ -401,7 +610,7 @@ jobs:
|
|
|
401
610
|
|
|
402
611
|
- run: npm run type-check
|
|
403
612
|
- run: npm test
|
|
404
|
-
`,
|
|
613
|
+
`, s = `name: Deploy to Dev
|
|
405
614
|
|
|
406
615
|
on:
|
|
407
616
|
push:
|
|
@@ -466,9 +675,9 @@ jobs:
|
|
|
466
675
|
apiToken: \${{ secrets.PROD_CLOUDFLARE_API_TOKEN }}
|
|
467
676
|
command: deploy --env production
|
|
468
677
|
`;
|
|
469
|
-
|
|
678
|
+
a.writeFileSync(i.join(e, "unit-tests.yml"), r), a.writeFileSync(i.join(e, "deploy-dev.yml"), s), a.writeFileSync(i.join(e, "release.yml"), n), console.log("\\n📦 GitHub CI workflows created!");
|
|
470
679
|
}
|
|
471
|
-
function
|
|
680
|
+
function U(t) {
|
|
472
681
|
const e = {
|
|
473
682
|
branches: ["main"],
|
|
474
683
|
plugins: [
|
|
@@ -477,109 +686,115 @@ function $(t) {
|
|
|
477
686
|
"@semantic-release/github"
|
|
478
687
|
]
|
|
479
688
|
};
|
|
480
|
-
|
|
689
|
+
a.writeFileSync(
|
|
481
690
|
i.join(t, ".releaserc.json"),
|
|
482
691
|
JSON.stringify(e, null, 2)
|
|
483
692
|
);
|
|
484
|
-
const
|
|
485
|
-
|
|
486
|
-
...
|
|
693
|
+
const r = i.join(t, "package.json"), s = JSON.parse(a.readFileSync(r, "utf-8"));
|
|
694
|
+
s.devDependencies = {
|
|
695
|
+
...s.devDependencies,
|
|
487
696
|
"semantic-release": "^24.0.0"
|
|
488
|
-
},
|
|
489
|
-
...
|
|
697
|
+
}, s.scripts = {
|
|
698
|
+
...s.scripts,
|
|
490
699
|
test: 'echo "No tests yet"',
|
|
491
700
|
"type-check": "tsc --noEmit"
|
|
492
|
-
},
|
|
701
|
+
}, a.writeFileSync(r, JSON.stringify(s, null, 2));
|
|
493
702
|
}
|
|
494
703
|
function v(t, e) {
|
|
495
|
-
return new Promise((
|
|
496
|
-
const n =
|
|
704
|
+
return new Promise((r, s) => {
|
|
705
|
+
const n = N(t, [], {
|
|
497
706
|
cwd: e,
|
|
498
707
|
shell: !0,
|
|
499
708
|
stdio: "inherit"
|
|
500
709
|
});
|
|
501
|
-
n.on("close", (
|
|
502
|
-
|
|
503
|
-
}), n.on("error",
|
|
710
|
+
n.on("close", (o) => {
|
|
711
|
+
o === 0 ? r() : s(new Error(`Command failed with exit code ${o}`));
|
|
712
|
+
}), n.on("error", s);
|
|
504
713
|
});
|
|
505
714
|
}
|
|
506
|
-
function
|
|
507
|
-
return new Promise((
|
|
508
|
-
const
|
|
715
|
+
function D(t, e, r) {
|
|
716
|
+
return new Promise((s, n) => {
|
|
717
|
+
const o = N(t, [], {
|
|
509
718
|
cwd: e,
|
|
510
719
|
shell: !0,
|
|
511
720
|
stdio: "inherit",
|
|
512
|
-
env: { ...process.env, ...
|
|
721
|
+
env: { ...process.env, ...r }
|
|
513
722
|
});
|
|
514
|
-
|
|
515
|
-
c === 0 ?
|
|
516
|
-
}),
|
|
723
|
+
o.on("close", (c) => {
|
|
724
|
+
c === 0 ? s() : n(new Error(`Command failed with exit code ${c}`));
|
|
725
|
+
}), o.on("error", n);
|
|
517
726
|
});
|
|
518
727
|
}
|
|
519
|
-
function
|
|
520
|
-
const
|
|
521
|
-
|
|
522
|
-
i.join(
|
|
523
|
-
|
|
524
|
-
),
|
|
525
|
-
i.join(
|
|
526
|
-
|
|
728
|
+
function F(t, e) {
|
|
729
|
+
const r = i.join(t, "src");
|
|
730
|
+
a.writeFileSync(
|
|
731
|
+
i.join(r, "app.ts"),
|
|
732
|
+
M(e)
|
|
733
|
+
), a.writeFileSync(
|
|
734
|
+
i.join(r, "seed.ts"),
|
|
735
|
+
L(e)
|
|
527
736
|
);
|
|
528
737
|
}
|
|
529
|
-
function
|
|
738
|
+
function I(t) {
|
|
530
739
|
console.log(`
|
|
531
740
|
` + "─".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) + `
|
|
532
741
|
`);
|
|
533
742
|
}
|
|
534
|
-
function
|
|
743
|
+
function x(t) {
|
|
535
744
|
console.log(`
|
|
536
745
|
` + "─".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) + `
|
|
537
746
|
`);
|
|
538
747
|
}
|
|
539
|
-
|
|
748
|
+
b.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(
|
|
540
749
|
"--package-manager <pm>",
|
|
541
750
|
"package manager to use: npm, yarn, pnpm, or bun"
|
|
542
751
|
).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) => {
|
|
543
|
-
const
|
|
752
|
+
const r = e.yes === !0;
|
|
544
753
|
console.log(`
|
|
545
754
|
🔐 Welcome to AuthHero!
|
|
546
755
|
`);
|
|
547
|
-
let
|
|
548
|
-
|
|
756
|
+
let s = t;
|
|
757
|
+
s || (r ? (s = "auth-server", console.log(`Using default project name: ${s}`)) : s = (await u.prompt([
|
|
549
758
|
{
|
|
550
759
|
type: "input",
|
|
551
760
|
name: "projectName",
|
|
552
761
|
message: "Project name:",
|
|
553
762
|
default: "auth-server",
|
|
554
|
-
validate: (
|
|
763
|
+
validate: (d) => d !== "" || "Project name cannot be empty"
|
|
555
764
|
}
|
|
556
765
|
])).projectName);
|
|
557
|
-
const n = i.join(process.cwd(),
|
|
558
|
-
|
|
559
|
-
let
|
|
560
|
-
e.template ? (["local", "cloudflare"].includes(e.template) || (console.error(`❌ Invalid template: ${e.template}`), console.error("Valid options: local, cloudflare"), process.exit(1)),
|
|
766
|
+
const n = i.join(process.cwd(), s);
|
|
767
|
+
a.existsSync(n) && (console.error(`❌ Project "${s}" already exists.`), process.exit(1));
|
|
768
|
+
let o;
|
|
769
|
+
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([
|
|
561
770
|
{
|
|
562
771
|
type: "list",
|
|
563
772
|
name: "setupType",
|
|
564
773
|
message: "Select your setup type:",
|
|
565
774
|
choices: [
|
|
566
775
|
{
|
|
567
|
-
name: `${
|
|
568
|
-
${
|
|
776
|
+
name: `${p.local.name}
|
|
777
|
+
${p.local.description}`,
|
|
569
778
|
value: "local",
|
|
570
|
-
short:
|
|
779
|
+
short: p.local.name
|
|
571
780
|
},
|
|
572
781
|
{
|
|
573
|
-
name: `${
|
|
574
|
-
${
|
|
782
|
+
name: `${p.cloudflare.name}
|
|
783
|
+
${p.cloudflare.description}`,
|
|
575
784
|
value: "cloudflare",
|
|
576
|
-
short:
|
|
785
|
+
short: p.cloudflare.name
|
|
786
|
+
},
|
|
787
|
+
{
|
|
788
|
+
name: `${p["aws-sst"].name}
|
|
789
|
+
${p["aws-sst"].description}`,
|
|
790
|
+
value: "aws-sst",
|
|
791
|
+
short: p["aws-sst"].name
|
|
577
792
|
}
|
|
578
793
|
]
|
|
579
794
|
}
|
|
580
795
|
])).setupType;
|
|
581
796
|
let c;
|
|
582
|
-
e.multiTenant !== void 0 ? (c = e.multiTenant, console.log(`Multi-tenant mode: ${c ? "enabled" : "disabled"}`)) :
|
|
797
|
+
e.multiTenant !== void 0 ? (c = e.multiTenant, console.log(`Multi-tenant mode: ${c ? "enabled" : "disabled"}`)) : r ? c = !1 : c = (await u.prompt([
|
|
583
798
|
{
|
|
584
799
|
type: "confirm",
|
|
585
800
|
name: "multiTenant",
|
|
@@ -588,56 +803,57 @@ I.version("1.0.0").description("Create a new AuthHero project").argument("[proje
|
|
|
588
803
|
default: !1
|
|
589
804
|
}
|
|
590
805
|
])).multiTenant;
|
|
591
|
-
const
|
|
592
|
-
|
|
806
|
+
const A = p[o];
|
|
807
|
+
a.mkdirSync(n, { recursive: !0 }), a.writeFileSync(
|
|
593
808
|
i.join(n, "package.json"),
|
|
594
|
-
JSON.stringify(
|
|
809
|
+
JSON.stringify(A.packageJson(s, c), null, 2)
|
|
595
810
|
);
|
|
596
|
-
const
|
|
811
|
+
const _ = A.templateDir, S = i.join(
|
|
597
812
|
import.meta.url.replace("file://", "").replace("/create-authhero.js", ""),
|
|
598
|
-
|
|
813
|
+
_
|
|
599
814
|
);
|
|
600
|
-
if (
|
|
601
|
-
const l = i.join(n, "wrangler.toml"),
|
|
602
|
-
|
|
603
|
-
const
|
|
604
|
-
|
|
815
|
+
if (a.existsSync(S) ? E(S, n) : (console.error(`❌ Template directory not found: ${S}`), process.exit(1)), o === "cloudflare" && F(n, c), o === "cloudflare") {
|
|
816
|
+
const l = i.join(n, "wrangler.toml"), d = i.join(n, "wrangler.local.toml");
|
|
817
|
+
a.existsSync(l) && a.copyFileSync(l, d);
|
|
818
|
+
const m = i.join(n, ".dev.vars.example"), g = i.join(n, ".dev.vars");
|
|
819
|
+
a.existsSync(m) && a.copyFileSync(m, g), console.log(
|
|
605
820
|
"📁 Created wrangler.local.toml and .dev.vars for local development"
|
|
606
821
|
);
|
|
607
822
|
}
|
|
608
823
|
let w = !1;
|
|
609
|
-
if (
|
|
824
|
+
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([
|
|
610
825
|
{
|
|
611
826
|
type: "confirm",
|
|
612
827
|
name: "includeGithubCi",
|
|
613
828
|
message: "Would you like to include GitHub CI with semantic versioning?",
|
|
614
829
|
default: !1
|
|
615
830
|
}
|
|
616
|
-
])).includeGithubCi), w && (
|
|
617
|
-
const l =
|
|
618
|
-
|
|
619
|
-
const
|
|
620
|
-
|
|
831
|
+
])).includeGithubCi), w && (H(n), U(n))), o === "local") {
|
|
832
|
+
const l = P(c);
|
|
833
|
+
a.writeFileSync(i.join(n, "src/seed.ts"), l);
|
|
834
|
+
const d = R(c);
|
|
835
|
+
a.writeFileSync(i.join(n, "src/app.ts"), d);
|
|
621
836
|
}
|
|
622
|
-
|
|
837
|
+
o === "aws-sst" && $(n, c);
|
|
838
|
+
const T = c ? "multi-tenant" : "single-tenant";
|
|
623
839
|
console.log(
|
|
624
840
|
`
|
|
625
|
-
✅ Project "${
|
|
841
|
+
✅ Project "${s}" has been created with ${A.name} (${T}) setup!
|
|
626
842
|
`
|
|
627
843
|
);
|
|
628
|
-
let
|
|
629
|
-
if (e.skipInstall ?
|
|
844
|
+
let h;
|
|
845
|
+
if (e.skipInstall ? h = !1 : r ? h = !0 : h = (await u.prompt([
|
|
630
846
|
{
|
|
631
847
|
type: "confirm",
|
|
632
848
|
name: "shouldInstall",
|
|
633
849
|
message: "Would you like to install dependencies now?",
|
|
634
850
|
default: !0
|
|
635
851
|
}
|
|
636
|
-
])).shouldInstall,
|
|
852
|
+
])).shouldInstall, h) {
|
|
637
853
|
let l;
|
|
638
854
|
e.packageManager ? (["npm", "yarn", "pnpm", "bun"].includes(e.packageManager) || (console.error(
|
|
639
855
|
`❌ Invalid package manager: ${e.packageManager}`
|
|
640
|
-
), console.error("Valid options: npm, yarn, pnpm, bun"), process.exit(1)), l = e.packageManager) :
|
|
856
|
+
), console.error("Valid options: npm, yarn, pnpm, bun"), process.exit(1)), l = e.packageManager) : r ? l = "pnpm" : l = (await u.prompt([
|
|
641
857
|
{
|
|
642
858
|
type: "list",
|
|
643
859
|
name: "packageManager",
|
|
@@ -654,14 +870,14 @@ I.version("1.0.0").description("Create a new AuthHero project").argument("[proje
|
|
|
654
870
|
📦 Installing dependencies with ${l}...
|
|
655
871
|
`);
|
|
656
872
|
try {
|
|
657
|
-
const
|
|
658
|
-
if (await v(
|
|
873
|
+
const d = l === "pnpm" ? "pnpm install --ignore-workspace" : `${l} install`;
|
|
874
|
+
if (await v(d, n), o === "local" && (console.log(`
|
|
659
875
|
🔧 Building native modules...
|
|
660
876
|
`), await v("npm rebuild better-sqlite3", n)), console.log(`
|
|
661
877
|
✅ Dependencies installed successfully!
|
|
662
|
-
`),
|
|
878
|
+
`), o === "local" || o === "cloudflare") {
|
|
663
879
|
let g;
|
|
664
|
-
if (e.skipMigrate && e.skipSeed ? g = !1 :
|
|
880
|
+
if (e.skipMigrate && e.skipSeed ? g = !1 : r ? g = !e.skipMigrate || !e.skipSeed : g = (await u.prompt([
|
|
665
881
|
{
|
|
666
882
|
type: "confirm",
|
|
667
883
|
name: "shouldSetup",
|
|
@@ -669,11 +885,11 @@ I.version("1.0.0").description("Create a new AuthHero project").argument("[proje
|
|
|
669
885
|
default: !0
|
|
670
886
|
}
|
|
671
887
|
])).shouldSetup, g) {
|
|
672
|
-
let
|
|
673
|
-
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)),
|
|
888
|
+
let f;
|
|
889
|
+
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 = {
|
|
674
890
|
username: e.email,
|
|
675
891
|
password: e.password
|
|
676
|
-
}, console.log(`Using admin email: ${e.email}`)) :
|
|
892
|
+
}, console.log(`Using admin email: ${e.email}`)) : f = await u.prompt([
|
|
677
893
|
{
|
|
678
894
|
type: "input",
|
|
679
895
|
name: "username",
|
|
@@ -692,49 +908,51 @@ I.version("1.0.0").description("Create a new AuthHero project").argument("[proje
|
|
|
692
908
|
🔄 Running migrations...
|
|
693
909
|
`), await v(`${l} run migrate`, n)), e.skipSeed || (console.log(`
|
|
694
910
|
🌱 Seeding database...
|
|
695
|
-
`),
|
|
911
|
+
`), o === "local" ? await D(
|
|
696
912
|
`${l} run seed`,
|
|
697
913
|
n,
|
|
698
914
|
{
|
|
699
|
-
ADMIN_EMAIL:
|
|
700
|
-
ADMIN_PASSWORD:
|
|
915
|
+
ADMIN_EMAIL: f.username,
|
|
916
|
+
ADMIN_PASSWORD: f.password
|
|
701
917
|
}
|
|
702
|
-
) : await
|
|
918
|
+
) : await D(
|
|
703
919
|
`${l} run seed:local`,
|
|
704
920
|
n,
|
|
705
921
|
{
|
|
706
|
-
ADMIN_EMAIL:
|
|
707
|
-
ADMIN_PASSWORD:
|
|
922
|
+
ADMIN_EMAIL: f.username,
|
|
923
|
+
ADMIN_PASSWORD: f.password
|
|
708
924
|
}
|
|
709
925
|
));
|
|
710
926
|
}
|
|
711
927
|
}
|
|
712
|
-
let
|
|
713
|
-
e.skipStart ||
|
|
928
|
+
let m;
|
|
929
|
+
e.skipStart || r ? m = !1 : m = (await u.prompt([
|
|
714
930
|
{
|
|
715
931
|
type: "confirm",
|
|
716
932
|
name: "shouldStart",
|
|
717
933
|
message: "Would you like to start the development server?",
|
|
718
934
|
default: !0
|
|
719
935
|
}
|
|
720
|
-
])).shouldStart,
|
|
721
|
-
`), await v(`${l} run dev`, n)),
|
|
936
|
+
])).shouldStart, m && (o === "cloudflare" ? I(c) : o === "aws-sst" ? C(c) : x(c), console.log(`🚀 Starting development server...
|
|
937
|
+
`), await v(`${l} run dev`, n)), r && !m && (console.log(`
|
|
722
938
|
✅ Setup complete!`), console.log(`
|
|
723
|
-
To start the development server:`), console.log(` cd ${
|
|
724
|
-
} catch (
|
|
939
|
+
To start the development server:`), console.log(` cd ${s}`), console.log(" npm run dev"), o === "cloudflare" ? I(c) : o === "aws-sst" ? C(c) : x(c));
|
|
940
|
+
} catch (d) {
|
|
725
941
|
console.error(`
|
|
726
|
-
❌ An error occurred:`,
|
|
942
|
+
❌ An error occurred:`, d), process.exit(1);
|
|
727
943
|
}
|
|
728
944
|
}
|
|
729
|
-
|
|
945
|
+
h || (console.log("Next steps:"), console.log(` cd ${s}`), o === "local" ? (console.log(" npm install"), console.log(" npm run migrate"), console.log(
|
|
730
946
|
" ADMIN_EMAIL=admin@example.com ADMIN_PASSWORD=yourpassword npm run seed"
|
|
731
|
-
), console.log(" npm run dev")) :
|
|
947
|
+
), console.log(" npm run dev")) : o === "cloudflare" ? (console.log(" npm install"), console.log(
|
|
732
948
|
" npm run migrate # or npm run db:migrate:remote for production"
|
|
733
949
|
), console.log(
|
|
734
950
|
" ADMIN_EMAIL=admin@example.com ADMIN_PASSWORD=yourpassword npm run seed"
|
|
735
|
-
), console.log(" npm run dev # or npm run dev:remote for production")), console.log(
|
|
951
|
+
), 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(
|
|
952
|
+
" TABLE_NAME=<your-table> ADMIN_EMAIL=admin@example.com ADMIN_PASSWORD=yourpassword npm run seed"
|
|
953
|
+
)), console.log(`
|
|
736
954
|
Server will be available at: https://localhost:3000`), console.log("Portal available at: https://local.authhero.net"), console.log(`
|
|
737
955
|
For more information, visit: https://authhero.net/docs
|
|
738
956
|
`));
|
|
739
957
|
});
|
|
740
|
-
|
|
958
|
+
b.parse(process.argv);
|