trackasia-gl
Version:
BSD licensed community fork of mapbox-gl, a WebGL interactive maps library
402 lines (335 loc) • 15.3 kB
text/typescript
import fs from 'fs';
import path from 'path';
import typedocConfig from '../typedoc.json' with {type: 'json'};
import packageJson from '../package.json' with {type: 'json'};
import {get} from 'https';
import sharp from 'sharp';
import yaml from 'js-yaml';
import 'dotenv/config';
type CoutryCodeType = 'VN' | 'SG' | 'TH' | 'MY' | 'TW';
type AllowedLangCodes = 'vi' | 'en' | 'th' | 'tw';
type ExampleSpecificTranslation = {
title: string;
description: string;
};
type LanguageTranslations = {
[exampleFileName: string]: ExampleSpecificTranslation;
};
type exampleTranslationsType = {
[key in AllowedLangCodes]: LanguageTranslations;
};
type HtmlDoc = {
title: string;
description: string;
mdFileName: string;
};
const COUNTRY_CODE = process.env.COUNTRY_CODE as CoutryCodeType;
if (!COUNTRY_CODE) {
throw new Error('Environment variable COUNTRY_CODE is not set.');
}
function loadTranslations(translationsDir: string): any {
const allTranslations: any = {};
try {
const files = fs.readdirSync(translationsDir);
files.forEach(file => {
const filePath = path.join(translationsDir, file);
if ((path.extname(file) === '.yml' || path.extname(file) === '.yaml') && fs.statSync(filePath).isFile()) {
const langCode = path.basename(file, path.extname(file)); // e.g., "vi" from "vi.yml"
try {
const fileContent = fs.readFileSync(filePath, 'utf8');
allTranslations[langCode] = yaml.load(fileContent);
} catch (e) {
console.error(`Error loading or parsing translation file ${filePath}:`, e);
}
}
});
} catch (e) {
console.error(`Error reading translations directory ${translationsDir}:`, e);
}
return allTranslations;
}
const translationsDir = path.join('build', 'translations');
const exampleTranslations: exampleTranslationsType = loadTranslations(translationsDir);
function generateAPIIntroMarkdown(lines: string[]): string {
let intro = `# Intro
This file is intended as a reference for the important and public classes of this API.
We recommend looking at the [examples](../examples/index.md) as they will help you the most to start with TrackAsia.
Most of the classes written here have an "Options" object for initialization, it is recommended to check which options exist.
It is recommended to import what you need and the use it. Some examples for classes assume you did that.
For example, import the \`Map\` class like this:
\`\`\`ts
import {Map} from 'trackasia-gl';
const map = new Map(...)
\`\`\`
Import declarations are omitted from the examples for brevity.
`;
intro += lines.map(l => l.replace('../', './')).join('\n');
return intro;
}
function generateMarkdownForExample(title: string, description: string, file: string, htmlContent: string): string {
return `---
hide:
- toc
---
# ${title}
${description}
<iframe src="../${file}" width="100%" style="border:none; height:400px"></iframe>
\`\`\`html
${htmlContent}
\`\`\`
`;
}
async function generateMarkdownIndexFileOfAllExamplesAndPackImages(indexArray: HtmlDoc[]): Promise<string> {
let indexMarkdown = '---\nhide:\n - toc\n---\n\n# Overview \n\n';
const promises: Promise<any>[] = [];
for (const indexArrayItem of indexArray) {
const imagePath = `docs/assets/examples/${indexArrayItem.mdFileName!.replace('.md', '.png')}`;
if (!fs.existsSync(imagePath)) console.warn(`Image ${imagePath} not found`);
const outputPath = imagePath.replace('.png', '.webp');
promises.push(sharp(imagePath).webp({quality: 90, lossless: false}).toFile(outputPath));
indexMarkdown += `
## [${indexArrayItem.title}](./${indexArrayItem.mdFileName})
}){ loading=lazy }
${indexArrayItem.description}
`;
}
await Promise.all(promises);
return indexMarkdown;
}
/**
* Builds the README.md file by parsing the modules.md file generated by typedoc.
*/
function generateReadme() {
const globalsFile = path.join(typedocConfig.out, 'globals.md');
const content = fs.readFileSync(globalsFile, 'utf-8');
let lines = content.split('\n');
const classesLineIndex = lines.indexOf(lines.find(l => l.endsWith('Classes')) as string);
lines = lines.splice(2, classesLineIndex - 2);
const contentString = generateAPIIntroMarkdown(lines);
fs.writeFileSync(path.join(typedocConfig.out, 'README.md'), contentString);
// fs.rmSync(globalsFile);
}
/**
* This takes the examples folder with all the html files and generates a markdown file for each of them.
* It also create an index file with all the examples and their images.
*/
async function generateExamplesFolder() {
const examplesDocsFolder = path.join('docs', 'examples');
// if (fs.existsSync(examplesDocsFolder)) {
// fs.rmSync(examplesDocsFolder, {recursive: true, force: true});
// }
if (!fs.existsSync(examplesDocsFolder)) fs.mkdirSync(examplesDocsFolder);
const examplesFolder = path.join('test', 'examples', COUNTRY_CODE);
const files = fs.readdirSync(examplesFolder).filter(f => f.endsWith('html'));
const trackasiaUnpkg = `https://unpkg.com/trackasia-gl@${packageJson.version}/`;
const indexArray = [] as HtmlDoc[];
for (const file of files) {
const htmlFile = path.join(examplesFolder, file);
let htmlContent = fs.readFileSync(htmlFile, 'utf-8');
htmlContent = htmlContent.replace(/\.\.\/\.\.\//g, trackasiaUnpkg);
htmlContent = htmlContent.replace(/-dev.js/g, '.js');
const htmlContentLines = htmlContent.split('\n');
const title = htmlContentLines.find(l => l.includes('<title'))?.replace('<title>', '').replace('</title>', '').trim()!;
const description = htmlContentLines.find(l => l.includes('og:description'))?.replace(/.*content=\"(.*)\".*/, '$1')!;
fs.writeFileSync(path.join(examplesDocsFolder, file), htmlContent);
const mdFileName = file.replace('.html', '.md');
indexArray.push({
title,
description,
mdFileName
});
const exampleMarkdown = generateMarkdownForExample(title, description, file, htmlContent);
fs.writeFileSync(path.join(examplesDocsFolder, mdFileName), exampleMarkdown);
}
const indexMarkdown = await generateMarkdownIndexFileOfAllExamplesAndPackImages(indexArray);
fs.writeFileSync(path.join(examplesDocsFolder, 'index.md'), indexMarkdown);
}
async function fetchUrlContent(url: string) {
return new Promise<string>((resolve, reject) => {
get(url, (res) => {
let data = '';
if (res.statusCode && (res.statusCode < 200 || res.statusCode >= 300)) {
reject(new Error(res.statusMessage));
return;
}
res.on('data', (chunk) => {
data += chunk;
});
res.on('end', () => {
resolve(data);
});
}).on('error', reject);
});
}
async function generatePluginsPage() {
/**
* It extract some sections from Awesome TrackAsia README.md so we can integrate it into our plugins page
*
* ```
* header
* <!-- [SOME-ID]:BEGIN -->
* CONTENT-TO-EXTRACT
* <!-- [SOME-ID]:END -->
* footer
* ```
*/
const awesomeReadmeUrl = 'https://raw.githubusercontent.com/maplibre/awesome-maplibre/main/README.md';
const awesomeReadme = await fetchUrlContent(awesomeReadmeUrl);
const contentGroupsRE = /<!--\s*\[([-a-zA-Z]+)\]:BEGIN\s*-->([\s\S]*?)<!--\s*\[\1\]:END\s*-->/g;
const matches = awesomeReadme.matchAll(contentGroupsRE);
const groups = Object.fromEntries(
Array.from(matches).map(([, key, content]) => [key, content])
);
const pluginsContent = `# Plugins
${groups['JAVASCRIPT-PLUGINS']}
## Framework Integrations
${groups['JAVASCRIPT-BINDINGS']}
`;
fs.writeFileSync('docs/plugins.md', pluginsContent, {encoding: 'utf-8'});
}
// Vietnamese version of the docs
// It is a copy of the English version with some translations
function generateViAPIIntroMarkdown(lines: string[]): string {
let intro = `# Giới thiệu
Tệp này được sử dụng như một tài liệu tham khảo cho các lớp quan trọng và công khai của API này.
Chúng tôi khuyến nghị bạn xem qua [các ví dụ](../examples/index.vi.md) vì chúng sẽ giúp bạn bắt đầu với TrackAsia một cách tốt nhất.
Hầu hết các lớp được liệt kê ở đây đều có một đối tượng "Options" để khởi tạo, bạn nên kiểm tra xem có những tùy chọn nào.
Chúng tôi khuyến nghị bạn chỉ nhập những gì cần thiết và sử dụng chúng. Một số ví dụ về các lớp giả định rằng bạn đã làm điều đó.
Ví dụ, nhập lớp \`Map\` như sau:
\`\`\`ts
import {Map} from 'trackasia-gl';
const map = new Map(...)
\`\`\`
Các khai báo nhập được lược bỏ trong các ví dụ để ngắn gọn.
`;
intro += lines.map(l => l.replace('../', './')).join('\n');
return intro;
}
function generateViReadme() {
const globalsFile = path.join(typedocConfig.out, 'globals.md');
const content = fs.readFileSync(globalsFile, 'utf-8');
let lines = content.split('\n');
const classesLineIndex = lines.indexOf(lines.find(l => l.endsWith('Classes')) as string);
lines = lines.splice(2, classesLineIndex - 2);
// const translations = {
// '## Main': '## Mục Chính',
// '## Markers and Controls': '## Đánh dấu và điều khiển',
// '## Geography and Geometry': '## Địa lý và hình học',
// '## Handlers': '## Xử lý',
// '## Sources': '## Nguồn',
// '## Event Related': '## Liên quan đến sự kiện'
// };
// lines = lines.map(l => translations[l] || l);
const contentString = generateViAPIIntroMarkdown(lines);
fs.writeFileSync(path.join(typedocConfig.out, 'README.vi.md'), contentString);
// fs.rmSync(globalsFile);
}
async function generateViMarkdownIndexFileOfAllExamplesAndPackImages(indexArray: HtmlDoc[]): Promise<string> {
let indexMarkdown = '---\nhide:\n - toc\n---\n\n# Tổng Quan \n\n';
const promises: Promise<any>[] = [];
for (const indexArrayItem of indexArray) {
const imagePath = `docs/assets/examples/${indexArrayItem.mdFileName!.replace('.vi.md', '.png')}`;
const outputPath = imagePath.replace('.png', '.webp');
promises.push(sharp(imagePath).webp({quality: 90, lossless: false}).toFile(outputPath));
indexMarkdown += `
## [${indexArrayItem.title}](./${indexArrayItem.mdFileName})
}){ loading=lazy }
${indexArrayItem.description}
`;
}
await Promise.all(promises);
return indexMarkdown;
}
async function generateViExamplesFolder() {
const examplesDocsFolder = path.join('docs', 'examples');
// if (fs.existsSync(examplesDocsFolder)) {
// fs.rmSync(examplesDocsFolder, {recursive: true, force: true});
// }
if (!fs.existsSync(examplesDocsFolder)) fs.mkdirSync(examplesDocsFolder);
const examplesFolder = path.join('test', 'examples', COUNTRY_CODE);
const files = fs.readdirSync(examplesFolder).filter(f => f.endsWith('html'));
const trackasiaUnpkg = `https://unpkg.com/trackasia-gl@${packageJson.version}/`;
const indexArray = [] as HtmlDoc[];
for (const file of files) {
const htmlFile = path.join(examplesFolder, file);
let htmlContent = fs.readFileSync(htmlFile, 'utf-8');
htmlContent = htmlContent.replace(/\.\.\/\.\.\//g, trackasiaUnpkg);
htmlContent = htmlContent.replace(/-dev.js/g, '.js');
fs.writeFileSync(path.join(examplesDocsFolder, file.replace('.html', '.vi.html')), htmlContent);
const translations = exampleTranslations['vi'][file];
if (translations) {
const mdFileName = file.replace('.html', '.vi.md');
indexArray.push({
title: translations.title,
description: translations.description,
mdFileName: mdFileName
});
const viExampleMarkdown = generateMarkdownForExample(translations.title, translations.description, file, htmlContent);
fs.writeFileSync(path.join(examplesDocsFolder, mdFileName), viExampleMarkdown);
} else {
console.error(`No translations found for ${file}`);
}
}
const indexMarkdown = await generateViMarkdownIndexFileOfAllExamplesAndPackImages(indexArray);
fs.writeFileSync(path.join(examplesDocsFolder, 'index.vi.md'), indexMarkdown);
}
async function generateViPluginsPage() {
/**
* It extract some sections from Awesome TrackAsia README.md so we can integrate it into our plugins page
*
* ```
* header
* <!-- [SOME-ID]:BEGIN -->
* CONTENT-TO-EXTRACT
* <!-- [SOME-ID]:END -->
* footer
* ```
*/
const awesomeReadmeUrl = 'https://raw.githubusercontent.com/maplibre/awesome-maplibre/main/README.md';
const awesomeReadme = await fetchUrlContent(awesomeReadmeUrl);
const contentGroupsRE = /<!--\s*\[([-a-zA-Z]+)\]:BEGIN\s*-->([\s\S]*?)<!--\s*\[\1\]:END\s*-->/g;
const matches = awesomeReadme.matchAll(contentGroupsRE);
const groups = Object.fromEntries(
Array.from(matches).map(([, key, content]) => [key, content])
);
const pluginsContent = `# Plugins
${groups['JAVASCRIPT-PLUGINS']}
## Tích Hợp Framework
${groups['JAVASCRIPT-BINDINGS']}
`;
fs.writeFileSync('docs/plugins.vi.md', pluginsContent, {encoding: 'utf-8'});
}
function updateTrackAsiaVersionForUNPKG() {
// Read index.md
const indexPath = 'docs/index.md';
let indexContent = fs.readFileSync(indexPath, 'utf-8');
// Replace the version number
indexContent = indexContent.replace(/unpkg\.com\/trackasia-gl@\^(\d+\.\d+\.\d+)/g, `unpkg.com/trackasia-gl@^${packageJson.version}`);
// Save index.md
fs.writeFileSync(indexPath, indexContent);
}
// !!Main flow start here!!
if (!fs.existsSync(typedocConfig.out)) {
throw new Error('Please run typedoc generation first!');
}
fs.rmSync(path.join(typedocConfig.out, 'README.md'));
console.log('Cleaning up old docs...');
const examplesDocsFolder = path.join('docs', 'examples');
if (fs.existsSync(examplesDocsFolder)) {
fs.rmSync(examplesDocsFolder, {recursive: true, force: true});
}
// Generate the English docs
console.log('Generating English docs...');
generateReadme();
await generateExamplesFolder();
// await generatePluginsPage();
// Generate the Vietnamese docs
console.log('Generating Vietnamese docs...');
generateViReadme();
await generateViExamplesFolder();
// await generateViPluginsPage();
updateTrackAsiaVersionForUNPKG();
// Remove redundant files
fs.rmSync(path.join(typedocConfig.out, 'globals.md'));
fs.rmSync(path.join(typedocConfig.out, '_media'), {recursive: true, force: true});
console.log('Docs generation completed, to see it in action run\n npm run start-docs');