cf-yoyo 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (141) hide show
  1. package/.eslintrc.json +28 -0
  2. package/.github/workflows/ci.yml +96 -0
  3. package/.prettierrc.json +10 -0
  4. package/CHANGELOG.md +55 -0
  5. package/README.md +138 -0
  6. package/__tests__/cli-e2e.test.ts +145 -0
  7. package/__tests__/config.test.ts +268 -0
  8. package/__tests__/filesystem.test.ts +453 -0
  9. package/__tests__/logger.test.ts +274 -0
  10. package/__tests__/template-engine.test.ts +450 -0
  11. package/__tests__/types.test.ts +25 -0
  12. package/deep_todos.md +766 -0
  13. package/dist/cli/commands/create.d.ts +26 -0
  14. package/dist/cli/commands/create.d.ts.map +1 -0
  15. package/dist/cli/commands/create.js +308 -0
  16. package/dist/cli/commands/create.js.map +1 -0
  17. package/dist/cli/commands/git.d.ts +10 -0
  18. package/dist/cli/commands/git.d.ts.map +1 -0
  19. package/dist/cli/commands/git.js +887 -0
  20. package/dist/cli/commands/git.js.map +1 -0
  21. package/dist/cli/commands/list.d.ts +10 -0
  22. package/dist/cli/commands/list.d.ts.map +1 -0
  23. package/dist/cli/commands/list.js +90 -0
  24. package/dist/cli/commands/list.js.map +1 -0
  25. package/dist/cli/index.d.ts +15 -0
  26. package/dist/cli/index.d.ts.map +1 -0
  27. package/dist/cli/index.js +62 -0
  28. package/dist/cli/index.js.map +1 -0
  29. package/dist/core/config.d.ts +35 -0
  30. package/dist/core/config.d.ts.map +1 -0
  31. package/dist/core/config.js +260 -0
  32. package/dist/core/config.js.map +1 -0
  33. package/dist/core/filesystem.d.ts +84 -0
  34. package/dist/core/filesystem.d.ts.map +1 -0
  35. package/dist/core/filesystem.js +417 -0
  36. package/dist/core/filesystem.js.map +1 -0
  37. package/dist/core/git-token.d.ts +81 -0
  38. package/dist/core/git-token.d.ts.map +1 -0
  39. package/dist/core/git-token.js +244 -0
  40. package/dist/core/git-token.js.map +1 -0
  41. package/dist/core/git.d.ts +70 -0
  42. package/dist/core/git.d.ts.map +1 -0
  43. package/dist/core/git.js +367 -0
  44. package/dist/core/git.js.map +1 -0
  45. package/dist/core/prompt.d.ts +28 -0
  46. package/dist/core/prompt.d.ts.map +1 -0
  47. package/dist/core/prompt.js +253 -0
  48. package/dist/core/prompt.js.map +1 -0
  49. package/dist/core/template-engine.d.ts +52 -0
  50. package/dist/core/template-engine.d.ts.map +1 -0
  51. package/dist/core/template-engine.js +308 -0
  52. package/dist/core/template-engine.js.map +1 -0
  53. package/dist/core/template-manager.d.ts +54 -0
  54. package/dist/core/template-manager.d.ts.map +1 -0
  55. package/dist/core/template-manager.js +330 -0
  56. package/dist/core/template-manager.js.map +1 -0
  57. package/dist/index.d.ts +12 -0
  58. package/dist/index.d.ts.map +1 -0
  59. package/dist/index.js +19 -0
  60. package/dist/index.js.map +1 -0
  61. package/dist/types/index.d.ts +244 -0
  62. package/dist/types/index.d.ts.map +1 -0
  63. package/dist/types/index.js +51 -0
  64. package/dist/types/index.js.map +1 -0
  65. package/dist/utils/logger.d.ts +68 -0
  66. package/dist/utils/logger.d.ts.map +1 -0
  67. package/dist/utils/logger.js +140 -0
  68. package/dist/utils/logger.js.map +1 -0
  69. package/memory.md +241 -0
  70. package/need-debug.md +395 -0
  71. package/package.json +42 -0
  72. package/src/cli/commands/create.ts +326 -0
  73. package/src/cli/commands/git.ts +1001 -0
  74. package/src/cli/commands/list.ts +97 -0
  75. package/src/cli/index.ts +71 -0
  76. package/src/core/config.ts +262 -0
  77. package/src/core/filesystem.ts +408 -0
  78. package/src/core/git-token.ts +248 -0
  79. package/src/core/git.ts +384 -0
  80. package/src/core/prompt.ts +345 -0
  81. package/src/core/template-engine.ts +324 -0
  82. package/src/core/template-manager.ts +338 -0
  83. package/src/index.ts +19 -0
  84. package/src/types/index.ts +259 -0
  85. package/src/utils/logger.ts +150 -0
  86. package/templates/pages/basic/README.md.mustache +63 -0
  87. package/templates/pages/basic/package.json.mustache +23 -0
  88. package/templates/pages/basic/public/css/style.css +199 -0
  89. package/templates/pages/basic/public/index.html.mustache +72 -0
  90. package/templates/pages/basic/public/js/main.js +103 -0
  91. package/templates/pages/basic/template.json +38 -0
  92. package/templates/pages/basic/tsconfig.json +21 -0
  93. package/templates/pages/basic/wrangler.toml.mustache +14 -0
  94. package/templates/pages/basic-js/README.md.mustache +62 -0
  95. package/templates/pages/basic-js/package.json.mustache +25 -0
  96. package/templates/pages/basic-js/public/css/style.css +212 -0
  97. package/templates/pages/basic-js/public/index.html.mustache +53 -0
  98. package/templates/pages/basic-js/public/js/main.js +134 -0
  99. package/templates/pages/basic-js/template.json +35 -0
  100. package/templates/pages/basic-js/wrangler.toml.mustache +14 -0
  101. package/templates/pages/react/README.md.mustache +97 -0
  102. package/templates/pages/react/index.html.mustache +14 -0
  103. package/templates/pages/react/package.json.mustache +34 -0
  104. package/templates/pages/react/src/App.css +168 -0
  105. package/templates/pages/react/src/App.tsx.mustache +62 -0
  106. package/templates/pages/react/src/index.css +53 -0
  107. package/templates/pages/react/src/main.tsx.mustache +10 -0
  108. package/templates/pages/react/src/vite-env.d.ts +1 -0
  109. package/templates/pages/react/template.json +54 -0
  110. package/templates/pages/react/tsconfig.json +21 -0
  111. package/templates/pages/react/tsconfig.node.json +10 -0
  112. package/templates/pages/react/vite.config.ts +16 -0
  113. package/templates/worker/basic/README.md.mustache +56 -0
  114. package/templates/worker/basic/package.json.mustache +29 -0
  115. package/templates/worker/basic/src/index.ts.mustache +125 -0
  116. package/templates/worker/basic/template.json +30 -0
  117. package/templates/worker/basic/tsconfig.json +24 -0
  118. package/templates/worker/basic/wrangler.toml.mustache +33 -0
  119. package/templates/worker/basic-js/README.md.mustache +55 -0
  120. package/templates/worker/basic-js/package.json.mustache +25 -0
  121. package/templates/worker/basic-js/src/index.js.mustache +146 -0
  122. package/templates/worker/basic-js/template.json +27 -0
  123. package/templates/worker/basic-js/wrangler.toml.mustache +33 -0
  124. package/templates/worker/hono/README.md.mustache +79 -0
  125. package/templates/worker/hono/package.json.mustache +33 -0
  126. package/templates/worker/hono/src/index.ts.mustache +64 -0
  127. package/templates/worker/hono/src/routes/index.ts.mustache +165 -0
  128. package/templates/worker/hono/template.json +34 -0
  129. package/templates/worker/hono/tsconfig.json +24 -0
  130. package/templates/worker/hono/wrangler.toml.mustache +36 -0
  131. package/templates/worker/hono-js/README.md.mustache +67 -0
  132. package/templates/worker/hono-js/package.json.mustache +29 -0
  133. package/templates/worker/hono-js/src/index.js.mustache +55 -0
  134. package/templates/worker/hono-js/src/routes/index.js.mustache +127 -0
  135. package/templates/worker/hono-js/template.json +31 -0
  136. package/templates/worker/hono-js/wrangler.toml.mustache +36 -0
  137. package/thoughts/ledgers/CONTINUITY_ses_287e.md +74 -0
  138. package/thoughts/ledgers/CONTINUITY_ses_28b5.md +85 -0
  139. package/tsconfig.json +30 -0
  140. package/vitest.config.ts +20 -0
  141. package//351/240/205/347/233/256/350/241/250.md +140 -0
