shoplazza-cli 1.0.12 → 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/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,100 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const fs = require('fs/promises');
|
|
3
|
+
const chalk = require('chalk');
|
|
4
|
+
const AdmZip = require('adm-zip');
|
|
5
|
+
const crypto = require('crypto');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* 计算文件夹内容的哈希值
|
|
9
|
+
* @param {string} dirPath - 文件夹路径
|
|
10
|
+
* @returns {Promise<string>} 返回基于文件夹内容的哈希值
|
|
11
|
+
*/
|
|
12
|
+
async function calculateFolderHash(dirPath) {
|
|
13
|
+
if (!(await pathExists(dirPath))) {
|
|
14
|
+
throw new Error(chalk.red(`Calculate folder hash path does not exist: ${dirPath}`));
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const hash = crypto.createHash('md5');
|
|
18
|
+
|
|
19
|
+
async function walk(currentPath) {
|
|
20
|
+
const stat = await fs.stat(currentPath);
|
|
21
|
+
if (stat.isDirectory()) {
|
|
22
|
+
const entries = (await fs.readdir(currentPath)).sort(); // 排序保证跨平台一致
|
|
23
|
+
for (const entry of entries) {
|
|
24
|
+
await walk(path.join(currentPath, entry));
|
|
25
|
+
}
|
|
26
|
+
} else {
|
|
27
|
+
const content = await fs.readFile(currentPath);
|
|
28
|
+
hash.update(content);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
await walk(dirPath);
|
|
33
|
+
return hash.digest('hex');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* 检查路径是否存在
|
|
38
|
+
* @param {string} targetPath
|
|
39
|
+
* @returns {Promise<boolean>}
|
|
40
|
+
*/
|
|
41
|
+
async function pathExists(targetPath) {
|
|
42
|
+
try {
|
|
43
|
+
await fs.access(targetPath);
|
|
44
|
+
return true;
|
|
45
|
+
} catch {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* 压缩目录或文件
|
|
52
|
+
* @param {string} sourcePath - 需要压缩的目录或文件路径
|
|
53
|
+
* @param {string} outputDir - 压缩文件输出目录
|
|
54
|
+
* @param {Object} [options] - 压缩选项
|
|
55
|
+
* @param {string} [options.rename] - 压缩包中目录的重命名
|
|
56
|
+
* @returns {Promise<Object>} 返回压缩文件路径和文件名
|
|
57
|
+
*/
|
|
58
|
+
async function compress(sourcePath, outputDir, options = {}) {
|
|
59
|
+
const { rename } = options;
|
|
60
|
+
|
|
61
|
+
if (!(await pathExists(sourcePath))) {
|
|
62
|
+
throw new Error(chalk.red(`Compress path does not exist: ${sourcePath}`));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const sourceName = path.basename(sourcePath);
|
|
66
|
+
const sourceHash = await calculateFolderHash(sourcePath);
|
|
67
|
+
const fileName = `${sourceName}-${sourceHash.substring(0, 8)}${Date.now().toString(16).substring(0, 8)}.zip`;
|
|
68
|
+
const outputPath = path.resolve(outputDir, fileName);
|
|
69
|
+
|
|
70
|
+
// 确保输出目录存在
|
|
71
|
+
await fs.mkdir(path.dirname(outputPath), { recursive: true });
|
|
72
|
+
|
|
73
|
+
return new Promise(async (resolve, reject) => {
|
|
74
|
+
const zip = new AdmZip();
|
|
75
|
+
const stat = await fs.stat(sourcePath);
|
|
76
|
+
|
|
77
|
+
if (stat.isDirectory()) {
|
|
78
|
+
zip.addLocalFolder(sourcePath, rename || sourceName);
|
|
79
|
+
} else {
|
|
80
|
+
zip.addLocalFile(sourcePath);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
zip.writeZip(outputPath, (err) => {
|
|
84
|
+
if (err) reject(err);
|
|
85
|
+
else resolve({ zipPath: outputPath, zipName: fileName });
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async function buildTheme(extension) {
|
|
91
|
+
const extensionDir = path.dirname(extension.configPath);
|
|
92
|
+
|
|
93
|
+
const outputDir = path.join(extensionDir, '..', '..', 'app-deploy');
|
|
94
|
+
const zipInfo = await compress(extensionDir, outputDir, { rename: 'theme-app' });
|
|
95
|
+
return zipInfo.zipPath; // 返回压缩包路径
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
module.exports = {
|
|
99
|
+
buildTheme
|
|
100
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
const { buildFunction } = require('./buildFunction');
|
|
2
|
+
const { buildCheckout } = require('./buildCheckout');
|
|
3
|
+
const { buildTheme } = require('./buildTheme');
|
|
4
|
+
const { EXTENSION_TYPES } = require('../../constant/extension');
|
|
5
|
+
|
|
6
|
+
const strategies = {
|
|
7
|
+
[EXTENSION_TYPES.THEME]: buildTheme,
|
|
8
|
+
[EXTENSION_TYPES.CHECKOUT]: buildCheckout,
|
|
9
|
+
[EXTENSION_TYPES.FUNCTION]: buildFunction
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
async function buildExtension(extension) {
|
|
13
|
+
const strategy = strategies[extension.extension_type];
|
|
14
|
+
if (strategy) {
|
|
15
|
+
return strategy(extension);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
throw new Error(`Unsupported extension type: ${extension.extension_type}`);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
module.exports = {
|
|
22
|
+
buildExtension
|
|
23
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
const parseExtension = (str, name) => {
|
|
2
|
+
const data = str.split(/(?:import\s*(?:.*?)\s*from\s*(?:.*?)\s*;)/g);
|
|
3
|
+
const main = data[data.length - 1];
|
|
4
|
+
const id = JSON.stringify(name);
|
|
5
|
+
const importString = str.match(/(?:import\s*(?:.*?)\s*from\s*(?:.*?)\s*;)/g)?.join(' ') || '';
|
|
6
|
+
return `${importString}(function(){const __EXTENSION_ID__=${id};${main}})()`;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
module.exports = {
|
|
10
|
+
vitePluginAddExtensionId: function (id) {
|
|
11
|
+
return {
|
|
12
|
+
name: 'vitePluginAddExtensionId',
|
|
13
|
+
enforce: 'pre',
|
|
14
|
+
apply: 'build',
|
|
15
|
+
transform: (code, _id) => {
|
|
16
|
+
if (/.*\/extensions\/.*\/src\/index\.js/.test(_id)) {
|
|
17
|
+
return parseExtension(code, id);
|
|
18
|
+
}
|
|
19
|
+
return code;
|
|
20
|
+
},
|
|
21
|
+
buildEnd: () => {
|
|
22
|
+
console.log('vitePluginAddExtensionId end');
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
};
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const j = require('jscodeshift');
|
|
4
|
+
|
|
5
|
+
const EXTENSION_RENDER_FN_NAME = 'extend';
|
|
6
|
+
const HTML_FILE_PATH_KEY = 'component';
|
|
7
|
+
const ASTNodeType = {
|
|
8
|
+
RETURN_STATMENT: 'ReturnStatement',
|
|
9
|
+
CALL_EXPRESSION: 'CallExpression',
|
|
10
|
+
OBJECT_EXPRESSION: 'ObjectExpression',
|
|
11
|
+
IDENTIFIER: 'Identifier',
|
|
12
|
+
IMPORT_DEFAULT_SPECIFIER: 'ImportDefaultSpecifier',
|
|
13
|
+
IMPORT_DECLARATION: 'ImportDeclaration'
|
|
14
|
+
};
|
|
15
|
+
const findRenderFnList = (code) => {
|
|
16
|
+
const renderFnList = [];
|
|
17
|
+
|
|
18
|
+
j(code)
|
|
19
|
+
.find(j.CallExpression)
|
|
20
|
+
.forEach((astNode) => {
|
|
21
|
+
if (astNode?.value?.callee?.name === EXTENSION_RENDER_FN_NAME) {
|
|
22
|
+
const objectExpressionNode = astNode?.value?.arguments?.find(
|
|
23
|
+
(args) => args.type === ASTNodeType.OBJECT_EXPRESSION
|
|
24
|
+
);
|
|
25
|
+
if (objectExpressionNode) {
|
|
26
|
+
const componentProp = objectExpressionNode?.properties?.find(
|
|
27
|
+
(prop) => prop?.key?.name === HTML_FILE_PATH_KEY
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
const name = componentProp?.value?.callee?.name;
|
|
31
|
+
name && renderFnList.push(name);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
return renderFnList;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const findImportDeclareStatementNodeList = (code) => {
|
|
40
|
+
const importDeclareStatementList = [];
|
|
41
|
+
|
|
42
|
+
j(code)
|
|
43
|
+
.find(j.ImportDeclaration)
|
|
44
|
+
.forEach((importStatementNode) => {
|
|
45
|
+
const importDefaultSpecifier = importStatementNode?.value?.specifiers?.find(
|
|
46
|
+
(specifierNode) => specifierNode?.type === ASTNodeType.IMPORT_DEFAULT_SPECIFIER
|
|
47
|
+
)?.local?.name;
|
|
48
|
+
const importPath = importStatementNode?.value?.source?.value;
|
|
49
|
+
|
|
50
|
+
importDefaultSpecifier &&
|
|
51
|
+
importPath &&
|
|
52
|
+
typeof importPath === 'string' &&
|
|
53
|
+
importDeclareStatementList.push({ importDefaultSpecifier, importPath });
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
return importDeclareStatementList;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const readFile = (path) => {
|
|
60
|
+
const supportedExt = ['html'];
|
|
61
|
+
|
|
62
|
+
const ext = path.split('.').pop()?.toLowerCase() || '';
|
|
63
|
+
if (supportedExt.includes(ext)) {
|
|
64
|
+
return fs.readFileSync(path, {
|
|
65
|
+
encoding: 'utf8'
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return '';
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const removeBodyMark = (code) => {
|
|
73
|
+
return code.replaceAll(/\<body\>/g, '').replaceAll(/\<\/body\>/g, '');
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const addExtensionApiPrefix = (code) => {
|
|
77
|
+
const EXTENSION_API_CALL_EXPRESSION = '__EXTENSION_UI__.extensionApi.';
|
|
78
|
+
return code.replaceAll(/extensionApi\./g, EXTENSION_API_CALL_EXPRESSION);
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
let cachedHtmlStr = {};
|
|
82
|
+
|
|
83
|
+
const handleHtmlDeps = (htmlStr, curPath) => {
|
|
84
|
+
let str = htmlStr;
|
|
85
|
+
// eg. import "./index.html" import './index.html'
|
|
86
|
+
const importReg = /import\s*(?:(?:\"([\.\/\w]+)\")|(?:'([\.\/\w]+)'))/;
|
|
87
|
+
|
|
88
|
+
let result = importReg.exec(str);
|
|
89
|
+
while (result) {
|
|
90
|
+
const importedHtmlPath = result[1] || result[2];
|
|
91
|
+
if (importedHtmlPath) {
|
|
92
|
+
try {
|
|
93
|
+
const importedHtmlAbsolutePath = path.resolve(path.dirname(curPath), importedHtmlPath);
|
|
94
|
+
if (!Object.prototype.hasOwnProperty.call(cachedHtmlStr, importedHtmlAbsolutePath)) {
|
|
95
|
+
cachedHtmlStr[importedHtmlAbsolutePath] = '';
|
|
96
|
+
|
|
97
|
+
const importedHtmlStr = readFile(importedHtmlAbsolutePath);
|
|
98
|
+
|
|
99
|
+
cachedHtmlStr[importedHtmlAbsolutePath] = handleHtmlDeps(importedHtmlStr, curPath);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
str = str.replace(importReg, () => cachedHtmlStr[importedHtmlAbsolutePath]);
|
|
103
|
+
} catch (e) {
|
|
104
|
+
console.error(e);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
result = importReg.exec(str);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
str = removeBodyMark(str);
|
|
111
|
+
|
|
112
|
+
return str;
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const strMinify = (code) => code.split('\n').filter(Boolean).join('');
|
|
116
|
+
|
|
117
|
+
const createTemplateLiteral = (code) => j.stringLiteral(code);
|
|
118
|
+
|
|
119
|
+
const removeImportDeclareStatment = (code, specifiers) => {
|
|
120
|
+
return j(code)
|
|
121
|
+
.find(j.Program)
|
|
122
|
+
.forEach((program) => {
|
|
123
|
+
const body = program?.value?.body || [];
|
|
124
|
+
if (body.length > 0) {
|
|
125
|
+
const index = body.findIndex(
|
|
126
|
+
(node) =>
|
|
127
|
+
node?.type === ASTNodeType.IMPORT_DECLARATION &&
|
|
128
|
+
!!node?.specifiers?.find(
|
|
129
|
+
(specifier) =>
|
|
130
|
+
specifier?.type === ASTNodeType.IMPORT_DEFAULT_SPECIFIER &&
|
|
131
|
+
specifiers.includes(specifier?.local?.name || '')
|
|
132
|
+
)
|
|
133
|
+
);
|
|
134
|
+
if (index > -1) program.value.body.splice(index, 1);
|
|
135
|
+
}
|
|
136
|
+
})
|
|
137
|
+
.toSource();
|
|
138
|
+
};
|
|
139
|
+
module.exports = {
|
|
140
|
+
vitePluginTransformExtensionHtml: () => {
|
|
141
|
+
return {
|
|
142
|
+
name: 'vitePluginTransformExtensionHtml',
|
|
143
|
+
enforce: 'pre',
|
|
144
|
+
apply: 'build',
|
|
145
|
+
transform(code, srcPath) {
|
|
146
|
+
if (!srcPath.endsWith('.js')) {
|
|
147
|
+
return code;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const renderFnList = findRenderFnList(code);
|
|
151
|
+
const importDeclareStatementList = findImportDeclareStatementNodeList(code);
|
|
152
|
+
|
|
153
|
+
const removedImportSpecifiers = [];
|
|
154
|
+
|
|
155
|
+
const returnStatementChangedCode = j(code)
|
|
156
|
+
.find(j.FunctionDeclaration)
|
|
157
|
+
.forEach((functionDeclareNode) => {
|
|
158
|
+
const fnName = functionDeclareNode?.value?.id?.name || '';
|
|
159
|
+
if (!renderFnList.includes(fnName)) return;
|
|
160
|
+
|
|
161
|
+
const returnStatment = functionDeclareNode?.value?.body?.body?.find(
|
|
162
|
+
(node) => node.type === ASTNodeType.RETURN_STATMENT
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
const isIdentifierReturnType = returnStatment?.argument?.type === ASTNodeType.IDENTIFIER;
|
|
166
|
+
if (!isIdentifierReturnType) return;
|
|
167
|
+
|
|
168
|
+
const identifierName = returnStatment?.argument?.name;
|
|
169
|
+
const htmlFilePathNode = importDeclareStatementList.find(
|
|
170
|
+
(stat) => stat.importDefaultSpecifier === identifierName
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
if (htmlFilePathNode) {
|
|
174
|
+
try {
|
|
175
|
+
const absolutePath = path.resolve(path.dirname(srcPath), htmlFilePathNode.importPath);
|
|
176
|
+
cachedHtmlStr[absolutePath] = '';
|
|
177
|
+
const htmlStr = readFile(absolutePath);
|
|
178
|
+
|
|
179
|
+
let result = handleHtmlDeps(htmlStr, srcPath);
|
|
180
|
+
cachedHtmlStr = {};
|
|
181
|
+
|
|
182
|
+
result = strMinify(result);
|
|
183
|
+
result = addExtensionApiPrefix(result);
|
|
184
|
+
const templateLiteral = createTemplateLiteral(result);
|
|
185
|
+
|
|
186
|
+
returnStatment.argument = templateLiteral;
|
|
187
|
+
|
|
188
|
+
removedImportSpecifiers.push(identifierName);
|
|
189
|
+
} catch (e) {
|
|
190
|
+
console.error(e);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
})
|
|
194
|
+
.toSource();
|
|
195
|
+
|
|
196
|
+
const importStatementRemovedCode = removeImportDeclareStatment(
|
|
197
|
+
returnStatementChangedCode,
|
|
198
|
+
removedImportSpecifiers
|
|
199
|
+
);
|
|
200
|
+
return importStatementRemovedCode;
|
|
201
|
+
},
|
|
202
|
+
buildEnd: () => {
|
|
203
|
+
console.log('transform html end');
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
};
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
const chalk = require('chalk');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @typedef {Object} Extension
|
|
5
|
+
* @property {string} [extension_id] 扩展ID
|
|
6
|
+
* @property {string} extension_name 扩展名称
|
|
7
|
+
* @property {string} extension_type 扩展类型 theme | checkout | function
|
|
8
|
+
* @property {string} [extension_version] 扩展版本
|
|
9
|
+
* @property {string} [extension_version_id] 扩展版本ID
|
|
10
|
+
* @property {string} [resource_url] 资源地址
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* 高阶匹配函数
|
|
15
|
+
* @param {Array<Extension>} localList
|
|
16
|
+
* @param {Array<Extension>} remoteList
|
|
17
|
+
* @param {(local: Extension, remote: Extension) => boolean} matchFn 匹配条件
|
|
18
|
+
* @param {(local: Extension, remote: Extension) => void} [updateFn] 匹配成功时的更新规则
|
|
19
|
+
*/
|
|
20
|
+
function matchAndExtract(localList, remoteList, matchFn, updateFn) {
|
|
21
|
+
const matched = [];
|
|
22
|
+
const remainingLocal = [];
|
|
23
|
+
const remainingRemote = [...remoteList];
|
|
24
|
+
|
|
25
|
+
for (const local of localList) {
|
|
26
|
+
const idx = remainingRemote.findIndex((remote) => matchFn(local, remote));
|
|
27
|
+
if (idx !== -1) {
|
|
28
|
+
if (updateFn) updateFn(local, remainingRemote[idx]);
|
|
29
|
+
matched.push(local);
|
|
30
|
+
remainingRemote.splice(idx, 1);
|
|
31
|
+
} else {
|
|
32
|
+
remainingLocal.push(local);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return { matched, remainingLocal, remainingRemote };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* 默认的 CLI 交互函数(依赖 inquirer)
|
|
41
|
+
*/
|
|
42
|
+
async function defaultPrompt(localExt, sameTypeRemoteExtensions) {
|
|
43
|
+
const inquirer = require('inquirer');
|
|
44
|
+
const { isUseRemoteExt } = await inquirer.prompt({
|
|
45
|
+
type: 'confirm',
|
|
46
|
+
name: 'isUseRemoteExt',
|
|
47
|
+
message: `Do you want to reuse the extension ${chalk.green(localExt.extension_name)} (${chalk.green(
|
|
48
|
+
localExt.extension_type
|
|
49
|
+
)})?`,
|
|
50
|
+
default: true
|
|
51
|
+
});
|
|
52
|
+
if (!isUseRemoteExt) return null;
|
|
53
|
+
|
|
54
|
+
const { selected } = await inquirer.prompt({
|
|
55
|
+
type: 'list',
|
|
56
|
+
name: 'selected',
|
|
57
|
+
message: `Please select the extension to match ${chalk.green(localExt.extension_name)} (${chalk.green(
|
|
58
|
+
localExt.extension_type
|
|
59
|
+
)})`,
|
|
60
|
+
choices: sameTypeRemoteExtensions.map((remoteExt) => ({
|
|
61
|
+
name: remoteExt.extension_name,
|
|
62
|
+
value: remoteExt
|
|
63
|
+
}))
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
return selected;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* 对比本地扩展和远程扩展
|
|
71
|
+
* @param {Array<Extension>} localExtensions
|
|
72
|
+
* @param {Array<Extension>} remoteExtensions
|
|
73
|
+
* @param {Object} [options]
|
|
74
|
+
* @param {(localExt: Extension, candidates: Array<Extension>) => Promise<Extension|null>} [options.promptFn] 自定义交互函数
|
|
75
|
+
* @returns {Promise<{updateExtensions: Extension[], deleteExtensions: Extension[], addExtensions: Extension[]}>}
|
|
76
|
+
*/
|
|
77
|
+
async function extensionDiff(localExtensions, remoteExtensions, { promptFn = defaultPrompt } = {}) {
|
|
78
|
+
let updateExtensions = [];
|
|
79
|
+
let localRest = [...localExtensions];
|
|
80
|
+
let remoteRest = [...remoteExtensions];
|
|
81
|
+
let matched = [];
|
|
82
|
+
|
|
83
|
+
// 1. 按 ID 匹配
|
|
84
|
+
({
|
|
85
|
+
matched,
|
|
86
|
+
remainingLocal: localRest,
|
|
87
|
+
remainingRemote: remoteRest
|
|
88
|
+
} = matchAndExtract(localRest, remoteRest, (l, r) => !!l.extension_id && l.extension_id === r.extension_id));
|
|
89
|
+
updateExtensions.push(...matched);
|
|
90
|
+
|
|
91
|
+
// 2. 按 title+type 匹配
|
|
92
|
+
({
|
|
93
|
+
matched,
|
|
94
|
+
remainingLocal: localRest,
|
|
95
|
+
remainingRemote: remoteRest
|
|
96
|
+
} = matchAndExtract(
|
|
97
|
+
localRest,
|
|
98
|
+
remoteRest,
|
|
99
|
+
(l, r) => l.extension_name === r.extension_name && l.extension_type === r.extension_type,
|
|
100
|
+
(l, r) => {
|
|
101
|
+
if (r.extension_id) l.extension_id = r.extension_id;
|
|
102
|
+
}
|
|
103
|
+
));
|
|
104
|
+
updateExtensions.push(...matched);
|
|
105
|
+
|
|
106
|
+
// 3. 按 type 匹配(需要交互)
|
|
107
|
+
for (const localExt of [...localRest]) {
|
|
108
|
+
const sameTypeRemote = remoteRest.filter((r) => r.extension_type === localExt.extension_type);
|
|
109
|
+
if (sameTypeRemote.length === 0) continue;
|
|
110
|
+
|
|
111
|
+
const selected = await promptFn(localExt, sameTypeRemote);
|
|
112
|
+
if (selected) {
|
|
113
|
+
localExt.extension_id = selected.extension_id;
|
|
114
|
+
updateExtensions.push(localExt);
|
|
115
|
+
localRest = localRest.filter((l) => l !== localExt);
|
|
116
|
+
remoteRest = remoteRest.filter((r) => r.extension_id !== selected.extension_id);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// 4. 剩余 local → 新增
|
|
121
|
+
const addExtensions = localRest.map((localExt) => ({
|
|
122
|
+
...localExt,
|
|
123
|
+
extension_id: ''
|
|
124
|
+
}));
|
|
125
|
+
|
|
126
|
+
// 5. 剩余 remote → 删除
|
|
127
|
+
const deleteExtensions = remoteRest;
|
|
128
|
+
|
|
129
|
+
return { updateExtensions, deleteExtensions, addExtensions };
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
module.exports = { extensionDiff, defaultPrompt };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
const upsertFunction = require('./upsertFunction');
|
|
2
|
+
const upsertCheckout = require('./upsertCheckout');
|
|
3
|
+
const upsertTheme = require('./upsertTheme');
|
|
4
|
+
|
|
5
|
+
const strategies = {
|
|
6
|
+
function: upsertFunction,
|
|
7
|
+
checkout: upsertCheckout,
|
|
8
|
+
theme: upsertTheme
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
async function upsertExtension(extension, appClientId, partnerId, appAccessToken) {
|
|
12
|
+
const strategy = strategies[extension.extension_type];
|
|
13
|
+
if (!strategy) {
|
|
14
|
+
throw new Error(`Unsupported extension type: ${extension.extension_type}`);
|
|
15
|
+
}
|
|
16
|
+
return strategy(extension, appClientId, partnerId, appAccessToken);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
module.exports = {
|
|
20
|
+
upsertExtension
|
|
21
|
+
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
const { createCheckoutExtensionRequest, commitCheckoutExtensionRequest } = require('../../api/openapi');
|
|
2
|
+
|
|
3
|
+
// 提取 返回结果
|
|
4
|
+
function extractCheckoutResult(res) {
|
|
5
|
+
return {
|
|
6
|
+
extension_id: res.data.extension.extension_id,
|
|
7
|
+
extension_version: res.data.extension.version,
|
|
8
|
+
extension_version_id: res.data.extension.id
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
async function upsertCheckout(extension, appClientId, partnerId, appAccessToken) {
|
|
13
|
+
// 有 extension_id 则 升级,没有则创建
|
|
14
|
+
if (extension.extension_id && extension.extension_version) {
|
|
15
|
+
// 升级
|
|
16
|
+
const res = await commitCheckoutExtensionRequest(appClientId, partnerId, {
|
|
17
|
+
name: extension.extension_name,
|
|
18
|
+
version: extension.extension_version,
|
|
19
|
+
extension_id: extension.extension_id,
|
|
20
|
+
resource_url: extension.resource_url
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
if (res?.status === 0) {
|
|
24
|
+
return extractCheckoutResult(res);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
throw new Error(res?.message || 'upsert checkout extension failed');
|
|
28
|
+
} else {
|
|
29
|
+
// 创建
|
|
30
|
+
const res = await createCheckoutExtensionRequest(appClientId, partnerId, {
|
|
31
|
+
version: '1.0.0',
|
|
32
|
+
name: extension.extension_name,
|
|
33
|
+
resource_url: extension.resource_url
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
if (res?.status === 0) {
|
|
37
|
+
return extractCheckoutResult(res);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
throw new Error(res?.message || 'upsert checkout extension failed');
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
module.exports = upsertCheckout;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
const { commitFunctionRequest, createFunctionRequest } = require('../../api/partnerOpenapi');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
const NAMESPACE = 'cart_transform';
|
|
5
|
+
|
|
6
|
+
// 提取 返回结果
|
|
7
|
+
function extractFunctionResult(res) {
|
|
8
|
+
return {
|
|
9
|
+
extension_id: res.data.function_id,
|
|
10
|
+
extension_version: res.data.version,
|
|
11
|
+
extension_version_id: res.data.version_id
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async function upsertFunction(extension, appClientId, partnerId, appAccessToken) {
|
|
16
|
+
const sourceCodePath = path.join(extension.configPath, '..', 'src', 'index.js');
|
|
17
|
+
|
|
18
|
+
// 有 version_id 则 升级,没有则创建
|
|
19
|
+
if (extension.extension_id && extension.extension_version) {
|
|
20
|
+
// 升级
|
|
21
|
+
const res = await commitFunctionRequest(appClientId, appAccessToken.access_token, {
|
|
22
|
+
name: extension.extension_name,
|
|
23
|
+
namespace: NAMESPACE,
|
|
24
|
+
source_code: sourceCodePath,
|
|
25
|
+
file: extension.distPath,
|
|
26
|
+
function_id: extension.extension_id,
|
|
27
|
+
version: extension.extension_version
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
if (res?.code === 'SUCCESS') {
|
|
31
|
+
return extractFunctionResult(res);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
throw new Error(res?.message || 'commit function extension failed');
|
|
35
|
+
} else {
|
|
36
|
+
// 创建
|
|
37
|
+
const res = await createFunctionRequest(appClientId, appAccessToken.access_token, {
|
|
38
|
+
name: extension.extension_name,
|
|
39
|
+
namespace: NAMESPACE,
|
|
40
|
+
source_code: sourceCodePath,
|
|
41
|
+
file: extension.distPath
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
if (res?.code === 'SUCCESS') {
|
|
45
|
+
return extractFunctionResult(res);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
throw new Error(res?.message || 'create function extension failed');
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
module.exports = upsertFunction;
|