@unvt/charites 0.3.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 (74) hide show
  1. package/.eslintrc.js +1 -1
  2. package/.github/workflows/build-docs.yml +4 -4
  3. package/.github/workflows/build.yml +8 -7
  4. package/dist/cli/build.js +4 -4
  5. package/dist/cli/convert.js +2 -2
  6. package/dist/cli/init.js +3 -3
  7. package/dist/cli/serve.js +7 -3
  8. package/dist/commands/build.js +7 -12
  9. package/dist/commands/convert.js +2 -12
  10. package/dist/commands/init.js +1 -1
  11. package/dist/commands/serve.js +65 -12
  12. package/dist/lib/build-sprite.js +6 -8
  13. package/dist/lib/error.js +2 -1
  14. package/dist/lib/tileinfo-importer/index.js +1 -0
  15. package/dist/lib/yaml-writer.js +21 -12
  16. package/dist/types/index.js +6 -2
  17. package/docs/Pipfile.lock +136 -193
  18. package/docs/source/index.rst +2 -2
  19. package/docs/source/install/img/windows-guide-01.png +0 -0
  20. package/docs/source/install/index.rst +1 -0
  21. package/docs/source/install/install.rst +1 -0
  22. package/docs/source/install/install_on_nanban.rst +1 -1
  23. package/docs/source/install/installation_guide_for_windows.rst +52 -0
  24. package/docs/source/install/recommended_environment.rst +2 -2
  25. package/docs/source/usage/commandline_interface.rst +21 -8
  26. package/docs/source/usage/example2.rst +166 -0
  27. package/docs/source/usage/img/example02-001.png +0 -0
  28. package/docs/source/usage/img/example02-002.png +0 -0
  29. package/docs/source/usage/img/example02-003.png +0 -0
  30. package/docs/source/usage/img/example02-004.png +0 -0
  31. package/docs/source/usage/img/example02-005.png +0 -0
  32. package/docs/source/usage/img/example02-006.png +0 -0
  33. package/docs/source/usage/img/example02-007.png +0 -0
  34. package/docs/source/usage/img/example02-008.png +0 -0
  35. package/docs/source/usage/img/example02-009.png +0 -0
  36. package/docs/source/usage/img/example02-010.png +0 -0
  37. package/docs/source/usage/img/example02-011.png +0 -0
  38. package/docs/source/usage/img/example02-012.png +0 -0
  39. package/docs/source/usage/img/example02-013.png +0 -0
  40. package/docs/source/usage/img/example02-014.png +0 -0
  41. package/docs/source/usage/img/example02-015.png +0 -0
  42. package/docs/source/usage/img/example02-016.png +0 -0
  43. package/docs/source/usage/img/example02-017.png +0 -0
  44. package/docs/source/usage/img/example02-018.png +0 -0
  45. package/docs/source/usage/index.rst +1 -0
  46. package/package.json +31 -29
  47. package/provider/default/app.js +1 -3
  48. package/provider/default/index.html +2 -2
  49. package/provider/default/shared.js +11 -2
  50. package/provider/geolonia/app.js +1 -3
  51. package/provider/mapbox/app.js +1 -3
  52. package/src/cli/init.ts +1 -1
  53. package/src/cli/serve.ts +9 -2
  54. package/src/commands/build.ts +2 -6
  55. package/src/commands/convert.ts +2 -10
  56. package/src/commands/init.ts +1 -1
  57. package/src/commands/serve.ts +80 -11
  58. package/src/lib/build-sprite.ts +2 -6
  59. package/src/lib/get-sprite-slug.ts +1 -1
  60. package/src/lib/tileinfo-importer/base-importer.ts +1 -1
  61. package/src/lib/tileinfo-importer/metadata-importer.ts +1 -1
  62. package/src/lib/tileinfo-importer/tilejson-importer.ts +1 -1
  63. package/src/lib/validate-style.ts +1 -1
  64. package/src/lib/yaml-parser.ts +1 -1
  65. package/src/lib/yaml-writer.ts +23 -14
  66. package/test/build.spec.ts +2 -2
  67. package/test/command.serve.spec.ts +103 -0
  68. package/test/convert.spec.ts +24 -4
  69. package/test/data/convert.json +7 -0
  70. package/test/init.spec.ts +7 -5
  71. package/test/util/charitesCmd.ts +3 -1
  72. package/test/util/execPromise.ts +51 -1
  73. package/test/util/index.ts +3 -0
  74. package/test/validate-style.spec.ts +1 -1
