Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
168 changes: 126 additions & 42 deletions tools/packager/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,14 @@ use std::process::Command;
use zip::write::FileOptions;
use zip::ZipWriter;

struct ReleaseDirectories {
release_dir: PathBuf,
tools_dir: PathBuf,
runtime_dir: PathBuf,
docs_dir: PathBuf,
examples_dir: PathBuf,
}

fn main() -> anyhow::Result<()> {
println!("=== Starting TechScript 2.0 Packaging Pipeline ===");

Expand All @@ -32,13 +40,52 @@ fn main() -> anyhow::Result<()> {
compile_binary(&root_dir, "techscript_lsp")?;

// 4. Create release folder structure
let dirs = setup_release_directories(&root_dir)?;

// 5. Build Tools binaries
build_and_copy_tools(&root_dir, &dirs.tools_dir)?;

// 6-9. Copy resources and generate notes
let (git_commit, build_date) = copy_resources(
&root_dir,
&dirs.runtime_dir,
&dirs.examples_dir,
&dirs.docs_dir,
&dirs.release_dir,
)?;

// 10-17. Package installers, ZIPs, and sign binaries
package_installers(
&root_dir,
&dirs.release_dir,
&version,
&dirs.runtime_dir,
&dirs.examples_dir,
&dirs.docs_dir,
&dirs.tools_dir,
)?;

// 18-20. Generate manifest, checksums, and versioned release folder
finalize_release(
&root_dir,
&dirs.release_dir,
&version,
&git_commit,
&build_date,
&dirs.examples_dir,
&dirs.docs_dir,
)?;

Ok(())
}

fn setup_release_directories(root_dir: &Path) -> anyhow::Result<ReleaseDirectories> {
let release_dir = root_dir.join("releases").join("current");
if release_dir.exists() {
fs::remove_dir_all(&release_dir).context("Failed to clean current release directory")?;
}
fs::create_dir_all(&release_dir)?;

// Target Directories
let tools_dir = release_dir.join("tools");
let runtime_dir = release_dir.join("runtime");
let docs_dir = release_dir.join("docs");
Expand All @@ -49,7 +96,16 @@ fn main() -> anyhow::Result<()> {
fs::create_dir_all(&docs_dir)?;
fs::create_dir_all(&examples_dir)?;

// 5. Build Tools binaries
Ok(ReleaseDirectories {
release_dir,
tools_dir,
runtime_dir,
docs_dir,
examples_dir,
})
}

fn build_and_copy_tools(root_dir: &Path, tools_dir: &Path) -> anyhow::Result<()> {
let target_release = root_dir.join("target").join("release");
let tsc_exe = target_release.join("tsc.exe");
let lsp_exe = target_release.join("techscript-lsp.exe");
Expand All @@ -64,7 +120,6 @@ fn main() -> anyhow::Result<()> {
));
}

// Copy compiler driver & duplicate to make compiler tool suite
let tools_list = [
"tsc.exe",
"tsvm.exe",
Expand All @@ -77,10 +132,8 @@ fn main() -> anyhow::Result<()> {
for tool_name in &tools_list {
fs::copy(&tsc_exe, tools_dir.join(tool_name))?;
}
// Copy LSP as tsls.exe
fs::copy(&lsp_exe, tools_dir.join("tsls.exe"))?;

// Create the First-Run Experience welcome batch file
let welcome_bat_content = r#"@echo off
title Welcome to TechScript!
color 0A
Expand All @@ -107,19 +160,28 @@ pause
"#;
fs::write(tools_dir.join("welcome.bat"), welcome_bat_content)?;

// 6. Copy standard library sources to runtime/stdlib
Ok(())
}

fn copy_resources(
root_dir: &Path,
runtime_dir: &Path,
examples_dir: &Path,
docs_dir: &Path,
release_dir: &Path,
) -> anyhow::Result<(String, String)> {
let stdlib_dest = runtime_dir.join("stdlib");
fs::create_dir_all(&stdlib_dest)?;
if root_dir.join("stdlib").exists() {
copy_dir_all(root_dir.join("stdlib"), &stdlib_dest)?;
} else {
fs::write(
stdlib_dest.join("README.md"),
"# Stdlib runtime source placeholder\n",
"# Stdlib runtime source placeholder
",
)?;
}

// 7. Copy flattened examples to examples/ (retaining compat)
if root_dir.join("examples").exists() {
for entry in fs::read_dir(root_dir.join("examples"))? {
let entry = entry?;
Expand All @@ -133,7 +195,6 @@ pause
}
}

// 8. Copy specific docs to docs/
let required_docs = [
"LanguageGuide.md",
"SyntaxGuide.md",
Expand All @@ -153,12 +214,12 @@ pause
fs::copy(&doc_src, docs_dir.join(doc_name))?;
}
}
// Also copy root README.md to docs/README.md

if root_dir.join("README.md").exists() {
fs::copy(root_dir.join("README.md"), docs_dir.join("README.md"))?;
fs::copy(root_dir.join("README.md"), release_dir.join("README.md"))?;
}

// 9. Copy licenses & metadata to release root
if root_dir.join("LICENSE").exists() {
fs::copy(root_dir.join("LICENSE"), release_dir.join("LICENSE"))?;
}
Expand All @@ -168,93 +229,116 @@ pause
release_dir.join("CHANGELOG.md"),
)?;
}
if root_dir.join("README.md").exists() {
fs::copy(root_dir.join("README.md"), release_dir.join("README.md"))?;
}

