@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 +16 -2
- package/tests/plugin-pages.test.ts +94 -0
- package/tsconfig.json +4 -0
- package/vitest.config.ts +5 -0
- package/LICENSE +0 -86
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oneie/plugin-pages",
|
|
3
|
-
"version": "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": "
|
|
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
package/vitest.config.ts
ADDED
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
|