@agiflowai/aicode-utils 1.0.8 → 1.0.10
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/index.cjs +266 -64
- package/dist/index.d.cts +129 -47
- package/dist/index.d.mts +129 -47
- package/dist/index.mjs +260 -48
- package/package.json +4 -3
package/dist/index.cjs
CHANGED
|
@@ -36,6 +36,7 @@ let pino = require("pino");
|
|
|
36
36
|
pino = __toESM(pino);
|
|
37
37
|
let js_yaml = require("js-yaml");
|
|
38
38
|
js_yaml = __toESM(js_yaml);
|
|
39
|
+
let execa = require("execa");
|
|
39
40
|
let chalk = require("chalk");
|
|
40
41
|
chalk = __toESM(chalk);
|
|
41
42
|
|
|
@@ -92,12 +93,6 @@ async function ensureDir(dirPath) {
|
|
|
92
93
|
await node_fs_promises.mkdir(dirPath, { recursive: true });
|
|
93
94
|
}
|
|
94
95
|
/**
|
|
95
|
-
* Ensure a directory exists (sync), creating it recursively if needed
|
|
96
|
-
*/
|
|
97
|
-
function ensureDirSync(dirPath) {
|
|
98
|
-
(0, node_fs.mkdirSync)(dirPath, { recursive: true });
|
|
99
|
-
}
|
|
100
|
-
/**
|
|
101
96
|
* Remove a file or directory recursively
|
|
102
97
|
*/
|
|
103
98
|
async function remove(filePath) {
|
|
@@ -140,19 +135,6 @@ async function writeJson(filePath, data, options) {
|
|
|
140
135
|
const content = JSON.stringify(data, null, options?.spaces ?? 2);
|
|
141
136
|
await node_fs_promises.writeFile(filePath, `${content}\n`, "utf-8");
|
|
142
137
|
}
|
|
143
|
-
/**
|
|
144
|
-
* Write an object as JSON to a file (sync)
|
|
145
|
-
*/
|
|
146
|
-
function writeJsonSync(filePath, data, options) {
|
|
147
|
-
(0, node_fs.writeFileSync)(filePath, `${JSON.stringify(data, null, options?.spaces ?? 2)}\n`, "utf-8");
|
|
148
|
-
}
|
|
149
|
-
/**
|
|
150
|
-
* Output file - writes content ensuring directory exists
|
|
151
|
-
*/
|
|
152
|
-
async function outputFile(filePath, content) {
|
|
153
|
-
await ensureDir(node_path.default.dirname(filePath));
|
|
154
|
-
await node_fs_promises.writeFile(filePath, content, "utf-8");
|
|
155
|
-
}
|
|
156
138
|
const readFile = node_fs_promises.readFile;
|
|
157
139
|
const writeFile = node_fs_promises.writeFile;
|
|
158
140
|
const readdir = node_fs_promises.readdir;
|
|
@@ -222,8 +204,7 @@ var TemplatesManagerService = class TemplatesManagerService {
|
|
|
222
204
|
* 4. Verify the templates directory exists
|
|
223
205
|
*
|
|
224
206
|
* @param startPath - The path to start searching from (defaults to process.cwd())
|
|
225
|
-
* @returns The absolute path to the templates directory
|
|
226
|
-
* @throws Error if templates directory is not found
|
|
207
|
+
* @returns The absolute path to the templates directory, or null if not found
|
|
227
208
|
*/
|
|
228
209
|
static async findTemplatesPath(startPath = process.cwd()) {
|
|
229
210
|
const workspaceRoot = await TemplatesManagerService.findWorkspaceRoot(startPath);
|
|
@@ -235,12 +216,12 @@ var TemplatesManagerService = class TemplatesManagerService {
|
|
|
235
216
|
if (config?.templatesPath) {
|
|
236
217
|
const templatesPath$1 = node_path.default.isAbsolute(config.templatesPath) ? config.templatesPath : node_path.default.join(workspaceRoot, config.templatesPath);
|
|
237
218
|
if (await pathExists(templatesPath$1)) return templatesPath$1;
|
|
238
|
-
else
|
|
219
|
+
else return null;
|
|
239
220
|
}
|
|
240
221
|
}
|
|
241
222
|
const templatesPath = node_path.default.join(workspaceRoot, TemplatesManagerService.TEMPLATES_FOLDER);
|
|
242
223
|
if (await pathExists(templatesPath)) return templatesPath;
|
|
243
|
-
|
|
224
|
+
return null;
|
|
244
225
|
}
|
|
245
226
|
/**
|
|
246
227
|
* Find the workspace root by searching upwards for .git folder
|
|
@@ -259,8 +240,7 @@ var TemplatesManagerService = class TemplatesManagerService {
|
|
|
259
240
|
* Use this when you need immediate access and are sure templates exist.
|
|
260
241
|
*
|
|
261
242
|
* @param startPath - The path to start searching from (defaults to process.cwd())
|
|
262
|
-
* @returns The absolute path to the templates directory
|
|
263
|
-
* @throws Error if templates directory is not found
|
|
243
|
+
* @returns The absolute path to the templates directory, or null if not found
|
|
264
244
|
*/
|
|
265
245
|
static findTemplatesPathSync(startPath = process.cwd()) {
|
|
266
246
|
const workspaceRoot = TemplatesManagerService.findWorkspaceRootSync(startPath);
|
|
@@ -272,12 +252,12 @@ var TemplatesManagerService = class TemplatesManagerService {
|
|
|
272
252
|
if (config?.templatesPath) {
|
|
273
253
|
const templatesPath$1 = node_path.default.isAbsolute(config.templatesPath) ? config.templatesPath : node_path.default.join(workspaceRoot, config.templatesPath);
|
|
274
254
|
if (pathExistsSync(templatesPath$1)) return templatesPath$1;
|
|
275
|
-
else
|
|
255
|
+
else return null;
|
|
276
256
|
}
|
|
277
257
|
}
|
|
278
258
|
const templatesPath = node_path.default.join(workspaceRoot, TemplatesManagerService.TEMPLATES_FOLDER);
|
|
279
259
|
if (pathExistsSync(templatesPath)) return templatesPath;
|
|
280
|
-
|
|
260
|
+
return null;
|
|
281
261
|
}
|
|
282
262
|
/**
|
|
283
263
|
* Find the workspace root synchronously by searching upwards for .git folder
|
|
@@ -782,6 +762,258 @@ function generateStableId(length = 8) {
|
|
|
782
762
|
return result;
|
|
783
763
|
}
|
|
784
764
|
|
|
765
|
+
//#endregion
|
|
766
|
+
//#region src/utils/git.ts
|
|
767
|
+
/**
|
|
768
|
+
* Git Utilities
|
|
769
|
+
*
|
|
770
|
+
* DESIGN PATTERNS:
|
|
771
|
+
* - Safe command execution: Use execa with array arguments to prevent shell injection
|
|
772
|
+
* - Defense in depth: Use '--' separator to prevent option injection attacks
|
|
773
|
+
*
|
|
774
|
+
* CODING STANDARDS:
|
|
775
|
+
* - All git commands must use execGit helper with array arguments
|
|
776
|
+
* - Use '--' separator before user-provided arguments (URLs, branches, paths)
|
|
777
|
+
* - Validate inputs where appropriate
|
|
778
|
+
*
|
|
779
|
+
* AVOID:
|
|
780
|
+
* - Shell string interpolation
|
|
781
|
+
* - Passing unsanitized user input as command options
|
|
782
|
+
*
|
|
783
|
+
* NOTE: These utilities perform I/O operations (git commands, file system) by necessity.
|
|
784
|
+
* Pure utility functions like parseGitHubUrl are side-effect free.
|
|
785
|
+
*/
|
|
786
|
+
/**
|
|
787
|
+
* Type guard to check if an error has the expected execa error shape
|
|
788
|
+
* @param error - The error to check
|
|
789
|
+
* @returns True if the error has message property (and optionally stderr)
|
|
790
|
+
* @example
|
|
791
|
+
* try {
|
|
792
|
+
* await execa('git', ['status']);
|
|
793
|
+
* } catch (error) {
|
|
794
|
+
* if (isExecaError(error)) {
|
|
795
|
+
* console.error(error.stderr || error.message);
|
|
796
|
+
* }
|
|
797
|
+
* }
|
|
798
|
+
*/
|
|
799
|
+
function isExecaError(error) {
|
|
800
|
+
if (typeof error !== "object" || error === null) return false;
|
|
801
|
+
if (!("message" in error)) return false;
|
|
802
|
+
return typeof error.message === "string";
|
|
803
|
+
}
|
|
804
|
+
/**
|
|
805
|
+
* Execute a git command safely using execa to prevent command injection
|
|
806
|
+
* @param args - Array of git command arguments
|
|
807
|
+
* @param cwd - Optional working directory for the command
|
|
808
|
+
* @throws Error when git command fails
|
|
809
|
+
*/
|
|
810
|
+
async function execGit(args, cwd) {
|
|
811
|
+
try {
|
|
812
|
+
await (0, execa.execa)("git", args, { cwd });
|
|
813
|
+
} catch (error) {
|
|
814
|
+
if (isExecaError(error)) throw new Error(`Git command failed: ${error.stderr || error.message}`);
|
|
815
|
+
throw error;
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
/**
|
|
819
|
+
* Execute git init safely using execa to prevent command injection
|
|
820
|
+
* Uses '--' to prevent projectPath from being interpreted as an option
|
|
821
|
+
* @param projectPath - Path where to initialize the git repository
|
|
822
|
+
* @throws Error when git init fails
|
|
823
|
+
*/
|
|
824
|
+
async function gitInit(projectPath) {
|
|
825
|
+
try {
|
|
826
|
+
await (0, execa.execa)("git", [
|
|
827
|
+
"init",
|
|
828
|
+
"--",
|
|
829
|
+
projectPath
|
|
830
|
+
]);
|
|
831
|
+
} catch (error) {
|
|
832
|
+
if (isExecaError(error)) throw new Error(`Git init failed: ${error.stderr || error.message}`);
|
|
833
|
+
throw error;
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
/**
|
|
837
|
+
* Find the workspace root by searching upwards for .git folder
|
|
838
|
+
* Returns null if no .git folder is found (indicating a new project setup is needed)
|
|
839
|
+
* @param startPath - The path to start searching from (default: current working directory)
|
|
840
|
+
* @returns The workspace root path or null if not in a git repository
|
|
841
|
+
* @example
|
|
842
|
+
* const root = await findWorkspaceRoot('/path/to/project/src');
|
|
843
|
+
* if (root) {
|
|
844
|
+
* console.log('Workspace root:', root);
|
|
845
|
+
* } else {
|
|
846
|
+
* console.log('No git repository found');
|
|
847
|
+
* }
|
|
848
|
+
*/
|
|
849
|
+
async function findWorkspaceRoot(startPath = process.cwd()) {
|
|
850
|
+
let currentPath = node_path.default.resolve(startPath);
|
|
851
|
+
const rootPath = node_path.default.parse(currentPath).root;
|
|
852
|
+
while (true) {
|
|
853
|
+
if (await pathExists(node_path.default.join(currentPath, ".git"))) return currentPath;
|
|
854
|
+
if (currentPath === rootPath) return null;
|
|
855
|
+
currentPath = node_path.default.dirname(currentPath);
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
/**
|
|
859
|
+
* Parse GitHub URL to detect if it's a subdirectory
|
|
860
|
+
* Supports formats:
|
|
861
|
+
* - https://github.com/user/repo
|
|
862
|
+
* - https://github.com/user/repo/tree/branch/path/to/dir
|
|
863
|
+
* - https://github.com/user/repo/tree/main/path/to/dir
|
|
864
|
+
* @param url - The GitHub URL to parse
|
|
865
|
+
* @returns Parsed URL components including owner, repo, branch, and subdirectory
|
|
866
|
+
* @example
|
|
867
|
+
* const result = parseGitHubUrl('https://github.com/user/repo/tree/main/src');
|
|
868
|
+
* // result.owner === 'user'
|
|
869
|
+
* // result.repo === 'repo'
|
|
870
|
+
* // result.branch === 'main'
|
|
871
|
+
* // result.subdirectory === 'src'
|
|
872
|
+
* // result.isSubdirectory === true
|
|
873
|
+
*/
|
|
874
|
+
function parseGitHubUrl(url) {
|
|
875
|
+
const treeMatch = url.match(/^https?:\/\/github\.com\/([^/]+)\/([^/]+)\/tree\/([^/]+)\/(.+)$/);
|
|
876
|
+
const blobMatch = url.match(/^https?:\/\/github\.com\/([^/]+)\/([^/]+)\/blob\/([^/]+)\/(.+)$/);
|
|
877
|
+
const rootMatch = url.match(/^https?:\/\/github\.com\/([^/]+)\/([^/]+?)(?:\.git)?$/);
|
|
878
|
+
if (treeMatch || blobMatch) {
|
|
879
|
+
const match = treeMatch || blobMatch;
|
|
880
|
+
return {
|
|
881
|
+
owner: match?.[1],
|
|
882
|
+
repo: match?.[2],
|
|
883
|
+
repoUrl: `https://github.com/${match?.[1]}/${match?.[2]}.git`,
|
|
884
|
+
branch: match?.[3],
|
|
885
|
+
subdirectory: match?.[4],
|
|
886
|
+
isSubdirectory: true
|
|
887
|
+
};
|
|
888
|
+
}
|
|
889
|
+
if (rootMatch) return {
|
|
890
|
+
owner: rootMatch[1],
|
|
891
|
+
repo: rootMatch[2],
|
|
892
|
+
repoUrl: `https://github.com/${rootMatch[1]}/${rootMatch[2]}.git`,
|
|
893
|
+
isSubdirectory: false
|
|
894
|
+
};
|
|
895
|
+
return {
|
|
896
|
+
repoUrl: url,
|
|
897
|
+
isSubdirectory: false
|
|
898
|
+
};
|
|
899
|
+
}
|
|
900
|
+
/**
|
|
901
|
+
* Clone a subdirectory from a git repository using sparse checkout
|
|
902
|
+
* Uses '--' to mark end of options - prevents malicious URLs like '--upload-pack=evil'
|
|
903
|
+
* from being interpreted as git options
|
|
904
|
+
* @param repoUrl - The git repository URL
|
|
905
|
+
* @param branch - The branch to clone from
|
|
906
|
+
* @param subdirectory - The subdirectory path within the repository
|
|
907
|
+
* @param targetFolder - The local folder to clone into
|
|
908
|
+
* @throws Error if subdirectory not found or target folder already exists
|
|
909
|
+
* @example
|
|
910
|
+
* await cloneSubdirectory(
|
|
911
|
+
* 'https://github.com/user/repo.git',
|
|
912
|
+
* 'main',
|
|
913
|
+
* 'packages/core',
|
|
914
|
+
* './my-project'
|
|
915
|
+
* );
|
|
916
|
+
*/
|
|
917
|
+
async function cloneSubdirectory(repoUrl, branch, subdirectory, targetFolder) {
|
|
918
|
+
const tempFolder = `${targetFolder}.tmp`;
|
|
919
|
+
try {
|
|
920
|
+
await execGit([
|
|
921
|
+
"init",
|
|
922
|
+
"--",
|
|
923
|
+
tempFolder
|
|
924
|
+
]);
|
|
925
|
+
await execGit([
|
|
926
|
+
"remote",
|
|
927
|
+
"add",
|
|
928
|
+
"origin",
|
|
929
|
+
"--",
|
|
930
|
+
repoUrl
|
|
931
|
+
], tempFolder);
|
|
932
|
+
await execGit([
|
|
933
|
+
"config",
|
|
934
|
+
"core.sparseCheckout",
|
|
935
|
+
"true"
|
|
936
|
+
], tempFolder);
|
|
937
|
+
await writeFile(node_path.default.join(tempFolder, ".git", "info", "sparse-checkout"), `${subdirectory}\n`);
|
|
938
|
+
await execGit([
|
|
939
|
+
"pull",
|
|
940
|
+
"--depth=1",
|
|
941
|
+
"origin",
|
|
942
|
+
"--",
|
|
943
|
+
branch
|
|
944
|
+
], tempFolder);
|
|
945
|
+
const sourceDir = node_path.default.join(tempFolder, subdirectory);
|
|
946
|
+
if (!await pathExists(sourceDir)) throw new Error(`Subdirectory '${subdirectory}' not found in repository at branch '${branch}'`);
|
|
947
|
+
if (await pathExists(targetFolder)) throw new Error(`Target folder already exists: ${targetFolder}`);
|
|
948
|
+
await move(sourceDir, targetFolder);
|
|
949
|
+
await remove(tempFolder);
|
|
950
|
+
} catch (error) {
|
|
951
|
+
if (await pathExists(tempFolder)) await remove(tempFolder);
|
|
952
|
+
throw error;
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
/**
|
|
956
|
+
* Clone entire repository
|
|
957
|
+
* Uses '--' to mark end of options - prevents malicious URLs like '--upload-pack=evil'
|
|
958
|
+
* from being interpreted as git options
|
|
959
|
+
* @param repoUrl - The git repository URL to clone
|
|
960
|
+
* @param targetFolder - The local folder path to clone into
|
|
961
|
+
* @throws Error if git clone fails
|
|
962
|
+
* @example
|
|
963
|
+
* await cloneRepository('https://github.com/user/repo.git', './my-project');
|
|
964
|
+
*/
|
|
965
|
+
async function cloneRepository(repoUrl, targetFolder) {
|
|
966
|
+
await execGit([
|
|
967
|
+
"clone",
|
|
968
|
+
"--",
|
|
969
|
+
repoUrl,
|
|
970
|
+
targetFolder
|
|
971
|
+
]);
|
|
972
|
+
const gitFolder = node_path.default.join(targetFolder, ".git");
|
|
973
|
+
if (await pathExists(gitFolder)) await remove(gitFolder);
|
|
974
|
+
}
|
|
975
|
+
/**
|
|
976
|
+
* Type guard to validate GitHub API content item structure
|
|
977
|
+
* @param item - The item to validate
|
|
978
|
+
* @returns True if the item has required GitHubContentItem properties
|
|
979
|
+
*/
|
|
980
|
+
function isGitHubContentItem(item) {
|
|
981
|
+
if (typeof item !== "object" || item === null) return false;
|
|
982
|
+
const obj = item;
|
|
983
|
+
return typeof obj.name === "string" && typeof obj.type === "string" && typeof obj.path === "string";
|
|
984
|
+
}
|
|
985
|
+
/**
|
|
986
|
+
* Fetch directory listing from GitHub API
|
|
987
|
+
* @param owner - The GitHub repository owner/organization
|
|
988
|
+
* @param repo - The repository name
|
|
989
|
+
* @param dirPath - The directory path within the repository
|
|
990
|
+
* @param branch - The branch to fetch from (default: 'main')
|
|
991
|
+
* @returns Array of directory entries with name, type, and path
|
|
992
|
+
* @throws Error if the API request fails or returns non-directory content
|
|
993
|
+
* @remarks
|
|
994
|
+
* - Requires network access to GitHub API
|
|
995
|
+
* - Subject to GitHub API rate limits (60 requests/hour unauthenticated)
|
|
996
|
+
* - Only works with public repositories without authentication
|
|
997
|
+
* @example
|
|
998
|
+
* const contents = await fetchGitHubDirectoryContents('facebook', 'react', 'packages', 'main');
|
|
999
|
+
* // contents: [{ name: 'react', type: 'dir', path: 'packages/react' }, ...]
|
|
1000
|
+
*/
|
|
1001
|
+
async function fetchGitHubDirectoryContents(owner, repo, dirPath, branch = "main") {
|
|
1002
|
+
const url = `https://api.github.com/repos/${owner}/${repo}/contents/${dirPath}?ref=${branch}`;
|
|
1003
|
+
const response = await fetch(url, { headers: {
|
|
1004
|
+
Accept: "application/vnd.github.v3+json",
|
|
1005
|
+
"User-Agent": "scaffold-mcp"
|
|
1006
|
+
} });
|
|
1007
|
+
if (!response.ok) throw new Error(`Failed to fetch directory contents: ${response.statusText}`);
|
|
1008
|
+
const data = await response.json();
|
|
1009
|
+
if (!Array.isArray(data)) throw new Error("Expected directory but got file");
|
|
1010
|
+
return data.filter(isGitHubContentItem).map((item) => ({
|
|
1011
|
+
name: item.name,
|
|
1012
|
+
type: item.type,
|
|
1013
|
+
path: item.path
|
|
1014
|
+
}));
|
|
1015
|
+
}
|
|
1016
|
+
|
|
785
1017
|
//#endregion
|
|
786
1018
|
//#region src/utils/print.ts
|
|
787
1019
|
/**
|
|
@@ -989,26 +1221,6 @@ async function detectProjectType(workspaceRoot) {
|
|
|
989
1221
|
indicators
|
|
990
1222
|
};
|
|
991
1223
|
}
|
|
992
|
-
/**
|
|
993
|
-
* Check if a workspace is a monorepo
|
|
994
|
-
* Convenience function that returns a boolean
|
|
995
|
-
*
|
|
996
|
-
* @param workspaceRoot - Absolute path to the workspace root directory
|
|
997
|
-
* @returns True if workspace is detected as monorepo, false otherwise
|
|
998
|
-
*/
|
|
999
|
-
async function isMonorepo(workspaceRoot) {
|
|
1000
|
-
return (await detectProjectType(workspaceRoot)).projectType === ProjectType.MONOREPO;
|
|
1001
|
-
}
|
|
1002
|
-
/**
|
|
1003
|
-
* Check if a workspace is a monolith
|
|
1004
|
-
* Convenience function that returns a boolean
|
|
1005
|
-
*
|
|
1006
|
-
* @param workspaceRoot - Absolute path to the workspace root directory
|
|
1007
|
-
* @returns True if workspace is detected as monolith, false otherwise
|
|
1008
|
-
*/
|
|
1009
|
-
async function isMonolith(workspaceRoot) {
|
|
1010
|
-
return (await detectProjectType(workspaceRoot)).projectType === ProjectType.MONOLITH;
|
|
1011
|
-
}
|
|
1012
1224
|
|
|
1013
1225
|
//#endregion
|
|
1014
1226
|
exports.ConfigSource = ConfigSource;
|
|
@@ -1018,28 +1230,23 @@ exports.ProjectType = ProjectType;
|
|
|
1018
1230
|
exports.ScaffoldProcessingService = ScaffoldProcessingService;
|
|
1019
1231
|
exports.TemplatesManagerService = TemplatesManagerService;
|
|
1020
1232
|
exports.accessSync = node_fs.accessSync;
|
|
1233
|
+
exports.cloneRepository = cloneRepository;
|
|
1234
|
+
exports.cloneSubdirectory = cloneSubdirectory;
|
|
1021
1235
|
exports.copy = copy;
|
|
1022
|
-
exports.cp = cp;
|
|
1023
1236
|
exports.detectProjectType = detectProjectType;
|
|
1024
1237
|
exports.ensureDir = ensureDir;
|
|
1025
|
-
exports.
|
|
1026
|
-
|
|
1027
|
-
enumerable: true,
|
|
1028
|
-
get: function () {
|
|
1029
|
-
return node_fs_promises;
|
|
1030
|
-
}
|
|
1031
|
-
});
|
|
1238
|
+
exports.fetchGitHubDirectoryContents = fetchGitHubDirectoryContents;
|
|
1239
|
+
exports.findWorkspaceRoot = findWorkspaceRoot;
|
|
1032
1240
|
exports.generateStableId = generateStableId;
|
|
1241
|
+
exports.gitInit = gitInit;
|
|
1033
1242
|
exports.icons = icons;
|
|
1034
|
-
exports.isMonolith = isMonolith;
|
|
1035
|
-
exports.isMonorepo = isMonorepo;
|
|
1036
1243
|
exports.log = log;
|
|
1037
1244
|
exports.logger = logger;
|
|
1038
1245
|
exports.messages = messages;
|
|
1039
1246
|
exports.mkdir = mkdir;
|
|
1040
1247
|
exports.mkdirSync = node_fs.mkdirSync;
|
|
1041
1248
|
exports.move = move;
|
|
1042
|
-
exports.
|
|
1249
|
+
exports.parseGitHubUrl = parseGitHubUrl;
|
|
1043
1250
|
exports.pathExists = pathExists;
|
|
1044
1251
|
exports.pathExistsSync = pathExistsSync;
|
|
1045
1252
|
exports.print = print;
|
|
@@ -1049,13 +1256,8 @@ exports.readJson = readJson;
|
|
|
1049
1256
|
exports.readJsonSync = readJsonSync;
|
|
1050
1257
|
exports.readdir = readdir;
|
|
1051
1258
|
exports.remove = remove;
|
|
1052
|
-
exports.rename = rename;
|
|
1053
|
-
exports.rm = rm;
|
|
1054
1259
|
exports.sections = sections;
|
|
1055
1260
|
exports.stat = stat;
|
|
1056
1261
|
exports.statSync = node_fs.statSync;
|
|
1057
|
-
exports.unlink = unlink;
|
|
1058
1262
|
exports.writeFile = writeFile;
|
|
1059
|
-
exports.writeFileSync = node_fs.writeFileSync;
|
|
1060
|
-
exports.writeJson = writeJson;
|
|
1061
|
-
exports.writeJsonSync = writeJsonSync;
|
|
1263
|
+
exports.writeFileSync = node_fs.writeFileSync;
|
package/dist/index.d.cts
CHANGED
|
@@ -267,10 +267,9 @@ declare class TemplatesManagerService {
|
|
|
267
267
|
* 4. Verify the templates directory exists
|
|
268
268
|
*
|
|
269
269
|
* @param startPath - The path to start searching from (defaults to process.cwd())
|
|
270
|
-
* @returns The absolute path to the templates directory
|
|
271
|
-
* @throws Error if templates directory is not found
|
|
270
|
+
* @returns The absolute path to the templates directory, or null if not found
|
|
272
271
|
*/
|
|
273
|
-
static findTemplatesPath(startPath?: string): Promise<string>;
|
|
272
|
+
static findTemplatesPath(startPath?: string): Promise<string | null>;
|
|
274
273
|
/**
|
|
275
274
|
* Find the workspace root by searching upwards for .git folder
|
|
276
275
|
*/
|
|
@@ -280,10 +279,9 @@ declare class TemplatesManagerService {
|
|
|
280
279
|
* Use this when you need immediate access and are sure templates exist.
|
|
281
280
|
*
|
|
282
281
|
* @param startPath - The path to start searching from (defaults to process.cwd())
|
|
283
|
-
* @returns The absolute path to the templates directory
|
|
284
|
-
* @throws Error if templates directory is not found
|
|
282
|
+
* @returns The absolute path to the templates directory, or null if not found
|
|
285
283
|
*/
|
|
286
|
-
static findTemplatesPathSync(startPath?: string): string;
|
|
284
|
+
static findTemplatesPathSync(startPath?: string): string | null;
|
|
287
285
|
/**
|
|
288
286
|
* Find the workspace root synchronously by searching upwards for .git folder
|
|
289
287
|
*/
|
|
@@ -353,10 +351,6 @@ declare function pathExistsSync(filePath: string): boolean;
|
|
|
353
351
|
* Ensure a directory exists, creating it recursively if needed
|
|
354
352
|
*/
|
|
355
353
|
declare function ensureDir(dirPath: string): Promise<void>;
|
|
356
|
-
/**
|
|
357
|
-
* Ensure a directory exists (sync), creating it recursively if needed
|
|
358
|
-
*/
|
|
359
|
-
declare function ensureDirSync(dirPath: string): void;
|
|
360
354
|
/**
|
|
361
355
|
* Remove a file or directory recursively
|
|
362
356
|
*/
|
|
@@ -377,31 +371,11 @@ declare function readJson<T = unknown>(filePath: string): Promise<T>;
|
|
|
377
371
|
* Read and parse a JSON file (sync)
|
|
378
372
|
*/
|
|
379
373
|
declare function readJsonSync<T = unknown>(filePath: string): T;
|
|
380
|
-
/**
|
|
381
|
-
* Write an object as JSON to a file
|
|
382
|
-
*/
|
|
383
|
-
declare function writeJson(filePath: string, data: unknown, options?: {
|
|
384
|
-
spaces?: number;
|
|
385
|
-
}): Promise<void>;
|
|
386
|
-
/**
|
|
387
|
-
* Write an object as JSON to a file (sync)
|
|
388
|
-
*/
|
|
389
|
-
declare function writeJsonSync(filePath: string, data: unknown, options?: {
|
|
390
|
-
spaces?: number;
|
|
391
|
-
}): void;
|
|
392
|
-
/**
|
|
393
|
-
* Output file - writes content ensuring directory exists
|
|
394
|
-
*/
|
|
395
|
-
declare function outputFile(filePath: string, content: string): Promise<void>;
|
|
396
374
|
declare const readFile: typeof fs.readFile;
|
|
397
375
|
declare const writeFile: typeof fs.writeFile;
|
|
398
376
|
declare const readdir: typeof fs.readdir;
|
|
399
377
|
declare const mkdir: typeof fs.mkdir;
|
|
400
378
|
declare const stat: typeof fs.stat;
|
|
401
|
-
declare const unlink: typeof fs.unlink;
|
|
402
|
-
declare const rename: typeof fs.rename;
|
|
403
|
-
declare const rm: typeof fs.rm;
|
|
404
|
-
declare const cp: typeof fs.cp;
|
|
405
379
|
//#endregion
|
|
406
380
|
//#region src/utils/generateStableId.d.ts
|
|
407
381
|
/**
|
|
@@ -421,6 +395,130 @@ declare const cp: typeof fs.cp;
|
|
|
421
395
|
*/
|
|
422
396
|
declare function generateStableId(length?: number): string;
|
|
423
397
|
//#endregion
|
|
398
|
+
//#region src/utils/git.d.ts
|
|
399
|
+
/**
|
|
400
|
+
* Git Utilities
|
|
401
|
+
*
|
|
402
|
+
* DESIGN PATTERNS:
|
|
403
|
+
* - Safe command execution: Use execa with array arguments to prevent shell injection
|
|
404
|
+
* - Defense in depth: Use '--' separator to prevent option injection attacks
|
|
405
|
+
*
|
|
406
|
+
* CODING STANDARDS:
|
|
407
|
+
* - All git commands must use execGit helper with array arguments
|
|
408
|
+
* - Use '--' separator before user-provided arguments (URLs, branches, paths)
|
|
409
|
+
* - Validate inputs where appropriate
|
|
410
|
+
*
|
|
411
|
+
* AVOID:
|
|
412
|
+
* - Shell string interpolation
|
|
413
|
+
* - Passing unsanitized user input as command options
|
|
414
|
+
*
|
|
415
|
+
* NOTE: These utilities perform I/O operations (git commands, file system) by necessity.
|
|
416
|
+
* Pure utility functions like parseGitHubUrl are side-effect free.
|
|
417
|
+
*/
|
|
418
|
+
/**
|
|
419
|
+
* Parsed GitHub URL result
|
|
420
|
+
*/
|
|
421
|
+
interface ParsedGitHubUrl {
|
|
422
|
+
owner?: string;
|
|
423
|
+
repo?: string;
|
|
424
|
+
repoUrl: string;
|
|
425
|
+
branch?: string;
|
|
426
|
+
subdirectory?: string;
|
|
427
|
+
isSubdirectory: boolean;
|
|
428
|
+
}
|
|
429
|
+
/**
|
|
430
|
+
* GitHub directory entry
|
|
431
|
+
*/
|
|
432
|
+
interface GitHubDirectoryEntry {
|
|
433
|
+
name: string;
|
|
434
|
+
type: string;
|
|
435
|
+
path: string;
|
|
436
|
+
}
|
|
437
|
+
/**
|
|
438
|
+
* Execute git init safely using execa to prevent command injection
|
|
439
|
+
* Uses '--' to prevent projectPath from being interpreted as an option
|
|
440
|
+
* @param projectPath - Path where to initialize the git repository
|
|
441
|
+
* @throws Error when git init fails
|
|
442
|
+
*/
|
|
443
|
+
declare function gitInit(projectPath: string): Promise<void>;
|
|
444
|
+
/**
|
|
445
|
+
* Find the workspace root by searching upwards for .git folder
|
|
446
|
+
* Returns null if no .git folder is found (indicating a new project setup is needed)
|
|
447
|
+
* @param startPath - The path to start searching from (default: current working directory)
|
|
448
|
+
* @returns The workspace root path or null if not in a git repository
|
|
449
|
+
* @example
|
|
450
|
+
* const root = await findWorkspaceRoot('/path/to/project/src');
|
|
451
|
+
* if (root) {
|
|
452
|
+
* console.log('Workspace root:', root);
|
|
453
|
+
* } else {
|
|
454
|
+
* console.log('No git repository found');
|
|
455
|
+
* }
|
|
456
|
+
*/
|
|
457
|
+
declare function findWorkspaceRoot(startPath?: string): Promise<string | null>;
|
|
458
|
+
/**
|
|
459
|
+
* Parse GitHub URL to detect if it's a subdirectory
|
|
460
|
+
* Supports formats:
|
|
461
|
+
* - https://github.com/user/repo
|
|
462
|
+
* - https://github.com/user/repo/tree/branch/path/to/dir
|
|
463
|
+
* - https://github.com/user/repo/tree/main/path/to/dir
|
|
464
|
+
* @param url - The GitHub URL to parse
|
|
465
|
+
* @returns Parsed URL components including owner, repo, branch, and subdirectory
|
|
466
|
+
* @example
|
|
467
|
+
* const result = parseGitHubUrl('https://github.com/user/repo/tree/main/src');
|
|
468
|
+
* // result.owner === 'user'
|
|
469
|
+
* // result.repo === 'repo'
|
|
470
|
+
* // result.branch === 'main'
|
|
471
|
+
* // result.subdirectory === 'src'
|
|
472
|
+
* // result.isSubdirectory === true
|
|
473
|
+
*/
|
|
474
|
+
declare function parseGitHubUrl(url: string): ParsedGitHubUrl;
|
|
475
|
+
/**
|
|
476
|
+
* Clone a subdirectory from a git repository using sparse checkout
|
|
477
|
+
* Uses '--' to mark end of options - prevents malicious URLs like '--upload-pack=evil'
|
|
478
|
+
* from being interpreted as git options
|
|
479
|
+
* @param repoUrl - The git repository URL
|
|
480
|
+
* @param branch - The branch to clone from
|
|
481
|
+
* @param subdirectory - The subdirectory path within the repository
|
|
482
|
+
* @param targetFolder - The local folder to clone into
|
|
483
|
+
* @throws Error if subdirectory not found or target folder already exists
|
|
484
|
+
* @example
|
|
485
|
+
* await cloneSubdirectory(
|
|
486
|
+
* 'https://github.com/user/repo.git',
|
|
487
|
+
* 'main',
|
|
488
|
+
* 'packages/core',
|
|
489
|
+
* './my-project'
|
|
490
|
+
* );
|
|
491
|
+
*/
|
|
492
|
+
declare function cloneSubdirectory(repoUrl: string, branch: string, subdirectory: string, targetFolder: string): Promise<void>;
|
|
493
|
+
/**
|
|
494
|
+
* Clone entire repository
|
|
495
|
+
* Uses '--' to mark end of options - prevents malicious URLs like '--upload-pack=evil'
|
|
496
|
+
* from being interpreted as git options
|
|
497
|
+
* @param repoUrl - The git repository URL to clone
|
|
498
|
+
* @param targetFolder - The local folder path to clone into
|
|
499
|
+
* @throws Error if git clone fails
|
|
500
|
+
* @example
|
|
501
|
+
* await cloneRepository('https://github.com/user/repo.git', './my-project');
|
|
502
|
+
*/
|
|
503
|
+
declare function cloneRepository(repoUrl: string, targetFolder: string): Promise<void>;
|
|
504
|
+
/**
|
|
505
|
+
* Fetch directory listing from GitHub API
|
|
506
|
+
* @param owner - The GitHub repository owner/organization
|
|
507
|
+
* @param repo - The repository name
|
|
508
|
+
* @param dirPath - The directory path within the repository
|
|
509
|
+
* @param branch - The branch to fetch from (default: 'main')
|
|
510
|
+
* @returns Array of directory entries with name, type, and path
|
|
511
|
+
* @throws Error if the API request fails or returns non-directory content
|
|
512
|
+
* @remarks
|
|
513
|
+
* - Requires network access to GitHub API
|
|
514
|
+
* - Subject to GitHub API rate limits (60 requests/hour unauthenticated)
|
|
515
|
+
* - Only works with public repositories without authentication
|
|
516
|
+
* @example
|
|
517
|
+
* const contents = await fetchGitHubDirectoryContents('facebook', 'react', 'packages', 'main');
|
|
518
|
+
* // contents: [{ name: 'react', type: 'dir', path: 'packages/react' }, ...]
|
|
519
|
+
*/
|
|
520
|
+
declare function fetchGitHubDirectoryContents(owner: string, repo: string, dirPath: string, branch?: string): Promise<GitHubDirectoryEntry[]>;
|
|
521
|
+
//#endregion
|
|
424
522
|
//#region src/utils/logger.d.ts
|
|
425
523
|
declare const logger: pino.Logger<never, boolean>;
|
|
426
524
|
declare const log: {
|
|
@@ -581,21 +679,5 @@ interface ProjectTypeDetectionResult {
|
|
|
581
679
|
* @returns Detection result with project type and indicators
|
|
582
680
|
*/
|
|
583
681
|
declare function detectProjectType(workspaceRoot: string): Promise<ProjectTypeDetectionResult>;
|
|
584
|
-
/**
|
|
585
|
-
* Check if a workspace is a monorepo
|
|
586
|
-
* Convenience function that returns a boolean
|
|
587
|
-
*
|
|
588
|
-
* @param workspaceRoot - Absolute path to the workspace root directory
|
|
589
|
-
* @returns True if workspace is detected as monorepo, false otherwise
|
|
590
|
-
*/
|
|
591
|
-
declare function isMonorepo(workspaceRoot: string): Promise<boolean>;
|
|
592
|
-
/**
|
|
593
|
-
* Check if a workspace is a monolith
|
|
594
|
-
* Convenience function that returns a boolean
|
|
595
|
-
*
|
|
596
|
-
* @param workspaceRoot - Absolute path to the workspace root directory
|
|
597
|
-
* @returns True if workspace is detected as monolith, false otherwise
|
|
598
|
-
*/
|
|
599
|
-
declare function isMonolith(workspaceRoot: string): Promise<boolean>;
|
|
600
682
|
//#endregion
|
|
601
|
-
export { ConfigSource, GeneratorContext, GeneratorFunction, IFileSystemService, IVariableReplacementService, NxProjectJson, ParsedInclude, ProjectConfig, ProjectConfigResolver, ProjectConfigResult, ProjectFinderService, ProjectType,
|
|
683
|
+
export { ConfigSource, GeneratorContext, GeneratorFunction, GitHubDirectoryEntry, IFileSystemService, IVariableReplacementService, NxProjectJson, ParsedGitHubUrl, ParsedInclude, ProjectConfig, ProjectConfigResolver, ProjectConfigResult, ProjectFinderService, ProjectType, ScaffoldProcessingService, ScaffoldResult, TemplatesManagerService, ToolkitConfig, accessSync, cloneRepository, cloneSubdirectory, copy, detectProjectType, ensureDir, fetchGitHubDirectoryContents, findWorkspaceRoot, generateStableId, gitInit, icons, log, logger, messages, mkdir, mkdirSync, move, parseGitHubUrl, pathExists, pathExistsSync, print, readFile, readFileSync, readJson, readJsonSync, readdir, remove, sections, stat, statSync, writeFile, writeFileSync };
|
package/dist/index.d.mts
CHANGED
|
@@ -267,10 +267,9 @@ declare class TemplatesManagerService {
|
|
|
267
267
|
* 4. Verify the templates directory exists
|
|
268
268
|
*
|
|
269
269
|
* @param startPath - The path to start searching from (defaults to process.cwd())
|
|
270
|
-
* @returns The absolute path to the templates directory
|
|
271
|
-
* @throws Error if templates directory is not found
|
|
270
|
+
* @returns The absolute path to the templates directory, or null if not found
|
|
272
271
|
*/
|
|
273
|
-
static findTemplatesPath(startPath?: string): Promise<string>;
|
|
272
|
+
static findTemplatesPath(startPath?: string): Promise<string | null>;
|
|
274
273
|
/**
|
|
275
274
|
* Find the workspace root by searching upwards for .git folder
|
|
276
275
|
*/
|
|
@@ -280,10 +279,9 @@ declare class TemplatesManagerService {
|
|
|
280
279
|
* Use this when you need immediate access and are sure templates exist.
|
|
281
280
|
*
|
|
282
281
|
* @param startPath - The path to start searching from (defaults to process.cwd())
|
|
283
|
-
* @returns The absolute path to the templates directory
|
|
284
|
-
* @throws Error if templates directory is not found
|
|
282
|
+
* @returns The absolute path to the templates directory, or null if not found
|
|
285
283
|
*/
|
|
286
|
-
static findTemplatesPathSync(startPath?: string): string;
|
|
284
|
+
static findTemplatesPathSync(startPath?: string): string | null;
|
|
287
285
|
/**
|
|
288
286
|
* Find the workspace root synchronously by searching upwards for .git folder
|
|
289
287
|
*/
|
|
@@ -353,10 +351,6 @@ declare function pathExistsSync(filePath: string): boolean;
|
|
|
353
351
|
* Ensure a directory exists, creating it recursively if needed
|
|
354
352
|
*/
|
|
355
353
|
declare function ensureDir(dirPath: string): Promise<void>;
|
|
356
|
-
/**
|
|
357
|
-
* Ensure a directory exists (sync), creating it recursively if needed
|
|
358
|
-
*/
|
|
359
|
-
declare function ensureDirSync(dirPath: string): void;
|
|
360
354
|
/**
|
|
361
355
|
* Remove a file or directory recursively
|
|
362
356
|
*/
|
|
@@ -377,31 +371,11 @@ declare function readJson<T = unknown>(filePath: string): Promise<T>;
|
|
|
377
371
|
* Read and parse a JSON file (sync)
|
|
378
372
|
*/
|
|
379
373
|
declare function readJsonSync<T = unknown>(filePath: string): T;
|
|
380
|
-
/**
|
|
381
|
-
* Write an object as JSON to a file
|
|
382
|
-
*/
|
|
383
|
-
declare function writeJson(filePath: string, data: unknown, options?: {
|
|
384
|
-
spaces?: number;
|
|
385
|
-
}): Promise<void>;
|
|
386
|
-
/**
|
|
387
|
-
* Write an object as JSON to a file (sync)
|
|
388
|
-
*/
|
|
389
|
-
declare function writeJsonSync(filePath: string, data: unknown, options?: {
|
|
390
|
-
spaces?: number;
|
|
391
|
-
}): void;
|
|
392
|
-
/**
|
|
393
|
-
* Output file - writes content ensuring directory exists
|
|
394
|
-
*/
|
|
395
|
-
declare function outputFile(filePath: string, content: string): Promise<void>;
|
|
396
374
|
declare const readFile: typeof fs.readFile;
|
|
397
375
|
declare const writeFile: typeof fs.writeFile;
|
|
398
376
|
declare const readdir: typeof fs.readdir;
|
|
399
377
|
declare const mkdir: typeof fs.mkdir;
|
|
400
378
|
declare const stat: typeof fs.stat;
|
|
401
|
-
declare const unlink: typeof fs.unlink;
|
|
402
|
-
declare const rename: typeof fs.rename;
|
|
403
|
-
declare const rm: typeof fs.rm;
|
|
404
|
-
declare const cp: typeof fs.cp;
|
|
405
379
|
//#endregion
|
|
406
380
|
//#region src/utils/generateStableId.d.ts
|
|
407
381
|
/**
|
|
@@ -421,6 +395,130 @@ declare const cp: typeof fs.cp;
|
|
|
421
395
|
*/
|
|
422
396
|
declare function generateStableId(length?: number): string;
|
|
423
397
|
//#endregion
|
|
398
|
+
//#region src/utils/git.d.ts
|
|
399
|
+
/**
|
|
400
|
+
* Git Utilities
|
|
401
|
+
*
|
|
402
|
+
* DESIGN PATTERNS:
|
|
403
|
+
* - Safe command execution: Use execa with array arguments to prevent shell injection
|
|
404
|
+
* - Defense in depth: Use '--' separator to prevent option injection attacks
|
|
405
|
+
*
|
|
406
|
+
* CODING STANDARDS:
|
|
407
|
+
* - All git commands must use execGit helper with array arguments
|
|
408
|
+
* - Use '--' separator before user-provided arguments (URLs, branches, paths)
|
|
409
|
+
* - Validate inputs where appropriate
|
|
410
|
+
*
|
|
411
|
+
* AVOID:
|
|
412
|
+
* - Shell string interpolation
|
|
413
|
+
* - Passing unsanitized user input as command options
|
|
414
|
+
*
|
|
415
|
+
* NOTE: These utilities perform I/O operations (git commands, file system) by necessity.
|
|
416
|
+
* Pure utility functions like parseGitHubUrl are side-effect free.
|
|
417
|
+
*/
|
|
418
|
+
/**
|
|
419
|
+
* Parsed GitHub URL result
|
|
420
|
+
*/
|
|
421
|
+
interface ParsedGitHubUrl {
|
|
422
|
+
owner?: string;
|
|
423
|
+
repo?: string;
|
|
424
|
+
repoUrl: string;
|
|
425
|
+
branch?: string;
|
|
426
|
+
subdirectory?: string;
|
|
427
|
+
isSubdirectory: boolean;
|
|
428
|
+
}
|
|
429
|
+
/**
|
|
430
|
+
* GitHub directory entry
|
|
431
|
+
*/
|
|
432
|
+
interface GitHubDirectoryEntry {
|
|
433
|
+
name: string;
|
|
434
|
+
type: string;
|
|
435
|
+
path: string;
|
|
436
|
+
}
|
|
437
|
+
/**
|
|
438
|
+
* Execute git init safely using execa to prevent command injection
|
|
439
|
+
* Uses '--' to prevent projectPath from being interpreted as an option
|
|
440
|
+
* @param projectPath - Path where to initialize the git repository
|
|
441
|
+
* @throws Error when git init fails
|
|
442
|
+
*/
|
|
443
|
+
declare function gitInit(projectPath: string): Promise<void>;
|
|
444
|
+
/**
|
|
445
|
+
* Find the workspace root by searching upwards for .git folder
|
|
446
|
+
* Returns null if no .git folder is found (indicating a new project setup is needed)
|
|
447
|
+
* @param startPath - The path to start searching from (default: current working directory)
|
|
448
|
+
* @returns The workspace root path or null if not in a git repository
|
|
449
|
+
* @example
|
|
450
|
+
* const root = await findWorkspaceRoot('/path/to/project/src');
|
|
451
|
+
* if (root) {
|
|
452
|
+
* console.log('Workspace root:', root);
|
|
453
|
+
* } else {
|
|
454
|
+
* console.log('No git repository found');
|
|
455
|
+
* }
|
|
456
|
+
*/
|
|
457
|
+
declare function findWorkspaceRoot(startPath?: string): Promise<string | null>;
|
|
458
|
+
/**
|
|
459
|
+
* Parse GitHub URL to detect if it's a subdirectory
|
|
460
|
+
* Supports formats:
|
|
461
|
+
* - https://github.com/user/repo
|
|
462
|
+
* - https://github.com/user/repo/tree/branch/path/to/dir
|
|
463
|
+
* - https://github.com/user/repo/tree/main/path/to/dir
|
|
464
|
+
* @param url - The GitHub URL to parse
|
|
465
|
+
* @returns Parsed URL components including owner, repo, branch, and subdirectory
|
|
466
|
+
* @example
|
|
467
|
+
* const result = parseGitHubUrl('https://github.com/user/repo/tree/main/src');
|
|
468
|
+
* // result.owner === 'user'
|
|
469
|
+
* // result.repo === 'repo'
|
|
470
|
+
* // result.branch === 'main'
|
|
471
|
+
* // result.subdirectory === 'src'
|
|
472
|
+
* // result.isSubdirectory === true
|
|
473
|
+
*/
|
|
474
|
+
declare function parseGitHubUrl(url: string): ParsedGitHubUrl;
|
|
475
|
+
/**
|
|
476
|
+
* Clone a subdirectory from a git repository using sparse checkout
|
|
477
|
+
* Uses '--' to mark end of options - prevents malicious URLs like '--upload-pack=evil'
|
|
478
|
+
* from being interpreted as git options
|
|
479
|
+
* @param repoUrl - The git repository URL
|
|
480
|
+
* @param branch - The branch to clone from
|
|
481
|
+
* @param subdirectory - The subdirectory path within the repository
|
|
482
|
+
* @param targetFolder - The local folder to clone into
|
|
483
|
+
* @throws Error if subdirectory not found or target folder already exists
|
|
484
|
+
* @example
|
|
485
|
+
* await cloneSubdirectory(
|
|
486
|
+
* 'https://github.com/user/repo.git',
|
|
487
|
+
* 'main',
|
|
488
|
+
* 'packages/core',
|
|
489
|
+
* './my-project'
|
|
490
|
+
* );
|
|
491
|
+
*/
|
|
492
|
+
declare function cloneSubdirectory(repoUrl: string, branch: string, subdirectory: string, targetFolder: string): Promise<void>;
|
|
493
|
+
/**
|
|
494
|
+
* Clone entire repository
|
|
495
|
+
* Uses '--' to mark end of options - prevents malicious URLs like '--upload-pack=evil'
|
|
496
|
+
* from being interpreted as git options
|
|
497
|
+
* @param repoUrl - The git repository URL to clone
|
|
498
|
+
* @param targetFolder - The local folder path to clone into
|
|
499
|
+
* @throws Error if git clone fails
|
|
500
|
+
* @example
|
|
501
|
+
* await cloneRepository('https://github.com/user/repo.git', './my-project');
|
|
502
|
+
*/
|
|
503
|
+
declare function cloneRepository(repoUrl: string, targetFolder: string): Promise<void>;
|
|
504
|
+
/**
|
|
505
|
+
* Fetch directory listing from GitHub API
|
|
506
|
+
* @param owner - The GitHub repository owner/organization
|
|
507
|
+
* @param repo - The repository name
|
|
508
|
+
* @param dirPath - The directory path within the repository
|
|
509
|
+
* @param branch - The branch to fetch from (default: 'main')
|
|
510
|
+
* @returns Array of directory entries with name, type, and path
|
|
511
|
+
* @throws Error if the API request fails or returns non-directory content
|
|
512
|
+
* @remarks
|
|
513
|
+
* - Requires network access to GitHub API
|
|
514
|
+
* - Subject to GitHub API rate limits (60 requests/hour unauthenticated)
|
|
515
|
+
* - Only works with public repositories without authentication
|
|
516
|
+
* @example
|
|
517
|
+
* const contents = await fetchGitHubDirectoryContents('facebook', 'react', 'packages', 'main');
|
|
518
|
+
* // contents: [{ name: 'react', type: 'dir', path: 'packages/react' }, ...]
|
|
519
|
+
*/
|
|
520
|
+
declare function fetchGitHubDirectoryContents(owner: string, repo: string, dirPath: string, branch?: string): Promise<GitHubDirectoryEntry[]>;
|
|
521
|
+
//#endregion
|
|
424
522
|
//#region src/utils/logger.d.ts
|
|
425
523
|
declare const logger: pino.Logger<never, boolean>;
|
|
426
524
|
declare const log: {
|
|
@@ -581,21 +679,5 @@ interface ProjectTypeDetectionResult {
|
|
|
581
679
|
* @returns Detection result with project type and indicators
|
|
582
680
|
*/
|
|
583
681
|
declare function detectProjectType(workspaceRoot: string): Promise<ProjectTypeDetectionResult>;
|
|
584
|
-
/**
|
|
585
|
-
* Check if a workspace is a monorepo
|
|
586
|
-
* Convenience function that returns a boolean
|
|
587
|
-
*
|
|
588
|
-
* @param workspaceRoot - Absolute path to the workspace root directory
|
|
589
|
-
* @returns True if workspace is detected as monorepo, false otherwise
|
|
590
|
-
*/
|
|
591
|
-
declare function isMonorepo(workspaceRoot: string): Promise<boolean>;
|
|
592
|
-
/**
|
|
593
|
-
* Check if a workspace is a monolith
|
|
594
|
-
* Convenience function that returns a boolean
|
|
595
|
-
*
|
|
596
|
-
* @param workspaceRoot - Absolute path to the workspace root directory
|
|
597
|
-
* @returns True if workspace is detected as monolith, false otherwise
|
|
598
|
-
*/
|
|
599
|
-
declare function isMonolith(workspaceRoot: string): Promise<boolean>;
|
|
600
682
|
//#endregion
|
|
601
|
-
export { ConfigSource, GeneratorContext, GeneratorFunction, IFileSystemService, IVariableReplacementService, NxProjectJson, ParsedInclude, ProjectConfig, ProjectConfigResolver, ProjectConfigResult, ProjectFinderService, ProjectType,
|
|
683
|
+
export { ConfigSource, GeneratorContext, GeneratorFunction, type GitHubDirectoryEntry, IFileSystemService, IVariableReplacementService, NxProjectJson, type ParsedGitHubUrl, ParsedInclude, ProjectConfig, ProjectConfigResolver, ProjectConfigResult, ProjectFinderService, ProjectType, ScaffoldProcessingService, ScaffoldResult, TemplatesManagerService, ToolkitConfig, accessSync, cloneRepository, cloneSubdirectory, copy, detectProjectType, ensureDir, fetchGitHubDirectoryContents, findWorkspaceRoot, generateStableId, gitInit, icons, log, logger, messages, mkdir, mkdirSync, move, parseGitHubUrl, pathExists, pathExistsSync, print, readFile, readFileSync, readJson, readJsonSync, readdir, remove, sections, stat, statSync, writeFile, writeFileSync };
|
package/dist/index.mjs
CHANGED
|
@@ -6,6 +6,7 @@ import { accessSync, mkdirSync, readFileSync, readFileSync as readFileSync$1, st
|
|
|
6
6
|
import * as os from "node:os";
|
|
7
7
|
import pino from "pino";
|
|
8
8
|
import * as yaml from "js-yaml";
|
|
9
|
+
import { execa } from "execa";
|
|
9
10
|
import chalk from "chalk";
|
|
10
11
|
|
|
11
12
|
//#region rolldown:runtime
|
|
@@ -65,12 +66,6 @@ async function ensureDir(dirPath) {
|
|
|
65
66
|
await fs.mkdir(dirPath, { recursive: true });
|
|
66
67
|
}
|
|
67
68
|
/**
|
|
68
|
-
* Ensure a directory exists (sync), creating it recursively if needed
|
|
69
|
-
*/
|
|
70
|
-
function ensureDirSync(dirPath) {
|
|
71
|
-
mkdirSync(dirPath, { recursive: true });
|
|
72
|
-
}
|
|
73
|
-
/**
|
|
74
69
|
* Remove a file or directory recursively
|
|
75
70
|
*/
|
|
76
71
|
async function remove(filePath) {
|
|
@@ -113,19 +108,6 @@ async function writeJson(filePath, data, options) {
|
|
|
113
108
|
const content = JSON.stringify(data, null, options?.spaces ?? 2);
|
|
114
109
|
await fs.writeFile(filePath, `${content}\n`, "utf-8");
|
|
115
110
|
}
|
|
116
|
-
/**
|
|
117
|
-
* Write an object as JSON to a file (sync)
|
|
118
|
-
*/
|
|
119
|
-
function writeJsonSync(filePath, data, options) {
|
|
120
|
-
writeFileSync(filePath, `${JSON.stringify(data, null, options?.spaces ?? 2)}\n`, "utf-8");
|
|
121
|
-
}
|
|
122
|
-
/**
|
|
123
|
-
* Output file - writes content ensuring directory exists
|
|
124
|
-
*/
|
|
125
|
-
async function outputFile(filePath, content) {
|
|
126
|
-
await ensureDir(path.dirname(filePath));
|
|
127
|
-
await fs.writeFile(filePath, content, "utf-8");
|
|
128
|
-
}
|
|
129
111
|
const readFile = fs.readFile;
|
|
130
112
|
const writeFile = fs.writeFile;
|
|
131
113
|
const readdir = fs.readdir;
|
|
@@ -195,8 +177,7 @@ var TemplatesManagerService = class TemplatesManagerService {
|
|
|
195
177
|
* 4. Verify the templates directory exists
|
|
196
178
|
*
|
|
197
179
|
* @param startPath - The path to start searching from (defaults to process.cwd())
|
|
198
|
-
* @returns The absolute path to the templates directory
|
|
199
|
-
* @throws Error if templates directory is not found
|
|
180
|
+
* @returns The absolute path to the templates directory, or null if not found
|
|
200
181
|
*/
|
|
201
182
|
static async findTemplatesPath(startPath = process.cwd()) {
|
|
202
183
|
const workspaceRoot = await TemplatesManagerService.findWorkspaceRoot(startPath);
|
|
@@ -208,12 +189,12 @@ var TemplatesManagerService = class TemplatesManagerService {
|
|
|
208
189
|
if (config?.templatesPath) {
|
|
209
190
|
const templatesPath$1 = path.isAbsolute(config.templatesPath) ? config.templatesPath : path.join(workspaceRoot, config.templatesPath);
|
|
210
191
|
if (await pathExists(templatesPath$1)) return templatesPath$1;
|
|
211
|
-
else
|
|
192
|
+
else return null;
|
|
212
193
|
}
|
|
213
194
|
}
|
|
214
195
|
const templatesPath = path.join(workspaceRoot, TemplatesManagerService.TEMPLATES_FOLDER);
|
|
215
196
|
if (await pathExists(templatesPath)) return templatesPath;
|
|
216
|
-
|
|
197
|
+
return null;
|
|
217
198
|
}
|
|
218
199
|
/**
|
|
219
200
|
* Find the workspace root by searching upwards for .git folder
|
|
@@ -232,8 +213,7 @@ var TemplatesManagerService = class TemplatesManagerService {
|
|
|
232
213
|
* Use this when you need immediate access and are sure templates exist.
|
|
233
214
|
*
|
|
234
215
|
* @param startPath - The path to start searching from (defaults to process.cwd())
|
|
235
|
-
* @returns The absolute path to the templates directory
|
|
236
|
-
* @throws Error if templates directory is not found
|
|
216
|
+
* @returns The absolute path to the templates directory, or null if not found
|
|
237
217
|
*/
|
|
238
218
|
static findTemplatesPathSync(startPath = process.cwd()) {
|
|
239
219
|
const workspaceRoot = TemplatesManagerService.findWorkspaceRootSync(startPath);
|
|
@@ -245,12 +225,12 @@ var TemplatesManagerService = class TemplatesManagerService {
|
|
|
245
225
|
if (config?.templatesPath) {
|
|
246
226
|
const templatesPath$1 = path.isAbsolute(config.templatesPath) ? config.templatesPath : path.join(workspaceRoot, config.templatesPath);
|
|
247
227
|
if (pathExistsSync(templatesPath$1)) return templatesPath$1;
|
|
248
|
-
else
|
|
228
|
+
else return null;
|
|
249
229
|
}
|
|
250
230
|
}
|
|
251
231
|
const templatesPath = path.join(workspaceRoot, TemplatesManagerService.TEMPLATES_FOLDER);
|
|
252
232
|
if (pathExistsSync(templatesPath)) return templatesPath;
|
|
253
|
-
|
|
233
|
+
return null;
|
|
254
234
|
}
|
|
255
235
|
/**
|
|
256
236
|
* Find the workspace root synchronously by searching upwards for .git folder
|
|
@@ -755,6 +735,258 @@ function generateStableId(length = 8) {
|
|
|
755
735
|
return result;
|
|
756
736
|
}
|
|
757
737
|
|
|
738
|
+
//#endregion
|
|
739
|
+
//#region src/utils/git.ts
|
|
740
|
+
/**
|
|
741
|
+
* Git Utilities
|
|
742
|
+
*
|
|
743
|
+
* DESIGN PATTERNS:
|
|
744
|
+
* - Safe command execution: Use execa with array arguments to prevent shell injection
|
|
745
|
+
* - Defense in depth: Use '--' separator to prevent option injection attacks
|
|
746
|
+
*
|
|
747
|
+
* CODING STANDARDS:
|
|
748
|
+
* - All git commands must use execGit helper with array arguments
|
|
749
|
+
* - Use '--' separator before user-provided arguments (URLs, branches, paths)
|
|
750
|
+
* - Validate inputs where appropriate
|
|
751
|
+
*
|
|
752
|
+
* AVOID:
|
|
753
|
+
* - Shell string interpolation
|
|
754
|
+
* - Passing unsanitized user input as command options
|
|
755
|
+
*
|
|
756
|
+
* NOTE: These utilities perform I/O operations (git commands, file system) by necessity.
|
|
757
|
+
* Pure utility functions like parseGitHubUrl are side-effect free.
|
|
758
|
+
*/
|
|
759
|
+
/**
|
|
760
|
+
* Type guard to check if an error has the expected execa error shape
|
|
761
|
+
* @param error - The error to check
|
|
762
|
+
* @returns True if the error has message property (and optionally stderr)
|
|
763
|
+
* @example
|
|
764
|
+
* try {
|
|
765
|
+
* await execa('git', ['status']);
|
|
766
|
+
* } catch (error) {
|
|
767
|
+
* if (isExecaError(error)) {
|
|
768
|
+
* console.error(error.stderr || error.message);
|
|
769
|
+
* }
|
|
770
|
+
* }
|
|
771
|
+
*/
|
|
772
|
+
function isExecaError(error) {
|
|
773
|
+
if (typeof error !== "object" || error === null) return false;
|
|
774
|
+
if (!("message" in error)) return false;
|
|
775
|
+
return typeof error.message === "string";
|
|
776
|
+
}
|
|
777
|
+
/**
|
|
778
|
+
* Execute a git command safely using execa to prevent command injection
|
|
779
|
+
* @param args - Array of git command arguments
|
|
780
|
+
* @param cwd - Optional working directory for the command
|
|
781
|
+
* @throws Error when git command fails
|
|
782
|
+
*/
|
|
783
|
+
async function execGit(args, cwd) {
|
|
784
|
+
try {
|
|
785
|
+
await execa("git", args, { cwd });
|
|
786
|
+
} catch (error) {
|
|
787
|
+
if (isExecaError(error)) throw new Error(`Git command failed: ${error.stderr || error.message}`);
|
|
788
|
+
throw error;
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
/**
|
|
792
|
+
* Execute git init safely using execa to prevent command injection
|
|
793
|
+
* Uses '--' to prevent projectPath from being interpreted as an option
|
|
794
|
+
* @param projectPath - Path where to initialize the git repository
|
|
795
|
+
* @throws Error when git init fails
|
|
796
|
+
*/
|
|
797
|
+
async function gitInit(projectPath) {
|
|
798
|
+
try {
|
|
799
|
+
await execa("git", [
|
|
800
|
+
"init",
|
|
801
|
+
"--",
|
|
802
|
+
projectPath
|
|
803
|
+
]);
|
|
804
|
+
} catch (error) {
|
|
805
|
+
if (isExecaError(error)) throw new Error(`Git init failed: ${error.stderr || error.message}`);
|
|
806
|
+
throw error;
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
/**
|
|
810
|
+
* Find the workspace root by searching upwards for .git folder
|
|
811
|
+
* Returns null if no .git folder is found (indicating a new project setup is needed)
|
|
812
|
+
* @param startPath - The path to start searching from (default: current working directory)
|
|
813
|
+
* @returns The workspace root path or null if not in a git repository
|
|
814
|
+
* @example
|
|
815
|
+
* const root = await findWorkspaceRoot('/path/to/project/src');
|
|
816
|
+
* if (root) {
|
|
817
|
+
* console.log('Workspace root:', root);
|
|
818
|
+
* } else {
|
|
819
|
+
* console.log('No git repository found');
|
|
820
|
+
* }
|
|
821
|
+
*/
|
|
822
|
+
async function findWorkspaceRoot(startPath = process.cwd()) {
|
|
823
|
+
let currentPath = path.resolve(startPath);
|
|
824
|
+
const rootPath = path.parse(currentPath).root;
|
|
825
|
+
while (true) {
|
|
826
|
+
if (await pathExists(path.join(currentPath, ".git"))) return currentPath;
|
|
827
|
+
if (currentPath === rootPath) return null;
|
|
828
|
+
currentPath = path.dirname(currentPath);
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
/**
|
|
832
|
+
* Parse GitHub URL to detect if it's a subdirectory
|
|
833
|
+
* Supports formats:
|
|
834
|
+
* - https://github.com/user/repo
|
|
835
|
+
* - https://github.com/user/repo/tree/branch/path/to/dir
|
|
836
|
+
* - https://github.com/user/repo/tree/main/path/to/dir
|
|
837
|
+
* @param url - The GitHub URL to parse
|
|
838
|
+
* @returns Parsed URL components including owner, repo, branch, and subdirectory
|
|
839
|
+
* @example
|
|
840
|
+
* const result = parseGitHubUrl('https://github.com/user/repo/tree/main/src');
|
|
841
|
+
* // result.owner === 'user'
|
|
842
|
+
* // result.repo === 'repo'
|
|
843
|
+
* // result.branch === 'main'
|
|
844
|
+
* // result.subdirectory === 'src'
|
|
845
|
+
* // result.isSubdirectory === true
|
|
846
|
+
*/
|
|
847
|
+
function parseGitHubUrl(url) {
|
|
848
|
+
const treeMatch = url.match(/^https?:\/\/github\.com\/([^/]+)\/([^/]+)\/tree\/([^/]+)\/(.+)$/);
|
|
849
|
+
const blobMatch = url.match(/^https?:\/\/github\.com\/([^/]+)\/([^/]+)\/blob\/([^/]+)\/(.+)$/);
|
|
850
|
+
const rootMatch = url.match(/^https?:\/\/github\.com\/([^/]+)\/([^/]+?)(?:\.git)?$/);
|
|
851
|
+
if (treeMatch || blobMatch) {
|
|
852
|
+
const match = treeMatch || blobMatch;
|
|
853
|
+
return {
|
|
854
|
+
owner: match?.[1],
|
|
855
|
+
repo: match?.[2],
|
|
856
|
+
repoUrl: `https://github.com/${match?.[1]}/${match?.[2]}.git`,
|
|
857
|
+
branch: match?.[3],
|
|
858
|
+
subdirectory: match?.[4],
|
|
859
|
+
isSubdirectory: true
|
|
860
|
+
};
|
|
861
|
+
}
|
|
862
|
+
if (rootMatch) return {
|
|
863
|
+
owner: rootMatch[1],
|
|
864
|
+
repo: rootMatch[2],
|
|
865
|
+
repoUrl: `https://github.com/${rootMatch[1]}/${rootMatch[2]}.git`,
|
|
866
|
+
isSubdirectory: false
|
|
867
|
+
};
|
|
868
|
+
return {
|
|
869
|
+
repoUrl: url,
|
|
870
|
+
isSubdirectory: false
|
|
871
|
+
};
|
|
872
|
+
}
|
|
873
|
+
/**
|
|
874
|
+
* Clone a subdirectory from a git repository using sparse checkout
|
|
875
|
+
* Uses '--' to mark end of options - prevents malicious URLs like '--upload-pack=evil'
|
|
876
|
+
* from being interpreted as git options
|
|
877
|
+
* @param repoUrl - The git repository URL
|
|
878
|
+
* @param branch - The branch to clone from
|
|
879
|
+
* @param subdirectory - The subdirectory path within the repository
|
|
880
|
+
* @param targetFolder - The local folder to clone into
|
|
881
|
+
* @throws Error if subdirectory not found or target folder already exists
|
|
882
|
+
* @example
|
|
883
|
+
* await cloneSubdirectory(
|
|
884
|
+
* 'https://github.com/user/repo.git',
|
|
885
|
+
* 'main',
|
|
886
|
+
* 'packages/core',
|
|
887
|
+
* './my-project'
|
|
888
|
+
* );
|
|
889
|
+
*/
|
|
890
|
+
async function cloneSubdirectory(repoUrl, branch, subdirectory, targetFolder) {
|
|
891
|
+
const tempFolder = `${targetFolder}.tmp`;
|
|
892
|
+
try {
|
|
893
|
+
await execGit([
|
|
894
|
+
"init",
|
|
895
|
+
"--",
|
|
896
|
+
tempFolder
|
|
897
|
+
]);
|
|
898
|
+
await execGit([
|
|
899
|
+
"remote",
|
|
900
|
+
"add",
|
|
901
|
+
"origin",
|
|
902
|
+
"--",
|
|
903
|
+
repoUrl
|
|
904
|
+
], tempFolder);
|
|
905
|
+
await execGit([
|
|
906
|
+
"config",
|
|
907
|
+
"core.sparseCheckout",
|
|
908
|
+
"true"
|
|
909
|
+
], tempFolder);
|
|
910
|
+
await writeFile(path.join(tempFolder, ".git", "info", "sparse-checkout"), `${subdirectory}\n`);
|
|
911
|
+
await execGit([
|
|
912
|
+
"pull",
|
|
913
|
+
"--depth=1",
|
|
914
|
+
"origin",
|
|
915
|
+
"--",
|
|
916
|
+
branch
|
|
917
|
+
], tempFolder);
|
|
918
|
+
const sourceDir = path.join(tempFolder, subdirectory);
|
|
919
|
+
if (!await pathExists(sourceDir)) throw new Error(`Subdirectory '${subdirectory}' not found in repository at branch '${branch}'`);
|
|
920
|
+
if (await pathExists(targetFolder)) throw new Error(`Target folder already exists: ${targetFolder}`);
|
|
921
|
+
await move(sourceDir, targetFolder);
|
|
922
|
+
await remove(tempFolder);
|
|
923
|
+
} catch (error) {
|
|
924
|
+
if (await pathExists(tempFolder)) await remove(tempFolder);
|
|
925
|
+
throw error;
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
/**
|
|
929
|
+
* Clone entire repository
|
|
930
|
+
* Uses '--' to mark end of options - prevents malicious URLs like '--upload-pack=evil'
|
|
931
|
+
* from being interpreted as git options
|
|
932
|
+
* @param repoUrl - The git repository URL to clone
|
|
933
|
+
* @param targetFolder - The local folder path to clone into
|
|
934
|
+
* @throws Error if git clone fails
|
|
935
|
+
* @example
|
|
936
|
+
* await cloneRepository('https://github.com/user/repo.git', './my-project');
|
|
937
|
+
*/
|
|
938
|
+
async function cloneRepository(repoUrl, targetFolder) {
|
|
939
|
+
await execGit([
|
|
940
|
+
"clone",
|
|
941
|
+
"--",
|
|
942
|
+
repoUrl,
|
|
943
|
+
targetFolder
|
|
944
|
+
]);
|
|
945
|
+
const gitFolder = path.join(targetFolder, ".git");
|
|
946
|
+
if (await pathExists(gitFolder)) await remove(gitFolder);
|
|
947
|
+
}
|
|
948
|
+
/**
|
|
949
|
+
* Type guard to validate GitHub API content item structure
|
|
950
|
+
* @param item - The item to validate
|
|
951
|
+
* @returns True if the item has required GitHubContentItem properties
|
|
952
|
+
*/
|
|
953
|
+
function isGitHubContentItem(item) {
|
|
954
|
+
if (typeof item !== "object" || item === null) return false;
|
|
955
|
+
const obj = item;
|
|
956
|
+
return typeof obj.name === "string" && typeof obj.type === "string" && typeof obj.path === "string";
|
|
957
|
+
}
|
|
958
|
+
/**
|
|
959
|
+
* Fetch directory listing from GitHub API
|
|
960
|
+
* @param owner - The GitHub repository owner/organization
|
|
961
|
+
* @param repo - The repository name
|
|
962
|
+
* @param dirPath - The directory path within the repository
|
|
963
|
+
* @param branch - The branch to fetch from (default: 'main')
|
|
964
|
+
* @returns Array of directory entries with name, type, and path
|
|
965
|
+
* @throws Error if the API request fails or returns non-directory content
|
|
966
|
+
* @remarks
|
|
967
|
+
* - Requires network access to GitHub API
|
|
968
|
+
* - Subject to GitHub API rate limits (60 requests/hour unauthenticated)
|
|
969
|
+
* - Only works with public repositories without authentication
|
|
970
|
+
* @example
|
|
971
|
+
* const contents = await fetchGitHubDirectoryContents('facebook', 'react', 'packages', 'main');
|
|
972
|
+
* // contents: [{ name: 'react', type: 'dir', path: 'packages/react' }, ...]
|
|
973
|
+
*/
|
|
974
|
+
async function fetchGitHubDirectoryContents(owner, repo, dirPath, branch = "main") {
|
|
975
|
+
const url = `https://api.github.com/repos/${owner}/${repo}/contents/${dirPath}?ref=${branch}`;
|
|
976
|
+
const response = await fetch(url, { headers: {
|
|
977
|
+
Accept: "application/vnd.github.v3+json",
|
|
978
|
+
"User-Agent": "scaffold-mcp"
|
|
979
|
+
} });
|
|
980
|
+
if (!response.ok) throw new Error(`Failed to fetch directory contents: ${response.statusText}`);
|
|
981
|
+
const data = await response.json();
|
|
982
|
+
if (!Array.isArray(data)) throw new Error("Expected directory but got file");
|
|
983
|
+
return data.filter(isGitHubContentItem).map((item) => ({
|
|
984
|
+
name: item.name,
|
|
985
|
+
type: item.type,
|
|
986
|
+
path: item.path
|
|
987
|
+
}));
|
|
988
|
+
}
|
|
989
|
+
|
|
758
990
|
//#endregion
|
|
759
991
|
//#region src/utils/print.ts
|
|
760
992
|
/**
|
|
@@ -962,26 +1194,6 @@ async function detectProjectType(workspaceRoot) {
|
|
|
962
1194
|
indicators
|
|
963
1195
|
};
|
|
964
1196
|
}
|
|
965
|
-
/**
|
|
966
|
-
* Check if a workspace is a monorepo
|
|
967
|
-
* Convenience function that returns a boolean
|
|
968
|
-
*
|
|
969
|
-
* @param workspaceRoot - Absolute path to the workspace root directory
|
|
970
|
-
* @returns True if workspace is detected as monorepo, false otherwise
|
|
971
|
-
*/
|
|
972
|
-
async function isMonorepo(workspaceRoot) {
|
|
973
|
-
return (await detectProjectType(workspaceRoot)).projectType === ProjectType.MONOREPO;
|
|
974
|
-
}
|
|
975
|
-
/**
|
|
976
|
-
* Check if a workspace is a monolith
|
|
977
|
-
* Convenience function that returns a boolean
|
|
978
|
-
*
|
|
979
|
-
* @param workspaceRoot - Absolute path to the workspace root directory
|
|
980
|
-
* @returns True if workspace is detected as monolith, false otherwise
|
|
981
|
-
*/
|
|
982
|
-
async function isMonolith(workspaceRoot) {
|
|
983
|
-
return (await detectProjectType(workspaceRoot)).projectType === ProjectType.MONOLITH;
|
|
984
|
-
}
|
|
985
1197
|
|
|
986
1198
|
//#endregion
|
|
987
|
-
export { ConfigSource, ProjectConfigResolver, ProjectFinderService, ProjectType, ScaffoldProcessingService, TemplatesManagerService, accessSync,
|
|
1199
|
+
export { ConfigSource, ProjectConfigResolver, ProjectFinderService, ProjectType, ScaffoldProcessingService, TemplatesManagerService, accessSync, cloneRepository, cloneSubdirectory, copy, detectProjectType, ensureDir, fetchGitHubDirectoryContents, findWorkspaceRoot, generateStableId, gitInit, icons, log, logger, messages, mkdir, mkdirSync, move, parseGitHubUrl, pathExists, pathExistsSync, print, readFile, readFileSync, readJson, readJsonSync, readdir, remove, sections, stat, statSync, writeFile, writeFileSync };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agiflowai/aicode-utils",
|
|
3
3
|
"description": "Shared utilities and types for AI-powered code generation, scaffolding, and analysis",
|
|
4
|
-
"version": "1.0.
|
|
4
|
+
"version": "1.0.10",
|
|
5
5
|
"license": "AGPL-3.0",
|
|
6
6
|
"author": "AgiflowIO",
|
|
7
7
|
"repository": {
|
|
@@ -31,7 +31,8 @@
|
|
|
31
31
|
],
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"chalk": "5.6.2",
|
|
34
|
-
"
|
|
34
|
+
"execa": "^9.5.2",
|
|
35
|
+
"js-yaml": "4.1.1",
|
|
35
36
|
"ora": "^9.0.0",
|
|
36
37
|
"pino": "^10.0.0"
|
|
37
38
|
},
|
|
@@ -43,7 +44,7 @@
|
|
|
43
44
|
"chance": "^1.1.13",
|
|
44
45
|
"tsdown": "^0.16.4",
|
|
45
46
|
"typescript": "5.9.3",
|
|
46
|
-
"vitest": "
|
|
47
|
+
"vitest": "4.0.15"
|
|
47
48
|
},
|
|
48
49
|
"type": "module",
|
|
49
50
|
"publishConfig": {
|