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 <leekelleher@gmail.com>
This commit is contained in:
@@ -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/**"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
],
|
||||
});
|
||||
|
||||
})
|
||||
|
||||
18
templates/UmbracoExtension/Client/src/api/client.gen.ts
Normal file
18
templates/UmbracoExtension/Client/src/api/client.gen.ts
Normal file
@@ -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<T extends DefaultClientOptions = ClientOptions> = (override?: Config<DefaultClientOptions & T>) => Config<Required<DefaultClientOptions> & T>;
|
||||
|
||||
export const client = createClient(createConfig<ClientOptions>({
|
||||
baseUrl: 'https://localhost:44389'
|
||||
}));
|
||||
@@ -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';
|
||||
@@ -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;
|
||||
78
templates/UmbracoExtension/Client/src/api/sdk.gen.ts
Normal file
78
templates/UmbracoExtension/Client/src/api/sdk.gen.ts
Normal file
@@ -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<TData extends TDataShape = TDataShape, ThrowOnError extends boolean = boolean> = ClientOptions<TData, ThrowOnError> & {
|
||||
/**
|
||||
* 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<string, unknown>;
|
||||
};
|
||||
|
||||
export class UmbracoExtensionService {
|
||||
public static ping<ThrowOnError extends boolean = false>(options?: Options<PingData, ThrowOnError>) {
|
||||
return (options?.client ?? _heyApiClient).get<PingResponse, unknown, ThrowOnError>({
|
||||
security: [
|
||||
{
|
||||
scheme: 'bearer',
|
||||
type: 'http'
|
||||
}
|
||||
],
|
||||
url: '/umbraco/umbracoextension/api/v1/ping',
|
||||
...options
|
||||
});
|
||||
}
|
||||
//#if(IncludeExample)
|
||||
public static whatsMyName<ThrowOnError extends boolean = false>(options?: Options<WhatsMyNameData, ThrowOnError>) {
|
||||
return (options?.client ?? _heyApiClient).get<WhatsMyNameResponse, unknown, ThrowOnError>({
|
||||
security: [
|
||||
{
|
||||
scheme: 'bearer',
|
||||
type: 'http'
|
||||
}
|
||||
],
|
||||
url: '/umbraco/umbracoextension/api/v1/whatsMyName',
|
||||
...options
|
||||
});
|
||||
}
|
||||
|
||||
public static whatsTheTimeMrWolf<ThrowOnError extends boolean = false>(options?: Options<WhatsTheTimeMrWolfData, ThrowOnError>) {
|
||||
return (options?.client ?? _heyApiClient).get<WhatsTheTimeMrWolfResponse, unknown, ThrowOnError>({
|
||||
security: [
|
||||
{
|
||||
scheme: 'bearer',
|
||||
type: 'http'
|
||||
}
|
||||
],
|
||||
url: '/umbraco/umbracoextension/api/v1/whatsTheTimeMrWolf',
|
||||
...options
|
||||
});
|
||||
}
|
||||
|
||||
public static whoAmI<ThrowOnError extends boolean = false>(options?: Options<WhoAmIData, ThrowOnError>) {
|
||||
return (options?.client ?? _heyApiClient).get<WhoAmIResponse, unknown, ThrowOnError>({
|
||||
security: [
|
||||
{
|
||||
scheme: 'bearer',
|
||||
type: 'http'
|
||||
}
|
||||
],
|
||||
url: '/umbraco/umbracoextension/api/v1/whoAmI',
|
||||
...options
|
||||
});
|
||||
}
|
||||
//#endif
|
||||
}
|
||||
@@ -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<ThrowOnError extends boolean = false>(options?: Options<unknown, ThrowOnError>) {
|
||||
return (options?.client ?? client).get<PingResponse, PingError, ThrowOnError>({
|
||||
...options,
|
||||
url: '/umbraco/umbracoextension/api/v1/ping'
|
||||
});
|
||||
}
|
||||
//#if(IncludeExample)
|
||||
public static whatsMyName<ThrowOnError extends boolean = false>(options?: Options<unknown, ThrowOnError>) {
|
||||
return (options?.client ?? client).get<WhatsMyNameResponse, WhatsMyNameError, ThrowOnError>({
|
||||
...options,
|
||||
url: '/umbraco/umbracoextension/api/v1/whatsMyName'
|
||||
});
|
||||
}
|
||||
|
||||
public static whatsTheTimeMrWolf<ThrowOnError extends boolean = false>(options?: Options<unknown, ThrowOnError>) {
|
||||
return (options?.client ?? client).get<WhatsTheTimeMrWolfResponse, WhatsTheTimeMrWolfError, ThrowOnError>({
|
||||
...options,
|
||||
url: '/umbraco/umbracoextension/api/v1/whatsTheTimeMrWolf'
|
||||
});
|
||||
}
|
||||
|
||||
public static whoAmI<ThrowOnError extends boolean = false>(options?: Options<unknown, ThrowOnError>) {
|
||||
return (options?.client ?? client).get<WhoAmIResponse, WhoAmIError, ThrowOnError>({
|
||||
...options,
|
||||
url: '/umbraco/umbracoextension/api/v1/whoAmI'
|
||||
});
|
||||
}
|
||||
//#endif
|
||||
}
|
||||
@@ -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<number>;
|
||||
permissions: Array<string>;
|
||||
granularPermissions: Array<DocumentGranularPermissionModel | UnknownTypeGranularPermissionModel>;
|
||||
allowedSections: Array<string>;
|
||||
};
|
||||
|
||||
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<string>;
|
||||
granularPermissions: Array<DocumentGranularPermissionModel | UnknownTypeGranularPermissionModel>;
|
||||
readonly allowedSections: Array<string>;
|
||||
readonly userCount: number;
|
||||
readonly allowedLanguages: Array<(number)>;
|
||||
readonly allowedLanguages: Array<number>;
|
||||
};
|
||||
|
||||
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<string>;
|
||||
profileData: UserModel | UserProfileModel;
|
||||
securityStamp?: string | null;
|
||||
avatar?: string | null;
|
||||
sessionTimeout: number;
|
||||
startContentIds?: Array<(number)> | null;
|
||||
startMediaIds?: Array<(number)> | null;
|
||||
language?: (string) | null;
|
||||
startContentIds?: Array<number> | null;
|
||||
startMediaIds?: Array<number> | null;
|
||||
language?: string | null;
|
||||
kind: UserKindModel;
|
||||
readonly groups: Array<(ReadOnlyUserGroupModel | UserGroupModel)>;
|
||||
readonly groups: Array<ReadOnlyUserGroupModel | UserGroupModel>;
|
||||
};
|
||||
|
||||
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 & {});
|
||||
};
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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`
|
||||
<uui-box headline="Who am I?">
|
||||
<div slot="header">[Server]</div>
|
||||
<h2><uui-icon name="icon-user"></uui-icon>${this._serverUserData?.email ? this._serverUserData.email : 'Press the button!'}</h2>
|
||||
<ul>
|
||||
${this._serverUserData?.groups.map(group => html`<li>${group.name}</li>`)}
|
||||
</ul>
|
||||
<uui-button color="default" look="primary" @click="${this.#onClickWhoAmI}">
|
||||
Who am I?
|
||||
</uui-button>
|
||||
<p>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.</p>
|
||||
</uui-box>
|
||||
<uui-box headline="Who am I?">
|
||||
<div slot="header">[Server]</div>
|
||||
<h2>
|
||||
<uui-icon name="icon-user"></uui-icon>${this._serverUserData?.email
|
||||
? this._serverUserData.email
|
||||
: "Press the button!"}
|
||||
</h2>
|
||||
<ul>
|
||||
${this._serverUserData?.groups.map(
|
||||
(group) => html`<li>${group.name}</li>`
|
||||
)}
|
||||
</ul>
|
||||
<uui-button
|
||||
color="default"
|
||||
look="primary"
|
||||
@click="${this.#onClickWhoAmI}"
|
||||
>
|
||||
Who am I?
|
||||
</uui-button>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
</uui-box>
|
||||
|
||||
<uui-box headline="What's my Name?">
|
||||
<div slot="header">[Server]</div>
|
||||
<h2><uui-icon name="icon-user"></uui-icon> ${this._yourName }</h2>
|
||||
<uui-button color="default" look="primary" @click="${this.#onClickWhatsMyName}">
|
||||
Whats my name?
|
||||
</uui-button>
|
||||
<p>This endpoint has a forced delay to show the button 'waiting' state for a few seconds before completing the request.</p>
|
||||
</uui-box>
|
||||
<uui-box headline="What's my Name?">
|
||||
<div slot="header">[Server]</div>
|
||||
<h2><uui-icon name="icon-user"></uui-icon> ${this._yourName}</h2>
|
||||
<uui-button
|
||||
color="default"
|
||||
look="primary"
|
||||
@click="${this.#onClickWhatsMyName}"
|
||||
>
|
||||
Whats my name?
|
||||
</uui-button>
|
||||
<p>
|
||||
This endpoint has a forced delay to show the button 'waiting' state
|
||||
for a few seconds before completing the request.
|
||||
</p>
|
||||
</uui-box>
|
||||
|
||||
<uui-box headline="What's the Time?">
|
||||
<div slot="header">[Server]</div>
|
||||
<h2><uui-icon name="icon-alarm-clock"></uui-icon> ${this._timeFromMrWolf ? this._timeFromMrWolf.toLocaleString() : 'Press the button!'}</h2>
|
||||
<uui-button color="default" look="primary" @click="${this.#onClickWhatsTheTimeMrWolf}">
|
||||
Whats the time Mr Wolf?
|
||||
</uui-button>
|
||||
<p>This endpoint gets the current date and time from the server.</p>
|
||||
</uui-box>
|
||||
<uui-box headline="What's the Time?">
|
||||
<div slot="header">[Server]</div>
|
||||
<h2>
|
||||
<uui-icon name="icon-alarm-clock"></uui-icon> ${this._timeFromMrWolf
|
||||
? this._timeFromMrWolf.toLocaleString()
|
||||
: "Press the button!"}
|
||||
</h2>
|
||||
<uui-button
|
||||
color="default"
|
||||
look="primary"
|
||||
@click="${this.#onClickWhatsTheTimeMrWolf}"
|
||||
>
|
||||
Whats the time Mr Wolf?
|
||||
</uui-button>
|
||||
<p>This endpoint gets the current date and time from the server.</p>
|
||||
</uui-box>
|
||||
|
||||
<uui-box headline="Who am I?" class="wide">
|
||||
<div slot="header">[Context]</div>
|
||||
<p>Current user email: <b>${this._contextCurrentUser?.email}</b></p>
|
||||
<p>This is the JSON object available by consuming the 'UMB_CURRENT_USER_CONTEXT' context:</p>
|
||||
<umb-code-block language="json" copy>${JSON.stringify(this._contextCurrentUser, null, 2)}</umb-code-block>
|
||||
</uui-box>
|
||||
<uui-box headline="Who am I?" class="wide">
|
||||
<div slot="header">[Context]</div>
|
||||
<p>Current user email: <b>${this._contextCurrentUser?.email}</b></p>
|
||||
<p>
|
||||
This is the JSON object available by consuming the
|
||||
'UMB_CURRENT_USER_CONTEXT' context:
|
||||
</p>
|
||||
<umb-code-block language="json" copy
|
||||
>${JSON.stringify(this._contextCurrentUser, null, 2)}</umb-code-block
|
||||
>
|
||||
</uui-box>
|
||||
`;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,17 +2,17 @@ export const manifests: Array<UmbExtensionManifest> = [
|
||||
{
|
||||
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",
|
||||
},
|
||||
],
|
||||
}
|
||||
},
|
||||
];
|
||||
|
||||
@@ -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 👋");
|
||||
};
|
||||
|
||||
@@ -3,6 +3,6 @@ export const manifests: Array<UmbExtensionManifest> = [
|
||||
name: "Umbraco ExtensionEntrypoint",
|
||||
alias: "Umbraco.Extension.Entrypoint",
|
||||
type: "backofficeEntryPoint",
|
||||
js: () => import("./entrypoint"),
|
||||
}
|
||||
js: () => import("./entrypoint.js"),
|
||||
},
|
||||
];
|
||||
|
||||
@@ -13,5 +13,5 @@ export default defineConfig({
|
||||
rollupOptions: {
|
||||
external: [/^@umbraco/],
|
||||
},
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -24,13 +24,34 @@
|
||||
<PackageReference Include="Umbraco.Cms.Api.Common" Version="UMBRACO_VERSION_FROM_TEMPLATE" />
|
||||
<PackageReference Include="Umbraco.Cms.Api.Management" Version="UMBRACO_VERSION_FROM_TEMPLATE" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<ClientAssetsInputs Include="Client\**" Exclude="$(DefaultItemExcludes)" />
|
||||
|
||||
<!-- Dont include the client folder as part of packaging nuget build -->
|
||||
<Content Remove="Client\**" />
|
||||
|
||||
<!-- However make the Umbraco-package.json included for dotnet pack or nuget package and visible to the solution -->
|
||||
<None Include="Client\public\umbraco-package.json" Pack="false" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="wwwroot\" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Restore and build Client files -->
|
||||
<Target Name="RestoreClient" Inputs="Client\package.json;Client\package-lock.json" Outputs="Client\node_modules\.package-lock.json">
|
||||
<Message Importance="high" Text="Restoring Client NPM packages..." />
|
||||
<Exec Command="npm i" WorkingDirectory="Client" />
|
||||
</Target>
|
||||
|
||||
<Target Name="BuildClient" BeforeTargets="AssignTargetPaths" DependsOnTargets="RestoreClient" Inputs="@(ClientAssetsInputs)" Outputs="$(IntermediateOutputPath)client.complete.txt">
|
||||
<Message Importance="high" Text="Executing Client NPM build script..." />
|
||||
<Exec Command="npm run build" WorkingDirectory="Client" />
|
||||
<ItemGroup>
|
||||
<_ClientAssetsBuildOutput Include="wwwroot\App_Plugins\**" />
|
||||
</ItemGroup>
|
||||
<WriteLinesToFile File="$(IntermediateOutputPath)client.complete.txt" Lines="@(_ClientAssetsBuildOutput)" Overwrite="true" />
|
||||
</Target>
|
||||
|
||||
</Project>
|
||||
|
||||
Reference in New Issue
Block a user