@vcmap/plugin-cli 2.1.17 → 3.0.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
@@ -18,7 +18,7 @@ The `@vcmap/plugin-cli` helps develop and build plugins for the **VC Map**.
18
18
 
19
19
  ## Prerequisite
20
20
 
21
- You need [nodejs](https://nodejs.org/en/) 16 and npm installed on your system
21
+ You need [nodejs](https://nodejs.org/en/) 18 and npm installed on your system
22
22
  to use this tool.
23
23
 
24
24
  ## Installation
@@ -51,6 +51,33 @@ All commands have (optional) cli options. Run `vcmplugin --help` or `vcmplugin h
51
51
  For `serve` and `preview` you can alternatively define a `vcs.config.js` in your plugin's root directory.
52
52
  For more information see [here](#vcm-config-js).
53
53
 
54
+ ## Folder structure
55
+
56
+ As of v3, all plugins must follow the same rudimentary folder structure, as depicted below:
57
+
58
+ ```
59
+ -| src/
60
+ -| index.js
61
+ -| package.json
62
+ -| README.md
63
+ ```
64
+
65
+ And for TS based plugins:
66
+
67
+ ```
68
+ -| src/
69
+ -| index.ts
70
+ -| package.json
71
+ -| README.md
72
+ -| tsconfig.json
73
+ ```
74
+
75
+ It is important to, not that the entry point for _building_ the plugin (and
76
+ the file which exports the default export for the plugin interface) **MUST** be
77
+ located at `./src/index.js` or `./src/index.ts` respectively. If you have created
78
+ your plugin using any version of the `@vcmap/plugins-cli`, this will already be
79
+ the case.
80
+
54
81
  ### 1. Creating a new plugin
55
82
 
56
83
  To create a new plugin template, run the following:
@@ -62,7 +89,7 @@ vcmplugin create
62
89
  This will open a command prompt helping you to create the basic [structure of a plugin](#vc-map-plugins).
63
90
  Be sure to check out the [peer dependecy section](#about-peer-dependencies) as well.
64
91
 
65
- 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.
92
+ Optionally, in the create-prompt you can choose an existing plugin [@vcmap/hello-world](https://www.npmjs.com/package/@vcmap/hello-world) as a template.
66
93
 
67
94
  ### 2. Serving a plugin for development
68
95
 
@@ -173,15 +200,18 @@ export default {
173
200
 
174
201
  The following parameters are valid:
175
202
 
176
- | parameter | type | description |
177
- | --------- | ------------------ | --------------------------------------------------------------------------------------------- |
178
- | config | string|Object | an optional configObject or fileName to use for configuring the plugin |
179
- | auth | string | potential auth string to download assets (index.html, config) with |
180
- | port | number | optional alternative port (default 8008) |
181
- | https | boolean | whether to use http (default) or https |
182
- | appConfig | string|Object | an optional configObject resp. fileName or URL to an app config (for `serve` command) |
183
- | vcm | string | a filename or URL to a map (for `preview` command) |
184
- | proxy | Object | a server proxy (see [vitejs.dev](https://vitejs.dev/config/server-options.html#server-proxy)) |
203
+ | parameter | type | description |
204
+ | --------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------- |
205
+ | config | string|Object | An optional configObject or fileName to use for configuring the plugin |
206
+ | auth | string | Potential auth string to download assets (index.html, config) with |
207
+ | port | number | Optional alternative port (default 8008) |
208
+ | appConfig | string|Object | An optional configObject resp. fileName or URL to an app config |
209
+ | vcm | string | A filename or URL to a VC Map application. Only works for `preview` command! Takes precedence over `appConfig` parameter. |
210
+ | proxy | Object | A server proxy (see [vitejs.dev](https://vitejs.dev/config/server-options.html#server-proxy)) |
211
+
212
+ > 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.
213
+
214
+ > 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.
185
215
 
186
216
  ## About Peer Dependencies
187
217
 
@@ -229,6 +259,24 @@ should be rewritten to:
229
259
  import { Cartesian3 } from '@vcmap-cesium/engine';
230
260
  ```
231
261
 
262
+ ### Overwriting Peer Dependencies
263
+
264
+ If you want to work with a release candidate or a specific branch of @vcmap/core or @vcmap/ui you need to define [overrides](https://docs.npmjs.com/cli/v8/configuring-npm/package-json#overrides) within your plugin's `package.json`.
265
+ This will replace the package(s) in your dependency tree with the corresponding version.
266
+
267
+ ```json
268
+ {
269
+ "peerDependencies": {
270
+ "@vcmap/core": "5.1.0-rc.3",
271
+ "@vcmap/ui": "5.1.0-rc.3"
272
+ },
273
+ "overrides": {
274
+ "@vcmap/core": "5.1.0-rc.3",
275
+ "@vcmap/ui": "5.1.0-rc.3"
276
+ }
277
+ }
278
+ ```
279
+
232
280
  ### What about openlayers?
233
281
 
234
282
  openlayers provides a special case, since its modules do not provide a _flat_ namespace.
@@ -0,0 +1,25 @@
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
+ };
@@ -0,0 +1,78 @@
1
+ import { VcsPlugin, VcsUiApp, PluginConfigEditor } from '@vcmap/ui';
2
+ import { name, version, mapVersion } from '../package.json';
3
+
4
+ type PluginConfig = Record<never, never>;
5
+ type PluginState = Record<never, never>;
6
+
7
+ type MyPlugin = VcsPlugin<PluginConfig, PluginState>;
8
+
9
+ export default function plugin(
10
+ config: PluginConfig,
11
+ baseUrl: string,
12
+ ): MyPlugin {
13
+ // eslint-disable-next-line no-console
14
+ console.log(config, baseUrl);
15
+ return {
16
+ get name(): string {
17
+ return name;
18
+ },
19
+ get version(): string {
20
+ return version;
21
+ },
22
+ get mapVersion(): string {
23
+ return mapVersion;
24
+ },
25
+ initialize(vcsUiApp: VcsUiApp, state?: PluginState): Promise<void> {
26
+ // eslint-disable-next-line no-console
27
+ console.log(
28
+ 'Called before loading the rest of the current context. Passed in the containing Vcs UI App ',
29
+ vcsUiApp,
30
+ state,
31
+ );
32
+ return Promise.resolve();
33
+ },
34
+ onVcsAppMounted(vcsUiApp: VcsUiApp): void {
35
+ // eslint-disable-next-line no-console
36
+ console.log(
37
+ 'Called when the root UI component is mounted and managers are ready to accept components',
38
+ vcsUiApp,
39
+ );
40
+ },
41
+ /**
42
+ * should return all default values of the configuration
43
+ */
44
+ getDefaultOptions(): PluginConfig {
45
+ return {};
46
+ },
47
+ /**
48
+ * should return the plugin's serialization excluding all default values
49
+ */
50
+ toJSON(): PluginConfig {
51
+ // eslint-disable-next-line no-console
52
+ console.log('Called when serializing this plugin instance');
53
+ return {};
54
+ },
55
+ /**
56
+ * should return the plugins state
57
+ * @param {boolean} forUrl
58
+ * @returns {PluginState}
59
+ */
60
+ getState(forUrl?: boolean): PluginState {
61
+ // eslint-disable-next-line no-console
62
+ console.log('Called when collecting state, e.g. for create link', forUrl);
63
+ return {
64
+ prop: '*',
65
+ };
66
+ },
67
+ /**
68
+ * components for configuring the plugin and/ or custom items defined by the plugin
69
+ */
70
+ getConfigEditors(): PluginConfigEditor[] {
71
+ return [];
72
+ },
73
+ destroy(): void {
74
+ // eslint-disable-next-line no-console
75
+ console.log('hook to cleanup');
76
+ },
77
+ };
78
+ }
@@ -0,0 +1,179 @@
1
+ default:
2
+ image: gitlab.virtualcitysystems.de:5050/vcsuite/devops/gitlabrunner/node:18-bullseye
3
+
4
+ variables:
5
+ GIT_CLONE_PATH: $CI_BUILDS_DIR/$CI_PROJECT_PATH_SLUG/$CI_COMMIT_REF_SLUG
6
+
7
+ stages:
8
+ - build
9
+ - test
10
+ - bundle
11
+ - deploy
12
+ - version
13
+ - publish
14
+ - deployCluster
15
+
16
+ .template: &job_definition
17
+ only:
18
+ - /^(feature-.*|hotfix-.*|main|release-.*)$/
19
+ tags:
20
+ - linux-2.0
21
+
22
+ build:
23
+ <<: *job_definition
24
+ script:
25
+ - npm ci
26
+ - npm run ensure-types
27
+ before_script:
28
+ - mkdir -p ~/.ssh
29
+ - chmod 700 ~/.ssh
30
+ - echo "$SSH_RUNNER_KEY" | tr -d '\r' > ~/.ssh/id_rsa
31
+ - chmod 600 ~/.ssh/id_rsa
32
+ - ssh-keyscan gitlab.virtualcitysystems.de >> ~/.ssh/known_hosts
33
+ - chmod 644 ~/.ssh/known_hosts
34
+ - git config user.name "Gitlab Runner"
35
+ - git config user.email "gitlab-runner@vc.systems"
36
+ stage: build
37
+
38
+ .after_build_template: &after_build_definition
39
+ <<: *job_definition
40
+ variables:
41
+ GIT_STRATEGY: none
42
+
43
+ .staging_build_template: &staging_build_template
44
+ <<: *after_build_definition
45
+ except:
46
+ variables:
47
+ - $PUBLISH
48
+
49
+ lint:
50
+ <<: *after_build_definition
51
+ stage: test
52
+ script:
53
+ - npm run lint
54
+
55
+ type-check:
56
+ <<: *after_build_definition
57
+ stage: test
58
+ script:
59
+ - npm run type-check
60
+
61
+ test:
62
+ <<: *after_build_definition
63
+ stage: test
64
+ script:
65
+ - npm run coverage -- --reporter junit --outputFile test-report.xml
66
+ coverage: '/^Statements\s*:\s*([^%]+)/'
67
+ artifacts:
68
+ reports:
69
+ junit: test-report.xml
70
+
71
+ audit:
72
+ <<: *after_build_definition
73
+ stage: test
74
+ script:
75
+ - npm audit --production --audit-level=low
76
+
77
+ buildPreview:
78
+ <<: *staging_build_template
79
+ stage: bundle
80
+ script:
81
+ - npm run buildStagingApp
82
+
83
+ bundle:
84
+ <<: *after_build_definition
85
+ stage: bundle
86
+ only:
87
+ variables:
88
+ - $PUBLISH
89
+ refs:
90
+ - /^(main|release-v.*)$/
91
+ script:
92
+ - npm run build
93
+
94
+ deployStaging:
95
+ <<: *staging_build_template
96
+ stage: deploy
97
+ environment:
98
+ name: staging/$CI_COMMIT_REF_SLUG
99
+ url: http://$CI_PROJECT_PATH_SLUG-$CI_COMMIT_REF_SLUG.stagingcluster.intern.virtualcitysystems.de
100
+ on_stop: stopEnvironment
101
+ image:
102
+ name: gcr.io/kaniko-project/executor:debug
103
+ entrypoint: ['']
104
+ script:
105
+ - /kaniko/executor --context dist/ --dockerfile build/staging/Dockerfile --destination $CI_REGISTRY_IMAGE/staging:$CI_COMMIT_REF_SLUG
106
+ before_script:
107
+ - mkdir -p /kaniko/.docker
108
+ - echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > /kaniko/.docker/config.json
109
+
110
+ stopEnvironment:
111
+ stage: deploy
112
+ variables:
113
+ GIT_STRATEGY: none
114
+ image:
115
+ name: bitnami/kubectl:latest
116
+ entrypoint: ['']
117
+ tags:
118
+ - linux-2.0
119
+ script:
120
+ - echo "Stop environment staging/$CI_COMMIT_REF_NAME"
121
+ - echo "Delete namespace on k9s $CI_PROJECT_PATH_SLUG-$CI_COMMIT_REF_SLUG"
122
+ - kubectl config use-context vcsuite/cluster-management:agent
123
+ - kubectl delete namespace $CI_PROJECT_PATH_SLUG-$CI_COMMIT_REF_SLUG
124
+ when: manual
125
+ environment:
126
+ name: staging/$CI_COMMIT_REF_SLUG
127
+ action: stop
128
+
129
+ deployStagingCluster:
130
+ stage: deployCluster
131
+ except:
132
+ variables:
133
+ - $PUBLISH
134
+ inherit:
135
+ variables: false
136
+ variables:
137
+ STAGE_BRANCH: $CI_COMMIT_REF_SLUG
138
+ STAGE_PROJECT_NAME: $CI_PROJECT_PATH_SLUG
139
+ STAGE_REGISTRY_IMAGE: $CI_REGISTRY_IMAGE
140
+ STAGE_NAMESPACE: $CI_PROJECT_PATH_SLUG-$CI_COMMIT_REF_SLUG
141
+ trigger:
142
+ project: vcsuite/devops/manifests
143
+ branch: main
144
+
145
+ version:
146
+ <<: *after_build_definition
147
+ stage: version
148
+ only:
149
+ variables:
150
+ - $PUBLISH
151
+ refs:
152
+ - /^(main|release-v.*)$/
153
+ script:
154
+ - npm version patch -m "%s [skip-ci]"
155
+ - TAG=`git describe --abbrev=0`
156
+ - echo git push git@gitlab:vcsuite/"$CI_PROJECT_PATH".git
157
+ - git push git@gitlab:"$CI_PROJECT_PATH".git $TAG
158
+ - git push git@gitlab:"$CI_PROJECT_PATH".git HEAD:$CI_COMMIT_REF_NAME
159
+ before_script:
160
+ - mkdir -p ~/.ssh
161
+ - chmod 700 ~/.ssh
162
+ - echo "$SSH_RUNNER_KEY" | tr -d '\r' > ~/.ssh/id_rsa
163
+ - chmod 600 ~/.ssh/id_rsa
164
+ - ssh-keyscan gitlab >> ~/.ssh/known_hosts
165
+ - chmod 644 ~/.ssh/known_hosts
166
+ - git config user.name "Gitlab Runner"
167
+ - git config user.email "gitlab-runner@vc.systems"
168
+
169
+ publish:
170
+ <<: *after_build_definition
171
+ stage: publish
172
+ only:
173
+ refs:
174
+ - /^(main|release-v.*)$/
175
+ variables:
176
+ - $PUBLISH
177
+ script:
178
+ - npm config set '//registry.npmjs.org/:_authToken' "${NPM_TOKEN}"
179
+ - npm publish --registry https://registry.npmjs.org --access public
@@ -0,0 +1,34 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "es2022",
4
+ "module": "es2022",
5
+ "incremental": false,
6
+ "lib": ["esnext", "dom"],
7
+ "allowJs": true,
8
+ "checkJs": false,
9
+ "declaration": false,
10
+ "rootDir": ".",
11
+ /* Strict Type-Checking Options */
12
+ "strict": true,
13
+ "noImplicitAny": true,
14
+ "strictNullChecks": true,
15
+
16
+ /* Additional Checks */
17
+ "noImplicitReturns": false,
18
+ "noUnusedLocals": true,
19
+ "noUnusedParameters": true,
20
+ "noFallthroughCasesInSwitch": false,
21
+ "noImplicitThis": false,
22
+
23
+ /* Module Resolution Options */
24
+ "baseUrl": ".",
25
+ "moduleResolution": "node",
26
+ "resolveJsonModule": true,
27
+ "esModuleInterop": true,
28
+ "preserveSymlinks": true,
29
+ "allowSyntheticDefaultImports": true,
30
+ "skipLibCheck": true
31
+ },
32
+ "exclude": ["dist/", "build/", ".tests/"],
33
+ "include": ["src/"]
34
+ }
package/cli.js CHANGED
@@ -5,6 +5,7 @@ import { create, serve, build, pack, preview, update } from './index.js';
5
5
  import { version } from './src/pluginCliHelper.js';
