@astryxdesign/cli 0.6.3-canary.ea2f048 → 0.6.3-canary.ebaebc4

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 (188) hide show
  1. package/README.md +2 -1
  2. package/api/build/build.type.d.mts +2 -2
  3. package/api/build/build.type.mjs +2 -2
  4. package/api/component/component.type.d.mts +6 -6
  5. package/api/component/component.type.mjs +19 -19
  6. package/api/discover/discover.type.d.mts +4 -4
  7. package/api/discover/discover.type.mjs +10 -10
  8. package/api/docs/_adapter.d.mts +37 -24
  9. package/api/docs/_adapter.mjs +169 -83
  10. package/api/docs/compiled-topics.test.mjs +78 -0
  11. package/api/docs/detail/detail.mjs +14 -63
  12. package/api/docs/detail/section/section.d.mts +1 -1
  13. package/api/docs/detail/section/section.mjs +44 -20
  14. package/api/docs/detail/section/section.test.mjs +41 -0
  15. package/api/docs/docs.d.mts +7 -2
  16. package/api/docs/docs.doc.mjs +27 -10
  17. package/api/docs/docs.mjs +16 -9
  18. package/api/docs/docs.test.mjs +6 -0
  19. package/api/docs/docs.type.d.mts +40 -3
  20. package/api/docs/docs.type.mjs +36 -8
  21. package/api/docs/index/index.d.mts +18 -0
  22. package/api/docs/index/index.mjs +32 -0
  23. package/api/docs/index/index.test.mjs +62 -0
  24. package/api/docs/integrationDocs.test.mjs +106 -0
  25. package/api/doctor/doctor.d.mts +48 -0
  26. package/api/doctor/doctor.mjs +232 -0
  27. package/api/doctor/doctor.test.mjs +196 -0
  28. package/api/hook/hook.type.d.mts +3 -3
  29. package/api/hook/hook.type.mjs +11 -11
  30. package/api/hook/list/list.d.mts +1 -1
  31. package/api/integration/add-contribution.mjs +5 -3
  32. package/api/integration/add-contribution.test.mjs +4 -4
  33. package/api/integration/integration-authoring.type.d.mts +1 -1
  34. package/api/integration/pack-check.mjs +49 -7
  35. package/api/integration/pack-check.test.mjs +249 -0
  36. package/api/search/search.d.mts +1 -1
  37. package/api/search/search.mjs +5 -5
  38. package/api/search/search.type.d.mts +2 -2
  39. package/api/search/search.type.mjs +1 -1
  40. package/api/swizzle/swizzle.type.d.mts +2 -2
  41. package/api/swizzle/swizzle.type.mjs +2 -2
  42. package/api/template/template.d.mts +1 -1
  43. package/api/template/template.type.d.mts +6 -6
  44. package/api/template/template.type.mjs +12 -12
  45. package/api/theme/build/build.mjs +20 -6
  46. package/api/theme/build/build.test.mjs +127 -0
  47. package/api/theme/palette/generate/generate.mjs +1 -1
  48. package/api/theme/palette/generate/generator.d.mts +10 -13
  49. package/api/theme/palette/generate/generator.mjs +7 -3
  50. package/api/theme/theme.type.d.mts +170 -11
  51. package/api/theme/theme.type.mjs +94 -27
  52. package/api/upgrade/_adapter.mjs +71 -5
  53. package/api/upgrade/project-context.test.mjs +272 -0
  54. package/api/upgrade/upgrade.doc.mjs +4 -3
  55. package/api/upgrade/upgrade.type.d.mts +5 -5
  56. package/api/upgrade/upgrade.type.mjs +11 -11
  57. package/assets/codemods/integration-discovery.mjs +40 -2
  58. package/assets/codemods/integration-discovery.test.mjs +58 -0
  59. package/assets/codemods/transforms/v0.3.0/__tests__/unwrap-authoring-factories.test.mjs +27 -5
  60. package/assets/codemods/transforms/v0.3.0/unwrap-authoring-factories.mjs +20 -5
  61. package/assets/docs/README.md +9 -0
  62. package/assets/docs/authoring.doc.mjs +14 -0
  63. package/assets/docs/cli-integrations.doc.mjs +86 -15
  64. package/assets/docs/styling-libraries.doc.mjs +1 -1
  65. package/assets/docs/working-with-ai.doc.mjs +1 -1
  66. package/authoring/_shared/contract.ts +22 -0
  67. package/authoring/codemod/codemod.doc.mjs +6 -1
  68. package/authoring/codemod/parse.d.mts +8 -8
  69. package/authoring/codemod/parse.mjs +8 -6
  70. package/authoring/config/parse.d.mts +13 -13
  71. package/authoring/config/parse.mjs +8 -8
  72. package/authoring/config/type.ts +3 -3
  73. package/authoring/debug/parse.d.mts +5 -5
  74. package/authoring/debug/parse.mjs +3 -3
  75. package/authoring/doctypes/_schema.d.mts +788 -23
  76. package/authoring/doctypes/_schema.mjs +492 -39
  77. package/authoring/doctypes/base/graph-fields.doc.d.mts +9 -0
  78. package/authoring/doctypes/base/graph-fields.doc.mjs +62 -0
  79. package/authoring/doctypes/base/type.ts +40 -0
  80. package/authoring/doctypes/command/command.doc.mjs +3 -2
  81. package/authoring/doctypes/command/parse.d.mts +2 -2
  82. package/authoring/doctypes/command/parse.mjs +1 -1
  83. package/authoring/doctypes/command/type.ts +3 -2
  84. package/authoring/doctypes/component/component.doc.mjs +6 -3
  85. package/authoring/doctypes/component/parse.d.mts +2 -2
  86. package/authoring/doctypes/component/parse.mjs +1 -1
  87. package/authoring/doctypes/component/type.ts +4 -3
  88. package/authoring/doctypes/enum/parse.d.mts +2 -2
  89. package/authoring/doctypes/enum/parse.mjs +1 -1
  90. package/authoring/doctypes/enum/type.ts +3 -1
  91. package/authoring/doctypes/function/function.doc.mjs +4 -0
  92. package/authoring/doctypes/function/parse.d.mts +2 -2
  93. package/authoring/doctypes/function/parse.mjs +1 -1
  94. package/authoring/doctypes/function/type.ts +6 -2
  95. package/authoring/doctypes/hook/hook.doc.mjs +4 -0
  96. package/authoring/doctypes/hook/parse.d.mts +2 -2
  97. package/authoring/doctypes/hook/parse.mjs +1 -1
  98. package/authoring/doctypes/hook/type.ts +3 -2
  99. package/authoring/doctypes/legacy.d.mts +8 -6
  100. package/authoring/doctypes/legacy.mjs +5 -4
  101. package/authoring/doctypes/load-contract.test.mjs +207 -0
  102. package/authoring/doctypes/namespace/namespace.doc.d.mts +9 -0
  103. package/authoring/doctypes/namespace/namespace.doc.mjs +132 -0
  104. package/authoring/doctypes/namespace/parse.d.mts +12 -0
  105. package/authoring/doctypes/namespace/parse.mjs +25 -0
  106. package/authoring/doctypes/namespace/parse.test.mjs +165 -0
  107. package/authoring/doctypes/namespace/type.ts +71 -0
  108. package/authoring/doctypes/parse.d.mts +20 -18
  109. package/authoring/doctypes/parse.mjs +16 -10
  110. package/authoring/doctypes/parse.test.mjs +77 -3
  111. package/authoring/doctypes/reference/parse.d.mts +2 -2
  112. package/authoring/doctypes/reference/parse.mjs +8 -5
  113. package/authoring/doctypes/reference/reference.doc.mjs +17 -4
  114. package/authoring/doctypes/reference/type.ts +51 -5
  115. package/authoring/doctypes/schema/parse.d.mts +2 -2
  116. package/authoring/doctypes/schema/parse.mjs +1 -1
  117. package/authoring/doctypes/schema/type.ts +3 -2
  118. package/authoring/doctypes/template/parse.d.mts +92 -1
  119. package/authoring/doctypes/template/parse.mjs +36 -2
  120. package/authoring/doctypes/template/parse.test.mjs +8 -2
  121. package/authoring/doctypes/template/template.doc.mjs +4 -0
  122. package/authoring/doctypes/template/type.ts +5 -2
  123. package/authoring/doctypes/types.ts +10 -9
  124. package/authoring/gap-report/parse.d.mts +10 -10
  125. package/authoring/gap-report/parse.mjs +6 -6
  126. package/authoring/gap-report/type.ts +1 -1
  127. package/authoring/identity/identity.doc.d.mts +9 -0
  128. package/authoring/identity/identity.doc.mjs +61 -0
  129. package/authoring/identity/type.ts +132 -0
  130. package/authoring/index.d.mts +1 -0
  131. package/authoring/index.d.ts +49 -17
  132. package/authoring/index.mjs +1 -0
  133. package/authoring/integration/integration.doc.mjs +13 -6
  134. package/authoring/integration/parse.d.mts +2 -2
  135. package/authoring/integration/parse.mjs +1 -1
  136. package/authoring/integration/parse.test.mjs +10 -1
  137. package/authoring/integration/schema.d.mts +6 -4
  138. package/authoring/integration/schema.mjs +9 -3
  139. package/authoring/integration/type.ts +23 -6
  140. package/authoring/shadcn/receipt.d.mts +6 -6
  141. package/clients/cli/commands/docs.doc.mjs +13 -3
  142. package/clients/cli/commands/docs.mjs +121 -21
  143. package/clients/cli/commands/docs.test.mjs +88 -0
  144. package/clients/cli/commands/integration-authoring.test.mjs +13 -9
  145. package/clients/cli/commands/theme-palette-generate.doc.mjs +8 -4
  146. package/clients/cli/commands/upgrade.doc.mjs +2 -2
  147. package/clients/cli/formatters/index.mjs +162 -1
  148. package/clients/cli/formatters/index.test.mjs +91 -0
  149. package/clients/cli/lib/manifest.mjs +7 -2
  150. package/foundation/config/project.mjs +21 -6
  151. package/foundation/discovery/authoring-self-docs.d.mts +69 -0
  152. package/foundation/discovery/authoring-self-docs.mjs +214 -0
  153. package/foundation/discovery/authoring-self-docs.test.mjs +154 -0
  154. package/foundation/discovery/component-discovery.d.mts +1 -1
  155. package/foundation/discovery/component-discovery.mjs +2 -1
  156. package/foundation/discovery/docs-discovery.d.mts +11 -4
  157. package/foundation/discovery/docs-discovery.mjs +208 -88
  158. package/foundation/discovery/docs-discovery.test.mjs +279 -13
  159. package/foundation/discovery/docs-output-budget.d.mts +28 -0
  160. package/foundation/discovery/docs-output-budget.mjs +50 -0
  161. package/foundation/discovery/docs-section-key.d.mts +98 -0
  162. package/foundation/discovery/docs-section-key.mjs +221 -0
  163. package/foundation/discovery/docs-section-key.test.mjs +224 -0
  164. package/foundation/discovery/template-adapter.mjs +2 -1
  165. package/foundation/discovery/theming-targets.test.mjs +4 -0
  166. package/foundation/doc-compiler/compile.d.mts +162 -0
  167. package/foundation/doc-compiler/compile.mjs +262 -0
  168. package/foundation/doc-compiler/doc-compiler.test.mjs +687 -0
  169. package/foundation/doc-compiler/ir.d.mts +9 -0
  170. package/foundation/doc-compiler/ir.mjs +287 -0
  171. package/foundation/doc-compiler/lenses.d.mts +33 -0
  172. package/foundation/doc-compiler/lenses.mjs +127 -0
  173. package/foundation/identity/provider-identity.d.mts +90 -0
  174. package/foundation/identity/provider-identity.mjs +320 -0
  175. package/foundation/identity/provider-identity.test.mjs +254 -0
  176. package/foundation/identity/providers.d.mts +7 -0
  177. package/foundation/identity/providers.mjs +16 -0
  178. package/foundation/integrations/autolink.mjs +12 -5
  179. package/foundation/integrations/integration-warnings.mjs +6 -0
  180. package/foundation/integrations/integrations.d.mts +46 -2
  181. package/foundation/integrations/integrations.mjs +167 -8
  182. package/foundation/integrations/integrations.test.mjs +384 -1
  183. package/foundation/integrations/provider-conflicts.test.mjs +125 -0
  184. package/foundation/integrations/validate-contributions.d.mts +2 -0
  185. package/foundation/integrations/validate-contributions.mjs +10 -0
  186. package/foundation/response/json-contract.test.mjs +46 -17
  187. package/foundation/response/response-types.doc.mjs +6 -1
  188. package/package.json +9 -11
