forge-remote 0.1.1 → 0.1.3
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/firestore.rules +100 -0
- package/package.json +5 -4
- package/src/cli.js +8 -5
- package/src/cloudflared-installer.js +163 -0
- package/src/desktop.js +17 -4
- package/src/google-auth.js +436 -0
- package/src/init.js +527 -292
- package/src/project-scanner.js +116 -50
- package/src/session-manager.js +379 -27
- package/src/tunnel-manager.js +226 -33
package/src/project-scanner.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { readdirSync, statSync, existsSync } from "fs";
|
|
1
|
+
import { readdirSync, readFileSync, statSync, existsSync } from "fs";
|
|
2
2
|
import { join, basename } from "path";
|
|
3
3
|
import { homedir } from "os";
|
|
4
|
+
import * as log from "./logger.js";
|
|
4
5
|
|
|
5
|
-
//
|
|
6
|
-
const
|
|
6
|
+
// Default directories to scan for projects.
|
|
7
|
+
const DEFAULT_SCAN_DIRS = [
|
|
7
8
|
join(homedir(), "Documents"),
|
|
8
9
|
join(homedir(), "Projects"),
|
|
9
10
|
join(homedir(), "Developer"),
|
|
@@ -15,6 +16,29 @@ const SCAN_DIRS = [
|
|
|
15
16
|
join(homedir(), "Desktop"),
|
|
16
17
|
];
|
|
17
18
|
|
|
19
|
+
// Maximum depth to recurse into each scan directory.
|
|
20
|
+
const MAX_DEPTH = 4;
|
|
21
|
+
|
|
22
|
+
// Directories to skip during recursive scanning.
|
|
23
|
+
const SKIP_DIRS = new Set([
|
|
24
|
+
"node_modules",
|
|
25
|
+
".git",
|
|
26
|
+
"__pycache__",
|
|
27
|
+
"target",
|
|
28
|
+
"build",
|
|
29
|
+
"dist",
|
|
30
|
+
".next",
|
|
31
|
+
".nuxt",
|
|
32
|
+
"vendor",
|
|
33
|
+
".dart_tool",
|
|
34
|
+
".pub-cache",
|
|
35
|
+
"Pods",
|
|
36
|
+
".gradle",
|
|
37
|
+
"venv",
|
|
38
|
+
".venv",
|
|
39
|
+
"env",
|
|
40
|
+
]);
|
|
41
|
+
|
|
18
42
|
// Files that indicate a directory is a project root.
|
|
19
43
|
const PROJECT_MARKERS = [
|
|
20
44
|
".git",
|
|
@@ -29,8 +53,34 @@ const PROJECT_MARKERS = [
|
|
|
29
53
|
"build.gradle",
|
|
30
54
|
"Makefile",
|
|
31
55
|
"CMakeLists.txt",
|
|
56
|
+
".xcodeproj", // Xcode projects (check for any child matching)
|
|
57
|
+
"Podfile",
|
|
58
|
+
"composer.json",
|
|
59
|
+
"mix.exs",
|
|
60
|
+
"stack.yaml",
|
|
61
|
+
"dune-project",
|
|
32
62
|
];
|
|
33
63
|
|
|
64
|
+
/**
|
|
65
|
+
* Load user-configured extra scan paths from ~/.forge-remote/config.json.
|
|
66
|
+
* The config can contain a "scanPaths" array of absolute directory paths.
|
|
67
|
+
*/
|
|
68
|
+
function loadExtraScanPaths() {
|
|
69
|
+
try {
|
|
70
|
+
const configPath = join(homedir(), ".forge-remote", "config.json");
|
|
71
|
+
if (!existsSync(configPath)) return [];
|
|
72
|
+
const config = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
73
|
+
if (Array.isArray(config.scanPaths)) {
|
|
74
|
+
return config.scanPaths.filter(
|
|
75
|
+
(p) => typeof p === "string" && p.startsWith("/"),
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
} catch {
|
|
79
|
+
// Ignore config parse errors.
|
|
80
|
+
}
|
|
81
|
+
return [];
|
|
82
|
+
}
|
|
83
|
+
|
|
34
84
|
/**
|
|
35
85
|
* Scan common directories for projects.
|
|
36
86
|
* Returns an array of { path, name, lastOpened } objects.
|
|
@@ -39,64 +89,80 @@ export async function scanProjects() {
|
|
|
39
89
|
const projects = [];
|
|
40
90
|
const seen = new Set();
|
|
41
91
|
|
|
42
|
-
|
|
43
|
-
|
|
92
|
+
const extraPaths = loadExtraScanPaths();
|
|
93
|
+
const scanDirs = [...DEFAULT_SCAN_DIRS, ...extraPaths];
|
|
44
94
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
const dirPath = join(scanDir, entry.name);
|
|
52
|
-
if (seen.has(dirPath)) continue;
|
|
53
|
-
|
|
54
|
-
if (isProject(dirPath)) {
|
|
55
|
-
seen.add(dirPath);
|
|
56
|
-
const stat = statSync(dirPath);
|
|
57
|
-
projects.push({
|
|
58
|
-
path: dirPath,
|
|
59
|
-
name: basename(dirPath),
|
|
60
|
-
lastOpened: stat.mtime.toISOString(),
|
|
61
|
-
});
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// Also scan one level deeper for nested project dirs
|
|
65
|
-
// (e.g. ~/Documents/IronForgeApps/Mobile/ForgeRemote).
|
|
66
|
-
try {
|
|
67
|
-
const subEntries = readdirSync(dirPath, { withFileTypes: true });
|
|
68
|
-
for (const sub of subEntries) {
|
|
69
|
-
if (!sub.isDirectory() || sub.name.startsWith(".")) continue;
|
|
70
|
-
const subPath = join(dirPath, sub.name);
|
|
71
|
-
if (seen.has(subPath)) continue;
|
|
72
|
-
|
|
73
|
-
if (isProject(subPath)) {
|
|
74
|
-
seen.add(subPath);
|
|
75
|
-
const stat = statSync(subPath);
|
|
76
|
-
projects.push({
|
|
77
|
-
path: subPath,
|
|
78
|
-
name: basename(subPath),
|
|
79
|
-
lastOpened: stat.mtime.toISOString(),
|
|
80
|
-
});
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
} catch {
|
|
84
|
-
// Skip subdirs we can't read.
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
} catch {
|
|
88
|
-
// Skip dirs we can't read.
|
|
89
|
-
}
|
|
95
|
+
log.info(`Scanning ${scanDirs.length} directories for projects...`);
|
|
96
|
+
|
|
97
|
+
for (const scanDir of scanDirs) {
|
|
98
|
+
if (!existsSync(scanDir)) continue;
|
|
99
|
+
scanRecursive(scanDir, 0, projects, seen);
|
|
90
100
|
}
|
|
91
101
|
|
|
92
102
|
// Sort by lastOpened descending.
|
|
93
103
|
projects.sort((a, b) => b.lastOpened.localeCompare(a.lastOpened));
|
|
94
104
|
|
|
105
|
+
log.info(`Found ${projects.length} projects`);
|
|
95
106
|
return projects;
|
|
96
107
|
}
|
|
97
108
|
|
|
109
|
+
/**
|
|
110
|
+
* Recursively scan a directory for projects up to MAX_DEPTH.
|
|
111
|
+
* When a project is found, it is added to the list and we do NOT recurse
|
|
112
|
+
* into it (a project root's subdirectories are not separate projects).
|
|
113
|
+
*/
|
|
114
|
+
function scanRecursive(dirPath, depth, projects, seen) {
|
|
115
|
+
if (depth > MAX_DEPTH) return;
|
|
116
|
+
|
|
117
|
+
let entries;
|
|
118
|
+
try {
|
|
119
|
+
entries = readdirSync(dirPath, { withFileTypes: true });
|
|
120
|
+
} catch {
|
|
121
|
+
return; // Permission denied or unreadable.
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
for (const entry of entries) {
|
|
125
|
+
if (!entry.isDirectory()) continue;
|
|
126
|
+
const name = entry.name;
|
|
127
|
+
if (name.startsWith(".") && name !== ".git") continue;
|
|
128
|
+
if (SKIP_DIRS.has(name)) continue;
|
|
129
|
+
|
|
130
|
+
const fullPath = join(dirPath, name);
|
|
131
|
+
if (seen.has(fullPath)) continue;
|
|
132
|
+
|
|
133
|
+
if (isProject(fullPath)) {
|
|
134
|
+
seen.add(fullPath);
|
|
135
|
+
try {
|
|
136
|
+
const stat = statSync(fullPath);
|
|
137
|
+
projects.push({
|
|
138
|
+
path: fullPath,
|
|
139
|
+
name: basename(fullPath),
|
|
140
|
+
lastOpened: stat.mtime.toISOString(),
|
|
141
|
+
});
|
|
142
|
+
} catch {
|
|
143
|
+
// stat failed — skip.
|
|
144
|
+
}
|
|
145
|
+
// Don't recurse into project directories.
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Not a project — recurse deeper.
|
|
150
|
+
scanRecursive(fullPath, depth + 1, projects, seen);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
98
154
|
function isProject(dirPath) {
|
|
99
155
|
for (const marker of PROJECT_MARKERS) {
|
|
156
|
+
// For .xcodeproj, check if any child ends with .xcodeproj
|
|
157
|
+
if (marker === ".xcodeproj") {
|
|
158
|
+
try {
|
|
159
|
+
const children = readdirSync(dirPath);
|
|
160
|
+
if (children.some((c) => c.endsWith(".xcodeproj"))) return true;
|
|
161
|
+
} catch {
|
|
162
|
+
// ignore
|
|
163
|
+
}
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
100
166
|
if (existsSync(join(dirPath, marker))) return true;
|
|
101
167
|
}
|
|
102
168
|
return false;
|