@@ -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,
@@ -52,21 +65,17 @@ const writeDecompositedYaml = (
52
65
  fs.mkdirSync(path.dirname(filePath), { recursive: true })
53
66
  fs.writeFileSync(filePath, layerYml)
54
67
 
55
- // ts-ignore is required here because !!inc/file string is not compatible with the Layer object type.
56
- // We use path.posix.join to make sure the path uses / path separators, even when run on Windows.
68
+ // ts-ignore is required here because the !!inc/file object is not compatible with the Layer object type.
57
69
  // @ts-ignore
58
- layers.push(`!!inc/file ${path.posix.join('layers', fileName)}`)
70
+ layers.push(new IncFileTag(fileName))
59
71
  }
60
72
 
61
73
  style.layers = layers
62
74
 
63
75
  fs.writeFileSync(
64
76
  destinationPath,
65
- YAML.dump(style).replace(
66
- /'\!\!inc\/file layers\/.+\.yml'/g,
67
- function (match) {
68
- return match.replace(/'/g, '')
69
- },
70
- ),
77
+ YAML.dump(style, {
78
+ schema: INC_PATH_OUTPUT_SCHEMA,
79
+ }),
71
80
  )
72
81
  }
@@ -46,8 +46,8 @@ describe('Test for the `build.ts`.', () => {
46
46
 
47
47
  const fixtureStyleJson = path.join(__dirname, 'data/style.json')
48
48
  assert.equal(
49
- fs.readFileSync(styleJson, 'utf-8'),
50
- fs.readFileSync(fixtureStyleJson, 'utf-8'),
49
+ fs.readFileSync(styleJson, 'utf-8').replace(/\r\n/gm, '\n'),
50
+ fs.readFileSync(fixtureStyleJson, 'utf-8').replace(/\r\n/gm, '\n'),
51
51
  )
52
52
  })
53
53
 
@@ -0,0 +1,103 @@
1
+ import { expect } from 'chai'
2
+ import { AbortController } from 'node-abort-controller'
3
+ import axios from 'axios'
4
+ import { abortableExecFile } from './util/execPromise'
5
+ import { copyFixturesDir, copyFixturesFile } from './util/copyFixtures'
6
+ import { makeTempDir } from './util/makeTempDir'
7
+ import { charitesCliJs } from './util/charitesCmd'
8
+ import { sleep } from './util'
9
+
10
+ let tmpdir = ''
11
+
12
+ describe('Test for `charites serve`', () => {
13
+ beforeEach(async function () {
14
+ tmpdir = makeTempDir()
15
+ copyFixturesFile('style.yml', tmpdir)
16
+ copyFixturesDir('layers', tmpdir)
17
+ copyFixturesDir('icons', tmpdir)
18
+ })
19
+
20
+ it('charites serve style.yml', async () => {
21
+ const abort = new AbortController()
22
+ const server = abortableExecFile(
23
+ process.execPath,
24
+ [charitesCliJs, 'serve', '--no-open', 'style.yml'],
25
+ abort.signal,
26
+ tmpdir,
27
+ )
28
+ try {
29
+ await sleep(500)
30
+ await Promise.all([
31
+ (async () => {
32
+ const res = await axios('http://127.0.0.1:8080/style.json', {})
33
+ expect(res.data.version).to.equal(8)
34
+ })(),
35
+ (async () => {
36
+ await axios('http://127.0.0.1:8080/sprite.json', {
37
+ validateStatus: (status) => status === 404,
38
+ })
39
+ })(),
40
+ ])
41
+ } finally {
42
+ abort.abort()
43
+ }
44
+ abort.abort()
45
+ const { stdout, stderr } = await server
46
+ expect(stderr).to.equal('')
47
+ expect(stdout).to.match(/^Your map is running on http:\/\/localhost:8080/m)
48
+ })
49
+
50
+ it('charites serve --sprite-input ./icons style.yml', async () => {
51
+ const abort = new AbortController()
52
+ const server = abortableExecFile(
53
+ process.execPath,
54
+ [
55
+ charitesCliJs,
56
+ 'serve',
57
+ '--no-open',
58
+ '--sprite-input',
59
+ './icons',
60
+ 'style.yml',
61
+ ],
62
+ abort.signal,
63
+ tmpdir,
64
+ )
65
+
66
+ try {
67
+ await sleep(500)
68
+ await Promise.all([
69
+ (async () => {
70
+ const res = await axios('http://127.0.0.1:8080/style.json', {})
71
+ expect(res.status).to.equal(200)
72
+ expect(res.data.version).to.equal(8)
73
+ expect(res.data.sprite).to.equal('http://127.0.0.1:8080/sprite')
74
+ })(),
75
+ (async () => {
76
+ const res = await axios('http://127.0.0.1:8080/sprite.json', {})
77
+ expect(res.status).to.equal(200)
78
+ expect(Object.entries(res.data).length).to.be.greaterThan(0)
79
+ })(),
80
+ (async () => {
81
+ const res = await axios('http://127.0.0.1:8080/sprite@2x.json', {})
82
+ expect(res.status).to.equal(200)
83
+ expect(Object.entries(res.data).length).to.be.greaterThan(0)
84
+ })(),
85
+ (async () => {
86
+ const res = await axios('http://127.0.0.1:8080/sprite.png', {})
87
+ expect(res.status).to.equal(200)
88
+ expect(res.data.length).to.be.greaterThan(0)
89
+ })(),
90
+ (async () => {
91
+ const res = await axios('http://127.0.0.1:8080/sprite@2x.png', {})
92
+ expect(res.status).to.equal(200)
93
+ expect(res.data.length).to.be.greaterThan(0)
94
+ })(),
95
+ ])
96
+ } finally {
97
+ abort.abort()
98
+ }
99
+ const { stdout, stderr } = await server
100
+ expect(stderr).to.equal('')
101
+ expect(stdout).to.match(/^Your map is running on http:\/\/localhost:8080/m)
102
+ })
103
+ })
@@ -4,14 +4,24 @@ import fs from 'fs'
4
4
  import os from 'os'
