chokibasic 1.1.11 → 1.2.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/index.d.ts CHANGED
@@ -1,5 +1,23 @@
1
-
2
1
  declare namespace chokibasic {
2
+ // --- NOUVEAUX TYPES POUR buildConf ---
3
+
4
+ /**
5
+ * Callback pour buildConf.
6
+ * Reçoit le contenu brut (Buffer) et le chemin relatif du fichier.
7
+ * Doit retourner la nouvelle valeur (string, objet, etc.) qui remplacera le chemin dans le JSON final.
8
+ */
9
+ export type BuildConfCallback = (content: Buffer, relPath: string) => any | Promise<any>;
10
+
11
+ /**
12
+ * Dictionnaire de patterns globaux associés à des callbacks de transformation.
13
+ * Exemple: { "** / *.mid": (buf) => processMidi(buf) }
14
+ */
15
+ export interface BuildConfMatchers {
16
+ [glob: string]: BuildConfCallback;
17
+ }
18
+
19
+ // --- TYPES EXISTANTS ---
20
+
3
21
  export type GlobPattern = string;
4
22
 
5
23
  export type IgnoreMatcher =
@@ -10,9 +28,7 @@ declare namespace chokibasic {
10
28
  export type WatchEventType = "add" | "change" | "unlink" | (string & {});
11
29
 
12
30
  export interface WatchEvent {
13
- /** Type d’évènement (dans le code actuel: surtout "change") */
14
31
  type: WatchEventType;
15
- /** Chemin relatif à cwd, normalisé POSIX (slash "/") */
16
32
  file: string;
17
33
  }
18
34
 
@@ -21,22 +37,10 @@ declare namespace chokibasic {
21
37
  }
22
38
 
23
39
  export interface WatchRule {
24
- /** Nom affiché en debug */
25
40
  name?: string;
26
-
27
- /** Globs d’inclusion (ex: "src/styles/ ** / *.scss") */
28
41
  patterns: GlobPattern | GlobPattern[];
29
-
30
- /** Ignorés additionnels (globs / regex / function) */
31
42
  ignored?: IgnoreMatcher[];
32
-
33
- /** Debounce en ms (défaut: 150) */
34
43
  debounceMs?: number;
35
-
36
- /**
37
- * Callback appelé avec un batch d’évènements.
38
- * Le batch contient des objets { type, file }.
39
- */
40
44
  callback: (events: WatchEvent[], ctx: WatchRuleContext) => void | Promise<void>;
41
45
  }
42
46
 
@@ -46,39 +50,18 @@ declare namespace chokibasic {
46
50
  }
47
51
 
48
52
  export interface CreateWatchersOptions {
49
- /** Répertoire racine utilisé pour calculer les chemins relatifs */
50
53
  cwd?: string;
51
-
52
- /**
53
- * Ignorés globaux (appliqués dans queue(), pas via chokidar "ignored")
54
- * Défaut: ["** /node_modules/ **","** /.git/ **","** /dist/ **"]
55
- */
56
54
  globalIgnored?: string[];
57
-
58
- /** chokidar: ignoreInitial */
59
55
  ignoreInitial?: boolean;
60
-
61
- /** chokidar: awaitWriteFinish */
62
56
  awaitWriteFinish?: boolean | AwaitWriteFinishOptions;
63
-
64
- /** chokidar: usePolling */
65
57
  usePolling?: boolean;
66
-
67
- /** chokidar: interval */
68
58
  interval?: number;
69
-
70
- /** chokidar: binaryInterval */
71
59
  binaryInterval?: number;
72
-
73
- /** Log console */
74
60
  debug?: boolean;
75
-
76
- /** Permet d’accepter d’autres options sans casser les types */
77
61
  [key: string]: unknown;
78
62
  }
79
63
 
80
64
  export interface WatchersController {
81
- /** Ferme tous les watchers et annule les timers */
82
65
  close: () => Promise<void>;
83
66
  }
84
67
 
@@ -87,59 +70,61 @@ declare namespace chokibasic {
87
70
  skipped: number;
88
71
  }
89
72
 
90
- /** Options forwardées à esbuild.build() */
91
- export type BuildJSOptions = Parameters<typeof import("esbuild").build>[0];
73
+ export interface ExportDistOptions {
74
+ ignore?: string[];
75
+ include?: string[];
76
+ debug?: boolean;
77
+ filter?: (relPath: string) => boolean;
78
+ }
92
79
 
93
- /** Options forwardées à sass.compile() */
80
+ export type BuildJSOptions = Parameters<typeof import("esbuild").build>[0];
94
81
  export type BuildCSSOptions = NonNullable<Parameters<typeof import("sass").compile>[1]>;
95
82
 
83
+ // --- FONCTIONS EXPORTÉES ---
84
+
96
85
  /**
97
- * Crée un ou plusieurs watchers (un par règle).
86
+ * Transforme un fichier YAML en JSON en appliquant des transformations sur les fichiers référencés.
87
+ * @param src Chemin vers le fichier source YAML.
88
+ * @param dst Chemin vers le fichier de destination JSON.
89
+ * @param matchers Dictionnaire de patterns et callbacks de transformation.
98
90
  */
91
+ export function buildConf(
92
+ src: string,
93
+ dst: string,
94
+ matchers?: BuildConfMatchers
95
+ ): Promise<void>;
96
+
99
97
  export function createWatchers(
100
98
  rules: WatchRule[],
101
99
  options?: CreateWatchersOptions
102
100
  ): WatchersController;
103
101
 
104
- /**
105
- * Exporte un dossier `src` vers `dist` en respectant .gitignore + exclusions.
106
- */
107
102
  export function exportDist(
108
103
  src: string,
109
104
  dist: string,
110
- banner?: string | null
105
+ banner?: string | null,
106
+ options?: ExportDistOptions
111
107
  ): Promise<ExportDistStats>;
112
108
 
113
- /**
114
- * Compile SCSS -> CSS minifié (csso), écrit dans outCssMin.
115
- */
116
109
  export function buildCSS(
117
110
  inputScss: string,
118
111
  outCssMin: string,
119
112
  options?: BuildCSSOptions
120
113
  ): Promise<void>;
121
114
 
122
- /**
123
- * Bundle/minify JS via esbuild.
124
- */
125
115
  export function buildJS(
126
116
  entry: string,
127
117
  outfile: string,
128
118
  options?: BuildJSOptions
129
119
  ): Promise<void>;
130
120
 
131
- /**
132
- * Rend un fichier via pxpros.render(file).
133
- */
134
121
  export function buildPHP(file: string): Promise<void>;
135
122
 
136
- /**
137
- * Génère un sitemap via pxpros.sitemap(file).
138
- */
139
123
  export function buildSitemap(file: string): Promise<void>;
140
124
  }
141
125
 
142
126
  declare const chokibasic: {
127
+ buildConf: typeof chokibasic.buildConf; // Ajouté ici aussi
143
128
  createWatchers: typeof chokibasic.createWatchers;
144
129
  exportDist: typeof chokibasic.exportDist;
145
130
  buildCSS: typeof chokibasic.buildCSS;
@@ -149,4 +134,4 @@ declare const chokibasic: {
149
134
  };
150
135
 
151
136
  export = chokibasic;
152
- export as namespace chokibasic;
137
+ export as namespace chokibasic;
package/index.js CHANGED
@@ -2,6 +2,7 @@ module.exports = {
2
2
  ...require("./src/buildjs.js"),
3
3
  ...require("./src/buildcss.js"),
4
4
  ...require("./src/buildphp.js"),
5
+ ...require("./src/buildconf.js"),
5
6
  ...require("./src/export.js"),
6
7
  ...require("./src/watchers.js"),
7
8
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chokibasic",
3
- "version": "1.1.11",
3
+ "version": "1.2.0",
4
4
  "description": "Basic chokidar watcher + pxpros + esbuild + sass + csso helpers",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -35,6 +35,8 @@
35
35
  "csso": "^5.0.5",
36
36
  "esbuild": "^0.28.0",
37
37
  "ignore": "^7.0.5",
38
+ "js-yaml": "^4.1.1",
39
+ "picomatch": "^4.0.4",
38
40
  "pxpros": "^1.1.3",
39
41
  "sass": "^1.99.0"
40
42
  }
package/readme.md CHANGED
@@ -9,7 +9,7 @@ Tiny build & watch helpers for simple static projects.
9
9
  - `buildJS(entry, outfile, options?)`
10
10
  - `buildPHP(file)`
11
11
  - `buildSitemap(file)`
12
- - `exportDist(src, dist, bannerPath?)`
12
+ - `exportDist(src, dist, bannerPath?, options?)`
13
13
 
14
14
  ---
15
15
 
@@ -0,0 +1,61 @@
1
+ const fs = require("fs").promises;
2
+ const path = require("path");
3
+ const yaml = require("js-yaml");
4
+ const pm = require("picomatch");
5
+
6
+ const ROOT = process.cwd();
7
+
8
+ /**
9
+ * Parcourt récursivement la configuration et transforme les valeurs
10
+ * si elles correspondent à un fichier existant et à un pattern défini.
11
+ */
12
+ async function walkAndTransform(node, matchers) {
13
+ if (Array.isArray(node)) {
14
+ return await Promise.all(node.map(item => walkAndTransform(item, matchers)));
15
+ }
16
+
17
+ if (typeof node === 'object' && node !== null) {
18
+ const newNode = {};
19
+ for (const [key, value] of Object.entries(node)) {
20
+ newNode[key] = await walkAndTransform(value, matchers);
21
+ }
22
+ return newNode;
23
+ }
24
+
25
+ if (typeof node === 'string') {
26
+ const fullPath = path.join(ROOT, node);
27
+ try {
28
+ const stats = await fs.stat(fullPath);
29
+ if (stats.isFile()) {
30
+ for (const [pattern, callback] of Object.entries(matchers)) {
31
+ const isMatch = pm(pattern);
32
+ if (isMatch(node)) {
33
+ const content = await fs.readFile(fullPath);
34
+ return await callback(content, node);
35
+ }
36
+ }
37
+ }
38
+ } catch (err) {
39
+ }
40
+ }
41
+
42
+ return node;
43
+ }
44
+
45
+ /**
46
+ * Fonction principale buildConf
47
+ */
48
+ async function buildConf(src, dst, matchers = {}) {
49
+ try {
50
+ const fileContents = await fs.readFile(src, 'utf8');
51
+ const rawData = yaml.load(fileContents);
52
+ const processedData = await walkAndTransform(rawData, matchers);
53
+ const jsonContent = JSON.stringify(processedData, null, 2);
54
+ await fs.writeFile(dst, jsonContent, 'utf8');
55
+ console.log(`✅ Build de la configuration réussi : ${dst}`);
56
+ } catch (error) {
57
+ console.error("❌ Erreur lors du buildConf :", error);
58
+ }
59
+ }
60
+
61
+ module.exports = { buildConf };
package/src/export.js CHANGED
@@ -5,14 +5,14 @@ const path = require("path");
5
5
 
6
6
  const ROOT = process.cwd();
7
7
 
8
-
9
8
  async function emptyDir(dir) {
10
9
  await fsprom.mkdir(dir, { recursive: true });
11
10
  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 })));
