17 Commits

Author SHA1 Message Date
0459b0a84e Add the templates for npm
All checks were successful
Build and Deploy on Tag / build-crosslang-shell-and-cpkg (push) Successful in 37s
2026-05-10 20:52:26 -05:00
e77b071af1 Make CPKG more complete
All checks were successful
Build and Deploy on Tag / build-crosslang-shell-and-cpkg (push) Successful in 47s
2026-05-08 02:13:25 -05:00
e6d0a8ad6a Make CPKG more complete
All checks were successful
Build and Deploy on Tag / build-crosslang-shell-and-cpkg (push) Successful in 52s
2026-05-08 02:10:08 -05:00
8c11aa6f24 Make CPKG more complete
Some checks are pending
Build and Deploy on Tag / build-crosslang-shell-and-cpkg (push) Waiting to run
2026-05-08 01:56:55 -05:00
ea45c4c4f5 Make CPKG more complete
All checks were successful
Build and Deploy on Tag / update-tap (push) Successful in 43s
2026-05-08 01:40:51 -05:00
4f7be79841 Fix broken code due to runtime change 2026-05-01 08:37:00 -05:00
441d17e29f Fix tag.yaml
All checks were successful
Build and Deploy on Tag / update-tap (push) Successful in 39s
2026-05-01 08:20:30 -05:00
6c8d014bca First to gitea[
Some checks failed
Build and Deploy on Tag / update-tap (push) Failing after 17s
2026-05-01 08:16:45 -05:00
443984b837 Change docker file 2026-04-16 22:41:50 -05:00
8d574179e6 Change docker file 2026-04-16 22:39:54 -05:00
7d2a68223d add prebuild to build tool 2026-04-16 18:52:24 -05:00
7ad098cef5 Add meta 2026-01-23 05:00:24 -06:00
0a9ae78dbd Add better crosslang 2026-01-22 02:28:35 -06:00
d5906e552c Add better crosslang 2026-01-22 02:27:36 -06:00
3af3f08395 Fix plugin download bug 2026-01-15 00:12:56 -06:00
0890f838d1 Add ability to set icon from template 2025-12-28 13:27:54 -06:00
5b20a64c36 Fix template icon 2025-12-28 13:16:29 -06:00
89 changed files with 2735 additions and 190 deletions

95
.gitea/workflows/tag.yaml Normal file
View File

@@ -0,0 +1,95 @@
name: Build and Deploy on Tag
on:
push:
tags:
- 'v*'
env:
VERSION: ${{ gitea.ref_name }}
CLWS_PACKAGE: tesses50/crosslang-withshell
CPKG_PACKAGE: tesses50/cpkg
GITEA_DOMAIN: git.tesses.org
GITEA_REGISTRY_USER: tesses50
jobs:
build-crosslang-shell-and-cpkg:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: |
cd vscode-extension
npm install
npm install -g typescript
npm install -g @vscode/vsce
vsce package
cd ..
wget -O /usr/local/bin/crosslang https://redirect.tesses.net/cl-slim-x86_64-linux-musl
chmod 755 /usr/local/bin/crosslang
ln -s /usr/local/bin/crosslang /usr/local/bin/crossc
ln -s /usr/local/bin/crosslang /usr/local/bin/crossint
ln -s /usr/local/bin/crosslang /usr/local/bin/crossvm
crossint ./build.tcross pack
crossint ./build.tcross install
mkdir -p artifacts
cp Tesses.CrossLang.ShellPackage-*.crvm artifacts/ShellPackage.crvm
cp Tesses.CrossLang.PackageServer/bin/Tesses.CrossLang.PackageServer-*.crvm artifacts/CPKG.crvm
cd Tesses.CrossLang.PackageServer
crosslang upload-package --token="${{ secrets.CPKG_KEY }}" --host="https://cpkg.tesseslanguage.com/"
cd ../Tesses.CrossLang.Args
crosslang upload-package --token="${{ secrets.CPKG_KEY }}" --host="https://cpkg.tesseslanguage.com/"
cd ../Tesses.CrossLang.BuildEssentials
crosslang upload-package --token="${{ secrets.CPKG_KEY }}" --host="https://cpkg.tesseslanguage.com/"
cd ../Tesses.CrossLang.Std
crosslang upload-package --token="${{ secrets.CPKG_KEY }}" --host="https://cpkg.tesseslanguage.com/"
cd ../Tesses.CrossLang.Reference
crosslang upload-package --token="${{ secrets.CPKG_KEY }}" --host="https://cpkg.tesseslanguage.com/"
cd ../Templates/compiletool
crosslang upload-package --token="${{ secrets.CPKG_KEY }}" --host="https://cpkg.tesseslanguage.com/"
cd ../console
crosslang upload-package --token="${{ secrets.CPKG_KEY }}" --host="https://cpkg.tesseslanguage.com/"
cd ../emptyweb
crosslang upload-package --token="${{ secrets.CPKG_KEY }}" --host="https://cpkg.tesseslanguage.com/"
cd ../lib
crosslang upload-package --token="${{ secrets.CPKG_KEY }}" --host="https://cpkg.tesseslanguage.com/"
cd ../template
crosslang upload-package --token="${{ secrets.CPKG_KEY }}" --host="https://cpkg.tesseslanguage.com/"
cd ../tool
crosslang upload-package --token="${{ secrets.CPKG_KEY }}" --host="https://cpkg.tesseslanguage.com/"
cd ../web
crosslang upload-package --token="${{ secrets.CPKG_KEY }}" --host="https://cpkg.tesseslanguage.com/"
cd ../webapp
crosslang upload-package --token="${{ secrets.CPKG_KEY }}" --host="https://cpkg.tesseslanguage.com/"
cd ../npmweb
crosslang upload-package --token="${{ secrets.CPKG_KEY }}" --host="https://cpkg.tesseslanguage.com/"
cd ../npmwebapp
crosslang upload-package --token="${{ secrets.CPKG_KEY }}" --host="https://cpkg.tesseslanguage.com/"
- name: "Publish artifacts"
uses: akkuman/gitea-release-action@v1
env:
NODE_OPTIONS: '--experimental-fetch' # if nodejs < 18
with:
prerelease: true
files: |-
artifacts/**
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to registry
uses: docker/login-action@v3
with:
registry: ${{ env.GITEA_DOMAIN }}
username: ${{ env.GITEA_REGISTRY_USER }}
password: ${{ secrets.PACKAGE_AND_BREW }}
- name: Build and push image
uses: docker/build-push-action@v6
with:
context: .
file: ./Dockerfile
push: true
tags: ${{ env.GITEA_DOMAIN }}/${{ env.CLWS_PACKAGE }}:latest
- name: Build and push image
uses: docker/build-push-action@v6
with:
context: .
file: ./Dockerfile.cpkg
push: true
tags: ${{ env.GITEA_DOMAIN }}/${{ env.CPKG_PACKAGE }}:latest

4
.gitignore vendored
View File

@@ -8,3 +8,7 @@ tmp
vscode-extension/out
vscode-extension/package-lock.json
vscode-extension/node-modules
Packages
Tesses.CrossLang.PackageServer/conf.json
Tesses.CrossLang.PackageServer/data.db
Temp

View File

@@ -47,18 +47,6 @@ jobs:
artifacts: Tesses.CrossLang.PackageServer-*.crvm
condition: SUCCESSFUL
optional: false
- type: BuildImageStep
name: Build container
dockerfile: Dockerfile
output:
type: RegistryOutput
tags: onedev.site.tesses.net/crosslang/crosslangextras/crosslangextras:latest
registryLogins:
- registryUrl: '@server_url@'
userName: '@job_token@'
passwordSecret: dockersecret
condition: SUCCESSFUL
optional: false
- type: SCPCommandStep
name: Copy Shell Package
privateKeySecret: tesses_www_private_key
@@ -66,18 +54,6 @@ jobs:
target: mike@@10.137.42.28:/var/www/downloads/ShellPackage.crvm
condition: SUCCESSFUL
optional: false
- type: BuildImageStep
name: Build container (package server)
dockerfile: Dockerfile.packageserver
output:
type: RegistryOutput
tags: onedev.site.tesses.net/crosslang/crosslangextras/packageserver:latest
registryLogins:
- registryUrl: '@server_url@'
userName: '@job_token@'
passwordSecret: dockersecret
condition: SUCCESSFUL
optional: false
- type: CommandStep
name: Push packages to CPKG
runInContainer: true
@@ -117,6 +93,30 @@ jobs:
useTTY: true
condition: SUCCESSFUL
optional: false
- type: BuildImageStep
name: Build container
dockerfile: Dockerfile
output:
type: RegistryOutput
tags: onedev.site.tesses.net/crosslang/crosslangextras/crosslangextras:latest
registryLogins:
- registryUrl: '@server_url@'
userName: '@job_token@'
passwordSecret: dockersecret
condition: SUCCESSFUL
optional: false
- type: BuildImageStep
name: Build container (package server)
dockerfile: Dockerfile.packageserver
output:
type: RegistryOutput
tags: onedev.site.tesses.net/crosslang/crosslangextras/packageserver:latest
registryLogins:
- registryUrl: '@server_url@'
userName: '@job_token@'
passwordSecret: dockersecret
condition: SUCCESSFUL
optional: false
- type: CommandStep
name: push to coolify
runInContainer: true
@@ -124,7 +124,7 @@ jobs:
interpreter:
type: DefaultInterpreter
commands: |
curl "$COOLIFY_WEBHOOK" -H "Authorization: Bearer $COOLIFY_TOKEN"
curl "$COOLIFY_WEBHOOK" -H "Authorization: Bearer $COOLIFY_TOKEN" > /dev/null
envVars:
- name: COOLIFY_WEBHOOK
value: '@secret:COOLIFY_WEBHOOK@'

10
Changelog.md Normal file
View File

@@ -0,0 +1,10 @@
## Changelog
## v0.0.2
Add the templates for npm
## v0.0.1
Make CPKG more complete
## v0.0.0
Migrate to Gitea

View File

@@ -1,5 +1,3 @@
FROM onedev.site.tesses.net/crosslang/crosslang:latest
FROM git.tesses.org/tesses50/crosslang:latest
COPY ./Tesses.CrossLang.ShellPackage-1.0.0.0-prod.crvm /root/Tesses.CrossLang.ShellPackage-1.0.0.0-prod.crvm
RUN crossarchiveextract /root/Tesses.CrossLang.ShellPackage-1.0.0.0-prod.crvm /root/.config/Tesses/CrossLang
RUN crosslang update-shell

6
Dockerfile.cpkg Normal file
View File

@@ -0,0 +1,6 @@
FROM git.tesses.org/tesses50/crosslang:latest
WORKDIR /app
COPY ./Tesses.CrossLang.PackageServer/bin/Tesses.CrossLang.PackageServer-1.0.0.0-dev.crvm /app/Tesses.CrossLang.PackageServer-1.0.0.0-dev.crvm
EXPOSE 4206
ENTRYPOINT ["crossvm","Tesses.CrossLang.PackageServer-1.0.0.0-dev.crvm","/data"]

View File

@@ -1,6 +0,0 @@
FROM onedev.site.tesses.net/crosslang/crosslang:latest
WORKDIR /app
COPY ./Tesses.CrossLang.PackageServer/bin/Tesses.CrossLang.PackageServer-1.0.0.0-prod.crvm /app/Tesses.CrossLang.PackageServer-1.0.0.0-prod.crvm
EXPOSE 4206
ENTRYPOINT ["crossvm","Tesses.CrossLang.PackageServer-1.0.0.0-prod.crvm","/data"]

View File

@@ -1,8 +1,8 @@
# CrossLang Essentials
> :warning: **NOT READY FOR PRODUCTION**
> :warning: **NOT READY FOR PRODUCTION, BECAUSE IT MAY (WILL) HAVE BREAKING CHANGES**
[CrossLang](https://gitea.site.tesses.net/tesses50/crosslang) is required to build this
[CrossLang](https://git.tesses.org/tesses50/crosslang) is required to build this
# To Build
```bash

5
Templates/compiletool/.gitignore vendored Normal file
View File

@@ -0,0 +1,5 @@
.DS_Store
bin
obj
publish
thumbs.db

View File

@@ -1,10 +1,10 @@
{
"name": "Tesses.CrossLang.Template.CompileTool",
"version": "1.0.0.0-prod",
"version": "1.0.0.0-dev",
"info": {
"maintainer": "Mike Nolan",
"type": "template",
"repo": "https://onedev.site.tesses.net/CrossLang/CrossLangExtras",
"repo": "https://git.tesses.org/tesses50/crosslangextras",
"homepage": "https://crosslang.tesseslanguage.com/",
"license": "MIT",
"short_name": "compiletool",

5
Templates/console/.gitignore vendored Normal file
View File

@@ -0,0 +1,5 @@
.DS_Store
bin
obj
publish
thumbs.db

View File

@@ -1,10 +1,10 @@
{
"name": "Tesses.CrossLang.Template.Console",
"version": "1.0.0.0-prod",
"version": "1.0.0.0-dev",
"info": {
"maintainer": "Mike Nolan",
"type": "template",
"repo": "https://onedev.site.tesses.net/CrossLang/CrossLangExtras",
"repo": "https://git.tesses.org/tesses50/crosslangextras",
"homepage": "https://crosslang.tesseslanguage.com/",
"license": "MIT",
"short_name": "console",

5
Templates/emptyweb/.gitignore vendored Normal file
View File

@@ -0,0 +1,5 @@
.DS_Store
bin
obj
publish
thumbs.db

View File

@@ -1,10 +1,10 @@
{
"name": "Tesses.CrossLang.Template.EmptyWebsite",
"version": "1.0.0.0-prod",
"version": "1.0.0.0-dev",
"info": {
"maintainer": "Mike Nolan",
"type": "template",
"repo": "https://onedev.site.tesses.net/CrossLang/CrossLangExtras",
"repo": "https://git.tesses.org/tesses50/crosslangextras",
"homepage": "https://crosslang.tesseslanguage.com/",
"license": "MIT",
"short_name": "emptyweb",

5
Templates/lib/.gitignore vendored Normal file
View File

@@ -0,0 +1,5 @@
.DS_Store
bin
obj
publish
thumbs.db

View File

@@ -1,10 +1,10 @@
{
"name": "Tesses.CrossLang.Template.Library",
"version": "1.0.0.0-prod",
"version": "1.0.0.0-dev",
"info": {
"maintainer": "Mike Nolan",
"type": "template",
"repo": "https://onedev.site.tesses.net/CrossLang/CrossLangExtras",
"repo": "https://git.tesses.org/tesses50/crosslangextras",
"homepage": "https://crosslang.tesseslanguage.com/",
"license": "MIT",
"short_name": "lib",

View File

@@ -0,0 +1,8 @@
.DS_Store
bin
obj
publish
npm/node_modules
npm/package-lock.json
npm/dist
thumbs.db

8
Templates/npmweb/.gitignore vendored Normal file
View File

@@ -0,0 +1,8 @@
.DS_Store
bin
obj
publish
npm/node_modules
npm/package-lock.json
npm/dist
thumbs.db

View File

@@ -0,0 +1,41 @@
{
"$schema": "https:\/\/crosslang.tesseslanguage.com\/\/schema\/cross-json-schema.json",
"info": {
"template_ignored_files": [
".DS_Store",
"bin",
"obj",
"publish",
"npm/node_modules",
"npm/package-lock.json",
"npm/dist",
"thumbs.db"
],
"short_name_pretty": "NPM WebSite",
"maintainer": "Mike Nolan",
"type": "template",
"repo": "https://git.tesses.org/tesses50/crosslangextras",
"homepage": "https://crosslang.tesseslanguage.com/",
"license": "MIT",
"short_name": "npmweb",
"description": "A NPM Website Template with BeerCSS and HTMX",
"template_info": {
"type": "console"
},
"template_prebuild": [
{
"workdir": "npm",
"commands": [
["npm","install"],
["npm","run","publish"]
],
"res": [
"npm/dist"
]
}
]
},
"name": "Tesses.CrossLang.Template.NPMWebsite",
"version": "1.0.0.0-dev"
}

View File

@@ -0,0 +1,12 @@
{
"dependencies": {
"beercss": "^4.0.21",
"htmx.org": "^2.0.10"
},
"devDependencies": {
"esbuild": "^0.28.0"
},
"scripts": {
"publish": "esbuild --minify --sourcemap --define:'process.env.NODE_ENV=\"production\"' --bundle --outdir=dist web.mjs --loader:.svg=file --loader:.woff2=file"
}
}

View File

@@ -0,0 +1,78 @@
:root,
body.light {
--primary:#855400;
--on-primary:#ffffff;
--primary-container:#ffddb7;
--on-primary-container:#2a1700;
--secondary:#705b41;
--on-secondary:#ffffff;
--secondary-container:#fcdebc;
--on-secondary-container:#281805;
--tertiary:#53643e;
--on-tertiary:#ffffff;
--tertiary-container:#d6e9b9;
--on-tertiary-container:#121f03;
--error:#ba1a1a;
--on-error:#ffffff;
--error-container:#ffdad6;
--on-error-container:#410002;
--background:#fffbff;
--on-background:#1f1b16;
--surface:#fff8f4;
--on-surface:#1f1b16;
--surface-variant:#f0e0d0;
--on-surface-variant:#504539;
--outline:#827568;
--outline-variant:#d4c4b5;
--shadow:#000000;
--scrim:#000000;
--inverse-surface:#352f2a;
--inverse-on-surface:#f9efe7;
--inverse-primary:#ffb95c;
--surface-dim:#e2d8d1;
--surface-bright:#fff8f4;
--surface-container-lowest:#ffffff;
--surface-container-low:#fcf2ea;
--surface-container:#f6ece4;
--surface-container-high:#f0e6de;
--surface-container-highest:#ebe1d9;
}
body.dark {
--primary:#ffb95c;
--on-primary:#462a00;
--primary-container:#653e00;
--on-primary-container:#ffddb7;
--secondary:#dfc2a2;
--on-secondary:#3f2d17;
--secondary-container:#57432b;
--on-secondary-container:#fcdebc;
--tertiary:#bacd9f;
--on-tertiary:#263514;
--tertiary-container:#3c4c28;
--on-tertiary-container:#d6e9b9;
--error:#ffb4ab;
--on-error:#690005;
--error-container:#93000a;
--on-error-container:#ffb4ab;
--background:#1f1b16;
--on-background:#ebe1d9;
--surface:#17130e;
--on-surface:#ebe1d9;
--surface-variant:#504539;
--on-surface-variant:#d4c4b5;
--outline:#9c8e80;
--outline-variant:#504539;
--shadow:#000000;
--scrim:#000000;
--inverse-surface:#ebe1d9;
--inverse-on-surface:#352f2a;
--inverse-primary:#855400;
--surface-dim:#17130e;
--surface-bright:#3e3833;
--surface-container-lowest:#110e09;
--surface-container-low:#1f1b16;
--surface-container:#231f1a;
--surface-container-high:#2e2924;
--surface-container-highest:#39342f;
}

View File

@@ -0,0 +1,3 @@
import 'beercss'
import 'htmx.org'
import './web.css'

View File

@@ -0,0 +1,78 @@
func Components.Shell(title,pages,body)
{
<return>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="/dist/web.css">
<title>%PROJECT_NAME% - {title}</title>
<script src="/dist/web.js" defer></script>
</head>
<body>
<dialog class="left" id="leftpane">
<header>
<nav>
<img class="circle large" src="/tytd-128.png">
<h6 class="max">%PROJECT_NAME%</h6>
<button hx-on:click="ui('#leftpane')" class="transparent circle large">
<i>close</i>
</button>
</nav>
</header>
<div class="space"></div>
<ul class="list">
<each(var item : pages)>
<if(item.active)>
<true>
<li class="wave round primary" hx-target="body" hx-push-url="true" hx-get={item.link}>
<i>{item.icon}</i>
<span class="max">{item.text}</span>
<b></b>
</li>
</true>
<false>
<li class="wave round" hx-target="body" hx-push-url="true" hx-get={item.route}>
<i>{item.icon}</i>
<span class="max">{item.text}</span>
<b></b>
</li>
</false>
</if>
</each>
</ul>
</dialog>
<header class="fixed primary-container">
<nav>
<button hx-on:click="ui('#leftpane')" class="circle transparent">
<i>menu</i>
</button>
<button hx-target="body" hx-push-url="true" hx-get="/" class="circle transparent">
<i>home</i>
</button>
<div class="max"></div>
<div>
<button class="circle transparent">
<i>more_vert</i>
</button>
<menu class="left no-wrap">
<li><a href="https://beercss.com/">BeerCSS</a></li>
</menu>
</div>
</nav>
<div class="small-padding">
<h5>%PROJECT_NAME%</h5>
</div>
</header>
<main class="responsive">
<raw(body)>
</main>
</body>
</html>
</return>
}

View File

@@ -0,0 +1,33 @@
func Pages.About(ctx)
{
var pages = [
{
active = false,
route = "/",
text = "Home",
icon = "home"
},
{
active = false,
route = "/counter",
text = "Counter",
icon = "exposure_plus_1"
},
{
active = true,
route = "/about",
text = "About",
icon = "info"
}
];
ctx.WithMimeType("text/html").SendText(Components.Shell("About Me",pages,<section>
<h1>About Me</h1>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
</p>
</section>
));
return true;
}

View File

@@ -0,0 +1,30 @@
var counter = 0;
func Pages.Counter(ctx)
{
var pages = [
{
active = false,
route = "/",
text = "Home",
icon = "home"
},
{
active = true,
route = "/counter",
text = "Counter",
icon = "exposure_plus_1"
},
{
active = false,
route = "/about",
text = "About",
icon = "info"
}
];
ctx.WithMimeType("text/html").SendText(Components.Shell("Counter",pages,<section>
<button hx-get="./counter" hx-target="body" hx-push-url="true">Counter is {++counter}</button>
</section>
));
return true;
}

View File

@@ -0,0 +1,35 @@
func Pages.Echo(ctx)
{
const text = ctx.QueryParams.TryGetFirst("text");
var pages = [
{
active = true,
route = "/",
text = "Home",
icon = "home"
},
{
active = false,
route = "/counter",
text = "Counter",
icon = "exposure_plus_1"
},
{
active = false,
route = "/about",
text = "About",
icon = "info"
}
];
ctx.WithMimeType("text/html").SendText(Components.Shell("Echo",pages,<section>
<if(text != null)>
<true>
<plink(text)>
</true>
<false>
<p>No text available</p>
</false>
</if>
</section>));
return true;
}

View File

@@ -0,0 +1,42 @@
func Pages.Index(ctx)
{
var pages = [
{
active = true,
route = "/",
text = "Home",
icon = "home"
},
{
active = false,
route = "/counter",
text = "Counter",
icon = "exposure_plus_1"
},
{
active = false,
route = "/about",
text = "About",
icon = "info"
}
];
ctx.WithMimeType("text/html").SendText(Components.Shell("Main Page",pages,<section>
<form hx-get="./echo" hx-target="body" hx-push-url="true" action="./echo" method="GET">
<div class="row">
<div class="max">
<div class="field label border">
<input type="text" name="text">
<label>Text to echo</label>
</div>
</div>
<div class="min">
<button type="submit">
Echo
</button>
</div>
</div>
</form>
<p>1 John 4:4: You, dear children, are from God and have overcome them, because the one who is in you is greater than the one who is in the world.</p>
</section>));
return true;
}

View File

@@ -0,0 +1,40 @@
class MyWebApp {
private fileServer;
private mountable;
private pages;
public MyWebApp()
{
this.fileServer = new FileServer(embeddir("/"), true, false);
this.mountable = new MountableServer((ctx)=>{
const page = this.pages.[ctx.Path];
if(TypeIsDefined(page)) return page(ctx);
return false;
});
this.mountable.Mount("/dist/",this.fileServer);
this.pages = {
.["/"] = Pages.Index,
.["/counter"] = Pages.Counter,
.["/about"] = Pages.About,
.["/echo"] = Pages.Echo
};
}
public Handle(ctx)
{
return this.mountable.Handle(ctx);
}
public Close()
{
this.mountable = null;
}
}
func main(args)
{
const webApp=new MyWebApp();
Net.Http.ListenSimpleWithLoop(webApp,4206);
webApp.Close();
}

View File

@@ -0,0 +1,8 @@
.DS_Store
bin
obj
publish
npm/node_modules
npm/package-lock.json
npm/dist
thumbs.db

8
Templates/npmwebapp/.gitignore vendored Normal file
View File

@@ -0,0 +1,8 @@
.DS_Store
bin
obj
publish
npm/node_modules
npm/package-lock.json
npm/dist
thumbs.db

View File

@@ -0,0 +1,43 @@
{
"$schema": "https:\/\/crosslang.tesseslanguage.com\/\/schema\/cross-json-schema.json",
"info": {
"template_ignored_files": [
".DS_Store",
"bin",
"obj",
"publish",
"npm/node_modules",
"npm/package-lock.json",
"npm/dist",
"thumbs.db"
],
"short_name_pretty": "NPM WebApp",
"maintainer": "Mike Nolan",
"type": "template",
"repo": "https://git.tesses.org/tesses50/crosslangextras",
"homepage": "https://crosslang.tesseslanguage.com/",
"license": "MIT",
"short_name": "npmwebapp",
"description": "A NPM WebApp Template with BeerCSS and HTMX",
"template_info": {
"type": "webapp",
"short_name": "changeme",
"short_name_pretty": "Change Me"
},
"template_prebuild": [
{
"workdir": "npm",
"commands": [
["npm","install"],
["npm","run","publish"]
],
"res": [
"npm/dist"
]
}
]
},
"name": "Tesses.CrossLang.Template.NPMWebApp",
"version": "1.0.0.0-dev"
}

View File

@@ -0,0 +1,12 @@
{
"dependencies": {
"beercss": "^4.0.21",
"htmx.org": "^2.0.10"
},
"devDependencies": {
"esbuild": "^0.28.0"
},
"scripts": {
"publish": "esbuild --minify --sourcemap --define:'process.env.NODE_ENV=\"production\"' --bundle --outdir=dist web.mjs --loader:.svg=file --loader:.woff2=file"
}
}

View File

@@ -0,0 +1,78 @@
:root,
body.light {
--primary:#855400;
--on-primary:#ffffff;
--primary-container:#ffddb7;
--on-primary-container:#2a1700;
--secondary:#705b41;
--on-secondary:#ffffff;
--secondary-container:#fcdebc;
--on-secondary-container:#281805;
--tertiary:#53643e;
--on-tertiary:#ffffff;
--tertiary-container:#d6e9b9;
--on-tertiary-container:#121f03;
--error:#ba1a1a;
--on-error:#ffffff;
--error-container:#ffdad6;
--on-error-container:#410002;
--background:#fffbff;
--on-background:#1f1b16;
--surface:#fff8f4;
--on-surface:#1f1b16;
--surface-variant:#f0e0d0;
--on-surface-variant:#504539;
--outline:#827568;
--outline-variant:#d4c4b5;
--shadow:#000000;
--scrim:#000000;
--inverse-surface:#352f2a;
--inverse-on-surface:#f9efe7;
--inverse-primary:#ffb95c;
--surface-dim:#e2d8d1;
--surface-bright:#fff8f4;
--surface-container-lowest:#ffffff;
--surface-container-low:#fcf2ea;
--surface-container:#f6ece4;
--surface-container-high:#f0e6de;
--surface-container-highest:#ebe1d9;
}
body.dark {
--primary:#ffb95c;
--on-primary:#462a00;
--primary-container:#653e00;
--on-primary-container:#ffddb7;
--secondary:#dfc2a2;
--on-secondary:#3f2d17;
--secondary-container:#57432b;
--on-secondary-container:#fcdebc;
--tertiary:#bacd9f;
--on-tertiary:#263514;
--tertiary-container:#3c4c28;
--on-tertiary-container:#d6e9b9;
--error:#ffb4ab;
--on-error:#690005;
--error-container:#93000a;
--on-error-container:#ffb4ab;
--background:#1f1b16;
--on-background:#ebe1d9;
--surface:#17130e;
--on-surface:#ebe1d9;
--surface-variant:#504539;
--on-surface-variant:#d4c4b5;
--outline:#9c8e80;
--outline-variant:#504539;
--shadow:#000000;
--scrim:#000000;
--inverse-surface:#ebe1d9;
--inverse-on-surface:#352f2a;
--inverse-primary:#855400;
--surface-dim:#17130e;
--surface-bright:#3e3833;
--surface-container-lowest:#110e09;
--surface-container-low:#1f1b16;
--surface-container:#231f1a;
--surface-container-high:#2e2924;
--surface-container-highest:#39342f;
}

View File

@@ -0,0 +1,3 @@
import 'beercss'
import 'htmx.org'
import './web.css'

View File

@@ -0,0 +1,78 @@
func Components.Shell(title,pages,body)
{
<return>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="/dist/web.css">
<title>%PROJECT_NAME% - {title}</title>
<script src="/dist/web.js" defer></script>
</head>
<body>
<dialog class="left" id="leftpane">
<header>
<nav>
<img class="circle large" src="/tytd-128.png">
<h6 class="max">%PROJECT_NAME%</h6>
<button hx-on:click="ui('#leftpane')" class="transparent circle large">
<i>close</i>
</button>
</nav>
</header>
<div class="space"></div>
<ul class="list">
<each(var item : pages)>
<if(item.active)>
<true>
<li class="wave round primary" hx-target="body" hx-push-url="true" hx-get={item.link}>
<i>{item.icon}</i>
<span class="max">{item.text}</span>
<b></b>
</li>
</true>
<false>
<li class="wave round" hx-target="body" hx-push-url="true" hx-get={item.route}>
<i>{item.icon}</i>
<span class="max">{item.text}</span>
<b></b>
</li>
</false>
</if>
</each>
</ul>
</dialog>
<header class="fixed primary-container">
<nav>
<button hx-on:click="ui('#leftpane')" class="circle transparent">
<i>menu</i>
</button>
<button hx-target="body" hx-push-url="true" hx-get="/" class="circle transparent">
<i>home</i>
</button>
<div class="max"></div>
<div>
<button class="circle transparent">
<i>more_vert</i>
</button>
<menu class="left no-wrap">
<li><a href="https://beercss.com/">BeerCSS</a></li>
</menu>
</div>
</nav>
<div class="small-padding">
<h5>%PROJECT_NAME%</h5>
</div>
</header>
<main class="responsive">
<raw(body)>
</main>
</body>
</html>
</return>
}

View File

@@ -0,0 +1,33 @@
func Pages.About(ctx)
{
var pages = [
{
active = false,
route = "/",
text = "Home",
icon = "home"
},
{
active = false,
route = "/counter",
text = "Counter",
icon = "exposure_plus_1"
},
{
active = true,
route = "/about",
text = "About",
icon = "info"
}
];
ctx.WithMimeType("text/html").SendText(Components.Shell("About Me",pages,<section>
<h1>About Me</h1>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
</p>
</section>
));
return true;
}

View File

@@ -0,0 +1,30 @@
var counter = 0;
func Pages.Counter(ctx)
{
var pages = [
{
active = false,
route = "/",
text = "Home",
icon = "home"
},
{
active = true,
route = "/counter",
text = "Counter",
icon = "exposure_plus_1"
},
{
active = false,
route = "/about",
text = "About",
icon = "info"
}
];
ctx.WithMimeType("text/html").SendText(Components.Shell("Counter",pages,<section>
<button hx-get="./counter" hx-target="body" hx-push-url="true">Counter is {++counter}</button>
</section>
));
return true;
}

View File

@@ -0,0 +1,35 @@
func Pages.Echo(ctx)
{
const text = ctx.QueryParams.TryGetFirst("text");
var pages = [
{
active = true,
route = "/",
text = "Home",
icon = "home"
},
{
active = false,
route = "/counter",
text = "Counter",
icon = "exposure_plus_1"
},
{
active = false,
route = "/about",
text = "About",
icon = "info"
}
];
ctx.WithMimeType("text/html").SendText(Components.Shell("Echo",pages,<section>
<if(text != null)>
<true>
<plink(text)>
</true>
<false>
<p>No text available</p>
</false>
</if>
</section>));
return true;
}

View File

@@ -0,0 +1,42 @@
func Pages.Index(ctx)
{
var pages = [
{
active = true,
route = "/",
text = "Home",
icon = "home"
},
{
active = false,
route = "/counter",
text = "Counter",
icon = "exposure_plus_1"
},
{
active = false,
route = "/about",
text = "About",
icon = "info"
}
];
ctx.WithMimeType("text/html").SendText(Components.Shell("Main Page",pages,<section>
<form hx-get="./echo" hx-target="body" hx-push-url="true" action="./echo" method="GET">
<div class="row">
<div class="max">
<div class="field label border">
<input type="text" name="text">
<label>Text to echo</label>
</div>
</div>
<div class="min">
<button type="submit">
Echo
</button>
</div>
</div>
</form>
<p>1 John 4:4: You, dear children, are from God and have overcome them, because the one who is in you is greater than the one who is in the world.</p>
</section>));
return true;
}

View File

@@ -0,0 +1,37 @@
class MyWebApp {
private fileServer;
private mountable;
private pages;
public MyWebApp()
{
this.fileServer = new FileServer(embeddir("/"), true, false);
this.mountable = new MountableServer((ctx)=>{
const page = this.pages.[ctx.Path];
if(TypeIsDefined(page)) return page(ctx);
return false;
});
this.mountable.Mount("/dist/",this.fileServer);
this.pages = {
.["/"] = Pages.Index,
.["/counter"] = Pages.Counter,
.["/about"] = Pages.About,
.["/echo"] = Pages.Echo
};
}
public Handle(ctx)
{
return this.mountable.Handle(ctx);
}
public Close()
{
this.mountable = null;
}
}
func WebAppMain(args)
{
return new MyWebApp();
}

5
Templates/template/.gitignore vendored Normal file
View File

@@ -0,0 +1,5 @@
.DS_Store
bin
obj
publish
thumbs.db

View File

@@ -1,10 +1,10 @@
{
"name": "Tesses.CrossLang.Template.Template",
"version": "1.0.0.0-prod",
"version": "1.0.0.0-dev",
"info": {
"maintainer": "Mike Nolan",
"type": "template",
"repo": "https://onedev.site.tesses.net/CrossLang/CrossLangExtras",
"repo": "https://git.tesses.org/tesses50/crosslangextras",
"homepage": "https://crosslang.tesseslanguage.com/",
"license": "MIT",
"short_name": "template",

5
Templates/tool/.gitignore vendored Normal file
View File

@@ -0,0 +1,5 @@
.DS_Store
bin
obj
publish
thumbs.db

View File

@@ -17,12 +17,12 @@
"description": "A crosslang tool that you can run via crosslang tool",
"type": "template",
"maintainer": "Mike Nolan",
"repo": "https://onedev.site.tesses.net/CrossLang/CrossLangExtras",
"repo": "https://git.tesses.org/tesses50/crosslangextras",
"homepage": "https://crosslang.tesseslanguage.com/",
"license": "MIT",
"short_name_pretty": "Tool"
},
"version": "1.0.0.0-prod",
"version": "1.0.0.0-dev",
"name": "Tesses.CrossLang.Template.Tool",
"$schema": "https://crosslang.tesseslanguage.com//schema/cross-json-schema.json"
}

5
Templates/web/.gitignore vendored Normal file
View File

@@ -0,0 +1,5 @@
.DS_Store
bin
obj
publish
thumbs.db

View File

@@ -1,10 +1,10 @@
{
"name": "Tesses.CrossLang.Template.Website",
"version": "1.0.0.0-prod",
"version": "1.0.0.0-dev",
"info": {
"maintainer": "Mike Nolan",
"type": "template",
"repo": "https://onedev.site.tesses.net/CrossLang/CrossLangExtras",
"repo": "https://git.tesses.org/tesses50/crosslangextras",
"homepage": "https://crosslang.tesseslanguage.com/",
"license": "MIT",
"short_name": "web",

5
Templates/webapp/.gitignore vendored Normal file
View File

@@ -0,0 +1,5 @@
.DS_Store
bin
obj
publish
thumbs.db

View File

@@ -15,12 +15,12 @@
"short_name_pretty": "Web Application",
"template_project_dependencies": [
],
"repo": "https://onedev.site.tesses.net/CrossLang/CrossLangExtras",
"repo": "https://git.tesses.org/tesses50/crosslangextras",
"homepage": "https://crosslang.tesseslanguage.com/",
"type": "template",
"description": "A web app (for webview or eventual cloud OS)"
},
"name": "Tesses.CrossLang.Template.WebApp",
"version": "1.0.0.0-prod",
"version": "1.0.0.0-dev",
"$schema": "https://crosslang.tesseslanguage.com//schema/cross-json-schema.json"
}

View File

@@ -11,5 +11,5 @@
"project_dependencies": [
"..\/Tesses.CrossLang.BuildEssentials"
],
"version": "1.0.0.0-prod"
"version": "1.0.0.0-dev"
}

View File

@@ -1,10 +1,10 @@
{
"name": "Tesses.CrossLang.Args",
"version": "1.0.0.0-prod",
"version": "1.0.0.0-dev",
"info": {
"maintainer": "Mike Nolan",
"type": "lib",
"repo": "https://onedev.site.tesses.net/CrossLang/CrossLangExtras",
"repo": "https://git.tesses.org/tesses50/crosslangextras",
"homepage": "https://crosslang.tesseslanguage.com/",
"license": "LGPLv3"
}

View File

@@ -1,10 +1,10 @@
{
"name": "Tesses.CrossLang.BuildEssentials",
"version": "1.0.0.0-prod",
"version": "1.0.0.0-dev",
"info": {
"maintainer": "Mike Nolan",
"type": "lib",
"repo": "https://onedev.site.tesses.net/CrossLang/CrossLangExtras",
"repo": "https://git.tesses.org/tesses50/crosslangextras",
"homepage": "https://crosslang.tesseslanguage.com/",
"license": "LGPLv3"
}

View File

@@ -208,7 +208,7 @@ class Tesses.CrossLang.PackageManager
{
each(var dep : _vm.Dependencies)
{
var path = /short_name/$"{dep.Name}-{dep.Version.ToString()}";
var path = /short_name/$"{dep.Name}-{dep.Version.ToString()}.crvm";
if(!dirFs.RegularFileExists(path))
{
var pkg2 = this.GetPackage(dep.Name,dep.Version.ToString());

View File

@@ -113,7 +113,8 @@ class Tesses.CrossLang.BuildTool
if(TypeOf(configData.info) != "Undefined")
info = configData.info;
if(TypeOf(configData.icon) != "Undefined")
icon = (resDir/configData.icon).ToString();
icon = configData.icon;
@@ -121,7 +122,8 @@ class Tesses.CrossLang.BuildTool
if(TypeOf(info.type) == "String" && (info.type == "template" || info.type == "archive"))
{
//vfs, strm, name, version, info, icon
//vfs, strm, name, version, info
var subdir = new SubdirFilesystem(FS.Local,dir);
var output = $"{name}-{version}.crvm";
var outFile = FS.Local.OpenFile(dir / outputDir / output,"wb");
@@ -135,7 +137,7 @@ class Tesses.CrossLang.BuildTool
}
FS.WriteAllText(FS.Local, dir / ".crossarchiveignore", ignored);
}
FS.CreateArchive(subdir, outFile, name, version, Json.Encode(info),icon);
FS.CreateArchive(subdir, outFile, name, version, Json.Encode(info),icon == "" ? "" : (/resDir/icon).ToString());
outFile.Close();
subdir.Close();
return {
@@ -148,9 +150,14 @@ class Tesses.CrossLang.BuildTool
}
FS.Local.CreateDirectory(dir / objDir / "packages");
FS.Local.CreateDirectory(dir / objDir / "res");
FS.Local.CreateDirectory(dir/resDir);
const svfs = new Tesses.CrossLang.SharedViewFilesystem();
svfs.AddFS(new SubdirFilesystem(FS.Local, dir / resDir));
svfs.AddFS(new SubdirFilesystem(FS.Local, dir / objDir / "res"));
var dependencies = [];
if(TypeOf(configData.project_dependencies) == "List")
{
@@ -200,7 +207,9 @@ class Tesses.CrossLang.BuildTool
env.LoadFileWithDependencies(FS.Local,newFile);
env.GetDictionary().RunTool({
ProjectDirectory = dir,
Project = new SubdirFilesystem(FS.Local, dir),
ProjectJson = configData,
ProjectInfo = info,
GeneratedSource = sources,
Config = this.Config
@@ -219,6 +228,62 @@ class Tesses.CrossLang.BuildTool
var file_deps = [];
var file_tools = [];
if(TypeIsList(configData.prebuild))
{
//do the prebuild things
each(var job : configData.prebuild)
{
if(TypeIsDictionary(job))
{
if(TypeIsString(job.condition))
{
if(!VM.Eval($"return {job.condition};"))
continue;
}
var workdir = dir;
if(TypeIsString(job.workdir))
workdir /= job.workdir;
if(TypeIsList(job.commands))
each(var cmd : job.commands)
{
if(TypeIsList(cmd) && cmd.Length > 0)
{
const name = cmd[0];
const path = Env.GetRealExecutablePath(name).ToString();
cmd.RemoveAt(0);
var p= Process.Start({
FileName = path,
Arguments = cmd,
WorkingDirectory = workdir
});
const result = p.Join();
if(result != 0)
throw $"process: {name}, did not indicate success: {result}";
}
}
if(TypeIsList(job.res))
{
each(var reso : job.res)
{
if(TypeIsString(reso))
{
svfs.AddFS(new SubdirFilesystem(FS.Local, dir / reso));
}
else if(TypeIsDictionary(reso) && TypeIsString(reso.src) && TypeOfString(reso.dest))
{
const mountable = new MountableFilesystem(new Filesystem({}));
mountable.Mount(reso.dest,new SubdirFilesystem(FS.Local, dir / reso.src));
svfs.AddFS(mountable);
}
}
}
}
}
}
each(var dep : dependencies)
{
if(dep.Info.type == "lib")
@@ -242,6 +307,7 @@ class Tesses.CrossLang.BuildTool
func walk_for_source(sourceDir)
{
if(FS.Local.DirectoryExists(sourceDir))
each(var file : FS.Local.EnumeratePaths(sourceDir))
{
if(FS.Local.RegularFileExists(file) && file.GetExtension()==".tcross")
@@ -354,7 +420,7 @@ class Tesses.CrossLang.BuildTool
Info = Json.Encode(info),
Icon = icon,
Tools = file_tools,
ResourceFileSystem = new SubdirFilesystem(FS.Local, dir / resDir),
ResourceFileSystem = svfs,
Dependencies = file_deps,
Output = outFile,
CompTime = compTimeEnv,
@@ -378,7 +444,6 @@ class Tesses.CrossLang.BuildTool
Path = dirStr,
Data = myData
});
return myData;
}
return null;

View File

@@ -0,0 +1,52 @@
func New.Tesses.CrossLang.SharedViewFilesystem()
{
const fileSystems = [];
func enumeratePaths (path) {
var found = [];
each(var fs : fileSystems)
{
if(fs.DirectoryExists(path))
{
each(var ent : fs.EnumeratePaths(path))
{
const name = ent.GetFileName();
if(found.Contains(name)) continue;
found.Add(name);
}
}
}
return found;
}
const fs_dict = {
OpenFile = (path, mode) => {
if(mode == "r" || mode == "rb")
{
each(var fs : fileSystems)
{
if(fs.FileExists(path))
return fs.OpenFile(path,mode);
}
}
return null;
},
EnumeratePaths = enumeratePaths,
Stat = (path) => {
each(var fs : fileSystems)
{
const st = fs.Stat(path);
if(TypeIsDictionary(st)) return st;
}
return null;
},
AddFS = (fs)=>{
fileSystems.Add(fs);
}
};
return new Filesystem(fs_dict);
}

View File

@@ -3,9 +3,13 @@
"info": {
"type": "console",
"short_name": "packageserver",
"short_name_pretty": "Package Server"
"short_name_pretty": "Package Server",
"description": "This webapp allows you to serve packages",
"repo": "https://git.tesses.org/tesses50/crosslangextras",
"homepage": "https://cpkg.tesseslanguage.com/",
"license": "AGPLv3"
},
"version": "1.0.0.0-prod",
"version": "1.0.0.0-dev",
"name": "Tesses.CrossLang.PackageServer",
"compTime": "secure"
}

View File

@@ -66,7 +66,8 @@ func DB.CreateUserFromAdmin(email, name, password, flags)
var res = {Success=true};
DB.Lock();
var dbCon = DB.Open();
exec = Sqlite.Exec(dbCon,$"SELECT * FROM accounts WHERE accountName = {Sqlite.Escape(name)};");
var 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"};
@@ -75,8 +76,8 @@ func DB.CreateUserFromAdmin(email, name, password, flags)
{
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, verifyExpire, flags) values ({Sqlite.Escape(email)},{Sqlite.Escape(name)},{Sqlite.Escape(Crypto.Base64Encode(hash))},{Sqlite.Escape(Crypto.Base64Encode(salt))},{0},{flags});");
const now = DateTime.NowEpoch ?? 0;
var r = Sqlite.Exec(dbCon,$"INSERT INTO accounts (email, accountName, password_hash, password_salt, verifyExpire, flags, created, verified) values ({Sqlite.Escape(email)},{Sqlite.Escape(name)},{Sqlite.Escape(Crypto.Base64Encode(hash))},{Sqlite.Escape(Crypto.Base64Encode(salt))},{0},{flags},{now},{now});");
if(TypeOf(r) == "String") res = {Success = false, Reason = r};
res = {Success=true};
}
@@ -87,8 +88,12 @@ func DB.CreateUserFromAdmin(email, name, password, flags)
var verify_hash = Crypto.RandomBytes(32, "CPKG");
var verify_hash_str = Crypto.Base64Encode(verify_hash);
const now = DateTime.NowEpoch ?? 0;
var r = Sqlite.Exec(dbCon,$"INSERT INTO accounts (email, accountName, password_hash, password_salt, verifyKey, verifyExpire, flags) values ({Sqlite.Escape(email)},{Sqlite.Escape(name)},{Sqlite.Escape(Crypto.Base64Encode(hash))},{Sqlite.Escape(Crypto.Base64Encode(salt))},{Sqlite.Escape(verify_hash_str)},{DateTime.NowEpoch+600},{flags});");
const resp = $"INSERT INTO accounts (email, accountName, password_hash, password_salt, verifyKey, verifyExpire, flags, created, verified) values ({Sqlite.Escape(email)},{Sqlite.Escape(name)},{Sqlite.Escape(Crypto.Base64Encode(hash))},{Sqlite.Escape(Crypto.Base64Encode(salt))},{Sqlite.Escape(verify_hash_str)},{now+600},{flags},{now},0);";
Console.WriteLine(resp);
var r = Sqlite.Exec(dbCon, resp);
Console.WriteLine(r);
if(TypeOf(r) == "String") res = {Success = false, Reason = r};
if(DB.Config.MailConfig)
@@ -111,8 +116,8 @@ func DB.CreateUser(email, name, password)
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});");
const now = DateTime.NowEpoch ?? 0;
var r = Sqlite.Exec(dbCon,$"INSERT INTO accounts (email, accountName, password_hash, password_salt, flags, created, verified) values ({Sqlite.Escape(email)},{Sqlite.Escape(name)},{Sqlite.Escape(Crypto.Base64Encode(hash))},{Sqlite.Escape(Crypto.Base64Encode(salt))},{DB.FLAG_ADMIN|DB.FLAG_VERIFIED},{now},{now});");
if(TypeOf(r) == "String") res = {Success = false, Reason = r};
}
@@ -146,8 +151,8 @@ func DB.CreateUser(email, name, password)
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, verifyExpire, flags) values ({Sqlite.Escape(email)},{Sqlite.Escape(name)},{Sqlite.Escape(Crypto.Base64Encode(hash))},{Sqlite.Escape(Crypto.Base64Encode(salt))},{Sqlite.Escape(verify_hash_str)},{DateTime.NowEpoch+600},{DB.FLAG_VERIFY});");
const now = DateTime.NowEpoch ?? 0;
var r = Sqlite.Exec(dbCon,$"INSERT INTO accounts (email, accountName, password_hash, password_salt, verifyKey, verifyExpire, flags, created, verified) values ({Sqlite.Escape(email)},{Sqlite.Escape(name)},{Sqlite.Escape(Crypto.Base64Encode(hash))},{Sqlite.Escape(Crypto.Base64Encode(salt))},{Sqlite.Escape(verify_hash_str)},{now+600},{DB.FLAG_VERIFY},{now},0);");
if(TypeOf(r) == "String") {res = {Success = false, Reason = r};}
else {
if(DB.Config.MailConfig)

View File

@@ -105,8 +105,8 @@ func DB.Init(working)
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, pluginHost TEXT);");
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, verifyExpire INTEGER, 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 accounts (id INTEGER PRIMARY KEY AUTOINCREMENT, email TEXT UNIQUE, accountName TEXT UNIQUE, password_hash TEXT, password_salt TEXT, motto TEXT, verifyKey TEXT UNIQUE, verifyExpire INTEGER, flags INTEGER, created INTEGER, verified INTEGER);");
Sqlite.Exec(dbCon,"CREATE TABLE IF NOT EXISTS sessions (id INTEGER PRIMARY KEY AUTOINCREMENT, accountId INTEGER, key STRING UNIQUE, expires INTEGER, created INTEGER, name TEXT);");
Sqlite.Exec(dbCon,"CREATE TABLE IF NOT EXISTS reserved_prefixes (id INTEGER PRIMARY KEY AUTOINCREMENT, accountId INTEGER, prefix STRING UNIQUE);");
Sqlite.Close(dbCon);
}
@@ -119,5 +119,6 @@ DB.FLAG_VERIFY = 0b00000100;
DB.ITTR = 35000;
DB.Expires = 86400*7;

View File

@@ -151,7 +151,8 @@ func DB.VerifyEmail(verifyKey)
{
flags &= ~DB.FLAG_VERIFY;
flags |= DB.FLAG_VERIFIED;
Sqlite.Exec(dbCon,$"UPDATE accounts SET flags = {flags} WHERE id = {exec[0].id};");
const now = DateTime.NowEpoch;
Sqlite.Exec(dbCon,$"UPDATE accounts SET flags = {flags} WHERE id = {exec[0].id}, verified = {now};");
Sqlite.Close(dbCon);
DB.Unlock();
return { Success=true };

View File

@@ -458,3 +458,43 @@ func DB.QueryReservedPrefixes(name)
Items = items
};
}
func DB.DownloadPackage(log,userId,server, name, version)
{
Console.WriteLine("INDLPKG");
if(!DB.CanUploadPackagePrefix(userId,name)) return;
var res = DB.PackageExists(userId, {Name = name, Version = Version.Parse(version)});
if(res == 2 || res == 4) return;
const resp=Net.Http.MakeRequest($"{server.TrimEnd('/')}/api/v1/download?name={Net.Http.UrlEncode(name)}&version={Net.Http.UrlEncode(version)}",{FollowRedirects=true});
if(resp.StatusCode >= 200 && resp.StatusCode <= 299)
{
var filePath = DB.working / "Temp" / $"{DB.GetUniqueNumber()}.crvm";
var strm = FS.Local.OpenFile(filePath,"wb");
resp.CopyToStream(strm);
strm.Close();
resp.Close();
//we need to load the exec, because the main thing erases it
strm = FS.Local.OpenFile(filePath,"rb");
const exec = VM.LoadExecutable(strm);
strm.Close();
const status = DB.UploadPackage(userId, filePath);
if(status.Success)
{
each(var item in exec.Dependencies)
{
DB.DownloadPackage(log, userId, server, item.Name, item.Version.ToString());
}
each(var item in exec.Tools)
{
DB.DownloadPackage(log, userId, server, item.Name, item.Version.ToString());
}
}
else {
log($"Failed to add package: {name} with {version}");
}
}
}

View File

@@ -4,12 +4,37 @@ func DB.GetUserIdFromSession(session)
var dbCon = DB.Open();
var exec = Sqlite.Exec(dbCon,$"SELECT * FROM sessions WHERE key = {Sqlite.Escape(session)};");
if(TypeOf(exec) == "List" && exec.Length == 1)
{
var expiry = 0;
const expires = ParseLong(exec[0].expires);
if(TypeIsLong(expires) && expires != 0)
{
const whenItExpires = ParseLong(expires);
const currentTime = DateTime.NowEpoch ?? 0;
if(whenItExpires != 0 && currentTime < whenItExpires && (whenItExpires - currentTime) < (DB.Expires-3600))
{
expiry = currentTime + DB.Expires;
Sqlite.Exec(dbCon, $"UPDATE sessions SET expires = {expiry} WHERE key = {Sqlite.Escape(session)};");
}
else if(whenItExpires != 0 && currentTime >= whenItExpires)
{
Sqlite.Exec(dbCon, $"DELETE FROM sessions WHERE key = {Sqlite.Escape(session)};");
Sqlite.Close(dbCon);
DB.Unlock();
return null;
}
if(TypeOf(exec) == "List" && exec.Length == 1) return ParseLong(exec[0].accountId);
return -1;
}
Sqlite.Close(dbCon);
DB.Unlock();
return { accountId = ParseLong(exec[0].accountId), expiry };
}
Sqlite.Close(dbCon);
DB.Unlock();
return null;
}
func DB.GetSessionFromBearer(ctx)
{
@@ -20,7 +45,7 @@ func DB.GetSessionFromBearer(ctx)
if(auth.Length < 2) return null;
if(auth[0] != "Bearer") return null;
var uid = DB.GetUserIdFromSession(auth[1]);
if(uid != -1) return auth[1];
if(TypeIsDictionary(uid)) return auth[1];
}
return null;
}
@@ -37,23 +62,34 @@ func DB.GetSession(ctx)
if(cookieKV.Length == 2 && cookieKV[0] == "Session")
{
var session = cookieKV[1];
var sessionId = DB.GetUserIdFromSession(session);
var sessionObj = DB.GetUserIdFromSession(session);
if(TypeIsDictionary(sessionObj))
{
if(sessionObj.expiry > 0)
{
ctx.WithHeader("Set-Cookie",$"Session={session}; SameSite=Lax; Expires={new DateTime(sessionObj.expiry).ToHttpDate()}");
}
if(sessionId != -1)
return session;
}
}
}
}
}
return null;
}
func DB.CreateSession(userId)
func DB.CreateSession(userId, doesExpire, name)
{
const now = DateTime.NowEpoch;
const expiryTime = doesExpire ? (DB.Expires + now) : 0;
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.Exec(dbCon,$"INSERT INTO sessions (accountId,key, expires, created, name) VALUES ({userId},{Sqlite.Escape(rand)},{Sqlite.Escape(expiryTime)},{Sqlite.Escape(now)}, {Sqlite.Escape(name)});");
Sqlite.Close(dbCon);
DB.Unlock();
return rand;

View File

@@ -81,8 +81,18 @@ func Pages.Account(ctx)
<if(TypeOf(user) == "Dictionary")>
<true>
<h1>{user.accountName}</h1>
<a href={$"./packages?account={Net.Http.UrlEncode(name)}"}>Packages</a>|<a href={$"./reserved_prefixes?name={Net.Http.UrlEncode(name)}"}>Reserved Prefixes</a>
<h6>Created: {new DateTime(ParseLong(user.created)).ToString("%Y/%m/%d %H:%M:%S UTC")}</h6>
<if(user.verified != "0")>
<true>
<h6>Verified: {new DateTime(ParseLong(user.verified)).ToString("%Y/%m/%d %H:%M:%S UTC")}</h6>
</true>
<false>
<h6>Not verified</h6>
</false>
</if>
<div>
<a href={$"./packages?account={Net.Http.UrlEncode(name)}"}>Packages</a> | <a href={$"./reserved_prefixes?name={Net.Http.UrlEncode(name)}"}>Reserved Prefixes</a>
</div>
<if(active.active)>
<true>
@@ -100,6 +110,7 @@ func Pages.Account(ctx)
<a class="btn btn-secondary" href="./admin">Admin</a>
</true>
</if>
<a class="btn btn-info" href="./sessions">Sessions</a>
<a class="btn btn-danger" href="./delete_packages">Delete Packages</a>
<a class="btn btn-danger" href="./logout">Logout</a>
</form>

View File

@@ -33,6 +33,95 @@ func Pages.Admin(ctx)
//we have authorization
switch(action2)
{
case "cache_package":
{
const userInfo = DB.GetUserIdFromSession(active.session);
const url = ctx.QueryParams.TryGetFirst("url");
if(!TypeIsString(url) || url.Length == 0 || !url.Contains("/package?name=") || !TypeIsDictionary(userInfo))
{
ctx.StatusCode = 400;
return Shell("Invalid input", pages,<h1>Invalid Input</h1>);
}
const components=url.Split("/package?name=");
if(components.Length < 2)
{
ctx.StatusCode = 400;
return Shell("Invalid input", pages,<h1>Invalid Input</h1>);
}
const strm = ctx.WithMimeType("text/html").OpenResponseStream();
const textWriter = new StreamWriter(strm);
textWriter.Write("<!DOCTYPE html><html lang=\"en\"><head><meta charset=\"UTF-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"><meta name=\"color-scheme\" content=\"dark light\"></head><body>");
var finished = false;
var errors = "";
const thread = new Thread(()=>{
func log(err)
{
errors += "{err}\n";
}
const server = components[0];
const name = Net.Http.UrlDecode(components[1]);
try {
const resp=Net.Http.MakeRequest($"{server.TrimEnd('/')}/api/v1/versions?name={Net.Http.UrlEncode(name)}",{FollowRedirects=true});
if(resp.StatusCode >= 200 && resp.StatusCode <= 299)
{
const respJson = resp.ReadAsJson();
resp.Close();
if(respJson.success)
{
if(TypeIsList(respJson.versions))
{
each(var item in respJson.versions)
{
if(TypeIsString(item.version))
{
DB.DownloadPackage(log,userInfo.accountId,server, name, item.version);
}
}
}
else {
log("versions is not list");
}
}
else
{
log("versions json does not indicate success");
}
}
else {
log($"Status code does not indicate success {resp.StatusCode} {Net.Http.StatusCodeString(resp.StatusCode)}");
}
} catch(ex) {
log(ex.ToString());
}
finished=true;
});
while(!finished)
{
textWriter.Write(".");
DateTime.Sleep(1000);
}
errors = Net.Http.HtmlEncode(errors).Replace("\n","<br>");
textWriter.Write($"{}<br>Done <a href=\"./admin\">Go Back</a></body></html>");
thread.Join();
return null;
}
break;
case "server_config":
{
var prefix = ctx.QueryParams.TryGetFirst("prefix");
@@ -129,7 +218,7 @@ func Pages.Admin(ctx)
</fieldset>
<fieldset class="border p-2">
<legend class="float-none w-auto">Accounts</legend>
<form action="./admin_account" method="get">
<form action="./admin_accounts" method="get">
<div class="mb-3">
<label for="account" class="form-label">Account Name</label>
<input class="form-control" name="account" id="account" type="text" placeholder="Account Name" aria-label="Account Name">

View File

@@ -0,0 +1,305 @@
func Pages.AdminAccount(ctx)
{
var active = DB.LoginButton(ctx,false,"");
var csrf="";
var pages = [
{
active = false,
route = "/packages",
text = "Packages"
},
{
active = false,
route = "/upload",
text = "Upload"
},
active
];
if(!active.admin) ctx.StatusCode = 401;
if(ctx.Method == "POST")
{
var csrf2 = ctx.QueryParams.TryGetFirst("csrf");
if(!active.admin) {ctx.StatusCode = 401; return Shell("Not an admin", pages,<h1>Not an admin</h1>);}
if(TypeOf(csrf2) != "String") {ctx.StatusCode = 401; return Shell("Invalid CSRF", pages,<h1>Invalid CSRF</h1>);}
if(DB.VerifyCSRF(active.session, csrf2))
{
const oldname = ctx.QueryParams.TryGetFirst("oldname");
const newname = ctx.QueryParams.TryGetFirst("newname");
const motto = ctx.QueryParams.TryGetFirst("motto") ?? "";
const admin = ctx.QueryParams.GetFirstBoolean("admin");
const verified = ctx.QueryParams.GetFirstBoolean("verified");
if(TypeIsString(oldname) && TypeIsString(newname))
{
const userInfo = DB.GetAccountInfo(oldname);
if(TypeIsDictionary(userInfo))
{
var flags = ParseLong(userInfo.flags);
//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,
//verifyExpire INTEGER, flags INTEGER, created INTEGER, verified INTEGER);
const wasVerified = (flags & DB.FLAG_VERIFIED) != 0;
if(userInfo.accountName != active.text)
{
if(!admin)
{
flags &= ~DB.FLAG_ADMIN;
}
if(!verified)
{
flags |= DB.FLAG_VERIFY;
flags &= ~DB.FLAG_VERIFIED;
}
}
if(admin)
{
flags |= DB.FLAG_ADMIN;
}
if(verified)
{
flags |= DB.FLAG_VERIFIED;
flags &= ~DB.FLAG_VERIFY;
}
DB.Lock();
const dbCon = DB.Open();
if(!wasVerified && verified)
{
Sqlite.Exec(dbCon, $"UPDATE accounts SET accountName = {Sqlite.Escape(newname)}, motto = {Sqlite.Escape(motto)}, flags = {Sqlite.Escape(flags)}, verified = {Sqlite.Escape(DateTime.NowEpoch ?? 0)} WHERE id = {Sqlite.Escape(userInfo.id)};");
}
else {
Sqlite.Exec(dbCon, $"UPDATE accounts SET accountName = {Sqlite.Escape(newname)}, motto = {Sqlite.Escape(motto)}, flags = {Sqlite.Escape(flags)} WHERE id = {Sqlite.Escape(userInfo.id)};");
}
Sqlite.Close(dbCon);
DB.Unlock();
ctx.StatusCode=303;
ctx.ResponseHeaders.SetValue("Location", "/admin_accounts");
return Shell("Redirect",pages,
<null>
<h1>Redirecting</h1>
Click <a href="/admin_accounts">here</a> if it does not redirect
</null>
);
}
}
ctx.StatusCode=400;
return Shell("Must need a user",pages,
<null>
<h1>Must need a user</h1>
Click <a href="/admin_accounts">here</a> to go back to admin list
</null>
);
}
else
{
ctx.StatusCode = 401; return Shell("Invalid CSRF", pages,<h1>Invalid CSRF</h1>);
}
}
const name = ctx.QueryParams.TryGetFirst("account");
const page = ctx.QueryParams.TryGetFirstInt("page") ?? 1;
var cur = (page - 1) % 3;
var firstPage = (page-1) - cur;
var userInfo = null;
var motto_ta = "";
const list = [];
if(active.admin && TypeIsString(name))
{
userInfo = DB.GetAccountInfo(name);
if(!TypeIsDictionary(userInfo))
{
if(TypeIsString(userInfo) && userInfo == "No such user exists")
ctx.StatusCode = 404;
else
ctx.StatusCode = 500;
}
else {
csrf = DB.CreateCSRF(ctx);
motto_ta = TypeOf(userInfo.motto) == "String" ? userInfo.motto : "";
userInfo.flags = ParseLong(userInfo.flags);
}
}
if(active.admin && !TypeIsString(name))
{
const limit = 20;
DB.Lock();
const db = DB.Open();
const res = Sqlite.Exec(db, $"SELECT * FROM accounts LIMIT {Sqlite.Escape(limit)} OFFSET {Sqlite.Escape((page-1)*limit)};");
Sqlite.Close(db);
DB.Unlock();
if(TypeIsList(res))
{
each(var item in res)
{
const flags = ParseLong(item.flags);
list.Add({
name = item.accountName,
created = new DateTime(ParseLong(item.created)).ToString("%Y/%m/%d %H:%M:%S UTC"),
verified = (flags & DB.FLAG_VERIFIED) ? (new DateTime(ParseLong(item.verified)).ToString("%Y/%m/%d %H:%M:%S UTC")) : "N/A",
admin = (flags & DB.FLAG_ADMIN) ? "Yes" : "No"
});
}
}
else {
ctx.StatusCode = 500;
return Shell("Error", pages,
<h1>Error {res}</h1>);
}
}
var html = <div class="container">
<if(active.admin)>
<true>
<if(TypeIsString(name))>
<true>
<if(TypeIsDictionary(userInfo))>
<true>
<form method="POST" action="./admin_accounts">
<input type="hidden" name="csrf" value={csrf}>
<input type="hidden" name="oldname" value={name}>
<div class="mb-3">
<label for="name" class="form-label">Name</label>
<input type="text" class="form-control" id="name" name="newname" value={name}>
</div>
<div class="mb-3">
<label for="motto" class="form-label">Motto</label>
<if(motto_ta.Length == 0)>
<true><textarea class="form-control" id="motto" name="motto" placeholder="Type your motto and/or links here" id="floatingTextarea2" style="height: 100px"></textarea></true>
<false><textarea class="form-control" id="motto" name="motto" placeholder="Type your motto and/or links here" id="floatingTextarea2" style="height: 100px">{motto_ta}</textarea></false>
</if>
</div>
<div class="form-check">
<if(userInfo.flags & DB.FLAG_VERIFIED)>
<true>
<if(userInfo.accountName == active.text)>
<true>
<input class="form-check-input" type="checkbox" name="verified" id="verified" checked="ON" disabled="ON">
</true>
<false>
<input class="form-check-input" type="checkbox" name="verified" id="verified" checked="ON">
</false>
</if>
</true>
<false>
<input class="form-check-input" type="checkbox" name="verified" id="verified">
</false>
</if>
<label class="form-check-label" for="verified">
Is Verified
</label>
</div>
<div class="form-check">
<if(userInfo.flags & DB.FLAG_ADMIN)>
<true>
<if(userInfo.accountName == active.text)>
<true>
<input class="form-check-input" type="checkbox" name="admin" id="admin" checked="ON" disabled="ON">
</true>
<false>
<input class="form-check-input" type="checkbox" name="admin" id="admin" checked="ON">
</false>
</if>
</true>
<false>
<input class="form-check-input" type="checkbox" name="admin" id="admin">
</false>
</if>
<label class="form-check-label" for="admin">
Is Admin
</label>
</div>
<button type="submit" class="btn btn-primary" name="action">Save</button>
<a href="./admin_accounts" class="btn btn-secondary">Back</a>
</form>
</true>
<false>
<h1>Error {userInfo}</h1>
</false>
</if>
</true>
<false>
<a href="./admin" class="btn btn-primary">Back To Admin</a>
<table class="table">
<thead>
<tr>
<th scope="col">Name</th>
<th scope="col">Created</th>
<th scope="col">Verified</th>
<th scope="col">Admin</th>
</tr>
</thead>
<tbody>
<each(var item : list)>
<tr>
<th scope="row"><a href={$"./admin_accounts?account={Net.Http.UrlEncode(item.name)}"}>{item.name}</a></th>
<td>{item.created}</td>
<td>{item.verified}</td>
<td>{item.admin}</td>
</tr>
</each>
</tbody>
</table>
<nav aria-label="Page navigation example">
<ul class="pagination justify-content-center">
<if(page == 1)>
<true>
<li class="page-item disabled">
<a class="page-link">Previous</a>
</li>
</true>
<false>
<li class="page-item">
<a class="page-link" href={$"./admin_accounts?page={page-1}"}>Previous</a>
</li>
</false>
</if>
<for(var i = 1; i <= 3; i++)>
<li class={i == cur + 1 ? "page-item active" : "page-item"}><a class="page-link" href={$"./admin_accounts?page={i+firstPage}"}>{i+firstPage}</a></li>
</for>
<li class="page-item">
<a class="page-link" href={$"./admin_accounts?page={page+1}"}>Next</a>
</li>
</ul>
</nav>
</false>
</if>
</true>
<false>
<h1>You are not authorized in the admin panel</h1>
</false>
</if>
</div>;
return Shell("Admin Register", pages,html);
}

View File

@@ -31,7 +31,7 @@ func Pages.AdminRegister(ctx)
var displayName = ctx.QueryParams.TryGetFirst("displayName");
var password = ctx.QueryParams.TryGetFirst("password");
var confirm = ctx.QueryParams.TryGetFirst("confirm");
var flags = ctx.QueryParams.GetFirstBoolean("verified") ? DB.FLAG_VERIFIED : DB.FLAG_FLAG_VERIFY;
var flags = ctx.QueryParams.GetFirstBoolean("verified") ? DB.FLAG_VERIFIED : DB.FLAG_VERIFY;
flags |= (ctx.QueryParams.GetFirstBoolean("admin") ? DB.FLAG_ADMIN : 0);
if(TypeOf(email) != "String" || TypeOf(displayName) != "String" || TypeOf(password) != "String" || TypeOf(confirm) != "String")

View File

@@ -14,10 +14,20 @@ func Pages.CheckEmail(ctx)
DB.LoginButton(ctx,false)
];
var html = <div class={"container"}>
<if(DB.Config.MailConfig)>
<true>
<h1>Please check your email.</h1>
<p>
The email may or may not be in your spam.
</p>
</true>
<false>
<h1>The admin will need to verify your account</h1>
<p>
Mail is disabled
</p>
</false>
</if>
</div>;
return Shell("Check your email",pages,html);
return Shell(DB.Config.MailConfig ? "Check your email" : "Admin will need to verify",pages,html);
}

View File

@@ -0,0 +1,66 @@
func Pages.CreateSession(ctx)
{
var active = DB.LoginButton(ctx,false,"");
var pages = [
{
active = false,
route = "/packages",
text = "Packages"
},
{
active = false,
route = "/upload",
text = "Upload"
},
active
];
const account = DB.GetUserIdFromSession(active.session);
if(TypeIsDictionary(account))
{
if(ctx.Method == "GET")
{
return Shell("Create API Key", pages,
<div class="container">
<h1>Create session</h1>
<form method="POST" action="./create_session">
<input type="hidden" name="csrf" value={DB.CreateCSRF(ctx)}>
<div class="mb-3">
<label for="name" class="form-label">Session Name</label>
<input type="text" class="form-control" id="name" name="name" placeholder="Session Name">
</div>
<input type="submit" class="btn btn-primary" value="Create">
</form>
</div>
);
}
else if(ctx.Method == "POST")
{
var name = ctx.QueryParams.TryGetFirst("name") ?? "CrossLang Shell";
var csrf = ctx.QueryParams.TryGetFirst("csrf");
if(!DB.VerifyCSRF(active.session,csrf))
return Shell("Invalid CSRF", pages, "<h1>Invalid CSRF</h1>");
return Shell("Your API key", pages,
<div class="container">
<h1>Your API key</h1>
<p>{"It won't be shown again"}</p>
<div class="mb-3 row">
<label for="apiKey" class="col-sm-2 col-form-label">API Key</label>
<div class="col-sm-10">
<input type="text" readonly="ON" class="form-control-plaintext" id="apiKey" value={DB.CreateSession(account.accountId,false,name)}>
</div>
</div>
</div>
);
}
}
return Shell("Error", pages, "<h1>Not logged in</h1>");
}

View File

@@ -1,6 +1,6 @@
func Pages.Index(ctx)
{
var repo = TypeOf(DB.Config.RepoCommitPrefix) == "String" ? DB.Config.RepoCommitPrefix : "https://onedev.site.tesses.net/crosslang/crosslangextras/~files/";
var repo = TypeOf(DB.Config.RepoCommitPrefix) == "String" ? DB.Config.RepoCommitPrefix : "https://git.tesses.org/tesses50/crosslangextras/src/commit/";
var pages = [
{

View File

@@ -0,0 +1,74 @@
func Pages.Session(ctx)
{
var active = DB.LoginButton(ctx,false,"");
var pages = [
{
active = false,
route = "/packages",
text = "Packages"
},
{
active = false,
route = "/upload",
text = "Upload"
},
active
];
const account = DB.GetUserIdFromSession(active.session);
const id = ctx.QueryParams.TryGetFirstInt("id");
if(TypeIsDictionary(account) && TypeIsLong(id))
{
if(ctx.Method == "GET")
{
DB.Lock();
const dbCon = DB.Open();
const results = Sqlite.Exec(dbCon, $"SELECT * FROM sessions WHERE accountId = {Sqlite.Escape(account.accountId)} AND id = {Sqlite.Escape(id)};");
Sqlite.Close(dbCon);
DB.Unlock();
return Shell("Delete session?", pages, <if(TypeIsList(results) && results.Count == 1)>
<true>
<div class="container">
<h1>Do you want to delete session: {results[0].name}?</h1>
<p>Created: {new DateTime(ParseLong(results[0].created) ?? 0).ToString("%Y/%m/%d %H:%M:%S UTC")}</p>
<p>Expires: {results[0].expires == "0" ? "Won't" : new DateTime(ParseLong(results[0].expires) ?? 0).ToString("%Y/%m/%d %H:%M:%S UTC")}</p>
<p>Type: {results[0].key == active.session ? "This Session" : results[0].expires == "0" ? "API Key" : "Browser"}</p>
<form method="POST" action="./session">
<input type="hidden" name="csrf" value={DB.CreateCSRF(ctx)}>
<input type="hidden" name="id" value={id}>
<input type="submit" class="btn btn-danger" name="confirm" value="Yes">
<input type="submit" class="btn btn-primary" name="confirm" value="No">
</form>
</div>
</true>
</if>);
}
else if(ctx.Method == "POST")
{
var confirm = ctx.QueryParams.TryGetFirst("confirm") ?? "No";
if(confirm == "Yes")
{
var csrf = ctx.QueryParams.TryGetFirst("csrf");
if(!DB.VerifyCSRF(active.session,csrf))
return Shell("Invalid CSRF", pages, "<h1>Invalid CSRF</h1>");
DB.Lock();
const dbCon = DB.Open();
Sqlite.Exec(dbCon, $"DELETE FROM sessions WHERE accountId = {Sqlite.Escape(account.accountId)} AND id = {Sqlite.Escape(id)};");
Sqlite.Close(dbCon);
DB.Unlock();
}
ctx.StatusCode = 303;
ctx.ResponseHeaders.SetValue("Location", "./sessions");
return Shell("Redirecting", pages,<null>
<h1>Redirecting</h1>
Click <a href="./sessions">here</a> if it does not redirect
</null>);
}
}
return Shell("Error", pages, "<h1>Not logged in</h1>");
}

View File

@@ -0,0 +1,62 @@
func Pages.Sessions(ctx)
{
var active = DB.LoginButton(ctx,false,"");
var pages = [
{
active = false,
route = "/packages",
text = "Packages"
},
{
active = false,
route = "/upload",
text = "Upload"
},
active
];
const account = DB.GetUserIdFromSession(active.session);
if(TypeIsDictionary(account))
{
DB.Lock();
const dbCon = DB.Open();
const results = Sqlite.Exec(dbCon, $"SELECT * FROM sessions WHERE accountId = {Sqlite.Escape(account.accountId)};");
Sqlite.Close(dbCon);
DB.Unlock();
return Shell("Sessions",
pages,
<div class="container">
<a href="./create_session" class="btn btn-primary">Create API Key</a>
<if(TypeIsList(results))>
<true>
<table class="table">
<thead>
<tr>
<th scope="col">Name</th>
<th scope="col">Created</th>
<th scope="col">Expires</th>
<th scope="col">Type</th>
</tr>
</thead>
<tbody>
<each(var item : results)>
<tr>
<th scope="row"><a href={$"./session?id={Net.Http.UrlEncode(item.id)}"}>{item.name}</a></th>
<td>{new DateTime(ParseLong(item.created) ?? 0).ToString("%Y/%m/%d %H:%M:%S UTC")}</td>
<td>{item.expires == "0" ? "Won't" : new DateTime(ParseLong(item.expires) ?? 0).ToString("%Y/%m/%d %H:%M:%S UTC")}</td>
<td>{item.key == active.session ? "This Session" : item.expires == "0" ? "API Key" : "Browser"}</td>
</tr>
</each>
</tbody>
</table>
</true>
</if>
</div>
);
}
return Shell("You are not logged in",pages,<h1>You are not logged in</h1>);
}

View File

@@ -1,5 +1,7 @@
func main(args)
{
var dir = ".";
if(args.Length > 1)
{
@@ -7,6 +9,14 @@ func main(args)
}
DB.Init(dir);
const timer = new Timer(()=>{
const time = DateTime.NowEpoch ?? 0;
DB.Lock();
const db = DB.Open();
Sqlite.Exec(db, "DELETE FROM sessions WHERE expires < {time} AND expires > 0;");
Sqlite.Close(db);
DB.Unlock();
},900000);
@@ -14,7 +24,7 @@ func main(args)
PUT /api/v1/upload Authorization Bearer
POST /api/v1/login Json object with email and password returns json object with either 200 for success {"token": "TOKEN_VAL"} or non 2XX if fails {"reason": "SOME ERROR"}
POST /api/v1/logout use Authorization Bearer
GET /api/v1/latest?name=PackageName returns 200 OK with json {"version": "1.0.0.0-prod"} if it succeeds if it fails returns a failing status code with {"reason": "SOME ERROR"}
GET /api/v1/latest?name=PackageName returns 200 OK with json {"version":"1.0.0.0-dev"} if it succeeds if it fails returns a failing status code with {"reason": "SOME ERROR"}
GET /api/v1/download?name=PackageName&version=1.0.0.0-prod returns 200 OK with package bytes or 404 if doesn't exist
GET /api/v1/search?q=SomeQuery&offset=&limit= returns 200 OK with json of packages {"packages": [{"name": "pkgName","version": "latestVersion", ...}]} or non success status code
@@ -41,6 +51,11 @@ func main(args)
ctx.WithMimeType("text/html").SendText(Pages.AdminRegister(ctx));
return true;
}
if(ctx.Path == "/admin_accounts")
{
ctx.WithMimeType("text/html").SendText(Pages.AdminAccount(ctx));
return true;
}
if(ctx.Path == "/package")
{
var name = ctx.QueryParams.TryGetFirst("name");
@@ -137,9 +152,9 @@ func main(args)
if(DB.VerifyCSRF(session,csrf))
{
var userId = DB.GetUserIdFromSession(session);
var userId = DB.GetUserIdFromSession(session).accountId;
var url = DB.ChangeMotto(userId,motto);
ctx.StatusCode = 302;
ctx.StatusCode = 303;
ctx.ResponseHeaders.SetValue("Location", url);
ctx.WithMimeType("text/html").SendText(<null>
<h1>Redirecting</h1>
@@ -194,7 +209,7 @@ func main(args)
if(!DB.VerifyCSRF(session,csrf))
{
var userId = DB.GetUserIdFromSession(session);
var userId = DB.GetUserIdFromSession(session).accountId;
result = DB.UploadPackage(userId, filePath);
}
@@ -203,7 +218,7 @@ func main(args)
if(result.Success)
{
ctx.StatusCode = 302;
ctx.StatusCode = 303;
ctx.ResponseHeaders.SetValue("Location", "/");
ctx.WithMimeType("text/html").SendText(<null>
<h1>Redirecting</h1>
@@ -279,7 +294,7 @@ func main(args)
});
return true;
}
var userId = DB.GetUserIdFromSession(session);
var userId = DB.GetUserIdFromSession(session).accountId;
var filePath = DB.working / "Temp" / $"{DB.GetUniqueNumber()}.crvm";
var strm = FS.Local.OpenFile(filePath,"wb");
@@ -335,7 +350,7 @@ func main(args)
ctx.SendJson({
token = DB.CreateSession(accountId)
token = DB.CreateSession(accountId,false, TypeIsString(json.name) ? json.name : "CrossLang Shell")
});
return true;
@@ -432,13 +447,16 @@ func main(args)
if(accountId == -1)
{
ctx.StatusCode = 400;
ctx.SendText("<h1>Invalid credentials</h1>");
ctx.SendText(Shell("Invalid credentials",[],"<h1>Invalid credentials</h1>"));
return true;
}
ctx.StatusCode = 302;
ctx.StatusCode = 303;
ctx.ResponseHeaders.SetValue("Location", "/");
ctx.ResponseHeaders.SetValue("Set-Cookie", $"Session={DB.CreateSession(accountId)}; SameSite=Strict");
const browser="Browser";
const now = DateTime.NowEpoch??0;
ctx.ResponseHeaders.SetValue("Set-Cookie", $"Session={DB.CreateSession(accountId,true, browser)}; SameSite=Strict; Expires={new DateTime(now+DB.Expires).ToHttpDate()}");
ctx.WithMimeType("text/html").SendText(<null>
<h1>Redirecting</h1>
Click <a href="./">here</a> if it does not redirect
@@ -492,7 +510,7 @@ func main(args)
var res = DB.UnforgetPassword(code,password,confirm);
if(res.Success)
{
ctx.StatusCode = 302;
ctx.StatusCode = 303;
ctx.ResponseHeaders.SetValue("Location", "/");
ctx.WithMimeType("text/html").SendText(<null>
<h1>Redirecting</h1>
@@ -538,7 +556,7 @@ func main(args)
}
ctx.StatusCode = 302;
ctx.StatusCode = 303;
ctx.ResponseHeaders.SetValue("Location", res.Redirect);
ctx.WithMimeType("text/html").SendText(<null>
<h1>Redirecting</h1>
@@ -596,12 +614,12 @@ func main(args)
if(!res.Success)
{
ctx.StatusCode = 400;
ctx.SendText($"<h1>Error: {Net.Http.HtmlEncode(res.Reason)}</h1>");
ctx.SendText(Shell("Error",[],$"<h1>Error: {Net.Http.HtmlEncode(res.Reason)}</h1>"));
return true;
}
ctx.StatusCode = 302;
ctx.StatusCode = 303;
ctx.ResponseHeaders.SetValue("Location", res.Redirect);
ctx.WithMimeType("text/html").SendText(<null>
<h1>Redirecting</h1>
@@ -615,6 +633,22 @@ func main(args)
ctx.WithMimeType("text/html").SendText(Pages.Account(ctx));
return true;
}
if(ctx.Path == "/sessions")
{
ctx.WithMimeType("text/html").SendText(Pages.Sessions(ctx));
return true;
}
if(ctx.Path == "/create_session")
{
ctx.WithMimeType("text/html").SendText(Pages.CreateSession(ctx));
return true;
}
if(ctx.Path == "/session")
{
ctx.WithMimeType("text/html").SendText(Pages.Session(ctx));
return true;
}
if(ctx.Path == "/css/bootstrap.min.css")
{
ctx.WithMimeType("text/css").SendBytes(embed("css/bootstrap.min.css"));
@@ -650,4 +684,6 @@ func main(args)
return false;
},DB.Port);
timer.Callback = null;
}

View File

@@ -86,7 +86,7 @@ func VM.RunEventLoop()
/^
Merge all crvm files into one and returns path inside destVFS of output
You should have srcFS a SubdirFilesystem to directory with executable
so if the source executable was /app/bin/app-1.0.0.0-prod.crvm you should pass a SubdirFilesystem pointing to the directory /app/bin and sourcePath set to /app-1.0.0.0-prod.crvm and destVFS to a empty SubdirFilesystem
so if the source executable was /app/bin/app-1.0.0.0-dev.crvm you should pass a SubdirFilesystem pointing to the directory /app/bin and sourcePath set to /app-1.0.0.0-dev.crvm and destVFS to a empty SubdirFilesystem
^/
func VM.Merge(srcVFS,sourcePath,destVFS)
{

View File

@@ -1,11 +1,11 @@
{
"name": "Tesses.CrossLang.Shell",
"version": "1.0.0.0-prod",
"version": "1.0.0.0-dev",
"dependencies": [],
"info": {
"maintainer": "Mike Nolan",
"type": "console",
"repo": "https://onedev.site.tesses.net/CrossLang/CrossLangExtras",
"repo": "https://git.tesses.org/tesses50/crosslangextras",
"homepage": "https://crosslang.tesseslanguage.com/",
"license": "LGPLv3"
},

View File

@@ -5,6 +5,7 @@ func Tesses.CrossLang.Shell.Default(dd)
if(flag == "version")
{
Console.WriteLine($"VM version: {VM.RuntimeVersion}");
Console.WriteLine($"Bytecode version: {VM.BytecodeVersion}");
Console.WriteLine($"Shell version: {main.File.Version}");
Console.WriteLine($"Args version: {Tesses.CrossLang.Args.File.Version}");
Console.WriteLine($"BuildTool version: {Tesses.CrossLang.BuildTool.File.Version}");
@@ -15,6 +16,7 @@ func Tesses.CrossLang.Shell.Default(dd)
Console.WriteLine("COMMANDS:");
Console.WriteLine("new: create new project");
Console.WriteLine("build: build a project");
Console.WriteLine("test: test a project");
Console.WriteLine("run: run a project");
Console.WriteLine("debug: debug a project");
Console.WriteLine("install-console: install a console application");

View File

@@ -33,6 +33,9 @@ func Tesses.CrossLang.Shell.Login(dd)
var name = dd.Arguments[1];
var host = dd.Arguments[2];
Console.Write("Name (empty for CrossLang Shell): ");
var name = Console.ReadLine() ?? "";
if(name == "") name = "CrossLang Shell";
Console.Write("Email: ");
var email = Console.ReadLine();
@@ -45,6 +48,7 @@ func Tesses.CrossLang.Shell.Login(dd)
Console.WriteLine();
var accountRequest = {
name,
email,
password
};

View File

@@ -0,0 +1,168 @@
func Tesses.CrossLang.Shell.Logout(dd)
{
var accounts = [];
func help()
{
Console.WriteLine("USAGE: crosslang logout [name]");
Console.WriteLine();
Console.WriteLine("ARGUMENTS:");
Console.WriteLine("name: the session name");
}
if(FS.Local.FileExists(Env.CrossLangConfig / "auth.json"))
{
accounts = Json.Decode(FS.ReadAllText(FS.Local,Env.CrossLangConfig / "auth.json"));
if(TypeOf(accounts) != "List") accounts = [];
}
if(dd.Flags.Contains("help"))
{
help();
}
else {
if(FS.Local.FileExists(Env.CrossLangConfig / "auth.json"))
{
const json = Json.Decode(FS.ReadAllText(FS.Local,Env.CrossLangConfig / "auth.json"));
if(json.Length == 0)
{
Console.WriteLine("You were not logged in");
return 0;
}
else if(json.Length == 1)
{
const host = json[0].host;
const token = json[0].token;
if(!TypeIsString(host))
{
Console.WriteLine("Host is not a string");
FS.WriteAllText(FS.Local, Env.CrossLangConfig / "auth.json", "[]");
return 1;
}
if(!TypeIsString(token))
{
Console.WriteLine("Token is not a string");
FS.WriteAllText(FS.Local, Env.CrossLangConfig / "auth.json", "[]");
return 1;
}
const resp = Net.Http.MakeRequest($"{host.TrimEnd('/')}/api/v1/logout",{
RequestHeaders = [
{Key= "Authorization",Value=$"Bearer {token}"}
],
Method = "GET"
});
if(resp.StatusCode == 200)
{
const json = resp.ReadAsJson();
if(json.Success)
{
FS.WriteAllText(FS.Local, Env.CrossLangConfig / "auth.json", "[]");
resp.Close();
return 0;
}
else {
FS.WriteAllText(FS.Local, Env.CrossLangConfig / "auth.json", "[]");
resp.Close();
Console.WriteLine($"Failed to logout, go to {host.TrimEnd('/')}/sessions to logout");
return 1;
}
}
resp.Close();
return 1;
}
else {
if(dd.Arguments.Length < 2)
{
Console.WriteLine("Multiple entries in auth.json file, session is ambiguous.");
Console.WriteLine("Sessions:");
each(var item : json)
{
Console.WriteLine($"{item.name}: {item.host}");
}
}
else {
var host = "";
var token = "";
var found = false;
var cur = null;
each(var item : json)
{
if(item.name == dd.Arguments[1])
{
cur = item;
found=true;
host = item.host;
token = item.token;
break;
}
}
if(!found) {
Console.WriteLine($"Could not find session with name: {dd.Arguments[1]}");
return 1;
}
if(!TypeIsString(host))
{
Console.WriteLine("Host is not a string");
json.Remove(cur);
FS.WriteAllText(FS.Local, Env.CrossLangConfig / "auth.json", json.ToString());
return 1;
}
if(!TypeIsString(token))
{
Console.WriteLine("Token is not a string");
json.Remove(cur);
FS.WriteAllText(FS.Local, Env.CrossLangConfig / "auth.json", json.ToString());
return 1;
}
const resp = Net.Http.MakeRequest($"{host.TrimEnd('/')}/api/v1/logout",{
RequestHeaders = [
{Key= "Authorization",Value=$"Bearer {token}"}
],
Method = "GET"
});
if(resp.StatusCode == 200)
{
const json2 = resp.ReadAsJson();
if(json2.Success)
{
json.Remove(cur);
FS.WriteAllText(FS.Local, Env.CrossLangConfig / "auth.json", json.ToString());
resp.Close();
return 0;
}
else {
json.Remove(cur);
FS.WriteAllText(FS.Local, Env.CrossLangConfig / "auth.json", json.ToString());
resp.Close();
Console.WriteLine($"Failed to logout, go to {host.TrimEnd('/')}/sessions to logout");
return 1;
}
}
resp.Close();
return 1;
}
}
} else {
Console.WriteLine("You were not logged in");
return 0;
}
}
}

View File

@@ -80,6 +80,9 @@ func main(args)
case "publish":
return Tesses.CrossLang.Shell.Publish(dd);
break;
case "test":
return Tesses.CrossLang.Shell.Test(dd);
break;
default:
return Tesses.CrossLang.Shell.Default(dd);
}

View File

@@ -73,6 +73,8 @@ func Tesses.CrossLang.Shell.New(dd)
proj.version = "1.0.0.0-prod";
var old_info = proj.info;
proj.info = old_info.template_info;
proj.icon = old_info.template_icon;
proj.prebuild = old_info.template_prebuild;
proj.dependencies = old_info.template_project_dependencies;
var srcDir = proj.source_directory;
if(TypeOf(srcDir) == "Undefined") srcDir = "/src";
@@ -104,7 +106,7 @@ func Tesses.CrossLang.Shell.New(dd)
var src = FS.ReadAllText(projectDir, ent);
src = src.Replace("%PROJECT_NAME%",projectPath.GetFileName());
src = src.Replace("%TEMPLATE_PROJECT_NAME%","%PROJECT_NAME");
src = src.Replace("%TEMPLATE_PROJECT_NAME%","%PROJECT_NAME%");
FS.WriteAllText(projectDir, ent, src);
}

View File

@@ -0,0 +1,386 @@
class ExpectObj {
private inverted=false;
private currentSpec;
private currentTest;
private left;
public getNot() {
this.inverted = !this.inverted;
return this;
}
public ExpectObj(currentTest,currentSpec,left)
{
this.currentTest = currentTest;
this.currentSpec = currentSpec;
this.left = left;
}
public ToBe(right)
{
if(this.inverted)
{
this.SetRes(this.left != right, $"{this.left} is equal to {this.right}");
}
else
{
this.SetRes(this.left == right, $"{this.left} is not equal to {right}");
}
}
public LessThan(right)
{
if(this.inverted)
{
this.SetRes(!(this.left < right), $"{this.left} is less than {this.right}");
}
else
{
this.SetRes(this.left < right, $"{this.left} is not less than {right}");
}
}
public GreaterThan(right)
{
if(this.inverted)
{
this.SetRes(!(this.left > right), $"{this.left} is greater than {this.right}");
}
else
{
this.SetRes(this.left > right, $"{this.left} is not greater than {right}");
}
}
private SetRes(succeeded, failmsg)
{
if(!succeeded)
{
this.currentTest.success = false;
this.currentSpec.pass=false;
this.currentSpec.reason = failmsg;
}
}
}
func Tesses.CrossLang.Shell.Test(dd)
{
var offline=false;
var buildPath = ".";
var nobuild=false;
var allowFullCompTime=false;
var output="";
var debug = false;
each(var flag : dd.Flags)
{
if(flag == "debug")
{
debug = true;
}
else if(flag == "offline")
{
offline = true;
}
else if(flag == "allow-insecure-comptime")
{
allowFullCompTime=true;
}
else if(flag == "help")
{
Console.WriteLine("USAGE: crosslang test [run-flags-and-options] program-arguments...");
Console.WriteLine("USAGE: crosslang test [run-flags-and-options] -- program-arguments-and-options...");
Console.WriteLine("FLAGS:");
Console.WriteLine("offline: build with no internet (don't fetch files)");
Console.WriteLine("allow-insecure-comptime: Allow full comptime");
Console.WriteLine("nobuild: don't build, just run");
Console.WriteLine("debug: emit line info into executable");
Console.WriteLine("help: this help");
Console.WriteLine();
Console.WriteLine("OPTIONS:");
Console.WriteLine("conf=CONFIGSTRING: specify a conf string for compile_tool(s), is the property Config");
return 0;
}
else if(flag == "nobuild")
{
nobuild=true;
}
}
var conf = "";
each(var option : dd.Options)
{
if(option.Key == "conf")
{
conf = option.Value;
}
}
if(nobuild)
{
if(FS.Local.FileExists("cross.json"))
{
var proj = Json.Decode(FS.ReadAllText(FS.Local, "cross.json"));
var nameVer = $"{proj.name}-{proj.version}.crvm";
var buildDir = TypeOf(proj.bin_directory) == "String" ? proj.bin_directory : "bin";
output =./buildDir/nameVer;
if(!FS.Local.FileExists(output))
{
Console.WriteLine($"ERROR: file {output} does not exist.");
}
}
else
{
Console.WriteLine("ERROR: could not find project.");
return 1;
}
}
else
{
var pm = new Tesses.CrossLang.PackageManager();
pm.Offline = offline;
var bt = new Tesses.CrossLang.BuildTool(pm);
bt.Config = conf;
bt.Debug = debug;
bt.AllowFullCompTime = allowFullCompTime;
output = bt.BuildProject(buildPath).Output;
}
return Tesses.CrossLang.Shell.RunTestSuite(output);
}
func Tesses.CrossLang.Shell.RunTestSuite(execFile)
{
var configData = Json.Decode(FS.ReadAllText(FS.Local,"cross.json")) ?? {};
var testDir = configData.test_directory ?? "tests";
var sources = [];
func walk_for_source(sourceDir)
{
if(FS.Local.DirectoryExists(sourceDir))
each(var file : FS.Local.EnumeratePaths(sourceDir))
{
if(FS.Local.RegularFileExists(file) && file.GetExtension()==".tcross")
{
var src = {
FileName = file.ToString(),
Source = FS.ReadAllText(FS.Local, file)
};
sources.Add(src);
}
else if(FS.Local.DirectoryExists(file))
{
walk_for_source(file);
}
}
}
walk_for_source(testDir);
const strm = new MemoryStream(true);
var result = VM.Compile({
Name = "/test/",
Version = "1.0.0.0-prod",
Sources = sources,
Dependencies = [], //they are loaded for us
Output = strm,
CompTime = compTimeEnv,
Debug = this.Debug
});
if(result.Success)
{
const RED = "\e[0;91m";
const GREEN = "\e[0;92m";
const YELLOW = "\e[;93m";
const NC = "\e[0m";
strm.Seek(0,0);
var vmFile = VM.LoadExecutable(strm);
strm.GetBytes().Resize(0);
var env = VM.CreateEnvironment({});
env.RegisterEverything();
env.LockRegister();
env.LoadFileWithDependencies(FS.Local,execFile);
var failed = 0;
var total = 0;
var currentTest = null;
var currentSpec = null;
func Test(name, cb)
{
total++;
currentTest = {
success = true,
specs = []
};
testLists.Add(currentTest);
try {
cb();
if(!currentTest.success)
{
failed++;
Console.WriteLine($"[{RED}FAILED{NC}] {name}");
each(var s : currentTest.specs)
{
if(s.todo)
{
Console.WriteLine($"\t[{YELLOW}TODO{NC}] {s.name}");
}
else if(s.pass)
{
Console.WriteLine($"\t[{GREEN}PASS{NC}] {s.name}");
}
else
{
Console.WriteLine($"\t[{RED}FAILED{NC}] {s.name}");
Console.WriteLine($"\t\tReason: {s.reason}");
}
}
}
else {
Console.WriteLine($"[{GREEN}PASS{NC}] {name}");
each(var s : currentTest.specs)
{
if(s.todo)
{
Console.WriteLine($"\t[{YELLOW}TODO{NC}] {s.name}");
}
else if(s.pass)
{
Console.WriteLine($"\t[{GREEN}PASS{NC}] {s.name}");
}
}
}
} catch(ex)
{
Console.WriteLine($"[{RED}EXCEPTION{NC}] {name}");
Console.WriteLine($"\tReason: {ex}");
each(var s : currentTest.specs)
{
if(s.todo)
{
Console.WriteLine($"\t[{YELLOW}TODO{NC}] {s.name}");
}
else if(s.pass)
{
Console.WriteLine($"\t[{GREEN}PASS{NC}] {s.name}");
}
else
{
Console.WriteLine($"\t[{RED}FAILED{NC}] {s.name}");
Console.WriteLine($"\t\tReason: {s.reason}");
}
}
failed++;
}
currentTest = null;
}
func SpecToDo(name)
{
if(TypeIsDictionary(currentSpec))
{
throw "Must not be in Spec";
}
if(TypeIsDictionary(currentTest))
{
currentTest.specs.Add({
todo = true,
name
});
}
else {
throw "Must be within Test(\"My test name\",()=>{});";
}
}
func Spec(name,cb)
{
if(TypeIsDictionary(currentSpec))
{
throw "Must not be in Spec";
}
if(TypeIsDictionary(currentTest))
{
currentSpec = {
name
};
currentTest.specs.Add(currentSpec);
cb();
currentSpec.pass ??= true;
currentSpec = null;
} else {
throw "Must be within Test(\"My test name\",()=>{});";
}
}
func Expect(val)
{
if(TypeIsDictionary(currentTest) && TypeIsDictionary(currentSpec))
{
return new ExpectObj(currentTest,currentSpec,val);
}
else {
throw "Must be within Spec(\"My spec name\",()=>{});";
}
}
func Assert(val, failmsg)
{
if(TypeIsDictionary(currentTest) && TypeIsDictionary(currentSpec))
{
if(val)
else
{
currentSpec.pass=false;
currentSpec.reason = failmsg;
currentTest.success=false;
}
}
else {
throw "Must be within Spec(\"My spec name\",()=>{});";
}
}
env.SetVariable("Test", Test);
env.SetVariable("Spec", Spec);
env.SetVariable("SpecToDo", SpecToDo);
env.SetVariable("Expect", Expect);
env.SetVariable("Assert",Assert);
const beginTime = DateTime.Now;
try {
env.LoadFile(vmFile);
} catch(ex) {
Console.WriteLine($"[{RED}EXCEPTION{NC}] {ex}");
return 1;
}
Console.WriteLine();
if(failed == 0)
{
Console.WriteLine($"Tests {GREEN}passed{NC} in {(DateTime.Now-beginTime).ToString(true)}");
}
else
{
Console.WriteLine($"Tests {RED}failed{NC} in {(DateTime.Now-beginTime).ToString(true)}");
}
Console.WriteLine($"Passed: {total-failed}");
Console.WriteLine($"Failed: {failed}");
Console.WriteLine($"Total: {total}");
return failed > 0 ? 1 : 0;
}
return 0;
}

View File

@@ -1,5 +1,8 @@
func Tesses.CrossLang.Shell.Token(dd)
{
Console.Write("Name (empty for CrossLang Shell): ");
var name = Console.ReadLine() ?? "";
if(name == "") name = "CrossLang Shell";
Console.Write("Host: ");
var host = Console.ReadLine();
Console.Write("Email: ");
@@ -15,6 +18,7 @@ func Tesses.CrossLang.Shell.Token(dd)
var accountRequest = {
name,
email,
password
};

View File

@@ -4,5 +4,5 @@
"description": "The standard library of crosslang"
},
"name": "Tesses.CrossLang.Std",
"version": "1.0.0.0-prod"
"version": "1.0.0.0-dev"
}

View File

@@ -14,7 +14,7 @@ func cmd(name, args)
func main(args)
{
var args2=["-o","./Tesses.CrossLang.BuildEssentials/bin-tmp","-r","./Tesses.CrossLang.BuildEssentials/res","-n","Tesses.CrossLang.BuildEssentials","-v","1.0.0.0-prod","Tesses.CrossLang.BuildEssentials/main.tcross"];
var args2=["-o","./Tesses.CrossLang.BuildEssentials/bin-tmp","-r","./Tesses.CrossLang.BuildEssentials/res","-n","Tesses.CrossLang.BuildEssentials","-v","1.0.0.0-dev","Tesses.CrossLang.BuildEssentials/main.tcross"];
each(var f : FS.Local.EnumeratePaths("./Tesses.CrossLang.BuildEssentials/src"))
{
if(f.GetExtension() == ".tcross")
@@ -22,38 +22,41 @@ func main(args)
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","Tesses.CrossLang.Std"]);
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","Templates/webapp"]);
cmd("crossvm",["./Tesses.CrossLang.BuildEssentials/bin-tmp/Tesses.CrossLang.BuildEssentials-1.0.0.0-dev.crvm","Tesses.CrossLang.Shell"]);
cmd("crossvm",["./Tesses.CrossLang.Shell/bin/Tesses.CrossLang.Shell-1.0.0.0-dev.crvm","build","Tesses.CrossLang.PackageServer"]);
//cmd("crossvm",["./Tesses.CrossLang.Shell/bin/Tesses.CrossLang.Shell-1.0.0.0-dev.crvm","build","Tesses.CrossLang.WebSite"]);
cmd("crossvm",["./Tesses.CrossLang.Shell/bin/Tesses.CrossLang.Shell-1.0.0.0-dev.crvm","build","Tesses.CrossLang.Std"]);
cmd("crossvm",["./Tesses.CrossLang.Shell/bin/Tesses.CrossLang.Shell-1.0.0.0-dev.crvm","build","Templates/console"]);
cmd("crossvm",["./Tesses.CrossLang.Shell/bin/Tesses.CrossLang.Shell-1.0.0.0-dev.crvm","build","Templates/emptyweb"]);
cmd("crossvm",["./Tesses.CrossLang.Shell/bin/Tesses.CrossLang.Shell-1.0.0.0-dev.crvm","build","Templates/web"]);
cmd("crossvm",["./Tesses.CrossLang.Shell/bin/Tesses.CrossLang.Shell-1.0.0.0-dev.crvm","build","Templates/template"]);
cmd("crossvm",["./Tesses.CrossLang.Shell/bin/Tesses.CrossLang.Shell-1.0.0.0-dev.crvm","build","Templates/lib"]);
cmd("crossvm",["./Tesses.CrossLang.Shell/bin/Tesses.CrossLang.Shell-1.0.0.0-dev.crvm","build","Templates/compiletool"]);
cmd("crossvm",["./Tesses.CrossLang.Shell/bin/Tesses.CrossLang.Shell-1.0.0.0-dev.crvm","build","Templates/tool"]);
cmd("crossvm",["./Tesses.CrossLang.Shell/bin/Tesses.CrossLang.Shell-1.0.0.0-dev.crvm","build","Templates/webapp"]);
cmd("crossvm",["./Tesses.CrossLang.Shell/bin/Tesses.CrossLang.Shell-1.0.0.0-dev.crvm","build","Templates/npmweb"]);
cmd("crossvm",["./Tesses.CrossLang.Shell/bin/Tesses.CrossLang.Shell-1.0.0.0-dev.crvm","build","Templates/npmwebapp"]);
/*
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-dev.crvm","build","Tesses.CrossLang.PackageServer"]);
cmd("crossvm",["./Tesses.CrossLang.Shell/bin/Tesses.CrossLang.Shell-1.0.0.0-dev.crvm","build","Tesses.CrossLang.PackageServer"]);
cmd("crossvm",["./Tesses.CrossLang.Shell/bin/Tesses.CrossLang.Shell-1.0.0.0-dev.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"]);
cmd("crossvm",["./Tesses.CrossLang.Shell/bin/Tesses.CrossLang.Shell-1.0.0.0-dev.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"]);
cmd("crossvm",["crosslang_shell_archive_maker/bin/crosslang_shell_archive_maker-1.0.0.0-dev.crvm", "install"]);
}
else if(args[1] == "pack")
{
cmd("crossvm",["crosslang_shell_archive_maker/bin/crosslang_shell_archive_maker-1.0.0.0-prod.crvm"]);
cmd("crossvm",["crosslang_shell_archive_maker/bin/crosslang_shell_archive_maker-1.0.0.0-dev.crvm"]);
}
}
}

View File

@@ -6,5 +6,5 @@
"project_dependencies": [
"..\/Tesses.CrossLang.BuildEssentials"
],
"version": "1.0.0.0-prod"
"version": "1.0.0.0-dev"
}

View File

@@ -1,5 +1,5 @@
var name = "Tesses.CrossLang.ShellPackage-1.0.0.0-prod.crvm";
var name = "Tesses.CrossLang.ShellPackage-1.0.0.0-dev.crvm";
func main(args)
{
create_archive();
@@ -23,7 +23,9 @@ func install()
func create_archive()
{
var tmpFS = new SubdirFilesystem(FS.Local,"crosslang_shell_archive_maker/tmp");
var tmpFS = new SubdirFilesystem(FS.Local,FS.MakeFull("crosslang_shell_archive_maker/tmp"));
func copyFile(src,dest)
{
if(!FS.Local.FileExists(src)) return;
@@ -41,9 +43,9 @@ func create_archive()
tmpFS.CreateDirectory(shell);
copyFile("Tesses.CrossLang.Shell/bin/Tesses.CrossLang.Args-1.0.0.0-prod.crvm", shell / "Tesses.CrossLang.Args-1.0.0.0-prod.crvm");
copyFile("Tesses.CrossLang.Shell/bin/Tesses.CrossLang.BuildEssentials-1.0.0.0-prod.crvm", shell / "Tesses.CrossLang.BuildEssentials-1.0.0.0-prod.crvm");
copyFile("Tesses.CrossLang.Shell/bin/Tesses.CrossLang.Shell-1.0.0.0-prod.crvm", shell / "Shell.crvm");
copyFile("Tesses.CrossLang.Shell/bin/Tesses.CrossLang.Args-1.0.0.0-dev.crvm", shell / "Tesses.CrossLang.Args-1.0.0.0-dev.crvm");
copyFile("Tesses.CrossLang.Shell/bin/Tesses.CrossLang.BuildEssentials-1.0.0.0-dev.crvm", shell / "Tesses.CrossLang.BuildEssentials-1.0.0.0-dev.crvm");
copyFile("Tesses.CrossLang.Shell/bin/Tesses.CrossLang.Shell-1.0.0.0-dev.crvm", shell / "Shell.crvm");
copyFile("Tesses.CrossLang.Reference/bin/Tesses.CrossLang.Reference-1.0.0.0-dev.crvm", "Reference.crvm");
var devStudio = r / "DevStudio";
tmpFS.CreateDirectory(devStudio);
@@ -66,14 +68,16 @@ func create_archive()
copyFile("Templates/compiletool/bin/Tesses.CrossLang.Template.CompileTool-1.0.0.0-prod.crvm", templates / "compiletool.crvm");
copyFile("Templates/console/bin/Tesses.CrossLang.Template.Console-1.0.0.0-prod.crvm", templates / "console.crvm");
copyFile("Templates/lib/bin/Tesses.CrossLang.Template.Library-1.0.0.0-prod.crvm", templates / "lib.crvm");
copyFile("Templates/template/bin/Tesses.CrossLang.Template.Template-1.0.0.0-prod.crvm", templates / "template.crvm");
copyFile("Templates/web/bin/Tesses.CrossLang.Template.Website-1.0.0.0-prod.crvm", templates / "web.crvm");
copyFile("Templates/emptyweb/bin/Tesses.CrossLang.Template.EmptyWebsite-1.0.0.0-prod.crvm", templates / "emptyweb.crvm");
copyFile("Templates/tool/bin/Tesses.CrossLang.Template.Tool-1.0.0.0-prod.crvm", templates / "tool.crvm");
copyFile("Templates/webapp/bin/Tesses.CrossLang.Template.WebApp-1.0.0.0-prod.crvm", templates / "webapp.crvm");
copyFile("Templates/compiletool/bin/Tesses.CrossLang.Template.CompileTool-1.0.0.0-dev.crvm", templates / "compiletool.crvm");
copyFile("Templates/console/bin/Tesses.CrossLang.Template.Console-1.0.0.0-dev.crvm", templates / "console.crvm");
copyFile("Templates/lib/bin/Tesses.CrossLang.Template.Library-1.0.0.0-dev.crvm", templates / "lib.crvm");
copyFile("Templates/template/bin/Tesses.CrossLang.Template.Template-1.0.0.0-dev.crvm", templates / "template.crvm");
copyFile("Templates/web/bin/Tesses.CrossLang.Template.Website-1.0.0.0-dev.crvm", templates / "web.crvm");
copyFile("Templates/emptyweb/bin/Tesses.CrossLang.Template.EmptyWebsite-1.0.0.0-dev.crvm", templates / "emptyweb.crvm");
copyFile("Templates/tool/bin/Tesses.CrossLang.Template.Tool-1.0.0.0-dev.crvm", templates / "tool.crvm");
copyFile("Templates/webapp/bin/Tesses.CrossLang.Template.WebApp-1.0.0.0-dev.crvm", templates / "webapp.crvm");
copyFile("Templates/npmwebapp/bin/Tesses.CrossLang.Template.NPMWebApp-1.0.0.0-dev.crvm", templates / "npmwebapp.crvm");
copyFile("Templates/npmweb/bin/Tesses.CrossLang.Template.NPMWebsite-1.0.0.0-dev.crvm", templates / "npmweb.crvm");
var packageCache = r / "PackageCache";
@@ -82,18 +86,18 @@ func create_archive()
tmpFS.CreateDirectory(packageCache / "Tesses.CrossLang.BuildEssentials");
tmpFS.CreateDirectory(packageCache / "Tesses.CrossLang.Markup");
tmpFS.CreateDirectory(packageCache / "Tesses.CrossLang.Std");
copyFile("Tesses.CrossLang.Args/bin/Tesses.CrossLang.Args-1.0.0.0-prod.crvm", packageCache / "Tesses.CrossLang.Args" / "1.0.0.0-prod");
copyFile("Tesses.CrossLang.BuildEssentials/bin/Tesses.CrossLang.BuildEssentials-1.0.0.0-prod.crvm", packageCache / "Tesses.CrossLang.BuildEssentials" / "1.0.0.0-prod");
//copyFile("Tesses.CrossLang.Markup/bin/Tesses.CrossLang.Markup-1.0.0.0-prod.crvm", packageCache / "Tesses.CrossLang.Markup" / "1.0.0.0-prod");
copyFile("Tesses.CrossLang.Args/bin/Tesses.CrossLang.Args-1.0.0.0-prod.crvm", packageCache / "Tesses.CrossLang.Args" / "1.0.0.0-prod");
copyFile("Tesses.CrossLang.Std/bin/Tesses.CrossLang.Std-1.0.0.0-prod.crvm", packageCache / "Tesses.CrossLang.Std" / "1.0.0.0-prod");
copyFile("Tesses.CrossLang.Args/bin/Tesses.CrossLang.Args-1.0.0.0-dev.crvm", packageCache / "Tesses.CrossLang.Args" / "1.0.0.0-prod");
copyFile("Tesses.CrossLang.BuildEssentials/bin/Tesses.CrossLang.BuildEssentials-1.0.0.0-dev.crvm", packageCache / "Tesses.CrossLang.BuildEssentials" / "1.0.0.0-prod");
//copyFile("Tesses.CrossLang.Markup/bin/Tesses.CrossLang.Markup-1.0.0.0-dev.crvm", packageCache / "Tesses.CrossLang.Markup" / "1.0.0.0-prod");
copyFile("Tesses.CrossLang.Args/bin/Tesses.CrossLang.Args-1.0.0.0-dev.crvm", packageCache / "Tesses.CrossLang.Args" / "1.0.0.0-prod");
copyFile("Tesses.CrossLang.Std/bin/Tesses.CrossLang.Std-1.0.0.0-dev.crvm", packageCache / "Tesses.CrossLang.Std" / "1.0.0.0-prod");
var installer = FS.Local.OpenFile(name, "wb");
var ifo = {
type = "archive",
maintainer = "Mike Nolan",
repo = "https://onedev.site.tesses.net/CrossLang/CrossLangExtras",
repo = "https://git.tesses.org/tesses50/crosslangextras",
homepage = "https://crosslang.tesseslanguage.com/"
};

View File

@@ -1,6 +1,6 @@
services:
pkg:
image: onedev.site.tesses.net/crosslang/crosslangextras/packageserver:latest
image: git.tesses.org/tesses50/cpkg:latest
ports:
- "4206:4206"
volumes:

View File

@@ -1,6 +1,6 @@
console: a console application (or webserver)
lib: a library
webapp: A web app (for webview or eventual cloud OS)
webapp: A web app (for webview or android, can specify port on cli)
template: used for project templates
compile_tool: run before source is compiled for project if it is in the dependency tree for the project (and is not linked to said projects)
tool: a tool invokable via crosslang (gets Tesses.CrossLang.Args for free)

View File

@@ -7,7 +7,7 @@
"vscode": "^1.107.0"
},
"publisher": "tesses50",
"repository": "https://onedev.site.tesses.net/crosslang/crosslangextras",
"repository": "https://git.tesses.org/tesses50/crosslangextras",
"categories": [
"Programming Languages"
],
@@ -17,7 +17,14 @@
"aliases": ["CrossLang", "crosslang"],
"extensions": [".tcross"],
"configuration": "./language-configuration.json"
},{
},
{
"id": "bcrosslang",
"aliases": ["Better CrossLang", "better-crosslang"],
"extensions": [".btcross"],
"configuration": "./language-configuration.json"
}
,{
"id": "crossasm",
"aliases": ["CrossAsm", "crossasm"],
"extensions": [".tcasm"],
@@ -27,6 +34,10 @@
"language": "crosslang",
"scopeName": "source.crosslang",
"path": "./syntaxes/crosslang.tmLanguage.json"
},{
"language": "bcrosslang",
"scopeName": "source.bcrosslang",
"path": "./syntaxes/bcrosslang.tmLanguage.json"
},{
"language": "crossasm",
"scopeName": "source.crossasm",

View File

@@ -202,6 +202,8 @@ export async function createTemplate()
if(project_name)
{
const dir = join(getDocumentsFolder(),"CrossLangProjects",project_name);
mkdirSync(getDocumentsFolder());
mkdirSync(join(getDocumentsFolder(),"CrossLangProjects"));
mkdirSync(dir);
await readCommandToEnd("crosslang",["new",templateName,dir]);

View File

@@ -0,0 +1,137 @@
{
"$schema": "https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json",
"name": "Better CrossLang",
"patterns": [
{
"include": "#comment"
},
{
"include": "#keywords"
},
{
"include": "#chars"
},
{
"include": "#operators"
},
{
"include": "#function"
},
{
"include": "#identifier"
},
{
"include": "#consts"
},
{
"include": "#double"
},
{
"include": "#long"
},
{
"include": "#strings"
}
],
"repository": {
"comment": {
"patterns": [{
"name": "comment.line.double-slash.bcrosslang",
"begin": "//",
"end": "\\n"
},
{
"name": "comment.block.bcrosslang",
"begin": "\\/\\*",
"end": "\\*\\/"
},
{
"name": "comment.line.bcrosslang",
"begin": "#",
"end": "\\n"
},
{
"name":"comment.block.documentation.bcrosslang",
"begin": "\\/\\^",
"end": "\\^\\/"
}
]
},
"strings": {
"name": "string.quoted.double.bcrosslang",
"begin": "\\\"",
"end": "\\\"",
"patterns": [
{
"name": "constant.character.escape.bcrosslang",
"match": "\\\\."
}
]
},
"operators": {
"patterns": [{
"name": "keyword.operator",
"match": "(\\+|\\-|\\*|\\\/|\\%|\\!|\\||\\&|\\^|\\<\\<|\\>\\>|\\<|\\>|\\=)"
}]
},
"function": {
"patterns": [{
"name": "entity.name.function.bcrosslang",
"match": "\\b[_a-zA-Z\\x80-\\xFF\\$][_a-zA-Z0-9\\x80-\\xFF\\$]*\\("
}]
},
"identifier": {
"patterns": [{
"name": "entity.name.bcrosslang",
"match": "\\b[_a-zA-Z\\x80-\\xFF\\$][_a-zA-Z0-9\\x80-\\xFF\\$]*"
}]
},
"double": {
"name": "constant.numeric.bcrosslang",
"match": "\\b[0-9][0-9]*\\.[0-9]*\\b"
},
"long": {
"patterns": [
{
"name": "constant.numeric.bcrosslang",
"match": "\\b[0-9][0-9]*\\b"
}
]
},
"keywords": {
"patterns": [
{
"name": "constant.language.bcrosslang",
"match": "\\b(true|false|null|undefined|this)\\b"
},
{
"name": "keyword.operator.new.bcrosslang",
"match": "\\bnew\\b"
},
{
"name": "keyword.control.bcrosslang",
"match": "\\b(if|else|while|for|do|return|each|break|try|catch|finally|defer|enumerable|yield|switch|case|default|await|breakpoint|throw)\\b"
},
{
"name": "keyword.bcrosslang",
"match": "\\b(Double|Long|ByteArray|List|Dictionary|Object|String|Char|Callable|Path|Filesystem|Regex|Variant|Boolean|as|var|meta|of|in|interface|delegate|extern|namespace|use|const|func|class|public|private|protected|static|operator|embed|embeddir|embedstrm|comptime|async)\\b"
}
]
},
"chars": {
"name": "constant.character.bcrosslang",
"begin": "'",
"end": "'",
"patterns": [
{
"name": "constant.character.escape.bcrosslang",
"match": "\\\\."
}
]
}
},
"scopeName": "source.bcrosslang"
}

View File

@@ -112,11 +112,11 @@
},
{
"name": "keyword.control.crosslang",
"match": "\\b(if|else|while|for|do|return|each|break|try|catch|finally|defer|enumerable|yield|switch|case|default|await|breakpoint|throw)\\b"
"match": "\\b(if|else|while|for|do|return|each|in|break|continue|try|catch|finally|defer|enumerable|yield|switch|case|default|await|breakpoint|throw)\\b"
},
{
"name": "keyword.crosslang",
"match": "\\b(var|const|func|class|public|private|protected|static|operator|embed|embeddir|embedstrm|comptime|async)\\b"
"match": "\\b(meta|var|const|func|class|public|private|protected|static|operator|embed|embeddir|embedstrm|comptime|async)\\b"
}
]
},