@relesio/cli 0.2.6

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.
Files changed (75) hide show
  1. package/README.md +224 -0
  2. package/bin/relesio.js +4 -0
  3. package/dist/commands/auth/index.d.ts +2 -0
  4. package/dist/commands/auth/index.js +9 -0
  5. package/dist/commands/auth/login.d.ts +2 -0
  6. package/dist/commands/auth/login.js +82 -0
  7. package/dist/commands/auth/logout.d.ts +2 -0
  8. package/dist/commands/auth/logout.js +25 -0
  9. package/dist/commands/auth/status.d.ts +2 -0
  10. package/dist/commands/auth/status.js +35 -0
  11. package/dist/commands/deploy.d.ts +2 -0
  12. package/dist/commands/deploy.js +293 -0
  13. package/dist/commands/organizations/index.d.ts +2 -0
  14. package/dist/commands/organizations/index.js +8 -0
  15. package/dist/commands/organizations/list.d.ts +2 -0
  16. package/dist/commands/organizations/list.js +54 -0
  17. package/dist/commands/organizations/set.d.ts +2 -0
  18. package/dist/commands/organizations/set.js +34 -0
  19. package/dist/commands/projects/index.d.ts +2 -0
  20. package/dist/commands/projects/index.js +6 -0
  21. package/dist/commands/projects/list.d.ts +2 -0
  22. package/dist/commands/projects/list.js +49 -0
  23. package/dist/commands/rollback.d.ts +2 -0
  24. package/dist/commands/rollback.js +119 -0
  25. package/dist/commands/status.d.ts +2 -0
  26. package/dist/commands/status.js +59 -0
  27. package/dist/commands/team/get.d.ts +2 -0
  28. package/dist/commands/team/get.js +34 -0
  29. package/dist/commands/team/index.d.ts +2 -0
  30. package/dist/commands/team/index.js +7 -0
  31. package/dist/commands/team/list.d.ts +2 -0
  32. package/dist/commands/team/list.js +46 -0
  33. package/dist/commands/upload.d.ts +2 -0
  34. package/dist/commands/upload.js +365 -0
  35. package/dist/commands/versions/index.d.ts +2 -0
  36. package/dist/commands/versions/index.js +6 -0
  37. package/dist/commands/versions/list.d.ts +2 -0
  38. package/dist/commands/versions/list.js +51 -0
  39. package/dist/commands/whoami.d.ts +2 -0
  40. package/dist/commands/whoami.js +52 -0
  41. package/dist/index.d.ts +2 -0
  42. package/dist/index.js +90 -0
  43. package/dist/lib/api/client.d.ts +32 -0
  44. package/dist/lib/api/client.js +187 -0
  45. package/dist/lib/api/deployments.d.ts +122 -0
  46. package/dist/lib/api/deployments.js +55 -0
  47. package/dist/lib/api/organizations.d.ts +27 -0
  48. package/dist/lib/api/organizations.js +37 -0
  49. package/dist/lib/api/projects.d.ts +38 -0
  50. package/dist/lib/api/projects.js +34 -0
  51. package/dist/lib/api/teams.d.ts +15 -0
  52. package/dist/lib/api/teams.js +44 -0
  53. package/dist/lib/api/types.d.ts +89 -0
  54. package/dist/lib/api/types.js +2 -0
  55. package/dist/lib/api/versions.d.ts +140 -0
  56. package/dist/lib/api/versions.js +53 -0
  57. package/dist/lib/config/manager.d.ts +35 -0
  58. package/dist/lib/config/manager.js +78 -0
  59. package/dist/lib/config/types.d.ts +15 -0
  60. package/dist/lib/config/types.js +15 -0
  61. package/dist/lib/errors/handler.d.ts +1 -0
  62. package/dist/lib/errors/handler.js +75 -0
  63. package/dist/lib/errors/types.d.ts +19 -0
  64. package/dist/lib/errors/types.js +34 -0
  65. package/dist/lib/files/scanner.d.ts +23 -0
  66. package/dist/lib/files/scanner.js +81 -0
  67. package/dist/lib/git/metadata.d.ts +31 -0
  68. package/dist/lib/git/metadata.js +90 -0
  69. package/dist/lib/output/colors.d.ts +14 -0
  70. package/dist/lib/output/colors.js +23 -0
  71. package/dist/lib/output/formatter.d.ts +2 -0
  72. package/dist/lib/output/formatter.js +7 -0
  73. package/dist/lib/output/table.d.ts +7 -0
  74. package/dist/lib/output/table.js +64 -0
  75. package/package.json +58 -0
