vite-wp 0.1.7 → 0.1.9

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 CHANGED
@@ -2,22 +2,14 @@
2
2
 
3
3
  ViteWP is an Astro-first WordPress development framework.
4
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.
5
+ One entrypoint for everything you need, no fuzz and no magic.
12
6
 
13
7
  ## Requirements
14
8
 
15
9
  - Node.js 20+
16
10
  - PHP 8.2+
17
11
  - Composer
18
- - MySQL or MariaDB
19
-
20
- ViteWP is bring-your-own database. It does not start MySQL for you.
12
+ - a MySQL or MariaDB server
21
13
 
22
14
  ## Get started
23
15
 
@@ -27,12 +19,11 @@ cd my-site
27
19
  npm init -y
28
20
  npx vite-wp init
29
21
  cp .env.example .env
30
- npm run dev
31
22
  ```
32
23
 
33
24
  `vite-wp init` creates the starter files, adds the needed package dependencies, and runs install.
34
25
 
35
- Edit `.env` with your database credentials, then open:
26
+ Edit `.env` with your database credentials, run `npm run dev`, wait for the initial configuration and then open:
36
27
 
37
28
  ```txt
38
29
  http://localhost:3000
@@ -47,6 +38,8 @@ npm run dev # Start the local ViteWP runtime
47
38
  npm run doctor # Check PHP, Composer, WordPress, config, and environment
48
39
  npm run types # Generate WordPress-derived TypeScript types
49
40
  npm run check # Run Astro type checking
41
+ npx vite-wp composer install # Run Composer in the project
42
+ npx vite-wp wp plugin list # Run WP-CLI when installed
50
43
  ```
51
44
 
52
45
  For internal runtime diagnostics:
@@ -71,15 +64,15 @@ my-site/
71
64
  wordpress/
72
65
  public/ # Composer-installed WordPress core
73
66
  content/
74
- mu-plugins/
75
- themes/
67
+ mu-plugins/ # ViteWP bridge is generated here
68
+ themes/ # ViteWP placeholder theme is generated here
76
69
  plugins/
77
70
  uploads/
78
71
  ```
79
72
 
80
73
  ## Templates
81
74
 
82
- ViteWP ships default templates from the package. Create files in `src/templates` only when you want to override them. Project templates always win over package defaults.
75
+ ViteWP ships default templates from the package. Create files in `src/templates` only when you want to override them.
83
76
 
84
77
  Examples:
85
78
 
