d365fo-mcp 1.5.2 → 1.7.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/README.md +1 -1
- package/bridge/D365MetadataBridge/D365MetadataBridge.csproj +33 -3
- package/bridge/D365MetadataBridge/Program.cs +1 -1
- package/bridge/D365MetadataBridge/Services/MetadataWriteService.cs +54 -1
- package/dist/bridge/bridgeAdapter.d.ts +1 -1
- package/dist/bridge/bridgeAdapter.js +34 -3
- package/dist/bridge/bridgeClient.d.ts +1 -1
- package/dist/bridge/bridgeClient.js +3 -5
- package/dist/bridge/bridgeTypes.d.ts +15 -0
- package/dist/cli/commands/doctor.js +39 -0
- package/dist/cli/commands/instance.js +7 -4
- package/dist/cli/commands/setup.js +11 -7
- package/dist/cli/copilotFiles.d.ts +3 -9
- package/dist/cli/copilotFiles.js +13 -24
- package/dist/cli/settingsPrompt.js +4 -1
- package/dist/config/settings.js +3 -1
- package/dist/metadata/modelDescriptor.d.ts +53 -0
- package/dist/metadata/modelDescriptor.js +129 -0
- package/dist/metadata/symbolIndex.js +4 -6
- package/dist/metadata/types.d.ts +8 -0
- package/dist/metadata/xmlParser.d.ts +5 -1
- package/dist/metadata/xmlParser.js +10 -2
- package/dist/metadata/xppDeclaration.d.ts +22 -0
- package/dist/metadata/xppDeclaration.js +36 -2
- package/dist/scripts/build-database.js +76 -10
- package/dist/scripts/build-fts.js +76 -10
- package/dist/scripts/extract-metadata.js +141 -66
- package/dist/server/toolSchemas/d365foFile.js +6 -6
- package/dist/server/toolSchemas/verifyD365foProject.js +1 -1
- package/dist/tools/analyzeExtensionPoints.js +141 -5
- package/dist/tools/buildProject.d.ts +58 -0
- package/dist/tools/buildProject.js +147 -55
- package/dist/tools/compileLabels.d.ts +62 -0
- package/dist/tools/compileLabels.js +239 -0
- package/dist/tools/completion.js +11 -2
- package/dist/tools/createD365File.d.ts +15 -7
- package/dist/tools/createD365File.js +51 -24
- package/dist/tools/createLabel.js +5 -4
- package/dist/tools/dataEntityInfo.d.ts +9 -12
- package/dist/tools/dataEntityInfo.js +12 -3
- package/dist/tools/dataEntityViewExtensionXml.d.ts +74 -0
- package/dist/tools/dataEntityViewExtensionXml.js +142 -0
- package/dist/tools/dataEntityXml.d.ts +60 -0
- package/dist/tools/dataEntityXml.js +165 -21
- package/dist/tools/dbSync.js +2 -1
- package/dist/tools/enumInfo.d.ts +10 -3
- package/dist/tools/enumInfo.js +69 -10
- package/dist/tools/formInfo.js +11 -0
- package/dist/tools/generateD365Xml.d.ts +157 -0
- package/dist/tools/generateD365Xml.js +51 -15
- package/dist/tools/generateSmart.js +1 -1
- package/dist/tools/generateSmartForm.js +2 -1
- package/dist/tools/generateSmartReport.d.ts +2 -1
- package/dist/tools/generateSmartReport.js +119 -117
- package/dist/tools/generateSmartTable.js +5 -4
- package/dist/tools/getMethodSource.js +49 -1
- package/dist/tools/menuItemExtensionXml.d.ts +39 -0
- package/dist/tools/menuItemExtensionXml.js +91 -0
- package/dist/tools/methodSignature.js +71 -25
- package/dist/tools/prepareChange.js +70 -34
- package/dist/tools/queryInfo.d.ts +10 -3
- package/dist/tools/queryInfo.js +123 -10
- package/dist/tools/renameLabel.js +5 -4
- package/dist/tools/reportInfo.js +42 -14
- package/dist/tools/resolveReferences.d.ts +14 -1
- package/dist/tools/resolveReferences.js +160 -14
- package/dist/tools/runBpCheck.js +23 -16
- package/dist/tools/sysTestRunner.js +2 -1
- package/dist/tools/updateSymbolIndex.js +3 -4
- package/dist/tools/validateCode.js +2 -1
- package/dist/tools/verifyD365Project.js +6 -5
- package/dist/tools/viewInfo.d.ts +15 -4
- package/dist/tools/viewInfo.js +162 -9
- package/dist/tools/xppKnowledge.js +160 -12
- package/dist/utils/axTablePropertyOrder.d.ts +1 -1
- package/dist/utils/axTablePropertyOrder.js +5 -0
- package/dist/utils/configManager.d.ts +7 -0
- package/dist/utils/configManager.js +17 -25
- package/dist/utils/frameworkBin.d.ts +19 -0
- package/dist/utils/frameworkBin.js +48 -0
- package/dist/utils/indexedXmlLookup.d.ts +54 -0
- package/dist/utils/indexedXmlLookup.js +103 -0
- package/dist/utils/inheritanceChain.d.ts +44 -0
- package/dist/utils/inheritanceChain.js +99 -0
- package/dist/utils/packagesRoot.d.ts +55 -0
- package/dist/utils/packagesRoot.js +134 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
[](https://www.typescriptlang.org/)
|
|
11
11
|
[](docs/TESTING.md)
|
|
12
12
|
<!-- coverage-badge:start -->
|
|
13
|
-
[](eval/COVERAGE.md) [](eval/COVERAGE.md) [](eval/COVERAGE.md)
|
|
14
14
|
<!-- coverage-badge:end -->
|
|
15
15
|
|
|
16
16
|
*Grounded AI development for Dynamics 365 Finance & Operations — works with GitHub Copilot and Claude Code*
|
|
@@ -23,10 +23,18 @@
|
|
|
23
23
|
|
|
24
24
|
<!--
|
|
25
25
|
D365FO bin directory — contains all Microsoft.Dynamics.*.dll assemblies.
|
|
26
|
-
Every D365FO development desktop has this directory available
|
|
26
|
+
Every D365FO development desktop has this directory available, but WHICH
|
|
27
|
+
volume it sits on is decided by the VM image: K: on cloud-hosted
|
|
28
|
+
environments, C: on the downloadable VHD, J: on newer images (#769), and
|
|
29
|
+
nothing stops the next image from using another letter again.
|
|
30
|
+
|
|
31
|
+
So the drive is swept rather than guessed: the first
|
|
32
|
+
<drive>:\AosService\PackagesLocalDirectory\bin that exists wins, with the
|
|
33
|
+
historically standard letters tried first and the rest of the alphabet
|
|
34
|
+
after them. MSBuild has no loop at evaluation time, and the property has to
|
|
35
|
+
be resolved before Reference items are globbed — hence one line per letter.
|
|
36
|
+
A: and B: are left out on purpose (floppy letters stall the probe).
|
|
27
37
|
|
|
28
|
-
Traditional VM: K:\AosService\PackagesLocalDirectory\bin
|
|
29
|
-
Cloud-hosted: C:\AosService\PackagesLocalDirectory\bin
|
|
30
38
|
UDE (Unified Developer Experience): path from .mcp.json microsoftPackagesPath/bin
|
|
31
39
|
|
|
32
40
|
Override per-machine via:
|
|
@@ -36,6 +44,28 @@
|
|
|
36
44
|
<PropertyGroup>
|
|
37
45
|
<D365BinPath Condition="'$(D365BinPath)' == '' And Exists('K:\AosService\PackagesLocalDirectory\bin')">K:\AosService\PackagesLocalDirectory\bin</D365BinPath>
|
|
38
46
|
<D365BinPath Condition="'$(D365BinPath)' == '' And Exists('C:\AosService\PackagesLocalDirectory\bin')">C:\AosService\PackagesLocalDirectory\bin</D365BinPath>
|
|
47
|
+
<D365BinPath Condition="'$(D365BinPath)' == '' And Exists('J:\AosService\PackagesLocalDirectory\bin')">J:\AosService\PackagesLocalDirectory\bin</D365BinPath>
|
|
48
|
+
<D365BinPath Condition="'$(D365BinPath)' == '' And Exists('I:\AosService\PackagesLocalDirectory\bin')">I:\AosService\PackagesLocalDirectory\bin</D365BinPath>
|
|
49
|
+
<D365BinPath Condition="'$(D365BinPath)' == '' And Exists('D:\AosService\PackagesLocalDirectory\bin')">D:\AosService\PackagesLocalDirectory\bin</D365BinPath>
|
|
50
|
+
<D365BinPath Condition="'$(D365BinPath)' == '' And Exists('E:\AosService\PackagesLocalDirectory\bin')">E:\AosService\PackagesLocalDirectory\bin</D365BinPath>
|
|
51
|
+
<D365BinPath Condition="'$(D365BinPath)' == '' And Exists('F:\AosService\PackagesLocalDirectory\bin')">F:\AosService\PackagesLocalDirectory\bin</D365BinPath>
|
|
52
|
+
<D365BinPath Condition="'$(D365BinPath)' == '' And Exists('G:\AosService\PackagesLocalDirectory\bin')">G:\AosService\PackagesLocalDirectory\bin</D365BinPath>
|
|
53
|
+
<D365BinPath Condition="'$(D365BinPath)' == '' And Exists('H:\AosService\PackagesLocalDirectory\bin')">H:\AosService\PackagesLocalDirectory\bin</D365BinPath>
|
|
54
|
+
<D365BinPath Condition="'$(D365BinPath)' == '' And Exists('L:\AosService\PackagesLocalDirectory\bin')">L:\AosService\PackagesLocalDirectory\bin</D365BinPath>
|
|
55
|
+
<D365BinPath Condition="'$(D365BinPath)' == '' And Exists('M:\AosService\PackagesLocalDirectory\bin')">M:\AosService\PackagesLocalDirectory\bin</D365BinPath>
|
|
56
|
+
<D365BinPath Condition="'$(D365BinPath)' == '' And Exists('N:\AosService\PackagesLocalDirectory\bin')">N:\AosService\PackagesLocalDirectory\bin</D365BinPath>
|
|
57
|
+
<D365BinPath Condition="'$(D365BinPath)' == '' And Exists('O:\AosService\PackagesLocalDirectory\bin')">O:\AosService\PackagesLocalDirectory\bin</D365BinPath>
|
|
58
|
+
<D365BinPath Condition="'$(D365BinPath)' == '' And Exists('P:\AosService\PackagesLocalDirectory\bin')">P:\AosService\PackagesLocalDirectory\bin</D365BinPath>
|
|
59
|
+
<D365BinPath Condition="'$(D365BinPath)' == '' And Exists('Q:\AosService\PackagesLocalDirectory\bin')">Q:\AosService\PackagesLocalDirectory\bin</D365BinPath>
|
|
60
|
+
<D365BinPath Condition="'$(D365BinPath)' == '' And Exists('R:\AosService\PackagesLocalDirectory\bin')">R:\AosService\PackagesLocalDirectory\bin</D365BinPath>
|
|
61
|
+
<D365BinPath Condition="'$(D365BinPath)' == '' And Exists('S:\AosService\PackagesLocalDirectory\bin')">S:\AosService\PackagesLocalDirectory\bin</D365BinPath>
|
|
62
|
+
<D365BinPath Condition="'$(D365BinPath)' == '' And Exists('T:\AosService\PackagesLocalDirectory\bin')">T:\AosService\PackagesLocalDirectory\bin</D365BinPath>
|
|
63
|
+
<D365BinPath Condition="'$(D365BinPath)' == '' And Exists('U:\AosService\PackagesLocalDirectory\bin')">U:\AosService\PackagesLocalDirectory\bin</D365BinPath>
|
|
64
|
+
<D365BinPath Condition="'$(D365BinPath)' == '' And Exists('V:\AosService\PackagesLocalDirectory\bin')">V:\AosService\PackagesLocalDirectory\bin</D365BinPath>
|
|
65
|
+
<D365BinPath Condition="'$(D365BinPath)' == '' And Exists('W:\AosService\PackagesLocalDirectory\bin')">W:\AosService\PackagesLocalDirectory\bin</D365BinPath>
|
|
66
|
+
<D365BinPath Condition="'$(D365BinPath)' == '' And Exists('X:\AosService\PackagesLocalDirectory\bin')">X:\AosService\PackagesLocalDirectory\bin</D365BinPath>
|
|
67
|
+
<D365BinPath Condition="'$(D365BinPath)' == '' And Exists('Y:\AosService\PackagesLocalDirectory\bin')">Y:\AosService\PackagesLocalDirectory\bin</D365BinPath>
|
|
68
|
+
<D365BinPath Condition="'$(D365BinPath)' == '' And Exists('Z:\AosService\PackagesLocalDirectory\bin')">Z:\AosService\PackagesLocalDirectory\bin</D365BinPath>
|
|
39
69
|
</PropertyGroup>
|
|
40
70
|
|
|
41
71
|
<!-- NuGet packages -->
|
|
@@ -416,7 +416,7 @@ Usage:
|
|
|
416
416
|
D365MetadataBridge.exe [options]
|
|
417
417
|
|
|
418
418
|
Options:
|
|
419
|
-
--packages-path <path> Path to primary PackagesLocalDirectory (
|
|
419
|
+
--packages-path <path> Path to primary PackagesLocalDirectory (required; the server detects it per machine)
|
|
420
420
|
--reference-packages-path <path> UDE: secondary packages path (Microsoft FrameworkDirectory). Objects not found in
|
|
421
421
|
the primary path are looked up here, enabling resolution of both custom and
|
|
422
422
|
Microsoft-shipped metadata in UDE environments.
|
|
@@ -1504,7 +1504,7 @@ namespace D365MetadataBridge.Services
|
|
|
1504
1504
|
?? throw new ArgumentException($"Table '{objectName}' not found");
|
|
1505
1505
|
var msi = GetModelSaveInfoForObject(_provider.Tables, objectName);
|
|
1506
1506
|
if (!SetAxTableProperty(obj, propertyPath, propertyValue))
|
|
1507
|
-
throw new ArgumentException($"Unknown AxTable property '{propertyPath}' — nothing was written. Supported: label, developerDocumentation, configurationKey, formRef, tableGroup, cacheLookup, clusteredIndex, primaryIndex, replacementKey, saveDataPerCompany, tableType, supportInheritance, instanceRelationType, extends, titleField1, titleField2.");
|
|
1507
|
+
throw new ArgumentException($"Unknown AxTable property '{propertyPath}' — nothing was written. Supported: label, developerDocumentation, configurationKey, formRef, tableGroup, cacheLookup, clusteredIndex, primaryIndex, replacementKey, saveDataPerCompany, allowRowVersionChangeTracking, createdBy, createdDateTime, createdTransactionId, modifiedBy, modifiedDateTime, modifiedTransactionId, tableType, supportInheritance, instanceRelationType, extends, titleField1, titleField2.");
|
|
1508
1508
|
((IMetaTableProvider)_provider.Tables).Update(obj, msi);
|
|
1509
1509
|
return new { success = true, operation = "modify-property", objectType, objectName, propertyPath, propertyValue, api = "Update" };
|
|
1510
1510
|
}
|
|
@@ -1548,6 +1548,16 @@ namespace D365MetadataBridge.Services
|
|
|
1548
1548
|
((IMetaViewProvider)_provider.Views).Update(obj, msi);
|
|
1549
1549
|
return new { success = true, operation = "modify-property", objectType, objectName, propertyPath, propertyValue, api = "Update" };
|
|
1550
1550
|
}
|
|
1551
|
+
case "data-entity":
|
|
1552
|
+
{
|
|
1553
|
+
var obj = _provider.DataEntityViews.Read(objectName)
|
|
1554
|
+
?? throw new ArgumentException($"Data entity '{objectName}' not found");
|
|
1555
|
+
var msi = GetModelSaveInfoForObject(_provider.DataEntityViews, objectName);
|
|
1556
|
+
if (!SetAxDataEntityViewProperty(obj, propertyPath, propertyValue))
|
|
1557
|
+
throw new ArgumentException($"Unknown AxDataEntityView property '{propertyPath}' — nothing was written. Supported: label, developerDocumentation, primaryKey, isPublic, publicEntityName, publicCollectionName, dataManagementEnabled, dataManagementStagingTable, entityCategory, allowRowVersionChangeTracking, allowRetention.");
|
|
1558
|
+
((IMetaDataEntityViewProvider)_provider.DataEntityViews).Update(obj, msi);
|
|
1559
|
+
return new { success = true, operation = "modify-property", objectType, objectName, propertyPath, propertyValue, api = "Update" };
|
|
1560
|
+
}
|
|
1551
1561
|
case "menu-item-action":
|
|
1552
1562
|
{
|
|
1553
1563
|
var obj = _provider.MenuItemActions.Read(objectName)
|
|
@@ -2642,6 +2652,16 @@ namespace D365MetadataBridge.Services
|
|
|
2642
2652
|
case "savedatapercompany":
|
|
2643
2653
|
tbl.SaveDataPerCompany = ParseNoYes(value);
|
|
2644
2654
|
break;
|
|
2655
|
+
// Dual-write's table-side change-tracking prerequisite.
|
|
2656
|
+
case "allowrowversionchangetracking":
|
|
2657
|
+
tbl.AllowRowVersionChangeTracking = ParseNoYes(value);
|
|
2658
|
+
break;
|
|
2659
|
+
case "createdby": tbl.CreatedBy = ParseNoYes(value); break;
|
|
2660
|
+
case "createddatetime": tbl.CreatedDateTime = ParseNoYes(value); break;
|
|
2661
|
+
case "createdtransactionid": tbl.CreatedTransactionId = ParseNoYes(value); break;
|
|
2662
|
+
case "modifiedby": tbl.ModifiedBy = ParseNoYes(value); break;
|
|
2663
|
+
case "modifieddatetime": tbl.ModifiedDateTime = ParseNoYes(value); break;
|
|
2664
|
+
case "modifiedtransactionid": tbl.ModifiedTransactionId = ParseNoYes(value); break;
|
|
2645
2665
|
case "tabletype":
|
|
2646
2666
|
if (!Enum.TryParse<Microsoft.Dynamics.AX.Metadata.Core.MetaModel.TableType>(value, true, out var tt))
|
|
2647
2667
|
throw new ArgumentException(
|
|
@@ -2725,6 +2745,39 @@ namespace D365MetadataBridge.Services
|
|
|
2725
2745
|
return true;
|
|
2726
2746
|
}
|
|
2727
2747
|
|
|
2748
|
+
/// <summary>
|
|
2749
|
+
/// AxDataEntityView property setter. AllowRowVersionChangeTracking is
|
|
2750
|
+
/// dual-write's change-tracking switch and is also required on every
|
|
2751
|
+
/// source table.
|
|
2752
|
+
/// </summary>
|
|
2753
|
+
private bool SetAxDataEntityViewProperty(AxDataEntityView e, string prop, string value)
|
|
2754
|
+
{
|
|
2755
|
+
switch (prop.ToLowerInvariant())
|
|
2756
|
+
{
|
|
2757
|
+
case "label": e.Label = value; break;
|
|
2758
|
+
case "developerdocumentation": e.DeveloperDocumentation = value; break;
|
|
2759
|
+
case "primarykey": e.PrimaryKey = value; break;
|
|
2760
|
+
case "publicentityname": e.PublicEntityName = value; break;
|
|
2761
|
+
case "publiccollectionname": e.PublicCollectionName = value; break;
|
|
2762
|
+
case "datamanagementstagingtable": e.DataManagementStagingTable = value; break;
|
|
2763
|
+
case "ispublic": e.IsPublic = ParseNoYes(value); break;
|
|
2764
|
+
case "datamanagementenabled": e.DataManagementEnabled = ParseNoYes(value); break;
|
|
2765
|
+
case "allowrowversionchangetracking": e.AllowRowVersionChangeTracking = ParseNoYes(value); break;
|
|
2766
|
+
case "allowretention": e.AllowRetention = ParseNoYes(value); break;
|
|
2767
|
+
case "entitycategory":
|
|
2768
|
+
if (!Enum.TryParse<Microsoft.Dynamics.AX.Metadata.Core.MetaModel.EntityCategory>(value, true, out var ec))
|
|
2769
|
+
throw new ArgumentException(
|
|
2770
|
+
$"'{value}' is not a valid EntityCategory. Valid values: " +
|
|
2771
|
+
string.Join(", ", Enum.GetNames(typeof(Microsoft.Dynamics.AX.Metadata.Core.MetaModel.EntityCategory))) + ".");
|
|
2772
|
+
e.EntityCategory = ec;
|
|
2773
|
+
break;
|
|
2774
|
+
default:
|
|
2775
|
+
Console.Error.WriteLine($"[WriteService] Unknown AxDataEntityView property: {prop}");
|
|
2776
|
+
return false;
|
|
2777
|
+
}
|
|
2778
|
+
return true;
|
|
2779
|
+
}
|
|
2780
|
+
|
|
2728
2781
|
private bool SetAxViewProperty(AxView v, string prop, string value)
|
|
2729
2782
|
{
|
|
2730
2783
|
switch (prop.ToLowerInvariant())
|
|
@@ -358,7 +358,7 @@ export declare function bridgeDiscoverFormPatterns(bridge: BridgeClient | undefi
|
|
|
358
358
|
export declare function tryBridgeSecurityArtifact(bridge: BridgeClient | undefined, name: string, artifactType: 'privilege' | 'duty' | 'role', includeChain: boolean): Promise<ToolResult | null>;
|
|
359
359
|
export declare function tryBridgeMenuItem(bridge: BridgeClient | undefined, name: string, itemType?: string): Promise<ToolResult | null>;
|
|
360
360
|
export declare function tryBridgeTableExtensions(bridge: BridgeClient | undefined, baseTableName: string): Promise<ToolResult | null>;
|
|
361
|
-
export declare function tryBridgeCompletion(bridge: BridgeClient | undefined, symbolName: string, prefix?: string): Promise<ToolResult | null>;
|
|
361
|
+
export declare function tryBridgeCompletion(bridge: BridgeClient | undefined, symbolName: string, prefix?: string, ancestors?: string[]): Promise<ToolResult | null>;
|
|
362
362
|
export declare function tryBridgeCocExtensions(bridge: BridgeClient | undefined, baseClassName: string, methodName?: string): Promise<ToolResult | null>;
|
|
363
363
|
export declare function tryBridgeEventHandlers(bridge: BridgeClient | undefined, targetName: string, eventName?: string, handlerType?: string): Promise<ToolResult | null>;
|
|
364
364
|
export declare function tryBridgeApiUsageCallers(bridge: BridgeClient | undefined, apiName: string, limit?: number): Promise<ToolResult | null>;
|
|
@@ -1077,7 +1077,7 @@ const BRIDGE_MODIFY_OPS = new Set([
|
|
|
1077
1077
|
*/
|
|
1078
1078
|
const BRIDGE_MODIFY_TYPES = new Set([
|
|
1079
1079
|
'class', 'table', 'enum', 'edt',
|
|
1080
|
-
'form', 'query', 'view',
|
|
1080
|
+
'form', 'query', 'view', 'data-entity',
|
|
1081
1081
|
'class-extension', 'table-extension', 'form-extension', 'enum-extension',
|
|
1082
1082
|
'menu-item-action', 'menu-item-display', 'menu-item-output',
|
|
1083
1083
|
'menu',
|
|
@@ -1911,13 +1911,38 @@ function formatTableExtensions(r) {
|
|
|
1911
1911
|
return out;
|
|
1912
1912
|
}
|
|
1913
1913
|
// CODE COMPLETION (Phase 6)
|
|
1914
|
-
export async function tryBridgeCompletion(bridge, symbolName, prefix) {
|
|
1914
|
+
export async function tryBridgeCompletion(bridge, symbolName, prefix, ancestors) {
|
|
1915
1915
|
if (!bridge?.isReady || !bridge.metadataAvailable)
|
|
1916
1916
|
return null;
|
|
1917
1917
|
try {
|
|
1918
1918
|
const result = await bridge.getCompletionMembers(symbolName);
|
|
1919
1919
|
if (!result || !result.members || result.members.length === 0)
|
|
1920
1920
|
return null;
|
|
1921
|
+
// IMetadataProvider returns DECLARED members only, so a subclass lists
|
|
1922
|
+
// nothing it inherits and the reader concludes the member does not exist.
|
|
1923
|
+
// Merge the base classes in, nearest first; a name already present wins,
|
|
1924
|
+
// since that is the subclass's own override.
|
|
1925
|
+
if (ancestors?.length) {
|
|
1926
|
+
const seen = new Set(result.members.map(m => m.name.toLowerCase()));
|
|
1927
|
+
for (const ancestor of ancestors) {
|
|
1928
|
+
let inherited = null;
|
|
1929
|
+
try {
|
|
1930
|
+
inherited = await bridge.getCompletionMembers(ancestor);
|
|
1931
|
+
}
|
|
1932
|
+
catch (e) {
|
|
1933
|
+
// One unreadable link must not discard the members already merged.
|
|
1934
|
+
console.error(`[BridgeAdapter] getCompletionMembers(${ancestor}) failed: ${e}`);
|
|
1935
|
+
continue;
|
|
1936
|
+
}
|
|
1937
|
+
for (const m of inherited?.members ?? []) {
|
|
1938
|
+
const key = m.name.toLowerCase();
|
|
1939
|
+
if (seen.has(key))
|
|
1940
|
+
continue;
|
|
1941
|
+
seen.add(key);
|
|
1942
|
+
result.members.push({ ...m, inheritedFrom: inherited?.symbolName || ancestor });
|
|
1943
|
+
}
|
|
1944
|
+
}
|
|
1945
|
+
}
|
|
1921
1946
|
return { content: [{ type: 'text', text: formatCompletion(result, prefix) }] };
|
|
1922
1947
|
}
|
|
1923
1948
|
catch (e) {
|
|
@@ -1943,9 +1968,15 @@ function formatCompletion(r, prefix) {
|
|
|
1943
1968
|
const methodMembers = members.filter(m => m.kind === 'method');
|
|
1944
1969
|
const fieldMembers = members.filter(m => m.kind === 'field');
|
|
1945
1970
|
if (methodMembers.length > 0) {
|
|
1971
|
+
const inheritedCount = methodMembers.filter(m => m.inheritedFrom).length;
|
|
1946
1972
|
out += `## Methods (${methodMembers.length})\n`;
|
|
1947
1973
|
for (const m of methodMembers) {
|
|
1948
|
-
|
|
1974
|
+
const body = m.signature ? `\`${m.signature}\`` : m.name;
|
|
1975
|
+
out += m.inheritedFrom ? `- ${body} _(inherited from ${m.inheritedFrom})_\n` : `- ${body}\n`;
|
|
1976
|
+
}
|
|
1977
|
+
if (inheritedCount > 0) {
|
|
1978
|
+
out += `\n> ${inheritedCount} of these are inherited — callable on ${r.symbolName}, but ` +
|
|
1979
|
+
`declared on a base class. To read or wrap one, target the class named beside it.\n`;
|
|
1949
1980
|
}
|
|
1950
1981
|
}
|
|
1951
1982
|
if (fieldMembers.length > 0) {
|
|
@@ -19,7 +19,7 @@ export * from './bridgeTypes.js';
|
|
|
19
19
|
export interface BridgeClientOptions {
|
|
20
20
|
/** Path to the D365MetadataBridge.exe (auto-detected if omitted) */
|
|
21
21
|
bridgeExePath?: string;
|
|
22
|
-
/** K:\AosService\PackagesLocalDirectory */
|
|
22
|
+
/** e.g. K:\AosService\PackagesLocalDirectory — the volume varies by VM image */
|
|
23
23
|
packagesPath: string;
|
|
24
24
|
/**
|
|
25
25
|
* Optional secondary packages path.
|
|
@@ -17,6 +17,7 @@ import { EventEmitter } from 'events';
|
|
|
17
17
|
import * as path from 'path';
|
|
18
18
|
import * as fs from 'fs';
|
|
19
19
|
import { fileURLToPath } from 'url';
|
|
20
|
+
import { packagesRoots } from '../utils/packagesRoot.js';
|
|
20
21
|
export * from './bridgeTypes.js';
|
|
21
22
|
const BRIDGE_EXE_NAME = 'D365MetadataBridge.exe';
|
|
22
23
|
/** Parse a positive-integer env var with a fallback (ignores invalid/non-positive values). */
|
|
@@ -751,11 +752,8 @@ function detectPackagesPath() {
|
|
|
751
752
|
const candidates = [
|
|
752
753
|
process.env.D365FO_PACKAGE_PATH ?? '',
|
|
753
754
|
process.env.PACKAGES_PATH ?? '',
|
|
754
|
-
//
|
|
755
|
-
|
|
756
|
-
'C:\\AOSService\\PackagesLocalDirectory',
|
|
757
|
-
'J:\\AosService\\PackagesLocalDirectory',
|
|
758
|
-
'K:\\AosService\\PackagesLocalDirectory',
|
|
755
|
+
// Whatever AosService volumes this machine actually has (C:, J:, K:, …)
|
|
756
|
+
...packagesRoots(),
|
|
759
757
|
].filter(Boolean);
|
|
760
758
|
for (const p of candidates) {
|
|
761
759
|
// Traditional: bin is directly under packagesPath
|
|
@@ -92,6 +92,14 @@ export interface BridgeMethodInfo {
|
|
|
92
92
|
returnType?: string;
|
|
93
93
|
source?: string;
|
|
94
94
|
isStatic?: boolean;
|
|
95
|
+
/**
|
|
96
|
+
* ⚠️ Never sent by the bridge. The C# MethodInfoModel behind readClass carries
|
|
97
|
+
* only name/source/isStatic, so this is always undefined on bridge-sourced
|
|
98
|
+
* methods — it is populated exclusively by the XML parser path
|
|
99
|
+
* (xmlParser.parseClassFile). Reading it off a readClass result silently
|
|
100
|
+
* yields nothing; for a bridge-sourced modifier parse the declaration line out
|
|
101
|
+
* of getCompletionMembers().members[].signature instead.
|
|
102
|
+
*/
|
|
95
103
|
visibility?: string;
|
|
96
104
|
}
|
|
97
105
|
export interface BridgeEnumInfo {
|
|
@@ -499,6 +507,13 @@ export interface BridgeCompletionMember {
|
|
|
499
507
|
name: string;
|
|
500
508
|
signature?: string;
|
|
501
509
|
kind: string;
|
|
510
|
+
/**
|
|
511
|
+
* Set by the caller (not the bridge) when the member was picked up from a
|
|
512
|
+
* base class rather than declared on the requested one. IMetadataProvider
|
|
513
|
+
* returns declared members only, so inherited members are merged in on the
|
|
514
|
+
* TypeScript side and tagged here.
|
|
515
|
+
*/
|
|
516
|
+
inheritedFrom?: string;
|
|
502
517
|
}
|
|
503
518
|
export interface BridgeCompletionResult {
|
|
504
519
|
symbolName: string;
|
|
@@ -16,6 +16,7 @@ import { checkRelease } from '../npmRegistry.js';
|
|
|
16
16
|
import { conflictingLegacyValues, readPath, readSetting } from '../settingsStore.js';
|
|
17
17
|
import { instanceTarget, rootTarget } from '../target.js';
|
|
18
18
|
import { isXppConfigStale, listXppConfigs, xppConfigDir } from '../xppConfig.js';
|
|
19
|
+
import { describePackagesRootScan, packagesRoots } from '../../utils/packagesRoot.js';
|
|
19
20
|
const REQUIRED_NODE_MAJOR = 24;
|
|
20
21
|
/**
|
|
21
22
|
* How to reach the wizard from here. `npm run setup` only exists in a checkout
|
|
@@ -74,6 +75,40 @@ function checkConfig(target, label) {
|
|
|
74
75
|
fix: SETUP_COMMAND,
|
|
75
76
|
};
|
|
76
77
|
}
|
|
78
|
+
/**
|
|
79
|
+
* The configured packages root against what the machine actually has.
|
|
80
|
+
*
|
|
81
|
+
* A packagePath pointing at a drive this image does not use is the failure
|
|
82
|
+
* behind "setup can't find the namespaces" (#769) — the index then builds from
|
|
83
|
+
* nothing and every lookup comes back empty, with no error saying why. Naming
|
|
84
|
+
* the detected volume turns that into a one-line fix.
|
|
85
|
+
*/
|
|
86
|
+
function checkPackagesRoot(store, label) {
|
|
87
|
+
if (!isWindows)
|
|
88
|
+
return [];
|
|
89
|
+
const configured = String(readSetting(store, settingByPath('environment.packagePath')) ?? '').trim();
|
|
90
|
+
const detected = packagesRoots();
|
|
91
|
+
if (!configured) {
|
|
92
|
+
// UDE resolves its roots from the XPP config, so silence here is normal.
|
|
93
|
+
if (detected.length === 0)
|
|
94
|
+
return [];
|
|
95
|
+
return [{
|
|
96
|
+
severity: 'info',
|
|
97
|
+
message: `${label}: packages root not configured — the server will use ${detected[0]}`,
|
|
98
|
+
}];
|
|
99
|
+
}
|
|
100
|
+
if (fs.existsSync(configured)) {
|
|
101
|
+
return [{ severity: 'ok', message: `${label}: packages root OK (${configured})` }];
|
|
102
|
+
}
|
|
103
|
+
return [{
|
|
104
|
+
severity: 'fail',
|
|
105
|
+
message: `${label}: packages root does not exist (${configured})` +
|
|
106
|
+
(detected.length > 0 ? `\n found instead: ${detected.join(', ')}` : `\n ${describePackagesRootScan()}`),
|
|
107
|
+
fix: detected.length > 0
|
|
108
|
+
? `set environment.packagePath to ${detected[0]} (${SETUP_COMMAND})`
|
|
109
|
+
: `point environment.packagePath at this machine's PackagesLocalDirectory (${SETUP_COMMAND})`,
|
|
110
|
+
}];
|
|
111
|
+
}
|
|
77
112
|
/** A legacy .env that disagrees with the config is a trap: the config wins. */
|
|
78
113
|
function legacyEnvChecks(target, label) {
|
|
79
114
|
if (!target.envFile || !fs.existsSync(target.store.configPath))
|
|
@@ -205,6 +240,8 @@ export async function doctorCommand() {
|
|
|
205
240
|
emit(checkConfig(root, 'Root'));
|
|
206
241
|
for (const r of legacyEnvChecks(root, 'Root'))
|
|
207
242
|
emit(r);
|
|
243
|
+
for (const r of checkPackagesRoot(root.store, 'Root'))
|
|
244
|
+
emit(r);
|
|
208
245
|
// Database (root)
|
|
209
246
|
emit(checkDb(root.store, paths.defaultDb, 'Root'));
|
|
210
247
|
// C# bridge: the only write path; Windows-only.
|
|
@@ -246,6 +283,8 @@ export async function doctorCommand() {
|
|
|
246
283
|
emit(r);
|
|
247
284
|
for (const r of legacyEnvChecks(target, `Instance '${inst.name}'`))
|
|
248
285
|
emit(r);
|
|
286
|
+
for (const r of checkPackagesRoot(target.store, `Instance '${inst.name}'`))
|
|
287
|
+
emit(r);
|
|
249
288
|
emit(checkDb(target.store, resolve(inst.dir, 'data', 'xpp-metadata.db'), `Instance '${inst.name}'`));
|
|
250
289
|
if (isWindows && isXppConfigStale(target.store)) {
|
|
251
290
|
emit({
|
|
@@ -8,7 +8,7 @@ import * as fs from 'node:fs';
|
|
|
8
8
|
import { resolve } from 'node:path';
|
|
9
9
|
import { settingByPath, settingsInSection } from '../../config/settings.js';
|
|
10
10
|
import { pinBridgeExe } from '../bridgePath.js';
|
|
11
|
-
import {
|
|
11
|
+
import { maybePrepareCopilotInstructions } from '../copilotFiles.js';
|
|
12
12
|
import { createInstance, getInstance, listInstances, normalizeInstanceLayout, suggestPort } from '../instances.js';
|
|
13
13
|
import { mcpJsonNote, placementNote, stdioServer } from '../mcpJson.js';
|
|
14
14
|
import { selectXppConfig } from './config.js';
|
|
@@ -17,6 +17,7 @@ import { openInstanceStore, readPath, readSetting, saveStore, writeSetting } fro
|
|
|
17
17
|
import { instanceTarget } from '../target.js';
|
|
18
18
|
import { askConfirm, askSelect, askText, p } from '../ui.js';
|
|
19
19
|
import { listXppConfigs, xppConfigDir } from '../xppConfig.js';
|
|
20
|
+
import { findPackagesRoot } from '../../utils/packagesRoot.js';
|
|
20
21
|
import { rebuildIndex } from './indexCmd.js';
|
|
21
22
|
const dbPathSetting = settingByPath('index.dbPath');
|
|
22
23
|
const xppConfigNameSetting = settingByPath('environment.xppConfigName');
|
|
@@ -93,7 +94,10 @@ export async function instanceAddCommand(name, portArg) {
|
|
|
93
94
|
await selectXppConfig(store);
|
|
94
95
|
}
|
|
95
96
|
else {
|
|
96
|
-
await askSetting(store, settingByPath('environment.packagePath'), {
|
|
97
|
+
await askSetting(store, settingByPath('environment.packagePath'), {
|
|
98
|
+
required: true,
|
|
99
|
+
initial: findPackagesRoot() ?? undefined,
|
|
100
|
+
});
|
|
97
101
|
await askSetting(store, settingByPath('environment.customModels'), { required: true });
|
|
98
102
|
}
|
|
99
103
|
p.log.step('Workspace and naming');
|
|
@@ -108,14 +112,13 @@ export async function instanceAddCommand(name, portArg) {
|
|
|
108
112
|
// Copilot needs the instructions file just as much as it needs .mcp.json,
|
|
109
113
|
// and an instance is somebody's only setup run — asking here is the only
|
|
110
114
|
// chance scenario F gets.
|
|
111
|
-
|
|
115
|
+
await maybePrepareCopilotInstructions(String(readSetting(store, settingByPath('workspace.solutionsPath')) ?? ''));
|
|
112
116
|
// Both ways to reach this instance: the IDE spawning it over stdio with its
|
|
113
117
|
// own config, or an HTTP client on the port it was given.
|
|
114
118
|
mcpJsonNote({
|
|
115
119
|
[`d365fo-${instanceName}`]: stdioServer(store),
|
|
116
120
|
[`d365fo-${instanceName}-http`]: { url: `http://localhost:${port}/mcp/` },
|
|
117
121
|
}, `.mcp.json — keep the stdio entry OR the http one, not both`);
|
|
118
|
-
finalizeStagedCopilotFiles(copilotPlan);
|
|
119
122
|
placementNote();
|
|
120
123
|
p.note(`Config: ${store.configPath}\n\n` +
|
|
121
124
|
`1. Build the index: d365fo-mcp instance rebuild ${instanceName}\n` +
|
|
@@ -15,11 +15,12 @@ import { DOTNET_MISSING, dataRoot, installMode, isWindows, paths, repoRoot, setD
|
|
|
15
15
|
import { settingByPath, settingsInSection } from '../../config/settings.js';
|
|
16
16
|
import { commandExists, runExe, runShell } from '../exec.js';
|
|
17
17
|
import { pinBridgeExe } from '../bridgePath.js';
|
|
18
|
-
import {
|
|
18
|
+
import { maybePrepareCopilotInstructions } from '../copilotFiles.js';
|
|
19
19
|
import { mcpJsonNote, placementNote, stdioServer } from '../mcpJson.js';
|
|
20
20
|
import { checkRelease } from '../npmRegistry.js';
|
|
21
21
|
import { askAdvanced, askSecrets, askSetting, askSettings } from '../settingsPrompt.js';
|
|
22
22
|
import { migrateLegacyEnv, openStore, readSetting, saveStore, writeSetting } from '../settingsStore.js';
|
|
23
|
+
import { findPackagesRoot } from '../../utils/packagesRoot.js';
|
|
23
24
|
import { rootTarget } from '../target.js';
|
|
24
25
|
import { askConfirm, askSelect, askText, p, requireFullInstall } from '../ui.js';
|
|
25
26
|
import { listXppConfigs } from '../xppConfig.js';
|
|
@@ -188,7 +189,13 @@ async function configureEnvironment(store, scenario) {
|
|
|
188
189
|
}
|
|
189
190
|
return 'ude';
|
|
190
191
|
}
|
|
191
|
-
|
|
192
|
+
// Offer the AosService volume this machine actually has. Which drive that is
|
|
193
|
+
// differs per VM image (K:, C:, J:, …), and a wrong guess is what turns the
|
|
194
|
+
// rest of setup into "no namespaces found" (#769).
|
|
195
|
+
const detected = findPackagesRoot();
|
|
196
|
+
if (detected)
|
|
197
|
+
p.log.success(`Found PackagesLocalDirectory at ${detected}`);
|
|
198
|
+
await askSetting(store, setting('environment.packagePath'), { required: true, initial: detected ?? undefined });
|
|
192
199
|
await askSetting(store, setting('environment.customModels'), { required: true });
|
|
193
200
|
return 'traditional';
|
|
194
201
|
}
|
|
@@ -276,7 +283,7 @@ export async function setupCommand() {
|
|
|
276
283
|
const url = await askText({ message: 'Azure server URL', placeholder: 'https://your-server.azurewebsites.net/mcp/', required: true });
|
|
277
284
|
await configureEnvironment(store, scenario);
|
|
278
285
|
await configureWorkspace(store, scenario);
|
|
279
|
-
|
|
286
|
+
await maybePrepareCopilotInstructions(solutionsPath(store));
|
|
280
287
|
await configureNaming(store);
|
|
281
288
|
await askSecrets(store, ['behavior']);
|
|
282
289
|
await askAdvanced(store, ['environment', 'workspace', 'naming', 'bridge', 'behavior', 'server']);
|
|
@@ -286,7 +293,6 @@ export async function setupCommand() {
|
|
|
286
293
|
'd365fo-azure': { url },
|
|
287
294
|
'd365fo-local': stdioServer(store),
|
|
288
295
|
});
|
|
289
|
-
finalizeStagedCopilotFiles(copilotPlan);
|
|
290
296
|
placementNote();
|
|
291
297
|
p.outro('Hybrid setup complete — no local index needed (Azure serves the search).');
|
|
292
298
|
return;
|
|
@@ -295,7 +301,7 @@ export async function setupCommand() {
|
|
|
295
301
|
writeSetting(store, setting('server.mode'), 'full');
|
|
296
302
|
await configureEnvironment(store, scenario);
|
|
297
303
|
await configureWorkspace(store, scenario);
|
|
298
|
-
|
|
304
|
+
await maybePrepareCopilotInstructions(solutionsPath(store));
|
|
299
305
|
await configureNaming(store);
|
|
300
306
|
await configureIndex(store);
|
|
301
307
|
let port = Number(readSetting(store, setting('server.port')) ?? 8080);
|
|
@@ -313,7 +319,6 @@ export async function setupCommand() {
|
|
|
313
319
|
}
|
|
314
320
|
if (scenario === 'local-http') {
|
|
315
321
|
mcpJsonNote({ 'd365fo-mcp-tools': { url: `http://localhost:${port}/mcp/` } });
|
|
316
|
-
finalizeStagedCopilotFiles(copilotPlan);
|
|
317
322
|
placementNote();
|
|
318
323
|
p.outro('Done. Start the server with: d365fo-mcp start');
|
|
319
324
|
return;
|
|
@@ -321,7 +326,6 @@ export async function setupCommand() {
|
|
|
321
326
|
// D / E — the IDE spawns dist/index.js itself and is pointed at the config
|
|
322
327
|
// file; every other setting comes from there.
|
|
323
328
|
mcpJsonNote({ 'd365fo-mcp-tools': stdioServer(store) });
|
|
324
|
-
finalizeStagedCopilotFiles(copilotPlan);
|
|
325
329
|
placementNote();
|
|
326
330
|
p.outro('Done. VS spawns the server automatically — no manual start needed.');
|
|
327
331
|
}
|
|
@@ -1,15 +1,9 @@
|
|
|
1
|
-
export type CopilotFilePlan = {
|
|
2
|
-
stagingDir?: string;
|
|
3
|
-
};
|
|
4
1
|
/**
|
|
5
2
|
* Offer to place copilot-instructions.md, falling back to a staging folder.
|
|
6
3
|
*
|
|
7
4
|
* `solutionsPath` is what the user gave for workspace.solutionsPath; when it
|
|
8
|
-
* is empty there is nowhere to copy to, so the
|
|
9
|
-
*
|
|
10
|
-
* once the .mcp.json exists.
|
|
5
|
+
* is empty there is nowhere to copy to, so the file is staged instead and the
|
|
6
|
+
* README says where it has to end up.
|
|
11
7
|
*/
|
|
12
|
-
export declare function maybePrepareCopilotInstructions(solutionsPath: string): Promise<
|
|
13
|
-
/** Add the generated .mcp.json to the staging folder — call after mcpJsonNote(). */
|
|
14
|
-
export declare function finalizeStagedCopilotFiles(plan: CopilotFilePlan): void;
|
|
8
|
+
export declare function maybePrepareCopilotInstructions(solutionsPath: string): Promise<void>;
|
|
15
9
|
//# sourceMappingURL=copilotFiles.d.ts.map
|
package/dist/cli/copilotFiles.js
CHANGED
|
@@ -6,8 +6,12 @@
|
|
|
6
6
|
* built-in file tools and edits X++ behind the metadata provider's back. Both
|
|
7
7
|
* files therefore have to be placed for a working install, so the wizards
|
|
8
8
|
* offer to put this one down as well — either straight into the solutions
|
|
9
|
-
* folder, or, when that is unknown, into a staging folder
|
|
10
|
-
*
|
|
9
|
+
* folder, or, when that is unknown, into a staging folder with a README
|
|
10
|
+
* naming the two destinations.
|
|
11
|
+
*
|
|
12
|
+
* The README points at the one .mcp.json `mcpJsonNote()` writes in the data
|
|
13
|
+
* root rather than copying it: the staging folder sits in that same data
|
|
14
|
+
* root, so a second copy is only one more file to keep in sync.
|
|
11
15
|
*
|
|
12
16
|
* Shared by `setup` (scenarios B–E) and `instance add` (scenario F), which
|
|
13
17
|
* both end on the same two-file placement step.
|
|
@@ -28,9 +32,7 @@ function writeCopilotSetupReadme(targetDir, opts) {
|
|
|
28
32
|
'1. Copy .mcp.json',
|
|
29
33
|
' - Recommended (all solutions): %USERPROFILE%\\.mcp.json',
|
|
30
34
|
' - Per solution: place .mcp.json next to your .sln file',
|
|
31
|
-
|
|
32
|
-
? ' - Local copy prepared here: .mcp.json'
|
|
33
|
-
: ` - Local copy path: ${paths.mcpSuggestion}`,
|
|
35
|
+
` - Generated copy: ${paths.mcpSuggestion}`,
|
|
34
36
|
'',
|
|
35
37
|
'2. Copy .github/copilot-instructions.md',
|
|
36
38
|
opts.copilotAlreadyPlaced
|
|
@@ -46,16 +48,15 @@ function writeCopilotSetupReadme(targetDir, opts) {
|
|
|
46
48
|
* Offer to place copilot-instructions.md, falling back to a staging folder.
|
|
47
49
|
*
|
|
48
50
|
* `solutionsPath` is what the user gave for workspace.solutionsPath; when it
|
|
49
|
-
* is empty there is nowhere to copy to, so the
|
|
50
|
-
*
|
|
51
|
-
* once the .mcp.json exists.
|
|
51
|
+
* is empty there is nowhere to copy to, so the file is staged instead and the
|
|
52
|
+
* README says where it has to end up.
|
|
52
53
|
*/
|
|
53
54
|
export async function maybePrepareCopilotInstructions(solutionsPath) {
|
|
54
55
|
const source = copilotSource();
|
|
55
56
|
if (!fs.existsSync(source)) {
|
|
56
57
|
p.log.warn('Cannot find copilot-instructions.md in this installation; skipping copy helper.\n' +
|
|
57
58
|
` Looked for: ${source}`);
|
|
58
|
-
return
|
|
59
|
+
return;
|
|
59
60
|
}
|
|
60
61
|
const wantsDirectCopy = await askConfirm('Create/copy .github/copilot-instructions.md into the solutions folder now?', true);
|
|
61
62
|
const target = solutionsPath.trim();
|
|
@@ -63,16 +64,16 @@ export async function maybePrepareCopilotInstructions(solutionsPath) {
|
|
|
63
64
|
const targetDir = resolve(target, '.github');
|
|
64
65
|
fs.mkdirSync(targetDir, { recursive: true });
|
|
65
66
|
fs.copyFileSync(source, resolve(targetDir, 'copilot-instructions.md'));
|
|
66
|
-
writeCopilotSetupReadme(target, {
|
|
67
|
+
writeCopilotSetupReadme(target, { copilotAlreadyPlaced: true });
|
|
67
68
|
p.log.success(`Prepared: ${resolve(targetDir, 'copilot-instructions.md')}`);
|
|
68
69
|
p.log.success(`Prepared: ${resolve(target, 'README.md')}`);
|
|
69
|
-
return
|
|
70
|
+
return;
|
|
70
71
|
}
|
|
71
72
|
const stageDir = resolve(dataRoot(), 'copilot-setup');
|
|
72
73
|
const stageGitHubDir = resolve(stageDir, '.github');
|
|
73
74
|
fs.mkdirSync(stageGitHubDir, { recursive: true });
|
|
74
75
|
fs.copyFileSync(source, resolve(stageGitHubDir, 'copilot-instructions.md'));
|
|
75
|
-
writeCopilotSetupReadme(stageDir, {
|
|
76
|
+
writeCopilotSetupReadme(stageDir, { copilotAlreadyPlaced: false });
|
|
76
77
|
if (wantsDirectCopy && !target) {
|
|
77
78
|
p.log.warn('Solutions folder is empty, so files were prepared in the local staging folder instead.');
|
|
78
79
|
}
|
|
@@ -80,17 +81,5 @@ export async function maybePrepareCopilotInstructions(solutionsPath) {
|
|
|
80
81
|
p.log.info('Copy was skipped; files were prepared in a local staging folder for later use.');
|
|
81
82
|
}
|
|
82
83
|
p.log.info(`Staging folder: ${stageDir}`);
|
|
83
|
-
return { stagingDir: stageDir };
|
|
84
|
-
}
|
|
85
|
-
/** Add the generated .mcp.json to the staging folder — call after mcpJsonNote(). */
|
|
86
|
-
export function finalizeStagedCopilotFiles(plan) {
|
|
87
|
-
if (!plan.stagingDir)
|
|
88
|
-
return;
|
|
89
|
-
const localMcp = paths.mcpSuggestion;
|
|
90
|
-
if (fs.existsSync(localMcp)) {
|
|
91
|
-
fs.copyFileSync(localMcp, resolve(plan.stagingDir, '.mcp.json'));
|
|
92
|
-
writeCopilotSetupReadme(plan.stagingDir, { includeLocalMcpCopy: true, copilotAlreadyPlaced: false });
|
|
93
|
-
p.log.success(`Prepared: ${resolve(plan.stagingDir, '.mcp.json')}`);
|
|
94
|
-
}
|
|
95
84
|
}
|
|
96
85
|
//# sourceMappingURL=copilotFiles.js.map
|
|
@@ -107,9 +107,12 @@ export async function askSetting(store, setting, opts) {
|
|
|
107
107
|
return list;
|
|
108
108
|
}
|
|
109
109
|
default: {
|
|
110
|
+
// A stored answer always wins; the caller's suggestion only fills an
|
|
111
|
+
// empty field, so re-running setup never overwrites a deliberate value
|
|
112
|
+
// with something merely detected.
|
|
110
113
|
const raw = await askText({
|
|
111
114
|
message: message(setting, required ? '' : c.dim('(Enter to skip)')),
|
|
112
|
-
initialValue: initialText(store, setting),
|
|
115
|
+
initialValue: initialText(store, setting) || opts?.initial || '',
|
|
113
116
|
placeholder: setting.placeholder,
|
|
114
117
|
required,
|
|
115
118
|
});
|
package/dist/config/settings.js
CHANGED
|
@@ -81,7 +81,9 @@ export const SETTINGS = [
|
|
|
81
81
|
type: 'path',
|
|
82
82
|
label: 'Packages root (PackagesLocalDirectory)',
|
|
83
83
|
description: 'AOT packages folder (PackagesLocalDirectory) used as the read-only source for indexing. ' +
|
|
84
|
-
'Machine-wide on a traditional VM; UDE resolves it from the XPP config instead.'
|
|
84
|
+
'Machine-wide on a traditional VM; UDE resolves it from the XPP config instead. ' +
|
|
85
|
+
'Left empty, the server scans the machine\'s drives for AosService\\PackagesLocalDirectory — ' +
|
|
86
|
+
'which volume that is depends on the VM image (K:, C:, J:, …).',
|
|
85
87
|
placeholder: 'C:\\AOSService\\PackagesLocalDirectory',
|
|
86
88
|
},
|
|
87
89
|
{
|