pressship 0.1.12 → 0.1.14
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 +13 -0
- package/assets/web/app.js +7322 -0
- package/assets/web/harness-sdk-icon-mono.svg +33 -0
- package/assets/web/harness-sdk-icon.svg +120 -0
- package/assets/web/index.html +392 -0
- package/assets/web/style.css +7906 -0
- package/dist/cli.js +14 -0
- package/dist/cli.js.map +1 -1
- package/dist/plugin/demo.d.ts +75 -4
- package/dist/plugin/demo.js +520 -31
- package/dist/plugin/demo.js.map +1 -1
- package/dist/utils/paths.d.ts +5 -0
- package/dist/utils/paths.js +15 -0
- package/dist/utils/paths.js.map +1 -1
- package/dist/web/ai-assistance.d.ts +70 -0
- package/dist/web/ai-assistance.js +262 -0
- package/dist/web/ai-assistance.js.map +1 -0
- package/dist/web/index.d.ts +9 -0
- package/dist/web/index.js +34 -0
- package/dist/web/index.js.map +1 -0
- package/dist/web/jobs.d.ts +33 -0
- package/dist/web/jobs.js +155 -0
- package/dist/web/jobs.js.map +1 -0
- package/dist/web/open-url.d.ts +1 -0
- package/dist/web/open-url.js +13 -0
- package/dist/web/open-url.js.map +1 -0
- package/dist/web/plugin-check-state.d.ts +47 -0
- package/dist/web/plugin-check-state.js +107 -0
- package/dist/web/plugin-check-state.js.map +1 -0
- package/dist/web/plugin-check.d.ts +11 -0
- package/dist/web/plugin-check.js +124 -0
- package/dist/web/plugin-check.js.map +1 -0
- package/dist/web/ports.d.ts +2 -0
- package/dist/web/ports.js +38 -0
- package/dist/web/ports.js.map +1 -0
- package/dist/web/registry.d.ts +49 -0
- package/dist/web/registry.js +106 -0
- package/dist/web/registry.js.map +1 -0
- package/dist/web/release.d.ts +87 -0
- package/dist/web/release.js +417 -0
- package/dist/web/release.js.map +1 -0
- package/dist/web/server.d.ts +31 -0
- package/dist/web/server.js +1453 -0
- package/dist/web/server.js.map +1 -0
- package/dist/web/settings.d.ts +36 -0
- package/dist/web/settings.js +72 -0
- package/dist/web/settings.js.map +1 -0
- package/dist/web/version-state.d.ts +28 -0
- package/dist/web/version-state.js +118 -0
- package/dist/web/version-state.js.map +1 -0
- package/dist/wordpress-org/publish.d.ts +1 -0
- package/dist/wordpress-org/publish.js +4 -2
- package/dist/wordpress-org/publish.js.map +1 -1
- package/dist/wordpress-org/submit.d.ts +1 -0
- package/dist/wordpress-org/submit.js +7 -5
- package/dist/wordpress-org/submit.js.map +1 -1
- package/package.json +4 -1
package/dist/plugin/demo.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { mkdir, rm, writeFile } from "node:fs/promises";
|
|
3
|
+
import { createServer as createNetServer } from "node:net";
|
|
3
4
|
import { homedir } from "node:os";
|
|
4
5
|
import path from "node:path";
|
|
5
6
|
import { execa } from "execa";
|
|
@@ -10,23 +11,106 @@ import { ui } from "../ui.js";
|
|
|
10
11
|
import { ensureCacheDir, pathExists } from "../utils/paths.js";
|
|
11
12
|
const playgroundCliPackage = "@wp-playground/cli@latest";
|
|
12
13
|
const compatibilityMuPluginPath = "/wordpress/wp-content/mu-plugins/pressship-playground-compat.php";
|
|
14
|
+
const defaultMysqlDatabasePrefix = "pressship_playground";
|
|
15
|
+
const managedMysqlContainerName = "pressship-playground-mariadb";
|
|
16
|
+
const managedMysqlImage = "mariadb:10.6";
|
|
17
|
+
const managedMysqlPassword = "pressship";
|
|
13
18
|
const compatibilityMuPlugin = `<?php
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
}
|
|
19
|
+
$pressship_deprecated_mask = (defined('E_DEPRECATED') ? E_DEPRECATED : 0) | (defined('E_USER_DEPRECATED') ? E_USER_DEPRECATED : 0);
|
|
20
|
+
if ($pressship_deprecated_mask) {
|
|
21
|
+
error_reporting(error_reporting() & ~$pressship_deprecated_mask);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function pressship_silence_deprecated_errors($severity) {
|
|
25
|
+
$deprecated_mask = (defined('E_DEPRECATED') ? E_DEPRECATED : 0) | (defined('E_USER_DEPRECATED') ? E_USER_DEPRECATED : 0);
|
|
26
|
+
return $deprecated_mask && (($severity & $deprecated_mask) !== 0);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
set_error_handler('pressship_silence_deprecated_errors');
|
|
18
30
|
|
|
19
31
|
if (! function_exists('is_plugin_active')) {
|
|
20
32
|
require_once ABSPATH . 'wp-admin/includes/plugin.php';
|
|
21
33
|
}
|
|
34
|
+
|
|
35
|
+
function pressship_remove_frame_options_header() {
|
|
36
|
+
remove_action('admin_init', 'send_frame_options_header');
|
|
37
|
+
remove_action('login_init', 'send_frame_options_header');
|
|
38
|
+
if (function_exists('header_remove')) {
|
|
39
|
+
header_remove('X-Frame-Options');
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
pressship_remove_frame_options_header();
|
|
44
|
+
|
|
45
|
+
function pressship_filter_playground_headers($headers) {
|
|
46
|
+
unset($headers['X-Frame-Options']);
|
|
47
|
+
unset($headers['x-frame-options']);
|
|
48
|
+
return $headers;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
add_filter('wp_headers', 'pressship_filter_playground_headers');
|
|
52
|
+
|
|
53
|
+
add_action('init', 'pressship_remove_frame_options_header', 0);
|
|
54
|
+
add_action('admin_init', 'pressship_remove_frame_options_header', 0);
|
|
55
|
+
add_action('admin_init', 'pressship_remove_frame_options_header', PHP_INT_MAX);
|
|
56
|
+
add_action('login_init', 'pressship_remove_frame_options_header', 0);
|
|
57
|
+
add_action('login_init', 'pressship_remove_frame_options_header', PHP_INT_MAX);
|
|
58
|
+
add_action('send_headers', 'pressship_remove_frame_options_header', PHP_INT_MAX);
|
|
59
|
+
|
|
60
|
+
function pressship_ensure_playground_admin() {
|
|
61
|
+
if (! username_exists('admin')) {
|
|
62
|
+
$user_id = wp_create_user('admin', 'password', 'admin@example.test');
|
|
63
|
+
if (! is_wp_error($user_id)) {
|
|
64
|
+
$user = new WP_User($user_id);
|
|
65
|
+
$user->set_role('administrator');
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (! is_admin() || is_user_logged_in() || ! isset($_GET['pressship_auto_login'])) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
$user = get_user_by('login', 'admin');
|
|
74
|
+
if (! $user) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
wp_set_current_user($user->ID);
|
|
79
|
+
wp_set_auth_cookie($user->ID);
|
|
80
|
+
wp_safe_redirect(remove_query_arg('pressship_auto_login'));
|
|
81
|
+
exit;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
add_action('init', 'pressship_ensure_playground_admin');
|
|
22
85
|
`;
|
|
86
|
+
const playgroundDatabaseModeSchema = z.enum(["auto", "sqlite", "mysql"]);
|
|
23
87
|
const demoOptionsSchema = z.object({
|
|
24
88
|
port: z.string().optional(),
|
|
25
89
|
wp: z.string().optional(),
|
|
26
90
|
php: z.string().optional(),
|
|
91
|
+
database: playgroundDatabaseModeSchema.default("auto"),
|
|
92
|
+
mysqlHost: z.string().trim().min(1).default("127.0.0.1"),
|
|
93
|
+
mysqlPort: z.coerce.number().int().min(1).max(65535).default(3306),
|
|
94
|
+
mysqlUser: z.string().trim().min(1).default("root"),
|
|
95
|
+
mysqlPassword: z.string().default(""),
|
|
96
|
+
mysqlDatabasePrefix: z
|
|
97
|
+
.string()
|
|
98
|
+
.trim()
|
|
99
|
+
.regex(/^[A-Za-z0-9_]+$/, "MySQL database prefix can only contain letters, numbers, and underscores.")
|
|
100
|
+
.min(1)
|
|
101
|
+
.max(40)
|
|
102
|
+
.default(defaultMysqlDatabasePrefix),
|
|
27
103
|
reset: z.boolean().default(false),
|
|
28
104
|
skipBrowser: z.boolean().default(false)
|
|
29
105
|
});
|
|
106
|
+
export class PlaygroundRuntimeUnsupportedError extends Error {
|
|
107
|
+
details;
|
|
108
|
+
constructor(message, details) {
|
|
109
|
+
super(message);
|
|
110
|
+
this.details = details;
|
|
111
|
+
this.name = "PlaygroundRuntimeUnsupportedError";
|
|
112
|
+
}
|
|
113
|
+
}
|
|
30
114
|
export async function demo(target, rawOptions = {}) {
|
|
31
115
|
const options = demoOptionsSchema.parse(rawOptions);
|
|
32
116
|
ui.intro("Start plugin demo");
|
|
@@ -37,6 +121,7 @@ export async function demo(target, rawOptions = {}) {
|
|
|
37
121
|
ui.keyValue("Slug", plan.slug);
|
|
38
122
|
ui.keyValue("WordPress", plan.wpVersion ?? "default");
|
|
39
123
|
ui.keyValue("PHP", plan.phpVersion ?? "default");
|
|
124
|
+
ui.keyValue("Database", describePlaygroundDatabase(plan.database));
|
|
40
125
|
ui.keyValue("Site", ui.path(plan.siteDir));
|
|
41
126
|
if (plan.url) {
|
|
42
127
|
ui.keyValue("URL", ui.path(plan.url));
|
|
@@ -45,36 +130,58 @@ export async function demo(target, rawOptions = {}) {
|
|
|
45
130
|
if (options.reset) {
|
|
46
131
|
await ui.task("Resetting persisted Playground site", () => resetPlaygroundSite(plan.siteDir), () => "Reset complete");
|
|
47
132
|
}
|
|
133
|
+
assertDemoLaunchPlanSupported(plan);
|
|
134
|
+
await prepareDemoRuntime(plan, { resetDatabase: options.reset });
|
|
48
135
|
ui.info("Starting WordPress Playground. Press Ctrl+C to stop the local server.");
|
|
49
136
|
await execa(plan.command, plan.args, {
|
|
50
137
|
cwd: plan.cwd,
|
|
51
138
|
stdio: "inherit"
|
|
52
139
|
});
|
|
53
140
|
}
|
|
54
|
-
export async function createDemoLaunchPlan(target,
|
|
141
|
+
export async function createDemoLaunchPlan(target, rawOptions) {
|
|
142
|
+
const options = demoOptionsSchema.parse(rawOptions);
|
|
55
143
|
const resolvedTarget = target ?? process.cwd();
|
|
56
144
|
if (isLocalPluginTarget(resolvedTarget)) {
|
|
57
145
|
const project = await discoverPluginProject(resolvedTarget);
|
|
58
|
-
const blueprintPath = await writeDemoBlueprint(project.slug, createLocalDemoBlueprint(project));
|
|
59
146
|
const playgroundOptions = resolveDemoPlaygroundOptions(options, {
|
|
60
147
|
wp: project.headers.requiresAtLeast ?? project.readme?.requiresAtLeast,
|
|
61
148
|
php: project.headers.requiresPhp ?? project.readme?.requiresPhp
|
|
62
149
|
});
|
|
150
|
+
const databaseMode = resolvePlaygroundDatabaseMode(playgroundOptions);
|
|
151
|
+
const playgroundCwd = await createPlaygroundRuntimeCwd(project.rootDir, project.slug, playgroundOptions, databaseMode);
|
|
152
|
+
const blueprintPath = await writeDemoBlueprint(project.slug, createLocalDemoBlueprint(project));
|
|
63
153
|
const url = options.port ? `http://127.0.0.1:${options.port}` : undefined;
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
154
|
+
const database = await createPlaygroundDatabasePlan({
|
|
155
|
+
mode: databaseMode,
|
|
156
|
+
runtimeCwd: playgroundCwd,
|
|
157
|
+
siteDir: getPlaygroundSiteDirForRuntime(playgroundCwd, databaseMode),
|
|
67
158
|
slug: project.slug,
|
|
68
|
-
|
|
69
|
-
|
|
159
|
+
sourcePath: project.rootDir,
|
|
160
|
+
options: playgroundOptions
|
|
161
|
+
});
|
|
162
|
+
const args = database.mode === "mysql"
|
|
163
|
+
? createPlaygroundServerArgs({
|
|
164
|
+
blueprintPath,
|
|
165
|
+
mount: `${project.rootDir}:/wordpress/wp-content/plugins/${project.slug}`,
|
|
166
|
+
database,
|
|
167
|
+
options: playgroundOptions
|
|
168
|
+
})
|
|
169
|
+
: createPlaygroundStartArgs({
|
|
70
170
|
path: project.rootDir,
|
|
71
171
|
blueprintPath,
|
|
72
172
|
mount: `${project.rootDir}:/wordpress/wp-content/plugins/${project.slug}`,
|
|
73
173
|
options: playgroundOptions
|
|
74
|
-
})
|
|
75
|
-
|
|
174
|
+
});
|
|
175
|
+
return {
|
|
176
|
+
source: "local",
|
|
177
|
+
name: project.headers.pluginName,
|
|
178
|
+
slug: project.slug,
|
|
179
|
+
command: "npx",
|
|
180
|
+
args,
|
|
181
|
+
cwd: playgroundCwd,
|
|
76
182
|
blueprintPath,
|
|
77
|
-
siteDir:
|
|
183
|
+
siteDir: getPlaygroundSiteDirForRuntime(playgroundCwd, databaseMode),
|
|
184
|
+
database,
|
|
78
185
|
wpVersion: playgroundOptions.wp,
|
|
79
186
|
phpVersion: playgroundOptions.php,
|
|
80
187
|
url
|
|
@@ -82,26 +189,44 @@ export async function createDemoLaunchPlan(target, options) {
|
|
|
82
189
|
}
|
|
83
190
|
const slug = slugFromHostedTarget(resolvedTarget);
|
|
84
191
|
const plugin = await fetchHostedPluginInfo(slug);
|
|
85
|
-
const blueprintPath = await writeDemoBlueprint(slug, createHostedDemoBlueprint(slug));
|
|
86
|
-
const demoDir = path.dirname(blueprintPath);
|
|
87
192
|
const playgroundOptions = resolveDemoPlaygroundOptions(options, {
|
|
88
193
|
wp: plugin.requires,
|
|
89
194
|
php: plugin.requiresPhp
|
|
90
195
|
});
|
|
196
|
+
const blueprintPath = await writeDemoBlueprint(slug, createHostedDemoBlueprint(slug));
|
|
197
|
+
const demoDir = path.dirname(blueprintPath);
|
|
198
|
+
const databaseMode = resolvePlaygroundDatabaseMode(playgroundOptions);
|
|
199
|
+
const playgroundCwd = await createPlaygroundRuntimeCwd(demoDir, slug, playgroundOptions, databaseMode);
|
|
91
200
|
const url = options.port ? `http://127.0.0.1:${options.port}` : undefined;
|
|
201
|
+
const database = await createPlaygroundDatabasePlan({
|
|
202
|
+
mode: databaseMode,
|
|
203
|
+
runtimeCwd: playgroundCwd,
|
|
204
|
+
siteDir: getPlaygroundSiteDirForRuntime(playgroundCwd, databaseMode),
|
|
205
|
+
slug,
|
|
206
|
+
sourcePath: demoDir,
|
|
207
|
+
options: playgroundOptions
|
|
208
|
+
});
|
|
209
|
+
const args = database.mode === "mysql"
|
|
210
|
+
? createPlaygroundServerArgs({
|
|
211
|
+
blueprintPath,
|
|
212
|
+
database,
|
|
213
|
+
options: playgroundOptions
|
|
214
|
+
})
|
|
215
|
+
: createPlaygroundStartArgs({
|
|
216
|
+
path: demoDir,
|
|
217
|
+
blueprintPath,
|
|
218
|
+
options: playgroundOptions
|
|
219
|
+
});
|
|
92
220
|
return {
|
|
93
221
|
source: "wordpress.org",
|
|
94
222
|
name: plugin.name,
|
|
95
223
|
slug,
|
|
96
224
|
command: "npx",
|
|
97
|
-
args
|
|
98
|
-
|
|
99
|
-
blueprintPath,
|
|
100
|
-
options: playgroundOptions
|
|
101
|
-
}),
|
|
102
|
-
cwd: demoDir,
|
|
225
|
+
args,
|
|
226
|
+
cwd: playgroundCwd,
|
|
103
227
|
blueprintPath,
|
|
104
|
-
siteDir:
|
|
228
|
+
siteDir: getPlaygroundSiteDirForRuntime(playgroundCwd, databaseMode),
|
|
229
|
+
database,
|
|
105
230
|
wpVersion: playgroundOptions.wp,
|
|
106
231
|
phpVersion: playgroundOptions.php,
|
|
107
232
|
url
|
|
@@ -153,6 +278,7 @@ export function createPlaygroundCompatibilitySteps() {
|
|
|
153
278
|
];
|
|
154
279
|
}
|
|
155
280
|
export function createPlaygroundStartArgs(input) {
|
|
281
|
+
const options = demoOptionsSchema.parse(input.options);
|
|
156
282
|
return [
|
|
157
283
|
"--yes",
|
|
158
284
|
playgroundCliPackage,
|
|
@@ -166,24 +292,120 @@ export function createPlaygroundStartArgs(input) {
|
|
|
166
292
|
"WP_DEBUG_DISPLAY",
|
|
167
293
|
"false",
|
|
168
294
|
...(input.mount ? ["--no-auto-mount", `--mount=${input.mount}`] : []),
|
|
169
|
-
...(
|
|
170
|
-
...(
|
|
171
|
-
...(
|
|
172
|
-
...(
|
|
295
|
+
...(options.port ? [`--port=${options.port}`] : []),
|
|
296
|
+
...(options.wp ? [`--wp=${options.wp}`] : []),
|
|
297
|
+
...(options.php ? [`--php=${options.php}`] : []),
|
|
298
|
+
...(options.skipBrowser ? ["--skip-browser"] : [])
|
|
173
299
|
];
|
|
174
300
|
}
|
|
175
|
-
export function
|
|
301
|
+
export function createPlaygroundServerArgs(input) {
|
|
302
|
+
const options = demoOptionsSchema.parse(input.options);
|
|
303
|
+
const siteUrl = options.port ? `http://127.0.0.1:${options.port}` : undefined;
|
|
304
|
+
return [
|
|
305
|
+
"--yes",
|
|
306
|
+
playgroundCliPackage,
|
|
307
|
+
"server",
|
|
308
|
+
`--mount-before-install=${input.database.configPath}:/wordpress/wp-config.php`,
|
|
309
|
+
`--mount-before-install=${input.database.sqliteBypassDir}:/wordpress/wp-content/mu-plugins/sqlite-database-integration`,
|
|
310
|
+
"--skip-sqlite-setup",
|
|
311
|
+
`--blueprint=${input.blueprintPath}`,
|
|
312
|
+
...(input.mount ? [`--mount=${input.mount}`] : []),
|
|
313
|
+
...(options.port ? [`--port=${options.port}`] : []),
|
|
314
|
+
...(siteUrl ? [`--site-url=${siteUrl}`] : []),
|
|
315
|
+
...(options.wp ? [`--wp=${options.wp}`] : []),
|
|
316
|
+
...(options.php ? [`--php=${options.php}`] : [])
|
|
317
|
+
];
|
|
318
|
+
}
|
|
319
|
+
export function resolveDemoPlaygroundOptions(rawOptions, _requiredVersions) {
|
|
320
|
+
const options = demoOptionsSchema.parse(rawOptions);
|
|
321
|
+
const wp = normalizePlaygroundVersion(options.wp);
|
|
322
|
+
const explicitPhp = normalizePlaygroundVersion(options.php);
|
|
176
323
|
return {
|
|
177
324
|
...options,
|
|
178
|
-
wp
|
|
179
|
-
php:
|
|
325
|
+
wp,
|
|
326
|
+
php: explicitPhp ?? inferPlaygroundPhpVersionForWordPress(wp)
|
|
180
327
|
};
|
|
181
328
|
}
|
|
329
|
+
export function resolvePlaygroundDatabaseMode(options) {
|
|
330
|
+
if (options.database === "mysql") {
|
|
331
|
+
return "mysql";
|
|
332
|
+
}
|
|
333
|
+
if (options.database === "sqlite") {
|
|
334
|
+
return "sqlite";
|
|
335
|
+
}
|
|
336
|
+
const branch = parseWordPressBranch(options.wp);
|
|
337
|
+
return branch && !isWordPressBranchAtLeast(branch, 4, 7) ? "mysql" : "sqlite";
|
|
338
|
+
}
|
|
339
|
+
export async function prepareDemoRuntime(plan, options = {}) {
|
|
340
|
+
if (plan.database.mode !== "mysql") {
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
await mkdir(plan.siteDir, { recursive: true, mode: 0o700 });
|
|
344
|
+
await mkdir(plan.database.sqliteBypassDir, { recursive: true, mode: 0o700 });
|
|
345
|
+
await writeFile(plan.database.configPath, createMysqlWpConfig(plan.database), { mode: 0o600 });
|
|
346
|
+
await prepareMysqlDatabase(plan.database, { reset: options.resetDatabase ?? false });
|
|
347
|
+
}
|
|
348
|
+
export function publicDemoLaunchPlan(plan) {
|
|
349
|
+
if (plan.database.mode === "sqlite") {
|
|
350
|
+
return { ...plan, database: { mode: "sqlite" } };
|
|
351
|
+
}
|
|
352
|
+
const { host, port, user, database, server } = plan.database;
|
|
353
|
+
return {
|
|
354
|
+
...plan,
|
|
355
|
+
database: {
|
|
356
|
+
mode: "mysql",
|
|
357
|
+
host,
|
|
358
|
+
port,
|
|
359
|
+
user,
|
|
360
|
+
database,
|
|
361
|
+
server
|
|
362
|
+
}
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
export function inferPlaygroundPhpVersionForWordPress(wpVersion) {
|
|
366
|
+
const branch = parseWordPressBranch(wpVersion);
|
|
367
|
+
if (!branch) {
|
|
368
|
+
return undefined;
|
|
369
|
+
}
|
|
370
|
+
if (isWordPressBranchAtLeast(branch, 6, 9))
|
|
371
|
+
return "8.5";
|
|
372
|
+
if (isWordPressBranchAtLeast(branch, 6, 7))
|
|
373
|
+
return "8.4";
|
|
374
|
+
if (isWordPressBranchAtLeast(branch, 6, 4))
|
|
375
|
+
return "8.3";
|
|
376
|
+
if (isWordPressBranchAtLeast(branch, 6, 1))
|
|
377
|
+
return "8.2";
|
|
378
|
+
if (isWordPressBranchAtLeast(branch, 5, 9))
|
|
379
|
+
return "8.1";
|
|
380
|
+
if (isWordPressBranchAtLeast(branch, 5, 6))
|
|
381
|
+
return "8.0";
|
|
382
|
+
if (isWordPressBranchAtLeast(branch, 4, 7))
|
|
383
|
+
return "7.4";
|
|
384
|
+
return "5.2";
|
|
385
|
+
}
|
|
386
|
+
export function getPlaygroundRuntimeLimitation(wpVersion) {
|
|
387
|
+
const branch = parseWordPressBranch(wpVersion);
|
|
388
|
+
if (!branch || isWordPressBranchAtLeast(branch, 4, 7)) {
|
|
389
|
+
return undefined;
|
|
390
|
+
}
|
|
391
|
+
return (`WordPress Playground cannot currently run WordPress ${wpVersion} with its matching legacy PHP runtime. ` +
|
|
392
|
+
"The PHP 5.2 runtime cannot parse the current SQLite integration, and the MySQL path needs the old PHP mysql extension that Playground does not provide. " +
|
|
393
|
+
"Use Latest or WordPress 4.7+ in Playground, or test this version in a local PHP/MySQL environment outside Playground.");
|
|
394
|
+
}
|
|
395
|
+
export function assertDemoLaunchPlanSupported(plan) {
|
|
396
|
+
const limitation = getPlaygroundRuntimeLimitation(plan.wpVersion);
|
|
397
|
+
if (limitation) {
|
|
398
|
+
throw new PlaygroundRuntimeUnsupportedError(limitation, {
|
|
399
|
+
wpVersion: plan.wpVersion,
|
|
400
|
+
phpVersion: plan.phpVersion
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
}
|
|
182
404
|
export function getPlaygroundSiteDir(projectPath) {
|
|
183
405
|
const siteId = createHash("sha256").update(projectPath).digest("hex");
|
|
184
406
|
return path.join(homedir(), ".wordpress-playground", "sites", siteId);
|
|
185
407
|
}
|
|
186
|
-
async function resetPlaygroundSite(siteDir) {
|
|
408
|
+
export async function resetPlaygroundSite(siteDir) {
|
|
187
409
|
await rm(siteDir, { recursive: true, force: true });
|
|
188
410
|
}
|
|
189
411
|
async function writeDemoBlueprint(slug, blueprint) {
|
|
@@ -194,6 +416,260 @@ async function writeDemoBlueprint(slug, blueprint) {
|
|
|
194
416
|
await writeFile(blueprintPath, `${JSON.stringify(blueprint, null, 2)}\n`);
|
|
195
417
|
return blueprintPath;
|
|
196
418
|
}
|
|
419
|
+
async function createPlaygroundRuntimeCwd(sourcePath, slug, options, databaseMode) {
|
|
420
|
+
const cacheDir = await ensureCacheDir();
|
|
421
|
+
const runtimeId = createHash("sha256")
|
|
422
|
+
.update(`${sourcePath}\0wp=${options.wp ?? "latest"}\0php=${options.php ?? "latest"}\0db=${databaseMode}`)
|
|
423
|
+
.digest("hex");
|
|
424
|
+
const runtimeCwd = path.join(cacheDir, "playground-runtime", slug, runtimeId);
|
|
425
|
+
await mkdir(runtimeCwd, { recursive: true, mode: 0o700 });
|
|
426
|
+
return runtimeCwd;
|
|
427
|
+
}
|
|
428
|
+
function getPlaygroundSiteDirForRuntime(runtimeCwd, databaseMode) {
|
|
429
|
+
return databaseMode === "mysql" ? path.join(runtimeCwd, "wordpress") : getPlaygroundSiteDir(runtimeCwd);
|
|
430
|
+
}
|
|
431
|
+
async function createPlaygroundDatabasePlan(input) {
|
|
432
|
+
if (input.mode === "sqlite") {
|
|
433
|
+
return { mode: "sqlite" };
|
|
434
|
+
}
|
|
435
|
+
const database = createPlaygroundMysqlDatabaseName(input.options.mysqlDatabasePrefix, input.slug, `${input.sourcePath}\0wp=${input.options.wp ?? "latest"}\0php=${input.options.php ?? "latest"}`);
|
|
436
|
+
const configPath = path.join(input.siteDir, "wp-config.mysql.php");
|
|
437
|
+
const sqliteBypassDir = path.join(input.siteDir, "sqlite-database-integration");
|
|
438
|
+
const plan = {
|
|
439
|
+
mode: "mysql",
|
|
440
|
+
host: input.options.mysqlHost,
|
|
441
|
+
port: input.options.mysqlPort,
|
|
442
|
+
user: input.options.mysqlUser,
|
|
443
|
+
password: input.options.mysqlPassword,
|
|
444
|
+
database,
|
|
445
|
+
configPath,
|
|
446
|
+
sqliteBypassDir,
|
|
447
|
+
server: "external"
|
|
448
|
+
};
|
|
449
|
+
return plan;
|
|
450
|
+
}
|
|
451
|
+
function createPlaygroundMysqlDatabaseName(prefix, slug, key) {
|
|
452
|
+
const safePrefix = sanitizeMysqlIdentifier(prefix) || defaultMysqlDatabasePrefix;
|
|
453
|
+
const safeSlug = sanitizeMysqlIdentifier(slug) || "plugin";
|
|
454
|
+
const hash = createHash("sha256").update(key).digest("hex").slice(0, 10);
|
|
455
|
+
const maxSlugLength = Math.max(1, 64 - safePrefix.length - hash.length - 2);
|
|
456
|
+
return `${safePrefix}_${safeSlug.slice(0, maxSlugLength)}_${hash}`.slice(0, 64);
|
|
457
|
+
}
|
|
458
|
+
function sanitizeMysqlIdentifier(value) {
|
|
459
|
+
return value.replace(/[^A-Za-z0-9_]/g, "_").replace(/^_+|_+$/g, "");
|
|
460
|
+
}
|
|
461
|
+
function createMysqlWpConfig(database) {
|
|
462
|
+
const dbHost = database.port === 3306 ? database.host : `${database.host}:${database.port}`;
|
|
463
|
+
const salts = [
|
|
464
|
+
"AUTH_KEY",
|
|
465
|
+
"SECURE_AUTH_KEY",
|
|
466
|
+
"LOGGED_IN_KEY",
|
|
467
|
+
"NONCE_KEY",
|
|
468
|
+
"AUTH_SALT",
|
|
469
|
+
"SECURE_AUTH_SALT",
|
|
470
|
+
"LOGGED_IN_SALT",
|
|
471
|
+
"NONCE_SALT"
|
|
472
|
+
]
|
|
473
|
+
.map((name) => `define(${phpString(name)}, ${phpString(randomSalt())});`)
|
|
474
|
+
.join("\n");
|
|
475
|
+
return `<?php
|
|
476
|
+
define('DB_NAME', ${phpString(database.database)});
|
|
477
|
+
define('DB_USER', ${phpString(database.user)});
|
|
478
|
+
define('DB_PASSWORD', ${phpString(database.password)});
|
|
479
|
+
define('DB_HOST', ${phpString(dbHost)});
|
|
480
|
+
define('DB_CHARSET', 'utf8');
|
|
481
|
+
define('DB_COLLATE', '');
|
|
482
|
+
define('WP_DEBUG', false);
|
|
483
|
+
define('WP_DEBUG_DISPLAY', false);
|
|
484
|
+
${salts}
|
|
485
|
+
|
|
486
|
+
$table_prefix = 'wp_';
|
|
487
|
+
|
|
488
|
+
if ( ! defined('ABSPATH') ) {
|
|
489
|
+
define('ABSPATH', dirname(__FILE__) . '/');
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
require_once ABSPATH . 'wp-settings.php';
|
|
493
|
+
`;
|
|
494
|
+
}
|
|
495
|
+
async function prepareMysqlDatabase(database, options) {
|
|
496
|
+
if (process.env.PRESSSHIP_TEST_SKIP_MYSQL_PREP === "1") {
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
try {
|
|
500
|
+
await createMysqlDatabase(database, options);
|
|
501
|
+
}
|
|
502
|
+
catch (error) {
|
|
503
|
+
if (shouldUseManagedMysqlFallback(database, error)) {
|
|
504
|
+
await configureManagedMysql(database);
|
|
505
|
+
await writeFile(database.configPath, createMysqlWpConfig(database), { mode: 0o600 });
|
|
506
|
+
await createMysqlDatabase(database, options);
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
throw createMysqlPrepareError(database, error);
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
async function createMysqlDatabase(database, options) {
|
|
513
|
+
const databaseName = escapeMysqlIdentifier(database.database);
|
|
514
|
+
const sql = options.reset
|
|
515
|
+
? `DROP DATABASE IF EXISTS ${databaseName};\nCREATE DATABASE ${databaseName} CHARACTER SET utf8 COLLATE utf8_general_ci;\n`
|
|
516
|
+
: `CREATE DATABASE IF NOT EXISTS ${databaseName} CHARACTER SET utf8 COLLATE utf8_general_ci;\n`;
|
|
517
|
+
const mysql = await import("mysql2/promise");
|
|
518
|
+
const connection = await mysql.createConnection({
|
|
519
|
+
host: database.host,
|
|
520
|
+
port: database.port,
|
|
521
|
+
user: database.user,
|
|
522
|
+
password: database.password,
|
|
523
|
+
multipleStatements: true
|
|
524
|
+
});
|
|
525
|
+
try {
|
|
526
|
+
await connection.query(sql);
|
|
527
|
+
}
|
|
528
|
+
finally {
|
|
529
|
+
await connection.end().catch(() => undefined);
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
async function configureManagedMysql(database) {
|
|
533
|
+
try {
|
|
534
|
+
const port = await ensureManagedMysqlContainer();
|
|
535
|
+
database.host = "127.0.0.1";
|
|
536
|
+
database.port = port;
|
|
537
|
+
database.user = "root";
|
|
538
|
+
database.password = managedMysqlPassword;
|
|
539
|
+
database.server = "managed-docker";
|
|
540
|
+
}
|
|
541
|
+
catch (error) {
|
|
542
|
+
throw new Error(`No MySQL server is reachable at ${database.host}:${database.port}, and Pressship could not start its managed MariaDB container. ` +
|
|
543
|
+
`Start Docker or OrbStack, or configure an external MySQL server in Settings. ${errorMessage(error)}`);
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
async function ensureManagedMysqlContainer() {
|
|
547
|
+
const existing = await inspectManagedMysqlContainer();
|
|
548
|
+
if (existing) {
|
|
549
|
+
if (!existing.running) {
|
|
550
|
+
await execa("docker", ["start", managedMysqlContainerName]);
|
|
551
|
+
}
|
|
552
|
+
await waitForManagedMysql(existing.port);
|
|
553
|
+
return existing.port;
|
|
554
|
+
}
|
|
555
|
+
const port = await getFreeLocalPort();
|
|
556
|
+
await execa("docker", [
|
|
557
|
+
"run",
|
|
558
|
+
"-d",
|
|
559
|
+
"--name",
|
|
560
|
+
managedMysqlContainerName,
|
|
561
|
+
"-p",
|
|
562
|
+
`127.0.0.1:${port}:3306`,
|
|
563
|
+
"-e",
|
|
564
|
+
`MARIADB_ROOT_PASSWORD=${managedMysqlPassword}`,
|
|
565
|
+
"-e",
|
|
566
|
+
"MARIADB_AUTO_UPGRADE=1",
|
|
567
|
+
"-e",
|
|
568
|
+
"MARIADB_INITDB_SKIP_TZINFO=1",
|
|
569
|
+
managedMysqlImage
|
|
570
|
+
]);
|
|
571
|
+
await waitForManagedMysql(port);
|
|
572
|
+
return port;
|
|
573
|
+
}
|
|
574
|
+
async function inspectManagedMysqlContainer() {
|
|
575
|
+
const result = await execa("docker", ["container", "inspect", managedMysqlContainerName], { reject: false });
|
|
576
|
+
if (result.exitCode !== 0) {
|
|
577
|
+
return undefined;
|
|
578
|
+
}
|
|
579
|
+
const inspected = JSON.parse(result.stdout);
|
|
580
|
+
const container = inspected[0];
|
|
581
|
+
const hostPort = container?.NetworkSettings?.Ports?.["3306/tcp"]?.[0]?.HostPort;
|
|
582
|
+
const port = Number(hostPort);
|
|
583
|
+
if (!Number.isInteger(port) || port <= 0) {
|
|
584
|
+
await execa("docker", ["rm", "-f", managedMysqlContainerName], { reject: false });
|
|
585
|
+
return undefined;
|
|
586
|
+
}
|
|
587
|
+
return {
|
|
588
|
+
running: Boolean(container?.State?.Running),
|
|
589
|
+
port
|
|
590
|
+
};
|
|
591
|
+
}
|
|
592
|
+
async function waitForManagedMysql(port) {
|
|
593
|
+
const started = Date.now();
|
|
594
|
+
let lastError;
|
|
595
|
+
while (Date.now() - started < 90_000) {
|
|
596
|
+
try {
|
|
597
|
+
const mysql = await import("mysql2/promise");
|
|
598
|
+
const connection = await mysql.createConnection({
|
|
599
|
+
host: "127.0.0.1",
|
|
600
|
+
port,
|
|
601
|
+
user: "root",
|
|
602
|
+
password: managedMysqlPassword
|
|
603
|
+
});
|
|
604
|
+
await connection.end().catch(() => undefined);
|
|
605
|
+
return;
|
|
606
|
+
}
|
|
607
|
+
catch (error) {
|
|
608
|
+
lastError = error;
|
|
609
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
throw new Error(`Managed MariaDB did not become ready on 127.0.0.1:${port}. ${errorMessage(lastError)}`);
|
|
613
|
+
}
|
|
614
|
+
async function getFreeLocalPort() {
|
|
615
|
+
return new Promise((resolve, reject) => {
|
|
616
|
+
const server = createNetServer();
|
|
617
|
+
server.once("error", reject);
|
|
618
|
+
server.listen(0, "127.0.0.1", () => {
|
|
619
|
+
const address = server.address();
|
|
620
|
+
const port = typeof address === "object" && address ? address.port : undefined;
|
|
621
|
+
server.close((error) => {
|
|
622
|
+
if (error) {
|
|
623
|
+
reject(error);
|
|
624
|
+
return;
|
|
625
|
+
}
|
|
626
|
+
if (!port) {
|
|
627
|
+
reject(new Error("Could not reserve a local port for managed MariaDB."));
|
|
628
|
+
return;
|
|
629
|
+
}
|
|
630
|
+
resolve(port);
|
|
631
|
+
});
|
|
632
|
+
});
|
|
633
|
+
});
|
|
634
|
+
}
|
|
635
|
+
function shouldUseManagedMysqlFallback(database, error) {
|
|
636
|
+
return database.server === "external" && isLocalMysqlHost(database.host) && isConnectionUnavailable(error);
|
|
637
|
+
}
|
|
638
|
+
function isLocalMysqlHost(host) {
|
|
639
|
+
return host === "127.0.0.1" || host === "localhost" || host === "::1";
|
|
640
|
+
}
|
|
641
|
+
function isConnectionUnavailable(error) {
|
|
642
|
+
const message = errorMessage(error);
|
|
643
|
+
return (message.includes("ECONNREFUSED") ||
|
|
644
|
+
message.includes("ETIMEDOUT") ||
|
|
645
|
+
message.includes("ENOTFOUND") ||
|
|
646
|
+
message.includes("EHOSTUNREACH"));
|
|
647
|
+
}
|
|
648
|
+
function createMysqlPrepareError(database, error) {
|
|
649
|
+
return new Error(`Could not prepare MySQL database ${database.database} at ${database.host}:${database.port}. ` +
|
|
650
|
+
`Check the Playground MySQL settings and make sure the database server is running. ${errorMessage(error)}`);
|
|
651
|
+
}
|
|
652
|
+
function errorMessage(error) {
|
|
653
|
+
return error instanceof Error ? error.message : String(error);
|
|
654
|
+
}
|
|
655
|
+
function describePlaygroundDatabase(database) {
|
|
656
|
+
if (database.mode === "sqlite") {
|
|
657
|
+
return "SQLite";
|
|
658
|
+
}
|
|
659
|
+
if (database.server === "managed-docker") {
|
|
660
|
+
return `Managed MariaDB ${database.host}:${database.port}/${database.database}`;
|
|
661
|
+
}
|
|
662
|
+
return `MySQL ${database.user}@${database.host}:${database.port}/${database.database}`;
|
|
663
|
+
}
|
|
664
|
+
function escapeMysqlIdentifier(value) {
|
|
665
|
+
return `\`${value.replace(/`/g, "``")}\``;
|
|
666
|
+
}
|
|
667
|
+
function phpString(value) {
|
|
668
|
+
return `'${value.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`;
|
|
669
|
+
}
|
|
670
|
+
function randomSalt() {
|
|
671
|
+
return createHash("sha256").update(`${Date.now()}:${Math.random()}:${process.hrtime.bigint()}`).digest("hex");
|
|
672
|
+
}
|
|
197
673
|
function isLocalPluginTarget(target) {
|
|
198
674
|
return target === "." || target.startsWith("..") || target.startsWith(`.${path.sep}`) || path.isAbsolute(target) || pathExists(target);
|
|
199
675
|
}
|
|
@@ -204,4 +680,17 @@ function normalizePlaygroundVersion(version) {
|
|
|
204
680
|
const trimmed = version.trim();
|
|
205
681
|
return trimmed && trimmed !== "0" ? trimmed : undefined;
|
|
206
682
|
}
|
|
683
|
+
function parseWordPressBranch(version) {
|
|
684
|
+
const match = version?.trim().match(/^(\d+)(?:\.(\d+))?/);
|
|
685
|
+
if (!match) {
|
|
686
|
+
return undefined;
|
|
687
|
+
}
|
|
688
|
+
return {
|
|
689
|
+
major: Number(match[1]),
|
|
690
|
+
minor: Number(match[2] ?? 0)
|
|
691
|
+
};
|
|
692
|
+
}
|
|
693
|
+
function isWordPressBranchAtLeast(branch, major, minor) {
|
|
694
|
+
return branch.major > major || (branch.major === major && branch.minor >= minor);
|
|
695
|
+
}
|
|
207
696
|
//# sourceMappingURL=demo.js.map
|