@woosau/dsh-custom-env 0.1.1 → 0.1.4
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 +55 -24
- package/README.zh-CN.md +55 -24
- package/lib/client.js +138 -1
- package/lib/index.js +312 -159
- 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 +16 -1
- 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,6 +686,7 @@ 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;
|
|
@@ -799,6 +705,9 @@ function loadBindings() {
|
|
|
799
705
|
openKey: advapi32.func(
|
|
800
706
|
"long __stdcall RegOpenKeyExW(uintptr_t hKey, const char16_t *subKey, uint32_t options, uint32_t samDesired, _Out_ void **result)"
|
|
801
707
|
),
|
|
708
|
+
queryValue: advapi32.func(
|
|
709
|
+
"long __stdcall RegQueryValueExW(void *hKey, const char16_t *valueName, void *reserved, _Inout_ uint32_t *type, uint8_t *data, _Inout_ uint32_t *cbData)"
|
|
710
|
+
),
|
|
802
711
|
setValue: advapi32.func(
|
|
803
712
|
"long __stdcall RegSetValueExW(void *hKey, const char16_t *valueName, uint32_t reserved, uint32_t type, const uint8_t *data, uint32_t cbData)"
|
|
804
713
|
),
|
|
@@ -827,7 +736,28 @@ function broadcastEnvironmentChange(api) {
|
|
|
827
736
|
broadcastResult
|
|
828
737
|
);
|
|
829
738
|
}
|
|
830
|
-
function
|
|
739
|
+
function hasWindowsUserEnv(name, platform = process.platform) {
|
|
740
|
+
if (platform !== "win32") return false;
|
|
741
|
+
const api = loadBindings();
|
|
742
|
+
const keySlot = [null];
|
|
743
|
+
const openStatus = api.openKey(HKEY_CURRENT_USER, "Environment", 0, KEY_QUERY_VALUE, keySlot);
|
|
744
|
+
if (openStatus !== ERROR_SUCCESS) {
|
|
745
|
+
throw new Error(`custom-env: RegOpenKeyExW failed (Win32 ${openStatus})`);
|
|
746
|
+
}
|
|
747
|
+
const hKey = keySlot[0];
|
|
748
|
+
try {
|
|
749
|
+
const cbData = [0];
|
|
750
|
+
const status = api.queryValue(hKey, name, null, null, null, cbData);
|
|
751
|
+
if (status === ERROR_FILE_NOT_FOUND) return false;
|
|
752
|
+
if (status !== ERROR_SUCCESS && status !== 234) {
|
|
753
|
+
throw new Error(`custom-env: RegQueryValueExW(${name}) failed (Win32 ${status})`);
|
|
754
|
+
}
|
|
755
|
+
return true;
|
|
756
|
+
} finally {
|
|
757
|
+
api.closeKey(hKey);
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
function writeWindowsUserEnv(entries, platform = process.platform) {
|
|
831
761
|
if (platform !== "win32") return;
|
|
832
762
|
const pairs = Object.entries(entries);
|
|
833
763
|
if (pairs.length === 0) return;
|
|
@@ -851,8 +781,8 @@ function persistWindowsUserEnv(entries, platform = process.platform) {
|
|
|
851
781
|
}
|
|
852
782
|
broadcastEnvironmentChange(api);
|
|
853
783
|
}
|
|
854
|
-
function
|
|
855
|
-
if (platform !== "win32") return;
|
|
784
|
+
function removeWindowsUserEnvs(names, platform = process.platform) {
|
|
785
|
+
if (platform !== "win32" || names.length === 0) return;
|
|
856
786
|
const api = loadBindings();
|
|
857
787
|
const keySlot = [null];
|
|
858
788
|
const openStatus = api.openKey(HKEY_CURRENT_USER, "Environment", 0, KEY_SET_VALUE, keySlot);
|
|
@@ -861,9 +791,11 @@ function removeWindowsUserEnv(name, platform = process.platform) {
|
|
|
861
791
|
}
|
|
862
792
|
const hKey = keySlot[0];
|
|
863
793
|
try {
|
|
864
|
-
const
|
|
865
|
-
|
|
866
|
-
|
|
794
|
+
for (const name of names) {
|
|
795
|
+
const status = api.deleteValue(hKey, name);
|
|
796
|
+
if (status !== ERROR_SUCCESS && status !== ERROR_FILE_NOT_FOUND) {
|
|
797
|
+
throw new Error(`custom-env: RegDeleteValueW(${name}) failed (Win32 ${status})`);
|
|
798
|
+
}
|
|
867
799
|
}
|
|
868
800
|
} finally {
|
|
869
801
|
api.closeKey(hKey);
|
|
@@ -872,13 +804,172 @@ function removeWindowsUserEnv(name, platform = process.platform) {
|
|
|
872
804
|
}
|
|
873
805
|
|
|
874
806
|
// src/os-user-env/index.ts
|
|
875
|
-
function
|
|
876
|
-
|
|
877
|
-
|
|
807
|
+
function hasUserEnv(name) {
|
|
808
|
+
return hasWindowsUserEnv(name) || hasMacosUserEnv(name);
|
|
809
|
+
}
|
|
810
|
+
function writeUserEnv(entries) {
|
|
811
|
+
writeWindowsUserEnv(entries);
|
|
812
|
+
writeMacosUserEnv(entries);
|
|
878
813
|
}
|
|
879
|
-
function
|
|
880
|
-
|
|
881
|
-
|
|
814
|
+
function removeUserEnvs(names) {
|
|
815
|
+
if (names.length === 0) return;
|
|
816
|
+
removeWindowsUserEnvs(names);
|
|
817
|
+
removeMacosUserEnvs(names);
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
// src/env-file.ts
|
|
821
|
+
function envFilePath() {
|
|
822
|
+
return join7(resolveDshHome(), ".env");
|
|
823
|
+
}
|
|
824
|
+
function readRaw() {
|
|
825
|
+
const path = envFilePath();
|
|
826
|
+
return existsSync7(path) ? readFileSync2(path, "utf8") : "";
|
|
827
|
+
}
|
|
828
|
+
function writeEnvFile(content) {
|
|
829
|
+
const path = envFilePath();
|
|
830
|
+
mkdirSync3(dirname5(path), { recursive: true });
|
|
831
|
+
writeFileSync3(path, content, "utf8");
|
|
832
|
+
}
|
|
833
|
+
function encodeValue(value) {
|
|
834
|
+
if (/^[A-Za-z0-9_./:@+-]*$/u.test(value)) return value;
|
|
835
|
+
return `"${value.replace(/\\/gu, "\\\\").replace(/"/gu, '\\"').replace(/\n/gu, "\\n")}"`;
|
|
836
|
+
}
|
|
837
|
+
function parseLines(content) {
|
|
838
|
+
const text = content.replace(/^\uFEFF/u, "");
|
|
839
|
+
if (text === "") return [];
|
|
840
|
+
return text.split(/\r?\n/u).map((raw) => {
|
|
841
|
+
const trimmed = raw.trim();
|
|
842
|
+
if (trimmed === "" || trimmed.startsWith("#")) return { kind: "other", raw };
|
|
843
|
+
const match = /^(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=/u.exec(trimmed);
|
|
844
|
+
if (match?.[1] === void 0) return { kind: "other", raw };
|
|
845
|
+
return { kind: "assignment", raw, name: match[1] };
|
|
846
|
+
});
|
|
847
|
+
}
|
|
848
|
+
function serializeLines(lines) {
|
|
849
|
+
const kept = lines.filter((line) => !(line.kind === "other" && line.raw.trim() === ""));
|
|
850
|
+
return kept.length === 0 ? "" : `${kept.map((line) => line.raw).join("\n")}
|
|
851
|
+
`;
|
|
852
|
+
}
|
|
853
|
+
function assertWritableName(name) {
|
|
854
|
+
if (!isWritableEnvName(name)) {
|
|
855
|
+
throw new Error(`custom-env: refused bootstrap or invalid name "${name}"`);
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
function readEnvMap() {
|
|
859
|
+
const raw = readRaw().replace(/^\uFEFF/u, "").trim();
|
|
860
|
+
if (raw === "") return {};
|
|
861
|
+
let parsed;
|
|
862
|
+
try {
|
|
863
|
+
parsed = parseEnv(raw);
|
|
864
|
+
} catch (error) {
|
|
865
|
+
throw new Error(`custom-env: invalid dotenv syntax in ${envFilePath()}`, { cause: error });
|
|
866
|
+
}
|
|
867
|
+
const out = {};
|
|
868
|
+
for (const [key, value] of Object.entries(parsed)) {
|
|
869
|
+
if (typeof value === "string" && isWritableEnvName(key)) out[key] = value;
|
|
870
|
+
}
|
|
871
|
+
return out;
|
|
872
|
+
}
|
|
873
|
+
function listEnvEntries() {
|
|
874
|
+
const path = envFilePath();
|
|
875
|
+
const exists = existsSync7(path);
|
|
876
|
+
if (!exists) return { path, exists, entries: [] };
|
|
877
|
+
const map = readEnvMap();
|
|
878
|
+
const entries = [];
|
|
879
|
+
const seen = /* @__PURE__ */ new Set();
|
|
880
|
+
for (const line of parseLines(readRaw())) {
|
|
881
|
+
if (line.kind !== "assignment" || line.name === void 0 || seen.has(line.name)) continue;
|
|
882
|
+
const value = map[line.name];
|
|
883
|
+
if (value === void 0) continue;
|
|
884
|
+
seen.add(line.name);
|
|
885
|
+
entries.push({ name: line.name, value });
|
|
886
|
+
}
|
|
887
|
+
return { path, exists, entries };
|
|
888
|
+
}
|
|
889
|
+
function hasEnvKey(name) {
|
|
890
|
+
return Object.hasOwn(readEnvMap(), name) || hasUserEnv(name);
|
|
891
|
+
}
|
|
892
|
+
function selectEnvWrites(entries, options = {}) {
|
|
893
|
+
if (entries.length === 0) return [];
|
|
894
|
+
const lastByName = /* @__PURE__ */ new Map();
|
|
895
|
+
for (const entry of entries) {
|
|
896
|
+
assertWritableName(entry.name);
|
|
897
|
+
lastByName.set(entry.name, entry.value);
|
|
898
|
+
}
|
|
899
|
+
const forceEmpty = options.forceEmpty === true;
|
|
900
|
+
const selected = [];
|
|
901
|
+
for (const [name, value] of lastByName) {
|
|
902
|
+
if (value === "" && !forceEmpty && hasEnvKey(name)) continue;
|
|
903
|
+
selected.push({ name, value });
|
|
904
|
+
}
|
|
905
|
+
return selected;
|
|
906
|
+
}
|
|
907
|
+
function writeEnvEntries(entries, options = {}) {
|
|
908
|
+
const selected = selectEnvWrites(entries, options);
|
|
909
|
+
if (selected.length === 0) return selected;
|
|
910
|
+
const pending = new Map(selected.map((entry) => [entry.name, entry.value]));
|
|
911
|
+
const next = [];
|
|
912
|
+
for (const line of parseLines(readRaw())) {
|
|
913
|
+
if (line.kind === "assignment" && line.name !== void 0 && pending.has(line.name)) {
|
|
914
|
+
const value = pending.get(line.name);
|
|
915
|
+
pending.delete(line.name);
|
|
916
|
+
next.push({
|
|
917
|
+
kind: "assignment",
|
|
918
|
+
raw: `${line.name}=${encodeValue(value)}`,
|
|
919
|
+
name: line.name
|
|
920
|
+
});
|
|
921
|
+
continue;
|
|
922
|
+
}
|
|
923
|
+
next.push(line);
|
|
924
|
+
}
|
|
925
|
+
for (const [name, value] of pending) {
|
|
926
|
+
next.push({ kind: "assignment", raw: `${name}=${encodeValue(value)}`, name });
|
|
927
|
+
}
|
|
928
|
+
writeEnvFile(serializeLines(next));
|
|
929
|
+
return selected;
|
|
930
|
+
}
|
|
931
|
+
function writeEnvEntry(name, value, options = {}) {
|
|
932
|
+
return writeEnvEntries([{ name, value }], options).length > 0;
|
|
933
|
+
}
|
|
934
|
+
function renameEnvEntry(from, to, value) {
|
|
935
|
+
assertWritableName(from);
|
|
936
|
+
assertWritableName(to);
|
|
937
|
+
const assignment = `${to}=${encodeValue(value)}`;
|
|
938
|
+
let replaced = false;
|
|
939
|
+
const next = [];
|
|
940
|
+
for (const line of parseLines(readRaw())) {
|
|
941
|
+
if (line.kind === "assignment" && line.name === from) {
|
|
942
|
+
replaced = true;
|
|
943
|
+
next.push({ kind: "assignment", raw: assignment, name: to });
|
|
944
|
+
continue;
|
|
945
|
+
}
|
|
946
|
+
if (line.kind === "assignment" && line.name === to) continue;
|
|
947
|
+
next.push(line);
|
|
948
|
+
}
|
|
949
|
+
if (!replaced) next.push({ kind: "assignment", raw: assignment, name: to });
|
|
950
|
+
writeEnvFile(serializeLines(next));
|
|
951
|
+
}
|
|
952
|
+
function removeEnvEntries(names) {
|
|
953
|
+
if (names.length === 0) return 0;
|
|
954
|
+
const drop = /* @__PURE__ */ new Set();
|
|
955
|
+
for (const name of names) {
|
|
956
|
+
assertWritableName(name);
|
|
957
|
+
drop.add(name);
|
|
958
|
+
}
|
|
959
|
+
if (!existsSync7(envFilePath())) return 0;
|
|
960
|
+
const lines = parseLines(readRaw());
|
|
961
|
+
let removed = 0;
|
|
962
|
+
const next = [];
|
|
963
|
+
for (const line of lines) {
|
|
964
|
+
if (line.kind === "assignment" && line.name !== void 0 && drop.has(line.name)) {
|
|
965
|
+
removed += 1;
|
|
966
|
+
continue;
|
|
967
|
+
}
|
|
968
|
+
next.push(line);
|
|
969
|
+
}
|
|
970
|
+
if (removed === 0) return 0;
|
|
971
|
+
writeEnvFile(serializeLines(next));
|
|
972
|
+
return removed;
|
|
882
973
|
}
|
|
883
974
|
|
|
884
975
|
// src/types.ts
|
|
@@ -893,26 +984,32 @@ function classifyEntry(id, name, value, baselineById) {
|
|
|
893
984
|
function asMessage(error) {
|
|
894
985
|
return error instanceof Error ? error.message : String(error);
|
|
895
986
|
}
|
|
896
|
-
function
|
|
987
|
+
function writeLive(entries) {
|
|
897
988
|
for (const [key, value] of Object.entries(entries)) process.env[key] = value;
|
|
898
989
|
try {
|
|
899
|
-
|
|
990
|
+
writeUserEnv(entries);
|
|
900
991
|
} catch (error) {
|
|
901
|
-
process.stderr.write(`custom-env: failed to
|
|
992
|
+
process.stderr.write(`custom-env: failed to write OS user environment: ${asMessage(error)}
|
|
902
993
|
`);
|
|
903
994
|
}
|
|
904
995
|
}
|
|
905
|
-
function
|
|
906
|
-
|
|
996
|
+
function removeLives(names) {
|
|
997
|
+
if (names.length === 0) return;
|
|
998
|
+
for (const name of names) delete process.env[name];
|
|
907
999
|
try {
|
|
908
|
-
|
|
1000
|
+
removeUserEnvs(names);
|
|
909
1001
|
} catch (error) {
|
|
910
|
-
process.stderr.write(
|
|
911
|
-
`)
|
|
1002
|
+
process.stderr.write(
|
|
1003
|
+
`custom-env: failed to remove OS user environment (${names.join(", ")}): ${asMessage(error)}
|
|
1004
|
+
`
|
|
1005
|
+
);
|
|
912
1006
|
}
|
|
913
1007
|
}
|
|
914
|
-
|
|
915
|
-
|
|
1008
|
+
function removeLive(name) {
|
|
1009
|
+
removeLives([name]);
|
|
1010
|
+
}
|
|
1011
|
+
var _cliInstall_dec, _cliStatus_dec, _restore_dec, _deleteEntry_dec, _removeVar_dec, _removeVars_dec, _setVar_dec, _setVars_dec, _upsert_dec, _list_dec, _a, _init;
|
|
1012
|
+
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
1013
|
constructor(ctx) {
|
|
917
1014
|
super(ctx, "customEnv");
|
|
918
1015
|
__runInitializers(_init, 5, this);
|
|
@@ -920,13 +1017,13 @@ var CustomEnvController = class extends (_a = TypertRemoteService, _list_dec = [
|
|
|
920
1017
|
__publicField(this, "idByName", /* @__PURE__ */ new Map());
|
|
921
1018
|
__publicField(this, "nameById", /* @__PURE__ */ new Map());
|
|
922
1019
|
__publicField(this, "idSeq", 0);
|
|
923
|
-
const loaded =
|
|
1020
|
+
const loaded = readEnvMap();
|
|
924
1021
|
for (const [name, value] of Object.entries(loaded)) {
|
|
925
1022
|
const id = this.allocId();
|
|
926
1023
|
this.bind(id, name);
|
|
927
1024
|
this.baselineById.set(id, { name, value });
|
|
928
1025
|
}
|
|
929
|
-
|
|
1026
|
+
writeLive(loaded);
|
|
930
1027
|
}
|
|
931
1028
|
allocId() {
|
|
932
1029
|
return `env-${++this.idSeq}`;
|
|
@@ -949,21 +1046,25 @@ var CustomEnvController = class extends (_a = TypertRemoteService, _list_dec = [
|
|
|
949
1046
|
}
|
|
950
1047
|
return name;
|
|
951
1048
|
}
|
|
952
|
-
/**
|
|
953
|
-
|
|
1049
|
+
/**
|
|
1050
|
+
* Write `name=value` replacing `from` in place; sync process.env / OS.
|
|
1051
|
+
* Empty values create missing keys only, unless `forceEmpty` (restore).
|
|
1052
|
+
*/
|
|
1053
|
+
commit(from, name, value, forceEmpty = false) {
|
|
954
1054
|
if (!isWritableEnvName(name) || from !== name && !isWritableEnvName(from)) {
|
|
955
1055
|
throw new RemoteError("custom-env/invalid-name", `refused name "${name}"`, { name });
|
|
956
1056
|
}
|
|
957
|
-
if (value === "") {
|
|
958
|
-
throw new RemoteError("custom-env/invalid-value", `empty value for "${name}"`, { name });
|
|
959
|
-
}
|
|
960
1057
|
try {
|
|
961
|
-
|
|
1058
|
+
if (from === name) {
|
|
1059
|
+
if (!writeEnvEntry(name, value, { forceEmpty })) return;
|
|
1060
|
+
} else {
|
|
1061
|
+
renameEnvEntry(from, name, value);
|
|
1062
|
+
removeLive(from);
|
|
1063
|
+
}
|
|
962
1064
|
} catch (error) {
|
|
963
1065
|
throw new RemoteError("custom-env/invalid-name", asMessage(error), { name }, { cause: error });
|
|
964
1066
|
}
|
|
965
|
-
|
|
966
|
-
applyLive({ [name]: value });
|
|
1067
|
+
writeLive({ [name]: value });
|
|
967
1068
|
}
|
|
968
1069
|
async list() {
|
|
969
1070
|
const { path, exists, entries: raw } = listEnvEntries();
|
|
@@ -993,18 +1094,66 @@ var CustomEnvController = class extends (_a = TypertRemoteService, _list_dec = [
|
|
|
993
1094
|
this.bind(id, name);
|
|
994
1095
|
return this.list();
|
|
995
1096
|
}
|
|
996
|
-
async
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1097
|
+
async setVars(entries) {
|
|
1098
|
+
if (entries.length === 0) return;
|
|
1099
|
+
const lastByName = /* @__PURE__ */ new Map();
|
|
1100
|
+
for (const entry of entries) {
|
|
1101
|
+
if (!isWritableEnvName(entry.name)) {
|
|
1102
|
+
throw new RemoteError("custom-env/invalid-name", `refused name "${entry.name}"`, { name: entry.name });
|
|
1103
|
+
}
|
|
1104
|
+
lastByName.set(entry.name, entry.value);
|
|
1000
1105
|
}
|
|
1106
|
+
const normalized = [...lastByName].map(([name, value]) => ({ name, value }));
|
|
1107
|
+
let written;
|
|
1001
1108
|
try {
|
|
1002
|
-
|
|
1109
|
+
written = writeEnvEntries(normalized);
|
|
1003
1110
|
} catch (error) {
|
|
1111
|
+
const name = normalized[0]?.name ?? "";
|
|
1004
1112
|
throw new RemoteError("custom-env/invalid-name", asMessage(error), { name }, { cause: error });
|
|
1005
1113
|
}
|
|
1006
|
-
|
|
1007
|
-
|
|
1114
|
+
if (written.length === 0) return;
|
|
1115
|
+
const live = {};
|
|
1116
|
+
for (const entry of written) {
|
|
1117
|
+
live[entry.name] = entry.value;
|
|
1118
|
+
if (!this.idByName.has(entry.name)) this.bind(this.allocId(), entry.name);
|
|
1119
|
+
}
|
|
1120
|
+
writeLive(live);
|
|
1121
|
+
}
|
|
1122
|
+
async setVar(name, value) {
|
|
1123
|
+
await this.setVars([{ name, value }]);
|
|
1124
|
+
}
|
|
1125
|
+
async removeVars(names) {
|
|
1126
|
+
if (names.length === 0) return 0;
|
|
1127
|
+
const unique = [];
|
|
1128
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1129
|
+
for (const name of names) {
|
|
1130
|
+
if (!isWritableEnvName(name)) {
|
|
1131
|
+
throw new RemoteError("custom-env/invalid-name", `refused name "${name}"`, { name });
|
|
1132
|
+
}
|
|
1133
|
+
if (seen.has(name)) continue;
|
|
1134
|
+
seen.add(name);
|
|
1135
|
+
unique.push(name);
|
|
1136
|
+
}
|
|
1137
|
+
let removed;
|
|
1138
|
+
try {
|
|
1139
|
+
removed = removeEnvEntries(unique);
|
|
1140
|
+
} catch (error) {
|
|
1141
|
+
const name = unique[0] ?? "";
|
|
1142
|
+
throw new RemoteError("custom-env/invalid-name", asMessage(error), { name }, { cause: error });
|
|
1143
|
+
}
|
|
1144
|
+
removeLives(unique);
|
|
1145
|
+
for (const name of unique) {
|
|
1146
|
+
const id = this.idByName.get(name);
|
|
1147
|
+
if (id !== void 0) this.unbind(id);
|
|
1148
|
+
}
|
|
1149
|
+
return removed;
|
|
1150
|
+
}
|
|
1151
|
+
async removeVar(name) {
|
|
1152
|
+
return await this.removeVars([name]) > 0;
|
|
1153
|
+
}
|
|
1154
|
+
async deleteEntry(id) {
|
|
1155
|
+
const name = this.requireName(id, id);
|
|
1156
|
+
await this.removeVars([name]);
|
|
1008
1157
|
return this.list();
|
|
1009
1158
|
}
|
|
1010
1159
|
async restore(id) {
|
|
@@ -1014,9 +1163,9 @@ var CustomEnvController = class extends (_a = TypertRemoteService, _list_dec = [
|
|
|
1014
1163
|
}
|
|
1015
1164
|
const base = this.baselineById.get(id);
|
|
1016
1165
|
if (base === void 0) return this.deleteEntry(id);
|
|
1017
|
-
if (name === base.name &&
|
|
1166
|
+
if (name === base.name && readEnvMap()[base.name] === base.value) return this.list();
|
|
1018
1167
|
try {
|
|
1019
|
-
this.commit(name, base.name, base.value);
|
|
1168
|
+
this.commit(name, base.name, base.value, true);
|
|
1020
1169
|
} catch (error) {
|
|
1021
1170
|
if (error instanceof RemoteError) {
|
|
1022
1171
|
throw new RemoteError("custom-env/restore-failed", error.message, { name }, { cause: error });
|
|
@@ -1041,6 +1190,10 @@ var CustomEnvController = class extends (_a = TypertRemoteService, _list_dec = [
|
|
|
1041
1190
|
_init = __decoratorStart(_a);
|
|
1042
1191
|
__decorateElement(_init, 1, "list", _list_dec, CustomEnvController);
|
|
1043
1192
|
__decorateElement(_init, 1, "upsert", _upsert_dec, CustomEnvController);
|
|
1193
|
+
__decorateElement(_init, 1, "setVars", _setVars_dec, CustomEnvController);
|
|
1194
|
+
__decorateElement(_init, 1, "setVar", _setVar_dec, CustomEnvController);
|
|
1195
|
+
__decorateElement(_init, 1, "removeVars", _removeVars_dec, CustomEnvController);
|
|
1196
|
+
__decorateElement(_init, 1, "removeVar", _removeVar_dec, CustomEnvController);
|
|
1044
1197
|
__decorateElement(_init, 1, "deleteEntry", _deleteEntry_dec, CustomEnvController);
|
|
1045
1198
|
__decorateElement(_init, 1, "restore", _restore_dec, CustomEnvController);
|
|
1046
1199
|
__decorateElement(_init, 1, "cliStatus", _cliStatus_dec, CustomEnvController);
|