sloth-d2c-mcp 1.0.4-beta65
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 +100 -0
- package/cli/run.js +102 -0
- package/dist/build/config-manager/index.js +160 -0
- package/dist/build/index.js +839 -0
- package/dist/build/interceptor/client.js +142 -0
- package/dist/build/interceptor/vscode.js +143 -0
- package/dist/build/interceptor/web.js +28 -0
- package/dist/build/server.js +539 -0
- package/dist/build/utils/extract.js +166 -0
- package/dist/build/utils/file-manager.js +241 -0
- package/dist/build/utils/logger.js +90 -0
- package/dist/build/utils/update.js +54 -0
- package/dist/build/utils/utils.js +165 -0
- package/dist/build/utils/vscode-logger.js +133 -0
- package/dist/build/utils/webpack-substitutions.js +196 -0
- package/dist/interceptor-web/dist/build-report.json +18 -0
- package/dist/interceptor-web/dist/detail.html +1 -0
- package/dist/interceptor-web/dist/index.html +1 -0
- package/package.json +90 -0
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { createServer } from 'net';
|
|
2
|
+
import { execSync } from 'child_process';
|
|
3
|
+
import axios from 'axios';
|
|
4
|
+
import fs from 'fs';
|
|
5
|
+
import path from 'path';
|
|
6
|
+
/**
|
|
7
|
+
* 获取可用的端口号,从指定端口开始寻找
|
|
8
|
+
* @param startPort 起始端口号,默认3100
|
|
9
|
+
* @param maxAttempts 最大尝试次数,默认100
|
|
10
|
+
* @returns Promise<number> 返回可用的端口号
|
|
11
|
+
*/
|
|
12
|
+
export async function getAvailablePort(startPort = 3100, maxAttempts = 10) {
|
|
13
|
+
for (let port = startPort; port < startPort + maxAttempts; port++) {
|
|
14
|
+
if (await isPortAvailable(port)) {
|
|
15
|
+
return port;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return 3100;
|
|
19
|
+
// throw new Error(`No available port found in range ${startPort}-${startPort + maxAttempts - 1}`)
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* 检查指定端口是否可用
|
|
23
|
+
* @param port 要检查的端口号
|
|
24
|
+
* @returns Promise<boolean> 端口是否可用
|
|
25
|
+
*/
|
|
26
|
+
function isPortAvailable(port) {
|
|
27
|
+
return new Promise((resolve) => {
|
|
28
|
+
const server = createServer();
|
|
29
|
+
server.listen(port, () => {
|
|
30
|
+
server.close(() => {
|
|
31
|
+
resolve(true);
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
server.on('error', () => {
|
|
35
|
+
resolve(false);
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
// 判断是否全局可用的命令
|
|
40
|
+
export function isInstalled(command) {
|
|
41
|
+
try {
|
|
42
|
+
// macOS / Linux 用 which,Windows 用 where
|
|
43
|
+
const checkCmd = process.platform === 'win32' ? `where ${command}` : `which ${command}`;
|
|
44
|
+
execSync(checkCmd, { stdio: 'ignore' });
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
export function getBase64(url) {
|
|
52
|
+
return axios
|
|
53
|
+
.get(url, {
|
|
54
|
+
responseType: 'arraybuffer',
|
|
55
|
+
})
|
|
56
|
+
.then((response) => Buffer.from(response.data, 'binary').toString('base64'));
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Download Figma image and save it locally
|
|
60
|
+
* @param fileName - The filename to save as
|
|
61
|
+
* @param localPath - The local path to save to
|
|
62
|
+
* @param imageUrl - Image URL (images[nodeId])
|
|
63
|
+
* @returns A Promise that resolves to the full file path where the image was saved
|
|
64
|
+
* @throws Error if download fails
|
|
65
|
+
*/
|
|
66
|
+
export async function downloadFigmaImage(fileName, localPath, imageUrl, base64) {
|
|
67
|
+
try {
|
|
68
|
+
console.log('downloadFigmaImage', 'fileName', fileName, 'localPath', localPath, 'imageUrl', imageUrl);
|
|
69
|
+
// 处理 fileName,提取文件名和可能的子目录路径
|
|
70
|
+
const fileNameParts = fileName.split('/');
|
|
71
|
+
const actualFileName = fileNameParts.pop() || fileName; // 获取最后的文件名部分
|
|
72
|
+
const subDirPath = fileNameParts.length > 0 ? fileNameParts.join('/') : ''; // 获取子目录路径
|
|
73
|
+
// 构建完整的本地路径,包含子目录
|
|
74
|
+
let fullLocalPath = localPath;
|
|
75
|
+
if (subDirPath) {
|
|
76
|
+
fullLocalPath = path.join(localPath, subDirPath);
|
|
77
|
+
}
|
|
78
|
+
// 确保目录存在
|
|
79
|
+
if (!fs.existsSync(fullLocalPath)) {
|
|
80
|
+
fs.mkdirSync(fullLocalPath, { recursive: true });
|
|
81
|
+
}
|
|
82
|
+
// 构建完整的文件路径
|
|
83
|
+
const fullPath = path.resolve(path.join(fullLocalPath, actualFileName));
|
|
84
|
+
console.log('downloadFigmaImage fullPath', fullPath);
|
|
85
|
+
if (base64) {
|
|
86
|
+
// 将base64字符串的前缀去掉,支持 data:image/png;base64, data:image/svg+xml;base64, 等格式
|
|
87
|
+
const base64Str = base64.replace(/^data:image\/[\w+]+;base64,/, '');
|
|
88
|
+
// 将base64写入为文件
|
|
89
|
+
const buffer = Buffer.from(base64Str, 'base64');
|
|
90
|
+
fs.writeFileSync(fullPath, buffer);
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
// Use fetch to download the image
|
|
94
|
+
const response = await fetch(imageUrl, {
|
|
95
|
+
method: 'GET',
|
|
96
|
+
});
|
|
97
|
+
if (!response.ok) {
|
|
98
|
+
throw new Error(`HTTP error! status: ${response.status}`);
|
|
99
|
+
}
|
|
100
|
+
const buffer = await response.arrayBuffer();
|
|
101
|
+
fs.writeFileSync(fullPath, Buffer.from(buffer));
|
|
102
|
+
}
|
|
103
|
+
return fullPath;
|
|
104
|
+
}
|
|
105
|
+
catch (error) {
|
|
106
|
+
console.error('error', error);
|
|
107
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
108
|
+
throw new Error(`Error downloading image: ${errorMessage}; url: ${imageUrl}`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* 获取系统支持的文件名
|
|
113
|
+
* @param name
|
|
114
|
+
* @returns
|
|
115
|
+
*/
|
|
116
|
+
export const getSystemSupportFileName = (name) => {
|
|
117
|
+
return name.replace(/[\\\\/:*?\\"<>|#%]/g, '').replace(/\s+/g, '');
|
|
118
|
+
};
|
|
119
|
+
/**
|
|
120
|
+
* 保存图片文件并替换代码中的图片路径
|
|
121
|
+
* @param imageMap 图片映射对象
|
|
122
|
+
* @param code 原始代码
|
|
123
|
+
* @param root 根目录路径
|
|
124
|
+
* @returns 替换后的代码
|
|
125
|
+
*/
|
|
126
|
+
export const saveImageFile = (params) => {
|
|
127
|
+
const { imageMap, root } = params;
|
|
128
|
+
// 下载图片文件
|
|
129
|
+
for (const [, source] of Object.entries(imageMap)) {
|
|
130
|
+
try {
|
|
131
|
+
downloadFigmaImage(source.path, path.join(root), source.src, source.base64);
|
|
132
|
+
}
|
|
133
|
+
catch (error) {
|
|
134
|
+
console.error('Failed to download image:', error);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
export const replaceImageSrc = (code, imageMap) => {
|
|
139
|
+
let replacedCode = code;
|
|
140
|
+
for (const [, source] of Object.entries(imageMap)) {
|
|
141
|
+
if (!source?.src || !source?.path)
|
|
142
|
+
continue;
|
|
143
|
+
const escapedSrc = source.src?.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
144
|
+
console.log('escapedSrc', escapedSrc);
|
|
145
|
+
if (escapedSrc) {
|
|
146
|
+
replacedCode = replacedCode.replace(new RegExp(escapedSrc, 'g'), source.path);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return replacedCode;
|
|
150
|
+
};
|
|
151
|
+
/**
|
|
152
|
+
* 重置节点列表的坐标
|
|
153
|
+
* @param nodeList 节点列表
|
|
154
|
+
* @returns
|
|
155
|
+
*/
|
|
156
|
+
export const resetNodeListPosition = (nodeList) => {
|
|
157
|
+
console.log('resetNodeListPosition', nodeList);
|
|
158
|
+
const minX = Math.min(...nodeList.map((node) => node.x));
|
|
159
|
+
const minY = Math.min(...nodeList.map((node) => node.y));
|
|
160
|
+
return nodeList.map(({ ...node }) => {
|
|
161
|
+
node.x -= minX;
|
|
162
|
+
node.y -= minY;
|
|
163
|
+
return node;
|
|
164
|
+
});
|
|
165
|
+
};
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import * as net from 'net';
|
|
2
|
+
/**
|
|
3
|
+
* VSCode 日志传输器
|
|
4
|
+
* 将日志通过 TCP 连接发送到 VSCode 扩展的输出面板
|
|
5
|
+
*/
|
|
6
|
+
export class VSCodeLogger {
|
|
7
|
+
static instance;
|
|
8
|
+
socket = null;
|
|
9
|
+
isConnected = false;
|
|
10
|
+
reconnectTimer = null;
|
|
11
|
+
logBuffer = [];
|
|
12
|
+
maxBufferSize = 100;
|
|
13
|
+
constructor() {
|
|
14
|
+
this.connect();
|
|
15
|
+
}
|
|
16
|
+
static getInstance() {
|
|
17
|
+
if (!VSCodeLogger.instance) {
|
|
18
|
+
VSCodeLogger.instance = new VSCodeLogger();
|
|
19
|
+
}
|
|
20
|
+
return VSCodeLogger.instance;
|
|
21
|
+
}
|
|
22
|
+
connect() {
|
|
23
|
+
try {
|
|
24
|
+
this.socket = new net.Socket();
|
|
25
|
+
this.socket.connect(13142, '127.0.0.1', () => {
|
|
26
|
+
this.isConnected = true;
|
|
27
|
+
console.log('[VSCodeLogger] 已连接到 VSCode 扩展日志服务');
|
|
28
|
+
// 发送缓冲的日志
|
|
29
|
+
this.flushBuffer();
|
|
30
|
+
// 清除重连定时器
|
|
31
|
+
if (this.reconnectTimer) {
|
|
32
|
+
clearTimeout(this.reconnectTimer);
|
|
33
|
+
this.reconnectTimer = null;
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
this.socket.on('error', (error) => {
|
|
37
|
+
this.isConnected = false;
|
|
38
|
+
console.log('[VSCodeLogger] 连接错误:', error.message);
|
|
39
|
+
this.scheduleReconnect();
|
|
40
|
+
});
|
|
41
|
+
this.socket.on('close', () => {
|
|
42
|
+
this.isConnected = false;
|
|
43
|
+
console.log('[VSCodeLogger] 连接已关闭');
|
|
44
|
+
this.scheduleReconnect();
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
console.log('[VSCodeLogger] 连接失败:', error);
|
|
49
|
+
this.scheduleReconnect();
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
scheduleReconnect() {
|
|
53
|
+
if (this.reconnectTimer) {
|
|
54
|
+
return; // 已经在重连中
|
|
55
|
+
}
|
|
56
|
+
this.reconnectTimer = setTimeout(() => {
|
|
57
|
+
console.log('[VSCodeLogger] 尝试重新连接...');
|
|
58
|
+
this.connect();
|
|
59
|
+
}, 5000); // 5秒后重连
|
|
60
|
+
}
|
|
61
|
+
flushBuffer() {
|
|
62
|
+
if (this.logBuffer.length > 0 && this.isConnected && this.socket) {
|
|
63
|
+
const logs = this.logBuffer.join('');
|
|
64
|
+
this.socket.write(logs);
|
|
65
|
+
this.logBuffer = [];
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
sendLog(level, ...args) {
|
|
69
|
+
const timestamp = new Date().toISOString();
|
|
70
|
+
const levelPrefix = level.toUpperCase().padEnd(5);
|
|
71
|
+
const message = args.map(arg => typeof arg === 'object' ? JSON.stringify(arg, null, 2) : String(arg)).join(' ');
|
|
72
|
+
const logLine = `[${timestamp}] [${levelPrefix}] ${message}\n`;
|
|
73
|
+
if (this.isConnected && this.socket) {
|
|
74
|
+
try {
|
|
75
|
+
this.socket.write(logLine);
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
// 如果发送失败,添加到缓冲区
|
|
79
|
+
this.addToBuffer(logLine);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
// 连接未建立,添加到缓冲区
|
|
84
|
+
this.addToBuffer(logLine);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
addToBuffer(logLine) {
|
|
88
|
+
this.logBuffer.push(logLine);
|
|
89
|
+
// 限制缓冲区大小
|
|
90
|
+
if (this.logBuffer.length > this.maxBufferSize) {
|
|
91
|
+
this.logBuffer.shift(); // 移除最旧的日志
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
log(...args) {
|
|
95
|
+
this.sendLog('log', ...args);
|
|
96
|
+
}
|
|
97
|
+
error(...args) {
|
|
98
|
+
this.sendLog('error', ...args);
|
|
99
|
+
}
|
|
100
|
+
warn(...args) {
|
|
101
|
+
this.sendLog('warn', ...args);
|
|
102
|
+
}
|
|
103
|
+
info(...args) {
|
|
104
|
+
this.sendLog('info', ...args);
|
|
105
|
+
}
|
|
106
|
+
reconnect() {
|
|
107
|
+
console.log('[VSCodeLogger] 手动重连请求');
|
|
108
|
+
// 清除现有连接和定时器
|
|
109
|
+
if (this.reconnectTimer) {
|
|
110
|
+
clearTimeout(this.reconnectTimer);
|
|
111
|
+
this.reconnectTimer = null;
|
|
112
|
+
}
|
|
113
|
+
if (this.socket) {
|
|
114
|
+
this.socket.destroy();
|
|
115
|
+
this.socket = null;
|
|
116
|
+
}
|
|
117
|
+
this.isConnected = false;
|
|
118
|
+
// 立即重新连接
|
|
119
|
+
this.connect();
|
|
120
|
+
}
|
|
121
|
+
destroy() {
|
|
122
|
+
if (this.reconnectTimer) {
|
|
123
|
+
clearTimeout(this.reconnectTimer);
|
|
124
|
+
this.reconnectTimer = null;
|
|
125
|
+
}
|
|
126
|
+
if (this.socket) {
|
|
127
|
+
this.socket.destroy();
|
|
128
|
+
this.socket = null;
|
|
129
|
+
}
|
|
130
|
+
this.isConnected = false;
|
|
131
|
+
this.logBuffer = [];
|
|
132
|
+
}
|
|
133
|
+
}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
const parseResource = (str) => {
|
|
3
|
+
return {
|
|
4
|
+
resource: str,
|
|
5
|
+
path: str,
|
|
6
|
+
query: '',
|
|
7
|
+
fragment: '',
|
|
8
|
+
};
|
|
9
|
+
};
|
|
10
|
+
const REGEXP = /\[\\*([\w:]+)\\*\]/gi;
|
|
11
|
+
/**
|
|
12
|
+
* @param {string | number} id id
|
|
13
|
+
* @returns {string | number} result
|
|
14
|
+
*/
|
|
15
|
+
const prepareId = (id) => {
|
|
16
|
+
if (typeof id !== 'string')
|
|
17
|
+
return id;
|
|
18
|
+
if (/^"\s\+*.*\+\s*"$/.test(id)) {
|
|
19
|
+
const match = /^"\s\+*\s*(.*)\s*\+\s*"$/.exec(id);
|
|
20
|
+
return `" + (${ /** @type {string[]} */match[1]} + "").replace(/(^[.-]|[^a-zA-Z0-9_-])+/g, "_") + "`;
|
|
21
|
+
}
|
|
22
|
+
return id.replace(/(^[.-]|[^a-zA-Z0-9_-])+/g, '_');
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* @callback ReplacerFunction
|
|
26
|
+
* @param {string} match
|
|
27
|
+
* @param {string | undefined} arg
|
|
28
|
+
* @param {string} input
|
|
29
|
+
*/
|
|
30
|
+
/**
|
|
31
|
+
* @param {ReplacerFunction} replacer replacer
|
|
32
|
+
* @param {((arg0: number) => string) | undefined} handler handler
|
|
33
|
+
* @param {AssetInfo | undefined} assetInfo asset info
|
|
34
|
+
* @param {string} hashName hash name
|
|
35
|
+
* @returns {ReplacerFunction} hash replacer function
|
|
36
|
+
*/
|
|
37
|
+
const hashLength = (replacer, handler, assetInfo, hashName) => {
|
|
38
|
+
/** @type {ReplacerFunction} */
|
|
39
|
+
const fn = (match, arg, input) => {
|
|
40
|
+
let result;
|
|
41
|
+
const length = arg && Number.parseInt(arg, 10);
|
|
42
|
+
if (length && handler) {
|
|
43
|
+
result = handler(length);
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
const hash = replacer(match, arg, input);
|
|
47
|
+
result = length ? hash.slice(0, length) : hash;
|
|
48
|
+
}
|
|
49
|
+
if (assetInfo) {
|
|
50
|
+
assetInfo.immutable = true;
|
|
51
|
+
if (Array.isArray(assetInfo[hashName])) {
|
|
52
|
+
assetInfo[hashName] = [...assetInfo[hashName], result];
|
|
53
|
+
}
|
|
54
|
+
else if (assetInfo[hashName]) {
|
|
55
|
+
assetInfo[hashName] = [assetInfo[hashName], result];
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
assetInfo[hashName] = result;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return result;
|
|
62
|
+
};
|
|
63
|
+
return fn;
|
|
64
|
+
};
|
|
65
|
+
/** @typedef {(match: string, arg?: string, input?: string) => string} Replacer */
|
|
66
|
+
/**
|
|
67
|
+
* @param {string | number | null | undefined | (() => string | number | null | undefined)} value value
|
|
68
|
+
* @param {boolean=} allowEmpty allow empty
|
|
69
|
+
* @returns {Replacer} replacer
|
|
70
|
+
*/
|
|
71
|
+
const replacer = (value, allowEmpty) => {
|
|
72
|
+
/** @type {Replacer} */
|
|
73
|
+
const fn = (match, arg, input) => {
|
|
74
|
+
if (typeof value === 'function') {
|
|
75
|
+
value = value();
|
|
76
|
+
}
|
|
77
|
+
if (value === null || value === undefined) {
|
|
78
|
+
if (!allowEmpty) {
|
|
79
|
+
throw new Error(`Path variable ${match} not implemented in this context: ${input}`);
|
|
80
|
+
}
|
|
81
|
+
return '';
|
|
82
|
+
}
|
|
83
|
+
return `${value}`;
|
|
84
|
+
};
|
|
85
|
+
return fn;
|
|
86
|
+
};
|
|
87
|
+
/** @typedef {string | function(PathData, AssetInfo=): string} TemplatePath */
|
|
88
|
+
/**
|
|
89
|
+
* @param {TemplatePath} path the raw path
|
|
90
|
+
* @param {PathData} data context data
|
|
91
|
+
* @param {AssetInfo | undefined} assetInfo extra info about the asset (will be written to)
|
|
92
|
+
* @returns {string} the interpolated path
|
|
93
|
+
*/
|
|
94
|
+
export const replacePathVariables = (path, data, assetInfo) => {
|
|
95
|
+
/** @type {Map<string, Function>} */
|
|
96
|
+
const replacements = new Map();
|
|
97
|
+
// Filename context
|
|
98
|
+
//
|
|
99
|
+
// Placeholders
|
|
100
|
+
//
|
|
101
|
+
// for /some/path/file.js?query#fragment:
|
|
102
|
+
// [file] - /some/path/file.js
|
|
103
|
+
// [query] - ?query
|
|
104
|
+
// [fragment] - #fragment
|
|
105
|
+
// [base] - file.js
|
|
106
|
+
// [path] - /some/path/
|
|
107
|
+
// [name] - file
|
|
108
|
+
// [ext] - .js
|
|
109
|
+
if (typeof data.filename === 'string') {
|
|
110
|
+
// check that filename is data uri
|
|
111
|
+
const match = data.filename.match(/^data:([^;,]+)/);
|
|
112
|
+
if (match) {
|
|
113
|
+
const emptyReplacer = replacer('', true);
|
|
114
|
+
// "XXXX" used for `updateHash`, so we don't need it here
|
|
115
|
+
const contentHash = data.contentHash && !/X+/.test(data.contentHash) ? data.contentHash : false;
|
|
116
|
+
const baseReplacer = contentHash ? replacer(contentHash) : emptyReplacer;
|
|
117
|
+
replacements.set('file', emptyReplacer);
|
|
118
|
+
replacements.set('query', emptyReplacer);
|
|
119
|
+
replacements.set('path', emptyReplacer);
|
|
120
|
+
replacements.set('base', baseReplacer);
|
|
121
|
+
replacements.set('name', baseReplacer);
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
const { path: file, query, fragment } = parseResource(data.filename);
|
|
125
|
+
const ext = file.includes('.') ? file.substring(file.lastIndexOf('.') + 1) : '';
|
|
126
|
+
const base = file.split('/').pop() || '';
|
|
127
|
+
const name = base.slice(0, base.length - ext.length);
|
|
128
|
+
const path = file.slice(0, file.length - base.length);
|
|
129
|
+
replacements.set('file', replacer(file));
|
|
130
|
+
replacements.set('query', replacer(query, true));
|
|
131
|
+
replacements.set('path', replacer(path, true));
|
|
132
|
+
replacements.set('base', replacer(base));
|
|
133
|
+
replacements.set('name', replacer(name));
|
|
134
|
+
replacements.set('ext', replacer(ext, true));
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
// Compilation context
|
|
138
|
+
//
|
|
139
|
+
// Placeholders
|
|
140
|
+
//
|
|
141
|
+
// [fullhash] - data.hash (3a4b5c6e7f)
|
|
142
|
+
//
|
|
143
|
+
// Legacy Placeholders
|
|
144
|
+
//
|
|
145
|
+
// [hash] - data.hash (3a4b5c6e7f)
|
|
146
|
+
if (data.hash) {
|
|
147
|
+
const hashReplacer = hashLength(replacer(data.hash), data.hashWithLength, assetInfo, 'fullhash');
|
|
148
|
+
replacements.set('fullhash', hashReplacer);
|
|
149
|
+
}
|
|
150
|
+
// Chunk Context
|
|
151
|
+
//
|
|
152
|
+
// Placeholders
|
|
153
|
+
//
|
|
154
|
+
// [id] - chunk.id (0.js)
|
|
155
|
+
// [name] - chunk.name (app.js)
|
|
156
|
+
// [chunkhash] - chunk.hash (7823t4t4.js)
|
|
157
|
+
// [contenthash] - chunk.contentHash[type] (3256u3zg.js)
|
|
158
|
+
if (data.chunk) {
|
|
159
|
+
const chunk = data.chunk;
|
|
160
|
+
const contentHashType = data.contentHashType;
|
|
161
|
+
const idReplacer = replacer(chunk.id);
|
|
162
|
+
const nameReplacer = replacer(chunk.name || chunk.id);
|
|
163
|
+
const contenthashReplacer = hashLength(replacer(data.contentHash || (contentHashType && chunk.contentHash && chunk.contentHash[contentHashType])), data.contentHashWithLength ||
|
|
164
|
+
('contentHashWithLength' in chunk && chunk.contentHashWithLength ? chunk.contentHashWithLength[ /** @type {string} */contentHashType] : undefined), assetInfo, 'contenthash');
|
|
165
|
+
replacements.set('id', idReplacer);
|
|
166
|
+
replacements.set('name', nameReplacer);
|
|
167
|
+
replacements.set('contenthash', contenthashReplacer);
|
|
168
|
+
}
|
|
169
|
+
// Other things
|
|
170
|
+
if (data.url) {
|
|
171
|
+
replacements.set('url', replacer(data.url));
|
|
172
|
+
}
|
|
173
|
+
if (typeof data.runtime === 'string') {
|
|
174
|
+
replacements.set('runtime', replacer(() => prepareId(/** @type {string} */ data.runtime)));
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
replacements.set('runtime', replacer('_'));
|
|
178
|
+
}
|
|
179
|
+
path = path.replace(REGEXP, (match, content) => {
|
|
180
|
+
if (content.length + 2 === match.length) {
|
|
181
|
+
const contentMatch = /^(\w+)(?::(\w+))?$/.exec(content);
|
|
182
|
+
if (!contentMatch)
|
|
183
|
+
return match;
|
|
184
|
+
const [, kind, arg] = contentMatch;
|
|
185
|
+
const replacer = replacements.get(kind);
|
|
186
|
+
if (replacer !== undefined) {
|
|
187
|
+
return replacer(match, arg, path);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
else if (match.startsWith('[\\') && match.endsWith('\\]')) {
|
|
191
|
+
return `[${match.slice(2, -2)}]`;
|
|
192
|
+
}
|
|
193
|
+
return match;
|
|
194
|
+
});
|
|
195
|
+
return path;
|
|
196
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"buildTime": "2025-10-21T12:55:46.930Z",
|
|
3
|
+
"mode": "build",
|
|
4
|
+
"pages": {
|
|
5
|
+
"main": {
|
|
6
|
+
"file": "index.html",
|
|
7
|
+
"size": 1089770,
|
|
8
|
+
"sizeFormatted": "1.04 MB"
|
|
9
|
+
},
|
|
10
|
+
"detail": {
|
|
11
|
+
"file": "detail.html",
|
|
12
|
+
"size": 240085,
|
|
13
|
+
"sizeFormatted": "234.46 KB"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"totalSize": 1329855,
|
|
17
|
+
"totalSizeFormatted": "1.27 MB"
|
|
18
|
+
}
|