@@ -1,6 +1,23 @@
1
1
  // @generated by scripts/sync-api-types.mjs from the JSDoc in foundation/**/*.mjs.
2
2
  // DO NOT EDIT — run `pnpm sync:api-types` to regenerate.
3
3
 
4
+ /**
5
+ * Resolve provider identity across loaded integrations, in precedence order.
6
+ *
7
+ * Artifact and document IDs are provider-scoped, so two packages cannot both
8
+ * contribute under one provider ID. The same package reached twice (an npm
9
+ * alias beside the package it aliases, with the same name and version) loads
10
+ * once. Any other later claimant, including the same package at another
11
+ * version, stays as an inert entry that carries the conflict. The package being
12
+ * authored claims its ID before precedence order is considered, because the
13
+ * source being edited is authoritative. The winner still contributes; Project
14
+ * issues, Doctor, and the per-command warning name the one set aside instead of
15
+ * dropping it without a word.
16
+ *
17
+ * @param {LoadedIntegration[]} integrations in precedence order
18
+ * @returns {LoadedIntegration[]}
19
+ */
20
+ export function markProviderConflicts(integrations: LoadedIntegration[]): LoadedIntegration[];
4
21
  /**
5
22
  * Return the conventional root manifest paths present in `dir`, in
6
23
  * load-precedence order. Unlike {@link resolveManifestPath} this never throws —
@@ -75,12 +92,15 @@ export function loadLocalIntegration(packageDir: string, { fresh }?: {
75
92
  * Load configured integrations.
76
93
  *
77
94
  * @param {string[]} [specs] package names
78
- * @param {{cwd?: string, fresh?: boolean}} [options]
95
+ * @param {{cwd?: string, fresh?: boolean, resolveProviders?: boolean}} [options]
96
+ * `resolveProviders: false` leaves provider identity to a caller that
97
+ * resolves it once over a larger set (Project.load)
79
98
  * @returns {Promise<LoadedIntegration[]>}
80
99
  */
