@getdashi/cli 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.
- package/LICENSE +631 -0
- package/dist/bin.d.ts +2 -0
- package/dist/bin.js +63 -0
- package/dist/commands/app-create.d.ts +6 -0
- package/dist/commands/app-create.js +89 -0
- package/dist/commands/app-dev.d.ts +6 -0
- package/dist/commands/app-dev.js +63 -0
- package/dist/entry.d.ts +2 -0
- package/dist/entry.js +13 -0
- package/dist/utils/serve-preview.d.ts +8 -0
- package/dist/utils/serve-preview.js +50 -0
- package/dist/utils/ui.d.ts +17 -0
- package/dist/utils/ui.js +56 -0
- package/dist/utils/wait-for-port.d.ts +6 -0
- package/dist/utils/wait-for-port.js +28 -0
- package/package.json +34 -0
- package/preview-dist/assets/index-77pJcSpc.css +1 -0
- package/preview-dist/assets/index-Bi2y3XyQ.js +11069 -0
- package/preview-dist/assets/index-TykmCjcq.js +56 -0
- package/preview-dist/index.html +18 -0
- package/templates/dashi-app/.prettierignore +4 -0
- package/templates/dashi-app/.prettierrc +9 -0
- package/templates/dashi-app/app/globals.css +29 -0
- package/templates/dashi-app/app/layout.tsx +22 -0
- package/templates/dashi-app/app/page.tsx +91 -0
- package/templates/dashi-app/app/preferences/page.tsx +95 -0
- package/templates/dashi-app/app/preferences.tsx +25 -0
- package/templates/dashi-app/app/providers.tsx +9 -0
- package/templates/dashi-app/eslint.config.js +23 -0
- package/templates/dashi-app/middleware.ts +46 -0
- package/templates/dashi-app/next.config.mjs +15 -0
- package/templates/dashi-app/package.json +36 -0
- package/templates/dashi-app/postcss.config.mjs +5 -0
- package/templates/dashi-app/public/manifest.json +10 -0
- package/templates/dashi-app/tsconfig.json +24 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>Dashi Preview</title>
|
|
7
|
+
<style>
|
|
8
|
+
*, *::before, *::after { box-sizing: border-box; }
|
|
9
|
+
html, body, #root { height: 100%; margin: 0; padding: 0; background: #0e0e10; }
|
|
10
|
+
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; color-scheme: dark; }
|
|
11
|
+
</style>
|
|
12
|
+
<script type="module" crossorigin src="/_preview/assets/index-TykmCjcq.js"></script>
|
|
13
|
+
<link rel="stylesheet" crossorigin href="/_preview/assets/index-77pJcSpc.css">
|
|
14
|
+
</head>
|
|
15
|
+
<body>
|
|
16
|
+
<div id="root"></div>
|
|
17
|
+
</body>
|
|
18
|
+
</html>
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
@import "tailwindcss";
|
|
2
|
+
|
|
3
|
+
@keyframes rainbow {
|
|
4
|
+
0% { color: hsl(0, 70%, 68%); }
|
|
5
|
+
20% { color: hsl(60, 70%, 62%); }
|
|
6
|
+
40% { color: hsl(140, 60%, 58%); }
|
|
7
|
+
60% { color: hsl(210, 75%, 65%); }
|
|
8
|
+
80% { color: hsl(280, 65%, 68%); }
|
|
9
|
+
100% { color: hsl(360, 70%, 68%); }
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
@keyframes rainbowGlow {
|
|
13
|
+
0% { filter: drop-shadow(0 0 18px hsla(0, 70%, 68%, var(--glow-alpha, 0))); }
|
|
14
|
+
20% { filter: drop-shadow(0 0 18px hsla(60, 70%, 62%, var(--glow-alpha, 0))); }
|
|
15
|
+
40% { filter: drop-shadow(0 0 18px hsla(140, 60%, 58%, var(--glow-alpha, 0))); }
|
|
16
|
+
60% { filter: drop-shadow(0 0 18px hsla(210, 75%, 65%, var(--glow-alpha, 0))); }
|
|
17
|
+
80% { filter: drop-shadow(0 0 18px hsla(280, 65%, 68%, var(--glow-alpha, 0))); }
|
|
18
|
+
100% { filter: drop-shadow(0 0 18px hsla(360, 70%, 68%, var(--glow-alpha, 0))); }
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
@keyframes float {
|
|
22
|
+
0%, 100% { transform: translateY(0); }
|
|
23
|
+
50% { transform: translateY(-10px); }
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
@keyframes pulse-logo {
|
|
27
|
+
0%, 100% { transform: scale(1); opacity: 0.8; }
|
|
28
|
+
50% { transform: scale(1.05); opacity: 1; }
|
|
29
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { Metadata, Viewport } from 'next'
|
|
2
|
+
import { DashiProvider } from './providers'
|
|
3
|
+
import './globals.css'
|
|
4
|
+
|
|
5
|
+
export const metadata: Metadata = {
|
|
6
|
+
title: '{{APP_NAME}}',
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const viewport: Viewport = {
|
|
10
|
+
width: 'device-width',
|
|
11
|
+
initialScale: 1,
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
15
|
+
return (
|
|
16
|
+
<html lang="en">
|
|
17
|
+
<body>
|
|
18
|
+
<DashiProvider>{children}</DashiProvider>
|
|
19
|
+
</body>
|
|
20
|
+
</html>
|
|
21
|
+
)
|
|
22
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useDashiContext } from '@getdashi/client/react'
|
|
4
|
+
import { usePreferences } from './preferences'
|
|
5
|
+
|
|
6
|
+
// undefined = inherit currentColor from parent so the logo adapts to any background
|
|
7
|
+
const COLOR_VALUE: Record<string, string | undefined> = {
|
|
8
|
+
rainbow: undefined, // driven by keyframe animation
|
|
9
|
+
monochrome: undefined, // inherits parent text color
|
|
10
|
+
purple: 'rgba(167,139,250,1)',
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const GLOW_VALUE: Record<string, string> = {
|
|
14
|
+
rainbow: '', // driven by rainbowGlow keyframe + --glow-alpha
|
|
15
|
+
monochrome: 'drop-shadow(0 0 18px rgba(255,255,255,0.2))',
|
|
16
|
+
purple: 'drop-shadow(0 0 18px rgba(167,139,250,0.35))',
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export default function Page() {
|
|
20
|
+
const [{ color, animation, glow }] = usePreferences()
|
|
21
|
+
const { preferencesOpen, inputCaptured, openPreferences } = useDashiContext()
|
|
22
|
+
|
|
23
|
+
const logoAnimations: string[] = []
|
|
24
|
+
if (color === 'rainbow') logoAnimations.push('rainbow 4s linear infinite')
|
|
25
|
+
// Always run rainbowGlow when color=rainbow so it stays in sync; --glow-alpha controls opacity.
|
|
26
|
+
if (color === 'rainbow') logoAnimations.push('rainbowGlow 4s linear infinite')
|
|
27
|
+
if (animation === 'float') logoAnimations.push('float 3s ease-in-out infinite')
|
|
28
|
+
if (animation === 'pulse') logoAnimations.push('pulse-logo 2s ease-in-out infinite')
|
|
29
|
+
|
|
30
|
+
const filterValue = color !== 'rainbow' ? (glow ? GLOW_VALUE[color] : 'none') : undefined
|
|
31
|
+
const isDisabled = preferencesOpen || inputCaptured
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<main className="flex flex-col items-center justify-center min-h-screen bg-[#151617] text-white/90 font-sans">
|
|
35
|
+
<div
|
|
36
|
+
style={
|
|
37
|
+
{
|
|
38
|
+
width: 'min(140px, 28vw)',
|
|
39
|
+
color: COLOR_VALUE[color],
|
|
40
|
+
animation: logoAnimations.length ? logoAnimations.join(', ') : 'none',
|
|
41
|
+
filter: filterValue,
|
|
42
|
+
'--glow-alpha': color === 'rainbow' ? (glow ? '0.4' : '0') : undefined,
|
|
43
|
+
} as React.CSSProperties
|
|
44
|
+
}
|
|
45
|
+
className="mb-4 transition-[filter,color] duration-300 ease-[ease]"
|
|
46
|
+
>
|
|
47
|
+
<LogoIcon />
|
|
48
|
+
</div>
|
|
49
|
+
|
|
50
|
+
<h1 className="text-[clamp(26px,5vw,40px)] font-serif italic font-normal tracking-[-0.01em] mb-[10px] text-white/90">
|
|
51
|
+
Welcome to your app!
|
|
52
|
+
</h1>
|
|
53
|
+
|
|
54
|
+
<p className="text-[clamp(16px,3vw,22px)] text-white/45 tracking-[-0.01em] mb-5">
|
|
55
|
+
Edit <Code>app/page.tsx</Code> to get started.
|
|
56
|
+
</p>
|
|
57
|
+
|
|
58
|
+
<button
|
|
59
|
+
type="button"
|
|
60
|
+
onClick={openPreferences}
|
|
61
|
+
disabled={isDisabled}
|
|
62
|
+
className={`bg-white/3 border border-white/12 text-white/70 text-[13px] px-[18px] py-2 rounded-lg transition-[opacity,background] duration-200 ease-[ease] ${isDisabled ? 'opacity-30 cursor-default' : 'cursor-pointer'}`}
|
|
63
|
+
>
|
|
64
|
+
Preferences →
|
|
65
|
+
</button>
|
|
66
|
+
</main>
|
|
67
|
+
)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function Code({ children }: { children: React.ReactNode }) {
|
|
71
|
+
return (
|
|
72
|
+
<code className="font-mono text-[0.75em] bg-black/30 border border-white/10 rounded-[6px] py-[6px] px-[6px] text-white/45">
|
|
73
|
+
{children}
|
|
74
|
+
</code>
|
|
75
|
+
)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function LogoIcon() {
|
|
79
|
+
return (
|
|
80
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="44 98 414 304" fill="currentColor">
|
|
81
|
+
<path
|
|
82
|
+
d="M456.3,365.6L427,127.1c-1.9-15.1-14.7-26.5-30-26.5H88.8c-16,0-29.2,12.5-30.1,28.4L47.1,329.6c-0.6,9.7,3.1,19.2,9.9,26.1
|
|
83
|
+
l33,32.9c7,7,16.4,10.9,26.4,10.9h310c8.6,0,16.9-3.7,22.6-10.2C454.7,382.8,457.4,374.1,456.3,365.6z M113.4,211.1l-6.4-52.5
|
|
84
|
+
c-1.1-9.1,5.4-16.5,14.5-16.5h93.8c9.1,0,17.4,7.4,18.6,16.5l6.4,52.5c1.1,9.1-5.4,16.5-14.5,16.5h-93.8
|
|
85
|
+
C122.8,227.6,114.5,220.2,113.4,211.1z M239.6,340.4h-93.8c-9.1,0-17.4-7.4-18.6-16.5l-6.3-51.5c-1.1-9.1,5.4-16.5,14.5-16.5h93.8
|
|
86
|
+
c9.1,0,17.4,7.4,18.6,16.5l6.3,51.5C255.2,333,248.7,340.4,239.6,340.4z M394.6,340.4h-93.8c-9.1,0-17.4-7.4-18.6-16.5L262,158.6
|
|
87
|
+
c-1.1-9.1,5.4-16.5,14.5-16.5h93.8c9.1,0,17.4,7.4,18.6,16.5l20.3,165.3C410.2,333,403.7,340.4,394.6,340.4z"
|
|
88
|
+
/>
|
|
89
|
+
</svg>
|
|
90
|
+
)
|
|
91
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { type Animation, type Color, usePreferences } from '../preferences'
|
|
4
|
+
|
|
5
|
+
const COLOR_OPTIONS: { value: Color; label: string }[] = [
|
|
6
|
+
{ value: 'rainbow', label: 'Rainbow' },
|
|
7
|
+
{ value: 'purple', label: 'Purple' },
|
|
8
|
+
{ value: 'monochrome', label: 'Mono' },
|
|
9
|
+
]
|
|
10
|
+
|
|
11
|
+
const ANIMATION_OPTIONS: { value: Animation; label: string }[] = [
|
|
12
|
+
{ value: 'float', label: 'Float' },
|
|
13
|
+
{ value: 'pulse', label: 'Pulse' },
|
|
14
|
+
{ value: 'none', label: 'None' },
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
export default function PreferencesPage() {
|
|
18
|
+
const [preferences, setPreferences] = usePreferences()
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<main className="flex flex-col min-h-screen font-mono bg-[#0e0e10] text-[#e5e5e5] p-5">
|
|
22
|
+
<div className="text-sky-500 text-[11px] tracking-[0.08em] uppercase font-semibold mb-4">
|
|
23
|
+
◆ preferences
|
|
24
|
+
</div>
|
|
25
|
+
|
|
26
|
+
<div className="flex flex-col gap-[14px]">
|
|
27
|
+
<PrefRow label="Color">
|
|
28
|
+
{COLOR_OPTIONS.map(opt => (
|
|
29
|
+
<Chip
|
|
30
|
+
key={opt.value}
|
|
31
|
+
active={preferences.color === opt.value}
|
|
32
|
+
onClick={() => setPreferences({ color: opt.value })}
|
|
33
|
+
>
|
|
34
|
+
{opt.label}
|
|
35
|
+
</Chip>
|
|
36
|
+
))}
|
|
37
|
+
</PrefRow>
|
|
38
|
+
|
|
39
|
+
<PrefRow label="Animation">
|
|
40
|
+
{ANIMATION_OPTIONS.map(opt => (
|
|
41
|
+
<Chip
|
|
42
|
+
key={opt.value}
|
|
43
|
+
active={preferences.animation === opt.value}
|
|
44
|
+
onClick={() => setPreferences({ animation: opt.value })}
|
|
45
|
+
>
|
|
46
|
+
{opt.label}
|
|
47
|
+
</Chip>
|
|
48
|
+
))}
|
|
49
|
+
</PrefRow>
|
|
50
|
+
|
|
51
|
+
<PrefRow label="Glow">
|
|
52
|
+
<Chip active={preferences.glow} onClick={() => setPreferences({ glow: true })}>
|
|
53
|
+
On
|
|
54
|
+
</Chip>
|
|
55
|
+
<Chip active={!preferences.glow} onClick={() => setPreferences({ glow: false })}>
|
|
56
|
+
Off
|
|
57
|
+
</Chip>
|
|
58
|
+
</PrefRow>
|
|
59
|
+
</div>
|
|
60
|
+
</main>
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function PrefRow({ label, children }: { label: string; children: React.ReactNode }) {
|
|
65
|
+
return (
|
|
66
|
+
<div className="flex items-center gap-3 text-xs">
|
|
67
|
+
<span className="text-gray-500 min-w-[80px] shrink-0">{label}</span>
|
|
68
|
+
<div className="flex gap-1 flex-wrap">{children}</div>
|
|
69
|
+
</div>
|
|
70
|
+
)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function Chip({
|
|
74
|
+
active,
|
|
75
|
+
onClick,
|
|
76
|
+
children,
|
|
77
|
+
}: {
|
|
78
|
+
active: boolean
|
|
79
|
+
onClick: () => void
|
|
80
|
+
children: React.ReactNode
|
|
81
|
+
}) {
|
|
82
|
+
return (
|
|
83
|
+
<button
|
|
84
|
+
type="button"
|
|
85
|
+
onClick={onClick}
|
|
86
|
+
className={`px-[10px] py-1 rounded border text-[11px] cursor-pointer font-[inherit] transition-[border-color,background,color] duration-150 ${
|
|
87
|
+
active
|
|
88
|
+
? 'border-sky-500 bg-sky-500/10 text-sky-500'
|
|
89
|
+
: 'border-neutral-600 bg-transparent text-gray-500'
|
|
90
|
+
}`}
|
|
91
|
+
>
|
|
92
|
+
{children}
|
|
93
|
+
</button>
|
|
94
|
+
)
|
|
95
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { buildDashiProvider } from '@getdashi/client/react'
|
|
4
|
+
|
|
5
|
+
const COLORS = ['rainbow', 'purple', 'monochrome'] as const
|
|
6
|
+
const ANIMATIONS = ['float', 'pulse', 'none'] as const
|
|
7
|
+
export type Color = (typeof COLORS)[number]
|
|
8
|
+
export type Animation = (typeof ANIMATIONS)[number]
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Type guard: narrows `unknown` to a member of a readonly tuple.
|
|
12
|
+
*/
|
|
13
|
+
function isOneOf<T>(options: readonly T[], value: unknown): value is T {
|
|
14
|
+
return (options as readonly unknown[]).includes(value)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const { Provider: DashiClientProvider, usePreferences } = buildDashiProvider({
|
|
18
|
+
preferences(stored) {
|
|
19
|
+
return {
|
|
20
|
+
color: isOneOf(COLORS, stored.color) ? stored.color : 'rainbow',
|
|
21
|
+
animation: isOneOf(ANIMATIONS, stored.animation) ? stored.animation : 'float',
|
|
22
|
+
glow: stored.glow === true,
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
})
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// Next.js only exposes the request cookie header inside server components,
|
|
2
|
+
// but preference state lives in React context (client-only). createDashiProvider
|
|
3
|
+
// bridges the gap: it's a server component that reads the cookie and passes it
|
|
4
|
+
// to DashiClientProvider as a plain string — the only value that can cross the
|
|
5
|
+
// server → client boundary.
|
|
6
|
+
import { createDashiProvider } from '@getdashi/client/next'
|
|
7
|
+
import { DashiClientProvider } from './preferences'
|
|
8
|
+
|
|
9
|
+
export const DashiProvider = createDashiProvider({ clientProvider: DashiClientProvider })
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import eslint from '@eslint/js'
|
|
2
|
+
import tseslint from 'typescript-eslint'
|
|
3
|
+
import prettierConfig from 'eslint-config-prettier'
|
|
4
|
+
|
|
5
|
+
export default tseslint.config(
|
|
6
|
+
{ ignores: ['node_modules/**', '.next/**', 'dist/**'] },
|
|
7
|
+
eslint.configs.recommended,
|
|
8
|
+
...tseslint.configs.recommended,
|
|
9
|
+
prettierConfig,
|
|
10
|
+
{
|
|
11
|
+
rules: {
|
|
12
|
+
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
|
|
13
|
+
'@typescript-eslint/consistent-type-imports': [
|
|
14
|
+
'error',
|
|
15
|
+
{
|
|
16
|
+
prefer: 'type-imports',
|
|
17
|
+
fixStyle: 'separate-type-imports',
|
|
18
|
+
disallowTypeAnnotations: false,
|
|
19
|
+
},
|
|
20
|
+
],
|
|
21
|
+
},
|
|
22
|
+
}
|
|
23
|
+
)
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { type NextRequest, NextResponse } from 'next/server'
|
|
2
|
+
|
|
3
|
+
const CONTEXT_PARAM = 'dashi_context'
|
|
4
|
+
const INPUT_CAPTURED_PARAM = 'dashi_input_captured'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Dashi middleware.
|
|
8
|
+
*
|
|
9
|
+
* Reads URL params injected by the container and converts them to
|
|
10
|
+
* cookies so Next.js server components can read them on the first
|
|
11
|
+
* request (SSR).
|
|
12
|
+
*
|
|
13
|
+
* Dashi_context — signed JWT identifying the dashboard; kept
|
|
14
|
+
* long-term. dashi_input_captured — SSR hint: '1' when the app starts
|
|
15
|
+
* in a captured-input state (e.g. tap-to-fullscreen split). Updated
|
|
16
|
+
* each load.
|
|
17
|
+
*/
|
|
18
|
+
export function middleware(request: NextRequest) {
|
|
19
|
+
const token = request.nextUrl.searchParams.get(CONTEXT_PARAM)
|
|
20
|
+
const inputCaptured = request.nextUrl.searchParams.get(INPUT_CAPTURED_PARAM)
|
|
21
|
+
|
|
22
|
+
if (!token && inputCaptured === null) return NextResponse.next()
|
|
23
|
+
|
|
24
|
+
const existing = request.headers.get('cookie') ?? ''
|
|
25
|
+
const requestHeaders = new Headers(request.headers)
|
|
26
|
+
|
|
27
|
+
// Build an updated cookie string with the new values injected.
|
|
28
|
+
const additions: string[] = []
|
|
29
|
+
if (token) additions.push(`${CONTEXT_PARAM}=${encodeURIComponent(token)}`)
|
|
30
|
+
if (inputCaptured !== null)
|
|
31
|
+
additions.push(`${INPUT_CAPTURED_PARAM}=${encodeURIComponent(inputCaptured)}`)
|
|
32
|
+
// Additions go first so they take precedence over any stale cookies with the
|
|
33
|
+
// same name already in the browser's cookie jar.
|
|
34
|
+
requestHeaders.set('cookie', [...additions, existing].filter(Boolean).join('; '))
|
|
35
|
+
|
|
36
|
+
const response = NextResponse.next({ request: { headers: requestHeaders } })
|
|
37
|
+
if (token) response.cookies.set(CONTEXT_PARAM, token, { path: '/', sameSite: 'lax' })
|
|
38
|
+
// Session-scoped: always reflects the current load's captured state.
|
|
39
|
+
if (inputCaptured !== null)
|
|
40
|
+
response.cookies.set(INPUT_CAPTURED_PARAM, inputCaptured, { path: '/', sameSite: 'lax' })
|
|
41
|
+
return response
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export const config = {
|
|
45
|
+
matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],
|
|
46
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/** @type {import('next').NextConfig} */
|
|
2
|
+
const nextConfig = {
|
|
3
|
+
// The preview tool runs on a different origin, so manifest.json must be
|
|
4
|
+
// cross-origin readable.
|
|
5
|
+
async headers() {
|
|
6
|
+
return [
|
|
7
|
+
{
|
|
8
|
+
source: '/manifest.json',
|
|
9
|
+
headers: [{ key: 'Access-Control-Allow-Origin', value: '*' }],
|
|
10
|
+
},
|
|
11
|
+
]
|
|
12
|
+
},
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export default nextConfig
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{APP_NAME}}",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"scripts": {
|
|
6
|
+
"dev": "dashi app dev --port {{APP_PORT}}",
|
|
7
|
+
"build:dev": "next dev",
|
|
8
|
+
"build": "next build",
|
|
9
|
+
"start": "next start",
|
|
10
|
+
"lint": "eslint .",
|
|
11
|
+
"lint:fix": "eslint . --fix",
|
|
12
|
+
"format": "prettier --write .",
|
|
13
|
+
"format:check": "prettier --check ."
|
|
14
|
+
},
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"@getdashi/client": "latest",
|
|
17
|
+
"next": "^14.2.10",
|
|
18
|
+
"react": "^18.3.1",
|
|
19
|
+
"react-dom": "^18.3.1"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"@getdashi/cli": "latest",
|
|
23
|
+
"@eslint/js": "^9.23.0",
|
|
24
|
+
"@tailwindcss/postcss": "^4.2.1",
|
|
25
|
+
"@types/node": "^20.11.19",
|
|
26
|
+
"@types/react": "^19.0.0",
|
|
27
|
+
"@types/react-dom": "^19.0.0",
|
|
28
|
+
"eslint": "^9.23.0",
|
|
29
|
+
"eslint-config-prettier": "^10.1.1",
|
|
30
|
+
"postcss": "^8.5.6",
|
|
31
|
+
"prettier": "^3.5.3",
|
|
32
|
+
"tailwindcss": "^4.2.1",
|
|
33
|
+
"typescript": "^5.3.3",
|
|
34
|
+
"typescript-eslint": "^8.28.0"
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"lib": ["dom", "dom.iterable", "esnext"],
|
|
5
|
+
"allowJs": true,
|
|
6
|
+
"skipLibCheck": true,
|
|
7
|
+
"strict": true,
|
|
8
|
+
"noEmit": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"module": "esnext",
|
|
11
|
+
"moduleResolution": "bundler",
|
|
12
|
+
"resolveJsonModule": true,
|
|
13
|
+
"isolatedModules": true,
|
|
14
|
+
"jsx": "preserve",
|
|
15
|
+
"incremental": true,
|
|
16
|
+
"allowImportingTsExtensions": true,
|
|
17
|
+
"plugins": [{ "name": "next" }],
|
|
18
|
+
"paths": {
|
|
19
|
+
"@/*": ["./*"]
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
|
23
|
+
"exclude": ["node_modules"]
|
|
24
|
+
}
|