6
6
  import setupMapUi from './src/setupMapUi.js';
7
7
  import buildStagingApp from './src/buildStagingApp.js';
8
+ import ensureTypes from './src/ensureTypes.js';
8
9
 
9
10
  program.version(version);
10
11
 
@@ -12,6 +13,7 @@ program
12
13
  .command('create')
13
14
  .summary('create new plugin')
14
15
  .defaultOptions()
16
+ .option('-t --typescript', 'Create a plugin using typescript')
15
17
  .safeAction(create);
16
18
 
17
19
  program
@@ -26,7 +28,7 @@ program
26
28
  .summary('start preview server')
27
29
  .defaultOptions()
28
30
  .defaultServeOptions()
29
- .option('--vcm [url]', 'URL to a virtualcityMAP application', (val) =>
31
+ .option('--vcm [url]', 'URL to a VC MAP application', (val) =>
30
32
  val.replace(/\/$/, ''),
31
33
  )
32
34
  .safeAction(preview);
@@ -67,4 +69,6 @@ program
67
69
  )
68
70
  .safeAction(update);
69
71
 
72
+ program.command('ensure-types').action(ensureTypes);
73
+
70
74
  program.parse(process.argv);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vcmap/plugin-cli",
3
- "version": "2.1.17",
3
+ "version": "3.0.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",
@@ -38,11 +38,11 @@
38
38
  "sass": "1.32.13",
