@tileflow/dev 0.1.0-alpha.1 → 0.1.0-alpha.11
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/README.md +13 -2
- package/dist/index.d.ts +18 -1
- package/dist/index.js +326 -35
- package/package.json +7 -3
package/README.md
CHANGED
|
@@ -2,12 +2,23 @@
|
|
|
2
2
|
|
|
3
3
|
Node utilities used by the Tileflow CLI and build integrations.
|
|
4
4
|
|
|
5
|
+
Most apps should use `@tileflow/cli`, `@tileflow/vite`, or `@tileflow/next` directly. Use this
|
|
6
|
+
package when you are building a custom dev server, bundler plugin, or deployment adapter that needs
|
|
7
|
+
to load and compile a `tileflow.config.ts`.
|
|
8
|
+
|
|
5
9
|
```ts
|
|
6
|
-
import {
|
|
10
|
+
import {createTileflowBuildArtifacts} from '@tileflow/dev';
|
|
7
11
|
|
|
8
12
|
const artifacts = await createTileflowBuildArtifacts({
|
|
9
|
-
config:
|
|
13
|
+
config: 'tileflow.config.ts',
|
|
10
14
|
});
|
|
11
15
|
```
|
|
12
16
|
|
|
17
|
+
If a map uses `icons: './icons/brand'`, build artifacts include the generated
|
|
18
|
+
MapLibre `sprite.json`, `sprite.png`, `sprite@2x.json`, and `sprite@2x.png`
|
|
19
|
+
assets alongside the styles.
|
|
20
|
+
|
|
21
|
+
This package is public, but it is an integration layer. Map styling primitives live in
|
|
22
|
+
`@tileflow/core`; React rendering lives in `@tileflow/react`.
|
|
23
|
+
|
|
13
24
|
Docs: https://tileflow.dev/docs
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
|
+
import { IncomingMessage, ServerResponse } from 'node:http';
|
|
1
2
|
import { TileflowManifest, MapLibreStyle, ValidationMessage, TileflowProjectConfig, TileflowStyleOptions } from '@tileflow/core';
|
|
2
3
|
|
|
4
|
+
type TileflowBuildAsset = {
|
|
5
|
+
contentType: string;
|
|
6
|
+
fileName: string;
|
|
7
|
+
source: string | Uint8Array;
|
|
8
|
+
};
|
|
9
|
+
|
|
3
10
|
declare const defaultTileflowApiUrl = "https://api.tileflow.dev";
|
|
4
11
|
declare const defaultTileflowConfigPath = "tileflow.config.ts";
|
|
5
12
|
declare const defaultTileflowManifestPath = "public/tileflow/manifest.json";
|
|
@@ -21,14 +28,17 @@ type TileflowDevRequestHandlerOptions = {
|
|
|
21
28
|
tileBaseUrl?: string;
|
|
22
29
|
};
|
|
23
30
|
type TileflowBuildArtifactsOptions = {
|
|
31
|
+
assetBaseUrl?: string;
|
|
24
32
|
config?: string;
|
|
25
33
|
cwd?: string;
|
|
26
34
|
styleBaseUrl?: string;
|
|
27
35
|
tileBaseUrl?: string;
|
|
28
36
|
};
|
|
29
37
|
type TileflowBuildArtifacts = {
|
|
38
|
+
assets: TileflowBuildAsset[];
|
|
30
39
|
manifest: TileflowManifest;
|
|
31
40
|
styles: Record<string, MapLibreStyle>;
|
|
41
|
+
watchPaths: string[];
|
|
32
42
|
};
|
|
33
43
|
type WriteTileflowBuildArtifactsOptions = TileflowBuildArtifactsOptions & {
|
|
34
44
|
outDir: string;
|
|
@@ -47,8 +57,15 @@ declare function getTileflowStyleMapName(path: string): string;
|
|
|
47
57
|
declare function createTileflowManifest(project: TileflowProjectConfig, options?: TileflowManifestOptions): TileflowManifest;
|
|
48
58
|
declare function createTileflowStyle(project: TileflowProjectConfig, mapName: string, options?: TileflowStyleOptions): MapLibreStyle;
|
|
49
59
|
declare function createTileflowBuildArtifacts(options?: TileflowBuildArtifactsOptions): Promise<TileflowBuildArtifacts>;
|
|
60
|
+
declare function getTileflowWatchPaths(options?: Pick<TileflowBuildArtifactsOptions, 'config' | 'cwd'>): Promise<string[]>;
|
|
50
61
|
declare function writeTileflowBuildArtifacts(options: WriteTileflowBuildArtifactsOptions): Promise<TileflowBuildArtifacts>;
|
|
62
|
+
declare function isTileflowRequestUrl(url: string | undefined, basePath: string): boolean;
|
|
63
|
+
declare function createTileflowNodeRequest(request: IncomingMessage): Request;
|
|
64
|
+
declare function writeTileflowNodeResponse(response: ServerResponse, tileflowResponse: Response): Promise<void>;
|
|
65
|
+
declare function joinTileflowPublicUrl(publicBase: string, basePath: string): string;
|
|
66
|
+
declare function getTileflowAssetBasePath(basePath: string): string;
|
|
67
|
+
declare function getTileflowAssetFileName(basePath: string, fileName: string): string;
|
|
51
68
|
declare function createTileflowDevRequestHandler(options?: TileflowDevRequestHandlerOptions): (request: Request) => Promise<Response>;
|
|
52
69
|
declare function normalizeTileflowBasePath(value?: string): string;
|
|
53
70
|
|
|
54
|
-
export { type LoadTileflowConfigOptions, type TileflowBuildArtifacts, type TileflowBuildArtifactsOptions, type TileflowDevRequestHandlerOptions, type TileflowManifestOptions, TileflowValidationError, type WriteTileflowBuildArtifactsOptions, asTileflowProjectConfig, assertValidTileflowConfig, createTileflowBuildArtifacts, createTileflowDevRequestHandler, createTileflowManifest, createTileflowStyle, defaultTileflowApiUrl, defaultTileflowConfigPath, defaultTileflowManifestPath, defaultTileflowTileset, getFirstTileflowMapName, getTileflowMapNames, getTileflowStyleMapName, loadTileflowConfig, loadValidTileflowConfig, normalizeTileflowBasePath, writeTileflowBuildArtifacts };
|
|
71
|
+
export { type LoadTileflowConfigOptions, type TileflowBuildArtifacts, type TileflowBuildArtifactsOptions, type TileflowDevRequestHandlerOptions, type TileflowManifestOptions, TileflowValidationError, type WriteTileflowBuildArtifactsOptions, asTileflowProjectConfig, assertValidTileflowConfig, createTileflowBuildArtifacts, createTileflowDevRequestHandler, createTileflowManifest, createTileflowNodeRequest, createTileflowStyle, defaultTileflowApiUrl, defaultTileflowConfigPath, defaultTileflowManifestPath, defaultTileflowTileset, getFirstTileflowMapName, getTileflowAssetBasePath, getTileflowAssetFileName, getTileflowMapNames, getTileflowStyleMapName, getTileflowWatchPaths, isTileflowRequestUrl, joinTileflowPublicUrl, loadTileflowConfig, loadValidTileflowConfig, normalizeTileflowBasePath, writeTileflowBuildArtifacts, writeTileflowNodeResponse };
|
package/dist/index.js
CHANGED
|
@@ -1,13 +1,206 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
|
+
import { createJiti } from "jiti";
|
|
3
|
+
import { existsSync as existsSync2 } from "fs";
|
|
4
|
+
import { mkdir, writeFile } from "fs/promises";
|
|
5
|
+
import { dirname, resolve as resolve2 } from "path";
|
|
2
6
|
import {
|
|
3
7
|
createManifest,
|
|
4
8
|
createStyleFromProject,
|
|
5
9
|
validateConfig
|
|
6
10
|
} from "@tileflow/core";
|
|
11
|
+
|
|
12
|
+
// src/icons.ts
|
|
7
13
|
import { existsSync } from "fs";
|
|
8
|
-
import {
|
|
9
|
-
import { resolve } from "path";
|
|
10
|
-
|
|
14
|
+
import { readdir } from "fs/promises";
|
|
15
|
+
import { extname, resolve } from "path";
|
|
16
|
+
var iconFileExtensions = /* @__PURE__ */ new Set([".svg", ".png", ".jpg", ".jpeg", ".webp"]);
|
|
17
|
+
var iconSpriteSize = 24;
|
|
18
|
+
async function prepareTileflowProjectIcons(project, options) {
|
|
19
|
+
const nextProject = cloneProject(project);
|
|
20
|
+
const localSources = getLocalIconSources(project, options.cwd);
|
|
21
|
+
for (const localSource of localSources) {
|
|
22
|
+
const mapConfig = nextProject.maps[localSource.mapName];
|
|
23
|
+
if (!mapConfig) {
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
const iconSet = resolveIconSet(mapConfig.icons, nextProject.icons);
|
|
27
|
+
if (!iconSet) {
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
const spriteUrl = joinUrl(options.assetBaseUrl, `icons/${localSource.mapName}/sprite`);
|
|
31
|
+
mapConfig.icons = {
|
|
32
|
+
...iconSet,
|
|
33
|
+
sprite: spriteUrl
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
if (localSources.length === 0) {
|
|
37
|
+
return {
|
|
38
|
+
assets: [],
|
|
39
|
+
project: nextProject,
|
|
40
|
+
watchPaths: []
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
const assets = (await Promise.all(
|
|
44
|
+
localSources.map(async (source) => buildSpriteAssets(source.sourceDir, source.mapName))
|
|
45
|
+
)).flat();
|
|
46
|
+
return {
|
|
47
|
+
assets,
|
|
48
|
+
project: nextProject,
|
|
49
|
+
watchPaths: localSources.map((source) => source.sourceDir)
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
function getTileflowIconWatchPaths(project, cwd) {
|
|
53
|
+
return uniqueStrings(getLocalIconSources(project, cwd).map((source) => source.sourceDir));
|
|
54
|
+
}
|
|
55
|
+
function getLocalIconSources(project, cwd) {
|
|
56
|
+
return Object.entries(project.maps).flatMap(([mapName, mapConfig]) => {
|
|
57
|
+
const iconSet = resolveIconSet(mapConfig.icons, project.icons);
|
|
58
|
+
const sourceDir = resolveLocalIconSource(iconSet, cwd);
|
|
59
|
+
return sourceDir ? [{ mapName, sourceDir }] : [];
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
function resolveLocalIconSource(iconSet, cwd) {
|
|
63
|
+
if (!iconSet?.source) {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
return resolve(cwd, iconSet.source);
|
|
67
|
+
}
|
|
68
|
+
function resolveIconSet(icons, iconSets, path = []) {
|
|
69
|
+
if (!icons) {
|
|
70
|
+
return void 0;
|
|
71
|
+
}
|
|
72
|
+
if (typeof icons === "string") {
|
|
73
|
+
const referenced = iconSets?.[icons];
|
|
74
|
+
if (referenced) {
|
|
75
|
+
if (path.includes(icons)) {
|
|
76
|
+
throw new Error(`Circular Tileflow icon set extends: ${[...path, icons].join(" -> ")}`);
|
|
77
|
+
}
|
|
78
|
+
return resolveIconSet(referenced, iconSets, [...path, icons]);
|
|
79
|
+
}
|
|
80
|
+
return isRemoteSpriteReference(icons) ? { sprite: icons } : { source: icons };
|
|
81
|
+
}
|
|
82
|
+
const extended = icons.extends ? resolveIconSet(icons.extends, iconSets, path) : void 0;
|
|
83
|
+
return {
|
|
84
|
+
...extended,
|
|
85
|
+
...icons,
|
|
86
|
+
mapping: {
|
|
87
|
+
...extended?.mapping,
|
|
88
|
+
...icons.mapping
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
async function buildSpriteAssets(sourceDir, mapName) {
|
|
93
|
+
const icons = await readIconFiles(sourceDir);
|
|
94
|
+
if (icons.length === 0) {
|
|
95
|
+
throw new Error(`No SVG or PNG icons found in ${sourceDir}.`);
|
|
96
|
+
}
|
|
97
|
+
const [sprite, sprite2x] = await Promise.all([
|
|
98
|
+
buildSprite(sourceDir, icons, 1),
|
|
99
|
+
buildSprite(sourceDir, icons, 2)
|
|
100
|
+
]);
|
|
101
|
+
return [
|
|
102
|
+
jsonAsset(`icons/${mapName}/sprite.json`, sprite.index),
|
|
103
|
+
binaryAsset(`icons/${mapName}/sprite.png`, sprite.image, "image/png"),
|
|
104
|
+
jsonAsset(`icons/${mapName}/sprite@2x.json`, sprite2x.index),
|
|
105
|
+
binaryAsset(`icons/${mapName}/sprite@2x.png`, sprite2x.image, "image/png")
|
|
106
|
+
];
|
|
107
|
+
}
|
|
108
|
+
async function readIconFiles(sourceDir) {
|
|
109
|
+
if (!existsSync(sourceDir)) {
|
|
110
|
+
throw new Error(`Icon source not found: ${sourceDir}`);
|
|
111
|
+
}
|
|
112
|
+
const entries = await readdir(sourceDir, { withFileTypes: true });
|
|
113
|
+
return entries.filter((entry) => entry.isFile() && iconFileExtensions.has(extname(entry.name).toLowerCase())).map((entry) => ({
|
|
114
|
+
name: entry.name.slice(0, -extname(entry.name).length),
|
|
115
|
+
path: resolve(sourceDir, entry.name)
|
|
116
|
+
})).sort((left, right) => left.name.localeCompare(right.name));
|
|
117
|
+
}
|
|
118
|
+
async function buildSprite(sourceDir, icons, pixelRatio) {
|
|
119
|
+
const sharp = await loadSharp();
|
|
120
|
+
const cellSize = iconSpriteSize * pixelRatio;
|
|
121
|
+
const columns = Math.ceil(Math.sqrt(icons.length));
|
|
122
|
+
const rows = Math.ceil(icons.length / columns);
|
|
123
|
+
const width = columns * cellSize;
|
|
124
|
+
const height = rows * cellSize;
|
|
125
|
+
const index = {};
|
|
126
|
+
const composite = await Promise.all(
|
|
127
|
+
icons.map(async (icon, indexNumber) => {
|
|
128
|
+
const column = indexNumber % columns;
|
|
129
|
+
const row = Math.floor(indexNumber / columns);
|
|
130
|
+
const left = column * cellSize;
|
|
131
|
+
const top = row * cellSize;
|
|
132
|
+
index[icon.name] = {
|
|
133
|
+
height: cellSize,
|
|
134
|
+
pixelRatio,
|
|
135
|
+
width: cellSize,
|
|
136
|
+
x: left,
|
|
137
|
+
y: top
|
|
138
|
+
};
|
|
139
|
+
return {
|
|
140
|
+
input: await sharp(icon.path, { density: 72 * pixelRatio }).resize(cellSize, cellSize, {
|
|
141
|
+
background: { alpha: 0, b: 0, g: 0, r: 0 },
|
|
142
|
+
fit: "contain"
|
|
143
|
+
}).png().toBuffer(),
|
|
144
|
+
left,
|
|
145
|
+
top
|
|
146
|
+
};
|
|
147
|
+
})
|
|
148
|
+
);
|
|
149
|
+
const image = await sharp({
|
|
150
|
+
create: {
|
|
151
|
+
background: { alpha: 0, b: 0, g: 0, r: 0 },
|
|
152
|
+
channels: 4,
|
|
153
|
+
height,
|
|
154
|
+
width
|
|
155
|
+
}
|
|
156
|
+
}).composite(composite).png().toBuffer();
|
|
157
|
+
return {
|
|
158
|
+
image,
|
|
159
|
+
index,
|
|
160
|
+
sourceDir
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
async function loadSharp() {
|
|
164
|
+
try {
|
|
165
|
+
return (await import("sharp")).default;
|
|
166
|
+
} catch (error) {
|
|
167
|
+
throw new Error(
|
|
168
|
+
'Local icon sprites require the optional "sharp" package. Install sharp or disable local icons.',
|
|
169
|
+
{ cause: error }
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
function jsonAsset(fileName, value) {
|
|
174
|
+
return {
|
|
175
|
+
contentType: "application/json; charset=utf-8",
|
|
176
|
+
fileName,
|
|
177
|
+
source: `${JSON.stringify(value, null, 2)}
|
|
178
|
+
`
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
function binaryAsset(fileName, source, contentType) {
|
|
182
|
+
return {
|
|
183
|
+
contentType,
|
|
184
|
+
fileName,
|
|
185
|
+
source
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
function cloneProject(project) {
|
|
189
|
+
return JSON.parse(JSON.stringify(project));
|
|
190
|
+
}
|
|
191
|
+
function isRemoteSpriteReference(value) {
|
|
192
|
+
return value.startsWith("http://") || value.startsWith("https://") || value.startsWith("/") || value.startsWith("data:");
|
|
193
|
+
}
|
|
194
|
+
function joinUrl(base, path) {
|
|
195
|
+
const trimmedBase = base.replace(/\/+$/g, "");
|
|
196
|
+
const trimmedPath = path.replace(/^\/+/g, "");
|
|
197
|
+
return trimmedBase ? `${trimmedBase}/${trimmedPath}` : trimmedPath;
|
|
198
|
+
}
|
|
199
|
+
function uniqueStrings(values) {
|
|
200
|
+
return [...new Set(values)];
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// src/index.ts
|
|
11
204
|
var defaultTileflowApiUrl = "https://api.tileflow.dev";
|
|
12
205
|
var defaultTileflowConfigPath = "tileflow.config.ts";
|
|
13
206
|
var defaultTileflowManifestPath = "public/tileflow/manifest.json";
|
|
@@ -22,8 +215,8 @@ var TileflowValidationError = class extends Error {
|
|
|
22
215
|
};
|
|
23
216
|
async function loadTileflowConfig(configPath = defaultTileflowConfigPath, options = {}) {
|
|
24
217
|
const cwd = options.cwd ?? process.cwd();
|
|
25
|
-
const resolvedPath =
|
|
26
|
-
if (!
|
|
218
|
+
const resolvedPath = resolve2(cwd, configPath);
|
|
219
|
+
if (!existsSync2(resolvedPath)) {
|
|
27
220
|
throw new Error(`Config not found: ${configPath}`);
|
|
28
221
|
}
|
|
29
222
|
const jiti = createJiti(import.meta.url, {
|
|
@@ -81,50 +274,104 @@ function createTileflowStyle(project, mapName, options = {}) {
|
|
|
81
274
|
return createStyleFromProject(project, mapName, options);
|
|
82
275
|
}
|
|
83
276
|
async function createTileflowBuildArtifacts(options = {}) {
|
|
84
|
-
const project = await loadValidTileflowConfig(
|
|
85
|
-
options.
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
277
|
+
const project = await loadValidTileflowConfig(options.config ?? defaultTileflowConfigPath, {
|
|
278
|
+
cwd: options.cwd,
|
|
279
|
+
fresh: true
|
|
280
|
+
});
|
|
281
|
+
const prepared = await prepareTileflowProjectIcons(project, {
|
|
282
|
+
assetBaseUrl: resolveAssetBaseUrl(options),
|
|
283
|
+
cwd: options.cwd ?? process.cwd()
|
|
284
|
+
});
|
|
91
285
|
const styleOptions = { tileBaseUrl: options.tileBaseUrl };
|
|
92
286
|
const styles = Object.fromEntries(
|
|
93
|
-
getTileflowMapNames(project).map((mapName) => [
|
|
287
|
+
getTileflowMapNames(prepared.project).map((mapName) => [
|
|
94
288
|
mapName,
|
|
95
|
-
createStyleFromProject(project, mapName, styleOptions)
|
|
289
|
+
createStyleFromProject(prepared.project, mapName, styleOptions)
|
|
96
290
|
])
|
|
97
291
|
);
|
|
98
292
|
return {
|
|
99
|
-
|
|
293
|
+
assets: prepared.assets,
|
|
294
|
+
manifest: createManifest(prepared.project, {
|
|
100
295
|
styleBaseUrl: options.styleBaseUrl,
|
|
101
296
|
tileBaseUrl: options.tileBaseUrl
|
|
102
297
|
}),
|
|
103
|
-
styles
|
|
298
|
+
styles,
|
|
299
|
+
watchPaths: prepared.watchPaths
|
|
104
300
|
};
|
|
105
301
|
}
|
|
302
|
+
async function getTileflowWatchPaths(options = {}) {
|
|
303
|
+
const cwd = options.cwd ?? process.cwd();
|
|
304
|
+
const project = await loadValidTileflowConfig(options.config ?? defaultTileflowConfigPath, {
|
|
305
|
+
cwd,
|
|
306
|
+
fresh: true
|
|
307
|
+
});
|
|
308
|
+
return getTileflowIconWatchPaths(project, cwd);
|
|
309
|
+
}
|
|
106
310
|
async function writeTileflowBuildArtifacts(options) {
|
|
107
311
|
const cwd = options.cwd ?? process.cwd();
|
|
108
|
-
const outDir =
|
|
109
|
-
const stylesDir =
|
|
312
|
+
const outDir = resolve2(cwd, options.outDir);
|
|
313
|
+
const stylesDir = resolve2(outDir, "styles");
|
|
110
314
|
const artifacts = await createTileflowBuildArtifacts(options);
|
|
111
315
|
await mkdir(stylesDir, { recursive: true });
|
|
112
316
|
for (const [mapName, style] of Object.entries(artifacts.styles)) {
|
|
113
317
|
await writeFile(
|
|
114
|
-
|
|
318
|
+
resolve2(stylesDir, `${mapName}.json`),
|
|
115
319
|
`${JSON.stringify(style, null, 2)}
|
|
116
320
|
`,
|
|
117
321
|
"utf8"
|
|
118
322
|
);
|
|
119
323
|
}
|
|
324
|
+
for (const asset of artifacts.assets) {
|
|
325
|
+
const outputPath = resolve2(outDir, asset.fileName);
|
|
326
|
+
await mkdir(dirname(outputPath), { recursive: true });
|
|
327
|
+
await writeFile(outputPath, asset.source);
|
|
328
|
+
}
|
|
120
329
|
await writeFile(
|
|
121
|
-
|
|
330
|
+
resolve2(outDir, "manifest.json"),
|
|
122
331
|
`${JSON.stringify(artifacts.manifest, null, 2)}
|
|
123
332
|
`,
|
|
124
333
|
"utf8"
|
|
125
334
|
);
|
|
126
335
|
return artifacts;
|
|
127
336
|
}
|
|
337
|
+
function isTileflowRequestUrl(url, basePath) {
|
|
338
|
+
const pathname = (url ?? "/").split("?")[0] ?? "/";
|
|
339
|
+
if (!basePath) {
|
|
340
|
+
return pathname === "/manifest.json" || pathname === "/style.json" || pathname === "/" || pathname.startsWith("/icons/") || pathname.startsWith("/styles/");
|
|
341
|
+
}
|
|
342
|
+
return pathname === basePath || pathname.startsWith(`${basePath}/`);
|
|
343
|
+
}
|
|
344
|
+
function createTileflowNodeRequest(request) {
|
|
345
|
+
const protocol = getNodeHeader(request, "x-forwarded-proto") ?? "http";
|
|
346
|
+
const host = getNodeHeader(request, "host") ?? "localhost";
|
|
347
|
+
return new Request(`${protocol}://${host}${request.url ?? "/"}`, {
|
|
348
|
+
method: request.method
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
async function writeTileflowNodeResponse(response, tileflowResponse) {
|
|
352
|
+
response.statusCode = tileflowResponse.status;
|
|
353
|
+
tileflowResponse.headers.forEach((value, key) => {
|
|
354
|
+
response.setHeader(key, value);
|
|
355
|
+
});
|
|
356
|
+
response.end(Buffer.from(await tileflowResponse.arrayBuffer()));
|
|
357
|
+
}
|
|
358
|
+
function joinTileflowPublicUrl(publicBase, basePath) {
|
|
359
|
+
const trimmedBase = publicBase.replace(/\/+$/g, "");
|
|
360
|
+
const trimmedPath = basePath.replace(/^\/+|\/+$/g, "");
|
|
361
|
+
if (!trimmedPath) {
|
|
362
|
+
return trimmedBase || "";
|
|
363
|
+
}
|
|
364
|
+
if (!trimmedBase || trimmedBase === ".") {
|
|
365
|
+
return `/${trimmedPath}`;
|
|
366
|
+
}
|
|
367
|
+
return `${trimmedBase}/${trimmedPath}`;
|
|
368
|
+
}
|
|
369
|
+
function getTileflowAssetBasePath(basePath) {
|
|
370
|
+
return basePath.replace(/^\/+|\/+$/g, "");
|
|
371
|
+
}
|
|
372
|
+
function getTileflowAssetFileName(basePath, fileName) {
|
|
373
|
+
return basePath ? `${basePath}/${fileName}` : fileName;
|
|
374
|
+
}
|
|
128
375
|
function createTileflowDevRequestHandler(options = {}) {
|
|
129
376
|
const basePath = normalizeTileflowBasePath(options.basePath);
|
|
130
377
|
const configPath = options.config ?? defaultTileflowConfigPath;
|
|
@@ -136,6 +383,16 @@ function createTileflowDevRequestHandler(options = {}) {
|
|
|
136
383
|
}
|
|
137
384
|
const url = new URL(request.url);
|
|
138
385
|
const path = stripTileflowBasePath(url.pathname, basePath);
|
|
386
|
+
const loadPreparedProject = async () => {
|
|
387
|
+
const project = await loadValidTileflowConfig(configPath, {
|
|
388
|
+
cwd,
|
|
389
|
+
fresh: true
|
|
390
|
+
});
|
|
391
|
+
return prepareTileflowProjectIcons(project, {
|
|
392
|
+
assetBaseUrl: `${url.origin}${basePath}`,
|
|
393
|
+
cwd
|
|
394
|
+
});
|
|
395
|
+
};
|
|
139
396
|
if (path === null) {
|
|
140
397
|
return jsonResponse({ error: "Not found" }, 404);
|
|
141
398
|
}
|
|
@@ -145,9 +402,7 @@ function createTileflowDevRequestHandler(options = {}) {
|
|
|
145
402
|
cwd,
|
|
146
403
|
fresh: true
|
|
147
404
|
});
|
|
148
|
-
return htmlResponse(
|
|
149
|
-
previewHtml(getFirstTileflowMapName(project), basePath)
|
|
150
|
-
);
|
|
405
|
+
return htmlResponse(previewHtml(getFirstTileflowMapName(project), basePath));
|
|
151
406
|
}
|
|
152
407
|
if (path === "/manifest.json") {
|
|
153
408
|
const project = await loadValidTileflowConfig(configPath, {
|
|
@@ -163,31 +418,34 @@ function createTileflowDevRequestHandler(options = {}) {
|
|
|
163
418
|
);
|
|
164
419
|
}
|
|
165
420
|
if (path === "/style.json") {
|
|
166
|
-
const
|
|
167
|
-
cwd,
|
|
168
|
-
fresh: true
|
|
169
|
-
});
|
|
421
|
+
const prepared = await loadPreparedProject();
|
|
170
422
|
return jsonResponse(
|
|
171
|
-
createStyleFromProject(project, getFirstTileflowMapName(project), {
|
|
423
|
+
createStyleFromProject(prepared.project, getFirstTileflowMapName(prepared.project), {
|
|
172
424
|
tileBaseUrl
|
|
173
425
|
})
|
|
174
426
|
);
|
|
175
427
|
}
|
|
176
428
|
if (path.startsWith("/styles/")) {
|
|
177
|
-
const
|
|
178
|
-
cwd,
|
|
179
|
-
fresh: true
|
|
180
|
-
});
|
|
429
|
+
const prepared = await loadPreparedProject();
|
|
181
430
|
const mapName = getTileflowStyleMapName(path);
|
|
182
|
-
if (!project.maps[mapName]) {
|
|
431
|
+
if (!prepared.project.maps[mapName]) {
|
|
183
432
|
return jsonResponse({ error: `Unknown map: ${mapName}` }, 404);
|
|
184
433
|
}
|
|
185
434
|
return jsonResponse(
|
|
186
|
-
createStyleFromProject(project, mapName, {
|
|
435
|
+
createStyleFromProject(prepared.project, mapName, {
|
|
187
436
|
tileBaseUrl
|
|
188
437
|
})
|
|
189
438
|
);
|
|
190
439
|
}
|
|
440
|
+
if (path.startsWith("/icons/")) {
|
|
441
|
+
const prepared = await loadPreparedProject();
|
|
442
|
+
const assetName = path.replace(/^\/+/, "");
|
|
443
|
+
const asset = prepared.assets.find((candidate) => candidate.fileName === assetName);
|
|
444
|
+
if (!asset) {
|
|
445
|
+
return jsonResponse({ error: `Unknown icon asset: ${assetName}` }, 404);
|
|
446
|
+
}
|
|
447
|
+
return assetResponse(asset);
|
|
448
|
+
}
|
|
191
449
|
return jsonResponse({ error: "Not found" }, 404);
|
|
192
450
|
} catch (error) {
|
|
193
451
|
options.onError?.(error);
|
|
@@ -195,6 +453,10 @@ function createTileflowDevRequestHandler(options = {}) {
|
|
|
195
453
|
}
|
|
196
454
|
};
|
|
197
455
|
}
|
|
456
|
+
function getNodeHeader(request, name) {
|
|
457
|
+
const value = request.headers[name];
|
|
458
|
+
return Array.isArray(value) ? value[0] : value;
|
|
459
|
+
}
|
|
198
460
|
function normalizeTileflowBasePath(value = "") {
|
|
199
461
|
const trimmed = value.trim().replace(/^\/+|\/+$/g, "");
|
|
200
462
|
return trimmed ? `/${trimmed}` : "";
|
|
@@ -245,6 +507,28 @@ function htmlResponse(body, status = 200) {
|
|
|
245
507
|
status
|
|
246
508
|
});
|
|
247
509
|
}
|
|
510
|
+
function assetResponse(asset) {
|
|
511
|
+
const body = typeof asset.source === "string" ? asset.source : Buffer.from(asset.source);
|
|
512
|
+
return new Response(body, {
|
|
513
|
+
headers: {
|
|
514
|
+
"Access-Control-Allow-Origin": "*",
|
|
515
|
+
"Cache-Control": "no-store",
|
|
516
|
+
"Content-Type": asset.contentType
|
|
517
|
+
}
|
|
518
|
+
});
|
|
519
|
+
}
|
|
520
|
+
function resolveAssetBaseUrl(options) {
|
|
521
|
+
if (options.assetBaseUrl !== void 0) {
|
|
522
|
+
return options.assetBaseUrl;
|
|
523
|
+
}
|
|
524
|
+
if (options.styleBaseUrl && !isRelativePublicUrl(options.styleBaseUrl)) {
|
|
525
|
+
return options.styleBaseUrl;
|
|
526
|
+
}
|
|
527
|
+
return "..";
|
|
528
|
+
}
|
|
529
|
+
function isRelativePublicUrl(value) {
|
|
530
|
+
return !(value.startsWith("http://") || value.startsWith("https://") || value.startsWith("/") || value.startsWith("data:"));
|
|
531
|
+
}
|
|
248
532
|
function previewHtml(mapName, basePath) {
|
|
249
533
|
const styleUrl = `${basePath}/styles/${mapName}.json`;
|
|
250
534
|
return `<!doctype html>
|
|
@@ -299,16 +583,23 @@ export {
|
|
|
299
583
|
createTileflowBuildArtifacts,
|
|
300
584
|
createTileflowDevRequestHandler,
|
|
301
585
|
createTileflowManifest,
|
|
586
|
+
createTileflowNodeRequest,
|
|
302
587
|
createTileflowStyle,
|
|
303
588
|
defaultTileflowApiUrl,
|
|
304
589
|
defaultTileflowConfigPath,
|
|
305
590
|
defaultTileflowManifestPath,
|
|
306
591
|
defaultTileflowTileset,
|
|
307
592
|
getFirstTileflowMapName,
|
|
593
|
+
getTileflowAssetBasePath,
|
|
594
|
+
getTileflowAssetFileName,
|
|
308
595
|
getTileflowMapNames,
|
|
309
596
|
getTileflowStyleMapName,
|
|
597
|
+
getTileflowWatchPaths,
|
|
598
|
+
isTileflowRequestUrl,
|
|
599
|
+
joinTileflowPublicUrl,
|
|
310
600
|
loadTileflowConfig,
|
|
311
601
|
loadValidTileflowConfig,
|
|
312
602
|
normalizeTileflowBasePath,
|
|
313
|
-
writeTileflowBuildArtifacts
|
|
603
|
+
writeTileflowBuildArtifacts,
|
|
604
|
+
writeTileflowNodeResponse
|
|
314
605
|
};
|
package/package.json
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tileflow/dev",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.11",
|
|
4
4
|
"description": "Node development utilities for Tileflow configs and builds.",
|
|
5
5
|
"type": "module",
|
|
6
|
+
"sideEffects": false,
|
|
6
7
|
"homepage": "https://tileflow.dev",
|
|
7
8
|
"repository": {
|
|
8
9
|
"type": "git",
|
|
@@ -36,10 +37,13 @@
|
|
|
36
37
|
},
|
|
37
38
|
"dependencies": {
|
|
38
39
|
"jiti": "^2.7.0",
|
|
39
|
-
"@tileflow/core": "0.1.0-alpha.
|
|
40
|
+
"@tileflow/core": "0.1.0-alpha.11"
|
|
41
|
+
},
|
|
42
|
+
"optionalDependencies": {
|
|
43
|
+
"sharp": "^0.34.5"
|
|
40
44
|
},
|
|
41
45
|
"scripts": {
|
|
42
|
-
"build": "tsup src/index.ts --format esm --dts --clean --platform node",
|
|
46
|
+
"build": "tsup src/index.ts --format esm --dts --clean --platform node --external sharp",
|
|
43
47
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
44
48
|
"lint": "tsc -p tsconfig.json --noEmit"
|
|
45
49
|
}
|