diff --git a/templates/Umbraco.Templates.csproj b/templates/Umbraco.Templates.csproj index b75df9f9a5..44d073a109 100644 --- a/templates/Umbraco.Templates.csproj +++ b/templates/Umbraco.Templates.csproj @@ -16,10 +16,9 @@ UmbracoProject\Program.cs UmbracoProject - - - + + UmbracoProject\Views\Partials\blocklist\%(RecursiveDir)%(Filename)%(Extension) UmbracoProject\Views\Partials\blocklist @@ -40,6 +39,13 @@ + + + + + + + <_TemplateJsonFiles Include="**\.template.config\template.json" Exclude="bin\**;obj\**" /> diff --git a/templates/UmbracoPackageRcl/.template.config/dotnetcli.host.json b/templates/UmbracoExtension/.template.config/dotnetcli.host.json similarity index 72% rename from templates/UmbracoPackageRcl/.template.config/dotnetcli.host.json rename to templates/UmbracoExtension/.template.config/dotnetcli.host.json index 9a960b348e..a3eea8aab7 100644 --- a/templates/UmbracoPackageRcl/.template.config/dotnetcli.host.json +++ b/templates/UmbracoExtension/.template.config/dotnetcli.host.json @@ -17,6 +17,14 @@ "SupportPagesAndViews": { "longName": "support-pages-and-views", "shortName": "s" + }, + "IncludeExample": { + "longName": "include-example", + "shortName": "ex" + }, + "SiteDomain": { + "longName": "site-domain", + "shortName": "sd" } } } diff --git a/templates/UmbracoExtension/.template.config/ide.host.json b/templates/UmbracoExtension/.template.config/ide.host.json new file mode 100644 index 0000000000..324a4045ae --- /dev/null +++ b/templates/UmbracoExtension/.template.config/ide.host.json @@ -0,0 +1,35 @@ +{ + "$schema": "https://json.schemastore.org/ide.host.json", + "order": 0, + "icon": "../../icon.png", + "description": { + "id": "umbraco-extension", + "text": "Umbraco Extension - A Razor Class Library project for building Umbraco extensions." + }, + "symbolInfo": [ + { + "id": "UmbracoVersion", + "isVisible": true + }, + { + "id": "SupportPagesAndViews", + "isVisible": true, + "persistenceScope": "templateGroup" + }, + { + "id": "IncludeExample", + "isVisible": true, + "description": { + "text": "Whether to include an example dashboard and supporting code" + } + }, + { + "id": "SiteDomain", + "isVisible": true, + "defaultValue": "https://localhost:5000", + "description": { + "text": "If using the --include-example then you can supply the domain prefix such as 'https://localhost:5000' to communicate with the Umbraco website for generating the TypeScript OpenAPI client" + } + } + ] +} diff --git a/templates/UmbracoExtension/.template.config/readme.md b/templates/UmbracoExtension/.template.config/readme.md new file mode 100644 index 0000000000..3dea89fff0 --- /dev/null +++ b/templates/UmbracoExtension/.template.config/readme.md @@ -0,0 +1,25 @@ +# Customising the umbraco-extension template + +## Source Name + +The source name is set to `Umbraco.Extension` + +The templating engine will rename any folder or file whose name contains `Umbraco.Extension` replacing it with the provided name. + +The templating engine will replace the text in any file as follows: + +- `Umbraco.Extension` with the safe namespace for the provided name +- `Umbraco_Extension` with the safe default class name for the provided name +- `umbraco.extension` with the safe namespace for the provided name, in lower case +- `umbraco_extension` with the safe default class name for the provided name, in lower case + +## Custom Replacements + +The following custom placeholders have been configured in `template.json`: + +- `UmbracoExtension` will be replaced with the safe namespace but without . or _ +- `umbracoextension` will be replaced with the safe namespace but without . or _ , in lower case +- `umbraco-extension` will be replaced with the kebab case transform of the provided name +- `Umbraco Extension` will be replaced with a 'friendly' version of the provided name, e.g. MyProject > My Project. NB it will render a trailing space so you don't need to add one. + +The first three custom placeholders have been configured to replace the text in both files and filenames. \ No newline at end of file diff --git a/templates/UmbracoExtension/.template.config/template.json b/templates/UmbracoExtension/.template.config/template.json new file mode 100644 index 0000000000..5ada4ae33f --- /dev/null +++ b/templates/UmbracoExtension/.template.config/template.json @@ -0,0 +1,170 @@ +{ + "$schema": "https://json.schemastore.org/template.json", + "author": "Umbraco HQ", + "classifications": [ + "Web", + "CMS", + "Umbraco", + "Extension", + "Plugin", + "Razor Class Library" + ], + "name": "Umbraco Extension", + "description": "A Razor Class Library project for building Umbraco extensions.", + "groupIdentity": "Umbraco.Templates.UmbracoExtension", + "identity": "Umbraco.Templates.UmbracoExtension", + "shortName": "umbraco-extension", + "tags": { + "language": "C#", + "type": "project" + }, + "sourceName": "Umbraco.Extension", + "defaultName": "Umbraco.Extension", + "preferNameDirectory": true, + "symbols": { + "Framework": { + "displayName": "Framework", + "description": "The target framework for the project.", + "type": "parameter", + "datatype": "choice", + "choices": [ + { + "displayName": ".NET 9.0", + "description": "Target net9.0", + "choice": "net9.0" + } + ], + "defaultValue": "net9.0", + "replaces": "net9.0" + }, + "UmbracoVersion": { + "displayName": "Umbraco version", + "description": "The version of Umbraco.Cms to add as PackageReference. By default it installs the latest non pre-release version", + "type": "parameter", + "datatype": "string", + "defaultValue": "*", + "replaces": "UMBRACO_VERSION_FROM_TEMPLATE" + }, + "SkipRestore": { + "displayName": "Skip restore", + "description": "If specified, skips the automatic restore of the project on create.", + "type": "parameter", + "datatype": "bool", + "defaultValue": "false" + }, + "SupportPagesAndViews": { + "type": "parameter", + "datatype": "bool", + "defaultValue": "false", + "displayName": "Support pages and views", + "description": "Whether to support adding traditional Razor pages and Views to this library." + }, + "KebabCasedName": { + "type": "derived", + "valueSource": "name", + "replaces": "umbraco-extension", + "fileRename": "umbraco-extension", + "valueTransform": "kebabCase" + }, + "SafeName": { + "type": "derived", + "valueSource": "name", + "valueTransform": "safe_namespace" + }, + "SafeCleanName": { + "type": "derived", + "valueSource": "SafeName", + "replaces": "UmbracoExtension", + "fileRename": "UmbracoExtension", + "valueTransform": "removePunctuation" + }, + "SafeCleanNameLower": { + "type": "derived", + "valueSource": "SafeCleanName", + "replaces": "umbracoextension", + "fileRename": "umbracoextension", + "valueTransform": "lowerCase" + }, + "SafeCleanNameFriendly": { + "type": "derived", + "valueSource": "SafeCleanName", + "replaces": "Umbraco Extension", + "valueTransform": "pascalCaseToSpaces" + }, + "IncludeExample": { + "displayName": "Include Example Code", + "description": "Whether to include an example dashboard and other code to get started with.", + "type": "parameter", + "datatype": "bool", + "defaultValue": "false" + }, + "SiteDomain": { + "displayName": "Site Domain", + "description": "If using the --include-example then you can supply the domain prefix such as 'https://localhost:5000' to communicate with the Umbraco website for generating the TypeScript OpenAPI client", + "type": "parameter", + "datatype": "string", + "defaultValue": "https://localhost:5000", + "replaces": "https://localhost:44339" + } + }, + "forms": { + "removePunctuation": { + "identifier": "replace", + "pattern": "[\\._]", + "replacement": "" + }, + "pascalCaseToSpaces": { + "identifier": "replace", + "pattern": "([A-Z][a-z]+)", + "replacement": "$1 " + } + }, + "primaryOutputs": [ + { + "path": "Umbraco.Extension.csproj" + } + ], + "postActions": [ + { + "id": "restore", + "condition": "(!SkipRestore)", + "description": "Restore NuGet packages required by this project.", + "manualInstructions": [ + { + "text": "Run 'dotnet restore'" + } + ], + "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": [ + { + "modifiers": [ + { + "condition": "(!IncludeExample)", + "exclude": [ + "[Cc]lient/src/dashboards/**", + "[Cc]lient/src/api/schemas.gen.ts" + ] + } + ] + } + ] +} diff --git a/templates/UmbracoExtension/Client/.vscode/extensions.json b/templates/UmbracoExtension/Client/.vscode/extensions.json new file mode 100644 index 0000000000..c152378477 --- /dev/null +++ b/templates/UmbracoExtension/Client/.vscode/extensions.json @@ -0,0 +1,5 @@ +{ + "recommendations": [ + "runem.lit-plugin" + ] +} diff --git a/templates/UmbracoExtension/Client/package.json b/templates/UmbracoExtension/Client/package.json new file mode 100644 index 0000000000..c4b1ae9b6b --- /dev/null +++ b/templates/UmbracoExtension/Client/package.json @@ -0,0 +1,21 @@ +{ + "name": "umbraco-extension", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "watch": "tsc && vite build --watch", + "build": "tsc && vite build", + "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", + "@umbraco-cms/backoffice": "^UMBRACO_VERSION_FROM_TEMPLATE", + "chalk": "^5.3.0", + "cross-env": "^7.0.3", + "node-fetch": "^3.3.2", + "typescript": "^5.6.3", + "vite": "^5.4.9" + } +} diff --git a/templates/UmbracoExtension/Client/public/umbraco-package.json b/templates/UmbracoExtension/Client/public/umbraco-package.json new file mode 100644 index 0000000000..2ca2f83317 --- /dev/null +++ b/templates/UmbracoExtension/Client/public/umbraco-package.json @@ -0,0 +1,14 @@ +{ + "id": "Umbraco.Extension", + "name": "Umbraco.Extension", + "version": "0.0.0", + "allowPackageTelemetry": true, + "extensions": [ + { + "name": "Umbraco ExtensionBundle", + "alias": "Umbraco.Extension.Bundle", + "type": "bundle", + "js": "/App_Plugins/UmbracoExtension/umbraco-extension.js" + } + ] +} diff --git a/templates/UmbracoExtension/Client/scripts/generate-openapi.js b/templates/UmbracoExtension/Client/scripts/generate-openapi.js new file mode 100644 index 0000000000..93bb481289 --- /dev/null +++ b/templates/UmbracoExtension/Client/scripts/generate-openapi.js @@ -0,0 +1,48 @@ +import fetch from 'node-fetch'; +import chalk from 'chalk'; +import { createClient } from '@hey-api/openapi-ts'; + +// Start notifying user we are generating the TypeScript client +console.log(chalk.green("Generating OpenAPI client...")); + +const swaggerUrl = process.argv[2]; +if (swaggerUrl === undefined) { + console.error(chalk.red(`ERROR: Missing URL to OpenAPI spec`)); + console.error(`Please provide the URL to the OpenAPI spec as the first argument found in ${chalk.yellow('package.json')}`); + console.error(`Example: node generate-openapi.js ${chalk.yellow('https://localhost:44331/umbraco/swagger/REPLACE_ME/swagger.json')}`); + process.exit(); +} + +// Needed to ignore self-signed certificates from running Umbraco on https on localhost +process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; + +// Start checking to see if we can connect to the OpenAPI spec +console.log("Ensure your Umbraco instance is running"); +console.log(`Fetching OpenAPI definition from ${chalk.yellow(swaggerUrl)}`); + +fetch(swaggerUrl).then(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`); + console.error(`Please verify or change the URL in the ${chalk.yellow('package.json')} for the script ${chalk.yellow('generate-openapi')}`); + return; + } + + console.log(`OpenAPI spec fetched successfully`); + console.log(`Calling ${chalk.yellow('hey-api')} to generate TypeScript client`); + + createClient({ + client: '@hey-api/client-fetch', + input: swaggerUrl, + output: 'src/api', + services: { + asClass: true, + } + }); + +}) + .catch(error => { + console.error(`ERROR: Failed to connect to the OpenAPI spec: ${chalk.red(error.message)}`); + console.error(`The URL to your Umbraco instance may be wrong or the instance is not running`); + console.error(`Please verify or change the URL in the ${chalk.yellow('package.json')} for the script ${chalk.yellow('generate-openapi')}`); + }); diff --git a/templates/UmbracoExtension/Client/src/api/index.ts b/templates/UmbracoExtension/Client/src/api/index.ts new file mode 100644 index 0000000000..7661d65371 --- /dev/null +++ b/templates/UmbracoExtension/Client/src/api/index.ts @@ -0,0 +1,6 @@ +// 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'; diff --git a/templates/UmbracoExtension/Client/src/api/schemas.gen.ts b/templates/UmbracoExtension/Client/src/api/schemas.gen.ts new file mode 100644 index 0000000000..1a1885f5d5 --- /dev/null +++ b/templates/UmbracoExtension/Client/src/api/schemas.gen.ts @@ -0,0 +1,391 @@ +// 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/services.gen.ts b/templates/UmbracoExtension/Client/src/api/services.gen.ts new file mode 100644 index 0000000000..c9d1c0b91b --- /dev/null +++ b/templates/UmbracoExtension/Client/src/api/services.gen.ts @@ -0,0 +1,41 @@ +// 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 new file mode 100644 index 0000000000..e666792803 --- /dev/null +++ b/templates/UmbracoExtension/Client/src/api/types.gen.ts @@ -0,0 +1,107 @@ +// This file is auto-generated by @hey-api/openapi-ts +//#if(IncludeExample) +export type DocumentGranularPermissionModel = { + key: string; + readonly context: string; + permission: string; +}; + +export type ReadOnlyUserGroupModel = { + id: number; + key: string; + name: string; + 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)>; +}; + +export type UnknownTypeGranularPermissionModel = { + context: string; + permission: string; +}; + +export type UserGroupModel = { + id: number; + key: string; + createDate: string; + updateDate: string; + deleteDate?: (string) | null; + readonly hasIdentity: boolean; + startMediaId?: (number) | null; + startContentId?: (number) | null; + icon?: (string) | null; + alias: string; + name?: (string) | null; + hasAccessToAllLanguages: boolean; + permissions: Array<(string)>; + granularPermissions: Array<(DocumentGranularPermissionModel | UnknownTypeGranularPermissionModel)>; + readonly allowedSections: Array<(string)>; + readonly userCount: number; + readonly allowedLanguages: Array<(number)>; +}; + +export type UserKindModel = 'Default' | 'Api'; + +export type UserModel = { + id: number; + key: string; + createDate: string; + updateDate: string; + deleteDate?: (string) | null; + readonly hasIdentity: boolean; + emailConfirmedDate?: (string) | null; + invitedDate?: (string) | null; + username: string; + email: string; + rawPasswordValue?: (string) | null; + passwordConfiguration?: (string) | null; + isApproved: boolean; + isLockedOut: boolean; + lastLoginDate?: (string) | null; + lastPasswordChangeDate?: (string) | null; + lastLockoutDate?: (string) | null; + failedPasswordAttempts: number; + comments?: (string) | null; + userState: UserStateModel; + name?: (string) | null; + readonly allowedSections: Array<(string)>; + readonly profileData: (UserModel | UserProfileModel); + securityStamp?: (string) | null; + avatar?: (string) | null; + sessionTimeout: number; + startContentIds?: Array<(number)> | null; + startMediaIds?: Array<(number)> | null; + language?: (string) | null; + kind: UserKindModel; + readonly groups: Array<(ReadOnlyUserGroupModel | UserGroupModel)>; +}; + +export type UserProfileModel = { + id: number; + name?: (string) | null; +}; + +export type UserStateModel = 'Active' | 'Disabled' | 'LockedOut' | 'Invited' | 'Inactive' | 'All'; +//#endif +export type PingResponse = (string); + +export type PingError = (unknown); +//#if(IncludeExample) +export type WhatsMyNameResponse = (string); + +export type WhatsMyNameError = (unknown); + +export type WhatsTheTimeMrWolfResponse = (string); + +export type WhatsTheTimeMrWolfError = (unknown); + +export type WhoAmIResponse = ((UserModel)); + +export type WhoAmIError = (unknown); +//#endif diff --git a/templates/UmbracoExtension/Client/src/bundle.manifests.ts b/templates/UmbracoExtension/Client/src/bundle.manifests.ts new file mode 100644 index 0000000000..fb2d2bfa09 --- /dev/null +++ b/templates/UmbracoExtension/Client/src/bundle.manifests.ts @@ -0,0 +1,13 @@ +import { manifests as entrypoints } from './entrypoints/manifest'; +//#if IncludeExample +import { manifests as dashboards } from './dashboards/manifest'; +//#endif + +// Job of the bundle is to collate all the manifests from different parts of the extension and load other manifests +// We load this bundle from umbraco-package.json +export const manifests: Array = [ + ...entrypoints, + //#if IncludeExample + ...dashboards, + //#endif +]; diff --git a/templates/UmbracoExtension/Client/src/dashboards/dashboard.element.ts b/templates/UmbracoExtension/Client/src/dashboards/dashboard.element.ts new file mode 100644 index 0000000000..287464086e --- /dev/null +++ b/templates/UmbracoExtension/Client/src/dashboards/dashboard.element.ts @@ -0,0 +1,176 @@ +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"; + +@customElement('example-dashboard') +export class ExampleDashboardElement extends UmbElementMixin(LitElement) { + + @state() + private _yourName: string | undefined = "Press the button!"; + + @state() + private _timeFromMrWolf: Date | undefined; + + @state() + private _serverUserData: UserModel | undefined = undefined; + + @state() + private _contextCurrentUser: UmbCurrentUserModel | undefined = undefined; + + constructor() { + super(); + + this.consumeContext(UMB_NOTIFICATION_CONTEXT, (notificationContext) => { + this.#notificationContext = notificationContext; + }); + + 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 + this.observe(currentUserContext.currentUser, (currentUser) => { + this._contextCurrentUser = currentUser; + }); + }); + } + + #notificationContext: UmbNotificationContext | undefined = undefined; + + #onClickWhoAmI = async (ev: Event) => { + const buttonElement = ev.target as UUIButtonElement; + buttonElement.state = "waiting"; + + const { data, error } = await UmbracoExtensionService.whoAmI(); + + if (error) { + buttonElement.state = "failed"; + console.error(error); + return; + } + + if (data !== undefined) { + this._serverUserData = data; + buttonElement.state = "success"; + } + + if (this.#notificationContext) { + this.#notificationContext.peek("warning", { + data: { + headline: `You are ${this._serverUserData?.name}`, + message: `Your email is ${this._serverUserData?.email}`, + } + }) + } + } + + #onClickWhatsTheTimeMrWolf = async (ev: Event) => { + const buttonElement = ev.target as UUIButtonElement; + buttonElement.state = "waiting"; + + // Getting a string - should I expect a datetime?! + const { data, error } = await UmbracoExtensionService.whatsTheTimeMrWolf(); + + if (error) { + buttonElement.state = "failed"; + console.error(error); + return; + } + + if (data !== undefined) { + this._timeFromMrWolf = new Date(data); + buttonElement.state = "success"; + } + } + + #onClickWhatsMyName = async (ev: Event) => { + const buttonElement = ev.target as UUIButtonElement; + buttonElement.state = "waiting"; + + const { data, error } = await UmbracoExtensionService.whatsMyName(); + + if (error) { + buttonElement.state = "failed"; + console.error(error); + return; + } + + 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._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.

