Get far on package manager

This commit is contained in:
2025-04-29 05:05:34 -05:00
parent 53c027ef63
commit 4c32bd9b99
21 changed files with 1176 additions and 0 deletions

View File

@@ -0,0 +1,2 @@
bin
obj

View File

@@ -0,0 +1,14 @@
<?component(body) name="Shell" ?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hello, world</title>
</head>
<body>
<?body?>
<?end?>
</body>
</html>
<?end?>

View File

@@ -0,0 +1,24 @@
{
"name": "Tesses.CrossLang.Template.EmptyWebsite",
"version": "1.0.0.0-prod",
"info": {
"maintainer": "Mike Nolan",
"type": "template",
"repo": "https://onedev.site.tesses.net/CrossLang/CrossLangExtras",
"homepage": "https://crosslang.tesseslanguage.com/",
"license": "MIT",
"template_name": "emptyweb",
"description": "An empty website with my template engine",
"template_info": {
"type": "console"
},
"template_project_dependencies": [
{
"name": "Tesses.CrossLang.Markup",
"version": "1.0.0.0-prod"
}
],
"template_extra_text_ftles": [],
"template_ignored_files": ["bin","obj"]
}
}

View File

@@ -0,0 +1,8 @@
<?page() route="/" ?>
<?Shell?>
<?arg_component() ?>
<h1>Hello, world</h1>
<span>Views: <?expr ++count ?></span>
<?end?>
<?end?>
<?end?>

View File

