@timbenniks/contentstack-platform-app-scaffold 0.1.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.
Files changed (51) hide show
  1. package/dist/commands/scaffold.d.ts +19 -0
  2. package/dist/commands/scaffold.d.ts.map +1 -0
  3. package/dist/commands/scaffold.js +83 -0
  4. package/dist/commands/scaffold.js.map +1 -0
  5. package/dist/index.d.ts +2 -0
  6. package/dist/index.d.ts.map +1 -0
  7. package/dist/index.js +9 -0
  8. package/dist/index.js.map +1 -0
  9. package/dist/services/prompts.d.ts +3 -0
  10. package/dist/services/prompts.d.ts.map +1 -0
  11. package/dist/services/prompts.js +154 -0
  12. package/dist/services/prompts.js.map +1 -0
  13. package/dist/services/scaffold.d.ts +3 -0
  14. package/dist/services/scaffold.d.ts.map +1 -0
  15. package/dist/services/scaffold.js +119 -0
  16. package/dist/services/scaffold.js.map +1 -0
  17. package/dist/types.d.ts +49 -0
  18. package/dist/types.d.ts.map +1 -0
  19. package/dist/types.js +16 -0
  20. package/dist/types.js.map +1 -0
  21. package/dist/utils/git.d.ts +5 -0
  22. package/dist/utils/git.d.ts.map +1 -0
  23. package/dist/utils/git.js +33 -0
  24. package/dist/utils/git.js.map +1 -0
  25. package/dist/utils/package-manager.d.ts +8 -0
  26. package/dist/utils/package-manager.d.ts.map +1 -0
  27. package/dist/utils/package-manager.js +40 -0
  28. package/dist/utils/package-manager.js.map +1 -0
  29. package/dist/utils/template-engine.d.ts +13 -0
  30. package/dist/utils/template-engine.d.ts.map +1 -0
  31. package/dist/utils/template-engine.js +87 -0
  32. package/dist/utils/template-engine.js.map +1 -0
  33. package/oclif.manifest.json +101 -0
  34. package/package.json +40 -0
  35. package/templates/nextjs/.env.example.hbs +22 -0
  36. package/templates/nextjs/app/api/auth/[...nextauth]/route.ts.hbs +3 -0
  37. package/templates/nextjs/app/api/brandkit/[...path]/route.ts.hbs +35 -0
  38. package/templates/nextjs/app/api/cma/[...path]/route.ts.hbs +35 -0
  39. package/templates/nextjs/app/api/launch/[...path]/route.ts.hbs +34 -0
  40. package/templates/nextjs/app/assets/page.tsx.hbs +25 -0
  41. package/templates/nextjs/app/brandkit/page.tsx.hbs +43 -0
  42. package/templates/nextjs/app/entries/page.tsx.hbs +26 -0
  43. package/templates/nextjs/app/launch/page.tsx.hbs +43 -0
  44. package/templates/nextjs/app/layout.tsx.hbs +20 -0
  45. package/templates/nextjs/app/page.tsx.hbs +55 -0
  46. package/templates/nextjs/app/providers.tsx.hbs +11 -0
  47. package/templates/nextjs/lib/auth.ts.hbs +12 -0
  48. package/templates/nextjs/manifest.json +19 -0
  49. package/templates/nextjs/next.config.js +4 -0
  50. package/templates/nextjs/package.json.hbs +24 -0
  51. package/templates/nextjs/tsconfig.json +21 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"template-engine.d.ts","sourceRoot":"","sources":["../../src/utils/template-engine.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,UAAU,CAAA;AAY/C,wEAAwE;AACxE,wBAAgB,cAAc,CAAC,cAAc,EAAE,MAAM,EAAE,OAAO,EAAE,eAAe,GAAG,MAAM,CAGvF;AA4CD;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAC7B,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,eAAe,GACvB,IAAI,CAgCN"}
