Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[stable30] fix: add script to generate core types (from shipped apps) #14581

Merged
merged 6 commits into from
Mar 7, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 89 additions & 0 deletions .github/workflows/update-nextcloud-openapi.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
# SPDX-License-Identifier: MIT

name: Update nextcloud/openapi

on:
workflow_dispatch:
schedule:
- cron: "5 4 * * 0"

jobs:
update-nextcloud-openapi:
runs-on: ubuntu-latest

strategy:
fail-fast: false
matrix:
branches: ['main', 'master', 'stable31', 'stable30']

name: Update Nextcloud OpenAPI types from core

steps:
- name: Set app env
run: |
# Split and keep last
echo "APP_NAME=${GITHUB_REPOSITORY##*/}" >> $GITHUB_ENV

- name: Checkout server
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
submodules: true
repository: nextcloud/server
ref: ${{ matrix.server-versions }}

- name: Checkout app
id: checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
path: apps/${{ env.APP_NAME }}
ref: ${{ matrix.branches }}
continue-on-error: true

- name: Read package.json node and npm engines version
if: steps.checkout.outcome == 'success'
uses: skjnldsv/read-package-engines-version-actions@06d6baf7d8f41934ab630e97d9e6c0bc9c9ac5e4 # v3
id: versions
with:
fallbackNode: '^20'
fallbackNpm: '^10'

- name: Set up node ${{ steps.versions.outputs.nodeVersion }}
if: steps.checkout.outcome == 'success'
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
with:
node-version: ${{ steps.versions.outputs.nodeVersion }}

- name: Set up npm ${{ steps.versions.outputs.npmVersion }}
if: steps.checkout.outcome == 'success'
run: npm i -g 'npm@${{ steps.versions.outputs.npmVersion }}'

- name: Install dependencies & generate types
if: steps.checkout.outcome == 'success'
working-directory: apps/${{ env.APP_NAME }}
env:
CYPRESS_INSTALL_BINARY: 0
PUPPETEER_SKIP_DOWNLOAD: true
run: |
npm ci
npm run typescript:generate-core-types --if-present

- name: Create Pull Request
if: steps.checkout.outcome == 'success'
uses: peter-evans/create-pull-request@5e914681df9dc83aa4e4905692ca88beb2f9e91f # v7.0.5
with:
path: apps/${{ env.APP_NAME }}
token: ${{ secrets.COMMAND_BOT_PAT }}
commit-message: 'chore(ts): update OpenAPI types from core'
committer: GitHub <[email protected]>
author: nextcloud-command <[email protected]>
signoff: true
branch: 'automated/noid/${{ matrix.branches }}-update-nextcloud-openapi'
title: '[${{ matrix.branches }}] Update Nextcloud OpenAPI types'
body: |
Auto-generated update of Nextcloud OpenAPI types
labels: |
dependencies
3. to review
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"serve": "webpack serve --node-env development --progress --allowed-hosts all",
"typescript:check": "tsc --noEmit",
"typescript:generate": "npx openapi-typescript -t",
"typescript:generate-core-types": "./src/types/generate-core-types.sh",
"test": "jest",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage",
Expand Down
40 changes: 40 additions & 0 deletions src/types/generate-core-types.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#!/usr/bin/env bash
#
# SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
# SPDX-License-Identifier: AGPL-3.0-only
#

# By default is expected that pwd returns <server_directory>/<apps or apps-extra>/spreed
SERVER_DIR="${1:-$(dirname $(dirname $(pwd)))}"
TYPES_DIR="$(pwd)/src/types"
CORE_TYPES_OUTPUT_DIR="$TYPES_DIR/openapi/core"
TEMP_DIR="$TYPES_DIR/tmp"

# Create the temporary directory if it doesn't exist
mkdir -p "$CORE_TYPES_OUTPUT_DIR"
mkdir -p "$TEMP_DIR"

# Find and copy openapi.json files, then translate to ts types
generate_ts_files() {
local full_path=$1
local file_name=$2
local openapi_file="$SERVER_DIR/$full_path/$file_name"
local temp_file="$TEMP_DIR/openapi_${full_path#apps/}.json"
local ts_file="$CORE_TYPES_OUTPUT_DIR/openapi_${full_path#apps/}.ts"

if [ -f "$openapi_file" ]; then
cp "$openapi_file" "$temp_file"
else
echo "Error: $openapi_file is not found"
return 1
fi

npx openapi-typescript --redocly $TYPES_DIR "$temp_file" -t -o "$ts_file"
}

generate_ts_files "core" "openapi.json"
generate_ts_files "apps/files_sharing" "openapi.json"
generate_ts_files "apps/dav" "openapi.json"
generate_ts_files "apps/provisioning_api" "openapi.json"

rm -rf "$TEMP_DIR"
47 changes: 8 additions & 39 deletions src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import type { AutocompleteResult } from './openapi/core/index.ts'
import type { components, operations } from './openapi/openapi-full.ts'
import { TASK_PROCESSING } from '../constants.js'

