pni 1.0.1 → 1.0.2

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/app.js CHANGED
@@ -4,7 +4,7 @@ import { detectProjectType } from './utils/project-detection.js';
4
4
  import { createApp } from './utils/app-creation.js';
5
5
  import { detectPackageManager, getInstallCommand, getDevInstallCommand, } from './utils/package-manager.js';
6
6
  import { getDependencies } from './utils/dependencies.js';
7
- import { generateConfigFiles } from './utils/config-generator.js';
7
+ import { generateConfigFiles, setupNuxtAppStructure } from './utils/config-generator.js';
8
8
  import { generateCSSVariables, updateIndexHtml, createTypographyPage } from './utils/css-variables.js';
9
9
  import { setupShadcnNuxt } from './utils/shadcn-setup.js';
10
10
  import FeatureSelector from './components/FeatureSelector.js';
@@ -71,6 +71,10 @@ export default function App({ nuxt = false, vue = false, threejs = false, cssVar
71
71
  // Generate configuration files
72
72
  setStep('configuring');
73
73
  await generateConfigFiles(finalProjectType, workingPath, selectedFeatures.threejs, selectedFeatures.cssVars);
74
+ // Set up Nuxt app structure (app.vue, pages/index.vue, Lenis)
75
+ if (finalProjectType === 'nuxt') {
76
+ await setupNuxtAppStructure(workingPath);
77
+ }
74
78
  // Generate CSS variables (always enabled - will be overwritten after shadcn-setup)
75
79
  if (finalProjectType === 'nuxt') {
76
80
  // For Nuxt, first create basic tailwind.css
@@ -0,0 +1,214 @@
1
+ @import "tailwindcss";
2
+ @import "tw-animate-css";
3
+
4
+ @font-face {
5
+ font-family: 'Helvetica';
6
+ src: url('~/assets/fonts/Helvetica.ttf') format('truetype');
7
+ font-weight: normal;
8
+ font-style: normal;
9
+ font-display: swap;
10
+ }
11
+
12
+ @custom-variant dark (&:is(.dark *));
13
+
14
+ @theme inline {
15
+ --spacing: 4rem;
16
+ --font-helvetica: 'Helvetica', sans-serif;
17
+ --radius-sm: calc(var(--radius) - 4px);
18
+ --radius-md: calc(var(--radius) - 2px);
19
+ --radius-lg: var(--radius);
20
+ --radius-xl: calc(var(--radius) + 4px);
21
+ --color-background: var(--background);
22
+ --color-foreground: var(--foreground);
23
+ --color-card: var(--card);
24
+ --color-card-foreground: var(--card-foreground);
25
+ --color-popover: var(--popover);
26
+ --color-popover-foreground: var(--popover-foreground);
27
+ --color-primary: var(--primary);
28
+ --color-primary-foreground: var(--primary-foreground);
29
+ --color-secondary: var(--secondary);
30
+ --color-secondary-foreground: var(--secondary-foreground);
31
+ --color-muted: var(--muted);
32
+ --color-muted-foreground: var(--muted-foreground);
33
+ --color-accent: var(--accent);
34
+ --color-accent-foreground: var(--accent-foreground);
35
+ --color-destructive: var(--destructive);
36
+ --color-border: var(--border);
37
+ --color-input: var(--input);
38
+ --color-ring: var(--ring);
39
+ --color-chart-1: var(--chart-1);
40
+ --color-chart-2: var(--chart-2);
41
+ --color-chart-3: var(--chart-3);
42
+ --color-chart-4: var(--chart-4);
43
+ --color-chart-5: var(--chart-5);
44
+ --color-sidebar: var(--sidebar);
45
+ --color-sidebar-foreground: var(--sidebar-foreground);
46
+ --color-sidebar-primary: var(--sidebar-primary);
47
+ --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
48
+ --color-sidebar-accent: var(--sidebar-accent);
49
+ --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
50
+ --color-sidebar-border: var(--sidebar-border);
51
+ --color-sidebar-ring: var(--sidebar-ring);
52
+ }
53
+
54
+ :root {
55
+ /* osmo scaling system starts here */
56
+ --size-unit: 16; /* body font-size in design - no px */
57
+ --size-container-ideal: 1440; /* screen-size in design - no px */
58
+ --size-container-min: 992px;
59
+ --size-container-max: 1920px;
60
+ --size-container: clamp(var(--size-container-min), 100vw, var(--size-container-max));
61
+ --size-font: calc(var(--size-container) / (var(--size-container-ideal) / var(--size-unit)));
62
+
63
+ /* 1 fluid pixel unit */
64
+ --fluid-px: calc(var(--size-font) / 16);
65
+ /* osmo scaling system ends here */
66
+
67
+
68
+
69
+ --radius: 0.625rem;
70
+ --background: oklch(1 0 0);
71
+ --foreground: oklch(0.145 0 0);
72
+ --card: oklch(1 0 0);
73
+ --card-foreground: oklch(0.145 0 0);
74
+ --popover: oklch(1 0 0);
75
+ --popover-foreground: oklch(0.145 0 0);
76
+ --primary: oklch(0.205 0 0);
77
+ --primary-foreground: oklch(0.985 0 0);
78
+ --secondary: oklch(0.97 0 0);
79
+ --secondary-foreground: oklch(0.205 0 0);
80
+ --muted: oklch(0.97 0 0);
81
+ --muted-foreground: oklch(0.556 0 0);
82
+ --accent: oklch(0.97 0 0);
83
+ --accent-foreground: oklch(0.205 0 0);
84
+ --destructive: oklch(0.577 0.245 27.325);
85
+ --border: oklch(0.922 0 0);
86
+ --input: oklch(0.922 0 0);
87
+ --ring: oklch(0.708 0 0);
88
+ --chart-1: oklch(0.646 0.222 41.116);
89
+ --chart-2: oklch(0.6 0.118 184.704);
90
+ --chart-3: oklch(0.398 0.07 227.392);
91
+ --chart-4: oklch(0.828 0.189 84.429);
92
+ --chart-5: oklch(0.769 0.188 70.08);
93
+ --sidebar: oklch(0.985 0 0);
94
+ --sidebar-foreground: oklch(0.145 0 0);
95
+ --sidebar-primary: oklch(0.205 0 0);
96
+ --sidebar-primary-foreground: oklch(0.985 0 0);
97
+ --sidebar-accent: oklch(0.97 0 0);
98
+ --sidebar-accent-foreground: oklch(0.205 0 0);
99
+ --sidebar-border: oklch(0.922 0 0);
100
+ --sidebar-ring: oklch(0.708 0 0);
101
+ }
102
+
103
+ .dark {
104
+ --background: oklch(0.145 0 0);
105
+ --foreground: oklch(0.985 0 0);
106
+ --card: oklch(0.205 0 0);
107
+ --card-foreground: oklch(0.985 0 0);
108
+ --popover: oklch(0.205 0 0);
109
+ --popover-foreground: oklch(0.985 0 0);
110
+ --primary: oklch(0.922 0 0);
111
+ --primary-foreground: oklch(0.205 0 0);
112
+ --secondary: oklch(0.269 0 0);
113
+ --secondary-foreground: oklch(0.985 0 0);
114
+ --muted: oklch(0.269 0 0);
115
+ --muted-foreground: oklch(0.708 0 0);
116
+ --accent: oklch(0.269 0 0);
117
+ --accent-foreground: oklch(0.985 0 0);
118
+ --destructive: oklch(0.704 0.191 22.216);
119
+ --border: oklch(1 0 0 / 10%);
120
+ --input: oklch(1 0 0 / 15%);
121
+ --ring: oklch(0.556 0 0);
122
+ --chart-1: oklch(0.488 0.243 264.376);
123
+ --chart-2: oklch(0.696 0.17 162.48);
124
+ --chart-3: oklch(0.769 0.188 70.08);
125
+ --chart-4: oklch(0.627 0.265 303.9);
126
+ --chart-5: oklch(0.645 0.246 16.439);
127
+ --sidebar: oklch(0.205 0 0);
128
+ --sidebar-foreground: oklch(0.985 0 0);
129
+ --sidebar-primary: oklch(0.488 0.243 264.376);
130
+ --sidebar-primary-foreground: oklch(0.985 0 0);
131
+ --sidebar-accent: oklch(0.269 0 0);
132
+ --sidebar-accent-foreground: oklch(0.985 0 0);
133
+ --sidebar-border: oklch(1 0 0 / 10%);
134
+ --sidebar-ring: oklch(0.556 0 0);
135
+ }
136
+
137
+ @layer base {
138
+ * {
139
+ @apply border-border outline-ring/50;
140
+ }
141
+
142
+ body {
143
+ @apply bg-background text-foreground;
144
+ }
145
+ }
146
+
147
+ html {
148
+ font-size: var(--fluid-px);
149
+ }
150
+
151
+ body {
152
+ /* figma design 16px fontsize = 16rem fluid pixel unit */
153
+ font-size: 16rem;
154
+ font-family: var(--font-helvetica);
155
+ }
156
+
157
+
158
+ /* osmo scaling system starts here */
159
+
160
+
161
+ .container {
162
+ max-width: var(--size-container);
163
+ }
164
+
165
+ .container.medium {
166
+ max-width: calc(var(--size-container) * 0.85);
167
+ }
168
+
169
+ .container.small {
170
+ max-width: calc(var(--size-container) * 0.7);
171
+ }
172
+
173
+ /* Tablet */
174
+ @media screen and (max-width: 991px) {
175
+ :root {
176
+ --size-container-ideal: 834;
177
+ /* screen-size in design - no px */
178
+ --size-container-min: 768px;
179
+ --size-container-max: 991px;
180
+ }
181
+
182
+ body {
183
+ background-color: red;
184
+ }
185
+ }
186
+
187
+ /* Mobile Landscape */
188
+ @media screen and (max-width: 767px) {
189
+ :root {
190
+ --size-container-ideal: 550;
191
+ /* screen-size in design - no px */
192
+ --size-container-min: 480px;
193
+ --size-container-max: 767px;
194
+ }
195
+
196
+ body {
197
+ background-color: blue;
198
+ }
199
+ }
200
+
201
+ /* Mobile Portrait */
202
+ @media screen and (max-width: 479px) {
203
+ :root {
204
+ --size-container-ideal: 375;
205
+ /* screen-size in design - no px */
206
+ --size-container-min: 320px;
207
+ --size-container-max: 479px;
208
+ }
209
+
210
+ body {
211
+ background-color: green;
212
+ }
213
+ }
214
+
@@ -0,0 +1,25 @@
1
+ <script setup lang="ts">
2
+ import { VueLenis } from 'lenis/vue'
3
+
4
+ const lenisOptions = {
5
+ // Lenis options (optional)
6
+ duration: 1.2,
7
+ easing: (t: number) => Math.min(1, 1.001 - Math.pow(2, -10 * t)),
8
+ orientation: 'vertical',
9
+ gestureOrientation: 'vertical',
10
+ smoothWheel: true,
11
+ wheelMultiplier: 1,
12
+ smoothTouch: false,
13
+ touchMultiplier: 2,
14
+ infinite: false,
15
+ }
16
+ </script>
17
+
18
+ <template>
19
+ <VueLenis root :options="lenisOptions">
20
+ <NuxtLayout>
21
+ <NuxtPage />
22
+ </NuxtLayout>
23
+ </VueLenis>
24
+ </template>
25
+
@@ -0,0 +1,90 @@
1
+ {{TAILWIND_IMPORT}}// https://nuxt.com/docs/api/configuration/nuxt-config
2
+ export default defineNuxtConfig({
3
+ // 1. Updated to a realistic 2024/2025 date for Nuxt 4 features
4
+ compatibilityDate: '2024-11-01',
5
+ devtools: { enabled: true },
6
+
7
+ {{CSS_IMPORT}} ssr: true,
8
+
9
+
10
+ // SEO: Centralizing data
11
+ site: {
12
+ name: 'New Setup',
13
+ url: 'https://newsetup.com',
14
+ description: 'A new setup for your project',
15
+ defaultLocale: 'en',
16
+ },
17
+
18
+ // App Config for UI-wide settings
19
+ app: {
20
+ head: {
21
+ charset: 'utf-8',
22
+ viewport: 'width=device-width, initial-scale=1',
23
+ meta: [
24
+ { name: 'description', content: 'A new setup for your project' },
25
+ { name: 'author', content: 'New Setup' },
26
+ { property: 'og:type', content: 'website' },
27
+ { name: 'msapplication-TileColor', content: '#000000' },
28
+ { name: 'theme-color', content: '#000000' },
29
+ ],
30
+ link: [
31
+ // { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' },
32
+ // { rel: 'apple-touch-icon', sizes: '180x180', href: '/apple-touch-icon-180x180.png' },
33
+ // { rel: 'manifest', href: '/manifest.webmanifest' },
34
+ ],
35
+ },
36
+ },
37
+
38
+ {{VITE_CONFIG}} nitro: {
39
+ compressPublicAssets: {
40
+ brotli: true,
41
+ gzip: true,
42
+ },
43
+ // Enable crawling for SEO module compatibility
44
+ prerender: {
45
+ // set to true for production
46
+ crawlLinks: false,
47
+ routes: ['/']
48
+ }
49
+ },
50
+
51
+ modules: [{{MODULES}}],
52
+
53
+
54
+ // Image Optimization
55
+ image: {
56
+ quality: 95,
57
+ format: ['webp'],
58
+ screens: {
59
+ xs: 320,
60
+ sm: 640,
61
+ md: 768,
62
+ lg: 1200,
63
+ xl: 1400,
64
+ xxl: 1800,
65
+ '2xl': 2000,
66
+ },
67
+ },
68
+
69
+ // UI Framework: Shadcn
70
+ {{SHADCN_CONFIG}} ogImage: {
71
+ defaults: {
72
+ component: 'OgImageTemplate',
73
+ props: {
74
+ title: 'New Setup',
75
+ description: 'A new setup for your project',
76
+ image: 'https://newsetup.com/og-image.png',
77
+ },
78
+ }
79
+ },
80
+ robots: {
81
+ disallow: ['/api',],
82
+ },
83
+
84
+
85
+
86
+ sitemap: {
87
+ // sources: ['/api/__sitemap__/urls'] // Fetch from API
88
+ },
89
+ })
90
+
@@ -0,0 +1,14 @@
1
+ <script setup lang="ts">
2
+ </script>
3
+
4
+ <template>
5
+ <div class="min-h-screen flex items-center justify-center">
6
+ <div class="text-center">
7
+ <h1 class="text-4xl font-bold mb-4">Welcome to Nuxt</h1>
8
+ <p class="text-lg text-muted-foreground">
9
+ Get started by editing <code class="px-2 py-1 bg-muted rounded">app/pages/index.vue</code>
10
+ </p>
11
+ </div>
12
+ </div>
13
+ </template>
14
+
@@ -0,0 +1,46 @@
1
+ <script setup lang="ts">
2
+ </script>
3
+
4
+ <template>
5
+ <section class="w-full py-20 px-4 min-h-screen flex items-center justify-center bg-[#DADADA] flex-col gap-16 ">
6
+ <div class="hero-container flex flex-col gap-16 leading-[1.2]">
7
+ <!-- figma design 120px fontsize -->
8
+ <h1 class="md:text-[120rem] text-[72rem]">Heading H1</h1>
9
+ <div class="divider"></div>
10
+ <!-- figma design 80px fontsize -->
11
+ <h2 class="md:text-[80rem] text-[64rem]">Heading H2</h2>
12
+ <div class="divider"></div>
13
+ <h3 class="md:text-[40rem] text-[32rem]">Heading H3</h3>
14
+ <!-- figma design 40px fontsize -->
15
+ <div class="divider"></div>
16
+ <h4 class="md:text-[28rem] text-[24rem]">Heading H4</h4>
17
+ <!-- figma design 28px fontsize -->
18
+ <div class="divider"></div>
19
+ <h5 class="md:text-[20rem] text-[18rem]">Heading H5</h5>
20
+ <!-- figma design 20px fontsize -->
21
+ <div class="divider"></div>
22
+ <!-- figma design 16px fontsize -->
23
+ <p class="text-[16rem] leading-[1.4] max-md:max-w-85.75 w-full font-helvetica">
24
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse varius enim in eros elementum
25
+ tristique. Duis cursus, all links in the website, eros dolor interdum nulla, ut commodo diam libero
26
+ vitae erat. Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque
27
+ laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae
28
+ vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit,
29
+ sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt.
30
+ </p>
31
+ </div>
32
+ </section>
33
+ </template>
34
+
35
+ <style scoped>
36
+ .hero-container {
37
+ max-width: calc(1184em / 16);
38
+ }
39
+
40
+ .divider {
41
+ background-color: #969696;
42
+ height: 1px;
43
+ width: 100%;
44
+ }
45
+ </style>
46
+
@@ -0,0 +1,7 @@
1
+ export default {
2
+ plugins: {
3
+ tailwindcss: {},
4
+ autoprefixer: {},
5
+ },
6
+ }
7
+
@@ -0,0 +1,52 @@
1
+ /** @type {import('tailwindcss').Config} */
2
+ export default {
3
+ content: [
4
+ {{CONTENT_PATHS}}
5
+ ],
6
+ theme: {
7
+ extend: {
8
+ colors: {
9
+ background: 'hsl(var(--background))',
10
+ foreground: 'hsl(var(--foreground))',
11
+ card: {
12
+ DEFAULT: 'hsl(var(--card))',
13
+ foreground: 'hsl(var(--card-foreground))',
14
+ },
15
+ popover: {
16
+ DEFAULT: 'hsl(var(--popover))',
17
+ foreground: 'hsl(var(--popover-foreground))',
18
+ },
19
+ primary: {
20
+ DEFAULT: 'hsl(var(--primary))',
21
+ foreground: 'hsl(var(--primary-foreground))',
22
+ },
23
+ secondary: {
24
+ DEFAULT: 'hsl(var(--secondary))',
25
+ foreground: 'hsl(var(--secondary-foreground))',
26
+ },
27
+ muted: {
28
+ DEFAULT: 'hsl(var(--muted))',
29
+ foreground: 'hsl(var(--muted-foreground))',
30
+ },
31
+ accent: {
32
+ DEFAULT: 'hsl(var(--accent))',
33
+ foreground: 'hsl(var(--accent-foreground))',
34
+ },
35
+ destructive: {
36
+ DEFAULT: 'hsl(var(--destructive))',
37
+ foreground: 'hsl(var(--destructive-foreground))',
38
+ },
39
+ border: 'hsl(var(--border))',
40
+ input: 'hsl(var(--input))',
41
+ ring: 'hsl(var(--ring))',
42
+ },
43
+ borderRadius: {
44
+ lg: 'var(--radius)',
45
+ md: 'calc(var(--radius) - 2px)',
46
+ sm: 'calc(var(--radius) - 4px)',
47
+ },
48
+ },
49
+ },
50
+ plugins: [],
51
+ }
52
+
@@ -0,0 +1,82 @@
1
+ import { fileURLToPath, URL } from 'node:url'
2
+ {{TAILWIND_IMPORT}}import { defineConfig } from 'vite'
3
+ import vue from '@vitejs/plugin-vue'
4
+ import vueDevTools from 'vite-plugin-vue-devtools'
5
+ import viteCompression from 'vite-plugin-compression'
6
+ import Sitemap from 'vite-plugin-sitemap'
7
+ import { ViteImageOptimizer } from 'vite-plugin-image-optimizer'
8
+
9
+ export default defineConfig(({ mode }) => {
10
+ const isProd = mode === 'production'
11
+
12
+ return {
13
+ plugins: [
14
+ vue(),
15
+ {{TAILWIND_PLUGIN}} // 1. Only load DevTools in development
16
+ !isProd && vueDevTools(),
17
+
18
+ ViteImageOptimizer({
19
+ test: /\.(jpe?g|png|gif|tiff|webp|svg|avif)$/i,
20
+ includePublic: true,
21
+ logStats: true,
22
+ png: { quality: 75 },
23
+ jpeg: { quality: 75 },
24
+ jpg: { quality: 75 },
25
+ webp: { lossless: false, quality: 75 },
26
+ avif: { quality: 70 },
27
+ }),
28
+
29
+ Sitemap({
30
+ hostname: 'https://newsetup.com', // Don't forget to update this!
31
+ dynamicRoutes: [],
32
+ }),
33
+
34
+ // 2. Gzip Compression (Universal Fallback)
35
+ viteCompression({
36
+ algorithm: 'gzip',
37
+ ext: '.gz',
38
+ threshold: 10240,
39
+ deleteOriginFile: false,
40
+ }),
41
+
42
+ // 3. Brotli Compression (Modern Performance)
43
+ viteCompression({
44
+ algorithm: 'brotliCompress',
45
+ ext: '.br',
46
+ threshold: 10240,
47
+ deleteOriginFile: false,
48
+ }),
49
+ ],
50
+
51
+ resolve: {
52
+ alias: {
53
+ '@': fileURLToPath(new URL('./src', import.meta.url))
54
+ },
55
+ },
56
+
57
+ build: {
58
+ cssMinify: 'lightningcss', // Ensure 'lightningcss' is in package.json
59
+ // 4. Split Chunks for better Browser Caching
60
+ rollupOptions: {
61
+ output: {
62
+ manualChunks(id) {
63
+ if (id.includes('node_modules')) {
64
+ // Split standard Vue dependencies into their own chunk
65
+ if (id.includes('vue') || id.includes('pinia') || id.includes('vue-router')) {
66
+ return 'vue-vendor';
67
+ }
68
+ {{THREEJS_CHUNK}}
69
+
70
+ return 'vendor';
71
+ }
72
+ },
73
+ },
74
+ },
75
+ },
76
+
77
+ esbuild: {
78
+ drop: isProd ? ['console', 'debugger'] : [],
79
+ },
80
+ }
81
+ })
82
+
@@ -3,4 +3,5 @@ export declare function generateNuxtConfig(projectPath: string, threejs: boolean
3
3
  export declare function generateViteConfig(projectPath: string, threejs: boolean, cssVars?: boolean): Promise<void>;
4
4
  export declare function generateTailwindConfig(projectPath: string, projectType: ProjectType): Promise<void>;
5
5
  export declare function generatePostCSSConfig(projectPath: string): Promise<void>;
6
+ export declare function setupNuxtAppStructure(projectPath: string): Promise<void>;
6
7
  export declare function generateConfigFiles(projectType: ProjectType, projectPath: string, threejs: boolean, cssVars: boolean): Promise<void>;
@@ -1,12 +1,13 @@
1
- import { readFileSync, writeFileSync, existsSync } from 'fs';
1
+ import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
2
2
  import { join } from 'path';
3
+ import { loadTemplateWithReplacements, loadTemplate } from './template-loader.js';
3
4
  export async function generateNuxtConfig(projectPath,
4
5
  //@ts-ignore
5
6
  threejs, cssVars) {
6
7
  const configPath = join(projectPath, 'nuxt.config.ts');
7
8
  // Always replace the config with the full template, regardless of whether it exists
8
- // Generate full config template
9
9
  const modules = [
10
+ 'lenis/nuxt',
10
11
  'shadcn-nuxt',
11
12
  '@nuxtjs/seo',
12
13
  '@nuxt/image',
@@ -17,44 +18,7 @@ threejs, cssVars) {
17
18
  const tailwindImport = cssVars
18
19
  ? "import tailwindcss from '@tailwindcss/vite'\n\n"
19
20
  : '';
20
- const configContent = `${tailwindImport}// https://nuxt.com/docs/api/configuration/nuxt-config
21
- export default defineNuxtConfig({
22
- // 1. Updated to a realistic 2024/2025 date for Nuxt 4 features
23
- compatibilityDate: '2024-11-01',
24
- devtools: { enabled: true },
25
-
26
- ${cssVars ? ` css: ['${cssImport}'],\n\n` : ''} ssr: true,
27
-
28
-
29
- // SEO: Centralizing data
30
- site: {
31
- name: 'New Setup',
32
- url: 'https://newsetup.com',
33
- description: 'A new setup for your project',
34
- defaultLocale: 'en',
35
- },
36
-
37
- // App Config for UI-wide settings
38
- app: {
39
- head: {
40
- charset: 'utf-8',
41
- viewport: 'width=device-width, initial-scale=1',
42
- meta: [
43
- { name: 'description', content: 'A new setup for your project' },
44
- { name: 'author', content: 'New Setup' },
45
- { property: 'og:type', content: 'website' },
46
- { name: 'msapplication-TileColor', content: '#000000' },
47
- { name: 'theme-color', content: '#000000' },
48
- ],
49
- link: [
50
- // { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' },
51
- // { rel: 'apple-touch-icon', sizes: '180x180', href: '/apple-touch-icon-180x180.png' },
52
- // { rel: 'manifest', href: '/manifest.webmanifest' },
53
- ],
54
- },
55
- },
56
-
57
- ${cssVars
21
+ const viteConfig = cssVars
58
22
  ? ` vite: {
59
23
  plugins: [tailwindcss()],
60
24
  esbuild: {
@@ -67,39 +31,8 @@ ${cssVars
67
31
  },
68
32
 
69
33
  `
70
- : ''} nitro: {
71
- compressPublicAssets: {
72
- brotli: true,
73
- gzip: true,
74
- },
75
- // Enable crawling for SEO module compatibility
76
- prerender: {
77
- // set to true for production
78
- crawlLinks: false,
79
- routes: ['/']
80
- }
81
- },
82
-
83
- modules: [${modules.map(m => `'${m}'`).join(', ')}],
84
-
85
-
86
- // Image Optimization
87
- image: {
88
- quality: 95,
89
- format: ['webp'],
90
- screens: {
91
- xs: 320,
92
- sm: 640,
93
- md: 768,
94
- lg: 1200,
95
- xl: 1400,
96
- xxl: 1800,
97
- '2xl': 2000,
98
- },
99
- },
100
-
101
- // UI Framework: Shadcn
102
- ${cssVars
34
+ : '';
35
+ const shadcnConfig = cssVars
103
36
  ? ` shadcn: {
104
37
  prefix: '',
105
38
  componentDir: '@/components/ui',
@@ -107,27 +40,15 @@ ${cssVars
107
40
 
108
41
 
109
42
  `
110
- : ''} ogImage: {
111
- defaults: {
112
- component: 'OgImageTemplate',
113
- props: {
114
- title: 'New Setup',
115
- description: 'A new setup for your project',
116
- image: 'https://newsetup.com/og-image.png',
117
- },
118
- }
119
- },
120
- robots: {
121
- disallow: ['/api',],
122
- },
123
-
124
-
125
-
126
- sitemap: {
127
- // sources: ['/api/__sitemap__/urls'] // Fetch from API
128
- },
129
- })
130
- `;
43
+ : '';
44
+ const cssImportLine = cssVars ? ` css: ['${cssImport}'],\n\n` : '';
45
+ const configContent = loadTemplateWithReplacements('nuxt/nuxt.config.ts.template', {
46
+ TAILWIND_IMPORT: tailwindImport,
47
+ CSS_IMPORT: cssImportLine,
48
+ VITE_CONFIG: viteConfig,
49
+ MODULES: modules.map(m => `'${m}'`).join(', '),
50
+ SHADCN_CONFIG: shadcnConfig,
51
+ });
131
52
  // Always write the full config, replacing any existing one
132
53
  writeFileSync(configPath, configContent, 'utf-8');
133
54
  }
@@ -159,167 +80,54 @@ threejs, cssVars = false) {
159
80
  ? "import tailwindcss from '@tailwindcss/vite'\n"
160
81
  : '';
161
82
  const tailwindPlugin = cssVars ? ' tailwindcss(),\n' : '';
162
- configContent = `import { fileURLToPath, URL } from 'node:url'
163
- ${tailwindImport}import { defineConfig } from 'vite'
164
- import vue from '@vitejs/plugin-vue'
165
- import vueDevTools from 'vite-plugin-vue-devtools'
166
- import viteCompression from 'vite-plugin-compression'
167
- import Sitemap from 'vite-plugin-sitemap'
168
- import { ViteImageOptimizer } from 'vite-plugin-image-optimizer'
169
-
170
- export default defineConfig(({ mode }) => {
171
- const isProd = mode === 'production'
172
-
173
- return {
174
- plugins: [
175
- vue(),
176
- ${tailwindPlugin} // 1. Only load DevTools in development
177
- !isProd && vueDevTools(),
178
-
179
- ViteImageOptimizer({
180
- test: /\\.(jpe?g|png|gif|tiff|webp|svg|avif)$/i,
181
- includePublic: true,
182
- logStats: true,
183
- png: { quality: 75 },
184
- jpeg: { quality: 75 },
185
- jpg: { quality: 75 },
186
- webp: { lossless: false, quality: 75 },
187
- avif: { quality: 70 },
188
- }),
189
-
190
- Sitemap({
191
- hostname: 'https://newsetup.com', // Don't forget to update this!
192
- dynamicRoutes: [],
193
- }),
194
-
195
- // 2. Gzip Compression (Universal Fallback)
196
- viteCompression({
197
- algorithm: 'gzip',
198
- ext: '.gz',
199
- threshold: 10240,
200
- deleteOriginFile: false,
201
- }),
202
-
203
- // 3. Brotli Compression (Modern Performance)
204
- viteCompression({
205
- algorithm: 'brotliCompress',
206
- ext: '.br',
207
- threshold: 10240,
208
- deleteOriginFile: false,
209
- }),
210
- ],
211
-
212
- resolve: {
213
- alias: {
214
- '@': fileURLToPath(new URL('./src', import.meta.url))
215
- },
216
- },
217
-
218
- build: {
219
- cssMinify: 'lightningcss', // Ensure 'lightningcss' is in package.json
220
- // 4. Split Chunks for better Browser Caching
221
- rollupOptions: {
222
- output: {
223
- manualChunks(id) {
224
- if (id.includes('node_modules')) {
225
- // Split standard Vue dependencies into their own chunk
226
- if (id.includes('vue') || id.includes('pinia') || id.includes('vue-router')) {
227
- return 'vue-vendor';
228
- }
229
- ${threejsChunk}
230
-
231
- return 'vendor';
232
- }
233
- },
234
- },
235
- },
236
- },
237
-
238
- esbuild: {
239
- drop: isProd ? ['console', 'debugger'] : [],
240
- },
241
- }
242
- })
243
- `;
83
+ configContent = loadTemplateWithReplacements('vite/vite.config.ts.template', {
84
+ TAILWIND_IMPORT: tailwindImport,
85
+ TAILWIND_PLUGIN: tailwindPlugin,
86
+ THREEJS_CHUNK: threejsChunk,
87
+ });
244
88
  }
245
89
  writeFileSync(configPath, configContent, 'utf-8');
246
90
  }
247
91
  export async function generateTailwindConfig(projectPath, projectType) {
248
92
  const configPath = join(projectPath, 'tailwind.config.js');
249
- const configContent = `/** @type {import('tailwindcss').Config} */
250
- export default {
251
- content: [
252
- ${projectType === 'nuxt'
253
- ? "'./components/**/*.{js,vue,ts}',"
254
- : "'./index.html',"}
255
- ${projectType === 'nuxt'
256
- ? "'./layouts/**/*.vue',"
257
- : "'./src/**/*.{vue,js,ts,jsx,tsx}',"}
258
- ${projectType === 'nuxt' ? "'./pages/**/*.vue'," : ''}
259
- ${projectType === 'nuxt' ? "'./plugins/**/*.{js,ts}'," : ''}
260
- ${projectType === 'nuxt' ? "'./app.vue'," : ''}
261
- ${projectType === 'nuxt' ? "'./error.vue'," : ''}
262
- ],
263
- theme: {
264
- extend: {
265
- colors: {
266
- background: 'hsl(var(--background))',
267
- foreground: 'hsl(var(--foreground))',
268
- card: {
269
- DEFAULT: 'hsl(var(--card))',
270
- foreground: 'hsl(var(--card-foreground))',
271
- },
272
- popover: {
273
- DEFAULT: 'hsl(var(--popover))',
274
- foreground: 'hsl(var(--popover-foreground))',
275
- },
276
- primary: {
277
- DEFAULT: 'hsl(var(--primary))',
278
- foreground: 'hsl(var(--primary-foreground))',
279
- },
280
- secondary: {
281
- DEFAULT: 'hsl(var(--secondary))',
282
- foreground: 'hsl(var(--secondary-foreground))',
283
- },
284
- muted: {
285
- DEFAULT: 'hsl(var(--muted))',
286
- foreground: 'hsl(var(--muted-foreground))',
287
- },
288
- accent: {
289
- DEFAULT: 'hsl(var(--accent))',
290
- foreground: 'hsl(var(--accent-foreground))',
291
- },
292
- destructive: {
293
- DEFAULT: 'hsl(var(--destructive))',
294
- foreground: 'hsl(var(--destructive-foreground))',
295
- },
296
- border: 'hsl(var(--border))',
297
- input: 'hsl(var(--input))',
298
- ring: 'hsl(var(--ring))',
299
- },
300
- borderRadius: {
301
- lg: 'var(--radius)',
302
- md: 'calc(var(--radius) - 2px)',
303
- sm: 'calc(var(--radius) - 4px)',
304
- },
305
- },
306
- },
307
- plugins: [],
308
- }
309
- `;
93
+ let contentPaths;
94
+ if (projectType === 'nuxt') {
95
+ contentPaths = ` './components/**/*.{js,vue,ts}',
96
+ './layouts/**/*.vue',
97
+ './pages/**/*.vue',
98
+ './plugins/**/*.{js,ts}',
99
+ './app.vue',
100
+ './error.vue',`;
101
+ }
102
+ else {
103
+ contentPaths = ` './index.html',
104
+ './src/**/*.{vue,js,ts,jsx,tsx}',`;
105
+ }
106
+ const configContent = loadTemplateWithReplacements('tailwind/tailwind.config.js.template', {
107
+ CONTENT_PATHS: contentPaths,
108
+ });
310
109
  writeFileSync(configPath, configContent, 'utf-8');
311
110
  }
312
111
  export async function generatePostCSSConfig(projectPath) {
313
112
  const configPath = join(projectPath, 'postcss.config.js');
314
- const configContent = `export default {
315
- plugins: {
316
- tailwindcss: {},
317
- autoprefixer: {},
318
- },
319
- }
320
- `;
113
+ const configContent = loadTemplate('postcss/postcss.config.js.template');
321
114
  writeFileSync(configPath, configContent, 'utf-8');
322
115
  }
116
+ export async function setupNuxtAppStructure(projectPath) {
117
+ // Set up app.vue with NuxtLayout, NuxtPage, and Lenis
118
+ const appVuePath = join(projectPath, 'app.vue');
119
+ const appVueContent = loadTemplate('nuxt/app.vue.template');
120
+ writeFileSync(appVuePath, appVueContent, 'utf-8');
121
+ // Create app/pages directory if it doesn't exist
122
+ const pagesDir = join(projectPath, 'app', 'pages');
123
+ if (!existsSync(pagesDir)) {
124
+ mkdirSync(pagesDir, { recursive: true });
125
+ }
126
+ // Create app/pages/index.vue
127
+ const indexPagePath = join(pagesDir, 'index.vue');
128
+ const indexPageContent = loadTemplate('nuxt/pages/index.vue.template');
129
+ writeFileSync(indexPagePath, indexPageContent, 'utf-8');
130
+ }
323
131
  export async function generateConfigFiles(projectType, projectPath, threejs, cssVars) {
324
132
  if (projectType === 'nuxt') {
325
133
  await generateNuxtConfig(projectPath, threejs, cssVars);
@@ -1,221 +1,6 @@
1
1
  import { writeFileSync, mkdirSync, readFileSync, existsSync } from 'fs';
2
2
  import { join, dirname } from 'path';
3
- const CSS_VARIABLES_CONTENT = `
4
- @import "tailwindcss";
5
- @import "tw-animate-css";
6
-
7
- @font-face {
8
- font-family: 'Helvetica';
9
- src: url('~/assets/fonts/Helvetica.ttf') format('truetype');
10
- font-weight: normal;
11
- font-style: normal;
12
- font-display: swap;
13
- }
14
-
15
- @custom-variant dark (&:is(.dark *));
16
-
17
- @theme inline {
18
- --spacing: 4rem;
19
- --font-helvetica: 'Helvetica', sans-serif;
20
- --radius-sm: calc(var(--radius) - 4px);
21
- --radius-md: calc(var(--radius) - 2px);
22
- --radius-lg: var(--radius);
23
- --radius-xl: calc(var(--radius) + 4px);
24
- --color-background: var(--background);
25
- --color-foreground: var(--foreground);
26
- --color-card: var(--card);
27
- --color-card-foreground: var(--card-foreground);
28
- --color-popover: var(--popover);
29
- --color-popover-foreground: var(--popover-foreground);
30
- --color-primary: var(--primary);
31
- --color-primary-foreground: var(--primary-foreground);
32
- --color-secondary: var(--secondary);
33
- --color-secondary-foreground: var(--secondary-foreground);
34
- --color-muted: var(--muted);
35
- --color-muted-foreground: var(--muted-foreground);
36
- --color-accent: var(--accent);
37
- --color-accent-foreground: var(--accent-foreground);
38
- --color-destructive: var(--destructive);
39
- --color-border: var(--border);
40
- --color-input: var(--input);
41
- --color-ring: var(--ring);
42
- --color-chart-1: var(--chart-1);
43
- --color-chart-2: var(--chart-2);
44
- --color-chart-3: var(--chart-3);
45
- --color-chart-4: var(--chart-4);
46
- --color-chart-5: var(--chart-5);
47
- --color-sidebar: var(--sidebar);
48
- --color-sidebar-foreground: var(--sidebar-foreground);
49
- --color-sidebar-primary: var(--sidebar-primary);
50
- --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
51
- --color-sidebar-accent: var(--sidebar-accent);
52
- --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
53
- --color-sidebar-border: var(--sidebar-border);
54
- --color-sidebar-ring: var(--sidebar-ring);
55
- }
56
-
57
- :root {
58
- /* osmo scaling system starts here */
59
- --size-unit: 16; /* body font-size in design - no px */
60
- --size-container-ideal: 1440; /* screen-size in design - no px */
61
- --size-container-min: 992px;
62
- --size-container-max: 1920px;
63
- --size-container: clamp(var(--size-container-min), 100vw, var(--size-container-max));
64
- --size-font: calc(var(--size-container) / (var(--size-container-ideal) / var(--size-unit)));
65
-
66
- /* 1 fluid pixel unit */
67
- --fluid-px: calc(var(--size-font) / 16);
68
- /* osmo scaling system ends here */
69
-
70
-
71
-
72
- --radius: 0.625rem;
73
- --background: oklch(1 0 0);
74
- --foreground: oklch(0.145 0 0);
75
- --card: oklch(1 0 0);
76
- --card-foreground: oklch(0.145 0 0);
77
- --popover: oklch(1 0 0);
78
- --popover-foreground: oklch(0.145 0 0);
79
- --primary: oklch(0.205 0 0);
80
- --primary-foreground: oklch(0.985 0 0);
81
- --secondary: oklch(0.97 0 0);
82
- --secondary-foreground: oklch(0.205 0 0);
83
- --muted: oklch(0.97 0 0);
84
- --muted-foreground: oklch(0.556 0 0);
85
- --accent: oklch(0.97 0 0);
86
- --accent-foreground: oklch(0.205 0 0);
87
- --destructive: oklch(0.577 0.245 27.325);
88
- --border: oklch(0.922 0 0);
89
- --input: oklch(0.922 0 0);
90
- --ring: oklch(0.708 0 0);
91
- --chart-1: oklch(0.646 0.222 41.116);
92
- --chart-2: oklch(0.6 0.118 184.704);
93
- --chart-3: oklch(0.398 0.07 227.392);
94
- --chart-4: oklch(0.828 0.189 84.429);
95
- --chart-5: oklch(0.769 0.188 70.08);
96
- --sidebar: oklch(0.985 0 0);
97
- --sidebar-foreground: oklch(0.145 0 0);
98
- --sidebar-primary: oklch(0.205 0 0);
99
- --sidebar-primary-foreground: oklch(0.985 0 0);
100
- --sidebar-accent: oklch(0.97 0 0);
101
- --sidebar-accent-foreground: oklch(0.205 0 0);
102
- --sidebar-border: oklch(0.922 0 0);
103
- --sidebar-ring: oklch(0.708 0 0);
104
- }
105
-
106
- .dark {
107
- --background: oklch(0.145 0 0);
108
- --foreground: oklch(0.985 0 0);
109
- --card: oklch(0.205 0 0);
110
- --card-foreground: oklch(0.985 0 0);
111
- --popover: oklch(0.205 0 0);
112
- --popover-foreground: oklch(0.985 0 0);
113
- --primary: oklch(0.922 0 0);
114
- --primary-foreground: oklch(0.205 0 0);
115
- --secondary: oklch(0.269 0 0);
116
- --secondary-foreground: oklch(0.985 0 0);
117
- --muted: oklch(0.269 0 0);
118
- --muted-foreground: oklch(0.708 0 0);
119
- --accent: oklch(0.269 0 0);
120
- --accent-foreground: oklch(0.985 0 0);
121
- --destructive: oklch(0.704 0.191 22.216);
122
- --border: oklch(1 0 0 / 10%);
123
- --input: oklch(1 0 0 / 15%);
124
- --ring: oklch(0.556 0 0);
125
- --chart-1: oklch(0.488 0.243 264.376);
126
- --chart-2: oklch(0.696 0.17 162.48);
127
- --chart-3: oklch(0.769 0.188 70.08);
128
- --chart-4: oklch(0.627 0.265 303.9);
129
- --chart-5: oklch(0.645 0.246 16.439);
130
- --sidebar: oklch(0.205 0 0);
131
- --sidebar-foreground: oklch(0.985 0 0);
132
- --sidebar-primary: oklch(0.488 0.243 264.376);
133
- --sidebar-primary-foreground: oklch(0.985 0 0);
134
- --sidebar-accent: oklch(0.269 0 0);
135
- --sidebar-accent-foreground: oklch(0.985 0 0);
136
- --sidebar-border: oklch(1 0 0 / 10%);
137
- --sidebar-ring: oklch(0.556 0 0);
138
- }
139
-
140
- @layer base {
141
- * {
142
- @apply border-border outline-ring/50;
143
- }
144
-
145
- body {
146
- @apply bg-background text-foreground;
147
- }
148
- }
149
-
150
- html {
151
- font-size: var(--fluid-px);
152
- }
153
-
154
- body {
155
- /* figma design 16px fontsize = 16rem fluid pixel unit */
156
- font-size: 16rem;
157
- font-family: var(--font-helvetica);
158
- }
159
-
160
-
161
- /* osmo scaling system starts here */
162
-
163
-
164
- .container {
165
- max-width: var(--size-container);
166
- }
167
-
168
- .container.medium {
169
- max-width: calc(var(--size-container) * 0.85);
170
- }
171
-
172
- .container.small {
173
- max-width: calc(var(--size-container) * 0.7);
174
- }
175
-
176
- /* Tablet */
177
- @media screen and (max-width: 991px) {
178
- :root {
179
- --size-container-ideal: 834;
180
- /* screen-size in design - no px */
181
- --size-container-min: 768px;
182
- --size-container-max: 991px;
183
- }
184
-
185
- body {
186
- background-color: red;
187
- }
188
- }
189
-
190
- /* Mobile Landscape */
191
- @media screen and (max-width: 767px) {
192
- :root {
193
- --size-container-ideal: 550;
194
- /* screen-size in design - no px */
195
- --size-container-min: 480px;
196
- --size-container-max: 767px;
197
- }
198
-
199
- body {
200
- background-color: blue;
201
- }
202
- }
203
-
204
- /* Mobile Portrait */
205
- @media screen and (max-width: 479px) {
206
- :root {
207
- --size-container-ideal: 375;
208
- /* screen-size in design - no px */
209
- --size-container-min: 320px;
210
- --size-container-max: 479px;
211
- }
212
-
213
- body {
214
- background-color: green;
215
- }
216
- }
217
-
218
- `;
3
+ import { loadTemplate } from './template-loader.js';
219
4
  export async function generateCSSVariables(projectType, projectPath, initialSetup = false) {
220
5
  let cssPath;
221
6
  if (projectType === 'nuxt') {
@@ -234,7 +19,8 @@ export async function generateCSSVariables(projectType, projectPath, initialSetu
234
19
  }
235
20
  else {
236
21
  // Overwrite the CSS file with full content
237
- writeFileSync(cssPath, CSS_VARIABLES_CONTENT.trim() + '\n', 'utf-8');
22
+ const cssContent = loadTemplate('css/tailwind.css.template');
23
+ writeFileSync(cssPath, cssContent.trim() + '\n', 'utf-8');
238
24
  }
239
25
  }
240
26
  export async function updateIndexHtml(projectPath) {
@@ -270,51 +56,6 @@ export async function createTypographyPage(projectPath, projectType) {
270
56
  const typographyPagePath = join(pagesDir, 'typography', 'index.vue');
271
57
  // Ensure directory exists
272
58
  mkdirSync(dirname(typographyPagePath), { recursive: true });
273
- const typographyPageContent = `<script setup lang="ts">
274
- </script>
275
-
276
- <template>
277
- <section class="w-full py-20 px-4 min-h-screen flex items-center justify-center bg-[#DADADA] flex-col gap-16 ">
278
- <div class="hero-container flex flex-col gap-16 leading-[1.2]">
279
- <!-- figma design 120px fontsize -->
280
- <h1 class="md:text-[120rem] text-[72rem]">Heading H1</h1>
281
- <div class="divider"></div>
282
- <!-- figma design 80px fontsize -->
283
- <h2 class="md:text-[80rem] text-[64rem]">Heading H2</h2>
284
- <div class="divider"></div>
285
- <h3 class="md:text-[40rem] text-[32rem]">Heading H3</h3>
286
- <!-- figma design 40px fontsize -->
287
- <div class="divider"></div>
288
- <h4 class="md:text-[28rem] text-[24rem]">Heading H4</h4>
289
- <!-- figma design 28px fontsize -->
290
- <div class="divider"></div>
291
- <h5 class="md:text-[20rem] text-[18rem]">Heading H5</h5>
292
- <!-- figma design 20px fontsize -->
293
- <div class="divider"></div>
294
- <!-- figma design 16px fontsize -->
295
- <p class="text-[16rem] leading-[1.4] max-md:max-w-85.75 w-full font-helvetica">
296
- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse varius enim in eros elementum
297
- tristique. Duis cursus, all links in the website, eros dolor interdum nulla, ut commodo diam libero
298
- vitae erat. Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque
299
- laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae
300
- vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit,
301
- sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt.
302
- </p>
303
- </div>
304
- </section>
305
- </template>
306
-
307
- <style scoped>
308
- .hero-container {
309
- max-width: calc(1184em / 16);
310
- }
311
-
312
- .divider {
313
- background-color: #969696;
314
- height: 1px;
315
- width: 100%;
316
- }
317
- </style>
318
- `;
59
+ const typographyPageContent = loadTemplate('nuxt/pages/typography/index.vue.template');
319
60
  writeFileSync(typographyPagePath, typographyPageContent, 'utf-8');
320
61
  }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Load a template file and return its content
3
+ * @param templatePath - Path to template file relative to template_code directory
4
+ * @returns Template content as string
5
+ */
6
+ export declare function loadTemplate(templatePath: string): string;
7
+ /**
8
+ * Load a template and replace placeholders
9
+ * @param templatePath - Path to template file relative to template_code directory
10
+ * @param replacements - Object with placeholder keys and replacement values
11
+ * @returns Template content with replacements applied
12
+ */
13
+ export declare function loadTemplateWithReplacements(templatePath: string, replacements: Record<string, string>): string;
@@ -0,0 +1,52 @@
1
+ import { readFileSync, existsSync } from 'fs';
2
+ import { join, dirname } from 'path';
3
+ import { fileURLToPath } from 'url';
4
+ const __filename = fileURLToPath(import.meta.url);
5
+ const __dirname = dirname(__filename);
6
+ /**
7
+ * Get the template directory path
8
+ * When compiled: dist/utils/template-loader.js -> dist/template_code
9
+ * When in source: source/utils/template-loader.ts -> source/template_code
10
+ */
11
+ function getTemplateDir() {
12
+ let templateDir = join(__dirname, '..', 'template_code');
13
+ // Check if template directory exists, if not try source location
14
+ if (!existsSync(templateDir)) {
15
+ // Try from source location (for development/testing)
16
+ const sourceTemplateDir = join(__dirname, '..', '..', 'source', 'template_code');
17
+ if (existsSync(sourceTemplateDir)) {
18
+ return sourceTemplateDir;
19
+ }
20
+ }
21
+ return templateDir;
22
+ }
23
+ /**
24
+ * Load a template file and return its content
25
+ * @param templatePath - Path to template file relative to template_code directory
26
+ * @returns Template content as string
27
+ */
28
+ export function loadTemplate(templatePath) {
29
+ const templateDir = getTemplateDir();
30
+ const fullPath = join(templateDir, templatePath);
31
+ try {
32
+ return readFileSync(fullPath, 'utf-8');
33
+ }
34
+ catch (error) {
35
+ throw new Error(`Template not found: ${fullPath}. Error: ${error instanceof Error ? error.message : 'Unknown error'}`);
36
+ }
37
+ }
38
+ /**
39
+ * Load a template and replace placeholders
40
+ * @param templatePath - Path to template file relative to template_code directory
41
+ * @param replacements - Object with placeholder keys and replacement values
42
+ * @returns Template content with replacements applied
43
+ */
44
+ export function loadTemplateWithReplacements(templatePath, replacements) {
45
+ let content = loadTemplate(templatePath);
46
+ // Replace all placeholders in format {{KEY}}
47
+ for (const [key, value] of Object.entries(replacements)) {
48
+ const placeholder = new RegExp(`\\{\\{${key}\\}\\}`, 'g');
49
+ content = content.replace(placeholder, value);
50
+ }
51
+ return content;
52
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pni",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "CLI tool for creating Nuxt/Vue projects with Three.js and CSS variables setup",
5
5
  "license": "MIT",
6
6
  "bin": {