@@ -0,0 +1,87 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.renderTemplate = renderTemplate;
7
+ exports.renderDirectory = renderDirectory;
8
+ const node_fs_1 = require("node:fs");
9
+ const node_path_1 = require("node:path");
10
+ const handlebars_1 = __importDefault(require("handlebars"));
11
+ /** Render a single Handlebars template string with the given context */
12
+ function renderTemplate(templateString, context) {
13
+ const template = handlebars_1.default.compile(templateString, { noEscape: true });
14
+ return template(context);
15
+ }
16
+ /** Walk a directory recursively and return all file paths relative to root */
17
+ function walkDirectory(dir, root = dir) {
18
+ const results = [];
19
+ for (const entry of (0, node_fs_1.readdirSync)(dir, { withFileTypes: true })) {
20
+ const fullPath = (0, node_path_1.join)(dir, entry.name);
21
+ if (entry.isDirectory()) {
22
+ results.push(...walkDirectory(fullPath, root));
23
+ }
24
+ else {
25
+ results.push((0, node_path_1.relative)(root, fullPath));
26
+ }
27
+ }
28
+ return results;
29
+ }
30
+ /** Check whether a file should be included based on feature flags and manifest */
31
+ function shouldIncludeFile(relPath, manifest, context) {
32
+ if (!manifest)
33
+ return true;
34
+ const entry = manifest.find((m) => m.path === relPath);
35
+ if (!entry)
36
+ return true;
37
+ if (entry.requiredFeatures && entry.requiredFeatures.length > 0) {
38
+ const hasAllRequired = entry.requiredFeatures.every((feature) => context.features[feature] === true);
39
+ if (!hasAllRequired)
40
+ return false;
41
+ }
42
+ if (entry.anyFeatures && entry.anyFeatures.length > 0) {
43
+ const hasAnyRequired = entry.anyFeatures.some((feature) => context.features[feature] === true);
44
+ if (!hasAnyRequired)
45
+ return false;
46
+ }
47
+ return true;
48
+ }
49
+ /**
50
+ * Render an entire template directory into an output directory.
51
+ *
52
+ * - Files ending in `.hbs` are rendered through Handlebars and the `.hbs` extension is stripped.
53
+ * - All other files are copied as-is.
54
+ * - Files listed in `manifest.json` with `requiredFeatures` are skipped if the features are not selected.
55
+ * - `manifest.json` itself is never copied to the output.
56
+ */
57
+ function renderDirectory(templateDir, outputDir, context) {
58
+ let manifest;
59
+ try {
60
+ const manifestContent = (0, node_fs_1.readFileSync)((0, node_path_1.join)(templateDir, "manifest.json"), "utf-8");
61
+ manifest = JSON.parse(manifestContent);
62
+ }
63
+ catch {
64
+ manifest = undefined;
65
+ }
66
+ const files = walkDirectory(templateDir);
67
+ for (const relPath of files) {
68
+ if (relPath === "manifest.json")
69
+ continue;
70
+ const outputRelPath = relPath.endsWith(".hbs") ? relPath.slice(0, -4) : relPath;
71
+ if (!shouldIncludeFile(outputRelPath, manifest, context))
72
+ continue;
73
+ const inputPath = (0, node_path_1.join)(templateDir, relPath);
74
+ const outputPath = (0, node_path_1.join)(outputDir, outputRelPath);
75
+ (0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(outputPath), { recursive: true });
76
+ if (relPath.endsWith(".hbs")) {
77
+ const templateContent = (0, node_fs_1.readFileSync)(inputPath, "utf-8");
78
+ const rendered = renderTemplate(templateContent, context);
79
+ (0, node_fs_1.writeFileSync)(outputPath, rendered, "utf-8");
80
+ }
81
+ else {
82
+ const content = (0, node_fs_1.readFileSync)(inputPath);
83
+ (0, node_fs_1.writeFileSync)(outputPath, content);
84
+ }
85
+ }
86
+ }
87
+ //# sourceMappingURL=template-engine.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"template-engine.js","sourceRoot":"","sources":["../../src/utils/template-engine.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,101 @@
1
+ {
2
+ "commands": {
3
+ "scaffold": {
4
+ "aliases": [],
5
+ "args": {
6
+ "name": {
7
+ "description": "Project directory name",
8
+ "name": "name",
9
+ "required": false
10
+ }
11
+ },
12
+ "description": "Scaffold a Contentstack app using the Platform SDK",
13
+ "examples": [
14
+ "<%= config.bin %> scaffold my-app",
15
+ "<%= config.bin %> scaffold my-app --framework nextjs --region eu",
16
+ "<%= config.bin %> scaffold my-app --features cma,oauth --no-install"
17
+ ],
18
+ "flags": {
19
+ "framework": {
20
+ "description": "Framework (nextjs)",
21
+ "name": "framework",
22
+ "hasDynamicHelp": false,
23
+ "multiple": false,
24
+ "options": [
25
+ "nextjs"
26
+ ],
27
+ "type": "option"
28
+ },
29
+ "region": {
30
+ "description": "Contentstack region (us|eu|au|azure-na|azure-eu|gcp-na|gcp-eu)",
31
+ "name": "region",
32
+ "hasDynamicHelp": false,
33
+ "multiple": false,
34
+ "options": [
35
+ "us",
36
+ "eu",
37
+ "au",
38
+ "azure-na",
39
+ "azure-eu",
40
+ "gcp-na",
41
+ "gcp-eu"
42
+ ],
43
+ "type": "option"
44
+ },
45
+ "features": {
46
+ "description": "Comma-separated features: cma,oauth,launch,brandkit",
47
+ "name": "features",
48
+ "hasDynamicHelp": false,
49
+ "multiple": false,
50
+ "type": "option"
51
+ },
52
+ "scopes": {
53
+ "description": "Comma-separated OAuth scopes",
54
+ "name": "scopes",
55
+ "hasDynamicHelp": false,
56
+ "multiple": false,
57
+ "type": "option"
58
+ },
59
+ "package-manager": {
60
+ "description": "Package manager (npm|yarn|pnpm)",
61
+ "name": "package-manager",
62
+ "hasDynamicHelp": false,
63
+ "multiple": false,
64
+ "options": [
65
+ "npm",
66
+ "yarn",
67
+ "pnpm"
68
+ ],
69
+ "type": "option"
70
+ },
71
+ "no-git": {
72
+ "description": "Skip git initialization",
73
+ "name": "no-git",
74
+ "allowNo": false,
75
+ "type": "boolean"
76
+ },
77
+ "no-install": {
78
+ "description": "Skip dependency installation",
79
+ "name": "no-install",
80
+ "allowNo": false,
81
+ "type": "boolean"
82
+ }
83
+ },
84
+ "hasDynamicHelp": false,
85
+ "hiddenAliases": [],
86
+ "id": "scaffold",
87
+ "pluginAlias": "@timbenniks/contentstack-platform-app-scaffold",
88
+ "pluginName": "@timbenniks/contentstack-platform-app-scaffold",
89
+ "pluginType": "core",
90
+ "strict": true,
91
+ "enableJsonFlag": false,
92
+ "isESM": false,
93
+ "relativePath": [
94
+ "dist",
95
+ "commands",
96
+ "scaffold.js"
97
+ ]
98
+ }
99
+ },
100
+ "version": "0.1.0"
101
+ }
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "@timbenniks/contentstack-platform-app-scaffold",
3
+ "version": "0.1.0",
4
+ "description": "Contentstack CLI plugin to scaffold apps using the Platform SDK",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "oclif": {
8
+ "commands": {
9
+ "strategy": "pattern",
10
+ "target": "./dist/commands"
11
+ },
12
+ "bin": "csdx"
13
+ },
14
+ "files": ["dist", "templates", "oclif.manifest.json"],
15
+ "scripts": {
16
+ "build": "tsc",
17
+ "postbuild": "oclif manifest",
18
+ "test": "vitest run",
19
+ "test:watch": "vitest",
20
+ "test:coverage": "vitest run --coverage",
21
+ "lint": "biome check src/",
22
+ "typecheck": "tsc --noEmit"
23
+ },
24
+ "dependencies": {
25
+ "@oclif/core": "^4.8.0",
26
+ "@clack/prompts": "^0.9.0",
27
+ "picocolors": "^1.1.0",
28
+ "handlebars": "^4.7.0"
29
+ },
30
+ "devDependencies": {
31
+ "@types/node": "^22.0.0",
32
+ "@vitest/coverage-v8": "^2.1.0",
33
+ "oclif": "^4.0.0",
34
+ "typescript": "^5.5.0",
35
+ "vitest": "^2.1.0"
36
+ },
37
+ "engines": {
38
+ "node": ">=18"
39
+ }
40
+ }
@@ -0,0 +1,22 @@
1
+ # Contentstack Configuration
2
+ CONTENTSTACK_REGION={{region}}
3
+ CONTENTSTACK_API_KEY=your_api_key
4
+ CONTENTSTACK_ORGANIZATION_UID=your_org_uid
5
+ CONTENTSTACK_AUTHTOKEN=your_authtoken # Optional: for Launch/Brand Kit when OAuth is not enabled
6
+ {{#if features.cma}}
7
+ {{#unless features.oauth}}
8
+ # CMA Access Token
9
+ CONTENTSTACK_MANAGEMENT_TOKEN=your_management_token
10
+ {{/unless}}
11
+ {{/if}}
12
+ {{#if features.oauth}}
13
+ # OAuth Credentials
14
+ CONTENTSTACK_APP_ID=your_app_id
15
+ CONTENTSTACK_CLIENT_ID=your_client_id
16
+ CONTENTSTACK_CLIENT_SECRET=your_client_secret
17
+ AUTH_SECRET=generate_a_random_secret
18
+ {{/if}}
19
+ {{#if features.brandkit}}
20
+ # Brand Kit
21
+ CONTENTSTACK_BRAND_KIT_UID=your_brand_kit_uid
22
+ {{/if}}
@@ -0,0 +1,3 @@
1
+ import { handlers } from "@/lib/auth"
2
+
3
+ export const { GET, POST } = handlers
@@ -0,0 +1,35 @@
1
+ import { createBrandKitProxy } from "@timbenniks/contentstack-platform-sdk/server/proxy"
2
+ import type { ContentstackRegion } from "@timbenniks/contentstack-platform-sdk/regions"
3
+ {{#if features.oauth}}
4
+ import { getAccessToken } from "@timbenniks/contentstack-platform-sdk/server/middleware"
5
+ import { auth } from "@/lib/auth"
6
+ {{/if}}
7
+
8
+ const proxy = createBrandKitProxy({
9
+ region: (process.env.CONTENTSTACK_REGION ?? "{{region}}") as ContentstackRegion,
10
+ organizationUid: process.env.CONTENTSTACK_ORGANIZATION_UID!,
11
+ brandKitUid: process.env.CONTENTSTACK_BRAND_KIT_UID!,
12
+ getAccessToken: async () => {
13
+ {{#if features.oauth}}
14
+ return await getAccessToken(auth)
15
+ {{else}}
16
+ return process.env.CONTENTSTACK_AUTHTOKEN ?? ""
17
+ {{/if}}
18
+ },
19
+ })
20
+
21
+ export async function GET(request: Request) {
22
+ return proxy(request)
23
+ }
24
+
25
+ export async function POST(request: Request) {
26
+ return proxy(request)
27
+ }
28
+
29
+ export async function PUT(request: Request) {
30
+ return proxy(request)
31
+ }
32
+
33
+ export async function DELETE(request: Request) {
34
+ return proxy(request)
35
+ }
@@ -0,0 +1,35 @@
1
+ import { createCMAProxy } from "@timbenniks/contentstack-platform-sdk/server/proxy"
2
+ import type { ContentstackRegion } from "@timbenniks/contentstack-platform-sdk/regions"
3
+ {{#if features.oauth}}
4
+ import { getAccessToken } from "@timbenniks/contentstack-platform-sdk/server/middleware"
5
+ import { auth } from "@/lib/auth"
6
+ {{/if}}
7
+
8
+ const proxy = createCMAProxy({
9
+ region: (process.env.CONTENTSTACK_REGION ?? "{{region}}") as ContentstackRegion,
10
+ apiKey: process.env.CONTENTSTACK_API_KEY!,
11
+ getAccessToken: async () => {
12
+ {{#if features.oauth}}
13
+ return await getAccessToken(auth)
14
+ {{else}}
15
+ return process.env.CONTENTSTACK_MANAGEMENT_TOKEN ?? ""
16
+ {{/if}}
17
+ },
18
+ allowedScopes: ["entries:read", "entries:write", "assets:read", "content-types:read"],
19
+ })
20
+
21
+ export async function GET(request: Request) {
22
+ return proxy(request)
23
+ }
24
+
25
+ export async function POST(request: Request) {
26
+ return proxy(request)
27
+ }
28
+
29
+ export async function PUT(request: Request) {
30
+ return proxy(request)
31
+ }
32
+
33
+ export async function DELETE(request: Request) {
34
+ return proxy(request)
35
+ }
@@ -0,0 +1,34 @@
1
+ import { createLaunchProxy } from "@timbenniks/contentstack-platform-sdk/server/proxy"
2
+ import type { ContentstackRegion } from "@timbenniks/contentstack-platform-sdk/regions"
3
+ {{#if features.oauth}}
4
+ import { getAccessToken } from "@timbenniks/contentstack-platform-sdk/server/middleware"
5
+ import { auth } from "@/lib/auth"
6
+ {{/if}}
7
+
8
+ const proxy = createLaunchProxy({
9
+ region: (process.env.CONTENTSTACK_REGION ?? "{{region}}") as ContentstackRegion,
10
+ organizationUid: process.env.CONTENTSTACK_ORGANIZATION_UID!,
11
+ getAccessToken: async () => {
12
+ {{#if features.oauth}}
13
+ return await getAccessToken(auth)
14
+ {{else}}
15
+ return process.env.CONTENTSTACK_AUTHTOKEN ?? ""
16
+ {{/if}}
17
+ },
18
+ })
19
+
20
+ export async function GET(request: Request) {
21
+ return proxy(request)
22
+ }
23
+
24
+ export async function POST(request: Request) {
25
+ return proxy(request)
26
+ }
27
+
28
+ export async function PUT(request: Request) {
29
+ return proxy(request)
30
+ }
31
+
32
+ export async function DELETE(request: Request) {
33
+ return proxy(request)
34
+ }
@@ -0,0 +1,25 @@
1
+ "use client"
2
+
3
+ import { useAssets } from "@timbenniks/contentstack-platform-sdk/react/hooks"
4
+
5
+ export default function AssetsPage() {
6
+ const { data: assets, loading, error } = useAssets()
7
+
8
+ return (
9
+ <main style=\{{ padding: "2rem", fontFamily: "system-ui, sans-serif" }}>
10
+ <h1>Assets</h1>
11
+
12
+ {loading && <p>Loading assets...</p>}
13
+ {error && <p>Error: {error.message}</p>}
14
+ {assets && (
15
+ <ul>
16
+ {assets.map((asset) => (
17
+ <li key={asset.uid}>{asset.title ?? asset.filename}</li>
18
+ ))}
19
+ </ul>
20
+ )}
21
+
22
+ <p><a href="/">Back to home</a></p>
23
+ </main>
24
+ )
25
+ }
@@ -0,0 +1,43 @@
1
+ "use client"
2
+
3
+ import type { BrandKit } from "@timbenniks/contentstack-platform-sdk/brandkit"
4
+ import { useBrandKit } from "@timbenniks/contentstack-platform-sdk/react/hooks"
5
+ import { useEffect, useState } from "react"
6
+
7
+ export default function BrandKitPage() {
8
+ const brandKit = useBrandKit()
9
+ const [brandKits, setBrandKits] = useState<BrandKit[]>([])
10
+ const [loading, setLoading] = useState(true)
11
+ const [error, setError] = useState<string | null>(null)
12
+
13
+ useEffect(() => {
14
+ brandKit.brandKits
15
+ .listAll()
16
+ .then(setBrandKits)
17
+ .catch((err: Error) => setError(err.message))
18
+ .finally(() => setLoading(false))
19
+ }, [brandKit])
20
+
21
+ return (
22
+ <main style=\{{ padding: "2rem", fontFamily: "system-ui, sans-serif" }}>
23
+ <h1>Brand Kits</h1>
24
+
25
+ {loading && <p>Loading brand kits...</p>}
26
+ {error && <p>Error: {error}</p>}
27
+
28
+ {!loading && !error && (
29
+ <ul>
30
+ {brandKits.map((kit) => (
31
+ <li key={kit.uid}>{kit.name}</li>
32
+ ))}
33
+ </ul>
34
+ )}
35
+
36
+ {!loading && !error && brandKits.length === 0 && (
37
+ <p>No brand kits found for this organization.</p>
38
+ )}
39
+
40
+ <p><a href="/">Back to home</a></p>
41
+ </main>
42
+ )
43
+ }
@@ -0,0 +1,26 @@
1
+ "use client"
2
+
3
+ import { useEntries } from "@timbenniks/contentstack-platform-sdk/react/hooks"
4
+
5
+ export default function EntriesPage() {
6
+ const { data: entries, loading, error } = useEntries("your_content_type")
7
+
8
+ return (
9
+ <main style=\{{ padding: "2rem", fontFamily: "system-ui, sans-serif" }}>
10
+ <h1>Entries</h1>
11
+ <p>Update <code>&quot;your_content_type&quot;</code> to a content type UID from your stack.</p>
12
+
13
+ {loading && <p>Loading entries...</p>}
14
+ {error && <p>Error: {error.message}</p>}
15
+ {entries && (
16
+ <ul>
17
+ {entries.map((entry) => (
18
+ <li key={entry.uid}>{entry.title ?? entry.uid}</li>
19
+ ))}
20
+ </ul>
21
+ )}
22
+
23
+ <p><a href="/">Back to home</a></p>
24
+ </main>
25
+ )
26
+ }
@@ -0,0 +1,43 @@
1
+ "use client"
2
+
3
+ import type { LaunchProject } from "@timbenniks/contentstack-platform-sdk/launch"
4
+ import { useLaunch } from "@timbenniks/contentstack-platform-sdk/react/hooks"
5
+ import { useEffect, useState } from "react"
6
+
7
+ export default function LaunchPage() {
8
+ const launch = useLaunch()
9
+ const [projects, setProjects] = useState<LaunchProject[]>([])
10
+ const [loading, setLoading] = useState(true)
11
+ const [error, setError] = useState<string | null>(null)
12
+
13
+ useEffect(() => {
14
+ launch.projects
15
+ .listAll()
16
+ .then(setProjects)
17
+ .catch((err: Error) => setError(err.message))
18
+ .finally(() => setLoading(false))
19
+ }, [launch])
20
+
21
+ return (
22
+ <main style=\{{ padding: "2rem", fontFamily: "system-ui, sans-serif" }}>
23
+ <h1>Launch Projects</h1>
24
+
25
+ {loading && <p>Loading projects...</p>}
26
+ {error && <p>Error: {error}</p>}
27
+
28
+ {!loading && !error && (
29
+ <ul>
30
+ {projects.map((project) => (
31
+ <li key={project.uid}>{project.name}</li>
32
+ ))}
33
+ </ul>
34
+ )}
35
+
36
+ {!loading && !error && projects.length === 0 && (
37
+ <p>No projects found in this organization.</p>
38
+ )}
39
+
40
+ <p><a href="/">Back to home</a></p>
41
+ </main>
42
+ )
43
+ }
@@ -0,0 +1,20 @@
1
+ import type { Metadata } from "next"
2
+ import type { ReactNode } from "react"
3
+ {{#if hasClientFeatures}}
4
+ import { Providers } from "./providers"
5
+ {{/if}}
6
+
7
+ export const metadata: Metadata = {
8
+ title: "{{projectName}}",
9
+ description: "Built with Contentstack Platform SDK",
10
+ }
11
+
12
+ export default function RootLayout({ children }: { children: ReactNode }) {
13
+ return (
14
+ <html lang="en">
15
+ <body>
16
+ {{#if hasClientFeatures}}<Providers>{children}</Providers>{{else}}{children}{{/if}}
17
+ </body>
18
+ </html>
19
+ )
20
+ }
@@ -0,0 +1,55 @@
1
+ {{#if features.oauth}}
2
+ import { auth } from "@/lib/auth"
3
+ {{/if}}
4
+
5
+ export default {{#if features.oauth}}async {{/if}}function Home() {
6
+ {{#if features.oauth}}
7
+ const session = await auth()
8
+ {{/if}}
9
+
10
+ return (
11
+ <main style=\{{ padding: "2rem", fontFamily: "system-ui, sans-serif" }}>
12
+ <h1>{{projectName}}</h1>
13
+ <p>Region: <code>{{region}}</code></p>
14
+ <p>Built with <a href="https://github.com/contentstack/platform-sdk">Contentstack Platform SDK</a></p>
15
+
16
+ <h2>Getting Started</h2>
17
+ <ol>
18
+ <li>Copy <code>.env.example</code> to <code>.env.local</code></li>
19
+ <li>Fill in your Contentstack credentials</li>
20
+ <li>Start building!</li>
21
+ </ol>
22
+ {{#if features.oauth}}
23
+
24
+ <h2>Authentication</h2>
25
+ {session ? (
26
+ <p>Signed in. <a href="/api/auth/signout">Sign out</a></p>
27
+ ) : (
28
+ <p><a href="/api/auth/signin">Sign in with Contentstack</a></p>
29
+ )}
30
+ {{/if}}
31
+ {{#if features.cma}}
32
+
33
+ <h2>Content Management</h2>
34
+ <ul>
35
+ <li><a href="/entries">Entries</a></li>
36
+ <li><a href="/assets">Assets</a></li>
37
+ </ul>
38
+ {{/if}}
39
+ {{#if features.launch}}
40
+
41
+ <h2>Launch</h2>
42
+ <ul>
43
+ <li><a href="/launch">Projects</a></li>
44
+ </ul>
45
+ {{/if}}
46
+ {{#if features.brandkit}}
47
+
48
+ <h2>Brand Kit AI</h2>
49
+ <ul>
50
+ <li><a href="/brandkit">Brand Kits</a></li>
51
+ </ul>
52
+ {{/if}}
53
+ </main>
54
+ )
55
+ }
@@ -0,0 +1,11 @@
1
+ "use client"
2
+
3
+ import { ContentstackProvider } from "@timbenniks/contentstack-platform-sdk/react/provider"
4
+
5
+ export function Providers({ children }: { children: React.ReactNode }) {
6
+ return (
7
+ <ContentstackProvider region="{{region}}">
8
+ {children}
9
+ </ContentstackProvider>
10
+ )
11
+ }
@@ -0,0 +1,12 @@
1
+ import { createContentstackAuth } from "@timbenniks/contentstack-platform-sdk/server/middleware"
2
+ import type { ContentstackRegion } from "@timbenniks/contentstack-platform-sdk/regions"
3
+
4
+ export const { handlers, auth } = await createContentstackAuth({
5
+ region: (process.env.CONTENTSTACK_REGION ?? "{{region}}") as ContentstackRegion,
6
+ appId: process.env.CONTENTSTACK_APP_ID!,
7
+ clientId: process.env.CONTENTSTACK_CLIENT_ID!,
8
+ clientSecret: process.env.CONTENTSTACK_CLIENT_SECRET!,
9
+ secret: process.env.AUTH_SECRET!,
10
+ scopes: [{{#each scopes}}"{{this}}"{{#unless @last}}, {{/unless}}{{/each}}],
11
+ trustHost: true,
12
+ })
@@ -0,0 +1,19 @@
1
+ [
2
+ { "path": "package.json" },
3
+ { "path": ".env.example" },
4
+ { "path": ".gitignore" },
5
+ { "path": "tsconfig.json" },
6
+ { "path": "next.config.js" },
7
+ { "path": "lib/auth.ts", "requiredFeatures": ["oauth"] },
8
+ { "path": "app/layout.tsx" },
9
+ { "path": "app/page.tsx" },
10
+ { "path": "app/providers.tsx", "anyFeatures": ["cma", "launch", "brandkit"] },
11
+ { "path": "app/api/auth/[...nextauth]/route.ts", "requiredFeatures": ["oauth"] },
12
+ { "path": "app/api/cma/[...path]/route.ts", "requiredFeatures": ["cma"] },
13
+ { "path": "app/api/launch/[...path]/route.ts", "requiredFeatures": ["launch"] },
14
+ { "path": "app/api/brandkit/[...path]/route.ts", "requiredFeatures": ["brandkit"] },
15
+ { "path": "app/entries/page.tsx", "requiredFeatures": ["cma"] },
16
+ { "path": "app/assets/page.tsx", "requiredFeatures": ["cma"] },
17
+ { "path": "app/launch/page.tsx", "requiredFeatures": ["launch"] },
18
+ { "path": "app/brandkit/page.tsx", "requiredFeatures": ["brandkit"] }
19
+ ]
@@ -0,0 +1,4 @@
1
+ /** @type {import('next').NextConfig} */
2
+ const nextConfig = {}
3
+
4
+ module.exports = nextConfig