msdevflow 0.7.6 → 0.7.7
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/lib/bootstrap.js +212 -89
- package/package.json +1 -1
package/lib/bootstrap.js
CHANGED
|
@@ -764,11 +764,13 @@ function outputLines(output) {
|
|
|
764
764
|
return output.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
765
765
|
}
|
|
766
766
|
|
|
767
|
-
|
|
768
|
-
|
|
767
|
+
function executableLookup(name, platform) {
|
|
768
|
+
return platform === "win32"
|
|
769
769
|
? { command: "where.exe", args: [name] }
|
|
770
770
|
: { command: "sh", args: ["-c", "command -v -- \"$1\"", "sh", name] };
|
|
771
|
-
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
function executableFromLookup(result, platform) {
|
|
772
774
|
if (result.status !== 0) {
|
|
773
775
|
return "";
|
|
774
776
|
}
|
|
@@ -779,6 +781,20 @@ export function resolveExecutable(name, run, platform) {
|
|
|
779
781
|
return candidates[0] || "";
|
|
780
782
|
}
|
|
781
783
|
|
|
784
|
+
export function resolveExecutable(name, run, platform) {
|
|
785
|
+
return executableFromLookup(
|
|
786
|
+
runOptional(run, executableLookup(name, platform)),
|
|
787
|
+
platform,
|
|
788
|
+
);
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
async function resolveExecutableAsync(name, run, platform) {
|
|
792
|
+
return executableFromLookup(
|
|
793
|
+
await runOptionalAsync(run, executableLookup(name, platform)),
|
|
794
|
+
platform,
|
|
795
|
+
);
|
|
796
|
+
}
|
|
797
|
+
|
|
782
798
|
export function detectClients(run, platform) {
|
|
783
799
|
return SUPPORTED_CLIENTS.filter((client) => Boolean(resolveExecutable(client, run, platform)));
|
|
784
800
|
}
|
|
@@ -849,60 +865,95 @@ function hasPythonShebang(file) {
|
|
|
849
865
|
}
|
|
850
866
|
}
|
|
851
867
|
|
|
852
|
-
function
|
|
853
|
-
|
|
868
|
+
function pythonCandidates(platform) {
|
|
869
|
+
return platform === "win32"
|
|
854
870
|
? [{ command: "py", prefix: ["-3"] }, { command: "python", prefix: [] }, { command: "python3", prefix: [] }]
|
|
855
871
|
: [{ command: "python3", prefix: [] }, { command: "python", prefix: [] }];
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
function parsePythonScriptDirectory(result) {
|
|
875
|
+
if (result.status !== 0) {
|
|
876
|
+
return "";
|
|
877
|
+
}
|
|
878
|
+
try {
|
|
879
|
+
const directory = JSON.parse(result.stdout.trim());
|
|
880
|
+
return typeof directory === "string" ? directory : "";
|
|
881
|
+
} catch {
|
|
882
|
+
return "";
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
function pythonScriptDirectories(run, platform) {
|
|
887
|
+
const probe = "import json,sysconfig; print(json.dumps(sysconfig.get_path('scripts')))";
|
|
888
|
+
return pythonCandidates(platform)
|
|
889
|
+
.map((candidate) => parsePythonScriptDirectory(runOptional(run, {
|
|
890
|
+
command: candidate.command,
|
|
891
|
+
args: [...candidate.prefix, "-c", probe],
|
|
892
|
+
})))
|
|
893
|
+
.filter(Boolean);
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
async function pythonScriptDirectoriesAsync(run, platform) {
|
|
856
897
|
const probe = "import json,sysconfig; print(json.dumps(sysconfig.get_path('scripts')))";
|
|
857
898
|
const directories = [];
|
|
858
|
-
for (const candidate of
|
|
859
|
-
const
|
|
899
|
+
for (const candidate of pythonCandidates(platform)) {
|
|
900
|
+
const directory = parsePythonScriptDirectory(await runOptionalAsync(run, {
|
|
860
901
|
command: candidate.command,
|
|
861
902
|
args: [...candidate.prefix, "-c", probe],
|
|
862
|
-
});
|
|
863
|
-
if (
|
|
864
|
-
|
|
865
|
-
}
|
|
866
|
-
try {
|
|
867
|
-
const directory = JSON.parse(result.stdout.trim());
|
|
868
|
-
if (typeof directory === "string" && directory) {
|
|
869
|
-
directories.push(directory);
|
|
870
|
-
}
|
|
871
|
-
} catch {
|
|
872
|
-
continue;
|
|
903
|
+
}));
|
|
904
|
+
if (directory) {
|
|
905
|
+
directories.push(directory);
|
|
873
906
|
}
|
|
874
907
|
}
|
|
875
908
|
return directories;
|
|
876
909
|
}
|
|
877
910
|
|
|
878
|
-
function
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
911
|
+
function isKnownPythonGitcodePath(executable) {
|
|
912
|
+
return /[\\/]Python[^\\/]*[\\/]Scripts[\\/]gitcode(?:\.exe)?$/i.test(executable)
|
|
913
|
+
|| /[\\/]pipx[\\/]/i.test(executable)
|
|
914
|
+
|| hasPythonShebang(executable);
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
function executableMatchesPythonScripts(executable, directories, platform) {
|
|
884
918
|
const directory = normalizedPath(executableDirectory(executable, platform), platform);
|
|
885
|
-
return
|
|
886
|
-
.some((candidate) => normalizedPath(candidate, platform) === directory);
|
|
919
|
+
return directories.some((candidate) => normalizedPath(candidate, platform) === directory);
|
|
887
920
|
}
|
|
888
921
|
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
922
|
+
function isPythonGitcode(executable, run, platform) {
|
|
923
|
+
return isKnownPythonGitcodePath(executable)
|
|
924
|
+
|| executableMatchesPythonScripts(
|
|
925
|
+
executable,
|
|
926
|
+
pythonScriptDirectories(run, platform),
|
|
927
|
+
platform,
|
|
928
|
+
);
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
async function isPythonGitcodeAsync(executable, run, platform) {
|
|
932
|
+
return isKnownPythonGitcodePath(executable)
|
|
933
|
+
|| executableMatchesPythonScripts(
|
|
934
|
+
executable,
|
|
935
|
+
await pythonScriptDirectoriesAsync(run, platform),
|
|
936
|
+
platform,
|
|
937
|
+
);
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
function absentGitcodeDiagnosis() {
|
|
941
|
+
return {
|
|
942
|
+
classification: "absent",
|
|
943
|
+
existingExecutable: null,
|
|
944
|
+
workflowCommand: "gitcode",
|
|
945
|
+
};
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
function pythonGitcodeDiagnosis(executable) {
|
|
949
|
+
return {
|
|
950
|
+
classification: "python",
|
|
951
|
+
existingExecutable: executable,
|
|
952
|
+
workflowCommand: "gitcode-npm",
|
|
953
|
+
};
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
function npmGitcodeDiagnosis(executable, doctor) {
|
|
906
957
|
if (doctor.status === 0) {
|
|
907
958
|
try {
|
|
908
959
|
const metadata = JSON.parse(doctor.stdout);
|
|
@@ -920,23 +971,74 @@ export function diagnoseGitcode(run, platform) {
|
|
|
920
971
|
throw new BootstrapError(`Existing gitcode has unknown ownership: ${executable}. Refusing to overwrite it.`, 3);
|
|
921
972
|
}
|
|
922
973
|
|
|
923
|
-
function
|
|
924
|
-
const
|
|
925
|
-
|
|
926
|
-
|
|
974
|
+
export function diagnoseGitcode(run, platform) {
|
|
975
|
+
const executable = resolveExecutable("gitcode", run, platform);
|
|
976
|
+
if (!executable) {
|
|
977
|
+
return absentGitcodeDiagnosis();
|
|
978
|
+
}
|
|
979
|
+
if (isPythonGitcode(executable, run, platform)) {
|
|
980
|
+
return pythonGitcodeDiagnosis(executable);
|
|
981
|
+
}
|
|
982
|
+
return npmGitcodeDiagnosis(
|
|
983
|
+
executable,
|
|
984
|
+
runOptional(run, executableCommand(executable, ["doctor", "install", "--json"], platform)),
|
|
985
|
+
);
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
async function diagnoseGitcodeAsync(run, platform) {
|
|
989
|
+
const executable = await resolveExecutableAsync("gitcode", run, platform);
|
|
990
|
+
if (!executable) {
|
|
991
|
+
return absentGitcodeDiagnosis();
|
|
992
|
+
}
|
|
993
|
+
if (await isPythonGitcodeAsync(executable, run, platform)) {
|
|
994
|
+
return pythonGitcodeDiagnosis(executable);
|
|
995
|
+
}
|
|
996
|
+
return npmGitcodeDiagnosis(
|
|
997
|
+
executable,
|
|
998
|
+
await runOptionalAsync(
|
|
999
|
+
run,
|
|
1000
|
+
executableCommand(executable, ["doctor", "install", "--json"], platform),
|
|
1001
|
+
),
|
|
1002
|
+
);
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
function parsePythonProbe(result) {
|
|
1006
|
+
if (result.status !== 0) {
|
|
1007
|
+
return null;
|
|
1008
|
+
}
|
|
1009
|
+
try {
|
|
1010
|
+
const data = JSON.parse(result.stdout.trim());
|
|
1011
|
+
if (data.version[0] > 3 || (data.version[0] === 3 && data.version[1] >= 10)) {
|
|
1012
|
+
return data;
|
|
1013
|
+
}
|
|
1014
|
+
} catch {
|
|
1015
|
+
return null;
|
|
1016
|
+
}
|
|
1017
|
+
return null;
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
function pythonProbe(candidate) {
|
|
927
1021
|
const probe = "import json,sys; print(json.dumps({'executable':sys.executable,'version':list(sys.version_info[:3])}))";
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
1022
|
+
return { command: candidate.command, args: [...candidate.prefix, "-c", probe] };
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
function detectPython(run, platform) {
|
|
1026
|
+
for (const candidate of pythonCandidates(platform)) {
|
|
1027
|
+
const data = parsePythonProbe(runOptional(run, pythonProbe(candidate)));
|
|
1028
|
+
if (data) {
|
|
1029
|
+
return data;
|
|
932
1030
|
}
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
1031
|
+
}
|
|
1032
|
+
throw new BootstrapError("Python >=3.10 is required for workflow scripts.", 2);
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
async function detectPythonAsync(run, platform) {
|
|
1036
|
+
for (const candidate of pythonCandidates(platform)) {
|
|
1037
|
+
const python = parsePythonProbe(
|
|
1038
|
+
await runOptionalAsync(run, pythonProbe(candidate)),
|
|
1039
|
+
);
|
|
1040
|
+
if (python) {
|
|
1041
|
+
return python;
|
|
940
1042
|
}
|
|
941
1043
|
}
|
|
942
1044
|
throw new BootstrapError("Python >=3.10 is required for workflow scripts.", 2);
|
|
@@ -1004,7 +1106,7 @@ export function pythonRuntimeDetails(
|
|
|
1004
1106
|
return pythonRuntimeAt(directory, python, platform);
|
|
1005
1107
|
}
|
|
1006
1108
|
|
|
1007
|
-
function validatePythonRuntime(runtime, run) {
|
|
1109
|
+
async function validatePythonRuntime(runtime, run) {
|
|
1008
1110
|
let marker;
|
|
1009
1111
|
try {
|
|
1010
1112
|
const metadata = lstatSync(runtime.directory);
|
|
@@ -1043,10 +1145,10 @@ function validatePythonRuntime(runtime, run) {
|
|
|
1043
1145
|
closeSync(descriptor);
|
|
1044
1146
|
}
|
|
1045
1147
|
}
|
|
1046
|
-
if (
|
|
1148
|
+
if ((await runOptionalAsync(run, {
|
|
1047
1149
|
command: runtime.executable,
|
|
1048
1150
|
args: ["-m", "pip", "--version"],
|
|
1049
|
-
}).status !== 0) {
|
|
1151
|
+
})).status !== 0) {
|
|
1050
1152
|
return "repair";
|
|
1051
1153
|
}
|
|
1052
1154
|
return "current";
|
|
@@ -1069,8 +1171,8 @@ function writePythonRuntimeMarker(runtime) {
|
|
|
1069
1171
|
writeFileSync(runtime.marker, PYTHON_RUNTIME_MARKER_CONTENT, { encoding: "utf8", flag: "wx" });
|
|
1070
1172
|
}
|
|
1071
1173
|
|
|
1072
|
-
async function ensurePythonRuntime(runtime, status, runLong
|
|
1073
|
-
if (validatePythonRuntime(runtime,
|
|
1174
|
+
async function ensurePythonRuntime(runtime, status, runLong) {
|
|
1175
|
+
if ((await validatePythonRuntime(runtime, runLong)) !== status) {
|
|
1074
1176
|
throw new BootstrapError(
|
|
1075
1177
|
`Managed Python runtime changed after confirmation: ${runtime.directory}`,
|
|
1076
1178
|
3,
|
|
@@ -1499,7 +1601,42 @@ export async function runSetup(options, dependencies = {}) {
|
|
|
1499
1601
|
platform,
|
|
1500
1602
|
)),
|
|
1501
1603
|
);
|
|
1502
|
-
const diagnosis =
|
|
1604
|
+
const { diagnosis, python, pythonRuntime } = await progress.run(
|
|
1605
|
+
"Inspecting local GitCode and Python environment",
|
|
1606
|
+
async () => {
|
|
1607
|
+
const [diagnosisResult, pythonResult] = await Promise.all([
|
|
1608
|
+
diagnoseGitcodeAsync(runLong, platform),
|
|
1609
|
+
detectPythonAsync(runLong, platform),
|
|
1610
|
+
]);
|
|
1611
|
+
const configuredPythonRuntime = pythonRuntimeDetails(
|
|
1612
|
+
pythonResult,
|
|
1613
|
+
environment,
|
|
1614
|
+
platform,
|
|
1615
|
+
);
|
|
1616
|
+
const runtime = dependencies.pythonRuntimeDir
|
|
1617
|
+
? pythonRuntimeAt(dependencies.pythonRuntimeDir, pythonResult, platform)
|
|
1618
|
+
: configuredPythonRuntime;
|
|
1619
|
+
const [venvCapability, runtimeStatus] = await Promise.all([
|
|
1620
|
+
runOptionalAsync(runLong, {
|
|
1621
|
+
command: pythonResult.executable,
|
|
1622
|
+
args: ["-m", "venv", "--help"],
|
|
1623
|
+
}),
|
|
1624
|
+
validatePythonRuntime(runtime, runLong),
|
|
1625
|
+
]);
|
|
1626
|
+
if (venvCapability.status !== 0) {
|
|
1627
|
+
throw new BootstrapError(
|
|
1628
|
+
`Python venv is unavailable for ${pythonResult.executable}. Install the venv component for this Python (for example python3-venv on Debian/Ubuntu) and rerun setup.`,
|
|
1629
|
+
2,
|
|
1630
|
+
);
|
|
1631
|
+
}
|
|
1632
|
+
runtime.status = runtimeStatus;
|
|
1633
|
+
return {
|
|
1634
|
+
diagnosis: diagnosisResult,
|
|
1635
|
+
python: pythonResult,
|
|
1636
|
+
pythonRuntime: runtime,
|
|
1637
|
+
};
|
|
1638
|
+
},
|
|
1639
|
+
);
|
|
1503
1640
|
diagnosis.officialLatest = latestResult.status === 0 ? latestResult.stdout.trim() || null : null;
|
|
1504
1641
|
const gitcodeInstall = gitcodeInstallDetails(diagnosis.classification, environment, platform, npmPrefix);
|
|
1505
1642
|
if (gitcodeInstall.wrapper) {
|
|
@@ -1511,22 +1648,6 @@ export async function runSetup(options, dependencies = {}) {
|
|
|
1511
1648
|
platform,
|
|
1512
1649
|
);
|
|
1513
1650
|
|
|
1514
|
-
const python = detectPython(run, platform);
|
|
1515
|
-
const venvCapability = runOptional(run, {
|
|
1516
|
-
command: python.executable,
|
|
1517
|
-
args: ["-m", "venv", "--help"],
|
|
1518
|
-
});
|
|
1519
|
-
if (venvCapability.status !== 0) {
|
|
1520
|
-
throw new BootstrapError(
|
|
1521
|
-
`Python venv is unavailable for ${python.executable}. Install the venv component for this Python (for example python3-venv on Debian/Ubuntu) and rerun setup.`,
|
|
1522
|
-
2,
|
|
1523
|
-
);
|
|
1524
|
-
}
|
|
1525
|
-
const configuredPythonRuntime = pythonRuntimeDetails(python, environment, platform);
|
|
1526
|
-
const pythonRuntime = dependencies.pythonRuntimeDir
|
|
1527
|
-
? pythonRuntimeAt(dependencies.pythonRuntimeDir, python, platform)
|
|
1528
|
-
: configuredPythonRuntime;
|
|
1529
|
-
pythonRuntime.status = validatePythonRuntime(pythonRuntime, run);
|
|
1530
1651
|
const venvInvocation = {
|
|
1531
1652
|
command: python.executable,
|
|
1532
1653
|
args: ["-m", "venv", pythonRuntime.directory],
|
|
@@ -1554,14 +1675,16 @@ export async function runSetup(options, dependencies = {}) {
|
|
|
1554
1675
|
if (!options.yes && !(await (dependencies.confirm || confirmPlan)(process.stdin, process.stdout))) {
|
|
1555
1676
|
throw new BootstrapError("Setup cancelled; no changes were applied.", 2);
|
|
1556
1677
|
}
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1678
|
+
await progress.run("Revalidating command ownership", async () => {
|
|
1679
|
+
const currentDiagnosis = await diagnoseGitcodeAsync(runLong, platform);
|
|
1680
|
+
if (currentDiagnosis.classification !== diagnosis.classification
|
|
1681
|
+
|| currentDiagnosis.existingExecutable !== diagnosis.existingExecutable) {
|
|
1682
|
+
throw new BootstrapError("GitCode command ownership changed after confirmation; refusing to install.", 3);
|
|
1683
|
+
}
|
|
1684
|
+
if (gitcodeInstall.wrapper) {
|
|
1685
|
+
validateWrapper(gitcodeInstall, platform);
|
|
1686
|
+
}
|
|
1687
|
+
});
|
|
1565
1688
|
|
|
1566
1689
|
await progress.run(
|
|
1567
1690
|
pythonRuntime.status === "absent"
|
|
@@ -1569,7 +1692,7 @@ export async function runSetup(options, dependencies = {}) {
|
|
|
1569
1692
|
: pythonRuntime.status === "repair"
|
|
1570
1693
|
? "Repairing the managed Python runtime"
|
|
1571
1694
|
: "Validating the managed Python runtime",
|
|
1572
|
-
() => ensurePythonRuntime(pythonRuntime, pythonRuntime.status, runLong
|
|
1695
|
+
() => ensurePythonRuntime(pythonRuntime, pythonRuntime.status, runLong),
|
|
1573
1696
|
);
|
|
1574
1697
|
await progress.run("Installing reviewed Python dependencies", () => runLong(pipInvocation));
|
|
1575
1698
|
await progress.run(
|