@vcmap/plugin-cli 2.0.11 → 2.0.12

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
@@ -5,11 +5,13 @@
5
5
  > For documentation on version 1 compatible with VC Map v4, see [this tag](https://github.com/virtualcitySYSTEMS/map-plugin-cli/tree/v1.1.1)
6
6
  > and be sure to install using `npm i -g @vcmap/plugin-cli@^1.1.0`**
7
7
 
8
- The `vcmplugin` cli helps develop and build plugins for the **VC Map**.
8
+ The `@vcmap/plugin-cli` helps develop and build plugins for the **VC Map**.
9
9
 
10
10
  ## Features
11
11
 
12
12
  - Creating basic plugin structure
13
+ - from scratch
14
+ - from an existing plugin [@vcmap/hello-world](https://www.npmjs.com/package/@vcmap/hello-world) used as template
13
15
  - Providing plugin development server
14
16
  - Building plugins for production
15
17
 
@@ -40,14 +42,20 @@ its current major version. You can then use either the scripts defined
40
42
  by the template in your package.json `npm start`, `npm run pack` etc. or `npx`
41
43
  to execute CLI commands.
42
44
 
45
+ All commands have (optional) cli options. Run `vcmplugin --help` or `vcmplugin help [command]` for more information.
46
+ For `serve` and `preview` you can alternatively define a `vcs.config.js` in your plugin's root directory.
47
+ For more information see [here](#vcm-config-js).
48
+
43
49
  ### 1. Creating a new plugin
44
50
 
45
- To create a new plugin template, run the following within your projects root:
51
+ To create a new plugin template, run the following:
46
52
  ```
47
53
  vcmplugin create
48
54
  ```
49
- This will open a command prompt helping you to create the basic [structure of a plugin](https://github.com/virtualcitySYSTEMS/map-plugin-examples/blob/main/doc/VCM_Plugin.md#2-structure-of-a-plugin).
50
- Be sure to check out the [peer dependecy section](#about_peer_dependencies) as well.
55
+ This will open a command prompt helping you to create the basic [structure of a plugin](#vc-map-plugins).
56
+ Be sure to check out the [peer dependecy section](#about-peer-dependencies) as well.
57
+
58
+ Optionally, in step 7 of the create-prompt you can choose an existing plugin [@vcmap/hello-world](https://www.npmjs.com/package/@vcmap/hello-world) as template.
51
59
 
52
60
  ### 2. Serving a plugin for development
53
61
 
@@ -56,7 +64,7 @@ To serve your plugin in dev mode, run the following within your projects root:
56
64
  npx vcmplugin serve
57
65
  ```
58
66
  The dev mode gives you complete debug information on all integrated libraries (@vcmap/core, ol etc.)
59
- By default this command will launch a dev server at localhost:8008 using
67
+ By default, this command will launch a dev server at localhost:8008 using
60
68
  the @vcmap/ui peer dependency package of your plugin as its base.
61
69
  You can provide an alternate map config if you wish.
62
70
 
@@ -122,15 +130,48 @@ npx vcmplugin pack
122
130
  This will create a folder `dist` with a zip file containing your bundled code and assets.
123
131
  To use the plugin productively in a hosted map,
124
132
  unzip this file on your server to `{vcm-root}/plugins` and add
125
- an entry to your VC MAP [config](#2-config) plugins section. This zip file can also be unzipped
133
+ an entry to your VC MAP `config` plugins section. This zip file can also be unzipped
126
134
  in the VC Publishers `plugins` public directory.
127
135
 
136
+ ## vcm config js
137
+
138
+ The `@vcmap/plugin-cli` supports an optional configuration file, which can be used for the commands `serve` and `preview`.
139
+ It's an alternative to providing cli parameters (which will still have precedence) and even has a few extra feature like proxy.
140
+ This can be helpful, if you want to share specific parameters valid for a specific plugin.
141
+ In order to do so just save a `vcm.config.js` in your plugin's root directory.
142
+ This file has to return a js object as default export.
143
+
144
+ Example `vcm.config.js` defining proxy and port:
145
+ ```js
146
+ export default {
147
+ // server.proxy see https://vitejs.dev/config/server-options.html#server-proxy
148
+ proxy: {
149
+ // string shorthand: http://localhost:8008/foo -> https://vc.systems/foo
150
+ '/foo': 'https://vc.systems',
151
+ },
152
+ port: 5005,
153
+ }
154
+ ```
155
+
156
+ The following parameters are valid:
157
+
158
+ | parameter | type | description |
159
+ |-----------|---------|-----------------------------------------------------------------------------------------------|
160
+ | config | string | an optional fileName to use for configuring the plugin |
161
+ | port | number | optional alternative port (default 8008) |
162
+ | https | boolean | wether to use http (default) or https |
163
+ | mapConfig | string | a filename or URL to a map config (for `serve` command) |
164
+ | vcm | string | a filename or URL to a map (for `preview` command) |
165
+ | proxy | Object | a server proxy (see [vitejs.dev](https://vitejs.dev/config/server-options.html#server-proxy)) |
166
+
167
+
128
168
  ## About Peer Dependencies
129
- The @vcmap/ui uses some _very large libraries_, notably `CesiumJS`. To reduce the amount
169
+ The [@vcmap/ui](https://github.com/virtualcitySYSTEMS/map-ui) uses some _very large libraries_, notably `CesiumJS`. To reduce the amount
130
170
  of traffic generated for loading plugins, all large libraries (see the list below),
131
- are _provided_ in production (instead of bundling them into every plugin). This a) guarantees
132
- a certain amount of type safety (using the @vcsuite/check parameter assertation library for instance),
133
- b) reduces the amount of traffic required to load an application and c) leverages browser
171
+ are _provided_ in production (instead of bundling them into every plugin). This
172
+ a) guarantees a certain amount of type safety (using the [@vcsuite/check](https://www.npmjs.com/package/@vcsuite/check) parameter assertion library for instance),
173
+ b) reduces the amount of traffic required to load an application and
174
+ c) leverages browser
134
175
  caching more readily.
135
176
 
136
177
  The following libraries are provided by the @vcmap/ui in a deployed application. You should define these
@@ -141,7 +182,14 @@ as peer dependencies if you use them in your plugin:
141
182
  - vue
142
183
  - vuetify
143
184
 
144
- During the build step, these libraries are automatically externalized by the vcmplugin-cli and in
185
+ If you want to update your plugin to a newer version of `@vcmap/ui`, the `@vcmap/plugin-cli` provides a update tool.
186
+ Just change to your plugin's directory and run:
187
+ ```bash
188
+ vcmplugin update
189
+ ```
190
+ This will automatically update all peer dependencies defined in your plugin to the corresponding version of the latest `@vcmap/ui`.
191
+
192
+ During the build step, these libraries are automatically externalized by the `@vcmap/plugin-cli` and in
145
193
  production all plugins & the map core _share_ the same cesium library.
146
194
 
147
195
  But, to make this work, it is important to define these dependencies as _peer dependencies_ of
@@ -161,7 +209,7 @@ import { Cartesian3 } from '@vcmap/cesium';
161
209
  openlayers provides a special case, since its modules do not provide a _flat_ namespace.
162
210
  To circumvent this limitation, _the @vcmap/ui provides a flat namespaced ol.js_ and a mechanic
163
211
  to rewrite openlayers imports. This is automatically applied by the `@vcmap/rollup-plugin-vcs-ol`
164
- used by the vcmplugin-cli build step. So openlayers imports can be written as:
212
+ used by the `@vcmap/plugin-cli` build step. So openlayers imports can be written as:
165
213
  ```js
166
214
  import Feature from 'ol/Feature.js';
167
215
  ```
@@ -192,7 +240,7 @@ to create your project, a template already adhering to these specs will be creat
192
240
  - Plugin dependencies have to be defined in the `package.json`.
193
241
  - `dependency`: all plugin specific dependencies NOT provided by the `@vcmap/ui`.
194
242
  - `peerDependency`: dependencies provided by the `@vcmap/ui`,
195
- - e.g. `@vcmap/core` or `@vcmap/ui` (see [About Peer Dependencies](#About-Peer-Dependencies) for more details)
243
+ - e.g. `@vcmap/core` or `@vcmap/ui` (see [About Peer Dependencies](#About-Peer-Dependencies) for more details)
196
244
  - `devDependency`: all dependencies only required for development, e.g. `eslint`.
197
245
  - Plugins can be published to NPM, but should contain both source and minified code
198
246
  to allow seamless integration into the [VC Map UI](https://github.com/virtualcitySYSTEMS/map-ui) environment.
@@ -305,7 +353,15 @@ cannot handle css resources.
305
353
  If you have to access assets _before_ your plugin is created (in the exported function of
306
354
  your plugin code), you will have to use the `baseUrl` provided to you to generate the URL yourself.
307
355
 
356
+ ## About testing plugins
357
+
358
+ To test your plugin's API you can use [vitest](https://vitest.dev/).
359
+ The `@vcmap/hello-world` plugin contains a basic setup of a test environment including example spec using vitest.
360
+ You will find the required setup in your created plugin, if you chose to add `test` as script to your `package.json` during the create-prompt.
361
+
362
+ As for now, we don't do any components testing.
363
+
308
364
  ## Notes on Developing
309
365
  To develop the plugin-cli, be sure to not `npm link` into plugins, since this will
310
366
  throw the resolver in resolving the @vcmap/ui peer dependency from the current plugin.
311
- Instead run `npm pack` in the plugin cli and install the tarball in the plugin directly.
367
+ Instead, run `npm pack` in the plugin cli and install the tarball in the plugin directly.
@@ -51,6 +51,16 @@ lint:
51
51
  script:
52
52
  - npm run lint
53
53
 
54
+ test:
55
+ <<: *after_build_definition
56
+ stage: test
57
+ script:
58
+ - npm run coverage -- --reporter junit --outputFile test-report.xml
59
+ coverage: '/^Statements\s*:\s*([^%]+)/'
60
+ artifacts:
61
+ reports:
62
+ junit: test-report.xml
63
+
54
64
  audit:
55
65
  <<: *after_build_definition
56
66
  stage: test
@@ -0,0 +1,30 @@
1
+ node_modules
2
+ /dist
3
+ test-report.xml
4
+
5
+
6
+ # local env files
7
+ .env.local
8
+ .env.*.local
9
+
10
+ # Log files
11
+ npm-debug.log*
12
+ yarn-debug.log*
13
+ yarn-error.log*
14
+ pnpm-debug.log*
15
+
16
+ # Editor directories and files
17
+ .idea
18
+ .vscode
19
+ *.suo
20
+ *.ntvs*
21
+ *.njsproj
22
+ *.sln
23
+ *.sw?
24
+
25
+ # test output
26
+ .nyc_output
27
+ coverage
28
+
29
+ # static output
30
+ docs
package/assets/index.html CHANGED
@@ -1,33 +1,29 @@
1
1
  <!DOCTYPE html>
2
2
  <html class="vcs-ui" lang="en">
3
- <head>
3
+ <head>
4
4
  <meta charset="utf-8" />
5
5
  <meta name="viewport" content="width=device-width,initial-scale=1.0" />
6
- <link
7
- rel="stylesheet"
8
- href="https://fonts.googleapis.com/css?family=Titillium+Web"
9
- />
10
- </head>
11
- <body style="height: 100vH;">
12
- <noscript>
13
- <strong>...</strong>
14
- </noscript>
15
- <div id="app">
16
- <div id="loading-wrapper">
6
+ </head>
7
+ <body style="height: 100vH;">
8
+ <noscript>
9
+ <strong>...</strong>
10
+ </noscript>
11
+ <div id="app">
12
+ <div id="loading-wrapper">
17
13
  <div id="loading-text">LOADING</div>
18
14
  <div id="loading-content"></div>
19
- </div>
20
15
  </div>
21
- <style>
22
- #loading-wrapper {
16
+ </div>
17
+ <style>
18
+ #loading-wrapper {
23
19
  position: fixed;
24
20
  width: 100%;
25
21
  height: 100%;
26
22
  left: 0;
27
23
  top: 0;
28
- }
24
+ }
29
25
 
30
- #loading-text {
26
+ #loading-text {
31
27
  display: block;
32
28
  position: absolute;
33
29
  top: 50%;
@@ -39,9 +35,9 @@
39
35
  text-align: center;
40
36
  font-family: 'PT Sans Narrow', sans-serif;
41
37
  font-size: 20px;
42
- }
38
+ }
43
39
 
44
- #loading-content {
40
+ #loading-content {
45
41
  display: block;
46
42
  position: relative;
47
43
  left: 50%;
@@ -49,9 +45,9 @@
49
45
  width: 170px;
50
46
  height: 170px;
51
47
  margin: -85px 0 0 -85px;
52
- }
48
+ }
53
49
 
54
- #loading-content {
50
+ #loading-content {
55
51
  border: 3px solid transparent;
56
52
  border-top-color: #409d76;
57
53
  border-bottom-color: #409d76;
@@ -60,22 +56,22 @@
60
56
  -moz-animation: loader 2s linear infinite;
61
57
  -o-animation: loader 2s linear infinite;
62
58
  animation: loader 2s linear infinite;
63
- }
59
+ }
64
60
 
65
- @keyframes loader {
61
+ @keyframes loader {
66
62
  0% {
67
- -webkit-transform: rotate(0deg);
68
- -ms-transform: rotate(0deg);
69
- transform: rotate(0deg);
63
+ -webkit-transform: rotate(0deg);
64
+ -ms-transform: rotate(0deg);
65
+ transform: rotate(0deg);
70
66
  }
71
67
 
72
68
  100% {
73
- -webkit-transform: rotate(360deg);
74
- -ms-transform: rotate(360deg);
75
- transform: rotate(360deg);
69
+ -webkit-transform: rotate(360deg);
70
+ -ms-transform: rotate(360deg);
71
+ transform: rotate(360deg);
76
72
  }
77
- }
78
- </style>
79
- <script type="module" src="./node_modules/@vcmap/ui/start.js"></script>
80
- </body>
73
+ }
74
+ </style>
75
+ <script type="module" src="./node_modules/@vcmap/ui/start.js"></script>
76
+ </body>
81
77
  </html>
@@ -0,0 +1,62 @@
1
+ import { version, name } from '../package.json';
2
+
3
+ /**
4
+ * @typedef {Object} PluginState
5
+ * @property {any} prop
6
+ */
7
+
8
+ /**
9
+ * @param {T} config - the configuration of this plugin instance, passed in from the app.
10
+ * @param {string} baseUrl - the absolute URL from which the plugin was loaded (without filename, ending on /)
11
+ * @returns {import("@vcmap/ui/src/vcsUiApp").VcsPlugin<T, PluginState>}
12
+ * @template {Object} T
13
+ */
14
+ export default function plugin(config, baseUrl) {
15
+ // eslint-disable-next-line no-console
16
+ console.log(config, baseUrl);
17
+ return {
18
+ get name() { return name; },
19
+ get version() { return version; },
20
+ /**
21
+ * @param {import("@vcmap/ui").VcsUiApp} vcsUiApp
22
+ * @param {PluginState=} state
23
+ * @returns {Promise<void>}
24
+ */
25
+ initialize: async (vcsUiApp, state) => {
26
+ // eslint-disable-next-line no-console
27
+ console.log('Called before loading the rest of the current context. Passed in the containing Vcs UI App ', vcsUiApp, state);
28
+ },
29
+ /**
30
+ * @param {import("@vcmap/ui").VcsUiApp} vcsUiApp
31
+ * @returns {Promise<void>}
32
+ */
33
+ onVcsAppMounted: async (vcsUiApp) => {
34
+ // eslint-disable-next-line no-console
35
+ console.log('Called when the root UI component is mounted and managers are ready to accept components', vcsUiApp);
36
+ },
37
+ /**
38
+ * @returns {T}
39
+ */
40
+ toJSON() {
41
+ // eslint-disable-next-line no-console
42
+ console.log('Called when serializing this plugin instance');
43
+ return {};
44
+ },
45
+ /**
46
+ * should return the plugins state
47
+ * @param {boolean} forUrl
48
+ * @returns {PluginState}
49
+ */
50
+ getState(forUrl) {
51
+ // eslint-disable-next-line no-console
52
+ console.log('Called when collecting state, e.g. for create link', forUrl);
53
+ return {
54
+ prop: '*',
55
+ };
56
+ },
57
+ destroy() {
58
+ // eslint-disable-next-line no-console
59
+ console.log('hook to cleanup');
60
+ },
61
+ };
62
+ }
package/cli.js CHANGED
@@ -2,9 +2,9 @@
2
2
  import program from 'commander';
3
3
  import './src/defaultCommand.js';
4
4
  import {
5
- create, serve, build, pack, preview,
5
+ create, serve, build, pack, preview, update,
6
6
  } from './index.js';
7
- import { version } from './src/create.js';
7
+ import { version } from './src/pluginCliHelper.js';
8
8
  import setupMapUi from './src/setupMapUi.js';
9
9
  import buildStagingApp from './src/buildStagingApp.js';
10
10
 
@@ -25,13 +25,6 @@ program
25
25
  .defaultOptions()
26
26
  .defaultServeOptions()
27
27
  .option('--vcm [url]', 'URL to a virtualcityMAP application', val => val.replace(/\/$/, ''))
28
- .option('--proxyRoute <route>', 'a route to proxy as well (e.g. if you have additional proxies on your server)', (val, prev) => {
29
- if (!prev) {
30
- return [val];
31
- }
32
- prev.push(val);
33
- return prev;
34
- }, [])
35
28
  .safeAction(preview);
36
29
 
37
30
  program
@@ -57,4 +50,9 @@ program
57
50
  .command('setup-map-ui')
58
51
  .safeAction(setupMapUi);
59
52
 
53
+ program
54
+ .command('update')
55
+ .defaultOptions()
56
+ .safeAction(update);
57
+
60
58
  program.parse(process.argv);
package/index.js CHANGED
@@ -4,5 +4,6 @@ export { default as build } from './src/build.js';
4
4
  export { default as pack } from './src/pack.js';
5
5
  export { default as preview } from './src/preview.js';
6
6
  export { default as buildStagingApp } from './src/buildStagingApp.js';
7
+ export { default as update } from './src/update.js';
7
8
  export { default as setupMapUi } from './src/setupMapUi.js';
8
9
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vcmap/plugin-cli",
3
- "version": "2.0.11",
3
+ "version": "2.0.12",
4
4
  "description": "A CLI to help develop and build plugins for the VC Map",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -34,13 +34,14 @@
34
34
  "prompts": "^2.4.1",
35
35
  "sass": "1.32.13",
36
36
  "semver": "^7.3.5",
37
+ "tar": "^6.1.13",
37
38
  "vite": "^3.2.0",
38
39
  "vite-plugin-vue2": "^2.0.2",
39
- "vue-template-compiler": "~2.7.3"
40
+ "vue-template-compiler": "~2.7.14"
40
41
  },
41
42
  "peerDependencies": {
42
43
  "@vcmap/ui": "^5.0.0-rc.15",
43
- "vue": "~2.7.3"
44
+ "vue": "~2.7.14"
44
45
  },
45
46
  "peerDependenciesMeta": {
46
47
  "@vcmap/ui": {
@@ -1,8 +1,9 @@
1
1
  import { cp, copyFile, writeFile, rm, mkdir } from 'fs/promises';
2
2
  import path from 'path';
3
3
  import fs from 'fs';
4
+ import { logger } from '@vcsuite/cli-logger';
4
5
  import { getContext, resolveContext } from './context.js';
5
- import { getConfigJson } from './hostingHelpers.js';
6
+ import { executeUiNpm, getConfigJson, resolveMapUi } from './hostingHelpers.js';
6
7
  import { getPluginName } from './packageJsonHelpers.js';
7
8
  import buildModule, { getDefaultConfig } from './build.js';
8
9
  import setupMapUi from './setupMapUi.js';
@@ -34,6 +35,15 @@ export default async function buildStagingApp() {
34
35
  );
35
36
  }
36
37
 
38
+ // In case @vcmap/ui is linked via git+ssh, dist folder is not available and must be built first
39
+ if (!fs.existsSync(resolveMapUi('dist'))) {
40
+ logger.spin('building @vcmap/ui');
41
+ await executeUiNpm('--production=false --no-package-lock', 'install');
42
+ await executeUiNpm('build');
43
+ logger.stopSpinner();
44
+ logger.info('@vcmap/ui built');
45
+ }
46
+
37
47
  await copyFile(
38
48
  path.join(getContext(), 'node_modules', '@vcmap', 'ui', 'dist', 'index.html'),
39
49
  path.join(distPath, 'index.html'),
@@ -55,4 +65,5 @@ export default async function buildStagingApp() {
55
65
  path.join(distPath, 'config'),
56
66
  { recursive: true },
57
67
  );
68
+ logger.success('buildStagingApp finished');
58
69
  }
package/src/create.js CHANGED
@@ -2,14 +2,12 @@ import fs from 'fs';
2
2
  import path from 'path';
3
3
  import prompts from 'prompts';
4
4
  import semver from 'semver';
5
- import util from 'util';
6
- import childProcess from 'child_process';
5
+ import tar from 'tar';
7
6
  import { logger } from '@vcsuite/cli-logger';
8
7
  import { LicenseType, writeLicense } from './licenses.js';
9
- import { getDirname } from './hostingHelpers.js';
10
-
11
- export const { version, name } = JSON.parse(fs.readFileSync(path.join(getDirname(), '..', 'package.json')).toString());
12
- const exec = util.promisify(childProcess.exec);
8
+ import { DepType, installDeps } from './packageJsonHelpers.js';
9
+ import { updatePeerDependencies } from './update.js';
10
+ import { name, version, promiseExec, getDirname } from './pluginCliHelper.js';
13
11
 
14
12
  /**
15
13
  * @typedef {Object} PluginTemplateOptions
@@ -20,77 +18,120 @@ const exec = util.promisify(childProcess.exec);
20
18
  * @property {string} author
21
19
  * @property {string} repository
22
20
  * @property {string} license
21
+ * @property {string} template
23
22
  * @property {Array<string>} peerDeps
23
+ * @property {boolean} gitlabCi
24
24
  */
25
25
 
26
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
27
+ * @param {string} pluginName
38
28
  * @param {string} pluginPath
39
- * @returns {Promise<void>}
29
+ * @param {string[]} filter - files or dirs to be extracted
40
30
  */
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';
31
+ async function downloadAndExtractPluginTar(pluginName, pluginPath, filter = undefined) {
32
+ const logMsg = filter ? filter.join(', ') : pluginName;
33
+ logger.spin(`Downloading and extracting ${logMsg}`);
34
+ const { stdout: packOut, stderr: packErr } = await promiseExec(`npm pack ${pluginName} --quiet`, { cwd: pluginPath });
35
+ logger.error(packErr);
36
+
37
+ const tarName = packOut.trim();
38
+ const tarPath = path.join(pluginPath, tarName);
39
+ const extractOptions = {
40
+ file: tarPath,
41
+ cwd: pluginPath,
42
+ strip: 1,
43
+ };
44
+ if (filter) {
45
+ extractOptions.filter = entryPath => filter.some(f => entryPath.includes(f));
47
46
  }
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);
47
+ await tar.x(extractOptions);
48
+ await fs.promises.rm(tarPath);
49
+ logger.success(`Downloaded and extracted template ${logMsg}`);
50
+ logger.stopSpinner();
52
51
  }
53
52
 
54
53
  /**
55
- * @param {Array<string>} deps
54
+ * Copies an existing plugin as template and edits package.json
55
+ * @param {PluginTemplateOptions} options
56
56
  * @param {string} pluginPath
57
57
  * @returns {Promise<void>}
58
58
  */
59
- async function setUiPeerDepVersions(deps, pluginPath) {
60
- const uiPackageJsonContent = await fs.promises.readFile(
61
- path.join(pluginPath, 'node_modules', '@vcmap', 'ui', 'package.json'),
59
+ async function copyPluginTemplate(options, pluginPath) {
60
+ await downloadAndExtractPluginTar(options.template, pluginPath);
61
+
62
+ const pluginPackageJson = JSON.parse((await fs.promises.readFile(path.join(pluginPath, 'package.json'))).toString());
63
+ const userPackageJson = {
64
+ name: options.name,
65
+ version: options.version,
66
+ description: options.description,
67
+ author: options.author,
68
+ license: options.license,
69
+ };
70
+ const packageJson = { ...pluginPackageJson, ...userPackageJson };
71
+ if (options.repository) {
72
+ packageJson.repository = {
73
+ url: options.repository,
74
+ };
75
+ } else {
76
+ delete options.repository;
77
+ }
78
+
79
+ const writePackagePromise = fs.promises.writeFile(
80
+ path.join(pluginPath, 'package.json'),
81
+ JSON.stringify(packageJson, null, 2),
62
82
  );
63
- const uiPackageJson = JSON.parse(uiPackageJsonContent);
64
- deps.forEach((dep, index) => {
65
- if (uiPackageJson.peerDependencies[dep]) {
66
- deps[index] = `${dep}@${uiPackageJson.peerDependencies[dep]}`;
83
+
84
+ const configJson = JSON.parse((await fs.promises.readFile(path.join(pluginPath, 'config.json'))).toString());
85
+ configJson.name = options.name;
86
+
87
+ const writeConfigPromise = fs.promises.writeFile(
88
+ path.join(pluginPath, 'config.json'),
89
+ JSON.stringify(configJson, null, 2),
90
+ );
91
+
92
+ await Promise.all([
93
+ writePackagePromise,
94
+ writeConfigPromise,
95
+ ]);
96
+ logger.debug('created plugin template');
97
+
98
+ try {
99
+ await updatePeerDependencies(packageJson.peerDependencies, pluginPath);
100
+ logger.spin('installing dependencies... (this may take a while)');
101
+ if (packageJson.dependencies) {
102
+ const deps = Object.entries(packageJson.dependencies)
103
+ .map(([depName, depVersion]) => `${depName}@${depVersion}`);
104
+ await installDeps(deps, DepType.DEP, pluginPath);
67
105
  }
68
- });
106
+ await installDeps([`${name}@${version}`], DepType.DEV, pluginPath);
107
+ logger.success('Installed dependencies');
108
+ } catch (e) {
109
+ logger.error(e);
110
+ logger.failure('Failed installing dependencies');
111
+ }
112
+ logger.stopSpinner();
69
113
  }
70
114
 
71
115
  /**
116
+ * Creates a plugin from scratch
72
117
  * @param {PluginTemplateOptions} options
118
+ * @param {string} pluginPath
73
119
  */
74
- async function createPluginTemplate(options) {
75
- if (!options.name) {
76
- logger.error('please provide a plugin name as input parameter');
77
- process.exit(1);
120
+ async function createPluginTemplate(options, pluginPath) {
121
+ const installVitest = options.scripts && options.scripts.find(script => script.test);
122
+ if (!installVitest) {
123
+ options.scripts.push({ test: 'echo "Error: no test specified" && exit 1' });
124
+ } else {
125
+ options.scripts.push(
126
+ { coverage: 'vitest run --coverage' },
127
+ );
78
128
  }
79
- logger.info(`creating new plugin: ${options.name}`);
80
129
 
81
- const pluginPath = path.join(process.cwd(), options.name);
82
- if (fs.existsSync(pluginPath)) {
83
- logger.error('plugin with the provided name already exists');
84
- process.exit(1);
85
- }
86
-
87
- await fs.promises.mkdir(pluginPath);
88
- await fs.promises.mkdir(path.join(pluginPath, 'plugin-assets'));
89
- logger.debug('created plugin directory');
90
130
  const packageJson = {
91
131
  name: options.name,
92
132
  version: options.version,
93
133
  description: options.description,
134
+ type: 'module',
94
135
  main: 'src/index.js',
95
136
  scripts: Object.assign({ prepublishOnly: 'vcmplugin build' }, ...options.scripts),
96
137
  author: options.author,
@@ -142,6 +183,66 @@ async function createPluginTemplate(options) {
142
183
  JSON.stringify(configJson, null, 2),
143
184
  );
144
185
 
186
+ await fs.promises.mkdir(path.join(pluginPath, 'src'));
187
+ logger.debug('created src directory');
188
+
189
+ const copyIndexPromise = fs.promises.copyFile(
190
+ path.join(getDirname(), '..', 'assets', 'index.js'),
191
+ path.join(pluginPath, 'src', 'index.js'),
192
+ );
193
+
194
+ await Promise.all([
195
+ writePackagePromise,
196
+ writeConfigPromise,
197
+ copyIndexPromise,
198
+ ]);
199
+
200
+ if (installVitest) {
201
+ logger.debug('setting up test environment');
202
+ await downloadAndExtractPluginTar('@vcmap/hello-world', pluginPath, ['tests', 'vitest.config.js']);
203
+ }
204
+
205
+ try {
206
+ const peerDependencies = options.peerDeps.reduce((obj, key) => ({ ...obj, [key]: 'latest' }), {});
207
+ await updatePeerDependencies(peerDependencies, pluginPath);
208
+ logger.spin('installing dependencies... (this may take a while)');
209
+ const devDeps = [`${name}@${version}`];
210
+ if (installEsLint) {
211
+ devDeps.push('@vcsuite/eslint-config');
212
+ }
213
+ if (installVitest) {
214
+ devDeps.push('vite', 'vitest', '@vitest/coverage-c8', 'jest-canvas-mock', 'jsdom');
215
+ }
216
+ await installDeps(devDeps, DepType.DEV, pluginPath);
217
+ logger.success('Installed dependencies');
218
+ } catch (e) {
219
+ logger.error(e);
220
+ logger.failure('Failed installing dependencies');
221
+ }
222
+ logger.stopSpinner();
223
+ }
224
+
225
+ /**
226
+ * Creates a new plugin either by copying a template or from scratch
227
+ * @param {PluginTemplateOptions} options
228
+ */
229
+ async function createPlugin(options) {
230
+ if (!options.name) {
231
+ logger.error('please provide a plugin name as input parameter');
232
+ process.exit(1);
233
+ }
234
+ logger.debug(`creating new plugin: ${options.name}`);
235
+
236
+ const pluginPath = path.join(process.cwd(), options.name);
237
+ if (fs.existsSync(pluginPath)) {
238
+ logger.error('plugin with the provided name already exists');
239
+ process.exit(1);
240
+ }
241
+
242
+ await fs.promises.mkdir(pluginPath);
243
+ await fs.promises.mkdir(path.join(pluginPath, 'plugin-assets'));
244
+ logger.debug('created plugin directory');
245
+
145
246
  const writeNpmrcPromise = fs.promises.writeFile(
146
247
  path.join(pluginPath, '.npmrc'),
147
248
  'registry=https://registry.npmjs.org\n',
@@ -160,49 +261,44 @@ async function createPluginTemplate(options) {
160
261
  `# v${options.version}\nDocument features and fixes`,
161
262
  );
162
263
 
163
- await fs.promises.mkdir(path.join(pluginPath, 'src'));
164
- logger.debug('created src directory');
165
-
166
- const copyTemplatePromise = fs.promises.cp(
167
- path.join(getDirname(), '..', 'assets', 'helloWorld'),
168
- pluginPath,
169
- { recursive: true },
264
+ const copyGitIgnorePromise = fs.promises.copyFile(
265
+ path.join(getDirname(), '..', 'assets', 'gitignore'),
266
+ path.join(pluginPath, '.gitignore'),
170
267
  );
171
268
 
172
269
  await Promise.all([
173
- writePackagePromise,
174
- writeConfigPromise,
175
270
  writeNpmrcPromise,
176
271
  writeReadmePromise,
177
272
  writeChangesPromise,
178
- copyTemplatePromise,
179
273
  writeLicense(options.author, options.license, pluginPath),
274
+ copyGitIgnorePromise,
180
275
  ]);
181
276
 
277
+ if (options.gitlabCi) {
278
+ await fs.promises.copyFile(
279
+ path.join(getDirname(), '..', 'assets', '.gitlab-ci.yml'),
280
+ path.join(pluginPath, '.gitlab-ci.yml'),
281
+ );
282
+ }
182
283
 
183
- logger.spin('installing dependencies... (this may take a while)');
184
- try {
185
- await installDeps(['@vcmap/ui'], DepType.PEER, pluginPath);
186
- await setUiPeerDepVersions(options.peerDeps, pluginPath);
187
- await installDeps(options.peerDeps, DepType.PEER, pluginPath);
188
- const devDeps = [`${name}@${version}`];
189
- if (installEsLint) {
190
- devDeps.push('@vcsuite/eslint-config');
191
- }
192
- await installDeps(devDeps, DepType.DEV, pluginPath);
193
- logger.success('installed dependencies');
194
- } catch (e) {
195
- logger.error(e);
196
- logger.failure('installed dependencies');
284
+ if (options.template) {
285
+ await copyPluginTemplate(options, pluginPath);
286
+ } else {
287
+ await createPluginTemplate(options, pluginPath);
197
288
  }
198
- logger.stopSpinner();
199
- logger.success('created plugin');
289
+ logger.success(`Created plugin ${options.name}`);
200
290
  }
201
291
 
202
292
  /**
203
293
  * @returns {Promise<void>}
204
294
  */
205
295
  export default async function create() {
296
+ const templateChoices = [
297
+ { title: 'no template (basic structure)', value: null },
298
+ { title: 'hello-world', value: '@vcmap/hello-world' },
299
+ // to add further templates add a choice here
300
+ ];
301
+
206
302
  const scriptChoices = [
207
303
  { title: 'build', value: { build: 'vcmplugin build' }, selected: true },
208
304
  { title: 'pack', value: { pack: 'vcmplugin pack' }, selected: true },
@@ -210,6 +306,7 @@ export default async function create() {
210
306
  { title: 'preview', value: { preview: 'vcmplugin preview' }, selected: true },
211
307
  { title: 'buildStagingApp', value: { buildStagingApp: 'vcmplugin buildStagingApp' }, selected: true },
212
308
  { title: 'lint', value: { lint: 'eslint "{src,tests}/**/*.{js,vue}"' }, selected: true },
309
+ { title: 'test', value: { test: 'vitest' }, selected: true },
213
310
  ];
214
311
 
215
312
  const peerDependencyChoices = [
@@ -229,7 +326,6 @@ export default async function create() {
229
326
  if (!value) {
230
327
  return false;
231
328
  }
232
-
233
329
  if (fs.existsSync(path.join(process.cwd(), value))) {
234
330
  return `Directory ${value} already exists`;
235
331
  }
@@ -249,13 +345,6 @@ export default async function create() {
249
345
  message: 'Description',
250
346
  initial: '',
251
347
  },
252
- {
253
- type: 'multiselect',
254
- message: 'Add the following scripts to the package.json.',
255
- name: 'scripts',
256
- choices: scriptChoices,
257
- hint: '- Space to select. Enter to submit',
258
- },
259
348
  {
260
349
  type: 'text',
261
350
  name: 'author',
@@ -280,15 +369,37 @@ export default async function create() {
280
369
  })),
281
370
  },
282
371
  {
372
+ type: 'select',
373
+ name: 'template',
374
+ message: 'Choose an existing plugin as template',
375
+ initial: 0,
376
+ choices: templateChoices,
377
+ },
378
+ {
379
+ type: (prev, values) => (!values.template ? 'multiselect' : null),
380
+ message: 'Add the following scripts to the package.json.',
381
+ name: 'scripts',
382
+ choices: scriptChoices,
383
+ hint: '- Space to select. Enter to submit',
384
+ },
385
+ {
386
+ type: (prev, values) => (!values.template ? 'multiselect' : null),
283
387
  name: 'peerDeps',
284
- type: 'multiselect',
285
388
  message: 'Add the following peer dependencies to the package.json.',
286
389
  choices: peerDependencyChoices,
287
390
  hint: '- Space to select. Enter to submit',
288
391
  },
392
+ {
393
+ type: 'toggle',
394
+ name: 'gitlabCi',
395
+ message: 'Add gitlab-ci.yml?',
396
+ initial: false,
397
+ active: 'yes',
398
+ inactive: 'no',
399
+ },
289
400
  ];
290
401
 
291
402
  const answers = await prompts(questions, { onCancel() { process.exit(0); } });
292
403
 
293
- await createPluginTemplate(answers);
404
+ await createPlugin(answers);
294
405
  }
@@ -1,6 +1,4 @@
1
- import { fileURLToPath, URL } from 'url';
2
- import { promisify } from 'util';
3
- import { exec } from 'child_process';
1
+ import { URL } from 'url';
4
2
  import https from 'https';
5
3
  import http from 'http';
6
4
  import fs from 'fs';
@@ -8,6 +6,7 @@ import path from 'path';
8
6
  import { logger } from '@vcsuite/cli-logger';
9
7
  import { getContext, resolveContext } from './context.js';
10
8
  import { getPluginEntry, getPluginName } from './packageJsonHelpers.js';
9
+ import { promiseExec, getDirname } from './pluginCliHelper.js';
11
10
 
12
11
  /**
13
12
  * @typedef {Object} HostingOptions
@@ -17,11 +16,6 @@ import { getPluginEntry, getPluginName } from './packageJsonHelpers.js';
17
16
  * @property {boolean} [https]
18
17
  */
19
18
 
20
- /**
21
- * @type {(arg1: string, opt?: Object) => Promise<string>}
22
- */
23
- const promiseExec = promisify(exec);
24
-
25
19
  /**
26
20
  * @param {...string} pathSegments
27
21
  * @returns {string}
@@ -39,13 +33,6 @@ export function checkReservedDirectories() {
39
33
  });
40
34
  }
41
35
 
42
- /**
43
- * @returns {string}
44
- */
45
- export function getDirname() {
46
- return path.dirname(fileURLToPath(import.meta.url));
47
- }
48
-
49
36
  /**
50
37
  * @param {string} stringUrl
51
38
  * @param {string} auth
@@ -301,10 +288,14 @@ export function addIndexRoute(app, server, production, hostedVcm, auth) {
301
288
  }
302
289
 
303
290
  /**
304
- * @param {string} command
291
+ * @param {string|null} args
292
+ * @param {string} [command='run']
305
293
  * @returns {Promise<string>}
306
294
  */
307
- export function executeUiNpm(command) {
295
+ export function executeUiNpm(args, command = 'run') {
308
296
  const mapUiDir = resolveMapUi();
309
- return promiseExec(`npm run ${command}`, { cwd: mapUiDir });
297
+ if (args) {
298
+ return promiseExec(`npm ${command} ${args}`, { cwd: mapUiDir });
299
+ }
300
+ return promiseExec(`npm ${command}`, { cwd: mapUiDir });
310
301
  }
@@ -1,5 +1,7 @@
1
1
  import fs from 'fs';
2
+ import { logger } from '@vcsuite/cli-logger';
2
3
  import { resolveContext } from './context.js';
4
+ import { promiseExec } from './pluginCliHelper.js';
3
5
 
4
6
  /** @type {Object|null} */
5
7
  let packageJson = null;
@@ -7,7 +9,7 @@ let packageJson = null;
7
9
  /**
8
10
  * @returns {Promise<Object>}
9
11
  */
10
- async function getPackageJson() {
12
+ export async function getPackageJson() {
11
13
  if (!packageJson) {
12
14
  const packageJsonFileName = resolveContext('package.json');
13
15
  if (!fs.existsSync(packageJsonFileName)) {
@@ -46,3 +48,34 @@ export async function getPluginEntry() {
46
48
  return entry;
47
49
  }
48
50
 
51
+ /**
52
+ * @enum {number}
53
+ */
54
+ export const DepType = {
55
+ DEP: 1,
56
+ PEER: 2,
57
+ DEV: 3,
58
+ };
59
+
60
+ /**
61
+ * @param {Array<string>} deps
62
+ * @param {DepType} type
63
+ * @param {string} pluginPath
64
+ * @returns {Promise<void>}
65
+ */
66
+ export async function installDeps(deps, type, pluginPath) {
67
+ if (deps.length < 1) {
68
+ return;
69
+ }
70
+ let save = '--save';
71
+ if (type === DepType.PEER) {
72
+ save = '--save-peer';
73
+ } else if (type === DepType.DEV) {
74
+ save = '--save-dev';
75
+ }
76
+ const installCmd = `npm i ${save} ${deps.join(' ')}`;
77
+ logger.debug(installCmd);
78
+ const { stdout, stderr } = await promiseExec(installCmd, { cwd: pluginPath });
79
+ logger.log(stdout);
80
+ logger.error(stderr);
81
+ }
@@ -0,0 +1,41 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import util from 'util';
4
+ import childProcess from 'child_process';
5
+ import { fileURLToPath, pathToFileURL } from 'url';
6
+ import { logger } from '@vcsuite/cli-logger';
7
+ import { getContext } from './context.js';
8
+
9
+ /**
10
+ * @returns {string}
11
+ */
12
+ export function getDirname() {
13
+ return path.dirname(fileURLToPath(import.meta.url));
14
+ }
15
+
16
+ /**
17
+ * @type {string} version
18
+ * @type {string} name
19
+ */
20
+ export const { version, name } = JSON.parse(fs.readFileSync(path.join(getDirname(), '..', 'package.json')).toString());
21
+
22
+ /**
23
+ * @type {(arg1: string) => Promise<string>}
24
+ */
25
+ export const promiseExec = util.promisify(childProcess.exec);
26
+
27
+ export async function getVcmConfigJs() {
28
+ let vcmConfigJs = {};
29
+ const vcmConfigJsPath = path.resolve(getContext(), 'vcm.config.js');
30
+ if (!fs.existsSync(vcmConfigJsPath)) {
31
+ logger.debug(`${vcmConfigJsPath} not existing!`);
32
+ return vcmConfigJs;
33
+ }
34
+ try {
35
+ vcmConfigJs = await import(pathToFileURL(vcmConfigJsPath));
36
+ logger.info('Using vcm.config.js found in current project.');
37
+ } catch (err) {
38
+ logger.error(err);
39
+ }
40
+ return vcmConfigJs;
41
+ }
package/src/preview.js CHANGED
@@ -10,11 +10,13 @@ import {
10
10
  addPluginAssets,
11
11
  checkReservedDirectories,
12
12
  createConfigJsonReloadPlugin,
13
- printVcmapUiVersion, resolveMapUi,
13
+ printVcmapUiVersion,
14
+ resolveMapUi,
14
15
  } from './hostingHelpers.js';
15
16
  import build, { getDefaultConfig, getLibraryPaths } from './build.js';
16
17
  import { getContext } from './context.js';
17
18
  import setupMapUi from './setupMapUi.js';
19
+ import { getVcmConfigJs } from './pluginCliHelper.js';
18
20
 
19
21
  /**
20
22
  * @typedef {HostingOptions} PreviewOptions
@@ -61,7 +63,7 @@ async function getServerOptions(hostedVcm, https) {
61
63
  alias,
62
64
  },
63
65
  server: {
64
- middlewareMode: 'html',
66
+ middlewareMode: true,
65
67
  proxy,
66
68
  https,
67
69
  },
@@ -73,20 +75,22 @@ async function getServerOptions(hostedVcm, https) {
73
75
  * @returns {Promise<void>}
74
76
  */
75
77
  export default async function preview(options) {
76
- if (!options.vcm) {
78
+ const { default: vcmConfigJs } = await getVcmConfigJs();
79
+ const mergedOptions = { ...vcmConfigJs, ...options };
80
+ if (!mergedOptions.vcm) {
77
81
  await printVcmapUiVersion();
78
82
  }
79
83
  checkReservedDirectories();
80
84
  await build({ development: false, watch: true });
81
85
  const app = express();
82
86
  logger.info('Starting preview server...');
83
- const server = await createServer(await getServerOptions(options.vcm, options.https));
87
+ const server = await createServer(await getServerOptions(mergedOptions.vcm, mergedOptions.https));
84
88
 
85
- addMapConfigRoute(app, options.vcm ? `${options.vcm}/map.config.json` : null, options.auth, options.config, true);
86
- addIndexRoute(app, server, true, options.vcm, options.auth);
89
+ addMapConfigRoute(app, mergedOptions.vcm ? `${mergedOptions.vcm}/map.config.json` : null, mergedOptions.auth, mergedOptions.config, true);
90
+ addIndexRoute(app, server, true, mergedOptions.vcm, mergedOptions.auth);
87
91
  addPluginAssets(app, 'dist');
88
92
 
89
- if (!options.vcm) {
93
+ if (!mergedOptions.vcm) {
90
94
  logger.spin('compiling preview');
91
95
  if (!fs.existsSync(resolveMapUi('plugins', 'node_modules'))) {
92
96
  logger.info('Could not detect node_modules in map ui plugins. Assuming map UI not setup');
@@ -98,12 +102,12 @@ export default async function preview(options) {
98
102
  logger.info('@vcmap/ui built for preview');
99
103
  app.use('/assets', express.static(path.join(getContext(), 'node_modules', '@vcmap', 'ui', 'dist', 'assets')));
100
104
  app.use('/plugins', express.static(path.join(getContext(), 'dist', 'plugins')));
101
- await addConfigRoute(app, options.auth, options.config, true);
105
+ await addConfigRoute(app, mergedOptions.auth, mergedOptions.config, true);
102
106
  }
103
107
 
104
108
  app.use(server.middlewares);
105
109
 
106
- const port = options.port || 5005;
110
+ const port = mergedOptions.port || 5005;
107
111
  await app.listen(port);
108
112
  logger.info(`Server running on port ${port}`);
109
113
  }
package/src/serve.js CHANGED
@@ -15,10 +15,11 @@ import {
15
15
  resolveMapUi,
16
16
  } from './hostingHelpers.js';
17
17
  import { getPluginName } from './packageJsonHelpers.js';
18
+ import { getVcmConfigJs } from './pluginCliHelper.js';
18
19
 
19
20
  /**
20
21
  * @typedef {HostingOptions} ServeOptions
21
- * @property {string} [mapConfig] - an filename or URL to a map config
22
+ * @property {string} [mapConfig] - a filename or URL to a map config
22
23
  */
23
24
 
24
25
  async function getProxy(protocol, port) {
@@ -48,6 +49,13 @@ async function getProxy(protocol, port) {
48
49
  if (hasThisPlugin) {
49
50
  delete proxy[hasThisPlugin];
50
51
  }
52
+
53
+ // exampleData is not part of the @vcmap/ui package and must be proxied therefore
54
+ proxy['^/exampleData'] = {
55
+ target: 'https://raw.githubusercontent.com/virtualcitySYSTEMS/map-ui/main',
56
+ changeOrigin: true,
57
+ secure: false,
58
+ };
51
59
  return proxy;
52
60
  }
53
61
 
@@ -60,13 +68,15 @@ export default async function serve(options) {
60
68
  logger.error('Can only serve in dev mode, if the map ui is a dependency of the current context');
61
69
  return;
62
70
  }
71
+ const { default: vcmConfigJs } = await getVcmConfigJs();
72
+ const mergedOptions = { ...vcmConfigJs, ...options };
63
73
  await printVcmapUiVersion();
64
74
  checkReservedDirectories();
65
75
  const app = express();
66
- const port = options.port || 8008;
76
+ const port = mergedOptions.port || 8008;
67
77
 
68
78
  logger.info('Starting development server...');
69
- const proxy = await getProxy(options.https ? 'https' : 'http', port);
79
+ const proxy = await getProxy(mergedOptions.https ? 'https' : 'http', port);
70
80
 
71
81
  const server = await createServer({
72
82
  root: getContext(),
@@ -90,9 +100,9 @@ export default async function serve(options) {
90
100
  createConfigJsonReloadPlugin(),
91
101
  ],
92
102
  server: {
93
- middlewareMode: 'html',
94
- https: options.https,
95
- proxy,
103
+ middlewareMode: true,
104
+ https: mergedOptions.https,
105
+ proxy: { ...mergedOptions.proxy, ...proxy },
96
106
  },
97
107
  css: {
98
108
  preprocessorOptions: {
@@ -103,10 +113,10 @@ export default async function serve(options) {
103
113
  },
104
114
  });
105
115
 
106
- addMapConfigRoute(app, options.mapConfig, options.auth, options.config);
116
+ addMapConfigRoute(app, mergedOptions.mapConfig, mergedOptions.auth, mergedOptions.config);
107
117
  addIndexRoute(app, server);
108
118
  addPluginAssets(app, 'src');
109
- await addConfigRoute(app, options.auth, options.config);
119
+ await addConfigRoute(app, mergedOptions.auth, mergedOptions.config);
110
120
 
111
121
  app.use(server.middlewares);
112
122
 
package/src/update.js ADDED
@@ -0,0 +1,50 @@
1
+ import { logger } from '@vcsuite/cli-logger';
2
+ import { DepType, getPackageJson, installDeps } from './packageJsonHelpers.js';
3
+ import { name, promiseExec } from './pluginCliHelper.js';
4
+
5
+ /**
6
+ * Update peer dependencies of a provided packageJson with @vcmap/ui@latest peers
7
+ * @param {Object} pluginPeer - peerDependencies of a plugin
8
+ * @param {string} pluginPath
9
+ * @returns {Promise<void>}
10
+ */
11
+ export async function updatePeerDependencies(pluginPeer, pluginPath) {
12
+ const { stdout, stderr } = await promiseExec('npm view @vcmap/ui --json');
13
+ logger.error(stderr);
14
+ const { name: mapName, peerDependencies: mapPeer } = JSON.parse(stdout);
15
+ const peerDeps = [`${mapName}@latest`]; // @vcmap/ui is a required peer dep and will be updated in any case
16
+ if (pluginPeer) {
17
+ const pluginPeerDeps = Object.keys(pluginPeer)
18
+ .filter(depName => !!mapPeer[depName] && pluginPeer[depName] !== mapPeer[depName])
19
+ .map(depName => `${depName}@${mapPeer[depName]}`);
20
+ peerDeps.push(pluginPeerDeps);
21
+ }
22
+ logger.spin('Updating peer dependencies');
23
+ await installDeps(peerDeps, DepType.PEER, pluginPath);
24
+ logger.stopSpinner();
25
+ logger.success('Updated peer dependencies');
26
+ }
27
+
28
+ /**
29
+ * Updates the @vcmap/plugin-cli
30
+ * @param {string} pluginPath
31
+ * @returns {Promise<void>}
32
+ */
33
+ async function updateCli(pluginPath) {
34
+ logger.spin(`Updating ${name}`);
35
+ await installDeps([`${name}@latest`], DepType.DEV, pluginPath);
36
+ logger.stopSpinner();
37
+ logger.success(`Updated ${name}`);
38
+ }
39
+
40
+ /**
41
+ * Updating peer dependencies to @vmap/ui@latest
42
+ * Updating @vcmap/plugin-cli
43
+ * @returns {Promise<void>}
44
+ */
45
+ export default async function update() {
46
+ const packageJson = await getPackageJson();
47
+ await updatePeerDependencies(packageJson.peerDependencies, process.cwd());
48
+ await updateCli(process.cwd());
49
+ logger.success(`Updated plugin ${packageJson.name}`);
50
+ }
@@ -1,2 +0,0 @@
1
- FROM httpd:2-buster
2
- COPY * /usr/local/apache2/htdocs/
@@ -1,54 +0,0 @@
1
- <template>
2
- <v-sheet class="hello-world">
3
- <v-card class="pa-2 ma-2">
4
- <v-container>
5
- <v-row class="justify-center mb-4">
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
- />
14
- </v-row>
15
- <v-row class="justify-center">
16
- <VcsButton
17
- icon="mdi-times"
18
- @click="closeSelf"
19
- >
20
- {{ $t('helloWorld.close')}}
21
- </VcsButton>
22
- </v-row>
23
- </v-container>
24
- </v-card>
25
- </v-sheet>
26
- </template>
27
-
28
- <style>
29
- .hello-world {
30
- background-color: aqua;
31
- }
32
- </style>
33
- <script>
34
- import { inject } from 'vue';
35
- import { VcsButton, getPluginAssetUrl } from '@vcmap/ui';
36
- import { name } from '../package.json';
37
-
38
- export const windowId = 'hello_world_window_id_plugin-cli';
39
-
40
- export default {
41
- name: 'HelloWorld',
42
- components: { VcsButton },
43
- setup() {
44
- const app = inject('vcsApp');
45
-
46
- return {
47
- closeSelf() {
48
- app.windowManager.remove(windowId);
49
- },
50
- logoUrl: getPluginAssetUrl(app, name, 'plugin-assets/vcs_logo.png'),
51
- };
52
- },
53
- };
54
- </script>
@@ -1,72 +0,0 @@
1
- import { WindowSlot } from '@vcmap/ui';
2
- import { version, name } from '../package.json';
3
- import HelloWorld, { windowId } from './helloWorld.vue';
4
-
5
- /**
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 /)
8
- * @returns {import("@vcmap/ui/src/vcsUiApp").VcsPlugin<T>}
9
- * @template {Object} T
10
- * @template {Object} S
11
- */
12
- export default function(config, baseUrl) {
13
- return {
14
- get name() { return name; },
15
- get version() { return version; },
16
- /**
17
- * @param {import("@vcmap/ui").VcsUiApp} vcsUiApp
18
- * @param {S=} state
19
- * @returns {Promise<void>}
20
- */
21
- initialize: async (vcsUiApp, state) => {
22
- console.log('Called before loading the rest of the current context. Passed in the containing Vcs UI App ');
23
- },
24
- /**
25
- * @param {import("@vcmap/ui").VcsUiApp} vcsUiApp
26
- * @returns {Promise<void>}
27
- */
28
- onVcsAppMounted: async (vcsUiApp) => {
29
- console.log('Called when the root UI component is mounted and managers are ready to accept components');
30
- vcsUiApp.windowManager.add({
31
- id: windowId,
32
- component: HelloWorld,
33
- WindowSlot: WindowSlot.DETACHED,
34
- position: {
35
- left: '40%',
36
- right: '40%',
37
- },
38
- }, name);
39
- },
40
- /**
41
- * @returns {Promise<S>}
42
- */
43
- getState: async () => {
44
- console.log('Called when serializing this plugin instance');
45
- return {};
46
- },
47
- /**
48
- * @returns {Promise<T>}
49
- */
50
- toJSON: async () => {
51
- console.log('Called when serializing this plugin instance');
52
- return {};
53
- },
54
- i18n: {
55
- en: {
56
- helloWorld: {
57
- helloWorld: '@vcmap/plugin-cli - Hello World',
58
- close: 'Close',
59
- },
60
- },
61
- de: {
62
- helloWorld: {
63
- helloWorld: '@vcmap/plugin-cli - Hallo Welt',
64
- close: 'Schließen',
65
- },
66
- },
67
- },
68
- destroy() {
69
- console.log('hook to cleanup');
70
- },
71
- };
72
- };