microsoft/typespec

Public

mirrored from https://github.com/microsoft/typespecAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
copilot/design-tspconfig-project-structure

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

packages/bundle-uploader/src/upload-browser-package.ts

130lines · modecode

1import { TokenCredential } from "@azure/identity";
2import {
3 AnonymousCredential,
4 BlobServiceClient,
5 ContainerClient,
6 StorageSharedKeyCredential,
7} from "@azure/storage-blob";
8import { BundleManifest, TypeSpecBundle, TypeSpecBundleFile } from "@typespec/bundler";
9import { join } from "path/posix";
10import { pkgsContainer, storageAccountName } from "./constants.js";
11
12export interface UploadBundleResult {
13 status: "uploaded" | "already-exists";
14 /** Resolve imports with absolute url. */
15 imports: Record<string, string>;
16}
17
18export interface PackageIndex {
19 version: string;
20 imports: Record<string, string>;
21}
22
23export class TypeSpecBundledPackageUploader {
24 #container: ContainerClient;
25
26 constructor(credential?: StorageSharedKeyCredential | AnonymousCredential | TokenCredential) {
27 this.#container = getCoverageContainer(storageAccountName, credential);
28 }
29
30 async createIfNotExists() {
31 await this.#container.createIfNotExists({
32 access: "blob",
33 });
34 }
35
36 async upload({ manifest, files }: TypeSpecBundle): Promise<UploadBundleResult> {
37 const imports = Object.fromEntries(
38 Object.entries(manifest.imports).map(([key, value]) => {
39 return [
40 key,
41 this.#container.url + "/" + normalizePath(join(manifest.name, manifest.version, value)),
42 ];
43 }),
44 );
45 const created = await this.#uploadManifest(manifest);
46 if (!created) {
47 return { status: "already-exists", imports };
48 }
49 for (const file of files) {
50 await this.#uploadJsFile(manifest.name, manifest.version, file);
51 }
52 return { status: "uploaded", imports };
53 }
54
55 async getIndex(name: string, version: string): Promise<PackageIndex | undefined> {
56 const blob = this.#container.getBlockBlobClient(`indexes/${name}/${version}.json`);
57 if (await blob.exists()) {
58 const response = await blob.download();
59 const body = await response.blobBody;
60 const existingContent = await body?.text();
61 if (existingContent) {
62 const parsed = JSON.parse(existingContent);
63 return parsed;
64 }
65 }
66 return undefined;
67 }
68 async updateIndex(name: string, index: PackageIndex) {
69 const blob = this.#container.getBlockBlobClient(`indexes/${name}/${index.version}.json`);
70 const content = JSON.stringify(index);
71 await blob.upload(content, content.length, {
72 blobHTTPHeaders: {
73 blobContentType: "application/json; charset=utf-8",
74 },
75 });
76 }
77
78 async #uploadManifest(manifest: BundleManifest) {
79 try {
80 const blob = this.#container.getBlockBlobClient(
81 normalizePath(join(manifest.name, manifest.version, "manifest.json")),
82 );
83 const content = JSON.stringify(manifest);
84 await blob.upload(content, content.length, {
85 blobHTTPHeaders: {
86 blobContentType: "application/json; charset=utf-8",
87 },
88 conditions: {
89 ifNoneMatch: "*",
90 },
91 });
92 } catch (e: any) {
93 if (e.code === "BlobAlreadyExists") {
94 return false;
95 }
96 throw e;
97 }
98 return true;
99 }
100
101 async #uploadJsFile(pkgName: string, version: string, file: TypeSpecBundleFile) {
102 const blob = this.#container.getBlockBlobClient(
103 normalizePath(join(pkgName, version, file.filename)),
104 );
105 await blob.uploadData(Buffer.from(file.content), {
106 blobHTTPHeaders: {
107 blobContentType: "application/javascript; charset=utf-8",
108 },
109 conditions: {
110 ifNoneMatch: "*",
111 },
112 });
113 }
114}
115
116function getCoverageContainer(
117 storageAccountName: string,
118 credential?: StorageSharedKeyCredential | AnonymousCredential | TokenCredential,
119): ContainerClient {
120 const blobSvc = new BlobServiceClient(
121 `https://${storageAccountName}.blob.core.windows.net`,
122 credential,
123 );
124 const containerClient = blobSvc.getContainerClient(pkgsContainer);
125 return containerClient;
126}
127
128function normalizePath(path: string): string {
129 return path.replace(/\\/g, "/");
130}
131