@aglyn/plugins-marketplace 1.0.0-beta.143

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 (165) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +7 -0
  3. package/package.json +51 -0
  4. package/src/index.d.ts +19 -0
  5. package/src/index.js +20 -0
  6. package/src/index.js.map +1 -0
  7. package/src/lib/components/artifact-update-dialog.component.d.ts +27 -0
  8. package/src/lib/components/artifact-update-dialog.component.js +244 -0
  9. package/src/lib/components/artifact-update-dialog.component.js.map +1 -0
  10. package/src/lib/components/host-plugins-card.component.d.ts +23 -0
  11. package/src/lib/components/host-plugins-card.component.js +566 -0
  12. package/src/lib/components/host-plugins-card.component.js.map +1 -0
  13. package/src/lib/components/listing-content.component.d.ts +57 -0
  14. package/src/lib/components/listing-content.component.js +1880 -0
  15. package/src/lib/components/listing-content.component.js.map +1 -0
  16. package/src/lib/components/listing-image.component.d.ts +46 -0
  17. package/src/lib/components/listing-image.component.js +38 -0
  18. package/src/lib/components/listing-image.component.js.map +1 -0
  19. package/src/lib/components/listing-reviews.component.d.ts +22 -0
  20. package/src/lib/components/listing-reviews.component.js +419 -0
  21. package/src/lib/components/listing-reviews.component.js.map +1 -0
  22. package/src/lib/components/marketplace-browse.component.d.ts +38 -0
  23. package/src/lib/components/marketplace-browse.component.js +802 -0
  24. package/src/lib/components/marketplace-browse.component.js.map +1 -0
  25. package/src/lib/components/plugin-site-set-panel.component.d.ts +24 -0
  26. package/src/lib/components/plugin-site-set-panel.component.js +137 -0
  27. package/src/lib/components/plugin-site-set-panel.component.js.map +1 -0
  28. package/src/lib/components/plugin-site-set.component.d.ts +43 -0
  29. package/src/lib/components/plugin-site-set.component.js +425 -0
  30. package/src/lib/components/plugin-site-set.component.js.map +1 -0
  31. package/src/lib/components/rating-input.component.d.ts +3 -0
  32. package/src/lib/components/rating-input.component.js +47 -0
  33. package/src/lib/components/rating-input.component.js.map +1 -0
  34. package/src/lib/components/report-target.component.d.ts +36 -0
  35. package/src/lib/components/report-target.component.js +181 -0
  36. package/src/lib/components/report-target.component.js.map +1 -0
  37. package/src/lib/components/uninstall-impact-dialog.component.d.ts +40 -0
  38. package/src/lib/components/uninstall-impact-dialog.component.js +244 -0
  39. package/src/lib/components/uninstall-impact-dialog.component.js.map +1 -0
  40. package/src/lib/constants/bundle-common.d.ts +8 -0
  41. package/src/lib/constants/bundle-common.js +9 -0
  42. package/src/lib/constants/bundle-common.js.map +1 -0
  43. package/src/lib/hooks/use-artifact-update.d.ts +41 -0
  44. package/src/lib/hooks/use-artifact-update.js +169 -0
  45. package/src/lib/hooks/use-artifact-update.js.map +1 -0
  46. package/src/lib/hooks/use-marketplace-actions.d.ts +37 -0
  47. package/src/lib/hooks/use-marketplace-actions.js +523 -0
  48. package/src/lib/hooks/use-marketplace-actions.js.map +1 -0
  49. package/src/lib/model/artifact-merge.d.ts +154 -0
  50. package/src/lib/model/artifact-merge.js +228 -0
  51. package/src/lib/model/artifact-merge.js.map +1 -0
  52. package/src/lib/model/index.d.ts +43 -0
  53. package/src/lib/model/index.js +41 -0
  54. package/src/lib/model/index.js.map +1 -0
  55. package/src/lib/model/marketplace-props.d.ts +57 -0
  56. package/src/lib/model/marketplace-props.js +441 -0
  57. package/src/lib/model/marketplace-props.js.map +1 -0
  58. package/src/lib/model/marketplace.d.ts +833 -0
  59. package/src/lib/model/marketplace.js +929 -0
  60. package/src/lib/model/marketplace.js.map +1 -0
  61. package/src/lib/model/rating-field.d.ts +24 -0
  62. package/src/lib/model/rating-field.js +33 -0
  63. package/src/lib/model/rating-field.js.map +1 -0
  64. package/src/lib/plugin.d.ts +33 -0
  65. package/src/lib/plugin.js +86 -0
  66. package/src/lib/plugin.js.map +1 -0
  67. package/src/lib/server/billing-webhook.d.ts +25 -0
  68. package/src/lib/server/billing-webhook.js +1062 -0
  69. package/src/lib/server/billing-webhook.js.map +1 -0
  70. package/src/lib/server/checkout.d.ts +76 -0
  71. package/src/lib/server/checkout.js +477 -0
  72. package/src/lib/server/checkout.js.map +1 -0
  73. package/src/lib/server/connect.d.ts +24 -0
  74. package/src/lib/server/connect.js +205 -0
  75. package/src/lib/server/connect.js.map +1 -0
  76. package/src/lib/server/install-dataset-schema.d.ts +32 -0
  77. package/src/lib/server/install-dataset-schema.js +255 -0
  78. package/src/lib/server/install-dataset-schema.js.map +1 -0
  79. package/src/lib/server/install-email-starter.d.ts +65 -0
  80. package/src/lib/server/install-email-starter.js +279 -0
  81. package/src/lib/server/install-email-starter.js.map +1 -0
  82. package/src/lib/server/install-email-template.d.ts +34 -0
  83. package/src/lib/server/install-email-template.js +252 -0
  84. package/src/lib/server/install-email-template.js.map +1 -0
  85. package/src/lib/server/install-layout.d.ts +29 -0
  86. package/src/lib/server/install-layout.js +323 -0
  87. package/src/lib/server/install-layout.js.map +1 -0
  88. package/src/lib/server/install-pin-counts.d.ts +173 -0
  89. package/src/lib/server/install-pin-counts.js +248 -0
  90. package/src/lib/server/install-pin-counts.js.map +1 -0
  91. package/src/lib/server/install-plugin.d.ts +18 -0
  92. package/src/lib/server/install-plugin.js +405 -0
  93. package/src/lib/server/install-plugin.js.map +1 -0
  94. package/src/lib/server/install-template.d.ts +37 -0
  95. package/src/lib/server/install-template.js +348 -0
  96. package/src/lib/server/install-template.js.map +1 -0
  97. package/src/lib/server/install-theme.d.ts +42 -0
  98. package/src/lib/server/install-theme.js +313 -0
  99. package/src/lib/server/install-theme.js.map +1 -0
  100. package/src/lib/server/install.d.ts +27 -0
  101. package/src/lib/server/install.js +276 -0
  102. package/src/lib/server/install.js.map +1 -0
  103. package/src/lib/server/listing-versions.d.ts +26 -0
  104. package/src/lib/server/listing-versions.js +378 -0
  105. package/src/lib/server/listing-versions.js.map +1 -0
  106. package/src/lib/server/preview-image.d.ts +37 -0
  107. package/src/lib/server/preview-image.js +216 -0
  108. package/src/lib/server/preview-image.js.map +1 -0
  109. package/src/lib/server/provenance.d.ts +100 -0
  110. package/src/lib/server/provenance.js +118 -0
  111. package/src/lib/server/provenance.js.map +1 -0
  112. package/src/lib/server/publish-dataset-schema.d.ts +31 -0
  113. package/src/lib/server/publish-dataset-schema.js +202 -0
  114. package/src/lib/server/publish-dataset-schema.js.map +1 -0
  115. package/src/lib/server/publish-email-starter.d.ts +44 -0
  116. package/src/lib/server/publish-email-starter.js +231 -0
  117. package/src/lib/server/publish-email-starter.js.map +1 -0
  118. package/src/lib/server/publish-email-template.d.ts +33 -0
  119. package/src/lib/server/publish-email-template.js +211 -0
  120. package/src/lib/server/publish-email-template.js.map +1 -0
  121. package/src/lib/server/publish-layout.d.ts +33 -0
  122. package/src/lib/server/publish-layout.js +243 -0
  123. package/src/lib/server/publish-layout.js.map +1 -0
  124. package/src/lib/server/publish-plugin.d.ts +18 -0
  125. package/src/lib/server/publish-plugin.js +485 -0
  126. package/src/lib/server/publish-plugin.js.map +1 -0
  127. package/src/lib/server/publish-preconditions.d.ts +86 -0
  128. package/src/lib/server/publish-preconditions.js +160 -0
  129. package/src/lib/server/publish-preconditions.js.map +1 -0
  130. package/src/lib/server/publish-template.d.ts +27 -0
  131. package/src/lib/server/publish-template.js +215 -0
  132. package/src/lib/server/publish-template.js.map +1 -0
  133. package/src/lib/server/publish-theme.d.ts +38 -0
  134. package/src/lib/server/publish-theme.js +186 -0
  135. package/src/lib/server/publish-theme.js.map +1 -0
  136. package/src/lib/server/publish.d.ts +26 -0
  137. package/src/lib/server/publish.js +204 -0
  138. package/src/lib/server/publish.js.map +1 -0
  139. package/src/lib/server/publisher-profile-save.d.ts +30 -0
  140. package/src/lib/server/publisher-profile-save.js +226 -0
  141. package/src/lib/server/publisher-profile-save.js.map +1 -0
  142. package/src/lib/server/publisher-profile.d.ts +86 -0
  143. package/src/lib/server/publisher-profile.js +116 -0
  144. package/src/lib/server/publisher-profile.js.map +1 -0
  145. package/src/lib/server/purchase-entitlement.d.ts +83 -0
  146. package/src/lib/server/purchase-entitlement.js +118 -0
  147. package/src/lib/server/purchase-entitlement.js.map +1 -0
  148. package/src/lib/server/report.d.ts +32 -0
  149. package/src/lib/server/report.js +101 -0
  150. package/src/lib/server/report.js.map +1 -0
  151. package/src/lib/server/reviews.d.ts +41 -0
  152. package/src/lib/server/reviews.js +240 -0
  153. package/src/lib/server/reviews.js.map +1 -0
  154. package/src/lib/server/update-artifact.d.ts +19 -0
  155. package/src/lib/server/update-artifact.js +569 -0
  156. package/src/lib/server/update-artifact.js.map +1 -0
  157. package/src/lib/server/verification-request.d.ts +31 -0
  158. package/src/lib/server/verification-request.js +173 -0
  159. package/src/lib/server/verification-request.js.map +1 -0
  160. package/src/lib/server/version-stats.d.ts +202 -0
  161. package/src/lib/server/version-stats.js +305 -0
  162. package/src/lib/server/version-stats.js.map +1 -0
  163. package/src/lib/server.d.ts +30 -0
  164. package/src/lib/server.js +95 -0
  165. package/src/lib/server.js.map +1 -0
