Preambuła
To bardzo stare pytanie, ale wydaje się, że wciąż uderza w nerwy w 2020 r., Jak w 2012 r. Sprawdziłem wszystkie pozostałe odpowiedzi i nie mogłem znaleźć techniki (zauważ, że ma to swoje ograniczenia, ale wszystkie inne nie są dotyczy również każdej sytuacji).
Proces potomny GIT +
Jeśli używasz GIT jako systemu kontroli wersji, problem z określeniem katalogu głównego projektu można zredukować do (który uważam za właściwy katalog główny projektu - w końcu chciałbyś, aby Twój VCS miał możliwie największy zakres widoczności) :
wycofana ścieżka katalogu głównego repozytorium
Ponieważ w tym celu musisz uruchomić komendę CLI, będziemy musieli odrodzić proces potomny. Dodatkowo, ponieważ istnieje duże prawdopodobieństwo, że katalog główny projektu nie zmieni się w czasie wykonywania, możemy użyć synchronicznej wersji child_processinterfejsów API modułów podczas uruchamiania.
Okazało spawnSync()się, że jestem najbardziej odpowiedni do tej pracy. Jeśli chodzi o faktyczne polecenie do uruchomienia, git worktree(z --porcelainopcją łatwego parsowania) wszystko, czego potrzebujemy, aby pobrać bezwzględną ścieżkę root.
W przykładzie zdecydowałem się zwrócić tablicę ścieżek, ponieważ może istnieć więcej niż jedno drzewo robocze (chociaż prawdopodobnie mają one wspólne ścieżki), aby się upewnić. Zauważ, że ponieważ używamy polecenia CLI, shellopcja powinna być ustawiona natrue (bezpieczeństwo nie powinno być problemem, ponieważ nie ma niezaufanych danych wejściowych).
Porównanie podejść i awarie
Rozumiejąc, że sytuacja, w której VCS może być niedostępny, przedstawiłem kilka awarii po przeanalizowaniu dokumentów i innych odpowiedzi. Podsumowując, proponowane rozwiązania sprowadzają się do (z wyjątkiem modułów innych producentów i specyficznych dla pakietu):
| Rozwiązanie | Zaleta | Główny problem |
| ------------------------ | ----------------------- | -------------------------------- |
| `__nazwa_pliku` | wskazuje na plik modułu | względem modułu |
| `__dirname` | wskazuje na moduł reż | taki sam jak `__nazwa_pliku` |
| spacer po drzewie | node_modules` prawie gwarantowany root | złożone chodzenie po drzewie, jeśli jest zagnieżdżone
| `path.resolve (". ")` | root, jeśli CWD to root | to samo co `process.cwd ()` |
| `process.argv [1]` | taki sam jak `__nazwa_pliku` | taki sam jak `__nazwa_pliku` |
| `process.env.INIT_CWD` | wskazuje na „npm run” reż | wymaga uruchomienia `npm` && CLI |
| `process.env.PWD` | wskazuje na bieżący katalog | w odniesieniu do (jest) uruchomienie katalog |
| `process.cwd ()` | to samo co `env.PWD` | `process.chdir (ścieżka)` w czasie wykonywania |
| `wymagają.main.nazwa_pliku` | root, jeśli `=== moduł` | kończy się niepowodzeniem na modułach „need`d”
Z powyższej tabeli porównawczej najbardziej uniwersalne są dwa podejścia:
require.main.filenamejako prosty sposób na rootowanie, jeśli require.main === modulezostanie spełniony
node_modulesproponowany ostatnio spacer po drzewie opiera się na innym założeniu:
jeśli katalog modułu ma katalog node_moduleswewnątrz, prawdopodobnie jest to katalog główny
W przypadku głównej aplikacji pobierze katalog główny aplikacji, a dla modułu - katalog główny projektu.
Fallback 1. Spacer po drzewie
Moja implementacja używa bardziej swobodnego podejścia, zatrzymując się po znalezieniu katalogu docelowego, ponieważ dla danego modułu jego katalogiem głównym jest katalog główny projektu. Można połączyć połączenia lub rozszerzyć je, aby skonfigurować głębokość wyszukiwania:
/**
* @summary gets root by walking up node_modules
* @param {import("fs")} fs
* @param {import("path")} pt
*/
const getRootFromNodeModules = (fs, pt) =>
/**
* @param {string} [startPath]
* @returns {string[]}
*/
(startPath = __dirname) => {
//avoid loop if reached root path
if (startPath === pt.parse(startPath).root) {
return [startPath];
}
const isRoot = fs.existsSync(pt.join(startPath, "node_modules"));
if (isRoot) {
return [startPath];
}
return getRootFromNodeModules(fs, pt)(pt.dirname(startPath));
};
Fallback 2. Moduł główny
Druga implementacja jest banalna
/**
* @summary gets app entry point if run directly
* @param {import("path")} pt
*/
const getAppEntryPoint = (pt) =>
/**
* @returns {string[]}
*/
() => {
const { main } = require;
const { filename } = main;
return main === module ?
[pt.parse(filename).dir] :
[];
};
Realizacja
Sugerowałbym użycie chodzika jako rezerwowego, ponieważ jest on bardziej wszechstronny:
const { spawnSync } = require("child_process");
const pt = require('path');
const fs = require("fs");
/**
* @summary returns worktree root path(s)
* @param {function : string[] } [fallback]
* @returns {string[]}
*/
const getProjectRoot = (fallback) => {
const { error, stdout } = spawnSync(
`git worktree list --porcelain`,
{
encoding: "utf8",
shell: true
}
);
if (!stdout) {
console.warn(`Could not use GIT to find root:\n\n${error}`);
return fallback ? fallback() : [];
}
return stdout
.split("\n")
.map(line => {
const [key, value] = line.split(/\s+/) || [];
return key === "worktree" ? value : "";
})
.filter(Boolean);
};
Niedogodności
Najbardziej oczywistym jest zainstalowanie i zainicjowanie GIT, co może być niepożądane / niewiarygodne (uwaga: zainstalowanie GIT na serwerach produkcyjnych nie jest rzadkością ani nie jest niebezpieczne ). Może być zapośredniczony przez awarie, jak opisano powyżej.
Notatki
- Kilka pomysłów na dalsze rozszerzenie podejścia 1:
- wprowadź config jako parametr funkcji
export funkcja, aby uczynić go modułem
- sprawdź, czy GIT jest zainstalowany i / lub zainicjowany
Bibliografia
git worktree odniesienie
spawnSync odniesienie
require.main odniesienie
path.dirname() odniesienie