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,45 @@
|
|
|
1
|
+
const axios = require('axios');
|
|
2
|
+
const { getPartnerHost } = require('./config');
|
|
3
|
+
const { getSSOAccessToken } = require('./sso-token');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 获取合作伙伴访问令牌
|
|
7
|
+
* @param {string} appClientId
|
|
8
|
+
* @param {string} appSecret
|
|
9
|
+
* @returns {Promise<{
|
|
10
|
+
* access_token: string,
|
|
11
|
+
* token_type: string,
|
|
12
|
+
* expires_in: number,
|
|
13
|
+
* created_at: number,
|
|
14
|
+
* store_id: string | null,
|
|
15
|
+
* store_name: string | null,
|
|
16
|
+
* expires_at: number,
|
|
17
|
+
* locale: string
|
|
18
|
+
* }>}
|
|
19
|
+
*/
|
|
20
|
+
async function getPartnerAccessToken(appClientId, appSecret) {
|
|
21
|
+
try {
|
|
22
|
+
const ssoAccessToken = await getSSOAccessToken();
|
|
23
|
+
const response = await axios.post(
|
|
24
|
+
`${getPartnerHost()}/partner/oauth/token`,
|
|
25
|
+
{
|
|
26
|
+
client_id: appClientId,
|
|
27
|
+
client_secret: appSecret,
|
|
28
|
+
grant_type: 'client_credentials'
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
headers: {
|
|
32
|
+
Cookie: `awesomev2=${ssoAccessToken.access_token}`
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
return response.data;
|
|
38
|
+
} catch (error) {
|
|
39
|
+
throw new Error(`[${error.config.url}] ${error.response.data.message}`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
module.exports = {
|
|
44
|
+
getPartnerAccessToken
|
|
45
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
const axios = require('axios');
|
|
2
|
+
const { getSSOHost, getSSOClientId } = require('./config');
|
|
3
|
+
const { startOAuthServer } = require('./oauth-server');
|
|
4
|
+
const { buildAuthUrl } = require('./url-builder');
|
|
5
|
+
const { GRANT_TYPE, REDIRECT_URI } = require('../../constant/sso');
|
|
6
|
+
const { ssoStore } = require('../../store');
|
|
7
|
+
const { SSO_STORE_KEY } = require('../../store/config');
|
|
8
|
+
const { errorMessageRender } = require('../../utils/views/message');
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* 获取 SSO 访问令牌
|
|
12
|
+
* @returns {Promise<{
|
|
13
|
+
* access_token: string,
|
|
14
|
+
* expires_in: number,
|
|
15
|
+
* session_id: string,
|
|
16
|
+
* token_type: string
|
|
17
|
+
* }>}
|
|
18
|
+
*/
|
|
19
|
+
async function getSSOAccessToken() {
|
|
20
|
+
if (ssoStore.has(SSO_STORE_KEY.ACCESS_TOKEN)) {
|
|
21
|
+
return ssoStore.getSSOInfo();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
let code;
|
|
25
|
+
try {
|
|
26
|
+
// 启动 OAuth 服务器并获取授权码
|
|
27
|
+
code = await startOAuthServer({
|
|
28
|
+
onGetUrl: buildAuthUrl // 传入URL构建函数
|
|
29
|
+
});
|
|
30
|
+
} catch (error) {
|
|
31
|
+
errorMessageRender(error.message);
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
let response;
|
|
36
|
+
try {
|
|
37
|
+
// 用授权码换取访问令牌
|
|
38
|
+
response = await axios.post(
|
|
39
|
+
`${getSSOHost()}/api/oauth/token`,
|
|
40
|
+
{
|
|
41
|
+
code,
|
|
42
|
+
client_id: getSSOClientId(),
|
|
43
|
+
redirect_uri: REDIRECT_URI,
|
|
44
|
+
grant_type: GRANT_TYPE
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
headers: {
|
|
48
|
+
'Content-Type': 'application/x-www-form-urlencoded'
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
);
|
|
52
|
+
} catch (error) {
|
|
53
|
+
errorMessageRender(error.message);
|
|
54
|
+
process.exit(1);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
ssoStore.setSSOInfo({
|
|
58
|
+
access_token: response.data.access_token,
|
|
59
|
+
expires_in: response.data.expires_in,
|
|
60
|
+
session_id: response.data.session_id,
|
|
61
|
+
token_type: response.data.token_type
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
return response.data;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
module.exports = {
|
|
68
|
+
getSSOAccessToken
|
|
69
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
const axios = require('axios');
|
|
2
|
+
const { getSSOHost, getAccountHost, getPartnerHost } = require('./config');
|
|
3
|
+
const { getSSOAccessToken } = require('./sso-token');
|
|
4
|
+
const { installStore } = require('../../store');
|
|
5
|
+
const { selectOne } = require('../../utils/views/select');
|
|
6
|
+
|
|
7
|
+
// 获取店铺列表
|
|
8
|
+
async function getStoreList(partnerId, ssoAccessToken) {
|
|
9
|
+
const response = await axios.get(`${getPartnerHost()}/api/partners/stores`, {
|
|
10
|
+
params: {
|
|
11
|
+
page_size: 10
|
|
12
|
+
},
|
|
13
|
+
headers: {
|
|
14
|
+
'x-shoplazza-partner-id': partnerId,
|
|
15
|
+
Cookie: `awesomev2=${ssoAccessToken}`
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
return response.data;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// 选择店铺
|
|
23
|
+
async function chooseStore(partnerId, ssoAccessToken) {
|
|
24
|
+
const storesResponse = await getStoreList(partnerId, ssoAccessToken);
|
|
25
|
+
|
|
26
|
+
if (storesResponse.count === 0) {
|
|
27
|
+
throw new Error('No store found, please create one first!');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const store = await selectOne(storesResponse.data, {
|
|
31
|
+
message: 'Please select a store to install:',
|
|
32
|
+
autoPickSingle: false,
|
|
33
|
+
formatChoice: (store) => ({ name: `${store.slug} (${store.id})`, value: store.primary_domain })
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
return store;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* 获取店铺访问令牌
|
|
41
|
+
* @param {string} appClientId
|
|
42
|
+
* @returns {Promise<{
|
|
43
|
+
* store_id: string,
|
|
44
|
+
* access_token: string,
|
|
45
|
+
* expires_at: number,
|
|
46
|
+
* }>}
|
|
47
|
+
*/
|
|
48
|
+
async function getStoreAccessToken(appClientId, partnerId) {
|
|
49
|
+
if (installStore.has(appClientId)) {
|
|
50
|
+
const installData = installStore.getInstallInfo(appClientId);
|
|
51
|
+
return installData;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
const ssoAccessToken = await getSSOAccessToken();
|
|
56
|
+
|
|
57
|
+
const store_domain = await chooseStore(partnerId, ssoAccessToken.access_token);
|
|
58
|
+
|
|
59
|
+
const response = await axios.get(`${getSSOHost()}/api/sso/current/users`, {
|
|
60
|
+
headers: {
|
|
61
|
+
Cookie: `awesomev2=${ssoAccessToken.access_token}`,
|
|
62
|
+
'Content-Type': 'application/json'
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
const userInfo = response.data;
|
|
67
|
+
const activeUser = userInfo.users.find((user) => user.active);
|
|
68
|
+
|
|
69
|
+
const storeTokenResponse = await axios.post(
|
|
70
|
+
`${getAccountHost()}/api/accounts/store/token`,
|
|
71
|
+
{
|
|
72
|
+
user_id: activeUser.user_id,
|
|
73
|
+
domain: store_domain
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
headers: {
|
|
77
|
+
Cookie: `awesomev2=${ssoAccessToken.access_token}`,
|
|
78
|
+
'Content-Type': 'application/json'
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
const installData = {
|
|
84
|
+
store_domain,
|
|
85
|
+
access_token: storeTokenResponse.data.access_token,
|
|
86
|
+
expires_at: storeTokenResponse.data.expires_at,
|
|
87
|
+
store_id: storeTokenResponse.data.store_id
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
installStore.setInstallInfo(appClientId, installData);
|
|
91
|
+
|
|
92
|
+
return installData;
|
|
93
|
+
} catch (error) {
|
|
94
|
+
throw new Error(`Store access token get failed: ${error.message}`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
module.exports = {
|
|
99
|
+
getStoreAccessToken
|
|
100
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
const { getSSOHost, getSSOClientId } = require('./config');
|
|
2
|
+
const { REDIRECT_URI } = require('../../constant/sso');
|
|
3
|
+
|
|
4
|
+
function buildAuthUrl() {
|
|
5
|
+
const url = new URL(`${getSSOHost()}/switch_account`);
|
|
6
|
+
url.searchParams.set('lack', '0');
|
|
7
|
+
url.searchParams.set('continue', buildContinueUrl());
|
|
8
|
+
return url.toString();
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function buildContinueUrl() {
|
|
12
|
+
const url = new URL(`${getSSOHost()}/api/oauth/authorize`);
|
|
13
|
+
url.searchParams.set('action', 'login');
|
|
14
|
+
url.searchParams.set('client_id', getSSOClientId());
|
|
15
|
+
url.searchParams.set('redirect_uri', REDIRECT_URI);
|
|
16
|
+
url.searchParams.set('response_type', 'code');
|
|
17
|
+
return url.toString();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
module.exports = {
|
|
21
|
+
buildAuthUrl,
|
|
22
|
+
buildContinueUrl
|
|
23
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
const { configStore } = require('../../store');
|
|
2
|
+
const { parseTomlFile } = require('../../utils/toml');
|
|
3
|
+
const { getAppCompleteInfoRequest } = require('../../api/cli');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @description 获取当前正在使用的 configFile
|
|
8
|
+
* @param {string} projectDir 项目目录,默认使用 process.env.APP_PROJECT_DIR
|
|
9
|
+
* @returns {string} 返回当前正在使用的 configFile
|
|
10
|
+
*/
|
|
11
|
+
function getActiveConfigFile(projectDir = process.env.APP_PROJECT_DIR) {
|
|
12
|
+
const configCache = configStore.getProjectConfig(projectDir);
|
|
13
|
+
return configCache?.configFile ? configCache.configFile : 'shoplazza.app.toml';
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @description 获取 config 相关信息
|
|
18
|
+
* @param {string} projectDir 项目目录,默认使用 process.env.APP_PROJECT_DIR
|
|
19
|
+
* @returns {Promise<{app: object, partner: object, user: object, configContent: object}>} 返回 app、partner、user、configContent 信息
|
|
20
|
+
*/
|
|
21
|
+
async function getAppCompleteInfo(projectDir = process.env.APP_PROJECT_DIR) {
|
|
22
|
+
const activeConfigFile = getActiveConfigFile(projectDir);
|
|
23
|
+
|
|
24
|
+
// 获取 configFile 信息
|
|
25
|
+
const configContent = parseTomlFile(path.join(projectDir, activeConfigFile));
|
|
26
|
+
|
|
27
|
+
// 获取 config 相关信息
|
|
28
|
+
const completeInfo = await getAppCompleteInfoRequest(configContent.client_id);
|
|
29
|
+
|
|
30
|
+
return {
|
|
31
|
+
app: completeInfo.app,
|
|
32
|
+
partner: completeInfo.partner,
|
|
33
|
+
user: completeInfo.user,
|
|
34
|
+
configContent: configContent
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
module.exports = {
|
|
39
|
+
getActiveConfigFile,
|
|
40
|
+
getAppCompleteInfo
|
|
41
|
+
};
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// lib/app/services/devServer/app.js
|
|
2
|
+
const express = require('express');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const crypto = require('crypto');
|
|
5
|
+
const axios = require('axios');
|
|
6
|
+
const { hmacValidatorMiddleWare } = require('./middleware');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* 创建 dev server 应用
|
|
10
|
+
* @param {{ client_id: string, client_secret: string, redirect_uri: string, app_url_path: string, redirect_url_path: string }} config 配置
|
|
11
|
+
* @returns {express.Application} dev server 应用
|
|
12
|
+
*/
|
|
13
|
+
function createApp(config) {
|
|
14
|
+
if (typeof config !== 'object') {
|
|
15
|
+
throw new Error('Dev server config is invalid');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const { client_id, client_secret, redirect_uri, app_url_path, redirect_url_path, scopes } = config;
|
|
19
|
+
|
|
20
|
+
if (!client_id || !client_secret || !redirect_uri || !app_url_path || !redirect_url_path || !scopes) {
|
|
21
|
+
throw new Error('Dev server config is invalid');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const app = express();
|
|
25
|
+
|
|
26
|
+
app.set('view engine', 'ejs');
|
|
27
|
+
app.set('views', path.join(__dirname, 'views'));
|
|
28
|
+
|
|
29
|
+
app.use(express.json());
|
|
30
|
+
app.use(express.urlencoded({ extended: true }));
|
|
31
|
+
|
|
32
|
+
// 将 config 挂载到 req.config 上
|
|
33
|
+
app.use((req, res, next) => {
|
|
34
|
+
req.config = { client_id, client_secret, redirect_uri };
|
|
35
|
+
next();
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
app.get('/', (req, res) => {
|
|
39
|
+
res.render('app', {
|
|
40
|
+
title: '插件调试导航',
|
|
41
|
+
preview_url: '/admin/card'
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
app.get(app_url_path, (req, res) => {
|
|
46
|
+
const encodedScopes = encodeURIComponent(scopes);
|
|
47
|
+
const state = crypto.randomBytes(16).toString('hex');
|
|
48
|
+
const { client_id, redirect_uri } = req.config;
|
|
49
|
+
|
|
50
|
+
res.redirect(
|
|
51
|
+
`https://${req.query.shop}/admin/oauth/authorize?client_id=${client_id}&scope=${encodedScopes}&redirect_uri=${redirect_uri}&response_type=code&state=${state}`
|
|
52
|
+
);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
app.get(redirect_url_path, hmacValidatorMiddleWare, async (req, res) => {
|
|
56
|
+
const { code, hmac, state, shop } = req.query;
|
|
57
|
+
const { client_id, client_secret, redirect_uri } = req.config;
|
|
58
|
+
|
|
59
|
+
if (shop && hmac && code) {
|
|
60
|
+
const { data } = await axios.post(`https://${shop}/admin/oauth/token`, {
|
|
61
|
+
client_id,
|
|
62
|
+
client_secret,
|
|
63
|
+
code,
|
|
64
|
+
grant_type: 'authorization_code',
|
|
65
|
+
redirect_uri
|
|
66
|
+
});
|
|
67
|
+
res.redirect('/');
|
|
68
|
+
} else {
|
|
69
|
+
res.status(400).send('Required parameters missing');
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
return app;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
module.exports = createApp;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
const createApp = require('./app');
|
|
2
|
+
const { CloudflareTunnel, NgrokTunnel, TunnelManager } = require('./tunnel');
|
|
3
|
+
const chalk = require('chalk');
|
|
4
|
+
const net = require('net');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* 检查端口是否可用
|
|
8
|
+
* @param {number} port 要检查的端口
|
|
9
|
+
* @returns {Promise<boolean>} 端口是否可用
|
|
10
|
+
*/
|
|
11
|
+
function isPortAvailable(port) {
|
|
12
|
+
return new Promise((resolve) => {
|
|
13
|
+
const server = net.createServer();
|
|
14
|
+
|
|
15
|
+
server.listen(port, () => {
|
|
16
|
+
server.once('close', () => {
|
|
17
|
+
resolve(true);
|
|
18
|
+
});
|
|
19
|
+
server.close();
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
server.on('error', () => {
|
|
23
|
+
resolve(false);
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* 查找可用的端口
|
|
30
|
+
* @param {number} startPort 起始端口,默认3457
|
|
31
|
+
* @param {number} maxAttempts 最大尝试次数,默认100
|
|
32
|
+
* @returns {Promise<number>} 可用的端口号
|
|
33
|
+
*/
|
|
34
|
+
async function findAvailablePort(startPort = 3457, maxAttempts = 100) {
|
|
35
|
+
for (let i = 0; i < maxAttempts; i++) {
|
|
36
|
+
const port = startPort + i;
|
|
37
|
+
if (await isPortAvailable(port)) {
|
|
38
|
+
return port;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
throw new Error(`无法找到可用端口,已尝试端口范围 ${startPort}-${startPort + maxAttempts - 1}`);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// 启动隧道(策略:cloudflare -> ngrok)
|
|
45
|
+
async function startTunnel(port) {
|
|
46
|
+
const manager = new TunnelManager([
|
|
47
|
+
new CloudflareTunnel(port),
|
|
48
|
+
new NgrokTunnel(port, process.env.NGROK_AUTHTOKEN, process.env.NGROK_DOMAIN)
|
|
49
|
+
]);
|
|
50
|
+
|
|
51
|
+
return manager.start();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const APP_PATH = '/auth';
|
|
55
|
+
const REDIRECT_PATH = '/auth/callback';
|
|
56
|
+
const PORT = 3457;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* 启动 dev 服务
|
|
60
|
+
* @param {Object} params - 请求参数
|
|
61
|
+
* @param {string} params.clientId - 客户端 ID
|
|
62
|
+
* @param {string} params.clientSecret - 客户端密钥
|
|
63
|
+
* @param {string} params.scopes - 权限范围
|
|
64
|
+
*/
|
|
65
|
+
async function startDevServer({ clientId, clientSecret, scopes }) {
|
|
66
|
+
// 查找可用端口
|
|
67
|
+
const availablePort = await findAvailablePort(PORT);
|
|
68
|
+
// 启动隧道
|
|
69
|
+
const { url: publicUrl, close: closeTunnel } = await startTunnel(availablePort);
|
|
70
|
+
|
|
71
|
+
const appUrl = new URL(APP_PATH, publicUrl).toString();
|
|
72
|
+
const redirectUrl = new URL(REDIRECT_PATH, publicUrl).toString();
|
|
73
|
+
|
|
74
|
+
const app = createApp({
|
|
75
|
+
client_id: clientId,
|
|
76
|
+
client_secret: clientSecret,
|
|
77
|
+
redirect_uri: redirectUrl,
|
|
78
|
+
app_url_path: APP_PATH,
|
|
79
|
+
redirect_url_path: REDIRECT_PATH,
|
|
80
|
+
scopes: scopes
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
return new Promise((resolve, reject) => {
|
|
84
|
+
const server = app.listen(availablePort, () => {
|
|
85
|
+
resolve({
|
|
86
|
+
server,
|
|
87
|
+
appUrl,
|
|
88
|
+
redirectUrl,
|
|
89
|
+
closeTunnel
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
console.log(`🔍 dev server port: ${availablePort}`);
|
|
93
|
+
|
|
94
|
+
console.log(chalk.green('Dev server start success 🎉 🎉 🎉'));
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
app.on('error', reject);
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
module.exports = {
|
|
102
|
+
startDevServer
|
|
103
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
const crypto = require('crypto');
|
|
2
|
+
const { secureCompare } = require('../utils');
|
|
3
|
+
|
|
4
|
+
module.exports = function hmacValidatorMiddleWare(req, res, next) {
|
|
5
|
+
const { code, hmac, state, shop } = req.query;
|
|
6
|
+
const map = Object.assign({}, req.query);
|
|
7
|
+
delete map['hmac'];
|
|
8
|
+
const sortedKeys = Object.keys(map).sort();
|
|
9
|
+
const message = sortedKeys.map((key) => `${key}=${map[key]}`).join('&');
|
|
10
|
+
const { client_secret } = req.config;
|
|
11
|
+
|
|
12
|
+
const generated_hash = crypto
|
|
13
|
+
.createHmac('sha256', client_secret)
|
|
14
|
+
.update(message)
|
|
15
|
+
.digest('hex');
|
|
16
|
+
if (!secureCompare(generated_hash, hmac)) {
|
|
17
|
+
return res.status(400).send('HMAC validation failed');
|
|
18
|
+
}
|
|
19
|
+
next();
|
|
20
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
const CloudflareTunnel = require('./providers/cloudflare');
|
|
2
|
+
const NgrokTunnel = require('./providers/ngrok');
|
|
3
|
+
|
|
4
|
+
class TunnelManager {
|
|
5
|
+
constructor(strategies) {
|
|
6
|
+
this.strategies = strategies;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
async start() {
|
|
10
|
+
const errors = []; // 收集所有策略的错误信息
|
|
11
|
+
|
|
12
|
+
for (let i = 0; i < this.strategies.length; i++) {
|
|
13
|
+
const strategy = this.strategies[i];
|
|
14
|
+
const strategyName = strategy.constructor.name;
|
|
15
|
+
|
|
16
|
+
try {
|
|
17
|
+
console.log(`🔄 Trying tunnel strategy: ${strategyName}...`);
|
|
18
|
+
const { url, close } = await strategy.start();
|
|
19
|
+
console.log(`✅ ${strategyName} tunnel started successfully!`);
|
|
20
|
+
return { url, close, strategy: strategyName };
|
|
21
|
+
} catch (err) {
|
|
22
|
+
console.error(`❌ ${strategyName} failed:`, err.message);
|
|
23
|
+
errors.push({ strategy: strategyName, error: err.message });
|
|
24
|
+
|
|
25
|
+
// 如果不是最后一个策略,继续尝试下一个
|
|
26
|
+
if (i < this.strategies.length - 1) {
|
|
27
|
+
console.log(`🔄 Falling back to next tunnel strategy...`);
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// 所有策略都失败了
|
|
34
|
+
const errorDetails = errors.map((e) => `${e.strategy}: ${e.error}`).join('; ');
|
|
35
|
+
throw new Error(`All tunnel strategies failed. Errors: ${errorDetails}`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
module.exports = {
|
|
40
|
+
CloudflareTunnel,
|
|
41
|
+
NgrokTunnel,
|
|
42
|
+
TunnelManager
|
|
43
|
+
};
|