@unvt/charites 0.2.0 → 0.4.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.
Files changed (90) hide show
  1. package/.devcontainer/README.md +1 -1
  2. package/.eslintrc.js +1 -1
  3. package/.github/PULL_REQUEST_TEMPLATE.md +1 -1
  4. package/.github/release.yml +14 -0
  5. package/.github/workflows/build-docs.yml +4 -4
  6. package/.github/workflows/build.yml +33 -7
  7. package/LICENSE +1 -1
  8. package/dist/cli/build.js +4 -4
  9. package/dist/cli/convert.js +2 -2
  10. package/dist/cli/init.js +3 -3
  11. package/dist/cli/serve.js +8 -4
  12. package/dist/commands/build.js +7 -12
  13. package/dist/commands/convert.js +2 -12
  14. package/dist/commands/init.js +1 -1
  15. package/dist/commands/serve.js +81 -16
  16. package/dist/lib/build-sprite.js +6 -8
  17. package/dist/lib/error.js +2 -1
  18. package/dist/lib/tileinfo-importer/index.js +1 -0
  19. package/dist/lib/yaml-writer.js +25 -13
  20. package/dist/types/index.js +6 -2
  21. package/docs/Pipfile.lock +136 -193
  22. package/docs/source/conf.py +3 -3
  23. package/docs/source/index.rst +2 -2
  24. package/docs/source/install/img/windows-guide-01.png +0 -0
  25. package/docs/source/install/index.rst +1 -0
  26. package/docs/source/install/install.rst +1 -0
  27. package/docs/source/install/install_on_nanban.rst +1 -1
  28. package/docs/source/install/installation_guide_for_windows.rst +52 -0
  29. package/docs/source/install/recommended_environment.rst +2 -2
  30. package/docs/source/usage/commandline_interface.rst +23 -10
  31. package/docs/source/usage/example2.rst +166 -0
  32. package/docs/source/usage/examples.rst +2 -2
  33. package/docs/source/usage/img/example02-001.png +0 -0
  34. package/docs/source/usage/img/example02-002.png +0 -0
  35. package/docs/source/usage/img/example02-003.png +0 -0
  36. package/docs/source/usage/img/example02-004.png +0 -0
  37. package/docs/source/usage/img/example02-005.png +0 -0
  38. package/docs/source/usage/img/example02-006.png +0 -0
  39. package/docs/source/usage/img/example02-007.png +0 -0
  40. package/docs/source/usage/img/example02-008.png +0 -0
  41. package/docs/source/usage/img/example02-009.png +0 -0
  42. package/docs/source/usage/img/example02-010.png +0 -0
  43. package/docs/source/usage/img/example02-011.png +0 -0
  44. package/docs/source/usage/img/example02-012.png +0 -0
  45. package/docs/source/usage/img/example02-013.png +0 -0
  46. package/docs/source/usage/img/example02-014.png +0 -0
  47. package/docs/source/usage/img/example02-015.png +0 -0
  48. package/docs/source/usage/img/example02-016.png +0 -0
  49. package/docs/source/usage/img/example02-017.png +0 -0
  50. package/docs/source/usage/img/example02-018.png +0 -0
  51. package/docs/source/usage/index.rst +1 -0
  52. package/package.json +34 -28
  53. package/playwright.config.ts +29 -0
  54. package/provider/default/app.js +26 -46
  55. package/provider/default/index.html +4 -3
  56. package/provider/default/shared.js +76 -0
  57. package/provider/geolonia/app.js +25 -32
  58. package/provider/geolonia/index.html +4 -1
  59. package/provider/mapbox/app.js +33 -53
  60. package/provider/mapbox/index.html +2 -1
  61. package/src/cli/init.ts +1 -1
  62. package/src/cli/serve.ts +10 -3
  63. package/src/commands/build.ts +2 -6
  64. package/src/commands/convert.ts +2 -10
  65. package/src/commands/init.ts +1 -1
  66. package/src/commands/serve.ts +107 -15
  67. package/src/lib/build-sprite.ts +2 -6
  68. package/src/lib/get-sprite-slug.ts +1 -1
  69. package/src/lib/tileinfo-importer/base-importer.ts +1 -1
  70. package/src/lib/tileinfo-importer/metadata-importer.ts +1 -1
  71. package/src/lib/tileinfo-importer/tilejson-importer.ts +1 -1
  72. package/src/lib/validate-style.ts +1 -1
  73. package/src/lib/yaml-parser.ts +1 -1
  74. package/src/lib/yaml-writer.ts +28 -15
  75. package/test/build.spec.ts +22 -12
  76. package/test/command.build.spec.ts +39 -31
  77. package/test/command.serve.spec.ts +103 -0
  78. package/test/convert.spec.ts +24 -4
  79. package/test/data/convert.json +7 -0
  80. package/test/data/style.json +4 -3
  81. package/test/init.spec.ts +7 -5
  82. package/test/playwright/provider/default/e2e.spec.ts +13 -0
  83. package/test/playwright/provider/geolonia/e2e.spec.ts +13 -0
  84. package/test/playwright/provider/mapbox/e2e.spec.ts +13 -0
  85. package/test/util/charitesCmd.ts +3 -1
  86. package/test/util/execPromise.ts +51 -1
  87. package/test/util/index.ts +3 -0
  88. package/test/validate-style.spec.ts +1 -1
  89. package/provider/geolonia/app.css +0 -17
  90. package/provider/mapbox/app.css +0 -17
