refactor: move source files into workspace crates
- Move src/* into crates/tui/src/ to create a proper workspace structure - Add .claude/ and .trimtab/ directories for Trimtab closed-loop workflow - Add DEPENDENCY_GRAPH.md and update documentation - Update Cargo.toml files to reflect new crate dependencies - Update CI workflows and npm package scripts - All tests pass, release build works
This commit is contained in:
+76
@@ -0,0 +1,76 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const crypto = require("crypto");
|
||||
const fs = require("fs/promises");
|
||||
const path = require("path");
|
||||
|
||||
const {
|
||||
allAssetNames,
|
||||
CHECKSUM_MANIFEST,
|
||||
detectBinaryNames,
|
||||
} = require("../../npm/deepseek-tui/scripts/artifacts");
|
||||
|
||||
async function sha256(filePath) {
|
||||
const content = await fs.readFile(filePath);
|
||||
return crypto.createHash("sha256").update(content).digest("hex");
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const prepareAllAssets =
|
||||
process.env.DEEPSEEK_TUI_PREPARE_ALL_ASSETS === "1" ||
|
||||
process.env.DEEPSEEK_PREPARE_ALL_ASSETS === "1";
|
||||
const outputDir = path.resolve(
|
||||
process.argv[2] || path.join("target", "npm-release-assets"),
|
||||
);
|
||||
const buildDir = path.resolve(
|
||||
process.argv[3] || path.join("target", "release"),
|
||||
);
|
||||
const { deepseek, tui } = detectBinaryNames();
|
||||
const isWindows = process.platform === "win32";
|
||||
|
||||
const assets = [
|
||||
{
|
||||
source: path.join(buildDir, isWindows ? "deepseek.exe" : "deepseek"),
|
||||
target: deepseek,
|
||||
},
|
||||
{
|
||||
source: path.join(buildDir, isWindows ? "deepseek-tui.exe" : "deepseek-tui"),
|
||||
target: tui,
|
||||
},
|
||||
];
|
||||
|
||||
if (prepareAllAssets) {
|
||||
for (const assetName of allAssetNames()) {
|
||||
if (assets.some((asset) => asset.target === assetName)) {
|
||||
continue;
|
||||
}
|
||||
assets.push({
|
||||
source: assetName.startsWith("deepseek-tui")
|
||||
? path.join(buildDir, isWindows ? "deepseek-tui.exe" : "deepseek-tui")
|
||||
: path.join(buildDir, isWindows ? "deepseek.exe" : "deepseek"),
|
||||
target: assetName,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
await fs.mkdir(outputDir, { recursive: true });
|
||||
|
||||
const manifestLines = [];
|
||||
for (const asset of assets) {
|
||||
const outputPath = path.join(outputDir, asset.target);
|
||||
await fs.copyFile(asset.source, outputPath);
|
||||
manifestLines.push(`${await sha256(outputPath)} ${asset.target}`);
|
||||
}
|
||||
|
||||
manifestLines.sort();
|
||||
const manifestPath = path.join(outputDir, CHECKSUM_MANIFEST);
|
||||
await fs.writeFile(manifestPath, `${manifestLines.join("\n")}\n`, "utf8");
|
||||
|
||||
console.log(`Prepared ${assets.length} assets in ${outputDir}`);
|
||||
console.log(`Wrote checksum manifest ${manifestPath}`);
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
console.error("Failed to prepare local release assets:", error.message);
|
||||
process.exit(1);
|
||||
});
|
||||
Executable
+110
@@ -0,0 +1,110 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
mode="${1:-dry-run}"
|
||||
case "${mode}" in
|
||||
dry-run|publish) ;;
|
||||
*)
|
||||
echo "usage: $0 [dry-run|publish]" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
packages=(
|
||||
deepseek-config
|
||||
deepseek-protocol
|
||||
deepseek-state
|
||||
deepseek-agent
|
||||
deepseek-execpolicy
|
||||
deepseek-hooks
|
||||
deepseek-mcp
|
||||
deepseek-tools
|
||||
deepseek-core
|
||||
deepseek-app-server
|
||||
deepseek-tui-core
|
||||
deepseek-tui-cli
|
||||
deepseek-tui
|
||||
)
|
||||
|
||||
workspace_version="$(
|
||||
python3 - <<'PY'
|
||||
import json
|
||||
import subprocess
|
||||
|
||||
metadata = json.loads(
|
||||
subprocess.check_output(["cargo", "metadata", "--format-version", "1", "--no-deps"])
|
||||
)
|
||||
workspace_members = set(metadata["workspace_members"])
|
||||
for pkg in metadata["packages"]:
|
||||
if pkg["id"] in workspace_members:
|
||||
print(pkg["version"])
|
||||
break
|
||||
PY
|
||||
)"
|
||||
|
||||
package_has_workspace_deps() {
|
||||
local package_name="$1"
|
||||
python3 - "${package_name}" <<'PY'
|
||||
import json
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
package_name = sys.argv[1]
|
||||
metadata = json.loads(
|
||||
subprocess.check_output(["cargo", "metadata", "--format-version", "1", "--no-deps"])
|
||||
)
|
||||
workspace_ids = set(metadata["workspace_members"])
|
||||
workspace_packages = {
|
||||
pkg["name"]: pkg for pkg in metadata["packages"] if pkg["id"] in workspace_ids
|
||||
}
|
||||
package = workspace_packages[package_name]
|
||||
has_workspace_dep = any(
|
||||
dep.get("path") and dep["name"] in workspace_packages
|
||||
for dep in package["dependencies"]
|
||||
)
|
||||
print("1" if has_workspace_dep else "0")
|
||||
PY
|
||||
}
|
||||
|
||||
crate_version_exists() {
|
||||
local crate_name="$1"
|
||||
local crate_version="$2"
|
||||
curl -fsSL "https://crates.io/api/v1/crates/${crate_name}/${crate_version}" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
wait_for_crate_version() {
|
||||
local crate_name="$1"
|
||||
local crate_version="$2"
|
||||
local attempts=30
|
||||
|
||||
for ((attempt = 1; attempt <= attempts; attempt += 1)); do
|
||||
if crate_version_exists "${crate_name}" "${crate_version}"; then
|
||||
return 0
|
||||
fi
|
||||
echo "Waiting for ${crate_name} ${crate_version} to appear on crates.io (${attempt}/${attempts})..."
|
||||
sleep 10
|
||||
done
|
||||
|
||||
echo "Timed out waiting for ${crate_name} ${crate_version} to appear on crates.io" >&2
|
||||
return 1
|
||||
}
|
||||
|
||||
for package in "${packages[@]}"; do
|
||||
echo "::group::${mode} ${package}"
|
||||
if [[ "${mode}" == "dry-run" ]]; then
|
||||
if [[ "$(package_has_workspace_deps "${package}")" == "1" ]]; then
|
||||
cargo package --allow-dirty --locked --list -p "${package}" >/dev/null
|
||||
echo "Verified package contents for ${package}; full crates.io dry-run requires workspace dependencies at ${workspace_version} to be published first."
|
||||
else
|
||||
cargo publish --dry-run --locked --allow-dirty -p "${package}"
|
||||
fi
|
||||
else
|
||||
if crate_version_exists "${package}" "${workspace_version}"; then
|
||||
echo "Skipping ${package} ${workspace_version}; already published."
|
||||
else
|
||||
cargo publish --locked -p "${package}"
|
||||
wait_for_crate_version "${package}" "${workspace_version}"
|
||||
fi
|
||||
fi
|
||||
echo "::endgroup::"
|
||||
done
|
||||
Executable
+36
@@ -0,0 +1,36 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
expected_version="${1:-}"
|
||||
if [[ -z "${expected_version}" && "${GITHUB_REF:-}" == refs/tags/v* ]]; then
|
||||
expected_version="${GITHUB_REF#refs/tags/v}"
|
||||
fi
|
||||
|
||||
if [[ -z "${expected_version}" ]]; then
|
||||
echo "usage: $0 <version>" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
python3 - "${expected_version}" <<'PY'
|
||||
import json
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
expected = sys.argv[1]
|
||||
metadata = json.loads(
|
||||
subprocess.check_output(["cargo", "metadata", "--format-version", "1", "--no-deps"])
|
||||
)
|
||||
workspace_members = set(metadata["workspace_members"])
|
||||
packages = [pkg for pkg in metadata["packages"] if pkg["id"] in workspace_members]
|
||||
mismatches = [
|
||||
f"{pkg['name']}={pkg['version']}" for pkg in packages if pkg["version"] != expected
|
||||
]
|
||||
|
||||
if mismatches:
|
||||
print(f"Tag version {expected} does not match all workspace crates:", file=sys.stderr)
|
||||
for item in mismatches:
|
||||
print(f" - {item}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
print(f"Verified {len(packages)} workspace packages at version {expected}")
|
||||
PY
|
||||
Reference in New Issue
Block a user