Skip to content

Commit 7ada4ce

Browse files
authored
feat/deno wrapper model (#52)
* Pivot wrappers to Deno * Install Deno in release workflows * Preserve shell env for Deno child commands * Bump version to 0.8.0 * Normalize runseal wrapper line endings
1 parent 820b48a commit 7ada4ce

78 files changed

Lines changed: 1957 additions & 8634 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitattributes

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
.runseal/** text eol=lf

.github/workflows/guard.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ jobs:
2929
with:
3030
components: rustfmt, clippy
3131

32+
- uses: denoland/setup-deno@v2
33+
with:
34+
deno-version: v2.x
35+
3236
- name: Format
3337
if: runner.os != 'Windows'
3438
run: |

.github/workflows/release-beta.yml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@ jobs:
4444
with:
4545
ref: ${{ inputs.ref }}
4646

47+
- uses: denoland/setup-deno@v2
48+
with:
49+
deno-version: v2.x
50+
4751
- name: Validate R2 access
4852
run: bash .github/scripts/release/r2/check.sh
4953

@@ -69,6 +73,10 @@ jobs:
6973
with:
7074
components: rustfmt, clippy
7175

76+
- uses: denoland/setup-deno@v2
77+
with:
78+
deno-version: v2.x
79+
7280
- name: Format
7381
run: cargo fmt --all --check
7482

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

175+
- uses: denoland/setup-deno@v2
176+
with:
177+
deno-version: v2.x
178+
167179
- name: Resolve guard version hash
168180
id: guard_hash
169181
shell: bash

.github/workflows/release-stable.yml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@ jobs:
4343
with:
4444
ref: ${{ inputs.ref }}
4545

46+
- uses: denoland/setup-deno@v2
47+
with:
48+
deno-version: v2.x
49+
4650
- name: Validate R2 access
4751
run: bash .github/scripts/release/r2/check.sh
4852

@@ -68,6 +72,10 @@ jobs:
6872
with:
6973
components: rustfmt, clippy
7074

75+
- uses: denoland/setup-deno@v2
76+
with:
77+
deno-version: v2.x
78+
7179
- name: Format
7280
run: cargo fmt --all --check
7381

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

173+
- uses: denoland/setup-deno@v2
174+
with:
175+
deno-version: v2.x
176+
165177
- name: Resolve guard version hash
166178
id: guard_hash
167179
shell: bash

.runseal/deno.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"compilerOptions": {
3+
"strict": true
4+
},
5+
"fmt": {
6+
"lineWidth": 100,
7+
"semiColons": true
8+
}
9+
}

.runseal/lib/runseal.ts

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
const decoder = new TextDecoder();
2+
const encoder = new TextEncoder();
3+
4+
export type CommandOptions = {
5+
cwd?: string;
6+
env?: Record<string, string>;
7+
stdin?: "inherit" | "null" | "piped";
8+
stdout?: "inherit" | "null" | "piped";
9+
stderr?: "inherit" | "null" | "piped";
10+
};
11+
12+
const blockedInheritedEnv = new Set([
13+
"DYLD_FALLBACK_LIBRARY_PATH",
14+
"DYLD_INSERT_LIBRARIES",
15+
"DYLD_LIBRARY_PATH",
16+
"LD_PRELOAD",
17+
"LD_LIBRARY_PATH",
18+
]);
19+
20+
function hasBlockedInheritedEnv(): boolean {
21+
for (const key of blockedInheritedEnv) {
22+
if (Deno.env.get(key) !== undefined) {
23+
return true;
24+
}
25+
}
26+
return false;
27+
}
28+
29+
function sanitizedEnv(extra: Record<string, string> | undefined): Record<string, string> {
30+
const env = Deno.env.toObject();
31+
for (const key of blockedInheritedEnv) {
32+
delete env[key];
33+
}
34+
return { ...env, ...(extra ?? {}) };
35+
}
36+
37+
function commandEnvOptions(
38+
extra: Record<string, string> | undefined,
39+
): Pick<Deno.CommandOptions, "clearEnv" | "env"> {
40+
if (hasBlockedInheritedEnv()) {
41+
return { clearEnv: true, env: sanitizedEnv(extra) };
42+
}
43+
return extra === undefined ? {} : { env: extra };
44+
}
45+
46+
export function print(value = ""): void {
47+
console.log(value);
48+
}
49+
50+
export function error(value: string): void {
51+
console.error(value);
52+
}
53+
54+
export function fail(message: string, code = 1): never {
55+
error(message);
56+
Deno.exit(code);
57+
}
58+
59+
export function env(name: string, fallback = ""): string {
60+
return Deno.env.get(name) ?? fallback;
61+
}
62+
63+
export function requireEnv(name: string): string {
64+
const value = Deno.env.get(name);
65+
if (value === undefined || value === "") {
66+
fail(`missing required env: ${name}`);
67+
}
68+
return value;
69+
}
70+
71+
export function isHelp(args: string[]): boolean {
72+
return args.length === 1 && ["-h", "--help", "help"].includes(args[0]);
73+
}
74+
75+
export async function run(command: string, args: string[] = [], options: CommandOptions = {}) {
76+
const status = await new Deno.Command(command, {
77+
args,
78+
cwd: options.cwd,
79+
...commandEnvOptions(options.env),
80+
stdin: options.stdin ?? "inherit",
81+
stdout: options.stdout ?? "inherit",
82+
stderr: options.stderr ?? "inherit",
83+
}).spawn().status;
84+
if (!status.success) {
85+
Deno.exit(status.code);
86+
}
87+
}
88+
89+
export async function runText(
90+
command: string,
91+
args: string[] = [],
92+
options: Omit<CommandOptions, "stdout"> = {},
93+
): Promise<string> {
94+
const output = await new Deno.Command(command, {
95+
args,
96+
cwd: options.cwd,
97+
...commandEnvOptions(options.env),
98+
stdin: options.stdin ?? "null",
99+
stdout: "piped",
100+
stderr: options.stderr ?? "inherit",
101+
}).output();
102+
if (!output.success) {
103+
Deno.exit(output.code);
104+
}
105+
return decoder.decode(output.stdout).trimEnd();
106+
}
107+
108+
export async function runInput(
109+
command: string,
110+
args: string[],
111+
input: string,
112+
options: Omit<CommandOptions, "stdin"> = {},
113+
): Promise<string> {
114+
const child = new Deno.Command(command, {
115+
args,
116+
cwd: options.cwd,
117+
...commandEnvOptions(options.env),
118+
stdin: "piped",
119+
stdout: options.stdout ?? "piped",
120+
stderr: options.stderr ?? "inherit",
121+
}).spawn();
122+
const writer = child.stdin.getWriter();
123+
await writer.write(encoder.encode(input));
124+
await writer.close();
125+
const output = await child.output();
126+
if (!output.success) {
127+
Deno.exit(output.code);
128+
}
129+
return decoder.decode(output.stdout).trimEnd();
130+
}
131+
132+
export async function runsealText(args: string[]): Promise<string> {
133+
return await runText("runseal", args);
134+
}
135+
136+
export async function runseal(args: string[]): Promise<void> {
137+
await run("runseal", args);
138+
}
139+
140+
export async function commandExists(name: string): Promise<boolean> {
141+
return (await runsealText(["@tool", "process", "exists", name])) === "true";
142+
}
143+
144+
export async function jsonGet(json: string, path: string): Promise<string> {
145+
return await runsealText(["@tool", "json", "get", json, path]);
146+
}
147+
148+
export async function jsonEmpty(json: string): Promise<boolean> {
149+
return (await runsealText(["@tool", "json", "empty", json])) === "true";
150+
}
151+
152+
export async function fileExists(path: string): Promise<boolean> {
153+
try {
154+
const stat = await Deno.stat(path);
155+
return stat.isFile;
156+
} catch (err) {
157+
if (err instanceof Deno.errors.NotFound) {
158+
return false;
159+
}
160+
throw err;
161+
}
162+
}
163+
164+
export async function dirExists(path: string): Promise<boolean> {
165+
try {
166+
const stat = await Deno.stat(path);
167+
return stat.isDirectory;
168+
} catch (err) {
169+
if (err instanceof Deno.errors.NotFound) {
170+
return false;
171+
}
172+
throw err;
173+
}
174+
}
175+
176+
export function pathJoin(...parts: string[]): string {
177+
const separator = Deno.build.os === "windows" ? "\\" : "/";
178+
const joined = parts
179+
.filter((part) => part !== "")
180+
.map((part, index) =>
181+
index === 0 ? part.replace(/[\\/]+$/g, "") : part.replace(/^[\\/]+|[\\/]+$/g, "")
182+
)
183+
.filter((part) => part !== "")
184+
.join(separator);
185+
return joined === "" ? "." : joined;
186+
}
187+
188+
export function pathListSeparator(): string {
189+
return Deno.build.os === "windows" ? ";" : ":";
190+
}
191+
192+
export async function readTextIfExists(path: string): Promise<string> {
193+
try {
194+
return await Deno.readTextFile(path);
195+
} catch (err) {
196+
if (err instanceof Deno.errors.NotFound) {
197+
return "";
198+
}
199+
throw err;
200+
}
201+
}

0 commit comments

Comments
 (0)