From c3a24968b31bd158aea92a12c9c5a83cf4a70734 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 4 Apr 2022 10:15:27 +0200 Subject: [PATCH] scaffolder: add filesystem access helpers Signed-off-by: Patrik Oldsberg --- .../src/lib/filesystem/WebFileSystemAccess.ts | 105 ++++++++++++++++++ .../scaffolder/src/lib/filesystem/helpers.ts | 27 +++++ .../scaffolder/src/lib/filesystem/index.ts | 23 ++++ .../scaffolder/src/lib/filesystem/types.ts | 29 +++++ 4 files changed, 184 insertions(+) create mode 100644 plugins/scaffolder/src/lib/filesystem/WebFileSystemAccess.ts create mode 100644 plugins/scaffolder/src/lib/filesystem/helpers.ts create mode 100644 plugins/scaffolder/src/lib/filesystem/index.ts create mode 100644 plugins/scaffolder/src/lib/filesystem/types.ts diff --git a/plugins/scaffolder/src/lib/filesystem/WebFileSystemAccess.ts b/plugins/scaffolder/src/lib/filesystem/WebFileSystemAccess.ts new file mode 100644 index 0000000000..2de3bd296f --- /dev/null +++ b/plugins/scaffolder/src/lib/filesystem/WebFileSystemAccess.ts @@ -0,0 +1,105 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + TemplateFileSystemAccess, + TemplateDirectoryAccess, + TemplateFileAccess, +} from './types'; + +type WritableFileHandle = FileSystemFileHandle & { + createWritable(): Promise<{ + write(data: string | Blob | BufferSource): Promise; + close(): Promise; + }>; +}; + +// A nicer type than the one from the TS lib +interface IterableDirectoryHandle extends FileSystemDirectoryHandle { + values(): AsyncIterable< + | ({ kind: 'file' } & WritableFileHandle) + | ({ kind: 'directory' } & IterableDirectoryHandle) + >; +} + +const showDirectoryPicker = (window as any).showDirectoryPicker as + | (() => Promise) + | undefined; + +class WebFileAccess implements TemplateFileAccess { + constructor( + readonly path: string, + private readonly handle: WritableFileHandle, + ) {} + + file(): Promise { + return this.handle.getFile(); + } + + async save(data: string | Blob | BufferSource): Promise { + const writable = await this.handle.createWritable(); + await writable.write(data); + await writable.close(); + } +} + +class WebDirectoryAccess implements TemplateDirectoryAccess { + constructor(private readonly handle: IterableDirectoryHandle) {} + + async listFiles(): Promise { + const content = []; + for await (const entry of this.listDirectoryContents(this.handle)) { + content.push(entry); + } + return content; + } + + private async *listDirectoryContents( + dirHandle: IterableDirectoryHandle, + basePath: string[] = [], + ): AsyncIterable { + for await (const handle of dirHandle.values()) { + if (handle.kind === 'file') { + yield new WebFileAccess([...basePath, handle.name].join('/'), handle); + } else if (handle.kind === 'directory') { + yield* this.listDirectoryContents(handle, [...basePath, handle.name]); + } + } + } +} + +/** @internal */ +export class WebFileSystemAccess implements TemplateFileSystemAccess { + private static instance = new WebFileSystemAccess(); + + static get(): TemplateFileSystemAccess { + return this.instance; + } + + static isSupported(): boolean { + return Boolean(showDirectoryPicker); + } + + private constructor() {} + + async requestDirectoryAccess(): Promise { + if (!showDirectoryPicker) { + throw new Error('File system access is not supported'); + } + const handle = await showDirectoryPicker(); + return new WebDirectoryAccess(handle); + } +} diff --git a/plugins/scaffolder/src/lib/filesystem/helpers.ts b/plugins/scaffolder/src/lib/filesystem/helpers.ts new file mode 100644 index 0000000000..530cf87277 --- /dev/null +++ b/plugins/scaffolder/src/lib/filesystem/helpers.ts @@ -0,0 +1,27 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export async function blobToBase64(file: Blob) { + const reader = new FileReader(); + reader.readAsDataURL(file); + return new Promise((resolve, reject) => { + reader.onload = () => { + const uri = reader.result as string; + resolve(uri.slice(uri.indexOf(',') + 1)); + }; + reader.onerror = reject; + }); +} diff --git a/plugins/scaffolder/src/lib/filesystem/index.ts b/plugins/scaffolder/src/lib/filesystem/index.ts new file mode 100644 index 0000000000..c57fc35756 --- /dev/null +++ b/plugins/scaffolder/src/lib/filesystem/index.ts @@ -0,0 +1,23 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export type { + TemplateFileAccess, + TemplateDirectoryAccess, + TemplateFileSystemAccess, +} from './types'; +export { blobToBase64 } from './helpers'; +export { WebFileSystemAccess } from './WebFileSystemAccess'; diff --git a/plugins/scaffolder/src/lib/filesystem/types.ts b/plugins/scaffolder/src/lib/filesystem/types.ts new file mode 100644 index 0000000000..820ae5f99d --- /dev/null +++ b/plugins/scaffolder/src/lib/filesystem/types.ts @@ -0,0 +1,29 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export interface TemplateFileAccess { + path: string; + file(): Promise; + save(data: string | BufferSource | Blob): Promise; +} + +export interface TemplateDirectoryAccess { + listFiles(): Promise>; +} + +export interface TemplateFileSystemAccess { + requestDirectoryAccess(): Promise; +}