vyriy 0.4.4 → 0.4.7

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.
@@ -29,4 +29,14 @@ export declare const presets: {
29
29
  description: string;
30
30
  preset: import("./types.js").Preset;
31
31
  };
32
+ rest: {
33
+ name: string;
34
+ description: string;
35
+ preset: import("./types.js").Preset;
36
+ };
37
+ gql: {
38
+ name: string;
39
+ description: string;
40
+ preset: import("./types.js").Preset;
41
+ };
32
42
  };
@@ -4,6 +4,8 @@ import { api } from './api.js';
4
4
  import { ssr } from './ssr.js';
5
5
  import { ssg } from './ssg.js';
6
6
  import { spa } from './spa.js';
7
+ import { rest } from './rest.js';
8
+ import { gql } from './gql.js';
7
9
  export const presets = {
8
10
  base: {
9
11
  name: 'Base',
@@ -35,4 +37,14 @@ export const presets = {
35
37
  description: 'Preset for Single Page Application (SPA)',
36
38
  preset: spa,
37
39
  },
40
+ rest: {
41
+ name: 'REST',
42
+ description: 'Preset for simple REST API',
43
+ preset: rest,
44
+ },
45
+ gql: {
46
+ name: 'GraphQL',
47
+ description: 'Preset for GraphQL API',
48
+ preset: gql,
49
+ },
38
50
  };
@@ -1,16 +1,344 @@
1
+ import packageJson from '../../../package.json' with { type: 'json' };
1
2
  import { base } from './base.js';
2
3
  export const rest = {
3
4
  files: (options) => ({
4
5
  ...base.files(options),
5
- }),
6
- ci: {
7
- github: {
8
- '.gitlab-ci.yml': 'code',
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
+ 'workspaces/*',
19
+ ],
20
+ scripts: {
21
+ storybook: 'cross-env STORYBOOK_DISABLE_TELEMETRY=1 storybook dev -p 6006 --disable-telemetry',
22
+ check: 'run-s lint build test',
23
+ fix: "run-s 'fix:*'",
24
+ start: "run-p 'start:*'",
25
+ lint: "run-s 'lint:*'",
26
+ build: "run-s 'build:*'",
27
+ test: "run-s 'test:*'",
28
+ 'fix:prettier': 'prettier . --write',
29
+ 'fix:eslint': 'eslint . --fix',
30
+ 'start:api': 'sh workspaces/api/bin/start.sh',
31
+ 'lint:ts': 'tsc',
32
+ 'lint:prettier': 'prettier . --check',
33
+ 'lint:eslint': 'eslint .',
34
+ 'build:api': 'rimraf dist && sh workspaces/api/bin/build.sh',
35
+ 'build:storybook': 'cross-env STORYBOOK_DISABLE_TELEMETRY=1 storybook build --quiet --disable-telemetry',
36
+ 'test:jest': 'jest',
37
+ postinstall: 'husky',
38
+ },
39
+ dependencies: {
40
+ '@vyriy/typescript-config': `^${packageJson.version}`,
41
+ typescript: packageJson.peerDependencies.typescript,
42
+ '@vyriy/prettier-config': `^${packageJson.version}`,
43
+ prettier: packageJson.peerDependencies.prettier,
44
+ '@vyriy/eslint-config': `^${packageJson.version}`,
45
+ eslint: packageJson.peerDependencies.eslint,
46
+ '@vyriy/jest-config': `^${packageJson.version}`,
47
+ jest: packageJson.peerDependencies.jest,
48
+ '@vyriy/storybook-config': `^${packageJson.version}`,
49
+ storybook: packageJson.peerDependencies.storybook,
50
+ '@vyriy/path': `^${packageJson.version}`,
51
+ husky: packageJson.peerDependencies.husky,
52
+ 'npm-run-all2': packageJson.peerDependencies['npm-run-all2'],
53
+ 'cross-env': packageJson.peerDependencies['cross-env'],
54
+ rimraf: packageJson.peerDependencies.rimraf,
55
+ '@vyriy/webpack-config': `^${packageJson.version}`,
56
+ '@vyriy/handler': `^${packageJson.version}`,
57
+ '@vyriy/server': `^${packageJson.version}`,
58
+ tsx: packageJson.peerDependencies.tsx,
59
+ 'webpack-cli': packageJson.peerDependencies['webpack-cli'],
60
+ '@vyriy/router': `^${packageJson.version}`,
61
+ '@vyriy/html': `^${packageJson.version}`,
62
+ },
63
+ }, null, 2) + '\n',
64
+ 'workspaces/api/bin/build.sh': `#!/usr/bin/env sh
65
+
66
+ set -e
67
+
68
+ scriptdir="$PWD/workspaces/api";
69
+
70
+ NODE_ENV=production npx webpack --config $scriptdir/webpack.config.ts
71
+
72
+ cp $scriptdir/package.json dist/api/package.json
73
+ npm pkg delete "type" --prefix dist/api
74
+ npm pkg delete "private" --prefix dist/api
75
+ `,
76
+ 'workspaces/api/bin/start.sh': `#!/usr/bin/env sh
77
+
78
+ set -e
79
+
80
+ scriptdir="$PWD/workspaces/api";
81
+
82
+ NODE_ENV=production LOG_LEVEL=info tsx $scriptdir/index.ts
83
+ `,
84
+ 'workspaces/api/doc.mdx': `import { Meta, Markdown } from '@storybook/addon-docs/blocks';
85
+ import ReadMe from './README.md?raw';
86
+
87
+ <Meta title="Workspaces/API" />
88
+
89
+ <Markdown>{ReadMe}</Markdown>
90
+ `,
91
+ 'workspaces/api/README.md': `# ${options.name} API\n\n${options.description}\n`,
92
+ 'workspaces/api/webpack.config.ts': `import { path } from '@vyriy/path';
93
+ import { ssr, external } from '@vyriy/webpack-config';
94
+
95
+ export default ssr(
96
+ '@w/api',
97
+ {
98
+ path: path('dist', 'api'),
99
+ filename: 'index.js',
100
+ library: { type: 'commonjs2' },
101
+ },
102
+ (config) => ({
103
+ ...config,
104
+ externals: [external({ allowlist: [/^@p/, /^@w/, /^@vyriy/] })],
105
+ }),
106
+ );
107
+ `,
108
+ 'workspaces/api/package.json': JSON.stringify({
109
+ name: '@w/api',
110
+ type: 'module',
111
+ private: true,
112
+ }, null, 2) + '\n',
113
+ 'workspaces/api/index.ts': `import { server } from '@vyriy/server';
114
+ import { api } from '@vyriy/handler';
115
+ import { createRouter } from '@vyriy/router';
116
+ import { html, minify } from '@vyriy/html';
117
+
118
+ const router = createRouter();
119
+
120
+ router.get('/api/test', async () => {
121
+ return Promise.resolve({
122
+ headers: {
123
+ 'content-type': 'application/json',
124
+ },
125
+ body: JSON.stringify({ test: 'ok' }),
126
+ });
127
+ });
128
+
129
+ router.get('/openapi.json', async () => {
130
+ return Promise.resolve({
131
+ headers: {
132
+ 'content-type': 'application/json',
133
+ },
134
+ body: JSON.stringify({
135
+ openapi: '3.0.0',
136
+ info: {
137
+ title: 'REST API',
138
+ description: 'A minimal example of an OpenAPI definition in JSON format.',
139
+ version: '1.0.0',
140
+ },
141
+ servers: [
142
+ {
143
+ url: 'http://localhost:3000',
144
+ description: 'Local server',
145
+ },
146
+ ],
147
+ paths: {
148
+ '/api/test': {
149
+ get: {
150
+ summary: 'Test endpoint',
151
+ operationId: 'getTest',
152
+ responses: {
153
+ '200': {
154
+ description: 'A successful test response',
155
+ content: {
156
+ 'application/json': {
157
+ schema: {
158
+ $ref: '#/components/schemas/TestResponse',
159
+ },
160
+ example: {
161
+ test: 'ok',
162
+ },
163
+ },
164
+ },
165
+ },
166
+ },
167
+ },
168
+ },
169
+ },
170
+ components: {
171
+ schemas: {
172
+ TestResponse: {
173
+ type: 'object',
174
+ required: ['test'],
175
+ properties: {
176
+ test: {
177
+ type: 'string',
178
+ example: 'ok',
179
+ },
180
+ },
181
+ },
9
182
  },
183
+ },
184
+ }),
185
+ });
186
+ });
187
+
188
+ router.get('/', async () => {
189
+ return Promise.resolve({
190
+ headers: {
191
+ 'content-type': 'text/html; charset=utf-8',
10
192
  },
11
- deploy: {
12
- docker: {
13
- Dockerfile: 'code',
193
+ body: minify(
194
+ html({
195
+ title: '<title>REST API</title>',
196
+ meta: '<meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" />',
197
+ body: [
198
+ '<div id="app"></div>',
199
+ '<script src="https://cdn.jsdelivr.net/npm/@scalar/api-reference"></script>',
200
+ "<script>Scalar.createApiReference('#app', { url: '/openapi.json' })</script>",
201
+ ].join(''),
202
+ }),
203
+ ),
204
+ });
205
+ });
206
+
207
+ server(api(async (event) => router.route(event)));
208
+ `,
209
+ 'workspaces/api/index.test.ts': `import { describe, expect, it, jest } from '@jest/globals';
210
+ import type { APIGatewayProxyEvent } from '@vyriy/router';
211
+
212
+ const apiMock = jest.fn((handler) => ({
213
+ handler,
214
+ }));
215
+ const serverMock = jest.fn();
216
+
217
+ jest.mock('@vyriy/handler', () => ({
218
+ api: apiMock,
219
+ }));
220
+
221
+ jest.mock('@vyriy/server', () => ({
222
+ server: serverMock,
223
+ }));
224
+
225
+ describe('workspaces/api/index.ts', () => {
226
+ const getEvent = (path: string): APIGatewayProxyEvent =>
227
+ ({
228
+ body: null,
229
+ headers: {},
230
+ httpMethod: 'GET',
231
+ path,
232
+ pathParameters: null,
233
+ queryStringParameters: null,
234
+ }) as APIGatewayProxyEvent;
235
+
236
+ it('starts the server with the API router handler', async () => {
237
+ await import('./index.js');
238
+
239
+ expect(apiMock).toHaveBeenCalledTimes(1);
240
+ expect(serverMock).toHaveBeenCalledTimes(1);
241
+ expect(serverMock).toHaveBeenCalledWith(apiMock.mock.results[0]?.value);
242
+
243
+ const handler = apiMock.mock.calls[0]?.[0] as (event: APIGatewayProxyEvent) => Promise<{
244
+ body: string;
245
+ headers?: Record<string, string>;
246
+ statusCode: number;
247
+ }>;
248
+
249
+ await expect(handler(getEvent('/api/test'))).resolves.toEqual({
250
+ body: JSON.stringify({
251
+ test: 'ok',
252
+ }),
253
+ headers: {
254
+ 'content-type': 'application/json',
255
+ },
256
+ statusCode: 200,
257
+ });
258
+
259
+ const openApiResponse = await handler(getEvent('/openapi.json'));
260
+
261
+ expect(openApiResponse).toEqual({
262
+ body: expect.any(String),
263
+ headers: {
264
+ 'content-type': 'application/json',
265
+ },
266
+ statusCode: 200,
267
+ });
268
+ expect(JSON.parse(openApiResponse.body)).toEqual({
269
+ components: {
270
+ schemas: {
271
+ TestResponse: {
272
+ properties: {
273
+ test: {
274
+ example: 'ok',
275
+ type: 'string',
276
+ },
277
+ },
278
+ required: ['test'],
279
+ type: 'object',
280
+ },
281
+ },
282
+ },
283
+ info: {
284
+ description: 'A minimal example of an OpenAPI definition in JSON format.',
285
+ title: 'REST API',
286
+ version: '1.0.0',
287
+ },
288
+ openapi: '3.0.0',
289
+ paths: {
290
+ '/api/test': {
291
+ get: {
292
+ operationId: 'getTest',
293
+ responses: {
294
+ '200': {
295
+ content: {
296
+ 'application/json': {
297
+ example: {
298
+ test: 'ok',
299
+ },
300
+ schema: {
301
+ $ref: '#/components/schemas/TestResponse',
302
+ },
303
+ },
304
+ },
305
+ description: 'A successful test response',
306
+ },
307
+ },
308
+ summary: 'Test endpoint',
309
+ },
310
+ },
311
+ },
312
+ servers: [
313
+ {
314
+ description: 'Local server',
315
+ url: 'http://localhost:3000',
14
316
  },
317
+ ],
318
+ });
319
+
320
+ const docsResponse = await handler(getEvent('/'));
321
+
322
+ expect(docsResponse).toEqual({
323
+ body: expect.stringContaining("Scalar.createApiReference('#app', { url: '/openapi.json' })"),
324
+ headers: {
325
+ 'content-type': 'text/html; charset=utf-8',
326
+ },
327
+ statusCode: 200,
328
+ });
329
+
330
+ await expect(handler(getEvent('/healthcheck'))).resolves.toEqual({
331
+ body: JSON.stringify({
332
+ message: 'Not Found',
333
+ }),
334
+ statusCode: 404,
335
+ });
336
+ });
337
+ });
338
+ `,
339
+ }),
340
+ ci: {
341
+ ...base.ci,
15
342
  },
