@staff0rd/assist 0.19.0 → 0.20.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 +663 -239
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -91,12 +91,427 @@ function commit(message) {
|
|
|
91
91
|
}
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
+
// src/commands/complexity/cyclomatic.ts
|
|
95
|
+
import chalk2 from "chalk";
|
|
96
|
+
|
|
97
|
+
// src/commands/complexity/shared.ts
|
|
98
|
+
import fs from "fs";
|
|
99
|
+
import path from "path";
|
|
100
|
+
import chalk from "chalk";
|
|
101
|
+
import { minimatch } from "minimatch";
|
|
102
|
+
import ts from "typescript";
|
|
103
|
+
function getNodeName(node) {
|
|
104
|
+
if (ts.isFunctionDeclaration(node) || ts.isFunctionExpression(node)) {
|
|
105
|
+
return node.name?.text ?? "<anonymous>";
|
|
106
|
+
}
|
|
107
|
+
if (ts.isMethodDeclaration(node) || ts.isMethodSignature(node)) {
|
|
108
|
+
if (ts.isIdentifier(node.name)) {
|
|
109
|
+
return node.name.text;
|
|
110
|
+
}
|
|
111
|
+
if (ts.isStringLiteral(node.name)) {
|
|
112
|
+
return node.name.text;
|
|
113
|
+
}
|
|
114
|
+
return "<computed>";
|
|
115
|
+
}
|
|
116
|
+
if (ts.isArrowFunction(node)) {
|
|
117
|
+
const parent = node.parent;
|
|
118
|
+
if (ts.isVariableDeclaration(parent) && ts.isIdentifier(parent.name)) {
|
|
119
|
+
return parent.name.text;
|
|
120
|
+
}
|
|
121
|
+
if (ts.isPropertyAssignment(parent) && ts.isIdentifier(parent.name)) {
|
|
122
|
+
return parent.name.text;
|
|
123
|
+
}
|
|
124
|
+
return "<arrow>";
|
|
125
|
+
}
|
|
126
|
+
if (ts.isGetAccessor(node) || ts.isSetAccessor(node)) {
|
|
127
|
+
const prefix = ts.isGetAccessor(node) ? "get " : "set ";
|
|
128
|
+
if (ts.isIdentifier(node.name)) {
|
|
129
|
+
return `${prefix}${node.name.text}`;
|
|
130
|
+
}
|
|
131
|
+
return `${prefix}<computed>`;
|
|
132
|
+
}
|
|
133
|
+
if (ts.isConstructorDeclaration(node)) {
|
|
134
|
+
return "constructor";
|
|
135
|
+
}
|
|
136
|
+
return "<unknown>";
|
|
137
|
+
}
|
|
138
|
+
function createSourceFromFile(filePath) {
|
|
139
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
140
|
+
return ts.createSourceFile(
|
|
141
|
+
path.basename(filePath),
|
|
142
|
+
content,
|
|
143
|
+
ts.ScriptTarget.Latest,
|
|
144
|
+
true,
|
|
145
|
+
filePath.endsWith(".tsx") ? ts.ScriptKind.TSX : ts.ScriptKind.TS
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
function withSourceFiles(pattern2, callback) {
|
|
149
|
+
const files = findSourceFilesWithPattern(pattern2);
|
|
150
|
+
if (files.length === 0) {
|
|
151
|
+
console.log(chalk.yellow("No files found matching pattern"));
|
|
152
|
+
return void 0;
|
|
153
|
+
}
|
|
154
|
+
return callback(files);
|
|
155
|
+
}
|
|
156
|
+
function forEachFunction(files, callback) {
|
|
157
|
+
for (const file of files) {
|
|
158
|
+
const sourceFile = createSourceFromFile(file);
|
|
159
|
+
const visit = (node) => {
|
|
160
|
+
if (hasFunctionBody(node)) {
|
|
161
|
+
callback(file, getNodeName(node), node);
|
|
162
|
+
}
|
|
163
|
+
ts.forEachChild(node, visit);
|
|
164
|
+
};
|
|
165
|
+
visit(sourceFile);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
function findSourceFilesWithPattern(pattern2, baseDir = ".") {
|
|
169
|
+
const results = [];
|
|
170
|
+
const extensions = [".ts", ".tsx"];
|
|
171
|
+
function walk(dir) {
|
|
172
|
+
if (!fs.existsSync(dir)) {
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
176
|
+
for (const entry of entries) {
|
|
177
|
+
const fullPath = path.join(dir, entry.name);
|
|
178
|
+
if (entry.isDirectory()) {
|
|
179
|
+
if (entry.name !== "node_modules" && entry.name !== ".git") {
|
|
180
|
+
walk(fullPath);
|
|
181
|
+
}
|
|
182
|
+
} else if (entry.isFile() && extensions.some((ext) => entry.name.endsWith(ext))) {
|
|
183
|
+
results.push(fullPath);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
if (pattern2.includes("*")) {
|
|
188
|
+
walk(baseDir);
|
|
189
|
+
return results.filter((f) => minimatch(f, pattern2));
|
|
190
|
+
}
|
|
191
|
+
if (fs.existsSync(pattern2) && fs.statSync(pattern2).isFile()) {
|
|
192
|
+
return [pattern2];
|
|
193
|
+
}
|
|
194
|
+
if (fs.existsSync(pattern2) && fs.statSync(pattern2).isDirectory()) {
|
|
195
|
+
walk(pattern2);
|
|
196
|
+
return results;
|
|
197
|
+
}
|
|
198
|
+
walk(baseDir);
|
|
199
|
+
return results.filter((f) => minimatch(f, pattern2));
|
|
200
|
+
}
|
|
201
|
+
function hasFunctionBody(node) {
|
|
202
|
+
if (ts.isFunctionDeclaration(node) || ts.isFunctionExpression(node) || ts.isArrowFunction(node) || ts.isMethodDeclaration(node) || ts.isGetAccessor(node) || ts.isSetAccessor(node) || ts.isConstructorDeclaration(node)) {
|
|
203
|
+
return node.body !== void 0;
|
|
204
|
+
}
|
|
205
|
+
return false;
|
|
206
|
+
}
|
|
207
|
+
function countSloc(content) {
|
|
208
|
+
let inMultiLineComment = false;
|
|
209
|
+
let count = 0;
|
|
210
|
+
const lines = content.split("\n");
|
|
211
|
+
for (const line of lines) {
|
|
212
|
+
const trimmed = line.trim();
|
|
213
|
+
if (inMultiLineComment) {
|
|
214
|
+
if (trimmed.includes("*/")) {
|
|
215
|
+
inMultiLineComment = false;
|
|
216
|
+
const afterComment = trimmed.substring(trimmed.indexOf("*/") + 2);
|
|
217
|
+
if (afterComment.trim().length > 0) {
|
|
218
|
+
count++;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
if (trimmed.startsWith("//")) {
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
if (trimmed.startsWith("/*")) {
|
|
227
|
+
if (trimmed.includes("*/")) {
|
|
228
|
+
const afterComment = trimmed.substring(trimmed.indexOf("*/") + 2);
|
|
229
|
+
if (afterComment.trim().length > 0) {
|
|
230
|
+
count++;
|
|
231
|
+
}
|
|
232
|
+
} else {
|
|
233
|
+
inMultiLineComment = true;
|
|
234
|
+
}
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
if (trimmed.length > 0) {
|
|
238
|
+
count++;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
return count;
|
|
242
|
+
}
|
|
243
|
+
function calculateCyclomaticComplexity(node) {
|
|
244
|
+
let complexity = 1;
|
|
245
|
+
function visit(n) {
|
|
246
|
+
switch (n.kind) {
|
|
247
|
+
case ts.SyntaxKind.IfStatement:
|
|
248
|
+
case ts.SyntaxKind.ForStatement:
|
|
249
|
+
case ts.SyntaxKind.ForInStatement:
|
|
250
|
+
case ts.SyntaxKind.ForOfStatement:
|
|
251
|
+
case ts.SyntaxKind.WhileStatement:
|
|
252
|
+
case ts.SyntaxKind.DoStatement:
|
|
253
|
+
case ts.SyntaxKind.CaseClause:
|
|
254
|
+
case ts.SyntaxKind.CatchClause:
|
|
255
|
+
case ts.SyntaxKind.ConditionalExpression:
|
|
256
|
+
complexity++;
|
|
257
|
+
break;
|
|
258
|
+
case ts.SyntaxKind.BinaryExpression: {
|
|
259
|
+
const binary = n;
|
|
260
|
+
if (binary.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken || binary.operatorToken.kind === ts.SyntaxKind.BarBarToken || binary.operatorToken.kind === ts.SyntaxKind.QuestionQuestionToken) {
|
|
261
|
+
complexity++;
|
|
262
|
+
}
|
|
263
|
+
break;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
ts.forEachChild(n, visit);
|
|
267
|
+
}
|
|
268
|
+
ts.forEachChild(node, visit);
|
|
269
|
+
return complexity;
|
|
270
|
+
}
|
|
271
|
+
function calculateHalstead(node) {
|
|
272
|
+
const operators = /* @__PURE__ */ new Map();
|
|
273
|
+
const operands = /* @__PURE__ */ new Map();
|
|
274
|
+
function addOperator(op) {
|
|
275
|
+
operators.set(op, (operators.get(op) ?? 0) + 1);
|
|
276
|
+
}
|
|
277
|
+
function addOperand(op) {
|
|
278
|
+
operands.set(op, (operands.get(op) ?? 0) + 1);
|
|
279
|
+
}
|
|
280
|
+
function visit(n) {
|
|
281
|
+
if (ts.isIdentifier(n)) {
|
|
282
|
+
addOperand(n.text);
|
|
283
|
+
} else if (ts.isNumericLiteral(n)) {
|
|
284
|
+
addOperand(n.text);
|
|
285
|
+
} else if (ts.isStringLiteral(n)) {
|
|
286
|
+
addOperand(n.text);
|
|
287
|
+
} else if (ts.isBinaryExpression(n)) {
|
|
288
|
+
addOperator(n.operatorToken.getText());
|
|
289
|
+
} else if (ts.isPrefixUnaryExpression(n) || ts.isPostfixUnaryExpression(n)) {
|
|
290
|
+
addOperator(ts.tokenToString(n.operator) ?? "");
|
|
291
|
+
} else if (ts.isCallExpression(n)) {
|
|
292
|
+
addOperator("()");
|
|
293
|
+
} else if (ts.isPropertyAccessExpression(n)) {
|
|
294
|
+
addOperator(".");
|
|
295
|
+
} else if (ts.isElementAccessExpression(n)) {
|
|
296
|
+
addOperator("[]");
|
|
297
|
+
} else if (ts.isConditionalExpression(n)) {
|
|
298
|
+
addOperator("?:");
|
|
299
|
+
} else if (ts.isReturnStatement(n)) {
|
|
300
|
+
addOperator("return");
|
|
301
|
+
} else if (ts.isIfStatement(n)) {
|
|
302
|
+
addOperator("if");
|
|
303
|
+
} else if (ts.isForStatement(n) || ts.isForInStatement(n) || ts.isForOfStatement(n)) {
|
|
304
|
+
addOperator("for");
|
|
305
|
+
} else if (ts.isWhileStatement(n)) {
|
|
306
|
+
addOperator("while");
|
|
307
|
+
} else if (ts.isDoStatement(n)) {
|
|
308
|
+
addOperator("do");
|
|
309
|
+
} else if (ts.isSwitchStatement(n)) {
|
|
310
|
+
addOperator("switch");
|
|
311
|
+
} else if (ts.isCaseClause(n)) {
|
|
312
|
+
addOperator("case");
|
|
313
|
+
} else if (ts.isDefaultClause(n)) {
|
|
314
|
+
addOperator("default");
|
|
315
|
+
} else if (ts.isBreakStatement(n)) {
|
|
316
|
+
addOperator("break");
|
|
317
|
+
} else if (ts.isContinueStatement(n)) {
|
|
318
|
+
addOperator("continue");
|
|
319
|
+
} else if (ts.isThrowStatement(n)) {
|
|
320
|
+
addOperator("throw");
|
|
321
|
+
} else if (ts.isTryStatement(n)) {
|
|
322
|
+
addOperator("try");
|
|
323
|
+
} else if (ts.isCatchClause(n)) {
|
|
324
|
+
addOperator("catch");
|
|
325
|
+
} else if (ts.isNewExpression(n)) {
|
|
326
|
+
addOperator("new");
|
|
327
|
+
} else if (ts.isTypeOfExpression(n)) {
|
|
328
|
+
addOperator("typeof");
|
|
329
|
+
} else if (ts.isAwaitExpression(n)) {
|
|
330
|
+
addOperator("await");
|
|
331
|
+
}
|
|
332
|
+
ts.forEachChild(n, visit);
|
|
333
|
+
}
|
|
334
|
+
ts.forEachChild(node, visit);
|
|
335
|
+
const n1 = operators.size;
|
|
336
|
+
const n2 = operands.size;
|
|
337
|
+
const N1 = Array.from(operators.values()).reduce((a, b) => a + b, 0);
|
|
338
|
+
const N2 = Array.from(operands.values()).reduce((a, b) => a + b, 0);
|
|
339
|
+
const vocabulary = n1 + n2;
|
|
340
|
+
const length = N1 + N2;
|
|
341
|
+
const volume = length > 0 && vocabulary > 0 ? length * Math.log2(vocabulary) : 0;
|
|
342
|
+
const difficulty = n2 > 0 ? n1 / 2 * (N2 / n2) : 0;
|
|
343
|
+
const effort = volume * difficulty;
|
|
344
|
+
const time = effort / 18;
|
|
345
|
+
const bugsDelivered = volume / 3e3;
|
|
346
|
+
return {
|
|
347
|
+
length,
|
|
348
|
+
vocabulary,
|
|
349
|
+
volume,
|
|
350
|
+
difficulty,
|
|
351
|
+
effort,
|
|
352
|
+
time,
|
|
353
|
+
bugsDelivered
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// src/commands/complexity/cyclomatic.ts
|
|
358
|
+
async function cyclomatic(pattern2 = "**/*.ts", options = {}) {
|
|
359
|
+
withSourceFiles(pattern2, (files) => {
|
|
360
|
+
const results = [];
|
|
361
|
+
let hasViolation = false;
|
|
362
|
+
forEachFunction(files, (file, name, node) => {
|
|
363
|
+
const complexity = calculateCyclomaticComplexity(node);
|
|
364
|
+
results.push({ file, name, complexity });
|
|
365
|
+
if (options.threshold !== void 0 && complexity > options.threshold) {
|
|
366
|
+
hasViolation = true;
|
|
367
|
+
}
|
|
368
|
+
});
|
|
369
|
+
results.sort((a, b) => b.complexity - a.complexity);
|
|
370
|
+
for (const { file, name, complexity } of results) {
|
|
371
|
+
const exceedsThreshold = options.threshold !== void 0 && complexity > options.threshold;
|
|
372
|
+
const color = exceedsThreshold ? chalk2.red : chalk2.white;
|
|
373
|
+
console.log(`${color(`${file}:${name}`)} \u2192 ${chalk2.cyan(complexity)}`);
|
|
374
|
+
}
|
|
375
|
+
console.log(
|
|
376
|
+
chalk2.dim(
|
|
377
|
+
`
|
|
378
|
+
Analyzed ${results.length} functions across ${files.length} files`
|
|
379
|
+
)
|
|
380
|
+
);
|
|
381
|
+
if (hasViolation) {
|
|
382
|
+
process.exit(1);
|
|
383
|
+
}
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// src/commands/complexity/halstead.ts
|
|
388
|
+
import chalk3 from "chalk";
|
|
389
|
+
async function halstead(pattern2 = "**/*.ts", options = {}) {
|
|
390
|
+
withSourceFiles(pattern2, (files) => {
|
|
391
|
+
const results = [];
|
|
392
|
+
let hasViolation = false;
|
|
393
|
+
forEachFunction(files, (file, name, node) => {
|
|
394
|
+
const metrics = calculateHalstead(node);
|
|
395
|
+
results.push({ file, name, metrics });
|
|
396
|
+
if (options.threshold !== void 0 && metrics.volume > options.threshold) {
|
|
397
|
+
hasViolation = true;
|
|
398
|
+
}
|
|
399
|
+
});
|
|
400
|
+
results.sort((a, b) => b.metrics.effort - a.metrics.effort);
|
|
401
|
+
for (const { file, name, metrics } of results) {
|
|
402
|
+
const exceedsThreshold = options.threshold !== void 0 && metrics.volume > options.threshold;
|
|
403
|
+
const color = exceedsThreshold ? chalk3.red : chalk3.white;
|
|
404
|
+
console.log(
|
|
405
|
+
`${color(`${file}:${name}`)} \u2192 volume: ${chalk3.cyan(metrics.volume.toFixed(1))}, difficulty: ${chalk3.yellow(metrics.difficulty.toFixed(1))}, effort: ${chalk3.magenta(metrics.effort.toFixed(1))}`
|
|
406
|
+
);
|
|
407
|
+
}
|
|
408
|
+
console.log(
|
|
409
|
+
chalk3.dim(
|
|
410
|
+
`
|
|
411
|
+
Analyzed ${results.length} functions across ${files.length} files`
|
|
412
|
+
)
|
|
413
|
+
);
|
|
414
|
+
if (hasViolation) {
|
|
415
|
+
process.exit(1);
|
|
416
|
+
}
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// src/commands/complexity/maintainability.ts
|
|
421
|
+
import fs2 from "fs";
|
|
422
|
+
import chalk4 from "chalk";
|
|
423
|
+
function calculateMaintainabilityIndex(halsteadVolume, cyclomaticComplexity, sloc2) {
|
|
424
|
+
if (halsteadVolume === 0 || sloc2 === 0) {
|
|
425
|
+
return 100;
|
|
426
|
+
}
|
|
427
|
+
const mi = 171 - 5.2 * Math.log(halsteadVolume) - 0.23 * cyclomaticComplexity - 16.2 * Math.log(sloc2);
|
|
428
|
+
return Math.max(0, Math.min(100, mi));
|
|
429
|
+
}
|
|
430
|
+
async function maintainability(pattern2 = "**/*.ts", options = {}) {
|
|
431
|
+
withSourceFiles(pattern2, (files) => {
|
|
432
|
+
const fileMetrics = /* @__PURE__ */ new Map();
|
|
433
|
+
for (const file of files) {
|
|
434
|
+
const content = fs2.readFileSync(file, "utf-8");
|
|
435
|
+
fileMetrics.set(file, { sloc: countSloc(content), functions: [] });
|
|
436
|
+
}
|
|
437
|
+
forEachFunction(files, (file, _name, node) => {
|
|
438
|
+
const metrics = fileMetrics.get(file);
|
|
439
|
+
if (metrics) {
|
|
440
|
+
const complexity = calculateCyclomaticComplexity(node);
|
|
441
|
+
const halstead2 = calculateHalstead(node);
|
|
442
|
+
const mi = calculateMaintainabilityIndex(
|
|
443
|
+
halstead2.volume,
|
|
444
|
+
complexity,
|
|
445
|
+
metrics.sloc
|
|
446
|
+
);
|
|
447
|
+
metrics.functions.push(mi);
|
|
448
|
+
}
|
|
449
|
+
});
|
|
450
|
+
const results = [];
|
|
451
|
+
let hasViolation = false;
|
|
452
|
+
for (const [file, metrics] of fileMetrics) {
|
|
453
|
+
if (metrics.functions.length === 0) continue;
|
|
454
|
+
const avgMaintainability = metrics.functions.reduce((a, b) => a + b, 0) / metrics.functions.length;
|
|
455
|
+
const minMaintainability = Math.min(...metrics.functions);
|
|
456
|
+
results.push({ file, avgMaintainability, minMaintainability });
|
|
457
|
+
if (options.threshold !== void 0 && minMaintainability < options.threshold) {
|
|
458
|
+
hasViolation = true;
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
results.sort((a, b) => a.minMaintainability - b.minMaintainability);
|
|
462
|
+
for (const { file, avgMaintainability, minMaintainability } of results) {
|
|
463
|
+
const exceedsThreshold = options.threshold !== void 0 && minMaintainability < options.threshold;
|
|
464
|
+
const color = exceedsThreshold ? chalk4.red : chalk4.white;
|
|
465
|
+
console.log(
|
|
466
|
+
`${color(file)} \u2192 avg: ${chalk4.cyan(avgMaintainability.toFixed(1))}, min: ${chalk4.yellow(minMaintainability.toFixed(1))}`
|
|
467
|
+
);
|
|
468
|
+
}
|
|
469
|
+
console.log(chalk4.dim(`
|
|
470
|
+
Analyzed ${results.length} files`));
|
|
471
|
+
if (hasViolation) {
|
|
472
|
+
process.exit(1);
|
|
473
|
+
}
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// src/commands/complexity/sloc.ts
|
|
478
|
+
import fs3 from "fs";
|
|
479
|
+
import chalk5 from "chalk";
|
|
480
|
+
async function sloc(pattern2 = "**/*.ts", options = {}) {
|
|
481
|
+
withSourceFiles(pattern2, (files) => {
|
|
482
|
+
const results = [];
|
|
483
|
+
let hasViolation = false;
|
|
484
|
+
for (const file of files) {
|
|
485
|
+
const content = fs3.readFileSync(file, "utf-8");
|
|
486
|
+
const lines = countSloc(content);
|
|
487
|
+
results.push({ file, lines });
|
|
488
|
+
if (options.threshold !== void 0 && lines > options.threshold) {
|
|
489
|
+
hasViolation = true;
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
results.sort((a, b) => b.lines - a.lines);
|
|
493
|
+
for (const { file, lines } of results) {
|
|
494
|
+
const exceedsThreshold = options.threshold !== void 0 && lines > options.threshold;
|
|
495
|
+
const color = exceedsThreshold ? chalk5.red : chalk5.white;
|
|
496
|
+
console.log(`${color(file)} \u2192 ${chalk5.cyan(lines)} lines`);
|
|
497
|
+
}
|
|
498
|
+
const total = results.reduce((sum, r) => sum + r.lines, 0);
|
|
499
|
+
console.log(
|
|
500
|
+
chalk5.dim(`
|
|
501
|
+
Total: ${total} lines across ${files.length} files`)
|
|
502
|
+
);
|
|
503
|
+
if (hasViolation) {
|
|
504
|
+
process.exit(1);
|
|
505
|
+
}
|
|
506
|
+
});
|
|
507
|
+
}
|
|
508
|
+
|
|
94
509
|
// src/commands/deploy/init.ts
|
|
95
510
|
import { execSync as execSync2 } from "child_process";
|
|
96
511
|
import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
97
512
|
import { dirname, join as join2 } from "path";
|
|
98
513
|
import { fileURLToPath } from "url";
|
|
99
|
-
import
|
|
514
|
+
import chalk7 from "chalk";
|
|
100
515
|
import enquirer2 from "enquirer";
|
|
101
516
|
|
|
102
517
|
// src/shared/promptConfirm.ts
|
|
@@ -117,7 +532,7 @@ async function promptConfirm(message, initial = true) {
|
|
|
117
532
|
}
|
|
118
533
|
|
|
119
534
|
// src/utils/printDiff.ts
|
|
120
|
-
import
|
|
535
|
+
import chalk6 from "chalk";
|
|
121
536
|
import * as diff from "diff";
|
|
122
537
|
function normalizeJson(content) {
|
|
123
538
|
try {
|
|
@@ -135,11 +550,11 @@ function printDiff(oldContent, newContent) {
|
|
|
135
550
|
const lines = change.value.replace(/\n$/, "").split("\n");
|
|
136
551
|
for (const line of lines) {
|
|
137
552
|
if (change.added) {
|
|
138
|
-
console.log(
|
|
553
|
+
console.log(chalk6.green(`+ ${line}`));
|
|
139
554
|
} else if (change.removed) {
|
|
140
|
-
console.log(
|
|
555
|
+
console.log(chalk6.red(`- ${line}`));
|
|
141
556
|
} else {
|
|
142
|
-
console.log(
|
|
557
|
+
console.log(chalk6.dim(` ${line}`));
|
|
143
558
|
}
|
|
144
559
|
}
|
|
145
560
|
}
|
|
@@ -170,27 +585,27 @@ async function updateWorkflow(siteId) {
|
|
|
170
585
|
if (existsSync2(WORKFLOW_PATH)) {
|
|
171
586
|
const oldContent = readFileSync2(WORKFLOW_PATH, "utf-8");
|
|
172
587
|
if (oldContent === newContent) {
|
|
173
|
-
console.log(
|
|
588
|
+
console.log(chalk7.green("build.yml is already up to date"));
|
|
174
589
|
return;
|
|
175
590
|
}
|
|
176
|
-
console.log(
|
|
591
|
+
console.log(chalk7.yellow("\nbuild.yml will be updated:"));
|
|
177
592
|
console.log();
|
|
178
593
|
printDiff(oldContent, newContent);
|
|
179
|
-
const confirm = await promptConfirm(
|
|
594
|
+
const confirm = await promptConfirm(chalk7.red("Update build.yml?"));
|
|
180
595
|
if (!confirm) {
|
|
181
596
|
console.log("Skipped build.yml update");
|
|
182
597
|
return;
|
|
183
598
|
}
|
|
184
599
|
}
|
|
185
600
|
writeFileSync2(WORKFLOW_PATH, newContent);
|
|
186
|
-
console.log(
|
|
601
|
+
console.log(chalk7.green(`
|
|
187
602
|
Created ${WORKFLOW_PATH}`));
|
|
188
603
|
}
|
|
189
604
|
async function init() {
|
|
190
|
-
console.log(
|
|
605
|
+
console.log(chalk7.bold("Initializing Netlify deployment...\n"));
|
|
191
606
|
const existingSiteId = getExistingSiteId();
|
|
192
607
|
if (existingSiteId) {
|
|
193
|
-
console.log(
|
|
608
|
+
console.log(chalk7.dim(`Using existing site ID: ${existingSiteId}
|
|
194
609
|
`));
|
|
195
610
|
await updateWorkflow(existingSiteId);
|
|
196
611
|
return;
|
|
@@ -202,10 +617,10 @@ async function init() {
|
|
|
202
617
|
});
|
|
203
618
|
} catch (error) {
|
|
204
619
|
if (error instanceof Error && error.message.includes("command not found")) {
|
|
205
|
-
console.error(
|
|
620
|
+
console.error(chalk7.red("\nNetlify CLI is not installed.\n"));
|
|
206
621
|
const install = await promptConfirm("Would you like to install it now?");
|
|
207
622
|
if (install) {
|
|
208
|
-
console.log(
|
|
623
|
+
console.log(chalk7.dim("\nInstalling netlify-cli...\n"));
|
|
209
624
|
execSync2("npm install -g netlify-cli", { stdio: "inherit" });
|
|
210
625
|
console.log();
|
|
211
626
|
execSync2("netlify sites:create --disable-linking", {
|
|
@@ -213,7 +628,7 @@ async function init() {
|
|
|
213
628
|
});
|
|
214
629
|
} else {
|
|
215
630
|
console.log(
|
|
216
|
-
|
|
631
|
+
chalk7.yellow(
|
|
217
632
|
"\nInstall it manually with: npm install -g netlify-cli\n"
|
|
218
633
|
)
|
|
219
634
|
);
|
|
@@ -230,17 +645,17 @@ async function init() {
|
|
|
230
645
|
validate: (value) => /^[a-f0-9-]+$/i.test(value) || "Invalid site ID format"
|
|
231
646
|
});
|
|
232
647
|
await updateWorkflow(siteId);
|
|
233
|
-
console.log(
|
|
648
|
+
console.log(chalk7.bold("\nDeployment initialized successfully!"));
|
|
234
649
|
console.log(
|
|
235
|
-
|
|
650
|
+
chalk7.yellow("\nTo complete setup, create a personal access token at:")
|
|
236
651
|
);
|
|
237
652
|
console.log(
|
|
238
|
-
|
|
653
|
+
chalk7.cyan(
|
|
239
654
|
"https://app.netlify.com/user/applications#personal-access-tokens"
|
|
240
655
|
)
|
|
241
656
|
);
|
|
242
657
|
console.log(
|
|
243
|
-
|
|
658
|
+
chalk7.yellow(
|
|
244
659
|
"\nThen add it as NETLIFY_AUTH_TOKEN in your GitHub repository secrets."
|
|
245
660
|
)
|
|
246
661
|
);
|
|
@@ -248,7 +663,7 @@ async function init() {
|
|
|
248
663
|
|
|
249
664
|
// src/commands/deploy/redirect.ts
|
|
250
665
|
import { existsSync as existsSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
251
|
-
import
|
|
666
|
+
import chalk8 from "chalk";
|
|
252
667
|
var TRAILING_SLASH_SCRIPT = ` <script>
|
|
253
668
|
if (!window.location.pathname.endsWith('/')) {
|
|
254
669
|
window.location.href = \`\${window.location.pathname}/\${window.location.search}\${window.location.hash}\`;
|
|
@@ -257,32 +672,32 @@ var TRAILING_SLASH_SCRIPT = ` <script>
|
|
|
257
672
|
function redirect() {
|
|
258
673
|
const indexPath = "index.html";
|
|
259
674
|
if (!existsSync3(indexPath)) {
|
|
260
|
-
console.log(
|
|
675
|
+
console.log(chalk8.yellow("No index.html found"));
|
|
261
676
|
return;
|
|
262
677
|
}
|
|
263
678
|
const content = readFileSync3(indexPath, "utf-8");
|
|
264
679
|
if (content.includes("window.location.pathname.endsWith('/')")) {
|
|
265
|
-
console.log(
|
|
680
|
+
console.log(chalk8.dim("Trailing slash script already present"));
|
|
266
681
|
return;
|
|
267
682
|
}
|
|
268
683
|
const headCloseIndex = content.indexOf("</head>");
|
|
269
684
|
if (headCloseIndex === -1) {
|
|
270
|
-
console.log(
|
|
685
|
+
console.log(chalk8.red("Could not find </head> tag in index.html"));
|
|
271
686
|
return;
|
|
272
687
|
}
|
|
273
688
|
const newContent = content.slice(0, headCloseIndex) + TRAILING_SLASH_SCRIPT + "\n " + content.slice(headCloseIndex);
|
|
274
689
|
writeFileSync3(indexPath, newContent);
|
|
275
|
-
console.log(
|
|
690
|
+
console.log(chalk8.green("Added trailing slash redirect to index.html"));
|
|
276
691
|
}
|
|
277
692
|
|
|
278
693
|
// src/commands/devlog/list.ts
|
|
279
694
|
import { execSync as execSync4 } from "child_process";
|
|
280
695
|
import { basename as basename2 } from "path";
|
|
281
|
-
import
|
|
696
|
+
import chalk10 from "chalk";
|
|
282
697
|
|
|
283
698
|
// src/commands/devlog/shared.ts
|
|
284
699
|
import { execSync as execSync3 } from "child_process";
|
|
285
|
-
import
|
|
700
|
+
import chalk9 from "chalk";
|
|
286
701
|
|
|
287
702
|
// src/commands/devlog/loadDevlogEntries.ts
|
|
288
703
|
import { readdirSync, readFileSync as readFileSync4 } from "fs";
|
|
@@ -343,13 +758,13 @@ function shouldIgnoreCommit(files, ignorePaths) {
|
|
|
343
758
|
}
|
|
344
759
|
function printCommitsWithFiles(commits, ignore2, verbose) {
|
|
345
760
|
for (const commit2 of commits) {
|
|
346
|
-
console.log(` ${
|
|
761
|
+
console.log(` ${chalk9.yellow(commit2.hash)} ${commit2.message}`);
|
|
347
762
|
if (verbose) {
|
|
348
763
|
const visibleFiles = commit2.files.filter(
|
|
349
764
|
(file) => !ignore2.some((p) => file.startsWith(p))
|
|
350
765
|
);
|
|
351
766
|
for (const file of visibleFiles) {
|
|
352
|
-
console.log(` ${
|
|
767
|
+
console.log(` ${chalk9.dim(file)}`);
|
|
353
768
|
}
|
|
354
769
|
}
|
|
355
770
|
}
|
|
@@ -402,12 +817,12 @@ function list(options) {
|
|
|
402
817
|
isFirst = false;
|
|
403
818
|
const entries = devlogEntries.get(date);
|
|
404
819
|
if (skipDays.has(date)) {
|
|
405
|
-
console.log(`${
|
|
820
|
+
console.log(`${chalk10.bold.blue(date)} ${chalk10.dim("skipped")}`);
|
|
406
821
|
} else if (entries && entries.length > 0) {
|
|
407
|
-
const entryInfo = entries.map((e) => `${
|
|
408
|
-
console.log(`${
|
|
822
|
+
const entryInfo = entries.map((e) => `${chalk10.green(e.version)} ${e.title}`).join(" | ");
|
|
823
|
+
console.log(`${chalk10.bold.blue(date)} ${entryInfo}`);
|
|
409
824
|
} else {
|
|
410
|
-
console.log(`${
|
|
825
|
+
console.log(`${chalk10.bold.blue(date)} ${chalk10.red("\u26A0 devlog missing")}`);
|
|
411
826
|
}
|
|
412
827
|
printCommitsWithFiles(dateCommits, ignore2, options.verbose ?? false);
|
|
413
828
|
}
|
|
@@ -415,7 +830,7 @@ function list(options) {
|
|
|
415
830
|
|
|
416
831
|
// src/commands/devlog/next.ts
|
|
417
832
|
import { execSync as execSync6 } from "child_process";
|
|
418
|
-
import
|
|
833
|
+
import chalk11 from "chalk";
|
|
419
834
|
|
|
420
835
|
// src/commands/devlog/getLastVersionInfo.ts
|
|
421
836
|
import { execSync as execSync5 } from "child_process";
|
|
@@ -524,43 +939,43 @@ function next(options) {
|
|
|
524
939
|
const targetDate = dates[0];
|
|
525
940
|
if (!targetDate) {
|
|
526
941
|
if (lastInfo) {
|
|
527
|
-
console.log(
|
|
942
|
+
console.log(chalk11.dim("No commits after last versioned entry"));
|
|
528
943
|
} else {
|
|
529
|
-
console.log(
|
|
944
|
+
console.log(chalk11.dim("No commits found"));
|
|
530
945
|
}
|
|
531
946
|
return;
|
|
532
947
|
}
|
|
533
948
|
const commits = commitsByDate.get(targetDate) ?? [];
|
|
534
|
-
console.log(`${
|
|
949
|
+
console.log(`${chalk11.bold("name:")} ${repoName}`);
|
|
535
950
|
if (config.commit?.conventional && commits.length > 0) {
|
|
536
951
|
const version2 = getVersionAtCommit(commits[0].hash);
|
|
537
952
|
if (version2) {
|
|
538
|
-
console.log(`${
|
|
953
|
+
console.log(`${chalk11.bold("version:")} ${stripToMinor(version2)}`);
|
|
539
954
|
} else {
|
|
540
|
-
console.log(`${
|
|
955
|
+
console.log(`${chalk11.bold("version:")} ${chalk11.red("unknown")}`);
|
|
541
956
|
}
|
|
542
957
|
} else if (patchVersion && minorVersion) {
|
|
543
958
|
console.log(
|
|
544
|
-
`${
|
|
959
|
+
`${chalk11.bold("version:")} ${patchVersion} (patch) or ${minorVersion} (minor)`
|
|
545
960
|
);
|
|
546
961
|
} else {
|
|
547
|
-
console.log(`${
|
|
962
|
+
console.log(`${chalk11.bold("version:")} v0.1 (initial)`);
|
|
548
963
|
}
|
|
549
|
-
console.log(`${
|
|
964
|
+
console.log(`${chalk11.bold.blue(targetDate)}`);
|
|
550
965
|
printCommitsWithFiles(commits, ignore2, options.verbose ?? false);
|
|
551
966
|
}
|
|
552
967
|
|
|
553
968
|
// src/commands/devlog/skip.ts
|
|
554
|
-
import
|
|
969
|
+
import chalk12 from "chalk";
|
|
555
970
|
function skip(date) {
|
|
556
971
|
if (!/^\d{4}-\d{2}-\d{2}$/.test(date)) {
|
|
557
|
-
console.log(
|
|
972
|
+
console.log(chalk12.red("Invalid date format. Use YYYY-MM-DD"));
|
|
558
973
|
process.exit(1);
|
|
559
974
|
}
|
|
560
975
|
const config = loadConfig();
|
|
561
976
|
const skipDays = config.devlog?.skip?.days ?? [];
|
|
562
977
|
if (skipDays.includes(date)) {
|
|
563
|
-
console.log(
|
|
978
|
+
console.log(chalk12.yellow(`${date} is already in skip list`));
|
|
564
979
|
return;
|
|
565
980
|
}
|
|
566
981
|
skipDays.push(date);
|
|
@@ -573,28 +988,28 @@ function skip(date) {
|
|
|
573
988
|
}
|
|
574
989
|
};
|
|
575
990
|
saveConfig(config);
|
|
576
|
-
console.log(
|
|
991
|
+
console.log(chalk12.green(`Added ${date} to skip list`));
|
|
577
992
|
}
|
|
578
993
|
|
|
579
994
|
// src/commands/devlog/version.ts
|
|
580
|
-
import
|
|
995
|
+
import chalk13 from "chalk";
|
|
581
996
|
function version() {
|
|
582
997
|
const config = loadConfig();
|
|
583
998
|
const name = getRepoName();
|
|
584
999
|
const lastInfo = getLastVersionInfo(name, config);
|
|
585
1000
|
const lastVersion = lastInfo?.version ?? null;
|
|
586
1001
|
const nextVersion = lastVersion ? bumpVersion(lastVersion, "patch") : null;
|
|
587
|
-
console.log(`${
|
|
588
|
-
console.log(`${
|
|
589
|
-
console.log(`${
|
|
1002
|
+
console.log(`${chalk13.bold("name:")} ${name}`);
|
|
1003
|
+
console.log(`${chalk13.bold("last:")} ${lastVersion ?? chalk13.dim("none")}`);
|
|
1004
|
+
console.log(`${chalk13.bold("next:")} ${nextVersion ?? chalk13.dim("none")}`);
|
|
590
1005
|
}
|
|
591
1006
|
|
|
592
1007
|
// src/commands/enable-ralph/index.ts
|
|
593
|
-
import * as
|
|
594
|
-
import * as
|
|
1008
|
+
import * as fs4 from "fs";
|
|
1009
|
+
import * as path2 from "path";
|
|
595
1010
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
596
|
-
import
|
|
597
|
-
var __dirname3 =
|
|
1011
|
+
import chalk14 from "chalk";
|
|
1012
|
+
var __dirname3 = path2.dirname(fileURLToPath2(import.meta.url));
|
|
598
1013
|
function deepMerge(target, source) {
|
|
599
1014
|
const result = { ...target };
|
|
600
1015
|
for (const key of Object.keys(source)) {
|
|
@@ -612,30 +1027,30 @@ function deepMerge(target, source) {
|
|
|
612
1027
|
return result;
|
|
613
1028
|
}
|
|
614
1029
|
async function enableRalph() {
|
|
615
|
-
const sourcePath =
|
|
1030
|
+
const sourcePath = path2.join(
|
|
616
1031
|
__dirname3,
|
|
617
1032
|
"commands/enable-ralph/settings.local.json"
|
|
618
1033
|
);
|
|
619
|
-
const targetPath =
|
|
620
|
-
const sourceData = JSON.parse(
|
|
621
|
-
const targetDir =
|
|
622
|
-
if (!
|
|
623
|
-
|
|
1034
|
+
const targetPath = path2.join(process.cwd(), ".claude/settings.local.json");
|
|
1035
|
+
const sourceData = JSON.parse(fs4.readFileSync(sourcePath, "utf-8"));
|
|
1036
|
+
const targetDir = path2.dirname(targetPath);
|
|
1037
|
+
if (!fs4.existsSync(targetDir)) {
|
|
1038
|
+
fs4.mkdirSync(targetDir, { recursive: true });
|
|
624
1039
|
}
|
|
625
1040
|
let targetData = {};
|
|
626
1041
|
let targetContent = "{}";
|
|
627
|
-
if (
|
|
628
|
-
targetContent =
|
|
1042
|
+
if (fs4.existsSync(targetPath)) {
|
|
1043
|
+
targetContent = fs4.readFileSync(targetPath, "utf-8");
|
|
629
1044
|
targetData = JSON.parse(targetContent);
|
|
630
1045
|
}
|
|
631
1046
|
const merged = deepMerge(targetData, sourceData);
|
|
632
1047
|
const mergedContent = `${JSON.stringify(merged, null, " ")}
|
|
633
1048
|
`;
|
|
634
1049
|
if (mergedContent === targetContent) {
|
|
635
|
-
console.log(
|
|
1050
|
+
console.log(chalk14.green("settings.local.json already has ralph enabled"));
|
|
636
1051
|
return;
|
|
637
1052
|
}
|
|
638
|
-
console.log(
|
|
1053
|
+
console.log(chalk14.yellow("\nChanges to settings.local.json:"));
|
|
639
1054
|
console.log();
|
|
640
1055
|
printDiff(targetContent, mergedContent);
|
|
641
1056
|
const confirm = await promptConfirm("Apply these changes?");
|
|
@@ -643,15 +1058,15 @@ async function enableRalph() {
|
|
|
643
1058
|
console.log("Skipped");
|
|
644
1059
|
return;
|
|
645
1060
|
}
|
|
646
|
-
|
|
1061
|
+
fs4.writeFileSync(targetPath, mergedContent);
|
|
647
1062
|
console.log(`Updated ${targetPath}`);
|
|
648
1063
|
}
|
|
649
1064
|
|
|
650
1065
|
// src/commands/verify/init.ts
|
|
651
|
-
import
|
|
1066
|
+
import chalk25 from "chalk";
|
|
652
1067
|
|
|
653
1068
|
// src/shared/promptMultiselect.ts
|
|
654
|
-
import
|
|
1069
|
+
import chalk15 from "chalk";
|
|
655
1070
|
import enquirer3 from "enquirer";
|
|
656
1071
|
async function promptMultiselect(message, options) {
|
|
657
1072
|
const { selected } = await enquirer3.prompt({
|
|
@@ -660,7 +1075,7 @@ async function promptMultiselect(message, options) {
|
|
|
660
1075
|
message,
|
|
661
1076
|
choices: options.map((opt) => ({
|
|
662
1077
|
name: opt.value,
|
|
663
|
-
message: `${opt.name} - ${
|
|
1078
|
+
message: `${opt.name} - ${chalk15.dim(opt.description)}`
|
|
664
1079
|
})),
|
|
665
1080
|
// @ts-expect-error - enquirer types don't include symbols but it's supported
|
|
666
1081
|
symbols: {
|
|
@@ -674,23 +1089,23 @@ async function promptMultiselect(message, options) {
|
|
|
674
1089
|
}
|
|
675
1090
|
|
|
676
1091
|
// src/shared/readPackageJson.ts
|
|
677
|
-
import * as
|
|
678
|
-
import * as
|
|
679
|
-
import
|
|
1092
|
+
import * as fs5 from "fs";
|
|
1093
|
+
import * as path3 from "path";
|
|
1094
|
+
import chalk16 from "chalk";
|
|
680
1095
|
function findPackageJson() {
|
|
681
|
-
const packageJsonPath =
|
|
682
|
-
if (
|
|
1096
|
+
const packageJsonPath = path3.join(process.cwd(), "package.json");
|
|
1097
|
+
if (fs5.existsSync(packageJsonPath)) {
|
|
683
1098
|
return packageJsonPath;
|
|
684
1099
|
}
|
|
685
1100
|
return null;
|
|
686
1101
|
}
|
|
687
1102
|
function readPackageJson(filePath) {
|
|
688
|
-
return JSON.parse(
|
|
1103
|
+
return JSON.parse(fs5.readFileSync(filePath, "utf-8"));
|
|
689
1104
|
}
|
|
690
1105
|
function requirePackageJson() {
|
|
691
1106
|
const packageJsonPath = findPackageJson();
|
|
692
1107
|
if (!packageJsonPath) {
|
|
693
|
-
console.error(
|
|
1108
|
+
console.error(chalk16.red("No package.json found in current directory"));
|
|
694
1109
|
process.exit(1);
|
|
695
1110
|
}
|
|
696
1111
|
const pkg = readPackageJson(packageJsonPath);
|
|
@@ -699,9 +1114,9 @@ function requirePackageJson() {
|
|
|
699
1114
|
function findPackageJsonWithVerifyScripts(startDir) {
|
|
700
1115
|
let currentDir = startDir;
|
|
701
1116
|
while (true) {
|
|
702
|
-
const packageJsonPath =
|
|
703
|
-
if (
|
|
704
|
-
const packageJson = JSON.parse(
|
|
1117
|
+
const packageJsonPath = path3.join(currentDir, "package.json");
|
|
1118
|
+
if (fs5.existsSync(packageJsonPath)) {
|
|
1119
|
+
const packageJson = JSON.parse(fs5.readFileSync(packageJsonPath, "utf-8"));
|
|
705
1120
|
const scripts = packageJson.scripts || {};
|
|
706
1121
|
const verifyScripts = Object.keys(scripts).filter(
|
|
707
1122
|
(name) => name.startsWith("verify:")
|
|
@@ -710,7 +1125,7 @@ function findPackageJsonWithVerifyScripts(startDir) {
|
|
|
710
1125
|
return { packageJsonPath, verifyScripts };
|
|
711
1126
|
}
|
|
712
1127
|
}
|
|
713
|
-
const parentDir =
|
|
1128
|
+
const parentDir = path3.dirname(currentDir);
|
|
714
1129
|
if (parentDir === currentDir) {
|
|
715
1130
|
return null;
|
|
716
1131
|
}
|
|
@@ -728,15 +1143,15 @@ var expectedScripts = {
|
|
|
728
1143
|
};
|
|
729
1144
|
|
|
730
1145
|
// src/commands/verify/setup/setupBuild.ts
|
|
731
|
-
import
|
|
1146
|
+
import chalk18 from "chalk";
|
|
732
1147
|
|
|
733
1148
|
// src/commands/verify/installPackage.ts
|
|
734
1149
|
import { execSync as execSync7 } from "child_process";
|
|
735
|
-
import * as
|
|
736
|
-
import * as
|
|
737
|
-
import
|
|
1150
|
+
import * as fs6 from "fs";
|
|
1151
|
+
import * as path4 from "path";
|
|
1152
|
+
import chalk17 from "chalk";
|
|
738
1153
|
function writePackageJson(filePath, pkg) {
|
|
739
|
-
|
|
1154
|
+
fs6.writeFileSync(filePath, `${JSON.stringify(pkg, null, 2)}
|
|
740
1155
|
`);
|
|
741
1156
|
}
|
|
742
1157
|
function addScript(pkg, name, command) {
|
|
@@ -749,36 +1164,36 @@ function addScript(pkg, name, command) {
|
|
|
749
1164
|
};
|
|
750
1165
|
}
|
|
751
1166
|
function installPackage(name, cwd) {
|
|
752
|
-
console.log(
|
|
1167
|
+
console.log(chalk17.dim(`Installing ${name}...`));
|
|
753
1168
|
try {
|
|
754
1169
|
execSync7(`npm install -D ${name}`, { stdio: "inherit", cwd });
|
|
755
1170
|
return true;
|
|
756
1171
|
} catch {
|
|
757
|
-
console.error(
|
|
1172
|
+
console.error(chalk17.red(`Failed to install ${name}`));
|
|
758
1173
|
return false;
|
|
759
1174
|
}
|
|
760
1175
|
}
|
|
761
1176
|
function addToKnipIgnoreBinaries(cwd, binary) {
|
|
762
|
-
const knipJsonPath =
|
|
1177
|
+
const knipJsonPath = path4.join(cwd, "knip.json");
|
|
763
1178
|
try {
|
|
764
1179
|
let knipConfig;
|
|
765
|
-
if (
|
|
766
|
-
knipConfig = JSON.parse(
|
|
1180
|
+
if (fs6.existsSync(knipJsonPath)) {
|
|
1181
|
+
knipConfig = JSON.parse(fs6.readFileSync(knipJsonPath, "utf-8"));
|
|
767
1182
|
} else {
|
|
768
1183
|
knipConfig = { $schema: "https://unpkg.com/knip@5/schema.json" };
|
|
769
1184
|
}
|
|
770
1185
|
const ignoreBinaries = knipConfig.ignoreBinaries ?? [];
|
|
771
1186
|
if (!ignoreBinaries.includes(binary)) {
|
|
772
1187
|
knipConfig.ignoreBinaries = [...ignoreBinaries, binary];
|
|
773
|
-
|
|
1188
|
+
fs6.writeFileSync(
|
|
774
1189
|
knipJsonPath,
|
|
775
1190
|
`${JSON.stringify(knipConfig, null, " ")}
|
|
776
1191
|
`
|
|
777
1192
|
);
|
|
778
|
-
console.log(
|
|
1193
|
+
console.log(chalk17.dim(`Added '${binary}' to knip.json ignoreBinaries`));
|
|
779
1194
|
}
|
|
780
1195
|
} catch {
|
|
781
|
-
console.log(
|
|
1196
|
+
console.log(chalk17.yellow("Warning: Could not update knip.json"));
|
|
782
1197
|
}
|
|
783
1198
|
}
|
|
784
1199
|
function setupVerifyScript(packageJsonPath, scriptName, command) {
|
|
@@ -790,7 +1205,7 @@ function setupVerifyScript(packageJsonPath, scriptName, command) {
|
|
|
790
1205
|
|
|
791
1206
|
// src/commands/verify/setup/setupBuild.ts
|
|
792
1207
|
async function setupBuild(packageJsonPath, hasVite, hasTypescript) {
|
|
793
|
-
console.log(
|
|
1208
|
+
console.log(chalk18.blue("\nSetting up build verification..."));
|
|
794
1209
|
let command;
|
|
795
1210
|
if (hasVite && hasTypescript) {
|
|
796
1211
|
command = "tsc -b && vite build --logLevel error";
|
|
@@ -799,17 +1214,17 @@ async function setupBuild(packageJsonPath, hasVite, hasTypescript) {
|
|
|
799
1214
|
} else {
|
|
800
1215
|
command = "tsc --noEmit";
|
|
801
1216
|
}
|
|
802
|
-
console.log(
|
|
1217
|
+
console.log(chalk18.dim(`Using: ${command}`));
|
|
803
1218
|
const pkg = readPackageJson(packageJsonPath);
|
|
804
1219
|
writePackageJson(packageJsonPath, addScript(pkg, "verify:build", command));
|
|
805
1220
|
}
|
|
806
1221
|
|
|
807
1222
|
// src/commands/verify/setup/setupDuplicateCode.ts
|
|
808
|
-
import * as
|
|
809
|
-
import
|
|
1223
|
+
import * as path5 from "path";
|
|
1224
|
+
import chalk19 from "chalk";
|
|
810
1225
|
async function setupDuplicateCode(packageJsonPath) {
|
|
811
|
-
console.log(
|
|
812
|
-
const cwd =
|
|
1226
|
+
console.log(chalk19.blue("\nSetting up jscpd..."));
|
|
1227
|
+
const cwd = path5.dirname(packageJsonPath);
|
|
813
1228
|
const pkg = readPackageJson(packageJsonPath);
|
|
814
1229
|
const hasJscpd = !!pkg.dependencies?.jscpd || !!pkg.devDependencies?.jscpd;
|
|
815
1230
|
if (!hasJscpd && !installPackage("jscpd", cwd)) {
|
|
@@ -823,11 +1238,11 @@ async function setupDuplicateCode(packageJsonPath) {
|
|
|
823
1238
|
}
|
|
824
1239
|
|
|
825
1240
|
// src/commands/verify/setup/setupHardcodedColors.ts
|
|
826
|
-
import * as
|
|
827
|
-
import
|
|
1241
|
+
import * as path6 from "path";
|
|
1242
|
+
import chalk20 from "chalk";
|
|
828
1243
|
async function setupHardcodedColors(packageJsonPath, hasOpenColor) {
|
|
829
|
-
console.log(
|
|
830
|
-
const cwd =
|
|
1244
|
+
console.log(chalk20.blue("\nSetting up hardcoded colors check..."));
|
|
1245
|
+
const cwd = path6.dirname(packageJsonPath);
|
|
831
1246
|
if (!hasOpenColor) {
|
|
832
1247
|
installPackage("open-color", cwd);
|
|
833
1248
|
}
|
|
@@ -840,11 +1255,11 @@ async function setupHardcodedColors(packageJsonPath, hasOpenColor) {
|
|
|
840
1255
|
}
|
|
841
1256
|
|
|
842
1257
|
// src/commands/verify/setup/setupKnip.ts
|
|
843
|
-
import * as
|
|
844
|
-
import
|
|
1258
|
+
import * as path7 from "path";
|
|
1259
|
+
import chalk21 from "chalk";
|
|
845
1260
|
async function setupKnip(packageJsonPath) {
|
|
846
|
-
console.log(
|
|
847
|
-
const cwd =
|
|
1261
|
+
console.log(chalk21.blue("\nSetting up knip..."));
|
|
1262
|
+
const cwd = path7.dirname(packageJsonPath);
|
|
848
1263
|
const pkg = readPackageJson(packageJsonPath);
|
|
849
1264
|
if (!pkg.devDependencies?.knip && !installPackage("knip", cwd)) {
|
|
850
1265
|
return;
|
|
@@ -857,15 +1272,15 @@ async function setupKnip(packageJsonPath) {
|
|
|
857
1272
|
}
|
|
858
1273
|
|
|
859
1274
|
// src/commands/verify/setup/setupLint.ts
|
|
860
|
-
import * as
|
|
861
|
-
import
|
|
1275
|
+
import * as path8 from "path";
|
|
1276
|
+
import chalk23 from "chalk";
|
|
862
1277
|
|
|
863
1278
|
// src/commands/lint/init.ts
|
|
864
1279
|
import { execSync as execSync9 } from "child_process";
|
|
865
1280
|
import { existsSync as existsSync8, readFileSync as readFileSync9, writeFileSync as writeFileSync7 } from "fs";
|
|
866
1281
|
import { dirname as dirname7, join as join7 } from "path";
|
|
867
1282
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
868
|
-
import
|
|
1283
|
+
import chalk22 from "chalk";
|
|
869
1284
|
|
|
870
1285
|
// src/shared/removeEslint.ts
|
|
871
1286
|
import { execSync as execSync8 } from "child_process";
|
|
@@ -971,10 +1386,10 @@ async function init2() {
|
|
|
971
1386
|
console.log("biome.json already has the correct linter config");
|
|
972
1387
|
return;
|
|
973
1388
|
}
|
|
974
|
-
console.log(
|
|
1389
|
+
console.log(chalk22.yellow("\n\u26A0\uFE0F biome.json will be updated:"));
|
|
975
1390
|
console.log();
|
|
976
1391
|
printDiff(oldContent, newContent);
|
|
977
|
-
const confirm = await promptConfirm(
|
|
1392
|
+
const confirm = await promptConfirm(chalk22.red("Update biome.json?"));
|
|
978
1393
|
if (!confirm) {
|
|
979
1394
|
console.log("Skipped biome.json update");
|
|
980
1395
|
return;
|
|
@@ -985,8 +1400,8 @@ async function init2() {
|
|
|
985
1400
|
|
|
986
1401
|
// src/commands/verify/setup/setupLint.ts
|
|
987
1402
|
async function setupLint(packageJsonPath) {
|
|
988
|
-
console.log(
|
|
989
|
-
const cwd =
|
|
1403
|
+
console.log(chalk23.blue("\nSetting up biome..."));
|
|
1404
|
+
const cwd = path8.dirname(packageJsonPath);
|
|
990
1405
|
const pkg = readPackageJson(packageJsonPath);
|
|
991
1406
|
if (!pkg.devDependencies?.["@biomejs/biome"]) {
|
|
992
1407
|
if (!installPackage("@biomejs/biome", cwd)) {
|
|
@@ -1002,11 +1417,11 @@ async function setupLint(packageJsonPath) {
|
|
|
1002
1417
|
}
|
|
1003
1418
|
|
|
1004
1419
|
// src/commands/verify/setup/setupTest.ts
|
|
1005
|
-
import * as
|
|
1006
|
-
import
|
|
1420
|
+
import * as path9 from "path";
|
|
1421
|
+
import chalk24 from "chalk";
|
|
1007
1422
|
async function setupTest(packageJsonPath) {
|
|
1008
|
-
console.log(
|
|
1009
|
-
const cwd =
|
|
1423
|
+
console.log(chalk24.blue("\nSetting up vitest..."));
|
|
1424
|
+
const cwd = path9.dirname(packageJsonPath);
|
|
1010
1425
|
const pkg = readPackageJson(packageJsonPath);
|
|
1011
1426
|
if (!pkg.devDependencies?.vitest && !installPackage("vitest", cwd)) {
|
|
1012
1427
|
return;
|
|
@@ -1148,16 +1563,16 @@ async function init3() {
|
|
|
1148
1563
|
const setup = detectExistingSetup(pkg);
|
|
1149
1564
|
const availableOptions = getAvailableOptions(setup);
|
|
1150
1565
|
if (availableOptions.length === 0) {
|
|
1151
|
-
console.log(
|
|
1566
|
+
console.log(chalk25.green("All verify scripts are already configured!"));
|
|
1152
1567
|
return;
|
|
1153
1568
|
}
|
|
1154
|
-
console.log(
|
|
1569
|
+
console.log(chalk25.bold("Available verify scripts to add:\n"));
|
|
1155
1570
|
const selected = await promptMultiselect(
|
|
1156
1571
|
"Select verify scripts to add:",
|
|
1157
1572
|
availableOptions
|
|
1158
1573
|
);
|
|
1159
1574
|
if (selected.length === 0) {
|
|
1160
|
-
console.log(
|
|
1575
|
+
console.log(chalk25.yellow("No scripts selected"));
|
|
1161
1576
|
return;
|
|
1162
1577
|
}
|
|
1163
1578
|
for (const choice of selected) {
|
|
@@ -1182,43 +1597,43 @@ async function init3() {
|
|
|
1182
1597
|
break;
|
|
1183
1598
|
}
|
|
1184
1599
|
}
|
|
1185
|
-
console.log(
|
|
1600
|
+
console.log(chalk25.green(`
|
|
1186
1601
|
Added ${selected.length} verify script(s):`));
|
|
1187
1602
|
for (const choice of selected) {
|
|
1188
|
-
console.log(
|
|
1603
|
+
console.log(chalk25.green(` - verify:${choice}`));
|
|
1189
1604
|
}
|
|
1190
|
-
console.log(
|
|
1605
|
+
console.log(chalk25.dim("\nRun 'assist verify' to run all verify scripts"));
|
|
1191
1606
|
}
|
|
1192
1607
|
|
|
1193
1608
|
// src/commands/vscode/init.ts
|
|
1194
|
-
import * as
|
|
1195
|
-
import * as
|
|
1196
|
-
import
|
|
1609
|
+
import * as fs8 from "fs";
|
|
1610
|
+
import * as path11 from "path";
|
|
1611
|
+
import chalk27 from "chalk";
|
|
1197
1612
|
|
|
1198
1613
|
// src/commands/vscode/createLaunchJson.ts
|
|
1199
|
-
import * as
|
|
1200
|
-
import * as
|
|
1201
|
-
import
|
|
1614
|
+
import * as fs7 from "fs";
|
|
1615
|
+
import * as path10 from "path";
|
|
1616
|
+
import chalk26 from "chalk";
|
|
1202
1617
|
function ensureVscodeFolder() {
|
|
1203
|
-
const vscodeDir =
|
|
1204
|
-
if (!
|
|
1205
|
-
|
|
1206
|
-
console.log(
|
|
1618
|
+
const vscodeDir = path10.join(process.cwd(), ".vscode");
|
|
1619
|
+
if (!fs7.existsSync(vscodeDir)) {
|
|
1620
|
+
fs7.mkdirSync(vscodeDir);
|
|
1621
|
+
console.log(chalk26.dim("Created .vscode folder"));
|
|
1207
1622
|
}
|
|
1208
1623
|
}
|
|
1209
1624
|
function removeVscodeFromGitignore() {
|
|
1210
|
-
const gitignorePath =
|
|
1211
|
-
if (!
|
|
1625
|
+
const gitignorePath = path10.join(process.cwd(), ".gitignore");
|
|
1626
|
+
if (!fs7.existsSync(gitignorePath)) {
|
|
1212
1627
|
return;
|
|
1213
1628
|
}
|
|
1214
|
-
const content =
|
|
1629
|
+
const content = fs7.readFileSync(gitignorePath, "utf-8");
|
|
1215
1630
|
const lines = content.split("\n");
|
|
1216
1631
|
const filteredLines = lines.filter(
|
|
1217
1632
|
(line) => !line.trim().toLowerCase().includes(".vscode")
|
|
1218
1633
|
);
|
|
1219
1634
|
if (filteredLines.length !== lines.length) {
|
|
1220
|
-
|
|
1221
|
-
console.log(
|
|
1635
|
+
fs7.writeFileSync(gitignorePath, filteredLines.join("\n"));
|
|
1636
|
+
console.log(chalk26.dim("Removed .vscode references from .gitignore"));
|
|
1222
1637
|
}
|
|
1223
1638
|
}
|
|
1224
1639
|
function createLaunchJson() {
|
|
@@ -1233,10 +1648,10 @@ function createLaunchJson() {
|
|
|
1233
1648
|
}
|
|
1234
1649
|
]
|
|
1235
1650
|
};
|
|
1236
|
-
const launchPath =
|
|
1237
|
-
|
|
1651
|
+
const launchPath = path10.join(process.cwd(), ".vscode", "launch.json");
|
|
1652
|
+
fs7.writeFileSync(launchPath, `${JSON.stringify(launchConfig, null, " ")}
|
|
1238
1653
|
`);
|
|
1239
|
-
console.log(
|
|
1654
|
+
console.log(chalk26.green("Created .vscode/launch.json"));
|
|
1240
1655
|
}
|
|
1241
1656
|
function createSettingsJson() {
|
|
1242
1657
|
const settings = {
|
|
@@ -1246,31 +1661,31 @@ function createSettingsJson() {
|
|
|
1246
1661
|
"source.organizeImports.biome": "explicit"
|
|
1247
1662
|
}
|
|
1248
1663
|
};
|
|
1249
|
-
const settingsPath =
|
|
1250
|
-
|
|
1664
|
+
const settingsPath = path10.join(process.cwd(), ".vscode", "settings.json");
|
|
1665
|
+
fs7.writeFileSync(settingsPath, `${JSON.stringify(settings, null, " ")}
|
|
1251
1666
|
`);
|
|
1252
|
-
console.log(
|
|
1667
|
+
console.log(chalk26.green("Created .vscode/settings.json"));
|
|
1253
1668
|
}
|
|
1254
1669
|
function createExtensionsJson() {
|
|
1255
1670
|
const extensions = {
|
|
1256
1671
|
recommendations: ["biomejs.biome"]
|
|
1257
1672
|
};
|
|
1258
|
-
const extensionsPath =
|
|
1259
|
-
|
|
1673
|
+
const extensionsPath = path10.join(process.cwd(), ".vscode", "extensions.json");
|
|
1674
|
+
fs7.writeFileSync(
|
|
1260
1675
|
extensionsPath,
|
|
1261
1676
|
`${JSON.stringify(extensions, null, " ")}
|
|
1262
1677
|
`
|
|
1263
1678
|
);
|
|
1264
|
-
console.log(
|
|
1679
|
+
console.log(chalk26.green("Created .vscode/extensions.json"));
|
|
1265
1680
|
}
|
|
1266
1681
|
|
|
1267
1682
|
// src/commands/vscode/init.ts
|
|
1268
1683
|
function detectExistingSetup2(pkg) {
|
|
1269
|
-
const vscodeDir =
|
|
1684
|
+
const vscodeDir = path11.join(process.cwd(), ".vscode");
|
|
1270
1685
|
return {
|
|
1271
|
-
hasVscodeFolder:
|
|
1272
|
-
hasLaunchJson:
|
|
1273
|
-
hasSettingsJson:
|
|
1686
|
+
hasVscodeFolder: fs8.existsSync(vscodeDir),
|
|
1687
|
+
hasLaunchJson: fs8.existsSync(path11.join(vscodeDir, "launch.json")),
|
|
1688
|
+
hasSettingsJson: fs8.existsSync(path11.join(vscodeDir, "settings.json")),
|
|
1274
1689
|
hasVite: !!pkg.devDependencies?.vite || !!pkg.dependencies?.vite
|
|
1275
1690
|
};
|
|
1276
1691
|
}
|
|
@@ -1293,16 +1708,16 @@ async function init4() {
|
|
|
1293
1708
|
});
|
|
1294
1709
|
}
|
|
1295
1710
|
if (availableOptions.length === 0) {
|
|
1296
|
-
console.log(
|
|
1711
|
+
console.log(chalk27.green("VS Code configuration already exists!"));
|
|
1297
1712
|
return;
|
|
1298
1713
|
}
|
|
1299
|
-
console.log(
|
|
1714
|
+
console.log(chalk27.bold("Available VS Code configurations to add:\n"));
|
|
1300
1715
|
const selected = await promptMultiselect(
|
|
1301
1716
|
"Select configurations to add:",
|
|
1302
1717
|
availableOptions
|
|
1303
1718
|
);
|
|
1304
1719
|
if (selected.length === 0) {
|
|
1305
|
-
console.log(
|
|
1720
|
+
console.log(chalk27.yellow("No configurations selected"));
|
|
1306
1721
|
return;
|
|
1307
1722
|
}
|
|
1308
1723
|
removeVscodeFromGitignore();
|
|
@@ -1319,7 +1734,7 @@ async function init4() {
|
|
|
1319
1734
|
}
|
|
1320
1735
|
}
|
|
1321
1736
|
console.log(
|
|
1322
|
-
|
|
1737
|
+
chalk27.green(`
|
|
1323
1738
|
Added ${selected.length} VS Code configuration(s)`)
|
|
1324
1739
|
);
|
|
1325
1740
|
}
|
|
@@ -1331,23 +1746,23 @@ async function init5() {
|
|
|
1331
1746
|
}
|
|
1332
1747
|
|
|
1333
1748
|
// src/commands/lint/runFileNameCheck.ts
|
|
1334
|
-
import
|
|
1335
|
-
import
|
|
1336
|
-
import
|
|
1749
|
+
import fs10 from "fs";
|
|
1750
|
+
import path13 from "path";
|
|
1751
|
+
import chalk28 from "chalk";
|
|
1337
1752
|
|
|
1338
1753
|
// src/shared/findSourceFiles.ts
|
|
1339
|
-
import
|
|
1340
|
-
import
|
|
1754
|
+
import fs9 from "fs";
|
|
1755
|
+
import path12 from "path";
|
|
1341
1756
|
var EXTENSIONS = [".ts", ".tsx"];
|
|
1342
1757
|
function findSourceFiles(dir, options = {}) {
|
|
1343
1758
|
const { includeTests = true } = options;
|
|
1344
1759
|
const results = [];
|
|
1345
|
-
if (!
|
|
1760
|
+
if (!fs9.existsSync(dir)) {
|
|
1346
1761
|
return results;
|
|
1347
1762
|
}
|
|
1348
|
-
const entries =
|
|
1763
|
+
const entries = fs9.readdirSync(dir, { withFileTypes: true });
|
|
1349
1764
|
for (const entry of entries) {
|
|
1350
|
-
const fullPath =
|
|
1765
|
+
const fullPath = path12.join(dir, entry.name);
|
|
1351
1766
|
if (entry.isDirectory() && entry.name !== "node_modules") {
|
|
1352
1767
|
results.push(...findSourceFiles(fullPath, options));
|
|
1353
1768
|
} else if (entry.isFile() && EXTENSIONS.some((ext) => entry.name.endsWith(ext))) {
|
|
@@ -1371,10 +1786,10 @@ function checkFileNames() {
|
|
|
1371
1786
|
const sourceFiles = findSourceFiles("src");
|
|
1372
1787
|
const violations = [];
|
|
1373
1788
|
for (const filePath of sourceFiles) {
|
|
1374
|
-
const fileName =
|
|
1789
|
+
const fileName = path13.basename(filePath);
|
|
1375
1790
|
const nameWithoutExt = fileName.replace(/\.(ts|tsx)$/, "");
|
|
1376
1791
|
if (/^[A-Z]/.test(nameWithoutExt)) {
|
|
1377
|
-
const content =
|
|
1792
|
+
const content = fs10.readFileSync(filePath, "utf-8");
|
|
1378
1793
|
if (!hasClassOrComponent(content)) {
|
|
1379
1794
|
violations.push({ filePath, fileName });
|
|
1380
1795
|
}
|
|
@@ -1385,16 +1800,16 @@ function checkFileNames() {
|
|
|
1385
1800
|
function runFileNameCheck() {
|
|
1386
1801
|
const violations = checkFileNames();
|
|
1387
1802
|
if (violations.length > 0) {
|
|
1388
|
-
console.error(
|
|
1803
|
+
console.error(chalk28.red("\nFile name check failed:\n"));
|
|
1389
1804
|
console.error(
|
|
1390
|
-
|
|
1805
|
+
chalk28.red(
|
|
1391
1806
|
" Files without classes or React components should not start with a capital letter.\n"
|
|
1392
1807
|
)
|
|
1393
1808
|
);
|
|
1394
1809
|
for (const violation of violations) {
|
|
1395
|
-
console.error(
|
|
1810
|
+
console.error(chalk28.red(` ${violation.filePath}`));
|
|
1396
1811
|
console.error(
|
|
1397
|
-
|
|
1812
|
+
chalk28.gray(
|
|
1398
1813
|
` Rename to: ${violation.fileName.charAt(0).toLowerCase()}${violation.fileName.slice(1)}
|
|
1399
1814
|
`
|
|
1400
1815
|
)
|
|
@@ -1411,20 +1826,20 @@ function runFileNameCheck() {
|
|
|
1411
1826
|
}
|
|
1412
1827
|
|
|
1413
1828
|
// src/commands/lint/runImportExtensionCheck.ts
|
|
1414
|
-
import
|
|
1829
|
+
import fs11 from "fs";
|
|
1415
1830
|
|
|
1416
1831
|
// src/commands/lint/shared.ts
|
|
1417
|
-
import
|
|
1832
|
+
import chalk29 from "chalk";
|
|
1418
1833
|
function reportViolations(violations, checkName, errorMessage, successMessage) {
|
|
1419
1834
|
if (violations.length > 0) {
|
|
1420
|
-
console.error(
|
|
1835
|
+
console.error(chalk29.red(`
|
|
1421
1836
|
${checkName} failed:
|
|
1422
1837
|
`));
|
|
1423
|
-
console.error(
|
|
1838
|
+
console.error(chalk29.red(` ${errorMessage}
|
|
1424
1839
|
`));
|
|
1425
1840
|
for (const violation of violations) {
|
|
1426
|
-
console.error(
|
|
1427
|
-
console.error(
|
|
1841
|
+
console.error(chalk29.red(` ${violation.filePath}:${violation.line}`));
|
|
1842
|
+
console.error(chalk29.gray(` ${violation.content}
|
|
1428
1843
|
`));
|
|
1429
1844
|
}
|
|
1430
1845
|
return false;
|
|
@@ -1437,7 +1852,7 @@ ${checkName} failed:
|
|
|
1437
1852
|
|
|
1438
1853
|
// src/commands/lint/runImportExtensionCheck.ts
|
|
1439
1854
|
function checkForImportExtensions(filePath) {
|
|
1440
|
-
const content =
|
|
1855
|
+
const content = fs11.readFileSync(filePath, "utf-8");
|
|
1441
1856
|
const lines = content.split("\n");
|
|
1442
1857
|
const violations = [];
|
|
1443
1858
|
const importExtensionPattern = /from\s+["']\..*\.(js|ts)["']/;
|
|
@@ -1471,9 +1886,9 @@ function runImportExtensionCheck() {
|
|
|
1471
1886
|
}
|
|
1472
1887
|
|
|
1473
1888
|
// src/commands/lint/runStaticImportCheck.ts
|
|
1474
|
-
import
|
|
1889
|
+
import fs12 from "fs";
|
|
1475
1890
|
function checkForDynamicImports(filePath) {
|
|
1476
|
-
const content =
|
|
1891
|
+
const content = fs12.readFileSync(filePath, "utf-8");
|
|
1477
1892
|
const lines = content.split("\n");
|
|
1478
1893
|
const violations = [];
|
|
1479
1894
|
const requirePattern = /\brequire\s*\(/;
|
|
@@ -1587,19 +2002,19 @@ function detectPlatform() {
|
|
|
1587
2002
|
|
|
1588
2003
|
// src/commands/notify/showWindowsNotificationFromWsl.ts
|
|
1589
2004
|
import { spawn } from "child_process";
|
|
1590
|
-
import
|
|
2005
|
+
import fs13 from "fs";
|
|
1591
2006
|
import { createRequire } from "module";
|
|
1592
|
-
import
|
|
2007
|
+
import path14 from "path";
|
|
1593
2008
|
var require2 = createRequire(import.meta.url);
|
|
1594
2009
|
function getSnoreToastPath() {
|
|
1595
|
-
const notifierPath =
|
|
1596
|
-
return
|
|
2010
|
+
const notifierPath = path14.dirname(require2.resolve("node-notifier"));
|
|
2011
|
+
return path14.join(notifierPath, "vendor", "snoreToast", "snoretoast-x64.exe");
|
|
1597
2012
|
}
|
|
1598
2013
|
function showWindowsNotificationFromWsl(options) {
|
|
1599
2014
|
const { title, message, sound } = options;
|
|
1600
2015
|
const snoreToastPath = getSnoreToastPath();
|
|
1601
2016
|
try {
|
|
1602
|
-
|
|
2017
|
+
fs13.chmodSync(snoreToastPath, 493);
|
|
1603
2018
|
} catch {
|
|
1604
2019
|
}
|
|
1605
2020
|
const args = ["-t", title, "-m", message];
|
|
@@ -1671,7 +2086,7 @@ async function notify() {
|
|
|
1671
2086
|
|
|
1672
2087
|
// src/commands/prs.ts
|
|
1673
2088
|
import { execSync as execSync11 } from "child_process";
|
|
1674
|
-
import
|
|
2089
|
+
import chalk30 from "chalk";
|
|
1675
2090
|
import enquirer4 from "enquirer";
|
|
1676
2091
|
var PAGE_SIZE = 10;
|
|
1677
2092
|
async function prs(options) {
|
|
@@ -1706,12 +2121,12 @@ async function displayPaginated(pullRequests) {
|
|
|
1706
2121
|
let currentPage = 0;
|
|
1707
2122
|
const getStatus = (pr) => {
|
|
1708
2123
|
if (pr.state === "MERGED" && pr.mergedAt) {
|
|
1709
|
-
return { label:
|
|
2124
|
+
return { label: chalk30.magenta("merged"), date: pr.mergedAt };
|
|
1710
2125
|
}
|
|
1711
2126
|
if (pr.state === "CLOSED" && pr.closedAt) {
|
|
1712
|
-
return { label:
|
|
2127
|
+
return { label: chalk30.red("closed"), date: pr.closedAt };
|
|
1713
2128
|
}
|
|
1714
|
-
return { label:
|
|
2129
|
+
return { label: chalk30.green("opened"), date: pr.createdAt };
|
|
1715
2130
|
};
|
|
1716
2131
|
const displayPage = (page) => {
|
|
1717
2132
|
const start = page * PAGE_SIZE;
|
|
@@ -1727,9 +2142,9 @@ Page ${page + 1} of ${totalPages} (${pullRequests.length} total)
|
|
|
1727
2142
|
const formattedDate = new Date(status.date).toISOString().split("T")[0];
|
|
1728
2143
|
const fileCount = pr.changedFiles.toLocaleString();
|
|
1729
2144
|
console.log(
|
|
1730
|
-
`${
|
|
2145
|
+
`${chalk30.cyan(`#${pr.number}`)} ${pr.title} ${chalk30.dim(`(${pr.author.login},`)} ${status.label} ${chalk30.dim(`${formattedDate})`)}`
|
|
1731
2146
|
);
|
|
1732
|
-
console.log(
|
|
2147
|
+
console.log(chalk30.dim(` ${fileCount} files | ${pr.url}`));
|
|
1733
2148
|
console.log();
|
|
1734
2149
|
}
|
|
1735
2150
|
};
|
|
@@ -1764,21 +2179,21 @@ Page ${page + 1} of ${totalPages} (${pullRequests.length} total)
|
|
|
1764
2179
|
|
|
1765
2180
|
// src/commands/refactor/check.ts
|
|
1766
2181
|
import { spawn as spawn2 } from "child_process";
|
|
1767
|
-
import * as
|
|
2182
|
+
import * as path15 from "path";
|
|
1768
2183
|
|
|
1769
2184
|
// src/commands/refactor/getViolations.ts
|
|
1770
2185
|
import { execSync as execSync12 } from "child_process";
|
|
1771
|
-
import
|
|
1772
|
-
import { minimatch } from "minimatch";
|
|
2186
|
+
import fs15 from "fs";
|
|
2187
|
+
import { minimatch as minimatch2 } from "minimatch";
|
|
1773
2188
|
|
|
1774
2189
|
// src/commands/refactor/getIgnoredFiles.ts
|
|
1775
|
-
import
|
|
2190
|
+
import fs14 from "fs";
|
|
1776
2191
|
var REFACTOR_YML_PATH = "refactor.yml";
|
|
1777
2192
|
function parseRefactorYml() {
|
|
1778
|
-
if (!
|
|
2193
|
+
if (!fs14.existsSync(REFACTOR_YML_PATH)) {
|
|
1779
2194
|
return [];
|
|
1780
2195
|
}
|
|
1781
|
-
const content =
|
|
2196
|
+
const content = fs14.readFileSync(REFACTOR_YML_PATH, "utf-8");
|
|
1782
2197
|
const entries = [];
|
|
1783
2198
|
const lines = content.split("\n");
|
|
1784
2199
|
let currentEntry = {};
|
|
@@ -1807,7 +2222,7 @@ function getIgnoredFiles() {
|
|
|
1807
2222
|
}
|
|
1808
2223
|
|
|
1809
2224
|
// src/commands/refactor/logViolations.ts
|
|
1810
|
-
import
|
|
2225
|
+
import chalk31 from "chalk";
|
|
1811
2226
|
var DEFAULT_MAX_LINES = 100;
|
|
1812
2227
|
function logViolations(violations, maxLines = DEFAULT_MAX_LINES) {
|
|
1813
2228
|
if (violations.length === 0) {
|
|
@@ -1816,43 +2231,43 @@ function logViolations(violations, maxLines = DEFAULT_MAX_LINES) {
|
|
|
1816
2231
|
}
|
|
1817
2232
|
return;
|
|
1818
2233
|
}
|
|
1819
|
-
console.error(
|
|
2234
|
+
console.error(chalk31.red(`
|
|
1820
2235
|
Refactor check failed:
|
|
1821
2236
|
`));
|
|
1822
|
-
console.error(
|
|
2237
|
+
console.error(chalk31.red(` The following files exceed ${maxLines} lines:
|
|
1823
2238
|
`));
|
|
1824
2239
|
for (const violation of violations) {
|
|
1825
|
-
console.error(
|
|
2240
|
+
console.error(chalk31.red(` ${violation.file} (${violation.lines} lines)`));
|
|
1826
2241
|
}
|
|
1827
2242
|
console.error(
|
|
1828
|
-
|
|
2243
|
+
chalk31.yellow(
|
|
1829
2244
|
`
|
|
1830
2245
|
Each file needs to be sensibly refactored, or if there is no sensible
|
|
1831
2246
|
way to refactor it, ignore it with:
|
|
1832
2247
|
`
|
|
1833
2248
|
)
|
|
1834
2249
|
);
|
|
1835
|
-
console.error(
|
|
2250
|
+
console.error(chalk31.gray(` assist refactor ignore <file>
|
|
1836
2251
|
`));
|
|
1837
2252
|
if (process.env.CLAUDECODE) {
|
|
1838
|
-
console.error(
|
|
2253
|
+
console.error(chalk31.cyan(`
|
|
1839
2254
|
## Extracting Code to New Files
|
|
1840
2255
|
`));
|
|
1841
2256
|
console.error(
|
|
1842
|
-
|
|
2257
|
+
chalk31.cyan(
|
|
1843
2258
|
` When extracting logic from one file to another, consider where the extracted code belongs:
|
|
1844
2259
|
`
|
|
1845
2260
|
)
|
|
1846
2261
|
);
|
|
1847
2262
|
console.error(
|
|
1848
|
-
|
|
2263
|
+
chalk31.cyan(
|
|
1849
2264
|
` 1. Keep related logic together: If the extracted code is tightly coupled to the
|
|
1850
2265
|
original file's domain, create a new folder containing both the original and extracted files.
|
|
1851
2266
|
`
|
|
1852
2267
|
)
|
|
1853
2268
|
);
|
|
1854
2269
|
console.error(
|
|
1855
|
-
|
|
2270
|
+
chalk31.cyan(
|
|
1856
2271
|
` 2. Share common utilities: If the extracted code can be reused across multiple
|
|
1857
2272
|
domains, move it to a common/shared folder.
|
|
1858
2273
|
`
|
|
@@ -1863,7 +2278,7 @@ Refactor check failed:
|
|
|
1863
2278
|
|
|
1864
2279
|
// src/commands/refactor/getViolations.ts
|
|
1865
2280
|
function countLines(filePath) {
|
|
1866
|
-
const content =
|
|
2281
|
+
const content = fs15.readFileSync(filePath, "utf-8");
|
|
1867
2282
|
return content.split("\n").length;
|
|
1868
2283
|
}
|
|
1869
2284
|
function getGitFiles(options) {
|
|
@@ -1892,7 +2307,7 @@ function getViolations(pattern2, options = {}, maxLines = DEFAULT_MAX_LINES) {
|
|
|
1892
2307
|
const ignoredFiles = getIgnoredFiles();
|
|
1893
2308
|
const gitFiles = getGitFiles(options);
|
|
1894
2309
|
if (pattern2) {
|
|
1895
|
-
sourceFiles = sourceFiles.filter((f) =>
|
|
2310
|
+
sourceFiles = sourceFiles.filter((f) => minimatch2(f, pattern2));
|
|
1896
2311
|
}
|
|
1897
2312
|
if (gitFiles) {
|
|
1898
2313
|
sourceFiles = sourceFiles.filter((f) => gitFiles.has(f));
|
|
@@ -1915,7 +2330,7 @@ async function runVerifyQuietly() {
|
|
|
1915
2330
|
return true;
|
|
1916
2331
|
}
|
|
1917
2332
|
const { packageJsonPath, verifyScripts } = result;
|
|
1918
|
-
const packageDir =
|
|
2333
|
+
const packageDir = path15.dirname(packageJsonPath);
|
|
1919
2334
|
const results = await Promise.all(
|
|
1920
2335
|
verifyScripts.map(
|
|
1921
2336
|
(script) => new Promise(
|
|
@@ -1968,28 +2383,28 @@ async function check(pattern2, options) {
|
|
|
1968
2383
|
}
|
|
1969
2384
|
|
|
1970
2385
|
// src/commands/refactor/ignore.ts
|
|
1971
|
-
import
|
|
1972
|
-
import
|
|
2386
|
+
import fs16 from "fs";
|
|
2387
|
+
import chalk32 from "chalk";
|
|
1973
2388
|
var REFACTOR_YML_PATH2 = "refactor.yml";
|
|
1974
2389
|
function ignore(file) {
|
|
1975
|
-
if (!
|
|
1976
|
-
console.error(
|
|
2390
|
+
if (!fs16.existsSync(file)) {
|
|
2391
|
+
console.error(chalk32.red(`Error: File does not exist: ${file}`));
|
|
1977
2392
|
process.exit(1);
|
|
1978
2393
|
}
|
|
1979
|
-
const content =
|
|
2394
|
+
const content = fs16.readFileSync(file, "utf-8");
|
|
1980
2395
|
const lineCount = content.split("\n").length;
|
|
1981
2396
|
const maxLines = lineCount + 10;
|
|
1982
2397
|
const entry = `- file: ${file}
|
|
1983
2398
|
maxLines: ${maxLines}
|
|
1984
2399
|
`;
|
|
1985
|
-
if (
|
|
1986
|
-
const existing =
|
|
1987
|
-
|
|
2400
|
+
if (fs16.existsSync(REFACTOR_YML_PATH2)) {
|
|
2401
|
+
const existing = fs16.readFileSync(REFACTOR_YML_PATH2, "utf-8");
|
|
2402
|
+
fs16.writeFileSync(REFACTOR_YML_PATH2, existing + entry);
|
|
1988
2403
|
} else {
|
|
1989
|
-
|
|
2404
|
+
fs16.writeFileSync(REFACTOR_YML_PATH2, entry);
|
|
1990
2405
|
}
|
|
1991
2406
|
console.log(
|
|
1992
|
-
|
|
2407
|
+
chalk32.green(
|
|
1993
2408
|
`Added ${file} to refactor ignore list (max ${maxLines} lines)`
|
|
1994
2409
|
)
|
|
1995
2410
|
);
|
|
@@ -2080,31 +2495,31 @@ async function statusLine() {
|
|
|
2080
2495
|
}
|
|
2081
2496
|
|
|
2082
2497
|
// src/commands/sync.ts
|
|
2083
|
-
import * as
|
|
2498
|
+
import * as fs18 from "fs";
|
|
2084
2499
|
import * as os from "os";
|
|
2085
|
-
import * as
|
|
2500
|
+
import * as path17 from "path";
|
|
2086
2501
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
2087
2502
|
|
|
2088
2503
|
// src/commands/sync/syncSettings.ts
|
|
2089
|
-
import * as
|
|
2090
|
-
import * as
|
|
2091
|
-
import
|
|
2504
|
+
import * as fs17 from "fs";
|
|
2505
|
+
import * as path16 from "path";
|
|
2506
|
+
import chalk33 from "chalk";
|
|
2092
2507
|
async function syncSettings(claudeDir, targetBase) {
|
|
2093
|
-
const source =
|
|
2094
|
-
const target =
|
|
2095
|
-
const sourceContent =
|
|
2508
|
+
const source = path16.join(claudeDir, "settings.json");
|
|
2509
|
+
const target = path16.join(targetBase, "settings.json");
|
|
2510
|
+
const sourceContent = fs17.readFileSync(source, "utf-8");
|
|
2096
2511
|
const normalizedSource = JSON.stringify(JSON.parse(sourceContent), null, 2);
|
|
2097
|
-
if (
|
|
2098
|
-
const targetContent =
|
|
2512
|
+
if (fs17.existsSync(target)) {
|
|
2513
|
+
const targetContent = fs17.readFileSync(target, "utf-8");
|
|
2099
2514
|
const normalizedTarget = JSON.stringify(JSON.parse(targetContent), null, 2);
|
|
2100
2515
|
if (normalizedSource !== normalizedTarget) {
|
|
2101
2516
|
console.log(
|
|
2102
|
-
|
|
2517
|
+
chalk33.yellow("\n\u26A0\uFE0F Warning: settings.json differs from existing file")
|
|
2103
2518
|
);
|
|
2104
2519
|
console.log();
|
|
2105
2520
|
printDiff(targetContent, sourceContent);
|
|
2106
2521
|
const confirm = await promptConfirm(
|
|
2107
|
-
|
|
2522
|
+
chalk33.red("Overwrite existing settings.json?"),
|
|
2108
2523
|
false
|
|
2109
2524
|
);
|
|
2110
2525
|
if (!confirm) {
|
|
@@ -2113,26 +2528,26 @@ async function syncSettings(claudeDir, targetBase) {
|
|
|
2113
2528
|
}
|
|
2114
2529
|
}
|
|
2115
2530
|
}
|
|
2116
|
-
|
|
2531
|
+
fs17.copyFileSync(source, target);
|
|
2117
2532
|
console.log("Copied settings.json to ~/.claude/settings.json");
|
|
2118
2533
|
}
|
|
2119
2534
|
|
|
2120
2535
|
// src/commands/sync.ts
|
|
2121
2536
|
var __filename2 = fileURLToPath4(import.meta.url);
|
|
2122
|
-
var __dirname5 =
|
|
2537
|
+
var __dirname5 = path17.dirname(__filename2);
|
|
2123
2538
|
async function sync() {
|
|
2124
|
-
const claudeDir =
|
|
2125
|
-
const targetBase =
|
|
2539
|
+
const claudeDir = path17.join(__dirname5, "..", "claude");
|
|
2540
|
+
const targetBase = path17.join(os.homedir(), ".claude");
|
|
2126
2541
|
syncCommands(claudeDir, targetBase);
|
|
2127
2542
|
await syncSettings(claudeDir, targetBase);
|
|
2128
2543
|
}
|
|
2129
2544
|
function syncCommands(claudeDir, targetBase) {
|
|
2130
|
-
const sourceDir =
|
|
2131
|
-
const targetDir =
|
|
2132
|
-
|
|
2133
|
-
const files =
|
|
2545
|
+
const sourceDir = path17.join(claudeDir, "commands");
|
|
2546
|
+
const targetDir = path17.join(targetBase, "commands");
|
|
2547
|
+
fs18.mkdirSync(targetDir, { recursive: true });
|
|
2548
|
+
const files = fs18.readdirSync(sourceDir);
|
|
2134
2549
|
for (const file of files) {
|
|
2135
|
-
|
|
2550
|
+
fs18.copyFileSync(path17.join(sourceDir, file), path17.join(targetDir, file));
|
|
2136
2551
|
console.log(`Copied ${file} to ${targetDir}`);
|
|
2137
2552
|
}
|
|
2138
2553
|
console.log(`Synced ${files.length} command(s) to ~/.claude/commands`);
|
|
@@ -2173,7 +2588,7 @@ Total: ${lines.length} hardcoded color(s)`);
|
|
|
2173
2588
|
|
|
2174
2589
|
// src/commands/verify/run.ts
|
|
2175
2590
|
import { spawn as spawn4 } from "child_process";
|
|
2176
|
-
import * as
|
|
2591
|
+
import * as path18 from "path";
|
|
2177
2592
|
function formatDuration(ms) {
|
|
2178
2593
|
if (ms < 1e3) {
|
|
2179
2594
|
return `${ms}ms`;
|
|
@@ -2203,7 +2618,7 @@ async function run2(options = {}) {
|
|
|
2203
2618
|
return;
|
|
2204
2619
|
}
|
|
2205
2620
|
const { packageJsonPath, verifyScripts } = result;
|
|
2206
|
-
const packageDir =
|
|
2621
|
+
const packageDir = path18.dirname(packageJsonPath);
|
|
2207
2622
|
console.log(`Running ${verifyScripts.length} verify script(s) in parallel:`);
|
|
2208
2623
|
for (const script of verifyScripts) {
|
|
2209
2624
|
console.log(` - ${script}`);
|
|
@@ -2292,4 +2707,13 @@ program.command("status-line").description("Format Claude Code status line from
|
|
|
2292
2707
|
program.command("notify").description(
|
|
2293
2708
|
"Show notification from Claude Code hook (reads JSON from stdin)"
|
|
2294
2709
|
).action(notify);
|
|
2710
|
+
var complexityCommand = program.command("complexity").description("Analyze TypeScript code complexity metrics");
|
|
2711
|
+
complexityCommand.command("cyclomatic [pattern]").description("Calculate cyclomatic complexity per function").option("--threshold <number>", "Max complexity threshold", Number.parseInt).action(cyclomatic);
|
|
2712
|
+
complexityCommand.command("halstead [pattern]").description("Calculate Halstead metrics per function").option("--threshold <number>", "Max volume threshold", Number.parseInt).action(halstead);
|
|
2713
|
+
complexityCommand.command("maintainability [pattern]").description("Calculate maintainability index per file").option(
|
|
2714
|
+
"--threshold <number>",
|
|
2715
|
+
"Min maintainability threshold",
|
|
2716
|
+
Number.parseInt
|
|
2717
|
+
).action(maintainability);
|
|
2718
|
+
complexityCommand.command("sloc [pattern]").description("Count source lines of code per file").option("--threshold <number>", "Max lines threshold", Number.parseInt).action(sloc);
|
|
2295
2719
|
program.parse();
|