shoplazza-cli 1.0.13 → 1.1.1
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 +12 -12
- package/bin/shoplazza +5 -3
- package/lib/app/api/cli.js +225 -0
- package/lib/app/api/openapi.js +121 -0
- package/lib/app/api/partnerOpenapi.js +104 -0
- package/lib/app/bin/index.js +20 -0
- package/lib/app/bin/javy/javy-arm-linux-v5.0.1 +0 -0
- package/lib/app/bin/javy/javy-arm-macos-v5.0.1 +0 -0
- package/lib/app/bin/javy/javy-x86_64-linux-v5.0.1 +0 -0
- package/lib/app/bin/javy/javy-x86_64-macos-v5.0.1 +0 -0
- package/lib/app/bin/javy/javy-x86_64-windows-v5.0.1 +0 -0
- package/lib/app/commands/config/actions/link.js +189 -0
- package/lib/app/commands/config/actions/use.js +40 -0
- package/lib/app/commands/config/index.js +25 -0
- package/lib/app/commands/config/link.js +11 -0
- package/lib/app/commands/config/use.js +11 -0
- package/lib/app/commands/deploy/actions/deploy.js +196 -0
- package/lib/app/commands/deploy/index.js +11 -0
- package/lib/app/commands/dev/actions/dev.js +206 -0
- package/lib/app/commands/dev/index.js +11 -0
- package/lib/app/commands/generate/actions/extension.js +97 -0
- package/lib/app/commands/generate/actions/generateCheckout.js +58 -0
- package/lib/app/commands/generate/actions/generateFunction.js +56 -0
- package/lib/app/commands/generate/actions/generateTheme.js +128 -0
- package/lib/app/commands/generate/extension.js +11 -0
- package/lib/app/commands/generate/index.js +22 -0
- package/lib/app/commands/index.js +82 -0
- package/lib/app/commands/info/actions/info.js +168 -0
- package/lib/app/commands/info/index.js +11 -0
- package/lib/app/commands/init/actions/init.js +176 -0
- package/lib/app/commands/init/index.js +14 -0
- package/lib/app/commands/versions/actions/list.js +210 -0
- package/lib/app/commands/versions/index.js +22 -0
- package/lib/app/commands/versions/list.js +14 -0
- package/lib/app/constant/code.js +7 -0
- package/lib/app/constant/color.js +18 -0
- package/lib/app/constant/extension.js +16 -0
- package/lib/app/constant/host.js +23 -0
- package/lib/app/constant/sso.js +7 -0
- package/lib/app/index.js +4 -25
- package/lib/app/services/auth/config.js +33 -0
- package/lib/app/services/auth/index.js +9 -0
- package/lib/app/services/auth/oauth-server.js +70 -0
- package/lib/app/services/auth/partner-token.js +45 -0
- package/lib/app/services/auth/sso-token.js +69 -0
- package/lib/app/services/auth/store-token.js +100 -0
- package/lib/app/services/auth/url-builder.js +23 -0
- package/lib/app/services/config/index.js +41 -0
- package/lib/app/services/devServer/app.js +76 -0
- package/lib/app/services/devServer/index.js +103 -0
- package/lib/app/services/devServer/middleware/hmacValidatorMiddleWare.js +20 -0
- package/lib/app/services/devServer/middleware/index.js +5 -0
- package/lib/app/services/devServer/tunnel/index.js +43 -0
- package/lib/app/services/devServer/tunnel/providers/cloudflare.js +364 -0
- package/lib/app/services/devServer/tunnel/providers/ngrok.js +70 -0
- package/lib/app/services/devServer/utils/index.js +5 -0
- package/lib/app/services/devServer/utils/secureCompare.js +5 -0
- package/lib/app/services/devServer/views/app.ejs +133 -0
- package/lib/app/services/extension-build/buildCheckout.js +47 -0
- package/lib/app/services/extension-build/buildFunction.js +57 -0
- package/lib/app/services/extension-build/buildTheme.js +100 -0
- package/lib/app/services/extension-build/index.js +23 -0
- package/lib/app/services/extension-build/plugins/vite-plugin-add-extension-id.js +26 -0
- package/lib/app/services/extension-build/plugins/vite-plugin-transform-extension-html.js +207 -0
- package/lib/app/services/extension-diff/index.js +132 -0
- package/lib/app/services/extension-upsert/index.js +21 -0
- package/lib/app/services/extension-upsert/upsertCheckout.js +44 -0
- package/lib/app/services/extension-upsert/upsertFunction.js +52 -0
- package/lib/app/services/extension-upsert/upsertTheme.js +113 -0
- package/lib/app/services/oss/index.js +45 -0
- package/lib/app/services/partner/index.js +52 -0
- package/lib/app/store/base-store.js +37 -0
- package/lib/app/store/config-store.js +55 -0
- package/lib/app/store/config.js +21 -0
- package/lib/app/store/index.js +14 -0
- package/lib/app/store/install-store.js +41 -0
- package/lib/app/store/sso-store.js +55 -0
- package/lib/app/utils/asyncPool.js +42 -0
- package/lib/app/utils/debug/index.js +16 -0
- package/lib/app/utils/env.js +24 -0
- package/lib/app/utils/error.js +20 -0
- package/lib/app/utils/git.js +20 -0
- package/lib/app/utils/json.js +27 -0
- package/lib/app/utils/path.js +33 -0
- package/lib/app/utils/platform.js +37 -0
- package/lib/app/utils/request/cli.js +72 -0
- package/lib/app/utils/request/debug.js +13 -0
- package/lib/app/utils/request/openapi.js +67 -0
- package/lib/app/utils/request/partnerOpenapi.js +47 -0
- package/lib/app/utils/toml.js +56 -0
- package/lib/app/utils/views/message.js +68 -0
- package/lib/app/utils/views/select.js +36 -0
- package/lib/app/utils/withTempDir.js +55 -0
- package/lib/checkout/api.js +2 -0
- package/lib/function/bin/javy/javy-arm-macos-v5.0.1 +0 -0
- package/lib/oss.js +5 -2
- package/lib/theme-extension/index.js +29 -0
- package/lib/utils/config.js +1 -1
- package/package.json +12 -1
- package/examples/checkout-extension/README.md +0 -19
- package/examples/checkout-extension/extension.config.js +0 -4
- package/examples/checkout-extension/extensions/add-shipping-desc/extension.json +0 -10
- package/examples/checkout-extension/extensions/add-shipping-desc/src/index.js +0 -7
- package/examples/checkout-extension/extensions/ext-1/extension.json +0 -10
- package/examples/checkout-extension/extensions/ext-1/src/content.html +0 -3
- package/examples/checkout-extension/extensions/ext-1/src/index.html +0 -5
- package/examples/checkout-extension/extensions/ext-1/src/index.js +0 -11
- package/examples/checkout-extension/extensions/ext-1/src/script.html +0 -3
- package/examples/checkout-extension/extensions/ext-1/src/style.html +0 -3
- package/examples/checkout-extension/extensions/product-list/extension.json +0 -10
- package/examples/checkout-extension/extensions/product-list/src/index.js +0 -5
- package/examples/checkout-extension/extensions/rewrite-navigate/extension.json +0 -10
- package/examples/checkout-extension/extensions/rewrite-navigate/src/content.html +0 -38
- package/examples/checkout-extension/extensions/rewrite-navigate/src/index.html +0 -5
- package/examples/checkout-extension/extensions/rewrite-navigate/src/index.js +0 -12
- package/examples/checkout-extension/extensions/rewrite-navigate/src/script.html +0 -26
- package/examples/checkout-extension/extensions/rewrite-navigate/src/style.html +0 -23
- package/examples/checkout-extension/package-lock.json +0 -121
- package/examples/checkout-extension/package.json +0 -17
- /package/lib/{app → theme-extension}/api/index.js +0 -0
- /package/lib/{app → theme-extension}/commands/build.js +0 -0
- /package/lib/{app → theme-extension}/commands/connect.js +0 -0
- /package/lib/{app → theme-extension}/commands/create.js +0 -0
- /package/lib/{app → theme-extension}/commands/deploy.js +0 -0
- /package/lib/{app → theme-extension}/commands/list.js +0 -0
- /package/lib/{app → theme-extension}/commands/release.js +0 -0
- /package/lib/{app → theme-extension}/commands/serve.js +0 -0
- /package/lib/{app → theme-extension}/commands/versions.js +0 -0
- /package/lib/{app → theme-extension}/template/basic-app/README.md +0 -0
- /package/lib/{app → theme-extension}/template/basic-app/extension.config.json +0 -0
- /package/lib/{app → theme-extension}/template/basic-app/package.json +0 -0
- /package/lib/{app → theme-extension}/template/basic-app/theme-app/assets/index.css +0 -0
- /package/lib/{app → theme-extension}/template/basic-app/theme-app/assets-manifest.json +0 -0
- /package/lib/{app → theme-extension}/template/basic-app/theme-app/blocks/index.liquid +0 -0
- /package/lib/{app → theme-extension}/template/basic-app/theme-app/locales/ar-SA.json +0 -0
- /package/lib/{app → theme-extension}/template/basic-app/theme-app/locales/de-DE.json +0 -0
- /package/lib/{app → theme-extension}/template/basic-app/theme-app/locales/en-US.json +0 -0
- /package/lib/{app → theme-extension}/template/basic-app/theme-app/locales/es-ES.json +0 -0
- /package/lib/{app → theme-extension}/template/basic-app/theme-app/locales/fr-FR.json +0 -0
- /package/lib/{app → theme-extension}/template/basic-app/theme-app/locales/id-ID.json +0 -0
- /package/lib/{app → theme-extension}/template/basic-app/theme-app/locales/it-IT.json +0 -0
- /package/lib/{app → theme-extension}/template/basic-app/theme-app/locales/ja-JP.json +0 -0
- /package/lib/{app → theme-extension}/template/basic-app/theme-app/locales/ko-KR.json +0 -0
- /package/lib/{app → theme-extension}/template/basic-app/theme-app/locales/nl-NL.json +0 -0
- /package/lib/{app → theme-extension}/template/basic-app/theme-app/locales/pl-PL.json +0 -0
- /package/lib/{app → theme-extension}/template/basic-app/theme-app/locales/pt-PT.json +0 -0
- /package/lib/{app → theme-extension}/template/basic-app/theme-app/locales/ru-RU.json +0 -0
- /package/lib/{app → theme-extension}/template/basic-app/theme-app/locales/th-TH.json +0 -0
- /package/lib/{app → theme-extension}/template/basic-app/theme-app/locales/zh-CN.json +0 -0
- /package/lib/{app → theme-extension}/template/basic-app/theme-app/locales/zh-TW.json +0 -0
- /package/lib/{app → theme-extension}/template/basic-app/theme-app/snippets/index.liquid +0 -0
- /package/lib/{app → theme-extension}/template/embed-app/README.md +0 -0
- /package/lib/{app → theme-extension}/template/embed-app/extension.config.json +0 -0
- /package/lib/{app → theme-extension}/template/embed-app/package.json +0 -0
- /package/lib/{app → theme-extension}/template/embed-app/theme-app/assets-manifest.json +0 -0
- /package/lib/{app → theme-extension}/template/embed-app/theme-app/blocks/index.liquid +0 -0
- /package/lib/{app → theme-extension}/template/embed-app/theme-app/locales/ar-SA.json +0 -0
- /package/lib/{app → theme-extension}/template/embed-app/theme-app/locales/de-DE.json +0 -0
- /package/lib/{app → theme-extension}/template/embed-app/theme-app/locales/en-US.json +0 -0
- /package/lib/{app → theme-extension}/template/embed-app/theme-app/locales/es-ES.json +0 -0
- /package/lib/{app → theme-extension}/template/embed-app/theme-app/locales/fr-FR.json +0 -0
- /package/lib/{app → theme-extension}/template/embed-app/theme-app/locales/id-ID.json +0 -0
- /package/lib/{app → theme-extension}/template/embed-app/theme-app/locales/it-IT.json +0 -0
- /package/lib/{app → theme-extension}/template/embed-app/theme-app/locales/ja-JP.json +0 -0
- /package/lib/{app → theme-extension}/template/embed-app/theme-app/locales/ko-KR.json +0 -0
- /package/lib/{app → theme-extension}/template/embed-app/theme-app/locales/nl-NL.json +0 -0
- /package/lib/{app → theme-extension}/template/embed-app/theme-app/locales/pl-PL.json +0 -0
- /package/lib/{app → theme-extension}/template/embed-app/theme-app/locales/pt-PT.json +0 -0
- /package/lib/{app → theme-extension}/template/embed-app/theme-app/locales/ru-RU.json +0 -0
- /package/lib/{app → theme-extension}/template/embed-app/theme-app/locales/th-TH.json +0 -0
- /package/lib/{app → theme-extension}/template/embed-app/theme-app/locales/zh-CN.json +0 -0
- /package/lib/{app → theme-extension}/template/embed-app/theme-app/locales/zh-TW.json +0 -0
- /package/lib/{app → theme-extension}/template/embed-app/theme-app/snippets/index.liquid +0 -0
- /package/lib/{app → theme-extension}/template/embed-app/theme-app/snippets/index_css.liquid +0 -0
- /package/lib/{app → theme-extension}/utils/config.js +0 -0
- /package/lib/{app → theme-extension}/utils/index.js +0 -0
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
const { exec, spawn } = require('child_process');
|
|
2
|
+
const os = require('os');
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const https = require('https');
|
|
6
|
+
|
|
7
|
+
// Cloudflare版本配置
|
|
8
|
+
const CLOUDFLARE_VERSION = '2024.8.2';
|
|
9
|
+
const CLOUDFLARE_BASE_URL = `https://github.com/cloudflare/cloudflared/releases/download/${CLOUDFLARE_VERSION}`;
|
|
10
|
+
|
|
11
|
+
// 不同平台和架构的二进制文件映射配置
|
|
12
|
+
const PLATFORM_CONFIGS = {
|
|
13
|
+
linux: {
|
|
14
|
+
x64: 'cloudflared-linux-amd64',
|
|
15
|
+
arm64: 'cloudflared-linux-arm64',
|
|
16
|
+
arm: 'cloudflared-linux-arm',
|
|
17
|
+
ia32: 'cloudflared-linux-386'
|
|
18
|
+
},
|
|
19
|
+
darwin: {
|
|
20
|
+
x64: 'cloudflared-darwin-amd64.tgz',
|
|
21
|
+
arm64: 'cloudflared-darwin-arm64.tgz'
|
|
22
|
+
},
|
|
23
|
+
win32: {
|
|
24
|
+
x64: 'cloudflared-windows-amd64.exe',
|
|
25
|
+
ia32: 'cloudflared-windows-386.exe',
|
|
26
|
+
arm64: 'cloudflared-windows-amd64.exe'
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
// 根据当前系统平台和架构获取cloudflared二进制文件的下载URL
|
|
31
|
+
function getDownloadUrl() {
|
|
32
|
+
const platform = os.platform();
|
|
33
|
+
const arch = os.arch();
|
|
34
|
+
|
|
35
|
+
const platformConfig = PLATFORM_CONFIGS[platform];
|
|
36
|
+
if (!platformConfig) {
|
|
37
|
+
throw new Error(`Unsupported platform: ${platform}`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const filename = platformConfig[arch];
|
|
41
|
+
if (!filename) {
|
|
42
|
+
throw new Error(`Unsupported architecture: ${platform}-${arch}`);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
url: `${CLOUDFLARE_BASE_URL}/${filename}`,
|
|
47
|
+
filename,
|
|
48
|
+
isCompressed: filename.endsWith('.tgz')
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// 获取cloudflared二进制文件的本地存储路径
|
|
53
|
+
function getBinaryPath() {
|
|
54
|
+
const platform = os.platform();
|
|
55
|
+
const extension = platform === 'win32' ? '.exe' : '';
|
|
56
|
+
const cloudflaredDir = path.join(process.env.APP_PROJECT_DIR, 'bin', 'cloudflared');
|
|
57
|
+
|
|
58
|
+
// 检查路径状态并确保目录存在
|
|
59
|
+
try {
|
|
60
|
+
const stat = fs.statSync(cloudflaredDir);
|
|
61
|
+
if (!stat.isDirectory()) {
|
|
62
|
+
console.log('⚠️ Removing conflicting file at cloudflared directory path...');
|
|
63
|
+
fs.unlinkSync(cloudflaredDir);
|
|
64
|
+
fs.mkdirSync(cloudflaredDir, { recursive: true });
|
|
65
|
+
}
|
|
66
|
+
// 如果已经是目录,无需处理
|
|
67
|
+
} catch (error) {
|
|
68
|
+
// 路径不存在,直接创建目录
|
|
69
|
+
fs.mkdirSync(cloudflaredDir, { recursive: true });
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return path.join(cloudflaredDir, `cloudflared${extension}`);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// 比较两个版本号,返回值:>0表示versionA更新,<0表示versionB更新,=0表示相同
|
|
76
|
+
function compareVersions(versionA, versionB) {
|
|
77
|
+
const parseVersion = (v) => v.split('.').map(Number);
|
|
78
|
+
const [majorA, minorA, patchA] = parseVersion(versionA);
|
|
79
|
+
const [majorB, minorB, patchB] = parseVersion(versionB);
|
|
80
|
+
|
|
81
|
+
if (majorA !== majorB) return majorA - majorB;
|
|
82
|
+
if (minorA !== minorB) return minorA - minorB;
|
|
83
|
+
return patchA - patchB;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// 检查本地cloudflared版本是否满足要求
|
|
87
|
+
async function checkVersion(binaryPath) {
|
|
88
|
+
return new Promise((resolve) => {
|
|
89
|
+
// 如果二进制文件不存在,返回false
|
|
90
|
+
if (!fs.existsSync(binaryPath)) return resolve(false);
|
|
91
|
+
|
|
92
|
+
// 执行version命令获取当前版本
|
|
93
|
+
exec(`"${binaryPath}" --version`, (error, stdout) => {
|
|
94
|
+
if (error) return resolve(false);
|
|
95
|
+
const versionMatch = stdout.match(/cloudflared version (\d+\.\d+\.\d+)/);
|
|
96
|
+
if (versionMatch) {
|
|
97
|
+
const currentVersion = versionMatch[1];
|
|
98
|
+
const needsUpdate = compareVersions(CLOUDFLARE_VERSION, currentVersion) > 0;
|
|
99
|
+
console.log(`Current version: ${currentVersion}, Target version: ${CLOUDFLARE_VERSION}`);
|
|
100
|
+
resolve(!needsUpdate);
|
|
101
|
+
} else {
|
|
102
|
+
resolve(false);
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// 下载文件,支持总体10秒下载时长限制和HTTP重定向处理
|
|
109
|
+
async function downloadFile(url, destination) {
|
|
110
|
+
return new Promise(async (resolve, reject) => {
|
|
111
|
+
console.log(`📥 Downloading: ${path.basename(url)}`);
|
|
112
|
+
|
|
113
|
+
const file = fs.createWriteStream(destination);
|
|
114
|
+
let isFinished = false;
|
|
115
|
+
|
|
116
|
+
// 设置总体下载时长限制:10秒,不管下载速度如何
|
|
117
|
+
const downloadTimeoutId = setTimeout(() => {
|
|
118
|
+
if (!isFinished) {
|
|
119
|
+
isFinished = true;
|
|
120
|
+
request.destroy();
|
|
121
|
+
file.close();
|
|
122
|
+
if (fs.existsSync(destination)) fs.unlinkSync(destination);
|
|
123
|
+
reject(new Error('Download took too long'));
|
|
124
|
+
}
|
|
125
|
+
}, 10000);
|
|
126
|
+
|
|
127
|
+
const request = https.get(url, (response) => {
|
|
128
|
+
// 处理HTTP重定向
|
|
129
|
+
if (response.statusCode === 301 || response.statusCode === 302) {
|
|
130
|
+
const redirectUrl = response.headers.location;
|
|
131
|
+
if (redirectUrl) {
|
|
132
|
+
clearTimeout(downloadTimeoutId);
|
|
133
|
+
file.close();
|
|
134
|
+
if (fs.existsSync(destination)) fs.unlinkSync(destination);
|
|
135
|
+
// 修复异步错误处理
|
|
136
|
+
downloadFile(redirectUrl, destination)
|
|
137
|
+
.then(() => resolve())
|
|
138
|
+
.catch((error) => reject(error));
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// 检查HTTP状态码
|
|
144
|
+
if (response.statusCode !== 200) {
|
|
145
|
+
clearTimeout(downloadTimeoutId);
|
|
146
|
+
file.close();
|
|
147
|
+
if (fs.existsSync(destination)) fs.unlinkSync(destination);
|
|
148
|
+
return reject(new Error(`Download failed: HTTP ${response.statusCode}`));
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// 将响应数据写入文件
|
|
152
|
+
response.pipe(file);
|
|
153
|
+
|
|
154
|
+
file.on('finish', () => {
|
|
155
|
+
if (!isFinished) {
|
|
156
|
+
isFinished = true;
|
|
157
|
+
clearTimeout(downloadTimeoutId);
|
|
158
|
+
file.close();
|
|
159
|
+
console.log('✅ Download completed');
|
|
160
|
+
resolve();
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
file.on('error', (err) => {
|
|
165
|
+
if (!isFinished) {
|
|
166
|
+
isFinished = true;
|
|
167
|
+
clearTimeout(downloadTimeoutId);
|
|
168
|
+
file.close();
|
|
169
|
+
if (fs.existsSync(destination)) fs.unlinkSync(destination);
|
|
170
|
+
reject(err);
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
// 处理请求错误
|
|
176
|
+
request.on('error', (err) => {
|
|
177
|
+
if (!isFinished) {
|
|
178
|
+
isFinished = true;
|
|
179
|
+
clearTimeout(downloadTimeoutId);
|
|
180
|
+
file.close();
|
|
181
|
+
if (fs.existsSync(destination)) fs.unlinkSync(destination);
|
|
182
|
+
reject(err);
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
// 设置请求超时(服务器无响应):5秒
|
|
187
|
+
request.setTimeout(5000, () => {
|
|
188
|
+
if (!isFinished) {
|
|
189
|
+
request.destroy();
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// 解压tgz文件(仅macOS平台的cloudflared需要解压)
|
|
196
|
+
async function extractTgz(tgzPath, extractDir) {
|
|
197
|
+
return new Promise((resolve, reject) => {
|
|
198
|
+
console.log('📦 Extracting file...');
|
|
199
|
+
const filename = path.basename(tgzPath);
|
|
200
|
+
exec(`tar -xzf "${filename}"`, { cwd: extractDir }, (error) => {
|
|
201
|
+
if (error) return reject(new Error(`Extraction failed: ${error.message}`));
|
|
202
|
+
console.log('✅ Extraction completed');
|
|
203
|
+
resolve();
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// 安装cloudflared(如果不存在或版本过低)
|
|
209
|
+
async function installCloudflared() {
|
|
210
|
+
console.log('🔍 Checking cloudflared...');
|
|
211
|
+
const binaryPath = getBinaryPath();
|
|
212
|
+
|
|
213
|
+
// 检查现有版本是否满足要求
|
|
214
|
+
if (await checkVersion(binaryPath)) {
|
|
215
|
+
console.log('✅ cloudflared is already at target version or higher');
|
|
216
|
+
return; // 成功时不需要返回值
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
console.log('🔄 Installing cloudflared...');
|
|
220
|
+
try {
|
|
221
|
+
const { url, filename, isCompressed } = getDownloadUrl();
|
|
222
|
+
const platform = os.platform();
|
|
223
|
+
|
|
224
|
+
// macOS平台需要解压tgz文件
|
|
225
|
+
if (isCompressed && platform === 'darwin') {
|
|
226
|
+
const tgzPath = path.join(path.dirname(binaryPath), filename);
|
|
227
|
+
await downloadFile(url, tgzPath);
|
|
228
|
+
await extractTgz(tgzPath, path.dirname(binaryPath));
|
|
229
|
+
const extractedPath = path.join(path.dirname(binaryPath), 'cloudflared');
|
|
230
|
+
if (fs.existsSync(extractedPath)) fs.renameSync(extractedPath, binaryPath);
|
|
231
|
+
if (fs.existsSync(tgzPath)) fs.unlinkSync(tgzPath);
|
|
232
|
+
} else {
|
|
233
|
+
// Linux直接下载二进制文件
|
|
234
|
+
await downloadFile(url, binaryPath);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// 为非Windows平台设置可执行权限
|
|
238
|
+
if (platform !== 'win32') {
|
|
239
|
+
fs.chmodSync(binaryPath, '755');
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// 验证安装是否成功
|
|
243
|
+
const isWorking = await new Promise((resolve) => {
|
|
244
|
+
exec(`"${binaryPath}" --version`, (error) => resolve(!error));
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
if (isWorking) {
|
|
248
|
+
console.log('✅ cloudflared installed successfully!');
|
|
249
|
+
return; // 成功时不需要返回值
|
|
250
|
+
}
|
|
251
|
+
throw new Error('Installation verification failed');
|
|
252
|
+
} catch (error) {
|
|
253
|
+
console.error('❌ Installation failed:', error.message);
|
|
254
|
+
// 抛出具体错误而不是返回false,这样上层能获得具体的失败原因
|
|
255
|
+
throw new Error(`cloudflared installation failed: ${error.message}`);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// 启动Cloudflare Quick Tunnel,返回包含url和close方法的对象
|
|
260
|
+
async function startQuickTunnel(port) {
|
|
261
|
+
console.log('🚀 Starting Cloudflare Tunnel...');
|
|
262
|
+
|
|
263
|
+
const binaryPath = getBinaryPath();
|
|
264
|
+
// 启动cloudflared进程,创建快速隧道
|
|
265
|
+
const tunnel = spawn(binaryPath, ['tunnel', '--url', `http://localhost:${port}`, '--no-autoupdate'], {
|
|
266
|
+
stdio: 'pipe'
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
let tunnelUrl = null;
|
|
270
|
+
|
|
271
|
+
// 设置10秒超时
|
|
272
|
+
const timeout = setTimeout(() => {
|
|
273
|
+
if (!tunnelUrl) {
|
|
274
|
+
console.error('❌ Tunnel startup timeout');
|
|
275
|
+
try {
|
|
276
|
+
tunnel.kill();
|
|
277
|
+
} catch {}
|
|
278
|
+
}
|
|
279
|
+
}, 10000);
|
|
280
|
+
|
|
281
|
+
// 从cloudflared输出中解析隧道URL
|
|
282
|
+
const tryParse = (buf) => {
|
|
283
|
+
const output = String(buf);
|
|
284
|
+
const match = output.match(/https:\/\/[a-zA-Z0-9-]+\.trycloudflare\.com/);
|
|
285
|
+
if (match && !tunnelUrl) {
|
|
286
|
+
tunnelUrl = match[0];
|
|
287
|
+
}
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
// 监听标准输出和错误输出
|
|
291
|
+
tunnel.stdout.on('data', tryParse);
|
|
292
|
+
tunnel.stderr.on('data', tryParse);
|
|
293
|
+
|
|
294
|
+
return await new Promise((resolve, reject) => {
|
|
295
|
+
const finishIfReady = () => {
|
|
296
|
+
if (tunnelUrl) {
|
|
297
|
+
clearTimeout(timeout);
|
|
298
|
+
resolve({
|
|
299
|
+
url: tunnelUrl,
|
|
300
|
+
close: () => {
|
|
301
|
+
try {
|
|
302
|
+
tunnel.kill('SIGTERM');
|
|
303
|
+
} catch {}
|
|
304
|
+
}
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
// 监听隧道进程事件
|
|
310
|
+
tunnel.on('error', (err) => {
|
|
311
|
+
clearTimeout(timeout);
|
|
312
|
+
reject(new Error(`Tunnel process error: ${err.message}`));
|
|
313
|
+
});
|
|
314
|
+
tunnel.on('close', (code) => {
|
|
315
|
+
clearTimeout(timeout);
|
|
316
|
+
if (!tunnelUrl) return reject(new Error(`Tunnel exited with code=${code}`));
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
// 每100ms检查一次隧道URL是否获取到
|
|
320
|
+
const iv = setInterval(() => {
|
|
321
|
+
if (tunnelUrl) {
|
|
322
|
+
clearInterval(iv);
|
|
323
|
+
finishIfReady();
|
|
324
|
+
}
|
|
325
|
+
}, 100);
|
|
326
|
+
|
|
327
|
+
// 兜底:31秒后停止轮询
|
|
328
|
+
setTimeout(() => {
|
|
329
|
+
clearInterval(iv);
|
|
330
|
+
if (!tunnelUrl) {
|
|
331
|
+
reject(new Error('Tunnel not ready'));
|
|
332
|
+
}
|
|
333
|
+
}, 31000);
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// Cloudflare隧道管理类
|
|
338
|
+
class CloudflareTunnel {
|
|
339
|
+
constructor(port) {
|
|
340
|
+
this.port = port;
|
|
341
|
+
this._closer = null;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// 安装cloudflared(如果需要)并启动Quick Tunnel
|
|
345
|
+
async start() {
|
|
346
|
+
// installCloudflared现在会直接抛出错误,不需要检查返回值
|
|
347
|
+
await installCloudflared();
|
|
348
|
+
const { url, close } = await startQuickTunnel(this.port);
|
|
349
|
+
this._closer = close;
|
|
350
|
+
return { url, close };
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// 关闭隧道
|
|
354
|
+
async close() {
|
|
355
|
+
if (this._closer) {
|
|
356
|
+
try {
|
|
357
|
+
await this._closer();
|
|
358
|
+
} catch {}
|
|
359
|
+
this._closer = null;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
module.exports = CloudflareTunnel;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
const ngrok = require('@ngrok/ngrok');
|
|
2
|
+
const chalk = require('chalk');
|
|
3
|
+
const inquirer = require('inquirer');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { writeEnvWithDotenv } = require('../../../../utils/env');
|
|
6
|
+
|
|
7
|
+
module.exports = class NgrokTunnel {
|
|
8
|
+
constructor(port, token, domain) {
|
|
9
|
+
this.port = port;
|
|
10
|
+
this.token = token;
|
|
11
|
+
this.domain = domain;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async start() {
|
|
15
|
+
if (!this.token) {
|
|
16
|
+
console.log(
|
|
17
|
+
`\nGet your ngrok token from ${chalk.underline.green('https://dashboard.ngrok.com/get-started/your-authtoken')}`
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
const token = await inquirer
|
|
21
|
+
.prompt({
|
|
22
|
+
type: 'input',
|
|
23
|
+
name: 'token',
|
|
24
|
+
message: 'Please input your ngrok token:',
|
|
25
|
+
validate: (value) => {
|
|
26
|
+
return value.trim() !== '' ? true : "NGROK_AUTHTOKEN can't be empty";
|
|
27
|
+
}
|
|
28
|
+
})
|
|
29
|
+
.then((r) => r.token);
|
|
30
|
+
|
|
31
|
+
this.token = token;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (!this.domain) {
|
|
35
|
+
console.log(
|
|
36
|
+
`\nGet your ngrok domain from ${chalk.underline.green('https://dashboard.ngrok.com/domains')}`
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
const domain = await inquirer
|
|
40
|
+
.prompt({
|
|
41
|
+
type: 'input',
|
|
42
|
+
name: 'domain',
|
|
43
|
+
message: 'Please input your ngrok domain:'
|
|
44
|
+
})
|
|
45
|
+
.then((r) => r.domain);
|
|
46
|
+
this.domain = domain;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// 存储 token(创建或更新 .env,而不覆盖其它键)
|
|
50
|
+
await writeEnvWithDotenv(path.join(process.cwd(), '.env'), {
|
|
51
|
+
NGROK_AUTHTOKEN: this.token,
|
|
52
|
+
NGROK_DOMAIN: this.domain
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const listener = await ngrok
|
|
56
|
+
.forward({
|
|
57
|
+
addr: this.port,
|
|
58
|
+
authtoken: this.token,
|
|
59
|
+
domain: this.domain
|
|
60
|
+
})
|
|
61
|
+
.catch((err) => {
|
|
62
|
+
throw err;
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
url: listener.url(),
|
|
67
|
+
close: () => listener.close()
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
};
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>Shoplazza Dev Server</title>
|
|
7
|
+
<style>
|
|
8
|
+
* {
|
|
9
|
+
margin: 0;
|
|
10
|
+
padding: 0;
|
|
11
|
+
box-sizing: border-box;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
body {
|
|
15
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', sans-serif;
|
|
16
|
+
background: linear-gradient(135deg, #f5f5f5 0%, #e8e8e8 100%);
|
|
17
|
+
min-height: 100vh;
|
|
18
|
+
display: flex;
|
|
19
|
+
align-items: center;
|
|
20
|
+
justify-content: center;
|
|
21
|
+
color: #333;
|
|
22
|
+
line-height: 1.6;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.container {
|
|
26
|
+
background: rgba(255, 255, 255, 0.95);
|
|
27
|
+
backdrop-filter: blur(20px);
|
|
28
|
+
border-radius: 20px;
|
|
29
|
+
padding: 3rem 2.5rem;
|
|
30
|
+
text-align: center;
|
|
31
|
+
max-width: 500px;
|
|
32
|
+
width: 90%;
|
|
33
|
+
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
|
|
34
|
+
border: 1px solid rgba(255, 255, 255, 0.2);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.logo {
|
|
38
|
+
width: 80px;
|
|
39
|
+
height: 80px;
|
|
40
|
+
background: linear-gradient(135deg, #e53e3e, #c53030);
|
|
41
|
+
border-radius: 20px;
|
|
42
|
+
margin: 0 auto 2rem;
|
|
43
|
+
display: flex;
|
|
44
|
+
align-items: center;
|
|
45
|
+
justify-content: center;
|
|
46
|
+
font-size: 2rem;
|
|
47
|
+
font-weight: bold;
|
|
48
|
+
color: white;
|
|
49
|
+
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
h1 {
|
|
53
|
+
font-size: 2.2rem;
|
|
54
|
+
margin-bottom: 1rem;
|
|
55
|
+
background: linear-gradient(135deg, #e53e3e, #c53030);
|
|
56
|
+
-webkit-background-clip: text;
|
|
57
|
+
-webkit-text-fill-color: transparent;
|
|
58
|
+
background-clip: text;
|
|
59
|
+
font-weight: 700;
|
|
60
|
+
letter-spacing: -0.5px;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
p {
|
|
64
|
+
font-size: 1.1rem;
|
|
65
|
+
color: #666;
|
|
66
|
+
margin-bottom: 2rem;
|
|
67
|
+
opacity: 0.8;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.status {
|
|
71
|
+
display: inline-flex;
|
|
72
|
+
align-items: center;
|
|
73
|
+
gap: 0.5rem;
|
|
74
|
+
background: #e8f5e8;
|
|
75
|
+
color: #2e7d2e;
|
|
76
|
+
padding: 0.6rem 1.2rem;
|
|
77
|
+
border-radius: 50px;
|
|
78
|
+
font-size: 0.9rem;
|
|
79
|
+
font-weight: 500;
|
|
80
|
+
border: 1px solid #c3e6c3;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.status-dot {
|
|
84
|
+
width: 8px;
|
|
85
|
+
height: 8px;
|
|
86
|
+
background: #4ade80;
|
|
87
|
+
border-radius: 50%;
|
|
88
|
+
animation: pulse 2s infinite;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
@keyframes pulse {
|
|
92
|
+
0%, 100% { opacity: 1; }
|
|
93
|
+
50% { opacity: 0.5; }
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.footer {
|
|
97
|
+
margin-top: 2rem;
|
|
98
|
+
font-size: 0.85rem;
|
|
99
|
+
color: #999;
|
|
100
|
+
opacity: 0.7;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
@media (max-width: 480px) {
|
|
104
|
+
.container {
|
|
105
|
+
padding: 2rem 1.5rem;
|
|
106
|
+
margin: 1rem;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
h1 {
|
|
110
|
+
font-size: 1.8rem;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
p {
|
|
114
|
+
font-size: 1rem;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
</style>
|
|
118
|
+
</head>
|
|
119
|
+
<body>
|
|
120
|
+
<div class="container">
|
|
121
|
+
<div class="logo">S</div>
|
|
122
|
+
<h1>Welcome to Shoplazza</h1>
|
|
123
|
+
<p>Development server is running smoothly</p>
|
|
124
|
+
<div class="status">
|
|
125
|
+
<div class="status-dot"></div>
|
|
126
|
+
Server Active
|
|
127
|
+
</div>
|
|
128
|
+
<div class="footer">
|
|
129
|
+
Ready to build amazing apps
|
|
130
|
+
</div>
|
|
131
|
+
</div>
|
|
132
|
+
</body>
|
|
133
|
+
</html>
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const { build } = require('vite');
|
|
3
|
+
const { vitePluginAddExtensionId } = require('./plugins/vite-plugin-add-extension-id');
|
|
4
|
+
const { vitePluginTransformExtensionHtml } = require('./plugins/vite-plugin-transform-extension-html');
|
|
5
|
+
|
|
6
|
+
async function buildCheckout(extension) {
|
|
7
|
+
// build checkout extension
|
|
8
|
+
const extensionDir = path.dirname(extension.configPath);
|
|
9
|
+
|
|
10
|
+
// entry file
|
|
11
|
+
const entryFile = path.join(extensionDir, 'src', 'index.js');
|
|
12
|
+
|
|
13
|
+
// outputDir
|
|
14
|
+
const outputDir = path.join(extensionDir, '..', '..', 'app-deploy');
|
|
15
|
+
|
|
16
|
+
const pageEntry = { [extension.extension_name]: entryFile };
|
|
17
|
+
|
|
18
|
+
const res = await build({
|
|
19
|
+
plugins: [vitePluginTransformExtensionHtml(), vitePluginAddExtensionId(extension.extension_name)],
|
|
20
|
+
root: path.dirname(entryFile),
|
|
21
|
+
build: {
|
|
22
|
+
minify: false,
|
|
23
|
+
emptyOutDir: false,
|
|
24
|
+
copyPublicDir: false,
|
|
25
|
+
rollupOptions: {
|
|
26
|
+
input: pageEntry,
|
|
27
|
+
output: {
|
|
28
|
+
entryFileNames: '[name].[hash].js',
|
|
29
|
+
chunkFileNames: '[name].[hash].js',
|
|
30
|
+
assetFileNames: '[name].[hash].[ext]',
|
|
31
|
+
compact: true,
|
|
32
|
+
inlineDynamicImports: false,
|
|
33
|
+
dir: outputDir
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// 返回打包后的文件路径
|
|
40
|
+
const outputFile = path.join(outputDir, res.output[0].fileName);
|
|
41
|
+
|
|
42
|
+
return outputFile;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
module.exports = {
|
|
46
|
+
buildCheckout
|
|
47
|
+
};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const crypto = require('crypto');
|
|
4
|
+
const { getLibPath } = require('../../bin');
|
|
5
|
+
const { exec } = require('child_process');
|
|
6
|
+
const { promisify } = require('util');
|
|
7
|
+
|
|
8
|
+
const execPromise = promisify(exec);
|
|
9
|
+
|
|
10
|
+
function hashFileStream(filePath, algorithm = 'md5') {
|
|
11
|
+
return new Promise((resolve, reject) => {
|
|
12
|
+
const hash = crypto.createHash(algorithm);
|
|
13
|
+
const stream = fs.createReadStream(filePath);
|
|
14
|
+
|
|
15
|
+
stream.on('data', (chunk) => hash.update(chunk));
|
|
16
|
+
stream.on('end', () => resolve(hash.digest('hex')));
|
|
17
|
+
stream.on('error', reject);
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async function buildFunction(extension) {
|
|
22
|
+
// build function extension
|
|
23
|
+
const extensionDir = path.dirname(extension.configPath);
|
|
24
|
+
|
|
25
|
+
// entry file
|
|
26
|
+
const entryFile = path.join(extensionDir, 'src', 'index.js');
|
|
27
|
+
|
|
28
|
+
// entry file hash
|
|
29
|
+
const entryFileHash = await hashFileStream(entryFile);
|
|
30
|
+
|
|
31
|
+
// output file
|
|
32
|
+
const outputFile = path.join(
|
|
33
|
+
extensionDir,
|
|
34
|
+
'..',
|
|
35
|
+
'..',
|
|
36
|
+
'app-deploy',
|
|
37
|
+
`${extension.extension_name}.${entryFileHash}.wasm`
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
// javy path
|
|
41
|
+
const javyPath = getLibPath('javy');
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
const { stderr } = await execPromise(`"${javyPath}" build "${entryFile}" -o "${outputFile}"`);
|
|
45
|
+
if (stderr) {
|
|
46
|
+
throw new Error(stderr);
|
|
47
|
+
}
|
|
48
|
+
} catch (err) {
|
|
49
|
+
throw err;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return outputFile;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
module.exports = {
|
|
56
|
+
buildFunction
|
|
57
|
+
};
|