@@ -0,0 +1,453 @@
1
+ /**
2
+ * filesystem.ts 模組測試
3
+ * 測試檔案系統操作功能
4
+ */
5
+
6
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
7
+ import * as fs from 'fs-extra';
8
+ import * as path from 'path';
9
+ import {
10
+ directoryExists,
11
+ fileExists,
12
+ isEmptyDir,
13
+ createDirectory,
14
+ deleteDirectory,
15
+ writeFile,
16
+ readFile,
17
+ copyFile,
18
+ copyDirectory,
19
+ deleteFile,
20
+ checkDirectoryConflict,
21
+ createProjectStructure,
22
+ listDirectory,
23
+ getFileSize,
24
+ getFileModifiedDate,
25
+ } from '../src/core/filesystem';
26
+ import { ConflictResolution } from '../src/types';
27
+
28
+ describe('FileSystem', () => {
29
+ let testBaseDir: string;
30
+
31
+ beforeEach(() => {
32
+ // 創建一個基礎臨時目錄供測試使用
33
+ testBaseDir = fs.mkdtempSync(path.join(process.cwd(), '__tests__', 'temp-'));
34
+ });
35
+
36
+ afterEach(() => {
37
+ if (fs.existsSync(testBaseDir)) {
38
+ fs.rmSync(testBaseDir, { recursive: true, force: true });
39
+ }
40
+ });
41
+
42
+ // ==================== Basic Queries ====================
43
+
44
+ describe('directoryExists', () => {
45
+ it('should return true for existing directory', () => {
46
+ const testDir = path.join(testBaseDir, 'existing-dir');
47
+ fs.mkdirSync(testDir);
48
+ expect(directoryExists(testDir)).toBe(true);
49
+ });
50
+
51
+ it('should return false for non-existing directory', () => {
52
+ expect(directoryExists(path.join(testBaseDir, 'nope'))).toBe(false);
53
+ });
54
+
55
+ it('should return false for file path', () => {
56
+ const testFile = path.join(testBaseDir, 'file.txt');
57
+ fs.writeFileSync(testFile, 'content');
58
+ expect(directoryExists(testFile)).toBe(false);
59
+ });
60
+ });
61
+
62
+ describe('fileExists', () => {
63
+ it('should return true for existing file', () => {
64
+ const testFile = path.join(testBaseDir, 'file.txt');
65
+ fs.writeFileSync(testFile, 'content');
66
+ expect(fileExists(testFile)).toBe(true);
67
+ });
68
+
69
+ it('should return false for non-existing file', () => {
70
+ expect(fileExists(path.join(testBaseDir, 'nope.txt'))).toBe(false);
71
+ });
72
+
73
+ it('should return false for directory path', () => {
74
+ const testDir = path.join(testBaseDir, 'some-dir');
75
+ fs.mkdirSync(testDir);
76
+ expect(fileExists(testDir)).toBe(false);
77
+ });
78
+ });
79
+
80
+ describe('isEmptyDir', () => {
81
+ it('should return true for empty directory', () => {
82
+ const testDir = path.join(testBaseDir, 'empty-dir');
83
+ fs.mkdirSync(testDir);
84
+ expect(isEmptyDir(testDir)).toBe(true);
85
+ });
86
+
87
+ it('should return false for non-empty directory', () => {
88
+ const testDir = path.join(testBaseDir, 'non-empty-dir');
89
+ fs.mkdirSync(testDir);
90
+ fs.writeFileSync(path.join(testDir, 'file.txt'), 'content');
91
+ expect(isEmptyDir(testDir)).toBe(false);
92
+ });
93
+
94
+ it('should return true for non-existing directory', () => {
95
+ expect(isEmptyDir(path.join(testBaseDir, 'nope'))).toBe(true);
96
+ });
97
+ });
98
+
99
+ // ==================== Directory Operations ====================
100
+
101
+ describe('createDirectory', () => {
102
+ it('should create directory successfully', () => {
103
+ const testDir = path.join(testBaseDir, 'new-dir');
104
+ const result = createDirectory(testDir);
105
+ expect(result).toBe(true);
106
+ expect(fs.existsSync(testDir)).toBe(true);
107
+ });
108
+
109
+ it('should return true when directory already exists (recursive mode)', () => {
110
+ const testDir = path.join(testBaseDir, 'existing-dir');
111
+ fs.mkdirSync(testDir);
112
+ const result = createDirectory(testDir);
113
+ expect(result).toBe(true);
114
+ });
115
+
116
+ it('should create nested directories', () => {
117
+ const nested = path.join(testBaseDir, 'a', 'b', 'c');
118
+ const result = createDirectory(nested);
119
+ expect(result).toBe(true);
120
+ expect(fs.existsSync(nested)).toBe(true);
121
+ });
122
+
123
+ it('should create parent directories when writing to nested path', () => {
124
+ const nested = path.join(testBaseDir, 'parent', 'child');
125
+ const result = createDirectory(nested, { recursive: true });
126
+ expect(result).toBe(true);
127
+ expect(fs.existsSync(nested)).toBe(true);
128
+ });
129
+ });
130
+
131
+ describe('deleteDirectory', () => {
132
+ it('should delete directory successfully', () => {
133
+ const testDir = path.join(testBaseDir, 'to-delete');
134
+ fs.mkdirSync(testDir);
135
+ const result = deleteDirectory(testDir);
136
+ expect(result).toBe(true);
137
+ expect(fs.existsSync(testDir)).toBe(false);
138
+ });
139
+
140
+ it('should return true for non-existing directory', () => {
141
+ const result = deleteDirectory(path.join(testBaseDir, 'nope'));
142
+ expect(result).toBe(true);
143
+ });
144
+
145
+ it('should delete directory with contents', () => {
146
+ const testDir = path.join(testBaseDir, 'with-contents');
147
+ fs.mkdirSync(testDir);
148
+ fs.mkdirSync(path.join(testDir, 'sub'));
149
+ fs.writeFileSync(path.join(testDir, 'file.txt'), 'content');
150
+ const result = deleteDirectory(testDir);
151
+ expect(result).toBe(true);
152
+ expect(fs.existsSync(testDir)).toBe(false);
153
+ });
154
+ });
155
+
156
+ describe('checkDirectoryConflict', () => {
157
+ it('should return no conflict when directory does not exist', () => {
158
+ const result = checkDirectoryConflict(path.join(testBaseDir, 'new'), () => {
159
+ // Should not be called
160
+ });
161
+ expect(result.hasConflict).toBe(false);
162
+ expect(result.resolution).toBeUndefined();
163
+ });
164
+
165
+ it('should return no conflict when directory is empty', () => {
166
+ const testDir = path.join(testBaseDir, 'empty');
167
+ fs.mkdirSync(testDir);
168
+ const result = checkDirectoryConflict(testDir, () => {
169
+ // Should not be called
170
+ });
171
+ expect(result.hasConflict).toBe(false);
172
+ expect(result.resolution).toBeUndefined();
173
+ });
174
+
175
+ it('should detect conflict when directory exists and is not empty', () => {
176
+ const testDir = path.join(testBaseDir, 'conflict-dir');
177
+ fs.mkdirSync(testDir);
178
+ fs.writeFileSync(path.join(testDir, 'file.txt'), 'content');
179
+
180
+ let resolutionCalled = false;
181
+ let capturedResolution: ConflictResolution | undefined;
182
+
183
+ const result = checkDirectoryConflict(testDir, (resolution) => {
184
+ resolutionCalled = true;
185
+ capturedResolution = resolution;
186
+ });
187
+
188
+ expect(result.hasConflict).toBe(true);
189
+ expect(resolutionCalled).toBe(true);
190
+ expect(capturedResolution).toBeDefined();
191
+ expect(capturedResolution?.action).toBe('abort');
192
+ });
193
+ });
194
+
195
+ // ==================== File Operations ====================
196
+
197
+ describe('writeFile', () => {
198
+ it('should write file successfully', () => {
199
+ const testFile = path.join(testBaseDir, 'test.txt');
200
+ const content = 'Hello, World!';
201
+ const result = writeFile(testFile, content);
202
+ expect(result).toBe(true);
203
+ expect(fs.existsSync(testFile)).toBe(true);
204
+ expect(fs.readFileSync(testFile, 'utf-8')).toBe(content);
205
+ });
206
+
207
+ it('should create parent directories if not exist', () => {
208
+ const testFile = path.join(testBaseDir, 'subdir', 'file.txt');
209
+ const content = 'Nested content';
210
+ const result = writeFile(testFile, content);
211
+ expect(result).toBe(true);
212
+ expect(fs.existsSync(testFile)).toBe(true);
213
+ });
214
+
215
+ it('should overwrite existing file by default', () => {
216
+ const testFile = path.join(testBaseDir, 'file.txt');
217
+ fs.writeFileSync(testFile, 'Original content');
218
+ const result = writeFile(testFile, 'Updated content');
219
+ expect(result).toBe(true);
220
+ expect(fs.readFileSync(testFile, 'utf-8')).toBe('Updated content');
221
+ });
222
+
223
+ it('should not overwrite when overwrite is false', () => {
224
+ const testFile = path.join(testBaseDir, 'file.txt');
225
+ fs.writeFileSync(testFile, 'Original content');
226
+ const result = writeFile(testFile, 'New content', { overwrite: false });
227
+ expect(result).toBe(false);
228
+ expect(fs.readFileSync(testFile, 'utf-8')).toBe('Original content');
229
+ });
230
+ });
231
+
232
+ describe('readFile', () => {
233
+ it('should read file successfully', () => {
234
+ const testFile = path.join(testBaseDir, 'read.txt');
235
+ const content = 'Test content';
236
+ fs.writeFileSync(testFile, content);
237
+ const result = readFile(testFile);
238
+ expect(result).toBe(content);
239
+ });
240
+
241
+ it('should return null for non-existent file', () => {
242
+ const result = readFile(path.join(testBaseDir, 'nope.txt'));
243
+ expect(result).toBeNull();
244
+ });
245
+
246
+ it('should handle custom encoding', () => {
247
+ const testFile = path.join(testBaseDir, 'utf16.txt');
248
+ const content = 'Unicode test 測試';
249
+ fs.writeFileSync(testFile, content, { encoding: 'utf8' });
250
+ const result = readFile(testFile, { encoding: 'utf8' });
251
+ expect(result).toBe(content);
252
+ });
253
+ });
254
+
255
+ describe('deleteFile', () => {
256
+ it('should delete file successfully', () => {
257
+ const testFile = path.join(testBaseDir, 'delete.txt');
258
+ fs.writeFileSync(testFile, 'content');
259
+ const result = deleteFile(testFile);
260
+ expect(result).toBe(true);
261
+ expect(fs.existsSync(testFile)).toBe(false);
262
+ });
263
+
264
+ it('should return true for non-existing file', () => {
265
+ const result = deleteFile(path.join(testBaseDir, 'nope.txt'));
266
+ expect(result).toBe(true);
267
+ });
268
+ });
269
+
270
+ describe('copyFile', () => {
271
+ it('should copy file successfully', () => {
272
+ const source = path.join(testBaseDir, 'source.txt');
273
+ const dest = path.join(testBaseDir, 'dest.txt');
274
+ fs.writeFileSync(source, 'Source content');
275
+ const result = copyFile(source, dest);
276
+ expect(result).toBe(true);
277
+ expect(fs.existsSync(dest)).toBe(true);
278
+ expect(fs.readFileSync(dest, 'utf-8')).toBe('Source content');
279
+ });
280
+
281
+ it('should return false if source does not exist', () => {
282
+ const result = copyFile(
283
+ path.join(testBaseDir, 'nope.txt'),
284
+ path.join(testBaseDir, 'dest.txt')
285
+ );
286
+ expect(result).toBe(false);
287
+ });
288
+
289
+ it('should create parent directories if needed', () => {
290
+ const source = path.join(testBaseDir, 'source.txt');
291
+ const dest = path.join(testBaseDir, 'subdir', 'dest.txt');
292
+ fs.writeFileSync(source, 'Content');
293
+ const result = copyFile(source, dest);
294
+ expect(result).toBe(true);
295
+ expect(fs.existsSync(dest)).toBe(true);
296
+ });
297
+
298
+ it('should not overwrite when overwrite is false', () => {
299
+ const source = path.join(testBaseDir, 'source.txt');
300
+ const dest = path.join(testBaseDir, 'dest.txt');
301
+ fs.writeFileSync(source, 'Source');
302
+ fs.writeFileSync(dest, 'Existing');
303
+ const result = copyFile(source, dest, { overwrite: false });
304
+ expect(result).toBe(false);
305
+ expect(fs.readFileSync(dest, 'utf-8')).toBe('Existing');
306
+ });
307
+ });
308
+
309
+ // ==================== Directory Copy ====================
310
+
311
+ describe('copyDirectory', () => {
312
+ it('should copy directory recursively', () => {
313
+ const sourceDir = path.join(testBaseDir, 'src');
314
+ const destDir = path.join(testBaseDir, 'dest');
315
+ fs.mkdirSync(sourceDir);
316
+ fs.mkdirSync(path.join(sourceDir, 'sub'));
317
+ fs.writeFileSync(path.join(sourceDir, 'file1.txt'), 'Content 1');
318
+ fs.writeFileSync(path.join(sourceDir, 'sub', 'file2.txt'), 'Content 2');
319
+
320
+ const result = copyDirectory(sourceDir, destDir);
321
+ expect(result.success).toBe(true);
322
+ expect(result.files.length).toBe(2);
323
+ expect(fs.existsSync(path.join(destDir, 'file1.txt'))).toBe(true);
324
+ expect(fs.existsSync(path.join(destDir, 'sub', 'file2.txt'))).toBe(true);
325
+ });
326
+
327
+ it('should return failure if source does not exist', () => {
328
+ const result = copyDirectory(path.join(testBaseDir, 'nope'), path.join(testBaseDir, 'dest'));
329
+ expect(result.success).toBe(false);
330
+ expect(result.files).toEqual([]);
331
+ });
332
+
333
+ it('should preserve directory structure', () => {
334
+ const sourceDir = path.join(testBaseDir, 'src');
335
+ const destDir = path.join(testBaseDir, 'dest');
336
+ fs.mkdirSync(sourceDir);
337
+ fs.mkdirSync(path.join(sourceDir, 'level1', 'level2'), { recursive: true });
338
+ fs.writeFileSync(path.join(sourceDir, 'level1', 'file.txt'), 'Level 1');
339
+ fs.writeFileSync(path.join(sourceDir, 'level1', 'level2', 'file.txt'), 'Level 2');
340
+
341
+ const result = copyDirectory(sourceDir, destDir);
342
+ expect(result.success).toBe(true);
343
+ expect(fs.existsSync(path.join(destDir, 'level1', 'file.txt'))).toBe(true);
344
+ expect(fs.existsSync(path.join(destDir, 'level1', 'level2', 'file.txt'))).toBe(true);
345
+ });
346
+ });
347
+
348
+ // ==================== Helper Functions ====================
349
+
350
+ describe('listDirectory', () => {
351
+ it('should list directory contents', () => {
352
+ const testDir = path.join(testBaseDir, 'list-dir');
353
+ fs.mkdirSync(testDir);
354
+ fs.writeFileSync(path.join(testDir, 'file1.txt'), '1');
355
+ fs.writeFileSync(path.join(testDir, 'file2.txt'), '2');
356
+ fs.mkdirSync(path.join(testDir, 'subdir'));
357
+
358
+ const result = listDirectory(testDir);
359
+ expect(result).toContain('file1.txt');
360
+ expect(result).toContain('file2.txt');
361
+ expect(result).toContain('subdir');
362
+ expect(result.length).toBe(3);
363
+ });
364
+
365
+ it('should return empty array for non-existing directory', () => {
366
+ const result = listDirectory(path.join(testBaseDir, 'nope'));
367
+ expect(result).toEqual([]);
368
+ });
369
+
370
+ it('should return empty array for empty directory', () => {
371
+ const testDir = path.join(testBaseDir, 'empty');
372
+ fs.mkdirSync(testDir);
373
+ const result = listDirectory(testDir);
374
+ expect(result).toEqual([]);
375
+ });
376
+ });
377
+
378
+ describe('getFileSize', () => {
379
+ it('should return size of file in bytes', () => {
380
+ const testFile = path.join(testBaseDir, 'size.txt');
381
+ const content = 'Hello, World!'; // 13 bytes
382
+ fs.writeFileSync(testFile, content);
383
+ const size = getFileSize(testFile);
384
+ expect(size).toBe(13);
385
+ });
386
+
387
+ it('should return 0 for non-existing file', () => {
388
+ const size = getFileSize(path.join(testBaseDir, 'nope.txt'));
389
+ expect(size).toBe(0);
390
+ });
391
+
392
+ it('should handle empty file', () => {
393
+ const testFile = path.join(testBaseDir, 'empty.txt');
394
+ fs.writeFileSync(testFile, '');
395
+ const size = getFileSize(testFile);
396
+ expect(size).toBe(0);
397
+ });
398
+ });
399
+
400
+ describe('getFileModifiedDate', () => {
401
+ it('should return Date object for existing file', () => {
402
+ const testFile = path.join(testBaseDir, 'date.txt');
403
+ fs.writeFileSync(testFile, 'content');
404
+ const date = getFileModifiedDate(testFile);
405
+ expect(date).toBeInstanceOf(Date);
406
+ expect(date).not.toBeNull();
407
+ });
408
+
409
+ it('should return null for non-existing file', () => {
410
+ const date = getFileModifiedDate(path.join(testBaseDir, 'nope.txt'));
411
+ expect(date).toBeNull();
412
+ });
413
+ });
414
+
415
+ // ==================== Project Structure ====================
416
+
417
+ describe('createProjectStructure', () => {
418
+ it('should create full project structure successfully', () => {
419
+ const projectDir = path.join(testBaseDir, 'my-project');
420
+ const result = createProjectStructure(projectDir, 'MyProject');
421
+
422
+ expect(result.success).toBe(true);
423
+ expect(result.directories.length).toBeGreaterThan(0);
424
+ expect(result.files.length).toBeGreaterThan(0);
425
+
426
+ // Check directories
427
+ expect(fs.existsSync(projectDir)).toBe(true);
428
+ expect(fs.existsSync(path.join(projectDir, 'src'))).toBe(true);
429
+ expect(fs.existsSync(path.join(projectDir, 'public'))).toBe(true);
430
+ expect(fs.existsSync(path.join(projectDir, 'tests'))).toBe(true);
431
+
432
+ // Check files
433
+ expect(fs.existsSync(path.join(projectDir, '.gitignore'))).toBe(true);
434
+ expect(fs.existsSync(path.join(projectDir, 'README.md'))).toBe(true);
435
+
436
+ // Check README content
437
+ const readme = fs.readFileSync(path.join(projectDir, 'README.md'), 'utf-8');
438
+ expect(readme).toContain('MyProject');
439
+ });
440
+
441
+ it('should include all required files in result', () => {
442
+ const projectDir = path.join(testBaseDir, 'test-project');
443
+ const result = createProjectStructure(projectDir, 'TestProject');
444
+
445
+ expect(result.files).toContain(path.join(projectDir, '.gitignore'));
446
+ expect(result.files).toContain(path.join(projectDir, 'README.md'));
447
+ expect(result.directories).toContain(projectDir);
448
+ expect(result.directories).toContain(path.join(projectDir, 'src'));
449
+ expect(result.directories).toContain(path.join(projectDir, 'public'));
450
+ expect(result.directories).toContain(path.join(projectDir, 'tests'));
451
+ });
452
+ });
453
+ });