@ui5/fs
Advanced tools
Comparing version 3.0.0-beta.4 to 3.0.0-rc.0
@@ -5,6 +5,19 @@ # Changelog | ||
A list of unreleased changes can be found [here](https://github.com/SAP/ui5-fs/compare/v3.0.0-beta.4...HEAD). | ||
A list of unreleased changes can be found [here](https://github.com/SAP/ui5-fs/compare/v3.0.0-rc.0...HEAD). | ||
<a name="v3.0.0-rc.0"></a> | ||
## [v3.0.0-rc.0] - 2022-12-23 | ||
### Breaking Changes | ||
- Throw an error on write of a resource when path does not starts with virBasePath of the respective adapter ([#453](https://github.com/SAP/ui5-fs/issues/453)) [`d76575f`](https://github.com/SAP/ui5-fs/commit/d76575f8f05a0b6695285200ba595e532620daed) | ||
- Clone resources when writing in and reading from the Memory ([#448](https://github.com/SAP/ui5-fs/issues/448)) [`3454bc1`](https://github.com/SAP/ui5-fs/commit/3454bc15be8a6ecd455b49607cb289e69b41d0f0) | ||
### BREAKING CHANGE | ||
An error is thrown when a resource shall be written to an adapter where the path of the resource does not starts with the virtual base path defined in the adapter. | ||
Resources stored in the adapters can not be modified by reference anymore. All modifications need to be persisted by using the #write method in order to be reflected in the adapter. | ||
<a name="v3.0.0-beta.4"></a> | ||
## [v3.0.0-beta.4] - 2022-11-30 | ||
## [v3.0.0-beta.4] - 2022-12-01 | ||
@@ -204,2 +217,3 @@ <a name="v3.0.0-beta.3"></a> | ||
[v3.0.0-rc.0]: https://github.com/SAP/ui5-fs/compare/v3.0.0-beta.4...v3.0.0-rc.0 | ||
[v3.0.0-beta.4]: https://github.com/SAP/ui5-fs/compare/v3.0.0-beta.3...v3.0.0-beta.4 | ||
@@ -206,0 +220,0 @@ [v3.0.0-beta.3]: https://github.com/SAP/ui5-fs/compare/v3.0.0-beta.2...v3.0.0-beta.3 |
@@ -30,3 +30,3 @@ { | ||
"type": "website", | ||
"image": "https://sap.github.io/ui5-tooling/docs/images/UI5_logo_wide.png", | ||
"image": "https://sap.github.io/ui5-tooling/v3/images/UI5_logo_wide.png", | ||
"site_name": "UI5 Tooling - API Reference", | ||
@@ -33,0 +33,0 @@ "url": "https://sap.github.io/ui5-tooling/" |
@@ -212,2 +212,7 @@ import logger from "@ui5/logger"; | ||
_write(resource) { | ||
if (!resource.getPath().startsWith(this._virBasePath)) { | ||
throw new Error(`The path of the resource '${resource.getPath()}' does not start with ` + | ||
`the configured virtual base path of the adapter '${this._virBasePath}'`); | ||
} | ||
if (this._project) { | ||
@@ -214,0 +219,0 @@ // Assign project to resource if necessary |
@@ -8,4 +8,4 @@ import logger from "@ui5/logger"; | ||
const chmod = promisify(fs.chmod); | ||
const mkdir = promisify(fs.mkdir); | ||
import {globby} from "globby"; | ||
import makeDir from "make-dir"; | ||
import {PassThrough} from "node:stream"; | ||
@@ -224,3 +224,3 @@ import AbstractAdapter from "./AbstractAdapter.js"; | ||
await makeDir(dirPath, {fs}); | ||
await mkdir(dirPath, {recursive: true}); | ||
@@ -232,5 +232,5 @@ const resourceSource = resource.getSource(); | ||
if (resourceSource.fsPath === fsPath) { | ||
log.silly("Skip writing to %s (Resource hasn't been modified)", fsPath); | ||
log.silly(`Skip writing to ${fsPath} (Resource hasn't been modified)`); | ||
} else { | ||
log.silly("Copying resource from %s to %s", resourceSource.fsPath, fsPath); | ||
log.silly(`Copying resource from ${resourceSource.fsPath} to ${fsPath}`); | ||
await copyFile(resourceSource.fsPath, fsPath); | ||
@@ -244,3 +244,3 @@ if (readOnly) { | ||
log.silly("Writing to %s", fsPath); | ||
log.silly(`Writing to ${fsPath}`); | ||
@@ -247,0 +247,0 @@ return new Promise((resolve, reject) => { |
@@ -31,2 +31,31 @@ import logger from "@ui5/logger"; | ||
/** | ||
* Matches and returns resources from a given map (either _virFiles or _virDirs). | ||
* | ||
* @private | ||
* @param {string[]} patterns | ||
* @param {object} resourceMap | ||
* @returns {Promise<module:@ui5/fs.Resource[]>} | ||
*/ | ||
async _matchPatterns(patterns, resourceMap) { | ||
const resourcePaths = Object.keys(resourceMap); | ||
const matchedPaths = micromatch(resourcePaths, patterns, { | ||
dot: true | ||
}); | ||
return await Promise.all(matchedPaths.map((virPath) => { | ||
const resource = resourceMap[virPath]; | ||
if (resource) { | ||
return this._cloneResource(resource); | ||
} | ||
})); | ||
} | ||
async _cloneResource(resource) { | ||
const clonedResource = await resource.clone(); | ||
if (this._project) { | ||
clonedResource.setProject(this._project); | ||
} | ||
return clonedResource; | ||
} | ||
/** | ||
* Locate resources by glob. | ||
@@ -59,18 +88,7 @@ * | ||
const filePaths = Object.keys(this._virFiles); | ||
const matchedFilePaths = micromatch(filePaths, patterns, { | ||
dot: true | ||
}); | ||
let matchedResources = matchedFilePaths.map((virPath) => { | ||
return this._virFiles[virPath]; | ||
}); | ||
let matchedResources = await this._matchPatterns(patterns, this._virFiles); | ||
if (!options.nodir) { | ||
const dirPaths = Object.keys(this._virDirs); | ||
const matchedDirs = micromatch(dirPaths, patterns, { | ||
dot: true | ||
}); | ||
matchedResources = matchedResources.concat(matchedDirs.map((virPath) => { | ||
return this._virDirs[virPath]; | ||
})); | ||
const matchedDirs = await this._matchPatterns(patterns, this._virDirs); | ||
matchedResources = matchedResources.concat(matchedDirs); | ||
} | ||
@@ -90,24 +108,21 @@ | ||
*/ | ||
_byPath(virPath, options, trace) { | ||
async _byPath(virPath, options, trace) { | ||
if (this.isPathExcluded(virPath)) { | ||
return Promise.resolve(null); | ||
return null; | ||
} | ||
return new Promise((resolve, reject) => { | ||
if (!virPath.startsWith(this._virBasePath) && virPath !== this._virBaseDir) { | ||
// Neither starts with basePath, nor equals baseDirectory | ||
resolve(null); | ||
return; | ||
} | ||
if (!virPath.startsWith(this._virBasePath) && virPath !== this._virBaseDir) { | ||
// Neither starts with basePath, nor equals baseDirectory | ||
return null; | ||
} | ||
const relPath = virPath.substr(this._virBasePath.length); | ||
trace.pathCall(); | ||
const relPath = virPath.substr(this._virBasePath.length); | ||
trace.pathCall(); | ||
const resource = this._virFiles[relPath]; | ||
const resource = this._virFiles[relPath]; | ||
if (!resource || (options.nodir && resource.getStatInfo().isDirectory())) { | ||
resolve(null); | ||
} else { | ||
resolve(resource); | ||
} | ||
}); | ||
if (!resource || (options.nodir && resource.getStatInfo().isDirectory())) { | ||
return null; | ||
} else { | ||
return await this._cloneResource(resource); | ||
} | ||
} | ||
@@ -125,38 +140,35 @@ | ||
super._write(resource); | ||
return new Promise((resolve, reject) => { | ||
const relPath = resource.getPath().substr(this._virBasePath.length); | ||
log.silly("Writing to virtual path %s", resource.getPath()); | ||
this._virFiles[relPath] = resource; | ||
const relPath = resource.getPath().substr(this._virBasePath.length); | ||
log.silly(`Writing to virtual path ${resource.getPath()}`); | ||
this._virFiles[relPath] = await resource.clone(); | ||
// Add virtual directories for all path segments of the written resource | ||
// TODO: Add tests for all this | ||
const pathSegments = relPath.split("/"); | ||
pathSegments.pop(); // Remove last segment representing the resource itself | ||
// Add virtual directories for all path segments of the written resource | ||
// TODO: Add tests for all this | ||
const pathSegments = relPath.split("/"); | ||
pathSegments.pop(); // Remove last segment representing the resource itself | ||
pathSegments.forEach((segment, i) => { | ||
if (i >= 1) { | ||
segment = pathSegments[i - 1] + "/" + segment; | ||
} | ||
pathSegments[i] = segment; | ||
}); | ||
pathSegments.forEach((segment, i) => { | ||
if (i >= 1) { | ||
segment = pathSegments[i - 1] + "/" + segment; | ||
} | ||
pathSegments[i] = segment; | ||
}); | ||
for (let i = pathSegments.length - 1; i >= 0; i--) { | ||
const segment = pathSegments[i]; | ||
if (!this._virDirs[segment]) { | ||
this._virDirs[segment] = this._createResource({ | ||
project: this._project, | ||
source: { | ||
adapter: "Memory" | ||
}, | ||
statInfo: { // TODO: make closer to fs stat info | ||
isDirectory: function() { | ||
return true; | ||
} | ||
}, | ||
path: this._virBasePath + segment | ||
}); | ||
} | ||
for (let i = pathSegments.length - 1; i >= 0; i--) { | ||
const segment = pathSegments[i]; | ||
if (!this._virDirs[segment]) { | ||
this._virDirs[segment] = this._createResource({ | ||
project: this._project, | ||
source: { | ||
adapter: "Memory" | ||
}, | ||
statInfo: { // TODO: make closer to fs stat info | ||
isDirectory: function() { | ||
return true; | ||
} | ||
}, | ||
path: this._virBasePath + segment | ||
}); | ||
} | ||
resolve(); | ||
}); | ||
} | ||
} | ||
@@ -163,0 +175,0 @@ } |
@@ -24,2 +24,10 @@ import AbstractReaderWriter from "./AbstractReaderWriter.js"; | ||
super(name); | ||
if (!reader) { | ||
throw new Error(`Cannot create DuplexCollection ${this._name}: No reader provided`); | ||
} | ||
if (!writer) { | ||
throw new Error(`Cannot create DuplexCollection ${this._name}: No writer provided`); | ||
} | ||
this._reader = reader; | ||
@@ -77,3 +85,4 @@ this._writer = writer; | ||
* @param {@ui5/fs/tracing.Trace} trace Trace instance | ||
* @returns {Promise<@ui5/fs/Resource>} Promise resolving to a single resource | ||
* @returns {Promise<@ui5/fs/Resource|null>} | ||
* Promise resolving to a single resource or <code>null</code> if no resource is found | ||
*/ | ||
@@ -80,0 +89,0 @@ _byPath(virPath, options, trace) { |
@@ -17,7 +17,11 @@ import AbstractReader from "./AbstractReader.js"; | ||
* @param {string} parameters.name The collection name | ||
* @param {@ui5/fs/AbstractReader[]} parameters.readers List of resource readers (all tried in parallel) | ||
* @param {@ui5/fs/AbstractReader[]} [parameters.readers] | ||
* List of resource readers (all tried in parallel). | ||
* If none are provided, the collection will never return any results. | ||
*/ | ||
constructor({name, readers}) { | ||
super(name); | ||
this._readers = readers; | ||
// Remove any undefined (empty) readers from array | ||
this._readers = readers.filter(($) => $); | ||
} | ||
@@ -51,3 +55,4 @@ | ||
* @param {@ui5/fs/tracing.Trace} trace Trace instance | ||
* @returns {Promise<@ui5/fs/Resource>} Promise resolving to a single resource | ||
* @returns {Promise<@ui5/fs/Resource|null>} | ||
* Promise resolving to a single resource or <code>null</code> if no resource is found | ||
*/ | ||
@@ -59,10 +64,9 @@ _byPath(virPath, options, trace) { | ||
if (this._readers.length === 0) { | ||
// Promise.race doesn't resolve for empty arrays | ||
return Promise.resolve(); | ||
if (resourceLocatorCount === 0) { | ||
// Short-circuit if there are no readers (Promise.race does not settle for empty arrays) | ||
trace.collection(that._name); | ||
return Promise.resolve(null); | ||
} | ||
/* Promise.race to cover the following (self defined) requirement: | ||
Deliver files that can be found fast at the cost of slower response times for files that cannot be found. | ||
*/ | ||
// Using Promise.race to deliver files that can be found as fast as possible | ||
return Promise.race(this._readers.map(function(resourceLocator) { | ||
@@ -69,0 +73,0 @@ return resourceLocator._byPath(virPath, options, trace).then(function(resource) { |
@@ -17,8 +17,11 @@ import AbstractReader from "./AbstractReader.js"; | ||
* @param {string} parameters.name The collection name | ||
* @param {@ui5/fs/AbstractReader[]} parameters.readers Prioritized list of resource readers | ||
* (first is tried first) | ||
* @param {@ui5/fs/AbstractReader[]} [parameters.readers] | ||
* Prioritized list of resource readers (tried in the order provided). | ||
* If none are provided, the collection will never return any results. | ||
*/ | ||
constructor({readers, name}) { | ||
super(name); | ||
this._readers = readers; | ||
// Remove any undefined (empty) readers from array | ||
this._readers = readers.filter(($) => $); | ||
} | ||
@@ -66,3 +69,4 @@ | ||
* @param {@ui5/fs/tracing.Trace} trace Trace instance | ||
* @returns {Promise<@ui5/fs/Resource>} Promise resolving to a single resource | ||
* @returns {Promise<@ui5/fs/Resource|null>} | ||
* Promise resolving to a single resource or <code>null</code> if no resource is found | ||
*/ | ||
@@ -69,0 +73,0 @@ _byPath(virPath, options, trace) { |
@@ -61,3 +61,3 @@ import stream from "node:stream"; | ||
// Indicator for adapters like FileSystem to detect whether a resource has been changed | ||
this._source.modified = false; | ||
this._source.modified = this._source.modified || false; | ||
} | ||
@@ -318,3 +318,3 @@ this.__project = project; // Two underscores since "_project" was widely used in UI5 Tooling 2.0 | ||
statInfo: clone(this._statInfo), | ||
source: this._source | ||
source: clone(this._source) | ||
}; | ||
@@ -335,2 +335,9 @@ | ||
* Retrieve the project assigned to the resource | ||
* <br/> | ||
* <b>Note for UI5 Tooling extensions (i.e. custom tasks, custom middleware):</b> | ||
* In order to ensure compatibility across UI5 Tooling versions, consider using the | ||
* <code>getProject(resource)</code> method provided by | ||
* [TaskUtil]{@link module:@ui5/project/build/helpers/TaskUtil} and | ||
* [MiddlewareUtil]{@link module:@ui5/server.middleware.MiddlewareUtil}, which will | ||
* return a Specification Version-compatible Project interface. | ||
* | ||
@@ -337,0 +344,0 @@ * @public |
@@ -190,2 +190,9 @@ import Resource from "./Resource.js"; | ||
* Retrieve the project assigned to the resource | ||
* <br/> | ||
* <b>Note for UI5 Tooling extensions (i.e. custom tasks, custom middleware):</b> | ||
* In order to ensure compatibility across UI5 Tooling versions, consider using the | ||
* <code>getProject(resource)</code> method provided by | ||
* [TaskUtil]{@link module:@ui5/project/build/helpers/TaskUtil} and | ||
* [MiddlewareUtil]{@link module:@ui5/server.middleware.MiddlewareUtil}, which will | ||
* return a Specification Version-compatible Project interface. | ||
* | ||
@@ -192,0 +199,0 @@ * @public |
@@ -35,7 +35,7 @@ import AbstractReaderWriter from "./AbstractReaderWriter.js"; | ||
if (!writerMapping) { | ||
throw new Error("Missing parameter 'writerMapping'"); | ||
throw new Error(`Cannot create WriterCollection ${this._name}: Missing parameter 'writerMapping'`); | ||
} | ||
const basePaths = Object.keys(writerMapping); | ||
if (!basePaths.length) { | ||
throw new Error("Empty parameter 'writerMapping'"); | ||
throw new Error(`Cannot create WriterCollection ${this._name}: Empty parameter 'writerMapping'`); | ||
} | ||
@@ -42,0 +42,0 @@ |
{ | ||
"name": "@ui5/fs", | ||
"version": "3.0.0-beta.4", | ||
"version": "3.0.0-rc.0", | ||
"description": "UI5 Tooling - File System Abstraction", | ||
@@ -124,10 +124,9 @@ "author": { | ||
"dependencies": { | ||
"@ui5/logger": "^3.0.1-beta.1", | ||
"@ui5/logger": "^3.0.1-rc.0", | ||
"clone": "^2.1.2", | ||
"escape-string-regexp": "^5.0.0", | ||
"globby": "^13.1.2", | ||
"globby": "^13.1.3", | ||
"graceful-fs": "^4.2.10", | ||
"make-dir": "^3.1.0", | ||
"micromatch": "^4.0.5", | ||
"minimatch": "^5.1.0", | ||
"minimatch": "^5.1.1", | ||
"pretty-hrtime": "^1.0.3", | ||
@@ -145,7 +144,7 @@ "random-int": "^3.0.0" | ||
"docdash": "^2.0.0", | ||
"eslint": "^8.28.0", | ||
"eslint": "^8.30.0", | ||
"eslint-config-google": "^0.14.0", | ||
"eslint-plugin-ava": "^13.2.0", | ||
"eslint-plugin-jsdoc": "^37.9.7", | ||
"esmock": "^2.0.9", | ||
"eslint-plugin-jsdoc": "^39.6.4", | ||
"esmock": "^2.1.0", | ||
"jsdoc": "^3.6.11", | ||
@@ -155,3 +154,3 @@ "nyc": "^15.1.0", | ||
"rimraf": "^3.0.2", | ||
"sinon": "^14.0.2", | ||
"sinon": "^15.0.1", | ||
"tap-nyan": "^1.1.0", | ||
@@ -158,0 +157,0 @@ "tap-xunit": "^2.4.1" |
128007
9
2710
- Removedmake-dir@^3.1.0
- Removedmake-dir@3.1.0(transitive)
- Removedsemver@6.3.1(transitive)
Updated@ui5/logger@^3.0.1-rc.0
Updatedglobby@^13.1.3
Updatedminimatch@^5.1.1