miolo 3.0.0-beta.147 → 3.0.0-beta.149

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.
@@ -0,0 +1,24 @@
1
+ import fs from 'node:fs'
2
+ import path from 'node:path'
3
+
4
+ /**
5
+ * Updates server/miolo/index.mjs with correct auth import
6
+ */
7
+ export function updateServerIndex(destPath, authMethod) {
8
+ const serverIndexPath = path.join(destPath, 'src/server/miolo/index.mjs')
9
+
10
+ if (!fs.existsSync(serverIndexPath)) {
11
+ console.warn('[miolo] Warning: server/miolo/index.mjs not found, skipping auth import update')
12
+ return
13
+ }
14
+
15
+ let content = fs.readFileSync(serverIndexPath, 'utf8')
16
+
17
+ // Replace the auth import line
18
+ content = content.replace(
19
+ /import auth from ['"]\.\/auth\/.*?['"]/,
20
+ `import auth from './auth/${authMethod}.mjs'`
21
+ )
22
+
23
+ fs.writeFileSync(serverIndexPath, content, 'utf8')
24
+ }
@@ -0,0 +1,156 @@
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', '.jsx', '.mjs', '.ts', '.tsx',
8
+ '.json', '.env', '.md', '.html',
9
+ '.css', '.scss', '.sass',
10
+ '.yml', '.yaml', '.toml',
11
+ '.txt', '.gitignore', '.editorconfig'
12
+ ]
13
+
14
+ // Files and directories to exclude from copying
15
+ const EXCLUDE_PATTERNS = [
16
+ 'node_modules',
17
+ 'dist',
18
+ 'build',
19
+ '.git'
20
+ ]
21
+
22
+ /**
23
+ * Checks if a file is a text file based on extension
24
+ */
25
+ export function isTextFile(filePath) {
26
+ const ext = path.extname(filePath).toLowerCase()
27
+ return TEXT_EXTENSIONS.includes(ext) || path.basename(filePath).startsWith('.')
28
+ }
29
+
30
+ /**
31
+ * Checks if a path should be excluded
32
+ */
33
+ export function shouldExclude(itemPath) {
34
+ const basename = path.basename(itemPath)
35
+ return EXCLUDE_PATTERNS.some(pattern => basename === pattern)
36
+ }
37
+
38
+ /**
39
+ * Replaces all occurrences of miolo-sample with the new app name
40
+ */
41
+ export function transformContent(content, appName) {
42
+ return content.replace(/miolo-sample/g, appName)
43
+ }
44
+
45
+ /**
46
+ * Copies a directory recursively with transformations
47
+ */
48
+ export function copyDirectory(src, dest, appName, options = {}) {
49
+ const { authMethod } = options
50
+
51
+ // Create destination if it doesn't exist
52
+ if (!fs.existsSync(dest)) {
53
+ fs.mkdirSync(dest, { recursive: true })
54
+ }
55
+
56
+ const items = fs.readdirSync(src)
57
+
58
+ for (const item of items) {
59
+ const srcPath = path.join(src, item)
60
+ const destPath = path.join(dest, item)
61
+ const stat = fs.statSync(srcPath)
62
+
63
+ // Skip excluded items
64
+ if (shouldExclude(srcPath)) {
65
+ continue
66
+ }
67
+
68
+ if (stat.isDirectory()) {
69
+ // Special handling for auth directory
70
+ if (srcPath.endsWith('src/server/miolo/auth')) {
71
+ // Create auth directory
72
+ fs.mkdirSync(destPath, { recursive: true })
73
+
74
+ // Copy only the selected auth file
75
+ const authFile = `${authMethod}.mjs`
76
+ const srcAuthFile = path.join(srcPath, authFile)
77
+ const destAuthFile = path.join(destPath, authFile)
78
+
79
+ if (fs.existsSync(srcAuthFile)) {
80
+ let content = fs.readFileSync(srcAuthFile, 'utf8')
81
+ content = transformContent(content, appName)
82
+ fs.writeFileSync(destAuthFile, content, 'utf8')
83
+ } else {
84
+ console.warn(`[miolo] Warning: Auth file ${authFile} not found, skipping`)
85
+ }
86
+ continue
87
+ }
88
+
89
+ // Recursively copy directories
90
+ copyDirectory(srcPath, destPath, appName, options)
91
+ } else {
92
+ // Copy and transform files
93
+ if (isTextFile(srcPath)) {
94
+ let content = fs.readFileSync(srcPath, 'utf8')
95
+
96
+ // Special handling for package.json to also replace versions
97
+ if (srcPath.endsWith('package.json')) {
98
+ content = transformPackageJson(content, appName)
99
+ } else {
100
+ content = transformContent(content, appName)
101
+ }
102
+
103
+ fs.writeFileSync(destPath, content, 'utf8')
104
+ } else {
105
+ // Binary files - just copy
106
+ fs.copyFileSync(srcPath, destPath)
107
+ }
108
+ }
109
+ }
110
+ }
111
+
112
+ /**
113
+ * Copies only root-level files and specified directories
114
+ */
115
+ export function copyTemplate(sourcePath, destPath, appName, options = {}) {
116
+ // Create destination directory
117
+ if (!fs.existsSync(destPath)) {
118
+ fs.mkdirSync(destPath, { recursive: true })
119
+ }
120
+
121
+ // Copy root-level files
122
+ const items = fs.readdirSync(sourcePath)
123
+
124
+ for (const item of items) {
125
+ const srcPath = path.join(sourcePath, item)
126
+ const destItemPath = path.join(destPath, item)
127
+ const stat = fs.statSync(srcPath)
128
+
129
+ if (shouldExclude(srcPath)) {
130
+ continue
131
+ }
132
+
133
+ if (stat.isFile()) {
134
+ // Copy root-level files
135
+ if (isTextFile(srcPath)) {
136
+ let content = fs.readFileSync(srcPath, 'utf8')
137
+
138
+ // Special handling for package.json to also replace versions
139
+ if (srcPath.endsWith('package.json')) {
140
+ content = transformPackageJson(content, appName)
141
+ } else {
142
+ content = transformContent(content, appName)
143
+ }
144
+
145
+ fs.writeFileSync(destItemPath, content, 'utf8')
146
+ } else {
147
+ fs.copyFileSync(srcPath, destItemPath)
148
+ }
149
+ } else if (stat.isDirectory()) {
150
+ // Only copy src/ and docker/ directories
151
+ if (item === 'src' || item === 'docker') {
152
+ copyDirectory(srcPath, destItemPath, appName, options)
153
+ }
154
+ }
155
+ }
156
+ }
@@ -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,112 @@
1
+ import fs from 'node:fs'
2
+ import path from 'node:path'
3
+ import { fileURLToPath } from 'node:url'
4
+ import { execSync } from 'node:child_process'
5
+
6
+ // Import modular functions
7
+ import { validateAppName, validateAuthMethod } from './validation.mjs'
8
+ import { updateEnvFile } from './pkgjson.mjs'
9
+ import { updateDockerFiles } from './docker.mjs'
10
+ import { updateServerIndex } from './auth.mjs'
11
+ import { copyTemplate } from './copy.mjs'
12
+
13
+ const __filename = fileURLToPath(import.meta.url)
14
+ const __dirname = path.dirname(__filename)
15
+
16
+ /**
17
+ * Main create function
18
+ */
19
+ export default async function create(appName, options = {}) {
20
+ try {
21
+ console.log('[miolo] Creating new miolo app:', appName)
22
+
23
+ // Validate app name
24
+ validateAppName(appName)
25
+
26
+ // Parse options
27
+ const {
28
+ port,
29
+ auth: authMethod = 'credentials',
30
+ dest = `./${appName}`
31
+ } = options
32
+
33
+ // Validate auth method
34
+ validateAuthMethod(authMethod)
35
+
36
+ // Get source path (template or miolo-sample for development)
37
+ // In development (monorepo), use miolo-sample directly
38
+ // In production (npm package), use bundled template folder
39
+ let sourcePath = path.resolve(__dirname, '../../../miolo-sample')
40
+ if (!fs.existsSync(sourcePath)) {
41
+ // Fallback to template folder (npm package)
42
+ sourcePath = path.resolve(__dirname, '../../template')
43
+ }
44
+ const destPath = path.resolve(process.cwd(), dest)
45
+
46
+ // Check if source exists
47
+ if (!fs.existsSync(sourcePath)) {
48
+ throw new Error(`Source template not found: ${sourcePath}`)
49
+ }
50
+
51
+ // Check if destination already exists and has content
52
+ if (fs.existsSync(destPath)) {
53
+ // Allow if destination is current directory (.) or an empty directory
54
+ const isCwd = path.resolve(destPath) === process.cwd()
55
+ if (!isCwd) {
56
+ const items = fs.readdirSync(destPath)
57
+ // Filter out hidden files/dirs that are safe to ignore
58
+ const significantItems = items.filter(item =>
59
+ !item.startsWith('.') && item !== 'node_modules'
60
+ )
61
+ if (significantItems.length > 0) {
62
+ throw new Error(`Destination already exists and is not empty: ${destPath}`)
63
+ }
64
+ }
65
+ }
66
+
67
+ console.log('[miolo] Copying template from:', sourcePath)
68
+ console.log('[miolo] Creating app at:', destPath)
69
+ console.log('[miolo] Auth method:', authMethod)
70
+ if (port) {
71
+ console.log('[miolo] Port:', port)
72
+ }
73
+
74
+ // Copy template files
75
+ copyTemplate(sourcePath, destPath, appName, { authMethod })
76
+
77
+ console.log('[miolo] Template copied successfully')
78
+
79
+ // Update .env with custom parameters
80
+ updateEnvFile(destPath, appName, { port })
81
+
82
+ // Update docker files with custom port
83
+ if (port) {
84
+ updateDockerFiles(destPath, port)
85
+ }
86
+
87
+ // Update server/miolo/index.mjs with correct auth import
88
+ updateServerIndex(destPath, authMethod)
89
+
90
+ // Install dependencies
91
+ console.log('[miolo] Installing dependencies...')
92
+ try {
93
+ execSync('npm install', {
94
+ cwd: destPath,
95
+ stdio: 'inherit'
96
+ })
97
+ console.log('[miolo] Dependencies installed successfully')
98
+ } catch (_error) {
99
+ console.warn('[miolo] Warning: Failed to install dependencies automatically')
100
+ console.warn('[miolo] Please run "npm install" manually in the project directory')
101
+ }
102
+
103
+ console.log('[miolo] ✅ App created successfully!')
104
+ console.log('[miolo] To get started:')
105
+ console.log(` cd ${dest}`)
106
+ console.log(' npm run dev')
107
+
108
+ } catch (error) {
109
+ console.error('[miolo] Error creating app:', error.message)
110
+ throw error
111
+ }
112
+ }
@@ -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,143 @@
1
+ #!/usr/bin/env node
2
+ import fs from 'fs'
3
+ import path from 'path'
4
+ import {fileURLToPath } from '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
36
+ const filesToCopy = [
37
+ '.env',
38
+ '.env.production',
39
+ '.editorconfig',
40
+ '.gitignore',
41
+ 'components.json',
42
+ 'jsconfig.json',
43
+ 'package.json',
44
+ 'eslint.config.js',
45
+ 'postcss.config.js',
46
+ 'vite.config.mjs'
47
+ ]
48
+
49
+ // Copy individual files
50
+ for (const file of filesToCopy) {
51
+ const srcFile = path.join(mioloSamplePath, file)
52
+ const destFile = path.join(templatePath, file)
53
+
54
+ if (fs.existsSync(srcFile)) {
55
+ fs.copyFileSync(srcFile, destFile)
56
+ console.log(`[prepare-template] ✓ Copied ${file}`)
57
+ } else {
58
+ console.warn(`[prepare-template] ⚠ File not found: ${file}`)
59
+ }
60
+ }
61
+
62
+ // Copy directories
63
+ const dirsToCopy = ['src', 'docker']
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
+ // Step 4: Update package.json versions
78
+ console.log('[prepare-template] Updating package.json versions...')
79
+ const templatePackageJsonPath = path.join(templatePath, 'package.json')
80
+ const templatePackageJson = JSON.parse(fs.readFileSync(templatePackageJsonPath, 'utf8'))
81
+
82
+ const replacements = {
83
+ 'file:../miolo': version,
84
+ 'file:../miolo-cli': version,
85
+ 'file:../miolo-react': version
86
+ }
87
+
88
+ let modified = false
89
+
90
+ // Update dependencies
91
+ if (templatePackageJson.dependencies) {
92
+ for (const [pkg, currentVersion] of Object.entries(templatePackageJson.dependencies)) {
93
+ if (replacements[currentVersion]) {
94
+ console.log(`[prepare-template] ${pkg}: ${currentVersion} → ${replacements[currentVersion]}`)
95
+ templatePackageJson.dependencies[pkg] = replacements[currentVersion]
96
+ modified = true
97
+ }
98
+ }
99
+ }
100
+
101
+ // Update devDependencies
102
+ if (templatePackageJson.devDependencies) {
103
+ for (const [pkg, currentVersion] of Object.entries(templatePackageJson.devDependencies)) {
104
+ if (replacements[currentVersion]) {
105
+ console.log(`[prepare-template] ${pkg}: ${currentVersion} → ${replacements[currentVersion]}`)
106
+ templatePackageJson.devDependencies[pkg] = replacements[currentVersion]
107
+ modified = true
108
+ }
109
+ }
110
+ }
111
+
112
+ if (modified) {
113
+ fs.writeFileSync(
114
+ templatePackageJsonPath,
115
+ JSON.stringify(templatePackageJson, null, 2) + '\n',
116
+ 'utf8'
117
+ )
118
+ console.log('[prepare-template] ✅ Template package.json updated')
119
+ } else {
120
+ console.log('[prepare-template] No file:../ references found to replace')
121
+ }
122
+
123
+ console.log('[prepare-template] ✅ Template synchronized from miolo-sample')
124
+
125
+ // Helper function to copy directories recursively
126
+ function copyDirRecursive(src, dest) {
127
+ if (!fs.existsSync(dest)) {
128
+ fs.mkdirSync(dest, { recursive: true })
129
+ }
130
+
131
+ const entries = fs.readdirSync(src, { withFileTypes: true })
132
+
133
+ for (const entry of entries) {
134
+ const srcPath = path.join(src, entry.name)
135
+ const destPath = path.join(dest, entry.name)
136
+
137
+ if (entry.isDirectory()) {
138
+ copyDirRecursive(srcPath, destPath)
139
+ } else {
140
+ fs.copyFileSync(srcPath, destPath)
141
+ }
142
+ }
143
+ }
@@ -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 validateAuthMethod(authMethod) {
22
+ const validAuthMethods = ['credentials', 'basic', 'guest']
23
+ if (!validAuthMethods.includes(authMethod)) {
24
+ throw new Error(`Invalid auth method: ${authMethod}. Valid options: ${validAuthMethods.join(', ')}`)
25
+ }
26
+ return true
27
+ }
package/bin/index.mjs CHANGED
@@ -18,7 +18,7 @@ async function main() {
18
18
  dest: args.d || args.dest // Support both --d and --dest
19
19
  }
