Skip to content

Commit 4f7c925

Browse files
committed
fix(create): normalize add-to-app output paths on Windows
Normalize generated and deleted output file paths to project-relative keys before compare/write/delete, preventing duplicated path segments when Windows drive letters are missing. Fixes #329
1 parent dea259a commit 4f7c925

File tree

3 files changed

+84
-7
lines changed

3 files changed

+84
-7
lines changed

.changeset/ten-wombats-learn.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@tanstack/create': patch
3+
---
4+
5+
Fix `tanstack add` on Windows when generated output paths lose the drive letter, preventing duplicated project paths from being written.

packages/create/src/add-to-app.ts

Lines changed: 42 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,41 @@ export async function writeFiles(
8888
},
8989
forced: boolean,
9090
) {
91+
const toRelativePath = (filePath: string) => {
92+
const normalizedFilePath = filePath.replace(/\\/g, '/')
93+
const normalizedCwd = cwd.replace(/\\/g, '/')
94+
const cwdWithoutDrive = normalizedCwd.replace(/^[a-zA-Z]:/, '')
95+
const cwdWithoutDriveNoLeading = cwdWithoutDrive.replace(/^\/+/, '')
96+
97+
if (normalizedFilePath === normalizedCwd) {
98+
return ''
99+
}
100+
if (normalizedFilePath.startsWith(`${normalizedCwd}/`)) {
101+
return normalizedFilePath.slice(normalizedCwd.length + 1)
102+
}
103+
if (normalizedFilePath === cwdWithoutDrive) {
104+
return ''
105+
}
106+
if (normalizedFilePath.startsWith(`${cwdWithoutDrive}/`)) {
107+
return normalizedFilePath.slice(cwdWithoutDrive.length + 1)
108+
}
109+
if (normalizedFilePath === cwdWithoutDriveNoLeading) {
110+
return ''
111+
}
112+
if (normalizedFilePath.startsWith(`${cwdWithoutDriveNoLeading}/`)) {
113+
return normalizedFilePath.slice(cwdWithoutDriveNoLeading.length + 1)
114+
}
115+
116+
return normalizedFilePath.replace(/^\/+/, '')
117+
}
118+
119+
const relativeOutputFiles = Object.keys(output.files).reduce<
120+
Record<string, string>
121+
>((acc, filePath) => {
122+
acc[toRelativePath(filePath)] = output.files[filePath]
123+
return acc
124+
}, {})
125+
91126
const currentFiles = await recursivelyGatherFilesFromEnvironment(
92127
environment,
93128
cwd,
@@ -96,10 +131,9 @@ export async function writeFiles(
96131

97132
const overwrittenFiles: Array<string> = []
98133
const changedFiles: Array<string> = []
99-
for (const file of Object.keys(output.files)) {
100-
const relativeFile = file.replace(cwd, '')
134+
for (const relativeFile of Object.keys(relativeOutputFiles)) {
101135
if (currentFiles[relativeFile]) {
102-
if (currentFiles[relativeFile] !== output.files[file]) {
136+
if (currentFiles[relativeFile] !== relativeOutputFiles[relativeFile]) {
103137
overwrittenFiles.push(relativeFile)
104138
}
105139
} else {
@@ -118,9 +152,10 @@ export async function writeFiles(
118152
}
119153
}
120154

121-
for (const file of output.deletedFiles) {
122-
if (environment.exists(resolve(cwd, file))) {
123-
await environment.deleteFile(resolve(cwd, file))
155+
for (const filePath of output.deletedFiles) {
156+
const relativeFilePath = toRelativePath(filePath)
157+
if (environment.exists(resolve(cwd, relativeFilePath))) {
158+
await environment.deleteFile(resolve(cwd, relativeFilePath))
124159
}
125160
}
126161

@@ -132,7 +167,7 @@ export async function writeFiles(
132167

133168
for (const file of [...changedFiles, ...overwrittenFiles]) {
134169
const fName = basename(file)
135-
const contents = output.files[file]
170+
const contents = relativeOutputFiles[file]
136171
if (fName === 'package.json') {
137172
const currentJson = JSON.parse(
138173
await environment.readFile(resolve(cwd, file)),

packages/create/tests/add-to-app.test.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,43 @@ describe('writeFiles', () => {
297297
environment.finishRun()
298298
expect(output.deletedFiles).toEqual(['bloop.txt'])
299299
})
300+
301+
it('should normalize windows-style output paths before writing', async () => {
302+
const writePaths: Array<string> = []
303+
const environment = {
304+
readdir: async () => [],
305+
isDirectory: () => false,
306+
readFile: async () => '',
307+
exists: () => false,
308+
deleteFile: async () => {},
309+
writeFileBase64: async () => {},
310+
writeFile: async (path: string) => {
311+
writePaths.push(path)
312+
},
313+
startStep: () => {},
314+
finishStep: () => {},
315+
warn: () => {},
316+
confirm: async () => true,
317+
} as any
318+
319+
await writeFiles(
320+
environment,
321+
'C:\\Users\\marv\\workspace\\fenbi-tanstack',
322+
{
323+
files: {
324+
'Users/marv/workspace/fenbi-tanstack/src/components.json': '{}',
325+
},
326+
deletedFiles: [],
327+
},
328+
true,
329+
)
330+
331+
expect(writePaths).toHaveLength(1)
332+
expect(writePaths[0]).toMatch(/components\.json$/)
333+
expect(writePaths[0]).not.toMatch(
334+
/Users[\\/]marv[\\/]workspace[\\/]fenbi-tanstack[\\/]Users[\\/]marv[\\/]workspace[\\/]fenbi-tanstack/,
335+
)
336+
})
300337
})
301338

302339
describe('runNewCommands', () => {

0 commit comments

Comments
 (0)