Repository navigation
Expand file tree
/
Copy pathphp.js
More file actions
281 lines (281 loc) · 11.5 KB
/
Copy pathphp.js
File metadata and controls
281 lines (281 loc) · 11.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
import { Command } from "commander";
import * as readline from "readline";
import * as path from "path";
import * as fs from "fs/promises";
import { PHPEngine, getDefaultExtensions } from "../PHPEngine.js";
import { normalizePhpRegistryName } from "../runtime/Reflection.js";
import { NodeJSExtension } from "../extensions/nodejs.js";
export async function runCLI(rawArgs) {
const disabledExts = new Set();
for (let i = 0; i < rawArgs.length; i++) {
if (rawArgs[i] === "--disable-extension" && rawArgs[i + 1]) {
disabledExts.add(rawArgs[i + 1].toLowerCase());
}
}
const allExtensions = [...getDefaultExtensions(), new NodeJSExtension()].filter((ext) => !disabledExts.has(ext.name.toLowerCase()));
const engine = new PHPEngine({ watch: false, extensions: allExtensions });
const program = new Command();
program
.name("php")
.description("JSPHP Command Line Interface")
.version(`PHP ${PHPEngine.VERSION} (cli) (built: Jan 1 2026 00:00:00) (jsphp)\nCopyright (c) The PHP Group\nZend Engine v4.5.0, Copyright (c) Zend Technologies`, "-v, --version")
.allowUnknownOption(true)
.helpOption("-h, --help", "Display this help message")
.option("-a, --interactive", "Run interactively (REPL)")
.option("-c <path>", "Look for php.ini file in the specified directory")
.option("-n", "No php.ini file will be used")
.option("-d, --define <setting=value...>", "Define INI entry setting with value")
.option("-e", "Generate extended information for debugger/profiler")
.option("-f, --file <file>", "Parse and execute <file>")
.option("-i, --info", "PHP information")
.option("-l, --syntax-check", "Syntax check only (lint)")
.option("-m, --modules", "Show loaded modules")
.option("-r, --run <code>", "Run PHP <code> without using script tags <?..?>")
.option("-B, --process-begin <code>", "Run PHP <code> before processing input lines")
.option("-R, --process-line <code>", "Run PHP <code> for every input line")
.option("-F, --process-file <file>", "Parse and execute <file> for every input line")
.option("-E, --process-end <code>", "Run PHP <code> after processing input lines")
.option("-s, --syntax-highlight", "Output HTML syntax highlighted source code")
.option("-w, --strip", "Output source with stripped comments and whitespace")
.option("--ini", "Show configuration file names")
.option("--rf, --reflection-function <function>", "Show information about function")
.option("--rc, --reflection-class <class>", "Show information about class")
.option("--re, --reflection-extension <extension>", "Show information about extension")
.option("--ri, --reflection-info <extension>", "Show configuration for extension")
.argument("[script]", "Script file to execute")
.argument("[args...]", "Arguments passed to script");
program.parse(rawArgs, { from: "user" });
const options = program.opts();
const args = program.args;
if (rawArgs.includes("-v") || rawArgs.includes("--version")) {
console.log(`PHP ${PHPEngine.VERSION} (cli) (built: Jan 1 2026 00:00:00) (jsphp)`);
console.log("Copyright (c) The PHP Group");
console.log("Zend Engine v4.5.0, Copyright (c) Zend Technologies");
return;
}
if (options.modules) {
console.log("[PHP Modules]");
console.log("Core");
const moduleNames = Object.keys(engine.extensions).sort();
for (const mod of moduleNames) {
console.log(mod);
}
console.log("[Zend Modules]");
return;
}
if (options.ini) {
console.log("Configuration File (php.ini) Path: (none)");
console.log("Loaded Configuration File: (none)");
console.log("Scan for additional .ini files in: (none)");
console.log("Additional .ini files parsed: (none)");
return;
}
if (options.info) {
console.log(`phpinfo()`);
console.log(`PHP Version => ${PHPEngine.VERSION}`);
console.log(`System => ${process.platform} ${process.arch}`);
console.log(`Build Date => Jan 1 2026 00:00:00`);
console.log(`Server API => Command Line Interface`);
console.log(`Virtual Directory Support => disabled`);
console.log(`Configuration File (php.ini) Path => (none)`);
console.log(`Loaded Configuration File => (none)`);
return;
}
if (options.reflectionFunction) {
const fnName = normalizePhpRegistryName(String(options.reflectionFunction));
const fn = engine.functions[fnName];
if (fn !== undefined) {
console.log(`Function [ <internal:${fnName}> function ${fnName} ] { }`);
}
else {
console.error(`Function ${options.reflectionFunction} does not exist`);
process.exit(1);
}
return;
}
if (options.reflectionClass) {
const clsName = normalizePhpRegistryName(String(options.reflectionClass));
const cls = engine.classes[clsName];
if (cls) {
console.log(`Class [ <user> class ${cls.name || options.reflectionClass} ] { }`);
}
else {
console.error(`Class ${options.reflectionClass} does not exist`);
process.exit(1);
}
return;
}
if (options.reflectionExtension || options.reflectionInfo) {
const extName = String(options.reflectionExtension || options.reflectionInfo).toLowerCase();
const ext = engine.extensions[extName];
if (ext) {
console.log(`Extension [ <persistent> extension # ${ext.name} version ${ext.version} ] { }`);
}
else {
console.error(`Extension ${extName} does not exist`);
process.exit(1);
}
return;
}
if (options.syntaxCheck) {
const fileToLint = options.file || args[0];
if (!fileToLint) {
console.error("No input file specified for syntax check.");
process.exit(1);
}
const resolvedPath = path.resolve(fileToLint);
try {
const code = await fs.readFile(resolvedPath, "utf8");
await engine.compileCode(code, resolvedPath);
console.log(`No syntax errors detected in ${resolvedPath}`);
return;
}
catch (err) {
console.error(`Parse error: ${err.message} in ${resolvedPath}`);
process.exit(255);
}
}
if (options.run) {
const scriptArgs = ["-r", ...args];
const ctx = engine.createContext({
stdout: (data) => process.stdout.write(data),
stderr: (data) => process.stderr.write(data),
superglobals: {
server: {
argv: scriptArgs,
argc: String(scriptArgs.length),
},
},
});
try {
await ctx.eval(options.run, "cli code");
}
catch (err) {
if (err?.name === "PHPExit") {
process.exit(err.status || 0);
}
throw err;
}
return;
}
if (options.processLine || options.processFile || options.processBegin || options.processEnd) {
const ctx = engine.createContext({
stdout: (data) => process.stdout.write(data),
stderr: (data) => process.stderr.write(data),
});
if (options.processBegin) {
await ctx.eval(options.processBegin, "process-begin");
}
if (options.processLine || options.processFile) {
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
terminal: false,
});
for await (const line of rl) {
ctx.setVar("arg", line);
if (options.processLine) {
await ctx.eval(options.processLine, "process-line");
}
if (options.processFile) {
await ctx.require(path.resolve(options.processFile));
}
}
}
if (options.processEnd) {
await ctx.eval(options.processEnd, "process-end");
}
return;
}
if (options.interactive) {
console.log("Interactive shell");
console.log("");
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
prompt: "php > ",
});
const ctx = engine.createContext({
stdout: (data) => process.stdout.write(data),
stderr: (data) => process.stderr.write(data),
});
let buffer = "";
rl.prompt();
rl.on("line", async (line) => {
const trimmed = line.trim();
if ((trimmed === "exit" || trimmed === "quit" || trimmed === "exit;") && !buffer) {
rl.close();
return;
}
buffer += (buffer ? "\n" : "") + line;
const openBraces = (buffer.match(/\{/g) || []).length;
const closeBraces = (buffer.match(/\}/g) || []).length;
const openParens = (buffer.match(/\(/g) || []).length;
const closeParens = (buffer.match(/\)/g) || []).length;
if (openBraces > closeBraces || openParens > closeParens) {
rl.setPrompt(" > ");
rl.prompt();
return;
}
rl.setPrompt("php > ");
let codeToEval = buffer.trim();
buffer = "";
if (codeToEval && !codeToEval.endsWith(";") && !codeToEval.endsWith("}") && !codeToEval.endsWith("{")) {
codeToEval += ";";
}
const beforeLen = ctx.outputText.length;
try {
await ctx.eval(codeToEval, "php shell code");
if (ctx.outputText.length > beforeLen && !ctx.outputText.endsWith("\n")) {
process.stdout.write("\n");
}
}
catch (err) {
if (err.name === "PHPExit") {
process.exit(err.status || 0);
}
else if (err.getPHPStackTraceString) {
console.error("Fatal error:", err.message);
console.error(err.getPHPStackTraceString());
}
else {
console.error("Error:", err.message || err);
}
}
rl.prompt();
});
return;
}
const fileToExec = options.file || args[0];
if (fileToExec) {
const filePath = path.resolve(fileToExec);
const positionalArgs = options.file ? args : args.slice(1);
const scriptArgs = [filePath, ...positionalArgs];
const ctx = engine.createContext({
cwd: path.dirname(filePath),
stdout: (data) => process.stdout.write(data),
stderr: (data) => process.stderr.write(data),
superglobals: {
server: {
SCRIPT_FILENAME: filePath,
SCRIPT_NAME: filePath,
PHP_SELF: filePath,
argv: scriptArgs,
argc: String(scriptArgs.length),
},
},
});
try {
await ctx.require(filePath);
}
catch (err) {
if (err?.name === "PHPExit") {
process.exit(err.status || 0);
}
throw err;
}
return;
}
program.help();
}
//# sourceMappingURL=php.js.map