@jvittechs/jai1-cli 0.1.105 → 1.0.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/cli.js +1492 -785
- package/dist/cli.js.map +1 -1
- package/package.json +13 -12
- package/scripts/redmine-sync-issue.sh +0 -0
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
|
-
import { Command as
|
|
4
|
+
import { Command as Command61 } from "commander";
|
|
5
5
|
|
|
6
6
|
// src/errors/index.ts
|
|
7
7
|
var Jai1Error = class extends Error {
|
|
@@ -33,7 +33,7 @@ var NetworkError = class extends Jai1Error {
|
|
|
33
33
|
// package.json
|
|
34
34
|
var package_default = {
|
|
35
35
|
name: "@jvittechs/jai1-cli",
|
|
36
|
-
version: "0.
|
|
36
|
+
version: "1.0.0",
|
|
37
37
|
description: "A unified CLI tool for JV-IT TECHS developers to manage Jai1 Framework. Please contact TeamAI for usage instructions.",
|
|
38
38
|
type: "module",
|
|
39
39
|
bin: {
|
|
@@ -101,6 +101,7 @@ var package_default = {
|
|
|
101
101
|
marked: "^12.0.0",
|
|
102
102
|
"marked-terminal": "^7.0.0",
|
|
103
103
|
open: "^10.1.0",
|
|
104
|
+
ora: "^9.0.0",
|
|
104
105
|
"p-limit": "^5.0.0",
|
|
105
106
|
"p-queue": "^7.4.1",
|
|
106
107
|
"p-retry": "^6.2.0",
|
|
@@ -479,6 +480,34 @@ var ComponentsService = class {
|
|
|
479
480
|
this.cacheDir = join2(homedir3(), ".jai1", "cache");
|
|
480
481
|
this.manifestFile = join2(projectRoot, ".jai1", "manifest.json");
|
|
481
482
|
}
|
|
483
|
+
/**
|
|
484
|
+
* Expand component paths with special prefixes
|
|
485
|
+
*
|
|
486
|
+
* Supported formats:
|
|
487
|
+
* - Standard paths: "rule-presets/react-spa-zustand", "rules/jai1.md", "workflows/commit-it.md"
|
|
488
|
+
* - Package prefix: "package:core" -> expands to all components in that package
|
|
489
|
+
*
|
|
490
|
+
* @param config - Jai1 config
|
|
491
|
+
* @param paths - Array of component paths (may include special prefixes)
|
|
492
|
+
* @returns Array of expanded component paths
|
|
493
|
+
*/
|
|
494
|
+
async expandPaths(config, paths) {
|
|
495
|
+
const expandedPaths = [];
|
|
496
|
+
for (const path13 of paths) {
|
|
497
|
+
if (path13.startsWith("package:")) {
|
|
498
|
+
const packageName = path13.substring("package:".length);
|
|
499
|
+
const components = await this.list(config);
|
|
500
|
+
if (packageName === "core") {
|
|
501
|
+
expandedPaths.push(...components.map((c) => c.filepath));
|
|
502
|
+
} else {
|
|
503
|
+
console.warn(`Warning: Unknown package '${packageName}', skipping`);
|
|
504
|
+
}
|
|
505
|
+
} else {
|
|
506
|
+
expandedPaths.push(path13);
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
return expandedPaths;
|
|
510
|
+
}
|
|
482
511
|
/**
|
|
483
512
|
* List components from API
|
|
484
513
|
*/
|
|
@@ -546,9 +575,6 @@ var ComponentsService = class {
|
|
|
546
575
|
if (!component.content) {
|
|
547
576
|
throw new Error(`Component ${filepath} has no content`);
|
|
548
577
|
}
|
|
549
|
-
const targetPath = join2(targetDir, filepath);
|
|
550
|
-
const targetFolder = join2(targetPath, "..");
|
|
551
|
-
await fs2.mkdir(targetFolder, { recursive: true });
|
|
552
578
|
let checksumContent = component.content;
|
|
553
579
|
if (component.contentType === "bundle" || component.contentType === "zip") {
|
|
554
580
|
let bundleJson;
|
|
@@ -593,6 +619,9 @@ var ComponentsService = class {
|
|
|
593
619
|
if (component.contentType === "markdown") {
|
|
594
620
|
checksumContent = component.content;
|
|
595
621
|
}
|
|
622
|
+
const targetPath = join2(targetDir, filepath);
|
|
623
|
+
const targetFolder = join2(targetPath, "..");
|
|
624
|
+
await fs2.mkdir(targetFolder, { recursive: true });
|
|
596
625
|
await fs2.writeFile(targetPath, component.content);
|
|
597
626
|
}
|
|
598
627
|
await this.markInstalled(filepath, component.version, this.calculateChecksum(checksumContent));
|
|
@@ -1358,12 +1387,18 @@ var UnifiedApplyApp = ({
|
|
|
1358
1387
|
setAvailableIdes(getMigrationIDEs());
|
|
1359
1388
|
}, []);
|
|
1360
1389
|
const filteredComponents = useMemo(() => {
|
|
1390
|
+
if (focusArea === "packages" && tags.length > 0) {
|
|
1391
|
+
const selectedTag = tags[selectedPackageIndex];
|
|
1392
|
+
if (selectedTag) {
|
|
1393
|
+
return components.filter((c) => c.tags?.includes(selectedTag.tag));
|
|
1394
|
+
}
|
|
1395
|
+
}
|
|
1361
1396
|
if (!searchQuery.trim()) return components;
|
|
1362
1397
|
const query = searchQuery.toLowerCase();
|
|
1363
1398
|
return components.filter(
|
|
1364
1399
|
(c) => c.filepath.toLowerCase().includes(query) || c.tags?.some((t) => t.toLowerCase().includes(query))
|
|
1365
1400
|
);
|
|
1366
|
-
}, [components, searchQuery]);
|
|
1401
|
+
}, [components, searchQuery, focusArea, selectedPackageIndex, tags]);
|
|
1367
1402
|
useInput((input5, key) => {
|
|
1368
1403
|
if (viewState === "done") {
|
|
1369
1404
|
if (key.return || input5 === "q" || key.escape) {
|
|
@@ -1479,7 +1514,6 @@ var UnifiedApplyApp = ({
|
|
|
1479
1514
|
packageComponents.forEach((c) => next.add(c.filepath));
|
|
1480
1515
|
return next;
|
|
1481
1516
|
});
|
|
1482
|
-
setSearchQuery(tag.tag);
|
|
1483
1517
|
}
|
|
1484
1518
|
}
|
|
1485
1519
|
}
|
|
@@ -1600,7 +1634,7 @@ var UnifiedApplyApp = ({
|
|
|
1600
1634
|
onChange: setSearchQuery,
|
|
1601
1635
|
placeholder: "Type to filter..."
|
|
1602
1636
|
}
|
|
1603
|
-
) : /* @__PURE__ */ React3.createElement(Text3, null, searchQuery || /* @__PURE__ */ React3.createElement(Text3, { dimColor: true }, "Press Tab to search"))), /* @__PURE__ */ React3.createElement(Box2, { marginBottom: 1, flexDirection: "column" }, /* @__PURE__ */ React3.createElement(Text3, { bold: true, dimColor: true }, "Quick Apply (Packages):"), /* @__PURE__ */ React3.createElement(Box2, { marginTop: 1, flexWrap: "wrap" }, tags.
|
|
1637
|
+
) : /* @__PURE__ */ React3.createElement(Text3, null, searchQuery || /* @__PURE__ */ React3.createElement(Text3, { dimColor: true }, "Press Tab to search"))), /* @__PURE__ */ React3.createElement(Box2, { marginBottom: 1, flexDirection: "column" }, /* @__PURE__ */ React3.createElement(Text3, { bold: true, dimColor: true }, "Quick Apply (Packages):"), /* @__PURE__ */ React3.createElement(Box2, { marginTop: 1, flexWrap: "wrap" }, tags.map((tag, i) => {
|
|
1604
1638
|
const isSelected = focusArea === "packages" && i === selectedPackageIndex;
|
|
1605
1639
|
return /* @__PURE__ */ React3.createElement(Box2, { key: tag.tag, marginRight: 1 }, /* @__PURE__ */ React3.createElement(
|
|
1606
1640
|
Text3,
|
|
@@ -1614,12 +1648,12 @@ var UnifiedApplyApp = ({
|
|
|
1614
1648
|
tag.count,
|
|
1615
1649
|
"]"
|
|
1616
1650
|
));
|
|
1617
|
-
}))), /* @__PURE__ */ React3.createElement(Box2, { flexDirection: "column", borderStyle: "round", borderColor: focusArea === "components" ? "cyan" : "gray", padding: 1 }, /* @__PURE__ */ React3.createElement(Box2, { marginBottom: 1 }, /* @__PURE__ */ React3.createElement(Text3, { bold: true }, "Components "), /* @__PURE__ */ React3.createElement(Text3, { dimColor: true }, "(", filteredComponents.length, " shown", hasMore ? `, scroll for more` : "", ")")), visibleComponents.map((comp, i) => {
|
|
1651
|
+
})), focusArea === "packages" && tags.length > 0 && /* @__PURE__ */ React3.createElement(Box2, { marginTop: 1 }, /* @__PURE__ */ React3.createElement(Text3, { dimColor: true }, "\u2190 \u2192 to browse packages \xB7 Space/Enter to select package"))), /* @__PURE__ */ React3.createElement(Box2, { flexDirection: "column", borderStyle: "round", borderColor: focusArea === "components" ? "cyan" : "gray", padding: 1 }, /* @__PURE__ */ React3.createElement(Box2, { marginBottom: 1 }, /* @__PURE__ */ React3.createElement(Text3, { bold: true }, "Components "), focusArea === "packages" && tags.length > 0 && /* @__PURE__ */ React3.createElement(Text3, { color: "cyan" }, "[", tags[selectedPackageIndex]?.tag, "] "), /* @__PURE__ */ React3.createElement(Text3, { dimColor: true }, "(", filteredComponents.length, " shown", hasMore ? `, scroll for more` : "", ")")), visibleComponents.map((comp, i) => {
|
|
1618
1652
|
const isCursor = i === cursorIndex && focusArea === "components";
|
|
1619
1653
|
const isChecked = selectedPaths.has(comp.filepath);
|
|
1620
1654
|
const isInstalled = installedPaths.has(comp.filepath);
|
|
1621
1655
|
return /* @__PURE__ */ React3.createElement(Box2, { key: comp.filepath }, /* @__PURE__ */ React3.createElement(Text3, { color: isCursor ? "cyan" : "white" }, isCursor ? "\u276F " : " ", isChecked ? "[\u2713]" : "[ ]", " ", comp.filepath), /* @__PURE__ */ React3.createElement(Text3, { dimColor: true }, " ", isInstalled ? "\u2713 installed" : "\u25CB new"));
|
|
1622
|
-
}), filteredComponents.length === 0 && /* @__PURE__ */ React3.createElement(Text3, { dimColor: true }, "No components match your search")), selectedPaths.size > 0 && /* @__PURE__ */ React3.createElement(Box2, { marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React3.createElement(Text3, { bold: true }, "Selected: ", selectedPaths.size, " components"), /* @__PURE__ */ React3.createElement(Box2, { flexDirection: "column", marginLeft: 2 }, Array.from(selectedPaths).slice(0, 4).map((fp) => /* @__PURE__ */ React3.createElement(Text3, { key: fp, dimColor: true }, "\u{1F4CC} ", fp)), selectedPaths.size > 4 && /* @__PURE__ */ React3.createElement(Text3, { dimColor: true }, " ... and ", selectedPaths.size - 4, " more"))), /* @__PURE__ */ React3.createElement(Box2, { marginTop: 1, borderStyle: "single", borderColor: "gray", paddingX: 1 }, /* @__PURE__ */ React3.createElement(Text3, { dimColor: true }, "[
|
|
1656
|
+
}), filteredComponents.length === 0 && /* @__PURE__ */ React3.createElement(Text3, { dimColor: true }, "No components match your search")), selectedPaths.size > 0 && /* @__PURE__ */ React3.createElement(Box2, { marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React3.createElement(Text3, { bold: true }, "Selected: ", selectedPaths.size, " components"), /* @__PURE__ */ React3.createElement(Box2, { flexDirection: "column", marginLeft: 2 }, Array.from(selectedPaths).slice(0, 4).map((fp) => /* @__PURE__ */ React3.createElement(Text3, { key: fp, dimColor: true }, "\u{1F4CC} ", fp)), selectedPaths.size > 4 && /* @__PURE__ */ React3.createElement(Text3, { dimColor: true }, " ... and ", selectedPaths.size - 4, " more"))), /* @__PURE__ */ React3.createElement(Box2, { marginTop: 1, borderStyle: "single", borderColor: "gray", paddingX: 1 }, /* @__PURE__ */ React3.createElement(Text3, { dimColor: true }, focusArea === "packages" && "[\u2190\u2192] Browse packages \xB7 [\u2423/Enter] Select package", focusArea === "components" && "[\u2191\u2193] Navigate \xB7 [\u2423] Toggle", focusArea === "search" && "Type to search...", " \xB7 [Tab] Switch area \xB7 [A] All \xB7 [C] Clear \xB7 [Enter] Apply \xB7 [Q] Quit")));
|
|
1623
1657
|
};
|
|
1624
1658
|
|
|
1625
1659
|
// src/commands/apply.ts
|
|
@@ -1966,7 +2000,7 @@ var MainMenuView = ({ ideContexts, onSelect }) => {
|
|
|
1966
2000
|
{
|
|
1967
2001
|
ide: "antigravity",
|
|
1968
2002
|
icon: "\u{1F680}",
|
|
1969
|
-
title: "Antigravity
|
|
2003
|
+
title: "Antigravity",
|
|
1970
2004
|
itemCount: ideContexts.find((ctx) => ctx.ide === "antigravity")?.stats.totalItems || 0,
|
|
1971
2005
|
available: ideContexts.some((ctx) => ctx.ide === "antigravity")
|
|
1972
2006
|
},
|
|
@@ -2293,7 +2327,7 @@ var IDE_CONFIGS = {
|
|
|
2293
2327
|
},
|
|
2294
2328
|
antigravity: {
|
|
2295
2329
|
id: "antigravity",
|
|
2296
|
-
name: "Antigravity
|
|
2330
|
+
name: "Antigravity",
|
|
2297
2331
|
icon: "\u{1F680}",
|
|
2298
2332
|
basePath: ".agent",
|
|
2299
2333
|
contentPaths: {
|
|
@@ -2794,6 +2828,25 @@ import { checkbox, confirm as confirm2, select } from "@inquirer/prompts";
|
|
|
2794
2828
|
import fs6 from "fs/promises";
|
|
2795
2829
|
import path3 from "path";
|
|
2796
2830
|
import { existsSync } from "fs";
|
|
2831
|
+
|
|
2832
|
+
// src/utils/prompt-theme.ts
|
|
2833
|
+
var keysHelpTipWithQuit = (keys) => {
|
|
2834
|
+
const keyStrings = keys.map(([key, action]) => `${key} ${action}`);
|
|
2835
|
+
keyStrings.push("Ctrl+C quit");
|
|
2836
|
+
return keyStrings.join(" \u2022 ");
|
|
2837
|
+
};
|
|
2838
|
+
var checkboxTheme = {
|
|
2839
|
+
style: {
|
|
2840
|
+
keysHelpTip: keysHelpTipWithQuit
|
|
2841
|
+
}
|
|
2842
|
+
};
|
|
2843
|
+
var selectTheme = {
|
|
2844
|
+
style: {
|
|
2845
|
+
keysHelpTip: keysHelpTipWithQuit
|
|
2846
|
+
}
|
|
2847
|
+
};
|
|
2848
|
+
|
|
2849
|
+
// src/commands/ide/setup.ts
|
|
2797
2850
|
var PERFORMANCE_GROUPS = {
|
|
2798
2851
|
telemetry: {
|
|
2799
2852
|
name: "Telemetry",
|
|
@@ -2980,7 +3033,8 @@ async function interactiveMode() {
|
|
|
2980
3033
|
{ name: "\u274C Disable optimization groups", value: "disable" },
|
|
2981
3034
|
{ name: "\u{1F680} Max Performance (enable all)", value: "max" },
|
|
2982
3035
|
{ name: "\u{1F504} Reset to defaults", value: "reset" }
|
|
2983
|
-
]
|
|
3036
|
+
],
|
|
3037
|
+
theme: selectTheme
|
|
2984
3038
|
});
|
|
2985
3039
|
if (action === "max") {
|
|
2986
3040
|
const allGroups = Object.keys(PERFORMANCE_GROUPS);
|
|
@@ -2999,7 +3053,8 @@ async function selectGroupsToApply(action) {
|
|
|
2999
3053
|
try {
|
|
3000
3054
|
const selectedGroups = await checkbox({
|
|
3001
3055
|
message: `Select groups to ${action} (SPACE to select, ENTER to confirm):`,
|
|
3002
|
-
choices
|
|
3056
|
+
choices,
|
|
3057
|
+
theme: checkboxTheme
|
|
3003
3058
|
});
|
|
3004
3059
|
if (selectedGroups.length === 0) {
|
|
3005
3060
|
console.log("\n\u26A0\uFE0F No groups selected!");
|
|
@@ -3135,7 +3190,8 @@ async function runSync(options) {
|
|
|
3135
3190
|
});
|
|
3136
3191
|
selectedIdes = await checkbox2({
|
|
3137
3192
|
message: "Select target IDE(s) (SPACE to select, ENTER to confirm):",
|
|
3138
|
-
choices: ideChoices
|
|
3193
|
+
choices: ideChoices,
|
|
3194
|
+
theme: checkboxTheme
|
|
3139
3195
|
});
|
|
3140
3196
|
if (selectedIdes.length === 0) {
|
|
3141
3197
|
console.log("\n\u26A0\uFE0F No IDE selected!");
|
|
@@ -3154,7 +3210,8 @@ async function runSync(options) {
|
|
|
3154
3210
|
];
|
|
3155
3211
|
selectedTypes = await checkbox2({
|
|
3156
3212
|
message: "Select content types to sync:",
|
|
3157
|
-
choices: typeChoices
|
|
3213
|
+
choices: typeChoices,
|
|
3214
|
+
theme: checkboxTheme
|
|
3158
3215
|
});
|
|
3159
3216
|
if (selectedTypes.length === 0) {
|
|
3160
3217
|
console.log("\n\u26A0\uFE0F No content type selected!");
|
|
@@ -3280,78 +3337,6 @@ var IDE_FORMATS = {
|
|
|
3280
3337
|
// Gemini requires AGENTS.md
|
|
3281
3338
|
}
|
|
3282
3339
|
};
|
|
3283
|
-
var JAI1_BASE_RULE = `# Jai1 Framework Agent
|
|
3284
|
-
|
|
3285
|
-
You are an AI coding assistant integrated with the Jai1 Framework.
|
|
3286
|
-
|
|
3287
|
-
## Skills Reference
|
|
3288
|
-
|
|
3289
|
-
The Jai1 Framework provides specialized skills that you can reference and apply:
|
|
3290
|
-
|
|
3291
|
-
### Pattern Detection
|
|
3292
|
-
|
|
3293
|
-
When you encounter:
|
|
3294
|
-
- **Component creation**: Reference \`@jai1-component-patterns\`
|
|
3295
|
-
- **API development**: Reference \`@jai1-api-patterns\`
|
|
3296
|
-
- **Database operations**: Reference \`@jai1-database-patterns\`
|
|
3297
|
-
- **Testing**: Reference \`@jai1-testing-patterns\`
|
|
3298
|
-
- **Deployment**: Reference \`@jai1-deployment-patterns\`
|
|
3299
|
-
|
|
3300
|
-
### Skill Loading Procedure
|
|
3301
|
-
|
|
3302
|
-
1. **Detect the task type** from user request
|
|
3303
|
-
2. **Load relevant skill** using \`@jai1-[skill-name]\` syntax
|
|
3304
|
-
3. **Apply patterns** from the skill to your response
|
|
3305
|
-
4. **Follow conventions** defined in the skill
|
|
3306
|
-
|
|
3307
|
-
Example:
|
|
3308
|
-
\`\`\`
|
|
3309
|
-
User: "Create a new API endpoint for users"
|
|
3310
|
-
Assistant:
|
|
3311
|
-
1. Loading @jai1-api-patterns
|
|
3312
|
-
2. Applying RESTful conventions
|
|
3313
|
-
3. Following project structure from @jai1-component-patterns
|
|
3314
|
-
4. Implementing validation using @jai1-testing-patterns
|
|
3315
|
-
\`\`\`
|
|
3316
|
-
|
|
3317
|
-
### Application
|
|
3318
|
-
|
|
3319
|
-
- **Always reference skills** before implementing patterns
|
|
3320
|
-
- **Follow naming conventions** from loaded skills
|
|
3321
|
-
- **Apply best practices** defined in skills
|
|
3322
|
-
- **Maintain consistency** with existing codebase patterns
|
|
3323
|
-
|
|
3324
|
-
### Naming Convention
|
|
3325
|
-
|
|
3326
|
-
Skills are named using the pattern: \`@jai1-[domain]-patterns\`
|
|
3327
|
-
|
|
3328
|
-
Examples:
|
|
3329
|
-
- \`@jai1-component-patterns\`
|
|
3330
|
-
- \`@jai1-api-patterns\`
|
|
3331
|
-
- \`@jai1-database-patterns\`
|
|
3332
|
-
- \`@jai1-testing-patterns\`
|
|
3333
|
-
- \`@jai1-deployment-patterns\`
|
|
3334
|
-
|
|
3335
|
-
## Best Practices
|
|
3336
|
-
|
|
3337
|
-
1. **Load before applying**: Always reference the appropriate skill before implementing
|
|
3338
|
-
2. **Follow project structure**: Use patterns from \`@jai1-component-patterns\`
|
|
3339
|
-
3. **Maintain consistency**: Apply conventions consistently across the codebase
|
|
3340
|
-
4. **Document decisions**: Explain why specific patterns are used
|
|
3341
|
-
|
|
3342
|
-
## Integration
|
|
3343
|
-
|
|
3344
|
-
This base rule works in conjunction with:
|
|
3345
|
-
- Project-specific rules (e.g., 01-project.md)
|
|
3346
|
-
- Technology-specific rules (e.g., 03-frontend.md)
|
|
3347
|
-
- Custom rules (e.g., 09-custom.md)
|
|
3348
|
-
- AGENTS.md (if present)
|
|
3349
|
-
|
|
3350
|
-
When conflicts arise, prioritize:
|
|
3351
|
-
1. Custom rules (09-custom.*)
|
|
3352
|
-
2. Project rules (01-project.*)
|
|
3353
|
-
3. This base rule (00-jai1.*)
|
|
3354
|
-
`;
|
|
3355
3340
|
|
|
3356
3341
|
// src/services/ide-detection.service.ts
|
|
3357
3342
|
var IdeDetectionService = class {
|
|
@@ -3507,9 +3492,9 @@ var IdeDetectionService = class {
|
|
|
3507
3492
|
/**
|
|
3508
3493
|
* Check if a path exists
|
|
3509
3494
|
*/
|
|
3510
|
-
async pathExists(
|
|
3495
|
+
async pathExists(path13) {
|
|
3511
3496
|
try {
|
|
3512
|
-
await fs7.access(
|
|
3497
|
+
await fs7.access(path13);
|
|
3513
3498
|
return true;
|
|
3514
3499
|
} catch {
|
|
3515
3500
|
return false;
|
|
@@ -4401,6 +4386,27 @@ AI: \u0110\u1ECDc rules \u2192 Bi\u1EBFt d\xF9ng HonoJS, c\u1EA5u tr\xFAc th\u01
|
|
|
4401
4386
|
|
|
4402
4387
|
> \u{1F4A1} **Tip:** Context l\xE0 ch\xECa kh\xF3a \u0111\u1EC3 AI l\xE0m vi\u1EC7c hi\u1EC7u qu\u1EA3!
|
|
4403
4388
|
|
|
4389
|
+
## Y\xEAu c\u1EA7u m\xF4i tr\u01B0\u1EDDng
|
|
4390
|
+
|
|
4391
|
+
### Windows
|
|
4392
|
+
|
|
4393
|
+
\u26A0\uFE0F **B\u1EAFt bu\u1ED9c:** C\xE0i \u0111\u1EB7t **Git for Windows** (bao g\u1ED3m Git Bash)
|
|
4394
|
+
|
|
4395
|
+
Workflows c\u1EE7a Jai1 s\u1EED d\u1EE5ng c\xE1c Unix commands (\`grep\`, \`wc\`, \`xargs\`...) kh\xF4ng c\xF3 s\u1EB5n tr\xEAn PowerShell/CMD.
|
|
4396
|
+
|
|
4397
|
+
1. Download: https://git-scm.com/download/win
|
|
4398
|
+
2. Khi c\xE0i \u0111\u1EB7t, ch\u1ECDn **"Git Bash Here"** context menu
|
|
4399
|
+
3. Trong VSCode, c\u1EA5u h\xECnh terminal m\u1EB7c \u0111\u1ECBnh:
|
|
4400
|
+
\`\`\`json
|
|
4401
|
+
{
|
|
4402
|
+
"terminal.integrated.defaultProfile.windows": "Git Bash"
|
|
4403
|
+
}
|
|
4404
|
+
\`\`\`
|
|
4405
|
+
|
|
4406
|
+
### macOS / Linux
|
|
4407
|
+
|
|
4408
|
+
\u2705 S\u1EB5n s\xE0ng - C\xE1c Unix commands \u0111\xE3 c\xF3 s\u1EB5n trong terminal.
|
|
4409
|
+
|
|
4404
4410
|
## B\u1EAFt \u0111\u1EA7u nhanh
|
|
4405
4411
|
|
|
4406
4412
|
1. **Kh\u1EDFi t\u1EA1o Jai1** trong d\u1EF1 \xE1n:
|
|
@@ -4598,6 +4604,52 @@ V\xED d\u1EE5 minh h\u1ECDa (n\u1EBFu c\u1EA7n)
|
|
|
4598
4604
|
- G\u1ECDi khi c\u1EA7n b\u1EB1ng \`@ruleName\`
|
|
4599
4605
|
- V\xED d\u1EE5: \`@database-migration\`, \`@api-design\`
|
|
4600
4606
|
|
|
4607
|
+
## Qu\u1EA3n l\xFD Rules v\u1EDBi CLI
|
|
4608
|
+
|
|
4609
|
+
### Apply Rules Preset
|
|
4610
|
+
|
|
4611
|
+
\`\`\`bash
|
|
4612
|
+
# Apply preset c\xF3 s\u1EB5n
|
|
4613
|
+
jai1 rules apply nextjs-saas
|
|
4614
|
+
|
|
4615
|
+
# Apply cho nhi\u1EC1u IDE c\xF9ng l\xFAc
|
|
4616
|
+
jai1 rules apply nextjs-saas --ides cursor,windsurf,agentsmd
|
|
4617
|
+
\`\`\`
|
|
4618
|
+
|
|
4619
|
+
### Sync Rules (NEW!)
|
|
4620
|
+
|
|
4621
|
+
Sau khi apply rules, b\u1EA1n c\xF3 th\u1EC3 sync sang IDE kh\xE1c ho\u1EB7c regenerate:
|
|
4622
|
+
|
|
4623
|
+
\`\`\`bash
|
|
4624
|
+
# Interactive mode - ch\u1ECDn IDE t\u1EEB menu
|
|
4625
|
+
jai1 rules sync
|
|
4626
|
+
|
|
4627
|
+
# Auto-detect active IDEs
|
|
4628
|
+
jai1 rules sync --detect
|
|
4629
|
+
|
|
4630
|
+
# Sync specific IDEs
|
|
4631
|
+
jai1 rules sync --ides cursor,windsurf
|
|
4632
|
+
|
|
4633
|
+
# Auto mode (no prompts)
|
|
4634
|
+
jai1 rules sync -y
|
|
4635
|
+
\`\`\`
|
|
4636
|
+
|
|
4637
|
+
**Interactive Mode Flow:**
|
|
4638
|
+
1. Hi\u1EC3n th\u1ECB current IDE configuration
|
|
4639
|
+
2. Smart suggestions d\u1EF1a tr\xEAn project structure
|
|
4640
|
+
3. Checkbox \u0111\u1EC3 ch\u1ECDn IDE (pre-selected current + suggested)
|
|
4641
|
+
4. Regenerate rules cho c\xE1c IDE \u0111\xE3 ch\u1ECDn
|
|
4642
|
+
|
|
4643
|
+
### Restore Rules
|
|
4644
|
+
|
|
4645
|
+
\`\`\`bash
|
|
4646
|
+
# List available backups
|
|
4647
|
+
jai1 rules restore --list
|
|
4648
|
+
|
|
4649
|
+
# Restore from backup
|
|
4650
|
+
jai1 rules restore
|
|
4651
|
+
\`\`\`
|
|
4652
|
+
|
|
4601
4653
|
## Best Practices
|
|
4602
4654
|
|
|
4603
4655
|
### \u2705 N\xCAN l\xE0m:
|
|
@@ -4606,6 +4658,8 @@ V\xED d\u1EE5 minh h\u1ECDa (n\u1EBFu c\u1EA7n)
|
|
|
4606
4658
|
- Focus v\xE0o "what to do", kh\xF4ng ph\u1EA3i "how to do"
|
|
4607
4659
|
- C\u1EADp nh\u1EADt rules khi d\u1EF1 \xE1n thay \u0111\u1ED5i
|
|
4608
4660
|
- Chia nh\u1ECF rules theo domain/feature
|
|
4661
|
+
- Edit rules trong \`.jai1/rule-preset/\` (source of truth)
|
|
4662
|
+
- Ch\u1EA1y \`jai1 rules sync\` sau khi edit \u0111\u1EC3 regenerate
|
|
4609
4663
|
|
|
4610
4664
|
### \u274C KH\xD4NG N\xCAN:
|
|
4611
4665
|
|
|
@@ -4613,8 +4667,9 @@ V\xED d\u1EE5 minh h\u1ECDa (n\u1EBFu c\u1EA7n)
|
|
|
4613
4667
|
- Copy-paste to\xE0n b\u1ED9 documentation
|
|
4614
4668
|
- Include code examples qu\xE1 chi ti\u1EBFt
|
|
4615
4669
|
- Hardcode paths ho\u1EB7c values
|
|
4670
|
+
- Edit tr\u1EF1c ti\u1EBFp trong \`.cursor/rules/\` (s\u1EBD b\u1ECB overwrite khi sync)
|
|
4616
4671
|
|
|
4617
|
-
> \u{1F4A1} **Tip:** D\xF9ng \`jai1 apply
|
|
4672
|
+
> \u{1F4A1} **Tip:** D\xF9ng \`jai1 rules apply\` \u0111\u1EC3 c\xE0i \u0111\u1EB7t rules m\u1EABu cho tech stack c\u1EE7a b\u1EA1n!
|
|
4618
4673
|
`,
|
|
4619
4674
|
"skills": `---
|
|
4620
4675
|
title: Skills
|
|
@@ -7759,7 +7814,8 @@ async function handleInteractiveFeedback(config) {
|
|
|
7759
7814
|
name: "\u{1F4DD} Suggestion - Share ideas or improvements",
|
|
7760
7815
|
value: "suggestion"
|
|
7761
7816
|
}
|
|
7762
|
-
]
|
|
7817
|
+
],
|
|
7818
|
+
theme: selectTheme
|
|
7763
7819
|
});
|
|
7764
7820
|
const title = await input({
|
|
7765
7821
|
message: "Title (max 200 characters):",
|
|
@@ -10293,27 +10349,147 @@ function createUtilsCommand() {
|
|
|
10293
10349
|
}
|
|
10294
10350
|
|
|
10295
10351
|
// src/commands/deps/index.ts
|
|
10296
|
-
import { Command as
|
|
10297
|
-
import
|
|
10352
|
+
import { Command as Command38 } from "commander";
|
|
10353
|
+
import chalk13 from "chalk";
|
|
10298
10354
|
|
|
10299
|
-
// src/commands/deps/
|
|
10355
|
+
// src/commands/deps/check.ts
|
|
10300
10356
|
import { Command as Command36 } from "commander";
|
|
10301
|
-
import
|
|
10357
|
+
import chalk11 from "chalk";
|
|
10358
|
+
import Table3 from "cli-table3";
|
|
10359
|
+
import ora from "ora";
|
|
10360
|
+
|
|
10361
|
+
// src/services/deps-detector.service.ts
|
|
10362
|
+
import * as fs13 from "fs/promises";
|
|
10363
|
+
import * as path8 from "path";
|
|
10364
|
+
var DepsDetectorService = class {
|
|
10365
|
+
/**
|
|
10366
|
+
* Detect all project types in the given directory
|
|
10367
|
+
*/
|
|
10368
|
+
async detectProjects(cwd) {
|
|
10369
|
+
const projects = [];
|
|
10370
|
+
if (await this.fileExists(cwd, "package.json")) {
|
|
10371
|
+
const manager = await this.detectNodePackageManager(cwd);
|
|
10372
|
+
projects.push({
|
|
10373
|
+
ecosystem: "node",
|
|
10374
|
+
manager,
|
|
10375
|
+
configFile: "package.json"
|
|
10376
|
+
});
|
|
10377
|
+
}
|
|
10378
|
+
if (await this.fileExists(cwd, "composer.json") || await this.fileExists(cwd, "composer.lock")) {
|
|
10379
|
+
const isLaravel = await this.detectLaravel(cwd);
|
|
10380
|
+
projects.push({
|
|
10381
|
+
ecosystem: "php",
|
|
10382
|
+
manager: "composer",
|
|
10383
|
+
isLaravel,
|
|
10384
|
+
configFile: "composer.json"
|
|
10385
|
+
});
|
|
10386
|
+
}
|
|
10387
|
+
if (await this.fileExists(cwd, "Pipfile")) {
|
|
10388
|
+
projects.push({
|
|
10389
|
+
ecosystem: "python",
|
|
10390
|
+
manager: "pipenv",
|
|
10391
|
+
configFile: "Pipfile"
|
|
10392
|
+
});
|
|
10393
|
+
} else if (await this.fileExists(cwd, "requirements.txt")) {
|
|
10394
|
+
projects.push({
|
|
10395
|
+
ecosystem: "python",
|
|
10396
|
+
manager: "pip",
|
|
10397
|
+
configFile: "requirements.txt"
|
|
10398
|
+
});
|
|
10399
|
+
}
|
|
10400
|
+
return projects;
|
|
10401
|
+
}
|
|
10402
|
+
/**
|
|
10403
|
+
* Detect Node.js package manager
|
|
10404
|
+
*/
|
|
10405
|
+
async detectNodePackageManager(cwd) {
|
|
10406
|
+
if (await this.fileExists(cwd, "pnpm-lock.yaml")) {
|
|
10407
|
+
return "pnpm";
|
|
10408
|
+
}
|
|
10409
|
+
if (await this.fileExists(cwd, "yarn.lock")) {
|
|
10410
|
+
return "yarn";
|
|
10411
|
+
}
|
|
10412
|
+
if (await this.fileExists(cwd, "bun.lockb")) {
|
|
10413
|
+
return "bun";
|
|
10414
|
+
}
|
|
10415
|
+
if (await this.fileExists(cwd, "package-lock.json")) {
|
|
10416
|
+
return "npm";
|
|
10417
|
+
}
|
|
10418
|
+
const userAgent = process.env.npm_config_user_agent || "";
|
|
10419
|
+
if (userAgent.includes("pnpm")) return "pnpm";
|
|
10420
|
+
if (userAgent.includes("yarn")) return "yarn";
|
|
10421
|
+
if (userAgent.includes("bun")) return "bun";
|
|
10422
|
+
return "npm";
|
|
10423
|
+
}
|
|
10424
|
+
/**
|
|
10425
|
+
* Detect if project is Laravel
|
|
10426
|
+
*/
|
|
10427
|
+
async detectLaravel(cwd) {
|
|
10428
|
+
try {
|
|
10429
|
+
const composerPath = path8.join(cwd, "composer.json");
|
|
10430
|
+
const content = await fs13.readFile(composerPath, "utf-8");
|
|
10431
|
+
const composerJson = JSON.parse(content);
|
|
10432
|
+
const deps = {
|
|
10433
|
+
...composerJson.require,
|
|
10434
|
+
...composerJson["require-dev"]
|
|
10435
|
+
};
|
|
10436
|
+
return "laravel/framework" in deps;
|
|
10437
|
+
} catch {
|
|
10438
|
+
return false;
|
|
10439
|
+
}
|
|
10440
|
+
}
|
|
10441
|
+
/**
|
|
10442
|
+
* Check if file exists
|
|
10443
|
+
*/
|
|
10444
|
+
async fileExists(cwd, filename) {
|
|
10445
|
+
try {
|
|
10446
|
+
await fs13.access(path8.join(cwd, filename));
|
|
10447
|
+
return true;
|
|
10448
|
+
} catch {
|
|
10449
|
+
return false;
|
|
10450
|
+
}
|
|
10451
|
+
}
|
|
10452
|
+
};
|
|
10302
10453
|
|
|
10303
10454
|
// src/services/deps.service.ts
|
|
10304
|
-
import { promises as
|
|
10305
|
-
import
|
|
10455
|
+
import { promises as fs14 } from "fs";
|
|
10456
|
+
import path9 from "path";
|
|
10306
10457
|
import { execSync } from "child_process";
|
|
10307
10458
|
import pLimit2 from "p-limit";
|
|
10308
10459
|
var DepsService = class {
|
|
10460
|
+
ecosystem = "node";
|
|
10309
10461
|
versionCache = /* @__PURE__ */ new Map();
|
|
10310
|
-
|
|
10311
|
-
|
|
10312
|
-
|
|
10462
|
+
async check(cwd, onProgress) {
|
|
10463
|
+
const pkgJson = await this.readPackageJson(cwd);
|
|
10464
|
+
const manager = await this.detectPackageManager(cwd);
|
|
10465
|
+
const packages = [];
|
|
10466
|
+
if (pkgJson.dependencies) {
|
|
10467
|
+
for (const [name, version] of Object.entries(pkgJson.dependencies)) {
|
|
10468
|
+
if (this.isValidNpmDependency(version)) {
|
|
10469
|
+
packages.push({ name, current: version, type: "dep" });
|
|
10470
|
+
}
|
|
10471
|
+
}
|
|
10472
|
+
}
|
|
10473
|
+
if (pkgJson.devDependencies) {
|
|
10474
|
+
for (const [name, version] of Object.entries(pkgJson.devDependencies)) {
|
|
10475
|
+
if (this.isValidNpmDependency(version)) {
|
|
10476
|
+
packages.push({ name, current: version, type: "dev" });
|
|
10477
|
+
}
|
|
10478
|
+
}
|
|
10479
|
+
}
|
|
10480
|
+
const upgradablePackages = await this.fetchBulkVersions(packages, onProgress);
|
|
10481
|
+
return { ecosystem: this.ecosystem, manager, packages: upgradablePackages };
|
|
10482
|
+
}
|
|
10483
|
+
async upgrade(cwd, options) {
|
|
10484
|
+
const manager = await this.detectPackageManager(cwd);
|
|
10485
|
+
const commands = this.buildUpgradeCommands(options.packages, manager);
|
|
10486
|
+
if (commands.deps) this.executeUpgrade(commands.deps);
|
|
10487
|
+
if (commands.devDeps) this.executeUpgrade(commands.devDeps);
|
|
10488
|
+
}
|
|
10313
10489
|
async readPackageJson(cwd) {
|
|
10314
|
-
const pkgPath =
|
|
10490
|
+
const pkgPath = path9.join(cwd, "package.json");
|
|
10315
10491
|
try {
|
|
10316
|
-
const content = await
|
|
10492
|
+
const content = await fs14.readFile(pkgPath, "utf-8");
|
|
10317
10493
|
return JSON.parse(content);
|
|
10318
10494
|
} catch (error) {
|
|
10319
10495
|
if (error.code === "ENOENT") {
|
|
@@ -10322,9 +10498,6 @@ var DepsService = class {
|
|
|
10322
10498
|
throw new Error(`L\u1ED7i \u0111\u1ECDc package.json: ${error.message}`);
|
|
10323
10499
|
}
|
|
10324
10500
|
}
|
|
10325
|
-
/**
|
|
10326
|
-
* Lấy version mới nhất từ npm registry
|
|
10327
|
-
*/
|
|
10328
10501
|
async fetchLatestVersion(packageName) {
|
|
10329
10502
|
if (this.versionCache.has(packageName)) {
|
|
10330
10503
|
return this.versionCache.get(packageName);
|
|
@@ -10334,9 +10507,7 @@ var DepsService = class {
|
|
|
10334
10507
|
`https://registry.npmjs.org/${encodeURIComponent(packageName)}/latest`,
|
|
10335
10508
|
{ signal: AbortSignal.timeout(1e4) }
|
|
10336
10509
|
);
|
|
10337
|
-
if (!response.ok)
|
|
10338
|
-
return null;
|
|
10339
|
-
}
|
|
10510
|
+
if (!response.ok) return null;
|
|
10340
10511
|
const data = await response.json();
|
|
10341
10512
|
this.versionCache.set(packageName, data.version);
|
|
10342
10513
|
return data.version;
|
|
@@ -10344,54 +10515,51 @@ var DepsService = class {
|
|
|
10344
10515
|
return null;
|
|
10345
10516
|
}
|
|
10346
10517
|
}
|
|
10347
|
-
/**
|
|
10348
|
-
* Fetch nhiều packages cùng lúc với giới hạn concurrent
|
|
10349
|
-
*/
|
|
10350
10518
|
async fetchBulkVersions(packages, onProgress) {
|
|
10351
10519
|
const limit = pLimit2(10);
|
|
10352
10520
|
const total = packages.length;
|
|
10353
10521
|
let completed = 0;
|
|
10354
10522
|
const results = await Promise.all(
|
|
10355
|
-
packages.map(
|
|
10356
|
-
|
|
10357
|
-
|
|
10358
|
-
|
|
10359
|
-
|
|
10360
|
-
|
|
10361
|
-
|
|
10362
|
-
|
|
10363
|
-
|
|
10364
|
-
|
|
10365
|
-
|
|
10366
|
-
|
|
10367
|
-
|
|
10368
|
-
|
|
10369
|
-
|
|
10370
|
-
|
|
10371
|
-
|
|
10372
|
-
|
|
10373
|
-
})
|
|
10374
|
-
)
|
|
10523
|
+
packages.map((pkg) => limit(async () => {
|
|
10524
|
+
const latest = await this.fetchLatestVersion(pkg.name);
|
|
10525
|
+
completed++;
|
|
10526
|
+
onProgress?.(completed, total);
|
|
10527
|
+
if (!latest) return null;
|
|
10528
|
+
const currentClean = this.stripSemverPrefix(pkg.current);
|
|
10529
|
+
const canUpgrade = this.isNewerVersion(latest, currentClean);
|
|
10530
|
+
const upgradeType = this.getUpgradeType(currentClean, latest);
|
|
10531
|
+
return {
|
|
10532
|
+
name: pkg.name,
|
|
10533
|
+
current: pkg.current,
|
|
10534
|
+
latest,
|
|
10535
|
+
latestWithPrefix: this.preserveSemverPrefix(pkg.current, latest),
|
|
10536
|
+
type: pkg.type,
|
|
10537
|
+
canUpgrade,
|
|
10538
|
+
upgradeType
|
|
10539
|
+
};
|
|
10540
|
+
}))
|
|
10375
10541
|
);
|
|
10376
10542
|
return results.filter((r) => r !== null && r.canUpgrade);
|
|
10377
10543
|
}
|
|
10378
|
-
|
|
10379
|
-
|
|
10380
|
-
|
|
10544
|
+
isValidNpmDependency(version) {
|
|
10545
|
+
const invalidPrefixes = ["git+", "git:", "file:", "link:", "workspace:"];
|
|
10546
|
+
return !invalidPrefixes.some((prefix) => version.startsWith(prefix));
|
|
10547
|
+
}
|
|
10548
|
+
getUpgradeType(current, latest) {
|
|
10549
|
+
const currentParts = current.split(".").map(Number);
|
|
10550
|
+
const latestParts = latest.split(".").map(Number);
|
|
10551
|
+
if ((latestParts[0] || 0) > (currentParts[0] || 0)) return "major";
|
|
10552
|
+
if ((latestParts[1] || 0) > (currentParts[1] || 0)) return "minor";
|
|
10553
|
+
return "patch";
|
|
10554
|
+
}
|
|
10381
10555
|
stripSemverPrefix(version) {
|
|
10382
10556
|
return version.replace(/^[~^>=<]+/, "");
|
|
10383
10557
|
}
|
|
10384
|
-
/**
|
|
10385
|
-
* Giữ nguyên semver prefix khi upgrade
|
|
10386
|
-
*/
|
|
10387
10558
|
preserveSemverPrefix(current, latest) {
|
|
10388
10559
|
const match = current.match(/^([~^>=<]*)/);
|
|
10389
10560
|
const prefix = match?.[1] || "";
|
|
10390
10561
|
return prefix + latest;
|
|
10391
10562
|
}
|
|
10392
|
-
/**
|
|
10393
|
-
* So sánh version để xác định có cần upgrade không
|
|
10394
|
-
*/
|
|
10395
10563
|
isNewerVersion(remote, local) {
|
|
10396
10564
|
const remoteParts = remote.split(".").map(Number);
|
|
10397
10565
|
const localParts = local.split(".").map(Number);
|
|
@@ -10403,13 +10571,6 @@ var DepsService = class {
|
|
|
10403
10571
|
}
|
|
10404
10572
|
return false;
|
|
10405
10573
|
}
|
|
10406
|
-
/**
|
|
10407
|
-
* Detect package manager từ lock files
|
|
10408
|
-
* Priority:
|
|
10409
|
-
* 1. Lock file trong project
|
|
10410
|
-
* 2. pnpm nếu có cài đặt trong máy
|
|
10411
|
-
* 3. npm (fallback)
|
|
10412
|
-
*/
|
|
10413
10574
|
async detectPackageManager(cwd) {
|
|
10414
10575
|
const lockFiles = [
|
|
10415
10576
|
{ file: "pnpm-lock.yaml", pm: "pnpm" },
|
|
@@ -10419,7 +10580,7 @@ var DepsService = class {
|
|
|
10419
10580
|
];
|
|
10420
10581
|
for (const { file, pm } of lockFiles) {
|
|
10421
10582
|
try {
|
|
10422
|
-
await
|
|
10583
|
+
await fs14.access(path9.join(cwd, file));
|
|
10423
10584
|
return pm;
|
|
10424
10585
|
} catch {
|
|
10425
10586
|
}
|
|
@@ -10428,27 +10589,22 @@ var DepsService = class {
|
|
|
10428
10589
|
if (userAgent.includes("pnpm")) return "pnpm";
|
|
10429
10590
|
if (userAgent.includes("yarn")) return "yarn";
|
|
10430
10591
|
if (userAgent.includes("bun")) return "bun";
|
|
10431
|
-
if (userAgent.includes("npm")) return "npm";
|
|
10432
10592
|
if (this.isCommandAvailable("pnpm")) return "pnpm";
|
|
10433
10593
|
return "npm";
|
|
10434
10594
|
}
|
|
10435
|
-
/**
|
|
10436
|
-
* Kiểm tra command có sẵn trong hệ thống không
|
|
10437
|
-
*/
|
|
10438
10595
|
isCommandAvailable(command) {
|
|
10439
10596
|
try {
|
|
10440
|
-
execSync(`${command} --version`, {
|
|
10441
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
10442
|
-
});
|
|
10597
|
+
execSync(`${command} --version`, { stdio: ["pipe", "pipe", "pipe"] });
|
|
10443
10598
|
return true;
|
|
10444
10599
|
} catch {
|
|
10445
10600
|
return false;
|
|
10446
10601
|
}
|
|
10447
10602
|
}
|
|
10448
|
-
|
|
10449
|
-
|
|
10450
|
-
|
|
10451
|
-
|
|
10603
|
+
getUpgradeCommands(packages) {
|
|
10604
|
+
const pm = process.env.npm_config_user_agent?.includes("pnpm") ? "pnpm" : "npm";
|
|
10605
|
+
return this.buildUpgradeCommands(packages, pm);
|
|
10606
|
+
}
|
|
10607
|
+
buildUpgradeCommands(packages, pm) {
|
|
10452
10608
|
const deps = packages.filter((p) => p.type === "dep");
|
|
10453
10609
|
const devDeps = packages.filter((p) => p.type === "dev");
|
|
10454
10610
|
const formatPackages = (pkgs) => pkgs.map((p) => `${p.name}@${p.latestWithPrefix}`).join(" ");
|
|
@@ -10488,240 +10644,655 @@ var DepsService = class {
|
|
|
10488
10644
|
}
|
|
10489
10645
|
return { deps: depsCmd, devDeps: devDepsCmd };
|
|
10490
10646
|
}
|
|
10491
|
-
/**
|
|
10492
|
-
* Thực thi upgrade command
|
|
10493
|
-
*/
|
|
10494
10647
|
executeUpgrade(command) {
|
|
10495
|
-
execSync(command, {
|
|
10496
|
-
stdio: "inherit",
|
|
10497
|
-
env: { ...process.env, FORCE_COLOR: "1" }
|
|
10498
|
-
});
|
|
10648
|
+
execSync(command, { stdio: "inherit", env: { ...process.env, FORCE_COLOR: "1" } });
|
|
10499
10649
|
}
|
|
10500
10650
|
};
|
|
10501
10651
|
|
|
10502
|
-
// src/
|
|
10503
|
-
|
|
10504
|
-
|
|
10505
|
-
|
|
10506
|
-
|
|
10507
|
-
|
|
10508
|
-
|
|
10509
|
-
|
|
10510
|
-
|
|
10511
|
-
|
|
10512
|
-
|
|
10513
|
-
|
|
10514
|
-
|
|
10515
|
-
|
|
10516
|
-
|
|
10517
|
-
}
|
|
10518
|
-
|
|
10519
|
-
|
|
10520
|
-
|
|
10521
|
-
|
|
10522
|
-
|
|
10523
|
-
|
|
10524
|
-
|
|
10525
|
-
|
|
10526
|
-
|
|
10527
|
-
|
|
10528
|
-
|
|
10529
|
-
|
|
10530
|
-
|
|
10531
|
-
|
|
10532
|
-
|
|
10533
|
-
if (!options.depsOnly && pkg.devDependencies) {
|
|
10534
|
-
for (const [name, version] of Object.entries(pkg.devDependencies)) {
|
|
10535
|
-
if (isNpmVersion(version)) {
|
|
10536
|
-
packagesToCheck.push({ name, current: version, type: "dev" });
|
|
10537
|
-
}
|
|
10538
|
-
}
|
|
10539
|
-
}
|
|
10540
|
-
if (packagesToCheck.length === 0) {
|
|
10541
|
-
console.log(`${colors3.yellow}\u26A0\uFE0F Kh\xF4ng t\xECm th\u1EA5y packages \u0111\u1EC3 ki\u1EC3m tra${colors3.reset}
|
|
10542
|
-
`);
|
|
10543
|
-
return;
|
|
10652
|
+
// src/services/deps-php.service.ts
|
|
10653
|
+
import { execSync as execSync2 } from "child_process";
|
|
10654
|
+
import { promises as fs15 } from "fs";
|
|
10655
|
+
import path10 from "path";
|
|
10656
|
+
var LARAVEL_PROTECTED_PACKAGES = /^laravel\//;
|
|
10657
|
+
var DepsPhpService = class {
|
|
10658
|
+
ecosystem = "php";
|
|
10659
|
+
isLaravel = false;
|
|
10660
|
+
async check(cwd, onProgress) {
|
|
10661
|
+
this.isLaravel = await this.detectLaravel(cwd);
|
|
10662
|
+
const outdated = await this.fetchOutdatedPackages(cwd);
|
|
10663
|
+
const composerJson = await this.readComposerJson(cwd);
|
|
10664
|
+
const packages = [];
|
|
10665
|
+
let processed = 0;
|
|
10666
|
+
for (const pkg of outdated) {
|
|
10667
|
+
const isDev = pkg.name in (composerJson["require-dev"] || {});
|
|
10668
|
+
const upgradeType = this.getUpgradeType(pkg.version, pkg.latest);
|
|
10669
|
+
const canUpgrade = this.canUpgradePackage(pkg.name, upgradeType);
|
|
10670
|
+
const blockedReason = !canUpgrade ? "Laravel major version blocked" : void 0;
|
|
10671
|
+
packages.push({
|
|
10672
|
+
name: pkg.name,
|
|
10673
|
+
current: pkg.version,
|
|
10674
|
+
latest: pkg.latest,
|
|
10675
|
+
latestWithPrefix: pkg.latest,
|
|
10676
|
+
type: isDev ? "dev" : "dep",
|
|
10677
|
+
upgradeType,
|
|
10678
|
+
canUpgrade,
|
|
10679
|
+
blockedReason
|
|
10680
|
+
});
|
|
10681
|
+
processed++;
|
|
10682
|
+
onProgress?.(processed, outdated.length);
|
|
10544
10683
|
}
|
|
10545
|
-
|
|
10546
|
-
|
|
10547
|
-
|
|
10548
|
-
|
|
10549
|
-
|
|
10550
|
-
|
|
10551
|
-
|
|
10552
|
-
|
|
10553
|
-
|
|
10554
|
-
|
|
10555
|
-
|
|
10556
|
-
|
|
10557
|
-
|
|
10558
|
-
|
|
10559
|
-
);
|
|
10560
|
-
|
|
10561
|
-
|
|
10562
|
-
|
|
10563
|
-
`);
|
|
10564
|
-
|
|
10684
|
+
return {
|
|
10685
|
+
ecosystem: this.ecosystem,
|
|
10686
|
+
manager: "composer",
|
|
10687
|
+
packages: packages.filter((p) => p.canUpgrade),
|
|
10688
|
+
isLaravel: this.isLaravel
|
|
10689
|
+
};
|
|
10690
|
+
}
|
|
10691
|
+
async upgrade(cwd, options) {
|
|
10692
|
+
const commands = this.getUpgradeCommands(options.packages);
|
|
10693
|
+
if (commands.deps) this.executeUpgrade(commands.deps, cwd);
|
|
10694
|
+
if (commands.devDeps) this.executeUpgrade(commands.devDeps, cwd);
|
|
10695
|
+
}
|
|
10696
|
+
getUpgradeCommands(packages) {
|
|
10697
|
+
const deps = packages.filter((p) => p.type === "dep");
|
|
10698
|
+
const devDeps = packages.filter((p) => p.type === "dev");
|
|
10699
|
+
let depsCmd = null;
|
|
10700
|
+
let devDepsCmd = null;
|
|
10701
|
+
if (deps.length > 0) {
|
|
10702
|
+
const pkgList = deps.map((p) => `${p.name}:${p.latest}`).join(" ");
|
|
10703
|
+
depsCmd = `composer require -W ${pkgList}`;
|
|
10565
10704
|
}
|
|
10566
|
-
|
|
10567
|
-
|
|
10568
|
-
|
|
10569
|
-
`${colors3.cyan}\u{1F4A1} Ch\u1EA1y "jai1 deps upgrade" \u0111\u1EC3 ti\u1EBFn h\xE0nh upgrade.${colors3.reset}
|
|
10570
|
-
`
|
|
10571
|
-
);
|
|
10572
|
-
return;
|
|
10705
|
+
if (devDeps.length > 0) {
|
|
10706
|
+
const pkgList = devDeps.map((p) => `${p.name}:${p.latest}`).join(" ");
|
|
10707
|
+
devDepsCmd = `composer require --dev -W ${pkgList}`;
|
|
10573
10708
|
}
|
|
10574
|
-
|
|
10575
|
-
|
|
10576
|
-
|
|
10577
|
-
|
|
10578
|
-
|
|
10579
|
-
|
|
10580
|
-
|
|
10581
|
-
|
|
10582
|
-
|
|
10583
|
-
|
|
10584
|
-
name: `${pkg2.name} (${pkg2.current} \u2192 ${pkg2.latestWithPrefix}) [${pkg2.type}]`,
|
|
10585
|
-
value: pkg2.name,
|
|
10586
|
-
checked: true
|
|
10587
|
-
// Mặc định chọn tất cả
|
|
10588
|
-
})),
|
|
10589
|
-
instructions: "\u2191\u2193 navigate \u2022 space select \u2022 a all \u2022 i invert \u2022 \u23CE submit \u2022 esc cancel"
|
|
10590
|
-
});
|
|
10591
|
-
if (selected.length === 0) {
|
|
10592
|
-
console.log(`${colors3.yellow}\u23F8\uFE0F Kh\xF4ng c\xF3 packages n\xE0o \u0111\u01B0\u1EE3c ch\u1ECDn${colors3.reset}
|
|
10593
|
-
`);
|
|
10594
|
-
return;
|
|
10595
|
-
}
|
|
10596
|
-
selectedPackages = upgradablePackages.filter((pkg2) => selected.includes(pkg2.name));
|
|
10597
|
-
} catch {
|
|
10598
|
-
console.log(`
|
|
10599
|
-
${colors3.yellow}\u23F8\uFE0F \u0110\xE3 h\u1EE7y${colors3.reset}
|
|
10600
|
-
`);
|
|
10601
|
-
return;
|
|
10709
|
+
return { deps: depsCmd, devDeps: devDepsCmd };
|
|
10710
|
+
}
|
|
10711
|
+
async readComposerJson(cwd) {
|
|
10712
|
+
const composerPath = path10.join(cwd, "composer.json");
|
|
10713
|
+
try {
|
|
10714
|
+
const content = await fs15.readFile(composerPath, "utf-8");
|
|
10715
|
+
return JSON.parse(content);
|
|
10716
|
+
} catch (error) {
|
|
10717
|
+
if (error.code === "ENOENT") {
|
|
10718
|
+
throw new Error("Kh\xF4ng t\xECm th\u1EA5y composer.json trong th\u01B0 m\u1EE5c hi\u1EC7n t\u1EA1i");
|
|
10602
10719
|
}
|
|
10720
|
+
throw new Error(`L\u1ED7i \u0111\u1ECDc composer.json: ${error.message}`);
|
|
10603
10721
|
}
|
|
10604
|
-
|
|
10722
|
+
}
|
|
10723
|
+
async detectLaravel(cwd) {
|
|
10605
10724
|
try {
|
|
10606
|
-
|
|
10607
|
-
|
|
10608
|
-
|
|
10609
|
-
});
|
|
10725
|
+
const composerJson = await this.readComposerJson(cwd);
|
|
10726
|
+
const deps = { ...composerJson.require, ...composerJson["require-dev"] };
|
|
10727
|
+
return "laravel/framework" in deps;
|
|
10610
10728
|
} catch {
|
|
10611
|
-
|
|
10612
|
-
${colors3.yellow}\u23F8\uFE0F \u0110\xE3 h\u1EE7y${colors3.reset}
|
|
10613
|
-
`);
|
|
10614
|
-
return;
|
|
10615
|
-
}
|
|
10616
|
-
if (!shouldProceed) {
|
|
10617
|
-
console.log(`${colors3.yellow}\u23F8\uFE0F Upgrade \u0111\xE3 h\u1EE7y${colors3.reset}
|
|
10618
|
-
`);
|
|
10619
|
-
return;
|
|
10729
|
+
return false;
|
|
10620
10730
|
}
|
|
10621
|
-
|
|
10622
|
-
|
|
10623
|
-
${colors3.cyan}\u{1F527} Package manager: ${pm}${colors3.reset}`);
|
|
10624
|
-
console.log(`${colors3.cyan}\u{1F4E5} \u0110ang upgrade...${colors3.reset}
|
|
10625
|
-
`);
|
|
10626
|
-
const commands = depsService.getUpgradeCommands(selectedPackages, pm);
|
|
10731
|
+
}
|
|
10732
|
+
async fetchOutdatedPackages(cwd) {
|
|
10627
10733
|
try {
|
|
10628
|
-
|
|
10629
|
-
|
|
10630
|
-
|
|
10631
|
-
|
|
10632
|
-
}
|
|
10633
|
-
|
|
10634
|
-
|
|
10635
|
-
${colors3.dim}$ ${commands.devDeps}${colors3.reset}
|
|
10636
|
-
`);
|
|
10637
|
-
depsService.executeUpgrade(commands.devDeps);
|
|
10638
|
-
}
|
|
10639
|
-
console.log(
|
|
10640
|
-
`
|
|
10641
|
-
${colors3.green}\u2705 \u0110\xE3 upgrade ${selectedPackages.length} packages th\xE0nh c\xF4ng!${colors3.reset}
|
|
10642
|
-
`
|
|
10643
|
-
);
|
|
10734
|
+
const output = execSync2("composer outdated --format=json --direct", {
|
|
10735
|
+
cwd,
|
|
10736
|
+
encoding: "utf-8",
|
|
10737
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
10738
|
+
});
|
|
10739
|
+
const result = JSON.parse(output);
|
|
10740
|
+
return result.installed || [];
|
|
10644
10741
|
} catch (error) {
|
|
10645
|
-
|
|
10646
|
-
|
|
10647
|
-
|
|
10648
|
-
|
|
10649
|
-
|
|
10650
|
-
|
|
10651
|
-
|
|
10652
|
-
|
|
10742
|
+
if (error.stdout) {
|
|
10743
|
+
try {
|
|
10744
|
+
const result = JSON.parse(error.stdout);
|
|
10745
|
+
return result.installed || [];
|
|
10746
|
+
} catch {
|
|
10747
|
+
}
|
|
10748
|
+
}
|
|
10749
|
+
throw new Error(`L\u1ED7i ki\u1EC3m tra composer packages: ${error.message}`);
|
|
10750
|
+
}
|
|
10751
|
+
}
|
|
10752
|
+
getUpgradeType(current, latest) {
|
|
10753
|
+
const currentParts = current.split(".").map(Number);
|
|
10754
|
+
const latestParts = latest.split(".").map(Number);
|
|
10755
|
+
if ((latestParts[0] || 0) > (currentParts[0] || 0)) return "major";
|
|
10756
|
+
if ((latestParts[1] || 0) > (currentParts[1] || 0)) return "minor";
|
|
10757
|
+
return "patch";
|
|
10758
|
+
}
|
|
10759
|
+
canUpgradePackage(packageName, upgradeType) {
|
|
10760
|
+
if (this.isLaravel && LARAVEL_PROTECTED_PACKAGES.test(packageName) && upgradeType === "major") {
|
|
10761
|
+
return false;
|
|
10762
|
+
}
|
|
10763
|
+
return true;
|
|
10764
|
+
}
|
|
10765
|
+
executeUpgrade(command, cwd) {
|
|
10766
|
+
execSync2(command, {
|
|
10767
|
+
cwd,
|
|
10768
|
+
stdio: "inherit",
|
|
10769
|
+
env: { ...process.env, FORCE_COLOR: "1" }
|
|
10770
|
+
});
|
|
10771
|
+
}
|
|
10772
|
+
};
|
|
10773
|
+
|
|
10774
|
+
// src/services/deps-python.service.ts
|
|
10775
|
+
import { execSync as execSync3 } from "child_process";
|
|
10776
|
+
import { promises as fs16 } from "fs";
|
|
10777
|
+
import path11 from "path";
|
|
10778
|
+
import pLimit3 from "p-limit";
|
|
10779
|
+
var DepsPythonService = class {
|
|
10780
|
+
ecosystem = "python";
|
|
10781
|
+
versionCache = /* @__PURE__ */ new Map();
|
|
10782
|
+
async check(cwd, onProgress) {
|
|
10783
|
+
const manager = await this.detectPackageManager(cwd);
|
|
10784
|
+
let packages;
|
|
10785
|
+
if (manager === "pipenv") {
|
|
10786
|
+
packages = await this.checkPipenv(cwd, onProgress);
|
|
10787
|
+
} else {
|
|
10788
|
+
packages = await this.checkPip(cwd, onProgress);
|
|
10789
|
+
}
|
|
10790
|
+
return { ecosystem: this.ecosystem, manager, packages };
|
|
10791
|
+
}
|
|
10792
|
+
async upgrade(cwd, options) {
|
|
10793
|
+
const manager = await this.detectPackageManager(cwd);
|
|
10794
|
+
const commands = this.getUpgradeCommands(options.packages);
|
|
10795
|
+
if (manager === "pipenv") {
|
|
10796
|
+
if (commands.deps || commands.devDeps) {
|
|
10797
|
+
const allPackages = options.packages.map((p) => p.name).join(" ");
|
|
10798
|
+
this.executeUpgrade(`pipenv update ${allPackages}`, cwd);
|
|
10799
|
+
}
|
|
10800
|
+
} else {
|
|
10801
|
+
if (commands.deps) this.executeUpgrade(commands.deps, cwd);
|
|
10802
|
+
if (commands.devDeps) this.executeUpgrade(commands.devDeps, cwd);
|
|
10803
|
+
}
|
|
10804
|
+
}
|
|
10805
|
+
getUpgradeCommands(packages) {
|
|
10806
|
+
const deps = packages.filter((p) => p.type === "dep");
|
|
10807
|
+
const devDeps = packages.filter((p) => p.type === "dev");
|
|
10808
|
+
let depsCmd = null;
|
|
10809
|
+
let devDepsCmd = null;
|
|
10810
|
+
if (deps.length > 0) {
|
|
10811
|
+
const pkgList = deps.map((p) => `${p.name}==${p.latest}`).join(" ");
|
|
10812
|
+
depsCmd = `pip install --upgrade ${pkgList}`;
|
|
10813
|
+
}
|
|
10814
|
+
if (devDeps.length > 0) {
|
|
10815
|
+
const pkgList = devDeps.map((p) => `${p.name}==${p.latest}`).join(" ");
|
|
10816
|
+
devDepsCmd = `pip install --upgrade ${pkgList}`;
|
|
10817
|
+
}
|
|
10818
|
+
return { deps: depsCmd, devDeps: devDepsCmd };
|
|
10819
|
+
}
|
|
10820
|
+
async detectPackageManager(cwd) {
|
|
10821
|
+
if (await this.fileExists(cwd, "Pipfile")) return "pipenv";
|
|
10822
|
+
return "pip";
|
|
10823
|
+
}
|
|
10824
|
+
async checkPip(cwd, onProgress) {
|
|
10825
|
+
const requirementsPath = path11.join(cwd, "requirements.txt");
|
|
10826
|
+
try {
|
|
10827
|
+
const content = await fs16.readFile(requirementsPath, "utf-8");
|
|
10828
|
+
const packages = this.parseRequirementsTxt(content);
|
|
10829
|
+
return await this.fetchBulkVersions(packages, onProgress);
|
|
10830
|
+
} catch (error) {
|
|
10831
|
+
if (error.code === "ENOENT") {
|
|
10832
|
+
throw new Error("Kh\xF4ng t\xECm th\u1EA5y requirements.txt trong th\u01B0 m\u1EE5c hi\u1EC7n t\u1EA1i");
|
|
10833
|
+
}
|
|
10834
|
+
throw new Error(`L\u1ED7i \u0111\u1ECDc requirements.txt: ${error.message}`);
|
|
10835
|
+
}
|
|
10836
|
+
}
|
|
10837
|
+
async checkPipenv(cwd, onProgress) {
|
|
10838
|
+
try {
|
|
10839
|
+
const output = execSync3("pipenv run pip list --outdated --format=json", {
|
|
10840
|
+
cwd,
|
|
10841
|
+
encoding: "utf-8",
|
|
10842
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
10843
|
+
});
|
|
10844
|
+
const outdated = JSON.parse(output);
|
|
10845
|
+
const total = outdated.length;
|
|
10846
|
+
let completed = 0;
|
|
10847
|
+
const packages = outdated.map((pkg) => {
|
|
10848
|
+
completed++;
|
|
10849
|
+
onProgress?.(completed, total);
|
|
10850
|
+
return {
|
|
10851
|
+
name: pkg.name,
|
|
10852
|
+
current: pkg.version,
|
|
10853
|
+
latest: pkg.latest_version,
|
|
10854
|
+
latestWithPrefix: pkg.latest_version,
|
|
10855
|
+
type: "dep",
|
|
10856
|
+
upgradeType: this.getUpgradeType(pkg.version, pkg.latest_version),
|
|
10857
|
+
canUpgrade: true
|
|
10858
|
+
};
|
|
10859
|
+
});
|
|
10860
|
+
return packages;
|
|
10861
|
+
} catch (error) {
|
|
10862
|
+
throw new Error(`L\u1ED7i ki\u1EC3m tra pipenv packages: ${error.message}`);
|
|
10863
|
+
}
|
|
10864
|
+
}
|
|
10865
|
+
parseRequirementsTxt(content) {
|
|
10866
|
+
const packages = [];
|
|
10867
|
+
const lines = content.split("\n");
|
|
10868
|
+
for (const line of lines) {
|
|
10869
|
+
const trimmed = line.trim();
|
|
10870
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
10871
|
+
const match = trimmed.match(/^([a-zA-Z0-9_-]+)\s*([=><]=?)\s*([0-9.]+)/);
|
|
10872
|
+
if (match) {
|
|
10873
|
+
const [, name, , version] = match;
|
|
10874
|
+
packages.push({ name: name.toLowerCase(), current: version, type: "dep" });
|
|
10875
|
+
}
|
|
10876
|
+
}
|
|
10877
|
+
return packages;
|
|
10878
|
+
}
|
|
10879
|
+
async fetchBulkVersions(packages, onProgress) {
|
|
10880
|
+
const limit = pLimit3(10);
|
|
10881
|
+
const total = packages.length;
|
|
10882
|
+
let completed = 0;
|
|
10883
|
+
const results = await Promise.all(
|
|
10884
|
+
packages.map((pkg) => limit(async () => {
|
|
10885
|
+
const latest = await this.fetchLatestVersion(pkg.name);
|
|
10886
|
+
completed++;
|
|
10887
|
+
onProgress?.(completed, total);
|
|
10888
|
+
if (!latest) return null;
|
|
10889
|
+
const canUpgrade = this.isNewerVersion(latest, pkg.current);
|
|
10890
|
+
if (!canUpgrade) return null;
|
|
10891
|
+
return {
|
|
10892
|
+
name: pkg.name,
|
|
10893
|
+
current: pkg.current,
|
|
10894
|
+
latest,
|
|
10895
|
+
latestWithPrefix: latest,
|
|
10896
|
+
type: pkg.type,
|
|
10897
|
+
upgradeType: this.getUpgradeType(pkg.current, latest),
|
|
10898
|
+
canUpgrade: true
|
|
10899
|
+
};
|
|
10900
|
+
}))
|
|
10901
|
+
);
|
|
10902
|
+
return results.filter((r) => r !== null);
|
|
10903
|
+
}
|
|
10904
|
+
async fetchLatestVersion(packageName) {
|
|
10905
|
+
if (this.versionCache.has(packageName)) return this.versionCache.get(packageName);
|
|
10906
|
+
try {
|
|
10907
|
+
const response = await fetch(
|
|
10908
|
+
`https://pypi.org/pypi/${encodeURIComponent(packageName)}/json`,
|
|
10909
|
+
{ signal: AbortSignal.timeout(1e4) }
|
|
10910
|
+
);
|
|
10911
|
+
if (!response.ok) return null;
|
|
10912
|
+
const data = await response.json();
|
|
10913
|
+
this.versionCache.set(packageName, data.info.version);
|
|
10914
|
+
return data.info.version;
|
|
10915
|
+
} catch {
|
|
10916
|
+
return null;
|
|
10917
|
+
}
|
|
10918
|
+
}
|
|
10919
|
+
getUpgradeType(current, latest) {
|
|
10920
|
+
const currentParts = current.split(".").map(Number);
|
|
10921
|
+
const latestParts = latest.split(".").map(Number);
|
|
10922
|
+
if ((latestParts[0] || 0) > (currentParts[0] || 0)) return "major";
|
|
10923
|
+
if ((latestParts[1] || 0) > (currentParts[1] || 0)) return "minor";
|
|
10924
|
+
return "patch";
|
|
10925
|
+
}
|
|
10926
|
+
isNewerVersion(remote, local) {
|
|
10927
|
+
const remoteParts = remote.split(".").map(Number);
|
|
10928
|
+
const localParts = local.split(".").map(Number);
|
|
10929
|
+
for (let i = 0; i < 3; i++) {
|
|
10930
|
+
const r = remoteParts[i] || 0;
|
|
10931
|
+
const l = localParts[i] || 0;
|
|
10932
|
+
if (r > l) return true;
|
|
10933
|
+
if (r < l) return false;
|
|
10934
|
+
}
|
|
10935
|
+
return false;
|
|
10936
|
+
}
|
|
10937
|
+
async fileExists(cwd, filename) {
|
|
10938
|
+
try {
|
|
10939
|
+
await fs16.access(path11.join(cwd, filename));
|
|
10940
|
+
return true;
|
|
10941
|
+
} catch {
|
|
10942
|
+
return false;
|
|
10943
|
+
}
|
|
10944
|
+
}
|
|
10945
|
+
executeUpgrade(command, cwd) {
|
|
10946
|
+
execSync3(command, { cwd, stdio: "inherit", env: { ...process.env, FORCE_COLOR: "1" } });
|
|
10947
|
+
}
|
|
10948
|
+
};
|
|
10949
|
+
|
|
10950
|
+
// src/commands/deps/check.ts
|
|
10951
|
+
function createDepsCheckCommand() {
|
|
10952
|
+
const checkCommand = new Command36("check").description("Ki\u1EC3m tra c\xE1c packages c\u1EA7n upgrade").action(async () => {
|
|
10953
|
+
const cwd = process.cwd();
|
|
10954
|
+
const detector = new DepsDetectorService();
|
|
10955
|
+
const spinner = ora("\u0110ang ph\xE1t hi\u1EC7n lo\u1EA1i project...").start();
|
|
10956
|
+
const projects = await detector.detectProjects(cwd);
|
|
10957
|
+
if (projects.length === 0) {
|
|
10958
|
+
spinner.fail("Kh\xF4ng t\xECm th\u1EA5y project n\xE0o \u0111\u01B0\u1EE3c h\u1ED7 tr\u1EE3");
|
|
10959
|
+
console.log();
|
|
10960
|
+
console.log(chalk11.dim("H\u1ED7 tr\u1EE3: Node.js (package.json), PHP (composer.json), Python (requirements.txt/Pipfile)"));
|
|
10653
10961
|
process.exit(1);
|
|
10654
10962
|
}
|
|
10963
|
+
spinner.succeed(`Ph\xE1t hi\u1EC7n ${projects.length} project:`);
|
|
10964
|
+
for (const project of projects) {
|
|
10965
|
+
const icon = project.ecosystem === "node" ? "\u{1F4E6}" : project.ecosystem === "php" ? "\u{1F418}" : "\u{1F40D}";
|
|
10966
|
+
const label = project.ecosystem === "node" ? "Node.js" : project.ecosystem === "php" ? "PHP/Composer" : "Python";
|
|
10967
|
+
console.log(` ${icon} ${label} (${project.manager})`);
|
|
10968
|
+
}
|
|
10969
|
+
console.log();
|
|
10970
|
+
for (const project of projects) {
|
|
10971
|
+
await checkEcosystem(project.ecosystem, project.manager, cwd);
|
|
10972
|
+
}
|
|
10973
|
+
console.log();
|
|
10974
|
+
console.log(chalk11.dim('\u{1F4A1} Ch\u1EA1y "jai1 deps upgrade" \u0111\u1EC3 n\xE2ng c\u1EA5p packages.'));
|
|
10975
|
+
});
|
|
10976
|
+
return checkCommand;
|
|
10977
|
+
}
|
|
10978
|
+
async function checkEcosystem(ecosystem, manager, cwd) {
|
|
10979
|
+
let service;
|
|
10980
|
+
let icon;
|
|
10981
|
+
let label;
|
|
10982
|
+
switch (ecosystem) {
|
|
10983
|
+
case "node":
|
|
10984
|
+
service = new DepsService();
|
|
10985
|
+
icon = "\u{1F4E6}";
|
|
10986
|
+
label = `Node.js (${manager})`;
|
|
10987
|
+
break;
|
|
10988
|
+
case "php":
|
|
10989
|
+
service = new DepsPhpService();
|
|
10990
|
+
icon = "\u{1F418}";
|
|
10991
|
+
label = "PHP/Composer";
|
|
10992
|
+
break;
|
|
10993
|
+
case "python":
|
|
10994
|
+
service = new DepsPythonService();
|
|
10995
|
+
icon = "\u{1F40D}";
|
|
10996
|
+
label = `Python (${manager})`;
|
|
10997
|
+
break;
|
|
10998
|
+
}
|
|
10999
|
+
const spinner = ora(`\u0110ang ki\u1EC3m tra ${label}...`).start();
|
|
11000
|
+
let result;
|
|
11001
|
+
try {
|
|
11002
|
+
result = await service.check(cwd, (completed, total) => {
|
|
11003
|
+
spinner.text = `\u0110ang ki\u1EC3m tra ${label}... (${completed}/${total})`;
|
|
11004
|
+
});
|
|
10655
11005
|
} catch (error) {
|
|
10656
|
-
|
|
10657
|
-
|
|
10658
|
-
|
|
11006
|
+
spinner.fail(`L\u1ED7i ki\u1EC3m tra ${label}`);
|
|
11007
|
+
console.log(chalk11.red(error.message));
|
|
11008
|
+
console.log();
|
|
11009
|
+
return;
|
|
11010
|
+
}
|
|
11011
|
+
if (result.packages.length === 0) {
|
|
11012
|
+
spinner.succeed(`${label} - T\u1EA5t c\u1EA3 packages \u0111\xE3 c\u1EADp nh\u1EADt`);
|
|
11013
|
+
console.log();
|
|
11014
|
+
return;
|
|
11015
|
+
}
|
|
11016
|
+
spinner.succeed(`${label} - ${result.packages.length} packages c\xF3 th\u1EC3 n\xE2ng c\u1EA5p`);
|
|
11017
|
+
console.log();
|
|
11018
|
+
const table = new Table3({
|
|
11019
|
+
head: [
|
|
11020
|
+
chalk11.cyan("Package"),
|
|
11021
|
+
chalk11.cyan("Hi\u1EC7n t\u1EA1i"),
|
|
11022
|
+
chalk11.cyan("M\u1EDBi nh\u1EA5t"),
|
|
11023
|
+
chalk11.cyan("Lo\u1EA1i")
|
|
11024
|
+
],
|
|
11025
|
+
style: {
|
|
11026
|
+
head: [],
|
|
11027
|
+
border: ["dim"]
|
|
11028
|
+
}
|
|
11029
|
+
});
|
|
11030
|
+
for (const pkg of result.packages) {
|
|
11031
|
+
const upgradeIcon = pkg.upgradeType === "major" ? "\u{1F534}" : pkg.upgradeType === "minor" ? "\u{1F7E1}" : "\u{1F7E2}";
|
|
11032
|
+
table.push([
|
|
11033
|
+
`${upgradeIcon} ${pkg.name}`,
|
|
11034
|
+
chalk11.dim(pkg.current),
|
|
11035
|
+
chalk11.green(pkg.latest),
|
|
11036
|
+
pkg.type === "dev" ? chalk11.dim("dev") : "dep"
|
|
11037
|
+
]);
|
|
11038
|
+
}
|
|
11039
|
+
console.log(table.toString());
|
|
11040
|
+
console.log();
|
|
11041
|
+
if (result.isLaravel) {
|
|
11042
|
+
const blockedPackages = result.packages.filter((p) => p.blockedReason);
|
|
11043
|
+
if (blockedPackages.length > 0) {
|
|
11044
|
+
console.log(chalk11.yellow("\u26A0\uFE0F Laravel major version upgrades blocked (nguy hi\u1EC3m):"));
|
|
11045
|
+
for (const pkg of blockedPackages) {
|
|
11046
|
+
console.log(chalk11.dim(` - ${pkg.name}: ${pkg.current} \u2192 ${pkg.latest}`));
|
|
11047
|
+
}
|
|
11048
|
+
console.log();
|
|
11049
|
+
console.log(chalk11.dim("\u{1F4A1} \u0110\u1EC3 n\xE2ng c\u1EA5p Laravel major version, ch\u1EA1y th\u1EE7 c\xF4ng:"));
|
|
11050
|
+
console.log(chalk11.dim(` composer require ${blockedPackages[0].name}:^${blockedPackages[0].latest}`));
|
|
11051
|
+
console.log();
|
|
11052
|
+
}
|
|
11053
|
+
}
|
|
11054
|
+
}
|
|
11055
|
+
|
|
11056
|
+
// src/commands/deps/upgrade.ts
|
|
11057
|
+
import { Command as Command37 } from "commander";
|
|
11058
|
+
import { checkbox as checkbox3, confirm as confirm6 } from "@inquirer/prompts";
|
|
11059
|
+
import chalk12 from "chalk";
|
|
11060
|
+
import ora2 from "ora";
|
|
11061
|
+
import Table4 from "cli-table3";
|
|
11062
|
+
function createDepsUpgradeCommand() {
|
|
11063
|
+
return new Command37("upgrade").description("Upgrade packages l\xEAn version m\u1EDBi nh\u1EA5t").option("--all", "Upgrade t\u1EA5t c\u1EA3 kh\xF4ng c\u1EA7n ch\u1ECDn").action(async (options) => {
|
|
11064
|
+
await handleDepsUpgrade(options);
|
|
11065
|
+
});
|
|
11066
|
+
}
|
|
11067
|
+
async function handleDepsUpgrade(options) {
|
|
11068
|
+
const cwd = process.cwd();
|
|
11069
|
+
const detector = new DepsDetectorService();
|
|
11070
|
+
try {
|
|
11071
|
+
const spinner = ora2("\u0110ang ph\xE1t hi\u1EC7n lo\u1EA1i project...").start();
|
|
11072
|
+
const projects = await detector.detectProjects(cwd);
|
|
11073
|
+
if (projects.length === 0) {
|
|
11074
|
+
spinner.fail("Kh\xF4ng t\xECm th\u1EA5y project n\xE0o \u0111\u01B0\u1EE3c h\u1ED7 tr\u1EE3");
|
|
11075
|
+
console.log();
|
|
11076
|
+
console.log(chalk12.dim("H\u1ED7 tr\u1EE3: Node.js (package.json), PHP (composer.json), Python (requirements.txt/Pipfile)"));
|
|
11077
|
+
process.exit(1);
|
|
11078
|
+
}
|
|
11079
|
+
spinner.succeed(`Ph\xE1t hi\u1EC7n ${projects.length} project:`);
|
|
11080
|
+
for (const project of projects) {
|
|
11081
|
+
const icon = getEcosystemIcon(project.ecosystem);
|
|
11082
|
+
const label = getEcosystemLabel(project.ecosystem);
|
|
11083
|
+
console.log(` ${icon} ${label} (${project.manager})`);
|
|
11084
|
+
}
|
|
11085
|
+
console.log();
|
|
11086
|
+
for (const project of projects) {
|
|
11087
|
+
await upgradeEcosystem(project, cwd, options);
|
|
11088
|
+
}
|
|
11089
|
+
console.log();
|
|
11090
|
+
console.log(chalk12.green("\u2705 Ho\xE0n th\xE0nh!"));
|
|
11091
|
+
} catch (error) {
|
|
11092
|
+
console.error(chalk12.red(`
|
|
11093
|
+
\u274C ${error.message}
|
|
11094
|
+
`));
|
|
10659
11095
|
process.exit(1);
|
|
10660
11096
|
}
|
|
10661
11097
|
}
|
|
10662
|
-
function
|
|
10663
|
-
|
|
10664
|
-
|
|
11098
|
+
async function upgradeEcosystem(project, cwd, options) {
|
|
11099
|
+
const service = getService(project.ecosystem);
|
|
11100
|
+
const label = `${getEcosystemIcon(project.ecosystem)} ${getEcosystemLabel(project.ecosystem)}`;
|
|
11101
|
+
console.log(chalk12.bold.cyan(`
|
|
11102
|
+
${"\u2501".repeat(80)}`));
|
|
11103
|
+
console.log(chalk12.bold.cyan(`${label}`));
|
|
11104
|
+
console.log(chalk12.bold.cyan("\u2501".repeat(80)));
|
|
11105
|
+
console.log();
|
|
11106
|
+
const spinner = ora2("\u0110ang ki\u1EC3m tra packages...").start();
|
|
11107
|
+
let packages;
|
|
11108
|
+
try {
|
|
11109
|
+
const result = await service.check(cwd, (completed, total) => {
|
|
11110
|
+
spinner.text = `\u0110ang ki\u1EC3m tra packages... (${completed}/${total})`;
|
|
11111
|
+
});
|
|
11112
|
+
packages = result.packages;
|
|
11113
|
+
} catch (error) {
|
|
11114
|
+
spinner.fail("L\u1ED7i ki\u1EC3m tra packages");
|
|
11115
|
+
console.log(chalk12.red(error.message));
|
|
11116
|
+
return;
|
|
11117
|
+
}
|
|
11118
|
+
if (packages.length === 0) {
|
|
11119
|
+
spinner.succeed("T\u1EA5t c\u1EA3 packages \u0111\xE3 c\u1EADp nh\u1EADt");
|
|
11120
|
+
return;
|
|
11121
|
+
}
|
|
11122
|
+
spinner.succeed(`T\xECm th\u1EA5y ${packages.length} packages c\xF3 th\u1EC3 n\xE2ng c\u1EA5p`);
|
|
11123
|
+
console.log();
|
|
11124
|
+
displayUpgradeTable(packages);
|
|
11125
|
+
let selectedPackages;
|
|
11126
|
+
if (options.all) {
|
|
11127
|
+
selectedPackages = packages;
|
|
11128
|
+
console.log(chalk12.cyan(`\u{1F4CB} \u0110\xE3 ch\u1ECDn t\u1EA5t c\u1EA3 ${selectedPackages.length} packages
|
|
11129
|
+
`));
|
|
11130
|
+
} else {
|
|
11131
|
+
try {
|
|
11132
|
+
const selected = await checkbox3({
|
|
11133
|
+
message: "Ch\u1ECDn packages \u0111\u1EC3 upgrade (\u2191\u2193 di chuy\u1EC3n \u2022 space ch\u1ECDn \u2022 a t\u1EA5t c\u1EA3 \u2022 i \u0111\u1EA3o \u2022 \u23CE x\xE1c nh\u1EADn):",
|
|
11134
|
+
choices: packages.map((pkg) => {
|
|
11135
|
+
const icon = pkg.upgradeType === "major" ? "\u{1F534}" : pkg.upgradeType === "minor" ? "\u{1F7E1}" : "\u{1F7E2}";
|
|
11136
|
+
return {
|
|
11137
|
+
name: `${icon} ${pkg.name} (${pkg.current} \u2192 ${pkg.latest}) [${pkg.type}]`,
|
|
11138
|
+
value: pkg.name,
|
|
11139
|
+
checked: true
|
|
11140
|
+
};
|
|
11141
|
+
}),
|
|
11142
|
+
pageSize: 15,
|
|
11143
|
+
theme: checkboxTheme
|
|
11144
|
+
});
|
|
11145
|
+
if (selected.length === 0) {
|
|
11146
|
+
console.log(chalk12.yellow("\u23F8\uFE0F Kh\xF4ng c\xF3 packages n\xE0o \u0111\u01B0\u1EE3c ch\u1ECDn\n"));
|
|
11147
|
+
return;
|
|
11148
|
+
}
|
|
11149
|
+
selectedPackages = packages.filter((p) => selected.includes(p.name));
|
|
11150
|
+
} catch {
|
|
11151
|
+
console.log(chalk12.yellow("\n\u23F8\uFE0F \u0110\xE3 h\u1EE7y\n"));
|
|
11152
|
+
return;
|
|
11153
|
+
}
|
|
11154
|
+
}
|
|
11155
|
+
let shouldProceed;
|
|
11156
|
+
try {
|
|
11157
|
+
shouldProceed = await confirm6({
|
|
11158
|
+
message: `Ti\u1EBFn h\xE0nh upgrade ${selectedPackages.length} packages? (ESC \u0111\u1EC3 h\u1EE7y)`,
|
|
11159
|
+
default: true
|
|
11160
|
+
});
|
|
11161
|
+
} catch {
|
|
11162
|
+
console.log(chalk12.yellow("\n\u23F8\uFE0F \u0110\xE3 h\u1EE7y\n"));
|
|
11163
|
+
return;
|
|
11164
|
+
}
|
|
11165
|
+
if (!shouldProceed) {
|
|
11166
|
+
console.log(chalk12.yellow("\u23F8\uFE0F Upgrade \u0111\xE3 h\u1EE7y\n"));
|
|
11167
|
+
return;
|
|
11168
|
+
}
|
|
11169
|
+
console.log();
|
|
11170
|
+
console.log(chalk12.cyan(`\u{1F527} Package manager: ${project.manager}`));
|
|
11171
|
+
console.log(chalk12.cyan("\u{1F4E5} \u0110ang upgrade...\n"));
|
|
11172
|
+
const commands = service.getUpgradeCommands(selectedPackages);
|
|
11173
|
+
try {
|
|
11174
|
+
if (commands.deps) {
|
|
11175
|
+
console.log(chalk12.dim(`$ ${commands.deps}
|
|
11176
|
+
`));
|
|
11177
|
+
}
|
|
11178
|
+
if (commands.devDeps) {
|
|
11179
|
+
console.log(chalk12.dim(`$ ${commands.devDeps}
|
|
11180
|
+
`));
|
|
11181
|
+
}
|
|
11182
|
+
await service.upgrade(cwd, { packages: selectedPackages });
|
|
11183
|
+
console.log(chalk12.green(`
|
|
11184
|
+
\u2705 \u0110\xE3 upgrade ${selectedPackages.length} packages th\xE0nh c\xF4ng!`));
|
|
11185
|
+
} catch (error) {
|
|
11186
|
+
console.error(chalk12.red("\n\u274C L\u1ED7i khi upgrade:"));
|
|
11187
|
+
console.error(chalk12.red(error.message));
|
|
11188
|
+
console.log(chalk12.yellow("\n\u{1F4A1} B\u1EA1n c\xF3 th\u1EC3 th\u1EED upgrade th\u1EE7 c\xF4ng:"));
|
|
11189
|
+
if (commands.deps) console.log(chalk12.cyan(` ${commands.deps}`));
|
|
11190
|
+
if (commands.devDeps) console.log(chalk12.cyan(` ${commands.devDeps}`));
|
|
11191
|
+
console.log();
|
|
11192
|
+
throw error;
|
|
10665
11193
|
}
|
|
10666
|
-
return true;
|
|
10667
11194
|
}
|
|
10668
11195
|
function displayUpgradeTable(packages) {
|
|
10669
|
-
|
|
10670
|
-
|
|
10671
|
-
|
|
10672
|
-
|
|
10673
|
-
|
|
10674
|
-
|
|
10675
|
-
|
|
10676
|
-
|
|
10677
|
-
|
|
10678
|
-
|
|
10679
|
-
|
|
10680
|
-
|
|
11196
|
+
const table = new Table4({
|
|
11197
|
+
head: [
|
|
11198
|
+
chalk12.cyan("Package"),
|
|
11199
|
+
chalk12.cyan("Hi\u1EC7n t\u1EA1i"),
|
|
11200
|
+
chalk12.cyan("M\u1EDBi nh\u1EA5t"),
|
|
11201
|
+
chalk12.cyan("Lo\u1EA1i")
|
|
11202
|
+
],
|
|
11203
|
+
style: {
|
|
11204
|
+
head: [],
|
|
11205
|
+
border: ["dim"]
|
|
11206
|
+
}
|
|
11207
|
+
});
|
|
10681
11208
|
for (const pkg of packages) {
|
|
10682
|
-
const
|
|
10683
|
-
|
|
10684
|
-
|
|
10685
|
-
|
|
10686
|
-
|
|
11209
|
+
const upgradeIcon = pkg.upgradeType === "major" ? "\u{1F534}" : pkg.upgradeType === "minor" ? "\u{1F7E1}" : "\u{1F7E2}";
|
|
11210
|
+
table.push([
|
|
11211
|
+
`${upgradeIcon} ${pkg.name}`,
|
|
11212
|
+
chalk12.dim(pkg.current),
|
|
11213
|
+
chalk12.green(pkg.latest),
|
|
11214
|
+
pkg.type === "dev" ? chalk12.dim("dev") : "dep"
|
|
11215
|
+
]);
|
|
11216
|
+
}
|
|
11217
|
+
console.log(table.toString());
|
|
11218
|
+
console.log();
|
|
11219
|
+
}
|
|
11220
|
+
function getService(ecosystem) {
|
|
11221
|
+
switch (ecosystem) {
|
|
11222
|
+
case "node":
|
|
11223
|
+
return new DepsService();
|
|
11224
|
+
case "php":
|
|
11225
|
+
return new DepsPhpService();
|
|
11226
|
+
case "python":
|
|
11227
|
+
return new DepsPythonService();
|
|
11228
|
+
}
|
|
11229
|
+
}
|
|
11230
|
+
function getEcosystemIcon(ecosystem) {
|
|
11231
|
+
switch (ecosystem) {
|
|
11232
|
+
case "node":
|
|
11233
|
+
return "\u{1F4E6}";
|
|
11234
|
+
case "php":
|
|
11235
|
+
return "\u{1F418}";
|
|
11236
|
+
case "python":
|
|
11237
|
+
return "\u{1F40D}";
|
|
11238
|
+
default:
|
|
11239
|
+
return "\u{1F4E6}";
|
|
11240
|
+
}
|
|
11241
|
+
}
|
|
11242
|
+
function getEcosystemLabel(ecosystem) {
|
|
11243
|
+
switch (ecosystem) {
|
|
11244
|
+
case "node":
|
|
11245
|
+
return "Node.js";
|
|
11246
|
+
case "php":
|
|
11247
|
+
return "PHP/Composer";
|
|
11248
|
+
case "python":
|
|
11249
|
+
return "Python";
|
|
11250
|
+
default:
|
|
11251
|
+
return ecosystem;
|
|
10687
11252
|
}
|
|
10688
|
-
console.log(bottomBorder);
|
|
10689
|
-
console.log("");
|
|
10690
11253
|
}
|
|
10691
11254
|
|
|
10692
11255
|
// src/commands/deps/index.ts
|
|
10693
11256
|
function showDepsHelp() {
|
|
10694
|
-
console.log(
|
|
11257
|
+
console.log(chalk13.bold.cyan("\u{1F4E6} jai1 deps") + chalk13.dim(" - Qu\u1EA3n l\xFD dependencies trong project"));
|
|
11258
|
+
console.log();
|
|
11259
|
+
console.log(chalk13.bold("C\xE1c l\u1EC7nh:"));
|
|
11260
|
+
console.log(` ${chalk13.cyan("check")} Ki\u1EC3m tra c\xE1c packages c\u1EA7n upgrade`);
|
|
11261
|
+
console.log(` ${chalk13.cyan("upgrade")} N\xE2ng c\u1EA5p dependencies l\xEAn phi\xEAn b\u1EA3n m\u1EDBi nh\u1EA5t`);
|
|
10695
11262
|
console.log();
|
|
10696
|
-
console.log(
|
|
10697
|
-
console.log(
|
|
11263
|
+
console.log(chalk13.bold("H\u1ED7 tr\u1EE3:"));
|
|
11264
|
+
console.log(chalk13.dim(" \u2022 Node.js (npm, pnpm, yarn, bun)"));
|
|
11265
|
+
console.log(chalk13.dim(" \u2022 PHP/Composer (v\u1EDBi b\u1EA3o v\u1EC7 Laravel major version)"));
|
|
11266
|
+
console.log(chalk13.dim(" \u2022 Python (pip, pipenv)"));
|
|
10698
11267
|
console.log();
|
|
10699
|
-
console.log(
|
|
10700
|
-
console.log(
|
|
10701
|
-
console.log(
|
|
11268
|
+
console.log(chalk13.bold("V\xED d\u1EE5:"));
|
|
11269
|
+
console.log(chalk13.dim(" $ jai1 deps check"));
|
|
11270
|
+
console.log(chalk13.dim(" $ jai1 deps upgrade"));
|
|
11271
|
+
console.log(chalk13.dim(" $ jai1 deps upgrade --all"));
|
|
10702
11272
|
console.log();
|
|
10703
|
-
console.log(
|
|
11273
|
+
console.log(chalk13.dim('Ch\u1EA1y "jai1 deps <l\u1EC7nh> --help" \u0111\u1EC3 xem chi ti\u1EBFt'));
|
|
10704
11274
|
}
|
|
10705
11275
|
function createDepsCommand() {
|
|
10706
|
-
const depsCommand = new
|
|
11276
|
+
const depsCommand = new Command38("deps").description("Qu\u1EA3n l\xFD dependencies trong project").action(() => {
|
|
10707
11277
|
showDepsHelp();
|
|
10708
11278
|
});
|
|
11279
|
+
depsCommand.addCommand(createDepsCheckCommand());
|
|
10709
11280
|
depsCommand.addCommand(createDepsUpgradeCommand());
|
|
10710
11281
|
return depsCommand;
|
|
10711
11282
|
}
|
|
10712
11283
|
|
|
10713
11284
|
// src/commands/kit/index.ts
|
|
10714
|
-
import { Command as
|
|
10715
|
-
import
|
|
11285
|
+
import { Command as Command42 } from "commander";
|
|
11286
|
+
import chalk15 from "chalk";
|
|
10716
11287
|
|
|
10717
11288
|
// src/commands/kit/list.ts
|
|
10718
|
-
import { Command as
|
|
10719
|
-
import
|
|
10720
|
-
import
|
|
11289
|
+
import { Command as Command39 } from "commander";
|
|
11290
|
+
import chalk14 from "chalk";
|
|
11291
|
+
import Table5 from "cli-table3";
|
|
10721
11292
|
|
|
10722
11293
|
// src/services/starter-kit.service.ts
|
|
10723
|
-
import { promises as
|
|
10724
|
-
import { join as
|
|
11294
|
+
import { promises as fs17 } from "fs";
|
|
11295
|
+
import { join as join7 } from "path";
|
|
10725
11296
|
import AdmZip from "adm-zip";
|
|
10726
11297
|
var StarterKitService = class {
|
|
10727
11298
|
/**
|
|
@@ -10768,29 +11339,29 @@ var StarterKitService = class {
|
|
|
10768
11339
|
throw new NetworkError(`Failed to download kit: HTTP ${response.status}`);
|
|
10769
11340
|
}
|
|
10770
11341
|
if (onProgress) onProgress(30);
|
|
10771
|
-
const tmpDir =
|
|
10772
|
-
await
|
|
10773
|
-
const tmpFile =
|
|
11342
|
+
const tmpDir = join7(process.env.TMPDIR || "/tmp", "jai1-kits");
|
|
11343
|
+
await fs17.mkdir(tmpDir, { recursive: true });
|
|
11344
|
+
const tmpFile = join7(tmpDir, `${slug}.zip`);
|
|
10774
11345
|
const buffer = await response.arrayBuffer();
|
|
10775
|
-
await
|
|
11346
|
+
await fs17.writeFile(tmpFile, Buffer.from(buffer));
|
|
10776
11347
|
if (onProgress) onProgress(60);
|
|
10777
11348
|
const zip = new AdmZip(tmpFile);
|
|
10778
|
-
await
|
|
11349
|
+
await fs17.mkdir(targetDir, { recursive: true });
|
|
10779
11350
|
zip.extractAllTo(targetDir, true);
|
|
10780
11351
|
if (onProgress) onProgress(100);
|
|
10781
|
-
await
|
|
11352
|
+
await fs17.unlink(tmpFile);
|
|
10782
11353
|
}
|
|
10783
11354
|
};
|
|
10784
11355
|
|
|
10785
11356
|
// src/commands/kit/list.ts
|
|
10786
11357
|
function createKitListCommand() {
|
|
10787
|
-
return new
|
|
11358
|
+
return new Command39("list").description("List available starter kits").option("-c, --category <category>", "Filter by category (backend, frontend, fullstack)").option("-s, --search <term>", "Search kits by name or description").action(async (options) => {
|
|
10788
11359
|
const configService = new ConfigService();
|
|
10789
11360
|
const config = await configService.load();
|
|
10790
11361
|
if (!config) {
|
|
10791
11362
|
throw new ValidationError('Not initialized. Run "jai1 auth" first.');
|
|
10792
11363
|
}
|
|
10793
|
-
console.log(
|
|
11364
|
+
console.log(chalk14.cyan("\u{1F4E6} \u0110ang t\u1EA3i danh s\xE1ch starter kits..."));
|
|
10794
11365
|
console.log();
|
|
10795
11366
|
const kitService = new StarterKitService();
|
|
10796
11367
|
const kits = await kitService.list(config, {
|
|
@@ -10798,9 +11369,9 @@ function createKitListCommand() {
|
|
|
10798
11369
|
search: options.search
|
|
10799
11370
|
});
|
|
10800
11371
|
if (kits.length === 0) {
|
|
10801
|
-
console.log(
|
|
11372
|
+
console.log(chalk14.yellow("Kh\xF4ng t\xECm th\u1EA5y starter kits n\xE0o."));
|
|
10802
11373
|
if (options.category || options.search) {
|
|
10803
|
-
console.log(
|
|
11374
|
+
console.log(chalk14.dim("Th\u1EED b\u1ECF filter \u0111\u1EC3 xem t\u1EA5t c\u1EA3."));
|
|
10804
11375
|
}
|
|
10805
11376
|
return;
|
|
10806
11377
|
}
|
|
@@ -10824,35 +11395,35 @@ function createKitListCommand() {
|
|
|
10824
11395
|
const categoryKits = byCategory[category];
|
|
10825
11396
|
const categoryIcon = category === "frontend" ? "\u{1F3A8}" : category === "backend" ? "\u2699\uFE0F" : category === "fullstack" ? "\u{1F680}" : "\u{1F4E6}";
|
|
10826
11397
|
console.log(
|
|
10827
|
-
|
|
11398
|
+
chalk14.bold(`${categoryIcon} ${category.charAt(0).toUpperCase() + category.slice(1)}`)
|
|
10828
11399
|
);
|
|
10829
|
-
const table = new
|
|
11400
|
+
const table = new Table5({
|
|
10830
11401
|
head: [
|
|
10831
|
-
|
|
10832
|
-
|
|
10833
|
-
|
|
11402
|
+
chalk14.cyan("Slug"),
|
|
11403
|
+
chalk14.cyan("M\xF4 t\u1EA3"),
|
|
11404
|
+
chalk14.cyan("Version")
|
|
10834
11405
|
],
|
|
10835
11406
|
style: { head: [], border: ["gray"] }
|
|
10836
11407
|
});
|
|
10837
11408
|
for (const kit of categoryKits) {
|
|
10838
11409
|
table.push([
|
|
10839
|
-
|
|
10840
|
-
|
|
10841
|
-
|
|
11410
|
+
chalk14.white(kit.slug),
|
|
11411
|
+
chalk14.dim(kit.description.slice(0, 50)),
|
|
11412
|
+
chalk14.green(`v${kit.version}`)
|
|
10842
11413
|
]);
|
|
10843
11414
|
}
|
|
10844
11415
|
console.log(table.toString());
|
|
10845
11416
|
console.log();
|
|
10846
11417
|
}
|
|
10847
|
-
console.log(
|
|
10848
|
-
console.log(
|
|
11418
|
+
console.log(chalk14.dim(`T\u1ED5ng c\u1ED9ng: ${kits.length} starter kit(s)`));
|
|
11419
|
+
console.log(chalk14.dim('\n\u{1F4A1} Ch\u1EA1y "jai1 kit create <slug>" \u0111\u1EC3 t\u1EA1o project m\u1EDBi'));
|
|
10849
11420
|
});
|
|
10850
11421
|
}
|
|
10851
11422
|
|
|
10852
11423
|
// src/commands/kit/info.ts
|
|
10853
|
-
import { Command as
|
|
11424
|
+
import { Command as Command40 } from "commander";
|
|
10854
11425
|
function createKitInfoCommand() {
|
|
10855
|
-
return new
|
|
11426
|
+
return new Command40("info").description("Show detailed information about a starter kit").argument("<slug>", "Starter kit slug").action(async (slug) => {
|
|
10856
11427
|
const configService = new ConfigService();
|
|
10857
11428
|
const config = await configService.load();
|
|
10858
11429
|
if (!config) {
|
|
@@ -10901,9 +11472,9 @@ Post-Init Commands:`);
|
|
|
10901
11472
|
}
|
|
10902
11473
|
|
|
10903
11474
|
// src/commands/kit/create.ts
|
|
10904
|
-
import { Command as
|
|
10905
|
-
import { promises as
|
|
10906
|
-
import { join as
|
|
11475
|
+
import { Command as Command41 } from "commander";
|
|
11476
|
+
import { promises as fs18 } from "fs";
|
|
11477
|
+
import { join as join8 } from "path";
|
|
10907
11478
|
import { select as select3, input as input2, checkbox as checkbox4 } from "@inquirer/prompts";
|
|
10908
11479
|
import { execa as execa2 } from "execa";
|
|
10909
11480
|
|
|
@@ -10946,7 +11517,7 @@ var HookExecutor = class {
|
|
|
10946
11517
|
|
|
10947
11518
|
// src/commands/kit/create.ts
|
|
10948
11519
|
function createKitCreateCommand() {
|
|
10949
|
-
return new
|
|
11520
|
+
return new Command41("create").description("Create a new project from a starter kit").argument("<slug>", "Starter kit slug").argument("[directory]", "Project directory (default: ./<slug>)").option("-y, --yes", "Auto mode - use defaults, no prompts").option("--name <name>", "Project name").option("--skip-install", "Skip dependency installation").option("--skip-git", "Skip git initialization").option("--skip-framework", "Skip framework apply").option("--skip-ide", "Skip IDE sync").action(async (slug, directory, options) => {
|
|
10950
11521
|
const configService = new ConfigService();
|
|
10951
11522
|
const config = await configService.load();
|
|
10952
11523
|
if (!config) {
|
|
@@ -10966,9 +11537,9 @@ function createKitCreateCommand() {
|
|
|
10966
11537
|
}
|
|
10967
11538
|
}
|
|
10968
11539
|
}
|
|
10969
|
-
const targetDir = directory ||
|
|
11540
|
+
const targetDir = directory || join8(process.cwd(), options.name || slug);
|
|
10970
11541
|
try {
|
|
10971
|
-
await
|
|
11542
|
+
await fs18.access(targetDir);
|
|
10972
11543
|
throw new Error(`Directory already exists: ${targetDir}`);
|
|
10973
11544
|
} catch (error) {
|
|
10974
11545
|
if (error.code !== "ENOENT") {
|
|
@@ -11005,7 +11576,8 @@ function createKitCreateCommand() {
|
|
|
11005
11576
|
const answer = await select3({
|
|
11006
11577
|
message: v.prompt,
|
|
11007
11578
|
choices: v.options.map((opt) => ({ name: opt, value: opt })),
|
|
11008
|
-
default: v.default
|
|
11579
|
+
default: v.default,
|
|
11580
|
+
theme: selectTheme
|
|
11009
11581
|
});
|
|
11010
11582
|
variables[v.name] = answer;
|
|
11011
11583
|
} else {
|
|
@@ -11024,9 +11596,13 @@ function createKitCreateCommand() {
|
|
|
11024
11596
|
if (!options.skipFramework && kit.config.framework?.apply) {
|
|
11025
11597
|
console.log("\u{1F916} Applying jai1 framework...");
|
|
11026
11598
|
const componentsService = new ComponentsService(targetDir);
|
|
11027
|
-
|
|
11599
|
+
const expandedPaths = await componentsService.expandPaths(
|
|
11600
|
+
config,
|
|
11601
|
+
kit.config.framework.components
|
|
11602
|
+
);
|
|
11603
|
+
for (const filepath of expandedPaths) {
|
|
11028
11604
|
console.log(` \u{1F4E5} Installing ${filepath}...`);
|
|
11029
|
-
await componentsService.install(config, filepath);
|
|
11605
|
+
await componentsService.install(config, filepath, join8(targetDir, ".jai1"));
|
|
11030
11606
|
}
|
|
11031
11607
|
console.log(" \u2713 Framework components applied");
|
|
11032
11608
|
}
|
|
@@ -11064,7 +11640,8 @@ function createKitCreateCommand() {
|
|
|
11064
11640
|
choices: ideChoices.map((c) => ({
|
|
11065
11641
|
...c,
|
|
11066
11642
|
checked: defaultTargets.includes(c.value)
|
|
11067
|
-
}))
|
|
11643
|
+
})),
|
|
11644
|
+
theme: checkboxTheme
|
|
11068
11645
|
});
|
|
11069
11646
|
selectedIdes = answer;
|
|
11070
11647
|
}
|
|
@@ -11086,7 +11663,7 @@ function createKitCreateCommand() {
|
|
|
11086
11663
|
async function applyVariableSubstitution(dir, variables) {
|
|
11087
11664
|
const files = await getAllFiles(dir);
|
|
11088
11665
|
for (const file of files) {
|
|
11089
|
-
let content = await
|
|
11666
|
+
let content = await fs18.readFile(file, "utf-8");
|
|
11090
11667
|
let modified = false;
|
|
11091
11668
|
for (const [key, value] of Object.entries(variables)) {
|
|
11092
11669
|
const regex = new RegExp(`\\{\\{${key}\\}\\}`, "g");
|
|
@@ -11096,15 +11673,15 @@ async function applyVariableSubstitution(dir, variables) {
|
|
|
11096
11673
|
}
|
|
11097
11674
|
}
|
|
11098
11675
|
if (modified) {
|
|
11099
|
-
await
|
|
11676
|
+
await fs18.writeFile(file, content, "utf-8");
|
|
11100
11677
|
}
|
|
11101
11678
|
}
|
|
11102
11679
|
}
|
|
11103
11680
|
async function getAllFiles(dir) {
|
|
11104
11681
|
const files = [];
|
|
11105
|
-
const entries = await
|
|
11682
|
+
const entries = await fs18.readdir(dir, { withFileTypes: true });
|
|
11106
11683
|
for (const entry of entries) {
|
|
11107
|
-
const fullPath =
|
|
11684
|
+
const fullPath = join8(dir, entry.name);
|
|
11108
11685
|
if (entry.isDirectory()) {
|
|
11109
11686
|
if (!entry.name.startsWith(".") && entry.name !== "node_modules") {
|
|
11110
11687
|
files.push(...await getAllFiles(fullPath));
|
|
@@ -11118,23 +11695,23 @@ async function getAllFiles(dir) {
|
|
|
11118
11695
|
|
|
11119
11696
|
// src/commands/kit/index.ts
|
|
11120
11697
|
function showKitHelp() {
|
|
11121
|
-
console.log(
|
|
11698
|
+
console.log(chalk15.bold.cyan("\u{1F4E6} jai1 kit") + chalk15.dim(" - Qu\u1EA3n l\xFD starter kits"));
|
|
11122
11699
|
console.log();
|
|
11123
|
-
console.log(
|
|
11124
|
-
console.log(` ${
|
|
11125
|
-
console.log(` ${
|
|
11126
|
-
console.log(` ${
|
|
11700
|
+
console.log(chalk15.bold("C\xE1c l\u1EC7nh:"));
|
|
11701
|
+
console.log(` ${chalk15.cyan("list")} Li\u1EC7t k\xEA c\xE1c starter kits c\xF3 s\u1EB5n`);
|
|
11702
|
+
console.log(` ${chalk15.cyan("info")} Xem chi ti\u1EBFt m\u1ED9t starter kit`);
|
|
11703
|
+
console.log(` ${chalk15.cyan("create")} T\u1EA1o project m\u1EDBi t\u1EEB starter kit`);
|
|
11127
11704
|
console.log();
|
|
11128
|
-
console.log(
|
|
11129
|
-
console.log(
|
|
11130
|
-
console.log(
|
|
11131
|
-
console.log(
|
|
11132
|
-
console.log(
|
|
11705
|
+
console.log(chalk15.bold("V\xED d\u1EE5:"));
|
|
11706
|
+
console.log(chalk15.dim(" $ jai1 kit list"));
|
|
11707
|
+
console.log(chalk15.dim(" $ jai1 kit list --category frontend"));
|
|
11708
|
+
console.log(chalk15.dim(" $ jai1 kit info next-tw4-shadcn"));
|
|
11709
|
+
console.log(chalk15.dim(" $ jai1 kit create next-tw4-shadcn my-project"));
|
|
11133
11710
|
console.log();
|
|
11134
|
-
console.log(
|
|
11711
|
+
console.log(chalk15.dim('Ch\u1EA1y "jai1 kit <l\u1EC7nh> --help" \u0111\u1EC3 xem chi ti\u1EBFt'));
|
|
11135
11712
|
}
|
|
11136
11713
|
function createKitCommand() {
|
|
11137
|
-
const cmd = new
|
|
11714
|
+
const cmd = new Command42("kit").description("Manage starter kits for new projects").action(() => {
|
|
11138
11715
|
showKitHelp();
|
|
11139
11716
|
});
|
|
11140
11717
|
cmd.addCommand(createKitListCommand());
|
|
@@ -11144,21 +11721,21 @@ function createKitCommand() {
|
|
|
11144
11721
|
}
|
|
11145
11722
|
|
|
11146
11723
|
// src/commands/rules/index.ts
|
|
11147
|
-
import { Command as
|
|
11148
|
-
import
|
|
11724
|
+
import { Command as Command49 } from "commander";
|
|
11725
|
+
import chalk17 from "chalk";
|
|
11149
11726
|
|
|
11150
11727
|
// src/commands/rules/list.ts
|
|
11151
|
-
import { Command as
|
|
11152
|
-
import
|
|
11153
|
-
import
|
|
11728
|
+
import { Command as Command43 } from "commander";
|
|
11729
|
+
import chalk16 from "chalk";
|
|
11730
|
+
import Table6 from "cli-table3";
|
|
11154
11731
|
function createRulesListCommand() {
|
|
11155
|
-
return new
|
|
11732
|
+
return new Command43("list").description("List available rule presets").option("--json", "Output as JSON").action(async (options) => {
|
|
11156
11733
|
const configService = new ConfigService();
|
|
11157
11734
|
const config = await configService.load();
|
|
11158
11735
|
if (!config) {
|
|
11159
11736
|
throw new ValidationError('Not initialized. Run "jai1 auth" first.');
|
|
11160
11737
|
}
|
|
11161
|
-
console.log(
|
|
11738
|
+
console.log(chalk16.cyan("\u{1F4CB} \u0110ang t\u1EA3i danh s\xE1ch rule presets..."));
|
|
11162
11739
|
console.log();
|
|
11163
11740
|
try {
|
|
11164
11741
|
const response = await fetch(`${config.apiUrl}/api/rules/presets`, {
|
|
@@ -11175,23 +11752,23 @@ function createRulesListCommand() {
|
|
|
11175
11752
|
return;
|
|
11176
11753
|
}
|
|
11177
11754
|
if (data.total === 0) {
|
|
11178
|
-
console.log(
|
|
11755
|
+
console.log(chalk16.yellow("Kh\xF4ng c\xF3 presets n\xE0o."));
|
|
11179
11756
|
return;
|
|
11180
11757
|
}
|
|
11181
11758
|
console.log(
|
|
11182
|
-
|
|
11759
|
+
chalk16.green(`\u2713 T\xECm th\u1EA5y ${chalk16.bold(data.total)} preset${data.total > 1 ? "s" : ""}`)
|
|
11183
11760
|
);
|
|
11184
11761
|
console.log();
|
|
11185
11762
|
for (const preset of data.presets) {
|
|
11186
|
-
console.log(
|
|
11187
|
-
const table = new
|
|
11763
|
+
console.log(chalk16.bold.cyan(`\u{1F4E6} ${preset.slug}`));
|
|
11764
|
+
const table = new Table6({
|
|
11188
11765
|
style: { head: [], border: ["gray"], compact: true },
|
|
11189
11766
|
colWidths: [15, 55]
|
|
11190
11767
|
});
|
|
11191
11768
|
table.push(
|
|
11192
|
-
[
|
|
11193
|
-
[
|
|
11194
|
-
[
|
|
11769
|
+
[chalk16.dim("T\xEAn"), chalk16.white(preset.name)],
|
|
11770
|
+
[chalk16.dim("M\xF4 t\u1EA3"), chalk16.white(preset.description)],
|
|
11771
|
+
[chalk16.dim("Version"), chalk16.green(`v${preset.version}`)]
|
|
11195
11772
|
);
|
|
11196
11773
|
const stackParts = [];
|
|
11197
11774
|
if (preset.stack.frontend) stackParts.push(preset.stack.frontend);
|
|
@@ -11199,16 +11776,16 @@ function createRulesListCommand() {
|
|
|
11199
11776
|
if (preset.stack.css) stackParts.push(preset.stack.css);
|
|
11200
11777
|
if (preset.stack.database) stackParts.push(preset.stack.database);
|
|
11201
11778
|
if (stackParts.length > 0) {
|
|
11202
|
-
table.push([
|
|
11779
|
+
table.push([chalk16.dim("Stack"), chalk16.yellow(stackParts.join(" + "))]);
|
|
11203
11780
|
}
|
|
11204
11781
|
table.push(
|
|
11205
|
-
[
|
|
11206
|
-
[
|
|
11782
|
+
[chalk16.dim("Tags"), chalk16.dim(preset.tags.join(", ") || "-")],
|
|
11783
|
+
[chalk16.dim("Downloads"), chalk16.white(preset.downloads.toString())]
|
|
11207
11784
|
);
|
|
11208
11785
|
console.log(table.toString());
|
|
11209
11786
|
console.log();
|
|
11210
11787
|
}
|
|
11211
|
-
console.log(
|
|
11788
|
+
console.log(chalk16.dim('\u{1F4A1} Ch\u1EA1y "jai1 rules apply <name>" \u0111\u1EC3 \xE1p d\u1EE5ng preset'));
|
|
11212
11789
|
} catch (error) {
|
|
11213
11790
|
throw new Error(
|
|
11214
11791
|
`L\u1ED7i khi t\u1EA3i presets: ${error instanceof Error ? error.message : String(error)}`
|
|
@@ -11218,12 +11795,132 @@ function createRulesListCommand() {
|
|
|
11218
11795
|
}
|
|
11219
11796
|
|
|
11220
11797
|
// src/commands/rules/init.ts
|
|
11221
|
-
import { Command as
|
|
11222
|
-
import { promises as
|
|
11223
|
-
import { join as
|
|
11798
|
+
import { Command as Command44 } from "commander";
|
|
11799
|
+
import { promises as fs20 } from "fs";
|
|
11800
|
+
import { join as join10 } from "path";
|
|
11224
11801
|
import { select as select4, confirm as confirm7 } from "@inquirer/prompts";
|
|
11802
|
+
|
|
11803
|
+
// src/services/project-config.service.ts
|
|
11804
|
+
import { promises as fs19 } from "fs";
|
|
11805
|
+
import { join as join9 } from "path";
|
|
11806
|
+
var ProjectConfigService = class {
|
|
11807
|
+
projectRoot;
|
|
11808
|
+
configDir;
|
|
11809
|
+
configPath;
|
|
11810
|
+
constructor(projectRoot = process.cwd()) {
|
|
11811
|
+
this.projectRoot = projectRoot;
|
|
11812
|
+
this.configDir = join9(this.projectRoot, ".jai1");
|
|
11813
|
+
this.configPath = join9(this.configDir, "project.json");
|
|
11814
|
+
}
|
|
11815
|
+
/**
|
|
11816
|
+
* Check if config file exists
|
|
11817
|
+
*/
|
|
11818
|
+
async exists() {
|
|
11819
|
+
try {
|
|
11820
|
+
await fs19.access(this.configPath);
|
|
11821
|
+
return true;
|
|
11822
|
+
} catch {
|
|
11823
|
+
return false;
|
|
11824
|
+
}
|
|
11825
|
+
}
|
|
11826
|
+
/**
|
|
11827
|
+
* Load full project configuration
|
|
11828
|
+
* @returns Config object or null if not found
|
|
11829
|
+
*/
|
|
11830
|
+
async load() {
|
|
11831
|
+
if (!await this.exists()) {
|
|
11832
|
+
return null;
|
|
11833
|
+
}
|
|
11834
|
+
try {
|
|
11835
|
+
const content = await fs19.readFile(this.configPath, "utf-8");
|
|
11836
|
+
return JSON.parse(content);
|
|
11837
|
+
} catch (error) {
|
|
11838
|
+
throw new Error(
|
|
11839
|
+
`Failed to load project config: ${error instanceof Error ? error.message : String(error)}`
|
|
11840
|
+
);
|
|
11841
|
+
}
|
|
11842
|
+
}
|
|
11843
|
+
/**
|
|
11844
|
+
* Save full project configuration
|
|
11845
|
+
* Creates .jai1 directory if it doesn't exist
|
|
11846
|
+
*/
|
|
11847
|
+
async save(config) {
|
|
11848
|
+
try {
|
|
11849
|
+
await fs19.mkdir(this.configDir, { recursive: true });
|
|
11850
|
+
await fs19.writeFile(this.configPath, JSON.stringify(config, null, 2), "utf-8");
|
|
11851
|
+
} catch (error) {
|
|
11852
|
+
throw new Error(
|
|
11853
|
+
`Failed to save project config: ${error instanceof Error ? error.message : String(error)}`
|
|
11854
|
+
);
|
|
11855
|
+
}
|
|
11856
|
+
}
|
|
11857
|
+
/**
|
|
11858
|
+
* Load rules configuration only
|
|
11859
|
+
* @returns RulesConfig or null if not found
|
|
11860
|
+
*/
|
|
11861
|
+
async loadRules() {
|
|
11862
|
+
const config = await this.load();
|
|
11863
|
+
return config?.rules ?? null;
|
|
11864
|
+
}
|
|
11865
|
+
/**
|
|
11866
|
+
* Save rules configuration
|
|
11867
|
+
* Merges with existing config, preserving other sections
|
|
11868
|
+
*/
|
|
11869
|
+
async saveRules(rulesConfig) {
|
|
11870
|
+
const existingConfig = await this.load() ?? {};
|
|
11871
|
+
existingConfig.rules = rulesConfig;
|
|
11872
|
+
await this.save(existingConfig);
|
|
11873
|
+
}
|
|
11874
|
+
/**
|
|
11875
|
+
* Update rules configuration partially
|
|
11876
|
+
* Merges with existing rules config
|
|
11877
|
+
*/
|
|
11878
|
+
async updateRules(partialRulesConfig) {
|
|
11879
|
+
const existingRules = await this.loadRules();
|
|
11880
|
+
if (!existingRules) {
|
|
11881
|
+
throw new Error('No rules configuration found. Run "jai1 rules apply" first.');
|
|
11882
|
+
}
|
|
11883
|
+
const updatedRules = {
|
|
11884
|
+
...existingRules,
|
|
11885
|
+
...partialRulesConfig
|
|
11886
|
+
};
|
|
11887
|
+
await this.saveRules(updatedRules);
|
|
11888
|
+
}
|
|
11889
|
+
/**
|
|
11890
|
+
* Add a backup entry to rules config
|
|
11891
|
+
* Keeps only the last 5 backups
|
|
11892
|
+
*/
|
|
11893
|
+
async addBackup(backup) {
|
|
11894
|
+
const rules = await this.loadRules();
|
|
11895
|
+
if (!rules) {
|
|
11896
|
+
throw new Error("No rules configuration found.");
|
|
11897
|
+
}
|
|
11898
|
+
const backups = [backup, ...rules.backups ?? []].slice(0, 5);
|
|
11899
|
+
await this.updateRules({ backups });
|
|
11900
|
+
}
|
|
11901
|
+
/**
|
|
11902
|
+
* Get config file path
|
|
11903
|
+
*/
|
|
11904
|
+
getConfigPath() {
|
|
11905
|
+
return this.configPath;
|
|
11906
|
+
}
|
|
11907
|
+
/**
|
|
11908
|
+
* Get config directory path (.jai1/)
|
|
11909
|
+
*/
|
|
11910
|
+
getConfigDir() {
|
|
11911
|
+
return this.configDir;
|
|
11912
|
+
}
|
|
11913
|
+
/**
|
|
11914
|
+
* Get project root path
|
|
11915
|
+
*/
|
|
11916
|
+
getProjectRoot() {
|
|
11917
|
+
return this.projectRoot;
|
|
11918
|
+
}
|
|
11919
|
+
};
|
|
11920
|
+
|
|
11921
|
+
// src/commands/rules/init.ts
|
|
11225
11922
|
function createRulesInitCommand() {
|
|
11226
|
-
return new
|
|
11923
|
+
return new Command44("init").description("Apply rule preset to project").option("--preset <slug>", "Preset slug to apply").option("--output <format>", "Output format: cursor, agents-md, both (default: cursor)", "cursor").option("-y, --yes", "Skip confirmations").action(async (options) => {
|
|
11227
11924
|
const configService = new ConfigService();
|
|
11228
11925
|
const config = await configService.load();
|
|
11229
11926
|
if (!config) {
|
|
@@ -11251,7 +11948,8 @@ function createRulesInitCommand() {
|
|
|
11251
11948
|
name: `${p.name} - ${p.description}`,
|
|
11252
11949
|
value: p.slug,
|
|
11253
11950
|
description: `v${p.version} | ${p.tags.join(", ")}`
|
|
11254
|
-
}))
|
|
11951
|
+
})),
|
|
11952
|
+
theme: selectTheme
|
|
11255
11953
|
});
|
|
11256
11954
|
}
|
|
11257
11955
|
console.log(`
|
|
@@ -11279,7 +11977,8 @@ function createRulesInitCommand() {
|
|
|
11279
11977
|
{ name: "Cursor (.cursor/rules/)", value: "cursor" },
|
|
11280
11978
|
{ name: "AGENTS.md (single file)", value: "agents-md" },
|
|
11281
11979
|
{ name: "Both", value: "both" }
|
|
11282
|
-
]
|
|
11980
|
+
],
|
|
11981
|
+
theme: selectTheme
|
|
11283
11982
|
});
|
|
11284
11983
|
}
|
|
11285
11984
|
if (!options.yes) {
|
|
@@ -11299,14 +11998,16 @@ function createRulesInitCommand() {
|
|
|
11299
11998
|
if (outputFormat === "agents-md" || outputFormat === "both") {
|
|
11300
11999
|
await applyAgentsMdFormat(bundle);
|
|
11301
12000
|
}
|
|
11302
|
-
const
|
|
12001
|
+
const projectConfigService = new ProjectConfigService();
|
|
12002
|
+
const rulesConfig = {
|
|
11303
12003
|
preset: bundle.preset.slug,
|
|
11304
12004
|
version: bundle.preset.version,
|
|
11305
12005
|
appliedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
12006
|
+
ides: [],
|
|
11306
12007
|
customContext: "09-custom.mdc"
|
|
11307
12008
|
};
|
|
11308
|
-
await
|
|
11309
|
-
console.log("\u2713 Created jai1
|
|
12009
|
+
await projectConfigService.saveRules(rulesConfig);
|
|
12010
|
+
console.log("\u2713 Created .jai1/project.json");
|
|
11310
12011
|
console.log("\n\u2705 Preset applied successfully!\n");
|
|
11311
12012
|
console.log("Next steps:");
|
|
11312
12013
|
console.log(" 1. Edit 09-custom.mdc to add project-specific rules");
|
|
@@ -11315,11 +12016,11 @@ function createRulesInitCommand() {
|
|
|
11315
12016
|
});
|
|
11316
12017
|
}
|
|
11317
12018
|
async function applyCursorFormat(bundle) {
|
|
11318
|
-
const rulesDir =
|
|
11319
|
-
await
|
|
12019
|
+
const rulesDir = join10(process.cwd(), ".cursor", "rules");
|
|
12020
|
+
await fs20.mkdir(rulesDir, { recursive: true });
|
|
11320
12021
|
for (const [filename, content] of Object.entries(bundle.files)) {
|
|
11321
|
-
const filePath =
|
|
11322
|
-
await
|
|
12022
|
+
const filePath = join10(rulesDir, filename);
|
|
12023
|
+
await fs20.writeFile(filePath, content, "utf-8");
|
|
11323
12024
|
console.log(`\u2713 Created .cursor/rules/${filename}`);
|
|
11324
12025
|
}
|
|
11325
12026
|
}
|
|
@@ -11346,14 +12047,14 @@ async function applyAgentsMdFormat(bundle) {
|
|
|
11346
12047
|
}
|
|
11347
12048
|
}
|
|
11348
12049
|
const agentsMd = sections.join("\n");
|
|
11349
|
-
await
|
|
12050
|
+
await fs20.writeFile("AGENTS.md", agentsMd, "utf-8");
|
|
11350
12051
|
console.log("\u2713 Created AGENTS.md");
|
|
11351
12052
|
}
|
|
11352
12053
|
|
|
11353
12054
|
// src/commands/rules/apply.ts
|
|
11354
|
-
import { Command as
|
|
11355
|
-
import { promises as
|
|
11356
|
-
import { join as
|
|
12055
|
+
import { Command as Command45 } from "commander";
|
|
12056
|
+
import { promises as fs22 } from "fs";
|
|
12057
|
+
import { join as join12 } from "path";
|
|
11357
12058
|
import { select as select5, confirm as confirm8, checkbox as checkbox5 } from "@inquirer/prompts";
|
|
11358
12059
|
|
|
11359
12060
|
// src/services/rules-generator.service.ts
|
|
@@ -11388,11 +12089,6 @@ var RulesGeneratorService = class {
|
|
|
11388
12089
|
*/
|
|
11389
12090
|
generateCursorFiles(bundle, format) {
|
|
11390
12091
|
const files = [];
|
|
11391
|
-
files.push({
|
|
11392
|
-
path: `${format.rulesPath}/00-jai1${format.fileExtension}`,
|
|
11393
|
-
content: this.generateCursorJai1Rule(),
|
|
11394
|
-
description: "Jai1 Framework base rule"
|
|
11395
|
-
});
|
|
11396
12092
|
for (const [filename, content] of Object.entries(bundle.files)) {
|
|
11397
12093
|
files.push({
|
|
11398
12094
|
path: `${format.rulesPath}/${filename}`,
|
|
@@ -11403,28 +12099,11 @@ var RulesGeneratorService = class {
|
|
|
11403
12099
|
}
|
|
11404
12100
|
return files;
|
|
11405
12101
|
}
|
|
11406
|
-
/**
|
|
11407
|
-
* Generate Cursor jai1 base rule
|
|
11408
|
-
*/
|
|
11409
|
-
generateCursorJai1Rule() {
|
|
11410
|
-
return `---
|
|
11411
|
-
description: Jai1 Framework integration and skill loading
|
|
11412
|
-
globs: ["**/*"]
|
|
11413
|
-
alwaysApply: true
|
|
11414
|
-
---
|
|
11415
|
-
|
|
11416
|
-
${JAI1_BASE_RULE}`;
|
|
11417
|
-
}
|
|
11418
12102
|
/**
|
|
11419
12103
|
* Generate Windsurf format (.md files with trigger metadata)
|
|
11420
12104
|
*/
|
|
11421
12105
|
generateWindsurfFiles(bundle, format) {
|
|
11422
12106
|
const files = [];
|
|
11423
|
-
files.push({
|
|
11424
|
-
path: `${format.rulesPath}/00-jai1${format.fileExtension}`,
|
|
11425
|
-
content: this.generateWindsurfJai1Rule(),
|
|
11426
|
-
description: "Jai1 Framework base rule"
|
|
11427
|
-
});
|
|
11428
12107
|
for (const [filename, content] of Object.entries(bundle.files)) {
|
|
11429
12108
|
const converted = this.convertToWindsurf(content, filename);
|
|
11430
12109
|
const newFilename = filename.replace(/\.mdc$/, ".md");
|
|
@@ -11459,27 +12138,12 @@ trigger: ${trigger}
|
|
|
11459
12138
|
|
|
11460
12139
|
${body.trim()}
|
|
11461
12140
|
`;
|
|
11462
|
-
}
|
|
11463
|
-
/**
|
|
11464
|
-
* Generate Windsurf jai1 base rule
|
|
11465
|
-
*/
|
|
11466
|
-
generateWindsurfJai1Rule() {
|
|
11467
|
-
return `---
|
|
11468
|
-
trigger: always
|
|
11469
|
-
---
|
|
11470
|
-
|
|
11471
|
-
${JAI1_BASE_RULE}`;
|
|
11472
12141
|
}
|
|
11473
12142
|
/**
|
|
11474
12143
|
* Generate Antigravity format (.md files with trigger metadata + @AGENTS.md reference)
|
|
11475
12144
|
*/
|
|
11476
12145
|
generateAntigravityFiles(bundle, format) {
|
|
11477
12146
|
const files = [];
|
|
11478
|
-
files.push({
|
|
11479
|
-
path: `${format.rulesPath}/00-jai1${format.fileExtension}`,
|
|
11480
|
-
content: this.generateAntigravityJai1Rule(),
|
|
11481
|
-
description: "Jai1 Framework base rule"
|
|
11482
|
-
});
|
|
11483
12147
|
for (const [filename, content] of Object.entries(bundle.files)) {
|
|
11484
12148
|
const converted = this.convertToAntigravity(content, filename);
|
|
11485
12149
|
const newFilename = filename.replace(/\.mdc$/, ".md");
|
|
@@ -11514,29 +12178,12 @@ trigger: ${trigger}
|
|
|
11514
12178
|
|
|
11515
12179
|
${body.trim()}
|
|
11516
12180
|
`;
|
|
11517
|
-
}
|
|
11518
|
-
/**
|
|
11519
|
-
* Generate Antigravity jai1 base rule
|
|
11520
|
-
*/
|
|
11521
|
-
generateAntigravityJai1Rule() {
|
|
11522
|
-
return `---
|
|
11523
|
-
trigger: always_on
|
|
11524
|
-
---
|
|
11525
|
-
|
|
11526
|
-
@AGENTS.md
|
|
11527
|
-
|
|
11528
|
-
${JAI1_BASE_RULE}`;
|
|
11529
12181
|
}
|
|
11530
12182
|
/**
|
|
11531
12183
|
* Generate Claude Code format (.md files with paths metadata)
|
|
11532
12184
|
*/
|
|
11533
12185
|
generateClaudeFiles(bundle, format) {
|
|
11534
12186
|
const files = [];
|
|
11535
|
-
files.push({
|
|
11536
|
-
path: `${format.rulesPath}/00-jai1${format.fileExtension}`,
|
|
11537
|
-
content: this.generateClaudeJai1Rule(),
|
|
11538
|
-
description: "Jai1 Framework base rule"
|
|
11539
|
-
});
|
|
11540
12187
|
for (const [filename, content] of Object.entries(bundle.files)) {
|
|
11541
12188
|
const converted = this.convertToClaude(content, filename);
|
|
11542
12189
|
const newFilename = filename.replace(/\.mdc$/, ".md");
|
|
@@ -11570,17 +12217,6 @@ paths: ${paths}`;
|
|
|
11570
12217
|
|
|
11571
12218
|
${body.trim()}
|
|
11572
12219
|
`;
|
|
11573
|
-
}
|
|
11574
|
-
/**
|
|
11575
|
-
* Generate Claude jai1 base rule
|
|
11576
|
-
*/
|
|
11577
|
-
generateClaudeJai1Rule() {
|
|
11578
|
-
return `---
|
|
11579
|
-
description: Jai1 Framework integration and skill loading
|
|
11580
|
-
paths: **/*
|
|
11581
|
-
---
|
|
11582
|
-
|
|
11583
|
-
${JAI1_BASE_RULE}`;
|
|
11584
12220
|
}
|
|
11585
12221
|
/**
|
|
11586
12222
|
* Generate AGENTS.md file (single merged file)
|
|
@@ -11590,10 +12226,8 @@ ${JAI1_BASE_RULE}`;
|
|
|
11590
12226
|
sections.push("# AGENTS.md\n");
|
|
11591
12227
|
sections.push(`<!-- Generated by jai1 rules - Preset: ${bundle.preset.slug} v${bundle.preset.version} -->
|
|
11592
12228
|
`);
|
|
11593
|
-
sections.push("## Jai1 Framework Integration\n");
|
|
11594
|
-
sections.push(JAI1_BASE_RULE.trim());
|
|
11595
|
-
sections.push("\n---\n");
|
|
11596
12229
|
const fileOrder = [
|
|
12230
|
+
"00-jai1.mdc",
|
|
11597
12231
|
"01-project.mdc",
|
|
11598
12232
|
"02-standards.mdc",
|
|
11599
12233
|
"03-frontend.mdc",
|
|
@@ -11713,8 +12347,8 @@ Follow all instructions and patterns defined in AGENTS.md above.
|
|
|
11713
12347
|
};
|
|
11714
12348
|
|
|
11715
12349
|
// src/services/backup.service.ts
|
|
11716
|
-
import { promises as
|
|
11717
|
-
import { join as
|
|
12350
|
+
import { promises as fs21 } from "fs";
|
|
12351
|
+
import { join as join11, dirname } from "path";
|
|
11718
12352
|
var BackupService = class {
|
|
11719
12353
|
backupDir = ".jai1/backups";
|
|
11720
12354
|
/**
|
|
@@ -11722,7 +12356,7 @@ var BackupService = class {
|
|
|
11722
12356
|
*/
|
|
11723
12357
|
async createBackup(ides, presetSlug) {
|
|
11724
12358
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
11725
|
-
const backupPath =
|
|
12359
|
+
const backupPath = join11(this.backupDir, timestamp);
|
|
11726
12360
|
const backedUpFiles = [];
|
|
11727
12361
|
let hasContent = false;
|
|
11728
12362
|
for (const ideId of ides) {
|
|
@@ -11731,7 +12365,7 @@ var BackupService = class {
|
|
|
11731
12365
|
console.warn(`Unknown IDE format: ${ideId}, skipping backup`);
|
|
11732
12366
|
continue;
|
|
11733
12367
|
}
|
|
11734
|
-
const rulesPath = format.rulesPath === "." ? process.cwd() :
|
|
12368
|
+
const rulesPath = format.rulesPath === "." ? process.cwd() : join11(process.cwd(), format.rulesPath);
|
|
11735
12369
|
try {
|
|
11736
12370
|
const exists = await this.pathExists(rulesPath);
|
|
11737
12371
|
if (!exists) {
|
|
@@ -11744,19 +12378,19 @@ var BackupService = class {
|
|
|
11744
12378
|
await this.backupSingleFile("GEMINI.md", backupPath, ideId, backedUpFiles);
|
|
11745
12379
|
hasContent = true;
|
|
11746
12380
|
} else {
|
|
11747
|
-
const stats = await
|
|
12381
|
+
const stats = await fs21.stat(rulesPath);
|
|
11748
12382
|
if (stats.isDirectory()) {
|
|
11749
|
-
const files = await
|
|
12383
|
+
const files = await fs21.readdir(rulesPath);
|
|
11750
12384
|
for (const file of files) {
|
|
11751
12385
|
if (file.endsWith(format.fileExtension)) {
|
|
11752
|
-
const originalPath =
|
|
11753
|
-
const relativePath =
|
|
11754
|
-
const destPath =
|
|
11755
|
-
await
|
|
11756
|
-
await
|
|
12386
|
+
const originalPath = join11(rulesPath, file);
|
|
12387
|
+
const relativePath = join11(format.rulesPath, file);
|
|
12388
|
+
const destPath = join11(backupPath, ideId, file);
|
|
12389
|
+
await fs21.mkdir(dirname(destPath), { recursive: true });
|
|
12390
|
+
await fs21.copyFile(originalPath, destPath);
|
|
11757
12391
|
backedUpFiles.push({
|
|
11758
12392
|
originalPath: relativePath,
|
|
11759
|
-
backupPath:
|
|
12393
|
+
backupPath: join11(ideId, file),
|
|
11760
12394
|
ide: ideId
|
|
11761
12395
|
});
|
|
11762
12396
|
hasContent = true;
|
|
@@ -11777,9 +12411,9 @@ var BackupService = class {
|
|
|
11777
12411
|
ides,
|
|
11778
12412
|
files: backedUpFiles
|
|
11779
12413
|
};
|
|
11780
|
-
await
|
|
11781
|
-
await
|
|
11782
|
-
|
|
12414
|
+
await fs21.mkdir(backupPath, { recursive: true });
|
|
12415
|
+
await fs21.writeFile(
|
|
12416
|
+
join11(backupPath, "metadata.json"),
|
|
11783
12417
|
JSON.stringify(metadata, null, 2),
|
|
11784
12418
|
"utf-8"
|
|
11785
12419
|
);
|
|
@@ -11789,18 +12423,18 @@ var BackupService = class {
|
|
|
11789
12423
|
* Backup a single file (for AGENTS.md, GEMINI.md)
|
|
11790
12424
|
*/
|
|
11791
12425
|
async backupSingleFile(filename, backupPath, ideId, backedUpFiles) {
|
|
11792
|
-
const originalPath =
|
|
12426
|
+
const originalPath = join11(process.cwd(), filename);
|
|
11793
12427
|
try {
|
|
11794
12428
|
const exists = await this.pathExists(originalPath);
|
|
11795
12429
|
if (!exists) {
|
|
11796
12430
|
return;
|
|
11797
12431
|
}
|
|
11798
|
-
const destPath =
|
|
11799
|
-
await
|
|
11800
|
-
await
|
|
12432
|
+
const destPath = join11(backupPath, ideId, filename);
|
|
12433
|
+
await fs21.mkdir(dirname(destPath), { recursive: true });
|
|
12434
|
+
await fs21.copyFile(originalPath, destPath);
|
|
11801
12435
|
backedUpFiles.push({
|
|
11802
12436
|
originalPath: filename,
|
|
11803
|
-
backupPath:
|
|
12437
|
+
backupPath: join11(ideId, filename),
|
|
11804
12438
|
ide: ideId
|
|
11805
12439
|
});
|
|
11806
12440
|
} catch (error) {
|
|
@@ -11810,16 +12444,16 @@ var BackupService = class {
|
|
|
11810
12444
|
* Restore files from a backup
|
|
11811
12445
|
*/
|
|
11812
12446
|
async restoreBackup(backupPath) {
|
|
11813
|
-
const metadataPath =
|
|
11814
|
-
const metadataContent = await
|
|
12447
|
+
const metadataPath = join11(backupPath, "metadata.json");
|
|
12448
|
+
const metadataContent = await fs21.readFile(metadataPath, "utf-8");
|
|
11815
12449
|
const metadata = JSON.parse(metadataContent);
|
|
11816
12450
|
console.log(`
|
|
11817
12451
|
Restoring backup from ${metadata.timestamp}...`);
|
|
11818
12452
|
for (const file of metadata.files) {
|
|
11819
|
-
const sourcePath =
|
|
11820
|
-
const destPath =
|
|
11821
|
-
await
|
|
11822
|
-
await
|
|
12453
|
+
const sourcePath = join11(backupPath, file.backupPath);
|
|
12454
|
+
const destPath = join11(process.cwd(), file.originalPath);
|
|
12455
|
+
await fs21.mkdir(dirname(destPath), { recursive: true });
|
|
12456
|
+
await fs21.copyFile(sourcePath, destPath);
|
|
11823
12457
|
console.log(`\u2713 Restored ${file.originalPath}`);
|
|
11824
12458
|
}
|
|
11825
12459
|
console.log("\n\u2705 Backup restored successfully!");
|
|
@@ -11829,18 +12463,18 @@ Restoring backup from ${metadata.timestamp}...`);
|
|
|
11829
12463
|
*/
|
|
11830
12464
|
async listBackups() {
|
|
11831
12465
|
try {
|
|
11832
|
-
const backupDirPath =
|
|
12466
|
+
const backupDirPath = join11(process.cwd(), this.backupDir);
|
|
11833
12467
|
const exists = await this.pathExists(backupDirPath);
|
|
11834
12468
|
if (!exists) {
|
|
11835
12469
|
return [];
|
|
11836
12470
|
}
|
|
11837
|
-
const entries = await
|
|
12471
|
+
const entries = await fs21.readdir(backupDirPath, { withFileTypes: true });
|
|
11838
12472
|
const backups = [];
|
|
11839
12473
|
for (const entry of entries) {
|
|
11840
12474
|
if (entry.isDirectory()) {
|
|
11841
|
-
const metadataPath =
|
|
12475
|
+
const metadataPath = join11(backupDirPath, entry.name, "metadata.json");
|
|
11842
12476
|
try {
|
|
11843
|
-
const metadataContent = await
|
|
12477
|
+
const metadataContent = await fs21.readFile(metadataPath, "utf-8");
|
|
11844
12478
|
const metadata = JSON.parse(metadataContent);
|
|
11845
12479
|
backups.push(metadata);
|
|
11846
12480
|
} catch {
|
|
@@ -11857,7 +12491,7 @@ Restoring backup from ${metadata.timestamp}...`);
|
|
|
11857
12491
|
* Delete a specific backup
|
|
11858
12492
|
*/
|
|
11859
12493
|
async deleteBackup(timestamp) {
|
|
11860
|
-
const backupPath =
|
|
12494
|
+
const backupPath = join11(process.cwd(), this.backupDir, timestamp);
|
|
11861
12495
|
await this.deleteDirectory(backupPath);
|
|
11862
12496
|
}
|
|
11863
12497
|
/**
|
|
@@ -11887,9 +12521,9 @@ Restoring backup from ${metadata.timestamp}...`);
|
|
|
11887
12521
|
/**
|
|
11888
12522
|
* Check if a path exists
|
|
11889
12523
|
*/
|
|
11890
|
-
async pathExists(
|
|
12524
|
+
async pathExists(path13) {
|
|
11891
12525
|
try {
|
|
11892
|
-
await
|
|
12526
|
+
await fs21.access(path13);
|
|
11893
12527
|
return true;
|
|
11894
12528
|
} catch {
|
|
11895
12529
|
return false;
|
|
@@ -11898,22 +12532,22 @@ Restoring backup from ${metadata.timestamp}...`);
|
|
|
11898
12532
|
/**
|
|
11899
12533
|
* Recursively delete a directory
|
|
11900
12534
|
*/
|
|
11901
|
-
async deleteDirectory(
|
|
12535
|
+
async deleteDirectory(path13) {
|
|
11902
12536
|
try {
|
|
11903
|
-
const exists = await this.pathExists(
|
|
12537
|
+
const exists = await this.pathExists(path13);
|
|
11904
12538
|
if (!exists) {
|
|
11905
12539
|
return;
|
|
11906
12540
|
}
|
|
11907
|
-
const entries = await
|
|
12541
|
+
const entries = await fs21.readdir(path13, { withFileTypes: true });
|
|
11908
12542
|
for (const entry of entries) {
|
|
11909
|
-
const fullPath =
|
|
12543
|
+
const fullPath = join11(path13, entry.name);
|
|
11910
12544
|
if (entry.isDirectory()) {
|
|
11911
12545
|
await this.deleteDirectory(fullPath);
|
|
11912
12546
|
} else {
|
|
11913
|
-
await
|
|
12547
|
+
await fs21.unlink(fullPath);
|
|
11914
12548
|
}
|
|
11915
12549
|
}
|
|
11916
|
-
await
|
|
12550
|
+
await fs21.rmdir(path13);
|
|
11917
12551
|
} catch (error) {
|
|
11918
12552
|
}
|
|
11919
12553
|
}
|
|
@@ -11921,20 +12555,20 @@ Restoring backup from ${metadata.timestamp}...`);
|
|
|
11921
12555
|
* Get backup directory path
|
|
11922
12556
|
*/
|
|
11923
12557
|
getBackupDir() {
|
|
11924
|
-
return
|
|
12558
|
+
return join11(process.cwd(), this.backupDir);
|
|
11925
12559
|
}
|
|
11926
12560
|
/**
|
|
11927
12561
|
* Ensure backup directory exists
|
|
11928
12562
|
*/
|
|
11929
12563
|
async ensureBackupDir() {
|
|
11930
|
-
const backupDirPath =
|
|
11931
|
-
await
|
|
12564
|
+
const backupDirPath = join11(process.cwd(), this.backupDir);
|
|
12565
|
+
await fs21.mkdir(backupDirPath, { recursive: true });
|
|
11932
12566
|
}
|
|
11933
12567
|
};
|
|
11934
12568
|
|
|
11935
12569
|
// src/commands/rules/apply.ts
|
|
11936
12570
|
function createRulesApplyCommand() {
|
|
11937
|
-
return new
|
|
12571
|
+
return new Command45("apply").description("Apply rule preset to project with multi-IDE support").argument("[preset]", "Preset slug to apply (optional)").option("--ides <ides>", "Comma-separated list of IDE formats (cursor,windsurf,antigravity,claude,agentsmd,gemini)").option("--skip-backup", "Skip backup creation").option("-y, --yes", "Skip all confirmations (auto mode)").action(async (presetSlug, options) => {
|
|
11938
12572
|
const configService = new ConfigService();
|
|
11939
12573
|
const config = await configService.load();
|
|
11940
12574
|
if (!config) {
|
|
@@ -11972,7 +12606,8 @@ function createRulesApplyCommand() {
|
|
|
11972
12606
|
name: `${p.name} - ${p.description}`,
|
|
11973
12607
|
value: p.slug,
|
|
11974
12608
|
description: `v${p.version} | ${p.tags.join(", ")}`
|
|
11975
|
-
}))
|
|
12609
|
+
})),
|
|
12610
|
+
theme: selectTheme
|
|
11976
12611
|
});
|
|
11977
12612
|
}
|
|
11978
12613
|
console.log(`
|
|
@@ -12042,7 +12677,8 @@ function createRulesApplyCommand() {
|
|
|
12042
12677
|
selectedIdes = await checkbox5({
|
|
12043
12678
|
message: "Select IDE formats to generate (pre-selected are recommended):",
|
|
12044
12679
|
choices,
|
|
12045
|
-
required: true
|
|
12680
|
+
required: true,
|
|
12681
|
+
theme: checkboxTheme
|
|
12046
12682
|
});
|
|
12047
12683
|
}
|
|
12048
12684
|
}
|
|
@@ -12084,21 +12720,21 @@ function createRulesApplyCommand() {
|
|
|
12084
12720
|
}
|
|
12085
12721
|
}
|
|
12086
12722
|
console.log("\n\u{1F4DD} Applying preset...\n");
|
|
12087
|
-
const rulePresetDir =
|
|
12723
|
+
const rulePresetDir = join12(process.cwd(), ".jai1", "rule-preset");
|
|
12088
12724
|
try {
|
|
12089
|
-
await
|
|
12725
|
+
await fs22.rm(rulePresetDir, { recursive: true, force: true });
|
|
12090
12726
|
} catch {
|
|
12091
12727
|
}
|
|
12092
|
-
await
|
|
12093
|
-
await
|
|
12094
|
-
|
|
12728
|
+
await fs22.mkdir(rulePresetDir, { recursive: true });
|
|
12729
|
+
await fs22.writeFile(
|
|
12730
|
+
join12(rulePresetDir, "preset.json"),
|
|
12095
12731
|
JSON.stringify(bundle.preset, null, 2),
|
|
12096
12732
|
"utf-8"
|
|
12097
12733
|
);
|
|
12098
12734
|
for (const [filename, content] of Object.entries(bundle.files)) {
|
|
12099
|
-
const filePath =
|
|
12100
|
-
await
|
|
12101
|
-
await
|
|
12735
|
+
const filePath = join12(rulePresetDir, filename);
|
|
12736
|
+
await fs22.mkdir(join12(filePath, ".."), { recursive: true });
|
|
12737
|
+
await fs22.writeFile(filePath, content, "utf-8");
|
|
12102
12738
|
}
|
|
12103
12739
|
console.log(`\u2713 Saved preset to .jai1/rule-preset/`);
|
|
12104
12740
|
const allGeneratedFiles = [];
|
|
@@ -12106,9 +12742,9 @@ function createRulesApplyCommand() {
|
|
|
12106
12742
|
try {
|
|
12107
12743
|
const files = generatorService.generateForIde(bundle, ideId);
|
|
12108
12744
|
for (const file of files) {
|
|
12109
|
-
const fullPath =
|
|
12110
|
-
await
|
|
12111
|
-
await
|
|
12745
|
+
const fullPath = join12(process.cwd(), file.path);
|
|
12746
|
+
await fs22.mkdir(join12(fullPath, ".."), { recursive: true });
|
|
12747
|
+
await fs22.writeFile(fullPath, file.content, "utf-8");
|
|
12112
12748
|
console.log(`\u2713 [${ideId}] ${file.path}`);
|
|
12113
12749
|
allGeneratedFiles.push({
|
|
12114
12750
|
ide: ideId,
|
|
@@ -12120,7 +12756,8 @@ function createRulesApplyCommand() {
|
|
|
12120
12756
|
console.error(`\u2717 Failed to generate ${ideId} files:`, error);
|
|
12121
12757
|
}
|
|
12122
12758
|
}
|
|
12123
|
-
const
|
|
12759
|
+
const projectConfigService = new ProjectConfigService();
|
|
12760
|
+
const rulesConfig = {
|
|
12124
12761
|
preset: bundle.preset.slug,
|
|
12125
12762
|
version: bundle.preset.version,
|
|
12126
12763
|
appliedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -12135,24 +12772,18 @@ function createRulesApplyCommand() {
|
|
|
12135
12772
|
] : []
|
|
12136
12773
|
};
|
|
12137
12774
|
try {
|
|
12138
|
-
const
|
|
12139
|
-
|
|
12140
|
-
|
|
12141
|
-
|
|
12142
|
-
|
|
12143
|
-
...projectConfig.backups,
|
|
12144
|
-
...existingConfig.backups.slice(0, 5)
|
|
12775
|
+
const existingRules = await projectConfigService.loadRules();
|
|
12776
|
+
if (existingRules?.backups && existingRules.backups.length > 0) {
|
|
12777
|
+
rulesConfig.backups = [
|
|
12778
|
+
...rulesConfig.backups,
|
|
12779
|
+
...existingRules.backups.slice(0, 5)
|
|
12145
12780
|
// Keep last 5 backups
|
|
12146
12781
|
];
|
|
12147
12782
|
}
|
|
12148
12783
|
} catch {
|
|
12149
12784
|
}
|
|
12150
|
-
await
|
|
12151
|
-
|
|
12152
|
-
JSON.stringify(projectConfig, null, 2),
|
|
12153
|
-
"utf-8"
|
|
12154
|
-
);
|
|
12155
|
-
console.log("\n\u2713 Updated jai1-rules.json");
|
|
12785
|
+
await projectConfigService.saveRules(rulesConfig);
|
|
12786
|
+
console.log("\n\u2713 Updated .jai1/project.json");
|
|
12156
12787
|
console.log("\n\u2705 Preset applied successfully!\n");
|
|
12157
12788
|
console.log("\u{1F4CA} Summary:");
|
|
12158
12789
|
console.log(` Generated ${allGeneratedFiles.length} files across ${resolvedIdes.length} IDE(s)`);
|
|
@@ -12179,11 +12810,11 @@ function createRulesApplyCommand() {
|
|
|
12179
12810
|
}
|
|
12180
12811
|
|
|
12181
12812
|
// src/commands/rules/restore.ts
|
|
12182
|
-
import { Command as
|
|
12183
|
-
import { join as
|
|
12813
|
+
import { Command as Command46 } from "commander";
|
|
12814
|
+
import { join as join13 } from "path";
|
|
12184
12815
|
import { select as select6, confirm as confirm9 } from "@inquirer/prompts";
|
|
12185
12816
|
function createRulesRestoreCommand() {
|
|
12186
|
-
return new
|
|
12817
|
+
return new Command46("restore").description("Restore rules from a backup").option("--latest", "Restore the most recent backup").option("-y, --yes", "Skip confirmation").action(async (options) => {
|
|
12187
12818
|
const backupService = new BackupService();
|
|
12188
12819
|
const backups = await backupService.listBackups();
|
|
12189
12820
|
if (backups.length === 0) {
|
|
@@ -12202,7 +12833,8 @@ function createRulesRestoreCommand() {
|
|
|
12202
12833
|
name: formatBackupInfo(backup),
|
|
12203
12834
|
value: backup.timestamp,
|
|
12204
12835
|
description: `${backup.files.length} files | IDEs: ${backup.ides.join(", ")}`
|
|
12205
|
-
}))
|
|
12836
|
+
})),
|
|
12837
|
+
theme: selectTheme
|
|
12206
12838
|
});
|
|
12207
12839
|
selectedBackup = backups.find((b) => b.timestamp === backupTimestamp);
|
|
12208
12840
|
if (!selectedBackup) {
|
|
@@ -12226,7 +12858,7 @@ function createRulesRestoreCommand() {
|
|
|
12226
12858
|
}
|
|
12227
12859
|
console.log("\n\u{1F504} Restoring backup...\n");
|
|
12228
12860
|
try {
|
|
12229
|
-
const backupPath =
|
|
12861
|
+
const backupPath = join13(backupService.getBackupDir(), selectedBackup.timestamp);
|
|
12230
12862
|
await backupService.restoreBackup(backupPath);
|
|
12231
12863
|
console.log("\n\u2705 Backup restored successfully!\n");
|
|
12232
12864
|
console.log("\u{1F4A1} Tip: Your IDE may need to be restarted to pick up the changes.");
|
|
@@ -12251,24 +12883,42 @@ function formatTimestamp(timestamp) {
|
|
|
12251
12883
|
}
|
|
12252
12884
|
|
|
12253
12885
|
// src/commands/rules/sync.ts
|
|
12254
|
-
import { Command as
|
|
12255
|
-
import { promises as
|
|
12256
|
-
import { join as
|
|
12257
|
-
import { confirm as confirm10 } from "@inquirer/prompts";
|
|
12886
|
+
import { Command as Command47 } from "commander";
|
|
12887
|
+
import { promises as fs23 } from "fs";
|
|
12888
|
+
import { join as join14 } from "path";
|
|
12889
|
+
import { checkbox as checkbox6, confirm as confirm10, Separator } from "@inquirer/prompts";
|
|
12258
12890
|
function createRulesSyncCommand() {
|
|
12259
|
-
return new
|
|
12260
|
-
const
|
|
12261
|
-
|
|
12891
|
+
return new Command47("sync").description("Regenerate rule outputs for all configured IDEs").option("--ides <ides>", "Comma-separated list of IDEs to sync (default: all configured)").option("--detect", "Auto-detect active IDEs instead of using config").option("-y, --yes", "Skip confirmations").action(async (options) => {
|
|
12892
|
+
const rulePresetDir = join14(process.cwd(), ".jai1", "rule-preset");
|
|
12893
|
+
const presetJsonPath = join14(rulePresetDir, "preset.json");
|
|
12894
|
+
let presetExists = false;
|
|
12895
|
+
let presetData = null;
|
|
12262
12896
|
try {
|
|
12263
|
-
const
|
|
12264
|
-
|
|
12897
|
+
const presetContent = await fs23.readFile(presetJsonPath, "utf-8");
|
|
12898
|
+
presetData = JSON.parse(presetContent);
|
|
12899
|
+
presetExists = true;
|
|
12265
12900
|
} catch {
|
|
12901
|
+
}
|
|
12902
|
+
if (!presetExists) {
|
|
12266
12903
|
throw new ValidationError(
|
|
12267
|
-
'No
|
|
12904
|
+
'No rule preset found in .jai1/rule-preset/\nRun "jai1 rules apply <preset>" or create a project with "jai1 kit create <kit>" first.'
|
|
12268
12905
|
);
|
|
12269
12906
|
}
|
|
12907
|
+
const projectConfigService = new ProjectConfigService();
|
|
12908
|
+
let rulesConfig = await projectConfigService.loadRules();
|
|
12909
|
+
if (!rulesConfig) {
|
|
12910
|
+
console.log("\u26A0\uFE0F No .jai1/project.json found, creating from preset...\n");
|
|
12911
|
+
rulesConfig = {
|
|
12912
|
+
preset: presetData.slug,
|
|
12913
|
+
version: presetData.version,
|
|
12914
|
+
appliedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
12915
|
+
ides: [],
|
|
12916
|
+
customContext: "09-custom.md",
|
|
12917
|
+
backups: []
|
|
12918
|
+
};
|
|
12919
|
+
}
|
|
12270
12920
|
console.log("\u{1F504} Syncing rules...\n");
|
|
12271
|
-
console.log(`Preset: ${
|
|
12921
|
+
console.log(`Preset: ${rulesConfig.preset} v${rulesConfig.version}`);
|
|
12272
12922
|
let idesToSync = [];
|
|
12273
12923
|
if (options.detect) {
|
|
12274
12924
|
console.log("\n\u{1F50D} Auto-detecting active IDEs...");
|
|
@@ -12299,18 +12949,52 @@ Detected ${detected.length} active IDE(s):
|
|
|
12299
12949
|
idesToSync = detected.map((d) => d.id);
|
|
12300
12950
|
} else if (options.ides) {
|
|
12301
12951
|
const requested = options.ides.split(",").map((ide) => ide.trim());
|
|
12302
|
-
const configured =
|
|
12952
|
+
const configured = rulesConfig.ides || [];
|
|
12303
12953
|
idesToSync = requested;
|
|
12304
12954
|
const notConfigured = requested.filter((ide) => !configured.includes(ide));
|
|
12305
12955
|
if (notConfigured.length > 0) {
|
|
12306
12956
|
console.log(`
|
|
12307
12957
|
\u26A0\uFE0F IDEs not in config: ${notConfigured.join(", ")}`);
|
|
12308
|
-
console.log(" They will still be synced, but may not be in jai1
|
|
12958
|
+
console.log(" They will still be synced, but may not be in .jai1/project.json");
|
|
12959
|
+
}
|
|
12960
|
+
} else if (!options.yes) {
|
|
12961
|
+
const currentIdes = rulesConfig.ides || [];
|
|
12962
|
+
console.log(`
|
|
12963
|
+
Current IDE(s): ${currentIdes.join(", ") || "none"}`);
|
|
12964
|
+
const detectionService = new IdeDetectionService();
|
|
12965
|
+
const detected = await detectionService.detectActiveIdes();
|
|
12966
|
+
const suggestions = await detectionService.suggestIdes();
|
|
12967
|
+
if (detected.length > 0) {
|
|
12968
|
+
const detectedCount = detected.reduce((sum, d) => sum + d.ruleCount + d.workflowCount, 0);
|
|
12969
|
+
console.log(` (${detectedCount} files detected)`);
|
|
12970
|
+
}
|
|
12971
|
+
if (suggestions.length > 0) {
|
|
12972
|
+
console.log("\n\u{1F4A1} Smart suggestions based on your project:\n");
|
|
12973
|
+
suggestions.slice(0, 3).forEach((s) => {
|
|
12974
|
+
const priority = s.priority === "high" ? "\u2B50" : s.priority === "medium" ? "\u{1F538}" : "\u25AB\uFE0F";
|
|
12975
|
+
console.log(`${priority} ${s.name} - ${s.reason}`);
|
|
12976
|
+
});
|
|
12977
|
+
console.log("");
|
|
12978
|
+
}
|
|
12979
|
+
const choices = buildIdeChoices(currentIdes, detected, suggestions);
|
|
12980
|
+
idesToSync = await checkbox6({
|
|
12981
|
+
message: "Select IDE formats to sync:",
|
|
12982
|
+
choices,
|
|
12983
|
+
required: false,
|
|
12984
|
+
theme: checkboxTheme
|
|
12985
|
+
});
|
|
12986
|
+
if (idesToSync.includes("__quit__")) {
|
|
12987
|
+
console.log("Cancelled.");
|
|
12988
|
+
return;
|
|
12989
|
+
}
|
|
12990
|
+
if (idesToSync.length === 0) {
|
|
12991
|
+
console.log("Cancelled.");
|
|
12992
|
+
return;
|
|
12309
12993
|
}
|
|
12310
12994
|
} else {
|
|
12311
|
-
idesToSync =
|
|
12995
|
+
idesToSync = rulesConfig.ides || [];
|
|
12312
12996
|
if (idesToSync.length === 0) {
|
|
12313
|
-
console.log("\n\u26A0\uFE0F No IDEs configured in jai1
|
|
12997
|
+
console.log("\n\u26A0\uFE0F No IDEs configured in .jai1/project.json");
|
|
12314
12998
|
console.log(" Detecting from existing files...\n");
|
|
12315
12999
|
const detectionService = new IdeDetectionService();
|
|
12316
13000
|
idesToSync = await detectionService.detectExistingIdes();
|
|
@@ -12331,7 +13015,7 @@ Detected ${detected.length} active IDE(s):
|
|
|
12331
13015
|
if (!config) {
|
|
12332
13016
|
throw new ValidationError('Not initialized. Run "jai1 auth" first.');
|
|
12333
13017
|
}
|
|
12334
|
-
const presetResponse = await fetch(`${config.apiUrl}/api/rules/presets/${
|
|
13018
|
+
const presetResponse = await fetch(`${config.apiUrl}/api/rules/presets/${rulesConfig.preset}`, {
|
|
12335
13019
|
headers: {
|
|
12336
13020
|
"JAI1-Access-Key": config.accessKey
|
|
12337
13021
|
}
|
|
@@ -12340,21 +13024,13 @@ Detected ${detected.length} active IDE(s):
|
|
|
12340
13024
|
throw new Error(`Failed to fetch preset: ${presetResponse.statusText}`);
|
|
12341
13025
|
}
|
|
12342
13026
|
const bundle = await presetResponse.json();
|
|
12343
|
-
const
|
|
12344
|
-
const
|
|
12345
|
-
|
|
12346
|
-
|
|
12347
|
-
|
|
12348
|
-
|
|
12349
|
-
const filePath = join12(rulePresetDir, file);
|
|
12350
|
-
const content = await fs19.readFile(filePath, "utf-8");
|
|
12351
|
-
bundle.files[file] = content;
|
|
12352
|
-
}
|
|
13027
|
+
const files = await fs23.readdir(rulePresetDir);
|
|
13028
|
+
for (const file of files) {
|
|
13029
|
+
if (file.endsWith(".mdc") || file.endsWith(".md")) {
|
|
13030
|
+
const filePath = join14(rulePresetDir, file);
|
|
13031
|
+
const content = await fs23.readFile(filePath, "utf-8");
|
|
13032
|
+
bundle.files[file] = content;
|
|
12353
13033
|
}
|
|
12354
|
-
} else {
|
|
12355
|
-
console.log("\n\u26A0\uFE0F No rule preset found in .jai1/rule-preset/");
|
|
12356
|
-
console.log(' Run "jai1 rules apply" first to apply a preset.\n');
|
|
12357
|
-
return;
|
|
12358
13034
|
}
|
|
12359
13035
|
const generatorService = new RulesGeneratorService();
|
|
12360
13036
|
for (const ideId of idesToSync) {
|
|
@@ -12364,25 +13040,21 @@ Detected ${detected.length} active IDE(s):
|
|
|
12364
13040
|
console.log(`\u26A0\uFE0F Unknown IDE format: ${ideId}, skipping`);
|
|
12365
13041
|
continue;
|
|
12366
13042
|
}
|
|
12367
|
-
const
|
|
12368
|
-
for (const file of
|
|
12369
|
-
const fullPath =
|
|
12370
|
-
await
|
|
12371
|
-
await
|
|
13043
|
+
const files2 = generatorService.generateForIde(bundle, ideId);
|
|
13044
|
+
for (const file of files2) {
|
|
13045
|
+
const fullPath = join14(process.cwd(), file.path);
|
|
13046
|
+
await fs23.mkdir(join14(fullPath, ".."), { recursive: true });
|
|
13047
|
+
await fs23.writeFile(fullPath, file.content, "utf-8");
|
|
12372
13048
|
}
|
|
12373
|
-
console.log(`\u2713 ${format.name} - ${
|
|
13049
|
+
console.log(`\u2713 ${format.name} - ${files2.length} files regenerated`);
|
|
12374
13050
|
} catch (error) {
|
|
12375
13051
|
console.error(`\u2717 Failed to sync ${ideId}:`, error);
|
|
12376
13052
|
}
|
|
12377
13053
|
}
|
|
12378
|
-
if (JSON.stringify(
|
|
12379
|
-
|
|
12380
|
-
await
|
|
12381
|
-
|
|
12382
|
-
JSON.stringify(projectConfig, null, 2),
|
|
12383
|
-
"utf-8"
|
|
12384
|
-
);
|
|
12385
|
-
console.log("\n\u2713 Updated jai1-rules.json with synced IDEs");
|
|
13054
|
+
if (JSON.stringify(rulesConfig.ides) !== JSON.stringify(idesToSync)) {
|
|
13055
|
+
rulesConfig.ides = idesToSync;
|
|
13056
|
+
await projectConfigService.saveRules(rulesConfig);
|
|
13057
|
+
console.log("\n\u2713 Updated .jai1/project.json with synced IDEs");
|
|
12386
13058
|
}
|
|
12387
13059
|
console.log("\n\u2705 Rules synced successfully!\n");
|
|
12388
13060
|
console.log("\u{1F4A1} Next steps:");
|
|
@@ -12391,44 +13063,74 @@ Detected ${detected.length} active IDE(s):
|
|
|
12391
13063
|
console.log(" \u2022 Edit source files in .jai1/rule-preset/ and sync again\n");
|
|
12392
13064
|
});
|
|
12393
13065
|
}
|
|
12394
|
-
|
|
12395
|
-
|
|
12396
|
-
|
|
12397
|
-
|
|
12398
|
-
|
|
12399
|
-
|
|
12400
|
-
|
|
13066
|
+
function buildIdeChoices(currentIdes, detected, suggestions) {
|
|
13067
|
+
const choices = [
|
|
13068
|
+
{
|
|
13069
|
+
name: "Cursor (.cursor/rules/)",
|
|
13070
|
+
value: "cursor",
|
|
13071
|
+
checked: currentIdes.includes("cursor") || detected.some((d) => d.id === "cursor")
|
|
13072
|
+
},
|
|
13073
|
+
{
|
|
13074
|
+
name: "Windsurf (.windsurf/rules/)",
|
|
13075
|
+
value: "windsurf",
|
|
13076
|
+
checked: currentIdes.includes("windsurf") || detected.some((d) => d.id === "windsurf")
|
|
13077
|
+
},
|
|
13078
|
+
{
|
|
13079
|
+
name: "Antigravity (.agent/rules/)",
|
|
13080
|
+
value: "antigravity",
|
|
13081
|
+
checked: currentIdes.includes("antigravity") || detected.some((d) => d.id === "antigravity")
|
|
13082
|
+
},
|
|
13083
|
+
{
|
|
13084
|
+
name: "Claude Code (.claude/rules/)",
|
|
13085
|
+
value: "claude",
|
|
13086
|
+
checked: currentIdes.includes("claude") || detected.some((d) => d.id === "claude")
|
|
13087
|
+
},
|
|
13088
|
+
{
|
|
13089
|
+
name: "AGENTS.md (single file)",
|
|
13090
|
+
value: "agentsmd",
|
|
13091
|
+
checked: currentIdes.includes("agentsmd") || detected.some((d) => d.id === "agentsmd")
|
|
13092
|
+
},
|
|
13093
|
+
{
|
|
13094
|
+
name: "Gemini CLI (GEMINI.md)",
|
|
13095
|
+
value: "gemini",
|
|
13096
|
+
checked: currentIdes.includes("gemini") || detected.some((d) => d.id === "gemini")
|
|
13097
|
+
},
|
|
13098
|
+
new Separator(),
|
|
13099
|
+
{
|
|
13100
|
+
name: "\u2716 Quit",
|
|
13101
|
+
value: "__quit__",
|
|
13102
|
+
checked: false
|
|
13103
|
+
}
|
|
13104
|
+
];
|
|
13105
|
+
return choices;
|
|
12401
13106
|
}
|
|
12402
13107
|
|
|
12403
13108
|
// src/commands/rules/info.ts
|
|
12404
|
-
import { Command as
|
|
12405
|
-
import { promises as
|
|
12406
|
-
import { join as
|
|
13109
|
+
import { Command as Command48 } from "commander";
|
|
13110
|
+
import { promises as fs24 } from "fs";
|
|
13111
|
+
import { join as join15 } from "path";
|
|
12407
13112
|
function createRulesInfoCommand() {
|
|
12408
|
-
return new
|
|
12409
|
-
const
|
|
12410
|
-
|
|
12411
|
-
|
|
12412
|
-
const configContent = await fs20.readFile(configPath, "utf-8");
|
|
12413
|
-
projectConfig = JSON.parse(configContent);
|
|
12414
|
-
} catch {
|
|
13113
|
+
return new Command48("info").description("Show current preset information").option("--json", "Output as JSON").action(async (options) => {
|
|
13114
|
+
const projectConfigService = new ProjectConfigService();
|
|
13115
|
+
const rulesConfig = await projectConfigService.loadRules();
|
|
13116
|
+
if (!rulesConfig) {
|
|
12415
13117
|
throw new ValidationError(
|
|
12416
|
-
'No jai1
|
|
13118
|
+
'No .jai1/project.json found. Run "jai1 rules apply" first.'
|
|
12417
13119
|
);
|
|
12418
13120
|
}
|
|
12419
13121
|
if (options.json) {
|
|
12420
|
-
console.log(JSON.stringify(
|
|
13122
|
+
console.log(JSON.stringify(rulesConfig, null, 2));
|
|
12421
13123
|
return;
|
|
12422
13124
|
}
|
|
12423
13125
|
console.log("\u{1F4CB} Current Preset Information\n");
|
|
12424
|
-
const rulePresetDir =
|
|
12425
|
-
const presetJsonPath =
|
|
13126
|
+
const rulePresetDir = join15(process.cwd(), ".jai1", "rule-preset");
|
|
13127
|
+
const presetJsonPath = join15(rulePresetDir, "preset.json");
|
|
12426
13128
|
let presetMetadata = null;
|
|
12427
13129
|
let presetFiles = [];
|
|
12428
13130
|
try {
|
|
12429
|
-
const presetContent = await
|
|
13131
|
+
const presetContent = await fs24.readFile(presetJsonPath, "utf-8");
|
|
12430
13132
|
presetMetadata = JSON.parse(presetContent);
|
|
12431
|
-
const files = await
|
|
13133
|
+
const files = await fs24.readdir(rulePresetDir);
|
|
12432
13134
|
presetFiles = files.filter((f) => f.endsWith(".mdc"));
|
|
12433
13135
|
} catch {
|
|
12434
13136
|
}
|
|
@@ -12440,10 +13142,10 @@ function createRulesInfoCommand() {
|
|
|
12440
13142
|
console.log(`Tags: ${presetMetadata.tags.join(", ")}`);
|
|
12441
13143
|
}
|
|
12442
13144
|
} else {
|
|
12443
|
-
console.log(`Preset: ${
|
|
12444
|
-
console.log(`Version: ${
|
|
13145
|
+
console.log(`Preset: ${rulesConfig.preset}`);
|
|
13146
|
+
console.log(`Version: ${rulesConfig.version}`);
|
|
12445
13147
|
}
|
|
12446
|
-
console.log(`Applied at: ${new Date(
|
|
13148
|
+
console.log(`Applied at: ${new Date(rulesConfig.appliedAt).toLocaleString()}`);
|
|
12447
13149
|
console.log(`
|
|
12448
13150
|
Source of Truth:`);
|
|
12449
13151
|
if (presetFiles.length > 0) {
|
|
@@ -12458,10 +13160,10 @@ Source of Truth:`);
|
|
|
12458
13160
|
console.log(` \u26A0\uFE0F .jai1/rule-preset/ not found`);
|
|
12459
13161
|
console.log(` Run "jai1 rules apply" to create it`);
|
|
12460
13162
|
}
|
|
12461
|
-
if (
|
|
13163
|
+
if (rulesConfig.ides && rulesConfig.ides.length > 0) {
|
|
12462
13164
|
console.log(`
|
|
12463
|
-
Configured IDEs (${
|
|
12464
|
-
for (const ideId of
|
|
13165
|
+
Configured IDEs (${rulesConfig.ides.length}):`);
|
|
13166
|
+
for (const ideId of rulesConfig.ides) {
|
|
12465
13167
|
const format = IDE_FORMATS[ideId];
|
|
12466
13168
|
if (format) {
|
|
12467
13169
|
const exists = await checkIdeFilesExist(ideId, format);
|
|
@@ -12470,15 +13172,15 @@ Configured IDEs (${projectConfig.ides.length}):`);
|
|
|
12470
13172
|
}
|
|
12471
13173
|
}
|
|
12472
13174
|
}
|
|
12473
|
-
if (
|
|
13175
|
+
if (rulesConfig.backups && rulesConfig.backups.length > 0) {
|
|
12474
13176
|
console.log(`
|
|
12475
|
-
Available Backups (${
|
|
12476
|
-
for (const backup of
|
|
13177
|
+
Available Backups (${rulesConfig.backups.length}):`);
|
|
13178
|
+
for (const backup of rulesConfig.backups.slice(0, 3)) {
|
|
12477
13179
|
const timestamp = new Date(backup.timestamp).toLocaleString();
|
|
12478
13180
|
console.log(` \u2022 ${timestamp} - IDEs: ${backup.ides.join(", ")}`);
|
|
12479
13181
|
}
|
|
12480
|
-
if (
|
|
12481
|
-
console.log(` ... and ${
|
|
13182
|
+
if (rulesConfig.backups.length > 3) {
|
|
13183
|
+
console.log(` ... and ${rulesConfig.backups.length - 3} more`);
|
|
12482
13184
|
}
|
|
12483
13185
|
}
|
|
12484
13186
|
console.log("\n\u2139\uFE0F Commands:");
|
|
@@ -12487,9 +13189,9 @@ Available Backups (${projectConfig.backups.length}):`);
|
|
|
12487
13189
|
console.log(' \u2022 "jai1 rules apply" - Apply a different preset (replaces current)');
|
|
12488
13190
|
});
|
|
12489
13191
|
}
|
|
12490
|
-
async function
|
|
13192
|
+
async function checkPathExists(path13) {
|
|
12491
13193
|
try {
|
|
12492
|
-
await
|
|
13194
|
+
await fs24.access(join15(process.cwd(), path13));
|
|
12493
13195
|
return true;
|
|
12494
13196
|
} catch {
|
|
12495
13197
|
return false;
|
|
@@ -12498,11 +13200,11 @@ async function checkPathExists2(path10) {
|
|
|
12498
13200
|
async function checkIdeFilesExist(ideId, format) {
|
|
12499
13201
|
try {
|
|
12500
13202
|
if (ideId === "agentsmd") {
|
|
12501
|
-
return await
|
|
13203
|
+
return await checkPathExists("AGENTS.md");
|
|
12502
13204
|
} else if (ideId === "gemini") {
|
|
12503
|
-
return await
|
|
13205
|
+
return await checkPathExists("GEMINI.md");
|
|
12504
13206
|
} else {
|
|
12505
|
-
return await
|
|
13207
|
+
return await checkPathExists(format.rulesPath);
|
|
12506
13208
|
}
|
|
12507
13209
|
} catch {
|
|
12508
13210
|
return false;
|
|
@@ -12511,26 +13213,26 @@ async function checkIdeFilesExist(ideId, format) {
|
|
|
12511
13213
|
|
|
12512
13214
|
// src/commands/rules/index.ts
|
|
12513
13215
|
function showRulesHelp() {
|
|
12514
|
-
console.log(
|
|
13216
|
+
console.log(chalk17.bold.cyan("\u{1F4CB} jai1 rules") + chalk17.dim(" - Qu\u1EA3n l\xFD rule presets cho AI agents"));
|
|
12515
13217
|
console.log();
|
|
12516
|
-
console.log(
|
|
12517
|
-
console.log(` ${
|
|
12518
|
-
console.log(` ${
|
|
12519
|
-
console.log(` ${
|
|
12520
|
-
console.log(` ${
|
|
12521
|
-
console.log(` ${
|
|
12522
|
-
console.log(` ${
|
|
13218
|
+
console.log(chalk17.bold("C\xE1c l\u1EC7nh:"));
|
|
13219
|
+
console.log(` ${chalk17.cyan("list")} Li\u1EC7t k\xEA c\xE1c presets c\xF3 s\u1EB5n`);
|
|
13220
|
+
console.log(` ${chalk17.cyan("info")} Xem chi ti\u1EBFt m\u1ED9t preset`);
|
|
13221
|
+
console.log(` ${chalk17.cyan("init")} Kh\u1EDFi t\u1EA1o rules t\u1EEB preset`);
|
|
13222
|
+
console.log(` ${chalk17.cyan("apply")} \xC1p d\u1EE5ng preset v\xE0o project`);
|
|
13223
|
+
console.log(` ${chalk17.cyan("sync")} \u0110\u1ED3ng b\u1ED9 rules sang c\xE1c \u0111\u1ECBnh d\u1EA1ng IDE`);
|
|
13224
|
+
console.log(` ${chalk17.cyan("restore")} Kh\xF4i ph\u1EE5c rules t\u1EEB backup`);
|
|
12523
13225
|
console.log();
|
|
12524
|
-
console.log(
|
|
12525
|
-
console.log(
|
|
12526
|
-
console.log(
|
|
12527
|
-
console.log(
|
|
12528
|
-
console.log(
|
|
13226
|
+
console.log(chalk17.bold("V\xED d\u1EE5:"));
|
|
13227
|
+
console.log(chalk17.dim(" $ jai1 rules list"));
|
|
13228
|
+
console.log(chalk17.dim(" $ jai1 rules info react-typescript"));
|
|
13229
|
+
console.log(chalk17.dim(" $ jai1 rules init --preset=react-typescript"));
|
|
13230
|
+
console.log(chalk17.dim(" $ jai1 rules apply react-typescript"));
|
|
12529
13231
|
console.log();
|
|
12530
|
-
console.log(
|
|
13232
|
+
console.log(chalk17.dim('Ch\u1EA1y "jai1 rules <l\u1EC7nh> --help" \u0111\u1EC3 xem chi ti\u1EBFt'));
|
|
12531
13233
|
}
|
|
12532
13234
|
function createRulesCommand() {
|
|
12533
|
-
const rulesCommand = new
|
|
13235
|
+
const rulesCommand = new Command49("rules").description("Manage rule presets for AI agents").action(() => {
|
|
12534
13236
|
showRulesHelp();
|
|
12535
13237
|
});
|
|
12536
13238
|
rulesCommand.addCommand(createRulesListCommand());
|
|
@@ -12543,10 +13245,10 @@ function createRulesCommand() {
|
|
|
12543
13245
|
}
|
|
12544
13246
|
|
|
12545
13247
|
// src/commands/upgrade.ts
|
|
12546
|
-
import { Command as
|
|
13248
|
+
import { Command as Command50 } from "commander";
|
|
12547
13249
|
import { confirm as confirm11 } from "@inquirer/prompts";
|
|
12548
|
-
import { execSync as
|
|
12549
|
-
var
|
|
13250
|
+
import { execSync as execSync4 } from "child_process";
|
|
13251
|
+
var colors3 = {
|
|
12550
13252
|
yellow: "\x1B[33m",
|
|
12551
13253
|
green: "\x1B[32m",
|
|
12552
13254
|
cyan: "\x1B[36m",
|
|
@@ -12555,7 +13257,7 @@ var colors4 = {
|
|
|
12555
13257
|
bold: "\x1B[1m"
|
|
12556
13258
|
};
|
|
12557
13259
|
function createUpgradeCommand() {
|
|
12558
|
-
return new
|
|
13260
|
+
return new Command50("upgrade").description("Upgrade jai1-client to the latest version").option("--check", "Only check for updates without installing").option("--force", "Force upgrade without confirmation").action(async (options) => {
|
|
12559
13261
|
await handleUpgrade(options);
|
|
12560
13262
|
});
|
|
12561
13263
|
}
|
|
@@ -12566,7 +13268,7 @@ async function handleUpgrade(options) {
|
|
|
12566
13268
|
throw new ValidationError('Not initialized. Run "jai1 auth" first.');
|
|
12567
13269
|
}
|
|
12568
13270
|
try {
|
|
12569
|
-
console.log(`${
|
|
13271
|
+
console.log(`${colors3.cyan}\u{1F50D} Checking for updates...${colors3.reset}`);
|
|
12570
13272
|
const response = await fetch(`${config.apiUrl}/api/versions/client`, {
|
|
12571
13273
|
headers: {
|
|
12572
13274
|
"JAI1-Access-Key": config.accessKey
|
|
@@ -12581,20 +13283,20 @@ async function handleUpgrade(options) {
|
|
|
12581
13283
|
const latestVersion = data.version;
|
|
12582
13284
|
const currentVersion = package_default.version;
|
|
12583
13285
|
console.log(`
|
|
12584
|
-
${
|
|
12585
|
-
console.log(`${
|
|
13286
|
+
${colors3.bold}Current version:${colors3.reset} ${currentVersion}`);
|
|
13287
|
+
console.log(`${colors3.bold}Latest version:${colors3.reset} ${latestVersion}
|
|
12586
13288
|
`);
|
|
12587
13289
|
if (!isNewerVersion2(latestVersion, currentVersion)) {
|
|
12588
|
-
console.log(`${
|
|
13290
|
+
console.log(`${colors3.green}\u2705 You're already on the latest version!${colors3.reset}
|
|
12589
13291
|
`);
|
|
12590
13292
|
return;
|
|
12591
13293
|
}
|
|
12592
|
-
console.log(`${
|
|
12593
|
-
console.log(`${
|
|
12594
|
-
console.log(`${
|
|
13294
|
+
console.log(`${colors3.yellow}\u256D\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E${colors3.reset}`);
|
|
13295
|
+
console.log(`${colors3.yellow}\u2502${colors3.reset} ${colors3.bold}\u2B06\uFE0F Update available!${colors3.reset} ${currentVersion} \u2192 ${colors3.cyan}${latestVersion}${colors3.reset} ${colors3.yellow}\u2502${colors3.reset}`);
|
|
13296
|
+
console.log(`${colors3.yellow}\u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256F${colors3.reset}
|
|
12595
13297
|
`);
|
|
12596
13298
|
if (options.check) {
|
|
12597
|
-
console.log(`${
|
|
13299
|
+
console.log(`${colors3.cyan}Run "jai1 upgrade" to install the latest version.${colors3.reset}
|
|
12598
13300
|
`);
|
|
12599
13301
|
return;
|
|
12600
13302
|
}
|
|
@@ -12604,24 +13306,24 @@ ${colors4.bold}Current version:${colors4.reset} ${currentVersion}`);
|
|
|
12604
13306
|
default: true
|
|
12605
13307
|
});
|
|
12606
13308
|
if (!shouldUpdate) {
|
|
12607
|
-
console.log(`${
|
|
13309
|
+
console.log(`${colors3.yellow}\u23F8\uFE0F Upgrade cancelled.${colors3.reset}
|
|
12608
13310
|
`);
|
|
12609
13311
|
return;
|
|
12610
13312
|
}
|
|
12611
13313
|
}
|
|
12612
13314
|
console.log(`
|
|
12613
|
-
${
|
|
13315
|
+
${colors3.cyan}\u{1F4E5} Installing latest version...${colors3.reset}
|
|
12614
13316
|
`);
|
|
12615
13317
|
try {
|
|
12616
13318
|
const packageManager2 = detectPackageManager();
|
|
12617
13319
|
const installCommand = getInstallCommand(packageManager2);
|
|
12618
|
-
console.log(`${
|
|
12619
|
-
|
|
13320
|
+
console.log(`${colors3.cyan}Using ${packageManager2}...${colors3.reset}`);
|
|
13321
|
+
execSync4(installCommand, {
|
|
12620
13322
|
stdio: "inherit",
|
|
12621
13323
|
env: { ...process.env, FORCE_COLOR: "1" }
|
|
12622
13324
|
});
|
|
12623
13325
|
console.log(`
|
|
12624
|
-
${
|
|
13326
|
+
${colors3.green}\u2705 Successfully upgraded to version ${latestVersion}!${colors3.reset}
|
|
12625
13327
|
`);
|
|
12626
13328
|
trackAction("upgrade", {
|
|
12627
13329
|
from_version: currentVersion,
|
|
@@ -12631,17 +13333,17 @@ ${colors4.green}\u2705 Successfully upgraded to version ${latestVersion}!${color
|
|
|
12631
13333
|
disableUpdateCheck();
|
|
12632
13334
|
} catch (error) {
|
|
12633
13335
|
console.error(`
|
|
12634
|
-
${
|
|
12635
|
-
console.error(`${
|
|
13336
|
+
${colors3.red}\u274C Upgrade failed!${colors3.reset}`);
|
|
13337
|
+
console.error(`${colors3.red}Error: ${error instanceof Error ? error.message : "Unknown error"}${colors3.reset}
|
|
12636
13338
|
`);
|
|
12637
|
-
console.error(`${
|
|
13339
|
+
console.error(`${colors3.yellow}\u{1F4A1} You can try manually upgrading with:${colors3.reset}`);
|
|
12638
13340
|
const manualCommands = {
|
|
12639
13341
|
npm: `npm install -g @jvittechs/jai1-cli@latest`,
|
|
12640
13342
|
pnpm: `pnpm add -g @jvittechs/jai1-cli@latest`,
|
|
12641
13343
|
yarn: `yarn global add @jvittechs/jai1-cli@latest`,
|
|
12642
13344
|
bun: `bun add -g @jvittechs/jai1-cli@latest`
|
|
12643
13345
|
};
|
|
12644
|
-
console.error(` ${
|
|
13346
|
+
console.error(` ${colors3.cyan}${manualCommands[packageManager]}${colors3.reset}
|
|
12645
13347
|
`);
|
|
12646
13348
|
throw error;
|
|
12647
13349
|
}
|
|
@@ -12665,7 +13367,7 @@ function isNewerVersion2(remote, local) {
|
|
|
12665
13367
|
}
|
|
12666
13368
|
function detectPackageManager() {
|
|
12667
13369
|
try {
|
|
12668
|
-
const jai1Path =
|
|
13370
|
+
const jai1Path = execSync4("which jai1 || where jai1", {
|
|
12669
13371
|
encoding: "utf-8",
|
|
12670
13372
|
stdio: ["pipe", "pipe", "ignore"]
|
|
12671
13373
|
}).trim();
|
|
@@ -12703,11 +13405,11 @@ function getInstallCommand(packageManager2) {
|
|
|
12703
13405
|
}
|
|
12704
13406
|
|
|
12705
13407
|
// src/commands/clean.ts
|
|
12706
|
-
import { Command as
|
|
13408
|
+
import { Command as Command51 } from "commander";
|
|
12707
13409
|
import { confirm as confirm12, select as select7 } from "@inquirer/prompts";
|
|
12708
|
-
import { join as
|
|
13410
|
+
import { join as join16 } from "path";
|
|
12709
13411
|
function createCleanCommand() {
|
|
12710
|
-
return new
|
|
13412
|
+
return new Command51("clean").description("Clean up backups, cache, and temporary files").option("-y, --yes", "Skip confirmation").option("--backups", "Clean only backup files").option("--all", "Clean all (backups + cache)").action(async (options) => {
|
|
12711
13413
|
await handleClean(options);
|
|
12712
13414
|
});
|
|
12713
13415
|
}
|
|
@@ -12718,7 +13420,7 @@ async function handleClean(options) {
|
|
|
12718
13420
|
{
|
|
12719
13421
|
name: "Backups",
|
|
12720
13422
|
description: "Component backup files (.jai1_backup/)",
|
|
12721
|
-
path:
|
|
13423
|
+
path: join16(cwd, ".jai1_backup"),
|
|
12722
13424
|
check: async () => {
|
|
12723
13425
|
const backups = await service.listBackups(cwd);
|
|
12724
13426
|
return { exists: backups.length > 0, count: backups.length };
|
|
@@ -12777,7 +13479,8 @@ async function handleClean(options) {
|
|
|
12777
13479
|
})),
|
|
12778
13480
|
{ name: "\u{1F9F9} Clean all", value: "all" },
|
|
12779
13481
|
{ name: "\u274C Cancel", value: "cancel" }
|
|
12780
|
-
]
|
|
13482
|
+
],
|
|
13483
|
+
theme: selectTheme
|
|
12781
13484
|
});
|
|
12782
13485
|
if (action === "cancel") {
|
|
12783
13486
|
console.log("\n\u274C Operation cancelled.");
|
|
@@ -12820,10 +13523,10 @@ async function cleanTarget(target, skipConfirm) {
|
|
|
12820
13523
|
}
|
|
12821
13524
|
|
|
12822
13525
|
// src/commands/redmine/check.ts
|
|
12823
|
-
import { Command as
|
|
13526
|
+
import { Command as Command52 } from "commander";
|
|
12824
13527
|
|
|
12825
13528
|
// src/services/redmine-config.service.ts
|
|
12826
|
-
import { readFile as
|
|
13529
|
+
import { readFile as readFile7 } from "fs/promises";
|
|
12827
13530
|
import { resolve as resolve2 } from "path";
|
|
12828
13531
|
|
|
12829
13532
|
// src/types/redmine.types.ts
|
|
@@ -12882,7 +13585,7 @@ var RedmineConfigService = class {
|
|
|
12882
13585
|
*/
|
|
12883
13586
|
async load() {
|
|
12884
13587
|
try {
|
|
12885
|
-
const content = await
|
|
13588
|
+
const content = await readFile7(this.configPath, "utf-8");
|
|
12886
13589
|
const rawConfig = parse(content);
|
|
12887
13590
|
return RedmineConfigSchema.parse(rawConfig);
|
|
12888
13591
|
} catch (error) {
|
|
@@ -12921,7 +13624,7 @@ var RedmineConfigService = class {
|
|
|
12921
13624
|
// src/api.ts
|
|
12922
13625
|
import { fetch as fetch2 } from "undici";
|
|
12923
13626
|
import pRetry2 from "p-retry";
|
|
12924
|
-
import
|
|
13627
|
+
import pLimit4 from "p-limit";
|
|
12925
13628
|
var RedmineApiError = class extends Error {
|
|
12926
13629
|
constructor(message, status, response) {
|
|
12927
13630
|
super(message);
|
|
@@ -12939,10 +13642,10 @@ var RedmineApiClient = class {
|
|
|
12939
13642
|
this.baseUrl = config.baseUrl.replace(/\/$/, "");
|
|
12940
13643
|
this.apiAccessToken = config.apiAccessToken;
|
|
12941
13644
|
this.retryConfig = config.defaults.retry;
|
|
12942
|
-
this.concurrencyLimit =
|
|
13645
|
+
this.concurrencyLimit = pLimit4(config.defaults.concurrency);
|
|
12943
13646
|
}
|
|
12944
|
-
async request(
|
|
12945
|
-
const url = `${this.baseUrl}${
|
|
13647
|
+
async request(path13, options = {}) {
|
|
13648
|
+
const url = `${this.baseUrl}${path13}`;
|
|
12946
13649
|
const headers = {
|
|
12947
13650
|
"X-Redmine-API-Key": this.apiAccessToken,
|
|
12948
13651
|
"Content-Type": "application/json",
|
|
@@ -13003,8 +13706,8 @@ var RedmineApiClient = class {
|
|
|
13003
13706
|
if (include && include.length > 0) {
|
|
13004
13707
|
params.append("include", include.join(","));
|
|
13005
13708
|
}
|
|
13006
|
-
const
|
|
13007
|
-
return this.request(
|
|
13709
|
+
const path13 = `/issues/${issueId}.json${params.toString() ? `?${params.toString()}` : ""}`;
|
|
13710
|
+
return this.request(path13);
|
|
13008
13711
|
}
|
|
13009
13712
|
async getIssues(projectId, options = {}) {
|
|
13010
13713
|
const params = new URLSearchParams();
|
|
@@ -13024,8 +13727,8 @@ var RedmineApiClient = class {
|
|
|
13024
13727
|
if (options.updatedSince) {
|
|
13025
13728
|
params.append("updated_on", `>=${options.updatedSince}`);
|
|
13026
13729
|
}
|
|
13027
|
-
const
|
|
13028
|
-
return this.request(
|
|
13730
|
+
const path13 = `/issues.json?${params.toString()}`;
|
|
13731
|
+
return this.request(path13);
|
|
13029
13732
|
}
|
|
13030
13733
|
async getAllIssues(projectId, options = {}) {
|
|
13031
13734
|
const pageSize = options.pageSize || 100;
|
|
@@ -13127,7 +13830,7 @@ async function checkConnectivity(config) {
|
|
|
13127
13830
|
|
|
13128
13831
|
// src/commands/redmine/check.ts
|
|
13129
13832
|
function createRedmineCheckCommand() {
|
|
13130
|
-
const cmd = new
|
|
13833
|
+
const cmd = new Command52("check").description("Check Redmine connectivity").option("-c, --config <path>", "Config file path", "redmine.config.yaml").option("--json", "Output as JSON").action(async (options) => {
|
|
13131
13834
|
await handleRedmineCheck(options);
|
|
13132
13835
|
});
|
|
13133
13836
|
return cmd;
|
|
@@ -13155,7 +13858,7 @@ async function handleRedmineCheck(options) {
|
|
|
13155
13858
|
}
|
|
13156
13859
|
|
|
13157
13860
|
// src/commands/redmine/sync-issue.ts
|
|
13158
|
-
import { Command as
|
|
13861
|
+
import { Command as Command53 } from "commander";
|
|
13159
13862
|
|
|
13160
13863
|
// src/sync-issue.ts
|
|
13161
13864
|
import { resolve as resolve3, relative } from "path";
|
|
@@ -13297,7 +14000,7 @@ function generateFilename(issueId, title, config, existingSlugs = /* @__PURE__ *
|
|
|
13297
14000
|
}
|
|
13298
14001
|
|
|
13299
14002
|
// src/file.util.ts
|
|
13300
|
-
import { readFile as
|
|
14003
|
+
import { readFile as readFile8, writeFile as writeFile2, mkdir } from "fs/promises";
|
|
13301
14004
|
import { dirname as dirname2 } from "path";
|
|
13302
14005
|
import matter4 from "gray-matter";
|
|
13303
14006
|
async function ensureDir(filePath) {
|
|
@@ -13306,7 +14009,7 @@ async function ensureDir(filePath) {
|
|
|
13306
14009
|
}
|
|
13307
14010
|
async function readMarkdownFile(filePath) {
|
|
13308
14011
|
try {
|
|
13309
|
-
const content = await
|
|
14012
|
+
const content = await readFile8(filePath, "utf-8");
|
|
13310
14013
|
return parseMarkdownContent(content);
|
|
13311
14014
|
} catch (error) {
|
|
13312
14015
|
if (error.code === "ENOENT") {
|
|
@@ -13539,7 +14242,7 @@ function extractIssueIdFromUrl(url) {
|
|
|
13539
14242
|
|
|
13540
14243
|
// src/commands/redmine/sync-issue.ts
|
|
13541
14244
|
function createSyncIssueCommand() {
|
|
13542
|
-
const cmd = new
|
|
14245
|
+
const cmd = new Command53("issue").description("Sync a single issue").option("-i, --id <number>", "Issue ID").option("-u, --url <url>", "Issue URL").option("--dry-run", "Preview without making changes").option("-c, --config <path>", "Config file path").option("-o, --output-dir <path>", "Output directory").option("--json", "Output as JSON").action(async (options) => {
|
|
13543
14246
|
await handleSyncIssue(options);
|
|
13544
14247
|
});
|
|
13545
14248
|
return cmd;
|
|
@@ -13583,7 +14286,7 @@ async function handleSyncIssue(options) {
|
|
|
13583
14286
|
}
|
|
13584
14287
|
|
|
13585
14288
|
// src/commands/redmine/sync-project.ts
|
|
13586
|
-
import { Command as
|
|
14289
|
+
import { Command as Command54 } from "commander";
|
|
13587
14290
|
|
|
13588
14291
|
// src/sync-project.ts
|
|
13589
14292
|
async function syncProject(config, options = {}) {
|
|
@@ -13653,7 +14356,7 @@ async function syncProject(config, options = {}) {
|
|
|
13653
14356
|
|
|
13654
14357
|
// src/commands/redmine/sync-project.ts
|
|
13655
14358
|
function createSyncProjectCommand() {
|
|
13656
|
-
const cmd = new
|
|
14359
|
+
const cmd = new Command54("project").description("Sync all issues in a project").option("-s, --status <status>", "Filter by status (default: *)", "*").option("--updated-since <date>", "Only sync issues updated since YYYY-MM-DD").option("--concurrency <number>", "Number of concurrent requests").option("--page-size <number>", "Page size for API requests").option("--dry-run", "Preview without making changes").option("-c, --config <path>", "Config file path").option("-o, --output-dir <path>", "Output directory").option("--json", "Output as JSON").action(async (options) => {
|
|
13657
14360
|
await handleSyncProject(options);
|
|
13658
14361
|
});
|
|
13659
14362
|
return cmd;
|
|
@@ -13708,12 +14411,12 @@ async function handleSyncProject(options) {
|
|
|
13708
14411
|
}
|
|
13709
14412
|
|
|
13710
14413
|
// src/commands/framework/info.ts
|
|
13711
|
-
import { Command as
|
|
13712
|
-
import { promises as
|
|
13713
|
-
import { join as
|
|
14414
|
+
import { Command as Command55 } from "commander";
|
|
14415
|
+
import { promises as fs25 } from "fs";
|
|
14416
|
+
import { join as join17 } from "path";
|
|
13714
14417
|
import { homedir as homedir5 } from "os";
|
|
13715
14418
|
function createInfoCommand() {
|
|
13716
|
-
const cmd = new
|
|
14419
|
+
const cmd = new Command55("info").description("Show jai1-client configuration and status").option("--json", "Output as JSON").option("--verbose", "Show detailed information").action(async (options) => {
|
|
13717
14420
|
await handleInfo(options);
|
|
13718
14421
|
});
|
|
13719
14422
|
return cmd;
|
|
@@ -13724,7 +14427,7 @@ async function handleInfo(options) {
|
|
|
13724
14427
|
if (!config) {
|
|
13725
14428
|
throw new ValidationError('Not initialized. Run "jai1 auth" first.');
|
|
13726
14429
|
}
|
|
13727
|
-
const frameworkPath =
|
|
14430
|
+
const frameworkPath = join17(homedir5(), ".jai1", "framework");
|
|
13728
14431
|
const projectStatus = await getProjectStatus2();
|
|
13729
14432
|
const info = {
|
|
13730
14433
|
configPath: configService.getConfigPath(),
|
|
@@ -13759,9 +14462,9 @@ function maskKey3(key) {
|
|
|
13759
14462
|
return "****" + key.slice(-4);
|
|
13760
14463
|
}
|
|
13761
14464
|
async function getProjectStatus2() {
|
|
13762
|
-
const projectJai1 =
|
|
14465
|
+
const projectJai1 = join17(process.cwd(), ".jai1");
|
|
13763
14466
|
try {
|
|
13764
|
-
await
|
|
14467
|
+
await fs25.access(projectJai1);
|
|
13765
14468
|
return { exists: true, version: "Synced" };
|
|
13766
14469
|
} catch {
|
|
13767
14470
|
return { exists: false };
|
|
@@ -13769,10 +14472,10 @@ async function getProjectStatus2() {
|
|
|
13769
14472
|
}
|
|
13770
14473
|
|
|
13771
14474
|
// src/commands/self-update.ts
|
|
13772
|
-
import { Command as
|
|
14475
|
+
import { Command as Command56 } from "commander";
|
|
13773
14476
|
import { confirm as confirm13 } from "@inquirer/prompts";
|
|
13774
|
-
import { execSync as
|
|
13775
|
-
var
|
|
14477
|
+
import { execSync as execSync5 } from "child_process";
|
|
14478
|
+
var colors4 = {
|
|
13776
14479
|
yellow: "\x1B[33m",
|
|
13777
14480
|
green: "\x1B[32m",
|
|
13778
14481
|
cyan: "\x1B[36m",
|
|
@@ -13781,7 +14484,7 @@ var colors5 = {
|
|
|
13781
14484
|
bold: "\x1B[1m"
|
|
13782
14485
|
};
|
|
13783
14486
|
function createSelfUpdateCommand() {
|
|
13784
|
-
return new
|
|
14487
|
+
return new Command56("self-update").description("Update jai1-client to the latest version").option("--check", "Only check for updates without installing").option("--force", "Force update without confirmation").action(async (options) => {
|
|
13785
14488
|
await handleSelfUpdate(options);
|
|
13786
14489
|
});
|
|
13787
14490
|
}
|
|
@@ -13792,7 +14495,7 @@ async function handleSelfUpdate(options) {
|
|
|
13792
14495
|
throw new ValidationError('Not initialized. Run "jai1 auth" first.');
|
|
13793
14496
|
}
|
|
13794
14497
|
try {
|
|
13795
|
-
console.log(`${
|
|
14498
|
+
console.log(`${colors4.cyan}\u{1F50D} Checking for updates...${colors4.reset}`);
|
|
13796
14499
|
const response = await fetch(`${config.apiUrl}/api/versions/client`, {
|
|
13797
14500
|
headers: {
|
|
13798
14501
|
"JAI1-Access-Key": config.accessKey
|
|
@@ -13807,20 +14510,20 @@ async function handleSelfUpdate(options) {
|
|
|
13807
14510
|
const latestVersion = data.version;
|
|
13808
14511
|
const currentVersion = package_default.version;
|
|
13809
14512
|
console.log(`
|
|
13810
|
-
${
|
|
13811
|
-
console.log(`${
|
|
14513
|
+
${colors4.bold}Current version:${colors4.reset} ${currentVersion}`);
|
|
14514
|
+
console.log(`${colors4.bold}Latest version:${colors4.reset} ${latestVersion}
|
|
13812
14515
|
`);
|
|
13813
14516
|
if (!isNewerVersion3(latestVersion, currentVersion)) {
|
|
13814
|
-
console.log(`${
|
|
14517
|
+
console.log(`${colors4.green}\u2705 You're already on the latest version!${colors4.reset}
|
|
13815
14518
|
`);
|
|
13816
14519
|
return;
|
|
13817
14520
|
}
|
|
13818
|
-
console.log(`${
|
|
13819
|
-
console.log(`${
|
|
13820
|
-
console.log(`${
|
|
14521
|
+
console.log(`${colors4.yellow}\u256D\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E${colors4.reset}`);
|
|
14522
|
+
console.log(`${colors4.yellow}\u2502${colors4.reset} ${colors4.bold}\u2B06\uFE0F Update available!${colors4.reset} ${currentVersion} \u2192 ${colors4.cyan}${latestVersion}${colors4.reset} ${colors4.yellow}\u2502${colors4.reset}`);
|
|
14523
|
+
console.log(`${colors4.yellow}\u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256F${colors4.reset}
|
|
13821
14524
|
`);
|
|
13822
14525
|
if (options.check) {
|
|
13823
|
-
console.log(`${
|
|
14526
|
+
console.log(`${colors4.cyan}Run "jai1 self-update" to install the latest version.${colors4.reset}
|
|
13824
14527
|
`);
|
|
13825
14528
|
return;
|
|
13826
14529
|
}
|
|
@@ -13830,24 +14533,24 @@ ${colors5.bold}Current version:${colors5.reset} ${currentVersion}`);
|
|
|
13830
14533
|
default: true
|
|
13831
14534
|
});
|
|
13832
14535
|
if (!shouldUpdate) {
|
|
13833
|
-
console.log(`${
|
|
14536
|
+
console.log(`${colors4.yellow}\u23F8\uFE0F Update cancelled.${colors4.reset}
|
|
13834
14537
|
`);
|
|
13835
14538
|
return;
|
|
13836
14539
|
}
|
|
13837
14540
|
}
|
|
13838
14541
|
console.log(`
|
|
13839
|
-
${
|
|
14542
|
+
${colors4.cyan}\u{1F4E5} Installing latest version...${colors4.reset}
|
|
13840
14543
|
`);
|
|
13841
14544
|
try {
|
|
13842
14545
|
const packageManager2 = detectPackageManager2();
|
|
13843
14546
|
const installCommand = getInstallCommand2(packageManager2);
|
|
13844
|
-
console.log(`${
|
|
13845
|
-
|
|
14547
|
+
console.log(`${colors4.cyan}Using ${packageManager2}...${colors4.reset}`);
|
|
14548
|
+
execSync5(installCommand, {
|
|
13846
14549
|
stdio: "inherit",
|
|
13847
14550
|
env: { ...process.env, FORCE_COLOR: "1" }
|
|
13848
14551
|
});
|
|
13849
14552
|
console.log(`
|
|
13850
|
-
${
|
|
14553
|
+
${colors4.green}\u2705 Successfully updated to version ${latestVersion}!${colors4.reset}
|
|
13851
14554
|
`);
|
|
13852
14555
|
trackAction("self_update", {
|
|
13853
14556
|
from_version: currentVersion,
|
|
@@ -13857,11 +14560,11 @@ ${colors5.green}\u2705 Successfully updated to version ${latestVersion}!${colors
|
|
|
13857
14560
|
disableUpdateCheck();
|
|
13858
14561
|
} catch (error) {
|
|
13859
14562
|
console.error(`
|
|
13860
|
-
${
|
|
13861
|
-
console.error(`${
|
|
14563
|
+
${colors4.red}\u274C Update failed!${colors4.reset}`);
|
|
14564
|
+
console.error(`${colors4.red}Error: ${error instanceof Error ? error.message : "Unknown error"}${colors4.reset}
|
|
13862
14565
|
`);
|
|
13863
|
-
console.error(`${
|
|
13864
|
-
console.error(` ${
|
|
14566
|
+
console.error(`${colors4.yellow}\u{1F4A1} You can try manually updating with:${colors4.reset}`);
|
|
14567
|
+
console.error(` ${colors4.cyan}npm install -g @jvittechs/jai1-cli@latest${colors4.reset}
|
|
13865
14568
|
`);
|
|
13866
14569
|
throw error;
|
|
13867
14570
|
}
|
|
@@ -13889,17 +14592,17 @@ function detectPackageManager2() {
|
|
|
13889
14592
|
if (userAgent.includes("yarn")) return "yarn";
|
|
13890
14593
|
if (userAgent.includes("bun")) return "bun";
|
|
13891
14594
|
try {
|
|
13892
|
-
|
|
14595
|
+
execSync5("pnpm --version", { stdio: "ignore" });
|
|
13893
14596
|
return "pnpm";
|
|
13894
14597
|
} catch {
|
|
13895
14598
|
}
|
|
13896
14599
|
try {
|
|
13897
|
-
|
|
14600
|
+
execSync5("yarn --version", { stdio: "ignore" });
|
|
13898
14601
|
return "yarn";
|
|
13899
14602
|
} catch {
|
|
13900
14603
|
}
|
|
13901
14604
|
try {
|
|
13902
|
-
|
|
14605
|
+
execSync5("bun --version", { stdio: "ignore" });
|
|
13903
14606
|
return "bun";
|
|
13904
14607
|
} catch {
|
|
13905
14608
|
}
|
|
@@ -13921,10 +14624,10 @@ function getInstallCommand2(packageManager2) {
|
|
|
13921
14624
|
}
|
|
13922
14625
|
|
|
13923
14626
|
// src/commands/clear-backups.ts
|
|
13924
|
-
import { Command as
|
|
14627
|
+
import { Command as Command57 } from "commander";
|
|
13925
14628
|
import { confirm as confirm14 } from "@inquirer/prompts";
|
|
13926
14629
|
function createClearBackupsCommand() {
|
|
13927
|
-
return new
|
|
14630
|
+
return new Command57("clear-backups").description("Clear backup files").option("-y, --yes", "Skip confirmation").action(async (options) => {
|
|
13928
14631
|
const service = new ComponentsService();
|
|
13929
14632
|
const backups = await service.listBackups(process.cwd());
|
|
13930
14633
|
if (backups.length === 0) {
|
|
@@ -13949,10 +14652,10 @@ function createClearBackupsCommand() {
|
|
|
13949
14652
|
}
|
|
13950
14653
|
|
|
13951
14654
|
// src/commands/vscode/index.ts
|
|
13952
|
-
import { Command as
|
|
14655
|
+
import { Command as Command58 } from "commander";
|
|
13953
14656
|
import { checkbox as checkbox7, confirm as confirm15, select as select8 } from "@inquirer/prompts";
|
|
13954
|
-
import
|
|
13955
|
-
import
|
|
14657
|
+
import fs26 from "fs/promises";
|
|
14658
|
+
import path12 from "path";
|
|
13956
14659
|
import { existsSync as existsSync3 } from "fs";
|
|
13957
14660
|
var PERFORMANCE_GROUPS2 = {
|
|
13958
14661
|
telemetry: {
|
|
@@ -14089,7 +14792,7 @@ var PERFORMANCE_GROUPS2 = {
|
|
|
14089
14792
|
}
|
|
14090
14793
|
};
|
|
14091
14794
|
function createVSCodeCommand() {
|
|
14092
|
-
const vscodeCommand = new
|
|
14795
|
+
const vscodeCommand = new Command58("vscode").description("Qu\u1EA3n l\xFD c\xE0i \u0111\u1EB7t VSCode cho d\u1EF1 \xE1n hi\u1EC7n t\u1EA1i");
|
|
14093
14796
|
vscodeCommand.action(async () => {
|
|
14094
14797
|
await interactiveMode2();
|
|
14095
14798
|
});
|
|
@@ -14140,7 +14843,8 @@ async function interactiveMode2() {
|
|
|
14140
14843
|
{ name: "\u274C Disable c\xE1c nh\xF3m t\u1ED1i \u01B0u", value: "disable" },
|
|
14141
14844
|
{ name: "\u{1F680} Max Performance (enable t\u1EA5t c\u1EA3)", value: "max" },
|
|
14142
14845
|
{ name: "\u{1F504} Reset v\u1EC1 m\u1EB7c \u0111\u1ECBnh", value: "reset" }
|
|
14143
|
-
]
|
|
14846
|
+
],
|
|
14847
|
+
theme: selectTheme
|
|
14144
14848
|
});
|
|
14145
14849
|
if (action === "max") {
|
|
14146
14850
|
const allGroups = Object.keys(PERFORMANCE_GROUPS2);
|
|
@@ -14159,7 +14863,8 @@ async function selectGroupsToApply2(action) {
|
|
|
14159
14863
|
try {
|
|
14160
14864
|
const selectedGroups = await checkbox7({
|
|
14161
14865
|
message: `Ch\u1ECDn c\xE1c nh\xF3m \u0111\u1EC3 ${action === "enable" ? "enable" : "disable"} (SPACE \u0111\u1EC3 ch\u1ECDn, ENTER \u0111\u1EC3 x\xE1c nh\u1EADn):`,
|
|
14162
|
-
choices
|
|
14866
|
+
choices,
|
|
14867
|
+
theme: checkboxTheme
|
|
14163
14868
|
});
|
|
14164
14869
|
if (selectedGroups.length === 0) {
|
|
14165
14870
|
console.log("\n\u26A0\uFE0F B\u1EA1n ch\u01B0a ch\u1ECDn nh\xF3m n\xE0o!");
|
|
@@ -14172,8 +14877,8 @@ async function selectGroupsToApply2(action) {
|
|
|
14172
14877
|
}
|
|
14173
14878
|
}
|
|
14174
14879
|
async function applyGroups2(groupKeys, action) {
|
|
14175
|
-
const vscodeDir =
|
|
14176
|
-
const settingsPath =
|
|
14880
|
+
const vscodeDir = path12.join(process.cwd(), ".vscode");
|
|
14881
|
+
const settingsPath = path12.join(vscodeDir, "settings.json");
|
|
14177
14882
|
const invalidGroups = groupKeys.filter((key) => !PERFORMANCE_GROUPS2[key]);
|
|
14178
14883
|
if (invalidGroups.length > 0) {
|
|
14179
14884
|
console.log(`
|
|
@@ -14182,13 +14887,13 @@ async function applyGroups2(groupKeys, action) {
|
|
|
14182
14887
|
return;
|
|
14183
14888
|
}
|
|
14184
14889
|
if (!existsSync3(vscodeDir)) {
|
|
14185
|
-
await
|
|
14890
|
+
await fs26.mkdir(vscodeDir, { recursive: true });
|
|
14186
14891
|
console.log("\u{1F4C1} \u0110\xE3 t\u1EA1o th\u01B0 m\u1EE5c .vscode/");
|
|
14187
14892
|
}
|
|
14188
14893
|
let currentSettings = {};
|
|
14189
14894
|
if (existsSync3(settingsPath)) {
|
|
14190
14895
|
try {
|
|
14191
|
-
const content = await
|
|
14896
|
+
const content = await fs26.readFile(settingsPath, "utf-8");
|
|
14192
14897
|
currentSettings = JSON.parse(content);
|
|
14193
14898
|
console.log("\u{1F4C4} \u0110\xE3 \u0111\u1ECDc c\xE0i \u0111\u1EB7t hi\u1EC7n t\u1EA1i t\u1EEB settings.json");
|
|
14194
14899
|
} catch {
|
|
@@ -14228,14 +14933,14 @@ async function applyGroups2(groupKeys, action) {
|
|
|
14228
14933
|
}
|
|
14229
14934
|
}
|
|
14230
14935
|
}
|
|
14231
|
-
await
|
|
14936
|
+
await fs26.writeFile(settingsPath, JSON.stringify(newSettings, null, 2));
|
|
14232
14937
|
console.log(`
|
|
14233
14938
|
\u2705 \u0110\xE3 c\u1EADp nh\u1EADt c\xE0i \u0111\u1EB7t VSCode t\u1EA1i: ${settingsPath}`);
|
|
14234
14939
|
console.log("\u{1F4A1} M\u1EB9o: Kh\u1EDFi \u0111\u1ED9ng l\u1EA1i VSCode \u0111\u1EC3 \xE1p d\u1EE5ng c\xE1c thay \u0111\u1ED5i.");
|
|
14235
14940
|
}
|
|
14236
14941
|
async function resetSettings2(groupKeys) {
|
|
14237
|
-
const vscodeDir =
|
|
14238
|
-
const settingsPath =
|
|
14942
|
+
const vscodeDir = path12.join(process.cwd(), ".vscode");
|
|
14943
|
+
const settingsPath = path12.join(vscodeDir, "settings.json");
|
|
14239
14944
|
if (!existsSync3(settingsPath)) {
|
|
14240
14945
|
console.log("\n\u26A0\uFE0F Kh\xF4ng t\xECm th\u1EA5y file settings.json");
|
|
14241
14946
|
return;
|
|
@@ -14249,7 +14954,7 @@ async function resetSettings2(groupKeys) {
|
|
|
14249
14954
|
return;
|
|
14250
14955
|
}
|
|
14251
14956
|
if (groupKeys.length === 0) {
|
|
14252
|
-
await
|
|
14957
|
+
await fs26.unlink(settingsPath);
|
|
14253
14958
|
console.log("\n\u2705 \u0110\xE3 x\xF3a file settings.json");
|
|
14254
14959
|
} else {
|
|
14255
14960
|
await applyGroups2(groupKeys, "disable");
|
|
@@ -14260,9 +14965,9 @@ async function resetSettings2(groupKeys) {
|
|
|
14260
14965
|
// src/commands/context.ts
|
|
14261
14966
|
import React43 from "react";
|
|
14262
14967
|
import { render as render6 } from "ink";
|
|
14263
|
-
import { Command as
|
|
14968
|
+
import { Command as Command59 } from "commander";
|
|
14264
14969
|
function createContextCommand() {
|
|
14265
|
-
const cmd = new
|
|
14970
|
+
const cmd = new Command59("context").description("Kh\xE1m ph\xE1 v\xE0 qu\u1EA3n l\xFD context d\u1EF1 \xE1n cho c\xE1c IDE").option("--ide <ide>", "M\u1EDF tr\u1EF1c ti\u1EBFp IDE c\u1EE5 th\u1EC3 (cursor, windsurf, antigravity, jai1)").option("--type <type>", "Hi\u1EC3n th\u1ECB lo\u1EA1i context c\u1EE5 th\u1EC3 (rules, workflows, skills, agents, prompts)").option("--stats", "Hi\u1EC3n th\u1ECB th\u1ED1ng k\xEA context (non-interactive)").action(async (options) => {
|
|
14266
14971
|
let initialIDE;
|
|
14267
14972
|
if (options.ide) {
|
|
14268
14973
|
const validIDEs = ["cursor", "windsurf", "antigravity", "jai1"];
|
|
@@ -14339,10 +15044,10 @@ async function printStats2() {
|
|
|
14339
15044
|
}
|
|
14340
15045
|
|
|
14341
15046
|
// src/commands/migrate-ide.ts
|
|
14342
|
-
import { Command as
|
|
15047
|
+
import { Command as Command60 } from "commander";
|
|
14343
15048
|
import { checkbox as checkbox8, confirm as confirm16 } from "@inquirer/prompts";
|
|
14344
15049
|
function createMigrateIdeCommand() {
|
|
14345
|
-
const cmd = new
|
|
15050
|
+
const cmd = new Command60("migrate-ide").description("Migrate .jai1 rules v\xE0 workflows sang IDEs (Cursor, Windsurf, Claude Code, etc.)").option("--ide <ides...>", "Target IDEs (cursor, windsurf, antigravity, claudecode, opencode)").option("--type <types...>", "Content types (rules, workflows, commands)").option("--dry-run", "Preview changes without writing files").action(async (options) => {
|
|
14346
15051
|
await runMigrateIde(options);
|
|
14347
15052
|
});
|
|
14348
15053
|
return cmd;
|
|
@@ -14372,7 +15077,8 @@ async function runMigrateIde(options) {
|
|
|
14372
15077
|
});
|
|
14373
15078
|
selectedIdes = await checkbox8({
|
|
14374
15079
|
message: "Ch\u1ECDn IDE(s) \u0111\u1EC3 migrate (SPACE \u0111\u1EC3 ch\u1ECDn, ENTER \u0111\u1EC3 x\xE1c nh\u1EADn):",
|
|
14375
|
-
choices: ideChoices
|
|
15080
|
+
choices: ideChoices,
|
|
15081
|
+
theme: checkboxTheme
|
|
14376
15082
|
});
|
|
14377
15083
|
if (selectedIdes.length === 0) {
|
|
14378
15084
|
console.log("\n\u26A0\uFE0F B\u1EA1n ch\u01B0a ch\u1ECDn IDE n\xE0o!");
|
|
@@ -14391,7 +15097,8 @@ async function runMigrateIde(options) {
|
|
|
14391
15097
|
];
|
|
14392
15098
|
selectedTypes = await checkbox8({
|
|
14393
15099
|
message: "Ch\u1ECDn content types \u0111\u1EC3 migrate:",
|
|
14394
|
-
choices: typeChoices
|
|
15100
|
+
choices: typeChoices,
|
|
15101
|
+
theme: checkboxTheme
|
|
14395
15102
|
});
|
|
14396
15103
|
if (selectedTypes.length === 0) {
|
|
14397
15104
|
console.log("\n\u26A0\uFE0F B\u1EA1n ch\u01B0a ch\u1ECDn content type n\xE0o!");
|
|
@@ -14449,54 +15156,54 @@ async function runMigrateIde(options) {
|
|
|
14449
15156
|
|
|
14450
15157
|
// src/utils/help-formatter.ts
|
|
14451
15158
|
import boxen4 from "boxen";
|
|
14452
|
-
import
|
|
15159
|
+
import chalk18 from "chalk";
|
|
14453
15160
|
import gradient from "gradient-string";
|
|
14454
15161
|
import figlet from "figlet";
|
|
14455
15162
|
function showCustomHelp(version) {
|
|
14456
15163
|
const title = figlet.textSync("JAI1", { font: "Small" });
|
|
14457
15164
|
console.log(gradient.pastel(title));
|
|
14458
15165
|
console.log(
|
|
14459
|
-
boxen4(
|
|
15166
|
+
boxen4(chalk18.cyan(`Agentic Coding CLI v${version}`), {
|
|
14460
15167
|
padding: { left: 1, right: 1, top: 0, bottom: 0 },
|
|
14461
15168
|
borderStyle: "round",
|
|
14462
15169
|
borderColor: "cyan"
|
|
14463
15170
|
})
|
|
14464
15171
|
);
|
|
14465
|
-
console.log(
|
|
15172
|
+
console.log(chalk18.bold("\n\u{1F527} Thi\u1EBFt l\u1EADp & Th\xF4ng tin"));
|
|
14466
15173
|
console.log(" auth X\xE1c th\u1EF1c v\xE0 c\u1EA5u h\xECnh jai1-client");
|
|
14467
15174
|
console.log(" status Hi\u1EC3n th\u1ECB tr\u1EA1ng th\xE1i c\u1EA5u h\xECnh");
|
|
14468
15175
|
console.log(" guide Trung t\xE2m h\u1ECDc Agentic Coding");
|
|
14469
|
-
console.log(
|
|
15176
|
+
console.log(chalk18.bold("\n\u{1F4E6} Qu\u1EA3n l\xFD Components"));
|
|
14470
15177
|
console.log(" apply C\xE0i \u0111\u1EB7t components (interactive)");
|
|
14471
15178
|
console.log(" update C\u1EADp nh\u1EADt components \u0111\xE3 c\xE0i");
|
|
14472
15179
|
console.log(" check Ki\u1EC3m tra c\u1EADp nh\u1EADt t\u1EEB server");
|
|
14473
|
-
console.log(
|
|
15180
|
+
console.log(chalk18.bold("\n\u{1F5A5}\uFE0F IDE & T\xEDch h\u1EE3p"));
|
|
14474
15181
|
console.log(" ide L\u1EC7nh c\u1EA5u h\xECnh IDE");
|
|
14475
15182
|
console.log(" chat Chat AI v\u1EDBi Jai1 LLM Proxy");
|
|
14476
15183
|
console.log(" openai-keys Th\xF4ng tin API credentials");
|
|
14477
|
-
console.log(
|
|
15184
|
+
console.log(chalk18.bold("\n\u{1F916} AI Tools"));
|
|
14478
15185
|
console.log(" translate D\u1ECBch v\u0103n b\u1EA3n/file b\u1EB1ng AI");
|
|
14479
15186
|
console.log(" image T\u1EA1o \u1EA3nh (Coming Soon)");
|
|
14480
15187
|
console.log(" stats Th\u1ED1ng k\xEA s\u1EED d\u1EE5ng LLM");
|
|
14481
15188
|
console.log(" feedback G\u1EEDi b\xE1o c\xE1o/\u0111\u1EC1 xu\u1EA5t");
|
|
14482
|
-
console.log(
|
|
15189
|
+
console.log(chalk18.bold("\n\u{1F4C1} Project"));
|
|
14483
15190
|
console.log(" kit Qu\u1EA3n l\xFD starter kits");
|
|
14484
15191
|
console.log(" rules Qu\u1EA3n l\xFD rule presets");
|
|
14485
15192
|
console.log(" deps Qu\u1EA3n l\xFD dependencies");
|
|
14486
15193
|
console.log(" redmine Redmine context sync");
|
|
14487
|
-
console.log(
|
|
15194
|
+
console.log(chalk18.bold("\n\u2699\uFE0F B\u1EA3o tr\xEC"));
|
|
14488
15195
|
console.log(" upgrade C\u1EADp nh\u1EADt jai1-client");
|
|
14489
15196
|
console.log(" clean D\u1ECDn d\u1EB9p cache/backup");
|
|
14490
15197
|
console.log(" utils Developer utilities");
|
|
14491
|
-
console.log(
|
|
15198
|
+
console.log(chalk18.dim("\nS\u1EED d\u1EE5ng: jai1 [l\u1EC7nh] --help \u0111\u1EC3 xem chi ti\u1EBFt"));
|
|
14492
15199
|
}
|
|
14493
15200
|
function showUnknownCommand(commandName) {
|
|
14494
|
-
console.error(
|
|
14495
|
-
console.error(
|
|
15201
|
+
console.error(chalk18.red(`\u274C L\u1EC7nh kh\xF4ng t\u1ED3n t\u1EA1i: ${commandName}`));
|
|
15202
|
+
console.error(chalk18.dim("\nG\u1EE3i \xFD: Ch\u1EA1y jai1 --help \u0111\u1EC3 xem danh s\xE1ch l\u1EC7nh"));
|
|
14496
15203
|
}
|
|
14497
15204
|
|
|
14498
15205
|
// src/cli.ts
|
|
14499
|
-
var program = new
|
|
15206
|
+
var program = new Command61();
|
|
14500
15207
|
if (process.argv.includes("-v") || process.argv.includes("--version")) {
|
|
14501
15208
|
console.log(package_default.version);
|
|
14502
15209
|
if (!process.argv.includes("--skip-update-check")) {
|
|
@@ -14530,9 +15237,9 @@ program.addCommand(createKitCommand());
|
|
|
14530
15237
|
program.addCommand(createRulesCommand());
|
|
14531
15238
|
program.addCommand(createUpgradeCommand());
|
|
14532
15239
|
program.addCommand(createCleanCommand());
|
|
14533
|
-
var redmineCommand = new
|
|
15240
|
+
var redmineCommand = new Command61("redmine").description("Redmine context sync commands");
|
|
14534
15241
|
redmineCommand.addCommand(createRedmineCheckCommand());
|
|
14535
|
-
var syncCommand = new
|
|
15242
|
+
var syncCommand = new Command61("sync").description("Sync Redmine issues to markdown files");
|
|
14536
15243
|
syncCommand.addCommand(createSyncIssueCommand());
|
|
14537
15244
|
syncCommand.addCommand(createSyncProjectCommand());
|
|
14538
15245
|
redmineCommand.addCommand(syncCommand);
|