From 84004b5c9fba77040a5eb0a07abe98ce9248b6f1 Mon Sep 17 00:00:00 2001 From: Convly Date: Fri, 31 Jan 2025 11:23:57 +0100 Subject: [PATCH 01/19] release: 1.0.0-beta.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a4db287..3c692c6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@strapi/sdk-js", - "version": "1.0.0-beta.0", + "version": "1.0.0-beta.1", "description": "An SDK you can use the easily interface with Strapi from your javascript project", "keywords": [ "strapi", From 25f402b573f2f01678bafddbe71043cc5b1248af Mon Sep 17 00:00:00 2001 From: Ben Irvin Date: Thu, 30 Jan 2025 13:34:31 +0100 Subject: [PATCH 02/19] chore: update demo/.strapi-app/package.json MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Jean-Sébastien Herbaux --- demo/.strapi-app/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demo/.strapi-app/package.json b/demo/.strapi-app/package.json index f628191..804e82e 100644 --- a/demo/.strapi-app/package.json +++ b/demo/.strapi-app/package.json @@ -34,6 +34,6 @@ "npm": ">=6.0.0" }, "strapi": { - "uuid": "72804b4e-1794-45ab-aec0-b38a3d505e58" + "uuid": "getstarted" } } From 0149c68ea592d962fb74f3d316db62b5ea3f123b Mon Sep 17 00:00:00 2001 From: Convly Date: Tue, 28 Jan 2025 11:10:08 +0100 Subject: [PATCH 03/19] fix(interceptors): prevent overriding existing headers --- src/interceptors/http.ts | 10 +++++++++- tests/unit/interceptors/http.test.ts | 4 ++-- tests/unit/sdk.test.ts | 28 ++++++++++++++++++++++++++++ 3 files changed, 39 insertions(+), 3 deletions(-) diff --git a/src/interceptors/http.ts b/src/interceptors/http.ts index 10fe8a7..c088dc2 100644 --- a/src/interceptors/http.ts +++ b/src/interceptors/http.ts @@ -26,8 +26,16 @@ export class HttpInterceptors { *``` */ public static setDefaultHeaders(): RequestInterceptor { + const DEFAULT_HEADERS = new Map([['Content-Type', 'application/json']]); + return ({ request }) => { - request.headers.set('Content-Type', 'application/json'); + for (const [key, value] of DEFAULT_HEADERS.entries()) { + const hasHeader = request.headers.has(key); + + if (!hasHeader) { + request.headers.set(key, value); + } + } return { request }; }; diff --git a/tests/unit/interceptors/http.test.ts b/tests/unit/interceptors/http.test.ts index 384cbc5..267b7d6 100644 --- a/tests/unit/interceptors/http.test.ts +++ b/tests/unit/interceptors/http.test.ts @@ -16,7 +16,7 @@ describe('HTTP Interceptors', () => { expect(request.headers.get('Content-Type')).toBe('application/json'); }); - it('should override the headers in the given request', async () => { + it('should not override the headers if a value is already set', async () => { // Arrange const request = new Request('https://example.com', { headers: { 'Content-Type': 'text/plain' }, @@ -28,7 +28,7 @@ describe('HTTP Interceptors', () => { await interceptor({ request }); // Assert - expect(request.headers.get('Content-Type')).toBe('application/json'); + expect(request.headers.get('Content-Type')).toBe('text/plain'); }); }); diff --git a/tests/unit/sdk.test.ts b/tests/unit/sdk.test.ts index 10d7533..f300e35 100644 --- a/tests/unit/sdk.test.ts +++ b/tests/unit/sdk.test.ts @@ -192,6 +192,34 @@ describe('Strapi', () => { expect((headers as Headers).get('Content-Type')).toBe('application/json'); }); + it('should not set the application/json Content-Type header if it has been manually set', async () => { + // Arrange + const path = '/upload'; + const contentType = 'multipart/form-data'; + + const config = { baseURL: 'https://localhost:1337/api' } satisfies StrapiConfig; + const init = { + method: 'POST', + headers: { 'Content-Type': contentType }, + } satisfies RequestInit; + + const mockValidator = new MockStrapiConfigValidator(); + const mockAuthManager = new MockAuthManager(); + + const sdk = new Strapi(config, mockValidator, mockAuthManager, mockHttpClientFactory); + + const fetchSpy = jest.spyOn(MockHttpClient.prototype, 'fetch'); + + // Act + await sdk.fetch(path, init); + const headers = fetchSpy.mock.lastCall?.[1]?.headers; + + // Assert + expect(headers).toBeDefined(); + expect(headers).toBeInstanceOf(Headers); + expect((headers as Headers).get('Content-Type')).toBe(contentType); + }); + it.each([ ['Bad Request', StatusCode.BAD_REQUEST, HTTPBadRequestError], ['Unauthorized', StatusCode.UNAUTHORIZED, HTTPAuthorizationError], From b1804d2c4f911f13922425fa2b4a46e21beb71a4 Mon Sep 17 00:00:00 2001 From: Convly Date: Tue, 28 Jan 2025 11:35:58 +0100 Subject: [PATCH 04/19] test(http-interceptors): add case insensitive header check --- tests/unit/interceptors/http.test.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/unit/interceptors/http.test.ts b/tests/unit/interceptors/http.test.ts index 267b7d6..671b2fb 100644 --- a/tests/unit/interceptors/http.test.ts +++ b/tests/unit/interceptors/http.test.ts @@ -30,6 +30,21 @@ describe('HTTP Interceptors', () => { // Assert expect(request.headers.get('Content-Type')).toBe('text/plain'); }); + + it('should perform case insensitive checks on headers', async () => { + // Arrange + const request = new Request('https://example.com', { + headers: { 'content-type': 'text/plain' }, + }); + + const interceptor = HttpInterceptors.setDefaultHeaders(); + + // Act + await interceptor({ request }); + + // Assert + expect(request.headers.get('Content-Type')).toBe('text/plain'); + }); }); describe('transformErrors', () => { From fb39858c145ee3e54fc2e60e29769b6f8e133243 Mon Sep 17 00:00:00 2001 From: Convly Date: Wed, 29 Jan 2025 15:54:02 +0100 Subject: [PATCH 05/19] feat(sdk): simplify auth config with direct token support --- README.md | 8 ++--- src/index.ts | 68 +++++++++++++++++++++++++++++++--------- src/sdk.ts | 19 ++++++----- tests/unit/index.test.ts | 36 ++++++++++++++------- tests/unit/sdk.test.ts | 37 ++++++++++++++++++++-- 5 files changed, 129 insertions(+), 39 deletions(-) diff --git a/README.md b/README.md index f00f83f..5698986 100644 --- a/README.md +++ b/README.md @@ -108,11 +108,10 @@ If your Strapi instance uses API tokens, configure the SDK like this: ```typescript const sdk = strapi({ + // Endpoint configuration baseURL: 'http://localhost:1337/api', - auth: { - strategy: 'api-token', - options: { token: 'your-api-token-here' }, - }, + // Auth configuration + auth: 'your-api-token-here', }); ``` @@ -248,7 +247,6 @@ Below is a list of available namespaces to use: | `strapi:auth:factory` | Logs the registration and creation of authentication providers. | | `strapi:auth:manager` | Logs authentication lifecycle management. | | `strapi:auth:provider:api-token` | Logs operations related to API token authentication. | -| `strapi:auth:provider:users-permissions` | Logs operations related to user and permissions-based authentication. | | `strapi:ct:collection` | Logs interactions with collection-type content managers. | | `strapi:ct:single` | Logs interactions with single-type content managers. | | `strapi:utils:url-helper` | Logs URL helper utility operations (e.g., appending query parameters or formatting URLs). | diff --git a/src/index.ts b/src/index.ts index cbdff78..b60a5c2 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,10 +1,46 @@ +import { ApiTokenAuthProvider } from './auth'; import { Strapi } from './sdk'; -import { StrapiConfigValidator } from './validators'; import type { StrapiConfig } from './sdk'; export * from './errors'; +export interface Config { + /** + * The base URL of the Strapi content API. + * + * This specifies where the SDK should send requests. + * + * The URL must include the protocol (`http` or `https`) and serve + * as the root path for all later API operations. + * + * @example + * 'https://api.example.com' + * + * @remarks + * Failing to provide a valid HTTP or HTTPS URL results in a + * `StrapiInitializationError`. + */ + baseURL: string; + + /** + * API token to authenticate requests (optional). + * + * When provided, this token is included in the `Authorization` header + * of every request to the Strapi API. + * + * @remarks + * - A valid token must be a non-empty string. + * + * - If the token is invalid or improperly formatted, the SDK + * throws a `StrapiValidationError` during initialization. + * + * - If excluded, the SDK operates without authentication. + */ + + auth?: string; +} + /** * Creates a new instance of the Strapi SDK with a specified configuration. * @@ -25,10 +61,7 @@ export * from './errors'; * // Basic configuration using API token auth * const config = { * baseURL: 'https://api.example.com', - * auth: { - * strategy: 'api-token', - * options: { token: 'your_token_here' } - * } + * auth: 'your_token_here', * }; * * // Create the SDK instance @@ -44,13 +77,20 @@ export * from './errors'; * @throws {StrapiInitializationError} If the provided baseURL doesn't conform to a valid HTTP or HTTPS URL, * or if the auth configuration is invalid. */ -export const strapi = (config: StrapiConfig) => { - const configValidator = new StrapiConfigValidator(); - - return new Strapi( - // Properties - config, - // Dependencies - configValidator - ); +export const strapi = (config: Config) => { + const { baseURL, auth } = config; + + const sdkConfig: StrapiConfig = { baseURL }; + + // In this factory, to keep things simple, users can't manually set the strategy options. + // Since the SDK constructor needs to define a proper strategy, + // it is handled here if the auth property is provided + if (auth !== undefined) { + sdkConfig.auth = { + strategy: ApiTokenAuthProvider.identifier, + options: { token: auth }, + }; + } + + return new Strapi(sdkConfig); }; diff --git a/src/sdk.ts b/src/sdk.ts index 68ea7d7..1c00d47 100644 --- a/src/sdk.ts +++ b/src/sdk.ts @@ -2,7 +2,7 @@ import createDebug from 'debug'; import { AuthManager } from './auth'; import { CollectionTypeManager, SingleTypeManager } from './content-types'; -import { StrapiInitializationError } from './errors'; +import { StrapiError, StrapiInitializationError } from './errors'; import { HttpClient } from './http'; import { AuthInterceptors, HttpInterceptors } from './interceptors'; import { StrapiConfigValidator } from './validators'; @@ -39,12 +39,10 @@ export interface AuthConfig { * * It serves as the main interface through which users interact with * their Strapi installation programmatically. - * - * @template T_Config - Configuration type inferred from the user-provided SDK configuration */ -export class Strapi { +export class Strapi { /** @internal */ - private readonly _config: T_Config; + private readonly _config: StrapiConfig; /** @internal */ private readonly _validator: StrapiConfigValidator; @@ -58,7 +56,7 @@ export class Strapi { /** @internal */ constructor( // Properties - config: T_Config, + config: StrapiConfig, // Dependencies validator: StrapiConfigValidator = new StrapiConfigValidator(), @@ -189,7 +187,14 @@ export class Strapi { debug('setting up the auth strategy using %o', strategy); - this._authManager.setStrategy(strategy, options); + try { + this._authManager.setStrategy(strategy, options); + } catch (e) { + throw new StrapiInitializationError( + e, + `Failed to initialize the SDK auth manager: ${e instanceof StrapiError ? e.cause : e}` + ); + } } this._httpClient.interceptors.request diff --git a/tests/unit/index.test.ts b/tests/unit/index.test.ts index 8079848..0103e84 100644 --- a/tests/unit/index.test.ts +++ b/tests/unit/index.test.ts @@ -1,12 +1,13 @@ -import { strapi, StrapiInitializationError, StrapiValidationError } from '../../src'; +import { strapi, StrapiInitializationError } from '../../src'; +import { ApiTokenAuthProvider } from '../../src/auth'; import { Strapi } from '../../src/sdk'; -import type { StrapiConfig } from '../../src/sdk'; +import type { Config } from '../../src'; describe('strapi', () => { - it('should create an SDK instance with valid configuration', () => { + it('should create an SDK instance with valid http configuration', () => { // Arrange - const config = { baseURL: 'https://api.example.com' } satisfies StrapiConfig; + const config = { baseURL: 'https://api.example.com' } satisfies Config; // Act const sdk = strapi(config); @@ -16,9 +17,25 @@ describe('strapi', () => { expect(sdk).toHaveProperty('baseURL', config.baseURL); }); + it('should create an SDK instance with valid auth configuration', () => { + // Arrange + const token = ''; + const config = { baseURL: 'https://api.example.com', auth: token } satisfies Config; + + // Act + const sdk = strapi(config); + + // Assert + expect(sdk).toBeInstanceOf(Strapi); + expect(sdk).toHaveProperty('auth', { + strategy: ApiTokenAuthProvider.identifier, // default auth strategy + options: { token }, + }); + }); + it('should throw an error for an invalid baseURL', () => { // Arrange - const config = { baseURL: 'invalid-url' } satisfies StrapiConfig; + const config = { baseURL: 'invalid-url' } satisfies Config; // Act & Assert expect(() => strapi(config)).toThrow(StrapiInitializationError); @@ -28,13 +45,10 @@ describe('strapi', () => { // Arrange const config = { baseURL: 'https://api.example.com', - auth: { - strategy: 'api-token', - options: { token: '' }, // Invalid token - }, - } satisfies StrapiConfig; + auth: '', // Invalid API token + } satisfies Config; // Act & Assert - expect(() => strapi(config)).toThrow(StrapiValidationError); + expect(() => strapi(config)).toThrow(StrapiInitializationError); }); }); diff --git a/tests/unit/sdk.test.ts b/tests/unit/sdk.test.ts index f300e35..b2090dd 100644 --- a/tests/unit/sdk.test.ts +++ b/tests/unit/sdk.test.ts @@ -6,7 +6,9 @@ import { HTTPInternalServerError, HTTPNotFoundError, HTTPTimeoutError, + StrapiError, StrapiInitializationError, + StrapiValidationError, } from '../../src'; import { CollectionTypeManager, SingleTypeManager } from '../../src/content-types'; import { HttpClient, StatusCode } from '../../src/http'; @@ -46,7 +48,7 @@ describe('Strapi', () => { // Arrange const config = { baseURL: 'https://localhost:1337/api', - auth: { strategy: MockAuthProvider.identifier, options: {} }, + auth: { strategy: MockAuthProvider.identifier }, } satisfies StrapiConfig; const mockValidator = new MockStrapiConfigValidator(); @@ -62,7 +64,7 @@ describe('Strapi', () => { expect(sdk).toBeInstanceOf(Strapi); expect(validatorSpy).toHaveBeenCalledWith(config); - expect(authSetStrategySpy).toHaveBeenCalledWith(MockAuthProvider.identifier, {}); + expect(authSetStrategySpy).toHaveBeenCalledWith(MockAuthProvider.identifier, undefined); }); it('should not set the auth strategy if no auth config is provided', () => { @@ -82,6 +84,37 @@ describe('Strapi', () => { expect(authSetStrategySpy).not.toHaveBeenCalled(); }); + it.each([ + ['common error', new Error('unexpected error')], + ['strapi error', new StrapiError(new StrapiValidationError('invalid auth configuration'))], + ])( + 'should throw an initialization error if a %s error occurs during the auth strategy init', + async (_title, error) => { + // Arrange + const config = { + baseURL: 'https://localhost:1337/api', + auth: { strategy: MockAuthProvider.identifier }, + } satisfies StrapiConfig; + + const mockValidator = new MockStrapiConfigValidator(); + const mockAuthManager = new MockAuthManager(); + + jest.spyOn(mockAuthManager, 'setStrategy').mockImplementationOnce(() => { + throw error; + }); + + // Act + expect( + () => new Strapi(config, mockValidator, mockAuthManager, mockHttpClientFactory) + ).toThrow(StrapiInitializationError); + + expect(mockAuthManager.setStrategy).toHaveBeenCalledWith( + MockAuthProvider.identifier, + undefined + ); + } + ); + it('should throw an error on invalid baseURL', () => { // Arrange const config = { baseURL: 'invalid-url' } satisfies StrapiConfig; From 53f8e9e6570588b242cb38e27afa10a6fdb10144 Mon Sep 17 00:00:00 2001 From: Convly Date: Wed, 29 Jan 2025 15:54:39 +0100 Subject: [PATCH 06/19] chore(README): fix table formatting for namespaces list --- README.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 5698986..f7f1cff 100644 --- a/README.md +++ b/README.md @@ -238,15 +238,15 @@ The `debug` tool allows you to control logs using wildcard patterns (`*`): Below is a list of available namespaces to use: -| Namespace | Description | -| ---------------------------------------- | ----------------------------------------------------------------------------------------- | -| `strapi:core` | Logs SDK initialization, configuration validation, and HTTP client setup. | -| `strapi:validators:config` | Logs details related to SDK configuration validation. | -| `strapi:validators:url` | Logs URL validation processes. | -| `strapi:http` | Logs HTTP client setup, request processing, and response/error handling. | -| `strapi:auth:factory` | Logs the registration and creation of authentication providers. | -| `strapi:auth:manager` | Logs authentication lifecycle management. | -| `strapi:auth:provider:api-token` | Logs operations related to API token authentication. | -| `strapi:ct:collection` | Logs interactions with collection-type content managers. | -| `strapi:ct:single` | Logs interactions with single-type content managers. | -| `strapi:utils:url-helper` | Logs URL helper utility operations (e.g., appending query parameters or formatting URLs). | +| Namespace | Description | +| -------------------------------- | ----------------------------------------------------------------------------------------- | +| `strapi:core` | Logs SDK initialization, configuration validation, and HTTP client setup. | +| `strapi:validators:config` | Logs details related to SDK configuration validation. | +| `strapi:validators:url` | Logs URL validation processes. | +| `strapi:http` | Logs HTTP client setup, request processing, and response/error handling. | +| `strapi:auth:factory` | Logs the registration and creation of authentication providers. | +| `strapi:auth:manager` | Logs authentication lifecycle management. | +| `strapi:auth:provider:api-token` | Logs operations related to API token authentication. | +| `strapi:ct:collection` | Logs interactions with collection-type content managers. | +| `strapi:ct:single` | Logs interactions with single-type content managers. | +| `strapi:utils:url-helper` | Logs URL helper utility operations (e.g., appending query parameters or formatting URLs). | From 35d640e67c51ff169b26b6ae558b162440561014 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-S=C3=A9bastien=20Herbaux?= Date: Wed, 29 Jan 2025 16:03:40 +0100 Subject: [PATCH 07/19] chore: update wording in src/index.ts Co-authored-by: Ben Irvin --- src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/index.ts b/src/index.ts index b60a5c2..a31ca81 100644 --- a/src/index.ts +++ b/src/index.ts @@ -82,7 +82,7 @@ export const strapi = (config: Config) => { const sdkConfig: StrapiConfig = { baseURL }; - // In this factory, to keep things simple, users can't manually set the strategy options. + // In this factory, while there is only one auth strategy available, users can't manually set the strategy options. // Since the SDK constructor needs to define a proper strategy, // it is handled here if the auth property is provided if (auth !== undefined) { From 7e1b602bef7b97dfc0466374aec1b4e1be27e249 Mon Sep 17 00:00:00 2001 From: Convly Date: Thu, 30 Jan 2025 11:42:18 +0100 Subject: [PATCH 08/19] chore(config): remove useless indentation rules --- .editorconfig | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.editorconfig b/.editorconfig index 473e451..4a7ea30 100644 --- a/.editorconfig +++ b/.editorconfig @@ -8,9 +8,5 @@ charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true -[{package.json,*.yml}] -indent_style = space -indent_size = 2 - [*.md] trim_trailing_whitespace = false From 89956b4d757ccd3332ae6f99fecc446e43ef1044 Mon Sep 17 00:00:00 2001 From: Convly Date: Thu, 30 Jan 2025 11:50:09 +0100 Subject: [PATCH 09/19] chore(demos): add Node.js JavaScript demo --- demos/demo-node-javascript/index.js | 16 ++++++++++++++++ demos/demo-node-javascript/package.json | 20 ++++++++++++++++++++ demos/demo-node-javascript/pnpm-lock.yaml | 12 ++++++++++++ 3 files changed, 48 insertions(+) create mode 100644 demos/demo-node-javascript/index.js create mode 100644 demos/demo-node-javascript/package.json create mode 100644 demos/demo-node-javascript/pnpm-lock.yaml diff --git a/demos/demo-node-javascript/index.js b/demos/demo-node-javascript/index.js new file mode 100644 index 0000000..6dccc51 --- /dev/null +++ b/demos/demo-node-javascript/index.js @@ -0,0 +1,16 @@ +const { strapi } = require('@strapi/sdk-js'); + +async function main() { + // Create the SDK instance + const sdk = strapi({ baseURL: 'http://localhost:1337/api' }); + + // Create a collection type query manager for the categories + const categories = sdk.collection('categories'); + + // Fetch the list of all categories + const docs = await categories.find(); + + console.dir(docs, { depth: null }); +} + +main().catch(console.error); diff --git a/demos/demo-node-javascript/package.json b/demos/demo-node-javascript/package.json new file mode 100644 index 0000000..da443e1 --- /dev/null +++ b/demos/demo-node-javascript/package.json @@ -0,0 +1,20 @@ +{ + "name": "demo-node-javascript", + "version": "1.0.0", + "description": "A Strapi SDK demo using a Node x JavaScript application", + "main": "index.js", + "scripts": { + "start": "node index.js", + "debug": "DEBUG=* node index.js" + }, + "dependencies": { + "@strapi/sdk-js": "link:../.." + }, + "keywords": [], + "author": { + "name": "Strapi Solutions SAS", + "email": "hi@strapi.io", + "url": "https://strapi.io" + }, + "license": "ISC" +} diff --git a/demos/demo-node-javascript/pnpm-lock.yaml b/demos/demo-node-javascript/pnpm-lock.yaml new file mode 100644 index 0000000..08fcad6 --- /dev/null +++ b/demos/demo-node-javascript/pnpm-lock.yaml @@ -0,0 +1,12 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + .: + dependencies: + '@strapi/sdk-js': + specifier: link:../.. + version: link:../.. From f99d0651d1702c649884df7d892e71fed96d2a87 Mon Sep 17 00:00:00 2001 From: Convly Date: Thu, 30 Jan 2025 13:08:12 +0100 Subject: [PATCH 10/19] chore(build): update output file extensions for bundler --- package.json | 4 ++-- rollup.config.mjs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index cd3a27c..a9e10b9 100644 --- a/package.json +++ b/package.json @@ -28,8 +28,8 @@ "exports": { ".": { "types": "./dist/index.d.ts", - "require": "./dist/bundle.cjs.js", - "import": "./dist/bundle.esm.js", + "require": "./dist/bundle.cjs", + "import": "./dist/bundle.mjs", "browser": { "import": "./dist/bundle.browser.min.js", "require": "./dist/bundle.browser.min.js" diff --git a/rollup.config.mjs b/rollup.config.mjs index a058b89..4f51b6e 100644 --- a/rollup.config.mjs +++ b/rollup.config.mjs @@ -35,14 +35,14 @@ const node_build = { output: [ // CommonJS build { - file: 'dist/bundle.cjs.js', + file: 'dist/bundle.cjs', format: 'cjs', sourcemap: isProduction ? 'hidden' : true, exports: 'named', }, // ESM build { - file: 'dist/bundle.esm.js', + file: 'dist/bundle.mjs', format: 'esm', sourcemap: isProduction ? 'hidden' : true, exports: 'named', From fa522793ad687d44c2222b2af80361227c38425d Mon Sep 17 00:00:00 2001 From: Convly Date: Thu, 30 Jan 2025 13:08:49 +0100 Subject: [PATCH 11/19] chore(build): update output file extensions in doc --- rollup.config.mjs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rollup.config.mjs b/rollup.config.mjs index 4f51b6e..1556636 100644 --- a/rollup.config.mjs +++ b/rollup.config.mjs @@ -13,11 +13,11 @@ const isProduction = process.env.NODE_ENV === 'production'; * system, ensuring compatibility with a wide range of tools and runtimes. * * Outputs: - * - CommonJS (dist/bundle.cjs.js): for environments using `require`. + * - CommonJS (dist/bundle.cjs): for environments using `require`. * Compatible with older tools and Node.js versions. * Includes source maps for debugging. * - * - ES Module (dist/bundle.esm.js): for modern import/export environments. + * - ES Module (dist/bundle.esm): for modern import/export environments. * Supports tree-shaking for smaller builds and also includes source maps. * * Plugins Used: From fda990d7f6ecf062e83d613e5112aef6cfb1de32 Mon Sep 17 00:00:00 2001 From: Convly Date: Thu, 30 Jan 2025 13:11:13 +0100 Subject: [PATCH 12/19] chore(demos): add demo for node and TypeScript using Strapi SDK --- demos/demo-node-typescript/package.json | 27 ++++++++++++++++++++++ demos/demo-node-typescript/pnpm-lock.yaml | 28 +++++++++++++++++++++++ demos/demo-node-typescript/src/index.ts | 12 ++++++++++ demos/demo-node-typescript/tsconfig.json | 26 +++++++++++++++++++++ 4 files changed, 93 insertions(+) create mode 100644 demos/demo-node-typescript/package.json create mode 100644 demos/demo-node-typescript/pnpm-lock.yaml create mode 100644 demos/demo-node-typescript/src/index.ts create mode 100644 demos/demo-node-typescript/tsconfig.json diff --git a/demos/demo-node-typescript/package.json b/demos/demo-node-typescript/package.json new file mode 100644 index 0000000..bab7024 --- /dev/null +++ b/demos/demo-node-typescript/package.json @@ -0,0 +1,27 @@ +{ + "name": "demo-node-typescript", + "version": "1.0.0", + "description": "A Strapi SDK demo using a Node x TypeScript application", + "main": "dist/index.js", + "private": true, + "type": "module", + "scripts": { + "build": "tsc -p tsconfig.json", + "watch": "tsc -p tsconfig.json -w", + "start": "node dist/index.js", + "debug": "DEBUG=* node dist/index.js" + }, + "dependencies": { + "@strapi/sdk-js": "link:../.." + }, + "devDependencies": { + "typescript": "5.7.3" + }, + "keywords": [], + "author": { + "name": "Strapi Solutions SAS", + "email": "hi@strapi.io", + "url": "https://strapi.io" + }, + "license": "ISC" +} diff --git a/demos/demo-node-typescript/pnpm-lock.yaml b/demos/demo-node-typescript/pnpm-lock.yaml new file mode 100644 index 0000000..868fce2 --- /dev/null +++ b/demos/demo-node-typescript/pnpm-lock.yaml @@ -0,0 +1,28 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + .: + dependencies: + '@strapi/sdk-js': + specifier: link:../.. + version: link:../.. + devDependencies: + typescript: + specifier: 5.7.3 + version: 5.7.3 + +packages: + typescript@5.7.3: + resolution: + { + integrity: sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==, + } + engines: { node: '>=14.17' } + hasBin: true + +snapshots: + typescript@5.7.3: {} diff --git a/demos/demo-node-typescript/src/index.ts b/demos/demo-node-typescript/src/index.ts new file mode 100644 index 0000000..b803d47 --- /dev/null +++ b/demos/demo-node-typescript/src/index.ts @@ -0,0 +1,12 @@ +import { strapi } from '@strapi/sdk-js'; + +// Create the SDK instance +const sdk = strapi({ baseURL: 'http://localhost:1337/api' }); + +// Create a collection type query manager for the categories +const categories = sdk.collection('categories'); + +// Fetch the list of all categories +const docs = await categories.find(); + +console.dir(docs, { depth: null }); diff --git a/demos/demo-node-typescript/tsconfig.json b/demos/demo-node-typescript/tsconfig.json new file mode 100644 index 0000000..a1c54f0 --- /dev/null +++ b/demos/demo-node-typescript/tsconfig.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + /* Language and Environment */ + "allowSyntheticDefaultImports": true, + + /* Modules */ + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "module": "NodeNext", + + /* Emit */ + "moduleResolution": "NodeNext", + + /* Interop Constraints */ + + "outDir": "./dist", + "rootDir": "./src", + "skipLibCheck": false, + + /* Type Checking */ + "strict": true, + + /* Completeness */ + "target": "ESNext" + } +} From 06e2e48c07294ab19b7a8f17f574ec309267df38 Mon Sep 17 00:00:00 2001 From: Convly Date: Thu, 30 Jan 2025 13:12:29 +0100 Subject: [PATCH 13/19] chore(demo): rename demos/ to demo/ --- {demos => demo}/demo-node-javascript/index.js | 0 {demos => demo}/demo-node-javascript/package.json | 0 {demos => demo}/demo-node-javascript/pnpm-lock.yaml | 0 {demos => demo}/demo-node-typescript/package.json | 0 {demos => demo}/demo-node-typescript/pnpm-lock.yaml | 0 {demos => demo}/demo-node-typescript/src/index.ts | 0 {demos => demo}/demo-node-typescript/tsconfig.json | 0 7 files changed, 0 insertions(+), 0 deletions(-) rename {demos => demo}/demo-node-javascript/index.js (100%) rename {demos => demo}/demo-node-javascript/package.json (100%) rename {demos => demo}/demo-node-javascript/pnpm-lock.yaml (100%) rename {demos => demo}/demo-node-typescript/package.json (100%) rename {demos => demo}/demo-node-typescript/pnpm-lock.yaml (100%) rename {demos => demo}/demo-node-typescript/src/index.ts (100%) rename {demos => demo}/demo-node-typescript/tsconfig.json (100%) diff --git a/demos/demo-node-javascript/index.js b/demo/demo-node-javascript/index.js similarity index 100% rename from demos/demo-node-javascript/index.js rename to demo/demo-node-javascript/index.js diff --git a/demos/demo-node-javascript/package.json b/demo/demo-node-javascript/package.json similarity index 100% rename from demos/demo-node-javascript/package.json rename to demo/demo-node-javascript/package.json diff --git a/demos/demo-node-javascript/pnpm-lock.yaml b/demo/demo-node-javascript/pnpm-lock.yaml similarity index 100% rename from demos/demo-node-javascript/pnpm-lock.yaml rename to demo/demo-node-javascript/pnpm-lock.yaml diff --git a/demos/demo-node-typescript/package.json b/demo/demo-node-typescript/package.json similarity index 100% rename from demos/demo-node-typescript/package.json rename to demo/demo-node-typescript/package.json diff --git a/demos/demo-node-typescript/pnpm-lock.yaml b/demo/demo-node-typescript/pnpm-lock.yaml similarity index 100% rename from demos/demo-node-typescript/pnpm-lock.yaml rename to demo/demo-node-typescript/pnpm-lock.yaml diff --git a/demos/demo-node-typescript/src/index.ts b/demo/demo-node-typescript/src/index.ts similarity index 100% rename from demos/demo-node-typescript/src/index.ts rename to demo/demo-node-typescript/src/index.ts diff --git a/demos/demo-node-typescript/tsconfig.json b/demo/demo-node-typescript/tsconfig.json similarity index 100% rename from demos/demo-node-typescript/tsconfig.json rename to demo/demo-node-typescript/tsconfig.json From e7fbdd61924963ddbc0a6379703d7aa0fcd4ff77 Mon Sep 17 00:00:00 2001 From: Ben Irvin Date: Thu, 30 Jan 2025 13:51:49 +0100 Subject: [PATCH 14/19] chore: add api tokens to demo projects on seed --- demo/.strapi-app/scripts/seed.js | 31 ++++++++++++++++++++ demo/.strapi-app/src/index.ts | 5 +--- demo/demo-node-javascript/package.json | 4 +-- demo/demo-node-javascript/{ => src}/index.js | 2 +- demo/demo-node-typescript/package.json | 3 +- demo/demo-node-typescript/pnpm-lock.yaml | 25 ++++++++++++---- demo/demo-node-typescript/src/index.ts | 2 +- 7 files changed, 58 insertions(+), 14 deletions(-) rename demo/demo-node-javascript/{ => src}/index.js (72%) diff --git a/demo/.strapi-app/scripts/seed.js b/demo/.strapi-app/scripts/seed.js index ecb7623..38838b2 100644 --- a/demo/.strapi-app/scripts/seed.js +++ b/demo/.strapi-app/scripts/seed.js @@ -266,6 +266,11 @@ async function createApiTokens(app) { { name: 'Read Only Token', type: 'read-only' }, ]; + const envPaths = [ + path.join(__dirname, '../../demo-node-javascript/.env'), + path.join(__dirname, '../../demo-node-typescript/.env'), + ]; + for (const tokenType of tokenTypes) { const tokenDetails = { name: tokenType.name, @@ -282,7 +287,33 @@ async function createApiTokens(app) { console.log( `Created API Token ${token.name} (${token.type}) with access key: ${token.accessKey}` ); + + // Update .env files + const envKey = tokenType.type.toUpperCase().replace('-', '_') + '_TOKEN'; + for (const envPath of envPaths) { + updateEnvFile(envPath, envKey, token.accessKey); + } + } +} + +function updateEnvFile(filePath, key, value) { + let envContent = ''; + if (fs.existsSync(filePath)) { + envContent = fs.readFileSync(filePath, 'utf8'); + } + + const envLines = envContent.split('\n'); + const keyIndex = envLines.findIndex((line) => line.startsWith(`${key}=`)); + + if (keyIndex !== -1) { + // Update existing key + envLines[keyIndex] = `${key}=${value}`; + } else { + // Add new key + envLines.push(`${key}=${value}`); } + + fs.writeFileSync(filePath, envLines.join('\n'), 'utf8'); } async function main() { diff --git a/demo/.strapi-app/src/index.ts b/demo/.strapi-app/src/index.ts index 16b41ab..58be268 100644 --- a/demo/.strapi-app/src/index.ts +++ b/demo/.strapi-app/src/index.ts @@ -7,10 +7,7 @@ export default { * * This gives you an opportunity to extend code. */ - register(/* { strapi }: { strapi: Core.Strapi } */) { - // Full Access Token: 08f8b5a10cc8a723e8e90d5f7c1bd379bd7e60dda3d8e10489e978149019747c6af671963167c71eeb71fc43cf795aa70e7bcf8743ff23db39570146dccf5d464a7983d43f2c4e58c819e9434f86ae81f694bb320c6edc4eac6b8fd1fc53cff35f45f938cb1aaf932a0b320d92cb3b230df6943f95b41e6ca5c2b9e786cbc99e - // Read Only Token: - }, + register(/* { strapi }: { strapi: Core.Strapi } */) {}, /** * An asynchronous bootstrap function that runs before diff --git a/demo/demo-node-javascript/package.json b/demo/demo-node-javascript/package.json index da443e1..4917931 100644 --- a/demo/demo-node-javascript/package.json +++ b/demo/demo-node-javascript/package.json @@ -4,8 +4,8 @@ "description": "A Strapi SDK demo using a Node x JavaScript application", "main": "index.js", "scripts": { - "start": "node index.js", - "debug": "DEBUG=* node index.js" + "start": "node src/index.js", + "debug": "DEBUG=* node src/index.js" }, "dependencies": { "@strapi/sdk-js": "link:../.." diff --git a/demo/demo-node-javascript/index.js b/demo/demo-node-javascript/src/index.js similarity index 72% rename from demo/demo-node-javascript/index.js rename to demo/demo-node-javascript/src/index.js index 6dccc51..0c2df4e 100644 --- a/demo/demo-node-javascript/index.js +++ b/demo/demo-node-javascript/src/index.js @@ -2,7 +2,7 @@ const { strapi } = require('@strapi/sdk-js'); async function main() { // Create the SDK instance - const sdk = strapi({ baseURL: 'http://localhost:1337/api' }); + const sdk = strapi({ baseURL: 'http://localhost:1337/api', auth: process.env.FULL_ACCESS_TOKEN }); // READ_ONLY_TOKEN is also available // Create a collection type query manager for the categories const categories = sdk.collection('categories'); diff --git a/demo/demo-node-typescript/package.json b/demo/demo-node-typescript/package.json index bab7024..338e8a0 100644 --- a/demo/demo-node-typescript/package.json +++ b/demo/demo-node-typescript/package.json @@ -12,7 +12,8 @@ "debug": "DEBUG=* node dist/index.js" }, "dependencies": { - "@strapi/sdk-js": "link:../.." + "@strapi/sdk-js": "link:../..", + "@types/node": "^22.12.0" }, "devDependencies": { "typescript": "5.7.3" diff --git a/demo/demo-node-typescript/pnpm-lock.yaml b/demo/demo-node-typescript/pnpm-lock.yaml index 868fce2..58a3bee 100644 --- a/demo/demo-node-typescript/pnpm-lock.yaml +++ b/demo/demo-node-typescript/pnpm-lock.yaml @@ -5,24 +5,39 @@ settings: excludeLinksFromLockfile: false importers: + .: dependencies: '@strapi/sdk-js': specifier: link:../.. version: link:../.. + '@types/node': + specifier: ^22.12.0 + version: 22.12.0 devDependencies: typescript: specifier: 5.7.3 version: 5.7.3 packages: + + '@types/node@22.12.0': + resolution: {integrity: sha512-Fll2FZ1riMjNmlmJOdAyY5pUbkftXslB5DgEzlIuNaiWhXd00FhWxVC/r4yV/4wBb9JfImTu+jiSvXTkJ7F/gA==} + typescript@5.7.3: - resolution: - { - integrity: sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==, - } - engines: { node: '>=14.17' } + resolution: {integrity: sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==} + engines: {node: '>=14.17'} hasBin: true + undici-types@6.20.0: + resolution: {integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==} + snapshots: + + '@types/node@22.12.0': + dependencies: + undici-types: 6.20.0 + typescript@5.7.3: {} + + undici-types@6.20.0: {} diff --git a/demo/demo-node-typescript/src/index.ts b/demo/demo-node-typescript/src/index.ts index b803d47..a281188 100644 --- a/demo/demo-node-typescript/src/index.ts +++ b/demo/demo-node-typescript/src/index.ts @@ -1,7 +1,7 @@ import { strapi } from '@strapi/sdk-js'; // Create the SDK instance -const sdk = strapi({ baseURL: 'http://localhost:1337/api' }); +const sdk = strapi({ baseURL: 'http://localhost:1337/api', auth: process.env.FULL_ACCESS_TOKEN }); // READ_ONLY_TOKEN is also available // Create a collection type query manager for the categories const categories = sdk.collection('categories'); From 421608ef703145a4dc15774a582f9d61e0d39a9d Mon Sep 17 00:00:00 2001 From: Ben Irvin Date: Thu, 30 Jan 2025 13:58:32 +0100 Subject: [PATCH 15/19] chore: fix scripts for portability --- package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index a9e10b9..c677bf3 100644 --- a/package.json +++ b/package.json @@ -53,9 +53,9 @@ "prettier:write": "prettier --write .", "test": "jest --verbose", "test:cov": "jest --verbose --coverage", - "demo:seed": "(cd demo/.strapi-app && pnpm seed:example)", - "demo:seed:clean": "rm demo/.strapi-app/.tmp/data.db ; pnpm run demo:seed", - "demo:run": "(cd demo/.strapi-app && pnpm develop)", + "demo:seed": "pnpm -C demo/.strapi-app seed:example", + "demo:seed:clean": "pnpm exec rimraf demo/.strapi-app/.tmp/data.db && pnpm run demo:seed", + "demo:run": "pnpm -C demo/.strapi-app develop", "ts:check": "tsc -p tsconfig.build.json --noEmit", "watch": "rollup --config rollup.config.mjs --watch --failAfterWarnings", "prepack": "pnpm exec ./scripts/pre-pack.sh" From 2fd4f02a5ec62caaefdc2558f4b497847a2bf2e9 Mon Sep 17 00:00:00 2001 From: Ben Irvin Date: Thu, 30 Jan 2025 13:59:53 +0100 Subject: [PATCH 16/19] chore: devdeps --- demo/demo-node-typescript/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/demo/demo-node-typescript/package.json b/demo/demo-node-typescript/package.json index 338e8a0..940d309 100644 --- a/demo/demo-node-typescript/package.json +++ b/demo/demo-node-typescript/package.json @@ -12,10 +12,10 @@ "debug": "DEBUG=* node dist/index.js" }, "dependencies": { - "@strapi/sdk-js": "link:../..", - "@types/node": "^22.12.0" + "@strapi/sdk-js": "link:../.." }, "devDependencies": { + "@types/node": "^22.12.0", "typescript": "5.7.3" }, "keywords": [], From 225fd97b28b52f28cf7e7587b3cb166253888810 Mon Sep 17 00:00:00 2001 From: Ben Irvin Date: Thu, 30 Jan 2025 14:06:44 +0100 Subject: [PATCH 17/19] chore: fixed package version --- demo/demo-node-typescript/package.json | 2 +- demo/demo-node-typescript/pnpm-lock.yaml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/demo/demo-node-typescript/package.json b/demo/demo-node-typescript/package.json index 940d309..3b23153 100644 --- a/demo/demo-node-typescript/package.json +++ b/demo/demo-node-typescript/package.json @@ -15,7 +15,7 @@ "@strapi/sdk-js": "link:../.." }, "devDependencies": { - "@types/node": "^22.12.0", + "@types/node": "22.12.0", "typescript": "5.7.3" }, "keywords": [], diff --git a/demo/demo-node-typescript/pnpm-lock.yaml b/demo/demo-node-typescript/pnpm-lock.yaml index 58a3bee..11d8db0 100644 --- a/demo/demo-node-typescript/pnpm-lock.yaml +++ b/demo/demo-node-typescript/pnpm-lock.yaml @@ -11,10 +11,10 @@ importers: '@strapi/sdk-js': specifier: link:../.. version: link:../.. + devDependencies: '@types/node': - specifier: ^22.12.0 + specifier: 22.12.0 version: 22.12.0 - devDependencies: typescript: specifier: 5.7.3 version: 5.7.3 From 5cca7c5225a10a57feb214b21110d21047d306ea Mon Sep 17 00:00:00 2001 From: Ben Irvin Date: Thu, 30 Jan 2025 14:10:57 +0100 Subject: [PATCH 18/19] chore: add demos to readme --- README.md | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/README.md b/README.md index f7f1cff..00750fb 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,7 @@ - [`.collection()`](#collectionresource) - [`.single()`](#singleresource) 5. [Debug](#-debug) +6. [Demo Projects](#-demo-projects) ## 🛠 Getting started @@ -250,3 +251,48 @@ Below is a list of available namespaces to use: | `strapi:ct:collection` | Logs interactions with collection-type content managers. | | `strapi:ct:single` | Logs interactions with single-type content managers. | | `strapi:utils:url-helper` | Logs URL helper utility operations (e.g., appending query parameters or formatting URLs). | + +## 🚀 Demo Projects + +This repository includes demo projects located in the `/demo` directory to help you get started with using the Strapi SDK. The actual Strapi application is located in the `.strapi-app` directory. + +### Demo Structure + +- **`.strapi-app`**: This is the main Strapi application used for the demo projects. +- **`demo/demo-node-typescript`**: A Node.js project using TypeScript. +- **`demo/demo-node-javascript`**: A Node.js project using JavaScript. + +### Using Demo Scripts + +The `package.json` includes several scripts to help you manage and run the demo projects: + +- **`demo:seed`**: Seeds the Strapi application with example data. This script also generates `.env` files with API tokens for the `demo-node-typescript` and `demo-node-javascript` projects. + + ```bash + pnpm run demo:seed + ``` + +- **`demo:seed:clean`**: Cleans the existing data and re-seeds the Strapi application. + + ```bash + pnpm run demo:seed:clean + ``` + +- **`demo:run`**: Starts the Strapi application in development mode. + + ```bash + pnpm run demo:run + ``` + +### Adding New Projects + +If you add new projects to the `/demo` directory, you will need to update the seed script located at `/demo/.strapi-app/scripts/seed.js` with the new project paths to ensure they are properly configured and seeded. + +### Future Plans + +We plan to expand the demo projects to include: + +- A basic HTML project. +- A Next.js project. + +These additions will provide more examples of how to integrate the Strapi SDK into different types of applications. From ceec2a20a56b22b90b2085111650920756e2a87c Mon Sep 17 00:00:00 2001 From: Ben Irvin Date: Fri, 31 Jan 2025 09:59:30 +0100 Subject: [PATCH 19/19] enhancement: demo setup script --- package.json | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index c677bf3..28223c4 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,13 @@ "build": "rollup --config rollup.config.mjs --failAfterWarnings", "build:clean": "pnpm run clean && pnpm run build", "clean": "pnpm exec rimraf ./dist", + "demo:build": "pnpm run build && pnpm -C demo/demo-node-typescript build", + "demo:env": "test -f demo/.strapi-app/.env || cp demo/.strapi-app/.env.example demo/.strapi-app/.env", + "demo:install": "pnpm install && pnpm -C demo/.strapi-app install && pnpm -C demo/demo-node-typescript install && pnpm -C demo/demo-node-javascript install", + "demo:seed": "pnpm -C demo/.strapi-app seed:example", + "demo:seed:clean": "pnpm exec rimraf demo/.strapi-app/.tmp/data.db && pnpm run demo:seed", + "demo:setup": "pnpm demo:install && pnpm run demo:env && pnpm run demo:build && pnpm run demo:seed:clean", + "demo:run": "pnpm -C demo/.strapi-app develop", "lint": "eslint .", "lint:fix": "eslint . --fix", "lint:fix:dry": "eslint . --fix-dry-run", @@ -53,9 +60,6 @@ "prettier:write": "prettier --write .", "test": "jest --verbose", "test:cov": "jest --verbose --coverage", - "demo:seed": "pnpm -C demo/.strapi-app seed:example", - "demo:seed:clean": "pnpm exec rimraf demo/.strapi-app/.tmp/data.db && pnpm run demo:seed", - "demo:run": "pnpm -C demo/.strapi-app develop", "ts:check": "tsc -p tsconfig.build.json --noEmit", "watch": "rollup --config rollup.config.mjs --watch --failAfterWarnings", "prepack": "pnpm exec ./scripts/pre-pack.sh"