evernote-upgrade-migration
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseEvernote 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版本历史
| Version | Node.js | Key Changes |
|---|---|---|
| 2.0.x | 10+ | Promise-based API, ES6 modules |
| 1.25.x | 8+ | Legacy callback pattern |
| 1.x | 6+ | Original SDK |
| 版本 | Node.js | 主要变更 |
|---|---|---|
| 2.0.x | 10+ | 基于Promise的API、ES6模块 |
| 1.25.x | 8+ | 旧版回调模式 |
| 1.x | 6+ | 初代SDK |
Instructions
操作步骤
Step 1: Check Current Version
步骤1:检查当前版本
bash
undefinedbash
undefinedCheck 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
undefinednpm outdated evernote
undefinedStep 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
undefinedbash
undefinedUpdate 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
undefinednpm update
undefinedStep 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
undefinedmarkdown
undefinedSDK 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- 移除兼容层(验证期过后)
- 删除备份文件
- 更新文档
- 归档迁移脚本
undefinedOutput
输出内容
- 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
错误处理
| Issue | Cause | Solution |
|---|---|---|
| Old import style | Use |
| Mixed patterns | Use Promise or callback, not both |
| Using old callback-only method | Update to Promise-based method |
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 旧版导入方式 | 使用 |
| 混合使用两种模式 | 仅使用Promise或回调中的一种 |
| 使用了仅支持回调的旧版方法 | 更新为基于Promise的方法 |
Resources
参考资源
Next Steps
后续步骤
For CI/CD integration, see .
evernote-ci-integration如需集成CI/CD,请查看。
evernote-ci-integration