@timeax/scaffold 0.0.10 → 0.0.12
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/.idea/prettier.xml +6 -0
- package/dist/cli.cjs +37 -29
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.mjs +37 -29
- package/dist/cli.mjs.map +1 -1
- package/dist/index.cjs +21 -7
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +21 -7
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/core/apply-structure.ts +306 -255
- package/src/core/watcher.ts +29 -32
package/package.json
CHANGED
|
@@ -1,255 +1,306 @@
|
|
|
1
|
-
// src/core/apply-structure.ts
|
|
2
|
-
|
|
3
|
-
import fs from
|
|
4
|
-
import path from
|
|
5
|
-
import type {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
} from
|
|
12
|
-
import { CacheManager } from
|
|
13
|
-
import { HookRunner } from
|
|
14
|
-
import {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
} from
|
|
20
|
-
import type { Logger } from
|
|
21
|
-
import { defaultLogger } from
|
|
22
|
-
|
|
23
|
-
export interface InteractiveDeleteParams {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export interface ApplyOptions {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
export async function applyStructure(opts: ApplyOptions): Promise<void> {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
)
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
1
|
+
// src/core/apply-structure.ts
|
|
2
|
+
|
|
3
|
+
import fs from "fs";
|
|
4
|
+
import path from "path";
|
|
5
|
+
import type {
|
|
6
|
+
ScaffoldConfig,
|
|
7
|
+
StructureEntry,
|
|
8
|
+
FileEntry,
|
|
9
|
+
DirEntry,
|
|
10
|
+
HookContext,
|
|
11
|
+
} from "../schema";
|
|
12
|
+
import { CacheManager } from "./cache-manager";
|
|
13
|
+
import { HookRunner } from "./hook-runner";
|
|
14
|
+
import {
|
|
15
|
+
ensureDirSync,
|
|
16
|
+
statSafeSync,
|
|
17
|
+
toProjectRelativePath,
|
|
18
|
+
toPosixPath,
|
|
19
|
+
} from "../util/fs-utils";
|
|
20
|
+
import type { Logger } from "../util/logger";
|
|
21
|
+
import { defaultLogger } from "../util/logger";
|
|
22
|
+
|
|
23
|
+
export interface InteractiveDeleteParams {
|
|
24
|
+
absolutePath: string;
|
|
25
|
+
relativePath: string; // project-root relative, POSIX
|
|
26
|
+
size: number;
|
|
27
|
+
createdByStub?: string;
|
|
28
|
+
groupName?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface ApplyOptions {
|
|
32
|
+
config: ScaffoldConfig;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Global project root for this run (absolute or relative to CWD).
|
|
36
|
+
*/
|
|
37
|
+
projectRoot: string;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Absolute directory where this structure group should be applied.
|
|
41
|
+
* For grouped mode, this is projectRoot + group.root.
|
|
42
|
+
* For single mode, this will simply be projectRoot.
|
|
43
|
+
*/
|
|
44
|
+
baseDir: string;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Which structure entries to apply (already resolved from txt or inline).
|
|
48
|
+
*/
|
|
49
|
+
structure: StructureEntry[];
|
|
50
|
+
|
|
51
|
+
cache: CacheManager;
|
|
52
|
+
hooks: HookRunner;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Optional group metadata (only set for groups).
|
|
56
|
+
*/
|
|
57
|
+
groupName?: string;
|
|
58
|
+
groupRoot?: string;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Optional override for deletion threshold.
|
|
62
|
+
* Falls back to config.sizePromptThreshold or internal default.
|
|
63
|
+
*/
|
|
64
|
+
sizePromptThreshold?: number;
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Optional interactive delete callback.
|
|
68
|
+
* Should ask the user and return 'delete' or 'keep'.
|
|
69
|
+
*/
|
|
70
|
+
interactiveDelete?: (
|
|
71
|
+
params: InteractiveDeleteParams,
|
|
72
|
+
) => Promise<"delete" | "keep">;
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Optional logger; defaults to defaultLogger.child('[apply]').
|
|
76
|
+
*/
|
|
77
|
+
logger?: Logger;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export async function applyStructure(opts: ApplyOptions): Promise<void> {
|
|
81
|
+
const {
|
|
82
|
+
config,
|
|
83
|
+
projectRoot,
|
|
84
|
+
baseDir,
|
|
85
|
+
structure,
|
|
86
|
+
cache,
|
|
87
|
+
hooks,
|
|
88
|
+
groupName,
|
|
89
|
+
groupRoot,
|
|
90
|
+
sizePromptThreshold,
|
|
91
|
+
interactiveDelete,
|
|
92
|
+
} = opts;
|
|
93
|
+
|
|
94
|
+
const logger =
|
|
95
|
+
opts.logger ??
|
|
96
|
+
defaultLogger.child(groupName ? `[apply:${groupName}]` : "[apply]");
|
|
97
|
+
|
|
98
|
+
// Normalize roots to absolute, consistent paths
|
|
99
|
+
const projectRootAbs = path.resolve(projectRoot);
|
|
100
|
+
const baseDirAbs = path.resolve(baseDir);
|
|
101
|
+
|
|
102
|
+
// Helper for “is this absolute path inside this baseDir?”
|
|
103
|
+
const baseDirAbsWithSep = baseDirAbs.endsWith(path.sep)
|
|
104
|
+
? baseDirAbs
|
|
105
|
+
: baseDirAbs + path.sep;
|
|
106
|
+
|
|
107
|
+
function isUnderBaseDir(absPath: string): boolean {
|
|
108
|
+
const norm = path.resolve(absPath);
|
|
109
|
+
return norm === baseDirAbs || norm.startsWith(baseDirAbsWithSep);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const desiredPaths = new Set<string>(); // project-root relative, POSIX
|
|
113
|
+
|
|
114
|
+
const threshold =
|
|
115
|
+
sizePromptThreshold ?? config.sizePromptThreshold ?? 128 * 1024;
|
|
116
|
+
|
|
117
|
+
async function walk(
|
|
118
|
+
entry: StructureEntry,
|
|
119
|
+
inheritedStub?: string,
|
|
120
|
+
): Promise<void> {
|
|
121
|
+
const effectiveStub = entry.stub ?? inheritedStub;
|
|
122
|
+
if (entry.type === "dir") {
|
|
123
|
+
await handleDir(entry as DirEntry, effectiveStub);
|
|
124
|
+
} else {
|
|
125
|
+
await handleFile(entry as FileEntry, effectiveStub);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
async function handleDir(
|
|
130
|
+
entry: DirEntry,
|
|
131
|
+
inheritedStub?: string,
|
|
132
|
+
): Promise<void> {
|
|
133
|
+
const relFromBase = entry.path.replace(/^[./]+/, "");
|
|
134
|
+
const absDir = path.resolve(baseDirAbs, relFromBase);
|
|
135
|
+
const relFromRoot = toPosixPath(
|
|
136
|
+
toProjectRelativePath(projectRootAbs, absDir),
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
desiredPaths.add(relFromRoot);
|
|
140
|
+
|
|
141
|
+
ensureDirSync(absDir);
|
|
142
|
+
|
|
143
|
+
const nextStub = entry.stub ?? inheritedStub;
|
|
144
|
+
|
|
145
|
+
if (entry.children) {
|
|
146
|
+
for (const child of entry.children) {
|
|
147
|
+
// eslint-disable-next-line no-await-in-loop
|
|
148
|
+
await walk(child, nextStub);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async function handleFile(
|
|
154
|
+
entry: FileEntry,
|
|
155
|
+
inheritedStub?: string,
|
|
156
|
+
): Promise<void> {
|
|
157
|
+
const relFromBase = entry.path.replace(/^[./]+/, "");
|
|
158
|
+
const absFile = path.resolve(baseDirAbs, relFromBase);
|
|
159
|
+
const relFromRoot = toPosixPath(
|
|
160
|
+
toProjectRelativePath(projectRootAbs, absFile),
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
desiredPaths.add(relFromRoot);
|
|
164
|
+
|
|
165
|
+
const stubName = entry.stub ?? inheritedStub;
|
|
166
|
+
|
|
167
|
+
const ctx: HookContext = {
|
|
168
|
+
projectRoot: projectRootAbs,
|
|
169
|
+
targetPath: relFromRoot,
|
|
170
|
+
absolutePath: absFile,
|
|
171
|
+
isDirectory: false,
|
|
172
|
+
stubName,
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
// If file already exists, do not overwrite; just ensure hooks (later we might
|
|
176
|
+
// add an "onExistingFile" hook, but right now we simply skip creation).
|
|
177
|
+
if (fs.existsSync(absFile)) {
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
await hooks.runRegular("preCreateFile", ctx);
|
|
182
|
+
|
|
183
|
+
const dir = path.dirname(absFile);
|
|
184
|
+
ensureDirSync(dir);
|
|
185
|
+
|
|
186
|
+
if (stubName) {
|
|
187
|
+
await hooks.runStub("preStub", ctx);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
let content = "";
|
|
191
|
+
const stubContent = await hooks.renderStubContent(ctx);
|
|
192
|
+
if (typeof stubContent === "string") {
|
|
193
|
+
content = stubContent;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
fs.writeFileSync(absFile, content, "utf8");
|
|
197
|
+
const stats = fs.statSync(absFile);
|
|
198
|
+
|
|
199
|
+
cache.set({
|
|
200
|
+
path: relFromRoot,
|
|
201
|
+
createdAt: new Date().toISOString(),
|
|
202
|
+
sizeAtCreate: stats.size,
|
|
203
|
+
createdByStub: stubName,
|
|
204
|
+
groupName,
|
|
205
|
+
groupRoot,
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
logger.info(`created ${relFromRoot}`);
|
|
209
|
+
|
|
210
|
+
if (stubName) {
|
|
211
|
+
await hooks.runStub("postStub", ctx);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
await hooks.runRegular("postCreateFile", ctx);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// 1) Create/update from structure
|
|
218
|
+
for (const entry of structure) {
|
|
219
|
+
// eslint-disable-next-line no-await-in-loop
|
|
220
|
+
await walk(entry);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// 2) Handle deletions: any cached path not in desiredPaths
|
|
224
|
+
//
|
|
225
|
+
// IMPORTANT:
|
|
226
|
+
// We *only* consider cached files that live under this run's baseDir.
|
|
227
|
+
// This prevents group A from deleting files owned by group B when
|
|
228
|
+
// applyStructure is called multiple times with different baseDir values.
|
|
229
|
+
// 2) Handle deletions: any cached path not in desiredPaths
|
|
230
|
+
for (const cachedPath of cache.allPaths()) {
|
|
231
|
+
const entry = cache.get(cachedPath);
|
|
232
|
+
|
|
233
|
+
// Group-aware deletion:
|
|
234
|
+
// - If we're in a group, only touch entries for this group.
|
|
235
|
+
// - If we're in single-root mode (no groupName), only touch entries
|
|
236
|
+
// that also have no groupName (legacy / single-structure runs).
|
|
237
|
+
if (groupName) {
|
|
238
|
+
if (!entry || entry.groupName !== groupName) {
|
|
239
|
+
continue;
|
|
240
|
+
}
|
|
241
|
+
} else {
|
|
242
|
+
if (entry && entry.groupName) {
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// If this path is still desired within this group, skip it.
|
|
248
|
+
if (desiredPaths.has(cachedPath)) {
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const abs = path.resolve(projectRoot, cachedPath);
|
|
253
|
+
const stats = statSafeSync(abs);
|
|
254
|
+
|
|
255
|
+
if (!stats) {
|
|
256
|
+
// File disappeared on disk – just clean cache.
|
|
257
|
+
cache.delete(cachedPath);
|
|
258
|
+
continue;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Only handle files here; dirs are not tracked in cache.
|
|
262
|
+
if (!stats.isFile()) {
|
|
263
|
+
cache.delete(cachedPath);
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const ctx: HookContext = {
|
|
268
|
+
projectRoot,
|
|
269
|
+
targetPath: cachedPath,
|
|
270
|
+
absolutePath: abs,
|
|
271
|
+
isDirectory: false,
|
|
272
|
+
stubName: entry?.createdByStub,
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
await hooks.runRegular("preDeleteFile", ctx);
|
|
276
|
+
|
|
277
|
+
let shouldDelete = true;
|
|
278
|
+
if (stats.size > threshold && interactiveDelete) {
|
|
279
|
+
const res = await interactiveDelete({
|
|
280
|
+
absolutePath: abs,
|
|
281
|
+
relativePath: cachedPath,
|
|
282
|
+
size: stats.size,
|
|
283
|
+
createdByStub: entry?.createdByStub,
|
|
284
|
+
groupName: entry?.groupName,
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
if (res === "keep") {
|
|
288
|
+
shouldDelete = false;
|
|
289
|
+
cache.delete(cachedPath); // user takes ownership
|
|
290
|
+
logger.info(`keeping ${cachedPath} (removed from cache)`);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
if (shouldDelete) {
|
|
295
|
+
try {
|
|
296
|
+
fs.unlinkSync(abs);
|
|
297
|
+
logger.info(`deleted ${cachedPath}`);
|
|
298
|
+
} catch (err) {
|
|
299
|
+
logger.warn(`failed to delete ${cachedPath}`, err);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
cache.delete(cachedPath);
|
|
303
|
+
await hooks.runRegular("postDeleteFile", ctx);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
package/src/core/watcher.ts
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
import path from 'path';
|
|
4
4
|
import chokidar from 'chokidar';
|
|
5
|
-
import {runOnce, type RunOptions} from './runner';
|
|
6
|
-
import {defaultLogger, type Logger} from '../util/logger';
|
|
7
|
-
import {SCAFFOLD_ROOT_DIR} from '..';
|
|
5
|
+
import { runOnce, type RunOptions } from './runner';
|
|
6
|
+
import { defaultLogger, type Logger } from '../util/logger';
|
|
7
|
+
import { SCAFFOLD_ROOT_DIR } from '..';
|
|
8
8
|
|
|
9
9
|
export interface WatchOptions extends RunOptions {
|
|
10
10
|
/**
|
|
@@ -24,11 +24,11 @@ export interface WatchOptions extends RunOptions {
|
|
|
24
24
|
/**
|
|
25
25
|
* Watch the scaffold directory and re-run scaffold on changes.
|
|
26
26
|
*
|
|
27
|
-
* This watches
|
|
28
|
-
* -
|
|
29
|
-
*
|
|
27
|
+
* This watches the entire .scaffold folder and then filters events
|
|
28
|
+
* in-process to:
|
|
29
|
+
* - config.* files
|
|
30
|
+
* - *.txt / *.tss / *.stx
|
|
30
31
|
*
|
|
31
|
-
* CLI can call this when `--watch` is enabled.
|
|
32
32
|
* Any `format` options in RunOptions are passed straight through to `runOnce`,
|
|
33
33
|
* so formatting from config / CLI is applied on each re-run.
|
|
34
34
|
*/
|
|
@@ -60,7 +60,7 @@ export function watchScaffold(cwd: string, options: WatchOptions = {}): void {
|
|
|
60
60
|
// we already resolved scaffoldDir for watcher; pass it down
|
|
61
61
|
scaffoldDir,
|
|
62
62
|
});
|
|
63
|
-
logger.info('Scaffold run completed
|
|
63
|
+
logger.info('Scaffold run completed');
|
|
64
64
|
} catch (err) {
|
|
65
65
|
logger.error('Scaffold run failed:', err);
|
|
66
66
|
} finally {
|
|
@@ -77,32 +77,29 @@ export function watchScaffold(cwd: string, options: WatchOptions = {}): void {
|
|
|
77
77
|
timer = setTimeout(run, debounceMs);
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
80
|
+
// Only react to config.* and structure files inside scaffoldDir
|
|
81
|
+
function isInteresting(filePath: string): boolean {
|
|
82
|
+
const rel = path.relative(scaffoldDir, filePath);
|
|
83
|
+
// Outside .scaffold or in parent → ignore
|
|
84
|
+
if (rel.startsWith('..')) return false;
|
|
85
|
+
|
|
86
|
+
const base = path.basename(filePath).toLowerCase();
|
|
87
|
+
// config.ts / config.js / config.mts / etc.
|
|
88
|
+
if (base.startsWith('config.')) return true;
|
|
89
|
+
|
|
90
|
+
const ext = path.extname(base);
|
|
91
|
+
return ext === '.txt' || ext === '.tss' || ext === '.stx';
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const watcher = chokidar.watch(scaffoldDir, {
|
|
95
|
+
ignoreInitial: false,
|
|
96
|
+
persistent: true,
|
|
97
|
+
});
|
|
94
98
|
|
|
95
99
|
watcher
|
|
96
|
-
.on('
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
})
|
|
100
|
-
.on('change', (filePath) => {
|
|
101
|
-
logger.debug(`File changed: ${filePath}`);
|
|
102
|
-
scheduleRun();
|
|
103
|
-
})
|
|
104
|
-
.on('unlink', (filePath) => {
|
|
105
|
-
logger.debug(`File removed: ${filePath}`);
|
|
100
|
+
.on('all', (event, filePath) => {
|
|
101
|
+
if (!isInteresting(filePath)) return;
|
|
102
|
+
logger.debug(`Event ${event} on ${filePath}`);
|
|
106
103
|
scheduleRun();
|
|
107
104
|
})
|
|
108
105
|
.on('error', (error) => {
|