@@ -0,0 +1,5 @@
var count = 0;
func main(args)
{
Net.Http.ListenSimpleWithLoop(Router,4206);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,621 @@
func DB.Open()
{
var path = DB.working / "data.db";
return Sqlite.Open(path);
}
/*func DB.GetPackageDetails(name)
{
DB.Lock();
var dbCon = DB.Open();
Sqlite.Close(dbCon);
DB.Unlock();
}*/
func DB.CanUploadPackagePrefix(userId, packageName)
{
var prefix = packageName.Split(".",true,2);
DB.Lock();
var dbCon = DB.Open();
var exec = Sqlite.Exec(dbCon, $"SELECT * FROM reserved_prefixes WHERE prefix = {Sqlite.Escape(prefix)};");
Sqlite.Close(dbCon);
DB.Unlock();
if(TypeOf(exec) == "List" && exec.Length > 0) return ParseLong(exec[0].accountId) == userId;
return true;
}
func DB.GetUserInfo(userId)
{
DB.Lock();
var dbCon = DB.Open();
var exec = Sqlite.Exec(dbCon, $"SELECT * FROM sessions WHERE id = {userId};");
Sqlite.Close(dbCon);
DB.Unlock();
if(TypeOf(exec) == "List" && exec.Length == 1)
{
var data = exec[0];
data.flags = ParseLong(data.flags);
return data;
}
return null;
}
func DB.PackageExists(userId,pkgInfo)
{
var statusCode = 4;
DB.Lock();
var dbCon = DB.Open();
var exec = Sqlite.Exec(dbCon,$"SELECT * from packages WHERE packageName = {Sqlite.Escape(pkgInfo.Name)};");
if(TypeOf(exec) == "List")
{
if(exec.Length == 1)
{
var pkgId = exec[0].id;
if(ParseLong(exec[0].accountId) != userId)
statusCode=4;
else
{
exec = Sqlite.Exec(dbCon,$"SELECT * from versions WHERE packageId = {pkgId} AND version = {pkgInfo.Version.VersionInt};");
if(TypeOf(exec) == "List")
{
if(exec.Length == 1)
{
if(pkgInfo.Version.Stage == "dev")
statusCode=3;
else
statusCode=2;
}
else statusCode=1;
}
}
}
else statusCode=0;
}
Sqlite.Close(dbCon);
DB.Unlock();
return statusCode;
}
func DB.AddPackage(userId,pkgInfo)
{
DB.Lock();
var dbCon = DB.Open();
Sqlite.Exec(dbCon,$"INSERT INTO packages (packageName, accountId) VALUES ({Sqlite.Escape(pkgInfo.Name)}, {userId});");
Sqlite.Close(dbCon);
DB.Unlock();
}
func DB.UpdateVersion(pkgInfo)
{
DB.Lock();
var dbCon = DB.Open();
var exec = Sqlite.Exec(dbCon,$"SELECT * FROM packages WHERE packageName = {Sqlite.Escape(pkgInfo.Name)};");
var pkgId = 0;
if(TypeOf(exec) == "List" && exec.Length == 1)
{
pkgId = exec[0].id;
}
var version = pkgInfo.Version.VersionInt;
var info = Json.Decode(pkgInfo.Info);
var description = info.description;
var type = info.type;
var maintainer = info.maintainer;
var homepage = info.homepage;
var repo = info.repo;
var license = info.license;
if(TypeOf(description) != "String") description="";
if(TypeOf(type) != "String") type="";
if(TypeOf(maintainer) != "String") maintainer="";
if(TypeOf(homepage) != "String") homepage="";
if(TypeOf(repo) != "String") repo="";
if(TypeOf(license) != "String") license="";
//CREATE TABLE IF NOT EXISTS versions (id INTEGER PRIMARY KEY AUTOINCREMENT, packageId INTEGER, version INTEGER, description TEXT, type TEXT, maintainer TEXT, homepage TEXT, repo TEXT, license TEXT);
//VALUES ({pkgId},{version},{Sqlite.Escape(description)},{Sqlite.Escape(type)},{Sqlite.Escape(maintainer)},{Sqlite.Escape(homepage)},{Sqlite.Escape(repo)},{Sqlite.Escape(license)});
Sqlite.Exec(dbCon,$"UPDATE versions SET description = {Sqlite.Escape(description)}, type = {Sqlite.Escape(type)}, maintainer = {Sqlite.Escape(maintainer)}, homepage = {Sqlite.Escape(homepage)}, repo = {Sqlite.Escape(repo)}, license = {Sqlite.Escape(license)}, uploadTime = {Time.Now} WHERE packageId = {pkgId} AND version = {version};");
Sqlite.Close(dbCon);
DB.Unlock();
}
func DB.AddVersion(pkgInfo)
{
DB.Lock();
var dbCon = DB.Open();
var exec = Sqlite.Exec(dbCon,$"SELECT * FROM packages WHERE packageName = {Sqlite.Escape(pkgInfo.Name)};");
var pkgId = 0;
if(TypeOf(exec) == "List" && exec.Length == 1)
{
pkgId = exec[0].id;
}
var version = pkgInfo.Version.VersionInt;
var info = Json.Decode(pkgInfo.Info);
var description = info.description;
var type = info.type;
var maintainer = info.maintainer;
var homepage = info.homepage;
var repo = info.repo;
var license = info.license;
if(TypeOf(description) != "String") description="";
if(TypeOf(type) != "String") type="";
if(TypeOf(maintainer) != "String") maintainer="";
if(TypeOf(homepage) != "String") homepage="";
if(TypeOf(repo) != "String") repo="";
if(TypeOf(license) != "String") license="";
//CREATE TABLE IF NOT EXISTS versions (id INTEGER PRIMARY KEY AUTOINCREMENT, packageId INTEGER, version INTEGER, description TEXT, type TEXT, maintainer TEXT, homepage TEXT, repo TEXT, license TEXT);
Sqlite.Exec(dbCon,$"INSERT INTO versions (packageId,version,description,type,maintainer,homepage,repo,license,uploadTime) VALUES ({pkgId},{version},{Sqlite.Escape(description)},{Sqlite.Escape(type)},{Sqlite.Escape(maintainer)},{Sqlite.Escape(homepage)},{Sqlite.Escape(repo)},{Sqlite.Escape(license)},{Time.Now});");
Sqlite.Close(dbCon);
DB.Unlock();
}
func DB.GetPackageIcon(name, version)
{
if(TypeOf(name) != "String") name = "";
if(TypeOf(version) != "String") version = "";
var file = DB.working / "Packages" / name / $"{name}-{version}.crvm";
if(FS.Local.FileExists(file) && name.Length > 0 && version.Length > 0)
{
var strm = FS.Local.OpenFile(file,"rb");
if(strm != null)
{
var exec = VM.LoadExecutable(strm);
strm.Close();
if(exec != null)
{
if(exec.Icon != null)
{
return exec.Icon;
}
}
}
}
return embed("crosslang.png");
}
func DB.UploadPackage(userId, filePath)
{
if(!FS.Local.FileExists(filePath)) return {Success=false, Reason = "File does not exist"};
var userInfo = DB.GetUserInfo(userId);
if(userInfo == null) return { Success=false, Reason = "User does not exist"};
if((userInfo.flags & DB.FLAG_VERIFIED) == 0) return { Success=false, Reason = "User is not verified"};
var strm = FS.Local.OpenFile(filePath,"rb");
var failed=false;
var reason = "";
try {
var pkgInfo = VM.LoadExecutable(strm);
}catch(ex) {
if(ex.Type == "NativeException")
reason = ex.Text;
failed=true;
}
strm.Close();
if(failed) return {Success=false, Reason = reason};
if(!DB.CanUploadPackagePrefix(userId,pkgInfo.Name)) return { Success = false, Reason = "You can't upload a package with that prefix."};
switch(DB.PackageExists(userId, pkgInfo))
{
case 0:
//package name does not exist
DB.AddPackage(userId,pkgInfo);
case 1:
DB.AddVersion(pkgInfo);
FS.Local.CreateDirectory(DB.working / "Packages" / pkgInfo.Name);
FS.Local.MoveFile(filePath, DB.working / "Packages" / pkgInfo.Name / $"{pkgInfo.Name}-{pkgInfo.Version}.crvm");
//package version does not exist
break;
case 2:
return { Success = false, Reason = "Package already exists and is not a dev package." };
case 3:
//update package version (it exists but is dev)
DB.UpdateVersion(pkgInfo);
FS.Local.MoveFile(filePath, DB.working / "Packages" / pkgInfo.Name / $"{pkgInfo.Name}-{pkgInfo.Version}.crvm");
break;
case 4:
//package is not yours
return { Success = false, Reason = "You don't own the package."};
}
return {Success=true};
}
func DB.GetUserIdFromSession(session)
{
DB.Lock();
var dbCon = DB.Open();
var exec = Sqlite.Exec(dbCon,$"SELECT * FROM sessions WHERE key = {Sqlite.Escape(session)};");
Sqlite.Close(dbCon);
DB.Unlock();
if(TypeOf(exec) == "List" && exec.Length == 1) return ParseLong(exec[0].accountId);
return -1;
}
func DB.GetSession(ctx)
{
var cookie = ctx.RequestHeaders.TryGetFirst("Cookie");
if(TypeOf(cookie) == "String")
{
each(var part : cookie.Split("; "))
{
if(part.Length > 0)
{
var cookieKV = part.Split("=",true,2);
if(cookieKV.Length == 2 && cookieKV[0] == "Session")
{
var session = cookieKV[1];
var sessionId = DB.GetUserIdFromSession(session);
if(sessionId != -1)
return session;
}
}
}
}
return null;
}
func DB.CreateSession(userId)
{
DB.Lock();
var dbCon = DB.Open();
var rand = Net.Http.UrlEncode(Crypto.Base64Encode(Crypto.RandomBytes(32, "CPKG")));
Sqlite.Exec(dbCon, $"INSERT INTO sessions (accountId,key) VALUES ({userId},{Sqlite.Escape(rand)});");
Sqlite.Close(dbCon);
DB.Unlock();
return rand;
}
func DB.CreateCSRF(ctx)
{
var session = DB.GetSession(ctx);
if(session)
{
var csrf = Crypto.Base64Encode(Crypto.RandomBytes(32, "CPKG"));
var expires = Time.Now + 600;
DB.Lock();
DB.csrf.Add({
Token = csrf,
Expires = expires,
Session = session
});
DB.Unlock();
return csrf;
}
return "";
}
func DB.VerifyCSRF(session,csrf)
{
if(TypeOf(csrf) != "String") return false;
if(csrf.Length == 0) return false;
csrf = csrf.Replace("\n","").Replace("\r","\n").Replace(" ","");
if(csrf.Length == 0) return false;
var retVal = false;
var time = Time.Now;
DB.Lock();
var _oldCSRF = DB.csrf;
DB.csrf = [];
//This will be inefficient but hopefully there won't be too many csrf tokens at any given time (we try to delete the expired ones)
each(var csrfItem : _oldCSRF)
{
if(time >= csrfItem.Expires) continue;
if(csrfItem.Token == csrf && time < csrfItem.Expires && session == csrfItem.Session)
{
retVal=true;
continue;
}
DB.csrf.Add(csrfItem);
}
DB.Unlock();
return retVal;
}
func DB.GetLatestVersion(name)
{
var sql = $"SELECT * FROM packages p join versions v on p.id = v.packageId and v.version = (SELECT MAX(version) FROM versions WHERE packageId = v.packageId) and p.packageName = {Sqlite.Escape(name)};";
DB.Lock();
var dbCon = DB.Open();
var res = Sqlite.Exec(dbCon, sql);
DB.Unlock();
if(TypeOf(res) == "List" && res.Length > 0)
{
return Version.FromLong(ParseLong(res[0].version)).ToString();
}
return null;
}
func DB.GetPackages($page)
{
if(TypeOf(page) != "Long") page = 1;
var sql = "SELECT * FROM packages p join versions v on p.id = v.packageId and v.version = (SELECT MAX(version) FROM versions WHERE packageId = v.packageId);";
DB.Lock();
var dbCon = DB.Open();
var res = Sqlite.Exec(dbCon, sql);
Sqlite.Close(dbCon);
DB.Unlock();
var res2 = [];
each(var item : res)
{
res2.Add({
id = item.packageId,
name = item.packageName,
version = Version.FromLong(ParseLong(item.version)).ToString(),
homepage = item.homepage,
repo = item.repo,
type = item.type,
maintainer = item.maintainer,
description = item.description
});
}
return res2;
}
func DB.Lock()
{
DB.mtx.Lock();
}
func DB.Unlock()
{
DB.mtx.Unlock();
}
func DB.GetUniqueNumber()
{
DB.Lock();
var unum = DB.u_n_counter++;
DB.Unlock();
return unum;
}
func DB.Init(working)
{
DB.working = Path.FromString(working);
DB.csrf = [];
DB.u_n_counter=0;
FS.Local.CreateDirectory(DB.working);
FS.Local.CreateDirectory(DB.working / "Temp");
FS.Local.CreateDirectory(DB.working / "Packages");
each(var f : FS.Local.EnumeratePaths(DB.working / "Temp"))
{
if(FS.Local.FileExists(f)) FS.Local.DeleteFile(f);
}
DB.mtx = Mutex();
var p = DB.working / "conf.json";
if(FS.Local.FileExists(p))
DB.Config = Json.Decode(FS.ReadAllText(FS.Local, p));
else
DB.Config = {
AllowRegister=false,
};
var dbCon = DB.Open();
Sqlite.Exec(dbCon,"CREATE TABLE IF NOT EXISTS packages (id INTEGER PRIMARY KEY AUTOINCREMENT, packageName TEXT UNIQUE, accountId INTEGER);");
Sqlite.Exec(dbCon,"CREATE TABLE IF NOT EXISTS versions (id INTEGER PRIMARY KEY AUTOINCREMENT, packageId INTEGER, version INTEGER, description TEXT, type TEXT, maintainer TEXT, homepage TEXT, repo TEXT, license TEXT, uploadTime INTEGER);");
Sqlite.Exec(dbCon,"CREATE TABLE IF NOT EXISTS accounts (id INTEGER PRIMARY KEY AUTOINCREMENT, email TEXT UNIQUE, accountName TEXT UNIQUE, password_hash TEXT, password_salt TEXT, motto TEXT, verifyKey TEXT UNIQUE, flags INTEGER);");
Sqlite.Exec(dbCon,"CREATE TABLE IF NOT EXISTS sessions (id INTEGER PRIMARY KEY AUTOINCREMENT, accountId INTEGER, key STRING UNIQUE);");
Sqlite.Exec(dbCon,"CREATE TABLE IF NOT EXISTS reserved_prefixes (id INTEGER PRIMARY KEY AUTOINCREMENT, accountId INTEGER, prefix STRING UNIQUE);");
Sqlite.Close(dbCon);
}
DB.FLAG_ADMIN = 0b00000010;
DB.FLAG_VERIFIED = 0b00000001;
DB.FLAG_RESETPASS = 0b00001000;
DB.FLAG_VERIFY = 0b00000100;
DB.ITTR = 35000;
func DB.GetAccountId(email, password)
{
DB.Lock();
var dbCon = DB.Open();
var exec = Sqlite.Exec(dbCon,$"SELECT * FROM accounts WHERE email = {Sqlite.Escape(email)};");
Sqlite.Close(dbCon);
DB.Unlock();
var correct=false;
if(TypeOf(exec) == "List" && exec.Length > 0)
{
var salt = Crypto.Base64Decode(exec[0].password_salt);
var hash = Crypto.PBKDF2(password, salt, DB.ITTR,64,384);
var hashStr = Crypto.Base64Encode(hash);
if(exec[0].password_hash == hashStr)
{
return ParseLong(exec[0].id);
}
}
return -1;
}
func DB.SendVerifyEmail(email,name, verify_hash_str)
{
var emailText = $"
<h1>Hello {Net.Http.HtmlEncode(name)}</h1>
<span>To verify your account go to <a href=\"{DB.Config.Prefix.TrimEnd('/')}/verify?code={Net.Http.UrlEncode(verify_hash_str)}\">{DB.Config.Prefix.TrimEnd('/')}/verify?code={Net.Http.UrlEncode(verify_hash_str)}</a></span>
";
var data = {
server = DB.Config.MailConfig.Server,
auth = DB.Config.MailConfig.Auth,
domain = DB.Config.MailConfig.Domain,
from = DB.Config.MailConfig.From,
to = email.Replace(">","").Replace("\n",""),
subject = "Verify your account on CPKG",
body = {
type = "text/html",
data = emailText
}
};
Net.Smtp.Send(data);
}
func DB.CreateUser(email, name, password)
{
var res = {Success = true, Redirect="/"};
DB.Lock();
var dbCon = DB.Open();
if(Sqlite.Exec(dbCon, "SELECT * FROM accounts LIMIT 1;").Length == 0)
{
//create the admin
var salt = Crypto.RandomBytes(32, "CPKG");
var hash = Crypto.PBKDF2(password, salt, DB.ITTR,64,384);
var r = Sqlite.Exec(dbCon,$"INSERT INTO accounts (email, accountName, password_hash, password_salt, flags) values ({Sqlite.Escape(email)},{Sqlite.Escape(name)},{Sqlite.Escape(Crypto.Base64Encode(hash))},{Sqlite.Escape(Crypto.Base64Encode(salt))},{DB.FLAG_ADMIN|DB.FLAG_VERIFIED});");
if(TypeOf(r) == "String") res = {Success = false, Reason = r};
}
else
{
var exec = Sqlite.Exec(dbCon,$"SELECT * FROM accounts WHERE email = {Sqlite.Escape(email)};");
if(TypeOf(exec) == "List" && exec.Length > 0)
{
exec = Sqlite.Exec(dbCon,$"SELECT * FROM accounts WHERE accountName = {Sqlite.Escape(name)};");
if(TypeOf(exec) == "List" && exec.Length > 0)
{
res = {Success=false, Reason = "Email and Name already exists"};
}
else
{
res = {Success=false, Reason = "Email already exists"};
}
}
else {
exec = Sqlite.Exec(dbCon,$"SELECT * FROM accounts WHERE accountName = {Sqlite.Escape(name)};");
if(TypeOf(exec) == "List" && exec.Length > 0)
{
res = {Success=false, Reason = "Name already exists"};
}
else if(DB.Config.AllowRegister)
{
//email and name already exists
var salt = Crypto.RandomBytes(32, "CPKG");
var hash = Crypto.PBKDF2(password, salt, DB.ITTR,64,384);
var verify_hash = Crypto.RandomBytes(32, "CPKG");
var verify_hash_str = Crypto.Base64Encode(verify_hash);
var r = Sqlite.Exec(dbCon,$"INSERT INTO accounts (email, accountName, password_hash, password_salt, verifyKey, flags) values ({Sqlite.Escape(email)},{Sqlite.Escape(name)},{Sqlite.Escape(Crypto.Base64Encode(hash))},{Sqlite.Escape(Crypto.Base64Encode(salt))},{Sqlite.Escape(verify_hash_str)},{DB.FLAG_VERIFY});");
if(TypeOf(r) == "String") res = {Success = false, Reason = r};
if(DB.Config.MailConfig)
DB.SendVerifyEmail(email,name, verify_hash_str);
res = {Success=true, Redirect="/check_email"};
}
else
{
res = {Success=false, Reason="Registration is disabled on this server"};
}
}
}
Sqlite.Close(dbCon);
DB.Unlock();
return res;
}
func DB.GetPackageVersions(name)
{
var sql = $"SELECT * FROM packages p inner join versions v on p.id = v.packageId inner join accounts a on p.accountId = a.id WHERE p.packageName = {Sqlite.Escape(name)} ORDER BY v.version DESC;";
var dbCon = DB.Open();
var res = Sqlite.Exec(dbCon, sql);
Sqlite.Close(dbCon);
DB.Unlock();
var list = [];
each(var item : res)
{
var version = Version.FromLong(ParseLong(item.version)).ToString();
var uploadTime = ParseLong(item.uploadTime);
list.Add({
version,
download = $"./api/v1/download?name={Net.Http.UrlEncode(name)}&version={Net.Http.UrlEncode(version)}",
accountName = item.accountName,
description = item.description,
maintainer = item.maintainer,
type = item.type,
repo = item.repo,
homepage = item.homepage,
license = item.license,
uploadTime,
uploadDate=Time.UTCUsSlashDate(uploadTime)
});
}
return list;
}
func DB.QueryPackages(q, offset, limit)
{
if(TypeOf(offset) != "Long") offset = 0;
if(TypeOf(limit) != "Long") limit = 20;
var q2 = Sqlite.Escape($"%{q}%");
var sql = $"SELECT * FROM packages p inner join versions v on p.id = v.packageId and (p.packageName LIKE {q2} OR v.description LIKE {q2}) and v.version = (SELECT MAX(version) FROM versions WHERE packageId = v.packageId) INNER JOIN accounts a on a.id = p.accountId LIMIT {limit} OFFSET {offset};";
DB.Lock();
var dbCon = DB.Open();
var res = Sqlite.Exec(dbCon, sql);
Sqlite.Close(dbCon);
DB.Unlock();
var res2=[];
each(var item : res)
{
var uploadTime = ParseLong(item.uploadTime);
res2.Add({
accountName = item.accountName,
description = item.description,
homepage = item.homepage,
license = item.license,
maintainer = item.maintainer,
packageName = item.packageName,
uploadTime,
uploadDate = Time.UTCUsSlashDate(uploadTime),
repo = item.repo,
type = item.type,
version = Version.FromLong(ParseLong(item.version)).ToString()
});
}
return res2;
}

View File

@@ -0,0 +1,13 @@
func Components.Package(name,version,owner,upload,desc)
{
var image = $"./api/v1/package_icon.png?name={Net.Http.UrlEncode(name)}&version={Net.Http.UrlEncode(version)}";
var pkgUrl = $"./package?name={Net.Http.UrlEncode(name)}";
return <li>
<img width={"64"} height={"64"} src={image} alt={"icon"}>
<a href={pkgUrl}>{name}</a> | <span>By: {owner}</span>
<br>
<span>Updated: {upload} | Latest version: {version}</span>
<p>{desc}</p>
</li>;
}

View File

@@ -0,0 +1,43 @@
func Shell(title,pages,body)
{
var shell=
<html lang={"en"}>
<head>
<meta charset={"UTF-8"}>
<meta name={"viewport"} content={"width=device-width, initial-scale=1.0"}>
<link rel={"stylesheet"} href={"./css/bootstrap.min.css"}>
<title>CPKG - {title}</title>
</head>
<body>
<header>
<nav class={"navbar navbar-expand-lg"} style={"background-color: #ffff00;"}>
<div class={"container-fluid"}>
<a class={"navbar-brand"} href={"./"}>CPKG</a>
<button class={"navbar-toggler"} type={"button"} data-bs-toggle={"collapse"} data-bs-target={"#navbarNav"} aria-controls={"navbarNav"} aria-expanded={"false"} aria-label={"Toggle navigation"}>
<span class={"navbar-toggler-icon"}></span>
</button>
<div class={"collapse navbar-collapse"} id={"navbarNav"}>
<ul class={"navbar-nav"}>
<each(var item : pages)>
<li class={"nav-item"}>
<if(item.active)>
<a class={"nav-link active"} aria-current={"page"} href={item.route}>{item.text}</a>
<else>
<a class={"nav-link"} href={item.route}>{item.text}</a>
</if>
</li>
</each>
</ul>
</div>
</div>
</nav>
</header>
<br>
<raw(body)>
<script src={"./js/bootstrap.min.js"}></script>
</body>
</html>;
return shell;
}

View File

@@ -0,0 +1,27 @@
func Pages.CheckEmail()
{
var pages = [
{
active = false,
route = "/packages",
text = "Packages"
},
{
active = false,
route = "/upload",
text = "Upload"
},
{
active = false,
route = "/login",
text = "Login"
}
];
var html = <div class={"container"}>
<h1>Please check your email.</h1>
<p>
The email may or may not be in your spam.
</p>
</div>;
return Shell("Check your email",pages,html);
}

View File

@@ -0,0 +1,38 @@
func Pages.Index()
{
var pages = [
{
active = false,
route = "/packages",
text = "Packages"
},
{
active = false,
route = "/upload",
text = "Upload"
},
{
active = false,
route = "/login",
text = "Login"
}
];
var html = <article><h1 class={"display-5"} style={"text-align: center;"}>Make crosslang development faster and more convenient with CPKG</h1><form action={"./packages"} method={"get"}><div class={"container text-center"}><div class={"row"}><div class={"col"}>
</div>
<div class={"col-6"}>
<div class={"input-group mb-3"}>
<input type={"search"} name={"q"} class={"form-control"} placeholder={"Search for packages..."} aria-label={"Search for packages..."} aria-describedby={"button-search"}>
<button class={"btn btn-outline-secondary"} type={"submit"} id={"button-search"}>Search</button>
</div>
</div>
<div class={"col"}>
</div>
</div>
</div>
</form>
</article>;
return Shell("Main Page",pages,html);
}

View File

@@ -0,0 +1,36 @@
func Pages.Login()
{
var pages = [
{
active = false,
route = "/packages",
text = "Packages"
},
{
active = false,
route = "/upload",
text = "Upload"
},
{
active = true,
route = "/login",
text = "Login"
}
];
var html = <div class={"container min-vh-100 d-flex justify-content-center align-items-center"}>
<form action={"./login"} method={"post"} enctype={"application/x-www-form-urlencoded"}>
<div class={"mb-3"}>
<label for={"email"} class={"form-label"}>Email address</label>
<input type={"email"} class={"form-control"} id={"email"} name={"email"} aria-describedby={"emailHelp"}>
<div id={"emailHelp"} class={"form-text"}>{"We'll"} never share your email with anyone else.</div>
</div>
<div class={"mb-3"}>
<label for={"password"} class={"form-label"}>Password</label>
<input type={"password"} class={"form-control"} name={"password"} id={"password"}>
</div>
<a href={"./signup"}>Sign up</a>
<button type={"submit"} class={"btn btn-primary"}>Login</button>
</form>
</div>;
return Shell("Login",pages,html);
}

View File

@@ -0,0 +1,157 @@
func Pages.Package(name)
{
var package = DB.GetPackageVersions(name);
var pages = [
{
active = false,
route = "/packages",
text = "Packages"
},
{
active = false,
route = "/upload",
text = "Upload"
},
{
active = false,
route = "/login",
text = "Login"
}
];
var html = <if(package.Length > 0)><section class={"container"}>
<br>
<img width={"64"} height={"64"} src={$"./api/v1/package_icon.png?name={Net.Http.UrlEncode(name)}&version={Net.Http.UrlEncode(package[0].version)}"} alt={"icon"}>
<h1>{name}</h1>
</section>
<section class={"container"}>
<h2>To install</h2>
<ul class={"nav nav-tabs"} id={"myTab"} role={"tablist"}>
<li class={"nav-item"} role={"presentation"}>
<button class={"nav-link active"} id={"home-tab"} data-bs-toggle={"tab"} data-bs-target={"#home-tab-pane"} type={"button"} role={"tab"} aria-controls={"home-tab-pane"} aria-selected={"true"}>CLI</button>
</li>
<li class={"nav-item"} role={"presentation"}>
<button class={"nav-link"} id={"profile-tab"} data-bs-toggle={"tab"} data-bs-target={"#profile-tab-pane"} type={"button"} role={"tab"} aria-controls={"profile-tab-pane"} aria-selected={"false"}>cross.json</button>
</li>
</ul>
<div class={"tab-content"} id={"myTabContent"}>
<div class={"tab-pane fade show active"} id={"home-tab-pane"} role={"tabpanel"} aria-labelledby={"home-tab"} tabindex={"0"}><div class={"card"}>
<div class={"card-body"}>
<if(package[0].type == "lib" || package[0].type == "compile_tool")>
crosslang add-dependency {name}
</if>
</div>
</div>
</div>
<div class={"tab-pane fade"} id={"profile-tab-pane"} role={"tabpanel"} aria-labelledby={"profile-tab"} tabindex={"0"}><div class={"card"}>
<div class={"card-body"}>
{Json.Encode({name=name,version = package[0].version })}
</div>
</div>
</div>
</div>
<br>
<h3>More options and info</h3>
<ul>
<li>Account: <a href={$"./account?name={Net.Http.UrlEncode(package[0].accountName)}"}>{package[0].accountName}</a></li>
<if(package[0].maintainer.Length > 0)>
<li>Maintainer: {package[0].maintainer}</li>
</if>
<if(package[0].license.Length > 0)>
<li>License: {package[0].license}</li>
</if>
<if(package[0].homepage.Length > 0)>
<li>Homepage: <a href={package[0].homepage}>{package[0].homepage}</a></li>
</if>
<if(package[0].repo.Length > 0)>
<li>Repo: <a href={package[0].repo}>{package[0].repo}</a></li>
</if>
<li>Last updated: {package[0].uploadDate}</li>
<li>Latest version: {package[0].version}</li>
<li>Type: {package[0].type}</li>
<li><a href={package[0].download}>Download</a></li>
</ul>
<if(package.Length > 1)>
<br>
<h3>Older versions</h3>
<div class={"accordion accordion-flush"} id={"accordionFlushExample"}>
<for(var i=1; i<package.Length; i++)>
<div class={"accordion-item"}>
<h2 class={"accordion-header"}>
<button class={"accordion-button collapsed"} type={"button"} data-bs-toggle={"collapse"} data-bs-target={$"#collapse-{Net.Http.HtmlEncode(package[i].version)}"} aria-expanded={"false"} aria-controls={"flush-collapseOne"}>
{package[i].version}
</button>
</h2>
<div id={$"collapse-{Net.Http.HtmlEncode(package[i].version)}"} class={"accordion-collapse collapse"} data-bs-parent={"#accordionFlushExample"}>
<div class={"accordion-body"}>
<ul>
<if(package[i].maintainer.Length > 0)>
<li>Maintainer: {package[i].maintainer}</li>
</if>
<if(package[i].license.Length > 0)>
<li>License: {package[i].license}</li>
</if>
<if(package[i].homepage.Length > 0)>
<li>Homepage: <a href={package[i].homepage}>{package[i].homepage}</a></li>
</if>
<if(package[i].repo.Length > 0)>
<li>Repo: <a href={package[i].repo}>{package[i].repo}</a></li>
</if>
<li>Uploaded: {package[i].uploadDate}</li>
<li>Type: {package[i].type}</li>
<li><a href={package[i].download}>Download</a></li>
</ul>
</div>
</div>
</div>
</for>
</div>
</if>
</section>
</if>;
return Shell($"Package {name}", pages,html);
}
/*
<section class="container">
<br>
<img width="64" height="64" src="https://api.nuget.org/v3-flatcontainer/newtonsoft.json/13.0.3/icon" alt="icon">
<h1>Package</h1>
</section>
<section class="container">
<h2>To install:</h2>
<ul class="nav nav-tabs" id="myTab" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active" id="home-tab" data-bs-toggle="tab" data-bs-target="#home-tab-pane" type="button" role="tab" aria-controls="home-tab-pane" aria-selected="true">CLI</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="profile-tab" data-bs-toggle="tab" data-bs-target="#profile-tab-pane" type="button" role="tab" aria-controls="profile-tab-pane" aria-selected="false">cross.json</button>
</li>
</ul>
<div class="tab-content" id="myTabContent">
<div class="tab-pane fade show active" id="home-tab-pane" role="tabpanel" aria-labelledby="home-tab" tabindex="0"><div class="card">
<div class="card-body">
crosslang add Package
</div>
</div>
</div>
<div class="tab-pane fade" id="profile-tab-pane" role="tabpanel" aria-labelledby="profile-tab" tabindex="0"><div class="card">
<div class="card-body">
{"name": "Package","Version": "1.0.0.0-prod"}
</div>
</div>
</div>
</div>
</section>
*/

View File

@@ -0,0 +1,46 @@
func Pages.Packages(ctx)
{
var q = ctx.QueryParams.TryGetFirst("q");
if(TypeOf(q) != "String") q = "";
var pages = [
{
active = true,
route = "/packages",
text = "Packages"
},
{
active = false,
route = "/upload",
text = "Upload"
},
{
active = false,
route = "/login",
text = "Login"
}
];
var html = <article><form action={"./packages"} method={"get"}><div class={"container text-center"}><div class={"row"}><div class={"col"}>
</div>
<div class={"col-6"}>
<div class={"input-group mb-3"}>
<input type={"search"} name={"q"} class={"form-control"} placeholder={"Search for packages..."} aria-label={"Search for packages..."} aria-describedby={"button-search"} value={q}>
<button class={"btn btn-outline-secondary"} type={"submit"} id={"button-search"}>Search</button>
</div>
</div>
<div class={"col"}>
</div>
</div>
</div>
</form>
</article>
+
<ul>
<each(var item : DB.QueryPackages(q,0,20))>
<raw(Components.Package(item.packageName,item.version,item.accountName,item.uploadDate,item.description))>
</each>
</ul>;
return Shell("Packages",pages,html);
}

View File

@@ -0,0 +1,44 @@
func Pages.Signup()
{
var pages = [
{
active = false,
route = "/packages",
text = "Packages"
},
{
active = false,
route = "/upload",
text = "Upload"
},
{
active = false,
route = "/login",
text = "Login"
}
];
var html = <div class={"container min-vh-100 d-flex justify-content-center align-items-center"}>
<form action={"./signup"} method={"post"} enctype={"application/x-www-form-urlencoded"}>
<div class={"mb-3"}>
<label for={"email"} class={"form-label"}>Email address</label>
<input type={"email"} class={"form-control"} id={"email"} aria-describedby={"emailHelp"} name={"email"}>
<div id={"emailHelp"} class={"form-text"}>{"We'll"} never share your email with anyone else.</div>
</div>
<div class={"mb-3"}>
<label for={"displayName"} class={"form-label"}>Display Name</label>
<input type={"text"} class={"form-control"} id={"displayName"} name={"displayName"}>
</div>
<div class={"mb-3"}>
<label for={"password"} class={"form-label"}>Password</label>
<input type={"password"} class={"form-control"} id={"password"} name={"password"}>
</div>
<div class={"mb-3"}>
<label for={"passwordconfirm"} class={"form-label"}>Confirm Password</label>
<input type={"password"} class={"form-control"} id={"passwordconfirm"} name={"passwordconfirm"}>
</div>
<a href={"./login"}>Login</a>
<button type={"submit"} class={"btn btn-primary"}>Sign Up</button>
</form>
</div>;
return Shell("Signup", pages, html);
}

View File

@@ -0,0 +1,29 @@
func Pages.Upload(ctx)
{
var pages = [
{
active = false,
route = "/packages",
text = "Packages"
},
{
active = true,
route = "/upload",
text = "Upload"
},
{
active = false,
route = "/login",
text = "Login"
}
];
var html = <div class={"container min-vh-100 d-flex justify-content-center align-items-center"}>
<form action={"./upload"} method={"post"} enctype={"multipart/form-data"}>
<h1>Upload Package</h1>
<input type={"file"} class={"form-control"} name={"package"} accept={".crvm"}>
<button type={"submit"} class={"btn btn-primary"}>Upload</button>
<input type={"hidden"} name={"csrf"} value={DB.CreateCSRF(ctx)}>
</form>
</div>;
return Shell("Upload Package",pages,html);
}

54
build.tcross Normal file
View File

@@ -0,0 +1,54 @@
func cmd(name, args)
{
var filename = Env.GetRealExecutablePath(name).ToString();
Console.Write($"Running command, {filename}");
each(var arg : args)
Console.Write($" {arg}");
Console.WriteLine();
var p= Process.Start({
FileName = filename,
Arguments = args
});
p.Join();
}
func main(args)
{
var args2=["-o","./Tesses.CrossLang.BuildEssentials/bin-tmp","-n","Tesses.CrossLang.BuildEssentials","-v","1.0.0.0-prod","Tesses.CrossLang.BuildEssentials/main.tcross"];
each(var f : FS.Local.EnumeratePaths("./Tesses.CrossLang.BuildEssentials/src"))
{
if(f.GetExtension() == ".tcross")
{
args2.Add(f.ToString());
}
}
cmd("crossc", args2);
cmd("crossvm",["./Tesses.CrossLang.BuildEssentials/bin-tmp/Tesses.CrossLang.BuildEssentials-1.0.0.0-prod.crvm","Tesses.CrossLang.Shell"]);
cmd("crossvm",["./Tesses.CrossLang.Shell/bin/Tesses.CrossLang.Shell-1.0.0.0-prod.crvm","build","Tesses.CrossLang.PackageServer"]);
//cmd("crossvm",["./Tesses.CrossLang.Shell/bin/Tesses.CrossLang.Shell-1.0.0.0-prod.crvm","build","Tesses.CrossLang.WebSite"]);
cmd("crossvm",["./Tesses.CrossLang.Shell/bin/Tesses.CrossLang.Shell-1.0.0.0-prod.crvm","build","Templates/console"]);
// cmd("crossvm",["./Tesses.CrossLang.Shell/bin/Tesses.CrossLang.Shell-1.0.0.0-prod.crvm","build","Templates/emptyweb"]);
// cmd("crossvm",["./Tesses.CrossLang.Shell/bin/Tesses.CrossLang.Shell-1.0.0.0-prod.crvm","build","Templates/web"]);
cmd("crossvm",["./Tesses.CrossLang.Shell/bin/Tesses.CrossLang.Shell-1.0.0.0-prod.crvm","build","Templates/template"]);
cmd("crossvm",["./Tesses.CrossLang.Shell/bin/Tesses.CrossLang.Shell-1.0.0.0-prod.crvm","build","Templates/lib"]);
cmd("crossvm",["./Tesses.CrossLang.Shell/bin/Tesses.CrossLang.Shell-1.0.0.0-prod.crvm","build","Templates/compiletool"]);
cmd("crossvm",["./Tesses.CrossLang.Shell/bin/Tesses.CrossLang.Shell-1.0.0.0-prod.crvm","build","Templates/tool"]);
/*cmd("crossvm",["./Tesses.CrossLang.Shell/bin/Tesses.CrossLang.Shell-1.0.0.0-prod.crvm","build","Tesses.CrossLang.PackageServer"]);
cmd("crossvm",["./Tesses.CrossLang.Shell/bin/Tesses.CrossLang.Shell-1.0.0.0-prod.crvm","build","Tesses.CrossLang.PackageServer"]);
cmd("crossvm",["./Tesses.CrossLang.Shell/bin/Tesses.CrossLang.Shell-1.0.0.0-prod.crvm","build","Tesses.CrossLang.PackageServer"]);
cmd("crossvm",["./Tesses.CrossLang.Shell/bin/Tesses.CrossLang.Shell-1.0.0.0-prod.crvm","build","Tesses.CrossLang.PackageServer"]);
cmd("crossvm",["./Tesses.CrossLang.Shell/bin/Tesses.CrossLang.Shell-1.0.0.0-prod.crvm","build","Tesses.CrossLang.PackageServer"]);*/
cmd("crossvm",["./Tesses.CrossLang.Shell/bin/Tesses.CrossLang.Shell-1.0.0.0-prod.crvm","build","crosslang_shell_archive_maker"]);
if(args.Length > 1)
{
if(args[1] == "install")
{
cmd("crossvm",["crosslang_shell_archive_maker/bin/crosslang_shell_archive_maker-1.0.0.0-prod.crvm", "install"]);
}
else if(args[1] == "pack")
{
cmd("crossvm",["crosslang_shell_archive_maker/bin/crosslang_shell_archive_maker-1.0.0.0-prod.crvm"]);
}
}
}