CtrlK
BlogDocsLog inGet started
Tessl Logo

evernote-multi-env-setup

Configure multi-environment setup for Evernote integrations. Use when setting up dev, staging, and production environments, or managing environment-specific configurations. Trigger with phrases like "evernote environments", "evernote staging", "evernote dev setup", "multiple environments evernote".

Install with Tessl CLI

npx tessl i github:jeremylongshore/claude-code-plugins-plus-skills --skill evernote-multi-env-setup
What are skills?

79

Does it follow best practices?

Validation for skill structure

SKILL.md
Review
Evals

Evernote Multi-Environment Setup

Overview

Configure separate development, staging, and production environments for Evernote integrations with proper isolation and configuration management.

Prerequisites

  • Multiple Evernote API keys (sandbox and production)
  • Environment management infrastructure
  • CI/CD pipeline

Environment Strategy

EnvironmentEvernote ModePurposeUsers
DevelopmentSandboxLocal developmentDevelopers
StagingSandboxIntegration testingQA team
ProductionProductionLive usersCustomers

Instructions

Step 1: Environment Configuration Files

# Project structure
project/
├── config/
│   ├── default.js        # Shared defaults
│   ├── development.js    # Dev overrides
│   ├── staging.js        # Staging overrides
│   └── production.js     # Production overrides
├── .env.development
├── .env.staging
├── .env.production
└── .env.example
// config/default.js
module.exports = {
  evernote: {
    requestTimeout: 30000,
    connectionTimeout: 10000,
    maxRetries: 3
  },
  cache: {
    ttl: 300,
    prefix: 'evernote:'
  },
  logging: {
    redactTokens: true
  }
};
// config/development.js
module.exports = {
  evernote: {
    sandbox: true,
    serviceHost: 'sandbox.evernote.com',
    // More lenient for debugging
    requestTimeout: 60000,
    maxRetries: 1
  },
  cache: {
    ttl: 60, // Short TTL for development
    prefix: 'evernote:dev:'
  },
  logging: {
    level: 'debug'
  }
};
// config/staging.js
module.exports = {
  evernote: {
    sandbox: true, // Still using sandbox for staging
    serviceHost: 'sandbox.evernote.com',
    requestTimeout: 30000,
    maxRetries: 2
  },
  cache: {
    ttl: 180,
    prefix: 'evernote:staging:'
  },
  logging: {
    level: 'info'
  }
};
// config/production.js
module.exports = {
  evernote: {
    sandbox: false, // Production mode
    serviceHost: 'www.evernote.com',
    requestTimeout: 30000,
    maxRetries: 3
  },
  cache: {
    ttl: 300,
    prefix: 'evernote:prod:'
  },
  logging: {
    level: 'warn'
  }
};

Step 2: Environment Variables

# .env.example
NODE_ENV=development

# Evernote
EVERNOTE_CONSUMER_KEY=
EVERNOTE_CONSUMER_SECRET=
EVERNOTE_SANDBOX=true

# App
APP_URL=http://localhost:3000
SESSION_SECRET=change-this-secret

# Database
DATABASE_URL=

# Redis
REDIS_URL=
# .env.development
NODE_ENV=development
EVERNOTE_CONSUMER_KEY=your-sandbox-key
EVERNOTE_CONSUMER_SECRET=your-sandbox-secret
EVERNOTE_SANDBOX=true
APP_URL=http://localhost:3000
SESSION_SECRET=dev-secret-not-secure
DATABASE_URL=postgresql://localhost:5432/evernote_dev
REDIS_URL=redis://localhost:6379/0
# .env.staging
NODE_ENV=staging
EVERNOTE_CONSUMER_KEY=your-sandbox-key
EVERNOTE_CONSUMER_SECRET=your-sandbox-secret
EVERNOTE_SANDBOX=true
APP_URL=https://staging.yourapp.com
# Secret managed externally (Vault, AWS Secrets, etc.)
# DATABASE_URL managed externally
# REDIS_URL managed externally
# .env.production
NODE_ENV=production
# ALL SECRETS MANAGED EXTERNALLY
EVERNOTE_SANDBOX=false
APP_URL=https://yourapp.com

Step 3: Configuration Loader

// config/index.js
const path = require('path');
const deepMerge = require('lodash.merge');

class ConfigLoader {
  constructor() {
    this.env = process.env.NODE_ENV || 'development';
    this.config = this.loadConfig();
  }

