Initial commit

This commit is contained in:
marked 2025-03-02 05:07:57 +01:00
commit f06242ebb5
33 changed files with 6060 additions and 0 deletions

16
src/init.luau Normal file
View file

@ -0,0 +1,16 @@
--!strict
local release = require("./release")
local sync = require("./sync")
local res = sync("jecs")
if not res.ok then
print(`Can't continue: {res.err}`)
return
end
if res.val == false then
print(`No changes made. Aborting.`)
return
end
release("jecs", { pesde = "marked/jecs_nightly", wally = "mark-marks/jecs-nightly" })

10
src/read_version.luau Normal file
View file

@ -0,0 +1,10 @@
--!strict
local fs = require("@lune/fs")
local serde = require("@lune/serde")
local types = require("./types")
local manifest_contents = fs.readFile("jecs/pesde.toml")
local manifest: types.PesdeManifest = serde.decode("toml", manifest_contents) or error("Couldn't decode manifest.")
local jecs_version = manifest.version
print(jecs_version)

161
src/release.luau Normal file
View file

@ -0,0 +1,161 @@
--!strict
local datetime = require("@lune/datetime")
local fs = require("@lune/fs")
local net = require("@lune/net")
local process = require("@lune/process")
local serde = require("@lune/serde")
local stdio = require("@lune/stdio")
local progress_bar = require("./util/progress")
local result = require("./util/result")
local types = require("./types")
-- Returns an ISO 8601 date (YYYYmmddThhmmssZ)
local function iso_date_light(now: datetime.DateTime): string
return now:formatUniversalTime("%Y%m%dT%H%M%SZ")
end
local function make_pesde_manifest(version: string, scope: string): types.PesdeManifest
return {
name = scope,
version = version,
authors = { "jecs authors" },
repository = "https://git.devmarked.win/jecs-nightly",
license = "MIT",
includes = {
"init.luau",
"pesde.toml",
"README.md",
"CHANGELOG.md",
"LICENSE",
".luaurc",
},
target = {
environment = "luau",
lib = "jecs.luau",
},
indices = {
default = "https://github.com/daimond113/pesde-index",
},
}
end
local function make_wally_manifest(version: string, scope: string): types.WallyManifest
return {
package = {
name = scope,
version = version,
registry = "https://github.com/UpliftGames/wally-index",
realm = "shared",
license = "MIT",
include = {
"default.project.json",
"jecs.luau",
"wally.toml",
"README.md",
"CHANGELOG.md",
"LICENSE",
},
exclude = { "**" },
},
}
end
local function round_to(n: number, places: number)
local x = 10 ^ (places or 0)
return math.round(n * x) / x
end
--- Fetches the given file raw from the jecs github
local function fetch_raw(file: string): result.Identity<string>
local res = net.request(`https://raw.githubusercontent.com/Ukendio/jecs/refs/heads/main/{file}`)
if not res.ok then
return result(false, `Not ok: {res.statusMessage} @ {res.statusCode}`)
end
return result(true, res.body)
end
local function release(origin: string, scopes: { wally: string, pesde: string }, dry: boolean?): result.Identity<nil>
local begin = os.clock()
local now = datetime.now()
local progress = progress_bar
.new()
:withStage("init", "Initializing")
:withStage("pull", "Pull latest version")
:withStage("prepare", "Preparing manifests")
:withStage("release (pesde)", "Releasing on pesde")
:withStage("release (wally)", "Releasing on wally")
progress:start()
dry = dry or true
if not fs.metadata(origin).exists then
progress:stop()
stdio.ewrite(`🔥 {origin} is not a valid directory which exists.\n`)
return result(false, `{origin} is not a valid directory which exists.`)
end
progress:nextStage()
local wally_contents = fetch_raw("wally.toml")
if not wally_contents.ok then
progress:stop()
stdio.ewrite(`🔥 Couldn't get the jecs wally manifest:\n{wally_contents.err}\n`)
return result(false, `Couldn't get the jecs wally manifest:\n{wally_contents.err}`)
end
local parsed: types.WallyManifest = serde.decode("toml", wally_contents.val)
local version = `{parsed.package.version}-nightly.{iso_date_light(now)}`
progress:nextStage()
do
local manifest = make_pesde_manifest(version, scopes.pesde)
local encoded = serde.encode("toml", manifest)
fs.writeFile(`{origin}/pesde.toml`, encoded)
end
do
local manifest = make_wally_manifest(version, scopes.wally)
local encoded = serde.encode("toml", manifest)
fs.writeFile(`{origin}/wally.toml`, encoded)
end
progress:nextStage()
local cwd = process.cwd .. origin
if not dry then
process.spawn("pesde", { "publish", "-y" }, { cwd = cwd })
else
process.spawn("pesde", { "publish", "-d", "-y" }, { cwd = cwd })
end
progress:nextStage()
if not dry then
process.spawn("wally", { "publish" }, { cwd = cwd })
else
process.spawn("wally", { "package", "--output", "wally.tar.gz" }, { cwd = cwd })
end
progress:stop()
local took = round_to((os.clock() - begin) * 1_000, 2)
if not dry then
print(`🚀 Published packages {stdio.style("dim")}(took {took}ms){stdio.style("reset")}`)
else
print(`📦 Packaged packages {stdio.style("dim")}(took {took}ms){stdio.style("reset")}`)
end
print({
pesde = `{scopes.pesde}@{version}`,
wally = `{scopes.wally}@{version}`,
})
return result(true, nil)
end
return release