@@ -1,8 +1,9 @@
1
1
  <!DOCTYPE html>
2
- <html>
2
+ <html id="charites-geolonia">
3
3
  <head>
4
4
  <link rel="stylesheet" href="/app.css" />
5
5
  <title>Charites Live Preview</title>
6
+ <link href='https://watergis.github.io/maplibre-gl-legend/maplibre-gl-legend.css' rel='stylesheet' />
6
7
  </head>
7
8
  <body>
8
9
  <div class="overlay">
@@ -14,6 +15,8 @@
14
15
  </div>
15
16
  <div id="map"></div>
16
17
  <script type="text/javascript" src="https://cdn.geolonia.com/v1/embed?geolonia-api-key=YOUR-API-KEY"></script>
18
+ <script src="https://watergis.github.io/maplibre-gl-legend/maplibre-gl-legend.js"></script>
19
+ <script type="text/javascript" src="/shared.js"></script>
17
20
  <script type="text/javascript" src="/app.js"></script>
18
21
  </body>
19
22
  </html>
@@ -1,53 +1,33 @@
1
- mapboxgl.accessToken = '___MAPBOX_ACCESS_TOKEN___'
2
-
3
- const map = new mapboxgl.Map({
4
- container: 'map',
5
- hash: true,
6
- style: `http://${window.location.host}/style.json`,
7
- })
8
-
9
- const socket = new WebSocket('ws://localhost:___PORT___')
10
-
11
- socket.addEventListener('message', (message) => {
12
- map.setStyle(JSON.parse(message.data))
13
- })
14
-
15
- map.addControl(new mapboxgl.NavigationControl(), 'top-right')
16
-
17
- map.addControl(
18
- new watergis.MapboxLegendControl(
19
- {},
20
- {
21
- showDefault: true,
22
- showCheckbox: true,
23
- onlyRendered: true,
24
- reverseOrder: true,
25
- accesstoken: mapboxgl.accessToken,
26
- },
27
- ),
28
- 'bottom-left',
29
- )
30
-
31
- const showTileBoundaries = document.getElementById('showTileBoundaries')
32
- const setShowTileBoundaries = function () {
33
- const checked = showTileBoundaries.checked
34
- map.showTileBoundaries = checked
35
- }
36
- setShowTileBoundaries()
37
- showTileBoundaries.addEventListener('click', setShowTileBoundaries)
38
-
39
- const showCollisionBoxes = document.getElementById('showCollisionBoxes')
40
- const setShowCollisionBoxes = function () {
41
- const checked = showCollisionBoxes.checked
42
- map.showCollisionBoxes = checked
43
- }
44
- setShowCollisionBoxes()
45
- showCollisionBoxes.addEventListener('click', setShowCollisionBoxes)
46
-
47
- const showPadding = document.getElementById('showPadding')
48
- const setShowPadding = function () {
49
- const checked = showPadding.checked
50
- map.showPadding = checked
51
- }
52
- setShowPadding()
53
- showPadding.addEventListener('click', setShowPadding)
1
+ ;(async () => {
2
+ mapboxgl.accessToken = '___MAPBOX_ACCESS_TOKEN___'
3
+
4
+ const { style, center, zoom } = await window._charites.parseMapStyle()
5
+ const options = {
6
+ container: 'map',
7
+ hash: true,
8
+ style,
9
+ }
10
+ if (center) options.center = center
11
+ if (zoom) options.zoom = zoom
12
+ const map = new mapboxgl.Map(options)
13
+
14
+ window._charites.initializeWebSocket(map)
15
+
16
+ map.addControl(new mapboxgl.NavigationControl(), 'top-right')
17
+
18
+ map.addControl(
19
+ new MapboxLegendControl(
20
+ {},
21
+ {
22
+ showDefault: true,
23
+ showCheckbox: true,
24
+ onlyRendered: true,
25
+ reverseOrder: true,
26
+ accesstoken: mapboxgl.accessToken,
27
+ },
28
+ ),
29
+ 'bottom-left',
30
+ )
31
+
32
+ window._charites.setupDebugCheckboxes(map)
33
+ })()
@@ -1,5 +1,5 @@
1
1
  <!DOCTYPE html>
