claude-coder 1.10.0 → 1.10.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.
Files changed (81) hide show
  1. package/README.md +239 -236
  2. package/bin/cli.js +170 -170
  3. package/package.json +55 -55
  4. package/recipes/_shared/roles/developer.md +11 -11
  5. package/recipes/_shared/roles/product.md +12 -12
  6. package/recipes/_shared/roles/tester.md +12 -12
  7. package/recipes/_shared/test/report-format.md +86 -86
  8. package/recipes/backend/base.md +27 -27
  9. package/recipes/backend/components/auth.md +18 -18
  10. package/recipes/backend/components/crud-api.md +18 -18
  11. package/recipes/backend/components/file-service.md +15 -15
  12. package/recipes/backend/manifest.json +20 -20
  13. package/recipes/backend/test/api-test.md +25 -25
  14. package/recipes/console/base.md +37 -37
  15. package/recipes/console/components/modal-form.md +20 -20
  16. package/recipes/console/components/pagination.md +17 -17
  17. package/recipes/console/components/search.md +17 -17
  18. package/recipes/console/components/table-list.md +18 -18
  19. package/recipes/console/components/tabs.md +14 -14
  20. package/recipes/console/components/tree.md +15 -15
  21. package/recipes/console/components/upload.md +15 -15
  22. package/recipes/console/manifest.json +24 -24
  23. package/recipes/console/test/crud-e2e.md +47 -47
  24. package/recipes/h5/base.md +26 -26
  25. package/recipes/h5/components/animation.md +11 -11
  26. package/recipes/h5/components/countdown.md +11 -11
  27. package/recipes/h5/components/share.md +11 -11
  28. package/recipes/h5/components/swiper.md +11 -11
  29. package/recipes/h5/manifest.json +21 -21
  30. package/recipes/h5/test/h5-e2e.md +20 -20
  31. package/src/commands/auth.js +420 -420
  32. package/src/commands/setup-modules/helpers.js +100 -100
  33. package/src/commands/setup-modules/index.js +25 -25
  34. package/src/commands/setup-modules/mcp.js +115 -115
  35. package/src/commands/setup-modules/provider.js +260 -260
  36. package/src/commands/setup-modules/safety.js +47 -47
  37. package/src/commands/setup-modules/simplify.js +52 -52
  38. package/src/commands/setup.js +172 -172
  39. package/src/common/assets.js +259 -259
  40. package/src/common/config.js +147 -147
  41. package/src/common/constants.js +55 -55
  42. package/src/common/indicator.js +260 -260
  43. package/src/common/interaction.js +170 -170
  44. package/src/common/logging.js +77 -77
  45. package/src/common/sdk.js +48 -48
  46. package/src/common/tasks.js +88 -88
  47. package/src/common/utils.js +215 -214
  48. package/src/core/coding.js +35 -35
  49. package/src/core/design.js +268 -268
  50. package/src/core/go.js +264 -264
  51. package/src/core/hooks.js +514 -514
  52. package/src/core/init.js +175 -175
  53. package/src/core/plan.js +194 -194
  54. package/src/core/prompts.js +292 -292
  55. package/src/core/repair.js +36 -36
  56. package/src/core/runner.js +438 -471
  57. package/src/core/scan.js +94 -94
  58. package/src/core/session.js +294 -294
  59. package/src/core/simplify.js +76 -76
  60. package/src/core/state.js +120 -120
  61. package/src/index.js +80 -80
  62. package/templates/coding/system.md +65 -65
  63. package/templates/coding/user.md +18 -18
  64. package/templates/design/base.md +103 -103
  65. package/templates/design/fixSystem.md +71 -71
  66. package/templates/design/fixUser.md +3 -3
  67. package/templates/design/init.md +304 -304
  68. package/templates/design/system.md +108 -108
  69. package/templates/design/user.md +11 -11
  70. package/templates/go/system.md +130 -130
  71. package/templates/other/bash-process.md +12 -12
  72. package/templates/other/coreProtocol.md +30 -30
  73. package/templates/other/guidance.json +72 -72
  74. package/templates/other/requirements.example.md +57 -57
  75. package/templates/other/test_rule.md +192 -192
  76. package/templates/other/web-testing.md +17 -17
  77. package/templates/plan/system.md +78 -78
  78. package/templates/plan/user.md +9 -9
  79. package/templates/scan/system.md +120 -120
  80. package/templates/scan/user.md +10 -10
  81. package/types/index.d.ts +217 -217
