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.
- package/AGENTS-for-pwa.md +211 -0
- package/AGENTS.md +141 -0
- package/README.md +182 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +58 -0
- package/dist/cli.js.map +1 -0
- package/dist/deps-manager.d.ts.map +1 -0
- package/dist/deps-manager.js +119 -0
- package/dist/deps-manager.js.map +1 -0
- package/dist/engine.d.ts.map +1 -0
- package/dist/engine.js +229 -0
- package/dist/engine.js.map +1 -0
- package/dist/process-fs-handler.d.ts.map +1 -0
- package/dist/process-fs-handler.js +1199 -0
- package/dist/process-fs-handler.js.map +1 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +50 -0
- package/pnpm-workspace.yaml +2 -0
|
@@ -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
|