@unsetsoft/ryunix-presets 1.0.23-canary.8 → 1.0.24

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@unsetsoft/ryunix-presets",
3
3
  "description": "Package with presets for different development environments.",
4
- "version": "1.0.23-canary.8",
4
+ "version": "1.0.24",
5
5
  "author": "Neyunse",
6
6
  "type": "module",
7
7
  "repository": "https://github.com/UnSetSoft/Ryunixjs",
@@ -35,42 +35,49 @@
35
35
  "dependencies": {
36
36
  "@babel/cli": "7.28.3",
37
37
  "@babel/core": "7.28.5",
38
- "@babel/plugin-proposal-class-properties": "7.18.6",
39
38
  "@babel/plugin-transform-react-jsx": "7.27.1",
40
39
  "@babel/preset-env": "7.28.5",
41
40
  "@babel/preset-react": "7.28.5",
42
- "@eslint/eslintrc": "3.3.1",
43
- "@eslint/js": "9.38.0",
41
+ "@eslint/eslintrc": "3.3.3",
42
+ "@eslint/js": "9.39.2",
44
43
  "@rollup/plugin-terser": "0.4.4",
45
- "@swc/core": "1.12.14",
44
+ "@swc/core": "1.15.5",
46
45
  "babel-loader": "10.0.0",
47
46
  "chalk": "5.6.2",
48
47
  "copy-webpack-plugin": "13.0.1",
49
48
  "css-loader": "7.1.2",
50
- "css-minimizer-webpack-plugin": "7.0.2",
51
- "sass": "1.93.2",
49
+ "css-minimizer-webpack-plugin": "7.0.4",
50
+ "sass": "1.97.0",
52
51
  "sass-loader": "16.0.6",
53
52
  "dotenv-webpack": "8.1.1",
54
- "eslint": "9.38.0",
53
+ "eslint": "9.39.2",
55
54
  "eslint-plugin-react": "^7.37.5",
56
55
  "eslint-webpack-plugin": "5.0.2",
57
56
  "file-loader": "6.2.0",
58
- "glob": "11.0.3",
59
- "globals": "16.4.0",
57
+ "glob": "13.0.0",
58
+ "globals": "16.5.0",
60
59
  "html-loader": "5.1.0",
61
- "html-webpack-plugin": "5.6.4",
62
- "image-webpack-loader": "^8.1.0",
60
+ "html-webpack-plugin": "5.6.5",
63
61
  "lodash": "4.17.21",
64
62
  "mini-css-extract-plugin": "2.9.4",
65
- "rollup": "4.52.5",
63
+ "rollup": "4.53.5",
66
64
  "style-loader": "4.0.0",
67
65
  "terminal-log": "1.0.1",
68
- "terser-webpack-plugin": "5.3.14",
69
- "thread-loader": "4.0.4",
66
+ "terser-webpack-plugin": "5.3.16",
70
67
  "url-loader": "4.1.1",
71
- "webpack": "5.102.1",
68
+ "webpack": "5.104.0",
72
69
  "webpack-cli": "6.0.1",
73
70
  "webpack-dev-server": "5.2.2",
74
- "yargs": "18.0.0"
71
+ "thread-loader": "4.0.4",
72
+ "yargs": "18.0.0",
73
+ "@mdx-js/loader": "3.1.1",
74
+ "@mdx-js/rollup": "3.1.1",
75
+ "eslint-plugin-mdx": "3.6.2",
76
+ "remark-gfm": "4.0.1",
77
+ "rehype-highlight": "7.0.2",
78
+ "remark-frontmatter": "5.0.0",
79
+ "remark-mdx-frontmatter": "5.2.0",
80
+ "@remark-embedder/core": "3.0.3",
81
+ "@remark-embedder/transformer-oembed": "5.0.1"
75
82
  }
76
83
  }
@@ -49,11 +49,13 @@ const StartServer = async (cliSettings) => {
49
49
  `${defaultSettings.webpack.output.buildDirectory}/cache`,
50
50
  )
51
51
 
