@geode/opengeodeweb-front 10.6.1 → 10.6.2

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.
Files changed (44) hide show
  1. package/.oxlintrc.json +50 -6
  2. package/app/components/Recaptcha.vue +1 -1
  3. package/app/stores/app.js +11 -13
  4. package/app/stores/geode.js +4 -16
  5. package/app/stores/infra.js +3 -4
  6. package/app/stores/viewer.js +4 -18
  7. package/app/utils/extension.js +0 -1
  8. package/app/utils/file_import_workflow.js +1 -1
  9. package/app/utils/local/app_mode.js +7 -0
  10. package/app/utils/local/cleanup.js +151 -0
  11. package/app/utils/local/microservices.js +9 -115
  12. package/app/utils/local/path.js +11 -55
  13. package/app/utils/local/scripts.js +3 -1
  14. package/nuxt.config.js +4 -18
  15. package/package.json +4 -2
  16. package/server/api/app/project_folder_path.post.js +5 -2
  17. package/server/api/app/run_back.post.js +7 -3
  18. package/server/api/app/run_viewer.post.js +7 -3
  19. package/tests/integration/setup.js +6 -7
  20. package/tests/integration/stores/data_style/mesh/cells.nuxt.test.js +1 -1
  21. package/tests/integration/stores/data_style/mesh/edges.nuxt.test.js +1 -1
  22. package/tests/integration/stores/data_style/mesh/index.nuxt.test.js +1 -1
  23. package/tests/integration/stores/data_style/mesh/points.nuxt.test.js +1 -1
  24. package/tests/integration/stores/data_style/mesh/polygons.nuxt.test.js +1 -1
  25. package/tests/integration/stores/data_style/mesh/polyhedra.nuxt.test.js +1 -1
  26. package/tests/integration/stores/data_style/model/blocks.nuxt.test.js +1 -1
  27. package/tests/integration/stores/data_style/model/corners.nuxt.test.js +1 -1
  28. package/tests/integration/stores/data_style/model/edges.nuxt.test.js +1 -1
  29. package/tests/integration/stores/data_style/model/index.nuxt.test.js +1 -1
  30. package/tests/integration/stores/data_style/model/lines.nuxt.test.js +1 -1
  31. package/tests/integration/stores/data_style/model/points.nuxt.test.js +1 -1
  32. package/tests/integration/stores/data_style/model/surfaces.nuxt.test.js +1 -1
  33. package/tests/integration/stores/viewer.nuxt.test.js +1 -1
  34. package/tests/unit/composables/project_manager.nuxt.test.js +1 -1
  35. package/tests/unit/stores/geode.nuxt.test.js +1 -1
  36. package/tests/unit/stores/infra.nuxt.test.js +1 -1
  37. package/tests/unit/stores/viewer.nuxt.test.js +1 -1
  38. package/app/utils/app_mode.js +0 -19
  39. package/tests/integration/microservices/back/requirements-internal.in +0 -1
  40. package/tests/integration/microservices/back/requirements.in +0 -0
  41. package/tests/integration/microservices/back/requirements.txt +0 -8
  42. package/tests/integration/microservices/viewer/requirements-internal.in +0 -1
  43. package/tests/integration/microservices/viewer/requirements.in +0 -0
  44. package/tests/integration/microservices/viewer/requirements.txt +0 -8
package/.oxlintrc.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "categories": {
3
3
  "correctness": "error",
4
4
  "suspicious": "error",
5
- "pedantic": "warn",
5
+ "pedantic": "error",
6
6
  "perf": "error",
7
7
  "style": "error",
8
8
  "restriction": "error"
@@ -45,18 +45,60 @@
45
45
  }
46
46
  ],
47
47
  "eslint/no-console": "warn", // Disable for debugging. Disable later to not have browser logs
48
- "sort-imports": ["error", { "allowSeparatedGroups": true }],
48
+ "sort-imports": [
49
+ "error",
50
+ {
51
+ "allowSeparatedGroups": true
52
+ }
53
+ ],
49
54
  "eslint/no-undefined": "off", // Conflict with unicorn/no-typeof-undefined which prefers direct undefined comparison
50
55
  "import/prefer-default-export": "off",
51
56
  "import/no-named-export": "off",
52
- "import/no-namespace": ["error", { "ignore": ["vuetify/*"] }],
53
- "vue/max-props": ["error", { "maxProps": 8 }],
57
+ "import/no-namespace": [
58
+ "error",
59
+ {
60
+ "ignore": ["vuetify/*"]
61
+ }
62
+ ],
63
+ "import/max-dependencies": [
64
+ "warn",
65
+ {
66
+ "max": 10
67
+ }
68
+ ],
69
+ "max-lines-per-function": [
70
+ "warn",
71
+ {
72
+ "max": 50,
73
+ "skipBlankLines": true,
74
+ "skipComments": true
75
+ }
76
+ ],
77
+ "unicorn/no-useless-undefined": "off",
78
+ "max-lines": [
79
+ "error",
80
+ {
81
+ "skipBlankLines": true,
82
+ "skipComments": true
83
+ }
84
+ ],
85
+ "vue/max-props": [
86
+ "error",
87
+ {
88
+ "maxProps": 8
89
+ }
90
+ ],
54
91
  "oxc/no-optional-chaining": "off",
55
92
  "node/no-process-env": "off",
56
93
  "no-continue": "off",
57
94
  "vitest/require-hook": "off",
58
95
  "import/unambiguous": "off",
59
- "max-params": ["warn", { "max": 4 }],
96
+ "max-params": [
97
+ "warn",
98
+ {
99
+ "max": 4
100
+ }
101
+ ],
60
102
  "import/no-nodejs-modules": "off",
61
103
  "eslint/no-magic-numbers": [
62
104
  "error",
@@ -94,7 +136,9 @@
94
136
  "rules": {
95
137
  "vitest/require-hook": "off",
96
138
  "vitest/no-hooks": "off",
97
- "vitest/no-importing-vitest-globals": "off"
139
+ "vitest/no-importing-vitest-globals": "off",
140
+ "max-lines-per-function": "off",
141
+ "max-statements": "off"
98
142
  }
99
143
  },
