@quasar/icongenie 5.0.1 → 6.0.0
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/lib/modes/v2/capacitor.js +7 -139
- package/lib/modes/v2/electron.js +5 -5
- package/lib/modes/v2/pwa.js +32 -14
- package/lib/mount/index.js +4 -2
- package/lib/mount/mount-capacitor.js +74 -0
- package/lib/mount/mount-cordova.js +13 -16
- package/lib/runner/generate.js +1 -3
- package/lib/utils/logger.js +54 -0
- package/lib/utils/package-manager.js +241 -0
- package/lib/utils/spawn-sync.js +48 -8
- package/package.json +2 -1
|
@@ -1,148 +1,16 @@
|
|
|
1
|
-
const iosIconRegex = /AppIcon-(\d+\.?\d?)x?(\d+\.?\d?)?@?(\d+)?x?-?\d?\.png/
|
|
2
|
-
|
|
3
|
-
function getAndroidIcons(entries) {
|
|
4
|
-
const list = []
|
|
5
|
-
|
|
6
|
-
entries.forEach(entry => {
|
|
7
|
-
const icon = {
|
|
8
|
-
generator: 'png',
|
|
9
|
-
folder: `src-capacitor/android/app/src/main/res/mipmap-${entry[0]}`
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
list.push({
|
|
13
|
-
...icon,
|
|
14
|
-
name: 'ic_launcher_foreground.png',
|
|
15
|
-
sizes: [entry[2]]
|
|
16
|
-
})
|
|
17
|
-
|
|
18
|
-
list.push({
|
|
19
|
-
...icon,
|
|
20
|
-
name: 'ic_launcher_round.png',
|
|
21
|
-
sizes: [entry[1]]
|
|
22
|
-
})
|
|
23
|
-
|
|
24
|
-
list.push({
|
|
25
|
-
...icon,
|
|
26
|
-
name: 'ic_launcher.png',
|
|
27
|
-
sizes: [entry[1]]
|
|
28
|
-
})
|
|
29
|
-
})
|
|
30
|
-
|
|
31
|
-
return list
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function getAndroidSplashscreen(entries) {
|
|
35
|
-
const list = []
|
|
36
|
-
|
|
37
|
-
entries.forEach(entry => {
|
|
38
|
-
const icon = {
|
|
39
|
-
generator: 'splashscreen',
|
|
40
|
-
name: 'splash.png'
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
list.push({
|
|
44
|
-
...icon,
|
|
45
|
-
folder: `src-capacitor/android/app/src/main/res/drawable-land-${entry[0]}`,
|
|
46
|
-
sizes: [[entry[1], entry[2]]]
|
|
47
|
-
})
|
|
48
|
-
|
|
49
|
-
list.push({
|
|
50
|
-
...icon,
|
|
51
|
-
folder: `src-capacitor/android/app/src/main/res/drawable-port-${entry[0]}`,
|
|
52
|
-
sizes: [[entry[2], entry[1]]]
|
|
53
|
-
})
|
|
54
|
-
})
|
|
55
|
-
|
|
56
|
-
return list
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function getIosIcon(name) {
|
|
60
|
-
const [, size, , multiplier] = name.match(iosIconRegex)
|
|
61
|
-
|
|
62
|
-
return {
|
|
63
|
-
generator: 'png',
|
|
64
|
-
name,
|
|
65
|
-
folder: 'src-capacitor/ios/App/App/Assets.xcassets/AppIcon.appiconset',
|
|
66
|
-
sizes: [
|
|
67
|
-
multiplier
|
|
68
|
-
? Number.parseFloat(size) * Number.parseInt(multiplier, 10)
|
|
69
|
-
: Number.parseFloat(size)
|
|
70
|
-
],
|
|
71
|
-
background: true
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
1
|
export default [
|
|
76
|
-
/***************
|
|
77
|
-
*** Android ***
|
|
78
|
-
***************/
|
|
79
|
-
|
|
80
|
-
...getAndroidIcons([
|
|
81
|
-
['hdpi', 49, 162],
|
|
82
|
-
['mdpi', 48, 108],
|
|
83
|
-
['xhdpi', 96, 216],
|
|
84
|
-
['xxhdpi', 144, 324],
|
|
85
|
-
['xxxhdpi', 192, 432]
|
|
86
|
-
]),
|
|
87
|
-
|
|
88
2
|
{
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
...getAndroidSplashscreen([
|
|
96
|
-
['mdpi', 480, 320],
|
|
97
|
-
['hdpi', 800, 480],
|
|
98
|
-
['xhdpi', 1280, 720],
|
|
99
|
-
['xxhdpi', 1600, 960],
|
|
100
|
-
['xxxhdpi', 1920, 1280]
|
|
101
|
-
]),
|
|
102
|
-
|
|
103
|
-
/**************
|
|
104
|
-
**** iOS *****
|
|
105
|
-
**************/
|
|
106
|
-
|
|
107
|
-
...[
|
|
108
|
-
'AppIcon-20x20@1x.png',
|
|
109
|
-
'AppIcon-20x20@2x-1.png',
|
|
110
|
-
'AppIcon-20x20@2x.png',
|
|
111
|
-
'AppIcon-20x20@3x.png',
|
|
112
|
-
'AppIcon-29x29@1x.png',
|
|
113
|
-
'AppIcon-29x29@2x-1.png',
|
|
114
|
-
'AppIcon-29x29@2x.png',
|
|
115
|
-
'AppIcon-29x29@3x.png',
|
|
116
|
-
'AppIcon-40x40@1x.png',
|
|
117
|
-
'AppIcon-40x40@2x-1.png',
|
|
118
|
-
'AppIcon-40x40@2x.png',
|
|
119
|
-
'AppIcon-40x40@3x.png',
|
|
120
|
-
'AppIcon-60x60@2x.png',
|
|
121
|
-
'AppIcon-60x60@3x.png',
|
|
122
|
-
'AppIcon-76x76@1x.png',
|
|
123
|
-
'AppIcon-76x76@2x.png',
|
|
124
|
-
'AppIcon-83.5x83.5@2x.png',
|
|
125
|
-
'AppIcon-512@2x.png'
|
|
126
|
-
].map(getIosIcon),
|
|
127
|
-
|
|
128
|
-
{
|
|
129
|
-
generator: 'splashscreen',
|
|
130
|
-
name: 'splash-2732x2732-1.png',
|
|
131
|
-
folder: 'src-capacitor/ios/App/App/Assets.xcassets/Splash.imageset',
|
|
132
|
-
sizes: [2732]
|
|
133
|
-
},
|
|
134
|
-
|
|
135
|
-
{
|
|
136
|
-
generator: 'splashscreen',
|
|
137
|
-
name: 'splash-2732x2732-2.png',
|
|
138
|
-
folder: 'src-capacitor/ios/App/App/Assets.xcassets/Splash.imageset',
|
|
139
|
-
sizes: [2732]
|
|
3
|
+
// tray icon (all platforms)
|
|
4
|
+
generator: 'png',
|
|
5
|
+
name: 'icon.png',
|
|
6
|
+
folder: `src-capacitor/assets`,
|
|
7
|
+
sizes: [1024]
|
|
140
8
|
},
|
|
141
9
|
|
|
142
10
|
{
|
|
143
11
|
generator: 'splashscreen',
|
|
144
|
-
name: 'splash
|
|
145
|
-
folder: 'src-capacitor/
|
|
12
|
+
name: 'splash.png',
|
|
13
|
+
folder: 'src-capacitor/assets',
|
|
146
14
|
sizes: [2732]
|
|
147
15
|
}
|
|
148
16
|
]
|
package/lib/modes/v2/electron.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs'
|
|
2
2
|
import { resolveDir } from '../../utils/app-paths.js'
|
|
3
3
|
|
|
4
|
-
const dir = existsSync(resolveDir('src-electron/electron-assets'))
|
|
5
|
-
? 'electron-assets' // q/app-vite v3+
|
|
4
|
+
const dir = existsSync(resolveDir('src-electron/electron-assets/icons'))
|
|
5
|
+
? 'electron-assets/icons' // q/app-vite v3+
|
|
6
6
|
: existsSync(resolveDir('src-electron/icons'))
|
|
7
7
|
? 'icons' // q/app-vite v2 or q/app-webpack v4
|
|
8
8
|
: 'electron-assets' // fallback to q/app-webpack v3 specs
|
|
@@ -12,21 +12,21 @@ export default [
|
|
|
12
12
|
// macos (embedded icons)
|
|
13
13
|
generator: 'icns',
|
|
14
14
|
name: 'icon.icns',
|
|
15
|
-
folder: `src-electron/${dir}
|
|
15
|
+
folder: `src-electron/${dir}`
|
|
16
16
|
},
|
|
17
17
|
|
|
18
18
|
{
|
|
19
19
|
// windows (embedded icon)
|
|
20
20
|
generator: 'ico',
|
|
21
21
|
name: 'icon.ico',
|
|
22
|
-
folder: `src-electron/${dir}
|
|
22
|
+
folder: `src-electron/${dir}`
|
|
23
23
|
},
|
|
24
24
|
|
|
25
25
|
{
|
|
26
26
|
// tray icon (all platforms)
|
|
27
27
|
generator: 'png',
|
|
28
28
|
name: 'icon.png',
|
|
29
|
-
folder: `src-electron/${dir}
|
|
29
|
+
folder: `src-electron/${dir}`,
|
|
30
30
|
sizes: [512]
|
|
31
31
|
}
|
|
32
32
|
]
|
package/lib/modes/v2/pwa.js
CHANGED
|
@@ -52,20 +52,38 @@ export default [
|
|
|
52
52
|
},
|
|
53
53
|
|
|
54
54
|
...[
|
|
55
|
-
[
|
|
56
|
-
[
|
|
57
|
-
[
|
|
58
|
-
[
|
|
59
|
-
[
|
|
60
|
-
[
|
|
61
|
-
[
|
|
62
|
-
[
|
|
63
|
-
[
|
|
55
|
+
[1320, 2868, 3, '<!-- iPhone 17 Pro Max, 16 Pro Max -->'],
|
|
56
|
+
[1260, 2736, 3, '<!-- iPhone 17 Air -->'],
|
|
57
|
+
[1206, 2622, 3, '<!-- iPhone 17 Pro, 17, 16 Pro -->'],
|
|
58
|
+
[1290, 2796, 3, '<!-- iPhone 16 Plus, 15 Pro Max, 15 Plus, 14 Pro Max -->'],
|
|
59
|
+
[1179, 2556, 3, '<!-- iPhone 16, 15 Pro, 15, 14 Pro -->'],
|
|
60
|
+
[1284, 2778, 3, '<!-- iPhone 14 Plus, 13 Pro Max, 12 Pro Max -->'],
|
|
61
|
+
[1170, 2532, 3, '<!-- iPhone 17e, 16e, 14, 13 Pro, 13, 12 Pro, 12 -->'],
|
|
62
|
+
[1080, 2340, 3, '<!-- iPhone 13 mini, 12 mini -->'],
|
|
63
|
+
[1242, 2688, 3, '<!-- iPhone 11 Pro Max, XS Max -->'],
|
|
64
|
+
[1125, 2436, 3, '<!-- iPhone 11 Pro, X, XS -->'],
|
|
65
|
+
[828, 1792, 2, '<!-- iPhone 11, XR -->'],
|
|
64
66
|
[1242, 2208, 3, '<!-- iPhone 8 Plus, 7 Plus, 6s Plus, 6 Plus -->'],
|
|
65
|
-
[
|
|
66
|
-
|
|
67
|
-
[
|
|
68
|
-
[
|
|
69
|
-
[
|
|
67
|
+
[750, 1334, 2, '<!-- iPhone 8, 7, 6s, 6, SE (2nd & 3rd gen) -->'],
|
|
68
|
+
|
|
69
|
+
[2064, 2752, 2, '<!-- iPad Pro 13" (M4) -->'],
|
|
70
|
+
[2048, 2732, 2, '<!-- iPad Pro 12.9", iPad Air 13" (M2) -->'],
|
|
71
|
+
[1668, 2420, 2, '<!-- iPad Pro 11" (M4) -->'],
|
|
72
|
+
[1668, 2388, 2, '<!-- iPad Pro 11" (M1/M2) -->'],
|
|
73
|
+
[
|
|
74
|
+
1640,
|
|
75
|
+
2360,
|
|
76
|
+
2,
|
|
77
|
+
'<!-- iPad 11" (11th gen), iPad 10.9" (10th gen), iPad Air 11" (M2), iPad Air 10.9" -->'
|
|
78
|
+
],
|
|
79
|
+
[1668, 2224, 2, '<!-- iPad Pro 10.5", iPad Air 3rd Gen -->'],
|
|
80
|
+
[1620, 2160, 2, '<!-- iPad 10.2" (7th, 8th, 9th gen) -->'],
|
|
81
|
+
[1488, 2266, 2, '<!-- iPad Mini (6th & 7th gen) -->'],
|
|
82
|
+
[
|
|
83
|
+
1536,
|
|
84
|
+
2048,
|
|
85
|
+
2,
|
|
86
|
+
'<!-- iPad Mini (up to 5th gen), iPad Air 9.7", iPad 9.7" -->'
|
|
87
|
+
]
|
|
70
88
|
].map(getAppleLaunch)
|
|
71
89
|
]
|
package/lib/mount/index.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
import { mountCapacitor } from './mount-capacitor.js'
|
|
1
2
|
import { isCordovaFile, mountCordova, verifyCordova } from './mount-cordova.js'
|
|
2
3
|
import { mountTag } from './mount-tag.js'
|
|
3
4
|
|
|
4
|
-
export function mount(files) {
|
|
5
|
-
|
|
5
|
+
export async function mount(files) {
|
|
6
|
+
await mountCapacitor(files)
|
|
7
|
+
await mountCordova(files)
|
|
6
8
|
mountTag(files)
|
|
7
9
|
}
|
|
8
10
|
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs'
|
|
2
|
+
|
|
3
|
+
import { resolveDir } from '../utils/app-paths.js'
|
|
4
|
+
import { warn } from '../utils/logger.js'
|
|
5
|
+
import { spawnSync } from '../utils/spawn-sync.js'
|
|
6
|
+
import { createInstance } from '../utils/package-manager.js'
|
|
7
|
+
|
|
8
|
+
const srcCapacitorDir = resolveDir('src-capacitor')
|
|
9
|
+
|
|
10
|
+
async function installCapacitorAssets() {
|
|
11
|
+
const pkgPath = resolveDir('src-capacitor/package.json')
|
|
12
|
+
|
|
13
|
+
// malformed /src-capacitor...
|
|
14
|
+
if (!existsSync(pkgPath)) return false
|
|
15
|
+
|
|
16
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'))
|
|
17
|
+
|
|
18
|
+
if (
|
|
19
|
+
!pkg.devDependencies?.['@capacitor/assets'] &&
|
|
20
|
+
!pkg.dependencies?.['@capacitor/assets']
|
|
21
|
+
) {
|
|
22
|
+
const pm = createInstance(srcCapacitorDir)
|
|
23
|
+
if (typeof pm === 'string') {
|
|
24
|
+
warn(pm)
|
|
25
|
+
return false
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const success = await pm.installPackage('@capacitor/assets', {
|
|
29
|
+
isDevDependency: true,
|
|
30
|
+
allowBuilds: true
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
if (!success) {
|
|
34
|
+
warn()
|
|
35
|
+
warn('Failed to install @capacitor/assets. Please do it manually.')
|
|
36
|
+
return false
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (
|
|
41
|
+
!pkg.dependencies?.['@capacitor/splash-screen'] &&
|
|
42
|
+
!pkg.devDependencies?.['@capacitor/splash-screen']
|
|
43
|
+
) {
|
|
44
|
+
const pm = createInstance(srcCapacitorDir)
|
|
45
|
+
if (typeof pm === 'string') {
|
|
46
|
+
warn(pm)
|
|
47
|
+
return false
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const success = await pm.installPackage('@capacitor/splash-screen')
|
|
51
|
+
|
|
52
|
+
if (!success) {
|
|
53
|
+
warn()
|
|
54
|
+
warn('Failed to install @capacitor/splash-screen. Please do it manually.')
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return true
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export async function mountCapacitor() {
|
|
62
|
+
const hasInstalled = await installCapacitorAssets()
|
|
63
|
+
if (!hasInstalled) return
|
|
64
|
+
|
|
65
|
+
const success = await spawnSync('npx', ['@capacitor/assets', 'generate'], {
|
|
66
|
+
cwd: srcCapacitorDir
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
if (!success) {
|
|
70
|
+
warn()
|
|
71
|
+
warn('Failed to run @capacitor/assets. Please do it manually.')
|
|
72
|
+
console.log(' -> /src-capacitor: $ npx @capacitor/assets generate\n')
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -128,7 +128,7 @@ function hasDeepProp(target, ...args) {
|
|
|
128
128
|
return true
|
|
129
129
|
}
|
|
130
130
|
|
|
131
|
-
function installSplashscreenPlugin() {
|
|
131
|
+
async function installSplashscreenPlugin() {
|
|
132
132
|
const pkgPath = resolveDir('src-cordova/package.json')
|
|
133
133
|
|
|
134
134
|
// malformed /src-cordova...
|
|
@@ -144,29 +144,26 @@ function installSplashscreenPlugin() {
|
|
|
144
144
|
return
|
|
145
145
|
}
|
|
146
146
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
spawnSync(
|
|
147
|
+
const hasInstalled = await spawnSync(
|
|
150
148
|
'cordova',
|
|
151
149
|
['plugin', 'add', 'cordova-plugin-splashscreen'],
|
|
152
150
|
{
|
|
153
151
|
cwd: srcCordovaDir
|
|
154
|
-
},
|
|
155
|
-
() => {
|
|
156
|
-
warn()
|
|
157
|
-
warn(
|
|
158
|
-
'Failed to install cordova-plugin-splashscreen. Please do it manually.'
|
|
159
|
-
)
|
|
160
|
-
console.log(
|
|
161
|
-
' -> /src-cordova: $ cordova plugin add cordova-plugin-splashscreen\n'
|
|
162
|
-
)
|
|
163
152
|
}
|
|
164
153
|
)
|
|
165
154
|
|
|
166
|
-
|
|
155
|
+
if (!hasInstalled) {
|
|
156
|
+
warn()
|
|
157
|
+
warn(
|
|
158
|
+
'Failed to install cordova-plugin-splashscreen. Please do it manually.'
|
|
159
|
+
)
|
|
160
|
+
console.log(
|
|
161
|
+
' -> /src-cordova: $ cordova plugin add cordova-plugin-splashscreen\n'
|
|
162
|
+
)
|
|
163
|
+
}
|
|
167
164
|
}
|
|
168
165
|
|
|
169
|
-
export function mountCordova(files) {
|
|
166
|
+
export async function mountCordova(files) {
|
|
170
167
|
if (existsSync(cordovaConfigXml)) {
|
|
171
168
|
const cordovaFiles = getCordovaFiles(files)
|
|
172
169
|
|
|
@@ -176,7 +173,7 @@ export function mountCordova(files) {
|
|
|
176
173
|
)
|
|
177
174
|
|
|
178
175
|
if (hasSplashscreen) {
|
|
179
|
-
installSplashscreenPlugin()
|
|
176
|
+
await installSplashscreenPlugin()
|
|
180
177
|
}
|
|
181
178
|
|
|
182
179
|
updateConfigXml(cordovaFiles, hasSplashscreen)
|
package/lib/runner/generate.js
CHANGED
|
@@ -114,9 +114,7 @@ async function generateFromProfile(profile) {
|
|
|
114
114
|
printBanner(assetsOf, params)
|
|
115
115
|
|
|
116
116
|
return Promise.all(uniqueFiles.map(file => generateFile(file, fileOptions)))
|
|
117
|
-
.then(() =>
|
|
118
|
-
mount(uniqueFiles)
|
|
119
|
-
})
|
|
117
|
+
.then(() => mount(uniqueFiles))
|
|
120
118
|
.then(() => uniqueFiles.length)
|
|
121
119
|
}
|
|
122
120
|
|
package/lib/utils/logger.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { green, red } from 'kolorist'
|
|
2
|
+
import { isCI } from 'ci-info'
|
|
2
3
|
|
|
3
4
|
const banner = '*'
|
|
4
5
|
|
|
@@ -17,3 +18,56 @@ export function fatal(msg) {
|
|
|
17
18
|
console.error(msg ? ` ${warnBanner} ⚠️ ${msg}` : '')
|
|
18
19
|
process.exit(1)
|
|
19
20
|
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Alternate Screen
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
export function enterAlternateScreen(message) {
|
|
27
|
+
if (isCI) return
|
|
28
|
+
|
|
29
|
+
// Enter Alternate Screen Buffer (hides current terminal history)
|
|
30
|
+
process.stdout.write('\u001B[?1049h')
|
|
31
|
+
// Move cursor to top left
|
|
32
|
+
process.stdout.write('\u001B[H')
|
|
33
|
+
|
|
34
|
+
if (message) console.log(`>>> ${message}\n`)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function exitAlternateScreen() {
|
|
38
|
+
if (isCI) return
|
|
39
|
+
process.stdout.write('\u001B[?1049l')
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function waitForKey() {
|
|
43
|
+
// Are we in a real terminal?
|
|
44
|
+
// If not (e.g., CI pipeline), resolve immediately so the script doesn't hang forever.
|
|
45
|
+
if (isCI) return Promise.resolve()
|
|
46
|
+
|
|
47
|
+
const { stdin } = process
|
|
48
|
+
process.stdout.write('Press any key to continue...')
|
|
49
|
+
|
|
50
|
+
const { promise, resolve: resolvePromise } = Promise.withResolvers()
|
|
51
|
+
|
|
52
|
+
// Enable raw mode to bypass the 'Enter' key requirement
|
|
53
|
+
stdin.setRawMode(true)
|
|
54
|
+
stdin.resume()
|
|
55
|
+
stdin.setEncoding('utf8')
|
|
56
|
+
|
|
57
|
+
const handleKey = key => {
|
|
58
|
+
stdin.off('data', handleKey)
|
|
59
|
+
stdin.setRawMode(false)
|
|
60
|
+
stdin.pause()
|
|
61
|
+
|
|
62
|
+
// Explicitly handle Ctrl+C
|
|
63
|
+
if (key === '\u0003') {
|
|
64
|
+
console.log('\nProcess cancelled by user (Ctrl+C)\n')
|
|
65
|
+
process.exit(1)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
resolvePromise()
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
stdin.on('data', handleKey)
|
|
72
|
+
return promise
|
|
73
|
+
}
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import fs from 'node:fs'
|
|
2
|
+
import { join, normalize, sep } from 'node:path'
|
|
3
|
+
import { sync as crossSpawnSync } from 'cross-spawn'
|
|
4
|
+
|
|
5
|
+
import { spawnSync } from './spawn-sync.js'
|
|
6
|
+
|
|
7
|
+
// returns a Promise!
|
|
8
|
+
function run({ name, params, cwd, env = 'development' }) {
|
|
9
|
+
return spawnSync(
|
|
10
|
+
name,
|
|
11
|
+
params.filter(param => typeof param === 'string' && param.length !== 0),
|
|
12
|
+
{ cwd, env: { NODE_ENV: env } }
|
|
13
|
+
)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function getMajorVersion(name) {
|
|
17
|
+
try {
|
|
18
|
+
const child = crossSpawnSync(name, ['--version'])
|
|
19
|
+
if (child.status === 0) {
|
|
20
|
+
const version = String(child.output[1]).trim()
|
|
21
|
+
return Number.parseInt(version.split('.')[0], 10)
|
|
22
|
+
}
|
|
23
|
+
} catch {
|
|
24
|
+
/* do nothing; we return null below */
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return null
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
class PackageManager {
|
|
31
|
+
appDir
|
|
32
|
+
|
|
33
|
+
constructor(appDir) {
|
|
34
|
+
this.appDir = appDir
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* To be declared by subclasses
|
|
39
|
+
*/
|
|
40
|
+
name = 'unknown'
|
|
41
|
+
lockFiles = ['unknown']
|
|
42
|
+
|
|
43
|
+
getInstallParams(/* env */) {
|
|
44
|
+
return []
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
getInstallPackageParams(/* names, isDev, allowBuilds */) {
|
|
48
|
+
return []
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
getUninstallPackageParams(/* names */) {
|
|
52
|
+
return []
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Implementation of the actual package manager
|
|
57
|
+
*/
|
|
58
|
+
|
|
59
|
+
majorVersion = null
|
|
60
|
+
cachedIsInstalled = null
|
|
61
|
+
|
|
62
|
+
isInstalled() {
|
|
63
|
+
if (this.cachedIsInstalled !== null) {
|
|
64
|
+
return this.cachedIsInstalled
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
this.majorVersion = getMajorVersion(this.name)
|
|
68
|
+
this.cachedIsInstalled = this.majorVersion !== null
|
|
69
|
+
|
|
70
|
+
return this.cachedIsInstalled
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// returns a Promise!
|
|
74
|
+
install({ cwd = this.appDir, params, env = 'development' } = {}) {
|
|
75
|
+
return run({
|
|
76
|
+
name: this.name,
|
|
77
|
+
params:
|
|
78
|
+
params && params.length !== 0 ? params : this.getInstallParams(env),
|
|
79
|
+
cwd,
|
|
80
|
+
env
|
|
81
|
+
})
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// returns a Promise!
|
|
85
|
+
installPackage(
|
|
86
|
+
name,
|
|
87
|
+
{ cwd = this.appDir, isDevDependency = false, allowBuilds = false } = {}
|
|
88
|
+
) {
|
|
89
|
+
return run({
|
|
90
|
+
name: this.name,
|
|
91
|
+
params: this.getInstallPackageParams(
|
|
92
|
+
Array.isArray(name) ? name : [name],
|
|
93
|
+
isDevDependency,
|
|
94
|
+
allowBuilds
|
|
95
|
+
),
|
|
96
|
+
cwd
|
|
97
|
+
})
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// returns a Promise!
|
|
101
|
+
uninstallPackage(name, { cwd = this.appDir } = {}) {
|
|
102
|
+
return run({
|
|
103
|
+
name: this.name,
|
|
104
|
+
params: this.getUninstallPackageParams(
|
|
105
|
+
Array.isArray(name) ? name : [name]
|
|
106
|
+
),
|
|
107
|
+
cwd
|
|
108
|
+
})
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
class Npm extends PackageManager {
|
|
113
|
+
name = 'npm'
|
|
114
|
+
lockFiles = ['package-lock.json']
|
|
115
|
+
|
|
116
|
+
getInstallParams(env) {
|
|
117
|
+
if (env === 'development') {
|
|
118
|
+
return ['install']
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return this.majorVersion >= 9
|
|
122
|
+
? ['install'] // env will be set to production
|
|
123
|
+
: ['install', '--production']
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
getInstallPackageParams(names, isDevDependency) {
|
|
127
|
+
return ['install', isDevDependency ? '--save-dev' : '', ...names]
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
getUninstallPackageParams(names) {
|
|
131
|
+
return ['uninstall', ...names]
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
class Yarn extends PackageManager {
|
|
136
|
+
name = 'yarn'
|
|
137
|
+
lockFiles = ['yarn.lock']
|
|
138
|
+
|
|
139
|
+
getInstallParams(env) {
|
|
140
|
+
if (env === 'development') {
|
|
141
|
+
return ['install']
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return this.majorVersion >= 2
|
|
145
|
+
? ['workspaces', 'focus', '--all', '--production']
|
|
146
|
+
: ['install', '--production']
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
getInstallPackageParams(names, isDevDependency) {
|
|
150
|
+
return ['add', isDevDependency ? '--dev' : '', ...names]
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
getUninstallPackageParams(names) {
|
|
154
|
+
return ['remove', ...names]
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
class Pnpm extends PackageManager {
|
|
159
|
+
name = 'pnpm'
|
|
160
|
+
lockFiles = ['pnpm-lock.yaml']
|
|
161
|
+
|
|
162
|
+
getInstallParams(env) {
|
|
163
|
+
return env === 'development' ? ['install'] : ['install', '--prod']
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
getInstallPackageParams(names, isDevDependency, allowBuilds) {
|
|
167
|
+
return [
|
|
168
|
+
'add',
|
|
169
|
+
isDevDependency ? '--save-dev' : '',
|
|
170
|
+
allowBuilds ? '--dangerously-allow-all-builds' : '',
|
|
171
|
+
...names
|
|
172
|
+
]
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
getUninstallPackageParams(names) {
|
|
176
|
+
return ['remove', ...names]
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
class Bun extends PackageManager {
|
|
181
|
+
name = 'bun'
|
|
182
|
+
lockFiles = ['bun.lock', 'bun.lockb']
|
|
183
|
+
|
|
184
|
+
getInstallParams(env) {
|
|
185
|
+
return env === 'development' ? ['install'] : ['install', '--production']
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
getInstallPackageParams(names, isDevDependency) {
|
|
189
|
+
return ['add', isDevDependency ? '--dev' : '', ...names]
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
getUninstallPackageParams(names) {
|
|
193
|
+
return ['remove', ...names]
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* @returns {PackageManager}
|
|
199
|
+
*/
|
|
200
|
+
function getProjectPackageManager(packageManagersList, dir) {
|
|
201
|
+
// Recursively checks for presence of the lock file by traversing
|
|
202
|
+
// the dir tree up to the root
|
|
203
|
+
while (dir.length !== 0 && dir.at(-1) !== sep) {
|
|
204
|
+
for (const pm of packageManagersList) {
|
|
205
|
+
if (pm.lockFiles.some(lockFile => fs.existsSync(join(dir, lockFile)))) {
|
|
206
|
+
return pm
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
dir = normalize(join(dir, '..'))
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export function createInstance(appDir) {
|
|
215
|
+
const packageManagersList = [
|
|
216
|
+
new Yarn(appDir),
|
|
217
|
+
new Pnpm(appDir),
|
|
218
|
+
new Npm(appDir),
|
|
219
|
+
new Bun(appDir)
|
|
220
|
+
]
|
|
221
|
+
|
|
222
|
+
const projectPackageManager = getProjectPackageManager(
|
|
223
|
+
packageManagersList,
|
|
224
|
+
appDir
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
// if the project folder uses a supported package manager
|
|
228
|
+
// and it is installed on this machine then use it
|
|
229
|
+
if (projectPackageManager !== void 0 && projectPackageManager.isInstalled()) {
|
|
230
|
+
return projectPackageManager
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// otherwise, use the first installed package manager
|
|
234
|
+
for (const pm of packageManagersList) {
|
|
235
|
+
if (pm.isInstalled()) {
|
|
236
|
+
return pm
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return 'Please install PNPM (recommended), Yarn, NPM or Bun before running this command.'
|
|
241
|
+
}
|
package/lib/utils/spawn-sync.js
CHANGED
|
@@ -1,18 +1,58 @@
|
|
|
1
1
|
import crossSpawn from 'cross-spawn'
|
|
2
|
+
import { relative } from 'node:path'
|
|
3
|
+
import { isCI } from 'ci-info'
|
|
2
4
|
|
|
3
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
enterAlternateScreen,
|
|
7
|
+
exitAlternateScreen,
|
|
8
|
+
log,
|
|
9
|
+
waitForKey
|
|
10
|
+
} from './logger.js'
|
|
4
11
|
|
|
5
|
-
|
|
6
|
-
|
|
12
|
+
const extraEnvParams = isCI
|
|
13
|
+
? {}
|
|
14
|
+
: { FORCE_COLOR: process.env.FORCE_COLOR ?? '1' }
|
|
15
|
+
|
|
16
|
+
/*
|
|
17
|
+
Returns nothing, takes onFail
|
|
18
|
+
*/
|
|
19
|
+
export async function spawnSync(cmd, params, opts) {
|
|
20
|
+
const targetFolder = opts?.cwd
|
|
21
|
+
? ` in /${relative(process.cwd(), opts.cwd)}`
|
|
22
|
+
: ''
|
|
23
|
+
const message = `Running "${cmd} ${params.join(' ')}"${targetFolder}`
|
|
24
|
+
|
|
25
|
+
log(message)
|
|
26
|
+
enterAlternateScreen(message)
|
|
7
27
|
|
|
8
28
|
const runner = crossSpawn.sync(cmd, params, {
|
|
9
29
|
stdio: 'inherit',
|
|
10
|
-
...opts
|
|
30
|
+
...opts,
|
|
31
|
+
env: { ...process.env, ...extraEnvParams, ...opts.env }
|
|
11
32
|
})
|
|
12
33
|
|
|
13
|
-
if (runner.status || runner.
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
34
|
+
if (runner.error || runner.status || runner.status === null) {
|
|
35
|
+
const errorMessage =
|
|
36
|
+
runner.status === null || runner.error?.code === 'ENOENT'
|
|
37
|
+
? `Command "${cmd}" not found! Please install it globally.`
|
|
38
|
+
: runner.status
|
|
39
|
+
? `Command "${cmd} ${params.join(' ')}" failed with exit code: ${runner.status}`
|
|
40
|
+
: `Command "${cmd} ${params.join(' ')}" failed!`
|
|
41
|
+
|
|
42
|
+
const msg = `⚠️ ⚠️ ⚠️ ${errorMessage} ⚠️ ⚠️ ⚠️ `
|
|
43
|
+
|
|
44
|
+
console.log()
|
|
45
|
+
console.error(msg)
|
|
46
|
+
console.log()
|
|
47
|
+
|
|
48
|
+
await waitForKey()
|
|
49
|
+
exitAlternateScreen()
|
|
50
|
+
console.error(msg)
|
|
51
|
+
|
|
52
|
+
return false
|
|
17
53
|
}
|
|
54
|
+
|
|
55
|
+
exitAlternateScreen()
|
|
56
|
+
log(`Executed "${cmd} ${params.join(' ')}"${targetFolder}`)
|
|
57
|
+
return true
|
|
18
58
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quasar/icongenie",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "6.0.0",
|
|
4
4
|
"description": "A Quasar tool for generating all your Quasar App's icons and splashscreens",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"bex",
|
|
@@ -42,6 +42,7 @@
|
|
|
42
42
|
"LICENSE"
|
|
43
43
|
],
|
|
44
44
|
"type": "module",
|
|
45
|
+
"main": "bin/icongenie.js",
|
|
45
46
|
"publishConfig": {
|
|
46
47
|
"access": "public"
|
|
47
48
|
},
|