diff --git a/.github/workflows/node-build.yml b/.github/workflows/node-build.yml index fe76b0fa..a7e81035 100644 --- a/.github/workflows/node-build.yml +++ b/.github/workflows/node-build.yml @@ -36,6 +36,7 @@ jobs: src: - '.github/workflows/**' - 'src/**' + - 'test/types/**' - 'appinfo/info.xml' - 'package.json' - 'package-lock.json' @@ -86,6 +87,9 @@ jobs: npm ci npm run build --if-present + - name: Check TypeScript consumers + run: npm run check:types + - name: Check build changes run: | bash -c "[[ ! \"`git status --porcelain `\" ]] || (echo 'Please recompile and commit the assets, see the section \"Show changes on failure\" for details' && exit 1)" diff --git a/README.md b/README.md index e479acb7..bd2a8880 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,29 @@ npm run dev npm run build ``` +## TypeScript definitions + +`npm run build` generates `dist/index.d.ts` for ES modules and +`dist/index.d.cts` for CommonJS. Development and watch builds also generate +these files. They are included in the npm package and resolved automatically: + +```typescript +import DavClient from '@nextcloud/cdav-library' + +const client = new DavClient({ rootUrl: 'https://example.com/remote.php/dav/' }) +await client.connect({ enableCalDAV: true, enableCardDAV: true }) +``` + +Definitions are generated from the JavaScript and JSDoc using the existing Vite +build. Update the JSDoc when changing the API; do not edit generated files in +`dist`. Dynamically exposed model properties need a JSDoc-annotated `this.property` +reference before they are installed in the constructor. Some legacy APIs still +use broad types where their JSDoc does not provide more detail. + +Run `npm run test:types` to build and check strict TypeScript consumers with +NodeNext (ES modules and CommonJS) and bundler module resolution. To check an +existing build, use `npm run check:types`. + ## Running tests You can use the provided npm command to run all tests by using: diff --git a/REUSE.toml b/REUSE.toml index 1dd65a66..9f2dc640 100644 --- a/REUSE.toml +++ b/REUSE.toml @@ -6,7 +6,7 @@ SPDX-PackageSupplier = "Nextcloud " SPDX-PackageDownloadLocation = "https://github.com/nextcloud/cdav-library" [[annotations]] -path = ["package.json", "package-lock.json", "test/assets/unit/parser/dprop.xml"] +path = ["package.json", "package-lock.json", "tsconfig.json", "test/types/*.json", "test/assets/unit/parser/dprop.xml"] precedence = "aggregate" SPDX-FileCopyrightText = "2018 Nextcloud GmbH and Nextcloud contributors" SPDX-License-Identifier = "AGPL-3.0-or-later" diff --git a/package-lock.json b/package-lock.json index 03ea5337..5e30b013 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,6 +19,7 @@ "@vitest/coverage-istanbul": "^4.1.11", "conventional-changelog-cli": "^5.0.0", "jsdom": "^30.0.1", + "typescript": "^5.9.3", "vite": "^7.3.6", "vitest": "^4.1.11", "xpath-ts": "^1.3.13" diff --git a/package.json b/package.json index 955e1bd3..05e537ee 100644 --- a/package.json +++ b/package.json @@ -6,8 +6,14 @@ "main": "dist/index.cjs", "exports": { ".": { - "import": "./dist/index.mjs", - "require": "./dist/index.cjs" + "import": { + "types": "./dist/index.d.ts", + "default": "./dist/index.mjs" + }, + "require": { + "types": "./dist/index.d.cts", + "default": "./dist/index.cjs" + } } }, "files": [ @@ -25,7 +31,9 @@ "test:watch": "vitest", "lint": "eslint src", "lint:fix": "eslint src --fix", - "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s" + "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s", + "test:types": "npm run build && npm run check:types", + "check:types": "tsc -p test/types/tsconfig.json && tsc -p test/types/tsconfig.bundler.json" }, "repository": { "type": "git", @@ -61,9 +69,11 @@ "jsdom": "^30.0.1", "vite": "^7.3.6", "vitest": "^4.1.11", - "xpath-ts": "^1.3.13" + "xpath-ts": "^1.3.13", + "typescript": "^5.9.3" }, "dependencies": { "@nextcloud/axios": "^2.6.0" - } + }, + "types": "./dist/index.d.ts" } diff --git a/src/index.js b/src/index.js index 2900cf1e..a2ca6348 100644 --- a/src/index.js +++ b/src/index.js @@ -51,7 +51,7 @@ export default class DavClient { /** * Principal object of current user * - * @type {Principal} + * @type {Principal|null} */ this.currentUserPrincipal = null @@ -111,7 +111,7 @@ export default class DavClient { /** * initializes the DAVClient * - * @param {object} options + * @param {import('./models/principal.js').PrincipalPropfindOptions} options * @return {Promise} */ async connect(options = { enableCalDAV: false, enableCardDAV: false }) { @@ -227,7 +227,7 @@ export default class DavClient { * * @param {string} address Address of the building the room is in * @param {string} story Story inside the building the room is in - * @return {Promise<[]>} + * @return {Promise} */ async principalPropertySearchByAddressAndStory(address, story) { const [skeleton] = XMLUtility.getRootSkeleton([NS.DAV, 'principal-property-search']) @@ -388,7 +388,7 @@ export default class DavClient { * * @param {Array} props * @param {string} match - * @param {string} test 'anyof', 'allof' or none + * @param {'anyof'|'allof'} [test] 'anyof', 'allof' or none * @return {Promise} */ async principalPropertySearch(props, match, test) { @@ -437,7 +437,7 @@ export default class DavClient { * finds one principal at a given principalUrl * * @param {string} principalUrl - * @return {Promise} + * @return {Promise} */ async findPrincipal(principalUrl) { return this._request.propFind(principalUrl, Principal.getPropFindList()).then(({ body }) => { diff --git a/src/models/addressBook.js b/src/models/addressBook.js index f5f27ef0..694936b6 100644 --- a/src/models/addressBook.js +++ b/src/models/addressBook.js @@ -29,7 +29,6 @@ const debug = debugFactory('AddressBook') * * The first two allowing read-write access * - * @augments DavCollection */ export class AddressBook extends davCollectionShareable(DavCollection) { /** @@ -41,6 +40,21 @@ export class AddressBook extends davCollectionShareable(DavCollection) { super._registerObjectFactory('text/vcard', VCard) super._registerPropSetFactory(addressBookPropSet) + // Type declarations for properties installed dynamically below. + /** + * @type {string | undefined} + */ + this.description + /** + * @type {boolean | undefined} + */ + this.enabled + /** + * @type {boolean | undefined} + * @readonly + */ + this.readOnly + super._exposeProperty('description', NS.IETF_CARDDAV, 'addressbook-description', true) super._exposeProperty('enabled', NS.OWNCLOUD, 'enabled', true) super._exposeProperty('readOnly', NS.OWNCLOUD, 'read-only') diff --git a/src/models/calendar.js b/src/models/calendar.js index f69fc8c3..77fdfb59 100644 --- a/src/models/calendar.js +++ b/src/models/calendar.js @@ -36,7 +36,6 @@ const debug = debugFactory('Calendar') * * The first seven allowing read-write access * - * @augments DavCollection */ export class Calendar extends davCollectionPublishable(davCollectionShareable(DavCollection)) { /** @@ -48,6 +47,45 @@ export class Calendar extends davCollectionPublishable(davCollectionShareable(Da super._registerObjectFactory('text/calendar', VObject) super._registerPropSetFactory(calendarPropSet) + // Type declarations for properties installed dynamically below. + /** + * @type {string | undefined} + */ + this.color + /** + * @type {boolean | undefined} + */ + this.enabled + /** + * @type {number | undefined} + */ + this.order + /** + * @type {string | undefined} + */ + this.timezone + /** + * @type {string[] | undefined} + * @readonly + */ + this.components + /** + * @type {string | undefined} + */ + this.transparency + /** + * @type {number | undefined} + */ + this.defaultAlarmPartDay + /** + * @type {number | undefined} + */ + this.defaultAlarmFullDay + /** + * @type {boolean | undefined} + */ + this.disableAlarmNotifications + super._exposeProperty('color', NS.APPLE, 'calendar-color', true) super._exposeProperty('enabled', NS.OWNCLOUD, 'calendar-enabled', true) super._exposeProperty('order', NS.APPLE, 'calendar-order', true) @@ -92,7 +130,7 @@ export class Calendar extends davCollectionPublishable(davCollectionShareable(Da /** * find all VObjects in a time-range filtered by type * - * @param {number} type + * @param {string} type * @param {Date} from * @param {Date} to * @return {Promise} @@ -122,7 +160,7 @@ export class Calendar extends davCollectionPublishable(davCollectionShareable(Da /** * create a VObject inside this calendar * - * @param data + * @param {string} data * @return {Promise} */ async createVObject(data) { diff --git a/src/models/calendarHome.js b/src/models/calendarHome.js index 08b352c8..071858c2 100644 --- a/src/models/calendarHome.js +++ b/src/models/calendarHome.js @@ -7,6 +7,10 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ +/** + * @typedef {import('./vobject.js').VObject} VObject + */ + import { debugFactory } from '../debug.js' import * as NS from '../utility/namespaceUtility.js' import * as XMLUtility from '../utility/xmlUtility.js' diff --git a/src/models/calendarTrashBin.js b/src/models/calendarTrashBin.js index 9c2afb5a..ff004b7d 100644 --- a/src/models/calendarTrashBin.js +++ b/src/models/calendarTrashBin.js @@ -21,6 +21,13 @@ export class CalendarTrashBin extends DavCollection { super._registerObjectFactory('text/calendar', DeletedCalendarObject) + // Type declarations for properties installed dynamically below. + /** + * @type {number | undefined} + * @readonly + */ + this.retentionDuration + super._exposeProperty('retentionDuration', NS.NEXTCLOUD, 'trash-bin-retention-duration') } diff --git a/src/models/davCollection.js b/src/models/davCollection.js index a1a6b479..d7532b21 100644 --- a/src/models/davCollection.js +++ b/src/models/davCollection.js @@ -7,6 +7,10 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ +/** + * @typedef {import('../request.js').default} Request + */ + import { debugFactory } from '../debug.js' import davCollectionPropSet from '../propset/davCollectionPropSet.js' import * as NS from '../utility/namespaceUtility.js' @@ -52,6 +56,37 @@ export class DavCollection extends DAVEventListener { this._registerPropSetFactory(davCollectionPropSet) + // Type declarations for properties installed dynamically below. + /** + * @type {string | undefined} + */ + this.displayname + /** + * @type {string | undefined} + * @readonly + */ + this.owner + /** + * @type {string[] | undefined} + * @readonly + */ + this.resourcetype + /** + * @type {string | undefined} + * @readonly + */ + this.syncToken + /** + * @type {string[] | undefined} + * @readonly + */ + this.currentUserPrivilegeSet + /** + * @type {string} + * @readonly + */ + this.url + this._exposeProperty('displayname', NS.DAV, 'displayname', true) this._exposeProperty('owner', NS.DAV, 'owner') this._exposeProperty('resourcetype', NS.DAV, 'resourcetype') diff --git a/src/models/davCollectionPublishable.js b/src/models/davCollectionPublishable.js index abe7b5bf..57fc0d46 100644 --- a/src/models/davCollectionPublishable.js +++ b/src/models/davCollectionPublishable.js @@ -14,16 +14,24 @@ const debug = debugFactory('DavCollectionPublishable') /** * - * @param Base + * @template {new (...args: any[]) => object} T + * @param {T} Base */ export function davCollectionPublishable(Base) { - return class extends Base { + class Publishable extends Base { /** * @inheritDoc */ constructor(...args) { super(...args) + // Type declarations for properties installed dynamically below. + /** + * @type {string | undefined} + * @readonly + */ + this.publishURL + super._exposeProperty('publishURL', NS.CALENDARSERVER, 'publish-url') } @@ -68,4 +76,7 @@ export function davCollectionPublishable(Base) { ]) } } + + // Keep inferred member types without the implicit index signature of a JS mixin. + return /** @type {T & (new (...args: any[]) => Pick)} */ (Publishable) } diff --git a/src/models/davCollectionShareable.js b/src/models/davCollectionShareable.js index 188024b7..18dfe678 100644 --- a/src/models/davCollectionShareable.js +++ b/src/models/davCollectionShareable.js @@ -14,16 +14,29 @@ const debug = debugFactory('DavCollectionShareable') /** * - * @param Base + * @template {new (...args: any[]) => object} T + * @param {T} Base */ export function davCollectionShareable(Base) { - return class extends Base { + class Shareable extends Base { /** * @inheritDoc */ constructor(...args) { super(...args) + // Type declarations for properties installed dynamically below. + /** + * @type {Array<{href: string, access: string[], 'common-name': string | null, 'invite-accepted': boolean}> | undefined} + * @readonly + */ + this.shares + /** + * @type {string[] | undefined} + * @readonly + */ + this.allowedSharingModes + super._exposeProperty('shares', NS.OWNCLOUD, 'invite') super._exposeProperty('allowedSharingModes', NS.CALENDARSERVER, 'allowed-sharing-modes') } @@ -144,4 +157,7 @@ export function davCollectionShareable(Base) { ]) } } + + // Keep inferred member types without the implicit index signature of a JS mixin. + return /** @type {T & (new (...args: any[]) => Pick)} */ (Shareable) } diff --git a/src/models/davEventListener.js b/src/models/davEventListener.js index 4c47d41b..3b946865 100644 --- a/src/models/davEventListener.js +++ b/src/models/davEventListener.js @@ -7,6 +7,10 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ +/** + * @typedef {import('./davEvent.js').default} DAVEvent + */ + export default class DAVEventListener { constructor() { this._eventListeners = {} diff --git a/src/models/davObject.js b/src/models/davObject.js index a3e82bb1..b5fe0d07 100644 --- a/src/models/davObject.js +++ b/src/models/davObject.js @@ -7,6 +7,11 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ +/** + * @typedef {import('./davCollection.js').DavCollection} DavCollection + * @typedef {import('../request.js').default} Request + */ + import { debugFactory } from '../debug.js' import NetworkRequestClientError from '../errors/networkRequestClientError.js' import * as NS from '../utility/namespaceUtility.js' @@ -44,6 +49,22 @@ export class DavObject extends DAVEventListener { _propSetFactory: [], }) + // Type declarations for properties installed dynamically below. + /** + * @type {string | null | undefined} + */ + this.etag + /** + * @type {string | undefined} + * @readonly + */ + this.contenttype + /** + * @type {string} + * @readonly + */ + this.url + this._exposeProperty('etag', NS.DAV, 'getetag', true) this._exposeProperty('contenttype', NS.DAV, 'getcontenttype') diff --git a/src/models/deletedCalendarObject.js b/src/models/deletedCalendarObject.js index 68547db7..65601428 100644 --- a/src/models/deletedCalendarObject.js +++ b/src/models/deletedCalendarObject.js @@ -22,6 +22,28 @@ export class DeletedCalendarObject extends VObject { constructor(...args) { super(...args) + // Type declarations for properties installed dynamically below. + /** + * @type {string | undefined} + * @readonly + */ + this.calendarUri + /** + * @type {string | undefined} + * @readonly + */ + this.sourceCalendarUri + /** + * @type {string | undefined} + * @readonly + */ + this.calendarOwnerPrincipalUri + /** + * @type {Date | undefined} + * @readonly + */ + this.deletedAt + super._exposeProperty('calendarUri', NS.NEXTCLOUD, 'calendar-uri') super._exposeProperty('sourceCalendarUri', NS.NEXTCLOUD, 'source-calendar-uri') super._exposeProperty('calendarOwnerPrincipalUri', NS.NEXTCLOUD, 'calendar-owner-principal-uri') diff --git a/src/models/principal.js b/src/models/principal.js index 0335dbe4..e325da0b 100644 --- a/src/models/principal.js +++ b/src/models/principal.js @@ -44,6 +44,127 @@ export class Principal extends DavObject { this._registerPropSetFactory(prinicipalPropSet) + // Type declarations for properties installed dynamically below. + /** + * @type {string | undefined} + * @readonly + */ + this.displayname + /** + * @type {string | undefined} + * @readonly + */ + this.calendarUserType + /** + * @type {string[] | undefined} + * @readonly + */ + this.calendarUserAddressSet + /** + * @type {string | undefined} + * @readonly + */ + this.principalUrl + /** + * @type {string | undefined} + * @readonly + */ + this.email + /** + * @type {string | undefined} + * @readonly + */ + this.language + /** + * @type {string[] | undefined} + * @readonly + */ + this.calendarHomes + /** + * @type {string | undefined} + * @readonly + */ + this.scheduleInbox + /** + * @type {string | undefined} + * @readonly + */ + this.scheduleOutbox + /** + * @type {string | undefined} + */ + this.scheduleDefaultCalendarUrl + /** + * @type {string[] | undefined} + * @readonly + */ + this.addressBookHomes + /** + * @type {string | undefined} + * @readonly + */ + this.roomType + /** + * @type {number | undefined} + * @readonly + */ + this.roomSeatingCapacity + /** + * @type {string | undefined} + * @readonly + */ + this.roomBuildingName + /** + * @type {string | undefined} + * @readonly + */ + this.roomBuildingAddress + /** + * @type {string | undefined} + * @readonly + */ + this.roomBuildingStory + /** + * @type {string | undefined} + * @readonly + */ + this.roomBuildingRoomNumber + /** + * @type {string | undefined} + * @readonly + */ + this.roomFeatures + /** + * @type {string} + * @readonly + */ + this.principalScheme + /** + * @type {string | null} + * @readonly + */ + this.userId + /** + * @type {string | null} + * @readonly + */ + this.groupId + /** + * @type {string | null} + * @readonly + */ + this.resourceId + /** + * @type {string | null} + * @readonly + */ + this.roomId + /** + * @type {string} + * @readonly + */ + this.roomAddress + this._exposeProperty('displayname', NS.DAV, 'displayname') this._exposeProperty('calendarUserType', NS.IETF_CALDAV, 'calendar-user-type') this._exposeProperty('calendarUserAddressSet', NS.IETF_CALDAV, 'calendar-user-address-set') diff --git a/src/models/scheduleInbox.js b/src/models/scheduleInbox.js index 11fb2ebf..f2cce98e 100644 --- a/src/models/scheduleInbox.js +++ b/src/models/scheduleInbox.js @@ -21,6 +21,12 @@ export default class ScheduleInbox extends Calendar { super._registerPropSetFactory(scheduleInboxPropSet) // https://tools.ietf.org/html/rfc7953#section-7.2.4 + // Type declarations for properties installed dynamically below. + /** + * @type {string | undefined} + */ + this.availability + super._exposeProperty('availability', NS.IETF_CALDAV, 'calendar-availability', true) } diff --git a/src/models/subscription.js b/src/models/subscription.js index a285c4d8..fe6c4e9c 100644 --- a/src/models/subscription.js +++ b/src/models/subscription.js @@ -23,6 +23,28 @@ export class Subscription extends Calendar { constructor(...args) { super(...args) + // Type declarations for properties installed dynamically below. + /** + * @type {string | undefined} + */ + this.source + /** + * @type {string | undefined} + */ + this.refreshRate + /** + * @type {boolean | undefined} + */ + this.stripTodos + /** + * @type {boolean | undefined} + */ + this.stripAlarms + /** + * @type {boolean | undefined} + */ + this.stripAttachments + super._exposeProperty('source', NS.CALENDARSERVER, 'source', true) super._exposeProperty('refreshRate', NS.APPLE, 'refreshrate', true) super._exposeProperty('stripTodos', NS.CALENDARSERVER, 'subscribed-strip-todos', true) diff --git a/src/models/vcard.js b/src/models/vcard.js index f7340985..b753932f 100644 --- a/src/models/vcard.js +++ b/src/models/vcard.js @@ -28,6 +28,21 @@ export class VCard extends DavObject { this._registerPropSetFactory(vcardPropSet) + // Type declarations for properties installed dynamically below. + /** + * @type {string | undefined} + */ + this.data + /** + * @type {boolean | undefined} + * @readonly + */ + this.hasphoto + /** + * @type {boolean | undefined} + */ + this.favorite + super._exposeProperty('data', NS.IETF_CARDDAV, 'address-data', true) super._exposeProperty('hasphoto', NS.NEXTCLOUD, 'has-photo', false) super._exposeProperty('favorite', NS.NEXTCLOUD, 'favorite', true) diff --git a/src/models/vobject.js b/src/models/vobject.js index c53c6db6..db9dc3e2 100644 --- a/src/models/vobject.js +++ b/src/models/vobject.js @@ -25,6 +25,12 @@ export class VObject extends DavObject { constructor(...args) { super(...args) + // Type declarations for properties installed dynamically below. + /** + * @type {string | undefined} + */ + this.data + super._exposeProperty('data', NS.IETF_CALDAV, 'calendar-data', true) } diff --git a/src/request.js b/src/request.js index 90f9d7b7..04a8cd2f 100644 --- a/src/request.js +++ b/src/request.js @@ -26,7 +26,7 @@ export default class Request { * * @param {string} baseUrl - root url of DAV server, use OC.remote('dav') * @param {{[name: string]: any}} [defaultHeaders] - additional HTTP headers to send with each request - * @param {import('./parser.js').Parser} parser - instance of Parser class + * @param {import('./parser.js').default} parser - instance of Parser class */ constructor(baseUrl, parser, defaultHeaders = {}) { this.baseUrl = baseUrl diff --git a/test/types/consumer.cts b/test/types/consumer.cts new file mode 100644 index 00000000..6884c861 --- /dev/null +++ b/test/types/consumer.cts @@ -0,0 +1,14 @@ +/** + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +import cdav = require('@nextcloud/cdav-library') + +const client = new cdav.default({ rootUrl: 'https://example.com/remote.php/dav/' }) +const connected: Promise = client.connect({ enableCardDAV: true }) +cdav.debug.enabled = false +cdav.debug('types')(cdav.namespaces.DAV, connected) +// @ts-expect-error The CommonJS module exposes the constructor as .default. +new cdav({ rootUrl: '/' }) +// @ts-expect-error Root URL must be a string. +new cdav.default({ rootUrl: 123 }) diff --git a/test/types/consumer.mts b/test/types/consumer.mts new file mode 100644 index 00000000..a816fbb6 --- /dev/null +++ b/test/types/consumer.mts @@ -0,0 +1,48 @@ +/** + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +import DavClient, { debug, namespaces } from '@nextcloud/cdav-library' + +const client = new DavClient({ rootUrl: 'https://example.com/remote.php/dav/' }) +const connected: Promise = client.connect({ enableCalDAV: true, enableCardDAV: true }) +const namespace: string = namespaces.DAV +debug.enabled = true +debug('types')(namespace, connected) + +async function exerciseModels() { + const calendars = await client.calendarHomes[0].findAllCalendars() + const calendar = calendars[0] + calendar.color = '#0082c9' + calendar.enabled = true + calendar.displayname = 'Work' + const url: string = calendar.url + await calendar.share('principal:principals/users/alice', true) + await calendar.publish() + const objects = await calendar.findByTypeInTimeRange('VEVENT', new Date(), new Date()) + objects[0].data = 'BEGIN:VCALENDAR\r\nEND:VCALENDAR' + await objects[0].update() + const books = await client.addressBookHomes[0].findAll() + const book = await client.addressBookHomes[0].createAddressBookCollection('Contacts') + await book.share('principal:principals/users/alice') + const card = await book.createVCard('BEGIN:VCARD\r\nEND:VCARD') + card.favorite = true + const data: string | undefined = card.data + const principals = await client.principalPropertySearchByAddressAndStory('Main Street', '2') + const name: string | undefined = principals[0].displayname + const userId: string | null = principals[0].userId + // @ts-expect-error A color must be a string. + calendar.color = 123 + // @ts-expect-error Dynamic read-only properties must stay read-only. + calendar.components = [] + // @ts-expect-error Mixing in sharing must not introduce an any index signature. + calendar.nonexistentMethod() + // @ts-expect-error Card favorites are booleans. + card.favorite = 'yes' + return { url, books, data, name, userId } +} +void exerciseModels +// @ts-expect-error A root URL is required. +new DavClient({}) +// @ts-expect-error Principal may be null until connected. +client.currentUserPrincipal.userId diff --git a/test/types/tsconfig.bundler.json b/test/types/tsconfig.bundler.json new file mode 100644 index 00000000..45bdb1bc --- /dev/null +++ b/test/types/tsconfig.bundler.json @@ -0,0 +1,8 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "ESNext", + "moduleResolution": "Bundler" + }, + "include": ["*.mts"] +} diff --git a/test/types/tsconfig.json b/test/types/tsconfig.json new file mode 100644 index 00000000..e4d4a9ff --- /dev/null +++ b/test/types/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "strict": true, + "noEmit": true, + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "types": [], + "skipLibCheck": false + }, + "include": ["*.mts", "*.cts"] +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 00000000..9ff5d4e5 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "allowJs": true, + "checkJs": false, + "declaration": true, + "emitDeclarationOnly": true, + "strictNullChecks": true, + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "rootDir": "src", + "outDir": "dist", + "types": [] + }, + "include": ["src/**/*.js"] +} diff --git a/vite.config.js b/vite.config.js index e06352ae..179aaf2b 100644 --- a/vite.config.js +++ b/vite.config.js @@ -3,12 +3,26 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ +import { copyFile } from 'node:fs/promises' import { createLibConfig } from '@nextcloud/vite-config' export default createLibConfig({ index: 'src/index.js', }, { libraryFormats: ['es', 'cjs'], + DTSPluginOptions: { + tsconfigPath: './tsconfig.json', + rollupTypes: true, + afterDiagnostic(diagnostics) { + if (diagnostics.length > 0) { + throw new Error('Type declaration generation failed') + } + }, + async afterBuild() { + // Both entry points expose the same exports, but require distinct module types. + await copyFile('dist/index.d.ts', 'dist/index.d.cts') + }, + }, config: { test: { setupFiles: ['test/setup.js'],