@agiflowai/aicode-utils 1.0.7 → 1.0.9
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 +276 -70
- package/dist/index.d.cts +129 -47
- package/dist/index.d.mts +129 -47
- package/dist/index.mjs +304 -92
- package/package.json +4 -3
package/dist/index.cjs
CHANGED
|
@@ -6,12 +6,16 @@ var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
|
6
6
|
var __getProtoOf = Object.getPrototypeOf;
|
|
7
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
8
|
var __copyProps = (to, from, except, desc) => {
|
|
9
|
-
if (from && typeof from === "object" || typeof from === "function")
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
9
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
10
|
+
for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
11
|
+
key = keys[i];
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except) {
|
|
13
|
+
__defProp(to, key, {
|
|
14
|
+
get: ((k) => from[k]).bind(null, key),
|
|
15
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
}
|
|
15
19
|
}
|
|
16
20
|
return to;
|
|
17
21
|
};
|
|
@@ -32,6 +36,7 @@ let pino = require("pino");
|
|
|
32
36
|
pino = __toESM(pino);
|
|
33
37
|
let js_yaml = require("js-yaml");
|
|
34
38
|
js_yaml = __toESM(js_yaml);
|
|
39
|
+
let execa = require("execa");
|
|
35
40
|
let chalk = require("chalk");
|
|
36
41
|
chalk = __toESM(chalk);
|
|
37
42
|
|
|
@@ -88,12 +93,6 @@ async function ensureDir(dirPath) {
|
|
|
88
93
|
await node_fs_promises.mkdir(dirPath, { recursive: true });
|
|
89
94
|
}
|
|
90
95
|
/**
|
|
91
|
-
* Ensure a directory exists (sync), creating it recursively if needed
|
|
92
|
-
*/
|
|
93
|
-
function ensureDirSync(dirPath) {
|
|
94
|
-
(0, node_fs.mkdirSync)(dirPath, { recursive: true });
|
|
95
|
-
}
|
|
96
|
-
/**
|
|
97
96
|
* Remove a file or directory recursively
|
|
98
97
|
*/
|
|
99
98
|
async function remove(filePath) {
|
|
@@ -136,19 +135,6 @@ async function writeJson(filePath, data, options) {
|
|
|
136
135
|
const content = JSON.stringify(data, null, options?.spaces ?? 2);
|
|
137
136
|
await node_fs_promises.writeFile(filePath, `${content}\n`, "utf-8");
|
|
138
137
|
}
|
|
139
|
-
/**
|
|
140
|
-
* Write an object as JSON to a file (sync)
|
|
141
|
-
*/
|
|
142
|
-
function writeJsonSync(filePath, data, options) {
|
|
143
|
-
(0, node_fs.writeFileSync)(filePath, `${JSON.stringify(data, null, options?.spaces ?? 2)}\n`, "utf-8");
|
|
144
|
-
}
|
|
145
|
-
/**
|
|
146
|
-
* Output file - writes content ensuring directory exists
|
|
147
|
-
*/
|
|
148
|
-
async function outputFile(filePath, content) {
|
|
149
|
-
await ensureDir(node_path.default.dirname(filePath));
|
|
150
|
-
await node_fs_promises.writeFile(filePath, content, "utf-8");
|
|
151
|
-
}
|
|
152
138
|
const readFile = node_fs_promises.readFile;
|
|
153
139
|
const writeFile = node_fs_promises.writeFile;
|
|
154
140
|
const readdir = node_fs_promises.readdir;
|
|
@@ -218,8 +204,7 @@ var TemplatesManagerService = class TemplatesManagerService {
|
|
|
218
204
|
* 4. Verify the templates directory exists
|
|
219
205
|
*
|
|
220
206
|
* @param startPath - The path to start searching from (defaults to process.cwd())
|
|
221
|
-
* @returns The absolute path to the templates directory
|
|
222
|
-
* @throws Error if templates directory is not found
|
|
207
|
+
* @returns The absolute path to the templates directory, or null if not found
|
|
223
208
|
*/
|
|
224
209
|
static async findTemplatesPath(startPath = process.cwd()) {
|
|
225
210
|
const workspaceRoot = await TemplatesManagerService.findWorkspaceRoot(startPath);
|
|
@@ -231,12 +216,12 @@ var TemplatesManagerService = class TemplatesManagerService {
|
|
|
231
216
|
if (config?.templatesPath) {
|
|
232
217
|
const templatesPath$1 = node_path.default.isAbsolute(config.templatesPath) ? config.templatesPath : node_path.default.join(workspaceRoot, config.templatesPath);
|
|
233
218
|
if (await pathExists(templatesPath$1)) return templatesPath$1;
|
|
234
|
-
else
|
|
219
|
+
else return null;
|
|
235
220
|
}
|
|
236
221
|
}
|
|
237
222
|
const templatesPath = node_path.default.join(workspaceRoot, TemplatesManagerService.TEMPLATES_FOLDER);
|
|
238
223
|
if (await pathExists(templatesPath)) return templatesPath;
|
|
239
|
-
|
|
224
|
+
return null;
|
|
240
225
|
}
|
|
241
226
|
/**
|
|
242
227
|
* Find the workspace root by searching upwards for .git folder
|
|
@@ -255,8 +240,7 @@ var TemplatesManagerService = class TemplatesManagerService {
|
|
|
255
240
|
* Use this when you need immediate access and are sure templates exist.
|
|
256
241
|
*
|
|
257
242
|
* @param startPath - The path to start searching from (defaults to process.cwd())
|
|
258
|
-
* @returns The absolute path to the templates directory
|
|
259
|
-
* @throws Error if templates directory is not found
|
|
243
|
+
* @returns The absolute path to the templates directory, or null if not found
|
|
260
244
|
*/
|
|
261
245
|
static findTemplatesPathSync(startPath = process.cwd()) {
|
|
262
246
|
const workspaceRoot = TemplatesManagerService.findWorkspaceRootSync(startPath);
|
|
@@ -268,12 +252,12 @@ var TemplatesManagerService = class TemplatesManagerService {
|
|
|
268
252
|
if (config?.templatesPath) {
|
|
269
253
|
const templatesPath$1 = node_path.default.isAbsolute(config.templatesPath) ? config.templatesPath : node_path.default.join(workspaceRoot, config.templatesPath);
|
|
270
254
|
if (pathExistsSync(templatesPath$1)) return templatesPath$1;
|
|
271
|
-
else
|
|
255
|
+
else return null;
|
|
272
256
|
}
|
|
273
257
|
}
|
|
274
258
|
const templatesPath = node_path.default.join(workspaceRoot, TemplatesManagerService.TEMPLATES_FOLDER);
|
|
275
259
|
if (pathExistsSync(templatesPath)) return templatesPath;
|
|
276
|
-
|
|
260
|
+
return null;
|
|
277
261
|
}
|
|
278
262
|
/**
|
|
279
263
|
* Find the workspace root synchronously by searching upwards for .git folder
|
|
@@ -778,6 +762,258 @@ function generateStableId(length = 8) {
|
|
|
778
762
|
return result;
|
|
779
763
|
}
|
|
780
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
|
+
|
|
781
1017
|
//#endregion
|
|
782
1018
|
//#region src/utils/print.ts
|
|
783
1019
|
/**
|
|
@@ -985,26 +1221,6 @@ async function detectProjectType(workspaceRoot) {
|
|
|
985
1221
|
indicators
|
|
986
1222
|
};
|
|
987
1223
|
}
|
|
988
|
-
/**
|
|
989
|
-
* Check if a workspace is a monorepo
|
|
990
|
-
* Convenience function that returns a boolean
|
|
991
|
-
*
|
|
992
|
-
* @param workspaceRoot - Absolute path to the workspace root directory
|
|
993
|
-
* @returns True if workspace is detected as monorepo, false otherwise
|
|
994
|
-
*/
|
|
995
|
-
async function isMonorepo(workspaceRoot) {
|
|
996
|
-
return (await detectProjectType(workspaceRoot)).projectType === ProjectType.MONOREPO;
|
|
997
|
-
}
|
|
998
|
-
/**
|
|
999
|
-
* Check if a workspace is a monolith
|
|
1000
|
-
* Convenience function that returns a boolean
|
|
1001
|
-
*
|
|
1002
|
-
* @param workspaceRoot - Absolute path to the workspace root directory
|
|
1003
|
-
* @returns True if workspace is detected as monolith, false otherwise
|
|
1004
|
-
*/
|
|
1005
|
-
async function isMonolith(workspaceRoot) {
|
|
1006
|
-
return (await detectProjectType(workspaceRoot)).projectType === ProjectType.MONOLITH;
|
|
1007
|
-
}
|
|
1008
1224
|
|
|
1009
1225
|
//#endregion
|
|
1010
1226
|
exports.ConfigSource = ConfigSource;
|
|
@@ -1014,28 +1230,23 @@ exports.ProjectType = ProjectType;
|
|
|
1014
1230
|
exports.ScaffoldProcessingService = ScaffoldProcessingService;
|
|
1015
1231
|
exports.TemplatesManagerService = TemplatesManagerService;
|
|
1016
1232
|
exports.accessSync = node_fs.accessSync;
|
|
1233
|
+
exports.cloneRepository = cloneRepository;
|
|
1234
|
+
exports.cloneSubdirectory = cloneSubdirectory;
|
|
1017
1235
|
exports.copy = copy;
|
|
1018
|
-
exports.cp = cp;
|
|
1019
1236
|
exports.detectProjectType = detectProjectType;
|
|
1020
1237
|
exports.ensureDir = ensureDir;
|
|
1021
|
-
exports.
|
|
1022
|
-
|
|
1023
|
-
enumerable: true,
|
|
1024
|
-
get: function () {
|
|
1025
|
-
return node_fs_promises;
|
|
1026
|
-
}
|
|
1027
|
-
});
|
|
1238
|
+
exports.fetchGitHubDirectoryContents = fetchGitHubDirectoryContents;
|
|
1239
|
+
exports.findWorkspaceRoot = findWorkspaceRoot;
|
|
1028
1240
|
exports.generateStableId = generateStableId;
|
|
1241
|
+
exports.gitInit = gitInit;
|
|
1029
1242
|
exports.icons = icons;
|
|
1030
|
-
exports.isMonolith = isMonolith;
|
|
1031
|
-
exports.isMonorepo = isMonorepo;
|
|
1032
1243
|
exports.log = log;
|
|
1033
1244
|
exports.logger = logger;
|
|
1034
1245
|
exports.messages = messages;
|
|
1035
1246
|
exports.mkdir = mkdir;
|
|
1036
1247
|
exports.mkdirSync = node_fs.mkdirSync;
|
|
1037
1248
|
exports.move = move;
|
|
1038
|
-
exports.
|
|
1249
|
+
exports.parseGitHubUrl = parseGitHubUrl;
|
|
1039
1250
|
exports.pathExists = pathExists;
|
|
1040
1251
|
exports.pathExistsSync = pathExistsSync;
|
|
1041
1252
|
exports.print = print;
|
|
@@ -1045,13 +1256,8 @@ exports.readJson = readJson;
|
|
|
1045
1256
|
exports.readJsonSync = readJsonSync;
|
|
1046
1257
|
exports.readdir = readdir;
|
|
1047
1258
|
exports.remove = remove;
|
|
1048
|
-
exports.rename = rename;
|
|
1049
|
-
exports.rm = rm;
|
|
1050
1259
|
exports.sections = sections;
|
|
1051
1260
|
exports.stat = stat;
|
|
1052
1261
|
exports.statSync = node_fs.statSync;
|
|
1053
|
-
exports.unlink = unlink;
|
|
1054
1262
|
exports.writeFile = writeFile;
|
|
1055
|
-
exports.writeFileSync = node_fs.writeFileSync;
|
|
1056
|
-
exports.writeJson = writeJson;
|
|
1057
|
-
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 };
|