Skip to content

Commit b861d49

Browse files
committed
v-0.2.1: Remote SSH compatibility
1 parent b2e9649 commit b861d49

7 files changed

Lines changed: 59 additions & 39 deletions

File tree

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,15 @@
11
# Change Log
22

3+
## [0.2.1] - 2026-04-27
4+
5+
### Fixed
6+
7+
- Remote SSH/provider compatibility for copy/create structure by keeping filesystem operations Uri-native end-to-end (no early `fsPath` conversion).
8+
- Plain Text multi-root parsing for service-style trees and root-level file entries.
9+
- Plain Text validation handling for decorative separator lines between root blocks.
10+
- Tree formatter root-level connector behavior (`├──` / `└──`) and spacing between root sections.
11+
- JSON structure support for `null` file leaves (e.g. `"Dockerfile": null`) in validation, preview, and creation.
12+
313
## [0.2.0] - 2025-10-09
414

515
### Added

README.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
<img src="./assets/banner.webp" alt="Folder Structure Pro" style="border: 4px solid rgba(255, 255, 255, 0.9); border-radius: 8px; box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);">
22

33
<p align="center">
4-
<a href="https://marketplace.visualstudio.com/items?itemName=iamshreydxv.copy-folder-structure">
5-
<img src="https://img.shields.io/visual-studio-marketplace/v/iamshreydxv.copy-folder-structure" alt="Marketplace Version"/>
4+
<a href="https://github.com/ShreyPurohit/folder-structure-pro-vscode/releases">
5+
<img src="https://img.shields.io/github/last-commit/ShreyPurohit/folder-structure-pro-vscode" alt="Last Commit"/>
66
</a>
7-
<a href="https://marketplace.visualstudio.com/items?itemName=iamshreydxv.copy-folder-structure">
8-
<img src="https://img.shields.io/visual-studio-marketplace/d/iamshreydxv.copy-folder-structure" alt="Downloads"/>
7+
<a href="https://github.com/ShreyPurohit/folder-structure-pro-vscode/issues">
8+
<img src="https://img.shields.io/github/issues/ShreyPurohit/folder-structure-pro-vscode" alt="Open Issues"/>
99
</a>
10-
<a href="https://marketplace.visualstudio.com/items?itemName=iamshreydxv.copy-folder-structure">
11-
<img src="https://img.shields.io/visual-studio-marketplace/r/iamshreydxv.copy-folder-structure" alt="Ratings"/>
10+
<a href="https://github.com/ShreyPurohit/folder-structure-pro-vscode/stargazers">
11+
<img src="https://img.shields.io/github/stars/ShreyPurohit/folder-structure-pro-vscode" alt="GitHub Stars"/>
1212
</a>
1313
<a href="https://github.com/sponsors/ShreyPurohit">
1414
<img src="https://img.shields.io/badge/Sponsor-GitHub%20Sponsors-ff69b4?style=flat-square&logo=github" alt="Sponsor me on GitHub" />

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"displayName": "Folder Structure Pro",
44
"description": "Easily copy & create folder structures, file names and jump-ready line paths with a single click.",
55
"publisher": "iamshreydxv",
6-
"version": "0.2.0",
6+
"version": "0.2.1",
77
"engines": {
88
"vscode": "^1.54.0"
99
},

src/commands/copyStructure.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ export async function copyStructure(uri: vscode.Uri): Promise<void> {
1919
target = pick[0];
2020
}
2121

22-
const structure = await StructureService.getStructure(target.fsPath);
22+
const structure = await StructureService.getStructure(target);
2323
const outputFormat = vscode.workspace
2424
.getConfiguration('folderStructure')
2525
.get<OutputFormat>('outputFormat', DEFAULT_OUTPUT_FORMAT);

src/commands/createStructure.ts

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import * as path from 'path';
21
import * as vscode from 'vscode';
32
import { ERROR_MESSAGES } from '../constants';
43
import { StructureService } from '../services/structure';
@@ -7,8 +6,6 @@ import { OutputFormat, WebviewMessage } from '../types';
76
import { createStructureInputPanel } from '../ui/webview';
87
import { DEFAULT_OUTPUT_FORMAT } from '../constants';
98

