wordpress-agent-kit 0.2.2 → 0.3.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/.github/agents/wp-architect.agent.md +1 -0
- package/.github/skills/wordpress-router/SKILL.md +1 -0
- package/.github/skills/wp-abilities-api/SKILL.md +1 -0
- package/.github/skills/wp-block-development/SKILL.md +1 -0
- package/.github/skills/wp-block-themes/SKILL.md +1 -0
- package/.github/skills/wp-interactivity-api/SKILL.md +1 -0
- package/.github/skills/wp-performance/SKILL.md +1 -0
- package/.github/skills/wp-phpstan/SKILL.md +1 -0
- package/.github/skills/wp-playground/SKILL.md +1 -0
- package/.github/skills/wp-plugin-development/SKILL.md +1 -0
- package/.github/skills/wp-project-triage/SKILL.md +1 -0
- package/.github/skills/wp-rest-api/SKILL.md +1 -0
- package/.github/skills/wp-wpcli-and-ops/SKILL.md +1 -0
- package/.github/skills/wpds/SKILL.md +1 -0
- package/AGENTS.md +33 -10
- package/AGENTS.template.md +63 -18
- package/README.md +220 -121
- package/biome.json +1 -1
- package/dist/commands/install.js +47 -6
- package/dist/commands/upgrade.js +34 -5
- package/dist/lib/api.js +63 -8
- package/dist/lib/installer.js +114 -6
- package/dist/lib/updater.js +260 -0
- package/extensions/wp-agent-kit/index.ts +630 -0
- package/package.json +21 -3
- package/kit-learnings.md +0 -192
package/dist/lib/api.js
CHANGED
|
@@ -9,6 +9,7 @@ import { ExitCode, withExitCode } from '../utils/exit-codes.js';
|
|
|
9
9
|
import { OutputFormatter } from '../utils/output.js';
|
|
10
10
|
import { PACKAGE_ROOT } from '../utils/paths.js';
|
|
11
11
|
import { installKit } from './installer.js';
|
|
12
|
+
import { computeChanges, isKitInstalled } from './updater.js';
|
|
12
13
|
/**
|
|
13
14
|
* Install the WordPress Agent Kit programmatically.
|
|
14
15
|
*/
|
|
@@ -16,12 +17,12 @@ export async function installKitApi(options) {
|
|
|
16
17
|
const startTime = Date.now();
|
|
17
18
|
const formatter = new OutputFormatter('json', 'install', '0.0.0');
|
|
18
19
|
try {
|
|
19
|
-
const { targetDir, platform, force = false, dryRun = false } = options;
|
|
20
|
+
const { targetDir, platform, force = false, dryRun = false, safe = true, backup = true, } = options;
|
|
20
21
|
if (dryRun) {
|
|
21
|
-
return dryRunInstall(targetDir, platform, force);
|
|
22
|
+
return dryRunInstall(targetDir, platform, { force, safe, backup });
|
|
22
23
|
}
|
|
23
24
|
await withExitCode(async () => {
|
|
24
|
-
|
|
25
|
+
installKit(targetDir, platform, { force, safe, backup });
|
|
25
26
|
return { success: true };
|
|
26
27
|
});
|
|
27
28
|
const filesCreated = getInstalledFiles(targetDir, platform);
|
|
@@ -33,6 +34,8 @@ export async function installKitApi(options) {
|
|
|
33
34
|
filesSkipped: [],
|
|
34
35
|
errors: [],
|
|
35
36
|
durationMs,
|
|
37
|
+
isUpdate: isKitInstalled(targetDir, platform),
|
|
38
|
+
backupDir: null,
|
|
36
39
|
});
|
|
37
40
|
}
|
|
38
41
|
catch (error) {
|
|
@@ -48,15 +51,66 @@ export async function installKitApi(options) {
|
|
|
48
51
|
/**
|
|
49
52
|
* Dry-run preview for install.
|
|
50
53
|
*/
|
|
51
|
-
function dryRunInstall(targetDir, platform,
|
|
54
|
+
function dryRunInstall(targetDir, platform, options) {
|
|
55
|
+
const { force = false, safe = true } = options;
|
|
52
56
|
const platformFolder = getPlatformFolder(platform);
|
|
57
|
+
const formatter = new OutputFormatter('json', 'install', '0.0.0');
|
|
58
|
+
// Use change-computation for safe updates on existing installations
|
|
59
|
+
if (safe && isKitInstalled(targetDir, platform)) {
|
|
60
|
+
const changes = computeChanges(targetDir, platform, force);
|
|
61
|
+
const actions = changes.map((c) => {
|
|
62
|
+
const target = path.join(targetDir, platformFolder, c.relativePath);
|
|
63
|
+
return {
|
|
64
|
+
type: c.action === 'created' ? 'create' : c.action === 'updated' ? 'update' : 'skip',
|
|
65
|
+
target,
|
|
66
|
+
description: `${c.action}: ${c.relativePath}${c.reason ? ` (${c.reason})` : ''}`,
|
|
67
|
+
};
|
|
68
|
+
});
|
|
69
|
+
// AGENTS.md check
|
|
70
|
+
const targetAgents = path.join(targetDir, 'AGENTS.md');
|
|
71
|
+
if (!fs.existsSync(targetAgents)) {
|
|
72
|
+
actions.push({
|
|
73
|
+
type: 'create',
|
|
74
|
+
target: targetAgents,
|
|
75
|
+
description: 'Create AGENTS.md from template',
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
actions.push({
|
|
80
|
+
type: 'skip',
|
|
81
|
+
target: targetAgents,
|
|
82
|
+
description: 'AGENTS.md exists (preserved)',
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
return formatter.success({
|
|
86
|
+
wouldExecute: true,
|
|
87
|
+
actions,
|
|
88
|
+
summary: {
|
|
89
|
+
targetDir,
|
|
90
|
+
platform,
|
|
91
|
+
filesCreated: changes
|
|
92
|
+
.filter((c) => c.action === 'created')
|
|
93
|
+
.map((c) => path.join(platformFolder, c.relativePath)),
|
|
94
|
+
filesSkipped: changes
|
|
95
|
+
.filter((c) => c.action === 'skipped' || c.action === 'conflict')
|
|
96
|
+
.map((c) => `${path.join(platformFolder, c.relativePath)} (${c.reason})`),
|
|
97
|
+
errors: [],
|
|
98
|
+
durationMs: 0,
|
|
99
|
+
isUpdate: true,
|
|
100
|
+
backupDir: null,
|
|
101
|
+
conflicts: changes
|
|
102
|
+
.filter((c) => c.action === 'conflict')
|
|
103
|
+
.map((c) => path.join(platformFolder, c.relativePath)),
|
|
104
|
+
},
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
// Legacy dry-run for fresh installs
|
|
53
108
|
const actions = [];
|
|
54
109
|
const sourceGithub = path.join(PACKAGE_ROOT, '.github');
|
|
55
110
|
const targetPlatform = path.join(targetDir, platformFolder);
|
|
56
111
|
const templatePath = path.join(PACKAGE_ROOT, 'AGENTS.template.md');
|
|
57
112
|
const targetAgentsTemplate = path.join(targetDir, 'AGENTS.template.md');
|
|
58
113
|
const targetAgents = path.join(targetDir, 'AGENTS.md');
|
|
59
|
-
// Platform folder
|
|
60
114
|
if (fs.existsSync(sourceGithub)) {
|
|
61
115
|
if (fs.existsSync(targetPlatform) && !force) {
|
|
62
116
|
actions.push({
|
|
@@ -74,7 +128,6 @@ function dryRunInstall(targetDir, platform, force) {
|
|
|
74
128
|
});
|
|
75
129
|
}
|
|
76
130
|
}
|
|
77
|
-
// AGENTS.template.md
|
|
78
131
|
if (fs.existsSync(templatePath)) {
|
|
79
132
|
actions.push({
|
|
80
133
|
type: 'copy',
|
|
@@ -83,7 +136,6 @@ function dryRunInstall(targetDir, platform, force) {
|
|
|
83
136
|
description: 'Copy AGENTS.template.md',
|
|
84
137
|
});
|
|
85
138
|
}
|
|
86
|
-
// AGENTS.md
|
|
87
139
|
if (!fs.existsSync(targetAgents) || force) {
|
|
88
140
|
if (fs.existsSync(templatePath)) {
|
|
89
141
|
actions.push({
|
|
@@ -101,7 +153,7 @@ function dryRunInstall(targetDir, platform, force) {
|
|
|
101
153
|
description: 'AGENTS.md exists (use --force to overwrite)',
|
|
102
154
|
});
|
|
103
155
|
}
|
|
104
|
-
return
|
|
156
|
+
return formatter.success({
|
|
105
157
|
wouldExecute: true,
|
|
106
158
|
actions,
|
|
107
159
|
summary: {
|
|
@@ -111,6 +163,8 @@ function dryRunInstall(targetDir, platform, force) {
|
|
|
111
163
|
filesSkipped: actions.filter((a) => a.type === 'update').map((a) => a.target),
|
|
112
164
|
errors: [],
|
|
113
165
|
durationMs: 0,
|
|
166
|
+
isUpdate: false,
|
|
167
|
+
backupDir: null,
|
|
114
168
|
},
|
|
115
169
|
});
|
|
116
170
|
}
|
|
@@ -454,3 +508,4 @@ function getInstalledFiles(targetDir, platform) {
|
|
|
454
508
|
}
|
|
455
509
|
export { ExitCode } from '../utils/exit-codes.js';
|
|
456
510
|
export { OutputFormatter, createFormatter, parseOutputFormat } from '../utils/output.js';
|
|
511
|
+
export { computeChanges, isKitInstalled, loadManifest, updateKit } from './updater.js';
|
package/dist/lib/installer.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { PACKAGE_ROOT } from '../utils/paths.js';
|
|
4
|
+
import { updateKit } from './updater.js';
|
|
4
5
|
/**
|
|
5
6
|
* Platform-specific folder names
|
|
6
7
|
*/
|
|
@@ -13,13 +14,36 @@ export const PLATFORM_FOLDERS = {
|
|
|
13
14
|
};
|
|
14
15
|
/**
|
|
15
16
|
* Installs the WordPress Agent Kit into the specified directory for a given platform.
|
|
16
|
-
*
|
|
17
|
+
* If the kit is already installed, uses safe update logic to preserve user modifications.
|
|
17
18
|
*
|
|
18
|
-
* @param
|
|
19
|
-
* @param
|
|
20
|
-
* @returns
|
|
19
|
+
* @param targetDir - The directory where the kit should be installed.
|
|
20
|
+
* @param platform - The target platform (github, cursor, claude, agent, pi)
|
|
21
|
+
* @returns InstallKitResult with details of what was created/skipped
|
|
21
22
|
*/
|
|
22
|
-
export
|
|
23
|
+
export function installKit(targetDir, platform = 'github', options = {}) {
|
|
24
|
+
const { force = false, backup = true, safe = true } = options;
|
|
25
|
+
// Check if kit is already installed
|
|
26
|
+
const isInstalled = isKitAlreadyInstalled(targetDir, platform);
|
|
27
|
+
// Use safe update for existing installations
|
|
28
|
+
if (isInstalled && safe) {
|
|
29
|
+
return safeUpdateInstall(targetDir, platform, { force, backup });
|
|
30
|
+
}
|
|
31
|
+
// Fresh install or fallback to full replacement
|
|
32
|
+
return fullInstall(targetDir, platform, force);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Check if kit is already installed for a platform.
|
|
36
|
+
*/
|
|
37
|
+
function isKitAlreadyInstalled(targetDir, platform) {
|
|
38
|
+
const platformFolder = PLATFORM_FOLDERS[platform];
|
|
39
|
+
const targetPlatform = path.join(targetDir, platformFolder);
|
|
40
|
+
return fs.existsSync(targetPlatform);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Full install (fresh or force-replace).
|
|
44
|
+
* Only used when no existing installation is detected or safe=false.
|
|
45
|
+
*/
|
|
46
|
+
function fullInstall(targetDir, platform, _force) {
|
|
23
47
|
const platformFolder = PLATFORM_FOLDERS[platform];
|
|
24
48
|
console.log(`Installing WordPress Agent Kit (${platform}) into: ${targetDir}`);
|
|
25
49
|
if (!fs.existsSync(targetDir)) {
|
|
@@ -28,6 +52,8 @@ export async function installKit(targetDir, platform = 'github') {
|
|
|
28
52
|
const templatePath = path.join(PACKAGE_ROOT, 'AGENTS.template.md');
|
|
29
53
|
const agentsPath = path.join(PACKAGE_ROOT, 'AGENTS.md');
|
|
30
54
|
const sourceGithub = path.join(PACKAGE_ROOT, '.github');
|
|
55
|
+
const filesCreated = [];
|
|
56
|
+
const filesSkipped = [];
|
|
31
57
|
// Copy platform-specific folder
|
|
32
58
|
const targetPlatform = path.join(targetDir, platformFolder);
|
|
33
59
|
if (fs.existsSync(targetPlatform)) {
|
|
@@ -39,18 +65,100 @@ export async function installKit(targetDir, platform = 'github') {
|
|
|
39
65
|
else {
|
|
40
66
|
throw new Error('Could not find source .github directory.');
|
|
41
67
|
}
|
|
42
|
-
//
|
|
68
|
+
// Collect created files
|
|
69
|
+
const collectFiles = (dir, prefix) => {
|
|
70
|
+
if (!fs.existsSync(dir))
|
|
71
|
+
return;
|
|
72
|
+
const entries = fs.readdirSync(dir);
|
|
73
|
+
for (const entry of entries) {
|
|
74
|
+
const fullPath = path.join(dir, entry);
|
|
75
|
+
const stat = fs.statSync(fullPath);
|
|
76
|
+
if (stat.isDirectory()) {
|
|
77
|
+
collectFiles(fullPath, path.join(prefix, entry));
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
filesCreated.push(path.join(prefix, entry));
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
collectFiles(targetPlatform, platformFolder);
|
|
85
|
+
// Copy AGENTS.md template
|
|
43
86
|
const targetAgentsTemplate = path.join(targetDir, 'AGENTS.template.md');
|
|
44
87
|
if (fs.existsSync(templatePath)) {
|
|
45
88
|
fs.copyFileSync(templatePath, targetAgentsTemplate);
|
|
89
|
+
filesCreated.push('AGENTS.template.md');
|
|
46
90
|
}
|
|
91
|
+
// Copy AGENTS.md (only if it doesn't already exist)
|
|
47
92
|
const targetAgents = path.join(targetDir, 'AGENTS.md');
|
|
48
93
|
if (!fs.existsSync(targetAgents)) {
|
|
49
94
|
if (fs.existsSync(templatePath)) {
|
|
50
95
|
fs.copyFileSync(templatePath, targetAgents);
|
|
96
|
+
filesCreated.push('AGENTS.md');
|
|
51
97
|
}
|
|
52
98
|
else if (fs.existsSync(agentsPath)) {
|
|
53
99
|
fs.copyFileSync(agentsPath, targetAgents);
|
|
100
|
+
filesCreated.push('AGENTS.md');
|
|
54
101
|
}
|
|
55
102
|
}
|
|
103
|
+
else {
|
|
104
|
+
filesSkipped.push('AGENTS.md (already exists, preserved)');
|
|
105
|
+
}
|
|
106
|
+
return {
|
|
107
|
+
targetDir,
|
|
108
|
+
platform,
|
|
109
|
+
filesCreated,
|
|
110
|
+
filesSkipped,
|
|
111
|
+
errors: [],
|
|
112
|
+
isUpdate: false,
|
|
113
|
+
backupDir: null,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Safe update install using the updater module.
|
|
118
|
+
*/
|
|
119
|
+
function safeUpdateInstall(targetDir, platform, options) {
|
|
120
|
+
console.log(`Updating WordPress Agent Kit (${platform}) in: ${targetDir}`);
|
|
121
|
+
const updateOptions = {
|
|
122
|
+
targetDir,
|
|
123
|
+
platform,
|
|
124
|
+
force: options.force ?? false,
|
|
125
|
+
backup: options.backup ?? true,
|
|
126
|
+
};
|
|
127
|
+
const result = updateKit(updateOptions);
|
|
128
|
+
// Handle AGENTS.md separately (not part of the platform folder)
|
|
129
|
+
const filesSkipped = [];
|
|
130
|
+
const filesCreated = [...result.created];
|
|
131
|
+
const templatePath = path.join(PACKAGE_ROOT, 'AGENTS.template.md');
|
|
132
|
+
const targetAgentsTemplate = path.join(targetDir, 'AGENTS.template.md');
|
|
133
|
+
if (fs.existsSync(templatePath)) {
|
|
134
|
+
fs.copyFileSync(templatePath, targetAgentsTemplate);
|
|
135
|
+
// Don't add template to created list as it's always overwritten
|
|
136
|
+
}
|
|
137
|
+
const targetAgents = path.join(targetDir, 'AGENTS.md');
|
|
138
|
+
if (!fs.existsSync(targetAgents)) {
|
|
139
|
+
fs.copyFileSync(templatePath, targetAgents);
|
|
140
|
+
filesCreated.push('AGENTS.md (from template)');
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
filesSkipped.push('AGENTS.md (preserved)');
|
|
144
|
+
}
|
|
145
|
+
return {
|
|
146
|
+
targetDir,
|
|
147
|
+
platform,
|
|
148
|
+
filesCreated: [...result.created, ...result.updated].map((f) => path.join(PLATFORM_FOLDERS[platform], f)),
|
|
149
|
+
filesSkipped: [
|
|
150
|
+
...filesSkipped,
|
|
151
|
+
...result.skipped.map((f) => {
|
|
152
|
+
const filePath = path.join(PLATFORM_FOLDERS[platform], f);
|
|
153
|
+
return `${filePath} (skipped - not tracked or user modified)`;
|
|
154
|
+
}),
|
|
155
|
+
],
|
|
156
|
+
conflicts: result.conflicts.map((f) => {
|
|
157
|
+
const filePath = path.join(PLATFORM_FOLDERS[platform], f);
|
|
158
|
+
return `${filePath} (conflict - user modified, use --force to overwrite)`;
|
|
159
|
+
}),
|
|
160
|
+
errors: [],
|
|
161
|
+
isUpdate: true,
|
|
162
|
+
backupDir: result.backupDir,
|
|
163
|
+
};
|
|
56
164
|
}
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Safe update logic for WordPress Agent Kit installations.
|
|
3
|
+
* Tracks file origins and detects user modifications to avoid overwriting custom work.
|
|
4
|
+
*/
|
|
5
|
+
import crypto from 'node:crypto';
|
|
6
|
+
import fs from 'node:fs';
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
import { PACKAGE_ROOT } from '../utils/paths.js';
|
|
9
|
+
import { PLATFORM_FOLDERS } from './installer.js';
|
|
10
|
+
/** Get the platform folder for a target */
|
|
11
|
+
export function getPlatformTarget(targetDir, platform) {
|
|
12
|
+
const folder = PLATFORM_FOLDERS[platform];
|
|
13
|
+
return path.join(targetDir, folder);
|
|
14
|
+
}
|
|
15
|
+
/** Get the manifest file path */
|
|
16
|
+
function getManifestPath(targetDir, platform) {
|
|
17
|
+
return path.join(targetDir, `.wp-agent-kit-manifest.${platform}.json`);
|
|
18
|
+
}
|
|
19
|
+
/** Hash a file's content */
|
|
20
|
+
function hashFile(filePath) {
|
|
21
|
+
const content = fs.readFileSync(filePath);
|
|
22
|
+
return crypto.createHash('sha256').update(content).digest('hex');
|
|
23
|
+
}
|
|
24
|
+
/** Walk a directory recursively, returning relative paths */
|
|
25
|
+
function walkDir(dir) {
|
|
26
|
+
const result = [];
|
|
27
|
+
if (!fs.existsSync(dir))
|
|
28
|
+
return result;
|
|
29
|
+
const entries = fs.readdirSync(dir);
|
|
30
|
+
for (const entry of entries) {
|
|
31
|
+
const fullPath = path.join(dir, entry);
|
|
32
|
+
const stat = fs.statSync(fullPath);
|
|
33
|
+
if (stat.isDirectory()) {
|
|
34
|
+
const subPaths = walkDir(fullPath);
|
|
35
|
+
for (const sub of subPaths) {
|
|
36
|
+
result.push(path.join(entry, sub));
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
result.push(entry);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return result;
|
|
44
|
+
}
|
|
45
|
+
/** Load existing manifest if present */
|
|
46
|
+
export function loadManifest(targetDir, platform) {
|
|
47
|
+
const manifestPath = getManifestPath(targetDir, platform);
|
|
48
|
+
if (!fs.existsSync(manifestPath))
|
|
49
|
+
return null;
|
|
50
|
+
try {
|
|
51
|
+
return JSON.parse(fs.readFileSync(manifestPath, 'utf-8'));
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/** Save manifest */
|
|
58
|
+
function saveManifest(targetDir, platform, manifest) {
|
|
59
|
+
const manifestPath = getManifestPath(targetDir, platform);
|
|
60
|
+
fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2), 'utf-8');
|
|
61
|
+
}
|
|
62
|
+
/** Create a backup of target files before overwriting */
|
|
63
|
+
function createBackup(targetDir, platform, files) {
|
|
64
|
+
if (files.length === 0)
|
|
65
|
+
return null;
|
|
66
|
+
const platformFolder = PLATFORM_FOLDERS[platform];
|
|
67
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
68
|
+
const backupDir = path.join(targetDir, `.wp-agent-kit-backup-${timestamp}`);
|
|
69
|
+
for (const file of files) {
|
|
70
|
+
const srcPath = path.join(targetDir, platformFolder, file);
|
|
71
|
+
const destPath = path.join(backupDir, platformFolder, file);
|
|
72
|
+
if (fs.existsSync(srcPath)) {
|
|
73
|
+
fs.mkdirSync(path.dirname(destPath), { recursive: true });
|
|
74
|
+
fs.copyFileSync(srcPath, destPath);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return backupDir;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Compute file changes between current state and what would be installed.
|
|
81
|
+
*/
|
|
82
|
+
export function computeChanges(targetDir, platform, force) {
|
|
83
|
+
const existingManifest = loadManifest(targetDir, platform);
|
|
84
|
+
const sourceDir = path.join(PACKAGE_ROOT, '.github');
|
|
85
|
+
const targetPlatform = getPlatformTarget(targetDir, platform);
|
|
86
|
+
const changes = [];
|
|
87
|
+
const sourceFiles = walkDir(sourceDir);
|
|
88
|
+
const targetFiles = fs.existsSync(targetPlatform) ? walkDir(targetPlatform) : [];
|
|
89
|
+
// Build lookup of known files from manifest
|
|
90
|
+
const knownFiles = new Map();
|
|
91
|
+
if (existingManifest) {
|
|
92
|
+
for (const entry of existingManifest.files) {
|
|
93
|
+
knownFiles.set(entry.path, entry.hash);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
// Process source files
|
|
97
|
+
for (const sourceFile of sourceFiles) {
|
|
98
|
+
const targetPath = path.join(targetPlatform, sourceFile);
|
|
99
|
+
const sourceHash = hashFile(path.join(sourceDir, sourceFile));
|
|
100
|
+
if (!fs.existsSync(targetPath)) {
|
|
101
|
+
changes.push({
|
|
102
|
+
relativePath: sourceFile,
|
|
103
|
+
action: 'created',
|
|
104
|
+
reason: 'New file from kit',
|
|
105
|
+
});
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
const targetHash = hashFile(targetPath);
|
|
109
|
+
if (sourceHash === targetHash) {
|
|
110
|
+
// Identical - no change needed
|
|
111
|
+
changes.push({
|
|
112
|
+
relativePath: sourceFile,
|
|
113
|
+
action: 'unchanged',
|
|
114
|
+
reason: 'Content identical',
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
else if (knownFiles.has(sourceFile)) {
|
|
118
|
+
const manifestHash = knownFiles.get(sourceFile);
|
|
119
|
+
if (targetHash === manifestHash) {
|
|
120
|
+
// Same as original from manifest, safe to update
|
|
121
|
+
changes.push({
|
|
122
|
+
relativePath: sourceFile,
|
|
123
|
+
action: 'updated',
|
|
124
|
+
reason: 'Safe update (no user modification)',
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
else if (force) {
|
|
128
|
+
changes.push({
|
|
129
|
+
relativePath: sourceFile,
|
|
130
|
+
action: 'updated',
|
|
131
|
+
reason: 'Force update (overwriting user modification)',
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
changes.push({
|
|
136
|
+
relativePath: sourceFile,
|
|
137
|
+
action: 'conflict',
|
|
138
|
+
reason: 'User modified; skipped. Use --force to overwrite.',
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
// Not in manifest (pre-manifest install or manual add), but exists
|
|
144
|
+
if (force) {
|
|
145
|
+
changes.push({
|
|
146
|
+
relativePath: sourceFile,
|
|
147
|
+
action: 'updated',
|
|
148
|
+
reason: 'Force update (file not tracked in manifest)',
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
changes.push({
|
|
153
|
+
relativePath: sourceFile,
|
|
154
|
+
action: 'skipped',
|
|
155
|
+
reason: 'File exists but not tracked; skipped. Use --force to overwrite.',
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
// Note user-added files not in source (these are kept, not reported as changes)
|
|
161
|
+
const sourceSet = new Set(sourceFiles);
|
|
162
|
+
for (const targetFile of targetFiles) {
|
|
163
|
+
if (!sourceSet.has(targetFile)) {
|
|
164
|
+
changes.push({
|
|
165
|
+
relativePath: targetFile,
|
|
166
|
+
action: 'unchanged',
|
|
167
|
+
reason: 'User-added file (preserved)',
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return changes;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Perform a safe update of the WordPress Agent Kit in the target directory.
|
|
175
|
+
* Compares files against manifest and source to avoid overwriting user modifications.
|
|
176
|
+
*/
|
|
177
|
+
export function updateKit(options) {
|
|
178
|
+
const { targetDir, platform, force = false, backup = true } = options;
|
|
179
|
+
const changes = computeChanges(targetDir, platform, force);
|
|
180
|
+
const sourceDir = path.join(PACKAGE_ROOT, '.github');
|
|
181
|
+
const targetPlatform = getPlatformTarget(targetDir, platform);
|
|
182
|
+
const created = [];
|
|
183
|
+
const updated = [];
|
|
184
|
+
const skipped = [];
|
|
185
|
+
const conflicts = [];
|
|
186
|
+
// Determine which files will be overwritten (for backup)
|
|
187
|
+
const filesToBackup = changes.filter((c) => c.action === 'updated').map((c) => c.relativePath);
|
|
188
|
+
let backupDir = null;
|
|
189
|
+
if (backup && filesToBackup.length > 0) {
|
|
190
|
+
backupDir = createBackup(targetDir, platform, filesToBackup);
|
|
191
|
+
}
|
|
192
|
+
// Apply changes
|
|
193
|
+
for (const change of changes) {
|
|
194
|
+
const sourcePath = path.join(sourceDir, change.relativePath);
|
|
195
|
+
const targetPath = path.join(targetPlatform, change.relativePath);
|
|
196
|
+
switch (change.action) {
|
|
197
|
+
case 'created':
|
|
198
|
+
fs.mkdirSync(path.dirname(targetPath), { recursive: true });
|
|
199
|
+
fs.copyFileSync(sourcePath, targetPath);
|
|
200
|
+
created.push(change.relativePath);
|
|
201
|
+
break;
|
|
202
|
+
case 'updated':
|
|
203
|
+
fs.mkdirSync(path.dirname(targetPath), { recursive: true });
|
|
204
|
+
fs.copyFileSync(sourcePath, targetPath);
|
|
205
|
+
updated.push(change.relativePath);
|
|
206
|
+
break;
|
|
207
|
+
case 'skipped':
|
|
208
|
+
skipped.push(change.relativePath);
|
|
209
|
+
break;
|
|
210
|
+
case 'conflict':
|
|
211
|
+
conflicts.push(change.relativePath);
|
|
212
|
+
break;
|
|
213
|
+
// 'unchanged' - do nothing
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
// Build and save new manifest
|
|
217
|
+
const newManifest = {
|
|
218
|
+
version: getPackageVersion(),
|
|
219
|
+
platform,
|
|
220
|
+
installedAt: new Date().toISOString(),
|
|
221
|
+
files: walkDir(sourceDir).map((file) => ({
|
|
222
|
+
path: file,
|
|
223
|
+
hash: hashFile(path.join(sourceDir, file)),
|
|
224
|
+
})),
|
|
225
|
+
};
|
|
226
|
+
saveManifest(targetDir, platform, newManifest);
|
|
227
|
+
return {
|
|
228
|
+
targetDir,
|
|
229
|
+
platform,
|
|
230
|
+
changes,
|
|
231
|
+
created,
|
|
232
|
+
updated,
|
|
233
|
+
skipped,
|
|
234
|
+
conflicts,
|
|
235
|
+
backupDir,
|
|
236
|
+
manifestUpdated: true,
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
/** Get current package version */
|
|
240
|
+
function getPackageVersion() {
|
|
241
|
+
try {
|
|
242
|
+
const pkgPath = path.join(PACKAGE_ROOT, 'package.json');
|
|
243
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
244
|
+
return pkg.version;
|
|
245
|
+
}
|
|
246
|
+
catch {
|
|
247
|
+
return 'unknown';
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Check if a directory has a WordPress Agent Kit installation.
|
|
252
|
+
*/
|
|
253
|
+
export function isKitInstalled(targetDir, platform) {
|
|
254
|
+
const manifestPath = getManifestPath(targetDir, platform);
|
|
255
|
+
if (fs.existsSync(manifestPath))
|
|
256
|
+
return true;
|
|
257
|
+
const platformFolder = PLATFORM_FOLDERS[platform];
|
|
258
|
+
const targetPlatform = path.join(targetDir, platformFolder);
|
|
259
|
+
return fs.existsSync(targetPlatform);
|
|
260
|
+
}
|