@veaceslav-golden/wp-ai-kit 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/dist/index.js ADDED
@@ -0,0 +1,596 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/cli.ts
4
+ import { createRequire } from "module";
5
+ import { Command } from "commander";
6
+
7
+ // src/commands/init/index.ts
8
+ import { intro, outro, note, spinner, logger as logger7, Rollback } from "@veaceslav-golden/wp-ai-kit-core";
9
+
10
+ // src/commands/init/prompts.ts
11
+ import { promptText, confirm, select } from "@veaceslav-golden/wp-ai-kit-core";
12
+
13
+ // src/commands/init/normalize-name.ts
14
+ function toSlug(input) {
15
+ return input.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
16
+ }
17
+ function toPascalCase(input) {
18
+ return input.trim().replace(/[^a-zA-Z0-9]+/g, " ").trim().split(" ").map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join("");
19
+ }
20
+
21
+ // src/commands/init/prompts.ts
22
+ async function askInitQuestions() {
23
+ const location = await select({
24
+ message: "Where do you want to set up the project?",
25
+ options: [
26
+ { value: "new-folder", label: "Create a new folder", hint: "recommended" },
27
+ { value: "current-dir", label: "Use the current directory" }
28
+ ]
29
+ });
30
+ const projectName = await promptText({
31
+ message: "Project name:",
32
+ placeholder: "my-wordpress-site",
33
+ validate: (v) => {
34
+ if (!v.trim()) return "Project name is required";
35
+ if (!/^[a-zA-Z0-9 _-]+$/.test(v.trim()))
36
+ return "Only letters, numbers, spaces, hyphens, and underscores are allowed";
37
+ }
38
+ });
39
+ const defaultSlug = toSlug(projectName);
40
+ const slug = await promptText({
41
+ message: "Slug (folder name / text domain):",
42
+ placeholder: defaultSlug,
43
+ defaultValue: defaultSlug,
44
+ validate: (v) => {
45
+ if (!v.trim()) return "Slug is required";
46
+ if (!/^[a-z0-9-]+$/.test(v.trim()))
47
+ return "Slug must be lowercase letters, numbers, and hyphens only";
48
+ }
49
+ });
50
+ const defaultNamespace = toPascalCase(slug);
51
+ const namespace = await promptText({
52
+ message: "PHP namespace (PascalCase):",
53
+ placeholder: defaultNamespace,
54
+ defaultValue: defaultNamespace,
55
+ validate: (v) => {
56
+ if (!v.trim()) return "Namespace is required";
57
+ if (!/^[A-Z][a-zA-Z0-9]*$/.test(v.trim()))
58
+ return "Namespace must start with uppercase letter, letters and numbers only";
59
+ }
60
+ });
61
+ const textDomain = slug;
62
+ const preset = await select({
63
+ message: "Preset:",
64
+ options: [{ value: "standard", label: "Standard", hint: "Timber + ACF + Vite + Tailwind" }],
65
+ initialValue: "standard"
66
+ });
67
+ const ready = await confirm(
68
+ `Ready to scaffold "${projectName}"${location === "new-folder" ? ` in ./${slug}/` : " in the current directory"}?`
69
+ );
70
+ if (!ready) {
71
+ process.exit(0);
72
+ }
73
+ return {
74
+ location,
75
+ projectName,
76
+ slug,
77
+ namespace,
78
+ textDomain,
79
+ preset
80
+ };
81
+ }
82
+
83
+ // src/commands/init/steps/create-dir.ts
84
+ import { mkdirSync, existsSync, readdirSync } from "fs";
85
+ import { resolve } from "path";
86
+ import { logger } from "@veaceslav-golden/wp-ai-kit-core";
87
+ import { rmSync } from "fs";
88
+ function createProjectDir(cwd, slug, useCurrentDir, rollback) {
89
+ if (useCurrentDir) {
90
+ const entries = readdirSync(cwd);
91
+ const nonHidden = entries.filter((e) => !e.startsWith("."));
92
+ if (nonHidden.length > 0) {
93
+ logger.warn(
94
+ `Current directory is not empty (${nonHidden.length} item(s) found). Continuing anyway.`
95
+ );
96
+ }
97
+ logger.step("Using current directory");
98
+ return cwd;
99
+ }
100
+ const targetDir = resolve(cwd, slug);
101
+ if (existsSync(targetDir)) {
102
+ throw new Error(`Directory "${slug}" already exists in the current folder.`);
103
+ }
104
+ mkdirSync(targetDir, { recursive: true });
105
+ rollback.add(() => {
106
+ rmSync(targetDir, { recursive: true, force: true });
107
+ logger.step(`Removed directory: ${slug}`);
108
+ });
109
+ logger.step(`Created directory: ${slug}/`);
110
+ return targetDir;
111
+ }
112
+
113
+ // src/commands/init/steps/download-wp.ts
114
+ import { createWriteStream, rmSync as rmSync2, existsSync as existsSync2 } from "fs";
115
+ import { resolve as resolve2 } from "path";
116
+ import { pipeline } from "stream/promises";
117
+ import { exec, logger as logger2 } from "@veaceslav-golden/wp-ai-kit-core";
118
+
119
+ // src/constants.ts
120
+ var BOILERPLATE_REPO = "git@github.com:GoldenCodesSlava/boilerplate-wp-standard.git";
121
+ var WP_VERSION_API = "https://api.wordpress.org/core/version-check/1.7/";
122
+ var WP_DOWNLOAD_URL = (version2) => `https://wordpress.org/wordpress-${version2}.tar.gz`;
123
+ var BOILERPLATE_THEME_SLUG = "boilerplate";
124
+ var BOILERPLATE_NAMESPACE = "Boilerplate";
125
+ var BOILERPLATE_TEXT_DOMAIN = "boilerplate";
126
+ var BOILERPLATE_THEME_NAME = "Boilerplate";
127
+ var BOILERPLATE_COMPOSER_NAME = "boilerplate/wordpress-theme";
128
+
129
+ // src/commands/init/steps/download-wp.ts
130
+ async function fetchLatestWpVersion() {
131
+ const res = await fetch(WP_VERSION_API);
132
+ if (!res.ok) throw new Error(`Failed to fetch WP version info: ${res.status}`);
133
+ const data = await res.json();
134
+ const latest = data.offers.find((o) => o.response === "upgrade" || o.response === "latest");
135
+ if (!latest) throw new Error("Could not determine latest WordPress version");
136
+ return latest.version;
137
+ }
138
+ async function downloadWordPress(targetDir) {
139
+ logger2.step("Fetching latest WordPress version...");
140
+ const version2 = await fetchLatestWpVersion();
141
+ logger2.step(`Downloading WordPress ${version2}...`);
142
+ const tarballUrl = WP_DOWNLOAD_URL(version2);
143
+ const tarballPath = resolve2(targetDir, "wordpress.tar.gz");
144
+ const res = await fetch(tarballUrl);
145
+ if (!res.ok) throw new Error(`Failed to download WordPress: ${res.status}`);
146
+ if (!res.body) throw new Error("Response body is empty");
147
+ const { Writable } = await import("stream");
148
+ const fileStream = createWriteStream(tarballPath);
149
+ const webStream = res.body;
150
+ const { Readable } = await import("stream");
151
+ const nodeReadable = Readable.fromWeb(webStream);
152
+ await pipeline(nodeReadable, fileStream);
153
+ logger2.step(`Extracting WordPress ${version2}...`);
154
+ await exec("tar", ["-xzf", "wordpress.tar.gz", "--strip-components=1"], { cwd: targetDir });
155
+ rmSync2(tarballPath, { force: true });
156
+ const wpContentDir = resolve2(targetDir, "wp-content");
157
+ if (existsSync2(wpContentDir)) {
158
+ rmSync2(wpContentDir, { recursive: true, force: true });
159
+ }
160
+ logger2.step(`WordPress ${version2} extracted`);
161
+ }
162
+
163
+ // src/commands/init/steps/clone-boilerplate.ts
164
+ import { resolve as resolve3 } from "path";
165
+ import { existsSync as existsSync3, rmSync as rmSync3, renameSync } from "fs";
166
+ import { exec as exec2, logger as logger3 } from "@veaceslav-golden/wp-ai-kit-core";
167
+ async function cloneBoilerplate(targetDir) {
168
+ logger3.step("Cloning boilerplate wp-content...");
169
+ const tempDir = resolve3(targetDir, "__boilerplate-tmp");
170
+ try {
171
+ await exec2("git", ["clone", "--depth", "1", BOILERPLATE_REPO, tempDir], {
172
+ env: {
173
+ ...process.env,
174
+ // BatchMode=yes: fail immediately instead of hanging on passphrase/host prompts
175
+ // ConnectTimeout=10: don't wait forever if GitHub is unreachable
176
+ // StrictHostKeyChecking=accept-new: auto-accept github.com host key on first connection
177
+ GIT_SSH_COMMAND: "ssh -o BatchMode=yes -o ConnectTimeout=10 -o StrictHostKeyChecking=accept-new"
178
+ }
179
+ });
180
+ } catch (err) {
181
+ rmSync3(tempDir, { recursive: true, force: true });
182
+ const msg = err.message;
183
+ if (msg.includes("Permission denied") || msg.includes("publickey")) {
184
+ throw new Error(
185
+ "SSH authentication failed.\n\nYour SSH key is not loaded in the agent. Run:\n macOS: ssh-add --apple-use-keychain ~/.ssh/id_ed25519\n Linux: ssh-add ~/.ssh/id_ed25519\n\nThen verify with: ssh -T git@github.com"
186
+ );
187
+ }
188
+ throw err;
189
+ }
190
+ const clonedWpContent = resolve3(tempDir, "wp-content");
191
+ if (!existsSync3(clonedWpContent)) {
192
+ rmSync3(tempDir, { recursive: true, force: true });
193
+ throw new Error("Boilerplate repository does not contain a wp-content directory");
194
+ }
195
+ const destWpContent = resolve3(targetDir, "wp-content");
196
+ renameSync(clonedWpContent, destWpContent);
197
+ rmSync3(tempDir, { recursive: true, force: true });
198
+ logger3.step("Boilerplate wp-content cloned");
199
+ }
200
+
201
+ // src/commands/init/steps/rename.ts
202
+ import {
203
+ readdirSync as readdirSync2,
204
+ statSync,
205
+ readFileSync,
206
+ writeFileSync,
207
+ renameSync as renameSync2,
208
+ existsSync as existsSync4
209
+ } from "fs";
210
+ import { resolve as resolve4, join } from "path";
211
+ import { logger as logger4 } from "@veaceslav-golden/wp-ai-kit-core";
212
+ var TEXT_EXTENSIONS = /* @__PURE__ */ new Set([
213
+ ".php",
214
+ ".json",
215
+ ".css",
216
+ ".js",
217
+ ".ts",
218
+ ".twig",
219
+ ".html",
220
+ ".txt",
221
+ ".md",
222
+ ".xml",
223
+ ".neon",
224
+ ".yml",
225
+ ".yaml"
226
+ ]);
227
+ function replaceInFile(filePath, replacements) {
228
+ let content = readFileSync(filePath, "utf-8");
229
+ let changed = false;
230
+ for (const [from, to] of replacements) {
231
+ const next = content.replaceAll(from, to);
232
+ if (next !== content) {
233
+ changed = true;
234
+ content = next;
235
+ }
236
+ }
237
+ if (changed) {
238
+ writeFileSync(filePath, content, "utf-8");
239
+ }
240
+ }
241
+ function walkDir(dir, callback) {
242
+ for (const entry of readdirSync2(dir)) {
243
+ if (entry.startsWith(".") || entry === "vendor" || entry === "node_modules") continue;
244
+ const fullPath = join(dir, entry);
245
+ const stat = statSync(fullPath);
246
+ if (stat.isDirectory()) {
247
+ walkDir(fullPath, callback);
248
+ } else {
249
+ callback(fullPath);
250
+ }
251
+ }
252
+ }
253
+ function renameBoilerplate(targetDir, projectName, slug, namespace, textDomain) {
254
+ logger4.step("Renaming boilerplate identifiers...");
255
+ const themesDir = resolve4(targetDir, "wp-content", "themes");
256
+ const boilerplateThemeDir = resolve4(themesDir, BOILERPLATE_THEME_SLUG);
257
+ const newThemeDir = resolve4(themesDir, slug);
258
+ if (!existsSync4(boilerplateThemeDir)) {
259
+ throw new Error(`Expected theme folder "themes/${BOILERPLATE_THEME_SLUG}" not found`);
260
+ }
261
+ const replacements = [
262
+ // PHP namespace: "namespace Boilerplate\" → "namespace {Namespace}\"
263
+ [`namespace ${BOILERPLATE_NAMESPACE}\\`, `namespace ${namespace}\\`],
264
+ // PHP use statements: "use Boilerplate\" → "use {Namespace}\"
265
+ [`use ${BOILERPLATE_NAMESPACE}\\`, `use ${namespace}\\`],
266
+ // Composer autoload PSR-4 key: "Boilerplate\\" → "{Namespace}\\"
267
+ [`${BOILERPLATE_NAMESPACE}\\\\`, `${namespace}\\\\`],
268
+ // composer.json name: "boilerplate/wordpress-theme" → "{slug}/wordpress-theme"
269
+ [BOILERPLATE_COMPOSER_NAME, `${slug}/wordpress-theme`],
270
+ // style.css Theme Name
271
+ [`Theme Name: ${BOILERPLATE_THEME_NAME}`, `Theme Name: ${projectName}`],
272
+ // Text domain in PHP: 'boilerplate' → '{textDomain}'
273
+ [`'${BOILERPLATE_TEXT_DOMAIN}'`, `'${textDomain}'`],
274
+ // Text domain in JSON/twig: "boilerplate" → "{textDomain}"
275
+ [`"${BOILERPLATE_TEXT_DOMAIN}"`, `"${textDomain}"`],
276
+ // ACF JSON field keys that reference the theme slug
277
+ [BOILERPLATE_THEME_SLUG, slug]
278
+ ];
279
+ walkDir(boilerplateThemeDir, (filePath) => {
280
+ const ext = filePath.slice(filePath.lastIndexOf("."));
281
+ if (TEXT_EXTENSIONS.has(ext)) {
282
+ replaceInFile(filePath, replacements);
283
+ }
284
+ });
285
+ renameSync2(boilerplateThemeDir, newThemeDir);
286
+ logger4.step(`Theme renamed: themes/${BOILERPLATE_THEME_SLUG} \u2192 themes/${slug}`);
287
+ }
288
+
289
+ // src/commands/init/steps/post-install.ts
290
+ import { existsSync as existsSync5 } from "fs";
291
+ import { resolve as resolve5 } from "path";
292
+ import { exec as exec3, logger as logger5 } from "@veaceslav-golden/wp-ai-kit-core";
293
+ async function runIfExists(cmd, args, cwd, label) {
294
+ try {
295
+ await exec3(cmd, ["--version"], { cwd });
296
+ } catch {
297
+ logger5.warn(`${label}: "${cmd}" not found, skipping`);
298
+ return;
299
+ }
300
+ logger5.step(`Running ${label}...`);
301
+ try {
302
+ await exec3(cmd, args, { cwd, verbose: false });
303
+ logger5.step(`${label} complete`);
304
+ } catch (err) {
305
+ logger5.warn(`${label} failed: ${err.message}`);
306
+ }
307
+ }
308
+ async function runPostInstall(targetDir, slug) {
309
+ const themeDir = resolve5(targetDir, "wp-content", "themes", slug);
310
+ if (!existsSync5(themeDir)) {
311
+ logger5.warn("Theme directory not found, skipping post-install");
312
+ return;
313
+ }
314
+ const hasComposer = existsSync5(resolve5(themeDir, "composer.json"));
315
+ const hasPackageJson = existsSync5(resolve5(themeDir, "package.json"));
316
+ if (hasComposer) {
317
+ await runIfExists("composer", ["install", "--no-interaction"], themeDir, "composer install");
318
+ }
319
+ if (hasPackageJson) {
320
+ await runIfExists("npm", ["install"], themeDir, "npm install");
321
+ await runIfExists("npm", ["run", "build"], themeDir, "npm run build");
322
+ }
323
+ }
324
+
325
+ // src/commands/init/steps/write-config.ts
326
+ import { writeConfig, logger as logger6 } from "@veaceslav-golden/wp-ai-kit-core";
327
+ function writeProjectConfig(targetDir, data) {
328
+ const config = {
329
+ ...data,
330
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
331
+ };
332
+ writeConfig(config, targetDir);
333
+ logger6.step("Created .wpaikit.json");
334
+ }
335
+
336
+ // src/commands/init/index.ts
337
+ async function runInit() {
338
+ intro("wpaikit init");
339
+ const answers = await askInitQuestions();
340
+ const { location, projectName, slug, namespace, textDomain, preset } = answers;
341
+ const cwd = process.cwd();
342
+ const rollback = new Rollback();
343
+ try {
344
+ const targetDir = createProjectDir(cwd, slug, location === "current-dir", rollback);
345
+ const s = spinner();
346
+ s.start("Downloading WordPress...");
347
+ try {
348
+ await downloadWordPress(targetDir);
349
+ s.stop("WordPress downloaded");
350
+ } catch (err) {
351
+ s.stop("Download failed");
352
+ throw err;
353
+ }
354
+ const s2 = spinner();
355
+ s2.start("Cloning boilerplate...");
356
+ try {
357
+ await cloneBoilerplate(targetDir);
358
+ s2.stop("Boilerplate cloned");
359
+ } catch (err) {
360
+ s2.stop("Clone failed");
361
+ throw err;
362
+ }
363
+ renameBoilerplate(targetDir, projectName, slug, namespace, textDomain);
364
+ await runPostInstall(targetDir, slug);
365
+ writeProjectConfig(targetDir, { name: projectName, namespace, textDomain, preset });
366
+ rollback.clear();
367
+ const isCurrentDir = location === "current-dir";
368
+ const relativePath = isCurrentDir ? "." : slug;
369
+ const nextSteps = [
370
+ `Theme: wp-content/themes/${slug}/`,
371
+ `Namespace: ${namespace}\\\\Theme`,
372
+ `Text domain: ${textDomain}`,
373
+ "",
374
+ "Next steps:"
375
+ ];
376
+ let stepNum = 1;
377
+ if (!isCurrentDir) nextSteps.push(` ${stepNum++}. cd ${slug}`);
378
+ nextSteps.push(` ${stepNum++}. Create a local database (Herd, MAMP, TablePlus, or CLI)`);
379
+ nextSteps.push(` ${stepNum++}. Configure wp-config.php (DB_NAME, DB_USER, DB_PASSWORD, DB_HOST)`);
380
+ nextSteps.push(` ${stepNum++}. Open the site in a browser and complete WordPress setup`);
381
+ nextSteps.push(` ${stepNum}. Activate the "${slug}" theme in wp-admin`);
382
+ note(nextSteps.join("\n"), "Project scaffolded");
383
+ outro(`Done! Project "${projectName}" is ready.`);
384
+ } catch (err) {
385
+ if (rollback.size > 0) {
386
+ logger7.warn("Scaffolding failed \u2014 rolling back...");
387
+ await rollback.run();
388
+ }
389
+ throw err;
390
+ }
391
+ }
392
+
393
+ // src/commands/doctor/index.ts
394
+ import { intro as intro2, outro as outro2, logger as logger8 } from "@veaceslav-golden/wp-ai-kit-core";
395
+
396
+ // src/commands/doctor/checks.ts
397
+ import { exec as exec4 } from "@veaceslav-golden/wp-ai-kit-core";
398
+ import { accessSync, constants } from "fs";
399
+ async function checkNode() {
400
+ const version2 = process.version;
401
+ const major = parseInt(version2.slice(1));
402
+ if (major < 18) {
403
+ return {
404
+ name: "Node.js",
405
+ status: "error",
406
+ message: `${version2} (minimum: v18)`,
407
+ fix: "Update Node.js to v18 or later: https://nodejs.org"
408
+ };
409
+ }
410
+ return { name: "Node.js", status: "ok", message: version2 };
411
+ }
412
+ async function checkGit() {
413
+ try {
414
+ const { stdout } = await exec4("git", ["--version"]);
415
+ const version2 = stdout.trim().replace("git version ", "");
416
+ return { name: "git", status: "ok", message: version2 };
417
+ } catch {
418
+ return {
419
+ name: "git",
420
+ status: "error",
421
+ message: "not found",
422
+ fix: "Install git: https://git-scm.com"
423
+ };
424
+ }
425
+ }
426
+ async function checkComposer() {
427
+ try {
428
+ const { stdout } = await exec4("composer", ["--version", "--no-ansi"]);
429
+ const version2 = stdout.trim().split("\n")[0] ?? "unknown";
430
+ return { name: "composer", status: "ok", message: version2 };
431
+ } catch {
432
+ return {
433
+ name: "composer",
434
+ status: "warn",
435
+ message: "not found (optional)",
436
+ fix: "Install Composer: https://getcomposer.org \u2014 needed for theme PHP dependencies"
437
+ };
438
+ }
439
+ }
440
+ async function checkNpm() {
441
+ try {
442
+ const { stdout } = await exec4("npm", ["--version"]);
443
+ return { name: "npm", status: "ok", message: `v${stdout.trim()}` };
444
+ } catch {
445
+ return {
446
+ name: "npm",
447
+ status: "warn",
448
+ message: "not found (optional)",
449
+ fix: "Install Node.js (includes npm): https://nodejs.org \u2014 needed for theme frontend build"
450
+ };
451
+ }
452
+ }
453
+ async function checkSsh() {
454
+ try {
455
+ await exec4("ssh", [
456
+ "-T",
457
+ "git@github.com",
458
+ "-o",
459
+ "BatchMode=yes",
460
+ "-o",
461
+ "ConnectTimeout=5",
462
+ "-o",
463
+ "StrictHostKeyChecking=accept-new"
464
+ ]);
465
+ return { name: "SSH \u2192 github.com", status: "ok", message: "authenticated" };
466
+ } catch (err) {
467
+ const msg = err.message ?? "";
468
+ if (msg.includes("successfully authenticated")) {
469
+ return { name: "SSH \u2192 github.com", status: "ok", message: "authenticated" };
470
+ }
471
+ if (msg.includes("Permission denied") || msg.includes("publickey")) {
472
+ return {
473
+ name: "SSH \u2192 github.com",
474
+ status: "error",
475
+ message: "SSH key not loaded in agent",
476
+ fix: "macOS: ssh-add --apple-use-keychain ~/.ssh/id_ed25519\nLinux: ssh-add ~/.ssh/id_ed25519"
477
+ };
478
+ }
479
+ return {
480
+ name: "SSH \u2192 github.com",
481
+ status: "warn",
482
+ message: "could not connect (check your internet connection)"
483
+ };
484
+ }
485
+ }
486
+ function checkWriteAccess(cwd) {
487
+ try {
488
+ accessSync(cwd, constants.W_OK);
489
+ return { name: "Write access", status: "ok", message: cwd };
490
+ } catch {
491
+ return {
492
+ name: "Write access",
493
+ status: "error",
494
+ message: `no write permission in ${cwd}`,
495
+ fix: "Run from a directory where you have write access"
496
+ };
497
+ }
498
+ }
499
+ async function runAllChecks(cwd) {
500
+ const [node, git, composer, npm, ssh] = await Promise.all([
501
+ checkNode(),
502
+ checkGit(),
503
+ checkComposer(),
504
+ checkNpm(),
505
+ checkSsh()
506
+ ]);
507
+ const write = checkWriteAccess(cwd);
508
+ return [node, git, composer, npm, ssh, write];
509
+ }
510
+
511
+ // src/commands/doctor/index.ts
512
+ var ICONS = {
513
+ ok: "\u2713",
514
+ warn: "\u26A0",
515
+ error: "\u2717"
516
+ };
517
+ function formatResults(results) {
518
+ for (const r of results) {
519
+ const icon = ICONS[r.status];
520
+ const line = `${icon} ${r.name}: ${r.message}`;
521
+ if (r.status === "ok") logger8.success(line);
522
+ else if (r.status === "warn") logger8.warn(line);
523
+ else logger8.error(line);
524
+ if (r.fix && r.status !== "ok") {
525
+ for (const fixLine of r.fix.split("\n")) {
526
+ logger8.message(` ${fixLine}`);
527
+ }
528
+ }
529
+ }
530
+ }
531
+ function printJson(results) {
532
+ const hasError = results.some((r) => r.status === "error");
533
+ process.stdout.write(
534
+ JSON.stringify(
535
+ {
536
+ ok: !hasError,
537
+ checks: results.map(({ name, status, message, fix }) => ({
538
+ name,
539
+ status,
540
+ message,
541
+ ...fix ? { fix } : {}
542
+ }))
543
+ },
544
+ null,
545
+ 2
546
+ ) + "\n"
547
+ );
548
+ }
549
+ async function runDoctor(options) {
550
+ if (!options.json) intro2("wpaikit doctor");
551
+ const results = await runAllChecks(process.cwd());
552
+ if (options.json) {
553
+ printJson(results);
554
+ return;
555
+ }
556
+ formatResults(results);
557
+ const errors = results.filter((r) => r.status === "error");
558
+ const warns = results.filter((r) => r.status === "warn");
559
+ if (errors.length > 0) {
560
+ outro2(`${errors.length} error(s) found \u2014 fix them before running wpaikit init`);
561
+ process.exit(1);
562
+ } else if (warns.length > 0) {
563
+ outro2(`All required checks passed (${warns.length} warning(s))`);
564
+ } else {
565
+ outro2("All checks passed \u2014 ready to run wpaikit init");
566
+ }
567
+ }
568
+
569
+ // src/cli.ts
570
+ var require2 = createRequire(import.meta.url);
571
+ var { version } = require2("../package.json");
572
+ var program = new Command().name("wpaikit").description("CLI for scaffolding and developing WordPress sites with custom boilerplates").version(version, "-v, --version", "output the current version");
573
+ program.command("init").description("Scaffold a new WordPress project from the standard boilerplate").action(async () => {
574
+ try {
575
+ await runInit();
576
+ } catch (err) {
577
+ process.stderr.write(`
578
+ Error: ${err.message}
579
+ `);
580
+ process.exit(1);
581
+ }
582
+ });
583
+ program.command("doctor").description("Check that your environment is ready to use wpaikit").option("--json", "output results as JSON").action(async (opts) => {
584
+ try {
585
+ await runDoctor(opts);
586
+ } catch (err) {
587
+ process.stderr.write(`
588
+ Error: ${err.message}
589
+ `);
590
+ process.exit(1);
591
+ }
592
+ });
593
+
594
+ // src/index.ts
595
+ program.parse();
596
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/cli.ts","../src/commands/init/index.ts","../src/commands/init/prompts.ts","../src/commands/init/normalize-name.ts","../src/commands/init/steps/create-dir.ts","../src/commands/init/steps/download-wp.ts","../src/constants.ts","../src/commands/init/steps/clone-boilerplate.ts","../src/commands/init/steps/rename.ts","../src/commands/init/steps/post-install.ts","../src/commands/init/steps/write-config.ts","../src/commands/doctor/index.ts","../src/commands/doctor/checks.ts","../src/index.ts"],"sourcesContent":["import { createRequire } from 'node:module'\nimport { Command } from 'commander'\nimport { runInit } from './commands/init/index.js'\nimport { runDoctor } from './commands/doctor/index.js'\n\nconst require = createRequire(import.meta.url)\nconst { version } = require('../package.json') as { version: string }\n\nexport const program = new Command()\n .name('wpaikit')\n .description('CLI for scaffolding and developing WordPress sites with custom boilerplates')\n .version(version, '-v, --version', 'output the current version')\n\nprogram\n .command('init')\n .description('Scaffold a new WordPress project from the standard boilerplate')\n .action(async () => {\n try {\n await runInit()\n } catch (err) {\n process.stderr.write(`\\nError: ${(err as Error).message}\\n`)\n process.exit(1)\n }\n })\n\nprogram\n .command('doctor')\n .description('Check that your environment is ready to use wpaikit')\n .option('--json', 'output results as JSON')\n .action(async (opts: { json?: boolean }) => {\n try {\n await runDoctor(opts)\n } catch (err) {\n process.stderr.write(`\\nError: ${(err as Error).message}\\n`)\n process.exit(1)\n }\n })\n","import { resolve } from 'node:path'\nimport { intro, outro, note, spinner, logger, Rollback } from '@veaceslav-golden/wp-ai-kit-core'\nimport { askInitQuestions } from './prompts.js'\nimport { createProjectDir } from './steps/create-dir.js'\nimport { downloadWordPress } from './steps/download-wp.js'\nimport { cloneBoilerplate } from './steps/clone-boilerplate.js'\nimport { renameBoilerplate } from './steps/rename.js'\nimport { runPostInstall } from './steps/post-install.js'\nimport { writeProjectConfig } from './steps/write-config.js'\n\nexport async function runInit(): Promise<void> {\n intro('wpaikit init')\n\n const answers = await askInitQuestions()\n\n const { location, projectName, slug, namespace, textDomain, preset } = answers\n\n const cwd = process.cwd()\n const rollback = new Rollback()\n\n try {\n // 1. Resolve target directory\n const targetDir = createProjectDir(cwd, slug, location === 'current-dir', rollback)\n\n // 2. Download WordPress\n const s = spinner()\n s.start('Downloading WordPress...')\n try {\n await downloadWordPress(targetDir)\n s.stop('WordPress downloaded')\n } catch (err) {\n s.stop('Download failed')\n throw err\n }\n\n // 3. Clone boilerplate wp-content\n const s2 = spinner()\n s2.start('Cloning boilerplate...')\n try {\n await cloneBoilerplate(targetDir)\n s2.stop('Boilerplate cloned')\n } catch (err) {\n s2.stop('Clone failed')\n throw err\n }\n\n // 4. Rename theme + namespace + text domain\n renameBoilerplate(targetDir, projectName, slug, namespace, textDomain)\n\n // 5. Post-install (composer install, npm install, npm run build)\n await runPostInstall(targetDir, slug)\n\n // 6. Write .wpaikit.json\n writeProjectConfig(targetDir, { name: projectName, namespace, textDomain, preset })\n\n // Done — clear rollback stack (no need to clean up on success)\n rollback.clear()\n\n const isCurrentDir = location === 'current-dir'\n const relativePath = isCurrentDir ? '.' : slug\n\n const nextSteps: string[] = [\n `Theme: wp-content/themes/${slug}/`,\n `Namespace: ${namespace}\\\\\\\\Theme`,\n `Text domain: ${textDomain}`,\n '',\n 'Next steps:',\n ]\n\n let stepNum = 1\n if (!isCurrentDir) nextSteps.push(` ${stepNum++}. cd ${slug}`)\n nextSteps.push(` ${stepNum++}. Create a local database (Herd, MAMP, TablePlus, or CLI)`)\n nextSteps.push(` ${stepNum++}. Configure wp-config.php (DB_NAME, DB_USER, DB_PASSWORD, DB_HOST)`)\n nextSteps.push(` ${stepNum++}. Open the site in a browser and complete WordPress setup`)\n nextSteps.push(` ${stepNum}. Activate the \"${slug}\" theme in wp-admin`)\n\n note(nextSteps.join('\\n'), 'Project scaffolded')\n\n outro(`Done! Project \"${projectName}\" is ready.`)\n } catch (err) {\n if (rollback.size > 0) {\n logger.warn('Scaffolding failed — rolling back...')\n await rollback.run()\n }\n throw err\n }\n}\n","import { promptText, confirm, select } from '@veaceslav-golden/wp-ai-kit-core'\nimport { toSlug, toPascalCase } from './normalize-name.js'\n\nexport type LocationChoice = 'new-folder' | 'current-dir'\n\nexport interface InitAnswers {\n location: LocationChoice\n projectName: string\n slug: string\n namespace: string\n textDomain: string\n preset: string\n}\n\nexport async function askInitQuestions(): Promise<InitAnswers> {\n const location = await select<LocationChoice>({\n message: 'Where do you want to set up the project?',\n options: [\n { value: 'new-folder', label: 'Create a new folder', hint: 'recommended' },\n { value: 'current-dir', label: 'Use the current directory' },\n ],\n })\n\n const projectName = await promptText({\n message: 'Project name:',\n placeholder: 'my-wordpress-site',\n validate: (v) => {\n if (!v.trim()) return 'Project name is required'\n if (!/^[a-zA-Z0-9 _-]+$/.test(v.trim()))\n return 'Only letters, numbers, spaces, hyphens, and underscores are allowed'\n },\n })\n\n const defaultSlug = toSlug(projectName)\n const slug = await promptText({\n message: 'Slug (folder name / text domain):',\n placeholder: defaultSlug,\n defaultValue: defaultSlug,\n validate: (v) => {\n if (!v.trim()) return 'Slug is required'\n if (!/^[a-z0-9-]+$/.test(v.trim()))\n return 'Slug must be lowercase letters, numbers, and hyphens only'\n },\n })\n\n const defaultNamespace = toPascalCase(slug)\n const namespace = await promptText({\n message: 'PHP namespace (PascalCase):',\n placeholder: defaultNamespace,\n defaultValue: defaultNamespace,\n validate: (v) => {\n if (!v.trim()) return 'Namespace is required'\n if (!/^[A-Z][a-zA-Z0-9]*$/.test(v.trim()))\n return 'Namespace must start with uppercase letter, letters and numbers only'\n },\n })\n\n const textDomain = slug\n\n const preset = await select<string>({\n message: 'Preset:',\n options: [{ value: 'standard', label: 'Standard', hint: 'Timber + ACF + Vite + Tailwind' }],\n initialValue: 'standard',\n })\n\n const ready = await confirm(\n `Ready to scaffold \"${projectName}\"${location === 'new-folder' ? ` in ./${slug}/` : ' in the current directory'}?`,\n )\n\n if (!ready) {\n process.exit(0)\n }\n\n return {\n location,\n projectName,\n slug,\n namespace,\n textDomain,\n preset,\n }\n}\n","/**\n * Convert any string to a kebab-case slug (lowercase, hyphens only).\n * e.g. \"My Cool Site\" → \"my-cool-site\"\n */\nexport function toSlug(input: string): string {\n return input\n .trim()\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '')\n}\n\n/**\n * Convert a slug or any string to PascalCase namespace.\n * e.g. \"my-cool-site\" → \"MyCoolSite\"\n */\nexport function toPascalCase(input: string): string {\n return input\n .trim()\n .replace(/[^a-zA-Z0-9]+/g, ' ')\n .trim()\n .split(' ')\n .map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())\n .join('')\n}\n\n/**\n * Convert a slug or any string to a human-readable title.\n * e.g. \"my-cool-site\" → \"My Cool Site\"\n */\nexport function toTitle(input: string): string {\n return input\n .trim()\n .replace(/[^a-zA-Z0-9]+/g, ' ')\n .trim()\n .split(' ')\n .map((word) => word.charAt(0).toUpperCase() + word.slice(1))\n .join(' ')\n}\n","import { mkdirSync, existsSync, readdirSync } from 'node:fs'\nimport { resolve } from 'node:path'\nimport { logger } from '@veaceslav-golden/wp-ai-kit-core'\nimport type { Rollback } from '@veaceslav-golden/wp-ai-kit-core'\nimport { rmSync } from 'node:fs'\n\nexport function createProjectDir(\n cwd: string,\n slug: string,\n useCurrentDir: boolean,\n rollback: Rollback,\n): string {\n if (useCurrentDir) {\n const entries = readdirSync(cwd)\n const nonHidden = entries.filter((e) => !e.startsWith('.'))\n if (nonHidden.length > 0) {\n logger.warn(\n `Current directory is not empty (${nonHidden.length} item(s) found). Continuing anyway.`,\n )\n }\n logger.step('Using current directory')\n return cwd\n }\n\n const targetDir = resolve(cwd, slug)\n\n if (existsSync(targetDir)) {\n throw new Error(`Directory \"${slug}\" already exists in the current folder.`)\n }\n\n mkdirSync(targetDir, { recursive: true })\n rollback.add(() => {\n rmSync(targetDir, { recursive: true, force: true })\n logger.step(`Removed directory: ${slug}`)\n })\n\n logger.step(`Created directory: ${slug}/`)\n return targetDir\n}\n","import { createWriteStream, rmSync, existsSync } from 'node:fs'\nimport { resolve } from 'node:path'\nimport { pipeline } from 'node:stream/promises'\nimport { exec, logger } from '@veaceslav-golden/wp-ai-kit-core'\nimport { WP_VERSION_API, WP_DOWNLOAD_URL } from '../../../constants.js'\n\ninterface WpVersionResponse {\n offers: Array<{ version: string; response: string }>\n}\n\nasync function fetchLatestWpVersion(): Promise<string> {\n const res = await fetch(WP_VERSION_API)\n if (!res.ok) throw new Error(`Failed to fetch WP version info: ${res.status}`)\n const data = (await res.json()) as WpVersionResponse\n const latest = data.offers.find((o) => o.response === 'upgrade' || o.response === 'latest')\n if (!latest) throw new Error('Could not determine latest WordPress version')\n return latest.version\n}\n\nexport async function downloadWordPress(targetDir: string): Promise<void> {\n logger.step('Fetching latest WordPress version...')\n const version = await fetchLatestWpVersion()\n logger.step(`Downloading WordPress ${version}...`)\n\n const tarballUrl = WP_DOWNLOAD_URL(version)\n const tarballPath = resolve(targetDir, 'wordpress.tar.gz')\n\n const res = await fetch(tarballUrl)\n if (!res.ok) throw new Error(`Failed to download WordPress: ${res.status}`)\n if (!res.body) throw new Error('Response body is empty')\n\n const { Writable } = await import('node:stream')\n const fileStream = createWriteStream(tarballPath)\n const webStream = res.body\n\n // Convert web ReadableStream to Node.js readable\n const { Readable } = await import('node:stream')\n const nodeReadable = Readable.fromWeb(webStream as Parameters<typeof Readable.fromWeb>[0])\n await pipeline(nodeReadable, fileStream)\n\n logger.step(`Extracting WordPress ${version}...`)\n\n // --strip-components=1 extracts directly into targetDir (skips the \"wordpress/\" prefix)\n await exec('tar', ['-xzf', 'wordpress.tar.gz', '--strip-components=1'], { cwd: targetDir })\n\n // Cleanup tarball\n rmSync(tarballPath, { force: true })\n\n const wpContentDir = resolve(targetDir, 'wp-content')\n if (existsSync(wpContentDir)) {\n rmSync(wpContentDir, { recursive: true, force: true })\n }\n\n logger.step(`WordPress ${version} extracted`)\n}\n","export const BOILERPLATE_REPO = 'git@github.com:GoldenCodesSlava/boilerplate-wp-standard.git'\nexport const WP_VERSION_API = 'https://api.wordpress.org/core/version-check/1.7/'\nexport const WP_DOWNLOAD_URL = (version: string) =>\n `https://wordpress.org/wordpress-${version}.tar.gz`\n\nexport const BOILERPLATE_THEME_SLUG = 'boilerplate'\nexport const BOILERPLATE_NAMESPACE = 'Boilerplate'\nexport const BOILERPLATE_TEXT_DOMAIN = 'boilerplate'\nexport const BOILERPLATE_THEME_NAME = 'Boilerplate'\nexport const BOILERPLATE_COMPOSER_NAME = 'boilerplate/wordpress-theme'\n","import { resolve } from 'node:path'\nimport { existsSync, rmSync, renameSync } from 'node:fs'\nimport { exec, logger } from '@veaceslav-golden/wp-ai-kit-core'\nimport { BOILERPLATE_REPO } from '../../../constants.js'\n\nexport async function cloneBoilerplate(targetDir: string): Promise<void> {\n logger.step('Cloning boilerplate wp-content...')\n\n const tempDir = resolve(targetDir, '__boilerplate-tmp')\n\n try {\n await exec('git', ['clone', '--depth', '1', BOILERPLATE_REPO, tempDir], {\n env: {\n ...process.env,\n // BatchMode=yes: fail immediately instead of hanging on passphrase/host prompts\n // ConnectTimeout=10: don't wait forever if GitHub is unreachable\n // StrictHostKeyChecking=accept-new: auto-accept github.com host key on first connection\n GIT_SSH_COMMAND:\n 'ssh -o BatchMode=yes -o ConnectTimeout=10 -o StrictHostKeyChecking=accept-new',\n },\n })\n } catch (err) {\n rmSync(tempDir, { recursive: true, force: true })\n const msg = (err as Error).message\n if (msg.includes('Permission denied') || msg.includes('publickey')) {\n throw new Error(\n 'SSH authentication failed.\\n\\n' +\n 'Your SSH key is not loaded in the agent. Run:\\n' +\n ' macOS: ssh-add --apple-use-keychain ~/.ssh/id_ed25519\\n' +\n ' Linux: ssh-add ~/.ssh/id_ed25519\\n\\n' +\n 'Then verify with: ssh -T git@github.com',\n )\n }\n throw err\n }\n\n const clonedWpContent = resolve(tempDir, 'wp-content')\n if (!existsSync(clonedWpContent)) {\n rmSync(tempDir, { recursive: true, force: true })\n throw new Error('Boilerplate repository does not contain a wp-content directory')\n }\n\n const destWpContent = resolve(targetDir, 'wp-content')\n renameSync(clonedWpContent, destWpContent)\n\n rmSync(tempDir, { recursive: true, force: true })\n\n logger.step('Boilerplate wp-content cloned')\n}\n","import {\n readdirSync,\n statSync,\n readFileSync,\n writeFileSync,\n renameSync,\n existsSync,\n} from 'node:fs'\nimport { resolve, join } from 'node:path'\nimport { logger } from '@veaceslav-golden/wp-ai-kit-core'\nimport {\n BOILERPLATE_THEME_SLUG,\n BOILERPLATE_NAMESPACE,\n BOILERPLATE_TEXT_DOMAIN,\n BOILERPLATE_THEME_NAME,\n BOILERPLATE_COMPOSER_NAME,\n} from '../../../constants.js'\n\nconst TEXT_EXTENSIONS = new Set([\n '.php',\n '.json',\n '.css',\n '.js',\n '.ts',\n '.twig',\n '.html',\n '.txt',\n '.md',\n '.xml',\n '.neon',\n '.yml',\n '.yaml',\n])\n\nfunction replaceInFile(filePath: string, replacements: Array<[string, string]>): void {\n let content = readFileSync(filePath, 'utf-8')\n let changed = false\n for (const [from, to] of replacements) {\n const next = content.replaceAll(from, to)\n if (next !== content) {\n changed = true\n content = next\n }\n }\n if (changed) {\n writeFileSync(filePath, content, 'utf-8')\n }\n}\n\nfunction walkDir(dir: string, callback: (filePath: string) => void): void {\n for (const entry of readdirSync(dir)) {\n // Skip hidden dirs and vendor/node_modules\n if (entry.startsWith('.') || entry === 'vendor' || entry === 'node_modules') continue\n const fullPath = join(dir, entry)\n const stat = statSync(fullPath)\n if (stat.isDirectory()) {\n walkDir(fullPath, callback)\n } else {\n callback(fullPath)\n }\n }\n}\n\nexport function renameBoilerplate(\n targetDir: string,\n projectName: string,\n slug: string,\n namespace: string,\n textDomain: string,\n): void {\n logger.step('Renaming boilerplate identifiers...')\n\n const themesDir = resolve(targetDir, 'wp-content', 'themes')\n const boilerplateThemeDir = resolve(themesDir, BOILERPLATE_THEME_SLUG)\n const newThemeDir = resolve(themesDir, slug)\n\n if (!existsSync(boilerplateThemeDir)) {\n throw new Error(`Expected theme folder \"themes/${BOILERPLATE_THEME_SLUG}\" not found`)\n }\n\n // Replacements applied to file contents (order matters — most specific first)\n const replacements: Array<[string, string]> = [\n // PHP namespace: \"namespace Boilerplate\\\" → \"namespace {Namespace}\\\"\n [`namespace ${BOILERPLATE_NAMESPACE}\\\\`, `namespace ${namespace}\\\\`],\n // PHP use statements: \"use Boilerplate\\\" → \"use {Namespace}\\\"\n [`use ${BOILERPLATE_NAMESPACE}\\\\`, `use ${namespace}\\\\`],\n // Composer autoload PSR-4 key: \"Boilerplate\\\\\" → \"{Namespace}\\\\\"\n [`${BOILERPLATE_NAMESPACE}\\\\\\\\`, `${namespace}\\\\\\\\`],\n // composer.json name: \"boilerplate/wordpress-theme\" → \"{slug}/wordpress-theme\"\n [BOILERPLATE_COMPOSER_NAME, `${slug}/wordpress-theme`],\n // style.css Theme Name\n [`Theme Name: ${BOILERPLATE_THEME_NAME}`, `Theme Name: ${projectName}`],\n // Text domain in PHP: 'boilerplate' → '{textDomain}'\n [`'${BOILERPLATE_TEXT_DOMAIN}'`, `'${textDomain}'`],\n // Text domain in JSON/twig: \"boilerplate\" → \"{textDomain}\"\n [`\"${BOILERPLATE_TEXT_DOMAIN}\"`, `\"${textDomain}\"`],\n // ACF JSON field keys that reference the theme slug\n [BOILERPLATE_THEME_SLUG, slug],\n ]\n\n // Process all text files inside the theme folder\n walkDir(boilerplateThemeDir, (filePath) => {\n const ext = filePath.slice(filePath.lastIndexOf('.'))\n if (TEXT_EXTENSIONS.has(ext)) {\n replaceInFile(filePath, replacements)\n }\n })\n\n // Rename theme folder last\n renameSync(boilerplateThemeDir, newThemeDir)\n\n logger.step(`Theme renamed: themes/${BOILERPLATE_THEME_SLUG} → themes/${slug}`)\n}\n","import { existsSync } from 'node:fs'\nimport { resolve } from 'node:path'\nimport { exec, logger } from '@veaceslav-golden/wp-ai-kit-core'\n\nasync function runIfExists(\n cmd: string,\n args: string[],\n cwd: string,\n label: string,\n): Promise<void> {\n try {\n await exec(cmd, ['--version'], { cwd })\n } catch {\n logger.warn(`${label}: \"${cmd}\" not found, skipping`)\n return\n }\n\n logger.step(`Running ${label}...`)\n try {\n await exec(cmd, args, { cwd, verbose: false })\n logger.step(`${label} complete`)\n } catch (err) {\n logger.warn(`${label} failed: ${(err as Error).message}`)\n }\n}\n\nexport async function runPostInstall(targetDir: string, slug: string): Promise<void> {\n const themeDir = resolve(targetDir, 'wp-content', 'themes', slug)\n\n if (!existsSync(themeDir)) {\n logger.warn('Theme directory not found, skipping post-install')\n return\n }\n\n const hasComposer = existsSync(resolve(themeDir, 'composer.json'))\n const hasPackageJson = existsSync(resolve(themeDir, 'package.json'))\n\n if (hasComposer) {\n await runIfExists('composer', ['install', '--no-interaction'], themeDir, 'composer install')\n }\n\n if (hasPackageJson) {\n await runIfExists('npm', ['install'], themeDir, 'npm install')\n await runIfExists('npm', ['run', 'build'], themeDir, 'npm run build')\n }\n}\n","import { writeConfig, logger } from '@veaceslav-golden/wp-ai-kit-core'\nimport type { WpaikitConfig } from '@veaceslav-golden/wp-ai-kit-core'\n\nexport function writeProjectConfig(\n targetDir: string,\n data: Omit<WpaikitConfig, 'createdAt'>,\n): void {\n const config: WpaikitConfig = {\n ...data,\n createdAt: new Date().toISOString(),\n }\n writeConfig(config, targetDir)\n logger.step('Created .wpaikit.json')\n}\n","import { intro, outro, logger } from '@veaceslav-golden/wp-ai-kit-core'\nimport { runAllChecks } from './checks.js'\nimport type { CheckResult, CheckStatus } from './checks.js'\n\nconst ICONS: Record<CheckStatus, string> = {\n ok: '✓',\n warn: '⚠',\n error: '✗',\n}\n\nfunction formatResults(results: CheckResult[]): void {\n for (const r of results) {\n const icon = ICONS[r.status]\n const line = `${icon} ${r.name}: ${r.message}`\n if (r.status === 'ok') logger.success(line)\n else if (r.status === 'warn') logger.warn(line)\n else logger.error(line)\n\n if (r.fix && r.status !== 'ok') {\n for (const fixLine of r.fix.split('\\n')) {\n logger.message(` ${fixLine}`)\n }\n }\n }\n}\n\nfunction printJson(results: CheckResult[]): void {\n const hasError = results.some((r) => r.status === 'error')\n process.stdout.write(\n JSON.stringify(\n {\n ok: !hasError,\n checks: results.map(({ name, status, message, fix }) => ({\n name,\n status,\n message,\n ...(fix ? { fix } : {}),\n })),\n },\n null,\n 2,\n ) + '\\n',\n )\n}\n\nexport async function runDoctor(options: { json?: boolean }): Promise<void> {\n if (!options.json) intro('wpaikit doctor')\n\n const results = await runAllChecks(process.cwd())\n\n if (options.json) {\n printJson(results)\n return\n }\n\n formatResults(results)\n\n const errors = results.filter((r) => r.status === 'error')\n const warns = results.filter((r) => r.status === 'warn')\n\n if (errors.length > 0) {\n outro(`${errors.length} error(s) found — fix them before running wpaikit init`)\n process.exit(1)\n } else if (warns.length > 0) {\n outro(`All required checks passed (${warns.length} warning(s))`)\n } else {\n outro('All checks passed — ready to run wpaikit init')\n }\n}\n","import { exec } from '@veaceslav-golden/wp-ai-kit-core'\nimport { accessSync, constants } from 'node:fs'\n\nexport type CheckStatus = 'ok' | 'warn' | 'error'\n\nexport interface CheckResult {\n name: string\n status: CheckStatus\n message: string\n fix?: string\n}\n\nexport async function checkNode(): Promise<CheckResult> {\n const version = process.version\n const major = parseInt(version.slice(1))\n if (major < 18) {\n return {\n name: 'Node.js',\n status: 'error',\n message: `${version} (minimum: v18)`,\n fix: 'Update Node.js to v18 or later: https://nodejs.org',\n }\n }\n return { name: 'Node.js', status: 'ok', message: version }\n}\n\nexport async function checkGit(): Promise<CheckResult> {\n try {\n const { stdout } = await exec('git', ['--version'])\n const version = stdout.trim().replace('git version ', '')\n return { name: 'git', status: 'ok', message: version }\n } catch {\n return {\n name: 'git',\n status: 'error',\n message: 'not found',\n fix: 'Install git: https://git-scm.com',\n }\n }\n}\n\nexport async function checkComposer(): Promise<CheckResult> {\n try {\n const { stdout } = await exec('composer', ['--version', '--no-ansi'])\n const version = stdout.trim().split('\\n')[0] ?? 'unknown'\n return { name: 'composer', status: 'ok', message: version }\n } catch {\n return {\n name: 'composer',\n status: 'warn',\n message: 'not found (optional)',\n fix: 'Install Composer: https://getcomposer.org — needed for theme PHP dependencies',\n }\n }\n}\n\nexport async function checkNpm(): Promise<CheckResult> {\n try {\n const { stdout } = await exec('npm', ['--version'])\n return { name: 'npm', status: 'ok', message: `v${stdout.trim()}` }\n } catch {\n return {\n name: 'npm',\n status: 'warn',\n message: 'not found (optional)',\n fix: 'Install Node.js (includes npm): https://nodejs.org — needed for theme frontend build',\n }\n }\n}\n\nexport async function checkSsh(): Promise<CheckResult> {\n try {\n await exec('ssh', [\n '-T',\n 'git@github.com',\n '-o',\n 'BatchMode=yes',\n '-o',\n 'ConnectTimeout=5',\n '-o',\n 'StrictHostKeyChecking=accept-new',\n ])\n return { name: 'SSH → github.com', status: 'ok', message: 'authenticated' }\n } catch (err) {\n const msg = (err as Error).message ?? ''\n // Exit code 1 with \"successfully authenticated\" = success (GitHub returns 1 for non-shell access)\n if (msg.includes('successfully authenticated')) {\n return { name: 'SSH → github.com', status: 'ok', message: 'authenticated' }\n }\n if (msg.includes('Permission denied') || msg.includes('publickey')) {\n return {\n name: 'SSH → github.com',\n status: 'error',\n message: 'SSH key not loaded in agent',\n fix: 'macOS: ssh-add --apple-use-keychain ~/.ssh/id_ed25519\\nLinux: ssh-add ~/.ssh/id_ed25519',\n }\n }\n return {\n name: 'SSH → github.com',\n status: 'warn',\n message: 'could not connect (check your internet connection)',\n }\n }\n}\n\nexport function checkWriteAccess(cwd: string): CheckResult {\n try {\n accessSync(cwd, constants.W_OK)\n return { name: 'Write access', status: 'ok', message: cwd }\n } catch {\n return {\n name: 'Write access',\n status: 'error',\n message: `no write permission in ${cwd}`,\n fix: 'Run from a directory where you have write access',\n }\n }\n}\n\nexport async function runAllChecks(cwd: string): Promise<CheckResult[]> {\n const [node, git, composer, npm, ssh] = await Promise.all([\n checkNode(),\n checkGit(),\n checkComposer(),\n checkNpm(),\n checkSsh(),\n ])\n const write = checkWriteAccess(cwd)\n return [node, git, composer, npm, ssh, write]\n}\n","#!/usr/bin/env node\nimport { program } from './cli.js'\n\nprogram.parse()\n"],"mappings":"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","names":["logger","rmSync","existsSync","resolve","logger","version","logger","version","resolve","rmSync","existsSync","resolve","existsSync","rmSync","exec","logger","logger","resolve","exec","rmSync","existsSync","readdirSync","renameSync","existsSync","resolve","logger","readdirSync","logger","resolve","existsSync","renameSync","existsSync","resolve","exec","logger","logger","logger","intro","outro","logger","exec","version","logger","intro","outro","require"]}
package/package.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "@veaceslav-golden/wp-ai-kit",
3
+ "version": "0.1.0",
4
+ "description": "CLI for scaffolding and developing WordPress sites with custom boilerplates",
5
+ "type": "module",
6
+ "bin": {
7
+ "wpaikit": "./dist/index.js"
8
+ },
9
+ "files": [
10
+ "dist"
11
+ ],
12
+ "dependencies": {
13
+ "commander": "^12.0.0",
14
+ "@veaceslav-golden/wp-ai-kit-core": "0.1.0"
15
+ },
16
+ "publishConfig": {
17
+ "access": "public"
18
+ },
19
+ "scripts": {
20
+ "build": "tsup",
21
+ "dev": "tsup --watch",
22
+ "test": "vitest run"
23
+ }
24
+ }