10-
const FORMAT_OPTIONS: OutputFormat[] = ['Plain Text Format', 'JSON Format'];
11-
129
export async function createStructure(): Promise<void> {
1310
try {
1411
const defaultUri = vscode.workspace.workspaceFolders?.[0]?.uri;
@@ -20,8 +17,8 @@ export async function createStructure(): Promise<void> {
2017
openLabel: 'Select target folder',
2118
});
2219

23-
const resolvedPath = pick?.[0]?.fsPath;
24-
if (!resolvedPath) {
20+
const resolvedUri = pick?.[0];
21+
if (!resolvedUri) {
2522
throw new Error(ERROR_MESSAGES.TARGET_REQUIRED);
2623
}
2724

@@ -113,7 +110,7 @@ export async function createStructure(): Promise<void> {
113110

114111
const existing: string[] = [];
115112
for (const name of targets) {
116-
const full = path.join(resolvedPath, name);
113+
const full = vscode.Uri.joinPath(resolvedUri, name);
117114
if (await FileSystemService.exists(full)) {
118115
existing.push(name);
119116
}
@@ -132,7 +129,7 @@ export async function createStructure(): Promise<void> {
132129
}
133130
if (selection === 'Replace') {
134131
for (const name of existing) {
135-
const full = path.join(resolvedPath, name);
132+
const full = vscode.Uri.joinPath(resolvedUri, name);
136133
await FileSystemService.delete(full, {
137134
recursive: true,
138135
useTrash: true,
@@ -143,7 +140,7 @@ export async function createStructure(): Promise<void> {
143140
}
144141

145142
await StructureService.createStructure(
146-
resolvedPath,
143+
resolvedUri,
147144
message.text,
148145
currentFormat,
149146
);

src/services/gitignore.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,19 @@
11
import ignore from 'ignore';
2-
import * as path from 'path';
32
import * as vscode from 'vscode';
43
import { FileSystemService } from './fileSystem';
54

65
export class GitignoreService {
7-
static async loadRules(dirPath: string): Promise<string[]> {
6+
static async loadRules(dirUri: vscode.Uri): Promise<string[]> {
87
const config = vscode.workspace.getConfiguration('folderStructure');
98
const ignorePatterns = config.get<string[]>('ignorePatterns', ['node_modules', '.*']);
109
const respectGitignore = config.get<boolean>('respectGitignore', true);
1110

1211
let rules = [...ignorePatterns];
1312

1413
if (respectGitignore) {
15-
const gitignorePath = path.join(dirPath, '.gitignore');
16-
if (await FileSystemService.exists(gitignorePath)) {
17-
const content = await FileSystemService.readFile(gitignorePath);
14+
const gitignoreUri = vscode.Uri.joinPath(dirUri, '.gitignore');
15+
if (await FileSystemService.exists(gitignoreUri)) {
16+
const content = await FileSystemService.readFile(gitignoreUri);
1817
rules = rules.concat(
1918
content.split('\n').filter((line) => line.trim() && !line.startsWith('#')),
2019
);

src/services/structure.ts

Lines changed: 32 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -9,34 +9,48 @@ import { GitignestFormatter, JsonFormatter } from './formatters';
99
import { GitignoreService } from './gitignore';
1010

1111
export class StructureService {
12-
static async getStructure(dirPath: string): Promise<FolderStructure> {
13-
const ignoreRules = await GitignoreService.loadRules(dirPath);
12+
private static relativeFromRoot(rootUri: vscode.Uri, targetUri: vscode.Uri): string {
13+
const root = rootUri.path.replace(/\/+$/, '');
14+
const target = targetUri.path;
15+
if (!target.startsWith(root)) {
16+
return target.replace(/^\/+/, '');
17+
}
18+
return target.slice(root.length).replace(/^\/+/, '');
19+
}
20+
21+
private static uriBaseName(uri: vscode.Uri): string {
22+
const segments = uri.path.split('/').filter(Boolean);
23+
return segments[segments.length - 1] ?? uri.path;
24+
}
25+
26+
static async getStructure(dirUri: vscode.Uri): Promise<FolderStructure> {
27+
const ignoreRules = await GitignoreService.loadRules(dirUri);
1428
const ig = ignore().add(ignoreRules);
15-
const folderName = path.basename(dirPath);
29+
const folderName = this.uriBaseName(dirUri);
1630
return {
17-
[folderName]: await this.buildStructure(dirPath, ig, dirPath),
31+
[folderName]: await this.buildStructure(dirUri, ig, dirUri),
1832
};
1933
}
2034

2135
static async buildStructure(
22-
dirPath: string,
36+
dirUri: vscode.Uri,
2337
ig: ReturnType<typeof ignore>,
24-
rootPath: string,
38+
rootUri: vscode.Uri,
2539
): Promise<FolderStructure> {
2640
const structure: FolderStructure = {};
27-
const entries = await FileSystemService.readdir(dirPath);
41+
const entries = await FileSystemService.readdir(dirUri);
2842

2943
for (const entry of entries) {
30-
const fullPath = path.join(dirPath, entry.name);
31-
const relFromRoot = path.relative(rootPath, fullPath);
44+
const fullUri = vscode.Uri.joinPath(dirUri, entry.name);
45+
const relFromRoot = this.relativeFromRoot(rootUri, fullUri);
3246

3347
if (entry.name.startsWith('.') || ig.ignores(relFromRoot)) {
3448
continue;
3549
}
3650

3751
const isDir = (entry.type & vscode.FileType.Directory) === vscode.FileType.Directory;
3852
if (isDir) {
39-
structure[entry.name] = await this.buildStructure(fullPath, ig, rootPath);
53+
structure[entry.name] = await this.buildStructure(fullUri, ig, rootUri);
4054
} else {
4155
const ext = this.fileTypeFor(entry.name);
4256
const base = this.baseNameFor(entry.name);
@@ -60,7 +74,7 @@ export class StructureService {
6074
}
6175

6276
static async createStructure(
63-
basePath: string,
77+
baseUri: vscode.Uri,
6478
content: string,
6579
format: OutputFormat,
6680
): Promise<void> {
@@ -69,7 +83,7 @@ export class StructureService {
6983
}
7084

7185
if (format === 'Plain Text Format') {
72-
await this.createFromPlainText(basePath, content);
86+
await this.createFromPlainText(baseUri, content);
7387
} else {
7488
try {
7589
const structure = JSON.parse(content);
@@ -78,14 +92,14 @@ export class StructureService {
7892
'Invalid JSON structure: use nested objects for folders and string file types for files',
7993
);
8094
}
81-
await this.createFromJSON(basePath, structure);
95+
await this.createFromJSON(baseUri, structure);
8296
} catch (error) {
8397
throw new Error('Invalid JSON format');
8498
}
8599
}
86100
}
87101

88-
private static async createFromPlainText(basePath: string, content: string): Promise<void> {
102+
private static async createFromPlainText(baseUri: vscode.Uri, content: string): Promise<void> {
89103
const rawLines = content.split('\n');
90104
// Ignore the first line (header/title) regardless of its text
91105
const lines = rawLines
@@ -107,7 +121,7 @@ export class StructureService {
107121
pathStack.pop();
108122
}
109123

110-
const fullPath = path.join(basePath, ...pathStack, node.name);
124+
const fullPath = vscode.Uri.joinPath(baseUri, ...pathStack, node.name);
111125

112126
if (node.isDirectory) {
113127
await FileSystemService.mkdirIfAbsent(fullPath);
@@ -126,16 +140,16 @@ export class StructureService {
126140
}
127141

128142
private static async createFromJSON(
129-
basePath: string,
143+
baseUri: vscode.Uri,
130144
structure: FolderStructure,
131145
): Promise<void> {
132146
for (const [key, value] of Object.entries(structure)) {
133147
if (typeof value === 'string') {
134148
const fileName = value === 'file' || value.trim() === '' ? key : `${key}.${value}`;
135-
const fullPath = path.join(basePath, fileName);
149+
const fullPath = vscode.Uri.joinPath(baseUri, fileName);
136150
await FileSystemService.writeFileIfAbsent(fullPath, '');
137151
} else {
138-
const dirPath = path.join(basePath, key);
152+
const dirPath = vscode.Uri.joinPath(baseUri, key);
139153
await FileSystemService.mkdirIfAbsent(dirPath);
140154
await this.createFromJSON(dirPath, value);
141155
}

0 commit comments

Comments
 (0)