39
39
  "semver": "^7.5.4",
40
40
  "tar": "^6.1.15",
41
- "vite": "^4.4.9",
41
+ "vite": "^4.5.2",
42
42
  "vue-template-compiler": "~2.7.14"
43
43
  },
44
44
  "peerDependencies": {
45
- "@vcmap/ui": "^5.0.1",
45
+ "@vcmap/ui": "^5.1.0",
46
46
  "vue": "~2.7.14"
47
47
  },
48
48
  "peerDependenciesMeta": {
@@ -54,12 +54,13 @@
54
54
  }
55
55
  },
56
56
  "devDependencies": {
57
- "@vcsuite/eslint-config": "^3.0.5",
57
+ "@vcsuite/eslint-config": "^3.0.6",
58
58
  "eslint": "^8.38.0"
59
59
  },
60
60
  "eslintIgnore": [
61
61
  "node_modules",
62
- "assets/tests"
62
+ "assets/tests",
63
+ "assets/index.ts"
63
64
  ],
64
65
  "eslintConfig": {
65
66
  "extends": "@vcsuite/eslint-config/node",
package/src/build.js CHANGED
@@ -1,10 +1,10 @@
1
1
  import path from 'path';
2
- import { rm, mkdir } from 'fs/promises';
3
- import fs from 'fs';
2
+ import { rm, mkdir } from 'node:fs/promises';
3
+ import fs from 'node:fs';
4
4
  import vue2 from '@vitejs/plugin-vue2';
5
5
  import vcsOl from '@vcmap/rollup-plugin-vcs-ol';
6
6
  import { logger } from '@vcsuite/cli-logger';
7
- import { getPluginEntry, getPluginName } from './packageJsonHelpers.js';
7
+ import { getEntry, getPluginName } from './packageJsonHelpers.js';
8
8
  import { getContext } from './context.js';
9
9
  import { executeUiNpm, resolveMapUi } from './hostingHelpers.js';
10
10
 
@@ -44,14 +44,7 @@ export async function getLibraryPaths(pluginName) {
44
44
  * @returns {Promise<void>}
45
45
  */
46
46
  export default async function buildModule(options) {
47
- const entry = await getPluginEntry();
48
- if (path.relative('src', entry).startsWith('.')) {
49
- logger.warning(`detected irregular entry ${entry}`);
50
- logger.warning(
51
- 'vuetify component resolution expects source files to be within "src"',
52
- );
53
- }
54
-
47
+ const entry = await getEntry();
55
48
  const pluginName = await getPluginName();
56
49
  const libraryPaths = await getLibraryPaths(pluginName);
57
50
  const distPath = path.join(getContext(), 'dist');
package/src/create.js CHANGED
@@ -1,13 +1,19 @@
1
1
  import fs from 'fs';
2
2
  import path from 'path';
3
3
  import prompts from 'prompts';
4
- import semver from 'semver';
4
+ import { minVersion, parse, valid } from 'semver';
5
5
  import tar from 'tar';
6
6
  import { logger } from '@vcsuite/cli-logger';
7
7
  import { LicenseType, writeLicense } from './licenses.js';
8
8
  import { DepType, installDeps, setVcMapVersion } from './packageJsonHelpers.js';
9
9
  import { updatePeerDependencies } from './update.js';
10
- import { name, version, promiseExec, getDirname } from './pluginCliHelper.js';
10
+ import {
11
+ name,
12
+ version,
13
+ promiseExec,
14
+ getDirname,
15
+ peerDependencies as cliPeerDependencies,
16
+ } from './pluginCliHelper.js';
11
17
 
12
18
  /**
13
19
  * @typedef {Object} PluginTemplateOptions
@@ -21,6 +27,7 @@ import { name, version, promiseExec, getDirname } from './pluginCliHelper.js';
21
27
  * @property {string} template
22
28
  * @property {Array<string>} peerDeps
23
29
  * @property {boolean} gitlabCi
30
+ * @property {boolean} typescript
24
31
  */
25
32
 
26
33
  /**
@@ -29,15 +36,25 @@ import { name, version, promiseExec, getDirname } from './pluginCliHelper.js';
29
36
  * @returns {Object}
30
37
  */
31
38
  function createPackageJson(options) {
39
+ const typescriptScripts = options.typescript
40
+ ? {
41
+ 'type-check': 'vue-tsc --noEmit',
42
+ 'ensure-types': 'vcmplugin ensure-types',
43
+ }
44
+ : {};
45
+ const main = options.typescript ? 'dist/index.js' : 'src/index.js';
32
46
  return {
33
47
  name: options.name,
34
48
  version: options.version,
35
49
  description: options.description,
36
50
  type: 'module',
37
- main: 'src/index.js',
51
+ main,
38
52
  scripts: Object.assign(
39
- { prepublishOnly: 'vcmplugin build' },
53
+ {
54
+ prepublishOnly: 'vcmplugin build',
55
+ },
40
56
  ...options.scripts,
57
+ typescriptScripts,
41
58
  ),
42
59
  author: options.author,
43
60
  license: options.license,
@@ -52,7 +69,7 @@ function createPackageJson(options) {
52
69
  'CHANGELOG.md',
53
70
  ],
54
71
  exports: {
55
- '.': './src/index.js',
72
+ '.': options.typescript ? 'dist/index.js' : 'src/index.js',
56
73
  './dist': './dist/index.js',
57
74
  },
58
75
  };
@@ -142,10 +159,12 @@ async function createPluginTemplate(options, pluginPath) {
142
159
  const installEsLint = options.scripts.find((script) => script.lint);
143
160
  if (installEsLint) {
144
161
  packageJson.eslintIgnore = ['node_modules', 'dist', 'plugin-assets'];
145
- packageJson.eslintConfig = {
146
- root: true,
147
- extends: '@vcsuite/eslint-config/vue',
148
- };
162
+ if (!options.typescript) {
163
+ packageJson.eslintConfig = {
164
+ root: true,
165
+ extends: '@vcsuite/eslint-config/vue',
166
+ };
167
+ }
149
168
  packageJson.prettier = '@vcsuite/eslint-config/prettier.js';
150
169
  }
151
170
 
@@ -172,9 +191,10 @@ async function createPluginTemplate(options, pluginPath) {
172
191
  logger.debug('created src directory');
173
192
  }
174
193
 
194
+ const indexFile = options.typescript ? 'index.ts' : 'index.js';
175
195
  await fs.promises.copyFile(
176
- path.join(getDirname(), '..', 'assets', 'index.js'),
177
- path.join(pluginPath, 'src', 'index.js'),
196
+ path.join(getDirname(), '..', 'assets', indexFile),
197
+ path.join(pluginPath, 'src', indexFile),
178
198
  );
179
199
  }
180
200
 
@@ -196,7 +216,11 @@ async function createPluginTemplate(options, pluginPath) {
196
216
  (obj, key) => ({ ...obj, [key]: 'latest' }),
197
217
  {},
198
218
  );
199
- await updatePeerDependencies(peerDependencies, pluginPath);
219
+ const { major, minor } = parse(
220
+ minVersion(cliPeerDependencies['@vcmap/ui']),
221
+ );
222
+ const mapVersion = `^${major}.${minor}`;
223
+ await updatePeerDependencies(peerDependencies, pluginPath, { mapVersion });
200
224
  logger.spin('installing dependencies... (this may take a while)');
201
225
  const devDeps = [`${name}@${version}`];
202
226
  if (installEsLint) {
@@ -211,6 +235,9 @@ async function createPluginTemplate(options, pluginPath) {
211
235
  'jsdom',
212
236
  );
213
237
  }
238
+ if (options.typescript) {
239
+ devDeps.push('typescript', 'vue-tsc');
240
+ }
214
241
  await installDeps(devDeps, DepType.DEV, pluginPath);
215
242
  logger.success('Installed dependencies');
216
243
  } catch (e) {
@@ -280,8 +307,11 @@ async function createPlugin(options) {
280
307
  ]);
281
308
 
282
309
  if (options.gitlabCi) {
310
+ const gitlabFile = options.typescript
311
+ ? 'ts.gitlab-ci.yml'
312
+ : '.gitlab-ci.yml';
283
313
  await fs.promises.copyFile(
284
- path.join(getDirname(), '..', 'assets', '.gitlab-ci.yml'),
314
+ path.join(getDirname(), '..', 'assets', gitlabFile),
285
315
  path.join(pluginPath, '.gitlab-ci.yml'),
286
316
  );
287
317
  await fs.promises.cp(
@@ -291,6 +321,17 @@ async function createPlugin(options) {
291
321
  );
292
322
  }
293
323
 
324
+ if (options.typescript) {
325
+ await fs.promises.copyFile(
326
+ path.join(getDirname(), '..', 'assets', 'tsconfig.json'),
327
+ path.join(pluginPath, 'tsconfig.json'),
328
+ );
329
+ await fs.promises.copyFile(
330
+ path.join(getDirname(), '..', 'assets', 'eslintrc.cjs'),
331
+ path.join(pluginPath, '.eslintrc.cjs'),
332
+ );
333
+ }
334
+
294
335
  await createPluginTemplate(options, pluginPath);
295
336
  await setVcMapVersion(pluginPath);
296
337
  logger.success(`Created plugin ${options.name}`);
@@ -367,7 +408,7 @@ export default async function create() {
367
408
  name: 'version',
368
409
  message: 'Version',
369
410
  initial: '1.0.0',
370
- validate: (value) => !!semver.valid(value),
411
+ validate: (value) => !!valid(value),
371
412
  },
372
413
  {
373
414
  type: 'text',
@@ -397,6 +438,14 @@ export default async function create() {
397
438
  value: type,
398
439
  })),
399
440
  },
441
+ {
442
+ type: 'toggle',
443
+ name: 'typescript',
444
+ message: 'Create plugin using typescript (recommended).',
445
+ initial: true,
446
+ active: 'yes',
447
+ inactive: 'no',
448
+ },
400
449
  {
401
450
  type: 'select',
402
451
  name: 'template',
@@ -404,6 +453,13 @@ export default async function create() {
404
453
  initial: 0,
405
454
  choices: templateChoices,
406
455
  },
456
+ {
457
+ type: (prev, values) => (values.typescript && prev ? 'confirm' : null),
458
+ name: 'keepTs',
459
+ message:
460
+ 'The selected template is not in typescript. You will have to manually transform it. Keep typescript?',
461
+ initial: true,
462
+ },
407
463
  {
408
464
  type: 'multiselect',
409
465
  message: 'Add the following scripts to the package.json.',
@@ -434,5 +490,9 @@ export default async function create() {
434
490
  },
435
491
  });
436
492
 
493
+ if (answers.template && answers.typescript) {
494
+ answers.typescript = !!answers.keepTs;
495
+ }
496
+
437
497
  await createPlugin(answers);
438
498
  }
@@ -24,8 +24,7 @@ Command.prototype.defaultServeOptions = function defaultServeOptions() {
24
24
  .option(
25
25
  '-c, --config <config>',
26
26
  'a config override to not use the default plugin config',
27
- )
28
- .option('--https', 'use https for serving');
27
+ );
29
28
 
30
29
  return this;
31
30
  };