110
src/sync.luau Normal file
View file

@ -0,0 +1,110 @@
--!strict
local fs = require("@lune/fs")
local net = require("@lune/net")
local stdio = require("@lune/stdio")
local progress_bar = require("./util/progress")
local result = require("./util/result")
--- Fetches the given file raw from the jecs github
local function fetch_raw(file: string): result.Identity<string>
local res = net.request(`https://raw.githubusercontent.com/Ukendio/jecs/refs/heads/main/{file}`)
if not res.ok then
return result(false, `Not ok: {res.statusMessage} @ {res.statusCode}`)
end
return result(true, res.body)
end
local function save_if_diff(filepath: string, contents: string): result.Identity<nil>
if not fs.metadata(filepath).exists then
fs.writeFile(filepath, contents)
return result(true, nil)
end
local existing = fs.readFile(filepath)
if existing == contents then
return result(false, "Contents are the same.")
end
fs.writeFile(filepath, contents)
return result(true, nil)
end
local function round_to(n: number, places: number)
local x = 10 ^ (places or 0)
return math.round(n * x) / x
end
--- Synchronizes the required files from the jecs main branch.
local function sync(to: string): result.Identity<boolean>
local begin = os.clock()
local progress = progress_bar
.new()
:withStage("init", "Initializing")
:withStage("fetch", "Fetching latest files")
:withStage("save", "Saving files")
progress:start()
if not fs.metadata(to).exists then
fs.writeDir(to)
end
progress:nextStage()
local includes = {
"jecs.luau",
"README.md",
"CHANGELOG.md",
"LICENSE",
".luaurc",
"default.project.json",
}
local sources = {}
for _, file in includes do
local contents = fetch_raw(file)
if not contents.ok then
progress:stop()
stdio.ewrite(`🔥 Couldn't get the latest source for {file}:\n{contents.err}\n`)
return result(false, `Couldn't get the latest source for {file}.`)
end
sources[file] = contents.val
end
progress:nextStage()
local sources_modified = {}
local any_changed = false
for file, contents in sources do
local res = save_if_diff(`{to}/{file}`, contents)
if res.ok then
any_changed = true
table.insert(sources_modified, file)
end
end
if not any_changed then
progress:stop()
local took = round_to((os.clock() - begin) * 1_000, 2)
print(
`🕛 Finished synchronizing, no changes since latest source {stdio.style("dim")}(took {took}ms){stdio.style(
"reset"
)}`
)
return result(true, false)
end
progress:stop()
local took = round_to((os.clock() - begin) * 1_000, 2)
print(`🪨 Finished synchronizing {stdio.style("dim")}(took {took}ms){stdio.style("reset")}`)
print(`Changed files:`)
print(sources_modified)
return result(true, true)
end
return sync

78
src/types.luau Normal file
View file

@ -0,0 +1,78 @@
--!strict
export type SPDXLicense =
"MIT"
| "Apache-2.0"
| "BSD-2-Clause"
| "BSD-3-Clause"
| "GPL-2.0"
| "GPL-3.0"
| "LGPL-2.1"
| "LGPL-3.0"
| "MPL-2.0"
| "ISC"
| "Unlicense"
| "WTFPL"
| "Zlib"
| "CC0-1.0"
| "CC-BY-4.0"
| "CC-BY-SA-4.0"
| "BSL-1.0"
| "EPL-2.0"
| "AGPL-3.0"
export type DependencySpecifier = ((
{ name: string, version: string, index: string? }
| { workspace: string, version: string }
| { repo: string, rev: string, path: string? }
) & {
target: string?,
}) | { wally: string, version: string, index: string? }
export type PackageTarget = {
environment: "luau" | "lune" | "roblox" | "roblox_server",
lib: string,
} | {
environment: "luau" | "lune",
bin: string,
}
export type PesdeManifest = {
name: string,
version: string,
description: string?,
license: SPDXLicense?,
authors: { string }?,
repository: string?,
private: boolean?,
includes: { string }?,
pesde_version: string?,
workspace_members: { string }?,
target: PackageTarget,
build_files: { string }?,
scripts: { [string]: string }?,
indices: { [string]: string },
wally_indices: { [string]: string }?,
overrides: { [string]: DependencySpecifier }?,
patches: { [string]: { [string]: string } }?,
place: { [string]: string }?,
dependencies: { [string]: DependencySpecifier }?,
peer_dependencies: { [string]: DependencySpecifier }?,
dev_dependencies: { [string]: DependencySpecifier }?,
}
export type WallyManifest = {
package: {
name: string,
version: string,
registry: string,
realm: string,
license: string?,
exclude: { string }?,
include: { string }?,
},
dependencies: {
[string]: string,
}?,
}
return "<types>"

