UNPKG

7.46 kBJavaScriptView Raw
1const
2 path = require('path'),
3 semver = require('semver'),
4 merge = require('webpack-merge')
5
6const
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 */
17module.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}