@@ -0,0 +1,16 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { executeUiNpm, resolveMapUi } from './hostingHelpers.js';
3
+ import { getPackageJson } from './packageJsonHelpers.js';
4
+
5
+ export default async function ensureTypes() {
6
+ const packageJson = await getPackageJson();
7
+ if (packageJson.devDependencies?.typescript) {
8
+ const indexTs = resolveMapUi('index.d.ts');
9
+
10
+ if (!existsSync(indexTs)) {
11
+ console.log('building types');
12
+ await executeUiNpm('build-types -- --skipValidation');
13
+ }
14
+ console.log('types ensured');
15
+ }
16
+ }
@@ -5,7 +5,7 @@ import fs from 'fs';
5
5
  import path from 'path';
6
6
  import { logger } from '@vcsuite/cli-logger';
7
7
  import { getContext, resolveContext } from './context.js';
8
- import { getPluginEntry, getPluginName } from './packageJsonHelpers.js';
8
+ import { getPluginName, getEntry } from './packageJsonHelpers.js';
9
9
  import { promiseExec, getDirname } from './pluginCliHelper.js';
10
10
 
11
11
  /**
@@ -13,7 +13,6 @@ import { promiseExec, getDirname } from './pluginCliHelper.js';
13
13
  * @property {string|Object} [config] - an optional configObject or fileName to use for configuring the plugin
14
14
  * @property {string} [auth] - potential auth string to download assets (index.html, config) with
15
15
  * @property {number} [port]
16
- * @property {boolean} [https]
17
16
  */
