vite-wp 0.1.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/LICENSE +21 -0
- package/README.md +132 -0
- package/dist/astro.d.ts +5 -0
- package/dist/astro.js +27 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +40 -0
- package/dist/commands/dev.d.ts +1 -0
- package/dist/commands/dev.js +62 -0
- package/dist/commands/doctor.d.ts +6 -0
- package/dist/commands/doctor.js +139 -0
- package/dist/commands/init.d.ts +1 -0
- package/dist/commands/init.js +55 -0
- package/dist/commands/smoke.d.ts +1 -0
- package/dist/commands/smoke.js +138 -0
- package/dist/commands/types.d.ts +1 -0
- package/dist/commands/types.js +67 -0
- package/dist/config.d.ts +71 -0
- package/dist/config.js +74 -0
- package/dist/content.d.ts +44 -0
- package/dist/content.js +190 -0
- package/dist/dev-toolbar/vitewp-toolbar.d.ts +23 -0
- package/dist/dev-toolbar/vitewp-toolbar.js +144 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +2 -0
- package/dist/live.config.d.ts +64 -0
- package/dist/live.config.js +21 -0
- package/dist/runtime/astro.d.ts +4 -0
- package/dist/runtime/astro.js +69 -0
- package/dist/runtime/composer.d.ts +2 -0
- package/dist/runtime/composer.js +36 -0
- package/dist/runtime/php.d.ts +3 -0
- package/dist/runtime/php.js +79 -0
- package/dist/runtime/ports.d.ts +3 -0
- package/dist/runtime/ports.js +38 -0
- package/dist/runtime/process.d.ts +12 -0
- package/dist/runtime/process.js +51 -0
- package/dist/runtime/proxy.d.ts +6 -0
- package/dist/runtime/proxy.js +204 -0
- package/dist/runtime/wp-config.d.ts +3 -0
- package/dist/runtime/wp-config.js +82 -0
- package/dist/wordpress/client.d.ts +98 -0
- package/dist/wordpress/client.js +133 -0
- package/dist/wordpress/generated-types.d.ts +33 -0
- package/dist/wordpress/generated-types.js +2 -0
- package/dist/wordpress/menus.d.ts +24 -0
- package/dist/wordpress/menus.js +20 -0
- package/dist/wordpress/schemas.d.ts +58 -0
- package/dist/wordpress/schemas.js +39 -0
- package/dist/wordpress/templates.d.ts +19 -0
- package/dist/wordpress/templates.js +88 -0
- package/package.json +78 -0
- package/starter/.env.example +15 -0
- package/starter/astro.config.mjs +16 -0
- package/starter/composer.json +37 -0
- package/starter/src/env.d.ts +1 -0
- package/starter/src/live.config.ts +22 -0
- package/starter/src/pages/[...slug].astro +101 -0
- package/starter/src/templates/404.astro +11 -0
- package/starter/src/templates/pages/.gitkeep +0 -0
- package/starter/src/templates/pages/default.astro +12 -0
- package/starter/src/templates/pages/front-page.astro +13 -0
- package/starter/src/templates/partials/Header.astro +23 -0
- package/starter/src/templates/partials/Pagination.astro +21 -0
- package/starter/src/templates/post-types/.gitkeep +0 -0
- package/starter/src/templates/posts/.gitkeep +0 -0
- package/starter/src/templates/posts/archive.astro +25 -0
- package/starter/src/templates/posts/single.astro +12 -0
- package/starter/src/templates/search.astro +30 -0
- package/starter/src/templates/taxonomies/.gitkeep +0 -0
- package/starter/src/templates/taxonomies/taxonomy-[taxonomy].astro +28 -0
- package/starter/src/wordpress/client.ts +263 -0
- package/starter/src/wordpress/generated-types.ts +38 -0
- package/starter/src/wordpress/menus.ts +51 -0
- package/starter/src/wordpress/schemas.ts +44 -0
- package/starter/src/wordpress/templates.ts +113 -0
- package/starter/tsconfig.json +4 -0
- package/starter/vitewp.config.ts +29 -0
- package/starter/wordpress/content/mu-plugins/.gitkeep +0 -0
- package/starter/wordpress/content/mu-plugins/vitewp-bridge.php +560 -0
- package/starter/wordpress/content/themes/vitewp/functions.php +9 -0
- package/starter/wordpress/content/themes/vitewp/index.php +22 -0
- package/starter/wordpress/content/themes/vitewp/style.css +10 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Andreas Ekström (https://github.com/didair)
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# ViteWP
|
|
2
|
+
|
|
3
|
+
ViteWP is an Astro-first WordPress development framework.
|
|
4
|
+
|
|
5
|
+
A ViteWP project **is** the local WordPress site: Composer installs WordPress, PHP runs WordPress, Astro renders the frontend, and one local URL serves admin, REST, media, previews, and Astro pages.
|
|
6
|
+
|
|
7
|
+
```txt
|
|
8
|
+
http://localhost:3000
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
PHP and Astro still run internally, but you normally only use the unified ViteWP URL.
|
|
12
|
+
|
|
13
|
+
## Requirements
|
|
14
|
+
|
|
15
|
+
- Node.js 20+
|
|
16
|
+
- PHP 8.2+
|
|
17
|
+
- Composer
|
|
18
|
+
- MySQL or MariaDB
|
|
19
|
+
|
|
20
|
+
ViteWP is bring-your-own database. It does not start MySQL for you.
|
|
21
|
+
|
|
22
|
+
## Get started
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
mkdir my-site
|
|
26
|
+
cd my-site
|
|
27
|
+
npm init -y
|
|
28
|
+
npm install vitewp astro typescript @astrojs/check
|
|
29
|
+
npx vitewp init
|
|
30
|
+
cp .env.example .env
|
|
31
|
+
npm run dev
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Edit `.env` with your database credentials, then open:
|
|
35
|
+
|
|
36
|
+
```txt
|
|
37
|
+
http://localhost:3000
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
If the database is empty, go to `/wp-admin/` and complete the WordPress installer.
|
|
41
|
+
|
|
42
|
+
## Commands
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
npm run dev # Start the local ViteWP runtime
|
|
46
|
+
npm run doctor # Check PHP, Composer, WordPress, config, and environment
|
|
47
|
+
npm run types # Generate WordPress-derived TypeScript types
|
|
48
|
+
npm run check # Run Astro type checking
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
For internal runtime diagnostics:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
npm run dev -- --verbose
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Folder structure
|
|
58
|
+
|
|
59
|
+
```txt
|
|
60
|
+
my-site/
|
|
61
|
+
astro.config.mjs
|
|
62
|
+
vitewp.config.ts
|
|
63
|
+
composer.json
|
|
64
|
+
.env
|
|
65
|
+
|
|
66
|
+
src/
|
|
67
|
+
live.config.ts
|
|
68
|
+
pages/
|
|
69
|
+
[...slug].astro
|
|
70
|
+
templates/
|
|
71
|
+
pages/
|
|
72
|
+
front-page.astro
|
|
73
|
+
default.astro
|
|
74
|
+
posts/
|
|
75
|
+
single.astro
|
|
76
|
+
archive.astro
|
|
77
|
+
taxonomies/
|
|
78
|
+
taxonomy-[taxonomy].astro
|
|
79
|
+
search.astro
|
|
80
|
+
404.astro
|
|
81
|
+
wordpress/
|
|
82
|
+
client.ts
|
|
83
|
+
templates.ts
|
|
84
|
+
menus.ts
|
|
85
|
+
generated-types.ts
|
|
86
|
+
|
|
87
|
+
wordpress/
|
|
88
|
+
public/ # Composer-installed WordPress core
|
|
89
|
+
content/
|
|
90
|
+
mu-plugins/
|
|
91
|
+
themes/
|
|
92
|
+
plugins/
|
|
93
|
+
uploads/
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Templates
|
|
97
|
+
|
|
98
|
+
The catch-all Astro route asks WordPress what the current URL is, then renders the first matching template from `src/templates`.
|
|
99
|
+
|
|
100
|
+
Examples:
|
|
101
|
+
|
|
102
|
+
```txt
|
|
103
|
+
src/templates/pages/front-page.astro
|
|
104
|
+
src/templates/pages/page-about.astro
|
|
105
|
+
src/templates/pages/default.astro
|
|
106
|
+
src/templates/posts/single.astro
|
|
107
|
+
src/templates/posts/archive.astro
|
|
108
|
+
src/templates/taxonomies/category.astro
|
|
109
|
+
src/templates/search.astro
|
|
110
|
+
src/templates/404.astro
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## WordPress data
|
|
114
|
+
|
|
115
|
+
ViteWP uses Astro Live Collections for WordPress data:
|
|
116
|
+
|
|
117
|
+
```ts
|
|
118
|
+
import { getLiveCollection, getLiveEntry } from 'astro:content';
|
|
119
|
+
|
|
120
|
+
const route = await getLiveEntry('routes', { path: '/' });
|
|
121
|
+
const posts = await getLiveCollection('posts', { page: 1, perPage: 10 });
|
|
122
|
+
const menu = await getLiveEntry('menus', { location: 'primary' });
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
After `npm run dev` or `npm run check`, collection names and filters should have TypeScript completion.
|
|
126
|
+
|
|
127
|
+
## Notes
|
|
128
|
+
|
|
129
|
+
- WordPress core is installed into `wordpress/public` by Composer.
|
|
130
|
+
- Do not commit `wordpress/public`, `.env`, `.vitewp`, or uploads.
|
|
131
|
+
- The tiny placeholder theme exists only to keep WordPress admin happy.
|
|
132
|
+
- Docker is optional, not required.
|
package/dist/astro.d.ts
ADDED
package/dist/astro.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { fileURLToPath } from 'node:url';
|
|
3
|
+
export default function vitewp(_options = {}) {
|
|
4
|
+
return {
|
|
5
|
+
name: 'vitewp',
|
|
6
|
+
hooks: {
|
|
7
|
+
'astro:config:setup': ({ command, addDevToolbarApp, logger }) => {
|
|
8
|
+
if (command === 'dev') {
|
|
9
|
+
addDevToolbarApp({
|
|
10
|
+
id: 'vitewp',
|
|
11
|
+
name: 'ViteWP',
|
|
12
|
+
icon: 'sitemap',
|
|
13
|
+
entrypoint: getDevToolbarEntrypoint(),
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
logger.info('ViteWP integration loaded.');
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
function getDevToolbarEntrypoint() {
|
|
22
|
+
const sourceEntrypoint = new URL('./dev-toolbar/vitewp-toolbar.ts', import.meta.url);
|
|
23
|
+
if (existsSync(fileURLToPath(sourceEntrypoint))) {
|
|
24
|
+
return sourceEntrypoint;
|
|
25
|
+
}
|
|
26
|
+
return new URL('./dev-toolbar/vitewp-toolbar.js', import.meta.url);
|
|
27
|
+
}
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { runDev } from './commands/dev.js';
|
|
3
|
+
import { runDoctor } from './commands/doctor.js';
|
|
4
|
+
import { runSmoke } from './commands/smoke.js';
|
|
5
|
+
import { runTypes } from './commands/types.js';
|
|
6
|
+
import { runInit } from './commands/init.js';
|
|
7
|
+
const command = process.argv[2] ?? 'help';
|
|
8
|
+
switch (command) {
|
|
9
|
+
case 'dev':
|
|
10
|
+
await runDev();
|
|
11
|
+
break;
|
|
12
|
+
case 'doctor':
|
|
13
|
+
await runDoctor();
|
|
14
|
+
break;
|
|
15
|
+
case 'init':
|
|
16
|
+
runInit();
|
|
17
|
+
break;
|
|
18
|
+
case 'types':
|
|
19
|
+
await runTypes();
|
|
20
|
+
break;
|
|
21
|
+
case 'smoke':
|
|
22
|
+
await runSmoke();
|
|
23
|
+
break;
|
|
24
|
+
case 'help':
|
|
25
|
+
case '--help':
|
|
26
|
+
case '-h':
|
|
27
|
+
printHelp();
|
|
28
|
+
break;
|
|
29
|
+
default:
|
|
30
|
+
console.error(`Unknown command: ${command}`);
|
|
31
|
+
printHelp();
|
|
32
|
+
process.exitCode = 1;
|
|
33
|
+
}
|
|
34
|
+
function printHelp() {
|
|
35
|
+
console.log(`vitewp\n\nUsage:\n vitewp init Copy starter project files into the current directory
|
|
36
|
+
vitewp dev Start the local WordPress + Astro development runtime
|
|
37
|
+
vitewp doctor Check the current project setup
|
|
38
|
+
vitewp types Generate TypeScript types from WordPress metadata
|
|
39
|
+
vitewp smoke Verify the running ViteWP dev runtime\n`);
|
|
40
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function runDev(): Promise<void>;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { loadViteWpConfig } from '../config.js';
|
|
2
|
+
import { ensureComposerInstall } from '../runtime/composer.js';
|
|
3
|
+
import { startAstroServer, stopAstroServer } from '../runtime/astro.js';
|
|
4
|
+
import { startPhpServer } from '../runtime/php.js';
|
|
5
|
+
import { waitForExit } from '../runtime/process.js';
|
|
6
|
+
import { assertPortAvailable, resolveInternalPort } from '../runtime/ports.js';
|
|
7
|
+
import { startUnifiedProxy } from '../runtime/proxy.js';
|
|
8
|
+
import { phpServerUrl, writeWordPressConfig } from '../runtime/wp-config.js';
|
|
9
|
+
import { runDoctorChecks } from './doctor.js';
|
|
10
|
+
export async function runDev() {
|
|
11
|
+
const config = await loadViteWpConfig();
|
|
12
|
+
const verbose = isVerbose();
|
|
13
|
+
if (verbose) {
|
|
14
|
+
process.env.VITEWP_VERBOSE = '1';
|
|
15
|
+
}
|
|
16
|
+
console.log('ViteWP dev runtime');
|
|
17
|
+
console.log(`- local site: ${config.wordpress.url}`);
|
|
18
|
+
console.log(`- WordPress docroot: ${config.wordpress.docroot}`);
|
|
19
|
+
console.log(`- WordPress content: ${config.wordpress.contentDir}`);
|
|
20
|
+
console.log(`- templates: ${config.templates.directory}`);
|
|
21
|
+
console.log(`- database: ${config.database.driver}://${config.database.user}@${config.database.host}:${config.database.port}/${config.database.name}`);
|
|
22
|
+
console.log('');
|
|
23
|
+
const result = await runDoctorChecks(config);
|
|
24
|
+
if (result.errors > 0) {
|
|
25
|
+
console.log('');
|
|
26
|
+
console.log('Fix the errors above before starting the dev runtime.');
|
|
27
|
+
process.exitCode = 1;
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
try {
|
|
31
|
+
await ensureComposerInstall(config);
|
|
32
|
+
writeWordPressConfig(config);
|
|
33
|
+
await stopAstroServer(config);
|
|
34
|
+
config.dev.phpPort = await resolveInternalPort(config.dev.phpHost, config.dev.phpPort);
|
|
35
|
+
config.dev.astroPort = await resolveInternalPort(config.dev.astroHost, config.dev.astroPort);
|
|
36
|
+
const publicUrl = new URL(config.wordpress.url);
|
|
37
|
+
await assertPortAvailable(publicUrl.hostname, Number(publicUrl.port || 3000), 'ViteWP proxy');
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
console.error(error instanceof Error ? error.message : error);
|
|
41
|
+
process.exitCode = 1;
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
process.env.VITEWP_PUBLIC_URL = config.wordpress.url;
|
|
45
|
+
process.env.VITEWP_PHP_URL = phpServerUrl(config);
|
|
46
|
+
console.log('');
|
|
47
|
+
if (verbose) {
|
|
48
|
+
console.log(`✓ WordPress/PHP internal server: ${phpServerUrl(config)}`);
|
|
49
|
+
console.log(`✓ Astro internal server: http://${config.dev.astroHost}:${config.dev.astroPort}`);
|
|
50
|
+
}
|
|
51
|
+
const php = startPhpServer(config);
|
|
52
|
+
const astro = await startAstroServer(config);
|
|
53
|
+
const proxy = await startUnifiedProxy(config);
|
|
54
|
+
console.log(`✓ ViteWP ready at ${proxy.url}`);
|
|
55
|
+
console.log('Press Ctrl+C to stop.');
|
|
56
|
+
console.log('');
|
|
57
|
+
await waitForExit([php, astro]);
|
|
58
|
+
await proxy.stop();
|
|
59
|
+
}
|
|
60
|
+
function isVerbose() {
|
|
61
|
+
return process.argv.includes('--verbose') || process.env.VITEWP_VERBOSE === '1';
|
|
62
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { execFile } from 'node:child_process';
|
|
4
|
+
import { promisify } from 'node:util';
|
|
5
|
+
import { loadViteWpConfig } from '../config.js';
|
|
6
|
+
const execFileAsync = promisify(execFile);
|
|
7
|
+
export async function runDoctor() {
|
|
8
|
+
const config = await loadViteWpConfig();
|
|
9
|
+
const result = await runDoctorChecks(config);
|
|
10
|
+
process.exitCode = result.errors > 0 ? 1 : 0;
|
|
11
|
+
}
|
|
12
|
+
export async function runDoctorChecks(config) {
|
|
13
|
+
const checks = [];
|
|
14
|
+
checks.push({
|
|
15
|
+
status: config.configFile ? 'pass' : 'warn',
|
|
16
|
+
label: 'ViteWP config',
|
|
17
|
+
detail: config.configFile ?? 'No vitewp.config.* file found; using defaults.',
|
|
18
|
+
});
|
|
19
|
+
checks.push(fileCheck(config.root, 'astro.config.mjs', 'Astro config'));
|
|
20
|
+
checks.push(fileCheck(config.root, 'composer.json', 'Composer manifest'));
|
|
21
|
+
checks.push(composerWordPressCheck(config.root, config.composer.wordpressPackage));
|
|
22
|
+
checks.push(fileCheck(config.root, 'composer.lock', 'Composer lockfile'));
|
|
23
|
+
checks.push(fileCheck(config.root, `${config.wordpress.docroot}/wp-settings.php`, 'Composer-installed WordPress core'));
|
|
24
|
+
checks.push(fileCheck(config.root, `${config.wordpress.docroot}/wp-config.php`, 'Generated wp-config.php'));
|
|
25
|
+
checks.push(fileCheck(config.root, `${config.wordpress.contentDir}/themes/vitewp/style.css`, 'ViteWP placeholder theme'));
|
|
26
|
+
checks.push(fileCheck(config.root, config.templates.directory, 'Template directory'));
|
|
27
|
+
checks.push(databaseConfigCheck(config));
|
|
28
|
+
checks.push(await commandCheck('php', ['-v'], 'PHP runtime'));
|
|
29
|
+
checks.push(await commandCheck('composer', ['--version'], 'Composer'));
|
|
30
|
+
checks.push(gitignoreCheck(config.root, config.wordpress.docroot));
|
|
31
|
+
printChecks(checks);
|
|
32
|
+
const errors = checks.filter((check) => check.status === 'fail').length;
|
|
33
|
+
const warnings = checks.filter((check) => check.status === 'warn').length;
|
|
34
|
+
console.log('');
|
|
35
|
+
console.log(`Doctor finished with ${errors} error(s) and ${warnings} warning(s).`);
|
|
36
|
+
return { errors, warnings };
|
|
37
|
+
}
|
|
38
|
+
function fileCheck(root, relativePath, label) {
|
|
39
|
+
const path = join(root, relativePath);
|
|
40
|
+
return existsSync(path)
|
|
41
|
+
? { status: 'pass', label, detail: relativePath }
|
|
42
|
+
: { status: 'warn', label, detail: `${relativePath} does not exist yet.` };
|
|
43
|
+
}
|
|
44
|
+
async function commandCheck(command, args, label) {
|
|
45
|
+
try {
|
|
46
|
+
const { stdout, stderr } = await execFileAsync(command, args);
|
|
47
|
+
const firstLine = `${stdout}${stderr}`.split('\n').find(Boolean);
|
|
48
|
+
return { status: 'pass', label, detail: firstLine };
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return { status: 'fail', label, detail: `${command} was not found on PATH.` };
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function databaseConfigCheck(config) {
|
|
55
|
+
const database = config.database;
|
|
56
|
+
const missing = [
|
|
57
|
+
['host', database.host],
|
|
58
|
+
['name', database.name],
|
|
59
|
+
['user', database.user],
|
|
60
|
+
['tablePrefix', database.tablePrefix],
|
|
61
|
+
].filter(([, value]) => !value);
|
|
62
|
+
if (missing.length > 0) {
|
|
63
|
+
return {
|
|
64
|
+
status: 'fail',
|
|
65
|
+
label: 'Database config',
|
|
66
|
+
detail: `Missing ${missing.map(([key]) => key).join(', ')}.`,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
if (database.driver !== 'mysql' && database.driver !== 'mariadb') {
|
|
70
|
+
return {
|
|
71
|
+
status: 'fail',
|
|
72
|
+
label: 'Database config',
|
|
73
|
+
detail: `Unsupported driver: ${database.driver}.`,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
return {
|
|
77
|
+
status: 'pass',
|
|
78
|
+
label: 'Database config',
|
|
79
|
+
detail: `${database.driver}://${database.user}@${database.host}:${database.port}/${database.name} (${database.tablePrefix})`,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
function composerWordPressCheck(root, wordpressPackage) {
|
|
83
|
+
const composerPath = join(root, 'composer.json');
|
|
84
|
+
if (!existsSync(composerPath)) {
|
|
85
|
+
return {
|
|
86
|
+
status: 'warn',
|
|
87
|
+
label: 'Composer WordPress package',
|
|
88
|
+
detail: 'composer.json does not exist yet.',
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
try {
|
|
92
|
+
const composer = JSON.parse(readFileSync(composerPath, 'utf8'));
|
|
93
|
+
const version = composer.require?.[wordpressPackage];
|
|
94
|
+
if (!version) {
|
|
95
|
+
return {
|
|
96
|
+
status: 'fail',
|
|
97
|
+
label: 'Composer WordPress package',
|
|
98
|
+
detail: `${wordpressPackage} is not declared in composer.json.`,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
const installDir = composer.extra?.['wordpress-install-dir'];
|
|
102
|
+
const installDetail = installDir ? ` -> ${installDir}` : '';
|
|
103
|
+
return {
|
|
104
|
+
status: 'pass',
|
|
105
|
+
label: 'Composer WordPress package',
|
|
106
|
+
detail: `${wordpressPackage}@${version}${installDetail}`,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
catch (error) {
|
|
110
|
+
return {
|
|
111
|
+
status: 'fail',
|
|
112
|
+
label: 'Composer WordPress package',
|
|
113
|
+
detail: error instanceof Error ? error.message : 'Could not parse composer.json.',
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
function gitignoreCheck(root, docroot) {
|
|
118
|
+
const gitignorePath = join(root, '.gitignore');
|
|
119
|
+
if (!existsSync(gitignorePath)) {
|
|
120
|
+
return { status: 'warn', label: 'Generated WordPress ignored', detail: '.gitignore is missing.' };
|
|
121
|
+
}
|
|
122
|
+
const gitignore = readFileSync(gitignorePath, 'utf8');
|
|
123
|
+
const normalizedDocroot = docroot.endsWith('/') ? docroot : `${docroot}/`;
|
|
124
|
+
return gitignore.includes(normalizedDocroot)
|
|
125
|
+
? { status: 'pass', label: 'Generated WordPress ignored', detail: normalizedDocroot }
|
|
126
|
+
: {
|
|
127
|
+
status: 'warn',
|
|
128
|
+
label: 'Generated WordPress ignored',
|
|
129
|
+
detail: `Add ${normalizedDocroot} to .gitignore.`,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
function printChecks(checks) {
|
|
133
|
+
console.log('ViteWP doctor');
|
|
134
|
+
console.log('');
|
|
135
|
+
for (const check of checks) {
|
|
136
|
+
const icon = check.status === 'pass' ? '✓' : check.status === 'warn' ? '!' : '✕';
|
|
137
|
+
console.log(`${icon} ${check.label}${check.detail ? ` — ${check.detail}` : ''}`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function runInit(): void;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { cpSync, existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, join, relative, resolve } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
export function runInit() {
|
|
5
|
+
const target = resolve(process.cwd(), getTargetDirectory());
|
|
6
|
+
const force = process.argv.includes('--force');
|
|
7
|
+
const starter = resolve(dirname(fileURLToPath(import.meta.url)), '../../starter');
|
|
8
|
+
if (!existsSync(starter)) {
|
|
9
|
+
console.error('Could not find the ViteWP starter files in this package.');
|
|
10
|
+
process.exitCode = 1;
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
mkdirSync(target, { recursive: true });
|
|
14
|
+
copyDirectory(starter, target, force);
|
|
15
|
+
updatePackageJson(target);
|
|
16
|
+
console.log(`ViteWP project files initialized in ${relative(process.cwd(), target) || '.'}.`);
|
|
17
|
+
console.log('');
|
|
18
|
+
console.log('Next steps:');
|
|
19
|
+
console.log(' 1. Copy .env.example to .env and set database credentials.');
|
|
20
|
+
console.log(' 2. Run npm install if dependencies are not installed yet.');
|
|
21
|
+
console.log(' 3. Run npm run dev.');
|
|
22
|
+
}
|
|
23
|
+
function getTargetDirectory() {
|
|
24
|
+
const positional = process.argv.slice(3).find((arg) => !arg.startsWith('-'));
|
|
25
|
+
return positional ?? '.';
|
|
26
|
+
}
|
|
27
|
+
function copyDirectory(from, to, force) {
|
|
28
|
+
for (const entry of readdirSync(from)) {
|
|
29
|
+
const source = join(from, entry);
|
|
30
|
+
const destination = join(to, entry);
|
|
31
|
+
const stats = statSync(source);
|
|
32
|
+
if (stats.isDirectory()) {
|
|
33
|
+
mkdirSync(destination, { recursive: true });
|
|
34
|
+
copyDirectory(source, destination, force);
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
if (existsSync(destination) && !force) {
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
cpSync(source, destination, { force: true });
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function updatePackageJson(root) {
|
|
44
|
+
const file = join(root, 'package.json');
|
|
45
|
+
const packageJson = existsSync(file)
|
|
46
|
+
? JSON.parse(readFileSync(file, 'utf8'))
|
|
47
|
+
: { private: true, type: 'module' };
|
|
48
|
+
packageJson.type ??= 'module';
|
|
49
|
+
packageJson.scripts ??= {};
|
|
50
|
+
packageJson.scripts.dev ??= 'vitewp dev';
|
|
51
|
+
packageJson.scripts.doctor ??= 'vitewp doctor';
|
|
52
|
+
packageJson.scripts.types ??= 'vitewp types';
|
|
53
|
+
packageJson.scripts.check ??= 'astro check';
|
|
54
|
+
writeFileSync(file, `${JSON.stringify(packageJson, null, 2)}\n`, 'utf8');
|
|
55
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function runSmoke(): Promise<void>;
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { randomBytes } from 'node:crypto';
|
|
2
|
+
import net from 'node:net';
|
|
3
|
+
import { loadViteWpConfig } from '../config.js';
|
|
4
|
+
export async function runSmoke() {
|
|
5
|
+
const config = await loadViteWpConfig();
|
|
6
|
+
const baseUrl = config.wordpress.url.replace(/\/$/, '');
|
|
7
|
+
const checks = [
|
|
8
|
+
{
|
|
9
|
+
label: 'Unified frontend route',
|
|
10
|
+
run: () => expectHttp(`${baseUrl}/`, [200]),
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
label: 'ViteWP dev route metadata',
|
|
14
|
+
run: () => expectBody(`${baseUrl}/`, 'window.__VITEWP_ROUTE_INFO__'),
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
label: 'WordPress admin route',
|
|
18
|
+
run: () => expectHttp(`${baseUrl}/wp-admin/`, [200, 302]),
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
label: 'Vite client through proxy',
|
|
22
|
+
run: () => expectHttp(`${baseUrl}/@vite/client`, [200], 'text/javascript'),
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
label: 'wp-content static asset',
|
|
26
|
+
run: () => expectHttp(`${baseUrl}/wp-content/themes/vitewp/style.css`, [200], 'text/css'),
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
label: 'ViteWP route bridge',
|
|
30
|
+
run: () => expectJson(`${baseUrl}/wp-json/vitewp/v1/resolve?path=/`, ['found', 'kind']),
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
label: 'ViteWP type metadata',
|
|
34
|
+
run: () => expectJson(`${baseUrl}/wp-json/vitewp/v1/types`, ['postTypes', 'taxonomies']),
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
label: 'Vite HMR websocket',
|
|
38
|
+
run: () => expectWebSocket(baseUrl),
|
|
39
|
+
},
|
|
40
|
+
];
|
|
41
|
+
console.log('ViteWP smoke test');
|
|
42
|
+
console.log('');
|
|
43
|
+
let failures = 0;
|
|
44
|
+
for (const check of checks) {
|
|
45
|
+
try {
|
|
46
|
+
const detail = await check.run();
|
|
47
|
+
console.log(`✓ ${check.label}${detail ? ` — ${detail}` : ''}`);
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
failures += 1;
|
|
51
|
+
console.log(`✕ ${check.label} — ${error instanceof Error ? error.message : String(error)}`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
console.log('');
|
|
55
|
+
console.log(`Smoke test finished with ${failures} failure(s).`);
|
|
56
|
+
process.exitCode = failures > 0 ? 1 : 0;
|
|
57
|
+
}
|
|
58
|
+
async function expectHttp(url, statuses, expectedContentType) {
|
|
59
|
+
const response = await fetchWithTimeout(url, { method: 'GET' });
|
|
60
|
+
const contentType = response.headers.get('content-type') ?? '';
|
|
61
|
+
if (!statuses.includes(response.status)) {
|
|
62
|
+
throw new Error(`Expected ${statuses.join('/')} from ${url}, got ${response.status}.`);
|
|
63
|
+
}
|
|
64
|
+
if (expectedContentType && !contentType.includes(expectedContentType)) {
|
|
65
|
+
throw new Error(`Expected content-type ${expectedContentType}, got ${contentType || 'none'}.`);
|
|
66
|
+
}
|
|
67
|
+
await response.body?.cancel();
|
|
68
|
+
return `${response.status}${contentType ? ` ${contentType}` : ''}`;
|
|
69
|
+
}
|
|
70
|
+
async function expectJson(url, keys) {
|
|
71
|
+
const response = await fetchWithTimeout(url);
|
|
72
|
+
if (!response.ok) {
|
|
73
|
+
throw new Error(`Expected 2xx from ${url}, got ${response.status}.`);
|
|
74
|
+
}
|
|
75
|
+
const json = await response.json();
|
|
76
|
+
const missing = keys.filter((key) => !(key in json));
|
|
77
|
+
if (missing.length > 0) {
|
|
78
|
+
throw new Error(`Missing JSON key(s): ${missing.join(', ')}.`);
|
|
79
|
+
}
|
|
80
|
+
return `${response.status}`;
|
|
81
|
+
}
|
|
82
|
+
async function expectBody(url, needle) {
|
|
83
|
+
const response = await fetchWithTimeout(url);
|
|
84
|
+
const body = await response.text();
|
|
85
|
+
if (!response.ok) {
|
|
86
|
+
throw new Error(`Expected 2xx from ${url}, got ${response.status}.`);
|
|
87
|
+
}
|
|
88
|
+
if (!body.includes(needle)) {
|
|
89
|
+
throw new Error(`Expected response body to include ${needle}.`);
|
|
90
|
+
}
|
|
91
|
+
return `${response.status}`;
|
|
92
|
+
}
|
|
93
|
+
async function fetchWithTimeout(url, init = {}) {
|
|
94
|
+
const controller = new AbortController();
|
|
95
|
+
const timeout = setTimeout(() => controller.abort(), 5000);
|
|
96
|
+
try {
|
|
97
|
+
return await fetch(url, { ...init, signal: controller.signal, redirect: 'manual' });
|
|
98
|
+
}
|
|
99
|
+
finally {
|
|
100
|
+
clearTimeout(timeout);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
function expectWebSocket(baseUrl) {
|
|
104
|
+
const url = new URL(baseUrl);
|
|
105
|
+
const host = url.hostname;
|
|
106
|
+
const port = Number(url.port || (url.protocol === 'https:' ? 443 : 80));
|
|
107
|
+
const key = randomBytes(16).toString('base64');
|
|
108
|
+
return new Promise((resolve, reject) => {
|
|
109
|
+
const socket = net.connect(port, host, () => {
|
|
110
|
+
socket.write([
|
|
111
|
+
'GET / HTTP/1.1',
|
|
112
|
+
`Host: ${url.host}`,
|
|
113
|
+
'Connection: Upgrade',
|
|
114
|
+
'Upgrade: websocket',
|
|
115
|
+
`Sec-WebSocket-Key: ${key}`,
|
|
116
|
+
'Sec-WebSocket-Version: 13',
|
|
117
|
+
'Sec-WebSocket-Protocol: vite-hmr',
|
|
118
|
+
'',
|
|
119
|
+
'',
|
|
120
|
+
].join('\r\n'));
|
|
121
|
+
});
|
|
122
|
+
socket.setTimeout(5000);
|
|
123
|
+
socket.once('data', (data) => {
|
|
124
|
+
const firstLine = data.toString().split('\r\n')[0] ?? '';
|
|
125
|
+
socket.destroy();
|
|
126
|
+
if (!firstLine.includes('101')) {
|
|
127
|
+
reject(new Error(`Expected 101 Switching Protocols, got ${firstLine || 'no response'}.`));
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
resolve('101 Switching Protocols');
|
|
131
|
+
});
|
|
132
|
+
socket.once('timeout', () => {
|
|
133
|
+
socket.destroy();
|
|
134
|
+
reject(new Error('Timed out waiting for websocket upgrade.'));
|
|
135
|
+
});
|
|
136
|
+
socket.once('error', reject);
|
|
137
|
+
});
|
|
138
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function runTypes(): Promise<void>;
|