Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@ui5/fs

Package Overview
Dependencies
Maintainers
3
Versions
43
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@ui5/fs - npm Package Compare versions

Comparing version 3.0.0-rc.2 to 3.0.0-rc.3

11

CHANGELOG.md

@@ -5,6 +5,12 @@ # Changelog

A list of unreleased changes can be found [here](https://github.com/SAP/ui5-fs/compare/v3.0.0-rc.2...HEAD).
A list of unreleased changes can be found [here](https://github.com/SAP/ui5-fs/compare/v3.0.0-rc.3...HEAD).
<a name="v3.0.0-rc.3"></a>
## [v3.0.0-rc.3] - 2023-01-20
### Features
- **Resource:** Add isModified method [`f6a590a`](https://github.com/SAP/ui5-fs/commit/f6a590a284a5ef2879d4d755b5b37be164cf3a45)
<a name="v3.0.0-rc.2"></a>
## [v3.0.0-rc.2] - 2023-01-15
## [v3.0.0-rc.2] - 2023-01-19
### Dependency Updates

@@ -226,2 +232,3 @@ - Bump minimatch from 5.1.4 to 6.1.5 [`e6b8d14`](https://github.com/SAP/ui5-fs/commit/e6b8d142517a19b138dab5fc19390ed98db425e1)

[v3.0.0-rc.3]: https://github.com/SAP/ui5-fs/compare/v3.0.0-rc.2...v3.0.0-rc.3
[v3.0.0-rc.2]: https://github.com/SAP/ui5-fs/compare/v3.0.0-rc.1...v3.0.0-rc.2

@@ -228,0 +235,0 @@ [v3.0.0-rc.1]: https://github.com/SAP/ui5-fs/compare/v3.0.0-rc.0...v3.0.0-rc.1

@@ -188,3 +188,7 @@ import logger from "@ui5/logger";

async _migrateResource(resource) {
_migrateResource(resource) {
// This function only returns a promise if a migration is necessary.
// Since this is rarely the case, we therefore reduce the amount of
// created Promises by making this differentiation
// Check if its a fs/Resource v3, function 'hasProject' was

@@ -195,3 +199,6 @@ // introduced with v3 therefore take it as the indicator

}
return this._createFromLegacyResource(resource);
}
async _createFromLegacyResource(resource) {
const options = {

@@ -198,0 +205,0 @@ path: resource._path,

86

lib/adapters/FileSystem.js

@@ -15,3 +15,3 @@ import logger from "@ui5/logger";

const READ_ONLY_MODE = 0o444;
const ADAPTER_NAME = "FileSystem";
/**

@@ -74,4 +74,4 @@ * File system resource adapter

path: this._virBaseDir,
source: {
adapter: "FileSystem",
sourceMetadata: {
adapter: ADAPTER_NAME,
fsPath: this._fsBasePath

@@ -111,4 +111,4 @@ },

path: virPath,
source: {
adapter: "FileSystem",
sourceMetadata: {
adapter: ADAPTER_NAME,
fsPath: fsPath

@@ -188,4 +188,4 @@ },

path: virPath,
source: {
adapter: "FileSystem",
sourceMetadata: {
adapter: ADAPTER_NAME,
fsPath

@@ -231,3 +231,8 @@ }

async _write(resource, {drain, readOnly}) {
resource = await this._migrateResource(resource);
resource = this._migrateResource(resource);
if (resource instanceof Promise) {
// Only await if the migrate function returned a promise
// Otherwise await would automatically create a Promise, causing unwanted overhead
resource = await resource;
}
super._write(resource);

@@ -245,16 +250,33 @@ if (drain && readOnly) {

const resourceSource = resource.getSource();
if (!resourceSource.modified && resourceSource.adapter === "FileSystem" && resourceSource.fsPath) {
// fs.copyFile can be used when the resource is from FS and hasn't been modified
// In addition, nothing needs to be done when src === dest
if (resourceSource.fsPath === fsPath) {
log.silly(`Skip writing to ${fsPath} (Resource hasn't been modified)`);
} else {
log.silly(`Copying resource from ${resourceSource.fsPath} to ${fsPath}`);
await copyFile(resourceSource.fsPath, fsPath);
const sourceMetadata = resource.getSourceMetadata();
if (sourceMetadata && sourceMetadata.adapter === ADAPTER_NAME && sourceMetadata.fsPath) {
// Resource has been created by FileSystem adapter. This means it might require special handling
/* The following code covers these four conditions:
1. FS-paths not equal + Resource not modified => Shortcut: Use fs.copyFile
2. FS-paths equal + Resource not modified => Shortcut: Skip write altogether
3. FS-paths equal + Resource modified => Drain stream into buffer. Later write from buffer as usual
4. FS-paths not equal + Resource modified => No special handling. Write from stream or buffer
*/
if (sourceMetadata.fsPath !== fsPath && !sourceMetadata.contentModified) {
// Shortcut: fs.copyFile can be used when the resource hasn't been modified
log.silly(`Resource hasn't been modified. Copying resource from ${sourceMetadata.fsPath} to ${fsPath}`);
await copyFile(sourceMetadata.fsPath, fsPath);
if (readOnly) {
await chmod(fsPath, READ_ONLY_MODE);
}
}
return;
return;
} else if (sourceMetadata.fsPath === fsPath && !sourceMetadata.contentModified) {
log.silly(
`Resource hasn't been modified, target path equals source path. Skipping write to ${fsPath}`);
if (readOnly) {
await chmod(fsPath, READ_ONLY_MODE);
}
return;
} else if (sourceMetadata.fsPath === fsPath && sourceMetadata.contentModified) {
// Resource has been modified. Make sure all streams are drained to prevent
// issues caused by piping the original read-stream into a write-stream for the same path
await resource.getBuffer();
} else {/* Different paths + modifications require no special handling */}
}

@@ -264,6 +286,6 @@

return new Promise((resolve, reject) => {
await new Promise((resolve, reject) => {
let contentStream;
if ((drain || readOnly) && resourceSource.fsPath !== fsPath) {
if (drain || readOnly) {
// Stream will be drained

@@ -302,8 +324,2 @@ contentStream = resource.getStream();

write.on("close", (ex) => {
if (readOnly) {
// Create new stream from written file
resource.setStream(function() {
return fs.createReadStream(fsPath);
});
}
resolve();

@@ -313,2 +329,18 @@ });

});
if (readOnly) {
if (sourceMetadata?.fsPath === fsPath) {
// When streaming into the same file, permissions need to be changed explicitly
await chmod(fsPath, READ_ONLY_MODE);
}
// In case of readOnly, we drained the stream and can now set a new callback
// for creating a stream from written file
// This should be identical to buffering the resource content in memory, since the written file
// can not be modified.
// We chose this approach to be more memory efficient in scenarios where readOnly is used
resource.setStream(function() {
return fs.createReadStream(fsPath);
});
}
}

@@ -315,0 +347,0 @@ }

@@ -6,2 +6,4 @@ import logger from "@ui5/logger";

const ADAPTER_NAME = "Memory";
/**

@@ -80,4 +82,4 @@ * Virtual resource Adapter

},
source: {
adapter: "Memory"
sourceMetadata: {
adapter: ADAPTER_NAME
},

@@ -137,3 +139,8 @@ path: this._virBasePath.slice(0, -1)

async _write(resource) {
resource = await this._migrateResource(resource);
resource = this._migrateResource(resource);
if (resource instanceof Promise) {
// Only await if the migrate function returned a promise
// Otherwise await would automatically create a Promise, causing unwanted overhead
resource = await resource;
}
super._write(resource);

@@ -161,4 +168,4 @@ const relPath = resource.getPath().substr(this._virBasePath.length);

project: this._project,
source: {
adapter: "Memory"
sourceMetadata: {
adapter: ADAPTER_NAME
},

@@ -165,0 +172,0 @@ statInfo: { // TODO: make closer to fs stat info

@@ -9,3 +9,3 @@ import stream from "node:stream";

/**
* Resource
* Resource. UI5 Tooling specific representation of a file's content and metadata
*

@@ -17,2 +17,16 @@ * @public

class Resource {
#project;
#buffer;
#buffering;
#collections;
#contentDrained;
#createStream;
#name;
#path;
#sourceMetadata;
#statInfo;
#stream;
#streamDrained;
#isModified;
/**

@@ -27,3 +41,2 @@ * Function for dynamic creation of content streams

/**
* The constructor.
*

@@ -46,5 +59,6 @@ * @public

* @param {@ui5/project/specifications/Project} [parameters.project] Project this resource is associated with
* @param {object} [parameters.source] Experimental, internal parameter. Do not use
* @param {object} [parameters.sourceMetadata] Source metadata for UI5 Tooling internal use.
* Typically set by an adapter to store information for later retrieval.
*/
constructor({path, statInfo, buffer, string, createStream, stream, project, source}) {
constructor({path, statInfo, buffer, string, createStream, stream, project, sourceMetadata}) {
if (!path) {

@@ -59,13 +73,18 @@ throw new Error("Cannot create Resource: path parameter missing");

this._path = path;
this._name = Resource._getNameFromPath(path);
this.#path = path;
this.#name = Resource._getNameFromPath(path);
this._source = source; // Experimental, internal parameter
if (this._source) {
// Indicator for adapters like FileSystem to detect whether a resource has been changed
this._source.modified = this._source.modified || false;
this.#sourceMetadata = sourceMetadata;
if (this.#sourceMetadata) {
// This flag indicates whether a resource has changed from its original source.
// resource.isModified() is not sufficient, since it only reflects the modification state of the
// current instance.
// Since the sourceMetadata object is inherited to clones, it is the only correct indicator
this.#sourceMetadata.contentModified = this.#sourceMetadata.contentModified || false;
}
this.__project = project; // Two underscores since "_project" was widely used in UI5 Tooling 2.0
this.#isModified = false;
this._statInfo = statInfo || { // TODO
this.#project = project;
this.#statInfo = statInfo || { // TODO
isFile: fnTrue,

@@ -89,13 +108,15 @@ isDirectory: fnFalse,

if (createStream) {
this._createStream = createStream;
this.#createStream = createStream;
} else if (stream) {
this._stream = stream;
this.#stream = stream;
} else if (buffer) {
this.setBuffer(buffer);
// Use private setter, not to accidentally set any modified flags
this.#setBuffer(buffer);
} else if (typeof string === "string" || string instanceof String) {
this.setString(string);
// Use private setter, not to accidentally set any modified flags
this.#setBuffer(Buffer.from(string, "utf8"));
}
// Tracing:
this._collections = [];
this.#collections = [];
}

@@ -114,13 +135,13 @@

async getBuffer() {
if (this._contentDrained) {
throw new Error(`Content of Resource ${this._path} has been drained. ` +
if (this.#contentDrained) {
throw new Error(`Content of Resource ${this.#path} has been drained. ` +
"This might be caused by requesting resource content after a content stream has been " +
"requested and no new content (e.g. a new stream) has been set.");
}
if (this._buffer) {
return this._buffer;
} else if (this._createStream || this._stream) {
return this._getBufferFromStream();
if (this.#buffer) {
return this.#buffer;
} else if (this.#createStream || this.#stream) {
return this.#getBufferFromStream();
} else {
throw new Error(`Resource ${this._path} has no content`);
throw new Error(`Resource ${this.#path} has no content`);
}

@@ -136,13 +157,18 @@ }

setBuffer(buffer) {
if (this._source && !this._source.modified) {
this._source.modified = true;
if (this.#sourceMetadata) {
this.#sourceMetadata.contentModified = true;
}
this._createStream = null;
// if (this._stream) { // TODO this may cause strange issues
// this._stream.destroy();
this.#isModified = true;
this.#setBuffer(buffer);
}
#setBuffer(buffer) {
this.#createStream = null;
// if (this.#stream) { // TODO this may cause strange issues
// this.#stream.destroy();
// }
this._stream = null;
this._buffer = buffer;
this._contentDrained = false;
this._streamDrained = false;
this.#stream = null;
this.#buffer = buffer;
this.#contentDrained = false;
this.#streamDrained = false;
}

@@ -157,4 +183,4 @@

getString() {
if (this._contentDrained) {
return Promise.reject(new Error(`Content of Resource ${this._path} has been drained. ` +
if (this.#contentDrained) {
return Promise.reject(new Error(`Content of Resource ${this.#path} has been drained. ` +
"This might be caused by requesting resource content after a content stream has been " +

@@ -188,4 +214,4 @@ "requested and no new content (e.g. a new stream) has been set."));

getStream() {
if (this._contentDrained) {
throw new Error(`Content of Resource ${this._path} has been drained. ` +
if (this.#contentDrained) {
throw new Error(`Content of Resource ${this.#path} has been drained. ` +
"This might be caused by requesting resource content after a content stream has been " +

@@ -195,11 +221,11 @@ "requested and no new content (e.g. a new stream) has been set.");

let contentStream;
if (this._buffer) {
if (this.#buffer) {
const bufferStream = new stream.PassThrough();
bufferStream.end(this._buffer);
bufferStream.end(this.#buffer);
contentStream = bufferStream;
} else if (this._createStream || this._stream) {
contentStream = this._getStream();
} else if (this.#createStream || this.#stream) {
contentStream = this.#getStream();
}
if (!contentStream) {
throw new Error(`Resource ${this._path} has no content`);
throw new Error(`Resource ${this.#path} has no content`);
}

@@ -214,3 +240,3 @@ // If a stream instance is being returned, it will typically get drained be the consumer.

// createStream callback is being used.
this._contentDrained = true;
this.#contentDrained = true;
return contentStream;

@@ -227,18 +253,20 @@ }

setStream(stream) {
if (this._source && !this._source.modified) {
this._source.modified = true;
this.#isModified = true;
if (this.#sourceMetadata) {
this.#sourceMetadata.contentModified = true;
}
this._buffer = null;
// if (this._stream) { // TODO this may cause strange issues
// this._stream.destroy();
this.#buffer = null;
// if (this.#stream) { // TODO this may cause strange issues
// this.#stream.destroy();
// }
if (typeof stream === "function") {
this._createStream = stream;
this._stream = null;
this.#createStream = stream;
this.#stream = null;
} else {
this._stream = stream;
this._createStream = null;
this.#stream = stream;
this.#createStream = null;
}
this._contentDrained = false;
this._streamDrained = false;
this.#contentDrained = false;
this.#streamDrained = false;
}

@@ -253,3 +281,3 @@

getPath() {
return this._path;
return this.#path;
}

@@ -264,4 +292,4 @@

setPath(path) {
this._path = path;
this._name = Resource._getNameFromPath(path);
this.#path = path;
this.#name = Resource._getNameFromPath(path);
}

@@ -276,3 +304,3 @@

getName() {
return this._name;
return this.#name;
}

@@ -291,3 +319,3 @@

getStatInfo() {
return this._statInfo;
return this.#statInfo;
}

@@ -303,3 +331,3 @@

// if resource does not have any content it should have 0 bytes
if (!this._buffer && !this._createStream && !this._stream) {
if (!this.#buffer && !this.#createStream && !this.#stream) {
return 0;

@@ -317,3 +345,3 @@ }

pushCollection(name) {
this._collections.push(name);
this.#collections.push(name);
}

@@ -328,19 +356,19 @@

async clone() {
const options = await this._getCloneOptions();
const options = await this.#getCloneOptions();
return new Resource(options);
}
async _getCloneOptions() {
async #getCloneOptions() {
const options = {
path: this._path,
statInfo: clone(this._statInfo),
source: clone(this._source)
path: this.#path,
statInfo: clone(this.#statInfo),
sourceMetadata: clone(this.#sourceMetadata)
};
if (this._stream) {
options.buffer = await this._getBufferFromStream();
} else if (this._createStream) {
options.createStream = this._createStream;
} else if (this._buffer) {
options.buffer = this._buffer;
if (this.#stream) {
options.buffer = await this.#getBufferFromStream();
} else if (this.#createStream) {
options.createStream = this.#createStream;
} else if (this.#buffer) {
options.buffer = this.#buffer;
}

@@ -365,3 +393,3 @@

getProject() {
return this.__project;
return this.#project;
}

@@ -376,7 +404,7 @@

setProject(project) {
if (this.__project) {
throw new Error(`Unable to assign project ${project.getName()} to resource ${this._path}: ` +
`Resource is already associated to project ${this.__project}`);
if (this.#project) {
throw new Error(`Unable to assign project ${project.getName()} to resource ${this.#path}: ` +
`Resource is already associated to project ${this.#project}`);
}
this.__project = project;
this.#project = project;
}

@@ -391,6 +419,16 @@

hasProject() {
return !!this.__project;
return !!this.#project;
}
/**
* Check whether the content of this resource has been changed during its life cycle
*
* @public
* @returns {boolean} True if the resource's content has been changed
*/
isModified() {
return this.#isModified;
}
/**
* Tracing: Get tree for printing out trace

@@ -403,6 +441,6 @@ *

let pointer = tree[this._path] = Object.create(null);
let pointer = tree[this.#path] = Object.create(null);
for (let i = this._collections.length - 1; i >= 0; i--) {
pointer = pointer[this._collections[i]] = Object.create(null);
for (let i = this.#collections.length - 1; i >= 0; i--) {
pointer = pointer[this.#collections[i]] = Object.create(null);
}

@@ -413,4 +451,10 @@

getSource() {
return this._source || {};
/**
* Returns source metadata if any where provided during the creation of this resource.
* Typically set by an adapter to store information for later retrieval.
*
* @returns {object|null}
*/
getSourceMetadata() {
return this.#sourceMetadata || null;
}

@@ -424,11 +468,11 @@

*/
_getStream() {
if (this._streamDrained) {
throw new Error(`Content stream of Resource ${this._path} is flagged as drained.`);
#getStream() {
if (this.#streamDrained) {
throw new Error(`Content stream of Resource ${this.#path} is flagged as drained.`);
}
if (this._createStream) {
return this._createStream();
if (this.#createStream) {
return this.#createStream();
}
this._streamDrained = true;
return this._stream;
this.#streamDrained = true;
return this.#stream;
}

@@ -442,8 +486,8 @@

*/
_getBufferFromStream() {
if (this._buffering) { // Prevent simultaneous buffering, causing unexpected access to drained stream
return this._buffering;
#getBufferFromStream() {
if (this.#buffering) { // Prevent simultaneous buffering, causing unexpected access to drained stream
return this.#buffering;
}
return this._buffering = new Promise((resolve, reject) => {
const contentStream = this._getStream();
return this.#buffering = new Promise((resolve, reject) => {
const contentStream = this.#getStream();
const buffers = [];

@@ -458,12 +502,4 @@ contentStream.on("data", (data) => {

const buffer = Buffer.concat(buffers);
let modified;
if (this._source) {
modified = this._source.modified;
}
this.setBuffer(buffer);
// Modified flag should be reset as the resource hasn't been modified from the outside
if (this._source) {
this._source.modified = modified;
}
this._buffering = null;
this.#setBuffer(buffer);
this.#buffering = null;
resolve(buffer);

@@ -470,0 +506,0 @@ });

@@ -11,9 +11,12 @@ import Resource from "./Resource.js";

class ResourceFacade {
#path;
#name;
#resource;
/**
* The constructor.
*
* @public
* @param {object} parameters Parameters
* @param {string} parameters.path Virtual path
* @param {@ui5/fs/Resource} parameters.resource Resource to cover
* @param {string} parameters.path Virtual path of the facade resource
* @param {@ui5/fs/Resource} parameters.resource Resource to conceal
*/

@@ -27,5 +30,5 @@ constructor({path, resource}) {

}
this._path = path;
this._name = Resource._getNameFromPath(path);
this._resource = resource;
this.#path = path;
this.#name = Resource._getNameFromPath(path);
this.#resource = resource;
}

@@ -40,3 +43,3 @@

getPath() {
return this._path;
return this.#path;
}

@@ -51,3 +54,3 @@

getName() {
return this._name;
return this.#name;
}

@@ -74,3 +77,3 @@

// Cloning resolves the facade
const resourceClone = await this._resource.clone();
const resourceClone = await this.#resource.clone();
resourceClone.setPath(this.getPath());

@@ -92,3 +95,3 @@ return resourceClone;

async getBuffer() {
return this._resource.getBuffer();
return this.#resource.getBuffer();
}

@@ -103,3 +106,3 @@

setBuffer(buffer) {
return this._resource.setBuffer(buffer);
return this.#resource.setBuffer(buffer);
}

@@ -114,3 +117,3 @@

getString() {
return this._resource.getString();
return this.#resource.getString();
}

@@ -125,3 +128,3 @@

setString(string) {
return this._resource.setString(string);
return this.#resource.setString(string);
}

@@ -141,3 +144,3 @@

getStream() {
return this._resource.getStream();
return this.#resource.getStream();
}

@@ -153,3 +156,3 @@

setStream(stream) {
return this._resource.setStream(stream);
return this.#resource.setStream(stream);
}

@@ -168,3 +171,3 @@

getStatInfo() {
return this._resource.getStatInfo();
return this.#resource.getStatInfo();
}

@@ -179,3 +182,3 @@

async getSize() {
return this._resource.getSize();
return this.#resource.getSize();
}

@@ -189,3 +192,3 @@

pushCollection(name) {
return this._resource.pushCollection(name);
return this.#resource.pushCollection(name);
}

@@ -199,3 +202,3 @@

getPathTree() {
return this._resource.getPathTree();
return this.#resource.getPathTree();
}

@@ -217,3 +220,3 @@

getProject() {
return this._resource.getProject();
return this.#resource.getProject();
}

@@ -228,3 +231,3 @@

setProject(project) {
return this._resource.setProject(project);
return this.#resource.setProject(project);
}

@@ -239,11 +242,32 @@

hasProject() {
return this._resource.hasProject();
return this.#resource.hasProject();
}
/**
* Check whether the content of this resource has been changed during its life cycle
*
* @public
* @returns {boolean} True if the resource's content has been changed
*/
isModified() {
return this.#resource.isModified();
}
getConcealedResource() {
return this._resource;
/**
* Returns source metadata if any where provided during the creation of this resource.
* Typically set by an adapter to store information for later retrieval.
*
* @returns {object|null}
*/
getSourceMetadata() {
return this.#resource.getSourceMetadata();
}
getSource() {
return this._resource.getSource();
/**
* Returns the resource concealed by this facade
*
* @returns {@ui5/fs/Resource}
*/
getConcealedResource() {
return this.#resource;
}

@@ -250,0 +274,0 @@ }

{
"name": "@ui5/fs",
"version": "3.0.0-rc.2",
"version": "3.0.0-rc.3",
"description": "UI5 Tooling - File System Abstraction",

@@ -135,3 +135,3 @@ "author": {

"@istanbuljs/esm-loader-hook": "^0.2.0",
"ava": "^5.1.0",
"ava": "^5.1.1",
"chai": "^4.3.7",

@@ -146,3 +146,3 @@ "chai-fs": "^2.0.0",

"eslint-plugin-ava": "^13.2.0",
"eslint-plugin-jsdoc": "^39.6.4",
"eslint-plugin-jsdoc": "^39.6.7",
"esmock": "^2.1.0",

@@ -149,0 +149,0 @@ "jsdoc": "^3.6.11",

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc