wordpress-agent-kit 0.2.1 → 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/.github/workflows/ci.yml +44 -0
- package/.husky/pre-commit +7 -0
- package/AGENTS.md +33 -10
- package/AGENTS.template.md +63 -18
- package/CLI_REVIEW.md +250 -0
- package/README.md +240 -68
- package/biome.json +39 -0
- package/dist/cli.js +75 -4
- package/dist/commands/install.js +84 -10
- package/dist/commands/run-playground.js +59 -14
- package/dist/commands/setup.js +222 -163
- package/dist/commands/sync-skills.js +33 -60
- package/dist/commands/upgrade.js +211 -0
- package/dist/lib/api.js +511 -0
- package/dist/lib/installer.js +114 -6
- package/dist/lib/triage-mapper.js +18 -20
- package/dist/lib/updater.js +260 -0
- package/dist/utils/exit-codes.js +60 -0
- package/dist/utils/output.js +96 -0
- package/dist/utils/paths.js +1 -1
- package/dist/utils/run.js +1 -1
- package/extensions/wp-agent-kit/index.ts +630 -0
- package/package.json +27 -4
|
@@ -15,14 +15,14 @@ export function mapProjectType(primary) {
|
|
|
15
15
|
'wp-theme': 'theme',
|
|
16
16
|
'wp-site': 'site',
|
|
17
17
|
'wp-core': 'other',
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
gutenberg: 'blocks',
|
|
19
|
+
unknown: null,
|
|
20
20
|
};
|
|
21
21
|
return typeMap[primary] || null;
|
|
22
22
|
}
|
|
23
23
|
/**
|
|
24
24
|
* Maps triage signals and tooling to tech stack array
|
|
25
|
-
* @param {
|
|
25
|
+
* @param {TriageResult} triageResult - Full triage result object
|
|
26
26
|
* @returns {string[]} - Array of tech stack values
|
|
27
27
|
*/
|
|
28
28
|
export function mapTechStack(triageResult) {
|
|
@@ -60,39 +60,37 @@ export function mapTechStack(triageResult) {
|
|
|
60
60
|
/**
|
|
61
61
|
* Checks if detection has enough confidence to skip questions
|
|
62
62
|
* @param {string|null} detectedType - Detected project type
|
|
63
|
-
* @param {string[]} detectedTech - Detected tech stack
|
|
64
63
|
* @returns {boolean} - True if confident enough
|
|
65
64
|
*/
|
|
66
|
-
export function hasConfidentDetection(detectedType
|
|
65
|
+
export function hasConfidentDetection(detectedType) {
|
|
67
66
|
return detectedType !== null && detectedType !== 'other';
|
|
68
67
|
}
|
|
69
68
|
/**
|
|
70
69
|
* Formats detection results for display
|
|
71
70
|
* @param {string|null} detectedType - Detected project type
|
|
72
71
|
* @param {string[]} detectedTech - Detected tech stack
|
|
73
|
-
* @param {object} triageResult - Full triage result for additional notes
|
|
74
72
|
* @returns {string} - Formatted string for display
|
|
75
73
|
*/
|
|
76
|
-
export function formatDetectionResults(detectedType, detectedTech
|
|
74
|
+
export function formatDetectionResults(detectedType, detectedTech) {
|
|
77
75
|
const typeLabels = {
|
|
78
|
-
|
|
79
|
-
|
|
76
|
+
plugin: 'WordPress Plugin',
|
|
77
|
+
theme: 'WordPress Theme',
|
|
80
78
|
'block-theme': 'Block Theme',
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
79
|
+
site: 'Full Site / Multisite',
|
|
80
|
+
blocks: 'Gutenberg Blocks',
|
|
81
|
+
other: 'Other / Mixed',
|
|
84
82
|
};
|
|
85
83
|
const techLabels = {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
84
|
+
gutenberg: 'Blocks',
|
|
85
|
+
interactivity: 'Interactivity API',
|
|
86
|
+
wpcli: 'WP-CLI',
|
|
89
87
|
'rest-api': 'REST API',
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
88
|
+
composer: 'Composer',
|
|
89
|
+
phpstan: 'PHPStan',
|
|
90
|
+
npm: 'npm/package.json',
|
|
91
|
+
playground: 'Playground',
|
|
94
92
|
};
|
|
95
93
|
const typeLabel = detectedType ? typeLabels[detectedType] : 'Unknown';
|
|
96
|
-
const techList = detectedTech.map(t => techLabels[t] || t).join(', ');
|
|
94
|
+
const techList = detectedTech.map((t) => techLabels[t] || t).join(', ');
|
|
97
95
|
return `Project Type: ${typeLabel}\nTech Stack: ${techList}`;
|
|
98
96
|
}
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Semantic exit codes for the CLI.
|
|
3
|
+
* Allows scripts and agents to programmatically determine failure reasons.
|
|
4
|
+
*/
|
|
5
|
+
export const ExitCode = {
|
|
6
|
+
/** Success */
|
|
7
|
+
OK: 0,
|
|
8
|
+
/** General/unknown error */
|
|
9
|
+
ERROR: 1,
|
|
10
|
+
/** Invalid command-line arguments or usage */
|
|
11
|
+
INVALID_ARGS: 2,
|
|
12
|
+
/** Target not found (ENOENT) */
|
|
13
|
+
NOT_FOUND: 3,
|
|
14
|
+
/** Permission denied (EACCES) */
|
|
15
|
+
PERMISSION_DENIED: 4,
|
|
16
|
+
/** File/directory already exists (EEXIST) - use --force to override */
|
|
17
|
+
ALREADY_EXISTS: 5,
|
|
18
|
+
/** Git/submodule operation failed */
|
|
19
|
+
GIT_ERROR: 6,
|
|
20
|
+
/** Network/fetch operation failed */
|
|
21
|
+
NETWORK_ERROR: 7,
|
|
22
|
+
/** Validation failed (schema, input, config) */
|
|
23
|
+
VALIDATION_ERROR: 8,
|
|
24
|
+
/** Cancelled by user (SIGINT) */
|
|
25
|
+
CANCELLED: 130,
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Maps Node.js errno to semantic exit code.
|
|
29
|
+
*/
|
|
30
|
+
export function mapErrnoToExitCode(errno) {
|
|
31
|
+
switch (errno) {
|
|
32
|
+
case 'ENOENT':
|
|
33
|
+
return ExitCode.NOT_FOUND;
|
|
34
|
+
case 'EACCES':
|
|
35
|
+
case 'EPERM':
|
|
36
|
+
return ExitCode.PERMISSION_DENIED;
|
|
37
|
+
case 'EEXIST':
|
|
38
|
+
return ExitCode.ALREADY_EXISTS;
|
|
39
|
+
default:
|
|
40
|
+
return ExitCode.ERROR;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Wraps an async operation and maps errors to exit codes.
|
|
45
|
+
* Re-throws with exitCode property attached.
|
|
46
|
+
*/
|
|
47
|
+
export async function withExitCode(operation, onError) {
|
|
48
|
+
try {
|
|
49
|
+
return await operation();
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
const err = error;
|
|
53
|
+
const exitCode = err.exitCode ?? mapErrnoToExitCode(err.code);
|
|
54
|
+
const enhancedError = Object.assign(err, { exitCode });
|
|
55
|
+
if (onError) {
|
|
56
|
+
onError(enhancedError);
|
|
57
|
+
}
|
|
58
|
+
throw enhancedError;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { ExitCode } from './exit-codes.js';
|
|
2
|
+
/** Output formatter handles all CLI output modes */
|
|
3
|
+
export class OutputFormatter {
|
|
4
|
+
format;
|
|
5
|
+
startTime;
|
|
6
|
+
commandName;
|
|
7
|
+
version;
|
|
8
|
+
ndjsonStarted = false;
|
|
9
|
+
constructor(format, commandName, version) {
|
|
10
|
+
this.format = format;
|
|
11
|
+
this.startTime = Date.now();
|
|
12
|
+
this.commandName = commandName;
|
|
13
|
+
this.version = version;
|
|
14
|
+
}
|
|
15
|
+
/** Set output format at runtime */
|
|
16
|
+
setFormat(format) {
|
|
17
|
+
this.format = format;
|
|
18
|
+
}
|
|
19
|
+
/** Build standard result envelope */
|
|
20
|
+
buildResult(success, data, error) {
|
|
21
|
+
return {
|
|
22
|
+
success,
|
|
23
|
+
data,
|
|
24
|
+
error,
|
|
25
|
+
meta: {
|
|
26
|
+
durationMs: Date.now() - this.startTime,
|
|
27
|
+
timestamp: new Date().toISOString(),
|
|
28
|
+
version: this.version,
|
|
29
|
+
command: this.commandName,
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
/** Output success result */
|
|
34
|
+
success(data) {
|
|
35
|
+
const result = this.buildResult(true, data);
|
|
36
|
+
this.emit(result);
|
|
37
|
+
return result;
|
|
38
|
+
}
|
|
39
|
+
/** Output error result */
|
|
40
|
+
fail(error) {
|
|
41
|
+
const result = this.buildResult(false, undefined, error);
|
|
42
|
+
this.emit(result);
|
|
43
|
+
return result;
|
|
44
|
+
}
|
|
45
|
+
/** Emit NDJSON progress event */
|
|
46
|
+
progress(event) {
|
|
47
|
+
if (this.format !== 'ndjson')
|
|
48
|
+
return;
|
|
49
|
+
const fullEvent = {
|
|
50
|
+
...event,
|
|
51
|
+
timestamp: new Date().toISOString(),
|
|
52
|
+
};
|
|
53
|
+
console.log(JSON.stringify(fullEvent));
|
|
54
|
+
}
|
|
55
|
+
/** Start NDJSON stream */
|
|
56
|
+
startStream() {
|
|
57
|
+
if (this.format === 'ndjson') {
|
|
58
|
+
this.ndjsonStarted = true;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/** Emit raw object (used for JSON mode) */
|
|
62
|
+
emit(obj) {
|
|
63
|
+
switch (this.format) {
|
|
64
|
+
case 'json':
|
|
65
|
+
case 'ndjson':
|
|
66
|
+
console.log(JSON.stringify(obj));
|
|
67
|
+
break;
|
|
68
|
+
case 'human':
|
|
69
|
+
// Human mode: commands should handle their own console output
|
|
70
|
+
// This is a no-op for structured results
|
|
71
|
+
break;
|
|
72
|
+
case 'quiet':
|
|
73
|
+
// Suppress all output
|
|
74
|
+
break;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/** Get exit code from result */
|
|
78
|
+
static getExitCode(result) {
|
|
79
|
+
return result.success ? ExitCode.OK : (result.error?.exitCode ?? ExitCode.ERROR);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/** Parse output format from CLI flags */
|
|
83
|
+
export function parseOutputFormat(json, quiet, ndjson) {
|
|
84
|
+
if (json)
|
|
85
|
+
return 'json';
|
|
86
|
+
if (ndjson)
|
|
87
|
+
return 'ndjson';
|
|
88
|
+
if (quiet)
|
|
89
|
+
return 'quiet';
|
|
90
|
+
return 'human';
|
|
91
|
+
}
|
|
92
|
+
/** Create formatter from parsed options */
|
|
93
|
+
export function createFormatter(options, commandName, version) {
|
|
94
|
+
const format = parseOutputFormat(options.json, options.quiet, options.ndjson);
|
|
95
|
+
return new OutputFormatter(format, commandName, version);
|
|
96
|
+
}
|
package/dist/utils/paths.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { fileURLToPath } from 'node:url';
|
|
2
1
|
import path from 'node:path';
|
|
2
|
+
import { fileURLToPath } from 'node:url';
|
|
3
3
|
// Get the root directory of the package (where package.json lives)
|
|
4
4
|
// When running from src (ts-node), it's ../
|
|
5
5
|
// When running from dist (node), it's ../
|
package/dist/utils/run.js
CHANGED
|
@@ -11,7 +11,7 @@ export function run(command, args, cwd = process.cwd()) {
|
|
|
11
11
|
const result = spawnSync(command, args, {
|
|
12
12
|
cwd,
|
|
13
13
|
stdio: 'inherit',
|
|
14
|
-
shell: process.platform === 'win32'
|
|
14
|
+
shell: process.platform === 'win32',
|
|
15
15
|
});
|
|
16
16
|
if (result.status !== 0) {
|
|
17
17
|
console.error(`Command failed with status ${result.status}: ${command} ${args.join(' ')}`);
|