@vcmap/plugin-cli 2.0.11 → 2.1.1
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 +70 -14
- package/assets/{helloWorld/.gitlab-ci.yml → .gitlab-ci.yml} +10 -0
- package/assets/gitignore +30 -0
- package/assets/index.html +29 -33
- package/assets/index.js +62 -0
- package/cli.js +7 -9
- package/index.js +1 -0
- package/package.json +4 -3
- package/src/buildStagingApp.js +12 -1
- package/src/create.js +198 -87
- package/src/hostingHelpers.js +9 -18
- package/src/packageJsonHelpers.js +34 -1
- package/src/pluginCliHelper.js +41 -0
- package/src/preview.js +13 -9
- package/src/serve.js +18 -8
- package/src/update.js +50 -0
- package/assets/helloWorld/build/staging/Dockerfile +0 -2
- package/assets/helloWorld/plugin-assets/vcs_logo.png +0 -0
- package/assets/helloWorld/src/helloWorld.vue +0 -54
- package/assets/helloWorld/src/index.js +0 -72
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 `
|
|
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
|
|
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](
|
|
50
|
-
Be sure to check out the [peer dependecy section](#
|
|
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
|
|
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
|
|
132
|
-
a certain amount of type safety (using the @vcsuite/check parameter
|
|
133
|
-
b) reduces the amount of traffic required to load an application and
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
package/assets/gitignore
ADDED
|
@@ -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
|
-
|
|
3
|
+
<head>
|
|
4
4
|
<meta charset="utf-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
<
|
|
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
|
-
|
|
22
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
61
|
+
@keyframes loader {
|
|
66
62
|
0% {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
63
|
+
-webkit-transform: rotate(0deg);
|
|
64
|
+
-ms-transform: rotate(0deg);
|
|
65
|
+
transform: rotate(0deg);
|
|
70
66
|
}
|
|
71
67
|
|
|
72
68
|
100% {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
69
|
+
-webkit-transform: rotate(360deg);
|
|
70
|
+
-ms-transform: rotate(360deg);
|
|
71
|
+
transform: rotate(360deg);
|
|
76
72
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
73
|
+
}
|
|
74
|
+
</style>
|
|
75
|
+
<script type="module" src="./node_modules/@vcmap/ui/start.js"></script>
|
|
76
|
+
</body>
|
|
81
77
|
</html>
|
package/assets/index.js
ADDED
|
@@ -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/
|
|
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.
|
|
3
|
+
"version": "2.1.1",
|
|
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.
|
|
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.
|
|
44
|
+
"vue": "~2.7.14"
|
|
44
45
|
},
|
|
45
46
|
"peerDependenciesMeta": {
|
|
46
47
|
"@vcmap/ui": {
|
package/src/buildStagingApp.js
CHANGED
|
@@ -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
|
|
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 {
|
|
10
|
-
|
|
11
|
-
|
|
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
|
-
* @
|
|
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
|
-
* @
|
|
29
|
+
* @param {string[]} filter - files or dirs to be extracted
|
|
40
30
|
*/
|
|
41
|
-
async function
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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
|
-
|
|
49
|
-
|
|
50
|
-
logger.
|
|
51
|
-
logger.
|
|
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
|
-
*
|
|
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
|
|
60
|
-
|
|
61
|
-
|
|
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
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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
|
-
|
|
164
|
-
|
|
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
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
await
|
|
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.
|
|
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
|
|
404
|
+
await createPlugin(answers);
|
|
294
405
|
}
|
package/src/hostingHelpers.js
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
import {
|
|
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}
|
|
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
|
-
|
|
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,
|
|
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:
|
|
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
|
-
|
|
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(
|
|
87
|
+
const server = await createServer(await getServerOptions(mergedOptions.vcm, mergedOptions.https));
|
|
84
88
|
|
|
85
|
-
addMapConfigRoute(app,
|
|
86
|
-
addIndexRoute(app, server, true,
|
|
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 (!
|
|
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,
|
|
105
|
+
await addConfigRoute(app, mergedOptions.auth, mergedOptions.config, true);
|
|
102
106
|
}
|
|
103
107
|
|
|
104
108
|
app.use(server.middlewares);
|
|
105
109
|
|
|
106
|
-
const port =
|
|
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] -
|
|
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 =
|
|
76
|
+
const port = mergedOptions.port || 8008;
|
|
67
77
|
|
|
68
78
|
logger.info('Starting development server...');
|
|
69
|
-
const proxy = await getProxy(
|
|
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:
|
|
94
|
-
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,
|
|
116
|
+
addMapConfigRoute(app, mergedOptions.mapConfig, mergedOptions.auth, mergedOptions.config);
|
|
107
117
|
addIndexRoute(app, server);
|
|
108
118
|
addPluginAssets(app, 'src');
|
|
109
|
-
await addConfigRoute(app,
|
|
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
|
+
}
|
|
Binary file
|
|
@@ -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
|
-
};
|