Skip to content

Commit

Permalink
feat: Add support for Node 22 (microsoft#4808)
Browse files Browse the repository at this point in the history
* Use tsup to compile ESM-only filenamify package

* Update yamls to include node 22

* Throw error on crypto.createDecipher for node 22

* Add NODE_NO_WARNINGS flag to bf dialogs:merge exec

* Apply feedback

* Update unit tests
  • Loading branch information
ceciliaavila authored Dec 10, 2024
1 parent 89ada0e commit 6751c41
Show file tree
Hide file tree
Showing 16 changed files with 95 additions and 95 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/depcheck.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ jobs:
steps:
- uses: actions/checkout@v2

- name: use node 20.x
- name: use node 22.x
uses: actions/setup-node@v2-beta
with:
node-version: 20.x
node-version: 22.x

- name: yarn cache dir
id: yarn-cache-dir
Expand All @@ -33,7 +33,7 @@ jobs:
- uses: actions/cache@v2
with:
path: ${{ steps.yarn-cache-dir.outputs.dir }}
key: ${{ runner.os }}-node20.x-yarn-${{ hashFiles('**/yarn.lock') }}
key: ${{ runner.os }}-node22.x-yarn-${{ hashFiles('**/yarn.lock') }}

- name: yarn
run: yarn --frozen-lockfile
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ jobs:
steps:
- uses: actions/checkout@v2

- name: use node 20.x
- name: use node 22.x
uses: actions/setup-node@v2-beta
with:
node-version: 20.x
node-version: 22.x

- name: yarn cache dir
id: yarn-cache-dir
Expand All @@ -33,7 +33,7 @@ jobs:
- uses: actions/cache@v2
with:
path: ${{ steps.yarn-cache-dir.outputs.dir }}
key: ${{ runner.os }}-node20.x-yarn-${{ hashFiles('**/yarn.lock') }}
key: ${{ runner.os }}-node22.x-yarn-${{ hashFiles('**/yarn.lock') }}

- name: yarn
run: yarn --frozen-lockfile
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/test-compat.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ jobs:
steps:
- uses: actions/checkout@v2

- name: use node 20.x
- name: use node 22.x
uses: actions/setup-node@v2-beta
with:
node-version: 20.x
node-version: 22.x

- name: yarn cache dir
id: yarn-cache-dir
Expand All @@ -33,7 +33,7 @@ jobs:
- uses: actions/cache@v2
with:
path: ${{ steps.yarn-cache-dir.outputs.dir }}
key: ${{ runner.os }}-node20.x-yarn-${{ hashFiles('**/yarn.lock') }}
key: ${{ runner.os }}-node22.x-yarn-${{ hashFiles('**/yarn.lock') }}

- name: yarn
run: yarn --frozen-lockfile
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/test-consumer.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ jobs:
steps:
- uses: actions/checkout@v2

- name: use node 20.x
- name: use node 22.x
uses: actions/setup-node@v2-beta
with:
node-version: 20.x
node-version: 22.x

- name: yarn cache dir
id: yarn-cache-dir
Expand All @@ -33,7 +33,7 @@ jobs:
- uses: actions/cache@v2
with:
path: ${{ steps.yarn-cache-dir.outputs.dir }}
key: ${{ runner.os }}-node20.x-yarn-${{ hashFiles('**/yarn.lock') }}
key: ${{ runner.os }}-node22.x-yarn-${{ hashFiles('**/yarn.lock') }}

- name: yarn
run: yarn --frozen-lockfile
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/test-repoutils.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ jobs:
steps:
- uses: actions/checkout@v2

- name: use node 20.x
- name: use node 22.x
uses: actions/setup-node@v2-beta
with:
node-version: 20.x
node-version: 22.x

- name: yarn cache dir
id: yarn-cache-dir
Expand All @@ -33,7 +33,7 @@ jobs:
- uses: actions/cache@v2
with:
path: ${{ steps.yarn-cache-dir.outputs.dir }}
key: ${{ runner.os }}-node20.x-yarn-${{ hashFiles('**/yarn.lock') }}
key: ${{ runner.os }}-node22.x-yarn-${{ hashFiles('**/yarn.lock') }}

- name: yarn
run: yarn --frozen-lockfile
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/test-schemas.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ jobs:
steps:
- uses: actions/checkout@v2

- name: use node 20.x
- name: use node 22.x
uses: actions/setup-node@v2-beta
with:
node-version: 20.x
node-version: 22.x

- name: yarn cache dir
id: yarn-cache-dir
Expand All @@ -33,7 +33,7 @@ jobs:
- uses: actions/cache@v2
with:
path: ${{ steps.yarn-cache-dir.outputs.dir }}
key: ${{ runner.os }}-node20.x-yarn-${{ hashFiles('**/yarn.lock') }}
key: ${{ runner.os }}-node22.x-yarn-${{ hashFiles('**/yarn.lock') }}

- name: npm install @microsoft/botframework-cli@next --global
run: npm install @microsoft/botframework-cli@next --global
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:
strategy:
matrix:
os: [ubuntu, windows]
node-version: [18.x, 20.x]
node-version: [18.x, 20.x, 22.x]

steps:
- uses: actions/checkout@v2
Expand Down Expand Up @@ -50,7 +50,7 @@ jobs:
run: yarn test:github

- name: coveralls
if: matrix.node-version == '20.x'
if: matrix.node-version == '22.x'
uses: coverallsapp/[email protected]
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
Expand Down
2 changes: 1 addition & 1 deletion build/yaml/botbuilder-js-daily.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ pool:
vmImage: 'ubuntu-latest'

variables:
NodeVersion: 20.x
NodeVersion: 22.x
Packaging.EnableSBOMSigning: true
YARN_CACHE_FOLDER: $(Pipeline.Workspace)/.yarn
# SDK_JS_org_registry_Url: define this in Azure
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ async function runCommand(command, envObject) {
// We need to combine our process.env with envObject so,
// 1) We can use existing env vars (like from CI), and
// 2) npx doesn't like to install without the existing APPDATA windows env var.
const env = { ...process.env, ...envObject };
const env = { ...process.env, ...envObject, NODE_NO_WARNINGS: 1 };
const { stdout, stderr } = await exec(command, { env });

if (stderr) {
Expand Down
9 changes: 5 additions & 4 deletions libraries/botbuilder/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@
"filenamify": "^6.0.0",
"fs-extra": "^11.2.0",
"htmlparser2": "^9.0.1",
"import-sync": "^2.2.2",
"uuid": "^10.0.0",
"zod": "^3.23.8"
},
Expand All @@ -54,9 +53,10 @@
"build": "tsc -b",
"build-docs": "typedoc --theme markdown --entryPoint botbuilder --excludePrivate --includeDeclarations --ignoreCompilerErrors --module amd --out ..\\..\\doc\\botbuilder .\\lib\\index.d.ts ..\\botbuilder-core\\lib\\index.d.ts ..\\botframework-schema\\lib\\index.d.ts --hideGenerator --name \"Bot Builder SDK\" --readme none",
"build:rollup": "yarn clean && yarn build && api-extractor run --verbose --local",
"clean": "rimraf _ts3.4 lib tsconfig.tsbuildinfo",
"clean": "rimraf _ts3.4 lib vendors tsconfig.tsbuildinfo",
"depcheck": "depcheck --config ../../.depcheckrc",
"lint": "eslint .",
"prebuild": "tsup ./node_modules/filenamify/*.js --format cjs --dts --out-dir vendors/filenamify --clean --sourcemap",
"postbuild": "downlevel-dts lib _ts3.4/lib --checksum",
"test": "npm-run-all build test:mocha",
"test:compat": "api-extractor run --verbose",
Expand All @@ -70,6 +70,7 @@
"files": [
"_ts3.4",
"lib",
"src"
"src",
"vendors"
]
}
}
4 changes: 1 addition & 3 deletions libraries/botbuilder/src/fileTranscriptStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,7 @@
import { join, parse } from 'path';
import { mkdirp, pathExists, readdir, readFile, remove, writeFile } from 'fs-extra';
import { Activity, PagedResult, TranscriptInfo, TranscriptStore } from 'botbuilder-core';
import importSync from 'import-sync';

