@seeed-studio/sensecraft-cli 0.1.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,56 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const fs = require('fs');
5
+ const { spawnSync } = require('child_process');
6
+
7
+ const { installBinary } = require('../scripts/download');
8
+ const { buildEnv } = require('../scripts/env');
9
+ const { runInstall } = require('../scripts/install');
10
+ const { getBinaryPath } = require('../scripts/paths');
11
+ const { resolvePlatformTarget } = require('../scripts/platform');
12
+
13
+ async function ensureBinary() {
14
+ const { binary } = resolvePlatformTarget();
15
+ const binaryPath = getBinaryPath(binary);
16
+ if (fs.existsSync(binaryPath)) {
17
+ return binaryPath;
18
+ }
19
+ const result = await installBinary();
20
+ return result.targetPath;
21
+ }
22
+
23
+ async function runCli(args) {
24
+ const binaryPath = await ensureBinary();
25
+ const result = spawnSync(binaryPath, args, {
26
+ stdio: 'inherit',
27
+ env: buildEnv(),
28
+ });
29
+
30
+ if (result.error) {
31
+ console.error(result.error.message);
32
+ process.exit(1);
33
+ }
34
+
35
+ process.exit(result.status == null ? 1 : result.status);
36
+ }
37
+
38
+ async function main() {
39
+ const args = process.argv.slice(2);
40
+
41
+ if (args[0] === 'install') {
42
+ try {
43
+ await runInstall();
44
+ } catch (err) {
45
+ console.error('');
46
+ console.error('安装失败:', err.message);
47
+ console.error('');
48
+ process.exit(1);
49
+ }
50
+ return;
51
+ }
52
+
53
+ await runCli(args);
54
+ }
55
+
56
+ main();
Binary file
Binary file
Binary file
Binary file
package/package.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "@seeed-studio/sensecraft-cli",
3
+ "version": "0.1.0",
4
+ "description": "SenseCraft Cli",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://iteam-gitlab.seeed.cn/sensecap/sensecraft-data-terminal.git",
9
+ "directory": "packages/cli"
10
+ },
11
+ "publishConfig": {
12
+ "access": "public"
13
+ },
14
+ "bin": {
15
+ "sensecraft-cli": "bin/sensecraft-cli.js"
16
+ },
17
+ "scripts": {
18
+ "postinstall": "node scripts/postinstall.js",
19
+ "test:install": "node scripts/postinstall.js && node bin/sensecraft-cli.js install"
20
+ },
21
+ "files": [
22
+ "bin",
23
+ "scripts",
24
+ "dist"
25
+ ],
26
+ "engines": {
27
+ "node": ">=16"
28
+ }
29
+ }
@@ -0,0 +1,15 @@
1
+ 'use strict';
2
+
3
+ const VERSION = '1.0.0';
4
+
5
+ const RELEASE_BASE =
6
+ process.env.SENSECRAFT_CLI_RELEASE_BASE ||
7
+ 'https://iteam-gitlab.seeed.cn/sensecap/sensecraft-data-terminal/-/releases';
8
+
9
+ const BINARY_NAME = 'sensecraft-cli';
10
+
11
+ module.exports = {
12
+ VERSION,
13
+ RELEASE_BASE,
14
+ BINARY_NAME,
15
+ };
@@ -0,0 +1,98 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const https = require('https');
5
+ const http = require('http');
6
+ const path = require('path');
7
+
8
+ const { VERSION, RELEASE_BASE } = require('./constants');
9
+ const { getVendorDir, getBinaryPath } = require('./paths');
10
+ const { resolvePlatformTarget } = require('./platform');
11
+
12
+ function request(url, redirects = 0) {
13
+ return new Promise((resolve, reject) => {
14
+ const client = url.startsWith('https:') ? https : http;
15
+ client
16
+ .get(url, { headers: { 'User-Agent': 'sensecraft-cli-npm-installer' } }, (res) => {
17
+ if (
18
+ res.statusCode &&
19
+ [301, 302, 303, 307, 308].includes(res.statusCode) &&
20
+ res.headers.location
21
+ ) {
22
+ if (redirects >= 5) {
23
+ reject(new Error(`下载重定向次数过多: ${url}`));
24
+ return;
25
+ }
26
+ const next = new URL(res.headers.location, url).toString();
27
+ res.resume();
28
+ request(next, redirects + 1).then(resolve, reject);
29
+ return;
30
+ }
31
+
32
+ if (res.statusCode !== 200) {
33
+ res.resume();
34
+ reject(new Error(`下载失败 (${res.statusCode}): ${url}`));
35
+ return;
36
+ }
37
+
38
+ const chunks = [];
39
+ res.on('data', (chunk) => chunks.push(chunk));
40
+ res.on('end', () => resolve(Buffer.concat(chunks)));
41
+ })
42
+ .on('error', reject);
43
+ });
44
+ }
45
+
46
+ function ensureExecutable(filePath) {
47
+ if (process.platform === 'win32') {
48
+ return;
49
+ }
50
+ const mode = fs.statSync(filePath).mode;
51
+ fs.chmodSync(filePath, mode | 0o111);
52
+ }
53
+
54
+ function copyLocalBinary(sourcePath, targetPath) {
55
+ fs.mkdirSync(path.dirname(targetPath), { recursive: true });
56
+ fs.copyFileSync(sourcePath, targetPath);
57
+ ensureExecutable(targetPath);
58
+ }
59
+
60
+ async function installBinary() {
61
+ const { artifact, binary } = resolvePlatformTarget();
62
+ const targetPath = getBinaryPath(binary);
63
+
64
+ if (fs.existsSync(targetPath)) {
65
+ return { binary, targetPath, reused: true };
66
+ }
67
+
68
+ const localDist = process.env.SENSECRAFT_CLI_LOCAL_DIST;
69
+ if (localDist) {
70
+ const sourcePath = path.resolve(localDist, artifact);
71
+ if (!fs.existsSync(sourcePath)) {
72
+ throw new Error(`本地二进制不存在: ${sourcePath}`);
73
+ }
74
+ copyLocalBinary(sourcePath, targetPath);
75
+ return { binary, targetPath, reused: false, source: 'local' };
76
+ }
77
+
78
+ if (process.env.SENSECRAFT_CLI_SKIP_DOWNLOAD === '1') {
79
+ throw new Error(
80
+ '已设置 SENSECRAFT_CLI_SKIP_DOWNLOAD=1,但未找到已缓存的二进制。' +
81
+ '可设置 SENSECRAFT_CLI_LOCAL_DIST 指向 dist 目录。'
82
+ );
83
+ }
84
+
85
+ // const url = `${RELEASE_BASE}/v${VERSION}/downloads/${artifact}`;
86
+ const url = `https://sensecraft-statics.oss-us-east-1.aliyuncs.com/static/bin/sensecraft-cli/${artifact}`;
87
+ const data = await request(url);
88
+
89
+ fs.mkdirSync(getVendorDir(), { recursive: true });
90
+ fs.writeFileSync(targetPath, data);
91
+ ensureExecutable(targetPath);
92
+
93
+ return { binary, targetPath, reused: false, source: 'release', url };
94
+ }
95
+
96
+ module.exports = {
97
+ installBinary,
98
+ };
package/scripts/env.js ADDED
@@ -0,0 +1,15 @@
1
+ 'use strict';
2
+
3
+ const { getDefaultConfigDir } = require('./paths');
4
+
5
+ function buildEnv(baseEnv = process.env) {
6
+ const env = { ...baseEnv };
7
+ if (!env.SENSECRAFT_CLI_PATH) {
8
+ env.SENSECRAFT_CLI_PATH = getDefaultConfigDir();
9
+ }
10
+ return env;
11
+ }
12
+
13
+ module.exports = {
14
+ buildEnv,
15
+ };
@@ -0,0 +1,102 @@
1
+ 'use strict';
2
+
3
+ const { execSync, spawnSync } = require('child_process');
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+
7
+ const { installBinary } = require('./download');
8
+ const { buildEnv } = require('./env');
9
+ const { getPackageRoot, isInitialized } = require('./paths');
10
+
11
+ function getGlobalBinDir() {
12
+ const prefix = execSync('npm prefix -g', {
13
+ encoding: 'utf8',
14
+ env: process.env,
15
+ }).trim();
16
+ if (process.platform === 'win32') {
17
+ return prefix;
18
+ }
19
+ return path.join(prefix, 'bin');
20
+ }
21
+
22
+ function getGlobalCliPath() {
23
+ const binDir = getGlobalBinDir();
24
+ const name = process.platform === 'win32' ? 'sensecraft-cli.cmd' : 'sensecraft-cli';
25
+ return path.join(binDir, name);
26
+ }
27
+
28
+ function installGlobally() {
29
+ const packageRoot = getPackageRoot();
30
+ console.log('正在全局安装 sensecraft-cli...');
31
+ execSync(`npm install -g ${JSON.stringify(packageRoot)}`, {
32
+ stdio: 'inherit',
33
+ env: process.env,
34
+ });
35
+
36
+ const cliPath = getGlobalCliPath();
37
+ if (!fs.existsSync(cliPath)) {
38
+ throw new Error(`全局命令未找到: ${cliPath},请确认 npm 全局 bin 目录在 PATH 中`);
39
+ }
40
+ return cliPath;
41
+ }
42
+
43
+ function runInitIfNeeded(cliPath) {
44
+ if (isInitialized()) {
45
+ console.log('');
46
+ console.log('✓ 配置文件已存在,跳过初始化');
47
+ console.log(' sensecraft-cli auth info');
48
+ return;
49
+ }
50
+
51
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
52
+ console.log('');
53
+ console.log('未检测到交互式终端,已跳过自动初始化。');
54
+ console.log('请稍后运行: sensecraft-cli auth init');
55
+ return;
56
+ }
57
+
58
+ console.log('');
59
+ console.log('未检测到配置文件,开始自动初始化...');
60
+ console.log('');
61
+
62
+ const result = spawnSync(cliPath, ['auth', 'init'], {
63
+ stdio: 'inherit',
64
+ env: buildEnv(),
65
+ shell: process.platform === 'win32',
66
+ });
67
+
68
+ if (result.error) {
69
+ throw result.error;
70
+ }
71
+ if (result.status !== 0) {
72
+ process.exit(result.status == null ? 1 : result.status);
73
+ }
74
+ }
75
+
76
+ function printDone(globalCliPath) {
77
+ const globalBin = getGlobalBinDir();
78
+ console.log('');
79
+ console.log('✓ 安装完成');
80
+ console.log(` 命令: sensecraft-cli`);
81
+ console.log(` 路径: ${globalCliPath}`);
82
+ if (!process.env.PATH || !process.env.PATH.split(path.delimiter).includes(globalBin)) {
83
+ console.log('');
84
+ console.log('提示: 若无法直接运行 sensecraft-cli,请将以下目录加入 PATH:');
85
+ console.log(` ${globalBin}`);
86
+ }
87
+ console.log('');
88
+ }
89
+
90
+ async function runInstall() {
91
+ await installBinary();
92
+ const globalCliPath = installGlobally();
93
+ runInitIfNeeded(globalCliPath);
94
+ printDone(globalCliPath);
95
+ }
96
+
97
+ module.exports = {
98
+ runInstall,
99
+ installGlobally,
100
+ runInitIfNeeded,
101
+ getGlobalCliPath,
102
+ };
@@ -0,0 +1,46 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const os = require('os');
5
+ const path = require('path');
6
+
7
+ const { BINARY_NAME } = require('./constants');
8
+
9
+ function getPackageRoot() {
10
+ return path.resolve(__dirname, '..');
11
+ }
12
+
13
+ function getVendorDir() {
14
+ return path.join(getPackageRoot(), 'vendor');
15
+ }
16
+
17
+ function getBinaryPath(binaryFileName) {
18
+ return path.join(getVendorDir(), binaryFileName);
19
+ }
20
+
21
+ function getDefaultConfigDir() {
22
+ if (process.platform === 'win32') {
23
+ const appData = process.env.APPDATA || path.join(os.homedir(), 'AppData', 'Roaming');
24
+ return path.join(appData, BINARY_NAME);
25
+ }
26
+ return path.join(os.homedir(), '.config', BINARY_NAME);
27
+ }
28
+
29
+ function isInitialized() {
30
+ const configPath = path.join(getDefaultConfigDir(), 'sensecraft.json');
31
+ try {
32
+ const raw = fs.readFileSync(configPath, 'utf8');
33
+ const cfg = JSON.parse(raw);
34
+ return Array.isArray(cfg.app_config_list) && cfg.app_config_list.length > 0;
35
+ } catch {
36
+ return false;
37
+ }
38
+ }
39
+
40
+ module.exports = {
41
+ getPackageRoot,
42
+ getVendorDir,
43
+ getBinaryPath,
44
+ getDefaultConfigDir,
45
+ isInitialized,
46
+ };
@@ -0,0 +1,34 @@
1
+ 'use strict';
2
+
3
+ function resolvePlatformTarget() {
4
+ const platform = process.platform;
5
+ const arch = process.arch;
6
+
7
+ if (platform === 'darwin' && arch === 'x64') {
8
+ return { artifact: 'sensecraft-cli-darwin-amd64', binary: 'sensecraft-cli' };
9
+ }
10
+ if (platform === 'darwin' && arch === 'arm64') {
11
+ return { artifact: 'sensecraft-cli-darwin-arm64', binary: 'sensecraft-cli' };
12
+ }
13
+ if (platform === 'linux' && arch === 'x64') {
14
+ return { artifact: 'sensecraft-cli-linux-amd64', binary: 'sensecraft-cli' };
15
+ }
16
+ if (platform === 'linux' && arch === 'arm64') {
17
+ return { artifact: 'sensecraft-cli-linux-arm64', binary: 'sensecraft-cli' };
18
+ }
19
+ if (platform === 'win32' && arch === 'x64') {
20
+ return {
21
+ artifact: 'sensecraft-cli-windows-amd64.exe',
22
+ binary: 'sensecraft-cli.exe',
23
+ };
24
+ }
25
+
26
+ throw new Error(
27
+ `不支持的平台: ${platform}/${arch}。` +
28
+ '当前支持 darwin/linux x64/arm64 与 windows x64。'
29
+ );
30
+ }
31
+
32
+ module.exports = {
33
+ resolvePlatformTarget,
34
+ };
@@ -0,0 +1,21 @@
1
+ 'use strict';
2
+
3
+ const { installBinary } = require('./download');
4
+
5
+ async function main() {
6
+ try {
7
+ await installBinary();
8
+ } catch (err) {
9
+ console.error('');
10
+ console.error('sensecraft-cli 二进制下载失败:', err.message);
11
+ console.error('');
12
+ console.error('可尝试:');
13
+ console.error(' 1. 确认 Release / OSS 上已有对应平台二进制');
14
+ console.error(' 2. 设置 SENSECRAFT_CLI_LOCAL_DIST 指向本地 dist 目录后重装');
15
+ console.error(' 3. 设置 SENSECRAFT_CLI_RELEASE_BASE 覆盖下载地址');
16
+ console.error('');
17
+ process.exit(1);
18
+ }
19
+ }
20
+
21
+ main();