@azure-utils/storybooks
Version:
Utils to upload and manage Storybooks via Azure Functions and storage.
367 lines (363 loc) • 14 kB
JavaScript
const require_chunk = require('./chunk-DWy1uDak.cjs');
const require_constants = require('./constants-94H7Co6A.cjs');
const require_azure_data_tables = require('./azure-data-tables-Cr_5xanT.cjs');
const require_azure_storage_blob = require('./azure-storage-blob-BAHnImGv.cjs');
const require_shared = require('./shared-BSQDPNdH.cjs');
const zod = require_chunk.__toESM(require("zod"));
const __azure_data_tables = require_chunk.__toESM(require("@azure/data-tables"));
const __azure_storage_blob = require_chunk.__toESM(require("@azure/storage-blob"));
//#region src/models/builds.tsx
/** @private */
const BuildSchema = zod.default.object({
label: zod.default.string(),
sha: require_shared.BuildSHASchema,
authorName: zod.default.string(),
authorEmail: zod.default.string().refine((val) => val.includes("@"), "Invalid email format").meta({ description: "Email of the author" }),
message: zod.default.optional(zod.default.string()),
timestamp: zod.default.string().optional()
});
const BuildUploadSchema = BuildSchema.omit({ label: true }).extend({ labels: zod.default.string().array().meta({ description: "Label slugs associated with the build. Must be created beforehand." }) });
const BuildUploadFormSchema = BuildUploadSchema.extend({ zipFile: zod.default.file() });
/**
* - partitionKey: label
* - rowKey: sha
*/
var BuildModel = class {
#context;
#projectId;
#tableClient;
#blobService;
projectModel;
constructor(context, connectionString, projectId) {
this.#context = context;
this.#projectId = projectId;
this.#tableClient = __azure_data_tables.TableClient.fromConnectionString(connectionString, require_azure_data_tables.generateProjectAzureTableName(projectId, "Builds"));
this.#blobService = __azure_storage_blob.BlobServiceClient.fromConnectionString(connectionString);
this.projectModel = new ProjectModel(context, connectionString);
}
parse = BuildSchema.parse;
async list(options) {
this.#context.log("List builds for project '%s'...", this.#projectId);
const entities = await require_azure_data_tables.listAzureTableEntities(this.#context, this.#tableClient, options);
const builds = BuildSchema.array().parse(entities);
const groupBySHA = Object.groupBy(builds, (b) => b.sha);
const groupedBuilds = Object.values(groupBySHA).map((group) => group && group.length > 0 ? {
...group[0],
label: group.map((b) => b.label).join(",")
} : void 0).filter((build) => build !== void 0);
return groupedBuilds;
}
async get(sha, labelSlug) {
this.#context.log("Get build: '%s' for project '%s'...", sha, this.#projectId);
const entity = labelSlug ? await this.#tableClient.getEntity(labelSlug, sha) : (await this.list({ filter: `RowKey eq '${sha}'` })).at(0);
return BuildSchema.parse(entity);
}
async has(sha) {
try {
await this.get(sha);
return true;
} catch {
return false;
}
}
async create(data) {
const { labels, sha,...rest } = data;
this.#context.log("Create build '%s' for project '%s'...", sha, this.#projectId);
for (const labelSlug of labels.filter(Boolean)) {
await this.#tableClient.createEntity({
partitionKey: labelSlug,
rowKey: sha,
...rest,
label: labelSlug,
sha
});
const labelModel = this.projectModel.labelModel(this.#projectId);
try {
await labelModel.update(labelSlug, { buildSHA: sha });
} catch {
this.#context.log("A new label '$s' is being created, please update its information", labelSlug);
await labelModel.create({
value: labelSlug,
buildSHA: sha,
type: /\d+/.test(labelSlug) ? "pr" : "branch"
});
}
}
try {
const project = await this.projectModel.get(this.#projectId);
if (labels.includes(project.gitHubDefaultBranch)) this.projectModel.update(project.id, { buildSHA: sha });
} catch (error) {
this.#context.error(error);
}
}
async update() {
throw new Error("Update operation is not supported for builds.");
}
async delete(sha) {
this.#context.log("Delete build: '%s' for project '%s'...", sha, this.#projectId);
const matchingEntities = await require_azure_data_tables.listAzureTableEntities(this.#context, this.#tableClient, { filter: `sha eq '${sha}'` });
for (const entity of matchingEntities) if (entity.partitionKey && entity.rowKey) await this.#tableClient.deleteEntity(entity.partitionKey, entity.rowKey);
const containerClient = this.#blobService.getContainerClient(require_azure_storage_blob.generateAzureStorageContainerName(this.#projectId));
await require_azure_storage_blob.deleteBlobsFromAzureStorageContainerOrThrow(this.#context, containerClient, sha);
}
async deleteByLabel(labelSlug) {
this.#context.log("Delete build by label: '%s' for project '%s'...", labelSlug, this.#projectId);
const matchingEntities = await require_azure_data_tables.listAzureTableEntities(this.#context, this.#tableClient, { filter: `label eq '${labelSlug}'` });
const containerClient = this.#blobService.getContainerClient(require_azure_storage_blob.generateAzureStorageContainerName(this.#projectId));
for (const entity of matchingEntities) if (entity.partitionKey && entity.rowKey) {
await this.#tableClient.deleteEntity(entity.partitionKey, entity.rowKey);
const remainingLabelsForSHA = await require_azure_data_tables.listAzureTableEntities(this.#context, this.#tableClient, { filter: `sha eq '${entity.rowKey}'` });
if (remainingLabelsForSHA.length === 0) await require_azure_storage_blob.deleteBlobsFromAzureStorageContainerOrThrow(this.#context, containerClient, entity.rowKey);
}
}
};
//#endregion
//#region src/models/labels.tsx
const labelTypes = ["branch", "pr"];
/** @private */
const LabelSchema = zod.default.object({
slug: require_shared.LabelSlugSchema,
value: zod.default.string().meta({ description: "The value of the label." }),
type: zod.default.enum(labelTypes),
buildSHA: require_shared.BuildSHASchema.optional(),
timestamp: zod.default.string().optional()
}).meta({
id: "storybook-label",
description: "A Storybook label."
});
const LabelCreateSchema = LabelSchema.omit({
slug: true,
timestamp: true
});
const LabelUpdateSchema = LabelSchema.omit({
slug: true,
timestamp: true
}).partial();
var LabelModel = class LabelModel {
#context;
#projectId;
#tableClient;
projectModel;
constructor(context, connectionString, projectId) {
this.#context = context;
this.#projectId = projectId;
this.#tableClient = __azure_data_tables.TableClient.fromConnectionString(connectionString, require_azure_data_tables.generateProjectAzureTableName(projectId, "Labels"));
this.projectModel = new ProjectModel(context, connectionString);
}
async list(options) {
this.#context.log("List labels for project '%s'...", this.#projectId);
const entities = await require_azure_data_tables.listAzureTableEntities(this.#context, this.#tableClient, options);
return LabelSchema.array().parse(entities);
}
async get(slug) {
this.#context.log("Get label '%s' for project '%s'...", slug, this.#projectId);
const entity = await this.#tableClient.getEntity(this.#projectId, slug);
return LabelSchema.parse(entity);
}
async has(slug) {
try {
await this.get(slug);
return true;
} catch {
return false;
}
}
async create(data) {
this.#context.log("Create label '%s' for project '%s'...", data.value, this.#projectId);
const slug = LabelModel.createSlug(data.value);
await this.#tableClient.createEntity({
...data,
partitionKey: this.#projectId,
rowKey: slug,
slug
});
}
async update(slug, data) {
this.#context.log("Update label '%s' for project '%s'...", slug, this.#projectId);
await this.#tableClient.updateEntity({
...data,
partitionKey: this.#projectId,
rowKey: slug,
slug
}, "Merge");
}
async delete(slug) {
this.#context.log("Delete label '%s' for project '%s'...", slug, this.#projectId);
const { gitHubDefaultBranch } = await this.projectModel.get(this.#projectId);
if (slug === LabelModel.createSlug(gitHubDefaultBranch)) {
const message = `Cannot delete the label associated with default branch (${gitHubDefaultBranch}) of the project '${this.#projectId}'.`;
this.#context.warn(message);
throw new Error(message);
}
await this.#tableClient.deleteEntity(this.#projectId, slug);
await this.projectModel.buildModel(this.#projectId).deleteByLabel(slug);
}
static createSlug(value) {
return value.trim().toLowerCase().replace(/\W+/, "-");
}
};
//#endregion
//#region src/models/projects.tsx
/** @private */
const ProjectSchema = zod.default.object({
id: require_shared.ProjectIdSchema,
name: zod.default.string().meta({ description: "Name of the project." }),
purgeBuildsAfterDays: zod.default.coerce.number().min(1).default(require_constants.DEFAULT_PURGE_AFTER_DAYS).meta({ description: "Days after which the builds in the project should be purged." }),
gitHubRepo: zod.default.string().check(zod.default.minLength(1, "Query-param 'gitHubRepo' is required."), zod.default.refine((val) => val.split("/").length === 2, "Query-param 'gitHubRepo' should be in the format 'owner/repo'.")),
gitHubPath: zod.default.string().optional().meta({ description: "Path to the storybook project with respect to repository root." }),
gitHubDefaultBranch: zod.default.string().default(require_constants.DEFAULT_GITHUB_BRANCH).meta({ description: "Default branch to use for GitHub repository" }),
buildSHA: require_shared.BuildSHASchema.optional(),
timestamp: zod.default.string().optional()
}).meta({
id: "storybook-project",
description: "Storybook project"
});
const ProjectCreateSchema = ProjectSchema.omit({
buildSHA: true,
timestamp: true
});
const partitionKey = "projects";
var ProjectModel = class {
#connectionString;
#context;
#blobService;
#tableClient;
constructor(context, connectionString) {
this.#connectionString = connectionString;
this.#context = context;
this.#blobService = __azure_storage_blob.BlobServiceClient.fromConnectionString(connectionString);
this.#tableClient = __azure_data_tables.TableClient.fromConnectionString(connectionString, require_constants.DEFAULT_SERVICE_NAME);
}
parse = ProjectSchema.parse;
buildModel(projectId) {
return new BuildModel(this.#context, this.#connectionString, projectId);
}
labelModel(projectId) {
return new LabelModel(this.#context, this.#connectionString, projectId);
}
async list(options) {
this.#context.log("List projects...");
const entities = await require_azure_data_tables.listAzureTableEntities(this.#context, this.#tableClient, options);
return ProjectSchema.array().parse(entities);
}
async get(id) {
this.#context.log("Get project: '%s'...", id);
const entity = await this.#tableClient.getEntity(partitionKey, id);
return ProjectSchema.parse(entity);
}
async has(id) {
try {
await this.get(id);
return true;
} catch {
return false;
}
}
async create(data) {
this.#context.log("Create project: '%s'...", data.id);
const gitHubDefaultBranch = data.gitHubDefaultBranch || require_constants.DEFAULT_GITHUB_BRANCH;
await this.#tableClient.createTable();
await this.#tableClient.createEntity({
partitionKey,
rowKey: data.id,
...data,
gitHubDefaultBranch
});
await __azure_data_tables.TableClient.fromConnectionString(this.#connectionString, require_azure_data_tables.generateProjectAzureTableName(data.id, "Labels")).createTable();
await __azure_data_tables.TableClient.fromConnectionString(this.#connectionString, require_azure_data_tables.generateProjectAzureTableName(data.id, "Builds")).createTable();
await this.#blobService.createContainer(require_azure_storage_blob.generateAzureStorageContainerName(data.id));
await this.labelModel(data.id).create({
type: "branch",
value: gitHubDefaultBranch
});
}
async update(id, data) {
this.#context.log("Update project: '%s'...", id);
await this.#tableClient.updateEntity({
partitionKey,
rowKey: id,
...data,
id
}, "Merge");
}
async delete(id) {
this.#context.log("Delete project: '%s'...", id);
await this.#tableClient.deleteEntity(partitionKey, id);
await __azure_data_tables.TableClient.fromConnectionString(this.#connectionString, require_azure_data_tables.generateProjectAzureTableName(id, "Labels")).deleteTable();
await __azure_data_tables.TableClient.fromConnectionString(this.#connectionString, require_azure_data_tables.generateProjectAzureTableName(id, "Builds")).deleteTable();
await this.#blobService.deleteContainer(require_azure_storage_blob.generateAzureStorageContainerName(id));
}
};
//#endregion
Object.defineProperty(exports, 'BuildModel', {
enumerable: true,
get: function () {
return BuildModel;
}
});
Object.defineProperty(exports, 'BuildSchema', {
enumerable: true,
get: function () {
return BuildSchema;
}
});
Object.defineProperty(exports, 'BuildUploadFormSchema', {
enumerable: true,
get: function () {
return BuildUploadFormSchema;
}
});
Object.defineProperty(exports, 'BuildUploadSchema', {
enumerable: true,
get: function () {
return BuildUploadSchema;
}
});
Object.defineProperty(exports, 'LabelCreateSchema', {
enumerable: true,
get: function () {
return LabelCreateSchema;
}
});
Object.defineProperty(exports, 'LabelModel', {
enumerable: true,
get: function () {
return LabelModel;
}
});
Object.defineProperty(exports, 'LabelSchema', {
enumerable: true,
get: function () {
return LabelSchema;
}
});
Object.defineProperty(exports, 'LabelUpdateSchema', {
enumerable: true,
get: function () {
return LabelUpdateSchema;
}
});
Object.defineProperty(exports, 'ProjectCreateSchema', {
enumerable: true,
get: function () {
return ProjectCreateSchema;
}
});
Object.defineProperty(exports, 'ProjectModel', {
enumerable: true,
get: function () {
return ProjectModel;
}
});
Object.defineProperty(exports, 'ProjectSchema', {
enumerable: true,
get: function () {
return ProjectSchema;
}
});
Object.defineProperty(exports, 'labelTypes', {
enumerable: true,
get: function () {
return labelTypes;
}
});