const filenamify = importSync('filenamify').default;
import filenamify from '../vendors/filenamify/index';

/**
* @private
Expand Down
5 changes: 4 additions & 1 deletion libraries/botframework-config/src/botConfiguration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,10 @@ export class BotConfiguration extends BotConfigurationBase {
}
}
}
} catch {
} catch (legacyErr) {
if (legacyErr.message.includes('Node.js versions')) {
throw legacyErr;
}
throw err;
}
}
Expand Down
27 changes: 22 additions & 5 deletions libraries/botframework-config/src/encrypt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
*/

import crypto from 'crypto';
import { version as nodeVersion } from 'process';

/**
* @private
Expand Down Expand Up @@ -100,10 +101,26 @@ export function decryptString(encryptedValue: string, secret: string): string {
* @param secret
*/
export function legacyDecrypt(encryptedValue: string, secret: string): string {
// LEGACY for pre standardized SHA256 encryption, this uses some undocumented nodejs MD5 hash internally and is deprecated
const decipher: crypto.Decipher = crypto.createDecipher('aes192', secret);
let value: string = decipher.update(encryptedValue, 'hex', 'utf8');
value += decipher.final('utf8');
const UNSUPPORTED_VERSION = 'v22.0.0';
if (!isNodeCompatible(nodeVersion, UNSUPPORTED_VERSION)) {
throw new Error(`This method is not available for Node.js versions over ${UNSUPPORTED_VERSION}.`);
} else {
// LEGACY for pre standardized SHA256 encryption, this uses some undocumented nodejs MD5 hash internally and is deprecated
const decipher: crypto.Decipher = crypto.createDecipher('aes192', secret);
let value: string = decipher.update(encryptedValue, 'hex', 'utf8');
value += decipher.final('utf8');

return value;
}
}