@@ -1,259 +1,259 @@
1
- 'use strict';
2
-
3
- const fs = require('fs');
4
- const path = require('path');
5
-
6
- const BUNDLED_DIR = path.join(__dirname, '..', '..', 'templates');
7
- const BUNDLED_RECIPES_DIR = path.join(__dirname, '..', '..', 'recipes');
8
-
9
- // kind: 'template' — 双目录解析(用户 assets → 内置 bundled),有缓存
10
- // kind: 'data' — .claude-coder/ 目录,无缓存
11
- // kind: 'runtime' — .claude-coder/.runtime/ 目录,无缓存
12
- // kind: 'root' — 项目根目录,无缓存
13
- const REGISTRY = new Map([
14
- // Go templates
15
- ['goSystem', { file: ['go', 'system.md'], kind: 'template' }],
16
-
17
- // Plan templates
18
- ['planSystem', { file: ['plan', 'system.md'], kind: 'template' }],
19
- ['planUser', { file: ['plan', 'user.md'], kind: 'template' }],
20
-
21
- // Coding templates
22
- ['codingSystem', { file: ['coding', 'system.md'], kind: 'template' }],
23
- ['codingUser', { file: ['coding', 'user.md'], kind: 'template' }],
24
-
25
- // Scan templates
26
- ['scanSystem', { file: ['scan', 'system.md'], kind: 'template' }],
27
- ['scanUser', { file: ['scan', 'user.md'], kind: 'template' }],
28
-
29
- // Design templates
30
- ['designBase', { file: ['design', 'base.md'], kind: 'template' }],
31
- ['designSystem', { file: ['design', 'system.md'], kind: 'template' }],
32
- ['designFixSystem',{ file: ['design', 'fixSystem.md'], kind: 'template' }],
33
- ['designInit', { file: ['design', 'init.md'], kind: 'template' }],
34
- ['designUser', { file: ['design', 'user.md'], kind: 'template' }],
35
- ['designFixUser', { file: ['design', 'fixUser.md'], kind: 'template' }],
36
-
37
- // Shared / other templates
38
- ['coreProtocol', { file: ['other', 'coreProtocol.md'], kind: 'template' }],
39
- ['testRule', { file: ['other', 'test_rule.md'], kind: 'template' }],
40
- ['guidance', { file: ['other', 'guidance.json'], kind: 'template' }],
41
- ['webTesting', { file: ['other', 'web-testing.md'], kind: 'template' }],
42
- ['bashProcess', { file: ['other', 'bash-process.md'], kind: 'template' }],
43
- ['requirements', { file: ['other', 'requirements.example.md'], kind: 'template' }],
44
-
45
- // Data files (.claude-coder/)
46
- ['env', { file: '.env', kind: 'data' }],
47
- ['tasks', { file: 'tasks.json', kind: 'data' }],
48
- ['progress', { file: 'progress.json', kind: 'data' }],
49
- ['sessionResult', { file: 'session_result.json', kind: 'data' }],
50
- ['profile', { file: 'project_profile.json', kind: 'data' }],
51
- ['testEnv', { file: 'test.env', kind: 'data' }],
52
- ['playwrightAuth', { file: 'playwright-auth.json', kind: 'data' }],
53
- ['designMap', { file: ['design', 'design_map.json'], kind: 'data' }],
54
-
55
- // Runtime files (.claude-coder/.runtime/)
56
- ['harnessState', { file: 'harness_state.json', kind: 'runtime' }],
57
- ['browserProfile', { file: 'browser-profile', kind: 'runtime' }],
58
-
59
- // Root files (project root)
60
- ['mcpConfig', { file: '.mcp.json', kind: 'root' }],
61
- ]);
62
-
63
- const DIRS = new Map([
64
- ['loop', ''],
65
- ['assets', 'assets'],
66
- ['runtime', '.runtime'],
67
- ['logs', '.runtime/logs'],
68
- ['design', 'design'],
69
- ['designPages', 'design/pages'],
70
- ]);
71
-
72
- function renderTemplate(template, vars = {}) {
73
- return template
74
- .replace(/\{\{(\w+)\}\}/g, (_, key) =>
75
- Object.prototype.hasOwnProperty.call(vars, key) ? String(vars[key]) : ''
76
- )
77
- .replace(/^\s+$/gm, '')
78
- .replace(/\n{3,}/g, '\n\n')
79
- .trim();
80
- }
81
-
82
- class AssetManager {
83
- constructor() {
84
- this.projectRoot = null;
85
- this.loopDir = null;
86
- this.assetsDir = null;
87
- this.bundledDir = BUNDLED_DIR;
88
- this.registry = new Map(REGISTRY);
89
- }
90
-
91
- init(projectRoot) {
92
- this.projectRoot = projectRoot || process.cwd();
93
- this.loopDir = path.join(this.projectRoot, '.claude-coder');
94
- this.assetsDir = path.join(this.loopDir, 'assets');
95
- }
96
-
97
- _ensureInit() {
98
- if (!this.loopDir) this.init();
99
- }
100
-
101
- _fileSegments(file) {
102
- return Array.isArray(file) ? file : [file];
103
- }
104
-
105
-
106
- path(name) {
107
- this._ensureInit();
108
- const entry = this.registry.get(name);
109
- if (!entry) return null;
110
- const segs = this._fileSegments(entry.file);
111
- switch (entry.kind) {
112
- case 'template': return this._resolveTemplate(segs);
113
- case 'data': return path.join(this.loopDir, ...segs);
114
- case 'runtime': return path.join(this.loopDir, '.runtime', ...segs);
115
- case 'root': return path.join(this.projectRoot, ...segs);
116
- default: return null;
117
- }
118
- }
119
-
120
- _resolveTemplate(segments) {
121
- if (this.assetsDir) {
122
- const userPath = path.join(this.assetsDir, ...segments);
123
- if (fs.existsSync(userPath)) return userPath;
124
- }
125
- const bundled = path.join(this.bundledDir, ...segments);
126
- if (fs.existsSync(bundled)) return bundled;
127
- return null;
128
- }
129
-
130
- dir(name) {
131
- this._ensureInit();
132
- const rel = DIRS.get(name);
133
- if (rel === undefined) return null;
134
- return rel === '' ? this.loopDir : path.join(this.loopDir, rel);
135
- }
136
-
137
- exists(name) {
138
- const p = this.path(name);
139
- return p ? fs.existsSync(p) : false;
140
- }
141
-
142
- read(name) {
143
- this._ensureInit();
144
- const entry = this.registry.get(name);
145
- if (!entry) return null;
146
-
147
- const filePath = this.path(name);
148
- if (!filePath || !fs.existsSync(filePath)) return entry.kind === 'template' ? '' : null;
149
- return fs.readFileSync(filePath, 'utf8');
150
- }
151
-
152
- readJson(name, fallback = null) {
153
- const content = this.read(name);
154
- if (content === null || content === '') return fallback;
155
- try {
156
- return JSON.parse(content);
157
- } catch {
158
- return fallback;
159
- }
160
- }
161
-
162
- write(name, content) {
163
- this._ensureInit();
164
- const entry = this.registry.get(name);
165
- if (!entry || entry.kind === 'template') return;
166
- const filePath = this.path(name);
167
- if (!filePath) return;
168
- const dir = path.dirname(filePath);
169
- if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
170
- fs.writeFileSync(filePath, content, 'utf8');
171
- }
172
-
173
- writeJson(name, data) {
174
- this.write(name, JSON.stringify(data, null, 2) + '\n');
175
- }
176
-
177
- render(name, vars = {}) {
178
- const raw = this.read(name);
179
- if (!raw) return '';
180
- return renderTemplate(raw, vars);
181
- }
182
-
183
- ensureDirs() {
184
- this._ensureInit();
185
- for (const [, rel] of DIRS) {
186
- const dir = rel === '' ? this.loopDir : path.join(this.loopDir, rel);
187
- if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
188
- }
189
- }
190
-
191
- deployAll() {
192
- this._ensureInit();
193
- if (!fs.existsSync(this.assetsDir)) {
194
- fs.mkdirSync(this.assetsDir, { recursive: true });
195
- }
196
- const deployed = [];
197
- const walk = (srcBase, destBase) => {
198
- const entries = fs.readdirSync(srcBase, { withFileTypes: true });
199
- for (const entry of entries) {
200
- const srcPath = path.join(srcBase, entry.name);
201
- const destPath = path.join(destBase, entry.name);
202
- if (entry.isDirectory()) {
203
- if (!fs.existsSync(destPath)) fs.mkdirSync(destPath, { recursive: true });
204
- walk(srcPath, destPath);
205
- } else {
206
- if (fs.existsSync(destPath)) continue;
207
- try {
208
- fs.copyFileSync(srcPath, destPath);
209
- deployed.push(path.relative(this.assetsDir, destPath));
210
- } catch { /* skip */ }
211
- }
212
- }
213
- };
214
- walk(this.bundledDir, this.assetsDir);
215
- return deployed;
216
- }
217
-
218
- deployRecipes() {
219
- this._ensureInit();
220
- const destDir = path.join(this.loopDir, 'recipes');
221
- if (!fs.existsSync(BUNDLED_RECIPES_DIR)) return [];
222
- if (!fs.existsSync(destDir)) fs.mkdirSync(destDir, { recursive: true });
223
-
224
- const deployed = [];
225
- const walk = (srcBase, destBase) => {
226
- const entries = fs.readdirSync(srcBase, { withFileTypes: true });
227
- for (const entry of entries) {
228
- const srcPath = path.join(srcBase, entry.name);
229
- const destPath = path.join(destBase, entry.name);
230
- if (entry.isDirectory()) {
231
- if (!fs.existsSync(destPath)) fs.mkdirSync(destPath, { recursive: true });
232
- walk(srcPath, destPath);
233
- } else {
234
- if (fs.existsSync(destPath)) continue;
235
- try {
236
- fs.copyFileSync(srcPath, destPath);
237
- deployed.push(path.relative(destDir, destPath));
238
- } catch { /* skip */ }
239
- }
240
- }
241
- };
242
- walk(BUNDLED_RECIPES_DIR, destDir);
243
- return deployed;
244
- }
245
-
246
- recipesDir() {
247
- this._ensureInit();
248
- const projectRecipes = path.join(this.loopDir, 'recipes');
249
- if (fs.existsSync(projectRecipes) && fs.readdirSync(projectRecipes).length > 0) {
250
- return projectRecipes;
251
- }
252
- return BUNDLED_RECIPES_DIR;
253
- }
254
-
255
- }
256
-
257
- const assets = new AssetManager();
258
-
259
- module.exports = { AssetManager, assets, renderTemplate };
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+
6
+ const BUNDLED_DIR = path.join(__dirname, '..', '..', 'templates');
7
+ const BUNDLED_RECIPES_DIR = path.join(__dirname, '..', '..', 'recipes');
8
+
9
+ // kind: 'template' — 双目录解析(用户 assets → 内置 bundled),有缓存
10
+ // kind: 'data' — .claude-coder/ 目录,无缓存
11
+ // kind: 'runtime' — .claude-coder/.runtime/ 目录,无缓存
12
+ // kind: 'root' — 项目根目录,无缓存
13
+ const REGISTRY = new Map([
14
+ // Go templates
15
+ ['goSystem', { file: ['go', 'system.md'], kind: 'template' }],
16
+
17
+ // Plan templates
18
+ ['planSystem', { file: ['plan', 'system.md'], kind: 'template' }],
19
+ ['planUser', { file: ['plan', 'user.md'], kind: 'template' }],
20
+
21
+ // Coding templates
22
+ ['codingSystem', { file: ['coding', 'system.md'], kind: 'template' }],
23
+ ['codingUser', { file: ['coding', 'user.md'], kind: 'template' }],
24
+
25
+ // Scan templates
26
+ ['scanSystem', { file: ['scan', 'system.md'], kind: 'template' }],
27
+ ['scanUser', { file: ['scan', 'user.md'], kind: 'template' }],
28
+
29
+ // Design templates
30
+ ['designBase', { file: ['design', 'base.md'], kind: 'template' }],
31
+ ['designSystem', { file: ['design', 'system.md'], kind: 'template' }],
32
+ ['designFixSystem',{ file: ['design', 'fixSystem.md'], kind: 'template' }],
33
+ ['designInit', { file: ['design', 'init.md'], kind: 'template' }],
34
+ ['designUser', { file: ['design', 'user.md'], kind: 'template' }],
35
+ ['designFixUser', { file: ['design', 'fixUser.md'], kind: 'template' }],
36
+
37
+ // Shared / other templates
38
+ ['coreProtocol', { file: ['other', 'coreProtocol.md'], kind: 'template' }],
39
+ ['testRule', { file: ['other', 'test_rule.md'], kind: 'template' }],
40
+ ['guidance', { file: ['other', 'guidance.json'], kind: 'template' }],
41
+ ['webTesting', { file: ['other', 'web-testing.md'], kind: 'template' }],
42
+ ['bashProcess', { file: ['other', 'bash-process.md'], kind: 'template' }],
43
+ ['requirements', { file: ['other', 'requirements.example.md'], kind: 'template' }],
44
+
45
+ // Data files (.claude-coder/)
46
+ ['env', { file: '.env', kind: 'data' }],
47
+ ['tasks', { file: 'tasks.json', kind: 'data' }],
48
+ ['progress', { file: 'progress.json', kind: 'data' }],
49
+ ['sessionResult', { file: 'session_result.json', kind: 'data' }],
50
+ ['profile', { file: 'project_profile.json', kind: 'data' }],
51
+ ['testEnv', { file: 'test.env', kind: 'data' }],
52
+ ['playwrightAuth', { file: 'playwright-auth.json', kind: 'data' }],
53
+ ['designMap', { file: ['design', 'design_map.json'], kind: 'data' }],
54
+
55
+ // Runtime files (.claude-coder/.runtime/)
56
+ ['harnessState', { file: 'harness_state.json', kind: 'data' }],
57
+ ['browserProfile', { file: 'browser-profile', kind: 'runtime' }],
58
+
59
+ // Root files (project root)
60
+ ['mcpConfig', { file: '.mcp.json', kind: 'root' }],
61
+ ]);
62
+
63
+ const DIRS = new Map([
64
+ ['loop', ''],
65
+ ['assets', 'assets'],
66
+ ['runtime', '.runtime'],
67
+ ['logs', '.runtime/logs'],
68
+ ['design', 'design'],
69
+ ['designPages', 'design/pages'],
70
+ ]);
71
+
72
+ function renderTemplate(template, vars = {}) {
73
+ return template
74
+ .replace(/\{\{(\w+)\}\}/g, (_, key) =>
75
+ Object.prototype.hasOwnProperty.call(vars, key) ? String(vars[key]) : ''
76
+ )
77
+ .replace(/^\s+$/gm, '')
78
+ .replace(/\n{3,}/g, '\n\n')
79
+ .trim();
80
+ }
81
+
82
+ class AssetManager {
83
+ constructor() {
84
+ this.projectRoot = null;
85
+ this.loopDir = null;
86
+ this.assetsDir = null;
87
+ this.bundledDir = BUNDLED_DIR;
88
+ this.registry = new Map(REGISTRY);
89
+ }
90
+
91
+ init(projectRoot) {
92
+ this.projectRoot = projectRoot || process.cwd();
93
+ this.loopDir = path.join(this.projectRoot, '.claude-coder');
94
+ this.assetsDir = path.join(this.loopDir, 'assets');
95
+ }
96
+
97
+ _ensureInit() {
98
+ if (!this.loopDir) this.init();
99
+ }
100
+
101
+ _fileSegments(file) {
102
+ return Array.isArray(file) ? file : [file];
103
+ }
104
+
105
+
106
+ path(name) {
107
+ this._ensureInit();
108
+ const entry = this.registry.get(name);
109
+ if (!entry) return null;
110
+ const segs = this._fileSegments(entry.file);
111
+ switch (entry.kind) {
112
+ case 'template': return this._resolveTemplate(segs);
113
+ case 'data': return path.join(this.loopDir, ...segs);
114
+ case 'runtime': return path.join(this.loopDir, '.runtime', ...segs);
115
+ case 'root': return path.join(this.projectRoot, ...segs);
116
+ default: return null;
117
+ }
118
+ }
119
+
120
+ _resolveTemplate(segments) {
121
+ if (this.assetsDir) {
122
+ const userPath = path.join(this.assetsDir, ...segments);
123
+ if (fs.existsSync(userPath)) return userPath;
124
+ }
125
+ const bundled = path.join(this.bundledDir, ...segments);
126
+ if (fs.existsSync(bundled)) return bundled;
127
+ return null;
128
+ }
129
+
130
+ dir(name) {
131
+ this._ensureInit();
132
+ const rel = DIRS.get(name);
133
+ if (rel === undefined) return null;
134
+ return rel === '' ? this.loopDir : path.join(this.loopDir, rel);
135
+ }
136
+
137
+ exists(name) {
138
+ const p = this.path(name);
139
+ return p ? fs.existsSync(p) : false;
140
+ }
141
+
142
+ read(name) {
143
+ this._ensureInit();
144
+ const entry = this.registry.get(name);
145
+ if (!entry) return null;
146
+
147
+ const filePath = this.path(name);
148
+ if (!filePath || !fs.existsSync(filePath)) return entry.kind === 'template' ? '' : null;
149
+ return fs.readFileSync(filePath, 'utf8');
150
+ }
151
+
152
+ readJson(name, fallback = null) {
153
+ const content = this.read(name);
154
+ if (content === null || content === '') return fallback;
155
+ try {
156
+ return JSON.parse(content);
157
+ } catch {
158
+ return fallback;
159
+ }
160
+ }
161
+
162
+ write(name, content) {
163
+ this._ensureInit();
164
+ const entry = this.registry.get(name);
165
+ if (!entry || entry.kind === 'template') return;
166
+ const filePath = this.path(name);
167
+ if (!filePath) return;
168
+ const dir = path.dirname(filePath);
169
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
170
+ fs.writeFileSync(filePath, content, 'utf8');
171
+ }
172
+
173
+ writeJson(name, data) {
174
+ this.write(name, JSON.stringify(data, null, 2) + '\n');
175
+ }
176
+
177
+ render(name, vars = {}) {
178
+ const raw = this.read(name);
179
+ if (!raw) return '';
180
+ return renderTemplate(raw, vars);
181
+ }
182
+
183
+ ensureDirs() {
184
+ this._ensureInit();
185
+ for (const [, rel] of DIRS) {
186
+ const dir = rel === '' ? this.loopDir : path.join(this.loopDir, rel);
187
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
188
+ }
189
+ }
190
+
191
+ deployAll() {
192
+ this._ensureInit();
193
+ if (!fs.existsSync(this.assetsDir)) {
194
+ fs.mkdirSync(this.assetsDir, { recursive: true });
195
+ }
196
+ const deployed = [];
197
+ const walk = (srcBase, destBase) => {
198
+ const entries = fs.readdirSync(srcBase, { withFileTypes: true });
199
+ for (const entry of entries) {
200
+ const srcPath = path.join(srcBase, entry.name);
201
+ const destPath = path.join(destBase, entry.name);
202
+ if (entry.isDirectory()) {
203
+ if (!fs.existsSync(destPath)) fs.mkdirSync(destPath, { recursive: true });
204
+ walk(srcPath, destPath);
205
+ } else {
206
+ if (fs.existsSync(destPath)) continue;
207
+ try {
208
+ fs.copyFileSync(srcPath, destPath);
209
+ deployed.push(path.relative(this.assetsDir, destPath));
210
+ } catch { /* skip */ }
211
+ }
212
+ }
213
+ };
214
+ walk(this.bundledDir, this.assetsDir);
215
+ return deployed;
216
+ }
217
+
218
+ deployRecipes() {
219
+ this._ensureInit();
220
+ const destDir = path.join(this.loopDir, 'recipes');
221
+ if (!fs.existsSync(BUNDLED_RECIPES_DIR)) return [];
222
+ if (!fs.existsSync(destDir)) fs.mkdirSync(destDir, { recursive: true });
223
+
224
+ const deployed = [];
225
+ const walk = (srcBase, destBase) => {
226
+ const entries = fs.readdirSync(srcBase, { withFileTypes: true });
227
+ for (const entry of entries) {
228
+ const srcPath = path.join(srcBase, entry.name);
229
+ const destPath = path.join(destBase, entry.name);
230
+ if (entry.isDirectory()) {
231
+ if (!fs.existsSync(destPath)) fs.mkdirSync(destPath, { recursive: true });
232
+ walk(srcPath, destPath);
233
+ } else {
234
+ if (fs.existsSync(destPath)) continue;
235
+ try {
236
+ fs.copyFileSync(srcPath, destPath);
237
+ deployed.push(path.relative(destDir, destPath));
238
+ } catch { /* skip */ }
239
+ }
240
+ }
241
+ };
242
+ walk(BUNDLED_RECIPES_DIR, destDir);
243
+ return deployed;
244
+ }
245
+
246
+ recipesDir() {
247
+ this._ensureInit();
248
+ const projectRecipes = path.join(this.loopDir, 'recipes');
249
+ if (fs.existsSync(projectRecipes) && fs.readdirSync(projectRecipes).length > 0) {
250
+ return projectRecipes;
251
+ }
252
+ return BUNDLED_RECIPES_DIR;
253
+ }
254
+
255
+ }
256
+
257
+ const assets = new AssetManager();
258
+
259
+ module.exports = { AssetManager, assets, renderTemplate };