diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml index 70d11ae..ff98276 100644 --- a/.github/workflows/publish.yaml +++ b/.github/workflows/publish.yaml @@ -7,6 +7,11 @@ on: jobs: publish: runs-on: ubuntu-latest + + permissions: + contents: read + id-token: write + steps: - name: Checkout 🛎️ uses: actions/checkout@v4 @@ -43,7 +48,12 @@ jobs: pnpm -r publish --no-git-checks --access public env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - + + - name: Publish 🚀 PRODUCTION on JSR.io + if: '!github.event.release.prerelease' + run: | + pnpm --filter '*' jsr:publish + - name: Post to a Slack channel id: slack uses: slackapi/slack-github-action@v1.25.0 diff --git a/package.json b/package.json index e14273b..72458ed 100644 --- a/package.json +++ b/package.json @@ -14,12 +14,14 @@ "make:version": "lerna version --no-private", "example:cjs": "pnpm --filter cjs start", "example:esm": "pnpm --filter esm start", - "example:esm:serve": "pnpm --filter esm serve" + "example:esm:serve": "pnpm --filter esm serve", + "version": "node update-jsr-version.js && git add ." }, "devDependencies": { "@commercelayer/eslint-config-ts": "^1.3.0", "husky": "^9.0.11", "lerna": "^8.1.2", + "replace-in-file": "^7.1.0", "typescript": "^5.4.2" } } \ No newline at end of file diff --git a/packages/js-auth/README.md b/packages/js-auth/README.md index baac1de..098b9c1 100644 --- a/packages/js-auth/README.md +++ b/packages/js-auth/README.md @@ -1,6 +1,6 @@ # Commerce Layer JS Auth -A JavaScript Library wrapper that helps you use the Commerce Layer API for [Authentication](https://docs.commercelayer.io/developers/authentication). +A JavaScript library designed to simplify [authentication](https://docs.commercelayer.io/developers/authentication) when interacting with the Commerce Layer API. ## What is Commerce Layer? diff --git a/packages/js-auth/jsr.json b/packages/js-auth/jsr.json new file mode 100644 index 0000000..4338fee --- /dev/null +++ b/packages/js-auth/jsr.json @@ -0,0 +1,5 @@ +{ + "name": "@commercelayer/js-auth", + "version": "6.0.1", + "exports": "./src/index.ts" +} \ No newline at end of file diff --git a/packages/js-auth/package.json b/packages/js-auth/package.json index c8da768..106f1bb 100644 --- a/packages/js-auth/package.json +++ b/packages/js-auth/package.json @@ -1,7 +1,7 @@ { "name": "@commercelayer/js-auth", "version": "6.0.1", - "description": "Commerce Layer Javascript Auth", + "description": "A JavaScript library designed to simplify authentication when interacting with the Commerce Layer API.", "repository": { "url": "https://github.com/commercelayer/commercelayer-js-auth.git" }, @@ -37,7 +37,8 @@ "lint:fix": "eslint src --ext .ts,.tsx --fix", "test": "pnpm run lint && vitest run --silent", "test:watch": "vitest --silent", - "build": "tsup" + "build": "tsup", + "jsr:publish": "pnpm dlx jsr publish" }, "publishConfig": { "access": "public" diff --git a/packages/js-auth/src/authenticate.ts b/packages/js-auth/src/authenticate.ts index d1891ee..27c3b5c 100644 --- a/packages/js-auth/src/authenticate.ts +++ b/packages/js-auth/src/authenticate.ts @@ -2,11 +2,11 @@ import type { GrantType, AuthenticateOptions, AuthenticateReturn -} from '#types/index.js' +} from './types/index.js' -import { camelCaseToSnakeCase } from '#utils/camelCaseToSnakeCase.js' -import { mapKeys } from '#utils/mapKeys.js' -import { snakeCaseToCamelCase } from '#utils/snakeCaseToCamelCase.js' +import { camelCaseToSnakeCase } from './utils/camelCaseToSnakeCase.js' +import { mapKeys } from './utils/mapKeys.js' +import { snakeCaseToCamelCase } from './utils/snakeCaseToCamelCase.js' interface TokenJson { expires: Date @@ -14,10 +14,34 @@ interface TokenJson { [key: string]: string | number | Date } -export async function authenticate( - grantType: G, - { domain = 'commercelayer.io', headers, ...options }: AuthenticateOptions -): Promise> { +/** + * Authenticate helper used to get the access token. + * + * _Please note that the authentication endpoint is subject to a [rate limit](https://docs.commercelayer.io/core/rate-limits) + * of **max 30 reqs / 1 min** both in live and test mode._ + * @param grantType The type of OAuth 2.0 grant being used for authentication. + * @param options Authenticate options + * @returns + * @example + * ```ts + * import { authenticate } from '@commercelayer/js-auth' + * + * const auth = await authenticate('client_credentials', { + * clientId: '{{ clientId }}', + * scope: 'market:id:DGzAouppwn' + * }) + * + * console.log(auth.accessToken) + * ``` + */ +export async function authenticate( + grantType: TGrantType, + { + domain = 'commercelayer.io', + headers, + ...options + }: AuthenticateOptions +): Promise> { const body = mapKeys( { grant_type: grantType, @@ -39,5 +63,8 @@ export async function authenticate( const json: TokenJson = await response.json() json.expires = new Date(Date.now() + json.expires_in * 1000) - return mapKeys(json, snakeCaseToCamelCase) as unknown as AuthenticateReturn + return mapKeys( + json, + snakeCaseToCamelCase + ) as unknown as AuthenticateReturn } diff --git a/packages/js-auth/src/jwtDecode.ts b/packages/js-auth/src/jwtDecode.ts index 590a083..f2dc47c 100644 --- a/packages/js-auth/src/jwtDecode.ts +++ b/packages/js-auth/src/jwtDecode.ts @@ -1,4 +1,4 @@ -import { decodeBase64URLSafe } from '#utils/base64.js' +import { decodeBase64URLSafe } from './utils/base64.js' /** * Decode a Commerce Layer access token. @@ -133,24 +133,49 @@ type JWTIntegration = JWTOrganizationBase & { } } +/** + * Checks if the provided payload represents a `user`. + * @param payload The payload to be checked. + * @returns + */ export function jwtIsUser(payload: Payload): payload is JWTUser { return payload.application.kind === 'user' } +/** + * Checks if the provided payload represents a `dashboard`. + * @param payload The payload to be checked. + * @returns + */ export function jwtIsDashboard(payload: Payload): payload is JWTDashboard { return payload.application.kind === 'dashboard' } +/** + * Checks if the provided payload represents an `integration`. + * @param payload The payload to be checked. + * @returns + */ export function jwtIsIntegration(payload: Payload): payload is JWTIntegration { return payload.application.kind === 'integration' } +/** + * Checks if the provided payload represents a `sales_channel`. + * @param payload The payload to be checked. + * @returns + */ export function jwtIsSalesChannel( payload: Payload ): payload is JWTSalesChannel { return payload.application.kind === 'sales_channel' } +/** + * Checks if the provided payload represents a `webapp`. + * @param payload The payload to be checked. + * @returns + */ export function jwtIsWebApp(payload: Payload): payload is JWTWebApp { return payload.application.kind === 'webapp' } diff --git a/packages/js-auth/src/jwtEncode.ts b/packages/js-auth/src/jwtEncode.ts index 83da78d..ebdbdf4 100644 --- a/packages/js-auth/src/jwtEncode.ts +++ b/packages/js-auth/src/jwtEncode.ts @@ -1,4 +1,4 @@ -import { encodeBase64URLSafe } from '#utils/base64.js' +import { encodeBase64URLSafe } from './utils/base64.js' interface Owner { type: 'User' | 'Customer' diff --git a/packages/js-auth/src/revoke.ts b/packages/js-auth/src/revoke.ts index 16d2233..6926193 100644 --- a/packages/js-auth/src/revoke.ts +++ b/packages/js-auth/src/revoke.ts @@ -1,8 +1,22 @@ -import type { RevokeOptions, RevokeReturn } from '#types/index.js' +import type { RevokeOptions, RevokeReturn } from './types/index.js' -import { camelCaseToSnakeCase } from '#utils/camelCaseToSnakeCase.js' -import { mapKeys } from '#utils/mapKeys.js' +import { camelCaseToSnakeCase } from './utils/camelCaseToSnakeCase.js' +import { mapKeys } from './utils/mapKeys.js' +/** + * Revoke a previously generated access token (refresh tokens included) before its natural expiration date. + * + * @param options Revoke options + * @returns + * @example + * ```ts + * await revoke({ + * clientId: '{{ integrationClientId }}', + * clientSecret: '{{ integrationClientSecret }}', + * token: authenticateResponse.accessToken + * }) + * ``` + */ export async function revoke({ domain = 'commercelayer.io', ...options diff --git a/packages/js-auth/src/types/authorizationCode.ts b/packages/js-auth/src/types/authorizationCode.ts index 73c24cf..46ccd8e 100644 --- a/packages/js-auth/src/types/authorizationCode.ts +++ b/packages/js-auth/src/types/authorizationCode.ts @@ -1,4 +1,4 @@ -import type { TBaseOptions } from '#types/base.js' +import type { TBaseOptions } from './base.js' import type { TPasswordReturn } from './password.js' /** @@ -6,8 +6,17 @@ import type { TPasswordReturn } from './password.js' * @see https://docs.commercelayer.io/core/authentication/authorization-code#getting-an-access-token */ export interface TAuthorizationCodeOptions extends TBaseOptions { + /** + * The authorization code that [you got](https://docs.commercelayer.io/core/authentication/authorization-code#getting-an-authorization-code) from the redirect URI query string. + */ code: string + /** + * Your application's redirect URI. + */ redirectUri: string + /** + * Your application's client secret. + */ clientSecret: string } diff --git a/packages/js-auth/src/types/clientCredentials.ts b/packages/js-auth/src/types/clientCredentials.ts index 7659608..feb118f 100644 --- a/packages/js-auth/src/types/clientCredentials.ts +++ b/packages/js-auth/src/types/clientCredentials.ts @@ -1,9 +1,13 @@ -import type { TBaseOptions } from '#types/base.js' +import type { TBaseOptions } from './base.js' /** * The client credentials grant type is used by clients to obtain an access token outside of the context of a user. * @see https://docs.commercelayer.io/core/authentication/client-credentials */ export interface TClientCredentialsOptions extends TBaseOptions { + /** + * Your application's client secret + * (required for [confidential](https://docs.commercelayer.io/core/authentication/client-credentials#integration) API credentials). + */ clientSecret?: string } diff --git a/packages/js-auth/src/types/index.ts b/packages/js-auth/src/types/index.ts index de3ecd1..37e97a4 100644 --- a/packages/js-auth/src/types/index.ts +++ b/packages/js-auth/src/types/index.ts @@ -9,7 +9,7 @@ import type { TPasswordOptions, TPasswordReturn } from './password.js' import type { TRefreshTokenOptions } from './refreshToken.js' /** - * The grant type. + * The type of OAuth 2.0 grant being used for authentication. */ export type GrantType = | 'password' @@ -18,6 +18,7 @@ export type GrantType = | 'authorization_code' | 'urn:ietf:params:oauth:grant-type:jwt-bearer' +/** The options type for the `authenticate` helper. */ export type AuthenticateOptions = TGrantType extends 'urn:ietf:params:oauth:grant-type:jwt-bearer' ? TJwtBearerOptions @@ -31,6 +32,7 @@ export type AuthenticateOptions = ? TAuthorizationCodeOptions : never +/** The return type of the `authenticate` helper. */ export type AuthenticateReturn = TGrantType extends 'urn:ietf:params:oauth:grant-type:jwt-bearer' ? TJwtBearerReturn @@ -44,6 +46,7 @@ export type AuthenticateReturn = ? TAuthorizationCodeReturn : never +/** The options type for the `revoke` helper. */ export type RevokeOptions = Pick & { /** Your application's client secret (required for confidential API credentials and non-confidential API credentials without a customer or a user in the JWT only). */ clientSecret?: string @@ -51,4 +54,5 @@ export type RevokeOptions = Pick & { token: string } +/** The return type of the `revoke` helper. */ export type RevokeReturn = Pick diff --git a/packages/js-auth/src/types/jwtBearer.ts b/packages/js-auth/src/types/jwtBearer.ts index ce35282..5b332a8 100644 --- a/packages/js-auth/src/types/jwtBearer.ts +++ b/packages/js-auth/src/types/jwtBearer.ts @@ -1,4 +1,4 @@ -import type { TBaseOptions } from '#types/base.js' +import type { TBaseOptions } from './base.js' import type { TPasswordReturn } from './password.js' /** diff --git a/packages/js-auth/src/types/password.ts b/packages/js-auth/src/types/password.ts index 0642098..2ea7a85 100644 --- a/packages/js-auth/src/types/password.ts +++ b/packages/js-auth/src/types/password.ts @@ -1,11 +1,13 @@ -import type { TBaseOptions, TBaseReturn } from '#types/base.js' +import type { TBaseOptions, TBaseReturn } from './base.js' /** * The password grant type is used by first-party clients to exchange a user's credentials for an access token. * @see https://docs.commercelayer.io/core/authentication/password */ export interface TPasswordOptions extends TBaseOptions { + /** The customer's email address. */ username: string + /** The customer's password */ password: string } diff --git a/packages/js-auth/src/types/refreshToken.ts b/packages/js-auth/src/types/refreshToken.ts index 38affca..45ce15e 100644 --- a/packages/js-auth/src/types/refreshToken.ts +++ b/packages/js-auth/src/types/refreshToken.ts @@ -1,10 +1,17 @@ -import type { TBaseOptions } from '#types/base.js' +import type { TBaseOptions } from './base.js' /** * The refresh token grant type is used by clients to exchange a refresh token for an access token when the access token has expired. * @see https://docs.commercelayer.io/core/authentication/refresh-token */ export interface TRefreshTokenOptions extends TBaseOptions { + /** + * A valid `refresh_token`. + */ refreshToken: string + /** + * Your application's client secret + * (required for confidential API credentials — i.e. in case of [authorization code flow](https://docs.commercelayer.io/core/authentication/refresh-token#webapp-application-with-authorization-code-flow)). + */ clientSecret?: string } diff --git a/packages/js-auth/tsconfig.json b/packages/js-auth/tsconfig.json index 9283a8f..4366e13 100644 --- a/packages/js-auth/tsconfig.json +++ b/packages/js-auth/tsconfig.json @@ -18,11 +18,6 @@ "noPropertyAccessFromIndexSignature": true /* Require undeclared properties from index signatures to use element accesses. */, "types": ["vitest/globals"], "baseUrl": "." /* Base directory to resolve non-absolute module names. */, - "paths": { - "#utils/*": ["src/utils/*"], - "#types/*": ["src/types/*"], - "#types": ["src/types/index"] - }, "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */, "skipLibCheck": true /* Skip type checking of declaration files. */, "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f79475a..dbbae3e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -17,6 +17,9 @@ importers: lerna: specifier: ^8.1.2 version: 8.1.2 + replace-in-file: + specifier: ^7.1.0 + version: 7.1.0 typescript: specifier: ^5.4.2 version: 5.4.2 @@ -4236,7 +4239,7 @@ packages: hasBin: true dependencies: async: 3.2.5 - chalk: 4.1.0 + chalk: 4.1.2 filelist: 1.0.4 minimatch: 3.1.2 dev: true @@ -5984,6 +5987,16 @@ packages: set-function-name: 2.0.2 dev: true + /replace-in-file@7.1.0: + resolution: {integrity: sha512-1uZmJ78WtqNYCSuPC9IWbweXkGxPOtk2rKuar8diTw7naVIQZiE3Tm8ACx2PCMXDtVH6N+XxwaRY2qZ2xHPqXw==} + engines: {node: '>=10'} + hasBin: true + dependencies: + chalk: 4.1.2 + glob: 8.1.0 + yargs: 17.7.2 + dev: true + /require-directory@2.1.1: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} engines: {node: '>=0.10.0'} diff --git a/update-jsr-version.js b/update-jsr-version.js new file mode 100644 index 0000000..8e6f570 --- /dev/null +++ b/update-jsr-version.js @@ -0,0 +1,31 @@ +// @ts-check + +/** + * This script will replace all "version number" occurrences in jsr.json files so that it will point to the latest version. + * This script runs on "version lifecycle" - https://github.com/lerna/lerna/blob/main/commands/version/README.md#lifecycle-scripts + */ + +const { sync } = require('replace-in-file') +const { version } = require('./lerna.json') + +const options = { + dry: false, + files: ['./packages/**/jsr.json'], + from: /("version":\s")([0-9a-z\.\-]+)(",)/, + to: `$1${version}$3` +} + +try { + const results = sync(options) + const filteredResults = results.filter((r) => r.hasChanged).map((r) => r.file) + + if (filteredResults.length > 0) { + console.group('Updating "Package" version for https://jsr.io:') + filteredResults.forEach((r) => { + console.info('→', r) + }) + console.groupEnd() + } +} catch (error) { + console.error('Error occurred:', error) +}