343
+ deploy: {},
16
344
  };
@@ -51,14 +51,12 @@ export const spa = {
51
51
  '@vyriy/storybook-config': `^${packageJson.version}`,
52
52
  storybook: packageJson.peerDependencies.storybook,
53
53
  '@vyriy/path': `^${packageJson.version}`,
54
- vyriy: `^${packageJson.version}`,
55
54
  husky: packageJson.peerDependencies.husky,
56
55
  'npm-run-all2': packageJson.peerDependencies['npm-run-all2'],
57
56
  'cross-env': packageJson.peerDependencies['cross-env'],
58
57
  rimraf: packageJson.peerDependencies.rimraf,
59
58
  '@vyriy/webpack-config': `^${packageJson.version}`,
60
59
  tsx: packageJson.peerDependencies.tsx,
61
- webpack: packageJson.peerDependencies.webpack,
62
60
  'webpack-cli': packageJson.peerDependencies['webpack-cli'],
63
61
  react: packageJson.peerDependencies.react,
64
62
  'react-dom': packageJson.peerDependencies['react-dom'],
@@ -93,56 +91,56 @@ extends @vyriy/browserslist-config
93
91
  'packages/components/index.ts': "export * from './page/index.js';\n",
94
92
  'packages/components/index.test.tsx': `import { describe, expect, it } from '@jest/globals';
95
93
 
96
- import { Page } from './index.js';
97
- import { Page as PageImplementation } from './page/index.js';
94
+ import { Page } from './index.js';
95
+ import { Page as PageImplementation } from './page/index.js';
98
96
 
99
- describe('packages/components/page', () => {
100
- it('re-exports the page component', () => {
101
- expect(Page).toBe(PageImplementation);
102
- });
103
- });
104
- `,
97
+ describe('packages/components/page', () => {
98
+ it('re-exports the page component', () => {
99
+ expect(Page).toBe(PageImplementation);
100
+ });
101
+ });
102
+ `,
105
103
  'packages/components/page/index.ts': `export * from './page.js';
106
- export type * from './types.js';
107
- `,
104
+ export type * from './types.js';
105
+ `,
108
106
  'packages/components/page/index.test.ts': `import { describe, expect, it } from '@jest/globals';
109
107
 
110
- import { Page } from './index.js';
111
- import { Page as PageImplementation } from './page.js';
108
+ import { Page } from './index.js';
109
+ import { Page as PageImplementation } from './page.js';
112
110
 
113
- describe('packages/components/page', () => {
114
- it('re-exports the page component', () => {
115
- expect(Page).toBe(PageImplementation);
116
- });
117
- });
118
- `,
111
+ describe('packages/components/page', () => {
112
+ it('re-exports the page component', () => {
113
+ expect(Page).toBe(PageImplementation);
114
+ });
115
+ });
116
+ `,
119
117
  'packages/components/page/types.ts': `import { FC } from 'react';
120
118
 
121
- export type PageProps = {
122
- content: string;
123
- };
119
+ export type PageProps = {
120
+ content: string;
121
+ };
124
122
 
125
- export type PageType = FC<PageProps>;
126
- `,
123
+ export type PageType = FC<PageProps>;
124
+ `,
127
125
  'packages/components/page/page.tsx': `import type { PageType } from './types.js';
128
126
 
129
- export const Page: PageType = ({ content }) => <div className="content">{content}</div>;
130
- `,
127
+ export const Page: PageType = ({ content }) => <div className="content">{content}</div>;
128
+ `,
131
129
  'packages/components/page/styles.scss': `.content {
132
- display: block;
133
- }
134
- `,
130
+ display: block;
131
+ }
132
+ `,
135
133
  'packages/components/page/page.test.tsx': `import { renderToStaticMarkup } from 'react-dom/server';
136
- import { describe, expect, it } from '@jest/globals';
134
+ import { describe, expect, it } from '@jest/globals';
137
135
 
138
- import { Page } from './page.js';
136
+ import { Page } from './page.js';
139
137
 
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
- `,
138
+ describe('packages/components/page/page', () => {
139
+ it('renders content inside the page content container', () => {
140
+ expect(renderToStaticMarkup(<Page content="Page body" />)).toBe('<div class="content">Page body</div>');
141
+ });
142
+ });
143
+ `,
146
144
  'workspaces/spa/bin/build.sh': `#!/usr/bin/env sh
147
145
 
148
146
  set -e
@@ -160,12 +158,12 @@ scriptdir="$PWD/workspaces/spa";
160
158
  npx webpack serve --open --config $scriptdir/webpack.config.ts
161
159
  `,
162
160
  'workspaces/spa/doc.mdx': `import { Meta, Markdown } from '@storybook/addon-docs/blocks';
163
- import ReadMe from './README.md?raw';
161
+ import ReadMe from './README.md?raw';
164
162
 
165
- <Meta title="Workspaces/SPA" />
163
+ <Meta title="Workspaces/SPA" />
166
164
 
167
- <Markdown>{ReadMe}</Markdown>
168
- `,
165
+ <Markdown>{ReadMe}</Markdown>
166
+ `,
169
167
  'workspaces/spa/README.md': `# ${options.name} SPA\n\n${options.description}\n`,
170
168
  'workspaces/spa/webpack.config.ts': `import { csr, html } from '@vyriy/webpack-config';
171
169
  import { path } from '@vyriy/path';