97
src/util/progress.luau Normal file
View file

@ -0,0 +1,97 @@
--!strict
--> Inspired by Rokit's progress bar: https://github.com/rojo-rbx/rokit/blob/a303faf/src/util/progress.rs
-- Original: https://github.com/pesde-pkg/tooling/blob/main/toolchainlib/src/utils/progress.luau
local task = require("@lune/task")
local stdio = require("@lune/stdio")
local result = require("./result")
-- FORMAT: {SPINNER} {MESSAGE} {BAR} {STAGE}
local SPINNERS = { "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏" }
local BAR_COMPONENT = "▇"
local MAX_BAR_LENGTH = 30
local ProgressBar = {}
type ProgressBar = {
stages: { { tag: string, message: string } },
currentStageIndex: number,
finished: boolean,
thread: thread?,
}
export type ProgressBarImpl = typeof(setmetatable({} :: ProgressBar, { __index = ProgressBar }))
function ProgressBar.new(): ProgressBarImpl
return setmetatable(
{
stages = {},
currentStageIndex = 1,
finished = false,
} :: ProgressBar,
{
__index = ProgressBar,
}
)
end
function ProgressBar.withStage(self: ProgressBarImpl, tag: string, msg: string): ProgressBarImpl
table.insert(self.stages, { tag = tag, message = msg })
return self
end
function ProgressBar.start(self: ProgressBarImpl)
local BAR_LENGTH = MAX_BAR_LENGTH // #self.stages
local TOTAL_BAR_LENGTH = BAR_LENGTH * #self.stages
local BAR = string.rep(BAR_COMPONENT, BAR_LENGTH)
local MAX_MESSAGE_LENGTH = 0
for _, stage in self.stages do
local len = #stage.message
if len > MAX_MESSAGE_LENGTH then
MAX_MESSAGE_LENGTH = len
end
end
self.thread = task.spawn(function()
while true do
for _, spinner in SPINNERS do
if self.finished then
return
end
local stage = self.stages[self.currentStageIndex]
stdio.ewrite(
`\x1b[2K\x1b[0G{stdio.color("cyan")}{spinner} {stage.message}{stdio.color("reset")}{string.rep(
" ",
MAX_MESSAGE_LENGTH - #stage.message
)} [{stdio.style("dim")}{string.rep(BAR, self.currentStageIndex)}{string.rep(
" ",
TOTAL_BAR_LENGTH - (BAR_LENGTH * self.currentStageIndex)
)}{stdio.style("reset")}] {stdio.style("bold")}{self.currentStageIndex} / {#self.stages}{stdio.style(
"reset"
)}`
)
task.wait(0.1)
end
end
end)
end
function ProgressBar.stop(self: ProgressBarImpl)
-- Trigger upvalue, kill thread and clean progress bar remnant
self.finished = true
stdio.ewrite("\x1b[2K\x1b[0G")
end
function ProgressBar.nextStage(self: ProgressBarImpl): result.Identity<nil>
local inc = self.currentStageIndex + 1
if inc > #self.stages then
-- TODO: Make this a result
self.finished = true
return result(false, "OutOfBounds - Attempted to advance past last stage")
end
self.currentStageIndex = inc
return result(true, nil)
end
return ProgressBar

24
src/util/result.luau Normal file
View file

@ -0,0 +1,24 @@
--!strict
export type Identity<T> = {
ok: true,
val: T,
} | {
ok: false,
err: string,
}
local function construct<T>(ok: boolean, value: T & string): Identity<T>
if ok then
return {
ok = true,
val = value,
}
else
return {
ok = false,
err = value,
}
end
end
return (construct :: any) :: (<T>(ok: true, value: T) -> Identity<T>) & (<T>(ok: false, value: string) -> Identity<T>)