@vesk/adapter 0.0.1

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.
Files changed (60) hide show
  1. package/README.md +21 -0
  2. package/dist/api-function.d.ts +7 -0
  3. package/dist/api-function.d.ts.map +1 -0
  4. package/dist/api-function.js +187 -0
  5. package/dist/client-bundle.d.ts +19 -0
  6. package/dist/client-bundle.d.ts.map +1 -0
  7. package/dist/client-bundle.js +491 -0
  8. package/dist/dev-server.d.ts +4 -0
  9. package/dist/dev-server.d.ts.map +1 -0
  10. package/dist/dev-server.js +358 -0
  11. package/dist/esbuild-fallback.d.ts +3 -0
  12. package/dist/esbuild-fallback.d.ts.map +1 -0
  13. package/dist/esbuild-fallback.js +64 -0
  14. package/dist/hmr.d.ts +7 -0
  15. package/dist/hmr.d.ts.map +1 -0
  16. package/dist/hmr.js +411 -0
  17. package/dist/image-pipeline.d.ts +3 -0
  18. package/dist/image-pipeline.d.ts.map +1 -0
  19. package/dist/image-pipeline.js +120 -0
  20. package/dist/index.d.ts +4 -0
  21. package/dist/index.d.ts.map +1 -0
  22. package/dist/index.js +291 -0
  23. package/dist/manifest.d.ts +3 -0
  24. package/dist/manifest.d.ts.map +1 -0
  25. package/dist/manifest.js +47 -0
  26. package/dist/middleware.d.ts +4 -0
  27. package/dist/middleware.d.ts.map +1 -0
  28. package/dist/middleware.js +96 -0
  29. package/dist/package.json +15 -0
  30. package/dist/platform-deploy.d.ts +18 -0
  31. package/dist/platform-deploy.d.ts.map +1 -0
  32. package/dist/platform-deploy.js +354 -0
  33. package/dist/platform-handler.d.ts +32 -0
  34. package/dist/platform-handler.d.ts.map +1 -0
  35. package/dist/platform-handler.js +211 -0
  36. package/dist/platform-output.d.ts +30 -0
  37. package/dist/platform-output.d.ts.map +1 -0
  38. package/dist/platform-output.js +119 -0
  39. package/dist/platform.d.ts +17 -0
  40. package/dist/platform.d.ts.map +1 -0
  41. package/dist/platform.js +35 -0
  42. package/dist/prod-server.d.ts +5 -0
  43. package/dist/prod-server.d.ts.map +1 -0
  44. package/dist/prod-server.js +429 -0
  45. package/dist/runtime-bundle.d.ts +2 -0
  46. package/dist/runtime-bundle.d.ts.map +1 -0
  47. package/dist/runtime-bundle.js +140 -0
  48. package/dist/seo-audit.d.ts +3 -0
  49. package/dist/seo-audit.d.ts.map +1 -0
  50. package/dist/seo-audit.js +169 -0
  51. package/dist/ssr-function.d.ts +8 -0
  52. package/dist/ssr-function.d.ts.map +1 -0
  53. package/dist/ssr-function.js +415 -0
  54. package/dist/static.d.ts +8 -0
  55. package/dist/static.d.ts.map +1 -0
  56. package/dist/static.js +130 -0
  57. package/dist/types.d.ts +182 -0
  58. package/dist/types.d.ts.map +1 -0
  59. package/dist/types.js +1 -0
  60. package/package.json +54 -0
