diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000..618a234
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,13 @@
+version: 2
+updates:
+ - package-ecosystem: "github-actions"
+ directory: "/"
+ schedule:
+ interval: "monthly"
+ open-pull-requests-limit: 10
+
+ - package-ecosystem: "npm"
+ directory: "/"
+ schedule:
+ interval: "weekly"
+ open-pull-requests-limit: 10
\ No newline at end of file
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 2352650..edca6e2 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -1,19 +1,75 @@
-name: CI
on:
push:
- branches: ['master']
+ branches:
+ - main
+ - 'v*'
pull_request:
- branches: ['*']
+ paths-ignore:
+ - LICENSE
+ - '*.md'
+
+name: CI
+
jobs:
- test:
+ lint:
+ permissions:
+ contents: read
+ name: Lint
runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
+ - name: Install Node.js
+ uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
+ with:
+ node-version: v24.x
+ cache: 'npm'
+ cache-dependency-path: package.json
+
+ - name: Install dependencies
+ run: npm install
+ - name: Check linting
+ run: npm run lint:ci
+
+ tests:
+ permissions:
+ contents: read
+ name: Tests
strategy:
+ fail-fast: false
matrix:
- node-version: [18, 17, 16, 14]
+ os: [ubuntu-latest, macos-latest, windows-latest]
+ node-version: [20, 22, 24]
+ runs-on: ${{matrix.os}}
steps:
- - uses: actions/checkout@v2
- - uses: actions/setup-node@v1
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
+ with:
+ persist-credentials: false
+
+ - name: Use Node.js ${{ matrix.node-version }}
+ uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: ${{ matrix.node-version }}
- - run: npm install
- - run: npm test
+ cache: 'npm'
+ cache-dependency-path: package.json
+
+ - name: Install Dependencies
+ run: npm install
+
+ - name: Run Tests
+ # TODO: extend to test:ci
+ run: npm run test
+
+ automerge:
+ if: >
+ github.event_name == 'pull_request' && github.event.pull_request.user.login == 'dependabot[bot]'
+ needs:
+ - tests
+ runs-on: ubuntu-latest
+ permissions:
+ contents: write
+ pull-requests: write
+ steps:
+ - name: Merge Dependabot PR
+ uses: fastify/github-action-merge-dependabot@e820d631adb1d8ab16c3b93e5afe713450884a4a # v3.11.1
+ with:
+ github-token: ${{ secrets.GITHUB_TOKEN }}
\ No newline at end of file
diff --git a/.npmrc b/.npmrc
new file mode 100644
index 0000000..9cf9495
--- /dev/null
+++ b/.npmrc
@@ -0,0 +1 @@
+package-lock=false
\ No newline at end of file
diff --git a/index.d.ts b/index.d.ts
new file mode 100644
index 0000000..82e726a
--- /dev/null
+++ b/index.d.ts
@@ -0,0 +1,35 @@
+///
+import { SelfsignedOptions, CertificateField } from 'selfsigned';
+
+type CertificateAttribute = CertificateField;
+
+type HttpsPEMGenerateOpts = {
+ attr?: CertificateAttribute[];
+ opts?: SelfsignedOptions;
+};
+type HttpsPEMGenerateResult = {
+ key: string;
+ cert: string;
+};
+
+type HttpsPEM = {
+ key: string | null;
+ cert: string | null;
+ generate(config?: HttpsPEMGenerateOpts): Promise;
+ generate(
+ config: HttpsPEMGenerateOpts | null,
+ done: (err: Error | null, result: HttpsPEMGenerateResult) => void
+ ): void;
+};
+
+export default HttpsPEM;
+export declare const key: HttpsPEM['key'];
+export declare const cert: HttpsPEM['cert'];
+export declare const generate: HttpsPEM['generate'];
+
+export {
+ SelfsignedOptions,
+ CertificateAttribute,
+ HttpsPEMGenerateResult,
+ HttpsPEMGenerateOpts,
+};
diff --git a/index.js b/index.js
index 8bf44e7..19c678a 100644
--- a/index.js
+++ b/index.js
@@ -1,7 +1,31 @@
'use strict'
-const path = require('path')
-const fs = require('fs')
+const path = require('node:path')
+const fs = require('node:fs')
-exports.key = fs.readFileSync(path.join(__dirname, 'key.pem'))
-exports.cert = fs.readFileSync(path.join(__dirname, 'cert.pem'))
+let selfsigned
+const keyPath = path.join(__dirname, 'key.pem')
+const certPath = path.join(__dirname, 'cert.pem')
+
+// In case npm scripts are disabled, we still want to provide the key and cert
+exports.key = fs.existsSync(keyPath) ? fs.readFileSync(keyPath) : null
+exports.cert = fs.existsSync(certPath) ? fs.readFileSync(certPath) : null
+exports.generate = generate
+exports.default = exports
+
+function generate ({ attr, opts } = { attr: [], opts: null }, done) {
+ if (done == null) {
+ const promise = new Promise((resolve, reject) => {
+ generate({ attr, opts }, (err, pems) => {
+ if (err) return reject(err)
+ resolve({ key: pems.private, cert: pems.cert })
+ })
+ })
+
+ return promise
+ }
+
+ if (!selfsigned) selfsigned = require('selfsigned')
+
+ selfsigned.generate(attr, opts, done)
+}
diff --git a/install.js b/install.js
index b59dae8..1de2231 100644
--- a/install.js
+++ b/install.js
@@ -1,10 +1,16 @@
'use strict'
-const fs = require('fs')
-const path = require('path')
+const fs = require('node:fs')
+const path = require('node:path')
const selfsigned = require('selfsigned')
-const pems = selfsigned.generate()
+const nodeVersion = Number(process.versions.node.split('.')[0])
+const pems =
+ // Due to new version of openssl, we need to use a larger key size
+ // for node 24 and above, otherwise the default key size is sufficient
+ nodeVersion > 22
+ ? selfsigned.generate({}, { keySize: 2048 })
+ : selfsigned.generate()
fs.writeFileSync(path.join(__dirname, 'key.pem'), pems.private)
fs.writeFileSync(path.join(__dirname, 'cert.pem'), pems.cert)
diff --git a/package.json b/package.json
index bc8b998..ac9253e 100644
--- a/package.json
+++ b/package.json
@@ -1,15 +1,24 @@
{
- "name": "https-pem",
- "version": "3.0.0",
- "description": "Self-signed PEM key and certificate ready for use in your HTTPS server",
+ "name": "@metcoder95/https-pem",
+ "version": "1.0.0",
+ "description": "Self-signed PEM key and certificate ready for use in your HTTPS server (fork from https-pem)",
"main": "index.js",
+ "types": "index.d.ts",
+ "private": false,
"scripts": {
"postinstall": "node install.js",
- "test": "standard && node test.js"
+ "clean": "rm -rf *.pem",
+ "lint": "standard",
+ "lint:ci": "standard | snazzy",
+ "test": "node tests/index.js",
+ "test:ts": "tsd"
},
"repository": {
"type": "git",
- "url": "git+https://github.com/watson/https-pem.git"
+ "url": "git+https://github.com/metcoder95/https-pem.git"
+ },
+ "engines": {
+ "node": ">=20"
},
"keywords": [
"tls",
@@ -26,16 +35,37 @@
"server"
],
"author": "Thomas Watson Steen (https://twitter.com/wa7son)",
+ "contributors": [
+ {
+ "name": "Carlos Fuentes",
+ "email": "me@metcoder.dev",
+ "url": "https://bsky.app/profile/metcoder.dev"
+ }
+ ],
"license": "MIT",
"bugs": {
- "url": "https://github.com/watson/https-pem/issues"
+ "url": "https://github.com/metcoder95/https-pem/issues"
},
- "homepage": "https://github.com/watson/https-pem#readme",
+ "homepage": "https://github.com/metcoder95/https-pem#readme",
"dependencies": {
- "selfsigned": "^2.0.1"
+ "selfsigned": "^3.0.1"
},
"devDependencies": {
- "standard": "^17.0.0"
+ "@types/node": "^24.2.0",
+ "snazzy": "^9.0.0",
+ "standard": "^17.0.0",
+ "tsd": "^0.33.0",
+ "typescript": "^5.9.2",
+ "undici": "^7.13.0"
+ },
+ "tsd": {
+ "directory": "test"
+ },
+ "standard": {
+ "ignore": [
+ "*.d.ts",
+ "*.test-d.ts"
+ ]
},
"coordinates": [
55.7774667,
diff --git a/test.js b/test.js
deleted file mode 100644
index 7e70655..0000000
--- a/test.js
+++ /dev/null
@@ -1,23 +0,0 @@
-'use strict'
-
-const assert = require('assert')
-const https = require('https')
-const pem = require('./')
-
-const server = https.createServer(pem, function (req, res) {
- res.end('foo')
-})
-
-server.listen(function () {
- const opts = {
- port: server.address().port,
- rejectUnauthorized: false
- }
- https.request(opts, function (res) {
- assert.strictEqual(res.statusCode, 200)
- res.on('data', function (chunk) {
- assert.strictEqual(chunk.toString(), 'foo')
- process.exit(0)
- })
- }).end()
-})
diff --git a/tests/index.js b/tests/index.js
new file mode 100644
index 0000000..64d2b56
--- /dev/null
+++ b/tests/index.js
@@ -0,0 +1,60 @@
+'use strict'
+
+const https = require('node:https')
+const { once } = require('node:events')
+const { test } = require('node:test')
+
+const { Agent } = require('undici')
+
+const client = new Agent({
+ connect: {
+ rejectUnauthorized: false
+ }
+})
+
+test('https-pem (default)', async t => {
+ const pem = require('..')
+ const server = https.createServer(pem, function (req, res) {
+ res.end('foo')
+ })
+
+ server.listen()
+ await once(server, 'listening')
+ t.after(() => server.close())
+
+ const response = await client.request({
+ origin: `https://localhost:${server.address().port}`,
+ path: '/',
+ method: 'GET'
+ })
+
+ t.plan(2)
+ t.assert.strictEqual(response.statusCode, 200)
+ t.assert.strictEqual(await response.body.text(), 'foo')
+})
+
+test('https-pem (generate)', async t => {
+ const pem = require('..')
+ const pems = await pem.generate({
+ attr: [{ name: 'commonName', value: 'localhost' }],
+ opts: { keySize: 5120 }
+ })
+
+ const server = https.createServer(pems, function (req, res) {
+ res.end('foo')
+ })
+
+ server.listen()
+ await once(server, 'listening')
+ t.after(() => server.close())
+
+ const response = await client.request({
+ origin: `https://localhost:${server.address().port}`,
+ path: '/',
+ method: 'GET'
+ })
+
+ t.plan(2)
+ t.assert.strictEqual(response.statusCode, 200)
+ t.assert.strictEqual(await response.body.text(), 'foo')
+})
diff --git a/tests/index.test-d.ts b/tests/index.test-d.ts
new file mode 100644
index 0000000..4f4b65a
--- /dev/null
+++ b/tests/index.test-d.ts
@@ -0,0 +1,19 @@
+import { expectAssignable } from 'tsd';
+
+import {
+ generate,
+ key,
+ cert,
+ HttpsPEMGenerateResult,
+ HttpsPEMGenerateOpts,
+} from '..';
+
+expectAssignable(key);
+expectAssignable(cert);
+expectAssignable>(generate({}));
+expectAssignable(generate({}, () => {}));
+expectAssignable(generate(null, () => {}));
+expectAssignable({
+ attr: [{ name: 'hello', value: 'world' }],
+ opts: { keySize: 1234 },
+});
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..61a9e4e
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,101 @@
+{
+ "compilerOptions": {
+ /* Visit https://aka.ms/tsconfig.json to read more about this file */
+
+ /* Projects */
+ // "incremental": true, /* Enable incremental compilation */
+ // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
+ // "tsBuildInfoFile": "./", /* Specify the folder for .tsbuildinfo incremental compilation files. */
+ // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects */
+ // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
+ // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
+
+ /* Language and Environment */
+ "target": "es2020" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
+ // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
+ // "jsx": "preserve", /* Specify what JSX code is generated. */
+ // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
+ // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
+ // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h' */
+ // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
+ // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using `jsx: react-jsx*`.` */
+ // "reactNamespace": "", /* Specify the object invoked for `createElement`. This only applies when targeting `react` JSX emit. */
+ // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
+ // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
+
+ /* Modules */
+ "module": "commonjs" /* Specify what module code is generated. */,
+ // "rootDir": "./", /* Specify the root folder within your source files. */
+ "moduleResolution": "node" /* Specify how TypeScript looks up a file from a given module specifier. */,
+ // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
+ // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
+ // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
+ // "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */
+ // "types": [], /* Specify type package names to be included without being referenced in a source file. */
+ // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
+ // "resolveJsonModule": true, /* Enable importing .json files */
+ // "noResolve": true, /* Disallow `import`s, `require`s or ``s from expanding the number of files TypeScript should add to a project. */
+
+ /* JavaScript Support */
+ // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */
+ // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
+ // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`. */
+
+ /* Emit */
+ "declaration": true /* Generate .d.ts files from TypeScript and JavaScript files in your project. */,
+ "declarationMap": true /* Create sourcemaps for d.ts files. */,
+ // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
+ // "sourceMap": true, /* Create source map files for emitted JavaScript files. */
+ // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If `declaration` is true, also designates a file that bundles all .d.ts output. */
+ "outDir": "./" /* Specify an output folder for all emitted files. */,
+ // "removeComments": true, /* Disable emitting comments. */
+ // "noEmit": true, /* Disable emitting files from a compilation. */
+ // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
+ // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types */
+ // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
+ // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
+ // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
+ // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
+ // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
+ // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
+ // "newLine": "crlf", /* Set the newline character for emitting files. */
+ "stripInternal": true /* Disable emitting declarations that have `@internal` in their JSDoc comments. */,
+ // "noEmitHelpers": true, /* Disable generating custom helper functions like `__extends` in compiled output. */
+ // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
+ // "preserveConstEnums": true, /* Disable erasing `const enum` declarations in generated code. */
+ "declarationDir": "./types" /* Specify the output directory for generated declaration files. */,
+ // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
+
+ /* Interop Constraints */
+ // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
+ // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
+ "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */,
+ // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
+ "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */,
+
+ /* Type Checking */
+ "strict": true /* Enable all strict type-checking options. */,
+ // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied `any` type.. */
+ // "strictNullChecks": true, /* When type checking, take into account `null` and `undefined`. */
+ // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
+ // "strictBindCallApply": true, /* Check that the arguments for `bind`, `call`, and `apply` methods match the original function. */
+ // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
+ // "noImplicitThis": true, /* Enable error reporting when `this` is given the type `any`. */
+ // "useUnknownInCatchVariables": true, /* Type catch clause variables as 'unknown' instead of 'any'. */
+ "alwaysStrict": true /* Ensure 'use strict' is always emitted. */
+ // "noUnusedLocals": true, /* Enable error reporting when a local variables aren't read. */
+ // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read */
+ // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
+ // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
+ // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
+ // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */
+ // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
+ // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type */
+ // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
+ // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
+
+ /* Completeness */
+ // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
+ // "skipLibCheck": true /* Skip type checking all .d.ts files. */
+ }
+}