@promptscript/cli 1.0.0-alpha.0 → 1.0.0-alpha.2
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/CHANGELOG.md +253 -0
- package/README.md +9 -8
- package/index.js +1248 -116
- package/package.json +17 -6
- package/package.publish.json +20 -2
- package/src/cli.d.ts.map +1 -1
- package/src/commands/pull.d.ts +1 -2
- package/src/commands/pull.d.ts.map +1 -1
- package/src/types.d.ts +8 -0
- package/src/types.d.ts.map +1 -1
- package/src/utils/ai-tools-detector.d.ts +1 -1
- package/src/utils/ai-tools-detector.d.ts.map +1 -1
package/index.js
CHANGED
|
@@ -617,6 +617,11 @@ var AI_TOOL_PATTERNS = [
|
|
|
617
617
|
target: "cursor",
|
|
618
618
|
files: [".cursor/rules/project.mdc", ".cursorrules", ".cursor/rules.md"],
|
|
619
619
|
directories: [".cursor", ".cursor/rules"]
|
|
620
|
+
},
|
|
621
|
+
{
|
|
622
|
+
target: "antigravity",
|
|
623
|
+
files: [".agent/rules/project.md"],
|
|
624
|
+
directories: [".agent", ".agent/rules"]
|
|
620
625
|
}
|
|
621
626
|
];
|
|
622
627
|
async function directoryHasContent(dir, services) {
|
|
@@ -633,7 +638,8 @@ async function detectAITools(services = createDefaultServices()) {
|
|
|
633
638
|
const details = {
|
|
634
639
|
github: [],
|
|
635
640
|
claude: [],
|
|
636
|
-
cursor: []
|
|
641
|
+
cursor: [],
|
|
642
|
+
antigravity: []
|
|
637
643
|
};
|
|
638
644
|
for (const pattern of AI_TOOL_PATTERNS) {
|
|
639
645
|
const foundFiles = [];
|
|
@@ -655,7 +661,7 @@ async function detectAITools(services = createDefaultServices()) {
|
|
|
655
661
|
return { detected, details };
|
|
656
662
|
}
|
|
657
663
|
function getAllTargets() {
|
|
658
|
-
return ["github", "claude", "cursor"];
|
|
664
|
+
return ["github", "claude", "cursor", "antigravity"];
|
|
659
665
|
}
|
|
660
666
|
function getSuggestedTargets(detection) {
|
|
661
667
|
return detection.detected.length > 0 ? detection.detected : getAllTargets();
|
|
@@ -678,8 +684,8 @@ function formatDetectionResults(detection) {
|
|
|
678
684
|
var __filename = fileURLToPath(import.meta.url);
|
|
679
685
|
var __dirname = dirname(__filename);
|
|
680
686
|
async function initCommand(options, services = createDefaultServices()) {
|
|
681
|
-
const { fs } = services;
|
|
682
|
-
if (
|
|
687
|
+
const { fs: fs4 } = services;
|
|
688
|
+
if (fs4.existsSync("promptscript.yaml") && !options.force) {
|
|
683
689
|
ConsoleOutput.warn("PromptScript already initialized");
|
|
684
690
|
ConsoleOutput.muted("Use --force to reinitialize");
|
|
685
691
|
return;
|
|
@@ -689,11 +695,11 @@ async function initCommand(options, services = createDefaultServices()) {
|
|
|
689
695
|
const aiToolsDetection = await detectAITools(services);
|
|
690
696
|
const config = await resolveConfig(options, projectInfo, aiToolsDetection, services);
|
|
691
697
|
const spinner = createSpinner("Creating PromptScript configuration...").start();
|
|
692
|
-
await
|
|
698
|
+
await fs4.mkdir(".promptscript", { recursive: true });
|
|
693
699
|
const configContent = generateConfig(config);
|
|
694
|
-
await
|
|
700
|
+
await fs4.writeFile("promptscript.yaml", configContent, "utf-8");
|
|
695
701
|
const projectPsContent = generateProjectPs(config, projectInfo);
|
|
696
|
-
await
|
|
702
|
+
await fs4.writeFile(".promptscript/project.prs", projectPsContent, "utf-8");
|
|
697
703
|
spinner.succeed("PromptScript initialized");
|
|
698
704
|
ConsoleOutput.newline();
|
|
699
705
|
console.log("Created:");
|
|
@@ -786,7 +792,7 @@ async function runInteractivePrompts(options, projectInfo, aiToolsDetection, ser
|
|
|
786
792
|
}
|
|
787
793
|
const wantsRegistry = await prompts2.confirm({
|
|
788
794
|
message: "Do you want to configure a registry?",
|
|
789
|
-
default:
|
|
795
|
+
default: false
|
|
790
796
|
});
|
|
791
797
|
let registry;
|
|
792
798
|
if (wantsRegistry) {
|
|
@@ -830,7 +836,8 @@ function formatTargetName(target) {
|
|
|
830
836
|
const names = {
|
|
831
837
|
github: "GitHub Copilot",
|
|
832
838
|
claude: "Claude (Anthropic)",
|
|
833
|
-
cursor: "Cursor"
|
|
839
|
+
cursor: "Cursor",
|
|
840
|
+
antigravity: "Antigravity (Google)"
|
|
834
841
|
};
|
|
835
842
|
return names[target] ?? target;
|
|
836
843
|
}
|
|
@@ -907,7 +914,7 @@ ${frameworksLine}
|
|
|
907
914
|
// packages/cli/src/commands/compile.ts
|
|
908
915
|
import { resolve as resolve2, dirname as dirname3 } from "path";
|
|
909
916
|
import { writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
910
|
-
import { existsSync as
|
|
917
|
+
import { existsSync as existsSync6 } from "fs";
|
|
911
918
|
import chokidar from "chokidar";
|
|
912
919
|
|
|
913
920
|
// packages/cli/src/config/loader.ts
|
|
@@ -1962,21 +1969,13 @@ var GitHubFormatter = class extends BaseFormatter {
|
|
|
1962
1969
|
const subsections = [];
|
|
1963
1970
|
const eslint = configObj["eslint"];
|
|
1964
1971
|
if (eslint) {
|
|
1965
|
-
const
|
|
1966
|
-
|
|
1967
|
-
"Package configs should use `createBaseConfig(__dirname)` from the base config",
|
|
1968
|
-
"Do not duplicate ESLint rules in package configs - modify the base config instead"
|
|
1969
|
-
];
|
|
1970
|
-
subsections.push(renderer.renderSection("eslint", renderer.renderList(items), 2));
|
|
1972
|
+
const value = this.valueToString(eslint);
|
|
1973
|
+
subsections.push(renderer.renderSection("eslint", `ESLint: ${value}`, 2));
|
|
1971
1974
|
}
|
|
1972
1975
|
const vite = configObj["viteRoot"];
|
|
1973
1976
|
if (vite) {
|
|
1974
|
-
const
|
|
1975
|
-
|
|
1976
|
-
"Do NOT use `import.meta.dirname` - it causes TypeScript errors with current tsconfig settings",
|
|
1977
|
-
"Example: `root: __dirname,`"
|
|
1978
|
-
];
|
|
1979
|
-
subsections.push(renderer.renderSection("vite-vitest", renderer.renderList(items), 2));
|
|
1977
|
+
const value = this.valueToString(vite);
|
|
1978
|
+
subsections.push(renderer.renderSection("vite-vitest", `Vite root: ${value}`, 2));
|
|
1980
1979
|
}
|
|
1981
1980
|
if (subsections.length === 0) return null;
|
|
1982
1981
|
return renderer.renderSection("configuration-files", subsections.join("\n"));
|
|
@@ -2567,7 +2566,9 @@ var ClaudeFormatter = class extends BaseFormatter {
|
|
|
2567
2566
|
const identity2 = this.findBlock(ast, "identity");
|
|
2568
2567
|
if (!identity2) return null;
|
|
2569
2568
|
const text = this.extractText(identity2.content);
|
|
2570
|
-
const cleanText = text.split(
|
|
2569
|
+
const cleanText = text.split(/\n{2,}/).map(
|
|
2570
|
+
(para) => para.split("\n").map((line) => line.trim()).filter((line) => line).join("\n")
|
|
2571
|
+
).filter((para) => para).join("\n\n");
|
|
2571
2572
|
return renderer.renderSection("Project", cleanText) + "\n";
|
|
2572
2573
|
}
|
|
2573
2574
|
techStack(ast, renderer) {
|
|
@@ -2707,7 +2708,7 @@ var ClaudeFormatter = class extends BaseFormatter {
|
|
|
2707
2708
|
if (shortcuts) {
|
|
2708
2709
|
const props = this.getProps(shortcuts.content);
|
|
2709
2710
|
for (const [cmd, desc] of Object.entries(props)) {
|
|
2710
|
-
const shortDesc = this.valueToString(desc).split("\n")[0]
|
|
2711
|
+
const shortDesc = this.valueToString(desc).split("\n")[0] ?? "";
|
|
2711
2712
|
commandLines.push(`${cmd.padEnd(10)} - ${shortDesc}`);
|
|
2712
2713
|
}
|
|
2713
2714
|
}
|
|
@@ -2796,21 +2797,21 @@ var ClaudeFormatter = class extends BaseFormatter {
|
|
|
2796
2797
|
var CURSOR_VERSIONS = {
|
|
2797
2798
|
modern: {
|
|
2798
2799
|
name: "modern",
|
|
2799
|
-
description: "MDC format with YAML frontmatter (.cursor/rules/project.mdc)",
|
|
2800
|
+
description: "MDC format with YAML frontmatter (.cursor/rules/project.mdc) + slash commands (.cursor/commands/)",
|
|
2800
2801
|
outputPath: ".cursor/rules/project.mdc",
|
|
2801
2802
|
cursorVersion: "0.45+",
|
|
2802
2803
|
introduced: "2024-12"
|
|
2803
2804
|
},
|
|
2804
2805
|
frontmatter: {
|
|
2805
2806
|
name: "frontmatter",
|
|
2806
|
-
description: "Alias for modern format (MDC with YAML frontmatter)",
|
|
2807
|
+
description: "Alias for modern format (MDC with YAML frontmatter + slash commands)",
|
|
2807
2808
|
outputPath: ".cursor/rules/project.mdc",
|
|
2808
2809
|
cursorVersion: "0.45+",
|
|
2809
2810
|
introduced: "2024-12"
|
|
2810
2811
|
},
|
|
2811
2812
|
multifile: {
|
|
2812
2813
|
name: "multifile",
|
|
2813
|
-
description: "Multiple MDC files with glob-based targeting",
|
|
2814
|
+
description: "Multiple MDC files with glob-based targeting + slash commands",
|
|
2814
2815
|
outputPath: ".cursor/rules/project.mdc",
|
|
2815
2816
|
cursorVersion: "0.45+",
|
|
2816
2817
|
introduced: "2024-12"
|
|
@@ -2875,9 +2876,11 @@ var CursorFormatter = class extends BaseFormatter {
|
|
|
2875
2876
|
const frontmatter = this.frontmatter(ast);
|
|
2876
2877
|
sections.push(frontmatter);
|
|
2877
2878
|
this.addCommonSections(ast, sections);
|
|
2879
|
+
const commandFiles = this.generateCommandFiles(ast);
|
|
2878
2880
|
return {
|
|
2879
2881
|
path: options?.outputPath ?? CURSOR_VERSIONS.modern.outputPath,
|
|
2880
|
-
content: sections.join("\n\n") + "\n"
|
|
2882
|
+
content: sections.join("\n\n") + "\n",
|
|
2883
|
+
additionalFiles: commandFiles.length > 0 ? commandFiles : void 0
|
|
2881
2884
|
};
|
|
2882
2885
|
}
|
|
2883
2886
|
/**
|
|
@@ -2909,6 +2912,8 @@ var CursorFormatter = class extends BaseFormatter {
|
|
|
2909
2912
|
if (shortcutsFile) {
|
|
2910
2913
|
additionalFiles.push(shortcutsFile);
|
|
2911
2914
|
}
|
|
2915
|
+
const commandFiles = this.generateCommandFiles(ast);
|
|
2916
|
+
additionalFiles.push(...commandFiles);
|
|
2912
2917
|
const mainSections = [];
|
|
2913
2918
|
mainSections.push(this.frontmatter(ast));
|
|
2914
2919
|
mainSections.push(this.intro(ast));
|
|
@@ -3046,6 +3051,34 @@ var CursorFormatter = class extends BaseFormatter {
|
|
|
3046
3051
|
content: sections.join("\n\n") + "\n"
|
|
3047
3052
|
};
|
|
3048
3053
|
}
|
|
3054
|
+
/**
|
|
3055
|
+
* Generate command files for multi-line shortcuts.
|
|
3056
|
+
*
|
|
3057
|
+
* Shortcuts with multi-line content (containing newlines) are converted
|
|
3058
|
+
* to individual `.cursor/commands/*.md` files that can be invoked as
|
|
3059
|
+
* slash commands in Cursor 1.6+.
|
|
3060
|
+
*
|
|
3061
|
+
* Single-line shortcuts remain as documentation in the rules file.
|
|
3062
|
+
*
|
|
3063
|
+
* @see https://cursor.com/changelog/1-6
|
|
3064
|
+
*/
|
|
3065
|
+
generateCommandFiles(ast) {
|
|
3066
|
+
const block = this.findBlock(ast, "shortcuts");
|
|
3067
|
+
if (!block) return [];
|
|
3068
|
+
const props = this.getProps(block.content);
|
|
3069
|
+
const commands = [];
|
|
3070
|
+
for (const [name, value] of Object.entries(props)) {
|
|
3071
|
+
const content = this.valueToString(value);
|
|
3072
|
+
if (content.includes("\n")) {
|
|
3073
|
+
const fileName = name.replace(/^\//, "");
|
|
3074
|
+
commands.push({
|
|
3075
|
+
path: `.cursor/commands/${fileName}.md`,
|
|
3076
|
+
content: content.trim()
|
|
3077
|
+
});
|
|
3078
|
+
}
|
|
3079
|
+
}
|
|
3080
|
+
return commands;
|
|
3081
|
+
}
|
|
3049
3082
|
/**
|
|
3050
3083
|
* Add common sections shared between modern and legacy formats.
|
|
3051
3084
|
*/
|
|
@@ -3350,24 +3383,24 @@ ${items.map((i) => "- " + i).join("\n")}`;
|
|
|
3350
3383
|
${lines.join("\n")}` : null;
|
|
3351
3384
|
}
|
|
3352
3385
|
devCommands(ast) {
|
|
3353
|
-
const
|
|
3354
|
-
if (!
|
|
3355
|
-
const
|
|
3356
|
-
|
|
3357
|
-
|
|
3358
|
-
|
|
3386
|
+
const knowledge = this.findBlock(ast, "knowledge");
|
|
3387
|
+
if (!knowledge) return null;
|
|
3388
|
+
const text = this.extractText(knowledge.content);
|
|
3389
|
+
const match = this.extractSectionWithCodeBlock(text, "## Development Commands");
|
|
3390
|
+
if (!match) return null;
|
|
3391
|
+
const content = match.replace("## Development Commands", "").trim();
|
|
3359
3392
|
return `Development Commands:
|
|
3360
|
-
${
|
|
3393
|
+
${content}`;
|
|
3361
3394
|
}
|
|
3362
3395
|
postWork(ast) {
|
|
3363
|
-
const
|
|
3364
|
-
if (!
|
|
3365
|
-
const
|
|
3366
|
-
|
|
3367
|
-
|
|
3368
|
-
|
|
3396
|
+
const knowledge = this.findBlock(ast, "knowledge");
|
|
3397
|
+
if (!knowledge) return null;
|
|
3398
|
+
const text = this.extractText(knowledge.content);
|
|
3399
|
+
const match = this.extractSectionWithCodeBlock(text, "## Post-Work Verification");
|
|
3400
|
+
if (!match) return null;
|
|
3401
|
+
const content = match.replace("## Post-Work Verification", "").trim();
|
|
3369
3402
|
return `Post-Work Verification:
|
|
3370
|
-
${
|
|
3403
|
+
${content}`;
|
|
3371
3404
|
}
|
|
3372
3405
|
documentation(ast) {
|
|
3373
3406
|
const items = this.extractDocItems(ast);
|
|
@@ -3882,25 +3915,17 @@ ${items.map((i) => "- " + i).join("\n")}`;
|
|
|
3882
3915
|
const subsections = [];
|
|
3883
3916
|
const eslint = configObj["eslint"];
|
|
3884
3917
|
if (eslint) {
|
|
3885
|
-
const
|
|
3886
|
-
"All package ESLint configs must inherit from `eslint.base.config.cjs` in the root",
|
|
3887
|
-
"Package configs should use `createBaseConfig(__dirname)` from the base config",
|
|
3888
|
-
"Do not duplicate ESLint rules in package configs - modify the base config instead"
|
|
3889
|
-
];
|
|
3918
|
+
const value = this.valueToString(eslint);
|
|
3890
3919
|
subsections.push(`### ESLint
|
|
3891
3920
|
|
|
3892
|
-
|
|
3921
|
+
- ESLint: ${value}`);
|
|
3893
3922
|
}
|
|
3894
3923
|
const vite = configObj["viteRoot"];
|
|
3895
3924
|
if (vite) {
|
|
3896
|
-
const
|
|
3897
|
-
"Use `__dirname` for the `root` option in both `vite.config.ts` and `vitest.config.mts`",
|
|
3898
|
-
"Do NOT use `import.meta.dirname` - it causes TypeScript errors with current tsconfig settings",
|
|
3899
|
-
"Example: `root: __dirname,`"
|
|
3900
|
-
];
|
|
3925
|
+
const value = this.valueToString(vite);
|
|
3901
3926
|
subsections.push(`### Vite/Vitest
|
|
3902
3927
|
|
|
3903
|
-
|
|
3928
|
+
- Vite root: ${value}`);
|
|
3904
3929
|
}
|
|
3905
3930
|
if (subsections.length === 0) return null;
|
|
3906
3931
|
return `## Configuration Files
|
|
@@ -14414,11 +14439,22 @@ function mergeBlockContent(parent, child) {
|
|
|
14414
14439
|
return deepClone(child);
|
|
14415
14440
|
}
|
|
14416
14441
|
function mergeTextContent2(parent, child) {
|
|
14442
|
+
const parentVal = parent.value.trim();
|
|
14443
|
+
const childVal = child.value.trim();
|
|
14444
|
+
if (parentVal === childVal) {
|
|
14445
|
+
return { ...child, value: childVal };
|
|
14446
|
+
}
|
|
14447
|
+
if (childVal.includes(parentVal)) {
|
|
14448
|
+
return { ...child, value: childVal };
|
|
14449
|
+
}
|
|
14450
|
+
if (parentVal.includes(childVal)) {
|
|
14451
|
+
return { ...child, value: parentVal };
|
|
14452
|
+
}
|
|
14417
14453
|
return {
|
|
14418
14454
|
...child,
|
|
14419
|
-
value: `${
|
|
14455
|
+
value: `${parentVal}
|
|
14420
14456
|
|
|
14421
|
-
${
|
|
14457
|
+
${childVal}`
|
|
14422
14458
|
};
|
|
14423
14459
|
}
|
|
14424
14460
|
function mergeObjectContent(parent, child) {
|
|
@@ -14436,7 +14472,7 @@ function mergeProperties(parent, child) {
|
|
|
14436
14472
|
} else if (Array.isArray(childVal) && Array.isArray(parentVal)) {
|
|
14437
14473
|
result[key] = uniqueConcat(parentVal, childVal);
|
|
14438
14474
|
} else if (isTextContent(childVal) && isTextContent(parentVal)) {
|
|
14439
|
-
result[key] =
|
|
14475
|
+
result[key] = deepCloneValue(childVal);
|
|
14440
14476
|
} else if (isPlainObject2(childVal) && isPlainObject2(parentVal)) {
|
|
14441
14477
|
result[key] = mergeProperties(
|
|
14442
14478
|
parentVal,
|
|
@@ -14493,36 +14529,189 @@ function deepCloneValue(value) {
|
|
|
14493
14529
|
// packages/resolver/src/imports.ts
|
|
14494
14530
|
var IMPORT_MARKER_PREFIX = "__import__";
|
|
14495
14531
|
function resolveUses(target, use, source) {
|
|
14496
|
-
const
|
|
14497
|
-
const
|
|
14498
|
-
|
|
14499
|
-
|
|
14500
|
-
|
|
14501
|
-
|
|
14502
|
-
type: "
|
|
14503
|
-
|
|
14504
|
-
|
|
14505
|
-
|
|
14532
|
+
const mergedBlocks = mergeBlocks2(target.blocks, source.blocks);
|
|
14533
|
+
const aliasedBlocks = [];
|
|
14534
|
+
if (use.alias) {
|
|
14535
|
+
const alias = use.alias;
|
|
14536
|
+
const markerName = `${IMPORT_MARKER_PREFIX}${alias}`;
|
|
14537
|
+
const marker = {
|
|
14538
|
+
type: "Block",
|
|
14539
|
+
name: markerName,
|
|
14540
|
+
content: {
|
|
14541
|
+
type: "ObjectContent",
|
|
14542
|
+
properties: {
|
|
14543
|
+
__source: use.path.raw,
|
|
14544
|
+
__blocks: source.blocks.map((b) => b.name)
|
|
14545
|
+
},
|
|
14546
|
+
loc: use.loc
|
|
14506
14547
|
},
|
|
14507
14548
|
loc: use.loc
|
|
14508
|
-
}
|
|
14509
|
-
|
|
14549
|
+
};
|
|
14550
|
+
aliasedBlocks.push(marker);
|
|
14551
|
+
for (const block of source.blocks) {
|
|
14552
|
+
aliasedBlocks.push({
|
|
14553
|
+
...block,
|
|
14554
|
+
name: `${IMPORT_MARKER_PREFIX}${alias}.${block.name}`
|
|
14555
|
+
});
|
|
14556
|
+
}
|
|
14557
|
+
}
|
|
14558
|
+
return {
|
|
14559
|
+
...target,
|
|
14560
|
+
blocks: [...mergedBlocks, ...aliasedBlocks]
|
|
14510
14561
|
};
|
|
14511
|
-
|
|
14512
|
-
|
|
14513
|
-
|
|
14514
|
-
|
|
14562
|
+
}
|
|
14563
|
+
function mergeBlocks2(target, source) {
|
|
14564
|
+
const targetMap = new Map(target.map((b) => [b.name, b]));
|
|
14565
|
+
const result = [];
|
|
14566
|
+
const seen = /* @__PURE__ */ new Set();
|
|
14567
|
+
for (const tb of target) {
|
|
14568
|
+
const sb = source.find((b) => b.name === tb.name);
|
|
14569
|
+
if (sb) {
|
|
14570
|
+
result.push(mergeBlock2(sb, tb));
|
|
14571
|
+
seen.add(tb.name);
|
|
14572
|
+
} else {
|
|
14573
|
+
result.push(deepClone(tb));
|
|
14574
|
+
}
|
|
14575
|
+
}
|
|
14576
|
+
for (const sb of source) {
|
|
14577
|
+
if (!seen.has(sb.name) && !targetMap.has(sb.name)) {
|
|
14578
|
+
result.push(deepClone(sb));
|
|
14579
|
+
}
|
|
14580
|
+
}
|
|
14581
|
+
return result;
|
|
14582
|
+
}
|
|
14583
|
+
function mergeBlock2(source, target) {
|
|
14515
14584
|
return {
|
|
14516
14585
|
...target,
|
|
14517
|
-
|
|
14586
|
+
content: mergeBlockContent2(source.content, target.content)
|
|
14518
14587
|
};
|
|
14519
14588
|
}
|
|
14520
|
-
function
|
|
14521
|
-
if (
|
|
14522
|
-
|
|
14589
|
+
function mergeBlockContent2(source, target) {
|
|
14590
|
+
if (source.type === target.type) {
|
|
14591
|
+
switch (target.type) {
|
|
14592
|
+
case "TextContent":
|
|
14593
|
+
return mergeTextContent3(source, target);
|
|
14594
|
+
case "ObjectContent":
|
|
14595
|
+
return mergeObjectContent2(source, target);
|
|
14596
|
+
case "ArrayContent":
|
|
14597
|
+
return mergeArrayContent2(source, target);
|
|
14598
|
+
case "MixedContent":
|
|
14599
|
+
return mergeMixedContent2(source, target);
|
|
14600
|
+
}
|
|
14601
|
+
}
|
|
14602
|
+
if (source.type === "MixedContent" && target.type === "TextContent") {
|
|
14603
|
+
return {
|
|
14604
|
+
...source,
|
|
14605
|
+
text: source.text ? mergeTextContent3(source.text, target) : deepClone(target)
|
|
14606
|
+
};
|
|
14607
|
+
}
|
|
14608
|
+
if (source.type === "TextContent" && target.type === "MixedContent") {
|
|
14609
|
+
return {
|
|
14610
|
+
...target,
|
|
14611
|
+
text: target.text ? mergeTextContent3(source, target.text) : deepClone(source)
|
|
14612
|
+
};
|
|
14613
|
+
}
|
|
14614
|
+
if (source.type === "MixedContent" && target.type === "ObjectContent") {
|
|
14615
|
+
return {
|
|
14616
|
+
...source,
|
|
14617
|
+
properties: mergeProperties2(source.properties, target.properties)
|
|
14618
|
+
};
|
|
14619
|
+
}
|
|
14620
|
+
if (source.type === "ObjectContent" && target.type === "MixedContent") {
|
|
14621
|
+
return {
|
|
14622
|
+
...target,
|
|
14623
|
+
properties: mergeProperties2(source.properties, target.properties)
|
|
14624
|
+
};
|
|
14625
|
+
}
|
|
14626
|
+
return deepClone(target);
|
|
14627
|
+
}
|
|
14628
|
+
function mergeTextContent3(source, target) {
|
|
14629
|
+
const sourceVal = source.value.trim();
|
|
14630
|
+
const targetVal = target.value.trim();
|
|
14631
|
+
if (sourceVal === targetVal) {
|
|
14632
|
+
return { ...target, value: targetVal };
|
|
14633
|
+
}
|
|
14634
|
+
if (targetVal.includes(sourceVal)) {
|
|
14635
|
+
return { ...target, value: targetVal };
|
|
14636
|
+
}
|
|
14637
|
+
if (sourceVal.includes(targetVal)) {
|
|
14638
|
+
return { ...target, value: sourceVal };
|
|
14639
|
+
}
|
|
14640
|
+
return {
|
|
14641
|
+
...target,
|
|
14642
|
+
value: `${sourceVal}
|
|
14643
|
+
|
|
14644
|
+
${targetVal}`
|
|
14645
|
+
};
|
|
14646
|
+
}
|
|
14647
|
+
function mergeObjectContent2(source, target) {
|
|
14648
|
+
return {
|
|
14649
|
+
...target,
|
|
14650
|
+
properties: mergeProperties2(source.properties, target.properties)
|
|
14651
|
+
};
|
|
14652
|
+
}
|
|
14653
|
+
function mergeProperties2(source, target) {
|
|
14654
|
+
const result = { ...source };
|
|
14655
|
+
for (const [key, targetVal] of Object.entries(target)) {
|
|
14656
|
+
const sourceVal = result[key];
|
|
14657
|
+
if (sourceVal === void 0) {
|
|
14658
|
+
result[key] = deepCloneValue2(targetVal);
|
|
14659
|
+
} else if (Array.isArray(targetVal) && Array.isArray(sourceVal)) {
|
|
14660
|
+
result[key] = uniqueConcat2(sourceVal, targetVal);
|
|
14661
|
+
} else if (isTextContent(targetVal) && isTextContent(sourceVal)) {
|
|
14662
|
+
} else if (isTextContent(sourceVal) || isTextContent(targetVal)) {
|
|
14663
|
+
} else if (isPlainObject3(targetVal) && isPlainObject3(sourceVal)) {
|
|
14664
|
+
result[key] = mergeProperties2(
|
|
14665
|
+
sourceVal,
|
|
14666
|
+
targetVal
|
|
14667
|
+
);
|
|
14668
|
+
} else if (typeof sourceVal === typeof targetVal) {
|
|
14669
|
+
} else {
|
|
14670
|
+
result[key] = deepCloneValue2(targetVal);
|
|
14671
|
+
}
|
|
14672
|
+
}
|
|
14673
|
+
return result;
|
|
14674
|
+
}
|
|
14675
|
+
function mergeArrayContent2(source, target) {
|
|
14676
|
+
return {
|
|
14677
|
+
...target,
|
|
14678
|
+
elements: uniqueConcat2(source.elements, target.elements)
|
|
14679
|
+
};
|
|
14680
|
+
}
|
|
14681
|
+
function mergeMixedContent2(source, target) {
|
|
14682
|
+
return {
|
|
14683
|
+
...target,
|
|
14684
|
+
text: source.text && target.text ? mergeTextContent3(source.text, target.text) : target.text ?? source.text,
|
|
14685
|
+
properties: mergeProperties2(source.properties, target.properties)
|
|
14686
|
+
};
|
|
14687
|
+
}
|
|
14688
|
+
function uniqueConcat2(source, target) {
|
|
14689
|
+
const seen = /* @__PURE__ */ new Set();
|
|
14690
|
+
const result = [];
|
|
14691
|
+
for (const item of [...source, ...target]) {
|
|
14692
|
+
const key = typeof item === "object" && item !== null ? JSON.stringify(item) : String(item);
|
|
14693
|
+
if (!seen.has(key)) {
|
|
14694
|
+
seen.add(key);
|
|
14695
|
+
result.push(deepCloneValue2(item));
|
|
14696
|
+
}
|
|
14523
14697
|
}
|
|
14524
|
-
|
|
14525
|
-
|
|
14698
|
+
return result;
|
|
14699
|
+
}
|
|
14700
|
+
function isPlainObject3(val) {
|
|
14701
|
+
return typeof val === "object" && val !== null && !Array.isArray(val) && Object.getPrototypeOf(val) === Object.prototype;
|
|
14702
|
+
}
|
|
14703
|
+
function deepCloneValue2(value) {
|
|
14704
|
+
if (value === null || typeof value !== "object") {
|
|
14705
|
+
return value;
|
|
14706
|
+
}
|
|
14707
|
+
if (Array.isArray(value)) {
|
|
14708
|
+
return value.map(deepCloneValue2);
|
|
14709
|
+
}
|
|
14710
|
+
const result = {};
|
|
14711
|
+
for (const [key, val] of Object.entries(value)) {
|
|
14712
|
+
result[key] = deepCloneValue2(val);
|
|
14713
|
+
}
|
|
14714
|
+
return result;
|
|
14526
14715
|
}
|
|
14527
14716
|
|
|
14528
14717
|
// packages/resolver/src/extensions.ts
|
|
@@ -14706,7 +14895,7 @@ function mergeValue2(existing, extContent) {
|
|
|
14706
14895
|
return extractValue(extContent);
|
|
14707
14896
|
}
|
|
14708
14897
|
if (Array.isArray(existing) && extContent.type === "ArrayContent") {
|
|
14709
|
-
return
|
|
14898
|
+
return uniqueConcat3(existing, extContent.elements);
|
|
14710
14899
|
}
|
|
14711
14900
|
if (typeof existing === "object" && existing !== null && !Array.isArray(existing) && extContent.type === "ObjectContent") {
|
|
14712
14901
|
const merged = deepMerge(
|
|
@@ -14742,13 +14931,13 @@ ${ext.value}`
|
|
|
14742
14931
|
case "ArrayContent":
|
|
14743
14932
|
return {
|
|
14744
14933
|
...ext,
|
|
14745
|
-
elements:
|
|
14934
|
+
elements: uniqueConcat3(target.elements, ext.elements)
|
|
14746
14935
|
};
|
|
14747
14936
|
case "MixedContent":
|
|
14748
|
-
return
|
|
14937
|
+
return mergeMixedContent3(target, ext);
|
|
14749
14938
|
}
|
|
14750
14939
|
}
|
|
14751
|
-
function
|
|
14940
|
+
function mergeMixedContent3(target, ext) {
|
|
14752
14941
|
const mergedText = target.text && ext.text ? {
|
|
14753
14942
|
...ext.text,
|
|
14754
14943
|
value: `${target.text.value}
|
|
@@ -14799,7 +14988,7 @@ ${ext.value}` } : ext
|
|
|
14799
14988
|
}
|
|
14800
14989
|
return deepClone(ext);
|
|
14801
14990
|
}
|
|
14802
|
-
function
|
|
14991
|
+
function uniqueConcat3(parent, child) {
|
|
14803
14992
|
const seen = /* @__PURE__ */ new Set();
|
|
14804
14993
|
const result = [];
|
|
14805
14994
|
for (const item of [...parent, ...child]) {
|
|
@@ -14956,6 +15145,874 @@ var Resolver = class {
|
|
|
14956
15145
|
}
|
|
14957
15146
|
};
|
|
14958
15147
|
|
|
15148
|
+
// packages/resolver/src/registry.ts
|
|
15149
|
+
import { existsSync as existsSync3, promises as fs } from "fs";
|
|
15150
|
+
import { join as join3 } from "path";
|
|
15151
|
+
var FileSystemRegistry = class {
|
|
15152
|
+
rootPath;
|
|
15153
|
+
constructor(options) {
|
|
15154
|
+
this.rootPath = options.rootPath;
|
|
15155
|
+
}
|
|
15156
|
+
/**
|
|
15157
|
+
* Resolve a path relative to the registry root.
|
|
15158
|
+
*/
|
|
15159
|
+
resolvePath(path) {
|
|
15160
|
+
return join3(this.rootPath, path);
|
|
15161
|
+
}
|
|
15162
|
+
async fetch(path) {
|
|
15163
|
+
const fullPath = this.resolvePath(path);
|
|
15164
|
+
try {
|
|
15165
|
+
return await fs.readFile(fullPath, "utf-8");
|
|
15166
|
+
} catch (err) {
|
|
15167
|
+
const error = err;
|
|
15168
|
+
if (error.code === "ENOENT") {
|
|
15169
|
+
throw new FileNotFoundError(fullPath);
|
|
15170
|
+
}
|
|
15171
|
+
throw err;
|
|
15172
|
+
}
|
|
15173
|
+
}
|
|
15174
|
+
async exists(path) {
|
|
15175
|
+
const fullPath = this.resolvePath(path);
|
|
15176
|
+
return existsSync3(fullPath);
|
|
15177
|
+
}
|
|
15178
|
+
async list(path) {
|
|
15179
|
+
const fullPath = this.resolvePath(path);
|
|
15180
|
+
try {
|
|
15181
|
+
const entries = await fs.readdir(fullPath, { withFileTypes: true });
|
|
15182
|
+
return entries.map((e) => e.isDirectory() ? `${e.name}/` : e.name);
|
|
15183
|
+
} catch {
|
|
15184
|
+
return [];
|
|
15185
|
+
}
|
|
15186
|
+
}
|
|
15187
|
+
};
|
|
15188
|
+
var HttpRegistry = class {
|
|
15189
|
+
baseUrl;
|
|
15190
|
+
auth;
|
|
15191
|
+
cacheEnabled;
|
|
15192
|
+
cacheTtl;
|
|
15193
|
+
maxRetries;
|
|
15194
|
+
initialDelay;
|
|
15195
|
+
timeout;
|
|
15196
|
+
cache = /* @__PURE__ */ new Map();
|
|
15197
|
+
constructor(options) {
|
|
15198
|
+
this.baseUrl = options.baseUrl.replace(/\/$/, "");
|
|
15199
|
+
this.auth = options.auth;
|
|
15200
|
+
this.cacheEnabled = options.cache?.enabled ?? false;
|
|
15201
|
+
this.cacheTtl = options.cache?.ttl ?? 3e5;
|
|
15202
|
+
this.maxRetries = options.retry?.maxRetries ?? 3;
|
|
15203
|
+
this.initialDelay = options.retry?.initialDelay ?? 1e3;
|
|
15204
|
+
this.timeout = options.timeout ?? 3e4;
|
|
15205
|
+
}
|
|
15206
|
+
/**
|
|
15207
|
+
* Build full URL for a path.
|
|
15208
|
+
*/
|
|
15209
|
+
buildUrl(path) {
|
|
15210
|
+
const normalizedPath = path.startsWith("/") ? path : `/${path}`;
|
|
15211
|
+
return `${this.baseUrl}${normalizedPath}`;
|
|
15212
|
+
}
|
|
15213
|
+
/**
|
|
15214
|
+
* Build headers for request.
|
|
15215
|
+
*/
|
|
15216
|
+
buildHeaders() {
|
|
15217
|
+
const headers = {};
|
|
15218
|
+
if (this.auth) {
|
|
15219
|
+
if (this.auth.type === "bearer") {
|
|
15220
|
+
headers["Authorization"] = `Bearer ${this.auth.token}`;
|
|
15221
|
+
} else if (this.auth.type === "basic") {
|
|
15222
|
+
const encoded = Buffer.from(this.auth.token).toString("base64");
|
|
15223
|
+
headers["Authorization"] = `Basic ${encoded}`;
|
|
15224
|
+
}
|
|
15225
|
+
}
|
|
15226
|
+
return headers;
|
|
15227
|
+
}
|
|
15228
|
+
/**
|
|
15229
|
+
* Sleep for specified milliseconds.
|
|
15230
|
+
*/
|
|
15231
|
+
sleep(ms) {
|
|
15232
|
+
return new Promise((resolve7) => setTimeout(resolve7, ms));
|
|
15233
|
+
}
|
|
15234
|
+
/**
|
|
15235
|
+
* Fetch with timeout.
|
|
15236
|
+
*/
|
|
15237
|
+
async fetchWithTimeout(url, options) {
|
|
15238
|
+
const controller = new AbortController();
|
|
15239
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
15240
|
+
try {
|
|
15241
|
+
return await fetch(url, {
|
|
15242
|
+
...options,
|
|
15243
|
+
signal: controller.signal
|
|
15244
|
+
});
|
|
15245
|
+
} finally {
|
|
15246
|
+
clearTimeout(timeoutId);
|
|
15247
|
+
}
|
|
15248
|
+
}
|
|
15249
|
+
/**
|
|
15250
|
+
* Check if an error should not be retried.
|
|
15251
|
+
*/
|
|
15252
|
+
isNonRetryableError(error) {
|
|
15253
|
+
return error.isClientError === true || error.name === "AbortError";
|
|
15254
|
+
}
|
|
15255
|
+
/**
|
|
15256
|
+
* Handle HTTP response, throwing for errors.
|
|
15257
|
+
*/
|
|
15258
|
+
handleResponse(response) {
|
|
15259
|
+
if (response.ok) {
|
|
15260
|
+
return response;
|
|
15261
|
+
}
|
|
15262
|
+
const errorMessage = `HTTP ${response.status}: ${response.statusText}`;
|
|
15263
|
+
const error = new Error(errorMessage);
|
|
15264
|
+
if (response.status >= 400 && response.status < 500) {
|
|
15265
|
+
error.isClientError = true;
|
|
15266
|
+
}
|
|
15267
|
+
throw error;
|
|
15268
|
+
}
|
|
15269
|
+
/**
|
|
15270
|
+
* Fetch with retry logic.
|
|
15271
|
+
*/
|
|
15272
|
+
async fetchWithRetry(url) {
|
|
15273
|
+
let lastError;
|
|
15274
|
+
let delay = this.initialDelay;
|
|
15275
|
+
for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
|
|
15276
|
+
try {
|
|
15277
|
+
const response = await this.fetchWithTimeout(url, {
|
|
15278
|
+
method: "GET",
|
|
15279
|
+
headers: this.buildHeaders()
|
|
15280
|
+
});
|
|
15281
|
+
return this.handleResponse(response);
|
|
15282
|
+
} catch (err) {
|
|
15283
|
+
const error = err;
|
|
15284
|
+
if (this.isNonRetryableError(error)) {
|
|
15285
|
+
if (error.name === "AbortError") {
|
|
15286
|
+
throw new Error(`Request timeout after ${this.timeout}ms`);
|
|
15287
|
+
}
|
|
15288
|
+
throw error;
|
|
15289
|
+
}
|
|
15290
|
+
lastError = error;
|
|
15291
|
+
}
|
|
15292
|
+
if (attempt < this.maxRetries) {
|
|
15293
|
+
await this.sleep(delay);
|
|
15294
|
+
delay *= 2;
|
|
15295
|
+
}
|
|
15296
|
+
}
|
|
15297
|
+
throw lastError ?? new Error("Unknown fetch error");
|
|
15298
|
+
}
|
|
15299
|
+
/**
|
|
15300
|
+
* Check if cached entry is still valid.
|
|
15301
|
+
*/
|
|
15302
|
+
isCacheValid(entry) {
|
|
15303
|
+
return Date.now() - entry.timestamp < this.cacheTtl;
|
|
15304
|
+
}
|
|
15305
|
+
async fetch(path) {
|
|
15306
|
+
if (this.cacheEnabled) {
|
|
15307
|
+
const cached = this.cache.get(path);
|
|
15308
|
+
if (cached && this.isCacheValid(cached)) {
|
|
15309
|
+
return cached.content;
|
|
15310
|
+
}
|
|
15311
|
+
}
|
|
15312
|
+
const url = this.buildUrl(path);
|
|
15313
|
+
try {
|
|
15314
|
+
const response = await this.fetchWithRetry(url);
|
|
15315
|
+
const content = await response.text();
|
|
15316
|
+
if (this.cacheEnabled) {
|
|
15317
|
+
this.cache.set(path, {
|
|
15318
|
+
content,
|
|
15319
|
+
timestamp: Date.now()
|
|
15320
|
+
});
|
|
15321
|
+
}
|
|
15322
|
+
return content;
|
|
15323
|
+
} catch (err) {
|
|
15324
|
+
const error = err;
|
|
15325
|
+
if (error.message.includes("404")) {
|
|
15326
|
+
throw new FileNotFoundError(path);
|
|
15327
|
+
}
|
|
15328
|
+
throw new Error(`Failed to fetch ${path}: ${error.message}`);
|
|
15329
|
+
}
|
|
15330
|
+
}
|
|
15331
|
+
async exists(path) {
|
|
15332
|
+
try {
|
|
15333
|
+
const url = this.buildUrl(path);
|
|
15334
|
+
const response = await this.fetchWithTimeout(url, {
|
|
15335
|
+
method: "HEAD",
|
|
15336
|
+
headers: this.buildHeaders()
|
|
15337
|
+
});
|
|
15338
|
+
return response.ok;
|
|
15339
|
+
} catch {
|
|
15340
|
+
return false;
|
|
15341
|
+
}
|
|
15342
|
+
}
|
|
15343
|
+
async list(path) {
|
|
15344
|
+
const url = this.buildUrl(`${path}/.index.json`);
|
|
15345
|
+
try {
|
|
15346
|
+
const response = await this.fetchWithTimeout(url, {
|
|
15347
|
+
method: "GET",
|
|
15348
|
+
headers: this.buildHeaders()
|
|
15349
|
+
});
|
|
15350
|
+
if (!response.ok) {
|
|
15351
|
+
return [];
|
|
15352
|
+
}
|
|
15353
|
+
const content = await response.text();
|
|
15354
|
+
return JSON.parse(content);
|
|
15355
|
+
} catch {
|
|
15356
|
+
return [];
|
|
15357
|
+
}
|
|
15358
|
+
}
|
|
15359
|
+
/**
|
|
15360
|
+
* Clear the cache.
|
|
15361
|
+
*/
|
|
15362
|
+
clearCache() {
|
|
15363
|
+
this.cache.clear();
|
|
15364
|
+
}
|
|
15365
|
+
};
|
|
15366
|
+
function createFileSystemRegistry(rootPath) {
|
|
15367
|
+
return new FileSystemRegistry({ rootPath });
|
|
15368
|
+
}
|
|
15369
|
+
function createHttpRegistry(options) {
|
|
15370
|
+
return new HttpRegistry(options);
|
|
15371
|
+
}
|
|
15372
|
+
|
|
15373
|
+
// packages/resolver/src/git-registry.ts
|
|
15374
|
+
import { existsSync as existsSync5, promises as fs3 } from "fs";
|
|
15375
|
+
import { join as join5 } from "path";
|
|
15376
|
+
import { simpleGit } from "simple-git";
|
|
15377
|
+
|
|
15378
|
+
// packages/resolver/src/git-cache-manager.ts
|
|
15379
|
+
import { existsSync as existsSync4, promises as fs2 } from "fs";
|
|
15380
|
+
import { join as join4 } from "path";
|
|
15381
|
+
import { homedir } from "os";
|
|
15382
|
+
|
|
15383
|
+
// packages/resolver/src/git-url-utils.ts
|
|
15384
|
+
import { createHash } from "crypto";
|
|
15385
|
+
var GIT_URL_PATTERNS = {
|
|
15386
|
+
// HTTPS: https://github.com/org/repo.git or https://github.com/org/repo
|
|
15387
|
+
https: /^https?:\/\/(?<host>[^/:]+)(?::(?<port>\d+))?\/(?<owner>[^/]+)\/(?<repo>[^/]+?)(?:\.git)?$/,
|
|
15388
|
+
// SSH: git@github.com:org/repo.git or git@github.com:org/repo
|
|
15389
|
+
ssh: /^git@(?<host>[^:]+):(?<owner>[^/]+)\/(?<repo>[^/]+?)(?:\.git)?$/,
|
|
15390
|
+
// Git protocol: git://github.com/org/repo.git
|
|
15391
|
+
git: /^git:\/\/(?<host>[^/:]+)(?::(?<port>\d+))?\/(?<owner>[^/]+)\/(?<repo>[^/]+?)(?:\.git)?$/
|
|
15392
|
+
};
|
|
15393
|
+
function parseGitUrl(url) {
|
|
15394
|
+
if (url.startsWith("git@")) {
|
|
15395
|
+
const match = GIT_URL_PATTERNS.ssh.exec(url);
|
|
15396
|
+
if (match?.groups) {
|
|
15397
|
+
const { host, owner, repo } = match.groups;
|
|
15398
|
+
if (host && owner && repo) {
|
|
15399
|
+
return {
|
|
15400
|
+
original: url,
|
|
15401
|
+
protocol: "ssh",
|
|
15402
|
+
host,
|
|
15403
|
+
owner,
|
|
15404
|
+
repo
|
|
15405
|
+
};
|
|
15406
|
+
}
|
|
15407
|
+
}
|
|
15408
|
+
return null;
|
|
15409
|
+
}
|
|
15410
|
+
if (url.startsWith("git://")) {
|
|
15411
|
+
const match = GIT_URL_PATTERNS.git.exec(url);
|
|
15412
|
+
if (match?.groups) {
|
|
15413
|
+
const { host, owner, repo, port } = match.groups;
|
|
15414
|
+
if (host && owner && repo) {
|
|
15415
|
+
return {
|
|
15416
|
+
original: url,
|
|
15417
|
+
protocol: "git",
|
|
15418
|
+
host,
|
|
15419
|
+
owner,
|
|
15420
|
+
repo,
|
|
15421
|
+
port: port ? parseInt(port, 10) : void 0
|
|
15422
|
+
};
|
|
15423
|
+
}
|
|
15424
|
+
}
|
|
15425
|
+
return null;
|
|
15426
|
+
}
|
|
15427
|
+
if (url.startsWith("https://") || url.startsWith("http://")) {
|
|
15428
|
+
const match = GIT_URL_PATTERNS.https.exec(url);
|
|
15429
|
+
if (match?.groups) {
|
|
15430
|
+
const { host, owner, repo, port } = match.groups;
|
|
15431
|
+
if (host && owner && repo) {
|
|
15432
|
+
return {
|
|
15433
|
+
original: url,
|
|
15434
|
+
protocol: "https",
|
|
15435
|
+
host,
|
|
15436
|
+
owner,
|
|
15437
|
+
repo,
|
|
15438
|
+
port: port ? parseInt(port, 10) : void 0
|
|
15439
|
+
};
|
|
15440
|
+
}
|
|
15441
|
+
}
|
|
15442
|
+
}
|
|
15443
|
+
return null;
|
|
15444
|
+
}
|
|
15445
|
+
function normalizeGitUrl(url) {
|
|
15446
|
+
const parsed = parseGitUrl(url);
|
|
15447
|
+
if (!parsed) {
|
|
15448
|
+
return url;
|
|
15449
|
+
}
|
|
15450
|
+
const portPart = parsed.port ? `:${parsed.port}` : "";
|
|
15451
|
+
return `https://${parsed.host}${portPart}/${parsed.owner}/${parsed.repo}.git`;
|
|
15452
|
+
}
|
|
15453
|
+
function buildAuthenticatedUrl(url, token) {
|
|
15454
|
+
const parsed = parseGitUrl(url);
|
|
15455
|
+
if (!parsed) {
|
|
15456
|
+
return url;
|
|
15457
|
+
}
|
|
15458
|
+
if (parsed.protocol === "ssh") {
|
|
15459
|
+
return url;
|
|
15460
|
+
}
|
|
15461
|
+
const portPart = parsed.port ? `:${parsed.port}` : "";
|
|
15462
|
+
return `https://${token}@${parsed.host}${portPart}/${parsed.owner}/${parsed.repo}.git`;
|
|
15463
|
+
}
|
|
15464
|
+
function getCacheKey(url, ref) {
|
|
15465
|
+
const parsed = parseGitUrl(url);
|
|
15466
|
+
if (!parsed) {
|
|
15467
|
+
const hash2 = createHash("sha256").update(url).digest("hex").slice(0, 8);
|
|
15468
|
+
return `unknown-${hash2}`;
|
|
15469
|
+
}
|
|
15470
|
+
const components = [parsed.host, parsed.owner, parsed.repo];
|
|
15471
|
+
if (ref) {
|
|
15472
|
+
components.push(ref);
|
|
15473
|
+
}
|
|
15474
|
+
const hash = createHash("sha256").update(components.join("/")).digest("hex").slice(0, 8);
|
|
15475
|
+
return `${parsed.host}-${parsed.owner}-${parsed.repo}${ref ? `-${ref}` : ""}-${hash}`;
|
|
15476
|
+
}
|
|
15477
|
+
function parseVersionedPath(path) {
|
|
15478
|
+
const versionMatch = /@(v?\d+(?:\.\d+)*(?:-[\w.]+)?)$/.exec(path);
|
|
15479
|
+
if (versionMatch) {
|
|
15480
|
+
const version = versionMatch[1];
|
|
15481
|
+
const basePath = path.slice(0, -versionMatch[0].length);
|
|
15482
|
+
return { path: basePath, version };
|
|
15483
|
+
}
|
|
15484
|
+
return { path, version: void 0 };
|
|
15485
|
+
}
|
|
15486
|
+
|
|
15487
|
+
// packages/resolver/src/git-cache-manager.ts
|
|
15488
|
+
var DEFAULT_CACHE_DIR = join4(homedir(), ".promptscript", ".cache", "git");
|
|
15489
|
+
var DEFAULT_TTL = 36e5;
|
|
15490
|
+
var METADATA_FILE = ".prs-cache-meta.json";
|
|
15491
|
+
var GitCacheManager = class {
|
|
15492
|
+
cacheDir;
|
|
15493
|
+
ttl;
|
|
15494
|
+
constructor(options = {}) {
|
|
15495
|
+
this.cacheDir = options.cacheDir ?? DEFAULT_CACHE_DIR;
|
|
15496
|
+
this.ttl = options.ttl ?? DEFAULT_TTL;
|
|
15497
|
+
}
|
|
15498
|
+
/**
|
|
15499
|
+
* Get the cache directory for a given URL and ref.
|
|
15500
|
+
*
|
|
15501
|
+
* @param url - Git repository URL
|
|
15502
|
+
* @param ref - Git ref (branch/tag/commit)
|
|
15503
|
+
* @returns Path to the cache directory
|
|
15504
|
+
*/
|
|
15505
|
+
getCachePath(url, ref) {
|
|
15506
|
+
const cacheKey = getCacheKey(url, ref);
|
|
15507
|
+
return join4(this.cacheDir, cacheKey);
|
|
15508
|
+
}
|
|
15509
|
+
/**
|
|
15510
|
+
* Check if a cache entry exists and is not stale.
|
|
15511
|
+
*
|
|
15512
|
+
* @param url - Git repository URL
|
|
15513
|
+
* @param ref - Git ref (branch/tag/commit)
|
|
15514
|
+
* @returns True if cache is valid (exists and not stale)
|
|
15515
|
+
*/
|
|
15516
|
+
async isValid(url, ref) {
|
|
15517
|
+
const entry = await this.get(url, ref);
|
|
15518
|
+
return entry !== null && !entry.isStale;
|
|
15519
|
+
}
|
|
15520
|
+
/**
|
|
15521
|
+
* Get a cache entry if it exists.
|
|
15522
|
+
*
|
|
15523
|
+
* @param url - Git repository URL
|
|
15524
|
+
* @param ref - Git ref (branch/tag/commit)
|
|
15525
|
+
* @returns Cache entry or null if not found
|
|
15526
|
+
*/
|
|
15527
|
+
async get(url, ref) {
|
|
15528
|
+
const cachePath = this.getCachePath(url, ref);
|
|
15529
|
+
if (!existsSync4(cachePath)) {
|
|
15530
|
+
return null;
|
|
15531
|
+
}
|
|
15532
|
+
const metadata = await this.readMetadata(cachePath);
|
|
15533
|
+
if (!metadata) {
|
|
15534
|
+
return null;
|
|
15535
|
+
}
|
|
15536
|
+
const isStale = Date.now() - metadata.lastUpdated > this.ttl;
|
|
15537
|
+
return {
|
|
15538
|
+
path: cachePath,
|
|
15539
|
+
metadata,
|
|
15540
|
+
isStale
|
|
15541
|
+
};
|
|
15542
|
+
}
|
|
15543
|
+
/**
|
|
15544
|
+
* Create or update a cache entry.
|
|
15545
|
+
*
|
|
15546
|
+
* @param url - Git repository URL
|
|
15547
|
+
* @param ref - Git ref (branch/tag/commit)
|
|
15548
|
+
* @param commitHash - Current commit hash
|
|
15549
|
+
* @returns Path to the cache directory
|
|
15550
|
+
*/
|
|
15551
|
+
async set(url, ref, commitHash) {
|
|
15552
|
+
const cachePath = this.getCachePath(url, ref);
|
|
15553
|
+
await fs2.mkdir(cachePath, { recursive: true });
|
|
15554
|
+
const existingMetadata = await this.readMetadata(cachePath);
|
|
15555
|
+
const now = Date.now();
|
|
15556
|
+
const metadata = {
|
|
15557
|
+
url,
|
|
15558
|
+
ref,
|
|
15559
|
+
commitHash,
|
|
15560
|
+
lastUpdated: now,
|
|
15561
|
+
createdAt: existingMetadata?.createdAt ?? now,
|
|
15562
|
+
version: 1
|
|
15563
|
+
};
|
|
15564
|
+
await this.writeMetadata(cachePath, metadata);
|
|
15565
|
+
return cachePath;
|
|
15566
|
+
}
|
|
15567
|
+
/**
|
|
15568
|
+
* Update the lastUpdated timestamp for an existing cache entry.
|
|
15569
|
+
*
|
|
15570
|
+
* @param url - Git repository URL
|
|
15571
|
+
* @param ref - Git ref (branch/tag/commit)
|
|
15572
|
+
* @param commitHash - Optional new commit hash (if fetch resulted in update)
|
|
15573
|
+
*/
|
|
15574
|
+
async touch(url, ref, commitHash) {
|
|
15575
|
+
const cachePath = this.getCachePath(url, ref);
|
|
15576
|
+
const metadata = await this.readMetadata(cachePath);
|
|
15577
|
+
if (!metadata) {
|
|
15578
|
+
throw new Error(`Cache entry not found: ${cachePath}`);
|
|
15579
|
+
}
|
|
15580
|
+
metadata.lastUpdated = Date.now();
|
|
15581
|
+
if (commitHash) {
|
|
15582
|
+
metadata.commitHash = commitHash;
|
|
15583
|
+
}
|
|
15584
|
+
await this.writeMetadata(cachePath, metadata);
|
|
15585
|
+
}
|
|
15586
|
+
/**
|
|
15587
|
+
* Remove a cache entry.
|
|
15588
|
+
*
|
|
15589
|
+
* @param url - Git repository URL
|
|
15590
|
+
* @param ref - Git ref (branch/tag/commit)
|
|
15591
|
+
*/
|
|
15592
|
+
async remove(url, ref) {
|
|
15593
|
+
const cachePath = this.getCachePath(url, ref);
|
|
15594
|
+
if (existsSync4(cachePath)) {
|
|
15595
|
+
await fs2.rm(cachePath, { recursive: true, force: true });
|
|
15596
|
+
}
|
|
15597
|
+
}
|
|
15598
|
+
/**
|
|
15599
|
+
* List all cache entries.
|
|
15600
|
+
*
|
|
15601
|
+
* @returns Array of cache entries
|
|
15602
|
+
*/
|
|
15603
|
+
async list() {
|
|
15604
|
+
if (!existsSync4(this.cacheDir)) {
|
|
15605
|
+
return [];
|
|
15606
|
+
}
|
|
15607
|
+
const entries = [];
|
|
15608
|
+
const dirs = await fs2.readdir(this.cacheDir, { withFileTypes: true });
|
|
15609
|
+
for (const dir of dirs) {
|
|
15610
|
+
if (!dir.isDirectory()) {
|
|
15611
|
+
continue;
|
|
15612
|
+
}
|
|
15613
|
+
const cachePath = join4(this.cacheDir, dir.name);
|
|
15614
|
+
const metadata = await this.readMetadata(cachePath);
|
|
15615
|
+
if (metadata) {
|
|
15616
|
+
const isStale = Date.now() - metadata.lastUpdated > this.ttl;
|
|
15617
|
+
entries.push({ path: cachePath, metadata, isStale });
|
|
15618
|
+
}
|
|
15619
|
+
}
|
|
15620
|
+
return entries;
|
|
15621
|
+
}
|
|
15622
|
+
/**
|
|
15623
|
+
* Remove all stale cache entries.
|
|
15624
|
+
*
|
|
15625
|
+
* @returns Number of entries removed
|
|
15626
|
+
*/
|
|
15627
|
+
async cleanupStale() {
|
|
15628
|
+
const entries = await this.list();
|
|
15629
|
+
const staleEntries = entries.filter((e) => e.isStale);
|
|
15630
|
+
for (const entry of staleEntries) {
|
|
15631
|
+
await fs2.rm(entry.path, { recursive: true, force: true });
|
|
15632
|
+
}
|
|
15633
|
+
return staleEntries.length;
|
|
15634
|
+
}
|
|
15635
|
+
/**
|
|
15636
|
+
* Remove all cache entries.
|
|
15637
|
+
*/
|
|
15638
|
+
async clear() {
|
|
15639
|
+
if (existsSync4(this.cacheDir)) {
|
|
15640
|
+
await fs2.rm(this.cacheDir, { recursive: true, force: true });
|
|
15641
|
+
}
|
|
15642
|
+
}
|
|
15643
|
+
/**
|
|
15644
|
+
* Get the total size of the cache in bytes.
|
|
15645
|
+
*
|
|
15646
|
+
* @returns Total cache size in bytes
|
|
15647
|
+
*/
|
|
15648
|
+
async getSize() {
|
|
15649
|
+
if (!existsSync4(this.cacheDir)) {
|
|
15650
|
+
return 0;
|
|
15651
|
+
}
|
|
15652
|
+
return this.calculateDirSize(this.cacheDir);
|
|
15653
|
+
}
|
|
15654
|
+
/**
|
|
15655
|
+
* Calculate the size of a directory recursively.
|
|
15656
|
+
*/
|
|
15657
|
+
async calculateDirSize(dirPath) {
|
|
15658
|
+
let size = 0;
|
|
15659
|
+
const entries = await fs2.readdir(dirPath, { withFileTypes: true });
|
|
15660
|
+
for (const entry of entries) {
|
|
15661
|
+
const fullPath = join4(dirPath, entry.name);
|
|
15662
|
+
if (entry.isDirectory()) {
|
|
15663
|
+
size += await this.calculateDirSize(fullPath);
|
|
15664
|
+
} else {
|
|
15665
|
+
const stat = await fs2.stat(fullPath);
|
|
15666
|
+
size += stat.size;
|
|
15667
|
+
}
|
|
15668
|
+
}
|
|
15669
|
+
return size;
|
|
15670
|
+
}
|
|
15671
|
+
/**
|
|
15672
|
+
* Read metadata from a cache directory.
|
|
15673
|
+
*/
|
|
15674
|
+
async readMetadata(cachePath) {
|
|
15675
|
+
const metadataPath = join4(cachePath, METADATA_FILE);
|
|
15676
|
+
if (!existsSync4(metadataPath)) {
|
|
15677
|
+
return null;
|
|
15678
|
+
}
|
|
15679
|
+
try {
|
|
15680
|
+
const content = await fs2.readFile(metadataPath, "utf-8");
|
|
15681
|
+
return JSON.parse(content);
|
|
15682
|
+
} catch {
|
|
15683
|
+
return null;
|
|
15684
|
+
}
|
|
15685
|
+
}
|
|
15686
|
+
/**
|
|
15687
|
+
* Write metadata to a cache directory.
|
|
15688
|
+
*/
|
|
15689
|
+
async writeMetadata(cachePath, metadata) {
|
|
15690
|
+
const metadataPath = join4(cachePath, METADATA_FILE);
|
|
15691
|
+
await fs2.writeFile(metadataPath, JSON.stringify(metadata, null, 2), "utf-8");
|
|
15692
|
+
}
|
|
15693
|
+
};
|
|
15694
|
+
|
|
15695
|
+
// packages/resolver/src/git-registry.ts
|
|
15696
|
+
var GitCloneError = class extends Error {
|
|
15697
|
+
constructor(message, url, cause) {
|
|
15698
|
+
super(message, { cause });
|
|
15699
|
+
this.url = url;
|
|
15700
|
+
this.name = "GitCloneError";
|
|
15701
|
+
this.cause = cause;
|
|
15702
|
+
}
|
|
15703
|
+
cause;
|
|
15704
|
+
};
|
|
15705
|
+
var GitAuthError = class extends Error {
|
|
15706
|
+
constructor(message, url, cause) {
|
|
15707
|
+
super(message, { cause });
|
|
15708
|
+
this.url = url;
|
|
15709
|
+
this.name = "GitAuthError";
|
|
15710
|
+
this.cause = cause;
|
|
15711
|
+
}
|
|
15712
|
+
cause;
|
|
15713
|
+
};
|
|
15714
|
+
var GitRefNotFoundError = class extends Error {
|
|
15715
|
+
constructor(ref, url) {
|
|
15716
|
+
super(`Git ref not found: ${ref} in ${url}`);
|
|
15717
|
+
this.ref = ref;
|
|
15718
|
+
this.url = url;
|
|
15719
|
+
this.name = "GitRefNotFoundError";
|
|
15720
|
+
}
|
|
15721
|
+
};
|
|
15722
|
+
var GitRegistry = class {
|
|
15723
|
+
url;
|
|
15724
|
+
originalUrl;
|
|
15725
|
+
defaultRef;
|
|
15726
|
+
subPath;
|
|
15727
|
+
auth;
|
|
15728
|
+
cacheEnabled;
|
|
15729
|
+
timeout;
|
|
15730
|
+
cacheManager;
|
|
15731
|
+
initialized = false;
|
|
15732
|
+
constructor(options) {
|
|
15733
|
+
this.originalUrl = options.url;
|
|
15734
|
+
this.url = normalizeGitUrl(options.url);
|
|
15735
|
+
this.defaultRef = options.ref ?? "main";
|
|
15736
|
+
this.subPath = options.path ?? "";
|
|
15737
|
+
this.auth = options.auth;
|
|
15738
|
+
this.cacheEnabled = options.cache?.enabled ?? true;
|
|
15739
|
+
this.timeout = options.timeout ?? 6e4;
|
|
15740
|
+
this.cacheManager = new GitCacheManager({
|
|
15741
|
+
cacheDir: options.cacheDir,
|
|
15742
|
+
ttl: options.cache?.ttl
|
|
15743
|
+
});
|
|
15744
|
+
}
|
|
15745
|
+
/**
|
|
15746
|
+
* Fetch the content of a file from the registry.
|
|
15747
|
+
*
|
|
15748
|
+
* @param path - Path to the file (may include version tag)
|
|
15749
|
+
* @returns File content as string
|
|
15750
|
+
* @throws FileNotFoundError if the file doesn't exist
|
|
15751
|
+
* @throws GitCloneError if cloning fails
|
|
15752
|
+
* @throws GitRefNotFoundError if the ref doesn't exist
|
|
15753
|
+
*/
|
|
15754
|
+
async fetch(path) {
|
|
15755
|
+
const { path: basePath, version } = parseVersionedPath(path);
|
|
15756
|
+
const ref = version ?? this.defaultRef;
|
|
15757
|
+
const repoPath = await this.ensureCloned(ref);
|
|
15758
|
+
const filePath = this.resolveFilePath(repoPath, basePath);
|
|
15759
|
+
if (!existsSync5(filePath)) {
|
|
15760
|
+
throw new FileNotFoundError(path);
|
|
15761
|
+
}
|
|
15762
|
+
return fs3.readFile(filePath, "utf-8");
|
|
15763
|
+
}
|
|
15764
|
+
/**
|
|
15765
|
+
* Check if a file exists in the registry.
|
|
15766
|
+
*
|
|
15767
|
+
* @param path - Path to check (may include version tag)
|
|
15768
|
+
* @returns True if the file exists
|
|
15769
|
+
*/
|
|
15770
|
+
async exists(path) {
|
|
15771
|
+
try {
|
|
15772
|
+
const { path: basePath, version } = parseVersionedPath(path);
|
|
15773
|
+
const ref = version ?? this.defaultRef;
|
|
15774
|
+
const repoPath = await this.ensureCloned(ref);
|
|
15775
|
+
const filePath = this.resolveFilePath(repoPath, basePath);
|
|
15776
|
+
return existsSync5(filePath);
|
|
15777
|
+
} catch {
|
|
15778
|
+
return false;
|
|
15779
|
+
}
|
|
15780
|
+
}
|
|
15781
|
+
/**
|
|
15782
|
+
* List files in a directory.
|
|
15783
|
+
*
|
|
15784
|
+
* @param path - Directory path (may include version tag)
|
|
15785
|
+
* @returns Array of file/directory names
|
|
15786
|
+
*/
|
|
15787
|
+
async list(path) {
|
|
15788
|
+
try {
|
|
15789
|
+
const { path: basePath, version } = parseVersionedPath(path);
|
|
15790
|
+
const ref = version ?? this.defaultRef;
|
|
15791
|
+
const repoPath = await this.ensureCloned(ref);
|
|
15792
|
+
const dirPath = this.resolveDirectoryPath(repoPath, basePath);
|
|
15793
|
+
if (!existsSync5(dirPath)) {
|
|
15794
|
+
return [];
|
|
15795
|
+
}
|
|
15796
|
+
const entries = await fs3.readdir(dirPath, { withFileTypes: true });
|
|
15797
|
+
return entries.map((e) => e.isDirectory() ? `${e.name}/` : e.name);
|
|
15798
|
+
} catch {
|
|
15799
|
+
return [];
|
|
15800
|
+
}
|
|
15801
|
+
}
|
|
15802
|
+
/**
|
|
15803
|
+
* Force refresh the cache for a specific ref.
|
|
15804
|
+
*
|
|
15805
|
+
* @param ref - Git ref to refresh (defaults to defaultRef)
|
|
15806
|
+
*/
|
|
15807
|
+
async refresh(ref) {
|
|
15808
|
+
const targetRef = ref ?? this.defaultRef;
|
|
15809
|
+
await this.cacheManager.remove(this.url, targetRef);
|
|
15810
|
+
await this.ensureCloned(targetRef);
|
|
15811
|
+
}
|
|
15812
|
+
/**
|
|
15813
|
+
* Get the current commit hash for a ref.
|
|
15814
|
+
*
|
|
15815
|
+
* @param ref - Git ref (defaults to defaultRef)
|
|
15816
|
+
* @returns Commit hash
|
|
15817
|
+
*/
|
|
15818
|
+
async getCommitHash(ref) {
|
|
15819
|
+
const targetRef = ref ?? this.defaultRef;
|
|
15820
|
+
const repoPath = await this.ensureCloned(targetRef);
|
|
15821
|
+
const git = this.createGit(repoPath);
|
|
15822
|
+
const result = await git.revparse(["HEAD"]);
|
|
15823
|
+
return result.trim();
|
|
15824
|
+
}
|
|
15825
|
+
/**
|
|
15826
|
+
* Ensure the repository is cloned and at the correct ref.
|
|
15827
|
+
*/
|
|
15828
|
+
async ensureCloned(ref) {
|
|
15829
|
+
if (this.cacheEnabled) {
|
|
15830
|
+
const entry = await this.cacheManager.get(this.url, ref);
|
|
15831
|
+
if (entry && !entry.isStale) {
|
|
15832
|
+
return entry.path;
|
|
15833
|
+
}
|
|
15834
|
+
if (entry) {
|
|
15835
|
+
try {
|
|
15836
|
+
await this.fetchUpdates(entry.path, ref);
|
|
15837
|
+
const commitHash2 = await this.getCurrentCommit(entry.path);
|
|
15838
|
+
await this.cacheManager.touch(this.url, ref, commitHash2);
|
|
15839
|
+
return entry.path;
|
|
15840
|
+
} catch {
|
|
15841
|
+
await this.cacheManager.remove(this.url, ref);
|
|
15842
|
+
}
|
|
15843
|
+
}
|
|
15844
|
+
}
|
|
15845
|
+
const cachePath = this.cacheManager.getCachePath(this.url, ref);
|
|
15846
|
+
await this.clone(cachePath, ref);
|
|
15847
|
+
const commitHash = await this.getCurrentCommit(cachePath);
|
|
15848
|
+
await this.cacheManager.set(this.url, ref, commitHash);
|
|
15849
|
+
return cachePath;
|
|
15850
|
+
}
|
|
15851
|
+
/**
|
|
15852
|
+
* Clone the repository to the specified path.
|
|
15853
|
+
*/
|
|
15854
|
+
async clone(targetPath, ref) {
|
|
15855
|
+
if (existsSync5(targetPath)) {
|
|
15856
|
+
await fs3.rm(targetPath, { recursive: true, force: true });
|
|
15857
|
+
}
|
|
15858
|
+
await fs3.mkdir(targetPath, { recursive: true });
|
|
15859
|
+
const cloneUrl = this.getAuthenticatedUrl();
|
|
15860
|
+
const git = this.createGit();
|
|
15861
|
+
try {
|
|
15862
|
+
await git.clone(cloneUrl, targetPath, ["--depth=1", `--branch=${ref}`, "--single-branch"]);
|
|
15863
|
+
} catch (err) {
|
|
15864
|
+
const error = err;
|
|
15865
|
+
if (this.isAuthError(error)) {
|
|
15866
|
+
throw new GitAuthError(`Authentication failed for ${this.url}`, this.url, error);
|
|
15867
|
+
}
|
|
15868
|
+
if (this.isRefError(error)) {
|
|
15869
|
+
try {
|
|
15870
|
+
await git.clone(cloneUrl, targetPath, ["--depth=1"]);
|
|
15871
|
+
const repoGit = this.createGit(targetPath);
|
|
15872
|
+
await repoGit.fetch(["origin", ref, "--depth=1"]);
|
|
15873
|
+
await repoGit.checkout(ref);
|
|
15874
|
+
} catch (fetchErr) {
|
|
15875
|
+
const fetchError = fetchErr;
|
|
15876
|
+
if (this.isRefError(fetchError)) {
|
|
15877
|
+
throw new GitRefNotFoundError(ref, this.url);
|
|
15878
|
+
}
|
|
15879
|
+
throw new GitCloneError(
|
|
15880
|
+
`Failed to clone repository: ${fetchError.message}`,
|
|
15881
|
+
this.url,
|
|
15882
|
+
fetchError
|
|
15883
|
+
);
|
|
15884
|
+
}
|
|
15885
|
+
return;
|
|
15886
|
+
}
|
|
15887
|
+
throw new GitCloneError(`Failed to clone repository: ${error.message}`, this.url, error);
|
|
15888
|
+
}
|
|
15889
|
+
}
|
|
15890
|
+
/**
|
|
15891
|
+
* Fetch updates for an existing clone.
|
|
15892
|
+
*/
|
|
15893
|
+
async fetchUpdates(repoPath, ref) {
|
|
15894
|
+
const git = this.createGit(repoPath);
|
|
15895
|
+
try {
|
|
15896
|
+
await git.fetch(["origin", ref, "--depth=1"]);
|
|
15897
|
+
await git.checkout(ref);
|
|
15898
|
+
await git.reset(["--hard", `origin/${ref}`]);
|
|
15899
|
+
} catch (err) {
|
|
15900
|
+
const error = err;
|
|
15901
|
+
if (this.isRefError(error)) {
|
|
15902
|
+
try {
|
|
15903
|
+
await git.fetch(["origin", "--tags", "--depth=1"]);
|
|
15904
|
+
await git.checkout(ref);
|
|
15905
|
+
return;
|
|
15906
|
+
} catch {
|
|
15907
|
+
throw new GitRefNotFoundError(ref, this.url);
|
|
15908
|
+
}
|
|
15909
|
+
}
|
|
15910
|
+
throw error;
|
|
15911
|
+
}
|
|
15912
|
+
}
|
|
15913
|
+
/**
|
|
15914
|
+
* Get the current commit hash of a repository.
|
|
15915
|
+
*/
|
|
15916
|
+
async getCurrentCommit(repoPath) {
|
|
15917
|
+
const git = this.createGit(repoPath);
|
|
15918
|
+
const result = await git.revparse(["HEAD"]);
|
|
15919
|
+
return result.trim();
|
|
15920
|
+
}
|
|
15921
|
+
/**
|
|
15922
|
+
* Build the authenticated URL for cloning.
|
|
15923
|
+
*/
|
|
15924
|
+
getAuthenticatedUrl() {
|
|
15925
|
+
if (!this.auth) {
|
|
15926
|
+
return this.url;
|
|
15927
|
+
}
|
|
15928
|
+
if (this.auth.type === "token") {
|
|
15929
|
+
const token = this.resolveToken();
|
|
15930
|
+
if (token) {
|
|
15931
|
+
return buildAuthenticatedUrl(this.url, token);
|
|
15932
|
+
}
|
|
15933
|
+
}
|
|
15934
|
+
return this.url;
|
|
15935
|
+
}
|
|
15936
|
+
/**
|
|
15937
|
+
* Resolve the authentication token.
|
|
15938
|
+
*/
|
|
15939
|
+
resolveToken() {
|
|
15940
|
+
if (this.auth?.token) {
|
|
15941
|
+
return this.auth.token;
|
|
15942
|
+
}
|
|
15943
|
+
if (this.auth?.tokenEnvVar) {
|
|
15944
|
+
return process.env[this.auth.tokenEnvVar];
|
|
15945
|
+
}
|
|
15946
|
+
return void 0;
|
|
15947
|
+
}
|
|
15948
|
+
/**
|
|
15949
|
+
* Create a simple-git instance.
|
|
15950
|
+
*/
|
|
15951
|
+
createGit(baseDir) {
|
|
15952
|
+
const options = {
|
|
15953
|
+
timeout: {
|
|
15954
|
+
block: this.timeout
|
|
15955
|
+
}
|
|
15956
|
+
};
|
|
15957
|
+
if (baseDir) {
|
|
15958
|
+
options.baseDir = baseDir;
|
|
15959
|
+
}
|
|
15960
|
+
const git = simpleGit(options);
|
|
15961
|
+
if (this.auth?.type === "ssh" && this.auth.sshKeyPath) {
|
|
15962
|
+
const parsed = parseGitUrl(this.originalUrl);
|
|
15963
|
+
if (parsed?.protocol === "ssh") {
|
|
15964
|
+
git.env("GIT_SSH_COMMAND", `ssh -i ${this.auth.sshKeyPath} -o StrictHostKeyChecking=no`);
|
|
15965
|
+
}
|
|
15966
|
+
}
|
|
15967
|
+
return git;
|
|
15968
|
+
}
|
|
15969
|
+
/**
|
|
15970
|
+
* Resolve a file path within the repository.
|
|
15971
|
+
*
|
|
15972
|
+
* Note: The @ prefix is preserved as it's part of the directory name
|
|
15973
|
+
* (e.g., @core/base.prs lives in a directory literally named "@core")
|
|
15974
|
+
*/
|
|
15975
|
+
resolveFilePath(repoPath, relativePath) {
|
|
15976
|
+
let cleanPath = relativePath;
|
|
15977
|
+
if (!cleanPath.endsWith(".prs") && !cleanPath.endsWith("/")) {
|
|
15978
|
+
cleanPath += ".prs";
|
|
15979
|
+
}
|
|
15980
|
+
if (this.subPath) {
|
|
15981
|
+
return join5(repoPath, this.subPath, cleanPath);
|
|
15982
|
+
}
|
|
15983
|
+
return join5(repoPath, cleanPath);
|
|
15984
|
+
}
|
|
15985
|
+
/**
|
|
15986
|
+
* Resolve a directory path within the repository.
|
|
15987
|
+
*
|
|
15988
|
+
* Note: The @ prefix is preserved as it's part of the directory name
|
|
15989
|
+
* (e.g., @core lives in a directory literally named "@core")
|
|
15990
|
+
*/
|
|
15991
|
+
resolveDirectoryPath(repoPath, relativePath) {
|
|
15992
|
+
if (this.subPath) {
|
|
15993
|
+
return join5(repoPath, this.subPath, relativePath);
|
|
15994
|
+
}
|
|
15995
|
+
return join5(repoPath, relativePath);
|
|
15996
|
+
}
|
|
15997
|
+
/**
|
|
15998
|
+
* Check if an error is an authentication error.
|
|
15999
|
+
*/
|
|
16000
|
+
isAuthError(error) {
|
|
16001
|
+
const message = error.message.toLowerCase();
|
|
16002
|
+
return message.includes("authentication") || message.includes("permission denied") || message.includes("could not read from remote") || message.includes("invalid credentials") || message.includes("401") || message.includes("403");
|
|
16003
|
+
}
|
|
16004
|
+
/**
|
|
16005
|
+
* Check if an error is a ref not found error.
|
|
16006
|
+
*/
|
|
16007
|
+
isRefError(error) {
|
|
16008
|
+
const message = error.message.toLowerCase();
|
|
16009
|
+
return message.includes("could not find remote branch") || message.includes("couldn't find remote ref") || message.includes("pathspec") || message.includes("did not match any") || message.includes("not found in upstream");
|
|
16010
|
+
}
|
|
16011
|
+
};
|
|
16012
|
+
function createGitRegistry(options) {
|
|
16013
|
+
return new GitRegistry(options);
|
|
16014
|
+
}
|
|
16015
|
+
|
|
14959
16016
|
// packages/validator/src/rules/required-meta.ts
|
|
14960
16017
|
var requiredMetaId = {
|
|
14961
16018
|
id: "PS001",
|
|
@@ -15501,6 +16558,11 @@ var Compiler = class _Compiler {
|
|
|
15501
16558
|
const formatOptions = this.getFormatOptionsForTarget(formatter.name, config);
|
|
15502
16559
|
const output = formatter.format(resolved.ast, formatOptions);
|
|
15503
16560
|
outputs.set(output.path, output);
|
|
16561
|
+
if (output.additionalFiles) {
|
|
16562
|
+
for (const additionalFile of output.additionalFiles) {
|
|
16563
|
+
outputs.set(additionalFile.path, additionalFile);
|
|
16564
|
+
}
|
|
16565
|
+
}
|
|
15504
16566
|
} catch (err) {
|
|
15505
16567
|
formatErrors.push({
|
|
15506
16568
|
name: "FormatterError",
|
|
@@ -15793,7 +16855,7 @@ async function compileCommand(options) {
|
|
|
15793
16855
|
customConventions: config.customConventions
|
|
15794
16856
|
});
|
|
15795
16857
|
const entryPath = resolve2("./.promptscript/project.prs");
|
|
15796
|
-
if (!
|
|
16858
|
+
if (!existsSync6(entryPath)) {
|
|
15797
16859
|
spinner.fail("Entry file not found");
|
|
15798
16860
|
ConsoleOutput.error(`File not found: ${entryPath}`);
|
|
15799
16861
|
ConsoleOutput.muted("Run: prs init");
|
|
@@ -15858,7 +16920,7 @@ function watchForChanges(dir, callback) {
|
|
|
15858
16920
|
|
|
15859
16921
|
// packages/cli/src/commands/validate.ts
|
|
15860
16922
|
import { resolve as resolve3 } from "path";
|
|
15861
|
-
import { existsSync as
|
|
16923
|
+
import { existsSync as existsSync7 } from "fs";
|
|
15862
16924
|
function printValidationErrors(errors) {
|
|
15863
16925
|
if (errors.length === 0) return;
|
|
15864
16926
|
console.log(`Errors (${errors.length}):`);
|
|
@@ -15949,7 +17011,7 @@ async function validateCommand(options) {
|
|
|
15949
17011
|
// No formatters needed for validation only
|
|
15950
17012
|
});
|
|
15951
17013
|
const entryPath = resolve3("./.promptscript/project.prs");
|
|
15952
|
-
if (!
|
|
17014
|
+
if (!existsSync7(entryPath)) {
|
|
15953
17015
|
handleEntryNotFound(entryPath, isJsonFormat, spinner);
|
|
15954
17016
|
}
|
|
15955
17017
|
const result = await compiler.compile(entryPath);
|
|
@@ -15988,37 +17050,33 @@ function outputJsonResult(result) {
|
|
|
15988
17050
|
}
|
|
15989
17051
|
|
|
15990
17052
|
// packages/cli/src/commands/pull.ts
|
|
15991
|
-
import { mkdir as mkdir3, writeFile as writeFile3
|
|
15992
|
-
import { existsSync as
|
|
17053
|
+
import { mkdir as mkdir3, writeFile as writeFile3 } from "fs/promises";
|
|
17054
|
+
import { existsSync as existsSync8 } from "fs";
|
|
15993
17055
|
import { resolve as resolve4, dirname as dirname4 } from "path";
|
|
15994
17056
|
async function pullCommand(options) {
|
|
15995
17057
|
const spinner = createSpinner("Loading configuration...").start();
|
|
15996
17058
|
try {
|
|
15997
17059
|
const config = await loadConfig();
|
|
15998
|
-
if (config.registry?.url) {
|
|
15999
|
-
spinner.fail("Remote registry not yet supported");
|
|
16000
|
-
ConsoleOutput.error("Remote registry support coming soon");
|
|
16001
|
-
ConsoleOutput.muted("For now, use a local registry path in your config.");
|
|
16002
|
-
process.exit(1);
|
|
16003
|
-
}
|
|
16004
17060
|
if (!config.inherit) {
|
|
16005
17061
|
spinner.warn("No inheritance configured");
|
|
16006
17062
|
ConsoleOutput.muted('Add "inherit" to your config to pull from registry');
|
|
16007
17063
|
return;
|
|
16008
17064
|
}
|
|
16009
17065
|
spinner.text = `Pulling ${config.inherit}...`;
|
|
16010
|
-
const
|
|
17066
|
+
const registry = await createRegistry(config, options, spinner);
|
|
16011
17067
|
const inheritPath = parseInheritPath(config.inherit);
|
|
16012
|
-
|
|
16013
|
-
|
|
17068
|
+
spinner.text = `Checking ${inheritPath}...`;
|
|
17069
|
+
const exists = await registry.exists(inheritPath);
|
|
17070
|
+
if (!exists) {
|
|
16014
17071
|
spinner.fail("Inheritance source not found");
|
|
16015
|
-
ConsoleOutput.error(`Not found: ${
|
|
16016
|
-
ConsoleOutput.muted("Make sure the registry
|
|
17072
|
+
ConsoleOutput.error(`Not found in registry: ${config.inherit}`);
|
|
17073
|
+
ConsoleOutput.muted("Make sure the registry is configured correctly and the file exists");
|
|
16017
17074
|
process.exit(1);
|
|
16018
17075
|
}
|
|
16019
|
-
|
|
17076
|
+
spinner.text = `Fetching ${inheritPath}...`;
|
|
17077
|
+
const content = await registry.fetch(inheritPath);
|
|
16020
17078
|
const destPath = resolve4("./.promptscript/.inherited", inheritPath);
|
|
16021
|
-
if (
|
|
17079
|
+
if (existsSync8(destPath) && !options.force && !options.dryRun) {
|
|
16022
17080
|
spinner.warn("File already exists (use --force to overwrite)");
|
|
16023
17081
|
ConsoleOutput.muted(destPath);
|
|
16024
17082
|
return;
|
|
@@ -16026,9 +17084,9 @@ async function pullCommand(options) {
|
|
|
16026
17084
|
if (options.dryRun) {
|
|
16027
17085
|
spinner.succeed("Dry run completed");
|
|
16028
17086
|
ConsoleOutput.newline();
|
|
16029
|
-
ConsoleOutput.dryRun(`Would
|
|
17087
|
+
ConsoleOutput.dryRun(`Would fetch: ${config.inherit}`);
|
|
16030
17088
|
ConsoleOutput.dryRun(` to: ${destPath}`);
|
|
16031
|
-
if (
|
|
17089
|
+
if (existsSync8(destPath)) {
|
|
16032
17090
|
ConsoleOutput.dryRun("(would overwrite existing file)");
|
|
16033
17091
|
}
|
|
16034
17092
|
return;
|
|
@@ -16038,23 +17096,97 @@ async function pullCommand(options) {
|
|
|
16038
17096
|
spinner.succeed("Pulled from registry");
|
|
16039
17097
|
ConsoleOutput.success(destPath);
|
|
16040
17098
|
} catch (error) {
|
|
16041
|
-
spinner
|
|
16042
|
-
ConsoleOutput.error(error.message);
|
|
17099
|
+
handlePullError(error, spinner);
|
|
16043
17100
|
process.exit(1);
|
|
16044
17101
|
}
|
|
16045
17102
|
}
|
|
17103
|
+
async function createRegistry(config, options, spinner) {
|
|
17104
|
+
const gitRef = options.commit ?? options.tag ?? options.branch;
|
|
17105
|
+
if (config.registry?.git) {
|
|
17106
|
+
const gitConfig = config.registry.git;
|
|
17107
|
+
spinner.text = `Connecting to Git registry: ${gitConfig.url}...`;
|
|
17108
|
+
return createGitRegistry({
|
|
17109
|
+
url: gitConfig.url,
|
|
17110
|
+
ref: gitRef ?? gitConfig.ref,
|
|
17111
|
+
path: gitConfig.path,
|
|
17112
|
+
auth: gitConfig.auth,
|
|
17113
|
+
cache: {
|
|
17114
|
+
enabled: !options.refresh && (config.registry.cache?.enabled ?? true),
|
|
17115
|
+
ttl: config.registry.cache?.ttl
|
|
17116
|
+
}
|
|
17117
|
+
});
|
|
17118
|
+
}
|
|
17119
|
+
if (config.registry?.url) {
|
|
17120
|
+
spinner.text = `Connecting to HTTP registry: ${config.registry.url}...`;
|
|
17121
|
+
const httpAuth = config.registry.auth;
|
|
17122
|
+
let token;
|
|
17123
|
+
if (httpAuth) {
|
|
17124
|
+
token = httpAuth.token ?? (httpAuth.tokenEnvVar ? process.env[httpAuth.tokenEnvVar] : void 0);
|
|
17125
|
+
}
|
|
17126
|
+
return createHttpRegistry({
|
|
17127
|
+
baseUrl: config.registry.url,
|
|
17128
|
+
auth: httpAuth && token ? { type: httpAuth.type, token } : void 0,
|
|
17129
|
+
cache: {
|
|
17130
|
+
enabled: config.registry.cache?.enabled ?? true,
|
|
17131
|
+
ttl: config.registry.cache?.ttl ?? 3e5
|
|
17132
|
+
}
|
|
17133
|
+
});
|
|
17134
|
+
}
|
|
17135
|
+
const registryPath = config.registry?.path ?? "./registry";
|
|
17136
|
+
spinner.text = `Using local registry: ${registryPath}...`;
|
|
17137
|
+
if (!existsSync8(registryPath)) {
|
|
17138
|
+
throw new Error(`Local registry path not found: ${registryPath}`);
|
|
17139
|
+
}
|
|
17140
|
+
return createFileSystemRegistry(resolve4(registryPath));
|
|
17141
|
+
}
|
|
16046
17142
|
function parseInheritPath(inheritPath) {
|
|
16047
|
-
|
|
17143
|
+
const versionIndex = inheritPath.lastIndexOf("@");
|
|
17144
|
+
const atIndex = inheritPath.indexOf("@");
|
|
17145
|
+
let pathWithoutVersion = inheritPath;
|
|
17146
|
+
if (versionIndex > atIndex && versionIndex !== atIndex) {
|
|
17147
|
+
pathWithoutVersion = inheritPath.slice(0, versionIndex);
|
|
17148
|
+
}
|
|
17149
|
+
let path = pathWithoutVersion.startsWith("@") ? pathWithoutVersion.slice(1) : pathWithoutVersion;
|
|
16048
17150
|
if (!path.endsWith(".prs")) {
|
|
16049
17151
|
path += ".prs";
|
|
16050
17152
|
}
|
|
16051
17153
|
return path;
|
|
16052
17154
|
}
|
|
17155
|
+
function handlePullError(error, spinner) {
|
|
17156
|
+
if (error instanceof GitAuthError) {
|
|
17157
|
+
spinner.fail("Git authentication failed");
|
|
17158
|
+
ConsoleOutput.error(error.message);
|
|
17159
|
+
ConsoleOutput.muted("Check your authentication configuration:");
|
|
17160
|
+
ConsoleOutput.muted(" - For token auth: ensure GITHUB_TOKEN or tokenEnvVar is set");
|
|
17161
|
+
ConsoleOutput.muted(" - For SSH auth: ensure SSH key is configured and accessible");
|
|
17162
|
+
return;
|
|
17163
|
+
}
|
|
17164
|
+
if (error instanceof GitRefNotFoundError) {
|
|
17165
|
+
spinner.fail("Git ref not found");
|
|
17166
|
+
ConsoleOutput.error(error.message);
|
|
17167
|
+
ConsoleOutput.muted("Available options:");
|
|
17168
|
+
ConsoleOutput.muted(" - Use --branch <name> to specify a branch");
|
|
17169
|
+
ConsoleOutput.muted(" - Use --tag <name> to specify a tag");
|
|
17170
|
+
ConsoleOutput.muted(" - Use --commit <hash> to specify a commit");
|
|
17171
|
+
return;
|
|
17172
|
+
}
|
|
17173
|
+
if (error instanceof GitCloneError) {
|
|
17174
|
+
spinner.fail("Git clone failed");
|
|
17175
|
+
ConsoleOutput.error(error.message);
|
|
17176
|
+
ConsoleOutput.muted("Possible causes:");
|
|
17177
|
+
ConsoleOutput.muted(" - Network connectivity issues");
|
|
17178
|
+
ConsoleOutput.muted(" - Invalid repository URL");
|
|
17179
|
+
ConsoleOutput.muted(" - Missing authentication for private repository");
|
|
17180
|
+
return;
|
|
17181
|
+
}
|
|
17182
|
+
spinner.fail("Error");
|
|
17183
|
+
ConsoleOutput.error(error.message);
|
|
17184
|
+
}
|
|
16053
17185
|
|
|
16054
17186
|
// packages/cli/src/commands/diff.ts
|
|
16055
17187
|
import { resolve as resolve5 } from "path";
|
|
16056
|
-
import { readFile as
|
|
16057
|
-
import { existsSync as
|
|
17188
|
+
import { readFile as readFile4 } from "fs/promises";
|
|
17189
|
+
import { existsSync as existsSync9 } from "fs";
|
|
16058
17190
|
|
|
16059
17191
|
// packages/cli/src/output/pager.ts
|
|
16060
17192
|
import { spawn } from "child_process";
|
|
@@ -16170,14 +17302,14 @@ function printNewFilePreview(content, showFull, pager) {
|
|
|
16170
17302
|
async function compareOutput(_name, output, _config, showFull, pager) {
|
|
16171
17303
|
const outputPath = resolve5(output.path);
|
|
16172
17304
|
const newContent = output.content;
|
|
16173
|
-
if (!
|
|
17305
|
+
if (!existsSync9(outputPath)) {
|
|
16174
17306
|
pager.write(chalk2.green(`+ ${outputPath} (new file)`));
|
|
16175
17307
|
pager.write("");
|
|
16176
17308
|
printNewFilePreview(newContent, showFull, pager);
|
|
16177
17309
|
pager.write("");
|
|
16178
17310
|
return true;
|
|
16179
17311
|
}
|
|
16180
|
-
const existingContent = await
|
|
17312
|
+
const existingContent = await readFile4(outputPath, "utf-8");
|
|
16181
17313
|
if (existingContent === newContent) {
|
|
16182
17314
|
pager.write(chalk2.gray(` ${outputPath} (no changes)`));
|
|
16183
17315
|
return false;
|
|
@@ -16207,7 +17339,7 @@ async function diffCommand(options) {
|
|
|
16207
17339
|
customConventions: config.customConventions
|
|
16208
17340
|
});
|
|
16209
17341
|
const entryPath = resolve5("./.promptscript/project.prs");
|
|
16210
|
-
if (!
|
|
17342
|
+
if (!existsSync9(entryPath)) {
|
|
16211
17343
|
spinner.fail("Entry file not found");
|
|
16212
17344
|
ConsoleOutput.error(`File not found: ${entryPath}`);
|
|
16213
17345
|
ConsoleOutput.muted("Run: prs init");
|
|
@@ -16273,7 +17405,7 @@ function printSimpleDiff(existingLines, newLines, showFull, pager) {
|
|
|
16273
17405
|
}
|
|
16274
17406
|
|
|
16275
17407
|
// packages/cli/src/commands/check.ts
|
|
16276
|
-
import { existsSync as
|
|
17408
|
+
import { existsSync as existsSync10 } from "fs";
|
|
16277
17409
|
import { resolve as resolve6 } from "path";
|
|
16278
17410
|
async function checkCommand(_options) {
|
|
16279
17411
|
const spinner = createSpinner("Checking project health...").start();
|
|
@@ -16336,7 +17468,7 @@ async function checkCommand(_options) {
|
|
|
16336
17468
|
});
|
|
16337
17469
|
}
|
|
16338
17470
|
const entryPath = config.input?.entry ?? ".promptscript/project.prs";
|
|
16339
|
-
if (
|
|
17471
|
+
if (existsSync10(entryPath)) {
|
|
16340
17472
|
results.push({
|
|
16341
17473
|
name: "Entry file",
|
|
16342
17474
|
status: "ok",
|
|
@@ -16352,7 +17484,7 @@ async function checkCommand(_options) {
|
|
|
16352
17484
|
}
|
|
16353
17485
|
if (config.registry?.path) {
|
|
16354
17486
|
const registryPath = resolve6(config.registry.path);
|
|
16355
|
-
if (
|
|
17487
|
+
if (existsSync10(registryPath)) {
|
|
16356
17488
|
results.push({
|
|
16357
17489
|
name: "Registry path",
|
|
16358
17490
|
status: "ok",
|
|
@@ -16451,7 +17583,7 @@ program.name("prs").description("PromptScript CLI - Standardize AI instructions"
|
|
|
16451
17583
|
program.command("init").description("Initialize PromptScript in current directory").option("-n, --name <name>", "Project name (auto-detected from package.json, etc.)").option("-t, --team <team>", "Team namespace").option("--inherit <path>", "Inheritance path (e.g., @company/team)").option("--registry <path>", "Registry path").option("--targets <targets...>", "Target AI tools (github, claude, cursor)").option("-i, --interactive", "Force interactive mode").option("-y, --yes", "Skip prompts, use defaults").option("-f, --force", "Force reinitialize even if already initialized").action((opts) => initCommand(opts));
|
|
16452
17584
|
program.command("compile").description("Compile PromptScript to target formats").option("-t, --target <target>", "Specific target (github, claude, cursor)").option("-f, --format <format>", "Output format (alias for --target)").option("-a, --all", "All configured targets", true).option("-w, --watch", "Watch mode").option("-o, --output <dir>", "Output directory").option("--dry-run", "Preview changes").option("--registry <path>", "Registry path (overrides config)").option("-c, --config <path>", "Path to custom config file").action(compileCommand);
|
|
16453
17585
|
program.command("validate").description("Validate PromptScript files").option("--strict", "Treat warnings as errors").option("--format <format>", "Output format (text, json)", "text").action(validateCommand);
|
|
16454
|
-
program.command("pull").description("Pull updates from registry").option("-f, --force", "Force overwrite").option("--dry-run", "Preview changes without pulling").action(pullCommand);
|
|
17586
|
+
program.command("pull").description("Pull updates from registry").option("-f, --force", "Force overwrite").option("--dry-run", "Preview changes without pulling").option("-b, --branch <name>", "Git branch to pull from").option("--tag <name>", "Git tag to pull from").option("--commit <hash>", "Git commit to pull from").option("--refresh", "Force re-fetch from remote (ignore cache)").action(pullCommand);
|
|
16455
17587
|
program.command("diff").description("Show diff for compiled output").option("-t, --target <target>", "Specific target").option("-a, --all", "Show diff for all targets at once").option("--full", "Show full diff without truncation").option("--no-pager", "Disable pager output").option("--color", "Force colored output").option("--no-color", "Disable colored output").action(diffCommand);
|
|
16456
17588
|
program.command("check").description("Check configuration and dependencies health").option("--fix", "Attempt to fix issues").action(checkCommand);
|
|
16457
17589
|
function run(args = process.argv) {
|