@stacksjs/storage 0.58.48 → 0.58.49

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@stacksjs/storage",
3
3
  "type": "module",
4
- "version": "0.58.48",
4
+ "version": "0.58.49",
5
5
  "description": "The Stacks file system.",
6
6
  "author": "Chris Breuer",
7
7
  "license": "MIT",
@@ -42,7 +42,8 @@
42
42
  ],
43
43
  "files": [
44
44
  "README.md",
45
- "dist"
45
+ "dist",
46
+ "src"
46
47
  ],
47
48
  "scripts": {
48
49
  "build": "bun --bun build.ts",
package/src/copy.ts ADDED
@@ -0,0 +1,42 @@
1
+ import { contains } from '@stacksjs/arrays'
2
+ import { join } from '@stacksjs/path'
3
+ import { fs } from './fs'
4
+
5
+ export function copy(src: string | string[], dest: string, exclude: string[] = []): void {
6
+ if (Array.isArray(src)) {
7
+ src.forEach((file) => {
8
+ copy(file, dest, exclude)
9
+ })
10
+ }
11
+ else {
12
+ if (fs.statSync(src).isDirectory())
13
+ copyFolder(src, dest, exclude)
14
+
15
+ else
16
+ copyFile(src, dest)
17
+ }
18
+ }
19
+
20
+ export function copyFile(src: string, dest: string): void {
21
+ fs.copyFileSync(src, dest)
22
+ }
23
+
24
+ export function copyFolder(src: string, dest: string, exclude: string[] = []): void {
25
+ if (!fs.existsSync(dest))
26
+ fs.mkdirSync(dest, { recursive: true })
27
+
28
+ if (fs.existsSync(src)) {
29
+ fs.readdirSync(src).forEach((file) => {
30
+ if (!contains(join(src, file), exclude)) {
31
+ const srcPath = join(src, file)
32
+ const destPath = join(dest, file)
33
+
34
+ if (fs.statSync(srcPath).isDirectory())
35
+ copyFolder(srcPath, destPath, exclude)
36
+
37
+ else
38
+ fs.copyFileSync(srcPath, destPath)
39
+ }
40
+ })
41
+ }
42
+ }
package/src/delete.ts ADDED
@@ -0,0 +1,131 @@
1
+ import type { Result } from '@stacksjs/error-handling'
2
+ import { err, ok } from '@stacksjs/error-handling'
3
+ import { italic, log } from '@stacksjs/cli'
4
+ import { isFile } from './files'
5
+ import { isFolder } from './folders'
6
+ import { glob } from './glob'
7
+ import { fs } from './fs'
8
+
9
+ export function deleteFolder(path: string): Promise<Result<string, Error>> {
10
+ return new Promise((resolve, reject) => {
11
+ try {
12
+ if (isFolder(path)) {
13
+ fs.rmSync(path, { recursive: true, force: true })
14
+ return resolve(ok(`Deleted ${path}`))
15
+ }
16
+
17
+ return resolve(ok(`Path ${path} was not a directory`))
18
+ }
19
+ catch (error) {
20
+ return reject(err(error))
21
+ }
22
+ })
23
+ }
24
+
25
+ export async function isDirectoryEmpty(path: string): Promise<Result<boolean, Error>> {
26
+ return new Promise((resolve, reject) => {
27
+ try {
28
+ if (fs.statSync(path).isDirectory()) {
29
+ if (fs.readdirSync(path).length === 0)
30
+ return resolve(ok(true))
31
+ else
32
+ return resolve(ok(false))
33
+ }
34
+
35
+ return resolve(ok(false))
36
+ }
37
+ catch (error) {
38
+ return reject(err(error))
39
+ }
40
+ })
41
+ }
42
+
43
+ export async function deleteEmptyFolder(path: string): Promise<Result<string, Error>> {
44
+ return new Promise((resolve, reject) => {
45
+ try {
46
+ if (fs.statSync(path).isDirectory()) {
47
+ if (fs.readdirSync(path).length === 0) {
48
+ fs.rmSync(path, { recursive: true, force: true })
49
+ return resolve(ok(`Deleted ${path}`))
50
+ }
51
+ else { return resolve(ok(`Path ${path} was not empty`)) }
52
+ }
53
+
54
+ return resolve(ok(`Path ${path} was not a directory`))
55
+ }
56
+ catch (error) {
57
+ return reject(err(error))
58
+ }
59
+ })
60
+ }
61
+
62
+ export async function deleteEmptyFolders(dir: string): Promise<Result<string, Error>> {
63
+ try {
64
+ if (!fs.existsSync(dir))
65
+ return ok(`Path ${dir} does not exist`)
66
+
67
+ const files = fs.readdirSync(dir)
68
+ for (const file of files) {
69
+ const p = join(dir, file)
70
+ if (isFolder(p)) {
71
+ if (fs.readdirSync(p).length === 0)
72
+ fs.rmSync(p, { recursive: true, force: true })
73
+ else
74
+ await deleteEmptyFolders(p)
75
+ }
76
+ }
77
+
78
+ return ok(`Deleted empty folders located in ${dir}`)
79
+ }
80
+ catch (error: any) {
81
+ return err(error)
82
+ }
83
+ }
84
+
85
+ export function deleteFile(path: string): Promise<Result<string, Error>> {
86
+ return new Promise((resolve, reject) => {
87
+ try {
88
+ if (isFile(path)) {
89
+ fs.rmSync(path, { recursive: true, force: true })
90
+ return resolve(ok(`Deleted ${path}`))
91
+ }
92
+
93
+ return resolve(ok(`Path ${path} was not a file`))
94
+ }
95
+ catch (error) {
96
+ return reject(err(error))
97
+ }
98
+ })
99
+ }
100
+
101
+ export async function deleteGlob(path: string): Promise<Result<string, Error>> {
102
+ if (!path.includes('*'))
103
+ return err(handleError(`Path ${path} does not contain a glob`))
104
+
105
+ const directories = await glob([path], { onlyDirectories: true })
106
+
107
+ for (const directory of directories) {
108
+ const result = await deleteFolder(directory)
109
+ if (result.isErr()) {
110
+ log.error(result.error)
111
+ return result
112
+ }
113
+
114
+ log.info(`Deleted ${italic(directory)}`)
115
+ }
116
+
117
+ return ok(`Deleted ${directories.length} directories`)
118
+ }
119
+
120
+ export async function del(path: string): Promise<Result<string, Error>> {
121
+ if (isFile(path))
122
+ return await deleteFile(path)
123
+
124
+ if (isFolder(path))
125
+ return await deleteFolder(path)
126
+
127
+ if (path.includes('*'))
128
+ return await deleteGlob(path)
129
+
130
+ return err(handleError(`Path ${path} cannot be deleted due to an unhandled condition. Please report this issue.`))
131
+ }
@@ -0,0 +1,15 @@
1
+ import type { NestedStackProps } from 'aws-cdk-lib'
2
+ import { NestedStack } from 'aws-cdk-lib'
3
+ import type { Construct } from 'constructs'
4
+
5
+ export class StorageStack extends NestedStack {
6
+ constructor(scope: Construct, id: string, props?: NestedStackProps) {
7
+ super(scope, id, props)
8
+
9
+ // if (!storage.name)
10
+ // throw new Error('./config storage.name is not defined')
11
+
12
+ // // eslint-disable-next-line no-new
13
+ // new s3.Bucket(this, storage.name)
14
+ }
15
+ }
@@ -0,0 +1 @@
1
+ export * as aws from './aws'
package/src/files.ts ADDED
@@ -0,0 +1,190 @@
1
+ import type { BunFile, PathLike } from 'bun'
2
+ import type { JsonFile, PackageJson, TextFile } from '@stacksjs/types'
3
+ import { detectIndent, detectNewline } from '@stacksjs/strings'
4
+ import { dirname, join, path as p } from '@stacksjs/path'
5
+ import { contains } from '@stacksjs/arrays'
6
+ import { existsSync, fs } from './fs'
7
+
8
+ /**
9
+ * Reads a JSON file and returns the parsed data.
10
+ */
11
+ export async function readJsonFile(name: string, cwd?: string): Promise<JsonFile> {
12
+ const file = await readTextFile(name, cwd)
13
+ const data = JSON.parse(file.data)
14
+ const indent = detectIndent(file.data).indent
15
+ const newline = detectNewline(file.data)
16
+
17
+ return { ...file, data, indent, newline }
18
+ }
19
+
20
+ /**
21
+ * Reads a package.json file and returns the parsed data.
22
+ */
23
+ export async function readPackageJson(name: string, cwd?: string) {
24
+ const file = await readJsonFile(name, cwd)
25
+ return file.data as PackageJson
26
+ }
27
+
28
+ type Path = BunFile | PathLike
29
+ type Data = Blob | TypedArray | ArrayBufferLike | string | BlobPart[]
30
+
31
+ /**
32
+ * Writes the given text to the specified file.
33
+ */
34
+ export async function writeFile(path: Path, data: Data): Promise<number> {
35
+ if (typeof path === 'string') {
36
+ const dirPath = dirname(path)
37
+ if (!await existsSync(dirPath))
38
+ await createFolder(dirPath)
39
+
40
+ return await Bun.write(Bun.file(path), data)
41
+ }
42
+
43
+ return await Bun.write(path, data)
44
+ }
45
+
46
+ /**
47
+ * Writes the given data to the specified JSON file.
48
+ */
49
+ export async function writeJsonFile(file: JsonFile): Promise<number> {
50
+ let json = JSON.stringify(file.data, undefined, file.indent)
51
+
52
+ if (file.newline)
53
+ json += file.newline
54
+
55
+ return writeTextFile({ ...file, data: json })
56
+ }
57
+
58
+ /**
59
+ * Reads a text file and returns its contents.
60
+ */
61
+ export function readTextFile(name: string, cwd?: string): Promise<TextFile> {
62
+ return new Promise((resolve, reject) => {
63
+ let filePath: string
64
+
65
+ if (cwd)
66
+ filePath = join(cwd, name)
67
+ else
68
+ filePath = name
69
+
70
+ fs.readFile(filePath, 'utf8', (err, text) => {
71
+ if (err) {
72
+ reject(err)
73
+ }
74
+ else {
75
+ resolve({
76
+ path: filePath,
77
+ data: text,
78
+ })
79
+ }
80
+ })
81
+ })
82
+ }
83
+
84
+ /**
85
+ * Writes the given text to the specified file.
86
+ */
87
+ export async function writeTextFile(file: TextFile): Promise<number> {
88
+ return await Bun.write(file.path, file.data)
89
+ }
90
+
91
+ /**
92
+ * Determine whether a path exists.
93
+ */
94
+ export function isFile(path: string): boolean {
95
+ return fs.existsSync(path)
96
+ }
97
+
98
+ export function doesExist(path: string): boolean {
99
+ return !isFile(path) || !isFolder(path)
100
+ }
101
+
102
+ export function doesNotExist(path: string): boolean {
103
+ return !isFile(path) && !isFolder(path)
104
+ }
105
+
106
+ /**
107
+ * Determine whether a folder has any files in it.
108
+ */
109
+ export function hasFiles(folder: string): boolean {
110
+ try {
111
+ return fs.readdirSync(folder).length > 0
112
+ }
113
+ catch (err) {
114
+ return false
115
+ }
116
+ }
117
+
118
+ export function hasComponents(): boolean {
119
+ return hasFiles(p.componentsPath())
120
+ }
121
+
122
+ export function hasFunctions(): boolean {
123
+ return hasFiles(p.functionsPath())
124
+ }
125
+
126
+ export function deleteFiles(dir: string, exclude: string[] = []) {
127
+ if (fs.existsSync(dir)) {
128
+ fs.readdirSync(dir).forEach((file) => {
129
+ const p = join(dir, file)
130
+ if (fs.statSync(p).isDirectory()) {
131
+ if (fs.readdirSync(p).length === 0)
132
+ fs.rmSync(p, { recursive: true, force: true })
133
+
134
+ else
135
+ deleteFiles(p, exclude)
136
+ }
137
+
138
+ else if (!contains(p, exclude)) {
139
+ fs.rmSync(p)
140
+ }
141
+ })
142
+ }
143
+ }
144
+
145
+ export function getFiles(dir: string, exclude: string[] = []): string[] {
146
+ let results: string[] = []
147
+ const list = fs.readdirSync(dir)
148
+
149
+ list.forEach((file) => {
150
+ file = join(dir, file)
151
+ const stat = fs.statSync(file)
152
+
153
+ if (stat && stat.isDirectory())
154
+ results = results.concat(getFiles(file, exclude))
155
+
156
+ else if (!contains(file, exclude))
157
+ results.push(file)
158
+ })
159
+
160
+ return results
161
+ }
162
+
163
+ export function put(path: string, contents: string) {
164
+ const dirPath = dirname(path)
165
+
166
+ if (!fs.existsSync(dirPath))
167
+ fs.mkdirSync(dirPath, { recursive: true })
168
+
169
+ fs.writeFileSync(path, contents, 'utf-8')
170
+ }
171
+
172
+ export async function get(path: string): Promise<string> {
173
+ return Bun.file(path).text()
174
+ }
175
+
176
+ export const files = {
177
+ readJsonFile,
178
+ readPackageJson,
179
+ readTextFile,
180
+ writeJsonFile,
181
+ writeTextFile,
182
+ isFile,
183
+ hasFiles,
184
+ hasComponents,
185
+ hasFunctions,
186
+ deleteFiles,
187
+ getFiles,
188
+ put,
189
+ get,
190
+ }
package/src/folders.ts ADDED
@@ -0,0 +1,51 @@
1
+ import { join } from '@stacksjs/path'
2
+ import { fs } from './fs'
3
+
4
+ /**
5
+ * Determine whether a path is a folder.
6
+ */
7
+ export function isFolder(path: string): boolean {
8
+ try {
9
+ return fs.statSync(path).isDirectory()
10
+ }
11
+ catch {
12
+ return false
13
+ }
14
+ }
15
+
16
+ export function isDirectory(path: string): boolean {
17
+ return isFolder(path)
18
+ }
19
+
20
+ export function isDir(path: string): boolean {
21
+ return isFolder(path)
22
+ }
23
+
24
+ export function doesFolderExist(path: string) {
25
+ return fs.existsSync(path)
26
+ }
27
+
28
+ export function createFolder(dir: string): Promise<void> {
29
+ return new Promise((resolve, reject) => {
30
+ fs.mkdirs(dir, (err: any) => {
31
+ if (err)
32
+ reject(err)
33
+
34
+ else
35
+ resolve()
36
+ })
37
+ })
38
+ }
39
+
40
+ export function getFolders(dir: string): string[] {
41
+ return fs.readdirSync(dir).filter((file) => {
42
+ return fs.statSync(join(dir, file)).isDirectory()
43
+ })
44
+ }
45
+
46
+ export const folders = {
47
+ isFolder,
48
+ doesFolderExist,
49
+ createFolder,
50
+ getFolders,
51
+ }
package/src/fs.ts ADDED
@@ -0,0 +1,9 @@
1
+ import { mkdirSync, writeFileSync } from 'node:fs'
2
+ import * as fs from 'fs-extra'
3
+ import { pathExists as existsSync } from 'fs-extra'
4
+
5
+ export async function exists(path: string): Promise<boolean> {
6
+ return await existsSync(path)
7
+ }
8
+
9
+ export { fs, existsSync, mkdirSync, writeFileSync }
package/src/glob.ts ADDED
@@ -0,0 +1 @@
1
+ export { default as glob } from 'fast-glob'
package/src/hash.ts ADDED
@@ -0,0 +1,39 @@
1
+ import type { Hash } from 'node:crypto'
2
+ import { createHash } from 'node:crypto'
3
+ import { path as p } from '@stacksjs/path'
4
+ import { fs } from './fs'
5
+
6
+ export function hashFileOrDirectory(path: string, hash: Hash): void {
7
+ if (fs.statSync(path).isDirectory()) {
8
+ const files = fs.readdirSync(path)
9
+ for (const file of files) {
10
+ const filePath = p.join(path, file)
11
+ hashFileOrDirectory(filePath, hash)
12
+ }
13
+ }
14
+ else {
15
+ hash.update(fs.readFileSync(path))
16
+ }
17
+ }
18
+
19
+ export function hashDirectory(directory: string): string {
20
+ const hash = createHash('sha256')
21
+ hashFileOrDirectory(directory, hash)
22
+ return hash.digest('hex')
23
+ }
24
+
25
+ export function hashPath(path: string): string {
26
+ const hash = createHash('sha256')
27
+ hashFileOrDirectory(path, hash)
28
+ return hash.digest('hex')
29
+ }
30
+
31
+ export function hashPaths(paths: string | string[]): string {
32
+ const hash = createHash('sha256')
33
+ const pathsArray = Array.isArray(paths) ? paths : [paths]
34
+
35
+ for (const path of pathsArray)
36
+ hashFileOrDirectory(path, hash)
37
+
38
+ return hash.digest('hex')
39
+ }
package/src/helpers.ts ADDED
@@ -0,0 +1,29 @@
1
+ import { fileURLToPath } from 'node:url'
2
+ import { dirname } from '@stacksjs/path'
3
+ import { fs } from './fs'
4
+
5
+ export const _dirname = typeof __dirname !== 'undefined'
6
+ ? __dirname
7
+ : dirname(fileURLToPath(import.meta.url))
8
+
9
+ export function updateConfigFile(filePath: string, newConfig: Record<string, unknown>): Promise<void> {
10
+ return new Promise((resolve, reject) => {
11
+ const config = JSON.parse(fs.readFileSync(filePath, 'utf8')) as Record<string, unknown>
12
+
13
+ for (const key in newConfig)
14
+ config[key] = newConfig[key]
15
+
16
+ try {
17
+ fs.writeFileSync(filePath, JSON.stringify(config, null, 2))
18
+ resolve()
19
+ }
20
+ catch (error) {
21
+ reject(error)
22
+ }
23
+ })
24
+ }
25
+
26
+ export const helpers = {
27
+ _dirname,
28
+ updateConfigFile,
29
+ }
package/src/index.ts ADDED
@@ -0,0 +1,11 @@
1
+ export * from './files'
2
+ export * from './folders'
3
+ export * from './hash'
4
+ export * from './helpers'
5
+ export * from './fs'
6
+ export * from './copy'
7
+ export * from './glob'
8
+ export * from './delete'
9
+ export * from './zip'
10
+
11
+ export * as storage from './storage'
package/src/move.ts ADDED
@@ -0,0 +1,84 @@
1
+ import { type Result, err, ok } from '@stacksjs/error-handling'
2
+ import { log } from '@stacksjs/logging'
3
+ import { path } from '@stacksjs/path'
4
+ import { fs } from './fs'
5
+
6
+ interface MoveOptions {
7
+ // glob?: glob.Options
8
+ overwrite?: boolean
9
+ }
10
+
11
+ export async function move(
12
+ src: string | string[],
13
+ dest: string,
14
+ options?: MoveOptions,
15
+ ): Promise<Result<{ message: string }, Error>> {
16
+ try {
17
+ if (Array.isArray(src)) {
18
+ const operations = src.map(async (file) => {
19
+ const from = file
20
+ const to = path.resolve(dest, path.basename(file))
21
+ const result = await rename(from, to, options)
22
+
23
+ if (result.isErr()) {
24
+ log.error(result.error)
25
+ return err(handleError(result.error.message, result.error))
26
+ }
27
+ })
28
+
29
+ await Promise.all(operations)
30
+
31
+ return ok({ message: 'Files moved successfully' })
32
+ }
33
+
34
+ const from = src
35
+ const to = dest
36
+ const result = await rename(from, to, options)
37
+
38
+ if (result.isErr()) {
39
+ log.error(result.error)
40
+ return err(handleError(result.error))
41
+ }
42
+
43
+ return ok({ message: 'File moved successfully' })
44
+ }
45
+ catch (error: any) {
46
+ return err(handleError(error))
47
+ }
48
+ }
49
+
50
+ export async function rename(from: string, to: string, options?: MoveOptions): Promise<Result<{ message: string }, Error>> {
51
+ return new Promise((resolve, reject) => {
52
+ try {
53
+ // Check if the "to" directory exists
54
+ const dir = path.dirname(to)
55
+ if (!fs.existsSync(dir))
56
+ fs.mkdirSync(dir, { recursive: true })
57
+
58
+ // Ensure the "from" file exists
59
+ if (!fs.existsSync(from))
60
+ return reject(err(new Error(`File or directory does not exist: ${from}`)))
61
+
62
+ // Ensure the "to" file does not exist
63
+ if (fs.existsSync(to)) {
64
+ if (!options?.overwrite)
65
+ return reject(err(new Error(`File or directory already exists: ${to}`)))
66
+
67
+ fs.unlinkSync(to)
68
+ }
69
+
70
+ // "Move" the file
71
+ fs.renameSync(from, to)
72
+
73
+ return resolve(ok({ message: 'File moved successfully' }))
74
+ }
75
+ catch (error: any) {
76
+ if (error.code === 'ENOENT')
77
+ log.error('File or directory does not exist\n\n', error)
78
+ else
79
+ log.error(error)
80
+
81
+ return reject(err(new Error(error)))
82
+ }
83
+ })
84
+ }
package/src/storage.ts ADDED
@@ -0,0 +1,10 @@
1
+ export * from './files'
2
+ export * from './folders'
3
+ export * from './helpers'
4
+ export * from './fs'
5
+
6
+ export * from './copy'
7
+ export * from './delete'
8
+ export * from './move'
9
+ export * from './zip'
10
+ export * from './visibility'
@@ -0,0 +1,6 @@
1
+ // type Visibility = 'public' | 'private'
2
+
3
+ // export function setVisibility(path: string, visibility: Visibility) {
4
+ export function setVisibility() {
5
+ return 'wip'
6
+ }
package/src/zip.ts ADDED
@@ -0,0 +1,55 @@
1
+ import type { ZlibCompressionOptions } from 'bun'
2
+ import { runCommand } from '@stacksjs/cli'
3
+
4
+ interface ZipOptions {
5
+ cwd?: string
6
+ }
7
+
8
+ export async function zip(from: string | string[], to?: string, options?: ZipOptions) {
9
+ const toPath = to || 'archive.zip'
10
+ const fromPath = Array.isArray(from) ? from.join(' ') : from
11
+
12
+ if (Array.isArray(from))
13
+ return runCommand(`zip -r ${toPath} ${fromPath}`, options)
14
+
15
+ return runCommand(`zip -r ${to} ${from}`, options)
16
+ }
17
+
18
+ export async function unzip(paths: string | string[]) {
19
+ if (Array.isArray(paths))
20
+ return runCommand(`unzip ${paths.join(' ')}`)
21
+
22
+ return runCommand(`unzip ${paths}`)
23
+ }
24
+
25
+ export function archive(paths: string | string[]) {
26
+ return zip(paths)
27
+ }
28
+
29
+ export function unarchive(paths: string | string[]) {
30
+ return unzip(paths)
31
+ }
32
+
33
+ export function compress(paths: string[]) {
34
+ return zip(paths)
35
+ }
36
+
37
+ export function decompress(paths: string | string[]) {
38
+ return unzip(paths)
39
+ }
40
+
41
+ export function gzipSync(data: Uint8Array, options?: ZlibCompressionOptions) {
42
+ return Bun.gzipSync(data, options) // buffer extends Uint8Array
43
+ }
44
+
45
+ export function gunzipSync(data: Uint8Array) {
46
+ return Bun.gunzipSync(data) // decompressed
47
+ }
48
+
49
+ export function deflateSync(data: Uint8Array, options?: ZlibCompressionOptions) {
50
+ return Bun.deflateSync(data, options) // compressed
51
+ }
52
+
53
+ export function inflateSync(data: Uint8Array) {
54
+ return Bun.inflateSync(data) // decompressed
55
+ }