@vcmap/plugin-cli 2.0.9 → 2.0.10

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/README.md CHANGED
@@ -178,6 +178,7 @@ to create your project, a template already adhering to these specs will be creat
178
178
  - `config.json` with default parameters for the plugins' configuration.
179
179
  - `README.md` describing the plugins' capabilities and usage.
180
180
  - `src/index.js` JS entry point.
181
+ - A plugin _may_ provide static plugin assets in a `plugin-assets` directory. (See [About Plugin Assets](#About-Plugin-Assets)
181
182
  - Plugin names are defined by the plugins' package name and therefore must obey npm [package name guidelines](https://docs.npmjs.com/package-name-guidelines):
182
183
  - choose a name that
183
184
  - is unique
@@ -191,7 +192,7 @@ to create your project, a template already adhering to these specs will be creat
191
192
  - Plugin dependencies have to be defined in the `package.json`.
192
193
  - `dependency`: all plugin specific dependencies NOT provided by the `@vcmap/ui`.
193
194
  - `peerDependency`: dependencies provided by the `@vcmap/ui`,
194
- - e.g. `@vcmap/core` or `@vcmap/ui` (see [About Peer Dependencies](#About_Peer_Dependencies) for more details)
195
+ - e.g. `@vcmap/core` or `@vcmap/ui` (see [About Peer Dependencies](#About-Peer-Dependencies) for more details)
195
196
  - `devDependency`: all dependencies only required for development, e.g. `eslint`.
196
197
  - Plugins can be published to NPM, but should contain both source and minified code
197
198
  to allow seamless integration into the [VC Map UI](https://github.com/virtualcitySYSTEMS/map-ui) environment.
@@ -203,10 +204,11 @@ For this reason the package.json of a plugin defines two exports:
203
204
  }
204
205
  ```
205
206
 
206
-
207
207
  ### Plugin Interface:
208
208
  Plugins must provide a function default export which returns an Object complying
209
- with the VC Map Plugin Interface describe below:
209
+ with the VC Map Plugin Interface describe below. This function is passed the current
210
+ configuration of the plugin as its first argument and the base URL (without the filename)
211
+ from which the plugin was loaded as its second argument.
210
212
 
211
213
  ```typescript
212
214
  declare interface VcsPlugin<T extends Object, S extends Object> {
@@ -219,7 +221,7 @@ declare interface VcsPlugin<T extends Object, S extends Object> {
219
221
  destroy():void;
220
222
  }
221
223
 
222
- declare function defaultExport<T extends Object, S extends Object>(config: T):VcsPlugin<T, S>;
224
+ declare function defaultExport<T extends Object, S extends Object>(config: T, baseUrl: string):VcsPlugin<T, S>;
223
225
  ```
224
226
 
225
227
  A Simple JavaScript implementation of this interface can be seen below::
@@ -229,7 +231,7 @@ A Simple JavaScript implementation of this interface can be seen below::
229
231
  * @param {PluginExampleConfig} config
230
232
  * @returns {VcsPlugin}
231
233
  */
232
- export default function defaultExport(config) {
234
+ export default function defaultExport(config, baseUrl) {
233
235
  return {
234
236
  get name() {
235
237
  return packageJSON.name;
@@ -237,7 +239,9 @@ export default function defaultExport(config) {
237
239
  get version() {
238
240
  return packageJSON.version;
239
241
  },
240
- async initialize (app, state) {},
242
+ async initialize (app, state) {
243
+ console.log('I was loaded from ', baseUrl);
244
+ },
241
245
  async onVcsAppMounted(app) {},
242
246
  async getState() { return {}; },
243
247
  async toJSON() { return {}; },
@@ -246,6 +250,60 @@ export default function defaultExport(config) {
246
250
  }
247
251
  ```
248
252
 
253
+ ### About Plugin Assets
254
+ Plugin assets are considered to be static files, such as images, fonts etc. which shall be
255
+ access from within the plugin. Since plugins have no knowledge of _where_ they will
256
+ be deployed, the `@vcmap/ui` provides the `getPluginAssetUrl` helper function
257
+ which allows you to generate an asset URL at runtime.
258
+
259
+ Place all your assets into the `plugin-assets` directory in your plugin (top level). Your
260
+ plugin structure should look something like this:
261
+ ```
262
+ -| my-plugin/
263
+ ---| src/
264
+ -----| index.js
265
+ ---| plugin-assets/
266
+ -----| icon.png
267
+ ---| package.json
268
+ ```
269
+
270
+ To access the `icon.png` from within your code, you would do the following:
271
+ ```vue
272
+ <template>
273
+ <v-img
274
+ :src="icon"
275
+ alt="plugin-icon"
276
+ max-width="200"
277
+ />
278
+ </template>
279
+
280
+ <script>
281
+ import { inject } from 'vue';
282
+ import { getPluginAssetUrl } from '@vcmap/ui';
283
+ import { name } from '../package.json';
284
+
285
+ export const windowId = 'hello_world_window_id_plugin-cli';
286
+
287
+ export default {
288
+ name: 'HelloWorld',
289
+ components: { VcsButton },
290
+ setup() {
291
+ const app = inject('vcsApp');
292
+
293
+ return {
294
+ icon: getPluginAssetUrl(app, name, 'plugin-assets/icon.png'),
295
+ };
296
+ },
297
+ };
298
+ </script>
299
+ ```
300
+ You can of course, use `fetch` to retrieve assets in the same fashion. Should you
301
+ wish to use assets (such as images) in your _css_ make sure that they are embedded or
302
+ you will have to use an inline style & a bound vue property, since the helper
303
+ cannot handle css resources.
304
+
305
+ If you have to access assets _before_ your plugin is created (in the exported function of
306
+ your plugin code), you will have to use the `baseUrl` provided to you to generate the URL yourself.
249
307
 
250
308
  ## Notes on Developing
251
309
  To develop the plugin-cli, be sure to not `npm link` into plugins, since this will
@@ -1,9 +1,16 @@
1
1
  <template>
2
- <v-sheet>
2
+ <v-sheet class="hello-world">
3
3
  <v-card class="pa-2 ma-2">
4
4
  <v-container>
5
5
  <v-row class="justify-center mb-4">
6
- <h1>{{ $t('helloWorld.helloWorld')}}</h1>
6
+ <h1>{{ $t('helloWorld.helloWorld') }}</h1>
7
+ </v-row>
8
+ <v-row class="justify-center mb-4">
9
+ <v-img
10
+ :src="logoUrl"
11
+ alt="plugin-assets example"
12
+ max-width="200"
13
+ />
7
14
  </v-row>
8
15
  <v-row class="justify-center">
9
16
  <VcsButton
@@ -18,9 +25,15 @@
18
25
  </v-sheet>
19
26
  </template>
20
27
 
28
+ <style>
29
+ .hello-world {
30
+ background-color: aqua;
31
+ }
32
+ </style>
21
33
  <script>
22
34
  import { inject } from 'vue';
23
- import { VcsButton } from '@vcmap/ui';
35
+ import { VcsButton, getPluginAssetUrl } from '@vcmap/ui';
36
+ import { name } from '../package.json';
24
37
 
25
38
  export const windowId = 'hello_world_window_id_plugin-cli';
26
39
 
@@ -34,6 +47,7 @@
34
47
  closeSelf() {
35
48
  app.windowManager.remove(windowId);
36
49
  },
50
+ logoUrl: getPluginAssetUrl(app, name, 'plugin-assets/vcs_logo.png'),
37
51
  };
38
52
  },
39
53
  };
@@ -4,11 +4,12 @@ import HelloWorld, { windowId } from './helloWorld.vue';
4
4
 
5
5
  /**
6
6
  * @param {T} config - the configuration of this plugin instance, passed in from the app.
7
+ * @param {string} baseUrl - the absolute URL from which the plugin was loaded (without filename, ending on /)
7
8
  * @returns {import("@vcmap/ui/src/vcsUiApp").VcsPlugin<T>}
8
9
  * @template {Object} T
9
10
  * @template {Object} S
10
11
  */
11
- export default function(config) {
12
+ export default function(config, baseUrl) {
12
13
  return {
13
14
  get name() { return name; },
14
15
  get version() { return version; },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vcmap/plugin-cli",
3
- "version": "2.0.9",
3
+ "version": "2.0.10",
4
4
  "description": "A CLI to help develop and build plugins for the VC Map",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -6,7 +6,6 @@ import { getConfigJson } from './hostingHelpers.js';
6
6
  import { getPluginName } from './packageJsonHelpers.js';
7
7
  import buildModule, { getDefaultConfig } from './build.js';
8
8
  import setupMapUi from './setupMapUi.js';
9
- import { replaceAssets } from './pack.js';
10
9
 
11
10
 
12
11
  /**
@@ -28,7 +27,6 @@ export default async function buildStagingApp() {
28
27
 
29
28
  // copy assets folder if exists
30
29
  if (fs.existsSync(resolveContext('plugin-assets'))) {
31
- await replaceAssets(path.join(distPath, 'plugins', pluginName, 'index.js'), pluginName);
32
30
  await cp(
33
31
  resolveContext('plugin-assets'),
34
32
  path.join(distPath, 'plugins', pluginName, 'plugin-assets'),
package/src/create.js CHANGED
@@ -9,6 +9,7 @@ import { LicenseType, writeLicense } from './licenses.js';
9
9
  import { getDirname } from './hostingHelpers.js';
10
10
 
11
11
  export const { version, name } = JSON.parse(fs.readFileSync(path.join(getDirname(), '..', 'package.json')).toString());
12
+ const exec = util.promisify(childProcess.exec);
12
13
 
13
14
  /**
14
15
  * @typedef {Object} PluginTemplateOptions
@@ -22,6 +23,51 @@ export const { version, name } = JSON.parse(fs.readFileSync(path.join(getDirname
22
23
  * @property {Array<string>} peerDeps
23
24
  */
24
25
 
26
+ /**
27
+ * @enum {number}
28
+ */
29
+ const DepType = {
30
+ DEP: 1,
31
+ PEER: 2,
32
+ DEV: 3,
33
+ };
34
+
35
+ /**
36
+ * @param {Array<string>} deps
37
+ * @param {DepType} type
38
+ * @param {string} pluginPath
39
+ * @returns {Promise<void>}
40
+ */
41
+ async function installDeps(deps, type, pluginPath) {
42
+ let save = '--save';
43
+ if (type === DepType.PEER) {
44
+ save = '--save-peer';
45
+ } else if (type === DepType.DEV) {
46
+ save = '--save-dev';
47
+ }
48
+ const installCmd = `npm i ${save} ${deps.join(' ')}`;
49
+ const { stdout, stderr } = await exec(installCmd, { cwd: pluginPath });
50
+ logger.log(stdout);
51
+ logger.error(stderr);
52
+ }
53
+
54
+ /**
55
+ * @param {Array<string>} deps
56
+ * @param {string} pluginPath
57
+ * @returns {Promise<void>}
58
+ */
59
+ async function setUiPeerDepVersions(deps, pluginPath) {
60
+ const uiPackageJsonContent = await fs.promises.readFile(
61
+ path.join(pluginPath, 'node_modules', '@vcmap', 'ui', 'package.json'),
62
+ );
63
+ const uiPackageJson = JSON.parse(uiPackageJsonContent);
64
+ deps.forEach((dep, index) => {
65
+ if (uiPackageJson.peerDependencies[dep]) {
66
+ deps[index] = `${dep}@${uiPackageJson.peerDependencies[dep]}`;
67
+ }
68
+ });
69
+ }
70
+
25
71
  /**
26
72
  * @param {PluginTemplateOptions} options
27
73
  */
@@ -135,21 +181,15 @@ async function createPluginTemplate(options) {
135
181
 
136
182
 
137
183
  logger.spin('installing dependencies... (this may take a while)');
138
- const exec = util.promisify(childProcess.exec);
139
184
  try {
140
- options.peerDeps.push('@vcmap/ui');
141
- const installCmd = `npm i --save-peer ${options.peerDeps.join(' ')}`;
142
- const { stdout, stderr } = await exec(installCmd, { cwd: pluginPath });
143
- logger.log(stdout);
144
- logger.error(stderr);
185
+ await installDeps(['@vcmap/ui'], DepType.PEER, pluginPath);
186
+ await setUiPeerDepVersions(options.peerDeps, pluginPath);
187
+ await installDeps(options.peerDeps, DepType.PEER, pluginPath);
145
188
  const devDeps = [`${name}@${version}`];
146
189
  if (installEsLint) {
147
190
  devDeps.push('@vcsuite/eslint-config');
148
191
  }
149
- const installDevCmd = `npm i --save-dev ${devDeps.join(' ')}`;
150
- const { stdout: stdoutDev, stderr: stderrDev } = await exec(installDevCmd, { cwd: pluginPath });
151
- logger.log(stdoutDev);
152
- logger.error(stderrDev);
192
+ await installDeps(devDeps, DepType.DEV, pluginPath);
153
193
  logger.success('installed dependencies');
154
194
  } catch (e) {
155
195
  logger.error(e);
@@ -175,9 +215,9 @@ export default async function create() {
175
215
  const peerDependencyChoices = [
176
216
  { title: '@vcmap/core', value: '@vcmap/core' },
177
217
  { title: '@vcmap/cesium', value: '@vcmap/cesium' },
178
- { title: 'ol', value: 'ol@~6.13.0' },
179
- { title: 'vue', value: 'vue@~2.7.3' },
180
- { title: 'vuetify', value: 'vue@~2.6.7' },
218
+ { title: 'ol', value: 'ol' },
219
+ { title: 'vue', value: 'vue' },
220
+ { title: 'vuetify', value: 'vuetify' },
181
221
  ];
182
222
 
183
223
  const questions = [
@@ -269,6 +269,16 @@ export async function getMapUiIndexHtml(production) {
269
269
  return buffer.toString();
270
270
  }
271
271
 
272
+ /**
273
+ * @param {Express} app
274
+ * @param {string} base
275
+ */
276
+ export function addPluginAssets(app, base) {
277
+ app.use(`/${base}/plugin-assets*`, (req, res) => {
278
+ res.redirect(308, req.originalUrl.replace(`/${base}`, ''));
279
+ });
280
+ }
281
+
272
282
  /**
273
283
  * @param {Express} app
274
284
  * @param {import("vite").ViteDevServer} server
package/src/pack.js CHANGED
@@ -1,21 +1,10 @@
1
1
  import fs from 'fs';
2
- import { readFile, writeFile } from 'fs/promises';
3
2
  import archiver from 'archiver';
4
3
  import { logger } from '@vcsuite/cli-logger';
5
4
  import { getPluginName } from './packageJsonHelpers.js';
6
5
  import { resolveContext } from './context.js';
7
6
  import build from './build.js';
8
7
 
9
- /**
10
- * @param {string} fileName
11
- * @param {string} name
12
- * @returns {Promise<void>}
13
- */
14
- export async function replaceAssets(fileName, name) {
15
- let fileContent = await readFile(fileName, { encoding: 'utf8' });
16
- fileContent = fileContent.replace(/\.?\/?(plugin-assets)\//g, `plugins/${name}/$1/`);
17
- await writeFile(fileName, fileContent);
18
- }
19
8
 
20
9
  /**
21
10
  * @returns {Promise<void>}
@@ -94,8 +83,6 @@ export default async function pack() {
94
83
  const pluginName = await getPluginName();
95
84
  logger.spin(`building plugin: ${pluginName}`);
96
85
  await build({});
97
- await replaceAssets(resolveContext('dist', 'index.js'), pluginName);
98
- logger.debug('fixed asset paths');
99
86
  await ensureConfigJson();
100
87
  logger.debug('ensuring config.json');
101
88
  await zip(pluginName);
package/src/preview.js CHANGED
@@ -7,6 +7,7 @@ import {
7
7
  addConfigRoute,
8
8
  addIndexRoute,
9
9
  addMapConfigRoute,
10
+ addPluginAssets,
10
11
  checkReservedDirectories,
11
12
  createConfigJsonReloadPlugin,
12
13
  printVcmapUiVersion, resolveMapUi,
@@ -83,6 +84,7 @@ export default async function preview(options) {
83
84
 
84
85
  addMapConfigRoute(app, options.vcm ? `${options.vcm}/map.config.json` : null, options.auth, options.config, true);
85
86
  addIndexRoute(app, server, true, options.vcm, options.auth);
87
+ addPluginAssets(app, 'dist');
86
88
 
87
89
  if (!options.vcm) {
88
90
  logger.spin('compiling preview');
package/src/serve.js CHANGED
@@ -10,7 +10,7 @@ import { getContext } from './context.js';
10
10
  import {
11
11
  addConfigRoute,
12
12
  addIndexRoute,
13
- addMapConfigRoute,
13
+ addMapConfigRoute, addPluginAssets,
14
14
  checkReservedDirectories,
15
15
  createConfigJsonReloadPlugin,
16
16
  printVcmapUiVersion,
@@ -114,6 +114,7 @@ export default async function serve(options) {
114
114
 
115
115
  addMapConfigRoute(app, options.mapConfig, options.auth, options.config);
116
116
  addIndexRoute(app, server);
117
+ addPluginAssets(app, 'src');
117
118
  await addConfigRoute(app, options.auth, options.config);
118
119
 
119
120
  app.use(server.middlewares);