@airiot/cli 1.0.0

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.
@@ -0,0 +1,45 @@
1
+ import chalk from 'chalk';
2
+ import {createServer} from 'vite';
3
+ import { getHost, getBaseUrl } from '../config/envs.js';
4
+ import paths from '../config/paths.js';
5
+ import plugins from '../vite-plugin/index.js';
6
+
7
+ const DEFAULT_PORT = parseInt(process.env.PORT, 10) || 3000;
8
+
9
+ const host = getHost();
10
+ const baseUrl = getBaseUrl();
11
+
12
+ console.log(chalk.green('[iot:envs] 请求 IOT_URL 为: ' + host));
13
+
14
+ (async () => {
15
+ const proxy = {
16
+ '/rest': {
17
+ target: host,
18
+ changeOrigin: true
19
+ },
20
+ '/ws': {
21
+ ws: true,
22
+ target: host,
23
+ changeOrigin: true
24
+ }
25
+ }
26
+ if (!(baseUrl.startsWith('http://') || baseUrl.startsWith('https://') || baseUrl.startsWith('//'))) {
27
+ proxy[baseUrl + '/node_modules/@airiot'] = {
28
+ target: host,
29
+ changeOrigin: true
30
+ }
31
+ }
32
+
33
+ const server = await createServer({
34
+ configFile: false,
35
+ root: paths.appPath,
36
+ plugins: await plugins(),
37
+ server: {
38
+ proxy,
39
+ port: DEFAULT_PORT,
40
+ },
41
+ });
42
+ await server.listen();
43
+
44
+ server.printUrls();
45
+ })();
@@ -0,0 +1 @@
1
+ console.log('暂不支持测试')
@@ -0,0 +1,137 @@
1
+ import chalk from "chalk";
2
+ import inquirer from "inquirer";
3
+ import os from "os";
4
+ import archiver from "archiver";
5
+ import packlist from 'npm-packlist';
6
+ import fetch from "node-fetch";
7
+ import FormData from "form-data";
8
+ import fs from "fs";
9
+ import paths from "../config/paths.js";
10
+
11
+ const jsonData = fs.readFileSync(paths.appPackageJson, 'utf-8');
12
+ const packageJson = JSON.parse(jsonData);
13
+
14
+ const envs = process.env;
15
+ const args = process.argv;
16
+ let packageName = packageJson.name;
17
+ const buildPath = 'dist'
18
+
19
+ const getOpt = (name, envName) => {
20
+ let value
21
+ const hostArgs = args.filter(arg => arg.startsWith(`--${name}=`))
22
+ if(hostArgs.length != 0) {
23
+ value = hostArgs[0].replace(`--${name}=`, '')
24
+ } else if(envs[envName]) {
25
+ value = envs[envName]
26
+ }
27
+
28
+ if(value && value.endsWith('/')) {
29
+ value = value.substring(0, value.length - 1)
30
+ }
31
+
32
+ return value
33
+ }
34
+
35
+ const questions = [];
36
+ const forceQuestion = args.filter(arg => arg.startsWith("-f")).length != 0
37
+ const opts = {
38
+ 'host': getOpt('host', 'IOT_URL'),
39
+ 'username': getOpt('username', 'IOT_USER'),
40
+ 'password': getOpt('password', 'IOT_PASSWORD')
41
+ }
42
+
43
+ if (forceQuestion || !opts.host) {
44
+ questions.push({ name: "host", message: "[iot] Host:" });
45
+ }
46
+ if (forceQuestion || !opts.username) {
47
+ questions.push({ name: "username", message: "[iot] Username:" });
48
+ }
49
+ if (forceQuestion || !opts.password) {
50
+ questions.push({ name: "password", message: "[iot] Password:", type: "password" });
51
+ }
52
+
53
+ const getFormData = () =>
54
+ new Promise((resolve, reject) => {
55
+ const form = new FormData();
56
+
57
+ packlist({ path: paths.appPath })
58
+ .then(files => {
59
+ // 压缩包方式
60
+ const archive = archiver("zip", {
61
+ zlib: { level: 9 }, // Sets the compression level.
62
+ });
63
+ const fileKey = Math.ceil(Math.random() * 10000)
64
+ const output = fs.createWriteStream(os.tmpdir() + "/package_"+fileKey +".zip");
65
+
66
+ output.on("close", function () {
67
+ form.append(
68
+ "zipFile",
69
+ fs.createReadStream(os.tmpdir() + "/package_"+fileKey +".zip")
70
+ );
71
+ resolve(form);
72
+ });
73
+
74
+ archive.pipe(output);
75
+ files.forEach(f => archive.file(f));
76
+ archive.finalize();
77
+ })
78
+ });
79
+
80
+ inquirer
81
+ .prompt(questions)
82
+ .then((answers) => {
83
+ let host = answers.host || opts.host;
84
+ let username = answers.username || opts.username;
85
+ let password = answers.password || opts.password;
86
+
87
+ host = host.endsWith("/") ? host : host + "/";
88
+
89
+ // get use token from environment
90
+ fetch(host + "rest/core/auth/login", {
91
+ method: "POST",
92
+ body: JSON.stringify({ username, password }),
93
+ headers: { "Content-Type": "application/json", "Request-Type": "service" },
94
+ })
95
+ .then((res) => {
96
+ if (res.status != 200) {
97
+ return new Promise((resolve, reject) => {
98
+ res.text().then(err => {
99
+ reject(err)
100
+ });
101
+ });
102
+ } else {
103
+ return res.json();
104
+ }
105
+ })
106
+ .then((json) => {
107
+ if (!json.token) {
108
+ throw new Error(json);
109
+ }
110
+ const token = json.token;
111
+ getFormData().then((form) => {
112
+ console.log(chalk.yellow("[iot:upload] 上传中 ...... "));
113
+
114
+ fetch(host + "rest/front/static/upload/" + packageName, {
115
+ method: "POST",
116
+ body: form,
117
+ headers: { ...form.getHeaders(), Authorization: token, "Request-Type": "service" },
118
+ })
119
+ .then((res) => {
120
+ if (res.status != 200) {
121
+ res.text().then(text => console.log(chalk.red("[iot:install] 接口错误" + "\n" + text)))
122
+ } else {
123
+ console.log(
124
+ chalk.green("[iot:upload] " + packageName + " 上传成功")
125
+ );
126
+ }
127
+ })
128
+ .catch((err) => console.log(chalk.red("\n" + err.toString())));
129
+ });
130
+ })
131
+ .catch((err) =>
132
+ console.error(chalk.red("[iot:upload] login failed" + "\n" + err.toString()))
133
+ );
134
+ })
135
+ .catch((err) =>
136
+ console.error(chalk.red("[iot:upload] login failed" + "\n" + err.toString()))
137
+ );
@@ -0,0 +1,79 @@
1
+ import paths from '../config/paths.js'
2
+ import { join, resolve } from 'path'
3
+ import { promises as fsPromises, readFileSync, writeFileSync } from 'fs';
4
+ import { execFileSync, exec } from 'child_process';
5
+
6
+ const isCI = process.env.CI &&
7
+ (typeof process.env.CI !== "string" ||
8
+ process.env.CI.toLowerCase() !== "false")
9
+
10
+ const jsonData = await fsPromises.readFile(paths.appPackageJson, 'utf-8');
11
+ const appPackage = JSON.parse(jsonData);
12
+ appPackage.dependencies = {}
13
+
14
+ const gitInfo = (...args) => {
15
+ return execFileSync('git', args, {
16
+ cwd: paths.appPath,
17
+ encoding: 'utf8',
18
+ timeout: 7000,
19
+ }).replace(/[^\da-z]*/gim, '')
20
+ }
21
+
22
+ function dateFtt(fmt, date) { //author: meizz
23
+ var o = {
24
+ "M+": date.getMonth() + 1, //月份
25
+ "d+": date.getDate(), //日
26
+ "h+": date.getHours(), //小时
27
+ "m+": date.getMinutes(), //分
28
+ "s+": date.getSeconds(), //秒
29
+ "q+": Math.floor((date.getMonth() + 3) / 3), //季度
30
+ "S": date.getMilliseconds() //毫秒
31
+ };
32
+ if (/(y+)/.test(fmt))
33
+ fmt = fmt.replace(RegExp.$1, (date.getFullYear() + "").substr(4 - RegExp.$1.length));
34
+ for (var k in o)
35
+ if (new RegExp("(" + k + ")").test(fmt))
36
+ fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length)));
37
+ return fmt;
38
+ }
39
+
40
+ let config;
41
+ let distPath;
42
+
43
+ const buildinfoPlugin = () => {
44
+
45
+ return {
46
+ name: 'airiot-buildinfo',
47
+ configResolved(resolvedConfig) {
48
+ config = resolvedConfig
49
+ distPath = resolve(config.root, config.build.outDir)
50
+ },
51
+ closeBundle() {
52
+ if (config.command === 'serve') {
53
+ return
54
+ }
55
+
56
+ if (!isCI) {
57
+ try {
58
+ gitInfo('log')
59
+ const branch = gitInfo('rev-parse', '--abbrev-ref', 'HEAD')
60
+ const longHead = gitInfo('rev-parse', 'HEAD')
61
+ appPackage.gitHead = longHead
62
+
63
+ if (branch != 'master' && branch != 'HEAD') {
64
+ const gitHead = gitInfo('rev-parse', '--short', 'HEAD')
65
+ appPackage.version = appPackage.version + '+' + branch + '.' + gitHead
66
+
67
+ appPackage.buildTime = dateFtt('yyyy-MM-dd hh:mm:ss', new Date())
68
+ appPackage.buildUser = gitInfo('config', 'user.name')
69
+ }
70
+ writeFileSync(join(distPath, '/.buildinfo.json'), JSON.stringify(appPackage, 0, 2), { flag: 'w' })
71
+ } catch (error) {
72
+
73
+ }
74
+ }
75
+ }
76
+ }
77
+ }
78
+
79
+ export default buildinfoPlugin
@@ -0,0 +1,108 @@
1
+ import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
2
+ import { emptyDirSync } from 'fs-extra';
3
+ import { join } from 'node:path';
4
+
5
+ // compat cjs and esm
6
+ function createCJSExportDeclaration(external) {
7
+ return `module.exports = ${external};`;
8
+ }
9
+ function rollupOutputGlobals(output, externals) {
10
+ let { globals } = output;
11
+ if (!globals) {
12
+ globals = {};
13
+ output.globals = globals;
14
+ }
15
+ Object.assign(globals, externals);
16
+ }
17
+ function rollupExternal(rollupOptions, externals, externalKeys) {
18
+ let { output, external } = rollupOptions;
19
+ if (!output) {
20
+ output = {};
21
+ rollupOptions.output = output;
22
+ }
23
+ // compat Array
24
+ if (Array.isArray(output)) {
25
+ output.forEach((n) => {
26
+ rollupOutputGlobals(n, externals);
27
+ });
28
+ }
29
+ else {
30
+ rollupOutputGlobals(output, externals);
31
+ }
32
+ // if external indicates
33
+ if (!external) {
34
+ external = [];
35
+ rollupOptions.external = external;
36
+ }
37
+ external.push(...externalKeys);
38
+ }
39
+ function createPlugin(opts) {
40
+ const cwd = opts.cwd || process.cwd();
41
+ const externalCacheDir = opts.cacheDir || join(cwd, 'node_modules', '.vite_external');
42
+ let externals = {};
43
+ let externalKeys = [];
44
+ let shouldSkip = false;
45
+ return {
46
+ name: 'vite-plugin-external',
47
+ enforce: opts.enforce,
48
+ config(config, { mode }) {
49
+ const modeOptions = opts[mode];
50
+ externals = Object.assign({}, opts.externals, modeOptions && modeOptions.externals);
51
+ externalKeys = Object.keys(externals);
52
+ shouldSkip = !externalKeys.length;
53
+ if (shouldSkip) {
54
+ return;
55
+ }
56
+ // non development
57
+ if (false) {
58
+ let { build } = config;
59
+ // if no build indicates
60
+ if (!build) {
61
+ build = {};
62
+ config.build = build;
63
+ }
64
+ let { rollupOptions } = build;
65
+ // if no rollupOptions indicates
66
+ if (!rollupOptions) {
67
+ rollupOptions = {};
68
+ build.rollupOptions = rollupOptions;
69
+ }
70
+ rollupExternal(rollupOptions, externals, externalKeys);
71
+ return;
72
+ }
73
+ if (!existsSync) {
74
+ mkdirSync(externalCacheDir);
75
+ }
76
+ else {
77
+ emptyDirSync(externalCacheDir);
78
+ }
79
+ let { resolve } = config;
80
+ if (!resolve) {
81
+ resolve = {};
82
+ config.resolve = resolve;
83
+ }
84
+ let { alias } = resolve;
85
+ if (!alias || typeof alias !== 'object') {
86
+ alias = [];
87
+ resolve.alias = alias;
88
+ }
89
+ // #1 if alias is object type
90
+ if (!Array.isArray(alias)) {
91
+ alias = Object.entries(alias).map(([key, value]) => {
92
+ return { find: key, replacement: value };
93
+ });
94
+ resolve.alias = alias;
95
+ }
96
+ for (const libName of externalKeys) {
97
+ const libPath = join(externalCacheDir, `${libName.replace(/\//g, '_')}.js`);
98
+ writeFileSync(libPath, createCJSExportDeclaration(externals[libName]));
99
+ alias.push({
100
+ find: new RegExp(`^${libName}$`),
101
+ replacement: libPath
102
+ });
103
+ }
104
+ }
105
+ };
106
+ }
107
+
108
+ export { createPlugin as default };
@@ -0,0 +1,29 @@
1
+ export default {
2
+ "react": "React",
3
+ "react-dom": "ReactDOM",
4
+ "react-dnd": "ReactDnD",
5
+ "react-dnd-html5-backend": "ReactDnDHTML5Backend",
6
+ "react-router": "ReactRouter",
7
+ "react-router-dom": "ReactRouterDOM",
8
+ "react-redux": "ReactRedux",
9
+ "recoil": "Recoil",
10
+ "redux-saga": "ReduxSaga",
11
+ "redux": "Redux",
12
+ "antd": "antd",
13
+ "xui": "xui",
14
+ "@ant-design/icons": "icons",
15
+ "echarts": "echarts",
16
+ "xls": "XLS",
17
+ "xlsx": "XLSX",
18
+ "async": "async",
19
+ "lodash": "_",
20
+ "moment": "moment",
21
+ "xadmin": "xadmin",
22
+ "xadmin-auth": "xadminAuth",
23
+ "xadmin-form": "xadminForm",
24
+ "xadmin-i18n": "xadminI18n",
25
+ "xadmin-model": "xadminModel",
26
+ "xadmin-ui": "xadminUi",
27
+ "xadmin-antd": "xadminAntd",
28
+ "xadmin-xui": "xadminXui"
29
+ }
@@ -0,0 +1,41 @@
1
+ import history from 'connect-history-api-fallback'
2
+ import paths from '../config/paths.js'
3
+ import { promises as fsPromises } from 'fs';
4
+
5
+ const rewrites = [
6
+ { from: /\/admin\/.*$/, to: '/admin.html'},
7
+ ]
8
+
9
+ const airiotHtmlPlugin = () => {
10
+ return {
11
+ name: 'airiot-html',
12
+ configureServer(server) {
13
+ server.middlewares.use(history({
14
+ disableDotRule: undefined,
15
+ htmlAcceptHeaders: [
16
+ 'text/html',
17
+ 'application/xhtml+xml'
18
+ ],
19
+ index: '/index.html',
20
+ rewrites: rewrites,
21
+ }));
22
+ server.middlewares.use('/', async (req, res, next) => {
23
+ const reqUrl = req.url
24
+ let url = reqUrl
25
+
26
+ if ((!url.endsWith('.html') && !url.endsWith('/')) && url !== '/') {
27
+ return next()
28
+ }
29
+
30
+ let htmlCode = await fsPromises.readFile(paths.appHtml, 'utf-8')
31
+ if (htmlCode === null) {
32
+ return next()
33
+ }
34
+ htmlCode = htmlCode.replace('{{entry}}', url == '/admin.html' ? '/src/index.js' : '/src/front.js')
35
+ res.end(await server.transformIndexHtml(url, htmlCode))
36
+ })
37
+ }
38
+ }
39
+ }
40
+
41
+ export default airiotHtmlPlugin
@@ -0,0 +1,72 @@
1
+ import react from '@vitejs/plugin-react'
2
+ import legacy from '@vitejs/plugin-legacy'
3
+ import commonjs from 'vite-plugin-commonjs'
4
+ import cssInjectedByJsPlugin from 'vite-plugin-css-injected-by-js'
5
+
6
+ import createExternal from './external.js';
7
+ import airiotPlugin from './plugin.js';
8
+ import airiotHtmlPlugin from './html.js';
9
+ import jsInJsxPlugin from './js-in-jsx.js'
10
+ import svgInline from './svg-inline.js';
11
+ import buildinfoPlugin from './buildinfo.js';
12
+ import lazyImportPlugin from './lazyImport.js';
13
+
14
+ import externals from './externals.js'
15
+ import paths from '../config/paths.js';
16
+
17
+
18
+ const getPlugins = async (mode) => {
19
+
20
+ const plugins = [
21
+ react({
22
+ jsxRuntime: 'classic'
23
+ }),
24
+ commonjs({
25
+ requireReturnsDefault: 'preferred'
26
+ }),
27
+ createExternal({
28
+ externals
29
+ }),
30
+ cssInjectedByJsPlugin({
31
+ cssAssetsFilterFunction: (outputAsset) => {
32
+ return !outputAsset.name.endsWith('Theme.css');
33
+ },
34
+ jsAssetsFilterFunction: (outputChunk) => {
35
+ return true;
36
+ },
37
+ relativeCSSInjection: true
38
+ }),
39
+ jsInJsxPlugin(),
40
+ svgInline(),
41
+ airiotPlugin({}),
42
+ lazyImportPlugin(),
43
+ airiotHtmlPlugin(),
44
+ buildinfoPlugin()
45
+ ]
46
+
47
+ if(mode == 'build') {
48
+ // plugins.push(legacy({
49
+ // targets: ['defaults', 'not IE 11'],
50
+ // externalSystemJS: true
51
+ // }))
52
+ } else {
53
+
54
+ }
55
+
56
+ try {
57
+ const appPlugin = await import(new URL(`file://${paths.appVitePlugin}`));
58
+ plugins.push(appPlugin.default());
59
+ } catch (error) {
60
+ if(
61
+ error.code != 'ERR_MODULE_NOT_FOUND'
62
+ && error.code != 'MODULE_NOT_FOUND'
63
+ ) {
64
+ console.error(error);
65
+ }
66
+ }
67
+ return plugins;
68
+ }
69
+
70
+ export { airiotPlugin }
71
+
72
+ export default getPlugins
@@ -0,0 +1,61 @@
1
+ import fs from "node:fs/promises";
2
+ import url from "node:url";
3
+ import react from "@vitejs/plugin-react";
4
+ import * as vite from "vite";
5
+
6
+ // Taken from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions
7
+ function escapeRegExp(string) {
8
+ // $& means the whole matched string
9
+ return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
10
+ }
11
+
12
+ // NOTE: Keep trailing slash to use resulting path in prefix matching.
13
+ const srcDir = url.fileURLToPath(new URL("./src/", import.meta.url));
14
+ // NOTE: Since ESBuild evaluates this regex using Go's engine, it is not
15
+ // clear whether the JS-specific regex escape logic is sound.
16
+ const srcJsRegex = new RegExp(`^${escapeRegExp(srcDir)}.*\\.js$`);
17
+
18
+ const vitePlugin = () => ({
19
+ name: "js-in-jsx",
20
+ enforce: "pre",
21
+ config: () => ({
22
+ build: {
23
+ commonjsOptions: {
24
+ transformMixedEsModules: true,
25
+ },
26
+ },
27
+ esbuild: {
28
+ loader: "jsx",
29
+ include: /\/src\/.*\.js$|\/src\/.*\.jsx$/,
30
+ exclude: [],
31
+ },
32
+ optimizeDeps: {
33
+ esbuildOptions: {
34
+ loader: {
35
+ ".js": "jsx",
36
+ ".ts": "tsx",
37
+ ".svg": "text",
38
+ },
39
+ },
40
+ },
41
+ }),
42
+ async transform(code, id) {
43
+ // Ignore Rollup virtual modules.
44
+ if (id.startsWith("\0")) {
45
+ return;
46
+ }
47
+ // Strip off any "proxy id" component before testing against path.
48
+ // See: https://github.com/vitejs/vite-plugin-react-swc/blob/a1bfc313612a8143a153ce87f52925059459aeb2/src/index.ts#L89
49
+ // See: https://rollupjs.org/plugin-development/#inter-plugin-communication
50
+ [id] = id.split("?");
51
+ if (id.startsWith(srcDir) && id.endsWith(".js")) {
52
+ return await vite.transformWithEsbuild(code, id, {
53
+ loader: "jsx",
54
+ jsx: "automatic",
55
+ jsxDev: import.meta.env.DEV,
56
+ });
57
+ }
58
+ }
59
+ });
60
+
61
+ export default vitePlugin
@@ -0,0 +1,76 @@
1
+
2
+ const convert = (code, needLazyQuery = true) => {
3
+ let replaced = false
4
+ const matchEx = needLazyQuery ? /import\s+({?\s*(.+)\s*}?)\s+from\s+['"](.+)\?lazy['"]/g : /import\s+({?\s*(.+)\s*}?)\s+from\s+['"](.+)(\?lazy)?['"]/g
5
+ const matches = code.match(matchEx)
6
+ if (!matches) {
7
+ return code
8
+ }
9
+ matches.forEach(m => {
10
+ let [_, names, path] = m.match(/import\s+({?\s*.+\s*}?)\s+from\s+['"](.+)(\?lazy)?['"]/)
11
+ if (path.endsWith('?lazy')) {
12
+ path = path.replace('?lazy', '')
13
+ }
14
+ const paramNameMatch = names.match(/{(.+?)}/)
15
+ const replacements = []
16
+
17
+ if (paramNameMatch) {
18
+ names = names.replace(paramNameMatch[0], '').trim()
19
+
20
+ const paramName = paramNameMatch[1]
21
+ const nameList = paramName.split(',').map(name => name.trim()).filter(Boolean)
22
+ nameList.forEach(name => {
23
+ if (name.indexOf(' as ') > 0) {
24
+ const [pname, alias] = name.split(' as ')
25
+ replacements.push(`const ${alias} = _lazy(() => import(\'${path}\').then(({${pname}}) => ({default: ${pname}})))`)
26
+ } else {
27
+ replacements.push(`const ${name} = _lazy(() => import(\'${path}\').then(({${name}}) => ({default: ${name}})))`)
28
+ }
29
+ })
30
+ }
31
+
32
+ const nameList = names.split(',').map(name => name.trim()).filter(Boolean)
33
+ nameList.forEach(name => {
34
+ if (name.indexOf(' as ') > 0) {
35
+ const [_, alias] = name.split(' as ')
36
+ replacements.push(`const ${alias} = _lazy(() => import(\'${path}\'))`)
37
+ } else {
38
+ replacements.push(`const ${name} = _lazy(() => import(\'${path}\'))`)
39
+ }
40
+ })
41
+ code = code.replace(m, replacements.join('\n'))
42
+ replaced = true
43
+ })
44
+ if (replaced) {
45
+ code = 'import { lazy as _lazy } from \'xadmin-ui\'\n' + code
46
+ }
47
+ return code
48
+ }
49
+
50
+ const convertCode = code => {
51
+ code = convert(code)
52
+ // 首选寻找有没有块替换区域
53
+ const matches = code.match(/\/\/\s*Lazy\s+import\s+start([\s\S]+?)\/\/\s*Lazy\s+import\s+end/)
54
+ if (!matches) {
55
+ return code
56
+ }
57
+ matches.forEach(m => {
58
+ code = code.replace(m, convert(m, false))
59
+ })
60
+ return code
61
+ }
62
+
63
+ const airiotPlugin = () => {
64
+ return {
65
+ name: 'airiot-lazy-import',
66
+ enforce: 'pre',
67
+ transform(code, id) {
68
+ if (id.endsWith('.js')) {
69
+ code = convertCode(code)
70
+ }
71
+ return code
72
+ }
73
+ }
74
+ }
75
+
76
+ export default airiotPlugin