~mrms/umbox

966f4b25e4cc273e308e8da84e530025ff36497b — Marek Ma┼íkarinec 3 months ago 602a1d4
Update to newer umka version
10 files changed, 218 insertions(+), 197 deletions(-)

M src/build.um
M src/common.um
M src/init.um
M src/install.um
M src/register.um
M src/remove.um
M src/run.um
M src/search.um
M src/update.um
M src/upload.um
M src/build.um => src/build.um +5 -13
@@ 13,11 13,8 @@ fn run*(url: str, argi: int) {
		return
	}

	ok, meta := common.getMeta("box.json") 
	if !ok {
		fprintf(std.stderr(), "Not in an UmBox directory.\n")
		return
	}
	meta, err := common.getMeta("box.json") 
	std.exitif(err)
	
	if len(meta.preBuild) > 0 {
		code := std.system(meta.preBuild)


@@ 29,6 26,7 @@ fn run*(url: str, argi: int) {
	}
	
	t, err := tar.open("box.tar", "w")
	std.exitif(err)
	
	t.addFile("box.json")
	if len(meta.readme) > 0 {


@@ 46,16 44,10 @@ fn run*(url: str, argi: int) {
	}
	
	err = t.finalize()
	if err != 0 {
		fprintf(std.stderr(), "Error finalizing tar: %s\n", tar.strerror(err))
		return
	}
	std.exitif(err)
	
	err = t.close()
	if err != 0 {
		fprintf(std.stderr(), "Error closing tar: %s\n", tar.strerror(err))
		return
	}
	std.exitif(err)
	
	if len(meta.postBuild) > 0 {
		code := std.system(meta.postBuild)

M src/common.um => src/common.um +69 -63
@@ 14,7 14,7 @@ var url*: str = "https://umbox.tophat2d.dev/"

type Box* = interface {
	exists(file: str = "version"): bool
	download(file: str = "box.tar"): ([]uint8, bool, str)
	download(file: str = "box.tar"): ([]char, std.Err)
	encode(): str
	getName(): str
}


@@ 29,26 29,27 @@ fn (b: ^HTTPBox) getName*(): str {
}

fn (b: ^HTTPBox) exists*(file: str = "version"): bool {
	w := io.mkMemory({})
	resp := http.get(sprintf("%s/api/package/%s/download/%s", b.url, b.name, file), w, {
	resp := http.get(sprintf("%s/api/package/%s/download/%s", b.url, b.name, file), {
		userAgent: "umbox-cli"
	})

	return resp.ok && resp.status == 200
}

fn (b: ^HTTPBox) download*(file: str = "box.tar"): ([]uint8, bool, str) {
	w := io.mkMemory({})
	resp := http.get(sprintf("%s/api/package/%s/download/%s", b.url, b.name, file), w, {
fn (b: ^HTTPBox) download*(file: str = "box.tar"): ([]char, std.Err) {
	resp := http.get(sprintf("%s/api/package/%s/download/%s", b.url, b.name, file), {
		userAgent: "umbox-cli"
	})
	   
	msg := resp.err

	if !resp.ok {
		return {}, std.error(1, resp.err)
	}

	if resp.status != 200 {
		msg = str([]char(w.read()))
		return resp.body, std.error(resp.status, str(resp.body))
	}
	
	return w.read(), resp.ok && resp.status == 200, msg

	return resp.body, {}
}

fn (b: ^HTTPBox) encode*(): str {


@@ 65,8 66,8 @@ type FileBox* = struct {
	handle: tar.Tar
}

fn (b: ^FileBox) open(): tar.Errno {
	var err: tar.Errno
fn (b: ^FileBox) open(): std.Err {
	var err: std.Err
	b.handle, err = tar.open(b.path, "r")

	return err


@@ 82,48 83,51 @@ fn (b: ^FileBox) exists*(file: str = "version"): bool {
	}

	if b.handle._ == null {
		if err := b.open(); err != 0 {
		if err := b.open(); err.code != 0 {
			return false
		}
	}
	
	dat, err := b.handle.read(file)
	return err == 0
	return err.code == 0
}

fn (b: ^FileBox) download*(file: str = "box.tar"): ([]uint8, bool, str) {
fn (b: ^FileBox) download*(file: str = "box.tar"): ([]char, std.Err) {
	if file == "box.tar" {
		f := std.fopen(b.path, "rb")
		if f == null {
			return {}, false, "File not found"
		f, err := std.fopen(b.path, "rb")
		if err.code != 0 {
			return {}, err
		}
		d, err := std.freadall(f)
		if err.code != 0 {
			return {}, err
		}
		d := io.mkFile(f).read()
		std.fclose(f)

		return d, true, ""
		return d, {}
	}
	
	if file == "version" {
		sb, err := os.stat(b.path)
		if err != 0 {
			return {}, false, os.strerror(err)
		sb, oserr := os.stat(b.path)
		if oserr != 0 {
			return {}, std.error(oserr, os.strerror(oserr))
		}
		
		return []uint8([]char(std.itoa(sb.mtime))), true, ""
		return []char(std.itoa(sb.mtime)), {}
	}

	if b.handle._ == null {
		if err := b.open(); err != 0 {
			return {}, false, tar.strerror(err)
		if err := b.open(); err.code != 0 {
			return {}, err
		}
	}

	dat, err := b.handle.read(file)
	if err != 0 {
		return {}, false, tar.strerror(err)
	if err.code != 0 {
		return {}, err
	}
	
	return dat, true, ""
	return dat, {}
}

fn (b: ^FileBox) encode*(): str {


@@ 221,16 225,30 @@ fn parseDep*(dep: str): Box {
	return null
}

fn getMeta*(path: str): (bool, Meta) {
fn getMeta*(path: str): (Meta, std.Err) {
	if !os.isfile(path) {
		return false, {}
		return {}, std.error(1, "file not found", path)
	}
	
	f := std.fopen(path, "r")
	r := ^map[str]any(json.parse(str([]char(io.mkFile(f).read()))))
	f, err := std.fopen(path, "r")
	if err.code != 0 {
		return {}, err
	}

	d, err := std.freadall(f)
	if err.code != 0 {
		return {}, err
	}

	parsed := json.parse(str(d))
	std.fclose(f)
	if errs := ^[]json.Error(parsed); errs != null {
		return {}, std.error(1, sprintf("json parser error: %d %s", errs[0].message, errs[0].lno), path)
	}

	r := ^map[str]any(parsed)
	if r == null {
		return false, {}
		return {}, std.error(1, "invalid top level json object", path)
	}
	   
	m := Meta{}


@@ 238,50 256,43 @@ fn getMeta*(path: str): (bool, Meta) {
	if ^str(r["name"]) != null {
		m.name = str(r["name"])
	} else {
		fprintf(std.stderr(), "%s: missing key 'name'\n", path)
		return false, {}
		return {}, std.error(1, "missing key 'name'", path)
	}
	
	if ^str(r["version"]) != null {
		m.version = str(r["version"])
	} else {
		fprintf(std.stderr(), "%s: missing key 'version'\n", path)
		return false, {}
		return {}, std.error(1, "missing key 'version'", path)
	}
	
	if ^str(r["author"]) != null {
		m.author = str(r["author"])
	} else {
		fprintf(std.stderr(), "%s: missing key 'author'\n", path)
		return false, {}
		return {}, std.error(1, "missing key 'author'", path)
	}

	if ^str(r["license"]) != null {
		m.license = str(r["license"])
	} else {
		fprintf(std.stderr(), "%s: missing key 'license'\n", path)
		return false, {}
		return {}, std.error(1, "missing key 'license'", path)
	}

	if ^str(r["description"]) != null {
		m.description = str(r["description"])
	} else {
		fprintf(std.stderr(), "%s: missing key 'description'\n", path)
		return false, {}
		return {}, std.error(1, "missing key 'description'", path)
	}

	if ^str(r["readme"]) != null {
		m.readme = str(r["readme"])
	} else {
		fprintf(std.stderr(), "%s: missing key 'readme'\n", path)
		return false, {}
		return {}, std.error(1, "missing key 'readme'", path)
	}

	if ^str(r["link"]) != null {
		m.link = str(r["link"])
	} else {
		fprintf(std.stderr(), "%s: missing key 'link'\n", path)
		return false, {}
		return {}, std.error(1, "missing key 'link'", path)
	}

	if ^[]any(r["dependencies"]) != null {


@@ 289,20 300,17 @@ fn getMeta*(path: str): (bool, Meta) {
		for i, v in []any(r["dependencies"]) {
			m.dependencies[i] = parseDep(str(v))
			if !valid(m.dependencies[i]) {
				fprintf(std.stderr(), "Malformed dependency %s\n", str(v))
				return false, {}
				return {}, std.error(1, sprintf("malformed dependency %s", str(v)), path)
			}
		}
	} else {
		fprintf(std.stderr(), "%s: missing key 'dependencies'\n", path)
		return false, {}
		return {}, std.error(1, "missing key 'dependencies'", path)
	}

	if ^[]any(r["include"]) != null {
		m.include = []str([]any(r["include"]))
	} else {
		fprintf(std.stderr(), "%s: missing key 'include'\n", path)
		return false, {}
		return {}, std.error(1, "missing key 'include'", path)
	}

	if ^str(r["run"]) != null {


@@ 325,7 333,7 @@ fn getMeta*(path: str): (bool, Meta) {
		m.postBuild = str(r["post_build"])
	}

	return true, m
	return m, {}
}

type ApiResp* = struct {


@@ 335,7 343,7 @@ type ApiResp* = struct {
	msg: str
}

fn parseResp(r: io.Reader, resp: http.Response): ApiResp {
fn parseResp(resp: http.Response): ApiResp {
	if !resp.ok {
		return {
			ok: false,


@@ 347,7 355,7 @@ fn parseResp(r: io.Reader, resp: http.Response): ApiResp {
	out := ApiResp{}
	out.status = resp.status
	
	payload := json.parse(str([]char(r.read())))
	payload := json.parse(str(resp.body))
	if errs := ^[]json.Error(payload); errs != null {
		out.ok = false
		out.status = -2


@@ 375,7 383,6 @@ fn parseResp(r: io.Reader, resp: http.Response): ApiResp {

fn get*(url, endpoint: str, token: str = ""): ApiResp {
	url = sprintf("%s/%s", url, endpoint)
	rw := io.mkMemory({})
	par := http.GetParams{
		userAgent: "umbox-cli"
	}


@@ 384,12 391,11 @@ fn get*(url, endpoint: str, token: str = ""): ApiResp {
		par.headers = append(par.headers, "Authorization: UmBox " + token)
	}
	
	return parseResp(rw, http.get(url, rw, par))
	return parseResp(http.get(url, par))
}

fn post*(data: io.Reader, url, endpoint: str, token: str = ""): ApiResp {
fn post*(data: []char, url, endpoint: str, token: str = ""): ApiResp {
	url = sprintf("%s/%s", url, endpoint)
	rw := io.mkMemory({})
	par := http.PostParams{
		userAgent: "umbox-cli"
	}


@@ 398,5 404,5 @@ fn post*(data: io.Reader, url, endpoint: str, token: str = ""): ApiResp {
		par.headers = append(par.headers, "Authorization: UmBox " + token)
	}
	
	return parseResp(rw, http.post(url, data, rw, par))
	return parseResp(http.post(url, data, par))
}

M src/init.um => src/init.um +23 -24
@@ 10,43 10,43 @@ import (
	"../umbox/os/os.um"
)

fn downloadPreset(box: common.Box): (common.Meta, str) {
fn downloadPreset(box: common.Box): (common.Meta, std.Err) {
	if !box.exists("init.tar") {
		return {}, sprintf("Box '%s' doesn't have an init preset", box.getName())
		return {}, std.error(254, sprintf("box '%s' doesn't have an init preset", box.getName()))
	}

	dat, ok, msg := box.download("init.tar")
	if !ok {
		error(msg)
	dat, err := box.download("init.tar")
	if err.code != 0{
		return {}, err
	}

	tf, err := tar.openBytes(dat)
	if err != 0 {
		return {}, tar.strerror(err)
	tf, err := tar.openBytes([]uint8(dat))
	if err.code != 0 {
		return {}, err
	}

	err = tf.extract(".")
	if err != 0 {
		return {}, tar.strerror(err)
	if err.code != 0 {
		return {}, err
	}

	ok, m := common.getMeta("box.json")
	if !ok {
		return m, "box.json is invalid"
	m, err := common.getMeta("box.json")
	if err.code != 0 {
		return m, err
	}
	return m, ""
	return m, {}
}

fn run*(url: str, argi: int) {
fn run*(url: str, argi: int): std.Err {
	if std.argc() < 2 {
		fprintf(std.stderr(), "Usage: umbox init [<preset>]\n")
		return
		return {}
	}
	
	if ls, err := os.listdir("."); len(ls) > 2 {
		fprintf(std.stderr(), "%v\n", ls)
		fprintf(std.stderr(), "Refusing to init a box in a non-empty directory\n")
		return
		return {}
	}

	m := common.Meta{


@@ 58,23 58,22 @@ fn run*(url: str, argi: int) {
		preset := common.parseDep(std.argv(argi))
		if !preset.exists() {
			fprintf(std.stderr(), "Box '%s' doesn't exist\n", preset)
			return
			return {}
		}

		var err: str
		var err: std.Err
		m, err = downloadPreset(preset)
		if len(err) != 0 {
			fprintf(std.stderr(), "Could not download preset: %s\n", err)
			return
		if err.code != 0 {
			return err
		}
	}

	name, err := os.getCwd()
	m.name = filepath.file(name)

	f := std.fopen("box.json", "w")
	f := std.fopen("box.json", "w").item0
	fprintf(f, "%s", m.toJSON())
	std.fclose(f)

	update.run(url, std.argc())
	return update.run(url, std.argc())
}

M src/install.um => src/install.um +8 -9
@@ 6,23 6,22 @@ import (
	"../umbox/io/io.um"
)

fn run*(url: str, argi: int) {
fn run*(url: str, argi: int): std.Err {
	if argi != std.argc() - 1 {
		fprintf(std.stderr(), "Usage: umbox install <box_name>\n")
		return
		return {}
	}
	
	box := common.parseDep(std.argv(argi))

	ok, meta := common.getMeta("box.json") 
	if !ok {
		fprintf(std.stderr(), "Not in an UmBox directory.\n")
		return
	meta, err := common.getMeta("box.json") 
	if err.code != 0 {
		return err
	}

	if !box.exists() {
		fprintf(std.stderr(), "Box '%s' does not exist.\n", box.getName())
		return
		return {}
	}

	exists := false


@@ 37,9 36,9 @@ fn run*(url: str, argi: int) {
		meta.dependencies = append(meta.dependencies, box)
	}

	f := std.fopen("box.json", "w")
	f := std.fopen("box.json", "w").item0
	fprintf(f, "%s", meta.toJSON())
	std.fclose(f)

	update.run(url, std.argc())
	return update.run(url, std.argc())
}

M src/register.um => src/register.um +6 -4
@@ 6,10 6,10 @@ import (
	"../umbox/io/io.um"
)

fn run*(url: str, argi: int) {
fn run*(url: str, argi: int): std.Err {
	if argi != std.argc() - 1 {
		fprintf(std.stderr(), "Usage: umbox register <query>\n")
		return
		return {}
	}
	
	name := std.argv(argi)


@@ 17,8 17,10 @@ fn run*(url: str, argi: int) {
	w := io.mkMemory({})
	resp := common.get(url, sprintf("api/register/%s", name))
	if !resp.ok {
		error(resp.msg)
		return std.error(1, resp.msg)
	}
	      
     
	printf("%s\n", str(resp.data))

	return {}
}

M src/remove.um => src/remove.um +7 -8
@@ 6,18 6,17 @@ import (
	"../umbox/io/io.um"
)

fn run*(url: str, argi: int) {
fn run*(url: str, argi: int): std.Err {
	if argi != std.argc() - 1 {
		fprintf(std.stderr(), "Usage: umbox install <package_name>\n")
		return
		return {}
	}

	box := common.parseDep(std.argv(argi))

	ok, meta := common.getMeta("box.json") 
	if !ok {
		fprintf(std.stderr(), "Not in an UmBox directory.\n")
		return
	meta, err := common.getMeta("box.json") 
	if err.code != 0 {
		return err
	}

	for i,dep in meta.dependencies {


@@ 27,9 26,9 @@ fn run*(url: str, argi: int) {
		}
	}
      
	f := std.fopen("box.json", "w")
	f := std.fopen("box.json", "w").item0
	fprintf(f, "%s", meta.toJSON())
	std.fclose(f)

	update.run(url, std.argc())
	return update.run(url, std.argc())
}

M src/run.um => src/run.um +7 -6
@@ 7,11 7,10 @@ import (
	"../umbox/strings/strings.um"
)

fn run*(url: str, argi: int) {
	ok, meta := common.getMeta("box.json") 
	if !ok {
		fprintf(std.stderr(), "Not in an UmBox directory.\n")
		return
fn run*(url: str, argi: int): std.Err {
	meta, err := common.getMeta("box.json") 
	if err.code != 0 {
		return err
	}
	
	args := ""


@@ 52,6 51,8 @@ fn run*(url: str, argi: int) {

		std.system(cmd)
	}
	

	std.system(args)

	return {}
}

M src/search.um => src/search.um +5 -3
@@ 8,17 8,17 @@ import (
	"common.um"
)

fn run*(url: str, argi: int) {
fn run*(url: str, argi: int): std.Err {
	if argi != std.argc() - 1 {
		fprintf(std.stderr(), "Usage: umbox search <query>\n")
		return
		return {}
	}

	query := std.argv(argi)

	resp := common.get(url, sprintf("api/search/%s", query))
	if !resp.ok {
		error(resp.msg)
		return std.error(1, resp.msg)
	}

	res := []any(resp.data)


@@ 26,4 26,6 @@ fn run*(url: str, argi: int) {
		o := map[str]any(r)
		printf("%s - %s\n", str(o["name"]), str(o["description"]))
	}

	return {}
}

M src/update.um => src/update.um +76 -54
@@ 12,56 12,53 @@ import (
	"common.um"
)

fn fetchDeps(deps: []common.Box, versions: ^map[str]str)
fn fetchDeps(deps: []common.Box, versions: ^map[str]str): std.Err

var (
	copyDeps: bool = false
)

fn copyFile(p1, p2: str) {
	f1 := std.fopen(p1, "rb")
	r := io.mkFile(f1)
	f2 := std.fopen(p2, "wb")
	w := io.mkFile(f2)

	w.write(r.read())

	f1, err := std.fopen(p1, "rb")
	d, err := std.freadall(f1)
	f2, err := std.fopen(p2, "wb")
	std.fwrite(f2, d)
	std.fclose(f1)
	std.fclose(f2)
}

fn fetchDep(dep: common.Box, versions: ^map[str]str) {
fn fetchDep(dep: common.Box, versions: ^map[str]str): std.Err {
	if !dep.exists() {
		fprintf(std.stderr(), "No such package: %s\n", dep)
		exit()
		return std.error(255, "package not found")
	}
	
	os.mkdirp(filepath.join("umbox", dep.getName()))

	// fetch and extract box.tar
	d, ok, msg := dep.download("box.tar")
	if !ok {
		error(msg)
	d, err := dep.download("box.tar")
	if err.code != 0 {
		return err
	}
	tf, err := tar.openBytes(d)
	if err != 0 {
		fprintf(std.stderr(), "Error parsing box.tar: %s\n", tar.strerror(err))
		exit()
	tf, err := tar.openBytes([]uint8(d))
	if err.code != 0 {
		return err
	}
	
	err = tf.extract(filepath.join("umbox", dep.getName(), ""))
	if err != 0 {
		fprintf(std.stderr(), "Error extracting box.tar: %s\n", tar.strerror(err))
		exit()
	if err.code != 0 {
		return err
	}
	
	// Fetch dependencies
	ok, meta := common.getMeta(filepath.join("umbox", dep.getName(), "box.json"))
	if !ok {
		fprintf(std.stderr(), "Error parsing %s\n", filepath.join("umbox", dep.getName(), "box.json"))
		exit()
	meta, err := common.getMeta(filepath.join("umbox", dep.getName(), "box.json"))
	if err.code != 0 {
		return err
	}

	err = fetchDeps(meta.dependencies, versions)
	if err.code != 0 {
		return err
	}
	fetchDeps(meta.dependencies, versions)

	// Create symlinks for dependencies
	os.mkdirp(filepath.join("umbox", dep.getName(), "umbox"))


@@ 77,71 74,89 @@ fn fetchDep(dep: common.Box, versions: ^map[str]str) {
				copyFile(file, file2)
			}, { excludeDirs: true })
		} else {
			err := os.link(filepath.join("..", "..", d.getName()), filepath.join("umbox", dep.getName(), "umbox", d.getName()))
			if err != 0 && os.getPlatform() == os.PlatformWindows {
			oserr := os.link(filepath.join("..", "..", d.getName()), filepath.join("umbox", dep.getName(), "umbox", d.getName()))
			if oserr != 0 && os.getPlatform() == os.PlatformWindows {
				std.system("mklink /j " + filepath.join("umbox", dep.getName(), "umbox", d.getName()) + " " + filepath.join("umbox", d.getName()))
			}
		}
	}

	{
		d, ok, msg := dep.download("version")
		if !ok {
			error(msg)
		d, err := dep.download("version")
		if err.code != 0 {
			return err
		}
		versions[dep.getName()] = str([]char(d))
	}

	return {}
}

fn fetchDeps(deps: []common.Box, versions: ^map[str]str) {
fn fetchDeps(deps: []common.Box, versions: ^map[str]str): std.Err {
	for i,dep in deps {
		// If the dependency doesn't exist, fetch it.
		if !os.isdir(filepath.join("umbox", dep.getName())) {
			fprintf(std.stderr(), "Downloading %s...\n", dep.getName())
			fetchDep(dep, versions)
			err := fetchDep(dep, versions)
			if err.code != 0 {
				return err
			}
			continue
		}

		// If the dependency doesn't have a known version, fetch it.
		if !validkey(versions^, dep.getName()) {
			fprintf(std.stderr(), "Downloading %s...\n", dep.getName())
			fetchDep(dep, versions)
			err := fetchDep(dep, versions)
			if err.code != 0 {
				return err
			}
			continue
		}

		// Check for new versions.
		{
			d, ok, msg := dep.download("version")
			if !ok {
				error(msg)
			d, err := dep.download("version")
			if err.code != 0 {
				return err
			}
			if versions[dep.getName()] != str([]char(d)) {
				fprintf(std.stderr(), "Updating %s...\n", dep.getName())
				fetchDep(dep, versions)
				err = fetchDep(dep, versions)
				if err.code != 0 {
					return err
				}
				continue
			}
		}
	}

	return {}
}

fn touchDep(dep: str, touched: ^map[str]bool) {
fn touchDep(dep: str, touched: ^map[str]bool): std.Err {
	if validkey(touched^, dep) {
		return
		return {}
	}

	touched[dep] = true

	ok, meta := common.getMeta(filepath.join("umbox", dep, "box.json"))
	if !ok {
		return
	meta, err := common.getMeta(filepath.join("umbox", dep, "box.json"))
	if err.code != 0 {
		return err
	}

	for i, dep in meta.dependencies {
		touchDep(dep.getName(), touched)
		err = touchDep(dep.getName(), touched)
		if err.code != 0 {
			return err
		}
	}

	return {}
}

fn run*(url: str, argi: int) {
fn run*(url: str, argi: int): std.Err {
	for argi < std.argc() {
		v := std.argv(argi)



@@ 150,23 165,22 @@ fn run*(url: str, argi: int) {
			printf("NOTE: The -c flag is experimental. It is advised to always remove the umbox/\ndirectory before running update with this flag\n")
		} else {
			printf("usage: umbox update [ -c ]\n  -c: Don't use symlinks\n")
			return
			return {}
		}

		argi++
	}

	ok, meta := common.getMeta("box.json")
	if !ok {
		fprintf(std.stderr(), "Not in an UmBox directory.\n")
		return
	meta, err := common.getMeta("box.json")
	if err.code != 0 {
		return err
	}
	
	os.mkdirp("umbox")

	versions := map[str]str{}
	if os.isfile(filepath.join("umbox", "versions.json")) {
		f := std.fopen(filepath.join("umbox", "versions.json"), "r")
		f := std.fopen(filepath.join("umbox", "versions.json"), "r").item0
		r := map[str]any(json.parse(str([]char(io.mkFile(f).read()))))
		std.fclose(f)
		for k,v in r {


@@ 174,11 188,17 @@ fn run*(url: str, argi: int) {
		}
	}

	fetchDeps(meta.dependencies, &versions)
	err = fetchDeps(meta.dependencies, &versions)
	if err.code != 0 {
		return err
	}

	touched := map[str]bool{}
	for i,dep in meta.dependencies {
		touchDep(dep.getName(), &touched)
		err = touchDep(dep.getName(), &touched)
		if err.code != 0 {
			return err
		}
	}
		
	enc := jsonenc.mk(true)


@@ 191,7 211,7 @@ fn run*(url: str, argi: int) {
	}
	enc.endObject()

	f := std.fopen(filepath.join("umbox", "versions.json"), "w")
	f := std.fopen(filepath.join("umbox", "versions.json"), "w").item0
	fprintf(f, "%s", enc.toStr())
	std.fclose(f)
	


@@ 213,4 233,6 @@ fn run*(url: str, argi: int) {
			os.remove(filepath.join("umbox", d))
		}
	}

	return {}
}

M src/upload.um => src/upload.um +12 -13
@@ 9,10 9,10 @@ import (
	"common.um"
)

fn run*(url: str, argi: int) {
fn run*(url: str, argi: int): std.Err {
	if argi != std.argc() - 2 {
		fprintf(std.stderr(), "usage: umbox upload <token> <file>\n")
		return
		return {}
	}
	
	token := std.argv(argi)


@@ 20,31 20,30 @@ fn run*(url: str, argi: int) {
	
	if !os.isfile(file) {
		fprintf(std.stderr(), "File not found\n")
		return
		return {}
	}
		   
	if len(token) != 64 {
		fprintf(std.stderr(), "Invalid token\n")
		return
		return {}
	}
	
	ok, meta := common.getMeta("box.json")
	if !ok {
		fprintf(std.stderr(), "Not in an UmBox directory\n")
		return
	meta, err := common.getMeta("box.json")
	if err.code != 0 {
		return err
	}
				

	tokenHash := blake2.blake2b([]uint8([]char(token)))
	tokenHashStr := ""
	for i,b in tokenHash {
		tokenHashStr += sprintf("%02x", b)
	}
	f := std.fopen(file, "rb")
	r := io.mkFile(f)
	f := std.fopen(file, "rb").item0
	
	reqUrl := sprintf("api/package/%s/upload/%s", meta.name, filepath.file(file))
	resp := common.post(r, url, reqUrl, tokenHashStr)
	resp := common.post(std.freadall(f).item0, url, reqUrl, tokenHashStr)
	if !resp.ok {
		error(resp.msg)
		return std.error(1, resp.msg)
	}
	return {}
}