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 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();
@@ -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, '&amp;')
41
+ .replace(/</g, '&lt;')
42
+ .replace(/>/g, '&gt;')
43
+ .replace(/"/g, '&quot;')
44
+ .replace(/'/g, '&apos;');
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
+ }
@@ -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
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
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
+ }