2
- <html>
2
+ <html id="charites-mapbox">
3
3
  <head>
4
4
  <link rel="stylesheet" href="/app.css" />
5
5
  <link href='https://api.mapbox.com/mapbox-gl-js/v2.5.0/mapbox-gl.css' rel='stylesheet' />
@@ -17,6 +17,7 @@
17
17
  <label><input type="checkbox" id="showPadding">show Padding</label>
18
18
  </div>
19
19
  <div id="map"></div>
20
+ <script type="text/javascript" src="/shared.js"></script>
20
21
  <script type="text/javascript" src="/app.js"></script>
21
22
  </body>
22
23
  </html>
package/src/cli/init.ts CHANGED
@@ -6,7 +6,7 @@ const program = new Command()
6
6
  program
7
7
  .name('init')
8
8
  .arguments('<file>')
9
- .description('initialize a style JSON')
9
+ .description('initialize a style YAML')
10
10
  .option(
11
11
  '-t, --tilejson-urls <tilejson_urls>',
12
12
  'an URL for TileJSON. It will create empty layers from vector_layers property of TileJSON. Please use comma (,) in case multiple TileJSONs require.',
package/src/cli/serve.ts CHANGED
@@ -15,14 +15,21 @@ program
15
15
  )
16
16
  .option(
17
17
  '--mapbox-access-token [mapboxAccessToken]',
18
- 'Access Token for the Mapbox',
18
+ 'Your Mapbox Access Token (required if using the `mapbox` provider)',
19
+ )
20
+ .option(
21
+ '-i, --sprite-input [<icon input directory>]',
22
+ 'directory path of icon source to build icons. The default <icon source> is `icons/`',
19
23
  )
20
24
  .option('--port [port]', 'Specify custom port')