return value;
/**
* private
*
* @param currentVersion The current version of Node.js.
* @param minVersion The minimum unsupported version.
* @returns true if the current version of Node is lower than the unsupported version.
*/
function isNodeCompatible(currentVersion: string, minVersion: string): boolean {
return minVersion.localeCompare(currentVersion) > 0;
}
51 changes: 33 additions & 18 deletions libraries/botframework-config/tests/loadAndSave.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const testBotPath = require.resolve('./test.bot');
const govTestBotPath = require.resolve('./govTest.bot');
const legacyBotPath = require.resolve('./legacy.bot');
const saveBotPath = testBotPath.replace('test.bot', 'save.bot');
const UNSUPPORTED_VERSION = 'v22.0.0';

describe('LoadAndSaveTests', function () {
it('DeserializeBotFile', async function () {
Expand Down Expand Up @@ -89,12 +90,19 @@ describe('LoadAndSaveTests', function () {
const config = await bf.BotConfiguration.load(testBotPath);
await config.saveAs(saveBotPath, secret);

await assert.rejects(
bf.BotConfiguration.load(saveBotPath),
new Error(
'You are attempting to perform an operation which needs access to the secret and --secret is missing'
)
);
if (UNSUPPORTED_VERSION.localeCompare(process.version) < 1) {
await assert.rejects(
bf.BotConfiguration.load(saveBotPath),
new Error(`This method is not available for Node.js versions over ${UNSUPPORTED_VERSION}.`),
);
} else {
await assert.rejects(
bf.BotConfiguration.load(saveBotPath),
new Error(
'You are attempting to perform an operation which needs access to the secret and --secret is missing'
),
);
}
});

it('LoadAndVerifyChannelServiceSync', async function () {
Expand Down Expand Up @@ -354,17 +362,24 @@ describe('LoadAndSaveTests', function () {
});

it('LegacyEncryption', async function () {
let config = await bf.BotConfiguration.load(legacyBotPath, 'password');
assert.equal(config.services[0].appPassword, 'xyzpdq', 'value should be unencrypted');
assert.ok(config.padlock != null, 'padlock should exist');
assert.ok(!config.secretKey, 'secretKey should not exist');

const secret = bf.BotConfiguration.generateKey();
await config.saveAs(saveBotPath, secret);
config = await bf.BotConfiguration.load(saveBotPath, secret);
fs.unlinkSync(saveBotPath);
assert.ok(config.padlock != null, 'padlock should exist');
assert.ok(config.padlock.length > 0, 'padlock should not be empty');
assert.ok(!config.secretKey, 'secretKey should not exist');
if (UNSUPPORTED_VERSION.localeCompare(process.version) < 1) {
await assert.rejects(
bf.BotConfiguration.load(legacyBotPath, 'password'),
new Error(`This method is not available for Node.js versions over ${UNSUPPORTED_VERSION}.`),
);
} else {
let config = await bf.BotConfiguration.load(legacyBotPath, 'password');
assert.equal(config.services[0].appPassword, 'xyzpdq', 'value should be unencrypted');
assert.ok(config.padlock != null, 'padlock should exist');
assert.ok(!config.secretKey, 'secretKey should not exist');

const secret = bf.BotConfiguration.generateKey();
await config.saveAs(saveBotPath, secret);
config = await bf.BotConfiguration.load(saveBotPath, secret);
fs.unlinkSync(saveBotPath);
assert.ok(config.padlock != null, 'padlock should exist');
assert.ok(config.padlock.length > 0, 'padlock should not be empty');
assert.ok(!config.secretKey, 'secretKey should not exist');
}
});
});
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,12 @@
"generators/generator-botbuilder/generators/app/templates/*"
],
"nohoist": [
"**/@types/selenium-webdriver"
"**/@types/selenium-webdriver",
"botbuilder/filenamify"
],
"nohoistComments": {
"**/@types/selenium-webdriver": "This package is excluded from the root @types folder as it requires ES2015+, whereas some BotBuilder libraries support ES5+."
"**/@types/selenium-webdriver": "This package is excluded from the root @types folder as it requires ES2015+, whereas some BotBuilder libraries support ES5+.",
"botbuilder/filenamify": "This package is excluded because it's compiled as CJS by tsup as it's ESM-only."
}
},
"scripts": {
Expand Down
Loading

0 comments on commit 6751c41

Please sign in to comment.