@mapsight/traffic-style 5.0.1 → 5.0.3

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.
@@ -1,292 +0,0 @@
1
- #!/usr/bin/env node
2
- import * as fs from "node:fs/promises";
3
- import * as path from "node:path";
4
- import {parseArgs} from "node:util";
5
-
6
- import {watch} from "chokidar";
7
- import * as fse from "fs-extra";
8
- import sharp from "sharp";
9
-
10
- import {
11
- DistMetaDataSchema,
12
- type IconGroupName,
13
- type IconVariant,
14
- type MetaData,
15
- } from "./lib/meta.ts";
16
-
17
- interface Tile {
18
- name: string;
19
- info: {
20
- width: number;
21
- height: number;
22
- };
23
- buffer: Buffer;
24
- }
25
-
26
- interface LayoutItem {
27
- width: number;
28
- height: number;
29
- x: number;
30
- y: number;
31
- meta: Tile;
32
- }
33
-
34
- interface Options {
35
- srcDir: string;
36
- spriteName: string;
37
- metaPath: string;
38
- outputScss: string;
39
- outputImg: string;
40
- groups: IconGroupName[];
41
- variants: IconVariant[];
42
- filetype: string;
43
- margin: number;
44
- overridesPath?: string;
45
- }
46
-
47
- const calcGlobs = (
48
- groups: IconGroupName[],
49
- metaData: MetaData,
50
- variants: IconVariant[],
51
- fileType: string,
52
- ): string[] => {
53
- const globs: string[] = [];
54
- metaData.icons.forEach((iconMeta) =>
55
- groups.forEach((group: IconGroupName) => {
56
- if (iconMeta.groups && iconMeta.groups.includes(group)) {
57
- variants
58
- .map(
59
- (variant: string) =>
60
- `**/${iconMeta.id}-${variant}.${fileType}`,
61
- )
62
- .forEach((glob: string) => {
63
- globs.push(glob);
64
- });
65
- }
66
- }),
67
- );
68
- return globs;
69
- };
70
-
71
- async function buildIcons(opts: Options): Promise<void> {
72
- const {
73
- srcDir,
74
- spriteName,
75
- metaPath,
76
- outputScss,
77
- outputImg,
78
- groups = ["default"],
79
- variants = ["default", "small", "xsmall"],
80
- filetype = "png",
81
- margin = 4,
82
- overridesPath,
83
- } = opts;
84
-
85
- const absMetaPath = path.resolve(metaPath);
86
- if (!(await fse.pathExists(absMetaPath))) {
87
- throw new Error(`Missing metadata at ${absMetaPath}`);
88
- }
89
- const metaDataStr = await fs.readFile(absMetaPath, {encoding: "utf-8"});
90
- const metaData = DistMetaDataSchema.parse(JSON.parse(metaDataStr));
91
-
92
- let globs = calcGlobs(groups, metaData, variants, filetype);
93
- if (groups.length === 0) {
94
- globs = variants.map((variant) => `**/*-${variant}.${filetype}`);
95
- }
96
-
97
- const srcFiles = [];
98
- for (const pattern of globs) {
99
- for await (const entry of fs.glob(pattern, {
100
- cwd: srcDir,
101
- })) {
102
- srcFiles.push(path.resolve(srcDir, entry));
103
- }
104
- }
105
- console.log(`Found ${srcFiles.length} icon files matching globs`);
106
-
107
- const tmpDir = path.resolve("./tmp", spriteName);
108
- await fse.ensureDir(tmpDir);
109
-
110
- let copiedCount = 0;
111
- for (const srcFile of srcFiles) {
112
- const relPath = path.relative(srcDir, srcFile);
113
- const destFile = path.join(tmpDir, relPath);
114
- await fse.ensureDir(path.dirname(destFile));
115
-
116
- const doCopy = async () => {
117
- await fse.copy(srcFile, destFile);
118
- copiedCount++;
119
- };
120
-
121
- if (await fse.pathExists(destFile)) {
122
- const srcStat = await fs.stat(srcFile);
123
- const destStat = await fs.stat(destFile);
124
- if (destStat.mtime > srcStat.mtime) {
125
- continue;
126
- }
127
- }
128
- await doCopy();
129
- }
130
- console.log(`Copied ${copiedCount} icons to ${tmpDir}`);
131
-
132
- if (overridesPath) {
133
- const absOverrides = path.resolve(overridesPath);
134
- const overrideFiles = [];
135
- for (const pattern of globs) {
136
- for await (const entry of fs.glob(pattern, {
137
- cwd: absOverrides,
138
- })) {
139
- overrideFiles.push(path.resolve(absOverrides, entry));
140
- }
141
- }
142
- for (const ovFile of overrideFiles) {
143
- const relPath = path.relative(absOverrides, ovFile);
144
- const destFile = path.join(tmpDir, relPath);
145
- await fse.ensureDir(path.dirname(destFile));
146
- await fse.copy(ovFile, destFile);
147
- }
148
- console.log(`Copied overrides from ${overridesPath}`);
149
- }
150
-
151
- const tmpFiles = [];
152
- for await (const entry of fs.glob("**/*", {
153
- cwd: tmpDir,
154
- })) {
155
- tmpFiles.push(entry);
156
- }
157
- const tiles: Tile[] = [];
158
- for (const rel of tmpFiles) {
159
- const fullPath = path.join(tmpDir, rel);
160
- const stat = await fs.stat(fullPath);
161
- if (!stat.isFile()) continue;
162
- const buffer = await fs.readFile(fullPath);
163
- const metadata = await sharp(buffer).metadata();
164
- if (!metadata.width || !metadata.height) continue;
165
- const name = path.parse(rel).name;
166
- tiles.push({
167
- name,
168
- info: {width: metadata.width, height: metadata.height},
169
- buffer,
170
- });
171
- }
172
-
173
- const marginNum = Number(margin);
174
- let items: LayoutItem[] = tiles.map((tile) => ({
175
- width: tile.info.width + 2 * marginNum,
176
- height: tile.info.height + 2 * marginNum,
177
- meta: tile,
178
- x: 0,
179
- y: 0,
180
- }));
181
-
182
- items = items.sort((a, b) => a.height - b.height);
183
-
184
- let currentY = 0;
185
- for (const item of items) {
186
- item.x = 0;
187
- item.y = currentY;
188
- currentY += item.height;
189
- }
190
-
191
- const layoutWidth =
192
- items.length > 0 ? Math.max(...items.map((i) => i.width)) : 0;
193
- const layoutHeight = currentY;
194
-
195
- // SCSS
196
- const absScssDir = path.resolve(outputScss);
197
- await fse.ensureDir(absScssDir);
198
- const scssFile = path.join(absScssDir, `${spriteName}.scss`);
199
- let scssContent = `$${spriteName}: (\n`;
200
- for (const item of items) {
201
- scssContent += ` "${item.meta.name}": (
202
- x: ${item.x},
203
- y: ${item.y},
204
- width: ${item.width},
205
- height: ${item.height},
206
- ),
207
- `;
208
- }
209
- scssContent += `);\n`;
210
- await fs.writeFile(scssFile, scssContent, "utf-8");
211
- console.log(`Generated SCSS: ${scssFile}`);
212
-
213
- // PNG
214
- const absImgDir = path.resolve(outputImg);
215
- await fse.ensureDir(absImgDir);
216
- const pngFile = path.join(absImgDir, `${spriteName}.png`);
217
- const spriteSharp = sharp({
218
- create: {
219
- width: layoutWidth,
220
- height: layoutHeight,
221
- channels: 4,
222
- background: {r: 0, g: 0, b: 0, alpha: 0},
223
- },
224
- });
225
- const composites = items.map((item) => ({
226
- input: item.meta.buffer,
227
- left: item.x,
228
- top: item.y,
229
- width: item.width,
230
- height: item.height,
231
- }));
232
- await spriteSharp.composite(composites).png({effort: 10}).toFile(pngFile);
233
- console.log(`Generated PNG: ${pngFile} (${layoutWidth}x${layoutHeight})`);
234
- }
235
-
236
- const debounceAsync = (fn: () => Promise<void>, delay: number) => {
237
- let timer: NodeJS.Timeout;
238
- return () => {
239
- clearTimeout(timer);
240
- timer = setTimeout(() => {
241
- fn().catch(console.error);
242
- }, delay);
243
- };
244
- };
245
-
246
- const {values: options, positionals} = parseArgs({
247
- options: {
248
- "sprite-name": {type: "string", short: "n"},
249
- "meta-path": {type: "string", short: "m"},
250
- "output-scss": {type: "string", short: "s"},
251
- "output-img": {type: "string", short: "i"},
252
- groups: {type: "string", short: "g"},
253
- variants: {type: "string", short: "v"},
254
- filetype: {type: "string", short: "f"},
255
- margin: {type: "string"},
256
- overrides: {type: "string", short: "o"},
257
- watch: {type: "boolean", short: "w"},
258
- },
259
- allowPositionals: true,
260
- });
261
-
262
- const srcDir = positionals[0] ?? "src/img/mapsight-icons-2x/";
263
- const absOpts: Options = {
264
- srcDir: path.resolve(srcDir),
265
- spriteName:
266
- options["sprite-name"] ?? "mapsight-traffic-style-icon-sprite-2x",
267
- metaPath: options["meta-path"] ?? "dist/meta.json",
268
- outputScss: options["output-scss"] ?? "dist/",
269
- outputImg: options["output-img"] ?? "dist/img/",
270
- groups: (options.groups?.split(",") as IconGroupName[]) ?? ["default"],
271
- variants: (options.variants?.split(",") as IconVariant[]) ?? [
272
- "default",
273
- "small",
274
- "xsmall",
275
- ],
276
- filetype: options.filetype ?? "png",
277
- margin: parseInt(options.margin ?? "4", 10),
278
- overridesPath: options.overrides,
279
- };
280
-
281
- const runBuild = () => buildIcons(absOpts);
282
-
283
- await runBuild();
284
-
285
- if (options.watch) {
286
- const debouncedBuild = debounceAsync(runBuild, 300);
287
- const watchPaths = [absOpts.srcDir, absOpts.metaPath];
288
- if (absOpts.overridesPath)
289
- watchPaths.push(path.resolve(absOpts.overridesPath));
290
- watch(watchPaths, {ignored: /node_modules|tmp/}).on("all", debouncedBuild);
291
- console.log(`Watching ${watchPaths.join(", ")}...`);
292
- }