sln2csproj 0.2.0
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/LICENSE +21 -0
- package/README.md +273 -0
- package/bin/cli.js +189 -0
- package/bin/generator.js +119 -0
- package/bin/io.js +61 -0
- package/bin/parser.js +127 -0
- package/bin/resolver.js +158 -0
- package/bin/sln.js +80 -0
- package/bin/types.js +2 -0
- package/package.json +31 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 bnggbn
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
# sln2csproj
|
|
2
|
+
|
|
3
|
+
Generate a fake `.csproj` for legacy ASP.NET WebSite projects found in a `.sln`, so VS Code / C# tooling can provide IntelliSense without converting the original solution.
|
|
4
|
+
|
|
5
|
+
## What It Does
|
|
6
|
+
|
|
7
|
+
This tool is for old Visual Studio WebSite solutions that do not have a real `.csproj` for the website itself.
|
|
8
|
+
|
|
9
|
+
It reads the `.sln`, finds the Website project, and generates:
|
|
10
|
+
|
|
11
|
+
- a fake website `.csproj`
|
|
12
|
+
- a fake `.sln` that points the Website entry to that fake `.csproj`
|
|
13
|
+
- reference hint paths for project DLLs and DLLs already present in the website `Bin` folder
|
|
14
|
+
|
|
15
|
+
The original solution is not modified.
|
|
16
|
+
|
|
17
|
+
## Typical Use Case
|
|
18
|
+
|
|
19
|
+
You have an old VS2005/VS2008/VS2010-era ASP.NET WebSite project.
|
|
20
|
+
|
|
21
|
+
- Visual Studio can still open it
|
|
22
|
+
- VS Code / Roslyn / C# extension cannot resolve the website correctly
|
|
23
|
+
- you want IntelliSense only
|
|
24
|
+
- you do not want to upgrade or convert the project
|
|
25
|
+
|
|
26
|
+
## Install
|
|
27
|
+
|
|
28
|
+
### From npm
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
npm install -g sln2csproj
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Then run:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
sln2csproj path/to/Your.sln
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Or without global install:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
npx sln2csproj path/to/Your.sln
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Local Development
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
npm install
|
|
50
|
+
npm run build
|
|
51
|
+
node ./bin/cli.js path/to/Your.sln
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Usage
|
|
55
|
+
|
|
56
|
+
```text
|
|
57
|
+
Usage:
|
|
58
|
+
sln2csproj <solution.sln> [options]
|
|
59
|
+
|
|
60
|
+
Options:
|
|
61
|
+
--pick <N> Select Nth Website project if multiple exist
|
|
62
|
+
--outDir <dir> Output directory (default: tools/_intellisense)
|
|
63
|
+
--mode <copy|link>
|
|
64
|
+
copy: copy referenced DLLs (default)
|
|
65
|
+
link: reference original paths only
|
|
66
|
+
--check List Website projects and exit
|
|
67
|
+
--verbose Print detailed resolution info
|
|
68
|
+
-h, --help Show help
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Examples
|
|
72
|
+
|
|
73
|
+
### Default output
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
sln2csproj MyApp.sln
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
This writes output under:
|
|
80
|
+
|
|
81
|
+
```text
|
|
82
|
+
<solution-dir>\tools\_intellisense\<WebsiteName>\
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Custom output directory
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
sln2csproj MyApp.sln --outDir D:\temp\_intellisense
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
If `--outDir` is absolute, output is written there.
|
|
92
|
+
If `--outDir` is relative, it is resolved from the solution directory.
|
|
93
|
+
|
|
94
|
+
### List Website projects first
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
sln2csproj MyApp.sln --check
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Useful when the solution contains more than one Website project.
|
|
101
|
+
|
|
102
|
+
### Pick the second Website project
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
sln2csproj MyApp.sln --pick 2
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Link mode
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
sln2csproj MyApp.sln --mode link
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Verbose output
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
sln2csproj MyApp.sln --verbose
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Output Structure
|
|
121
|
+
|
|
122
|
+
Example output:
|
|
123
|
+
|
|
124
|
+
```text
|
|
125
|
+
tools\_intellisense\HCT.SCTM.Web\
|
|
126
|
+
HCT.SCTM.Web.intellisense.csproj
|
|
127
|
+
fake_HCT.SCTM.Web.sln
|
|
128
|
+
refs\
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Files:
|
|
132
|
+
|
|
133
|
+
- `*.intellisense.csproj`: fake project for IntelliSense
|
|
134
|
+
- `fake_*.sln`: rewritten solution that points the Website entry to the fake project
|
|
135
|
+
- `refs\`: copied DLLs when `--mode copy` is used and DLL copy succeeds
|
|
136
|
+
|
|
137
|
+
## Reference Resolution
|
|
138
|
+
|
|
139
|
+
The generated fake `.csproj` includes:
|
|
140
|
+
|
|
141
|
+
- standard .NET framework references
|
|
142
|
+
- DLLs resolved from `.sln` Website `ProjectReferences`
|
|
143
|
+
- DLLs already present in the website `Bin` folder, such as `Newtonsoft.Json.dll`
|
|
144
|
+
|
|
145
|
+
This matters because many legacy WebSite projects depend on assemblies in `Bin` that are not listed as normal project references in the solution.
|
|
146
|
+
|
|
147
|
+
## `copy` vs `link`
|
|
148
|
+
|
|
149
|
+
### `--mode copy`
|
|
150
|
+
|
|
151
|
+
Default mode.
|
|
152
|
+
|
|
153
|
+
- copies resolved project DLLs into `refs\`
|
|
154
|
+
- keeps the fake folder more self-contained
|
|
155
|
+
- if DLL copy fails or a DLL is missing, it falls back to linking to the website `Bin` path
|
|
156
|
+
|
|
157
|
+
### `--mode link`
|
|
158
|
+
|
|
159
|
+
- does not copy project DLLs
|
|
160
|
+
- references original resolved paths directly
|
|
161
|
+
- useful when you do not want duplicated files
|
|
162
|
+
|
|
163
|
+
Note:
|
|
164
|
+
DLLs discovered directly from the website `Bin` folder are referenced from their original `Bin` location in both modes.
|
|
165
|
+
|
|
166
|
+
## VS Code Workflow
|
|
167
|
+
|
|
168
|
+
After generation:
|
|
169
|
+
|
|
170
|
+
1. Open the generated folder under `tools/_intellisense/<WebsiteName>/`
|
|
171
|
+
2. Let the C# extension load the fake `.sln` / `.csproj`
|
|
172
|
+
3. Use IntelliSense against the original website source files
|
|
173
|
+
4. Delete that generated website folder when you no longer need it
|
|
174
|
+
|
|
175
|
+
In practice, deleting only the generated website subfolder is usually enough. You do not need to delete the whole `_intellisense` root.
|
|
176
|
+
|
|
177
|
+
## Limitations
|
|
178
|
+
|
|
179
|
+
- This is for IntelliSense, not for real builds
|
|
180
|
+
- It does not convert WebSite projects into SDK-style projects
|
|
181
|
+
- It does not guarantee every legacy dependency pattern will be inferred
|
|
182
|
+
- Some environments may lock generated files while VS Code / OmniSharp is indexing them
|
|
183
|
+
|
|
184
|
+
## Troubleshooting
|
|
185
|
+
|
|
186
|
+
### `No Website Project found in the solution.`
|
|
187
|
+
|
|
188
|
+
The `.sln` may not contain a legacy WebSite project entry, or the format may differ from what this tool supports.
|
|
189
|
+
|
|
190
|
+
### `Newtonsoft` or another assembly is still unresolved
|
|
191
|
+
|
|
192
|
+
Check whether the DLL exists in the website `Bin` folder.
|
|
193
|
+
|
|
194
|
+
This tool now includes `Bin` DLLs in the generated fake project, but if the DLL is missing from `Bin`, IntelliSense still cannot resolve it.
|
|
195
|
+
|
|
196
|
+
### Cannot delete generated `_intellisense` files
|
|
197
|
+
|
|
198
|
+
Usually this is not a tool bug. The generated folder is often being used by:
|
|
199
|
+
|
|
200
|
+
- VS Code
|
|
201
|
+
- C# extension / Roslyn / OmniSharp
|
|
202
|
+
- file indexers or security tools
|
|
203
|
+
|
|
204
|
+
Close the generated fake solution/folder first, then delete only the target website subfolder under `_intellisense`.
|
|
205
|
+
|
|
206
|
+
### Output inside the project is hard to delete in your environment
|
|
207
|
+
|
|
208
|
+
Use a custom output path:
|
|
209
|
+
|
|
210
|
+
```bash
|
|
211
|
+
sln2csproj MyApp.sln --outDir D:\temp\_intellisense
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
## Test
|
|
215
|
+
|
|
216
|
+
```bash
|
|
217
|
+
npm test
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
The test suite covers:
|
|
221
|
+
|
|
222
|
+
- solution parsing
|
|
223
|
+
- fake `.csproj` generation
|
|
224
|
+
- fake `.sln` rewriting
|
|
225
|
+
- Chinese path handling
|
|
226
|
+
- disk-based fixture parsing
|
|
227
|
+
- CLI integration using a temporary workspace
|
|
228
|
+
|
|
229
|
+
## Publish
|
|
230
|
+
|
|
231
|
+
Create a local package tarball:
|
|
232
|
+
|
|
233
|
+
```bash
|
|
234
|
+
npm pack
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
Publish to npm:
|
|
238
|
+
|
|
239
|
+
```bash
|
|
240
|
+
npm publish
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
## GitHub Actions Publish
|
|
244
|
+
|
|
245
|
+
This repository includes a GitHub Actions workflow at `.github/workflows/publish.yml`.
|
|
246
|
+
|
|
247
|
+
It runs when:
|
|
248
|
+
|
|
249
|
+
- you run the workflow manually from GitHub Actions
|
|
250
|
+
- you push a tag such as `v0.2.0`
|
|
251
|
+
|
|
252
|
+
Required setup:
|
|
253
|
+
|
|
254
|
+
1. Create an npm granular access token with publish permission
|
|
255
|
+
2. If your npm account requires it, make sure the token supports bypass 2FA
|
|
256
|
+
3. Add the token to the GitHub repository secrets as `NPM_TOKEN`
|
|
257
|
+
|
|
258
|
+
Suggested release flow:
|
|
259
|
+
|
|
260
|
+
```bash
|
|
261
|
+
git tag v0.2.0
|
|
262
|
+
git push origin v0.2.0
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
The workflow will:
|
|
266
|
+
|
|
267
|
+
- install dependencies
|
|
268
|
+
- run tests
|
|
269
|
+
- publish the npm package
|
|
270
|
+
- build a Windows `exe`
|
|
271
|
+
- attach the Windows `exe` and `.zip` to the GitHub Release when triggered by a version tag
|
|
272
|
+
|
|
273
|
+
The Windows executable is built with Node.js Single Executable Applications (SEA), so Windows users can download it from GitHub Releases without installing Node first.
|
package/bin/cli.js
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
4
|
+
if (k2 === undefined) k2 = k;
|
|
5
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
6
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
7
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
8
|
+
}
|
|
9
|
+
Object.defineProperty(o, k2, desc);
|
|
10
|
+
}) : (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
o[k2] = m[k];
|
|
13
|
+
}));
|
|
14
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
15
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
16
|
+
}) : function(o, v) {
|
|
17
|
+
o["default"] = v;
|
|
18
|
+
});
|
|
19
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
20
|
+
var ownKeys = function(o) {
|
|
21
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
22
|
+
var ar = [];
|
|
23
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
24
|
+
return ar;
|
|
25
|
+
};
|
|
26
|
+
return ownKeys(o);
|
|
27
|
+
};
|
|
28
|
+
return function (mod) {
|
|
29
|
+
if (mod && mod.__esModule) return mod;
|
|
30
|
+
var result = {};
|
|
31
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
32
|
+
__setModuleDefault(result, mod);
|
|
33
|
+
return result;
|
|
34
|
+
};
|
|
35
|
+
})();
|
|
36
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
const io_1 = require("./io");
|
|
40
|
+
const parser_1 = require("./parser");
|
|
41
|
+
const resolver_1 = require("./resolver");
|
|
42
|
+
const generator_1 = require("./generator");
|
|
43
|
+
const sln_1 = require("./sln");
|
|
44
|
+
function collectWebsiteBinHintPaths(websiteAbs, websiteRelFromFake, hintByDll) {
|
|
45
|
+
const binDirAbs = path.join(websiteAbs, 'Bin');
|
|
46
|
+
if (!(0, io_1.exists)(binDirAbs))
|
|
47
|
+
return;
|
|
48
|
+
const existing = new Set(Array.from(hintByDll.keys(), key => key.toLowerCase()));
|
|
49
|
+
for (const name of fs.readdirSync(binDirAbs)) {
|
|
50
|
+
if (!/\.dll$/i.test(name))
|
|
51
|
+
continue;
|
|
52
|
+
const key = name.toLowerCase();
|
|
53
|
+
if (existing.has(key))
|
|
54
|
+
continue;
|
|
55
|
+
hintByDll.set(name, path.join(websiteRelFromFake, 'Bin', name).replace(/\//g, '\\'));
|
|
56
|
+
existing.add(key);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
function usage() {
|
|
60
|
+
console.log([
|
|
61
|
+
'Usage:',
|
|
62
|
+
' sln2csproj <solution.sln> [options]',
|
|
63
|
+
'',
|
|
64
|
+
'Options:',
|
|
65
|
+
' --pick <N> Select Nth Website project if multiple exist',
|
|
66
|
+
' --outDir <dir> Output directory (default: tools/_intellisense)',
|
|
67
|
+
' --mode <copy|link>',
|
|
68
|
+
' copy: copy referenced DLLs (default)',
|
|
69
|
+
' link: reference original paths only',
|
|
70
|
+
' --check List Website projects and exit',
|
|
71
|
+
' --verbose Print detailed resolution info',
|
|
72
|
+
' -h, --help Show help',
|
|
73
|
+
'',
|
|
74
|
+
'Examples:',
|
|
75
|
+
' sln2csproj MyApp.sln',
|
|
76
|
+
' sln2csproj MyApp.sln --mode link --verbose',
|
|
77
|
+
' sln2csproj MyApp.sln --check',
|
|
78
|
+
].join('\n'));
|
|
79
|
+
}
|
|
80
|
+
function parseArgs(argv) {
|
|
81
|
+
const args = argv.slice(2);
|
|
82
|
+
const opt = { slnPath: '' };
|
|
83
|
+
for (let i = 0; i < args.length; i++) {
|
|
84
|
+
const a = args[i];
|
|
85
|
+
if (a === '--pick') {
|
|
86
|
+
opt.pick = parseInt(args[++i], 10);
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
if (a === '--outDir') {
|
|
90
|
+
opt.outDir = args[++i];
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
if (a === '--mode') {
|
|
94
|
+
opt.mode = args[++i] || 'copy';
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
if (a === '--verbose') {
|
|
98
|
+
opt.verbose = true;
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
if (a === '--check') {
|
|
102
|
+
opt.check = true;
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
if (a === '--help' || a === '-h') {
|
|
106
|
+
opt.help = true;
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
if (!opt.slnPath)
|
|
110
|
+
opt.slnPath = a;
|
|
111
|
+
}
|
|
112
|
+
return opt;
|
|
113
|
+
}
|
|
114
|
+
function main() {
|
|
115
|
+
const opt = parseArgs(process.argv);
|
|
116
|
+
if (opt.help) {
|
|
117
|
+
usage();
|
|
118
|
+
process.exit(0);
|
|
119
|
+
}
|
|
120
|
+
if (!opt.slnPath) {
|
|
121
|
+
usage();
|
|
122
|
+
process.exit(1);
|
|
123
|
+
}
|
|
124
|
+
const slnAbs = path.resolve(opt.slnPath);
|
|
125
|
+
if (!(0, io_1.exists)(slnAbs)) {
|
|
126
|
+
console.error(`File not found: ${slnAbs}`);
|
|
127
|
+
process.exit(1);
|
|
128
|
+
}
|
|
129
|
+
const slnDir = path.dirname(slnAbs);
|
|
130
|
+
const outRootAbs = path.resolve(slnDir, opt.outDir || path.join('tools', '_intellisense'));
|
|
131
|
+
(0, io_1.mkdirp)(outRootAbs);
|
|
132
|
+
const slnContent = (0, io_1.readText)(slnAbs);
|
|
133
|
+
const blocks = (0, parser_1.splitSlnProjects)(slnContent);
|
|
134
|
+
const websites = (0, parser_1.parseWebsites)(blocks);
|
|
135
|
+
if (websites.length === 0) {
|
|
136
|
+
console.error('No Website Project found in the solution.');
|
|
137
|
+
process.exit(1);
|
|
138
|
+
}
|
|
139
|
+
if (opt.check) {
|
|
140
|
+
console.log('Website Projects:');
|
|
141
|
+
websites.forEach((w, i) => {
|
|
142
|
+
console.log(`${i + 1}. ${w.name}`);
|
|
143
|
+
console.log(` PhysicalPath: ${w.physicalPath}`);
|
|
144
|
+
console.log(` Framework: ${w.targetFramework}`);
|
|
145
|
+
});
|
|
146
|
+
if (websites.length > 1) {
|
|
147
|
+
console.log('\nMultiple Website projects found. Use --pick N to choose one.');
|
|
148
|
+
process.exit(2);
|
|
149
|
+
}
|
|
150
|
+
process.exit(0);
|
|
151
|
+
}
|
|
152
|
+
const pickIndex = Math.max(0, (opt.pick ?? 1) - 1);
|
|
153
|
+
const website = websites[Math.min(pickIndex, websites.length - 1)];
|
|
154
|
+
const csharpProjects = (0, parser_1.parseCSharpProjects)(blocks);
|
|
155
|
+
(0, resolver_1.resolveDlls)(slnDir, website, csharpProjects);
|
|
156
|
+
const safeName = website.name.replace(/[<>:"/\\|?*]+/g, '_');
|
|
157
|
+
const fakeDirAbs = path.join(outRootAbs, safeName);
|
|
158
|
+
(0, io_1.mkdirp)(fakeDirAbs);
|
|
159
|
+
const websiteAbs = path.join(slnDir, website.physicalPath);
|
|
160
|
+
const websiteRelFromFake = path.relative(fakeDirAbs, websiteAbs).replace(/\//g, '\\') || '.';
|
|
161
|
+
const mode = opt.mode || 'copy';
|
|
162
|
+
const refsDirAbs = path.join(fakeDirAbs, 'refs');
|
|
163
|
+
const hintByDll = (0, resolver_1.materializeRefs)(mode, fakeDirAbs, refsDirAbs, websiteAbs, websiteRelFromFake, website);
|
|
164
|
+
collectWebsiteBinHintPaths(websiteAbs, websiteRelFromFake, hintByDll);
|
|
165
|
+
const csprojContent = (0, generator_1.generateFakeCsproj)(website, websiteRelFromFake, hintByDll);
|
|
166
|
+
const csprojPath = path.join(fakeDirAbs, `${safeName}.intellisense.csproj`);
|
|
167
|
+
(0, io_1.writeText)(csprojPath, csprojContent);
|
|
168
|
+
const fakeSlnContent = (0, sln_1.buildFakeSln)(slnContent, slnDir, fakeDirAbs, website, csprojPath);
|
|
169
|
+
const fakeSlnPath = path.join(fakeDirAbs, `fake_${safeName}.sln`);
|
|
170
|
+
(0, io_1.writeText)(fakeSlnPath, fakeSlnContent);
|
|
171
|
+
console.log(`Website: ${website.name}`);
|
|
172
|
+
console.log(` PhysicalPath: ${website.physicalPath}`);
|
|
173
|
+
console.log(` Framework: ${website.targetFramework}`);
|
|
174
|
+
console.log(`Output: ${csprojPath}`);
|
|
175
|
+
console.log(` Mode: ${mode}`);
|
|
176
|
+
console.log(` Refs: ${website.projectReferences.length}`);
|
|
177
|
+
console.log(`Fake SLN: ${fakeSlnPath}`);
|
|
178
|
+
if (opt.verbose) {
|
|
179
|
+
console.log('\nDependencies:');
|
|
180
|
+
for (const ref of website.projectReferences) {
|
|
181
|
+
const hint = hintByDll.get(ref.dllName) || '(none)';
|
|
182
|
+
console.log(`- ${ref.dllName}`);
|
|
183
|
+
console.log(` from: ${ref.resolvedFrom || ''}`);
|
|
184
|
+
console.log(` hint: ${hint}`);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
console.log('\nVS Code: open tools/_intellisense/<WebsiteName>/ and delete the folder when finished.');
|
|
188
|
+
}
|
|
189
|
+
main();
|
package/bin/generator.js
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.generateFakeCsproj = generateFakeCsproj;
|
|
37
|
+
const path = __importStar(require("path"));
|
|
38
|
+
function esc(s) {
|
|
39
|
+
return s
|
|
40
|
+
.replace(/&/g, '&')
|
|
41
|
+
.replace(/</g, '<')
|
|
42
|
+
.replace(/>/g, '>')
|
|
43
|
+
.replace(/"/g, '"')
|
|
44
|
+
.replace(/'/g, ''');
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* websiteRelFromFake: 例如 "..\\..\\..\\HCT.WebSite"
|
|
48
|
+
* hintByDll: dllName -> HintPath (relative to fake project dir)
|
|
49
|
+
*/
|
|
50
|
+
function generateFakeCsproj(website, websiteRelFromFake, hintByDll) {
|
|
51
|
+
const prefix = websiteRelFromFake.replace(/\//g, '\\').replace(/\\+$/, '');
|
|
52
|
+
const compileInclude = esc(path.join(prefix, '**', '*.cs')).replace(/\//g, '\\');
|
|
53
|
+
const compileExclude = [
|
|
54
|
+
path.join(prefix, 'obj', '**'),
|
|
55
|
+
path.join(prefix, 'bin', '**'),
|
|
56
|
+
path.join(prefix, 'Bin', '**'),
|
|
57
|
+
path.join(prefix, 'App_Data', '**'),
|
|
58
|
+
path.join(prefix, 'Packages', '**'),
|
|
59
|
+
path.join(prefix, 'node_modules', '**'),
|
|
60
|
+
path.join(prefix, '**', 'Temporary ASP.NET Files', '**'),
|
|
61
|
+
path.join(prefix, 'Properties', 'AssemblyInfo.cs'),
|
|
62
|
+
].map(s => s.replace(/\//g, '\\')).join(';');
|
|
63
|
+
const contentPatterns = ['aspx', 'ascx', 'master', 'ashx', 'asmx', 'config', 'asax'].map(ext => {
|
|
64
|
+
const inc = esc(path.join(prefix, '**', `*.${ext}`)).replace(/\//g, '\\');
|
|
65
|
+
return ` <Content Include="${inc}" />`;
|
|
66
|
+
}).join('\n');
|
|
67
|
+
const references = Array.from(hintByDll.entries()).map(([dllName, hintPath]) => {
|
|
68
|
+
const includeName = esc(dllName.replace(/\.dll$/i, ''));
|
|
69
|
+
const hint = esc(hintPath || path.join(prefix, 'Bin', dllName)).replace(/\//g, '\\');
|
|
70
|
+
return ` <Reference Include="${includeName}">
|
|
71
|
+
<HintPath>${hint}</HintPath>
|
|
72
|
+
<Private>False</Private>
|
|
73
|
+
</Reference>`;
|
|
74
|
+
}).join('\n');
|
|
75
|
+
return `<?xml version="1.0" encoding="utf-8"?>
|
|
76
|
+
<!-- ⚠️ 此檔案由 sln2csproj 自動產生 -->
|
|
77
|
+
<!-- ⚠️ 僅供 IntelliSense 使用,請勿拿來 build -->
|
|
78
|
+
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
|
79
|
+
<PropertyGroup>
|
|
80
|
+
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
|
81
|
+
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
|
82
|
+
<ProjectGuid>{${website.guid}}</ProjectGuid>
|
|
83
|
+
<OutputType>Library</OutputType>
|
|
84
|
+
<RootNamespace>${esc(website.name.replace(/\./g, '_'))}</RootNamespace>
|
|
85
|
+
<AssemblyName>${esc(website.name)}</AssemblyName>
|
|
86
|
+
<TargetFrameworkVersion>${esc(website.targetFramework)}</TargetFrameworkVersion>
|
|
87
|
+
</PropertyGroup>
|
|
88
|
+
|
|
89
|
+
<ItemGroup>
|
|
90
|
+
<!-- 基本 Framework refs -->
|
|
91
|
+
<Reference Include="System" />
|
|
92
|
+
<Reference Include="System.Data" />
|
|
93
|
+
<Reference Include="System.Drawing" />
|
|
94
|
+
<Reference Include="System.Web" />
|
|
95
|
+
<Reference Include="System.Web.Extensions" />
|
|
96
|
+
<Reference Include="System.Web.Services" />
|
|
97
|
+
<Reference Include="System.Xml" />
|
|
98
|
+
<Reference Include="System.Configuration" />
|
|
99
|
+
<Reference Include="System.Xml.Linq" />
|
|
100
|
+
|
|
101
|
+
<!-- 從 sln 解析出的 refs -->
|
|
102
|
+
${references}
|
|
103
|
+
</ItemGroup>
|
|
104
|
+
|
|
105
|
+
<ItemGroup>
|
|
106
|
+
<Compile Include="${compileInclude}" Exclude="${esc(compileExclude)}" />
|
|
107
|
+
</ItemGroup>
|
|
108
|
+
|
|
109
|
+
<ItemGroup>
|
|
110
|
+
${contentPatterns}
|
|
111
|
+
</ItemGroup>
|
|
112
|
+
|
|
113
|
+
<Import Project="$(MSBuildToolsPath)\\Microsoft.CSharp.targets" />
|
|
114
|
+
|
|
115
|
+
<Target Name="Build">
|
|
116
|
+
<Message Text="[INFO] fake csproj for IntelliSense only" Importance="high" />
|
|
117
|
+
</Target>
|
|
118
|
+
</Project>`;
|
|
119
|
+
}
|
package/bin/io.js
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.readText = readText;
|
|
37
|
+
exports.exists = exists;
|
|
38
|
+
exports.mkdirp = mkdirp;
|
|
39
|
+
exports.writeText = writeText;
|
|
40
|
+
exports.copyFile = copyFile;
|
|
41
|
+
const fs = __importStar(require("fs"));
|
|
42
|
+
function readText(p) {
|
|
43
|
+
return fs.readFileSync(p, 'utf8');
|
|
44
|
+
}
|
|
45
|
+
function exists(p) {
|
|
46
|
+
try {
|
|
47
|
+
return fs.existsSync(p);
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function mkdirp(p) {
|
|
54
|
+
fs.mkdirSync(p, { recursive: true });
|
|
55
|
+
}
|
|
56
|
+
function writeText(p, content) {
|
|
57
|
+
fs.writeFileSync(p, content, 'utf8');
|
|
58
|
+
}
|
|
59
|
+
function copyFile(src, dst) {
|
|
60
|
+
fs.copyFileSync(src, dst);
|
|
61
|
+
}
|
package/bin/parser.js
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.splitSlnProjects = splitSlnProjects;
|
|
4
|
+
exports.parseWebsites = parseWebsites;
|
|
5
|
+
exports.parseCSharpProjects = parseCSharpProjects;
|
|
6
|
+
const WEBSITE_TYPE_GUID = 'E24C65DC-7377-472B-9ABA-BC803B73C61A';
|
|
7
|
+
const CSHARP_LEGACY_TYPE_GUID = 'FAE04EC0-301F-11D3-BF4B-00C04F79EFBC';
|
|
8
|
+
const CSHARP_SDK_TYPE_GUID = '9A19103F-16F7-4668-BE54-9A1E7A4F7556';
|
|
9
|
+
function splitSlnProjects(slnContent) {
|
|
10
|
+
const blocks = [];
|
|
11
|
+
const lineRe = /^.*(?:\r?\n|$)/gm;
|
|
12
|
+
const projectHeaderRe = /^Project\("?\{([0-9A-Fa-f\-]+)\}"?\)\s*=\s*"([^"]+)",\s*"([^"]+)",\s*"\{([0-9A-Fa-f\-]+)\}"\s*$/;
|
|
13
|
+
let activeStart = -1;
|
|
14
|
+
let activeMeta = null;
|
|
15
|
+
let match;
|
|
16
|
+
while ((match = lineRe.exec(slnContent)) !== null) {
|
|
17
|
+
const fullLine = match[0];
|
|
18
|
+
if (fullLine.length === 0)
|
|
19
|
+
break;
|
|
20
|
+
const line = fullLine.replace(/\r?\n$/, '');
|
|
21
|
+
if (!activeMeta) {
|
|
22
|
+
const header = line.trim().match(projectHeaderRe);
|
|
23
|
+
if (!header)
|
|
24
|
+
continue;
|
|
25
|
+
activeStart = match.index;
|
|
26
|
+
activeMeta = {
|
|
27
|
+
typeGuid: header[1],
|
|
28
|
+
name: header[2],
|
|
29
|
+
relPath: header[3],
|
|
30
|
+
guid: header[4],
|
|
31
|
+
};
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
if (/^\s*EndProject\s*$/.test(line)) {
|
|
35
|
+
blocks.push({
|
|
36
|
+
...activeMeta,
|
|
37
|
+
rawBlock: slnContent.slice(activeStart, match.index + line.length),
|
|
38
|
+
});
|
|
39
|
+
activeStart = -1;
|
|
40
|
+
activeMeta = null;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return blocks;
|
|
44
|
+
}
|
|
45
|
+
function parseWebsiteProperties(block) {
|
|
46
|
+
const props = {};
|
|
47
|
+
const lines = block.split(/\r?\n/);
|
|
48
|
+
let inWebsiteProperties = false;
|
|
49
|
+
for (const rawLine of lines) {
|
|
50
|
+
const line = rawLine.trim();
|
|
51
|
+
if (!inWebsiteProperties) {
|
|
52
|
+
if (/^ProjectSection\(WebsiteProperties\)\s*=\s*preProject\s*$/i.test(line)) {
|
|
53
|
+
inWebsiteProperties = true;
|
|
54
|
+
}
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
if (/^EndProjectSection\s*$/i.test(line)) {
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
const match = rawLine.match(/^\s*([A-Za-z0-9.\-_]+)\s*=\s*"([^"]*)"\s*$/);
|
|
61
|
+
if (!match)
|
|
62
|
+
continue;
|
|
63
|
+
props[match[1]] = match[2];
|
|
64
|
+
}
|
|
65
|
+
return props;
|
|
66
|
+
}
|
|
67
|
+
function normalizeFramework(raw) {
|
|
68
|
+
if (!raw || !raw.trim())
|
|
69
|
+
return 'v3.5';
|
|
70
|
+
const s = raw.trim();
|
|
71
|
+
if (s.startsWith('v'))
|
|
72
|
+
return s;
|
|
73
|
+
const m1 = s.match(/Version\s*=\s*(v?\d+(\.\d+)?)/i);
|
|
74
|
+
if (m1?.[1])
|
|
75
|
+
return m1[1].startsWith('v') ? m1[1] : `v${m1[1]}`;
|
|
76
|
+
if (/^\d+(\.\d+)?$/.test(s))
|
|
77
|
+
return `v${s}`;
|
|
78
|
+
const m3 = s.match(/^net(\d)(\d)$/i);
|
|
79
|
+
if (m3)
|
|
80
|
+
return `v${m3[1]}.${m3[2]}`;
|
|
81
|
+
return 'v3.5';
|
|
82
|
+
}
|
|
83
|
+
function parseWebsiteProjectReferences(value) {
|
|
84
|
+
if (!value)
|
|
85
|
+
return [];
|
|
86
|
+
return value
|
|
87
|
+
.split(';')
|
|
88
|
+
.map(s => s.trim())
|
|
89
|
+
.filter(Boolean)
|
|
90
|
+
.map(item => item.split('|'))
|
|
91
|
+
.filter(parts => parts.length === 2)
|
|
92
|
+
.map(parts => ({
|
|
93
|
+
guid: parts[0].replace(/[{}]/g, '').trim(),
|
|
94
|
+
dllName: parts[1].trim(),
|
|
95
|
+
}));
|
|
96
|
+
}
|
|
97
|
+
function parseWebsites(blocks) {
|
|
98
|
+
const websiteBlocks = blocks.filter(b => b.typeGuid.toUpperCase() === WEBSITE_TYPE_GUID);
|
|
99
|
+
return websiteBlocks.map(b => {
|
|
100
|
+
const props = parseWebsiteProperties(b.rawBlock);
|
|
101
|
+
const targetFramework = normalizeFramework(props['TargetFramework'] || props['TargetFrameworkVersion']);
|
|
102
|
+
const physicalPath = (props['Debug.AspNetCompiler.PhysicalPath'] ||
|
|
103
|
+
props['Release.AspNetCompiler.PhysicalPath'] ||
|
|
104
|
+
b.relPath)
|
|
105
|
+
.replace(/\\+$/, '')
|
|
106
|
+
.replace(/\/+$/, '');
|
|
107
|
+
return {
|
|
108
|
+
name: b.name,
|
|
109
|
+
guid: b.guid,
|
|
110
|
+
physicalPath,
|
|
111
|
+
targetFramework,
|
|
112
|
+
projectReferences: parseWebsiteProjectReferences(props['ProjectReferences']),
|
|
113
|
+
};
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
function parseCSharpProjects(blocks) {
|
|
117
|
+
return blocks
|
|
118
|
+
.filter(b => {
|
|
119
|
+
const tg = b.typeGuid.toUpperCase();
|
|
120
|
+
return tg === CSHARP_LEGACY_TYPE_GUID || tg === CSHARP_SDK_TYPE_GUID;
|
|
121
|
+
})
|
|
122
|
+
.map(b => ({
|
|
123
|
+
guid: b.guid,
|
|
124
|
+
name: b.name,
|
|
125
|
+
relPath: b.relPath,
|
|
126
|
+
}));
|
|
127
|
+
}
|
package/bin/resolver.js
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.resolveDlls = resolveDlls;
|
|
37
|
+
exports.materializeRefs = materializeRefs;
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
const io_1 = require("./io");
|
|
40
|
+
function tryReadCsprojInfo(csprojAbsPath) {
|
|
41
|
+
if (!(0, io_1.exists)(csprojAbsPath))
|
|
42
|
+
return {};
|
|
43
|
+
const xml = (0, io_1.readText)(csprojAbsPath);
|
|
44
|
+
const am = xml.match(/<AssemblyName>\s*([^<]+)\s*<\/AssemblyName>/i);
|
|
45
|
+
const assemblyName = am?.[1]?.trim();
|
|
46
|
+
function pick(config) {
|
|
47
|
+
const rg = new RegExp(`<PropertyGroup[^>]*Condition="[^"]*${config}\\|AnyCPU[^"]*"[^>]*>[\\s\\S]*?<OutputPath>\\s*([^<]+)\\s*<\\/OutputPath>`, 'i');
|
|
48
|
+
return xml.match(rg)?.[1]?.trim();
|
|
49
|
+
}
|
|
50
|
+
return { assemblyName, debugOutputPath: pick('Debug'), releaseOutputPath: pick('Release') };
|
|
51
|
+
}
|
|
52
|
+
function limitedFindDllUnderProject(projDir, dllName) {
|
|
53
|
+
const candidates = [
|
|
54
|
+
path.join(projDir, 'bin', 'Debug', dllName),
|
|
55
|
+
path.join(projDir, 'bin', 'Release', dllName),
|
|
56
|
+
path.join(projDir, 'bin', dllName),
|
|
57
|
+
];
|
|
58
|
+
for (const p of candidates)
|
|
59
|
+
if ((0, io_1.exists)(p))
|
|
60
|
+
return p;
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
function resolveDlls(slnDir, website, csharpProjects) {
|
|
64
|
+
const websiteAbs = path.join(slnDir, website.physicalPath);
|
|
65
|
+
for (const ref of website.projectReferences) {
|
|
66
|
+
const proj = csharpProjects.find(p => p.guid.toLowerCase() === ref.guid.toLowerCase());
|
|
67
|
+
if (!proj) {
|
|
68
|
+
const binAbs = path.join(websiteAbs, 'Bin', ref.dllName);
|
|
69
|
+
ref.resolvedAbs = (0, io_1.exists)(binAbs) ? binAbs : path.join(websiteAbs, 'Bin', ref.dllName);
|
|
70
|
+
ref.resolvedFrom = (0, io_1.exists)(binAbs) ? 'website:Bin' : 'guess:website Bin';
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
ref.projectPath = proj.relPath;
|
|
74
|
+
const csprojAbs = path.join(slnDir, proj.relPath);
|
|
75
|
+
const projDirAbs = path.dirname(csprojAbs);
|
|
76
|
+
const info = tryReadCsprojInfo(csprojAbs);
|
|
77
|
+
const assemblyGuess = info.assemblyName ? `${info.assemblyName}.dll` : ref.dllName;
|
|
78
|
+
const byOutputPath = (outRaw) => {
|
|
79
|
+
if (!outRaw)
|
|
80
|
+
return null;
|
|
81
|
+
const outAbs = path.resolve(projDirAbs, outRaw);
|
|
82
|
+
const a = path.join(outAbs, ref.dllName);
|
|
83
|
+
const b = path.join(outAbs, assemblyGuess);
|
|
84
|
+
if ((0, io_1.exists)(a))
|
|
85
|
+
return a;
|
|
86
|
+
if ((0, io_1.exists)(b))
|
|
87
|
+
return b;
|
|
88
|
+
return a; // guess path (may not exist)
|
|
89
|
+
};
|
|
90
|
+
let dllAbs = byOutputPath(info.debugOutputPath);
|
|
91
|
+
let from = 'csproj:Debug OutputPath';
|
|
92
|
+
if (dllAbs && !(0, io_1.exists)(dllAbs)) {
|
|
93
|
+
const rel = byOutputPath(info.releaseOutputPath);
|
|
94
|
+
if (rel) {
|
|
95
|
+
dllAbs = rel;
|
|
96
|
+
from = 'csproj:Release OutputPath';
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
if (!dllAbs || !(0, io_1.exists)(dllAbs)) {
|
|
100
|
+
const found = limitedFindDllUnderProject(projDirAbs, ref.dllName);
|
|
101
|
+
if (found) {
|
|
102
|
+
dllAbs = found;
|
|
103
|
+
from = 'fallback:bin';
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
dllAbs = path.join(projDirAbs, 'bin', 'Debug', ref.dllName);
|
|
107
|
+
from = 'guess:bin\\Debug';
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
ref.resolvedAbs = dllAbs;
|
|
111
|
+
ref.resolvedFrom = from;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* mode=copy 時,把 resolvedAbs 存在的 dll 複製到 refsDir,
|
|
116
|
+
* 回傳每個 ref 的 HintPath(相對於 fake project dir)
|
|
117
|
+
*/
|
|
118
|
+
function materializeRefs(mode, fakeDirAbs, refsDirAbs, websiteAbs, websiteRelFromFake, website) {
|
|
119
|
+
const hintByDll = new Map();
|
|
120
|
+
if (mode === 'copy') {
|
|
121
|
+
(0, io_1.mkdirp)(refsDirAbs);
|
|
122
|
+
for (const ref of website.projectReferences) {
|
|
123
|
+
const dllName = ref.dllName;
|
|
124
|
+
const src = ref.resolvedAbs;
|
|
125
|
+
const dst = path.join(refsDirAbs, dllName);
|
|
126
|
+
if (src && (0, io_1.exists)(src)) {
|
|
127
|
+
try {
|
|
128
|
+
(0, io_1.copyFile)(src, dst);
|
|
129
|
+
hintByDll.set(dllName, path.relative(fakeDirAbs, dst).replace(/\//g, '\\')); // usually "refs\\X.dll"
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
// fallback to link if copy fails
|
|
133
|
+
const linked = path.join(websiteAbs, 'Bin', dllName);
|
|
134
|
+
hintByDll.set(dllName, path.join(websiteRelFromFake, 'Bin', dllName).replace(/\//g, '\\'));
|
|
135
|
+
ref.resolvedFrom = (ref.resolvedFrom || '') + ' + copyFail->link';
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
// fallback: link to website Bin
|
|
140
|
+
hintByDll.set(dllName, path.join(websiteRelFromFake, 'Bin', dllName).replace(/\//g, '\\'));
|
|
141
|
+
ref.resolvedFrom = (ref.resolvedFrom || '') + ' + missing->link';
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return hintByDll;
|
|
145
|
+
}
|
|
146
|
+
// mode === 'link'
|
|
147
|
+
for (const ref of website.projectReferences) {
|
|
148
|
+
const dllName = ref.dllName;
|
|
149
|
+
// Prefer resolvedAbs if exists, else website Bin
|
|
150
|
+
if (ref.resolvedAbs && (0, io_1.exists)(ref.resolvedAbs)) {
|
|
151
|
+
hintByDll.set(dllName, path.relative(fakeDirAbs, ref.resolvedAbs).replace(/\//g, '\\'));
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
hintByDll.set(dllName, path.join(websiteRelFromFake, 'Bin', dllName).replace(/\//g, '\\'));
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return hintByDll;
|
|
158
|
+
}
|
package/bin/sln.js
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.buildFakeSln = buildFakeSln;
|
|
37
|
+
const path = __importStar(require("path"));
|
|
38
|
+
const parser_1 = require("./parser");
|
|
39
|
+
const CSHARP_LEGACY_TYPE_GUID = 'FAE04EC0-301F-11D3-BF4B-00C04F79EFBC';
|
|
40
|
+
function detectEol(text) {
|
|
41
|
+
return text.includes('\r\n') ? '\r\n' : '\n';
|
|
42
|
+
}
|
|
43
|
+
function isUrlLike(p) {
|
|
44
|
+
return /:\/\//.test(p);
|
|
45
|
+
}
|
|
46
|
+
function adjustRelPath(relPath, slnDir, fakeSlnDir) {
|
|
47
|
+
if (!relPath)
|
|
48
|
+
return relPath;
|
|
49
|
+
if (isUrlLike(relPath))
|
|
50
|
+
return relPath;
|
|
51
|
+
if (path.isAbsolute(relPath))
|
|
52
|
+
return relPath;
|
|
53
|
+
const abs = path.resolve(slnDir, relPath);
|
|
54
|
+
const rel = path.relative(fakeSlnDir, abs) || '.';
|
|
55
|
+
return rel.replace(/\//g, '\\');
|
|
56
|
+
}
|
|
57
|
+
function replaceProjectPathInBlock(rawBlock, newRelPath) {
|
|
58
|
+
const re = /(Project\("?\{[0-9A-Fa-f\-]+\}"?\)\s*=\s*"[^"]+",\s*")([^"]+)(".*)/;
|
|
59
|
+
return rawBlock.replace(re, `$1${newRelPath}$3`);
|
|
60
|
+
}
|
|
61
|
+
function makeProjectBlock(typeGuid, name, relPath, guid, eol) {
|
|
62
|
+
return `Project("{${typeGuid}}") = "${name}", "${relPath}", "{${guid}}"${eol}EndProject`;
|
|
63
|
+
}
|
|
64
|
+
function buildFakeSln(slnContent, slnDir, fakeSlnDir, website, fakeCsprojAbs) {
|
|
65
|
+
const blocks = (0, parser_1.splitSlnProjects)(slnContent);
|
|
66
|
+
const eol = detectEol(slnContent);
|
|
67
|
+
let output = slnContent;
|
|
68
|
+
for (const b of blocks) {
|
|
69
|
+
if (b.guid.toLowerCase() === website.guid.toLowerCase()) {
|
|
70
|
+
const rel = path.relative(fakeSlnDir, fakeCsprojAbs).replace(/\//g, '\\') || '.';
|
|
71
|
+
const newBlock = makeProjectBlock(CSHARP_LEGACY_TYPE_GUID, b.name, rel, b.guid, eol);
|
|
72
|
+
output = output.replace(b.rawBlock, newBlock);
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
const adjusted = adjustRelPath(b.relPath, slnDir, fakeSlnDir);
|
|
76
|
+
const newBlock = replaceProjectPathInBlock(b.rawBlock, adjusted);
|
|
77
|
+
output = output.replace(b.rawBlock, newBlock);
|
|
78
|
+
}
|
|
79
|
+
return output;
|
|
80
|
+
}
|
package/bin/types.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "sln2csproj",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Generate a fake csproj for legacy ASP.NET WebSite projects in a .sln.",
|
|
5
|
+
"type": "commonjs",
|
|
6
|
+
"bin": {
|
|
7
|
+
"sln2csproj": "bin/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"bin",
|
|
11
|
+
"README.md",
|
|
12
|
+
"LICENSE"
|
|
13
|
+
],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "tsc -p tsconfig.json",
|
|
16
|
+
"dev": "ts-node src/cli.ts",
|
|
17
|
+
"gen": "node bin/cli.js",
|
|
18
|
+
"pretest": "npm run build",
|
|
19
|
+
"test": "node --test",
|
|
20
|
+
"prepare": "npm run build"
|
|
21
|
+
},
|
|
22
|
+
"engines": {
|
|
23
|
+
"node": ">=18"
|
|
24
|
+
},
|
|
25
|
+
"license": "MIT",
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@types/node": "^20.17.0",
|
|
28
|
+
"ts-node": "^10.9.2",
|
|
29
|
+
"typescript": "^5.5.4"
|
|
30
|
+
}
|
|
31
|
+
}
|