Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions src/config/configUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,37 @@ export interface CloudinaryEnvironment {
uploadPreset: string; // Default upload preset to use
}

/**
* Checks if the provided credentials are placeholder values.
* @param cloudName - The cloud name to check.
* @param apiKey - The API key to check.
* @param apiSecret - The API secret to check.
* @returns True if any of the credentials are placeholders, false otherwise.
*/
export function isPlaceholderConfig(
cloudName: string | null,
apiKey: string | null,
apiSecret: string | null
): boolean {
const placeholderPatterns = [
'your-cloud-name',
'<your-api-key>',
'<your-api-secret>',
'<your-default-upload-preset>',
'your-api-key',
'your-api-secret',
'your-default-upload-preset',
];

const values = [cloudName, apiKey, apiSecret].filter(Boolean) as string[];

return values.some(value =>
placeholderPatterns.some(pattern =>
value.toLowerCase().includes(pattern.toLowerCase())
)
);
}

/**
* Returns the absolute path to the global Cloudinary config file.
* If it doesn't exist, it creates one with a placeholder template.
Expand Down
6 changes: 6 additions & 0 deletions src/config/detectFolderMode.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as vscode from 'vscode';
import { generateUserAgent } from '../utils/userAgent';
import { isPlaceholderConfig } from './configUtils';

/**
* Detects if the cloud supports dynamic folders by making a request to the root folder API.
Expand All @@ -18,6 +19,11 @@ export default async function detectFolderMode(
return false;
}

// Don't make API calls with placeholder credentials
if (isPlaceholderConfig(cloudName, apiKey, apiSecret)) {
return false;
}

const authHeader = `Basic ${Buffer.from(`${apiKey}:${apiSecret}`).toString('base64')}`;
const url = `https://api.cloudinary.com/v1_1/${cloudName}/folders`;

Expand Down
34 changes: 34 additions & 0 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
getGlobalConfigPath,
loadEnvironments,
CloudinaryEnvironment,
isPlaceholderConfig,
} from "./config/configUtils";
import detectFolderMode from "./config/detectFolderMode";
import { registerAllCommands } from "./commands/registerCommands";
Expand Down Expand Up @@ -43,6 +44,28 @@ export async function activate(context: vscode.ExtensionContext) {
return;
}

// Check if credentials are placeholder values
if (isPlaceholderConfig(firstCloudName, selectedEnv.apiKey, selectedEnv.apiSecret)) {
// Initialize status bar with placeholder indicator (no popup message to avoid scaring new users)
statusBar = vscode.window.createStatusBarItem(
vscode.StatusBarAlignment.Right,
500
);
statusBar.text = `$(warning) Cloudinary: Not Configured`;
statusBar.tooltip = "Click to configure Cloudinary credentials";
statusBar.command = "cloudinary.openGlobalConfig";
statusBar.show();
context.subscriptions.push(statusBar);

// Still register the tree view but don't make API calls
vscode.window.registerTreeDataProvider(
"cloudinaryMediaLibrary",
cloudinaryProvider
);
registerAllCommands(context, cloudinaryProvider, statusBar);
return;
}

cloudinaryProvider.cloudName = firstCloudName;
cloudinaryProvider.apiKey = selectedEnv.apiKey;
cloudinaryProvider.apiSecret = selectedEnv.apiSecret;
Expand Down Expand Up @@ -93,12 +116,23 @@ export async function activate(context: vscode.ExtensionContext) {

const env = updatedEnvs[newCloudName!];

// Check if updated credentials are still placeholders
if (isPlaceholderConfig(newCloudName!, env.apiKey, env.apiSecret)) {
statusBar.text = `$(warning) Cloudinary: Not Configured`;
statusBar.tooltip = "Click to configure Cloudinary credentials";
statusBar.command = "cloudinary.openGlobalConfig";
// Don't show message - just update status bar silently
return;
}

cloudinaryProvider.cloudName = newCloudName;
cloudinaryProvider.apiKey = env.apiKey;
cloudinaryProvider.apiSecret = env.apiSecret;
cloudinaryProvider.uploadPreset = env.uploadPreset;

statusBar.text = `$(cloud) ${newCloudName}`;
statusBar.tooltip = "Click to switch Cloudinary environment";
statusBar.command = "cloudinary.switchEnvironment";

// Update user platform for analytics
(cloudinary.utils as any).userPlatform = generateUserAgent();
Expand Down
17 changes: 11 additions & 6 deletions src/tree/treeDataProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import * as vscode from 'vscode';
import { v2 as cloudinary } from 'cloudinary';
import CloudinaryItem from './cloudinaryItem';
import { handleCloudinaryError } from '../utils/cloudinaryErrorHandler';
import { isPlaceholderConfig } from '../config/configUtils';

export class CloudinaryTreeDataProvider implements vscode.TreeDataProvider<CloudinaryItem> {
// Cloudinary credentials
Expand Down Expand Up @@ -53,7 +54,11 @@ export class CloudinaryTreeDataProvider implements vscode.TreeDataProvider<Cloud

async getChildren(element?: CloudinaryItem): Promise<CloudinaryItem[]> {
if (!this.apiKey || !this.apiSecret || !this.cloudName) {
vscode.window.showErrorMessage("Cloudinary credentials are not set. Please update your settings.");
return [];
}

// Prevent API calls with placeholder credentials
if (isPlaceholderConfig(this.cloudName, this.apiKey, this.apiSecret)) {
return [];
}

Expand Down Expand Up @@ -90,7 +95,7 @@ export class CloudinaryTreeDataProvider implements vscode.TreeDataProvider<Cloud
.max_results(maxResults)
.with_field(["tags", "context", "metadata"]);

if (nextCursor) {assetQuery.next_cursor(nextCursor);}
if (nextCursor) { assetQuery.next_cursor(nextCursor); }

const [foldersResult, assetsResult] = await Promise.all([
folderPromise,
Expand All @@ -112,8 +117,8 @@ export class CloudinaryTreeDataProvider implements vscode.TreeDataProvider<Cloud
const filteredAssets = assetsResult.resources.filter((asset: any) => {
const isRootLoad = folderPath === '' && !this.dynamicFolders;
const isNestedAsset = asset.public_id.includes('/');
if (isRootLoad && isNestedAsset) {return false;}
if (this.viewState.resourceTypeFilter === 'all') {return true;}
if (isRootLoad && isNestedAsset) { return false; }
if (this.viewState.resourceTypeFilter === 'all') { return true; }
return asset.resource_type?.toLowerCase() === this.viewState.resourceTypeFilter;
});

Expand Down Expand Up @@ -168,7 +173,7 @@ export class CloudinaryTreeDataProvider implements vscode.TreeDataProvider<Cloud
.sort_by('public_id', 'asc')
.max_results(maxResults);

if (nextCursor) {searchQuery.next_cursor(nextCursor);}
if (nextCursor) { searchQuery.next_cursor(nextCursor); }

const assetsResult = await searchQuery.execute();

Expand Down Expand Up @@ -245,7 +250,7 @@ export class CloudinaryTreeDataProvider implements vscode.TreeDataProvider<Cloud

public updateLoadMoreItem(folderPath: string, nextCursor: string) {
const items = this.assetMap.get(folderPath);
if (!items) {return;}
if (!items) { return; }

const index = items.findIndex(
(item) =>
Expand Down