@pixelbyte-software/pixcode 1.47.0 → 1.47.2
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/assets/index-BBdWwJi6.css +32 -0
- package/dist/assets/{index-UgtuipzF.js → index-CR14oRxl.js} +209 -193
- package/dist/index.html +2 -2
- package/dist-server/server/index.js +8 -4
- package/dist-server/server/index.js.map +1 -1
- package/dist-server/server/projects.js +65 -0
- package/dist-server/server/projects.js.map +1 -1
- package/package.json +1 -1
- package/scripts/smoke/code-editor-theme.mjs +55 -0
- package/scripts/smoke/git-install-update.mjs +141 -0
- package/scripts/smoke/vscode-workbench-polish.mjs +107 -0
- package/scripts/update-git-install.mjs +128 -0
- package/server/index.js +8 -4
- package/server/projects.js +72 -0
- package/dist/assets/index-CFPtm6sP.css +0 -32
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { spawnSync } from 'node:child_process';
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import os from 'node:os';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
|
|
7
|
+
const serverIndex = fs.readFileSync('server/index.js', 'utf8');
|
|
8
|
+
const modal = fs.readFileSync('src/components/version-upgrade/view/VersionUpgradeModal.tsx', 'utf8');
|
|
9
|
+
const updater = fs.readFileSync('scripts/update-git-install.mjs', 'utf8');
|
|
10
|
+
const updaterPath = path.resolve('scripts/update-git-install.mjs');
|
|
11
|
+
|
|
12
|
+
assert.match(
|
|
13
|
+
serverIndex,
|
|
14
|
+
/update-git-install\.mjs/,
|
|
15
|
+
'Git install updates should use the safe updater script instead of raw git pull.',
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
assert.doesNotMatch(
|
|
19
|
+
serverIndex,
|
|
20
|
+
/git checkout main && git pull && npm install/,
|
|
21
|
+
'Server update command should not use the brittle raw git checkout/pull/install chain.',
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
assert.match(
|
|
25
|
+
modal,
|
|
26
|
+
/node scripts\/update-git-install\.mjs/,
|
|
27
|
+
'Version modal should show the safe git updater command.',
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
assert.match(
|
|
31
|
+
updater,
|
|
32
|
+
/stash[\s\S]*push[\s\S]*--include-untracked[\s\S]*--message/,
|
|
33
|
+
'Safe updater should stash dirty tracked and untracked files before updating.',
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
assert.match(
|
|
37
|
+
updater,
|
|
38
|
+
/branch[\s\S]*backupBranch/,
|
|
39
|
+
'Safe updater should preserve divergent local commits in a backup branch.',
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
assert.match(
|
|
43
|
+
updater,
|
|
44
|
+
/reset[\s\S]*--hard[\s\S]*origin\/main/,
|
|
45
|
+
'Safe updater should be able to normalize a divergent install checkout after preserving it.',
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
assert.match(
|
|
49
|
+
updater,
|
|
50
|
+
/npm[\s\S]*install[\s\S]*--no-audit[\s\S]*--no-fund/,
|
|
51
|
+
'Safe updater should reinstall dependencies after updating source files.',
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'pixcode-git-update-'));
|
|
55
|
+
const origin = path.join(tempRoot, 'origin.git');
|
|
56
|
+
const source = path.join(tempRoot, 'source');
|
|
57
|
+
const install = path.join(tempRoot, 'install');
|
|
58
|
+
|
|
59
|
+
function run(command, args, cwd) {
|
|
60
|
+
const result = spawnSync(command, args, {
|
|
61
|
+
cwd,
|
|
62
|
+
encoding: 'utf8',
|
|
63
|
+
env: {
|
|
64
|
+
...process.env,
|
|
65
|
+
GIT_AUTHOR_NAME: 'Pixcode Smoke',
|
|
66
|
+
GIT_AUTHOR_EMAIL: 'smoke@pixcode.local',
|
|
67
|
+
GIT_COMMITTER_NAME: 'Pixcode Smoke',
|
|
68
|
+
GIT_COMMITTER_EMAIL: 'smoke@pixcode.local',
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
assert.equal(
|
|
73
|
+
result.status,
|
|
74
|
+
0,
|
|
75
|
+
`${command} ${args.join(' ')} failed\nstdout:\n${result.stdout}\nstderr:\n${result.stderr}`,
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
return result.stdout.trim();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function writePackage(version) {
|
|
82
|
+
fs.writeFileSync(
|
|
83
|
+
path.join(source, 'package.json'),
|
|
84
|
+
JSON.stringify({ name: 'pixcode-update-smoke', version }, null, 2),
|
|
85
|
+
);
|
|
86
|
+
fs.writeFileSync(
|
|
87
|
+
path.join(source, 'package-lock.json'),
|
|
88
|
+
JSON.stringify({
|
|
89
|
+
name: 'pixcode-update-smoke',
|
|
90
|
+
version,
|
|
91
|
+
lockfileVersion: 3,
|
|
92
|
+
requires: true,
|
|
93
|
+
packages: {
|
|
94
|
+
'': {
|
|
95
|
+
name: 'pixcode-update-smoke',
|
|
96
|
+
version,
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
}, null, 2),
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
fs.mkdirSync(source, { recursive: true });
|
|
104
|
+
run('git', ['init', '--bare', origin], tempRoot);
|
|
105
|
+
run('git', ['init', '-b', 'main'], source);
|
|
106
|
+
writePackage('1.0.0');
|
|
107
|
+
fs.writeFileSync(path.join(source, 'tracked.txt'), 'old\n');
|
|
108
|
+
run('git', ['add', '.'], source);
|
|
109
|
+
run('git', ['commit', '-m', 'initial'], source);
|
|
110
|
+
run('git', ['remote', 'add', 'origin', origin], source);
|
|
111
|
+
run('git', ['push', '-u', 'origin', 'main'], source);
|
|
112
|
+
run('git', ['symbolic-ref', 'HEAD', 'refs/heads/main'], origin);
|
|
113
|
+
run('git', ['clone', origin, install], tempRoot);
|
|
114
|
+
|
|
115
|
+
writePackage('1.0.1');
|
|
116
|
+
fs.writeFileSync(path.join(source, 'tracked.txt'), 'new\n');
|
|
117
|
+
run('git', ['add', '.'], source);
|
|
118
|
+
run('git', ['commit', '-m', 'update'], source);
|
|
119
|
+
run('git', ['push', 'origin', 'main'], source);
|
|
120
|
+
|
|
121
|
+
fs.writeFileSync(path.join(install, 'tracked.txt'), 'local dirty change\n');
|
|
122
|
+
fs.writeFileSync(path.join(install, 'untracked.txt'), 'local untracked change\n');
|
|
123
|
+
run(process.execPath, [updaterPath], install);
|
|
124
|
+
|
|
125
|
+
assert.equal(
|
|
126
|
+
JSON.parse(fs.readFileSync(path.join(install, 'package.json'), 'utf8')).version,
|
|
127
|
+
'1.0.1',
|
|
128
|
+
'Safe updater should fast-forward the install checkout.',
|
|
129
|
+
);
|
|
130
|
+
assert.equal(
|
|
131
|
+
fs.readFileSync(path.join(install, 'tracked.txt'), 'utf8'),
|
|
132
|
+
'new\n',
|
|
133
|
+
'Safe updater should apply the remote tracked file after stashing local edits.',
|
|
134
|
+
);
|
|
135
|
+
assert.match(
|
|
136
|
+
run('git', ['stash', 'list'], install),
|
|
137
|
+
/pixcode-auto-update-/,
|
|
138
|
+
'Safe updater should leave local dirty files recoverable in git stash.',
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
console.log('git install update smoke passed');
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
|
|
4
|
+
const read = (path) => fs.readFileSync(path, 'utf8');
|
|
5
|
+
|
|
6
|
+
const workbench = read('src/components/vscode-workbench/view/VSCodeWorkbench.tsx');
|
|
7
|
+
const projectType = read('src/types/app.ts');
|
|
8
|
+
const projectsServer = read('server/projects.js');
|
|
9
|
+
const mainState = read('src/components/main-content/view/subcomponents/MainContentStateView.tsx');
|
|
10
|
+
const chatInterface = read('src/components/chat/view/ChatInterface.tsx');
|
|
11
|
+
const chatComposer = read('src/components/chat/view/subcomponents/ChatComposer.tsx');
|
|
12
|
+
const workerSlots = read('src/components/chat/view/subcomponents/WorkerSlotsControl.tsx');
|
|
13
|
+
const wizard = read('src/components/project-creation-wizard/ProjectCreationWizard.tsx');
|
|
14
|
+
const sidebar = read('src/components/sidebar/view/Sidebar.tsx');
|
|
15
|
+
|
|
16
|
+
assert.match(
|
|
17
|
+
workbench,
|
|
18
|
+
/function WorkbenchMenuBar/,
|
|
19
|
+
'VS Code workbench should render a top menu bar.',
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
for (const item of ['File', 'Edit', 'Selection', 'View', 'Go', 'Run', 'Terminal', 'Help']) {
|
|
23
|
+
assert.match(workbench, new RegExp(`label: '${item}'`), `Workbench menu should include ${item}.`);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
assert.match(
|
|
27
|
+
workbench,
|
|
28
|
+
/type:\s*'existing'/,
|
|
29
|
+
'File > Open Project should dispatch the existing-folder project wizard flow.',
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
assert.match(
|
|
33
|
+
workbench,
|
|
34
|
+
/type:\s*'new'/,
|
|
35
|
+
'File > Clone From GitHub should dispatch the GitHub clone project wizard flow.',
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
assert.match(
|
|
39
|
+
workbench,
|
|
40
|
+
/function WorkbenchProjectsPanel/,
|
|
41
|
+
'Projects activity should use a dedicated project-directory panel.',
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
assert.doesNotMatch(
|
|
45
|
+
workbench,
|
|
46
|
+
/return <Sidebar \{\.\.\.sidebarProps\} isMobile=\{false\} \/>/,
|
|
47
|
+
'Projects activity should not render the chat-history sidebar.',
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
assert.match(
|
|
51
|
+
workbench,
|
|
52
|
+
/formatProjectPath/,
|
|
53
|
+
'Projects panel should render a shortened path instead of dumping the full directory.',
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
assert.match(
|
|
57
|
+
workbench,
|
|
58
|
+
/formatFileCount/,
|
|
59
|
+
'Projects panel should show file counts.',
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
assert.match(projectType, /fileCount\?: number/, 'Project type should expose optional fileCount metadata.');
|
|
63
|
+
assert.match(projectsServer, /async function countProjectFiles/, 'Backend should count project files for the workbench project list.');
|
|
64
|
+
|
|
65
|
+
assert.match(
|
|
66
|
+
mainState,
|
|
67
|
+
/repeat\(auto-fit,\s*minmax\(min\(100%,\s*11rem\),\s*1fr\)\)/,
|
|
68
|
+
'Start workspace cards should auto-fit instead of forcing cramped fixed columns.',
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
assert.doesNotMatch(
|
|
72
|
+
workbench,
|
|
73
|
+
/<main className="min-w-\[360px\]/,
|
|
74
|
+
'Workbench center panel should be allowed to shrink with narrow three-pane layouts.',
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
assert.match(
|
|
78
|
+
workbench,
|
|
79
|
+
/compactComposer/,
|
|
80
|
+
'Workbench should request compact composer behavior in the right CLI pane.',
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
assert.match(
|
|
84
|
+
workbench,
|
|
85
|
+
/activeTab === 'chat' && activityPanel === 'projects'/,
|
|
86
|
+
'Projects activity should stay selected while the center chat tab is active.',
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
assert.match(chatInterface, /compactComposer\?: boolean/, 'ChatInterface should expose compactComposer for narrow workbench panes.');
|
|
90
|
+
assert.match(chatComposer, /compact\?: boolean/, 'ChatComposer should expose a compact prop.');
|
|
91
|
+
assert.match(chatComposer, /flex-wrap/, 'ChatComposer footer should wrap controls in narrow panes.');
|
|
92
|
+
assert.match(workerSlots, /panelClassName\?: string/, 'WorkerSlotsControl should allow a compact panel width override.');
|
|
93
|
+
assert.match(chatComposer, /panelClassName=\{compact/, 'Compact composer should constrain the worker-slot panel.');
|
|
94
|
+
|
|
95
|
+
assert.match(
|
|
96
|
+
wizard,
|
|
97
|
+
/initialWorkspaceType\?: WorkspaceType/,
|
|
98
|
+
'Project wizard should accept an initial workspace type from the workbench File menu.',
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
assert.match(
|
|
102
|
+
sidebar,
|
|
103
|
+
/event as CustomEvent<\{ workspaceType\?: WorkspaceType \}>/,
|
|
104
|
+
'Sidebar create-project event should forward the requested workspace type into the wizard.',
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
console.log('vscode workbench polish smoke passed');
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawn } from 'node:child_process';
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
|
|
6
|
+
const repoRoot = process.cwd();
|
|
7
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
8
|
+
const stashMessage = `pixcode-auto-update-${timestamp}`;
|
|
9
|
+
const backupBranch = `pixcode-backup-before-update-${timestamp}`;
|
|
10
|
+
|
|
11
|
+
function log(message) {
|
|
12
|
+
process.stdout.write(`${message}\n`);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function run(command, args, options = {}) {
|
|
16
|
+
const {
|
|
17
|
+
allowFailure = false,
|
|
18
|
+
collectOutput = false,
|
|
19
|
+
env = process.env,
|
|
20
|
+
} = options;
|
|
21
|
+
|
|
22
|
+
return new Promise((resolve, reject) => {
|
|
23
|
+
log(`$ ${[command, ...args].join(' ')}`);
|
|
24
|
+
const child = spawn(command, args, {
|
|
25
|
+
cwd: repoRoot,
|
|
26
|
+
env,
|
|
27
|
+
shell: false,
|
|
28
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
let stdout = '';
|
|
32
|
+
let stderr = '';
|
|
33
|
+
|
|
34
|
+
child.stdout?.on('data', (chunk) => {
|
|
35
|
+
const text = chunk.toString();
|
|
36
|
+
stdout += text;
|
|
37
|
+
process.stdout.write(text);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
child.stderr?.on('data', (chunk) => {
|
|
41
|
+
const text = chunk.toString();
|
|
42
|
+
stderr += text;
|
|
43
|
+
process.stderr.write(text);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
child.on('error', reject);
|
|
47
|
+
child.on('close', (code) => {
|
|
48
|
+
const result = { code, stdout, stderr };
|
|
49
|
+
if (code === 0 || allowFailure) {
|
|
50
|
+
resolve(collectOutput ? result : code);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const error = new Error(`${command} ${args.join(' ')} exited with code ${code}`);
|
|
55
|
+
error.result = result;
|
|
56
|
+
reject(error);
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async function getOutput(command, args, options = {}) {
|
|
62
|
+
const result = await run(command, args, { ...options, collectOutput: true });
|
|
63
|
+
return result.stdout.trim();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async function main() {
|
|
67
|
+
if (!fs.existsSync(path.join(repoRoot, '.git'))) {
|
|
68
|
+
throw new Error(`Git metadata not found in ${repoRoot}`);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
log('Pixcode safe git update started.');
|
|
72
|
+
log(`Repository: ${repoRoot}`);
|
|
73
|
+
|
|
74
|
+
await run('git', ['rev-parse', '--is-inside-work-tree']);
|
|
75
|
+
await run('git', ['fetch', 'origin', 'main']);
|
|
76
|
+
|
|
77
|
+
const status = await getOutput('git', ['status', '--porcelain', '--untracked-files=all']);
|
|
78
|
+
if (status) {
|
|
79
|
+
log('Local checkout has modified or untracked files.');
|
|
80
|
+
log(`Saving them to git stash: ${stashMessage}`);
|
|
81
|
+
await run('git', [
|
|
82
|
+
'-c',
|
|
83
|
+
'user.name=Pixcode Updater',
|
|
84
|
+
'-c',
|
|
85
|
+
'user.email=updater@pixcode.local',
|
|
86
|
+
'stash',
|
|
87
|
+
'push',
|
|
88
|
+
'--include-untracked',
|
|
89
|
+
'--message',
|
|
90
|
+
stashMessage,
|
|
91
|
+
]);
|
|
92
|
+
log('Local changes are preserved in git stash.');
|
|
93
|
+
} else {
|
|
94
|
+
log('Working tree is clean.');
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const checkoutMain = await run('git', ['checkout', 'main'], { allowFailure: true, collectOutput: true });
|
|
98
|
+
if (checkoutMain.code !== 0) {
|
|
99
|
+
log('Local main branch checkout failed; recreating main from origin/main.');
|
|
100
|
+
await run('git', ['checkout', '-B', 'main', 'origin/main']);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const isAncestor = await run('git', ['merge-base', '--is-ancestor', 'HEAD', 'origin/main'], {
|
|
104
|
+
allowFailure: true,
|
|
105
|
+
collectOutput: true,
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
if (isAncestor.code === 0) {
|
|
109
|
+
await run('git', ['merge', '--ff-only', 'origin/main']);
|
|
110
|
+
} else if (isAncestor.code === 1) {
|
|
111
|
+
log(`Local main has commits that are not on origin/main. Preserving them in branch: ${backupBranch}`);
|
|
112
|
+
await run('git', ['branch', backupBranch]);
|
|
113
|
+
await run('git', ['reset', '--hard', 'origin/main']);
|
|
114
|
+
} else {
|
|
115
|
+
throw new Error('Could not compare local main with origin/main.');
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const packageVersion = JSON.parse(fs.readFileSync(path.join(repoRoot, 'package.json'), 'utf8')).version;
|
|
119
|
+
log(`Repository updated to Pixcode ${packageVersion}.`);
|
|
120
|
+
|
|
121
|
+
await run('npm', ['install', '--no-audit', '--no-fund']);
|
|
122
|
+
log('Pixcode git install update completed.');
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
main().catch((error) => {
|
|
126
|
+
process.stderr.write(`Pixcode git install update failed: ${error.message}\n`);
|
|
127
|
+
process.exit(1);
|
|
128
|
+
});
|
package/server/index.js
CHANGED
|
@@ -482,18 +482,21 @@ app.use(express.static(path.join(APP_ROOT, 'public'), {
|
|
|
482
482
|
// npm tarball, extracts it to the writable runtime dir, and triggers
|
|
483
483
|
// a server restart so the Electron wrapper respawns with new code.
|
|
484
484
|
// ~4 MB download, ~10 s; no npm/git/shell required on the host.
|
|
485
|
-
// 2. installMode === 'git' →
|
|
485
|
+
// 2. installMode === 'git' → safe git updater script. It stashes dirty
|
|
486
|
+
// checkout state before pulling so source installs do not fail on local
|
|
487
|
+
// modified files left by older releases or manual edits.
|
|
486
488
|
// 3. fallback → `npm install -g …` (classic npm-distributed install).
|
|
487
489
|
app.post('/api/system/update', authenticateToken, async (req, res) => {
|
|
488
490
|
const projectRoot = APP_ROOT;
|
|
489
491
|
console.log('Starting system update from directory:', projectRoot);
|
|
490
492
|
|
|
491
493
|
const runtimeDir = process.env.PIXCODE_RUNTIME_DIR || null;
|
|
494
|
+
const gitUpdateScript = path.join(projectRoot, 'scripts', 'update-git-install.mjs');
|
|
492
495
|
|
|
493
496
|
const updateCommand = IS_PLATFORM
|
|
494
497
|
? 'npm run update:platform'
|
|
495
498
|
: installMode === 'git'
|
|
496
|
-
?
|
|
499
|
+
? `${JSON.stringify(process.execPath)} ${JSON.stringify(gitUpdateScript)}`
|
|
497
500
|
: 'npm install -g @pixelbyte-software/pixcode@latest';
|
|
498
501
|
|
|
499
502
|
const updateCwd = IS_PLATFORM || installMode === 'git'
|
|
@@ -738,8 +741,9 @@ app.post('/api/system/update', authenticateToken, async (req, res) => {
|
|
|
738
741
|
// Short-circuit for "already on latest" in the npm-global path so
|
|
739
742
|
// users don't accidentally crash their own daemon by clicking Update
|
|
740
743
|
// while already up to date. The runtime-dir branch above already has
|
|
741
|
-
// this guard (line ~504); replicate it for npm mode. Git mode
|
|
742
|
-
//
|
|
744
|
+
// this guard (line ~504); replicate it for npm mode. Git mode still
|
|
745
|
+
// runs because users may be on the latest package version but behind
|
|
746
|
+
// the source branch or have a dirty checkout that needs normalization.
|
|
743
747
|
if (!IS_PLATFORM && installMode === 'npm') {
|
|
744
748
|
try {
|
|
745
749
|
send('log', { stream: 'meta', chunk: 'Querying registry for latest version…\n' });
|
package/server/projects.js
CHANGED
|
@@ -68,6 +68,74 @@ import Database from 'better-sqlite3';
|
|
|
68
68
|
import sessionManager from './sessionManager.js';
|
|
69
69
|
import { applyCustomSessionNames } from './database/db.js';
|
|
70
70
|
|
|
71
|
+
const FILE_COUNT_LIMIT = 20000;
|
|
72
|
+
const FILE_COUNT_IGNORED_DIRECTORIES = new Set([
|
|
73
|
+
'.cache',
|
|
74
|
+
'.git',
|
|
75
|
+
'.hg',
|
|
76
|
+
'.next',
|
|
77
|
+
'.npm',
|
|
78
|
+
'.pnpm-store',
|
|
79
|
+
'.svn',
|
|
80
|
+
'.turbo',
|
|
81
|
+
'.vite',
|
|
82
|
+
'build',
|
|
83
|
+
'coverage',
|
|
84
|
+
'dist',
|
|
85
|
+
'dist-server',
|
|
86
|
+
'node_modules',
|
|
87
|
+
'out',
|
|
88
|
+
'target',
|
|
89
|
+
'vendor'
|
|
90
|
+
]);
|
|
91
|
+
|
|
92
|
+
async function countProjectFiles(projectPath, maxFiles = FILE_COUNT_LIMIT) {
|
|
93
|
+
if (!projectPath || typeof projectPath !== 'string') {
|
|
94
|
+
return undefined;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
const stats = await fs.stat(projectPath);
|
|
99
|
+
if (!stats.isDirectory()) {
|
|
100
|
+
return undefined;
|
|
101
|
+
}
|
|
102
|
+
} catch {
|
|
103
|
+
return undefined;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
let count = 0;
|
|
107
|
+
const pendingDirectories = [projectPath];
|
|
108
|
+
|
|
109
|
+
while (pendingDirectories.length > 0) {
|
|
110
|
+
const currentDirectory = pendingDirectories.pop();
|
|
111
|
+
let entries = [];
|
|
112
|
+
|
|
113
|
+
try {
|
|
114
|
+
entries = await fs.readdir(currentDirectory, { withFileTypes: true });
|
|
115
|
+
} catch {
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
for (const entry of entries) {
|
|
120
|
+
if (entry.isDirectory()) {
|
|
121
|
+
if (!FILE_COUNT_IGNORED_DIRECTORIES.has(entry.name)) {
|
|
122
|
+
pendingDirectories.push(path.join(currentDirectory, entry.name));
|
|
123
|
+
}
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (entry.isFile() || entry.isSymbolicLink()) {
|
|
128
|
+
count += 1;
|
|
129
|
+
if (count >= maxFiles) {
|
|
130
|
+
return count;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return count;
|
|
137
|
+
}
|
|
138
|
+
|
|
71
139
|
// Import TaskMaster detection functions
|
|
72
140
|
async function detectTaskMasterFolder(projectPath) {
|
|
73
141
|
try {
|
|
@@ -691,6 +759,7 @@ async function getProjects(progressCallback = null) {
|
|
|
691
759
|
path: actualProjectDir,
|
|
692
760
|
displayName: customName || autoDisplayName,
|
|
693
761
|
fullPath: actualProjectDir,
|
|
762
|
+
fileCount: await countProjectFiles(actualProjectDir),
|
|
694
763
|
isCustomName: !!customName,
|
|
695
764
|
isManuallyAdded,
|
|
696
765
|
autoDiscovered: isAutoDiscovered,
|
|
@@ -832,6 +901,7 @@ async function getProjects(progressCallback = null) {
|
|
|
832
901
|
path: actualProjectDir,
|
|
833
902
|
displayName: projectConfig.displayName || await generateDisplayName(projectName, actualProjectDir),
|
|
834
903
|
fullPath: actualProjectDir,
|
|
904
|
+
fileCount: await countProjectFiles(actualProjectDir),
|
|
835
905
|
isCustomName: !!projectConfig.displayName,
|
|
836
906
|
isManuallyAdded,
|
|
837
907
|
autoDiscovered: isAutoDiscovered,
|
|
@@ -1557,6 +1627,7 @@ async function addProjectManually(projectPath, displayName = null) {
|
|
|
1557
1627
|
path: absolutePath,
|
|
1558
1628
|
fullPath: absolutePath,
|
|
1559
1629
|
displayName: displayName || await generateDisplayName(projectName, absolutePath),
|
|
1630
|
+
fileCount: await countProjectFiles(absolutePath),
|
|
1560
1631
|
isManuallyAdded: true,
|
|
1561
1632
|
sessions: [],
|
|
1562
1633
|
cursorSessions: []
|
|
@@ -1584,6 +1655,7 @@ async function addProjectManually(projectPath, displayName = null) {
|
|
|
1584
1655
|
path: absolutePath,
|
|
1585
1656
|
fullPath: absolutePath,
|
|
1586
1657
|
displayName: displayName || await generateDisplayName(projectName, absolutePath),
|
|
1658
|
+
fileCount: await countProjectFiles(absolutePath),
|
|
1587
1659
|
isManuallyAdded: true,
|
|
1588
1660
|
sessions: [],
|
|
1589
1661
|
cursorSessions: []
|