evernote-upgrade-migration

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Evernote Upgrade & Migration

Evernote SDK升级与迁移

Overview

概述

Guide for upgrading Evernote SDK versions, handling breaking changes, and migrating legacy integrations to current API patterns.
本指南介绍如何升级Evernote SDK版本、处理破坏性变更,以及将旧版集成迁移至当前API模式。

Prerequisites

前提条件

  • Existing Evernote integration
  • Test environment for validation
  • Understanding of current implementation
  • 已有的Evernote集成
  • 用于验证的测试环境
  • 了解当前实现逻辑

SDK Version History

SDK版本历史

VersionNode.jsKey Changes
2.0.x10+Promise-based API, ES6 modules
1.25.x8+Legacy callback pattern
1.x6+Original SDK
版本Node.js主要变更
2.0.x10+基于Promise的API、ES6模块
1.25.x8+旧版回调模式
1.x6+初代SDK

Instructions

操作步骤

Step 1: Check Current Version

步骤1:检查当前版本

bash
undefined
bash
undefined

Check installed version

检查已安装版本

npm list evernote
npm list evernote

Check available versions

查看可用版本

npm view evernote versions
npm view evernote versions

Check for outdated packages

检查过时包

npm outdated evernote
undefined
npm outdated evernote
undefined

Step 2: Review Breaking Changes

步骤2:查看破坏性变更

javascript
// SDK 1.x -> 2.x Breaking Changes

// 1. Client initialization changed
// OLD (1.x):
var Evernote = require('evernote').Evernote;
var client = new Evernote.Client({
  consumerKey: 'key',
  consumerSecret: 'secret',
  sandbox: true
});

// NEW (2.x):
const Evernote = require('evernote');
const client = new Evernote.Client({
  consumerKey: 'key',
  consumerSecret: 'secret',
  sandbox: true
});

// 2. API calls return Promises (not callbacks)
// OLD (1.x):
noteStore.listNotebooks(function(err, notebooks) {
  if (err) console.error(err);
  else console.log(notebooks);
});

// NEW (2.x):
noteStore.listNotebooks()
  .then(notebooks => console.log(notebooks))
  .catch(err => console.error(err));

// Or with async/await:
const notebooks = await noteStore.listNotebooks();

// 3. Type constructors
// OLD (1.x):
var note = new Evernote.Note();

// NEW (2.x):
const note = new Evernote.Types.Note();
javascript
// SDK 1.x 迁移至2.x的破坏性变更

// 1. 客户端初始化方式变更
// 旧版(1.x):
var Evernote = require('evernote').Evernote;
var client = new Evernote.Client({
  consumerKey: 'key',
  consumerSecret: 'secret',
  sandbox: true
});

// 新版(2.x):
const Evernote = require('evernote');
const client = new Evernote.Client({
  consumerKey: 'key',
  consumerSecret: 'secret',
  sandbox: true
});

// 2. API调用返回Promise(而非回调)
// 旧版(1.x):
noteStore.listNotebooks(function(err, notebooks) {
  if (err) console.error(err);
  else console.log(notebooks);
});

// 新版(2.x):
noteStore.listNotebooks()
  .then(notebooks => console.log(notebooks))
  .catch(err => console.error(err));

// 或使用async/await:
const notebooks = await noteStore.listNotebooks();

// 3. 类型构造函数
// 旧版(1.x):
var note = new Evernote.Note();

// 新版(2.x):
const note = new Evernote.Types.Note();

Step 3: Migration Script

步骤3:迁移脚本

javascript
// scripts/migrate-to-v2.js
const fs = require('fs');
const path = require('path');
const glob = require('glob');

