@stone-js/starters 0.8.3 → 0.8.4
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/basic-react-declarative/app/Application.tsx +1 -0
- package/basic-react-declarative/package.json +5 -5
- package/basic-react-declarative/public/logo.svg +16 -0
- package/basic-react-declarative/tests/Application.spec.ts +11 -2
- package/basic-react-imperative/app/Application.tsx +1 -0
- package/basic-react-imperative/package.json +5 -5
- package/basic-react-imperative/public/logo.svg +16 -0
- package/basic-react-imperative/tests/Application.spec.ts +11 -2
- package/basic-service-declarative/package.json +4 -4
- package/basic-service-imperative/package.json +4 -4
- package/continuum-showcase/LICENSE +21 -0
- package/continuum-showcase/app/Application.ts +45 -0
- package/continuum-showcase/app/layouts/MainLayout.tsx +1 -2
- package/continuum-showcase/app/pages/BlogPostPage.tsx +1 -1
- package/continuum-showcase/app/pages/HomePage.tsx +2 -2
- package/continuum-showcase/assets/css/index.css +26 -0
- package/continuum-showcase/assets/images/logo.svg +16 -0
- package/continuum-showcase/package.json +22 -10
- package/continuum-showcase/tests/HomePage.spec.tsx +40 -0
- package/continuum-showcase/tsconfig.json +33 -0
- package/continuum-showcase/vitest.config.ts +31 -0
- package/full-react-declarative/app/layout/AppLayout/AppLayout.tsx +1 -1
- package/full-react-declarative/app/layout/ErrorLayout/ErrorLayout.tsx +1 -1
- package/full-react-declarative/app/layout/SecurityLayout/SecurityLayout.tsx +1 -1
- package/full-react-declarative/package.json +10 -10
- package/full-react-declarative/public/logo.svg +16 -0
- package/full-react-declarative/tests/AppLayout.spec.ts +51 -0
- package/full-react-imperative/app/components/LayoutHeader/LayoutHeader.tsx +1 -1
- package/full-react-imperative/app/components/PostWidget/PostWidget.tsx +1 -1
- package/full-react-imperative/app/components/PullToRefresh/PullToRefresh.css +10 -0
- package/full-react-imperative/app/components/PullToRefresh/PullToRefresh.tsx +66 -0
- package/full-react-imperative/app/layout/ErrorLayout/ErrorLayout.tsx +1 -1
- package/full-react-imperative/app/layout/SecurityLayout/SecurityLayout.tsx +1 -1
- package/full-react-imperative/package.json +11 -12
- package/full-react-imperative/public/logo.svg +16 -0
- package/full-react-imperative/tests/LayoutHeader.spec.ts +39 -0
- package/full-service-declarative/package.json +9 -9
- package/full-service-declarative/tests/PostService.spec.ts +56 -0
- package/full-service-declarative/vitest.config.ts +1 -1
- package/full-service-imperative/package.json +9 -9
- package/full-service-imperative/tests/PostService.spec.ts +47 -0
- package/full-service-imperative/vitest.config.ts +1 -1
- package/package.json +1 -1
- package/standard-react-declarative/app/pages/welcome/WelcomePage.tsx +1 -0
- package/standard-react-declarative/package.json +8 -8
- package/standard-react-declarative/public/logo.svg +16 -0
- package/standard-react-declarative/tests/WelcomePage.spec.ts +69 -0
- package/standard-react-imperative/app/pages/welcome/WelcomePage.tsx +1 -0
- package/standard-react-imperative/package.json +8 -8
- package/standard-react-imperative/public/logo.svg +16 -0
- package/standard-react-imperative/tests/WelcomePage.spec.ts +51 -0
- package/standard-service-declarative/package.json +6 -6
- package/standard-service-declarative/tests/Application.spec.ts +91 -0
- package/standard-service-declarative/vitest.config.ts +1 -1
- package/standard-service-imperative/package.json +6 -6
- package/standard-service-imperative/tests/Application.spec.ts +75 -0
- package/standard-service-imperative/vitest.config.ts +1 -1
- package/basic-react-declarative/public/logo.png +0 -0
- package/basic-react-declarative/tests/__snapshots__/Application.spec.ts.snap +0 -3
- package/basic-react-imperative/public/logo.png +0 -0
- package/basic-react-imperative/tests/__snapshots__/Application.spec.ts.snap +0 -3
- package/full-react-declarative/assets/img/logo.png +0 -0
- package/full-react-declarative/public/logo.png +0 -0
- package/full-react-imperative/assets/img/logo.png +0 -0
- package/full-react-imperative/public/logo.png +0 -0
|
@@ -75,6 +75,7 @@ export class Application implements IPage<ReactIncomingEvent> {
|
|
|
75
75
|
render ({ data }: PageRenderContext<ResponseData>): JSX.Element {
|
|
76
76
|
return (
|
|
77
77
|
<section className='container'>
|
|
78
|
+
<img src='/logo.svg' alt='Stone.js' width={96} height={96} />
|
|
78
79
|
<h1 className='h1 text-center mt-64'>{data?.message}</h1>
|
|
79
80
|
</section>
|
|
80
81
|
)
|
|
@@ -20,16 +20,16 @@
|
|
|
20
20
|
"dependencies": {
|
|
21
21
|
"react": "^19.0.0",
|
|
22
22
|
"react-dom": "^19.0.0",
|
|
23
|
-
"@stone-js/core": "^0.8.
|
|
24
|
-
"@stone-js/use-react": "^0.8.
|
|
25
|
-
"@stone-js/browser-adapter": "^0.8.
|
|
26
|
-
"@stone-js/node-cli-adapter": "^0.8.
|
|
23
|
+
"@stone-js/core": "^0.8.4",
|
|
24
|
+
"@stone-js/use-react": "^0.8.4",
|
|
25
|
+
"@stone-js/browser-adapter": "^0.8.4",
|
|
26
|
+
"@stone-js/node-cli-adapter": "^0.8.4"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
29
|
"@babel/plugin-proposal-decorators": "^7.25.9",
|
|
30
30
|
"@babel/preset-env": "^7.26.9",
|
|
31
31
|
"@babel/preset-typescript": "^7.27.0",
|
|
32
|
-
"@stone-js/cli": "^0.8.
|
|
32
|
+
"@stone-js/cli": "^0.8.4",
|
|
33
33
|
"@types/node": "^24.0.7",
|
|
34
34
|
"@types/react": "^19.0.7",
|
|
35
35
|
"@types/react-dom": "^19.0.3",
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 96 96" width="96" height="96" role="img" aria-label="Stone.js">
|
|
2
|
+
<title>Stone.js</title>
|
|
3
|
+
<style>
|
|
4
|
+
.ink { stroke: #1B1613; }
|
|
5
|
+
@media (prefers-color-scheme: dark) { .ink { stroke: #EFE8DC; } }
|
|
6
|
+
</style>
|
|
7
|
+
<defs>
|
|
8
|
+
<linearGradient id="braise" gradientUnits="userSpaceOnUse" x1="22" y1="14" x2="74" y2="26.1">
|
|
9
|
+
<stop offset="0" stop-color="#FFC46B" />
|
|
10
|
+
<stop offset="1" stop-color="#FF5A1F" />
|
|
11
|
+
</linearGradient>
|
|
12
|
+
</defs>
|
|
13
|
+
<path class="ink" d="M 79.96 36.4 A 34 34 0 0 1 53.9 81.5" fill="none" stroke-width="11" stroke-linecap="round" opacity=".85" />
|
|
14
|
+
<path class="ink" d="M 42.1 81.5 A 34 34 0 0 1 16.05 36.4" fill="none" stroke-width="11" stroke-linecap="round" opacity=".85" />
|
|
15
|
+
<path d="M 22 26.1 A 34 34 0 0 1 74 26.1" fill="none" stroke="url(#braise)" stroke-width="11" stroke-linecap="round" />
|
|
16
|
+
</svg>
|
|
@@ -83,7 +83,7 @@ describe('Application', () => {
|
|
|
83
83
|
expect(mockedLogger.info).toHaveBeenCalledWith(expectedMessage)
|
|
84
84
|
})
|
|
85
85
|
|
|
86
|
-
it('should render
|
|
86
|
+
it('should render the message', () => {
|
|
87
87
|
// Arrange
|
|
88
88
|
const message = 'Hello World!'
|
|
89
89
|
|
|
@@ -91,6 +91,15 @@ describe('Application', () => {
|
|
|
91
91
|
const response = renderToString(app.render({ data: { message } } as any))
|
|
92
92
|
|
|
93
93
|
// Assert
|
|
94
|
-
expect(response).
|
|
94
|
+
expect(response).toContain(message)
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
it('should render the Stone.js logo', () => {
|
|
98
|
+
// Act
|
|
99
|
+
const response = renderToString(app.render({ data: { message: 'Hello World!' } } as any))
|
|
100
|
+
|
|
101
|
+
// Assert
|
|
102
|
+
expect(response).toContain('/logo.svg')
|
|
103
|
+
expect(response).toContain('alt="Stone.js"')
|
|
95
104
|
})
|
|
96
105
|
})
|
|
@@ -44,6 +44,7 @@ export const FactoryHandler = ({ logger }: AppOptions): IPage<ReactIncomingEvent
|
|
|
44
44
|
render ({ data }: PageRenderContext<ResponseData>): JSX.Element {
|
|
45
45
|
return (
|
|
46
46
|
<section className='container'>
|
|
47
|
+
<img src='/logo.svg' alt='Stone.js' width={96} height={96} />
|
|
47
48
|
<h1 className='h1 text-center mt-64'>{data?.message}</h1>
|
|
48
49
|
</section>
|
|
49
50
|
)
|
|
@@ -20,16 +20,16 @@
|
|
|
20
20
|
"dependencies": {
|
|
21
21
|
"react": "^19.0.0",
|
|
22
22
|
"react-dom": "^19.0.0",
|
|
23
|
-
"@stone-js/core": "^0.8.
|
|
24
|
-
"@stone-js/use-react": "^0.8.
|
|
25
|
-
"@stone-js/browser-adapter": "^0.8.
|
|
26
|
-
"@stone-js/node-cli-adapter": "^0.8.
|
|
23
|
+
"@stone-js/core": "^0.8.4",
|
|
24
|
+
"@stone-js/use-react": "^0.8.4",
|
|
25
|
+
"@stone-js/browser-adapter": "^0.8.4",
|
|
26
|
+
"@stone-js/node-cli-adapter": "^0.8.4"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
29
|
"@babel/plugin-proposal-decorators": "^7.25.9",
|
|
30
30
|
"@babel/preset-env": "^7.26.9",
|
|
31
31
|
"@babel/preset-typescript": "^7.27.0",
|
|
32
|
-
"@stone-js/cli": "^0.8.
|
|
32
|
+
"@stone-js/cli": "^0.8.4",
|
|
33
33
|
"@types/node": "^24.0.7",
|
|
34
34
|
"@types/react": "^19.0.7",
|
|
35
35
|
"@types/react-dom": "^19.0.3",
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 96 96" width="96" height="96" role="img" aria-label="Stone.js">
|
|
2
|
+
<title>Stone.js</title>
|
|
3
|
+
<style>
|
|
4
|
+
.ink { stroke: #1B1613; }
|
|
5
|
+
@media (prefers-color-scheme: dark) { .ink { stroke: #EFE8DC; } }
|
|
6
|
+
</style>
|
|
7
|
+
<defs>
|
|
8
|
+
<linearGradient id="braise" gradientUnits="userSpaceOnUse" x1="22" y1="14" x2="74" y2="26.1">
|
|
9
|
+
<stop offset="0" stop-color="#FFC46B" />
|
|
10
|
+
<stop offset="1" stop-color="#FF5A1F" />
|
|
11
|
+
</linearGradient>
|
|
12
|
+
</defs>
|
|
13
|
+
<path class="ink" d="M 79.96 36.4 A 34 34 0 0 1 53.9 81.5" fill="none" stroke-width="11" stroke-linecap="round" opacity=".85" />
|
|
14
|
+
<path class="ink" d="M 42.1 81.5 A 34 34 0 0 1 16.05 36.4" fill="none" stroke-width="11" stroke-linecap="round" opacity=".85" />
|
|
15
|
+
<path d="M 22 26.1 A 34 34 0 0 1 74 26.1" fill="none" stroke="url(#braise)" stroke-width="11" stroke-linecap="round" />
|
|
16
|
+
</svg>
|
|
@@ -55,7 +55,7 @@ describe('Application', () => {
|
|
|
55
55
|
expect(mockedLogger.info).toHaveBeenCalledWith(expectedMessage)
|
|
56
56
|
})
|
|
57
57
|
|
|
58
|
-
it('should render
|
|
58
|
+
it('should render the message', () => {
|
|
59
59
|
// Arrange
|
|
60
60
|
const message = 'Hello World!'
|
|
61
61
|
|
|
@@ -63,7 +63,16 @@ describe('Application', () => {
|
|
|
63
63
|
const response = renderToString(app.render({ data: { message } } as any))
|
|
64
64
|
|
|
65
65
|
// Assert
|
|
66
|
-
expect(response).
|
|
66
|
+
expect(response).toContain(message)
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
it('should render the Stone.js logo', () => {
|
|
70
|
+
// Act
|
|
71
|
+
const response = renderToString(app.render({ data: { message: 'Hello World!' } } as any))
|
|
72
|
+
|
|
73
|
+
// Assert
|
|
74
|
+
expect(response).toContain('/logo.svg')
|
|
75
|
+
expect(response).toContain('alt="Stone.js"')
|
|
67
76
|
})
|
|
68
77
|
|
|
69
78
|
it('should get config', () => {
|
|
@@ -18,15 +18,15 @@
|
|
|
18
18
|
"test:cvg": "npm run test -- --coverage"
|
|
19
19
|
},
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"@stone-js/core": "^0.8.
|
|
22
|
-
"@stone-js/node-cli-adapter": "^0.8.
|
|
23
|
-
"@stone-js/node-http-adapter": "^0.8.
|
|
21
|
+
"@stone-js/core": "^0.8.4",
|
|
22
|
+
"@stone-js/node-cli-adapter": "^0.8.4",
|
|
23
|
+
"@stone-js/node-http-adapter": "^0.8.4"
|
|
24
24
|
},
|
|
25
25
|
"devDependencies": {
|
|
26
26
|
"@babel/plugin-proposal-decorators": "^7.25.9",
|
|
27
27
|
"@babel/preset-env": "^7.26.9",
|
|
28
28
|
"@babel/preset-typescript": "^7.27.0",
|
|
29
|
-
"@stone-js/cli": "^0.8.
|
|
29
|
+
"@stone-js/cli": "^0.8.4",
|
|
30
30
|
"@types/node": "^24.0.7",
|
|
31
31
|
"@vitest/coverage-v8": "^3.0.9",
|
|
32
32
|
"vitest": "^3.0.9"
|
|
@@ -18,15 +18,15 @@
|
|
|
18
18
|
"test:cvg": "npm run test -- --coverage"
|
|
19
19
|
},
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"@stone-js/core": "^0.8.
|
|
22
|
-
"@stone-js/node-cli-adapter": "^0.8.
|
|
23
|
-
"@stone-js/node-http-adapter": "^0.8.
|
|
21
|
+
"@stone-js/core": "^0.8.4",
|
|
22
|
+
"@stone-js/node-cli-adapter": "^0.8.4",
|
|
23
|
+
"@stone-js/node-http-adapter": "^0.8.4"
|
|
24
24
|
},
|
|
25
25
|
"devDependencies": {
|
|
26
26
|
"@babel/plugin-proposal-decorators": "^7.25.9",
|
|
27
27
|
"@babel/preset-env": "^7.26.9",
|
|
28
28
|
"@babel/preset-typescript": "^7.27.0",
|
|
29
|
-
"@stone-js/cli": "^0.8.
|
|
29
|
+
"@stone-js/cli": "^0.8.4",
|
|
30
30
|
"@types/node": "^24.0.7",
|
|
31
31
|
"@vitest/coverage-v8": "^3.0.9",
|
|
32
32
|
"vitest": "^3.0.9"
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright © 2026 Stone Foundation
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { Routing } from '@stone-js/router'
|
|
2
|
+
import { UseReact } from '@stone-js/use-react'
|
|
3
|
+
import { Browser } from '@stone-js/browser-adapter'
|
|
4
|
+
import { NodeHttp } from '@stone-js/node-http-adapter'
|
|
5
|
+
import { StoneApp, AdapterHookListenerContext, Hook, LogLevel, Logger } from '@stone-js/core'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Application — the single entry point of the showcase.
|
|
9
|
+
*
|
|
10
|
+
* The whole point of the Continuum Architecture is here: you declare the CONTEXT once
|
|
11
|
+
* (the adapters + features below) and the same domain (the `@Page` handlers) runs under it,
|
|
12
|
+
* server-side and in the browser, unchanged.
|
|
13
|
+
*
|
|
14
|
+
* @NodeHttp() serves the pages over HTTP with server-side rendering.
|
|
15
|
+
* @Browser() re-hydrates and runs the very same pages client-side.
|
|
16
|
+
* @UseReact() plugs React in as the view engine.
|
|
17
|
+
* @Routing() enables the universal router the `@Page` classes register on.
|
|
18
|
+
* @StoneApp() bootstraps the application (required).
|
|
19
|
+
*/
|
|
20
|
+
@Routing()
|
|
21
|
+
@Browser()
|
|
22
|
+
@UseReact()
|
|
23
|
+
@NodeHttp({ default: true })
|
|
24
|
+
@StoneApp({ name: 'Stone.js Showcase', logger: { level: LogLevel.INFO } })
|
|
25
|
+
export class Application {
|
|
26
|
+
/**
|
|
27
|
+
* Run before the application starts.
|
|
28
|
+
*
|
|
29
|
+
* @param blueprint - The blueprint.
|
|
30
|
+
*/
|
|
31
|
+
@Hook('onStart')
|
|
32
|
+
onStart ({ blueprint }: AdapterHookListenerContext): void {
|
|
33
|
+
Logger.log(`${String(blueprint.get('stone.name', 'Stone App'))} is starting...`)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Run just before the application stops.
|
|
38
|
+
*
|
|
39
|
+
* @param blueprint - The blueprint.
|
|
40
|
+
*/
|
|
41
|
+
@Hook('onStop')
|
|
42
|
+
onStop ({ blueprint }: AdapterHookListenerContext): void {
|
|
43
|
+
Logger.log(`${String(blueprint.get('stone.name', 'Stone App'))} is stopping...`)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
|
-
import { PageLayout } from '@stone-js/
|
|
3
|
-
import { IPageLayout, PageLayoutRenderContext } from '@stone-js/use-react'
|
|
2
|
+
import { PageLayout, IPageLayout, PageLayoutRenderContext } from '@stone-js/use-react'
|
|
4
3
|
|
|
5
4
|
/**
|
|
6
5
|
* The default layout. In a future iteration (hierarchical heads), a layout may also
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
body {
|
|
2
|
+
margin: 0;
|
|
3
|
+
padding: 0;
|
|
4
|
+
color: #333;
|
|
5
|
+
font-size: 14px;
|
|
6
|
+
line-height: 1.5;
|
|
7
|
+
background-color: #fff;
|
|
8
|
+
font-family: 'Roboto', sans-serif;
|
|
9
|
+
}
|
|
10
|
+
.h1 {
|
|
11
|
+
margin: 0;
|
|
12
|
+
font-size: 3rem;
|
|
13
|
+
font-weight: 500;
|
|
14
|
+
font-weight: bold;
|
|
15
|
+
}
|
|
16
|
+
.container {
|
|
17
|
+
width: 100vw;
|
|
18
|
+
display: flex;
|
|
19
|
+
justify-content: center;
|
|
20
|
+
}
|
|
21
|
+
.text-center {
|
|
22
|
+
text-align: center;
|
|
23
|
+
}
|
|
24
|
+
.mt-64 {
|
|
25
|
+
margin-top: 64px;
|
|
26
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 96 96" width="96" height="96" role="img" aria-label="Stone.js">
|
|
2
|
+
<title>Stone.js</title>
|
|
3
|
+
<style>
|
|
4
|
+
.ink { stroke: #1B1613; }
|
|
5
|
+
@media (prefers-color-scheme: dark) { .ink { stroke: #EFE8DC; } }
|
|
6
|
+
</style>
|
|
7
|
+
<defs>
|
|
8
|
+
<linearGradient id="braise" gradientUnits="userSpaceOnUse" x1="22" y1="14" x2="74" y2="26.1">
|
|
9
|
+
<stop offset="0" stop-color="#FFC46B" />
|
|
10
|
+
<stop offset="1" stop-color="#FF5A1F" />
|
|
11
|
+
</linearGradient>
|
|
12
|
+
</defs>
|
|
13
|
+
<path class="ink" d="M 79.96 36.4 A 34 34 0 0 1 53.9 81.5" fill="none" stroke-width="11" stroke-linecap="round" opacity=".85" />
|
|
14
|
+
<path class="ink" d="M 42.1 81.5 A 34 34 0 0 1 16.05 36.4" fill="none" stroke-width="11" stroke-linecap="round" opacity=".85" />
|
|
15
|
+
<path d="M 22 26.1 A 34 34 0 0 1 74 26.1" fill="none" stroke="url(#braise)" stroke-width="11" stroke-linecap="round" />
|
|
16
|
+
</svg>
|
|
@@ -3,23 +3,35 @@
|
|
|
3
3
|
"version": "0.0.0",
|
|
4
4
|
"private": true,
|
|
5
5
|
"type": "module",
|
|
6
|
-
"description": "
|
|
6
|
+
"description": "Continuum showcase: one domain rendered server-side and in the browser, with the fluent head/OpenGraph API and static-asset aliases.",
|
|
7
7
|
"scripts": {
|
|
8
8
|
"dev": "stone dev",
|
|
9
9
|
"build": "stone build",
|
|
10
|
-
"
|
|
10
|
+
"build:ssr": "stone build --ssr",
|
|
11
|
+
"preview": "stone preview",
|
|
12
|
+
"test": "vitest run",
|
|
13
|
+
"test:cvg": "npm run test -- --coverage"
|
|
11
14
|
},
|
|
12
15
|
"dependencies": {
|
|
13
|
-
"@stone-js/browser-adapter": "^0.8.3",
|
|
14
|
-
"@stone-js/core": "^0.8.3",
|
|
15
|
-
"@stone-js/node-http-adapter": "^0.8.3",
|
|
16
|
-
"@stone-js/router": "^0.8.3",
|
|
17
|
-
"@stone-js/use-react": "^0.8.3",
|
|
18
|
-
"@stone-js/use-view": "^0.8.3",
|
|
19
16
|
"react": "^19.0.0",
|
|
20
|
-
"react-dom": "^19.0.0"
|
|
17
|
+
"react-dom": "^19.0.0",
|
|
18
|
+
"@stone-js/core": "^0.8.4",
|
|
19
|
+
"@stone-js/router": "^0.8.4",
|
|
20
|
+
"@stone-js/use-react": "^0.8.4",
|
|
21
|
+
"@stone-js/http-core": "^0.8.4",
|
|
22
|
+
"@stone-js/pipeline": "^0.8.4",
|
|
23
|
+
"@stone-js/browser-adapter": "^0.8.4",
|
|
24
|
+
"@stone-js/node-http-adapter": "^0.8.4"
|
|
21
25
|
},
|
|
22
26
|
"devDependencies": {
|
|
23
|
-
"@
|
|
27
|
+
"@babel/plugin-proposal-decorators": "^7.25.9",
|
|
28
|
+
"@babel/preset-env": "^7.26.9",
|
|
29
|
+
"@babel/preset-typescript": "^7.27.0",
|
|
30
|
+
"@stone-js/cli": "^0.8.4",
|
|
31
|
+
"@types/node": "^24.0.7",
|
|
32
|
+
"@types/react": "^19.0.7",
|
|
33
|
+
"@types/react-dom": "^19.0.3",
|
|
34
|
+
"@vitest/coverage-v8": "^3.0.9",
|
|
35
|
+
"vitest": "^3.0.9"
|
|
24
36
|
}
|
|
25
37
|
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { renderToString } from 'react-dom/server'
|
|
2
|
+
import { HomePage } from '../app/pages/HomePage'
|
|
3
|
+
|
|
4
|
+
// Mock the page decorator so the class can be instantiated without a running app context.
|
|
5
|
+
vi.mock('@stone-js/use-react', async (importOriginal) => {
|
|
6
|
+
const actual: any = await importOriginal()
|
|
7
|
+
return {
|
|
8
|
+
...actual,
|
|
9
|
+
Page: vi.fn(() => vi.fn())
|
|
10
|
+
}
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
describe('HomePage', () => {
|
|
14
|
+
it('returns the domain message from handle', () => {
|
|
15
|
+
// Arrange
|
|
16
|
+
const page = new HomePage()
|
|
17
|
+
|
|
18
|
+
// Act
|
|
19
|
+
const data = page.handle()
|
|
20
|
+
|
|
21
|
+
// Assert
|
|
22
|
+
expect(data.message).toBe('Write your domain once. Stone.js applies the context.')
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
it('renders the Stone.js logo and the message', () => {
|
|
26
|
+
// Arrange
|
|
27
|
+
const page = new HomePage()
|
|
28
|
+
|
|
29
|
+
// Act
|
|
30
|
+
const html = renderToString(page.render({ data: page.handle() } as any))
|
|
31
|
+
|
|
32
|
+
// Assert: the Stone.js logo is rendered (Vite inlines the small SVG as a data URI),
|
|
33
|
+
// alongside the title and the domain message.
|
|
34
|
+
expect(html).toContain('<img')
|
|
35
|
+
expect(html).toContain('image/svg+xml')
|
|
36
|
+
expect(html).toContain('alt="Stone.js"')
|
|
37
|
+
expect(html).toContain('Stone.js Showcase')
|
|
38
|
+
expect(html).toContain('Write your domain once')
|
|
39
|
+
})
|
|
40
|
+
})
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"useDefineForClassFields": true,
|
|
5
|
+
"module": "ESNext",
|
|
6
|
+
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
|
7
|
+
"skipLibCheck": true,
|
|
8
|
+
"experimentalDecorators": true,
|
|
9
|
+
|
|
10
|
+
/* Bundler mode */
|
|
11
|
+
"moduleResolution": "bundler",
|
|
12
|
+
"allowImportingTsExtensions": true,
|
|
13
|
+
"isolatedModules": true,
|
|
14
|
+
"moduleDetection": "force",
|
|
15
|
+
"noEmit": true,
|
|
16
|
+
"jsx": "react-jsx",
|
|
17
|
+
"resolveJsonModule": true,
|
|
18
|
+
|
|
19
|
+
/* Linting */
|
|
20
|
+
"strict": true,
|
|
21
|
+
"noUnusedLocals": true,
|
|
22
|
+
"noUnusedParameters": true,
|
|
23
|
+
"noFallthroughCasesInSwitch": true,
|
|
24
|
+
"noUncheckedSideEffectImports": true,
|
|
25
|
+
|
|
26
|
+
"types": ["node", "vite/client", "vitest/globals"],
|
|
27
|
+
},
|
|
28
|
+
"include": [
|
|
29
|
+
"app",
|
|
30
|
+
"tests",
|
|
31
|
+
"vitest.config.ts"
|
|
32
|
+
]
|
|
33
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { fileURLToPath } from 'node:url'
|
|
2
|
+
import { defineConfig } from 'vitest/config'
|
|
3
|
+
|
|
4
|
+
export default defineConfig({
|
|
5
|
+
// Mirror the CLI's static-asset aliases (see stone.config.mjs) so component imports like
|
|
6
|
+
// `import logo from '@img/logo.svg'` also resolve under the test runner.
|
|
7
|
+
resolve: {
|
|
8
|
+
alias: {
|
|
9
|
+
'@img': fileURLToPath(new URL('./assets/images', import.meta.url)),
|
|
10
|
+
'@css': fileURLToPath(new URL('./assets/css', import.meta.url)),
|
|
11
|
+
'@assets': fileURLToPath(new URL('./assets', import.meta.url))
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
test: {
|
|
15
|
+
globals: true,
|
|
16
|
+
environment: 'node',
|
|
17
|
+
include: ['./tests/**/*.{test,spec}.?(c|m)[jt]s?(x)'],
|
|
18
|
+
coverage: {
|
|
19
|
+
provider: 'v8',
|
|
20
|
+
include: ['app/**/*.ts', 'app/**/*.tsx'],
|
|
21
|
+
reporter: ['text', 'html'],
|
|
22
|
+
reportsDirectory: './coverage',
|
|
23
|
+
watermarks: {
|
|
24
|
+
statements: [60, 80],
|
|
25
|
+
functions: [60, 80],
|
|
26
|
+
branches: [60, 80],
|
|
27
|
+
lines: [60, 80]
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
})
|
|
@@ -48,7 +48,7 @@ export class AppLayout implements IPageLayout {
|
|
|
48
48
|
<header>
|
|
49
49
|
<p>
|
|
50
50
|
<StoneLink to='/' className='logo'>
|
|
51
|
-
<img src='/logo.
|
|
51
|
+
<img src='/logo.svg' alt='Stone.js' width={96} height={96} />
|
|
52
52
|
<span>Stone.js</span>
|
|
53
53
|
</StoneLink>
|
|
54
54
|
</p>
|
|
@@ -19,7 +19,7 @@ export class ErrorLayout implements IPageLayout {
|
|
|
19
19
|
<header className='mt-64'>
|
|
20
20
|
<div className='container'>
|
|
21
21
|
<p className='pa-24'>
|
|
22
|
-
<img src='/logo.
|
|
22
|
+
<img src='/logo.svg' className='logo' alt='Stone.js' />
|
|
23
23
|
</p>
|
|
24
24
|
</div>
|
|
25
25
|
</header>
|
|
@@ -19,7 +19,7 @@ export class SecurityLayout implements IPageLayout {
|
|
|
19
19
|
<header className='mt-64'>
|
|
20
20
|
<div className='container'>
|
|
21
21
|
<p className='pa-24'>
|
|
22
|
-
<img src='/logo.
|
|
22
|
+
<img src='/logo.svg' className='logo' alt='Stone.js' />
|
|
23
23
|
</p>
|
|
24
24
|
</div>
|
|
25
25
|
</header>
|
|
@@ -18,15 +18,15 @@
|
|
|
18
18
|
"test:cvg": "npm run test -- --coverage"
|
|
19
19
|
},
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"@stone-js/env": "^0.8.
|
|
22
|
-
"@stone-js/http-core": "^0.8.
|
|
23
|
-
"@stone-js/node-http-adapter": "^0.8.
|
|
24
|
-
"@stone-js/router": "^0.8.
|
|
25
|
-
"@stone-js/filesystem": "^0.8.
|
|
26
|
-
"@stone-js/core": "^0.8.
|
|
27
|
-
"@stone-js/use-react": "^0.8.
|
|
28
|
-
"@stone-js/browser-adapter": "^0.8.
|
|
29
|
-
"@stone-js/node-cli-adapter": "^0.8.
|
|
21
|
+
"@stone-js/env": "^0.8.4",
|
|
22
|
+
"@stone-js/http-core": "^0.8.4",
|
|
23
|
+
"@stone-js/node-http-adapter": "^0.8.4",
|
|
24
|
+
"@stone-js/router": "^0.8.4",
|
|
25
|
+
"@stone-js/filesystem": "^0.8.4",
|
|
26
|
+
"@stone-js/core": "^0.8.4",
|
|
27
|
+
"@stone-js/use-react": "^0.8.4",
|
|
28
|
+
"@stone-js/browser-adapter": "^0.8.4",
|
|
29
|
+
"@stone-js/node-cli-adapter": "^0.8.4",
|
|
30
30
|
"axios": "^1.7.9",
|
|
31
31
|
"react": "^19.0.0",
|
|
32
32
|
"react-dom": "^19.0.0"
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"@babel/plugin-proposal-decorators": "^7.25.9",
|
|
36
36
|
"@babel/preset-env": "^7.26.9",
|
|
37
37
|
"@babel/preset-typescript": "^7.27.0",
|
|
38
|
-
"@stone-js/cli": "^0.8.
|
|
38
|
+
"@stone-js/cli": "^0.8.4",
|
|
39
39
|
"@types/axios": "^0.14.4",
|
|
40
40
|
"@types/node": "^24.0.7",
|
|
41
41
|
"@types/react": "^19.0.7",
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 96 96" width="96" height="96" role="img" aria-label="Stone.js">
|
|
2
|
+
<title>Stone.js</title>
|
|
3
|
+
<style>
|
|
4
|
+
.ink { stroke: #1B1613; }
|
|
5
|
+
@media (prefers-color-scheme: dark) { .ink { stroke: #EFE8DC; } }
|
|
6
|
+
</style>
|
|
7
|
+
<defs>
|
|
8
|
+
<linearGradient id="braise" gradientUnits="userSpaceOnUse" x1="22" y1="14" x2="74" y2="26.1">
|
|
9
|
+
<stop offset="0" stop-color="#FFC46B" />
|
|
10
|
+
<stop offset="1" stop-color="#FF5A1F" />
|
|
11
|
+
</linearGradient>
|
|
12
|
+
</defs>
|
|
13
|
+
<path class="ink" d="M 79.96 36.4 A 34 34 0 0 1 53.9 81.5" fill="none" stroke-width="11" stroke-linecap="round" opacity=".85" />
|
|
14
|
+
<path class="ink" d="M 42.1 81.5 A 34 34 0 0 1 16.05 36.4" fill="none" stroke-width="11" stroke-linecap="round" opacity=".85" />
|
|
15
|
+
<path d="M 22 26.1 A 34 34 0 0 1 74 26.1" fill="none" stroke="url(#braise)" stroke-width="11" stroke-linecap="round" />
|
|
16
|
+
</svg>
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { renderToString } from 'react-dom/server'
|
|
2
|
+
import { AppLayout } from '../app/layout/AppLayout/AppLayout'
|
|
3
|
+
|
|
4
|
+
// We must mock decorators and router-bound components to lighten the test environment
|
|
5
|
+
vi.mock('@stone-js/use-react', async (importOriginal) => {
|
|
6
|
+
const actual: any = await importOriginal()
|
|
7
|
+
return {
|
|
8
|
+
...actual,
|
|
9
|
+
PageLayout: vi.fn(() => vi.fn()),
|
|
10
|
+
StoneLink: ({ children }: any) => children,
|
|
11
|
+
StoneOutlet: ({ children }: any) => children
|
|
12
|
+
}
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
describe('AppLayout', () => {
|
|
16
|
+
let layout: AppLayout
|
|
17
|
+
|
|
18
|
+
beforeEach(() => {
|
|
19
|
+
layout = new AppLayout({} as any)
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
const render = (): string => {
|
|
23
|
+
const container = {
|
|
24
|
+
make: () => ({ getUser: () => undefined })
|
|
25
|
+
}
|
|
26
|
+
return renderToString(layout.render({ container, children: null } as any))
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
it('should create an App Layout instance', () => {
|
|
30
|
+
// Assert
|
|
31
|
+
expect(layout).toBeInstanceOf(AppLayout)
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
it('should render the real Stone.js logo', () => {
|
|
35
|
+
// Act
|
|
36
|
+
const html = render()
|
|
37
|
+
|
|
38
|
+
// Assert
|
|
39
|
+
expect(html).toContain('/logo.svg')
|
|
40
|
+
expect(html).toContain('alt="Stone.js"')
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
it('should render the unauthenticated header', () => {
|
|
44
|
+
// Act
|
|
45
|
+
const html = render()
|
|
46
|
+
|
|
47
|
+
// Assert
|
|
48
|
+
expect(html).toContain('Login')
|
|
49
|
+
expect(html).toContain('Stone.js')
|
|
50
|
+
})
|
|
51
|
+
})
|
|
@@ -27,7 +27,7 @@ export const LayoutHeader: FC<LayoutHeaderOptions> = ({ container }) => {
|
|
|
27
27
|
<nav className='app-navbar'>
|
|
28
28
|
<div className='header-left'>
|
|
29
29
|
<StoneLink to='/' className='logo'>
|
|
30
|
-
<img src='/logo.
|
|
30
|
+
<img src='/logo.svg' alt='Stone.js' width={96} height={96} />
|
|
31
31
|
<span>Stone.js</span>
|
|
32
32
|
</StoneLink>
|
|
33
33
|
</div>
|