opkg 0.9.2 → 0.9.3

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.
Files changed (140) hide show
  1. package/package.json +3 -1
  2. package/packages/cli/dist/add-U44SL3OR.js +624 -0
  3. package/packages/cli/dist/add-U44SL3OR.js.map +7 -0
  4. package/packages/cli/dist/chunk-4B5HJLP2.js +48 -0
  5. package/packages/cli/dist/chunk-4B5HJLP2.js.map +7 -0
  6. package/packages/cli/dist/chunk-BROJ6OUT.js +631 -0
  7. package/packages/cli/dist/chunk-BROJ6OUT.js.map +7 -0
  8. package/packages/cli/dist/chunk-CVA64SXK.js +1136 -0
  9. package/packages/cli/dist/chunk-CVA64SXK.js.map +7 -0
  10. package/packages/cli/dist/chunk-D6LEPODL.js +413 -0
  11. package/packages/cli/dist/chunk-D6LEPODL.js.map +7 -0
  12. package/packages/cli/dist/chunk-HTYHJA3B.js +61 -0
  13. package/packages/cli/dist/chunk-HTYHJA3B.js.map +7 -0
  14. package/packages/cli/dist/chunk-KI7FDU3H.js +99 -0
  15. package/packages/cli/dist/chunk-KI7FDU3H.js.map +7 -0
  16. package/packages/cli/dist/chunk-PSQXKAL4.js +371 -0
  17. package/packages/cli/dist/chunk-PSQXKAL4.js.map +7 -0
  18. package/packages/cli/dist/chunk-PUDRKDVZ.js +1419 -0
  19. package/packages/cli/dist/chunk-PUDRKDVZ.js.map +7 -0
  20. package/packages/cli/dist/chunk-U7FW7SXX.js +568 -0
  21. package/packages/cli/dist/chunk-U7FW7SXX.js.map +7 -0
  22. package/packages/cli/dist/configure-3AZUMDJZ.js +107 -0
  23. package/packages/cli/dist/configure-3AZUMDJZ.js.map +7 -0
  24. package/packages/cli/dist/index.js +15 -15
  25. package/packages/cli/dist/install-EZNWMLJR.js +7581 -0
  26. package/packages/cli/dist/install-EZNWMLJR.js.map +7 -0
  27. package/packages/cli/dist/list-XR7RSJFS.js +327 -0
  28. package/packages/cli/dist/list-XR7RSJFS.js.map +7 -0
  29. package/packages/cli/dist/login-NRKHXZKM.js +150 -0
  30. package/packages/cli/dist/login-NRKHXZKM.js.map +7 -0
  31. package/packages/cli/dist/logout-SYHXCVCQ.js +40 -0
  32. package/packages/cli/dist/logout-SYHXCVCQ.js.map +7 -0
  33. package/packages/cli/dist/new-F46OSD72.js +277 -0
  34. package/packages/cli/dist/new-F46OSD72.js.map +7 -0
  35. package/packages/cli/dist/publish-4H43PCSG.js +619 -0
  36. package/packages/cli/dist/publish-4H43PCSG.js.map +7 -0
  37. package/packages/cli/dist/remove-BD52BHR2.js +542 -0
  38. package/packages/cli/dist/remove-BD52BHR2.js.map +7 -0
  39. package/packages/cli/dist/save-N3QWF2WN.js +1728 -0
  40. package/packages/cli/dist/save-N3QWF2WN.js.map +7 -0
  41. package/packages/cli/dist/search-ABROK3UO.js +157 -0
  42. package/packages/cli/dist/search-ABROK3UO.js.map +7 -0
  43. package/packages/cli/dist/set-NGM2FIKF.js +251 -0
  44. package/packages/cli/dist/set-NGM2FIKF.js.map +7 -0
  45. package/packages/cli/dist/uninstall-Q3CP4UN5.js +539 -0
  46. package/packages/cli/dist/uninstall-Q3CP4UN5.js.map +7 -0
  47. package/packages/cli/dist/unpublish-VBTNTMS5.js +245 -0
  48. package/packages/cli/dist/unpublish-VBTNTMS5.js.map +7 -0
  49. package/packages/cli/dist/view-MXRBMXOG.js +488 -0
  50. package/packages/cli/dist/view-MXRBMXOG.js.map +7 -0
  51. package/packages/cli/package.json +2 -0
  52. package/packages/core/dist/core/install/conflicts/file-conflict-resolver.d.ts +5 -4
  53. package/packages/core/dist/core/install/conflicts/file-conflict-resolver.d.ts.map +1 -1
  54. package/packages/core/dist/core/install/conflicts/file-conflict-resolver.js +6 -5
  55. package/packages/core/dist/core/install/conflicts/file-conflict-resolver.js.map +1 -1
  56. package/packages/core/dist/core/install/install-reporting.js +1 -1
  57. package/packages/core/dist/core/install/install-reporting.js.map +1 -1
  58. package/packages/core/dist/core/install/list-handler.d.ts.map +1 -1
  59. package/packages/core/dist/core/install/list-handler.js +7 -0
  60. package/packages/core/dist/core/install/list-handler.js.map +1 -1
  61. package/packages/core/dist/core/install/marketplace-handler.d.ts.map +1 -1
  62. package/packages/core/dist/core/install/marketplace-handler.js.map +1 -1
  63. package/packages/core/dist/core/install/operations/conflict-handler.d.ts +2 -1
  64. package/packages/core/dist/core/install/operations/conflict-handler.d.ts.map +1 -1
  65. package/packages/core/dist/core/install/operations/conflict-handler.js +2 -2
  66. package/packages/core/dist/core/install/operations/conflict-handler.js.map +1 -1
  67. package/packages/core/dist/core/install/orchestrator/orchestrator.d.ts.map +1 -1
  68. package/packages/core/dist/core/install/orchestrator/orchestrator.js +22 -14
  69. package/packages/core/dist/core/install/orchestrator/orchestrator.js.map +1 -1
  70. package/packages/core/dist/core/install/orchestrator/strategies/git-strategy.d.ts +1 -0
  71. package/packages/core/dist/core/install/orchestrator/strategies/git-strategy.d.ts.map +1 -1
  72. package/packages/core/dist/core/install/orchestrator/strategies/git-strategy.js +11 -24
  73. package/packages/core/dist/core/install/orchestrator/strategies/git-strategy.js.map +1 -1
  74. package/packages/core/dist/core/install/orchestrator/strategies/path-strategy.d.ts +2 -0
  75. package/packages/core/dist/core/install/orchestrator/strategies/path-strategy.d.ts.map +1 -1
  76. package/packages/core/dist/core/install/orchestrator/strategies/path-strategy.js +14 -14
  77. package/packages/core/dist/core/install/orchestrator/strategies/path-strategy.js.map +1 -1
  78. package/packages/core/dist/core/install/orchestrator/strategies/registry-strategy.d.ts +7 -0
  79. package/packages/core/dist/core/install/orchestrator/strategies/registry-strategy.d.ts.map +1 -1
  80. package/packages/core/dist/core/install/orchestrator/strategies/registry-strategy.js +28 -0
  81. package/packages/core/dist/core/install/orchestrator/strategies/registry-strategy.js.map +1 -1
  82. package/packages/core/dist/core/install/platform-resolution.d.ts +3 -0
  83. package/packages/core/dist/core/install/platform-resolution.d.ts.map +1 -1
  84. package/packages/core/dist/core/install/platform-resolution.js +5 -2
  85. package/packages/core/dist/core/install/platform-resolution.js.map +1 -1
  86. package/packages/core/dist/core/install/preprocessing/context-population.d.ts +18 -0
  87. package/packages/core/dist/core/install/preprocessing/context-population.d.ts.map +1 -0
  88. package/packages/core/dist/core/install/preprocessing/context-population.js +36 -0
  89. package/packages/core/dist/core/install/preprocessing/context-population.js.map +1 -0
  90. package/packages/core/dist/core/install/preprocessing/convenience-preprocessor.d.ts +23 -0
  91. package/packages/core/dist/core/install/preprocessing/convenience-preprocessor.d.ts.map +1 -1
  92. package/packages/core/dist/core/install/preprocessing/convenience-preprocessor.js +44 -0
  93. package/packages/core/dist/core/install/preprocessing/convenience-preprocessor.js.map +1 -1
  94. package/packages/core/dist/core/install/sources/path-source.d.ts.map +1 -1
  95. package/packages/core/dist/core/install/sources/path-source.js +8 -0
  96. package/packages/core/dist/core/install/sources/path-source.js.map +1 -1
  97. package/packages/core/dist/core/install/unified/context-builders.d.ts +5 -0
  98. package/packages/core/dist/core/install/unified/context-builders.d.ts.map +1 -1
  99. package/packages/core/dist/core/install/unified/context-builders.js +13 -0
  100. package/packages/core/dist/core/install/unified/context-builders.js.map +1 -1
  101. package/packages/core/dist/core/install/unified/context-helpers.d.ts.map +1 -1
  102. package/packages/core/dist/core/install/unified/context-helpers.js +5 -0
  103. package/packages/core/dist/core/install/unified/context-helpers.js.map +1 -1
  104. package/packages/core/dist/core/install/unified/context.d.ts +6 -0
  105. package/packages/core/dist/core/install/unified/context.d.ts.map +1 -1
  106. package/packages/core/dist/core/install/unified/multi-context-pipeline.d.ts.map +1 -1
  107. package/packages/core/dist/core/install/unified/multi-context-pipeline.js +0 -2
  108. package/packages/core/dist/core/install/unified/multi-context-pipeline.js.map +1 -1
  109. package/packages/core/dist/core/install/unified/phases/conflicts.d.ts.map +1 -1
  110. package/packages/core/dist/core/install/unified/phases/conflicts.js +1 -1
  111. package/packages/core/dist/core/install/unified/phases/conflicts.js.map +1 -1
  112. package/packages/core/dist/core/install/unified/phases/execute.d.ts.map +1 -1
  113. package/packages/core/dist/core/install/unified/phases/execute.js +2 -1
  114. package/packages/core/dist/core/install/unified/phases/execute.js.map +1 -1
  115. package/packages/core/dist/core/install/unified/phases/report.js +1 -1
  116. package/packages/core/dist/core/install/unified/phases/report.js.map +1 -1
  117. package/packages/core/dist/core/install/unified/pipeline.d.ts.map +1 -1
  118. package/packages/core/dist/core/install/unified/pipeline.js +5 -2
  119. package/packages/core/dist/core/install/unified/pipeline.js.map +1 -1
  120. package/packages/core/dist/core/ports/resolve.d.ts +0 -13
  121. package/packages/core/dist/core/ports/resolve.d.ts.map +1 -1
  122. package/packages/core/dist/core/ports/resolve.js +0 -28
  123. package/packages/core/dist/core/ports/resolve.js.map +1 -1
  124. package/packages/core/dist/core/remove/removal-confirmation.d.ts +4 -1
  125. package/packages/core/dist/core/remove/removal-confirmation.d.ts.map +1 -1
  126. package/packages/core/dist/core/remove/removal-confirmation.js +5 -4
  127. package/packages/core/dist/core/remove/removal-confirmation.js.map +1 -1
  128. package/packages/core/dist/core/remove/remove-from-source-pipeline.d.ts.map +1 -1
  129. package/packages/core/dist/core/remove/remove-from-source-pipeline.js +1 -10
  130. package/packages/core/dist/core/remove/remove-from-source-pipeline.js.map +1 -1
  131. package/packages/core/dist/core/uninstall/uninstall-executor.js +1 -1
  132. package/packages/core/dist/core/uninstall/uninstall-executor.js.map +1 -1
  133. package/packages/core/dist/core/uninstall/uninstall-reporter.d.ts +2 -2
  134. package/packages/core/dist/core/uninstall/uninstall-reporter.d.ts.map +1 -1
  135. package/packages/core/dist/core/uninstall/uninstall-reporter.js +4 -4
  136. package/packages/core/dist/core/uninstall/uninstall-reporter.js.map +1 -1
  137. package/packages/core/dist/index.d.ts +1 -1
  138. package/packages/core/dist/index.d.ts.map +1 -1
  139. package/packages/core/dist/types/execution-context.d.ts +24 -10
  140. package/packages/core/dist/types/execution-context.d.ts.map +1 -1
@@ -0,0 +1,413 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ getPlatformRootFileNames
4
+ } from "./chunk-E2HYZWNV.js";
5
+ import {
6
+ cleanupEmptyParents
7
+ } from "./chunk-AMHDY5PC.js";
8
+ import {
9
+ removePackageFromOpenpackageYml
10
+ } from "./chunk-CVA64SXK.js";
11
+ import {
12
+ CLOSE_MARKER_REGEX,
13
+ buildOpenMarkerRegex,
14
+ deleteNestedKey,
15
+ isEffectivelyEmpty
16
+ } from "./chunk-QURLGVA4.js";
17
+ import {
18
+ getTargetPath
19
+ } from "./chunk-SHKYQQJJ.js";
20
+ import {
21
+ readWorkspaceIndex,
22
+ writeWorkspaceIndex
23
+ } from "./chunk-GP5FJYSS.js";
24
+ import {
25
+ getLocalOpenPackageDir,
26
+ getLocalPackageYmlPath
27
+ } from "./chunk-BCYZDID6.js";
28
+ import {
29
+ resolveOutput
30
+ } from "./chunk-BROJ6OUT.js";
31
+ import {
32
+ getAllPlatforms,
33
+ getPlatformDefinition
34
+ } from "./chunk-GKEHDSL4.js";
35
+ import {
36
+ FILE_PATTERNS
37
+ } from "./chunk-J4IFFBLP.js";
38
+ import {
39
+ exists,
40
+ readTextFile,
41
+ remove,
42
+ walkFiles,
43
+ writeTextFile
44
+ } from "./chunk-S47F4OG4.js";
45
+ import {
46
+ ValidationError
47
+ } from "./chunk-ID4SVDQZ.js";
48
+ import {
49
+ logger
50
+ } from "./chunk-5EFWGD33.js";
51
+
52
+ // ../core/src/core/platform/directory-preservation.ts
53
+ import path from "path";
54
+ function extractDirectoryFromPattern(pattern, targetDir) {
55
+ let normalized = pattern.replace(/\\/g, "/"), hasExtension = !/^\.[^./]+$/.test(normalized) && /\.[a-z0-9]+$/i.test(normalized), isDirectory = normalized.endsWith("/") || !hasExtension, dirPath;
56
+ if (isDirectory)
57
+ dirPath = normalized.replace(/\/$/, "");
58
+ else {
59
+ let lastSlash = normalized.lastIndexOf("/");
60
+ dirPath = lastSlash >= 0 ? normalized.substring(0, lastSlash) : "";
61
+ }
62
+ let absPath = dirPath ? path.join(targetDir, dirPath) : targetDir;
63
+ return absPath === targetDir ? null : absPath;
64
+ }
65
+ function buildPreservedDirectoriesSet(targetDir) {
66
+ let preserved = /* @__PURE__ */ new Set(), platforms = getAllPlatforms(void 0, targetDir);
67
+ for (let platform of platforms) {
68
+ let definition = getPlatformDefinition(platform, targetDir);
69
+ if (definition.detection && definition.detection.length > 0)
70
+ for (let pattern of definition.detection) {
71
+ let dirToPreserve = extractDirectoryFromPattern(pattern, targetDir);
72
+ dirToPreserve && preserved.add(dirToPreserve);
73
+ }
74
+ else if (definition.rootDir) {
75
+ let rootPath = path.join(targetDir, definition.rootDir);
76
+ rootPath !== targetDir && preserved.add(rootPath);
77
+ }
78
+ }
79
+ return preserved;
80
+ }
81
+
82
+ // ../core/src/core/uninstall/uninstall-pipeline.ts
83
+ import path2 from "path";
84
+
85
+ // ../core/src/utils/workspace-index-ownership.ts
86
+ function removeWorkspaceIndexEntry(index, packageName) {
87
+ index.packages && index.packages[packageName] && delete index.packages[packageName];
88
+ }
89
+ function removeWorkspaceIndexFileKeys(index, packageName, sourceKeysToRemove) {
90
+ let pkg = index.packages?.[packageName];
91
+ if (pkg) {
92
+ for (let key of sourceKeysToRemove)
93
+ delete pkg.files[key];
94
+ Object.keys(pkg.files).length === 0 && delete index.packages[packageName];
95
+ }
96
+ }
97
+
98
+ // ../core/src/core/platform/root-file-uninstaller.ts
99
+ import { join } from "path";
100
+ function stripPackageSection(content, packageName) {
101
+ if (!content) return { changed: !1, content };
102
+ let openRe = buildOpenMarkerRegex(packageName), closeRe = CLOSE_MARKER_REGEX, openMatch = openRe.exec(content);
103
+ if (!openMatch) return { changed: !1, content };
104
+ let before = content.slice(0, openMatch.index), rest = content.slice(openMatch.index + openMatch[0].length), closeMatch = closeRe.exec(rest);
105
+ if (!closeMatch) return { changed: !1, content };
106
+ let after = rest.slice(closeMatch.index + closeMatch[0].length);
107
+ return { changed: !0, content: before + after };
108
+ }
109
+ function stripMultiplePackageSections(content, packageNames) {
110
+ let changed = !1, current = content;
111
+ for (let name of packageNames) {
112
+ let result = stripPackageSection(current, name);
113
+ result.changed && (changed = !0), current = result.content;
114
+ }
115
+ return { changed, content: current };
116
+ }
117
+ function getUniqueRootFilenames() {
118
+ let set = /* @__PURE__ */ new Set([FILE_PATTERNS.AGENTS_MD]);
119
+ for (let platform of getAllPlatforms()) {
120
+ let def = getPlatformDefinition(platform);
121
+ def.rootFile && set.add(def.rootFile);
122
+ }
123
+ return Array.from(set);
124
+ }
125
+ async function processRootFileRemovals(targetDir, packageNames, options = {}) {
126
+ let updated = [], rootFiles = getUniqueRootFilenames();
127
+ for (let filename of rootFiles) {
128
+ let absPath = join(targetDir, filename);
129
+ if (!await exists(absPath)) continue;
130
+ let original = await readTextFile(absPath), { changed, content } = stripMultiplePackageSections(original, packageNames);
131
+ changed && (options.dryRun || (await writeTextFile(absPath, content), logger.debug(`Updated root file: ${absPath}`)), updated.push(filename));
132
+ }
133
+ return { updated };
134
+ }
135
+
136
+ // ../core/src/utils/package-index-yml.ts
137
+ import * as yaml from "js-yaml";
138
+ function ensureTrailingSlash(value) {
139
+ return value.endsWith("/") ? value : `${value}/`;
140
+ }
141
+ function sortMapping(record) {
142
+ let sortedKeys = Object.keys(record).sort(), normalized = {};
143
+ for (let key of sortedKeys) {
144
+ let values = record[key] || [];
145
+ if (values.some((v) => typeof v == "object" && v !== null)) {
146
+ let sorted = [...values].sort((a, b) => {
147
+ let targetA = typeof a == "string" ? a : a.target, targetB = typeof b == "string" ? b : b.target;
148
+ return targetA.localeCompare(targetB);
149
+ }), seen = /* @__PURE__ */ new Set();
150
+ normalized[key] = sorted.filter((item) => {
151
+ let target = typeof item == "string" ? item : item.target;
152
+ return seen.has(target) ? !1 : (seen.add(target), !0);
153
+ });
154
+ } else {
155
+ let sortedValues = [...new Set(values)].sort();
156
+ normalized[key] = sortedValues;
157
+ }
158
+ }
159
+ return normalized;
160
+ }
161
+ function isDirKey(key) {
162
+ return key.endsWith("/");
163
+ }
164
+
165
+ // ../core/src/core/uninstall/flow-aware-uninstaller.ts
166
+ import { join as join2 } from "path";
167
+ import yaml2 from "js-yaml";
168
+ import * as TOML from "smol-toml";
169
+ function detectFileFormat(filePath) {
170
+ switch (filePath.toLowerCase().split(".").pop()) {
171
+ case "json":
172
+ return "json";
173
+ case "jsonc":
174
+ return "jsonc";
175
+ case "yaml":
176
+ case "yml":
177
+ return "yaml";
178
+ case "toml":
179
+ return "toml";
180
+ default:
181
+ return "text";
182
+ }
183
+ }
184
+ function parseContent(content, format) {
185
+ try {
186
+ switch (format) {
187
+ case "json":
188
+ case "jsonc":
189
+ let cleaned = format === "jsonc" ? content.replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "") : content;
190
+ return JSON.parse(cleaned);
191
+ case "yaml":
192
+ case "yml":
193
+ return yaml2.load(content);
194
+ case "toml":
195
+ return TOML.parse(content);
196
+ default:
197
+ return content;
198
+ }
199
+ } catch (error) {
200
+ throw new Error(`Failed to parse ${format} file: ${error instanceof Error ? error.message : String(error)}`);
201
+ }
202
+ }
203
+ function serializeContent(data, format) {
204
+ try {
205
+ switch (format) {
206
+ case "json":
207
+ case "jsonc":
208
+ return JSON.stringify(data, null, 2);
209
+ case "yaml":
210
+ case "yml":
211
+ return yaml2.dump(data, { indent: 2, flowLevel: 1, lineWidth: -1 });
212
+ case "toml":
213
+ return TOML.stringify(data);
214
+ default:
215
+ return typeof data == "string" ? data : JSON.stringify(data, null, 2);
216
+ }
217
+ } catch (error) {
218
+ throw new Error(`Failed to serialize ${format} file: ${error instanceof Error ? error.message : String(error)}`);
219
+ }
220
+ }
221
+ async function removeKeysFromMergedFile(targetDir, targetPath, keysToRemove) {
222
+ let absPath = join2(targetDir, targetPath);
223
+ if (!await exists(absPath))
224
+ return { deleted: !1, updated: !1 };
225
+ let content = await readTextFile(absPath), format = detectFileFormat(targetPath), data;
226
+ try {
227
+ data = parseContent(content, format);
228
+ } catch (error) {
229
+ return logger.warn(`Failed to parse ${targetPath} for key removal: ${error instanceof Error ? error.message : String(error)}`), { deleted: !1, updated: !1 };
230
+ }
231
+ let hasNestedKeyPath = (obj, keyPath) => {
232
+ if (!obj || typeof obj != "object") return !1;
233
+ let parts = keyPath.split(".").filter(Boolean), current = obj;
234
+ for (let part of parts) {
235
+ if (!current || typeof current != "object" || !(part in current)) return !1;
236
+ current = current[part];
237
+ }
238
+ return !0;
239
+ }, existingBefore = keysToRemove.filter((k) => typeof k == "string" && hasNestedKeyPath(data, k)).length;
240
+ for (let key of keysToRemove)
241
+ deleteNestedKey(data, key);
242
+ if (isEffectivelyEmpty(data))
243
+ return await remove(absPath), logger.info(`Removed empty file: ${targetPath}`), { deleted: !0, updated: !1 };
244
+ let serialized = serializeContent(data, format);
245
+ return await writeTextFile(absPath, serialized), logger.info(`Updated ${targetPath}: removed ${keysToRemove.length} keys`), { deleted: !1, updated: !0 };
246
+ }
247
+ async function removeFileMapping(targetDir, mapping, packageName) {
248
+ let removed = [], updated = [];
249
+ if (typeof mapping == "string") {
250
+ let absPath = join2(targetDir, mapping);
251
+ await exists(absPath) && (await remove(absPath), removed.push(mapping), logger.debug(`Removed file: ${mapping}`));
252
+ } else {
253
+ let targetPath = mapping.target;
254
+ if (mapping.merge === "composite")
255
+ logger.debug(`Skipping composite merge file (handled by root file logic): ${targetPath}`);
256
+ else if (mapping.keys && mapping.keys.length > 0) {
257
+ let result = await removeKeysFromMergedFile(targetDir, targetPath, mapping.keys);
258
+ result.deleted ? removed.push(targetPath) : result.updated && updated.push(targetPath), logger.debug(
259
+ `Removed ${mapping.keys.length} keys from ${targetPath}: ${mapping.keys.join(", ")}`
260
+ );
261
+ } else if (mapping.merge === "deep" || mapping.merge === "shallow")
262
+ logger.warn(
263
+ `Cannot precisely remove ${targetPath} for ${packageName} - no key tracking available. File may contain content from other packages.`
264
+ );
265
+ else {
266
+ let absPath = join2(targetDir, targetPath);
267
+ await exists(absPath) && (await remove(absPath), removed.push(targetPath), logger.debug(`Removed file: ${targetPath}`));
268
+ }
269
+ }
270
+ return { removed, updated };
271
+ }
272
+
273
+ // ../core/src/core/uninstall/uninstall-pipeline.ts
274
+ function isRootFileKey(key, rootNames) {
275
+ let normalized = key.replace(/\\/g, "/");
276
+ return rootNames.has(normalized);
277
+ }
278
+ async function processFileMappings(filesMapping, targetDir, packageName, rootNames, options = {}) {
279
+ let removed = [], updated = [], seenPaths = /* @__PURE__ */ new Set();
280
+ for (let [rawKey, mappings] of Object.entries(filesMapping || {})) {
281
+ if (!Array.isArray(mappings) || mappings.length === 0) continue;
282
+ if (isDirKey(rawKey)) {
283
+ for (let mapping of mappings) {
284
+ let targetPath = getTargetPath(mapping), absDir = path2.join(targetDir, targetPath);
285
+ if (await exists(absDir))
286
+ if (options.dryRun)
287
+ for await (let filePath of walkFiles(absDir))
288
+ seenPaths.has(filePath) || (seenPaths.add(filePath), removed.push(filePath));
289
+ else {
290
+ let result = await removeFileMapping(targetDir, mapping, packageName);
291
+ removed.push(...result.removed), updated.push(...result.updated);
292
+ }
293
+ }
294
+ continue;
295
+ }
296
+ if (!isRootFileKey(rawKey, rootNames))
297
+ for (let mapping of mappings) {
298
+ let targetPath = getTargetPath(mapping), absPath = path2.join(targetDir, targetPath);
299
+ if (options.dryRun)
300
+ seenPaths.has(absPath) || (seenPaths.add(absPath), removed.push(absPath));
301
+ else {
302
+ let result = await removeFileMapping(targetDir, mapping, packageName);
303
+ removed.push(...result.removed), updated.push(...result.updated);
304
+ }
305
+ }
306
+ }
307
+ return { removed, updated };
308
+ }
309
+ async function runUninstallPipeline(packageName, options = {}, execContext) {
310
+ let targetDir = execContext.targetDir, openpkgDir = getLocalOpenPackageDir(targetDir), manifestPath = getLocalPackageYmlPath(targetDir);
311
+ if (!await exists(openpkgDir) || !await exists(manifestPath))
312
+ throw new ValidationError(
313
+ `No .openpackage/openpackage.yml found in ${targetDir}.`
314
+ );
315
+ let { index, path: indexPath } = await readWorkspaceIndex(targetDir), pkgEntry = index.packages?.[packageName];
316
+ if (!pkgEntry)
317
+ return { success: !1, error: `Package '${packageName}' not found in workspace index.` };
318
+ let rootNames = getPlatformRootFileNames(getAllPlatforms(void 0, targetDir), targetDir);
319
+ if (options.dryRun) {
320
+ let out = resolveOutput(execContext), plannedRemovals = await processFileMappings(
321
+ pkgEntry.files || {},
322
+ targetDir,
323
+ packageName,
324
+ rootNames,
325
+ { dryRun: !0 }
326
+ ), rootPlan = await processRootFileRemovals(targetDir, [packageName], { dryRun: !0 });
327
+ out.info(`(dry-run) Would remove ${plannedRemovals.removed.length} files for ${packageName}`);
328
+ for (let filePath of plannedRemovals.removed)
329
+ out.info(` - ${filePath}`);
330
+ return rootPlan.updated.length > 0 && (out.info("Root files to update:"), rootPlan.updated.forEach((f) => out.info(` - ${f}`))), {
331
+ success: !0,
332
+ data: {
333
+ removedFiles: plannedRemovals.removed,
334
+ rootFilesUpdated: rootPlan.updated
335
+ }
336
+ };
337
+ }
338
+ let { removed: deleted, updated } = await processFileMappings(
339
+ pkgEntry.files || {},
340
+ targetDir,
341
+ packageName,
342
+ rootNames,
343
+ { dryRun: !1 }
344
+ ), rootResult = await processRootFileRemovals(targetDir, [packageName]);
345
+ removeWorkspaceIndexEntry(index, packageName), await writeWorkspaceIndex({ path: indexPath, index }), await removePackageFromOpenpackageYml(targetDir, packageName);
346
+ let preservedDirs = buildPreservedDirectoriesSet(targetDir), deletedAbsolutePaths = deleted.map((relativePath) => path2.join(targetDir, relativePath));
347
+ return await cleanupEmptyParents(targetDir, deletedAbsolutePaths, preservedDirs), logger.info(`Uninstalled ${packageName}: removed ${deleted.length} files, updated ${updated.length} merged files`), {
348
+ success: !0,
349
+ data: {
350
+ removedFiles: deleted,
351
+ rootFilesUpdated: [...rootResult.updated, ...updated]
352
+ }
353
+ };
354
+ }
355
+ async function runSelectiveUninstallPipeline(packageName, sourceKeysToRemove, options = {}, execContext) {
356
+ let targetDir = execContext.targetDir, openpkgDir = getLocalOpenPackageDir(targetDir), manifestPath = getLocalPackageYmlPath(targetDir);
357
+ if (!await exists(openpkgDir) || !await exists(manifestPath))
358
+ throw new ValidationError(
359
+ `No .openpackage/openpackage.yml found in ${targetDir}.`
360
+ );
361
+ let { index, path: indexPath } = await readWorkspaceIndex(targetDir), pkgEntry = index.packages?.[packageName];
362
+ if (!pkgEntry)
363
+ return { success: !1, error: `Package '${packageName}' not found in workspace index.` };
364
+ let filteredFiles = {};
365
+ for (let key of sourceKeysToRemove)
366
+ pkgEntry.files[key] && (filteredFiles[key] = pkgEntry.files[key]);
367
+ let rootNames = getPlatformRootFileNames(getAllPlatforms(void 0, targetDir), targetDir);
368
+ if (options.dryRun) {
369
+ let out = resolveOutput(execContext), plannedRemovals = await processFileMappings(
370
+ filteredFiles,
371
+ targetDir,
372
+ packageName,
373
+ rootNames,
374
+ { dryRun: !0 }
375
+ );
376
+ out.info(`(dry-run) Would remove ${plannedRemovals.removed.length} files for ${packageName}`);
377
+ for (let filePath of plannedRemovals.removed)
378
+ out.info(` - ${filePath}`);
379
+ return {
380
+ success: !0,
381
+ data: {
382
+ removedFiles: plannedRemovals.removed,
383
+ rootFilesUpdated: []
384
+ }
385
+ };
386
+ }
387
+ let { removed: deleted, updated } = await processFileMappings(
388
+ filteredFiles,
389
+ targetDir,
390
+ packageName,
391
+ rootNames,
392
+ { dryRun: !1 }
393
+ );
394
+ removeWorkspaceIndexFileKeys(index, packageName, sourceKeysToRemove), await writeWorkspaceIndex({ path: indexPath, index });
395
+ let preservedDirs = buildPreservedDirectoriesSet(targetDir), deletedAbsolutePaths = deleted.map((relativePath) => path2.join(targetDir, relativePath));
396
+ return await cleanupEmptyParents(targetDir, deletedAbsolutePaths, preservedDirs), logger.info(`Selectively uninstalled from ${packageName}: removed ${deleted.length} files, updated ${updated.length} merged files`), {
397
+ success: !0,
398
+ data: {
399
+ removedFiles: deleted,
400
+ rootFilesUpdated: updated
401
+ }
402
+ };
403
+ }
404
+
405
+ export {
406
+ ensureTrailingSlash,
407
+ sortMapping,
408
+ isDirKey,
409
+ buildPreservedDirectoriesSet,
410
+ runUninstallPipeline,
411
+ runSelectiveUninstallPipeline
412
+ };
413
+ //# sourceMappingURL=chunk-D6LEPODL.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../core/src/core/platform/directory-preservation.ts", "../../core/src/core/uninstall/uninstall-pipeline.ts", "../../core/src/utils/workspace-index-ownership.ts", "../../core/src/core/platform/root-file-uninstaller.ts", "../../core/src/utils/package-index-yml.ts", "../../core/src/core/uninstall/flow-aware-uninstaller.ts"],
4
+ "sourcesContent": ["/**\n * Directory Preservation Utilities\n * \n * Determines which directories should be preserved during uninstall cleanup.\n * Uses platform detection patterns to identify platform root directories that\n * should never be removed, even if empty.\n */\n\nimport path from 'path';\nimport { getAllPlatforms, getPlatformDefinition } from '../platforms.js';\n\n/**\n * Extract the directory path from a detection pattern.\n * \n * Detection patterns can be:\n * - Directory names: \".cursor\", \".claude\"\n * - File paths: \".claude-plugin/plugin.json\", \"CLAUDE.md\"\n * - Future: glob patterns\n * \n * @param pattern - Detection pattern from platform definition\n * @param targetDir - Target directory (workspace root or global home)\n * @returns Absolute directory path to preserve, or null if pattern points to workspace root\n * \n * @example\n * extractDirectoryFromPattern(\".cursor\", \"/workspace\") \n * \u2192 \"/workspace/.cursor\"\n * \n * extractDirectoryFromPattern(\".claude-plugin/plugin.json\", \"/workspace\")\n * \u2192 \"/workspace/.claude-plugin\"\n * \n * extractDirectoryFromPattern(\"CLAUDE.md\", \"/workspace\")\n * \u2192 null (root file, don't preserve workspace root)\n */\nexport function extractDirectoryFromPattern(pattern: string, targetDir: string): string | null {\n // Normalize path separators\n const normalized = pattern.replace(/\\\\/g, '/');\n \n // Check if it's a directory or a file\n // A dotfile directory (e.g., \".cursor\", \".claude\") is a single segment starting with \".\"\n // and no path separators \u2014 distinct from a file extension like \"file.md\"\n const isDotfileDir = /^\\.[^./]+$/.test(normalized);\n const hasExtension = !isDotfileDir && /\\.[a-z0-9]+$/i.test(normalized);\n const isDirectory = normalized.endsWith('/') || !hasExtension;\n \n let dirPath: string;\n \n if (isDirectory) {\n // Remove trailing slash if present\n dirPath = normalized.replace(/\\/$/, '');\n } else {\n // Extract directory from file path\n const lastSlash = normalized.lastIndexOf('/');\n dirPath = lastSlash >= 0 ? normalized.substring(0, lastSlash) : '';\n }\n \n // Convert to absolute path\n const absPath = dirPath ? path.join(targetDir, dirPath) : targetDir;\n \n // Don't preserve the workspace root itself\n // Root files (like CLAUDE.md, AGENTS.md) have their own preservation logic\n return absPath === targetDir ? null : absPath;\n}\n\n/**\n * Build a Set of absolute directory paths that should be preserved during cleanup.\n * \n * These directories are identified from platform detection patterns and represent\n * platform root directories (e.g., .cursor, .claude, .opencode) that should never\n * be removed, even if they become empty after uninstalling packages.\n * \n * @param targetDir - Target directory (workspace root or global home)\n * @returns Set of absolute directory paths to preserve\n * \n * @example\n * buildPreservedDirectoriesSet(\"/workspace\")\n * \u2192 Set([\n * \"/workspace/.cursor\",\n * \"/workspace/.claude\", \n * \"/workspace/.opencode\",\n * \"/workspace/.claude-plugin\",\n * ...\n * ])\n */\nexport function buildPreservedDirectoriesSet(targetDir: string): Set<string> {\n const preserved = new Set<string>();\n const platforms = getAllPlatforms(undefined, targetDir);\n \n for (const platform of platforms) {\n const definition = getPlatformDefinition(platform, targetDir);\n \n // Primary: use detection patterns\n if (definition.detection && definition.detection.length > 0) {\n for (const pattern of definition.detection) {\n const dirToPreserve = extractDirectoryFromPattern(pattern, targetDir);\n if (dirToPreserve) {\n preserved.add(dirToPreserve);\n }\n }\n } else if (definition.rootDir) {\n // Fallback: use rootDir for platforms without detection patterns\n const rootPath = path.join(targetDir, definition.rootDir);\n if (rootPath !== targetDir) {\n preserved.add(rootPath);\n }\n }\n }\n \n return preserved;\n}\n", "import path from 'path';\n\nimport type { CommandResult, UninstallOptions, ExecutionContext } from '../../types/index.js';\nimport { ValidationError } from '../../utils/errors.js';\nimport { getLocalOpenPackageDir, getLocalPackageYmlPath } from '../../utils/paths.js';\nimport { readWorkspaceIndex, writeWorkspaceIndex } from '../../utils/workspace-index-yml.js';\nimport { removeWorkspaceIndexEntry, removeWorkspaceIndexFileKeys } from '../../utils/workspace-index-ownership.js';\nimport { processRootFileRemovals } from '../platform/root-file-uninstaller.js';\nimport { exists, remove, walkFiles } from '../../utils/fs.js';\nimport { isDirKey } from '../../utils/package-index-yml.js';\nimport { removePackageFromOpenpackageYml } from '../package-management.js';\nimport { getPlatformRootFileNames } from '../platform/platform-root-files.js';\nimport { getAllPlatforms } from '../platforms.js';\nimport { logger } from '../../utils/logger.js';\nimport { removeFileMapping } from './flow-aware-uninstaller.js';\nimport { getTargetPath } from '../../utils/workspace-index-helpers.js';\nimport { buildPreservedDirectoriesSet } from '../platform/directory-preservation.js';\nimport { cleanupEmptyParents } from '../../utils/cleanup-empty-parents.js';\nimport type { WorkspaceIndexFileMapping } from '../../types/workspace-index.js';\nimport type { OutputPort } from '../ports/output.js';\nimport { resolveOutput } from '../ports/resolve.js';\n\ninterface ProcessFileMappingsOptions {\n dryRun?: boolean;\n}\n\ninterface ProcessFileMappingsResult {\n removed: string[];\n updated: string[];\n}\n\nfunction isRootFileKey(key: string, rootNames: Set<string>): boolean {\n const normalized = key.replace(/\\\\/g, '/');\n return rootNames.has(normalized);\n}\n\nasync function processFileMappings(\n filesMapping: Record<string, (string | WorkspaceIndexFileMapping)[]>,\n targetDir: string,\n packageName: string,\n rootNames: Set<string>,\n options: ProcessFileMappingsOptions = {}\n): Promise<ProcessFileMappingsResult> {\n const removed: string[] = [];\n const updated: string[] = [];\n const seenPaths = new Set<string>();\n\n for (const [rawKey, mappings] of Object.entries(filesMapping || {})) {\n if (!Array.isArray(mappings) || mappings.length === 0) continue;\n\n const isDir = isDirKey(rawKey);\n\n if (isDir) {\n for (const mapping of mappings) {\n const targetPath = getTargetPath(mapping);\n const absDir = path.join(targetDir, targetPath);\n if (!(await exists(absDir))) continue;\n\n if (options.dryRun) {\n for await (const filePath of walkFiles(absDir)) {\n if (!seenPaths.has(filePath)) {\n seenPaths.add(filePath);\n removed.push(filePath);\n }\n }\n } else {\n const result = await removeFileMapping(targetDir, mapping, packageName);\n removed.push(...result.removed);\n updated.push(...result.updated);\n }\n }\n continue;\n }\n\n if (isRootFileKey(rawKey, rootNames)) {\n continue;\n }\n\n for (const mapping of mappings) {\n const targetPath = getTargetPath(mapping);\n const absPath = path.join(targetDir, targetPath);\n\n if (options.dryRun) {\n if (!seenPaths.has(absPath)) {\n seenPaths.add(absPath);\n removed.push(absPath);\n }\n } else {\n const result = await removeFileMapping(targetDir, mapping, packageName);\n removed.push(...result.removed);\n updated.push(...result.updated);\n }\n }\n }\n\n return { removed, updated };\n}\n\nexport interface UninstallPipelineResult {\n removedFiles: string[];\n rootFilesUpdated: string[];\n}\n\nexport async function runUninstallPipeline(\n packageName: string,\n options: UninstallOptions = {},\n execContext: ExecutionContext\n): Promise<CommandResult<UninstallPipelineResult>> {\n // Use targetDir for uninstall operations\n const targetDir = execContext.targetDir;\n const openpkgDir = getLocalOpenPackageDir(targetDir);\n const manifestPath = getLocalPackageYmlPath(targetDir);\n\n if (!(await exists(openpkgDir)) || !(await exists(manifestPath))) {\n throw new ValidationError(\n `No .openpackage/openpackage.yml found in ${targetDir}.`\n );\n }\n\n // Look up package by exact name provided by user (no normalization)\n const { index, path: indexPath } = await readWorkspaceIndex(targetDir);\n const pkgEntry = index.packages?.[packageName];\n\n if (!pkgEntry) {\n return { success: false, error: `Package '${packageName}' not found in workspace index.` };\n }\n\n const rootNames = getPlatformRootFileNames(getAllPlatforms(undefined, targetDir), targetDir);\n\n if (options.dryRun) {\n const out = resolveOutput(execContext);\n const plannedRemovals = await processFileMappings(\n pkgEntry.files || {},\n targetDir,\n packageName,\n rootNames,\n { dryRun: true }\n );\n const rootPlan = await processRootFileRemovals(targetDir, [packageName], { dryRun: true });\n out.info(`(dry-run) Would remove ${plannedRemovals.removed.length} files for ${packageName}`);\n for (const filePath of plannedRemovals.removed) {\n out.info(` - ${filePath}`);\n }\n if (rootPlan.updated.length > 0) {\n out.info(`Root files to update:`);\n rootPlan.updated.forEach(f => out.info(` - ${f}`));\n }\n return {\n success: true,\n data: {\n removedFiles: plannedRemovals.removed,\n rootFilesUpdated: rootPlan.updated\n }\n };\n }\n\n const { removed: deleted, updated } = await processFileMappings(\n pkgEntry.files || {},\n targetDir,\n packageName,\n rootNames,\n { dryRun: false }\n );\n\n const rootResult = await processRootFileRemovals(targetDir, [packageName]);\n\n // Update workspace index (migration will happen on write)\n removeWorkspaceIndexEntry(index, packageName);\n await writeWorkspaceIndex({ path: indexPath, index });\n\n // Update openpackage.yml (migration will happen on write)\n await removePackageFromOpenpackageYml(targetDir, packageName);\n\n // Cleanup empty directories (preserve platform roots from detection patterns)\n const preservedDirs = buildPreservedDirectoriesSet(targetDir);\n // Convert relative paths to absolute paths for cleanup\n const deletedAbsolutePaths = deleted.map(relativePath => path.join(targetDir, relativePath));\n await cleanupEmptyParents(targetDir, deletedAbsolutePaths, preservedDirs);\n\n logger.info(`Uninstalled ${packageName}: removed ${deleted.length} files, updated ${updated.length} merged files`);\n\n return {\n success: true,\n data: {\n removedFiles: deleted,\n rootFilesUpdated: [...rootResult.updated, ...updated]\n }\n };\n}\n\nexport async function runSelectiveUninstallPipeline(\n packageName: string,\n sourceKeysToRemove: Set<string>,\n options: UninstallOptions = {},\n execContext: ExecutionContext\n): Promise<CommandResult<UninstallPipelineResult>> {\n const targetDir = execContext.targetDir;\n const openpkgDir = getLocalOpenPackageDir(targetDir);\n const manifestPath = getLocalPackageYmlPath(targetDir);\n\n if (!(await exists(openpkgDir)) || !(await exists(manifestPath))) {\n throw new ValidationError(\n `No .openpackage/openpackage.yml found in ${targetDir}.`\n );\n }\n\n const { index, path: indexPath } = await readWorkspaceIndex(targetDir);\n const pkgEntry = index.packages?.[packageName];\n\n if (!pkgEntry) {\n return { success: false, error: `Package '${packageName}' not found in workspace index.` };\n }\n\n const filteredFiles: Record<string, (string | WorkspaceIndexFileMapping)[]> = {};\n for (const key of sourceKeysToRemove) {\n if (pkgEntry.files[key]) {\n filteredFiles[key] = pkgEntry.files[key];\n }\n }\n\n const rootNames = getPlatformRootFileNames(getAllPlatforms(undefined, targetDir), targetDir);\n\n if (options.dryRun) {\n const out = resolveOutput(execContext);\n const plannedRemovals = await processFileMappings(\n filteredFiles,\n targetDir,\n packageName,\n rootNames,\n { dryRun: true }\n );\n out.info(`(dry-run) Would remove ${plannedRemovals.removed.length} files for ${packageName}`);\n for (const filePath of plannedRemovals.removed) {\n out.info(` - ${filePath}`);\n }\n return {\n success: true,\n data: {\n removedFiles: plannedRemovals.removed,\n rootFilesUpdated: []\n }\n };\n }\n\n const { removed: deleted, updated } = await processFileMappings(\n filteredFiles,\n targetDir,\n packageName,\n rootNames,\n { dryRun: false }\n );\n\n removeWorkspaceIndexFileKeys(index, packageName, sourceKeysToRemove);\n await writeWorkspaceIndex({ path: indexPath, index });\n\n const preservedDirs = buildPreservedDirectoriesSet(targetDir);\n const deletedAbsolutePaths = deleted.map(relativePath => path.join(targetDir, relativePath));\n await cleanupEmptyParents(targetDir, deletedAbsolutePaths, preservedDirs);\n\n logger.info(`Selectively uninstalled from ${packageName}: removed ${deleted.length} files, updated ${updated.length} merged files`);\n\n return {\n success: true,\n data: {\n removedFiles: deleted,\n rootFilesUpdated: updated\n }\n };\n}\n", "import { join } from 'path';\nimport { getTargetPath } from './workspace-index-helpers.js';\n\nimport type { WorkspaceIndex, WorkspaceIndexPackage } from '../types/workspace-index.js';\nimport { normalizePathForProcessing } from './path-normalization.js';\nimport { exists, walkFiles } from './fs.js';\n\nexport interface WorkspaceConflictOwner {\n packageName: string;\n key: string;\n type: 'file' | 'dir';\n}\n\nexport interface WorkspaceOwnershipContext {\n /**\n * Directory-key owners (key ends with \"/\").\n * Each key may have multiple owners, though consumers typically expect none or one.\n */\n dirKeyOwners: Map<string, WorkspaceConflictOwner[]>;\n /**\n * Concrete workspace path \u2192 owning package.\n */\n installedPathOwners: Map<string, WorkspaceConflictOwner>;\n}\n\nexport function getWorkspaceIndexEntry(\n index: WorkspaceIndex,\n packageName: string\n): WorkspaceIndexPackage | undefined {\n return index.packages?.[packageName];\n}\n\nexport function upsertWorkspaceIndexEntry(\n index: WorkspaceIndex,\n packageName: string,\n entry: WorkspaceIndexPackage\n): void {\n if (!index.packages) {\n index.packages = {};\n }\n index.packages[packageName] = entry;\n}\n\nexport function removeWorkspaceIndexEntry(index: WorkspaceIndex, packageName: string): void {\n if (index.packages && index.packages[packageName]) {\n delete index.packages[packageName];\n }\n}\n\nexport function removeWorkspaceIndexFileKeys(\n index: WorkspaceIndex,\n packageName: string,\n sourceKeysToRemove: Set<string>\n): void {\n const pkg = index.packages?.[packageName];\n if (!pkg) return;\n\n for (const key of sourceKeysToRemove) {\n delete pkg.files[key];\n }\n\n if (Object.keys(pkg.files).length === 0) {\n delete index.packages[packageName];\n }\n}\n\n/**\n * Build ownership maps from the unified workspace index.\n *\n * - Directory keys (trailing \"/\") are expanded to the concrete file paths that\n * currently exist on disk under the mapped directories.\n * - File keys map directly to the listed workspace-relative paths.\n */\nexport async function buildWorkspaceOwnershipContext(\n cwd: string,\n index: WorkspaceIndex,\n opts: { excludePackage?: string } = {}\n): Promise<WorkspaceOwnershipContext> {\n const dirKeyOwners = new Map<string, WorkspaceConflictOwner[]>();\n const installedPathOwners = new Map<string, WorkspaceConflictOwner>();\n\n const packages = index.packages ?? {};\n const exclude = opts.excludePackage;\n\n for (const [rawName, pkg] of Object.entries(packages)) {\n if (exclude && rawName === exclude) continue;\n if (!pkg || typeof pkg !== 'object') continue;\n\n const pkgName = rawName;\n const files = pkg.files ?? {};\n\n for (const [rawKey, rawValues] of Object.entries(files)) {\n if (!Array.isArray(rawValues)) continue;\n const normalizedKey = normalizePathForProcessing(rawKey);\n if (!normalizedKey) continue;\n\n const owner: WorkspaceConflictOwner = {\n packageName: pkgName,\n key: normalizedKey,\n type: normalizedKey.endsWith('/') ? 'dir' : 'file'\n };\n\n if (owner.type === 'dir') {\n if (!dirKeyOwners.has(normalizedKey)) {\n dirKeyOwners.set(normalizedKey, []);\n }\n dirKeyOwners.get(normalizedKey)!.push(owner);\n\n for (const rawMapping of rawValues) {\n // Handle both simple string and WorkspaceIndexFileMapping\n const targetPath = typeof rawMapping === 'string' ? rawMapping : rawMapping.target;\n const dirRel = normalizePathForProcessing(targetPath);\n if (!dirRel) continue;\n const expanded = await collectFilesUnderDirectory(cwd, dirRel);\n for (const relFile of expanded) {\n if (!installedPathOwners.has(relFile)) {\n installedPathOwners.set(relFile, owner);\n }\n }\n }\n continue;\n }\n\n // file key\n for (const rawMapping of rawValues) {\n // Handle both simple string and WorkspaceIndexFileMapping\n const targetPath = typeof rawMapping === 'string' ? rawMapping : rawMapping.target;\n const relPath = normalizePathForProcessing(targetPath);\n if (!relPath) continue;\n if (!installedPathOwners.has(relPath)) {\n installedPathOwners.set(relPath, owner);\n }\n }\n }\n }\n\n return { dirKeyOwners, installedPathOwners };\n}\n\nasync function collectFilesUnderDirectory(cwd: string, dirRel: string): Promise<string[]> {\n const normalizedDir = normalizePathForProcessing(dirRel);\n if (!normalizedDir) return [];\n\n const absDir = join(cwd, normalizedDir);\n if (!(await exists(absDir))) {\n return [];\n }\n\n const collected: string[] = [];\n for await (const absFile of walkFiles(absDir)) {\n const rel = normalizePathForProcessing(absFile.slice(absDir.length + 1));\n if (rel) {\n collected.push(normalizePathForProcessing(join(normalizedDir, rel)));\n }\n }\n return collected;\n}\n", "/**\n * Root File Uninstaller\n * Utilities to remove package-marked sections from root files and delete empty files\n */\n\nimport { join } from 'path';\nimport { exists, readTextFile, writeTextFile } from '../../utils/fs.js';\nimport { logger } from '../../utils/logger.js';\nimport { getAllPlatforms, getPlatformDefinition } from '../platforms.js';\nimport { buildOpenMarkerRegex, CLOSE_MARKER_REGEX } from '../../utils/root-file-extractor.js';\nimport { FILE_PATTERNS } from '../../constants/index.js';\n\n/** Remove a single package section from root-file content using markers */\nfunction stripPackageSection(content: string, packageName: string): { changed: boolean; content: string } {\n if (!content) return { changed: false, content };\n const openRe = buildOpenMarkerRegex(packageName);\n const closeRe = CLOSE_MARKER_REGEX;\n const openMatch = openRe.exec(content);\n if (!openMatch) return { changed: false, content };\n const before = content.slice(0, openMatch.index);\n const rest = content.slice(openMatch.index + openMatch[0].length);\n const closeMatch = closeRe.exec(rest);\n if (!closeMatch) return { changed: false, content };\n const after = rest.slice(closeMatch.index + closeMatch[0].length);\n return { changed: true, content: before + after };\n}\n\n/** Remove multiple package sections from content */\nfunction stripMultiplePackageSections(content: string, packageNames: string[]): { changed: boolean; content: string } {\n let changed = false;\n let current = content;\n for (const name of packageNames) {\n const result = stripPackageSection(current, name);\n if (result.changed) changed = true;\n current = result.content;\n }\n return { changed, content: current };\n}\n\n/** Discover platform root filenames from platform definitions */\nfunction getUniqueRootFilenames(): string[] {\n const set = new Set<string>([FILE_PATTERNS.AGENTS_MD]);\n for (const platform of getAllPlatforms()) {\n const def = getPlatformDefinition(platform);\n if (def.rootFile) set.add(def.rootFile);\n }\n return Array.from(set);\n}\n\n/**\n * Process root file removals: strip package sections from root files.\n * When dryRun is true, returns the list of files that would be updated without writing.\n * When dryRun is false (default), writes the changes and returns the updated files.\n */\nexport async function processRootFileRemovals(\n targetDir: string,\n packageNames: string[],\n options: { dryRun?: boolean } = {}\n): Promise<{ updated: string[] }> {\n const updated: string[] = [];\n const rootFiles = getUniqueRootFilenames();\n\n for (const filename of rootFiles) {\n const absPath = join(targetDir, filename);\n if (!(await exists(absPath))) continue;\n\n const original = await readTextFile(absPath);\n const { changed, content } = stripMultiplePackageSections(original, packageNames);\n if (!changed) continue;\n\n if (!options.dryRun) {\n await writeTextFile(absPath, content);\n logger.debug(`Updated root file: ${absPath}`);\n }\n\n updated.push(filename);\n }\n\n return { updated };\n}\n", "import { join, dirname } from 'path';\nimport * as yaml from 'js-yaml';\nimport { FILE_PATTERNS } from '../constants/index.js';\nimport { exists, readTextFile, writeTextFile, ensureDir } from './fs.js';\nimport { getLocalOpenPackageDir, getLocalPackageContentDir } from './paths.js';\nimport { normalizePathForProcessing } from './path-normalization.js';\nimport { logger } from './logger.js';\n\nconst HEADER_COMMENT = '# This file is managed by OpenPackage. Do not edit manually.';\n\nexport type PackageIndexLocation = 'root' | 'nested';\n\nexport interface PackageIndexWorkspace {\n hash?: string;\n version: string;\n}\n\nexport interface PackageIndexData {\n workspace: PackageIndexWorkspace;\n files: Record<string, string[]>;\n}\n\nexport interface PackageIndexRecord extends PackageIndexData {\n path: string;\n packageName: string;\n}\n\nexport function getPackageIndexPath(\n cwd: string,\n packageName: string,\n location: PackageIndexLocation = 'nested'\n): string {\n if (location === 'root') {\n return join(getLocalOpenPackageDir(cwd), FILE_PATTERNS.OPENPACKAGE_INDEX_YML);\n }\n\n // Nested: cached package root (cwd/.openpackage/packages/<name>/)\n const contentDir = getLocalPackageContentDir(cwd, packageName);\n return join(contentDir, FILE_PATTERNS.OPENPACKAGE_INDEX_YML);\n}\n\nexport function ensureTrailingSlash(value: string): string {\n return value.endsWith('/') ? value : `${value}/`;\n}\n\nexport function sortMapping(record: Record<string, any[]>): Record<string, any[]> {\n const sortedKeys = Object.keys(record).sort();\n const normalized: Record<string, any[]> = {};\n for (const key of sortedKeys) {\n const values = record[key] || [];\n const hasComplex = values.some(v => typeof v === 'object' && v !== null);\n if (hasComplex) {\n // Complex mappings - sort by target path\n const sorted = [...values].sort((a, b) => {\n const targetA = typeof a === 'string' ? a : a.target;\n const targetB = typeof b === 'string' ? b : b.target;\n return targetA.localeCompare(targetB);\n });\n // Dedupe by target\n const seen = new Set<string>();\n normalized[key] = sorted.filter(item => {\n const target = typeof item === 'string' ? item : item.target;\n if (seen.has(target)) return false;\n seen.add(target);\n return true;\n });\n } else {\n // Simple string array\n const sortedValues = [...new Set(values)].sort();\n normalized[key] = sortedValues;\n }\n }\n return normalized;\n}\n\nexport function sanitizeIndexData(data: any): PackageIndexData | null {\n if (!data || typeof data !== 'object') return null;\n\n let workspaceVer: string | undefined;\n let workspaceHash: string | undefined;\n\n const workspaceSection = (data as { workspace?: unknown }).workspace;\n if (workspaceSection && typeof workspaceSection === 'object') {\n const maybeVersion = (workspaceSection as { version?: unknown }).version;\n if (typeof maybeVersion === 'string') {\n workspaceVer = maybeVersion;\n }\n const maybeHash = (workspaceSection as { hash?: unknown }).hash;\n if (typeof maybeHash === 'string') {\n workspaceHash = maybeHash;\n }\n }\n\n if (typeof workspaceVer !== 'string') return null;\n\n const files = (data as { files?: unknown }).files;\n if (!files || typeof files !== 'object') return null;\n\n const entries: Record<string, string[]> = {};\n for (const [rawKey, rawValue] of Object.entries(files as Record<string, unknown>)) {\n if (typeof rawKey !== 'string') continue;\n if (!Array.isArray(rawValue)) continue;\n\n const cleanedValues = rawValue\n .filter((entry): entry is string => typeof entry === 'string' && entry.trim().length > 0)\n .map(value => normalizePathForProcessing(value));\n\n entries[normalizePathForProcessing(rawKey)] = cleanedValues;\n }\n\n return {\n workspace: {\n version: workspaceVer,\n hash: workspaceHash\n },\n files: sortMapping(entries)\n };\n}\n\nexport async function readPackageIndex(\n cwd: string,\n packageName: string,\n location: PackageIndexLocation = 'nested'\n): Promise<PackageIndexRecord | null> {\n const canonicalPath = getPackageIndexPath(cwd, packageName, location);\n const indexPath = canonicalPath;\n\n if (!(await exists(indexPath))) {\n return null;\n }\n\n try {\n const content = await readTextFile(indexPath);\n const parsed = yaml.load(content) as any;\n const sanitized = sanitizeIndexData(parsed);\n if (!sanitized) {\n logger.warn(`Invalid package index detected at ${indexPath}, will repair on write.`);\n return {\n path: indexPath,\n packageName,\n workspace: { version: '', hash: undefined },\n files: {}\n };\n }\n return {\n path: canonicalPath,\n packageName,\n workspace: sanitized.workspace,\n files: sanitized.files\n };\n } catch (error) {\n logger.warn(`Failed to read package index at ${indexPath}: ${error}`);\n return {\n path: canonicalPath,\n packageName,\n workspace: { version: '', hash: undefined },\n files: {}\n };\n }\n}\n\nexport async function writePackageIndex(record: PackageIndexRecord): Promise<void> {\n const { path: indexPath, files } = record;\n const workspaceVer = record.workspace?.version;\n if (!workspaceVer) {\n throw new Error(`workspace.version is required when writing ${FILE_PATTERNS.OPENPACKAGE_INDEX_YML}`);\n }\n const workspace: PackageIndexWorkspace = {\n hash: record.workspace?.hash,\n version: workspaceVer\n };\n await ensureDir(dirname(indexPath));\n\n const normalizedFiles = sortMapping(files);\n const body = yaml.dump(\n {\n workspace,\n files: normalizedFiles\n },\n {\n lineWidth: 120,\n sortKeys: true\n }\n );\n\n const serialized = `${HEADER_COMMENT}\\n\\n${body}`;\n await writeTextFile(indexPath, serialized);\n}\n\nexport function isDirKey(key: string): boolean {\n return key.endsWith('/');\n}\n\n/**\n * Prune nested child directories if their parent directory is already present.\n * Example: keep \"skills/nestjs/\" and drop \"skills/nestjs/examples/\".\n */\nexport function pruneNestedDirectories(dirs: string[]): string[] {\n const sorted = [...dirs].sort((a, b) => {\n if (a.length === b.length) {\n return a.localeCompare(b);\n }\n return a.length - b.length;\n });\n\n const pruned: string[] = [];\n for (const dir of sorted) {\n const hasParent = pruned.some(parent => dir !== parent && dir.startsWith(parent));\n if (!hasParent) {\n pruned.push(dir);\n }\n }\n return pruned;\n}\n\n", "/**\n * Flow-Aware Uninstaller\n * \n * Handles uninstallation of packages installed with flows,\n * including precise removal of keys from merged files.\n */\n\nimport { join } from 'path';\nimport { readTextFile, writeTextFile, exists, remove } from '../../utils/fs.js';\nimport { logger } from '../../utils/logger.js';\nimport type { WorkspaceIndexFileMapping } from '../../types/workspace-index.js';\nimport { deleteNestedKey, isEffectivelyEmpty } from '../flows/flow-key-extractor.js';\nimport yaml from 'js-yaml';\nimport * as TOML from 'smol-toml';\n\n/**\n * File format detection\n */\ntype FileFormat = 'json' | 'jsonc' | 'yaml' | 'yml' | 'toml' | 'text';\n\nfunction detectFileFormat(filePath: string): FileFormat {\n const ext = filePath.toLowerCase().split('.').pop();\n switch (ext) {\n case 'json':\n return 'json';\n case 'jsonc':\n return 'jsonc';\n case 'yaml':\n case 'yml':\n return 'yaml';\n case 'toml':\n return 'toml';\n default:\n return 'text';\n }\n}\n\n/**\n * Parse file content based on format\n */\nfunction parseContent(content: string, format: FileFormat): any {\n try {\n switch (format) {\n case 'json':\n case 'jsonc':\n // Strip comments for JSONC\n const cleaned = format === 'jsonc' \n ? content.replace(/\\/\\/.*$/gm, '').replace(/\\/\\*[\\s\\S]*?\\*\\//g, '')\n : content;\n return JSON.parse(cleaned);\n \n case 'yaml':\n case 'yml':\n return yaml.load(content);\n \n case 'toml':\n return TOML.parse(content);\n \n default:\n return content;\n }\n } catch (error) {\n throw new Error(`Failed to parse ${format} file: ${error instanceof Error ? error.message : String(error)}`);\n }\n}\n\n/**\n * Serialize content based on format\n */\nfunction serializeContent(data: any, format: FileFormat): string {\n try {\n switch (format) {\n case 'json':\n case 'jsonc':\n return JSON.stringify(data, null, 2);\n \n case 'yaml':\n case 'yml':\n return yaml.dump(data, { indent: 2, flowLevel: 1, lineWidth: -1 });\n \n case 'toml':\n return TOML.stringify(data);\n \n default:\n return typeof data === 'string' ? data : JSON.stringify(data, null, 2);\n }\n } catch (error) {\n throw new Error(`Failed to serialize ${format} file: ${error instanceof Error ? error.message : String(error)}`);\n }\n}\n\n/**\n * Remove specific keys from a merged file\n * Deletes the file if it becomes empty after removal\n * \n * @param targetDir - Target directory (workspace root or global home)\n * @param targetPath - Relative path to target file\n * @param keysToRemove - Dot-notated keys to remove\n * @returns true if file was deleted, false if updated\n */\nexport async function removeKeysFromMergedFile(\n targetDir: string,\n targetPath: string,\n keysToRemove: string[]\n): Promise<{ deleted: boolean; updated: boolean }> {\n const absPath = join(targetDir, targetPath);\n\n if (!(await exists(absPath))) {\n return { deleted: false, updated: false };\n }\n\n // Load and parse file\n const content = await readTextFile(absPath);\n const format = detectFileFormat(targetPath);\n let data: any;\n\n try {\n data = parseContent(content, format);\n } catch (error) {\n logger.warn(`Failed to parse ${targetPath} for key removal: ${error instanceof Error ? error.message : String(error)}`);\n return { deleted: false, updated: false };\n }\n\n const hasNestedKeyPath = (obj: any, keyPath: string): boolean => {\n if (!obj || typeof obj !== 'object') return false;\n const parts = keyPath.split('.').filter(Boolean);\n let current: any = obj;\n for (const part of parts) {\n if (!current || typeof current !== 'object' || !(part in current)) return false;\n current = current[part];\n }\n return true;\n };\n\n const existingBefore = keysToRemove.filter(k => typeof k === 'string' && hasNestedKeyPath(data, k)).length;\n // Remove each key\n for (const key of keysToRemove) {\n deleteNestedKey(data, key);\n }\n\n // Check if file is now empty\n if (isEffectivelyEmpty(data)) {\n await remove(absPath);\n logger.info(`Removed empty file: ${targetPath}`);\n return { deleted: true, updated: false };\n }\n\n // Write back updated content\n const serialized = serializeContent(data, format);\n await writeTextFile(absPath, serialized);\n logger.info(`Updated ${targetPath}: removed ${keysToRemove.length} keys`);\n return { deleted: false, updated: true };\n}\n\n/**\n * Remove a file mapping during uninstall\n * Handles both simple file removal and key-based removal from merged files\n * \n * @param targetDir - Target directory (workspace root or global home)\n * @param mapping - File mapping from workspace index\n * @param packageName - Package being uninstalled (for logging)\n * @returns Paths that were removed or updated\n */\nexport async function removeFileMapping(\n targetDir: string,\n mapping: string | WorkspaceIndexFileMapping,\n packageName: string\n): Promise<{ removed: string[]; updated: string[] }> {\n const removed: string[] = [];\n const updated: string[] = [];\n\n if (typeof mapping === 'string') {\n // Simple file mapping - delete entire file\n const absPath = join(targetDir, mapping);\n if (await exists(absPath)) {\n await remove(absPath);\n removed.push(mapping);\n logger.debug(`Removed file: ${mapping}`);\n }\n } else {\n // Complex mapping with potential key tracking\n const targetPath = mapping.target;\n\n if (mapping.merge === 'composite') {\n // Composite merge uses delimiters - handled by existing root file logic\n // This is already handled by applyRootFileRemovals\n logger.debug(`Skipping composite merge file (handled by root file logic): ${targetPath}`);\n } else if (mapping.keys && mapping.keys.length > 0) {\n // Remove specific keys from merged file\n const result = await removeKeysFromMergedFile(targetDir, targetPath, mapping.keys);\n \n if (result.deleted) {\n removed.push(targetPath);\n } else if (result.updated) {\n updated.push(targetPath);\n }\n\n logger.debug(\n `Removed ${mapping.keys.length} keys from ${targetPath}: ${mapping.keys.join(', ')}`\n );\n } else if (mapping.merge === 'deep' || mapping.merge === 'shallow') {\n // Merged file but no key tracking - this shouldn't happen with new installs\n logger.warn(\n `Cannot precisely remove ${targetPath} for ${packageName} - no key tracking available. ` +\n `File may contain content from other packages.`\n );\n // Don't delete - safer to leave it\n } else {\n // merge: 'replace' or no merge - delete entire file\n const absPath = join(targetDir, targetPath);\n if (await exists(absPath)) {\n await remove(absPath);\n removed.push(targetPath);\n logger.debug(`Removed file: ${targetPath}`);\n }\n }\n }\n\n return { removed, updated };\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["path", "join", "yaml", "join", "path"]
7
+ }
@@ -0,0 +1,61 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ resolveOutput
4
+ } from "./chunk-BROJ6OUT.js";
5
+
6
+ // ../core/src/core/list/list-tree-renderer.ts
7
+ var DIM = "\x1B[2m", RESET = "\x1B[0m", RED = "\x1B[31m";
8
+ function dim(text) {
9
+ return `${DIM}${text}${RESET}`;
10
+ }
11
+ function red(text) {
12
+ return `${RED}${text}${RESET}`;
13
+ }
14
+ function getTreeConnector(isLast, hasBranches) {
15
+ return isLast ? hasBranches ? "\u2514\u2500\u252C " : "\u2514\u2500\u2500 " : hasBranches ? "\u251C\u2500\u252C " : "\u251C\u2500\u2500 ";
16
+ }
17
+ function getChildPrefix(parentPrefix, isLast) {
18
+ return parentPrefix + (isLast ? " " : "\u2502 ");
19
+ }
20
+ function formatFileLabel(file, config) {
21
+ let filePath = config.formatPath(file);
22
+ return config.isMissing(file) ? `${dim(filePath)} ${red("[MISSING]")}` : dim(filePath);
23
+ }
24
+ function renderFlatFileList(files, prefix, config, output) {
25
+ let out = output ?? resolveOutput();
26
+ for (let fi = 0; fi < files.length; fi++) {
27
+ let file = files[fi], isLastFile = fi === files.length - 1, fileConnector = getTreeConnector(isLastFile, !1), label = formatFileLabel(file, config);
28
+ out.message(`${prefix}${fileConnector}${label}`);
29
+ }
30
+ }
31
+ function renderResource(resource, prefix, isLast, showFiles, config, output) {
32
+ let out = output ?? resolveOutput(), enhanced = resource, packageLabels = config.getResourcePackageLabels?.(enhanced.packages) ?? [], hasFileBranches = showFiles && resource.files.length > 0, connector = getTreeConnector(isLast, hasFileBranches), childPrefix = getChildPrefix(prefix, isLast), packagePrefix = hasFileBranches ? childPrefix + "\u2502 " : childPrefix, badge = config.getResourceBadge?.(enhanced.scopes) ?? "";
33
+ out.message(`${prefix}${connector}${resource.name}${badge ? " " + badge : ""}`);
34
+ let packageSpacing = hasFileBranches ? "" : " ";
35
+ for (let label of packageLabels)
36
+ out.message(`${packagePrefix}${packageSpacing}${label}`);
37
+ if (hasFileBranches) {
38
+ let sortedFiles = [...resource.files].sort(config.sortFiles);
39
+ renderFlatFileList(sortedFiles, childPrefix, config, output);
40
+ }
41
+ }
42
+ function flattenResourceGroups(groups) {
43
+ let flat = [];
44
+ for (let group of groups)
45
+ flat.push(...group.resources);
46
+ return flat.sort((a, b) => a.name.localeCompare(b.name));
47
+ }
48
+ function renderFlatResourceList(resources, prefix, showFiles, config, hasMoreSiblings, output) {
49
+ for (let ri = 0; ri < resources.length; ri++) {
50
+ let resource = resources[ri], isNaturalLast = ri === resources.length - 1;
51
+ renderResource(resource, prefix, hasMoreSiblings ? !1 : isNaturalLast, showFiles, config, output);
52
+ }
53
+ }
54
+
55
+ export {
56
+ getTreeConnector,
57
+ getChildPrefix,
58
+ flattenResourceGroups,
59
+ renderFlatResourceList
60
+ };
61
+ //# sourceMappingURL=chunk-HTYHJA3B.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../core/src/core/list/list-tree-renderer.ts"],
4
+ "sourcesContent": ["import type { ListResourceGroup, ListResourceInfo, ListFileMapping } from './list-pipeline.js';\nimport type { ResourceScope } from '../resources/scope-traversal.js';\nimport type { OutputPort } from '../ports/output.js';\nimport { resolveOutput } from '../ports/resolve.js';\n\nexport type { ResourceScope } from '../resources/scope-traversal.js';\n\n/**\n * Enhanced file mapping with status and scope\n */\nexport interface EnhancedFileMapping extends ListFileMapping {\n status: 'tracked' | 'untracked' | 'missing';\n scope: ResourceScope;\n}\n\n/**\n * Enhanced resource info with status and scopes\n */\nexport interface EnhancedResourceInfo {\n name: string;\n resourceType: string;\n files: EnhancedFileMapping[];\n status: 'tracked' | 'partial' | 'untracked' | 'mixed';\n scopes: Set<ResourceScope>;\n /** Package(s) this resource belongs to (tracked resources only) */\n packages?: Set<string>;\n}\n\n/**\n * Enhanced resource group\n */\nexport interface EnhancedResourceGroup {\n resourceType: string;\n resources: EnhancedResourceInfo[];\n}\n\n/**\n * Configuration for tree rendering behavior\n */\nexport interface TreeRenderConfig<TFile> {\n /** Function to format file path for display */\n formatPath: (file: TFile) => string;\n /** Function to check if file is missing */\n isMissing: (file: TFile) => boolean;\n /** Function to sort files */\n sortFiles: (a: TFile, b: TFile) => number;\n /** Optional badge/suffix for resource names */\n getResourceBadge?: (scopes?: Set<ResourceScope>) => string;\n /** Optional dimmed package labels shown under resource name, one line per package (vertical bar, no connector) */\n getResourcePackageLabels?: (packages?: Set<string>) => string[];\n}\n\n// ANSI color codes\nconst DIM = '\\x1b[2m';\nconst RESET = '\\x1b[0m';\nconst RED = '\\x1b[31m';\n\nfunction dim(text: string): string {\n return `${DIM}${text}${RESET}`;\n}\n\nfunction red(text: string): string {\n return `${RED}${text}${RESET}`;\n}\n\n/**\n * Get tree connector character based on position\n */\nexport function getTreeConnector(isLast: boolean, hasBranches: boolean): string {\n if (isLast) {\n return hasBranches ? '\u2514\u2500\u252C ' : '\u2514\u2500\u2500 ';\n }\n return hasBranches ? '\u251C\u2500\u252C ' : '\u251C\u2500\u2500 ';\n}\n\n/**\n * Calculate child prefix based on parent prefix and position\n */\nexport function getChildPrefix(parentPrefix: string, isLast: boolean): string {\n return parentPrefix + (isLast ? ' ' : '\u2502 ');\n}\n\n/**\n * Format file label with missing indicator if needed\n */\nexport function formatFileLabel<TFile>(\n file: TFile,\n config: TreeRenderConfig<TFile>\n): string {\n const filePath = config.formatPath(file);\n const isMissing = config.isMissing(file);\n \n return isMissing\n ? `${dim(filePath)} ${red('[MISSING]')}`\n : dim(filePath);\n}\n\n/**\n * Collect and sort all files from a resource group\n */\nexport function collectGroupFiles<TFile>(\n group: ListResourceGroup | EnhancedResourceGroup,\n config: TreeRenderConfig<TFile>\n): TFile[] {\n const allFiles: TFile[] = [];\n for (const resource of group.resources) {\n allFiles.push(...(resource.files as TFile[]));\n }\n return allFiles.sort(config.sortFiles);\n}\n\n/**\n * Render files directly under a group (for 'other' type)\n */\nexport function renderFlatFileList<TFile>(\n files: TFile[],\n prefix: string,\n config: TreeRenderConfig<TFile>,\n output?: OutputPort\n): void {\n const out = output ?? resolveOutput();\n for (let fi = 0; fi < files.length; fi++) {\n const file = files[fi];\n const isLastFile = fi === files.length - 1;\n const fileConnector = getTreeConnector(isLastFile, false);\n const label = formatFileLabel(file, config);\n out.message(`${prefix}${fileConnector}${label}`);\n }\n}\n\n/**\n * Render a single resource with its files\n */\nexport function renderResource<TFile>(\n resource: ListResourceInfo | EnhancedResourceInfo,\n prefix: string,\n isLast: boolean,\n showFiles: boolean,\n config: TreeRenderConfig<TFile>,\n output?: OutputPort\n): void {\n const out = output ?? resolveOutput();\n const enhanced = resource as EnhancedResourceInfo;\n const packageLabels = config.getResourcePackageLabels?.(enhanced.packages) ?? [];\n\n // Single source of truth: file branches (\u251C\u2500\u252C/\u2514\u2500\u252C) only when -f and resource has files.\n // Package-only uses \u251C\u2500\u2500/\u2514\u2500\u2500; double \u2502 for package labels only when file branches exist.\n const hasFileBranches = showFiles && resource.files.length > 0;\n\n const connector = getTreeConnector(isLast, hasFileBranches);\n const childPrefix = getChildPrefix(prefix, isLast);\n const packagePrefix = hasFileBranches ? childPrefix + '\u2502 ' : childPrefix;\n\n // Resource name with optional badge\n const badge = config.getResourceBadge?.(enhanced.scopes) ?? '';\n out.message(`${prefix}${connector}${resource.name}${badge ? ' ' + badge : ''}`);\n\n // Package labels: dimmed (package) under resource name, one per package.\n // With -f: align (package) with resource name (no extra spacing); without -f: 2 spaces.\n const packageSpacing = hasFileBranches ? '' : ' ';\n for (const label of packageLabels) {\n out.message(`${packagePrefix}${packageSpacing}${label}`);\n }\n\n // Render files if requested\n if (hasFileBranches) {\n const sortedFiles = [...(resource.files as TFile[])].sort(config.sortFiles);\n renderFlatFileList(sortedFiles, childPrefix, config, output);\n }\n}\n\n/**\n * Flatten resource groups into a single sorted list of resources.\n * Shared by resources view, deps view, and remote package detail.\n */\nexport function flattenResourceGroups<T extends { name: string; files: unknown[] }>(\n groups: Array<{ resourceType: string; resources: T[] }>\n): T[] {\n const flat: T[] = [];\n for (const group of groups) {\n flat.push(...group.resources);\n }\n return flat.sort((a, b) => a.name.localeCompare(b.name));\n}\n\n/**\n * Render a flat list of resources (no category grouping).\n * Each resource is displayed as category/namespace with optional file children.\n * @param hasMoreSiblings - when true, the last resource uses \u251C instead of \u2514 (more siblings follow)\n */\nexport function renderFlatResourceList<TFile>(\n resources: (ListResourceInfo | EnhancedResourceInfo)[],\n prefix: string,\n showFiles: boolean,\n config: TreeRenderConfig<TFile>,\n hasMoreSiblings?: boolean,\n output?: OutputPort\n): void {\n for (let ri = 0; ri < resources.length; ri++) {\n const resource = resources[ri];\n const isNaturalLast = ri === resources.length - 1;\n const isLast = hasMoreSiblings ? false : isNaturalLast;\n renderResource(resource, prefix, isLast, showFiles, config, output);\n }\n}\n\n/**\n * Render a single resource group with all its resources.\n * Reserved for potential future hierarchical (grouped) display.\n * Currently unused; list/view use renderFlatResourceList.\n *\n * @deprecated Unused - kept for potential hierarchical view support\n */\nexport function renderResourceGroup<TFile>(\n group: ListResourceGroup | EnhancedResourceGroup,\n prefix: string,\n isLast: boolean,\n showFiles: boolean,\n config: TreeRenderConfig<TFile>,\n output?: OutputPort\n): void {\n const out = output ?? resolveOutput();\n const isOtherGroup = group.resourceType === 'other';\n \n if (isOtherGroup) {\n // Flatten: show files directly without resource subcategories\n const allFiles = collectGroupFiles<TFile>(group, config);\n const totalFileCount = allFiles.length;\n const hasFiles = showFiles && allFiles.length > 0;\n \n const connector = getTreeConnector(isLast, hasFiles);\n out.message(`${prefix}${connector}${group.resourceType}${dim(` (${totalFileCount})`)}`);\n \n if (hasFiles) {\n const childPrefix = getChildPrefix(prefix, isLast);\n renderFlatFileList(allFiles, childPrefix, config, output);\n }\n } else {\n // Normal: show resources as subcategories, then files\n const hasResources = group.resources.length > 0;\n const connector = getTreeConnector(isLast, hasResources);\n const childPrefix = getChildPrefix(prefix, isLast);\n \n out.message(`${prefix}${connector}${group.resourceType}${dim(` (${group.resources.length})`)}`);\n \n for (let ri = 0; ri < group.resources.length; ri++) {\n const resource = group.resources[ri];\n const isLastResource = ri === group.resources.length - 1;\n renderResource(resource, childPrefix, isLastResource, showFiles, config, output);\n }\n }\n}\n"],
5
+ "mappings": ";;;;;;AAqDA,IAAM,MAAM,WACN,QAAQ,WACR,MAAM;AAEZ,SAAS,IAAI,MAAsB;AACjC,SAAO,GAAG,GAAG,GAAG,IAAI,GAAG,KAAK;AAC9B;AAEA,SAAS,IAAI,MAAsB;AACjC,SAAO,GAAG,GAAG,GAAG,IAAI,GAAG,KAAK;AAC9B;AAKO,SAAS,iBAAiB,QAAiB,aAA8B;AAC9E,SAAI,SACK,cAAc,wBAAS,wBAEzB,cAAc,wBAAS;AAChC;AAKO,SAAS,eAAe,cAAsB,QAAyB;AAC5E,SAAO,gBAAgB,SAAS,OAAO;AACzC;AAKO,SAAS,gBACd,MACA,QACQ;AACR,MAAM,WAAW,OAAO,WAAW,IAAI;AAGvC,SAFkB,OAAO,UAAU,IAAI,IAGnC,GAAG,IAAI,QAAQ,CAAC,IAAI,IAAI,WAAW,CAAC,KACpC,IAAI,QAAQ;AAClB;AAmBO,SAAS,mBACd,OACA,QACA,QACA,QACM;AACN,MAAM,MAAM,UAAU,cAAc;AACpC,WAAS,KAAK,GAAG,KAAK,MAAM,QAAQ,MAAM;AACxC,QAAM,OAAO,MAAM,EAAE,GACf,aAAa,OAAO,MAAM,SAAS,GACnC,gBAAgB,iBAAiB,YAAY,EAAK,GAClD,QAAQ,gBAAgB,MAAM,MAAM;AAC1C,QAAI,QAAQ,GAAG,MAAM,GAAG,aAAa,GAAG,KAAK,EAAE;AAAA,EACjD;AACF;AAKO,SAAS,eACd,UACA,QACA,QACA,WACA,QACA,QACM;AACN,MAAM,MAAM,UAAU,cAAc,GAC9B,WAAW,UACX,gBAAgB,OAAO,2BAA2B,SAAS,QAAQ,KAAK,CAAC,GAIzE,kBAAkB,aAAa,SAAS,MAAM,SAAS,GAEvD,YAAY,iBAAiB,QAAQ,eAAe,GACpD,cAAc,eAAe,QAAQ,MAAM,GAC3C,gBAAgB,kBAAkB,cAAc,YAAO,aAGvD,QAAQ,OAAO,mBAAmB,SAAS,MAAM,KAAK;AAC5D,MAAI,QAAQ,GAAG,MAAM,GAAG,SAAS,GAAG,SAAS,IAAI,GAAG,QAAQ,MAAM,QAAQ,EAAE,EAAE;AAI9E,MAAM,iBAAiB,kBAAkB,KAAK;AAC9C,WAAW,SAAS;AAClB,QAAI,QAAQ,GAAG,aAAa,GAAG,cAAc,GAAG,KAAK,EAAE;AAIzD,MAAI,iBAAiB;AACnB,QAAM,cAAc,CAAC,GAAI,SAAS,KAAiB,EAAE,KAAK,OAAO,SAAS;AAC1E,uBAAmB,aAAa,aAAa,QAAQ,MAAM;AAAA,EAC7D;AACF;AAMO,SAAS,sBACd,QACK;AACL,MAAM,OAAY,CAAC;AACnB,WAAW,SAAS;AAClB,SAAK,KAAK,GAAG,MAAM,SAAS;AAE9B,SAAO,KAAK,KAAK,CAAC,GAAG,MAAM,EAAE,KAAK,cAAc,EAAE,IAAI,CAAC;AACzD;AAOO,SAAS,uBACd,WACA,QACA,WACA,QACA,iBACA,QACM;AACN,WAAS,KAAK,GAAG,KAAK,UAAU,QAAQ,MAAM;AAC5C,QAAM,WAAW,UAAU,EAAE,GACvB,gBAAgB,OAAO,UAAU,SAAS;AAEhD,mBAAe,UAAU,QADV,kBAAkB,KAAQ,eACA,WAAW,QAAQ,MAAM;AAAA,EACpE;AACF;",
6
+ "names": []
7
+ }