mirror of
https://github.com/larksuite/cli.git
synced 2026-07-03 14:02:43 +08:00
feat: open-source lark-cli — the official CLI for Lark/Feishu
Change-Id: I113d9cdb5403cec347efe4595415e34a18b7decf
This commit is contained in:
82
scripts/fetch_meta.py
Normal file
82
scripts/fetch_meta.py
Normal file
@@ -0,0 +1,82 @@
|
||||
#!/usr/bin/env python3
|
||||
# Copyright (c) 2026 Lark Technologies Pte. Ltd.
|
||||
# SPDX-License-Identifier: MIT
|
||||
"""Fetch meta_data.json from remote API for build-time embedding.
|
||||
|
||||
Usage:
|
||||
python3 scripts/fetch_meta.py # fetch from feishu (default)
|
||||
python3 scripts/fetch_meta.py --brand lark # fetch from larksuite
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import urllib.request
|
||||
import urllib.error
|
||||
|
||||
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
ROOT = os.path.join(SCRIPT_DIR, "..")
|
||||
OUT_PATH = os.path.join(ROOT, "internal", "registry", "meta_data.json")
|
||||
|
||||
API_HOSTS = {
|
||||
"feishu": "https://open.feishu.cn/api/tools/open/api_definition",
|
||||
"lark": "https://open.larksuite.com/api/tools/open/api_definition",
|
||||
}
|
||||
|
||||
TIMEOUT = 10 # seconds
|
||||
|
||||
|
||||
def get_version():
|
||||
"""Get version from git tags, matching Makefile logic."""
|
||||
try:
|
||||
return subprocess.check_output(
|
||||
["git", "describe", "--tags", "--always", "--dirty"],
|
||||
stderr=subprocess.DEVNULL,
|
||||
text=True,
|
||||
cwd=ROOT,
|
||||
).strip()
|
||||
except Exception:
|
||||
return "dev"
|
||||
|
||||
|
||||
def fetch_remote(brand):
|
||||
"""Fetch MergedRegistry from remote API."""
|
||||
base = API_HOSTS.get(brand, API_HOSTS["feishu"])
|
||||
version = get_version()
|
||||
url = f"{base}?protocol=meta&client_version={urllib.request.quote(version)}"
|
||||
|
||||
print(f"fetch-meta: GET {url}", file=sys.stderr)
|
||||
req = urllib.request.Request(url)
|
||||
resp = urllib.request.urlopen(req, timeout=TIMEOUT)
|
||||
body = resp.read()
|
||||
|
||||
envelope = json.loads(body)
|
||||
if envelope.get("msg") != "succeeded":
|
||||
raise RuntimeError(f"unexpected response msg: {envelope.get('msg')!r}")
|
||||
|
||||
data = envelope.get("data", {})
|
||||
if not data.get("services"):
|
||||
raise RuntimeError("remote returned empty services")
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Fetch meta_data.json for build-time embedding")
|
||||
parser.add_argument("--brand", default="feishu", choices=["feishu", "lark"],
|
||||
help="API brand (default: feishu)")
|
||||
args = parser.parse_args()
|
||||
|
||||
data = fetch_remote(args.brand)
|
||||
count = len(data.get("services", []))
|
||||
print(f"fetch-meta: OK, {count} services from remote API", file=sys.stderr)
|
||||
|
||||
with open(OUT_PATH, "w") as fp:
|
||||
json.dump(data, fp, ensure_ascii=False, indent=2)
|
||||
fp.write("\n")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
100
scripts/install.js
Normal file
100
scripts/install.js
Normal file
@@ -0,0 +1,100 @@
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const https = require("https");
|
||||
const { execSync } = require("child_process");
|
||||
const os = require("os");
|
||||
|
||||
const VERSION = require("../package.json").version;
|
||||
const REPO = "larksuite/cli";
|
||||
const NAME = "lark-cli";
|
||||
|
||||
const PLATFORM_MAP = {
|
||||
darwin: "darwin",
|
||||
linux: "linux",
|
||||
win32: "windows",
|
||||
};
|
||||
|
||||
const ARCH_MAP = {
|
||||
x64: "amd64",
|
||||
arm64: "arm64",
|
||||
};
|
||||
|
||||
const platform = PLATFORM_MAP[process.platform];
|
||||
const arch = ARCH_MAP[process.arch];
|
||||
|
||||
if (!platform || !arch) {
|
||||
console.error(
|
||||
`Unsupported platform: ${process.platform}-${process.arch}`
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const isWindows = process.platform === "win32";
|
||||
const ext = isWindows ? ".zip" : ".tar.gz";
|
||||
const archiveName = `${NAME}-${VERSION}-${platform}-${arch}${ext}`;
|
||||
const url = `https://github.com/${REPO}/releases/download/v${VERSION}/${archiveName}`;
|
||||
const binDir = path.join(__dirname, "..", "bin");
|
||||
const dest = path.join(binDir, NAME + (isWindows ? ".exe" : ""));
|
||||
|
||||
fs.mkdirSync(binDir, { recursive: true });
|
||||
|
||||
function download(url, destPath) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const client = url.startsWith("https") ? https : require("http");
|
||||
client
|
||||
.get(url, (res) => {
|
||||
if (res.statusCode === 302 || res.statusCode === 301) {
|
||||
return download(res.headers.location, destPath).then(
|
||||
resolve,
|
||||
reject
|
||||
);
|
||||
}
|
||||
if (res.statusCode !== 200) {
|
||||
return reject(
|
||||
new Error(`Download failed with status ${res.statusCode}: ${url}`)
|
||||
);
|
||||
}
|
||||
const file = fs.createWriteStream(destPath);
|
||||
res.pipe(file);
|
||||
file.on("finish", () => {
|
||||
file.close();
|
||||
resolve();
|
||||
});
|
||||
})
|
||||
.on("error", reject);
|
||||
});
|
||||
}
|
||||
|
||||
async function install() {
|
||||
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "lark-cli-"));
|
||||
const archivePath = path.join(tmpDir, archiveName);
|
||||
|
||||
try {
|
||||
await download(url, archivePath);
|
||||
|
||||
if (isWindows) {
|
||||
execSync(
|
||||
`powershell -Command "Expand-Archive -Path '${archivePath}' -DestinationPath '${tmpDir}'"`,
|
||||
{ stdio: "ignore" }
|
||||
);
|
||||
} else {
|
||||
execSync(`tar -xzf "${archivePath}" -C "${tmpDir}"`, {
|
||||
stdio: "ignore",
|
||||
});
|
||||
}
|
||||
|
||||
const binaryName = NAME + (isWindows ? ".exe" : "");
|
||||
const extractedBinary = path.join(tmpDir, binaryName);
|
||||
|
||||
fs.copyFileSync(extractedBinary, dest);
|
||||
fs.chmodSync(dest, 0o755);
|
||||
console.log(`${NAME} v${VERSION} installed successfully`);
|
||||
} finally {
|
||||
fs.rmSync(tmpDir, { recursive: true, force: true });
|
||||
}
|
||||
}
|
||||
|
||||
install().catch((err) => {
|
||||
console.error(`Failed to install ${NAME}:`, err.message);
|
||||
process.exit(1);
|
||||
});
|
||||
12
scripts/run.js
Normal file
12
scripts/run.js
Normal file
@@ -0,0 +1,12 @@
|
||||
#!/usr/bin/env node
|
||||
const { execFileSync } = require("child_process");
|
||||
const path = require("path");
|
||||
|
||||
const ext = process.platform === "win32" ? ".exe" : "";
|
||||
const bin = path.join(__dirname, "..", "bin", "lark-cli" + ext);
|
||||
|
||||
try {
|
||||
execFileSync(bin, process.argv.slice(2), { stdio: "inherit" });
|
||||
} catch (e) {
|
||||
process.exit(e.status || 1);
|
||||
}
|
||||
51
scripts/tag-release.sh
Executable file
51
scripts/tag-release.sh
Executable file
@@ -0,0 +1,51 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
||||
|
||||
# Read version from package.json
|
||||
VERSION=$(node -p "require('${REPO_ROOT}/package.json').version")
|
||||
|
||||
if [ -z "$VERSION" ]; then
|
||||
echo "Error: could not read version from package.json" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
TAG="v${VERSION}"
|
||||
|
||||
echo "Version: ${VERSION}"
|
||||
echo "Tag: ${TAG}"
|
||||
|
||||
# Check if tag already exists locally
|
||||
if git rev-parse "$TAG" >/dev/null 2>&1; then
|
||||
echo "Tag ${TAG} already exists locally, skipping."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Check if tag already exists on remote
|
||||
if git ls-remote --tags origin "$TAG" | grep -q "$TAG"; then
|
||||
echo "Tag ${TAG} already exists on remote, skipping."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Ensure package.json changes are committed before tagging
|
||||
if git diff --name-only | grep -q 'package.json' || git diff --cached --name-only | grep -q 'package.json'; then
|
||||
echo "Error: package.json has uncommitted changes. Please commit before tagging." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Ensure current branch is pushed to remote before tagging
|
||||
CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD)
|
||||
LOCAL_SHA=$(git rev-parse HEAD)
|
||||
REMOTE_SHA=$(git rev-parse "origin/${CURRENT_BRANCH}" 2>/dev/null || echo "")
|
||||
if [ "$LOCAL_SHA" != "$REMOTE_SHA" ]; then
|
||||
echo "Error: local branch '${CURRENT_BRANCH}' is not in sync with remote. Please push your commits first." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Create and push tag
|
||||
git tag "$TAG"
|
||||
git push origin "$TAG"
|
||||
|
||||
echo "Successfully created and pushed tag ${TAG}"
|
||||
Reference in New Issue
Block a user