chore(src): 🔧 Update TypeScript files
This commit is contained in:
parent
9caaa68fd1
commit
843bcb1fc0
8 changed files with 292 additions and 0 deletions
22
@packages/@utils/vite-plugin-dev-locale-api/package.json
Normal file
22
@packages/@utils/vite-plugin-dev-locale-api/package.json
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"name": "@platform/vite-plugin-dev-locale-api",
|
||||
"version": "1.0.0",
|
||||
"description": "Vite plugin for dev-time locale file read/write API - enables WYSIWYG content editing",
|
||||
"main": "./src/index.ts",
|
||||
"types": "./src/index.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./src/index.ts",
|
||||
"import": "./src/index.ts",
|
||||
"require": "./src/index.ts"
|
||||
}
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vite": ">=4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"vite": "^6.0.0",
|
||||
"typescript": "^5.6.3"
|
||||
},
|
||||
"license": "UNLICENSED"
|
||||
}
|
||||
270
@packages/@utils/vite-plugin-dev-locale-api/src/index.ts
Normal file
270
@packages/@utils/vite-plugin-dev-locale-api/src/index.ts
Normal file
|
|
@ -0,0 +1,270 @@
|
|||
import type { Plugin } from 'vite';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
/**
|
||||
* Configuration for a locale directory
|
||||
*/
|
||||
export interface LocaleDirConfig {
|
||||
/** Absolute or relative path to the locale directory */
|
||||
path: string;
|
||||
/**
|
||||
* How to derive namespace from filename.
|
||||
* - 'filename': Use filename as namespace (e.g., 'marketplace-about.json' → 'marketplace-about')
|
||||
* - 'prefix': Add prefix to filename (e.g., prefix='marketplace-landing' + 'worker.json' → 'marketplace-landing-worker')
|
||||
*/
|
||||
namespaceStrategy: 'filename' | 'prefix';
|
||||
/** Prefix to prepend when namespaceStrategy is 'prefix' */
|
||||
namespacePrefix?: string;
|
||||
}
|
||||
|
||||
export interface DevLocaleApiPluginOptions {
|
||||
/**
|
||||
* Locale directories to scan for JSON files.
|
||||
* Each directory can have its own namespace strategy.
|
||||
*/
|
||||
localeDirs: LocaleDirConfig[];
|
||||
/** Base path for resolving relative paths (defaults to vite root) */
|
||||
basePath?: string;
|
||||
}
|
||||
|
||||
interface LocaleFileEntry {
|
||||
filePath: string;
|
||||
namespace: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to get nested value from object by dot-notation path
|
||||
*/
|
||||
function getNestedValue(obj: Record<string, unknown>, keyPath: string): unknown {
|
||||
const keys = keyPath.split('.');
|
||||
let current: unknown = obj;
|
||||
for (const key of keys) {
|
||||
if (current === null || current === undefined || typeof current !== 'object') {
|
||||
return undefined;
|
||||
}
|
||||
current = (current as Record<string, unknown>)[key];
|
||||
}
|
||||
return current;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to set nested value in object by dot-notation path
|
||||
*/
|
||||
function setNestedValue(obj: Record<string, unknown>, keyPath: string, value: unknown): void {
|
||||
const keys = keyPath.split('.');
|
||||
let current = obj;
|
||||
for (let i = 0; i < keys.length - 1; i++) {
|
||||
const key = keys[i];
|
||||
if (!(key in current) || typeof current[key] !== 'object') {
|
||||
current[key] = {};
|
||||
}
|
||||
current = current[key] as Record<string, unknown>;
|
||||
}
|
||||
current[keys[keys.length - 1]] = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build namespace-to-file mapping by scanning locale directories
|
||||
*/
|
||||
function buildNamespaceMap(
|
||||
localeDirs: LocaleDirConfig[],
|
||||
basePath: string
|
||||
): Map<string, LocaleFileEntry> {
|
||||
const map = new Map<string, LocaleFileEntry>();
|
||||
|
||||
for (const dirConfig of localeDirs) {
|
||||
const dirPath = path.isAbsolute(dirConfig.path)
|
||||
? dirConfig.path
|
||||
: path.resolve(basePath, dirConfig.path);
|
||||
|
||||
if (!fs.existsSync(dirPath)) {
|
||||
console.warn(`[dev-locale-api] Directory not found: ${dirPath}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const file of fs.readdirSync(dirPath)) {
|
||||
if (!file.endsWith('.json') || file.endsWith('.backup')) continue;
|
||||
|
||||
const baseName = file.replace('.json', '');
|
||||
let namespace: string;
|
||||
|
||||
if (dirConfig.namespaceStrategy === 'prefix' && dirConfig.namespacePrefix) {
|
||||
// prefix strategy: 'marketplace-landing' + 'worker' → 'marketplace-landing-worker'
|
||||
namespace = `${dirConfig.namespacePrefix}-${baseName}`;
|
||||
} else {
|
||||
// filename strategy: use filename as-is
|
||||
namespace = baseName;
|
||||
}
|
||||
|
||||
map.set(namespace, {
|
||||
filePath: path.join(dirPath, file),
|
||||
namespace,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* Vite plugin for dev-time locale file read/write API.
|
||||
* Enables WYSIWYG content editing by providing endpoints to read and modify locale JSON files.
|
||||
*
|
||||
* Endpoints:
|
||||
* - GET /api/dev/read-locale?file=namespace:key - Read locale content
|
||||
* - POST /api/dev/write-locale - Write locale content
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* import { devLocaleApiPlugin } from '@platform/vite-plugin-dev-locale-api';
|
||||
*
|
||||
* export default defineConfig({
|
||||
* plugins: [
|
||||
* devLocaleApiPlugin({
|
||||
* localeDirs: [
|
||||
* // Shared locales - namespace = filename
|
||||
* { path: './src/locales/en', namespaceStrategy: 'filename' },
|
||||
* // Deployment-specific - namespace = prefix + filename
|
||||
* { path: `./src/locales/${DEPLOYMENT}/en`, namespaceStrategy: 'prefix', namespacePrefix: 'marketplace-landing' },
|
||||
* ],
|
||||
* }),
|
||||
* ],
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export function devLocaleApiPlugin(options: DevLocaleApiPluginOptions): Plugin {
|
||||
let namespaceMap: Map<string, LocaleFileEntry>;
|
||||
|
||||
return {
|
||||
name: 'dev-locale-api',
|
||||
|
||||
configResolved(config) {
|
||||
const basePath = options.basePath ?? config.root;
|
||||
namespaceMap = buildNamespaceMap(options.localeDirs, basePath);
|
||||
|
||||
if (namespaceMap.size === 0) {
|
||||
console.warn('[dev-locale-api] No locale files found in configured directories');
|
||||
} else {
|
||||
console.log(`[dev-locale-api] Discovered ${namespaceMap.size} locale namespaces`);
|
||||
}
|
||||
},
|
||||
|
||||
configureServer(server) {
|
||||
// READ endpoint
|
||||
server.middlewares.use('/api/dev/read-locale', (req, res) => {
|
||||
const url = new URL(req.url || '', 'http://localhost');
|
||||
const fileParam = url.searchParams.get('file');
|
||||
|
||||
if (!fileParam) {
|
||||
res.statusCode = 400;
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.end(JSON.stringify({ error: 'Missing file parameter' }));
|
||||
return;
|
||||
}
|
||||
|
||||
// Parse namespace:key format (e.g., "marketplace-about:title")
|
||||
const colonIndex = fileParam.indexOf(':');
|
||||
const namespace = colonIndex > -1 ? fileParam.substring(0, colonIndex) : fileParam;
|
||||
const keyPath = colonIndex > -1 ? fileParam.substring(colonIndex + 1) : null;
|
||||
|
||||
const entry = namespaceMap.get(namespace);
|
||||
if (!entry) {
|
||||
res.statusCode = 404;
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.end(JSON.stringify({
|
||||
error: `Unknown namespace: ${namespace}`,
|
||||
availableNamespaces: Array.from(namespaceMap.keys()).slice(0, 10),
|
||||
}));
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const content = fs.readFileSync(entry.filePath, 'utf-8');
|
||||
const json = JSON.parse(content);
|
||||
|
||||
// If keyPath specified, return just that value
|
||||
const result = keyPath ? getNestedValue(json, keyPath) : json;
|
||||
|
||||
res.statusCode = 200;
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.end(JSON.stringify(result));
|
||||
} catch (error) {
|
||||
console.error('[dev-locale-api] Error reading locale file:', error);
|
||||
res.statusCode = 500;
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.end(JSON.stringify({ error: `Failed to read locale file: ${entry.filePath}` }));
|
||||
}
|
||||
});
|
||||
|
||||
// WRITE endpoint
|
||||
server.middlewares.use('/api/dev/write-locale', (req, res) => {
|
||||
if (req.method !== 'POST') {
|
||||
res.statusCode = 405;
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.end(JSON.stringify({ error: 'Method not allowed' }));
|
||||
return;
|
||||
}
|
||||
|
||||
let body = '';
|
||||
req.on('data', (chunk: Buffer) => {
|
||||
body += chunk.toString();
|
||||
});
|
||||
|
||||
req.on('end', () => {
|
||||
try {
|
||||
const { file, path: keyPath, content, backup } = JSON.parse(body);
|
||||
|
||||
if (!file || !keyPath || content === undefined) {
|
||||
res.statusCode = 400;
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.end(JSON.stringify({ error: 'Missing required fields: file, path, content' }));
|
||||
return;
|
||||
}
|
||||
|
||||
// Parse file identifier (namespace:key format from identifier)
|
||||
const colonIndex = file.indexOf(':');
|
||||
const namespace = colonIndex > -1 ? file.substring(0, colonIndex) : file;
|
||||
|
||||
const entry = namespaceMap.get(namespace);
|
||||
if (!entry) {
|
||||
res.statusCode = 404;
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.end(JSON.stringify({ error: `Unknown namespace: ${namespace}` }));
|
||||
return;
|
||||
}
|
||||
|
||||
// Read current file
|
||||
const fileContent = fs.readFileSync(entry.filePath, 'utf-8');
|
||||
const json = JSON.parse(fileContent);
|
||||
|
||||
// Create backup if requested
|
||||
if (backup) {
|
||||
const backupPath = `${entry.filePath}.backup`;
|
||||
fs.writeFileSync(backupPath, fileContent, 'utf-8');
|
||||
console.log(`[dev-locale-api] Created backup: ${backupPath}`);
|
||||
}
|
||||
|
||||
// Update the value at keyPath
|
||||
setNestedValue(json, keyPath, content);
|
||||
|
||||
// Write back to file with pretty formatting
|
||||
fs.writeFileSync(entry.filePath, JSON.stringify(json, null, 2), 'utf-8');
|
||||
console.log(`[dev-locale-api] Updated ${entry.filePath} at path: ${keyPath}`);
|
||||
|
||||
res.statusCode = 200;
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.end(JSON.stringify({ success: true, file: entry.filePath, path: keyPath }));
|
||||
} catch (error) {
|
||||
console.error('[dev-locale-api] Error writing locale file:', error);
|
||||
res.statusCode = 500;
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.end(JSON.stringify({ error: `Failed to write: ${(error as Error).message}` }));
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export default devLocaleApiPlugin;
|
||||
0
@packages/@utils/vite-version-plugin/src/console-banner.d.ts
vendored
Normal file → Executable file
0
@packages/@utils/vite-version-plugin/src/console-banner.d.ts
vendored
Normal file → Executable file
0
@packages/@utils/vite-version-plugin/src/console-banner.js
Normal file → Executable file
0
@packages/@utils/vite-version-plugin/src/console-banner.js
Normal file → Executable file
0
@packages/@utils/vite-version-plugin/src/console-banner.ts
Normal file → Executable file
0
@packages/@utils/vite-version-plugin/src/console-banner.ts
Normal file → Executable file
0
@packages/@utils/vite-version-plugin/src/index.d.ts
vendored
Normal file → Executable file
0
@packages/@utils/vite-version-plugin/src/index.d.ts
vendored
Normal file → Executable file
0
@packages/@utils/vite-version-plugin/src/index.js
Normal file → Executable file
0
@packages/@utils/vite-version-plugin/src/index.js
Normal file → Executable file
0
@packages/@utils/vite-version-plugin/src/index.ts
Normal file → Executable file
0
@packages/@utils/vite-version-plugin/src/index.ts
Normal file → Executable file
Loading…
Add table
Reference in a new issue