shoplazza-cli 1.0.5 → 1.0.7-beta.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/bin/shoplazza +3 -14
- package/lib/app/api/index.js +96 -0
- package/lib/app/commands/build.js +101 -53
- package/lib/app/commands/create.js +101 -0
- package/lib/app/commands/deploy.js +44 -208
- package/lib/app/commands/list.js +35 -0
- package/lib/app/commands/serve.js +171 -0
- package/lib/app/commands/versions.js +52 -0
- package/lib/app/index.js +19 -30
- package/lib/app/template/basic-app/README.md +98 -0
- package/lib/app/template/basic-app/package.json +16 -0
- package/lib/app/template/basic-app/theme-app/assets/index.css +4 -0
- package/lib/app/template/basic-app/theme-app/assets-manifest.json +1 -0
- package/lib/app/template/basic-app/theme-app/blocks/index.liquid +16 -0
- package/lib/app/template/basic-app/theme-app/snippets/index.liquid +8 -0
- package/lib/app/template/basic-app/theme-extension.config.json +4 -0
- package/lib/app/template/embed-app/README.md +97 -0
- package/lib/app/template/embed-app/package.json +16 -0
- package/lib/app/template/embed-app/theme-app/assets-manifest.json +1 -0
- package/lib/app/template/embed-app/theme-app/blocks/index.liquid +18 -0
- package/lib/app/template/embed-app/theme-app/locales/ar-SA.json +3 -0
- package/lib/app/template/embed-app/theme-app/locales/de-DE.json +3 -0
- package/lib/app/template/embed-app/theme-app/locales/en-US.json +3 -0
- package/lib/app/template/embed-app/theme-app/locales/es-ES.json +3 -0
- package/lib/app/template/embed-app/theme-app/locales/fr-FR.json +3 -0
- package/lib/app/template/embed-app/theme-app/locales/id-ID.json +3 -0
- package/lib/app/template/embed-app/theme-app/locales/it-IT.json +3 -0
- package/lib/app/template/embed-app/theme-app/locales/ja-JP.json +3 -0
- package/lib/app/template/embed-app/theme-app/locales/ko-KR.json +3 -0
- package/lib/app/template/embed-app/theme-app/locales/nl-NL.json +3 -0
- package/lib/app/template/embed-app/theme-app/locales/pl-PL.json +3 -0
- package/lib/app/template/embed-app/theme-app/locales/pt-PT.json +3 -0
- package/lib/app/template/embed-app/theme-app/locales/ru-RU.json +3 -0
- package/lib/app/template/embed-app/theme-app/locales/th-TH.json +3 -0
- package/lib/app/template/embed-app/theme-app/locales/zh-CN.json +3 -0
- package/lib/app/template/embed-app/theme-app/locales/zh-TW.json +3 -0
- package/lib/app/template/embed-app/theme-app/snippets/index.liquid +8 -0
- package/lib/app/template/embed-app/theme-app/snippets/index_css.liquid +6 -0
- package/lib/app/template/embed-app/theme-extension.config.json +4 -0
- package/lib/app/utils/config.js +32 -0
- package/lib/app/utils/index.js +213 -0
- package/lib/checkout/api.js +11 -34
- package/lib/checkout/build/plugin/vite-plugin-transform-extension-html.js +1 -1
- package/lib/checkout/dev/index.js +2 -1
- package/lib/commands/login.js +1 -1
- package/lib/commands/logout.js +1 -1
- package/lib/openAPI/index.js +21 -10
- package/lib/oss.js +99 -0
- package/lib/utils.js +52 -0
- package/package.json +2 -2
- package/lib/app/commands/generate.js +0 -50
- package/lib/app/commands/publish.js +0 -52
- package/lib/app/extensions/index.js +0 -13
- package/lib/app/extensions/theme-app.js +0 -103
- package/lib/app/inquirers/version.js +0 -131
- /package/lib/{app → common}/constants.js +0 -0
- /package/lib/{app → common}/db/partner.js +0 -0
- /package/lib/{app → common}/inquirers/choose-app.js +0 -0
- /package/lib/{app → common}/inquirers/choose-partner.js +0 -0
- /package/lib/{app → common}/log.js +0 -0
- /package/lib/{app → common}/login.js +0 -0
- /package/lib/{app → common}/logout.js +0 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const { get } = require('../../db/user');
|
|
3
|
+
|
|
4
|
+
const WORKSPACE_PATH = process.cwd();
|
|
5
|
+
|
|
6
|
+
const THEME_APP_DIR_PATH = path.resolve(WORKSPACE_PATH, 'theme-app');
|
|
7
|
+
|
|
8
|
+
const THEME_APP_CONFIG_PATH = path.resolve(WORKSPACE_PATH, 'theme-extension.config.json')
|
|
9
|
+
|
|
10
|
+
const STORE_DOMAIN = get('store_domain');
|
|
11
|
+
|
|
12
|
+
const EXCHANGE_TOKEN = get('exchange_token');
|
|
13
|
+
|
|
14
|
+
const THEME_APP_TYPE = {
|
|
15
|
+
BASIC_APP: {
|
|
16
|
+
description: 'basic extension(just an basic extension)',
|
|
17
|
+
templatePath: path.resolve(__dirname, '../template/basic-app')
|
|
18
|
+
},
|
|
19
|
+
EMBEDS_APP: {
|
|
20
|
+
description: 'embed extension(embed in a theme)',
|
|
21
|
+
templatePath: path.resolve(__dirname, '../template/embed-app')
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
module.exports = {
|
|
26
|
+
WORKSPACE_PATH,
|
|
27
|
+
THEME_APP_DIR_PATH,
|
|
28
|
+
THEME_APP_CONFIG_PATH,
|
|
29
|
+
STORE_DOMAIN,
|
|
30
|
+
EXCHANGE_TOKEN,
|
|
31
|
+
THEME_APP_TYPE,
|
|
32
|
+
};
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
const fsExtra = require('fs-extra');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const chalk = require('chalk');
|
|
4
|
+
const AdmZip = require('adm-zip');
|
|
5
|
+
const md5 = require('md5');
|
|
6
|
+
const { table } = require('table');
|
|
7
|
+
const { WORKSPACE_PATH, THEME_APP_CONFIG_PATH } = require('./config');
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* 通用的占位符替换函数
|
|
11
|
+
*/
|
|
12
|
+
const replacePlaceholders = async (filePath, replacements) => {
|
|
13
|
+
if (!fsExtra.pathExistsSync(filePath)) {
|
|
14
|
+
console.warn(`File "${filePath}" does not exist, skipping replacement.`);
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
let content = await fsExtra.readFile(filePath, 'utf-8');
|
|
18
|
+
// 替换占位符
|
|
19
|
+
for (const [placeholder, value] of Object.entries(replacements)) {
|
|
20
|
+
const regex = new RegExp(`\\{\\{${placeholder}\\}\\}`, 'g'); // 匹配占位符格式:{{placeholder}}
|
|
21
|
+
content = content.replace(regex, value);
|
|
22
|
+
}
|
|
23
|
+
// 写回文件
|
|
24
|
+
await fsExtra.writeFile(filePath, content, 'utf-8');
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* 重命名指定文件
|
|
29
|
+
* @param {string} filePath - 原始文件的完整路径
|
|
30
|
+
* @param {string} targetName - 新的文件名
|
|
31
|
+
*/
|
|
32
|
+
const renameFile = async (filePath, targetName) => {
|
|
33
|
+
if (!fsExtra.pathExistsSync(filePath)) {
|
|
34
|
+
console.warn(`File "${filePath}" does not exist, skipping.`);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
const targetFilePath = path.resolve(path.dirname(filePath), `${targetName}${path.extname(filePath)}`);
|
|
38
|
+
await fsExtra.rename(filePath, targetFilePath);
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* 获取themeApp配置(信息)
|
|
43
|
+
* @returns {Promise<string>}
|
|
44
|
+
*/
|
|
45
|
+
async function getThemeAppConfig() {
|
|
46
|
+
if (!fsExtra.pathExistsSync(THEME_APP_CONFIG_PATH)) {
|
|
47
|
+
throw new Error(
|
|
48
|
+
`The current workspace is not a valid theme extension project, because "${WORKSPACE_PATH}" is missing the required "theme-extension.config.json" file.`
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
return fsExtra.readJson(THEME_APP_CONFIG_PATH);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* 更新themeApp配置(信息)
|
|
56
|
+
* @param {Object} config 待更新的配置信息
|
|
57
|
+
*/
|
|
58
|
+
async function setThemeAppConfig(config) {
|
|
59
|
+
const oldThemeAppConfig = await getThemeAppConfig();
|
|
60
|
+
const newThemeAppConfig = { ...oldThemeAppConfig, ...config };
|
|
61
|
+
await fsExtra.writeJson(THEME_APP_CONFIG_PATH, newThemeAppConfig, { spaces: 2 });
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* 计算文件夹内容的哈希值
|
|
66
|
+
* @param {string} dirPath - 文件夹路径
|
|
67
|
+
* @returns {Promise<string>} 返回基于文件夹内容的哈希值
|
|
68
|
+
*/
|
|
69
|
+
async function calculateFolderHash(dirPath) {
|
|
70
|
+
if (!fsExtra.pathExistsSync(dirPath)) {
|
|
71
|
+
throw new Error(chalk.red(`Calculate folder hash path does not exist: ${dirPath}`));
|
|
72
|
+
}
|
|
73
|
+
const files = await fsExtra.readdir(dirPath);
|
|
74
|
+
let contentHash = '';
|
|
75
|
+
for (const file of files) {
|
|
76
|
+
const filePath = path.join(dirPath, file);
|
|
77
|
+
const fileStats = await fsExtra.stat(filePath);
|
|
78
|
+
|
|
79
|
+
if (fileStats.isDirectory()) {
|
|
80
|
+
// 如果是目录,递归计算文件夹内容的哈希值
|
|
81
|
+
contentHash += await calculateFolderHash(filePath);
|
|
82
|
+
} else {
|
|
83
|
+
// 如果是文件,读取文件内容并计算哈希
|
|
84
|
+
const fileContent = await fsExtra.readFile(filePath);
|
|
85
|
+
contentHash += md5(fileContent);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return md5(contentHash);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* 通用的压缩函数(同目录生成zip压缩文件)
|
|
94
|
+
* @param {string} sourcePath - 需要压缩的目录或文件路径
|
|
95
|
+
* @returns {Promise<Object>} 返回压缩文件的生成路径&压碎文件名
|
|
96
|
+
*/
|
|
97
|
+
async function compress(sourcePath) {
|
|
98
|
+
if (!fsExtra.pathExistsSync(sourcePath)) {
|
|
99
|
+
throw new Error(chalk.red(`Compress path does not exist: ${sourcePath}`));
|
|
100
|
+
}
|
|
101
|
+
const sourceName = path.basename(sourcePath);
|
|
102
|
+
const outputDir = path.dirname(sourcePath);
|
|
103
|
+
const sourceHash = await calculateFolderHash(sourcePath);
|
|
104
|
+
const fileName = `${sourceName}-${sourceHash.substring(0, 8)}${Date.now().toString(16).substring(0, 8)}.zip`;
|
|
105
|
+
const outputPath = path.resolve(outputDir, fileName);
|
|
106
|
+
|
|
107
|
+
return new Promise((resolve, reject) => {
|
|
108
|
+
const zip = new AdmZip();
|
|
109
|
+
if (fsExtra.statSync(sourcePath).isDirectory()) {
|
|
110
|
+
zip.addLocalFolder(sourcePath, sourceName);
|
|
111
|
+
} else {
|
|
112
|
+
zip.addLocalFile(sourcePath);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
zip.writeZip(outputPath, (err) => {
|
|
116
|
+
if (err) {
|
|
117
|
+
reject(err);
|
|
118
|
+
} else {
|
|
119
|
+
resolve({
|
|
120
|
+
zipPath: outputPath,
|
|
121
|
+
zipName: fileName
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* 比较两个版本号的大小
|
|
130
|
+
* @param {String} version1 版本号1
|
|
131
|
+
* @param {String} version2 版本号2
|
|
132
|
+
* @returns version1 < version2 返回 -1,version1 > version2 返回 1,相等返回 0
|
|
133
|
+
*/
|
|
134
|
+
function compareVersions(version1, version2) {
|
|
135
|
+
const v1 = version1.split('.').map(Number);
|
|
136
|
+
const v2 = version2.split('.').map(Number);
|
|
137
|
+
|
|
138
|
+
for (let i = 0; i < Math.max(v1.length, v2.length); i++) {
|
|
139
|
+
const num1 = v1[i] || 0;
|
|
140
|
+
const num2 = v2[i] || 0;
|
|
141
|
+
|
|
142
|
+
if (num1 < num2) {
|
|
143
|
+
return -1;
|
|
144
|
+
}
|
|
145
|
+
if (num1 > num2) {
|
|
146
|
+
return 1;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return 0;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* 获取文件名及其直接父目录名
|
|
154
|
+
* @param {string} filePath - 文件的绝对路径
|
|
155
|
+
* @returns {{ fileName: string, parentDirName: string }} 返回包含文件名和父目录名的对象
|
|
156
|
+
*/
|
|
157
|
+
function getFileInfo(filePath) {
|
|
158
|
+
const fileName = path.basename(filePath);
|
|
159
|
+
const parentDirName = path.basename(path.dirname(filePath));
|
|
160
|
+
return { fileName, parentDirName };
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// 表格配置
|
|
164
|
+
const TABLE_CONFIG = {
|
|
165
|
+
border: {
|
|
166
|
+
topBody: '═',
|
|
167
|
+
topJoin: '╤',
|
|
168
|
+
topLeft: '╔',
|
|
169
|
+
topRight: '╗',
|
|
170
|
+
bottomBody: '═',
|
|
171
|
+
bottomJoin: '╧',
|
|
172
|
+
bottomLeft: '╚',
|
|
173
|
+
bottomRight: '╝',
|
|
174
|
+
bodyLeft: '║',
|
|
175
|
+
bodyRight: '║',
|
|
176
|
+
bodyJoin: '│',
|
|
177
|
+
joinBody: '─',
|
|
178
|
+
joinLeft: '╟',
|
|
179
|
+
joinRight: '╢',
|
|
180
|
+
joinJoin: '┼'
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* 图像化表格渲染数据
|
|
186
|
+
* @param {Array} data - 数据
|
|
187
|
+
* @param {Array} options - 配置项 [{ label:string, filed:string, color?:string}, ...]
|
|
188
|
+
* @example
|
|
189
|
+
* renderTable([{name:'Jack',age:20},{name:'Tom',age:30}],
|
|
190
|
+
* [{label:'姓名',filed:'name',color:''},{label:'年龄',filed:'age',color:''}]
|
|
191
|
+
* )
|
|
192
|
+
*/
|
|
193
|
+
function renderTable(data, options) {
|
|
194
|
+
const header = options.map((item) => chalk.bold(item.label));
|
|
195
|
+
const rows = data.map((item) => options.map((opt) => chalk[opt.color || 'whiteBright'](item[opt.filed])));
|
|
196
|
+
const tableData = [header, ...rows];
|
|
197
|
+
return table(tableData, {
|
|
198
|
+
border: Object.fromEntries(
|
|
199
|
+
Object.entries(TABLE_CONFIG.border).map(([key, val]) => [key, chalk.blueBright.bold(val)])
|
|
200
|
+
)
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
module.exports = {
|
|
205
|
+
replacePlaceholders,
|
|
206
|
+
renameFile,
|
|
207
|
+
getThemeAppConfig,
|
|
208
|
+
setThemeAppConfig,
|
|
209
|
+
compress,
|
|
210
|
+
compareVersions,
|
|
211
|
+
getFileInfo,
|
|
212
|
+
renderTable
|
|
213
|
+
};
|
package/lib/checkout/api.js
CHANGED
|
@@ -8,6 +8,7 @@ const { isDebug } = require('./console');
|
|
|
8
8
|
const { getProjectConfig } = require('./util');
|
|
9
9
|
const chalk = require('chalk');
|
|
10
10
|
const { consoleWarn } = require('./console');
|
|
11
|
+
const { useOss } = require('../oss');
|
|
11
12
|
|
|
12
13
|
const FILE_SIGN_URL = '/checkout_extensions/file/sign';
|
|
13
14
|
const CREATE_URL = '/checkout_extensions/create';
|
|
@@ -19,6 +20,7 @@ const UNDEPLOY_URL = '/checkout_extensions/undeploy';
|
|
|
19
20
|
const PREVIEW_URL = '/checkout_extensions/preview';
|
|
20
21
|
|
|
21
22
|
let configJson = undefined;
|
|
23
|
+
let uploadOss = null;
|
|
22
24
|
|
|
23
25
|
const request = new Axios.create();
|
|
24
26
|
|
|
@@ -64,9 +66,6 @@ request.interceptors.response.use(
|
|
|
64
66
|
if (isDebug()) {
|
|
65
67
|
console.log(`\n<== error ${error.response.status}: ${error.response.config.url}`);
|
|
66
68
|
}
|
|
67
|
-
if (!error.response) {
|
|
68
|
-
throw chalk.red('Network error, please check your connection.');
|
|
69
|
-
}
|
|
70
69
|
if (error.response?.status == 403) {
|
|
71
70
|
return Promise.reject(new Error('Token permission failure'));
|
|
72
71
|
}
|
|
@@ -74,42 +73,20 @@ request.interceptors.response.use(
|
|
|
74
73
|
}
|
|
75
74
|
);
|
|
76
75
|
|
|
77
|
-
async function upload(path, name) {
|
|
78
|
-
const form = new FormData();
|
|
79
|
-
const url = await request(`${FILE_SIGN_URL}?key=${name}`)
|
|
80
|
-
.then((data) => {
|
|
81
|
-
return data.data;
|
|
82
|
-
})
|
|
83
|
-
.then(async (data) => {
|
|
84
|
-
const url = data.write_host;
|
|
85
|
-
form.append('policy', data.policy);
|
|
86
|
-
form.append('OSSAccessKeyId', data.access_id);
|
|
87
|
-
form.append('success_action_status', 200);
|
|
88
|
-
form.append('signature', data.sign);
|
|
89
|
-
form.append('x-oss-forbid-overwrite', 'true');
|
|
90
|
-
form.append('key', name);
|
|
91
|
-
form.append('file', fs.createReadStream(path));
|
|
92
|
-
await request.post(`https:${url}`, form, { headers: form.getHeaders() }).catch((error) => {
|
|
93
|
-
if (error.response?.data.includes('<Code>FileAlreadyExists</Code>')) {
|
|
94
|
-
consoleWarn('The current file already exists, not need to upload.');
|
|
95
|
-
return;
|
|
96
|
-
} else {
|
|
97
|
-
return Promise.reject(error);
|
|
98
|
-
}
|
|
99
|
-
});
|
|
100
|
-
return `${data.read_host}${data.read_host.endsWith('/') ? '' : '/'}${name}`;
|
|
101
|
-
});
|
|
102
|
-
loading('succeed upload').succeed();
|
|
103
|
-
return url;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
76
|
async function uploadFile(dirPath, file) {
|
|
107
|
-
|
|
77
|
+
if (!uploadOss) {
|
|
78
|
+
uploadOss = useOss(
|
|
79
|
+
configJson.token,
|
|
80
|
+
new URL(configJson.store).hostname,
|
|
81
|
+
`✗ No store found. Please check your "extension.config.js" file.`
|
|
82
|
+
).uploadOss;
|
|
83
|
+
}
|
|
84
|
+
return uploadOss(`${dirPath}/${file}`);
|
|
108
85
|
}
|
|
109
86
|
async function uploadDir(dirPath, files) {
|
|
110
87
|
return Promise.all(
|
|
111
88
|
files.map((file) => {
|
|
112
|
-
return
|
|
89
|
+
return uploadOss(`${dirPath}/${file}`);
|
|
113
90
|
})
|
|
114
91
|
);
|
|
115
92
|
}
|
|
@@ -99,7 +99,7 @@ const handleHtmlDeps = (htmlStr, curPath) => {
|
|
|
99
99
|
cachedHtmlStr[importedHtmlAbsolutePath] = handleHtmlDeps(importedHtmlStr, curPath);
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
-
str = str.replace(importReg, cachedHtmlStr[importedHtmlAbsolutePath]);
|
|
102
|
+
str = str.replace(importReg, () => cachedHtmlStr[importedHtmlAbsolutePath]);
|
|
103
103
|
} catch (e) {
|
|
104
104
|
console.error(e);
|
|
105
105
|
}
|
|
@@ -32,10 +32,11 @@ function handleWsConnection(ws) {
|
|
|
32
32
|
console.log(`Received message ${data} from user `);
|
|
33
33
|
});
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
let extensionList = [];
|
|
36
36
|
fs.readdirSync(getExtensionsPath()).forEach((ext) => {
|
|
37
37
|
extensionList.push(generateExtensionData(ext));
|
|
38
38
|
});
|
|
39
|
+
extensionList = extensionList.filter((ext) => !ext.resourceUrl.endsWith('undefined'));
|
|
39
40
|
ws.send(JSON.stringify({ event: 'init', data: extensionList }));
|
|
40
41
|
|
|
41
42
|
}
|
package/lib/commands/login.js
CHANGED
|
@@ -8,7 +8,7 @@ const log = require('../log');
|
|
|
8
8
|
const { getShopDetail } = require('../openAPI/api');
|
|
9
9
|
const getCode = require('../auth/getCode');
|
|
10
10
|
const { postAccessToken, getUserInfo, postExchangeToken, postStoreToken } = require('../auth');
|
|
11
|
-
const { loginIntoPartner } = require('../
|
|
11
|
+
const { loginIntoPartner } = require('../common/login');
|
|
12
12
|
|
|
13
13
|
let spinner;
|
|
14
14
|
|
package/lib/commands/logout.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const Sentry = require('@sentry/node');
|
|
2
2
|
const chalk = require('chalk');
|
|
3
3
|
const { empty: emptyUser } = require('../db/user');
|
|
4
|
-
const { logoutPartner } = require('../
|
|
4
|
+
const { logoutPartner } = require('../common/logout');
|
|
5
5
|
const log = require('../log');
|
|
6
6
|
|
|
7
7
|
const logoutStore = () => {
|
package/lib/openAPI/index.js
CHANGED
|
@@ -12,17 +12,23 @@ const openAPI = axios.create({
|
|
|
12
12
|
headers: { 'Access-Token': get('exchange_token') }
|
|
13
13
|
});
|
|
14
14
|
|
|
15
|
-
openAPI.interceptors.request.use(
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
15
|
+
openAPI.interceptors.request.use(
|
|
16
|
+
(config) => {
|
|
17
|
+
if (!get('store_domain')) {
|
|
18
|
+
log.error(
|
|
19
|
+
chalk.red(
|
|
20
|
+
`\n✗ No store found. Please run ${chalk.cyan('shoplazza login --store STORE')} to login to a specific store`
|
|
21
|
+
)
|
|
22
|
+
);
|
|
23
|
+
process.exit(-1);
|
|
24
|
+
}
|
|
25
|
+
return config;
|
|
26
|
+
},
|
|
27
|
+
(error) => {
|
|
28
|
+
console.error(chalk.red(`[REQUEST ERROR] ${error.message}`));
|
|
29
|
+
return Promise.reject(error);
|
|
23
30
|
}
|
|
24
|
-
|
|
25
|
-
});
|
|
31
|
+
);
|
|
26
32
|
|
|
27
33
|
openAPI.interceptors.response.use(
|
|
28
34
|
function (response) {
|
|
@@ -39,6 +45,11 @@ openAPI.interceptors.response.use(
|
|
|
39
45
|
return openAPI(error.config);
|
|
40
46
|
});
|
|
41
47
|
}
|
|
48
|
+
error.response && console.error(
|
|
49
|
+
chalk.red(
|
|
50
|
+
`[RESPONSE ERROR] ${error.response.status} ${error.response.config.baseURL}${error.response.config.url}`
|
|
51
|
+
)
|
|
52
|
+
);
|
|
42
53
|
return Promise.reject(error);
|
|
43
54
|
}
|
|
44
55
|
);
|
package/lib/oss.js
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
const axios = require('axios');
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const FormData = require('form-data');
|
|
5
|
+
const chalk = require('chalk');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* 获取OSS上传功能,适用于上传文件到店铺的OSS服务器
|
|
9
|
+
* @param {string} accessToken - 用于认证的token
|
|
10
|
+
* @param {string} storeDomain - 店铺域名
|
|
11
|
+
* @param {string} storeDomainTips - 店铺域名为空时的提示信息
|
|
12
|
+
* @returns {{ uploadOss: (filePath: string) => Promise<string> }} 返回一个对象,包含上传文件到OSS的函数
|
|
13
|
+
* @example
|
|
14
|
+
* const { uploadOss } = useOss('your-access-token', 'your-store-domain', 'Store domain is required');
|
|
15
|
+
* const ossUrl = await uploadOss('/path/to/file.txt');
|
|
16
|
+
* console.log(ossUrl); // 输出文件的 OSS 地址
|
|
17
|
+
*/
|
|
18
|
+
function useOss(accessToken, storeDomain, storeDomainTips) {
|
|
19
|
+
const ins = axios.create({
|
|
20
|
+
headers: { 'Access-Token': accessToken }
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
ins.interceptors.request.use(
|
|
24
|
+
(config) => {
|
|
25
|
+
if (!storeDomain) {
|
|
26
|
+
console.error(
|
|
27
|
+
// chalk.red(`\n\n`)
|
|
28
|
+
chalk.red(`\n${storeDomainTips}\n`)
|
|
29
|
+
);
|
|
30
|
+
process.exit(-1);
|
|
31
|
+
}
|
|
32
|
+
if (config.url.startsWith('/checkout_extensions')) {
|
|
33
|
+
config.baseURL = `https://${storeDomain}/openapi`;
|
|
34
|
+
}
|
|
35
|
+
return config;
|
|
36
|
+
},
|
|
37
|
+
(error) => {
|
|
38
|
+
console.error(chalk.red(`[REQUEST ERROR] ${error.message}`));
|
|
39
|
+
return Promise.reject(error);
|
|
40
|
+
}
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
ins.interceptors.response.use(
|
|
44
|
+
(response) => {
|
|
45
|
+
return response.data;
|
|
46
|
+
},
|
|
47
|
+
(error) => {
|
|
48
|
+
console.log(error);
|
|
49
|
+
|
|
50
|
+
console.error(
|
|
51
|
+
chalk.red(
|
|
52
|
+
`[RESPONSE ERROR] ${error.response.status} ${error.response.config.baseURL}${error.response.config.url}`
|
|
53
|
+
)
|
|
54
|
+
);
|
|
55
|
+
return Promise.reject(error);
|
|
56
|
+
}
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* 上传文件到OSS
|
|
61
|
+
* @param {string} filePath - 要上传的文件路径
|
|
62
|
+
* @returns {Promise<string>} 返回文件的 OSS 地址
|
|
63
|
+
* @throws {Error} 如果上传失败或发生未知错误,抛出错误
|
|
64
|
+
* @example
|
|
65
|
+
* const ossUrl = await uploadOss('/path/to/file.txt');
|
|
66
|
+
* console.log(ossUrl); // 输出文件的 OSS 地址
|
|
67
|
+
*/
|
|
68
|
+
async function uploadOss(filePath) {
|
|
69
|
+
let fileName = path.basename(filePath);
|
|
70
|
+
const key = 'chick-extension/' + fileName;
|
|
71
|
+
const form = new FormData();
|
|
72
|
+
const url = await ins.get(`/checkout_extensions/file/sign?key=${key}`).then(async (data) => {
|
|
73
|
+
const url = data.write_host;
|
|
74
|
+
form.append('policy', data.policy);
|
|
75
|
+
form.append('OSSAccessKeyId', data.access_id);
|
|
76
|
+
form.append('success_action_status', 200);
|
|
77
|
+
form.append('signature', data.sign);
|
|
78
|
+
form.append('x-oss-forbid-overwrite', 'true');
|
|
79
|
+
form.append('key', key);
|
|
80
|
+
form.append('file', fs.createReadStream(filePath));
|
|
81
|
+
await ins.post(`https:${url}`, form, { headers: form.getHeaders() }).catch((error) => {
|
|
82
|
+
if (error.response?.data.includes('<Code>FileAlreadyExists</Code>')) {
|
|
83
|
+
console.log(chalk.yellow('[WARN] The current file already exists, not need to upload.'));
|
|
84
|
+
return;
|
|
85
|
+
} else {
|
|
86
|
+
return Promise.reject(error);
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
return `${data.read_host}${data.read_host.endsWith('/') ? '' : '/'}${key}`;
|
|
90
|
+
});
|
|
91
|
+
return url;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return { uploadOss };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
module.exports = {
|
|
98
|
+
useOss
|
|
99
|
+
};
|
package/lib/utils.js
CHANGED
|
@@ -2,6 +2,8 @@ const path = require('path');
|
|
|
2
2
|
const fs = require('fs-extra');
|
|
3
3
|
const AdmZip = require('adm-zip');
|
|
4
4
|
const chalk = require('chalk');
|
|
5
|
+
const chokidar = require('chokidar');
|
|
6
|
+
const fsExtra = require('fs-extra');
|
|
5
7
|
|
|
6
8
|
const {
|
|
7
9
|
SSO_AUTH_URL,
|
|
@@ -111,3 +113,53 @@ const SSO_AUTH_URL_MAP = {
|
|
|
111
113
|
exports.getSSOAuthUrl = (store) => {
|
|
112
114
|
return SSO_AUTH_URL_MAP[getEnv(store)];
|
|
113
115
|
};
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* 监听文件夹变化
|
|
119
|
+
* @param {string} workspace - 需要监听的文件夹路径
|
|
120
|
+
* @param {Object} options - 配置项
|
|
121
|
+
* @param {Function} [options.onAdd] - 文件添加时的回调函数,接收文件路径作为参数
|
|
122
|
+
* @param {Function} [options.onChange] - 文件更改时的回调函数,接收文件路径作为参数
|
|
123
|
+
* @param {Function} [options.onDelete] - 文件删除时的回调函数,接收文件路径作为参数
|
|
124
|
+
* @param {any} [options.xxx] - 其他chokidar.watch配置项
|
|
125
|
+
* @returns {Function} - 取消监听的函数
|
|
126
|
+
*/
|
|
127
|
+
exports.watchWorkspace = (workspace, options = {}) => {
|
|
128
|
+
if (!fsExtra.pathExistsSync(workspace)) {
|
|
129
|
+
console.error(chalk.red(`[ERROR] "${workspace}" does not exist.`));
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
const defaultOptions = {
|
|
133
|
+
persistent: true,
|
|
134
|
+
ignoreInitial: true
|
|
135
|
+
};
|
|
136
|
+
const { onAdd = () => {}, onChange = () => {}, onDelete = () => {}, ...restOptions } = options;
|
|
137
|
+
const watcherOptions = { ...defaultOptions, ...restOptions };
|
|
138
|
+
const watcher = chokidar.watch(workspace, watcherOptions);
|
|
139
|
+
|
|
140
|
+
watcher.on('add', (filePath) => {
|
|
141
|
+
console.log(chalk.cyan(`[ADD] ${filePath}`));
|
|
142
|
+
onAdd(filePath);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
watcher.on('change', (filePath) => {
|
|
146
|
+
console.log(chalk.yellow(`[CHANGE] ${filePath}`));
|
|
147
|
+
onChange(filePath);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
watcher.on('unlink', (filePath) => {
|
|
151
|
+
console.log(chalk.red(`[DELETE] ${filePath}`));
|
|
152
|
+
onDelete(filePath);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
watcher.on('error', (error) => {
|
|
156
|
+
console.error(chalk.red(`[ERROR] ${error}`));
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
const stopWatcher = () => {
|
|
160
|
+
console.log(chalk.green('[INFO] Stopping watcher...'));
|
|
161
|
+
watcher.close();
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
return stopWatcher;
|
|
165
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "shoplazza-cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.7-beta.1",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "bin/shoplazza",
|
|
6
6
|
"engines": {
|
|
@@ -26,7 +26,6 @@
|
|
|
26
26
|
"@sentry/tracing": "^6.19.7",
|
|
27
27
|
"adm-zip": "^0.5.9",
|
|
28
28
|
"ali-oss": "^6.17.1",
|
|
29
|
-
"archiver": "^5.3.1",
|
|
30
29
|
"axios": "^1.6.8",
|
|
31
30
|
"better-sqlite3": "^11.5.0",
|
|
32
31
|
"chalk": "^4.1.2",
|
|
@@ -64,6 +63,7 @@
|
|
|
64
63
|
"semver": "~7.3.5",
|
|
65
64
|
"serve-static": "^1.15.0",
|
|
66
65
|
"spark-md5": "^3.0.2",
|
|
66
|
+
"table": "^6.9.0",
|
|
67
67
|
"uglify-js": "^3.17.4",
|
|
68
68
|
"update-notifier": "^5.1.0",
|
|
69
69
|
"vite": "~4.3.1",
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
const inquirer = require('inquirer');
|
|
2
|
-
const { set, PARTNER_KEYS } = require('../db/partner');
|
|
3
|
-
const { themeAppHandler, checkoutUIHandler } = require('../extensions');
|
|
4
|
-
const { line } = require('../log');
|
|
5
|
-
|
|
6
|
-
const EXTENSION_TYPES = {
|
|
7
|
-
CHECKOUT_UI: 'checkout_ui',
|
|
8
|
-
THEME_APP: 'theme_app'
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
const generate = async () => {
|
|
12
|
-
line();
|
|
13
|
-
const answer = await inquirer.prompt([
|
|
14
|
-
{
|
|
15
|
-
type: 'list',
|
|
16
|
-
name: 'extensionType',
|
|
17
|
-
message: 'Choose your extension type ↓',
|
|
18
|
-
default: EXTENSION_TYPES.THEME_APP,
|
|
19
|
-
prefix: '*',
|
|
20
|
-
choices: [
|
|
21
|
-
{
|
|
22
|
-
name: 'Theme App Extension',
|
|
23
|
-
value: EXTENSION_TYPES.THEME_APP
|
|
24
|
-
}
|
|
25
|
-
]
|
|
26
|
-
},
|
|
27
|
-
{
|
|
28
|
-
type: 'input',
|
|
29
|
-
name: 'extensionName',
|
|
30
|
-
message: 'Input your extesion name: ',
|
|
31
|
-
default: 'my-extension',
|
|
32
|
-
prefix: '*'
|
|
33
|
-
}
|
|
34
|
-
]);
|
|
35
|
-
line();
|
|
36
|
-
|
|
37
|
-
set({ [PARTNER_KEYS.EXTENSION_TYPE]: answer.extensionType });
|
|
38
|
-
|
|
39
|
-
if (answer.extensionType === EXTENSION_TYPES.THEME_APP) {
|
|
40
|
-
await themeAppHandler(answer.extensionName);
|
|
41
|
-
} else if (answer.extensionType === EXTENSION_TYPES.CHECKOUT_UI) {
|
|
42
|
-
await checkoutUIHandler(answer.extensionName);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
line();
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
module.exports = {
|
|
49
|
-
generate
|
|
50
|
-
};
|