20
20
 
21
- const createAppHandler = (await import ('./create/create.mjs')).default
21
+ const createAppHandler = (await import ('./create/index.mjs')).default
22
22
  await createAppHandler(createAppName, createOptions)
23
23
  return
24
24
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "miolo",
3
- "version": "3.0.0-beta.147",
3
+ "version": "3.0.0-beta.149",
4
4
  "description": "all-in-one koa-based server",
5
5
  "author": "Donato Lorenzo <donato@afialapis.com>",
6
6
  "contributors": [
@@ -38,9 +38,8 @@
38
38
  "reset": "rm -fr package-lock.json npm-lock.yaml dist/* && npm i",
39
39
  "clean": "rm -fr ./dist/*",
40
40
  "bundle": "npx xeira bundle --target=node --source_index=./src/index.mjs --bundle_folder=./dist --bundle_name=miolo --bundle_extension=node.mjs",
41
- "dist": "npm run clean && npm run bundle",
42
- "prepare-template": "rm -rf template && mkdir -p template && cp -r ../miolo-sample/.env ../miolo-sample/.env.production ../miolo-sample/.editorconfig ../miolo-sample/.gitignore ../miolo-sample/*.json ../miolo-sample/src ../miolo-sample/docker template/ && echo 'Template synchronized from miolo-sample'",
43
- "prepublishOnly": "npm run prepare-template"
41
+ "prepare-template": "node bin/create/prepare-template.mjs",
42
+ "dist": "npm run clean && npm run bundle && npm run prepare-template"
44
43
  },
