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.
- package/bin/create/auth.mjs +24 -0
- package/bin/create/copy.mjs +156 -0
- package/bin/create/docker.mjs +25 -0
- package/bin/create/index.mjs +112 -0
- package/bin/create/pkgjson.mjs +69 -0
- package/bin/create/prepare-template.mjs +143 -0
- package/bin/create/validation.mjs +27 -0
- package/bin/index.mjs +1 -1
- package/package.json +3 -4
- package/template/eslint.config.js +25 -0
- package/template/package.json +4 -3
- package/template/postcss.config.js +9 -0
- package/bin/create/create.mjs +0 -340
|
@@ -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/
|
|
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.
|
|
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
|
-
"
|
|
42
|
-
"
|
|
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
|
+
];
|
package/template/package.json
CHANGED
|
@@ -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": "
|
|
48
|
-
"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": "
|
|
63
|
+
"miolo": "^3.0.0-beta.149",
|
|
63
64
|
"sass-embedded": "^1.97.3",
|
|
64
65
|
"xeira": "^2.0.0-beta.10"
|
|
65
66
|
},
|
package/bin/create/create.mjs
DELETED
|
@@ -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
|
-
}
|