@unvt/charites 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.editorconfig +9 -0
- package/.github/workflows/build.yml +50 -0
- package/LICENSE +21 -0
- package/README.md +137 -0
- package/dist/cli.js +76 -0
- package/dist/commands/build.js +62 -0
- package/dist/commands/convert.js +89 -0
- package/dist/commands/init.js +33 -0
- package/dist/commands/serve.js +100 -0
- package/dist/lib/defaultValues.js +31 -0
- package/dist/lib/validate-style.js +24 -0
- package/dist/lib/yaml-parser.js +46 -0
- package/package.json +42 -0
- package/provider/default/app.css +7 -0
- package/provider/default/app.js +11 -0
- package/provider/default/index.html +13 -0
- package/provider/geolonia/app.css +7 -0
- package/provider/geolonia/app.js +11 -0
- package/provider/geolonia/index.html +12 -0
- package/provider/mapbox/app.css +7 -0
- package/provider/mapbox/app.js +13 -0
- package/provider/mapbox/index.html +13 -0
- package/src/cli.ts +83 -0
- package/src/commands/build.ts +64 -0
- package/src/commands/convert.ts +95 -0
- package/src/commands/init.ts +29 -0
- package/src/commands/serve.ts +105 -0
- package/src/lib/defaultValues.ts +36 -0
- package/src/lib/validate-style.ts +22 -0
- package/src/lib/yaml-parser.ts +51 -0
- package/test/build.spec.ts +36 -0
- package/test/convert.spec.ts +43 -0
- package/test/data/convert.json +23 -0
- package/test/data/error.yml +1 -0
- package/test/data/example.yml +20 -0
- package/test/data/layers/background.yml +4 -0
- package/test/data/layers/water.yml +8 -0
- package/test/data/names.yml +3 -0
- package/test/data/style.json +33 -0
- package/test/data/style.yml +12 -0
- package/test/validate-style.spec.ts +18 -0
- package/test/yaml-parser.spec.ts +25 -0
- package/tsconfig.json +19 -0
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@unvt/charites",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "",
|
|
5
|
+
"bin": {
|
|
6
|
+
"charites": "dist/cli.js"
|
|
7
|
+
},
|
|
8
|
+
"scripts": {
|
|
9
|
+
"build": "tsc -p .",
|
|
10
|
+
"watch": "tsc -w",
|
|
11
|
+
"lint": "eslint .",
|
|
12
|
+
"fix": "eslint --fix .",
|
|
13
|
+
"test": "mocha -r ts-node/register test/*.ts",
|
|
14
|
+
"test:watch": "npm test -- --watch --watch-files src/**/*.ts --watch-files test/**/*.ts",
|
|
15
|
+
"command": "./node_modules/.bin/ts-node ./src/cli.ts"
|
|
16
|
+
},
|
|
17
|
+
"author": "",
|
|
18
|
+
"license": "MIT",
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"@mapbox/mapbox-gl-style-spec": "^13.22.0",
|
|
21
|
+
"@maplibre/maplibre-gl-style-spec": "^14.0.2",
|
|
22
|
+
"@types/jsonminify": "^0.4.1",
|
|
23
|
+
"commander": "^8.2.0",
|
|
24
|
+
"js-yaml": "^4.0.0",
|
|
25
|
+
"jsonminify": "^0.4.1",
|
|
26
|
+
"node-watch": "^0.7.2",
|
|
27
|
+
"open": "^8.2.1",
|
|
28
|
+
"ws": "^8.2.2",
|
|
29
|
+
"yaml-include": "^1.2.1"
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@types/chai": "^4.2.11",
|
|
33
|
+
"@types/js-yaml": "^4.0.3",
|
|
34
|
+
"@types/mocha": "^7.0.2",
|
|
35
|
+
"@types/node": "^14.0.14",
|
|
36
|
+
"@types/ws": "^8.2.0",
|
|
37
|
+
"chai": "^4.2.0",
|
|
38
|
+
"mocha": "^8.0.1",
|
|
39
|
+
"ts-node": "^8.10.2",
|
|
40
|
+
"typescript": "^3.9.5"
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
const map = new maplibregl.Map({
|
|
2
|
+
container: 'map',
|
|
3
|
+
hash: true,
|
|
4
|
+
style: `http://${window.location.host}/style.json`
|
|
5
|
+
});
|
|
6
|
+
|
|
7
|
+
const socket = new WebSocket('ws://localhost:___PORT___');
|
|
8
|
+
|
|
9
|
+
socket.addEventListener('message',(message)=>{
|
|
10
|
+
map.setStyle(JSON.parse(message.data))
|
|
11
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<link rel="stylesheet" href="/app.css" />
|
|
5
|
+
<link href='https://unpkg.com/maplibre-gl@1.15.2/dist/maplibre-gl.css' rel='stylesheet' />
|
|
6
|
+
<title>Charites Live Preview</title>
|
|
7
|
+
<script src='https://unpkg.com/maplibre-gl@1.15.2/dist/maplibre-gl.js'></script>
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<div id="map"></div>
|
|
11
|
+
<script type="text/javascript" src="/app.js"></script>
|
|
12
|
+
</body>
|
|
13
|
+
</html>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
const map = new geolonia.Map({
|
|
2
|
+
container: "#map",
|
|
3
|
+
hash: true,
|
|
4
|
+
style: `http://${window.location.host}/style.json`
|
|
5
|
+
})
|
|
6
|
+
|
|
7
|
+
const socket = new WebSocket('ws://localhost:___PORT___');
|
|
8
|
+
|
|
9
|
+
socket.addEventListener('message',(message)=>{
|
|
10
|
+
map.setStyle(JSON.parse(message.data))
|
|
11
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<link rel="stylesheet" href="/app.css" />
|
|
5
|
+
<title>Charites Live Preview</title>
|
|
6
|
+
</head>
|
|
7
|
+
<body>
|
|
8
|
+
<div id="map"></div>
|
|
9
|
+
<script type="text/javascript" src="https://cdn.geolonia.com/v1/embed?geolonia-api-key=YOUR-API-KEY"></script>
|
|
10
|
+
<script type="text/javascript" src="/app.js"></script>
|
|
11
|
+
</body>
|
|
12
|
+
</html>
|
|
@@ -0,0 +1,13 @@
|
|
|
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
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<link rel="stylesheet" href="/app.css" />
|
|
5
|
+
<link href='https://api.mapbox.com/mapbox-gl-js/v2.5.0/mapbox-gl.css' rel='stylesheet' />
|
|
6
|
+
<title>Charites Live Preview</title>
|
|
7
|
+
<script src='https://api.mapbox.com/mapbox-gl-js/v2.5.0/mapbox-gl.js'></script>
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<div id="map"></div>
|
|
11
|
+
<script type="text/javascript" src="/app.js"></script>
|
|
12
|
+
</body>
|
|
13
|
+
</html>
|
package/src/cli.ts
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { Command } from 'commander';
|
|
4
|
+
import fs from 'fs'
|
|
5
|
+
|
|
6
|
+
import { init } from './commands/init'
|
|
7
|
+
import { convert } from './commands/convert'
|
|
8
|
+
import { build } from './commands/build'
|
|
9
|
+
import { serve } from './commands/serve'
|
|
10
|
+
|
|
11
|
+
import { defaultSettings } from './lib/defaultValues'
|
|
12
|
+
|
|
13
|
+
interface buildOptions {
|
|
14
|
+
compactOutput?: boolean
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const program = new Command();
|
|
18
|
+
|
|
19
|
+
const error = (message: any) => {
|
|
20
|
+
console.error(message.toString())
|
|
21
|
+
process.exit(1)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
program
|
|
25
|
+
.option('--provider [provider]', 'your map service. e.g. `mapbox`, `geolonia`')
|
|
26
|
+
.option('--mapbox-access-token [mapboxAccessToken]', 'Access Token for the Mapbox')
|
|
27
|
+
|
|
28
|
+
program
|
|
29
|
+
.command('init <file>')
|
|
30
|
+
.description('initialize a style JSON')
|
|
31
|
+
.action((file: string) => {
|
|
32
|
+
try {
|
|
33
|
+
init(file)
|
|
34
|
+
} catch(e) {
|
|
35
|
+
error(e)
|
|
36
|
+
}
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
program
|
|
40
|
+
.command('convert <source> [destination]')
|
|
41
|
+
.description('convert the style JSON to YAML')
|
|
42
|
+
.action((source: string, destination: string) => {
|
|
43
|
+
try {
|
|
44
|
+
convert(source, destination)
|
|
45
|
+
} catch(e) {
|
|
46
|
+
error(e)
|
|
47
|
+
}
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
program
|
|
51
|
+
.command('build <source> [destination]')
|
|
52
|
+
.description('build a style JSON from the YAML')
|
|
53
|
+
.option('-c, --compact-output', 'build a minified style JSON')
|
|
54
|
+
.action((source: string, destination: string, buildOptions: buildOptions) => {
|
|
55
|
+
const options = program.opts()
|
|
56
|
+
options.compactOutput = buildOptions.compactOutput
|
|
57
|
+
|
|
58
|
+
if (! fs.existsSync(defaultSettings.configFile)) {
|
|
59
|
+
fs.writeFileSync(defaultSettings.configFile, `provider: ${options.provider || 'default'}`)
|
|
60
|
+
}
|
|
61
|
+
try {
|
|
62
|
+
build(source, destination, options)
|
|
63
|
+
} catch(e) {
|
|
64
|
+
error(e)
|
|
65
|
+
}
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
program
|
|
69
|
+
.command('serve <source>')
|
|
70
|
+
.description('serve your map locally')
|
|
71
|
+
.action((source: string) => {
|
|
72
|
+
const options = program.opts()
|
|
73
|
+
if (! fs.existsSync(defaultSettings.configFile)) {
|
|
74
|
+
fs.writeFileSync(defaultSettings.configFile, `provider: ${options.provider || 'default'}`)
|
|
75
|
+
}
|
|
76
|
+
try {
|
|
77
|
+
serve(source, program.opts())
|
|
78
|
+
} catch(e) {
|
|
79
|
+
error(e)
|
|
80
|
+
}
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
program.parse(process.argv)
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import path from 'path'
|
|
2
|
+
import fs from 'fs'
|
|
3
|
+
import { parser } from '../lib/yaml-parser'
|
|
4
|
+
import { validateStyle } from '../lib/validate-style'
|
|
5
|
+
import { defaultValues } from '../lib/defaultValues'
|
|
6
|
+
import jsonminify from 'jsonminify'
|
|
7
|
+
|
|
8
|
+
interface options {
|
|
9
|
+
provider?: string,
|
|
10
|
+
compactOutput?: boolean
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function build(source: string, destination: string, options: options) {
|
|
14
|
+
let sourcePath = path.resolve(process.cwd(), source)
|
|
15
|
+
|
|
16
|
+
// The `source` is absolute path.
|
|
17
|
+
if (source.match(/^\//)) {
|
|
18
|
+
sourcePath = source
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (! fs.existsSync(sourcePath)) {
|
|
22
|
+
throw `${sourcePath}: No such file or directory`
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
let destinationPath = ""
|
|
26
|
+
|
|
27
|
+
if (destination) {
|
|
28
|
+
if (destination.match(/^\//)) {
|
|
29
|
+
destinationPath = destination
|
|
30
|
+
} else {
|
|
31
|
+
destinationPath = path.resolve(process.cwd(), destination)
|
|
32
|
+
}
|
|
33
|
+
} else {
|
|
34
|
+
destinationPath = path.join(path.dirname(sourcePath), `${path.basename(source, '.yml')}.json`)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
let provider = defaultValues.provider
|
|
38
|
+
if (options.provider) {
|
|
39
|
+
provider = options.provider
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
let style = ''
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
const _style = parser(sourcePath)
|
|
46
|
+
validateStyle(_style, provider)
|
|
47
|
+
style = JSON.stringify(_style, null, ' ')
|
|
48
|
+
if (options.compactOutput) {
|
|
49
|
+
style = jsonminify(style)
|
|
50
|
+
}
|
|
51
|
+
} catch(err) {
|
|
52
|
+
if (err) {
|
|
53
|
+
throw err
|
|
54
|
+
} else {
|
|
55
|
+
throw `${sourcePath}: Invalid YAML syntax`
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
try {
|
|
60
|
+
fs.writeFileSync(destinationPath, style)
|
|
61
|
+
} catch(err) {
|
|
62
|
+
throw `${destinationPath}: Permission denied`
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import path from 'path'
|
|
2
|
+
import fs from 'fs'
|
|
3
|
+
import YAML from 'js-yaml'
|
|
4
|
+
import readline from 'readline'
|
|
5
|
+
|
|
6
|
+
// TODO: Type of style should be loaded from maplibre or mapbox style spec.
|
|
7
|
+
const writeYaml = (destinationPath: string, style: any) => {
|
|
8
|
+
const layers = []
|
|
9
|
+
|
|
10
|
+
for (let i = 0; i < style.layers.length; i++) {
|
|
11
|
+
const layer = style.layers[i]
|
|
12
|
+
const layerYml = YAML.dump(layer)
|
|
13
|
+
const fileName = `${style.layers[i].id}.yml`
|
|
14
|
+
const dirName = path.join(path.dirname(destinationPath), 'layers')
|
|
15
|
+
fs.mkdirSync(dirName, { recursive: true })
|
|
16
|
+
fs.writeFileSync(path.join(dirName, fileName), layerYml)
|
|
17
|
+
|
|
18
|
+
layers.push(`!!inc/file ${path.join('layers', fileName)}`)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
style.layers = layers
|
|
22
|
+
|
|
23
|
+
fs.writeFileSync(destinationPath, YAML.dump(style).replace(/'\!\!inc\/file layers\/.+\.yml'/g, function (match) {
|
|
24
|
+
return match.replace(/'/g, '')
|
|
25
|
+
}))
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const getDestinationPath = (destination: string, sourcePath: string = '') => {
|
|
29
|
+
let destinationPath
|
|
30
|
+
|
|
31
|
+
if (destination) {
|
|
32
|
+
if (destination.match(/^\//)) {
|
|
33
|
+
destinationPath = destination
|
|
34
|
+
} else {
|
|
35
|
+
destinationPath = path.resolve(process.cwd(), destination)
|
|
36
|
+
}
|
|
37
|
+
} else {
|
|
38
|
+
if (sourcePath) {
|
|
39
|
+
destinationPath = path.join(path.dirname(sourcePath), `${path.basename(sourcePath, '.json')}.yml`)
|
|
40
|
+
} else {
|
|
41
|
+
destinationPath = path.join(process.cwd(), 'style.yml')
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return destinationPath
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function convert(source: string, destination: string) {
|
|
49
|
+
let style, sourcePath
|
|
50
|
+
|
|
51
|
+
if ('-' === source) {
|
|
52
|
+
const rl = readline.createInterface({
|
|
53
|
+
input: process.stdin,
|
|
54
|
+
terminal: false
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
const lines: string[] = []
|
|
58
|
+
|
|
59
|
+
rl.on("line", (line) => {
|
|
60
|
+
lines.push(line)
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
rl.on("close", () => {
|
|
64
|
+
const style = JSON.parse(lines.join(''))
|
|
65
|
+
const destinationPath = getDestinationPath(destination)
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
writeYaml(destinationPath, style)
|
|
69
|
+
} catch(err) {
|
|
70
|
+
throw `${destinationPath}: Permission denied`
|
|
71
|
+
}
|
|
72
|
+
})
|
|
73
|
+
} else {
|
|
74
|
+
sourcePath = path.resolve(process.cwd(), source)
|
|
75
|
+
|
|
76
|
+
// The `source` is absolute path.
|
|
77
|
+
if (source.match(/^\//)) {
|
|
78
|
+
sourcePath = source
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (! fs.existsSync(sourcePath)) {
|
|
82
|
+
throw `${sourcePath}: No such file or directory`
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
style = JSON.parse(fs.readFileSync(sourcePath, 'utf-8'))
|
|
86
|
+
|
|
87
|
+
const destinationPath = getDestinationPath(destination, sourcePath)
|
|
88
|
+
|
|
89
|
+
try {
|
|
90
|
+
writeYaml(destinationPath, style)
|
|
91
|
+
} catch(err) {
|
|
92
|
+
throw `${destinationPath}: Permission denied`
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import path from 'path'
|
|
2
|
+
import fs from 'fs'
|
|
3
|
+
import YAML from 'js-yaml'
|
|
4
|
+
|
|
5
|
+
// TODO: We need type definition for style.
|
|
6
|
+
const styleRoot = {
|
|
7
|
+
version: 8,
|
|
8
|
+
name: "My Style",
|
|
9
|
+
sprite: "",
|
|
10
|
+
glyphs: "",
|
|
11
|
+
sources: {},
|
|
12
|
+
layers: []
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function init(file: string) {
|
|
16
|
+
const styleYAML = YAML.dump(styleRoot)
|
|
17
|
+
let stylePath = path.resolve(process.cwd(), file)
|
|
18
|
+
|
|
19
|
+
// The `source` is absolute path.
|
|
20
|
+
if (file.match(/^\//)) {
|
|
21
|
+
stylePath = file
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
fs.writeFileSync(stylePath, styleYAML)
|
|
26
|
+
} catch(err) {
|
|
27
|
+
throw `${stylePath}: Permission denied`
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import path from 'path'
|
|
2
|
+
import fs from 'fs'
|
|
3
|
+
import http from 'http'
|
|
4
|
+
import open from 'open'
|
|
5
|
+
import { WebSocketServer } from 'ws'
|
|
6
|
+
import watch from 'node-watch'
|
|
7
|
+
|
|
8
|
+
import { parser } from '../lib/yaml-parser'
|
|
9
|
+
import { validateStyle } from '../lib/validate-style'
|
|
10
|
+
import { defaultValues } from '../lib/defaultValues'
|
|
11
|
+
|
|
12
|
+
interface options {
|
|
13
|
+
provider?: string
|
|
14
|
+
mapboxAccessToken?: string
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function serve(source: string, options: options) {
|
|
18
|
+
const port = process.env.PORT || 8080
|
|
19
|
+
let sourcePath = path.resolve(process.cwd(), source)
|
|
20
|
+
|
|
21
|
+
let provider = defaultValues.provider
|
|
22
|
+
if (options.provider) {
|
|
23
|
+
provider = options.provider
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// The `source` is absolute path.
|
|
27
|
+
if (source.match(/^\//)) {
|
|
28
|
+
sourcePath = source
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (! fs.existsSync(sourcePath)) {
|
|
32
|
+
throw `${sourcePath}: No such file or directory`
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const server = http.createServer((req, res) => {
|
|
36
|
+
const url = (req.url || '').replace(/\?.*/, '')
|
|
37
|
+
const dir = path.join(defaultValues.providerDir, provider)
|
|
38
|
+
|
|
39
|
+
switch (url) {
|
|
40
|
+
case '/':
|
|
41
|
+
res.statusCode = 200
|
|
42
|
+
res.setHeader('Content-Type', 'text/html; charset=UTF-8')
|
|
43
|
+
const content = fs.readFileSync(path.join(dir, 'index.html'), 'utf-8')
|
|
44
|
+
res.end(content)
|
|
45
|
+
break;
|
|
46
|
+
case '/style.json':
|
|
47
|
+
const style = parser(sourcePath)
|
|
48
|
+
try {
|
|
49
|
+
validateStyle(style, provider)
|
|
50
|
+
} catch(error) {
|
|
51
|
+
console.log(error)
|
|
52
|
+
}
|
|
53
|
+
res.statusCode = 200
|
|
54
|
+
res.setHeader('Content-Type', 'application/json; charset=UTF-8')
|
|
55
|
+
res.end(JSON.stringify(style))
|
|
56
|
+
break;
|
|
57
|
+
case '/app.css':
|
|
58
|
+
res.statusCode = 200
|
|
59
|
+
res.setHeader('Content-Type', 'text/css; charset=UTF-8')
|
|
60
|
+
const css = fs.readFileSync(path.join(dir, 'app.css'), 'utf-8')
|
|
61
|
+
res.end(css)
|
|
62
|
+
break;
|
|
63
|
+
case `/app.js`:
|
|
64
|
+
res.statusCode = 200
|
|
65
|
+
res.setHeader('Content-Type', 'application/javascript; charset=UTF-8')
|
|
66
|
+
try {
|
|
67
|
+
const app = fs.readFileSync(path.join(dir, 'app.js'), 'utf-8')
|
|
68
|
+
const js = app.replace('___PORT___', `${port}`)
|
|
69
|
+
.replace('___MAPBOX_ACCESS_TOKEN___', `${options.mapboxAccessToken || defaultValues.mapboxAccessToken}`)
|
|
70
|
+
res.end(js)
|
|
71
|
+
} catch(e) {
|
|
72
|
+
throw `Invalid provider: ${provider}`
|
|
73
|
+
}
|
|
74
|
+
break;
|
|
75
|
+
}
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
server.listen(port, () => {
|
|
79
|
+
console.log(`Provider: ${provider}`)
|
|
80
|
+
console.log(`Loading your style: ${sourcePath}`)
|
|
81
|
+
console.log(`Your map is running on http://localhost:${port}/\n`)
|
|
82
|
+
open(`http://localhost:${port}`)
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
const wss = new WebSocketServer({ server });
|
|
86
|
+
|
|
87
|
+
wss.on('connection', (ws) => {
|
|
88
|
+
watch(path.dirname(sourcePath), { recursive: true, filter: /\.yml$/ }, (event, file) => {
|
|
89
|
+
console.log(`${(event || '').toUpperCase()}: ${file}`)
|
|
90
|
+
try {
|
|
91
|
+
const style = parser(sourcePath)
|
|
92
|
+
try {
|
|
93
|
+
validateStyle(style, provider)
|
|
94
|
+
} catch(error) {
|
|
95
|
+
console.log(error)
|
|
96
|
+
}
|
|
97
|
+
ws.send(JSON.stringify(style))
|
|
98
|
+
} catch(e) {
|
|
99
|
+
// Nothing to do
|
|
100
|
+
}
|
|
101
|
+
})
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
return server
|
|
105
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import path from 'path'
|
|
2
|
+
import os from 'os'
|
|
3
|
+
import fs from 'fs'
|
|
4
|
+
import YAML from 'js-yaml'
|
|
5
|
+
|
|
6
|
+
interface Config {
|
|
7
|
+
provider: string
|
|
8
|
+
providerDir: string
|
|
9
|
+
mapboxAccessToken?: string
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const homedir = os.homedir()
|
|
13
|
+
const defaultProvider = "default"
|
|
14
|
+
|
|
15
|
+
const configDir = path.join(homedir, '.charites')
|
|
16
|
+
fs.mkdirSync(configDir, { recursive: true });
|
|
17
|
+
|
|
18
|
+
const configFile = path.join(configDir, 'config.yml')
|
|
19
|
+
let config: Config = {provider: '', providerDir: '', mapboxAccessToken: ''}
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
const yaml = fs.readFileSync(configFile, 'utf-8')
|
|
23
|
+
config = YAML.load(yaml) as Config
|
|
24
|
+
} catch(e) {
|
|
25
|
+
// nothing to do
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const defaultValues: Config = {
|
|
29
|
+
provider: config.provider || defaultProvider,
|
|
30
|
+
providerDir: path.join(path.dirname(path.dirname(__dirname)), 'provider'),
|
|
31
|
+
mapboxAccessToken: config.mapboxAccessToken || ''
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export const defaultSettings = {
|
|
35
|
+
configFile: configFile
|
|
36
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
const maplibreStyleSpec = require('@maplibre/maplibre-gl-style-spec')
|
|
2
|
+
const mapboxStyleSpec = require('@mapbox/mapbox-gl-style-spec')
|
|
3
|
+
|
|
4
|
+
export function validateStyle(style: object, provider: string = "default"): void {
|
|
5
|
+
let result = []
|
|
6
|
+
if ('mapbox' === provider) {
|
|
7
|
+
result = mapboxStyleSpec.validate(style)
|
|
8
|
+
} else {
|
|
9
|
+
result = maplibreStyleSpec.validate(style)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const errors = []
|
|
13
|
+
for (let i = 0; i < result.length; i++) {
|
|
14
|
+
if (result[i].message) {
|
|
15
|
+
errors.push(result[i].message)
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (errors.length) {
|
|
20
|
+
throw `\u001b[31mError:\u001b[0m ${errors.join("\n\u001b[31mError:\u001b[0m ")}`
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import fs from 'fs'
|
|
2
|
+
import YAML from 'js-yaml'
|
|
3
|
+
|
|
4
|
+
const yamlinc = require('yaml-include')
|
|
5
|
+
|
|
6
|
+
interface StyleObject {
|
|
7
|
+
[key: string]: any;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function parser(file: string): object {
|
|
11
|
+
yamlinc.setBaseFile(file)
|
|
12
|
+
const yaml = fs.readFileSync(file, 'utf8')
|
|
13
|
+
|
|
14
|
+
const obj: StyleObject = YAML.load(yaml, {
|
|
15
|
+
schema: yamlinc.YAML_INCLUDE_SCHEMA,
|
|
16
|
+
filename: file,
|
|
17
|
+
json: true
|
|
18
|
+
}) as StyleObject
|
|
19
|
+
|
|
20
|
+
const styleObj: StyleObject = {}
|
|
21
|
+
let variables: StyleObject = {}
|
|
22
|
+
|
|
23
|
+
for (const key in obj as any) {
|
|
24
|
+
if (key.match(/^\$/)) {
|
|
25
|
+
variables[key] = obj[key]
|
|
26
|
+
} else {
|
|
27
|
+
styleObj[key] = obj[key]
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Handle all nested variables.
|
|
32
|
+
while(JSON.stringify(Object.values(variables)).match(/\$/)) {
|
|
33
|
+
for (const key in variables as any) {
|
|
34
|
+
for (const variable in variables) {
|
|
35
|
+
let _value = JSON.stringify(variables[key])
|
|
36
|
+
const regex = new RegExp(`\"\\${variable}\"`, 'g')
|
|
37
|
+
_value = _value.replace(regex, JSON.stringify(variables[variable]))
|
|
38
|
+
variables[key] = JSON.parse(_value)
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
let style = JSON.stringify(styleObj)
|
|
44
|
+
|
|
45
|
+
for (const key in variables) {
|
|
46
|
+
const regex = new RegExp(`\"\\${key}\"`, 'g')
|
|
47
|
+
style = style.replace(regex, JSON.stringify(variables[key]))
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return JSON.parse(style)
|
|
51
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { assert } from 'chai'
|
|
2
|
+
import path from 'path'
|
|
3
|
+
import fs from 'fs'
|
|
4
|
+
import os from 'os'
|
|
5
|
+
|
|
6
|
+
import { build } from '../src/commands/build'
|
|
7
|
+
import { defaultValues } from '../src/lib/defaultValues'
|
|
8
|
+
|
|
9
|
+
describe('Test for the `build.ts`.', () => {
|
|
10
|
+
|
|
11
|
+
const stylePath = path.join(__dirname, 'data/style.yml')
|
|
12
|
+
|
|
13
|
+
it('Should convert `data/style.yml` to JSON.', () => {
|
|
14
|
+
const tmpdir = fs.mkdtempSync(path.join(os.tmpdir(), 'charites-'))
|
|
15
|
+
const styleJson = path.join(tmpdir, 'style.json')
|
|
16
|
+
|
|
17
|
+
build(stylePath, styleJson, {provider: defaultValues.provider})
|
|
18
|
+
|
|
19
|
+
// The file should exists.
|
|
20
|
+
assert.deepEqual(true, !! fs.statSync(styleJson))
|
|
21
|
+
assert.deepEqual(8, JSON.parse(fs.readFileSync(styleJson, 'utf-8')).version)
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('Should minify `data/style.yml` to JSON.', () => {
|
|
25
|
+
|
|
26
|
+
const tmpdir = fs.mkdtempSync(path.join(os.tmpdir(), 'charites-'))
|
|
27
|
+
const styleJson = path.join(tmpdir, 'style.json')
|
|
28
|
+
|
|
29
|
+
build(stylePath, styleJson, {provider: defaultValues.provider, compactOutput: true})
|
|
30
|
+
|
|
31
|
+
const contents = fs.readFileSync(styleJson, 'utf-8')
|
|
32
|
+
const lines = contents.split('\n').length
|
|
33
|
+
|
|
34
|
+
assert.deepEqual(lines, 1)
|
|
35
|
+
});
|
|
36
|
+
});
|