@nuee/cli 0.4.1 → 0.5.0
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/dist/add.d.ts +5 -5
- package/dist/add.js +69 -67
- package/dist/arguments.d.ts +13 -3
- package/dist/arguments.js +17 -18
- package/dist/config.d.ts +4 -0
- package/dist/config.js +57 -31
- package/dist/dependencies.d.ts +1 -0
- package/dist/dependencies.js +25 -4
- package/dist/doctor.js +108 -93
- package/dist/index.js +25 -25
- package/dist/init.d.ts +21 -0
- package/dist/init.js +88 -81
- package/dist/new-component.js +28 -14
- package/dist/registry.js +36 -68
- package/dist/source.d.ts +14 -0
- package/dist/source.js +162 -0
- package/package.json +6 -3
package/dist/add.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
export
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
export { getMissingDependencies } from "./dependencies.js";
|
|
2
|
+
import type { CliOptions } from "./arguments.js";
|
|
3
|
+
export type AddOptions = CliOptions & {
|
|
4
|
+
/** @deprecated Use skip-dependencies. */
|
|
4
5
|
skipDependencyInstall?: boolean;
|
|
5
|
-
|
|
6
|
-
} & Record<string, boolean | string | undefined>;
|
|
6
|
+
};
|
|
7
7
|
export declare function add(projectDirectory: string, componentNames: string | readonly string[], options?: AddOptions): Promise<void>;
|
package/dist/add.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
|
-
import { dirname, isAbsolute, relative, resolve } from "node:path";
|
|
2
|
+
import { basename, dirname, isAbsolute, relative, resolve } from "node:path";
|
|
3
3
|
import { createInterface } from "node:readline/promises";
|
|
4
|
-
import { defaultConfig, hasConfig, readConfig, resolveConfigAlias } from "./config.js";
|
|
5
|
-
|
|
6
|
-
import {
|
|
4
|
+
import { defaultConfig, getDefaultAliases, hasConfig, readConfig, resolveConfigAlias, } from "./config.js";
|
|
5
|
+
export { getMissingDependencies } from "./dependencies.js";
|
|
6
|
+
import { getMissingDependencies, installDependencies } from "./dependencies.js";
|
|
7
|
+
import { applyInitialization, prepareInitialization } from "./init.js";
|
|
7
8
|
import { resolveComponent } from "./registry.js";
|
|
9
|
+
import { parseSource, removeReducedMotionStyles } from "./source.js";
|
|
8
10
|
function isNotFoundError(error) {
|
|
9
11
|
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
10
12
|
}
|
|
@@ -23,13 +25,11 @@ async function askYesNo(question, defaultValue) {
|
|
|
23
25
|
readline.close();
|
|
24
26
|
}
|
|
25
27
|
}
|
|
26
|
-
async function writeSource(source, targetPath
|
|
28
|
+
async function writeSource(source, targetPath) {
|
|
27
29
|
try {
|
|
28
30
|
const currentSource = await readFile(targetPath, "utf8");
|
|
29
31
|
if (currentSource === source)
|
|
30
|
-
return
|
|
31
|
-
if (!(await confirmOverwrite()))
|
|
32
|
-
throw new Error("Component installation canceled.");
|
|
32
|
+
return;
|
|
33
33
|
}
|
|
34
34
|
catch (error) {
|
|
35
35
|
if (!isNotFoundError(error))
|
|
@@ -37,7 +37,6 @@ async function writeSource(source, targetPath, confirmOverwrite) {
|
|
|
37
37
|
}
|
|
38
38
|
await mkdir(dirname(targetPath), { recursive: true });
|
|
39
39
|
await writeFile(targetPath, source, "utf8");
|
|
40
|
-
return "written";
|
|
41
40
|
}
|
|
42
41
|
function resolveTargetPath(uiDirectory, filePath) {
|
|
43
42
|
if (isAbsolute(filePath))
|
|
@@ -52,35 +51,6 @@ function resolveTargetPath(uiDirectory, filePath) {
|
|
|
52
51
|
function replaceTokenImport(source, stylesAlias) {
|
|
53
52
|
return source.replaceAll("@nuee/tokens/semantic.stylex", `${stylesAlias}/semantic.stylex`);
|
|
54
53
|
}
|
|
55
|
-
function removeReducedMotionStyles(source) {
|
|
56
|
-
const mediaQuery = '"@media (prefers-reduced-motion: reduce)":';
|
|
57
|
-
let transformedSource = source;
|
|
58
|
-
let mediaQueryIndex = transformedSource.indexOf(mediaQuery);
|
|
59
|
-
while (mediaQueryIndex !== -1) {
|
|
60
|
-
const propertyStart = transformedSource.lastIndexOf("\n", mediaQueryIndex) + 1;
|
|
61
|
-
const openingBraceIndex = transformedSource.indexOf("{", mediaQueryIndex + mediaQuery.length);
|
|
62
|
-
let depth = 0;
|
|
63
|
-
let propertyEnd = openingBraceIndex;
|
|
64
|
-
for (let index = openingBraceIndex; index < transformedSource.length; index += 1) {
|
|
65
|
-
if (transformedSource[index] === "{")
|
|
66
|
-
depth += 1;
|
|
67
|
-
if (transformedSource[index] === "}")
|
|
68
|
-
depth -= 1;
|
|
69
|
-
if (depth !== 0)
|
|
70
|
-
continue;
|
|
71
|
-
propertyEnd = index + 1;
|
|
72
|
-
if (transformedSource[propertyEnd] === ",")
|
|
73
|
-
propertyEnd += 1;
|
|
74
|
-
if (transformedSource[propertyEnd] === "\n")
|
|
75
|
-
propertyEnd += 1;
|
|
76
|
-
break;
|
|
77
|
-
}
|
|
78
|
-
transformedSource =
|
|
79
|
-
transformedSource.slice(0, propertyStart) + transformedSource.slice(propertyEnd);
|
|
80
|
-
mediaQueryIndex = transformedSource.indexOf(mediaQuery);
|
|
81
|
-
}
|
|
82
|
-
return transformedSource;
|
|
83
|
-
}
|
|
84
54
|
export async function add(projectDirectory, componentNames, options = {}) {
|
|
85
55
|
const componentNameList = [
|
|
86
56
|
...new Set(typeof componentNames === "string" ? [componentNames] : componentNames),
|
|
@@ -89,43 +59,75 @@ export async function add(projectDirectory, componentNames, options = {}) {
|
|
|
89
59
|
throw new Error("Enter at least one component name to add.");
|
|
90
60
|
}
|
|
91
61
|
const shouldInitialize = !(await hasConfig(projectDirectory));
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
62
|
+
let config;
|
|
63
|
+
if (shouldInitialize) {
|
|
64
|
+
const defaultAliases = await getDefaultAliases(projectDirectory);
|
|
65
|
+
config = {
|
|
66
|
+
...defaultConfig,
|
|
67
|
+
aliases: {
|
|
68
|
+
ui: options.ui ?? defaultAliases.ui,
|
|
69
|
+
styles: options.styles ?? options.tokens ?? defaultAliases.styles,
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
config = await readConfig(projectDirectory);
|
|
97
75
|
}
|
|
98
|
-
const config = options["dry-run"] && shouldInitialize ? defaultConfig : await readConfig(projectDirectory);
|
|
99
76
|
const resolvedList = await Promise.all(componentNameList.map(resolveComponent));
|
|
77
|
+
await resolveConfigAlias(projectDirectory, config.aliases.styles, "aliases.styles");
|
|
100
78
|
const uiDirectory = await resolveConfigAlias(projectDirectory, config.aliases.ui, "aliases.ui");
|
|
101
|
-
|
|
102
|
-
async function confirmOverwrite() {
|
|
103
|
-
if (isOverwriteConfirmed)
|
|
104
|
-
return true;
|
|
105
|
-
isOverwriteConfirmed = await askYesNo("Files with the same names already exist. Overwrite all?", false);
|
|
106
|
-
return isOverwriteConfirmed;
|
|
107
|
-
}
|
|
79
|
+
const sources = [];
|
|
108
80
|
for (const resolved of resolvedList) {
|
|
109
81
|
for (const file of resolved.files) {
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
82
|
+
const isScript = /\.[cm]?[jt]sx?$/.test(file.path);
|
|
83
|
+
let source = replaceTokenImport(file.content, config.aliases.styles);
|
|
84
|
+
if (isScript && !config.accessibility.respectReducedMotion) {
|
|
85
|
+
source = removeReducedMotionStyles(source);
|
|
86
|
+
}
|
|
87
|
+
if (isScript)
|
|
88
|
+
parseSource(source);
|
|
89
|
+
sources.push({ source, targetPath: resolveTargetPath(uiDirectory, file.path) });
|
|
116
90
|
}
|
|
117
91
|
}
|
|
118
|
-
const
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
92
|
+
const externalDependencies = await getMissingDependencies(projectDirectory, [
|
|
93
|
+
...new Set(resolvedList.flatMap((resolved) => resolved.externalDependencies)),
|
|
94
|
+
]);
|
|
95
|
+
const skipDependencies = options.skipDependencyInstall || options["skip-dependencies"];
|
|
96
|
+
const initialization = shouldInitialize
|
|
97
|
+
? await prepareInitialization(projectDirectory, {
|
|
98
|
+
...options,
|
|
99
|
+
defaults: true,
|
|
100
|
+
ui: config.aliases.ui,
|
|
101
|
+
styles: config.aliases.styles,
|
|
102
|
+
"skip-dependencies": skipDependencies,
|
|
103
|
+
})
|
|
104
|
+
: undefined;
|
|
105
|
+
const overwriteFileNames = [];
|
|
106
|
+
for (const { source, targetPath } of sources) {
|
|
107
|
+
try {
|
|
108
|
+
if ((await readFile(targetPath, "utf8")) !== source)
|
|
109
|
+
overwriteFileNames.push(basename(targetPath));
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
if (!isNotFoundError(error))
|
|
113
|
+
throw error;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
if (overwriteFileNames.length > 0 && !options["dry-run"]) {
|
|
117
|
+
const fileLabel = overwriteFileNames.join(", ");
|
|
118
|
+
const shouldOverwrite = await askYesNo(`Overwrite ${overwriteFileNames.length} existing file${overwriteFileNames.length === 1 ? "" : "s"} (${fileLabel})?`, false);
|
|
119
|
+
if (!shouldOverwrite)
|
|
120
|
+
throw new Error("Component installation canceled.");
|
|
121
|
+
}
|
|
122
|
+
if (initialization && !options["dry-run"]) {
|
|
123
|
+
await applyInitialization(projectDirectory, initialization);
|
|
124
|
+
}
|
|
125
|
+
for (const { source, targetPath } of sources) {
|
|
126
|
+
if (options["dry-run"])
|
|
127
|
+
continue;
|
|
128
|
+
await writeSource(source, targetPath);
|
|
122
129
|
}
|
|
123
|
-
const
|
|
124
|
-
const shouldInstallDependencies = externalDependencies.length > 0 &&
|
|
125
|
-
!options.skipDependencyInstall &&
|
|
126
|
-
!options["skip-dependencies"] &&
|
|
127
|
-
!options["dry-run"] &&
|
|
128
|
-
(await askYesNo(`Install external dependencies (${externalDependencies.join(", ")})?`, true));
|
|
130
|
+
const shouldInstallDependencies = externalDependencies.length > 0 && !skipDependencies && !options["dry-run"];
|
|
129
131
|
if (shouldInstallDependencies) {
|
|
130
132
|
await installDependencies(projectDirectory, externalDependencies);
|
|
131
133
|
}
|
package/dist/arguments.d.ts
CHANGED
|
@@ -1,16 +1,26 @@
|
|
|
1
|
-
export type CliOptions =
|
|
1
|
+
export type CliOptions = {
|
|
2
2
|
cwd?: string;
|
|
3
3
|
defaults?: boolean;
|
|
4
|
-
framework?: string;
|
|
5
4
|
"dry-run"?: boolean;
|
|
6
5
|
force?: boolean;
|
|
7
6
|
"skip-dependencies"?: boolean;
|
|
8
7
|
styles?: string;
|
|
9
8
|
tokens?: string;
|
|
10
9
|
ui?: string;
|
|
10
|
+
vite?: boolean;
|
|
11
11
|
};
|
|
12
12
|
export declare function parseArguments(arguments_: readonly string[]): {
|
|
13
13
|
command: string;
|
|
14
|
-
options:
|
|
14
|
+
options: {
|
|
15
|
+
cwd?: string | undefined;
|
|
16
|
+
defaults?: boolean | undefined;
|
|
17
|
+
"dry-run"?: boolean | undefined;
|
|
18
|
+
force?: boolean | undefined;
|
|
19
|
+
"skip-dependencies"?: boolean | undefined;
|
|
20
|
+
styles?: string | undefined;
|
|
21
|
+
tokens?: string | undefined;
|
|
22
|
+
ui?: string | undefined;
|
|
23
|
+
vite?: boolean | undefined;
|
|
24
|
+
};
|
|
15
25
|
positionals: string[];
|
|
16
26
|
};
|
package/dist/arguments.js
CHANGED
|
@@ -1,21 +1,20 @@
|
|
|
1
|
+
import { parseArgs } from "node:util";
|
|
1
2
|
export function parseArguments(arguments_) {
|
|
2
|
-
const [command, ...
|
|
3
|
-
const options = {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
index += 1;
|
|
19
|
-
}
|
|
3
|
+
const [command, ...args] = arguments_;
|
|
4
|
+
const { values: options, positionals } = parseArgs({
|
|
5
|
+
args,
|
|
6
|
+
allowPositionals: true,
|
|
7
|
+
options: {
|
|
8
|
+
cwd: { type: "string" },
|
|
9
|
+
defaults: { type: "boolean" },
|
|
10
|
+
"dry-run": { type: "boolean" },
|
|
11
|
+
force: { type: "boolean" },
|
|
12
|
+
"skip-dependencies": { type: "boolean" },
|
|
13
|
+
styles: { type: "string" },
|
|
14
|
+
tokens: { type: "string" },
|
|
15
|
+
ui: { type: "string" },
|
|
16
|
+
vite: { type: "boolean" },
|
|
17
|
+
},
|
|
18
|
+
});
|
|
20
19
|
return { command, options, positionals };
|
|
21
20
|
}
|
package/dist/config.d.ts
CHANGED
|
@@ -14,4 +14,8 @@ export declare function validateConfig(config: unknown): NueeConfig;
|
|
|
14
14
|
export declare function readConfig(projectDirectory: string): Promise<NueeConfig>;
|
|
15
15
|
export declare function writeConfig(projectDirectory: string, config: NueeConfig): Promise<string>;
|
|
16
16
|
export declare function resolveConfigPath(projectDirectory: string, path: string, name: string): string;
|
|
17
|
+
export declare function getDefaultAliases(projectDirectory: string): Promise<{
|
|
18
|
+
ui: string;
|
|
19
|
+
styles: string;
|
|
20
|
+
}>;
|
|
17
21
|
export declare function resolveConfigAlias(projectDirectory: string, alias: string, name: string): Promise<string>;
|
package/dist/config.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { access, readFile, writeFile } from "node:fs/promises";
|
|
2
2
|
import { isAbsolute, join, relative, resolve } from "node:path";
|
|
3
|
+
import { createPathsMatcher, parseTsconfig } from "get-tsconfig";
|
|
3
4
|
import { parse } from "jsonc-parser";
|
|
4
5
|
export const configFileName = "nuee.json";
|
|
5
6
|
export const defaultConfig = {
|
|
@@ -11,12 +12,26 @@ export const defaultConfig = {
|
|
|
11
12
|
styles: "@/styles",
|
|
12
13
|
},
|
|
13
14
|
};
|
|
15
|
+
function getAliasPrefix(paths) {
|
|
16
|
+
if (!paths)
|
|
17
|
+
return undefined;
|
|
18
|
+
for (const [pattern, targetList] of Object.entries(paths)) {
|
|
19
|
+
if (!pattern.endsWith("*"))
|
|
20
|
+
continue;
|
|
21
|
+
const hasProjectRootTarget = targetList.some((target) => ["*", "./*", "src/*", "./src/*"].includes(target.replaceAll("\\", "/")));
|
|
22
|
+
if (hasProjectRootTarget)
|
|
23
|
+
return pattern.slice(0, -1);
|
|
24
|
+
}
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
14
27
|
export async function hasConfig(projectDirectory) {
|
|
15
28
|
try {
|
|
16
29
|
await access(resolve(projectDirectory, configFileName));
|
|
17
30
|
return true;
|
|
18
31
|
}
|
|
19
|
-
catch {
|
|
32
|
+
catch (error) {
|
|
33
|
+
if (!isNotFoundError(error))
|
|
34
|
+
throw error;
|
|
20
35
|
return false;
|
|
21
36
|
}
|
|
22
37
|
}
|
|
@@ -28,6 +43,9 @@ function ensureRelativePath(projectDirectory, path, name) {
|
|
|
28
43
|
}
|
|
29
44
|
}
|
|
30
45
|
export function validateConfig(config) {
|
|
46
|
+
if (typeof config !== "object" || config === null || Array.isArray(config)) {
|
|
47
|
+
throw new Error("Configure nuee.json as an object.");
|
|
48
|
+
}
|
|
31
49
|
const candidate = config;
|
|
32
50
|
if (candidate.paths || candidate.tokens) {
|
|
33
51
|
throw new Error("This nuee.json uses an older format. Run `nuee init --force` to create alias-based configuration.");
|
|
@@ -43,7 +61,8 @@ export function validateConfig(config) {
|
|
|
43
61
|
throw new Error("Configure aliases.styles.");
|
|
44
62
|
}
|
|
45
63
|
if (candidate.accessibility !== undefined &&
|
|
46
|
-
(
|
|
64
|
+
(candidate.accessibility === null ||
|
|
65
|
+
typeof candidate.accessibility !== "object" ||
|
|
47
66
|
typeof candidate.accessibility.respectReducedMotion !== "boolean")) {
|
|
48
67
|
throw new Error("Configure accessibility.respectReducedMotion as a boolean.");
|
|
49
68
|
}
|
|
@@ -88,44 +107,51 @@ export function resolveConfigPath(projectDirectory, path, name) {
|
|
|
88
107
|
}
|
|
89
108
|
async function readTsConfig(projectDirectory) {
|
|
90
109
|
for (const fileName of ["tsconfig.json", "jsconfig.json"]) {
|
|
110
|
+
const path = resolve(projectDirectory, fileName);
|
|
91
111
|
try {
|
|
92
|
-
|
|
112
|
+
const source = await readFile(path, "utf8");
|
|
113
|
+
const errors = [];
|
|
114
|
+
const value = parse(source, errors, { allowTrailingComma: true });
|
|
115
|
+
if (errors.length || !value || typeof value !== "object" || Array.isArray(value)) {
|
|
116
|
+
throw new Error(`Invalid configuration: ${fileName}`);
|
|
117
|
+
}
|
|
118
|
+
return { path, config: parseTsconfig(path) };
|
|
93
119
|
}
|
|
94
120
|
catch (error) {
|
|
95
|
-
if (!isNotFoundError(error))
|
|
96
|
-
|
|
97
|
-
}
|
|
121
|
+
if (!isNotFoundError(error))
|
|
122
|
+
throw error;
|
|
98
123
|
}
|
|
99
124
|
}
|
|
100
125
|
return undefined;
|
|
101
126
|
}
|
|
102
|
-
function
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
const suffix = pattern.slice(wildcardIndex + 1);
|
|
112
|
-
if (!alias.startsWith(prefix) || !alias.endsWith(suffix))
|
|
113
|
-
continue;
|
|
114
|
-
const wildcard = alias.slice(prefix.length, alias.length - suffix.length);
|
|
115
|
-
return targets[0]?.replace("*", wildcard);
|
|
116
|
-
}
|
|
117
|
-
return undefined;
|
|
127
|
+
export async function getDefaultAliases(projectDirectory) {
|
|
128
|
+
const tsConfig = await readTsConfig(projectDirectory);
|
|
129
|
+
const aliasPrefix = getAliasPrefix(tsConfig?.config.compilerOptions?.paths);
|
|
130
|
+
if (!aliasPrefix)
|
|
131
|
+
return defaultConfig.aliases;
|
|
132
|
+
return {
|
|
133
|
+
styles: `${aliasPrefix}styles`,
|
|
134
|
+
ui: `${aliasPrefix}components/ui`,
|
|
135
|
+
};
|
|
118
136
|
}
|
|
119
137
|
export async function resolveConfigAlias(projectDirectory, alias, name) {
|
|
120
|
-
if (alias.startsWith("@/")) {
|
|
121
|
-
return resolveConfigPath(projectDirectory, join("src", alias.slice(2)), name);
|
|
122
|
-
}
|
|
123
138
|
const tsConfig = await readTsConfig(projectDirectory);
|
|
124
|
-
const
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
139
|
+
const paths = tsConfig?.config.compilerOptions?.paths;
|
|
140
|
+
// Only explicit paths mappings override the conventional @/src fallback.
|
|
141
|
+
const hasMatchingPath = paths &&
|
|
142
|
+
Object.keys(paths).some((pattern) => {
|
|
143
|
+
const wildcard = pattern.indexOf("*");
|
|
144
|
+
if (wildcard === -1)
|
|
145
|
+
return pattern === alias;
|
|
146
|
+
return (alias.startsWith(pattern.slice(0, wildcard)) && alias.endsWith(pattern.slice(wildcard + 1)));
|
|
147
|
+
});
|
|
148
|
+
if (tsConfig && hasMatchingPath) {
|
|
149
|
+
const targets = createPathsMatcher(tsConfig)?.(alias);
|
|
150
|
+
if (targets?.[0])
|
|
151
|
+
return resolveConfigPath(projectDirectory, targets[0], name);
|
|
152
|
+
}
|
|
153
|
+
else if (alias.startsWith("@/")) {
|
|
154
|
+
return resolveConfigPath(projectDirectory, join("src", alias.slice(2)), name);
|
|
129
155
|
}
|
|
130
|
-
|
|
156
|
+
throw new Error(`Could not resolve ${name} (${alias}). Add it to tsconfig.json or jsconfig.json compilerOptions.paths.`);
|
|
131
157
|
}
|
package/dist/dependencies.d.ts
CHANGED
|
@@ -1 +1,2 @@
|
|
|
1
1
|
export declare function installDependencies(projectDirectory: string, dependencies: readonly string[], isDevelopmentDependency?: boolean): Promise<void>;
|
|
2
|
+
export declare function getMissingDependencies(projectDirectory: string, dependencies: readonly string[]): Promise<readonly string[]>;
|
package/dist/dependencies.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
import { resolve } from "node:path";
|
|
2
4
|
function detectPackageManager() {
|
|
3
5
|
const userAgent = process.env.npm_config_user_agent ?? "";
|
|
4
6
|
if (userAgent.startsWith("pnpm"))
|
|
@@ -13,10 +15,10 @@ export function installDependencies(projectDirectory, dependencies, isDevelopmen
|
|
|
13
15
|
if (dependencies.length === 0)
|
|
14
16
|
return Promise.resolve();
|
|
15
17
|
const packageManager = detectPackageManager();
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
const arguments_ = [packageManager === "npm" ? "install" : "add"];
|
|
19
|
+
if (isDevelopmentDependency)
|
|
20
|
+
arguments_.push("-D");
|
|
21
|
+
arguments_.push(...dependencies);
|
|
20
22
|
return new Promise((resolvePromise, reject) => {
|
|
21
23
|
const child = spawn(packageManager, arguments_, { cwd: projectDirectory, stdio: "ignore" });
|
|
22
24
|
child.on("error", reject);
|
|
@@ -28,3 +30,22 @@ export function installDependencies(projectDirectory, dependencies, isDevelopmen
|
|
|
28
30
|
});
|
|
29
31
|
});
|
|
30
32
|
}
|
|
33
|
+
function getPackageName(dependency) {
|
|
34
|
+
const versionStart = dependency.lastIndexOf("@");
|
|
35
|
+
return versionStart > 0 ? dependency.slice(0, versionStart) : dependency;
|
|
36
|
+
}
|
|
37
|
+
export async function getMissingDependencies(projectDirectory, dependencies) {
|
|
38
|
+
try {
|
|
39
|
+
const packageJson = JSON.parse(await readFile(resolve(projectDirectory, "package.json"), "utf8"));
|
|
40
|
+
const installedDependencies = new Set([
|
|
41
|
+
...Object.keys(packageJson.dependencies ?? {}),
|
|
42
|
+
...Object.keys(packageJson.devDependencies ?? {}),
|
|
43
|
+
]);
|
|
44
|
+
return dependencies.filter((dependency) => !installedDependencies.has(getPackageName(dependency)));
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
if (typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT")
|
|
48
|
+
return dependencies;
|
|
49
|
+
throw error;
|
|
50
|
+
}
|
|
51
|
+
}
|