godot-cli 0.13.3 → 0.15.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.
@@ -1,3 +1,4 @@
1
+ import type { ExtensionDescriptor } from '../utils/extensions-catalog.js';
1
2
  /**
2
3
  * Discriminated union describing every progress event the library can emit to
3
4
  * the optional `onProgress` callback. Consumers narrow on `event.phase`.
@@ -360,11 +361,11 @@ export interface AddonMaterializeOutcome {
360
361
  /** The local source directory used (`--source` path only). */
361
362
  sourceDir?: string;
362
363
  }
363
- /** What `install-plugin` did to the consumer `.csproj`'s PackageReferences. */
364
+ /** What `install-plugin` did to the consumer `.csproj`'s PackageReferences + EmbeddedResources. */
364
365
  export interface CsprojPatchOutcome {
365
366
  /** Absolute path to the consumer `.csproj` that was patched, if one was found. */
366
367
  csprojPath?: string;
367
- /** True when the csproj text was modified. */
368
+ /** True when the csproj text was modified (pins and/or embeds). */
368
369
  changed: boolean;
369
370
  /** Per-package summary: each addon pin's add / update / unchanged outcome. */
370
371
  packages: {
@@ -372,6 +373,12 @@ export interface CsprojPatchOutcome {
372
373
  action: 'added' | 'updated' | 'unchanged';
373
374
  version: string;
374
375
  }[];
376
+ /** Per-resource summary: each addon `<EmbeddedResource>`'s add / update / unchanged outcome. */
377
+ embeds: {
378
+ include: string;
379
+ action: 'added' | 'updated' | 'unchanged';
380
+ logicalName: string;
381
+ }[];
375
382
  }
376
383
  export interface InstallPluginSuccess {
377
384
  kind: 'success';
@@ -415,3 +422,57 @@ export interface RemovePluginFailure {
415
422
  error: Error;
416
423
  }
417
424
  export type RemovePluginResult = RemovePluginSuccess | RemovePluginFailure;
425
+ /**
426
+ * What `installExtension` did to the consumer `.csproj` — the CLI mirror of the dock's
427
+ * `ExtensionInstallOutcome`:
428
+ * - `added` — the `<PackageReference>` was newly added (rebuild required).
429
+ * - `updated` — its version was bumped up (rebuild required).
430
+ * - `already-up-to-date`— present at an equal/newer version, or the descriptor has no pin (no write).
431
+ * - `no-project` — no consumer `.csproj` was found to install into (nothing written).
432
+ */
433
+ export type ExtensionInstallOutcome = 'added' | 'updated' | 'already-up-to-date' | 'no-project';
434
+ export interface InstallExtensionOptions {
435
+ /** Absolute or relative path to the Godot project root (holds `project.godot` + the consumer `.csproj`). */
436
+ godotProjectPath: string;
437
+ /** The extension `<id>` to install — matched against the catalog by `packageId` (then `name`), case-insensitive. */
438
+ extensionId: string;
439
+ /** Override the catalog's pinned version for this install (the `--version` flag). Ignored when empty. */
440
+ version?: string;
441
+ /**
442
+ * Catalog to resolve `extensionId` against. Defaults to the bundled `EXTENSIONS_CATALOG`
443
+ * (single-sourced from `addons/godot_mcp/extensions.catalog.json`). Injectable for tests.
444
+ */
445
+ catalog?: readonly ExtensionDescriptor[];
446
+ onProgress?: ProgressCallback;
447
+ }
448
+ export interface InstallExtensionSuccess {
449
+ kind: 'success';
450
+ success: true;
451
+ /** The classified outcome (added / updated / already-up-to-date / no-project). */
452
+ outcome: ExtensionInstallOutcome;
453
+ /** True when the `.csproj` was written (added or updated). */
454
+ changed: boolean;
455
+ /** True when a write happened, so the consumer must rebuild solutions (Godot has no programmatic restore). */
456
+ rebuildRequired: boolean;
457
+ /** The user-supplied id that resolved to the descriptor. */
458
+ extensionId: string;
459
+ /** The resolved descriptor's package id (the `<PackageReference Include>`). */
460
+ packageId: string;
461
+ /** Version currently referenced before this install: `null` when not installed, `''` when referenced unversioned. */
462
+ fromVersion: string | null;
463
+ /** The version this install targeted (catalog pin or `--version` override), or `null` when unpinned. */
464
+ toVersion: string | null;
465
+ /** Absolute path to the consumer `.csproj`, when one was located. */
466
+ csprojPath?: string;
467
+ /** A short human-readable status line, safe to print. */
468
+ message: string;
469
+ warnings: string[];
470
+ }
471
+ export interface InstallExtensionFailure {
472
+ kind: 'failure';
473
+ success: false;
474
+ projectGodotPath?: string;
475
+ warnings: string[];
476
+ error: Error;
477
+ }
478
+ export type InstallExtensionResult = InstallExtensionSuccess | InstallExtensionFailure;
package/dist/lib.d.ts CHANGED
@@ -5,4 +5,7 @@ export { runTool, runSystemTool } from './lib/run-tool.js';
5
5
  export { setupMcp, listAgentIds } from './lib/setup-mcp.js';
6
6
  export { setupSkills } from './lib/setup-skills.js';
7
7
  export { installPlugin, removePlugin } from './lib/install-plugin.js';
8
- export type { ProgressEvent, ProgressCallback, ResultKind, OpenProjectOptions, OpenProjectResult, OpenProjectSuccess, OpenProjectFailure, OpenProjectAuthOption, OpenProjectConnectionMode, BuildProjectOptions, BuildProjectResult, BuildProjectSuccess, BuildProjectFailure, BuildSkipReason, CreateProjectOptions, CreateProjectResult, CreateProjectSuccess, CreateProjectFailure, RunToolOptions, RunToolResult, RunToolSuccess, RunToolFailure, RunToolFailureReason, RunSystemToolOptions, RunSystemToolResult, RunSystemToolSuccess, RunSystemToolFailure, SetupMcpOptions, SetupMcpResult, SetupMcpSuccess, SetupMcpFailure, SetupSkillsOptions, SetupSkillsResult, SetupSkillsSuccess, SetupSkillsFailure, InstallPluginOptions, InstallPluginResult, InstallPluginSuccess, InstallPluginFailure, AddonMaterializeOutcome, CsprojPatchOutcome, RemovePluginOptions, RemovePluginResult, RemovePluginSuccess, RemovePluginFailure, } from './lib/types.js';
8
+ export { installExtension } from './lib/install-extension.js';
9
+ export { EXTENSIONS_CATALOG, findExtension, hasVersion } from './utils/extensions-catalog.js';
10
+ export type { ExtensionDescriptor, ExtensionTool } from './utils/extensions-catalog.js';
11
+ export type { ProgressEvent, ProgressCallback, ResultKind, OpenProjectOptions, OpenProjectResult, OpenProjectSuccess, OpenProjectFailure, OpenProjectAuthOption, OpenProjectConnectionMode, BuildProjectOptions, BuildProjectResult, BuildProjectSuccess, BuildProjectFailure, BuildSkipReason, CreateProjectOptions, CreateProjectResult, CreateProjectSuccess, CreateProjectFailure, RunToolOptions, RunToolResult, RunToolSuccess, RunToolFailure, RunToolFailureReason, RunSystemToolOptions, RunSystemToolResult, RunSystemToolSuccess, RunSystemToolFailure, SetupMcpOptions, SetupMcpResult, SetupMcpSuccess, SetupMcpFailure, SetupSkillsOptions, SetupSkillsResult, SetupSkillsSuccess, SetupSkillsFailure, InstallPluginOptions, InstallPluginResult, InstallPluginSuccess, InstallPluginFailure, AddonMaterializeOutcome, CsprojPatchOutcome, RemovePluginOptions, RemovePluginResult, RemovePluginSuccess, RemovePluginFailure, InstallExtensionOptions, InstallExtensionResult, InstallExtensionSuccess, InstallExtensionFailure, ExtensionInstallOutcome, } from './lib/types.js';
package/dist/lib.js CHANGED
@@ -19,4 +19,8 @@ export { runTool, runSystemTool } from './lib/run-tool.js';
19
19
  export { setupMcp, listAgentIds } from './lib/setup-mcp.js';
20
20
  export { setupSkills } from './lib/setup-skills.js';
21
21
  export { installPlugin, removePlugin } from './lib/install-plugin.js';
22
+ export { installExtension } from './lib/install-extension.js';
23
+ // Shared extension catalog (single-sourced from addons/godot_mcp/extensions.catalog.json)
24
+ // + its lookup helpers, so the app can render the same list the dock + CLI install from.
25
+ export { EXTENSIONS_CATALOG, findExtension, hasVersion } from './utils/extensions-catalog.js';
22
26
  //# sourceMappingURL=lib.js.map
package/dist/lib.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"lib.js","sourceRoot":"","sources":["../src/lib.ts"],"names":[],"mappings":"AAAA,uCAAuC;AACvC,EAAE;AACF,eAAe;AACf,+EAA+E;AAC/E,wDAAwD;AACxD,oDAAoD;AACpD,yEAAyE;AACzE,4DAA4D;AAC5D,+EAA+E;AAC/E,gEAAgE;AAChE,6EAA6E;AAC7E,EAAE;AACF,0EAA0E;AAC1E,4CAA4C;AAE5C,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AACxD,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAC3D,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAC5D,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACpD,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC"}
1
+ {"version":3,"file":"lib.js","sourceRoot":"","sources":["../src/lib.ts"],"names":[],"mappings":"AAAA,uCAAuC;AACvC,EAAE;AACF,eAAe;AACf,+EAA+E;AAC/E,wDAAwD;AACxD,oDAAoD;AACpD,yEAAyE;AACzE,4DAA4D;AAC5D,+EAA+E;AAC/E,gEAAgE;AAChE,6EAA6E;AAC7E,EAAE;AACF,0EAA0E;AAC1E,4CAA4C;AAE5C,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AACxD,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAC3D,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAC5D,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACpD,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AACtE,OAAO,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AAE9D,0FAA0F;AAC1F,yFAAyF;AACzF,OAAO,EAAE,kBAAkB,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC"}
@@ -5,11 +5,34 @@ export interface AddonPackageReference {
5
5
  /** The pinned version (the `Version=` attribute). */
6
6
  version: string;
7
7
  }
8
+ /**
9
+ * A single `<EmbeddedResource>` the consumer csproj must declare so an asset the
10
+ * addon reads at editor runtime via `GetManifestResourceStream` is embedded into
11
+ * the consumer's compiled project assembly (Godot globs every `*.cs` into one
12
+ * assembly, but does NOT carry the addon's own `Godot-MCP.csproj` `<EmbeddedResource>`
13
+ * — the addon ships as source, not a NuGet package).
14
+ */
15
+ export interface AddonEmbeddedResource {
16
+ /** The file to embed, relative to the consumer project root (the `Include=` attribute). */
17
+ include: string;
18
+ /** The manifest-resource name the addon resolves it by (the `LogicalName=` attribute). */
19
+ logicalName: string;
20
+ }
8
21
  /**
9
22
  * The exact NuGet `PackageReference`s the consumer's project `.csproj` needs,
10
23
  * single-sourced from the addon's `Godot-MCP.csproj` pins. Order matches the
11
24
  * addon csproj (ReflectorNet first, then McpPlugin).
12
25
  */
13
26
  export declare const ADDON_PACKAGE_REFERENCES: readonly AddonPackageReference[];
27
+ /**
28
+ * The exact `<EmbeddedResource>`s the consumer's project `.csproj` needs, single-sourced
29
+ * from the addon's `Godot-MCP.csproj`. The extension catalog is read at editor runtime by
30
+ * the pure-managed `GodotExtensionRegistry` via `GetManifestResourceStream` (no `res://` /
31
+ * filesystem fallback), so without this embed a consumer that installs via `install-plugin`
32
+ * gets an EMPTY Extensions panel. A parity test (`cli/tests/addon-deps-parity.test.ts`)
33
+ * reads `Godot-MCP.csproj` and FAILS the build if these constants ever drift from the addon's
34
+ * own `<EmbeddedResource>` (Include path + LogicalName), mirroring the NuGet-pin tripwire.
35
+ */
36
+ export declare const ADDON_EMBEDDED_RESOURCES: readonly AddonEmbeddedResource[];
14
37
  /** The resource path of the addon manifest a consumer project loads the plugin from. */
15
38
  export declare const ADDON_PLUGIN_CFG_RESOURCE = "res://addons/godot_mcp/plugin.cfg";
@@ -22,6 +22,21 @@ export const ADDON_PACKAGE_REFERENCES = [
22
22
  { id: 'com.IvanMurzak.ReflectorNet', version: '5.3.1' },
23
23
  { id: 'com.IvanMurzak.McpPlugin', version: '6.10.0' },
24
24
  ];
25
+ /**
26
+ * The exact `<EmbeddedResource>`s the consumer's project `.csproj` needs, single-sourced
27
+ * from the addon's `Godot-MCP.csproj`. The extension catalog is read at editor runtime by
28
+ * the pure-managed `GodotExtensionRegistry` via `GetManifestResourceStream` (no `res://` /
29
+ * filesystem fallback), so without this embed a consumer that installs via `install-plugin`
30
+ * gets an EMPTY Extensions panel. A parity test (`cli/tests/addon-deps-parity.test.ts`)
31
+ * reads `Godot-MCP.csproj` and FAILS the build if these constants ever drift from the addon's
32
+ * own `<EmbeddedResource>` (Include path + LogicalName), mirroring the NuGet-pin tripwire.
33
+ */
34
+ export const ADDON_EMBEDDED_RESOURCES = [
35
+ {
36
+ include: 'addons/godot_mcp/extensions.catalog.json',
37
+ logicalName: 'Godot-MCP.extensions.catalog.json',
38
+ },
39
+ ];
25
40
  /** The resource path of the addon manifest a consumer project loads the plugin from. */
26
41
  export const ADDON_PLUGIN_CFG_RESOURCE = 'res://addons/godot_mcp/plugin.cfg';
27
42
  //# sourceMappingURL=addon-deps.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"addon-deps.js","sourceRoot":"","sources":["../../src/utils/addon-deps.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,mFAAmF;AACnF,oEAAoE;AACpE,8EAA8E;AAC9E,yEAAyE;AACzE,EAAE;AACF,6EAA6E;AAC7E,kFAAkF;AAClF,8EAA8E;AAC9E,4EAA4E;AAC5E,iFAAiF;AACjF,iFAAiF;AACjF,+CAA+C;AAC/C,EAAE;AACF,sEAAsE;AAUtE;;;;GAIG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAqC;IACxE,EAAE,EAAE,EAAE,6BAA6B,EAAE,OAAO,EAAE,OAAO,EAAE;IACvD,EAAE,EAAE,EAAE,0BAA0B,EAAE,OAAO,EAAE,QAAQ,EAAE;CAC7C,CAAC;AAEX,wFAAwF;AACxF,MAAM,CAAC,MAAM,yBAAyB,GAAG,mCAAmC,CAAC"}
1
+ {"version":3,"file":"addon-deps.js","sourceRoot":"","sources":["../../src/utils/addon-deps.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,mFAAmF;AACnF,oEAAoE;AACpE,8EAA8E;AAC9E,yEAAyE;AACzE,EAAE;AACF,6EAA6E;AAC7E,kFAAkF;AAClF,8EAA8E;AAC9E,4EAA4E;AAC5E,iFAAiF;AACjF,iFAAiF;AACjF,+CAA+C;AAC/C,EAAE;AACF,sEAAsE;AAwBtE;;;;GAIG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAqC;IACxE,EAAE,EAAE,EAAE,6BAA6B,EAAE,OAAO,EAAE,OAAO,EAAE;IACvD,EAAE,EAAE,EAAE,0BAA0B,EAAE,OAAO,EAAE,QAAQ,EAAE;CAC7C,CAAC;AAEX;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAqC;IACxE;QACE,OAAO,EAAE,0CAA0C;QACnD,WAAW,EAAE,mCAAmC;KACjD;CACO,CAAC;AAEX,wFAAwF;AACxF,MAAM,CAAC,MAAM,yBAAyB,GAAG,mCAAmC,CAAC"}
@@ -1,4 +1,4 @@
1
- import { type AddonPackageReference } from './addon-deps.js';
1
+ import { type AddonPackageReference, type AddonEmbeddedResource } from './addon-deps.js';
2
2
  /**
3
3
  * Idempotently reconcile a Godot consumer `.csproj`'s `<PackageReference>`s so it
4
4
  * declares exactly the addon's two required NuGet pins (see `addon-deps.ts`).
@@ -37,4 +37,40 @@ export interface CsprojPatchResult {
37
37
  /** One entry per required package describing what happened to it. */
38
38
  changes: CsprojPatchChange[];
39
39
  }
40
+ export type CsprojEmbedChange = {
41
+ include: string;
42
+ action: 'added';
43
+ logicalName: string;
44
+ } | {
45
+ include: string;
46
+ action: 'updated';
47
+ from: string;
48
+ logicalName: string;
49
+ } | {
50
+ include: string;
51
+ action: 'unchanged';
52
+ logicalName: string;
53
+ };
54
+ export interface CsprojEmbedResult {
55
+ /** The new csproj text (identical to input when nothing changed). */
56
+ text: string;
57
+ /** True when the text was modified. */
58
+ changed: boolean;
59
+ /** One entry per required embedded resource describing what happened to it. */
60
+ changes: CsprojEmbedChange[];
61
+ }
40
62
  export declare function addAddonPackageReferences(csprojText: string, requiredRefs?: readonly AddonPackageReference[]): CsprojPatchResult;
63
+ /**
64
+ * Idempotently reconcile a Godot consumer `.csproj`'s `<EmbeddedResource>`s so it
65
+ * declares exactly the addon's required embeds (see `addon-deps.ts`
66
+ * `ADDON_EMBEDDED_RESOURCES`). Pure (operates on csproj TEXT, never touches the
67
+ * filesystem) and exported for unit tests. Sibling of `addAddonPackageReferences`.
68
+ * Handles, idempotently:
69
+ * - a project with NO `<EmbeddedResource>` ItemGroup (groups into one, else appends
70
+ * a fresh `<ItemGroup>` before `</Project>`);
71
+ * - a project that already embeds the resource with the right LogicalName (no change);
72
+ * - a project that embeds the same Include with a DIFFERENT LogicalName (reconciles
73
+ * in place);
74
+ * Re-running `install-plugin` therefore never duplicates the `<EmbeddedResource>`.
75
+ */
76
+ export declare function addAddonEmbeddedResources(csprojText: string, requiredResources?: readonly AddonEmbeddedResource[]): CsprojEmbedResult;
@@ -1,4 +1,4 @@
1
- import { ADDON_PACKAGE_REFERENCES } from './addon-deps.js';
1
+ import { ADDON_PACKAGE_REFERENCES, ADDON_EMBEDDED_RESOURCES, } from './addon-deps.js';
2
2
  /** Escape a string for use inside a RegExp source. */
3
3
  function escapeRegExp(value) {
4
4
  return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
@@ -18,16 +18,32 @@ function packageRefRegex(id) {
18
18
  `|` +
19
19
  `<PackageReference\\b[^>]*?\\bVersion\\s*=\\s*"([^"]*)"[^>]*?\\bInclude\\s*=\\s*"${escId}"[^>]*?(?:/>|></PackageReference>)`, 'i');
20
20
  }
21
+ /**
22
+ * Match an existing `<EmbeddedResource ... Include="<include>" ... />` element for a
23
+ * specific file path, capturing the whole element (in either attribute order, and
24
+ * whether self-closed `/>` or an explicit `></EmbeddedResource>` pair). The
25
+ * `LogicalName` is extracted from the captured element text separately so the regex
26
+ * stays order-agnostic and tolerant of an absent `LogicalName`.
27
+ */
28
+ function embeddedResourceRegex(include) {
29
+ const escInclude = escapeRegExp(include);
30
+ return new RegExp(`<EmbeddedResource\\b[^>]*?\\bInclude\\s*=\\s*"${escInclude}"[^>]*?(?:/>|></EmbeddedResource>)`, 'i');
31
+ }
21
32
  /** Render the canonical self-closing element for a required reference. */
22
33
  function renderRef(ref) {
23
34
  return `<PackageReference Include="${ref.id}" Version="${ref.version}" />`;
24
35
  }
36
+ /** Render the canonical self-closing element for a required embedded resource. */
37
+ function renderEmbed(res) {
38
+ return `<EmbeddedResource Include="${res.include}" LogicalName="${res.logicalName}" />`;
39
+ }
25
40
  /**
26
- * Detect the indentation used by the FIRST existing `<PackageReference>` (or fall
27
- * back to 4 spaces) so appended/inserted elements match the consumer's style.
41
+ * Detect the indentation used by the FIRST existing `<PackageReference>` /
42
+ * `<EmbeddedResource>` (or fall back to 4 spaces) so appended/inserted elements
43
+ * match the consumer's style.
28
44
  */
29
45
  function detectIndent(text) {
30
- const m = text.match(/\n([ \t]*)<PackageReference\b/);
46
+ const m = text.match(/\n([ \t]*)<(?:PackageReference|EmbeddedResource)\b/);
31
47
  return m ? m[1] : ' ';
32
48
  }
33
49
  /**
@@ -101,16 +117,99 @@ export function addAddonPackageReferences(csprojText, requiredRefs = ADDON_PACKA
101
117
  changes: orderedChanges,
102
118
  };
103
119
  }
120
+ /**
121
+ * Idempotently reconcile a Godot consumer `.csproj`'s `<EmbeddedResource>`s so it
122
+ * declares exactly the addon's required embeds (see `addon-deps.ts`
123
+ * `ADDON_EMBEDDED_RESOURCES`). Pure (operates on csproj TEXT, never touches the
124
+ * filesystem) and exported for unit tests. Sibling of `addAddonPackageReferences`.
125
+ * Handles, idempotently:
126
+ * - a project with NO `<EmbeddedResource>` ItemGroup (groups into one, else appends
127
+ * a fresh `<ItemGroup>` before `</Project>`);
128
+ * - a project that already embeds the resource with the right LogicalName (no change);
129
+ * - a project that embeds the same Include with a DIFFERENT LogicalName (reconciles
130
+ * in place);
131
+ * Re-running `install-plugin` therefore never duplicates the `<EmbeddedResource>`.
132
+ */
133
+ export function addAddonEmbeddedResources(csprojText, requiredResources = ADDON_EMBEDDED_RESOURCES) {
134
+ const changes = [];
135
+ let text = csprojText;
136
+ const indent = detectIndent(text);
137
+ const eol = detectEol(text);
138
+ // First pass: reconcile any embeds that already exist (possibly with a different
139
+ // LogicalName). Collect the ones still missing for the append pass.
140
+ const missing = [];
141
+ for (const res of requiredResources) {
142
+ const re = embeddedResourceRegex(res.include);
143
+ const match = text.match(re);
144
+ if (!match) {
145
+ missing.push(res);
146
+ continue;
147
+ }
148
+ const logicalMatch = match[0].match(/\bLogicalName\s*=\s*"([^"]*)"/i);
149
+ const existingLogical = logicalMatch ? logicalMatch[1] : '';
150
+ if (existingLogical === res.logicalName) {
151
+ changes.push({ include: res.include, action: 'unchanged', logicalName: res.logicalName });
152
+ continue;
153
+ }
154
+ // Reconcile in place: replace the whole element with the canonical embed.
155
+ text = text.replace(re, renderEmbed(res));
156
+ changes.push({ include: res.include, action: 'updated', from: existingLogical, logicalName: res.logicalName });
157
+ }
158
+ // Second pass: add the missing embeds. Prefer an existing <ItemGroup> that already
159
+ // holds an <EmbeddedResource> (group them together); else append a fresh
160
+ // <ItemGroup> just before </Project>. Deliberately NOT grouped with the
161
+ // PackageReference ItemGroup — the addon's own csproj keeps the two in separate
162
+ // groups, and this keeps the scaffold's layout identical.
163
+ if (missing.length > 0) {
164
+ const block = missing.map((res) => `${indent}${renderEmbed(res)}`).join(eol);
165
+ const embedItemGroupClose = findItemGroupCloseContaining(text, /<EmbeddedResource\b/i);
166
+ if (embedItemGroupClose !== -1) {
167
+ text = `${text.slice(0, embedItemGroupClose)}${block}${eol}${text.slice(embedItemGroupClose)}`;
168
+ }
169
+ else {
170
+ const groupIndent = indent.length >= 2 ? indent.slice(0, Math.floor(indent.length / 2)) : ' ';
171
+ const newGroup = `${groupIndent}<ItemGroup>${eol}${block}${eol}${groupIndent}</ItemGroup>${eol}`;
172
+ const closeIdx = text.lastIndexOf('</Project>');
173
+ if (closeIdx === -1) {
174
+ text = `${text}${eol}${newGroup}`;
175
+ }
176
+ else {
177
+ text = `${text.slice(0, closeIdx)}${newGroup}${text.slice(closeIdx)}`;
178
+ }
179
+ }
180
+ for (const res of missing) {
181
+ changes.push({ include: res.include, action: 'added', logicalName: res.logicalName });
182
+ }
183
+ }
184
+ // Restore required-order in `changes` to match `requiredResources` for stable output.
185
+ const orderedChanges = requiredResources
186
+ .map((res) => changes.find((c) => c.include === res.include))
187
+ .filter((c) => c !== undefined);
188
+ return {
189
+ text,
190
+ changed: orderedChanges.some((c) => c.action !== 'unchanged'),
191
+ changes: orderedChanges,
192
+ };
193
+ }
104
194
  /**
105
195
  * Return the index of the `</ItemGroup>` that closes the FIRST `<ItemGroup>`
106
196
  * containing a `<PackageReference>`, or -1 when none exists. Used to group the
107
197
  * added pins with the consumer's existing package references.
108
198
  */
