From e2b4fbfc9ad9361fcacb1181f39a986ab62997a7 Mon Sep 17 00:00:00 2001 From: Lingyun Cai Date: Mon, 9 Oct 2023 16:34:23 +0800 Subject: [PATCH] Support more project metadata (#9) * support more project metadata - author - keywords - repository - homepage - bugs - license * update schema validation patterns --- src/project_caches/project_config.ts | 114 +++++++++++++++++++++++++++ src/schemas/config_schema.ts | 49 ++++++++++++ src/schemas/metadata_schema.ts | 78 +++++++++++++++++- typings/webinizer.d.ts | 57 ++++++++++++++ 4 files changed, 295 insertions(+), 3 deletions(-) diff --git a/src/project_caches/project_config.ts b/src/project_caches/project_config.ts index 04ae601..8572168 100644 --- a/src/project_caches/project_config.ts +++ b/src/project_caches/project_config.ts @@ -35,6 +35,8 @@ import { IProjectBuildConfig, IJsonObject, IBuilder, + IProjectPerson, + IProjectRepository, } from "webinizer"; import { configSchema, buildTargetConfigSchema } from "../schemas/config_schema"; @@ -893,6 +895,76 @@ export class ProjectConfig extends ProjectCacheFile implements IProjectConfig { } } + get keywords(): string | undefined { + return this.data.keywords as string; + } + + set keywords(v: string | undefined) { + this.data = { keywords: v }; + if (this.keywords === undefined && this.proj.meta.get("keywords") === undefined) return; + if (this.keywords !== ((this.proj.meta.get("keywords") || []) as string[]).join(",")) { + this.proj.meta.set( + "keywords", + this.keywords ? this.keywords.split(",").map((k) => k.trim()) : [] + ); + } + } + + get homepage(): string | undefined { + return this.data.homepage as string; + } + + set homepage(v: string | undefined) { + this.data = { homepage: v }; + if (this.homepage !== this.proj.meta.get("homepage")) { + this.proj.meta.set("homepage", this.homepage); + } + } + + get bugs(): string | undefined { + return this.data.bugs as string; + } + + set bugs(v: string | undefined) { + this.data = { bugs: v }; + if (this.bugs !== this.proj.meta.get("bugs")) { + this.proj.meta.set("bugs", this.bugs); + } + } + + get license(): string | undefined { + return this.data.license as string; + } + + set license(v: string | undefined) { + this.data = { license: v }; + if (this.license !== this.proj.meta.get("license")) { + this.proj.meta.set("license", this.license); + } + } + + get author(): IProjectPerson | undefined { + return this.data.author as IProjectPerson; + } + + set author(v: IProjectPerson | undefined) { + this.data = { author: v }; + if (JSON.stringify(this.author) !== JSON.stringify(this.proj.meta.get("author"))) { + this.proj.meta.set("author", _.cloneDeep(this.author)); + } + } + + get repository(): IProjectRepository | undefined { + return this.data.repository as IProjectRepository; + } + + set repository(v: IProjectRepository | undefined) { + this.data = { repository: v }; + if (JSON.stringify(this.repository) !== JSON.stringify(this.proj.meta.get("repository"))) { + this.proj.meta.set("repository", _.cloneDeep(this.repository)); + } + } + // img: path to project image file get img(): string | undefined { return this.data.img as string; @@ -1375,6 +1447,27 @@ export class ProjectConfig extends ProjectCacheFile implements IProjectConfig { if (jsonKeys.includes("desc")) { this.proj.meta.set("description", this.desc); } + if (jsonKeys.includes("keywords")) { + this.proj.meta.set( + "keywords", + this.keywords ? this.keywords.split(",").map((k) => k.trim()) : undefined + ); + } + if (jsonKeys.includes("homepage")) { + this.proj.meta.set("homepage", this.homepage); + } + if (jsonKeys.includes("bugs")) { + this.proj.meta.set("bugs", this.bugs); + } + if (jsonKeys.includes("license")) { + this.proj.meta.set("license", this.license); + } + if (jsonKeys.includes("author")) { + this.proj.meta.set("author", _.cloneDeep(this.author)); + } + if (jsonKeys.includes("repository")) { + this.proj.meta.set("repository", _.cloneDeep(this.repository)); + } if (jsonKeys.includes("buildTargets")) { this._buildTargetConfigMap = null; this.convertBuildTargetsToMeta(); @@ -1541,6 +1634,27 @@ export class ProjectConfig extends ProjectCacheFile implements IProjectConfig { if (dotProp.has(diffContent, "description")) { this.desc = (this.proj.meta.get("description") || "") as string; } + if (dotProp.has(diffContent, "keywords")) { + this.keywords = this.proj.meta.get("keywords") + ? (this.proj.meta.get("keywords") as string[]).map((k) => k.trim()).join(",") + : undefined; + } + if (dotProp.has(diffContent, "homepage")) { + this.homepage = (this.proj.meta.get("homepage") || "") as string; + } + if (dotProp.has(diffContent, "bugs")) { + this.bugs = (this.proj.meta.get("bugs") || "") as string; + } + if (dotProp.has(diffContent, "license")) { + this.license = (this.proj.meta.get("license") || "") as string; + } + if (dotProp.has(diffContent, "author")) { + this.author = (_.cloneDeep(this.proj.meta.get("author")) || undefined) as IProjectPerson; + } + if (dotProp.has(diffContent, "repository")) { + this.repository = (_.cloneDeep(this.proj.meta.get("repository")) || + undefined) as IProjectRepository; + } /* handle webinizer specific fields */ if (dotProp.has(diffContent, "webinizer.nativeLibrary")) { this.nativeLibrary = (_.cloneDeep(this.proj.meta.get("webinizer.nativeLibrary")) || diff --git a/src/schemas/config_schema.ts b/src/schemas/config_schema.ts index d857f25..afd459d 100644 --- a/src/schemas/config_schema.ts +++ b/src/schemas/config_schema.ts @@ -126,6 +126,55 @@ export const configSchema = { description: "The description of the project.", type: "string", }, + keywords: { + description: "The project keywords.", + type: "string", + }, + homepage: { + description: "The project homepage address.", + type: "string", + pattern: "(^$)|(^(?=s*$))|(^(?!.*S))|(^(https|http)://)", + }, + bugs: { + description: "The project's issue tracker address.", + type: "string", + pattern: "(^$)|(^(?=s*$))|(^(?!.*S))|(^(https|http)://)", + }, + license: { + description: "The project license.", + type: "string", + }, + author: { + description: "The project author info.", + type: "object", + properties: { + name: { type: "string" }, + email: { + type: "string", + pattern: + "(^$)|(^(?=s*$))|(^(?!.*S))|([a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)", + }, + url: { + type: "string", + pattern: "(^$)|(^(?=s*$))|(^(?!.*S))|(^(https|http)://)", + }, + }, + additionalProperties: false, + }, + repository: { + description: "The project repository address.", + type: "object", + properties: { + type: { type: "string" }, + url: { + type: "string", + pattern: "(^$)|(^(?=s*$))|(^(?!.*S))|(^(https|https+git|git+https|git)://)", + }, + directory: { type: "string" }, + }, + required: ["type", "url"], + additionalProperties: false, + }, img: { description: "The path to the project icon image.", type: "string", diff --git a/src/schemas/metadata_schema.ts b/src/schemas/metadata_schema.ts index e2a8b4f..6af5db3 100644 --- a/src/schemas/metadata_schema.ts +++ b/src/schemas/metadata_schema.ts @@ -33,18 +33,27 @@ export const buildTargetMetaSchema = { ], }, structure: { + description: "Webinizer specific metadata fields.", type: "object", properties: { envs: { + description: "The environment variables defined for build.", type: "object", properties: { - cflags: { type: "string" }, - ldflags: { type: "string" }, + cflags: { + description: "The compiler flags defined for build.", + type: "string", + }, + ldflags: { + description: "The linker flags defined for build.", + type: "string", + }, }, required: ["cflags", "ldflags"], additionalProperties: false, }, buildSteps: { + description: "The build steps defined for build.", type: "array", items: { type: "object", @@ -58,6 +67,7 @@ export const buildTargetMetaSchema = { }, }, pkgConfig: { + description: "The package configurations defined for projects depending on this.", type: "object", properties: { prefix: { type: "string", minLength: 1 }, @@ -100,6 +110,7 @@ export const webinizerFieldMetaSchema = { type: "object", properties: { buildTargets: { + description: "The supported build targets and corresponding build configurations.", type: "object", properties: { static: { $ref: "buildTargetMeta.schema#" }, @@ -109,12 +120,14 @@ export const webinizerFieldMetaSchema = { additionalProperties: false, }, toolchain: { + description: "The information of toolchain used for build.", type: "object", properties: { emscripten: { type: "string" }, }, }, nativeLibrary: { + description: "The native library information used for conversion.", type: "object", properties: { name: { type: "string", minLength: 1 }, @@ -132,17 +145,76 @@ export const metaSchema = { type: "object", properties: { name: { + description: "The name of the project.", type: "string", maxLength: 214, minLength: 1, pattern: "^(?:@(?:[a-z0-9-*~][a-z0-9-*._~]*)?/)?[a-z0-9-~][a-z0-9-._~]*$", }, version: { + description: "The version of the project.", type: "string", pattern: "^[0-9]+.[0-9]+.[0-9]+(?:-[a-z]+(?:[_\\.-]*[a-z0-9]+)*)*$", }, - description: { type: "string" }, + description: { + description: "The description of the project.", + type: "string", + }, + keywords: { + description: "The project keywords.", + type: "array", + items: { + type: "string", + }, + }, + homepage: { + description: "The project homepage address.", + type: "string", + pattern: "(^$)|(^(?=s*$))|(^(?!.*S))|(^(https|http)://)", + }, + bugs: { + description: "The project's issue tracker address.", + type: "string", + pattern: "(^$)|(^(?=s*$))|(^(?!.*S))|(^(https|http)://)", + }, + license: { + description: "The project license.", + type: "string", + }, + author: { + description: "The project author info.", + type: "object", + properties: { + name: { type: "string" }, + email: { + type: "string", + pattern: + "(^$)|(^(?=s*$))|(^(?!.*S))|([a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)", + }, + url: { + type: "string", + pattern: "(^$)|(^(?=s*$))|(^(?!.*S))|(^(https|http)://)", + }, + }, + required: ["name"], + additionalProperties: false, + }, + repository: { + description: "The project repository address.", + type: "object", + properties: { + type: { type: "string" }, + url: { + type: "string", + pattern: "(^$)|(^(?=s*$))|(^(?!.*S))|(^(https|https+git|git+https|git)://)", + }, + directory: { type: "string" }, + }, + required: ["type", "url"], + additionalProperties: false, + }, dependencies: { + description: "The project dependent packages.", type: "object", patternProperties: { "^(?:@(?:[a-z0-9-*~][a-z0-9-*._~]*)?/)?[a-z0-9-~][a-z0-9-._~]*$": { type: "string" }, diff --git a/typings/webinizer.d.ts b/typings/webinizer.d.ts index 18399a5..3fe5c7a 100644 --- a/typings/webinizer.d.ts +++ b/typings/webinizer.d.ts @@ -172,6 +172,30 @@ declare module "webinizer" { * The project description. */ readonly desc: string | undefined; + /** + * The project keywords. + */ + readonly keywords: string | undefined; + /** + * The project homepage address. + */ + readonly homepage: string | undefined; + /** + * The project's issue tracker address. + */ + readonly bugs: string | undefined; + /** + * The project license. + */ + readonly license: string | undefined; + /** + * The project author. + */ + readonly author: IProjectPerson | undefined; + /** + * The project repository address. + */ + readonly repository: IProjectRepository | undefined; /** * The build target for the project. Currently we only support value `static` and `shared`. * @@ -276,6 +300,39 @@ declare module "webinizer" { export type ProjectPkgConfig = { [k in PkgConfigType]: string; }; + + /** + * An object represents a person. + */ + export interface IProjectPerson { + /** + * Person name. + */ + name: string; + /** + * Email address. + */ + email?: string; + /** + * Peronal homepage address. + */ + url?: string; + } + + /** + * An object represents the project repository. + */ + export interface IProjectRepository { + /** + * Repository type, i.e., git. + */ + type: string; + /** + * The URL to the project repository. + */ + url: string; + } + /** * An object represents the project build options. */