@sparkletree/cli 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -6
- package/dist/bin.js +1 -1
- package/dist/fixtures-0.5.0.d.ts +10 -0
- package/dist/fixtures-0.5.0.d.ts.map +1 -0
- package/dist/fixtures-0.5.0.js +243 -0
- package/dist/fixtures-0.5.0.js.map +1 -0
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +2 -50
- package/dist/registry.js.map +1 -1
- package/dist/upgrade.d.ts +46 -22
- package/dist/upgrade.d.ts.map +1 -1
- package/dist/upgrade.js +219 -99
- package/dist/upgrade.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -22,12 +22,18 @@ sparkletree list
|
|
|
22
22
|
sparkletree help
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
-
`upgrade` ports hero/cta copies
|
|
26
|
-
wiring
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
25
|
+
`upgrade` ports hero/cta copies from any earlier `add` to the current template
|
|
26
|
+
wiring, one generation at a time: a 0.3.0 copy gets the 0.4.0 reservation
|
|
27
|
+
wiring (the hold reserves space with your fallback instead of rendering
|
|
28
|
+
nothing; see LAYOUT-DOCTRINE.md in the repo) and then the 0.6.1 change that
|
|
29
|
+
imports `safeActionUrl` from `@sparkletree/core` instead of freezing a local
|
|
30
|
+
copy; a 0.4.0/0.5.0 copy gets only the latter. What a copy is missing is
|
|
31
|
+
decided by its content, never by guessing a version. It rewrites a copy only
|
|
32
|
+
where it still matches the template it came from exactly, ignoring the
|
|
33
|
+
Tailwind classes you were invited to change; a copy edited where the wiring
|
|
34
|
+
lands is left untouched and reported, never guessed at. `--dry-run` previews
|
|
35
|
+
the verdicts, and nothing is written when the installed packages are older
|
|
36
|
+
than the rewritten copy needs to compile.
|
|
31
37
|
|
|
32
38
|
| Option | What it does |
|
|
33
39
|
|---|---|
|
package/dist/bin.js
CHANGED
|
@@ -16,7 +16,7 @@ const HELP = `sparkletree — add SparkleTree components to your project
|
|
|
16
16
|
|
|
17
17
|
Usage
|
|
18
18
|
sparkletree add <component...> [options]
|
|
19
|
-
sparkletree upgrade [options] port
|
|
19
|
+
sparkletree upgrade [options] port copied components to the current template wiring
|
|
20
20
|
sparkletree list
|
|
21
21
|
sparkletree help
|
|
22
22
|
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The files `add` ACTUALLY wrote at v0.5.0, extracted from the tagged
|
|
3
|
+
* registry byte for byte (the same doctrine as the 0.3.0 fixtures in
|
|
4
|
+
* upgrade.test.ts: only the real output can falsify the codemod's `find`
|
|
5
|
+
* claims). Machine-generated from `git show v0.5.0:cli/src/registry.ts` —
|
|
6
|
+
* do not "tidy" these.
|
|
7
|
+
*/
|
|
8
|
+
export declare const HERO_050 = "\"use client\";\n\nimport { StreamText, useIsland, withReserve, type UseIslandOptions } from \"@sparkletree/react\";\nimport type { FieldState } from \"@sparkletree/react\";\nimport { themeStyle } from \"@sparkletree/core\";\n\n/**\n * Your hero. Edit freely \u2014 this file is yours.\n *\n * What you should NOT need to touch, and what you will not find here: when the\n * copy paints, how long it holds, whether it types or lands composed, and\n * whether it carries the adaptation mark. All of that lives in\n * @sparkletree/core and @sparkletree/react so it can be fixed for you without\n * you re-copying this file.\n *\n * `fallback` is required. There is no loading state and no skeleton: if the\n * network is slow or the campaign is unreachable, this copy is what your\n * visitor reads. Write it as if it were the only version \u2014 often it is.\n *\n * Requires Tailwind CSS for the default classes \u2014 swap them freely.\n */\nexport interface HeroProps {\n campaignId: string;\n /** First paint. Real copy, never a placeholder. */\n fallback: UseIslandOptions[\"fallback\"];\n}\n\n/**\n * Server-driven URLs are configuration, not trust. Only http(s), mailto and\n * tel absolutes and relative navigations (\"/\", \"./\", \"#\", \"?\") pass; anything\n * else \u2014 javascript:, data:, an unprefixed string \u2014 is dropped.\n */\nfunction safeActionUrl(url: string | null | undefined): string | undefined {\n if (!url) return undefined;\n if (\n (url.startsWith(\"/\") && !url.startsWith(\"//\")) ||\n url.startsWith(\"./\") ||\n url.startsWith(\"#\") ||\n url.startsWith(\"?\")\n ) {\n return url;\n }\n try {\n const protocol = new URL(url).protocol;\n if ([\"https:\", \"http:\", \"mailto:\", \"tel:\"].includes(protocol)) return url;\n } catch {\n // Not a parseable URL at all.\n }\n return undefined;\n}\n\nexport function Hero({ campaignId, fallback }: HeroProps) {\n const { state, ref } = useIsland({ campaignId, island: \"hero\", fallback });\n const backgroundImage = safeActionUrl(state.backgroundImage);\n\n // The hold RESERVES (LAYOUT-DOCTRINE.md): while the canvas waits, each\n // field carries its fallback so StreamText keeps the geometry invisibly \u2014\n // nothing below the hero moves whichever way the wait ends. Delete this\n // and the section collapses during the hold; it is load-bearing.\n const holding = state.phase === \"holding\";\n const reserveOf = (name: \"greeting\" | \"headline\" | \"body\") =>\n state.held?.[name] || fallback[name];\n const held = (field: FieldState, reserve?: string) =>\n holding && reserve && !field.text ? withReserve(field, reserve) : field;\n\n return (\n <section\n ref={ref}\n className=\"relative overflow-hidden rounded-2xl p-10\"\n style={themeStyle(state.theme)}\n >\n {backgroundImage ? (\n <img\n src={backgroundImage}\n alt=\"\"\n className=\"absolute inset-0 h-full w-full object-cover\"\n />\n ) : null}\n\n <div className=\"relative\">\n {state.fields.greeting.text || (holding && reserveOf(\"greeting\")) ? (\n <StreamText\n field={held(state.fields.greeting, reserveOf(\"greeting\"))}\n as=\"p\"\n className=\"text-sm uppercase tracking-wide opacity-80\"\n />\n ) : null}\n\n {/* h2 to match the packaged Hero. The heading level is yours: pass\n as=\"h1\" if this section is your page's primary heading. */}\n <StreamText\n field={held(state.fields.headline, reserveOf(\"headline\"))}\n as=\"h2\"\n className=\"mt-2 text-4xl font-semibold\"\n />\n\n {state.fields.body.text || (holding && reserveOf(\"body\")) ? (\n <StreamText\n field={held(state.fields.body, reserveOf(\"body\"))}\n as=\"p\"\n className=\"mt-4 max-w-prose\"\n />\n ) : null}\n </div>\n </section>\n );\n}\n";
|
|
9
|
+
export declare const CTA_050 = "\"use client\";\n\nimport { StreamText, useIsland, useSparkletree, withReserve, type UseIslandOptions } from \"@sparkletree/react\";\nimport { readableTextColor, themeStyle, track } from \"@sparkletree/core\";\n\n/**\n * Your call to action. Edit freely.\n *\n * The button keeps its size while the label streams in, so the surrounding\n * layout does not shift under the visitor's cursor mid-click.\n *\n * Requires Tailwind CSS for the default classes \u2014 swap them freely.\n */\nexport interface CtaProps extends Omit<UseIslandOptions, \"island\"> {\n /** Where the button goes when the server has not resolved an action. */\n href?: string;\n onClick?: () => void;\n}\n\n/**\n * Server-driven URLs are configuration, not trust. Only http(s), mailto and\n * tel absolutes and relative navigations (\"/\", \"./\", \"#\", \"?\") pass; anything\n * else \u2014 javascript:, data:, an unprefixed string \u2014 is dropped.\n */\nfunction safeActionUrl(url: string | null | undefined): string | undefined {\n if (!url) return undefined;\n if (\n (url.startsWith(\"/\") && !url.startsWith(\"//\")) ||\n url.startsWith(\"./\") ||\n url.startsWith(\"#\") ||\n url.startsWith(\"?\")\n ) {\n return url;\n }\n try {\n const protocol = new URL(url).protocol;\n if ([\"https:\", \"http:\", \"mailto:\", \"tel:\"].includes(protocol)) return url;\n } catch {\n // Not a parseable URL at all.\n }\n return undefined;\n}\n\nexport function Cta(props: CtaProps) {\n const { href, onClick, ...islandOptions } = props;\n const { apiBase, organizationId } = useSparkletree();\n const { state, ref } = useIsland({ ...islandOptions, island: \"cta\" });\n // A rejected server action falls back to the href prop you control. The\n // prop is vetted too \u2014 the same render path, the same rules, one outcome.\n const action = safeActionUrl(state.ctaAction) ?? safeActionUrl(href);\n const { campaignId, surfaceId } = islandOptions;\n const variantId = state.variantId ?? undefined;\n // A surface-only Cta has no campaignId prop, but the click must still be\n // attributed: the server names the campaign it served in the terminal\n // done.meta, mirroring the impression path in useIsland.\n const metaCampaignId =\n typeof state.meta?.campaignId === \"string\" ? state.meta.campaignId : undefined;\n const resolvedCampaignId = campaignId ?? metaCampaignId;\n\n // The click is the other half of CTR. The SDK tells the server to suppress\n // its own counting for this page view \u2014 so if nothing here reports the\n // click, CTR for SDK traffic is a division by silence. Fire-and-forget:\n // track never throws and never blocks the navigation.\n const handleClick = () => {\n if (resolvedCampaignId) {\n void track(\n { apiBase, organizationId, campaignId: resolvedCampaignId, variantId, surfaceId },\n \"click\",\n { island: \"cta\" },\n );\n }\n onClick?.();\n };\n\n // The hold RESERVES (LAYOUT-DOCTRINE.md): a holding label rides the\n // fallback into StreamText invisibly, so the button keeps the size its\n // label will need. Delete this and the button collapses during the hold.\n const holding = state.phase === \"holding\";\n const ctaReserve = state.held?.cta || islandOptions.fallback.cta;\n const reserved = holding && !!ctaReserve && !state.fields.cta.text;\n const labelField = reserved\n ? withReserve(state.fields.cta, ctaReserve as string)\n : state.fields.cta;\n\n // Shared by the link and the no-destination fallback below.\n const shared = {\n onClick: handleClick,\n className:\n \"inline-flex min-h-11 min-w-40 items-center justify-center rounded-full px-6 font-medium\",\n style: {\n ...themeStyle(state.theme),\n // --st-cta-text is the label ink that reads on the theme's primary, so\n // a light theme can never produce a ghost button.\n ...(state.theme?.colors?.primary\n ? { [\"--st-cta-text\" as string]: readableTextColor(state.theme.colors.primary) }\n : {}),\n },\n // A CTA whose label is still empty is not yet a button a viewer can\n // meaningfully act on \u2014 but it must still occupy its space, or the\n // layout shifts when the copy lands.\n // A reserved button is full-size but its visible label is a hidden\n // reservation outside the a11y tree \u2014 never a NAMELESS control: the\n // reservation text is the honest accessible name.\n \"aria-disabled\": state.fields.cta.text ? undefined : true,\n \"aria-label\": reserved ? (ctaReserve as string) : undefined,\n };\n\n // Neither the stream nor you resolved a destination: render a\n // non-interactive element rather than an inert <a> with no href, which\n // some assistive tech still announces as a link to nowhere. Clicks are\n // still observed \u2014 engagement with the creative counts toward CTR whether\n // or not it navigates.\n if (action === undefined) {\n return (\n <span ref={ref as unknown as (element: HTMLSpanElement | null) => void} {...shared}>\n <StreamText field={labelField} />\n </span>\n );\n }\n\n return (\n <a ref={ref} href={action} {...shared}>\n <StreamText field={labelField} />\n </a>\n );\n}\n";
|
|
10
|
+
//# sourceMappingURL=fixtures-0.5.0.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fixtures-0.5.0.d.ts","sourceRoot":"","sources":["../src/fixtures-0.5.0.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,eAAO,MAAM,QAAQ,q6HA2GpB,CAAC;AAEF,eAAO,MAAM,OAAO,yoKA8HnB,CAAC"}
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The files `add` ACTUALLY wrote at v0.5.0, extracted from the tagged
|
|
3
|
+
* registry byte for byte (the same doctrine as the 0.3.0 fixtures in
|
|
4
|
+
* upgrade.test.ts: only the real output can falsify the codemod's `find`
|
|
5
|
+
* claims). Machine-generated from `git show v0.5.0:cli/src/registry.ts` —
|
|
6
|
+
* do not "tidy" these.
|
|
7
|
+
*/
|
|
8
|
+
export const HERO_050 = `"use client";
|
|
9
|
+
|
|
10
|
+
import { StreamText, useIsland, withReserve, type UseIslandOptions } from "@sparkletree/react";
|
|
11
|
+
import type { FieldState } from "@sparkletree/react";
|
|
12
|
+
import { themeStyle } from "@sparkletree/core";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Your hero. Edit freely — this file is yours.
|
|
16
|
+
*
|
|
17
|
+
* What you should NOT need to touch, and what you will not find here: when the
|
|
18
|
+
* copy paints, how long it holds, whether it types or lands composed, and
|
|
19
|
+
* whether it carries the adaptation mark. All of that lives in
|
|
20
|
+
* @sparkletree/core and @sparkletree/react so it can be fixed for you without
|
|
21
|
+
* you re-copying this file.
|
|
22
|
+
*
|
|
23
|
+
* \`fallback\` is required. There is no loading state and no skeleton: if the
|
|
24
|
+
* network is slow or the campaign is unreachable, this copy is what your
|
|
25
|
+
* visitor reads. Write it as if it were the only version — often it is.
|
|
26
|
+
*
|
|
27
|
+
* Requires Tailwind CSS for the default classes — swap them freely.
|
|
28
|
+
*/
|
|
29
|
+
export interface HeroProps {
|
|
30
|
+
campaignId: string;
|
|
31
|
+
/** First paint. Real copy, never a placeholder. */
|
|
32
|
+
fallback: UseIslandOptions["fallback"];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Server-driven URLs are configuration, not trust. Only http(s), mailto and
|
|
37
|
+
* tel absolutes and relative navigations ("/", "./", "#", "?") pass; anything
|
|
38
|
+
* else — javascript:, data:, an unprefixed string — is dropped.
|
|
39
|
+
*/
|
|
40
|
+
function safeActionUrl(url: string | null | undefined): string | undefined {
|
|
41
|
+
if (!url) return undefined;
|
|
42
|
+
if (
|
|
43
|
+
(url.startsWith("/") && !url.startsWith("//")) ||
|
|
44
|
+
url.startsWith("./") ||
|
|
45
|
+
url.startsWith("#") ||
|
|
46
|
+
url.startsWith("?")
|
|
47
|
+
) {
|
|
48
|
+
return url;
|
|
49
|
+
}
|
|
50
|
+
try {
|
|
51
|
+
const protocol = new URL(url).protocol;
|
|
52
|
+
if (["https:", "http:", "mailto:", "tel:"].includes(protocol)) return url;
|
|
53
|
+
} catch {
|
|
54
|
+
// Not a parseable URL at all.
|
|
55
|
+
}
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function Hero({ campaignId, fallback }: HeroProps) {
|
|
60
|
+
const { state, ref } = useIsland({ campaignId, island: "hero", fallback });
|
|
61
|
+
const backgroundImage = safeActionUrl(state.backgroundImage);
|
|
62
|
+
|
|
63
|
+
// The hold RESERVES (LAYOUT-DOCTRINE.md): while the canvas waits, each
|
|
64
|
+
// field carries its fallback so StreamText keeps the geometry invisibly —
|
|
65
|
+
// nothing below the hero moves whichever way the wait ends. Delete this
|
|
66
|
+
// and the section collapses during the hold; it is load-bearing.
|
|
67
|
+
const holding = state.phase === "holding";
|
|
68
|
+
const reserveOf = (name: "greeting" | "headline" | "body") =>
|
|
69
|
+
state.held?.[name] || fallback[name];
|
|
70
|
+
const held = (field: FieldState, reserve?: string) =>
|
|
71
|
+
holding && reserve && !field.text ? withReserve(field, reserve) : field;
|
|
72
|
+
|
|
73
|
+
return (
|
|
74
|
+
<section
|
|
75
|
+
ref={ref}
|
|
76
|
+
className="relative overflow-hidden rounded-2xl p-10"
|
|
77
|
+
style={themeStyle(state.theme)}
|
|
78
|
+
>
|
|
79
|
+
{backgroundImage ? (
|
|
80
|
+
<img
|
|
81
|
+
src={backgroundImage}
|
|
82
|
+
alt=""
|
|
83
|
+
className="absolute inset-0 h-full w-full object-cover"
|
|
84
|
+
/>
|
|
85
|
+
) : null}
|
|
86
|
+
|
|
87
|
+
<div className="relative">
|
|
88
|
+
{state.fields.greeting.text || (holding && reserveOf("greeting")) ? (
|
|
89
|
+
<StreamText
|
|
90
|
+
field={held(state.fields.greeting, reserveOf("greeting"))}
|
|
91
|
+
as="p"
|
|
92
|
+
className="text-sm uppercase tracking-wide opacity-80"
|
|
93
|
+
/>
|
|
94
|
+
) : null}
|
|
95
|
+
|
|
96
|
+
{/* h2 to match the packaged Hero. The heading level is yours: pass
|
|
97
|
+
as="h1" if this section is your page's primary heading. */}
|
|
98
|
+
<StreamText
|
|
99
|
+
field={held(state.fields.headline, reserveOf("headline"))}
|
|
100
|
+
as="h2"
|
|
101
|
+
className="mt-2 text-4xl font-semibold"
|
|
102
|
+
/>
|
|
103
|
+
|
|
104
|
+
{state.fields.body.text || (holding && reserveOf("body")) ? (
|
|
105
|
+
<StreamText
|
|
106
|
+
field={held(state.fields.body, reserveOf("body"))}
|
|
107
|
+
as="p"
|
|
108
|
+
className="mt-4 max-w-prose"
|
|
109
|
+
/>
|
|
110
|
+
) : null}
|
|
111
|
+
</div>
|
|
112
|
+
</section>
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
`;
|
|
116
|
+
export const CTA_050 = `"use client";
|
|
117
|
+
|
|
118
|
+
import { StreamText, useIsland, useSparkletree, withReserve, type UseIslandOptions } from "@sparkletree/react";
|
|
119
|
+
import { readableTextColor, themeStyle, track } from "@sparkletree/core";
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Your call to action. Edit freely.
|
|
123
|
+
*
|
|
124
|
+
* The button keeps its size while the label streams in, so the surrounding
|
|
125
|
+
* layout does not shift under the visitor's cursor mid-click.
|
|
126
|
+
*
|
|
127
|
+
* Requires Tailwind CSS for the default classes — swap them freely.
|
|
128
|
+
*/
|
|
129
|
+
export interface CtaProps extends Omit<UseIslandOptions, "island"> {
|
|
130
|
+
/** Where the button goes when the server has not resolved an action. */
|
|
131
|
+
href?: string;
|
|
132
|
+
onClick?: () => void;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Server-driven URLs are configuration, not trust. Only http(s), mailto and
|
|
137
|
+
* tel absolutes and relative navigations ("/", "./", "#", "?") pass; anything
|
|
138
|
+
* else — javascript:, data:, an unprefixed string — is dropped.
|
|
139
|
+
*/
|
|
140
|
+
function safeActionUrl(url: string | null | undefined): string | undefined {
|
|
141
|
+
if (!url) return undefined;
|
|
142
|
+
if (
|
|
143
|
+
(url.startsWith("/") && !url.startsWith("//")) ||
|
|
144
|
+
url.startsWith("./") ||
|
|
145
|
+
url.startsWith("#") ||
|
|
146
|
+
url.startsWith("?")
|
|
147
|
+
) {
|
|
148
|
+
return url;
|
|
149
|
+
}
|
|
150
|
+
try {
|
|
151
|
+
const protocol = new URL(url).protocol;
|
|
152
|
+
if (["https:", "http:", "mailto:", "tel:"].includes(protocol)) return url;
|
|
153
|
+
} catch {
|
|
154
|
+
// Not a parseable URL at all.
|
|
155
|
+
}
|
|
156
|
+
return undefined;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export function Cta(props: CtaProps) {
|
|
160
|
+
const { href, onClick, ...islandOptions } = props;
|
|
161
|
+
const { apiBase, organizationId } = useSparkletree();
|
|
162
|
+
const { state, ref } = useIsland({ ...islandOptions, island: "cta" });
|
|
163
|
+
// A rejected server action falls back to the href prop you control. The
|
|
164
|
+
// prop is vetted too — the same render path, the same rules, one outcome.
|
|
165
|
+
const action = safeActionUrl(state.ctaAction) ?? safeActionUrl(href);
|
|
166
|
+
const { campaignId, surfaceId } = islandOptions;
|
|
167
|
+
const variantId = state.variantId ?? undefined;
|
|
168
|
+
// A surface-only Cta has no campaignId prop, but the click must still be
|
|
169
|
+
// attributed: the server names the campaign it served in the terminal
|
|
170
|
+
// done.meta, mirroring the impression path in useIsland.
|
|
171
|
+
const metaCampaignId =
|
|
172
|
+
typeof state.meta?.campaignId === "string" ? state.meta.campaignId : undefined;
|
|
173
|
+
const resolvedCampaignId = campaignId ?? metaCampaignId;
|
|
174
|
+
|
|
175
|
+
// The click is the other half of CTR. The SDK tells the server to suppress
|
|
176
|
+
// its own counting for this page view — so if nothing here reports the
|
|
177
|
+
// click, CTR for SDK traffic is a division by silence. Fire-and-forget:
|
|
178
|
+
// track never throws and never blocks the navigation.
|
|
179
|
+
const handleClick = () => {
|
|
180
|
+
if (resolvedCampaignId) {
|
|
181
|
+
void track(
|
|
182
|
+
{ apiBase, organizationId, campaignId: resolvedCampaignId, variantId, surfaceId },
|
|
183
|
+
"click",
|
|
184
|
+
{ island: "cta" },
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
onClick?.();
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
// The hold RESERVES (LAYOUT-DOCTRINE.md): a holding label rides the
|
|
191
|
+
// fallback into StreamText invisibly, so the button keeps the size its
|
|
192
|
+
// label will need. Delete this and the button collapses during the hold.
|
|
193
|
+
const holding = state.phase === "holding";
|
|
194
|
+
const ctaReserve = state.held?.cta || islandOptions.fallback.cta;
|
|
195
|
+
const reserved = holding && !!ctaReserve && !state.fields.cta.text;
|
|
196
|
+
const labelField = reserved
|
|
197
|
+
? withReserve(state.fields.cta, ctaReserve as string)
|
|
198
|
+
: state.fields.cta;
|
|
199
|
+
|
|
200
|
+
// Shared by the link and the no-destination fallback below.
|
|
201
|
+
const shared = {
|
|
202
|
+
onClick: handleClick,
|
|
203
|
+
className:
|
|
204
|
+
"inline-flex min-h-11 min-w-40 items-center justify-center rounded-full px-6 font-medium",
|
|
205
|
+
style: {
|
|
206
|
+
...themeStyle(state.theme),
|
|
207
|
+
// --st-cta-text is the label ink that reads on the theme's primary, so
|
|
208
|
+
// a light theme can never produce a ghost button.
|
|
209
|
+
...(state.theme?.colors?.primary
|
|
210
|
+
? { ["--st-cta-text" as string]: readableTextColor(state.theme.colors.primary) }
|
|
211
|
+
: {}),
|
|
212
|
+
},
|
|
213
|
+
// A CTA whose label is still empty is not yet a button a viewer can
|
|
214
|
+
// meaningfully act on — but it must still occupy its space, or the
|
|
215
|
+
// layout shifts when the copy lands.
|
|
216
|
+
// A reserved button is full-size but its visible label is a hidden
|
|
217
|
+
// reservation outside the a11y tree — never a NAMELESS control: the
|
|
218
|
+
// reservation text is the honest accessible name.
|
|
219
|
+
"aria-disabled": state.fields.cta.text ? undefined : true,
|
|
220
|
+
"aria-label": reserved ? (ctaReserve as string) : undefined,
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
// Neither the stream nor you resolved a destination: render a
|
|
224
|
+
// non-interactive element rather than an inert <a> with no href, which
|
|
225
|
+
// some assistive tech still announces as a link to nowhere. Clicks are
|
|
226
|
+
// still observed — engagement with the creative counts toward CTR whether
|
|
227
|
+
// or not it navigates.
|
|
228
|
+
if (action === undefined) {
|
|
229
|
+
return (
|
|
230
|
+
<span ref={ref as unknown as (element: HTMLSpanElement | null) => void} {...shared}>
|
|
231
|
+
<StreamText field={labelField} />
|
|
232
|
+
</span>
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return (
|
|
237
|
+
<a ref={ref} href={action} {...shared}>
|
|
238
|
+
<StreamText field={labelField} />
|
|
239
|
+
</a>
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
`;
|
|
243
|
+
//# sourceMappingURL=fixtures-0.5.0.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fixtures-0.5.0.js","sourceRoot":"","sources":["../src/fixtures-0.5.0.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,CAAC,MAAM,QAAQ,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA2GvB,CAAC;AAEF,MAAM,CAAC,MAAM,OAAO,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8HtB,CAAC"}
|
package/dist/registry.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,uDAAuD;IACvD,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC3C,mCAAmC;IACnC,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,mDAAmD;IACnD,oBAAoB,EAAE,MAAM,EAAE,CAAC;CAChC;
|
|
1
|
+
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,uDAAuD;IACvD,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC3C,mCAAmC;IACnC,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,mDAAmD;IACnD,oBAAoB,EAAE,MAAM,EAAE,CAAC;CAChC;AAsVD,eAAO,MAAM,QAAQ,EAAE,SAAS,iBAAiB,EAoChD,CAAC;AAEF,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,iBAAiB,GAAG,SAAS,CAEzE;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,iBAAiB,EAAE,CAe5E"}
|
package/dist/registry.js
CHANGED
|
@@ -24,7 +24,7 @@ const HERO_TSX = `"use client";
|
|
|
24
24
|
|
|
25
25
|
import { StreamText, useIsland, withReserve, type UseIslandOptions } from "@sparkletree/react";
|
|
26
26
|
import type { FieldState } from "@sparkletree/react";
|
|
27
|
-
import { themeStyle } from "@sparkletree/core";
|
|
27
|
+
import { safeActionUrl, themeStyle } from "@sparkletree/core";
|
|
28
28
|
|
|
29
29
|
/**
|
|
30
30
|
* Your hero. Edit freely — this file is yours.
|
|
@@ -49,30 +49,6 @@ export interface HeroProps {
|
|
|
49
49
|
fallback: UseIslandOptions["fallback"];
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
/**
|
|
53
|
-
* Server-driven URLs are configuration, not trust. Only http(s), mailto and
|
|
54
|
-
* tel absolutes and relative navigations ("/", "./", "#", "?") pass; anything
|
|
55
|
-
* else — javascript:, data:, an unprefixed string — is dropped.
|
|
56
|
-
*/
|
|
57
|
-
function safeActionUrl(url: string | null | undefined): string | undefined {
|
|
58
|
-
if (!url) return undefined;
|
|
59
|
-
if (
|
|
60
|
-
(url.startsWith("/") && !url.startsWith("//")) ||
|
|
61
|
-
url.startsWith("./") ||
|
|
62
|
-
url.startsWith("#") ||
|
|
63
|
-
url.startsWith("?")
|
|
64
|
-
) {
|
|
65
|
-
return url;
|
|
66
|
-
}
|
|
67
|
-
try {
|
|
68
|
-
const protocol = new URL(url).protocol;
|
|
69
|
-
if (["https:", "http:", "mailto:", "tel:"].includes(protocol)) return url;
|
|
70
|
-
} catch {
|
|
71
|
-
// Not a parseable URL at all.
|
|
72
|
-
}
|
|
73
|
-
return undefined;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
52
|
export function Hero({ campaignId, fallback }: HeroProps) {
|
|
77
53
|
const { state, ref } = useIsland({ campaignId, island: "hero", fallback });
|
|
78
54
|
const backgroundImage = safeActionUrl(state.backgroundImage);
|
|
@@ -133,7 +109,7 @@ export function Hero({ campaignId, fallback }: HeroProps) {
|
|
|
133
109
|
const CTA_TSX = `"use client";
|
|
134
110
|
|
|
135
111
|
import { StreamText, useIsland, useSparkletree, withReserve, type UseIslandOptions } from "@sparkletree/react";
|
|
136
|
-
import { readableTextColor, themeStyle, track } from "@sparkletree/core";
|
|
112
|
+
import { readableTextColor, safeActionUrl, themeStyle, track } from "@sparkletree/core";
|
|
137
113
|
|
|
138
114
|
/**
|
|
139
115
|
* Your call to action. Edit freely.
|
|
@@ -149,30 +125,6 @@ export interface CtaProps extends Omit<UseIslandOptions, "island"> {
|
|
|
149
125
|
onClick?: () => void;
|
|
150
126
|
}
|
|
151
127
|
|
|
152
|
-
/**
|
|
153
|
-
* Server-driven URLs are configuration, not trust. Only http(s), mailto and
|
|
154
|
-
* tel absolutes and relative navigations ("/", "./", "#", "?") pass; anything
|
|
155
|
-
* else — javascript:, data:, an unprefixed string — is dropped.
|
|
156
|
-
*/
|
|
157
|
-
function safeActionUrl(url: string | null | undefined): string | undefined {
|
|
158
|
-
if (!url) return undefined;
|
|
159
|
-
if (
|
|
160
|
-
(url.startsWith("/") && !url.startsWith("//")) ||
|
|
161
|
-
url.startsWith("./") ||
|
|
162
|
-
url.startsWith("#") ||
|
|
163
|
-
url.startsWith("?")
|
|
164
|
-
) {
|
|
165
|
-
return url;
|
|
166
|
-
}
|
|
167
|
-
try {
|
|
168
|
-
const protocol = new URL(url).protocol;
|
|
169
|
-
if (["https:", "http:", "mailto:", "tel:"].includes(protocol)) return url;
|
|
170
|
-
} catch {
|
|
171
|
-
// Not a parseable URL at all.
|
|
172
|
-
}
|
|
173
|
-
return undefined;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
128
|
export function Cta(props: CtaProps) {
|
|
177
129
|
const { href, onClick, ...islandOptions } = props;
|
|
178
130
|
const { apiBase, organizationId } = useSparkletree();
|
package/dist/registry.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAaH,MAAM,QAAQ,GAAG
|
|
1
|
+
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"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"}
|
package/dist/upgrade.d.ts
CHANGED
|
@@ -1,42 +1,60 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `sparkletree upgrade`
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* files (see registry.ts), so the SDK cannot patch them at runtime
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
4
|
+
* Version-aware codemods for copied components. Copies are the customer's
|
|
5
|
+
* files (see registry.ts), so the SDK cannot patch them at runtime; this
|
|
6
|
+
* command ports each GENERATION of template wiring into copies that predate
|
|
7
|
+
* it. A copy from `add` at 0.3.0 gets every generation in order; a 0.5.0
|
|
8
|
+
* copy gets only the generations it is missing. Which generations a copy is
|
|
9
|
+
* missing is decided by content (each generation's marker), never by
|
|
10
|
+
* guessing a version.
|
|
9
11
|
*
|
|
10
12
|
* How it stays safe in someone else's repository:
|
|
11
13
|
*
|
|
12
|
-
* - exact-substring hunks, all-or-nothing per file. Every hunk
|
|
13
|
-
* exactly once (or, where marked, at least
|
|
14
|
-
* written. One miss means the file has diverged
|
|
15
|
-
* line the upgrade needs, and guessing at edited
|
|
16
|
-
* "reverts three months of my hero" — so we write
|
|
17
|
-
* hunks failed, so the wiring can be ported by
|
|
14
|
+
* - exact-substring hunks, all-or-nothing per file. Every hunk of every
|
|
15
|
+
* needed generation must match exactly once (or, where marked, at least
|
|
16
|
+
* once) before anything is written. One miss means the file has diverged
|
|
17
|
+
* from the template at a line the upgrade needs, and guessing at edited
|
|
18
|
+
* code is how a tool "reverts three months of my hero" — so we write
|
|
19
|
+
* nothing and say which hunks failed, so the wiring can be ported by
|
|
20
|
+
* hand;
|
|
18
21
|
* - hunks avoid the Tailwind classNames, which `add` explicitly invites
|
|
19
22
|
* customers to change. A restyled copy still upgrades;
|
|
20
|
-
* - idempotent: a
|
|
21
|
-
*
|
|
23
|
+
* - idempotent: a generation whose marker is already in the file is
|
|
24
|
+
* skipped, and a copy missing none is reported up to date and never
|
|
25
|
+
* touched;
|
|
22
26
|
* - `--dry-run` reports the same verdicts and writes nothing.
|
|
23
27
|
*/
|
|
24
28
|
interface Hunk {
|
|
25
29
|
/** For the skipped-report: which part of the wiring this is. */
|
|
26
30
|
label: string;
|
|
27
|
-
/** Exact text as `add` wrote it
|
|
31
|
+
/** Exact text as `add` wrote it, byte for byte. */
|
|
28
32
|
find: string;
|
|
29
33
|
replace: string;
|
|
30
34
|
/** Replace every occurrence instead of requiring exactly one. */
|
|
31
35
|
all?: boolean;
|
|
32
36
|
}
|
|
37
|
+
interface Codemod {
|
|
38
|
+
/**
|
|
39
|
+
* Already-upgraded detection: presence of this marker means this
|
|
40
|
+
* generation's wiring is in the copy, so the generation is skipped.
|
|
41
|
+
*/
|
|
42
|
+
marker: string;
|
|
43
|
+
/**
|
|
44
|
+
* Minimum installed @sparkletree/react (core, react and cli version
|
|
45
|
+
* together) the REWRITTEN copy compiles against. Below it the run behaves
|
|
46
|
+
* as a dry run — writing code an old install cannot compile trades a
|
|
47
|
+
* cosmetic gap for a build error.
|
|
48
|
+
*/
|
|
49
|
+
needs: string;
|
|
50
|
+
hunks: Hunk[];
|
|
51
|
+
}
|
|
33
52
|
export interface UpgradeTarget {
|
|
34
53
|
component: string;
|
|
35
54
|
/** Where `add` writes it, relative to the target directory. */
|
|
36
55
|
path: string;
|
|
37
|
-
/**
|
|
38
|
-
|
|
39
|
-
hunks: Hunk[];
|
|
56
|
+
/** Oldest first; a copy gets every generation it is missing, in order. */
|
|
57
|
+
codemods: readonly Codemod[];
|
|
40
58
|
}
|
|
41
59
|
export declare const UPGRADE_TARGETS: readonly UpgradeTarget[];
|
|
42
60
|
export interface UpgradeOptions {
|
|
@@ -56,13 +74,19 @@ export interface UpgradeResult {
|
|
|
56
74
|
hunks: string[];
|
|
57
75
|
}[];
|
|
58
76
|
/**
|
|
59
|
-
* The installed @sparkletree/react version, when it predates
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
77
|
+
* The installed @sparkletree/react version, when it predates
|
|
78
|
+
* `requiredReact`. Writing code an old install cannot compile trades a
|
|
79
|
+
* cosmetic gap for a build error, so when set the run behaved as a dry
|
|
80
|
+
* run: `upgraded` lists what WOULD be written once the packages are
|
|
81
|
+
* updated.
|
|
64
82
|
*/
|
|
65
83
|
blockedByReact: string | null;
|
|
84
|
+
/**
|
|
85
|
+
* The highest `needs` among the generations this run wanted to apply —
|
|
86
|
+
* what to update @sparkletree/react (and core) to when blocked. Null when
|
|
87
|
+
* nothing needed upgrading.
|
|
88
|
+
*/
|
|
89
|
+
requiredReact: string | null;
|
|
66
90
|
}
|
|
67
91
|
export declare function upgrade(options?: UpgradeOptions): Promise<UpgradeResult>;
|
|
68
92
|
/** Human-readable summary. Mirrors what `upgrade` actually did. */
|
package/dist/upgrade.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"upgrade.d.ts","sourceRoot":"","sources":["../src/upgrade.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"upgrade.d.ts","sourceRoot":"","sources":["../src/upgrade.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAKH,UAAU,IAAI;IACZ,gEAAgE;IAChE,KAAK,EAAE,MAAM,CAAC;IACd,mDAAmD;IACnD,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,iEAAiE;IACjE,GAAG,CAAC,EAAE,OAAO,CAAC;CACf;AAED,UAAU,OAAO;IACf;;;OAGG;IACH,MAAM,EAAE,MAAM,CAAC;IACf;;;;;OAKG;IACH,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,IAAI,EAAE,CAAC;CACf;AAED,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,+DAA+D;IAC/D,IAAI,EAAE,MAAM,CAAC;IACb,0EAA0E;IAC1E,QAAQ,EAAE,SAAS,OAAO,EAAE,CAAC;CAC9B;AAsDD,eAAO,MAAM,eAAe,EAAE,SAAS,aAAa,EA8InD,CAAC;AAEF,MAAM,WAAW,cAAc;IAC7B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,0DAA0D;IAC1D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,0EAA0E;IAC1E,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,yEAAyE;IACzE,QAAQ,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,EAAE,CAAA;KAAE,EAAE,CAAC;IAC9C;;;;;;OAMG;IACH,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B;;;;OAIG;IACH,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B;AAgDD,wBAAsB,OAAO,CAAC,OAAO,GAAE,cAAmB,GAAG,OAAO,CAAC,aAAa,CAAC,CA8ElF;AAED,mEAAmE;AACnE,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,aAAa,EAAE,MAAM,UAAQ,GAAG,MAAM,CA6CjF"}
|
package/dist/upgrade.js
CHANGED
|
@@ -1,24 +1,28 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `sparkletree upgrade`
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* files (see registry.ts), so the SDK cannot patch them at runtime
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
4
|
+
* Version-aware codemods for copied components. Copies are the customer's
|
|
5
|
+
* files (see registry.ts), so the SDK cannot patch them at runtime; this
|
|
6
|
+
* command ports each GENERATION of template wiring into copies that predate
|
|
7
|
+
* it. A copy from `add` at 0.3.0 gets every generation in order; a 0.5.0
|
|
8
|
+
* copy gets only the generations it is missing. Which generations a copy is
|
|
9
|
+
* missing is decided by content (each generation's marker), never by
|
|
10
|
+
* guessing a version.
|
|
9
11
|
*
|
|
10
12
|
* How it stays safe in someone else's repository:
|
|
11
13
|
*
|
|
12
|
-
* - exact-substring hunks, all-or-nothing per file. Every hunk
|
|
13
|
-
* exactly once (or, where marked, at least
|
|
14
|
-
* written. One miss means the file has diverged
|
|
15
|
-
* line the upgrade needs, and guessing at edited
|
|
16
|
-
* "reverts three months of my hero" — so we write
|
|
17
|
-
* hunks failed, so the wiring can be ported by
|
|
14
|
+
* - exact-substring hunks, all-or-nothing per file. Every hunk of every
|
|
15
|
+
* needed generation must match exactly once (or, where marked, at least
|
|
16
|
+
* once) before anything is written. One miss means the file has diverged
|
|
17
|
+
* from the template at a line the upgrade needs, and guessing at edited
|
|
18
|
+
* code is how a tool "reverts three months of my hero" — so we write
|
|
19
|
+
* nothing and say which hunks failed, so the wiring can be ported by
|
|
20
|
+
* hand;
|
|
18
21
|
* - hunks avoid the Tailwind classNames, which `add` explicitly invites
|
|
19
22
|
* customers to change. A restyled copy still upgrades;
|
|
20
|
-
* - idempotent: a
|
|
21
|
-
*
|
|
23
|
+
* - idempotent: a generation whose marker is already in the file is
|
|
24
|
+
* skipped, and a copy missing none is reported up to date and never
|
|
25
|
+
* touched;
|
|
22
26
|
* - `--dry-run` reports the same verdicts and writes nothing.
|
|
23
27
|
*/
|
|
24
28
|
import { readFile, writeFile } from "node:fs/promises";
|
|
@@ -41,98 +45,176 @@ const CTA_RESERVATION = ` // The hold RESERVES (LAYOUT-DOCTRINE.md): a holding
|
|
|
41
45
|
const labelField = reserved
|
|
42
46
|
? withReserve(state.fields.cta, ctaReserve as string)
|
|
43
47
|
: state.fields.cta;`;
|
|
48
|
+
/**
|
|
49
|
+
* The pre-0.6.1 inline vetting helper, byte for byte as `add` wrote it into
|
|
50
|
+
* every hero and cta copy from 0.3.0 through 0.5.0. 0.6.1 moved the one
|
|
51
|
+
* implementation to @sparkletree/core — deleting this block and importing
|
|
52
|
+
* the shared helper IS that generation.
|
|
53
|
+
*/
|
|
54
|
+
const INLINE_SAFE_ACTION_URL = `/**
|
|
55
|
+
* Server-driven URLs are configuration, not trust. Only http(s), mailto and
|
|
56
|
+
* tel absolutes and relative navigations ("/", "./", "#", "?") pass; anything
|
|
57
|
+
* else — javascript:, data:, an unprefixed string — is dropped.
|
|
58
|
+
*/
|
|
59
|
+
function safeActionUrl(url: string | null | undefined): string | undefined {
|
|
60
|
+
if (!url) return undefined;
|
|
61
|
+
if (
|
|
62
|
+
(url.startsWith("/") && !url.startsWith("//")) ||
|
|
63
|
+
url.startsWith("./") ||
|
|
64
|
+
url.startsWith("#") ||
|
|
65
|
+
url.startsWith("?")
|
|
66
|
+
) {
|
|
67
|
+
return url;
|
|
68
|
+
}
|
|
69
|
+
try {
|
|
70
|
+
const protocol = new URL(url).protocol;
|
|
71
|
+
if (["https:", "http:", "mailto:", "tel:"].includes(protocol)) return url;
|
|
72
|
+
} catch {
|
|
73
|
+
// Not a parseable URL at all.
|
|
74
|
+
}
|
|
75
|
+
return undefined;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
`;
|
|
44
79
|
export const UPGRADE_TARGETS = [
|
|
45
80
|
{
|
|
46
81
|
component: "hero",
|
|
47
82
|
path: "sparkletree/hero.tsx",
|
|
48
|
-
|
|
49
|
-
|
|
83
|
+
codemods: [
|
|
84
|
+
{
|
|
85
|
+
// 0.4.0: the hold RESERVES instead of blanking (LAYOUT-DOCTRINE.md).
|
|
86
|
+
marker: "withReserve",
|
|
87
|
+
needs: "0.4.0",
|
|
88
|
+
hunks: [
|
|
89
|
+
{
|
|
90
|
+
label: "imports",
|
|
91
|
+
find: `import { StreamText, useIsland, type UseIslandOptions } from "@sparkletree/react";`,
|
|
92
|
+
replace: `import { StreamText, useIsland, withReserve, type UseIslandOptions } from "@sparkletree/react";
|
|
93
|
+
import type { FieldState } from "@sparkletree/react";`,
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
label: "reservation helpers",
|
|
97
|
+
find: `const backgroundImage = safeActionUrl(state.backgroundImage);`,
|
|
98
|
+
replace: `const backgroundImage = safeActionUrl(state.backgroundImage);
|
|
99
|
+
|
|
100
|
+
${HERO_RESERVATION}`,
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
label: "greeting condition",
|
|
104
|
+
find: `{state.fields.greeting.text ? (`,
|
|
105
|
+
replace: `{state.fields.greeting.text || (holding && reserveOf("greeting")) ? (`,
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
label: "greeting field",
|
|
109
|
+
find: `field={state.fields.greeting}`,
|
|
110
|
+
replace: `field={held(state.fields.greeting, reserveOf("greeting"))}`,
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
label: "headline field",
|
|
114
|
+
find: `field={state.fields.headline}`,
|
|
115
|
+
replace: `field={held(state.fields.headline, reserveOf("headline"))}`,
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
label: "body condition",
|
|
119
|
+
find: `{state.fields.body.text ? (`,
|
|
120
|
+
replace: `{state.fields.body.text || (holding && reserveOf("body")) ? (`,
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
label: "body field",
|
|
124
|
+
find: `field={state.fields.body}`,
|
|
125
|
+
replace: `field={held(state.fields.body, reserveOf("body"))}`,
|
|
126
|
+
},
|
|
127
|
+
],
|
|
128
|
+
},
|
|
50
129
|
{
|
|
51
|
-
|
|
52
|
-
|
|
130
|
+
// 0.6.1: one safeActionUrl, imported from core; the header stops
|
|
131
|
+
// claiming a default disclosure mark is handled upstream.
|
|
132
|
+
marker: `safeActionUrl, themeStyle } from "@sparkletree/core"`,
|
|
133
|
+
needs: "0.6.1",
|
|
134
|
+
hunks: [
|
|
135
|
+
{
|
|
136
|
+
label: "disclosure note",
|
|
137
|
+
find: ` * copy paints, how long it holds, whether it types or lands composed, and
|
|
53
138
|
* whether it carries the adaptation mark. All of that lives in
|
|
54
139
|
* @sparkletree/core and @sparkletree/react so it can be fixed for you without
|
|
55
140
|
* you re-copying this file.`,
|
|
56
|
-
|
|
141
|
+
replace: ` * copy paints, how long it holds, whether it types or lands composed, and the
|
|
57
142
|
* provenance every adapted field carries. All of that lives in
|
|
58
143
|
* @sparkletree/core and @sparkletree/react so it can be fixed for you without
|
|
59
144
|
* you re-copying this file. No visible AI-disclosure mark renders by default —
|
|
60
145
|
* if you want one, render \`InlineMark\` from @sparkletree/react where
|
|
61
146
|
* \`decideChrome(field.source, prefersStandard).marked\` is true.`,
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
${HERO_RESERVATION}`,
|
|
75
|
-
},
|
|
76
|
-
{
|
|
77
|
-
label: "greeting condition",
|
|
78
|
-
find: `{state.fields.greeting.text ? (`,
|
|
79
|
-
replace: `{state.fields.greeting.text || (holding && reserveOf("greeting")) ? (`,
|
|
80
|
-
},
|
|
81
|
-
{
|
|
82
|
-
label: "greeting field",
|
|
83
|
-
find: `field={state.fields.greeting}`,
|
|
84
|
-
replace: `field={held(state.fields.greeting, reserveOf("greeting"))}`,
|
|
85
|
-
},
|
|
86
|
-
{
|
|
87
|
-
label: "headline field",
|
|
88
|
-
find: `field={state.fields.headline}`,
|
|
89
|
-
replace: `field={held(state.fields.headline, reserveOf("headline"))}`,
|
|
90
|
-
},
|
|
91
|
-
{
|
|
92
|
-
label: "body condition",
|
|
93
|
-
find: `{state.fields.body.text ? (`,
|
|
94
|
-
replace: `{state.fields.body.text || (holding && reserveOf("body")) ? (`,
|
|
95
|
-
},
|
|
96
|
-
{
|
|
97
|
-
label: "body field",
|
|
98
|
-
find: `field={state.fields.body}`,
|
|
99
|
-
replace: `field={held(state.fields.body, reserveOf("body"))}`,
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
label: "core imports",
|
|
150
|
+
find: `import { themeStyle } from "@sparkletree/core";`,
|
|
151
|
+
replace: `import { safeActionUrl, themeStyle } from "@sparkletree/core";`,
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
label: "shared safeActionUrl",
|
|
155
|
+
find: INLINE_SAFE_ACTION_URL,
|
|
156
|
+
replace: ``,
|
|
157
|
+
},
|
|
158
|
+
],
|
|
100
159
|
},
|
|
101
160
|
],
|
|
102
161
|
},
|
|
103
162
|
{
|
|
104
163
|
component: "cta",
|
|
105
164
|
path: "sparkletree/cta.tsx",
|
|
106
|
-
|
|
107
|
-
hunks: [
|
|
165
|
+
codemods: [
|
|
108
166
|
{
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
167
|
+
// 0.4.0: the hold RESERVES instead of blanking (LAYOUT-DOCTRINE.md).
|
|
168
|
+
marker: "withReserve",
|
|
169
|
+
needs: "0.4.0",
|
|
170
|
+
hunks: [
|
|
171
|
+
{
|
|
172
|
+
label: "imports",
|
|
173
|
+
find: `import { StreamText, useIsland, useSparkletree, type UseIslandOptions } from "@sparkletree/react";`,
|
|
174
|
+
replace: `import { StreamText, useIsland, useSparkletree, withReserve, type UseIslandOptions } from "@sparkletree/react";`,
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
label: "reservation helpers",
|
|
178
|
+
find: ` onClick?.();
|
|
116
179
|
};`,
|
|
117
|
-
|
|
180
|
+
replace: ` onClick?.();
|
|
118
181
|
};
|
|
119
182
|
|
|
120
183
|
${CTA_RESERVATION}`,
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
label: "accessible name",
|
|
187
|
+
find: ` "aria-disabled": state.fields.cta.text ? undefined : true,`,
|
|
188
|
+
replace: ` // A reserved button is full-size but its visible label is a hidden
|
|
126
189
|
// reservation outside the a11y tree — never a NAMELESS control: the
|
|
127
190
|
// reservation text is the honest accessible name.
|
|
128
191
|
"aria-disabled": state.fields.cta.text ? undefined : true,
|
|
129
192
|
"aria-label": reserved ? (ctaReserve as string) : undefined,`,
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
label: "label field",
|
|
196
|
+
find: `<StreamText field={state.fields.cta} />`,
|
|
197
|
+
replace: `<StreamText field={labelField} />`,
|
|
198
|
+
all: true,
|
|
199
|
+
},
|
|
200
|
+
],
|
|
130
201
|
},
|
|
131
202
|
{
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
203
|
+
// 0.6.1: one safeActionUrl, imported from core.
|
|
204
|
+
marker: `readableTextColor, safeActionUrl, themeStyle, track } from "@sparkletree/core"`,
|
|
205
|
+
needs: "0.6.1",
|
|
206
|
+
hunks: [
|
|
207
|
+
{
|
|
208
|
+
label: "core imports",
|
|
209
|
+
find: `import { readableTextColor, themeStyle, track } from "@sparkletree/core";`,
|
|
210
|
+
replace: `import { readableTextColor, safeActionUrl, themeStyle, track } from "@sparkletree/core";`,
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
label: "shared safeActionUrl",
|
|
214
|
+
find: INLINE_SAFE_ACTION_URL,
|
|
215
|
+
replace: ``,
|
|
216
|
+
},
|
|
217
|
+
],
|
|
136
218
|
},
|
|
137
219
|
],
|
|
138
220
|
},
|
|
@@ -160,25 +242,43 @@ async function installedReactVersion(cwd) {
|
|
|
160
242
|
dir = parent;
|
|
161
243
|
}
|
|
162
244
|
}
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
245
|
+
/** installed >= needs, on plain major.minor.patch. Unparseable installed
|
|
246
|
+
* versions are not evidence of an old install and pass. */
|
|
247
|
+
function versionAtLeast(installed, needs) {
|
|
248
|
+
const have = installed.split(".").map(Number);
|
|
249
|
+
const want = needs.split(".").map(Number);
|
|
250
|
+
for (let i = 0; i < 3; i++) {
|
|
251
|
+
const a = have[i] ?? 0;
|
|
252
|
+
const b = want[i] ?? 0;
|
|
253
|
+
if (!Number.isFinite(a))
|
|
254
|
+
return true;
|
|
255
|
+
if (a !== b)
|
|
256
|
+
return a > b;
|
|
257
|
+
}
|
|
258
|
+
return true;
|
|
259
|
+
}
|
|
260
|
+
/** The larger of two plain versions, for accumulating `requiredReact`. */
|
|
261
|
+
function maxVersion(a, b) {
|
|
262
|
+
if (a === null)
|
|
263
|
+
return b;
|
|
264
|
+
return versionAtLeast(a, b) ? a : b;
|
|
169
265
|
}
|
|
170
266
|
export async function upgrade(options = {}) {
|
|
171
267
|
const cwd = options.cwd ?? process.cwd();
|
|
172
268
|
const targetDir = options.targetDir ?? "components";
|
|
173
269
|
const reactVersion = await installedReactVersion(cwd);
|
|
174
|
-
const blockedByReact = reactVersion !== null && !supportsReservation(reactVersion) ? reactVersion : null;
|
|
175
270
|
const result = {
|
|
176
271
|
upgraded: [],
|
|
177
272
|
upToDate: [],
|
|
178
273
|
missing: [],
|
|
179
274
|
diverged: [],
|
|
180
|
-
blockedByReact,
|
|
275
|
+
blockedByReact: null,
|
|
276
|
+
requiredReact: null,
|
|
181
277
|
};
|
|
278
|
+
// First pass: which generations does each copy still need? The block
|
|
279
|
+
// decision needs the answer across ALL files — updating packages is one
|
|
280
|
+
// action, so the run names one required version, not one per file.
|
|
281
|
+
const pending = [];
|
|
182
282
|
for (const target of UPGRADE_TARGETS) {
|
|
183
283
|
const absolute = resolve(cwd, targetDir, target.path);
|
|
184
284
|
const path = relative(cwd, absolute);
|
|
@@ -190,25 +290,45 @@ export async function upgrade(options = {}) {
|
|
|
190
290
|
result.missing.push(path);
|
|
191
291
|
continue;
|
|
192
292
|
}
|
|
193
|
-
|
|
293
|
+
const needed = target.codemods.filter((codemod) => !content.includes(codemod.marker));
|
|
294
|
+
if (!needed.length) {
|
|
194
295
|
result.upToDate.push(path);
|
|
195
296
|
continue;
|
|
196
297
|
}
|
|
197
|
-
const
|
|
198
|
-
.
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
298
|
+
for (const codemod of needed) {
|
|
299
|
+
result.requiredReact = maxVersion(result.requiredReact, codemod.needs);
|
|
300
|
+
}
|
|
301
|
+
pending.push({ absolute, path, content, needed });
|
|
302
|
+
}
|
|
303
|
+
if (reactVersion !== null &&
|
|
304
|
+
result.requiredReact !== null &&
|
|
305
|
+
!versionAtLeast(reactVersion, result.requiredReact)) {
|
|
306
|
+
result.blockedByReact = reactVersion;
|
|
307
|
+
}
|
|
308
|
+
// Second pass: validate and apply the needed generations IN ORDER against
|
|
309
|
+
// the evolving text — a later generation's hunks land on the earlier
|
|
310
|
+
// generation's output, exactly as they did across real releases. Still
|
|
311
|
+
// all-or-nothing per file: one miss anywhere and nothing is written.
|
|
312
|
+
for (const { absolute, path, content, needed } of pending) {
|
|
313
|
+
let next = content;
|
|
314
|
+
const failed = [];
|
|
315
|
+
for (const codemod of needed) {
|
|
316
|
+
for (const hunk of codemod.hunks) {
|
|
317
|
+
const count = occurrences(next, hunk.find);
|
|
318
|
+
if (hunk.all ? count === 0 : count !== 1) {
|
|
319
|
+
failed.push(hunk.label);
|
|
320
|
+
continue;
|
|
321
|
+
}
|
|
322
|
+
next = hunk.all
|
|
323
|
+
? next.replaceAll(hunk.find, hunk.replace)
|
|
324
|
+
: next.replace(hunk.find, hunk.replace);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
203
327
|
if (failed.length) {
|
|
204
328
|
result.diverged.push({ path, hunks: failed });
|
|
205
329
|
continue;
|
|
206
330
|
}
|
|
207
|
-
|
|
208
|
-
for (const hunk of target.hunks) {
|
|
209
|
-
next = hunk.all ? next.replaceAll(hunk.find, hunk.replace) : next.replace(hunk.find, hunk.replace);
|
|
210
|
-
}
|
|
211
|
-
if (!options.dryRun && !blockedByReact)
|
|
331
|
+
if (!options.dryRun && !result.blockedByReact)
|
|
212
332
|
await writeFile(absolute, next, "utf8");
|
|
213
333
|
result.upgraded.push(path);
|
|
214
334
|
}
|
|
@@ -219,10 +339,10 @@ export function formatUpgradeResult(result, dryRun = false) {
|
|
|
219
339
|
const lines = [];
|
|
220
340
|
const verb = dryRun || result.blockedByReact ? "Would upgrade" : "Upgraded";
|
|
221
341
|
if (result.blockedByReact) {
|
|
222
|
-
lines.push(`Nothing written: @sparkletree/react ${result.blockedByReact} is installed,`,
|
|
342
|
+
lines.push(`Nothing written: @sparkletree/react ${result.blockedByReact} is installed,`, `and this upgrade's wiring needs ^${result.requiredReact ?? "0.4.0"} to compile.`, "Update @sparkletree/react and @sparkletree/core, then re-run", "`sparkletree upgrade`.", "");
|
|
223
343
|
}
|
|
224
344
|
if (result.upgraded.length) {
|
|
225
|
-
lines.push(`${verb} to the
|
|
345
|
+
lines.push(`${verb} to the current template wiring:`);
|
|
226
346
|
for (const path of result.upgraded)
|
|
227
347
|
lines.push(` ${path}`);
|
|
228
348
|
}
|
|
@@ -236,12 +356,12 @@ export function formatUpgradeResult(result, dryRun = false) {
|
|
|
236
356
|
for (const item of result.diverged) {
|
|
237
357
|
lines.push(` ${item.path} — did not match at: ${item.hunks.join(", ")}`);
|
|
238
358
|
}
|
|
239
|
-
lines.push("", "Nothing was written to those files. Port the
|
|
359
|
+
lines.push("", "Nothing was written to those files. Port the wiring by hand (the", "current templates show it: `sparkletree add <name> --dir /tmp/fresh`", "writes a reference copy), or re-copy with `add --force` if you are", "happy to lose your edits.");
|
|
240
360
|
}
|
|
241
361
|
if (!lines.length) {
|
|
242
362
|
lines.push("Nothing to upgrade — no copied hero or cta found.");
|
|
243
363
|
}
|
|
244
|
-
lines.push("", "Why: 0.4.0 holds by RESERVING space with the fallback
|
|
364
|
+
lines.push("", "Why: copies drift. 0.4.0 holds by RESERVING space with the fallback", "(without it a copied hero or cta collapses during the hold), and 0.6.1", "moved URL vetting to one implementation in @sparkletree/core so security", "fixes reach copies through npm instead of a migration guide.");
|
|
245
365
|
return lines.join("\n");
|
|
246
366
|
}
|
|
247
367
|
//# sourceMappingURL=upgrade.js.map
|
package/dist/upgrade.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"upgrade.js","sourceRoot":"","sources":["../src/upgrade.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"upgrade.js","sourceRoot":"","sources":["../src/upgrade.ts"],"names":[],"mappings":"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"}
|