create-headroom 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,58 @@
1
+ # create-headroom
2
+
3
+ Scaffold a new [Headroom CMS](https://github.com/cykod/headroom) project.
4
+
5
+ ## Usage
6
+
7
+ ```bash
8
+ npm create headroom my-cms
9
+ ```
10
+
11
+ Or with npx:
12
+
13
+ ```bash
14
+ npx create-headroom my-cms
15
+ ```
16
+
17
+ The CLI will prompt for your sender email address (used for admin invitations and password resets), then generate a ready-to-deploy project.
18
+
19
+ ## What It Does
20
+
21
+ 1. Prompts for your **sender email** (must be verified in AWS SES)
22
+ 2. Copies the project template to `my-cms/`
23
+ 3. Substitutes your project name and email into the config files
24
+ 4. Runs `pnpm install` (falls back to `npm install`)
25
+
26
+ ## Generated Project Structure
27
+
28
+ ```
29
+ my-cms/
30
+ ├── sst.config.ts # SST config — imports HeadroomCMS component
31
+ ├── package.json # Dependencies: headroom-cms + sst
32
+ ├── tsconfig.json # TypeScript config
33
+ ├── sst-env.d.ts # SST type declarations
34
+ ├── .gitignore
35
+ └── scripts/
36
+ ├── create-admin.sh # Create a Cognito admin user
37
+ └── get-token.sh # Get a JWT token for API testing
38
+ ```
39
+
40
+ The `sst.config.ts` is the main file you'll edit to configure your CMS. See the [headroom-cms configuration reference](https://github.com/cykod/headroom/tree/main/packages/headroom-cms#configuration-reference) for all available options.
41
+
42
+ ## Next Steps
43
+
44
+ ```bash
45
+ cd my-cms
46
+
47
+ # Deploy to AWS
48
+ npx sst deploy --stage production
49
+
50
+ # Create your first admin user
51
+ ./scripts/create-admin.sh admin@example.com 'YourPassword123!'
52
+ ```
53
+
54
+ After deploying, open the Admin URL from the deploy output and sign in.
55
+
56
+ ## License
57
+
58
+ PolyForm Noncommercial 1.0.0
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+ export declare function copyDir(src: string, dest: string): void;
3
+ export declare function substituteFile(filePath: string, replacements: Record<string, string>): void;
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAWA,wBAAgB,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAWvD;AAED,wBAAgB,cAAc,CAC5B,QAAQ,EAAE,MAAM,EAChB,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GACnC,IAAI,CAMN"}
package/dist/index.js ADDED
@@ -0,0 +1,100 @@
1
+ #!/usr/bin/env node
2
+ import { execSync } from "child_process";
3
+ import * as fs from "fs";
4
+ import * as path from "path";
5
+ import * as readline from "readline";
6
+ import { fileURLToPath } from "url";
7
+ const __filename = fileURLToPath(import.meta.url);
8
+ const __dirname = path.dirname(__filename);
9
+ export function copyDir(src, dest) {
10
+ fs.mkdirSync(dest, { recursive: true });
11
+ for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
12
+ const srcPath = path.join(src, entry.name);
13
+ const destPath = path.join(dest, entry.name);
14
+ if (entry.isDirectory()) {
15
+ copyDir(srcPath, destPath);
16
+ }
17
+ else {
18
+ fs.copyFileSync(srcPath, destPath);
19
+ }
20
+ }
21
+ }
22
+ export function substituteFile(filePath, replacements) {
23
+ let content = fs.readFileSync(filePath, "utf-8");
24
+ for (const [placeholder, value] of Object.entries(replacements)) {
25
+ content = content.replaceAll(placeholder, value);
26
+ }
27
+ fs.writeFileSync(filePath, content);
28
+ }
29
+ async function main() {
30
+ const projectName = process.argv[2];
31
+ if (!projectName) {
32
+ console.error("Usage: create-headroom <project-name>");
33
+ process.exit(1);
34
+ }
35
+ const targetDir = path.resolve(process.cwd(), projectName);
36
+ if (fs.existsSync(targetDir)) {
37
+ console.error(`Error: Directory "${projectName}" already exists.`);
38
+ process.exit(1);
39
+ }
40
+ const rl = readline.createInterface({
41
+ input: process.stdin,
42
+ output: process.stdout,
43
+ });
44
+ const ask = (q) => new Promise((resolve) => rl.question(q, resolve));
45
+ console.log("\n Headroom CMS Setup\n");
46
+ const senderEmail = await ask(" Sender email for admin invitations: ");
47
+ rl.close();
48
+ const templateDir = path.resolve(__dirname, "../template");
49
+ // Copy template
50
+ copyDir(templateDir, targetDir);
51
+ // Substitute placeholders in all relevant files
52
+ const replacements = {
53
+ __APP_NAME__: projectName,
54
+ __SENDER_EMAIL__: senderEmail,
55
+ };
56
+ substituteFile(path.join(targetDir, "sst.config.ts"), replacements);
57
+ substituteFile(path.join(targetDir, "package.json"), replacements);
58
+ // Make scripts executable
59
+ const scriptsDir = path.join(targetDir, "scripts");
60
+ if (fs.existsSync(scriptsDir)) {
61
+ for (const file of fs.readdirSync(scriptsDir)) {
62
+ fs.chmodSync(path.join(scriptsDir, file), 0o755);
63
+ }
64
+ }
65
+ // Install dependencies
66
+ console.log("\n Installing dependencies...\n");
67
+ try {
68
+ execSync("pnpm install", { cwd: targetDir, stdio: "inherit" });
69
+ }
70
+ catch {
71
+ try {
72
+ execSync("npm install", { cwd: targetDir, stdio: "inherit" });
73
+ }
74
+ catch {
75
+ console.log("\n Could not install dependencies automatically. Run 'pnpm install' or 'npm install' manually.\n");
76
+ }
77
+ }
78
+ console.log(`
79
+ Done! Your Headroom CMS project is ready.
80
+
81
+ Next steps:
82
+ cd ${projectName}
83
+ npx sst dev # Start development
84
+ npx sst deploy --stage production # Deploy to production
85
+
86
+ After deploying, create your first admin user:
87
+ ./scripts/create-admin.sh admin@example.com 'YourPassword123!'
88
+
89
+ Documentation: https://github.com/cykod/headroom
90
+ `);
91
+ }
92
+ const isDirectRun = process.argv[1] &&
93
+ path.resolve(process.argv[1]) === path.resolve(__filename);
94
+ if (isDirectRun) {
95
+ main().catch((err) => {
96
+ console.error(err);
97
+ process.exit(1);
98
+ });
99
+ }
100
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "create-headroom",
3
+ "version": "0.1.1",
4
+ "description": "Create a new Headroom CMS project",
5
+ "type": "module",
6
+ "bin": {
7
+ "create-headroom": "dist/index.js"
8
+ },
9
+ "files": [
10
+ "dist/",
11
+ "template/"
12
+ ],
13
+ "scripts": {
14
+ "build": "tsc",
15
+ "test": "vitest run"
16
+ },
17
+ "devDependencies": {
18
+ "@types/node": "^22.0.0",
19
+ "typescript": "^5.9.0",
20
+ "vitest": "^3.2.1"
21
+ },
22
+ "keywords": [
23
+ "cms",
24
+ "headless",
25
+ "serverless",
26
+ "sst",
27
+ "aws",
28
+ "create"
29
+ ],
30
+ "license": "PolyForm-Noncommercial-1.0.0"
31
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "__APP_NAME__",
3
+ "version": "1.0.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "sst dev",
8
+ "deploy": "sst deploy",
9
+ "remove": "sst remove"
10
+ },
11
+ "dependencies": {
12
+ "headroom-cms": "^0.1.1"
13
+ },
14
+ "devDependencies": {
15
+ "sst": "^3.17.0",
16
+ "typescript": "^5.9.0"
17
+ }
18
+ }
@@ -0,0 +1,61 @@
1
+ #!/bin/bash
2
+ # Create an admin user in Cognito
3
+ #
4
+ # Usage:
5
+ # ./scripts/create-admin.sh <email> <password>
6
+ #
7
+ # Example:
8
+ # ./scripts/create-admin.sh admin@example.com 'SecurePass123!'
9
+
10
+ set -euo pipefail
11
+
12
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
13
+ PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
14
+ OUTPUTS_FILE="$PROJECT_ROOT/.sst/outputs.json"
15
+
16
+ if [ ! -f "$OUTPUTS_FILE" ]; then
17
+ echo "Error: $OUTPUTS_FILE not found. Run 'npx sst deploy' first." >&2
18
+ exit 1
19
+ fi
20
+
21
+ USER_POOL_ID=$(jq -r '.userPoolId' "$OUTPUTS_FILE")
22
+ CLIENT_ID=$(jq -r '.userPoolClientId' "$OUTPUTS_FILE")
23
+ ADMIN_URL=$(jq -r '.admin' "$OUTPUTS_FILE")
24
+
25
+ if [ -z "$1" ] || [ -z "$2" ]; then
26
+ echo "Usage: $0 <email> <password>" >&2
27
+ echo "" >&2
28
+ echo "Create an admin user in Cognito." >&2
29
+ echo "Password must be 12+ chars with uppercase, lowercase, numbers, and symbols." >&2
30
+ echo "" >&2
31
+ echo "User Pool ID: $USER_POOL_ID" >&2
32
+ echo "Client ID: $CLIENT_ID" >&2
33
+ exit 1
34
+ fi
35
+
36
+ EMAIL="$1"
37
+ PASSWORD="$2"
38
+
39
+ echo "Creating user $EMAIL..."
40
+
41
+ aws cognito-idp admin-create-user \
42
+ --user-pool-id "$USER_POOL_ID" \
43
+ --username "$EMAIL" \
44
+ --user-attributes Name=email,Value="$EMAIL" Name=email_verified,Value=true \
45
+ --message-action SUPPRESS \
46
+ > /dev/null
47
+
48
+ echo "Setting permanent password..."
49
+
50
+ aws cognito-idp admin-set-user-password \
51
+ --user-pool-id "$USER_POOL_ID" \
52
+ --username "$EMAIL" \
53
+ --password "$PASSWORD" \
54
+ --permanent
55
+
56
+ echo "Done! User $EMAIL created successfully."
57
+ echo ""
58
+ echo "Admin UI: $ADMIN_URL"
59
+ echo ""
60
+ echo "To get an access token:"
61
+ echo " ./scripts/get-token.sh '$EMAIL' '$PASSWORD'"
@@ -0,0 +1,44 @@
1
+ #!/bin/bash
2
+ # Get Cognito access token for admin API testing
3
+ #
4
+ # Usage:
5
+ # ./scripts/get-token.sh <email> <password>
6
+ #
7
+ # Example:
8
+ # export TOKEN=$(./scripts/get-token.sh admin@example.com 'YourPassword123!')
9
+ # curl -H "Authorization: Bearer $TOKEN" "$API_URL/v1/admin/sites"
10
+
11
+ set -euo pipefail
12
+
13
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
14
+ PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
15
+ OUTPUTS_FILE="$PROJECT_ROOT/.sst/outputs.json"
16
+
17
+ if [ ! -f "$OUTPUTS_FILE" ]; then
18
+ echo "Error: $OUTPUTS_FILE not found. Run 'npx sst deploy' first." >&2
19
+ exit 1
20
+ fi
21
+
22
+ USER_POOL_ID=$(jq -r '.userPoolId' "$OUTPUTS_FILE")
23
+ CLIENT_ID=$(jq -r '.userPoolClientId' "$OUTPUTS_FILE")
24
+
25
+ if [ -z "$1" ] || [ -z "$2" ]; then
26
+ echo "Usage: $0 <email> <password>" >&2
27
+ echo "" >&2
28
+ echo "Get a Cognito access token for testing admin API endpoints." >&2
29
+ echo "" >&2
30
+ echo "User Pool ID: $USER_POOL_ID" >&2
31
+ echo "Client ID: $CLIENT_ID" >&2
32
+ exit 1
33
+ fi
34
+
35
+ EMAIL="$1"
36
+ PASSWORD="$2"
37
+
38
+ aws cognito-idp admin-initiate-auth \
39
+ --user-pool-id "$USER_POOL_ID" \
40
+ --client-id "$CLIENT_ID" \
41
+ --auth-flow ADMIN_USER_PASSWORD_AUTH \
42
+ --auth-parameters "USERNAME=$EMAIL,PASSWORD=$PASSWORD" \
43
+ --query 'AuthenticationResult.AccessToken' \
44
+ --output text
@@ -0,0 +1,20 @@
1
+ /// <reference path="./.sst/platform/config.d.ts" />
2
+ import { HeadroomCMS } from "headroom-cms";
3
+
4
+ export default $config({
5
+ app(input) {
6
+ return {
7
+ name: "__APP_NAME__",
8
+ removal: input?.stage === "production" ? "retain" : "remove",
9
+ protect: input?.stage === "production",
10
+ home: "aws",
11
+ };
12
+ },
13
+ async run() {
14
+ const cms = new HeadroomCMS("CMS", {
15
+ senderEmail: "__SENDER_EMAIL__",
16
+ });
17
+
18
+ return cms.outputs;
19
+ },
20
+ });
@@ -0,0 +1,11 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ES2022",
5
+ "moduleResolution": "bundler",
6
+ "strict": true,
7
+ "esModuleInterop": true,
8
+ "skipLibCheck": true,
9
+ "forceConsistentCasingInFileNames": true
10
+ }
11
+ }