refactor: flatten Microsoft skills from nested to flat directory structure
Rewrote sync_microsoft_skills.py (v4) to use each SKILL.md's frontmatter 'name' field as the flat directory name under skills/, replacing the nested skills/official/microsoft/<lang>/<category>/<service>/ hierarchy. This fixes CI failures caused by the indexing, validation, and catalog scripts expecting skills/<id>/SKILL.md (depth 1). Changes: - Rewrite scripts/sync_microsoft_skills.py for flat output with collision detection - Update scripts/tests/inspect_microsoft_repo.py for flat name mapping - Update scripts/tests/test_comprehensive_coverage.py for name uniqueness checks - Delete skills/official/ nested directory - Add 129 Microsoft skills as flat directories (e.g. skills/azure-mgmt-botservice-dotnet/) - Move attribution files to docs/ (LICENSE-MICROSOFT, microsoft-skills-attribution.json) - Rebuild skills_index.json, CATALOG.md, README.md (845 total skills)
This commit is contained in:
493
skills/azure-storage-file-share-ts/SKILL.md
Normal file
493
skills/azure-storage-file-share-ts/SKILL.md
Normal file
@@ -0,0 +1,493 @@
|
||||
---
|
||||
name: azure-storage-file-share-ts
|
||||
description: |
|
||||
Azure File Share JavaScript/TypeScript SDK (@azure/storage-file-share) for SMB file share operations. Use for creating shares, managing directories, uploading/downloading files, and handling file metadata. Supports Azure Files SMB protocol scenarios. Triggers: "file share", "@azure/storage-file-share", "ShareServiceClient", "ShareClient", "SMB", "Azure Files".
|
||||
package: @azure/storage-file-share
|
||||
---
|
||||
|
||||
# @azure/storage-file-share (TypeScript/JavaScript)
|
||||
|
||||
SDK for Azure File Share operations — SMB file shares, directories, and file operations.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
npm install @azure/storage-file-share @azure/identity
|
||||
```
|
||||
|
||||
**Current Version**: 12.x
|
||||
**Node.js**: >= 18.0.0
|
||||
|
||||
## Environment Variables
|
||||
|
||||
```bash
|
||||
AZURE_STORAGE_ACCOUNT_NAME=<account-name>
|
||||
AZURE_STORAGE_ACCOUNT_KEY=<account-key>
|
||||
# OR connection string
|
||||
AZURE_STORAGE_CONNECTION_STRING=DefaultEndpointsProtocol=https;AccountName=...
|
||||
```
|
||||
|
||||
## Authentication
|
||||
|
||||
### Connection String (Simplest)
|
||||
|
||||
```typescript
|
||||
import { ShareServiceClient } from "@azure/storage-file-share";
|
||||
|
||||
const client = ShareServiceClient.fromConnectionString(
|
||||
process.env.AZURE_STORAGE_CONNECTION_STRING!
|
||||
);
|
||||
```
|
||||
|
||||
### StorageSharedKeyCredential (Node.js only)
|
||||
|
||||
```typescript
|
||||
import { ShareServiceClient, StorageSharedKeyCredential } from "@azure/storage-file-share";
|
||||
|
||||
const accountName = process.env.AZURE_STORAGE_ACCOUNT_NAME!;
|
||||
const accountKey = process.env.AZURE_STORAGE_ACCOUNT_KEY!;
|
||||
|
||||
const sharedKeyCredential = new StorageSharedKeyCredential(accountName, accountKey);
|
||||
const client = new ShareServiceClient(
|
||||
`https://${accountName}.file.core.windows.net`,
|
||||
sharedKeyCredential
|
||||
);
|
||||
```
|
||||
|
||||
### DefaultAzureCredential
|
||||
|
||||
```typescript
|
||||
import { ShareServiceClient } from "@azure/storage-file-share";
|
||||
import { DefaultAzureCredential } from "@azure/identity";
|
||||
|
||||
const accountName = process.env.AZURE_STORAGE_ACCOUNT_NAME!;
|
||||
const client = new ShareServiceClient(
|
||||
`https://${accountName}.file.core.windows.net`,
|
||||
new DefaultAzureCredential()
|
||||
);
|
||||
```
|
||||
|
||||
### SAS Token
|
||||
|
||||
```typescript
|
||||
import { ShareServiceClient } from "@azure/storage-file-share";
|
||||
|
||||
const accountName = process.env.AZURE_STORAGE_ACCOUNT_NAME!;
|
||||
const sasToken = process.env.AZURE_STORAGE_SAS_TOKEN!;
|
||||
|
||||
const client = new ShareServiceClient(
|
||||
`https://${accountName}.file.core.windows.net${sasToken}`
|
||||
);
|
||||
```
|
||||
|
||||
## Client Hierarchy
|
||||
|
||||
```
|
||||
ShareServiceClient (account level)
|
||||
└── ShareClient (share level)
|
||||
└── ShareDirectoryClient (directory level)
|
||||
└── ShareFileClient (file level)
|
||||
```
|
||||
|
||||
## Share Operations
|
||||
|
||||
### Create Share
|
||||
|
||||
```typescript
|
||||
const shareClient = client.getShareClient("my-share");
|
||||
await shareClient.create();
|
||||
|
||||
// Create with quota (in GB)
|
||||
await shareClient.create({ quota: 100 });
|
||||
```
|
||||
|
||||
### List Shares
|
||||
|
||||
```typescript
|
||||
for await (const share of client.listShares()) {
|
||||
console.log(share.name, share.properties.quota);
|
||||
}
|
||||
|
||||
// With prefix filter
|
||||
for await (const share of client.listShares({ prefix: "logs-" })) {
|
||||
console.log(share.name);
|
||||
}
|
||||
```
|
||||
|
||||
### Delete Share
|
||||
|
||||
```typescript
|
||||
await shareClient.delete();
|
||||
|
||||
// Delete if exists
|
||||
await shareClient.deleteIfExists();
|
||||
```
|
||||
|
||||
### Get Share Properties
|
||||
|
||||
```typescript
|
||||
const properties = await shareClient.getProperties();
|
||||
console.log("Quota:", properties.quota, "GB");
|
||||
console.log("Last Modified:", properties.lastModified);
|
||||
```
|
||||
|
||||
### Set Share Quota
|
||||
|
||||
```typescript
|
||||
await shareClient.setQuota(200); // 200 GB
|
||||
```
|
||||
|
||||
## Directory Operations
|
||||
|
||||
### Create Directory
|
||||
|
||||
```typescript
|
||||
const directoryClient = shareClient.getDirectoryClient("my-directory");
|
||||
await directoryClient.create();
|
||||
|
||||
// Create nested directory
|
||||
const nestedDir = shareClient.getDirectoryClient("parent/child/grandchild");
|
||||
await nestedDir.create();
|
||||
```
|
||||
|
||||
### List Directories and Files
|
||||
|
||||
```typescript
|
||||
const directoryClient = shareClient.getDirectoryClient("my-directory");
|
||||
|
||||
for await (const item of directoryClient.listFilesAndDirectories()) {
|
||||
if (item.kind === "directory") {
|
||||
console.log(`[DIR] ${item.name}`);
|
||||
} else {
|
||||
console.log(`[FILE] ${item.name} (${item.properties.contentLength} bytes)`);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Delete Directory
|
||||
|
||||
```typescript
|
||||
await directoryClient.delete();
|
||||
|
||||
// Delete if exists
|
||||
await directoryClient.deleteIfExists();
|
||||
```
|
||||
|
||||
### Check if Directory Exists
|
||||
|
||||
```typescript
|
||||
const exists = await directoryClient.exists();
|
||||
if (!exists) {
|
||||
await directoryClient.create();
|
||||
}
|
||||
```
|
||||
|
||||
## File Operations
|
||||
|
||||
### Upload File (Simple)
|
||||
|
||||
```typescript
|
||||
const fileClient = shareClient
|
||||
.getDirectoryClient("my-directory")
|
||||
.getFileClient("my-file.txt");
|
||||
|
||||
// Upload string
|
||||
const content = "Hello, World!";
|
||||
await fileClient.create(content.length);
|
||||
await fileClient.uploadRange(content, 0, content.length);
|
||||
```
|
||||
|
||||
### Upload File (Node.js - from local file)
|
||||
|
||||
```typescript
|
||||
import * as fs from "fs";
|
||||
import * as path from "path";
|
||||
|
||||
const fileClient = shareClient.rootDirectoryClient.getFileClient("uploaded.txt");
|
||||
const localFilePath = "/path/to/local/file.txt";
|
||||
const fileSize = fs.statSync(localFilePath).size;
|
||||
|
||||
await fileClient.create(fileSize);
|
||||
await fileClient.uploadFile(localFilePath);
|
||||
```
|
||||
|
||||
### Upload File (Buffer)
|
||||
|
||||
```typescript
|
||||
const buffer = Buffer.from("Hello, Azure Files!");
|
||||
const fileClient = shareClient.rootDirectoryClient.getFileClient("buffer-file.txt");
|
||||
|
||||
await fileClient.create(buffer.length);
|
||||
await fileClient.uploadRange(buffer, 0, buffer.length);
|
||||
```
|
||||
|
||||
### Upload File (Stream)
|
||||
|
||||
```typescript
|
||||
import * as fs from "fs";
|
||||
|
||||
const fileClient = shareClient.rootDirectoryClient.getFileClient("streamed.txt");
|
||||
const readStream = fs.createReadStream("/path/to/local/file.txt");
|
||||
const fileSize = fs.statSync("/path/to/local/file.txt").size;
|
||||
|
||||
await fileClient.create(fileSize);
|
||||
await fileClient.uploadStream(readStream, fileSize, 4 * 1024 * 1024, 4); // 4MB buffer, 4 concurrency
|
||||
```
|
||||
|
||||
### Download File
|
||||
|
||||
```typescript
|
||||
const fileClient = shareClient
|
||||
.getDirectoryClient("my-directory")
|
||||
.getFileClient("my-file.txt");
|
||||
|
||||
const downloadResponse = await fileClient.download();
|
||||
|
||||
// Read as string
|
||||
const chunks: Buffer[] = [];
|
||||
for await (const chunk of downloadResponse.readableStreamBody!) {
|
||||
chunks.push(Buffer.from(chunk));
|
||||
}
|
||||
const content = Buffer.concat(chunks).toString("utf-8");
|
||||
```
|
||||
|
||||
### Download to File (Node.js)
|
||||
|
||||
```typescript
|
||||
const fileClient = shareClient.rootDirectoryClient.getFileClient("my-file.txt");
|
||||
await fileClient.downloadToFile("/path/to/local/destination.txt");
|
||||
```
|
||||
|
||||
### Download to Buffer (Node.js)
|
||||
|
||||
```typescript
|
||||
const fileClient = shareClient.rootDirectoryClient.getFileClient("my-file.txt");
|
||||
const buffer = await fileClient.downloadToBuffer();
|
||||
console.log(buffer.toString());
|
||||
```
|
||||
|
||||
### Delete File
|
||||
|
||||
```typescript
|
||||
const fileClient = shareClient.rootDirectoryClient.getFileClient("my-file.txt");
|
||||
await fileClient.delete();
|
||||
|
||||
// Delete if exists
|
||||
await fileClient.deleteIfExists();
|
||||
```
|
||||
|
||||
### Copy File
|
||||
|
||||
```typescript
|
||||
const sourceUrl = "https://account.file.core.windows.net/share/source.txt";
|
||||
const destFileClient = shareClient.rootDirectoryClient.getFileClient("destination.txt");
|
||||
|
||||
// Start copy operation
|
||||
const copyPoller = await destFileClient.startCopyFromURL(sourceUrl);
|
||||
await copyPoller.pollUntilDone();
|
||||
```
|
||||
|
||||
## File Properties & Metadata
|
||||
|
||||
### Get File Properties
|
||||
|
||||
```typescript
|
||||
const fileClient = shareClient.rootDirectoryClient.getFileClient("my-file.txt");
|
||||
const properties = await fileClient.getProperties();
|
||||
|
||||
console.log("Content-Length:", properties.contentLength);
|
||||
console.log("Content-Type:", properties.contentType);
|
||||
console.log("Last Modified:", properties.lastModified);
|
||||
console.log("ETag:", properties.etag);
|
||||
```
|
||||
|
||||
### Set Metadata
|
||||
|
||||
```typescript
|
||||
await fileClient.setMetadata({
|
||||
author: "John Doe",
|
||||
category: "documents",
|
||||
});
|
||||
```
|
||||
|
||||
### Set HTTP Headers
|
||||
|
||||
```typescript
|
||||
await fileClient.setHttpHeaders({
|
||||
fileContentType: "text/plain",
|
||||
fileCacheControl: "max-age=3600",
|
||||
fileContentDisposition: "attachment; filename=download.txt",
|
||||
});
|
||||
```
|
||||
|
||||
## Range Operations
|
||||
|
||||
### Upload Range
|
||||
|
||||
```typescript
|
||||
const data = Buffer.from("partial content");
|
||||
await fileClient.uploadRange(data, 100, data.length); // Write at offset 100
|
||||
```
|
||||
|
||||
### Download Range
|
||||
|
||||
```typescript
|
||||
const downloadResponse = await fileClient.download(100, 50); // offset 100, length 50
|
||||
```
|
||||
|
||||
### Clear Range
|
||||
|
||||
```typescript
|
||||
await fileClient.clearRange(0, 100); // Clear first 100 bytes
|
||||
```
|
||||
|
||||
## Snapshot Operations
|
||||
|
||||
### Create Snapshot
|
||||
|
||||
```typescript
|
||||
const snapshotResponse = await shareClient.createSnapshot();
|
||||
console.log("Snapshot:", snapshotResponse.snapshot);
|
||||
```
|
||||
|
||||
### Access Snapshot
|
||||
|
||||
```typescript
|
||||
const snapshotShareClient = shareClient.withSnapshot(snapshotResponse.snapshot!);
|
||||
const snapshotFileClient = snapshotShareClient.rootDirectoryClient.getFileClient("file.txt");
|
||||
const content = await snapshotFileClient.downloadToBuffer();
|
||||
```
|
||||
|
||||
### Delete Snapshot
|
||||
|
||||
```typescript
|
||||
await shareClient.delete({ deleteSnapshots: "include" });
|
||||
```
|
||||
|
||||
## SAS Token Generation (Node.js only)
|
||||
|
||||
### Generate File SAS
|
||||
|
||||
```typescript
|
||||
import {
|
||||
generateFileSASQueryParameters,
|
||||
FileSASPermissions,
|
||||
StorageSharedKeyCredential,
|
||||
} from "@azure/storage-file-share";
|
||||
|
||||
const sharedKeyCredential = new StorageSharedKeyCredential(accountName, accountKey);
|
||||
|
||||
const sasToken = generateFileSASQueryParameters(
|
||||
{
|
||||
shareName: "my-share",
|
||||
filePath: "my-directory/my-file.txt",
|
||||
permissions: FileSASPermissions.parse("r"), // read only
|
||||
expiresOn: new Date(Date.now() + 3600 * 1000), // 1 hour
|
||||
},
|
||||
sharedKeyCredential
|
||||
).toString();
|
||||
|
||||
const sasUrl = `https://${accountName}.file.core.windows.net/my-share/my-directory/my-file.txt?${sasToken}`;
|
||||
```
|
||||
|
||||
### Generate Share SAS
|
||||
|
||||
```typescript
|
||||
import { ShareSASPermissions, generateFileSASQueryParameters } from "@azure/storage-file-share";
|
||||
|
||||
const sasToken = generateFileSASQueryParameters(
|
||||
{
|
||||
shareName: "my-share",
|
||||
permissions: ShareSASPermissions.parse("rcwdl"), // read, create, write, delete, list
|
||||
expiresOn: new Date(Date.now() + 24 * 3600 * 1000), // 24 hours
|
||||
},
|
||||
sharedKeyCredential
|
||||
).toString();
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
```typescript
|
||||
import { RestError } from "@azure/storage-file-share";
|
||||
|
||||
try {
|
||||
await shareClient.create();
|
||||
} catch (error) {
|
||||
if (error instanceof RestError) {
|
||||
switch (error.statusCode) {
|
||||
case 404:
|
||||
console.log("Share not found");
|
||||
break;
|
||||
case 409:
|
||||
console.log("Share already exists");
|
||||
break;
|
||||
case 403:
|
||||
console.log("Access denied");
|
||||
break;
|
||||
default:
|
||||
console.error(`Storage error ${error.statusCode}: ${error.message}`);
|
||||
}
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
```
|
||||
|
||||
## TypeScript Types Reference
|
||||
|
||||
```typescript
|
||||
import {
|
||||
// Clients
|
||||
ShareServiceClient,
|
||||
ShareClient,
|
||||
ShareDirectoryClient,
|
||||
ShareFileClient,
|
||||
|
||||
// Authentication
|
||||
StorageSharedKeyCredential,
|
||||
AnonymousCredential,
|
||||
|
||||
// SAS
|
||||
FileSASPermissions,
|
||||
ShareSASPermissions,
|
||||
AccountSASPermissions,
|
||||
AccountSASServices,
|
||||
AccountSASResourceTypes,
|
||||
generateFileSASQueryParameters,
|
||||
generateAccountSASQueryParameters,
|
||||
|
||||
// Options & Responses
|
||||
ShareCreateResponse,
|
||||
FileDownloadResponseModel,
|
||||
DirectoryItem,
|
||||
FileItem,
|
||||
ShareProperties,
|
||||
FileProperties,
|
||||
|
||||
// Errors
|
||||
RestError,
|
||||
} from "@azure/storage-file-share";
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Use connection strings for simplicity** — Easiest setup for development
|
||||
2. **Use DefaultAzureCredential for production** — Enable managed identity in Azure
|
||||
3. **Set quotas on shares** — Prevent unexpected storage costs
|
||||
4. **Use streaming for large files** — `uploadStream`/`downloadToFile` for files > 256MB
|
||||
5. **Use ranges for partial updates** — More efficient than full file replacement
|
||||
6. **Create snapshots before major changes** — Point-in-time recovery
|
||||
7. **Handle errors gracefully** — Check `RestError.statusCode` for specific handling
|
||||
8. **Use `*IfExists` methods** — For idempotent operations
|
||||
|
||||
## Platform Differences
|
||||
|
||||
| Feature | Node.js | Browser |
|
||||
|---------|---------|---------|
|
||||
| `StorageSharedKeyCredential` | ✅ | ❌ |
|
||||
| `uploadFile()` | ✅ | ❌ |
|
||||
| `uploadStream()` | ✅ | ❌ |
|
||||
| `downloadToFile()` | ✅ | ❌ |
|
||||
| `downloadToBuffer()` | ✅ | ❌ |
|
||||
| SAS generation | ✅ | ❌ |
|
||||
| DefaultAzureCredential | ✅ | ❌ |
|
||||
| Anonymous/SAS access | ✅ | ✅ |
|
||||
Reference in New Issue
Block a user