miolo 3.0.0-beta.2 → 3.0.0-beta.200

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (251) hide show
  1. package/bin/build/build.mjs +53 -0
  2. package/bin/build/build_bin.mjs +17 -0
  3. package/bin/build/cli/client.mjs +52 -0
  4. package/bin/build/cli/css.mjs +22 -0
  5. package/bin/build/cli/index.mjs +40 -0
  6. package/bin/build/cli/ssr.mjs +21 -0
  7. package/bin/build/server/aliases.mjs +112 -0
  8. package/bin/build/server/babel.config.js +24 -0
  9. package/bin/build/server/banner.mjs +20 -0
  10. package/bin/build/server/bundle.mjs +111 -0
  11. package/bin/build/server/fix.mjs +15 -0
  12. package/bin/build/server/index.mjs +69 -0
  13. package/bin/build/server/options.mjs +83 -0
  14. package/bin/create/auth.mjs +23 -0
  15. package/bin/create/copy.mjs +175 -0
  16. package/bin/create/docker.mjs +25 -0
  17. package/bin/create/index.mjs +137 -0
  18. package/bin/create/pkgjson.mjs +69 -0
  19. package/bin/create/prepare-template.mjs +158 -0
  20. package/bin/create/validation.mjs +27 -0
  21. package/bin/{dev.mjs → dev/dev.mjs} +33 -18
  22. package/bin/dev/dev_start.mjs +12 -0
  23. package/bin/index.mjs +93 -42
  24. package/bin/prod-bin/create-bin.mjs +46 -0
  25. package/bin/prod-bin/run.mjs +38 -0
  26. package/bin/run/pid.mjs +13 -0
  27. package/bin/run/restart.mjs +13 -0
  28. package/bin/run/start.mjs +18 -0
  29. package/bin/run/stop.mjs +20 -0
  30. package/bin/util.mjs +35 -38
  31. package/package.json +60 -46
  32. package/{bin → src/config}/.env +39 -15
  33. package/src/config/defaults.mjs +500 -429
  34. package/src/config/env.mjs +51 -0
  35. package/src/config/index.mjs +23 -24
  36. package/src/config/util.mjs +41 -0
  37. package/src/db-conn.mjs +34 -0
  38. package/src/engines/cron/emails.mjs +10 -5
  39. package/src/engines/cron/index.mjs +45 -51
  40. package/src/engines/cron/init.mjs +16 -17
  41. package/src/engines/cron/ipsum.mjs +65 -60
  42. package/src/engines/cron/syscheck.mjs +30 -30
  43. package/src/engines/emailer/index.mjs +1 -2
  44. package/src/engines/emailer/queue.mjs +14 -20
  45. package/src/engines/emailer/transporter.mjs +86 -74
  46. package/src/engines/geoip/index.mjs +23 -28
  47. package/src/engines/http/index.mjs +26 -15
  48. package/src/engines/logger/buildErrorEmailBody.mjs +72 -0
  49. package/src/engines/logger/index.mjs +114 -122
  50. package/src/engines/logger/injectStackTrace.mjs +59 -0
  51. package/src/engines/logger/logger_mail.mjs +47 -61
  52. package/src/engines/logger/reopenTransportOnHupSignal.mjs +12 -13
  53. package/src/engines/parser/Parser.mjs +77 -60
  54. package/src/engines/parser/index.mjs +1 -1
  55. package/src/engines/schema/diffObjs.mjs +41 -0
  56. package/src/engines/schema/index.mjs +4 -0
  57. package/src/engines/schema/input.mjs +52 -0
  58. package/src/engines/schema/output.mjs +63 -0
  59. package/src/engines/socket/index.mjs +8 -10
  60. package/src/index.mjs +15 -10
  61. package/src/middleware/auth/basic.mjs +41 -40
  62. package/src/middleware/auth/custom.mjs +10 -13
  63. package/src/middleware/auth/guest.mjs +27 -27
  64. package/src/middleware/auth/passport/index.mjs +352 -0
  65. package/src/middleware/auth/passport/session/index.mjs +38 -0
  66. package/src/middleware/auth/{credentials → passport}/session/store.mjs +35 -15
  67. package/src/middleware/auth/passport/session/store_koa_redis.mjs +3 -0
  68. package/src/middleware/context/cache/index.mjs +78 -33
  69. package/src/middleware/context/cache/options.mjs +19 -21
  70. package/src/middleware/context/db.mjs +45 -20
  71. package/src/middleware/context/index.mjs +13 -13
  72. package/src/middleware/extra.mjs +4 -5
  73. package/src/middleware/http/body.mjs +25 -25
  74. package/src/middleware/http/catcher.mjs +81 -8
  75. package/src/middleware/http/custom_blacklist.mjs +19 -16
  76. package/src/middleware/http/headers.mjs +37 -34
  77. package/src/middleware/http/ratelimit.mjs +16 -23
  78. package/src/middleware/http/request.mjs +60 -65
  79. package/src/middleware/routes/catch_js_error.mjs +30 -23
  80. package/src/middleware/routes/robots.mjs +4 -7
  81. package/src/middleware/routes/router/crud/attachCrudRoutes.mjs +108 -90
  82. package/src/middleware/routes/router/crud/getCrudConfig.mjs +31 -55
  83. package/src/middleware/routes/router/defaults.mjs +6 -19
  84. package/src/middleware/routes/router/index.mjs +17 -21
  85. package/src/middleware/routes/router/queries/attachQueriesRoutes.mjs +227 -50
  86. package/src/middleware/routes/router/queries/getQueriesConfig.mjs +45 -55
  87. package/src/middleware/routes/router/utils.mjs +41 -26
  88. package/src/middleware/ssr/context.mjs +5 -7
  89. package/src/middleware/ssr/fallbackIndex.mjs +2 -8
  90. package/src/middleware/ssr/html.mjs +77 -61
  91. package/src/middleware/ssr/loader.mjs +11 -14
  92. package/src/middleware/ssr/ssr_render.mjs +32 -17
  93. package/src/middleware/static/index.mjs +33 -14
  94. package/src/middleware/vite/devserver.mjs +38 -22
  95. package/src/middleware/vite/watcher.mjs +12 -14
  96. package/src/server-cron.mjs +8 -10
  97. package/src/server-dev.mjs +15 -18
  98. package/src/server.mjs +46 -52
  99. package/template/.agent/skills/miolo-app-arch/SKILL.md +250 -0
  100. package/template/.agent/skills/miolo-auth/SKILL.md +450 -0
  101. package/template/.agent/skills/miolo-cli-router/SKILL.md +394 -0
  102. package/template/.agent/skills/miolo-database/SKILL.md +358 -0
  103. package/template/.agent/skills/miolo-react-patterns/SKILL.md +426 -0
  104. package/template/.agent/skills/miolo-routing/SKILL.md +326 -0
  105. package/template/.agent/skills/miolo-schemas/SKILL.md +329 -0
  106. package/template/.agent/skills/miolo-session-context/SKILL.md +397 -0
  107. package/template/.agent/skills/miolo-ssr/SKILL.md +433 -0
  108. package/template/.editorconfig +18 -0
  109. package/template/.env +120 -0
  110. package/template/biome.json +63 -0
  111. package/template/components.json +21 -0
  112. package/template/db/init.sh +89 -0
  113. package/template/db/sql/00_drop.sql +2 -0
  114. package/template/db/sql/01_users.sql +31 -0
  115. package/template/db/sql/02_todos.sql +20 -0
  116. package/template/docker/Dockerfile +13 -0
  117. package/template/docker/docker-compose.yaml +79 -0
  118. package/template/gitignore +42 -0
  119. package/template/jsconfig.json +18 -0
  120. package/template/package.json +89 -0
  121. package/template/postcss.config.js +9 -0
  122. package/template/src/cli/App.jsx +25 -0
  123. package/template/src/cli/components/JsonTreeViewer.jsx +128 -0
  124. package/template/src/cli/components/shadcn-io/spinner/index.jsx +232 -0
  125. package/template/src/cli/components/stepper.jsx +408 -0
  126. package/template/src/cli/components/ui/avatar.jsx +36 -0
  127. package/template/src/cli/components/ui/badge.jsx +31 -0
  128. package/template/src/cli/components/ui/breadcrumb.jsx +97 -0
  129. package/template/src/cli/components/ui/card.jsx +73 -0
  130. package/template/src/cli/components/ui/collapsible.jsx +16 -0
  131. package/template/src/cli/components/ui/dropdown-menu.jsx +179 -0
  132. package/template/src/cli/components/ui/field.jsx +217 -0
  133. package/template/src/cli/components/ui/input.jsx +19 -0
  134. package/template/src/cli/components/ui/label.jsx +17 -0
  135. package/template/src/cli/components/ui/pagination.jsx +99 -0
  136. package/template/src/cli/components/ui/patched/alert.jsx +56 -0
  137. package/template/src/cli/components/ui/patched/button.jsx +45 -0
  138. package/template/src/cli/components/ui/patched/dialog.jsx +114 -0
  139. package/template/src/cli/components/ui/patched/sidebar.jsx +660 -0
  140. package/template/src/cli/components/ui/select.jsx +141 -0
  141. package/template/src/cli/components/ui/separator.jsx +21 -0
  142. package/template/src/cli/components/ui/sheet.jsx +115 -0
  143. package/template/src/cli/components/ui/skeleton.jsx +13 -0
  144. package/template/src/cli/components/ui/sonner.jsx +22 -0
  145. package/template/src/cli/components/ui/switch.jsx +25 -0
  146. package/template/src/cli/components/ui/table.jsx +88 -0
  147. package/template/src/cli/components/ui/textarea.jsx +16 -0
  148. package/template/src/cli/components/ui/tooltip.jsx +45 -0
  149. package/template/src/cli/config/store_keys.mjs +2 -0
  150. package/template/src/cli/context/data/DataContext.jsx +5 -0
  151. package/template/src/cli/context/data/DataProvider.jsx +44 -0
  152. package/template/src/cli/context/data/useBreads.mjs +15 -0
  153. package/template/src/cli/context/data/useDataContext.mjs +4 -0
  154. package/template/src/cli/context/session/SessionContext.mjs +4 -0
  155. package/template/src/cli/context/session/SessionProvider.jsx +31 -0
  156. package/template/src/cli/context/session/makePermissioner.mjs +34 -0
  157. package/template/src/cli/context/session/useSessionContext.mjs +6 -0
  158. package/template/src/cli/context/theme/ThemeContext.mjs +4 -0
  159. package/template/src/cli/context/theme/ThemeProvider.jsx +49 -0
  160. package/template/src/cli/context/theme/useThemeContext.mjs +6 -0
  161. package/template/src/cli/context/ui/UIContext.jsx +5 -0
  162. package/template/src/cli/context/ui/UIProvider.jsx +16 -0
  163. package/template/src/cli/context/ui/useUIContext.mjs +4 -0
  164. package/template/src/cli/context/util.mjs +17 -0
  165. package/template/src/cli/entry-cli.jsx +33 -0
  166. package/template/src/cli/hooks/useIsMobile.mjs +19 -0
  167. package/template/src/cli/hooks/useStoragedState.mjs +63 -0
  168. package/template/src/cli/index.html +29 -0
  169. package/template/src/cli/layout/app-sidebar.jsx +25 -0
  170. package/template/src/cli/layout/main-layout.jsx +63 -0
  171. package/template/src/cli/layout/nav-last-todos.jsx +72 -0
  172. package/template/src/cli/layout/nav-main.jsx +39 -0
  173. package/template/src/cli/layout/nav-user.jsx +105 -0
  174. package/template/src/cli/layout/prop-switcher.jsx +93 -0
  175. package/template/src/cli/lib/log.mjs +38 -0
  176. package/template/src/cli/lib/utils.mjs +10 -0
  177. package/template/src/cli/pages/Index.jsx +13 -0
  178. package/template/src/cli/pages/IndexOffline.jsx +13 -0
  179. package/template/src/cli/pages/IndexOnline.jsx +18 -0
  180. package/template/src/cli/pages/dash/Dashboard.jsx +29 -0
  181. package/template/src/cli/pages/offline/Login.jsx +43 -0
  182. package/template/src/cli/pages/offline/LoginForm.jsx +115 -0
  183. package/template/src/cli/pages/security/Security.jsx +39 -0
  184. package/template/src/cli/pages/security/SecurityForm.jsx +106 -0
  185. package/template/src/cli/pages/todos/TodoActions.jsx +83 -0
  186. package/template/src/cli/pages/todos/TodoAdd.jsx +43 -0
  187. package/template/src/cli/pages/todos/TodoList.jsx +60 -0
  188. package/template/src/cli/pages/todos/Todos.jsx +23 -0
  189. package/template/src/cli/pages/todos/context/TodosContext.jsx +5 -0
  190. package/template/src/cli/pages/todos/context/TodosProvider.jsx +157 -0
  191. package/template/src/cli/pages/todos/context/useTodosContext.mjs +4 -0
  192. package/template/src/ns/models/Todo.mjs +29 -0
  193. package/template/src/ns/models/TodoList.mjs +8 -0
  194. package/template/src/ns/models/User.mjs +40 -0
  195. package/template/src/server/cache/base.mjs +21 -0
  196. package/template/src/server/console/check_today.mjs +10 -0
  197. package/template/src/server/db/io/filter.mjs +92 -0
  198. package/template/src/server/db/io/todos/delete.mjs +29 -0
  199. package/template/src/server/db/io/todos/find.mjs +13 -0
  200. package/template/src/server/db/io/todos/read.mjs +83 -0
  201. package/template/src/server/db/io/todos/toggle.mjs +37 -0
  202. package/template/src/server/db/io/todos/upsave.mjs +32 -0
  203. package/template/src/server/db/io/users/auth.mjs +132 -0
  204. package/template/src/server/db/io/users/pwd.mjs +38 -0
  205. package/template/src/server/db/io/users/save.mjs +17 -0
  206. package/template/src/server/db/triggers/user.mjs +13 -0
  207. package/template/src/server/miolo/auth/basic.mjs +15 -0
  208. package/template/src/server/miolo/auth/guest.mjs +3 -0
  209. package/template/src/server/miolo/auth/passport.mjs +73 -0
  210. package/template/src/server/miolo/cache.mjs +11 -0
  211. package/template/src/server/miolo/cron/foo.mjs +7 -0
  212. package/template/src/server/miolo/cron/index.mjs +20 -0
  213. package/template/src/server/miolo/db.mjs +36 -0
  214. package/template/src/server/miolo/http.mjs +14 -0
  215. package/template/src/server/miolo/index.mjs +24 -0
  216. package/template/src/server/miolo/routes/crud.mjs +16 -0
  217. package/template/src/server/miolo/routes/index.mjs +8 -0
  218. package/template/src/server/miolo/ssr/entry-server.jsx +13 -0
  219. package/template/src/server/miolo/ssr/loader.mjs +18 -0
  220. package/template/src/server/routes/index.mjs +66 -0
  221. package/template/src/server/routes/todos/mod.mjs +52 -0
  222. package/template/src/server/routes/todos/read.mjs +45 -0
  223. package/template/src/server/routes/todos/special.mjs +47 -0
  224. package/template/src/server/routes/users/user.mjs +54 -0
  225. package/template/src/server/server.mjs +10 -0
  226. package/template/src/server/utils/crypt.mjs +38 -0
  227. package/template/src/server/utils/io.mjs +15 -0
  228. package/template/src/server/utils/pwdfor.mjs +25 -0
  229. package/template/src/server/utils/schema.mjs +22 -0
  230. package/template/src/static/img/default/profile.png +0 -0
  231. package/template/src/static/img/favicon.ico +0 -0
  232. package/template/src/static/img/miolo_logo.png +0 -0
  233. package/template/src/static/img/miolo_name.png +0 -0
  234. package/template/src/static/public/manifest.json +21 -0
  235. package/template/src/static/public/sw.js +79 -0
  236. package/template/src/static/style/globals.css +156 -0
  237. package/template/src/static/style/json-tree.css +54 -0
  238. package/template/src/static/style/skeleton.css +49 -0
  239. package/bin/build-client.mjs +0 -56
  240. package/bin/build-server.mjs +0 -56
  241. package/bin/create-bin.mjs +0 -38
  242. package/bin/dev_start.mjs +0 -11
  243. package/bin/env.mjs +0 -39
  244. package/bin/prod_start.mjs +0 -9
  245. package/bin/restart.mjs +0 -10
  246. package/bin/start.mjs +0 -18
  247. package/bin/stop.mjs +0 -19
  248. package/src/engines/logger/verify.mjs +0 -22
  249. package/src/middleware/auth/credentials/index.mjs +0 -151
  250. package/src/middleware/auth/credentials/session/index.mjs +0 -24
  251. package/src/middleware/auth/credentials/session/store_koa_redis.mjs +0 -3
