1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 |
|
18 |
|
19 |
|
20 | import path from 'path'
|
21 |
|
22 | import readPkgUp from 'read-pkg-up'
|
23 | import npm from 'npm-programmatic'
|
24 | import pkgDir from 'pkg-dir'
|
25 | import semver from 'semver'
|
26 | import inGfw from 'in-gfw'
|
27 |
|
28 | import {
|
29 | Puppet,
|
30 | PuppetImplementation,
|
31 | PuppetOptions,
|
32 | } from 'wechaty-puppet'
|
33 |
|
34 | import { looseInstanceOfClass } from './helper-functions/pure/loose-instance-of-class'
|
35 |
|
36 | import {
|
37 | log,
|
38 | } from './config'
|
39 | import {
|
40 | PUPPET_DEPENDENCIES,
|
41 | PuppetModuleName,
|
42 | } from './puppet-config'
|
43 |
|
44 | export interface ResolveOptions {
|
45 | puppet : Puppet | PuppetModuleName,
|
46 | puppetOptions? : PuppetOptions,
|
47 | }
|
48 |
|
49 |
|
50 |
|
51 |
|
52 |
|
53 |
|
54 | const looseInstanceOfPuppet = looseInstanceOfClass(Puppet as any as Puppet & { new (...args: any): Puppet })
|
55 |
|
56 | export class PuppetManager {
|
57 |
|
58 | public static async resolve (
|
59 | options: ResolveOptions
|
60 | ): Promise<Puppet> {
|
61 | log.verbose('PuppetManager', 'resolve({puppet: %s, puppetOptions: %s})',
|
62 | options.puppet,
|
63 | JSON.stringify(options.puppetOptions),
|
64 | )
|
65 |
|
66 | let puppetInstance: Puppet
|
67 |
|
68 | |
69 |
|
70 |
|
71 |
|
72 |
|
73 |
|
74 |
|
75 |
|
76 |
|
77 | if (looseInstanceOfPuppet(options.puppet)) {
|
78 | puppetInstance = await this.resolveInstance(options.puppet)
|
79 | } else if (typeof options.puppet !== 'string') {
|
80 | log.error('PuppetManager', 'resolve() %s',
|
81 | `
|
82 | Wechaty Framework must keep only one Puppet instance #1930
|
83 | See: https://github.com/wechaty/wechaty/issues/1930
|
84 | `,
|
85 | )
|
86 | throw new Error('Wechaty Framework must keep only one Puppet instance #1930')
|
87 | } else {
|
88 | const MyPuppet = await this.resolveName(options.puppet)
|
89 | |
90 |
|
91 |
|
92 |
|
93 |
|
94 |
|
95 |
|
96 |
|
97 |
|
98 |
|
99 |
|
100 |
|
101 | |
102 |
|
103 |
|
104 |
|
105 |
|
106 |
|
107 | puppetInstance = new (MyPuppet as any)(options.puppetOptions)
|
108 | }
|
109 |
|
110 | return puppetInstance
|
111 | }
|
112 |
|
113 | protected static async resolveName (
|
114 | puppetName: PuppetModuleName,
|
115 | ): Promise<PuppetImplementation> {
|
116 | log.verbose('PuppetManager', 'resolveName(%s)', puppetName)
|
117 |
|
118 | if (!puppetName) {
|
119 | throw new Error('must provide a puppet name')
|
120 | }
|
121 |
|
122 | if (!(puppetName in PUPPET_DEPENDENCIES)) {
|
123 | throw new Error(
|
124 | [
|
125 | '',
|
126 | 'puppet npm module not supported: "' + puppetName + '"',
|
127 | 'learn more about supported Wechaty Puppet from our directory at',
|
128 | '<https://github.com/wechaty/wechaty-puppet/wiki/Directory>',
|
129 | '',
|
130 | ].join('\n')
|
131 | )
|
132 | }
|
133 |
|
134 | await this.checkModule(puppetName)
|
135 |
|
136 | const puppetModule = await import(puppetName)
|
137 |
|
138 | if (!puppetModule.default) {
|
139 | throw new Error(`Puppet(${puppetName}) has not provided the default export`)
|
140 | }
|
141 |
|
142 | const MyPuppet = puppetModule.default as PuppetImplementation
|
143 |
|
144 | return MyPuppet
|
145 | }
|
146 |
|
147 | protected static async checkModule (puppetName: PuppetModuleName): Promise<void> {
|
148 | log.verbose('PuppetManager', 'checkModule(%s)', puppetName)
|
149 |
|
150 | const versionRange = PUPPET_DEPENDENCIES[puppetName]
|
151 |
|
152 | |
153 |
|
154 |
|
155 | if (!this.installed(puppetName)) {
|
156 | log.silly('PuppetManager', 'checkModule(%s) not installed.', puppetName)
|
157 | await this.install(puppetName, versionRange)
|
158 | return
|
159 | }
|
160 |
|
161 | const moduleVersion = this.getModuleVersion(puppetName)
|
162 |
|
163 | const satisfy = semver.satisfies(
|
164 | moduleVersion,
|
165 | versionRange,
|
166 | )
|
167 |
|
168 | |
169 |
|
170 |
|
171 | if (!satisfy) {
|
172 | log.silly('PuppetManager', 'checkModule() %s installed version %s NOT satisfied range %s',
|
173 | puppetName,
|
174 | moduleVersion,
|
175 | versionRange,
|
176 | )
|
177 | await this.install(puppetName, versionRange)
|
178 | return
|
179 | }
|
180 |
|
181 | |
182 |
|
183 |
|
184 | log.silly('PuppetManager', 'checkModule() %s installed version %s satisfied range %s',
|
185 | puppetName,
|
186 | moduleVersion,
|
187 | versionRange,
|
188 | )
|
189 | }
|
190 |
|
191 | protected static getModuleVersion (moduleName: string): string {
|
192 | const modulePath = path.dirname(
|
193 | require.resolve(
|
194 | moduleName,
|
195 | ),
|
196 | )
|
197 | const pkg = readPkgUp.sync({ cwd: modulePath })!.packageJson
|
198 | const version = pkg.version
|
199 |
|
200 | return version
|
201 | }
|
202 |
|
203 | protected static async resolveInstance (instance: Puppet): Promise<Puppet> {
|
204 | log.verbose('PuppetManager', 'resolveInstance(%s)', instance)
|
205 |
|
206 |
|
207 |
|
208 |
|
209 |
|
210 |
|
211 |
|
212 |
|
213 |
|
214 | return instance
|
215 | }
|
216 |
|
217 | protected static installed (moduleName: string): boolean {
|
218 | try {
|
219 | require.resolve(moduleName)
|
220 | return true
|
221 | } catch (e) {
|
222 | return false
|
223 | }
|
224 | }
|
225 |
|
226 | private static async preInstallPuppeteer (): Promise<void> {
|
227 | let gfw = false
|
228 | try {
|
229 | gfw = await inGfw()
|
230 | if (gfw) {
|
231 | log.verbose('PuppetManager', 'preInstallPuppeteer() inGfw = true')
|
232 | }
|
233 | } catch (e) {
|
234 | log.verbose('PuppetManager', 'preInstallPuppeteer() exception: %s', e)
|
235 | }
|
236 |
|
237 |
|
238 | if (gfw && !process.env['PUPPETEER_DOWNLOAD_HOST']) {
|
239 | log.info('PuppetManager', 'preInstallPuppeteer() set PUPPETEER_DOWNLOAD_HOST=https://npm.taobao.org/mirrors/')
|
240 | process.env['PUPPETEER_DOWNLOAD_HOST'] = 'https://npm.taobao.org/mirrors/'
|
241 | }
|
242 | }
|
243 |
|
244 | public static async install (
|
245 | puppetModule: string,
|
246 | puppetVersion = 'latest',
|
247 | ): Promise<void> {
|
248 | log.info('PuppetManager', 'install(%s@%s) please wait ...', puppetModule, puppetVersion)
|
249 |
|
250 | if (puppetModule === 'wechaty-puppet-puppeteer') {
|
251 | await this.preInstallPuppeteer()
|
252 | }
|
253 |
|
254 | await npm.install(
|
255 | `${puppetModule}@${puppetVersion}`,
|
256 | {
|
257 | cwd : await pkgDir(__dirname),
|
258 | output : true,
|
259 | save : false,
|
260 | },
|
261 | )
|
262 | log.info('PuppetManager', 'install(%s@%s) done', puppetModule, puppetVersion)
|
263 | }
|
264 |
|
265 | |
266 |
|
267 |
|
268 | public static async installAll (): Promise<void> {
|
269 | log.info('PuppetManager', 'installAll() please wait ...')
|
270 |
|
271 | const skipList = [
|
272 | '@juzibot/wechaty-puppet-donut',
|
273 | '@juzibot/wechaty-puppet-wxwork',
|
274 | ]
|
275 |
|
276 | const moduleList: string[] = []
|
277 |
|
278 | for (const puppetModuleName of Object.keys(PUPPET_DEPENDENCIES)) {
|
279 | const version = PUPPET_DEPENDENCIES[puppetModuleName as PuppetModuleName]
|
280 |
|
281 | if (version === '0.0.0' || skipList.includes(puppetModuleName)) {
|
282 | continue
|
283 | }
|
284 |
|
285 | moduleList.push(`${puppetModuleName}@${version}`)
|
286 | }
|
287 |
|
288 | await npm.install(
|
289 | moduleList,
|
290 | {
|
291 | cwd : await pkgDir(__dirname),
|
292 | output : true,
|
293 | save : false,
|
294 | },
|
295 | )
|
296 |
|
297 | }
|
298 |
|
299 | }
|