pugkit 1.0.2 → 1.1.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.
package/cli/develop.mjs CHANGED
@@ -2,7 +2,7 @@ import { createBuilder } from '../index.mjs'
2
2
  import { logger } from './logger.mjs'
3
3
 
4
4
  export async function develop(options = {}) {
5
- const { root = process.cwd(), port, host, open } = options
5
+ const { root = process.cwd(), port, host } = options
6
6
 
7
7
  logger.info('pugkit', 'starting dev server...')
8
8
 
@@ -10,7 +10,6 @@ export async function develop(options = {}) {
10
10
 
11
11
  if (port) builder.context.config.server.port = port
12
12
  if (host) builder.context.config.server.host = host
13
- if (open) builder.context.config.server.open = open
14
13
 
15
14
  await builder.watch()
16
15
  }
package/cli/logger.mjs CHANGED
@@ -6,26 +6,22 @@ import path from 'node:path'
6
6
  const __filename = fileURLToPath(import.meta.url)
7
7
  const __dirname = path.dirname(__filename)
8
8
 
9
- function getVersion() {
10
- const pkgPath = path.join(__dirname, '../package.json')
11
- const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'))
12
- return pkg.version
13
- }
9
+ const version = JSON.parse(readFileSync(path.join(__dirname, '../package.json'), 'utf8')).version
14
10
 
15
11
  export const logger = {
16
12
  info(name, message) {
17
- console.log(`${pc.cyan(pc.bold(name.toUpperCase()))} ${pc.dim(`v${getVersion()}`)} ${message}`)
13
+ console.log(`${pc.cyan(pc.bold(name.toUpperCase()))} ${pc.dim(`v${version}`)} ${message}`)
18
14
  },
19
15
 
20
16
  success(name, message) {
21
- console.log(`${pc.cyan(pc.bold(name.toUpperCase()))} ${pc.dim(`v${getVersion()}`)} ${message}`)
17
+ console.log(`${pc.cyan(pc.bold(name.toUpperCase()))} ${pc.dim(`v${version}`)} ${message}`)
22
18
  },
23
19
 
24
20
  warn(name, message) {
25
- console.log(`${pc.cyan(pc.bold(name.toUpperCase()))} ${pc.dim(`v${getVersion()}`)} ${message}`)
21
+ console.log(`${pc.cyan(pc.bold(name.toUpperCase()))} ${pc.dim(`v${version}`)} ${message}`)
26
22
  },
27
23
 
28
24
  error(name, message) {
29
- console.error(`${pc.cyan(pc.bold(name.toUpperCase()))} ${pc.dim(`v${getVersion()}`)} ${message}`)
25
+ console.error(`${pc.cyan(pc.bold(name.toUpperCase()))} ${pc.dim(`v${version}`)} ${message}`)
30
26
  }
31
27
  }
@@ -5,8 +5,7 @@ export const defaultConfig = {
5
5
  server: {
6
6
  port: 5555,
7
7
  host: 'localhost',
8
- startPath: '/',
9
- open: false
8
+ startPath: '/'
10
9
  },
11
10
  build: {
12
11
  imageOptimization: 'webp',
package/core/cache.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import { createHash } from 'node:crypto'
2
- import { stat, readFile } from 'node:fs/promises'
2
+ import { readFile } from 'node:fs/promises'
3
3
  import { existsSync } from 'node:fs'
4
4
 
5
5
  /**
@@ -76,13 +76,12 @@ export class CacheManager {
76
76
  }
77
77
 
78
78
  /**
79
- * ハッシュ計算(ファイル内容 + 更新日時)
79
+ * ハッシュ計算(ファイル内容のみ)
80
80
  */
81
81
  async computeHash(filePath) {
82
82
  try {
83
- const [stats, content] = await Promise.all([stat(filePath), readFile(filePath)])
84
-
85
- return createHash('md5').update(content).update(stats.mtime.toISOString()).digest('hex')
83
+ const content = await readFile(filePath)
84
+ return createHash('md5').update(content).digest('hex')
86
85
  } catch {
87
86
  return `error-${Math.random()}`
88
87
  }
package/core/watcher.mjs CHANGED
@@ -17,7 +17,7 @@ export async function watcherTask(context, options = {}) {
17
17
  class FileWatcher {
18
18
  constructor(context) {
19
19
  this.context = context
20
- this.watchers = []
20
+ this.watcher = null
21
21
  }
22
22
 
23
23
  async start() {
@@ -29,327 +29,208 @@ class FileWatcher {
29
29
  await this.context.taskRegistry.pug(this.context)
30
30
  }
31
31
 
32
- // Pug監視
33
- this.watchPug(paths.src)
32
+ this.watcher = chokidar
33
+ .watch([paths.src, paths.public], {
34
+ ignoreInitial: true,
35
+ ignored: [/(^|[\/\\])\./, /node_modules/, /\.git/],
36
+ persistent: true,
37
+ awaitWriteFinish: { stabilityThreshold: 100, pollInterval: 50 }
38
+ })
39
+ .on('change', filePath => this.handleChange(filePath))
40
+ .on('add', filePath => this.handleAdd(filePath))
41
+ .on('unlink', filePath => this.handleUnlink(filePath))
34
42
 
35
- // Sass監視
36
- this.watchSass(paths.src)
43
+ logger.info('watch', 'File watching started')
44
+ }
37
45
 
38
- // Script監視
39
- this.watchScript(paths.src)
46
+ // ---- ルーティング ----
47
+ handleChange(filePath) {
48
+ if (this.isPublic(filePath)) return this.onPublicChange(filePath, 'change')
49
+ if (filePath.endsWith('.pug')) return this.onPugChange(filePath)
50
+ if (filePath.endsWith('.scss')) return this.onSassChange(filePath)
51
+ if (this.isScript(filePath)) return this.onScriptChange(filePath)
52
+ if (filePath.endsWith('.svg') && !this.isIcons(filePath)) return this.onSvgChange(filePath, 'change')
53
+ if (/\.(jpg|jpeg|png|gif)$/i.test(filePath)) return this.onImageChange(filePath, 'change')
54
+ }
40
55
 
41
- // SVG監視
42
- this.watchSvg(paths.src)
56
+ handleAdd(filePath) {
57
+ if (this.isPublic(filePath)) return this.onPublicChange(filePath, 'add')
58
+ if (filePath.endsWith('.svg') && !this.isIcons(filePath)) return this.onSvgChange(filePath, 'add')
59
+ if (/\.(jpg|jpeg|png|gif)$/i.test(filePath)) return this.onImageChange(filePath, 'add')
60
+ }
43
61
 
44
- // 画像監視
45
- this.watchImages(paths.src)
62
+ handleUnlink(filePath) {
63
+ if (this.isPublic(filePath)) return this.onPublicUnlink(filePath)
64
+ if (filePath.endsWith('.pug')) return this.onPugUnlink(filePath)
65
+ if (filePath.endsWith('.scss')) return this.onSassUnlink(filePath)
66
+ if (this.isScript(filePath)) return this.onScriptUnlink(filePath)
67
+ if (filePath.endsWith('.svg') && !this.isIcons(filePath)) return this.onSvgUnlink(filePath)
68
+ if (/\.(jpg|jpeg|png|gif)$/i.test(filePath)) return this.onImageUnlink(filePath)
69
+ }
46
70
 
47
- // Public監視
48
- this.watchPublic(paths.public)
71
+ // ---- 判定ヘルパー ----
49
72
 
50
- logger.info('watch', 'File watching started')
73
+ isPublic(filePath) {
74
+ return filePath.startsWith(this.context.paths.public)
51
75
  }
52
76
 
53
- /**
54
- * Pug監視(依存関係を考慮)
55
- */
56
- watchPug(basePath) {
57
- const watcher = chokidar.watch(basePath, {
58
- ignoreInitial: true,
59
- ignored: [
60
- /(^|[\/\\])\../, // 隠しファイル
61
- /node_modules/,
62
- /\.git/
63
- ],
64
- persistent: true,
65
- awaitWriteFinish: {
66
- stabilityThreshold: 100,
67
- pollInterval: 50
68
- }
69
- })
70
-
71
- watcher.on('change', async path => {
72
- // .pugファイルのみ処理
73
- if (!path.endsWith('.pug')) {
74
- return
75
- }
76
- const relPath = relative(basePath, path)
77
- logger.info('change', `pug: ${relPath}`)
78
-
79
- try {
80
- // パーシャルの場合、依存する親ファイルも再ビルド
81
- const affectedFiles = this.context.graph.getAffectedParents(path)
82
-
83
- if (affectedFiles.length > 0) {
84
- logger.info('pug', `Rebuilding ${affectedFiles.length} affected file(s)`)
85
- }
86
-
87
- // キャッシュ無効化
88
- this.context.cache.invalidatePugTemplate(path)
89
- affectedFiles.forEach(f => this.context.cache.invalidatePugTemplate(f))
90
-
91
- // Pugタスクを実行(変更されたファイルのみ)
92
- if (this.context.taskRegistry?.pug) {
93
- await this.context.taskRegistry.pug(this.context, {
94
- files: [path, ...affectedFiles]
95
- })
96
- }
97
-
98
- this.reload()
99
- } catch (error) {
100
- logger.error('watch', `Pug build failed: ${error.message}`)
101
- }
102
- })
77
+ isScript(filePath) {
78
+ return (filePath.endsWith('.ts') || filePath.endsWith('.js')) && !filePath.endsWith('.d.ts')
79
+ }
103
80
 
104
- watcher.on('unlink', async path => {
105
- if (!path.endsWith('.pug')) return
106
- const relPath = relative(basePath, path)
81
+ isIcons(filePath) {
82
+ // Windows 対応: セパレータを正規化
83
+ return filePath.replace(/\\/g, '/').includes('/icons/')
84
+ }
107
85
 
108
- // キャッシュとグラフをクリア
109
- this.context.cache.invalidatePugTemplate(path)
110
- this.context.graph.clearDependencies(path)
86
+ // ---- Pug ----
111
87
 
112
- if (basename(path).startsWith('_')) {
113
- logger.info('unlink', relPath)
114
- return
88
+ async onPugChange(filePath) {
89
+ const { paths, graph, cache, taskRegistry } = this.context
90
+ const relPath = relative(paths.src, filePath)
91
+ logger.info('change', `pug: ${relPath}`)
92
+ try {
93
+ const affectedFiles = graph.getAffectedParents(filePath)
94
+ if (affectedFiles.length > 0) logger.info('pug', `Rebuilding ${affectedFiles.length} affected file(s)`)
95
+ cache.invalidatePugTemplate(filePath)
96
+ affectedFiles.forEach(f => cache.invalidatePugTemplate(f))
97
+ if (taskRegistry?.pug) {
98
+ await taskRegistry.pug(this.context, { files: [filePath, ...affectedFiles] })
115
99
  }
116
- const distPath = resolve(this.context.paths.dist, relPath.replace(/\.pug$/, '.html'))
117
- await this.deleteDistFile(distPath, relPath)
118
- })
119
-
120
- this.watchers.push(watcher)
100
+ this.reload()
101
+ } catch (error) {
102
+ logger.error('watch', `Pug build failed: ${error.message}`)
103
+ }
121
104
  }
122
105
 
123
- /**
124
- * Sass監視
125
- */
126
- watchSass(basePath) {
127
- const watcher = chokidar.watch(basePath, {
128
- ignoreInitial: true,
129
- ignored: [/(^|[\/\\])\../, /node_modules/, /\.git/],
130
- persistent: true,
131
- awaitWriteFinish: { stabilityThreshold: 50, pollInterval: 25 }
132
- })
133
-
134
- watcher.on('change', async path => {
135
- // .scssファイルのみ処理
136
- if (!path.endsWith('.scss')) {
137
- return
138
- }
139
- const relPath = relative(basePath, path)
140
- logger.info('change', `sass: ${relPath}`)
141
-
142
- try {
143
- // Sassタスクを実行
144
- if (this.context.taskRegistry?.sass) {
145
- await this.context.taskRegistry.sass(this.context)
146
- }
147
-
148
- this.injectCSS()
149
- } catch (error) {
150
- logger.error('watch', `Sass build failed: ${error.message}`)
151
- }
152
- })
153
-
154
- watcher.on('unlink', async path => {
155
- if (!path.endsWith('.scss')) return
156
- const relPath = relative(basePath, path)
157
- if (basename(path).startsWith('_')) {
158
- logger.info('unlink', relPath)
159
- return
160
- }
161
- const distPath = resolve(this.context.paths.dist, relPath.replace(/\.scss$/, '.css'))
162
- await this.deleteDistFile(distPath, relPath)
163
- })
164
-
165
- this.watchers.push(watcher)
106
+ async onPugUnlink(filePath) {
107
+ const { paths, cache, graph } = this.context
108
+ const relPath = relative(paths.src, filePath)
109
+ cache.invalidatePugTemplate(filePath)
110
+ graph.clearDependencies(filePath)
111
+ if (basename(filePath).startsWith('_')) {
112
+ logger.info('unlink', relPath)
113
+ return
114
+ }
115
+ const distPath = resolve(paths.dist, relPath.replace(/\.pug$/, '.html'))
116
+ await this.deleteDistFile(distPath, relPath)
166
117
  }
167
118
 
168
- /**
169
- * Script監視
170
- */
171
- watchScript(basePath) {
172
- const watcher = chokidar.watch(basePath, {
173
- ignoreInitial: true,
174
- ignored: [/(^|[\/\\])\../, /node_modules/, /\.git/, /\.d\.ts$/],
175
- persistent: true,
176
- awaitWriteFinish: { stabilityThreshold: 100, pollInterval: 50 }
177
- })
178
-
179
- watcher.on('change', async path => {
180
- // .ts/.jsファイルのみ処理(.d.tsを除く)
181
- if (!(path.endsWith('.ts') || path.endsWith('.js')) || path.endsWith('.d.ts')) {
182
- return
183
- }
184
- const relPath = relative(basePath, path)
185
- logger.info('change', `script: ${relPath}`)
186
-
187
- try {
188
- // Scriptタスクを実行
189
- if (this.context.taskRegistry?.script) {
190
- await this.context.taskRegistry.script(this.context)
191
- }
192
-
193
- this.reload()
194
- } catch (error) {
195
- logger.error('watch', `Script build failed: ${error.message}`)
196
- }
197
- })
198
-
199
- watcher.on('unlink', async path => {
200
- if (!(path.endsWith('.ts') || path.endsWith('.js')) || path.endsWith('.d.ts')) return
201
- const relPath = relative(basePath, path)
202
- if (basename(path).startsWith('_')) {
203
- logger.info('unlink', relPath)
204
- return
205
- }
206
- const distPath = resolve(this.context.paths.dist, relPath.replace(/\.ts$/, '.js'))
207
- await this.deleteDistFile(distPath, relPath)
208
- })
119
+ // ---- Sass ----
209
120
 
210
- this.watchers.push(watcher)
121
+ async onSassChange(filePath) {
122
+ const relPath = relative(this.context.paths.src, filePath)
123
+ logger.info('change', `sass: ${relPath}`)
124
+ try {
125
+ if (this.context.taskRegistry?.sass) await this.context.taskRegistry.sass(this.context)
126
+ this.injectCSS()
127
+ } catch (error) {
128
+ logger.error('watch', `Sass build failed: ${error.message}`)
129
+ }
211
130
  }
212
131
 
213
- /**
214
- * SVG監視
215
- */
216
- watchSvg(basePath) {
217
- const watcher = chokidar.watch(basePath, {
218
- ignoreInitial: true,
219
- ignored: [/(^|[\/\\])\../, /node_modules/, /\.git/, /icons/], // iconsはスプライト用なので除外
220
- persistent: true,
221
- awaitWriteFinish: { stabilityThreshold: 100, pollInterval: 50 }
222
- })
223
-
224
- const handleSvgChange = async (path, event) => {
225
- // .svgファイルのみ処理(iconsディレクトリは除外)
226
- // Windows対応: パスセパレータを正規化
227
- const normalizedPath = path.replace(/\\/g, '/')
228
- if (!path.endsWith('.svg') || normalizedPath.includes('/icons/')) {
229
- return
230
- }
231
- const relPath = relative(basePath, path)
232
- logger.info(event, `svg: ${relPath}`)
233
-
234
- try {
235
- // SVGタスクを実行(変更されたファイルのみ)
236
- if (this.context.taskRegistry?.svg) {
237
- await this.context.taskRegistry.svg(this.context, {
238
- files: [path]
239
- })
240
- }
241
-
242
- this.reload()
243
- } catch (error) {
244
- logger.error('watch', `SVG processing failed: ${error.message}`)
245
- }
132
+ async onSassUnlink(filePath) {
133
+ const { paths } = this.context
134
+ const relPath = relative(paths.src, filePath)
135
+ if (basename(filePath).startsWith('_')) {
136
+ logger.info('unlink', relPath)
137
+ return
246
138
  }
139
+ const distPath = resolve(paths.dist, relPath.replace(/\.scss$/, '.css'))
140
+ await this.deleteDistFile(distPath, relPath)
141
+ }
247
142
 
248
- watcher.on('change', path => handleSvgChange(path, 'change'))
249
- watcher.on('add', path => handleSvgChange(path, 'add'))
250
-
251
- watcher.on('unlink', async path => {
252
- const normalizedPath = path.replace(/\\/g, '/')
253
- if (!path.endsWith('.svg') || normalizedPath.includes('/icons/')) return
254
- const relPath = relative(basePath, path)
255
- const distPath = resolve(this.context.paths.dist, relPath)
256
- await this.deleteDistFile(distPath, relPath)
257
- })
143
+ // ---- Script ----
258
144
 
259
- this.watchers.push(watcher)
145
+ async onScriptChange(filePath) {
146
+ const relPath = relative(this.context.paths.src, filePath)
147
+ logger.info('change', `script: ${relPath}`)
148
+ try {
149
+ if (this.context.taskRegistry?.script) await this.context.taskRegistry.script(this.context)
150
+ this.reload()
151
+ } catch (error) {
152
+ logger.error('watch', `Script build failed: ${error.message}`)
153
+ }
260
154
  }
261
155
 
262
- /**
263
- * 画像監視
264
- */
265
- watchImages(basePath) {
266
- const watcher = chokidar.watch(basePath, {
267
- ignoreInitial: true,
268
- ignored: [/(^|[\/\\])\../, /node_modules/, /\.git/],
269
- persistent: true,
270
- awaitWriteFinish: { stabilityThreshold: 100, pollInterval: 50 }
271
- })
272
-
273
- const handleImageChange = async (path, event) => {
274
- // 画像ファイルのみ処理
275
- if (!/\.(jpg|jpeg|png|gif)$/i.test(path)) {
276
- return
277
- }
278
- const relPath = relative(basePath, path)
279
- logger.info(event, `image: ${relPath}`)
280
-
281
- try {
282
- // 追加・変更時: 画像を処理
283
- if (this.context.taskRegistry?.image) {
284
- await this.context.taskRegistry.image(this.context, {
285
- files: [path]
286
- })
287
- }
288
-
289
- this.reload()
290
- } catch (error) {
291
- logger.error('watch', `Image processing failed: ${error.message}`)
292
- }
156
+ async onScriptUnlink(filePath) {
157
+ const { paths } = this.context
158
+ const relPath = relative(paths.src, filePath)
159
+ if (basename(filePath).startsWith('_')) {
160
+ logger.info('unlink', relPath)
161
+ return
293
162
  }
163
+ const distPath = resolve(paths.dist, relPath.replace(/\.ts$/, '.js'))
164
+ await this.deleteDistFile(distPath, relPath)
165
+ }
294
166
 
295
- watcher.on('change', path => handleImageChange(path, 'change'))
296
- watcher.on('add', path => handleImageChange(path, 'add'))
167
+ // ---- SVG ----
297
168
 
298
- watcher.on('unlink', async path => {
299
- if (!/\.(jpg|jpeg|png|gif)$/i.test(path)) return
300
- const relPath = relative(basePath, path)
301
- const useWebp = this.context.config.build.imageOptimization === 'webp'
302
- const ext = extname(path)
303
- const destRelPath = useWebp ? relPath.replace(new RegExp(`\\${ext}$`, 'i'), '.webp') : relPath
304
- const distPath = resolve(this.context.paths.dist, destRelPath)
305
- await this.deleteDistFile(distPath, relPath)
306
- })
169
+ async onSvgChange(filePath, event) {
170
+ const relPath = relative(this.context.paths.src, filePath)
171
+ logger.info(event, `svg: ${relPath}`)
172
+ try {
173
+ if (this.context.taskRegistry?.svg) {
174
+ await this.context.taskRegistry.svg(this.context, { files: [filePath] })
175
+ }
176
+ this.reload()
177
+ } catch (error) {
178
+ logger.error('watch', `SVG processing failed: ${error.message}`)
179
+ }
180
+ }
307
181
 
308
- this.watchers.push(watcher)
182
+ async onSvgUnlink(filePath) {
183
+ const relPath = relative(this.context.paths.src, filePath)
184
+ const distPath = resolve(this.context.paths.dist, relPath)
185
+ await this.deleteDistFile(distPath, relPath)
309
186
  }
310
187
 
311
- /**
312
- * Public監視
313
- */
314
- watchPublic(basePath) {
315
- const watcher = chokidar.watch(basePath, {
316
- ignoreInitial: true,
317
- ignored: [/(^|[\/\\])\../, /node_modules/, /\.git/],
318
- persistent: true,
319
- awaitWriteFinish: { stabilityThreshold: 100, pollInterval: 50 }
320
- })
321
-
322
- const handlePublicChange = async (path, event) => {
323
- const relPath = relative(basePath, path)
324
- logger.info(event, `public: ${relPath}`)
325
-
326
- try {
327
- // Copyタスクを実行
328
- if (this.context.taskRegistry?.copy) {
329
- await this.context.taskRegistry.copy(this.context)
330
- }
331
-
332
- this.reload()
333
- } catch (error) {
334
- logger.error('watch', `Copy failed: ${error.message}`)
188
+ // ---- Image ----
189
+
190
+ async onImageChange(filePath, event) {
191
+ const relPath = relative(this.context.paths.src, filePath)
192
+ logger.info(event, `image: ${relPath}`)
193
+ try {
194
+ if (this.context.taskRegistry?.image) {
195
+ await this.context.taskRegistry.image(this.context, { files: [filePath] })
335
196
  }
197
+ this.reload()
198
+ } catch (error) {
199
+ logger.error('watch', `Image processing failed: ${error.message}`)
336
200
  }
201
+ }
337
202
 
338
- watcher.on('change', path => handlePublicChange(path, 'change'))
339
- watcher.on('add', path => handlePublicChange(path, 'add'))
203
+ async onImageUnlink(filePath) {
204
+ const { paths, config } = this.context
205
+ const relPath = relative(paths.src, filePath)
206
+ const useWebp = config.build.imageOptimization === 'webp'
207
+ const ext = extname(filePath)
208
+ const destRelPath = useWebp ? relPath.replace(new RegExp(`\\${ext}$`, 'i'), '.webp') : relPath
209
+ const distPath = resolve(paths.dist, destRelPath)
210
+ await this.deleteDistFile(distPath, relPath)
211
+ }
340
212
 
341
- watcher.on('unlink', async path => {
342
- const relPath = relative(basePath, path)
343
- const distPath = resolve(this.context.paths.dist, relPath)
344
- await this.deleteDistFile(distPath, relPath)
345
- })
213
+ // ---- Public ----
346
214
 
347
- this.watchers.push(watcher)
215
+ async onPublicChange(filePath, event) {
216
+ const relPath = relative(this.context.paths.public, filePath)
217
+ logger.info(event, `public: ${relPath}`)
218
+ try {
219
+ if (this.context.taskRegistry?.copy) await this.context.taskRegistry.copy(this.context, { files: [filePath] })
220
+ this.reload()
221
+ } catch (error) {
222
+ logger.error('watch', `Copy failed: ${error.message}`)
223
+ }
348
224
  }
349
225
 
350
- /**
351
- * ブラウザリロード
352
- */
226
+ async onPublicUnlink(filePath) {
227
+ const relPath = relative(this.context.paths.public, filePath)
228
+ const distPath = resolve(this.context.paths.dist, relPath)
229
+ await this.deleteDistFile(distPath, relPath)
230
+ }
231
+
232
+ // ---- 共通ヘルパー ----
233
+
353
234
  reload() {
354
235
  if (this.context.server) {
355
236
  setTimeout(() => {
@@ -358,9 +239,6 @@ class FileWatcher {
358
239
  }
359
240
  }
360
241
 
361
- /**
362
- * dist内のファイルを削除してブラウザをリロード
363
- */
364
242
  async deleteDistFile(distPath, relPath) {
365
243
  try {
366
244
  await rm(distPath, { force: true })
@@ -371,20 +249,14 @@ class FileWatcher {
371
249
  }
372
250
  }
373
251
 
374
- /**
375
- * CSSインジェクション
376
- */
377
252
  injectCSS() {
378
253
  if (this.context.server) {
379
254
  this.context.server.reloadCSS()
380
255
  }
381
256
  }
382
257
 
383
- /**
384
- * 監視停止
385
- */
386
258
  async stop() {
387
- await Promise.all(this.watchers.map(w => w.close()))
259
+ if (this.watcher) await this.watcher.close()
388
260
  logger.info('watch', 'File watching stopped')
389
261
  }
390
262
  }
package/generate/page.mjs CHANGED
@@ -4,7 +4,7 @@ import { ensureFileDir } from '../utils/file.mjs'
4
4
 
5
5
  export async function generatePage(filePath, html, paths) {
6
6
  const relativePath = relative(paths.src, filePath)
7
- const outputPath = resolve(paths.dist, relativePath.replace('.pug', '.html'))
7
+ const outputPath = resolve(paths.dist, relativePath.replace(/\.pug$/, '.html'))
8
8
 
9
9
  await ensureFileDir(outputPath)
10
10
  await writeFile(outputPath, html, 'utf8')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pugkit",
3
- "version": "1.0.2",
3
+ "version": "1.1.0",
4
4
  "description": "A build tool for Pug-based projects",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/tasks/copy.mjs CHANGED
@@ -10,6 +10,21 @@ import { ensureFileDir } from '../utils/file.mjs'
10
10
  export async function copyTask(context, options = {}) {
11
11
  const { paths } = context
12
12
 
13
+ // 特定のファイルが指定されている場合(watch時)
14
+ if (options.files && Array.isArray(options.files)) {
15
+ logger.info('copy', `Copying ${options.files.length} file(s)`)
16
+ await Promise.all(
17
+ options.files.map(async file => {
18
+ const relativePath = relative(paths.public, file)
19
+ const outputPath = resolve(paths.dist, relativePath)
20
+ await ensureFileDir(outputPath)
21
+ await copyFile(file, outputPath)
22
+ })
23
+ )
24
+ logger.success('copy', `Copied ${options.files.length} file(s)`)
25
+ return
26
+ }
27
+
13
28
  const files = await glob('**/*', {
14
29
  cwd: paths.public,
15
30
  absolute: true,
package/tasks/sass.mjs CHANGED
@@ -78,7 +78,7 @@ async function compileSassFile(filePath, context, isDebugMode) {
78
78
  )
79
79
  }
80
80
 
81
- const outputRelativePath = relative(paths.src, filePath).replace('.scss', '.css')
81
+ const outputRelativePath = relative(paths.src, filePath).replace(/\.scss$/, '.css')
82
82
  const outputPath = resolve(paths.dist, outputRelativePath)
83
83
 
84
84
  const postcssResult = await postcss(postcssPlugins).process(css, {