vyriy 0.5.2 → 0.5.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 +54 -36
- package/args.js +16 -0
- package/bin/vyriy.js +1 -1
- package/cli.js +68 -0
- package/package.json +52 -7
- package/types.d.ts +11 -0
- package/cli/args.js +0 -29
- package/cli/cli.js +0 -28
- package/cli/types.d.ts +0 -13
- package/commands/check-env.d.ts +0 -2
- package/commands/check-env.js +0 -120
- package/commands/create/index.d.ts +0 -2
- package/commands/create/index.js +0 -130
- package/commands/create/plan/index.d.ts +0 -4
- package/commands/create/plan/index.js +0 -3
- package/commands/create/plan/plan.d.ts +0 -7
- package/commands/create/plan/plan.js +0 -35
- package/commands/create/plan/question.d.ts +0 -2
- package/commands/create/plan/question.js +0 -25
- package/commands/create/plan/types.d.ts +0 -12
- package/commands/create/preset/api.d.ts +0 -2
- package/commands/create/preset/api.js +0 -63
- package/commands/create/preset/base.d.ts +0 -2
- package/commands/create/preset/base.js +0 -159
- package/commands/create/preset/fullstack.d.ts +0 -2
- package/commands/create/preset/fullstack.js +0 -158
- package/commands/create/preset/gql.d.ts +0 -2
- package/commands/create/preset/gql.js +0 -744
- package/commands/create/preset/index.d.ts +0 -52
- package/commands/create/preset/index.js +0 -62
- package/commands/create/preset/library.d.ts +0 -2
- package/commands/create/preset/library.js +0 -247
- package/commands/create/preset/mfe.d.ts +0 -2
- package/commands/create/preset/mfe.js +0 -326
- package/commands/create/preset/rest.d.ts +0 -2
- package/commands/create/preset/rest.js +0 -242
- package/commands/create/preset/shared.d.ts +0 -116
- package/commands/create/preset/shared.js +0 -245
- package/commands/create/preset/spa.d.ts +0 -2
- package/commands/create/preset/spa.js +0 -132
- package/commands/create/preset/ssg.d.ts +0 -2
- package/commands/create/preset/ssg.js +0 -171
- package/commands/create/preset/ssr.d.ts +0 -2
- package/commands/create/preset/ssr.js +0 -179
- package/commands/create/preset/types.d.ts +0 -9
- package/commands/create/prompt/conflict-strategy.d.ts +0 -5
- package/commands/create/prompt/conflict-strategy.js +0 -22
- package/commands/create/prompt/index.d.ts +0 -6
- package/commands/create/prompt/index.js +0 -5
- package/commands/create/prompt/preset.d.ts +0 -4
- package/commands/create/prompt/preset.js +0 -11
- package/commands/create/prompt/prompt.d.ts +0 -2
- package/commands/create/prompt/prompt.js +0 -4
- package/commands/create/prompt/resolve-option.d.ts +0 -6
- package/commands/create/prompt/resolve-option.js +0 -8
- package/commands/create/prompt/scope.d.ts +0 -2
- package/commands/create/prompt/scope.js +0 -2
- package/commands/create/prompt/types.d.ts +0 -4
- package/commands/dist.d.ts +0 -2
- package/commands/dist.js +0 -287
- package/commands/help.d.ts +0 -3
- package/commands/help.js +0 -24
- package/commands/index.d.ts +0 -5
- package/commands/index.js +0 -5
- package/commands/types.d.ts +0 -44
- package/commands/version.d.ts +0 -2
- package/commands/version.js +0 -6
- /package/{cli/args.d.ts → args.d.ts} +0 -0
- /package/{cli/cli.d.ts → cli.d.ts} +0 -0
- /package/{cli/index.d.ts → index.d.ts} +0 -0
- /package/{cli/index.js → index.js} +0 -0
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import { stdin, stdout } from 'node:process';
|
|
2
|
-
import { dirname, resolve } from 'node:path';
|
|
3
|
-
import { createInterface } from 'node:readline';
|
|
4
|
-
import { prompt, preset as promptPreset, scope as promptScope } from '../prompt/index.js';
|
|
5
|
-
import { question as createQuestion } from './question.js';
|
|
6
|
-
const toDirectoryName = (name, fallback) => {
|
|
7
|
-
const directoryName = name.trim().replaceAll(/[\s\\/]+/g, '_');
|
|
8
|
-
return directoryName || fallback;
|
|
9
|
-
};
|
|
10
|
-
const getTargetDefault = (name, dirName, appPath) => name === dirName ? appPath : resolve(dirname(appPath), toDirectoryName(name, dirName));
|
|
11
|
-
export const plan = async (dirName, appPath) => {
|
|
12
|
-
const readline = createInterface({ input: stdin, output: stdout });
|
|
13
|
-
const question = createQuestion(readline, stdout);
|
|
14
|
-
try {
|
|
15
|
-
stdout.write('\nVyriy Project Master\n\n');
|
|
16
|
-
const name = await prompt(question, 'Project name', dirName);
|
|
17
|
-
const description = await prompt(question, 'Project description', 'Calm cloud-ready application');
|
|
18
|
-
const target = await prompt(question, 'Target directory', getTargetDefault(name, dirName, appPath));
|
|
19
|
-
const preset = await promptPreset(question, stdout);
|
|
20
|
-
const scope = await promptScope(question, preset, name);
|
|
21
|
-
const confirmation = (await prompt(question, 'Use this project plan?', 'y')).toLowerCase();
|
|
22
|
-
return confirmation === 'y'
|
|
23
|
-
? {
|
|
24
|
-
name,
|
|
25
|
-
description,
|
|
26
|
-
target,
|
|
27
|
-
preset,
|
|
28
|
-
scope,
|
|
29
|
-
}
|
|
30
|
-
: undefined;
|
|
31
|
-
}
|
|
32
|
-
finally {
|
|
33
|
-
readline.close();
|
|
34
|
-
}
|
|
35
|
-
};
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
export const question = (readline, output) => {
|
|
2
|
-
const queuedLines = [];
|
|
3
|
-
const pendingQuestions = [];
|
|
4
|
-
readline.on('line', (line) => {
|
|
5
|
-
const resolve = pendingQuestions.shift();
|
|
6
|
-
if (resolve) {
|
|
7
|
-
resolve(line);
|
|
8
|
-
return;
|
|
9
|
-
}
|
|
10
|
-
queuedLines.push(line);
|
|
11
|
-
});
|
|
12
|
-
readline.on('close', () => {
|
|
13
|
-
for (const resolve of pendingQuestions.splice(0)) {
|
|
14
|
-
resolve('');
|
|
15
|
-
}
|
|
16
|
-
});
|
|
17
|
-
return (query) => {
|
|
18
|
-
output.write(query);
|
|
19
|
-
const queuedLine = queuedLines.shift();
|
|
20
|
-
if (queuedLine !== undefined) {
|
|
21
|
-
return Promise.resolve(queuedLine);
|
|
22
|
-
}
|
|
23
|
-
return new Promise((resolve) => pendingQuestions.push(resolve));
|
|
24
|
-
};
|
|
25
|
-
};
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import type { Interface } from 'node:readline';
|
|
2
|
-
import type { Writable } from 'node:stream';
|
|
3
|
-
export type Question = (readline: Interface, output: Writable) => (query: string) => Promise<string>;
|
|
4
|
-
export type Prompt = (question: (query: string) => Promise<string>, label: string, defaultValue: string) => Promise<string>;
|
|
5
|
-
export type PlanResult = {
|
|
6
|
-
name: string;
|
|
7
|
-
description: string;
|
|
8
|
-
target: string;
|
|
9
|
-
preset: string;
|
|
10
|
-
scope?: string;
|
|
11
|
-
};
|
|
12
|
-
export type Plan = (dirName: string, appPath: string) => Promise<PlanResult | undefined>;
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import { base } from './base.js';
|
|
2
|
-
import { apiWorkspaceBaseFiles, baseToolingDeps, buildPackageJson, serverDeps, webpackDeps, workspaceScripts, } from './shared.js';
|
|
3
|
-
export const api = (options) => ({
|
|
4
|
-
...base(options),
|
|
5
|
-
...apiWorkspaceBaseFiles(options.name, options.description),
|
|
6
|
-
'package.json': buildPackageJson(options, [
|
|
7
|
-
'workspaces/*',
|
|
8
|
-
], workspaceScripts('api'), {
|
|
9
|
-
...baseToolingDeps(),
|
|
10
|
-
...webpackDeps(),
|
|
11
|
-
...serverDeps(),
|
|
12
|
-
}),
|
|
13
|
-
'workspaces/api/index.ts': `import { server } from '@vyriy/server';
|
|
14
|
-
import { api } from '@vyriy/handler';
|
|
15
|
-
|
|
16
|
-
server(
|
|
17
|
-
api(async (event) =>
|
|
18
|
-
Promise.resolve({
|
|
19
|
-
statusCode: 200,
|
|
20
|
-
body: JSON.stringify({
|
|
21
|
-
path: event.path,
|
|
22
|
-
}),
|
|
23
|
-
}),
|
|
24
|
-
),
|
|
25
|
-
);
|
|
26
|
-
`,
|
|
27
|
-
'workspaces/api/index.test.ts': `import { describe, expect, it, jest } from '@jest/globals';
|
|
28
|
-
|
|
29
|
-
const apiMock = jest.fn((handler) => ({
|
|
30
|
-
handler,
|
|
31
|
-
}));
|
|
32
|
-
const serverMock = jest.fn();
|
|
33
|
-
|
|
34
|
-
jest.mock('@vyriy/handler', () => ({
|
|
35
|
-
api: apiMock,
|
|
36
|
-
}));
|
|
37
|
-
|
|
38
|
-
jest.mock('@vyriy/server', () => ({
|
|
39
|
-
server: serverMock,
|
|
40
|
-
}));
|
|
41
|
-
|
|
42
|
-
describe('workspaces/api/index.ts', () => {
|
|
43
|
-
it('starts the server with a handler that returns the request path', async () => {
|
|
44
|
-
await import('./index.js');
|
|
45
|
-
|
|
46
|
-
expect(apiMock).toHaveBeenCalledTimes(1);
|
|
47
|
-
expect(serverMock).toHaveBeenCalledTimes(1);
|
|
48
|
-
expect(serverMock).toHaveBeenCalledWith(apiMock.mock.results[0]?.value);
|
|
49
|
-
|
|
50
|
-
const handler = apiMock.mock.calls[0]?.[0] as (event: {
|
|
51
|
-
path: string;
|
|
52
|
-
}) => Promise<{ statusCode: number; body: string }>;
|
|
53
|
-
|
|
54
|
-
await expect(handler({ path: '/healthcheck' })).resolves.toEqual({
|
|
55
|
-
statusCode: 200,
|
|
56
|
-
body: JSON.stringify({
|
|
57
|
-
path: '/healthcheck',
|
|
58
|
-
}),
|
|
59
|
-
});
|
|
60
|
-
});
|
|
61
|
-
});
|
|
62
|
-
`,
|
|
63
|
-
});
|
|
@@ -1,159 +0,0 @@
|
|
|
1
|
-
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
-
import { dirname, resolve } from 'node:path';
|
|
3
|
-
import { fileURLToPath } from 'node:url';
|
|
4
|
-
import packageJson from '../../../package.json' with { type: 'json' };
|
|
5
|
-
const presetDir = dirname(fileURLToPath(import.meta.url));
|
|
6
|
-
const agentsPath = [
|
|
7
|
-
resolve(presetDir, '../../../AGENTS.md'),
|
|
8
|
-
resolve(presetDir, '../../../../AGENTS.md'),
|
|
9
|
-
].find(existsSync) ?? '';
|
|
10
|
-
const agentsContent = agentsPath ? readFileSync(agentsPath, 'utf8') : '';
|
|
11
|
-
export const base = ({ name, description }) => ({
|
|
12
|
-
'package.json': JSON.stringify({
|
|
13
|
-
name,
|
|
14
|
-
version: '0.0.0',
|
|
15
|
-
description,
|
|
16
|
-
private: true,
|
|
17
|
-
type: 'module',
|
|
18
|
-
agents: './AGENTS.md',
|
|
19
|
-
packageManager: packageJson.packageManager,
|
|
20
|
-
engines: {
|
|
21
|
-
node: packageJson.engines.node,
|
|
22
|
-
},
|
|
23
|
-
scripts: {
|
|
24
|
-
storybook: 'cross-env STORYBOOK_DISABLE_TELEMETRY=1 storybook dev -p 6006 --disable-telemetry',
|
|
25
|
-
check: 'run-s lint build test',
|
|
26
|
-
fix: "run-s 'fix:*'",
|
|
27
|
-
lint: "run-s 'lint:*'",
|
|
28
|
-
build: "run-s 'build:*'",
|
|
29
|
-
test: "run-s 'test:*'",
|
|
30
|
-
'fix:prettier': 'prettier . --write',
|
|
31
|
-
'fix:eslint': 'eslint . --fix',
|
|
32
|
-
'lint:ts': 'tsc',
|
|
33
|
-
'lint:prettier': 'prettier . --check',
|
|
34
|
-
'lint:eslint': 'eslint .',
|
|
35
|
-
'build:storybook': 'cross-env STORYBOOK_DISABLE_TELEMETRY=1 storybook build --quiet --disable-telemetry',
|
|
36
|
-
'test:jest': 'jest --passWithNoTests',
|
|
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
|
-
},
|
|
55
|
-
}, null, 2) + '\n',
|
|
56
|
-
'README.md': `# ${name}\n\n${description}\n`,
|
|
57
|
-
'doc.mdx': `import { Meta, Markdown } from '@storybook/addon-docs/blocks';
|
|
58
|
-
import ReadMe from './README.md?raw';
|
|
59
|
-
|
|
60
|
-
<Meta title="${name}" />
|
|
61
|
-
|
|
62
|
-
<Markdown>{ReadMe}</Markdown>
|
|
63
|
-
`,
|
|
64
|
-
'AGENTS.md': agentsContent,
|
|
65
|
-
'.editorconfig': `# https://editorconfig.org
|
|
66
|
-
root = true
|
|
67
|
-
|
|
68
|
-
[*]
|
|
69
|
-
charset = utf-8
|
|
70
|
-
end_of_line = lf
|
|
71
|
-
insert_final_newline = true
|
|
72
|
-
trim_trailing_whitespace = true
|
|
73
|
-
|
|
74
|
-
indent_style = space
|
|
75
|
-
indent_size = 2
|
|
76
|
-
|
|
77
|
-
max_line_length = 100
|
|
78
|
-
|
|
79
|
-
# Markdown
|
|
80
|
-
[*.md]
|
|
81
|
-
trim_trailing_whitespace = false
|
|
82
|
-
max_line_length = off
|
|
83
|
-
|
|
84
|
-
# YAML / YML
|
|
85
|
-
[*.{yml,yaml}]
|
|
86
|
-
indent_size = 2
|
|
87
|
-
|
|
88
|
-
# JSON
|
|
89
|
-
[*.json]
|
|
90
|
-
indent_size = 2
|
|
91
|
-
|
|
92
|
-
# TypeScript / JavaScript
|
|
93
|
-
[*.{ts,tsx,js,jsx}]
|
|
94
|
-
indent_size = 2
|
|
95
|
-
|
|
96
|
-
# Shell / Bash
|
|
97
|
-
[*.sh]
|
|
98
|
-
indent_size = 2
|
|
99
|
-
`,
|
|
100
|
-
'.gitignore': `.yarn/*
|
|
101
|
-
!.yarn/cache
|
|
102
|
-
!.yarn/patches
|
|
103
|
-
!.yarn/plugins
|
|
104
|
-
!.yarn/releases
|
|
105
|
-
!.yarn/sdks
|
|
106
|
-
!.yarn/versions
|
|
107
|
-
|
|
108
|
-
.DS_Store
|
|
109
|
-
.idea
|
|
110
|
-
node_modules
|
|
111
|
-
coverage
|
|
112
|
-
dist
|
|
113
|
-
storybook-static
|
|
114
|
-
*storybook.log
|
|
115
|
-
consumer
|
|
116
|
-
|
|
117
|
-
cdk.out
|
|
118
|
-
cdk.context.json
|
|
119
|
-
|
|
120
|
-
!/**/.gitkeep
|
|
121
|
-
`,
|
|
122
|
-
'.npmrc': 'engine-strict=true\n',
|
|
123
|
-
'.nvmrc': 'lts/krypton\n',
|
|
124
|
-
'.yarnrc.yml': 'nodeLinker: node-modules\nnpmMinimalAgeGate: 0\n',
|
|
125
|
-
'.husky/commit-msg': '#!/bin/sh\n',
|
|
126
|
-
'.husky/post-checkout': '#!/bin/sh\n\nyarn\n',
|
|
127
|
-
'.husky/post-merge': '#!/bin/sh\n\nyarn\n',
|
|
128
|
-
'.husky/pre-commit': '#!/bin/sh\n\nyarn check\n',
|
|
129
|
-
'.husky/pre-push': '#!/bin/sh\n\nyarn check\n',
|
|
130
|
-
'.storybook/main.ts': `import config from '@vyriy/storybook-config';
|
|
131
|
-
import { path } from '@vyriy/path';
|
|
132
|
-
|
|
133
|
-
export default {
|
|
134
|
-
...config,
|
|
135
|
-
stories: [
|
|
136
|
-
path('**/*.mdx'),
|
|
137
|
-
path('**/*.stories.@(js|jsx|mjs|ts|tsx)'),
|
|
138
|
-
],
|
|
139
|
-
};
|
|
140
|
-
`,
|
|
141
|
-
'.storybook/preview.tsx': "export { default } from '@vyriy/storybook-config/preview';\n",
|
|
142
|
-
'yarn.lock': '',
|
|
143
|
-
'tsconfig.json': JSON.stringify({
|
|
144
|
-
extends: '@vyriy/typescript-config/index.json',
|
|
145
|
-
include: [
|
|
146
|
-
'.storybook/**/*.ts',
|
|
147
|
-
'.storybook/**/*.tsx',
|
|
148
|
-
'packages/**/*.ts',
|
|
149
|
-
'packages/**/*.tsx',
|
|
150
|
-
'workspaces/**/*.ts',
|
|
151
|
-
'workspaces/**/*.tsx',
|
|
152
|
-
'*.ts',
|
|
153
|
-
],
|
|
154
|
-
}, null, 2) + '\n',
|
|
155
|
-
'prettier.config.ts': "export { default } from '@vyriy/prettier-config';\n",
|
|
156
|
-
'.prettierignore': 'node_modules\ndist\ncoverage\nstorybook-static\nconsumer\n',
|
|
157
|
-
'eslint.config.ts': "export { default } from '@vyriy/eslint-config';\n",
|
|
158
|
-
'jest.config.ts': "export { default } from '@vyriy/jest-config';\n",
|
|
159
|
-
});
|
|
@@ -1,158 +0,0 @@
|
|
|
1
|
-
import packageJson from '../../../package.json' with { type: 'json' };
|
|
2
|
-
import { mfe } from './mfe.js';
|
|
3
|
-
const mfeOnlyPaths = [
|
|
4
|
-
'packages/api/',
|
|
5
|
-
'packages/event/',
|
|
6
|
-
'packages/query/',
|
|
7
|
-
'workspaces/api/index.ts',
|
|
8
|
-
'workspaces/api/index.test.ts',
|
|
9
|
-
'workspaces/static/public/icon.svg',
|
|
10
|
-
'workspaces/static/public/screenshots/',
|
|
11
|
-
];
|
|
12
|
-
const getSharedFiles = (options) => Object.fromEntries(Object.entries(mfe(options)).filter(([file]) => !mfeOnlyPaths.some((path) => file.startsWith(path))));
|
|
13
|
-
const projectFiles = {
|
|
14
|
-
'.browserslistrc': '[development]\nextends @vyriy/browserslist-config\n\n[ssr]\nextends @vyriy/browserslist-config\n\n[production]\nextends @vyriy/browserslist-config\n\n[modern]\nextends @vyriy/browserslist-config\n',
|
|
15
|
-
'.storybook/preview.tsx': "import '../packages/components/styles.scss';\n\nexport { default } from '@vyriy/storybook-config/preview';\n",
|
|
16
|
-
'packages/env/env.test.ts': "import { afterEach, describe, expect, it } from '@jest/globals';\n\nimport { getApi, getCdn, getUi } from './env.js';\n\ndescribe('env getters', () => {\n afterEach(() => {\n delete process.env.API;\n delete process.env.CDN;\n delete process.env.UI;\n });\n\n it('reads required environment values', () => {\n process.env.API = 'http://localhost:3000';\n process.env.CDN = 'http://localhost:3001';\n process.env.UI = 'http://localhost:3002';\n\n expect(getApi()).toBe('http://localhost:3000');\n expect(getCdn()).toBe('http://localhost:3001');\n expect(getUi()).toBe('http://localhost:3002');\n });\n\n it('throws when a required environment value is missing', () => {\n expect(() => getUi()).toThrow('Environment variable UI is not defined!');\n });\n});\n",
|
|
17
|
-
'packages/env/env.ts': "import { getEnv } from '@vyriy/env';\n\n/** Reads the API origin used for server endpoints. */\nexport const getApi = () => getEnv('API');\n\n/** Reads the CDN origin used for static assets. */\nexport const getCdn = () => getEnv('CDN');\n\n/** Reads the UI origin used for browser assets. */\nexport const getUi = () => getEnv('UI');\n",
|
|
18
|
-
'packages/env/index.test.ts': "import { afterEach, describe, expect, it } from '@jest/globals';\n\nimport * as publicApi from './index.js';\n\nconst ENV_NAMES = [\n 'API',\n 'CDN',\n 'UI',\n] as const;\n\nconst clearEnv = () => {\n for (const name of ENV_NAMES) {\n delete process.env[name];\n }\n};\n\ndescribe('env public API', () => {\n afterEach(() => {\n clearEnv();\n });\n\n it('exports env getters', () => {\n expect(publicApi.getApi).toBeDefined();\n expect(publicApi.getCdn).toBeDefined();\n expect(publicApi.getUi).toBeDefined();\n });\n\n it('reads environment variables by public getter name', () => {\n process.env.API = 'http://localhost:3000';\n process.env.CDN = 'http://localhost:3001';\n process.env.UI = 'http://localhost:3002';\n\n expect(publicApi.getApi()).toBe('http://localhost:3000');\n expect(publicApi.getCdn()).toBe('http://localhost:3001');\n expect(publicApi.getUi()).toBe('http://localhost:3002');\n });\n\n it('throws when a required environment variable is missing', () => {\n clearEnv();\n\n expect(() => publicApi.getApi()).toThrow('Environment variable API is not defined!');\n });\n});\n",
|
|
19
|
-
'packages/env/README.md': '# @p/env\n\nRequired environment readers shared by API and UI workspaces.\n\n## Exports\n\n- `getApi()` reads `API`.\n- `getCdn()` reads `CDN`.\n- `getUi()` reads `UI`.\n\nEach getter throws when its environment variable is missing.\n',
|
|
20
|
-
'workspaces/api/index.test.tsx': "import { describe, expect, it, jest } from '@jest/globals';\nimport type { APIGatewayProxyEvent } from '@vyriy/router';\n\nconst apiMock = jest.fn((handler) => ({ handler }));\nconst serverMock = jest.fn();\n\njest.mock('@vyriy/handler', () => ({\n api: apiMock,\n}));\n\njest.mock('@vyriy/server', () => ({\n server: serverMock,\n}));\n\njest.mock('@p/env', () => ({\n getUi: () => 'http://localhost:3002',\n}));\n\ndescribe('workspaces/api/index.tsx', () => {\n type ApiHandler = (event: APIGatewayProxyEvent) => Promise<{\n body: string;\n headers?: Record<string, string>;\n statusCode: number;\n }>;\n\n const getEvent = (path: string): APIGatewayProxyEvent =>\n ({\n body: null,\n headers: {},\n httpMethod: 'GET',\n path,\n pathParameters: null,\n queryStringParameters: null,\n }) as APIGatewayProxyEvent;\n\n const loadHandler = async (): Promise<ApiHandler> => {\n await jest.isolateModulesAsync(async () => {\n await import('./index.js');\n });\n\n expect(apiMock).toHaveBeenCalledTimes(1);\n expect(serverMock).toHaveBeenCalledTimes(1);\n expect(serverMock).toHaveBeenCalledWith(apiMock.mock.results[0]?.value);\n\n return apiMock.mock.calls[0]?.[0] as ApiHandler;\n };\n\n it('starts the server with the API handler', async () => {\n await loadHandler();\n\n expect(apiMock).toHaveBeenCalledTimes(1);\n });\n\n it('renders the demo page for the root route', async () => {\n const handler = await loadHandler();\n const response = await handler(getEvent('/'));\n\n expect(response).toEqual({\n body: expect.any(String),\n headers: {\n 'access-control-allow-origin': '*',\n 'cache-control': 'public, max-age=300, s-maxage=3600, stale-while-revalidate=86400',\n 'content-type': 'text/html; charset=utf-8',\n 'x-content-type-options': 'nosniff',\n },\n isBase64Encoded: undefined,\n multiValueHeaders: undefined,\n statusCode: 200,\n });\n expect(response.body).toContain('<title>Demo</title>');\n expect(response.body).toContain('href=\"http://localhost:3002/main.css\"');\n expect(response.body).toContain('<div id=\"root\" rendered>');\n expect(response.body).toContain('Developer');\n expect(response.body).toContain('Senior IT Professional');\n expect(response.body).toContain('http://localhost:3001/avatar.svg');\n expect(response.body).toContain('src=\"http://localhost:3002/index.js\"');\n });\n\n it('returns not found for unknown routes', async () => {\n const handler = await loadHandler();\n\n await expect(handler(getEvent('/missing'))).resolves.toEqual({\n body: JSON.stringify({\n message: 'Not Found',\n }),\n statusCode: 404,\n });\n });\n});\n",
|
|
21
|
-
'workspaces/api/index.tsx': "import { server } from '@vyriy/server';\nimport { api } from '@vyriy/handler';\nimport { createRouter } from '@vyriy/router';\nimport { minify, html } from '@vyriy/html';\nimport { html as react } from '@vyriy/render';\n\nimport { ProfileCard } from '@p/components/profile-card';\nimport { getUi } from '@p/env';\n\nserver(\n api(async (event) =>\n createRouter()\n .get('/', () => ({\n body: minify(\n html({\n htmlAttributes: 'lang=\"en\"',\n title: '<title>Demo</title>',\n meta: '<meta charset=\"utf-8\" /><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />',\n link: `<link rel=\"stylesheet\" type=\"text/css\" href=\"${getUi()}/main.css\" />`,\n body: `<div id=\"root\" rendered>${react(\n <ProfileCard\n name=\"Developer\"\n title=\"Senior IT Professional\"\n avatarUrl=\"http://localhost:3001/avatar.svg\"\n />,\n )}</div>`,\n script: `<script defer=\"defer\" src=\"${getUi()}/index.js\"></script>`,\n }),\n ),\n headers: {\n 'content-type': 'text/html; charset=utf-8',\n 'cache-control': 'public, max-age=300, s-maxage=3600, stale-while-revalidate=86400',\n 'access-control-allow-origin': '*',\n 'x-content-type-options': 'nosniff',\n },\n }))\n .route(event),\n ),\n);\n",
|
|
22
|
-
'workspaces/api/webpack.config.ts': "import { EnvironmentPlugin } from 'webpack';\nimport { path } from '@vyriy/path';\nimport { ssr, external } from '@vyriy/webpack-config';\n\nexport default ssr(\n '@w/api',\n {\n path: path('dist', 'api'),\n filename: 'index.js',\n library: { type: 'commonjs2' },\n },\n (config) => ({\n ...config,\n externals: [external({ allowlist: [/^@p/, /^@w/, /^@vyriy/] })],\n plugins: [\n ...(config.plugins ?? []),\n new EnvironmentPlugin([\n 'API',\n 'CDN',\n 'UI',\n ]),\n ],\n }),\n);\n",
|
|
23
|
-
'workspaces/env.sh': '#!/usr/bin/env sh\n\n: "${API_PORT:=3000}"\n: "${CDN_PORT:=3001}"\n: "${UI_PORT:=3002}"\n: "${API:=http://localhost:$API_PORT}"\n: "${CDN:=http://localhost:$CDN_PORT}"\n: "${UI:=http://localhost:$UI_PORT}"\n\nexport API_PORT\nexport CDN_PORT\nexport UI_PORT\nexport API\nexport CDN\nexport UI\n',
|
|
24
|
-
'workspaces/static/README.md': '# @w/static\n\nStatic asset workspace for the profile-card UI.\n\n## Assets\n\n- `avatar.svg` is the default demo avatar.\n\nThe workspace is served as the CDN origin during local development.\n',
|
|
25
|
-
'workspaces/ui/index.test.tsx': "import { describe, expect, it, jest } from '@jest/globals';\nimport { isValidElement } from 'react';\nimport type { ReactElement } from 'react';\n\nconst elementMock = jest.fn();\n\njest.mock('@vyriy/render/element', () => ({\n element: elementMock,\n}));\n\ntype ProfileCardProps = {\n avatarUrl: string;\n name: string;\n title: string;\n};\n\ndescribe('workspaces/ui/index.tsx', () => {\n const loadEntry = async () => {\n const root = document.createElement('div');\n root.id = 'root';\n document.body.replaceChildren();\n document.body.append(root);\n\n await jest.isolateModulesAsync(async () => {\n await import('./index.js');\n });\n\n const [{ component }] = elementMock.mock.calls[0] as [{ component: ReactElement<ProfileCardProps> }];\n\n return {\n root,\n component,\n };\n };\n\n it('mounts the UI into the root element', async () => {\n const { root, component } = await loadEntry();\n\n expect(elementMock).toHaveBeenCalledTimes(1);\n expect(elementMock).toHaveBeenCalledWith({\n root,\n component,\n });\n });\n\n it('renders the profile card demo component', async () => {\n const { component } = await loadEntry();\n\n expect(isValidElement(component)).toBe(true);\n expect(typeof component.type).toBe('function');\n expect((component.type as { name?: string }).name).toBe('ProfileCard');\n expect(component.props).toEqual({\n avatarUrl: 'http://localhost:3001/avatar.svg',\n name: 'Developer',\n title: 'Senior IT Professional',\n });\n });\n});\n",
|
|
26
|
-
'workspaces/ui/index.tsx': "import { element } from '@vyriy/render/element';\n\nimport { ProfileCard } from '@p/components/profile-card';\nimport '@p/components/styles.scss';\n\nelement({\n root: document.getElementById('root'),\n component: (\n <ProfileCard name=\"Developer\" title=\"Senior IT Professional\" avatarUrl=\"http://localhost:3001/avatar.svg\" />\n ),\n});\n",
|
|
27
|
-
'workspaces/ui/webpack.config.ts': "import { EnvironmentPlugin } from 'webpack';\n\nimport { csr, html } from '@vyriy/webpack-config';\nimport { path } from '@vyriy/path';\n\nexport default csr(\n '@w/ui',\n {\n path: path('dist', 'cdn'),\n filename: 'index.js',\n },\n (config) => ({\n ...config,\n plugins: [\n ...(config.plugins ?? []),\n new EnvironmentPlugin(['API', 'CDN', 'UI']),\n html({\n htmlAttributes: 'lang=\"en\"',\n title: '<title>Demo</title>',\n meta: '<meta charset=\"utf-8\" /><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />',\n body: '<div id=\"root\"></div>',\n }),\n ],\n }),\n);\n",
|
|
28
|
-
};
|
|
29
|
-
export const fullstack = (options) => ({
|
|
30
|
-
...getSharedFiles(options),
|
|
31
|
-
...projectFiles,
|
|
32
|
-
'package.json': JSON.stringify({
|
|
33
|
-
name: options.name,
|
|
34
|
-
version: '0.0.0',
|
|
35
|
-
description: options.description,
|
|
36
|
-
private: true,
|
|
37
|
-
type: 'module',
|
|
38
|
-
agents: './AGENTS.md',
|
|
39
|
-
packageManager: packageJson.packageManager,
|
|
40
|
-
engines: {
|
|
41
|
-
node: packageJson.engines.node,
|
|
42
|
-
},
|
|
43
|
-
workspaces: [
|
|
44
|
-
'packages/*',
|
|
45
|
-
'workspaces/*',
|
|
46
|
-
],
|
|
47
|
-
scripts: {
|
|
48
|
-
storybook: 'cross-env STORYBOOK_DISABLE_TELEMETRY=1 storybook dev -p 6006 --disable-telemetry',
|
|
49
|
-
check: 'run-s lint build test',
|
|
50
|
-
fix: "run-s 'fix:*'",
|
|
51
|
-
start: "run-p 'start:*'",
|
|
52
|
-
lint: "run-s 'lint:*'",
|
|
53
|
-
build: "run-s 'build:*'",
|
|
54
|
-
test: "run-s 'test:*'",
|
|
55
|
-
'fix:prettier': 'prettier . --write',
|
|
56
|
-
'fix:eslint': 'eslint . --fix',
|
|
57
|
-
'fix:stylelint': "stylelint '**/*.{css,scss}' --fix",
|
|
58
|
-
'start:api': 'sh workspaces/api/bin/start.sh',
|
|
59
|
-
'start:static': 'sh workspaces/static/bin/start.sh',
|
|
60
|
-
'start:ui': 'sh workspaces/ui/bin/start.sh',
|
|
61
|
-
'lint:ts': 'tsc',
|
|
62
|
-
'lint:prettier': 'prettier . --check',
|
|
63
|
-
'lint:eslint': 'eslint .',
|
|
64
|
-
'lint:stylelint': "stylelint '**/*.{css,scss}'",
|
|
65
|
-
'build:api': 'sh workspaces/api/bin/build.sh',
|
|
66
|
-
'build:ui': 'sh workspaces/ui/bin/build.sh',
|
|
67
|
-
'build:static': 'sh workspaces/static/bin/build.sh',
|
|
68
|
-
'build:storybook': 'cross-env STORYBOOK_DISABLE_TELEMETRY=1 storybook build --quiet --disable-telemetry',
|
|
69
|
-
'test:jest': 'jest',
|
|
70
|
-
prebuild: 'rimraf dist',
|
|
71
|
-
postinstall: 'husky',
|
|
72
|
-
},
|
|
73
|
-
dependencies: {
|
|
74
|
-
'@testing-library/dom': packageJson.peerDependencies['@testing-library/dom'],
|
|
75
|
-
'@testing-library/react': packageJson.peerDependencies['@testing-library/react'],
|
|
76
|
-
'@types/jest': packageJson.peerDependencies['@types/jest'],
|
|
77
|
-
'@vyriy/browserslist-config': `^${packageJson.version}`,
|
|
78
|
-
'@vyriy/cn': `^${packageJson.version}`,
|
|
79
|
-
'@vyriy/env': `^${packageJson.version}`,
|
|
80
|
-
'@vyriy/eslint-config': `^${packageJson.version}`,
|
|
81
|
-
'@vyriy/handler': `^${packageJson.version}`,
|
|
82
|
-
'@vyriy/html': `^${packageJson.version}`,
|
|
83
|
-
'@vyriy/jest-config': `^${packageJson.version}`,
|
|
84
|
-
'@vyriy/path': `^${packageJson.version}`,
|
|
85
|
-
'@vyriy/prettier-config': `^${packageJson.version}`,
|
|
86
|
-
'@vyriy/render': `^${packageJson.version}`,
|
|
87
|
-
'@vyriy/router': `^${packageJson.version}`,
|
|
88
|
-
'@vyriy/server': `^${packageJson.version}`,
|
|
89
|
-
'@vyriy/storybook-config': `^${packageJson.version}`,
|
|
90
|
-
'@vyriy/stylelint-config': `^${packageJson.version}`,
|
|
91
|
-
'@vyriy/typescript-config': `^${packageJson.version}`,
|
|
92
|
-
'@vyriy/webpack-config': `^${packageJson.version}`,
|
|
93
|
-
'cross-env': packageJson.peerDependencies['cross-env'],
|
|
94
|
-
eslint: packageJson.peerDependencies.eslint,
|
|
95
|
-
husky: packageJson.peerDependencies.husky,
|
|
96
|
-
jest: packageJson.peerDependencies.jest,
|
|
97
|
-
'npm-run-all2': packageJson.peerDependencies['npm-run-all2'],
|
|
98
|
-
prettier: packageJson.peerDependencies.prettier,
|
|
99
|
-
rimraf: packageJson.peerDependencies.rimraf,
|
|
100
|
-
serve: packageJson.peerDependencies.serve,
|
|
101
|
-
storybook: packageJson.peerDependencies.storybook,
|
|
102
|
-
stylelint: packageJson.peerDependencies.stylelint,
|
|
103
|
-
tsx: packageJson.peerDependencies.tsx,
|
|
104
|
-
typescript: packageJson.peerDependencies.typescript,
|
|
105
|
-
webpack: packageJson.peerDependencies.webpack,
|
|
106
|
-
'webpack-cli': packageJson.peerDependencies['webpack-cli'],
|
|
107
|
-
},
|
|
108
|
-
}, null, 2) + '\n',
|
|
109
|
-
'README.md': `# ${options.name}
|
|
110
|
-
|
|
111
|
-
${options.description}
|
|
112
|
-
|
|
113
|
-
## Setup
|
|
114
|
-
|
|
115
|
-
\`\`\`bash
|
|
116
|
-
yarn install
|
|
117
|
-
\`\`\`
|
|
118
|
-
|
|
119
|
-
## Start
|
|
120
|
-
|
|
121
|
-
Start the API, static asset server, and UI dev server together:
|
|
122
|
-
|
|
123
|
-
\`\`\`bash
|
|
124
|
-
yarn start
|
|
125
|
-
\`\`\`
|
|
126
|
-
|
|
127
|
-
Start individual workspaces:
|
|
128
|
-
|
|
129
|
-
\`\`\`bash
|
|
130
|
-
yarn start:api
|
|
131
|
-
yarn start:static
|
|
132
|
-
yarn start:ui
|
|
133
|
-
\`\`\`
|
|
134
|
-
|
|
135
|
-
## Local URLs
|
|
136
|
-
|
|
137
|
-
Default ports are defined in \`workspaces/env.sh\`:
|
|
138
|
-
|
|
139
|
-
- API: \`http://localhost:3000\`
|
|
140
|
-
- Static/CDN assets: \`http://localhost:3001\`
|
|
141
|
-
- UI dev server: \`http://localhost:3002\`
|
|
142
|
-
|
|
143
|
-
## Validation
|
|
144
|
-
|
|
145
|
-
\`\`\`bash
|
|
146
|
-
yarn lint
|
|
147
|
-
yarn test
|
|
148
|
-
yarn build
|
|
149
|
-
\`\`\`
|
|
150
|
-
`,
|
|
151
|
-
'doc.mdx': `import { Meta, Markdown } from '@storybook/addon-docs/blocks';
|
|
152
|
-
import ReadMe from './README.md?raw';
|
|
153
|
-
|
|
154
|
-
<Meta title="${options.name}" />
|
|
155
|
-
|
|
156
|
-
<Markdown>{ReadMe}</Markdown>
|
|
157
|
-
`,
|
|
158
|
-
});
|