5
5
 
6
6
  import { convert } from '../src/commands/convert'
7
+ import { build } from '../src/commands/build'
8
+
9
+ let tmp: string
7
10
 
8
11
  describe('Test for the `convert.ts`.', () => {
9
- const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'charites-'))
10
12
  const jsonPath = path.join(__dirname, 'data/convert.json')
11
- const yamlPath = path.join(tmp, 'convert.yml')
12
- const layerPath = path.join(tmp, 'layers/background.yml')
13
13
 
14
- it('Should convert `data/convert.json` to YAML.', () => {
14
+ beforeEach(async () => {
15
+ tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'charites-'))
16
+ })
17
+
18
+ afterEach(async () => {
19
+ fs.rmSync(tmp, { recursive: true })
20
+ })
21
+
22
+ it('Should convert `data/convert.json` to YAML and the YAML should be valid.', async () => {
23
+ const yamlPath = path.join(tmp, 'convert.yml')
24
+
15
25
  convert(jsonPath, yamlPath)
16
26
  const yml = fs.readFileSync(yamlPath, 'utf-8')
17
27
 
@@ -27,13 +37,23 @@ sprite: https://sprites.geolonia.com/basic-white
27
37
  glyphs: https://glyphs.geolonia.com/{fontstack}/{range}.pbf
28
38
  layers:
29
39
  - !!inc/file layers/background.yml
40
+ - !!inc/file >-
41
+ layers/background-with-very-long-name-background-with-very-long-name-background-with-very-long-name.yml
30
42
  id: example
31
43
  `,
32
44
  yml,
33
45
  )
46
+
47
+ const outJsonPath = path.join(tmp, 'converted-back.json')
48
+ // This will throw an error if the outputted YAML was invalid
49
+ await build(yamlPath, outJsonPath, { provider: 'default' })
50
+ assert.isTrue(fs.existsSync(outJsonPath))
34
51
  })
35
52
 
36
53
  it('Should create layers directory.', () => {
54
+ const yamlPath = path.join(tmp, 'convert.yml')
55
+ const layerPath = path.join(tmp, 'layers/background.yml')
56
+
37
57
  convert(jsonPath, yamlPath)
38
58
 
39
59
  const result = fs.existsSync(layerPath)
@@ -17,6 +17,13 @@
17
17
  "paint": {
18
18
  "background-color": "rgba(19, 28, 54, 1)"
19
19
  }
20
+ },
21
+ {
22
+ "id": "background-with-very-long-name-background-with-very-long-name-background-with-very-long-name",
23
+ "type": "background",
24
+ "paint": {
25
+ "background-color": "rgba(19, 28, 54, 1)"
26
+ }
20
27
  }
21
28
  ],
22
29
  "id": "example"
package/test/init.spec.ts CHANGED
@@ -84,11 +84,13 @@ describe('Test for the `init.ts`.', () => {
84
84
  assert.deepEqual(true, !!fs.statSync(styleYaml))
85
85
  // the file should be the same with init_tilejson.yml
86
86
  assert.deepEqual(
87
- fs.readFileSync(styleYaml, 'utf8'),
88
- fs.readFileSync(
89
- path.join(__dirname, 'data/init/tilejson/init_decomposite.yml'),
90
- 'utf-8',
91
- ),
87
+ fs.readFileSync(styleYaml, 'utf8').replace(/\r\n/gm, '\n'),
88
+ fs
89
+ .readFileSync(
90
+ path.join(__dirname, 'data/init/tilejson/init_decomposite.yml'),
91
+ 'utf-8',
92
+ )
93
+ .replace(/\r\n/gm, '\n'),
92
94
  )
93
95
  assert.deepEqual(
94
96
  YAML.load(
@@ -1,3 +1,5 @@
1
1
  import path from 'path'
2
2
 
3
- export default `node ${path.join(__dirname, '..', '..', 'dist', 'cli.js')}`
3
+ export const charitesCliJs = path.join(__dirname, '..', '..', 'dist', 'cli.js')
4
+
5
+ export default `"${process.execPath}" "${charitesCliJs}"`
@@ -1,9 +1,22 @@
1
1
  import child_process from 'child_process'
2
2
  import util from 'util'
3
+
4
+ // MEMO: Remove node-abort-controller when requiring NodeJS >=16
5
+ // NOTE: AbortController is available by default from NodeJS 15
6
+ import { AbortSignal } from 'node-abort-controller'
3
7
  import { makeTempDir } from './makeTempDir'
4
8
  const execSync = util.promisify(child_process.exec)
5
9
 
6
- export const exec = async (cmd: string, cwd?: string) => {
10
+ type ExecResult = {
11
+ stdout: string
12
+ stderr: string
13
+ cwd: string
14
+ }
15
+
16
+ export const exec: (cmd: string, cwd?: string) => Promise<ExecResult> = async (
17
+ cmd,
18
+ cwd,
19
+ ) => {
7
20
  const temp = cwd ? cwd : makeTempDir()
8
21
  const { stdout, stderr } = await execSync(cmd, {
9
22
  encoding: 'utf8',
@@ -12,3 +25,40 @@ export const exec = async (cmd: string, cwd?: string) => {
12
25
 
13
26
  return { stdout, stderr, cwd: temp }
14
27
  }
28
+
29
+ // MEMO: This can be replaced with the native AbortSignal support in child_process.exec after
30
+ // requiring NodeJS >= 16.4.0, see:
31
+ // https://nodejs.org/docs/latest-v16.x/api/child_process.html#child_processexeccommand-options-callback
32
+ export const abortableExecFile = (
33
+ file: string,
34
+ args: string[],
35
+ signal: AbortSignal,
36
+ cwd?: string,
37
+ ) =>
38
+ new Promise<ExecResult>((resolve, reject) => {
39
+ const temp = cwd ? cwd : makeTempDir()
40
+
41
+ // Because Windows can't catch the SIGINT signal (it doesn't have signals)
42
+ // we set a flag in the parent whether the command can die or not.
43
+ // If the command exits before we request it to exit, the error will be
44
+ // thrown, but if it exits after we request it to exit, it won't be an error.
45
+ let noErrorOnDie = false
46
+ const process = child_process.execFile(
47
+ file,
48
+ args,
49
+ {
50
+ encoding: 'utf8',
51
+ cwd: temp,
52
+ },
53
+ (error, stdout, stderr) => {
54
+ if (error && noErrorOnDie !== true) {
55
+ return reject(error)
56
+ }
57
+ resolve({ stdout, stderr, cwd: temp })
58
+ },
59
+ )
60
+ signal.addEventListener('abort', () => {
61
+ noErrorOnDie = true
62
+ process.kill('SIGINT')
63
+ })
64
+ })
@@ -0,0 +1,3 @@
1
+ import { promisify } from 'util'
2
+
3
+ export const sleep = promisify(setTimeout)
@@ -1,5 +1,5 @@
1
1
  import { assert } from 'chai'
2
- import { StyleSpecification } from '@maplibre/maplibre-gl-style-spec/types'
2
+ import { StyleSpecification } from '@maplibre/maplibre-gl-style-spec'
3
3
 
4
4
  import { validateStyle } from '../src/lib/validate-style'
5
5