vyriy 0.4.1 → 0.4.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/README.md +19 -16
- package/commands/create/plan/plan.d.ts +1 -1
- package/commands/create/preset/api.js +1 -1
- package/commands/create/preset/base.js +1 -1
- package/commands/create/preset/index.d.ts +15 -0
- package/commands/create/preset/index.js +18 -0
- package/commands/create/preset/library.js +1 -1
- package/commands/create/preset/spa.js +242 -8
- package/commands/create/preset/ssg.js +308 -8
- package/commands/create/preset/ssr.js +317 -8
- package/package.json +6 -2
package/README.md
CHANGED
|
@@ -78,26 +78,26 @@ Prepares every package inside the `dist/` directory for npm publishing:
|
|
|
78
78
|
|
|
79
79
|
Registered presets:
|
|
80
80
|
|
|
81
|
-
| Key | Description
|
|
82
|
-
| --------- |
|
|
83
|
-
| `base` | Minimal monorepo with config only
|
|
84
|
-
| `library` | Workspaces layout with a sample React package
|
|
85
|
-
| `api` | Backend API workspace with server/build setup
|
|
81
|
+
| Key | Description |
|
|
82
|
+
| --------- | ---------------------------------------------- |
|
|
83
|
+
| `base` | Minimal monorepo with config only |
|
|
84
|
+
| `library` | Workspaces layout with a sample React package |
|
|
85
|
+
| `api` | Backend API workspace with server/build setup |
|
|
86
|
+
| `ssr` | Server-rendered React API with CMS placeholder |
|
|
87
|
+
| `ssg` | Static site generation workspace |
|
|
88
|
+
| `spa` | Single-page application |
|
|
86
89
|
|
|
87
90
|
Presets in progress:
|
|
88
91
|
|
|
89
|
-
| Key | Direction
|
|
90
|
-
| ------ |
|
|
91
|
-
| `rest` | REST API project
|
|
92
|
-
| `gql` | GraphQL API project
|
|
93
|
-
| `
|
|
94
|
-
| `ssg` | Static site generation project |
|
|
95
|
-
| `spa` | Single-page application |
|
|
96
|
-
| `mfe` | Micro-frontend project |
|
|
92
|
+
| Key | Direction |
|
|
93
|
+
| ------ | ---------------------- |
|
|
94
|
+
| `rest` | REST API project |
|
|
95
|
+
| `gql` | GraphQL API project |
|
|
96
|
+
| `mfe` | Micro-frontend project |
|
|
97
97
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
98
|
+
Registered presets are selectable by the wizard. In-progress presets exist as
|
|
99
|
+
source modules and are expected to become selectable as their generated project
|
|
100
|
+
shape is finalized.
|
|
101
101
|
|
|
102
102
|
## Providers
|
|
103
103
|
|
|
@@ -108,6 +108,9 @@ Provider selections add files to the generated project.
|
|
|
108
108
|
| `base` | `gitlab`, `github` | none |
|
|
109
109
|
| `library` | `gitlab`, `github` | none |
|
|
110
110
|
| `api` | `gitlab`, `github` | none |
|
|
111
|
+
| `ssr` | `gitlab`, `github` | none |
|
|
112
|
+
| `ssg` | `gitlab`, `github` | none |
|
|
113
|
+
| `spa` | `gitlab`, `github` | none |
|
|
111
114
|
|
|
112
115
|
## API
|
|
113
116
|
|
|
@@ -2,7 +2,7 @@ export declare const plan: (dirName: string, appPath: string) => Promise<{
|
|
|
2
2
|
name: string;
|
|
3
3
|
description: string;
|
|
4
4
|
target: string;
|
|
5
|
-
preset: "base" | "api" | "library";
|
|
5
|
+
preset: "ssr" | "base" | "api" | "library" | "ssg" | "spa";
|
|
6
6
|
scope: string | undefined;
|
|
7
7
|
ci: import("../preset/types.js").CiProvider | undefined;
|
|
8
8
|
deploy: import("../preset/types.js").DeployProvider | undefined;
|
|
@@ -36,7 +36,7 @@ export const api = {
|
|
|
36
36
|
'test:jest': 'jest',
|
|
37
37
|
postinstall: 'husky',
|
|
38
38
|
},
|
|
39
|
-
|
|
39
|
+
dependencies: {
|
|
40
40
|
'@vyriy/typescript-config': `^${packageJson.version}`,
|
|
41
41
|
typescript: packageJson.peerDependencies.typescript,
|
|
42
42
|
'@vyriy/prettier-config': `^${packageJson.version}`,
|
|
@@ -154,7 +154,7 @@ export default {
|
|
|
154
154
|
],
|
|
155
155
|
}, null, 2) + '\n',
|
|
156
156
|
'prettier.config.ts': "export { default } from '@vyriy/prettier-config';\n",
|
|
157
|
-
'.prettierignore': 'node_modules\ndist\ncoverage\nstorybook-static\n',
|
|
157
|
+
'.prettierignore': 'node_modules\ndist\ncoverage\nstorybook-static\nconsumer\n',
|
|
158
158
|
'eslint.config.ts': "export { default } from '@vyriy/eslint-config';\n",
|
|
159
159
|
'jest.config.ts': "export { default } from '@vyriy/jest-config';\n",
|
|
160
160
|
}),
|
|
@@ -14,4 +14,19 @@ export declare const presets: {
|
|
|
14
14
|
description: string;
|
|
15
15
|
preset: import("./types.js").Preset;
|
|
16
16
|
};
|
|
17
|
+
ssr: {
|
|
18
|
+
name: string;
|
|
19
|
+
description: string;
|
|
20
|
+
preset: import("./types.js").Preset;
|
|
21
|
+
};
|
|
22
|
+
ssg: {
|
|
23
|
+
name: string;
|
|
24
|
+
description: string;
|
|
25
|
+
preset: import("./types.js").Preset;
|
|
26
|
+
};
|
|
27
|
+
spa: {
|
|
28
|
+
name: string;
|
|
29
|
+
description: string;
|
|
30
|
+
preset: import("./types.js").Preset;
|
|
31
|
+
};
|
|
17
32
|
};
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { base } from './base.js';
|
|
2
2
|
import { library } from './library.js';
|
|
3
3
|
import { api } from './api.js';
|
|
4
|
+
import { ssr } from './ssr.js';
|
|
5
|
+
import { ssg } from './ssg.js';
|
|
6
|
+
import { spa } from './spa.js';
|
|
4
7
|
export const presets = {
|
|
5
8
|
base: {
|
|
6
9
|
name: 'Base',
|
|
@@ -17,4 +20,19 @@ export const presets = {
|
|
|
17
20
|
description: 'Preset to generate simple API',
|
|
18
21
|
preset: api,
|
|
19
22
|
},
|
|
23
|
+
ssr: {
|
|
24
|
+
name: 'SSR',
|
|
25
|
+
description: 'Preset to generate simple Server Side Rendering (SSR) API',
|
|
26
|
+
preset: ssr,
|
|
27
|
+
},
|
|
28
|
+
ssg: {
|
|
29
|
+
name: 'SSG',
|
|
30
|
+
description: 'Preset for Static Site Generation (SSG)',
|
|
31
|
+
preset: ssg,
|
|
32
|
+
},
|
|
33
|
+
spa: {
|
|
34
|
+
name: 'SPA',
|
|
35
|
+
description: 'Preset for Single Page Application (SPA)',
|
|
36
|
+
preset: spa,
|
|
37
|
+
},
|
|
20
38
|
};
|
|
@@ -44,7 +44,7 @@ export const library = {
|
|
|
44
44
|
'test:jest': 'jest',
|
|
45
45
|
postinstall: 'husky',
|
|
46
46
|
},
|
|
47
|
-
|
|
47
|
+
dependencies: {
|
|
48
48
|
'@vyriy/typescript-config': `^${packageJson.version}`,
|
|
49
49
|
typescript: packageJson.peerDependencies.typescript,
|
|
50
50
|
'@vyriy/prettier-config': `^${packageJson.version}`,
|
|
@@ -1,16 +1,250 @@
|
|
|
1
|
+
import packageJson from '../../../package.json' with { type: 'json' };
|
|
1
2
|
import { base } from './base.js';
|
|
2
3
|
export const spa = {
|
|
3
4
|
files: (options) => ({
|
|
4
5
|
...base.files(options),
|
|
6
|
+
'package.json': JSON.stringify({
|
|
7
|
+
name: options.name,
|
|
8
|
+
version: '0.0.0',
|
|
9
|
+
description: options.description,
|
|
10
|
+
private: true,
|
|
11
|
+
type: 'module',
|
|
12
|
+
agents: './AGENTS.md',
|
|
13
|
+
packageManager: packageJson.packageManager,
|
|
14
|
+
engines: {
|
|
15
|
+
node: packageJson.engines.node,
|
|
16
|
+
},
|
|
17
|
+
workspaces: [
|
|
18
|
+
'packages/*',
|
|
19
|
+
'workspaces/*',
|
|
20
|
+
],
|
|
21
|
+
scripts: {
|
|
22
|
+
storybook: 'cross-env STORYBOOK_DISABLE_TELEMETRY=1 storybook dev -p 6006 --disable-telemetry',
|
|
23
|
+
check: 'run-s lint build test',
|
|
24
|
+
fix: "run-s 'fix:*'",
|
|
25
|
+
start: "run-p 'start:*'",
|
|
26
|
+
lint: "run-s 'lint:*'",
|
|
27
|
+
build: "run-s 'build:*'",
|
|
28
|
+
test: "run-s 'test:*'",
|
|
29
|
+
'fix:prettier': 'prettier . --write',
|
|
30
|
+
'fix:eslint': 'eslint . --fix',
|
|
31
|
+
'fix:stylelint': 'stylelint "**/*.{css,scss}" --fix',
|
|
32
|
+
'start:spa': 'sh workspaces/spa/bin/start.sh',
|
|
33
|
+
'lint:ts': 'tsc',
|
|
34
|
+
'lint:prettier': 'prettier . --check',
|
|
35
|
+
'lint:eslint': 'eslint .',
|
|
36
|
+
'lint:stylelint': 'stylelint "**/*.{css,scss}"',
|
|
37
|
+
'build:spa': 'rimraf dist && sh workspaces/spa/bin/build.sh',
|
|
38
|
+
'build:storybook': 'cross-env STORYBOOK_DISABLE_TELEMETRY=1 storybook build --quiet --disable-telemetry',
|
|
39
|
+
'test:jest': 'jest',
|
|
40
|
+
postinstall: 'husky',
|
|
41
|
+
},
|
|
42
|
+
dependencies: {
|
|
43
|
+
'@vyriy/typescript-config': `^${packageJson.version}`,
|
|
44
|
+
typescript: packageJson.peerDependencies.typescript,
|
|
45
|
+
'@vyriy/prettier-config': `^${packageJson.version}`,
|
|
46
|
+
prettier: packageJson.peerDependencies.prettier,
|
|
47
|
+
'@vyriy/eslint-config': `^${packageJson.version}`,
|
|
48
|
+
eslint: packageJson.peerDependencies.eslint,
|
|
49
|
+
'@vyriy/jest-config': `^${packageJson.version}`,
|
|
50
|
+
jest: packageJson.peerDependencies.jest,
|
|
51
|
+
'@vyriy/storybook-config': `^${packageJson.version}`,
|
|
52
|
+
storybook: packageJson.peerDependencies.storybook,
|
|
53
|
+
'@vyriy/path': `^${packageJson.version}`,
|
|
54
|
+
vyriy: `^${packageJson.version}`,
|
|
55
|
+
husky: packageJson.peerDependencies.husky,
|
|
56
|
+
'npm-run-all2': packageJson.peerDependencies['npm-run-all2'],
|
|
57
|
+
'cross-env': packageJson.peerDependencies['cross-env'],
|
|
58
|
+
rimraf: packageJson.peerDependencies.rimraf,
|
|
59
|
+
'@vyriy/webpack-config': `^${packageJson.version}`,
|
|
60
|
+
tsx: packageJson.peerDependencies.tsx,
|
|
61
|
+
webpack: packageJson.peerDependencies.webpack,
|
|
62
|
+
'webpack-cli': packageJson.peerDependencies['webpack-cli'],
|
|
63
|
+
react: packageJson.peerDependencies.react,
|
|
64
|
+
'react-dom': packageJson.peerDependencies['react-dom'],
|
|
65
|
+
'@types/react': packageJson.peerDependencies['@types/react'],
|
|
66
|
+
'@types/react-dom': packageJson.peerDependencies['@types/react-dom'],
|
|
67
|
+
'@vyriy/stylelint-config': `^${packageJson.version}`,
|
|
68
|
+
'@vyriy/cn': `^${packageJson.version}`,
|
|
69
|
+
'@vyriy/html': `^${packageJson.version}`,
|
|
70
|
+
stylelint: packageJson.peerDependencies.stylelint,
|
|
71
|
+
'@vyriy/browserslist-config': `^${packageJson.version}`,
|
|
72
|
+
},
|
|
73
|
+
}, null, 2) + '\n',
|
|
74
|
+
'stylelint.config.ts': "export { default } from '@vyriy/stylelint-config';\n",
|
|
75
|
+
'assets.d.ts': "declare module '*.scss';\n",
|
|
76
|
+
'.browserslistrc': `[development]
|
|
77
|
+
extends @vyriy/browserslist-config
|
|
78
|
+
|
|
79
|
+
[ssr]
|
|
80
|
+
extends @vyriy/browserslist-config
|
|
81
|
+
|
|
82
|
+
[production]
|
|
83
|
+
extends @vyriy/browserslist-config
|
|
84
|
+
|
|
85
|
+
[modern]
|
|
86
|
+
extends @vyriy/browserslist-config
|
|
87
|
+
`,
|
|
88
|
+
'packages/components/package.json': JSON.stringify({
|
|
89
|
+
name: '@p/components',
|
|
90
|
+
private: true,
|
|
91
|
+
type: 'module',
|
|
92
|
+
}, null, 2) + '\n',
|
|
93
|
+
'packages/components/index.ts': "export * from './page/index.js';\n",
|
|
94
|
+
'packages/components/index.test.tsx': `import { describe, expect, it } from '@jest/globals';
|
|
95
|
+
|
|
96
|
+
import { Page } from './index.js';
|
|
97
|
+
import { Page as PageImplementation } from './page/index.js';
|
|
98
|
+
|
|
99
|
+
describe('packages/components/page', () => {
|
|
100
|
+
it('re-exports the page component', () => {
|
|
101
|
+
expect(Page).toBe(PageImplementation);
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
`,
|
|
105
|
+
'packages/components/page/index.ts': `export * from './page.js';
|
|
106
|
+
export type * from './types.js';
|
|
107
|
+
`,
|
|
108
|
+
'packages/components/page/index.test.ts': `import { describe, expect, it } from '@jest/globals';
|
|
109
|
+
|
|
110
|
+
import { Page } from './index.js';
|
|
111
|
+
import { Page as PageImplementation } from './page.js';
|
|
112
|
+
|
|
113
|
+
describe('packages/components/page', () => {
|
|
114
|
+
it('re-exports the page component', () => {
|
|
115
|
+
expect(Page).toBe(PageImplementation);
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
`,
|
|
119
|
+
'packages/components/page/types.ts': `import { FC } from 'react';
|
|
120
|
+
|
|
121
|
+
export type PageProps = {
|
|
122
|
+
content: string;
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
export type PageType = FC<PageProps>;
|
|
126
|
+
`,
|
|
127
|
+
'packages/components/page/page.tsx': `import type { PageType } from './types.js';
|
|
128
|
+
|
|
129
|
+
export const Page: PageType = ({ content }) => <div className="content">{content}</div>;
|
|
130
|
+
`,
|
|
131
|
+
'packages/components/page/styles.scss': `.content {
|
|
132
|
+
display: block;
|
|
133
|
+
}
|
|
134
|
+
`,
|
|
135
|
+
'packages/components/page/page.test.tsx': `import { renderToStaticMarkup } from 'react-dom/server';
|
|
136
|
+
import { describe, expect, it } from '@jest/globals';
|
|
137
|
+
|
|
138
|
+
import { Page } from './page.js';
|
|
139
|
+
|
|
140
|
+
describe('packages/components/page/page', () => {
|
|
141
|
+
it('renders content inside the page content container', () => {
|
|
142
|
+
expect(renderToStaticMarkup(<Page content="Page body" />)).toBe('<div class="content">Page body</div>');
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
`,
|
|
146
|
+
'workspaces/spa/bin/build.sh': `#!/usr/bin/env sh
|
|
147
|
+
|
|
148
|
+
set -e
|
|
149
|
+
|
|
150
|
+
scriptdir="$PWD/workspaces/spa";
|
|
151
|
+
|
|
152
|
+
NODE_ENV=production npx webpack --config $scriptdir/webpack.config.ts
|
|
153
|
+
`,
|
|
154
|
+
'workspaces/spa/bin/start.sh': `#!/usr/bin/env sh
|
|
155
|
+
|
|
156
|
+
set -e
|
|
157
|
+
|
|
158
|
+
scriptdir="$PWD/workspaces/spa";
|
|
159
|
+
|
|
160
|
+
npx webpack serve --open --config $scriptdir/webpack.config.ts
|
|
161
|
+
`,
|
|
162
|
+
'workspaces/spa/doc.mdx': `import { Meta, Markdown } from '@storybook/addon-docs/blocks';
|
|
163
|
+
import ReadMe from './README.md?raw';
|
|
164
|
+
|
|
165
|
+
<Meta title="Workspaces/SPA" />
|
|
166
|
+
|
|
167
|
+
<Markdown>{ReadMe}</Markdown>
|
|
168
|
+
`,
|
|
169
|
+
'workspaces/spa/README.md': `# ${options.name} SPA\n\n${options.description}\n`,
|
|
170
|
+
'workspaces/spa/webpack.config.ts': `import { csr, html } from '@vyriy/webpack-config';
|
|
171
|
+
import { path } from '@vyriy/path';
|
|
172
|
+
|
|
173
|
+
export default csr(
|
|
174
|
+
'@w/spa',
|
|
175
|
+
{
|
|
176
|
+
path: path('dist', 'spa'),
|
|
177
|
+
filename: 'index.js',
|
|
178
|
+
},
|
|
179
|
+
(config) => {
|
|
180
|
+
return {
|
|
181
|
+
...config,
|
|
182
|
+
plugins: [
|
|
183
|
+
...(config.plugins ?? []),
|
|
184
|
+
html({
|
|
185
|
+
title: '<title>SPA</title>',
|
|
186
|
+
body: '<div id="root"></div>',
|
|
187
|
+
}),
|
|
188
|
+
],
|
|
189
|
+
};
|
|
190
|
+
},
|
|
191
|
+
);
|
|
192
|
+
`,
|
|
193
|
+
'workspaces/spa/package.json': JSON.stringify({
|
|
194
|
+
name: '@w/spa',
|
|
195
|
+
type: 'module',
|
|
196
|
+
private: true,
|
|
197
|
+
}, null, 2) + '\n',
|
|
198
|
+
'workspaces/spa/index.tsx': `import { createRoot } from 'react-dom/client';
|
|
199
|
+
|
|
200
|
+
import { Page } from '@p/components';
|
|
201
|
+
|
|
202
|
+
import '@p/components/page/styles.scss';
|
|
203
|
+
|
|
204
|
+
createRoot(document.getElementById('root')!).render(<Page content="Test content" />);
|
|
205
|
+
`,
|
|
206
|
+
'workspaces/spa/index.test.tsx': `import type { ReactElement, ReactNode } from 'react';
|
|
207
|
+
import { describe, expect, it, jest } from '@jest/globals';
|
|
208
|
+
|
|
209
|
+
const renderMock = jest.fn<(children: ReactNode) => void>();
|
|
210
|
+
const createRootMock = jest.fn<(container: Element | DocumentFragment) => { render: typeof renderMock }>(() => ({
|
|
211
|
+
render: renderMock,
|
|
212
|
+
}));
|
|
213
|
+
const PageMock = jest.fn(({ content }: { content: string }) => <div>{content}</div>);
|
|
214
|
+
|
|
215
|
+
jest.mock('react-dom/client', () => ({
|
|
216
|
+
createRoot: createRootMock,
|
|
217
|
+
}));
|
|
218
|
+
|
|
219
|
+
jest.mock('@p/components', () => ({
|
|
220
|
+
Page: PageMock,
|
|
221
|
+
}));
|
|
222
|
+
|
|
223
|
+
describe('workspaces/spa/index.tsx', () => {
|
|
224
|
+
it('mounts the page component into the root element', async () => {
|
|
225
|
+
document.body.innerHTML = '<div id="root"></div>';
|
|
226
|
+
const rootElement = document.getElementById('root');
|
|
227
|
+
|
|
228
|
+
if (!rootElement) {
|
|
229
|
+
throw new Error('Expected root element to exist.');
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
await import('./index.js');
|
|
233
|
+
|
|
234
|
+
expect(createRootMock).toHaveBeenCalledTimes(1);
|
|
235
|
+
expect(createRootMock.mock.calls[0]?.[0]).toBe(rootElement);
|
|
236
|
+
expect(renderMock).toHaveBeenCalledTimes(1);
|
|
237
|
+
|
|
238
|
+
const renderedElement = renderMock.mock.calls[0]?.[0] as ReactElement<{ content: string }>;
|
|
239
|
+
|
|
240
|
+
expect(renderedElement.type).toBe(PageMock);
|
|
241
|
+
expect(renderedElement.props.content).toBe('Test content');
|
|
242
|
+
});
|
|
243
|
+
});
|
|
244
|
+
`,
|
|
5
245
|
}),
|
|
6
246
|
ci: {
|
|
7
|
-
|
|
8
|
-
'.gitlab-ci.yml': 'code',
|
|
9
|
-
},
|
|
10
|
-
},
|
|
11
|
-
deploy: {
|
|
12
|
-
docker: {
|
|
13
|
-
Dockerfile: 'code',
|
|
14
|
-
},
|
|
247
|
+
...base.ci,
|
|
15
248
|
},
|
|
249
|
+
deploy: {},
|
|
16
250
|
};
|
|
@@ -1,16 +1,316 @@
|
|
|
1
|
+
import packageJson from '../../../package.json' with { type: 'json' };
|
|
1
2
|
import { base } from './base.js';
|
|
2
3
|
export const ssg = {
|
|
3
4
|
files: (options) => ({
|
|
4
5
|
...base.files(options),
|
|
6
|
+
'package.json': JSON.stringify({
|
|
7
|
+
name: options.name,
|
|
8
|
+
version: '0.0.0',
|
|
9
|
+
description: options.description,
|
|
10
|
+
private: true,
|
|
11
|
+
type: 'module',
|
|
12
|
+
agents: './AGENTS.md',
|
|
13
|
+
packageManager: packageJson.packageManager,
|
|
14
|
+
engines: {
|
|
15
|
+
node: packageJson.engines.node,
|
|
16
|
+
},
|
|
17
|
+
workspaces: [
|
|
18
|
+
'packages/*',
|
|
19
|
+
'workspaces/*',
|
|
20
|
+
],
|
|
21
|
+
scripts: {
|
|
22
|
+
storybook: 'cross-env STORYBOOK_DISABLE_TELEMETRY=1 storybook dev -p 6006 --disable-telemetry',
|
|
23
|
+
check: 'run-s lint build test',
|
|
24
|
+
fix: "run-s 'fix:*'",
|
|
25
|
+
start: "run-p 'start:*'",
|
|
26
|
+
lint: "run-s 'lint:*'",
|
|
27
|
+
build: "run-s 'build:*'",
|
|
28
|
+
test: "run-s 'test:*'",
|
|
29
|
+
'fix:prettier': 'prettier . --write',
|
|
30
|
+
'fix:eslint': 'eslint . --fix',
|
|
31
|
+
'fix:stylelint': 'stylelint "**/*.{css,scss}" --fix',
|
|
32
|
+
'start:ssg': 'sh workspaces/ssg/bin/start.sh',
|
|
33
|
+
'lint:ts': 'tsc',
|
|
34
|
+
'lint:prettier': 'prettier . --check',
|
|
35
|
+
'lint:eslint': 'eslint .',
|
|
36
|
+
'lint:stylelint': 'stylelint "**/*.{css,scss}"',
|
|
37
|
+
'build:ssg': 'rimraf dist && sh workspaces/ssg/bin/build.sh',
|
|
38
|
+
'build:storybook': 'cross-env STORYBOOK_DISABLE_TELEMETRY=1 storybook build --quiet --disable-telemetry',
|
|
39
|
+
'test:jest': 'jest',
|
|
40
|
+
postinstall: 'husky',
|
|
41
|
+
},
|
|
42
|
+
dependencies: {
|
|
43
|
+
'@vyriy/typescript-config': `^${packageJson.version}`,
|
|
44
|
+
typescript: packageJson.peerDependencies.typescript,
|
|
45
|
+
'@vyriy/prettier-config': `^${packageJson.version}`,
|
|
46
|
+
prettier: packageJson.peerDependencies.prettier,
|
|
47
|
+
'@vyriy/eslint-config': `^${packageJson.version}`,
|
|
48
|
+
eslint: packageJson.peerDependencies.eslint,
|
|
49
|
+
'@vyriy/jest-config': `^${packageJson.version}`,
|
|
50
|
+
jest: packageJson.peerDependencies.jest,
|
|
51
|
+
'@vyriy/storybook-config': `^${packageJson.version}`,
|
|
52
|
+
storybook: packageJson.peerDependencies.storybook,
|
|
53
|
+
'@vyriy/path': `^${packageJson.version}`,
|
|
54
|
+
vyriy: `^${packageJson.version}`,
|
|
55
|
+
husky: packageJson.peerDependencies.husky,
|
|
56
|
+
'npm-run-all2': packageJson.peerDependencies['npm-run-all2'],
|
|
57
|
+
'cross-env': packageJson.peerDependencies['cross-env'],
|
|
58
|
+
rimraf: packageJson.peerDependencies.rimraf,
|
|
59
|
+
'@vyriy/webpack-config': `^${packageJson.version}`,
|
|
60
|
+
'@vyriy/script': `^${packageJson.version}`,
|
|
61
|
+
tsx: packageJson.peerDependencies.tsx,
|
|
62
|
+
webpack: packageJson.peerDependencies.webpack,
|
|
63
|
+
'webpack-cli': packageJson.peerDependencies['webpack-cli'],
|
|
64
|
+
react: packageJson.peerDependencies.react,
|
|
65
|
+
'react-dom': packageJson.peerDependencies['react-dom'],
|
|
66
|
+
'@types/react': packageJson.peerDependencies['@types/react'],
|
|
67
|
+
'@types/react-dom': packageJson.peerDependencies['@types/react-dom'],
|
|
68
|
+
'@vyriy/stylelint-config': `^${packageJson.version}`,
|
|
69
|
+
'@vyriy/cn': `^${packageJson.version}`,
|
|
70
|
+
'@vyriy/html': `^${packageJson.version}`,
|
|
71
|
+
stylelint: packageJson.peerDependencies.stylelint,
|
|
72
|
+
sass: packageJson.peerDependencies.sass,
|
|
73
|
+
},
|
|
74
|
+
}, null, 2) + '\n',
|
|
75
|
+
'stylelint.config.ts': "export { default } from '@vyriy/stylelint-config';\n",
|
|
76
|
+
'assets.d.ts': "declare module '*.scss';\n",
|
|
77
|
+
'packages/components/package.json': JSON.stringify({
|
|
78
|
+
name: '@p/components',
|
|
79
|
+
private: true,
|
|
80
|
+
type: 'module',
|
|
81
|
+
}, null, 2) + '\n',
|
|
82
|
+
'packages/components/index.ts': "export * from './page/index.js';\n",
|
|
83
|
+
'packages/components/index.test.tsx': `import { describe, expect, it } from '@jest/globals';
|
|
84
|
+
|
|
85
|
+
import { Page } from './index.js';
|
|
86
|
+
import { Page as PageImplementation } from './page/index.js';
|
|
87
|
+
|
|
88
|
+
describe('packages/components/page', () => {
|
|
89
|
+
it('re-exports the page component', () => {
|
|
90
|
+
expect(Page).toBe(PageImplementation);
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
`,
|
|
94
|
+
'packages/components/page/index.ts': `export * from './page.js';
|
|
95
|
+
export type * from './types.js';
|
|
96
|
+
`,
|
|
97
|
+
'packages/components/page/index.test.ts': `import { describe, expect, it } from '@jest/globals';
|
|
98
|
+
|
|
99
|
+
import { Page } from './index.js';
|
|
100
|
+
import { Page as PageImplementation } from './page.js';
|
|
101
|
+
|
|
102
|
+
describe('packages/components/page', () => {
|
|
103
|
+
it('re-exports the page component', () => {
|
|
104
|
+
expect(Page).toBe(PageImplementation);
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
`,
|
|
108
|
+
'packages/components/page/types.ts': `import { FC } from 'react';
|
|
109
|
+
|
|
110
|
+
export type PageProps = {
|
|
111
|
+
content: string;
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
export type PageType = FC<PageProps>;
|
|
115
|
+
`,
|
|
116
|
+
'packages/components/page/page.tsx': `import type { PageType } from './types.js';
|
|
117
|
+
|
|
118
|
+
export const Page: PageType = ({ content }) => <div className="content">{content}</div>;
|
|
119
|
+
`,
|
|
120
|
+
'packages/components/page/styles.scss': `.content {
|
|
121
|
+
display: block;
|
|
122
|
+
}
|
|
123
|
+
`,
|
|
124
|
+
'packages/components/page/page.test.tsx': `import { renderToStaticMarkup } from 'react-dom/server';
|
|
125
|
+
import { describe, expect, it } from '@jest/globals';
|
|
126
|
+
|
|
127
|
+
import { Page } from './page.js';
|
|
128
|
+
|
|
129
|
+
describe('packages/components/page/page', () => {
|
|
130
|
+
it('renders content inside the page content container', () => {
|
|
131
|
+
expect(renderToStaticMarkup(<Page content="Page body" />)).toBe('<div class="content">Page body</div>');
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
`,
|
|
135
|
+
'packages/services/package.json': JSON.stringify({
|
|
136
|
+
name: '@p/services',
|
|
137
|
+
private: true,
|
|
138
|
+
type: 'module',
|
|
139
|
+
}, null, 2) + '\n',
|
|
140
|
+
'packages/services/cms/index.ts': `export const cms = {
|
|
141
|
+
getContent: async () => {
|
|
142
|
+
// Placeholder for fetching content from a CMS
|
|
143
|
+
return Promise.resolve({
|
|
144
|
+
title: 'Sample Content',
|
|
145
|
+
body: 'This is a sample content fetched from the CMS.',
|
|
146
|
+
});
|
|
147
|
+
},
|
|
148
|
+
};
|
|
149
|
+
`,
|
|
150
|
+
'packages/services/cms/index.test.ts': `import { describe, expect, it } from '@jest/globals';
|
|
151
|
+
|
|
152
|
+
import { cms } from './index.js';
|
|
153
|
+
|
|
154
|
+
describe('packages/services/cms', () => {
|
|
155
|
+
it('returns content for rendering a page', async () => {
|
|
156
|
+
await expect(cms.getContent()).resolves.toEqual({
|
|
157
|
+
title: 'Sample Content',
|
|
158
|
+
body: 'This is a sample content fetched from the CMS.',
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
`,
|
|
163
|
+
'workspaces/ssg/bin/build.sh': `#!/usr/bin/env sh
|
|
164
|
+
|
|
165
|
+
set -e
|
|
166
|
+
|
|
167
|
+
scriptdir="$PWD/workspaces/ssg";
|
|
168
|
+
distdir="$PWD/dist/ssg";
|
|
169
|
+
|
|
170
|
+
NODE_ENV=production npx webpack --config $scriptdir/webpack.config.ts
|
|
171
|
+
|
|
172
|
+
yarn exec sass packages/components/page/styles.scss "$distdir/styles.css" --no-source-map --style=compressed
|
|
173
|
+
cp $scriptdir/package.json "$distdir/package.json"
|
|
174
|
+
npm pkg delete "type" --prefix "$distdir"
|
|
175
|
+
npm pkg delete "private" --prefix "$distdir"
|
|
176
|
+
`,
|
|
177
|
+
'workspaces/ssg/bin/start.sh': `#!/usr/bin/env sh
|
|
178
|
+
|
|
179
|
+
set -e
|
|
180
|
+
|
|
181
|
+
scriptdir="$PWD/workspaces/ssg";
|
|
182
|
+
distdir="$PWD/dist/ssg";
|
|
183
|
+
|
|
184
|
+
mkdir -p "$distdir"
|
|
185
|
+
yarn exec sass packages/components/page/styles.scss "$distdir/styles.css" --no-source-map
|
|
186
|
+
|
|
187
|
+
PROJECT_CWD="$distdir" NODE_ENV=production LOG_LEVEL=info "$PWD/node_modules/.bin/tsx" $scriptdir/index.tsx
|
|
188
|
+
`,
|
|
189
|
+
'workspaces/ssg/doc.mdx': `import { Meta, Markdown } from '@storybook/addon-docs/blocks';
|
|
190
|
+
import ReadMe from './README.md?raw';
|
|
191
|
+
|
|
192
|
+
<Meta title="Workspaces/SSG" />
|
|
193
|
+
|
|
194
|
+
<Markdown>{ReadMe}</Markdown>
|
|
195
|
+
`,
|
|
196
|
+
'workspaces/ssg/README.md': `# ${options.name} SSG\n\n${options.description}\n`,
|
|
197
|
+
'workspaces/ssg/webpack.config.ts': `import { path } from '@vyriy/path';
|
|
198
|
+
import { ssr, external } from '@vyriy/webpack-config';
|
|
199
|
+
|
|
200
|
+
export default ssr(
|
|
201
|
+
'@w/ssg',
|
|
202
|
+
{
|
|
203
|
+
path: path('dist', 'ssg'),
|
|
204
|
+
filename: 'index.js',
|
|
205
|
+
library: { type: 'commonjs2' },
|
|
206
|
+
},
|
|
207
|
+
(config) => ({
|
|
208
|
+
...config,
|
|
209
|
+
externals: [external({ allowlist: [/^@p/, /^@w/, /^@vyriy/] })],
|
|
210
|
+
}),
|
|
211
|
+
);
|
|
212
|
+
`,
|
|
213
|
+
'workspaces/ssg/package.json': JSON.stringify({
|
|
214
|
+
name: '@w/ssg',
|
|
215
|
+
type: 'module',
|
|
216
|
+
private: true,
|
|
217
|
+
}, null, 2) + '\n',
|
|
218
|
+
'workspaces/ssg/index.tsx': `import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
219
|
+
import { renderToString } from 'react-dom/server';
|
|
220
|
+
|
|
221
|
+
import { script } from '@vyriy/script';
|
|
222
|
+
import { html, minify } from '@vyriy/html';
|
|
223
|
+
import { path } from '@vyriy/path';
|
|
224
|
+
|
|
225
|
+
import { cms } from '@p/services/cms';
|
|
226
|
+
import { Page } from '@p/components';
|
|
227
|
+
|
|
228
|
+
const dashboardStyles = readFileSync(path('styles.css'), 'utf8');
|
|
229
|
+
const staticPath = path('static');
|
|
230
|
+
|
|
231
|
+
void script(async () => {
|
|
232
|
+
const content = await cms.getContent();
|
|
233
|
+
|
|
234
|
+
mkdirSync(staticPath, { recursive: true });
|
|
235
|
+
|
|
236
|
+
writeFileSync(
|
|
237
|
+
path(staticPath, 'index.html'),
|
|
238
|
+
minify(
|
|
239
|
+
html({
|
|
240
|
+
htmlAttributes: 'lang="en"',
|
|
241
|
+
title: \`<title>\${content.title}</title>\`,
|
|
242
|
+
meta: '<meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" />',
|
|
243
|
+
style: \`<style>\${dashboardStyles.trim()}</style>\`,
|
|
244
|
+
body: renderToString(<Page content={content.body} />),
|
|
245
|
+
}),
|
|
246
|
+
),
|
|
247
|
+
);
|
|
248
|
+
});
|
|
249
|
+
`,
|
|
250
|
+
'workspaces/ssg/index.test.tsx': `import { describe, expect, it, jest } from '@jest/globals';
|
|
251
|
+
|
|
252
|
+
const getContentMock = jest.fn(() =>
|
|
253
|
+
Promise.resolve({
|
|
254
|
+
title: 'Sample Content',
|
|
255
|
+
body: 'This is a sample content fetched from the CMS.',
|
|
256
|
+
}),
|
|
257
|
+
);
|
|
258
|
+
let scriptPromise: Promise<void> | undefined;
|
|
259
|
+
const scriptMock = jest.fn((handler: () => Promise<void>) => {
|
|
260
|
+
scriptPromise = handler();
|
|
261
|
+
|
|
262
|
+
return scriptPromise;
|
|
263
|
+
});
|
|
264
|
+
const nodeFs = jest.requireActual<typeof import('node:fs')>('node:fs');
|
|
265
|
+
const mkdirSyncMock = jest.fn();
|
|
266
|
+
const readFileSyncMock = jest.fn<(path: string | URL, encoding: 'utf8') => string>(
|
|
267
|
+
() => '.content { display: block; }',
|
|
268
|
+
);
|
|
269
|
+
const writeFileSyncMock = jest.fn();
|
|
270
|
+
|
|
271
|
+
jest.mock('node:fs', () => ({
|
|
272
|
+
...nodeFs,
|
|
273
|
+
mkdirSync: mkdirSyncMock,
|
|
274
|
+
readFileSync: readFileSyncMock,
|
|
275
|
+
writeFileSync: writeFileSyncMock,
|
|
276
|
+
}));
|
|
277
|
+
|
|
278
|
+
jest.mock('@vyriy/script', () => ({
|
|
279
|
+
script: scriptMock,
|
|
280
|
+
}));
|
|
281
|
+
|
|
282
|
+
jest.mock('@p/services/cms', () => ({
|
|
283
|
+
cms: {
|
|
284
|
+
getContent: getContentMock,
|
|
285
|
+
},
|
|
286
|
+
}));
|
|
287
|
+
|
|
288
|
+
describe('workspaces/ssg/index.tsx', () => {
|
|
289
|
+
it('generates a static index HTML file', async () => {
|
|
290
|
+
await import('./index.js');
|
|
291
|
+
await scriptPromise;
|
|
292
|
+
|
|
293
|
+
expect(scriptMock).toHaveBeenCalledTimes(1);
|
|
294
|
+
expect(readFileSyncMock).toHaveBeenCalledWith(expect.stringContaining('styles.css'), 'utf8');
|
|
295
|
+
expect(getContentMock).toHaveBeenCalledTimes(1);
|
|
296
|
+
expect(mkdirSyncMock).toHaveBeenCalledWith(expect.stringContaining('static'), {
|
|
297
|
+
recursive: true,
|
|
298
|
+
});
|
|
299
|
+
expect(writeFileSyncMock).toHaveBeenCalledWith(
|
|
300
|
+
expect.stringContaining('static/index.html'),
|
|
301
|
+
expect.stringContaining('<title>Sample Content</title>'),
|
|
302
|
+
);
|
|
303
|
+
|
|
304
|
+
const generatedHtml = writeFileSyncMock.mock.calls[0]?.[1] as string;
|
|
305
|
+
|
|
306
|
+
expect(generatedHtml).toContain('<style>.content { display: block; }</style>');
|
|
307
|
+
expect(generatedHtml).toContain('This is a sample content fetched from the CMS.');
|
|
308
|
+
});
|
|
309
|
+
});
|
|
310
|
+
`,
|
|
5
311
|
}),
|
|
6
312
|
ci: {
|
|
7
|
-
|
|
8
|
-
'.gitlab-ci.yml': 'code',
|
|
9
|
-
},
|
|
10
|
-
},
|
|
11
|
-
deploy: {
|
|
12
|
-
docker: {
|
|
13
|
-
Dockerfile: 'code',
|
|
14
|
-
},
|
|
313
|
+
...base.ci,
|
|
15
314
|
},
|
|
315
|
+
deploy: {},
|
|
16
316
|
};
|
|
@@ -1,16 +1,325 @@
|
|
|
1
|
+
import packageJson from '../../../package.json' with { type: 'json' };
|
|
1
2
|
import { base } from './base.js';
|
|
2
3
|
export const ssr = {
|
|
3
4
|
files: (options) => ({
|
|
4
5
|
...base.files(options),
|
|
6
|
+
'package.json': JSON.stringify({
|
|
7
|
+
name: options.name,
|
|
8
|
+
version: '0.0.0',
|
|
9
|
+
description: options.description,
|
|
10
|
+
private: true,
|
|
11
|
+
type: 'module',
|
|
12
|
+
agents: './AGENTS.md',
|
|
13
|
+
packageManager: packageJson.packageManager,
|
|
14
|
+
engines: {
|
|
15
|
+
node: packageJson.engines.node,
|
|
16
|
+
},
|
|
17
|
+
workspaces: [
|
|
18
|
+
'packages/*',
|
|
19
|
+
'workspaces/*',
|
|
20
|
+
],
|
|
21
|
+
scripts: {
|
|
22
|
+
storybook: 'cross-env STORYBOOK_DISABLE_TELEMETRY=1 storybook dev -p 6006 --disable-telemetry',
|
|
23
|
+
check: 'run-s lint build test',
|
|
24
|
+
fix: "run-s 'fix:*'",
|
|
25
|
+
start: "run-p 'start:*'",
|
|
26
|
+
lint: "run-s 'lint:*'",
|
|
27
|
+
build: "run-s 'build:*'",
|
|
28
|
+
test: "run-s 'test:*'",
|
|
29
|
+
'fix:prettier': 'prettier . --write',
|
|
30
|
+
'fix:eslint': 'eslint . --fix',
|
|
31
|
+
'fix:stylelint': 'stylelint "**/*.{css,scss}" --fix',
|
|
32
|
+
'start:api': 'sh workspaces/api/bin/start.sh',
|
|
33
|
+
'lint:ts': 'tsc',
|
|
34
|
+
'lint:prettier': 'prettier . --check',
|
|
35
|
+
'lint:eslint': 'eslint .',
|
|
36
|
+
'lint:stylelint': 'stylelint "**/*.{css,scss}"',
|
|
37
|
+
'build:api': 'rimraf dist && sh workspaces/api/bin/build.sh',
|
|
38
|
+
'build:storybook': 'cross-env STORYBOOK_DISABLE_TELEMETRY=1 storybook build --quiet --disable-telemetry',
|
|
39
|
+
'test:jest': 'jest',
|
|
40
|
+
postinstall: 'husky',
|
|
41
|
+
},
|
|
42
|
+
dependencies: {
|
|
43
|
+
'@vyriy/typescript-config': `^${packageJson.version}`,
|
|
44
|
+
typescript: packageJson.peerDependencies.typescript,
|
|
45
|
+
'@vyriy/prettier-config': `^${packageJson.version}`,
|
|
46
|
+
prettier: packageJson.peerDependencies.prettier,
|
|
47
|
+
'@vyriy/eslint-config': `^${packageJson.version}`,
|
|
48
|
+
eslint: packageJson.peerDependencies.eslint,
|
|
49
|
+
'@vyriy/jest-config': `^${packageJson.version}`,
|
|
50
|
+
jest: packageJson.peerDependencies.jest,
|
|
51
|
+
'@vyriy/storybook-config': `^${packageJson.version}`,
|
|
52
|
+
storybook: packageJson.peerDependencies.storybook,
|
|
53
|
+
'@vyriy/path': `^${packageJson.version}`,
|
|
54
|
+
vyriy: `^${packageJson.version}`,
|
|
55
|
+
husky: packageJson.peerDependencies.husky,
|
|
56
|
+
'npm-run-all2': packageJson.peerDependencies['npm-run-all2'],
|
|
57
|
+
'cross-env': packageJson.peerDependencies['cross-env'],
|
|
58
|
+
rimraf: packageJson.peerDependencies.rimraf,
|
|
59
|
+
'@vyriy/webpack-config': `^${packageJson.version}`,
|
|
60
|
+
'@vyriy/handler': `^${packageJson.version}`,
|
|
61
|
+
'@vyriy/server': `^${packageJson.version}`,
|
|
62
|
+
tsx: packageJson.peerDependencies.tsx,
|
|
63
|
+
webpack: packageJson.peerDependencies.webpack,
|
|
64
|
+
'webpack-cli': packageJson.peerDependencies['webpack-cli'],
|
|
65
|
+
react: packageJson.peerDependencies.react,
|
|
66
|
+
'react-dom': packageJson.peerDependencies['react-dom'],
|
|
67
|
+
'@types/react': packageJson.peerDependencies['@types/react'],
|
|
68
|
+
'@types/react-dom': packageJson.peerDependencies['@types/react-dom'],
|
|
69
|
+
'@vyriy/stylelint-config': `^${packageJson.version}`,
|
|
70
|
+
'@vyriy/cn': `^${packageJson.version}`,
|
|
71
|
+
'@vyriy/html': `^${packageJson.version}`,
|
|
72
|
+
stylelint: packageJson.peerDependencies.stylelint,
|
|
73
|
+
sass: packageJson.peerDependencies.sass,
|
|
74
|
+
},
|
|
75
|
+
}, null, 2) + '\n',
|
|
76
|
+
'stylelint.config.ts': "export { default } from '@vyriy/stylelint-config';\n",
|
|
77
|
+
'assets.d.ts': "declare module '*.scss';\n",
|
|
78
|
+
'packages/components/package.json': JSON.stringify({
|
|
79
|
+
name: '@p/components',
|
|
80
|
+
private: true,
|
|
81
|
+
type: 'module',
|
|
82
|
+
}, null, 2) + '\n',
|
|
83
|
+
'packages/components/index.ts': "export * from './page/index.js';\n",
|
|
84
|
+
'packages/components/index.test.tsx': `import { describe, expect, it } from '@jest/globals';
|
|
85
|
+
|
|
86
|
+
import { Page } from './index.js';
|
|
87
|
+
import { Page as PageImplementation } from './page/index.js';
|
|
88
|
+
|
|
89
|
+
describe('packages/components/page', () => {
|
|
90
|
+
it('re-exports the page component', () => {
|
|
91
|
+
expect(Page).toBe(PageImplementation);
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
`,
|
|
95
|
+
'packages/components/page/index.ts': `export * from './page.js';
|
|
96
|
+
export type * from './types.js';
|
|
97
|
+
`,
|
|
98
|
+
'packages/components/page/index.test.ts': `import { describe, expect, it } from '@jest/globals';
|
|
99
|
+
|
|
100
|
+
import { Page } from './index.js';
|
|
101
|
+
import { Page as PageImplementation } from './page.js';
|
|
102
|
+
|
|
103
|
+
describe('packages/components/page', () => {
|
|
104
|
+
it('re-exports the page component', () => {
|
|
105
|
+
expect(Page).toBe(PageImplementation);
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
`,
|
|
109
|
+
'packages/components/page/types.ts': `import { FC } from 'react';
|
|
110
|
+
|
|
111
|
+
export type PageProps = {
|
|
112
|
+
content: string;
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
export type PageType = FC<PageProps>;
|
|
116
|
+
`,
|
|
117
|
+
'packages/components/page/page.tsx': `import type { PageType } from './types.js';
|
|
118
|
+
|
|
119
|
+
export const Page: PageType = ({ content }) => <div className="content">{content}</div>;
|
|
120
|
+
`,
|
|
121
|
+
'packages/components/page/styles.scss': `.content {
|
|
122
|
+
display: block;
|
|
123
|
+
}
|
|
124
|
+
`,
|
|
125
|
+
'packages/components/page/page.test.tsx': `import { renderToStaticMarkup } from 'react-dom/server';
|
|
126
|
+
import { describe, expect, it } from '@jest/globals';
|
|
127
|
+
|
|
128
|
+
import { Page } from './page.js';
|
|
129
|
+
|
|
130
|
+
describe('packages/components/page/page', () => {
|
|
131
|
+
it('renders content inside the page content container', () => {
|
|
132
|
+
expect(renderToStaticMarkup(<Page content="Page body" />)).toBe('<div class="content">Page body</div>');
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
`,
|
|
136
|
+
'packages/services/package.json': JSON.stringify({
|
|
137
|
+
name: '@p/services',
|
|
138
|
+
private: true,
|
|
139
|
+
type: 'module',
|
|
140
|
+
}, null, 2) + '\n',
|
|
141
|
+
'packages/services/cms/index.ts': `export const cms = {
|
|
142
|
+
getContent: async () => {
|
|
143
|
+
// Placeholder for fetching content from a CMS
|
|
144
|
+
return Promise.resolve({
|
|
145
|
+
title: 'Sample Content',
|
|
146
|
+
body: 'This is a sample content fetched from the CMS.',
|
|
147
|
+
});
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
`,
|
|
151
|
+
'packages/services/cms/index.test.ts': `import { describe, expect, it } from '@jest/globals';
|
|
152
|
+
|
|
153
|
+
import { cms } from './index.js';
|
|
154
|
+
|
|
155
|
+
describe('packages/services/cms', () => {
|
|
156
|
+
it('returns content for rendering a page', async () => {
|
|
157
|
+
await expect(cms.getContent()).resolves.toEqual({
|
|
158
|
+
title: 'Sample Content',
|
|
159
|
+
body: 'This is a sample content fetched from the CMS.',
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
`,
|
|
164
|
+
'workspaces/api/bin/build.sh': `#!/usr/bin/env sh
|
|
165
|
+
|
|
166
|
+
set -e
|
|
167
|
+
|
|
168
|
+
scriptdir="$PWD/workspaces/api";
|
|
169
|
+
distdir="$PWD/dist/api";
|
|
170
|
+
|
|
171
|
+
NODE_ENV=production npx webpack --config $scriptdir/webpack.config.ts
|
|
172
|
+
|
|
173
|
+
yarn exec sass packages/components/page/styles.scss "$distdir/styles.css" --no-source-map --style=compressed
|
|
174
|
+
cp $scriptdir/package.json "$distdir/package.json"
|
|
175
|
+
npm pkg delete "type" --prefix "$distdir"
|
|
176
|
+
npm pkg delete "private" --prefix "$distdir"
|
|
177
|
+
`,
|
|
178
|
+
'workspaces/api/bin/start.sh': `#!/usr/bin/env sh
|
|
179
|
+
|
|
180
|
+
set -e
|
|
181
|
+
|
|
182
|
+
scriptdir="$PWD/workspaces/api";
|
|
183
|
+
distdir="$PWD/dist/api";
|
|
184
|
+
|
|
185
|
+
mkdir -p "$distdir"
|
|
186
|
+
yarn exec sass packages/components/page/styles.scss "$distdir/styles.css" --no-source-map
|
|
187
|
+
|
|
188
|
+
PROJECT_CWD="$distdir" NODE_ENV=production LOG_LEVEL=info "$PWD/node_modules/.bin/tsx" $scriptdir/index.tsx
|
|
189
|
+
`,
|
|
190
|
+
'workspaces/api/doc.mdx': `import { Meta, Markdown } from '@storybook/addon-docs/blocks';
|
|
191
|
+
import ReadMe from './README.md?raw';
|
|
192
|
+
|
|
193
|
+
<Meta title="Workspaces/API" />
|
|
194
|
+
|
|
195
|
+
<Markdown>{ReadMe}</Markdown>
|
|
196
|
+
`,
|
|
197
|
+
'workspaces/api/README.md': `# ${options.name} API\n\n${options.description}\n`,
|
|
198
|
+
'workspaces/api/webpack.config.ts': `import { path } from '@vyriy/path';
|
|
199
|
+
import { ssr, external } from '@vyriy/webpack-config';
|
|
200
|
+
|
|
201
|
+
export default ssr(
|
|
202
|
+
'@w/api',
|
|
203
|
+
{
|
|
204
|
+
path: path('dist', 'api'),
|
|
205
|
+
filename: 'index.js',
|
|
206
|
+
library: { type: 'commonjs2' },
|
|
207
|
+
},
|
|
208
|
+
(config) => ({
|
|
209
|
+
...config,
|
|
210
|
+
externals: [external({ allowlist: [/^@p/, /^@w/, /^@vyriy/] })],
|
|
211
|
+
}),
|
|
212
|
+
);
|
|
213
|
+
`,
|
|
214
|
+
'workspaces/api/package.json': JSON.stringify({
|
|
215
|
+
name: '@w/api',
|
|
216
|
+
type: 'module',
|
|
217
|
+
private: true,
|
|
218
|
+
}, null, 2) + '\n',
|
|
219
|
+
'workspaces/api/index.tsx': `import { readFileSync } from 'node:fs';
|
|
220
|
+
import { renderToString } from 'react-dom/server';
|
|
221
|
+
|
|
222
|
+
import { server } from '@vyriy/server';
|
|
223
|
+
import { api } from '@vyriy/handler';
|
|
224
|
+
import { html, minify } from '@vyriy/html';
|
|
225
|
+
import { path } from '@vyriy/path';
|
|
226
|
+
|
|
227
|
+
import { cms } from '@p/services/cms';
|
|
228
|
+
import { Page } from '@p/components';
|
|
229
|
+
|
|
230
|
+
const dashboardStyles = readFileSync(path('styles.css'), 'utf8');
|
|
231
|
+
|
|
232
|
+
server(
|
|
233
|
+
api(async () => {
|
|
234
|
+
const content = await cms.getContent();
|
|
235
|
+
|
|
236
|
+
return {
|
|
237
|
+
statusCode: 200,
|
|
238
|
+
headers: {
|
|
239
|
+
'content-type': 'text/html; charset=utf-8',
|
|
240
|
+
},
|
|
241
|
+
body: minify(
|
|
242
|
+
html({
|
|
243
|
+
htmlAttributes: 'lang="en"',
|
|
244
|
+
title: \`<title>\${content.title}</title>\`,
|
|
245
|
+
meta: '<meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" />',
|
|
246
|
+
style: \`<style>\${dashboardStyles.trim()}</style>\`,
|
|
247
|
+
body: renderToString(<Page content={content.body} />),
|
|
248
|
+
}),
|
|
249
|
+
),
|
|
250
|
+
};
|
|
251
|
+
}),
|
|
252
|
+
);
|
|
253
|
+
`,
|
|
254
|
+
'workspaces/api/index.test.tsx': `import { describe, expect, it, jest } from '@jest/globals';
|
|
255
|
+
|
|
256
|
+
const apiMock = jest.fn((handler) => ({
|
|
257
|
+
handler,
|
|
258
|
+
}));
|
|
259
|
+
const getContentMock = jest.fn(() =>
|
|
260
|
+
Promise.resolve({
|
|
261
|
+
title: 'Sample Content',
|
|
262
|
+
body: 'This is a sample content fetched from the CMS.',
|
|
263
|
+
}),
|
|
264
|
+
);
|
|
265
|
+
const nodeFs = jest.requireActual<typeof import('node:fs')>('node:fs');
|
|
266
|
+
const readFileSyncMock = jest.fn<(path: string | URL, encoding: 'utf8') => string>(
|
|
267
|
+
() => '.content { display: block; }',
|
|
268
|
+
);
|
|
269
|
+
const serverMock = jest.fn();
|
|
270
|
+
|
|
271
|
+
jest.mock('node:fs', () => ({
|
|
272
|
+
...nodeFs,
|
|
273
|
+
readFileSync: readFileSyncMock,
|
|
274
|
+
}));
|
|
275
|
+
|
|
276
|
+
jest.mock('@vyriy/handler', () => ({
|
|
277
|
+
api: apiMock,
|
|
278
|
+
}));
|
|
279
|
+
|
|
280
|
+
jest.mock('@vyriy/server', () => ({
|
|
281
|
+
server: serverMock,
|
|
282
|
+
}));
|
|
283
|
+
|
|
284
|
+
jest.mock('@p/services/cms', () => ({
|
|
285
|
+
cms: {
|
|
286
|
+
getContent: getContentMock,
|
|
287
|
+
},
|
|
288
|
+
}));
|
|
289
|
+
|
|
290
|
+
describe('workspaces/api/index.tsx', () => {
|
|
291
|
+
it('starts the server with a handler that returns rendered page HTML', async () => {
|
|
292
|
+
await import('./index.js');
|
|
293
|
+
|
|
294
|
+
expect(apiMock).toHaveBeenCalledTimes(1);
|
|
295
|
+
expect(serverMock).toHaveBeenCalledTimes(1);
|
|
296
|
+
expect(serverMock).toHaveBeenCalledWith(apiMock.mock.results[0]?.value);
|
|
297
|
+
|
|
298
|
+
const handler = apiMock.mock.calls[0]?.[0] as () => Promise<{
|
|
299
|
+
statusCode: number;
|
|
300
|
+
headers: Record<string, string>;
|
|
301
|
+
body: string;
|
|
302
|
+
}>;
|
|
303
|
+
|
|
304
|
+
const response = await handler();
|
|
305
|
+
|
|
306
|
+
expect(response).toEqual({
|
|
307
|
+
statusCode: 200,
|
|
308
|
+
headers: {
|
|
309
|
+
'content-type': 'text/html; charset=utf-8',
|
|
310
|
+
},
|
|
311
|
+
body: expect.stringContaining('<title>Sample Content</title>'),
|
|
312
|
+
});
|
|
313
|
+
expect(response.body).toContain('<style>.content { display: block; }</style>');
|
|
314
|
+
expect(response.body).toContain('This is a sample content fetched from the CMS.');
|
|
315
|
+
expect(readFileSyncMock).toHaveBeenCalledWith(expect.stringContaining('styles.css'), 'utf8');
|
|
316
|
+
expect(getContentMock).toHaveBeenCalledTimes(1);
|
|
317
|
+
});
|
|
318
|
+
});
|
|
319
|
+
`,
|
|
5
320
|
}),
|
|
6
321
|
ci: {
|
|
7
|
-
|
|
8
|
-
'.gitlab-ci.yml': 'code',
|
|
9
|
-
},
|
|
10
|
-
},
|
|
11
|
-
deploy: {
|
|
12
|
-
docker: {
|
|
13
|
-
Dockerfile: 'code',
|
|
14
|
-
},
|
|
322
|
+
...base.ci,
|
|
15
323
|
},
|
|
324
|
+
deploy: {},
|
|
16
325
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vyriy",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.4",
|
|
4
4
|
"description": "Interactive project master for calm cloud-ready applications.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": "./bin/vyriy.js",
|
|
@@ -20,11 +20,12 @@
|
|
|
20
20
|
"react": "^19.2.6",
|
|
21
21
|
"react-dom": "^19.2.6",
|
|
22
22
|
"rimraf": "^6.1.3",
|
|
23
|
+
"sass": "^1.100.0",
|
|
23
24
|
"storybook": "^10.4.1",
|
|
24
25
|
"stylelint": "^17.12.0",
|
|
25
26
|
"tsx": "^4.22.3",
|
|
26
27
|
"typescript": "^6.0.3",
|
|
27
|
-
"webpack": "^5.107.
|
|
28
|
+
"webpack": "^5.107.2",
|
|
28
29
|
"webpack-cli": "^7.0.2"
|
|
29
30
|
},
|
|
30
31
|
"peerDependenciesMeta": {
|
|
@@ -64,6 +65,9 @@
|
|
|
64
65
|
"rimraf": {
|
|
65
66
|
"optional": true
|
|
66
67
|
},
|
|
68
|
+
"sass": {
|
|
69
|
+
"optional": true
|
|
70
|
+
},
|
|
67
71
|
"storybook": {
|
|
68
72
|
"optional": true
|
|
69
73
|
},
|