@vcmap/plugin-cli 4.0.1 → 4.1.0

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
@@ -2,12 +2,42 @@
2
2
 
3
3
  > Part of the [VC Map Project](https://github.com/virtualcitySYSTEMS/map-ui)
4
4
 
5
- > Note: This documentation is for version @vcmap/ui 6.0.0, compatible with the [VC Map](https://github.com/virtualcitySYSTEMS/map-ui).
5
+ > Note: This documentation is for version @vcmap/ui 6, compatible with the [VC Map](https://github.com/virtualcitySYSTEMS/map-ui).
6
6
  >
7
- > [Migration Guide](https://github.com/virtualcitySYSTEMS/map-ui/blob/release-v6.0/MIGRATION_V6.md) for Plugins from @vcmap/ui 5.0.0
7
+ > [Migration Guide](https://github.com/virtualcitySYSTEMS/map-ui/blob/release-v6.0/MIGRATION_V6.md) for Plugins from @vcmap/ui 5
8
8
 
9
9
  The `@vcmap/plugin-cli` helps develop and build plugins for the **VC Map**.
10
10
 
11
+ ### Migration to Eslint9
12
+
13
+ If you are updating a plugin, you should also update to the new eslint config. To
14
+ do so, remove the eslint config and any eslint plugins (or even eslint itself) from
15
+ the devDependencies, then add the eslint config again:
16
+
17
+ ```bash
18
+ npm uninstall @vcsuite/eslint-config eslint
19
+ npm i -D @vcsuite/eslint-config
20
+ ```
21
+
22
+ You should then create a `eslint.config.js` in the root of your project.
23
+ You should then extend the `vue` or `vueTs` config, depending on
24
+ your project using typescript or not. You can then remove any .eslintrc files still left in your
25
+ project, plus the eslintConfig and eslintIgnore from your package.json.
26
+
27
+ ```bash
28
+ echo "import { configs } from '@vcsuite/eslint-config';" > eslint.config.js
29
+ echo "" >> eslint.config.js
30
+ echo "export default [" >> eslint.config.js
31
+ echo " ...configs.vueTs," >> eslint.config.js
32
+ echo " { ignores: ['node_modules/', 'dist/'] }," >> eslint.config.js
33
+ echo "];" >> eslint.config.js
34
+ echo "" >> eslint.config.js
35
+ ```
36
+
37
+ You can then remove any .eslintrc(.js) (including the one in the tests directory) files and any eslint config from your package.json.
38
+ To fix issues in the default vcs interface spec & setup, you can copy paste the template from [this projects assets folder](https://github.com/virtualcitySYSTEMS/map-plugin-cli/tree/main/assets/).
39
+ Be sure to copy the typescript or vanilla folder, depending on your project.
40
+
11
41
  ## Features
12
42
 
13
43
  - Creating basic plugin structure
@@ -189,19 +219,6 @@ This can be helpful, if you want to share specific parameters valid for a specif
189
219
  In order to do so just save a `vcm.config.js` in your plugin's root directory.
190
220
  This file has to return a js object as default export.
191
221
 
192
- Example `vcm.config.js` defining proxy and port:
193
-
194
- ```js
195
- export default {
196
- // server.proxy see https://vitejs.dev/config/server-options.html#server-proxy
197
- proxy: {
198
- // string shorthand: http://localhost:8008/foo -> https://vc.systems/foo
199
- '/foo': 'https://vc.systems',
200
- },
201
- port: 5005,
202
- };
203
- ```
204
-
205
222
  The following parameters are valid:
206
223
 
207
224
  | parameter | type | description |
@@ -213,9 +230,73 @@ The following parameters are valid:
213
230
  | vcm | string | A filename or URL to a VC Map application. Only works for `preview` command! Takes precedence over `appConfig` parameter. |
214
231
  | proxy | Object | A server proxy (see [vitejs.dev](https://vitejs.dev/config/server-options.html#server-proxy)) |
215
232
 
216
- > The `vcm` parameter uses a hosted map application to preview the plugin. The plugin is bundled and added to the application. This parameter is only working for `preview` mode.
233
+ Examples:
234
+
235
+ - defining modules:
236
+ > For the `appConfig` option, map and plugin are bundled to create a preview environment.
237
+ > You can provide modules from absolute or relative URLs or inline configurations.
238
+ > This parameter is working for both `preview` and `serve` mode.
239
+
240
+ ```js
241
+ export default {
242
+ appConfig: {
243
+ modules: [
244
+ "https://www.virtualcitymap.de/config/www.config.json",
245
+ "./node_modules/@vcmap/ui/config/dev.config.json",
246
+ {
247
+ "name": "myCustomConfig",
248
+ "startingViewpointName": "start",
249
+ "viewpoints": [
250
+ {
251
+ "type": "Viewpoint",
252
+ "name": "start",
253
+ "groundPosition": [...],
254
+ "distance": 100
255
+ }
256
+ ],
257
+ "layers": [
258
+ {
259
+ "name": "geojsonClassification",
260
+ "type": "GeoJSONLayer",
261
+ "features": [
262
+ {
263
+ "type": "Feature",
264
+ "geometry": {
265
+ "type": "Polygon",
266
+ "coordinates": [...]
267
+ },
268
+ }
269
+ ],
270
+ "activeOnStartup": true,
271
+ },
272
+ ]
273
+ }
274
+ ]
275
+ }
276
+ }
277
+ ```
278
+
279
+ - referencing a hosted application
280
+ > The `vcm` parameter uses a hosted map application to preview the plugin. The plugin is bundled and added to the application. This parameter is only working for `preview` mode.
217
281
 
218
- > For the `appConfig` option, map and plugin are bundled to create a preview environment. Here only the configuration is loaded from the provided url or object. This parameter is working for both `preview` and `serve` mode.
282
+ ```js
283
+ export default {
284
+ vcm: 'https://www.virtualcitymap.de/app.config.json',
285
+ };
286
+ ```
287
+
288
+ - defining proxy and port:
289
+
290
+ ```js
291
+ export default {
292
+ // server.proxy see https://vitejs.dev/config/server-options.html#server-proxy
293
+ proxy: {
294
+ // string shorthand: http://localhost:8008/foo -> https://vc.systems/foo
295
+ '/foo': 'https://vc.systems',
296
+ },
297
+ port: 5005,
298
+ };
299
+ ```
219
300
 
220
301
  ## About Peer Dependencies
221
302
 
@@ -426,12 +507,25 @@ If a plugin does not provide a config editor, the JsonEditor is always used as f
426
507
  To provide a custom editor, the plugin has to implement a `getConfigEditors` method returning one or more editors.
427
508
  A plugin config editor definition consists of
428
509
 
429
- - component: The vue component providing the ui of the editor. This vue component has to extend the `AbstractConfigEditor.vue`, which can be imported from `@vcmap/ui`. The component has to provide two props: `getConfig` for getting the serialized configuration and `setConfig` to update the changed configuration.
510
+ - component: The vue component providing the ui of the editor. This vue component has to extend the `AbstractConfigEditor.vue`, which can be imported from `@vcmap/ui`.
511
+ The component has to provide two props:
512
+ - `getConfig` for getting the serialized configuration and
513
+ - `setConfig` to update the changed configuration (do not pass proxies or anything by reference to setConfig! See note below for more information)
430
514
  - title: An optional title displayed in the window header of the editor and on action buttons (e.g. tooltip)
431
515
  - collectionName: The collection the item belongs to. Default is `plugins` collection. For a layer config editor you would provide `layers`.
432
516
  - itemName: The item the editor can be used for. Can be a name or className. Default is the plugin's name. For a layer you would provide `MyNewLayer.className`.
433
517
  - infoUrlCallback: An optional function returning an url referencing help or further information regarding the config editor.
434
518
 
519
+ > IMPORTANT NOTE:
520
+ >
521
+ > Make sure you do not pass proxy elements or internals by reference to `setConfig`!
522
+ >
523
+ > - proxies can be removed by using vue's `toRaw` function
524
+ > - `toRaw` is not deep, therefor nested proxy elements like arrays have to be manually removed, e.g. by iterating over the array and calling `toRaw` on each array item
525
+ > - everything passed by reference has to be deeply cloned, e.g. using `structuredClone`
526
+ >
527
+ > Best practice: Use `setConfig(structuredClone(...))` because it will throw, if you pass a proxy
528
+
435
529
  An example of plugin config editor can look like this:
436
530
 
437
531
  ```vue
@@ -462,7 +556,7 @@ An example of plugin config editor can look like this:
462
556
  VcsLabel,
463
557
  VcsTextField,
464
558
  } from '@vcmap/ui';
465
- import { ref } from 'vue';
559
+ import { ref, toRaw } from 'vue';
466
560
  import { getDefaultOptions } from '../defaultOptions.js';
467
561
 
468
562
  export default {
@@ -492,7 +586,8 @@ An example of plugin config editor can look like this:
492
586
  const localConfig = ref({ ...defaultOptions, ...config });
493
587
 
494
588
  const apply = () => {
495
- props.setConfig(localConfig.value);
589
+ // Do not pass proxy elements or internals by reference! See note above example for more information
590
+ props.setConfig(structuredClone(toRaw(localConfig.value)));
496
591
  };
497
592
 
498
593
  return {
@@ -9,6 +9,7 @@ stages:
9
9
  - test
10
10
  - bundle
11
11
  - deploy
12
+ - prepublish
12
13
  - version
13
14
  - publish
14
15
  - deployCluster
@@ -104,7 +105,7 @@ deployStaging:
104
105
  name: gcr.io/kaniko-project/executor:debug
105
106
  entrypoint: ['']
106
107
  script:
107
- - /kaniko/executor --context dist/ --dockerfile build/staging/Dockerfile --destination $CI_REGISTRY_IMAGE/staging:$CI_COMMIT_REF_SLUG
108
+ - /kaniko/executor --context dist/ --dockerfile node_modules/@vcmap/plugin-cli/assets/build/staging/Dockerfile --destination $CI_REGISTRY_IMAGE/staging:$CI_COMMIT_REF_SLUG
108
109
  before_script:
109
110
  - mkdir -p /kaniko/.docker
110
111
  - echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > /kaniko/.docker/config.json
@@ -144,6 +145,24 @@ deployStagingCluster:
144
145
  project: vcsuite/devops/manifests
145
146
  branch: main
146
147
 
148
+ sshDependenciesCheck:
149
+ <<: *after_build_definition
150
+ stage: prepublish
151
+ only:
152
+ variables:
153
+ - $PUBLISH
154
+ refs:
155
+ - /^(main|release-v.*)$/
156
+ script:
157
+ - echo "Checking for SSH dependencies in package.json..."
158
+ - |
159
+ if grep -E "git(\+ssh)?://git@gitlab|ssh://git@gitlab:|gitlab:" package.json; then
160
+ echo "Error: SSH link dependencies found in package.json."
161
+ exit 1
162
+ else
163
+ echo "No SSH link dependencies found."
164
+ fi
165
+
147
166
  version:
148
167
  <<: *after_build_definition
149
168
  stage: version
@@ -1,2 +1,9 @@
1
1
  FROM httpd:2-buster
2
- COPY * /usr/local/apache2/htdocs/
2
+ COPY . /usr/local/apache2/htdocs/
3
+ # allow for proxy
4
+ RUN sed -i '/LoadModule rewrite_module/s/^#//g' /usr/local/apache2/conf/httpd.conf && \
5
+ sed -i '/LoadModule proxy_module/s/^#//g' /usr/local/apache2/conf/httpd.conf && \
6
+ sed -i '/LoadModule proxy_http_module/s/^#//g' /usr/local/apache2/conf/httpd.conf && \
7
+ sed -i '/LoadModule ssl_module/s/^#//g' /usr/local/apache2/conf/httpd.conf && \
8
+ sed -i 's#AllowOverride [Nn]one#AllowOverride All#' /usr/local/apache2/conf/httpd.conf && \
9
+ echo "SSLProxyEngine On" | tee -a /usr/local/apache2/conf/httpd.conf
@@ -0,0 +1,8 @@
1
+ import { configs } from '@vcsuite/eslint-config';
2
+
3
+ export default [
4
+ ...configs.vue,
5
+ {
6
+ ignores: ['node_modules/', 'dist/'],
7
+ },
8
+ ];
@@ -0,0 +1,8 @@
1
+ import { configs } from '@vcsuite/eslint-config';
2
+
3
+ export default [
4
+ ...configs.vueTs,
5
+ {
6
+ ignores: ['node_modules/', 'dist/'],
7
+ },
8
+ ];
package/assets/index.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { VcsPlugin, VcsUiApp, PluginConfigEditor } from '@vcmap/ui';
1
+ import type { VcsPlugin, VcsUiApp, PluginConfigEditor } from '@vcmap/ui';
2
2
  import { name, version, mapVersion } from '../package.json';
3
3
 
4
4
  type PluginConfig = Record<never, never>;
@@ -67,7 +67,7 @@ export default function plugin(
67
67
  /**
68
68
  * components for configuring the plugin and/ or custom items defined by the plugin
69
69
  */
70
- getConfigEditors(): PluginConfigEditor[] {
70
+ getConfigEditors(): PluginConfigEditor<object>[] {
71
71
  return [];
72
72
  },
73
73
  destroy(): void {
@@ -1,4 +1,4 @@
1
- /* eslint-disable import/no-extraneous-dependencies, import/first */
1
+ /* eslint-disable import/first */
2
2
  import { vi } from 'vitest';
3
3
 
4
4
  vi.hoisted(() => {
@@ -9,8 +9,8 @@ function sleep(ms = 0) {
9
9
  });
10
10
  }
11
11
 
12
- window.VcsPluginLoaderFunction = (name, module) => ({
13
- default: () => plugin({ name }, module),
12
+ window.VcsPluginLoaderFunction = () => ({
13
+ default: (config, baseUrl) => plugin(config, baseUrl),
14
14
  });
15
15
 
16
16
  const testPropSymbol = Symbol('testProp');
@@ -34,9 +34,11 @@ describe('VcsPlugin Interface test', () => {
34
34
  expect(pluginInstance).to.have.property('name', packageJSON.name);
35
35
  expect(isValidPackageName(pluginInstance.name)).to.be.true;
36
36
  });
37
+
37
38
  it('should return the plugin version from the package.json', () => {
38
39
  expect(pluginInstance).to.have.property('version', packageJSON.version);
39
40
  });
41
+
40
42
  it('should return the plugin mapVersion from the package.json', () => {
41
43
  expect(pluginInstance).to.have.property(
42
44
  'mapVersion',
@@ -51,6 +53,7 @@ describe('VcsPlugin Interface test', () => {
51
53
  expect(pluginInstance?.i18n).to.be.a('object').with.property('en');
52
54
  }
53
55
  });
56
+
54
57
  it('should use unscoped, camel-case plugin name as namespace for plugin specific i18n entries', () => {
55
58
  if (pluginInstance?.i18n) {
56
59
  expect(pluginInstance.i18n).to.be.a('object');
@@ -74,12 +77,14 @@ describe('VcsPlugin Interface test', () => {
74
77
  .throw;
75
78
  }
76
79
  });
80
+
77
81
  it('may implement onVcsAppMounted', () => {
78
82
  if (pluginInstance?.onVcsAppMounted) {
79
83
  expect(pluginInstance.onVcsAppMounted).to.be.a('function');
80
84
  expect(pluginInstance.onVcsAppMounted(new VcsUiApp())).to.not.throw;
81
85
  }
82
86
  });
87
+
83
88
  it('should implement destroy', () => {
84
89
  if (pluginInstance?.destroy) {
85
90
  expect(pluginInstance.destroy).to.be.a('function');
@@ -93,6 +98,7 @@ describe('VcsPlugin Interface test', () => {
93
98
  expect(pluginInstance.getDefaultOptions()).to.be.a('object');
94
99
  }
95
100
  });
101
+
96
102
  it('may implement toJSON returning the plugin config', () => {
97
103
  if (pluginInstance?.toJSON) {
98
104
  expect(pluginInstance.toJSON()).to.be.a('object');
@@ -0,0 +1,15 @@
1
+ /* global global */
2
+ /* eslint-disable import/first */
3
+ import { vi } from 'vitest';
4
+
5
+ vi.hoisted(() => {
6
+ global.jest = vi;
7
+ });
8
+
9
+ import ResizeObserver from 'resize-observer-polyfill';
10
+
11
+ global.ResizeObserver = ResizeObserver;
12
+
13
+ import 'jest-canvas-mock';
14
+
15
+ window.CESIUM_BASE_URL = '/node_modules/@vcmap-cesium/engine/Build/';
@@ -1,5 +1,6 @@
1
1
  import { describe, it, expect, beforeAll, afterAll } from 'vitest';
2
- import { VcsUiApp, loadPlugin, isValidPackageName, VcsPlugin } from '@vcmap/ui';
2
+ import type { VcsPlugin } from '@vcmap/ui';
3
+ import { VcsUiApp, loadPlugin, isValidPackageName } from '@vcmap/ui';
3
4
  import plugin from '../src/index.js';
4
5
  import packageJSON from '../package.json';
5
6
 
@@ -12,14 +13,11 @@ function sleep(ms = 0): Promise<void> {
12
13
  type TestPluginInstance = VcsPlugin<object, object>;
13
14
 
14
15
  // @ts-expect-error: not defined on global
15
- window.VcsPluginLoaderFunction = (
16
- name: string,
17
- module: string,
18
- ): {
19
- default: () => TestPluginInstance;
16
+ window.VcsPluginLoaderFunction = (): {
17
+ default: (config: object, module: string) => TestPluginInstance;
20
18
  } => ({
21
19
  // @ts-expect-error: interface may not use this
22
- default: () => plugin({ name }, module),
20
+ default: (config, baseUrl) => plugin(config, baseUrl),
23
21
  });
24
22
 
25
23
  const testPropSymbol = Symbol('testProp');
@@ -43,9 +41,11 @@ describe('VcsPlugin Interface test', () => {
43
41
  expect(pluginInstance).to.have.property('name', packageJSON.name);
44
42
  expect(isValidPackageName(pluginInstance.name)).to.be.true;
45
43
  });
44
+
46
45
  it('should return the plugin version from the package.json', () => {
47
46
  expect(pluginInstance).to.have.property('version', packageJSON.version);
48
47
  });
48
+
49
49
  it('should return the plugin mapVersion from the package.json', () => {
50
50
  expect(pluginInstance).to.have.property(
51
51
  'mapVersion',
@@ -57,11 +57,10 @@ describe('VcsPlugin Interface test', () => {
57
57
  describe('internationalization', () => {
58
58
  it('may provide an i18n object and should provide at least en as fallback language', () => {
59
59
  if (pluginInstance?.i18n) {
60
- expect(pluginInstance?.i18n)
61
- .to.be.a('object')
62
- .with.property('en');
60
+ expect(pluginInstance?.i18n).to.be.a('object').with.property('en');
63
61
  }
64
62
  });
63
+
65
64
  it('should use unscoped, camel-case plugin name as namespace for plugin specific i18n entries', () => {
66
65
  if (pluginInstance?.i18n) {
67
66
  expect(pluginInstance.i18n).to.be.a('object');
@@ -85,12 +84,16 @@ describe('VcsPlugin Interface test', () => {
85
84
  .throw;
86
85
  }
87
86
  });
87
+
88
88
  it('may implement onVcsAppMounted', () => {
89
89
  if (pluginInstance?.onVcsAppMounted) {
90
90
  expect(pluginInstance.onVcsAppMounted).to.be.a('function');
91
- expect(pluginInstance.onVcsAppMounted(new VcsUiApp())).to.not.throw;
91
+ expect(() => {
92
+ pluginInstance.onVcsAppMounted(new VcsUiApp());
93
+ }).to.not.throw;
92
94
  }
93
95
  });
96
+
94
97
  it('should implement destroy', () => {
95
98
  if (pluginInstance?.destroy) {
96
99
  expect(pluginInstance.destroy).to.be.a('function');
@@ -104,6 +107,7 @@ describe('VcsPlugin Interface test', () => {
104
107
  expect(pluginInstance.getDefaultOptions()).to.be.a('object');
105
108
  }
106
109
  });
110
+
107
111
  it('may implement toJSON returning the plugin config', () => {
108
112
  if (pluginInstance?.toJSON) {
109
113
  expect(pluginInstance.toJSON()).to.be.a('object');
@@ -145,7 +149,9 @@ describe('VcsPlugin Interface test', () => {
145
149
  });
146
150
 
147
151
  it('should reincarnate the plugin correctly', async () => {
148
- expect(() => app.plugins.remove(pluginInstance2!)).to.not.throw;
152
+ expect(() => {
153
+ app.plugins.remove(pluginInstance2!);
154
+ }).to.not.throw;
149
155
  app.plugins.remove(pluginInstance2!);
150
156
  await sleep(0);
151
157
  expect(app.plugins.getByKey(packageJSON.name)).not.to.have.property(
@@ -9,6 +9,7 @@ stages:
9
9
  - test
10
10
  - bundle
11
11
  - deploy
12
+ - prepublish
12
13
  - version
13
14
  - publish
14
15
  - deployCluster
@@ -102,7 +103,7 @@ deployStaging:
102
103
  name: gcr.io/kaniko-project/executor:debug
103
104
  entrypoint: ['']
104
105
  script:
105
- - /kaniko/executor --context dist/ --dockerfile build/staging/Dockerfile --destination $CI_REGISTRY_IMAGE/staging:$CI_COMMIT_REF_SLUG
106
+ - /kaniko/executor --context dist/ --dockerfile node_modules/@vcmap/plugin-cli/assets/build/staging/Dockerfile --destination $CI_REGISTRY_IMAGE/staging:$CI_COMMIT_REF_SLUG
106
107
  before_script:
107
108
  - mkdir -p /kaniko/.docker
108
109
  - echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > /kaniko/.docker/config.json
@@ -142,6 +143,24 @@ deployStagingCluster:
142
143
  project: vcsuite/devops/manifests
143
144
  branch: main
144
145
 
146
+ sshDependenciesCheck:
147
+ <<: *after_build_definition
148
+ stage: prepublish
149
+ only:
150
+ variables:
151
+ - $PUBLISH
152
+ refs:
153
+ - /^(main|release-v.*)$/
154
+ script:
155
+ - echo "Checking for SSH dependencies in package.json..."
156
+ - |
157
+ if grep -E "git(\+ssh)?://git@gitlab|ssh://git@gitlab:|gitlab:" package.json; then
158
+ echo "Error: SSH link dependencies found in package.json."
159
+ exit 1
160
+ else
161
+ echo "No SSH link dependencies found."
162
+ fi
163
+
145
164
  version:
146
165
  <<: *after_build_definition
147
166
  stage: version
@@ -1,4 +1,3 @@
1
- // eslint-disable-next-line import/no-extraneous-dependencies
2
1
  import { defineConfig } from 'vite';
3
2
  import commonViteConfig from '@vcmap/ui/build/commonViteConfig.js';
4
3
 
package/cli.js CHANGED
@@ -14,6 +14,7 @@ program
14
14
  .summary('create new plugin')
15
15
  .defaultOptions()
16
16
  .option('-t --typescript', 'Create a plugin using typescript')
17
+ .option('-d --default <name>', 'use defaults')
17
18
  .safeAction(create);
18
19
 
19
20
  program
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vcmap/plugin-cli",
3
- "version": "4.0.1",
3
+ "version": "4.1.0",
4
4
  "description": "A CLI to help develop and build plugins for the VC Map",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -45,6 +45,9 @@
45
45
  "@vcmap/ui": "^6.0.0",
46
46
  "vue": "~3.4.38"
47
47
  },
48
+ "overrides": {
49
+ "esbuild": "^0.25.0"
50
+ },
48
51
  "peerDependenciesMeta": {
49
52
  "@vcmap/ui": {
50
53
  "optional": true
@@ -54,34 +57,11 @@
54
57
  }
55
58
  },
56
59
  "devDependencies": {
57
- "@vcsuite/eslint-config": "^3.0.7",
58
- "eslint": "^8.38.0"
59
- },
60
- "eslintIgnore": [
61
- "node_modules",
62
- "assets/tests",
63
- "assets/testsTypescript",
64
- "assets/index.ts"
65
- ],
66
- "eslintConfig": {
67
- "extends": "@vcsuite/eslint-config/node",
68
- "parserOptions": {
69
- "ecmaVersion": 2020
70
- },
71
- "rules": {
72
- "import/no-unresolved": [
73
- 2,
74
- {
75
- "ignore": [
76
- "^@vcmap/ui"
77
- ]
78
- }
79
- ]
80
- }
60
+ "@vcsuite/eslint-config": "^4.0.0"
81
61
  },
82
62
  "prettier": "@vcsuite/eslint-config/prettier.js",
83
63
  "engines": {
84
- "node": ">=18.12.0 || >= 20.0.0",
85
- "npm": ">=9.0.0 || 10.0.0"
64
+ "node": ">= 20.0.0",
65
+ "npm": ">=10.0.0"
86
66
  }
87
67
  }
@@ -9,6 +9,38 @@ import buildModule, { buildMapUI, getDefaultConfig } from './build.js';
9
9
  import setupMapUi from './setupMapUi.js';
10
10
  import { getVcmConfigJs } from './pluginCliHelper.js';
11
11
 
12
+ /**
13
+ * @param {VcmConfigJs} config
14
+ * @returns {string | undefined}
15
+ */
16
+ function getHtaccess(config) {
17
+ let htaccess;
18
+ if (config.htaccess) {
19
+ ({ htaccess } = config);
20
+ } else if (config.proxy) {
21
+ const htaccessLines = Object.keys(config.proxy).map((key) => {
22
+ const value = config.proxy[key];
23
+ let target;
24
+ if (typeof value === 'string') {
25
+ target = value;
26
+ } else {
27
+ ({ target } = value);
28
+ console.log(
29
+ `proxy settings for ${key} may be more complex, simply using rewrite to target: ${target}`,
30
+ );
31
+ }
32
+ return `RewriteRule ^${key.replace(/^\^/, '')}(.*) ${target}$1 [P,L]`;
33
+ });
34
+
35
+ if (htaccessLines.length > 0) {
36
+ htaccessLines.unshift('RewriteEngine On');
37
+ htaccess = htaccessLines.join('\n');
38
+ }
39
+ }
40
+
41
+ return htaccess;
42
+ }
43
+
12
44
  /**
13
45
  * creates production preview application in the dist folder based on the @vcmap/ui default configuration.
14
46
  * @returns {Promise<void>}
@@ -70,6 +102,12 @@ export default async function buildStagingApp() {
70
102
  if (pluginConfig) {
71
103
  pluginConfig.entry = `plugins/${pluginName}/index.js`;
72
104
  }
105
+ const htaccess = getHtaccess(vcmConfigJs);
106
+ if (htaccess) {
107
+ await writeFile(path.join(distPath, '.htaccess'), htaccess);
108
+ logger.log('built .htaccess');
109
+ }
110
+
73
111
  await writeFile(
74
112
  path.join(distPath, 'app.config.json'),
75
113
  JSON.stringify(appConfig, null, 2),
package/src/create.js CHANGED
@@ -19,12 +19,12 @@ import {
19
19
  * @typedef {Object} PluginTemplateOptions
20
20
  * @property {string} name
21
21
  * @property {string} version
22
- * @property {string} description
22
+ * @property {string} [description]
23
23
  * @property {Array<Object>} scripts
24
- * @property {string} author
25
- * @property {string} repository
24
+ * @property {string} [author]
25
+ * @property {string} [repository]
26
26
  * @property {string} license
27
- * @property {string} template
27
+ * @property {string} [template]
28
28
  * @property {Array<string>} peerDeps
29
29
  * @property {boolean} gitlabCi
30
30
  * @property {boolean} typescript
@@ -160,14 +160,14 @@ async function createPluginTemplate(options, pluginPath) {
160
160
  if (installEsLint) {
161
161
  packageJson.eslintIgnore = ['node_modules', 'dist', 'plugin-assets'];
162
162
  if (!options.typescript) {
163
- packageJson.eslintConfig = {
164
- root: true,
165
- extends: '@vcsuite/eslint-config/vue',
166
- };
163
+ await fs.promises.copyFile(
164
+ path.join(getDirname(), '..', 'assets', 'eslintConfig.js'),
165
+ path.join(pluginPath, 'eslint.config.js'),
166
+ );
167
167
  } else {
168
168
  await fs.promises.copyFile(
169
- path.join(getDirname(), '..', 'assets', 'eslintrc.cjs'),
170
- path.join(pluginPath, '.eslintrc.cjs'),
169
+ path.join(getDirname(), '..', 'assets', 'eslintConfigTs.js'),
170
+ path.join(pluginPath, 'eslint.config.js'),
171
171
  );
172
172
  }
173
173
  packageJson.prettier = '@vcsuite/eslint-config/prettier.js';
@@ -178,13 +178,12 @@ async function createPluginTemplate(options, pluginPath) {
178
178
  JSON.stringify(packageJson, null, 2),
179
179
  );
180
180
 
181
- const configJson = {
182
- name: options.name,
183
- };
184
-
185
181
  await fs.promises.writeFile(
186
182
  path.join(pluginPath, 'config.json'),
187
- JSON.stringify(configJson, null, 2),
183
+ `{
184
+ "name": "${options.name}"
185
+ }
186
+ `,
188
187
  );
189
188
 
190
189
  if (options.template) {
@@ -205,25 +204,14 @@ async function createPluginTemplate(options, pluginPath) {
205
204
 
206
205
  if (installVitest) {
207
206
  logger.debug('setting up test environment');
208
- await fs.promises.cp(
209
- path.join(getDirname(), '..', 'assets', 'tests'),
210
- path.join(pluginPath, 'tests'),
211
- { recursive: true },
212
- );
213
- if (options.typescript) {
214
- await fs.promises.rm(
215
- path.join(pluginPath, 'tests', 'vcsPluginInterface.spec.js'),
216
- );
217
- await fs.promises.cp(
218
- path.join(getDirname(), '..', 'assets', 'testsTypescript'),
219
- path.join(pluginPath, 'tests'),
220
- { recursive: true },
221
- );
222
- await fs.promises.copyFile(
223
- path.join(getDirname(), '..', 'assets', 'eslintrcTests.cjs'),
224
- path.join(pluginPath, '.eslintrc.cjs'),
225
- );
226
- }
207
+ const testSrcPath = options.typescript
208
+ ? path.join(getDirname(), '..', 'assets', 'testsTypescript')
209
+ : path.join(getDirname(), '..', 'assets', 'tests');
210
+
211
+ await fs.promises.cp(testSrcPath, path.join(pluginPath, 'tests'), {
212
+ recursive: true,
213
+ });
214
+
227
215
  await fs.promises.copyFile(
228
216
  path.join(getDirname(), '..', 'assets', 'vitest.config.js'),
229
217
  path.join(pluginPath, 'vitest.config.js'),
@@ -291,14 +279,14 @@ async function createPluginTemplate(options, pluginPath) {
291
279
  async function createPlugin(options) {
292
280
  if (!options.name) {
293
281
  logger.error('please provide a plugin name as input parameter');
294
- process.exit(1);
282
+ throw new Error('no plugin name provided');
295
283
  }
296
284
  logger.debug(`creating new plugin: ${options.name}`);
297
285
 
298
286
  const pluginPath = path.join(process.cwd(), options.name);
299
287
  if (fs.existsSync(pluginPath)) {
300
288
  logger.error('plugin with the provided name already exists');
301
- process.exit(1);
289
+ throw new Error('path already exists');
302
290
  }
303
291
 
304
292
  await fs.promises.mkdir(pluginPath);
@@ -314,14 +302,17 @@ async function createPlugin(options) {
314
302
  path.join(pluginPath, 'README.md'),
315
303
  [
316
304
  `# ${options.name}`,
305
+ '',
317
306
  '> Part of the [VC Map Project](https://github.com/virtualcitySYSTEMS/map-ui)',
307
+ '',
318
308
  'describe your plugin',
309
+ '',
319
310
  ].join('\n'),
320
311
  );
321
312
 
322
313
  const writeChangesPromise = fs.promises.writeFile(
323
314
  path.join(pluginPath, 'CHANGELOG.md'),
324
- `# v${options.version}\nDocument features and fixes`,
315
+ `# v${options.version}\n\nDocument features and fixes\n`,
325
316
  );
326
317
 
327
318
  const copyGitIgnorePromise = fs.promises.copyFile(
@@ -351,11 +342,6 @@ async function createPlugin(options) {
351
342
  path.join(getDirname(), '..', 'assets', gitlabFile),
352
343
  path.join(pluginPath, '.gitlab-ci.yml'),
353
344
  );
354
- await fs.promises.cp(
355
- path.join(getDirname(), '..', 'assets', 'build'),
356
- path.join(pluginPath, 'build'),
357
- { recursive: true },
358
- );
359
345
  }
360
346
 
361
347
  if (options.typescript) {
@@ -364,8 +350,8 @@ async function createPlugin(options) {
364
350
  path.join(pluginPath, 'tsconfig.json'),
365
351
  );
366
352
  await fs.promises.copyFile(
367
- path.join(getDirname(), '..', 'assets', 'eslintrc.cjs'),
368
- path.join(pluginPath, '.eslintrc.cjs'),
353
+ path.join(getDirname(), '..', 'assets', 'eslintConfigTs.js'),
354
+ path.join(pluginPath, 'eslint.config.js'),
369
355
  );
370
356
  }
371
357
 
@@ -375,9 +361,10 @@ async function createPlugin(options) {
375
361
  }
376
362
 
377
363
  /**
364
+ * @param {{ default?: string }} cliOptions
378
365
  * @returns {Promise<void>}
379
366
  */
380
- export default async function create() {
367
+ export default async function create(cliOptions) {
381
368
  const templateChoices = [
382
369
  { title: 'no template (basic structure)', value: null },
383
370
  { title: 'hello-world', value: '@vcmap-show-case/hello-world' },
@@ -521,15 +508,29 @@ export default async function create() {
521
508
  },
522
509
  ];
523
510
 
524
- const answers = await prompts(questions, {
525
- onCancel() {
526
- process.exit(0);
527
- },
528
- });
511
+ if (cliOptions.default) {
512
+ logger.info(`creating default plugin ${cliOptions.default}`);
513
+ await createPlugin({
514
+ name: cliOptions.default,
515
+ version: '1.0.0',
516
+ scripts: scriptChoices.filter((s) => s.selected).map((s) => s.value),
517
+ license: LicenseType.MIT,
518
+ peerDeps: [],
519
+ gitlabCi: true,
520
+ typescript: true,
521
+ });
522
+ } else {
523
+ const answers = await prompts(questions, {
524
+ onCancel() {
525
+ // eslint-disable-next-line n/no-process-exit
526
+ process.exit(0);
527
+ },
528
+ });
529
529
 
530
- if (answers.template && answers.typescript) {
531
- answers.typescript = !!answers.keepTs;
532
- }
530
+ if (answers.template && answers.typescript) {
531
+ answers.typescript = !!answers.keepTs;
532
+ }
533
533
 
534
- await createPlugin(answers);
534
+ await createPlugin(answers);
535
+ }
535
536
  }
@@ -48,6 +48,7 @@ Command.prototype.safeAction = function safeAction(action) {
48
48
  logger.error(e);
49
49
  }
50
50
  logger.stopSpinner();
51
+ // eslint-disable-next-line n/no-process-exit
51
52
  process.exit(1);
52
53
  }
53
54
  });
@@ -244,12 +244,18 @@ export function addAppConfigRoute(
244
244
  production,
245
245
  ) {
246
246
  app.get('/app.config.json', (req, res) => {
247
- getAppConfigJson(appConfig, auth, production, configFile).then((config) => {
248
- const stringConfig = JSON.stringify(config, null, 2);
249
- res.setHeader('Content-Type', 'application/json');
250
- res.write(stringConfig);
251
- res.end();
252
- });
247
+ getAppConfigJson(appConfig, auth, production, configFile)
248
+ .then((config) => {
249
+ const stringConfig = JSON.stringify(config, null, 2);
250
+ res.setHeader('Content-Type', 'application/json');
251
+ res.write(stringConfig);
252
+ res.end();
253
+ })
254
+ .catch((e) => {
255
+ logger.error(e);
256
+ res.statusCode = 500;
257
+ res.end();
258
+ });
253
259
  });
254
260
  }
255
261
 
package/src/licenses.js CHANGED
@@ -7,19 +7,18 @@ import path from 'path';
7
7
  * @returns {string}
8
8
  */
9
9
  function mit(user, year) {
10
- return `
11
- Copyright ${year} ${user}
10
+ return `Copyright ${year} ${user}
12
11
 
13
- Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
12
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
14
13
  documentation files (the "Software"), to deal in the Software without restriction, including without limitation
15
- the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
14
+ the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
16
15
  and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
17
16
 
18
17
  The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
19
18
 
20
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
21
- THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
22
- OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
19
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
20
+ THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
21
+ OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
23
22
  TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24
23
  `;
25
24
  }
@@ -30,8 +29,7 @@ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR TH
30
29
  * @returns {string}
31
30
  */
32
31
  function apache(user, year) {
33
- return `
34
- Copyright ${year} ${user}
32
+ return `Copyright ${year} ${user}
35
33
 
36
34
  Licensed under the Apache License, Version 2.0 (the "License");
37
35
  you may not use this file except in compliance with the License.
@@ -53,8 +51,7 @@ limitations under the License.
53
51
  * @returns {string}
54
52
  */
55
53
  function gpl3(user, year) {
56
- return `
57
- Copyright (C) ${year} ${user}
54
+ return `Copyright (C) ${year} ${user}
58
55
 
59
56
  This program is free software: you can redistribute it and/or modify
60
57
  it under the terms of the GNU General Public License as published by
@@ -77,8 +74,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
77
74
  * @returns {string}
78
75
  */
79
76
  function isc(user, year) {
80
- return `
81
- Copyright (c) ${year}, ${user}
77
+ return `Copyright (c) ${year}, ${user}
82
78
 
83
79
  Permission to use, copy, modify, and/or distribute this software for any
84
80
  purpose with or without fee is hereby granted, provided that the above
package/src/pack.js CHANGED
@@ -80,12 +80,12 @@ async function zip(name) {
80
80
  const read = fs.createReadStream(fileName);
81
81
  const write = fs.createWriteStream(`${fileName}.gz`);
82
82
 
83
- await new Promise((res, rej) => {
83
+ await new Promise((resolve, reject) => {
84
84
  pipeline([read, createGzip(), write], (err) => {
85
85
  if (err) {
86
- rej(err);
86
+ reject(err);
87
87
  } else {
88
- res();
88
+ resolve();
89
89
  }
90
90
  });
91
91
  });
@@ -28,7 +28,8 @@ export const promiseExec = util.promisify(childProcess.exec);
28
28
 
29
29
  /**
30
30
  * @typedef {PreviewOptions} VcmConfigJs
31
- * @property {Object} proxy - see https://vitejs.dev/config/server-options.html#server-proxy
31
+ * @property {Object} [proxy] - see https://vitejs.dev/config/server-options.html#server-proxy. when building a staging app, we try to deduce an .htaccess from it.
32
+ * @property {string} [htaccess] - a string representing an .htaccess file content for use in staging apps.
32
33
  */
33
34
 
34
35
  /**
@@ -1,25 +0,0 @@
1
- // eslint-disable-next-line import/no-extraneous-dependencies
2
- // const sharedLib = require('eslint-config-airbnb-typescript/lib/shared.js');
3
-
4
- module.exports = {
5
- root: true,
6
- extends: ['@vcsuite/eslint-config/vue-ts'],
7
- env: {
8
- node: true,
9
- },
10
- rules: {
11
- 'no-restricted-syntax': 'off',
12
- },
13
- overrides: [
14
- {
15
- files: ['*.ts', '*.vue'],
16
- parserOptions: {
17
- project: ['./tsconfig.json'],
18
- },
19
- rules: {
20
- '@typescript-eslint/no-non-null-assertion': 'off',
21
- },
22
- },
23
- ],
24
- ignorePatterns: ['dist/', 'node_modules/'],
25
- };
@@ -1,25 +0,0 @@
1
- // eslint-disable-next-line import/no-extraneous-dependencies
2
- // const sharedLib = require('eslint-config-airbnb-typescript/lib/shared.js');
3
-
4
- module.exports = {
5
- root: true,
6
- extends: ['@vcsuite/eslint-config/vue-ts'],
7
- env: {
8
- node: true,
9
- },
10
- rules: {
11
- 'no-restricted-syntax': 'off',
12
- },
13
- overrides: [
14
- {
15
- files: ['*.ts', '*.vue'],
16
- parserOptions: {
17
- project: ['./tsconfig.json', './tests/tsconfig.json'],
18
- },
19
- rules: {
20
- '@typescript-eslint/no-non-null-assertion': 'off',
21
- },
22
- },
23
- ],
24
- ignorePatterns: ['dist/', 'node_modules/'],
25
- };
@@ -1,6 +0,0 @@
1
- {
2
- "extends": ["@vcsuite/eslint-config/mocha"],
3
- "rules": {
4
- "import/extensions": ["error", "always"]
5
- }
6
- }