81
- export function loadIntegrations(specs?: string[], { cwd, fresh }?: {
100
+ export function loadIntegrations(specs?: string[], { cwd, fresh, resolveProviders }?: {
82
101
  cwd?: string;
83
102
  fresh?: boolean;
103
+ resolveProviders?: boolean;
84
104
  }): Promise<LoadedIntegration[]>;
85
105
  /**
86
106
  * A fully-resolved, loaded integration. Identity (`name`, `version`) comes from
@@ -89,6 +109,10 @@ export function loadIntegrations(specs?: string[], { cwd, fresh }?: {
89
109
  * are internal bookkeeping used by Doctor integration validation and Project.
90
110
  * @typedef {object} LoadedIntegration
91
111
  * @property {string} name
112
+ * @property {import('../../authoring/identity/type').ProviderId} [providerId]
113
+ * normalized stable provider identity; absent only for a legacy unnamed local
114
+ * package. Unique among contributing integrations: a later claimant is kept
115
+ * inert with `__providerConflict` (see {@link markProviderConflicts})
92
116
  * @property {string} [version]
93
117
  * @property {string} [components]
94
118
  * @property {string} [templates]
@@ -107,6 +131,10 @@ export function loadIntegrations(specs?: string[], { cwd, fresh }?: {
107
131
  * @property {string} __manifestFile
108
132
  * @property {string} [__loadError] set when the manifest failed to load/validate;
109
133
  * such an integration contributes nothing and is surfaced via Project.issues()
134
+ * @property {{providerId: import('../../authoring/identity/type').ProviderId, claimedBy: string, message: string}} [__providerConflict]
135
+ * set on a package whose provider ID an earlier-loaded package already
136
+ * claims; it contributes nothing and is reported as a `duplicate_provider`
137
+ * warning
110
138
  * @property {string[]} [__unknownKeys] manifest keys this CLI does not know —
111
139
  * surfaced as a warning; the rest of the manifest still contributes
112
140
  * @property {boolean} [__autolinked] loaded because the project declares the
@@ -134,6 +162,12 @@ export const MANIFEST_BASENAMES: string[];
134
162
  */
135
163
  export type LoadedIntegration = {
136
164
  name: string;
165
+ /**
166
+ * normalized stable provider identity; absent only for a legacy unnamed local
167
+ * package. Unique among contributing integrations: a later claimant is kept
168
+ * inert with `__providerConflict` (see {@link markProviderConflicts})
169
+ */
170
+ providerId?: import("../../authoring/identity/type").ProviderId | undefined;
137
171
  version?: string | undefined;
138
172
  components?: string | undefined;
139
173
  templates?: string | undefined;
@@ -163,6 +197,16 @@ export type LoadedIntegration = {
163
197
  * such an integration contributes nothing and is surfaced via Project.issues()
164
198
  */
165
199
  __loadError?: string | undefined;
200
+ /**
201
+ * set on a package whose provider ID an earlier-loaded package already
202
+ * claims; it contributes nothing and is reported as a `duplicate_provider`
203
+ * warning
204
+ */
205
+ __providerConflict?: {
206
+ providerId: import("../../authoring/identity/type").ProviderId;
207
+ claimedBy: string;
208
+ message: string;
209
+ } | undefined;
166
210
  /**
167
211
  * manifest keys this CLI does not know —
168
212
  * surfaced as a warning; the rest of the manifest still contributes
@@ -6,13 +6,19 @@
6
6
  * Integrations are PACKAGE NAMES listed in astryx.config.{ts,mjs,js}. Each
7
7
  * package declares a single conventional root manifest sibling to its
8
8
  * package.json — astryx.integration.{ts,mjs,js} — which contributes
9
- * components/templates/codemods/docs/themes roots and an optional issuesUrl. Identity
10
- * (name, version) comes from the package's package.json, not the manifest.
9
+ * components/templates/codemods/docs/themes roots and an optional issuesUrl.
10
+ * Stable provider identity and package metadata come from package.json, not the
11
+ * manifest.
12
+ *
13
+ * @input Integration package names, package.json files, and root manifests.
14
+ * @output LoadedIntegration records with normalized provider identity and roots.
15
+ * @position foundation/integrations — package contribution loading boundary.
11
16
  */
12
17
 
13
18
  import * as fs from 'node:fs';
14
19
  import * as path from 'node:path';
15
20
  import {assertWithin} from '../fs/path-safety.mjs';
21
+ import {normalizeProviderId} from '../identity/provider-identity.mjs';
16
22
  // The key census and contribution parsers are internal to the schema module on
17
23
  // purpose: the public parser still validates the complete authored type, while
18
24
  // the loader can isolate an invalid optional contribution from valid roots.
@@ -31,6 +37,10 @@ import {parseGapReportHandler} from '../../authoring/gap-report/parse.mjs';
31
37
  * are internal bookkeeping used by Doctor integration validation and Project.
32
38
  * @typedef {object} LoadedIntegration
33
39
  * @property {string} name
40
+ * @property {import('../../authoring/identity/type').ProviderId} [providerId]
41
+ * normalized stable provider identity; absent only for a legacy unnamed local
42
+ * package. Unique among contributing integrations: a later claimant is kept
43
+ * inert with `__providerConflict` (see {@link markProviderConflicts})
34
44
  * @property {string} [version]
35
45
  * @property {string} [components]
36
46
  * @property {string} [templates]
@@ -49,6 +59,10 @@ import {parseGapReportHandler} from '../../authoring/gap-report/parse.mjs';
49
59
  * @property {string} __manifestFile
50
60
  * @property {string} [__loadError] set when the manifest failed to load/validate;
51
61
  * such an integration contributes nothing and is surfaced via Project.issues()
62
+ * @property {{providerId: import('../../authoring/identity/type').ProviderId, claimedBy: string, message: string}} [__providerConflict]
63
+ * set on a package whose provider ID an earlier-loaded package already
64
+ * claims; it contributes nothing and is reported as a `duplicate_provider`
65
+ * warning
52
66
  * @property {string[]} [__unknownKeys] manifest keys this CLI does not know —
53
67
  * surfaced as a warning; the rest of the manifest still contributes
54
68
  * @property {boolean} [__autolinked] loaded because the project declares the
@@ -74,6 +88,137 @@ export const MANIFEST_BASENAMES = [
74
88
  'astryx.integration.js',
75
89
  ];
76
90
 
91
+ /**
92
+ * Normalize a package-owned provider ID without rejecting legacy unnamed local
93
+ * packages at this compatibility boundary.
94
+ * @param {unknown} name
95
+ * @returns {import('../../authoring/identity/type').ProviderId | undefined}
96
+ */
97
+ function providerIdForPackage(name) {
98
+ if (typeof name !== 'string' || name.length === 0) return undefined;
99
+ try {
100
+ return normalizeProviderId(name);
101
+ } catch {
102
+ return undefined;
103
+ }
104
+ }
105
+
106
+ /**
107
+ * Resolve provider identity across loaded integrations, in precedence order.
108
+ *
109
+ * Artifact and document IDs are provider-scoped, so two packages cannot both
110
+ * contribute under one provider ID. The same package reached twice (an npm
111
+ * alias beside the package it aliases, with the same name and version) loads
112
+ * once. Any other later claimant, including the same package at another
113
+ * version, stays as an inert entry that carries the conflict. The package being
114
+ * authored claims its ID before precedence order is considered, because the
115
+ * source being edited is authoritative. The winner still contributes; Project
116
+ * issues, Doctor, and the per-command warning name the one set aside instead of
117
+ * dropping it without a word.
118
+ *
119
+ * @param {LoadedIntegration[]} integrations in precedence order
120
+ * @returns {LoadedIntegration[]}
121
+ */
122
+ export function markProviderConflicts(integrations) {
123
+ /** @type {Map<string, LoadedIntegration>} */
124
+ const claims = new Map();
125
+ for (const integration of integrations) {
126
+ if (integration?.__local && claimsProvider(integration)) {
127
+ const providerId = /** @type {string} */ (integration.providerId);
128
+ if (!claims.has(providerId)) claims.set(providerId, integration);
129
+ }
130
+ }
131
+ /** @type {LoadedIntegration[]} */
132
+ const resolved = [];
133
+ for (const integration of integrations) {
134
+ if (!claimsProvider(integration)) {
135
+ resolved.push(integration);
136
+ continue;
137
+ }
138
+ const providerId = /** @type {string} */ (integration.providerId);
139
+ const claimant = claims.get(providerId);
140
+ if (claimant == null) {
141
+ claims.set(providerId, integration);
142
+ resolved.push(integration);
143
+ } else if (claimant === integration) {
144
+ resolved.push(integration);
145
+ } else if (
146
+ claimant.name !== integration.name ||
147
+ claimant.version !== integration.version
148
+ ) {
149
+ resolved.push(providerConflict(integration, claimant));
150
+ }
151
+ }
152
+ return resolved;
153
+ }
154
+
155
+ /**
156
+ * Whether an entry contributes under its provider ID: it has one, its manifest
157
+ * loaded, and it has not already been set aside.
158
+ * @param {LoadedIntegration | undefined} integration
159
+ * @returns {boolean}
160
+ */
161
+ function claimsProvider(integration) {
162
+ return (
163
+ integration?.providerId != null &&
164
+ integration.__loadError == null &&
165
+ integration.__providerConflict == null
166
+ );
167
+ }
168
+
169
+ /**
170
+ * @param {LoadedIntegration} integration
171
+ * @returns {string}
172
+ */
173
+ function describeIntegration(integration) {
174
+ const id = integration.version
175
+ ? `${integration.name}@${integration.version}`
176
+ : integration.name;
177
+ return integration.__spec && integration.__spec !== integration.name
178
+ ? `${id} (from "${integration.__spec}")`
179
+ : id;
180
+ }
181
+
182
+ /**
183
+ * An inert record for a package whose provider ID is already claimed. It keeps
184
+ * the package's identity and location for reporting, and drops every
185
+ * contribution root and handler.
186
+ * @param {LoadedIntegration} integration
187
+ * @param {LoadedIntegration} claimant
188
+ * @returns {LoadedIntegration}
189
+ */
190
+ function providerConflict(integration, claimant) {
191
+ const providerId =
192
+ /** @type {import('../../authoring/identity/type').ProviderId} */ (
193
+ integration.providerId
194
+ );
195
+ const winner = describeIntegration(claimant);
196
+ const setAside = describeIntegration(integration);
197
+ const reason = claimant.__local
198
+ ? 'is the package being authored, so it is used'
199
+ : 'loads first and is used';
200
+ return {
201
+ name: integration.name,
202
+ providerId,
203
+ version: integration.version,
204
+ __spec: integration.__spec,
205
+ __packageDir: integration.__packageDir,
206
+ __manifestFile: integration.__manifestFile,
207
+ ...(integration.__local ? {__local: true} : {}),
208
+ ...(integration.__autolinked
209
+ ? {__autolinked: true, __dependencyField: integration.__dependencyField}
210
+ : {}),
211
+ __providerConflict: {
212
+ providerId,
213
+ claimedBy: claimant.name,
214
+ message:
215
+ `${setAside} and ${winner} both claim provider ID "${providerId}". ` +
216
+ `${winner} ${reason}; ${setAside} contributes nothing ` +
217
+ 'until one package changes its providerId.',
218
+ },
219
+ };
220
+ }
221
+
77
222
  /**
78
223
  * Return the conventional root manifest paths present in `dir`, in
79
224
  * load-precedence order. Unlike {@link resolveManifestPath} this never throws —
@@ -274,6 +419,7 @@ export async function loadLocalIntegration(packageDir, {fresh = false} = {}) {
274
419
  typeof pkg.name === 'string' && pkg.name.length > 0
275
420
  ? pkg.name
276
421
  : '(local integration)';
422
+ const packageProviderId = providerIdForPackage(pkg.name);
277
423
  const manifestFile = resolveManifestPath(packageDir, spec);
278
424
 
279
425
  let manifest;
@@ -299,6 +445,7 @@ export async function loadLocalIntegration(packageDir, {fresh = false} = {}) {
299
445
  } catch (err) {
300
446
  return {
301
447
  name: spec,
448
+ ...(packageProviderId == null ? {} : {providerId: packageProviderId}),
302
449
  version: pkg.version,
303
450
  __spec: spec,
304
451
  __packageDir: packageDir,
@@ -308,6 +455,8 @@ export async function loadLocalIntegration(packageDir, {fresh = false} = {}) {
308
455
  };
309
456
  }
310
457
 
458
+ const providerId = providerIdForPackage(manifest.providerId ?? pkg.name);
459
+
311
460
  /** @param {string | null | undefined} value */
312
461
  const resolveRoot = value => {
313
462
  if (value == null) return undefined;
@@ -320,6 +469,7 @@ export async function loadLocalIntegration(packageDir, {fresh = false} = {}) {
320
469
 
321
470
  return {
322
471
  name: spec,
472
+ ...(providerId == null ? {} : {providerId}),
323
473
  version: pkg.version,
324
474
  components: resolveRoot(manifest.components),
325
475
  templates: resolveRoot(manifest.templates),
@@ -345,20 +495,22 @@ export async function loadLocalIntegration(packageDir, {fresh = false} = {}) {
345
495
  * Load configured integrations.
346
496
  *
347
497
  * @param {string[]} [specs] package names
348
- * @param {{cwd?: string, fresh?: boolean}} [options]
498
+ * @param {{cwd?: string, fresh?: boolean, resolveProviders?: boolean}} [options]
499
+ * `resolveProviders: false` leaves provider identity to a caller that
500
+ * resolves it once over a larger set (Project.load)
349
501
  * @returns {Promise<LoadedIntegration[]>}
350
502
  */
351
503
  export async function loadIntegrations(
352
504
  specs = [],
353
- {cwd = process.cwd(), fresh = false} = {},
505
+ {cwd = process.cwd(), fresh = false, resolveProviders = true} = {},
354
506
  ) {
355
507
  /** @type {LoadedIntegration[]} */
356
508
  const integrations = [];
357
- const seen = new Set();
509
+ const seenSpecs = new Set();
358
510
 
359
511
  for (const spec of specs) {
360
- if (!spec || seen.has(spec)) continue;
361
- seen.add(spec);
512
+ if (!spec || seenSpecs.has(spec)) continue;
513
+ seenSpecs.add(spec);
362
514
 
363
515
  const packageDir = resolvePackageDir(spec, cwd);
364
516
  const pkgPath = path.join(packageDir, 'package.json');
@@ -371,6 +523,7 @@ export async function loadIntegrations(
371
523
  );
372
524
  }
373
525
 
526
+ const packageProviderId = providerIdForPackage(pkg.name ?? spec);
374
527
  const manifestFile = resolveManifestPath(packageDir, spec);
375
528
  let manifest;
376
529
  /** @type {string[]} */
@@ -399,6 +552,7 @@ export async function loadIntegrations(
399
552
  // discovery loops naturally skip it (no components/templates/codemods).
400
553
  integrations.push({
401
554
  name: pkg.name ?? spec,
555
+ ...(packageProviderId == null ? {} : {providerId: packageProviderId}),
402
556
  version: pkg.version,
403
557
  __spec: spec,
404
558
  __packageDir: packageDir,
@@ -408,6 +562,10 @@ export async function loadIntegrations(
408
562
  continue;
409
563
  }
410
564
 
565
+ const providerId = providerIdForPackage(
566
+ manifest.providerId ?? pkg.name ?? spec,
567
+ );
568
+
411
569
  /** @param {string | null | undefined} value */
412
570
  const resolveRoot = value => {
413
571
  if (value == null) return undefined;
@@ -421,6 +579,7 @@ export async function loadIntegrations(
421
579
 
422
580
  integrations.push({
423
581
  name: pkg.name ?? spec,
582
+ ...(providerId == null ? {} : {providerId}),
424
583
  version: pkg.version,
425
584
  components: resolveRoot(manifest.components),
426
585
  templates: resolveRoot(manifest.templates),
@@ -441,5 +600,5 @@ export async function loadIntegrations(
441
600
  });
442
601
  }
443
602
 
444
- return integrations;
603
+ return resolveProviders ? markProviderConflicts(integrations) : integrations;
445
604
  }