18
17
 
19
18
  /**
@@ -120,14 +119,14 @@ export async function printVcmapUiVersion() {
120
119
  export async function reWriteAppConfig(appConfig, pluginConfig, production) {
121
120
  const name = await getPluginName();
122
121
  pluginConfig.name = name;
123
- pluginConfig.entry = production ? 'dist/index.js' : await getPluginEntry();
122
+ pluginConfig.entry = production ? 'dist/index.js' : await getEntry();
124
123
  appConfig.modules = appConfig.modules ?? [];
125
124
  appConfig.modules.forEach((config) => {
126
125
  if (Array.isArray(config.plugins)) {
127
126
  config.plugins = config.plugins.filter((p) => p.name !== name);
128
127
  }
129
128
  });
130
- appConfig.modules.push({ plugins: [pluginConfig] });
129
+ appConfig.modules.push({ _id: 'plugin-cli-module', plugins: [pluginConfig] });
131
130
  }
132
131
 
133
132
  /**
@@ -143,7 +142,8 @@ export function getAppConfigJson(appConfig, auth, production, configFile) {
143
142
  return Promise.resolve(configMap.get('app.config.json'));
144
143
  }
145
144
  const isObject = typeof appConfig === 'object' && appConfig !== null;
146
- const isWebVcm = !isObject && /^https?:\/\//.test(usedConfig);
145
+ const isURL = (url) => /^https?:\/\//.test(url);
146
+ const isWebVcm = !isObject && isURL(usedConfig);
147
147
  return new Promise((resolve, reject) => {
148
148
  async function handleAppConfig(data) {
149
149
  try {
@@ -151,6 +151,15 @@ export function getAppConfigJson(appConfig, auth, production, configFile) {
151
151
  configMap.set('app.config.json', appConfigJson);
152
152
  const pluginConfig = await getPluginConfig(configFile);
153
153
  await reWriteAppConfig(appConfigJson, pluginConfig, production);
154
+ if (isWebVcm) {
155
+ // replace relative URLs by absolute ones
156
+ appConfigJson.modules = appConfigJson.modules.map((m) => {
157
+ if (typeof m === 'string' && !isURL(m)) {
158
+ return new URL(m, usedConfig).toString();
159
+ }
160
+ return m;
161
+ });
162
+ }
154
163
  resolve(appConfigJson);
155
164
  } catch (e) {
156
165
  reject(e);
@@ -3,7 +3,7 @@ import { writeFile, readFile } from 'fs/promises';
3
3
  import { parse, satisfies, validRange } from 'semver';
4
4
  import { logger } from '@vcsuite/cli-logger';
5
5
  import path from 'path';
6
- import { getContext } from './context.js';
6
+ import { getContext, resolveContext } from './context.js';
7
7
  import { promiseExec } from './pluginCliHelper.js';
8
8
 
9
9
  /** @type {Object|null} */
