arcway 0.1.0
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/LICENSE +21 -0
- package/README.md +711 -0
- package/client/env.js +55 -0
- package/client/fetcher.js +50 -0
- package/client/graphql.js +35 -0
- package/client/head.js +140 -0
- package/client/hooks/use-api.js +80 -0
- package/client/hooks/use-debounce.js +12 -0
- package/client/hooks/use-form.js +86 -0
- package/client/hooks/use-graphql.js +30 -0
- package/client/hooks/use-interval.js +12 -0
- package/client/hooks/use-mutation.js +27 -0
- package/client/hooks/use-query.js +45 -0
- package/client/hooks/web/use-click-outside.js +22 -0
- package/client/hooks/web/use-local-storage.js +42 -0
- package/client/index.js +62 -0
- package/client/page-loader.js +155 -0
- package/client/provider.js +53 -0
- package/client/query.js +13 -0
- package/client/router.jsx +303 -0
- package/client/ui/accordion.jsx +65 -0
- package/client/ui/accordion.stories.jsx +48 -0
- package/client/ui/alert-dialog.jsx +122 -0
- package/client/ui/alert-dialog.stories.jsx +44 -0
- package/client/ui/alert.jsx +52 -0
- package/client/ui/alert.stories.jsx +31 -0
- package/client/ui/app-shell.jsx +39 -0
- package/client/ui/app-shell.stories.jsx +51 -0
- package/client/ui/aspect-ratio.jsx +6 -0
- package/client/ui/aspect-ratio.stories.jsx +69 -0
- package/client/ui/avatar.jsx +78 -0
- package/client/ui/avatar.stories.jsx +62 -0
- package/client/ui/badge.jsx +34 -0
- package/client/ui/badge.stories.js +32 -0
- package/client/ui/breadcrumb.jsx +86 -0
- package/client/ui/breadcrumb.stories.jsx +43 -0
- package/client/ui/button-group.jsx +58 -0
- package/client/ui/button-group.stories.jsx +67 -0
- package/client/ui/button.jsx +46 -0
- package/client/ui/button.stories.js +72 -0
- package/client/ui/calendar.jsx +172 -0
- package/client/ui/card.jsx +57 -0
- package/client/ui/card.stories.jsx +33 -0
- package/client/ui/carousel.jsx +167 -0
- package/client/ui/chart.jsx +244 -0
- package/client/ui/checkbox.jsx +24 -0
- package/client/ui/checkbox.stories.js +33 -0
- package/client/ui/collapsible.jsx +12 -0
- package/client/ui/collapsible.stories.jsx +42 -0
- package/client/ui/combobox.jsx +223 -0
- package/client/ui/command.jsx +128 -0
- package/client/ui/context-menu.jsx +170 -0
- package/client/ui/context-menu.stories.jsx +35 -0
- package/client/ui/dialog.jsx +109 -0
- package/client/ui/dialog.stories.jsx +37 -0
- package/client/ui/direction.jsx +9 -0
- package/client/ui/drawer.jsx +87 -0
- package/client/ui/dropdown-menu.jsx +172 -0
- package/client/ui/dropdown-menu.stories.jsx +34 -0
- package/client/ui/empty.jsx +76 -0
- package/client/ui/empty.stories.jsx +64 -0
- package/client/ui/field.jsx +174 -0
- package/client/ui/field.stories.jsx +118 -0
- package/client/ui/form.jsx +17 -0
- package/client/ui/hooks/use-mobile.js +16 -0
- package/client/ui/hover-card.jsx +26 -0
- package/client/ui/hover-card.stories.jsx +28 -0
- package/client/ui/index.js +649 -0
- package/client/ui/input-group.jsx +116 -0
- package/client/ui/input-group.stories.jsx +65 -0
- package/client/ui/input-otp.jsx +62 -0
- package/client/ui/input.jsx +16 -0
- package/client/ui/input.stories.js +27 -0
- package/client/ui/item.jsx +155 -0
- package/client/ui/item.stories.jsx +118 -0
- package/client/ui/kbd.jsx +24 -0
- package/client/ui/kbd.stories.jsx +32 -0
- package/client/ui/label.jsx +16 -0
- package/client/ui/label.stories.js +25 -0
- package/client/ui/lib/utils.js +6 -0
- package/client/ui/main-content.jsx +30 -0
- package/client/ui/menubar.jsx +189 -0
- package/client/ui/menubar.stories.jsx +43 -0
- package/client/ui/native-select.jsx +34 -0
- package/client/ui/native-select.stories.jsx +67 -0
- package/client/ui/navigation-menu.jsx +120 -0
- package/client/ui/navigation-menu.stories.jsx +45 -0
- package/client/ui/pagination.jsx +92 -0
- package/client/ui/pagination.stories.jsx +52 -0
- package/client/ui/panel.jsx +66 -0
- package/client/ui/popover.jsx +54 -0
- package/client/ui/popover.stories.jsx +27 -0
- package/client/ui/progress.jsx +19 -0
- package/client/ui/progress.stories.js +34 -0
- package/client/ui/radio-group.jsx +33 -0
- package/client/ui/radio-group.stories.jsx +49 -0
- package/client/ui/resizable.jsx +33 -0
- package/client/ui/scroll-area.jsx +41 -0
- package/client/ui/scroll-area.stories.jsx +43 -0
- package/client/ui/select.jsx +145 -0
- package/client/ui/select.stories.jsx +80 -0
- package/client/ui/separator.jsx +18 -0
- package/client/ui/separator.stories.jsx +37 -0
- package/client/ui/sheet.jsx +95 -0
- package/client/ui/sheet.stories.jsx +56 -0
- package/client/ui/sidebar.jsx +544 -0
- package/client/ui/skeleton.jsx +8 -0
- package/client/ui/skeleton.stories.js +23 -0
- package/client/ui/slider.jsx +41 -0
- package/client/ui/slider.stories.js +31 -0
- package/client/ui/sonner.jsx +37 -0
- package/client/ui/spinner.jsx +14 -0
- package/client/ui/spinner.stories.js +16 -0
- package/client/ui/style-mira.css +1316 -0
- package/client/ui/switch.jsx +22 -0
- package/client/ui/switch.stories.js +44 -0
- package/client/ui/table.jsx +33 -0
- package/client/ui/table.stories.jsx +42 -0
- package/client/ui/tabs.jsx +63 -0
- package/client/ui/tabs.stories.jsx +45 -0
- package/client/ui/textarea.jsx +15 -0
- package/client/ui/textarea.stories.js +33 -0
- package/client/ui/theme.css +459 -0
- package/client/ui/toggle-group.jsx +62 -0
- package/client/ui/toggle-group.stories.jsx +68 -0
- package/client/ui/toggle.jsx +34 -0
- package/client/ui/toggle.stories.js +46 -0
- package/client/ui/tooltip.jsx +37 -0
- package/client/ui/tooltip.stories.jsx +32 -0
- package/client/ui/use-transition.js +35 -0
- package/client/ws.js +132 -0
- package/package.json +134 -0
- package/server/bin/cli.js +42 -0
- package/server/bin/commands/build.js +23 -0
- package/server/bin/commands/dev.js +57 -0
- package/server/bin/commands/docs.js +30 -0
- package/server/bin/commands/graphql-schema.js +32 -0
- package/server/bin/commands/lint.js +35 -0
- package/server/bin/commands/mcp.js +26 -0
- package/server/bin/commands/migrate.js +82 -0
- package/server/bin/commands/schema.js +41 -0
- package/server/bin/commands/seed.js +36 -0
- package/server/bin/commands/start.js +31 -0
- package/server/bin/commands/test.js +20 -0
- package/server/bin/solo.js +4 -0
- package/server/boot/index.js +150 -0
- package/server/boot.js +2 -0
- package/server/build.js +23 -0
- package/server/cache/drivers/memory.js +23 -0
- package/server/cache/drivers/redis.js +28 -0
- package/server/cache/index.js +69 -0
- package/server/config/loader.js +89 -0
- package/server/config/modules/api.js +17 -0
- package/server/config/modules/build.js +9 -0
- package/server/config/modules/cache.js +10 -0
- package/server/config/modules/database.js +29 -0
- package/server/config/modules/events.js +15 -0
- package/server/config/modules/files.js +15 -0
- package/server/config/modules/jobs.js +20 -0
- package/server/config/modules/logger.js +9 -0
- package/server/config/modules/mail.js +11 -0
- package/server/config/modules/mcp.js +9 -0
- package/server/config/modules/pages.js +20 -0
- package/server/config/modules/queue.js +10 -0
- package/server/config/modules/redis.js +9 -0
- package/server/config/modules/server.js +30 -0
- package/server/config/modules/session.js +9 -0
- package/server/config/modules/websocket.js +11 -0
- package/server/constants.js +67 -0
- package/server/context.js +15 -0
- package/server/db/index.js +87 -0
- package/server/db/schema/drivers/mysql.js +28 -0
- package/server/db/schema/drivers/pg.js +34 -0
- package/server/db/schema/drivers/sqlite.js +22 -0
- package/server/db/schema/index.js +78 -0
- package/server/db/seeds.js +22 -0
- package/server/discovery.js +67 -0
- package/server/docs/openapi.js +153 -0
- package/server/env.js +17 -0
- package/server/events/drivers/memory.js +45 -0
- package/server/events/drivers/redis.js +64 -0
- package/server/events/handler.js +67 -0
- package/server/events/index.js +35 -0
- package/server/events/pattern.js +5 -0
- package/server/files/drivers/local.js +83 -0
- package/server/files/drivers/s3.js +113 -0
- package/server/files/index.js +57 -0
- package/server/filewatcher/index.js +156 -0
- package/server/glob.js +6 -0
- package/server/graphql/discovery.js +70 -0
- package/server/graphql/handler.js +41 -0
- package/server/graphql/index.js +13 -0
- package/server/graphql/loaders.js +19 -0
- package/server/graphql/merge.js +48 -0
- package/server/graphql/subscriptions.js +43 -0
- package/server/health.js +34 -0
- package/server/helpers.js +9 -0
- package/server/index.js +55 -0
- package/server/internals.js +139 -0
- package/server/jobs/cron.js +10 -0
- package/server/jobs/drivers/knex-queue.js +207 -0
- package/server/jobs/drivers/lease.js +148 -0
- package/server/jobs/drivers/memory-queue.js +134 -0
- package/server/jobs/queue.js +27 -0
- package/server/jobs/runner.js +197 -0
- package/server/jobs/throughput.js +63 -0
- package/server/lib/vault/encrypt.js +40 -0
- package/server/lib/vault/ids.js +9 -0
- package/server/lib/vault/index.js +14 -0
- package/server/lib/vault/jwt.js +55 -0
- package/server/lib/vault/password.js +10 -0
- package/server/lint/boundaries.js +77 -0
- package/server/logger/index.js +130 -0
- package/server/mail/drivers/console.js +31 -0
- package/server/mail/drivers/smtp.js +34 -0
- package/server/mail/imap.js +105 -0
- package/server/mail/inbound-store.js +58 -0
- package/server/mail/inbound.js +79 -0
- package/server/mail/index.js +112 -0
- package/server/mcp/debug-api.js +137 -0
- package/server/mcp/helpers.js +30 -0
- package/server/mcp/index.js +77 -0
- package/server/mcp/runtime.js +7 -0
- package/server/mcp/server.js +19 -0
- package/server/mcp/tools/debugging.js +133 -0
- package/server/mcp/tools/introspection.js +87 -0
- package/server/middlewares/cors.js +30 -0
- package/server/middlewares/index.js +3 -0
- package/server/middlewares/require-session.js +15 -0
- package/server/module-loader.js +9 -0
- package/server/pages/build-client.js +187 -0
- package/server/pages/build-css.js +47 -0
- package/server/pages/build-manifest.js +55 -0
- package/server/pages/build-plugins.js +75 -0
- package/server/pages/build-server.js +115 -0
- package/server/pages/build.js +116 -0
- package/server/pages/discovery.js +120 -0
- package/server/pages/fonts.js +128 -0
- package/server/pages/handler.js +276 -0
- package/server/pages/hmr.js +176 -0
- package/server/pages/pages-router.js +78 -0
- package/server/pages/ssr.js +276 -0
- package/server/pages/static.js +92 -0
- package/server/pages/watcher.js +90 -0
- package/server/queue/drivers/knex.js +67 -0
- package/server/queue/drivers/redis.js +91 -0
- package/server/queue/index.js +61 -0
- package/server/rate-limit/consume.js +21 -0
- package/server/rate-limit/drivers/memory.js +24 -0
- package/server/rate-limit/drivers/redis.js +32 -0
- package/server/rate-limit/index.js +33 -0
- package/server/redis/index.js +67 -0
- package/server/ring-buffer.js +44 -0
- package/server/route.js +4 -0
- package/server/router/api-router.js +317 -0
- package/server/router/cors.js +31 -0
- package/server/router/middleware.js +91 -0
- package/server/router/routes.js +132 -0
- package/server/server.js +35 -0
- package/server/session/helpers.js +21 -0
- package/server/session/index.js +89 -0
- package/server/static/index.js +36 -0
- package/server/system-jobs/index.js +50 -0
- package/server/system-routes/index.js +84 -0
- package/server/testing/index.js +263 -0
- package/server/validation.js +41 -0
- package/server/watcher.js +34 -0
- package/server/web-server.js +231 -0
- package/server/ws/discovery.js +54 -0
- package/server/ws/index.js +14 -0
- package/server/ws/realtime.js +318 -0
- package/server/ws/registry.js +17 -0
- package/server/ws/server.js +152 -0
- package/server/ws/ws-router.js +335 -0
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import fs from 'node:fs/promises';
|
|
3
|
+
import { createRequire } from 'node:module';
|
|
4
|
+
import esbuild from 'esbuild';
|
|
5
|
+
import { resolveLayoutChain, resolveLoadingChain } from './discovery.js';
|
|
6
|
+
import { clientIsolationPlugin } from './build-plugins.js';
|
|
7
|
+
import { patternToFileName, layoutDirToFileName } from './build-server.js';
|
|
8
|
+
import { reactRefreshPlugin } from './hmr.js';
|
|
9
|
+
async function buildClientBundles(
|
|
10
|
+
pages,
|
|
11
|
+
layouts,
|
|
12
|
+
outDir,
|
|
13
|
+
target,
|
|
14
|
+
minify,
|
|
15
|
+
rootDir,
|
|
16
|
+
loadings = [],
|
|
17
|
+
nodeEnv = 'production',
|
|
18
|
+
devMode = false,
|
|
19
|
+
) {
|
|
20
|
+
const clientDir = path.join(outDir, 'client');
|
|
21
|
+
const bundles = new Map();
|
|
22
|
+
const navBundles = new Map();
|
|
23
|
+
const layoutNavBundles = new Map();
|
|
24
|
+
const loadingNavBundles = new Map();
|
|
25
|
+
const entryPoints = {};
|
|
26
|
+
const tempDir = path.join(outDir, '.hydration-entries');
|
|
27
|
+
await fs.mkdir(tempDir, { recursive: true });
|
|
28
|
+
for (const page of pages) {
|
|
29
|
+
const outName = patternToFileName(page.pattern);
|
|
30
|
+
const layoutChain = resolveLayoutChain(page.pattern, layouts);
|
|
31
|
+
const loadingChain = resolveLoadingChain(page.pattern, loadings);
|
|
32
|
+
const entryContent = generateHydrationEntry(page.filePath, layoutChain, loadingChain);
|
|
33
|
+
const entryPath = path.join(tempDir, `${outName}.tsx`);
|
|
34
|
+
await fs.mkdir(path.dirname(entryPath), { recursive: true });
|
|
35
|
+
await fs.writeFile(entryPath, entryContent);
|
|
36
|
+
entryPoints[outName] = entryPath;
|
|
37
|
+
}
|
|
38
|
+
for (const page of pages) {
|
|
39
|
+
const navName = `nav_${patternToFileName(page.pattern)}`;
|
|
40
|
+
const importPath = page.filePath.replace(/\\/g, '/');
|
|
41
|
+
const navContent = `export { default } from '${importPath}';
|
|
42
|
+
`;
|
|
43
|
+
const navPath = path.join(tempDir, `${navName}.tsx`);
|
|
44
|
+
await fs.mkdir(path.dirname(navPath), { recursive: true });
|
|
45
|
+
await fs.writeFile(navPath, navContent);
|
|
46
|
+
entryPoints[navName] = navPath;
|
|
47
|
+
}
|
|
48
|
+
for (const layout of layouts) {
|
|
49
|
+
const layoutName = `nav_layout_${layoutDirToFileName(layout.dirPath)}`;
|
|
50
|
+
const importPath = layout.filePath.replace(/\\/g, '/');
|
|
51
|
+
const navContent = `export { default } from '${importPath}';
|
|
52
|
+
`;
|
|
53
|
+
const navPath = path.join(tempDir, `${layoutName}.tsx`);
|
|
54
|
+
await fs.mkdir(path.dirname(navPath), { recursive: true });
|
|
55
|
+
await fs.writeFile(navPath, navContent);
|
|
56
|
+
entryPoints[layoutName] = navPath;
|
|
57
|
+
}
|
|
58
|
+
for (const loading of loadings) {
|
|
59
|
+
const loadingName = `nav_loading_${layoutDirToFileName(loading.dirPath)}`;
|
|
60
|
+
const importPath = loading.filePath.replace(/\\/g, '/');
|
|
61
|
+
const navContent = `export { default } from '${importPath}';
|
|
62
|
+
`;
|
|
63
|
+
const navPath = path.join(tempDir, `${loadingName}.tsx`);
|
|
64
|
+
await fs.mkdir(path.dirname(navPath), { recursive: true });
|
|
65
|
+
await fs.writeFile(navPath, navContent);
|
|
66
|
+
entryPoints[loadingName] = navPath;
|
|
67
|
+
}
|
|
68
|
+
const projectRequire = createRequire(path.join(rootDir, 'package.json'));
|
|
69
|
+
const reactAlias = {};
|
|
70
|
+
for (const pkg of ['react', 'react-dom']) {
|
|
71
|
+
try {
|
|
72
|
+
reactAlias[pkg] = path.dirname(projectRequire.resolve(`${pkg}/package.json`));
|
|
73
|
+
} catch {}
|
|
74
|
+
}
|
|
75
|
+
const result = await esbuild.build({
|
|
76
|
+
entryPoints,
|
|
77
|
+
outdir: clientDir,
|
|
78
|
+
format: 'esm',
|
|
79
|
+
platform: 'browser',
|
|
80
|
+
target,
|
|
81
|
+
bundle: true,
|
|
82
|
+
splitting: true,
|
|
83
|
+
entryNames: '[name]-[hash]',
|
|
84
|
+
chunkNames: 'chunks/[name]-[hash]',
|
|
85
|
+
jsx: 'automatic',
|
|
86
|
+
loader: { '.js': 'jsx' },
|
|
87
|
+
minify,
|
|
88
|
+
sourcemap: true,
|
|
89
|
+
metafile: true,
|
|
90
|
+
alias: reactAlias,
|
|
91
|
+
define: { 'process.env.NODE_ENV': JSON.stringify(nodeEnv) },
|
|
92
|
+
plugins: [...(devMode ? [reactRefreshPlugin(rootDir)] : []), clientIsolationPlugin()],
|
|
93
|
+
});
|
|
94
|
+
await fs.rm(tempDir, { recursive: true, force: true });
|
|
95
|
+
const entryToPattern = new Map();
|
|
96
|
+
const entryToNavPattern = new Map();
|
|
97
|
+
const entryToLayoutDir = new Map();
|
|
98
|
+
for (const page of pages) {
|
|
99
|
+
const outName = patternToFileName(page.pattern);
|
|
100
|
+
entryToPattern.set(path.join(tempDir, `${outName}.tsx`), page.pattern);
|
|
101
|
+
entryToNavPattern.set(path.join(tempDir, `nav_${outName}.tsx`), page.pattern);
|
|
102
|
+
}
|
|
103
|
+
for (const layout of layouts) {
|
|
104
|
+
const layoutName = `nav_layout_${layoutDirToFileName(layout.dirPath)}`;
|
|
105
|
+
entryToLayoutDir.set(path.join(tempDir, `${layoutName}.tsx`), layout.dirPath);
|
|
106
|
+
}
|
|
107
|
+
const entryToLoadingDir = new Map();
|
|
108
|
+
for (const loading of loadings) {
|
|
109
|
+
const loadingName = `nav_loading_${layoutDirToFileName(loading.dirPath)}`;
|
|
110
|
+
entryToLoadingDir.set(path.join(tempDir, `${loadingName}.tsx`), loading.dirPath);
|
|
111
|
+
}
|
|
112
|
+
const sharedChunks = [];
|
|
113
|
+
for (const [outputPath, meta] of Object.entries(result.metafile.outputs)) {
|
|
114
|
+
const relPath = path.relative(outDir, outputPath).replace(/\\/g, '/');
|
|
115
|
+
if (outputPath.endsWith('.map')) continue;
|
|
116
|
+
if (relPath.startsWith('client/chunks/')) {
|
|
117
|
+
sharedChunks.push(relPath);
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
if (meta.entryPoint) {
|
|
121
|
+
const absEntry = path.resolve(meta.entryPoint);
|
|
122
|
+
for (const [entryPath, pattern] of entryToPattern) {
|
|
123
|
+
if (absEntry === entryPath) {
|
|
124
|
+
bundles.set(pattern, relPath);
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
for (const [entryPath, pattern] of entryToNavPattern) {
|
|
129
|
+
if (absEntry === entryPath) {
|
|
130
|
+
navBundles.set(pattern, relPath);
|
|
131
|
+
break;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
for (const [entryPath, dirPath] of entryToLayoutDir) {
|
|
135
|
+
if (absEntry === entryPath) {
|
|
136
|
+
layoutNavBundles.set(dirPath, relPath);
|
|
137
|
+
break;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
for (const [entryPath, dirPath] of entryToLoadingDir) {
|
|
141
|
+
if (absEntry === entryPath) {
|
|
142
|
+
loadingNavBundles.set(dirPath, relPath);
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return {
|
|
149
|
+
bundles,
|
|
150
|
+
navBundles,
|
|
151
|
+
layoutNavBundles,
|
|
152
|
+
loadingNavBundles,
|
|
153
|
+
sharedChunks,
|
|
154
|
+
...(devMode ? { metafile: result.metafile } : {}),
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
function generateHydrationEntry(componentPath, layouts = [], loadings = []) {
|
|
158
|
+
const importPath = componentPath.replace(/\\/g, '/');
|
|
159
|
+
const imports = [
|
|
160
|
+
`import { hydrateRoot } from 'react-dom/client';`,
|
|
161
|
+
`import { ApiProvider, Router } from 'arcway/lib/client';`,
|
|
162
|
+
];
|
|
163
|
+
imports.push(`import Component from '${importPath}';`);
|
|
164
|
+
const layoutNames = [];
|
|
165
|
+
for (let i = 0; i < layouts.length; i++) {
|
|
166
|
+
const layoutImportPath = layouts[i].filePath.replace(/\\/g, '/');
|
|
167
|
+
imports.push(`import Layout${i} from '${layoutImportPath}';`);
|
|
168
|
+
layoutNames.push(`Layout${i}`);
|
|
169
|
+
}
|
|
170
|
+
const loadingNames = [];
|
|
171
|
+
for (let i = 0; i < loadings.length; i++) {
|
|
172
|
+
const loadingImportPath = loadings[i].filePath.replace(/\\/g, '/');
|
|
173
|
+
imports.push(`import Loading${i} from '${loadingImportPath}';`);
|
|
174
|
+
loadingNames.push(`Loading${i}`);
|
|
175
|
+
}
|
|
176
|
+
const layoutsArray = layoutNames.length > 0 ? `[${layoutNames.join(', ')}]` : '[]';
|
|
177
|
+
const loadingsArray = loadingNames.length > 0 ? `[${loadingNames.join(', ')}]` : '[]';
|
|
178
|
+
return `${imports.join('\n')}
|
|
179
|
+
|
|
180
|
+
const container = document.getElementById('__app');
|
|
181
|
+
const propsEl = document.getElementById('__app_props');
|
|
182
|
+
const props = propsEl ? JSON.parse(propsEl.textContent || '{}') : {};
|
|
183
|
+
|
|
184
|
+
hydrateRoot(container!, <ApiProvider><Router initialPath={window.location.pathname} initialParams={props} initialComponent={Component} initialLayouts={${layoutsArray}} initialLoadings={${loadingsArray}} /></ApiProvider>);
|
|
185
|
+
`;
|
|
186
|
+
}
|
|
187
|
+
export { buildClientBundles, generateHydrationEntry };
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import crypto from 'node:crypto';
|
|
3
|
+
import fs from 'node:fs/promises';
|
|
4
|
+
import { createRequire } from 'node:module';
|
|
5
|
+
import esbuild from 'esbuild';
|
|
6
|
+
import postcss from 'postcss';
|
|
7
|
+
import tailwindPostcss from '@tailwindcss/postcss';
|
|
8
|
+
async function buildCssBundle(pagesDir, outDir, minify, stylesPath, fontFaceCss) {
|
|
9
|
+
try {
|
|
10
|
+
await fs.access(pagesDir);
|
|
11
|
+
} catch {
|
|
12
|
+
// Pages directory doesn't exist — no CSS to build
|
|
13
|
+
return void 0;
|
|
14
|
+
}
|
|
15
|
+
let cssSource;
|
|
16
|
+
if (stylesPath) {
|
|
17
|
+
cssSource = `@import ${JSON.stringify(stylesPath)};`;
|
|
18
|
+
} else {
|
|
19
|
+
const require2 = createRequire(import.meta.url);
|
|
20
|
+
const tailwindCssPath = path.dirname(require2.resolve('tailwindcss/package.json'));
|
|
21
|
+
const tailwindEntryPath = path.join(tailwindCssPath, 'index.css');
|
|
22
|
+
cssSource = [
|
|
23
|
+
`@import ${JSON.stringify(tailwindEntryPath)};`,
|
|
24
|
+
`@source ${JSON.stringify(pagesDir)};`,
|
|
25
|
+
].join('\n');
|
|
26
|
+
}
|
|
27
|
+
const processor = postcss([tailwindPostcss()]);
|
|
28
|
+
const result = await processor.process(cssSource, {
|
|
29
|
+
from: stylesPath ?? path.join(pagesDir, '__virtual_entry.css'),
|
|
30
|
+
to: path.join(outDir, 'client', 'styles.css'),
|
|
31
|
+
});
|
|
32
|
+
let css = fontFaceCss
|
|
33
|
+
? `${fontFaceCss}
|
|
34
|
+
|
|
35
|
+
${result.css}`
|
|
36
|
+
: result.css;
|
|
37
|
+
if (minify) {
|
|
38
|
+
const minified = await esbuild.transform(css, { loader: 'css', minify: true });
|
|
39
|
+
css = minified.code;
|
|
40
|
+
}
|
|
41
|
+
const hash = crypto.createHash('sha256').update(css).digest('hex').slice(0, 8);
|
|
42
|
+
const cssFileName = `styles-${hash}.css`;
|
|
43
|
+
const outFile = path.join(outDir, 'client', cssFileName);
|
|
44
|
+
await fs.writeFile(outFile, css);
|
|
45
|
+
return `client/${cssFileName}`;
|
|
46
|
+
}
|
|
47
|
+
export { buildCssBundle };
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import {
|
|
2
|
+
resolveLayoutChain,
|
|
3
|
+
resolveLoadingChain,
|
|
4
|
+
resolvePageMiddlewareChain,
|
|
5
|
+
} from './discovery.js';
|
|
6
|
+
function generateManifest(
|
|
7
|
+
pages,
|
|
8
|
+
layouts,
|
|
9
|
+
loadings,
|
|
10
|
+
middlewares,
|
|
11
|
+
serverBundles,
|
|
12
|
+
layoutServerBundles,
|
|
13
|
+
middlewareServerBundles,
|
|
14
|
+
clientResult,
|
|
15
|
+
) {
|
|
16
|
+
const entries = [];
|
|
17
|
+
for (const page of pages) {
|
|
18
|
+
const serverBundle = serverBundles.get(page.pattern);
|
|
19
|
+
const clientBundle = clientResult.bundles.get(page.pattern);
|
|
20
|
+
const navBundle = clientResult.navBundles.get(page.pattern);
|
|
21
|
+
if (!serverBundle || !clientBundle || !navBundle) continue;
|
|
22
|
+
const layoutChain = resolveLayoutChain(page.pattern, layouts);
|
|
23
|
+
const layoutServerBundlePaths = layoutChain
|
|
24
|
+
.map((l) => layoutServerBundles.get(l.dirPath))
|
|
25
|
+
.filter((p) => p !== void 0);
|
|
26
|
+
const layoutClientBundlePaths = layoutChain
|
|
27
|
+
.map((l) => clientResult.layoutNavBundles.get(l.dirPath))
|
|
28
|
+
.filter((p) => p !== void 0);
|
|
29
|
+
const loadingChain = resolveLoadingChain(page.pattern, loadings);
|
|
30
|
+
const loadingClientBundlePaths = loadingChain
|
|
31
|
+
.map((l) => clientResult.loadingNavBundles.get(l.dirPath))
|
|
32
|
+
.filter((p) => p !== void 0);
|
|
33
|
+
const middlewareChain = resolvePageMiddlewareChain(page.pattern, middlewares);
|
|
34
|
+
const middlewareServerBundlePaths = middlewareChain
|
|
35
|
+
.map((m) => middlewareServerBundles.get(m.dirPath))
|
|
36
|
+
.filter((p) => p !== void 0);
|
|
37
|
+
entries.push({
|
|
38
|
+
pattern: page.pattern,
|
|
39
|
+
paramNames: page.paramNames,
|
|
40
|
+
...(page.catchAllParam ? { catchAllParam: page.catchAllParam } : {}),
|
|
41
|
+
serverBundle,
|
|
42
|
+
clientBundle,
|
|
43
|
+
navBundle,
|
|
44
|
+
layoutServerBundles: layoutServerBundlePaths,
|
|
45
|
+
layoutClientBundles: layoutClientBundlePaths,
|
|
46
|
+
loadingClientBundles: loadingClientBundlePaths,
|
|
47
|
+
middlewareServerBundles: middlewareServerBundlePaths,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
return {
|
|
51
|
+
entries,
|
|
52
|
+
sharedChunks: clientResult.sharedChunks,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
export { generateManifest };
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { createRequire } from 'node:module';
|
|
2
|
+
import { NODE_BUILTINS, SERVER_ONLY_PACKAGES } from '../constants.js';
|
|
3
|
+
function serverExternalsPlugin() {
|
|
4
|
+
const resolvedCache = new Map();
|
|
5
|
+
return {
|
|
6
|
+
name: 'arcway-server-externals',
|
|
7
|
+
setup(build) {
|
|
8
|
+
build.onResolve({ filter: /^[^.\/\#]/ }, (args) => {
|
|
9
|
+
if (/^[A-Za-z]:/.test(args.path)) return void 0;
|
|
10
|
+
if (args.path.startsWith('node:')) return void 0;
|
|
11
|
+
const parts = args.path.split('/');
|
|
12
|
+
const pkgName =
|
|
13
|
+
args.path.startsWith('@') && parts.length >= 2 ? `${parts[0]}/${parts[1]}` : parts[0];
|
|
14
|
+
if (!resolvedCache.has(pkgName)) {
|
|
15
|
+
resolvedCache.set(pkgName, isTypeScriptPackage(pkgName, args.resolveDir));
|
|
16
|
+
}
|
|
17
|
+
if (resolvedCache.get(pkgName)) return void 0;
|
|
18
|
+
return { path: args.path, external: true };
|
|
19
|
+
});
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
function isTypeScriptPackage(pkgName, resolveDir) {
|
|
24
|
+
try {
|
|
25
|
+
const require2 = createRequire(resolveDir + '/');
|
|
26
|
+
const resolved = require2.resolve(pkgName);
|
|
27
|
+
return /\.tsx?$/.test(resolved);
|
|
28
|
+
} catch {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function clientIsolationPlugin() {
|
|
33
|
+
return {
|
|
34
|
+
name: 'arcway-client-isolation',
|
|
35
|
+
setup(build) {
|
|
36
|
+
const STUB_NAMESPACE = 'arcway-empty-stub';
|
|
37
|
+
build.onLoad({ filter: /.*/, namespace: STUB_NAMESPACE }, () => {
|
|
38
|
+
return { contents: 'export default {};', loader: 'js' };
|
|
39
|
+
});
|
|
40
|
+
build.onResolve({ filter: /.*/ }, (args) => {
|
|
41
|
+
if (NODE_BUILTINS.has(args.path)) {
|
|
42
|
+
return { path: args.path, namespace: STUB_NAMESPACE };
|
|
43
|
+
}
|
|
44
|
+
return void 0;
|
|
45
|
+
});
|
|
46
|
+
build.onResolve({ filter: /.*/ }, (args) => {
|
|
47
|
+
const parts = args.path.split('/');
|
|
48
|
+
const pkgName =
|
|
49
|
+
args.path.startsWith('@') && parts.length >= 2 ? `${parts[0]}/${parts[1]}` : parts[0];
|
|
50
|
+
if (SERVER_ONLY_PACKAGES.has(pkgName)) {
|
|
51
|
+
return { path: args.path, namespace: STUB_NAMESPACE };
|
|
52
|
+
}
|
|
53
|
+
return void 0;
|
|
54
|
+
});
|
|
55
|
+
build.onResolve({ filter: /^arcway(?:\/|$)/ }, (args) => {
|
|
56
|
+
if (
|
|
57
|
+
args.path === 'arcway/lib/client' ||
|
|
58
|
+
args.path.startsWith('arcway/lib/client/') ||
|
|
59
|
+
args.path === 'arcway/ui' ||
|
|
60
|
+
args.path.startsWith('arcway/ui/')
|
|
61
|
+
) {
|
|
62
|
+
return void 0;
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
errors: [
|
|
66
|
+
{
|
|
67
|
+
text: `Cannot import "${args.path}" in a page component. "${args.path}" contains server-only code (database, auth, etc.) that cannot run in the browser. Use "arcway/lib/client" for client-safe APIs (useApi, useMutation, etc.) or import the package directly (e.g. import { type } from "arktype" instead of from "arcway").`,
|
|
68
|
+
},
|
|
69
|
+
],
|
|
70
|
+
};
|
|
71
|
+
});
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
export { clientIsolationPlugin, serverExternalsPlugin };
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import esbuild from 'esbuild';
|
|
3
|
+
import { serverExternalsPlugin } from './build-plugins.js';
|
|
4
|
+
function patternToFileName(pattern) {
|
|
5
|
+
if (pattern === '/') return '_index';
|
|
6
|
+
return pattern
|
|
7
|
+
.slice(1)
|
|
8
|
+
.replace(/\*(\w+)\?/g, '_catchopt_$1')
|
|
9
|
+
.replace(/\*(\w+)/g, '_catch_$1')
|
|
10
|
+
.replace(/:(\w+)/g, '_$1');
|
|
11
|
+
}
|
|
12
|
+
function layoutDirToFileName(dirPath) {
|
|
13
|
+
if (dirPath === '') return 'root';
|
|
14
|
+
return dirPath.replace(/[\\/]/g, '_');
|
|
15
|
+
}
|
|
16
|
+
async function buildServerBundles(pages, outDir, target) {
|
|
17
|
+
const serverDir = path.join(outDir, 'server');
|
|
18
|
+
const resultMap = new Map();
|
|
19
|
+
for (const page of pages) {
|
|
20
|
+
const outName = patternToFileName(page.pattern);
|
|
21
|
+
const outFile = path.join(serverDir, `${outName}.js`);
|
|
22
|
+
await esbuild.build({
|
|
23
|
+
entryPoints: [page.filePath],
|
|
24
|
+
outfile: outFile,
|
|
25
|
+
format: 'esm',
|
|
26
|
+
platform: 'node',
|
|
27
|
+
target,
|
|
28
|
+
bundle: true,
|
|
29
|
+
jsx: 'automatic',
|
|
30
|
+
loader: { '.js': 'jsx' },
|
|
31
|
+
sourcemap: true,
|
|
32
|
+
plugins: [serverExternalsPlugin()],
|
|
33
|
+
});
|
|
34
|
+
resultMap.set(page.pattern, path.relative(outDir, outFile).replace(/\\/g, '/'));
|
|
35
|
+
}
|
|
36
|
+
return resultMap;
|
|
37
|
+
}
|
|
38
|
+
async function buildLayoutServerBundles(layouts, outDir, target) {
|
|
39
|
+
const serverDir = path.join(outDir, 'server');
|
|
40
|
+
const resultMap = new Map();
|
|
41
|
+
for (const layout of layouts) {
|
|
42
|
+
const outName = layoutDirToFileName(layout.dirPath);
|
|
43
|
+
const outFile = path.join(serverDir, `_layout_${outName}.js`);
|
|
44
|
+
await esbuild.build({
|
|
45
|
+
entryPoints: [layout.filePath],
|
|
46
|
+
outfile: outFile,
|
|
47
|
+
format: 'esm',
|
|
48
|
+
platform: 'node',
|
|
49
|
+
target,
|
|
50
|
+
bundle: true,
|
|
51
|
+
jsx: 'automatic',
|
|
52
|
+
loader: { '.js': 'jsx' },
|
|
53
|
+
sourcemap: true,
|
|
54
|
+
plugins: [serverExternalsPlugin()],
|
|
55
|
+
});
|
|
56
|
+
resultMap.set(layout.dirPath, path.relative(outDir, outFile).replace(/\\/g, '/'));
|
|
57
|
+
}
|
|
58
|
+
return resultMap;
|
|
59
|
+
}
|
|
60
|
+
async function buildMiddlewareServerBundles(middlewares, outDir, target) {
|
|
61
|
+
const serverDir = path.join(outDir, 'server');
|
|
62
|
+
const resultMap = new Map();
|
|
63
|
+
for (const mw of middlewares) {
|
|
64
|
+
const outName = layoutDirToFileName(mw.dirPath);
|
|
65
|
+
const outFile = path.join(serverDir, `_middleware_${outName}.js`);
|
|
66
|
+
await esbuild.build({
|
|
67
|
+
entryPoints: [mw.filePath],
|
|
68
|
+
outfile: outFile,
|
|
69
|
+
format: 'esm',
|
|
70
|
+
platform: 'node',
|
|
71
|
+
target,
|
|
72
|
+
bundle: true,
|
|
73
|
+
sourcemap: true,
|
|
74
|
+
plugins: [serverExternalsPlugin()],
|
|
75
|
+
});
|
|
76
|
+
resultMap.set(mw.dirPath, path.relative(outDir, outFile).replace(/\\/g, '/'));
|
|
77
|
+
}
|
|
78
|
+
return resultMap;
|
|
79
|
+
}
|
|
80
|
+
async function buildErrorPageBundles(errorPages, outDir, target) {
|
|
81
|
+
const serverDir = path.join(outDir, 'server');
|
|
82
|
+
const result = {};
|
|
83
|
+
const builds = [];
|
|
84
|
+
if (errorPages.errorPage) {
|
|
85
|
+
builds.push({ filePath: errorPages.errorPage, outName: '_error', key: 'error' });
|
|
86
|
+
}
|
|
87
|
+
if (errorPages.notFoundPage) {
|
|
88
|
+
builds.push({ filePath: errorPages.notFoundPage, outName: '_404', key: 'notFound' });
|
|
89
|
+
}
|
|
90
|
+
for (const build of builds) {
|
|
91
|
+
const outFile = path.join(serverDir, `${build.outName}.js`);
|
|
92
|
+
await esbuild.build({
|
|
93
|
+
entryPoints: [build.filePath],
|
|
94
|
+
outfile: outFile,
|
|
95
|
+
format: 'esm',
|
|
96
|
+
platform: 'node',
|
|
97
|
+
target,
|
|
98
|
+
bundle: true,
|
|
99
|
+
jsx: 'automatic',
|
|
100
|
+
loader: { '.js': 'jsx' },
|
|
101
|
+
sourcemap: true,
|
|
102
|
+
plugins: [serverExternalsPlugin()],
|
|
103
|
+
});
|
|
104
|
+
result[build.key] = path.relative(outDir, outFile);
|
|
105
|
+
}
|
|
106
|
+
return result;
|
|
107
|
+
}
|
|
108
|
+
export {
|
|
109
|
+
buildErrorPageBundles,
|
|
110
|
+
buildLayoutServerBundles,
|
|
111
|
+
buildMiddlewareServerBundles,
|
|
112
|
+
buildServerBundles,
|
|
113
|
+
layoutDirToFileName,
|
|
114
|
+
patternToFileName,
|
|
115
|
+
};
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import fs from 'node:fs/promises';
|
|
3
|
+
import {
|
|
4
|
+
discoverPages,
|
|
5
|
+
discoverLayouts,
|
|
6
|
+
discoverLoadings,
|
|
7
|
+
discoverPageMiddleware,
|
|
8
|
+
discoverErrorPages,
|
|
9
|
+
discoverStyles,
|
|
10
|
+
} from './discovery.js';
|
|
11
|
+
import {
|
|
12
|
+
buildServerBundles,
|
|
13
|
+
buildLayoutServerBundles,
|
|
14
|
+
buildMiddlewareServerBundles,
|
|
15
|
+
buildErrorPageBundles,
|
|
16
|
+
} from './build-server.js';
|
|
17
|
+
import { buildClientBundles } from './build-client.js';
|
|
18
|
+
import { buildCssBundle } from './build-css.js';
|
|
19
|
+
import { generateManifest } from './build-manifest.js';
|
|
20
|
+
import { resolveFonts } from './fonts.js';
|
|
21
|
+
import { buildHmrRuntimeBundle } from './hmr.js';
|
|
22
|
+
async function buildPages(options) {
|
|
23
|
+
const { rootDir } = options;
|
|
24
|
+
const pagesDir = options.pagesDir ?? path.join(rootDir, 'pages');
|
|
25
|
+
const outDir = path.resolve(rootDir, options.outDir ?? '.build/pages');
|
|
26
|
+
const serverTarget = options.serverTarget ?? 'node22';
|
|
27
|
+
const clientTarget = options.clientTarget ?? 'es2022';
|
|
28
|
+
const minify = options.minify ?? true;
|
|
29
|
+
const devMode = options.devMode ?? false;
|
|
30
|
+
const [pages, layouts, loadings, middlewares, errorPages, stylesPath] = await Promise.all([
|
|
31
|
+
discoverPages(pagesDir),
|
|
32
|
+
discoverLayouts(pagesDir),
|
|
33
|
+
discoverLoadings(pagesDir),
|
|
34
|
+
discoverPageMiddleware(pagesDir),
|
|
35
|
+
discoverErrorPages(pagesDir),
|
|
36
|
+
discoverStyles(pagesDir),
|
|
37
|
+
]);
|
|
38
|
+
if (pages.length === 0) {
|
|
39
|
+
return { pageCount: 0, outDir, manifest: { entries: [], sharedChunks: [] } };
|
|
40
|
+
}
|
|
41
|
+
await fs.rm(outDir, { recursive: true, force: true });
|
|
42
|
+
await fs.mkdir(path.join(outDir, 'server'), { recursive: true });
|
|
43
|
+
await fs.mkdir(path.join(outDir, 'client'), { recursive: true });
|
|
44
|
+
let fontFaceCss;
|
|
45
|
+
let fontPreloadHtml;
|
|
46
|
+
if (options.fonts && options.fonts.length > 0) {
|
|
47
|
+
const publicDir = path.join(rootDir, 'public');
|
|
48
|
+
const fontResult = await resolveFonts(options.fonts, publicDir);
|
|
49
|
+
if (fontResult.fontFaceCss) fontFaceCss = fontResult.fontFaceCss;
|
|
50
|
+
if (fontResult.preloadHtml) fontPreloadHtml = fontResult.preloadHtml;
|
|
51
|
+
}
|
|
52
|
+
const builds = [
|
|
53
|
+
buildServerBundles(pages, outDir, serverTarget),
|
|
54
|
+
buildLayoutServerBundles(layouts, outDir, serverTarget),
|
|
55
|
+
buildMiddlewareServerBundles(middlewares, outDir, serverTarget),
|
|
56
|
+
buildClientBundles(
|
|
57
|
+
pages,
|
|
58
|
+
layouts,
|
|
59
|
+
outDir,
|
|
60
|
+
clientTarget,
|
|
61
|
+
minify,
|
|
62
|
+
rootDir,
|
|
63
|
+
loadings,
|
|
64
|
+
minify ? 'production' : 'development',
|
|
65
|
+
devMode,
|
|
66
|
+
),
|
|
67
|
+
buildErrorPageBundles(errorPages, outDir, serverTarget),
|
|
68
|
+
buildCssBundle(pagesDir, outDir, minify, stylesPath, fontFaceCss),
|
|
69
|
+
];
|
|
70
|
+
if (devMode) builds.push(buildHmrRuntimeBundle(outDir));
|
|
71
|
+
const [
|
|
72
|
+
serverResult,
|
|
73
|
+
layoutServerResult,
|
|
74
|
+
middlewareServerResult,
|
|
75
|
+
clientResult,
|
|
76
|
+
errorBundles,
|
|
77
|
+
cssBundle,
|
|
78
|
+
] = await Promise.all(builds);
|
|
79
|
+
const manifest = generateManifest(
|
|
80
|
+
pages,
|
|
81
|
+
layouts,
|
|
82
|
+
loadings,
|
|
83
|
+
middlewares,
|
|
84
|
+
serverResult,
|
|
85
|
+
layoutServerResult,
|
|
86
|
+
middlewareServerResult,
|
|
87
|
+
clientResult,
|
|
88
|
+
);
|
|
89
|
+
if (errorBundles.error) manifest.errorBundle = errorBundles.error;
|
|
90
|
+
if (errorBundles.notFound) manifest.notFoundBundle = errorBundles.notFound;
|
|
91
|
+
if (cssBundle) manifest.cssBundle = cssBundle;
|
|
92
|
+
if (fontPreloadHtml) manifest.fontPreloadHtml = fontPreloadHtml;
|
|
93
|
+
await fs.writeFile(path.join(outDir, 'pages-manifest.json'), JSON.stringify(manifest, null, 2));
|
|
94
|
+
return {
|
|
95
|
+
pageCount: pages.length,
|
|
96
|
+
outDir,
|
|
97
|
+
manifest,
|
|
98
|
+
...(devMode && clientResult.metafile ? { clientMetafile: clientResult.metafile } : {}),
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
import { patternToFileName, layoutDirToFileName } from './build-server.js';
|
|
102
|
+
import { generateHydrationEntry } from './build-client.js';
|
|
103
|
+
import { generateManifest as generateManifest2 } from './build-manifest.js';
|
|
104
|
+
import { serverExternalsPlugin, clientIsolationPlugin } from './build-plugins.js';
|
|
105
|
+
import { NODE_BUILTINS, SERVER_ONLY_PACKAGES } from '../constants.js';
|
|
106
|
+
export {
|
|
107
|
+
NODE_BUILTINS,
|
|
108
|
+
SERVER_ONLY_PACKAGES,
|
|
109
|
+
buildPages,
|
|
110
|
+
generateHydrationEntry,
|
|
111
|
+
generateManifest2 as generateManifest,
|
|
112
|
+
layoutDirToFileName,
|
|
113
|
+
patternToFileName,
|
|
114
|
+
clientIsolationPlugin,
|
|
115
|
+
serverExternalsPlugin,
|
|
116
|
+
};
|