hadars 0.4.1 → 0.4.2
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/dist/{chunk-TV37IMRB.js → chunk-2TMQUXFL.js} +10 -10
- package/dist/{chunk-2J2L2H3H.js → chunk-NYLXE7T7.js} +6 -6
- package/dist/{chunk-OS3V4CPN.js → chunk-OZUZS2PD.js} +4 -4
- package/dist/cli.js +462 -496
- package/dist/cloudflare.cjs +11 -11
- package/dist/cloudflare.js +3 -3
- package/dist/index.d.cts +8 -4
- package/dist/index.d.ts +8 -4
- package/dist/lambda.cjs +11 -11
- package/dist/lambda.js +7 -7
- package/dist/loader.cjs +90 -54
- package/dist/slim-react/index.cjs +13 -13
- package/dist/slim-react/index.js +2 -2
- package/dist/slim-react/jsx-runtime.cjs +2 -4
- package/dist/slim-react/jsx-runtime.js +1 -1
- package/dist/ssr-render-worker.js +174 -161
- package/dist/ssr-watch.js +40 -74
- package/package.json +8 -10
- package/cli-lib.ts +0 -676
- package/cli.ts +0 -36
- package/index.ts +0 -17
- package/src/build.ts +0 -805
- package/src/cloudflare.ts +0 -140
- package/src/index.tsx +0 -41
- package/src/lambda.ts +0 -287
- package/src/slim-react/context.ts +0 -55
- package/src/slim-react/dispatcher.ts +0 -87
- package/src/slim-react/hooks.ts +0 -137
- package/src/slim-react/index.ts +0 -232
- package/src/slim-react/jsx-runtime.ts +0 -7
- package/src/slim-react/jsx.ts +0 -53
- package/src/slim-react/render.ts +0 -1101
- package/src/slim-react/renderContext.ts +0 -294
- package/src/slim-react/types.ts +0 -33
- package/src/source/context.ts +0 -113
- package/src/source/graphiql.ts +0 -101
- package/src/source/inference.ts +0 -260
- package/src/source/runner.ts +0 -138
- package/src/source/store.ts +0 -50
- package/src/ssr-render-worker.ts +0 -116
- package/src/ssr-watch.ts +0 -62
- package/src/static.ts +0 -109
- package/src/types/global.d.ts +0 -5
- package/src/types/hadars.ts +0 -350
- package/src/utils/Head.tsx +0 -462
- package/src/utils/clientScript.tsx +0 -71
- package/src/utils/cookies.ts +0 -16
- package/src/utils/loader.ts +0 -335
- package/src/utils/proxyHandler.tsx +0 -104
- package/src/utils/request.tsx +0 -9
- package/src/utils/response.tsx +0 -141
- package/src/utils/rspack.ts +0 -467
- package/src/utils/runtime.ts +0 -19
- package/src/utils/serve.ts +0 -155
- package/src/utils/ssrHandler.ts +0 -239
- package/src/utils/staticFile.ts +0 -43
- package/src/utils/template.html +0 -11
- package/src/utils/upgradeRequest.tsx +0 -19
package/cli-lib.ts
DELETED
|
@@ -1,676 +0,0 @@
|
|
|
1
|
-
import { existsSync } from 'node:fs'
|
|
2
|
-
import { mkdir, writeFile, unlink, readFile } from 'node:fs/promises'
|
|
3
|
-
import { resolve, join, dirname } from 'node:path'
|
|
4
|
-
import { fileURLToPath, pathToFileURL } from 'node:url'
|
|
5
|
-
import * as Hadars from './src/build'
|
|
6
|
-
import type { HadarsOptions, HadarsEntryModule } from './src/types/hadars'
|
|
7
|
-
import { renderStaticSite } from './src/static'
|
|
8
|
-
import { runSources } from './src/source/runner'
|
|
9
|
-
import { buildSchemaExecutor, buildSchemaSDL, introspectExecutorSDL } from './src/source/inference'
|
|
10
|
-
|
|
11
|
-
const SUPPORTED = ['hadars.config.js', 'hadars.config.mjs', 'hadars.config.cjs', 'hadars.config.ts']
|
|
12
|
-
|
|
13
|
-
function findConfig(cwd: string): string | null {
|
|
14
|
-
for (const name of SUPPORTED) {
|
|
15
|
-
const p = resolve(cwd, name)
|
|
16
|
-
if (existsSync(p)) return p
|
|
17
|
-
}
|
|
18
|
-
return null
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
async function dev(config: HadarsOptions) {
|
|
22
|
-
await Hadars.dev({
|
|
23
|
-
...config,
|
|
24
|
-
baseURL: '',
|
|
25
|
-
mode: 'development',
|
|
26
|
-
});
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
async function build(config: HadarsOptions) {
|
|
30
|
-
await Hadars.build({
|
|
31
|
-
...config,
|
|
32
|
-
mode: 'production',
|
|
33
|
-
});
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
async function run(config: HadarsOptions) {
|
|
37
|
-
await Hadars.run({
|
|
38
|
-
...config,
|
|
39
|
-
mode: 'production',
|
|
40
|
-
});
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
async function loadConfig(configPath: string): Promise<HadarsOptions> {
|
|
44
|
-
const url = `file://${configPath}`
|
|
45
|
-
const mod = await import(url)
|
|
46
|
-
return (mod && (mod.default ?? mod)) as HadarsOptions
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// ── hadars export schema ─────────────────────────────────────────────────────
|
|
50
|
-
|
|
51
|
-
async function exportSchema(
|
|
52
|
-
config: HadarsOptions,
|
|
53
|
-
outputFile: string,
|
|
54
|
-
): Promise<void> {
|
|
55
|
-
let sdl: string | null = null
|
|
56
|
-
|
|
57
|
-
if (config.sources && config.sources.length > 0) {
|
|
58
|
-
console.log(`Running ${config.sources.length} source plugin(s)...`)
|
|
59
|
-
const store = await runSources(config.sources)
|
|
60
|
-
console.log(`Schema inferred for types: ${store.getTypes().join(', ') || '(none)'}`)
|
|
61
|
-
sdl = await buildSchemaSDL(store)
|
|
62
|
-
if (!sdl) {
|
|
63
|
-
console.error(
|
|
64
|
-
'Error: `graphql` package not found.\n' +
|
|
65
|
-
'Source plugins require graphql-js to be installed:\n\n' +
|
|
66
|
-
' npm install graphql\n',
|
|
67
|
-
)
|
|
68
|
-
process.exit(1)
|
|
69
|
-
}
|
|
70
|
-
} else if (config.graphql) {
|
|
71
|
-
console.log('Introspecting custom GraphQL executor...')
|
|
72
|
-
sdl = await introspectExecutorSDL(config.graphql)
|
|
73
|
-
if (!sdl) {
|
|
74
|
-
console.error(
|
|
75
|
-
'Error: `graphql` package not found.\n' +
|
|
76
|
-
'Schema export requires graphql-js to be installed:\n\n' +
|
|
77
|
-
' npm install graphql\n',
|
|
78
|
-
)
|
|
79
|
-
process.exit(1)
|
|
80
|
-
}
|
|
81
|
-
} else {
|
|
82
|
-
console.error(
|
|
83
|
-
'Error: no GraphQL source configured.\n' +
|
|
84
|
-
'Add `sources` or a `graphql` executor to your hadars.config.ts first.\n',
|
|
85
|
-
)
|
|
86
|
-
process.exit(1)
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
await writeFile(outputFile, sdl, 'utf-8')
|
|
90
|
-
console.log(`Schema written to ${outputFile}`)
|
|
91
|
-
console.log(`\nNext steps — generate TypeScript types with graphql-codegen:`)
|
|
92
|
-
console.log(` npm install -D @graphql-codegen/cli @graphql-codegen/typescript @graphql-codegen/typescript-operations`)
|
|
93
|
-
console.log(` npx graphql-codegen --schema ${outputFile} --documents "src/**/*.tsx" --out src/gql/`)
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// ── hadars export static ─────────────────────────────────────────────────────
|
|
97
|
-
|
|
98
|
-
async function exportStatic(
|
|
99
|
-
config: HadarsOptions,
|
|
100
|
-
outputDir: string,
|
|
101
|
-
cwd: string,
|
|
102
|
-
): Promise<void> {
|
|
103
|
-
if (!config.paths) {
|
|
104
|
-
console.error(
|
|
105
|
-
'Error: `paths` is not defined in your hadars config.\n' +
|
|
106
|
-
'Add a `paths` function that returns the list of URLs to pre-render:\n\n' +
|
|
107
|
-
' paths: () => [\'/\', \'/about\', \'/contact\']\n',
|
|
108
|
-
)
|
|
109
|
-
process.exit(1)
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
console.log('Building hadars project...')
|
|
113
|
-
await Hadars.build({ ...config, mode: 'production' })
|
|
114
|
-
|
|
115
|
-
const ssrBundle = resolve(cwd, '.hadars', 'index.ssr.js')
|
|
116
|
-
const outHtml = resolve(cwd, '.hadars', 'static', 'out.html')
|
|
117
|
-
const staticSrc = resolve(cwd, '.hadars', 'static')
|
|
118
|
-
|
|
119
|
-
if (!existsSync(ssrBundle)) {
|
|
120
|
-
console.error(`SSR bundle not found: ${ssrBundle}`)
|
|
121
|
-
process.exit(1)
|
|
122
|
-
}
|
|
123
|
-
if (!existsSync(outHtml)) {
|
|
124
|
-
console.error(`HTML template not found: ${outHtml}`)
|
|
125
|
-
process.exit(1)
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
const ssrModule = await import(pathToFileURL(ssrBundle).href) as HadarsEntryModule<any>
|
|
129
|
-
const htmlSource = await readFile(outHtml, 'utf-8')
|
|
130
|
-
const outDir = resolve(cwd, outputDir)
|
|
131
|
-
|
|
132
|
-
// Run source plugins (if any) and build an auto-generated GraphQL executor.
|
|
133
|
-
// An explicit config.graphql always takes precedence over the inferred one.
|
|
134
|
-
let graphql = config.graphql
|
|
135
|
-
if (config.sources && config.sources.length > 0) {
|
|
136
|
-
console.log(`Running ${config.sources.length} source plugin(s)...`)
|
|
137
|
-
const store = await runSources(config.sources)
|
|
138
|
-
if (!graphql) {
|
|
139
|
-
const inferred = await buildSchemaExecutor(store)
|
|
140
|
-
if (!inferred) {
|
|
141
|
-
console.error(
|
|
142
|
-
'Error: `graphql` package not found.\n' +
|
|
143
|
-
'Source plugins require graphql-js to be installed:\n\n' +
|
|
144
|
-
' npm install graphql\n',
|
|
145
|
-
)
|
|
146
|
-
process.exit(1)
|
|
147
|
-
}
|
|
148
|
-
graphql = inferred
|
|
149
|
-
console.log(`Schema inferred for types: ${store.getTypes().join(', ') || '(none)'}`)
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
const staticCtx = {
|
|
154
|
-
graphql: graphql ?? (() => Promise.reject(
|
|
155
|
-
new Error('[hadars] No graphql executor configured. Add a `graphql` function to your hadars.config.'),
|
|
156
|
-
)),
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
const paths = await config.paths(staticCtx)
|
|
160
|
-
|
|
161
|
-
console.log(`Pre-rendering ${paths.length} page(s)...`)
|
|
162
|
-
|
|
163
|
-
const { rendered, errors } = await renderStaticSite({
|
|
164
|
-
ssrModule,
|
|
165
|
-
htmlSource,
|
|
166
|
-
staticSrc,
|
|
167
|
-
paths,
|
|
168
|
-
outputDir: outDir,
|
|
169
|
-
graphql,
|
|
170
|
-
})
|
|
171
|
-
|
|
172
|
-
for (const p of rendered) console.log(` [200] ${p}`)
|
|
173
|
-
for (const { path, error } of errors) console.error(` [ERR] ${path}: ${error.message}`)
|
|
174
|
-
|
|
175
|
-
console.log(`\nExported to ${outputDir}/`)
|
|
176
|
-
if (errors.length > 0) console.log(` ${errors.length} page(s) failed`)
|
|
177
|
-
console.log(`\nServe locally:`)
|
|
178
|
-
console.log(` npx serve ${outputDir}`)
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
// ── hadars export cloudflare ─────────────────────────────────────────────────
|
|
182
|
-
|
|
183
|
-
async function bundleCloudflare(
|
|
184
|
-
config: HadarsOptions,
|
|
185
|
-
configPath: string,
|
|
186
|
-
outputFile: string,
|
|
187
|
-
cwd: string,
|
|
188
|
-
): Promise<void> {
|
|
189
|
-
console.log('Building hadars project...')
|
|
190
|
-
await Hadars.build({ ...config, mode: 'production' })
|
|
191
|
-
|
|
192
|
-
const ssrBundle = resolve(cwd, '.hadars', 'index.ssr.js')
|
|
193
|
-
const outHtml = resolve(cwd, '.hadars', 'static', 'out.html')
|
|
194
|
-
|
|
195
|
-
if (!existsSync(ssrBundle)) {
|
|
196
|
-
console.error(`SSR bundle not found: ${ssrBundle}`)
|
|
197
|
-
process.exit(1)
|
|
198
|
-
}
|
|
199
|
-
if (!existsSync(outHtml)) {
|
|
200
|
-
console.error(`HTML template not found: ${outHtml}`)
|
|
201
|
-
process.exit(1)
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
// Resolve cloudflare.js from the dist/ directory (sibling of cli.js).
|
|
205
|
-
const cloudflareModule = resolve(dirname(fileURLToPath(import.meta.url)), 'cloudflare.js')
|
|
206
|
-
const shimPath = join(cwd, `.hadars-cloudflare-shim-${Date.now()}.ts`)
|
|
207
|
-
const shim = [
|
|
208
|
-
`import * as ssrModule from ${JSON.stringify(ssrBundle)};`,
|
|
209
|
-
`import outHtml from ${JSON.stringify(outHtml)};`,
|
|
210
|
-
`import { createCloudflareHandler } from ${JSON.stringify(cloudflareModule)};`,
|
|
211
|
-
`import config from ${JSON.stringify(configPath)};`,
|
|
212
|
-
`export default createCloudflareHandler(config as any, { ssrModule: ssrModule as any, outHtml });`,
|
|
213
|
-
].join('\n') + '\n'
|
|
214
|
-
await writeFile(shimPath, shim, 'utf-8')
|
|
215
|
-
|
|
216
|
-
try {
|
|
217
|
-
const { build: esbuild } = await import('esbuild')
|
|
218
|
-
console.log(`Bundling Cloudflare Worker → ${outputFile}`)
|
|
219
|
-
await esbuild({
|
|
220
|
-
entryPoints: [shimPath],
|
|
221
|
-
bundle: true,
|
|
222
|
-
// 'browser' avoids Node.js built-in shims; CF Workers uses Web APIs.
|
|
223
|
-
// If you use node:* APIs in your app code, add nodejs_compat to wrangler.toml.
|
|
224
|
-
platform: 'browser',
|
|
225
|
-
format: 'esm',
|
|
226
|
-
target: ['es2022'],
|
|
227
|
-
outfile: outputFile,
|
|
228
|
-
sourcemap: false,
|
|
229
|
-
loader: { '.html': 'text', '.tsx': 'tsx', '.ts': 'ts' },
|
|
230
|
-
// @rspack/* is build-time only — never imported at Worker runtime.
|
|
231
|
-
external: ['@rspack/*'],
|
|
232
|
-
// Cloudflare Workers supports the Web Crypto API natively; suppress
|
|
233
|
-
// esbuild's attempt to polyfill node:crypto.
|
|
234
|
-
define: { 'global': 'globalThis' },
|
|
235
|
-
})
|
|
236
|
-
console.log(`Cloudflare Worker bundle written to ${outputFile}`)
|
|
237
|
-
console.log(`\nDeploy instructions:`)
|
|
238
|
-
console.log(` 1. Ensure wrangler.toml points to the output file:`)
|
|
239
|
-
console.log(` name = "my-app"`)
|
|
240
|
-
console.log(` main = "${outputFile}"`)
|
|
241
|
-
console.log(` compatibility_date = "2024-09-23"`)
|
|
242
|
-
console.log(` compatibility_flags = ["nodejs_compat"]`)
|
|
243
|
-
console.log(` 2. Upload .hadars/static/ assets to R2 (or another CDN):`)
|
|
244
|
-
console.log(` wrangler r2 object put my-bucket/assets/ --file .hadars/static/ --recursive`)
|
|
245
|
-
console.log(` 3. Add a route rule in wrangler.toml to send *.js / *.css to R2`)
|
|
246
|
-
console.log(` and all other requests to the Worker.`)
|
|
247
|
-
console.log(` 4. Deploy: wrangler deploy`)
|
|
248
|
-
} finally {
|
|
249
|
-
await unlink(shimPath).catch(() => {})
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
// ── hadars export lambda ────────────────────────────────────────────────────
|
|
254
|
-
|
|
255
|
-
async function bundleLambda(
|
|
256
|
-
config: HadarsOptions,
|
|
257
|
-
configPath: string,
|
|
258
|
-
outputFile: string,
|
|
259
|
-
cwd: string,
|
|
260
|
-
): Promise<void> {
|
|
261
|
-
// 1. Ensure the hadars production build is up to date.
|
|
262
|
-
console.log('Building hadars project...')
|
|
263
|
-
await Hadars.build({ ...config, mode: 'production' })
|
|
264
|
-
|
|
265
|
-
// 2. Resolve paths.
|
|
266
|
-
const ssrBundle = resolve(cwd, '.hadars', 'index.ssr.js')
|
|
267
|
-
const outHtml = resolve(cwd, '.hadars', 'static', 'out.html')
|
|
268
|
-
|
|
269
|
-
if (!existsSync(ssrBundle)) {
|
|
270
|
-
console.error(`SSR bundle not found: ${ssrBundle}`)
|
|
271
|
-
process.exit(1)
|
|
272
|
-
}
|
|
273
|
-
if (!existsSync(outHtml)) {
|
|
274
|
-
console.error(`HTML template not found: ${outHtml}`)
|
|
275
|
-
process.exit(1)
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
// 3. Write a temporary entry shim that statically imports the SSR module
|
|
279
|
-
// and the HTML template so esbuild can inline both.
|
|
280
|
-
// Write the shim inside cwd so esbuild's module resolution finds local
|
|
281
|
-
// node_modules when walking up from the shim's directory.
|
|
282
|
-
// Use the absolute path to lambda.js (sibling of the CLI in dist/) so the
|
|
283
|
-
// shim doesn't depend on package name resolution at all.
|
|
284
|
-
const lambdaModule = resolve(dirname(fileURLToPath(import.meta.url)), 'lambda.js')
|
|
285
|
-
const shimPath = join(cwd, `.hadars-lambda-shim-${Date.now()}.ts`)
|
|
286
|
-
const shim = [
|
|
287
|
-
`import * as ssrModule from ${JSON.stringify(ssrBundle)};`,
|
|
288
|
-
`import outHtml from ${JSON.stringify(outHtml)};`,
|
|
289
|
-
`import { createLambdaHandler } from ${JSON.stringify(lambdaModule)};`,
|
|
290
|
-
`import config from ${JSON.stringify(configPath)};`,
|
|
291
|
-
`export const handler = createLambdaHandler(config as any, { ssrModule: ssrModule as any, outHtml });`,
|
|
292
|
-
].join('\n') + '\n'
|
|
293
|
-
await writeFile(shimPath, shim, 'utf-8')
|
|
294
|
-
|
|
295
|
-
// 4. Bundle with esbuild.
|
|
296
|
-
try {
|
|
297
|
-
const { build: esbuild } = await import('esbuild')
|
|
298
|
-
console.log(`Bundling Lambda handler → ${outputFile}`)
|
|
299
|
-
await esbuild({
|
|
300
|
-
entryPoints: [shimPath],
|
|
301
|
-
bundle: true,
|
|
302
|
-
platform: 'node',
|
|
303
|
-
format: 'esm',
|
|
304
|
-
target: ['node20'],
|
|
305
|
-
outfile: outputFile,
|
|
306
|
-
sourcemap: false,
|
|
307
|
-
loader: { '.html': 'text', '.tsx': 'tsx', '.ts': 'ts' },
|
|
308
|
-
// @rspack/* contains native binaries and is build-time only —
|
|
309
|
-
// it is never imported at Lambda runtime, so mark it external.
|
|
310
|
-
// Everything else (React, hadars runtime, etc.) is bundled in to
|
|
311
|
-
// produce a truly self-contained single-file deployment.
|
|
312
|
-
external: ['@rspack/*'],
|
|
313
|
-
})
|
|
314
|
-
console.log(`Lambda bundle written to ${outputFile}`)
|
|
315
|
-
console.log(`\nDeploy instructions:`)
|
|
316
|
-
console.log(` 1. Create a staging directory with just this file:`)
|
|
317
|
-
console.log(` mkdir -p lambda-deploy && cp ${outputFile} lambda-deploy/lambda.mjs`)
|
|
318
|
-
console.log(` 2. Upload lambda-deploy/ as your Lambda function code`)
|
|
319
|
-
console.log(` 3. Set handler to: lambda.handler (runtime: Node.js 20.x)`)
|
|
320
|
-
console.log(` 4. Upload .hadars/static/ assets to S3 and serve via CloudFront`)
|
|
321
|
-
console.log(` (the Lambda handler does not serve static JS/CSS — route those to S3)`)
|
|
322
|
-
} finally {
|
|
323
|
-
await unlink(shimPath).catch(() => {})
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
const TEMPLATES: Record<string, (name: string) => string> = {
|
|
330
|
-
'package.json': (name: string) => JSON.stringify({
|
|
331
|
-
name,
|
|
332
|
-
version: '0.1.0',
|
|
333
|
-
type: 'module',
|
|
334
|
-
private: true,
|
|
335
|
-
scripts: {
|
|
336
|
-
dev: 'hadars dev',
|
|
337
|
-
build: 'hadars build',
|
|
338
|
-
start: 'hadars run',
|
|
339
|
-
},
|
|
340
|
-
dependencies: {
|
|
341
|
-
'hadars': 'latest',
|
|
342
|
-
react: '^19.0.0',
|
|
343
|
-
'react-dom':'^19.0.0',
|
|
344
|
-
},
|
|
345
|
-
}, null, 2) + '\n',
|
|
346
|
-
|
|
347
|
-
'hadars.config.ts': () =>
|
|
348
|
-
`import type { HadarsOptions } from 'hadars';
|
|
349
|
-
|
|
350
|
-
const config: HadarsOptions = {
|
|
351
|
-
entry: 'src/App.tsx',
|
|
352
|
-
port: 3000,
|
|
353
|
-
};
|
|
354
|
-
|
|
355
|
-
export default config;
|
|
356
|
-
`,
|
|
357
|
-
|
|
358
|
-
'tsconfig.json': () => JSON.stringify({
|
|
359
|
-
compilerOptions: {
|
|
360
|
-
lib: ['ESNext', 'DOM'],
|
|
361
|
-
target: 'ESNext',
|
|
362
|
-
module: 'Preserve',
|
|
363
|
-
moduleDetection: 'force',
|
|
364
|
-
jsx: 'react-jsx',
|
|
365
|
-
moduleResolution: 'bundler',
|
|
366
|
-
allowImportingTsExtensions: true,
|
|
367
|
-
verbatimModuleSyntax: true,
|
|
368
|
-
noEmit: true,
|
|
369
|
-
strict: true,
|
|
370
|
-
skipLibCheck: true,
|
|
371
|
-
},
|
|
372
|
-
}, null, 2) + '\n',
|
|
373
|
-
|
|
374
|
-
'.gitignore': () =>
|
|
375
|
-
`node_modules/
|
|
376
|
-
.hadars/
|
|
377
|
-
dist/
|
|
378
|
-
`,
|
|
379
|
-
|
|
380
|
-
'src/App.tsx': () =>
|
|
381
|
-
`import React from 'react';
|
|
382
|
-
import { HadarsHead, type HadarsApp } from 'hadars';
|
|
383
|
-
|
|
384
|
-
const css = \`
|
|
385
|
-
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
386
|
-
|
|
387
|
-
body {
|
|
388
|
-
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
389
|
-
background: #0f0f13;
|
|
390
|
-
color: #e2e8f0;
|
|
391
|
-
min-height: 100vh;
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
.nav {
|
|
395
|
-
display: flex;
|
|
396
|
-
align-items: center;
|
|
397
|
-
justify-content: space-between;
|
|
398
|
-
padding: 1rem 2rem;
|
|
399
|
-
border-bottom: 1px solid #1e1e2e;
|
|
400
|
-
}
|
|
401
|
-
.nav-brand { font-weight: 700; font-size: 1.1rem; color: #a78bfa; letter-spacing: -0.02em; }
|
|
402
|
-
.nav-links { display: flex; gap: 1.5rem; }
|
|
403
|
-
.nav-links a { color: #94a3b8; text-decoration: none; font-size: 0.9rem; }
|
|
404
|
-
.nav-links a:hover { color: #e2e8f0; }
|
|
405
|
-
|
|
406
|
-
.hero {
|
|
407
|
-
text-align: center;
|
|
408
|
-
padding: 5rem 1rem 4rem;
|
|
409
|
-
max-width: 680px;
|
|
410
|
-
margin: 0 auto;
|
|
411
|
-
}
|
|
412
|
-
.hero-badge {
|
|
413
|
-
display: inline-block;
|
|
414
|
-
background: #1e1a2e;
|
|
415
|
-
border: 1px solid #4c1d95;
|
|
416
|
-
color: #a78bfa;
|
|
417
|
-
font-size: 0.75rem;
|
|
418
|
-
font-weight: 600;
|
|
419
|
-
letter-spacing: 0.05em;
|
|
420
|
-
text-transform: uppercase;
|
|
421
|
-
padding: 0.3rem 0.8rem;
|
|
422
|
-
border-radius: 999px;
|
|
423
|
-
margin-bottom: 1.5rem;
|
|
424
|
-
}
|
|
425
|
-
.hero h1 {
|
|
426
|
-
font-size: clamp(2rem, 5vw, 3.25rem);
|
|
427
|
-
font-weight: 800;
|
|
428
|
-
letter-spacing: -0.03em;
|
|
429
|
-
line-height: 1.15;
|
|
430
|
-
margin-bottom: 1rem;
|
|
431
|
-
}
|
|
432
|
-
.hero h1 span { color: #a78bfa; }
|
|
433
|
-
.hero p {
|
|
434
|
-
font-size: 1.1rem;
|
|
435
|
-
color: #94a3b8;
|
|
436
|
-
line-height: 1.7;
|
|
437
|
-
margin-bottom: 2.5rem;
|
|
438
|
-
}
|
|
439
|
-
.hero-actions { display: flex; gap: 1rem; justify-content: center; flex-wrap: wrap; }
|
|
440
|
-
.btn {
|
|
441
|
-
display: inline-flex;
|
|
442
|
-
align-items: center;
|
|
443
|
-
gap: 0.4rem;
|
|
444
|
-
padding: 0.65rem 1.4rem;
|
|
445
|
-
border-radius: 8px;
|
|
446
|
-
font-size: 0.9rem;
|
|
447
|
-
font-weight: 600;
|
|
448
|
-
cursor: pointer;
|
|
449
|
-
border: none;
|
|
450
|
-
transition: opacity 0.15s, transform 0.1s;
|
|
451
|
-
text-decoration: none;
|
|
452
|
-
}
|
|
453
|
-
.btn:hover { opacity: 0.85; transform: translateY(-1px); }
|
|
454
|
-
.btn:active { transform: translateY(0); }
|
|
455
|
-
.btn-primary { background: #7c3aed; color: #fff; }
|
|
456
|
-
.btn-ghost { background: #1e1e2e; color: #e2e8f0; border: 1px solid #2d2d3e; }
|
|
457
|
-
|
|
458
|
-
.features {
|
|
459
|
-
display: grid;
|
|
460
|
-
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
|
461
|
-
gap: 1rem;
|
|
462
|
-
max-width: 900px;
|
|
463
|
-
margin: 0 auto 4rem;
|
|
464
|
-
padding: 0 1.5rem;
|
|
465
|
-
}
|
|
466
|
-
.card {
|
|
467
|
-
background: #16161f;
|
|
468
|
-
border: 1px solid #1e1e2e;
|
|
469
|
-
border-radius: 12px;
|
|
470
|
-
padding: 1.5rem;
|
|
471
|
-
}
|
|
472
|
-
.card-icon { font-size: 1.5rem; margin-bottom: 0.75rem; }
|
|
473
|
-
.card h3 { font-size: 0.95rem; font-weight: 700; margin-bottom: 0.4rem; }
|
|
474
|
-
.card p { font-size: 0.85rem; color: #64748b; line-height: 1.6; }
|
|
475
|
-
|
|
476
|
-
.demo {
|
|
477
|
-
max-width: 480px;
|
|
478
|
-
margin: 0 auto 4rem;
|
|
479
|
-
padding: 0 1.5rem;
|
|
480
|
-
text-align: center;
|
|
481
|
-
}
|
|
482
|
-
.demo-box {
|
|
483
|
-
background: #16161f;
|
|
484
|
-
border: 1px solid #1e1e2e;
|
|
485
|
-
border-radius: 12px;
|
|
486
|
-
padding: 2rem;
|
|
487
|
-
}
|
|
488
|
-
.demo-box h2 { font-size: 0.8rem; font-weight: 600; color: #64748b; text-transform: uppercase; letter-spacing: 0.08em; margin-bottom: 1.25rem; }
|
|
489
|
-
.counter { font-size: 3.5rem; font-weight: 800; color: #a78bfa; letter-spacing: -0.04em; margin-bottom: 1.25rem; }
|
|
490
|
-
.demo-actions { display: flex; gap: 0.75rem; justify-content: center; }
|
|
491
|
-
|
|
492
|
-
\`;
|
|
493
|
-
|
|
494
|
-
const App: HadarsApp<{}> = () => {
|
|
495
|
-
const [count, setCount] = React.useState(0);
|
|
496
|
-
|
|
497
|
-
return (
|
|
498
|
-
<>
|
|
499
|
-
<HadarsHead status={200}>
|
|
500
|
-
<title>My App</title>
|
|
501
|
-
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
502
|
-
<style data-id="app-styles" dangerouslySetInnerHTML={{ __html: css }} />
|
|
503
|
-
</HadarsHead>
|
|
504
|
-
|
|
505
|
-
<nav className="nav">
|
|
506
|
-
<span className="nav-brand">my-app</span>
|
|
507
|
-
<div className="nav-links">
|
|
508
|
-
<a href="https://github.com/dpostolachi/hadar" target="_blank" rel="noopener">github</a>
|
|
509
|
-
</div>
|
|
510
|
-
</nav>
|
|
511
|
-
|
|
512
|
-
<section className="hero">
|
|
513
|
-
<div className="hero-badge">built with hadars</div>
|
|
514
|
-
<h1>Ship <span>React apps</span><br />at full speed</h1>
|
|
515
|
-
<p>
|
|
516
|
-
SSR out of the box, zero config, instant hot-reload.
|
|
517
|
-
Edit <code>src/App.tsx</code> to get started.
|
|
518
|
-
</p>
|
|
519
|
-
<div className="hero-actions">
|
|
520
|
-
<button className="btn btn-primary" onClick={() => setCount(c => c + 1)}>
|
|
521
|
-
Try the counter ↓
|
|
522
|
-
</button>
|
|
523
|
-
</div>
|
|
524
|
-
</section>
|
|
525
|
-
|
|
526
|
-
<div className="features">
|
|
527
|
-
<div className="card">
|
|
528
|
-
<div className="card-icon">⚡</div>
|
|
529
|
-
<h3>Server-side rendering</h3>
|
|
530
|
-
<p>Pages render on the server and hydrate on the client — great for SEO and first paint.</p>
|
|
531
|
-
</div>
|
|
532
|
-
<div className="card">
|
|
533
|
-
<div className="card-icon">🔥</div>
|
|
534
|
-
<h3>Hot module reload</h3>
|
|
535
|
-
<p>Changes in <code>src/App.tsx</code> reflect instantly in the browser during development.</p>
|
|
536
|
-
</div>
|
|
537
|
-
<div className="card">
|
|
538
|
-
<div className="card-icon">📦</div>
|
|
539
|
-
<h3>Zero config</h3>
|
|
540
|
-
<p>One config file. Export a React component, run <code>hadars dev</code>, done.</p>
|
|
541
|
-
</div>
|
|
542
|
-
<div className="card">
|
|
543
|
-
<div className="card-icon">🗄️</div>
|
|
544
|
-
<h3>Server data hooks</h3>
|
|
545
|
-
<p>Use <code>useServerData</code> to fetch data on the server without extra round-trips.</p>
|
|
546
|
-
</div>
|
|
547
|
-
</div>
|
|
548
|
-
|
|
549
|
-
<div className="demo">
|
|
550
|
-
<div className="demo-box">
|
|
551
|
-
<h2>Client interactivity works</h2>
|
|
552
|
-
<div className="counter">{count}</div>
|
|
553
|
-
<div className="demo-actions">
|
|
554
|
-
<button className="btn btn-ghost" onClick={() => setCount(c => c - 1)}>− dec</button>
|
|
555
|
-
<button className="btn btn-primary" onClick={() => setCount(c => c + 1)}>+ inc</button>
|
|
556
|
-
</div>
|
|
557
|
-
</div>
|
|
558
|
-
</div>
|
|
559
|
-
|
|
560
|
-
</>
|
|
561
|
-
);
|
|
562
|
-
};
|
|
563
|
-
|
|
564
|
-
export default App;
|
|
565
|
-
`,
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
async function createProject(name: string, cwd: string): Promise<void> {
|
|
569
|
-
const dir = resolve(cwd, name)
|
|
570
|
-
|
|
571
|
-
if (existsSync(dir)) {
|
|
572
|
-
console.error(`Directory already exists: ${dir}`)
|
|
573
|
-
process.exit(1)
|
|
574
|
-
}
|
|
575
|
-
|
|
576
|
-
console.log(`Creating hadars project in ${dir}`)
|
|
577
|
-
|
|
578
|
-
await mkdir(join(dir, 'src'), { recursive: true })
|
|
579
|
-
|
|
580
|
-
for (const [file, template] of Object.entries(TEMPLATES)) {
|
|
581
|
-
const content = template(name)
|
|
582
|
-
await writeFile(join(dir, file), content, 'utf-8')
|
|
583
|
-
console.log(` created ${file}`)
|
|
584
|
-
}
|
|
585
|
-
|
|
586
|
-
console.log(`
|
|
587
|
-
Done! Next steps:
|
|
588
|
-
|
|
589
|
-
cd ${name}
|
|
590
|
-
npm install # or: bun install / pnpm install
|
|
591
|
-
npm run dev # or: bun run dev
|
|
592
|
-
`)
|
|
593
|
-
}
|
|
594
|
-
|
|
595
|
-
// ── CLI entry ─────────────────────────────────────────────────────────────────
|
|
596
|
-
|
|
597
|
-
function usage(): void {
|
|
598
|
-
console.log('Usage: hadars <new <name> | dev | build | run | export lambda [output.mjs] | export cloudflare [output.mjs] | export static [outDir] | export schema [schema.graphql]>')
|
|
599
|
-
}
|
|
600
|
-
|
|
601
|
-
export async function runCli(argv: string[], cwd = process.cwd()): Promise<void> {
|
|
602
|
-
const cmd = argv[2]
|
|
603
|
-
|
|
604
|
-
if (cmd === 'new') {
|
|
605
|
-
const name = argv[3]
|
|
606
|
-
if (!name) {
|
|
607
|
-
console.error('Usage: hadars new <project-name>')
|
|
608
|
-
process.exit(1)
|
|
609
|
-
}
|
|
610
|
-
try {
|
|
611
|
-
await createProject(name, cwd)
|
|
612
|
-
} catch (err: any) {
|
|
613
|
-
console.error('Failed to create project:', err?.message ?? err)
|
|
614
|
-
process.exit(2)
|
|
615
|
-
}
|
|
616
|
-
return
|
|
617
|
-
}
|
|
618
|
-
|
|
619
|
-
if (!cmd || !['dev', 'build', 'run', 'export'].includes(cmd)) {
|
|
620
|
-
usage()
|
|
621
|
-
process.exit(1)
|
|
622
|
-
}
|
|
623
|
-
|
|
624
|
-
const configPath = findConfig(cwd)
|
|
625
|
-
if (!configPath) {
|
|
626
|
-
console.log(`No hadars.config.* found in ${cwd}`)
|
|
627
|
-
console.log('Proceeding with default behavior (no config)')
|
|
628
|
-
return
|
|
629
|
-
}
|
|
630
|
-
|
|
631
|
-
try {
|
|
632
|
-
const cfg = await loadConfig(configPath)
|
|
633
|
-
console.log(`Loaded config from ${configPath}`)
|
|
634
|
-
switch (cmd) {
|
|
635
|
-
case 'dev':
|
|
636
|
-
console.log('Starting development server...')
|
|
637
|
-
await dev(cfg);
|
|
638
|
-
break;
|
|
639
|
-
case 'build':
|
|
640
|
-
console.log('Building project...')
|
|
641
|
-
await build(cfg);
|
|
642
|
-
console.log('Build complete')
|
|
643
|
-
process.exit(0)
|
|
644
|
-
case 'export': {
|
|
645
|
-
const subCmd = argv[3]
|
|
646
|
-
if (subCmd === 'lambda') {
|
|
647
|
-
const outputFile = resolve(cwd, argv[4] ?? 'lambda.mjs')
|
|
648
|
-
await bundleLambda(cfg, configPath, outputFile, cwd)
|
|
649
|
-
process.exit(0)
|
|
650
|
-
} else if (subCmd === 'cloudflare') {
|
|
651
|
-
const outputFile = resolve(cwd, argv[4] ?? 'cloudflare.mjs')
|
|
652
|
-
await bundleCloudflare(cfg, configPath, outputFile, cwd)
|
|
653
|
-
process.exit(0)
|
|
654
|
-
} else if (subCmd === 'static') {
|
|
655
|
-
const outDirArg = argv[4] ?? 'out'
|
|
656
|
-
await exportStatic(cfg, outDirArg, cwd)
|
|
657
|
-
process.exit(0)
|
|
658
|
-
} else if (subCmd === 'schema') {
|
|
659
|
-
const outputFile = resolve(cwd, argv[4] ?? 'schema.graphql')
|
|
660
|
-
await exportSchema(cfg, outputFile)
|
|
661
|
-
process.exit(0)
|
|
662
|
-
} else {
|
|
663
|
-
console.error(`Unknown export target: ${subCmd ?? '(none)'}. Supported: lambda, cloudflare, static, schema`)
|
|
664
|
-
process.exit(1)
|
|
665
|
-
}
|
|
666
|
-
}
|
|
667
|
-
case 'run':
|
|
668
|
-
console.log('Running project...')
|
|
669
|
-
await run(cfg);
|
|
670
|
-
break;
|
|
671
|
-
}
|
|
672
|
-
} catch (err: any) {
|
|
673
|
-
console.error('Failed to load config:', err?.message ?? err)
|
|
674
|
-
process.exit(2)
|
|
675
|
-
}
|
|
676
|
-
}
|