@@ -0,0 +1,89 @@
1
+ export interface RequestOptions {
2
+ headers?: Record<string, string>;
3
+ query?: Record<string, string | number | boolean | undefined>;
4
+ timeout?: number;
5
+ /**
6
+ * When true, return the full API envelope (including meta/message)
7
+ * instead of unwrapping the `data` field.
8
+ */
9
+ raw?: boolean;
10
+ /**
11
+ * Organization ID to set in x-organization-id header
12
+ * Overrides default org from config
13
+ */
14
+ orgId?: string;
15
+ }
16
+ export interface APIResponse<T = unknown> {
17
+ message: string;
18
+ data: T;
19
+ pagination?: {
20
+ limit: number;
21
+ offset: number;
22
+ total: number;
23
+ };
24
+ }
25
+ export interface Organization {
26
+ id: string;
27
+ name: string;
28
+ slug: string | null;
29
+ imageUrl: string;
30
+ createdAt: string;
31
+ membersCount?: number;
32
+ }
33
+ export interface CreateOrganizationData {
34
+ name: string;
35
+ slug?: string;
36
+ publicMetadata?: Record<string, unknown>;
37
+ }
38
+ export interface UpdateOrganizationData {
39
+ name?: string;
40
+ slug?: string;
41
+ publicMetadata?: Record<string, unknown>;
42
+ }
43
+ export interface UserOrganization {
44
+ id: string;
45
+ name: string;
46
+ slug: string | null;
47
+ imageUrl: string;
48
+ isCurrent: boolean;
49
+ role: string;
50
+ }
51
+ export interface UserOrganizationsResponse {
52
+ organizations: UserOrganization[];
53
+ currentOrganizationId: string | null;
54
+ }
55
+ export interface Team {
56
+ id: string;
57
+ organizationId: string;
58
+ name: string;
59
+ description: string | null;
60
+ createdAt: string;
61
+ updatedAt: string;
62
+ memberCount?: number;
63
+ }
64
+ export interface CreateTeamData {
65
+ name: string;
66
+ description?: string;
67
+ }
68
+ export interface UpdateTeamData {
69
+ name?: string;
70
+ description?: string;
71
+ }
72
+ export interface TeamMember {
73
+ id: string;
74
+ teamId: string;
75
+ userId: string;
76
+ role: string;
77
+ createdAt: string;
78
+ }
79
+ export interface AddTeamMemberData {
80
+ userId: string;
81
+ role?: "admin" | "member";
82
+ }
83
+ export interface UpdateTeamMemberData {
84
+ role: "admin" | "member";
85
+ }
86
+ export interface PaginationQuery extends Record<string, number | undefined> {
87
+ limit?: number;
88
+ offset?: number;
89
+ }
@@ -0,0 +1,2 @@
1
+ // API Request and Response Types
2
+ export {};
@@ -0,0 +1,140 @@
1
+ import type { RelesioAPIClient } from "./client.js";
2
+ import type { GitMetadata } from "../git/metadata.js";
3
+ export interface Version {
4
+ id: string;
5
+ projectId: string;
6
+ version?: string;
7
+ snapshotId?: string;
8
+ semanticVersion?: string | null;
9
+ username?: string;
10
+ uploadId?: number;
11
+ gitSha?: string;
12
+ gitBranch?: string;
13
+ gitCommitMessage?: string;
14
+ gitAuthor?: string;
15
+ gitTag?: string | null;
16
+ uploadedBy: string;
17
+ uploadedByType?: string;
18
+ uploadedByName?: string;
19
+ uploadDate: string;
20
+ fileCount: number;
21
+ totalSizeBytes: number;
22
+ r2Prefix?: string;
23
+ status: "pending" | "uploading" | "completed" | "failed";
24
+ dependencies?: unknown[] | string;
25
+ metadata?: Record<string, unknown> | string;
26
+ createdAt: string;
27
+ updatedAt: string;
28
+ }
29
+ export interface FileManifest {
30
+ path: string;
31
+ contentType: string;
32
+ size: number;
33
+ checksum: string;
34
+ }
35
+ export interface CreateVersionInput {
36
+ version: string;
37
+ fileCount: number;
38
+ totalSizeBytes: number;
39
+ files: FileManifest[];
40
+ packageName?: string;
41
+ semanticVersion?: string;
42
+ dependencies?: unknown[];
43
+ metadata?: Record<string, unknown>;
44
+ gitSha?: string;
45
+ gitBranch?: string;
46
+ gitCommitMessage?: string;
47
+ gitAuthor?: string;
48
+ gitTag?: string;
49
+ }
50
+ export interface UploadUrl {
51
+ filePath: string;
52
+ r2Key: string;
53
+ uploadUrl: string;
54
+ }
55
+ export interface CreateVersionResponse {
56
+ versionId: string;
57
+ uploadUrls: UploadUrl[];
58
+ copiedFiles?: string[];
59
+ failedCopies?: Array<{
60
+ filePath: string;
61
+ reason: string;
62
+ }>;
63
+ stats?: {
64
+ toUpload: number;
65
+ copied: number;
66
+ failed: number;
67
+ uploadSizeBytes: number;
68
+ savedBytes: number;
69
+ alreadyExists?: boolean;
70
+ existingFileCount?: number;
71
+ };
72
+ previewUrl?: string;
73
+ message: string;
74
+ }
75
+ export interface Asset {
76
+ id: string;
77
+ versionId: string;
78
+ filePath: string;
79
+ sizeBytes: number;
80
+ contentType: string;
81
+ r2Key: string;
82
+ checksum: string;
83
+ createdAt: string;
84
+ }
85
+ export interface CompleteVersionInput {
86
+ assets: Array<{
87
+ filePath: string;
88
+ sizeBytes: number;
89
+ contentType: string;
90
+ r2Key: string;
91
+ checksum: string;
92
+ }>;
93
+ copiedAssets?: string[];
94
+ }
95
+ export interface CompleteVersionResponse {
96
+ versionId: string;
97
+ previewUrl: string;
98
+ fileCount: number;
99
+ totalSize: number;
100
+ message: string;
101
+ }
102
+ /**
103
+ * Create a new version (initiate upload)
104
+ */
105
+ export declare function createVersion(client: RelesioAPIClient, projectId: string, input: CreateVersionInput): Promise<CreateVersionResponse>;
106
+ /**
107
+ * Complete version upload
108
+ */
109
+ export declare function completeVersion(client: RelesioAPIClient, projectId: string, versionId: string, input: CompleteVersionInput): Promise<CompleteVersionResponse>;
110
+ /**
111
+ * List versions for a project
112
+ */
113
+ export declare function listVersions(client: RelesioAPIClient, projectId: string): Promise<{
114
+ versions: Version[];
115
+ count: number;
116
+ }>;
117
+ /**
118
+ * Get version details
119
+ */
120
+ export declare function getVersion(client: RelesioAPIClient, projectId: string, versionId: string): Promise<{
121
+ version: Version;
122
+ }>;
123
+ /**
124
+ * List assets for a version
125
+ */
126
+ export declare function listAssets(client: RelesioAPIClient, projectId: string, versionId: string): Promise<{
127
+ assets: Asset[];
128
+ count: number;
129
+ totalSize: number;
130
+ }>;
131
+ /**
132
+ * Helper: Convert git metadata to API format
133
+ */
134
+ export declare function gitMetadataToApiFormat(metadata: GitMetadata): {
135
+ gitSha?: string;
136
+ gitBranch?: string;
137
+ gitCommitMessage?: string;
138
+ gitAuthor?: string;
139
+ gitTag?: string;
140
+ };
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Create a new version (initiate upload)
3
+ */
4
+ export async function createVersion(client, projectId, input) {
5
+ return client.post(`/v1/projects/${projectId}/versions`, input);
6
+ }
7
+ /**
8
+ * Complete version upload
9
+ */
10
+ export async function completeVersion(client, projectId, versionId, input) {
11
+ return client.post(`/v1/projects/${projectId}/versions/${versionId}/complete`, input);
12
+ }
13
+ /**
14
+ * List versions for a project
15
+ */
16
+ export async function listVersions(client, projectId) {
17
+ const response = await client.get(`/v1/projects/${projectId}/versions`, { raw: true });
18
+ return {
19
+ versions: response.data,
20
+ count: response.meta?.count ?? response.data.length
21
+ };
22
+ }
23
+ /**
24
+ * Get version details
25
+ */
26
+ export async function getVersion(client, projectId, versionId) {
27
+ const response = await client.get(`/v1/projects/${projectId}/versions/${versionId}`, { raw: true });
28
+ return { version: response.data };
29
+ }
30
+ /**
31
+ * List assets for a version
32
+ */
33
+ export async function listAssets(client, projectId, versionId) {
34
+ const response = await client.get(`/v1/projects/${projectId}/versions/${versionId}/assets`, { raw: true });
35
+ return {
36
+ assets: response.data,
37
+ count: response.meta?.count ?? response.data.length,
38
+ totalSize: response.meta?.totalSize ??
39
+ response.data.reduce((sum, a) => sum + (a.sizeBytes ?? 0), 0)
40
+ };
41
+ }
42
+ /**
43
+ * Helper: Convert git metadata to API format
44
+ */
45
+ export function gitMetadataToApiFormat(metadata) {
46
+ return {
47
+ gitSha: metadata.sha,
48
+ gitBranch: metadata.branch,
49
+ gitCommitMessage: metadata.commitMessage,
50
+ gitAuthor: metadata.author,
51
+ gitTag: metadata.tag
52
+ };
53
+ }
@@ -0,0 +1,35 @@
1
+ import type { RelesioConfig } from "./types.js";
2
+ declare class ConfigManagerClass {
3
+ private conf;
4
+ constructor();
5
+ /**
6
+ * Load the full configuration, merging environment variables with config file
7
+ */
8
+ load(): RelesioConfig;
9
+ /**
10
+ * Save partial configuration (merges with existing)
11
+ */
12
+ save(partialConfig: Partial<RelesioConfig>): void;
13
+ /**
14
+ * Get a specific config value
15
+ */
16
+ get<K extends keyof RelesioConfig>(key: K): RelesioConfig[K];
17
+ /**
18
+ * Set a specific config value
19
+ */
20
+ set<K extends keyof RelesioConfig>(key: K, value: RelesioConfig[K]): void;
21
+ /**
22
+ * Clear all configuration (logout)
23
+ */
24
+ clear(): void;
25
+ /**
26
+ * Get the config file path
27
+ */
28
+ getConfigPath(): string;
29
+ /**
30
+ * Check if user is authenticated
31
+ */
32
+ isAuthenticated(): boolean;
33
+ }
34
+ export declare const ConfigManager: ConfigManagerClass;
35
+ export {};
@@ -0,0 +1,78 @@
1
+ import Conf from "conf";
2
+ import { DEFAULT_CONFIG } from "./types.js";
3
+ class ConfigManagerClass {
4
+ conf;
5
+ constructor() {
6
+ this.conf = new Conf({
7
+ projectName: "relesio",
8
+ defaults: DEFAULT_CONFIG
9
+ });
10
+ }
11
+ /**
12
+ * Load the full configuration, merging environment variables with config file
13
+ */
14
+ load() {
15
+ const config = this.conf.store;
16
+ // Override with environment variables if present
17
+ return {
18
+ ...config,
19
+ apiToken: process.env.RELESIO_API_TOKEN || config.apiToken,
20
+ apiBaseUrl: process.env.RELESIO_API_URL || config.apiBaseUrl,
21
+ activeOrganizationId: process.env.RELESIO_ORG_ID || config.activeOrganizationId,
22
+ // Legacy field for backward compatibility
23
+ currentOrgId: process.env.RELESIO_ORG_ID || config.currentOrgId
24
+ };
25
+ }
26
+ /**
27
+ * Save partial configuration (merges with existing)
28
+ */
29
+ save(partialConfig) {
30
+ for (const [key, value] of Object.entries(partialConfig)) {
31
+ this.conf.set(key, value);
32
+ }
33
+ }
34
+ /**
35
+ * Get a specific config value
36
+ */
37
+ get(key) {
38
+ // Check environment variable first
39
+ if (key === "apiToken" && process.env.RELESIO_API_TOKEN) {
40
+ return process.env.RELESIO_API_TOKEN;
41
+ }
42
+ if (key === "apiBaseUrl" && process.env.RELESIO_API_URL) {
43
+ return process.env.RELESIO_API_URL;
44
+ }
45
+ if ((key === "activeOrganizationId" || key === "currentOrgId") &&
46
+ process.env.RELESIO_ORG_ID) {
47
+ return process.env.RELESIO_ORG_ID;
48
+ }
49
+ return this.conf.get(key);
50
+ }
51
+ /**
52
+ * Set a specific config value
53
+ */
54
+ set(key, value) {
55
+ this.conf.set(key, value);
56
+ }
57
+ /**
58
+ * Clear all configuration (logout)
59
+ */
60
+ clear() {
61
+ this.conf.clear();
62
+ }
63
+ /**
64
+ * Get the config file path
65
+ */
66
+ getConfigPath() {
67
+ return this.conf.path;
68
+ }
69
+ /**
70
+ * Check if user is authenticated
71
+ */
72
+ isAuthenticated() {
73
+ const token = this.get("apiToken");
74
+ return token !== null && token !== "";
75
+ }
76
+ }
77
+ // Export singleton instance
78
+ export const ConfigManager = new ConfigManagerClass();
@@ -0,0 +1,15 @@
1
+ export interface RelesioConfig {
2
+ apiToken: string | null;
3
+ apiBaseUrl: string;
4
+ userId: string | null;
5
+ userEmail: string | null;
6
+ userName: string | null;
7
+ activeOrganizationId: string | null;
8
+ activeOrganizationName: string | null;
9
+ activeOrganizationSlug: string | null;
10
+ currentOrgId: string | null;
11
+ currentOrgName: string | null;
12
+ output: "json" | "table";
13
+ colorOutput: boolean;
14
+ }
15
+ export declare const DEFAULT_CONFIG: RelesioConfig;
@@ -0,0 +1,15 @@
1
+ export const DEFAULT_CONFIG = {
2
+ apiToken: null,
3
+ apiBaseUrl: "https://api.relesio.com",
4
+ userId: null,
5
+ userEmail: null,
6
+ userName: null,
7
+ activeOrganizationId: null,
8
+ activeOrganizationName: null,
9
+ activeOrganizationSlug: null,
10
+ // Legacy fields
11
+ currentOrgId: null,
12
+ currentOrgName: null,
13
+ output: "table",
14
+ colorOutput: true
15
+ };
@@ -0,0 +1 @@
1
+ export declare function handleError(error: unknown): never;
@@ -0,0 +1,75 @@
1
+ import chalk from "chalk";
2
+ import { AuthenticationError, APIError, ConfigError, ValidationError } from "./types.js";
3
+ import { APIError as ClientAPIError } from "../api/client.js";
4
+ export function handleError(error) {
5
+ // Convert non-Error values to Error
6
+ if (!(error instanceof Error)) {
7
+ const stringError = typeof error === "string" ? error : JSON.stringify(error);
8
+ error = new Error(stringError);
9
+ }
10
+ // Authentication errors
11
+ if (error instanceof AuthenticationError) {
12
+ console.error(chalk.red("\n✗ Authentication Error"));
13
+ console.error(chalk.dim(error.message));
14
+ console.error(chalk.yellow("\nTip: Run 'relesio auth login' to authenticate"));
15
+ process.exit(1);
16
+ }
17
+ // API errors (from client)
18
+ if (error instanceof ClientAPIError || error instanceof APIError) {
19
+ const apiError = error;
20
+ console.error(chalk.red(`\n✗ API Error (${apiError.statusCode})`));
21
+ console.error(chalk.dim(apiError.message));
22
+ // Show response details in debug mode
23
+ if (process.env.DEBUG) {
24
+ console.error(chalk.dim("\n[DEBUG] Full Error Details:"));
25
+ console.error(chalk.dim(JSON.stringify({
26
+ message: apiError.message,
27
+ statusCode: apiError.statusCode,
28
+ response: apiError.response,
29
+ stack: apiError.stack
30
+ }, null, 2)));
31
+ }
32
+ // Specific status code handling
33
+ if (apiError.statusCode === 401) {
34
+ console.error(chalk.yellow("\nYour authentication may have expired. Try running 'relesio auth login'"));
35
+ }
36
+ else if (apiError.statusCode === 403) {
37
+ console.error(chalk.yellow("\nYou don't have permission to perform this action"));
38
+ }
39
+ else if (apiError.statusCode === 404) {
40
+ console.error(chalk.yellow("\nThe requested resource was not found"));
41
+ }
42
+ else if (apiError.statusCode === 0) {
43
+ console.error(chalk.yellow("\nNetwork error. Check your internet connection and try again"));
44
+ }
45
+ process.exit(1);
46
+ }
47
+ // Config errors
48
+ if (error instanceof ConfigError) {
49
+ console.error(chalk.red("\n✗ Configuration Error"));
50
+ console.error(chalk.dim(error.message));
51
+ process.exit(1);
52
+ }
53
+ // Validation errors
54
+ if (error instanceof ValidationError) {
55
+ console.error(chalk.red("\n✗ Validation Error"));
56
+ console.error(chalk.dim(error.message));
57
+ if (error.details) {
58
+ console.error(chalk.dim("\nDetails:"));
59
+ console.error(chalk.dim(JSON.stringify(error.details, null, 2)));
60
+ }
61
+ process.exit(1);
62
+ }
63
+ // Unknown errors (at this point, error is guaranteed to be an Error instance)
64
+ const err = error;
65
+ console.error(chalk.red("\n✗ Unexpected Error"));
66
+ console.error(chalk.dim(err.message));
67
+ if (process.env.DEBUG && err.stack) {
68
+ console.error(chalk.dim("\nStack trace:"));
69
+ console.error(chalk.dim(err.stack));
70
+ }
71
+ else {
72
+ console.error(chalk.dim("\nRun with DEBUG=1 environment variable for more details"));
73
+ }
74
+ process.exit(1);
75
+ }
@@ -0,0 +1,19 @@
1
+ export declare class RelesioError extends Error {
2
+ code: string;
3
+ constructor(message: string, code: string);
4
+ }
5
+ export declare class AuthenticationError extends RelesioError {
6
+ constructor(message?: string);
7
+ }
8
+ export declare class APIError extends RelesioError {
9
+ statusCode: number;
10
+ response?: unknown | undefined;
11
+ constructor(message: string, statusCode: number, response?: unknown | undefined);
12
+ }
13
+ export declare class ConfigError extends RelesioError {
14
+ constructor(message: string);
15
+ }
16
+ export declare class ValidationError extends RelesioError {
17
+ details?: unknown | undefined;
18
+ constructor(message: string, details?: unknown | undefined);
19
+ }
@@ -0,0 +1,34 @@
1
+ export class RelesioError extends Error {
2
+ code;
3
+ constructor(message, code) {
4
+ super(message);
5
+ this.code = code;
6
+ this.name = "RelesioError";
7
+ }
8
+ }
9
+ export class AuthenticationError extends RelesioError {
10
+ constructor(message = "Not authenticated. Run 'relesio auth login' first") {
11
+ super(message, "AUTH_ERROR");
12
+ }
13
+ }
14
+ export class APIError extends RelesioError {
15
+ statusCode;
16
+ response;
17
+ constructor(message, statusCode, response) {
18
+ super(message, "API_ERROR");
19
+ this.statusCode = statusCode;
20
+ this.response = response;
21
+ }
22
+ }
23
+ export class ConfigError extends RelesioError {
24
+ constructor(message) {
25
+ super(message, "CONFIG_ERROR");
26
+ }
27
+ }
28
+ export class ValidationError extends RelesioError {
29
+ details;
30
+ constructor(message, details) {
31
+ super(message, "VALIDATION_ERROR");
32
+ this.details = details;
33
+ }
34
+ }
@@ -0,0 +1,23 @@
1
+ export interface FileInfo {
2
+ path: string;
3
+ absolutePath: string;
4
+ size: number;
5
+ contentType: string;
6
+ checksum: string;
7
+ }
8
+ /**
9
+ * Scan directory recursively and collect file information
10
+ */
11
+ export declare function scanDirectory(directoryPath: string, baseDir?: string): Promise<FileInfo[]>;
12
+ /**
13
+ * Calculate SHA-256 checksum for a file
14
+ */
15
+ export declare function calculateChecksum(filePath: string): Promise<string>;
16
+ /**
17
+ * Format file size for display
18
+ */
19
+ export declare function formatFileSize(bytes: number): string;
20
+ /**
21
+ * Get total size of files
22
+ */
23
+ export declare function getTotalSize(files: FileInfo[]): number;
@@ -0,0 +1,81 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ import * as crypto from "node:crypto";
4
+ import { lookup as mimeTypeLookup } from "mime-types";
5
+ /**
6
+ * Scan directory recursively and collect file information
7
+ */
8
+ export async function scanDirectory(directoryPath, baseDir) {
9
+ const resolvedPath = path.resolve(directoryPath);
10
+ const base = baseDir || resolvedPath;
11
+ if (!fs.existsSync(resolvedPath)) {
12
+ throw new Error(`Directory does not exist: ${directoryPath}`);
13
+ }
14
+ const stats = fs.statSync(resolvedPath);
15
+ if (!stats.isDirectory()) {
16
+ throw new Error(`Not a directory: ${directoryPath}`);
17
+ }
18
+ const files = [];
19
+ const entries = fs.readdirSync(resolvedPath);
20
+ for (const entry of entries) {
21
+ const fullPath = path.join(resolvedPath, entry);
22
+ const entryStat = fs.statSync(fullPath);
23
+ // Skip hidden files and common directories to ignore
24
+ if (entry.startsWith(".") ||
25
+ entry === "node_modules" ||
26
+ entry === "dist" ||
27
+ entry === "__tests__") {
28
+ continue;
29
+ }
30
+ if (entryStat.isDirectory()) {
31
+ // Recursively scan subdirectory
32
+ const subFiles = await scanDirectory(fullPath, base);
33
+ files.push(...subFiles);
34
+ }
35
+ else if (entryStat.isFile()) {
36
+ // Calc relative path from base directory
37
+ const relativePath = path.relative(base, fullPath);
38
+ // Get content type
39
+ const contentType = mimeTypeLookup(fullPath) || "application/octet-stream";
40
+ // Calculate checksum
41
+ const checksum = await calculateChecksum(fullPath);
42
+ files.push({
43
+ path: relativePath.replace(/\\/g, "/"), // Normalize to forward slashes
44
+ absolutePath: fullPath,
45
+ size: entryStat.size,
46
+ contentType,
47
+ checksum
48
+ });
49
+ }
50
+ }
51
+ return files;
52
+ }
53
+ /**
54
+ * Calculate SHA-256 checksum for a file
55
+ */
56
+ export async function calculateChecksum(filePath) {
57
+ return new Promise((resolve, reject) => {
58
+ const hash = crypto.createHash("sha256");
59
+ const stream = fs.createReadStream(filePath);
60
+ stream.on("data", (data) => hash.update(data));
61
+ stream.on("end", () => resolve(hash.digest("hex")));
62
+ stream.on("error", reject);
63
+ });
64
+ }
65
+ /**
66
+ * Format file size for display
67
+ */
68
+ export function formatFileSize(bytes) {
69
+ if (bytes === 0)
70
+ return "0 B";
71
+ const units = ["B", "KB", "MB", "GB", "TB"];
72
+ const k = 1024;
73
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
74
+ return `${(bytes / k ** i).toFixed(2)} ${units[i]}`;
75
+ }
76
+ /**
77
+ * Get total size of files
78
+ */
79
+ export function getTotalSize(files) {
80
+ return files.reduce((sum, file) => sum + file.size, 0);
81
+ }