11
+ await Promise.all(entries.map((e) =>
12
+ fsprom.rm(path.join(dir, e.name), { recursive: true, force: true })
13
+ ));
13
14
  }
14
15
 
15
-
16
16
  function formatFrDate(dateInput = new Date(), timeZone = 'America/Toronto') {
17
17
  const d = (dateInput instanceof Date) ? dateInput : new Date(dateInput);
18
18
  const fmt = new Intl.DateTimeFormat('fr-CA', {
@@ -26,109 +26,164 @@ function formatFrDate(dateInput = new Date(), timeZone = 'America/Toronto') {
26
26
  timeZone
27
27
  });
28
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"
29
+ const weekday = parts.weekday.charAt(0).toUpperCase() + parts.weekday.slice(1);
30
30
  return `${weekday} le ${parts.day} ${parts.month} ${parts.year} à ${parts.hour} h ${parts.minute}`;
31
31
  }
32
32
 
33
-
34
33
  function norm(p) {
35
- // normalise en chemin POSIX pour compat .gitignore
36
34
  return p.split(path.sep).join('/');
37
35
  }
38
36
 
39
- async function loadGitignore() {
37
+ async function loadGitignore(src, extraPatterns = []) {
40
38
  const ig = ignore();
41
39
  const giPath = path.join(ROOT, '.gitignore');
40
+
42
41
  if (fs.existsSync(giPath)) {
43
42
  const txt = await fsprom.readFile(giPath, 'utf8');
44
- ig.add(txt);
43
+
44
+ // ⚠️ IMPORTANT: on convert les patterns ROOT → relatifs à src
45
+ const lines = txt.split('\n')
46
+ .map(l => l.trim())
47
+ .filter(l => l && !l.startsWith('#'))
48
+ .map(l => {
49
+ // enlever "/" initial si présent
50
+ return l.replace(/^\//, '');
51
+ });
52
+
53
+ ig.add(lines);
45
54
  }
46
- // on ignore aussi le dossier dist par sécurité (pas nécessaire mais sain)
55
+
56
+ // Toujours ignorer dist (relatif ROOT → on garde tel quel)
47
57
  ig.add('dist/');
48
- return ig;
49
- }
50
58
 
51
- async function rmDir(dir) {
52
- await fsprom.rm(dir, { recursive: true, force: true });
53
- await fsprom.mkdir(dir, { recursive: true });
59
+ // 👇 Patterns custom (déjà relatifs à src)
60
+ if (Array.isArray(extraPatterns)) {
61
+ ig.add(extraPatterns);
62
+ }
63
+
64
+ return ig;
54
65
  }
55
66
 
56
- function shouldExcludeFile(relFromRoot, absPath) {
57
- // Exclusions de type/extension
67
+ function shouldExcludeFile(relFromSrc, absPath) {
58
68
  const lower = absPath.toLowerCase();
69
+
59
70
  if (path.basename(lower).startsWith('_')) return true;
60
71
  if (lower.endsWith('.scss')) return true;
61
72
  if (lower.endsWith('.js') && !lower.endsWith('.min.js')) return true;
73
+
62
74
  return false;
63
75
  }
64
76
 
65
77
  async function copyFilePreserveTree(absSrc, src, dist, ig) {
66
78
  const relFromSrc = path.relative(src, absSrc);
67
- const relFromRoot = path.relative(ROOT, absSrc);
68
- const relPosix = norm(relFromRoot);
79
+ const relPosix = norm(relFromSrc);
69
80
 
70
- // 1) Exclusions via .gitignore
81
+ // 1) ignore basé sur src
71
82
  if (ig.ignores(relPosix)) return false;
72
83
 
73
- // 2) Exclusions spécifiques (scss, js non minifiés)
84
+ // 2) exclusions internes
74
85
  if (shouldExcludeFile(relPosix, absSrc)) return false;
75
86
 
76
87
  const absDst = path.join(dist, relFromSrc);
77
- // console.log(absDst);
88
+
78
89
  await fsprom.mkdir(path.dirname(absDst), { recursive: true });
79
90
  await fsprom.copyFile(absSrc, absDst);
91
+
80
92
  return absDst;
81
93
  }
82
94
 
83
95
  async function walkAndCopy(dir, src, dest, ig, stats, banner = null) {
84
96
  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());
97
+
98
+ const bannerContent = (await fsprom.readFile(
99
+ banner || path.join(__dirname, 'banner.txt'),
100
+ 'utf8'
101
+ )).replace(/###DATE###/, formatFrDate());
102
+
103
+ const today = (d =>
104
+ `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')}`
105
+ )(new Date());
87
106
 
88
107
  for (const de of entries) {
89
108
  const abs = path.join(dir, de.name);
90
- const relFromRoot = path.relative(ROOT, abs);
91
- const relPosix = norm(relFromRoot);
109
+ const relFromSrc = path.relative(src, abs);
110
+ const relPosix = norm(relFromSrc);
92
111
 
93
112
  if (de.isDirectory()) {
94
- // Si le dossier est ignoré par .gitignore, on ne descend pas
95
113
  if (ig.ignores(relPosix + '/')) continue;
96
- if(de.name.startsWith('_')) continue;
114
+ if (de.name.startsWith('_')) continue;
115
+
97
116
  await walkAndCopy(abs, src, dest, ig, stats, banner);
98
- } else if (de.isFile()) {
117
+ }
118
+ else if (de.isFile()) {
99
119
  const copied = await copyFilePreserveTree(abs, src, dest, ig);
120
+
100
121
  if (copied) {
101
122
  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");
123
+
124
+ if (lower.endsWith('.js')) {
125
+ await fsprom.writeFile(
126
+ copied,
127
+ "/*!\n\n" + bannerContent + "\n\n*/\n" +
128
+ (await fsprom.readFile(copied, 'utf8')),
129
+ "utf8"
130
+ );
131
+ }
132
+ else if (lower.endsWith('.css')) {
133
+ await fsprom.writeFile(
134
+ copied,
135
+ "/*!\n\n" + bannerContent + "\n\n*/\n" +
136
+ (await fsprom.readFile(copied, 'utf8')),
137
+ "utf8"
138
+ );
139
+ }
140
+ else if (lower.endsWith('.html')) {
141
+ await fsprom.writeFile(
142
+ copied,
143
+ "<!--\n\n" + bannerContent + "\n\n-->\n" +
144
+ (await fsprom.readFile(copied, 'utf8'))
145
+ .replaceAll(/###YEAR###/g, (new Date).getFullYear())
146
+ .replaceAll(/###TIMESTAMP###/g, Math.floor(Date.now() / 1000)),
147
+ "utf8"
148
+ );
149
+ }
150
+ else if (lower.endsWith('sitemap.xml')) {
151
+ await fsprom.writeFile(
152
+ copied,
153
+ (await fsprom.readFile(copied, 'utf8'))
154
+ .replaceAll(/###TODAY###/g, today),
155
+ "utf8"
156
+ );
157
+ }
158
+
108
159
  stats.copied++;
109
160
  }
110
- else stats.skipped++;
161
+ else {
162
+ stats.skipped++;
163
+ }
111
164
  }
112
- // (symlinks & autres: ignorés)
113
165
  }
114
166
  }
115
167
 
116
-
117
- const exportDist = async (src, dist, banner = null) => {
168
+ const exportDist = async (src, dist, banner = null, options = {}) => {
118
169
  try {
119
- const ig = await loadGitignore();
170
+ const ig = await loadGitignore(src, options.ignore || []);
120
171
 
121
172
  await emptyDir(dist);
122
- if (!fs.existsSync(src)) throw new error('Folder src is invalid.');
173
+
174
+ if (!fs.existsSync(src)) {
175
+ throw new Error('Folder src is invalid.');
176
+ }
123
177
 
124
178
  const stats = { copied: 0, skipped: 0 };
179
+
125
180
  await walkAndCopy(src, src, dist, ig, stats, banner);
126
181
 
127
182
  return stats;
183
+
128
184
  } catch (err) {
129
- throw new error(err);
185
+ throw new Error(err);
130
186
  }
131
- }
132
-
187
+ };
133
188
 
134
- module.exports = { exportDist };
189
+ module.exports = { exportDist };