@@ -42,17 +42,15 @@ export async function getPluginName() {
42
42
  }
43
43
 
44
44
  /**
45
+ * Gets the entry of the package
45
46
  * @returns {Promise<string>}
46
47
  */
47
- export async function getPluginEntry() {
48
- const { main, module, type } = await getPackageJson();
49
-
50
- let entry = type === 'module' ? module : null;
51
- entry = entry || main;
52
- if (!entry) {
53
- throw new Error('Could not determine entry point');
48
+ export async function getEntry() {
49
+ const isTS = resolveContext('src', 'index.ts');
50
+ if (existsSync(isTS)) {
51
+ return 'src/index.ts';
54
52
  }
55
- return entry;
53
+ return 'src/index.js';
56
54
  }
57
55
 
58
56
  /**
@@ -17,7 +17,7 @@ export function getDirname() {
17
17
  * @type {string} version
18
18
  * @type {string} name
19
19
  */
20
- export const { version, name } = JSON.parse(
20
+ export const { version, name, peerDependencies } = JSON.parse(
21
21
  fs.readFileSync(path.join(getDirname(), '..', 'package.json')).toString(),
22
22
  );
23
23
 
@@ -27,7 +27,7 @@ export const { version, name } = JSON.parse(
27
27
  export const promiseExec = util.promisify(childProcess.exec);
28
28
 
29
29
  /**
30
- * @typedef {ServeOptions} VcmConfigJs
30
+ * @typedef {PreviewOptions} VcmConfigJs
31
31
  * @property {Object} proxy - see https://vitejs.dev/config/server-options.html#server-proxy
32
32
  */
33
33
 
package/src/preview.js CHANGED
@@ -23,8 +23,8 @@ import setupMapUi from './setupMapUi.js';
23
23
  import { getVcmConfigJs } from './pluginCliHelper.js';
24
24
 
25
25
  /**
26
- * @typedef {HostingOptions} PreviewOptions
27
- * @property {string} [vcm]
26
+ * @typedef {ServeOptions} PreviewOptions
27
+ * @property {string} [vcm] - an optional URL to a VC Map application
28
28
  */
29
29
 
30
30
  /**
@@ -38,12 +38,11 @@ function setAliases(alias, libraryPaths) {
38
38
  }
39
39
 
40
40
  /**
41
- * @param {string} [hostedVcm]
42
- * @param {boolean} [https]
41
+ * @param {VcmConfigJs} options
43
42
  * @returns {Promise<import("vite").InlineConfig>}
44
43
  */
45
- async function getServerOptions(hostedVcm, https) {
46
- let proxy;
44
+ async function getServerOptions(options) {
45
+ let proxy = options.proxy || {};
47
46
  const normalLibraries = await getLibraryPaths('normal');
48
47
  const scopedLibraries = await getLibraryPaths('@scoped/plugin');
49
48
  const alias = {
@@ -52,11 +51,24 @@ async function getServerOptions(hostedVcm, https) {
52
51
  setAliases(alias, normalLibraries);
53
52
  setAliases(alias, scopedLibraries);
54
53
 
55
- if (hostedVcm) {
54
+ if (options.vcm) {
55
+ const proxyOptions = {
56
+ target: options.vcm,
57
+ changeOrigin: true,
58
+ secure: false,
59
+ configure(httpProxy) {
60
+ httpProxy.on('proxyRes', (proxyRes) => {
61
+ delete proxyRes.headers['Content-Security-Policy'];
62
+ delete proxyRes.headers['content-security-policy'];
63
+ });
64
+ },
65
+ };
66
+
56
67
  proxy = {
57
- '^/style.css': hostedVcm,
58
- '^/assets': hostedVcm,
59
- '^/plugins': hostedVcm,
68
+ ...proxy,
69
+ '^/style.css': proxyOptions,
70
+ '^/assets': proxyOptions,
71
+ '^/plugins': proxyOptions,
60
72
  };
61
73
  }
62
74
 
@@ -69,7 +81,6 @@ async function getServerOptions(hostedVcm, https) {
69
81
  server: {
70
82
  middlewareMode: true,
71
83
  proxy,
72
- https,
73
84
  },
74
85
  };
75
86
  }
@@ -87,18 +98,21 @@ export default async function preview(options) {
87
98
  if (!fs.existsSync(resolveMapUi('dist'))) {
88
99
  await buildMapUI();
89
100
  }
101
+ } else {
102
+ logger.info(`Using hosted VC Map application ${mergedOptions.vcm}`);
90
103
  }
91
104
  checkReservedDirectories();
92
105
  await build({ development: false, watch: true });
93
106
  const app = express();
94
107
  logger.info('Starting preview server...');
95
- const server = await createServer(
96
- await getServerOptions(mergedOptions.vcm, mergedOptions.https),
97
- );
108
+ const inlineConfig = await getServerOptions(mergedOptions);
109
+ const server = await createServer(inlineConfig);
98
110
 
99
111
  addAppConfigRoute(
100
112
  app,
101
- mergedOptions.vcm ? `${mergedOptions.vcm}/app.config.json` : null,
113
+ mergedOptions.vcm
114
+ ? `${mergedOptions.vcm}/app.config.json`
115
+ : mergedOptions.appConfig,
102
116
  mergedOptions.auth,
103
117
  mergedOptions.config,
104
118
  true,
package/src/serve.js CHANGED
@@ -92,6 +92,7 @@ export default async function serve(options) {
92
92
  }
93
93
  const vcmConfigJs = await getVcmConfigJs();
94
94
  const mergedOptions = { ...vcmConfigJs, ...options };
95
+
95
96
  await printVcmapUiVersion();
96
97
  /*
97
98
  // In case @vcmap/ui is linked via git+ssh, dist folder is not available and must be built first
@@ -105,7 +106,7 @@ export default async function serve(options) {
105
106
  const port = mergedOptions.port || 8008;
106
107
 
107
108
  logger.info('Starting development server...');
108
- const proxy = await getProxy(mergedOptions.https ? 'https' : 'http', port);
109
+ const proxy = await getProxy('http', port);
109
110
  const { peerDependencies } = await getPackageJson();
110
111
 
111
112
  const optimizationIncludes = [
@@ -152,7 +153,6 @@ export default async function serve(options) {
152
153
  plugins: [vue2(), createConfigJsonReloadPlugin()],
153
154
  server: {
154
155
  middlewareMode: true,
155
- https: mergedOptions.https,
156
156
  proxy: { ...mergedOptions.proxy, ...proxy },
157
157
  },
158
158
  css: {
package/src/update.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { logger } from '@vcsuite/cli-logger';
2
- import { valid } from 'semver';
2
+ import { validRange } from 'semver';
3
3
  import {
4
4
  checkVcMapVersion,
5
5
  DepType,
@@ -26,7 +26,7 @@ export async function updatePeerDependencies(
26
26
  pluginPath,
27
27
  options = {},
28
28
  ) {
29
- if (options.mapVersion && !valid(options.mapVersion)) {
29
+ if (options.mapVersion && !validRange(options.mapVersion)) {
30
30
  logger.error(
31
31
  `The mapVersion ${options.mapVersion} is not valid. Using 'latest' instead`,
32
32
  );