shoplazza-cli 0.0.9 → 0.1.8
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 +2 -6
- package/bin/shoplazza +6 -0
- package/examples/checkout-extension/README.md +19 -0
- package/examples/checkout-extension/extension.config.js +4 -0
- package/examples/checkout-extension/extensions/add-shipping-desc/extension.json +10 -0
- package/examples/checkout-extension/extensions/add-shipping-desc/src/index.js +7 -0
- package/examples/checkout-extension/extensions/ext-1/extension.json +10 -0
- package/examples/checkout-extension/extensions/ext-1/src/content.html +3 -0
- package/examples/checkout-extension/extensions/ext-1/src/index.html +5 -0
- package/examples/checkout-extension/extensions/ext-1/src/index.js +11 -0
- package/examples/checkout-extension/extensions/ext-1/src/script.html +3 -0
- package/examples/checkout-extension/extensions/ext-1/src/style.html +3 -0
- package/examples/checkout-extension/extensions/rewrite-navigate/extension.json +10 -0
- package/examples/checkout-extension/extensions/rewrite-navigate/src/content.html +38 -0
- package/examples/checkout-extension/extensions/rewrite-navigate/src/index.html +5 -0
- package/examples/checkout-extension/extensions/rewrite-navigate/src/index.js +12 -0
- package/examples/checkout-extension/extensions/rewrite-navigate/src/script.html +26 -0
- package/examples/checkout-extension/extensions/rewrite-navigate/src/style.html +23 -0
- package/examples/checkout-extension/package.json +17 -0
- package/lib/app/commands/deploy.js +0 -1
- package/lib/app/constants.js +22 -5
- package/lib/app/login.js +0 -1
- package/lib/auth/index.js +42 -0
- package/lib/checkout/api.js +156 -0
- package/lib/checkout/build/plugin/vite-plugin-add-extension-id.js +25 -0
- package/lib/checkout/build/plugin/vite-plugin-transform-extension-html.js +207 -0
- package/lib/checkout/build/vite.config.js +34 -0
- package/lib/checkout/build.js +39 -0
- package/lib/checkout/config.js +97 -0
- package/lib/checkout/console.js +32 -0
- package/lib/checkout/create.js +132 -0
- package/lib/checkout/delete.js +26 -0
- package/lib/checkout/deploy.js +59 -0
- package/lib/checkout/dev/client.js +73 -0
- package/lib/checkout/dev/index.js +142 -0
- package/lib/checkout/fields.js +29 -0
- package/lib/checkout/index.js +63 -0
- package/lib/checkout/preview.js +52 -0
- package/lib/checkout/pull.js +10 -0
- package/lib/checkout/push.js +140 -0
- package/lib/checkout/template/README.md +34 -0
- package/lib/checkout/template/_gitignore +4 -0
- package/lib/checkout/template/extension.config.js +4 -0
- package/lib/checkout/template/extensions/extension-template/extension.json +10 -0
- package/lib/checkout/template/extensions/extension-template/src/content.html +3 -0
- package/lib/checkout/template/extensions/extension-template/src/index.html +5 -0
- package/lib/checkout/template/extensions/extension-template/src/index.js +11 -0
- package/lib/checkout/template/extensions/extension-template/src/script.html +3 -0
- package/lib/checkout/template/extensions/extension-template/src/style.html +3 -0
- package/lib/checkout/template/package.json +17 -0
- package/lib/checkout/undeploy.js +40 -0
- package/lib/checkout/util.js +204 -0
- package/lib/checkout/verify.js +16 -0
- package/lib/checkout/version.js +7 -0
- package/lib/commands/login.js +3 -2
- package/lib/commands/theme/init.js +2 -2
- package/lib/commands/theme/pull.js +1 -1
- package/lib/config.js +4 -0
- package/lib/db/user.js +5 -2
- package/lib/utils.js +57 -5
- package/package.json +29 -3
|
@@ -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,34 @@
|
|
|
1
|
+
const { cwd } = require('process');
|
|
2
|
+
const { vitePluginAddExtensionId } = require('./plugin/vite-plugin-add-extension-id');
|
|
3
|
+
const { vitePluginTransformExtensionHtml } = require('./plugin/vite-plugin-transform-extension-html');
|
|
4
|
+
const { getDistPath } = require('../util');
|
|
5
|
+
|
|
6
|
+
// https://vitejs.dev/config/
|
|
7
|
+
module.exports = {
|
|
8
|
+
viteConfig: (pageEntry) => {
|
|
9
|
+
return {
|
|
10
|
+
plugins: [vitePluginTransformExtensionHtml(), vitePluginAddExtensionId()],
|
|
11
|
+
base: '/',
|
|
12
|
+
root: cwd(),
|
|
13
|
+
build: {
|
|
14
|
+
minify: false,
|
|
15
|
+
emptyOutDir: false,
|
|
16
|
+
copyPublicDir: false,
|
|
17
|
+
rollupOptions: {
|
|
18
|
+
input: pageEntry,
|
|
19
|
+
output: {
|
|
20
|
+
entryFileNames: '[name].[hash].js',
|
|
21
|
+
chunkFileNames: '[name].[hash].js',
|
|
22
|
+
assetFileNames: '[name].[hash].[ext]',
|
|
23
|
+
compact: true,
|
|
24
|
+
inlineDynamicImports: false,
|
|
25
|
+
dir: getDistPath()
|
|
26
|
+
},
|
|
27
|
+
watch: {
|
|
28
|
+
include: './extensions'
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
const { consoleError, consoleBlue } = require('./console');
|
|
2
|
+
const { build } = require('vite');
|
|
3
|
+
const { viteConfig } = require('./build/vite.config');
|
|
4
|
+
const { getExtensionInfo, getDistPath } = require('./util');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
|
|
8
|
+
async function viteBuild(command) {
|
|
9
|
+
try {
|
|
10
|
+
require('./version');
|
|
11
|
+
const { id, path: extensionPath } = getExtensionInfo(command.id);
|
|
12
|
+
const pageEntry = {
|
|
13
|
+
[id]: path.join(extensionPath, 'src', 'index.js')
|
|
14
|
+
};
|
|
15
|
+
if (!fs.existsSync(getDistPath())) {
|
|
16
|
+
fs.mkdirSync(getDistPath());
|
|
17
|
+
}
|
|
18
|
+
fs.readdirSync(getDistPath()).forEach((fname) => {
|
|
19
|
+
if (fname.includes(id)) {
|
|
20
|
+
fs.rmSync(path.join(getDistPath(), fname));
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
await build(viteConfig(pageEntry));
|
|
24
|
+
} catch (err) {
|
|
25
|
+
consoleError('build error: ', err);
|
|
26
|
+
throw 'build_error';
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function cliBuild(command) {
|
|
31
|
+
try {
|
|
32
|
+
await viteBuild(command);
|
|
33
|
+
} catch (err) {}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
module.exports = {
|
|
37
|
+
cliBuild,
|
|
38
|
+
viteBuild
|
|
39
|
+
};
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
cwd: process.cwd(),
|
|
3
|
+
openAipVersion: '/openapi',
|
|
4
|
+
defaultExtensionDir: 'extensions',
|
|
5
|
+
configFile: 'extension.json',
|
|
6
|
+
buildDir: 'dist',
|
|
7
|
+
package: 'package.json',
|
|
8
|
+
questions: commands => {
|
|
9
|
+
const { store, token, theme } = commands;
|
|
10
|
+
return require('inquirer').prompt([
|
|
11
|
+
{
|
|
12
|
+
name: 'store',
|
|
13
|
+
type: 'input',
|
|
14
|
+
default: store,
|
|
15
|
+
message: 'Please enter your store(Eg: https://developer.myshoplaza.com)',
|
|
16
|
+
when: () => {
|
|
17
|
+
if (!store) return true;
|
|
18
|
+
try {
|
|
19
|
+
new URL(store);
|
|
20
|
+
} catch (e) {
|
|
21
|
+
return 'Incorrect store'
|
|
22
|
+
}
|
|
23
|
+
return false;
|
|
24
|
+
},
|
|
25
|
+
validate: text => {
|
|
26
|
+
if (!text) return false;
|
|
27
|
+
try {
|
|
28
|
+
new URL(text);
|
|
29
|
+
} catch (e) {
|
|
30
|
+
return 'Incorrect store'
|
|
31
|
+
}
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
name: 'token',
|
|
37
|
+
type: 'input',
|
|
38
|
+
default: token,
|
|
39
|
+
when: !token,
|
|
40
|
+
message: 'Please enter your token(Private app token)',
|
|
41
|
+
validate: text => !!text
|
|
42
|
+
}
|
|
43
|
+
]);
|
|
44
|
+
},
|
|
45
|
+
pullQuestion: () => {
|
|
46
|
+
return require('inquirer').prompt([
|
|
47
|
+
{
|
|
48
|
+
type: 'list',
|
|
49
|
+
name: 'pullType',
|
|
50
|
+
message: 'Choose pull type: ',
|
|
51
|
+
default: 'extension',
|
|
52
|
+
prefix: '*',
|
|
53
|
+
choices: [
|
|
54
|
+
{
|
|
55
|
+
name: 'Extension',
|
|
56
|
+
value: 'extension'
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
name: 'Fields',
|
|
60
|
+
value: 'fields',
|
|
61
|
+
},
|
|
62
|
+
// TODO:
|
|
63
|
+
// {
|
|
64
|
+
// name: 'Source',
|
|
65
|
+
// value: 'source'
|
|
66
|
+
// }
|
|
67
|
+
]
|
|
68
|
+
}
|
|
69
|
+
]);
|
|
70
|
+
},
|
|
71
|
+
outputDirPathQuestion: (commands = {}) => {
|
|
72
|
+
const outputDirPath = process.env.OUTPUT_DIR_PATH || commands.outputDirPath;
|
|
73
|
+
return require('inquirer').prompt([
|
|
74
|
+
{
|
|
75
|
+
type: 'input',
|
|
76
|
+
name: 'outputDirPath',
|
|
77
|
+
message: 'Please enter extension output directory relative path(Eg: ./src/xxx ): ',
|
|
78
|
+
default: outputDirPath || './dist/chick-extension',
|
|
79
|
+
when: !outputDirPath,
|
|
80
|
+
validate: text => !!text
|
|
81
|
+
}
|
|
82
|
+
]);
|
|
83
|
+
},
|
|
84
|
+
dirPathQuestion: (commands = {}) => {
|
|
85
|
+
const dirPath = process.env.DIR_PATH || commands.dirPath;
|
|
86
|
+
return require('inquirer').prompt([
|
|
87
|
+
{
|
|
88
|
+
type: 'input',
|
|
89
|
+
name: 'dirPath',
|
|
90
|
+
message: 'Please enter extension directory relative path(Eg: ./src/xxx ): ',
|
|
91
|
+
default: dirPath || './extensions',
|
|
92
|
+
when: !dirPath,
|
|
93
|
+
validate: text => !!text
|
|
94
|
+
}
|
|
95
|
+
]);
|
|
96
|
+
}
|
|
97
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
const colors = require('colors');
|
|
2
|
+
|
|
3
|
+
function isDebug() {
|
|
4
|
+
return ~process.argv.indexOf('--debug');
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
function consoleError(...args) {
|
|
8
|
+
if (isDebug()) {
|
|
9
|
+
console.trace(args);
|
|
10
|
+
}
|
|
11
|
+
console.log(colors.red(args.join(' ')));
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function consoleBlue(...args) {
|
|
15
|
+
console.log(colors.blue(args.join(' ')));
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function consoleSuccess(...args) {
|
|
19
|
+
console.log(colors.green(args.join(' ')));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function consoleWarn(...args) {
|
|
23
|
+
console.log(colors.yellow(args.join(' ')));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
module.exports = {
|
|
27
|
+
isDebug,
|
|
28
|
+
consoleError,
|
|
29
|
+
consoleSuccess,
|
|
30
|
+
consoleBlue,
|
|
31
|
+
consoleWarn
|
|
32
|
+
};
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
const { consoleError, consoleSuccess } = require('./console');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const inquirer = require('inquirer');
|
|
5
|
+
const { copy } = require('./util');
|
|
6
|
+
|
|
7
|
+
const cwd = process.cwd();
|
|
8
|
+
|
|
9
|
+
const templateDir = path.resolve(__dirname, './template');
|
|
10
|
+
|
|
11
|
+
const renameFiles = {
|
|
12
|
+
_gitignore: '.gitignore'
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const extensionNamePromptConfig = {
|
|
16
|
+
type: 'input',
|
|
17
|
+
name: 'extensionName',
|
|
18
|
+
message: 'Enter the extension name:',
|
|
19
|
+
prefix: '*'
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
async function promptWhenAddExtension(before = []) {
|
|
23
|
+
return inquirer.prompt([
|
|
24
|
+
...before,
|
|
25
|
+
{
|
|
26
|
+
type: 'input',
|
|
27
|
+
name: 'store',
|
|
28
|
+
message: 'Enter your store URL(example: https://xxx.myshoplaza.com/):',
|
|
29
|
+
prefix: '*'
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
type: 'input',
|
|
33
|
+
name: 'token',
|
|
34
|
+
message: 'Enter the store\'s token:',
|
|
35
|
+
prefix: '*'
|
|
36
|
+
},
|
|
37
|
+
extensionNamePromptConfig
|
|
38
|
+
]);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* 使用cli创建一个项目
|
|
43
|
+
* @param {} command 项目名
|
|
44
|
+
* @returns
|
|
45
|
+
*/
|
|
46
|
+
async function createProject(command) {
|
|
47
|
+
const { projectName, extensionName, store, token } = await promptWhenAddExtension([
|
|
48
|
+
{
|
|
49
|
+
type: 'input',
|
|
50
|
+
name: 'projectName',
|
|
51
|
+
message: 'Please input your project name:',
|
|
52
|
+
prefix: '*'
|
|
53
|
+
}
|
|
54
|
+
]);
|
|
55
|
+
const projectDir = projectName.replace(/\/+$/g, '');
|
|
56
|
+
const root = path.join(cwd, projectDir);
|
|
57
|
+
if (fs.existsSync(root)) {
|
|
58
|
+
consoleError(`the directory '${projectDir}' is exist.`);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
if (!extensionName.trim()) {
|
|
62
|
+
consoleError('extension id is required');
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
fs.mkdirSync(root, { recursive: true });
|
|
66
|
+
|
|
67
|
+
const write = (file, content) => {
|
|
68
|
+
const targetPath = path.join(root, renameFiles[file] ?? file);
|
|
69
|
+
if (content) {
|
|
70
|
+
fs.writeFileSync(targetPath, content);
|
|
71
|
+
} else {
|
|
72
|
+
copy(path.join(templateDir, file), targetPath);
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const files = fs.readdirSync(templateDir);
|
|
77
|
+
for (const file of files.filter((f) => !['package.json'].includes(f))) {
|
|
78
|
+
write(file);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
let projectConfigContent = fs.readFileSync(path.join(templateDir, 'extension.config.js'), 'utf-8');
|
|
82
|
+
projectConfigContent = projectConfigContent.replace('{store}', store).replace('{token}', token);
|
|
83
|
+
write('extension.config.js', projectConfigContent);
|
|
84
|
+
|
|
85
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(templateDir, `package.json`), 'utf-8'));
|
|
86
|
+
pkg.name = projectDir;
|
|
87
|
+
write('package.json', JSON.stringify(pkg, null, 2) + '\n');
|
|
88
|
+
|
|
89
|
+
fs.renameSync(path.join(root, 'extensions/extension-template'), path.join(root, `extensions/${extensionName}`));
|
|
90
|
+
|
|
91
|
+
const extensionConfig = JSON.parse(
|
|
92
|
+
fs.readFileSync(path.join(path.resolve(root, `extensions/${extensionName}`), `extension.json`), 'utf-8')
|
|
93
|
+
);
|
|
94
|
+
extensionConfig.extensionName = extensionName;
|
|
95
|
+
write(`extensions/${extensionName}/extension.json`, JSON.stringify(extensionConfig, null, 2) + '\n');
|
|
96
|
+
consoleSuccess(`Successfully created extension project '${projectName}'.`);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* 使用cli在现有项目中新增一个 extension
|
|
101
|
+
*/
|
|
102
|
+
async function createExtension(command) {
|
|
103
|
+
const { extensionName } = await inquirer.prompt([extensionNamePromptConfig]);
|
|
104
|
+
const extensionsDir = path.join(cwd, 'extensions');
|
|
105
|
+
if (!fs.existsSync(extensionsDir)) {
|
|
106
|
+
consoleError('Please check current dir is a extension project');
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const targetDir = path.join(extensionsDir, extensionName);
|
|
111
|
+
if (fs.existsSync(targetDir)) {
|
|
112
|
+
consoleError(`This '${extensionName}' extension already exists.`);
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
fs.mkdirSync(targetDir);
|
|
116
|
+
|
|
117
|
+
const extensionTemplateDir = path.join(templateDir, 'extensions', 'extension-template');
|
|
118
|
+
const files = fs.readdirSync(extensionTemplateDir);
|
|
119
|
+
for (const file of files.filter((f) => f !== 'extension.json')) {
|
|
120
|
+
copy(path.join(extensionTemplateDir, file), path.join(targetDir, file));
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const extensionConfig = JSON.parse(fs.readFileSync(path.join(extensionTemplateDir, `extension.json`), 'utf-8'));
|
|
124
|
+
extensionConfig.extensionName = extensionName;
|
|
125
|
+
fs.writeFileSync(path.join(targetDir, 'extension.json'), JSON.stringify(extensionConfig, null, 2) + '\n');
|
|
126
|
+
consoleSuccess(`Successfully created extension '${extensionName}'.`);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
module.exports = {
|
|
130
|
+
createProject,
|
|
131
|
+
createExtension
|
|
132
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
const api = require('./api');
|
|
2
|
+
const loading = require('loading-cli');
|
|
3
|
+
const { verifyConfig } = require('./verify');
|
|
4
|
+
const { getExtensionInfo, getExtensionConfig } = require('./util');
|
|
5
|
+
|
|
6
|
+
module.exports = async (commands = {}) => {
|
|
7
|
+
await verifyConfig(commands);
|
|
8
|
+
const info = getExtensionInfo(commands.id);
|
|
9
|
+
const config = getExtensionConfig(commands.id);
|
|
10
|
+
const deleteLoading = loading(`Delete the extension '${info.id}'.`).start();
|
|
11
|
+
await Promise.all([
|
|
12
|
+
api.deleteExtension({
|
|
13
|
+
extension_id: info.id,
|
|
14
|
+
version: config.version
|
|
15
|
+
})
|
|
16
|
+
])
|
|
17
|
+
.then(() => {
|
|
18
|
+
deleteLoading.succeed(`Delete the extension '${info.id}' succeed.`);
|
|
19
|
+
})
|
|
20
|
+
.catch(() => {
|
|
21
|
+
deleteLoading.fail(`Delete the extension '${info.id}' failed.`).fail();
|
|
22
|
+
})
|
|
23
|
+
.finally(() => {
|
|
24
|
+
deleteLoading.stop();
|
|
25
|
+
});
|
|
26
|
+
};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
const api = require('./api');
|
|
2
|
+
const { verifyConfig } = require('./verify');
|
|
3
|
+
const { useSelectExtensionMode } = require('./util');
|
|
4
|
+
const inquirer = require('inquirer');
|
|
5
|
+
const { consoleSuccess, consoleError, consoleBlue } = require('./console');
|
|
6
|
+
|
|
7
|
+
module.exports = async () => {
|
|
8
|
+
try {
|
|
9
|
+
await verifyConfig();
|
|
10
|
+
const extensionInfo = await initSelectedExtensionInfo();
|
|
11
|
+
await deployExtension(extensionInfo);
|
|
12
|
+
} catch (error) {
|
|
13
|
+
error.response ? consoleError(`Error: ${error.response.status}-${error.response.config.url}`) : consoleError(error);
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
async function initSelectedExtensionInfo() {
|
|
18
|
+
const res = await api.getExtensionList();
|
|
19
|
+
const extensionList = res.data?.data?.extensions || [];
|
|
20
|
+
return useSelectExtensionMode(extensionList, 'deploy');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async function deployExtension(extensionInfo) {
|
|
24
|
+
const res = await api.getVersionList({
|
|
25
|
+
extension_id: extensionInfo.extension_id
|
|
26
|
+
});
|
|
27
|
+
const versionList = res.data?.data?.extensions || [];
|
|
28
|
+
const choices = versionList.map((item) => ({
|
|
29
|
+
name: `v${item.version}${item.publish_status === 'published' ? '(Published)' : ''}`,
|
|
30
|
+
value: item.id
|
|
31
|
+
}));
|
|
32
|
+
const { versionId, confirm } = await inquirer.prompt([
|
|
33
|
+
{
|
|
34
|
+
type: 'list',
|
|
35
|
+
name: 'versionId',
|
|
36
|
+
message: `Please select a version`,
|
|
37
|
+
prefix: '*',
|
|
38
|
+
loop: false,
|
|
39
|
+
choices
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
type: 'confirm',
|
|
43
|
+
name: 'confirm',
|
|
44
|
+
message: 'Are you sure you want to deploy?',
|
|
45
|
+
default: false
|
|
46
|
+
}
|
|
47
|
+
]);
|
|
48
|
+
|
|
49
|
+
if (confirm) {
|
|
50
|
+
const versionInfo = versionList.find((item) => item.id === versionId);
|
|
51
|
+
await api.deployExtension({
|
|
52
|
+
extension_id: versionInfo.extension_id,
|
|
53
|
+
id: versionInfo.id
|
|
54
|
+
});
|
|
55
|
+
consoleSuccess(`Successfully deployed the extension '${versionInfo.name}(v${versionInfo.version})'.`);
|
|
56
|
+
} else {
|
|
57
|
+
consoleBlue('Deploy cancelled');
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
function setupWebSocket() {
|
|
2
|
+
const socket = new WebSocket(`ws://localhost:8888`, 'checkout-hmr');
|
|
3
|
+
let isOpened = false;
|
|
4
|
+
|
|
5
|
+
socket.addEventListener(
|
|
6
|
+
'open',
|
|
7
|
+
() => {
|
|
8
|
+
isOpened = true;
|
|
9
|
+
},
|
|
10
|
+
{ once: true }
|
|
11
|
+
);
|
|
12
|
+
|
|
13
|
+
socket.addEventListener('message', async ({ data }) => {
|
|
14
|
+
data = JSON.parse(data);
|
|
15
|
+
console.log('%c[Extension Development] Receive Data:', 'color:green;', data);
|
|
16
|
+
switch (data.event) {
|
|
17
|
+
case 'init':
|
|
18
|
+
CheckoutAPI.extension.DEV_addExtensions(data.data);
|
|
19
|
+
break;
|
|
20
|
+
case 'update':
|
|
21
|
+
CheckoutAPI.extension.DEV_updateExtension(data.data);
|
|
22
|
+
break;
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
socket.addEventListener('close', async ({ wasClean }) => {
|
|
27
|
+
if (wasClean) return;
|
|
28
|
+
console.debug('noClose');
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
return socket;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function initDevModeIcon() {
|
|
35
|
+
const icon = Object.assign(document.createElement('div'), {
|
|
36
|
+
className: 'checkout-extension-dev-icon',
|
|
37
|
+
textContent: 'Close Dev Mode',
|
|
38
|
+
style: `
|
|
39
|
+
position: fixed;
|
|
40
|
+
bottom: 45px;
|
|
41
|
+
right: 30px;
|
|
42
|
+
z-index: 99999;
|
|
43
|
+
color: #fff;
|
|
44
|
+
background: linear-gradient(135deg, #007bff, #0056b3);
|
|
45
|
+
border-radius: 20px;
|
|
46
|
+
font-size: 14px;
|
|
47
|
+
cursor: pointer;
|
|
48
|
+
padding: 10px;
|
|
49
|
+
text-align: center;
|
|
50
|
+
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
|
51
|
+
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
|
52
|
+
`
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
icon.addEventListener('mouseenter', () => {
|
|
56
|
+
icon.style.transform = 'scale(1.1)';
|
|
57
|
+
icon.style.boxShadow = '0 6px 12px rgba(0, 0, 0, 0.3)';
|
|
58
|
+
});
|
|
59
|
+
icon.addEventListener('mouseleave', () => {
|
|
60
|
+
icon.style.transform = 'scale(1)';
|
|
61
|
+
icon.style.boxShadow = '0 4px 8px rgba(0, 0, 0, 0.2)';
|
|
62
|
+
});
|
|
63
|
+
icon.addEventListener('click', () => {
|
|
64
|
+
CheckoutAPI.extension.DEV_switchDevMode();
|
|
65
|
+
},{
|
|
66
|
+
once: true,
|
|
67
|
+
});
|
|
68
|
+
document.body.appendChild(icon);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
setupWebSocket();
|
|
73
|
+
initDevModeIcon();
|