1 | const
|
2 | path = require('path'),
|
3 | semver = require('semver'),
|
4 | merge = require('webpack-merge')
|
5 |
|
6 | const
|
7 | appPaths = require('../app-paths'),
|
8 | logger = require('../helpers/logger'),
|
9 | warn = logger('app:extension(index)', 'red'),
|
10 | getPackageJson = require('../helpers/get-package-json'),
|
11 | getCallerPath = require('../helpers/get-caller-path'),
|
12 | extensionJson = require('./extension-json')
|
13 |
|
14 | /**
|
15 | * API for extension's /index.js script
|
16 | */
|
17 | module.exports = class IndexAPI {
|
18 | constructor ({ extId, prompts, ctx }) {
|
19 | this.ctx = ctx
|
20 | this.extId = extId
|
21 | this.prompts = prompts
|
22 | this.resolve = appPaths.resolve
|
23 | this.appDir = appPaths.appDir
|
24 |
|
25 | this.__hooks = {
|
26 | extendQuasarConf: [],
|
27 | extendWebpack: [],
|
28 | chainWebpackMainElectronProcess: [],
|
29 | extendWebpackMainElectronProcess: [],
|
30 | chainWebpack: [],
|
31 | beforeDev: [],
|
32 | afterDev: [],
|
33 | beforeBuild: [],
|
34 | afterBuild: [],
|
35 | onPublish: [],
|
36 | commands: {},
|
37 | describeApi: {}
|
38 | }
|
39 | }
|
40 |
|
41 | /**
|
42 | * Get the internal persistent config of this extension.
|
43 | * Returns empty object if it has none.
|
44 | *
|
45 | * @return {object} cfg
|
46 | */
|
47 | getPersistentConf () {
|
48 | return extensionJson.getInternal(this.extId)
|
49 | }
|
50 |
|
51 | /**
|
52 | * Set the internal persistent config of this extension.
|
53 | * If it already exists, it is overwritten.
|
54 | *
|
55 | * @param {object} cfg
|
56 | */
|
57 | setPersistentConf (cfg) {
|
58 | extensionJson.setInternal(this.extId, cfg || {})
|
59 | }
|
60 |
|
61 | /**
|
62 | * Deep merge into the internal persistent config of this extension.
|
63 | * If extension does not have any config already set, this is
|
64 | * essentially equivalent to setting it for the first time.
|
65 | *
|
66 | * @param {object} cfg
|
67 | */
|
68 | mergePersistentConf (cfg = {}) {
|
69 | const currentCfg = this.getPersistentConf()
|
70 | this.setPersistentConf(merge(currentCfg, cfg))
|
71 | }
|
72 |
|
73 | /**
|
74 | * Ensure the App Extension is compatible with
|
75 | * host app package through a
|
76 | * semver condition.
|
77 | *
|
78 | * If the semver condition is not met, then
|
79 | * @quasar/app errors out and halts execution
|
80 | *
|
81 | * Example of semver condition:
|
82 | * '1.x || >=2.5.0 || 5.0.0 - 7.2.3'
|
83 | *
|
84 | * @param {string} packageName
|
85 | * @param {string} semverCondition
|
86 | */
|
87 | compatibleWith (packageName, semverCondition) {
|
88 | const json = getPackageJson(packageName)
|
89 |
|
90 | if (json === void 0) {
|
91 | warn(`⚠️ Extension(${this.extId}): Dependency not found - ${packageName}. Please install it.`)
|
92 | process.exit(1)
|
93 | }
|
94 |
|
95 | if (!semver.satisfies(json.version, semverCondition)) {
|
96 | warn(`⚠️ Extension(${this.extId}): is not compatible with ${packageName} v${json.version}. Required version: ${semverCondition}`)
|
97 | process.exit(1)
|
98 | }
|
99 | }
|
100 |
|
101 | /**
|
102 | * Check if a host app package is installed. Can also
|
103 | * check its version against specific semver condition.
|
104 | *
|
105 | * Example of semver condition:
|
106 | * '1.x || >=2.5.0 || 5.0.0 - 7.2.3'
|
107 | *
|
108 | * @param {string} packageName
|
109 | * @param {string} (optional) semverCondition
|
110 | * @return {boolean} package is installed and meets optional semver condition
|
111 | */
|
112 | hasPackage (packageName, semverCondition) {
|
113 | const json = getPackageJson(packageName)
|
114 |
|
115 | if (json === void 0) {
|
116 | return false
|
117 | }
|
118 |
|
119 | return semverCondition !== void 0
|
120 | ? semver.satisfies(json.version, semverCondition)
|
121 | : true
|
122 | }
|
123 |
|
124 | /**
|
125 | * Check if another app extension is installed
|
126 | * (app extension npm package is installed and it was invoked)
|
127 | *
|
128 | * @param {string} extId
|
129 | * @return {boolean} has the extension installed & invoked
|
130 | */
|
131 | hasExtension (extId) {
|
132 | return extensionJson.has(extId)
|
133 | }
|
134 |
|
135 | /**
|
136 | * Get the version of a host app package.
|
137 | *
|
138 | * @param {string} packageName
|
139 | * @return {string|undefined} version of app's package
|
140 | */
|
141 | getPackageVersion (packageName) {
|
142 | const json = getPackageJson(packageName)
|
143 | return json !== void 0
|
144 | ? json.version
|
145 | : void 0
|
146 | }
|
147 |
|
148 | /**
|
149 | * Extend quasar.conf
|
150 | *
|
151 | * @param {function} fn
|
152 | * (cfg: Object, ctx: Object) => undefined
|
153 | */
|
154 | extendQuasarConf (fn) {
|
155 | this.__addHook('extendQuasarConf', fn)
|
156 | }
|
157 |
|
158 | /**
|
159 | * Chain webpack config
|
160 | *
|
161 | * @param {function} fn
|
162 | * (cfg: ChainObject, invoke: Object {isClient, isServer}) => undefined
|
163 | */
|
164 | chainWebpack (fn) {
|
165 | this.__addHook('chainWebpack', fn)
|
166 | }
|
167 |
|
168 | /**
|
169 | * Extend webpack config
|
170 | *
|
171 | * @param {function} fn
|
172 | * (cfg: Object, invoke: Object {isClient, isServer}) => undefined
|
173 | */
|
174 | extendWebpack (fn) {
|
175 | this.__addHook('extendWebpack', fn)
|
176 | }
|
177 |
|
178 | /**
|
179 | * Chain webpack config of main electron process
|
180 | *
|
181 | * @param {function} fn
|
182 | * (cfg: ChainObject) => undefined
|
183 | */
|
184 | chainWebpackMainElectronProcess (fn) {
|
185 | this.__addHook('chainWebpackMainElectronProcess', fn)
|
186 | }
|
187 |
|
188 | /**
|
189 | * Extend webpack config of main electron process
|
190 | *
|
191 | * @param {function} fn
|
192 | * (cfg: Object) => undefined
|
193 | */
|
194 | extendWebpackMainElectronProcess (fn) {
|
195 | this.__addHook('extendWebpackMainElectronProcess', fn)
|
196 | }
|
197 |
|
198 | /**
|
199 | * Register a command that will become available as
|
200 | * `quasar run <ext-id> <cmd> [args]` and `quasar <ext-id> <cmd> [args]`
|
201 | *
|
202 | * @param {string} commandName
|
203 | * @param {function} fn
|
204 | * ({ args: [ string, ... ], params: {object} }) => ?Promise
|
205 | */
|
206 | registerCommand (commandName, fn) {
|
207 | this.__hooks.commands[commandName] = fn
|
208 | }
|
209 |
|
210 | /**
|
211 | * Register an API file for "quasar describe" command
|
212 | *
|
213 | * @param {string} name
|
214 | * @param {string} relativePath
|
215 | * (relative path to Api file)
|
216 | */
|
217 | registerDescribeApi (name, relativePath) {
|
218 | const dir = getCallerPath()
|
219 | this.__hooks.describeApi[name] = path.resolve(dir, relativePath)
|
220 | }
|
221 |
|
222 | /**
|
223 | * Prepare external services before dev command runs.
|
224 | *
|
225 | * @param {function} fn
|
226 | * (api, { quasarConf }) => ?Promise
|
227 | */
|
228 | beforeDev (fn) {
|
229 | this.__addHook('beforeDev', fn)
|
230 | }
|
231 |
|
232 | /**
|
233 | * Run hook after Quasar dev server is started ($ quasar dev).
|
234 | * At this point, the dev server has been started and is available
|
235 | * should you wish to do something with it.
|
236 | *
|
237 | * @param {function} fn
|
238 | * (api, { quasarConf }) => ?Promise
|
239 | */
|
240 | afterDev(fn) {
|
241 | this.__addHook('afterDev', fn)
|
242 | }
|
243 |
|
244 | /**
|
245 | * Run hook before Quasar builds app for production ($ quasar build).
|
246 | * At this point, the distributables folder hasn't been created yet.
|
247 | *
|
248 | * @param {function} fn
|
249 | * (api, { quasarConf }) => ?Promise
|
250 | */
|
251 | beforeBuild (fn) {
|
252 | this.__addHook('beforeBuild', fn)
|
253 | }
|
254 |
|
255 | /**
|
256 | * Run hook after Quasar built app for production ($ quasar build).
|
257 | * At this point, the distributables folder has been created and is available
|
258 | * should you wish to do something with it.
|
259 | *
|
260 | * @param {function} fn
|
261 | * (api, { quasarConf }) => ?Promise
|
262 | */
|
263 | afterBuild (fn) {
|
264 | this.__addHook('afterBuild', fn)
|
265 | }
|
266 |
|
267 | /**
|
268 | * Run hook if publishing was requested ("$ quasar build -P"),
|
269 | * after Quasar built app for production and the afterBuild
|
270 | * hook (if specified) was executed.
|
271 | *
|
272 | * @param {function} fn
|
273 | * ({ arg, ...}) => ?Promise
|
274 | * * arg - argument supplied to "--publish"/"-P" parameter
|
275 | * * quasarConf - quasar.conf config object
|
276 | * * distDir - folder where distributables were built
|
277 | */
|
278 | onPublish (fn) {
|
279 | this.__addHook('onPublish', fn)
|
280 | }
|
281 |
|
282 | /**
|
283 | * Private methods
|
284 | */
|
285 |
|
286 | __getHooks () {
|
287 | return this.__hooks
|
288 | }
|
289 |
|
290 | __addHook (name, fn) {
|
291 | this.__hooks[name].push({ fn, api: this })
|
292 | }
|
293 | }
|