@saastro/studio 0.3.4 → 0.5.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/dist/chunk-FLFH5RVH.js +10 -0
- package/dist/chunk-FLFH5RVH.js.map +1 -0
- package/dist/{chunk-SPY66EPD.js → chunk-HEBSQKPT.js} +2 -2
- package/dist/{chunk-RKHQDJVM.js → chunk-MMUEPPBK.js} +2 -2
- package/dist/{chunk-WBTB3A3Y.js → chunk-WTHLK3XQ.js} +202 -21
- package/dist/chunk-WTHLK3XQ.js.map +1 -0
- package/dist/editable-image-B4UdcCKi.d.ts +13 -0
- package/dist/image/index.d.ts +78 -0
- package/dist/image/index.js +75 -0
- package/dist/image/index.js.map +1 -0
- package/dist/index.d.ts +3 -14
- package/dist/index.js +6 -9
- package/dist/index.js.map +1 -1
- package/dist/integration/index.d.ts +1 -1
- package/dist/integration/index.js +3 -3
- package/dist/middleware/index.js +1 -1
- package/dist/types-C9q8CVtQ.d.ts +38 -0
- package/dist/vite/index.d.ts +1 -1
- package/dist/vite/index.js +2 -2
- package/package.json +9 -3
- package/src/Img.astro +124 -0
- package/dist/chunk-WBTB3A3Y.js.map +0 -1
- package/dist/types-DlrT0PDc.d.ts +0 -19
- /package/dist/{chunk-SPY66EPD.js.map → chunk-HEBSQKPT.js.map} +0 -0
- /package/dist/{chunk-RKHQDJVM.js.map → chunk-MMUEPPBK.js.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
saastro
|
|
3
|
-
} from "./chunk-
|
|
4
|
-
import "./chunk-
|
|
5
|
-
import "./chunk-
|
|
3
|
+
} from "./chunk-HEBSQKPT.js";
|
|
4
|
+
import "./chunk-WTHLK3XQ.js";
|
|
5
|
+
import "./chunk-MMUEPPBK.js";
|
|
6
|
+
import {
|
|
7
|
+
editableImage
|
|
8
|
+
} from "./chunk-FLFH5RVH.js";
|
|
6
9
|
|
|
7
10
|
// src/helpers/editable-section.ts
|
|
8
11
|
function editableSection(fieldPrefix) {
|
|
@@ -10,12 +13,6 @@ function editableSection(fieldPrefix) {
|
|
|
10
13
|
return { "data-saastro": `sec:${fieldPrefix}` };
|
|
11
14
|
}
|
|
12
15
|
|
|
13
|
-
// src/helpers/editable-image.ts
|
|
14
|
-
function editableImage(fieldPrefix) {
|
|
15
|
-
if (!fieldPrefix) return {};
|
|
16
|
-
return { "data-saastro": `img:${fieldPrefix}` };
|
|
17
|
-
}
|
|
18
|
-
|
|
19
16
|
// src/helpers/editable-slot.ts
|
|
20
17
|
function editableSlot(fieldPrefix, slotName) {
|
|
21
18
|
if (!fieldPrefix || !slotName) return {};
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/helpers/editable-section.ts","../src/helpers/editable-
|
|
1
|
+
{"version":3,"sources":["../src/helpers/editable-section.ts","../src/helpers/editable-slot.ts","../src/helpers/editable-array-item.ts","../src/index.ts"],"sourcesContent":["import type { EditableAttrs } from './types.js'\n\n/**\n * Section marker — drop on the root element of a component that\n * receives `fieldPrefix`. Lets the Studio overlay hover/select/drag\n * the whole section.\n *\n * Returns empty object when fieldPrefix is falsy — safe to spread unconditionally.\n */\nexport function editableSection(fieldPrefix: string | undefined): EditableAttrs {\n if (!fieldPrefix) return {}\n return { 'data-saastro': `sec:${fieldPrefix}` }\n}\n","import type { EditableAttrs } from './types.js'\n\n/**\n * Slot marker — used for discrete named edits within a section\n * (e.g. a CTA label inside a hero). `fieldPrefix` scopes the slot\n * to its parent component; `slotName` is the local identifier.\n *\n * Returns empty object when either argument is falsy — safe to spread unconditionally.\n */\nexport function editableSlot(\n fieldPrefix: string | undefined,\n slotName: string,\n): EditableAttrs {\n if (!fieldPrefix || !slotName) return {}\n return { 'data-saastro': `slot:${fieldPrefix}.${slotName}` }\n}\n","import type { EditableAttrs } from './types.js'\n\n/**\n * Array-item marker — used on each rendered item inside a `.map(...)`\n * over repeatable content (pricing plans, testimonials, feature cards).\n * Studio uses this to select/duplicate/reorder individual items.\n *\n * Format: `arr:{arrPath}[{index}]` — brackets follow JS/TS convention\n * (lodash, JSON Pointer) and avoid ambiguity with numeric literal keys.\n *\n * Returns empty object when fieldPrefix is falsy or index is negative/non-finite.\n */\nexport function editableArrayItem(\n fieldPrefix: string | undefined,\n index: number,\n): EditableAttrs {\n if (!fieldPrefix || index < 0 || !Number.isFinite(index)) return {}\n return { 'data-saastro': `arr:${fieldPrefix}[${index}]` }\n}\n","/**\n * @saastro/studio — build-time instrumentation for Saastro Studio.\n *\n * This package provides:\n * - **Astro Integration** (default export): `saastro({ autoWrap: true })` —\n * registers the Vite plugin through Astro's own pipeline so ordering against\n * the `.astro` compiler is deterministic on cold builds.\n * - **Editable helpers**: `editableSection`, `editableImage`, `editableSlot`,\n * `editableArrayItem` + the `studioEditable` namespace.\n * - **Vite plugin** (separate entry, back-compat): `@saastro/studio/vite`.\n *\n * NOTE: This package is the *runtime instrumentation* installed in Astro\n * sites. The Studio editor UI lives at hub.saastro.io and is NOT part of this\n * package.\n *\n * @saastro/studio and @saastro/cms are peer packages — neither depends on the\n * other.\n */\n\nimport saastro from './integration/index.js'\n\nexport default saastro\nexport { saastro }\nexport type { SaastroStudioOptions } from './integration/index.js'\n\nexport type { EditableAttrs } from './helpers/types.js'\nexport { editableSection } from './helpers/editable-section.js'\nexport { editableImage } from './helpers/editable-image.js'\nexport { editableSlot } from './helpers/editable-slot.js'\nexport { editableArrayItem } from './helpers/editable-array-item.js'\n\nimport { editableSection } from './helpers/editable-section.js'\nimport { editableImage } from './helpers/editable-image.js'\nimport { editableSlot } from './helpers/editable-slot.js'\nimport { editableArrayItem } from './helpers/editable-array-item.js'\n\n/**\n * Convenience namespace — all four helpers grouped under one object.\n */\nexport const studioEditable = {\n section: editableSection,\n image: editableImage,\n slot: editableSlot,\n arrayItem: editableArrayItem,\n} as const\n"],"mappings":";;;;;;;;;;AASO,SAAS,gBAAgB,aAAgD;AAC9E,MAAI,CAAC,YAAa,QAAO,CAAC;AAC1B,SAAO,EAAE,gBAAgB,OAAO,WAAW,GAAG;AAChD;;;ACHO,SAAS,aACd,aACA,UACe;AACf,MAAI,CAAC,eAAe,CAAC,SAAU,QAAO,CAAC;AACvC,SAAO,EAAE,gBAAgB,QAAQ,WAAW,IAAI,QAAQ,GAAG;AAC7D;;;ACHO,SAAS,kBACd,aACA,OACe;AACf,MAAI,CAAC,eAAe,QAAQ,KAAK,CAAC,OAAO,SAAS,KAAK,EAAG,QAAO,CAAC;AAClE,SAAO,EAAE,gBAAgB,OAAO,WAAW,IAAI,KAAK,IAAI;AAC1D;;;ACGA,IAAO,cAAQ;AAkBR,IAAM,iBAAiB;AAAA,EAC5B,SAAS;AAAA,EACT,OAAO;AAAA,EACP,MAAM;AAAA,EACN,WAAW;AACb;","names":[]}
|
package/dist/middleware/index.js
CHANGED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
interface StudioAutoWrapOptions {
|
|
2
|
+
/** Turn on the auto-wrap transform. Off by default. */
|
|
3
|
+
autoWrap?: boolean;
|
|
4
|
+
/**
|
|
5
|
+
* Also auto-instrument whole route files under `src/pages/**` that carry
|
|
6
|
+
* NEITHER a hand-written `data-saastro` marker NOR a `fieldPrefix` prop.
|
|
7
|
+
* For each such page we match the route (`show.astro`, `our-team.astro`,
|
|
8
|
+
* `index.astro` → `home`; `[locale]`/static-locale dirs transparent) to the
|
|
9
|
+
* page's OWN i18n namespace — the `t.<ns>` it actually reads — and stamp
|
|
10
|
+
* `data-saastro="sec:<ns>"` on the first content element. Using the real
|
|
11
|
+
* namespace means it works whatever the naming convention (`ourTeam` or
|
|
12
|
+
* `our_team`, `faq`…) and never invents a key the Studio can't resolve. The
|
|
13
|
+
* Studio then derives editable fields from that namespace — so a NEW page
|
|
14
|
+
* becomes editable with zero code in the customer repo.
|
|
15
|
+
*
|
|
16
|
+
* Opt-in, off by default. Requires `autoWrap: true` (it extends the
|
|
17
|
+
* section-level transform; it does nothing on its own). Conservative: marks
|
|
18
|
+
* only when a referenced namespace matches the route — otherwise it leaves
|
|
19
|
+
* the page for the Hub's Studio doctor to flag. Dynamic routes
|
|
20
|
+
* (`[slug].astro`) are skipped.
|
|
21
|
+
*/
|
|
22
|
+
autoWrapPages?: boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Only transform files whose path matches this predicate. Default:
|
|
25
|
+
* every `.astro`, `.tsx`, `.jsx` file (further filtered by per-file
|
|
26
|
+
* heuristics — fieldPrefix on Astro, Props + `.map(` on TSX).
|
|
27
|
+
*/
|
|
28
|
+
include?: (id: string) => boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Instrument React components (.tsx/.jsx) whose Props match field
|
|
31
|
+
* names. When enabled, `.map((item, i) => <jsx/>)` patterns get
|
|
32
|
+
* `data-saastro-field` markers on the iterated element and on
|
|
33
|
+
* `{item.<sub>}` references. Defaults to `true`.
|
|
34
|
+
*/
|
|
35
|
+
instrumentTsx?: boolean;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export type { StudioAutoWrapOptions as S };
|
package/dist/vite/index.d.ts
CHANGED
package/dist/vite/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@saastro/studio",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Build-time instrumentation for Saastro Studio — Vite plugin + editable helpers for Astro sites managed by Saastro Hub",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "SAASTRO",
|
|
@@ -37,10 +37,16 @@
|
|
|
37
37
|
"./middleware": {
|
|
38
38
|
"types": "./dist/middleware/index.d.ts",
|
|
39
39
|
"import": "./dist/middleware/index.js"
|
|
40
|
-
}
|
|
40
|
+
},
|
|
41
|
+
"./image": {
|
|
42
|
+
"types": "./dist/image/index.d.ts",
|
|
43
|
+
"import": "./dist/image/index.js"
|
|
44
|
+
},
|
|
45
|
+
"./Img.astro": "./src/Img.astro"
|
|
41
46
|
},
|
|
42
47
|
"files": [
|
|
43
|
-
"dist"
|
|
48
|
+
"dist",
|
|
49
|
+
"src/Img.astro"
|
|
44
50
|
],
|
|
45
51
|
"peerDependencies": {
|
|
46
52
|
"astro": "^5.0.0 || ^6.0.0",
|
package/src/Img.astro
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* <Img> — edge-optimized, Studio-editable image for Saastro CMS sites.
|
|
4
|
+
*
|
|
5
|
+
* - Optimizes via Cloudflare Image Transformations (`/cdn-cgi/image/...`) at the
|
|
6
|
+
* zone edge — webp/avif + responsive srcset, no Sharp (works on SSR/Workers).
|
|
7
|
+
* - Handles BOTH a repo asset (`/images/...`, transformed root-relative on the
|
|
8
|
+
* site's own zone) AND an R2 media-library upload (`https://media.saastro.io/...`,
|
|
9
|
+
* transformed on the media host).
|
|
10
|
+
* - Stays EDITABLE: when `fieldPrefix` is set, the `<img>` carries the RAW src +
|
|
11
|
+
* the Studio marker (`editableImage`), while the optimized candidates live on
|
|
12
|
+
* `<source srcset>`. The overlay reads/writes the raw `<img src>` → round-trips
|
|
13
|
+
* cleanly into content (it must never see a `/cdn-cgi/image/...` URL).
|
|
14
|
+
* - Degrades gracefully: in `astro dev`, on localhost, with `raw`, or for
|
|
15
|
+
* svg/gif/remote sources, emits a plain `<img>` (identical to today).
|
|
16
|
+
*
|
|
17
|
+
* Ships with @saastro/studio (consumed via the `./Img.astro` export). The marker
|
|
18
|
+
* helper `editableImage` is native to this package; the raw-src requirement is
|
|
19
|
+
* dictated by the Hub overlay (apps/hub-react/src/server/studio/overlay-script.ts).
|
|
20
|
+
*/
|
|
21
|
+
import { buildImage, editableImage, DEFAULT_WIDTHS } from '@saastro/studio/image'
|
|
22
|
+
|
|
23
|
+
interface Props {
|
|
24
|
+
/** `/images/...` (repo) or `https://media.saastro.io/...` (R2). */
|
|
25
|
+
src: string
|
|
26
|
+
alt: string
|
|
27
|
+
width?: number
|
|
28
|
+
height?: number
|
|
29
|
+
/** Responsive `sizes`; default `100vw`. */
|
|
30
|
+
sizes?: string
|
|
31
|
+
/** srcset breakpoints (px); default [480,768,1024,1536,2048]. */
|
|
32
|
+
widths?: number[]
|
|
33
|
+
quality?: number
|
|
34
|
+
fit?: 'cover' | 'contain' | 'scale-down'
|
|
35
|
+
loading?: 'lazy' | 'eager'
|
|
36
|
+
fetchpriority?: 'high' | 'low' | 'auto'
|
|
37
|
+
decoding?: 'async' | 'sync' | 'auto'
|
|
38
|
+
/** Studio editability: emits `data-saastro="img:<fieldPrefix>"`. */
|
|
39
|
+
fieldPrefix?: string
|
|
40
|
+
class?: string
|
|
41
|
+
/** Force-disable CF transforms for this instance (serve the raw src). */
|
|
42
|
+
raw?: boolean
|
|
43
|
+
/** Override the media host (default `media.saastro.io`). */
|
|
44
|
+
mediaHost?: string
|
|
45
|
+
[key: string]: unknown
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const {
|
|
49
|
+
src,
|
|
50
|
+
alt,
|
|
51
|
+
width,
|
|
52
|
+
height,
|
|
53
|
+
sizes = '100vw',
|
|
54
|
+
widths,
|
|
55
|
+
quality = 80,
|
|
56
|
+
fit,
|
|
57
|
+
loading = 'lazy',
|
|
58
|
+
fetchpriority,
|
|
59
|
+
decoding = 'async',
|
|
60
|
+
fieldPrefix,
|
|
61
|
+
class: className,
|
|
62
|
+
raw = false,
|
|
63
|
+
mediaHost,
|
|
64
|
+
...rest
|
|
65
|
+
} = Astro.props
|
|
66
|
+
|
|
67
|
+
// Only emit /cdn-cgi/image URLs where a CF edge will actually serve them.
|
|
68
|
+
// `import.meta.env.DEV` is statically true in `astro dev` (no edge there);
|
|
69
|
+
// the host check is a belt-and-suspenders guard for `astro preview` /
|
|
70
|
+
// `wrangler dev`; PUBLIC_SAASTRO_IMG_RAW is a per-environment kill switch.
|
|
71
|
+
const isDev = import.meta.env.DEV === true
|
|
72
|
+
const reqHost = Astro.request.headers.get('host') ?? ''
|
|
73
|
+
const isLocalHost = /^(localhost|127\.0\.0\.1|\[::1\])(:\d+)?$/.test(reqHost)
|
|
74
|
+
const forceRaw = import.meta.env.PUBLIC_SAASTRO_IMG_RAW === '1'
|
|
75
|
+
const optimize = !isDev && !isLocalHost && !forceRaw && !raw
|
|
76
|
+
|
|
77
|
+
const img = buildImage({
|
|
78
|
+
src,
|
|
79
|
+
widths: widths ?? [...DEFAULT_WIDTHS],
|
|
80
|
+
quality,
|
|
81
|
+
fit,
|
|
82
|
+
optimize,
|
|
83
|
+
mediaHost,
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
const editAttrs = editableImage(fieldPrefix)
|
|
87
|
+
const isEditable = !!fieldPrefix
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
{
|
|
91
|
+
img.srcset ? (
|
|
92
|
+
<picture>
|
|
93
|
+
{/* One <source>: `format=auto` already negotiates avif/webp/jpeg at the
|
|
94
|
+
edge, so a single transformed srcset is enough. */}
|
|
95
|
+
<source srcset={img.srcset} sizes={sizes} />
|
|
96
|
+
<img
|
|
97
|
+
src={isEditable ? img.rawSrc : (img.optimizedSrc ?? img.rawSrc)}
|
|
98
|
+
alt={alt}
|
|
99
|
+
width={width}
|
|
100
|
+
height={height}
|
|
101
|
+
sizes={sizes}
|
|
102
|
+
loading={loading}
|
|
103
|
+
fetchpriority={fetchpriority}
|
|
104
|
+
decoding={decoding}
|
|
105
|
+
class={className}
|
|
106
|
+
{...editAttrs}
|
|
107
|
+
{...rest}
|
|
108
|
+
/>
|
|
109
|
+
</picture>
|
|
110
|
+
) : (
|
|
111
|
+
<img
|
|
112
|
+
src={img.rawSrc}
|
|
113
|
+
alt={alt}
|
|
114
|
+
width={width}
|
|
115
|
+
height={height}
|
|
116
|
+
loading={loading}
|
|
117
|
+
fetchpriority={fetchpriority}
|
|
118
|
+
decoding={decoding}
|
|
119
|
+
class={className}
|
|
120
|
+
{...editAttrs}
|
|
121
|
+
{...rest}
|
|
122
|
+
/>
|
|
123
|
+
)
|
|
124
|
+
}
|