@@ -106,10 +99,3 @@ const menu = await getLiveEntry('menus', { location: 'primary' });
106
99
  ```
107
100
 
108
101
  After `npm run dev` or `npm run check`, collection names and filters should have TypeScript completion.
109
-
110
- ## Notes
111
-
112
- - WordPress core is installed into `wordpress/public` by Composer.
113
- - Do not commit `wordpress/public`, `.env`, `.vitewp`, or uploads.
114
- - The tiny placeholder theme exists only to keep WordPress admin happy.
115
- - Docker is optional, not required.
@@ -47,6 +47,24 @@ add_action('rest_api_init', function () {
47
47
  },
48
48
  ]);
49
49
 
50
+ register_rest_route('vitewp/v1', '/health', [
51
+ 'methods' => WP_REST_Server::READABLE,
52
+ 'permission_callback' => '__return_true',
53
+ 'callback' => function () {
54
+ return [
55
+ 'wordpressVersion' => get_bloginfo('version'),
56
+ 'phpVersion' => PHP_VERSION,
57
+ 'homeUrl' => home_url('/'),
58
+ 'siteUrl' => site_url('/'),
59
+ 'permalinkStructure' => (string) get_option('permalink_structure'),
60
+ 'activeTheme' => get_stylesheet(),
61
+ 'template' => get_template(),
62
+ 'activePlugins' => array_values((array) get_option('active_plugins', [])),
63
+ 'muPlugins' => array_values(array_keys(get_mu_plugins())),
64
+ ];
65
+ },
66
+ ]);
67
+
50
68
  register_rest_route('vitewp/v1', '/types', [
51
69
  'methods' => WP_REST_Server::READABLE,
52
70
  'permission_callback' => '__return_true',
package/dist/astro.js CHANGED
@@ -5,7 +5,16 @@ export default function vitewp(_options = {}) {
5
5
  return {
6
6
  name: 'vitewp',
7
7
  hooks: {
8
- 'astro:config:setup': ({ command, config, injectRoute, addDevToolbarApp, logger }) => {
8
+ 'astro:config:setup': ({ command, config, injectRoute, addDevToolbarApp, logger, updateConfig }) => {
9
+ if (isSourceIntegration()) {
10
+ updateConfig({
11
+ vite: {
12
+ resolve: {
13
+ alias: sourceAliases(),
14
+ },
15
+ },
16
+ });
17
+ }
9
18
  if (!hasProjectCatchAllRoute(fileURLToPath(config.root))) {
10
19
  injectRoute({
11
20
  pattern: '/[...slug]',
@@ -47,3 +56,24 @@ function getDevToolbarEntrypoint() {
47
56
  }
48
57
  return new URL('./dev-toolbar/vitewp-toolbar.js', import.meta.url);
49
58
  }
59
+ function isSourceIntegration() {
60
+ return fileURLToPath(import.meta.url).endsWith('/src/astro.ts');
61
+ }
62
+ function sourceAliases() {
63
+ return [
64
+ alias('vite-wp/wordpress/templates', './wordpress/templates.ts'),
65
+ alias('vite-wp/wordpress/client', './wordpress/client.ts'),
66
+ alias('vite-wp/wordpress/menus', './wordpress/menus.ts'),
67
+ alias('vite-wp/wordpress/schemas', './wordpress/schemas.ts'),
68
+ alias('vite-wp/wordpress', './wordpress/index.ts'),
69
+ alias('vite-wp/content', './content.ts'),
70
+ alias('vite-wp/astro', './astro.ts'),
71
+ alias('vite-wp', './index.ts'),
72
+ ];
73
+ }
74
+ function alias(find, replacement) {
75
+ return {
76
+ find,
77
+ replacement: fileURLToPath(new URL(replacement, import.meta.url)),
78
+ };
79
+ }
package/dist/cli.js CHANGED
@@ -4,6 +4,8 @@ import { runDoctor } from './commands/doctor.js';
4
4
  import { runSmoke } from './commands/smoke.js';
5
5
  import { runTypes } from './commands/types.js';
6
6
  import { runInit } from './commands/init.js';
7
+ import { runComposerCommand } from './commands/composer.js';
8
+ import { runWpCommand } from './commands/wp.js';
7
9
  const command = process.argv[2] ?? 'help';
8
10
  switch (command) {
9
11
  case 'dev':
@@ -18,6 +20,12 @@ switch (command) {
18
20
  case 'types':
19
21
  await runTypes();
20
22
  break;
23
+ case 'composer':
24
+ await runComposerCommand();
25
+ break;
26
+ case 'wp':
27
+ await runWpCommand();
28
+ break;
21
29
  case 'smoke':
22
30
  await runSmoke();
23
31
  break;
@@ -36,6 +44,8 @@ function printHelp() {
36
44
  vite-wp dev Start the local WordPress + Astro development runtime
37
45
  vite-wp doctor Check the current project setup
38
46
  vite-wp types Generate TypeScript types from WordPress metadata
47
+ vite-wp composer Run Composer in the ViteWP project
48
+ vite-wp wp Run WP-CLI for the local WordPress runtime
39
49
  vite-wp smoke Verify the running ViteWP dev runtime
40
50
 
41
51
  Options:
@@ -0,0 +1 @@
1
+ export declare function runComposerCommand(args?: string[]): Promise<void>;
@@ -0,0 +1,13 @@
1
+ import { loadViteWpConfig } from '../config.js';
2
+ import { runComposer } from '../runtime/composer.js';
3
+ export async function runComposerCommand(args = process.argv.slice(3)) {
4
+ const config = await loadViteWpConfig();
5
+ const composerArgs = args.length > 0 ? args : ['install'];
6
+ try {
7
+ await runComposer(config.root, composerArgs);
8
+ }
9
+ catch (error) {
10
+ console.error(error instanceof Error ? error.message : error);
11
+ process.exitCode = 1;
12
+ }
13
+ }
@@ -20,16 +20,16 @@ export async function runDev() {
20
20
  console.log(`- templates: ${config.templates.directory}`);
21
21
  console.log(`- database: ${config.database.driver}://${config.database.user}@${config.database.host}:${config.database.port}/${config.database.name}`);
