pwa-sv 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1199 @@
1
+ import { execa } from 'execa';
2
+ import fs from 'fs-extra';
3
+ import { existsSync } from 'fs';
4
+ import path from 'path';
5
+ import { Either } from 'effect';
6
+ /**
7
+ * Creates a new SvelteKit project using pnpm create
8
+ * @param config Configuration object containing project details
9
+ */
10
+ export const createSvelteProject = async (config) => {
11
+ console.log(`Creating SvelteKit project: ${config.projectName}`);
12
+ // Check if directory already exists
13
+ if (existsSync(config.projectName)) {
14
+ console.log(`Directory ${config.projectName} already exists. Removing...`);
15
+ fs.removeSync(config.projectName);
16
+ }
17
+ // Create the project using npx sv create followed by sv add for additional functionality
18
+ try {
19
+ // Create a basic SvelteKit project with TypeScript
20
+ await execa('npx', [
21
+ 'sv',
22
+ 'create',
23
+ config.projectName,
24
+ '--template',
25
+ 'minimal',
26
+ '--types',
27
+ 'ts',
28
+ '--install',
29
+ 'pnpm',
30
+ '--no-add-ons' // Skip interactive add-ons prompt during creation for automation
31
+ ], {
32
+ stdio: 'inherit'
33
+ });
34
+ // Now add the necessary functionality using sv add with explicit package manager option
35
+ // Add ESLint for code quality
36
+ await execa('npx', [
37
+ 'sv',
38
+ 'add',
39
+ 'eslint',
40
+ '--install',
41
+ 'pnpm'
42
+ ], {
43
+ cwd: config.projectName, // Run in the project directory
44
+ stdio: 'inherit'
45
+ });
46
+ // Add Vitest for testing (automatically select both unit and component testing)
47
+ const vitestResult = await execa('npx', [
48
+ 'sv',
49
+ 'add',
50
+ 'vitest',
51
+ '--install',
52
+ 'pnpm'
53
+ ], {
54
+ cwd: config.projectName, // Run in the project directory
55
+ input: '\n\n' // Provide answers to interactive prompts (press Enter for defaults)
56
+ });
57
+ // Add Playwright for end-to-end testing (only if requested)
58
+ if (config.usePlaywright) {
59
+ await execa('npx', [
60
+ 'sv',
61
+ 'add',
62
+ 'playwright',
63
+ '--install',
64
+ 'pnpm'
65
+ ], {
66
+ cwd: config.projectName, // Run in the project directory
67
+ stdio: 'inherit'
68
+ });
69
+ }
70
+ console.log(`SvelteKit project ${config.projectName} created successfully`);
71
+ return Either.right(undefined);
72
+ }
73
+ catch (error) {
74
+ console.error('Failed to create SvelteKit project:', error);
75
+ return Either.left(new Error(`Failed to create SvelteKit project: ${error.message}`));
76
+ }
77
+ };
78
+ /**
79
+ * Handles file system operations for the project
80
+ * @param projectName Name of the project
81
+ * @param content Content to write to a file
82
+ * @param filePath Path of the file to write
83
+ */
84
+ export const writeFile = async (projectName, content, filePath) => {
85
+ try {
86
+ const fullPath = path.join(projectName, filePath);
87
+ await fs.outputFile(fullPath, content);
88
+ console.log(`File written: ${fullPath}`);
89
+ return Either.right(undefined);
90
+ }
91
+ catch (error) {
92
+ console.error(`Failed to write file ${filePath}:`, error);
93
+ return Either.left(new Error(`Failed to write file: ${error.message}`));
94
+ }
95
+ };
96
+ /**
97
+ * Reads a file from the project
98
+ * @param projectName Name of the project
99
+ * @param filePath Path of the file to read
100
+ * @returns Content of the file
101
+ */
102
+ export const readFile = async (projectName, filePath) => {
103
+ try {
104
+ const fullPath = path.join(projectName, filePath);
105
+ const content = await fs.readFile(fullPath, 'utf8');
106
+ return Either.right(content);
107
+ }
108
+ catch (error) {
109
+ console.error(`Failed to read file ${filePath}:`, error);
110
+ return Either.left(new Error(`Failed to read file: ${error.message}`));
111
+ }
112
+ };
113
+ /**
114
+ * Creates PWA configuration file (vite.config.ts) for the SvelteKit project
115
+ * @param projectName Name of the project
116
+ */
117
+ export const createPWAConfig = async (projectName) => {
118
+ console.log(`Creating PWA configuration file for project: ${projectName}`);
119
+ // Define content for vite.config.ts with PWA plugin configuration
120
+ const viteConfigContent = `import { sveltekit } from '@sveltejs/kit/vite';
121
+ import { defineConfig } from 'vite';
122
+ import { readFileSync } from 'fs';
123
+ import { fileURLToPath } from 'url';
124
+ import { dirname, resolve } from 'path';
125
+
126
+ // Read package.json to get app metadata
127
+ const file = fileURLToPath(import.meta.url);
128
+ const currentDir = dirname(file);
129
+ const packageJsonPath = resolve(currentDir, 'package.json');
130
+ let appInfo = { name: 'My SvelteKit PWA', shortName: 'SveltePWA', description: 'A SvelteKit Progressive Web App' };
131
+
132
+ try {
133
+ const packageJsonContent = readFileSync(packageJsonPath, 'utf8');
134
+ const packageJson = JSON.parse(packageJsonContent);
135
+ appInfo = {
136
+ name: packageJson.name || 'My SvelteKit PWA',
137
+ shortName: packageJson.shortName || packageJson.name || 'SveltePWA',
138
+ description: packageJson.description || 'A SvelteKit Progressive Web App'
139
+ };
140
+ } catch (error) {
141
+ console.warn('Could not read package.json, using default app info');
142
+ }
143
+
144
+ // Define icons configuration for different device sizes
145
+ const iconsConfig = {
146
+ src: 'src/lib/assets/pwa-icon.png', // Default PWA icon path
147
+ sizes: [64, 192, 512],
148
+ destination: 'static/',
149
+ injection: true
150
+ };
151
+
152
+ // Vite configuration with PWA plugin
153
+ export default defineConfig({
154
+ plugins: [
155
+ sveltekit(),
156
+ // PWA Configuration
157
+ process.env.NODE_ENV === 'production'
158
+ ? ((): any => {
159
+ const { VitePWA } = require('vite-plugin-pwa');
160
+ return VitePWA({
161
+ strategies: 'injectManifest', // Use injectManifest strategy for more control
162
+ srcDir: 'src',
163
+ filename: 'sw.ts', // Service worker file
164
+ injectRegister: 'auto', // Auto register service worker
165
+ workbox: {
166
+ globPatterns: ['**/*.{js,css,html,ico,png,svg,woff2}'], // Files to cache
167
+ runtimeCaching: [
168
+ // Cache API calls with network-first strategy
169
+ {
170
+ urlPattern: /^https:\\/\\/.*/i,
171
+ handler: 'NetworkFirst',
172
+ options: {
173
+ cacheName: 'api-cache',
174
+ expiration: {
175
+ maxEntries: 20,
176
+ maxAgeSeconds: 30 * 24 * 60 * 60, // 30 days
177
+ },
178
+ },
179
+ },
180
+ // Cache static assets with cache-first strategy
181
+ {
182
+ urlPattern: /^https:\\/\\/(?:fonts|images)\\.*/i,
183
+ handler: 'CacheFirst',
184
+ options: {
185
+ cacheName: 'static-assets-cache',
186
+ expiration: {
187
+ maxEntries: 100,
188
+ maxAgeSeconds: 30 * 24 * 60 * 60, // 30 days
189
+ },
190
+ },
191
+ }
192
+ ]
193
+ },
194
+ manifest: {
195
+ name: appInfo.name,
196
+ short_name: appInfo.shortName,
197
+ description: appInfo.description,
198
+ theme_color: '#42b549',
199
+ background_color: '#ffffff',
200
+ display: 'standalone',
201
+ orientation: 'portrait',
202
+ scope: '/',
203
+ start_url: '/',
204
+ icons: [
205
+ {
206
+ src: 'pwa-64x64.png',
207
+ sizes: '64x64',
208
+ type: 'image/png'
209
+ },
210
+ {
211
+ src: 'pwa-192x192.png',
212
+ sizes: '192x192',
213
+ type: 'image/png'
214
+ },
215
+ {
216
+ src: 'pwa-512x512.png',
217
+ sizes: '512x512',
218
+ type: 'image/png'
219
+ },
220
+ {
221
+ src: 'maskable-icon-512x512.png',
222
+ sizes: '512x512',
223
+ type: 'image/png',
224
+ purpose: 'maskable'
225
+ }
226
+ ]
227
+ }
228
+ });
229
+ })()
230
+ : null // Only include PWA plugin in production builds
231
+ ].filter(Boolean), // Filter out null plugins
232
+
233
+ // Additional configuration for PWA
234
+ build: {
235
+ sourcemap: true // Generate sourcemaps
236
+ }
237
+ });
238
+ `;
239
+ try {
240
+ // Write vite.config.ts file
241
+ const configResult = await writeFile(projectName, viteConfigContent, 'vite.config.ts');
242
+ if (Either.isLeft(configResult)) {
243
+ return configResult;
244
+ }
245
+ console.log('PWA configuration file created successfully');
246
+ return Either.right(undefined);
247
+ }
248
+ catch (error) {
249
+ console.error('Failed to create PWA configuration file:', error);
250
+ return Either.left(new Error(`Failed to create PWA configuration file: ${error.message}`));
251
+ }
252
+ };
253
+ /**
254
+ * Updates package.json with PWA-specific scripts
255
+ * @param projectName Name of the project
256
+ */
257
+ export const updatePackageJsonWithPWAScripts = async (projectName) => {
258
+ console.log(`Updating package.json with PWA scripts for project: ${projectName}`);
259
+ try {
260
+ const packageJsonPath = path.join(projectName, 'package.json');
261
+ // Read the existing package.json file
262
+ if (!await fs.pathExists(packageJsonPath)) {
263
+ return Either.left(new Error('package.json file not found'));
264
+ }
265
+ const packageJsonContent = await fs.readFile(packageJsonPath, 'utf8');
266
+ const packageJson = JSON.parse(packageJsonContent);
267
+ // Define PWA-specific scripts to add (only unique ones, avoiding duplicates of standard scripts)
268
+ const pwaScripts = {
269
+ 'build-pwa-assets': 'pwa-asset-generator' // Uses pwa-assets.config.ts for Android and web assets only
270
+ };
271
+ // Merge the new scripts with existing scripts
272
+ packageJson.scripts = {
273
+ ...packageJson.scripts, // Keep existing scripts
274
+ ...pwaScripts // Add PWA scripts
275
+ };
276
+ // Add pwa-asset-generator to devDependencies if it doesn't exist yet
277
+ if (!packageJson.devDependencies) {
278
+ packageJson.devDependencies = {};
279
+ }
280
+ if (!packageJson.devDependencies['pwa-asset-generator']) {
281
+ packageJson.devDependencies['pwa-asset-generator'] = '^6.0.0';
282
+ }
283
+ // Write the updated package.json back to the file
284
+ await fs.writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2));
285
+ console.log('PWA scripts added to package.json successfully');
286
+ return Either.right(undefined);
287
+ }
288
+ catch (error) {
289
+ console.error('Failed to update package.json with PWA scripts:', error);
290
+ return Either.left(new Error(`Failed to update package.json with PWA scripts: ${error.message}`));
291
+ }
292
+ };
293
+ /**
294
+ * Creates pwa-asset-generator configuration file to generate assets for Android and web only (excluding iOS)
295
+ * @param projectName Name of the project
296
+ */
297
+ export const createPWAAssetConfig = async (projectName) => {
298
+ console.log(`Creating pwa-asset-generator configuration for project: ${projectName}`);
299
+ const pwaAssetsConfigContent = `import { defineConfig } from '@vite-pwa/assets-generator/config'
300
+
301
+ export default defineConfig({
302
+ headLinkOptions: { preset: '2023' },
303
+ preset: {
304
+ transparent: {
305
+ sizes: [64, 192, 512],
306
+ favicons: [[48, 'favicon.ico']]
307
+ },
308
+ maskable: {
309
+ sizes: [512]
310
+ }
311
+ // NOTE: Apple property is intentionally omitted to exclude iOS assets
312
+ },
313
+ images: ['src/lib/assets/pwa-icon.png']
314
+ })
315
+ `;
316
+ try {
317
+ // Write pwa-assets.config.ts file
318
+ const configResult = await writeFile(projectName, pwaAssetsConfigContent, 'pwa-assets.config.ts');
319
+ if (Either.isLeft(configResult)) {
320
+ return configResult;
321
+ }
322
+ console.log('pwa-asset-generator configuration file created successfully');
323
+ return Either.right(undefined);
324
+ }
325
+ catch (error) {
326
+ console.error('Failed to create pwa-asset-generator configuration file:', error);
327
+ return Either.left(new Error(`Failed to create pwa-asset-generator configuration file: ${error.message}`));
328
+ }
329
+ };
330
+ /**
331
+ * Validates the project structure to ensure it matches expected architecture
332
+ * @param projectName Name of the project to validate
333
+ */
334
+ export const validateProjectStructure = async (projectName) => {
335
+ console.log(`Validating project structure for: ${projectName}`);
336
+ try {
337
+ // Define the expected files and directories that should exist in a properly created PWA project
338
+ const expectedFiles = [
339
+ 'package.json',
340
+ 'README.md',
341
+ 'vite.config.ts', // Added by createPWAConfig
342
+ 'svelte.config.js',
343
+ '.gitignore',
344
+ 'static/', // Static assets directory
345
+ 'src/',
346
+ 'src/app.html',
347
+ 'src/routes/',
348
+ 'src/lib/',
349
+ 'tsconfig.json'
350
+ ];
351
+ // Check for the existence of each expected file/directory
352
+ for (const expectedFile of expectedFiles) {
353
+ const fullPath = path.join(projectName, expectedFile);
354
+ const exists = await fs.pathExists(fullPath);
355
+ if (!exists) {
356
+ console.error(`Project structure validation failed: Missing expected file/directory: ${expectedFile}`);
357
+ return Either.left(new Error(`Project structure validation failed: Missing expected file/directory: ${expectedFile}`));
358
+ }
359
+ }
360
+ console.log('Project structure validation passed successfully');
361
+ return Either.right(undefined);
362
+ }
363
+ catch (error) {
364
+ console.error('Project structure validation failed with error:', error);
365
+ return Either.left(new Error(`Project structure validation failed: ${error.message}`));
366
+ }
367
+ };
368
+ /**
369
+ * Creates Skeleton UI configuration files and setup
370
+ * @param projectName Name of the project to configure
371
+ */
372
+ export const createSkeletonUIConfig = async (projectName) => {
373
+ console.log(`Creating Skeleton UI configuration for: ${projectName}`);
374
+ try {
375
+ // Create Tailwind CSS configuration file
376
+ const tailwindConfigContent = `import { Config } from 'tailwindcss';
377
+ import { skeleton } from '@skeletonlabs/skeleton/tailwind/skeleton.cjs';
378
+
379
+ export default {
380
+ content: [
381
+ './src/**/*.{html,js,svelte,ts}',
382
+ './node_modules/@skeletonlabs/skeleton/**/*.{html,js,svelte,ts}'
383
+ ],
384
+ theme: {
385
+ extend: {},
386
+ },
387
+ plugins: [
388
+ skeleton({
389
+ themes: {
390
+ preset: [
391
+ {
392
+ name: 'skeleton',
393
+ enhancements: true,
394
+ }
395
+ ]
396
+ }
397
+ })
398
+ ]
399
+ } satisfies Config;
400
+ `;
401
+ // Write tailwind.config.cjs file
402
+ const tailwindResult = await writeFile(projectName, tailwindConfigContent, 'tailwind.config.cjs');
403
+ if (Either.isLeft(tailwindResult)) {
404
+ return tailwindResult;
405
+ }
406
+ // Create svelte.config.js with Skeleton UI integration (if not exists or update existing)
407
+ const svelteConfigPath = path.join(projectName, 'svelte.config.js');
408
+ let svelteConfigContent = `import { vitePreprocess } from '@sveltejs/kit/vite';
409
+ import adapter from '@sveltejs/adapter-auto';
410
+ import { resolve } from 'path';
411
+
412
+ /** @type {import('@sveltejs/kit').Config} */
413
+ const config = {
414
+ preprocess: [vitePreprocess({})],
415
+ kit: {
416
+ adapter: adapter(),
417
+ alias: {
418
+ $lib: 'src/lib',
419
+ $components: 'src/lib/components' // Common alias for components
420
+ }
421
+ }
422
+ };
423
+
424
+ export default config;
425
+ `;
426
+ // Write svelte.config.js file
427
+ const svelteConfigResult = await writeFile(projectName, svelteConfigContent, 'svelte.config.js');
428
+ if (Either.isLeft(svelteConfigResult)) {
429
+ return svelteConfigResult;
430
+ }
431
+ // Create app.html with Tailwind CSS integration
432
+ const appHtmlPath = path.join(projectName, 'src', 'app.html');
433
+ if (await fs.pathExists(appHtmlPath)) {
434
+ // Read existing app.html and add Tailwind CSS classes to body tag
435
+ const existingAppHtml = await fs.readFile(appHtmlPath, 'utf8');
436
+ let updatedAppHtml = existingAppHtml;
437
+ if (existingAppHtml.includes('<body')) {
438
+ // Replace the body tag with Tailwind classes
439
+ updatedAppHtml = existingAppHtml.replace(/<body([^>]*)>/i, '<body$1 class="bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-100">');
440
+ }
441
+ else {
442
+ // If body tag doesn't exist, add it with Tailwind classes and data-theme for skeleton
443
+ updatedAppHtml = `<!DOCTYPE html>
444
+ <html lang="en" data-theme="cerberus">
445
+ <head>
446
+ <meta charset="utf-8" />
447
+ <link rel="icon" href="%sveltekit.assets%/favicon.png" />
448
+ <meta name="viewport" content="width=device-width" />
449
+ %sveltekit.head%
450
+ </head>
451
+ <body class="bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-100">
452
+ %sveltekit.body%
453
+ </body>
454
+ </html>`;
455
+ }
456
+ // Add data-theme to html tag if not present for skeleton
457
+ if (existingAppHtml.includes('<html') && !existingAppHtml.match(/<html[^>]*data-theme\b/i)) {
458
+ updatedAppHtml = updatedAppHtml.replace(/<html([^>]*)>/i, '<html$1 data-theme="cerberus">');
459
+ }
460
+ await fs.writeFile(appHtmlPath, updatedAppHtml);
461
+ }
462
+ // Create base CSS file with Tailwind and Skeleton imports
463
+ const cssContent = `@tailwind base;
464
+ @tailwind components;
465
+ @tailwind utilities;
466
+
467
+ @import '@skeletonlabs/skeleton';
468
+ @import '@skeletonlabs/skeleton-svelte';
469
+ @import '@skeletonlabs/skeleton/themes/cerberus.css';
470
+
471
+ /* Your custom styles go here */
472
+ `;
473
+ const cssResult = await writeFile(projectName, cssContent, 'src/app.postcss');
474
+ if (Either.isLeft(cssResult)) {
475
+ return cssResult;
476
+ }
477
+ // Create a sample component that demonstrates Skeleton UI usage
478
+ const sampleComponentContent = `<script>
479
+ import { Button } from '@skeletonlabs/skeleton';
480
+ import { Popup } from '@skeletonlabs/skeleton';
481
+ import { Modal } from '@skeletonlabs/skeleton';
482
+ </script>
483
+
484
+ <div>
485
+ <h1 class="text-2xl font-bold mb-4">Welcome to Skeleton UI!</h1>
486
+ <Button variant="primary">Click me</Button>
487
+ <p class="mt-4">This project includes Skeleton UI components.</p>
488
+ </div>
489
+ `;
490
+ // Write sample component file
491
+ const componentResult = await writeFile(projectName, sampleComponentContent, 'src/lib/components/SkeletonExample.svelte');
492
+ if (Either.isLeft(componentResult)) {
493
+ return componentResult;
494
+ }
495
+ console.log('Skeleton UI configuration created successfully');
496
+ return Either.right(undefined);
497
+ }
498
+ catch (error) {
499
+ console.error('Failed to create Skeleton UI configuration:', error);
500
+ return Either.left(new Error(`Failed to create Skeleton UI configuration: ${error.message}`));
501
+ }
502
+ };
503
+ /**
504
+ * Creates internationalization (i18n) configuration files and setup
505
+ * @param projectName Name of the project to configure
506
+ */
507
+ export const createI18nConfig = async (projectName) => {
508
+ console.log(`Creating internationalization configuration for: ${projectName}`);
509
+ try {
510
+ // Create i18n configuration file
511
+ const i18nConfigContent = `import { browser, locales, negotiate } from '$app/env';
512
+ import { init, register } from 'svelte-i18n';
513
+
514
+ // Initialize internationalization
515
+ export const setupI18n = () => {
516
+ // Register the locales directory
517
+ register('en', () => import('./locales/en.json'));
518
+ register('es', () => import('./locales/es.json'));
519
+ register('de', () => import('./locales/de.json'));
520
+ register('fr', () => import('./locales/fr.json'));
521
+
522
+ // Initialize the i18n system
523
+ init({
524
+ fallbackLocale: 'en',
525
+ initialLocale: browser ? undefined : 'en',
526
+ warnOnMissing: true
527
+ });
528
+ };
529
+
530
+ // Export common i18n functions
531
+ export { default as t } from 'svelte-i18n';
532
+ `;
533
+ // Write i18n configuration file
534
+ const i18nConfigResult = await writeFile(projectName, i18nConfigContent, 'src/lib/i18n/config.js');
535
+ if (Either.isLeft(i18nConfigResult)) {
536
+ return i18nConfigResult;
537
+ }
538
+ // Create directory for locale files if it doesn't exist
539
+ const localesDirPath = path.join(projectName, 'src', 'lib', 'locales');
540
+ await fs.ensureDir(localesDirPath);
541
+ // Create default English locale file
542
+ const enLocaleContent = `{
543
+ "welcome": "Welcome",
544
+ "hello": "Hello",
545
+ "goodbye": "Goodbye",
546
+ "app_title": "My PWA App",
547
+ "nav_home": "Home",
548
+ "nav_about": "About",
549
+ "nav_contact": "Contact",
550
+ "button_submit": "Submit",
551
+ "button_cancel": "Cancel",
552
+ "error_required": "This field is required",
553
+ "error_invalid": "Invalid input",
554
+ "success_message": "Operation completed successfully"
555
+ }`;
556
+ // Write English locale file
557
+ const enLocaleResult = await writeFile(projectName, enLocaleContent, 'src/lib/locales/en.json');
558
+ if (Either.isLeft(enLocaleResult)) {
559
+ return enLocaleResult;
560
+ }
561
+ // Create sample Spanish locale file
562
+ const esLocaleContent = `{
563
+ "welcome": "Bienvenido",
564
+ "hello": "Hola",
565
+ "goodbye": "Adios",
566
+ "app_title": "Mi aplicacion PWA",
567
+ "nav_home": "Inicio",
568
+ "nav_about": "Acerca de",
569
+ "nav_contact": "Contacto",
570
+ "button_submit": "Enviar",
571
+ "button_cancel": "Cancelar",
572
+ "error_required": "Este campo es obligatorio",
573
+ "error_invalid": "Entrada invalida",
574
+ "success_message": "Operacion completada exitosamente"
575
+ }`;
576
+ // Write Spanish locale file
577
+ const esLocaleResult = await writeFile(projectName, esLocaleContent, 'src/lib/locales/es.json');
578
+ if (Either.isLeft(esLocaleResult)) {
579
+ return esLocaleResult;
580
+ }
581
+ // Create a sample i18n-enabled component
582
+ const sampleI18nComponentContent = `<script>
583
+ import { t } from '$lib/i18n/config.js';
584
+ import { onMount } from 'svelte';
585
+ import { locale } from 'svelte-i18n';
586
+
587
+ // Set default language
588
+ onMount(async () => {
589
+ locale.set('en');
590
+ });
591
+ </script>
592
+
593
+ <div>
594
+ <h1>{t('app_title')}</h1>
595
+ <p>{t('welcome')}</p>
596
+ <button on:click={() => locale.set('es')}>Spanish</button>
597
+ <button on:click={() => locale.set('en')}>English</button>
598
+ </div>
599
+ `;
600
+ // Write sample component file
601
+ const componentResult = await writeFile(projectName, sampleI18nComponentContent, 'src/lib/components/I18nExample.svelte');
602
+ if (Either.isLeft(componentResult)) {
603
+ return componentResult;
604
+ }
605
+ // Update app.html to include locale attribute
606
+ const appHtmlPath = path.join(projectName, 'src', 'app.html');
607
+ if (await fs.pathExists(appHtmlPath)) {
608
+ const existingAppHtml = await fs.readFile(appHtmlPath, 'utf8');
609
+ let updatedAppHtml = existingAppHtml;
610
+ // Add lang attribute to html tag if not present
611
+ if (existingAppHtml.includes('<html')) {
612
+ // Check if lang attribute already exists to avoid duplicates
613
+ if (!existingAppHtml.match(/<html[^>]*lang\b/i)) {
614
+ updatedAppHtml = existingAppHtml.replace(/<html([^>]*)>/i, '<html$1 lang="en"');
615
+ }
616
+ }
617
+ else {
618
+ // If html tag doesn't exist, add proper structure with lang attribute
619
+ updatedAppHtml = `<!DOCTYPE html>
620
+ <html lang="en">
621
+ <head>
622
+ <meta charset="utf-8" />
623
+ <link rel="icon" href="%sveltekit.assets%/favicon.png" />
624
+ <meta name="viewport" content="width=device-width" />
625
+ %sveltekit.head%
626
+ </head>
627
+ <body>
628
+ %sveltekit.body%
629
+ </body>
630
+ </html>`;
631
+ }
632
+ await fs.writeFile(appHtmlPath, updatedAppHtml);
633
+ }
634
+ // Update package.json to add i18n scripts if they don't exist
635
+ const packageJsonPath = path.join(projectName, 'package.json');
636
+ if (await fs.pathExists(packageJsonPath)) {
637
+ const packageJsonContent = await fs.readFile(packageJsonPath, 'utf8');
638
+ let packageJson;
639
+ try {
640
+ packageJson = JSON.parse(packageJsonContent);
641
+ }
642
+ catch (e) {
643
+ // If package.json is invalid, skip updating it
644
+ console.warn('Could not parse package.json, skipping i18n scripts update');
645
+ }
646
+ if (packageJson && packageJson.scripts) {
647
+ // Add i18n-related scripts
648
+ packageJson.scripts = {
649
+ ...packageJson.scripts,
650
+ 'i18n:extract': 'i18n-extract report src/**/*.svelte src/**/*.js src/**/*.ts',
651
+ 'i18n:check': 'i18n-extract report --fail-on-translation-keys-missing-from-source src/**/*.svelte src/**/*.js src/**/*.ts'
652
+ };
653
+ await fs.writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2));
654
+ }
655
+ }
656
+ console.log('Internationalization configuration created successfully');
657
+ return Either.right(undefined);
658
+ }
659
+ catch (error) {
660
+ console.error('Failed to create internationalization configuration:', error);
661
+ return Either.left(new Error(`Failed to create internationalization configuration: ${error.message}`));
662
+ }
663
+ };
664
+ /**
665
+ * Creates AGENTS.md file with functional programming guidelines and empty docs folder
666
+ * @param projectName Name of the project
667
+ */
668
+ export const createAgentsFileAndDocsFolder = async (projectName) => {
669
+ console.log(`Creating AGENTS.md file and docs folder for project: ${projectName}`);
670
+ // Define content for AGENTS.md with functional programming guidelines
671
+ const agentsContent = `# Functional Programming Guidelines for AI Agents
672
+
673
+ This document provides explicit instructions for AI agents to follow functional programming paradigms when generating or modifying code. Adhere strictly to these principles.
674
+
675
+ ## Core Functional Programming Principles
676
+
677
+ ### Immutability & Pure Functions
678
+ - **Prefer immutability** - Use immutable data patterns wherever possible
679
+ - **Pure functions** - Functions should not produce side effects and should return the same output for same inputs
680
+ - **Immutable data operations** - Use non-mutating methods for objects and arrays
681
+
682
+ ### Error Handling with Effect Types
683
+ - **NO exceptions** - Use \`Effect<T, E>\` types from \`effect\` library instead of throwing errors
684
+ - **Explicit error handling** - All possible errors must be represented in return types
685
+ - **Predictable control flow** - Use \`map\`, \`flatMap\`, \`match\` for error handling chains
686
+
687
+ ### TypeScript-First Development
688
+ - **Proper TypeScript syntax only** - NO JSDoc type annotations allowed
689
+ - **Explicit type annotations** - All functions, parameters, and returns must have TypeScript types
690
+ - **Generic types** - Use TypeScript generics for reusable, type-safe code
691
+
692
+ ## Implementation Rules
693
+
694
+ ### Function Design
695
+ \`\`\`typescript
696
+ // ✅ CORRECT - Pure function with Effect type
697
+ import { Effect, pipe } from "effect";
698
+
699
+ const processUser = (user: User): Effect.Effect<ProcessedUser, ValidationError> => {
700
+ return pipe(
701
+ Effect.succeed(user),
702
+ Effect.andThen(validateUser),
703
+ Effect.map(transformUser),
704
+ Effect.map(enrichUserData)
705
+ );
706
+ }
707
+
708
+ // ❌ INCORRECT - Throws exceptions, mutable parameters
709
+ function processUser(user: User): ProcessedUser {
710
+ if (!user.valid) throw new Error("Invalid user");
711
+ user.processed = true; // Mutation!
712
+ return user;
713
+ }
714
+ \`\`\`
715
+
716
+ ### State Management in Svelte Components
717
+ \`\`\`typescript
718
+ // ✅ CORRECT - Svelte reactive declarations with proper typing
719
+ <script lang="ts">
720
+ // Svelte component state - let is required for reactivity
721
+ let count = $state<number>(0);
722
+ let users = $state<ReadonlyArray<User>>([]);
723
+
724
+ // Immutable updates to Svelte state
725
+ const addUser = (newUser: User): void => {
726
+ users = [...users, newUser]; // ✅ Correct - immutable update
727
+ };
728
+
729
+ const updateUser = (id: string, updates: Partial<User>): void => {
730
+ users = users.map(user =>
731
+ user.id === id ? { ...user, ...updates } : user
732
+ );
733
+ };
734
+ </script>
735
+
736
+ // ❌ INCORRECT - Direct mutation of Svelte state
737
+ <script lang="ts">
738
+ let users = $state<User[]>([]);
739
+
740
+ const addUser = (newUser: User): void => {
741
+ users.push(newUser); // ❌ Mutation - avoid this pattern
742
+ };
743
+ </script>
744
+ \`\`\`
745
+
746
+ ### Pure TypeScript Modules (Non-Svelte)
747
+ \`\`\`typescript
748
+ // ✅ CORRECT - Pure functional module with const
749
+ import { Effect } from "effect";
750
+
751
+ export const processUsers = (users: ReadonlyArray<User>): Effect.Effect<ReadonlyArray<ProcessedUser>, Error> => {
752
+ return Effect.gen(function*() {
753
+ const validUsers = yield* Effect.succeed(users.filter(isValidUser));
754
+ return yield* Effect.forEach(validUsers, processUser);
755
+ });
756
+ };
757
+
758
+ // ❌ INCORRECT - Mutable variables in pure TypeScript
759
+ export let processedUsers: User[] = []; // ❌ Avoid module-level mutable variables
760
+
761
+ export function addUser(user: User): void {
762
+ processedUsers.push(user); // ❌ Mutation
763
+ }
764
+ \`\`\`
765
+
766
+ ### Error Handling Patterns
767
+ \`\`\`typescript
768
+ // ✅ CORRECT - Using effect
769
+ import { Effect, pipe } from "effect";
770
+
771
+ const fetchUser = (id: string): Effect.Effect<User, NetworkError | NotFoundError> => {
772
+ return pipe(
773
+ Effect.try({
774
+ try: () => apiCall(id),
775
+ catch: (error) => error as NetworkError
776
+ }),
777
+ Effect.andThen(validateResponse),
778
+ Effect.map(parseUserData)
779
+ );
780
+ }
781
+
782
+ // Usage with proper handling
783
+ const result = pipe(
784
+ fetchUser('123'),
785
+ Effect.match({
786
+ onFailure: (error) => { /* handle error */ },
787
+ onSuccess: (user) => { /* handle success */ }
788
+ })
789
+ );
790
+ \`\`\`
791
+
792
+ ## Svelte 5 Specific Guidelines
793
+
794
+ ### State Management
795
+ - Use \`let\` with \`\\$state<T>()\` for Svelte component reactive state
796
+ - Use immutable patterns when updating Svelte state (spread operator, map, filter)
797
+ - Prefer \`readonly\` types for state that shouldn't be mutated externally
798
+ - Use \`\\$effect()\` for side effects with proper TypeScript typing
799
+
800
+ ### Component Patterns
801
+ \`\`\`typescript
802
+ // ✅ CORRECT - Functional Svelte component with immutable patterns
803
+ <script lang="ts">
804
+ // Svelte state declarations use let + $state
805
+ let count = $state<number>(0);
806
+ let items = $state<ReadonlyArray<string>>([]);
807
+
808
+ // Pure functions for business logic
809
+ const increment = (): void => {
810
+ count += 1; // ✅ This is Svelte reactivity, not variable reassignment
811
+ };
812
+
813
+ const addItem = (item: string): void => {
814
+ items = [...items, item]; // ✅ Immutable update
815
+ };
816
+
817
+ const removeItem = (index: number): void => {
818
+ items = items.filter((_, i) => i !== index); // ✅ Immutable update
819
+ };
820
+
821
+ $effect(() => {
822
+ // Side effects here
823
+ console.log(\`Count is: \${count}\`);
824
+ });
825
+ </script>
826
+ \`\`\`
827
+
828
+ ## Mandatory Checks Before Code Generation
829
+
830
+ 1. **Immutability Check**: No direct mutation of objects/arrays (use spread, map, filter instead)
831
+ 2. **Error Handling Check**: All possible errors must return Effect types, no throw statements
832
+ 3. **TypeScript Check**: Proper TypeScript syntax, no JSDoc types, explicit return types
833
+ 4. **Purity Check**: No side effects in pure functions, all dependencies explicit
834
+ 5. **Svelte Context Check**: Use \`let\` only in Svelte components for reactive state, use \`const\` elsewhere
835
+
836
+ ## Prohibited Patterns
837
+
838
+ - ❌ \`throw new Error()\` (use Effect types instead)
839
+ - ❌ Direct mutation: \`array.push()\`, \`array.splice()\`, \`object.property = value\`
840
+ - ❌ JSDoc \`@type\`, \`@param\`, \`@return\` annotations (use TypeScript syntax)
841
+ - ❌ \`let\` in pure TypeScript modules (use \`const\`)
842
+ - ❌ Class inheritance (\`extends\`) - prefer composition
843
+
844
+ ## Required Patterns
845
+
846
+ - ✅ \`let\` + \`\\$state<T>()\` for Svelte component reactive state
847
+ - ✅ \`const\` for all variable declarations outside Svelte components
848
+ - ✅ \`Effect<T, E>\` from effect for error handling
849
+ - ✅ \`map\`, \`filter\`, \`reduce\` for iteration with immutable updates
850
+ - ✅ TypeScript interface/type definitions
851
+ - ✅ Pure functions with explicit return types
852
+ - ✅ Immutable updates to Svelte state: \`array = [...array, newItem]\`
853
+
854
+ ## Context-Specific Rules
855
+
856
+ ### In Svelte Components (.svelte files):
857
+ \`\`\`typescript
858
+ // ✅ REQUIRED - Use let for reactive state
859
+ let count = $state<number>(0);
860
+ let user = $state<User | null>(null);
861
+
862
+ // ✅ Use const for helper functions
863
+ const calculateTotal = (items: readonly Item[]): number => {
864
+ return items.reduce((sum, item) => sum + item.price, 0);
865
+ };
866
+ \`\`\`
867
+
868
+ ### In TypeScript Modules (.ts files):
869
+ \`\`\`typescript
870
+ // ✅ REQUIRED - Use const for all declarations
871
+ import { Effect } from "effect";
872
+
873
+ export const DEFAULT_CONFIG = { theme: 'dark' } as const;
874
+ export const processData = (data: Data): Effect.Effect<Output, Error> => {
875
+ // Pure functional logic using Effect
876
+ return Effect.succeed(data);
877
+ };
878
+ \`\`\`
879
+
880
+ **Remember**: Functional programming principles are maintained through immutable data operations and pure functions, while respecting Svelte's reactive declaration patterns that require \`let\` for component state.
881
+ `;
882
+ try {
883
+ // Create the docs directory
884
+ const docsPath = path.join(projectName, 'docs');
885
+ await fs.ensureDir(docsPath);
886
+ console.log('Created empty docs folder');
887
+ // Write the AGENTS.md file
888
+ const agentsResult = await writeFile(projectName, agentsContent, 'AGENTS.md');
889
+ if (Either.isLeft(agentsResult)) {
890
+ return agentsResult;
891
+ }
892
+ // Create empty PRD.md file
893
+ const prdContent = `# Product Requirements Document (PRD)
894
+
895
+ ## Project Overview
896
+ - **Project Name**:
897
+ - **Purpose**:
898
+ - **Target Audience**:
899
+ - **Key Features**:
900
+
901
+ ## Requirements
902
+ ### Functional Requirements
903
+ -
904
+
905
+ ### Non-Functional Requirements
906
+ -
907
+
908
+ ## Success Metrics
909
+ -
910
+
911
+ ## Timeline
912
+ - **Start Date**:
913
+ - **Target Completion**:
914
+
915
+ ## Resources Needed
916
+ -
917
+ `;
918
+ const prdResult = await writeFile(projectName, prdContent, 'docs/PRD.md');
919
+ if (Either.isLeft(prdResult)) {
920
+ return prdResult;
921
+ }
922
+ // Create empty Architecture.md file
923
+ const archContent = `# System Architecture
924
+
925
+ ## Overview
926
+ - **Architecture Type**:
927
+ - **Main Components**:
928
+
929
+ ## Components
930
+ ### Frontend
931
+ -
932
+
933
+ ### Backend
934
+ -
935
+
936
+ ### Database
937
+ -
938
+
939
+ ### External Services
940
+ -
941
+
942
+ ## Data Flow
943
+ -
944
+
945
+ ## Deployment Architecture
946
+ -
947
+
948
+ ## Security Considerations
949
+ -
950
+ `;
951
+ const archResult = await writeFile(projectName, archContent, 'docs/Architecture.md');
952
+ if (Either.isLeft(archResult)) {
953
+ return archResult;
954
+ }
955
+ console.log('AGENTS.md file created successfully');
956
+ console.log('PRD.md file created successfully');
957
+ console.log('Architecture.md file created successfully');
958
+ // Create .gitignore file
959
+ const gitignoreResult = await createGitignore(projectName);
960
+ if (Either.isLeft(gitignoreResult)) {
961
+ return gitignoreResult;
962
+ }
963
+ return Either.right(undefined);
964
+ }
965
+ catch (error) {
966
+ console.error('Failed to create AGENTS.md file and docs folder:', error);
967
+ return Either.left(new Error(`Failed to create AGENTS.md file and docs folder: ${error.message}`));
968
+ }
969
+ };
970
+ /**
971
+ * Creates a .gitignore file for the SvelteKit project
972
+ * @param projectName Name of the project
973
+ */
974
+ export const createGitignore = async (projectName) => {
975
+ console.log(`Creating .gitignore file for project: ${projectName}`);
976
+ // Define content for .gitignore file
977
+ const gitignoreContent = `# Logs
978
+ *.log
979
+ npm-debug.log*
980
+ yarn-debug.log*
981
+ yarn-error.log*
982
+ pnpm-debug.log*
983
+ lerna-debug.log*
984
+
985
+ # Runtime data
986
+ pids
987
+ *.pid
988
+ *.seed
989
+ *.pid.lock
990
+
991
+ # Directory for instrumented libs generated by jscoverage/JSCover
992
+ lib-cov
993
+
994
+ # Coverage directory used by tools like istanbul
995
+ coverage
996
+ *.lcov
997
+
998
+ # nyc test coverage
999
+ .nyc_output
1000
+
1001
+ # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
1002
+ .grunt
1003
+
1004
+ # Bower dependency directory (https://bower.io/)
1005
+ bower_components
1006
+
1007
+ # node-waf configuration
1008
+ .lock-wscript
1009
+
1010
+ # Compiled binary addons (https://nodejs.org/api/addons.html)
1011
+ build/Release
1012
+
1013
+ # Dependency directories
1014
+ node_modules/
1015
+ jspm_packages/
1016
+
1017
+ # TypeScript v1 declaration files
1018
+ typings/
1019
+
1020
+ # TypeScript cache
1021
+ *.tsbuildinfo
1022
+
1023
+ # Optional npm cache directory
1024
+ .npm
1025
+
1026
+ # Optional eslint cache
1027
+ .eslintcache
1028
+
1029
+ # Microbundle cache
1030
+ .rpt2_cache/
1031
+ .rts2_cache_cjs/
1032
+ .rts2_cache_es/
1033
+ .rts2_cache_umd/
1034
+
1035
+ # Optional REPL history
1036
+ .node_repl_history
1037
+
1038
+ # Output of 'npm pack'
1039
+ *.tgz
1040
+
1041
+ # Yarn Integrity file
1042
+ .yarn-integrity
1043
+
1044
+ # dotenv environment variables file
1045
+ .env
1046
+ .env.test
1047
+
1048
+ # parcel-bundler cache (https://parceljs.org/)
1049
+ .cache
1050
+ .parcel-cache
1051
+
1052
+ # Next.js build output
1053
+ .next
1054
+ out/
1055
+
1056
+ # Nuxt build / generate output
1057
+ .nuxt
1058
+ dist
1059
+
1060
+ # Gatsby files
1061
+ .cache/
1062
+ # Ds Store
1063
+ .DS_Store
1064
+
1065
+ # Mac files
1066
+ .DS_Store
1067
+ .AppleDouble
1068
+ .LSOverride
1069
+
1070
+ # Windows thumbnail cache
1071
+ Thumbs.db
1072
+ ehthumbs.db
1073
+ ehthumbs_vista.db
1074
+
1075
+ # Windows shortcut files
1076
+ *.lnk
1077
+
1078
+ # SvelteKit
1079
+ .output
1080
+ build/
1081
+
1082
+ # Production
1083
+ dist/
1084
+
1085
+ # Static files (PWA assets and other static assets)
1086
+ static/
1087
+
1088
+ # .env
1089
+ .env
1090
+
1091
+ # Coverage
1092
+ coverage/
1093
+
1094
+ # Debug files
1095
+ debug.log
1096
+ *.log
1097
+
1098
+ # Local development settings
1099
+ .env.local
1100
+ .env.development.local
1101
+ .env.test.local
1102
+ .env.production.local
1103
+
1104
+ # Browser extensions
1105
+ # VS Code
1106
+ .vscode/
1107
+ !.vscode/settings.json
1108
+ !.vscode/tasks.json
1109
+ !.vscode/launch.json
1110
+ !.vscode/extensions.json
1111
+
1112
+ # IntelliJ IDE
1113
+ .idea/
1114
+
1115
+ # NetBeans
1116
+ nbproject/private/
1117
+ nbproject/project.bak
1118
+ nbproject/build-impl.xml
1119
+ nbproject/genfiles.properties
1120
+ nbproject/project.properties
1121
+ build/
1122
+
1123
+ # Code::Blocks
1124
+ *.depend
1125
+ *.layout
1126
+ *.cbp
1127
+
1128
+ # Dev-C++
1129
+ *.dev
1130
+
1131
+ # Sublime Text
1132
+ *.sublime-workspace
1133
+ *.sublime-project
1134
+
1135
+ # Vim
1136
+ [._]*.s[a-w][a-z]
1137
+ [._]s[a-w][a-z]
1138
+ *.un~
1139
+ Session.vim
1140
+ .netrwhist
1141
+
1142
+ # Emacs
1143
+ *~
1144
+ \\#\\#*
1145
+ .\\#*
1146
+ *.elc
1147
+ auto-save-list
1148
+ tramp
1149
+ .\.emacs\.desktop
1150
+ .\.emacs\.desktop\.lock
1151
+ .\.project\.desktop
1152
+ .\.project\.desktop\.lock
1153
+
1154
+ # Visual Studio
1155
+ *.user
1156
+ *.userosscache
1157
+ *.suo
1158
+ *.csproj.user
1159
+ *.csproj.userosscache
1160
+ *.vbproj.user
1161
+ *.vbproj.userosscache
1162
+ *.vcxproj.filters
1163
+ *.VC.db
1164
+ *.VC.VC.opendb
1165
+ *.VC.tagcache
1166
+
1167
+ # Visual Studio Code
1168
+ .vscode/
1169
+
1170
+ # Rider
1171
+ .idea/
1172
+
1173
+ # Platform-specific files
1174
+ *.swp
1175
+ *.swo
1176
+
1177
+ # OS generated files
1178
+ .DS_Store?
1179
+ ._*
1180
+ .Spotlight-V100
1181
+ .Trashes
1182
+ ehthumbs.db
1183
+ Thumbs.db
1184
+ `;
1185
+ try {
1186
+ // Write the .gitignore file
1187
+ const result = await writeFile(projectName, gitignoreContent, '.gitignore');
1188
+ if (Either.isLeft(result)) {
1189
+ return result;
1190
+ }
1191
+ console.log('.gitignore file created successfully');
1192
+ return Either.right(undefined);
1193
+ }
1194
+ catch (error) {
1195
+ console.error('Failed to create .gitignore file:', error);
1196
+ return Either.left(new Error(`Failed to create .gitignore file: ${error.message}`));
1197
+ }
1198
+ };
1199
+ //# sourceMappingURL=process-fs-handler.js.map