Skip to content

Commit 24ae358

Browse files
committed
core: refactor environment variable handling in JS
Replace tjs.environ + tjs.getenv + tjs.setenv + tjs.unsetenv with tjs.env, an object which does all of that under the hood. Ref: https://github.com/wintercg/proposal-cli-api?tab=readme-ov-file#environment-variables
1 parent 130d8b0 commit 24ae358

File tree

13 files changed

+44912
-44787
lines changed

13 files changed

+44912
-44787
lines changed

docs/types/txikijs.d.ts

+3-23
Original file line numberDiff line numberDiff line change
@@ -132,41 +132,21 @@ declare global {
132132

133133
/**
134134
* Object containing environment variables.
135+
* Setting and deleting properties on this object causes
136+
* environment variables to be set / deleted.
135137
*/
136138
type Environment = { [index: string]: string };
137139

138140
/**
139141
* System environment variables.
140142
*/
141-
const environ: Environment;
143+
const env: Environment;
142144

143145
/**
144146
* Returns the current system hostname.
145147
*/
146148
function gethostname(): string;
147149

148-
/**
149-
* Gets the environment variable of the given name.
150-
*
151-
* @param name Name of the environment variable to get.
152-
*/
153-
function getenv(name: string): string;
154-
155-
/**
156-
* Sets the given environment variable to the given value.
157-
*
158-
* @param name Name of the environment variable to be set.
159-
* @param value Value to be set to.
160-
*/
161-
function setenv(name: string, value: string): void;
162-
163-
/**
164-
* Unsets the given environment variable.
165-
*
166-
* @param name Name of the environment variable to unset.
167-
*/
168-
function unsetenv(name: string): void;
169-
170150
/**
171151
* String representation of the current platform.
172152
*/

examples/process.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ logStatus(status);
2727
status = await proc.wait();
2828
logStatus(status);
2929

30-
args = [exe, '-e', 'console.log(JSON.stringify(tjs.environ))'];
30+
args = [exe, '-e', 'console.log(JSON.stringify(tjs.env))'];
3131
proc = tjs.spawn(args, { env: { FOO: 'BAR', SPAM: 'EGGS'} });
3232
console.log(`proc PID: ${proc.pid}`);
3333
status = await proc.wait();

src/bundles/c/core/core.c

+5,767-5,667
Large diffs are not rendered by default.

src/bundles/c/core/polyfills.c

+32,916-32,917
Large diffs are not rendered by default.

src/bundles/c/core/run-main.c

+6,146-6,147
Large diffs are not rendered by default.

src/js/core/env.js

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
const core = globalThis[Symbol.for('tjs.internal.core')];
2+
3+
const env = new Proxy({}, {
4+
ownKeys() {
5+
return core._envKeys();
6+
},
7+
get(_, prop) {
8+
if (prop === Symbol.toStringTag) {
9+
return JSON.stringify(core._environ(), null, 2);
10+
} else if (prop === 'toJSON') {
11+
return () => core._environ();
12+
} else if (typeof prop === 'string') {
13+
try {
14+
return core._getenv(prop);
15+
} catch (_) { /* Ignored. */ }
16+
}
17+
18+
return undefined;
19+
},
20+
set(_, prop, val) {
21+
core._setenv(prop, val);
22+
23+
return true;
24+
},
25+
deleteProperty(_, prop) {
26+
core._unsetenv(prop);
27+
28+
return true;
29+
},
30+
has(_, key) {
31+
return key in core._envKeys();
32+
}
33+
});
34+
35+
export default env;

src/js/core/index.js

+9-8
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
const core = globalThis[Symbol.for('tjs.internal.core')];
22

33
import { alert, confirm, prompt } from './alert-confirm-prompt.js';
4+
import env from './env.js';
45
import { open, mkdir, mkstemp, rm } from './fs.js';
56
import pathModule from './path.js';
67
import { PosixSocket } from './posix-socket.js';
@@ -29,7 +30,6 @@ const noExport = [
2930
'XMLHttpRequest',
3031
'clearInterval',
3132
'clearTimeout',
32-
'environ',
3333
'evalFile',
3434
'evalScript',
3535
'ffi_load_native',
@@ -90,13 +90,6 @@ Object.defineProperty(tjs, 'prompt', {
9090
});
9191

9292
// Getters.
93-
Object.defineProperty(tjs, 'environ', {
94-
enumerable: true,
95-
configurable: false,
96-
get() {
97-
return core.environ();
98-
}
99-
});
10093
Object.defineProperty(tjs, 'pid', {
10194
enumerable: true,
10295
configurable: false,
@@ -112,6 +105,14 @@ Object.defineProperty(tjs, 'ppid', {
112105
}
113106
});
114107

108+
// Environment.
109+
Object.defineProperty(tjs, 'env', {
110+
enumerable: true,
111+
configurable: false,
112+
writable: false,
113+
value: env
114+
});
115+
115116
// FS.
116117
Object.defineProperty(tjs, 'open', {
117118
enumerable: true,

src/js/core/path.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,7 @@ const win32 = {
195195
// absolute path, get cwd for that drive, or the process cwd if
196196
// the drive cwd is not available. We're sure the device is not
197197
// a UNC path at this points, because UNC paths are always absolute.
198-
path = tjs.environ[`=${resolvedDevice}`] || tjs.cwd();
198+
path = tjs.env[`=${resolvedDevice}`] || tjs.cwd();
199199

200200
// Verify that a cwd was found and that it actually points
201201
// to our drive. If not, default to the drive's root.

src/js/polyfills/storage.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ const kStorageDb = Symbol('kStorageDb');
9090
function initDb() {
9191
const path = globalThis[Symbol.for('tjs.internal.modules.path')];
9292

93-
const TJS_HOME = tjs.environ.TJS_HOME ?? path.join(tjs.homedir(), '.tjs');
93+
const TJS_HOME = tjs.env.TJS_HOME ?? path.join(tjs.homedir(), '.tjs');
9494
const localStorageDb = path.join(TJS_HOME, 'localStorage.sqlite');
9595
const flags = sqlite3.SQLITE_OPEN_CREATE | sqlite3.SQLITE_OPEN_READWRITE;
9696

src/js/run-main/run-tests.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
const pathModule = globalThis[Symbol.for('tjs.internal.modules.path')];
44

5-
const verbose = Boolean(tjs.environ.VERBOSE_TESTS);
5+
const verbose = Boolean(tjs.env.VERBOSE_TESTS);
66
const TIMEOUT = 10 * 1000;
77

88
const colors = {
@@ -118,7 +118,7 @@ export async function runTests(d) {
118118
}
119119

120120
let failed = 0;
121-
const testConcurrency = tjs.environ.TJS_TEST_CONCURRENCY ?? tjs.availableParallelism();
121+
const testConcurrency = tjs.env.TJS_TEST_CONCURRENCY ?? tjs.availableParallelism();
122122
const running = new Set();
123123

124124
// eslint-disable-next-line no-constant-condition

src/os.c

+24-4
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,25 @@ static JSValue tjs_environ(JSContext *ctx, JSValueConst this_val, int argc, JSVa
105105
return obj;
106106
}
107107

108+
static JSValue tjs_envKeys(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) {
109+
uv_env_item_t *env;
110+
int envcount, r;
111+
112+
r = uv_os_environ(&env, &envcount);
113+
if (r != 0)
114+
return tjs_throw_errno(ctx, r);
115+
116+
JSValue obj = JS_NewArray(ctx);
117+
118+
for (int i = 0; i < envcount; i++) {
119+
JS_SetPropertyUint32(ctx, obj, i, JS_NewString(ctx, env[i].name));
120+
}
121+
122+
uv_os_free_environ(env, envcount);
123+
124+
return obj;
125+
}
126+
108127
static JSValue tjs_getenv(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) {
109128
if (!JS_IsString(argv[0]))
110129
return JS_ThrowTypeError(ctx, "expected a string");
@@ -475,9 +494,11 @@ static const JSCFunctionListEntry tjs_os_funcs[] = {
475494
TJS_CFUNC_DEF("uname", 0, tjs_uname),
476495
TJS_CFUNC_DEF("uptime", 0, tjs_uptime),
477496
TJS_CFUNC_DEF("guessHandle", 1, tjs_guess_handle),
478-
TJS_CFUNC_DEF("getenv", 0, tjs_getenv),
479-
TJS_CFUNC_DEF("setenv", 2, tjs_setenv),
480-
TJS_CFUNC_DEF("unsetenv", 1, tjs_unsetenv),
497+
TJS_CFUNC_DEF("_getenv", 0, tjs_getenv),
498+
TJS_CFUNC_DEF("_setenv", 2, tjs_setenv),
499+
TJS_CFUNC_DEF("_unsetenv", 1, tjs_unsetenv),
500+
TJS_CFUNC_DEF("_envKeys", 0, tjs_envKeys),
501+
TJS_CFUNC_DEF("_environ", 0, tjs_environ),
481502
TJS_CFUNC_DEF("chdir", 1, tjs_chdir),
482503
TJS_CFUNC_DEF("cwd", 0, tjs_cwd),
483504
TJS_CFUNC_DEF("homedir", 0, tjs_homedir),
@@ -487,7 +508,6 @@ static const JSCFunctionListEntry tjs_os_funcs[] = {
487508
TJS_CFUNC_DEF("loadavg", 0, tjs_loadavg),
488509
TJS_CFUNC_DEF("networkInterfaces", 0, tjs_network_interfaces),
489510
TJS_CFUNC_DEF("gethostname", 0, tjs_gethostname),
490-
TJS_CFUNC_DEF("environ", 0, tjs_environ),
491511
TJS_CFUNC_DEF("getPid", 0, tjs_getpid),
492512
TJS_CFUNC_DEF("getPpid", 0, tjs_getppid),
493513
TJS_CFUNC_DEF("userInfo", 0, tjs_userInfo),

tests/test-env.js

+6-15
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,9 @@
11
import assert from 'tjs:assert';
22

33

4-
assert.throws(() => { tjs.getenv() }, Error, 'must pass a string');
5-
assert.throws(() => { tjs.getenv(1234) }, Error, 'must pass a string');
6-
assert.ok(tjs.getenv('PATH'));
7-
8-
assert.throws(() => { tjs.setenv() }, Error, 'must pass a string');
9-
assert.throws(() => { tjs.setenv('FOO') }, Error, 'must pass a string');
10-
tjs.setenv('FOO', 123);
11-
assert.eq(tjs.environ.FOO, '123');
12-
tjs.setenv('FOO', 'BAR');
13-
assert.eq(tjs.environ.FOO, 'BAR');
14-
15-
assert.throws(() => { tjs.unsetenv() }, Error, 'must pass a string');
16-
assert.throws(() => { tjs.unsetenv(1234) }, Error, 'must pass a string');
17-
tjs.unsetenv('FOO');
18-
assert.eq(tjs.environ.FOO, undefined);
4+
tjs.env.FOO = 123;
5+
assert.eq(tjs.env.FOO, '123');
6+
tjs.env.FOO = 'BAR';
7+
assert.eq(tjs.env.FOO, 'BAR');
8+
delete tjs.env.FOO;
9+
assert.eq(tjs.env.FOO, undefined);

tests/test-storage.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import assert from 'tjs:assert';
22
import path from 'tjs:path';
33

44

5-
if (tjs.environ.TJS_HOME) {
5+
if (tjs.env.TJS_HOME) {
66
// This is the second test.
77
assert.eq(window.localStorage.getItem('foo'), '123');
88

0 commit comments

Comments
 (0)