@oneie/plugin-pages 0.1.0 → 0.2.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oneie/plugin-pages",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "ONE pages — renders a workspace's published Puck pages on any Astro site via the public pages:view receiver",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "type": "module",
@@ -11,6 +11,10 @@
11
11
  "./fetchPage.ts": "./src/fetchPage.ts",
12
12
  "./PageRoute.astro": "./src/PageRoute.astro"
13
13
  },
14
+ "scripts": {
15
+ "build": "tsc --noEmit",
16
+ "test": "vitest run"
17
+ },
14
18
  "dependencies": {
15
19
  "clsx": "^2.1.1",
16
20
  "lucide-react": "^1.14.0",
@@ -19,8 +23,18 @@
19
23
  },
20
24
  "peerDependencies": {
21
25
  "astro": ">=6.0.0",
22
- "@oneie/frontend": ">=0.1.0",
26
+ "@oneie/frontend": "^0.1.1",
23
27
  "@puckeditor/core": ">=0.21.0",
24
28
  "react": ">=19.0.0"
29
+ },
30
+ "devDependencies": {
31
+ "astro": "^6.2.2",
32
+ "@puckeditor/core": "^0.21.3",
33
+ "react": "^19.0.0",
34
+ "react-dom": "^19.0.0",
35
+ "@types/react": "^19.2.14",
36
+ "@types/react-dom": "^19.2.3",
37
+ "typescript": "^5.7.3",
38
+ "vitest": "^4.1.6"
25
39
  }
26
40
  }
