spck 0.3.1
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/.oxlintrc.json +49 -0
- package/LICENSE +21 -0
- package/README.md +631 -0
- package/bin/cli.js +20 -0
- package/bin/validate-cwd.js +41 -0
- package/dist/config/__tests__/config.test.d.ts +2 -0
- package/dist/config/__tests__/config.test.js +262 -0
- package/dist/config/__tests__/credentials.test.d.ts +2 -0
- package/dist/config/__tests__/credentials.test.js +360 -0
- package/dist/config/config.d.ts +33 -0
- package/dist/config/config.js +185 -0
- package/dist/config/credentials.d.ts +75 -0
- package/dist/config/credentials.js +259 -0
- package/dist/config/server-selection.d.ts +40 -0
- package/dist/config/server-selection.js +130 -0
- package/dist/connection/__tests__/firebase-auth.test.d.ts +2 -0
- package/dist/connection/__tests__/firebase-auth.test.js +96 -0
- package/dist/connection/__tests__/hmac.test.d.ts +2 -0
- package/dist/connection/__tests__/hmac.test.js +372 -0
- package/dist/connection/auth.d.ts +13 -0
- package/dist/connection/auth.js +91 -0
- package/dist/connection/firebase-auth.d.ts +40 -0
- package/dist/connection/firebase-auth.js +429 -0
- package/dist/connection/hmac.d.ts +24 -0
- package/dist/connection/hmac.js +109 -0
- package/dist/i18n/index.d.ts +25 -0
- package/dist/i18n/index.js +101 -0
- package/dist/i18n/locales/en.json +313 -0
- package/dist/i18n/locales/es.json +302 -0
- package/dist/i18n/locales/fr.json +302 -0
- package/dist/i18n/locales/id.json +302 -0
- package/dist/i18n/locales/ja.json +302 -0
- package/dist/i18n/locales/ko.json +302 -0
- package/dist/i18n/locales/locales/en.json +309 -0
- package/dist/i18n/locales/locales/es.json +302 -0
- package/dist/i18n/locales/locales/fr.json +302 -0
- package/dist/i18n/locales/locales/id.json +302 -0
- package/dist/i18n/locales/locales/ja.json +302 -0
- package/dist/i18n/locales/locales/ko.json +302 -0
- package/dist/i18n/locales/locales/pt.json +302 -0
- package/dist/i18n/locales/locales/zh-Hans.json +302 -0
- package/dist/i18n/locales/pt.json +302 -0
- package/dist/i18n/locales/zh-Hans.json +302 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.js +493 -0
- package/dist/proxy/ProxyClient.d.ts +125 -0
- package/dist/proxy/ProxyClient.js +781 -0
- package/dist/proxy/ProxySocketWrapper.d.ts +43 -0
- package/dist/proxy/ProxySocketWrapper.js +98 -0
- package/dist/proxy/__tests__/ProxyClient.test.d.ts +2 -0
- package/dist/proxy/__tests__/ProxyClient.test.js +445 -0
- package/dist/proxy/__tests__/ProxySocketWrapper.test.d.ts +2 -0
- package/dist/proxy/__tests__/ProxySocketWrapper.test.js +190 -0
- package/dist/proxy/__tests__/handshake-validation.test.d.ts +2 -0
- package/dist/proxy/__tests__/handshake-validation.test.js +282 -0
- package/dist/proxy/__tests__/token-refresh-race.test.d.ts +14 -0
- package/dist/proxy/__tests__/token-refresh-race.test.js +173 -0
- package/dist/proxy/chunking.d.ts +53 -0
- package/dist/proxy/chunking.js +127 -0
- package/dist/proxy/handshake-validation.d.ts +21 -0
- package/dist/proxy/handshake-validation.js +49 -0
- package/dist/rpc/__tests__/router.test.d.ts +2 -0
- package/dist/rpc/__tests__/router.test.js +262 -0
- package/dist/rpc/router.d.ts +37 -0
- package/dist/rpc/router.js +132 -0
- package/dist/services/BrowserProxyService.d.ts +13 -0
- package/dist/services/BrowserProxyService.js +139 -0
- package/dist/services/FilesystemService.d.ts +99 -0
- package/dist/services/FilesystemService.js +742 -0
- package/dist/services/GitService.d.ts +243 -0
- package/dist/services/GitService.js +1439 -0
- package/dist/services/SearchService.d.ts +93 -0
- package/dist/services/SearchService.js +670 -0
- package/dist/services/TerminalService.d.ts +62 -0
- package/dist/services/TerminalService.js +337 -0
- package/dist/services/__tests__/BrowserProxyService.test.d.ts +2 -0
- package/dist/services/__tests__/BrowserProxyService.test.js +145 -0
- package/dist/services/__tests__/FilesystemService.test.d.ts +2 -0
- package/dist/services/__tests__/FilesystemService.test.js +609 -0
- package/dist/services/__tests__/GitService.test.d.ts +2 -0
- package/dist/services/__tests__/GitService.test.js +953 -0
- package/dist/services/__tests__/SearchService.test.d.ts +2 -0
- package/dist/services/__tests__/SearchService.test.js +384 -0
- package/dist/services/__tests__/TerminalService.test.d.ts +2 -0
- package/dist/services/__tests__/TerminalService.test.js +513 -0
- package/dist/setup/wizard.d.ts +10 -0
- package/dist/setup/wizard.js +172 -0
- package/dist/types.d.ts +196 -0
- package/dist/types.js +44 -0
- package/dist/utils/__tests__/gitignore.test.d.ts +2 -0
- package/dist/utils/__tests__/gitignore.test.js +127 -0
- package/dist/utils/gitignore.d.ts +24 -0
- package/dist/utils/gitignore.js +77 -0
- package/dist/utils/logger.d.ts +96 -0
- package/dist/utils/logger.js +456 -0
- package/dist/utils/project-dir.d.ts +51 -0
- package/dist/utils/project-dir.js +191 -0
- package/dist/utils/ripgrep.d.ts +34 -0
- package/dist/utils/ripgrep.js +148 -0
- package/dist/utils/tool-detection.d.ts +17 -0
- package/dist/utils/tool-detection.js +126 -0
- package/dist/watcher/FileWatcher.d.ts +10 -0
- package/dist/watcher/FileWatcher.js +42 -0
- package/package.json +70 -0
- package/src/config/__tests__/config.test.ts +318 -0
- package/src/config/__tests__/credentials.test.ts +494 -0
- package/src/config/config.ts +206 -0
- package/src/config/credentials.ts +302 -0
- package/src/config/server-selection.ts +150 -0
- package/src/connection/__tests__/firebase-auth.test.ts +121 -0
- package/src/connection/__tests__/hmac.test.ts +509 -0
- package/src/connection/auth.ts +140 -0
- package/src/connection/firebase-auth.ts +504 -0
- package/src/connection/hmac.ts +139 -0
- package/src/i18n/index.ts +119 -0
- package/src/i18n/locales/en.json +313 -0
- package/src/i18n/locales/es.json +302 -0
- package/src/i18n/locales/fr.json +302 -0
- package/src/i18n/locales/id.json +302 -0
- package/src/i18n/locales/ja.json +302 -0
- package/src/i18n/locales/ko.json +302 -0
- package/src/i18n/locales/pt.json +302 -0
- package/src/i18n/locales/zh-Hans.json +302 -0
- package/src/index.ts +542 -0
- package/src/proxy/ProxyClient.ts +968 -0
- package/src/proxy/ProxySocketWrapper.ts +113 -0
- package/src/proxy/__tests__/ProxyClient.test.ts +575 -0
- package/src/proxy/__tests__/ProxySocketWrapper.test.ts +251 -0
- package/src/proxy/__tests__/handshake-validation.test.ts +367 -0
- package/src/proxy/chunking.ts +162 -0
- package/src/proxy/handshake-validation.ts +64 -0
- package/src/rpc/__tests__/router.test.ts +400 -0
- package/src/rpc/router.ts +183 -0
- package/src/services/BrowserProxyService.ts +179 -0
- package/src/services/FilesystemService.ts +841 -0
- package/src/services/GitService.ts +1639 -0
- package/src/services/SearchService.ts +809 -0
- package/src/services/TerminalService.ts +413 -0
- package/src/services/__tests__/BrowserProxyService.test.ts +155 -0
- package/src/services/__tests__/FilesystemService.test.ts +1002 -0
- package/src/services/__tests__/GitService.test.ts +1552 -0
- package/src/services/__tests__/SearchService.test.ts +484 -0
- package/src/services/__tests__/TerminalService.test.ts +702 -0
- package/src/setup/wizard.ts +242 -0
- package/src/types/fossil-delta.d.ts +4 -0
- package/src/types.ts +287 -0
- package/src/utils/__tests__/gitignore.test.ts +174 -0
- package/src/utils/gitignore.ts +91 -0
- package/src/utils/logger.ts +578 -0
- package/src/utils/project-dir.ts +218 -0
- package/src/utils/ripgrep.ts +180 -0
- package/src/utils/tool-detection.ts +141 -0
- package/src/watcher/FileWatcher.ts +53 -0
- package/tsconfig.json +24 -0
- package/vitest.config.ts +19 -0
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project directory management with symlink support
|
|
3
|
+
* .spck-editor/ is a regular directory with .spck-editor/config symlinked to ~/.spck-editor/projects/{id}
|
|
4
|
+
* This prevents accidental git commits of secrets while avoiding cross-device link errors
|
|
5
|
+
*/
|
|
6
|
+
import * as fs from 'fs';
|
|
7
|
+
import * as path from 'path';
|
|
8
|
+
import * as os from 'os';
|
|
9
|
+
import * as crypto from 'crypto';
|
|
10
|
+
import { t } from '../i18n/index.js';
|
|
11
|
+
const PROJECT_DIR_NAME = '.spck-editor';
|
|
12
|
+
const CONFIG_SUBDIR_NAME = 'config';
|
|
13
|
+
/**
|
|
14
|
+
* Get the home base directory for projects
|
|
15
|
+
* Called at runtime instead of module load time to avoid issues in test environments
|
|
16
|
+
*/
|
|
17
|
+
function getHomeBaseDir() {
|
|
18
|
+
return path.join(os.homedir(), '.spck-editor', 'projects');
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Generate a consistent project ID from the project root path
|
|
22
|
+
*/
|
|
23
|
+
export function generateProjectId(projectRoot) {
|
|
24
|
+
// Resolve to absolute path and normalize
|
|
25
|
+
const absolutePath = path.resolve(projectRoot);
|
|
26
|
+
// Generate SHA256 hash of the absolute path
|
|
27
|
+
const hash = crypto.createHash('sha256').update(absolutePath).digest('hex');
|
|
28
|
+
// Use first 16 characters for readability (still unique enough)
|
|
29
|
+
return hash.substring(0, 16);
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Get the home directory location for a project's data
|
|
33
|
+
*/
|
|
34
|
+
export function getProjectDataPath(projectRoot) {
|
|
35
|
+
const projectId = generateProjectId(projectRoot);
|
|
36
|
+
return path.join(getHomeBaseDir(), projectId);
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Get the .spck-editor directory path in the project
|
|
40
|
+
*/
|
|
41
|
+
export function getProjectDirPath(projectRoot) {
|
|
42
|
+
return path.join(projectRoot, PROJECT_DIR_NAME);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Get the config symlink path (.spck-editor/config)
|
|
46
|
+
*/
|
|
47
|
+
export function getConfigSymlinkPath(projectRoot) {
|
|
48
|
+
return path.join(projectRoot, PROJECT_DIR_NAME, CONFIG_SUBDIR_NAME);
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* @deprecated Use getConfigSymlinkPath() instead
|
|
52
|
+
* Legacy compatibility - returns config symlink path
|
|
53
|
+
*/
|
|
54
|
+
export function getProjectSymlinkPath(projectRoot) {
|
|
55
|
+
return getConfigSymlinkPath(projectRoot);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Check if the project directory exists and is properly set up
|
|
59
|
+
*/
|
|
60
|
+
export function isProjectDirSetup(projectRoot) {
|
|
61
|
+
const projectDir = getProjectDirPath(projectRoot);
|
|
62
|
+
const configSymlink = getConfigSymlinkPath(projectRoot);
|
|
63
|
+
const dataPath = getProjectDataPath(projectRoot);
|
|
64
|
+
// Check if .spck-editor directory exists
|
|
65
|
+
if (!fs.existsSync(projectDir)) {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
// Check if .spck-editor is a directory (not old-style symlink)
|
|
69
|
+
try {
|
|
70
|
+
const stats = fs.lstatSync(projectDir);
|
|
71
|
+
if (stats.isSymbolicLink()) {
|
|
72
|
+
// Old structure - needs migration
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
if (!stats.isDirectory()) {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
// Check if config symlink exists
|
|
83
|
+
if (!fs.existsSync(configSymlink)) {
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
// Check if config is a symlink
|
|
87
|
+
try {
|
|
88
|
+
const stats = fs.lstatSync(configSymlink);
|
|
89
|
+
if (!stats.isSymbolicLink()) {
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
// Check if data directory exists
|
|
97
|
+
if (!fs.existsSync(dataPath)) {
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Setup the project directory with config symlink
|
|
104
|
+
* Creates ~/.spck-editor/projects/{project_id}/ and symlinks .spck-editor/config to it
|
|
105
|
+
*/
|
|
106
|
+
export function setupProjectDir(projectRoot) {
|
|
107
|
+
const projectDir = getProjectDirPath(projectRoot);
|
|
108
|
+
const configSymlink = getConfigSymlinkPath(projectRoot);
|
|
109
|
+
const dataPath = getProjectDataPath(projectRoot);
|
|
110
|
+
const homeBaseDir = getHomeBaseDir();
|
|
111
|
+
// Ensure home base directory exists
|
|
112
|
+
if (!fs.existsSync(homeBaseDir)) {
|
|
113
|
+
fs.mkdirSync(homeBaseDir, { recursive: true, mode: 0o700 });
|
|
114
|
+
}
|
|
115
|
+
// Ensure project data directory exists
|
|
116
|
+
if (!fs.existsSync(dataPath)) {
|
|
117
|
+
fs.mkdirSync(dataPath, { recursive: true, mode: 0o700 });
|
|
118
|
+
}
|
|
119
|
+
// Create .spck-editor directory if it doesn't exist
|
|
120
|
+
if (!fs.existsSync(projectDir)) {
|
|
121
|
+
fs.mkdirSync(projectDir, { mode: 0o700 });
|
|
122
|
+
}
|
|
123
|
+
// Create subdirectories (.tmp, .trash, logs)
|
|
124
|
+
const subdirs = ['.tmp', '.trash', 'logs'];
|
|
125
|
+
for (const subdir of subdirs) {
|
|
126
|
+
const subdirPath = path.join(projectDir, subdir);
|
|
127
|
+
if (!fs.existsSync(subdirPath)) {
|
|
128
|
+
fs.mkdirSync(subdirPath, { mode: 0o700 });
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
// Create config symlink if it doesn't exist
|
|
132
|
+
if (!fs.existsSync(configSymlink)) {
|
|
133
|
+
fs.symlinkSync(dataPath, configSymlink, 'dir');
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
// Verify existing symlink points to correct location
|
|
137
|
+
const stats = fs.lstatSync(configSymlink);
|
|
138
|
+
if (stats.isSymbolicLink()) {
|
|
139
|
+
const target = fs.readlinkSync(configSymlink);
|
|
140
|
+
if (target !== dataPath) {
|
|
141
|
+
// Points to wrong location - remove and recreate
|
|
142
|
+
fs.unlinkSync(configSymlink);
|
|
143
|
+
fs.symlinkSync(dataPath, configSymlink, 'dir');
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
console.log(`✅ ${t('projectDir.configured')}`);
|
|
148
|
+
console.log(` ${t('projectDir.directory', { path: projectDir })}`);
|
|
149
|
+
console.log(` ${t('projectDir.configLink', { symlink: configSymlink, dataPath })}\n`);
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Ensure project directory is set up, creating if needed
|
|
153
|
+
*/
|
|
154
|
+
export function ensureProjectDir(projectRoot) {
|
|
155
|
+
if (!isProjectDirSetup(projectRoot)) {
|
|
156
|
+
setupProjectDir(projectRoot);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Get the absolute path to a file within the project directory
|
|
161
|
+
* Files go in .spck-editor/{filename} (local) or .spck-editor/config/{filename} (symlinked)
|
|
162
|
+
* Config files (like connection-settings.json) go in the config subdirectory
|
|
163
|
+
*/
|
|
164
|
+
export function getProjectFilePath(projectRoot, filename) {
|
|
165
|
+
const projectDir = getProjectDirPath(projectRoot);
|
|
166
|
+
// Config files go in the symlinked config directory
|
|
167
|
+
const configFiles = ['connection-settings.json', '.credentials.json', 'spck-cli.config.json'];
|
|
168
|
+
if (configFiles.includes(filename)) {
|
|
169
|
+
const configSymlink = getConfigSymlinkPath(projectRoot);
|
|
170
|
+
return path.join(configSymlink, filename);
|
|
171
|
+
}
|
|
172
|
+
// All other files go in the local .spck-editor directory
|
|
173
|
+
return path.join(projectDir, filename);
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Remove project directory and all associated data
|
|
177
|
+
* WARNING: This deletes the data directory in home and the local .spck-editor directory
|
|
178
|
+
*/
|
|
179
|
+
export function removeProjectDir(projectRoot) {
|
|
180
|
+
const projectDir = getProjectDirPath(projectRoot);
|
|
181
|
+
const dataPath = getProjectDataPath(projectRoot);
|
|
182
|
+
// Remove .spck-editor directory if exists (includes config symlink and local files)
|
|
183
|
+
if (fs.existsSync(projectDir)) {
|
|
184
|
+
fs.rmSync(projectDir, { recursive: true, force: true });
|
|
185
|
+
}
|
|
186
|
+
// Remove data directory in home if exists
|
|
187
|
+
if (fs.existsSync(dataPath)) {
|
|
188
|
+
fs.rmSync(dataPath, { recursive: true, force: true });
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
//# sourceMappingURL=project-dir.js.map
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ripgrep wrapper utility
|
|
3
|
+
*
|
|
4
|
+
* Uses system-installed ripgrep (rg) if available
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Get the ripgrep command name for the current platform
|
|
8
|
+
*/
|
|
9
|
+
export declare function getRipgrepCommand(): string;
|
|
10
|
+
/**
|
|
11
|
+
* Check if ripgrep is available on the system PATH
|
|
12
|
+
*/
|
|
13
|
+
export declare function isRipgrepAvailable(): Promise<boolean>;
|
|
14
|
+
/**
|
|
15
|
+
* Execute ripgrep search (buffered - waits for completion)
|
|
16
|
+
*/
|
|
17
|
+
export declare function executeRipgrep(path: string, args: string[], options?: {
|
|
18
|
+
timeout?: number;
|
|
19
|
+
}): Promise<{
|
|
20
|
+
stdout: string;
|
|
21
|
+
stderr: string;
|
|
22
|
+
exitCode: number;
|
|
23
|
+
}>;
|
|
24
|
+
/**
|
|
25
|
+
* Execute ripgrep search with streaming output
|
|
26
|
+
* Calls onLine for each line of output as it arrives
|
|
27
|
+
*/
|
|
28
|
+
export declare function executeRipgrepStream(path: string, args: string[], options: {
|
|
29
|
+
timeout?: number;
|
|
30
|
+
onLine: (line: string) => void;
|
|
31
|
+
}): Promise<{
|
|
32
|
+
exitCode: number;
|
|
33
|
+
}>;
|
|
34
|
+
//# sourceMappingURL=ripgrep.d.ts.map
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ripgrep wrapper utility
|
|
3
|
+
*
|
|
4
|
+
* Uses system-installed ripgrep (rg) if available
|
|
5
|
+
*/
|
|
6
|
+
import { spawn } from 'child_process';
|
|
7
|
+
/**
|
|
8
|
+
* Get the ripgrep command name for the current platform
|
|
9
|
+
*/
|
|
10
|
+
export function getRipgrepCommand() {
|
|
11
|
+
// On Windows, try both 'rg.exe' and 'rg'
|
|
12
|
+
// On Unix-like systems, use 'rg'
|
|
13
|
+
return process.platform === 'win32' ? 'rg.exe' : 'rg';
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Check if ripgrep is available on the system PATH
|
|
17
|
+
*/
|
|
18
|
+
export async function isRipgrepAvailable() {
|
|
19
|
+
const rgCommand = getRipgrepCommand();
|
|
20
|
+
// Try to execute ripgrep --version to verify it works
|
|
21
|
+
return new Promise((resolve) => {
|
|
22
|
+
try {
|
|
23
|
+
const proc = spawn(rgCommand, ['--version'], {
|
|
24
|
+
cwd: process.cwd(),
|
|
25
|
+
stdio: 'ignore',
|
|
26
|
+
shell: false
|
|
27
|
+
});
|
|
28
|
+
proc.on('error', () => resolve(false));
|
|
29
|
+
proc.on('close', (code) => resolve(code === 0));
|
|
30
|
+
}
|
|
31
|
+
catch (err) {
|
|
32
|
+
resolve(false);
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Execute ripgrep search (buffered - waits for completion)
|
|
38
|
+
*/
|
|
39
|
+
export async function executeRipgrep(path, args, options) {
|
|
40
|
+
const rgCommand = getRipgrepCommand();
|
|
41
|
+
return new Promise((resolve, reject) => {
|
|
42
|
+
const proc = spawn(rgCommand, args, {
|
|
43
|
+
cwd: path,
|
|
44
|
+
stdio: 'pipe',
|
|
45
|
+
shell: false
|
|
46
|
+
});
|
|
47
|
+
let stdout = '';
|
|
48
|
+
let stderr = '';
|
|
49
|
+
let killed = false;
|
|
50
|
+
// Set timeout if specified
|
|
51
|
+
const timeout = options?.timeout;
|
|
52
|
+
let timeoutId;
|
|
53
|
+
if (timeout) {
|
|
54
|
+
timeoutId = setTimeout(() => {
|
|
55
|
+
killed = true;
|
|
56
|
+
proc.kill();
|
|
57
|
+
reject(new Error(`Ripgrep execution timed out after ${timeout}ms`));
|
|
58
|
+
}, timeout);
|
|
59
|
+
}
|
|
60
|
+
proc.stdout?.on('data', (data) => {
|
|
61
|
+
stdout += data.toString();
|
|
62
|
+
});
|
|
63
|
+
proc.stderr?.on('data', (data) => {
|
|
64
|
+
stderr += data.toString();
|
|
65
|
+
});
|
|
66
|
+
proc.on('error', (error) => {
|
|
67
|
+
if (timeoutId)
|
|
68
|
+
clearTimeout(timeoutId);
|
|
69
|
+
if (!killed) {
|
|
70
|
+
reject(error);
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
proc.on('close', (code) => {
|
|
74
|
+
if (timeoutId)
|
|
75
|
+
clearTimeout(timeoutId);
|
|
76
|
+
if (!killed) {
|
|
77
|
+
resolve({
|
|
78
|
+
stdout,
|
|
79
|
+
stderr,
|
|
80
|
+
exitCode: code ?? -1
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Execute ripgrep search with streaming output
|
|
88
|
+
* Calls onLine for each line of output as it arrives
|
|
89
|
+
*/
|
|
90
|
+
export async function executeRipgrepStream(path, args, options) {
|
|
91
|
+
const rgCommand = getRipgrepCommand();
|
|
92
|
+
return new Promise((resolve, reject) => {
|
|
93
|
+
const proc = spawn(rgCommand, args, {
|
|
94
|
+
cwd: path,
|
|
95
|
+
stdio: 'pipe',
|
|
96
|
+
shell: false
|
|
97
|
+
});
|
|
98
|
+
let killed = false;
|
|
99
|
+
let buffer = '';
|
|
100
|
+
// Set timeout if specified
|
|
101
|
+
const timeout = options.timeout;
|
|
102
|
+
let timeoutId;
|
|
103
|
+
if (timeout) {
|
|
104
|
+
timeoutId = setTimeout(() => {
|
|
105
|
+
killed = true;
|
|
106
|
+
proc.kill();
|
|
107
|
+
reject(new Error(`Ripgrep execution timed out after ${timeout}ms`));
|
|
108
|
+
}, timeout);
|
|
109
|
+
}
|
|
110
|
+
// Process stdout line by line as it arrives
|
|
111
|
+
proc.stdout?.on('data', (data) => {
|
|
112
|
+
buffer += data.toString();
|
|
113
|
+
const lines = buffer.split('\n');
|
|
114
|
+
// Keep the last incomplete line in the buffer
|
|
115
|
+
buffer = lines.pop() || '';
|
|
116
|
+
// Process complete lines
|
|
117
|
+
for (const line of lines) {
|
|
118
|
+
if (line.trim()) {
|
|
119
|
+
options.onLine(line);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
proc.stderr?.on('data', (_data) => {
|
|
124
|
+
// Ignore stderr for now
|
|
125
|
+
});
|
|
126
|
+
proc.on('error', (error) => {
|
|
127
|
+
if (timeoutId)
|
|
128
|
+
clearTimeout(timeoutId);
|
|
129
|
+
if (!killed) {
|
|
130
|
+
reject(error);
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
proc.on('close', (code) => {
|
|
134
|
+
if (timeoutId)
|
|
135
|
+
clearTimeout(timeoutId);
|
|
136
|
+
// Process any remaining buffered data
|
|
137
|
+
if (buffer.trim()) {
|
|
138
|
+
options.onLine(buffer);
|
|
139
|
+
}
|
|
140
|
+
if (!killed) {
|
|
141
|
+
resolve({
|
|
142
|
+
exitCode: code ?? -1
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
//# sourceMappingURL=ripgrep.js.map
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool detection for git and ripgrep
|
|
3
|
+
* Checks if required tools are installed and displays warnings
|
|
4
|
+
*/
|
|
5
|
+
import { ToolDetectionResult } from '../types.js';
|
|
6
|
+
/**
|
|
7
|
+
* Detect available tools (git and ripgrep)
|
|
8
|
+
*/
|
|
9
|
+
export declare function detectTools(options?: {
|
|
10
|
+
disableGit?: boolean;
|
|
11
|
+
disableRipgrep?: boolean;
|
|
12
|
+
}): Promise<ToolDetectionResult>;
|
|
13
|
+
/**
|
|
14
|
+
* Display feature summary based on detected tools
|
|
15
|
+
*/
|
|
16
|
+
export declare function displayFeatureSummary(tools: ToolDetectionResult, terminalEnabled: boolean, userAuthEnabled?: boolean, browserProxyEnabled?: boolean): void;
|
|
17
|
+
//# sourceMappingURL=tool-detection.d.ts.map
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool detection for git and ripgrep
|
|
3
|
+
* Checks if required tools are installed and displays warnings
|
|
4
|
+
*/
|
|
5
|
+
import { exec } from 'child_process';
|
|
6
|
+
import { promisify } from 'util';
|
|
7
|
+
import { t } from '../i18n/index.js';
|
|
8
|
+
const execAsync = promisify(exec);
|
|
9
|
+
/**
|
|
10
|
+
* Check if a command is available
|
|
11
|
+
*/
|
|
12
|
+
async function isCommandAvailable(command) {
|
|
13
|
+
try {
|
|
14
|
+
await execAsync(`${command} --version`);
|
|
15
|
+
return true;
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Detect available tools (git and ripgrep)
|
|
23
|
+
*/
|
|
24
|
+
export async function detectTools(options) {
|
|
25
|
+
console.log(`\n=== ${t('tools.title')} ===\n`);
|
|
26
|
+
const result = {
|
|
27
|
+
git: false,
|
|
28
|
+
ripgrep: false
|
|
29
|
+
};
|
|
30
|
+
// Check Git (unless force-disabled for development)
|
|
31
|
+
if (options?.disableGit) {
|
|
32
|
+
console.log(`⚠️ ${t('tools.gitForceDisabled')}`);
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
result.git = await isCommandAvailable('git');
|
|
36
|
+
if (result.git) {
|
|
37
|
+
try {
|
|
38
|
+
const { stdout } = await execAsync('git --version');
|
|
39
|
+
console.log(`✅ ${t('tools.gitDetected', { version: stdout.trim() })}`);
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
console.log(`✅ ${t('tools.gitDetectedShort')}`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
console.warn(`⚠️ ${t('tools.gitNotDetected')}`);
|
|
47
|
+
console.warn(` ${t('tools.gitDisabledHint')}`);
|
|
48
|
+
console.warn(` ${t('tools.gitInstallHint')}`);
|
|
49
|
+
console.warn(` ${t('tools.gitInstallUrl')}\n`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
// Check Ripgrep (unless force-disabled for development)
|
|
53
|
+
if (options?.disableRipgrep) {
|
|
54
|
+
console.log(`⚠️ ${t('tools.ripgrepForceDisabled')}`);
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
result.ripgrep = await isCommandAvailable('rg');
|
|
58
|
+
if (result.ripgrep) {
|
|
59
|
+
try {
|
|
60
|
+
const { stdout } = await execAsync('rg --version');
|
|
61
|
+
const firstLine = stdout.split('\n')[0];
|
|
62
|
+
console.log(`✅ ${t('tools.ripgrepDetected', { version: firstLine })}`);
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
console.log(`✅ ${t('tools.ripgrepDetectedShort')}`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
console.warn(`⚠️ ${t('tools.ripgrepNotDetected')}`);
|
|
70
|
+
console.warn(` ${t('tools.ripgrepDisabledHint')}`);
|
|
71
|
+
console.warn(` ${t('tools.ripgrepInstallHint')}`);
|
|
72
|
+
console.warn(` ${t('tools.ripgrepInstallUrl')}\n`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return result;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Display feature summary based on detected tools
|
|
79
|
+
*/
|
|
80
|
+
export function displayFeatureSummary(tools, terminalEnabled, userAuthEnabled, browserProxyEnabled) {
|
|
81
|
+
console.log(`\n=== ${t('features.title')} ===\n`);
|
|
82
|
+
const features = [];
|
|
83
|
+
// Always available
|
|
84
|
+
features.push(`✅ ${t('features.filesystem')}`);
|
|
85
|
+
// Conditional features
|
|
86
|
+
if (tools.git) {
|
|
87
|
+
features.push(`✅ ${t('features.gitEnabled')}`);
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
features.push(`❌ ${t('features.gitDisabled')}`);
|
|
91
|
+
}
|
|
92
|
+
if (tools.ripgrep) {
|
|
93
|
+
features.push(`✅ ${t('features.searchFast')}`);
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
features.push(`⚠️ ${t('features.searchBasic')}`);
|
|
97
|
+
}
|
|
98
|
+
if (terminalEnabled) {
|
|
99
|
+
features.push(`✅ ${t('features.terminalEnabled')}`);
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
features.push(`❌ ${t('features.terminalDisabled')}`);
|
|
103
|
+
}
|
|
104
|
+
if (browserProxyEnabled !== false) {
|
|
105
|
+
features.push(`✅ ${t('features.browserProxyEnabled')}`);
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
features.push(`❌ ${t('features.browserProxyDisabled')}`);
|
|
109
|
+
}
|
|
110
|
+
features.forEach(feature => console.log(` ${feature}`));
|
|
111
|
+
// Display authentication mode
|
|
112
|
+
console.log(`\n=== ${t('features.securityTitle')} ===\n`);
|
|
113
|
+
if (userAuthEnabled) {
|
|
114
|
+
console.log(` 🔐 ${t('features.userAuthEnabled')}`);
|
|
115
|
+
console.log(` → ${t('features.userAuthEnabledHint1')}`);
|
|
116
|
+
console.log(` → ${t('features.userAuthEnabledHint2')}`);
|
|
117
|
+
console.log(` → ${t('features.userAuthEnabledHint3')}\n`);
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
console.log(` 🔓 ${t('features.userAuthDisabled')}`);
|
|
121
|
+
console.log(` → ${t('features.userAuthDisabledHint1')}`);
|
|
122
|
+
console.log(` → ${t('features.userAuthDisabledHint2')}`);
|
|
123
|
+
console.log(` → ${t('features.userAuthDisabledHint3')}\n`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
//# sourceMappingURL=tool-detection.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File system watcher using chokidar
|
|
3
|
+
*/
|
|
4
|
+
import { EventEmitter } from 'events';
|
|
5
|
+
export declare class FileWatcher extends EventEmitter {
|
|
6
|
+
private watcher;
|
|
7
|
+
constructor(rootPath: string, ignorePatterns: string[]);
|
|
8
|
+
close(): void;
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=FileWatcher.d.ts.map
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File system watcher using chokidar
|
|
3
|
+
*/
|
|
4
|
+
import * as chokidar from 'chokidar';
|
|
5
|
+
import { EventEmitter } from 'events';
|
|
6
|
+
export class FileWatcher extends EventEmitter {
|
|
7
|
+
constructor(rootPath, ignorePatterns) {
|
|
8
|
+
super();
|
|
9
|
+
this.watcher = chokidar.watch(rootPath, {
|
|
10
|
+
ignored: ignorePatterns,
|
|
11
|
+
persistent: true,
|
|
12
|
+
ignoreInitial: true,
|
|
13
|
+
awaitWriteFinish: {
|
|
14
|
+
stabilityThreshold: 100,
|
|
15
|
+
pollInterval: 50,
|
|
16
|
+
},
|
|
17
|
+
});
|
|
18
|
+
this.watcher.on('change', (path, stats) => {
|
|
19
|
+
this.emit('change', path, stats?.mtimeMs);
|
|
20
|
+
});
|
|
21
|
+
this.watcher.on('unlink', (path) => {
|
|
22
|
+
this.emit('removed', path);
|
|
23
|
+
});
|
|
24
|
+
this.watcher.on('add', (path, stats) => {
|
|
25
|
+
this.emit('added', path, stats?.mtimeMs);
|
|
26
|
+
});
|
|
27
|
+
this.watcher.on('unlinkDir', (path) => {
|
|
28
|
+
this.emit('removed', path);
|
|
29
|
+
});
|
|
30
|
+
this.watcher.on('addDir', (path) => {
|
|
31
|
+
this.emit('added', path);
|
|
32
|
+
});
|
|
33
|
+
this.watcher.on('error', (error) => {
|
|
34
|
+
console.error('File watcher error:', error);
|
|
35
|
+
this.emit('error', error);
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
close() {
|
|
39
|
+
this.watcher.close();
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=FileWatcher.js.map
|
package/package.json
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "spck",
|
|
3
|
+
"version": "0.3.1",
|
|
4
|
+
"description": "CLI tool for Spck Editor - provides remote filesystem, git, browser proxing, and terminal access",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"spck": "./bin/cli.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc && cp src/i18n/locales/*.json dist/i18n/locales/",
|
|
12
|
+
"start": "node dist/index.js",
|
|
13
|
+
"dev": "ts-node src/index.ts",
|
|
14
|
+
"watch": "tsc --watch",
|
|
15
|
+
"test": "vitest run",
|
|
16
|
+
"test:watch": "vitest",
|
|
17
|
+
"test:coverage": "vitest run --coverage",
|
|
18
|
+
"lint": "oxlint",
|
|
19
|
+
"lint:fix": "oxlint --fix",
|
|
20
|
+
"prepublishOnly": "npm run lint && npm test",
|
|
21
|
+
"prepare": "npm run build"
|
|
22
|
+
},
|
|
23
|
+
"keywords": [
|
|
24
|
+
"spck",
|
|
25
|
+
"editor",
|
|
26
|
+
"remote",
|
|
27
|
+
"filesystem",
|
|
28
|
+
"git",
|
|
29
|
+
"terminal"
|
|
30
|
+
],
|
|
31
|
+
"repository": {
|
|
32
|
+
"type": "git",
|
|
33
|
+
"url": "https://github.com/spck-io/spck-cli"
|
|
34
|
+
},
|
|
35
|
+
"author": "Spck Editor",
|
|
36
|
+
"license": "MIT",
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"@xterm/addon-serialize": "0.13.0",
|
|
39
|
+
"@xterm/headless": "5.5.0",
|
|
40
|
+
"chalk": "4.1.2",
|
|
41
|
+
"chokidar": "3.6.0",
|
|
42
|
+
"default-shell": "2.2.0",
|
|
43
|
+
"fossil-delta": "1.0.2",
|
|
44
|
+
"jsonwebtoken": "9.0.3",
|
|
45
|
+
"node-fetch": "3.3.2",
|
|
46
|
+
"node-pty": "1.2.0-beta.10",
|
|
47
|
+
"open": "8.4.2",
|
|
48
|
+
"os-locale": "8.0.0",
|
|
49
|
+
"qrcode-terminal": "0.12.0",
|
|
50
|
+
"socket.io-client": "4.8.3",
|
|
51
|
+
"write-file-atomic": "7.0.0",
|
|
52
|
+
"yargs": "17.7.2"
|
|
53
|
+
},
|
|
54
|
+
"devDependencies": {
|
|
55
|
+
"@types/jsonwebtoken": "^9.0.5",
|
|
56
|
+
"@types/node": "^20.0.0",
|
|
57
|
+
"@types/node-fetch": "^2.6.11",
|
|
58
|
+
"@types/qrcode-terminal": "^0.12.2",
|
|
59
|
+
"@types/socket.io-client": "^3.0.0",
|
|
60
|
+
"@types/write-file-atomic": "^4.0.3",
|
|
61
|
+
"@types/ws": "^8.5.10",
|
|
62
|
+
"@types/yargs": "^17.0.32",
|
|
63
|
+
"ts-node": "^10.9.2",
|
|
64
|
+
"vitest": "^3.0.0",
|
|
65
|
+
"typescript": "^5.3.0"
|
|
66
|
+
},
|
|
67
|
+
"engines": {
|
|
68
|
+
"node": ">=18.0.0"
|
|
69
|
+
}
|
|
70
|
+
}
|