@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,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 { copyFixturesFile, copyFixturesDir } from './util/copyFixtures'
@@ -7,11 +8,15 @@ import { makeTempDir } from './util/makeTempDir'
7
8
  import charites from './util/charitesCmd'
8
9
 
9
10
  let tmpdir = ''
11
+ chai.use(chaiAsPromised)
12
+ chai.should()
13
+ const assert = chai.assert
10
14
 
11
15
  describe('Test for the `charites build`', () => {
12
16
  beforeEach(async function () {
13
17
  tmpdir = makeTempDir()
14
18
  copyFixturesFile('style.yml', tmpdir)
19
+ copyFixturesFile('error.yml', tmpdir)
15
20
  copyFixturesDir('layers', tmpdir)
16
21
  copyFixturesDir('icons', tmpdir)
17
22
  })
@@ -62,39 +67,42 @@ describe('Test for the `charites build`', () => {
62
67
  assert.isTrue(fs.existsSync(path.join(tmpdir, 'basic-white.json')))
63
68
  })
64
69
 
65
- it('charites build style.yml style.json --sprite-url http://localhost:8080', async () => {
66
- try {
67
- // prettier-ignore
68
- await exec(`${charites} build style.yml style.json --sprite-url http://localhost:8080`, tmpdir)
69
- } catch (error) {
70
- assert.deepEqual(error.stdout, '')
71
- assert.deepEqual(error.stderr, 'Invalid sprite url format.\n')
72
- }
70
+ it('charites build style.yml style.json --sprite-url http://localhost:8080', () => {
71
+ const promise = exec(
72
+ `${charites} build style.yml style.json --sprite-url http://localhost:8080`,
73
+ tmpdir,
74
+ )
75
+ return assert.isRejected(promise, /Invalid sprite url format.\n/)
73
76
  })
74
77
 
75
- it('charites build style.yml style.json --sprite-input noExistDirname', async () => {
76
- try {
77
- // prettier-ignore
78
- await exec(`${charites} build style.yml style.json --sprite-input noExistDirname`, tmpdir)
79
- } catch (error) {
80
- assert.deepEqual(error.stdout, '')
81
- assert.deepEqual(
82
- error.stderr,
83
- 'noExistDirname: No such directory. Please specify valid icon input directory. For more help run charites build --help\n',
84
- )
85
- }
78
+ it('charites build style.yml style.json --sprite-input noExistDirname', () => {
79
+ const promise = exec(
80
+ `${charites} build style.yml style.json --sprite-input noExistDirname`,
81
+ tmpdir,
82
+ )
83
+ return assert.isRejected(
84
+ promise,
85
+ /noExistDirname: No such directory. Please specify valid icon input directory. For more help run charites build --help\n/,
86
+ )
86
87
  })
87
88
 
88
- it('charites build style.yml style.json --sprite-output noExistDirname', async () => {
89
- try {
90
- // prettier-ignore
91
- await exec(`${charites} build style.yml style.json --sprite-output noExistDirname`, tmpdir)
92
- } catch (error) {
93
- assert.deepEqual(error.stdout, '')
94
- assert.deepEqual(
95
- error.stderr,
96
- 'noExistDirname: No such directory. Please specify valid icon output directory. For more help run charites build --help\n',
97
- )
98
- }
89
+ it('charites build style.yml style.json --sprite-output noExistDirname', (done) => {
90
+ const promise = exec(
91
+ `${charites} build style.yml style.json --sprite-output noExistDirname`,
92
+ tmpdir,
93
+ )
94
+ promise.should.be.rejected
95
+ .then(function () {
96
+ return assert.isRejected(
97
+ promise,
98
+ /noExistDirname: No such directory. Please specify valid icon output directory. For more help run charites build --help\n/,
99
+ )
100
+ })
101
+ .should.notify(done)
102
+ })
103
+
104
+ it('charites build print error message', () => {
105
+ const promise = exec(`${charites} build error.yml`, tmpdir)
106
+ return assert.isRejected(promise, /missing required property "sources"/)
99
107
  })
100
108
  })
@@ -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"
@@ -14,8 +14,9 @@
14
14
  {
15
15
  "id": "background",
16
16
  "type": "background",
17
- "paint": null,
18
- "background-color": "rgba(19, 28, 54, 1)"
17
+ "paint": {
18
+ "background-color": "rgba(19, 28, 54, 1)"
19
+ }
19
20
  },
20
21
  {
21
22
  "id": "water",
@@ -30,4 +31,4 @@
30
31
  }
31
32
  }
32
33
  ]
33
- }
34
+ }
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(
@@ -0,0 +1,13 @@
1
+ import { test, expect } from '@playwright/test'
2
+
3
+ test('Charites Live Preview with maplibre', async ({ page }) => {
4
+ // collect errors on the page
5
+ const pageErrors: Error[] = []
6
+ page.on('pageerror', (exception) => pageErrors.push(exception))
7
+
8
+ await page.goto('http://localhost:8080/', { waitUntil: 'networkidle' })
9
+ await page.waitForTimeout(1000)
10
+ const title = await page.title()
11
+ expect(title).toBe('Charites Live Preview')
12
+ expect(pageErrors).toMatchObject([])
13
+ })
@@ -0,0 +1,13 @@
1
+ import { test, expect } from '@playwright/test'
2
+
3
+ test('Charites Live Preview with geolonia', async ({ page }) => {
4
+ // collect errors on the page
5
+ const pageErrors: Error[] = []
6
+ page.on('pageerror', (exception) => pageErrors.push(exception))
7
+
8
+ await page.goto('http://localhost:8088/', { waitUntil: 'networkidle' })
9
+ await page.waitForTimeout(1000)
10
+ const title = await page.title()
11
+ expect(title).toBe('Charites Live Preview')
12
+ expect(pageErrors).toMatchObject([])
13
+ })
@@ -0,0 +1,13 @@
1
+ import { test, expect } from '@playwright/test'
2
+
3
+ test('Charites Live Preview with mapbox', async ({ page }) => {
4
+ // collect errors on the page
5
+ const pageErrors: Error[] = []
6
+ page.on('pageerror', (exception) => pageErrors.push(exception))
7
+
8
+ await page.goto('http://localhost:8888/', { waitUntil: 'networkidle' })
9
+ await page.waitForTimeout(1000)
10
+ const title = await page.title()
11
+ expect(title).toBe('Charites Live Preview')
12
+ expect(pageErrors).toMatchObject([])
13
+ })
@@ -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
 
@@ -1,17 +0,0 @@
1
- html, body, #map
2
- {
3
- width: 100vw;
4
- height: 100vh;
5
- padding: 0;
6
- margin: 0;
7
- }
8
-
9
- .overlay {
10
- width: 200px;
11
- position: absolute;
12
- top: 10;
13
- left: 10;
14
- margin: 5px;
15
- z-index: 90;
16
- background-color: rgba(255, 255, 255, 0.6);
17
- }
@@ -1,17 +0,0 @@
1
- html, body, #map
2
- {
3
- width: 100vw;
4
- height: 100vh;
5
- padding: 0;
6
- margin: 0;
7
- }
8
-
9
- .overlay {
10
- width: 200px;
11
- position: absolute;
12
- top: 10;
13
- left: 10;
14
- margin: 5px;
15
- z-index: 90;
16
- background-color: rgba(255, 255, 255, 0.6);
17
- }