@@ -0,0 +1,175 @@
1
+ import fs from "node:fs"
2
+ import path from "node:path"
3
+ import { transformPackageJson } from "./pkgjson.mjs"
4
+
5
+ // Text file extensions to transform
6
+ const TEXT_EXTENSIONS = [
7
+ ".js",
8
+ ".jsx",
9
+ ".mjs",
10
+ ".ts",
11
+ ".tsx",
12
+ ".json",
13
+ ".env",
14
+ ".md",
15
+ ".html",
16
+ ".css",
17
+ ".scss",
18
+ ".sass",
19
+ ".yml",
20
+ ".yaml",
21
+ ".toml",
22
+ ".txt",
23
+ ".gitignore",
24
+ ".editorconfig",
25
+ ".sh"
26
+ ]
27
+
28
+ // Files and directories to exclude from copying
29
+ const EXCLUDE_PATTERNS = ["node_modules", "dist", "build", ".git"]
30
+
31
+ /**
32
+ * Checks if a file is a text file based on extension
33
+ */
34
+ export function isTextFile(filePath) {
35
+ const ext = path.extname(filePath).toLowerCase()
36
+ return (
37
+ TEXT_EXTENSIONS.includes(ext) ||
38
+ path.basename(filePath).startsWith(".") ||
39
+ path.basename(filePath) === "Dockerfile"
40
+ )
41
+ }
42
+
43
+ /**
44
+ * Checks if a path should be excluded
45
+ */
46
+ export function shouldExclude(itemPath) {
47
+ const basename = path.basename(itemPath)
48
+ return EXCLUDE_PATTERNS.some((pattern) => basename === pattern)
49
+ }
50
+
51
+ /**
52
+ * Replaces all occurrences of miolo-sample with the new app name
53
+ */
54
+ export function transformContent(content, appName) {
55
+ return content.replace(/miolo-sample/g, appName)
56
+ }
57
+
58
+ /**
59
+ * Copies a directory recursively with transformations
60
+ */
61
+ export function copyDirectory(src, dest, appName, options = {}) {
62
+ const { authType } = options
63
+
64
+ // Create destination if it doesn't exist
65
+ if (!fs.existsSync(dest)) {
66
+ fs.mkdirSync(dest, { recursive: true })
67
+ }
68
+
69
+ const items = fs.readdirSync(src)
70
+
71
+ for (const item of items) {
72
+ const srcPath = path.join(src, item)
73
+ const destPath = path.join(dest, item)
74
+ const stat = fs.statSync(srcPath)
75
+
76
+ // Skip excluded items
77
+ if (shouldExclude(srcPath)) {
78
+ continue
79
+ }
80
+
81
+ if (stat.isDirectory()) {
82
+ // Special handling for auth directory
83
+ if (srcPath.endsWith("src/server/miolo/auth")) {
84
+ // Create auth directory
85
+ fs.mkdirSync(destPath, { recursive: true })
86
+
87
+ // Copy only the selected auth file
88
+ const authFile = `${authType}.mjs`
89
+ const srcAuthFile = path.join(srcPath, authFile)
90
+ const destAuthFile = path.join(destPath, authFile)
91
+
92
+ if (fs.existsSync(srcAuthFile)) {
93
+ let content = fs.readFileSync(srcAuthFile, "utf8")
94
+ content = transformContent(content, appName)
95
+ fs.writeFileSync(destAuthFile, content, "utf8")
96
+ } else {
97
+ console.warn(`[miolo] Warning: Auth file ${authFile} not found, skipping`)
98
+ }
99
+ continue
100
+ }
101
+
102
+ // Recursively copy directories
103
+ copyDirectory(srcPath, destPath, appName, options)
104
+ } else {
105
+ // Copy and transform files
106
+ if (isTextFile(srcPath)) {
107
+ let content = fs.readFileSync(srcPath, "utf8")
108
+
109
+ // Special handling for package.json to also replace versions
110
+ if (srcPath.endsWith("package.json")) {
111
+ content = transformPackageJson(content, appName)
112
+ } else {
113
+ content = transformContent(content, appName)
114
+ }
115
+
116
+ fs.writeFileSync(destPath, content, "utf8")
117
+ } else {
118
+ // Binary files - just copy
119
+ fs.copyFileSync(srcPath, destPath)
120
+ }
121
+ }
122
+ }
123
+ }
124
+
125
+ /**
126
+ * Copies only root-level files and specified directories
127
+ */
128
+ export function copyTemplate(sourcePath, destPath, appName, options = {}) {
129
+ // Create destination directory
130
+ if (!fs.existsSync(destPath)) {
131
+ fs.mkdirSync(destPath, { recursive: true })
132
+ }
133
+
134
+ // Copy root-level files
135
+ const items = fs.readdirSync(sourcePath)
136
+
137
+ for (const item of items) {
138
+ const srcPath = path.join(sourcePath, item)
139
+ let destItemPath = path.join(destPath, item)
140
+ const stat = fs.statSync(srcPath)
141
+
142
+ if (shouldExclude(srcPath)) {
143
+ continue
144
+ }
145
+
146
+ if (stat.isFile()) {
147
+ // Special handling for gitignore -> .gitignore rename
148
+ // (npm doesn't include .gitignore in subdirectories, so we store it as gitignore)
149
+ if (item === "gitignore") {
150
+ destItemPath = path.join(destPath, ".gitignore")
151
+ }
152
+
153
+ // Copy root-level files
154
+ if (isTextFile(srcPath)) {
155
+ let content = fs.readFileSync(srcPath, "utf8")
156
+
157
+ // Special handling for package.json to also replace versions
158
+ if (srcPath.endsWith("package.json")) {
159
+ content = transformPackageJson(content, appName)
160
+ } else {
161
+ content = transformContent(content, appName)
162
+ }
163
+
164
+ fs.writeFileSync(destItemPath, content, "utf8")
165
+ } else {
166
+ fs.copyFileSync(srcPath, destItemPath)
167
+ }
168
+ } else if (stat.isDirectory()) {
169
+ // Copy src/, db/, docker/, and .agent/ directories
170
+ if (item === "src" || item === "docker" || item === "db" || item === ".agent") {
171
+ copyDirectory(srcPath, destItemPath, appName, options)
172
+ }
173
+ }
174
+ }
175
+ }
@@ -0,0 +1,25 @@
1
+ import fs from "node:fs"
2
+ import path from "node:path"
3
+
4
+ /**
5
+ * Updates docker-compose.yaml and Dockerfile with custom port
6
+ */
7
+ export function updateDockerFiles(destPath, port) {
8
+ // Update docker-compose.yaml
9
+ const dockerComposePath = path.join(destPath, "docker/docker-compose.yaml")
10
+ if (fs.existsSync(dockerComposePath)) {
11
+ let content = fs.readFileSync(dockerComposePath, "utf8")
12
+ // Replace port mapping (e.g., "8001:8001" -> "9000:9000")
13
+ content = content.replace(/- "\d+:\d+"/g, `- "${port}:${port}"`)
14
+ fs.writeFileSync(dockerComposePath, content, "utf8")
15
+ }
16
+
17
+ // Update Dockerfile
18
+ const dockerfilePath = path.join(destPath, "docker/Dockerfile")
19
+ if (fs.existsSync(dockerfilePath)) {
20
+ let content = fs.readFileSync(dockerfilePath, "utf8")
21
+ // Replace EXPOSE port
22
+ content = content.replace(/EXPOSE \d+/, `EXPOSE ${port}`)
23
+ fs.writeFileSync(dockerfilePath, content, "utf8")
24
+ }
25
+ }
@@ -0,0 +1,137 @@
1
+ import { execSync } from "node:child_process"
2
+ import fs from "node:fs"
3
+ import path from "node:path"
4
+ import { fileURLToPath } from "node:url"
5
+ import { updateServerIndex } from "./auth.mjs"
6
+ import { copyTemplate } from "./copy.mjs"
7
+ import { updateDockerFiles } from "./docker.mjs"
8
+ import { updateEnvFile } from "./pkgjson.mjs"
9
+ // Import modular functions
10
+ import { validateAppName, validateAuthType } from "./validation.mjs"
11
+
12
+ const __filename = fileURLToPath(import.meta.url)
13
+ const __dirname = path.dirname(__filename)
14
+
15
+ /**
16
+ * Main create function
17
+ */
18
+ export default async function create(appName, options = {}) {
19
+ try {
20
+ console.log("[miolo] Creating new miolo app:", appName)
21
+
22
+ // Validate app name
23
+ validateAppName(appName)
24
+
25
+ // Parse options
26
+ const { port, auth: authType = "passport", dest = `./${appName}` } = options
27
+
28
+ // Validate auth method
29
+ validateAuthType(authType)
30
+
31
+ // Get source path (template or miolo-sample for development)
32
+ // In development (monorepo), use miolo-sample directly
33
+ // In production (npm package), use bundled template folder
34
+ let sourcePath = path.resolve(__dirname, "../../../miolo-sample")
35
+ if (!fs.existsSync(sourcePath)) {
36
+ // Fallback to template folder (npm package)
37
+ sourcePath = path.resolve(__dirname, "../../template")
38
+ }
39
+ const destPath = path.resolve(process.cwd(), dest)
40
+
41
+ // Check if source exists
42
+ if (!fs.existsSync(sourcePath)) {
43
+ throw new Error(`Source template not found: ${sourcePath}`)
44
+ }
45
+
46
+ // Check if destination already exists and has content
47
+ if (fs.existsSync(destPath)) {
48
+ // Allow if destination is current directory (.) or an empty directory
49
+ const isCwd = path.resolve(destPath) === process.cwd()
50
+ if (!isCwd) {
51
+ const items = fs.readdirSync(destPath)
52
+ // Filter out hidden files/dirs that are safe to ignore
53
+ const significantItems = items.filter(
54
+ (item) => !item.startsWith(".") && item !== "node_modules"
55
+ )
56
+ if (significantItems.length > 0) {
57
+ throw new Error(`Destination already exists and is not empty: ${destPath}`)
58
+ }
59
+ }
60
+ }
61
+
62
+ console.log("[miolo] Copying template from:", sourcePath)
63
+ console.log("[miolo] Creating app at:", destPath)
64
+ console.log("[miolo] Auth method:", authType)
65
+ if (port) {
66
+ console.log("[miolo] Port:", port)
67
+ }
68
+
69
+ // Copy template files
70
+ copyTemplate(sourcePath, destPath, appName, { authType })
71
+
72
+ console.log("[miolo] Template copied successfully")
73
+
74
+ // Update .env with custom parameters
75
+ updateEnvFile(destPath, appName, { port })
76
+
77
+ // Update docker files with custom port
78
+ if (port) {
79
+ updateDockerFiles(destPath, port)
80
+ }
81
+
82
+ // Update server/miolo/index.mjs with correct auth import
83
+ updateServerIndex(destPath, authType)
84
+
85
+ // Install dependencies
86
+ console.log("[miolo] Installing dependencies...")
87
+ try {
88
+ execSync("npm install", {
89
+ cwd: destPath,
90
+ stdio: "inherit"
91
+ })
92
+ console.log("[miolo] Dependencies installed successfully")
93
+ } catch (_error) {
94
+ console.warn("[miolo] Warning: Failed to install dependencies automatically")
95
+ console.warn('[miolo] Please run "npm install" manually in the project directory')
96
+ }
97
+
98
+ // Initialize database
99
+ const dbInitScript = path.join(destPath, "db/init.sh")
100
+ if (fs.existsSync(dbInitScript)) {
101
+ console.log("")
102
+ console.log("[miolo] Database initialization")
103
+ console.log(
104
+ "[miolo] ⚠️ Note: This step may require sudo password if your user doesn't have CREATEDB permission"
105
+ )
106
+ console.log("[miolo] Database name will be:", appName)
107
+
108
+ // Make script executable
109
+ try {
110
+ fs.chmodSync(dbInitScript, 0o755)
111
+ } catch (_error) {
112
+ // Ignore chmod errors
113
+ }
114
+
115
+ try {
116
+ execSync(`${dbInitScript} ${appName}`, {
117
+ cwd: destPath,
118
+ stdio: "inherit"
119
+ })
120
+ console.log("[miolo] ✅ Database initialized successfully")
121
+ } catch (_error) {
122
+ console.warn("[miolo] ⚠️ Warning: Database initialization failed or was skipped")
123
+ console.warn("[miolo] You can initialize it manually later by running:")
124
+ console.warn(`[miolo] cd ${dest} && ./db/init.sh ${appName}`)
125
+ }
126
+ }
127
+
128
+ console.log("")
129
+ console.log("[miolo] ✅ App created successfully!")
130
+ console.log("[miolo] To get started:")
131
+ console.log(` cd ${dest}`)
132
+ console.log(" npm run dev")
133
+ } catch (error) {
134
+ console.error("[miolo] Error creating app:", error.message)
135
+ throw error
136
+ }
137
+ }
@@ -0,0 +1,69 @@
1
+ import fs from "node:fs"
2
+ import path from "node:path"
3
+ import { fileURLToPath } from "node:url"
4
+
5
+ const __filename = fileURLToPath(import.meta.url)
6
+ const __dirname = path.dirname(__filename)
7
+
8
+ /**
9
+ * Get miolo version from package.json
10
+ */
11
+ export function getMioloVersion() {
12
+ try {
13
+ const mioloPackageJsonPath = path.resolve(__dirname, "../../package.json")
14
+ const mioloPackageJson = JSON.parse(fs.readFileSync(mioloPackageJsonPath, "utf8"))
15
+ return `^${mioloPackageJson.version}`
16
+ } catch (_error) {
17
+ console.warn("[miolo] Warning: Could not read miolo version, using latest")
18
+ return "^3.0.0"
19
+ }
20
+ }
21
+
22
+ /**
23
+ * Transforms package.json content by replacing app name and file:../ references
24
+ */
25
+ export function transformPackageJson(content, appName) {
26
+ // First, replace app name
27
+ let transformed = content.replace(/miolo-sample/g, appName)
28
+
29
+ // Then, replace file:../ references with npm versions
30
+ const version = getMioloVersion()
31
+ transformed = transformed.replace(/"file:\.\.\/(miolo-cli|miolo-react|miolo)"/g, `"${version}"`)
32
+
33
+ return transformed
34
+ }
35
+
36
+ /**
37
+ * Updates the .env and .env.production files with custom parameters
38
+ */
39
+ export function updateEnvFile(destPath, _appName, options = {}) {
40
+ const { port } = options
41
+
42
+ // Update .env
43
+ const envPath = path.join(destPath, ".env")
44
+ if (fs.existsSync(envPath)) {
45
+ let content = fs.readFileSync(envPath, "utf8")
46
+
47
+ // Update port if specified
48
+ if (port) {
49
+ content = content.replace(/MIOLO_PORT=\d+/, `MIOLO_PORT=${port}`)
50
+ }
51
+
52
+ fs.writeFileSync(envPath, content, "utf8")
53
+ } else {
54
+ console.warn("[miolo] Warning: .env file not found")
55
+ }
56
+
57
+ // Update .env.production
58
+ const envProdPath = path.join(destPath, ".env.production")
59
+ if (fs.existsSync(envProdPath)) {
60
+ let content = fs.readFileSync(envProdPath, "utf8")
61
+
62
+ // Update port if specified
63
+ if (port) {
64
+ content = content.replace(/MIOLO_PORT=\d+/, `MIOLO_PORT=${port}`)
65
+ }
66
+
67
+ fs.writeFileSync(envProdPath, content, "utf8")
68
+ }
69
+ }
@@ -0,0 +1,158 @@
1
+ #!/usr/bin/env node
2
+ import fs from "node:fs"
3
+ import path from "node:path"
4
+ import { fileURLToPath } from "node:url"
5
+
6
+ const __filename = fileURLToPath(import.meta.url)
7
+ const __dirname = path.dirname(__filename)
8
+
9
+ console.log("[prepare-template] Starting template preparation...")
10
+
11
+ // Paths
12
+ const mioloSamplePath = path.resolve(__dirname, "../../../miolo-sample")
13
+ const templatePath = path.resolve(__dirname, "../../template")
14
+ const mioloPackageJsonPath = path.resolve(__dirname, "../../package.json")
15
+
16
+ // Read miolo version
17
+ const mioloPackageJson = JSON.parse(fs.readFileSync(mioloPackageJsonPath, "utf8"))
18
+ const version = `^${mioloPackageJson.version}`
19
+
20
+ console.log(`[prepare-template] Using version: ${version}`)
21
+
22
+ // Step 1: Remove existing template directory
23
+ if (fs.existsSync(templatePath)) {
24
+ console.log("[prepare-template] Removing existing template directory...")
25
+ fs.rmSync(templatePath, { recursive: true, force: true })
26
+ }
27
+
28
+ // Step 2: Create template directory
29
+ console.log("[prepare-template] Creating template directory...")
30
+ fs.mkdirSync(templatePath, { recursive: true })
31
+
32
+ // Step 3: Copy files from miolo-sample to template
33
+ console.log("[prepare-template] Copying files from miolo-sample...")
34
+
35
+ // Files to copy (note: .gitignore renamed to gitignore to avoid npm exclusion)
36
+ const filesToCopy = [
37
+ { src: ".env", dest: ".env" },
38
+ { src: ".env.production", dest: ".env.production" },
39
+ { src: ".editorconfig", dest: ".editorconfig" },
40
+ { src: ".gitignore", dest: "gitignore" }, // Rename to avoid npm ignoring it
41
+ { src: "components.json", dest: "components.json" },
42
+ { src: "jsconfig.json", dest: "jsconfig.json" },
43
+ { src: "package.json", dest: "package.json" },
44
+ { src: "biome.json", dest: "biome.json" },
45
+ { src: "postcss.config.js", dest: "postcss.config.js" }
46
+ //{ src: 'vite.config.mjs', dest: 'vite.config.mjs' }
47
+ ]
48
+
49
+ // Copy individual files
50
+ for (const { src, dest } of filesToCopy) {
51
+ const srcFile = path.join(mioloSamplePath, src)
52
+ const destFile = path.join(templatePath, dest)
53
+
54
+ if (fs.existsSync(srcFile)) {
55
+ fs.copyFileSync(srcFile, destFile)
56
+ console.log(`[prepare-template] ✓ Copied ${src}${src !== dest ? ` → ${dest}` : ""}`)
57
+ } else {
58
+ console.warn(`[prepare-template] ⚠ File not found: ${src}`)
59
+ }
60
+ }
61
+
62
+ // Copy directories
63
+ const dirsToCopy = ["src", "docker", "db"]
64
+
65
+ for (const dir of dirsToCopy) {
66
+ const srcDir = path.join(mioloSamplePath, dir)
67
+ const destDir = path.join(templatePath, dir)
68
+
69
+ if (fs.existsSync(srcDir)) {
70
+ copyDirRecursive(srcDir, destDir)
71
+ console.log(`[prepare-template] ✓ Copied ${dir}/`)
72
+ } else {
73
+ console.warn(`[prepare-template] ⚠ Directory not found: ${dir}`)
74
+ }
75
+ }
76
+
77
+ // Copy skills from workspace root
78
+ const skillsSrc = path.resolve(__dirname, "../../../../skills")
79
+ const skillsDest = path.join(templatePath, ".agent", "skills")
80
+ if (fs.existsSync(skillsSrc)) {
81
+ fs.mkdirSync(path.join(templatePath, ".agent"), { recursive: true })
82
+ copyDirRecursive(skillsSrc, skillsDest)
83
+ console.log("[prepare-template] ✓ Copied skills/")
84
+ } else {
85
+ console.warn("[prepare-template] ⚠ Skills directory not found at", skillsSrc)
86
+ }
87
+
88
+ // Step 4: Update package.json versions
89
+ console.log("[prepare-template] Updating package.json versions...")
90
+ const templatePackageJsonPath = path.join(templatePath, "package.json")
91
+ const templatePackageJson = JSON.parse(fs.readFileSync(templatePackageJsonPath, "utf8"))
92
+
93
+ const replacements = {
94
+ "file:../miolo": version,
95
+ "file:../miolo-cli": version,
96
+ "file:../miolo-react": version
97
+ }
98
+
99
+ let modified = false
100
+
101
+ // Update dependencies
102
+ if (templatePackageJson.dependencies) {
103
+ for (const [pkg, currentVersion] of Object.entries(templatePackageJson.dependencies)) {
104
+ if (replacements[currentVersion]) {
105
+ console.log(
106
+ `[prepare-template] ${pkg}: ${currentVersion} → ${replacements[currentVersion]}`
107
+ )
108
+ templatePackageJson.dependencies[pkg] = replacements[currentVersion]
109
+ modified = true
110
+ }
111
+ }
112
+ }
113
+
114
+ // Update devDependencies
115
+ if (templatePackageJson.devDependencies) {
116
+ for (const [pkg, currentVersion] of Object.entries(templatePackageJson.devDependencies)) {
117
+ if (replacements[currentVersion]) {
118
+ console.log(
119
+ `[prepare-template] ${pkg}: ${currentVersion} → ${replacements[currentVersion]}`
120
+ )
121
+ templatePackageJson.devDependencies[pkg] = replacements[currentVersion]
122
+ modified = true
123
+ }
124
+ }
125
+ }
126
+
127
+ if (modified) {
128
+ fs.writeFileSync(
129
+ templatePackageJsonPath,
130
+ JSON.stringify(templatePackageJson, null, 2) + "\n",
131
+ "utf8"
132
+ )
133
+ console.log("[prepare-template] ✅ Template package.json updated")
134
+ } else {
135
+ console.log("[prepare-template] No file:../ references found to replace")
136
+ }
137
+
138
+ console.log("[prepare-template] ✅ Template synchronized from miolo-sample")
139
+
140
+ // Helper function to copy directories recursively
141
+ function copyDirRecursive(src, dest) {
142
+ if (!fs.existsSync(dest)) {
143
+ fs.mkdirSync(dest, { recursive: true })
144
+ }
145
+
146
+ const entries = fs.readdirSync(src, { withFileTypes: true })
147
+
148
+ for (const entry of entries) {
149
+ const srcPath = path.join(src, entry.name)
150
+ const destPath = path.join(dest, entry.name)
151
+
152
+ if (entry.isDirectory()) {
153
+ copyDirRecursive(srcPath, destPath)
154
+ } else {
155
+ fs.copyFileSync(srcPath, destPath)
156
+ }
157
+ }
158
+ }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Validation helper functions for create command
3
+ */
4
+
5
+ /**
6
+ * Validates app name (alphanumeric + hyphens/underscores)
7
+ */
8
+ export function validateAppName(name) {
9
+ if (!name) {
10
+ throw new Error("App name is required")
11
+ }
12
+ if (!/^[a-z0-9-_]+$/i.test(name)) {
13
+ throw new Error("App name must contain only alphanumeric characters, hyphens, and underscores")
14
+ }
15
+ return true
16
+ }
17
+
18
+ /**
19
+ * Validates auth method
20
+ */
21
+ export function validateAuthType(authType) {
22
+ const validAuthTypes = ["passport", "basic", "guest"]
23
+ if (!validAuthTypes.includes(authType)) {
24
+ throw new Error(`Invalid auth type: ${authType}. Valid options: ${validAuthTypes.join(", ")}`)
25
+ }
26
+ return true
27
+ }
@@ -1,7 +1,6 @@
1
-
2
- import path from 'node:path'
3
- import { fileURLToPath } from 'node:url'
4
- import { fork } from 'node:child_process'
1
+ import { fork } from "node:child_process"
2
+ import path from "node:path"
3
+ import { fileURLToPath } from "node:url"
5
4
 
