chokibasic 1.1.2 → 1.1.3

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/index.d.ts CHANGED
@@ -1,128 +1,152 @@
1
- // index.d.ts
2
1
 
3
- export type GlobPattern = string;
2
+ declare namespace chokibasic {
3
+ export type GlobPattern = string;
4
4
 
5
- export type IgnoreMatcher =
6
- | string
7
- | RegExp
8
- | ((relPosixPath: string) => boolean);
5
+ export type IgnoreMatcher =
6
+ | string
7
+ | RegExp
8
+ | ((relPosixPath: string) => boolean);
9
9
 
10
- export interface WatchEvent {
11
- /** Type d’évènement (dans ton code actuel: surtout "change") */
12
- type: "add" | "change" | "unlink" | (string & {});
13
- /** Chemin relatif à cwd, normalisé en POSIX (slash "/") */
14
- file: string;
15
- }
10
+ export type WatchEventType = "add" | "change" | "unlink" | (string & {});
16
11
 
17
- export interface WatchRuleContext {
18
- rule: WatchRule;
19
- }
12
+ export interface WatchEvent {
13
+ /** Type d’évènement (dans le code actuel: surtout "change") */
14
+ type: WatchEventType;
15
+ /** Chemin relatif à cwd, normalisé POSIX (slash "/") */
16
+ file: string;
17
+ }
20
18
 
21
- export interface WatchRule {
22
- /** Nom affiché en debug */
23
- name?: string;
19
+ export interface WatchRuleContext {
20
+ rule: WatchRule;
21
+ }
24
22
 
25
- /** Globs d’inclusion (ex: "src/styles/ ** / *.scss") */
26
- patterns: GlobPattern | GlobPattern[];
23
+ export interface WatchRule {
24
+ /** Nom affiché en debug */
25
+ name?: string;
27
26
 
28
- /** Ignorés additionnels (globs / regex / function) */
29
- ignored?: IgnoreMatcher[];
27
+ /** Globs d’inclusion (ex: "src/styles/ ** / *.scss") */
28
+ patterns: GlobPattern | GlobPattern[];
30
29
 
31
- /** Debounce en ms (défaut: 150) */
32
- debounceMs?: number;
30
+ /** Ignorés additionnels (globs / regex / function) */
31
+ ignored?: IgnoreMatcher[];
33
32
 
34
- /**
35
- * Callback appelé avec un batch d’évènements.
36
- * Le batch contient des objets { type, file }.
37
- */
38
- callback: (events: WatchEvent[], ctx: WatchRuleContext) => void | Promise<void>;
39
- }
33
+ /** Debounce en ms (défaut: 150) */
34
+ debounceMs?: number;
40
35
 
41
- export interface AwaitWriteFinishOptions {
42
- stabilityThreshold?: number;
43
- pollInterval?: number;
44
- }
36
+ /**
37
+ * Callback appelé avec un batch d’évènements.
38
+ * Le batch contient des objets { type, file }.
39
+ */
40
+ callback: (events: WatchEvent[], ctx: WatchRuleContext) => void | Promise<void>;
41
+ }
45
42
 
46
- export interface CreateWatchersOptions {
47
- /** Répertoire racine utilisé pour calculer les chemins relatifs */
48
- cwd?: string;
43
+ export interface AwaitWriteFinishOptions {
44
+ stabilityThreshold?: number;
45
+ pollInterval?: number;
46
+ }
49
47
 
50
- /**
51
- * Ignorés globaux (appliqués dans queue(), pas via chokidar "ignored")
52
- * Défaut: ["** /node_modules/**","** /.git/ **","** /dist/ **"]
53
- */
54
- globalIgnored?: string[];
48
+ export interface CreateWatchersOptions {
49
+ /** Répertoire racine utilisé pour calculer les chemins relatifs */
50
+ cwd?: string;
55
51
 
56
- /** chokidar: ignoreInitial */
57
- ignoreInitial?: boolean;
52
+ /**
53
+ * Ignorés globaux (appliqués dans queue(), pas via chokidar "ignored")
54
+ * Défaut: ["** /node_modules/ **","** /.git/ **","** /dist/ **"]
55
+ */
56
+ globalIgnored?: string[];
58
57
 
59
- /** chokidar: awaitWriteFinish */
60
- awaitWriteFinish?: boolean | AwaitWriteFinishOptions;
58
+ /** chokidar: ignoreInitial */
59
+ ignoreInitial?: boolean;
61
60
 
62
- /** chokidar: usePolling */
63
- usePolling?: boolean;
61
+ /** chokidar: awaitWriteFinish */
62
+ awaitWriteFinish?: boolean | AwaitWriteFinishOptions;
64
63
 
65
- /** chokidar: interval */
66
- interval?: number;
64
+ /** chokidar: usePolling */
65
+ usePolling?: boolean;
67
66
 
68
- /** chokidar: binaryInterval */
69
- binaryInterval?: number;
67
+ /** chokidar: interval */
68
+ interval?: number;
70
69
 
71
- /** Log console */
72
- debug?: boolean;
70
+ /** chokidar: binaryInterval */
71
+ binaryInterval?: number;
73
72
 
74
- /** Permet d’accepter d’autres options sans casser les types */
75
- [key: string]: unknown;
76
- }
73
+ /** Log console */
74
+ debug?: boolean;
77
75
 
78
- export interface WatchersController {
79
- /** Ferme tous les watchers et annule les timers */
80
- close: () => Promise<void>;
81
- }
76
+ /** Permet d’accepter d’autres options sans casser les types */
77
+ [key: string]: unknown;
78
+ }
79
+
80
+ export interface WatchersController {
81
+ /** Ferme tous les watchers et annule les timers */
82
+ close: () => Promise<void>;
83
+ }
84
+
85
+ export interface ExportDistStats {
86
+ copied: number;
87
+ skipped: number;
88
+ }
82
89
 
83
- export interface ExportDistStats {
84
- copied: number;
85
- skipped: number;
90
+ /** Options forwardées à esbuild.build() */
91
+ export type BuildJSOptions = Parameters<typeof import("esbuild").build>[0];
92
+
93
+ /** Options forwardées à sass.compile() */
94
+ export type BuildCSSOptions = NonNullable<Parameters<typeof import("sass").compile>[1]>;
95
+
96
+ /**
97
+ * Crée un ou plusieurs watchers (un par règle).
98
+ */
99
+ export function createWatchers(
100
+ rules: WatchRule[],
101
+ options?: CreateWatchersOptions
102
+ ): WatchersController;
103
+
104
+ /**
105
+ * Exporte un dossier `src` vers `dist` en respectant .gitignore + exclusions.
106
+ */
107
+ export function exportDist(
108
+ src: string,
109
+ dist: string,
110
+ banner?: string | null
111
+ ): Promise<ExportDistStats>;
112
+
113
+ /**
114
+ * Compile SCSS -> CSS minifié (csso), écrit dans outCssMin.
115
+ */
116
+ export function buildCSS(
117
+ inputScss: string,
118
+ outCssMin: string,
119
+ options?: BuildCSSOptions
120
+ ): Promise<void>;
121
+
122
+ /**
123
+ * Bundle/minify JS via esbuild.
124
+ */
125
+ export function buildJS(
126
+ entry: string,
127
+ outfile: string,
128
+ options?: BuildJSOptions
129
+ ): Promise<void>;
130
+
131
+ /**
132
+ * Rend un fichier via pxpros.render(file).
133
+ */
134
+ export function buildPHP(file: string): Promise<void>;
135
+
136
+ /**
137
+ * Génère un sitemap via pxpros.sitemap(file).
138
+ */
139
+ export function buildSitemap(file: string): Promise<void>;
86
140
  }
87
141
 
88
- /**
89
- * Crée un ou plusieurs watchers (un par règle).
90
- */
91
- export function createWatchers(
92
- rules: WatchRule[],
93
- options?: CreateWatchersOptions
94
- ): WatchersController;
95
-
96
- /**
97
- * Exporte un dossier `src` vers `dist` en respectant .gitignore + exclusions.
98
- */
99
- export function exportDist(
100
- src: string,
101
- dist: string,
102
- banner?: string | null
103
- ): Promise<ExportDistStats>;
104
-
105
- /**
106
- * Compile SCSS -> CSS minifié (csso), écrit dans outCssMin.
107
- * `options` est forwardé à sass.compile().
108
- */
109
- export function buildCSS(
110
- inputScss: string,
111
- outCssMin: string,
112
- options?: Record<string, unknown>
113
- ): Promise<void>;
114
-
115
- /**
116
- * Bundle/minify JS via esbuild.
117
- * `options` est forwardé à esbuild.build().
118
- */
119
- export function buildJS(
120
- entry: string,
121
- outfile: string,
122
- options?: Record<string, unknown>
123
- ): Promise<void>;
124
-
125
- /**
126
- * Rend un fichier via pxpros.render(file).
127
- */
128
- export function buildPHP(file: string): Promise<void>;
142
+ declare const chokibasic: {
143
+ createWatchers: typeof chokibasic.createWatchers;
144
+ exportDist: typeof chokibasic.exportDist;
145
+ buildCSS: typeof chokibasic.buildCSS;
146
+ buildJS: typeof chokibasic.buildJS;
147
+ buildPHP: typeof chokibasic.buildPHP;
148
+ buildSitemap: typeof chokibasic.buildSitemap;
149
+ };
150
+
151
+ export = chokibasic;
152
+ export as namespace chokibasic;
package/index.js CHANGED
@@ -1 +1,7 @@
1
- module.exports = require("./src/watcher.js");
1
+ module.exports = {
2
+ ...require("./src/buildjs.js"),
3
+ ...require("./src/buildcss.js"),
4
+ ...require("./src/buildphp.js"),
5
+ ...require("./src/export.js"),
6
+ ...require("./src/watchers.js"),
7
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chokibasic",
3
- "version": "1.1.2",
3
+ "version": "1.1.3",
4
4
  "description": "Basic chokidar watcher + pxpros + esbuild + sass + csso helpers",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
package/readme.md CHANGED
@@ -1,17 +1,29 @@
1
1
  # chokibasic
2
2
 
3
- Helpers:
3
+ Tiny build & watch helpers for simple static projects.
4
+
5
+ ## Helpers
6
+
4
7
  - `createWatchers(rules, options)`
5
- - `buildCSS(inputScss, outCssMin)`
6
- - `buildJS(entry, outfile)`
8
+ - `buildCSS(inputScss, outCssMin, options?)`
9
+ - `buildJS(entry, outfile, options?)`
10
+ - `buildPHP(file)`
11
+ - `buildSitemap(file)`
12
+ - `exportDist(src, dist, bannerPath?)`
13
+
14
+ ---
7
15
 
8
16
  ## Install
17
+
9
18
  ```bash
10
19
  npm i chokibasic
11
- ```
20
+ ````
21
+
22
+ ---
12
23
 
13
- ## Usage
14
- ```JS
24
+ ## Usage (watch + build) — original example (fixed)
25
+
26
+ ```js
15
27
  const { createWatchers, buildCSS, buildJS } = require("chokibasic");
16
28
 
17
29
  const w = createWatchers(
@@ -39,19 +51,202 @@ const w = createWatchers(
39
51
 
40
52
  // later: await w.close();
41
53
  process.on("SIGINT", async () => {
42
- await close();
43
- process.exit(0);
54
+ await w.close();
55
+ process.exit(0);
44
56
  });
57
+ ```
58
+
59
+ ---
60
+
61
+ ## API
62
+
63
+ ### `createWatchers(rules, options)`
45
64
 
65
+ Creates one watcher per rule using `chokidar`, but with:
66
+
67
+ * **include filtering** using `rule.patterns` (glob)
68
+ * **ignore filtering** using `options.globalIgnored` + `rule.ignored` (glob/RegExp/function)
69
+ * **debounced batching**: multiple file events are grouped and sent to your callback
70
+
71
+ Each callback receives:
72
+
73
+ * `events`: an array like `{ type, file }`
74
+ * `ctx`: `{ rule }`
75
+
76
+ **Important note (current behavior):**
77
+ In the current code, only `"change"` events are enabled (`add` and `unlink` listeners are commented out).
78
+
79
+ #### Rule shape
80
+
81
+ ```js
82
+ {
83
+ name?: string,
84
+ patterns: string | string[],
85
+ ignored?: (string | RegExp | ((relPosixPath) => boolean))[],
86
+ debounceMs?: number, // default 150
87
+ callback: async (events, ctx) => {}
88
+ }
46
89
  ```
47
90
 
91
+ #### Options
92
+
93
+ ```js
94
+ {
95
+ cwd: process.cwd(),
96
+ globalIgnored: ["**/node_modules/**", "**/.git/**", "**/dist/**"],
97
+
98
+ // chokidar options used internally:
99
+ ignoreInitial: true,
100
+ awaitWriteFinish: { stabilityThreshold: 80, pollInterval: 10 },
101
+ usePolling: false,
102
+ interval: 200,
103
+ binaryInterval: 300,
104
+
105
+ debug: true
106
+ }
107
+ ```
108
+
109
+ ---
110
+
111
+ ### `buildCSS(inputScss, outCssMin, options?)`
112
+
113
+ Compiles SCSS using `sass.compile()` and minifies using `csso`.
114
+
115
+ * Defaults include:
116
+
117
+ * `loadPaths: ["<cwd>/node_modules"]`
118
+ * `style: "compressed"`
119
+ * no sourcemap
120
+ * `options` is forwarded to `sass.compile()` (and can override defaults).
121
+
122
+ Example:
123
+
124
+ ```js
125
+ const { buildCSS } = require("chokibasic");
126
+
127
+ await buildCSS("src/styles/main.scss", "dist/app.min.css", {
128
+ // any sass.compile options here
129
+ });
130
+ ```
131
+
132
+ ---
133
+
134
+ ### `buildJS(entry, outfile, options?)`
135
+
136
+ Bundles & minifies JS using `esbuild`.
137
+
138
+ Defaults include:
139
+
140
+ * `bundle: true`
141
+ * `platform: "browser"`
142
+ * `minify: true`
143
+ * `treeShaking: true`
144
+ * `target: ["es2020"]`
145
+ * `legalComments: "none"`
146
+
147
+ `options` is forwarded to `esbuild.build()` (and can override defaults).
148
+
149
+ Example:
150
+
151
+ ```js
152
+ const { buildJS } = require("chokibasic");
153
+
154
+ await buildJS("src/scripts/main.js", "dist/app.min.js", {
155
+ // any esbuild.build options here
156
+ });
157
+ ```
158
+
159
+ ---
160
+
161
+ ### `buildPHP(file)`
162
+
163
+ Runs `pxpros.render(file)` and logs generated HTML files.
164
+
165
+ Example:
166
+
167
+ ```js
168
+ const { buildPHP } = require("chokibasic");
169
+
170
+ await buildPHP("src/pages/_index.php");
171
+ ```
172
+
173
+ ---
174
+
175
+ ### `buildSitemap(file)`
176
+
177
+ Runs `pxpros.sitemap(file)` and logs generated sitemap XML files.
178
+
179
+ Example:
180
+
181
+ ```js
182
+ const { buildSitemap } = require("chokibasic");
183
+
184
+ await buildSitemap("src/pages/_index.php");
185
+ ```
186
+
187
+ ---
188
+
189
+ ### `exportDist(src, dist, bannerPath?)`
190
+
191
+ Copies your `src` folder to `dist`, while:
192
+
193
+ * respecting `.gitignore` (if present in project root)
194
+ * also ignoring `dist/` for safety
195
+ * skipping:
196
+
197
+ * any file/folder starting with `_`
198
+ * `.scss`
199
+ * `.js` files that are NOT `.min.js`
200
+
201
+ Then it can prepend a banner to:
202
+
203
+ * `.js` and `.css` files using `/*! ... */`
204
+ * `.html` files using `<!-- ... -->`
205
+
206
+ It also replaces placeholders:
207
+
208
+ * in `.html`:
209
+
210
+ * `###YEAR###` → current year
211
+ * `###TIMESTAMP###` → unix timestamp (seconds)
212
+ * in `sitemap.xml`:
213
+
214
+ * `###TODAY###` → `YYYY-MM-DD`
215
+ * in the banner text:
216
+
217
+ * `###DATE###` → formatted date (fr-CA, America/Toronto)
218
+
219
+ Example:
220
+
221
+ ```js
222
+ const { exportDist } = require("chokibasic");
223
+
224
+ const stats = await exportDist("src", "dist"); // uses built-in banner.txt
225
+ console.log(stats); // { copied: <number>, skipped: <number> }
226
+ ```
227
+
228
+ With a custom banner file:
229
+
230
+ ```js
231
+ await exportDist("src", "dist", "src/banner.txt");
232
+ ```
233
+
234
+ ---
235
+
48
236
  ## Changelog
49
237
 
238
+ ### Version 1.1.3
239
+ * Complete refactor
240
+ * Fix buildCSS "style: expanded"
241
+
50
242
  ### Version 1.1.2
51
- - exportDist: Add date remplacement for sitemap.xml
243
+
244
+ * exportDist: Add date replacement for sitemap.xml
52
245
 
53
246
  ### Version 1.1.1
54
- - Update dependancy: pxpros 1.0.4
247
+
248
+ * Update dependency: pxpros 1.0.4
55
249
 
56
250
  ### Version 1.1.0
57
- - Add a sitemap builder with pxpros
251
+
252
+ * Add a sitemap builder with pxpros
@@ -0,0 +1,29 @@
1
+ const sass = require("sass");
2
+ const csso = require("csso");
3
+ const path = require("path");
4
+ const fs = require("node:fs");
5
+
6
+ const buildCSS = async (inputScss, outCssMin, options = {}) => {
7
+ // let compiled;
8
+ try {
9
+ const compiled = sass.compile(inputScss, {
10
+ loadPaths: [path.resolve(process.cwd(), "./node_modules")],
11
+ style: "compressed",
12
+ sourceMap: false,
13
+ sourceMapIncludeSources: false,
14
+ ...options
15
+ });
16
+ if(options?.style == "expanded"){
17
+ fs.writeFileSync(outCssMin, compiled.css);
18
+ } else {
19
+ const minified = csso.minify(compiled.css, { restructure: false });
20
+ fs.writeFileSync(outCssMin, minified.css);
21
+ }
22
+ console.log(`✅ CSS generated: ${outCssMin}`);
23
+ } catch (err) {
24
+ console.error("❌ Sass compile error:");
25
+ console.error(err?.formatted || err?.message || err);
26
+ }
27
+ }
28
+
29
+ module.exports = { buildCSS };
package/src/buildjs.js ADDED
@@ -0,0 +1,35 @@
1
+ const esbuild = require("esbuild");
2
+
3
+
4
+ const buildJS = async (entry, outfile, options = {}) => {
5
+ try {
6
+ await esbuild.build({
7
+ entryPoints: [entry],
8
+ outfile,
9
+ bundle: true,
10
+ platform: "browser",
11
+ logLevel: "error",
12
+ treeShaking: true,
13
+ minify: true,
14
+ supported: { "template-literal": false },
15
+ target: ["es2020"],
16
+ legalComments: "none",
17
+ ...options
18
+ });
19
+ console.log(`✅ JS generated: ${outfile}`);
20
+ } catch (err) {
21
+ console.error("❌ esbuild build failed.");
22
+ if (err?.errors?.length) {
23
+ const formatted = await esbuild.formatMessages(err.errors, {
24
+ kind: "error",
25
+ color: true,
26
+ terminalWidth: process.stdout.columns || 80,
27
+ });
28
+ console.error(formatted.join("\n"));
29
+ } else {
30
+ console.error(err);
31
+ }
32
+ }
33
+ }
34
+
35
+ module.exports = { buildJS };
@@ -0,0 +1,25 @@
1
+ const pxpros = require("pxpros");
2
+
3
+
4
+ const buildPHP = async (file) => {
5
+ const results = await pxpros.render(file);
6
+ if(results.success) {
7
+ results.files.forEach(file => console.log(`✅ HTML generated: ${file}`));
8
+ } else {
9
+ console.error("❌ pxpros render failed.");
10
+ console.error(results.error);
11
+ }
12
+ }
13
+
14
+
15
+ const buildSitemap = async (file) => {
16
+ const results = await pxpros.sitemap(file);
17
+ if(results.success) {
18
+ results.files.forEach(file => console.log(`✅ XML Sitemap generated: ${file}`));
19
+ } else {
20
+ console.error("❌ pxpros sitemap creation failed.");
21
+ console.error(results.error);
22
+ }
23
+ }
24
+
25
+ module.exports = { buildPHP, buildSitemap };
package/src/export.js ADDED
@@ -0,0 +1,134 @@
1
+ const fsprom = require('fs/promises');
2
+ const ignore = require('ignore');
3
+ const fs = require("node:fs");
4
+ const path = require("path");
5
+
6
+ const ROOT = process.cwd();
7
+
8
+
9
+ async function emptyDir(dir) {
10
+ await fsprom.mkdir(dir, { recursive: true });
11
+ const entries = await fsprom.readdir(dir, { withFileTypes: true });
12
+ await Promise.all(entries.map((e) => fsprom.rm(path.join(dir, e.name), { recursive: true, force: true })));
13
+ }
14
+
15
+
16
+ function formatFrDate(dateInput = new Date(), timeZone = 'America/Toronto') {
17
+ const d = (dateInput instanceof Date) ? dateInput : new Date(dateInput);
18
+ const fmt = new Intl.DateTimeFormat('fr-CA', {
19
+ weekday: 'long',
20
+ day: 'numeric',
21
+ month: 'long',
22
+ year: 'numeric',
23
+ hour: 'numeric',
24
+ minute: '2-digit',
25
+ hour12: false,
26
+ timeZone
27
+ });
28
+ const parts = Object.fromEntries(fmt.formatToParts(d).map(p => [p.type, p.value]));
29
+ const weekday = parts.weekday.charAt(0).toUpperCase() + parts.weekday.slice(1); // "Samedi"
30
+ return `${weekday} le ${parts.day} ${parts.month} ${parts.year} à ${parts.hour} h ${parts.minute}`;
31
+ }
32
+
33
+
34
+ function norm(p) {
35
+ // normalise en chemin POSIX pour compat .gitignore
36
+ return p.split(path.sep).join('/');
37
+ }
38
+
39
+ async function loadGitignore() {
40
+ const ig = ignore();
41
+ const giPath = path.join(ROOT, '.gitignore');
42
+ if (fs.existsSync(giPath)) {
43
+ const txt = await fsprom.readFile(giPath, 'utf8');
44
+ ig.add(txt);
45
+ }
46
+ // on ignore aussi le dossier dist par sécurité (pas nécessaire mais sain)
47
+ ig.add('dist/');
48
+ return ig;
49
+ }
50
+
51
+ async function rmDir(dir) {
52
+ await fsprom.rm(dir, { recursive: true, force: true });
53
+ await fsprom.mkdir(dir, { recursive: true });
54
+ }
55
+
56
+ function shouldExcludeFile(relFromRoot, absPath) {
57
+ // Exclusions de type/extension
58
+ const lower = absPath.toLowerCase();
59
+ if (path.basename(lower).startsWith('_')) return true;
60
+ if (lower.endsWith('.scss')) return true;
61
+ if (lower.endsWith('.js') && !lower.endsWith('.min.js')) return true;
62
+ return false;
63
+ }
64
+
65
+ async function copyFilePreserveTree(absSrc, src, dist, ig) {
66
+ const relFromSrc = path.relative(src, absSrc);
67
+ const relFromRoot = path.relative(ROOT, absSrc);
68
+ const relPosix = norm(relFromRoot);
69
+
70
+ // 1) Exclusions via .gitignore
71
+ if (ig.ignores(relPosix)) return false;
72
+
73
+ // 2) Exclusions spécifiques (scss, js non minifiés)
74
+ if (shouldExcludeFile(relPosix, absSrc)) return false;
75
+
76
+ const absDst = path.join(dist, relFromSrc);
77
+ // console.log(absDst);
78
+ await fsprom.mkdir(path.dirname(absDst), { recursive: true });
79
+ await fsprom.copyFile(absSrc, absDst);
80
+ return absDst;
81
+ }
82
+
83
+ async function walkAndCopy(dir, src, dest, ig, stats, banner = null) {
84
+ const entries = await fsprom.readdir(dir, { withFileTypes: true });
85
+ const bannerContent = (await fsprom.readFile(banner || path.join(__dirname, 'banner.txt'), 'utf8')).replace(/###DATE###/, formatFrDate());
86
+ const today = (d => `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')}`)(new Date());
87
+
88
+ for (const de of entries) {
89
+ const abs = path.join(dir, de.name);
90
+ const relFromRoot = path.relative(ROOT, abs);
91
+ const relPosix = norm(relFromRoot);
92
+
93
+ if (de.isDirectory()) {
94
+ // Si le dossier est ignoré par .gitignore, on ne descend pas
95
+ if (ig.ignores(relPosix + '/')) continue;
96
+ if(de.name.startsWith('_')) continue;
97
+ await walkAndCopy(abs, src, dest, ig, stats, banner);
98
+ } else if (de.isFile()) {
99
+ const copied = await copyFilePreserveTree(abs, src, dest, ig);
100
+ if (copied) {
101
+ const lower = abs.toLowerCase();
102
+ if (lower.endsWith('.js')) await fsprom.writeFile(copied, "/*!\n\n" + bannerContent + "\n\n*/\n" + (await fsprom.readFile(copied, 'utf8')), "utf8");
103
+ else if (lower.endsWith('.css')) await fsprom.writeFile(copied, "/*!\n\n" + bannerContent + "\n\n*/\n" + (await fsprom.readFile(copied, 'utf8')), "utf8");
104
+ else if (lower.endsWith('.html')) await fsprom.writeFile(copied, "<!--\n\n" + bannerContent + "\n\n\-->\n" + (await fsprom.readFile(copied, 'utf8'))
105
+ .replaceAll(/###YEAR###/g, (new Date).getFullYear())
106
+ .replaceAll(/###TIMESTAMP###/g, Math.floor(Date.now() / 1000)), "utf8");
107
+ else if (lower.endsWith('sitemap.xml')) await fsprom.writeFile(copied, (await fsprom.readFile(copied, 'utf8')).replaceAll(/###TODAY###/g, today), "utf8");
108
+ stats.copied++;
109
+ }
110
+ else stats.skipped++;
111
+ }
112
+ // (symlinks & autres: ignorés)
113
+ }
114
+ }
115
+
116
+
117
+ const exportDist = async (src, dist, banner = null) => {
118
+ try {
119
+ const ig = await loadGitignore();
120
+
121
+ await emptyDir(dist);
122
+ if (!fs.existsSync(src)) throw new error('Folder src is invalid.');
123
+
124
+ const stats = { copied: 0, skipped: 0 };
125
+ await walkAndCopy(src, src, dist, ig, stats, banner);
126
+
127
+ return stats;
128
+ } catch (err) {
129
+ throw new error(err);
130
+ }
131
+ }
132
+
133
+
134
+ module.exports = { exportDist };
@@ -0,0 +1,203 @@
1
+ const path = require("path");
2
+ const chokidar = require("chokidar");
3
+
4
+ function toPosix(p) {
5
+ return p.replace(/\\/g, "/");
6
+ }
7
+
8
+ function normalizePatterns(patterns) {
9
+ return Array.isArray(patterns) ? patterns : [patterns];
10
+ }
11
+
12
+ function globBaseDir(glob) {
13
+ const g = toPosix(glob);
14
+ const idx = g.search(/[*?\[]/);
15
+ if (idx === -1) return g;
16
+ const base = g.slice(0, idx);
17
+ return base.replace(/\/+$/, "") || ".";
18
+ }
19
+
20
+ function globToRegExp(glob) {
21
+ const g = toPosix(glob);
22
+
23
+ let re = "^";
24
+ for (let i = 0; i < g.length; i++) {
25
+ const c = g[i];
26
+
27
+ if (c === "*" && g[i + 1] === "*") {
28
+ i++;
29
+ if (g[i + 1] === "/") {
30
+ i++;
31
+ re += "(?:.*/)?";
32
+ } else {
33
+ re += ".*";
34
+ }
35
+ continue;
36
+ }
37
+
38
+ if (c === "*") { re += "[^/]*"; continue; }
39
+ if (c === "?") { re += "[^/]"; continue; }
40
+
41
+ if ("\\.[]{}()+-^$|".includes(c)) re += "\\" + c;
42
+ else re += c;
43
+ }
44
+ re += "$";
45
+ return new RegExp(re);
46
+ }
47
+
48
+ function isIgnoredPosix(relPosix, ignoreMatchers) {
49
+ for (const m of ignoreMatchers) {
50
+ if (m instanceof RegExp) {
51
+ if (m.test(relPosix)) return true;
52
+ } else if (typeof m === "function") {
53
+ // si l'user fournit une fonction ignorée, on lui passe le rel posix
54
+ if (m(relPosix)) return true;
55
+ } else if (typeof m === "string") {
56
+ // strings glob déjà compilées dans ignoreMatchers (voir compileIgnoreMatchers)
57
+ // (fallback: on ne devrait pas arriver ici)
58
+ if (globToRegExp(m).test(relPosix)) return true;
59
+ }
60
+ }
61
+ return false;
62
+ }
63
+
64
+ function compileIgnoreMatchers(ignoreList) {
65
+ return (ignoreList || []).map((ig) => {
66
+ if (ig instanceof RegExp) return ig;
67
+ if (typeof ig === "function") return ig;
68
+ // string glob -> RegExp
69
+ return globToRegExp(ig);
70
+ });
71
+ }
72
+
73
+ /**
74
+ * createWatchers(rules, options)
75
+ * - Watch les dossiers racines (baseDirs)
76
+ * - Filtre include via globs (rule.patterns)
77
+ * - Filtre ignore via globs/regex (globalIgnored + rule.ignored)
78
+ */
79
+ function createWatchers(rules, options = {}) {
80
+ const opt = {
81
+ cwd: process.cwd(),
82
+
83
+ // Ignorés globaux (appliqués dans queue() — pas dans chokidar)
84
+ globalIgnored: ["**/node_modules/**", "**/.git/**", "**/dist/**"],
85
+
86
+ ignoreInitial: true,
87
+ awaitWriteFinish: { stabilityThreshold: 80, pollInterval: 10 },
88
+
89
+ // Si FS weird (OneDrive, réseau, WSL/Docker bind mount), mets true:
90
+ usePolling: false,
91
+ interval: 200,
92
+ binaryInterval: 300,
93
+
94
+ debug: true,
95
+ ...options,
96
+ };
97
+
98
+ const handles = [];
99
+
100
+ for (const rule of rules) {
101
+ if (!rule || typeof rule.callback !== "function") {
102
+ throw new Error("Each rule must have a callback(events, ctx).");
103
+ }
104
+
105
+ const name = rule.name || "rule";
106
+ const patterns = normalizePatterns(rule.patterns);
107
+
108
+ // include matchers (relatif à cwd, en posix)
109
+ const includeMatchers = patterns.map(globToRegExp);
110
+
111
+ // ignore matchers (relatif à cwd, en posix)
112
+ const ignoreMatchers = compileIgnoreMatchers([
113
+ ...(opt.globalIgnored || []),
114
+ ...(rule.ignored || []),
115
+ ]);
116
+
117
+ // baseDirs à watcher
118
+ const baseDirs = Array.from(new Set(patterns.map(globBaseDir).map((d) => d || ".")))
119
+ .map((d) => path.resolve(opt.cwd, d));
120
+
121
+ if (opt.debug) {
122
+ console.log(`\n[${name}] starting watcher`);
123
+ console.log(" cwd:", opt.cwd);
124
+ console.log(" patterns:", patterns);
125
+ console.log(" baseDirs:", baseDirs);
126
+ console.log(" ignored (applied in queue):", [...(opt.globalIgnored || []), ...(rule.ignored || [])]);
127
+ console.log("");
128
+ }
129
+
130
+ const pending = new Map();
131
+ let timer = null;
132
+ let running = false;
133
+ let rerun = false;
134
+
135
+ const flush = async () => {
136
+ if (running) { rerun = true; return; }
137
+ running = true;
138
+ try {
139
+ do {
140
+ rerun = false;
141
+ const batch = Array.from(pending.values());
142
+ pending.clear();
143
+ if (batch.length) await rule.callback(batch, { rule });
144
+ } while (rerun);
145
+ } finally {
146
+ running = false;
147
+ }
148
+ };
149
+
150
+ const queue = (type, absPath) => {
151
+ const rel = toPosix(path.relative(opt.cwd, absPath));
152
+
153
+ // ignore d'abord
154
+ if (isIgnoredPosix(rel, ignoreMatchers)) {
155
+ // if (opt.debug) console.log(`[${name}] ignored`, type, rel);
156
+ return;
157
+ }
158
+
159
+ // include ensuite
160
+ const ok = includeMatchers.some((rx) => rx.test(rel));
161
+ if (!ok) return;
162
+
163
+ if (opt.debug) console.log(`[${name}] queue`, type, rel);
164
+
165
+ pending.set(`${type}:${rel}`, { type, file: rel });
166
+ clearTimeout(timer);
167
+ timer = setTimeout(flush, rule.debounceMs ?? 150);
168
+ };
169
+
170
+ // ⚠️ IMPORTANT: on ne met PAS "ignored" ici
171
+ const watcher = chokidar.watch(baseDirs, {
172
+ ignoreInitial: opt.ignoreInitial,
173
+ awaitWriteFinish: opt.awaitWriteFinish,
174
+ persistent: true,
175
+ usePolling: opt.usePolling,
176
+ interval: opt.interval,
177
+ binaryInterval: opt.binaryInterval,
178
+ });
179
+
180
+ // watcher.on("ready", () => {
181
+ // console.log(`[${name}] ready`);
182
+ // });
183
+
184
+ // watcher.on("add", (p) => queue("add", p));
185
+ watcher.on("change", (p) => queue("change", p));
186
+ // watcher.on("unlink", (p) => queue("unlink", p));
187
+ watcher.on("error", (err) => console.error(`[${name}] watch error:`, err));
188
+
189
+ handles.push({
190
+ watcher,
191
+ stopTimers: () => { clearTimeout(timer); pending.clear(); }
192
+ });
193
+ }
194
+
195
+ return {
196
+ close: async () => {
197
+ for (const h of handles) h.stopTimers();
198
+ await Promise.all(handles.map((h) => h.watcher.close()));
199
+ },
200
+ };
201
+ }
202
+
203
+ module.exports = { createWatchers };
package/src/watcher.js DELETED
@@ -1,412 +0,0 @@
1
- const chokidar = require("chokidar");
2
- const fs = require("node:fs");
3
- const fsprom = require('fs/promises');
4
- const path = require("path");
5
- const esbuild = require("esbuild");
6
- const sass = require("sass");
7
- const csso = require("csso");
8
- const pxpros = require("pxpros");
9
- const ignore = require('ignore');
10
-
11
- const ROOT = process.cwd();
12
-
13
- function toPosix(p) {
14
- return p.replace(/\\/g, "/");
15
- }
16
-
17
- function normalizePatterns(patterns) {
18
- return Array.isArray(patterns) ? patterns : [patterns];
19
- }
20
-
21
- function globBaseDir(glob) {
22
- const g = toPosix(glob);
23
- const idx = g.search(/[*?\[]/);
24
- if (idx === -1) return g;
25
- const base = g.slice(0, idx);
26
- return base.replace(/\/+$/, "") || ".";
27
- }
28
-
29
- function globToRegExp(glob) {
30
- const g = toPosix(glob);
31
-
32
- let re = "^";
33
- for (let i = 0; i < g.length; i++) {
34
- const c = g[i];
35
-
36
- if (c === "*" && g[i + 1] === "*") {
37
- i++;
38
- if (g[i + 1] === "/") {
39
- i++;
40
- re += "(?:.*/)?";
41
- } else {
42
- re += ".*";
43
- }
44
- continue;
45
- }
46
-
47
- if (c === "*") { re += "[^/]*"; continue; }
48
- if (c === "?") { re += "[^/]"; continue; }
49
-
50
- if ("\\.[]{}()+-^$|".includes(c)) re += "\\" + c;
51
- else re += c;
52
- }
53
- re += "$";
54
- return new RegExp(re);
55
- }
56
-
57
- function isIgnoredPosix(relPosix, ignoreMatchers) {
58
- for (const m of ignoreMatchers) {
59
- if (m instanceof RegExp) {
60
- if (m.test(relPosix)) return true;
61
- } else if (typeof m === "function") {
62
- // si l'user fournit une fonction ignorée, on lui passe le rel posix
63
- if (m(relPosix)) return true;
64
- } else if (typeof m === "string") {
65
- // strings glob déjà compilées dans ignoreMatchers (voir compileIgnoreMatchers)
66
- // (fallback: on ne devrait pas arriver ici)
67
- if (globToRegExp(m).test(relPosix)) return true;
68
- }
69
- }
70
- return false;
71
- }
72
-
73
- function compileIgnoreMatchers(ignoreList) {
74
- return (ignoreList || []).map((ig) => {
75
- if (ig instanceof RegExp) return ig;
76
- if (typeof ig === "function") return ig;
77
- // string glob -> RegExp
78
- return globToRegExp(ig);
79
- });
80
- }
81
-
82
- /**
83
- * createWatchers(rules, options)
84
- * - Watch les dossiers racines (baseDirs)
85
- * - Filtre include via globs (rule.patterns)
86
- * - Filtre ignore via globs/regex (globalIgnored + rule.ignored)
87
- */
88
- function createWatchers(rules, options = {}) {
89
- const opt = {
90
- cwd: process.cwd(),
91
-
92
- // Ignorés globaux (appliqués dans queue() — pas dans chokidar)
93
- globalIgnored: ["**/node_modules/**", "**/.git/**", "**/dist/**"],
94
-
95
- ignoreInitial: true,
96
- awaitWriteFinish: { stabilityThreshold: 80, pollInterval: 10 },
97
-
98
- // Si FS weird (OneDrive, réseau, WSL/Docker bind mount), mets true:
99
- usePolling: false,
100
- interval: 200,
101
- binaryInterval: 300,
102
-
103
- debug: true,
104
- ...options,
105
- };
106
-
107
- const handles = [];
108
-
109
- for (const rule of rules) {
110
- if (!rule || typeof rule.callback !== "function") {
111
- throw new Error("Each rule must have a callback(events, ctx).");
112
- }
113
-
114
- const name = rule.name || "rule";
115
- const patterns = normalizePatterns(rule.patterns);
116
-
117
- // include matchers (relatif à cwd, en posix)
118
- const includeMatchers = patterns.map(globToRegExp);
119
-
120
- // ignore matchers (relatif à cwd, en posix)
121
- const ignoreMatchers = compileIgnoreMatchers([
122
- ...(opt.globalIgnored || []),
123
- ...(rule.ignored || []),
124
- ]);
125
-
126
- // baseDirs à watcher
127
- const baseDirs = Array.from(new Set(patterns.map(globBaseDir).map((d) => d || ".")))
128
- .map((d) => path.resolve(opt.cwd, d));
129
-
130
- if (opt.debug) {
131
- console.log(`\n[${name}] starting watcher`);
132
- console.log(" cwd:", opt.cwd);
133
- console.log(" patterns:", patterns);
134
- console.log(" baseDirs:", baseDirs);
135
- console.log(" ignored (applied in queue):", [...(opt.globalIgnored || []), ...(rule.ignored || [])]);
136
- console.log("");
137
- }
138
-
139
- const pending = new Map();
140
- let timer = null;
141
- let running = false;
142
- let rerun = false;
143
-
144
- const flush = async () => {
145
- if (running) { rerun = true; return; }
146
- running = true;
147
- try {
148
- do {
149
- rerun = false;
150
- const batch = Array.from(pending.values());
151
- pending.clear();
152
- if (batch.length) await rule.callback(batch, { rule });
153
- } while (rerun);
154
- } finally {
155
- running = false;
156
- }
157
- };
158
-
159
- const queue = (type, absPath) => {
160
- const rel = toPosix(path.relative(opt.cwd, absPath));
161
-
162
- // ignore d'abord
163
- if (isIgnoredPosix(rel, ignoreMatchers)) {
164
- // if (opt.debug) console.log(`[${name}] ignored`, type, rel);
165
- return;
166
- }
167
-
168
- // include ensuite
169
- const ok = includeMatchers.some((rx) => rx.test(rel));
170
- if (!ok) return;
171
-
172
- if (opt.debug) console.log(`[${name}] queue`, type, rel);
173
-
174
- pending.set(`${type}:${rel}`, { type, file: rel });
175
- clearTimeout(timer);
176
- timer = setTimeout(flush, rule.debounceMs ?? 150);
177
- };
178
-
179
- // ⚠️ IMPORTANT: on ne met PAS "ignored" ici
180
- const watcher = chokidar.watch(baseDirs, {
181
- ignoreInitial: opt.ignoreInitial,
182
- awaitWriteFinish: opt.awaitWriteFinish,
183
- persistent: true,
184
- usePolling: opt.usePolling,
185
- interval: opt.interval,
186
- binaryInterval: opt.binaryInterval,
187
- });
188
-
189
- // watcher.on("ready", () => {
190
- // console.log(`[${name}] ready`);
191
- // });
192
-
193
- // watcher.on("add", (p) => queue("add", p));
194
- watcher.on("change", (p) => queue("change", p));
195
- // watcher.on("unlink", (p) => queue("unlink", p));
196
- watcher.on("error", (err) => console.error(`[${name}] watch error:`, err));
197
-
198
- handles.push({
199
- watcher,
200
- stopTimers: () => { clearTimeout(timer); pending.clear(); }
201
- });
202
- }
203
-
204
- return {
205
- close: async () => {
206
- for (const h of handles) h.stopTimers();
207
- await Promise.all(handles.map((h) => h.watcher.close()));
208
- },
209
- };
210
- }
211
-
212
-
213
- const buildCSS = async (inputScss, outCssMin, options = {}) => {
214
- let compiled;
215
- try {
216
- compiled = sass.compile(inputScss, {
217
- loadPaths: [path.resolve(process.cwd(), "./node_modules")],
218
- style: "compressed",
219
- sourceMap: false,
220
- sourceMapIncludeSources: false,
221
- ...options
222
- });
223
- const minified = csso.minify(compiled.css, { restructure: false });
224
- fs.writeFileSync(outCssMin, minified.css);
225
- console.log(`✅ CSS generated: ${outCssMin}`);
226
- } catch (err) {
227
- console.error("❌ Sass compile error:");
228
- console.error(err?.formatted || err?.message || err);
229
- }
230
- }
231
-
232
-
233
- const buildJS = async (entry, outfile, options = {}) => {
234
- try {
235
- await esbuild.build({
236
- entryPoints: [entry],
237
- outfile,
238
- bundle: true,
239
- platform: "browser",
240
- logLevel: "error",
241
- treeShaking: true,
242
- minify: true,
243
- supported: { "template-literal": false },
244
- target: ["es2020"],
245
- legalComments: "none",
246
- ...options
247
- });
248
- console.log(`✅ JS generated: ${outfile}`);
249
- } catch (err) {
250
- console.error("❌ esbuild build failed.");
251
- if (err?.errors?.length) {
252
- const formatted = await esbuild.formatMessages(err.errors, {
253
- kind: "error",
254
- color: true,
255
- terminalWidth: process.stdout.columns || 80,
256
- });
257
- console.error(formatted.join("\n"));
258
- } else {
259
- console.error(err);
260
- }
261
- }
262
- }
263
-
264
-
265
- const buildPHP = async (file) => {
266
- const results = await pxpros.render(file);
267
- if(results.success) {
268
- results.files.forEach(file => console.log(`✅ HTML generated: ${file}`));
269
- } else {
270
- console.error("❌ pxpros render failed.");
271
- console.error(results.error);
272
- }
273
- }
274
-
275
-
276
- const buildSitemap = async (file) => {
277
- const results = await pxpros.sitemap(file);
278
- if(results.success) {
279
- results.files.forEach(file => console.log(`✅ XML Sitemap generated: ${file}`));
280
- } else {
281
- console.error("❌ pxpros sitemap creation failed.");
282
- console.error(results.error);
283
- }
284
- }
285
-
286
-
287
- async function emptyDir(dir) {
288
- await fsprom.mkdir(dir, { recursive: true });
289
- const entries = await fsprom.readdir(dir, { withFileTypes: true });
290
- await Promise.all(entries.map((e) => fsprom.rm(path.join(dir, e.name), { recursive: true, force: true })));
291
- }
292
-
293
-
294
- function formatFrDate(dateInput = new Date(), timeZone = 'America/Toronto') {
295
- const d = (dateInput instanceof Date) ? dateInput : new Date(dateInput);
296
- const fmt = new Intl.DateTimeFormat('fr-CA', {
297
- weekday: 'long',
298
- day: 'numeric',
299
- month: 'long',
300
- year: 'numeric',
301
- hour: 'numeric',
302
- minute: '2-digit',
303
- hour12: false,
304
- timeZone
305
- });
306
- const parts = Object.fromEntries(fmt.formatToParts(d).map(p => [p.type, p.value]));
307
- const weekday = parts.weekday.charAt(0).toUpperCase() + parts.weekday.slice(1); // "Samedi"
308
- return `${weekday} le ${parts.day} ${parts.month} ${parts.year} à ${parts.hour} h ${parts.minute}`;
309
- }
310
-
311
-
312
- function norm(p) {
313
- // normalise en chemin POSIX pour compat .gitignore
314
- return p.split(path.sep).join('/');
315
- }
316
-
317
- async function loadGitignore() {
318
- const ig = ignore();
319
- const giPath = path.join(ROOT, '.gitignore');
320
- if (fs.existsSync(giPath)) {
321
- const txt = await fsprom.readFile(giPath, 'utf8');
322
- ig.add(txt);
323
- }
324
- // on ignore aussi le dossier dist par sécurité (pas nécessaire mais sain)
325
- ig.add('dist/');
326
- return ig;
327
- }
328
-
329
- async function rmDir(dir) {
330
- await fsprom.rm(dir, { recursive: true, force: true });
331
- await fsprom.mkdir(dir, { recursive: true });
332
- }
333
-
334
- function shouldExcludeFile(relFromRoot, absPath) {
335
- // Exclusions de type/extension
336
- const lower = absPath.toLowerCase();
337
- if (path.basename(lower).startsWith('_')) return true;
338
- if (lower.endsWith('.scss')) return true;
339
- if (lower.endsWith('.js') && !lower.endsWith('.min.js')) return true;
340
- return false;
341
- }
342
-
343
- async function copyFilePreserveTree(absSrc, src, dist, ig) {
344
- const relFromSrc = path.relative(src, absSrc);
345
- const relFromRoot = path.relative(ROOT, absSrc);
346
- const relPosix = norm(relFromRoot);
347
-
348
- // 1) Exclusions via .gitignore
349
- if (ig.ignores(relPosix)) return false;
350
-
351
- // 2) Exclusions spécifiques (scss, js non minifiés)
352
- if (shouldExcludeFile(relPosix, absSrc)) return false;
353
-
354
- const absDst = path.join(dist, relFromSrc);
355
- // console.log(absDst);
356
- await fsprom.mkdir(path.dirname(absDst), { recursive: true });
357
- await fsprom.copyFile(absSrc, absDst);
358
- return absDst;
359
- }
360
-
361
- async function walkAndCopy(dir, src, dest, ig, stats, banner = null) {
362
- const entries = await fsprom.readdir(dir, { withFileTypes: true });
363
- const bannerContent = (await fsprom.readFile(banner || path.join(__dirname, 'banner.txt'), 'utf8')).replace(/###DATE###/, formatFrDate());
364
- const today = (d => `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')}`)(new Date());
365
-
366
- for (const de of entries) {
367
- const abs = path.join(dir, de.name);
368
- const relFromRoot = path.relative(ROOT, abs);
369
- const relPosix = norm(relFromRoot);
370
-
371
- if (de.isDirectory()) {
372
- // Si le dossier est ignoré par .gitignore, on ne descend pas
373
- if (ig.ignores(relPosix + '/')) continue;
374
- if(de.name.startsWith('_')) continue;
375
- await walkAndCopy(abs, src, dest, ig, stats, banner);
376
- } else if (de.isFile()) {
377
- const copied = await copyFilePreserveTree(abs, src, dest, ig);
378
- if (copied) {
379
- const lower = abs.toLowerCase();
380
- if (lower.endsWith('.js')) await fsprom.writeFile(copied, "/*!\n\n" + bannerContent + "\n\n*/\n" + (await fsprom.readFile(copied, 'utf8')), "utf8");
381
- else if (lower.endsWith('.css')) await fsprom.writeFile(copied, "/*!\n\n" + bannerContent + "\n\n*/\n" + (await fsprom.readFile(copied, 'utf8')), "utf8");
382
- else if (lower.endsWith('.html')) await fsprom.writeFile(copied, "<!--\n\n" + bannerContent + "\n\n\-->\n" + (await fsprom.readFile(copied, 'utf8'))
383
- .replaceAll(/###YEAR###/g, (new Date).getFullYear())
384
- .replaceAll(/###TIMESTAMP###/g, Math.floor(Date.now() / 1000)), "utf8");
385
- else if (lower.endsWith('sitemap.xml')) await fsprom.writeFile(copied, (await fsprom.readFile(copied, 'utf8')).replaceAll(/###TODAY###/g, today), "utf8");
386
- stats.copied++;
387
- }
388
- else stats.skipped++;
389
- }
390
- // (symlinks & autres: ignorés)
391
- }
392
- }
393
-
394
-
395
- const exportDist = async (src, dist, banner = null) => {
396
- try {
397
- const ig = await loadGitignore();
398
-
399
- await emptyDir(dist);
400
- if (!fs.existsSync(src)) throw new error('Folder src is invalid.');
401
-
402
- const stats = { copied: 0, skipped: 0 };
403
- await walkAndCopy(src, src, dist, ig, stats, banner);
404
-
405
- return stats;
406
- } catch (err) {
407
- throw new error(err);
408
- }
409
- }
410
-
411
-
412
- module.exports = { createWatchers, exportDist, buildCSS, buildJS, buildPHP, buildSitemap };