Skip to content

Commit 0998ea2

Browse files
committed
Always use a real path to PHP
1 parent e1a5ebe commit 0998ea2

File tree

5 files changed

+52
-43
lines changed

5 files changed

+52
-43
lines changed

.github/workflows/test.yml

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,10 @@ jobs:
1818
strategy:
1919
matrix:
2020
os:
21-
- macos-latest
21+
# - macos-latest
2222
# Testing on Linux is temporarily disabled until we can figure out the SUID sandboxing issue.
2323
# - ubuntu-latest
24-
# Testing on Windows is temporarily disabled because it's broken.
25-
# - windows-latest
24+
- windows-latest
2625
runs-on: ${{ matrix.os }}
2726
steps:
2827
- uses: actions/checkout@v4

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,10 +76,10 @@ Clone this repository, then `cd` into it and run:
7676
```shell
7777
composer run assets --working-dir=build
7878
npm install
79-
npm run start
79+
npm start
8080
```
8181

82-
To run tests, run `npm run test`. To test the full app bundle, run `npx electron-builder` and look for the final binary in the `dist` directory.
82+
To run tests, run `npm test`. To test the full app bundle, run `npx electron-builder` and look for the final binary in the `dist` directory.
8383

8484
## Limitations and alternatives
8585

src/main/installer.ts

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { access, appendFile, copyFile, type FileHandle, open } from 'node:fs/pro
88
import path from 'node:path';
99
import readline from 'node:readline';
1010
import { promisify as toPromise } from 'node:util';
11+
import { phpCommand } from './php';
1112

1213
// Create an awaitable version of execFile that won't block the main process,
1314
// which would produce a disconcerting beach ball on macOS.
@@ -25,7 +26,7 @@ async function createProject (win?: WebContents): Promise<void>
2526
// @todo Remove EventEmitter from the intersection type when Node's type
2627
// definitions are updated to reflect the documentation.
2728
// @see https://nodejs.org/docs/latest/api/fs.html#class-filehandle
28-
log = await open(installLog, 'w') as FileHandle&EventEmitter;
29+
log = await open(installLog, 'a') as FileHandle&EventEmitter;
2930

3031
// Invalidate the handle when the file is closed, so we don't try to write to
3132
// it accidentally.
@@ -37,33 +38,32 @@ async function createProject (win?: WebContents): Promise<void>
3738
log = null;
3839
}
3940

40-
const runComposer = (command: string[]) => {
41-
log?.write('\n>>> ' + command.join(' ') + '\n');
41+
// Always invoke Composer directly through the PHP interpreter, with a known set
42+
// of options.
43+
const _phpCommand = [
44+
...await phpCommand(),
45+
// We use an unpacked version of Composer because the phar file has a shebang
46+
// line that breaks us, due to GUI-launched Electron apps not inheriting the
47+
// parent environment in macOS and Linux.
48+
path.join('composer', 'bin', 'composer'),
49+
// Disable ANSI output (i.e., colors) so the log is readable.
50+
'--no-ansi',
51+
// We don't want Composer to ask us any questions, since we have no way for
52+
// the user to answer them.
53+
'--no-interaction',
54+
];
4255

56+
const runComposer = (command: string[]) => {
4357
// Always direct Composer to the created project root, unless we're about to
4458
// create it initially.
4559
if (command[0] !== 'create-project') {
4660
command.push(`--working-dir=${projectRoot}`);
4761
}
48-
command.push(
49-
// Disable ANSI output (i.e., colors) so the log is readable.
50-
'--no-ansi',
51-
// We don't want Composer to ask us any questions, since we have no way for
52-
// the user to answer them.
53-
'--no-interaction',
54-
);
55-
command.unshift(
56-
// Explicitly pass the cURL CA bundle so that HTTPS requests from Composer can
57-
// succeed on Windows.
58-
'-d',
59-
'curl.cainfo=' + path.join(bin, 'cacert.pem'),
60-
// We use an unpacked version of Composer because the phar file has a shebang
61-
// line that breaks us, due to GUI-launched Electron apps not inheriting the
62-
// parent environment in macOS and Linux.
63-
path.join('composer', 'bin', 'composer'),
64-
);
62+
command.unshift(..._phpCommand);
63+
// For forensic purposes, log the complete command we're about to execute.
64+
log?.write('\n>>> ' + command.join(' ') + '\n');
6565

66-
const task = execFileAsPromise(path.join(bin, 'php'), command, {
66+
const task = execFileAsPromise(command[0], command.slice(1), {
6767
// Run from the `bin` directory so we can use a relative path to Composer.
6868
cwd: bin,
6969
// Send a customized copy of the current environment variables to Composer.

src/main/main.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { autoUpdater } from 'electron-updater';
66
import install from './installer';
77
import { readFile } from 'node:fs/promises';
88
import path from 'node:path';
9-
import startServer from './php-server';
9+
import { startServer } from './php';
1010
import * as Sentry from "@sentry/electron/main";
1111

1212
Sentry.init({

src/main/php-server.ts renamed to src/main/php.ts

Lines changed: 26 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,40 @@
1-
import { projectRoot, bin, webRoot } from './config';
1+
import { bin, webRoot } from './config';
2+
import { app } from 'electron';
23
import { default as getPort, portNumbers } from 'get-port';
34
import { type ChildProcess, execFile } from 'node:child_process';
5+
import { realpath } from 'node:fs/promises';
46
import path from 'node:path';
57
import readline from 'node:readline';
8+
import process from "node:process";
69

7-
export default async (): Promise<{ url: string, serverProcess: ChildProcess }> => {
10+
export async function phpCommand (command: string[] = []): Promise<string[]>
11+
{
12+
const phpBin = path.join(bin, process.platform === 'win32' ? 'php.exe' : 'php');
13+
const caFile = path.join(bin, 'cacert.pem');
14+
15+
return [
16+
app.isPackaged ? phpBin : await realpath(phpBin),
17+
// Explicitly pass the cURL CA bundle so that HTTPS requests from Drupal can
18+
// succeed on Windows.
19+
'-d', `curl.cainfo="${caFile}"`,
20+
...command,
21+
];
22+
}
23+
24+
export async function startServer (): Promise<{ url: string, serverProcess: ChildProcess }>
25+
{
826
const port = await getPort({
927
port: portNumbers(8888, 9999),
1028
});
1129
const url = `http://localhost:${port}`;
12-
const caFile = path.join(bin, 'cacert.pem');
30+
31+
const command = await phpCommand([
32+
'-S', url.substring(7),
33+
'.ht.router.php',
34+
]);
1335

1436
// Start the built-in PHP web server.
15-
const serverProcess = execFile(
16-
path.join(bin, 'php'),
17-
[
18-
// Explicitly pass the cURL CA bundle so that HTTPS requests from Drupal can
19-
// succeed on Windows.
20-
'-d', `curl.cainfo="${caFile}"`,
21-
'-S', url.substring(7),
22-
'.ht.router.php',
23-
],
24-
{
25-
cwd: webRoot,
26-
},
27-
);
37+
const serverProcess = execFile(command[0], command.slice(1), { cwd: webRoot });
2838
const resolveWith = { url, serverProcess };
2939

3040
return new Promise((resolve): void => {

0 commit comments

Comments
 (0)