22
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
23
  try {
31
24
  await ensureComposerInstall(config);
32
25
  writeWordPressConfig(config);
26
+ const result = await runDoctorChecks(config, { live: false });
27
+ if (result.errors > 0) {
28
+ console.log('');
29
+ console.log('Fix the errors above before starting the dev runtime.');
30
+ process.exitCode = 1;
31
+ return;
32
+ }
33
33
  await stopAstroServer(config);
34
34
  config.dev.phpPort = await resolveInternalPort(config.dev.phpHost, config.dev.phpPort);
35
35
  config.dev.astroPort = await resolveInternalPort(config.dev.astroHost, config.dev.astroPort);
@@ -1,6 +1,8 @@
1
1
  import { type LoadedViteWpConfig } from '../config.js';
2
2
  export declare function runDoctor(): Promise<void>;
3
- export declare function runDoctorChecks(config: LoadedViteWpConfig): Promise<{
3
+ export declare function runDoctorChecks(config: LoadedViteWpConfig, options?: {
4
+ live?: boolean;
5
+ }): Promise<{
4
6
  errors: number;
5
7
  warnings: number;
6
8
  }>;
@@ -9,7 +9,8 @@ export async function runDoctor() {
9
9
  const result = await runDoctorChecks(config);
10
10
  process.exitCode = result.errors > 0 ? 1 : 0;
11
11
  }
12
- export async function runDoctorChecks(config) {
12
+ export async function runDoctorChecks(config, options = {}) {
13
+ const live = options.live ?? true;
13
14
  const checks = [];
14
15
  checks.push({
15
16
  status: config.configFile ? 'pass' : 'warn',
@@ -19,15 +20,23 @@ export async function runDoctorChecks(config) {
19
20
  checks.push(fileCheck(config.root, 'astro.config.mjs', 'Astro config'));
20
21
  checks.push(fileCheck(config.root, 'composer.json', 'Composer manifest'));
21
22
  checks.push(composerWordPressCheck(config.root, config.composer.wordpressPackage));
23
+ checks.push(composerInstallDirCheck(config));
22
24
  checks.push(fileCheck(config.root, 'composer.lock', 'Composer lockfile'));
25
+ checks.push(composerLockCheck(config.root, config.composer.wordpressPackage));
23
26
  checks.push(fileCheck(config.root, `${config.wordpress.docroot}/wp-settings.php`, 'Composer-installed WordPress core'));
24
27
  checks.push(fileCheck(config.root, `${config.wordpress.docroot}/wp-config.php`, 'Generated wp-config.php'));
28
+ checks.push(fileCheck(config.root, `${config.wordpress.contentDir}/mu-plugins/vitewp-bridge.php`, 'ViteWP bridge mu-plugin'));
25
29
  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'));
30
+ checks.push(templateDirectoryCheck(config));
27
31
  checks.push(databaseConfigCheck(config));
28
32
  checks.push(await commandCheck('php', ['-v'], 'PHP runtime'));
29
33
  checks.push(await commandCheck('composer', ['--version'], 'Composer'));
30
34
  checks.push(gitignoreCheck(config.root, config.wordpress.docroot));
35
+ checks.push(runtimeFilesIgnoredCheck(config.root, config.wordpress.contentDir));
36
+ checks.push(...requiredPluginFileChecks(config));
37
+ if (live) {
38
+ checks.push(await wordpressHealthCheck(config));
39
+ }
31
40
  printChecks(checks);
32
41
  const errors = checks.filter((check) => check.status === 'fail').length;
33
42
  const warnings = checks.filter((check) => check.status === 'warn').length;
@@ -41,6 +50,16 @@ function fileCheck(root, relativePath, label) {
41
50
  ? { status: 'pass', label, detail: relativePath }
42
51
  : { status: 'warn', label, detail: `${relativePath} does not exist yet.` };
43
52
  }
53
+ function templateDirectoryCheck(config) {
54
+ const directory = join(config.root, config.templates.directory);
55
+ return existsSync(directory)
56
+ ? { status: 'pass', label: 'Project template overrides', detail: config.templates.directory }
57
+ : {
58
+ status: 'pass',
59
+ label: 'Project template overrides',
60
+ detail: `${config.templates.directory} is optional; package defaults will be used.`,
61
+ };
62
+ }
44
63
  async function commandCheck(command, args, label) {
45
64
  try {
46
65
  const { stdout, stderr } = await execFileAsync(command, args);
@@ -114,6 +133,74 @@ function composerWordPressCheck(root, wordpressPackage) {
114
133
  };
115
134
  }
116
135
  }
136
+ function composerInstallDirCheck(config) {
137
+ const composerPath = join(config.root, 'composer.json');
138
+ if (!existsSync(composerPath)) {
139
+ return {
140
+ status: 'warn',
141
+ label: 'Composer WordPress install dir',
142
+ detail: 'composer.json does not exist yet.',
143
+ };
144
+ }
145
+ try {
146
+ const composer = JSON.parse(readFileSync(composerPath, 'utf8'));
147
+ const installDir = composer.extra?.['wordpress-install-dir'];
148
+ if (!installDir) {
149
+ return {
150
+ status: 'warn',
151
+ label: 'Composer WordPress install dir',
152
+ detail: 'extra.wordpress-install-dir is not set.',
153
+ };
154
+ }
155
+ return installDir === config.wordpress.docroot
156
+ ? { status: 'pass', label: 'Composer WordPress install dir', detail: installDir }
157
+ : {
158
+ status: 'fail',
159
+ label: 'Composer WordPress install dir',
160
+ detail: `${installDir} does not match config docroot ${config.wordpress.docroot}.`,
161
+ };
162
+ }
163
+ catch (error) {
164
+ return {
165
+ status: 'fail',
166
+ label: 'Composer WordPress install dir',
167
+ detail: error instanceof Error ? error.message : 'Could not parse composer.json.',
168
+ };
169
+ }
170
+ }
171
+ function composerLockCheck(root, wordpressPackage) {
172
+ const lockPath = join(root, 'composer.lock');
173
+ if (!existsSync(lockPath)) {
174
+ return {
175
+ status: 'warn',
176
+ label: 'Locked WordPress version',
177
+ detail: 'Run composer install to create composer.lock.',
178
+ };
179
+ }
180
+ try {
181
+ const lock = JSON.parse(readFileSync(lockPath, 'utf8'));
182
+ const wordpress = lock.packages?.find((pkg) => pkg.name === wordpressPackage);
183
+ if (!wordpress?.version) {
184
+ return {
185
+ status: 'fail',
186
+ label: 'Locked WordPress version',
187
+ detail: `${wordpressPackage} was not found in composer.lock.`,
188
+ };
189
+ }
190
+ return {
191
+ status: 'pass',
192
+ label: 'Locked WordPress version',
193
+ detail: `${wordpressPackage}@${wordpress.version}`,
194
+ };
195
+ }
196
+ catch (error) {
197
+ return {
198
+ status: 'fail',
199
+ label: 'Locked WordPress version',
200
+ detail: error instanceof Error ? error.message : 'Could not parse composer.lock.',
201
+ };
202
+ }
203
+ }
117
204
  function gitignoreCheck(root, docroot) {
118
205
  const gitignorePath = join(root, '.gitignore');
119
206
  if (!existsSync(gitignorePath)) {
@@ -129,6 +216,72 @@ function gitignoreCheck(root, docroot) {
129
216
  detail: `Add ${normalizedDocroot} to .gitignore.`,
130
217
  };
131
218
  }
219
+ function runtimeFilesIgnoredCheck(root, contentDir) {
220
+ const gitignorePath = join(root, '.gitignore');
221
+ if (!existsSync(gitignorePath)) {
222
+ return { status: 'warn', label: 'WordPress runtime files ignored', detail: '.gitignore is missing.' };
223
+ }
224
+ const gitignore = readFileSync(gitignorePath, 'utf8');
225
+ const required = [
226
+ `${contentDir}/uploads/`,
227
+ `${contentDir}/debug.log`,
228
+ ];
229
+ const missing = required.filter((entry) => !gitignore.includes(entry));
230
+ return missing.length === 0
231
+ ? { status: 'pass', label: 'WordPress runtime files ignored', detail: required.join(', ') }
232
+ : {
233
+ status: 'warn',
234
+ label: 'WordPress runtime files ignored',
235
+ detail: `Add ${missing.join(', ')} to .gitignore.`,
236
+ };
237
+ }
238
+ function requiredPluginFileChecks(config) {
239
+ return config.wordpress.requiredPlugins.map((plugin) => {
240
+ const directory = `${config.wordpress.contentDir}/plugins/${plugin}`;
241
+ return fileCheck(config.root, directory, `Required plugin files: ${plugin}`);
242
+ });
243
+ }
244
+ async function wordpressHealthCheck(config) {
245
+ const url = `${config.wordpress.url.replace(/\/$/, '')}/wp-json/vitewp/v1/health`;
246
+ const controller = new AbortController();
247
+ const timeout = setTimeout(() => controller.abort(), 5000);
248
+ try {
249
+ const response = await fetch(url, { signal: controller.signal });
250
+ if (!response.ok) {
251
+ return {
252
+ status: 'warn',
253
+ label: 'WordPress health endpoint',
254
+ detail: `${url} returned ${response.status}. Start vite-wp dev if the runtime is offline.`,
255
+ };
256
+ }
257
+ const health = await response.json();
258
+ const missingPlugins = config.wordpress.requiredPlugins.filter((plugin) => {
259
+ return !health.activePlugins?.some((active) => active === plugin || active.startsWith(`${plugin}/`));
260
+ });
261
+ if (missingPlugins.length > 0) {
262
+ return {
263
+ status: 'fail',
264
+ label: 'WordPress health endpoint',
265
+ detail: `Missing active plugin(s): ${missingPlugins.join(', ')}.`,
266
+ };
267
+ }
268
+ return {
269
+ status: 'pass',
270
+ label: 'WordPress health endpoint',
271
+ detail: `WP ${health.wordpressVersion ?? '?'} / PHP ${health.phpVersion ?? '?'} / theme ${health.activeTheme ?? '?'} / permalinks ${health.permalinkStructure || 'plain'}`,
272
+ };
273
+ }
274
+ catch {
275
+ return {
276
+ status: 'warn',
277
+ label: 'WordPress health endpoint',
278
+ detail: `Could not reach ${url}. Start vite-wp dev to run live health checks.`,
279
+ };
280
+ }
281
+ finally {
282
+ clearTimeout(timeout);
283
+ }
284
+ }
132
285
  function printChecks(checks) {
133
286
  console.log('ViteWP doctor');
134
287
  console.log('');
@@ -16,6 +16,7 @@ export function runInit() {
16
16
  mkdirSync(target, { recursive: true });
17
17
  copyDirectory(starter, target, force);
18
18
  updatePackageJson(target, packageRoot);
19
+ ensureGitignore(target);
19
20
  console.log(`ViteWP project files initialized in ${relative(process.cwd(), target) || '.'}.`);
20
21
  if (shouldInstall) {
21
22
  installDependencies(target);
@@ -58,6 +59,8 @@ function updatePackageJson(root, packageRoot) {
58
59
  packageJson.scripts.dev ??= 'vite-wp dev';
59
60
  packageJson.scripts.doctor ??= 'vite-wp doctor';
60
61
  packageJson.scripts.types ??= 'vite-wp types';
62
+ packageJson.scripts.composer ??= 'vite-wp composer';
63
+ packageJson.scripts.wp ??= 'vite-wp wp';
61
64
  packageJson.scripts.check ??= 'astro check';
62
65
  packageJson.dependencies['vite-wp'] ??= `^${viteWp.version}`;
63
66
  packageJson.dependencies.astro ??= '^7.0.6';
@@ -65,6 +68,50 @@ function updatePackageJson(root, packageRoot) {
65
68
  packageJson.devDependencies['@astrojs/check'] ??= '^0.9.9';
66
69
  writeFileSync(file, `${JSON.stringify(packageJson, null, 2)}\n`, 'utf8');
67
70
  }
71
+ function ensureGitignore(root) {
72
+ const file = join(root, '.gitignore');
73
+ if (existsSync(file)) {
74
+ return;
75
+ }
76
+ writeFileSync(file, renderGitignore(), 'utf8');
77
+ }
78
+ function renderGitignore() {
79
+ return `# Dependencies
80
+ node_modules/
81
+ vendor/
82
+
83
+ # Environment
84
+ .env
85
+ .env.*
86
+ !.env.example
87
+
88
+ # Build output
89
+ dist/
90
+ dist-ssr/
91
+
92
+ # ViteWP generated/runtime files
93
+ .vitewp/
94
+ .astro/
95
+ wordpress/public/
96
+ wordpress/content/mu-plugins/vitewp-bridge.php
97
+ wordpress/content/themes/vitewp/
98
+ wordpress/content/uploads/
99
+ wordpress/content/debug.log
100
+
101
+ # Logs
102
+ logs/
103
+ *.log
104
+ npm-debug.log*
105
+ yarn-debug.log*
106
+ pnpm-debug.log*
107
+
108
+ # Editor and OS files
109
+ .DS_Store
110
+ .idea/
111
+ .vscode/*
112
+ !.vscode/extensions.json
113
+ `;
114
+ }
68
115
  function readOwnPackage(packageRoot) {
69
116
  const file = join(packageRoot, 'package.json');
70
117
  const packageJson = JSON.parse(readFileSync(file, 'utf8'));
@@ -0,0 +1 @@
1
+ export declare function runWpCommand(args?: string[]): Promise<void>;
@@ -0,0 +1,41 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { spawn } from 'node:child_process';
4
+ import { loadViteWpConfig } from '../config.js';
5
+ export async function runWpCommand(args = process.argv.slice(3)) {
6
+ const config = await loadViteWpConfig();
7
+ const command = wpBinary(config);
8
+ if (!command) {
9
+ console.error('Could not find WP-CLI. Install it globally as `wp` or add it to Composer.');
10
+ process.exitCode = 1;
11
+ return;
12
+ }
13
+ const wpArgs = [
14
+ `--path=${config.wordpress.docroot}`,
15
+ `--url=${config.wordpress.url}`,
16
+ ...args,
17
+ ];
18
+ const code = await spawnWp(config, command, wpArgs);
19
+ process.exitCode = code;
20
+ }
21
+ function wpBinary(config) {
22
+ const local = join(config.root, 'vendor/bin/wp');
23
+ if (existsSync(local)) {
24
+ return local;
25
+ }
26
+ return process.platform === 'win32' ? 'wp.cmd' : 'wp';
27
+ }
28
+ function spawnWp(config, command, args) {
29
+ return new Promise((resolve) => {
30
+ const child = spawn(command, args, {
31
+ cwd: config.root,
32
+ stdio: 'inherit',
33
+ env: process.env,
34
+ });
35
+ child.once('exit', (code) => resolve(code ?? 1));
36
+ child.once('error', (error) => {
37
+ console.error(`Could not run WP-CLI (${command}). ${error.message}`);
38
+ resolve(1);
39
+ });
40
+ });
41
+ }
package/dist/config.d.ts CHANGED
@@ -14,6 +14,7 @@ export interface ViteWpConfig {
14
14
  url?: string;
15
15
  docroot?: string;
16
16
  contentDir?: string;
17
+ requiredPlugins?: string[];
17
18
  };
18
19
  composer?: {
19
20
  install?: boolean;
@@ -49,6 +50,7 @@ export interface LoadedViteWpConfig {
49
50
  url: string;
50
51
  docroot: string;
51
52
  contentDir: string;
53
+ requiredPlugins: string[];
52
54
  };
53
55
  composer: {
54
56
  install: boolean;
package/dist/config.js CHANGED
@@ -37,6 +37,7 @@ export async function loadViteWpConfig(root = process.cwd()) {
37
37
  url: userConfig.wordpress?.url ?? 'http://localhost:3000',
38
38
  docroot: userConfig.wordpress?.docroot ?? 'wordpress/public',
39
39
  contentDir: userConfig.wordpress?.contentDir ?? 'wordpress/content',
40
+ requiredPlugins: userConfig.wordpress?.requiredPlugins ?? [],
40
41
  },
41
42
  composer: {
42
43
  install: userConfig.composer?.install ?? true,
@@ -1,2 +1,3 @@
1
1
  import type { LoadedViteWpConfig } from '../config.js';
2
2
  export declare function ensureComposerInstall(config: LoadedViteWpConfig): Promise<void>;
3
+ export declare function runComposer(cwd: string, args: string[]): Promise<void>;
@@ -7,19 +7,23 @@ export async function ensureComposerInstall(config) {
7
7
  return;
8
8
  }
9
9
  const lockfile = join(config.root, 'composer.lock');
10
+ const manifest = join(config.root, 'composer.json');
10
11
  const vendor = join(config.root, 'vendor');
11
12
  const wpSettings = join(config.root, config.wordpress.docroot, 'wp-settings.php');
13
+ if (!existsSync(manifest)) {
14
+ throw new Error('composer.json is missing. Run `vite-wp init` or add a Composer manifest before starting dev.');
15
+ }
12
16
  if (existsSync(lockfile) && existsSync(vendor) && existsSync(wpSettings)) {
13
17
  console.log('✓ Composer dependencies already installed');
14
18
  return;
15
19
  }
16
20
  console.log('Installing Composer dependencies...');
17
- await runComposerInstall(config.root);
21
+ await runComposer(config.root, ['install']);
18
22
  console.log('✓ Composer dependencies ready');
19
23
  }
20
- function runComposerInstall(cwd) {
24
+ export function runComposer(cwd, args) {
21
25
  return new Promise((resolve, reject) => {
22
- const child = spawn('composer', ['install'], {
26
+ const child = spawn('composer', args, {
23
27
  cwd,
24
28
  stdio: 'inherit',
25
29
  env: { ...process.env, COMPOSER_ALLOW_SUPERUSER: process.env.COMPOSER_ALLOW_SUPERUSER ?? '1' },
@@ -29,8 +33,11 @@ function runComposerInstall(cwd) {
29
33
  resolve();
30
34
  }
31
35
  else {
32
- reject(new Error(`composer install failed with code ${code ?? 'unknown'}.`));
36
+ reject(new Error(`composer ${args.join(' ')} failed with code ${code ?? 'unknown'}.`));
33
37
  }
34
38
  });
39
+ child.once('error', (error) => {
40
+ reject(new Error(`Could not run composer. Is Composer installed?\n${error.message}`));
41
+ });
35
42
  });
36
43
  }
@@ -1,6 +1,7 @@
1
- import { mkdirSync, symlinkSync, writeFileSync, existsSync, lstatSync } from 'node:fs';
1
+ import { cpSync, existsSync, lstatSync, mkdirSync, symlinkSync, writeFileSync } from 'node:fs';
2
2
  import { dirname, join, relative, resolve } from 'node:path';
3
3
  import { randomBytes } from 'node:crypto';
4
+ import { fileURLToPath } from 'node:url';
4
5
  export function writeWordPressConfig(config) {
5
6
  const docroot = resolve(config.root, config.wordpress.docroot);
6
7
  const contentDir = resolve(config.root, config.wordpress.contentDir);
@@ -10,6 +11,7 @@ export function writeWordPressConfig(config) {
10
11
  mkdirSync(join(contentDir, 'plugins'), { recursive: true });
11
12
  mkdirSync(join(contentDir, 'mu-plugins'), { recursive: true });
12
13
  mkdirSync(join(contentDir, 'uploads'), { recursive: true });
14
+ ensureDefaultContentFiles(contentDir);
13
15
  ensureContentSymlink(docroot, contentDir);
14
16
  writeFileSync(wpConfigPath, renderWpConfig(config, publicUrl, contentDir), 'utf8');
15
17
  console.log(`✓ wp-config.php generated at ${relative(config.root, wpConfigPath)}`);
@@ -80,3 +82,20 @@ function ensureContentSymlink(docroot, contentDir) {
80
82
  const target = relative(dirname(link), contentDir);
81
83
  symlinkSync(target, link, 'dir');
82
84
  }
85
+ function ensureDefaultContentFiles(contentDir) {
86
+ const packageContentDir = resolve(dirname(fileURLToPath(import.meta.url)), '../../defaults/wordpress/content');
87
+ copyPackageFile(join(packageContentDir, 'mu-plugins/vitewp-bridge.php'), join(contentDir, 'mu-plugins/vitewp-bridge.php'));
88
+ copyIfMissing(join(packageContentDir, 'themes/vitewp'), join(contentDir, 'themes/vitewp'));
89
+ }
90
+ function copyPackageFile(source, destination) {
91
+ if (!existsSync(source))
92
+ return;
93
+ mkdirSync(dirname(destination), { recursive: true });
94
+ cpSync(source, destination);
95
+ }
96
+ function copyIfMissing(source, destination) {
97
+ if (existsSync(destination) || !existsSync(source))
98
+ return;
99
+ mkdirSync(dirname(destination), { recursive: true });
100
+ cpSync(source, destination, { recursive: true });
101
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "vite-wp",
3
3
  "private": false,
4
- "version": "0.1.7",
4
+ "version": "0.1.9",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "vite-wp": "dist/cli.js"
@@ -43,6 +43,8 @@
43
43
  "scripts": {
44
44
  "dev": "tsx src/cli.ts dev",
45
45
  "doctor": "tsx src/cli.ts doctor",
46
+ "composer": "tsx src/cli.ts composer",
47
+ "wp": "tsx src/cli.ts wp",
46
48
  "build": "rm -rf dist && tsc",
47
49
  "prepack": "npm run build",
48
50
  "pack:dry": "npm pack --dry-run",
File without changes