Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.runseal/** text eol=lf
4 changes: 4 additions & 0 deletions .github/workflows/guard.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ jobs:
with:
components: rustfmt, clippy

- uses: denoland/setup-deno@v2
with:
deno-version: v2.x

- name: Format
if: runner.os != 'Windows'
run: |
Expand Down
12 changes: 12 additions & 0 deletions .github/workflows/release-beta.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ jobs:
with:
ref: ${{ inputs.ref }}

- uses: denoland/setup-deno@v2
with:
deno-version: v2.x

- name: Validate R2 access
run: bash .github/scripts/release/r2/check.sh

Expand All @@ -69,6 +73,10 @@ jobs:
with:
components: rustfmt, clippy

- uses: denoland/setup-deno@v2
with:
deno-version: v2.x

- name: Format
run: cargo fmt --all --check

Expand Down Expand Up @@ -164,6 +172,10 @@ jobs:
path: dist/${{ needs.metadata.outputs.release_version }}
merge-multiple: true

- uses: denoland/setup-deno@v2
with:
deno-version: v2.x

- name: Resolve guard version hash
id: guard_hash
shell: bash
Expand Down
12 changes: 12 additions & 0 deletions .github/workflows/release-stable.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ jobs:
with:
ref: ${{ inputs.ref }}

- uses: denoland/setup-deno@v2
with:
deno-version: v2.x

- name: Validate R2 access
run: bash .github/scripts/release/r2/check.sh

Expand All @@ -68,6 +72,10 @@ jobs:
with:
components: rustfmt, clippy

- uses: denoland/setup-deno@v2
with:
deno-version: v2.x

- name: Format
run: cargo fmt --all --check

Expand Down Expand Up @@ -162,6 +170,10 @@ jobs:
path: dist/${{ needs.metadata.outputs.release_version }}
merge-multiple: true

- uses: denoland/setup-deno@v2
with:
deno-version: v2.x

- name: Resolve guard version hash
id: guard_hash
shell: bash
Expand Down
9 changes: 9 additions & 0 deletions .runseal/deno.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"compilerOptions": {
"strict": true
},
"fmt": {
"lineWidth": 100,
"semiColons": true
}
}
201 changes: 201 additions & 0 deletions .runseal/lib/runseal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
const decoder = new TextDecoder();
const encoder = new TextEncoder();

export type CommandOptions = {
cwd?: string;
env?: Record<string, string>;
stdin?: "inherit" | "null" | "piped";
stdout?: "inherit" | "null" | "piped";
stderr?: "inherit" | "null" | "piped";
};

const blockedInheritedEnv = new Set([
"DYLD_FALLBACK_LIBRARY_PATH",
"DYLD_INSERT_LIBRARIES",
"DYLD_LIBRARY_PATH",
"LD_PRELOAD",
"LD_LIBRARY_PATH",
]);

function hasBlockedInheritedEnv(): boolean {
for (const key of blockedInheritedEnv) {
if (Deno.env.get(key) !== undefined) {
return true;
}
}
return false;
}

function sanitizedEnv(extra: Record<string, string> | undefined): Record<string, string> {
const env = Deno.env.toObject();
for (const key of blockedInheritedEnv) {
delete env[key];
}
return { ...env, ...(extra ?? {}) };
}

function commandEnvOptions(
extra: Record<string, string> | undefined,
): Pick<Deno.CommandOptions, "clearEnv" | "env"> {
if (hasBlockedInheritedEnv()) {
return { clearEnv: true, env: sanitizedEnv(extra) };
}
return extra === undefined ? {} : { env: extra };
}

export function print(value = ""): void {
console.log(value);
}

export function error(value: string): void {
console.error(value);
}

export function fail(message: string, code = 1): never {
error(message);
Deno.exit(code);
}

export function env(name: string, fallback = ""): string {
return Deno.env.get(name) ?? fallback;
}

export function requireEnv(name: string): string {
const value = Deno.env.get(name);
if (value === undefined || value === "") {
fail(`missing required env: ${name}`);
}
return value;
}

export function isHelp(args: string[]): boolean {
return args.length === 1 && ["-h", "--help", "help"].includes(args[0]);
}

export async function run(command: string, args: string[] = [], options: CommandOptions = {}) {
const status = await new Deno.Command(command, {
args,
cwd: options.cwd,
...commandEnvOptions(options.env),
stdin: options.stdin ?? "inherit",
stdout: options.stdout ?? "inherit",
stderr: options.stderr ?? "inherit",
}).spawn().status;
if (!status.success) {
Deno.exit(status.code);
}
}

export async function runText(
command: string,
args: string[] = [],
options: Omit<CommandOptions, "stdout"> = {},
): Promise<string> {
const output = await new Deno.Command(command, {
args,
cwd: options.cwd,
...commandEnvOptions(options.env),
stdin: options.stdin ?? "null",
stdout: "piped",
stderr: options.stderr ?? "inherit",
}).output();
if (!output.success) {
Deno.exit(output.code);
}
return decoder.decode(output.stdout).trimEnd();
}

export async function runInput(
command: string,
args: string[],
input: string,
options: Omit<CommandOptions, "stdin"> = {},
): Promise<string> {
const child = new Deno.Command(command, {
args,
cwd: options.cwd,
...commandEnvOptions(options.env),
stdin: "piped",
stdout: options.stdout ?? "piped",
stderr: options.stderr ?? "inherit",
}).spawn();
const writer = child.stdin.getWriter();
await writer.write(encoder.encode(input));
await writer.close();
const output = await child.output();
if (!output.success) {
Deno.exit(output.code);
}
return decoder.decode(output.stdout).trimEnd();
}

export async function runsealText(args: string[]): Promise<string> {
return await runText("runseal", args);
}

export async function runseal(args: string[]): Promise<void> {
await run("runseal", args);
}

export async function commandExists(name: string): Promise<boolean> {
return (await runsealText(["@tool", "process", "exists", name])) === "true";
}

export async function jsonGet(json: string, path: string): Promise<string> {
return await runsealText(["@tool", "json", "get", json, path]);
}

export async function jsonEmpty(json: string): Promise<boolean> {
return (await runsealText(["@tool", "json", "empty", json])) === "true";
}

export async function fileExists(path: string): Promise<boolean> {
try {
const stat = await Deno.stat(path);
return stat.isFile;
} catch (err) {
if (err instanceof Deno.errors.NotFound) {
return false;
}
throw err;
}
}

export async function dirExists(path: string): Promise<boolean> {
try {
const stat = await Deno.stat(path);
return stat.isDirectory;
} catch (err) {
if (err instanceof Deno.errors.NotFound) {
return false;
}
throw err;
}
}

export function pathJoin(...parts: string[]): string {
const separator = Deno.build.os === "windows" ? "\\" : "/";
const joined = parts
.filter((part) => part !== "")
.map((part, index) =>
index === 0 ? part.replace(/[\\/]+$/g, "") : part.replace(/^[\\/]+|[\\/]+$/g, "")
)
.filter((part) => part !== "")
.join(separator);
return joined === "" ? "." : joined;
}

export function pathListSeparator(): string {
return Deno.build.os === "windows" ? ";" : ":";
}

export async function readTextIfExists(path: string): Promise<string> {
try {
return await Deno.readTextFile(path);
} catch (err) {
if (err instanceof Deno.errors.NotFound) {
return "";
}
throw err;
}
}
Loading
Loading