doccupine 0.0.49 → 0.0.51

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.
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,48 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { generateSlug, escapeTemplateContent } from "./index.js";
3
+ describe("generateSlug", () => {
4
+ it("returns empty string for index.mdx", () => {
5
+ expect(generateSlug("index.mdx")).toBe("");
6
+ expect(generateSlug("./index.mdx")).toBe("");
7
+ });
8
+ it("strips .mdx extension", () => {
9
+ expect(generateSlug("getting-started.mdx")).toBe("getting-started");
10
+ });
11
+ it("lowercases the slug", () => {
12
+ expect(generateSlug("MyPage.mdx")).toBe("mypage");
13
+ });
14
+ it("replaces special characters with hyphens", () => {
15
+ expect(generateSlug("hello world.mdx")).toBe("hello-world");
16
+ });
17
+ it("preserves forward slashes for nested paths", () => {
18
+ expect(generateSlug("guides/setup.mdx")).toBe("guides/setup");
19
+ });
20
+ it("converts backslashes to forward slashes", () => {
21
+ expect(generateSlug("guides\\setup.mdx")).toBe("guides/setup");
22
+ });
23
+ it("preserves underscores and hyphens", () => {
24
+ expect(generateSlug("my_page-name.mdx")).toBe("my_page-name");
25
+ });
26
+ });
27
+ describe("escapeTemplateContent", () => {
28
+ it("escapes backticks", () => {
29
+ expect(escapeTemplateContent("hello `world`")).toBe("hello \\`world\\`");
30
+ });
31
+ it("escapes template literal expressions", () => {
32
+ expect(escapeTemplateContent("${variable}")).toBe("\\${variable}");
33
+ });
34
+ it("escapes backslashes", () => {
35
+ expect(escapeTemplateContent("path\\to\\file")).toBe("path\\\\to\\\\file");
36
+ });
37
+ it("escapes all dangerous characters together", () => {
38
+ const input = "Use `${process.env.SECRET}` here";
39
+ const result = escapeTemplateContent(input);
40
+ expect(result).toBe("Use \\`\\${process.env.SECRET}\\` here");
41
+ });
42
+ it("handles content with no special characters", () => {
43
+ expect(escapeTemplateContent("plain text")).toBe("plain text");
44
+ });
45
+ it("handles empty string", () => {
46
+ expect(escapeTemplateContent("")).toBe("");
47
+ });
48
+ });
@@ -1 +1 @@
1
- export declare const docsTemplate = "import { Flex } from \"cherry-styled-components/src/lib\";\nimport {\n DocsContainer,\n StyledMarkdownContainer,\n} from \"@/components/layout/DocsComponents\";\nimport { MDXRemote } from \"next-mdx-remote/rsc\";\nimport remarkGfm from \"remark-gfm\";\nimport { useMDXComponents } from \"@/components/MDXComponents\";\nimport { DocsSideBar } from \"@/components/DocsSideBar\";\nimport { ActionBar } from \"@/components/layout/ActionBar\";\n\ninterface DocsProps {\n content: string;\n}\n\ninterface Heading {\n id: string;\n text: string;\n level: number;\n}\n\nfunction generateId(text: string): string {\n return text\n .toLowerCase()\n .replace(/[^\\w\\s-]/g, \"\")\n .replace(/\\s+/g, \"-\")\n .trim();\n}\n\nfunction extractHeadings(content: string): Heading[] {\n const contentWithoutCodeBlocks = content.replace(/```[\\s\\S]*?```/g, \"\");\n const headingRegex = /^(#{1,6})\\s+(.+)$/gm;\n const headings: Heading[] = [];\n let match;\n\n while ((match = headingRegex.exec(contentWithoutCodeBlocks)) !== null) {\n const level = match[1].length;\n const text = match[2].trim();\n const id = generateId(text);\n headings.push({ id, text, level });\n }\n\n return headings;\n}\n\nfunction Docs({ content }: DocsProps) {\n const headings = extractHeadings(content);\n const components = useMDXComponents({});\n\n return (\n <>\n <DocsContainer>\n <ActionBar content={content}>\n <Flex $gap={20}>\n <StyledMarkdownContainer>\n {content && (\n <MDXRemote\n source={content}\n options={{\n mdxOptions: {\n remarkPlugins: [remarkGfm],\n },\n }}\n components={components}\n />\n )}\n </StyledMarkdownContainer>\n </Flex>\n </ActionBar>\n </DocsContainer>\n <DocsSideBar headings={headings} />\n </>\n );\n}\n\nexport { Docs };\n";
1
+ export declare const docsTemplate = "import { Flex } from \"cherry-styled-components/src/lib\";\nimport {\n DocsContainer,\n StyledMarkdownContainer,\n} from \"@/components/layout/DocsComponents\";\nimport { MDXRemote } from \"next-mdx-remote/rsc\";\nimport remarkGfm from \"remark-gfm\";\nimport { useMDXComponents } from \"@/components/MDXComponents\";\nimport { DocsSideBar } from \"@/components/DocsSideBar\";\nimport { ActionBar } from \"@/components/layout/ActionBar\";\n\ninterface DocsProps {\n content: string;\n}\n\ninterface Heading {\n id: string;\n text: string;\n level: number;\n}\n\nfunction generateId(text: string): string {\n return text\n .toLowerCase()\n .replace(/[^\\w\\s-]/g, \"\")\n .replace(/\\s+/g, \"-\")\n .trim();\n}\n\nfunction extractHeadings(content: string): Heading[] {\n const contentWithoutCodeBlocks = content.replace(/```[\\s\\S]*?```/g, \"\");\n const headingRegex = /^(#{1,6})\\s+(.+)$/gm;\n const headings: Heading[] = [];\n let match;\n\n while ((match = headingRegex.exec(contentWithoutCodeBlocks)) !== null) {\n const level = match[1].length;\n const text = match[2].trim();\n const id = generateId(text);\n headings.push({ id, text, level });\n }\n\n return headings;\n}\n\nfunction Docs({ content }: DocsProps) {\n const headings = extractHeadings(content);\n const components = useMDXComponents({});\n\n return (\n <>\n <DocsContainer>\n <ActionBar content={content}>\n <Flex $gap={20}>\n <StyledMarkdownContainer>\n {content && (\n <MDXRemote\n source={content}\n options={{\n blockJS: false,\n mdxOptions: {\n remarkPlugins: [remarkGfm],\n },\n }}\n components={components}\n />\n )}\n </StyledMarkdownContainer>\n </Flex>\n </ActionBar>\n </DocsContainer>\n <DocsSideBar headings={headings} />\n </>\n );\n}\n\nexport { Docs };\n";
@@ -57,6 +57,7 @@ function Docs({ content }: DocsProps) {
57
57
  <MDXRemote
58
58
  source={content}
59
59
  options={{
60
+ blockJS: false,
60
61
  mdxOptions: {
61
62
  remarkPlugins: [remarkGfm],
62
63
  },
@@ -1 +1 @@
1
- export declare const buttonTemplate = "\"use client\";\nimport Link from \"next/link\";\nimport styled from \"styled-components\";\nimport {\n theme as localTheme,\n ButtonProps,\n buttonStyles,\n} from \"cherry-styled-components/src/lib\";\nimport { Icon } from \"@/components/layout/Icon\";\n\ninterface LinkButtonProps extends ButtonProps {\n href?: string;\n target?: \"_blank\" | \"_self\" | \"_parent\" | \"_top\";\n variant?: \"primary\" | \"secondary\" | \"tertiary\";\n size?: \"default\" | \"big\";\n outline?: boolean;\n fullWidth?: boolean;\n icon?: string;\n iconPosition?: \"left\" | \"right\";\n}\n\nconst StyledLinkButton = styled(Link)<LinkButtonProps>`\n ${({ theme, $variant, $size, $outline, $fullWidth, disabled }) =>\n buttonStyles(theme, $variant, $size, $outline, $fullWidth, disabled)}\n\n & svg.lucide {\n margin: auto 0;\n min-width: min-content;\n color: ${({ theme, $outline }) =>\n $outline ? \"inherit\" : theme.colors.light};\n stroke: ${({ theme, $outline }) =>\n $outline ? \"currentColor\" : theme.colors.light};\n }\n`;\n\nconst ButtonBase = styled.button<ButtonProps>`\n ${({ theme, $variant, $size, $outline, $fullWidth, disabled }) =>\n buttonStyles(theme, $variant, $size, $outline, $fullWidth, disabled)}\n\n & svg.lucide {\n margin: auto 0;\n min-width: min-content;\n color: ${({ theme, $outline }) =>\n $outline ? \"inherit\" : theme.colors.light};\n stroke: ${({ theme, $outline }) =>\n $outline ? \"currentColor\" : theme.colors.light};\n }\n`;\n\nfunction Button({\n variant = \"primary\",\n size,\n outline,\n fullWidth,\n icon,\n iconPosition = \"left\",\n theme = localTheme,\n href,\n ...props\n}: LinkButtonProps) {\n return href ? (\n <div>\n <StyledLinkButton\n {...props}\n href={href}\n $variant={variant}\n $size={size}\n $outline={outline}\n $fullWidth={fullWidth}\n >\n {iconPosition === \"left\" && icon && (\n <Icon name={icon} color={theme.colors.light} size={16} />\n )}\n {props.children}\n {iconPosition === \"right\" && icon && (\n <Icon name={icon} color={theme.colors.light} size={16} />\n )}\n </StyledLinkButton>\n </div>\n ) : (\n <div>\n <ButtonBase\n {...props}\n $variant={variant}\n $size={size}\n $outline={outline}\n $fullWidth={fullWidth}\n >\n {iconPosition === \"left\" && icon && (\n <Icon name={icon} color={theme.colors.light} size={16} />\n )}\n {props.children}\n {iconPosition === \"right\" && icon && (\n <Icon name={icon} color={theme.colors.light} size={16} />\n )}\n </ButtonBase>\n </div>\n );\n}\n\nexport { Button };\n";
1
+ export declare const buttonTemplate = "\"use client\";\nimport Link from \"next/link\";\nimport styled from \"styled-components\";\nimport {\n theme as localTheme,\n ButtonProps,\n buttonStyles,\n} from \"cherry-styled-components/src/lib\";\nimport { Icon } from \"@/components/layout/Icon\";\n\ninterface LinkButtonProps extends ButtonProps {\n href?: string;\n target?: \"_blank\" | \"_self\" | \"_parent\" | \"_top\";\n variant?: \"primary\" | \"secondary\" | \"tertiary\";\n size?: \"default\" | \"big\";\n outline?: boolean;\n fullWidth?: boolean;\n icon?: string;\n iconPosition?: \"left\" | \"right\";\n}\n\nconst StyledLinkButton = styled(Link)<LinkButtonProps>`\n ${({ theme, $variant, $size, $outline, $fullWidth, disabled }) =>\n buttonStyles(theme, $variant, $size, $outline, $fullWidth, disabled)}\n\n & svg.lucide {\n margin: auto 0;\n min-width: min-content;\n color: inherit;\n }\n`;\n\nconst ButtonBase = styled.button<ButtonProps>`\n ${({ theme, $variant, $size, $outline, $fullWidth, disabled }) =>\n buttonStyles(theme, $variant, $size, $outline, $fullWidth, disabled)}\n\n & svg.lucide {\n margin: auto 0;\n min-width: min-content;\n color: inherit;\n }\n`;\n\nfunction Button({\n variant = \"primary\",\n size,\n outline,\n fullWidth,\n icon,\n iconPosition = \"left\",\n theme = localTheme,\n href,\n ...props\n}: LinkButtonProps) {\n return href ? (\n <div>\n <StyledLinkButton\n {...props}\n href={href}\n $variant={variant}\n $size={size}\n $outline={outline}\n $fullWidth={fullWidth}\n >\n {iconPosition === \"left\" && icon && <Icon name={icon} size={16} />}\n {props.children}\n {iconPosition === \"right\" && icon && <Icon name={icon} size={16} />}\n </StyledLinkButton>\n </div>\n ) : (\n <div>\n <ButtonBase\n {...props}\n $variant={variant}\n $size={size}\n $outline={outline}\n $fullWidth={fullWidth}\n >\n {iconPosition === \"left\" && icon && <Icon name={icon} size={16} />}\n {props.children}\n {iconPosition === \"right\" && icon && <Icon name={icon} size={16} />}\n </ButtonBase>\n </div>\n );\n}\n\nexport { Button };\n";
@@ -26,10 +26,7 @@ const StyledLinkButton = styled(Link)<LinkButtonProps>\`
26
26
  & svg.lucide {
27
27
  margin: auto 0;
28
28
  min-width: min-content;
29
- color: \${({ theme, $outline }) =>
30
- $outline ? "inherit" : theme.colors.light};
31
- stroke: \${({ theme, $outline }) =>
32
- $outline ? "currentColor" : theme.colors.light};
29
+ color: inherit;
33
30
  }
34
31
  \`;
35
32
 
@@ -40,10 +37,7 @@ const ButtonBase = styled.button<ButtonProps>\`
40
37
  & svg.lucide {
41
38
  margin: auto 0;
42
39
  min-width: min-content;
43
- color: \${({ theme, $outline }) =>
44
- $outline ? "inherit" : theme.colors.light};
45
- stroke: \${({ theme, $outline }) =>
46
- $outline ? "currentColor" : theme.colors.light};
40
+ color: inherit;
47
41
  }
48
42
  \`;
49
43
 
@@ -68,13 +62,9 @@ function Button({
68
62
  $outline={outline}
69
63
  $fullWidth={fullWidth}
70
64
  >
71
- {iconPosition === "left" && icon && (
72
- <Icon name={icon} color={theme.colors.light} size={16} />
73
- )}
65
+ {iconPosition === "left" && icon && <Icon name={icon} size={16} />}
74
66
  {props.children}
75
- {iconPosition === "right" && icon && (
76
- <Icon name={icon} color={theme.colors.light} size={16} />
77
- )}
67
+ {iconPosition === "right" && icon && <Icon name={icon} size={16} />}
78
68
  </StyledLinkButton>
79
69
  </div>
80
70
  ) : (
@@ -86,13 +76,9 @@ function Button({
86
76
  $outline={outline}
87
77
  $fullWidth={fullWidth}
88
78
  >
89
- {iconPosition === "left" && icon && (
90
- <Icon name={icon} color={theme.colors.light} size={16} />
91
- )}
79
+ {iconPosition === "left" && icon && <Icon name={icon} size={16} />}
92
80
  {props.children}
93
- {iconPosition === "right" && icon && (
94
- <Icon name={icon} color={theme.colors.light} size={16} />
95
- )}
81
+ {iconPosition === "right" && icon && <Icon name={icon} size={16} />}
96
82
  </ButtonBase>
97
83
  </div>
98
84
  );
@@ -1 +1 @@
1
- export declare const iconTemplate = "import { icons } from \"lucide-react\";\n\nexport type IconProps = keyof typeof icons;\n\ninterface Props {\n name: string | IconProps;\n color?: string;\n size?: number;\n className?: string;\n}\n\nfunction transformIconName(name: string): string {\n return name\n .split(\"-\")\n .map((word) => word.charAt(0).toUpperCase() + word.slice(1))\n .join(\"\");\n}\n\nconst Icon = ({ name, color, size, className }: Props) => {\n const IconName = transformIconName(name as string);\n const LucideIcon = icons[IconName as keyof typeof icons];\n if (!LucideIcon) return null;\n\n return <LucideIcon color={color} size={size} className={className} />;\n};\n\nexport { Icon };\n";
1
+ export declare const iconTemplate = "import { icons } from \"lucide-react\";\n\nexport type IconProps = keyof typeof icons;\n\ninterface Props {\n name: string | IconProps;\n color?: string;\n size?: string | number;\n className?: string;\n}\n\nfunction transformIconName(name: string): string {\n return name\n .split(\"-\")\n .map((word) => word.charAt(0).toUpperCase() + word.slice(1))\n .join(\"\");\n}\n\nconst Icon = ({ name, color, size, className }: Props) => {\n const IconName = transformIconName(name as string);\n const LucideIcon = icons[IconName as keyof typeof icons];\n if (!LucideIcon) return null;\n\n const numericSize = size != null ? Number(size) : undefined;\n\n return <LucideIcon color={color} size={numericSize} className={className} />;\n};\n\nexport { Icon };\n";
@@ -5,7 +5,7 @@ export type IconProps = keyof typeof icons;
5
5
  interface Props {
6
6
  name: string | IconProps;
7
7
  color?: string;
8
- size?: number;
8
+ size?: string | number;
9
9
  className?: string;
10
10
  }
11
11
 
@@ -21,7 +21,9 @@ const Icon = ({ name, color, size, className }: Props) => {
21
21
  const LucideIcon = icons[IconName as keyof typeof icons];
22
22
  if (!LucideIcon) return null;
23
23
 
24
- return <LucideIcon color={color} size={size} className={className} />;
24
+ const numericSize = size != null ? Number(size) : undefined;
25
+
26
+ return <LucideIcon color={color} size={numericSize} className={className} />;
25
27
  };
26
28
 
27
29
  export { Icon };
@@ -0,0 +1 @@
1
+ export declare const stepsTemplate = "\"use client\";\nimport React from \"react\";\nimport styled, { useTheme } from \"styled-components\";\nimport { styledText, Theme } from \"cherry-styled-components/src/lib\";\nimport { rgba } from \"polished\";\nimport { Icon, IconProps } from \"@/components/layout/Icon\";\n\nconst StyledStepsContainer = styled.div<{ theme: Theme }>`\n position: relative;\n width: 100%;\n`;\n\nconst StyledStep = styled.div<{ theme: Theme }>`\n background: ${({ theme }) => theme.colors.light};\n border-radius: ${({ theme }) => theme.spacing.radius.lg};\n padding: 0 0 20px 52px;\n margin: 0;\n position: relative;\n ${({ theme }) => styledText(theme)}\n color: ${({ theme }) => theme.colors.grayDark};\n\n &::after {\n content: \"\";\n position: absolute;\n left: 16px;\n top: 0;\n width: 1px;\n height: 100%;\n background: ${({ theme }) => theme.colors.primary};\n background: linear-gradient(\n 180deg,\n ${({ theme }) => theme.colors.primary},\n ${({ theme }) => rgba(theme.colors.primary, 0)}\n );\n border-radius: 4px;\n }\n`;\n\nconst StepNumber = styled.div<{ theme: Theme }>`\n width: 32px;\n height: 32px;\n border-radius: 50%;\n background: ${({ theme }) => theme.colors.primary};\n color: ${({ theme }) => theme.colors.light};\n display: flex;\n align-items: center;\n justify-content: center;\n font-weight: 700;\n margin-bottom: 12px;\n ${({ theme }) => styledText(theme)};\n position: absolute;\n left: 0;\n top: 0;\n z-index: 1;\n`;\n\nconst StyledStepTitle = styled.h3<{ theme: Theme }>`\n margin: 0 0 10px 0;\n padding: 2px 0 0 0;\n color: ${({ theme }) => theme.colors.dark};\n ${({ theme }) => styledText(theme)};\n`;\n\nconst StepContent = styled.div<{ theme: Theme }>`\n color: ${({ theme }) => theme.colors.grayDark};\n ${({ theme }) => styledText(theme)};\n\n & > .code-wrapper {\n margin: 10px 0;\n }\n`;\n\ninterface StepProps extends React.HTMLAttributes<HTMLDivElement> {\n title: string;\n children: React.ReactNode;\n icon?: IconProps;\n}\n\nfunction Step({ title, children, icon, ...props }: StepProps) {\n return null;\n}\n\ninterface StepsProps extends React.HTMLAttributes<HTMLDivElement> {\n children: React.ReactNode;\n}\n\nfunction Steps({ children, ...props }: StepsProps) {\n const theme = useTheme() as Theme;\n\n const steps = React.Children.toArray(children).filter(\n (child): child is React.ReactElement<StepProps> =>\n React.isValidElement(child),\n );\n\n return (\n <StyledStepsContainer theme={theme}>\n {steps.map((step, index) => {\n const { title, children: stepContent, icon } = step.props;\n\n return (\n <StyledStep key={index} theme={theme}>\n <StepNumber theme={theme}>{index + 1}</StepNumber>\n {icon && <Icon name={icon} color={theme.colors.primary} />}\n <StyledStepTitle theme={theme}>{title}</StyledStepTitle>\n <StepContent theme={theme}>{stepContent}</StepContent>\n </StyledStep>\n );\n })}\n </StyledStepsContainer>\n );\n}\n\nexport { Steps, Step };\n";
@@ -0,0 +1,114 @@
1
+ export const stepsTemplate = `"use client";
2
+ import React from "react";
3
+ import styled, { useTheme } from "styled-components";
4
+ import { styledText, Theme } from "cherry-styled-components/src/lib";
5
+ import { rgba } from "polished";
6
+ import { Icon, IconProps } from "@/components/layout/Icon";
7
+
8
+ const StyledStepsContainer = styled.div<{ theme: Theme }>\`
9
+ position: relative;
10
+ width: 100%;
11
+ \`;
12
+
13
+ const StyledStep = styled.div<{ theme: Theme }>\`
14
+ background: \${({ theme }) => theme.colors.light};
15
+ border-radius: \${({ theme }) => theme.spacing.radius.lg};
16
+ padding: 0 0 20px 52px;
17
+ margin: 0;
18
+ position: relative;
19
+ \${({ theme }) => styledText(theme)}
20
+ color: \${({ theme }) => theme.colors.grayDark};
21
+
22
+ &::after {
23
+ content: "";
24
+ position: absolute;
25
+ left: 16px;
26
+ top: 0;
27
+ width: 1px;
28
+ height: 100%;
29
+ background: \${({ theme }) => theme.colors.primary};
30
+ background: linear-gradient(
31
+ 180deg,
32
+ \${({ theme }) => theme.colors.primary},
33
+ \${({ theme }) => rgba(theme.colors.primary, 0)}
34
+ );
35
+ border-radius: 4px;
36
+ }
37
+ \`;
38
+
39
+ const StepNumber = styled.div<{ theme: Theme }>\`
40
+ width: 32px;
41
+ height: 32px;
42
+ border-radius: 50%;
43
+ background: \${({ theme }) => theme.colors.primary};
44
+ color: \${({ theme }) => theme.colors.light};
45
+ display: flex;
46
+ align-items: center;
47
+ justify-content: center;
48
+ font-weight: 700;
49
+ margin-bottom: 12px;
50
+ \${({ theme }) => styledText(theme)};
51
+ position: absolute;
52
+ left: 0;
53
+ top: 0;
54
+ z-index: 1;
55
+ \`;
56
+
57
+ const StyledStepTitle = styled.h3<{ theme: Theme }>\`
58
+ margin: 0 0 10px 0;
59
+ padding: 2px 0 0 0;
60
+ color: \${({ theme }) => theme.colors.dark};
61
+ \${({ theme }) => styledText(theme)};
62
+ \`;
63
+
64
+ const StepContent = styled.div<{ theme: Theme }>\`
65
+ color: \${({ theme }) => theme.colors.grayDark};
66
+ \${({ theme }) => styledText(theme)};
67
+
68
+ & > .code-wrapper {
69
+ margin: 10px 0;
70
+ }
71
+ \`;
72
+
73
+ interface StepProps extends React.HTMLAttributes<HTMLDivElement> {
74
+ title: string;
75
+ children: React.ReactNode;
76
+ icon?: IconProps;
77
+ }
78
+
79
+ function Step({ title, children, icon, ...props }: StepProps) {
80
+ return null;
81
+ }
82
+
83
+ interface StepsProps extends React.HTMLAttributes<HTMLDivElement> {
84
+ children: React.ReactNode;
85
+ }
86
+
87
+ function Steps({ children, ...props }: StepsProps) {
88
+ const theme = useTheme() as Theme;
89
+
90
+ const steps = React.Children.toArray(children).filter(
91
+ (child): child is React.ReactElement<StepProps> =>
92
+ React.isValidElement(child),
93
+ );
94
+
95
+ return (
96
+ <StyledStepsContainer theme={theme}>
97
+ {steps.map((step, index) => {
98
+ const { title, children: stepContent, icon } = step.props;
99
+
100
+ return (
101
+ <StyledStep key={index} theme={theme}>
102
+ <StepNumber theme={theme}>{index + 1}</StepNumber>
103
+ {icon && <Icon name={icon} color={theme.colors.primary} />}
104
+ <StyledStepTitle theme={theme}>{title}</StyledStepTitle>
105
+ <StepContent theme={theme}>{stepContent}</StepContent>
106
+ </StyledStep>
107
+ );
108
+ })}
109
+ </StyledStepsContainer>
110
+ );
111
+ }
112
+
113
+ export { Steps, Step };
114
+ `;
@@ -1 +1 @@
1
- export declare const eslintConfigTeamplate = "import nextConfig from \"eslint-config-next\";\n\nconst config = [...nextConfig];\n\nexport default config;\n";
1
+ export declare const eslintConfigTemplate = "import nextConfig from \"eslint-config-next\";\n\nconst config = [...nextConfig];\n\nexport default config;\n";
@@ -1,4 +1,4 @@
1
- export const eslintConfigTeamplate = `import nextConfig from "eslint-config-next";
1
+ export const eslintConfigTemplate = `import nextConfig from "eslint-config-next";
2
2
 
3
3
  const config = [...nextConfig];
4
4
 
@@ -1 +1 @@
1
- export declare const deploymentMdxTemplate = "---\ntitle: \"Deployment\"\ndescription: \"Deploy your documentation site with Doccupine or self-host on Vercel.\"\ndate: \"2025-01-15\"\ncategory: \"Configuration\"\ncategoryOrder: 3\norder: 9\n---\n# Deployment\n\n## Deploy with Doccupine\n\nSign up for an account at [Doccupine](https://www.doccupine.com) and create your docs instantly \u2014 no build configuration, no infrastructure to manage.\n\nDoccupine gives you:\n- **Automatic deployments** on every push to your repository\n- **Site customization** through a visual dashboard \u2014 no code changes needed\n- **Team collaboration** so your whole team can manage docs together\n- **Custom domains** with automatic SSL\n- **AI Assistant and MCP server** included out of the box, no API key required\n\nGet started at [doccupine.com](https://www.doccupine.com).\n\n---\n\n## Self-hosting on Vercel\n\nIf you prefer to self-host, Doccupine generates a standard Next.js app that can be deployed to Vercel.\n\n<Callout type=\"warning\">\n Deploy the generated website directory (the Next.js app), not your MDX source folder. In a monorepo, set the Vercel <strong>Root Directory</strong> to the generated site folder.\n</Callout>\n\n### Quick start\n1. Push the generated site folder to GitHub, GitLab, or Bitbucket.\n2. Import the repository at [vercel.com/new](https://vercel.com/new). Vercel auto-detects Next.js and applies the correct build settings.\n3. Add any required environment variables under Project \u2192 Settings \u2192 Environment Variables.\n4. Deploy. Vercel creates Preview deployments per branch and promotes to Production on merge to main.\n\nYou can also deploy from the command line with the [Vercel CLI](https://vercel.com/docs/cli):\n\n```bash\nnpm i -g vercel\n```\n\n### Custom domains\nAdd a domain under Project \u2192 Settings \u2192 Domains and point your DNS to Vercel.\n\n### Troubleshooting\n- **Build failed** \u2014 Check build logs. Ensure your lockfile and correct Node.js version are present.\n- **Missing content** \u2014 Verify your MDX files and assets are in the repository.";
1
+ export declare const deploymentMdxTemplate = "---\ntitle: \"Deployment\"\ndescription: \"Deploy your documentation site with Doccupine or self-host on any platform that supports Next.js.\"\ndate: \"2025-01-15\"\ncategory: \"Configuration\"\ncategoryOrder: 3\norder: 9\n---\n# Deployment\n\n## Deploy with Doccupine\n\nSign up for an account at [Doccupine](https://www.doccupine.com) and create your docs instantly - no build configuration, no infrastructure to manage.\n\nDoccupine gives you:\n- **Automatic deployments** on every push to your repository\n- **Site customization** through a visual dashboard - no code changes needed\n- **Team collaboration** so your whole team can manage docs together\n- **Custom domains** with automatic SSL\n- **AI Assistant and MCP server** included out of the box, no API key required\n\nGet started at [doccupine.com](https://www.doccupine.com).\n\n---\n\n## Self-hosting\n\nDoccupine generates a standard Next.js app, so you can deploy it anywhere that supports Node.js or Next.js.\n\n<Callout type=\"warning\">\n Deploy the generated website directory (the Next.js app), not your MDX source folder. In a monorepo, set the root directory to the generated site folder.\n</Callout>\n\n### Popular hosting options\n\n- **Vercel** - native Next.js support, zero-config deploys. Connect your repo and set the root directory to the generated app folder.\n- **Netlify** - supports Next.js via the `@netlify/plugin-nextjs` adapter. Works with the standard `next build` output.\n- **AWS Amplify** - fully managed hosting with CI/CD. Supports Next.js SSR out of the box.\n- **Cloudflare Pages** - deploy using the `@cloudflare/next-on-pages` adapter for edge-based hosting.\n- **Docker** - build a container from the generated app using the standard [Next.js Docker example](https://github.com/vercel/next.js/tree/canary/examples/with-docker) and deploy to any container platform.\n- **Node.js server** - run `next build && next start` on any server or VPS with Node.js installed.\n\n### Troubleshooting\n- **Build failed** - check build logs. Ensure your lockfile and correct Node.js version are present.\n- **Missing content** - verify your MDX files and assets are in the repository.\n- **SSR issues on edge platforms** - some features (like the AI chat API routes) require a Node.js runtime. Check your platform's documentation for SSR/API route support.";
@@ -1,6 +1,6 @@
1
1
  export const deploymentMdxTemplate = `---
2
2
  title: "Deployment"
3
- description: "Deploy your documentation site with Doccupine or self-host on Vercel."
3
+ description: "Deploy your documentation site with Doccupine or self-host on any platform that supports Next.js."
4
4
  date: "2025-01-15"
5
5
  category: "Configuration"
6
6
  categoryOrder: 3
@@ -10,11 +10,11 @@ order: 9
10
10
 
11
11
  ## Deploy with Doccupine
12
12
 
13
- Sign up for an account at [Doccupine](https://www.doccupine.com) and create your docs instantly no build configuration, no infrastructure to manage.
13
+ Sign up for an account at [Doccupine](https://www.doccupine.com) and create your docs instantly - no build configuration, no infrastructure to manage.
14
14
 
15
15
  Doccupine gives you:
16
16
  - **Automatic deployments** on every push to your repository
17
- - **Site customization** through a visual dashboard no code changes needed
17
+ - **Site customization** through a visual dashboard - no code changes needed
18
18
  - **Team collaboration** so your whole team can manage docs together
19
19
  - **Custom domains** with automatic SSL
20
20
  - **AI Assistant and MCP server** included out of the box, no API key required
@@ -23,29 +23,24 @@ Get started at [doccupine.com](https://www.doccupine.com).
23
23
 
24
24
  ---
25
25
 
26
- ## Self-hosting on Vercel
26
+ ## Self-hosting
27
27
 
28
- If you prefer to self-host, Doccupine generates a standard Next.js app that can be deployed to Vercel.
28
+ Doccupine generates a standard Next.js app, so you can deploy it anywhere that supports Node.js or Next.js.
29
29
 
30
30
  <Callout type="warning">
31
- Deploy the generated website directory (the Next.js app), not your MDX source folder. In a monorepo, set the Vercel <strong>Root Directory</strong> to the generated site folder.
31
+ Deploy the generated website directory (the Next.js app), not your MDX source folder. In a monorepo, set the root directory to the generated site folder.
32
32
  </Callout>
33
33
 
34
- ### Quick start
35
- 1. Push the generated site folder to GitHub, GitLab, or Bitbucket.
36
- 2. Import the repository at [vercel.com/new](https://vercel.com/new). Vercel auto-detects Next.js and applies the correct build settings.
37
- 3. Add any required environment variables under Project → Settings → Environment Variables.
38
- 4. Deploy. Vercel creates Preview deployments per branch and promotes to Production on merge to main.
34
+ ### Popular hosting options
39
35
 
40
- You can also deploy from the command line with the [Vercel CLI](https://vercel.com/docs/cli):
41
-
42
- \`\`\`bash
43
- npm i -g vercel
44
- \`\`\`
45
-
46
- ### Custom domains
47
- Add a domain under Project → Settings → Domains and point your DNS to Vercel.
36
+ - **Vercel** - native Next.js support, zero-config deploys. Connect your repo and set the root directory to the generated app folder.
37
+ - **Netlify** - supports Next.js via the \`@netlify/plugin-nextjs\` adapter. Works with the standard \`next build\` output.
38
+ - **AWS Amplify** - fully managed hosting with CI/CD. Supports Next.js SSR out of the box.
39
+ - **Cloudflare Pages** - deploy using the \`@cloudflare/next-on-pages\` adapter for edge-based hosting.
40
+ - **Docker** - build a container from the generated app using the standard [Next.js Docker example](https://github.com/vercel/next.js/tree/canary/examples/with-docker) and deploy to any container platform.
41
+ - **Node.js server** - run \`next build && next start\` on any server or VPS with Node.js installed.
48
42
 
49
43
  ### Troubleshooting
50
- - **Build failed** Check build logs. Ensure your lockfile and correct Node.js version are present.
51
- - **Missing content** Verify your MDX files and assets are in the repository.`;
44
+ - **Build failed** - check build logs. Ensure your lockfile and correct Node.js version are present.
45
+ - **Missing content** - verify your MDX files and assets are in the repository.
46
+ - **SSR issues on edge platforms** - some features (like the AI chat API routes) require a Node.js runtime. Check your platform's documentation for SSR/API route support.`;
@@ -24,10 +24,10 @@ export const packageJsonTemplate = JSON.stringify({
24
24
  "@types/react": "^19",
25
25
  "@types/react-dom": "^19",
26
26
  "baseline-browser-mapping": "^2.9.19",
27
- "cherry-styled-components": "^0.1.8",
27
+ "cherry-styled-components": "^0.1.11",
28
28
  eslint: "^9",
29
29
  "eslint-config-next": "16.1.6",
30
- "lucide-react": "^0.563.0",
30
+ "lucide-react": "^0.564.0",
31
31
  "next-mdx-remote": "^6.0.0",
32
32
  polished: "^4.3.1",
33
33
  prettier: "^3.8.1",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "doccupine",
3
- "version": "0.0.49",
3
+ "version": "0.0.51",
4
4
  "description": "Document management system that allows you to store, organize, and share your documentation with ease. AI-ready.",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -11,7 +11,8 @@
11
11
  "build": "tsc",
12
12
  "dev": "tsc --watch",
13
13
  "start": "node dist/index.js",
14
- "prepare": "npm run build"
14
+ "prepare": "tsc",
15
+ "test": "vitest run"
15
16
  },
16
17
  "keywords": [
17
18
  "doccupine",
@@ -20,6 +21,7 @@
20
21
  "mdx",
21
22
  "ai",
22
23
  "llm",
24
+ "mcp",
23
25
  "nextjs",
24
26
  "cli",
25
27
  "generator",
@@ -27,9 +29,9 @@
27
29
  "static-site-generator"
28
30
  ],
29
31
  "homepage": "https://doccupine.com",
30
- "repository": "https://github.com/thethemefoundry/doccupine-cli",
32
+ "repository": "https://github.com/doccupine/cli",
31
33
  "author": "Luan Gjokaj",
32
- "license": "MIT",
34
+ "license": "SEE LICENSE IN LICENSE",
33
35
  "dependencies": {
34
36
  "chalk": "^5.6.2",
35
37
  "chokidar": "^5.0.0",
@@ -46,7 +48,8 @@
46
48
  "@types/fs-extra": "^11.0.4",
47
49
  "@types/node": "^25.2.3",
48
50
  "@types/prompts": "^2.4.9",
49
- "typescript": "^5.9.3"
51
+ "typescript": "^5.9.3",
52
+ "vitest": "^4.0.18"
50
53
  },
51
54
  "files": [
52
55
  "dist/**/*"