@kirigami/php-prepros 1.1.0 → 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/src/prepros.js CHANGED
@@ -8,6 +8,7 @@ import { walkFile } from '@kirigami/struct-walker';
8
8
  import { getPHPRuntime, getPHPRuntimeWithNetwork } from "@kirigami/php-wasm";
9
9
 
10
10
 
11
+ const __modules = new Map;
11
12
  const __project = process.cwd();
12
13
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
13
14
  const __configpath = path.join(__project, 'kirigami.yaml');
@@ -20,14 +21,19 @@ const config = await walkFile(__configpath);
20
21
  if(!config) throw `Invalid config file: ${__configpath}`;
21
22
 
22
23
 
23
-
24
24
  const getPHPInstance = async () => {
25
25
  if(!__php) {
26
26
  if(config?.kirigami?.root === undefined) throw `Missing prepros:root property in config file: ${__configpath}`;
27
27
  __root = path.join(__project, config.kirigami.root);
28
28
  if (!fs.existsSync(__root)) throw `Invalid prepros:root path: ${__root}`;
29
29
 
30
+ if(!config.image) config.image = {};
31
+ if(!config.image.format) config.image.format = 'webp';
32
+ if(!config.image.source) config.image.source = 'assets/images/';
33
+ if(!config.image.dest) config.image.dest = 'images/';
34
+
30
35
  const preprosConfig = config.prepros || {};
36
+ preprosConfig.image = config.image;
31
37
  preprosConfig.timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
32
38
  preprosConfig.root = joinWith('/project/', config?.kirigami?.root);
33
39
  preprosConfig.data = config.kirigami || {};
@@ -35,24 +41,35 @@ const getPHPInstance = async () => {
35
41
  __php = await (preprosConfig.network ? getPHPRuntimeWithNetwork() : getPHPRuntime());
36
42
  __php.setSpawnHandler((command, args, options) => spawn(command, args, options));
37
43
  __php.preprosConfig = preprosConfig;
44
+ __php.setIniValues({
45
+ log_errors: 1,
46
+ html_errors: 0,
47
+ display_errors: 1,
48
+ error_reporting: 32767,
49
+ error_log: 'php://stderr',
50
+ memory_limit: '2G',
51
+ });
52
+
53
+ await mountPath(__dirname, '/prepros', __php);
54
+ await mountPath(joinWith(__project, config?.kirigami?.root), joinWith('/project', config?.kirigami?.root), __php);
38
55
 
39
56
  const __cache = path.join(__project, '.cache.db');
40
57
  const __cookie = path.join(__project, '.cookie.txt');
41
- mountPath(__php, __dirname, '/prepros');
42
- mountPath(__php, joinWith(__project, config?.kirigami?.root), joinWith('/project', config?.kirigami?.root));
43
-
44
- if (fs.existsSync(__cache)) mountPath(__php, __cache, '/project/.cache.db');
45
- if (fs.existsSync(__cookie)) mountPath(__php, __cookie, '/project/.cookie.txt');
58
+ if (fs.existsSync(__cache)) await mountPath(__cache, '/project/.cache.db', __php);
59
+ if (fs.existsSync(__cookie)) await mountPath(__cookie, '/project/.cookie.txt', __php);
46
60
 
47
- __php.onMessage(async (data) => {
61
+ __php.onMessage(async data => {
48
62
  const msg = JSON.parse(data);
49
63
  if(!msg.command) return { success: false, error: "Invalid command."};
50
64
  const file = path.join(__dirname, 'phpjs', `${msg.command}.js`);
51
65
  if(!fs.existsSync(file)) return { success: false, error: "Invalid command."};
52
-
53
66
  try {
54
- const cmdModule = await import(pathToFileURL(file).href);
55
- const results = await cmdModule.default(__php, msg);
67
+ if(!__modules.has(msg.command)) {
68
+ const cmdModule = await import(pathToFileURL(file).href);
69
+ __modules.set(msg.command, cmdModule);
70
+ }
71
+ const mod = __modules.get(msg.command);
72
+ const results = await mod.default(__php, msg);
56
73
  return JSON.stringify({
57
74
  success: true,
58
75
  results: results
@@ -63,35 +80,36 @@ const getPHPInstance = async () => {
63
80
  error: typeof e == 'string' ? e : e.message
64
81
  }
65
82
  }
66
-
67
-
68
- // console.log(msg);
69
- // console.log(__dirname);
70
-
71
83
  });
72
84
  }
73
85
  return __php;
74
86
  }
75
87
 
76
88
 
77
- const mountPath = (php, localPath, virtualDir) => {
89
+ const mountPath = async (localPath, virtualDir, php) => {
90
+ php = php || await getPHPInstance();
91
+ if(!path.isAbsolute(localPath)) localPath = path.join(__project, localPath);
92
+ virtualDir = virtualDir || path.posix.join('/project', localPath.replace(__project + path.sep, ''));
78
93
  const includeExtensions = new Set(['.php', '.json', '.yaml', '.yml', '.md', '.db', '.txt', ...(config?.prepros?.mountext || [])]);
79
94
  const stat = fs.statSync(localPath);
80
95
  if (stat.isDirectory()) {
81
96
  php.mkdirTree(virtualDir);
82
97
  for (const entry of fs.readdirSync(localPath, { withFileTypes: true })) {
83
- mountPath(php, path.join(localPath, entry.name), virtualDir + '/' + entry.name);
84
- }
85
- } else {
86
- const ext = path.extname(localPath).toLowerCase();
87
- if (includeExtensions.has(ext)) {
88
- const buf = fs.readFileSync(localPath);
89
- const parentDir = virtualDir.substring(0, virtualDir.lastIndexOf('/'));
90
- if (parentDir) {
91
- php.mkdirTree(parentDir);
98
+ const target = path.join(localPath, entry.name);
99
+ if(!fs.statSync(target).isDirectory()) {
100
+ const ext = path.extname(target).toLowerCase();
101
+ if (includeExtensions.has(ext)) {
102
+ await mountPath(target, virtualDir + '/' + entry.name, php);
103
+ }
104
+ } else {
105
+ await mountPath(target, virtualDir + '/' + entry.name, php);
92
106
  }
93
- php.writeFile(virtualDir, isBinary(buf) ? buf : buf.toString('utf8'));
94
107
  }
108
+ } else {
109
+ const buf = fs.readFileSync(localPath);
110
+ const parentDir = virtualDir.substring(0, virtualDir.lastIndexOf('/'));
111
+ if (parentDir) php.mkdirTree(parentDir);
112
+ php.writeFile(virtualDir, isBinary(buf) ? buf : buf.toString('utf8'));
95
113
  }
96
114
  }
97
115
 
@@ -129,7 +147,6 @@ const run = async (args = [], script = null, mountfiles = []) => {
129
147
  const buffer = php.readFileAsBuffer(resultPath);
130
148
  retobj = JSON.parse(Buffer.from(buffer).toString('utf8'));
131
149
  retobj.debug = stdout;
132
-
133
150
  if(retobj.files) await Promise.all(retobj.files.map(async (file, i) => {
134
151
  const fbuffer = php.readFileAsBuffer(file);
135
152
  const dest = file.replace(/^\/project\//i, '');
@@ -167,29 +184,19 @@ const runenv = async (script, paths = [], ...args) => {
167
184
 
168
185
 
169
186
  const render = async (file = '.') => {
170
- const php = await getPHPInstance();
171
- const target = path.resolve(__project, config?.kirigami?.root, file);
187
+ const target = path.resolve(config?.kirigami?.root, file);
172
188
  const fsvm = path.join('/project', config?.kirigami?.root, file).replace(/\\/g, '/');
173
- mountPath(php, target, fsvm);
174
- if(config?.prepros?.before) {
175
- const include = path.resolve(__project, config?.kirigami?.root, config?.prepros?.before);
176
- const dest = path.join('/project', config?.kirigami?.root, config?.prepros?.before).replace(/\\/g, '/');
177
- mountPath(php, include, dest);
178
- }
179
- if(config?.prepros?.after) {
180
- const include = path.resolve(__project, config?.kirigami?.root, config?.prepros?.after);
181
- const dest = path.join('/project', config?.kirigami?.root, config?.prepros?.after).replace(/\\/g, '/');
182
- mountPath(php, include, dest);
183
- }
189
+ await mountPath(target);
190
+ if(config?.prepros?.before) await mountPath(path.resolve(config?.kirigami?.root, config?.prepros?.before));
191
+ if(config?.prepros?.after) await mountPath(path.resolve(config?.kirigami?.root, config?.prepros?.after));
184
192
  return run([fsvm]);
185
193
  }
186
194
 
187
195
 
188
- const sitemap = async (dir) => {
189
- const php = await getPHPInstance();
190
- mountPath(php, __root, '/project/' + config?.kirigami?.root);
196
+ const sitemap = async () => {
197
+ await mountPath(config?.kirigami?.root);
191
198
  return run(['sitemap']);
192
199
  }
193
200
 
194
201
 
195
- export { runenv, render, sitemap };
202
+ export { runenv, render, sitemap, mountPath };
@@ -0,0 +1,30 @@
1
+ import { stat } from 'node:fs/promises';
2
+
3
+ /**
4
+ * Gets a file's information.
5
+ * @param {string} filePath - the file path
6
+ * @returns {Promise<object>} object holding the file's info
7
+ */
8
+ async function getFileStats(filePath) {
9
+ try {
10
+ const stats = await stat(filePath);
11
+
12
+ return {
13
+ exists: true,
14
+ isFile: stats.isFile(),
15
+ isDirectory: stats.isDirectory(),
16
+ size: stats.size, // in bytes
17
+ createdAt: stats.birthtime,
18
+ modifiedAt: stats.mtime,
19
+ accessedAt: stats.atime,
20
+ changedAt: stats.ctime, // last metadata change
21
+ };
22
+ } catch (error) {
23
+ if (error.code === 'ENOENT') {
24
+ return { exists: false };
25
+ }
26
+ throw error; // other errors (permissions, etc.)
27
+ }
28
+ }
29
+
30
+ export default getFileStats;
@@ -1,5 +1,5 @@
1
- // Table de magic bytes compilée une seule fois au module load
2
- // Clé = premier octet, valeur = liste de signatures [octets, longueur à lire]
1
+ // Magic-bytes table compiled once at module load
2
+ // Key = first byte, value = list of signatures [bytes, length to read]
3
3
  const MAGIC_MAP = new Map([
4
4
  [0x89, [[[0x89, 0x50, 0x4E, 0x47]]]], // PNG
5
5
  [0xFF, [[[0xFF, 0xD8, 0xFF]], [[0xFF, 0xFE]]]], // JPEG, UTF-16 LE (text BOM)
@@ -14,20 +14,20 @@ const MAGIC_MAP = new Map([
14
14
  [0x00, [[[0x00, 0x00, 0xFE, 0xFF]]]], // UTF-32 BE BOM (text!)
15
15
  ]);
16
16
 
17
- // BOMs texte → jamais binaire, early exit immédiat
17
+ // Text BOMs → never binary, immediate early exit
18
18
  const TEXT_BOMS = new Set([
19
19
  '\xEF\xBB\xBF', // UTF-8
20
20
  '\xFF\xFE', // UTF-16 LE
21
21
  '\xFE\xFF', // UTF-16 BE
22
22
  ]);
23
23
 
24
- const SAMPLE_SIZE = 512; // 8 Ko c'est inutile pour un mount local
24
+ const SAMPLE_SIZE = 512; // 8 KB is overkill for a local mount
25
25
 
26
26
  /**
27
- * Détection binaire en 3 passes avec early exit agressif.
28
- * Optimisé pour être appelé sur des centaines de fichiers au démarrage.
27
+ * 3-pass binary detection with aggressive early exit.
28
+ * Optimized to be called on hundreds of files at startup.
29
29
  *
30
- * @param {Buffer} buf - Buffer du fichier (readFileSync)
30
+ * @param {Buffer} buf - the file's Buffer (readFileSync)
31
31
  * @returns {boolean}
32
32
  */
33
33
  function isBinary(buf) {
@@ -36,16 +36,16 @@ function isBinary(buf) {
36
36
 
37
37
  const b0 = buf[0];
38
38
 
39
- // Passe 1 — Magic bytes (O(1), max 4 comparaisons)
39
+ // Pass 1 — Magic bytes (O(1), max 4 comparisons)
40
40
  const candidates = MAGIC_MAP.get(b0);
41
41
  if (candidates) {
42
42
  for (const sigs of candidates) {
43
43
  for (const sig of sigs) {
44
44
  if (sig.every((b, i) => buf[i] === b)) {
45
- // BOM texte → sortie immédiate "text"
45
+ // Text BOM → immediate "text" exit
46
46
  if (b0 === 0xEF || b0 === 0xFE || (b0 === 0xFF && buf[1] === 0xFE)) return false;
47
47
  if (b0 === 0x00 && buf[1] === 0x00) return false; // UTF-32
48
- return true; // Binaire confirmé
48
+ return true; // Binary confirmed
49
49
  }
50
50
  }
51
51
  }
@@ -53,17 +53,17 @@ function isBinary(buf) {
53
53
 
54
54
  const sample = Math.min(len, SAMPLE_SIZE);
55
55
 
56
- // Passe 2 — Octet NUL (signal binaire quasi-certain, très fréquent dans les binaires)
57
- // indexOf est natif C++ → beaucoup plus rapide qu'une boucle JS
56
+ // Pass 2 — NUL byte (a near-certain binary signal, very common in binaries)
57
+ // indexOf is native C++ → much faster than a JS loop
58
58
  if (buf.indexOf(0x00, 0, 'binary') !== -1 &&
59
59
  buf.indexOf(0x00) < sample) return true;
60
60
 
61
- // Passe 3 — Ratio statistique sur l'échantillon
61
+ // Pass 3 — Statistical ratio over the sample
62
62
  let nonText = 0;
63
63
  for (let i = 0; i < sample; i++) {
64
64
  const b = buf[i];
65
65
  if (b < 0x20) {
66
- // Caractères de contrôle légitimes en texte
66
+ // Control characters that are legitimate in text
67
67
  if (b !== 0x09 && b !== 0x0A && b !== 0x0D && b !== 0x0C && b !== 0x1B) {
68
68
  nonText++;
69
69
  }
package/src/utils.inc.php CHANGED
@@ -1,11 +1,4 @@
1
1
  <?php
2
- ini_set('log_errors', 1);
3
- ini_set('html_errors', 0);
4
- ini_set('display_errors', 1);
5
- ini_set('error_reporting', 32767);
6
- ini_set('error_log', 'php://stderr');
7
- error_reporting(E_ALL);
8
-
9
2
 
10
3
  const BR = '<br>';
11
4
  const RN = "\r\n";
@@ -14,13 +7,16 @@ const R = "\r";
14
7
  const N = "\n";
15
8
 
16
9
 
17
- function _print_r(mixed $obj) {
10
+ function _print_r(mixed $obj, bool $ret = false) {
11
+ if($ret) return '<pre>' . print_r($obj, $ret) . '</pre>';
18
12
  echo '<pre>' . print_r($obj, true) . '</pre>';
19
13
  }
20
14
 
21
15
 
22
16
  spl_autoload_register(function ($class) {
23
17
  static $catalog = [
18
+
19
+ // Out of the box
24
20
  'ARR' => 'arr.class.php',
25
21
  'CACHE' => 'cache.class.php',
26
22
  'CURL' => 'curl.class.php',
@@ -28,12 +24,17 @@ spl_autoload_register(function ($class) {
28
24
  'HTML' => 'html.class.php',
29
25
  'IMG' => 'img.class.php',
30
26
  'MD' => 'md.class.php',
27
+ 'NORM' => 'norm.class.php',
31
28
  'OBF' => 'obf.class.php',
32
29
  'PREPROS' => 'prepros.class.php',
30
+ 'SCHEMA' => 'schema.class.php',
33
31
  'SCRAPER' => 'scraper.class.php',
34
32
  'STD' => 'std.class.php',
35
33
  'STR' => 'str.class.php',
36
34
  'YAML' => 'yaml.class.php',
35
+
36
+ // Fallbacks
37
+ 'Normalizer' => 'normalizer.class.php'
37
38
  ];
38
39
  if (isset($catalog[$class])) require_once(__DIR__ . '/libraries/' . $catalog[$class]);
39
40
  }, true, true);