@@ -0,0 +1,154 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ /**
18
+ * Three-way diff for COPIED marketplace artifacts (AGL-1018).
19
+ *
20
+ * Plugins are pinned: an update moves a pointer at immutable bytes and nothing
21
+ * can be lost. Everything else — components, layouts, templates, dataset
22
+ * schemas, email templates — is copied into the workspace and then edited, so
23
+ * an update is the vendor-branch problem. Re-installing overwrote the copy, and
24
+ * a customer's edits went with it, silently.
25
+ *
26
+ * With AGL-1015's base snapshot every field is classifiable against three
27
+ * versions, and the classification is the whole design:
28
+ *
29
+ * * changed upstream, untouched here → **safe**, take it
30
+ * * changed here, untouched upstream → **kept**, theirs is already right
31
+ * * changed on both sides → **conflict**, ask, and default to keeping theirs
32
+ *
33
+ * Pure and content-shaped on purpose: it knows nothing about Firestore or
34
+ * artifact types, so the same function classifies a component tree, a layout
35
+ * and a dataset schema, and can be tested without a database.
36
+ */
37
+ /** How a single field changed across base → current → incoming. */
38
+ export type ChangeKind =
39
+ /** Upstream changed it, the user did not. Applying is lossless. */
40
+ 'safe'
41
+ /** The user changed it, upstream did not. Applying would lose their edit. */
42
+ | 'kept'
43
+ /** Both changed it, to different values. Needs a decision. */
44
+ | 'conflict';
45
+ export interface ArtifactChange {
46
+ /** Dotted path into the content, e.g. `nodes.abc123.props.title`. */
47
+ path: string;
48
+ kind: ChangeKind;
49
+ /** The publisher's original value at install. */
50
+ base: unknown;
51
+ /** What the workspace has now. */
52
+ current: unknown;
53
+ /** What the publisher ships in the new version. */
54
+ incoming: unknown;
55
+ /**
56
+ * True when the change adds or removes the field rather than editing it.
57
+ * A removal is worth saying out loud separately: for a dataset schema it
58
+ * means existing records, and for a node tree it means content disappearing
59
+ * from a page rather than merely looking different.
60
+ */
61
+ added?: boolean;
62
+ removed?: boolean;
63
+ }
64
+ export interface ArtifactUpdatePlan {
65
+ safe: ArtifactChange[];
66
+ kept: ArtifactChange[];
67
+ conflicts: ArtifactChange[];
68
+ /** Fields identical in all three versions — reported as a count only. */
69
+ unchanged: number;
70
+ /** Nothing to take: the incoming content matches what is installed. */
71
+ identical: boolean;
72
+ }
73
+ /**
74
+ * Classifies every field of an artifact against its base, the workspace's
75
+ * current copy, and the version on offer.
76
+ *
77
+ * `base` is required and not defaulted: without the snapshot there is no way to
78
+ * tell the publisher's change from the user's, and guessing at one is how you
79
+ * produce a confident, wrong merge. Callers with no base offer "install as a
80
+ * new copy" and nothing else.
81
+ */
82
+ export declare function planArtifactUpdate(base: unknown, current: unknown, incoming: unknown): ArtifactUpdatePlan;
83
+ export interface ApplyUpdateOptions {
84
+ /**
85
+ * Conflicting paths the user chose to take from the publisher. Everything
86
+ * absent from this list keeps the workspace's value — the default is always
87
+ * the safe one, so a user who applies without reading loses nothing.
88
+ */
89
+ takePaths?: readonly string[];
90
+ }
91
+ export interface AppliedUpdate {
92
+ content: unknown;
93
+ /** Paths actually written, for a summary that reports what happened. */
94
+ applied: string[];
95
+ /** Conflicting paths left at the workspace's value. */
96
+ skipped: string[];
97
+ }
98
+ /**
99
+ * Produces the merged content: the workspace's copy with every safe change
100
+ * taken and only the explicitly chosen conflicts overwritten.
101
+ *
102
+ * Built by patching `current` rather than by patching `incoming`, because the
103
+ * copy is what the site is running: anything this function fails to consider —
104
+ * a field the publisher never shipped, a node the user added — survives by
105
+ * default rather than vanishing.
106
+ */
107
+ export declare function applyArtifactUpdate(plan: ArtifactUpdatePlan, current: unknown, options?: ApplyUpdateOptions): AppliedUpdate;
108
+ /**
109
+ * A short human label for a change, so the dialog, the summary snackbar and the
110
+ * server's response all describe the same edit the same way.
111
+ *
112
+ * Values are summarised, never dumped: the interesting thing about a changed
113
+ * node is which node and what kind of change, and a page of serialised JSON in
114
+ * a list row is unreadable by the time it matters.
115
+ */
116
+ export declare function describeChange(change: ArtifactChange): string;
117
+ /** A one-line value preview for a diff row. */
118
+ export declare function summarizeValue(value: unknown): string;
119
+ /**
120
+ * Dataset schemas, classified by what an update would do to EXISTING RECORDS
121
+ * (AGL-1018).
122
+ *
123
+ * The sharp edge of the whole project: adding a field is inert, but removing or
124
+ * retyping one reinterprets data that is already there. So the schema plan is
125
+ * not "safe vs conflict" but "additive vs destructive", and destructive changes
126
+ * are never taken without a count of the records at stake.
127
+ */
128
+ export interface SchemaChangeSummary {
129
+ /** Field ids the new version adds. Applying these cannot harm a record. */
130
+ added: string[];
131
+ /** Field ids it removes — the values in existing records become orphaned. */
132
+ removed: string[];
133
+ /** Field ids whose TYPE changed — existing values may not survive a re-read. */
134
+ retyped: string[];
135
+ /** Anything else (labels, options, ordering). */
136
+ edited: string[];
137
+ /** True when nothing removes or retypes a field. */
138
+ additiveOnly: boolean;
139
+ }
140
+ interface SchemaShape {
141
+ order?: unknown;
142
+ fields?: Record<string, {
143
+ type?: unknown;
144
+ } | undefined>;
145
+ }
146
+ /**
147
+ * Compares two dataset schemas field by field.
148
+ *
149
+ * Deliberately ignores the base: for a schema the question is not "who edited
150
+ * this" but "what happens to the rows", and that is answered by the current
151
+ * schema against the incoming one regardless of who moved which field.
152
+ */
153
+ export declare function summarizeSchemaChange(current: SchemaShape | null | undefined, incoming: SchemaShape | null | undefined): SchemaChangeSummary;
154
+ export {};
@@ -0,0 +1,228 @@
1
+ import { _ as _extends } from "@swc/helpers/_/_extends";
2
+ /**
3
+ * @license
4
+ * Copyright 2026 Aglyn LLC
5
+ *
6
+ * Licensed under the Apache License, Version 2.0 (the "License");
7
+ * you may not use this file except in compliance with the License.
8
+ * You may obtain a copy of the License at
9
+ *
10
+ * http://www.apache.org/licenses/LICENSE-2.0
11
+ *
12
+ * Unless required by applicable law or agreed to in writing, software
13
+ * distributed under the License is distributed on an "AS IS" BASIS,
14
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ * See the License for the specific language governing permissions and
16
+ * limitations under the License.
17
+ */ import { stableStringify } from "@aglyn/aglyn/app-utils/marketplace-provenance";
18
+ /**
19
+ * Paths whose CHILDREN are compared as a unit rather than walked.
20
+ *
21
+ * A besigner node is `{id, type, props, childIds}`. Walking into it produces
22
+ * true field-level granularity, which is what the issue asks for; but the
23
+ * `childIds` array is where structure lives, and comparing arrays element-wise
24
+ * makes a single insert read as a change to every position after it. Arrays are
25
+ * therefore atomic everywhere (see {@link isAtomic}), and node maps are walked
26
+ * by KEY — which is the structural, id-keyed comparison a node tree needs, and
27
+ * the reason a re-ordered tree does not read as a conflict in every node.
28
+ */ function isAtomic(value) {
29
+ return value === null || typeof value !== 'object' || Array.isArray(value) || // Dates, Firestore timestamps and anything else non-plain: comparing their
30
+ // enumerable members would compare internals, not content.
31
+ Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null;
32
+ }
33
+ function same(a, b) {
34
+ return stableStringify(a) === stableStringify(b);
35
+ }
36
+ const ABSENT = Symbol('absent');
37
+ function get(container, key) {
38
+ if (container === ABSENT || container == null || typeof container !== 'object') {
39
+ return ABSENT;
40
+ }
41
+ const record = container;
42
+ return key in record ? record[key] : ABSENT;
43
+ }
44
+ function keysOf(...containers) {
45
+ const keys = [];
46
+ const seen = new Set();
47
+ for (const container of containers){
48
+ if (container === ABSENT || container == null || typeof container !== 'object') {
49
+ continue;
50
+ }
51
+ for (const key of Object.keys(container)){
52
+ if (!seen.has(key)) {
53
+ seen.add(key);
54
+ keys.push(key);
55
+ }
56
+ }
57
+ }
58
+ return keys;
59
+ }
60
+ /**
61
+ * Classifies every field of an artifact against its base, the workspace's
62
+ * current copy, and the version on offer.
63
+ *
64
+ * `base` is required and not defaulted: without the snapshot there is no way to
65
+ * tell the publisher's change from the user's, and guessing at one is how you
66
+ * produce a confident, wrong merge. Callers with no base offer "install as a
67
+ * new copy" and nothing else.
68
+ */ export function planArtifactUpdate(base, current, incoming) {
69
+ const plan = {
70
+ safe: [],
71
+ kept: [],
72
+ conflicts: [],
73
+ unchanged: 0,
74
+ identical: false
75
+ };
76
+ const walk = (baseValue, currentValue, incomingValue, path)=>{
77
+ const userChanged = !same(baseValue, currentValue);
78
+ const publisherChanged = !same(baseValue, incomingValue);
79
+ if (!userChanged && !publisherChanged) {
80
+ plan.unchanged += 1;
81
+ return;
82
+ }
83
+ // Recurse while all three sides still have a walkable object here, so an
84
+ // edit to one node's text and an upstream edit to a different node are two
85
+ // independent changes rather than one whole-tree conflict.
86
+ //
87
+ // A side that is ABSENT stops the walk: the field was added or removed
88
+ // wholesale, and reporting that as one change ("this node is gone") is both
89
+ // truer and more readable than reporting every leaf inside it.
90
+ const walkable = baseValue !== ABSENT && currentValue !== ABSENT && incomingValue !== ABSENT && !isAtomic(baseValue) && !isAtomic(currentValue) && !isAtomic(incomingValue);
91
+ if (walkable) {
92
+ for (const key of keysOf(baseValue, currentValue, incomingValue)){
93
+ walk(get(baseValue, key), get(currentValue, key), get(incomingValue, key), path ? `${path}.${key}` : key);
94
+ }
95
+ return;
96
+ }
97
+ const change = _extends({
98
+ path,
99
+ kind: userChanged ? publisherChanged ? 'conflict' : 'kept' : 'safe',
100
+ base: baseValue === ABSENT ? undefined : baseValue,
101
+ current: currentValue === ABSENT ? undefined : currentValue,
102
+ incoming: incomingValue === ABSENT ? undefined : incomingValue
103
+ }, baseValue === ABSENT ? {
104
+ added: true
105
+ } : {}, incomingValue === ABSENT && baseValue !== ABSENT ? {
106
+ removed: true
107
+ } : {});
108
+ // A conflict where both sides landed on the SAME value is not a conflict —
109
+ // two people fixing the same typo agree.
110
+ if (change.kind === 'conflict' && same(currentValue, incomingValue)) {
111
+ plan.unchanged += 1;
112
+ return;
113
+ }
114
+ if (change.kind === 'safe') plan.safe.push(change);
115
+ else if (change.kind === 'kept') plan.kept.push(change);
116
+ else plan.conflicts.push(change);
117
+ };
118
+ walk(base, current, incoming, '');
119
+ plan.identical = same(current, incoming);
120
+ return plan;
121
+ }
122
+ function setPath(target, path, value) {
123
+ if (!path.length) return value === ABSENT ? undefined : value;
124
+ const [head, ...rest] = path;
125
+ const container = target != null && typeof target === 'object' && !Array.isArray(target) ? _extends({}, target) : {};
126
+ if (!rest.length) {
127
+ if (value === ABSENT) delete container[head];
128
+ else container[head] = value;
129
+ return container;
130
+ }
131
+ container[head] = setPath(container[head], rest, value);
132
+ return container;
133
+ }
134
+ /**
135
+ * Produces the merged content: the workspace's copy with every safe change
136
+ * taken and only the explicitly chosen conflicts overwritten.
137
+ *
138
+ * Built by patching `current` rather than by patching `incoming`, because the
139
+ * copy is what the site is running: anything this function fails to consider —
140
+ * a field the publisher never shipped, a node the user added — survives by
141
+ * default rather than vanishing.
142
+ */ export function applyArtifactUpdate(plan, current, options) {
143
+ var _ref;
144
+ const take = new Set((_ref = options == null ? void 0 : options.takePaths) != null ? _ref : []);
145
+ const applied = [];
146
+ const skipped = [];
147
+ let content = current;
148
+ const write = (change)=>{
149
+ const segments = change.path ? change.path.split('.') : [];
150
+ content = setPath(content, segments, change.removed ? ABSENT : change.incoming);
151
+ applied.push(change.path);
152
+ };
153
+ for (const change of plan.safe)write(change);
154
+ for (const change of plan.conflicts){
155
+ if (take.has(change.path)) write(change);
156
+ else skipped.push(change.path);
157
+ }
158
+ return {
159
+ content,
160
+ applied,
161
+ skipped
162
+ };
163
+ }
164
+ /**
165
+ * A short human label for a change, so the dialog, the summary snackbar and the
166
+ * server's response all describe the same edit the same way.
167
+ *
168
+ * Values are summarised, never dumped: the interesting thing about a changed
169
+ * node is which node and what kind of change, and a page of serialised JSON in
170
+ * a list row is unreadable by the time it matters.
171
+ */ export function describeChange(change) {
172
+ const label = change.path || 'the whole artifact';
173
+ if (change.added) return `${label} — added by the publisher`;
174
+ if (change.removed) return `${label} — removed by the publisher`;
175
+ return label;
176
+ }
177
+ /** A one-line value preview for a diff row. */ export function summarizeValue(value) {
178
+ if (value === undefined) return '—';
179
+ if (value === null) return 'null';
180
+ if (typeof value === 'string') {
181
+ return value.length > 60 ? `${value.slice(0, 57)}…` : value || '(empty)';
182
+ }
183
+ if (typeof value !== 'object') return String(value);
184
+ if (Array.isArray(value)) {
185
+ return `${value.length} item${value.length === 1 ? '' : 's'}`;
186
+ }
187
+ const keys = Object.keys(value);
188
+ return `{ ${keys.slice(0, 3).join(', ')}${keys.length > 3 ? ', …' : ''} }`;
189
+ }
190
+ /**
191
+ * Compares two dataset schemas field by field.
192
+ *
193
+ * Deliberately ignores the base: for a schema the question is not "who edited
194
+ * this" but "what happens to the rows", and that is answered by the current
195
+ * schema against the incoming one regardless of who moved which field.
196
+ */ export function summarizeSchemaChange(current, incoming) {
197
+ var _ref, _ref1;
198
+ const currentFields = (_ref = current == null ? void 0 : current.fields) != null ? _ref : {};
199
+ const incomingFields = (_ref1 = incoming == null ? void 0 : incoming.fields) != null ? _ref1 : {};
200
+ const added = [];
201
+ const removed = [];
202
+ const retyped = [];
203
+ const edited = [];
204
+ for (const id of Object.keys(incomingFields)){
205
+ var _currentFields_id, _incomingFields_id;
206
+ if (!(id in currentFields)) {
207
+ added.push(id);
208
+ continue;
209
+ }
210
+ if (String((_currentFields_id = currentFields[id]) == null ? void 0 : _currentFields_id.type) !== String((_incomingFields_id = incomingFields[id]) == null ? void 0 : _incomingFields_id.type)) {
211
+ retyped.push(id);
212
+ } else if (!same(currentFields[id], incomingFields[id])) {
213
+ edited.push(id);
214
+ }
215
+ }
216
+ for (const id of Object.keys(currentFields)){
217
+ if (!(id in incomingFields)) removed.push(id);
218
+ }
219
+ return {
220
+ added,
221
+ removed,
222
+ retyped,
223
+ edited,
224
+ additiveOnly: !removed.length && !retyped.length
225
+ };
226
+ }
227
+
228
+ //# sourceMappingURL=artifact-merge.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/marketplace/src/lib/model/artifact-merge.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { stableStringify } from '@aglyn/aglyn/app-utils/marketplace-provenance'\n\n/**\n * Three-way diff for COPIED marketplace artifacts (AGL-1018).\n *\n * Plugins are pinned: an update moves a pointer at immutable bytes and nothing\n * can be lost. Everything else — components, layouts, templates, dataset\n * schemas, email templates — is copied into the workspace and then edited, so\n * an update is the vendor-branch problem. Re-installing overwrote the copy, and\n * a customer's edits went with it, silently.\n *\n * With AGL-1015's base snapshot every field is classifiable against three\n * versions, and the classification is the whole design:\n *\n * * changed upstream, untouched here → **safe**, take it\n * * changed here, untouched upstream → **kept**, theirs is already right\n * * changed on both sides → **conflict**, ask, and default to keeping theirs\n *\n * Pure and content-shaped on purpose: it knows nothing about Firestore or\n * artifact types, so the same function classifies a component tree, a layout\n * and a dataset schema, and can be tested without a database.\n */\n\n/** How a single field changed across base → current → incoming. */\nexport type ChangeKind =\n /** Upstream changed it, the user did not. Applying is lossless. */\n | 'safe'\n /** The user changed it, upstream did not. Applying would lose their edit. */\n | 'kept'\n /** Both changed it, to different values. Needs a decision. */\n | 'conflict'\n\nexport interface ArtifactChange {\n /** Dotted path into the content, e.g. `nodes.abc123.props.title`. */\n path: string\n kind: ChangeKind\n /** The publisher's original value at install. */\n base: unknown\n /** What the workspace has now. */\n current: unknown\n /** What the publisher ships in the new version. */\n incoming: unknown\n /**\n * True when the change adds or removes the field rather than editing it.\n * A removal is worth saying out loud separately: for a dataset schema it\n * means existing records, and for a node tree it means content disappearing\n * from a page rather than merely looking different.\n */\n added?: boolean\n removed?: boolean\n}\n\nexport interface ArtifactUpdatePlan {\n safe: ArtifactChange[]\n kept: ArtifactChange[]\n conflicts: ArtifactChange[]\n /** Fields identical in all three versions — reported as a count only. */\n unchanged: number\n /** Nothing to take: the incoming content matches what is installed. */\n identical: boolean\n}\n\n/**\n * Paths whose CHILDREN are compared as a unit rather than walked.\n *\n * A besigner node is `{id, type, props, childIds}`. Walking into it produces\n * true field-level granularity, which is what the issue asks for; but the\n * `childIds` array is where structure lives, and comparing arrays element-wise\n * makes a single insert read as a change to every position after it. Arrays are\n * therefore atomic everywhere (see {@link isAtomic}), and node maps are walked\n * by KEY — which is the structural, id-keyed comparison a node tree needs, and\n * the reason a re-ordered tree does not read as a conflict in every node.\n */\nfunction isAtomic(value: unknown): boolean {\n return (\n value === null ||\n typeof value !== 'object' ||\n Array.isArray(value) ||\n // Dates, Firestore timestamps and anything else non-plain: comparing their\n // enumerable members would compare internals, not content.\n (Object.getPrototypeOf(value) !== Object.prototype &&\n Object.getPrototypeOf(value) !== null)\n )\n}\n\nfunction same(a: unknown, b: unknown): boolean {\n return stableStringify(a) === stableStringify(b)\n}\n\nconst ABSENT = Symbol('absent')\n\nfunction get(container: unknown, key: string): unknown | typeof ABSENT {\n if (container === ABSENT || container == null || typeof container !== 'object') {\n return ABSENT\n }\n const record = container as Record<string, unknown>\n return key in record ? record[key] : ABSENT\n}\n\nfunction keysOf(...containers: unknown[]): string[] {\n const keys: string[] = []\n const seen = new Set<string>()\n for (const container of containers) {\n if (container === ABSENT || container == null || typeof container !== 'object') {\n continue\n }\n for (const key of Object.keys(container as Record<string, unknown>)) {\n if (!seen.has(key)) {\n seen.add(key)\n keys.push(key)\n }\n }\n }\n return keys\n}\n\n/**\n * Classifies every field of an artifact against its base, the workspace's\n * current copy, and the version on offer.\n *\n * `base` is required and not defaulted: without the snapshot there is no way to\n * tell the publisher's change from the user's, and guessing at one is how you\n * produce a confident, wrong merge. Callers with no base offer \"install as a\n * new copy\" and nothing else.\n */\nexport function planArtifactUpdate(\n base: unknown,\n current: unknown,\n incoming: unknown,\n): ArtifactUpdatePlan {\n const plan: ArtifactUpdatePlan = {\n safe: [],\n kept: [],\n conflicts: [],\n unchanged: 0,\n identical: false,\n }\n\n const walk = (\n baseValue: unknown,\n currentValue: unknown,\n incomingValue: unknown,\n path: string,\n ): void => {\n const userChanged = !same(baseValue, currentValue)\n const publisherChanged = !same(baseValue, incomingValue)\n\n if (!userChanged && !publisherChanged) {\n plan.unchanged += 1\n return\n }\n\n // Recurse while all three sides still have a walkable object here, so an\n // edit to one node's text and an upstream edit to a different node are two\n // independent changes rather than one whole-tree conflict.\n //\n // A side that is ABSENT stops the walk: the field was added or removed\n // wholesale, and reporting that as one change (\"this node is gone\") is both\n // truer and more readable than reporting every leaf inside it.\n const walkable =\n baseValue !== ABSENT &&\n currentValue !== ABSENT &&\n incomingValue !== ABSENT &&\n !isAtomic(baseValue) &&\n !isAtomic(currentValue) &&\n !isAtomic(incomingValue)\n if (walkable) {\n for (const key of keysOf(baseValue, currentValue, incomingValue)) {\n walk(\n get(baseValue, key),\n get(currentValue, key),\n get(incomingValue, key),\n path ? `${path}.${key}` : key,\n )\n }\n return\n }\n\n const change: ArtifactChange = {\n path,\n kind: userChanged\n ? publisherChanged\n ? 'conflict'\n : 'kept'\n : 'safe',\n base: baseValue === ABSENT ? undefined : baseValue,\n current: currentValue === ABSENT ? undefined : currentValue,\n incoming: incomingValue === ABSENT ? undefined : incomingValue,\n ...(baseValue === ABSENT ? { added: true } : {}),\n ...(incomingValue === ABSENT && baseValue !== ABSENT\n ? { removed: true }\n : {}),\n }\n // A conflict where both sides landed on the SAME value is not a conflict —\n // two people fixing the same typo agree.\n if (change.kind === 'conflict' && same(currentValue, incomingValue)) {\n plan.unchanged += 1\n return\n }\n if (change.kind === 'safe') plan.safe.push(change)\n else if (change.kind === 'kept') plan.kept.push(change)\n else plan.conflicts.push(change)\n }\n\n walk(base, current, incoming, '')\n plan.identical = same(current, incoming)\n return plan\n}\n\nfunction setPath(target: unknown, path: string[], value: unknown | typeof ABSENT): unknown {\n if (!path.length) return value === ABSENT ? undefined : value\n const [head, ...rest] = path\n const container: Record<string, unknown> =\n target != null && typeof target === 'object' && !Array.isArray(target)\n ? { ...(target as Record<string, unknown>) }\n : {}\n if (!rest.length) {\n if (value === ABSENT) delete container[head]\n else container[head] = value\n return container\n }\n container[head] = setPath(container[head], rest, value)\n return container\n}\n\nexport interface ApplyUpdateOptions {\n /**\n * Conflicting paths the user chose to take from the publisher. Everything\n * absent from this list keeps the workspace's value — the default is always\n * the safe one, so a user who applies without reading loses nothing.\n */\n takePaths?: readonly string[]\n}\n\nexport interface AppliedUpdate {\n content: unknown\n /** Paths actually written, for a summary that reports what happened. */\n applied: string[]\n /** Conflicting paths left at the workspace's value. */\n skipped: string[]\n}\n\n/**\n * Produces the merged content: the workspace's copy with every safe change\n * taken and only the explicitly chosen conflicts overwritten.\n *\n * Built by patching `current` rather than by patching `incoming`, because the\n * copy is what the site is running: anything this function fails to consider —\n * a field the publisher never shipped, a node the user added — survives by\n * default rather than vanishing.\n */\nexport function applyArtifactUpdate(\n plan: ArtifactUpdatePlan,\n current: unknown,\n options?: ApplyUpdateOptions,\n): AppliedUpdate {\n const take = new Set(options?.takePaths ?? [])\n const applied: string[] = []\n const skipped: string[] = []\n let content = current\n const write = (change: ArtifactChange) => {\n const segments = change.path ? change.path.split('.') : []\n content = setPath(\n content,\n segments,\n change.removed ? ABSENT : change.incoming,\n )\n applied.push(change.path)\n }\n for (const change of plan.safe) write(change)\n for (const change of plan.conflicts) {\n if (take.has(change.path)) write(change)\n else skipped.push(change.path)\n }\n return { content, applied, skipped }\n}\n\n/**\n * A short human label for a change, so the dialog, the summary snackbar and the\n * server's response all describe the same edit the same way.\n *\n * Values are summarised, never dumped: the interesting thing about a changed\n * node is which node and what kind of change, and a page of serialised JSON in\n * a list row is unreadable by the time it matters.\n */\nexport function describeChange(change: ArtifactChange): string {\n const label = change.path || 'the whole artifact'\n if (change.added) return `${label} — added by the publisher`\n if (change.removed) return `${label} — removed by the publisher`\n return label\n}\n\n/** A one-line value preview for a diff row. */\nexport function summarizeValue(value: unknown): string {\n if (value === undefined) return '—'\n if (value === null) return 'null'\n if (typeof value === 'string') {\n return value.length > 60 ? `${value.slice(0, 57)}…` : value || '(empty)'\n }\n if (typeof value !== 'object') return String(value)\n if (Array.isArray(value)) {\n return `${value.length} item${value.length === 1 ? '' : 's'}`\n }\n const keys = Object.keys(value as Record<string, unknown>)\n return `{ ${keys.slice(0, 3).join(', ')}${keys.length > 3 ? ', …' : ''} }`\n}\n\n/**\n * Dataset schemas, classified by what an update would do to EXISTING RECORDS\n * (AGL-1018).\n *\n * The sharp edge of the whole project: adding a field is inert, but removing or\n * retyping one reinterprets data that is already there. So the schema plan is\n * not \"safe vs conflict\" but \"additive vs destructive\", and destructive changes\n * are never taken without a count of the records at stake.\n */\nexport interface SchemaChangeSummary {\n /** Field ids the new version adds. Applying these cannot harm a record. */\n added: string[]\n /** Field ids it removes — the values in existing records become orphaned. */\n removed: string[]\n /** Field ids whose TYPE changed — existing values may not survive a re-read. */\n retyped: string[]\n /** Anything else (labels, options, ordering). */\n edited: string[]\n /** True when nothing removes or retypes a field. */\n additiveOnly: boolean\n}\n\ninterface SchemaShape {\n order?: unknown\n fields?: Record<string, { type?: unknown } | undefined>\n}\n\n/**\n * Compares two dataset schemas field by field.\n *\n * Deliberately ignores the base: for a schema the question is not \"who edited\n * this\" but \"what happens to the rows\", and that is answered by the current\n * schema against the incoming one regardless of who moved which field.\n */\nexport function summarizeSchemaChange(\n current: SchemaShape | null | undefined,\n incoming: SchemaShape | null | undefined,\n): SchemaChangeSummary {\n const currentFields = (current?.fields ?? {}) as Record<string, { type?: unknown }>\n const incomingFields = (incoming?.fields ?? {}) as Record<string, { type?: unknown }>\n const added: string[] = []\n const removed: string[] = []\n const retyped: string[] = []\n const edited: string[] = []\n for (const id of Object.keys(incomingFields)) {\n if (!(id in currentFields)) {\n added.push(id)\n continue\n }\n if (String(currentFields[id]?.type) !== String(incomingFields[id]?.type)) {\n retyped.push(id)\n } else if (!same(currentFields[id], incomingFields[id])) {\n edited.push(id)\n }\n }\n for (const id of Object.keys(currentFields)) {\n if (!(id in incomingFields)) removed.push(id)\n }\n return {\n added,\n removed,\n retyped,\n edited,\n additiveOnly: !removed.length && !retyped.length,\n }\n}\n"],"names":["stableStringify","isAtomic","value","Array","isArray","Object","getPrototypeOf","prototype","same","a","b","ABSENT","Symbol","get","container","key","record","keysOf","containers","keys","seen","Set","has","add","push","planArtifactUpdate","base","current","incoming","plan","safe","kept","conflicts","unchanged","identical","walk","baseValue","currentValue","incomingValue","path","userChanged","publisherChanged","walkable","change","kind","undefined","added","removed","setPath","target","length","head","rest","applyArtifactUpdate","options","take","takePaths","applied","skipped","content","write","segments","split","describeChange","label","summarizeValue","slice","String","join","summarizeSchemaChange","currentFields","fields","incomingFields","retyped","edited","id","type","additiveOnly"],"mappings":"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"}
@@ -0,0 +1,43 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ /**
18
+ * Marketplace marketplace model (AGL-413): listing/profile shapes + publish sanitization relocated from core app-utils.
19
+ * Context-free — importable by client components, /server handlers, and
20
+ * other plugins/apps via `@aglyn/plugins-marketplace/model`.
21
+ */
22
+ export * from './marketplace';
23
+ /**
24
+ * Provenance and update state live in core (AGL-1015/1016) so the console can
25
+ * read them too, but they are marketplace vocabulary — re-exported here so a
26
+ * publishing or install route still imports the whole model from one place.
27
+ * Named rather than star-exported: `MarketplaceArtifactType` is re-exported by
28
+ * `./marketplace` already, and two barrels claiming it is an ambiguity.
29
+ *
30
+ * Deep paths rather than a core barrel, for the same reason `./marketplace`
31
+ * uses one: this module is in the browser bundle AND in API routes, so
32
+ * `@aglyn/aglyn` (createContext) and `@aglyn/aglyn/server` (node:fs) are both
33
+ * unusable from here.
34
+ */
35
+ export { ARTIFACT_BASE_COLLECTION, ARTIFACT_BASE_MAX_BYTES, resolveProvenance, stableStringify, type InstalledFrom, type ProvenanceState, type ResolvedProvenance, } from '@aglyn/aglyn/app-utils/marketplace-provenance';
36
+ export { applyArtifactUpdate, describeChange, planArtifactUpdate, summarizeSchemaChange, summarizeValue, type ArtifactChange, type ArtifactUpdatePlan, type ChangeKind, } from './artifact-merge';
37
+ export { compareArtifactVersions, resolveUpdateState, updateStateLabel, type UpdateComparableInstall, type UpdateComparableListing, type UpdateState, type UpdateStatus, } from '@aglyn/aglyn/app-utils/marketplace-update-state';
38
+ /**
39
+ * The email-starter policy, re-exported for the same reason the provenance and
40
+ * visibility policies are: it lives in core because `scope:app` may not depend
41
+ * on this lib, and publishing and install code should keep one import site.
42
+ */
43
+ export { emailStarterAssuranceLabel, emailStarterRefusal, emailStarterSendBlock, inspectEmailStarter, resolveEmailStarterAssurance, type EmailStarterAssurance, type EmailStarterInspection, type EmailStarterSendBlock, type EmailStarterViolation, type EmailStarterViolationCode, } from '@aglyn/aglyn/app-utils/email-starter-policy';
@@ -0,0 +1,41 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ /**
17
+ * Marketplace marketplace model (AGL-413): listing/profile shapes + publish sanitization relocated from core app-utils.
18
+ * Context-free — importable by client components, /server handlers, and
19
+ * other plugins/apps via `@aglyn/plugins-marketplace/model`.
20
+ */ export * from "./marketplace.js";
21
+ /**
22
+ * Provenance and update state live in core (AGL-1015/1016) so the console can
23
+ * read them too, but they are marketplace vocabulary — re-exported here so a
24
+ * publishing or install route still imports the whole model from one place.
25
+ * Named rather than star-exported: `MarketplaceArtifactType` is re-exported by
26
+ * `./marketplace` already, and two barrels claiming it is an ambiguity.
27
+ *
28
+ * Deep paths rather than a core barrel, for the same reason `./marketplace`
29
+ * uses one: this module is in the browser bundle AND in API routes, so
30
+ * `@aglyn/aglyn` (createContext) and `@aglyn/aglyn/server` (node:fs) are both
31
+ * unusable from here.
32
+ */ export { ARTIFACT_BASE_COLLECTION, ARTIFACT_BASE_MAX_BYTES, resolveProvenance, stableStringify } from "@aglyn/aglyn/app-utils/marketplace-provenance";
33
+ export { applyArtifactUpdate, describeChange, planArtifactUpdate, summarizeSchemaChange, summarizeValue } from "./artifact-merge.js";
34
+ export { compareArtifactVersions, resolveUpdateState, updateStateLabel } from "@aglyn/aglyn/app-utils/marketplace-update-state";
35
+ /**
36
+ * The email-starter policy, re-exported for the same reason the provenance and
37
+ * visibility policies are: it lives in core because `scope:app` may not depend
38
+ * on this lib, and publishing and install code should keep one import site.
39
+ */ export { emailStarterAssuranceLabel, emailStarterRefusal, emailStarterSendBlock, inspectEmailStarter, resolveEmailStarterAssurance } from "@aglyn/aglyn/app-utils/email-starter-policy";
40
+
41
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/marketplace/src/lib/model/index.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * Marketplace marketplace model (AGL-413): listing/profile shapes + publish sanitization relocated from core app-utils.\n * Context-free — importable by client components, /server handlers, and\n * other plugins/apps via `@aglyn/plugins-marketplace/model`.\n */\nexport * from './marketplace'\n/**\n * Provenance and update state live in core (AGL-1015/1016) so the console can\n * read them too, but they are marketplace vocabulary — re-exported here so a\n * publishing or install route still imports the whole model from one place.\n * Named rather than star-exported: `MarketplaceArtifactType` is re-exported by\n * `./marketplace` already, and two barrels claiming it is an ambiguity.\n *\n * Deep paths rather than a core barrel, for the same reason `./marketplace`\n * uses one: this module is in the browser bundle AND in API routes, so\n * `@aglyn/aglyn` (createContext) and `@aglyn/aglyn/server` (node:fs) are both\n * unusable from here.\n */\nexport {\n ARTIFACT_BASE_COLLECTION,\n ARTIFACT_BASE_MAX_BYTES,\n resolveProvenance,\n stableStringify,\n type InstalledFrom,\n type ProvenanceState,\n type ResolvedProvenance,\n} from '@aglyn/aglyn/app-utils/marketplace-provenance'\nexport {\n applyArtifactUpdate,\n describeChange,\n planArtifactUpdate,\n summarizeSchemaChange,\n summarizeValue,\n type ArtifactChange,\n type ArtifactUpdatePlan,\n type ChangeKind,\n} from './artifact-merge'\nexport {\n compareArtifactVersions,\n resolveUpdateState,\n updateStateLabel,\n type UpdateComparableInstall,\n type UpdateComparableListing,\n type UpdateState,\n type UpdateStatus,\n} from '@aglyn/aglyn/app-utils/marketplace-update-state'\n/**\n * The email-starter policy, re-exported for the same reason the provenance and\n * visibility policies are: it lives in core because `scope:app` may not depend\n * on this lib, and publishing and install code should keep one import site.\n */\nexport {\n emailStarterAssuranceLabel,\n emailStarterRefusal,\n emailStarterSendBlock,\n inspectEmailStarter,\n resolveEmailStarterAssurance,\n type EmailStarterAssurance,\n type EmailStarterInspection,\n type EmailStarterSendBlock,\n type EmailStarterViolation,\n type EmailStarterViolationCode,\n} from '@aglyn/aglyn/app-utils/email-starter-policy'\n"],"names":["ARTIFACT_BASE_COLLECTION","ARTIFACT_BASE_MAX_BYTES","resolveProvenance","stableStringify","applyArtifactUpdate","describeChange","planArtifactUpdate","summarizeSchemaChange","summarizeValue","compareArtifactVersions","resolveUpdateState","updateStateLabel","emailStarterAssuranceLabel","emailStarterRefusal","emailStarterSendBlock","inspectEmailStarter","resolveEmailStarterAssurance"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;CAIC,GACD,cAAc,mBAAe;AAC7B;;;;;;;;;;;CAWC,GACD,SACEA,wBAAwB,EACxBC,uBAAuB,EACvBC,iBAAiB,EACjBC,eAAe,QAIV,gDAA+C;AACtD,SACEC,mBAAmB,EACnBC,cAAc,EACdC,kBAAkB,EAClBC,qBAAqB,EACrBC,cAAc,QAIT,sBAAkB;AACzB,SACEC,uBAAuB,EACvBC,kBAAkB,EAClBC,gBAAgB,QAKX,kDAAiD;AACxD;;;;CAIC,GACD,SACEC,0BAA0B,EAC1BC,mBAAmB,EACnBC,qBAAqB,EACrBC,mBAAmB,EACnBC,4BAA4B,QAMvB,8CAA6C"}
@@ -0,0 +1,57 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ import type { ReusableComponentProp } from '@aglyn/aglyn/foundation/definitions/platform.types';
18
+ /** The most properties one published component or layout may declare. */
19
+ export declare const MARKETPLACE_PROPS_MAX_COUNT = 200;
20
+ /** The sanitized declarations, or why the list was refused outright. */
21
+ export type MarketplacePropsResult = {
22
+ ok: true;
23
+ props: ReusableComponentProp[];
24
+ /** Names of the properties whose default was refused and cleared. */
25
+ clearedDefaults: string[];
26
+ } | {
27
+ ok: false;
28
+ error: string;
29
+ };
30
+ /**
31
+ * Holds a published component's or layout's declared properties to the
32
+ * marketplace sanitizer.
33
+ *
34
+ * Every property with a usable name is kept, in order, with only the keys a
35
+ * declaration has. What each default, label, help text and option label must
36
+ * satisfy is the rule the published tree's own values meet — see the module
37
+ * comment — and whatever fails it is cleared rather than taken as grounds to
38
+ * lose the property. An entry with no usable name is skipped: nothing can
39
+ * bind to it, so it is not a property. A second entry with a name already
40
+ * taken is skipped for the same reason — bindings resolve to the first.
41
+ *
42
+ * Refuses outright only a declaration list too large to publish, which is
43
+ * the same answer the tree gets.
44
+ *
45
+ * Idempotent, so install and update run it again over what publish stored.
46
+ */
47
+ export declare function sanitizeMarketplaceProps(raw: unknown): MarketplacePropsResult;
48
+ /**
49
+ * The properties a stored listing version carries, re-held to the sanitizer,
50
+ * or `undefined` for a version published before properties were carried.
51
+ *
52
+ * The distinction is what install and update act on: a version with a list —
53
+ * even an empty one — says what the component declares, so it replaces what
54
+ * the installed copy has; a version with none says nothing, so the installed
55
+ * copy keeps its own.
56
+ */
57
+ export declare function readPublishedProps(raw: unknown): ReusableComponentProp[] | undefined;