// Create a release notes file
let git_commit = get_git_commit();
let build_date = Local::now().format("%Y-%m-%d").to_string();
let release_notes_content = format!(
"# TechScript 2.0 Release Notes\n\n- Build Date: {}\n- Git Commit: {}\n- Official froze v2.0.0 Release.\n",
"# TechScript 2.0 Release Notes

- Build Date: {}
- Git Commit: {}
- Official froze v2.0.0 Release.
",
build_date, git_commit
);
fs::write(release_dir.join("RELEASE_NOTES.md"), &release_notes_content)?;
fs::write(docs_dir.join("ReleaseNotes.md"), &release_notes_content)?;

// 10. Package VS Code Extension (vsix)
Ok((git_commit, build_date))
}

fn package_installers(
root_dir: &Path,
release_dir: &Path,
version: &str,
runtime_dir: &Path,
examples_dir: &Path,
docs_dir: &Path,
tools_dir: &Path,
) -> anyhow::Result<()> {
let vsix_dest = release_dir.join("TechScript.vsix");
package_vsix(&root_dir, &vsix_dest, &version)?;
package_vsix(root_dir, &vsix_dest, version)?;

// 11. Create Portable release ZIP (excluding installers/zip themselves)
let portable_zip_dest = release_dir.join("TechScript_Portable.zip");
println!(
"Generating portable release: {}",
portable_zip_dest.display()
);
zip_release_folder(&release_dir, &portable_zip_dest)?;
zip_release_folder(release_dir, &portable_zip_dest)?;

// 12. Create Zip packages for Online Installer download
let installer_res_dir = release_dir.join("installer");
fs::create_dir_all(&installer_res_dir)?;

zip_sub_directory(&runtime_dir, &installer_res_dir.join("stdlib.zip"))?;
zip_sub_directory(&examples_dir, &installer_res_dir.join("examples.zip"))?;
zip_sub_directory(&docs_dir, &installer_res_dir.join("docs.zip"))?;
zip_sub_directory(runtime_dir, &installer_res_dir.join("stdlib.zip"))?;
zip_sub_directory(examples_dir, &installer_res_dir.join("examples.zip"))?;
zip_sub_directory(docs_dir, &installer_res_dir.join("docs.zip"))?;

// 13. Write Inno Setup offline and online script configurations
let offline_iss = installer_res_dir.join("offline_installer.iss");
let online_iss = installer_res_dir.join("online_installer.iss");

generate_offline_inno_script(&offline_iss, &version)?;
generate_online_inno_script(&online_iss, &version)?;
generate_offline_inno_script(&offline_iss, version)?;
generate_online_inno_script(&online_iss, version)?;

// 14. Code-sign all tools/ executables before installer packaging
let tools_list = [
"tsc.exe",
"tsvm.exe",
"tspm.exe",
"tsfmt.exe",
"tslint.exe",
"tsdoc.exe",
"tsmigrate.exe",
];
for tool_name in &tools_list {
sign_executable(&tools_dir.join(tool_name));
}
sign_executable(&tools_dir.join("tsls.exe"));

// 15. Compile Offline installer using Inno Setup and copy to online filename so both are same
compile_inno_installer(&offline_iss, &release_dir.join("TechScript_Setup.exe"))?;
fs::copy(
release_dir.join("TechScript_Setup.exe"),
release_dir.join("TechScript_Online_Setup.exe"),
)?;

// 16. Code-sign the setup installers
sign_executable(&release_dir.join("TechScript_Setup.exe"));
sign_executable(&release_dir.join("TechScript_Online_Setup.exe"));

// 17. Clean up installer zip components to keep releases folder clean
let _ = fs::remove_dir_all(&installer_res_dir);

// 18. Generate manifest.json (Release Manifest)
let examples_count = count_files(&examples_dir, "*.txs")?;
let docs_count = count_files(&docs_dir, "*.md")?;
Ok(())
}

fn finalize_release(
root_dir: &Path,
release_dir: &Path,
version: &str,
git_commit: &str,
build_date: &str,
examples_dir: &Path,
docs_dir: &Path,
) -> anyhow::Result<()> {
let examples_count = count_files(examples_dir, "*.txs")?;
let docs_count = count_files(docs_dir, "*.md")?;
let manifest_dest = release_dir.join("manifest.json");

let checksum_json = calculate_checksums_json(&release_dir)?;
let checksum_json = calculate_checksums_json(release_dir)?;
generate_release_manifest(
&manifest_dest,
&version,
&git_commit,
&build_date,
version,
git_commit,
build_date,
examples_count,
docs_count,
&checksum_json,
)?;

// 19. Generate SHA256SUMS.txt
generate_checksums_txt(&release_dir)?;
generate_checksums_txt(release_dir)?;

// 20. Versioned Releases Folder (v2.0.0)
let versioned_dir = root_dir.join("releases").join(format!("v{}", version));
if versioned_dir.exists() {
fs::remove_dir_all(&versioned_dir)?;
}
fs::create_dir_all(&versioned_dir)?;
copy_dir_all(&release_dir, &versioned_dir)?;
copy_dir_all(release_dir, &versioned_dir)?;

println!("=== Packaging Complete! ===");
Ok(())
Expand Down
Loading