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