@agility/create-next-app 1.0.0-beta.2 → 1.0.0-beta.3
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/templates/copyTemplates.d.ts.map +1 -1
- package/dist/templates/copyTemplates.js +35 -7
- package/dist/templates/copyTemplates.js.map +1 -1
- package/package.json +2 -2
- package/src/templates/copyTemplates.ts +42 -7
- package/templates/README.md +36 -2
- package/templates/data/redirections.json +5 -0
- package/templates/lib/cms-content/checkRedirect.ts +35 -0
- package/templates/lib/cms-content/rebuildRedirectCache.ts +29 -0
- package/templates/node/prebuild.ts +16 -0
- package/templates/proxy.ts +32 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"copyTemplates.d.ts","sourceRoot":"","sources":["../../src/templates/copyTemplates.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"copyTemplates.d.ts","sourceRoot":"","sources":["../../src/templates/copyTemplates.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAS3C;;;;GAIG;AACH,wBAAsB,aAAa,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAmD3F"}
|
|
@@ -8,6 +8,10 @@ const fs_1 = __importDefault(require("fs"));
|
|
|
8
8
|
const path_1 = __importDefault(require("path"));
|
|
9
9
|
const ora_1 = __importDefault(require("ora"));
|
|
10
10
|
const copyDirectory_1 = require("./copyDirectory");
|
|
11
|
+
// Folders that should be copied to project root instead of src/
|
|
12
|
+
const ROOT_FOLDERS = ['.claude', 'node', 'data'];
|
|
13
|
+
// Files that should be copied to project root instead of src/
|
|
14
|
+
const ROOT_FILES = [];
|
|
11
15
|
/**
|
|
12
16
|
* Copies template files to the project
|
|
13
17
|
* @param projectPath - Path to the project
|
|
@@ -22,17 +26,22 @@ async function copyTemplates(projectPath, options) {
|
|
|
22
26
|
if (!fs_1.default.existsSync(srcDir)) {
|
|
23
27
|
fs_1.default.mkdirSync(srcDir, { recursive: true });
|
|
24
28
|
}
|
|
25
|
-
// Copy all template files to src directory, except
|
|
29
|
+
// Copy all template files to src directory, except special folders/files
|
|
26
30
|
const entries = fs_1.default.readdirSync(templatesDir, { withFileTypes: true });
|
|
27
31
|
for (const entry of entries) {
|
|
28
32
|
const srcPath = path_1.default.join(templatesDir, entry.name);
|
|
29
|
-
//
|
|
30
|
-
if (entry.name
|
|
31
|
-
const destPath = path_1.default.join(projectPath,
|
|
32
|
-
if (
|
|
33
|
-
fs_1.default.
|
|
33
|
+
// Check if this should go to project root
|
|
34
|
+
if (ROOT_FOLDERS.includes(entry.name) || ROOT_FILES.includes(entry.name)) {
|
|
35
|
+
const destPath = path_1.default.join(projectPath, entry.name);
|
|
36
|
+
if (entry.isDirectory()) {
|
|
37
|
+
if (!fs_1.default.existsSync(destPath)) {
|
|
38
|
+
fs_1.default.mkdirSync(destPath, { recursive: true });
|
|
39
|
+
}
|
|
40
|
+
await (0, copyDirectory_1.copyDirectory)(srcPath, destPath);
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
fs_1.default.copyFileSync(srcPath, destPath);
|
|
34
44
|
}
|
|
35
|
-
await (0, copyDirectory_1.copyDirectory)(srcPath, destPath);
|
|
36
45
|
}
|
|
37
46
|
else {
|
|
38
47
|
// All other files go to src directory
|
|
@@ -48,6 +57,8 @@ async function copyTemplates(projectPath, options) {
|
|
|
48
57
|
}
|
|
49
58
|
}
|
|
50
59
|
}
|
|
60
|
+
// Add prebuild script to package.json
|
|
61
|
+
await addPrebuildScript(projectPath);
|
|
51
62
|
spinner.succeed('Template files copied');
|
|
52
63
|
}
|
|
53
64
|
catch (error) {
|
|
@@ -55,4 +66,21 @@ async function copyTemplates(projectPath, options) {
|
|
|
55
66
|
throw error;
|
|
56
67
|
}
|
|
57
68
|
}
|
|
69
|
+
/**
|
|
70
|
+
* Adds the prebuild script to package.json
|
|
71
|
+
*/
|
|
72
|
+
async function addPrebuildScript(projectPath) {
|
|
73
|
+
const packageJsonPath = path_1.default.join(projectPath, 'package.json');
|
|
74
|
+
if (fs_1.default.existsSync(packageJsonPath)) {
|
|
75
|
+
const packageJson = JSON.parse(fs_1.default.readFileSync(packageJsonPath, 'utf-8'));
|
|
76
|
+
// Add prebuild script
|
|
77
|
+
packageJson.scripts = packageJson.scripts || {};
|
|
78
|
+
packageJson.scripts.prebuild = 'npx tsx node/prebuild.ts';
|
|
79
|
+
// Update build script to run prebuild first
|
|
80
|
+
if (packageJson.scripts.build && !packageJson.scripts.build.includes('prebuild')) {
|
|
81
|
+
packageJson.scripts.build = 'npm run prebuild && next build';
|
|
82
|
+
}
|
|
83
|
+
fs_1.default.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n');
|
|
84
|
+
}
|
|
85
|
+
}
|
|
58
86
|
//# sourceMappingURL=copyTemplates.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"copyTemplates.js","sourceRoot":"","sources":["../../src/templates/copyTemplates.ts"],"names":[],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"copyTemplates.js","sourceRoot":"","sources":["../../src/templates/copyTemplates.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agility/create-next-app",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.3",
|
|
4
4
|
"description": "Create a new Next.js project with Agility CMS integration",
|
|
5
5
|
"main": "dist/cli/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -42,4 +42,4 @@
|
|
|
42
42
|
"engines": {
|
|
43
43
|
"node": ">=18.0.0"
|
|
44
44
|
}
|
|
45
|
-
}
|
|
45
|
+
}
|
|
@@ -4,6 +4,12 @@ import ora from 'ora';
|
|
|
4
4
|
import type { CliOptions } from '../types';
|
|
5
5
|
import { copyDirectory } from './copyDirectory';
|
|
6
6
|
|
|
7
|
+
// Folders that should be copied to project root instead of src/
|
|
8
|
+
const ROOT_FOLDERS = ['.claude', 'node', 'data'];
|
|
9
|
+
|
|
10
|
+
// Files that should be copied to project root instead of src/
|
|
11
|
+
const ROOT_FILES: string[] = [];
|
|
12
|
+
|
|
7
13
|
/**
|
|
8
14
|
* Copies template files to the project
|
|
9
15
|
* @param projectPath - Path to the project
|
|
@@ -21,19 +27,23 @@ export async function copyTemplates(projectPath: string, options: CliOptions): P
|
|
|
21
27
|
fs.mkdirSync(srcDir, { recursive: true });
|
|
22
28
|
}
|
|
23
29
|
|
|
24
|
-
// Copy all template files to src directory, except
|
|
30
|
+
// Copy all template files to src directory, except special folders/files
|
|
25
31
|
const entries = fs.readdirSync(templatesDir, { withFileTypes: true });
|
|
26
32
|
|
|
27
33
|
for (const entry of entries) {
|
|
28
34
|
const srcPath = path.join(templatesDir, entry.name);
|
|
29
35
|
|
|
30
|
-
//
|
|
31
|
-
if (entry.name
|
|
32
|
-
const destPath = path.join(projectPath,
|
|
33
|
-
if (
|
|
34
|
-
fs.
|
|
36
|
+
// Check if this should go to project root
|
|
37
|
+
if (ROOT_FOLDERS.includes(entry.name) || ROOT_FILES.includes(entry.name)) {
|
|
38
|
+
const destPath = path.join(projectPath, entry.name);
|
|
39
|
+
if (entry.isDirectory()) {
|
|
40
|
+
if (!fs.existsSync(destPath)) {
|
|
41
|
+
fs.mkdirSync(destPath, { recursive: true });
|
|
42
|
+
}
|
|
43
|
+
await copyDirectory(srcPath, destPath);
|
|
44
|
+
} else {
|
|
45
|
+
fs.copyFileSync(srcPath, destPath);
|
|
35
46
|
}
|
|
36
|
-
await copyDirectory(srcPath, destPath);
|
|
37
47
|
} else {
|
|
38
48
|
// All other files go to src directory
|
|
39
49
|
const destPath = path.join(srcDir, entry.name);
|
|
@@ -48,6 +58,9 @@ export async function copyTemplates(projectPath: string, options: CliOptions): P
|
|
|
48
58
|
}
|
|
49
59
|
}
|
|
50
60
|
|
|
61
|
+
// Add prebuild script to package.json
|
|
62
|
+
await addPrebuildScript(projectPath);
|
|
63
|
+
|
|
51
64
|
spinner.succeed('Template files copied');
|
|
52
65
|
} catch (error) {
|
|
53
66
|
spinner.fail('Failed to copy template files');
|
|
@@ -55,3 +68,25 @@ export async function copyTemplates(projectPath: string, options: CliOptions): P
|
|
|
55
68
|
}
|
|
56
69
|
}
|
|
57
70
|
|
|
71
|
+
/**
|
|
72
|
+
* Adds the prebuild script to package.json
|
|
73
|
+
*/
|
|
74
|
+
async function addPrebuildScript(projectPath: string): Promise<void> {
|
|
75
|
+
const packageJsonPath = path.join(projectPath, 'package.json');
|
|
76
|
+
|
|
77
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
78
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
|
|
79
|
+
|
|
80
|
+
// Add prebuild script
|
|
81
|
+
packageJson.scripts = packageJson.scripts || {};
|
|
82
|
+
packageJson.scripts.prebuild = 'npx tsx node/prebuild.ts';
|
|
83
|
+
|
|
84
|
+
// Update build script to run prebuild first
|
|
85
|
+
if (packageJson.scripts.build && !packageJson.scripts.build.includes('prebuild')) {
|
|
86
|
+
packageJson.scripts.build = 'npm run prebuild && next build';
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n');
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
package/templates/README.md
CHANGED
|
@@ -141,6 +141,9 @@ AGILITY_API_FETCH_KEY=your-fetch-api-key
|
|
|
141
141
|
AGILITY_API_PREVIEW_KEY=your-preview-api-key
|
|
142
142
|
AGILITY_LOCALES=en-us
|
|
143
143
|
AGILITY_SITEMAP=website
|
|
144
|
+
|
|
145
|
+
# Optional: Build Hook URL for redirect rebuilds (see Deployment section)
|
|
146
|
+
BUILD_HOOK_URL=https://your-build-hook-url
|
|
144
147
|
```
|
|
145
148
|
|
|
146
149
|
## 🎯 Common Tasks
|
|
@@ -180,7 +183,8 @@ const posts = await getContentList({
|
|
|
180
183
|
|
|
181
184
|
```bash
|
|
182
185
|
npm run dev # Start development server
|
|
183
|
-
npm run
|
|
186
|
+
npm run prebuild # Rebuild redirect cache from Agility CMS
|
|
187
|
+
npm run build # Build for production (runs prebuild automatically)
|
|
184
188
|
npm run start # Start production server
|
|
185
189
|
npm run lint # Run ESLint
|
|
186
190
|
```
|
|
@@ -193,6 +197,7 @@ npm run lint # Run ESLint
|
|
|
193
197
|
- ✅ **TypeScript** - Full type safety
|
|
194
198
|
- ✅ **Caching** - ISR with on-demand revalidation
|
|
195
199
|
- ✅ **Preview Mode** - See draft content before publishing
|
|
200
|
+
- ✅ **URL Redirects** - Managed in Agility CMS, applied via middleware
|
|
196
201
|
- ✅ **AI-Friendly Docs** - Optimized for all AI coding tools
|
|
197
202
|
|
|
198
203
|
## 🔗 Key Concepts
|
|
@@ -209,6 +214,16 @@ Collections like blog posts, testimonials, team members. Fetched with `getConten
|
|
|
209
214
|
### Locales
|
|
210
215
|
Multi-language support. Default locale (en-us) has clean URLs, others are prefixed (/fr/, /es/).
|
|
211
216
|
|
|
217
|
+
### URL Redirects
|
|
218
|
+
URL redirects are managed in Agility CMS (Settings > URL Redirections) and applied via Next.js middleware.
|
|
219
|
+
|
|
220
|
+
**How it works:**
|
|
221
|
+
1. The `prebuild` script fetches all redirects from Agility CMS and saves them to `data/redirections.json`
|
|
222
|
+
2. The middleware checks incoming requests against this cache
|
|
223
|
+
3. When a redirect is published in Agility CMS, the revalidate webhook triggers a rebuild (if `BUILD_HOOK_URL` is configured)
|
|
224
|
+
|
|
225
|
+
**Important:** Redirects are cached at build time. Changes to redirects in Agility CMS require a rebuild to take effect. Configure a build hook to automate this.
|
|
226
|
+
|
|
212
227
|
## 📖 Learn More
|
|
213
228
|
|
|
214
229
|
### Documentation
|
|
@@ -245,9 +260,28 @@ Set these in your hosting platform:
|
|
|
245
260
|
- `AGILITY_GUID`
|
|
246
261
|
- `AGILITY_API_FETCH_KEY`
|
|
247
262
|
- `AGILITY_API_PREVIEW_KEY`
|
|
248
|
-
- `AGILITY_SECURITY_KEY` (for
|
|
263
|
+
- `AGILITY_SECURITY_KEY` (required for preview mode and Web Studio)
|
|
249
264
|
- `AGILITY_LOCALES`
|
|
250
265
|
- `AGILITY_SITEMAP`
|
|
266
|
+
- `BUILD_HOOK_URL` (optional, for automatic redirect rebuilds)
|
|
267
|
+
|
|
268
|
+
> **Important:** To enable preview mode and Web Studio integration from Agility CMS, you must add the **Security Key** from your Agility CMS instance to the `AGILITY_SECURITY_KEY` environment variable on your deployment platform. You can find this key in Agility CMS under **Settings > API Keys > Security Key**.
|
|
269
|
+
|
|
270
|
+
### Configure Build Hook for URL Redirects
|
|
271
|
+
|
|
272
|
+
When URL redirects are changed in Agility CMS, the site needs to rebuild to pick up the changes. To automate this:
|
|
273
|
+
|
|
274
|
+
**Vercel:**
|
|
275
|
+
1. Go to your project Settings > Git > Deploy Hooks
|
|
276
|
+
2. Create a new hook (e.g., "Agility Redirect Rebuild")
|
|
277
|
+
3. Copy the hook URL and add it to your environment variables as `BUILD_HOOK_URL`
|
|
278
|
+
|
|
279
|
+
**Netlify:**
|
|
280
|
+
1. Go to Site Settings > Build & deploy > Build hooks
|
|
281
|
+
2. Add a new build hook
|
|
282
|
+
3. Copy the hook URL and add it to your environment variables as `BUILD_HOOK_URL`
|
|
283
|
+
|
|
284
|
+
When a redirect is published in Agility CMS (not a page or content item), the `/api/revalidate` webhook will automatically trigger this build hook.
|
|
251
285
|
|
|
252
286
|
## 🐛 Troubleshooting
|
|
253
287
|
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { Redirection, RedirectionsMap } from "../cms/getRedirections"
|
|
2
|
+
|
|
3
|
+
// Import redirections from the prebuild-generated JSON file
|
|
4
|
+
// This file is generated by running `npm run prebuild` before building
|
|
5
|
+
let allRedirects: RedirectionsMap = { lastAccessDate: "", isUpToDate: false, items: {} }
|
|
6
|
+
|
|
7
|
+
try {
|
|
8
|
+
// Dynamic import at build time - the file may not exist during development
|
|
9
|
+
allRedirects = require("../../../data/redirections.json") as RedirectionsMap
|
|
10
|
+
} catch {
|
|
11
|
+
// File doesn't exist yet - that's OK during development or first build
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Check if a path should be redirected.
|
|
16
|
+
* Uses the prebuild-generated redirections.json file for fast lookups.
|
|
17
|
+
*
|
|
18
|
+
* Inspired by: https://nextjs.org/docs/app/building-your-application/routing/redirecting#managing-redirects-at-scale-advanced
|
|
19
|
+
*
|
|
20
|
+
* @param path - The URL path to check for redirects
|
|
21
|
+
* @returns The redirection if found, null otherwise
|
|
22
|
+
*/
|
|
23
|
+
export const checkRedirect = async ({ path }: { path: string }): Promise<Redirection | null> => {
|
|
24
|
+
// Don't redirect the root path
|
|
25
|
+
if (path === "/") return null
|
|
26
|
+
|
|
27
|
+
// Get the redirections from the cached file
|
|
28
|
+
const redirections = allRedirects
|
|
29
|
+
if (!redirections || !redirections.items) return null
|
|
30
|
+
|
|
31
|
+
// Look up the path (case-insensitive)
|
|
32
|
+
const redirection = redirections.items[path.toLowerCase()]
|
|
33
|
+
|
|
34
|
+
return redirection || null
|
|
35
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import fs from 'fs/promises'
|
|
2
|
+
import { getRedirections } from '../cms/getRedirections'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Rebuild the redirection cache.
|
|
6
|
+
* This should be called during prebuild (before `next build`) to ensure
|
|
7
|
+
* redirects are available at runtime in the middleware.
|
|
8
|
+
*
|
|
9
|
+
* Inspired by: https://nextjs.org/docs/app/building-your-application/routing/redirecting#managing-redirects-at-scale-advanced
|
|
10
|
+
*/
|
|
11
|
+
export const rebuildRedirectCache = async () => {
|
|
12
|
+
console.log("Agility CMS => Rebuilding redirect cache...")
|
|
13
|
+
try {
|
|
14
|
+
// Force a rebuild of the redirection cache from Agility CMS
|
|
15
|
+
const redirections = await getRedirections({ forceUpdate: true })
|
|
16
|
+
|
|
17
|
+
const allKeys = Object.keys(redirections.items)
|
|
18
|
+
console.log(`Agility CMS => Found ${allKeys.length} redirects`)
|
|
19
|
+
|
|
20
|
+
// Save the redirections to the data folder for use in middleware
|
|
21
|
+
const fileName = 'data/redirections.json'
|
|
22
|
+
await fs.writeFile(fileName, JSON.stringify(redirections, null, 2), 'utf8')
|
|
23
|
+
|
|
24
|
+
console.log("Agility CMS => Redirect cache rebuilt successfully")
|
|
25
|
+
|
|
26
|
+
} catch (error) {
|
|
27
|
+
console.error("Error rebuilding redirect cache", error)
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { rebuildRedirectCache } from "../src/lib/cms-content/rebuildRedirectCache"
|
|
2
|
+
|
|
3
|
+
require("dotenv").config({
|
|
4
|
+
path: `.env.local`,
|
|
5
|
+
})
|
|
6
|
+
|
|
7
|
+
const doWork = async () => {
|
|
8
|
+
console.log("Agility CMS => Prebuild Started")
|
|
9
|
+
|
|
10
|
+
// Rebuild the redirects cache from Agility CMS
|
|
11
|
+
await rebuildRedirectCache()
|
|
12
|
+
|
|
13
|
+
console.log("Agility CMS => Prebuild Complete")
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
doWork()
|
package/templates/proxy.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { NextResponse } from 'next/server'
|
|
2
2
|
import type { NextRequest } from 'next/server'
|
|
3
3
|
import { defaultLocale, locales, isValidLocale, getLocaleFromPathname, removeLocaleFromPathname } from './lib/i18n/config'
|
|
4
|
+
import { checkRedirect } from './lib/cms-content/checkRedirect'
|
|
4
5
|
|
|
5
6
|
export async function proxy(request: NextRequest) {
|
|
6
7
|
/*****************************
|
|
@@ -9,6 +10,7 @@ export async function proxy(request: NextRequest) {
|
|
|
9
10
|
* 2: Check if we are exiting preview
|
|
10
11
|
* 3: Check if this is a direct to a dynamic page
|
|
11
12
|
* based on a content id
|
|
13
|
+
* 4: Check for URL redirects from Agility CMS
|
|
12
14
|
*******************************/
|
|
13
15
|
|
|
14
16
|
let pathname = request.nextUrl.pathname
|
|
@@ -38,6 +40,36 @@ export async function proxy(request: NextRequest) {
|
|
|
38
40
|
return NextResponse.rewrite(dynredirectUrl)
|
|
39
41
|
}
|
|
40
42
|
} else if ((!ext || ext.length === 0)) {
|
|
43
|
+
|
|
44
|
+
/**********************
|
|
45
|
+
* CHECK FOR REDIRECT *
|
|
46
|
+
**********************/
|
|
47
|
+
const redirection = await checkRedirect({ path: request.nextUrl.pathname })
|
|
48
|
+
|
|
49
|
+
if (redirection) {
|
|
50
|
+
// Redirect to the destination URL
|
|
51
|
+
// Cache the redirect for 10 minutes (relative) or 1 hour (absolute)
|
|
52
|
+
if (redirection.destinationUrl.startsWith("/")) {
|
|
53
|
+
// Handle relative paths
|
|
54
|
+
const url = request.nextUrl.clone()
|
|
55
|
+
url.pathname = redirection.destinationUrl
|
|
56
|
+
return NextResponse.redirect(url, {
|
|
57
|
+
status: redirection.statusCode,
|
|
58
|
+
headers: {
|
|
59
|
+
"Cache-Control": "public, max-age=600, stale-while-revalidate"
|
|
60
|
+
}
|
|
61
|
+
})
|
|
62
|
+
} else {
|
|
63
|
+
// Handle absolute paths (external URLs)
|
|
64
|
+
return NextResponse.redirect(redirection.destinationUrl, {
|
|
65
|
+
status: redirection.statusCode,
|
|
66
|
+
headers: {
|
|
67
|
+
"Cache-Control": "public, max-age=3600, stale-while-revalidate"
|
|
68
|
+
}
|
|
69
|
+
})
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
41
73
|
/**************************************
|
|
42
74
|
* SPECIAL CASE FOR lang= QUERY PARAM *
|
|
43
75
|
**************************************/
|