@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/README.md +1114 -869
- package/index.d.ts +75 -2
- package/index.js +1 -1
- package/package.json +4 -4
- package/src/libraries/curl.class.php +5 -0
- package/src/libraries/html.class.php +23 -22
- package/src/libraries/img.class.php +356 -16
- package/src/libraries/md.class.php +476 -170
- package/src/libraries/md.plugins.php +19 -19
- package/src/libraries/normalizer.class.php +310 -0
- package/src/libraries/prepros.class.php +36 -7
- package/src/libraries/prepros.plugins.php +24 -0
- package/src/libraries/schema.class.php +550 -0
- package/src/libraries/scraper.class.php +64 -59
- package/src/libraries/str.class.php +49 -15
- package/src/libraries/yaml.class.php +66 -66
- package/src/phpjs/fstat.js +9 -0
- package/src/prepros.js +51 -44
- package/src/utils/getfilestats.js +30 -0
- package/src/utils/isbinary.js +14 -14
- package/src/utils.inc.php +9 -8
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(
|
|
42
|
-
|
|
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
|
|
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
|
-
|
|
55
|
-
|
|
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 = (
|
|
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
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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
|
|
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(
|
|
174
|
-
if(config?.prepros?.before)
|
|
175
|
-
|
|
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 (
|
|
189
|
-
|
|
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;
|
package/src/utils/isbinary.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
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
|
|
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
|
|
24
|
+
const SAMPLE_SIZE = 512; // 8 KB is overkill for a local mount
|
|
25
25
|
|
|
26
26
|
/**
|
|
27
|
-
*
|
|
28
|
-
*
|
|
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 -
|
|
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
|
-
//
|
|
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
|
|
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; //
|
|
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
|
-
//
|
|
57
|
-
// indexOf
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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);
|