const migrations = [
  // Client import
  {
    pattern: /require\(['"]evernote['"]\)\.Evernote/g,
    replacement: "require('evernote')"
  },

  // Type constructors
  {
    pattern: /new Evernote\.Note\(\)/g,
    replacement: 'new Evernote.Types.Note()'
  },
  {
    pattern: /new Evernote\.Notebook\(\)/g,
    replacement: 'new Evernote.Types.Notebook()'
  },
  {
    pattern: /new Evernote\.Tag\(\)/g,
    replacement: 'new Evernote.Types.Tag()'
  },
  {
    pattern: /new Evernote\.Resource\(\)/g,
    replacement: 'new Evernote.Types.Resource()'
  },

  // NoteFilter
  {
    pattern: /new Evernote\.NoteFilter\(\)/g,
    replacement: 'new Evernote.NoteStore.NoteFilter()'
  },

  // NotesMetadataResultSpec
  {
    pattern: /new Evernote\.NotesMetadataResultSpec\(\)/g,
    replacement: 'new Evernote.NoteStore.NotesMetadataResultSpec()'
  }
];

function migrateFile(filePath) {
  let content = fs.readFileSync(filePath, 'utf8');
  let modified = false;

  for (const { pattern, replacement } of migrations) {
    if (pattern.test(content)) {
      content = content.replace(pattern, replacement);
      modified = true;
    }
  }

  if (modified) {
    // Create backup
    fs.writeFileSync(`${filePath}.bak`, fs.readFileSync(filePath));
    // Write migrated file
    fs.writeFileSync(filePath, content);
    console.log(`Migrated: ${filePath}`);
    return true;
  }

  return false;
}

// Run migration
const files = glob.sync('src/**/*.js');
let migratedCount = 0;

for (const file of files) {
  if (migrateFile(file)) {
    migratedCount++;
  }
}

console.log(`\nMigration complete. ${migratedCount} files modified.`);
console.log('Backup files created with .bak extension');
javascript
// scripts/migrate-to-v2.js
const fs = require('fs');
const path = require('path');
const glob = require('glob');

const migrations = [
  // 客户端导入语句
  {
    pattern: /require\(['"]evernote['"]\)\.Evernote/g,
    replacement: "require('evernote')"
  },

  // 类型构造函数
  {
    pattern: /new Evernote\.Note\(\)/g,
    replacement: 'new Evernote.Types.Note()'
  },
  {
    pattern: /new Evernote\.Notebook\(\)/g,
    replacement: 'new Evernote.Types.Notebook()'
  },
  {
    pattern: /new Evernote\.Tag\(\)/g,
    replacement: 'new Evernote.Types.Tag()'
  },
  {
    pattern: /new Evernote\.Resource\(\)/g,
    replacement: 'new Evernote.Types.Resource()'
  },

  // NoteFilter
  {
    pattern: /new Evernote\.NoteFilter\(\)/g,
    replacement: 'new Evernote.NoteStore.NoteFilter()'
  },

  // NotesMetadataResultSpec
  {
    pattern: /new Evernote\.NotesMetadataResultSpec\(\)/g,
    replacement: 'new Evernote.NoteStore.NotesMetadataResultSpec()'
  }
];

function migrateFile(filePath) {
  let content = fs.readFileSync(filePath, 'utf8');
  let modified = false;

  for (const { pattern, replacement } of migrations) {
    if (pattern.test(content)) {
      content = content.replace(pattern, replacement);
      modified = true;
    }
  }

  if (modified) {
    // 创建备份
    fs.writeFileSync(`${filePath}.bak`, fs.readFileSync(filePath));
    // 写入迁移后的文件
    fs.writeFileSync(filePath, content);
    console.log(`已迁移: ${filePath}`);
    return true;
  }

  return false;
}

// 执行迁移
const files = glob.sync('src/**/*.js');
let migratedCount = 0;

for (const file of files) {
  if (migrateFile(file)) {
    migratedCount++;
  }
}

console.log(`\n迁移完成。共修改${migratedCount}个文件。`);
console.log('已创建带.bak后缀的备份文件');

Step 4: Convert Callbacks to Promises

步骤4:将回调转换为Promise

javascript
// utils/promisify-legacy.js

/**
 * Wrapper for legacy callback-based code
 */
function promisifyNoteStore(noteStore) {
  const promisified = {};

  // List of methods that need promisification
  const methods = [
    'listNotebooks',
    'getNotebook',
    'createNotebook',
    'updateNotebook',
    'listTags',
    'getTag',
    'createTag',
    'getNote',
    'createNote',
    'updateNote',
    'deleteNote',
    'findNotesMetadata',
    'getNoteContent',
    'getResource'
  ];

  for (const method of methods) {
    promisified[method] = (...args) => {
      return new Promise((resolve, reject) => {
        noteStore[method](...args, (err, result) => {
          if (err) reject(err);
          else resolve(result);
        });
      });
    };
  }

  return promisified;
}

module.exports = { promisifyNoteStore };
javascript
// utils/promisify-legacy.js

/**
 * 旧版回调代码的包装器
 */
function promisifyNoteStore(noteStore) {
  const promisified = {};

  // 需要Promise化的方法列表
  const methods = [
    'listNotebooks',
    'getNotebook',
    'createNotebook',
    'updateNotebook',
    'listTags',
    'getTag',
    'createTag',
    'getNote',
    'createNote',
    'updateNote',
    'deleteNote',
    'findNotesMetadata',
    'getNoteContent',
    'getResource'
  ];

  for (const method of methods) {
    promisified[method] = (...args) => {
      return new Promise((resolve, reject) => {
        noteStore[method](...args, (err, result) => {
          if (err) reject(err);
          else resolve(result);
        });
      });
    };
  }

  return promisified;
}

module.exports = { promisifyNoteStore };

Step 5: Upgrade Dependencies

步骤5:升级依赖

bash
undefined
bash
undefined

Update to latest version

更新至最新版本

npm install evernote@latest
npm install evernote@latest

If using TypeScript, update types

若使用TypeScript,更新类型定义

npm install @types/evernote@latest --save-dev
npm install @types/evernote@latest --save-dev

Update related dependencies

更新相关依赖

npm update
undefined
npm update
undefined

Step 6: Test Suite Updates

步骤6:更新测试套件

javascript
// tests/migration-tests.js
const assert = require('assert');
const Evernote = require('evernote');

describe('SDK v2 Migration Tests', () => {
  let client;
  let noteStore;

  before(() => {
    client = new Evernote.Client({
      token: process.env.EVERNOTE_ACCESS_TOKEN,
      sandbox: true
    });
    noteStore = client.getNoteStore();
  });

  describe('Type Constructors', () => {
    it('should create Note using Types namespace', () => {
      const note = new Evernote.Types.Note();
      assert(note);
      note.title = 'Test';
      assert.equal(note.title, 'Test');
    });

    it('should create NoteFilter using NoteStore namespace', () => {
      const filter = new Evernote.NoteStore.NoteFilter({
        words: 'test'
      });
      assert(filter);
      assert.equal(filter.words, 'test');
    });
  });

  describe('Promise-based API', () => {
    it('should return Promise from listNotebooks', async () => {
      const result = noteStore.listNotebooks();
      assert(result instanceof Promise);
    });

    it('should resolve notebooks array', async () => {
      const notebooks = await noteStore.listNotebooks();
      assert(Array.isArray(notebooks));
    });
  });

  describe('Error Handling', () => {
    it('should reject with EDAMUserException for invalid data', async () => {
      const note = new Evernote.Types.Note();
      // Missing required fields

      try {
        await noteStore.createNote(note);
        assert.fail('Should have thrown');
      } catch (error) {
        assert(error.errorCode !== undefined);
      }
    });
  });
});
javascript
// tests/migration-tests.js
const assert = require('assert');
const Evernote = require('evernote');

describe('SDK v2迁移测试', () => {
  let client;
  let noteStore;

  before(() => {
    client = new Evernote.Client({
      token: process.env.EVERNOTE_ACCESS_TOKEN,
      sandbox: true
    });
    noteStore = client.getNoteStore();
  });

  describe('类型构造函数', () => {
    it('应能通过Types命名空间创建Note', () => {
      const note = new Evernote.Types.Note();
      assert(note);
      note.title = '测试';
      assert.equal(note.title, '测试');
    });

    it('应能通过NoteStore命名空间创建NoteFilter', () => {
      const filter = new Evernote.NoteStore.NoteFilter({
        words: 'test'
      });
      assert(filter);
      assert.equal(filter.words, 'test');
    });
  });

  describe('基于Promise的API', () => {
    it('listNotebooks应返回Promise', async () => {
      const result = noteStore.listNotebooks();
      assert(result instanceof Promise);
    });

    it('应能解析出笔记本数组', async () => {
      const notebooks = await noteStore.listNotebooks();
      assert(Array.isArray(notebooks));
    });
  });

  describe('错误处理', () => {
    it('无效数据应抛出EDAMUserException', async () => {
      const note = new Evernote.Types.Note();
      // 缺少必填字段

      try {
        await noteStore.createNote(note);
        assert.fail('本应抛出异常');
      } catch (error) {
        assert(error.errorCode !== undefined);
      }
    });
  });
});

Step 7: Compatibility Layer

步骤7:兼容层

javascript
// utils/compatibility.js

/**
 * Compatibility layer for gradual migration
 * Supports both callback and Promise patterns
 */
class CompatibleNoteStore {
  constructor(noteStore) {
    this.store = noteStore;
  }

  listNotebooks(callback) {
    const promise = this.store.listNotebooks();

    if (callback) {
      // Legacy callback support
      promise
        .then(result => callback(null, result))
        .catch(error => callback(error, null));
      return;
    }

    return promise;
  }

  getNote(guid, withContent, withResources, withRecognition, withAlternate, callback) {
    const promise = this.store.getNote(
      guid,
      withContent,
      withResources,
      withRecognition,
      withAlternate
    );

    if (callback) {
      promise
        .then(result => callback(null, result))
        .catch(error => callback(error, null));
      return;
    }

    return promise;
  }

  createNote(note, callback) {
    const promise = this.store.createNote(note);

    if (callback) {
      promise
        .then(result => callback(null, result))
        .catch(error => callback(error, null));
      return;
    }

    return promise;
  }

  // Add more methods as needed
}

module.exports = CompatibleNoteStore;
javascript
// utils/compatibility.js

/**
 * 用于渐进式迁移的兼容层
 * 同时支持回调和Promise模式
 */
class CompatibleNoteStore {
  constructor(noteStore) {
    this.store = noteStore;
  }

  listNotebooks(callback) {
    const promise = this.store.listNotebooks();

    if (callback) {
      // 支持旧版回调
      promise
        .then(result => callback(null, result))
        .catch(error => callback(error, null));
      return;
    }

    return promise;
  }

  getNote(guid, withContent, withResources, withRecognition, withAlternate, callback) {
    const promise = this.store.getNote(
      guid,
      withContent,
      withResources,
      withRecognition,
      withAlternate
    );

    if (callback) {
      promise
        .then(result => callback(null, result))
        .catch(error => callback(error, null));
      return;
    }

    return promise;
  }

  createNote(note, callback) {
    const promise = this.store.createNote(note);

    if (callback) {
      promise
        .then(result => callback(null, result))
        .catch(error => callback(error, null));
      return;
    }

    return promise;
  }

  // 根据需要添加更多方法
}

module.exports = CompatibleNoteStore;

Step 8: Deprecation Warnings

步骤8:弃用警告

javascript
// utils/deprecation-warnings.js

const deprecations = new Set();

function warnOnce(message) {
  if (!deprecations.has(message)) {
    deprecations.add(message);
    console.warn(`[DEPRECATION WARNING] ${message}`);
  }
}

// Proxy to add deprecation warnings
function wrapWithDeprecationWarnings(noteStore) {
  return new Proxy(noteStore, {
    get(target, prop) {
      // Warn about deprecated patterns
      if (prop === 'getNote') {
        return (...args) => {
          if (args.length === 6 && typeof args[5] === 'function') {
            warnOnce(
              'Callback pattern is deprecated. ' +
              'Use Promise: noteStore.getNote(...).then(note => ...)'
            );
          }
          return target[prop](...args);
        };
      }

      return target[prop];
    }
  });
}

module.exports = { wrapWithDeprecationWarnings };
javascript
// utils/deprecation-warnings.js

const deprecations = new Set();

function warnOnce(message) {
  if (!deprecations.has(message)) {
    deprecations.add(message);
    console.warn(`[弃用警告] ${message}`);
  }
}

// 通过代理添加弃用警告
function wrapWithDeprecationWarnings(noteStore) {
  return new Proxy(noteStore, {
    get(target, prop) {
      // 针对已弃用模式发出警告
      if (prop === 'getNote') {
        return (...args) => {
          if (args.length === 6 && typeof args[5] === 'function') {
            warnOnce(
              '回调模式已弃用。' +
              '请使用Promise: noteStore.getNote(...).then(note => ...)'
            );
          }
          return target[prop](...args);
        };
      }

      return target[prop];
    }
  });
}

module.exports = { wrapWithDeprecationWarnings };

Migration Checklist

迁移检查清单

markdown
undefined
markdown
undefined

SDK v2 Migration Checklist

SDK v2迁移检查清单

Pre-Migration

迁移前准备

  • Backup current codebase
  • Document current SDK version
  • Review breaking changes
  • Set up test environment
  • 备份当前代码库
  • 记录当前SDK版本
  • 查看破坏性变更
  • 搭建测试环境

Code Changes

代码修改

  • Update import statements
  • Update type constructors (Types namespace)
  • Update filter constructors (NoteStore namespace)
  • Convert callbacks to Promises/async-await
  • Update error handling for Promise rejections
  • 更新导入语句
  • 更新类型构造函数(使用Types命名空间)
  • 更新过滤器构造函数(使用NoteStore命名空间)
  • 将回调转换为Promises/async-await
  • 更新Promise拒绝的错误处理逻辑

Testing

测试环节

  • Run migration script on test branch
  • Execute full test suite
  • Test OAuth flow end-to-end
  • Test note CRUD operations
  • Test search functionality
  • Verify error handling
  • 在测试分支运行迁移脚本
  • 执行完整测试套件
  • 端到端测试OAuth流程
  • 测试笔记CRUD操作
  • 测试搜索功能
  • 验证错误处理逻辑

Deployment

部署上线

  • Deploy to staging
  • Run integration tests
  • Monitor for errors
  • Deploy to production
  • Monitor metrics
  • 部署至预发布环境
  • 运行集成测试
  • 监控错误情况
  • 部署至生产环境
  • 监控指标数据

Cleanup

收尾工作

  • Remove compatibility layers (after validation period)
  • Remove backup files
  • Update documentation
  • Archive migration scripts
undefined
  • 移除兼容层(验证期过后)
  • 删除备份文件
  • 更新文档
  • 归档迁移脚本
undefined

Output

输出内容

  • SDK version comparison
  • Automated migration script
  • Callback to Promise conversion
  • Compatibility layer for gradual migration
  • Migration test suite
  • Deprecation warning system
  • SDK版本对比
  • 自动化迁移脚本
  • 回调转Promise转换工具
  • 用于渐进式迁移的兼容层
  • 迁移测试套件
  • 弃用警告系统

Error Handling

错误处理

IssueCauseSolution
Evernote.Note is not a constructor
Old import styleUse
Evernote.Types.Note
callback is not a function
Mixed patternsUse Promise or callback, not both
Cannot read property 'then'
Using old callback-only methodUpdate to Promise-based method
问题原因解决方案
Evernote.Note is not a constructor
旧版导入方式使用
Evernote.Types.Note
callback is not a function
混合使用两种模式仅使用Promise或回调中的一种
Cannot read property 'then'
使用了仅支持回调的旧版方法更新为基于Promise的方法

Resources

参考资源

Next Steps

后续步骤

For CI/CD integration, see
evernote-ci-integration
.
如需集成CI/CD,请查看
evernote-ci-integration