52
- if (!defaultSettings.webpack.production) {
52
+ const mode = cliSettings.production || defaultSettings.webpack.production ? true : false
53
+
54
+ if (!mode) {
53
55
  cleanCacheDir(cacheDir)
54
56
  }
55
57
 
56
- webpackConfig.mode = 'development'
58
+ webpackConfig.mode = mode ? 'production' : 'development'
57
59
  const compiler = Webpack(webpackConfig)
58
60
  let port = webpackConfig.devServer.port || 3000
59
61
 
@@ -65,7 +67,7 @@ const StartServer = async (cliSettings) => {
65
67
  const devServerOptions = { ...webpackConfig.devServer, ...cliSettings }
66
68
  const server = new WebpackDevServer(devServerOptions, compiler)
67
69
 
68
- const devMode = Boolean(!defaultSettings.webpack.production)
70
+ const devMode = Boolean(!mode)
69
71
 
70
72
  const { version } = await getPackageVersion()
71
73
 
@@ -81,7 +83,7 @@ const StartServer = async (cliSettings) => {
81
83
  const envStatus = envPath()
82
84
  ? chalk.green('loaded')
83
85
  : chalk.yellow('not found')
84
- const modeLabel = defaultSettings.webpack.production
86
+ const modeLabel = mode
85
87
  ? chalk.green('production')
86
88
  : chalk.yellow('development')
87
89
 
@@ -109,4 +111,4 @@ const StartServer = async (cliSettings) => {
109
111
  await startServer()
110
112
  }
111
113
 
112
- export { StartServer }
114
+ export { StartServer as StartDevServer }
@@ -1,7 +1,7 @@
1
1
  #! /usr/bin/env node
2
2
  import yargs from 'yargs'
3
3
  import { hideBin } from 'yargs/helpers'
4
- import { StartServer } from './serve.mjs'
4
+ import { StartDevServer } from './dev.server.mjs'
5
5
  import { compiler } from './compiler.mjs'
6
6
  import logger from 'terminal-log'
7
7
  import chalk from 'chalk'
@@ -15,6 +15,14 @@ import {
15
15
  import { ESLint } from 'eslint'
16
16
  import eslintConfig from '../eslint.config.mjs'
17
17
  import fs from 'fs'
18
+ import { fileURLToPath } from 'url'
19
+ import { dirname, join } from 'path'
20
+ import server from './prod.server.mjs'
21
+ import config from '../utils/config.cjs';
22
+ const __filename = fileURLToPath(import.meta.url)
23
+
24
+ const __dirname = dirname(__filename)
25
+
18
26
  const lint = {
19
27
  command: 'lint',
20
28
  describe: 'Lint code',
@@ -47,19 +55,43 @@ const lint = {
47
55
  },
48
56
  }
49
57
 
50
- const serv = {
51
- command: 'server',
52
- describe: 'Run server',
58
+ const dev = {
59
+ command: 'dev',
60
+ describe: 'Run server for developer mode.',
53
61
  handler: async (arg) => {
62
+ if (defaultSettings.webpack.production) {
63
+ logger.error("You need use development mode! change webpack.production to false in ryunix.config.js.")
64
+ return
65
+ }
54
66
  const open = Boolean(arg.browser) || false
55
67
  const settings = {
56
68
  open,
57
69
  }
58
70
 
59
- StartServer(settings)
71
+ StartDevServer(settings)
60
72
  },
61
73
  }
62
74
 
75
+ const prod = {
76
+ command: 'start',
77
+ describe: 'Run server for production mode. Requiere .ryunix/static',
78
+ handler: async (arg) => {
79
+ if (!defaultSettings.webpack.production) {
80
+ logger.error("You need use production mode!")
81
+ return
82
+ }
83
+
84
+ if (!fs.existsSync(join(process.cwd(), config.webpack.output.buildDirectory, 'static'))) {
85
+ logger.error("You need build first!")
86
+ return
87
+ }
88
+
89
+ server.listen(config.webpack.devServer.port, () => {
90
+ console.log(`Server running at http://localhost:${config.webpack.devServer.port}/`);
91
+ });
92
+ }
93
+ }
94
+
63
95
  const build = {
64
96
  command: 'build',
65
97
  describe: 'Run builder',
@@ -98,6 +130,8 @@ const build = {
98
130
  minutes > 0 ? `${minutes}m ${seconds}s` : `${seconds}s`
99
131
 
100
132
  if (defaultSettings.webpack.production) {
133
+
134
+
101
135
  await Prerender(defaultSettings.webpack.output.buildDirectory)
102
136
  }
103
137
 
@@ -113,4 +147,21 @@ const build = {
113
147
  },
114
148
  }
115
149
 
116
- yargs(hideBin(process.argv)).command(serv).command(build).command(lint).parse()
150
+ const extractHTML = {
151
+ command: 'customHtml',
152
+ describe: 'Extract HTML for customization',
153
+ handler: async (arg) => {
154
+ const runPath = process.cwd()
155
+
156
+ fs.copyFile(join(__dirname, "..", "template/index.html"), join(runPath, "public/index.html"), (err) => {
157
+ if (err) {
158
+ console.error("Error extracting HTML: ", err.message);
159
+ return;
160
+ }
161
+ console.log("File extracted successfully. Now you can enable the template with static.customTemplate inside ryunix.config.js");
162
+ });
163
+ },
164
+ }
165
+
166
+
167
+ yargs(hideBin(process.argv)).command(dev).command(build).command(prod).command(lint).command(extractHTML).parse()
@@ -22,12 +22,19 @@ const Prerender = async (directory) => {
22
22
  if (fs.existsSync(manifestPath)) {
23
23
  try {
24
24
  routes = JSON.parse(fs.readFileSync(manifestPath, 'utf-8'))
25
- console.log(`[SSG] Found ${routes.length} routes in manifest`)
26
25
  } catch (error) {
27
26
  console.error('[SSG] Error reading routes manifest:', error)
28
27
  }
29
28
  }
30
29
 
30
+ const metaExist = routes.some((route) => route.meta)
31
+ if (metaExist && defaultSettings.static.seo.meta.length > 0) {
32
+
33
+ console.error("[Ryunix Error] You are mixing static and dynamic meta tags; you can only use one of the two. Remove static.seo.meta from ryunix.config.js.")
34
+ process.exit(1)
35
+
36
+ }
37
+
31
38
  if (routes.length === 0) {
32
39
  routes = defaultSettings.experimental?.ssg?.prerender || []
33
40
  if (routes.length > 0) {
@@ -0,0 +1,393 @@
1
+ import http from 'http'
2
+ import { promises as fs } from 'fs'
3
+ import path from 'path'
4
+ import { createHash } from 'crypto'
5
+ import zlib from 'zlib'
6
+ import { promisify } from 'util'
7
+ import { createReadStream } from 'fs'
8
+ import { pipeline } from 'stream/promises'
9
+ import config from '../utils/config.cjs'
10
+
11
+ const gzip = promisify(zlib.gzip)
12
+ const brotliCompress = promisify(zlib.brotliCompress)
13
+
14
+ // MIME types dictionary
15
+ const MIME_TYPES = {
16
+ '.js': 'application/javascript',
17
+ '.mjs': 'application/javascript',
18
+ '.css': 'text/css',
19
+ '.html': 'text/html',
20
+ '.json': 'application/json',
21
+ '.png': 'image/png',
22
+ '.jpg': 'image/jpeg',
23
+ '.jpeg': 'image/jpeg',
24
+ '.gif': 'image/gif',
25
+ '.svg': 'image/svg+xml',
26
+ '.woff': 'font/woff',
27
+ '.woff2': 'font/woff2',
28
+ '.ttf': 'font/ttf',
29
+ '.eot': 'application/vnd.ms-fontobject',
30
+ '.otf': 'font/otf',
31
+ '.wasm': 'application/wasm',
32
+ '.ico': 'image/x-icon',
33
+ '.mp3': 'audio/mpeg',
34
+ '.mp4': 'video/mp4',
35
+ '.pdf': 'application/pdf',
36
+ '.zip': 'application/zip',
37
+ '.gz': 'application/gzip',
38
+ '.tar': 'application/x-tar',
39
+ '.7z': 'application/x-7z-compressed',
40
+ '.rar': 'application/x-rar-compressed',
41
+ '.avi': 'video/x-msvideo',
42
+ '.mov': 'video/quicktime',
43
+ '.wmv': 'video/x-ms-wmv',
44
+ '.flv': 'video/x-flv',
45
+ '.webm': 'video/webm',
46
+ '.ogg': 'audio/ogg',
47
+ '.ogv': 'video/ogg',
48
+ '.m4v': 'video/mp4',
49
+ '.3gp': 'video/3gpp',
50
+ '.3g2': 'video/3gpp2',
51
+ '.mkv': 'video/x-matroska',
52
+ '.ts': 'video/mp2t',
53
+ }
54
+
55
+ // File cache for production server
56
+ const fileCache = new Map()
57
+ const MAX_CACHE_SIZE = 50 * 1024 * 1024 // 50MB
58
+ let currentCacheSize = 0
59
+
60
+ /**
61
+ * Get MIME type from file extension
62
+ */
63
+ const getMimeType = (filePath) => {
64
+ const ext = path.extname(filePath).toLowerCase()
65
+ return MIME_TYPES[ext] || 'application/octet-stream'
66
+ }
67
+
68
+ /**
69
+ * Validate path to prevent directory traversal attacks
70
+ */
71
+ const validatePath = (requestPath, rootDir) => {
72
+ try {
73
+ const normalizedPath = path.normalize(requestPath)
74
+ const resolvedPath = path.resolve(rootDir, normalizedPath.slice(1))
75
+
76
+ if (!resolvedPath.startsWith(rootDir)) {
77
+ return null
78
+ }
79
+
80
+ return resolvedPath
81
+ } catch {
82
+ return null
83
+ }
84
+ }
85
+
86
+ /**
87
+ * Generate ETag from file content
88
+ */
89
+ const generateETag = (content) => {
90
+ return createHash('md5').update(content).digest('hex')
91
+ }
92
+
93
+ /**
94
+ * Check compression support (Brotli preferred over Gzip)
95
+ */
96
+ const getAcceptedEncoding = (headers) => {
97
+ const encoding = headers['accept-encoding'] || ''
98
+ if (encoding.includes('br')) return 'br'
99
+ if (encoding.includes('gzip')) return 'gzip'
100
+ return null
101
+ }
102
+
103
+ /**
104
+ * Check if MIME type is compressible
105
+ */
106
+ const isCompressible = (mimeType) => {
107
+ return mimeType.startsWith('text/') ||
108
+ mimeType.includes('javascript') ||
109
+ mimeType.includes('json') ||
110
+ mimeType.includes('css')
111
+ }
112
+
113
+ /**
114
+ * Parse Range header
115
+ */
116
+ const parseRange = (rangeHeader, fileSize) => {
117
+ if (!rangeHeader) return null
118
+
119
+ const parts = rangeHeader.replace(/bytes=/, '').split('-')
120
+ const start = parseInt(parts[0], 10)
121
+ const end = parts[1] ? parseInt(parts[1], 10) : fileSize - 1
122
+
123
+ if (isNaN(start) || isNaN(end) || start > end || end >= fileSize) {
124
+ return null
125
+ }
126
+
127
+ return { start, end, length: end - start + 1 }
128
+ }
129
+
130
+ /**
131
+ * Check if file should support range requests (media files)
132
+ */
133
+ const supportsRangeRequests = (mimeType) => {
134
+ return mimeType.startsWith('video/') ||
135
+ mimeType.startsWith('audio/') ||
136
+ mimeType === 'application/pdf'
137
+ }
138
+
139
+ /**
140
+ * Serve file with range support (for video/audio)
141
+ */
142
+ const serveWithRange = async (filePath, req, res, stats) => {
143
+ const mimeType = getMimeType(filePath)
144
+ const range = parseRange(req.headers.range, stats.size)
145
+
146
+ const headers = {
147
+ 'Content-Type': mimeType,
148
+ 'Accept-Ranges': 'bytes',
149
+ 'Cache-Control': 'public, max-age=31536000',
150
+ }
151
+
152
+ if (range) {
153
+ // Partial content
154
+ headers['Content-Range'] = `bytes ${range.start}-${range.end}/${stats.size}`
155
+ headers['Content-Length'] = range.length
156
+
157
+ res.writeHead(206, headers)
158
+
159
+ const stream = createReadStream(filePath, { start: range.start, end: range.end })
160
+ await pipeline(stream, res)
161
+ } else {
162
+ // Full content
163
+ headers['Content-Length'] = stats.size
164
+ res.writeHead(200, headers)
165
+
166
+ const stream = createReadStream(filePath)
167
+ await pipeline(stream, res)
168
+ }
169
+
170
+ return true
171
+ }
172
+
173
+ /**
174
+ * Serve static file with caching and compression
175
+ */
176
+ const serveStaticFile = async (filePath, req, res) => {
177
+ try {
178
+ let stats = await fs.stat(filePath)
179
+
180
+ // 👉 If is a directory
181
+ if (stats.isDirectory()) {
182
+ const indexPath = path.join(filePath, 'index.html')
183
+ await fs.access(indexPath)
184
+ stats = await fs.stat(indexPath)
185
+ filePath = indexPath
186
+ }
187
+
188
+ const mimeType = getMimeType(filePath)
189
+
190
+ // Use range requests for media files or large files
191
+ if (supportsRangeRequests(mimeType) || stats.size > 5 * 1024 * 1024) {
192
+ return await serveWithRange(filePath, req, res, stats)
193
+ }
194
+
195
+ let cached = fileCache.get(filePath)
196
+
197
+ if (!cached) {
198
+ // Read and cache file
199
+ const content = await fs.readFile(filePath)
200
+ const etag = generateETag(content)
201
+
202
+ // Compress if text-based content
203
+ let brotli = null
204
+ let gzipped = null
205
+
206
+ if (isCompressible(mimeType)) {
207
+ try {
208
+ [brotli, gzipped] = await Promise.all([
209
+ brotliCompress(content, {
210
+ params: {
211
+ [zlib.constants.BROTLI_PARAM_QUALITY]: 6,
212
+ },
213
+ }),
214
+ gzip(content),
215
+ ])
216
+ } catch {
217
+ // Compression failed, serve uncompressed
218
+ }
219
+ }
220
+
221
+ cached = {
222
+ content,
223
+ brotli,
224
+ gzipped,
225
+ etag,
226
+ mimeType,
227
+ size: stats.size,
228
+ }
229
+
230
+ // Update cache
231
+ if (currentCacheSize + stats.size < MAX_CACHE_SIZE) {
232
+ fileCache.set(filePath, cached)
233
+ currentCacheSize += stats.size
234
+ }
235
+ }
236
+
237
+ // Check ETag for 304 Not Modified
238
+ if (req.headers['if-none-match'] === cached.etag) {
239
+ res.writeHead(304)
240
+ res.end()
241
+ return true
242
+ }
243
+
244
+ // Select best encoding
245
+ const encoding = getAcceptedEncoding(req.headers)
246
+ let responseContent = cached.content
247
+ let contentEncoding = null
248
+
249
+ if (encoding === 'br' && cached.brotli) {
250
+ responseContent = cached.brotli
251
+ contentEncoding = 'br'
252
+ } else if (encoding === 'gzip' && cached.gzipped) {
253
+ responseContent = cached.gzipped
254
+ contentEncoding = 'gzip'
255
+ }
256
+
257
+ const headers = {
258
+ 'Content-Type': cached.mimeType,
259
+ 'Content-Length': responseContent.length,
260
+ 'ETag': cached.etag,
261
+ 'Cache-Control': 'public, max-age=31536000',
262
+ }
263
+
264
+ if (contentEncoding) {
265
+ headers['Content-Encoding'] = contentEncoding
266
+ }
267
+
268
+ res.writeHead(200, headers)
269
+ res.end(responseContent)
270
+ return true
271
+
272
+ } catch (error) {
273
+ return false
274
+ }
275
+ }
276
+
277
+ /**
278
+ * Serve HTML page with SPA fallback support
279
+ */
280
+ const serveHTMLPage = async (pathname, staticDir, req, res) => {
281
+ try {
282
+ const candidates = []
283
+
284
+ // / → /index.html
285
+ if (pathname === '/') {
286
+ candidates.push(path.join(staticDir, 'index.html'))
287
+ } else {
288
+ // /test → /test/index.html
289
+ candidates.push(path.join(staticDir, pathname, 'index.html'))
290
+
291
+ // /test → /test.html
292
+ candidates.push(path.join(staticDir, `${pathname}.html`))
293
+
294
+ // SPA fallback
295
+ candidates.push(path.join(staticDir, 'index.html'))
296
+ }
297
+
298
+ let pageFile = null
299
+
300
+ for (const file of candidates) {
301
+ try {
302
+ await fs.access(file)
303
+ pageFile = file
304
+ break
305
+ } catch { }
306
+ }
307
+
308
+ if (!pageFile) {
309
+ res.writeHead(404, { 'Content-Type': 'text/html; charset=utf-8' })
310
+ res.end('404')
311
+ return
312
+ }
313
+
314
+ const content = await fs.readFile(pageFile, 'utf-8')
315
+ const etag = generateETag(Buffer.from(content))
316
+
317
+ if (req.headers['if-none-match'] === etag) {
318
+ res.writeHead(304)
319
+ res.end()
320
+ return
321
+ }
322
+
323
+ // Compress HTML
324
+ let responseContent = content
325
+ const headers = {
326
+ 'Content-Type': 'text/html; charset=utf-8',
327
+ 'ETag': etag,
328
+ 'Cache-Control': 'no-cache',
329
+ }
330
+
331
+ const encoding = getAcceptedEncoding(req.headers)
332
+
333
+ if (encoding === 'br') {
334
+ try {
335
+ responseContent = await brotliCompress(Buffer.from(content))
336
+ headers['Content-Encoding'] = 'br'
337
+ } catch {
338
+ // Fallback to uncompressed
339
+ }
340
+ } else if (encoding === 'gzip') {
341
+ try {
342
+ responseContent = await gzip(Buffer.from(content))
343
+ headers['Content-Encoding'] = 'gzip'
344
+ } catch {
345
+ // Fallback to uncompressed
346
+ }
347
+ }
348
+
349
+ headers['Content-Length'] = Buffer.byteLength(responseContent)
350
+
351
+ res.writeHead(200, headers)
352
+ res.end(responseContent)
353
+
354
+ } catch (error) {
355
+ res.writeHead(500, { 'Content-Type': 'text/html; charset=utf-8' })
356
+ res.end('500')
357
+ }
358
+ }
359
+
360
+ /**
361
+ * Request handler
362
+ */
363
+ const requestHandler = async (req, res) => {
364
+ const rootDir = process.cwd()
365
+ const staticDir = path.join(rootDir, config.webpack.output.buildDirectory, 'static')
366
+
367
+ try {
368
+ const parsedUrl = new URL(req.url, `http://${req.headers.host}`)
369
+ const pathname = decodeURIComponent(parsedUrl.pathname)
370
+
371
+ const safePath = validatePath(pathname, staticDir)
372
+ if (!safePath) {
373
+ res.writeHead(403, { 'Content-Type': 'text/plain; charset=utf-8' })
374
+ res.end('403')
375
+ return
376
+ }
377
+
378
+ const fileServed = await serveStaticFile(safePath, req, res)
379
+
380
+ if (!fileServed) {
381
+ await serveHTMLPage(pathname, staticDir, req, res)
382
+ }
383
+
384
+ } catch (error) {
385
+ console.error('[Ryunix Server Error]:', error.message)
386
+ res.writeHead(500, { 'Content-Type': 'text/plain; charset=utf-8' })
387
+ res.end('500')
388
+ }
389
+ }
390
+
391
+ const httpServ = http.createServer(requestHandler)
392
+
393
+ export default httpServ
@@ -1,15 +1,27 @@
1
1
  import config from './utils/config.cjs'
2
- import { resolveApp } from './utils/index.mjs'
3
2
  import { defineConfig } from 'eslint/config'
4
- const dir = process.cwd()
5
3
 
4
+ /**
5
+ * ESLint Configuration for Ryunix
6
+ *
7
+ * NOTE ABOUT MDX:
8
+ * .mdx and .md files are excluded from ESLint due to compatibility issues
9
+ * between eslint-plugin-mdx and ESM/flat config.
10
+ *
11
+ * Error: “Could not find ESLint Linter in require cache”
12
+ *
13
+ * MDX files are validated during compilation by @mdx-js/loader,
14
+ * which is sufficient for detecting syntax and JSX errors.
15
+ */
6
16
  const eslintConfig = defineConfig([
7
17
  {
8
18
  files: ['**/*.ryx', ...config?.eslint?.files],
19
+
20
+ ignores: ['**/*.mdx', '**/*.md', '**/node_modules/**'],
21
+
9
22
  languageOptions: {
10
23
  ecmaVersion: 2021,
11
24
  sourceType: 'module',
12
-
13
25
  parserOptions: {
14
26
  ecmaFeatures: {
15
27
  jsx: true,
@@ -19,8 +31,8 @@ const eslintConfig = defineConfig([
19
31
  },
20
32
  settings: {
21
33
  react: {
22
- pragma: 'Ryunix.createElement', // Para JSX transpile a Ryunix.createElement
23
- fragment: 'Ryunix.Fragment', // Para fragmentos JSX
34
+ pragma: 'Ryunix.createElement',
35
+ fragment: 'Ryunix.Fragment',
24
36
  },
25
37
  },
26
38
  plugins: config?.eslint?.plugins,