@woosau/dsh-custom-env 0.1.1 → 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +84 -53
- package/README.zh-CN.md +84 -53
- package/lib/client.js +138 -1
- package/lib/index.js +327 -180
- package/lib/typert.host.js +171 -1
- package/lib/typert.remote-client.js +143 -1
- package/lib/types/env-file.d.ts +59 -11
- package/lib/types/index.d.ts +33 -4
- package/lib/types/os-user-env/index.d.ts +15 -4
- package/lib/types/os-user-env/macos.d.ts +15 -1
- package/lib/types/os-user-env/windows.d.ts +20 -6
- package/lib/types/types.d.ts +5 -0
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -519,8 +519,8 @@ async function cliInstall() {
|
|
|
519
519
|
}
|
|
520
520
|
|
|
521
521
|
// src/env-file.ts
|
|
522
|
-
import { existsSync as
|
|
523
|
-
import { dirname as dirname5, join as
|
|
522
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync3, readFileSync as readFileSync2, writeFileSync as writeFileSync3 } from "node:fs";
|
|
523
|
+
import { dirname as dirname5, join as join7 } from "node:path";
|
|
524
524
|
import { parseEnv } from "node:util";
|
|
525
525
|
import { resolveDshHome } from "@deepseek-ai/dsh-home-paths";
|
|
526
526
|
|
|
@@ -586,116 +586,14 @@ function isWritableEnvName(name) {
|
|
|
586
586
|
return ENV_NAME.test(name) && !isBootstrapOnly(name);
|
|
587
587
|
}
|
|
588
588
|
|
|
589
|
-
// src/env-file.ts
|
|
590
|
-
function envFilePath() {
|
|
591
|
-
return join6(resolveDshHome(), ".env");
|
|
592
|
-
}
|
|
593
|
-
function readRaw() {
|
|
594
|
-
const path = envFilePath();
|
|
595
|
-
return existsSync6(path) ? readFileSync2(path, "utf8") : "";
|
|
596
|
-
}
|
|
597
|
-
function writeEnvFile(content) {
|
|
598
|
-
const path = envFilePath();
|
|
599
|
-
mkdirSync2(dirname5(path), { recursive: true });
|
|
600
|
-
writeFileSync2(path, content, "utf8");
|
|
601
|
-
}
|
|
602
|
-
function encodeValue(value) {
|
|
603
|
-
if (/^[A-Za-z0-9_./:@+-]*$/u.test(value)) return value;
|
|
604
|
-
return `"${value.replace(/\\/gu, "\\\\").replace(/"/gu, '\\"').replace(/\n/gu, "\\n")}"`;
|
|
605
|
-
}
|
|
606
|
-
function parseLines(content) {
|
|
607
|
-
const text = content.replace(/^\uFEFF/u, "");
|
|
608
|
-
if (text === "") return [];
|
|
609
|
-
return text.split(/\r?\n/u).map((raw) => {
|
|
610
|
-
const trimmed = raw.trim();
|
|
611
|
-
if (trimmed === "" || trimmed.startsWith("#")) return { kind: "other", raw };
|
|
612
|
-
const match = /^(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=/u.exec(trimmed);
|
|
613
|
-
if (match?.[1] === void 0) return { kind: "other", raw };
|
|
614
|
-
return { kind: "assignment", raw, name: match[1] };
|
|
615
|
-
});
|
|
616
|
-
}
|
|
617
|
-
function serializeLines(lines) {
|
|
618
|
-
const kept = lines.filter((line) => !(line.kind === "other" && line.raw.trim() === ""));
|
|
619
|
-
return kept.length === 0 ? "" : `${kept.map((line) => line.raw).join("\n")}
|
|
620
|
-
`;
|
|
621
|
-
}
|
|
622
|
-
function assertWritable(name, value) {
|
|
623
|
-
if (!isWritableEnvName(name)) {
|
|
624
|
-
throw new Error(`custom-env: refused bootstrap or invalid name "${name}"`);
|
|
625
|
-
}
|
|
626
|
-
if (value === "") {
|
|
627
|
-
throw new Error(`custom-env: empty value for "${name}"`);
|
|
628
|
-
}
|
|
629
|
-
}
|
|
630
|
-
function loadEnvMap() {
|
|
631
|
-
const raw = readRaw().replace(/^\uFEFF/u, "").trim();
|
|
632
|
-
if (raw === "") return {};
|
|
633
|
-
let parsed;
|
|
634
|
-
try {
|
|
635
|
-
parsed = parseEnv(raw);
|
|
636
|
-
} catch (error) {
|
|
637
|
-
throw new Error(`custom-env: invalid dotenv syntax in ${envFilePath()}`, { cause: error });
|
|
638
|
-
}
|
|
639
|
-
const out = {};
|
|
640
|
-
for (const [key, value] of Object.entries(parsed)) {
|
|
641
|
-
if (typeof value === "string" && value !== "" && isWritableEnvName(key)) out[key] = value;
|
|
642
|
-
}
|
|
643
|
-
return out;
|
|
644
|
-
}
|
|
645
|
-
function listEnvEntries() {
|
|
646
|
-
const path = envFilePath();
|
|
647
|
-
const exists = existsSync6(path);
|
|
648
|
-
if (!exists) return { path, exists, entries: [] };
|
|
649
|
-
const map = loadEnvMap();
|
|
650
|
-
const entries = [];
|
|
651
|
-
const seen = /* @__PURE__ */ new Set();
|
|
652
|
-
for (const line of parseLines(readRaw())) {
|
|
653
|
-
if (line.kind !== "assignment" || line.name === void 0 || seen.has(line.name)) continue;
|
|
654
|
-
const value = map[line.name];
|
|
655
|
-
if (value === void 0) continue;
|
|
656
|
-
seen.add(line.name);
|
|
657
|
-
entries.push({ name: line.name, value });
|
|
658
|
-
}
|
|
659
|
-
return { path, exists, entries };
|
|
660
|
-
}
|
|
661
|
-
function writeEnvEntry(replaceName, name, value) {
|
|
662
|
-
assertWritable(replaceName, value);
|
|
663
|
-
assertWritable(name, value);
|
|
664
|
-
const assignment = `${name}=${encodeValue(value)}`;
|
|
665
|
-
let replaced = false;
|
|
666
|
-
const next = [];
|
|
667
|
-
for (const line of parseLines(readRaw())) {
|
|
668
|
-
if (line.kind === "assignment" && line.name === replaceName) {
|
|
669
|
-
replaced = true;
|
|
670
|
-
next.push({ kind: "assignment", raw: assignment, name });
|
|
671
|
-
continue;
|
|
672
|
-
}
|
|
673
|
-
if (replaceName !== name && line.kind === "assignment" && line.name === name) continue;
|
|
674
|
-
next.push(line);
|
|
675
|
-
}
|
|
676
|
-
if (!replaced) next.push({ kind: "assignment", raw: assignment, name });
|
|
677
|
-
writeEnvFile(serializeLines(next));
|
|
678
|
-
}
|
|
679
|
-
function removeEnvEntry(name) {
|
|
680
|
-
if (!isWritableEnvName(name)) {
|
|
681
|
-
throw new Error(`custom-env: refused bootstrap or invalid name "${name}"`);
|
|
682
|
-
}
|
|
683
|
-
if (!existsSync6(envFilePath())) return false;
|
|
684
|
-
const lines = parseLines(readRaw());
|
|
685
|
-
const next = lines.filter((line) => !(line.kind === "assignment" && line.name === name));
|
|
686
|
-
if (next.length === lines.length) return false;
|
|
687
|
-
writeEnvFile(serializeLines(next));
|
|
688
|
-
return true;
|
|
689
|
-
}
|
|
690
|
-
|
|
691
589
|
// src/os-user-env/macos.ts
|
|
692
|
-
import { mkdirSync as
|
|
590
|
+
import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync2, unlinkSync as unlinkSync2, existsSync as existsSync6 } from "node:fs";
|
|
693
591
|
import { homedir } from "node:os";
|
|
694
|
-
import { join as
|
|
592
|
+
import { join as join6 } from "node:path";
|
|
695
593
|
import { spawnSync } from "node:child_process";
|
|
696
594
|
var AGENT_LABEL = "ai.dsh.custom-env";
|
|
697
|
-
var AGENT_DIR = () =>
|
|
698
|
-
var AGENT_PATH = () =>
|
|
595
|
+
var AGENT_DIR = () => join6(homedir(), "Library", "LaunchAgents");
|
|
596
|
+
var AGENT_PATH = () => join6(AGENT_DIR(), `${AGENT_LABEL}.plist`);
|
|
699
597
|
var cached;
|
|
700
598
|
function escapeXml(value) {
|
|
701
599
|
return value.replace(/&/gu, "&").replace(/</gu, "<").replace(/>/gu, ">").replace(/"/gu, """);
|
|
@@ -739,7 +637,7 @@ function ensureCache() {
|
|
|
739
637
|
function writeAgent(entries) {
|
|
740
638
|
const path = AGENT_PATH();
|
|
741
639
|
if (Object.keys(entries).length === 0) {
|
|
742
|
-
if (
|
|
640
|
+
if (existsSync6(path)) {
|
|
743
641
|
try {
|
|
744
642
|
launchctl(["unload", path]);
|
|
745
643
|
} catch (_error) {
|
|
@@ -748,15 +646,20 @@ function writeAgent(entries) {
|
|
|
748
646
|
}
|
|
749
647
|
return;
|
|
750
648
|
}
|
|
751
|
-
|
|
752
|
-
|
|
649
|
+
mkdirSync2(AGENT_DIR(), { recursive: true });
|
|
650
|
+
writeFileSync2(path, buildPlist(entries), "utf8");
|
|
753
651
|
try {
|
|
754
652
|
launchctl(["unload", path]);
|
|
755
653
|
} catch (_error) {
|
|
756
654
|
}
|
|
757
655
|
launchctl(["load", path]);
|
|
758
656
|
}
|
|
759
|
-
function
|
|
657
|
+
function hasMacosUserEnv(name, platform = process.platform) {
|
|
658
|
+
if (platform !== "darwin") return false;
|
|
659
|
+
if (Object.hasOwn(process.env, name)) return true;
|
|
660
|
+
return Object.hasOwn(ensureCache(), name);
|
|
661
|
+
}
|
|
662
|
+
function writeMacosUserEnv(entries, platform = process.platform) {
|
|
760
663
|
if (platform !== "darwin") return;
|
|
761
664
|
const pairs = Object.entries(entries);
|
|
762
665
|
if (pairs.length === 0) return;
|
|
@@ -767,13 +670,15 @@ function persistMacosUserEnv(entries, platform = process.platform) {
|
|
|
767
670
|
}
|
|
768
671
|
writeAgent(map);
|
|
769
672
|
}
|
|
770
|
-
function
|
|
771
|
-
if (platform !== "darwin") return;
|
|
673
|
+
function removeMacosUserEnvs(names, platform = process.platform) {
|
|
674
|
+
if (platform !== "darwin" || names.length === 0) return;
|
|
772
675
|
const map = ensureCache();
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
676
|
+
for (const name of names) {
|
|
677
|
+
delete map[name];
|
|
678
|
+
try {
|
|
679
|
+
launchctl(["unsetenv", name]);
|
|
680
|
+
} catch (_error) {
|
|
681
|
+
}
|
|
777
682
|
}
|
|
778
683
|
writeAgent(map);
|
|
779
684
|
}
|
|
@@ -781,53 +686,59 @@ function removeMacosUserEnv(name, platform = process.platform) {
|
|
|
781
686
|
// src/os-user-env/windows.ts
|
|
782
687
|
import { createRequire } from "node:module";
|
|
783
688
|
var HKEY_CURRENT_USER = 2147483649;
|
|
689
|
+
var KEY_QUERY_VALUE = 1;
|
|
784
690
|
var KEY_SET_VALUE = 2;
|
|
785
691
|
var REG_SZ = 1;
|
|
786
692
|
var ERROR_SUCCESS = 0;
|
|
787
693
|
var ERROR_FILE_NOT_FOUND = 2;
|
|
788
|
-
var HWND_BROADCAST = 65535;
|
|
789
|
-
var WM_SETTINGCHANGE = 26;
|
|
790
|
-
var SMTO_ABORTIFHUNG = 2;
|
|
791
694
|
var bindings;
|
|
792
695
|
function loadBindings() {
|
|
793
696
|
if (bindings !== void 0) return bindings;
|
|
794
697
|
const require2 = createRequire(import.meta.url);
|
|
795
698
|
const koffi = require2("koffi");
|
|
796
699
|
const advapi32 = koffi.load("advapi32.dll");
|
|
797
|
-
const user32 = koffi.load("user32.dll");
|
|
798
700
|
bindings = {
|
|
799
701
|
openKey: advapi32.func(
|
|
800
702
|
"long __stdcall RegOpenKeyExW(uintptr_t hKey, const char16_t *subKey, uint32_t options, uint32_t samDesired, _Out_ void **result)"
|
|
801
703
|
),
|
|
704
|
+
queryValue: advapi32.func(
|
|
705
|
+
"long __stdcall RegQueryValueExW(void *hKey, const char16_t *valueName, void *reserved, _Inout_ uint32_t *type, uint8_t *data, _Inout_ uint32_t *cbData)"
|
|
706
|
+
),
|
|
802
707
|
setValue: advapi32.func(
|
|
803
708
|
"long __stdcall RegSetValueExW(void *hKey, const char16_t *valueName, uint32_t reserved, uint32_t type, const uint8_t *data, uint32_t cbData)"
|
|
804
709
|
),
|
|
805
710
|
deleteValue: advapi32.func(
|
|
806
711
|
"long __stdcall RegDeleteValueW(void *hKey, const char16_t *valueName)"
|
|
807
712
|
),
|
|
808
|
-
closeKey: advapi32.func("long __stdcall RegCloseKey(void *hKey)")
|
|
809
|
-
sendMessageTimeout: user32.func(
|
|
810
|
-
"intptr_t __stdcall SendMessageTimeoutW(uintptr_t hWnd, uint32_t msg, uintptr_t wParam, const char16_t *lParam, uint32_t flags, uint32_t timeout, _Out_ uintptr_t *result)"
|
|
811
|
-
)
|
|
713
|
+
closeKey: advapi32.func("long __stdcall RegCloseKey(void *hKey)")
|
|
812
714
|
};
|
|
813
715
|
return bindings;
|
|
814
716
|
}
|
|
815
717
|
function encodeRegSz(value) {
|
|
816
718
|
return Buffer.from(`${value}\0`, "utf16le");
|
|
817
719
|
}
|
|
818
|
-
function
|
|
819
|
-
|
|
820
|
-
api
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
720
|
+
function hasWindowsUserEnv(name, platform = process.platform) {
|
|
721
|
+
if (platform !== "win32") return false;
|
|
722
|
+
const api = loadBindings();
|
|
723
|
+
const keySlot = [null];
|
|
724
|
+
const openStatus = api.openKey(HKEY_CURRENT_USER, "Environment", 0, KEY_QUERY_VALUE, keySlot);
|
|
725
|
+
if (openStatus !== ERROR_SUCCESS) {
|
|
726
|
+
throw new Error(`custom-env: RegOpenKeyExW failed (Win32 ${openStatus})`);
|
|
727
|
+
}
|
|
728
|
+
const hKey = keySlot[0];
|
|
729
|
+
try {
|
|
730
|
+
const cbData = [0];
|
|
731
|
+
const status = api.queryValue(hKey, name, null, null, null, cbData);
|
|
732
|
+
if (status === ERROR_FILE_NOT_FOUND) return false;
|
|
733
|
+
if (status !== ERROR_SUCCESS && status !== 234) {
|
|
734
|
+
throw new Error(`custom-env: RegQueryValueExW(${name}) failed (Win32 ${status})`);
|
|
735
|
+
}
|
|
736
|
+
return true;
|
|
737
|
+
} finally {
|
|
738
|
+
api.closeKey(hKey);
|
|
739
|
+
}
|
|
829
740
|
}
|
|
830
|
-
function
|
|
741
|
+
function writeWindowsUserEnv(entries, platform = process.platform) {
|
|
831
742
|
if (platform !== "win32") return;
|
|
832
743
|
const pairs = Object.entries(entries);
|
|
833
744
|
if (pairs.length === 0) return;
|
|
@@ -849,10 +760,9 @@ function persistWindowsUserEnv(entries, platform = process.platform) {
|
|
|
849
760
|
} finally {
|
|
850
761
|
api.closeKey(hKey);
|
|
851
762
|
}
|
|
852
|
-
broadcastEnvironmentChange(api);
|
|
853
763
|
}
|
|
854
|
-
function
|
|
855
|
-
if (platform !== "win32") return;
|
|
764
|
+
function removeWindowsUserEnvs(names, platform = process.platform) {
|
|
765
|
+
if (platform !== "win32" || names.length === 0) return;
|
|
856
766
|
const api = loadBindings();
|
|
857
767
|
const keySlot = [null];
|
|
858
768
|
const openStatus = api.openKey(HKEY_CURRENT_USER, "Environment", 0, KEY_SET_VALUE, keySlot);
|
|
@@ -861,24 +771,184 @@ function removeWindowsUserEnv(name, platform = process.platform) {
|
|
|
861
771
|
}
|
|
862
772
|
const hKey = keySlot[0];
|
|
863
773
|
try {
|
|
864
|
-
const
|
|
865
|
-
|
|
866
|
-
|
|
774
|
+
for (const name of names) {
|
|
775
|
+
const status = api.deleteValue(hKey, name);
|
|
776
|
+
if (status !== ERROR_SUCCESS && status !== ERROR_FILE_NOT_FOUND) {
|
|
777
|
+
throw new Error(`custom-env: RegDeleteValueW(${name}) failed (Win32 ${status})`);
|
|
778
|
+
}
|
|
867
779
|
}
|
|
868
780
|
} finally {
|
|
869
781
|
api.closeKey(hKey);
|
|
870
782
|
}
|
|
871
|
-
broadcastEnvironmentChange(api);
|
|
872
783
|
}
|
|
873
784
|
|
|
874
785
|
// src/os-user-env/index.ts
|
|
875
|
-
function
|
|
876
|
-
|
|
877
|
-
|
|
786
|
+
function hasUserEnv(name) {
|
|
787
|
+
return hasWindowsUserEnv(name) || hasMacosUserEnv(name);
|
|
788
|
+
}
|
|
789
|
+
function writeUserEnv(entries) {
|
|
790
|
+
writeWindowsUserEnv(entries);
|
|
791
|
+
writeMacosUserEnv(entries);
|
|
878
792
|
}
|
|
879
|
-
function
|
|
880
|
-
|
|
881
|
-
|
|
793
|
+
function removeUserEnvs(names) {
|
|
794
|
+
if (names.length === 0) return;
|
|
795
|
+
removeWindowsUserEnvs(names);
|
|
796
|
+
removeMacosUserEnvs(names);
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
// src/env-file.ts
|
|
800
|
+
function envFilePath() {
|
|
801
|
+
return join7(resolveDshHome(), ".env");
|
|
802
|
+
}
|
|
803
|
+
function readRaw() {
|
|
804
|
+
const path = envFilePath();
|
|
805
|
+
return existsSync7(path) ? readFileSync2(path, "utf8") : "";
|
|
806
|
+
}
|
|
807
|
+
function writeEnvFile(content) {
|
|
808
|
+
const path = envFilePath();
|
|
809
|
+
mkdirSync3(dirname5(path), { recursive: true });
|
|
810
|
+
writeFileSync3(path, content, "utf8");
|
|
811
|
+
}
|
|
812
|
+
function encodeValue(value) {
|
|
813
|
+
if (/^[A-Za-z0-9_./:@+-]*$/u.test(value)) return value;
|
|
814
|
+
return `"${value.replace(/\\/gu, "\\\\").replace(/"/gu, '\\"').replace(/\n/gu, "\\n")}"`;
|
|
815
|
+
}
|
|
816
|
+
function parseLines(content) {
|
|
817
|
+
const text = content.replace(/^\uFEFF/u, "");
|
|
818
|
+
if (text === "") return [];
|
|
819
|
+
return text.split(/\r?\n/u).map((raw) => {
|
|
820
|
+
const trimmed = raw.trim();
|
|
821
|
+
if (trimmed === "" || trimmed.startsWith("#")) return { kind: "other", raw };
|
|
822
|
+
const match = /^(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=/u.exec(trimmed);
|
|
823
|
+
if (match?.[1] === void 0) return { kind: "other", raw };
|
|
824
|
+
return { kind: "assignment", raw, name: match[1] };
|
|
825
|
+
});
|
|
826
|
+
}
|
|
827
|
+
function serializeLines(lines) {
|
|
828
|
+
const kept = lines.filter((line) => !(line.kind === "other" && line.raw.trim() === ""));
|
|
829
|
+
return kept.length === 0 ? "" : `${kept.map((line) => line.raw).join("\n")}
|
|
830
|
+
`;
|
|
831
|
+
}
|
|
832
|
+
function assertWritableName(name) {
|
|
833
|
+
if (!isWritableEnvName(name)) {
|
|
834
|
+
throw new Error(`custom-env: refused bootstrap or invalid name "${name}"`);
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
function readEnvMap() {
|
|
838
|
+
const raw = readRaw().replace(/^\uFEFF/u, "").trim();
|
|
839
|
+
if (raw === "") return {};
|
|
840
|
+
let parsed;
|
|
841
|
+
try {
|
|
842
|
+
parsed = parseEnv(raw);
|
|
843
|
+
} catch (error) {
|
|
844
|
+
throw new Error(`custom-env: invalid dotenv syntax in ${envFilePath()}`, { cause: error });
|
|
845
|
+
}
|
|
846
|
+
const out = {};
|
|
847
|
+
for (const [key, value] of Object.entries(parsed)) {
|
|
848
|
+
if (typeof value === "string" && isWritableEnvName(key)) out[key] = value;
|
|
849
|
+
}
|
|
850
|
+
return out;
|
|
851
|
+
}
|
|
852
|
+
function listEnvEntries() {
|
|
853
|
+
const path = envFilePath();
|
|
854
|
+
const exists = existsSync7(path);
|
|
855
|
+
if (!exists) return { path, exists, entries: [] };
|
|
856
|
+
const map = readEnvMap();
|
|
857
|
+
const entries = [];
|
|
858
|
+
const seen = /* @__PURE__ */ new Set();
|
|
859
|
+
for (const line of parseLines(readRaw())) {
|
|
860
|
+
if (line.kind !== "assignment" || line.name === void 0 || seen.has(line.name)) continue;
|
|
861
|
+
const value = map[line.name];
|
|
862
|
+
if (value === void 0) continue;
|
|
863
|
+
seen.add(line.name);
|
|
864
|
+
entries.push({ name: line.name, value });
|
|
865
|
+
}
|
|
866
|
+
return { path, exists, entries };
|
|
867
|
+
}
|
|
868
|
+
function hasEnvKey(name) {
|
|
869
|
+
return Object.hasOwn(readEnvMap(), name) || hasUserEnv(name);
|
|
870
|
+
}
|
|
871
|
+
function selectEnvWrites(entries, options = {}) {
|
|
872
|
+
if (entries.length === 0) return [];
|
|
873
|
+
const lastByName = /* @__PURE__ */ new Map();
|
|
874
|
+
for (const entry of entries) {
|
|
875
|
+
assertWritableName(entry.name);
|
|
876
|
+
lastByName.set(entry.name, entry.value);
|
|
877
|
+
}
|
|
878
|
+
const forceEmpty = options.forceEmpty === true;
|
|
879
|
+
const selected = [];
|
|
880
|
+
for (const [name, value] of lastByName) {
|
|
881
|
+
if (value === "" && !forceEmpty && hasEnvKey(name)) continue;
|
|
882
|
+
selected.push({ name, value });
|
|
883
|
+
}
|
|
884
|
+
return selected;
|
|
885
|
+
}
|
|
886
|
+
function writeEnvEntries(entries, options = {}) {
|
|
887
|
+
const selected = selectEnvWrites(entries, options);
|
|
888
|
+
if (selected.length === 0) return selected;
|
|
889
|
+
const pending = new Map(selected.map((entry) => [entry.name, entry.value]));
|
|
890
|
+
const next = [];
|
|
891
|
+
for (const line of parseLines(readRaw())) {
|
|
892
|
+
if (line.kind === "assignment" && line.name !== void 0 && pending.has(line.name)) {
|
|
893
|
+
const value = pending.get(line.name);
|
|
894
|
+
pending.delete(line.name);
|
|
895
|
+
next.push({
|
|
896
|
+
kind: "assignment",
|
|
897
|
+
raw: `${line.name}=${encodeValue(value)}`,
|
|
898
|
+
name: line.name
|
|
899
|
+
});
|
|
900
|
+
continue;
|
|
901
|
+
}
|
|
902
|
+
next.push(line);
|
|
903
|
+
}
|
|
904
|
+
for (const [name, value] of pending) {
|
|
905
|
+
next.push({ kind: "assignment", raw: `${name}=${encodeValue(value)}`, name });
|
|
906
|
+
}
|
|
907
|
+
writeEnvFile(serializeLines(next));
|
|
908
|
+
return selected;
|
|
909
|
+
}
|
|
910
|
+
function writeEnvEntry(name, value, options = {}) {
|
|
911
|
+
return writeEnvEntries([{ name, value }], options).length > 0;
|
|
912
|
+
}
|
|
913
|
+
function renameEnvEntry(from, to, value) {
|
|
914
|
+
assertWritableName(from);
|
|
915
|
+
assertWritableName(to);
|
|
916
|
+
const assignment = `${to}=${encodeValue(value)}`;
|
|
917
|
+
let replaced = false;
|
|
918
|
+
const next = [];
|
|
919
|
+
for (const line of parseLines(readRaw())) {
|
|
920
|
+
if (line.kind === "assignment" && line.name === from) {
|
|
921
|
+
replaced = true;
|
|
922
|
+
next.push({ kind: "assignment", raw: assignment, name: to });
|
|
923
|
+
continue;
|
|
924
|
+
}
|
|
925
|
+
if (line.kind === "assignment" && line.name === to) continue;
|
|
926
|
+
next.push(line);
|
|
927
|
+
}
|
|
928
|
+
if (!replaced) next.push({ kind: "assignment", raw: assignment, name: to });
|
|
929
|
+
writeEnvFile(serializeLines(next));
|
|
930
|
+
}
|
|
931
|
+
function removeEnvEntries(names) {
|
|
932
|
+
if (names.length === 0) return 0;
|
|
933
|
+
const drop = /* @__PURE__ */ new Set();
|
|
934
|
+
for (const name of names) {
|
|
935
|
+
assertWritableName(name);
|
|
936
|
+
drop.add(name);
|
|
937
|
+
}
|
|
938
|
+
if (!existsSync7(envFilePath())) return 0;
|
|
939
|
+
const lines = parseLines(readRaw());
|
|
940
|
+
let removed = 0;
|
|
941
|
+
const next = [];
|
|
942
|
+
for (const line of lines) {
|
|
943
|
+
if (line.kind === "assignment" && line.name !== void 0 && drop.has(line.name)) {
|
|
944
|
+
removed += 1;
|
|
945
|
+
continue;
|
|
946
|
+
}
|
|
947
|
+
next.push(line);
|
|
948
|
+
}
|
|
949
|
+
if (removed === 0) return 0;
|
|
950
|
+
writeEnvFile(serializeLines(next));
|
|
951
|
+
return removed;
|
|
882
952
|
}
|
|
883
953
|
|
|
884
954
|
// src/types.ts
|
|
@@ -893,26 +963,39 @@ function classifyEntry(id, name, value, baselineById) {
|
|
|
893
963
|
function asMessage(error) {
|
|
894
964
|
return error instanceof Error ? error.message : String(error);
|
|
895
965
|
}
|
|
896
|
-
function
|
|
966
|
+
function applyProcessEnv(entries) {
|
|
897
967
|
for (const [key, value] of Object.entries(entries)) process.env[key] = value;
|
|
968
|
+
}
|
|
969
|
+
function applyOsUserEnv(entries) {
|
|
970
|
+
if (Object.keys(entries).length === 0) return;
|
|
898
971
|
try {
|
|
899
|
-
|
|
972
|
+
writeUserEnv(entries);
|
|
900
973
|
} catch (error) {
|
|
901
|
-
process.stderr.write(`custom-env: failed to
|
|
974
|
+
process.stderr.write(`custom-env: failed to write OS user environment: ${asMessage(error)}
|
|
902
975
|
`);
|
|
903
976
|
}
|
|
904
977
|
}
|
|
905
|
-
function
|
|
906
|
-
|
|
978
|
+
function writeLive(entries) {
|
|
979
|
+
applyProcessEnv(entries);
|
|
980
|
+
applyOsUserEnv(entries);
|
|
981
|
+
}
|
|
982
|
+
function removeLives(names) {
|
|
983
|
+
if (names.length === 0) return;
|
|
984
|
+
for (const name of names) delete process.env[name];
|
|
907
985
|
try {
|
|
908
|
-
|
|
986
|
+
removeUserEnvs(names);
|
|
909
987
|
} catch (error) {
|
|
910
|
-
process.stderr.write(
|
|
911
|
-
`)
|
|
988
|
+
process.stderr.write(
|
|
989
|
+
`custom-env: failed to remove OS user environment (${names.join(", ")}): ${asMessage(error)}
|
|
990
|
+
`
|
|
991
|
+
);
|
|
912
992
|
}
|
|
913
993
|
}
|
|
914
|
-
|
|
915
|
-
|
|
994
|
+
function removeLive(name) {
|
|
995
|
+
removeLives([name]);
|
|
996
|
+
}
|
|
997
|
+
var _cliInstall_dec, _cliStatus_dec, _restore_dec, _deleteEntry_dec, _removeVar_dec, _removeVars_dec, _setVar_dec, _setVars_dec, _upsert_dec, _list_dec, _a, _init;
|
|
998
|
+
var CustomEnvController = class extends (_a = TypertRemoteService, _list_dec = [Remote("list")], _upsert_dec = [Remote("upsert")], _setVars_dec = [Remote("setVars")], _setVar_dec = [Remote("setVar")], _removeVars_dec = [Remote("removeVars")], _removeVar_dec = [Remote("removeVar")], _deleteEntry_dec = [Remote("deleteEntry")], _restore_dec = [Remote("restore")], _cliStatus_dec = [Remote("cliStatus")], _cliInstall_dec = [Remote("cliInstall")], _a) {
|
|
916
999
|
constructor(ctx) {
|
|
917
1000
|
super(ctx, "customEnv");
|
|
918
1001
|
__runInitializers(_init, 5, this);
|
|
@@ -920,13 +1003,21 @@ var CustomEnvController = class extends (_a = TypertRemoteService, _list_dec = [
|
|
|
920
1003
|
__publicField(this, "idByName", /* @__PURE__ */ new Map());
|
|
921
1004
|
__publicField(this, "nameById", /* @__PURE__ */ new Map());
|
|
922
1005
|
__publicField(this, "idSeq", 0);
|
|
923
|
-
const loaded =
|
|
1006
|
+
const loaded = readEnvMap();
|
|
924
1007
|
for (const [name, value] of Object.entries(loaded)) {
|
|
925
1008
|
const id = this.allocId();
|
|
926
1009
|
this.bind(id, name);
|
|
927
1010
|
this.baselineById.set(id, { name, value });
|
|
928
1011
|
}
|
|
929
|
-
|
|
1012
|
+
applyProcessEnv(loaded);
|
|
1013
|
+
if (Object.keys(loaded).length > 0) {
|
|
1014
|
+
const handle = setImmediate(() => {
|
|
1015
|
+
applyOsUserEnv(loaded);
|
|
1016
|
+
});
|
|
1017
|
+
ctx.effect(() => () => {
|
|
1018
|
+
clearImmediate(handle);
|
|
1019
|
+
}, "custom-env: deferred OS env sync");
|
|
1020
|
+
}
|
|
930
1021
|
}
|
|
931
1022
|
allocId() {
|
|
932
1023
|
return `env-${++this.idSeq}`;
|
|
@@ -949,21 +1040,25 @@ var CustomEnvController = class extends (_a = TypertRemoteService, _list_dec = [
|
|
|
949
1040
|
}
|
|
950
1041
|
return name;
|
|
951
1042
|
}
|
|
952
|
-
/**
|
|
953
|
-
|
|
1043
|
+
/**
|
|
1044
|
+
* Write `name=value` replacing `from` in place; sync process.env / OS.
|
|
1045
|
+
* Empty values create missing keys only, unless `forceEmpty` (restore).
|
|
1046
|
+
*/
|
|
1047
|
+
commit(from, name, value, forceEmpty = false) {
|
|
954
1048
|
if (!isWritableEnvName(name) || from !== name && !isWritableEnvName(from)) {
|
|
955
1049
|
throw new RemoteError("custom-env/invalid-name", `refused name "${name}"`, { name });
|
|
956
1050
|
}
|
|
957
|
-
if (value === "") {
|
|
958
|
-
throw new RemoteError("custom-env/invalid-value", `empty value for "${name}"`, { name });
|
|
959
|
-
}
|
|
960
1051
|
try {
|
|
961
|
-
|
|
1052
|
+
if (from === name) {
|
|
1053
|
+
if (!writeEnvEntry(name, value, { forceEmpty })) return;
|
|
1054
|
+
} else {
|
|
1055
|
+
renameEnvEntry(from, name, value);
|
|
1056
|
+
removeLive(from);
|
|
1057
|
+
}
|
|
962
1058
|
} catch (error) {
|
|
963
1059
|
throw new RemoteError("custom-env/invalid-name", asMessage(error), { name }, { cause: error });
|
|
964
1060
|
}
|
|
965
|
-
|
|
966
|
-
applyLive({ [name]: value });
|
|
1061
|
+
writeLive({ [name]: value });
|
|
967
1062
|
}
|
|
968
1063
|
async list() {
|
|
969
1064
|
const { path, exists, entries: raw } = listEnvEntries();
|
|
@@ -993,18 +1088,66 @@ var CustomEnvController = class extends (_a = TypertRemoteService, _list_dec = [
|
|
|
993
1088
|
this.bind(id, name);
|
|
994
1089
|
return this.list();
|
|
995
1090
|
}
|
|
996
|
-
async
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1091
|
+
async setVars(entries) {
|
|
1092
|
+
if (entries.length === 0) return;
|
|
1093
|
+
const lastByName = /* @__PURE__ */ new Map();
|
|
1094
|
+
for (const entry of entries) {
|
|
1095
|
+
if (!isWritableEnvName(entry.name)) {
|
|
1096
|
+
throw new RemoteError("custom-env/invalid-name", `refused name "${entry.name}"`, { name: entry.name });
|
|
1097
|
+
}
|
|
1098
|
+
lastByName.set(entry.name, entry.value);
|
|
1000
1099
|
}
|
|
1100
|
+
const normalized = [...lastByName].map(([name, value]) => ({ name, value }));
|
|
1101
|
+
let written;
|
|
1001
1102
|
try {
|
|
1002
|
-
|
|
1103
|
+
written = writeEnvEntries(normalized);
|
|
1003
1104
|
} catch (error) {
|
|
1105
|
+
const name = normalized[0]?.name ?? "";
|
|
1004
1106
|
throw new RemoteError("custom-env/invalid-name", asMessage(error), { name }, { cause: error });
|
|
1005
1107
|
}
|
|
1006
|
-
|
|
1007
|
-
|
|
1108
|
+
if (written.length === 0) return;
|
|
1109
|
+
const live = {};
|
|
1110
|
+
for (const entry of written) {
|
|
1111
|
+
live[entry.name] = entry.value;
|
|
1112
|
+
if (!this.idByName.has(entry.name)) this.bind(this.allocId(), entry.name);
|
|
1113
|
+
}
|
|
1114
|
+
writeLive(live);
|
|
1115
|
+
}
|
|
1116
|
+
async setVar(name, value) {
|
|
1117
|
+
await this.setVars([{ name, value }]);
|
|
1118
|
+
}
|
|
1119
|
+
async removeVars(names) {
|
|
1120
|
+
if (names.length === 0) return 0;
|
|
1121
|
+
const unique = [];
|
|
1122
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1123
|
+
for (const name of names) {
|
|
1124
|
+
if (!isWritableEnvName(name)) {
|
|
1125
|
+
throw new RemoteError("custom-env/invalid-name", `refused name "${name}"`, { name });
|
|
1126
|
+
}
|
|
1127
|
+
if (seen.has(name)) continue;
|
|
1128
|
+
seen.add(name);
|
|
1129
|
+
unique.push(name);
|
|
1130
|
+
}
|
|
1131
|
+
let removed;
|
|
1132
|
+
try {
|
|
1133
|
+
removed = removeEnvEntries(unique);
|
|
1134
|
+
} catch (error) {
|
|
1135
|
+
const name = unique[0] ?? "";
|
|
1136
|
+
throw new RemoteError("custom-env/invalid-name", asMessage(error), { name }, { cause: error });
|
|
1137
|
+
}
|
|
1138
|
+
removeLives(unique);
|
|
1139
|
+
for (const name of unique) {
|
|
1140
|
+
const id = this.idByName.get(name);
|
|
1141
|
+
if (id !== void 0) this.unbind(id);
|
|
1142
|
+
}
|
|
1143
|
+
return removed;
|
|
1144
|
+
}
|
|
1145
|
+
async removeVar(name) {
|
|
1146
|
+
return await this.removeVars([name]) > 0;
|
|
1147
|
+
}
|
|
1148
|
+
async deleteEntry(id) {
|
|
1149
|
+
const name = this.requireName(id, id);
|
|
1150
|
+
await this.removeVars([name]);
|
|
1008
1151
|
return this.list();
|
|
1009
1152
|
}
|
|
1010
1153
|
async restore(id) {
|
|
@@ -1014,9 +1157,9 @@ var CustomEnvController = class extends (_a = TypertRemoteService, _list_dec = [
|
|
|
1014
1157
|
}
|
|
1015
1158
|
const base = this.baselineById.get(id);
|
|
1016
1159
|
if (base === void 0) return this.deleteEntry(id);
|
|
1017
|
-
if (name === base.name &&
|
|
1160
|
+
if (name === base.name && readEnvMap()[base.name] === base.value) return this.list();
|
|
1018
1161
|
try {
|
|
1019
|
-
this.commit(name, base.name, base.value);
|
|
1162
|
+
this.commit(name, base.name, base.value, true);
|
|
1020
1163
|
} catch (error) {
|
|
1021
1164
|
if (error instanceof RemoteError) {
|
|
1022
1165
|
throw new RemoteError("custom-env/restore-failed", error.message, { name }, { cause: error });
|
|
@@ -1041,6 +1184,10 @@ var CustomEnvController = class extends (_a = TypertRemoteService, _list_dec = [
|
|
|
1041
1184
|
_init = __decoratorStart(_a);
|
|
1042
1185
|
__decorateElement(_init, 1, "list", _list_dec, CustomEnvController);
|
|
1043
1186
|
__decorateElement(_init, 1, "upsert", _upsert_dec, CustomEnvController);
|
|
1187
|
+
__decorateElement(_init, 1, "setVars", _setVars_dec, CustomEnvController);
|
|
1188
|
+
__decorateElement(_init, 1, "setVar", _setVar_dec, CustomEnvController);
|
|
1189
|
+
__decorateElement(_init, 1, "removeVars", _removeVars_dec, CustomEnvController);
|
|
1190
|
+
__decorateElement(_init, 1, "removeVar", _removeVar_dec, CustomEnvController);
|
|
1044
1191
|
__decorateElement(_init, 1, "deleteEntry", _deleteEntry_dec, CustomEnvController);
|
|
1045
1192
|
__decorateElement(_init, 1, "restore", _restore_dec, CustomEnvController);
|
|
1046
1193
|
__decorateElement(_init, 1, "cliStatus", _cliStatus_dec, CustomEnvController);
|