45
44
  "dependencies": {
46
45
  "@babel/plugin-proposal-decorators": "^7.29.0",
@@ -0,0 +1,25 @@
1
+ import {eslintConfig} from 'xeira'
2
+
3
+
4
+ export default [
5
+ // Extiende la configuración base de xeira
6
+ ...eslintConfig,
7
+
8
+ // Opcional: añade reglas o configuraciones específicas para este proyecto
9
+ {
10
+ files: ['src/**/*.mjs', 'src/**/*.jsx'],
11
+ rules: {
12
+ 'react/display-name': 0,
13
+ 'no-unused-vars': [
14
+ "warn", // or "error"
15
+ {
16
+ "argsIgnorePattern": "^_",
17
+ "varsIgnorePattern": "^_",
18
+ "caughtErrorsIgnorePattern": "^_"
19
+ }
20
+ ],
21
+ // React 19 seems to not need this, but it is required by our build approach
22
+ 'react/react-in-jsx-scope': 'error',
23
+ },
24
+ },
25
+ ];
@@ -43,9 +43,10 @@
43
43
  "clsx": "^2.1.1",
44
44
  "farrapa": "^3.0.0-beta.3",
45
45
  "intre": "^3.0.0-beta.3",
46
+ "joi": "^18.0.2",
46
47
  "lucide-react": "^0.563.0",
47
- "miolo-cli": "file:../miolo-cli",
48
- "miolo-react": "file:../miolo-react",
48
+ "miolo-cli": "^3.0.0-beta.149",
49
+ "miolo-react": "^3.0.0-beta.149",
49
50
  "next-themes": "^0.4.6",
50
51
  "radix-ui": "^1.4.3",
51
52
  "react": "^19.2.4",
@@ -59,7 +60,7 @@
59
60
  "tw-animate-css": "^1.4.0"
60
61
  },