100
144
  {
@@ -1,5 +1,5 @@
1
1
  <script setup>
2
- import { appMode } from "@ogw_front/utils/app_mode"
2
+ import { appMode } from "@ogw_front/utils/local/app_mode"
3
3
  import { useInfraStore } from "@ogw_front/stores/infra"
4
4
 
5
5
  const RESPONSE_STATUS_OK = 200
package/app/stores/app.js CHANGED
@@ -265,27 +265,25 @@ export const useAppStore = defineStore("app", () => {
265
265
 
266
266
  const projectFolderPath = ref("")
267
267
  function createProjectFolder() {
268
- const { PROJECT } = useRuntimeConfig().public
269
268
  const schema = {
270
269
  $id: "/api/app/project_folder_path",
271
270
  methods: ["POST"],
272
271
  type: "object",
273
- properties: {
274
- PROJECT: { type: "string" },
275
- },
276
- required: ["PROJECT"],
272
+ properties: {},
273
+ required: [],
277
274
  additionalProperties: true,
278
275
  }
279
- const params = { PROJECT }
280
-
281
- console.log(createProjectFolder.name, { PROJECT })
282
276
 
283
- return request(schema, params, {
284
- response_function: async (response) => {
285
- console.log(`[APP] ${response.projectFolderPath} created`)
286
- projectFolderPath.value = response.projectFolderPath
277
+ return request(
278
+ schema,
279
+ {},
280
+ {
281
+ response_function: async (response) => {
282
+ console.log(`[APP] ${response.projectFolderPath} created`)
283
+ projectFolderPath.value = response.projectFolderPath
284
+ },
287
285
  },
288
- })
286
+ )
289
287
  }
290
288
 
291
289
  return {
@@ -1,6 +1,6 @@
1
1
  import { Status } from "@ogw_front/utils/status"
2
2
  import { api_fetch } from "@ogw_internal/utils/api_fetch"
3
- import { appMode } from "@ogw_front/utils/app_mode"
3
+ import { appMode } from "@ogw_front/utils/local/app_mode"
4
4
  import back_schemas from "@geode/opengeodeweb-back/opengeodeweb_back_schemas.json"
5
5
  import { upload_file } from "@ogw_internal/utils/upload_file.js"
6
6
  import { useAppStore } from "@ogw_front/stores/app"
@@ -82,28 +82,16 @@ export const useGeodeStore = defineStore("geode", {
82
82
  launch(args) {
83
83
  console.log("[GEODE] Launching back microservice...", { args })
84
84
  const appStore = useAppStore()
85
-
86
- const { BACK_PATH, BACK_COMMAND } = useRuntimeConfig().public
87
-
88
- console.log("[GEODE] BACK_PATH", BACK_PATH)
89
- console.log("[GEODE] BACK_COMMAND", BACK_COMMAND)
90
85
  const schema = {
91
86
  $id: "/api/app/run_back",
92
87
  methods: ["POST"],
93
88
  type: "object",
94
- properties: {
95
- BACK_PATH: { type: "string" },
96
- BACK_COMMAND: { type: "string" },
97
- },
98
- required: ["BACK_PATH", "BACK_COMMAND"],
89
+ properties: {},
90
+ required: [],
99
91
  additionalProperties: true,
100
92
  }
101
93
 
102
- const params = {
103
- BACK_PATH,
104
- BACK_COMMAND,
105
- args,
106
- }
94
+ const params = { args }
107
95
 
108
96
  console.log("[GEODE] params", params)
109
97
  return appStore.request(schema, params, {
@@ -1,13 +1,12 @@
1
- import { appMode, getAppMode } from "@ogw_front/utils/app_mode"
2
1
  import { Status } from "@ogw_front/utils/status"
2
+ import { appMode } from "@ogw_front/utils/local/app_mode"
3
+ import { registerRunningExtensions } from "@ogw_front/utils/extension"
3
4
  import { useAppStore } from "@ogw_front/stores/app"
4
5
  import { useLambdaStore } from "@ogw_front/stores/lambda"
5
6
 
6
- import { registerRunningExtensions } from "@ogw_front/utils/extension"
7
-
8
7
  export const useInfraStore = defineStore("infra", {
9
8
  state: () => ({
10
- app_mode: getAppMode(),
9
+ app_mode: useRuntimeConfig().public.MODE,
11
10
  ID: "",
12
11
  is_captcha_validated: false,
13
12
  status: Status.NOT_CREATED,
@@ -8,11 +8,10 @@ import "@kitware/vtk.js/Rendering/OpenGL/Profiles/Geometry"
8
8
  import SmartConnect from "wslink/src/SmartConnect"
9
9
  import { connectImageStream } from "@kitware/vtk.js/Rendering/Misc/RemoteView"
10
10
  import schemas from "@geode/opengeodeweb-viewer/opengeodeweb_viewer_schemas.json"
11
- import { useRuntimeConfig } from "nuxt/app"
12
11
 
13
12
  // Local imports
14
13
  import { Status } from "@ogw_front/utils/status"
15
- import { appMode } from "@ogw_front/utils/app_mode"
14
+ import { appMode } from "@ogw_front/utils/local/app_mode"
16
15
  import { useAppStore } from "@ogw_front/stores/app"
17
16
  import { useInfraStore } from "@ogw_front/stores/infra"
18
17
  import { viewer_call } from "@ogw_internal/utils/viewer_call"
@@ -140,29 +139,16 @@ export const useViewerStore = defineStore(
140
139
  function launch(args = { projectFolderPath }) {
141
140
  console.log("[VIEWER] Launching viewer microservice...", { args })
142
141
  const appStore = useAppStore()
143
-
144
- const { VIEWER_PATH, VIEWER_COMMAND } = useRuntimeConfig().public
145
-
146
- console.log("[VIEWER] VIEWER_PATH", VIEWER_PATH)
147
- console.log("[VIEWER] VIEWER_COMMAND", VIEWER_COMMAND)
148
142
  const schema = {
149
143
  $id: "/api/app/run_viewer",
150
144
  methods: ["POST"],
151
145
  type: "object",
152
- properties: {
153
- VIEWER_PATH: { type: "string" },
154
- VIEWER_COMMAND: { type: "string" },
155
- },
156
- required: ["VIEWER_PATH", "VIEWER_COMMAND"],
146
+ properties: {},
147
+ required: [],
157
148
  additionalProperties: true,
158
149
  }
159
150
 
160
- const params = {
161
- VIEWER_PATH,
162
- VIEWER_COMMAND,
163
- args,
164
- }
165
-
151
+ const params = { args }
166
152
  console.log("[VIEWER] params", params)
167
153
 
168
154
  return appStore.request(schema, params, {
@@ -2,7 +2,6 @@
2
2
 
3
3
  // Third party imports
4
4
  import _ from "lodash"
5
- import { useRuntimeConfig } from "nuxt/app"
6
5
 
7
6
  // Local imports
8
7
  import { useAppStore } from "@ogw_front/stores/app"
@@ -41,7 +41,7 @@ async function importItem(item) {
41
41
  const treeviewStore = useTreeviewStore()
42
42
  const registerTask = dataStore.registerObject(item.id)
43
43
  const addDataTask = dataStore.addItem(item)
44
- console.log({ dataStore })
44
+
45
45
  const addDataComponentsTask =
46
46
  item.viewer_type === "model"
47
47
  ? dataStore.addComponents(item)
@@ -0,0 +1,7 @@
1
+ const appMode = {
2
+ DESKTOP: "DESKTOP",
3
+ BROWSER: "BROWSER",
4
+ CLOUD: "CLOUD",
5
+ }
6
+
7
+ export { appMode }
@@ -0,0 +1,151 @@
1
+ // Node imports
2
+ import fs from "node:fs"
3
+ import path from "node:path"
4
+ import { setTimeout } from "node:timers/promises"
5
+
6
+ // Third party imports
7
+ import { WebSocket } from "ws"
8
+ import pTimeout from "p-timeout"
9
+ import { rimraf } from "rimraf"
10
+
11
+ const MAX_DELETE_FOLDER_RETRIES = 5
12
+
13
+ async function deleteFolderRecursive(folderPath) {
14
+ if (!fs.existsSync(folderPath)) {
15
+ console.log(`Folder ${folderPath} does not exist.`)
16
+ return
17
+ }
18
+ for (let i = 0; i <= MAX_DELETE_FOLDER_RETRIES; i += 1) {
19
+ try {
20
+ console.log(`Deleting folder: ${folderPath}`)
21
+ // oxlint-disable-next-line no-await-in-loop
22
+ await rimraf(folderPath)
23
+ console.log(`Deleted folder: ${folderPath}`)
24
+ return
25
+ } catch (error) {
26
+ console.error(`Error deleting folder ${folderPath}:`, error)
27
+ // Wait before retrying
28
+ const MILLISECONDS_PER_RETRY = 1000
29
+ const DELAY = MILLISECONDS_PER_RETRY * (i + 1)
30
+ // oxlint-disable-next-line no-await-in-loop
31
+ await setTimeout(DELAY)
32
+ console.log("Retrying delete folder")
33
+ }
34
+ }
35
+ }
36
+
37
+ function killHttpMicroservice(microservice) {
38
+ console.log("killHttpMicroservice", { ...microservice })
39
+ const failMessage = `Failed to kill ${microservice.name}`
40
+ async function do_kill() {
41
+ try {
42
+ await fetch(microservice.url, {
43
+ method: microservice.method,
44
+ })
45
+ throw new Error(failMessage)
46
+ } catch (error) {
47
+ console.log(`${microservice.name} closed`, error)
48
+ }
49
+ }
50
+ return pTimeout(do_kill(), {
51
+ milliseconds: 5000,
52
+ message: failMessage,
53
+ })
54
+ }
55
+
56
+ function killWebsocketMicroservice(microservice) {
57
+ console.log("killWebsocketMicroservice", { ...microservice })
58
+ const failMessage = `Failed to kill ${microservice.name}`
59
+ const successMessage = `Disconnected from ${microservice.name} WebSocket server`
60
+ function do_kill() {
61
+ // oxlint-disable-next-line promise/avoid-new
62
+ return new Promise((resolve) => {
63
+ const socket = new WebSocket(microservice.url)
64
+ socket.on("open", () => {
65
+ console.log("Connected to WebSocket server")
66
+ socket.send(
67
+ JSON.stringify({
68
+ id: "system:hello",
69
+ method: "wslink.hello",
70
+ args: [{ secret: "wslink-secret" }],
71
+ }),
72
+ )
73
+ })
74
+ socket.on("message", (data) => {
75
+ const message = data.toString()
76
+ console.log("Received from server:", message)
77
+ if (message.includes("hello")) {
78
+ socket.send(
79
+ JSON.stringify({
80
+ id: "application.exit",
81
+ method: "application.exit",
82
+ }),
83
+ )
84
+ console.log(successMessage)
85
+ socket.close()
86
+ resolve()
87
+ }
88
+ })
89
+ socket.on("close", () => {
90
+ console.log(successMessage)
91
+ resolve()
92
+ })
93
+ socket.on("error", (error) => {
94
+ console.error("WebSocket error:", error)
95
+ socket.close()
96
+ resolve()
97
+ })
98
+ })
99
+ }
100
+ return pTimeout(do_kill(), {
101
+ milliseconds: 5000,
102
+ message: failMessage,
103
+ })
104
+ }
105
+
106
+ function killMicroservice(microservice) {
107
+ if (microservice.type === "back") {
108
+ return killHttpMicroservice(microservice)
109
+ } else if (microservice.type === "viewer") {
110
+ return killWebsocketMicroservice(microservice)
111
+ } else {
112
+ throw new Error(`Unknown microservice type: ${microservice.type}`)
113
+ }
114
+ }
115
+
116
+ function killMicroservices(microservices) {
117
+ console.log("killMicroservices", { microservices })
118
+ return Promise.all(
119
+ microservices.map(
120
+ async (microservice) => await killMicroservice(microservice),
121
+ ),
122
+ )
123
+ }
124
+
125
+ function projectMicroservices(projectFolderPath) {
126
+ console.log("projectMicroservices", { projectFolderPath })
127
+ const filePath = microservicesMetadatasPath(projectFolderPath)
128
+
129
+ if (!fs.existsSync(filePath)) {
130
+ const microservicesMetadatas = { microservices: [] }
131
+ fs.writeFileSync(
132
+ filePath,
133
+ JSON.stringify(microservicesMetadatas, undefined, 2),
134
+ "utf8",
135
+ )
136
+ }
137
+ const content = JSON.parse(fs.readFileSync(filePath, "utf8"))
138
+ return content.microservices
139
+ }
140
+
141
+ async function cleanupBackend(projectFolderPath) {
142
+ const microservices = projectMicroservices(projectFolderPath)
143
+ await killMicroservices(microservices)
144
+ await deleteFolderRecursive(projectFolderPath)
145
+ }
146
+
147
+ function microservicesMetadatasPath(projectFolderPath) {
148
+ return path.join(projectFolderPath, "microservices.json")
149
+ }
150
+
151
+ export { cleanupBackend, microservicesMetadatasPath }
@@ -4,18 +4,14 @@ import fs from "node:fs"
4
4
  import path from "node:path"
5
5
 
6
6
  // Third party imports
7
- import { WebSocket } from "ws"
8
7
  import back_schemas from "@geode/opengeodeweb-back/opengeodeweb_back_schemas.json" with { type: "json" }
9
8
  import { getPort } from "get-port-please"
10
9
  import pTimeout from "p-timeout"
11
10
 
12
11
  // Local imports
13
12
  import { commandExistsSync, waitForReady } from "./scripts.js"
14
- import {
15
- deleteFolderRecursive,
16
- executableName,
17
- executablePath,
18
- } from "./path.js"
13
+ import { executableName, executablePath } from "./path.js"
14
+ import { microservicesMetadatasPath } from "./cleanup.js"
19
15
 
20
16
  const DEFAULT_TIMEOUT_SECONDS = 30
21
17
  const MILLISECONDS_PER_SECOND = 1000
@@ -34,9 +30,12 @@ async function runScript(
34
30
  expectedResponse,
35
31
  timeoutSeconds = DEFAULT_TIMEOUT_SECONDS,
36
32
  ) {
37
- const command = commandExistsSync(execName)
38
- ? execName
39
- : path.join(await executablePath(execPath), executableName(execName))
33
+ let command = ""
34
+ if (commandExistsSync(execName)) {
35
+ command = execName
36
+ } else {
37
+ command = path.join(executablePath(execPath), executableName(execName))
38
+ }
40
39
  console.log("runScript", command, args)
41
40
  const child = child_process.spawn(command, args, {
42
41
  encoding: "utf8",
@@ -119,93 +118,6 @@ async function runViewer(execName, execPath, args = {}) {
119
118
  return port
120
119
  }
121
120
 
122
- function killHttpMicroservice(microservice) {
123
- console.log("killHttpMicroservice", { ...microservice })
124
- const failMessage = `Failed to kill ${microservice.name}`
125
- async function do_kill() {
126
- try {
127
- await fetch(microservice.url, {
128
- method: microservice.method,
129
- })
130
- throw new Error(failMessage)
131
- } catch (error) {
132
- console.log(`${microservice.name} closed`, error)
133
- }
134
- }
135
- return pTimeout(do_kill(), {
136
- milliseconds: 5000,
137
- message: failMessage,
138
- })
139
- }
140
-
141
- function killWebsocketMicroservice(microservice) {
142
- console.log("killWebsocketMicroservice", { ...microservice })
143
- const failMessage = `Failed to kill ${microservice.name}`
144
- const successMessage = `Disconnected from ${microservice.name} WebSocket server`
145
- function do_kill() {
146
- // oxlint-disable-next-line promise/avoid-new
147
- return new Promise((resolve) => {
148
- const socket = new WebSocket(microservice.url)
149
- socket.on("open", () => {
150
- console.log("Connected to WebSocket server")
151
- socket.send(
152
- JSON.stringify({
153
- id: "system:hello",
154
- method: "wslink.hello",
155
- args: [{ secret: "wslink-secret" }],
156
- }),
157
- )
158
- })
159
- socket.on("message", (data) => {
160
- const message = data.toString()
161
- console.log("Received from server:", message)
162
- if (message.includes("hello")) {
163
- socket.send(
164
- JSON.stringify({
165
- id: "application.exit",
166
- method: "application.exit",
167
- }),
168
- )
169
- console.log(successMessage)
170
- socket.close()
171
- resolve()
172
- }
173
- })
174
- socket.on("close", () => {
175
- console.log(successMessage)
176
- resolve()
177
- })
178
- socket.on("error", (error) => {
179
- console.error("WebSocket error:", error)
180
- socket.close()
181
- resolve()
182
- })
183
- })
184
- }
185
- return pTimeout(do_kill(), {
186
- milliseconds: 5000,
187
- message: failMessage,
188
- })
189
- }
190
-
191
- function killMicroservice(microservice) {
192
- if (microservice.type === "back") {
193
- return killHttpMicroservice(microservice)
194
- } else if (microservice.type === "viewer") {
195
- return killWebsocketMicroservice(microservice)
196
- } else {
197
- throw new Error(`Unknown microservice type: ${microservice.type}`)
198
- }
199
- }
200
-
201
- function killMicroservices(microservices) {
202
- console.log("killMicroservices", { microservices })
203
- return Promise.all(
204
- microservices.map(
205
- async (microservice) => await killMicroservice(microservice),
206
- ),
207
- )
208
- }
209
121
  function projectMicroservices(projectFolderPath) {
210
122
  console.log("projectMicroservices", { projectFolderPath })
211
123
  const filePath = microservicesMetadatasPath(projectFolderPath)
@@ -222,16 +134,6 @@ function projectMicroservices(projectFolderPath) {
222
134
  return content.microservices
223
135
  }
224
136
 
225
- async function cleanupBackend(projectFolderPath) {
226
- const microservices = projectMicroservices(projectFolderPath)
227
- await killMicroservices(microservices)
228
- await deleteFolderRecursive(projectFolderPath)
229
- }
230
-
231
- function microservicesMetadatasPath(projectFolderPath) {
232
- return path.join(projectFolderPath, "microservices.json")
233
- }
234
-
235
137
  function addMicroserviceMetadatas(projectFolderPath, serviceObj) {
236
138
  const microservices = projectMicroservices(projectFolderPath)
237
139
  if (serviceObj.type === "back") {
@@ -250,12 +152,4 @@ function addMicroserviceMetadatas(projectFolderPath, serviceObj) {
250
152
  )
251
153
  }
252
154
 
253
- export {
254
- addMicroserviceMetadatas,
255
- cleanupBackend,
256
- getAvailablePort,
257
- killMicroservices,
258
- projectMicroservices,
259
- runBack,
260
- runViewer,
261
- }
155
+ export { addMicroserviceMetadatas, getAvailablePort, runBack, runViewer }
@@ -2,37 +2,22 @@
2
2
  import fs from "node:fs"
3
3
  import os from "node:os"
4
4
  import path from "node:path"
5
- import { setTimeout } from "node:timers/promises"
6
5
 
7
6
  // Third party imports
8
- import isElectron from "is-electron"
9
- import { rimraf } from "rimraf"
10
7
  import { v4 as uuidv4 } from "uuid"
11
8
 
12
- const MAX_DELETE_FOLDER_RETRIES = 5
9
+ // Local imports
10
+ import { appMode } from "./app_mode.js"
13
11
 
14
- function venvScriptPath(microservicePath) {
15
- const venvPath = path.join(microservicePath, "venv")
16
- let scriptPath = ""
17
- if (process.platform === "win32") {
18
- scriptPath = path.join(venvPath, "Scripts")
19
- } else {
20
- scriptPath = path.join(venvPath, "bin")
21
- }
22
- return scriptPath
23
- }
24
-
25
- async function executablePath(microservicePath) {
26
- if (isElectron()) {
27
- const electron = await import("electron")
28
- if (electron.app.isPackaged) {
29
- return process.resourcesPath
30
- } else {
31
- return venvScriptPath(microservicePath)
32
- }
33
- } else {
34
- return venvScriptPath(microservicePath)
12
+ function executablePath(microservicePath) {
13
+ console.log("[executablePath]", { microservicePath }, process.env.NODE_ENV)
14
+ if (
15
+ useRuntimeConfig().public.MODE === appMode.DESKTOP &&
16
+ process.env.NODE_ENV === "production"
17
+ ) {
18
+ return process.env.RESOURCES_PATH
35
19
  }
20
+ return microservicePath
36
21
  }
37
22
 
38
23
  function executableName(name) {
@@ -54,33 +39,4 @@ function generateProjectFolderPath(projectName) {
54
39
  return path.join(os.tmpdir(), projectName.replace(/\//g, "_"), uuidv4())
55
40
  }
56
41
 
57
- async function deleteFolderRecursive(folderPath) {
58
- if (!fs.existsSync(folderPath)) {
59
- console.log(`Folder ${folderPath} does not exist.`)
60
- return
61
- }
62
- for (let i = 0; i <= MAX_DELETE_FOLDER_RETRIES; i += 1) {
63
- try {
64
- console.log(`Deleting folder: ${folderPath}`)
65
- // oxlint-disable-next-line no-await-in-loop
66
- await rimraf(folderPath)
67
- console.log(`Deleted folder: ${folderPath}`)
68
- return
69
- } catch (error) {
70
- console.error(`Error deleting folder ${folderPath}:`, error)
71
- // Wait before retrying
72
- const MILLISECONDS_PER_RETRY = 1000
73
- const DELAY = MILLISECONDS_PER_RETRY * (i + 1)
74
- // oxlint-disable-next-line no-await-in-loop
75
- await setTimeout(DELAY)
76
- console.log("Retrying delete folder")
77
- }
78
- }
79
- }
80
- export {
81
- createPath,
82
- executablePath,
83
- executableName,
84
- deleteFolderRecursive,
85
- generateProjectFolderPath,
86
- }
42
+ export { createPath, executablePath, executableName, generateProjectFolderPath }
@@ -4,6 +4,8 @@ import fs from "node:fs"
4
4
  import { on } from "node:events"
5
5
  import path from "node:path"
6
6
 
7
+ import { appMode } from "./app_mode.js"
8
+
7
9
  function commandExistsSync(execName) {
8
10
  const envPath = process.env.PATH || ""
9
11
  return envPath.split(path.delimiter).some((dir) => {
@@ -37,7 +39,7 @@ async function waitNuxt(nuxtProcess) {
37
39
  }
38
40
 
39
41
  async function runBrowser(scriptName) {
40
- process.env.BROWSER = true
42
+ process.env.MODE = appMode.BROWSER
41
43
 
42
44
  const nuxtProcess = child_process.spawn("npm", ["run", scriptName], {
43
45
  shell: true,
package/nuxt.config.js CHANGED
@@ -11,26 +11,13 @@ export default defineNuxtConfig({
11
11
  runtimeConfig: {
12
12
  public: {
13
13
  API_URL: "api.geode-solutions.com",
14
- BACK_COMMAND: "opengeodeweb-back",
15
- BACK_PATH: path.join(
16
- __dirname,
17
- "tests",
18
- "integration",
19
- "microservices",
20
- "back",
21
- ),
22
- BROWSER: process.env.BROWSER ?? false,
14
+ COMMAND_BACK: "opengeodeweb-back",
15
+ COMMAND_VIEWER: "opengeodeweb-viewer",
16
+ NUXT_ROOT_PATH: __dirname,
17
+ MODE: process.env.MODE || "CLOUD",
23
18
  PROJECT: package_json.name,
24
19
  SITE_BRANCH:
25
20
  process.env.NODE_ENV === "production" ? process.env.SITE_BRANCH : "",
26
- VIEWER_COMMAND: "opengeodeweb-viewer",
27
- VIEWER_PATH: path.join(
28
- __dirname,
29
- "tests",
30
- "integration",
31
- "microservices",
32
- "viewer",
33
- ),
34
21
  },
35
22
  },
36
23
 
@@ -69,7 +56,6 @@ export default defineNuxtConfig({
69
56
  "fast-deep-equal",
70
57
  "globalthis",
71
58
  "h3",
72
- "is-electron",
73
59
  "js-file-download",
74
60
  "lodash",
75
61
  "seedrandom",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@geode/opengeodeweb-front",
3
- "version": "10.6.1",
3
+ "version": "10.6.2",
4
4
  "description": "OpenSource Vue/Nuxt/Pinia/Vuetify framework for web applications",
5
5
  "homepage": "https://github.com/Geode-solutions/OpenGeodeWeb-Front",
6
6
  "bugs": {
@@ -23,6 +23,9 @@
23
23
  },
24
24
  "scripts": {
25
25
  "lint": "eslint --fix --ext .js,.vue --ignore-path .gitignore .",
26
+ "build:back": "npx opengeodeweb-microservice-pyinstaller ../OpenGeodeWeb-Back",
27
+ "build:viewer": "npx opengeodeweb-microservice-pyinstaller ../OpenGeodeWeb-Viewer",
28
+ "build:microservices": "concurrently \"npm run build:back\" \"npm run build:viewer\"",
26
29
  "test": "npm run test:unit",
27
30
  "tests": "vitest --config ./tests/vitest.config.js",
28
31
  "test:unit": "npm run tests --project unit",
@@ -45,7 +48,6 @@
45
48
  "conf": "15.1.0",
46
49
  "dexie": "4.2.1",
47
50
  "get-port-please": "3.2.0",
48
- "is-electron": "2.2.2",
49
51
  "js-file-download": "0.4.12",
50
52
  "jszip": "3.10.1",
51
53
  "node-stream-zip": "1.15.0",
@@ -1,7 +1,7 @@
1
1
  // Node imports
2
2
 
3
3
  // Third party imports
4
- import { createError, defineEventHandler, readBody } from "h3"
4
+ import { createError, defineEventHandler } from "h3"
5
5
 
6
6
  // Local imports
7
7
  import {
@@ -9,9 +9,12 @@ import {
9
9
  generateProjectFolderPath,
10
10
  } from "@geode/opengeodeweb-front/app/utils/local/path.js"
11
11
 
12
+ import { useRuntimeConfig } from "nitropack/runtime"
13
+
12
14
  export default defineEventHandler(async (event) => {
13
15
  try {
14
- const { PROJECT } = await readBody(event)
16
+ const config = useRuntimeConfig(event).public
17
+ const { PROJECT } = config
15
18
  const projectFolderPath = generateProjectFolderPath(PROJECT)
16
19
  await createPath(projectFolderPath)
17
20
 
@@ -9,13 +9,17 @@ import {
9
9
  runBack,
10
10
  } from "@geode/opengeodeweb-front/app/utils/local/microservices.js"
11
11
 
12
+ import { useRuntimeConfig } from "nitropack/runtime"
13
+
12
14
  export default defineEventHandler(async (event) => {
13
15
  try {
14
- const { BACK_COMMAND, BACK_PATH, args } = await readBody(event)
15
- const port = await runBack(BACK_COMMAND, BACK_PATH, args)
16
+ const config = useRuntimeConfig(event).public
17
+ const { COMMAND_BACK, NUXT_ROOT_PATH } = config
18
+ const { args } = await readBody(event)
19
+ const port = await runBack(COMMAND_BACK, NUXT_ROOT_PATH, args)
16
20
  await addMicroserviceMetadatas(args.projectFolderPath, {
17
21
  type: "back",
18
- name: BACK_COMMAND,
22
+ name: COMMAND_BACK,
19
23
  port,
20
24
  })
21
25
 
@@ -9,13 +9,17 @@ import {
9
9
  runViewer,
10
10
  } from "@geode/opengeodeweb-front/app/utils/local/microservices.js"
11
11
 
12
+ import { useRuntimeConfig } from "nitropack/runtime"
13
+
12
14
  export default defineEventHandler(async (event) => {
13
15
  try {
14
- const { VIEWER_COMMAND, VIEWER_PATH, args } = await readBody(event)
15
- const port = await runViewer(VIEWER_COMMAND, VIEWER_PATH, args)
16
+ const config = useRuntimeConfig(event).public
17
+ const { COMMAND_VIEWER, NUXT_ROOT_PATH } = config
18
+ const { args } = await readBody(event)
19
+ const port = await runViewer(COMMAND_VIEWER, NUXT_ROOT_PATH, args)
16
20
  await addMicroserviceMetadatas(args.projectFolderPath, {
17
21
  type: "viewer",
18
- name: VIEWER_COMMAND,
22
+ name: COMMAND_VIEWER,
19
23
  port,
20
24
  })
21
25
 
@@ -4,7 +4,6 @@ import path from "node:path"
4
4
 
5
5
  // Third party imports
6
6
  import { afterAll, beforeAll, expect, vi } from "vitest"
7
- import { useRuntimeConfig } from "nuxt/app"
8
7
 
9
8
  // Local imports
10
9
  import {
@@ -17,7 +16,7 @@ import {
17
16
  generateProjectFolderPath,
18
17
  } from "@ogw_front/utils/local/path"
19
18
  import { Status } from "@ogw_front/utils/status"
20
- import { appMode } from "@ogw_front/utils/app_mode"
19
+ import { appMode } from "@ogw_front/utils/local/app_mode"
21
20
  import { importFile } from "@ogw_front/utils/file_import_workflow"
22
21
  import { setupActivePinia } from "@ogw_tests/utils"
23
22
  import { useGeodeStore } from "@ogw_front/stores/geode"
@@ -32,17 +31,17 @@ async function runMicroservices() {
32
31
  const infraStore = useInfraStore()
33
32
  const viewerStore = useViewerStore()
34
33
  infraStore.app_mode = appMode.BROWSER
35
- const { BACK_COMMAND, BACK_PATH, PROJECT, VIEWER_COMMAND, VIEWER_PATH } =
34
+ const { COMMAND_BACK, PROJECT, COMMAND_VIEWER, NUXT_ROOT_PATH } =
36
35
  useRuntimeConfig().public
37
36
  const projectFolderPath = generateProjectFolderPath(PROJECT)
38
37
  await createPath(projectFolderPath)
39
38
 
40
39
  const [back_port, viewer_port] = await Promise.all([
41
- runBack(BACK_COMMAND, BACK_PATH, {
40
+ runBack(COMMAND_BACK, NUXT_ROOT_PATH, {
42
41
  projectFolderPath,
43
42
  uploadFolderPath: data_folder,
44
43
  }),
45
- runViewer(VIEWER_COMMAND, VIEWER_PATH, { projectFolderPath }),
44
+ runViewer(COMMAND_VIEWER, NUXT_ROOT_PATH, { projectFolderPath }),
46
45
  ])
47
46
 
48
47
  console.log("back_port", back_port)
@@ -50,12 +49,12 @@ async function runMicroservices() {
50
49
 
51
50
  await addMicroserviceMetadatas(projectFolderPath, {
52
51
  type: "back",
53
- name: BACK_COMMAND,
52
+ name: COMMAND_BACK,
54
53
  port: back_port,
55
54
  })
56
55
  await addMicroserviceMetadatas(projectFolderPath, {
57
56
  type: "viewer",
58
- name: VIEWER_COMMAND,
57
+ name: COMMAND_VIEWER,
59
58
  port: viewer_port,
60
59
  })
61
60
 
@@ -4,7 +4,7 @@ import viewer_schemas from "@geode/opengeodeweb-viewer/opengeodeweb_viewer_schem
4
4
 
5
5
  // Local imports
6
6
  import { Status } from "@ogw_front/utils/status"
7
- import { cleanupBackend } from "@ogw_front/utils/local/microservices"
7
+ import { cleanupBackend } from "@ogw_front/utils/local/cleanup"
8
8
  import { setupIntegrationTests } from "@ogw_tests/integration/setup"
9
9
  import { useDataStyleStore } from "@ogw_front/stores/data_style"
10
10
  import { useViewerStore } from "@ogw_front/stores/viewer"
@@ -4,7 +4,7 @@ import viewer_schemas from "@geode/opengeodeweb-viewer/opengeodeweb_viewer_schem
4
4
 
5
5
  // Local imports
6
6
  import { Status } from "@ogw_front/utils/status"
7
- import { cleanupBackend } from "@ogw_front/utils/local/microservices"
7
+ import { cleanupBackend } from "@ogw_front/utils/local/cleanup"
8
8
  import { setupIntegrationTests } from "@ogw_tests/integration/setup"
9
9
  import { useDataStyleStore } from "@ogw_front/stores/data_style"
10
10
  import { useViewerStore } from "@ogw_front/stores/viewer"
@@ -4,7 +4,7 @@ import viewer_schemas from "@geode/opengeodeweb-viewer/opengeodeweb_viewer_schem
4
4
 
5
5
  // Local imports
6
6
  import { Status } from "@ogw_front/utils/status"
7
- import { cleanupBackend } from "@ogw_front/utils/local/microservices"
7
+ import { cleanupBackend } from "@ogw_front/utils/local/cleanup"
8
8
  import { setupIntegrationTests } from "@ogw_tests/integration/setup"
9
9
  import { useDataStyleStore } from "@ogw_front/stores/data_style"
10
10
  import { useViewerStore } from "@ogw_front/stores/viewer"
@@ -4,7 +4,7 @@ import viewer_schemas from "@geode/opengeodeweb-viewer/opengeodeweb_viewer_schem
4
4
 
5
5
  // Local imports
6
6
  import { Status } from "@ogw_front/utils/status"
7
- import { cleanupBackend } from "@ogw_front/utils/local/microservices"
7
+ import { cleanupBackend } from "@ogw_front/utils/local/cleanup"
8
8
  import { setupIntegrationTests } from "@ogw_tests/integration/setup"
9
9
  import { useDataStyleStore } from "@ogw_front/stores/data_style"
10
10
  import { useViewerStore } from "@ogw_front/stores/viewer"
@@ -4,7 +4,7 @@ import viewer_schemas from "@geode/opengeodeweb-viewer/opengeodeweb_viewer_schem
4
4
 
5
5
  // Local imports
6
6
  import { Status } from "@ogw_front/utils/status"
7
- import { cleanupBackend } from "@ogw_front/utils/local/microservices"
7
+ import { cleanupBackend } from "@ogw_front/utils/local/cleanup"
8
8
  import { setupIntegrationTests } from "@ogw_tests/integration/setup"
9
9
  import { useDataStyleStore } from "@ogw_front/stores/data_style"
10
10
  import { useViewerStore } from "@ogw_front/stores/viewer"
@@ -4,7 +4,7 @@ import viewer_schemas from "@geode/opengeodeweb-viewer/opengeodeweb_viewer_schem
4
4
 
5
5
  // Local imports
6
6
  import { Status } from "@ogw_front/utils/status"
7
- import { cleanupBackend } from "@ogw_front/utils/local/microservices"
7
+ import { cleanupBackend } from "@ogw_front/utils/local/cleanup"
8
8
  import { setupIntegrationTests } from "@ogw_tests/integration/setup"
9
9
  import { useDataStyleStore } from "@ogw_front/stores/data_style"
10
10
  import { useViewerStore } from "@ogw_front/stores/viewer"
@@ -4,7 +4,7 @@ import viewer_schemas from "@geode/opengeodeweb-viewer/opengeodeweb_viewer_schem
4
4
 
5
5
  // Local imports
6
6
  import { Status } from "@ogw_front/utils/status"
7
- import { cleanupBackend } from "@ogw_front/utils/local/microservices"
7
+ import { cleanupBackend } from "@ogw_front/utils/local/cleanup"
8
8
  import { setupIntegrationTests } from "@ogw_tests/integration/setup"
9
9
  import { useDataStore } from "@ogw_front/stores/data"
10
10
  import { useDataStyleStore } from "@ogw_front/stores/data_style"
@@ -4,7 +4,7 @@ import viewer_schemas from "@geode/opengeodeweb-viewer/opengeodeweb_viewer_schem
4
4
 
5
5
  // Local imports
6
6
  import { Status } from "@ogw_front/utils/status"
7
- import { cleanupBackend } from "@ogw_front/utils/local/microservices"
7
+ import { cleanupBackend } from "@ogw_front/utils/local/cleanup"
8
8
  import { setupIntegrationTests } from "@ogw_tests/integration/setup"
9
9
  import { useDataStore } from "@ogw_front/stores/data"
10
10
  import { useDataStyleStore } from "@ogw_front/stores/data_style"
@@ -4,7 +4,7 @@ import viewer_schemas from "@geode/opengeodeweb-viewer/opengeodeweb_viewer_schem
4
4
 
5
5
  // Local imports
6
6
  import { Status } from "@ogw_front/utils/status"
7
- import { cleanupBackend } from "@ogw_front/utils/local/microservices"
7
+ import { cleanupBackend } from "@ogw_front/utils/local/cleanup"
8
8
  import { setupIntegrationTests } from "@ogw_tests/integration/setup"
9
9
  import { useDataStyleStore } from "@ogw_front/stores/data_style"
10
10
  import { useViewerStore } from "@ogw_front/stores/viewer"
@@ -4,7 +4,7 @@ import viewer_schemas from "@geode/opengeodeweb-viewer/opengeodeweb_viewer_schem
4
4
 
5
5
  // Local imports
6
6
  import { Status } from "@ogw_front/utils/status"
7
- import { cleanupBackend } from "@ogw_front/utils/local/microservices"
7
+ import { cleanupBackend } from "@ogw_front/utils/local/cleanup"
8
8
  import { setupIntegrationTests } from "@ogw_tests/integration/setup"
9
9
  import { useDataStyleStore } from "@ogw_front/stores/data_style"
10
10
  import { useViewerStore } from "@ogw_front/stores/viewer"
@@ -4,7 +4,7 @@ import viewer_schemas from "@geode/opengeodeweb-viewer/opengeodeweb_viewer_schem
4
4
 
5
5
  // Local imports
6
6
  import { Status } from "@ogw_front/utils/status"
7
- import { cleanupBackend } from "@ogw_front/utils/local/microservices"
7
+ import { cleanupBackend } from "@ogw_front/utils/local/cleanup"
8
8
  import { setupIntegrationTests } from "@ogw_tests/integration/setup"
9
9
  import { useDataStore } from "@ogw_front/stores/data"
10
10
  import { useDataStyleStore } from "@ogw_front/stores/data_style"
@@ -4,7 +4,7 @@ import viewer_schemas from "@geode/opengeodeweb-viewer/opengeodeweb_viewer_schem
4
4
 
5
5
  // Local imports
6
6
  import { Status } from "@ogw_front/utils/status"
7
- import { cleanupBackend } from "@ogw_front/utils/local/microservices"
7
+ import { cleanupBackend } from "@ogw_front/utils/local/cleanup"
8
8
  import { setupIntegrationTests } from "@ogw_tests/integration/setup"
9
9
  import { useDataStyleStore } from "@ogw_front/stores/data_style"
10
10
  import { useViewerStore } from "@ogw_front/stores/viewer"
@@ -4,7 +4,7 @@ import viewer_schemas from "@geode/opengeodeweb-viewer/opengeodeweb_viewer_schem
4
4
 
5
5
  // Local imports
6
6
  import { Status } from "@ogw_front/utils/status"
7
- import { cleanupBackend } from "@ogw_front/utils/local/microservices"
7
+ import { cleanupBackend } from "@ogw_front/utils/local/cleanup"
8
8
  import { setupIntegrationTests } from "@ogw_tests/integration/setup"
9
9
  import { useDataStore } from "@ogw_front/stores/data"
10
10
  import { useDataStyleStore } from "@ogw_front/stores/data_style"
@@ -6,7 +6,7 @@ import opengeodeweb_viewer_schemas from "@geode/opengeodeweb-viewer/opengeodeweb
6
6
 
7
7
  // Local imports
8
8
  import { Status } from "@ogw_front/utils/status"
9
- import { cleanupBackend } from "@ogw_front/utils/local/microservices"
9
+ import { cleanupBackend } from "@ogw_front/utils/local/cleanup"
10
10
  import { runMicroservices } from "@ogw_tests/integration/setup"
11
11
  import { setupActivePinia } from "@ogw_tests/utils"
12
12
  import { useViewerStore } from "@ogw_front/stores/viewer"
@@ -2,7 +2,7 @@
2
2
  import { beforeEach, describe, expect, test, vi } from "vitest"
3
3
 
4
4
  // Local imports
5
- import { appMode } from "@ogw_front/utils/app_mode"
5
+ import { appMode } from "@ogw_front/utils/local/app_mode"
6
6
  import { setupActivePinia } from "@ogw_tests/utils"
7
7
  import { useProjectManager } from "@ogw_front/composables/project_manager"
8
8
 
@@ -5,7 +5,7 @@ import { registerEndpoint } from "@nuxt/test-utils/runtime"
5
5
 
6
6
  // Local imports
7
7
  import { Status } from "@ogw_front/utils/status"
8
- import { appMode } from "@ogw_front/utils/app_mode"
8
+ import { appMode } from "@ogw_front/utils/local/app_mode"
9
9
  import { setupActivePinia } from "@ogw_tests/utils"
10
10
  import { useGeodeStore } from "@ogw_front/stores/geode"
11
11
  import { useInfraStore } from "@ogw_front/stores/infra"
@@ -4,7 +4,7 @@ import { registerEndpoint } from "@nuxt/test-utils/runtime"
4
4
 
5
5
  // Local imports
6
6
  import { Status } from "@ogw_front/utils/status"
7
- import { appMode } from "@ogw_front/utils/app_mode"
7
+ import { appMode } from "@ogw_front/utils/local/app_mode"
8
8
  import { setupActivePinia } from "@ogw_tests/utils"
9
9
  import { useGeodeStore } from "@ogw_front/stores/geode"
10
10
  import { useInfraStore } from "@ogw_front/stores/infra"
@@ -12,7 +12,7 @@ import {
12
12
 
13
13
  import { WebSocket } from "ws"
14
14
  // Local imports
15
- import { appMode } from "@ogw_front/utils/app_mode"
15
+ import { appMode } from "@ogw_front/utils/local/app_mode"
16
16
  import { setupActivePinia } from "@ogw_tests/utils"
17
17
  import { useInfraStore } from "@ogw_front/stores/infra"
18
18
  import { useViewerStore } from "@ogw_front/stores/viewer"
@@ -1,19 +0,0 @@
1
- import isElectron from "is-electron"
2
-
3
- const appMode = {
4
- DESKTOP: "DESKTOP",
5
- BROWSER: "BROWSER",
6
- CLOUD: "CLOUD",
7
- }
8
-
9
- function getAppMode() {
10
- if (isElectron()) {
11
- return appMode.DESKTOP
12
- }
13
- if (useRuntimeConfig().public.BROWSER === "true") {
14
- return appMode.BROWSER
15
- }
16
- return appMode.CLOUD
17
- }
18
-
19
- export { appMode, getAppMode }
@@ -1 +0,0 @@
1
- opengeodeweb-back
@@ -1,8 +0,0 @@
1
- #
2
- # This file is autogenerated by pip-compile with Python 3.12
3
- # by the following command:
4
- #
5
- # pip-compile --output-file=tests/integration/microservices/back/requirements.txt tests/integration/microservices/back/requirements.in
6
- #
7
-
8
- opengeodeweb-back==6.*,>=6.3.4
@@ -1 +0,0 @@
1
- opengeodeweb-viewer
@@ -1,8 +0,0 @@
1
- #
2
- # This file is autogenerated by pip-compile with Python 3.12
3
- # by the following command:
4
- #
5
- # pip-compile --output-file=tests/integration/microservices/viewer/requirements.txt tests/integration/microservices/viewer/requirements.in
6
- #
7
-
8
- opengeodeweb-viewer==1.*,>=1.15.6