@cmssy/cli 1.0.0 → 9.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/dist/index.js +437 -0
- package/package.json +20 -34
- package/LICENSE +0 -21
- package/README.md +0 -91
- package/dist/cli.js +0 -718
- package/templates/block/Component.module.css.tpl +0 -12
- package/templates/block/Component.tsx.tpl +0 -15
- package/templates/block/block.ts.tpl +0 -11
- package/templates/init/app/[[...path]]/page.tsx +0 -19
- package/templates/init/app/api/draft/route.ts +0 -5
- package/templates/init/blocks/hero/Hero.module.css +0 -131
- package/templates/init/blocks/hero/Hero.tsx +0 -100
- package/templates/init/blocks/hero/block.ts +0 -21
- package/templates/init/cmssy/blocks.ts +0 -5
- package/templates/init/cmssy/editor.tsx +0 -10
- package/templates/init/cmssy.config.ts +0 -8
- package/templates/init/env.example +0 -4
- package/templates/init/next.config.mjs +0 -8
- package/templates/init/proxy.ts +0 -32
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import styles from "./{{Pascal}}.module.css";
|
|
2
|
-
|
|
3
|
-
type {{Pascal}}Content = {
|
|
4
|
-
heading?: string;
|
|
5
|
-
};
|
|
6
|
-
|
|
7
|
-
export default function {{Pascal}}({ content }: { content: {{Pascal}}Content }) {
|
|
8
|
-
const { heading } = content;
|
|
9
|
-
if (!heading) return null;
|
|
10
|
-
return (
|
|
11
|
-
<section className={styles.block}>
|
|
12
|
-
<h2 className={styles.heading}>{heading}</h2>
|
|
13
|
-
</section>
|
|
14
|
-
);
|
|
15
|
-
}
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { defineBlock, fields } from "@cmssy/react";
|
|
2
|
-
import {{Pascal}} from "./{{Pascal}}";
|
|
3
|
-
|
|
4
|
-
export const {{camel}}Block = defineBlock({
|
|
5
|
-
type: "{{type}}",
|
|
6
|
-
label: "{{Label}}",
|
|
7
|
-
component: {{Pascal}},
|
|
8
|
-
props: {
|
|
9
|
-
heading: fields.singleLine({ label: "Heading" }),
|
|
10
|
-
},
|
|
11
|
-
});
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { buildCmssyMetadata, createCmssyPage } from "@cmssy/next";
|
|
2
|
-
import { cmssy } from "@/cmssy.config";
|
|
3
|
-
import { blocks } from "@/cmssy/blocks";
|
|
4
|
-
import { CmssyEditor } from "@/cmssy/editor";
|
|
5
|
-
|
|
6
|
-
// SEO comes from the page's cmssy fields (title/description) + workspace
|
|
7
|
-
// branding (OG image), resolved server-side.
|
|
8
|
-
export async function generateMetadata({
|
|
9
|
-
params,
|
|
10
|
-
}: {
|
|
11
|
-
params: Promise<{ path?: string[] }>;
|
|
12
|
-
}) {
|
|
13
|
-
const { path } = await params;
|
|
14
|
-
return buildCmssyMetadata(cmssy, path);
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
// One catch-all route renders every cmssy page. Passing `editor` lets the cmssy
|
|
18
|
-
// editor frame and edit this site; published requests render published content.
|
|
19
|
-
export default createCmssyPage(cmssy, blocks, { editor: CmssyEditor });
|
|
@@ -1,131 +0,0 @@
|
|
|
1
|
-
.hero {
|
|
2
|
-
--c-bg: #ffffff;
|
|
3
|
-
--c-fg: #0a0a0a;
|
|
4
|
-
--c-primary: #7c3aed;
|
|
5
|
-
--c-primary-fg: #ffffff;
|
|
6
|
-
--c-muted: #f4f4f5;
|
|
7
|
-
--c-muted-fg: #71717a;
|
|
8
|
-
--c-border: #e4e4e7;
|
|
9
|
-
|
|
10
|
-
position: relative;
|
|
11
|
-
overflow: hidden;
|
|
12
|
-
background: linear-gradient(
|
|
13
|
-
135deg,
|
|
14
|
-
var(--c-muted),
|
|
15
|
-
var(--c-bg) 50%,
|
|
16
|
-
var(--c-muted)
|
|
17
|
-
);
|
|
18
|
-
color: var(--c-fg);
|
|
19
|
-
font-family:
|
|
20
|
-
system-ui,
|
|
21
|
-
-apple-system,
|
|
22
|
-
sans-serif;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
.inner {
|
|
26
|
-
margin: 0 auto;
|
|
27
|
-
max-width: 48rem;
|
|
28
|
-
padding: 6rem 1.5rem;
|
|
29
|
-
text-align: center;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
.badge {
|
|
33
|
-
display: inline-block;
|
|
34
|
-
margin-bottom: 1.5rem;
|
|
35
|
-
border-radius: 9999px;
|
|
36
|
-
background: color-mix(in srgb, var(--c-primary) 12%, transparent);
|
|
37
|
-
padding: 0.375rem 1rem;
|
|
38
|
-
font-size: 0.875rem;
|
|
39
|
-
font-weight: 500;
|
|
40
|
-
color: var(--c-primary);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
.title {
|
|
44
|
-
margin: 0;
|
|
45
|
-
font-size: clamp(2.25rem, 5vw, 3.75rem);
|
|
46
|
-
font-weight: 700;
|
|
47
|
-
letter-spacing: -0.025em;
|
|
48
|
-
line-height: 1.1;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
.highlight {
|
|
52
|
-
background: linear-gradient(90deg, var(--c-primary), var(--c-fg));
|
|
53
|
-
-webkit-background-clip: text;
|
|
54
|
-
background-clip: text;
|
|
55
|
-
color: transparent;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
.subheading {
|
|
59
|
-
margin: 1.5rem auto 0;
|
|
60
|
-
max-width: 36rem;
|
|
61
|
-
font-size: 1.125rem;
|
|
62
|
-
color: var(--c-muted-fg);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
.actions {
|
|
66
|
-
margin-top: 2.5rem;
|
|
67
|
-
display: flex;
|
|
68
|
-
flex-direction: column;
|
|
69
|
-
align-items: center;
|
|
70
|
-
justify-content: center;
|
|
71
|
-
gap: 1rem;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
@media (min-width: 640px) {
|
|
75
|
-
.actions {
|
|
76
|
-
flex-direction: row;
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
.buttonPrimary,
|
|
81
|
-
.buttonSecondary {
|
|
82
|
-
display: inline-flex;
|
|
83
|
-
height: 3rem;
|
|
84
|
-
align-items: center;
|
|
85
|
-
justify-content: center;
|
|
86
|
-
border-radius: 0.375rem;
|
|
87
|
-
padding: 0 2rem;
|
|
88
|
-
font-size: 1rem;
|
|
89
|
-
font-weight: 500;
|
|
90
|
-
text-decoration: none;
|
|
91
|
-
transition:
|
|
92
|
-
opacity 0.2s,
|
|
93
|
-
background-color 0.2s;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
.buttonPrimary {
|
|
97
|
-
background: var(--c-primary);
|
|
98
|
-
color: var(--c-primary-fg);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
.buttonPrimary:hover {
|
|
102
|
-
opacity: 0.9;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
.buttonSecondary {
|
|
106
|
-
border: 1px solid var(--c-border);
|
|
107
|
-
color: var(--c-fg);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
.buttonSecondary:hover {
|
|
111
|
-
background: var(--c-muted);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
.media {
|
|
115
|
-
margin-top: 4rem;
|
|
116
|
-
overflow: hidden;
|
|
117
|
-
border-radius: 0.75rem;
|
|
118
|
-
border: 1px solid var(--c-border);
|
|
119
|
-
box-shadow: 0 25px 50px -12px rgb(0 0 0 / 0.25);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
.mediaFrame {
|
|
123
|
-
position: relative;
|
|
124
|
-
aspect-ratio: 16 / 9;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
.video {
|
|
128
|
-
width: 100%;
|
|
129
|
-
height: 100%;
|
|
130
|
-
object-fit: cover;
|
|
131
|
-
}
|
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
import Image from "next/image";
|
|
2
|
-
import { CmssyLink } from "@cmssy/next/client";
|
|
3
|
-
import styles from "./Hero.module.css";
|
|
4
|
-
|
|
5
|
-
type HeroContent = {
|
|
6
|
-
badgeText?: string;
|
|
7
|
-
heading?: string;
|
|
8
|
-
headingHighlight?: string;
|
|
9
|
-
subheading?: string;
|
|
10
|
-
primaryButtonText?: string;
|
|
11
|
-
primaryButtonUrl?: string;
|
|
12
|
-
secondaryButtonText?: string;
|
|
13
|
-
secondaryButtonUrl?: string;
|
|
14
|
-
media?: string;
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
const isVideo = (src: string) => /\.(mp4|webm|ogg)$/i.test(src);
|
|
18
|
-
|
|
19
|
-
export default function Hero({ content }: { content: HeroContent }) {
|
|
20
|
-
const {
|
|
21
|
-
badgeText,
|
|
22
|
-
heading,
|
|
23
|
-
headingHighlight,
|
|
24
|
-
subheading,
|
|
25
|
-
primaryButtonText,
|
|
26
|
-
primaryButtonUrl = "#",
|
|
27
|
-
secondaryButtonText,
|
|
28
|
-
secondaryButtonUrl = "#",
|
|
29
|
-
media,
|
|
30
|
-
} = content;
|
|
31
|
-
|
|
32
|
-
if (!heading && !headingHighlight) return null;
|
|
33
|
-
|
|
34
|
-
return (
|
|
35
|
-
<section className={styles.hero}>
|
|
36
|
-
<div className={styles.inner}>
|
|
37
|
-
{badgeText && <span className={styles.badge}>{badgeText}</span>}
|
|
38
|
-
|
|
39
|
-
<h1 className={styles.title}>
|
|
40
|
-
{heading}
|
|
41
|
-
{headingHighlight && (
|
|
42
|
-
<>
|
|
43
|
-
{heading ? " " : ""}
|
|
44
|
-
<span className={styles.highlight}>{headingHighlight}</span>
|
|
45
|
-
</>
|
|
46
|
-
)}
|
|
47
|
-
</h1>
|
|
48
|
-
|
|
49
|
-
{subheading && <p className={styles.subheading}>{subheading}</p>}
|
|
50
|
-
|
|
51
|
-
{(primaryButtonText || secondaryButtonText) && (
|
|
52
|
-
<div className={styles.actions}>
|
|
53
|
-
{primaryButtonText && (
|
|
54
|
-
<CmssyLink
|
|
55
|
-
href={primaryButtonUrl}
|
|
56
|
-
className={styles.buttonPrimary}
|
|
57
|
-
>
|
|
58
|
-
{primaryButtonText}
|
|
59
|
-
</CmssyLink>
|
|
60
|
-
)}
|
|
61
|
-
{secondaryButtonText && (
|
|
62
|
-
<CmssyLink
|
|
63
|
-
href={secondaryButtonUrl}
|
|
64
|
-
className={styles.buttonSecondary}
|
|
65
|
-
>
|
|
66
|
-
{secondaryButtonText}
|
|
67
|
-
</CmssyLink>
|
|
68
|
-
)}
|
|
69
|
-
</div>
|
|
70
|
-
)}
|
|
71
|
-
|
|
72
|
-
{media && (
|
|
73
|
-
<div className={styles.media}>
|
|
74
|
-
<div className={styles.mediaFrame}>
|
|
75
|
-
{isVideo(media) ? (
|
|
76
|
-
<video
|
|
77
|
-
src={media}
|
|
78
|
-
autoPlay
|
|
79
|
-
muted
|
|
80
|
-
loop
|
|
81
|
-
playsInline
|
|
82
|
-
className={styles.video}
|
|
83
|
-
/>
|
|
84
|
-
) : (
|
|
85
|
-
<Image
|
|
86
|
-
src={media}
|
|
87
|
-
alt={heading ?? ""}
|
|
88
|
-
fill
|
|
89
|
-
sizes="(max-width: 768px) 100vw, 768px"
|
|
90
|
-
style={{ objectFit: "cover" }}
|
|
91
|
-
priority
|
|
92
|
-
/>
|
|
93
|
-
)}
|
|
94
|
-
</div>
|
|
95
|
-
</div>
|
|
96
|
-
)}
|
|
97
|
-
</div>
|
|
98
|
-
</section>
|
|
99
|
-
);
|
|
100
|
-
}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import { defineBlock, fields } from "@cmssy/react";
|
|
2
|
-
import Hero from "./Hero";
|
|
3
|
-
|
|
4
|
-
// A simple content block: plain scalar fields, no loader. The component renders
|
|
5
|
-
// nothing until a heading is set, so an empty block is invisible on the page.
|
|
6
|
-
export const heroBlock = defineBlock({
|
|
7
|
-
type: "hero",
|
|
8
|
-
label: "Hero",
|
|
9
|
-
component: Hero,
|
|
10
|
-
props: {
|
|
11
|
-
badgeText: fields.singleLine({ label: "Badge" }),
|
|
12
|
-
heading: fields.singleLine({ label: "Heading", required: true }),
|
|
13
|
-
headingHighlight: fields.singleLine({ label: "Heading highlight" }),
|
|
14
|
-
subheading: fields.multiLine({ label: "Subheading" }),
|
|
15
|
-
primaryButtonText: fields.singleLine({ label: "Primary button text" }),
|
|
16
|
-
primaryButtonUrl: fields.link({ label: "Primary button URL" }),
|
|
17
|
-
secondaryButtonText: fields.singleLine({ label: "Secondary button text" }),
|
|
18
|
-
secondaryButtonUrl: fields.link({ label: "Secondary button URL" }),
|
|
19
|
-
media: fields.media({ label: "Media (image or video)" }),
|
|
20
|
-
},
|
|
21
|
-
});
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import { CmssyLazyEditor } from "@cmssy/react/client";
|
|
4
|
-
import type { CmssyEditorProps } from "@cmssy/next";
|
|
5
|
-
|
|
6
|
-
// The editor lazy-loads your block registry on the client, so server-only
|
|
7
|
-
// code (block loaders) is never bundled for the browser.
|
|
8
|
-
export function CmssyEditor(props: CmssyEditorProps) {
|
|
9
|
-
return <CmssyLazyEditor {...props} load={() => import("./blocks")} />;
|
|
10
|
-
}
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import type { CmssyNextConfig } from "@cmssy/next";
|
|
2
|
-
|
|
3
|
-
export const cmssy: CmssyNextConfig = {
|
|
4
|
-
workspaceSlug: process.env.CMSSY_WORKSPACE_SLUG ?? "",
|
|
5
|
-
draftSecret: process.env.CMSSY_DRAFT_SECRET ?? "",
|
|
6
|
-
// apiUrl: process.env.CMSSY_API_URL,
|
|
7
|
-
// editorOrigin: process.env.CMSSY_EDITOR_ORIGIN,
|
|
8
|
-
};
|
package/templates/init/proxy.ts
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import { NextResponse, type NextRequest } from "next/server";
|
|
2
|
-
import {
|
|
3
|
-
applyCmssyCsp,
|
|
4
|
-
CMSSY_EDIT_HEADER,
|
|
5
|
-
isCmssyEditRequest,
|
|
6
|
-
} from "@cmssy/next";
|
|
7
|
-
import { cmssy } from "@/cmssy.config";
|
|
8
|
-
|
|
9
|
-
// Next.js "proxy" (formerly middleware). It does two things:
|
|
10
|
-
// 1. Detects cmssy edit mode (`?cmssyEdit=1`) and exposes it to server
|
|
11
|
-
// components via the CMSSY_EDIT_HEADER (stripped from inbound requests
|
|
12
|
-
// first so a client can't forge it).
|
|
13
|
-
// 2. Applies the CSP `frame-ancestors` so the cmssy editor can frame this
|
|
14
|
-
// site in edit mode. editorOrigin defaults to the cmssy admin.
|
|
15
|
-
export function proxy(request: NextRequest) {
|
|
16
|
-
const editMode = isCmssyEditRequest(request);
|
|
17
|
-
|
|
18
|
-
const requestHeaders = new Headers(request.headers);
|
|
19
|
-
requestHeaders.delete(CMSSY_EDIT_HEADER);
|
|
20
|
-
if (editMode) requestHeaders.set(CMSSY_EDIT_HEADER, "1");
|
|
21
|
-
|
|
22
|
-
const response = NextResponse.next({ request: { headers: requestHeaders } });
|
|
23
|
-
if (editMode) {
|
|
24
|
-
applyCmssyCsp(response, { editorOrigin: cmssy.editorOrigin });
|
|
25
|
-
}
|
|
26
|
-
return response;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export const config = {
|
|
30
|
-
// Skip Next internals, the API routes, and files with an extension.
|
|
31
|
-
matcher: ["/((?!_next/|api/|.*\\..*).*)"],
|
|
32
|
-
};
|