61
62
  "devDependencies": {
62
- "miolo": "file:../miolo",
63
+ "miolo": "^3.0.0-beta.149",
63
64
  "sass-embedded": "^1.97.3",
64
65
  "xeira": "^2.0.0-beta.10"
65
66
  },
@@ -0,0 +1,9 @@
1
+ import tailwindcss from '@tailwindcss/postcss'
2
+ import autoprefixer from 'autoprefixer'
3
+
4
+ export default {
5
+ plugins: [
6
+ tailwindcss,
7
+ autoprefixer,
8
+ ],
9
+ }
@@ -1,340 +0,0 @@
1
- import fs from 'node:fs'
2
- import path from 'node:path'
3
- import { fileURLToPath } from 'node:url'
4
- import { execSync } from 'node:child_process'
5
-
6
- const __filename = fileURLToPath(import.meta.url)
7
- const __dirname = path.dirname(__filename)
8
-
9
- // Text file extensions to transform
10
- const TEXT_EXTENSIONS = [
11
- '.js', '.jsx', '.mjs', '.ts', '.tsx',
12
- '.json', '.env', '.md', '.html',
13
- '.css', '.scss', '.sass',
14
- '.yml', '.yaml', '.toml',
15
- '.txt', '.gitignore', '.editorconfig'
16
- ]
17
-
18
- // Files and directories to exclude from copying
19
- const EXCLUDE_PATTERNS = [
20
- 'node_modules',
21
- 'dist',
22
- 'build',
23
- '.git'
24
- ]
25
-
26
- /**
27
- * Validates app name (alphanumeric + hyphens/underscores)
28
- */
29
- function validateAppName(name) {
30
- if (!name) {
31
- throw new Error('App name is required')
32
- }
33
- if (!/^[a-z0-9-_]+$/i.test(name)) {
34
- throw new Error('App name must contain only alphanumeric characters, hyphens, and underscores')
35
- }
36
- return true
37
- }
38
-
39
- /**
40
- * Checks if a file is a text file based on extension
41
- */
42
- function isTextFile(filePath) {
43
- const ext = path.extname(filePath).toLowerCase()
44
- return TEXT_EXTENSIONS.includes(ext) || path.basename(filePath).startsWith('.')
45
- }
46
-
47
- /**
48
- * Checks if a path should be excluded
49
- */
50
- function shouldExclude(itemPath) {
51
- const basename = path.basename(itemPath)
52
- return EXCLUDE_PATTERNS.some(pattern => basename === pattern)
53
- }
54
-
55
- /**
56
- * Replaces all occurrences of miolo-sample with the new app name
57
- */
58
- function transformContent(content, appName) {
59
- return content.replace(/miolo-sample/g, appName)
60
- }
61
-
62
- /**
63
- * Copies a directory recursively with transformations
64
- */
65
- function copyDirectory(src, dest, appName, options = {}) {
66
- const { authMethod = 'credentials' } = options
67
-
68
- // Create destination directory
69
- if (!fs.existsSync(dest)) {
70
- fs.mkdirSync(dest, { recursive: true })
71
- }
72
-
73
- const items = fs.readdirSync(src)
74
-
75
- for (const item of items) {
76
- const srcPath = path.join(src, item)
77
- const destPath = path.join(dest, item)
78
- const stat = fs.statSync(srcPath)
79
-
80
- // Skip excluded items
81
- if (shouldExclude(srcPath)) {
82
- continue
83
- }
84
-
85
- if (stat.isDirectory()) {
86
- // Special handling for auth directory
87
- if (srcPath.endsWith('src/server/miolo/auth')) {
88
- // Create auth directory
89
- fs.mkdirSync(destPath, { recursive: true })
90
-
91
- // Copy only the selected auth file
92
- const authFile = `${authMethod}.mjs`
93
- const srcAuthFile = path.join(srcPath, authFile)
94
- const destAuthFile = path.join(destPath, authFile)
95
-
96
- if (fs.existsSync(srcAuthFile)) {
97
- let content = fs.readFileSync(srcAuthFile, 'utf8')
98
- content = transformContent(content, appName)
99
- fs.writeFileSync(destAuthFile, content, 'utf8')
100
- } else {
101
- console.warn(`[miolo] Warning: Auth file ${authFile} not found, skipping`)
102
- }
103
- continue
104
- }
105
-
106
- // Recursively copy directories
107
- copyDirectory(srcPath, destPath, appName, options)
108
- } else {
109
- // Copy and transform files
110
- if (isTextFile(srcPath)) {
111
- let content = fs.readFileSync(srcPath, 'utf8')
112
- content = transformContent(content, appName)
113
- fs.writeFileSync(destPath, content, 'utf8')
114
- } else {
115
- // Binary files - just copy
116
- fs.copyFileSync(srcPath, destPath)
117
- }
118
- }
119
- }
120
- }
121
-
122
- /**
123
- * Updates the .env file with custom parameters
124
- */
125
- function updateEnvFile(destPath, appName, options = {}) {
126
- const { port } = options
127
-
128
- // Update .env
129
- const envPath = path.join(destPath, '.env')
130
- if (fs.existsSync(envPath)) {
131
- let content = fs.readFileSync(envPath, 'utf8')
132
-
133
- // Update port if specified
134
- if (port) {
135
- content = content.replace(/MIOLO_PORT=\d+/, `MIOLO_PORT=${port}`)
136
- }
137
-
138
- fs.writeFileSync(envPath, content, 'utf8')
139
- } else {
140
- console.warn('[miolo] Warning: .env file not found')
141
- }
142
-
143
- // Update .env.production
144
- const envProdPath = path.join(destPath, '.env.production')
145
- if (fs.existsSync(envProdPath)) {
146
- let content = fs.readFileSync(envProdPath, 'utf8')
147
-
148
- // Update port if specified
149
- if (port) {
150
- content = content.replace(/MIOLO_PORT=\d+/, `MIOLO_PORT=${port}`)
151
- }
152
-
153
- fs.writeFileSync(envProdPath, content, 'utf8')
154
- }
155
- }
156
-
157
- /**
158
- * Updates server/miolo/index.mjs to import the correct auth method
159
- */
160
- function updateServerIndex(destPath, authMethod) {
161
- const indexPath = path.join(destPath, 'src/server/miolo/index.mjs')
162
-
163
- if (!fs.existsSync(indexPath)) {
164
- console.warn('[miolo] Warning: src/server/miolo/index.mjs not found')
165
- return
166
- }
167
-
168
- let content = fs.readFileSync(indexPath, 'utf8')
169
-
170
- // Replace the auth import
171
- content = content.replace(
172
- /import auth from ['"]\.\/auth\/\w+\.mjs['"]/,
173
- `import auth from './auth/${authMethod}.mjs'`
174
- )
175
-
176
- fs.writeFileSync(indexPath, content, 'utf8')
177
- }
178
-
179
- /**
180
- * Updates docker files with custom port
181
- */
182
- function updateDockerFiles(destPath, port) {
183
- // Update docker-compose.yaml
184
- const dockerComposePath = path.join(destPath, 'docker/docker-compose.yaml')
185
- if (fs.existsSync(dockerComposePath)) {
186
- let content = fs.readFileSync(dockerComposePath, 'utf8')
187
- // Replace port mapping (e.g., "8001:8001" -> "9000:9000")
188
- content = content.replace(/- "\d+:\d+"/, `- "${port}:${port}"`)
189
- fs.writeFileSync(dockerComposePath, content, 'utf8')
190
- }
191
-
192
- // Update Dockerfile
193
- const dockerfilePath = path.join(destPath, 'docker/Dockerfile')
194
- if (fs.existsSync(dockerfilePath)) {
195
- let content = fs.readFileSync(dockerfilePath, 'utf8')
196
- // Replace EXPOSE port
197
- content = content.replace(/EXPOSE \d+/, `EXPOSE ${port}`)
198
- fs.writeFileSync(dockerfilePath, content, 'utf8')
199
- }
200
- }
201
-
202
- /**
203
- * Copies only root-level files and specified directories
204
- */
205
- function copyTemplate(sourcePath, destPath, appName, options = {}) {
206
- // Create destination directory
207
- if (!fs.existsSync(destPath)) {
208
- fs.mkdirSync(destPath, { recursive: true })
209
- }
210
-
211
- // Copy root-level files
212
- const items = fs.readdirSync(sourcePath)
213
-
214
- for (const item of items) {
215
- const srcPath = path.join(sourcePath, item)
216
- const destItemPath = path.join(destPath, item)
217
- const stat = fs.statSync(srcPath)
218
-
219
- if (shouldExclude(srcPath)) {
220
- continue
221
- }
222
-
223
- if (stat.isFile()) {
224
- // Copy root-level files
225
- if (isTextFile(srcPath)) {
226
- let content = fs.readFileSync(srcPath, 'utf8')
227
- content = transformContent(content, appName)
228
- fs.writeFileSync(destItemPath, content, 'utf8')
229
- } else {
230
- fs.copyFileSync(srcPath, destItemPath)
231
- }
232
- } else if (stat.isDirectory()) {
233
- // Only copy src/ and docker/ directories
234
- if (item === 'src' || item === 'docker') {
235
- copyDirectory(srcPath, destItemPath, appName, options)
236
- }
237
- }
238
- }
239
- }
240
-
241
- /**
242
- * Main create function
243
- */
244
- export default async function create(appName, options = {}) {
245
- try {
246
- console.log('[miolo] Creating new miolo app:', appName)
247
-
248
- // Validate app name
249
- validateAppName(appName)
250
-
251
- // Parse options
252
- const {
253
- port,
254
- auth: authMethod = 'credentials',
255
- dest = `./${appName}`
256
- } = options
257
-
258
- // Validate auth method
259
- const validAuthMethods = ['credentials', 'basic', 'guest']
260
- if (!validAuthMethods.includes(authMethod)) {
261
- throw new Error(`Invalid auth method: ${authMethod}. Valid options: ${validAuthMethods.join(', ')}`)
262
- }
263
-
264
- // Get source path (template or miolo-sample for development)
265
- // In development (monorepo), use miolo-sample directly
266
- // In production (npm package), use bundled template folder
267
- let sourcePath = path.resolve(__dirname, '../../../miolo-sample')
268
- if (!fs.existsSync(sourcePath)) {
269
- // Fallback to template folder (npm package)
270
- sourcePath = path.resolve(__dirname, '../../template')
271
- }
272
- const destPath = path.resolve(process.cwd(), dest)
273
-
274
- // Check if source exists
275
- if (!fs.existsSync(sourcePath)) {
276
- throw new Error(`Source template not found: ${sourcePath}`)
277
- }
278
-
279
- // Check if destination already exists and has content
280
- if (fs.existsSync(destPath)) {
281
- // Allow if destination is current directory (.) or an empty directory
282
- const isCwd = path.resolve(destPath) === process.cwd()
283
- if (!isCwd) {
284
- const items = fs.readdirSync(destPath)
285
- // Filter out hidden files/dirs that are safe to ignore
286
- const significantItems = items.filter(item =>
287
- !item.startsWith('.') && item !== 'node_modules'
288
- )
289
- if (significantItems.length > 0) {
290
- throw new Error(`Destination already exists and is not empty: ${destPath}`)
291
- }
292
- }
293
- }
294
-
295
- console.log('[miolo] Copying template from:', sourcePath)
296
- console.log('[miolo] Creating app at:', destPath)
297
- console.log('[miolo] Auth method:', authMethod)
298
- if (port) {
299
- console.log('[miolo] Port:', port)
300
- }
301
-
302
- // Copy template
303
- copyTemplate(sourcePath, destPath, appName, { authMethod })
304
-
305
- // Update .env with custom parameters
306
- updateEnvFile(destPath, appName, { port })
307
-
308
- // Update docker files with custom port
309
- if (port) {
310
- updateDockerFiles(destPath, port)
311
- }
312
-
313
- // Update server/miolo/index.mjs with correct auth import
314
- updateServerIndex(destPath, authMethod)
315
-
316
- console.log('[miolo] Template copied successfully')
317
- console.log('[miolo] Installing dependencies...')
318
-
319
- // Install dependencies
320
- try {
321
- execSync('npm install', {
322
- cwd: destPath,
323
- stdio: 'inherit'
324
- })
325
- console.log('[miolo] Dependencies installed successfully')
326
- } catch (_error) {
327
- console.warn('[miolo] Warning: Failed to install dependencies automatically')
328
- console.warn('[miolo] Please run "npm install" manually in the project directory')
329
- }
330
-
331
- console.log('[miolo] ✅ App created successfully!')
332
- console.log('[miolo] To get started:')
333
- console.log(` cd ${dest}`)
334
- console.log(` npm run dev`)
335
-
336
- } catch (error) {
337
- console.error('[miolo] Error creating app:', error.message)
338
- process.exit(1)
339
- }
340
- }