  loadConfig() {
    // Load default config
    const defaultConfig = require('./default');

    // Load environment-specific config
    let envConfig = {};
    try {
      envConfig = require(`./${this.env}`);
    } catch (error) {
      console.warn(`No config file for environment: ${this.env}`);
    }

    // Merge configs
    const merged = deepMerge({}, defaultConfig, envConfig);

    // Override with environment variables
    return this.applyEnvOverrides(merged);
  }

  applyEnvOverrides(config) {
    // Evernote overrides
    if (process.env.EVERNOTE_CONSUMER_KEY) {
      config.evernote.consumerKey = process.env.EVERNOTE_CONSUMER_KEY;
    }
    if (process.env.EVERNOTE_CONSUMER_SECRET) {
      config.evernote.consumerSecret = process.env.EVERNOTE_CONSUMER_SECRET;
    }
    if (process.env.EVERNOTE_SANDBOX !== undefined) {
      config.evernote.sandbox = process.env.EVERNOTE_SANDBOX === 'true';
    }

    // App overrides
    if (process.env.APP_URL) {
      config.app = config.app || {};
      config.app.url = process.env.APP_URL;
    }

    // Database overrides
    if (process.env.DATABASE_URL) {
      config.database = config.database || {};
      config.database.url = process.env.DATABASE_URL;
    }

    // Redis overrides
    if (process.env.REDIS_URL) {
      config.redis = config.redis || {};
      config.redis.url = process.env.REDIS_URL;
    }

    return config;
  }

  get(key) {
    const keys = key.split('.');
    let value = this.config;

    for (const k of keys) {
      if (value === undefined) return undefined;
      value = value[k];
    }

    return value;
  }

  getAll() {
    return this.config;
  }

  isProduction() {
    return this.env === 'production';
  }

  isDevelopment() {
    return this.env === 'development';
  }

  isStaging() {
    return this.env === 'staging';
  }
}

module.exports = new ConfigLoader();

Step 4: Environment-Aware Client Factory

// services/evernote-factory.js
const Evernote = require('evernote');
const config = require('../config');

class EvernoteClientFactory {
  static createOAuthClient() {
    return new Evernote.Client({
      consumerKey: config.get('evernote.consumerKey'),
      consumerSecret: config.get('evernote.consumerSecret'),
      sandbox: config.get('evernote.sandbox'),
      china: false
    });
  }

  static createAuthenticatedClient(accessToken) {
    return new Evernote.Client({
      token: accessToken,
      sandbox: config.get('evernote.sandbox')
    });
  }

  static getEnvironmentInfo() {
    return {
      environment: process.env.NODE_ENV,
      sandbox: config.get('evernote.sandbox'),
      serviceHost: config.get('evernote.sandbox')
        ? 'sandbox.evernote.com'
        : 'www.evernote.com'
    };
  }

  static validateEnvironment() {
    const errors = [];

    if (!config.get('evernote.consumerKey')) {
      errors.push('Missing EVERNOTE_CONSUMER_KEY');
    }

    if (!config.get('evernote.consumerSecret')) {
      errors.push('Missing EVERNOTE_CONSUMER_SECRET');
    }

    if (config.isProduction() && config.get('evernote.sandbox')) {
      errors.push('Production environment cannot use sandbox mode');
    }

    return {
      valid: errors.length === 0,
      errors
    };
  }
}

module.exports = EvernoteClientFactory;

Step 5: Docker Compose for Local Development

# docker-compose.yml
version: '3.8'

services:
  app:
    build:
      context: .
      dockerfile: Dockerfile.dev
    volumes:
      - .:/app
      - /app/node_modules
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=development
    env_file:
      - .env.development
    depends_on:
      - postgres
      - redis

  postgres:
    image: postgres:15-alpine
    volumes:
      - postgres_data:/var/lib/postgresql/data
    environment:
      POSTGRES_DB: evernote_dev
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: postgres
    ports:
      - "5432:5432"

  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"

volumes:
  postgres_data:

Step 6: Environment-Specific Middleware

// middleware/environment.js
const config = require('../config');

function environmentMiddleware(req, res, next) {
  // Add environment info to request
  req.environment = {
    name: process.env.NODE_ENV,
    sandbox: config.get('evernote.sandbox'),
    isProduction: config.isProduction()
  };

  // Development-only features
  if (config.isDevelopment()) {
    // Add debug headers
    res.setHeader('X-Environment', 'development');
    res.setHeader('X-Evernote-Mode', 'sandbox');
  }

  // Production safety checks
  if (config.isProduction()) {
    // Ensure HTTPS
    if (req.protocol !== 'https') {
      return res.redirect(301, `https://${req.hostname}${req.url}`);
    }
  }

  next();
}

