From df6a88b4d56a4ea3a47f5c6b1b4cc0ed668a2c9e Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Tue, 11 Mar 2025 17:14:47 +0100 Subject: [PATCH] V15: Enable umbraco-extension client to auto-build (#18597) * feat: adds autobuild of umbraco extensions created by the dotnet template * adds file extensions to imports to follow the esmodule convention + apply formatting * build(deps): upgrade hey-api, vite, typescript and generate new api * chore: formatting * clean up actions as client is now automatically being built when starting the site * revert change with UMBRACO_VERSION_FROM_TEMPLATE * revert if(includeExample) * use template name * use template name * feat: update the way it sets the auth token * add back in IncludeExample if * fix: rename allowPackageTelemetry to allowTelemetry because that is what it is actually called * `.csproj` amends - Adds `BeforeTargets` to "BuildClient" target - Adds "package.json" to "RestoreClient" target input - Removes extra parameters from `npm i` command --------- Co-authored-by: leekelleher --- .../.template.config/template.json | 21 +- .../UmbracoExtension/Client/package.json | 12 +- .../Client/public/umbraco-package.json | 4 +- .../Client/scripts/generate-openapi.js | 22 +- .../Client/src/api/client.gen.ts | 18 + .../UmbracoExtension/Client/src/api/index.ts | 5 +- .../Client/src/api/schemas.gen.ts | 391 ------------------ .../Client/src/api/sdk.gen.ts | 78 ++++ .../Client/src/api/services.gen.ts | 41 -- .../Client/src/api/types.gen.ts | 183 +++++--- .../Client/src/bundle.manifests.ts | 4 +- .../src/dashboards/dashboard.element.ts | 170 +++++--- .../Client/src/dashboards/manifest.ts | 14 +- .../Client/src/entrypoints/entrypoint.ts | 27 +- .../Client/src/entrypoints/manifest.ts | 4 +- .../UmbracoExtension/Client/vite.config.ts | 2 +- .../UmbracoExtension/Umbraco.Extension.csproj | 25 +- 17 files changed, 410 insertions(+), 611 deletions(-) create mode 100644 templates/UmbracoExtension/Client/src/api/client.gen.ts delete mode 100644 templates/UmbracoExtension/Client/src/api/schemas.gen.ts create mode 100644 templates/UmbracoExtension/Client/src/api/sdk.gen.ts delete mode 100644 templates/UmbracoExtension/Client/src/api/services.gen.ts diff --git a/templates/UmbracoExtension/.template.config/template.json b/templates/UmbracoExtension/.template.config/template.json index 5ada4ae33f..b915adc90f 100644 --- a/templates/UmbracoExtension/.template.config/template.json +++ b/templates/UmbracoExtension/.template.config/template.json @@ -136,22 +136,6 @@ ], "actionId": "210D431B-A78B-4D2F-B762-4ED3E3EA9025", "continueOnError": true - }, - { - "actionId": "3A7C4B45-1F5D-4A30-959A-51B88E82B5D2", - "args": { - "executable": "powershell", - "args": "cd Client;npm install;npm run build;", - "redirectStandardError": false, - "redirectStandardOutput": false - }, - "manualInstructions": [ - { - "text": "From the 'Client' folder run 'npm install' and then 'npm run build'" - } - ], - "continueOnError": true, - "description ": "Installs node modules" } ], "sources": [ @@ -160,11 +144,10 @@ { "condition": "(!IncludeExample)", "exclude": [ - "[Cc]lient/src/dashboards/**", - "[Cc]lient/src/api/schemas.gen.ts" + "[Cc]lient/src/dashboards/**" ] } ] } ] -} +} \ No newline at end of file diff --git a/templates/UmbracoExtension/Client/package.json b/templates/UmbracoExtension/Client/package.json index c4b1ae9b6b..f62550adfe 100644 --- a/templates/UmbracoExtension/Client/package.json +++ b/templates/UmbracoExtension/Client/package.json @@ -9,13 +9,13 @@ "generate-client": "node scripts/generate-openapi.js https://localhost:44339/umbraco/swagger/umbracoextension/swagger.json" }, "devDependencies": { - "@hey-api/client-fetch": "^0.4.2", - "@hey-api/openapi-ts": "^0.53.11", + "@hey-api/client-fetch": "^0.8.3", + "@hey-api/openapi-ts": "^0.64.10", "@umbraco-cms/backoffice": "^UMBRACO_VERSION_FROM_TEMPLATE", - "chalk": "^5.3.0", + "chalk": "^5.4.1", "cross-env": "^7.0.3", "node-fetch": "^3.3.2", - "typescript": "^5.6.3", - "vite": "^5.4.9" + "typescript": "^5.8.2", + "vite": "^6.2.0" } -} +} \ No newline at end of file diff --git a/templates/UmbracoExtension/Client/public/umbraco-package.json b/templates/UmbracoExtension/Client/public/umbraco-package.json index 2ca2f83317..ca3d52d518 100644 --- a/templates/UmbracoExtension/Client/public/umbraco-package.json +++ b/templates/UmbracoExtension/Client/public/umbraco-package.json @@ -2,7 +2,7 @@ "id": "Umbraco.Extension", "name": "Umbraco.Extension", "version": "0.0.0", - "allowPackageTelemetry": true, + "allowTelemetry": true, "extensions": [ { "name": "Umbraco ExtensionBundle", @@ -11,4 +11,4 @@ "js": "/App_Plugins/UmbracoExtension/umbraco-extension.js" } ] -} +} \ No newline at end of file diff --git a/templates/UmbracoExtension/Client/scripts/generate-openapi.js b/templates/UmbracoExtension/Client/scripts/generate-openapi.js index 93bb481289..b320781bc7 100644 --- a/templates/UmbracoExtension/Client/scripts/generate-openapi.js +++ b/templates/UmbracoExtension/Client/scripts/generate-openapi.js @@ -1,6 +1,6 @@ import fetch from 'node-fetch'; import chalk from 'chalk'; -import { createClient } from '@hey-api/openapi-ts'; +import { createClient, defaultPlugins } from '@hey-api/openapi-ts'; // Start notifying user we are generating the TypeScript client console.log(chalk.green("Generating OpenAPI client...")); @@ -20,7 +20,7 @@ process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; console.log("Ensure your Umbraco instance is running"); console.log(`Fetching OpenAPI definition from ${chalk.yellow(swaggerUrl)}`); -fetch(swaggerUrl).then(response => { +fetch(swaggerUrl).then(async (response) => { if (!response.ok) { console.error(chalk.red(`ERROR: OpenAPI spec returned with a non OK (200) response: ${response.status} ${response.statusText}`)); console.error(`The URL to your Umbraco instance may be wrong or the instance is not running`); @@ -31,13 +31,21 @@ fetch(swaggerUrl).then(response => { console.log(`OpenAPI spec fetched successfully`); console.log(`Calling ${chalk.yellow('hey-api')} to generate TypeScript client`); - createClient({ - client: '@hey-api/client-fetch', + await createClient({ input: swaggerUrl, output: 'src/api', - services: { - asClass: true, - } + plugins: [ + ...defaultPlugins, + '@hey-api/client-fetch', + { + name: '@hey-api/typescript', + enums: 'typescript' + }, + { + name: '@hey-api/sdk', + asClass: true + } + ], }); }) diff --git a/templates/UmbracoExtension/Client/src/api/client.gen.ts b/templates/UmbracoExtension/Client/src/api/client.gen.ts new file mode 100644 index 0000000000..d7bb5e8272 --- /dev/null +++ b/templates/UmbracoExtension/Client/src/api/client.gen.ts @@ -0,0 +1,18 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { ClientOptions } from './types.gen'; +import { type Config, type ClientOptions as DefaultClientOptions, createClient, createConfig } from '@hey-api/client-fetch'; + +/** + * The `createClientConfig()` function will be called on client initialization + * and the returned object will become the client's initial configuration. + * + * You may want to initialize your client this way instead of calling + * `setConfig()`. This is useful for example if you're using Next.js + * to ensure your client always has the correct values. + */ +export type CreateClientConfig = (override?: Config) => Config & T>; + +export const client = createClient(createConfig({ + baseUrl: 'https://localhost:44389' +})); \ No newline at end of file diff --git a/templates/UmbracoExtension/Client/src/api/index.ts b/templates/UmbracoExtension/Client/src/api/index.ts index 7661d65371..e64537d212 100644 --- a/templates/UmbracoExtension/Client/src/api/index.ts +++ b/templates/UmbracoExtension/Client/src/api/index.ts @@ -1,6 +1,3 @@ // This file is auto-generated by @hey-api/openapi-ts -//#if(IncludeExample) -export * from './schemas.gen'; -//#endif -export * from './services.gen'; export * from './types.gen'; +export * from './sdk.gen'; \ No newline at end of file diff --git a/templates/UmbracoExtension/Client/src/api/schemas.gen.ts b/templates/UmbracoExtension/Client/src/api/schemas.gen.ts deleted file mode 100644 index 1a1885f5d5..0000000000 --- a/templates/UmbracoExtension/Client/src/api/schemas.gen.ts +++ /dev/null @@ -1,391 +0,0 @@ -// This file is auto-generated by @hey-api/openapi-ts - -export const DocumentGranularPermissionModelSchema = { - required: ['context', 'key', 'permission'], - type: 'object', - properties: { - key: { - type: 'string', - format: 'uuid' - }, - context: { - type: 'string', - readOnly: true - }, - permission: { - type: 'string' - } - }, - additionalProperties: false -} as const; - -export const ReadOnlyUserGroupModelSchema = { - required: ['alias', 'allowedLanguages', 'allowedSections', 'granularPermissions', 'hasAccessToAllLanguages', 'id', 'key', 'name', 'permissions'], - type: 'object', - properties: { - id: { - type: 'integer', - format: 'int32' - }, - key: { - type: 'string', - format: 'uuid' - }, - name: { - type: 'string' - }, - icon: { - type: 'string', - nullable: true - }, - startContentId: { - type: 'integer', - format: 'int32', - nullable: true - }, - startMediaId: { - type: 'integer', - format: 'int32', - nullable: true - }, - alias: { - type: 'string' - }, - hasAccessToAllLanguages: { - type: 'boolean' - }, - allowedLanguages: { - type: 'array', - items: { - type: 'integer', - format: 'int32' - } - }, - permissions: { - uniqueItems: true, - type: 'array', - items: { - type: 'string' - } - }, - granularPermissions: { - uniqueItems: true, - type: 'array', - items: { - oneOf: [ - { - '$ref': '#/components/schemas/DocumentGranularPermissionModel' - }, - { - '$ref': '#/components/schemas/UnknownTypeGranularPermissionModel' - } - ] - } - }, - allowedSections: { - type: 'array', - items: { - type: 'string' - } - } - }, - additionalProperties: false -} as const; - -export const UnknownTypeGranularPermissionModelSchema = { - required: ['context', 'permission'], - type: 'object', - properties: { - context: { - type: 'string' - }, - permission: { - type: 'string' - } - }, - additionalProperties: false -} as const; - -export const UserGroupModelSchema = { - required: ['alias', 'allowedLanguages', 'allowedSections', 'createDate', 'granularPermissions', 'hasAccessToAllLanguages', 'hasIdentity', 'id', 'key', 'permissions', 'updateDate', 'userCount'], - type: 'object', - properties: { - id: { - type: 'integer', - format: 'int32' - }, - key: { - type: 'string', - format: 'uuid' - }, - createDate: { - type: 'string', - format: 'date-time' - }, - updateDate: { - type: 'string', - format: 'date-time' - }, - deleteDate: { - type: 'string', - format: 'date-time', - nullable: true - }, - hasIdentity: { - type: 'boolean', - readOnly: true - }, - startMediaId: { - type: 'integer', - format: 'int32', - nullable: true - }, - startContentId: { - type: 'integer', - format: 'int32', - nullable: true - }, - icon: { - type: 'string', - nullable: true - }, - alias: { - type: 'string' - }, - name: { - type: 'string', - nullable: true - }, - hasAccessToAllLanguages: { - type: 'boolean' - }, - permissions: { - uniqueItems: true, - type: 'array', - items: { - type: 'string' - } - }, - granularPermissions: { - uniqueItems: true, - type: 'array', - items: { - oneOf: [ - { - '$ref': '#/components/schemas/DocumentGranularPermissionModel' - }, - { - '$ref': '#/components/schemas/UnknownTypeGranularPermissionModel' - } - ] - } - }, - allowedSections: { - type: 'array', - items: { - type: 'string' - }, - readOnly: true - }, - userCount: { - type: 'integer', - format: 'int32', - readOnly: true - }, - allowedLanguages: { - type: 'array', - items: { - type: 'integer', - format: 'int32' - }, - readOnly: true - } - }, - additionalProperties: false -} as const; - -export const UserKindModelSchema = { - enum: ['Default', 'Api'], - type: 'string' -} as const; - -export const UserModelSchema = { - required: ['allowedSections', 'createDate', 'email', 'failedPasswordAttempts', 'groups', 'hasIdentity', 'id', 'isApproved', 'isLockedOut', 'key', 'kind', 'profileData', 'sessionTimeout', 'updateDate', 'username', 'userState'], - type: 'object', - properties: { - id: { - type: 'integer', - format: 'int32' - }, - key: { - type: 'string', - format: 'uuid' - }, - createDate: { - type: 'string', - format: 'date-time' - }, - updateDate: { - type: 'string', - format: 'date-time' - }, - deleteDate: { - type: 'string', - format: 'date-time', - nullable: true - }, - hasIdentity: { - type: 'boolean', - readOnly: true - }, - emailConfirmedDate: { - type: 'string', - format: 'date-time', - nullable: true - }, - invitedDate: { - type: 'string', - format: 'date-time', - nullable: true - }, - username: { - type: 'string' - }, - email: { - type: 'string' - }, - rawPasswordValue: { - type: 'string', - nullable: true - }, - passwordConfiguration: { - type: 'string', - nullable: true - }, - isApproved: { - type: 'boolean' - }, - isLockedOut: { - type: 'boolean' - }, - lastLoginDate: { - type: 'string', - format: 'date-time', - nullable: true - }, - lastPasswordChangeDate: { - type: 'string', - format: 'date-time', - nullable: true - }, - lastLockoutDate: { - type: 'string', - format: 'date-time', - nullable: true - }, - failedPasswordAttempts: { - type: 'integer', - format: 'int32' - }, - comments: { - type: 'string', - nullable: true - }, - userState: { - '$ref': '#/components/schemas/UserStateModel' - }, - name: { - type: 'string', - nullable: true - }, - allowedSections: { - type: 'array', - items: { - type: 'string' - }, - readOnly: true - }, - profileData: { - oneOf: [ - { - '$ref': '#/components/schemas/UserModel' - }, - { - '$ref': '#/components/schemas/UserProfileModel' - } - ], - readOnly: true - }, - securityStamp: { - type: 'string', - nullable: true - }, - avatar: { - type: 'string', - nullable: true - }, - sessionTimeout: { - type: 'integer', - format: 'int32' - }, - startContentIds: { - type: 'array', - items: { - type: 'integer', - format: 'int32' - }, - nullable: true - }, - startMediaIds: { - type: 'array', - items: { - type: 'integer', - format: 'int32' - }, - nullable: true - }, - language: { - type: 'string', - nullable: true - }, - kind: { - '$ref': '#/components/schemas/UserKindModel' - }, - groups: { - type: 'array', - items: { - oneOf: [ - { - '$ref': '#/components/schemas/ReadOnlyUserGroupModel' - }, - { - '$ref': '#/components/schemas/UserGroupModel' - } - ] - }, - readOnly: true - } - }, - additionalProperties: false -} as const; - -export const UserProfileModelSchema = { - required: ['id'], - type: 'object', - properties: { - id: { - type: 'integer', - format: 'int32' - }, - name: { - type: 'string', - nullable: true - } - }, - additionalProperties: false -} as const; - -export const UserStateModelSchema = { - enum: ['Active', 'Disabled', 'LockedOut', 'Invited', 'Inactive', 'All'], - type: 'string' -} as const; \ No newline at end of file diff --git a/templates/UmbracoExtension/Client/src/api/sdk.gen.ts b/templates/UmbracoExtension/Client/src/api/sdk.gen.ts new file mode 100644 index 0000000000..0465ad97b5 --- /dev/null +++ b/templates/UmbracoExtension/Client/src/api/sdk.gen.ts @@ -0,0 +1,78 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { Options as ClientOptions, TDataShape, Client } from '@hey-api/client-fetch'; +//#if(IncludeExample) +import type { PingData, PingResponse, WhatsMyNameData, WhatsMyNameResponse, WhatsTheTimeMrWolfData, WhatsTheTimeMrWolfResponse, WhoAmIData, WhoAmIResponse } from './types.gen'; +//#else +import type { PingData, PingResponse } from './types.gen'; +//#endif +import { client as _heyApiClient } from './client.gen'; + +export type Options = ClientOptions & { + /** + * You can provide a client instance returned by `createClient()` instead of + * individual options. This might be also useful if you want to implement a + * custom client. + */ + client?: Client; + /** + * You can pass arbitrary values through the `meta` object. This can be + * used to access values that aren't defined as part of the SDK function. + */ + meta?: Record; +}; + +export class UmbracoExtensionService { + public static ping(options?: Options) { + return (options?.client ?? _heyApiClient).get({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/umbraco/umbracoextension/api/v1/ping', + ...options + }); + } +//#if(IncludeExample) + public static whatsMyName(options?: Options) { + return (options?.client ?? _heyApiClient).get({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/umbraco/umbracoextension/api/v1/whatsMyName', + ...options + }); + } + + public static whatsTheTimeMrWolf(options?: Options) { + return (options?.client ?? _heyApiClient).get({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/umbraco/umbracoextension/api/v1/whatsTheTimeMrWolf', + ...options + }); + } + + public static whoAmI(options?: Options) { + return (options?.client ?? _heyApiClient).get({ + security: [ + { + scheme: 'bearer', + type: 'http' + } + ], + url: '/umbraco/umbracoextension/api/v1/whoAmI', + ...options + }); + } +//#endif +} \ No newline at end of file diff --git a/templates/UmbracoExtension/Client/src/api/services.gen.ts b/templates/UmbracoExtension/Client/src/api/services.gen.ts deleted file mode 100644 index c9d1c0b91b..0000000000 --- a/templates/UmbracoExtension/Client/src/api/services.gen.ts +++ /dev/null @@ -1,41 +0,0 @@ -// This file is auto-generated by @hey-api/openapi-ts - -import { createClient, createConfig, type Options } from '@hey-api/client-fetch'; -//#if(IncludeExample) -import type { PingError, PingResponse, WhatsMyNameError, WhatsMyNameResponse, WhatsTheTimeMrWolfError, WhatsTheTimeMrWolfResponse, WhoAmIError, WhoAmIResponse } from './types.gen'; -//#else -import type { PingError, PingResponse } from './types.gen'; -//#endif - -export const client = createClient(createConfig()); - -export class UmbracoExtensionService { - public static ping(options?: Options) { - return (options?.client ?? client).get({ - ...options, - url: '/umbraco/umbracoextension/api/v1/ping' - }); - } -//#if(IncludeExample) - public static whatsMyName(options?: Options) { - return (options?.client ?? client).get({ - ...options, - url: '/umbraco/umbracoextension/api/v1/whatsMyName' - }); - } - - public static whatsTheTimeMrWolf(options?: Options) { - return (options?.client ?? client).get({ - ...options, - url: '/umbraco/umbracoextension/api/v1/whatsTheTimeMrWolf' - }); - } - - public static whoAmI(options?: Options) { - return (options?.client ?? client).get({ - ...options, - url: '/umbraco/umbracoextension/api/v1/whoAmI' - }); - } -//#endif -} diff --git a/templates/UmbracoExtension/Client/src/api/types.gen.ts b/templates/UmbracoExtension/Client/src/api/types.gen.ts index e666792803..9da3ce6897 100644 --- a/templates/UmbracoExtension/Client/src/api/types.gen.ts +++ b/templates/UmbracoExtension/Client/src/api/types.gen.ts @@ -1,5 +1,5 @@ // This file is auto-generated by @hey-api/openapi-ts -//#if(IncludeExample) +//#if(IncludeExample) export type DocumentGranularPermissionModel = { key: string; readonly context: string; @@ -10,15 +10,15 @@ export type ReadOnlyUserGroupModel = { id: number; key: string; name: string; - icon?: (string) | null; - startContentId?: (number) | null; - startMediaId?: (number) | null; + icon?: string | null; + startContentId?: number | null; + startMediaId?: number | null; alias: string; hasAccessToAllLanguages: boolean; - allowedLanguages: Array<(number)>; - permissions: Array<(string)>; - granularPermissions: Array<(DocumentGranularPermissionModel | UnknownTypeGranularPermissionModel)>; - allowedSections: Array<(string)>; + allowedLanguages: Array; + permissions: Array; + granularPermissions: Array; + allowedSections: Array; }; export type UnknownTypeGranularPermissionModel = { @@ -31,77 +31,166 @@ export type UserGroupModel = { key: string; createDate: string; updateDate: string; - deleteDate?: (string) | null; + deleteDate?: string | null; readonly hasIdentity: boolean; - startMediaId?: (number) | null; - startContentId?: (number) | null; - icon?: (string) | null; + startMediaId?: number | null; + startContentId?: number | null; + icon?: string | null; alias: string; - name?: (string) | null; + name?: string | null; hasAccessToAllLanguages: boolean; - permissions: Array<(string)>; - granularPermissions: Array<(DocumentGranularPermissionModel | UnknownTypeGranularPermissionModel)>; - readonly allowedSections: Array<(string)>; + permissions: Array; + granularPermissions: Array; + readonly allowedSections: Array; readonly userCount: number; - readonly allowedLanguages: Array<(number)>; + readonly allowedLanguages: Array; }; -export type UserKindModel = 'Default' | 'Api'; +export enum UserKindModel { + DEFAULT = 'Default', + API = 'Api' +} export type UserModel = { id: number; key: string; createDate: string; updateDate: string; - deleteDate?: (string) | null; + deleteDate?: string | null; readonly hasIdentity: boolean; - emailConfirmedDate?: (string) | null; - invitedDate?: (string) | null; + emailConfirmedDate?: string | null; + invitedDate?: string | null; username: string; email: string; - rawPasswordValue?: (string) | null; - passwordConfiguration?: (string) | null; + rawPasswordValue?: string | null; + passwordConfiguration?: string | null; isApproved: boolean; isLockedOut: boolean; - lastLoginDate?: (string) | null; - lastPasswordChangeDate?: (string) | null; - lastLockoutDate?: (string) | null; + lastLoginDate?: string | null; + lastPasswordChangeDate?: string | null; + lastLockoutDate?: string | null; failedPasswordAttempts: number; - comments?: (string) | null; + comments?: string | null; userState: UserStateModel; - name?: (string) | null; - readonly allowedSections: Array<(string)>; - readonly profileData: (UserModel | UserProfileModel); - securityStamp?: (string) | null; - avatar?: (string) | null; + name?: string | null; + readonly allowedSections: Array; + profileData: UserModel | UserProfileModel; + securityStamp?: string | null; + avatar?: string | null; sessionTimeout: number; - startContentIds?: Array<(number)> | null; - startMediaIds?: Array<(number)> | null; - language?: (string) | null; + startContentIds?: Array | null; + startMediaIds?: Array | null; + language?: string | null; kind: UserKindModel; - readonly groups: Array<(ReadOnlyUserGroupModel | UserGroupModel)>; + readonly groups: Array; }; export type UserProfileModel = { id: number; - name?: (string) | null; + name?: string | null; }; -export type UserStateModel = 'Active' | 'Disabled' | 'LockedOut' | 'Invited' | 'Inactive' | 'All'; +export enum UserStateModel { + ACTIVE = 'Active', + DISABLED = 'Disabled', + LOCKED_OUT = 'LockedOut', + INVITED = 'Invited', + INACTIVE = 'Inactive', + ALL = 'All' +} //#endif -export type PingResponse = (string); +export type PingData = { + body?: never; + path?: never; + query?: never; + url: '/umbraco/hackclient/api/v1/ping'; +}; -export type PingError = (unknown); -//#if(IncludeExample) -export type WhatsMyNameResponse = (string); +export type PingErrors = { + /** + * The resource is protected and requires an authentication token + */ + 401: unknown; +}; -export type WhatsMyNameError = (unknown); +export type PingResponses = { + /** + * OK + */ + 200: string; +}; -export type WhatsTheTimeMrWolfResponse = (string); +export type PingResponse = PingResponses[keyof PingResponses]; +//#if(IncludeExample) +export type WhatsMyNameData = { + body?: never; + path?: never; + query?: never; + url: '/umbraco/hackclient/api/v1/whatsMyName'; +}; -export type WhatsTheTimeMrWolfError = (unknown); +export type WhatsMyNameErrors = { + /** + * The resource is protected and requires an authentication token + */ + 401: unknown; +}; -export type WhoAmIResponse = ((UserModel)); +export type WhatsMyNameResponses = { + /** + * OK + */ + 200: string; +}; -export type WhoAmIError = (unknown); +export type WhatsMyNameResponse = WhatsMyNameResponses[keyof WhatsMyNameResponses]; + +export type WhatsTheTimeMrWolfData = { + body?: never; + path?: never; + query?: never; + url: '/umbraco/hackclient/api/v1/whatsTheTimeMrWolf'; +}; + +export type WhatsTheTimeMrWolfErrors = { + /** + * The resource is protected and requires an authentication token + */ + 401: unknown; +}; + +export type WhatsTheTimeMrWolfResponses = { + /** + * OK + */ + 200: string; +}; + +export type WhatsTheTimeMrWolfResponse = WhatsTheTimeMrWolfResponses[keyof WhatsTheTimeMrWolfResponses]; + +export type WhoAmIData = { + body?: never; + path?: never; + query?: never; + url: '/umbraco/hackclient/api/v1/whoAmI'; +}; + +export type WhoAmIErrors = { + /** + * The resource is protected and requires an authentication token + */ + 401: unknown; +}; + +export type WhoAmIResponses = { + /** + * OK + */ + 200: UserModel; +}; + +export type WhoAmIResponse = WhoAmIResponses[keyof WhoAmIResponses]; //#endif +export type ClientOptions = { + baseUrl: 'https://localhost:44389' | (string & {}); +}; diff --git a/templates/UmbracoExtension/Client/src/bundle.manifests.ts b/templates/UmbracoExtension/Client/src/bundle.manifests.ts index fb2d2bfa09..6186b361f3 100644 --- a/templates/UmbracoExtension/Client/src/bundle.manifests.ts +++ b/templates/UmbracoExtension/Client/src/bundle.manifests.ts @@ -1,6 +1,6 @@ -import { manifests as entrypoints } from './entrypoints/manifest'; +import { manifests as entrypoints } from "./entrypoints/manifest.js"; //#if IncludeExample -import { manifests as dashboards } from './dashboards/manifest'; +import { manifests as dashboards } from "./dashboards/manifest.js"; //#endif // Job of the bundle is to collate all the manifests from different parts of the extension and load other manifests diff --git a/templates/UmbracoExtension/Client/src/dashboards/dashboard.element.ts b/templates/UmbracoExtension/Client/src/dashboards/dashboard.element.ts index 287464086e..7a2d0756ec 100644 --- a/templates/UmbracoExtension/Client/src/dashboards/dashboard.element.ts +++ b/templates/UmbracoExtension/Client/src/dashboards/dashboard.element.ts @@ -1,13 +1,24 @@ -import { LitElement, css, html, customElement, state } from "@umbraco-cms/backoffice/external/lit"; +import { + LitElement, + css, + html, + customElement, + state, +} from "@umbraco-cms/backoffice/external/lit"; import { UmbElementMixin } from "@umbraco-cms/backoffice/element-api"; -import { UmbracoExtensionService, UserModel } from "../api"; import { UUIButtonElement } from "@umbraco-cms/backoffice/external/uui"; -import { UMB_NOTIFICATION_CONTEXT, UmbNotificationContext } from "@umbraco-cms/backoffice/notification"; -import { UMB_CURRENT_USER_CONTEXT, UmbCurrentUserModel } from "@umbraco-cms/backoffice/current-user"; +import { + UMB_NOTIFICATION_CONTEXT, + UmbNotificationContext, +} from "@umbraco-cms/backoffice/notification"; +import { + UMB_CURRENT_USER_CONTEXT, + UmbCurrentUserModel, +} from "@umbraco-cms/backoffice/current-user"; +import { UmbracoExtensionService, UserModel } from "../api/index.js"; -@customElement('example-dashboard') +@customElement("example-dashboard") export class ExampleDashboardElement extends UmbElementMixin(LitElement) { - @state() private _yourName: string | undefined = "Press the button!"; @@ -28,7 +39,6 @@ export class ExampleDashboardElement extends UmbElementMixin(LitElement) { }); this.consumeContext(UMB_CURRENT_USER_CONTEXT, (currentUserContext) => { - // When we have the current user context // We can observe properties from it, such as the current user or perhaps just individual properties // When the currentUser object changes we will get notified and can reset the @state properrty @@ -62,10 +72,10 @@ export class ExampleDashboardElement extends UmbElementMixin(LitElement) { data: { headline: `You are ${this._serverUserData?.name}`, message: `Your email is ${this._serverUserData?.email}`, - } - }) + }, + }); } - } + }; #onClickWhatsTheTimeMrWolf = async (ev: Event) => { const buttonElement = ev.target as UUIButtonElement; @@ -84,7 +94,7 @@ export class ExampleDashboardElement extends UmbElementMixin(LitElement) { this._timeFromMrWolf = new Date(data); buttonElement.state = "success"; } - } + }; #onClickWhatsMyName = async (ev: Event) => { const buttonElement = ev.target as UUIButtonElement; @@ -100,77 +110,111 @@ export class ExampleDashboardElement extends UmbElementMixin(LitElement) { this._yourName = data; buttonElement.state = "success"; - } + }; render() { return html` - -
[Server]
-

${this._serverUserData?.email ? this._serverUserData.email : 'Press the button!'}

-
    - ${this._serverUserData?.groups.map(group => html`
  • ${group.name}
  • `)} -
- - Who am I? - -

This endpoint gets your current user from the server and displays your email and list of user groups. - It also displays a Notification with your details.

-
+ +
[Server]
+

+ ${this._serverUserData?.email + ? this._serverUserData.email + : "Press the button!"} +

+
    + ${this._serverUserData?.groups.map( + (group) => html`
  • ${group.name}
  • ` + )} +
+ + Who am I? + +

+ This endpoint gets your current user from the server and displays your + email and list of user groups. It also displays a Notification with + your details. +

+
- -
[Server]
-

${this._yourName }

- - Whats my name? - -

This endpoint has a forced delay to show the button 'waiting' state for a few seconds before completing the request.

-
+ +
[Server]
+

${this._yourName}

+ + Whats my name? + +

+ This endpoint has a forced delay to show the button 'waiting' state + for a few seconds before completing the request. +

+
- -
[Server]
-

${this._timeFromMrWolf ? this._timeFromMrWolf.toLocaleString() : 'Press the button!'}

- - Whats the time Mr Wolf? - -

This endpoint gets the current date and time from the server.

-
+ +
[Server]
+

+ ${this._timeFromMrWolf + ? this._timeFromMrWolf.toLocaleString() + : "Press the button!"} +

+ + Whats the time Mr Wolf? + +

This endpoint gets the current date and time from the server.

+
- -
[Context]
-

Current user email: ${this._contextCurrentUser?.email}

-

This is the JSON object available by consuming the 'UMB_CURRENT_USER_CONTEXT' context:

- ${JSON.stringify(this._contextCurrentUser, null, 2)} -
+ +
[Context]
+

Current user email: ${this._contextCurrentUser?.email}

+

+ This is the JSON object available by consuming the + 'UMB_CURRENT_USER_CONTEXT' context: +

+ ${JSON.stringify(this._contextCurrentUser, null, 2)} +
`; } static styles = [ css` - :host { - display: grid; - gap: var(--uui-size-layout-1); - padding: var(--uui-size-layout-1); - grid-template-columns: 1fr 1fr 1fr; - } + :host { + display: grid; + gap: var(--uui-size-layout-1); + padding: var(--uui-size-layout-1); + grid-template-columns: 1fr 1fr 1fr; + } - uui-box { - margin-bottom: var(--uui-size-layout-1); - } + uui-box { + margin-bottom: var(--uui-size-layout-1); + } - h2 { - margin-top:0; - } + h2 { + margin-top: 0; + } - .wide { - grid-column: span 3; - } - `]; + .wide { + grid-column: span 3; + } + `, + ]; } export default ExampleDashboardElement; declare global { interface HTMLElementTagNameMap { - 'example-dashboard': ExampleDashboardElement; + "example-dashboard": ExampleDashboardElement; } } diff --git a/templates/UmbracoExtension/Client/src/dashboards/manifest.ts b/templates/UmbracoExtension/Client/src/dashboards/manifest.ts index a020e3ab2c..1fa7c360b8 100644 --- a/templates/UmbracoExtension/Client/src/dashboards/manifest.ts +++ b/templates/UmbracoExtension/Client/src/dashboards/manifest.ts @@ -2,17 +2,17 @@ export const manifests: Array = [ { name: "Umbraco ExtensionDashboard", alias: "Umbraco.Extension.Dashboard", - type: 'dashboard', - js: () => import("./dashboard.element"), + type: "dashboard", + js: () => import("./dashboard.element.js"), meta: { label: "Example Dashboard", - pathname: "example-dashboard" + pathname: "example-dashboard", }, conditions: [ { - alias: 'Umb.Condition.SectionAlias', - match: 'Umb.Section.Content', - } + alias: "Umb.Condition.SectionAlias", + match: "Umb.Section.Content", + }, ], - } + }, ]; diff --git a/templates/UmbracoExtension/Client/src/entrypoints/entrypoint.ts b/templates/UmbracoExtension/Client/src/entrypoints/entrypoint.ts index 65796c3202..d6115312f4 100644 --- a/templates/UmbracoExtension/Client/src/entrypoints/entrypoint.ts +++ b/templates/UmbracoExtension/Client/src/entrypoints/entrypoint.ts @@ -1,38 +1,31 @@ -import { UmbEntryPointOnInit, UmbEntryPointOnUnload } from '@umbraco-cms/backoffice/extension-api'; +import { + UmbEntryPointOnInit, + UmbEntryPointOnUnload, +} from "@umbraco-cms/backoffice/extension-api"; //#if IncludeExample -import { UMB_AUTH_CONTEXT } from '@umbraco-cms/backoffice/auth'; -import { client } from '../api'; +import { UMB_AUTH_CONTEXT } from "@umbraco-cms/backoffice/auth"; +import { client } from "../api/client.gen.js"; //#endif // load up the manifests here export const onInit: UmbEntryPointOnInit = (_host, _extensionRegistry) => { - - console.log('Hello from my extension 🎉'); + console.log("Hello from my extension 🎉"); //#if IncludeExample // Will use only to add in Open API config with generated TS OpenAPI HTTPS Client // Do the OAuth token handshake stuff _host.consumeContext(UMB_AUTH_CONTEXT, async (authContext) => { - // Get the token info from Umbraco const config = authContext.getOpenApiConfiguration(); client.setConfig({ + auth: config.token, baseUrl: config.base, - credentials: config.credentials - }); - - // For every request being made, add the token to the headers - // Can't use the setConfig approach above as its set only once and - // tokens expire and get refreshed - client.interceptors.request.use(async (request, _options) => { - const token = await config.token(); - request.headers.set('Authorization', `Bearer ${token}`); - return request; + credentials: config.credentials, }); }); //#endif }; export const onUnload: UmbEntryPointOnUnload = (_host, _extensionRegistry) => { - console.log('Goodbye from my extension 👋'); + console.log("Goodbye from my extension 👋"); }; diff --git a/templates/UmbracoExtension/Client/src/entrypoints/manifest.ts b/templates/UmbracoExtension/Client/src/entrypoints/manifest.ts index 5dcda9de89..cd97f1e6a7 100644 --- a/templates/UmbracoExtension/Client/src/entrypoints/manifest.ts +++ b/templates/UmbracoExtension/Client/src/entrypoints/manifest.ts @@ -3,6 +3,6 @@ export const manifests: Array = [ name: "Umbraco ExtensionEntrypoint", alias: "Umbraco.Extension.Entrypoint", type: "backofficeEntryPoint", - js: () => import("./entrypoint"), - } + js: () => import("./entrypoint.js"), + }, ]; diff --git a/templates/UmbracoExtension/Client/vite.config.ts b/templates/UmbracoExtension/Client/vite.config.ts index 5232076b8c..d2db7d4ffd 100644 --- a/templates/UmbracoExtension/Client/vite.config.ts +++ b/templates/UmbracoExtension/Client/vite.config.ts @@ -13,5 +13,5 @@ export default defineConfig({ rollupOptions: { external: [/^@umbraco/], }, - } + }, }); diff --git a/templates/UmbracoExtension/Umbraco.Extension.csproj b/templates/UmbracoExtension/Umbraco.Extension.csproj index 3b70140f35..d6b03e516c 100644 --- a/templates/UmbracoExtension/Umbraco.Extension.csproj +++ b/templates/UmbracoExtension/Umbraco.Extension.csproj @@ -24,13 +24,34 @@ - + + + - + + + + + + + + + + + + + + + + <_ClientAssetsBuildOutput Include="wwwroot\App_Plugins\**" /> + + + +