@unsetsoft/ryunix-presets 1.0.19 → 1.0.23-canary.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.
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.19",
4
+ "version": "1.0.23-canary.1",
5
5
  "author": "Neyunse",
6
6
  "type": "module",
7
7
  "repository": "https://github.com/UnSetSoft/Ryunixjs",
@@ -15,7 +15,7 @@
15
15
  "webpack/"
16
16
  ],
17
17
  "peerDependencies": {
18
- "@unsetsoft/ryunixjs": "^1.2.1"
18
+ "@unsetsoft/ryunixjs": "^1.2.2"
19
19
  },
20
20
  "publishConfig": {
21
21
  "registry": "https://registry.npmjs.org"
@@ -14,6 +14,7 @@ import {
14
14
  } from '../utils/index.mjs'
15
15
  import { ESLint } from 'eslint'
16
16
  import eslintConfig from '../eslint.config.mjs'
17
+ import fs from 'fs'
17
18
  const lint = {
18
19
  command: 'lint',
19
20
  describe: 'Lint code',
@@ -72,7 +73,7 @@ const build = {
72
73
  return
73
74
  }
74
75
 
75
- if (defaultSettings.experimental.ssg.prerender.length > 0) {
76
+ if (fs.existsSync(resolveApp(process.cwd(), 'src/pages/routes.ryx'))) {
76
77
  await cleanBuildDirectory(
77
78
  resolveApp(
78
79
  process.cwd(),
@@ -96,8 +97,8 @@ const build = {
96
97
  const formattedTime =
97
98
  minutes > 0 ? `${minutes}m ${seconds}s` : `${seconds}s`
98
99
 
99
- if (defaultSettings.experimental.ssg.prerender.length > 0) {
100
- await Prerender()
100
+ if (defaultSettings.webpack.production) {
101
+ await Prerender(defaultSettings.webpack.output.buildDirectory)
101
102
  }
102
103
 
103
104
  logger.info(chalk.green('Compilation successful! 🎉'))
@@ -1,138 +1,47 @@
1
1
  /**
2
- * prerender metadata
3
- *
2
+ * Automatic Prerender - reads routes from manifest
4
3
  */
4
+ import { buildSSG } from '../utils/ssg.mjs'
5
5
  import { configFileExist } from '../utils/settingfile.cjs'
6
6
  import defaultSettings from '../utils/config.cjs'
7
7
  import { resolveApp } from '../utils/index.mjs'
8
8
  import fs from 'fs'
9
9
  import path from 'path'
10
10
 
11
- // proyect/.ryunix/static
12
- const buildDirectory = resolveApp(process.cwd(), '.ryunix/static')
13
- const indexFile = path.join(buildDirectory, 'index.html')
11
+ const Prerender = async (directory) => {
12
+ const buildDirectory = resolveApp(process.cwd(), directory)
14
13
 
15
- const siteMap = async (routes) => {
16
- if (!defaultSettings.experimental.ssg.sitemap.baseURL) {
17
- console.error(
18
- '❌ Base URL is not defined in the configuration file. Please set `experimental.ssg.sitemap.baseURL`.',
19
- )
20
- process.exit(1)
21
- }
22
-
23
- const sitemap = `<?xml version="1.0" encoding="UTF-8"?>
24
- <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
25
- ${routes
26
- .map((route) => {
27
- const url = `${defaultSettings.experimental.ssg.sitemap.baseURL}${route.path === '/' ? '' : route.path}`
28
- const meta = route.meta || {}
29
- const sitemap_settings = route.sitemap || {}
30
- const lastmod = meta.lastmod || new Date().toISOString().split('T')[0]
31
-
32
- // sitemap settings
33
- const changefreq =
34
- sitemap_settings.changefreq ||
35
- meta.changefreq || // TODO: Remove meta.changefreq
36
- defaultSettings.experimental.ssg.sitemap.settings.changefreq
37
- const priority =
38
- sitemap_settings.priority ||
39
- meta.priority || // TODO: Remove meta.priority
40
- defaultSettings.experimental.ssg.sitemap.settings.priority
41
-
42
- return `<url>
43
- <loc>${url}</loc>
44
- <lastmod>${lastmod}</lastmod>
45
- <changefreq>${changefreq}</changefreq>
46
- <priority>${priority}</priority>
47
- </url>`
48
- })
49
- .join('\n')}
50
- </urlset>`
51
-
52
- await fs.writeFileSync(
53
- path.resolve(buildDirectory, 'sitemap.xml'),
54
- sitemap,
55
- 'utf-8',
56
- )
57
-
58
- console.log('✅ Sitemap created')
59
- }
60
-
61
- const Prerender = async () => {
62
14
  if (!configFileExist()) {
63
15
  console.error('❌ No configuration file found.')
64
16
  process.exit(1)
65
17
  }
66
18
 
67
- const template = fs.readFileSync(indexFile, 'utf-8')
19
+ const manifestPath = path.join(process.cwd(), directory, 'ssg', 'routes.json')
20
+ let routes = []
68
21
 
69
- for (const route of defaultSettings.experimental.ssg.prerender) {
70
- if (route.path === '/') continue // Exclude root path, handled by defaultSettings.static.seo.meta
71
- let html = template
72
- const meta = route?.meta || defaultSettings.static.seo.meta
73
-
74
- // title
75
- if (meta.title) {
76
- html = html.replace(/<title>.*<\/title>/, `<title>${meta.title}<\/title>`)
22
+ if (fs.existsSync(manifestPath)) {
23
+ try {
24
+ routes = JSON.parse(fs.readFileSync(manifestPath, 'utf-8'))
25
+ console.log(`[SSG] Found ${routes.length} routes in manifest`)
26
+ } catch (error) {
27
+ console.error('[SSG] Error reading routes manifest:', error)
77
28
  }
29
+ }
78
30
 
79
- // Helper to add or replace metatag
80
- function upsertMetaTag(html, name, value) {
81
- if (value === undefined || value === null) return html
82
-
83
- const isProperty = name.startsWith('og:') || name.startsWith('twitter:')
84
- const attr = isProperty ? 'property' : 'name'
85
- const regex = new RegExp(`<meta ${attr}=["']${name}["'][^>]*>`, 'i')
86
- const tag = `<meta ${attr}="${name}" content="${value}">`
87
-
88
- if (regex.test(html)) {
89
- return html.replace(regex, tag)
90
- } else {
91
- return html.replace(/<\/head>/i, `${tag}\n<\/head>`)
92
- }
93
- }
94
-
95
- // Description
96
- html = upsertMetaTag(html, 'description', meta.description)
97
- // Keywords
98
- html = upsertMetaTag(html, 'keywords', meta.keywords)
99
-
100
- // dynamic metatags (excluding title, description, keywords, framework, mode)
101
- for (const [key, value] of Object.entries(meta)) {
102
- if (
103
- [
104
- 'title',
105
- 'description',
106
- 'keywords',
107
- 'framework',
108
- 'mode',
109
- 'viewport',
110
- ].includes(key)
111
- )
112
- continue
113
- html = upsertMetaTag(html, key, value)
114
- }
115
-
116
- const outputDir =
117
- route.path === '/'
118
- ? buildDirectory
119
- : path.join(buildDirectory, route.path)
120
-
121
- if (!fs.existsSync(outputDir)) {
122
- await fs.mkdirSync(outputDir, { recursive: true, force: true })
31
+ if (routes.length === 0) {
32
+ routes = defaultSettings.experimental?.ssg?.prerender || []
33
+ if (routes.length > 0) {
34
+ console.log(`[SSG] Using ${routes.length} routes from config`)
123
35
  }
124
-
125
- await fs.writeFileSync(path.join(outputDir, 'index.html'), html)
126
- console.log(`✅ Prerendered ${route.path}`)
127
36
  }
128
37
 
129
- // need base url and enable
130
- if (
131
- defaultSettings.experimental.ssg.sitemap.enable &&
132
- defaultSettings.experimental.ssg.sitemap.baseURL
133
- ) {
134
- await siteMap(defaultSettings.experimental.ssg.prerender)
38
+ if (routes.length === 0) {
39
+ console.log('[SSG] No routes to prerender, skipping SSG generation.')
40
+ return
135
41
  }
42
+
43
+ await buildSSG(routes, defaultSettings, buildDirectory)
44
+ console.log('✅ SSG build complete')
136
45
  }
137
46
 
138
47
  export default Prerender
@@ -6,6 +6,8 @@ import { join, dirname } from 'path'
6
6
  import { fileURLToPath } from 'url'
7
7
  import logger from 'terminal-log'
8
8
  import chalk from 'chalk'
9
+ import { createRequire } from 'node:module'
10
+ import { readFile } from 'node:fs/promises'
9
11
 
10
12
  const resolveApp = (appDirectory, relativePath) =>
11
13
  resolve(appDirectory, relativePath)
@@ -58,19 +60,12 @@ const getEnviroment = () =>
58
60
  },
59
61
  )
60
62
 
63
+ const require = createRequire(import.meta.url)
64
+
61
65
  const getPackageVersion = async () => {
62
- const __dirname = dirname(fileURLToPath(import.meta.url)) // Para obtener el directorio actual
63
- const packageJsonPath = join(
64
- __dirname,
65
- '..',
66
- '..',
67
- '..',
68
- 'ryunixjs',
69
- 'package.json',
70
- )
71
- const data = await fs.readFile(packageJsonPath, 'utf-8')
72
- const packageJson = JSON.parse(data)
73
- return packageJson
66
+ const packageJsonPath = require.resolve('@unsetsoft/ryunixjs/package.json')
67
+ const data = await readFile(packageJsonPath, 'utf-8')
68
+ return JSON.parse(data)
74
69
  }
75
70
 
76
71
  async function cleanCacheDir(dirPath) {
@@ -0,0 +1,197 @@
1
+ /**
2
+ * SSG Utilities - Improved static site generation
3
+ */
4
+ import fs from 'fs'
5
+ import path from 'path'
6
+
7
+ /**
8
+ * Extract routes from routes config for SSG
9
+ */
10
+ const extractSSGRoutes = (routes) => {
11
+ const ssgRoutes = []
12
+
13
+ const processRoute = (route, parentPath = '') => {
14
+ if (!route.path || route.path.includes(':')) return // Skip dynamic routes
15
+ if (route.NotFound || route.noRenderLink) return // Skip special routes
16
+
17
+ const fullPath = parentPath + route.path
18
+
19
+ ssgRoutes.push({
20
+ path: fullPath === '' ? '/' : fullPath,
21
+ component: route.component,
22
+ meta: route.meta || {},
23
+ sitemap: route.sitemap || {},
24
+ label: route.label,
25
+ })
26
+
27
+ // Process nested routes
28
+ if (route.subRoutes) {
29
+ route.subRoutes.forEach((subRoute) => {
30
+ processRoute(subRoute, fullPath)
31
+ })
32
+ }
33
+ }
34
+
35
+ routes.forEach((route) => processRoute(route))
36
+ return ssgRoutes
37
+ }
38
+
39
+ /**
40
+ * Generate robots.txt
41
+ */
42
+ const generateRobotsTxt = (baseURL, options = {}) => {
43
+ const { disallow = [], allow = [], userAgents = ['*'] } = options
44
+
45
+ let content = ''
46
+
47
+ userAgents.forEach((agent) => {
48
+ content += `User-agent: ${agent}\n`
49
+ allow.forEach((path) => (content += `Allow: ${path}\n`))
50
+ disallow.forEach((path) => (content += `Disallow: ${path}\n`))
51
+ })
52
+
53
+ content += `\nSitemap: ${baseURL}/sitemap.xml\n`
54
+
55
+ return content
56
+ }
57
+
58
+ /**
59
+ * Generate enhanced sitemap with all routes
60
+ */
61
+ const generateSitemap = (routes, baseURL, defaultSettings = {}) => {
62
+ const { changefreq = 'weekly', priority = '0.7' } = defaultSettings
63
+
64
+ const urls = routes
65
+ .map((route) => {
66
+ const url = `${baseURL}${route.path === '/' ? '' : route.path}`
67
+ const meta = route.meta || {}
68
+ const sitemap = route.sitemap || {}
69
+
70
+ const lastmod =
71
+ meta.lastmod ||
72
+ sitemap.lastmod ||
73
+ new Date().toISOString().split('T')[0]
74
+ const freq = sitemap.changefreq || meta.changefreq || changefreq
75
+ const prio = sitemap.priority || meta.priority || priority
76
+
77
+ return ` <url>
78
+ <loc>${url}</loc>
79
+ <lastmod>${lastmod}</lastmod>
80
+ <changefreq>${freq}</changefreq>
81
+ <priority>${prio}</priority>
82
+ </url>`
83
+ })
84
+ .join('\n')
85
+
86
+ return `<?xml version="1.0" encoding="UTF-8"?>
87
+ <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
88
+ ${urls}
89
+ </urlset>`
90
+ }
91
+
92
+ /**
93
+ * Generate meta tags HTML
94
+ */
95
+ const generateMetaTags = (meta, defaultMeta = {}) => {
96
+ const tags = { ...defaultMeta, ...meta }
97
+ let html = ''
98
+
99
+ Object.entries(tags).forEach(([key, value]) => {
100
+ if (['title', 'canonical'].includes(key)) return
101
+
102
+ const isProperty = key.startsWith('og:') || key.startsWith('twitter:')
103
+ const attr = isProperty ? 'property' : 'name'
104
+ html += ` <meta ${attr}="${key}" content="${value}">\n`
105
+ })
106
+
107
+ return html
108
+ }
109
+
110
+ /**
111
+ * Prerender route to HTML
112
+ */
113
+ const prerenderRoute = async (route, template, config) => {
114
+ const meta = route.meta || config.static.seo.meta
115
+ let html = template
116
+
117
+ // Replace title
118
+ if (meta.title) {
119
+ html = html.replace(/<title>.*?<\/title>/, `<title>${meta.title}</title>`)
120
+ }
121
+
122
+ // Add/replace meta tags
123
+ const metaTags = generateMetaTags(meta, config.static.seo.meta)
124
+
125
+ // Remove existing meta tags (except framework/mode)
126
+ html = html.replace(/<meta name="(?!framework|mode).*?>\n/g, '')
127
+ html = html.replace(/<meta property=.*?>\n/g, '')
128
+
129
+ // Add new meta tags before </head>
130
+ html = html.replace(/<\/head>/, `${metaTags} </head>`)
131
+
132
+ // Add canonical if provided
133
+ if (meta.canonical) {
134
+ const canonical = ` <link rel="canonical" href="${meta.canonical}">\n`
135
+ html = html.replace(/<\/head>/, `${canonical} </head>`)
136
+ }
137
+
138
+ return html
139
+ }
140
+
141
+ /**
142
+ * Full SSG build process
143
+ */
144
+ const buildSSG = async (routesConfig, config, buildDir) => {
145
+ const routes = extractSSGRoutes(routesConfig)
146
+
147
+ // Template is in buildDir/static/index.html
148
+ const templatePath = path.join(buildDir, 'static', 'index.html')
149
+
150
+ if (!fs.existsSync(templatePath)) {
151
+ console.error('❌ Template not found at:', templatePath)
152
+ return
153
+ }
154
+
155
+ const template = fs.readFileSync(templatePath, 'utf-8')
156
+
157
+ for (const route of routes) {
158
+ const html = await prerenderRoute(route, template, config)
159
+
160
+ const outputDir =
161
+ route.path === '/'
162
+ ? path.join(buildDir, 'static')
163
+ : path.join(buildDir, 'static', route.path)
164
+
165
+ if (!fs.existsSync(outputDir)) {
166
+ fs.mkdirSync(outputDir, { recursive: true })
167
+ }
168
+
169
+ fs.writeFileSync(path.join(outputDir, 'index.html'), html)
170
+ console.log(`✅ Prerendered ${route.path}`)
171
+ }
172
+
173
+ // Generate sitemap in static/
174
+ if (config.experimental?.ssg?.sitemap?.enable) {
175
+ const baseURL = config.experimental.ssg.sitemap.baseURL
176
+ const sitemap = generateSitemap(
177
+ routes,
178
+ baseURL,
179
+ config.experimental.ssg.sitemap.settings,
180
+ )
181
+ fs.writeFileSync(path.join(buildDir, 'static', 'sitemap.xml'), sitemap)
182
+ console.log('✅ Sitemap created')
183
+
184
+ const robots = generateRobotsTxt(baseURL, config.experimental?.ssg?.robots)
185
+ fs.writeFileSync(path.join(buildDir, 'static', 'robots.txt'), robots)
186
+ console.log('✅ Robots.txt created')
187
+ }
188
+ }
189
+
190
+ export {
191
+ extractSSGRoutes,
192
+ generateSitemap,
193
+ generateRobotsTxt,
194
+ generateMetaTags,
195
+ prerenderRoute,
196
+ buildSSG,
197
+ }
@@ -0,0 +1,118 @@
1
+ /**
2
+ * Webpack plugin to extract routes for SSG
3
+ */
4
+ import fs from 'fs'
5
+ import path from 'path'
6
+
7
+ class RyunixRoutesPlugin {
8
+ constructor(options = {}) {
9
+ this.routesPath = options.routesPath || 'src/pages/routes.ryx'
10
+ this.outputPath = options.outputPath || '.ryunix/ssg/routes.json'
11
+ }
12
+
13
+ apply(compiler) {
14
+ compiler.hooks.emit.tapAsync(
15
+ 'RyunixRoutesPlugin',
16
+ (compilation, callback) => {
17
+ // Skip in development mode
18
+ if (compiler.options.mode !== 'production') {
19
+ callback()
20
+ return
21
+ }
22
+
23
+ const routesFile = path.resolve(process.cwd(), this.routesPath)
24
+
25
+ if (!fs.existsSync(routesFile)) {
26
+ console.log(
27
+ '[SSG] No routes file found, skipping manifest generation.',
28
+ )
29
+ callback()
30
+ return
31
+ }
32
+
33
+ try {
34
+ const content = fs.readFileSync(routesFile, 'utf-8')
35
+ console.log('📄 Routes file size:', content.length, 'bytes')
36
+
37
+ const routes = this.parseRoutes(content)
38
+ console.log('✅ Extracted routes:', JSON.stringify(routes, null, 2))
39
+
40
+ const manifest = JSON.stringify(routes, null, 2)
41
+ const outputPath = path.resolve(process.cwd(), this.outputPath)
42
+
43
+ fs.mkdirSync(path.dirname(outputPath), { recursive: true })
44
+ fs.writeFileSync(outputPath, manifest)
45
+ } catch (error) {
46
+ console.error('❌ Error generating routes manifest:', error)
47
+ }
48
+
49
+ callback()
50
+ },
51
+ )
52
+ }
53
+
54
+ parseRoutes(content) {
55
+ const routes = []
56
+
57
+ // Match route objects more loosely
58
+ const routeRegex = /\{\s*path:\s*["']([^"']+)["'][\s\S]*?\}/g
59
+ let match
60
+
61
+ while ((match = routeRegex.exec(content)) !== null) {
62
+ const path = match[1]
63
+
64
+ // Skip dynamic and special routes
65
+ if (path.includes(':') || path === '*') continue
66
+
67
+ const route = { path }
68
+
69
+ // Get full route block (find next closing brace at same level)
70
+ const startIdx = match.index
71
+ let braceCount = 1
72
+ let endIdx = startIdx + 1
73
+
74
+ for (let i = startIdx + 1; i < content.length; i++) {
75
+ if (content[i] === '{') braceCount++
76
+ if (content[i] === '}') braceCount--
77
+ if (braceCount === 0) {
78
+ endIdx = i
79
+ break
80
+ }
81
+ }
82
+
83
+ const routeBlock = content.substring(startIdx, endIdx + 1)
84
+
85
+ // Extract meta with nested braces support
86
+ const metaMatch = routeBlock.match(/meta:\s*\{([\s\S]*?)\}(?:\s*,|\s*\})/)
87
+ if (metaMatch) {
88
+ route.meta = this.parseObject(metaMatch[1])
89
+ }
90
+
91
+ // Extract sitemap
92
+ const sitemapMatch = routeBlock.match(/sitemap:\s*\{([^}]+)\}/)
93
+ if (sitemapMatch) {
94
+ route.sitemap = this.parseObject(sitemapMatch[1])
95
+ }
96
+
97
+ routes.push(route)
98
+ }
99
+
100
+ return routes
101
+ }
102
+
103
+ parseObject(str) {
104
+ const obj = {}
105
+ const pairs = str.match(/["']?[\w:]+["']?\s*:\s*["'][^"']*["']/g) || []
106
+
107
+ pairs.forEach((pair) => {
108
+ const [key, value] = pair
109
+ .split(':')
110
+ .map((s) => s.trim().replace(/["']/g, ''))
111
+ obj[key] = value
112
+ })
113
+
114
+ return obj
115
+ }
116
+ }
117
+
118
+ export default RyunixRoutesPlugin
@@ -19,6 +19,7 @@ import fs from 'fs'
19
19
  import config from './utils/config.cjs'
20
20
  import Dotenv from 'dotenv-webpack'
21
21
  import { getPackageVersion } from './utils/index.mjs'
22
+ import RyunixRoutesPlugin from './utils/ssgPlugin.mjs'
22
23
 
23
24
  const __filename = fileURLToPath(import.meta.url)
24
25
 
@@ -171,7 +172,6 @@ export default {
171
172
  ? MiniCssExtractPlugin.loader
172
173
  : 'style-loader',
173
174
  'css-loader',
174
- 'sass-loader',
175
175
  ],
176
176
  },
177
177
  {
@@ -215,6 +215,13 @@ export default {
215
215
  systemvars: false,
216
216
  ignoreStub: true,
217
217
  }),
218
+ new RyunixRoutesPlugin({
219
+ routesPath: resolveApp(dir, `${config.webpack.root}/pages/routes.ryx`),
220
+ outputPath: resolveApp(
221
+ dir,
222
+ `${config.webpack.output.buildDirectory}/ssg/routes.json`,
223
+ ),
224
+ }),
218
225
  new webpack.DefinePlugin({
219
226
  'ryunix.config.env': JSON.stringify(config.experimental.env),
220
227
  }),