109
199
  function findPackageReferenceItemGroupClose(text) {
200
+ return findItemGroupCloseContaining(text, /<PackageReference\b/i);
201
+ }
202
+ /**
203
+ * Return the index of the `</ItemGroup>` that closes the FIRST `<ItemGroup>` whose
204
+ * body matches `childTagRe`, or -1 when none exists. Used to group an added element
205
+ * with the consumer's existing items of the same kind. `childTagRe` MUST NOT be a
206
+ * global regex (a stateful `lastIndex` would corrupt the `.test` below).
207
+ */
208
+ function findItemGroupCloseContaining(text, childTagRe) {
110
209
  const itemGroupRe = /<ItemGroup\b[^>]*>([\s\S]*?)<\/ItemGroup>/gi;
111
210
  let m;
112
211
  while ((m = itemGroupRe.exec(text)) !== null) {
113
- if (/<PackageReference\b/i.test(m[1])) {
212
+ if (childTagRe.test(m[1])) {
114
213
  // The close tag starts at the end of the match minus its length.
115
214
  const closeTag = '</ItemGroup>';
116
215
  return m.index + m[0].length - closeTag.length;
@@ -1 +1 @@
1
- {"version":3,"file":"csproj-deps.js","sourceRoot":"","sources":["../../src/utils/csproj-deps.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"csproj-deps.js","sourceRoot":"","sources":["../../src/utils/csproj-deps.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,47 @@
1
+ import type { ExtensionDescriptor } from './extensions-catalog.js';
2
+ export type ExtensionInstallAction = 'add' | 'update' | 'noop';
3
+ /**
4
+ * The MSBuild floating-version marker written for an UNPINNED (null/empty-version) descriptor.
5
+ * A versionless `<PackageReference Include="x" />` fails NuGet restore with NU1015, so the
6
+ * "float to latest" intent is materialized as `Version="*"`. Mirrors C# `ExtensionInstallPlanner.FloatVersion`.
7
+ */
8
+ export declare const FLOAT_VERSION = "*";
9
+ export interface ExtensionInstallPlan {
10
+ /** The add / update / no-op decision. */
11
+ action: ExtensionInstallAction;
12
+ /** The full `.csproj` text after applying the action (=== input for a no-op). */
13
+ resultingCsproj: string;
14
+ /** The package id the plan targets. */
15
+ packageId: string;
16
+ /** Version currently referenced: `null` when not installed; `''` when referenced without a version. */
17
+ fromVersion: string | null;
18
+ /** The version the plan writes: the descriptor's pin when set, `"*"` (the float marker) when unpinned and a reference is added or self-healed, `null` only on a no-op. */
19
+ toVersion: string | null;
20
+ }
21
+ /** Thrown by {@link planExtensionInstall} on a genuinely unparseable `.csproj` (the lib turns it into a structured failure). */
22
+ export declare class CsprojParseError extends Error {
23
+ }
24
+ /**
25
+ * Compare two dotted numeric version strings component-by-component as integers
26
+ * (so `1.10.0` > `1.2.0`, unlike an ordinal string compare). A trailing pre-release
27
+ * / build suffix on a component (`1.0.0-rc1`) is tolerated — only the leading integer
28
+ * of each component is read; a non-numeric component reads as 0. Missing trailing
29
+ * components are treated as 0 (`1.0` === `1.0.0`). Returns >0 when `a` is newer, <0
30
+ * when older, 0 when equal. Exact port of C# `InstalledStateDetector.CompareVersions`.
31
+ */
32
+ export declare function compareVersions(a: string, b: string): number;
33
+ /**
34
+ * Parse every `<PackageReference>` in the `.csproj` into a `{ packageId → version }`
35
+ * map (ids lower-cased — NuGet ids are case-insensitive; value is the `Version`
36
+ * attribute, else a child `<Version>` element, else `''`). Returns an EMPTY map for
37
+ * empty/whitespace input. Port of C# `InstalledStateDetector.ParsePackageReferences`
38
+ * (the CLI never needs the bad-XML tolerance branch — the planner validates first).
39
+ */
40
+ export declare function parsePackageReferences(csprojText: string | null | undefined): Map<string, string>;
41
+ /**
42
+ * Compute the install plan for `descriptor` against `csprojText`. Port of
43
+ * `ExtensionInstallPlanner.Plan`. Throws {@link CsprojParseError} only on a genuinely
44
+ * malformed `.csproj` (no recognizable `<Project ...>` root) — the caller maps that to
45
+ * a structured failure rather than corrupting the file.
46
+ */
47
+ export declare function planExtensionInstall(descriptor: ExtensionDescriptor, csprojText: string): ExtensionInstallPlan;