+
+ + +
[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; + } + + uui-box { + margin-bottom: var(--uui-size-layout-1); + } + + h2 { + margin-top:0; + } + + .wide { + grid-column: span 3; + } + `]; +} + +export default ExampleDashboardElement; + +declare global { + interface HTMLElementTagNameMap { + 'example-dashboard': ExampleDashboardElement; + } +} diff --git a/templates/UmbracoExtension/Client/src/dashboards/manifest.ts b/templates/UmbracoExtension/Client/src/dashboards/manifest.ts new file mode 100644 index 0000000000..a020e3ab2c --- /dev/null +++ b/templates/UmbracoExtension/Client/src/dashboards/manifest.ts @@ -0,0 +1,18 @@ +export const manifests: Array = [ + { + name: "Umbraco ExtensionDashboard", + alias: "Umbraco.Extension.Dashboard", + type: 'dashboard', + js: () => import("./dashboard.element"), + meta: { + label: "Example Dashboard", + pathname: "example-dashboard" + }, + conditions: [ + { + 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 new file mode 100644 index 0000000000..65796c3202 --- /dev/null +++ b/templates/UmbracoExtension/Client/src/entrypoints/entrypoint.ts @@ -0,0 +1,38 @@ +import { UmbEntryPointOnInit, UmbEntryPointOnUnload } from '@umbraco-cms/backoffice/extension-api'; +//#if IncludeExample +import { UMB_AUTH_CONTEXT } from '@umbraco-cms/backoffice/auth'; +import { client } from '../api'; +//#endif + +// load up the manifests here +export const onInit: UmbEntryPointOnInit = (_host, _extensionRegistry) => { + + 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({ + 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; + }); + }); + //#endif +}; + +export const onUnload: UmbEntryPointOnUnload = (_host, _extensionRegistry) => { + console.log('Goodbye from my extension 👋'); +}; diff --git a/templates/UmbracoExtension/Client/src/entrypoints/manifest.ts b/templates/UmbracoExtension/Client/src/entrypoints/manifest.ts new file mode 100644 index 0000000000..5dcda9de89 --- /dev/null +++ b/templates/UmbracoExtension/Client/src/entrypoints/manifest.ts @@ -0,0 +1,8 @@ +export const manifests: Array = [ + { + name: "Umbraco ExtensionEntrypoint", + alias: "Umbraco.Extension.Entrypoint", + type: "backofficeEntryPoint", + js: () => import("./entrypoint"), + } +]; diff --git a/templates/UmbracoExtension/Client/tsconfig.json b/templates/UmbracoExtension/Client/tsconfig.json new file mode 100644 index 0000000000..1b3364784e --- /dev/null +++ b/templates/UmbracoExtension/Client/tsconfig.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "target": "ES2020", + "experimentalDecorators": true, + "useDefineForClassFields": false, + "module": "ESNext", + "lib": [ "ES2020", "DOM", "DOM.Iterable" ], + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "isolatedModules": true, + "moduleDetection": "force", + "noEmit": true, + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + + "types": [ "@umbraco-cms/backoffice/extension-types" ] + }, + "include": [ "src" ] +} diff --git a/templates/UmbracoExtension/Client/vite.config.ts b/templates/UmbracoExtension/Client/vite.config.ts new file mode 100644 index 0000000000..5232076b8c --- /dev/null +++ b/templates/UmbracoExtension/Client/vite.config.ts @@ -0,0 +1,17 @@ +import { defineConfig } from "vite"; + +export default defineConfig({ + build: { + lib: { + entry: "src/bundle.manifests.ts", // Bundle registers one or more manifests + formats: ["es"], + fileName: "umbraco-extension", + }, + outDir: "../wwwroot/App_Plugins/UmbracoExtension", // your web component will be saved in this location + emptyOutDir: true, + sourcemap: true, + rollupOptions: { + external: [/^@umbraco/], + }, + } +}); diff --git a/templates/UmbracoExtension/Composers/UmbracoExtensionApiComposer.cs b/templates/UmbracoExtension/Composers/UmbracoExtensionApiComposer.cs new file mode 100644 index 0000000000..481e1d19bb --- /dev/null +++ b/templates/UmbracoExtension/Composers/UmbracoExtensionApiComposer.cs @@ -0,0 +1,73 @@ +using Asp.Versioning; +using Microsoft.AspNetCore.Mvc.ApiExplorer; +using Microsoft.AspNetCore.Mvc.Controllers; +using Microsoft.Extensions.Options; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.OpenApi.Models; +using Swashbuckle.AspNetCore.SwaggerGen; +using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.DependencyInjection; +using Umbraco.Cms.Api.Management.OpenApi; +using Umbraco.Cms.Api.Common.OpenApi; + +namespace Umbraco.Extension.Composers +{ + public class UmbracoExtensionApiComposer : IComposer + { + public void Compose(IUmbracoBuilder builder) + { + + builder.Services.AddSingleton(); + + builder.Services.Configure(opt => + { + // Related documentation: + // https://docs.umbraco.com/umbraco-cms/tutorials/creating-a-backoffice-api + // https://docs.umbraco.com/umbraco-cms/tutorials/creating-a-backoffice-api/adding-a-custom-swagger-document + // https://docs.umbraco.com/umbraco-cms/tutorials/creating-a-backoffice-api/versioning-your-api + // https://docs.umbraco.com/umbraco-cms/tutorials/creating-a-backoffice-api/access-policies + + // Configure the Swagger generation options + // Add in a new Swagger API document solely for our own package that can be browsed via Swagger UI + // Along with having a generated swagger JSON file that we can use to auto generate a TypeScript client + opt.SwaggerDoc(Constants.ApiName, new OpenApiInfo + { + Title = "Umbraco ExtensionBackoffice API", + Version = "1.0", + // Contact = new OpenApiContact + // { + // Name = "Some Developer", + // Email = "you@company.com", + // Url = new Uri("https://company.com") + // } + }); + + // Enable Umbraco authentication for the "Example" Swagger document + // PR: https://github.com/umbraco/Umbraco-CMS/pull/15699 + opt.OperationFilter(); + }); + } + + public class UmbracoExtensionOperationSecurityFilter : BackOfficeSecurityRequirementsOperationFilterBase + { + protected override string ApiName => Constants.ApiName; + } + + // This is used to generate nice operation IDs in our swagger json file + // So that the gnerated TypeScript client has nice method names and not too verbose + // https://docs.umbraco.com/umbraco-cms/tutorials/creating-a-backoffice-api/umbraco-schema-and-operation-ids#operation-ids + public class CustomOperationHandler : OperationIdHandler + { + public CustomOperationHandler(IOptions apiVersioningOptions) : base(apiVersioningOptions) + { + } + + protected override bool CanHandle(ApiDescription apiDescription, ControllerActionDescriptor controllerActionDescriptor) + { + return controllerActionDescriptor.ControllerTypeInfo.Namespace?.StartsWith("Umbraco.Extension.Controllers", comparisonType: StringComparison.InvariantCultureIgnoreCase) is true; + } + + public override string Handle(ApiDescription apiDescription) => $"{apiDescription.ActionDescriptor.RouteValues["action"]}"; + } + } +} diff --git a/templates/UmbracoExtension/Constants.cs b/templates/UmbracoExtension/Constants.cs new file mode 100644 index 0000000000..9fc6796da1 --- /dev/null +++ b/templates/UmbracoExtension/Constants.cs @@ -0,0 +1,7 @@ +namespace Umbraco.Extension +{ + public class Constants + { + public const string ApiName = "umbracoextension"; + } +} diff --git a/templates/UmbracoExtension/Controllers/UmbracoExtensionApiController.cs b/templates/UmbracoExtension/Controllers/UmbracoExtensionApiController.cs new file mode 100644 index 0000000000..2f62ec2785 --- /dev/null +++ b/templates/UmbracoExtension/Controllers/UmbracoExtensionApiController.cs @@ -0,0 +1,49 @@ +using Asp.Versioning; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +#if IncludeExample +using Umbraco.Cms.Core.Models.Membership; +using Umbraco.Cms.Core.Security; +#endif + +namespace Umbraco.Extension.Controllers +{ + [ApiVersion("1.0")] + [ApiExplorerSettings(GroupName = "Umbraco.Extension")] + public class UmbracoExtensionApiController : UmbracoExtensionApiControllerBase + { +#if IncludeExample + private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor; + + public UmbracoExtensionApiController(IBackOfficeSecurityAccessor backOfficeSecurityAccessor) + { + _backOfficeSecurityAccessor = backOfficeSecurityAccessor; + } +#endif + + [HttpGet("ping")] + [ProducesResponseType(StatusCodes.Status200OK)] + public string Ping() => "Pong"; +#if IncludeExample + + [HttpGet("whatsTheTimeMrWolf")] + [ProducesResponseType(typeof(DateTime), 200)] + public DateTime WhatsTheTimeMrWolf() => DateTime.Now; + + [HttpGet("whatsMyName")] + [ProducesResponseType(StatusCodes.Status200OK)] + public string WhatsMyName() + { + // So we can see a long request in the dashboard with a spinning progress wheel + Thread.Sleep(2000); + + var currentUser = _backOfficeSecurityAccessor.BackOfficeSecurity?.CurrentUser; + return currentUser?.Name ?? "I have no idea who you are"; + } + + [HttpGet("whoAmI")] + [ProducesResponseType(StatusCodes.Status200OK)] + public IUser? WhoAmI() => _backOfficeSecurityAccessor.BackOfficeSecurity?.CurrentUser; +#endif + } +} diff --git a/templates/UmbracoExtension/Controllers/UmbracoExtensionApiControllerBase.cs b/templates/UmbracoExtension/Controllers/UmbracoExtensionApiControllerBase.cs new file mode 100644 index 0000000000..3e5a5e14a0 --- /dev/null +++ b/templates/UmbracoExtension/Controllers/UmbracoExtensionApiControllerBase.cs @@ -0,0 +1,16 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Api.Common.Attributes; +using Umbraco.Cms.Web.Common.Authorization; +using Umbraco.Cms.Web.Common.Routing; + +namespace Umbraco.Extension.Controllers +{ + [ApiController] + [BackOfficeRoute("umbracoextension/api/v{version:apiVersion}")] + [Authorize(Policy = AuthorizationPolicies.SectionAccessContent)] + [MapToApi(Constants.ApiName)] + public class UmbracoExtensionApiControllerBase : ControllerBase + { + } +} diff --git a/templates/UmbracoExtension/README.txt b/templates/UmbracoExtension/README.txt new file mode 100644 index 0000000000..7beee9137e --- /dev/null +++ b/templates/UmbracoExtension/README.txt @@ -0,0 +1,38 @@ + _ _ _ + | | | | | | + __| | ___ | |_ _ __ ___| |_ _ __ _____ __ + / _` |/ _ \| __| '_ \ / _ \ __| | '_ \ / _ \ \ /\ / / + | (_| | (_) | |_| | | | __/ |_ | | | | __/\ V V / + \__,_|\___/ \__|_| |_|\___|\__| |_| |_|\___| \_/\_/ _ _ + | | | | (_) + _ _ _ __ ___ | |__ _ __ __ _ ___ ___ _____ _| |_ ___ _ __ ___ _ ___ _ __ + | | | | '_ ` _ \| '_ \| '__/ _` |/ __/ _ \ / _ \ \/ / __/ _ \ '_ \/ __| |/ _ \| '_ \ + | |_| | | | | | | |_) | | | (_| | (_| (_) | | __/> <| || __/ | | \__ \ | (_) | | | | + \__,_|_| |_| |_|_.__/|_| \__,_|\___\___/ \___/_/\_\\__\___|_| |_|___/_|\___/|_| |_| + + +== Requirements == +* Node LTS Version 20.17.0+ +* Use a tool such as NVM (Node Version Manager) for your OS to help manage multiple versions of Node + +== Node Version Manager tools == +* https://github.com/coreybutler/nvm-windows +* https://github.com/nvm-sh/nvm +* https://docs.volta.sh/guide/getting-started + +== Steps == +* Open a terminal inside the `\Client` folder +* Run `npm install` to install all the dependencies +* Run `npm run build` to build the project +* The build output is copied to `wwwroot\App_Plugins\UmbracoExtension\umbraco-extension.js` + +== File Watching == +* Add this Razor Class Library Project as a project reference to an Umbraco Website project +* From the `\Client` folder run the command `npm run watch` this will monitor the changes to the *.ts files and rebuild the project +* With the Umbraco website project running the Razor Class Library Project will refresh the browser when the build is complete + +== Suggestion == +* Use VSCode as the editor of choice as it has good tooling support for TypeScript and it will recommend a VSCode Extension for good Lit WebComponent completions + +== Other Resources == +* Umbraco Docs - https://docs.umbraco.com/umbraco-cms/customizing/extend-and-customize-editing-experience diff --git a/templates/UmbracoPackageRcl/UmbracoPackage.csproj b/templates/UmbracoExtension/Umbraco.Extension.csproj similarity index 50% rename from templates/UmbracoPackageRcl/UmbracoPackage.csproj rename to templates/UmbracoExtension/Umbraco.Extension.csproj index b959751c87..3b70140f35 100644 --- a/templates/UmbracoPackageRcl/UmbracoPackage.csproj +++ b/templates/UmbracoExtension/Umbraco.Extension.csproj @@ -4,16 +4,14 @@ enable enable true - UmbracoPackage - App_Plugins/UmbracoPackage + Umbraco.Extension + / - UmbracoPackage - UmbracoPackage - UmbracoPackage - ... - umbraco plugin package + Umbraco.Extension + Umbraco.Extension + Umbraco.Extension @@ -23,5 +21,16 @@ + + + + + + + + + + + diff --git a/templates/UmbracoPackage/.template.config/dotnetcli.host.json b/templates/UmbracoPackage/.template.config/dotnetcli.host.json deleted file mode 100644 index 6473c5c643..0000000000 --- a/templates/UmbracoPackage/.template.config/dotnetcli.host.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "$schema": "https://json.schemastore.org/dotnetcli.host.json", - "symbolInfo": { - "Framework": { - "longName": "Framework", - "shortName": "F", - "isHidden": true - }, - "UmbracoVersion": { - "longName": "version", - "shortName": "v" - }, - "SkipRestore": { - "longName": "no-restore", - "shortName": "" - } - } -} diff --git a/templates/UmbracoPackage/.template.config/ide.host.json b/templates/UmbracoPackage/.template.config/ide.host.json deleted file mode 100644 index 0464cfeb1f..0000000000 --- a/templates/UmbracoPackage/.template.config/ide.host.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "$schema": "https://json.schemastore.org/ide.host.json", - "order": 0, - "icon": "../../icon.png", - "description": { - "id": "UmbracoPackage", - "text": "Umbraco Package - An empty Umbraco CMS package/plugin." - }, - "symbolInfo": [ - { - "id": "UmbracoVersion", - "isVisible": true - } - ] -} diff --git a/templates/UmbracoPackage/.template.config/template.json b/templates/UmbracoPackage/.template.config/template.json deleted file mode 100644 index 5c93b1d68d..0000000000 --- a/templates/UmbracoPackage/.template.config/template.json +++ /dev/null @@ -1,108 +0,0 @@ -{ - "$schema": "https://json.schemastore.org/template.json", - "author": "Umbraco HQ", - "classifications": [ - "Web", - "CMS", - "Umbraco", - "Package", - "Plugin" - ], - "name": "Umbraco Package", - "description": "An empty Umbraco package/plugin project ready to get started.", - "groupIdentity": "Umbraco.Templates.UmbracoPackage", - "identity": "Umbraco.Templates.UmbracoPackage.CSharp", - "shortName": "umbracopackage", - "tags": { - "language": "C#", - "type": "project" - }, - "sourceName": "UmbracoPackage", - "defaultName": "UmbracoPackage1", - "preferNameDirectory": true, - "symbols": { - "Framework": { - "displayName": "Framework", - "description": "The target framework for the project.", - "type": "parameter", - "datatype": "choice", - "choices": [ - { - "displayName": ".NET 9.0", - "description": "Target net9.0", - "choice": "net9.0" - } - ], - "defaultValue": "net9.0", - "replaces": "net9.0" - }, - "UmbracoVersion": { - "displayName": "Umbraco version", - "description": "The version of Umbraco.Cms to add as PackageReference.", - "type": "parameter", - "datatype": "string", - "defaultValue": "*", - "replaces": "UMBRACO_VERSION_FROM_TEMPLATE" - }, - "SkipRestore": { - "displayName": "Skip restore", - "description": "If specified, skips the automatic restore of the project on create.", - "type": "parameter", - "datatype": "bool", - "defaultValue": "false" - }, - "Namespace": { - "type": "derived", - "valueSource": "name", - "valueTransform": "safe_namespace", - "fileRename": "UmbracoPackage", - "replaces": "UmbracoPackage" - }, - "MsBuildName": { - "type": "generated", - "generator": "regex", - "dataType": "string", - "parameters": { - "source": "name", - "steps": [ - { - "regex": "\\s", - "replacement": "" - }, - { - "regex": "\\.", - "replacement": "" - }, - { - "regex": "-", - "replacement": "" - }, - { - "regex": "^[^a-zA-Z_]+", - "replacement": "" - } - ] - }, - "replaces": "UmbracoPackageMsBuild" - } - }, - "primaryOutputs": [ - { - "path": "UmbracoPackage.csproj" - } - ], - "postActions": [ - { - "id": "restore", - "condition": "(!SkipRestore)", - "description": "Restore NuGet packages required by this project.", - "manualInstructions": [ - { - "text": "Run 'dotnet restore'" - } - ], - "actionId": "210D431B-A78B-4D2F-B762-4ED3E3EA9025", - "continueOnError": true - } - ] -} diff --git a/templates/UmbracoPackage/App_Plugins/UmbracoPackage/umbraco-package.json b/templates/UmbracoPackage/App_Plugins/UmbracoPackage/umbraco-package.json deleted file mode 100644 index 153f0b0576..0000000000 --- a/templates/UmbracoPackage/App_Plugins/UmbracoPackage/umbraco-package.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "id": "UmbracoPackage", - "name": "UmbracoPackage", - "allowPackageTelemetry": true, - "extensions": [] -} diff --git a/templates/UmbracoPackage/UmbracoPackage.csproj b/templates/UmbracoPackage/UmbracoPackage.csproj deleted file mode 100644 index 9790da947c..0000000000 --- a/templates/UmbracoPackage/UmbracoPackage.csproj +++ /dev/null @@ -1,27 +0,0 @@ - - - net9.0 - enable - enable - . - UmbracoPackage - - - - UmbracoPackage - UmbracoPackage - UmbracoPackage - ... - umbraco plugin package - - - - - - - - - - - - diff --git a/templates/UmbracoPackage/buildTransitive/UmbracoPackage.targets b/templates/UmbracoPackage/buildTransitive/UmbracoPackage.targets deleted file mode 100644 index 4c376ac97b..0000000000 --- a/templates/UmbracoPackage/buildTransitive/UmbracoPackage.targets +++ /dev/null @@ -1,21 +0,0 @@ - - - $(MSBuildThisFileDirectory)..\App_Plugins\UmbracoPackage\**\*.* - - - - - - - - - - - - - - - - - - diff --git a/templates/UmbracoPackageRcl/.template.config/ide.host.json b/templates/UmbracoPackageRcl/.template.config/ide.host.json deleted file mode 100644 index 8e630f1e99..0000000000 --- a/templates/UmbracoPackageRcl/.template.config/ide.host.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "$schema": "https://json.schemastore.org/ide.host.json", - "order": 0, - "icon": "../../icon.png", - "description": { - "id": "UmbracoPackageRcl", - "text": "Umbraco Package RCL - An empty Umbraco package/plugin (Razor Class Library)." - }, - "symbolInfo": [ - { - "id": "UmbracoVersion", - "isVisible": true - }, - { - "id": "SupportPagesAndViews", - "isVisible": true, - "persistenceScope": "templateGroup" - } - ] -} diff --git a/templates/UmbracoPackageRcl/.template.config/template.json b/templates/UmbracoPackageRcl/.template.config/template.json deleted file mode 100644 index c03c860141..0000000000 --- a/templates/UmbracoPackageRcl/.template.config/template.json +++ /dev/null @@ -1,82 +0,0 @@ -{ - "$schema": "https://json.schemastore.org/template.json", - "author": "Umbraco HQ", - "classifications": [ - "Web", - "CMS", - "Umbraco", - "Package", - "Plugin", - "Razor Class Library" - ], - "name": "Umbraco Package RCL", - "description": "An empty Umbraco package/plugin (Razor Class Library).", - "groupIdentity": "Umbraco.Templates.UmbracoPackageRcl", - "identity": "Umbraco.Templates.UmbracoPackageRcl.CSharp", - "shortName": "umbracopackage-rcl", - "tags": { - "language": "C#", - "type": "project" - }, - "sourceName": "UmbracoPackage", - "defaultName": "UmbracoPackage1", - "preferNameDirectory": true, - "symbols": { - "Framework": { - "displayName": "Framework", - "description": "The target framework for the project.", - "type": "parameter", - "datatype": "choice", - "choices": [ - { - "displayName": ".NET 9.0", - "description": "Target net9.0", - "choice": "net9.0" - } - ], - "defaultValue": "net9.0", - "replaces": "net9.0" - }, - "UmbracoVersion": { - "displayName": "Umbraco version", - "description": "The version of Umbraco.Cms to add as PackageReference.", - "type": "parameter", - "datatype": "string", - "defaultValue": "*", - "replaces": "UMBRACO_VERSION_FROM_TEMPLATE" - }, - "SkipRestore": { - "displayName": "Skip restore", - "description": "If specified, skips the automatic restore of the project on create.", - "type": "parameter", - "datatype": "bool", - "defaultValue": "false" - }, - "SupportPagesAndViews": { - "type": "parameter", - "datatype": "bool", - "defaultValue": "false", - "displayName": "Support pages and views", - "description": "Whether to support adding traditional Razor pages and Views to this library." - } - }, - "primaryOutputs": [ - { - "path": "UmbracoPackage.csproj" - } - ], - "postActions": [ - { - "id": "restore", - "condition": "(!SkipRestore)", - "description": "Restore NuGet packages required by this project.", - "manualInstructions": [ - { - "text": "Run 'dotnet restore'" - } - ], - "actionId": "210D431B-A78B-4D2F-B762-4ED3E3EA9025", - "continueOnError": true - } - ] -} diff --git a/templates/UmbracoPackageRcl/wwwroot/umbraco-package.json b/templates/UmbracoPackageRcl/wwwroot/umbraco-package.json deleted file mode 100644 index 153f0b0576..0000000000 --- a/templates/UmbracoPackageRcl/wwwroot/umbraco-package.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "id": "UmbracoPackage", - "name": "UmbracoPackage", - "allowPackageTelemetry": true, - "extensions": [] -}