@@ -0,0 +1,140 @@
1
+ import { writeFileSync, existsSync, unlinkSync } from 'node:fs';
2
+ import { resolve, dirname, join } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ import * as esbuild from './esbuild-fallback.js';
5
+ let buildId = 0;
6
+ const __dirname = dirname(fileURLToPath(import.meta.url));
7
+ function findCompilerSrc(appDir) {
8
+ const monorepoRoot = resolve(__dirname, '..', '..', '..');
9
+ const candidates = [
10
+ resolve(monorepoRoot, 'packages', 'compiler', 'dist'),
11
+ resolve(appDir, '..', 'node_modules', '@vesk/compiler'),
12
+ resolve(appDir, 'node_modules', '@vesk/compiler'),
13
+ ];
14
+ for (const base of candidates) {
15
+ for (const dir of [base, join(base, 'dist')]) {
16
+ if (existsSync(join(dir, 'server-codegen.js')))
17
+ return dir;
18
+ }
19
+ }
20
+ throw new Error('@vesk/compiler/dist not found — run "npm run build" first');
21
+ }
22
+ function findRuntimeSrc(appDir) {
23
+ const monorepoRoot = resolve(__dirname, '..', '..', '..');
24
+ const candidates = [
25
+ resolve(monorepoRoot, 'packages', 'runtime', 'dist'),
26
+ resolve(appDir, '..', 'node_modules', '@vesk/runtime'),
27
+ resolve(appDir, 'node_modules', '@vesk/runtime'),
28
+ ];
29
+ for (const base of candidates) {
30
+ for (const dir of [base, join(base, 'dist')]) {
31
+ if (existsSync(join(dir, 'index-server.js')))
32
+ return dir;
33
+ }
34
+ }
35
+ throw new Error('@vesk/runtime/dist not found — run "npm run build" first');
36
+ }
37
+ export async function bundleRuntime(appDir, outDir) {
38
+ const compilerRoot = findCompilerSrc(appDir);
39
+ const runtimeRoot = findRuntimeSrc(appDir);
40
+ const entryFile = resolve(outDir, 'server', `.runtime-entry-${buildId++}.mjs`);
41
+ const entryContent = [
42
+ `import { renderPage, renderFullPage, renderPageStream, compileFile, setRuntimeModule, setVskHydrate } from ${JSON.stringify(resolve(compilerRoot, 'server-codegen.js'))};`,
43
+ `import { parseCookies } from ${JSON.stringify(resolve(compilerRoot, 'server-cookies.js'))};`,
44
+ `import * as __veskRuntime from ${JSON.stringify(resolve(runtimeRoot, 'index-server.js'))};`,
45
+ '',
46
+ '// Inject runtime module so server-codegen can find components like NavLink, Link, etc.',
47
+ 'setRuntimeModule(__veskRuntime);',
48
+ '',
49
+ '// Server-side runtime hooks — read from globalThis.__vesk_request',
50
+ 'export function cookies() {',
51
+ ' const req = globalThis.__vesk_request;',
52
+ " if (!req) return { get: () => null, getAll: () => [], set: () => {}, delete: () => {} };",
53
+ ' const c = req.cookies || {};',
54
+ ' return {',
55
+ ' get: (name) => c[name] || null,',
56
+ ' getAll: () => Object.entries(c).map(([n, v]) => ({ name: n, value: v })),',
57
+ ' set: () => {},',
58
+ ' delete: () => {},',
59
+ ' };',
60
+ '}',
61
+ '',
62
+ 'export function headers() {',
63
+ ' const req = globalThis.__vesk_request;',
64
+ ' if (!req) return new Map();',
65
+ ' const h = req.headers || {};',
66
+ ' const m = new Map();',
67
+ ' for (const [k, v] of Object.entries(h)) m.set(k.toLowerCase(), String(v));',
68
+ ' m.get = m.get.bind(m);',
69
+ ' m.has = m.has.bind(m);',
70
+ ' m.forEach = m.forEach.bind(m);',
71
+ ' return m;',
72
+ '}',
73
+ '',
74
+ 'export function locals() {',
75
+ ' const req = globalThis.__vesk_request;',
76
+ " if (!req) return {};",
77
+ ' return req.locals || {};',
78
+ '}',
79
+ '',
80
+ 'export { renderPage, renderFullPage, renderPageStream, compileFile, setVskHydrate, parseCookies };',
81
+ 'export { withSsrStore } from "@vesk/compiler/src/ssr-store";',
82
+ '',
83
+ '// Deliver hydration data as an origin-served script so strict CSP (no unsafe-inline)',
84
+ '// does not block it. The prod server serves /ssr-data.js from the global store.',
85
+ 'export function storeDataScriptGlobal(payload) {',
86
+ " if (!payload || (!payload.props && !payload.ssrData)) return null;",
87
+ " const token = Math.random().toString(36).slice(2) + Date.now().toString(36);",
88
+ " const store = (globalThis.__vsk_ssr_data_store ||= {});",
89
+ " store[token] = payload;",
90
+ ' // Bound the store: if the browser never fetches /ssr-data.js the entry',
91
+ ' // would otherwise linger forever. Evict the oldest entry past 100.',
92
+ " const keys = Object.keys(store);",
93
+ ' if (keys.length > 100) {',
94
+ ' for (let i = 0; i < keys.length - 100; i++) {',
95
+ ' delete store[keys[i]];',
96
+ ' }',
97
+ ' }',
98
+ " return '/ssr-data.js?t=' + token;",
99
+ '}',
100
+ '',
101
+ '// Re-export runtime classes used by API routes',
102
+ 'export const VeskRequest = __veskRuntime.VeskRequest;',
103
+ 'export const VeskResponse = __veskRuntime.VeskResponse;',
104
+ '',
105
+ '// Server actions',
106
+ 'export const defineAction = __veskRuntime.defineAction;',
107
+ 'export const getAction = __veskRuntime.getAction;',
108
+ 'export const clearActions = __veskRuntime.clearActions;',
109
+ 'export const validateActionInput = __veskRuntime.validateActionInput;',
110
+ 'export const issuesToFieldMap = __veskRuntime.issuesToFieldMap;',
111
+ ].join('\n');
112
+ writeFileSync(entryFile, entryContent, 'utf-8');
113
+ try {
114
+ const result = await esbuild.build({
115
+ entryPoints: [entryFile],
116
+ bundle: true,
117
+ platform: 'neutral',
118
+ format: 'esm',
119
+ minify: true,
120
+ outfile: resolve(outDir, 'server', 'runtime.js'),
121
+ external: ['fs', 'node:fs', 'path', 'node:path', 'node:async_hooks'],
122
+ target: ['es2022'],
123
+ treeShaking: true,
124
+ });
125
+ if (result.errors.length > 0) {
126
+ throw new Error(`esbuild errors: ${result.errors.map((e) => e.text).join(', ')}`);
127
+ }
128
+ if (result.warnings.length > 0) {
129
+ for (const w of result.warnings)
130
+ console.error('vesk build warning:', w.text);
131
+ }
132
+ return resolve(outDir, 'server', 'runtime.js');
133
+ }
134
+ finally {
135
+ try {
136
+ unlinkSync(entryFile);
137
+ }
138
+ catch { /* ignore */ }
139
+ }
140
+ }
@@ -0,0 +1,3 @@
1
+ import type { SeoAuditResult } from '@vesk/adapter/src/types';
2
+ export declare function runSeoAudit(appDir: string, _options?: Record<string, unknown>): SeoAuditResult;
3
+ //# sourceMappingURL=seo-audit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"seo-audit.d.ts","sourceRoot":"","sources":["../src/seo-audit.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAiB,cAAc,EAAoB,MAAM,yBAAyB,CAAC;AAgI/F,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,cAAc,CA8C9F"}
@@ -0,0 +1,169 @@
1
+ import { readFileSync, existsSync, readdirSync, statSync } from 'node:fs';
2
+ import { resolve } from 'node:path';
3
+ const SEVERITY = { WARN: 'warn', ERROR: 'error' };
4
+ function walkFiles(dir) {
5
+ const results = [];
6
+ let entries;
7
+ try {
8
+ entries = readdirSync(dir);
9
+ }
10
+ catch {
11
+ return results;
12
+ }
13
+ for (const entry of entries) {
14
+ const full = resolve(dir, entry);
15
+ const st = statSync(full);
16
+ if (st.isDirectory()) {
17
+ if (!entry.startsWith('.'))
18
+ results.push(...walkFiles(full));
19
+ }
20
+ else if (entry === 'page.vsk' || entry === 'layout.vsk')
21
+ results.push(full);
22
+ }
23
+ return results;
24
+ }
25
+ function collectCombinedSource(appDir) {
26
+ const files = walkFiles(appDir);
27
+ const pages = files.filter(f => f.endsWith('/page.vsk') || f.endsWith('\\page.vsk'));
28
+ const combined = [];
29
+ for (const pagePath of pages) {
30
+ const dir = resolve(pagePath, '..');
31
+ const layoutPath = resolve(dir, 'layout.vsk');
32
+ const pageSrc = readFileSync(pagePath, 'utf-8');
33
+ const layoutSrc = existsSync(layoutPath) ? readFileSync(layoutPath, 'utf-8') : '';
34
+ const combinedSrc = layoutSrc ? layoutSrc + '\n' + pageSrc : pageSrc;
35
+ combined.push({
36
+ path: pagePath,
37
+ src: combinedSrc,
38
+ hasLayout: !!layoutSrc,
39
+ });
40
+ }
41
+ return combined;
42
+ }
43
+ const SEO_CHECKS = {
44
+ h1: (src) => {
45
+ const count = (src.match(/<h1[^>]*>/gi) || []).length;
46
+ const issues = [];
47
+ if (count === 0)
48
+ issues.push({ severity: SEVERITY.ERROR, message: 'Missing <h1> — each page needs exactly one' });
49
+ if (count > 1)
50
+ issues.push({ severity: SEVERITY.WARN, message: `Multiple <h1> (${count}) — should have exactly one` });
51
+ return issues;
52
+ },
53
+ altText: (src) => {
54
+ const issues = [];
55
+ const imgRegex = /<img[^>]+src=["']([^"']+)["'][^>]*>/gi;
56
+ let m;
57
+ while ((m = imgRegex.exec(src)) !== null) {
58
+ const tag = m[0];
59
+ if (!/alt\s*=/i.test(tag)) {
60
+ issues.push({ severity: SEVERITY.ERROR, message: `Image missing alt text: ${m[1]}` });
61
+ }
62
+ }
63
+ return issues;
64
+ },
65
+ imageAlt: (src) => {
66
+ const issues = [];
67
+ const imgRegex = /<Image\s+([^>]+)>/g;
68
+ let m;
69
+ while ((m = imgRegex.exec(src)) !== null) {
70
+ const tag = m[1];
71
+ if (!/alt\s*=/i.test(tag)) {
72
+ const srcMatch = tag.match(/src=["']([^"']+)["']/);
73
+ issues.push({ severity: SEVERITY.ERROR, message: `<Image> missing alt text: ${srcMatch ? srcMatch[1] : 'unknown'}` });
74
+ }
75
+ }
76
+ return issues;
77
+ },
78
+ metaDescription: (src) => {
79
+ if (!/name=["']description["']/.test(src)) {
80
+ return [{ severity: SEVERITY.WARN, message: 'Missing meta description' }];
81
+ }
82
+ return [];
83
+ },
84
+ ogTags: (src) => {
85
+ const issues = [];
86
+ const required = ['og:title', 'og:description', 'og:image'];
87
+ for (const tag of required) {
88
+ const pattern = new RegExp(`property=["']${tag}["']`);
89
+ if (!pattern.test(src)) {
90
+ issues.push({ severity: SEVERITY.WARN, message: `Missing Open Graph tag: ${tag}` });
91
+ }
92
+ }
93
+ return issues;
94
+ },
95
+ langAttr: (src) => {
96
+ if (!/<html[^>]*\slang=/i.test(src)) {
97
+ return [{ severity: SEVERITY.WARN, message: 'Missing lang attribute on <html>' }];
98
+ }
99
+ return [];
100
+ },
101
+ title: (src) => {
102
+ if (!/<title>/i.test(src) && !/<Head>/i.test(src) && !/title>/i.test(src)) {
103
+ return [{ severity: SEVERITY.ERROR, message: 'Missing <title> or <Head> — page title is critical for SEO' }];
104
+ }
105
+ return [];
106
+ },
107
+ headingOrder: (src) => {
108
+ const headings = [];
109
+ const hRegex = /<h(\d)[^>]*>/gi;
110
+ let m;
111
+ while ((m = hRegex.exec(src)) !== null)
112
+ headings.push(parseInt(m[1], 10));
113
+ const issues = [];
114
+ let expected = 1;
115
+ for (const level of headings) {
116
+ if (level > expected + 1) {
117
+ issues.push({ severity: SEVERITY.WARN, message: `Heading order skip: h${expected} → h${level}` });
118
+ }
119
+ expected = Math.max(expected, level);
120
+ }
121
+ return issues;
122
+ },
123
+ };
124
+ export function runSeoAudit(appDir, _options) {
125
+ const combined = collectCombinedSource(appDir);
126
+ if (combined.length === 0) {
127
+ console.error('vesk seo-audit: no page.vsk files found');
128
+ return { passed: 0, errors: 0, warnings: 0 };
129
+ }
130
+ let errors = 0;
131
+ let warnings = 0;
132
+ for (const { path, src } of combined) {
133
+ const relPath = path.replace(appDir, '').replace(/^[\\/]/, '');
134
+ const routeDir = relPath === 'page.vsk' ? '' : relPath.replace(/[\\/]page\.vsk$/, '');
135
+ const route = routeDir ? '/' + routeDir.replace(/\\/g, '/') : '/';
136
+ const label = relPath + (route === '/' ? ' (index)' : ` (route: ${route})`);
137
+ let fileErrors = 0;
138
+ let fileWarnings = 0;
139
+ const fileIssues = [];
140
+ for (const check of Object.values(SEO_CHECKS)) {
141
+ const issues = check(src);
142
+ for (const issue of issues) {
143
+ if (issue.severity === SEVERITY.ERROR) {
144
+ fileErrors++;
145
+ }
146
+ else {
147
+ fileWarnings++;
148
+ }
149
+ fileIssues.push(issue);
150
+ }
151
+ }
152
+ errors += fileErrors;
153
+ warnings += fileWarnings;
154
+ if (fileIssues.length === 0) {
155
+ console.error(` ✓ ${label}`);
156
+ }
157
+ else {
158
+ console.error(` ${fileErrors > 0 ? '✗' : '⚠'} ${label} (${fileErrors} errors, ${fileWarnings} warnings)`);
159
+ for (const issue of fileIssues) {
160
+ const prefix = issue.severity === SEVERITY.ERROR ? ' ✗' : ' ⚠';
161
+ console.error(` ${prefix} ${issue.message}`);
162
+ }
163
+ }
164
+ }
165
+ const total = combined.length;
166
+ const status = errors > 0 ? 'FAIL' : (warnings > 0 ? 'PASS_WARN' : 'PASS');
167
+ console.error(`vesk seo-audit: ${total} pages — ${errors} errors, ${warnings} warnings [${status}]`);
168
+ return { passed: total, errors, warnings };
169
+ }
@@ -0,0 +1,8 @@
1
+ import type { RouteNode, SsrFunctionOptions } from '@vesk/adapter/src/types';
2
+ export declare function resolveErrorFile(sourceDir: string, appDir: string): string | null;
3
+ export declare function generateSsrFunction(routeNode: RouteNode, appDir: string, outDir: string, componentMap?: Map<string, string>, options?: SsrFunctionOptions): {
4
+ funcPath: string;
5
+ funcCode: string;
6
+ name: string;
7
+ };
8
+ //# sourceMappingURL=ssr-function.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ssr-function.d.ts","sourceRoot":"","sources":["../src/ssr-function.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAkB,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAW7F,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAQjF;AAiDD,wBAAgB,mBAAmB,CACjC,SAAS,EAAE,SAAS,EACpB,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,YAAY,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EAClC,OAAO,CAAC,EAAE,kBAAkB,GAC3B;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CA8VtD"}