function requireProduction(req, res, next) {
  if (!config.isProduction()) {
    return res.status(403).json({
      error: 'This endpoint is only available in production'
    });
  }
  next();
}

function blockInProduction(req, res, next) {
  if (config.isProduction()) {
    return res.status(403).json({
      error: 'This endpoint is not available in production'
    });
  }
  next();
}

module.exports = {
  environmentMiddleware,
  requireProduction,
  blockInProduction
};

Step 7: CI/CD Environment Configuration

# .github/workflows/deploy.yml
name: Deploy

on:
  push:
    branches: [main, develop]

jobs:
  deploy:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        include:
          - branch: develop
            environment: staging
            evernote_sandbox: 'true'
          - branch: main
            environment: production
            evernote_sandbox: 'false'

    steps:
      - uses: actions/checkout@v4

      - name: Set environment
        if: github.ref == format('refs/heads/{0}', matrix.branch)
        run: echo "DEPLOY_ENV=${{ matrix.environment }}" >> $GITHUB_ENV

      - name: Deploy to ${{ matrix.environment }}
        if: env.DEPLOY_ENV != ''
        uses: ./.github/actions/deploy
        with:
          environment: ${{ matrix.environment }}
        env:
          EVERNOTE_CONSUMER_KEY: ${{ secrets[format('EVERNOTE_CONSUMER_KEY_{0}', matrix.environment)] }}
          EVERNOTE_CONSUMER_SECRET: ${{ secrets[format('EVERNOTE_CONSUMER_SECRET_{0}', matrix.environment)] }}
          EVERNOTE_SANDBOX: ${{ matrix.evernote_sandbox }}

Step 8: Environment Health Check

// routes/health.js
const config = require('../config');
const EvernoteClientFactory = require('../services/evernote-factory');

router.get('/health/environment', async (req, res) => {
  const envInfo = EvernoteClientFactory.getEnvironmentInfo();
  const validation = EvernoteClientFactory.validateEnvironment();

  res.json({
    status: validation.valid ? 'ok' : 'error',
    environment: {
      ...envInfo,
      nodeEnv: process.env.NODE_ENV,
      version: process.env.APP_VERSION || 'unknown'
    },
    validation,
    checks: {
      database: await checkDatabase(),
      redis: await checkRedis(),
      evernote: await checkEvernoteConnectivity()
    }
  });
});

async function checkEvernoteConnectivity() {
  try {
    // Simple connectivity check - doesn't require auth
    const https = require('https');
    const host = config.get('evernote.sandbox')
      ? 'sandbox.evernote.com'
      : 'www.evernote.com';

    return new Promise((resolve) => {
      const req = https.get(`https://${host}`, { timeout: 5000 }, (res) => {
        resolve({ status: 'ok', host });
      });

      req.on('error', () => {
        resolve({ status: 'error', host, message: 'Connection failed' });
      });

      req.on('timeout', () => {
        req.destroy();
        resolve({ status: 'error', host, message: 'Timeout' });
      });
    });
  } catch (error) {
    return { status: 'error', message: error.message };
  }
}

Output

  • Environment-specific configuration files
  • Configuration loader with environment variable support
  • Environment-aware client factory
  • Docker Compose for local development
  • CI/CD environment configuration
  • Health check endpoints

Environment Checklist

## Per-Environment Checklist

### Development
- [ ] Sandbox credentials configured
- [ ] Local database running
- [ ] Redis cache running
- [ ] Debug logging enabled

### Staging
- [ ] Sandbox credentials (separate from dev)
- [ ] Staging database provisioned
- [ ] Staging Redis provisioned
- [ ] Monitoring configured

### Production
- [ ] Production API key approved
- [ ] Secrets in secure storage
- [ ] Production database provisioned
- [ ] Production Redis provisioned
- [ ] Monitoring and alerting
- [ ] Backup procedures

Resources

  • 12 Factor App - Config
  • Evernote Sandbox
  • Docker Compose

Next Steps

For observability setup, see evernote-observability.

Repository
jeremylongshore/claude-code-plugins-plus-skills
Last updated
Created

Is this your skill?

If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.