// General
type ApiResponse<T> = Promise<{ data: T }>
Expand Down Expand Up @@ -71,13 +71,7 @@ export type ParticipantStatus = {
clearAt?: number | null,
}
export type Participant = components['schemas']['Participant']
export type ParticipantSearchResult = {
id: string,
label: string,
icon: string,
source: string,
subline: string,
shareWithDisplayNameUnique: string,
export type ParticipantSearchResult = AutocompleteResult & {
status: ParticipantStatus | '',
}

Expand Down Expand Up @@ -196,36 +190,11 @@ export type getMentionsParams = operations['chat-mentions']['parameters']['query
export type getMentionsResponse = ApiResponse<operations['chat-mentions']['responses'][200]['content']['application/json']>

// AI Summary
export type TaskProcessingResponse = ApiResponseUnwrapped<{
task: {
id: number,
lastUpdated: number,
type: string,
status: typeof TASK_PROCESSING.STATUS[keyof typeof TASK_PROCESSING.STATUS],
userId: string,
appId: string,
input: Record<string, unknown>,
output: Record<string, unknown> | null,
customId: string,
completionExpectedAt: number,
progress: number,
scheduledAt: number,
startedAt: number,
endedAt: number
}
}>
export type {
TaskProcessingResponse,
} from './openapi/core/index.ts'

// Out of office response
// From https://docs.nextcloud.com/server/latest/developer_manual/client_apis/OCS/ocs-out-of-office-api.html
export type OutOfOfficeResponse = ApiResponseUnwrapped<{
task: {
id: string,
userId: string,
startDate: number,
endDate: number,
shortMessage: string,
message: string,
replacementUserId?: string|null,
replacementUserDisplayName?: string|null,
}
}>
export type {
OutOfOfficeResponse,
} from './openapi/core/index.ts'
106 changes: 106 additions & 0 deletions src/types/openapi/core/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import type {
components as componentsCore,
operations as operationsCore,
} from './openapi_core.ts'
import type {
components as componentsDav,
operations as operationsDav,
} from './openapi_dav.ts'
import type {
components as componentsProv,
operations as operationsProv,
} from './openapi_provisioning_api.ts'

type ApiResponse<T> = Promise<{ data: T }>

// Groupware | DAV API
export type DavPrincipal = {
calendarHomes: string[],
calendarUserType: string,
displayname: string,
email: string,
language: string,
principalScheme: string,
principalUrl: string,
scheduleDefaultCalendarUrl: string,
scheduleInbox: string,
scheduleOutbox: string,
url: string,
userId: string,
[key: string]: unknown,
}

export type DavCalendar = {
displayname: string,
color?: string,
components: string[],
allowedSharingModes: string[],
currentUserPrivilegeSet: string[],
enabled?: boolean,
order: number,
owner: string,
resourcetype: string[],
timezone?: string,
transparency: string,
url: string,
[key: string]: unknown,
isWriteable: () => boolean,
}

export type DavCalendarHome = {
displayname: string,
url: string,
findAllCalendars: () => Promise<DavCalendar[]>,
}

export type OutOfOfficeResult = componentsDav['schemas']['CurrentOutOfOfficeData']
export type OutOfOfficeResponse = ApiResponse<operationsDav['out_of_office-get-current-out-of-office-data']['responses'][200]['content']['application/json']>

// FIXME upstream: the `recurrenceId` and `calendarAppUrl` fields are not in the OpenAPI spec
export type UpcomingEvent = componentsDav['schemas']['UpcomingEvent'] & {
recurrenceId?: number | null,
calendarAppUrl?: string | null,
}
export type UpcomingEventsResponse = ApiResponse<operationsDav['upcoming_events-get-events']['responses'][200]['content']['application/json']>

// Provisioning API
export type UserPreferencesParams = Required<operationsProv['preferences-set-preference']>['requestBody']['content']['application/json']
export type UserPreferencesResponse = ApiResponse<operationsProv['preferences-set-preference']['responses'][200]['content']['application/json']>

// Task Processing API
export type TaskProcessingResponse = ApiResponse<operationsCore['task_processing_api-get-task']['responses'][200]['content']['application/json']>


// Autocomplete API
export type AutocompleteResult = componentsCore['schemas']['AutocompleteResult']
export type AutocompleteParams = operationsCore['auto_complete-get']['parameters']['query']
export type AutocompleteResponse = ApiResponse<operationsCore['auto_complete-get']['responses'][200]['content']['application/json']>

// Unified Search API
type MessageSearchResultAttributes = {
conversation: string,
messageId: string,
actorType: string,
actorId: string,
timestamp: string,
}
export type SearchMessagePayload = operationsCore['unified_search-search']['parameters']['query'] & {
person?: string,
since?: string | null,
until?: string | null,
}

// FIXME upstream: the `attributes` field allows only string[] from OpenAPI spec
export type UnifiedSearchResultEntry = componentsCore['schemas']['UnifiedSearchResultEntry'] & {
attributes: MessageSearchResultAttributes,
}
export type UnifiedSearchResponse = ApiResponse<operationsCore['unified_search-search']['responses'][200]['content']['application/json'] & {
ocs: {
meta: componentsCore["schemas"]["OCSMeta"],
data: componentsCore["schemas"]["UnifiedSearchResult"] & {
entries: (componentsCore["schemas"]["UnifiedSearchResultEntry"] & {
attributes: MessageSearchResultAttributes
})[],
},
}
}>
Loading
Loading