21
- .action((source: string, serveOptions: serveOptions) => {
25
+ .option('--no-open', "Don't open the preview in the default browser")
26
+ .action(async (source: string, serveOptions: serveOptions) => {
22
27
  const options: serveOptions = program.opts()
23
28
  options.provider = serveOptions.provider
24
29
  options.mapboxAccessToken = serveOptions.mapboxAccessToken
25
30
  options.port = serveOptions.port
31
+ options.spriteInput = serveOptions.spriteInput
32
+ options.open = serveOptions.open
26
33
  if (!fs.existsSync(defaultSettings.configFile)) {
27
34
  fs.writeFileSync(
28
35
  defaultSettings.configFile,
@@ -30,7 +37,7 @@ program
30
37
  )
31
38
  }
32
39
  try {
33
- serve(source, program.opts())
40
+ await serve(source, program.opts())
34
41
  } catch (e) {
35
42
  error(e)
36
43
  }
@@ -6,7 +6,7 @@ import { buildSprite } from '../lib/build-sprite'
6
6
  import { getSpriteSlug } from '../lib/get-sprite-slug'
7
7
  import { defaultValues } from '../lib/defaultValues'
8
8
  import jsonminify from 'jsonminify'
9
- import { StyleSpecification } from '@maplibre/maplibre-gl-style-spec/types'
9
+ import { StyleSpecification } from '@maplibre/maplibre-gl-style-spec'
10
10
  import watch from 'node-watch'
11
11
 
12
12
  export interface buildOptions {
@@ -94,11 +94,7 @@ export async function build(
94
94
  }
95
95
  }
96
96
 
97
- try {
98
- fs.writeFileSync(destinationPath, style)
99
- } catch (err) {
100
- throw `${destinationPath}: Permission denied`
101
- }
97
+ fs.writeFileSync(destinationPath, style)
102
98
  }
103
99
 
104
100
  export function buildWatch(
@@ -45,11 +45,7 @@ export function convert(source: string, destination: string) {
45
45
  const style = JSON.parse(lines.join(''))
46
46
  const destinationPath = getDestinationPath(destination)
47
47
 
48
- try {
49
- writeYaml(destinationPath, style, false)
50
- } catch (err) {
51
- throw `${destinationPath}: Permission denied`
52
- }
48
+ writeYaml(destinationPath, style, false)
53
49
  })
54
50
  } else {
55
51
  sourcePath = path.resolve(process.cwd(), source)
@@ -67,10 +63,6 @@ export function convert(source: string, destination: string) {
67
63
 
68
64
  const destinationPath = getDestinationPath(destination, sourcePath)
69
65
 
70
- try {
71
- writeYaml(destinationPath, style, false)
72
- } catch (err) {
73
- throw `${destinationPath}: Permission denied`
74
- }
66
+ writeYaml(destinationPath, style, false)
75
67
  }
76
68
  }
@@ -1,4 +1,4 @@
1
- import { StyleSpecification } from '@maplibre/maplibre-gl-style-spec/types'
1
+ import { StyleSpecification } from '@maplibre/maplibre-gl-style-spec'
2
2
  import { writeYaml } from '../lib/yaml-writer'
3
3
  import {
4
4
  TileJSONImporter,
@@ -1,5 +1,6 @@
1
1
  import path from 'path'
2
2
  import fs from 'fs'
3
+ import os from 'os'
3
4
  import http from 'http'
4
5
  import open from 'open'
5
6
  import { WebSocketServer } from 'ws'
@@ -8,14 +9,17 @@ import watch from 'node-watch'
8
9
  import { parser } from '../lib/yaml-parser'
9
10
  import { validateStyle } from '../lib/validate-style'
10
11
  import { defaultValues } from '../lib/defaultValues'
12
+ import { buildSprite } from '../lib/build-sprite'
11
13
 
12
14
  export interface serveOptions {
13
15
  provider?: string
14
16
  mapboxAccessToken?: string
15
17
  port?: string
18
+ spriteInput?: string
19
+ open?: boolean
16
20
  }
17
21
 
18
- export function serve(source: string, options: serveOptions) {
22
+ export async function serve(source: string, options: serveOptions) {
19
23
  let port = process.env.PORT || 8080
20
24
  if (options.port) {
21
25
  port = Number(options.port)
@@ -36,40 +40,102 @@ export function serve(source: string, options: serveOptions) {
36
40
  throw `${sourcePath}: No such file or directory`
37
41
  }
38
42
 
39
- const server = http.createServer((req, res) => {
43
+ const mapboxAccessToken =
44
+ options.mapboxAccessToken || defaultValues.mapboxAccessToken
45
+ if (provider === 'mapbox' && !mapboxAccessToken) {
46
+ throw `Provider is mapbox, but the Mapbox Access Token is not set. Please provide it using --mapbox-access-token, or set it in \`~/.charites/config.yml\` (see the Global configuration section of the documentation for more information)`
47
+ }
48
+
49
+ let spriteOut: string | undefined = undefined
50
+ let spriteRefresher: (() => Promise<void>) | undefined = undefined
51
+ if (options.spriteInput) {
52
+ spriteOut = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'charites-'))
53
+ spriteRefresher = async () => {
54
+ if (
55
+ typeof options.spriteInput === 'undefined' ||
56
+ typeof spriteOut === 'undefined'
57
+ ) {
58
+ return
59
+ }
60
+ await buildSprite(options.spriteInput, spriteOut, 'sprite')
61
+ }
62
+ await spriteRefresher()
63
+ }
64
+
65
+ const server = http.createServer(async (req, res) => {
40
66
  const url = (req.url || '').replace(/\?.*/, '')
41
- const dir = path.join(defaultValues.providerDir, provider)
67
+ const defaultProviderDir = path.join(defaultValues.providerDir, 'default')
68
+ const providerDir = path.join(defaultValues.providerDir, provider)
69
+
70
+ if (
71
+ typeof spriteOut !== 'undefined' &&
72
+ url.match(/^\/sprite(@2x)?\.(json|png)/)
73
+ ) {
74
+ res.statusCode = 200
75
+ if (url.endsWith('.json')) {
76
+ res.setHeader('Content-Type', 'application/json; charset=UTF-8')
77
+ } else {
78
+ res.setHeader('Content-Type', 'image/png')
79
+ }
80
+ res.setHeader('Cache-Control', 'no-store')
81
+ const filename = path.basename(url)
82
+ const fsStream = fs.createReadStream(path.join(spriteOut, filename))
83
+ fsStream.pipe(res)
84
+ return
85
+ }
42
86
 
43
87
  switch (url) {
44
88
  case '/':
45
89
  res.statusCode = 200
46
90
  res.setHeader('Content-Type', 'text/html; charset=UTF-8')
47
- const content = fs.readFileSync(path.join(dir, 'index.html'), 'utf-8')
91
+ const content = fs.readFileSync(
92
+ path.join(providerDir, 'index.html'),
93
+ 'utf-8',
94
+ )
48
95
  res.end(content)
49
96
  break
50
97
  case '/style.json':
51
98
  let style
52
99
  try {
53
100
  style = parser(sourcePath)
101
+ if (typeof spriteOut !== 'undefined') {
102
+ style.sprite = `http://${
103
+ req.headers.host || `localhost:${port}`
104
+ }/sprite`
105
+ }
54
106
  validateStyle(style, provider)
55
107
  } catch (error) {
56
108
  console.log(error)
57
109
  }
58
110
  res.statusCode = 200
59
111
  res.setHeader('Content-Type', 'application/json; charset=UTF-8')
112
+ res.setHeader('Cache-Control', 'no-store')
60
113
  res.end(JSON.stringify(style))
61
114
  break
62
115
  case '/app.css':
63
116
  res.statusCode = 200
64
117
  res.setHeader('Content-Type', 'text/css; charset=UTF-8')
65
- const css = fs.readFileSync(path.join(dir, 'app.css'), 'utf-8')
118
+ const css = fs.readFileSync(
119
+ path.join(defaultProviderDir, 'app.css'),
120
+ 'utf-8',
121
+ )
66
122
  res.end(css)
67
123
  break
124
+ case `/shared.js`:
125
+ res.statusCode = 200
126
+ res.setHeader('Content-Type', 'application/javascript; charset=UTF-8')
127
+ const shared = fs.readFileSync(
128
+ path.join(defaultProviderDir, 'shared.js'),
129
+ 'utf-8',
130
+ )
131
+ const js = shared.replace('___PORT___', `${port}`)
132
+ res.end(js)
133
+ break
68
134
  case `/app.js`:
69
135
  res.statusCode = 200
70
136
  res.setHeader('Content-Type', 'application/javascript; charset=UTF-8')
71
137
  try {
72
- const app = fs.readFileSync(path.join(dir, 'app.js'), 'utf-8')
138
+ const app = fs.readFileSync(path.join(providerDir, 'app.js'), 'utf-8')
73
139
  const js = app
74
140
  .replace('___PORT___', `${port}`)
75
141
  .replace(
@@ -93,30 +159,56 @@ export function serve(source: string, options: serveOptions) {
93
159
  console.log(`Provider: ${provider}`)
94
160
  console.log(`Loading your style: ${sourcePath}`)
95
161
  console.log(`Your map is running on http://localhost:${port}/\n`)
96
- open(`http://localhost:${port}`)
162
+ if (options.open) {
163
+ open(`http://localhost:${port}`)
164
+ }
97
165
  })
98
166
 
99
167
  const wss = new WebSocketServer({ server })
100
168
 
101
169
  wss.on('connection', (ws) => {
102
- watch(
170
+ const watcher = watch(
103
171
  path.dirname(sourcePath),
104
- { recursive: true, filter: /\.yml$/ },
172
+ { recursive: true, filter: /\.yml$|\.svg$/i },
105
173
  (event, file) => {
106
174
  console.log(`${(event || '').toUpperCase()}: ${file}`)
107
175
  try {
108
- const style = parser(sourcePath)
109
- try {
110
- validateStyle(style, provider)
111
- } catch (error) {
112
- console.log(error)
176
+ if (file?.toLowerCase().endsWith('.yml')) {
177
+ ws.send(
178
+ JSON.stringify({
179
+ event: 'styleUpdate',
180
+ }),
181
+ )
182
+ } else if (
183
+ file?.toLowerCase().endsWith('.svg') &&
184
+ typeof spriteRefresher !== 'undefined'
185
+ ) {
186
+ spriteRefresher().then(() => {
187
+ ws.send(
188
+ JSON.stringify({
189
+ event: 'spriteUpdate',
190
+ }),
191
+ )
192
+ })
113
193
  }
114
- ws.send(JSON.stringify(style))
115
194
  } catch (e) {
116
195
  // Nothing to do
117
196
  }
118
197
  },
119
198
  )
199
+ ws.on('close', () => {
200
+ watcher.close()
201
+ })
202
+ })
203
+
204
+ process.on('SIGINT', () => {
205
+ console.log('Cleaning up...')
206
+ server.close()
207
+ if (typeof spriteOut !== 'undefined') {
208
+ fs.rmSync(spriteOut, { recursive: true })
209
+ spriteOut = undefined
210
+ }
211
+ process.exit(0)
120
212
  })
121
213
 
122
214
  return server
@@ -1,5 +1,5 @@
1
1
  import { generateSprite } from '@unvt/sprite-one'
2
- const path = require('path')
2
+ import path from 'path'
3
3
 
4
4
  export async function buildSprite(
5
5
  svgPath: string,
@@ -8,10 +8,6 @@ export async function buildSprite(
8
8
  ): Promise<void> {
9
9
  const pxRatios = [1, 2]
10
10
  const outPath = path.join(publicPath, iconSlug)
11
- try {
12
- await generateSprite(outPath, [svgPath], pxRatios)
13
- } catch (error) {
14
- throw error
15
- }
11
+ await generateSprite(outPath, [svgPath], pxRatios)
16
12
  return
17
13
  }
@@ -1,4 +1,4 @@
1
- import { StyleSpecification } from '@maplibre/maplibre-gl-style-spec/types'
1
+ import { StyleSpecification } from '@maplibre/maplibre-gl-style-spec'
2
2
 
3
3
  export function getSpriteSlug(style: StyleSpecification): string | false {
4
4
  if (!style.hasOwnProperty('sprite')) {
@@ -4,7 +4,7 @@ import {
4
4
  SourceSpecification,
5
5
  LayerSpecification,
6
6
  VectorSourceSpecification,
7
- } from '@maplibre/maplibre-gl-style-spec/types'
7
+ } from '@maplibre/maplibre-gl-style-spec'
8
8
 
9
9
  export type TileInfoJSONResponse = {
10
10
  sources: { [key: string]: SourceSpecification | VectorSourceSpecification }
@@ -3,7 +3,7 @@ import { MetadataJSON, VectorLayer } from '../../types'
3
3
  import {
4
4
  LayerSpecification,
5
5
  VectorSourceSpecification,
6
- } from '@maplibre/maplibre-gl-style-spec/types'
6
+ } from '@maplibre/maplibre-gl-style-spec'
7
7
  import { BaseImporter, TileInfoJSONResponse } from './base-importer'
8
8
 
9
9
  export class MetadataJSONImporter extends BaseImporter {
@@ -3,7 +3,7 @@ import { TileJSON } from '../../types'
3
3
  import {
4
4
  SourceSpecification,
5
5
  LayerSpecification,
6
- } from '@maplibre/maplibre-gl-style-spec/types'
6
+ } from '@maplibre/maplibre-gl-style-spec'
7
7
  import { BaseImporter, TileInfoJSONResponse } from './base-importer'
8
8
 
9
9
  export class TileJSONImporter extends BaseImporter {
@@ -1,6 +1,6 @@
1
1
  const maplibreStyleSpec = require('@maplibre/maplibre-gl-style-spec')
2
2
  const mapboxStyleSpec = require('@mapbox/mapbox-gl-style-spec')
3
- import { StyleSpecification } from '@maplibre/maplibre-gl-style-spec/types'
3
+ import { StyleSpecification } from '@maplibre/maplibre-gl-style-spec'
4
4
 
5
5
  export function validateStyle(
6
6
  style: StyleSpecification,
@@ -1,6 +1,6 @@
1
1
  import fs from 'fs'
2
2
  import YAML from 'js-yaml'
3
- import { StyleSpecification } from '@maplibre/maplibre-gl-style-spec/types'
3
+ import { StyleSpecification } from '@maplibre/maplibre-gl-style-spec'
4
4
 
5
5
  const yamlinc = require('yaml-include')
6
6
 
@@ -4,7 +4,7 @@ import YAML from 'js-yaml'
4
4
  import {
5
5
  StyleSpecification,
6
6
  LayerSpecification,
7
- } from '@maplibre/maplibre-gl-style-spec/types'
7
+ } from '@maplibre/maplibre-gl-style-spec'
8
8
 
9
9
  export const writeYaml = (
10
10
  destinationPath: string,
@@ -30,13 +30,26 @@ const writeCompositedYaml = (
30
30
  stylePath = destinationPath
31
31
  }
32
32
 
33
- try {
34
- fs.writeFileSync(stylePath, styleYAML)
35
- } catch (err) {
36
- throw `${stylePath}: Permission denied`
33
+ fs.writeFileSync(stylePath, styleYAML)
34
+ }
35
+
36
+ class IncFileTag {
37
+ value: string
38
+ constructor(fileName: string) {
39
+ // We use path.posix.join to make sure the path uses / path separators, even when run on Windows.
40
+ this.value = path.posix.join('layers', fileName)
37
41
  }
38
42
  }
39
43
 
44
+ const INC_PATH_TYPE = new YAML.Type('tag:yaml.org,2002:inc/file', {
45
+ kind: 'scalar',
46
+ resolve: (data) => data,
47
+ construct: (data) => new IncFileTag(data),
48
+ instanceOf: IncFileTag,
49
+ represent: (tag) => (tag as IncFileTag).value,
50
+ })
51
+ const INC_PATH_OUTPUT_SCHEMA = YAML.DEFAULT_SCHEMA.extend([INC_PATH_TYPE])
52
+
40
53
  const writeDecompositedYaml = (
41
54
  destinationPath: string,
42
55
  style: StyleSpecification,
@@ -47,22 +60,22 @@ const writeDecompositedYaml = (
47
60
  const layer = style.layers[i]
48
61
  const layerYml = YAML.dump(layer)
49
62
  const fileName = `${style.layers[i].id}.yml`
50
- const dirName = path.join(path.dirname(destinationPath), 'layers')
51
- fs.mkdirSync(dirName, { recursive: true })
52
- fs.writeFileSync(path.join(dirName, fileName), layerYml)
63
+ const layersDirName = path.join(path.dirname(destinationPath), 'layers')
64
+ const filePath = path.join(layersDirName, fileName)
65
+ fs.mkdirSync(path.dirname(filePath), { recursive: true })
66
+ fs.writeFileSync(filePath, layerYml)
67
+
68
+ // ts-ignore is required here because the !!inc/file object is not compatible with the Layer object type.
53
69
  // @ts-ignore
54
- layers.push(`!!inc/file ${path.join('layers', fileName)}`)
70
+ layers.push(new IncFileTag(fileName))
55
71
  }
56
72
 
57
73
  style.layers = layers
58
74
 
59
75
  fs.writeFileSync(
60
76
  destinationPath,
61
- YAML.dump(style).replace(
62
- /'\!\!inc\/file layers\/.+\.yml'/g,
63
- function (match) {
64
- return match.replace(/'/g, '')
65
- },
66
- ),
77
+ YAML.dump(style, {
78
+ schema: INC_PATH_OUTPUT_SCHEMA,
79
+ }),
67
80
  )
68
81
  }
@@ -1,4 +1,5 @@
1
- import { assert } from 'chai'
1
+ import chai from 'chai'
2
+ import chaiAsPromised from 'chai-as-promised'
2
3
  import path from 'path'
3
4
  import fs from 'fs'
4
5
  import os from 'os'
@@ -6,6 +7,10 @@ import os from 'os'
6
7
  import { build, buildWatch } from '../src/commands/build'
7
8
  import { defaultValues } from '../src/lib/defaultValues'
8
9
 
10
+ chai.use(chaiAsPromised)
11
+ chai.should()
12
+ const assert = chai.assert
13
+
9
14
  describe('Test for the `build.ts`.', () => {
10
15
  const styleYaml = path.join(__dirname, 'data/style.yml')
11
16
  const iconSource = path.join(__dirname, 'data/icons')
@@ -38,7 +43,12 @@ describe('Test for the `build.ts`.', () => {
38
43
 
39
44
  // The file should exists.
40
45
  assert.deepEqual(true, !!fs.statSync(styleJson))
41
- assert.deepEqual(8, JSON.parse(fs.readFileSync(styleJson, 'utf-8')).version)
46
+
47
+ const fixtureStyleJson = path.join(__dirname, 'data/style.json')
48
+ assert.equal(
49
+ fs.readFileSync(styleJson, 'utf-8').replace(/\r\n/gm, '\n'),
50
+ fs.readFileSync(fixtureStyleJson, 'utf-8').replace(/\r\n/gm, '\n'),
51
+ )
42
52
  })
43
53
 
44
54
  it('Should minify `data/style.yml` to JSON.', async () => {
@@ -101,18 +111,18 @@ describe('Test for the `build.ts`.', () => {
101
111
  })
102
112
  })
103
113
 
104
- it('Should not create sprite when input directory is not exist.', async () => {
114
+ it('Should not create sprite when input directory is not exist.', () => {
105
115
  const noExistInputDir = path.join(__dirname, 'data/hellooooo')
106
116
 
107
- try {
108
- await build(styleYaml, styleJson, {
109
- provider: defaultValues.provider,
110
- spriteInput: noExistInputDir,
111
- spriteOutput: tmpdir,
112
- })
113
- } catch (error) {
114
- assert.deepEqual(true, !!error) // It should have something error.
115
- }
117
+ const promise = build(styleYaml, styleJson, {
118
+ provider: defaultValues.provider,
119
+ spriteInput: noExistInputDir,
120
+ spriteOutput: tmpdir,
121
+ })
122
+ return assert.isRejected(
123
+ promise,
124
+ /No such directory. Please specify valid icon input directory. For more help run charites build --help/,
125
+ )
116
126
  })
117
127
 
118
128
  it('Should watch `*.yml` and convert it to JSON', async () => {