@@ -0,0 +1,94 @@
1
+ import { describe, it, expect } from 'vitest'
2
+ import { createElement } from 'react'
3
+ import { renderToStaticMarkup } from 'react-dom/server'
4
+ import { PageRenderer, type PuckData } from '../src/PageRenderer'
5
+ import { fetchPage } from '../src/fetchPage'
6
+
7
+ // Real production fixture — the exact `pages:view` response for workspace `tony`,
8
+ // page `beer`, captured live 2026-07-11. Not hand-invented: this is what caught
9
+ // C1's stub-block bug (block type names that never matched any real page) — a
10
+ // self-referential fixture would never have caught that, so this test uses real
11
+ // content on purpose. `Heading` (id only) is a structural block outside this
12
+ // cycle's ported set — expected to render nothing, not a failure.
13
+ const beerPage = {
14
+ root: {},
15
+ content: [
16
+ { type: 'LandingHero', props: { id: 'LandingHero-kr5o1y' } },
17
+ { type: 'Heading', props: { id: 'Heading-jxrapx' } },
18
+ { type: 'LandingFeatures', props: { id: 'LandingFeatures-al6pnl' } },
19
+ { type: 'HowItWorks', props: { id: 'HowItWorks-6gplnc' } },
20
+ { type: 'ProofBar', props: { id: 'ProofBar-g3yvbh' } },
21
+ { type: 'SecondaryCTA', props: { id: 'SecondaryCTA-drayza' } },
22
+ { type: 'FAQSection', props: { id: 'FAQSection-y7z0lt' } },
23
+ { type: 'Testimonials', props: { id: 'Testimonials-9cq0fo' } },
24
+ { type: 'PricingSection', props: { id: 'PricingSection-mtqhe8' } },
25
+ ],
26
+ zones: {},
27
+ } as unknown as PuckData
28
+
29
+ const mockFetch = (body: unknown): typeof fetch =>
30
+ (async () =>
31
+ new Response(JSON.stringify(body), {
32
+ status: 200,
33
+ headers: { 'content-type': 'application/json' },
34
+ })) as unknown as typeof fetch
35
+
36
+ describe('PageRenderer — real page content', () => {
37
+ it('renders every block type from a real published page (not a self-invented fixture)', () => {
38
+ const html = renderToStaticMarkup(createElement(PageRenderer, { data: beerPage }))
39
+ // The real beer page's stored props are just `{id}` per block (never customized
40
+ // past insertion) — Puck's read-only <Render> does NOT merge each component's
41
+ // config-level `defaultProps` (that's editor-only, for new-component insertion);
42
+ // it passes stored props straight through, so each *component's own* JS default
43
+ // parameter value is what actually renders. Asserting on those (not the config
44
+ // defaultProps) is what proves the real registry keys resolve to the right
45
+ // component — a stub-key config would render none of this.
46
+ expect(html).toContain('What you get') // LandingFeatures component default title
47
+ expect(html).toContain('How it works') // HowItWorks component default title
48
+ expect(html).toContain('Common questions') // FAQSection component default title
49
+ expect(html).toContain('What agencies say') // Testimonials component default title
50
+ expect(html).toContain('Simple pricing') // PricingSection component default title
51
+ expect(html).toContain('No credit card · 2-minute setup') // LandingHero + SecondaryCTA shared default frictionText
52
+ expect(html).toContain('lucide-arrow-right') // LandingHero/SecondaryCTA CTA icon actually rendered
53
+ expect((html.match(/<section/g) ?? []).length).toBe(8) // 8 of the 9 fixture blocks are ported (Heading is out of scope)
54
+ })
55
+
56
+ it('renders nothing for a block type outside the ported set, without throwing', () => {
57
+ const onlyUnknown = { root: {}, content: [{ type: 'Heading', props: { id: 'x' } }], zones: {} } as unknown as PuckData
58
+ expect(() => renderToStaticMarkup(createElement(PageRenderer, { data: onlyUnknown }))).not.toThrow()
59
+ })
60
+
61
+ it('never renders a javascript: URL stored in a link href (stored-XSS guard)', () => {
62
+ // Page content is workspace-owner-authored but rendered to the PUBLIC with no
63
+ // login — a malicious/compromised href must not become an executable link on
64
+ // every visitor's browser, on every site that installs this package.
65
+ const maliciousPage = {
66
+ root: {},
67
+ content: [
68
+ { type: 'LandingHero', props: { id: 'h1', headline: 'x', subhead: 'y', primaryCta: { label: 'Go', href: "javascript:alert(document.cookie)" } } },
69
+ { type: 'SecondaryCTA', props: { id: 's1', headline: 'x', primaryCta: { label: 'Go', href: "javascript:alert(1)" } } },
70
+ { type: 'PricingSection', props: { id: 'p1', tiers: [{ name: 'Free', monthlyPrice: '$0', annualPrice: '$0', description: '', features: [], cta: 'Buy', ctaHref: "javascript:alert(1)" }] } },
71
+ { type: 'FooterSection', props: { id: 'f1', links: [{ label: 'Evil', href: "javascript:alert(1)" }], socials: [{ platform: 'twitter', href: "data:text/html,evil" }] } },
72
+ ],
73
+ zones: {},
74
+ } as unknown as PuckData
75
+ const html = renderToStaticMarkup(createElement(PageRenderer, { data: maliciousPage }))
76
+ expect(html).not.toContain('javascript:')
77
+ expect(html).not.toContain('data:text/html')
78
+ })
79
+ })
80
+
81
+ describe('fetchPage', () => {
82
+ it('returns the Puck data for a published page', async () => {
83
+ const envelope = { outcome: 'result', result: { ok: true, slug: 'tony', title: 'Beer', data: beerPage, url: '/p/beer' } }
84
+ const data = await fetchPage('tony', 'beer', { fetchImpl: mockFetch(envelope) })
85
+ expect(data).not.toBeNull()
86
+ expect((data as PuckData).content[0].type).toBe('LandingHero')
87
+ })
88
+
89
+ it('never returns a draft page (server filters via status=published)', async () => {
90
+ const envelope = { outcome: 'result', result: { ok: false, error: 'not_found' } }
91
+ const data = await fetchPage('tony', 'secret-draft', { fetchImpl: mockFetch(envelope) })
92
+ expect(data).toBeNull()
93
+ })
94
+ })
package/tsconfig.json ADDED
@@ -0,0 +1,4 @@
1
+ {
2
+ "extends": "../tsconfig.base.json",
3
+ "include": ["src"]
4
+ }
@@ -0,0 +1,5 @@
1
+ import { defineConfig } from 'vitest/config'
2
+
3
+ export default defineConfig({
4
+ esbuild: { jsx: 'automatic', jsxImportSource: 'react' },
5
+ })
package/LICENSE DELETED
@@ -1,86 +0,0 @@
1
- # ONE License (Version 1.0)
2
-
3
- Copyright (c) 2024-2026 one.ie
4
-
5
- ## Maximum Freedom, One Obligation
6
-
7
- This license empowers you with complete commercial freedom to use, reuse, modify, sell and resell the software, AI and data, at any price you wish.
8
-
9
- No usage limits. No royalty fees. Just pure, unrestricted ability to innovate and profit from the software.
10
-
11
- You are free to run ONE locally, on your own servers, in the cloud and at the edge.
12
-
13
- ## You Receive
14
-
15
- ### Unlimited Rights
16
-
17
- You have unrestricted rights to use, modify, license, sublicense, distribute, sell, resell and monetize the Software without restrictions, subject only to the Brand Requirement below.
18
-
19
- ### Permitted Actions
20
-
21
- Including, but not limited to:
22
-
23
- - Commercial use and integration into any systems
24
- - Creation and sale of derivative works
25
- - Providing software as a service
26
- - AI training and content generation
27
- - Patenting innovations based on the Software
28
- - Open source applications and integrations
29
-
30
- ### License Compatibility
31
-
32
- This license is compatible with all major open-source licenses, including:
33
-
34
- - MIT License
35
- - Apache License
36
- - GNU General Public License (GPL)
37
- - BSD Licenses
38
- - Mozilla Public License
39
-
40
- ### Perpetual Rights
41
-
42
- These rights are granted in perpetuity and are irrevocable, provided the Brand Requirement is maintained.
43
-
44
- ## Intellectual Property
45
-
46
- - We retain ownership of the original Software and data.
47
- - You own any modifications you make.
48
- - You never have to share your code or data.
49
-
50
- ## Brand Requirement
51
-
52
- The only obligation is:
53
-
54
- - Don't remove the ONE brand, logo, and link to https://one.ie/ from the deployed product.
55
-
56
- To remove the Brand Requirement — white-label, no visible ONE branding — obtain the
57
- **ONE Enterprise License** (see `LICENSE-ENTERPRISE.md` or contact agent@one.ie).
58
-
59
- ## Liability and Warranty
60
-
61
- The Software and data is provided "AS IS". We bear no liability for its use.
62
-
63
- ## Termination
64
-
65
- This license terminates if you remove or hide the ONE brand, logo, or link without
66
- holding a current ONE Enterprise License.
67
-
68
- ## Governing Law and Disputes
69
-
70
- This license is governed by the laws of Ireland. The parties will attempt to resolve disputes through good-faith negotiation. If necessary, disputes will proceed to mediation under the Mediators' Institute of Ireland rules, and then to binding arbitration under the Arbitration Act 2010, seated in Dublin, conducted in English.
71
-
72
- ---
73
-
74
- This license is designed to maximize freedom to innovate and profit. There is no copyleft requirement to share any code, making it suitable for enterprise use.
75
-
76
- ## Enterprise Solutions
77
-
78
- Building something big? We're here to help:
79
-
80
- - **Free** — use every feature with the ONE brand link in the footer
81
- - **White-label** — remove the brand requirement (ONE Enterprise License)
82
- - **Custom** — white-label solutions tailored to your needs
83
- - **Enterprise** — full support and deployment assistance
84
- - **Training** — help getting your team started
85
-
86
- Contact agent@one.ie to share your needs · Learn at https://one.ie/learn · Agents: https://one.ie/llms.txt