Skip to content

Commit

Permalink
Merge branch 'main' into 585-implement-tests-for-the-http-package
Browse files Browse the repository at this point in the history
  • Loading branch information
basmasking committed Dec 31, 2024
2 parents 2cf17f4 + 6e91047 commit 7be4cad
Show file tree
Hide file tree
Showing 19 changed files with 443 additions and 164 deletions.
9 changes: 4 additions & 5 deletions packages/build/src/BuildManager.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@

import type { RuntimeConfiguration } from '@jitar/configuration';
import { Logger, LogLevel } from '@jitar/logging';
import type { FileManager } from '@jitar/sourcing';
import { Files, LocalFileManager } from '@jitar/sourcing';
import { Files, FileManager } from '@jitar/sourcing';

import { ApplicationReader } from './source';
import { ApplicationBuilder } from './target';
Expand All @@ -22,9 +21,9 @@ export default class BuildManager
{
this.#logger = new Logger(logLevel);

this.#projectFileManager = new LocalFileManager('./');
this.#sourceFileManager = new LocalFileManager(configuration.source);
this.#targetFileManager = new LocalFileManager(configuration.target);
this.#projectFileManager = new FileManager('./');
this.#sourceFileManager = new FileManager(configuration.source);
this.#targetFileManager = new FileManager(configuration.target);

this.#applicationReader = new ApplicationReader(this.#sourceFileManager);
this.#applicationBuilder = new ApplicationBuilder(this.#targetFileManager, this.#logger);
Expand Down
4 changes: 2 additions & 2 deletions packages/cli/src/commands/StartServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { ConfigurationManager, RuntimeConfiguration, ServerConfiguration } from
import { HttpRemoteBuilder, HttpServer } from '@jitar/http';
import { LogLevel, LogLevelParser } from '@jitar/logging';
import { ServerBuilder } from '@jitar/runtime';
import { LocalFileManager, SourcingManager } from '@jitar/sourcing';
import { FileManager, SourcingManager } from '@jitar/sourcing';

import ArgumentProcessor from '../ArgumentProcessor';
import Command from '../Command';
Expand Down Expand Up @@ -49,7 +49,7 @@ export default class StartServer implements Command
{
const [, , port] = serverConfiguration.url.split(':');

const fileManager = new LocalFileManager(runtimeConfiguration.target);
const fileManager = new FileManager(runtimeConfiguration.target);
const sourcingManager = new SourcingManager(fileManager);
const remoteBuilder = new HttpRemoteBuilder();
const serverBuilder = new ServerBuilder(sourcingManager, remoteBuilder);
Expand Down
4 changes: 2 additions & 2 deletions packages/configuration/src/ConfigurationManager.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@

import { Validator } from '@jitar/validation';
import { LocalFileManager } from '@jitar/sourcing';
import { FileManager } from '@jitar/sourcing';

import { EnvironmentConfigurator } from './environment';
import { RuntimeConfiguration, RuntimeConfigurationBuilder } from './runtime';
Expand All @@ -18,7 +18,7 @@ export default class ConfigurationManager

constructor(rootPath: string = DEFAULT_ROOT_PATH)
{
const fileManager = new LocalFileManager(rootPath);
const fileManager = new FileManager(rootPath);
const reader = new ConfigurationReader(fileManager);
const validator = new Validator();

Expand Down
123 changes: 123 additions & 0 deletions packages/sourcing/src/FileManager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@

import InvalidLocation from './errors/InvalidLocation';
import FileNotFound from './errors/FileNotFound';

import FileSystem from './interfaces/FileSystem';

import File from './models/File';

import LocalFileSystem from './LocalFileSystem';

const DEFAULT_MIME_TYPE = 'application/octet-stream';

export default class FileManager
{
readonly #location: string;
readonly #rootLocation: string;
readonly #fileSystem: FileSystem;

constructor(location: string, fileSystem = new LocalFileSystem())
{
this.#location = location;
this.#fileSystem = fileSystem;
this.#rootLocation = fileSystem.resolve(location);
}

// This method must be used by every function that needs to access
// the file system. This ensures that the path is always validated
// and prevents access to files outside of the base location.
getAbsoluteLocation(filename: string): string
{
const location = filename.startsWith('/') ? filename : this.#fileSystem.join(this.#location, filename);
const absolutePath = this.#fileSystem.resolve(location);

this.#validateLocation(absolutePath, filename);

return absolutePath;
}

getRelativeLocation(filename: string): string
{
return this.#fileSystem.relative(this.#location, filename);
}

async getType(filename: string): Promise<string>
{
const location = this.getAbsoluteLocation(filename);
const type = await this.#fileSystem.mimeType(location);

return type ?? DEFAULT_MIME_TYPE;
}

async getContent(filename: string): Promise<Buffer>
{
const location = this.getAbsoluteLocation(filename);
const exists = await this.#fileSystem.exists(location);

if (exists === false)
{
// Do NOT use the location in the error message,
// as it may contain sensitive information.
throw new FileNotFound(filename);
}

return this.#fileSystem.read(location);
}

async exists(filename: string): Promise<boolean>
{
const location = this.getAbsoluteLocation(filename);

return this.#fileSystem.exists(location);
}

async read(filename: string): Promise<File>
{
const absoluteFilename = this.getAbsoluteLocation(filename);

const type = await this.getType(absoluteFilename);
const content = await this.getContent(absoluteFilename);

return new File(filename, type, content);
}

async write(filename: string, content: string): Promise<void>
{
const location = this.getAbsoluteLocation(filename);

return this.#fileSystem.write(location, content);
}

async copy(source: string, destination: string): Promise<void>
{
const sourceLocation = this.getAbsoluteLocation(source);
const destinationLocation = this.getAbsoluteLocation(destination);

return this.#fileSystem.copy(sourceLocation, destinationLocation);
}

async delete(filename: string): Promise<void>
{
const location = this.getAbsoluteLocation(filename);

return this.#fileSystem.delete(location);
}

async filter(pattern: string): Promise<string[]>
{
const location = this.getAbsoluteLocation('./');

return this.#fileSystem.filter(location, pattern);
}

#validateLocation(location: string, filename: string): void
{
if (location.startsWith(this.#rootLocation) === false)
{
// The filename is only needed for the error message. This
// ensures that the error message does not contain sensitive
// information.
throw new InvalidLocation(filename);
}
}
}
112 changes: 0 additions & 112 deletions packages/sourcing/src/LocalFileManager.ts

This file was deleted.

64 changes: 64 additions & 0 deletions packages/sourcing/src/LocalFileSystem.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@

import fs from 'fs-extra';
import { glob } from 'glob';
import mime from 'mime-types';
import path from 'path';

import FileSystem from './interfaces/FileSystem';

export default class LocalFileSystem implements FileSystem
{
copy(source: string, destination: string): Promise<void>
{
return fs.copy(source, destination, { overwrite: true });
}

delete(location: string): Promise<void>
{
return fs.remove(location);
}

exists(location: string): Promise<boolean>
{
return fs.exists(location);
}

filter(location: string, pattern: string): Promise<string[]>
{
return glob(`${location}/${pattern}`);
}

join(...paths: string[]): string
{
return path.join(...paths);
}

read(location: string): Promise<Buffer>
{
return fs.readFile(location);
}

resolve(location: string): string
{
return path.resolve(location);
}

relative(from: string, to: string): string
{
return path.relative(from, to);
}

async mimeType(location: string): Promise<string | undefined>
{
return mime.lookup(location) || undefined;
}

async write(location: string, content: string): Promise<void>
{
const directory = path.dirname(location);

fs.mkdirSync(directory, { recursive: true });

return fs.writeFile(location, content);
}
}
4 changes: 2 additions & 2 deletions packages/sourcing/src/SourcingManager.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@

import type File from './models/File';
import type FileManager from './interfaces/FileManager';
import ModuleNotLoaded from './errors/ModuleNotLoaded';
import type Module from './types/Module';
import type FileManager from './FileManager';

export default class SourceManager
export default class SourcingManager
{
readonly #fileManager: FileManager;

Expand Down
14 changes: 14 additions & 0 deletions packages/sourcing/src/errors/InvalidLocation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@

export default class InvalidPath extends Error
{
readonly #location: string;

constructor(location: string)
{
super(`Invalid location: ${location}`);

this.#location = location;
}

get location() { return this.#location; }
}
Loading

0 comments on commit 7be4cad

Please sign in to comment.