6
5
  const __filename = fileURLToPath(import.meta.url)
7
6
  const __dirname = path.dirname(__filename)
@@ -14,18 +13,32 @@ function _isProcessRunning(pid) {
14
13
  try {
15
14
  process.kill(pid, 0) // Sending a signal 0 does nothing but checks if the process exists
16
15
  return true
17
- } catch (error) {
16
+ } catch (_) {
18
17
  return false
19
18
  }
20
19
  }
21
20
 
22
- async function startDevServerProcess({ appName }) {
23
- const serverPath = path.join(__dirname, './dev_start.mjs')
24
- serverProcess = fork(serverPath)
21
+ async function startDevServerProcess({ appName, debug }) {
22
+ const serverPath = path.join(__dirname, "./dev_start.mjs")
23
+
24
+ const execArgv = []
25
+ if (debug === true) {
26
+ const newDebugPort = process.debugPort + 1
27
+ console.log(
28
+ `[${appName}][dev] Debugging enabled. Attaching debugger to child process on port ${newDebugPort}`
29
+ )
30
+ execArgv.push(`--inspect=${newDebugPort}`)
31
+ }
32
+
33
+ serverProcess = fork(serverPath, [], {
34
+ execArgv: execArgv
35
+ })
25
36
 
26
- console.log(`[${appName}][dev] Server process started with pid ${serverProcess.pid} from ${process.pid}`)
37
+ console.log(
38
+ `[${appName}][dev] Server process started with pid ${serverProcess.pid} from ${process.pid}`
39
+ )
27
40
 
28
- serverProcess.on('exit', (code, args) => {
41
+ serverProcess.on("exit", (code, args) => {
29
42
  console.log(`[${appName}][dev] Server process exited with code ${code} - ${args}`)
30
43
  serverProcess = null
31
44
  // Puedes implementar lógica de reintento aquí si es necesario
@@ -38,34 +51,36 @@ async function startDevServerProcess({ appName }) {
38
51
  // } else if (code !== 0) {
39
52
  // console.error(`[${serverName}][dev] Server failed to start after ${maxRetries} retries. Exiting.`)
40
53
  // process.exit(1)
41
- // }
54
+ // }
42
55
  })
43
56
 
44
- serverProcess.on('message', (message) => {
45
- if (message === 'miolo_restart') {
57
+ serverProcess.on("message", (message) => {
58
+ if (message === "miolo_restart") {
46
59
  console.log(`[${appName}][dev] Received restart signal. Restarting server...`)
47
60
 
48
61
  const pidToKill = serverProcess ? serverProcess.pid : null
49
62
  if (pidToKill) {
50
63
  console.log(`[${appName}][dev] Killing process with PID: ${pidToKill}`)
51
64
  // Clean kill
52
- process.kill(pidToKill, 'SIGTERM')
65
+ process.kill(pidToKill, "SIGTERM")
53
66
 
54
67
  // Harder if still alive
55
68
  setTimeout(() => {
56
69
  if (_isProcessRunning(pidToKill)) {
57
- process.kill(pidToKill, 'SIGKILL')
70
+ process.kill(pidToKill, "SIGKILL")
58
71
  }
59
72
  }, 2000)
60
73
  }
61
74
 
62
- startDevServerProcess({ appName }) // Inicia un nuevo proceso
75
+ startDevServerProcess({ appName, debug }) // Inicia un nuevo proceso
63
76
  }
64
77
  })
65
78
  }
66
79
 
80
+ export default async function (appName = undefined, debug = false) {
81
+ // Based on command line params or .env
82
+ appName = appName || process.env.MIOLO_NAME
67
83
 
68
- export default async function(appName) {
69
84
  console.log(`[${appName}][dev] Running DEV server`)
70
- await startDevServerProcess({ appName })
85
+ await startDevServerProcess({ appName, debug })
71
86
  }