Initial commit

This commit is contained in:
2025-03-07 19:22:02 +01:00
commit 4a98255d83
55743 changed files with 5280367 additions and 0 deletions
+21
View File
@@ -0,0 +1,21 @@
Copyright (c) Microsoft Corporation.
MIT License
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+125
View File
@@ -0,0 +1,125 @@
# M365 Spec Parser
The M365 Spec Parser package is designed to parse OpenAPI specification files to generate resources for M365 applications.
## Sample Usage
### For NodeJS environment
```typescript
import { SpecParser, ParseOptions } from "@microsoft/m365-spec-parser";
// Define parsing options
const option: ParseOptions = {
allowMissingId: true, // Allow missing IDs in the specification, default true
allowSwagger: true, // Allow Swagger specifications, default true
allowAPIKeyAuth: false, // Disallow API key authentication, default false
allowMultipleParameters: false, // Disallow multiple parameters, default false
allowOauth2: false, // Disallow OAuth2 authentication, default false
};
// Create a new SpecParser instance with the given options
const parser = new SpecParser("path/to/spec/file", option);
// Validate the specification
const validateResult = await parser.validate();
// If the specification is not valid, log the errors and warnings
if (validateResult.status !== ValidationStatus.Valid) {
console.log(validateResult.errors);
console.log(validateResult.warnings);
}
// List the operations in the specification
const listResult = await parser.list();
// Log each operation
for (let i = 0; i < listResult.length; i++) {
console.log(listResult[i]);
}
// Define a filter for the operations to generate
const filter = ["GET /pet/{id}"];
// Define the paths for the Teams app manifest file, the output specification file, and the output adaptive card folder
const teamsAppManifestFilePath = "path/to/teamsapp/manifest/file";
const outputSpecFilePath = "path/to/output/spec/path";
const outputAdaptiveCardFolder = "adaptivecard/folder";
// Generate the operations
const generateResult = await parser.generate(
teamsAppManifestFilePath,
filter,
outputSpecFilePath,
outputAdaptiveCardFolder
);
// If not all operations were successfully generated, log the warnings
if (!generateResult.allSuccess) {
console.log(generateResult.warnings);
}
```
### For browser environment
```typescript
import { SpecParser, ParseOptions } from "@microsoft/m365-spec-parser";
// Define parsing options
const option: ParseOptions = {
allowMissingId: false, // Allow missing IDs in the specification, default false
allowSwagger: false, // Allow Swagger specifications, default false
allowAPIKeyAuth: false, // Disallow API key authentication, default false
allowMultipleParameters: false, // Disallow multiple parameters, default false
allowOauth2: false, // Disallow OAuth2 authentication, default false
};
// Create a new SpecParser instance with the given options
const parser = new SpecParser("path/to/spec/file", option);
// Validate the specification
const validateResult = await parser.validate();
// If the specification is not valid, log the errors and warnings
if (validateResult.status !== ValidationStatus.Valid) {
console.log(validateResult.errors);
console.log(validateResult.warnings);
}
// List the operations in the specification
const listResult = await parser.listSupportedAPIInfo();
// Log each operation
for (let i = 0; i < listResult.length; i++) {
console.log(listResult[i]);
}
```
## Data Collection.
The software may collect information about you and your use of the software and send it to Microsoft. Microsoft may use this information to provide services and improve our products and services. You may turn off the telemetry as described in the repository. There are also some features in the software that may enable you and Microsoft to collect data from users of your applications. If you use these features, you must comply with applicable law, including providing appropriate notices to users of your applications together with a copy of Microsoft's privacy statement. Our privacy statement is located at https://go.microsoft.com/fwlink/?LinkID=824704. You can learn more about data collection and use in the help documentation and our privacy statement. Your use of the software operates as your consent to these practices.
## Code of Conduct
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
## Contributing
There are many ways in which you can participate in the project, for example:
- [Submit bugs and feature requests](https://github.com/OfficeDev/TeamsFx/issues), and help us verify as they are checked in
- Review [source code changes](https://github.com/OfficeDev/TeamsFx/pulls)
If you are interested in fixing issues and contributing directly to the code base, please see the [Contributing Guide](./CONTRIBUTING.md).
## Reporting Security Issues
**Please do not report security vulnerabilities through public GitHub issues.**
Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://msrc.microsoft.com/create-report).
If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the the [Microsoft Security Response Center PGP Key page](https://www.microsoft.com/en-us/msrc/pgp-key-msrc).
You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc).
## Trademarks
This project may contain trademarks or logos for projects, products, or services. Authorized use of Microsoft trademarks or logos is subject to and must follow [Microsoft's Trademark & Brand Guidelines](https://www.microsoft.com/en-us/legal/intellectualproperty/trademarks/usage/general). Use of Microsoft trademarks or logos in modified versions of this project must not cause confusion or imply Microsoft sponsorship. Any use of third-party trademarks or logos are subject to those third-party's policies.
## License
Copyright (c) Microsoft Corporation. All rights reserved.
Licensed under the [MIT](LICENSE.txt) license.
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
@@ -0,0 +1,10 @@
import { OpenAPIV3 } from "openapi-types";
import { AdaptiveCard, ArrayElement, ImageElement, TextBlockElement, WarningResult } from "./interfaces";
export declare class AdaptiveCardGenerator {
static generateAdaptiveCard(operationItem: OpenAPIV3.OperationObject, allowMultipleMediaType?: boolean, maxElementCount?: number): [AdaptiveCard, string, any, WarningResult[]];
static generateCardFromResponse(schema: OpenAPIV3.SchemaObject, name: string, parentArrayName?: string, maxElementCount?: number, counter?: {
count: number;
}): Array<TextBlockElement | ImageElement | ArrayElement>;
static getResponseJsonPathFromSchema(schema: OpenAPIV3.SchemaObject): string;
static isImageUrlProperty(schema: OpenAPIV3.NonArraySchemaObject, name: string, parentArrayName: string): boolean;
}
@@ -0,0 +1,16 @@
import { ResponseSemanticsObject } from "@microsoft/teams-manifest";
import { AdaptiveCard, PreviewCardTemplate, WrappedAdaptiveCard } from "./interfaces";
export declare function wrapAdaptiveCard(card: AdaptiveCard, jsonPath: string): WrappedAdaptiveCard;
export declare function wrapResponseSemantics(card: AdaptiveCard, jsonPath: string): ResponseSemanticsObject;
/**
* Infers the preview card template from an Adaptive Card and a JSON path.
* The preview card template includes a title and an optional subtitle and image.
* It populates the preview card template with the first text block that matches
* each well-known name, in the order of title, subtitle, and image.
* If no text block matches the title or subtitle, it uses the first two text block as the title and subtitle.
* If the title is still empty and the subtitle is not empty, it uses subtitle as the title.
* @param card The Adaptive Card to infer the preview card template from.
* @param jsonPath The JSON path to the root object in the card body.
* @returns The inferred preview card template.
*/
export declare function inferPreviewCardTemplate(card: AdaptiveCard): PreviewCardTemplate;
+53
View File
@@ -0,0 +1,53 @@
export declare class ConstantString {
static readonly CancelledMessage = "Operation is cancelled.";
static readonly NoServerInformation = "No server information is found in the OpenAPI description document.";
static readonly RemoteRefNotSupported = "Remote reference is not supported: %s.";
static readonly MissingOperationId = "Missing operationIds: %s.";
static readonly NoSupportedApi = "No supported API is found in the OpenAPI description document: only GET and POST methods are supported, additionally, there can be at most one required parameter, and no auth is allowed.";
static readonly AdditionalPropertiesNotSupported = "'additionalProperties' is not supported, and will be ignored.";
static readonly SchemaNotSupported = "'oneOf', 'allOf', 'anyOf', and 'not' schema are not supported: %s.";
static readonly UnknownSchema = "Unknown schema: %s.";
static readonly UrlProtocolNotSupported = "Server url is not correct: protocol %s is not supported, you should use https protocol instead.";
static readonly RelativeServerUrlNotSupported = "Server url is not correct: relative server url is not supported.";
static readonly ResolveServerUrlFailed = "Unable to resolve the server URL: please make sure that the environment variable %s is defined.";
static readonly OperationOnlyContainsOptionalParam = "Operation %s contains multiple optional parameters. The first optional parameter is used for this command.";
static readonly ConvertSwaggerToOpenAPI = "The Swagger 2.0 file has been converted to OpenAPI 3.0.";
static readonly SwaggerNotSupported = "Swagger 2.0 is not supported. Please convert to OpenAPI 3.0 manually before proceeding.";
static readonly SpecVersionNotSupported = "Unsupported OpenAPI version %s. Please use version 3.0.x.";
static readonly MultipleAuthNotSupported = "Multiple authentication methods are unsupported. Ensure all selected APIs use identical authentication.";
static readonly OperationIdContainsSpecialCharacters = "Operation id '%s' in OpenAPI description document contained special characters and was renamed to '%s'.";
static readonly UnsupportedSchema = "Unsupported schema in %s %s: %s";
static readonly FuncDescriptionTooLong = "The description of the function '%s' is too long. The current length is %s characters, while the maximum allowed length is %s characters.";
static readonly GenerateJsonDataFailed = "Failed to generate JSON data for api: %s due to %s.";
static readonly WrappedCardVersion = "devPreview";
static readonly WrappedCardSchema = "https://developer.microsoft.com/json-schemas/teams/vDevPreview/MicrosoftTeams.ResponseRenderingTemplate.schema.json";
static readonly WrappedCardResponseLayout = "list";
static readonly GetMethod = "get";
static readonly PostMethod = "post";
static readonly AdaptiveCardVersion = "1.5";
static readonly AdaptiveCardSchema = "http://adaptivecards.io/schemas/adaptive-card.json";
static readonly AdaptiveCardType = "AdaptiveCard";
static readonly TextBlockType = "TextBlock";
static readonly ImageType = "Image";
static readonly ContainerType = "Container";
static readonly RegistrationIdPostfix: {
[key: string]: string;
};
static readonly ResponseCodeFor20X: string[];
static readonly AllOperationMethods: string[];
static readonly WellknownResultNames: string[];
static readonly WellknownTitleName: string[];
static readonly WellknownSubtitleName: string[];
static readonly WellknownImageName: string[];
static readonly ShortDescriptionMaxLens = 80;
static readonly FullDescriptionMaxLens = 4000;
static readonly CommandDescriptionMaxLens = 128;
static readonly ParameterDescriptionMaxLens = 128;
static readonly ConversationStarterMaxLens = 50;
static readonly CommandTitleMaxLens = 32;
static readonly ParameterTitleMaxLens = 32;
static readonly SMERequiredParamsMaxNum = 5;
static readonly FunctionDescriptionMaxLens = 100;
static readonly DefaultPluginId = "plugin_1";
static readonly PluginManifestSchema = "https://developer.microsoft.com/json-schemas/copilot/plugin/v2.2/schema.json";
}
+7
View File
@@ -0,0 +1,7 @@
export { SpecParser } from "./specParser.browser";
export { SpecParserError } from "./specParserError";
export { ValidationStatus, WarningType, ErrorType, ListAPIResult } from "./interfaces";
export type { ErrorResult, APIInfo, ValidateResult, WarningResult, ParseOptions, AdaptiveCard, ProjectType, } from "./interfaces";
export { ConstantString } from "./constants";
export { Utils } from "./utils";
export { AdaptiveCardGenerator } from "./adaptiveCardGenerator";
+6
View File
@@ -0,0 +1,6 @@
export { SpecParser } from "./specParser";
export { SpecParserError } from "./specParserError";
export { ValidationStatus, WarningType, ErrorType, WarningResult, ErrorResult, ListAPIResult, APIInfo, ValidateResult, ParseOptions, AdaptiveCard, ProjectType, InvalidAPIInfo, AuthType, } from "./interfaces";
export { ConstantString } from "./constants";
export { Utils } from "./utils";
export { AdaptiveCardGenerator } from "./adaptiveCardGenerator";
+286
View File
@@ -0,0 +1,286 @@
import { IParameter } from "@microsoft/teams-manifest";
import { OpenAPIV3 } from "openapi-types";
/**
* An interface that represents the result of validating an OpenAPI specification file.
*/
export interface ValidateResult {
/**
* The validation status of the OpenAPI specification file.
*/
status: ValidationStatus;
/**
* An array of warning results generated during validation.
*/
warnings: WarningResult[];
/**
* An array of error results generated during validation.
*/
errors: ErrorResult[];
specHash?: string;
}
export interface SpecValidationResult {
/**
* An array of warning results generated during validation.
*/
warnings: WarningResult[];
/**
* An array of error results generated during validation.
*/
errors: ErrorResult[];
}
/**
* An interface that represents a warning result generated during validation.
*/
export interface WarningResult {
/**
* The type of warning.
*/
type: WarningType;
/**
* The content of the warning.
*/
content: string;
/**
* data of the warning.
*/
data?: any;
}
/**
* An interface that represents an error result generated during validation.
*/
export interface ErrorResult {
/**
* The type of error.
*/
type: ErrorType;
/**
* The content of the error.
*/
content: string;
/**
* data of the error.
*/
data?: any;
}
export interface GenerateResult {
allSuccess: boolean;
warnings: WarningResult[];
}
/**
* An enum that represents the types of errors that can occur during validation.
*/
export declare enum ErrorType {
SpecNotValid = "spec-not-valid",
RemoteRefNotSupported = "remote-ref-not-supported",
NoServerInformation = "no-server-information",
UrlProtocolNotSupported = "url-protocol-not-supported",
RelativeServerUrlNotSupported = "relative-server-url-not-supported",
NoSupportedApi = "no-supported-api",
NoExtraAPICanBeAdded = "no-extra-api-can-be-added",
AddedAPINotInOriginalSpec = "added-api-not-in-original-spec",
ResolveServerUrlFailed = "resolve-server-url-failed",
SwaggerNotSupported = "swagger-not-supported",
MultipleAuthNotSupported = "multiple-auth-not-supported",
SpecVersionNotSupported = "spec-version-not-supported",
CircularReferenceNotSupported = "circular-reference-not-supported",
ListFailed = "list-failed",
listSupportedAPIInfoFailed = "list-supported-api-info-failed",
FilterSpecFailed = "filter-spec-failed",
UpdateManifestFailed = "update-manifest-failed",
GenerateAdaptiveCardFailed = "generate-adaptive-card-failed",
GenerateFailed = "generate-failed",
ValidateFailed = "validate-failed",
GetSpecFailed = "get-spec-failed",
AuthTypeIsNotSupported = "auth-type-is-not-supported",
MissingOperationId = "missing-operation-id",
PostBodyContainMultipleMediaTypes = "post-body-contain-multiple-media-types",
ResponseContainMultipleMediaTypes = "response-contain-multiple-media-types",
ResponseJsonIsEmpty = "response-json-is-empty",
PostBodySchemaIsNotJson = "post-body-schema-is-not-json",
PostBodyContainsRequiredUnsupportedSchema = "post-body-contains-required-unsupported-schema",
ParamsContainRequiredUnsupportedSchema = "params-contain-required-unsupported-schema",
ParamsContainsNestedObject = "params-contains-nested-object",
RequestBodyContainsNestedObject = "request-body-contains-nested-object",
ExceededRequiredParamsLimit = "exceeded-required-params-limit",
NoParameter = "no-parameter",
NoAPIInfo = "no-api-info",
MethodNotAllowed = "method-not-allowed",
UrlPathNotExist = "url-path-not-exist",
Cancelled = "cancelled",
Unknown = "unknown"
}
/**
* An enum that represents the types of warnings that can occur during validation.
*/
export declare enum WarningType {
OperationIdMissing = "operationid-missing",
GenerateCardFailed = "generate-card-failed",
OperationOnlyContainsOptionalParam = "operation-only-contains-optional-param",
ConvertSwaggerToOpenAPI = "convert-swagger-to-openapi",
FuncDescriptionTooLong = "function-description-too-long",
OperationIdContainsSpecialCharacters = "operationid-contains-special-characters",
GenerateJsonDataFailed = "generate-json-data-failed",
Unknown = "unknown"
}
/**
* An enum that represents the validation status of an OpenAPI specification file.
*/
export declare enum ValidationStatus {
Valid = 0,
Warning = 1,
Error = 2
}
export interface TextBlockElement {
type: string;
text: string;
wrap: boolean;
}
export interface ImageElement {
type: string;
url: string;
$when: string;
}
export declare type AdaptiveCardBody = Array<TextBlockElement | ImageElement | ArrayElement>;
export interface ArrayElement {
type: string;
$data: string;
items: AdaptiveCardBody;
}
export interface AdaptiveCard {
type: string;
$schema: string;
version: string;
body: AdaptiveCardBody;
}
export interface PreviewCardTemplate {
title: string;
subtitle?: string;
image?: {
url: string;
alt?: string;
$when?: string;
};
}
export interface WrappedAdaptiveCard {
version: string;
$schema?: string;
jsonPath?: string;
responseLayout: string;
responseCardTemplate: AdaptiveCard;
previewCardTemplate: PreviewCardTemplate;
}
export interface CheckParamResult {
requiredNum: number;
optionalNum: number;
isValid: boolean;
reason: ErrorType[];
}
export interface ParseOptions {
/**
* If true, the parser will not throw an error if an ID is missing the spec file.
*/
allowMissingId?: boolean;
/**
* If true, the parser will allow parsing of Swagger specifications.
*/
allowSwagger?: boolean;
/**
* If true, the parser will allow API Key authentication in the spec file.
*/
allowAPIKeyAuth?: boolean;
/**
* If true, the parser will allow Bearer Token authentication in the spec file.
*/
allowBearerTokenAuth?: boolean;
/**
* If true, the parser will allow multiple parameters in the spec file. Teams AI project would ignore this parameters and always true
*/
allowMultipleParameters?: boolean;
/**
* If true, the parser will allow OAuth2 authentication in the spec file. Currently only support OAuth2 with auth code flow.
*/
allowOauth2?: boolean;
/**
* An array of HTTP methods that the parser will allow in the spec file.
*/
allowMethods?: string[];
/**
* If true, the parser will allow conversation starters in plugin file. Only take effect in Copilot project
*/
allowConversationStarters?: boolean;
/**
* If true, the parser will allow response semantics in plugin file. Only take effect in Copilot project
*/
allowResponseSemantics?: boolean;
/**
* If true, the paser will allow confirmation in plugin file. Only take effect in Copilot project
*/
allowConfirmation?: boolean;
/**
* The type of project that the parser is being used for.
* Project can be SME/Copilot/TeamsAi
*/
projectType?: ProjectType;
/**
* If true, we will generate files of plugin for GPT (Declarative Extensions in a Copilot Extension). Otherwise, we will generate files of plugin for Copilot.
*/
isGptPlugin?: boolean;
}
export declare enum ProjectType {
Copilot = 0,
SME = 1,
TeamsAi = 2
}
export interface APIInfo {
method: string;
path: string;
title: string;
id: string;
parameters: IParameter[];
description: string;
warning?: WarningResult;
}
export interface ListAPIInfo {
api: string;
server: string;
operationId: string;
isValid: boolean;
reason: ErrorType[];
auth?: AuthInfo;
}
export interface APIMap {
[key: string]: {
operation: OpenAPIV3.OperationObject;
isValid: boolean;
reason: ErrorType[];
};
}
export interface APIValidationResult {
isValid: boolean;
reason: ErrorType[];
}
export interface ListAPIResult {
allAPICount: number;
validAPICount: number;
APIs: ListAPIInfo[];
}
export declare type AuthType = OpenAPIV3.SecuritySchemeObject | {
type: "multipleAuth";
};
export interface AuthInfo {
authScheme: AuthType;
name: string;
}
export interface InvalidAPIInfo {
api: string;
reason: ErrorType[];
}
export interface InferredProperties {
title?: string;
subtitle?: string;
imageUrl?: string;
}
export interface ExistingPluginManifestInfo {
manifestPath: string;
specPath: string;
}
@@ -0,0 +1,6 @@
import { OpenAPIV3 } from "openapi-types";
export declare class JsonDataGenerator {
private static visitedSchemas;
static generate(schema: OpenAPIV3.SchemaObject): any;
static generateMockData(schema: OpenAPIV3.SchemaObject): any;
}
+15
View File
@@ -0,0 +1,15 @@
import { OpenAPIV3 } from "openapi-types";
import { AuthInfo, ExistingPluginManifestInfo, ParseOptions, WarningResult } from "./interfaces";
import { IMessagingExtensionCommand, TeamsAppManifest, PluginManifestSchema } from "@microsoft/teams-manifest";
export declare class ManifestUpdater {
static updateManifestWithAiPlugin(manifestPath: string, outputSpecPath: string, apiPluginFilePath: string, spec: OpenAPIV3.Document, options: ParseOptions, authInfo?: AuthInfo, existingPluginManifestInfo?: ExistingPluginManifestInfo): Promise<[TeamsAppManifest, PluginManifestSchema, WarningResult[]]>;
static updateManifestDescription(manifest: TeamsAppManifest, spec: OpenAPIV3.Document): void;
static checkSchema(schema: OpenAPIV3.SchemaObject, method: string, pathUrl: string): void;
static generatePluginManifestSchema(spec: OpenAPIV3.Document, specRelativePath: string, apiPluginFilePath: string, appName: string, authInfo: AuthInfo | undefined, options: ParseOptions, existingPluginManifestInfo?: ExistingPluginManifestInfo): Promise<[PluginManifestSchema, WarningResult[]]>;
static updateManifest(manifestPath: string, outputSpecPath: string, spec: OpenAPIV3.Document, options: ParseOptions, adaptiveCardFolder?: string, authInfo?: AuthInfo): Promise<[TeamsAppManifest, WarningResult[]]>;
static generateCommands(spec: OpenAPIV3.Document, manifestPath: string, options: ParseOptions, adaptiveCardFolder?: string): Promise<[IMessagingExtensionCommand[], WarningResult[]]>;
static getRelativePath(from: string, to: string): string;
static removeEnvs(str: string): string;
static removeAllSpecialCharacters(str: string): string;
static getConfirmationBodyItem(paramName: string): string;
}
+5
View File
@@ -0,0 +1,5 @@
import { OpenAPIV3 } from "openapi-types";
import { ParseOptions } from "./interfaces";
export declare class SpecFilter {
static specFilter(filter: string[], unResolveSpec: OpenAPIV3.Document, resolvedSpec: OpenAPIV3.Document, options: ParseOptions): OpenAPIV3.Document;
}
+18
View File
@@ -0,0 +1,18 @@
import { OpenAPIV3 } from "openapi-types";
export interface OptimizerOptions {
removeUnusedComponents: boolean;
removeUnusedTags: boolean;
removeUserDefinedRootProperty: boolean;
removeUnusedSecuritySchemas: boolean;
}
export declare class SpecOptimizer {
private static defaultOptions;
static optimize(spec: OpenAPIV3.Document, options?: OptimizerOptions): OpenAPIV3.Document;
private static removeUnusedSecuritySchemas;
private static removeUnusedTags;
private static removeUserDefinedRootProperty;
private static removeUnusedComponents;
private static getComponentReferences;
private static getComponent;
private static addComponent;
}
@@ -0,0 +1,60 @@
import SwaggerParser from "@apidevtools/swagger-parser";
import { OpenAPIV3 } from "openapi-types";
import { APIInfo, GenerateResult, ParseOptions, ValidateResult, ListAPIResult } from "./interfaces";
/**
* A class that parses an OpenAPI specification file and provides methods to validate, list, and generate artifacts.
*/
export declare class SpecParser {
readonly pathOrSpec: string | OpenAPIV3.Document;
readonly parser: SwaggerParser;
readonly options: Required<ParseOptions>;
private spec;
private validator;
private unResolveSpec;
private isSwaggerFile;
private defaultOptions;
/**
* Creates a new instance of the SpecParser class.
* @param pathOrDoc The path to the OpenAPI specification file or the OpenAPI specification object.
* @param options The options for parsing the OpenAPI specification file.
*/
constructor(pathOrDoc: string | OpenAPIV3.Document, options?: ParseOptions);
/**
* Validates the OpenAPI specification file and returns a validation result.
*
* @returns A validation result object that contains information about any errors or warnings in the specification file.
*/
validate(): Promise<ValidateResult>;
listSupportedAPIInfo(): Promise<APIInfo[]>;
/**
* Lists all the OpenAPI operations in the specification file.
* @returns A string array that represents the HTTP method and path of each operation, such as ['GET /pets/{petId}', 'GET /user/{userId}']
* according to copilot plugin spec, only list get and post method without auth
*/
list(): Promise<ListAPIResult[]>;
/**
* Generate specs according to the filters.
* @param filter An array of strings that represent the filters to apply when generating the artifacts. If filter is empty, it would process nothing.
*/
getFilteredSpecs(filter: string[], signal?: AbortSignal): Promise<[OpenAPIV3.Document, OpenAPIV3.Document]>;
/**
* Generates and update artifacts from the OpenAPI specification file. Generate Adaptive Cards, update Teams app manifest, and generate a new OpenAPI specification file.
* @param manifestPath A file path of the Teams app manifest file to update.
* @param filter An array of strings that represent the filters to apply when generating the artifacts. If filter is empty, it would process nothing.
* @param outputSpecPath File path of the new OpenAPI specification file to generate. If not specified or empty, no spec file will be generated.
* @param pluginFilePath File path of the api plugin file to generate.
*/
generateForCopilot(manifestPath: string, filter: string[], outputSpecPath: string, pluginFilePath: string, signal?: AbortSignal): Promise<GenerateResult>;
/**
* Generates and update artifacts from the OpenAPI specification file. Generate Adaptive Cards, update Teams app manifest, and generate a new OpenAPI specification file.
* @param manifestPath A file path of the Teams app manifest file to update.
* @param filter An array of strings that represent the filters to apply when generating the artifacts. If filter is empty, it would process nothing.
* @param outputSpecPath File path of the new OpenAPI specification file to generate. If not specified or empty, no spec file will be generated.
* @param adaptiveCardFolder Folder path where the Adaptive Card files will be generated. If not specified or empty, Adaptive Card files will not be generated.
* @param isMe Boolean that indicates whether the project is an Messaging Extension. For Messaging Extension, composeExtensions will be added in Teams app manifest.
*/
generate(manifestPath: string, filter: string[], outputSpecPath: string, adaptiveCardFolder?: string, signal?: AbortSignal): Promise<GenerateResult>;
private loadSpec;
private getAPIs;
private getValidator;
}
+61
View File
@@ -0,0 +1,61 @@
import SwaggerParser from "@apidevtools/swagger-parser";
import { OpenAPIV3 } from "openapi-types";
import { APIInfo, GenerateResult, ListAPIResult, ParseOptions, ValidateResult } from "./interfaces";
/**
* A class that parses an OpenAPI specification file and provides methods to validate, list, and generate artifacts.
*/
export declare class SpecParser {
readonly pathOrSpec: string | OpenAPIV3.Document;
readonly parser: SwaggerParser;
readonly options: Required<ParseOptions>;
private validator;
private spec;
private unResolveSpec;
private isSwaggerFile;
private defaultOptions;
/**
* Creates a new instance of the SpecParser class.
* @param pathOrDoc The path to the OpenAPI specification file or the OpenAPI specification object.
* @param options The options for parsing the OpenAPI specification file.
*/
constructor(pathOrDoc: string | OpenAPIV3.Document, options?: ParseOptions);
/**
* Validates the OpenAPI specification file and returns a validation result.
*
* @returns A validation result object that contains information about any errors or warnings in the specification file.
*/
validate(): Promise<ValidateResult>;
listSupportedAPIInfo(): Promise<APIInfo[]>;
/**
* Lists all the OpenAPI operations in the specification file.
* @returns A string array that represents the HTTP method and path of each operation, such as ['GET /pets/{petId}', 'GET /user/{userId}']
* according to copilot plugin spec, only list get and post method without auth
*/
list(): Promise<ListAPIResult>;
/**
* Generate specs according to the filters.
* @param filter An array of strings that represent the filters to apply when generating the artifacts. If filter is empty, it would process nothing.
*/
getFilteredSpecs(filter: string[], signal?: AbortSignal): Promise<[OpenAPIV3.Document, OpenAPIV3.Document]>;
/**
* Generates and update artifacts from the OpenAPI specification file. Generate Adaptive Cards, update Teams app manifest, and generate a new OpenAPI specification file.
* @param manifestPath A file path of the Teams app manifest file to update.
* @param filter An array of strings that represent the filters to apply when generating the artifacts. If filter is empty, it would process nothing.
* @param outputSpecPath File path of the new OpenAPI specification file to generate. If not specified or empty, no spec file will be generated.
* @param pluginFilePath File path of the api plugin file to generate.
*/
generateForCopilot(manifestPath: string, filter: string[], outputSpecPath: string, pluginFilePath: string, existingPluginFilePath?: string, signal?: AbortSignal): Promise<GenerateResult>;
/**
* Generates and update artifacts from the OpenAPI specification file. Generate Adaptive Cards, update Teams app manifest, and generate a new OpenAPI specification file.
* @param manifestPath A file path of the Teams app manifest file to update.
* @param filter An array of strings that represent the filters to apply when generating the artifacts. If filter is empty, it would process nothing.
* @param outputSpecPath File path of the new OpenAPI specification file to generate. If not specified or empty, no spec file will be generated.
* @param adaptiveCardFolder Folder path where the Adaptive Card files will be generated. If not specified or empty, Adaptive Card files will not be generated.
*/
generate(manifestPath: string, filter: string[], outputSpecPath: string, adaptiveCardFolder?: string, signal?: AbortSignal): Promise<GenerateResult>;
private loadSpec;
private getAPIs;
private getValidator;
private saveFilterSpec;
private resolveEnvForSpec;
}
@@ -0,0 +1,5 @@
import { ErrorType } from "./interfaces";
export declare class SpecParserError extends Error {
readonly errorType: ErrorType;
constructor(message: string, errorType: ErrorType);
}
+30
View File
@@ -0,0 +1,30 @@
import { OpenAPIV3 } from "openapi-types";
import { AuthInfo, AuthType, ErrorResult, ParseOptions } from "./interfaces";
import { IMessagingExtensionCommand, IParameter } from "@microsoft/teams-manifest";
export declare class Utils {
static hasNestedObjectInSchema(schema: OpenAPIV3.SchemaObject): boolean;
static isObjectSchema(schema: OpenAPIV3.SchemaObject): boolean;
static containMultipleMediaTypes(bodyObject: OpenAPIV3.RequestBodyObject | OpenAPIV3.ResponseObject): boolean;
static isBearerTokenAuth(authScheme: AuthType): boolean;
static isAPIKeyAuth(authScheme: AuthType): boolean;
static isOAuthWithAuthCodeFlow(authScheme: AuthType): boolean;
static getAuthArray(securities: OpenAPIV3.SecurityRequirementObject[] | undefined, spec: OpenAPIV3.Document): AuthInfo[][];
static getAuthInfo(spec: OpenAPIV3.Document): AuthInfo | undefined;
static updateFirstLetter(str: string): string;
static getResponseJson(operationObject: OpenAPIV3.OperationObject | undefined, allowMultipleMediaType?: boolean): {
json: OpenAPIV3.MediaTypeObject;
multipleMediaType: boolean;
};
static convertPathToCamelCase(path: string): string;
static getUrlProtocol(urlString: string): string | undefined;
static resolveEnv(str: string): string;
static checkServerUrl(servers: OpenAPIV3.ServerObject[]): ErrorResult[];
static validateServer(spec: OpenAPIV3.Document, options: ParseOptions): ErrorResult[];
static isWellKnownName(name: string, wellknownNameList: string[]): boolean;
static generateParametersFromSchema(schema: OpenAPIV3.SchemaObject, name: string, allowMultipleParameters: boolean, isRequired?: boolean): [IParameter[], IParameter[]];
static updateParameterWithInputType(schema: OpenAPIV3.SchemaObject, param: IParameter): void;
static parseApiInfo(operationItem: OpenAPIV3.OperationObject, options: ParseOptions): IMessagingExtensionCommand;
static format(str: string, ...args: string[]): string;
static getSafeRegistrationIdEnvName(authName: string): string;
static getServerObject(spec: OpenAPIV3.Document, method: string, path: string): OpenAPIV3.ServerObject | undefined;
}
+15
View File
@@ -0,0 +1,15 @@
(The MIT License)
Copyright (c) 2011-2024 JP Richardson
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files
(the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify,
merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,292 @@
Node.js: fs-extra
=================
`fs-extra` adds file system methods that aren't included in the native `fs` module and adds promise support to the `fs` methods. It also uses [`graceful-fs`](https://github.com/isaacs/node-graceful-fs) to prevent `EMFILE` errors. It should be a drop in replacement for `fs`.
[![npm Package](https://img.shields.io/npm/v/fs-extra.svg)](https://www.npmjs.org/package/fs-extra)
[![License](https://img.shields.io/npm/l/fs-extra.svg)](https://github.com/jprichardson/node-fs-extra/blob/master/LICENSE)
[![build status](https://img.shields.io/github/actions/workflow/status/jprichardson/node-fs-extra/ci.yml?branch=master)](https://github.com/jprichardson/node-fs-extra/actions/workflows/ci.yml?query=branch%3Amaster)
[![downloads per month](http://img.shields.io/npm/dm/fs-extra.svg)](https://www.npmjs.org/package/fs-extra)
[![JavaScript Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://standardjs.com)
Why?
----
I got tired of including `mkdirp`, `rimraf`, and `ncp` in most of my projects.
Installation
------------
npm install fs-extra
Usage
-----
### CommonJS
`fs-extra` is a drop in replacement for native `fs`. All methods in `fs` are attached to `fs-extra`. All `fs` methods return promises if the callback isn't passed.
You don't ever need to include the original `fs` module again:
```js
const fs = require('fs') // this is no longer necessary
```
you can now do this:
```js
const fs = require('fs-extra')
```
or if you prefer to make it clear that you're using `fs-extra` and not `fs`, you may want
to name your `fs` variable `fse` like so:
```js
const fse = require('fs-extra')
```
you can also keep both, but it's redundant:
```js
const fs = require('fs')
const fse = require('fs-extra')
```
### ESM
There is also an `fs-extra/esm` import, that supports both default and named exports. However, note that `fs` methods are not included in `fs-extra/esm`; you still need to import `fs` and/or `fs/promises` seperately:
```js
import { readFileSync } from 'fs'
import { readFile } from 'fs/promises'
import { outputFile, outputFileSync } from 'fs-extra/esm'
```
Default exports are supported:
```js
import fs from 'fs'
import fse from 'fs-extra/esm'
// fse.readFileSync is not a function; must use fs.readFileSync
```
but you probably want to just use regular `fs-extra` instead of `fs-extra/esm` for default exports:
```js
import fs from 'fs-extra'
// both fs and fs-extra methods are defined
```
Sync vs Async vs Async/Await
-------------
Most methods are async by default. All async methods will return a promise if the callback isn't passed.
Sync methods on the other hand will throw if an error occurs.
Also Async/Await will throw an error if one occurs.
Example:
```js
const fs = require('fs-extra')
// Async with promises:
fs.copy('/tmp/myfile', '/tmp/mynewfile')
.then(() => console.log('success!'))
.catch(err => console.error(err))
// Async with callbacks:
fs.copy('/tmp/myfile', '/tmp/mynewfile', err => {
if (err) return console.error(err)
console.log('success!')
})
// Sync:
try {
fs.copySync('/tmp/myfile', '/tmp/mynewfile')
console.log('success!')
} catch (err) {
console.error(err)
}
// Async/Await:
async function copyFiles () {
try {
await fs.copy('/tmp/myfile', '/tmp/mynewfile')
console.log('success!')
} catch (err) {
console.error(err)
}
}
copyFiles()
```
Methods
-------
### Async
- [copy](docs/copy.md)
- [emptyDir](docs/emptyDir.md)
- [ensureFile](docs/ensureFile.md)
- [ensureDir](docs/ensureDir.md)
- [ensureLink](docs/ensureLink.md)
- [ensureSymlink](docs/ensureSymlink.md)
- [mkdirp](docs/ensureDir.md)
- [mkdirs](docs/ensureDir.md)
- [move](docs/move.md)
- [outputFile](docs/outputFile.md)
- [outputJson](docs/outputJson.md)
- [pathExists](docs/pathExists.md)
- [readJson](docs/readJson.md)
- [remove](docs/remove.md)
- [writeJson](docs/writeJson.md)
### Sync
- [copySync](docs/copy-sync.md)
- [emptyDirSync](docs/emptyDir-sync.md)
- [ensureFileSync](docs/ensureFile-sync.md)
- [ensureDirSync](docs/ensureDir-sync.md)
- [ensureLinkSync](docs/ensureLink-sync.md)
- [ensureSymlinkSync](docs/ensureSymlink-sync.md)
- [mkdirpSync](docs/ensureDir-sync.md)
- [mkdirsSync](docs/ensureDir-sync.md)
- [moveSync](docs/move-sync.md)
- [outputFileSync](docs/outputFile-sync.md)
- [outputJsonSync](docs/outputJson-sync.md)
- [pathExistsSync](docs/pathExists-sync.md)
- [readJsonSync](docs/readJson-sync.md)
- [removeSync](docs/remove-sync.md)
- [writeJsonSync](docs/writeJson-sync.md)
**NOTE:** You can still use the native Node.js methods. They are promisified and copied over to `fs-extra`. See [notes on `fs.read()`, `fs.write()`, & `fs.writev()`](docs/fs-read-write-writev.md)
### What happened to `walk()` and `walkSync()`?
They were removed from `fs-extra` in v2.0.0. If you need the functionality, `walk` and `walkSync` are available as separate packages, [`klaw`](https://github.com/jprichardson/node-klaw) and [`klaw-sync`](https://github.com/manidlou/node-klaw-sync).
Third Party
-----------
### CLI
[fse-cli](https://www.npmjs.com/package/@atao60/fse-cli) allows you to run `fs-extra` from a console or from [npm](https://www.npmjs.com) scripts.
### TypeScript
If you like TypeScript, you can use `fs-extra` with it: https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/fs-extra
### File / Directory Watching
If you want to watch for changes to files or directories, then you should use [chokidar](https://github.com/paulmillr/chokidar).
### Obtain Filesystem (Devices, Partitions) Information
[fs-filesystem](https://github.com/arthurintelligence/node-fs-filesystem) allows you to read the state of the filesystem of the host on which it is run. It returns information about both the devices and the partitions (volumes) of the system.
### Misc.
- [fs-extra-debug](https://github.com/jdxcode/fs-extra-debug) - Send your fs-extra calls to [debug](https://npmjs.org/package/debug).
- [mfs](https://github.com/cadorn/mfs) - Monitor your fs-extra calls.
Hacking on fs-extra
-------------------
Wanna hack on `fs-extra`? Great! Your help is needed! [fs-extra is one of the most depended upon Node.js packages](http://nodei.co/npm/fs-extra.png?downloads=true&downloadRank=true&stars=true). This project
uses [JavaScript Standard Style](https://github.com/feross/standard) - if the name or style choices bother you,
you're gonna have to get over it :) If `standard` is good enough for `npm`, it's good enough for `fs-extra`.
[![js-standard-style](https://cdn.rawgit.com/feross/standard/master/badge.svg)](https://github.com/feross/standard)
What's needed?
- First, take a look at existing issues. Those are probably going to be where the priority lies.
- More tests for edge cases. Specifically on different platforms. There can never be enough tests.
- Improve test coverage.
Note: If you make any big changes, **you should definitely file an issue for discussion first.**
### Running the Test Suite
fs-extra contains hundreds of tests.
- `npm run lint`: runs the linter ([standard](http://standardjs.com/))
- `npm run unit`: runs the unit tests
- `npm run unit-esm`: runs tests for `fs-extra/esm` exports
- `npm test`: runs the linter and all tests
When running unit tests, set the environment variable `CROSS_DEVICE_PATH` to the absolute path of an empty directory on another device (like a thumb drive) to enable cross-device move tests.
### Windows
If you run the tests on the Windows and receive a lot of symbolic link `EPERM` permission errors, it's
because on Windows you need elevated privilege to create symbolic links. You can add this to your Windows's
account by following the instructions here: http://superuser.com/questions/104845/permission-to-make-symbolic-links-in-windows-7
However, I didn't have much luck doing this.
Since I develop on Mac OS X, I use VMWare Fusion for Windows testing. I create a shared folder that I map to a drive on Windows.
I open the `Node.js command prompt` and run as `Administrator`. I then map the network drive running the following command:
net use z: "\\vmware-host\Shared Folders"
I can then navigate to my `fs-extra` directory and run the tests.
Naming
------
I put a lot of thought into the naming of these functions. Inspired by @coolaj86's request. So he deserves much of the credit for raising the issue. See discussion(s) here:
* https://github.com/jprichardson/node-fs-extra/issues/2
* https://github.com/flatiron/utile/issues/11
* https://github.com/ryanmcgrath/wrench-js/issues/29
* https://github.com/substack/node-mkdirp/issues/17
First, I believe that in as many cases as possible, the [Node.js naming schemes](http://nodejs.org/api/fs.html) should be chosen. However, there are problems with the Node.js own naming schemes.
For example, `fs.readFile()` and `fs.readdir()`: the **F** is capitalized in *File* and the **d** is not capitalized in *dir*. Perhaps a bit pedantic, but they should still be consistent. Also, Node.js has chosen a lot of POSIX naming schemes, which I believe is great. See: `fs.mkdir()`, `fs.rmdir()`, `fs.chown()`, etc.
We have a dilemma though. How do you consistently name methods that perform the following POSIX commands: `cp`, `cp -r`, `mkdir -p`, and `rm -rf`?
My perspective: when in doubt, err on the side of simplicity. A directory is just a hierarchical grouping of directories and files. Consider that for a moment. So when you want to copy it or remove it, in most cases you'll want to copy or remove all of its contents. When you want to create a directory, if the directory that it's suppose to be contained in does not exist, then in most cases you'll want to create that too.
So, if you want to remove a file or a directory regardless of whether it has contents, just call `fs.remove(path)`. If you want to copy a file or a directory whether it has contents, just call `fs.copy(source, destination)`. If you want to create a directory regardless of whether its parent directories exist, just call `fs.mkdirs(path)` or `fs.mkdirp(path)`.
Credit
------
`fs-extra` wouldn't be possible without using the modules from the following authors:
- [Isaac Shlueter](https://github.com/isaacs)
- [Charlie McConnel](https://github.com/avianflu)
- [James Halliday](https://github.com/substack)
- [Andrew Kelley](https://github.com/andrewrk)
License
-------
Licensed under MIT
Copyright (c) 2011-2024 [JP Richardson](https://github.com/jprichardson)
[1]: http://nodejs.org/docs/latest/api/fs.html
[jsonfile]: https://github.com/jprichardson/node-jsonfile
@@ -0,0 +1,171 @@
'use strict'
const fs = require('graceful-fs')
const path = require('path')
const mkdirsSync = require('../mkdirs').mkdirsSync
const utimesMillisSync = require('../util/utimes').utimesMillisSync
const stat = require('../util/stat')
function copySync (src, dest, opts) {
if (typeof opts === 'function') {
opts = { filter: opts }
}
opts = opts || {}
opts.clobber = 'clobber' in opts ? !!opts.clobber : true // default to true for now
opts.overwrite = 'overwrite' in opts ? !!opts.overwrite : opts.clobber // overwrite falls back to clobber
// Warn about using preserveTimestamps on 32-bit node
if (opts.preserveTimestamps && process.arch === 'ia32') {
process.emitWarning(
'Using the preserveTimestamps option in 32-bit node is not recommended;\n\n' +
'\tsee https://github.com/jprichardson/node-fs-extra/issues/269',
'Warning', 'fs-extra-WARN0002'
)
}
const { srcStat, destStat } = stat.checkPathsSync(src, dest, 'copy', opts)
stat.checkParentPathsSync(src, srcStat, dest, 'copy')
if (opts.filter && !opts.filter(src, dest)) return
const destParent = path.dirname(dest)
if (!fs.existsSync(destParent)) mkdirsSync(destParent)
return getStats(destStat, src, dest, opts)
}
function getStats (destStat, src, dest, opts) {
const statSync = opts.dereference ? fs.statSync : fs.lstatSync
const srcStat = statSync(src)
if (srcStat.isDirectory()) return onDir(srcStat, destStat, src, dest, opts)
else if (srcStat.isFile() ||
srcStat.isCharacterDevice() ||
srcStat.isBlockDevice()) return onFile(srcStat, destStat, src, dest, opts)
else if (srcStat.isSymbolicLink()) return onLink(destStat, src, dest, opts)
else if (srcStat.isSocket()) throw new Error(`Cannot copy a socket file: ${src}`)
else if (srcStat.isFIFO()) throw new Error(`Cannot copy a FIFO pipe: ${src}`)
throw new Error(`Unknown file: ${src}`)
}
function onFile (srcStat, destStat, src, dest, opts) {
if (!destStat) return copyFile(srcStat, src, dest, opts)
return mayCopyFile(srcStat, src, dest, opts)
}
function mayCopyFile (srcStat, src, dest, opts) {
if (opts.overwrite) {
fs.unlinkSync(dest)
return copyFile(srcStat, src, dest, opts)
} else if (opts.errorOnExist) {
throw new Error(`'${dest}' already exists`)
}
}
function copyFile (srcStat, src, dest, opts) {
fs.copyFileSync(src, dest)
if (opts.preserveTimestamps) handleTimestamps(srcStat.mode, src, dest)
return setDestMode(dest, srcStat.mode)
}
function handleTimestamps (srcMode, src, dest) {
// Make sure the file is writable before setting the timestamp
// otherwise open fails with EPERM when invoked with 'r+'
// (through utimes call)
if (fileIsNotWritable(srcMode)) makeFileWritable(dest, srcMode)
return setDestTimestamps(src, dest)
}
function fileIsNotWritable (srcMode) {
return (srcMode & 0o200) === 0
}
function makeFileWritable (dest, srcMode) {
return setDestMode(dest, srcMode | 0o200)
}
function setDestMode (dest, srcMode) {
return fs.chmodSync(dest, srcMode)
}
function setDestTimestamps (src, dest) {
// The initial srcStat.atime cannot be trusted
// because it is modified by the read(2) system call
// (See https://nodejs.org/api/fs.html#fs_stat_time_values)
const updatedSrcStat = fs.statSync(src)
return utimesMillisSync(dest, updatedSrcStat.atime, updatedSrcStat.mtime)
}
function onDir (srcStat, destStat, src, dest, opts) {
if (!destStat) return mkDirAndCopy(srcStat.mode, src, dest, opts)
return copyDir(src, dest, opts)
}
function mkDirAndCopy (srcMode, src, dest, opts) {
fs.mkdirSync(dest)
copyDir(src, dest, opts)
return setDestMode(dest, srcMode)
}
function copyDir (src, dest, opts) {
const dir = fs.opendirSync(src)
try {
let dirent
while ((dirent = dir.readSync()) !== null) {
copyDirItem(dirent.name, src, dest, opts)
}
} finally {
dir.closeSync()
}
}
function copyDirItem (item, src, dest, opts) {
const srcItem = path.join(src, item)
const destItem = path.join(dest, item)
if (opts.filter && !opts.filter(srcItem, destItem)) return
const { destStat } = stat.checkPathsSync(srcItem, destItem, 'copy', opts)
return getStats(destStat, srcItem, destItem, opts)
}
function onLink (destStat, src, dest, opts) {
let resolvedSrc = fs.readlinkSync(src)
if (opts.dereference) {
resolvedSrc = path.resolve(process.cwd(), resolvedSrc)
}
if (!destStat) {
return fs.symlinkSync(resolvedSrc, dest)
} else {
let resolvedDest
try {
resolvedDest = fs.readlinkSync(dest)
} catch (err) {
// dest exists and is a regular file or directory,
// Windows may throw UNKNOWN error. If dest already exists,
// fs throws error anyway, so no need to guard against it here.
if (err.code === 'EINVAL' || err.code === 'UNKNOWN') return fs.symlinkSync(resolvedSrc, dest)
throw err
}
if (opts.dereference) {
resolvedDest = path.resolve(process.cwd(), resolvedDest)
}
if (stat.isSrcSubdir(resolvedSrc, resolvedDest)) {
throw new Error(`Cannot copy '${resolvedSrc}' to a subdirectory of itself, '${resolvedDest}'.`)
}
// prevent copy if src is a subdir of dest since unlinking
// dest in this case would result in removing src contents
// and therefore a broken symlink would be created.
if (stat.isSrcSubdir(resolvedDest, resolvedSrc)) {
throw new Error(`Cannot overwrite '${resolvedDest}' with '${resolvedSrc}'.`)
}
return copyLink(resolvedSrc, dest)
}
}
function copyLink (resolvedSrc, dest) {
fs.unlinkSync(dest)
return fs.symlinkSync(resolvedSrc, dest)
}
module.exports = copySync
@@ -0,0 +1,182 @@
'use strict'
const fs = require('../fs')
const path = require('path')
const { mkdirs } = require('../mkdirs')
const { pathExists } = require('../path-exists')
const { utimesMillis } = require('../util/utimes')
const stat = require('../util/stat')
async function copy (src, dest, opts = {}) {
if (typeof opts === 'function') {
opts = { filter: opts }
}
opts.clobber = 'clobber' in opts ? !!opts.clobber : true // default to true for now
opts.overwrite = 'overwrite' in opts ? !!opts.overwrite : opts.clobber // overwrite falls back to clobber
// Warn about using preserveTimestamps on 32-bit node
if (opts.preserveTimestamps && process.arch === 'ia32') {
process.emitWarning(
'Using the preserveTimestamps option in 32-bit node is not recommended;\n\n' +
'\tsee https://github.com/jprichardson/node-fs-extra/issues/269',
'Warning', 'fs-extra-WARN0001'
)
}
const { srcStat, destStat } = await stat.checkPaths(src, dest, 'copy', opts)
await stat.checkParentPaths(src, srcStat, dest, 'copy')
const include = await runFilter(src, dest, opts)
if (!include) return
// check if the parent of dest exists, and create it if it doesn't exist
const destParent = path.dirname(dest)
const dirExists = await pathExists(destParent)
if (!dirExists) {
await mkdirs(destParent)
}
await getStatsAndPerformCopy(destStat, src, dest, opts)
}
async function runFilter (src, dest, opts) {
if (!opts.filter) return true
return opts.filter(src, dest)
}
async function getStatsAndPerformCopy (destStat, src, dest, opts) {
const statFn = opts.dereference ? fs.stat : fs.lstat
const srcStat = await statFn(src)
if (srcStat.isDirectory()) return onDir(srcStat, destStat, src, dest, opts)
if (
srcStat.isFile() ||
srcStat.isCharacterDevice() ||
srcStat.isBlockDevice()
) return onFile(srcStat, destStat, src, dest, opts)
if (srcStat.isSymbolicLink()) return onLink(destStat, src, dest, opts)
if (srcStat.isSocket()) throw new Error(`Cannot copy a socket file: ${src}`)
if (srcStat.isFIFO()) throw new Error(`Cannot copy a FIFO pipe: ${src}`)
throw new Error(`Unknown file: ${src}`)
}
async function onFile (srcStat, destStat, src, dest, opts) {
if (!destStat) return copyFile(srcStat, src, dest, opts)
if (opts.overwrite) {
await fs.unlink(dest)
return copyFile(srcStat, src, dest, opts)
}
if (opts.errorOnExist) {
throw new Error(`'${dest}' already exists`)
}
}
async function copyFile (srcStat, src, dest, opts) {
await fs.copyFile(src, dest)
if (opts.preserveTimestamps) {
// Make sure the file is writable before setting the timestamp
// otherwise open fails with EPERM when invoked with 'r+'
// (through utimes call)
if (fileIsNotWritable(srcStat.mode)) {
await makeFileWritable(dest, srcStat.mode)
}
// Set timestamps and mode correspondingly
// Note that The initial srcStat.atime cannot be trusted
// because it is modified by the read(2) system call
// (See https://nodejs.org/api/fs.html#fs_stat_time_values)
const updatedSrcStat = await fs.stat(src)
await utimesMillis(dest, updatedSrcStat.atime, updatedSrcStat.mtime)
}
return fs.chmod(dest, srcStat.mode)
}
function fileIsNotWritable (srcMode) {
return (srcMode & 0o200) === 0
}
function makeFileWritable (dest, srcMode) {
return fs.chmod(dest, srcMode | 0o200)
}
async function onDir (srcStat, destStat, src, dest, opts) {
// the dest directory might not exist, create it
if (!destStat) {
await fs.mkdir(dest)
}
const promises = []
// loop through the files in the current directory to copy everything
for await (const item of await fs.opendir(src)) {
const srcItem = path.join(src, item.name)
const destItem = path.join(dest, item.name)
promises.push(
runFilter(srcItem, destItem, opts).then(include => {
if (include) {
// only copy the item if it matches the filter function
return stat.checkPaths(srcItem, destItem, 'copy', opts).then(({ destStat }) => {
// If the item is a copyable file, `getStatsAndPerformCopy` will copy it
// If the item is a directory, `getStatsAndPerformCopy` will call `onDir` recursively
return getStatsAndPerformCopy(destStat, srcItem, destItem, opts)
})
}
})
)
}
await Promise.all(promises)
if (!destStat) {
await fs.chmod(dest, srcStat.mode)
}
}
async function onLink (destStat, src, dest, opts) {
let resolvedSrc = await fs.readlink(src)
if (opts.dereference) {
resolvedSrc = path.resolve(process.cwd(), resolvedSrc)
}
if (!destStat) {
return fs.symlink(resolvedSrc, dest)
}
let resolvedDest = null
try {
resolvedDest = await fs.readlink(dest)
} catch (e) {
// dest exists and is a regular file or directory,
// Windows may throw UNKNOWN error. If dest already exists,
// fs throws error anyway, so no need to guard against it here.
if (e.code === 'EINVAL' || e.code === 'UNKNOWN') return fs.symlink(resolvedSrc, dest)
throw e
}
if (opts.dereference) {
resolvedDest = path.resolve(process.cwd(), resolvedDest)
}
if (stat.isSrcSubdir(resolvedSrc, resolvedDest)) {
throw new Error(`Cannot copy '${resolvedSrc}' to a subdirectory of itself, '${resolvedDest}'.`)
}
// do not copy if src is a subdir of dest since unlinking
// dest in this case would result in removing src contents
// and therefore a broken symlink would be created.
if (stat.isSrcSubdir(resolvedDest, resolvedSrc)) {
throw new Error(`Cannot overwrite '${resolvedDest}' with '${resolvedSrc}'.`)
}
// copy the link
await fs.unlink(dest)
return fs.symlink(resolvedSrc, dest)
}
module.exports = copy
@@ -0,0 +1,7 @@
'use strict'
const u = require('universalify').fromPromise
module.exports = {
copy: u(require('./copy')),
copySync: require('./copy-sync')
}
@@ -0,0 +1,39 @@
'use strict'
const u = require('universalify').fromPromise
const fs = require('../fs')
const path = require('path')
const mkdir = require('../mkdirs')
const remove = require('../remove')
const emptyDir = u(async function emptyDir (dir) {
let items
try {
items = await fs.readdir(dir)
} catch {
return mkdir.mkdirs(dir)
}
return Promise.all(items.map(item => remove.remove(path.join(dir, item))))
})
function emptyDirSync (dir) {
let items
try {
items = fs.readdirSync(dir)
} catch {
return mkdir.mkdirsSync(dir)
}
items.forEach(item => {
item = path.join(dir, item)
remove.removeSync(item)
})
}
module.exports = {
emptyDirSync,
emptydirSync: emptyDirSync,
emptyDir,
emptydir: emptyDir
}
@@ -0,0 +1,66 @@
'use strict'
const u = require('universalify').fromPromise
const path = require('path')
const fs = require('../fs')
const mkdir = require('../mkdirs')
async function createFile (file) {
let stats
try {
stats = await fs.stat(file)
} catch { }
if (stats && stats.isFile()) return
const dir = path.dirname(file)
let dirStats = null
try {
dirStats = await fs.stat(dir)
} catch (err) {
// if the directory doesn't exist, make it
if (err.code === 'ENOENT') {
await mkdir.mkdirs(dir)
await fs.writeFile(file, '')
return
} else {
throw err
}
}
if (dirStats.isDirectory()) {
await fs.writeFile(file, '')
} else {
// parent is not a directory
// This is just to cause an internal ENOTDIR error to be thrown
await fs.readdir(dir)
}
}
function createFileSync (file) {
let stats
try {
stats = fs.statSync(file)
} catch { }
if (stats && stats.isFile()) return
const dir = path.dirname(file)
try {
if (!fs.statSync(dir).isDirectory()) {
// parent is not a directory
// This is just to cause an internal ENOTDIR error to be thrown
fs.readdirSync(dir)
}
} catch (err) {
// If the stat call above failed because the directory doesn't exist, create it
if (err && err.code === 'ENOENT') mkdir.mkdirsSync(dir)
else throw err
}
fs.writeFileSync(file, '')
}
module.exports = {
createFile: u(createFile),
createFileSync
}
@@ -0,0 +1,23 @@
'use strict'
const { createFile, createFileSync } = require('./file')
const { createLink, createLinkSync } = require('./link')
const { createSymlink, createSymlinkSync } = require('./symlink')
module.exports = {
// file
createFile,
createFileSync,
ensureFile: createFile,
ensureFileSync: createFileSync,
// link
createLink,
createLinkSync,
ensureLink: createLink,
ensureLinkSync: createLinkSync,
// symlink
createSymlink,
createSymlinkSync,
ensureSymlink: createSymlink,
ensureSymlinkSync: createSymlinkSync
}
@@ -0,0 +1,64 @@
'use strict'
const u = require('universalify').fromPromise
const path = require('path')
const fs = require('../fs')
const mkdir = require('../mkdirs')
const { pathExists } = require('../path-exists')
const { areIdentical } = require('../util/stat')
async function createLink (srcpath, dstpath) {
let dstStat
try {
dstStat = await fs.lstat(dstpath)
} catch {
// ignore error
}
let srcStat
try {
srcStat = await fs.lstat(srcpath)
} catch (err) {
err.message = err.message.replace('lstat', 'ensureLink')
throw err
}
if (dstStat && areIdentical(srcStat, dstStat)) return
const dir = path.dirname(dstpath)
const dirExists = await pathExists(dir)
if (!dirExists) {
await mkdir.mkdirs(dir)
}
await fs.link(srcpath, dstpath)
}
function createLinkSync (srcpath, dstpath) {
let dstStat
try {
dstStat = fs.lstatSync(dstpath)
} catch {}
try {
const srcStat = fs.lstatSync(srcpath)
if (dstStat && areIdentical(srcStat, dstStat)) return
} catch (err) {
err.message = err.message.replace('lstat', 'ensureLink')
throw err
}
const dir = path.dirname(dstpath)
const dirExists = fs.existsSync(dir)
if (dirExists) return fs.linkSync(srcpath, dstpath)
mkdir.mkdirsSync(dir)
return fs.linkSync(srcpath, dstpath)
}
module.exports = {
createLink: u(createLink),
createLinkSync
}
@@ -0,0 +1,101 @@
'use strict'
const path = require('path')
const fs = require('../fs')
const { pathExists } = require('../path-exists')
const u = require('universalify').fromPromise
/**
* Function that returns two types of paths, one relative to symlink, and one
* relative to the current working directory. Checks if path is absolute or
* relative. If the path is relative, this function checks if the path is
* relative to symlink or relative to current working directory. This is an
* initiative to find a smarter `srcpath` to supply when building symlinks.
* This allows you to determine which path to use out of one of three possible
* types of source paths. The first is an absolute path. This is detected by
* `path.isAbsolute()`. When an absolute path is provided, it is checked to
* see if it exists. If it does it's used, if not an error is returned
* (callback)/ thrown (sync). The other two options for `srcpath` are a
* relative url. By default Node's `fs.symlink` works by creating a symlink
* using `dstpath` and expects the `srcpath` to be relative to the newly
* created symlink. If you provide a `srcpath` that does not exist on the file
* system it results in a broken symlink. To minimize this, the function
* checks to see if the 'relative to symlink' source file exists, and if it
* does it will use it. If it does not, it checks if there's a file that
* exists that is relative to the current working directory, if does its used.
* This preserves the expectations of the original fs.symlink spec and adds
* the ability to pass in `relative to current working direcotry` paths.
*/
async function symlinkPaths (srcpath, dstpath) {
if (path.isAbsolute(srcpath)) {
try {
await fs.lstat(srcpath)
} catch (err) {
err.message = err.message.replace('lstat', 'ensureSymlink')
throw err
}
return {
toCwd: srcpath,
toDst: srcpath
}
}
const dstdir = path.dirname(dstpath)
const relativeToDst = path.join(dstdir, srcpath)
const exists = await pathExists(relativeToDst)
if (exists) {
return {
toCwd: relativeToDst,
toDst: srcpath
}
}
try {
await fs.lstat(srcpath)
} catch (err) {
err.message = err.message.replace('lstat', 'ensureSymlink')
throw err
}
return {
toCwd: srcpath,
toDst: path.relative(dstdir, srcpath)
}
}
function symlinkPathsSync (srcpath, dstpath) {
if (path.isAbsolute(srcpath)) {
const exists = fs.existsSync(srcpath)
if (!exists) throw new Error('absolute srcpath does not exist')
return {
toCwd: srcpath,
toDst: srcpath
}
}
const dstdir = path.dirname(dstpath)
const relativeToDst = path.join(dstdir, srcpath)
const exists = fs.existsSync(relativeToDst)
if (exists) {
return {
toCwd: relativeToDst,
toDst: srcpath
}
}
const srcExists = fs.existsSync(srcpath)
if (!srcExists) throw new Error('relative srcpath does not exist')
return {
toCwd: srcpath,
toDst: path.relative(dstdir, srcpath)
}
}
module.exports = {
symlinkPaths: u(symlinkPaths),
symlinkPathsSync
}
@@ -0,0 +1,34 @@
'use strict'
const fs = require('../fs')
const u = require('universalify').fromPromise
async function symlinkType (srcpath, type) {
if (type) return type
let stats
try {
stats = await fs.lstat(srcpath)
} catch {
return 'file'
}
return (stats && stats.isDirectory()) ? 'dir' : 'file'
}
function symlinkTypeSync (srcpath, type) {
if (type) return type
let stats
try {
stats = fs.lstatSync(srcpath)
} catch {
return 'file'
}
return (stats && stats.isDirectory()) ? 'dir' : 'file'
}
module.exports = {
symlinkType: u(symlinkType),
symlinkTypeSync
}
@@ -0,0 +1,67 @@
'use strict'
const u = require('universalify').fromPromise
const path = require('path')
const fs = require('../fs')
const { mkdirs, mkdirsSync } = require('../mkdirs')
const { symlinkPaths, symlinkPathsSync } = require('./symlink-paths')
const { symlinkType, symlinkTypeSync } = require('./symlink-type')
const { pathExists } = require('../path-exists')
const { areIdentical } = require('../util/stat')
async function createSymlink (srcpath, dstpath, type) {
let stats
try {
stats = await fs.lstat(dstpath)
} catch { }
if (stats && stats.isSymbolicLink()) {
const [srcStat, dstStat] = await Promise.all([
fs.stat(srcpath),
fs.stat(dstpath)
])
if (areIdentical(srcStat, dstStat)) return
}
const relative = await symlinkPaths(srcpath, dstpath)
srcpath = relative.toDst
const toType = await symlinkType(relative.toCwd, type)
const dir = path.dirname(dstpath)
if (!(await pathExists(dir))) {
await mkdirs(dir)
}
return fs.symlink(srcpath, dstpath, toType)
}
function createSymlinkSync (srcpath, dstpath, type) {
let stats
try {
stats = fs.lstatSync(dstpath)
} catch { }
if (stats && stats.isSymbolicLink()) {
const srcStat = fs.statSync(srcpath)
const dstStat = fs.statSync(dstpath)
if (areIdentical(srcStat, dstStat)) return
}
const relative = symlinkPathsSync(srcpath, dstpath)
srcpath = relative.toDst
type = symlinkTypeSync(relative.toCwd, type)
const dir = path.dirname(dstpath)
const exists = fs.existsSync(dir)
if (exists) return fs.symlinkSync(srcpath, dstpath, type)
mkdirsSync(dir)
return fs.symlinkSync(srcpath, dstpath, type)
}
module.exports = {
createSymlink: u(createSymlink),
createSymlinkSync
}
@@ -0,0 +1,68 @@
import _copy from './copy/index.js'
import _empty from './empty/index.js'
import _ensure from './ensure/index.js'
import _json from './json/index.js'
import _mkdirs from './mkdirs/index.js'
import _move from './move/index.js'
import _outputFile from './output-file/index.js'
import _pathExists from './path-exists/index.js'
import _remove from './remove/index.js'
// NOTE: Only exports fs-extra's functions; fs functions must be imported from "node:fs" or "node:fs/promises"
export const copy = _copy.copy
export const copySync = _copy.copySync
export const emptyDirSync = _empty.emptyDirSync
export const emptydirSync = _empty.emptydirSync
export const emptyDir = _empty.emptyDir
export const emptydir = _empty.emptydir
export const createFile = _ensure.createFile
export const createFileSync = _ensure.createFileSync
export const ensureFile = _ensure.ensureFile
export const ensureFileSync = _ensure.ensureFileSync
export const createLink = _ensure.createLink
export const createLinkSync = _ensure.createLinkSync
export const ensureLink = _ensure.ensureLink
export const ensureLinkSync = _ensure.ensureLinkSync
export const createSymlink = _ensure.createSymlink
export const createSymlinkSync = _ensure.createSymlinkSync
export const ensureSymlink = _ensure.ensureSymlink
export const ensureSymlinkSync = _ensure.ensureSymlinkSync
export const readJson = _json.readJson
export const readJSON = _json.readJSON
export const readJsonSync = _json.readJsonSync
export const readJSONSync = _json.readJSONSync
export const writeJson = _json.writeJson
export const writeJSON = _json.writeJSON
export const writeJsonSync = _json.writeJsonSync
export const writeJSONSync = _json.writeJSONSync
export const outputJson = _json.outputJson
export const outputJSON = _json.outputJSON
export const outputJsonSync = _json.outputJsonSync
export const outputJSONSync = _json.outputJSONSync
export const mkdirs = _mkdirs.mkdirs
export const mkdirsSync = _mkdirs.mkdirsSync
export const mkdirp = _mkdirs.mkdirp
export const mkdirpSync = _mkdirs.mkdirpSync
export const ensureDir = _mkdirs.ensureDir
export const ensureDirSync = _mkdirs.ensureDirSync
export const move = _move.move
export const moveSync = _move.moveSync
export const outputFile = _outputFile.outputFile
export const outputFileSync = _outputFile.outputFileSync
export const pathExists = _pathExists.pathExists
export const pathExistsSync = _pathExists.pathExistsSync
export const remove = _remove.remove
export const removeSync = _remove.removeSync
export default {
..._copy,
..._empty,
..._ensure,
..._json,
..._mkdirs,
..._move,
..._outputFile,
..._pathExists,
..._remove
}
@@ -0,0 +1,146 @@
'use strict'
// This is adapted from https://github.com/normalize/mz
// Copyright (c) 2014-2016 Jonathan Ong me@jongleberry.com and Contributors
const u = require('universalify').fromCallback
const fs = require('graceful-fs')
const api = [
'access',
'appendFile',
'chmod',
'chown',
'close',
'copyFile',
'cp',
'fchmod',
'fchown',
'fdatasync',
'fstat',
'fsync',
'ftruncate',
'futimes',
'glob',
'lchmod',
'lchown',
'lutimes',
'link',
'lstat',
'mkdir',
'mkdtemp',
'open',
'opendir',
'readdir',
'readFile',
'readlink',
'realpath',
'rename',
'rm',
'rmdir',
'stat',
'statfs',
'symlink',
'truncate',
'unlink',
'utimes',
'writeFile'
].filter(key => {
// Some commands are not available on some systems. Ex:
// fs.cp was added in Node.js v16.7.0
// fs.statfs was added in Node v19.6.0, v18.15.0
// fs.glob was added in Node.js v22.0.0
// fs.lchown is not available on at least some Linux
return typeof fs[key] === 'function'
})
// Export cloned fs:
Object.assign(exports, fs)
// Universalify async methods:
api.forEach(method => {
exports[method] = u(fs[method])
})
// We differ from mz/fs in that we still ship the old, broken, fs.exists()
// since we are a drop-in replacement for the native module
exports.exists = function (filename, callback) {
if (typeof callback === 'function') {
return fs.exists(filename, callback)
}
return new Promise(resolve => {
return fs.exists(filename, resolve)
})
}
// fs.read(), fs.write(), fs.readv(), & fs.writev() need special treatment due to multiple callback args
exports.read = function (fd, buffer, offset, length, position, callback) {
if (typeof callback === 'function') {
return fs.read(fd, buffer, offset, length, position, callback)
}
return new Promise((resolve, reject) => {
fs.read(fd, buffer, offset, length, position, (err, bytesRead, buffer) => {
if (err) return reject(err)
resolve({ bytesRead, buffer })
})
})
}
// Function signature can be
// fs.write(fd, buffer[, offset[, length[, position]]], callback)
// OR
// fs.write(fd, string[, position[, encoding]], callback)
// We need to handle both cases, so we use ...args
exports.write = function (fd, buffer, ...args) {
if (typeof args[args.length - 1] === 'function') {
return fs.write(fd, buffer, ...args)
}
return new Promise((resolve, reject) => {
fs.write(fd, buffer, ...args, (err, bytesWritten, buffer) => {
if (err) return reject(err)
resolve({ bytesWritten, buffer })
})
})
}
// Function signature is
// s.readv(fd, buffers[, position], callback)
// We need to handle the optional arg, so we use ...args
exports.readv = function (fd, buffers, ...args) {
if (typeof args[args.length - 1] === 'function') {
return fs.readv(fd, buffers, ...args)
}
return new Promise((resolve, reject) => {
fs.readv(fd, buffers, ...args, (err, bytesRead, buffers) => {
if (err) return reject(err)
resolve({ bytesRead, buffers })
})
})
}
// Function signature is
// s.writev(fd, buffers[, position], callback)
// We need to handle the optional arg, so we use ...args
exports.writev = function (fd, buffers, ...args) {
if (typeof args[args.length - 1] === 'function') {
return fs.writev(fd, buffers, ...args)
}
return new Promise((resolve, reject) => {
fs.writev(fd, buffers, ...args, (err, bytesWritten, buffers) => {
if (err) return reject(err)
resolve({ bytesWritten, buffers })
})
})
}
// fs.realpath.native sometimes not available if fs is monkey-patched
if (typeof fs.realpath.native === 'function') {
exports.realpath.native = u(fs.realpath.native)
} else {
process.emitWarning(
'fs.realpath.native is not a function. Is fs being monkey-patched?',
'Warning', 'fs-extra-WARN0003'
)
}
@@ -0,0 +1,16 @@
'use strict'
module.exports = {
// Export promiseified graceful-fs:
...require('./fs'),
// Export extra methods:
...require('./copy'),
...require('./empty'),
...require('./ensure'),
...require('./json'),
...require('./mkdirs'),
...require('./move'),
...require('./output-file'),
...require('./path-exists'),
...require('./remove')
}
@@ -0,0 +1,16 @@
'use strict'
const u = require('universalify').fromPromise
const jsonFile = require('./jsonfile')
jsonFile.outputJson = u(require('./output-json'))
jsonFile.outputJsonSync = require('./output-json-sync')
// aliases
jsonFile.outputJSON = jsonFile.outputJson
jsonFile.outputJSONSync = jsonFile.outputJsonSync
jsonFile.writeJSON = jsonFile.writeJson
jsonFile.writeJSONSync = jsonFile.writeJsonSync
jsonFile.readJSON = jsonFile.readJson
jsonFile.readJSONSync = jsonFile.readJsonSync
module.exports = jsonFile
@@ -0,0 +1,11 @@
'use strict'
const jsonFile = require('jsonfile')
module.exports = {
// jsonfile exports
readJson: jsonFile.readFile,
readJsonSync: jsonFile.readFileSync,
writeJson: jsonFile.writeFile,
writeJsonSync: jsonFile.writeFileSync
}
@@ -0,0 +1,12 @@
'use strict'
const { stringify } = require('jsonfile/utils')
const { outputFileSync } = require('../output-file')
function outputJsonSync (file, data, options) {
const str = stringify(data, options)
outputFileSync(file, str, options)
}
module.exports = outputJsonSync
@@ -0,0 +1,12 @@
'use strict'
const { stringify } = require('jsonfile/utils')
const { outputFile } = require('../output-file')
async function outputJson (file, data, options = {}) {
const str = stringify(data, options)
await outputFile(file, str, options)
}
module.exports = outputJson
@@ -0,0 +1,14 @@
'use strict'
const u = require('universalify').fromPromise
const { makeDir: _makeDir, makeDirSync } = require('./make-dir')
const makeDir = u(_makeDir)
module.exports = {
mkdirs: makeDir,
mkdirsSync: makeDirSync,
// alias
mkdirp: makeDir,
mkdirpSync: makeDirSync,
ensureDir: makeDir,
ensureDirSync: makeDirSync
}
@@ -0,0 +1,27 @@
'use strict'
const fs = require('../fs')
const { checkPath } = require('./utils')
const getMode = options => {
const defaults = { mode: 0o777 }
if (typeof options === 'number') return options
return ({ ...defaults, ...options }).mode
}
module.exports.makeDir = async (dir, options) => {
checkPath(dir)
return fs.mkdir(dir, {
mode: getMode(options),
recursive: true
})
}
module.exports.makeDirSync = (dir, options) => {
checkPath(dir)
return fs.mkdirSync(dir, {
mode: getMode(options),
recursive: true
})
}
@@ -0,0 +1,21 @@
// Adapted from https://github.com/sindresorhus/make-dir
// Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com)
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
'use strict'
const path = require('path')
// https://github.com/nodejs/node/issues/8987
// https://github.com/libuv/libuv/pull/1088
module.exports.checkPath = function checkPath (pth) {
if (process.platform === 'win32') {
const pathHasInvalidWinCharacters = /[<>:"|?*]/.test(pth.replace(path.parse(pth).root, ''))
if (pathHasInvalidWinCharacters) {
const error = new Error(`Path contains invalid characters: ${pth}`)
error.code = 'EINVAL'
throw error
}
}
}
@@ -0,0 +1,7 @@
'use strict'
const u = require('universalify').fromPromise
module.exports = {
move: u(require('./move')),
moveSync: require('./move-sync')
}
@@ -0,0 +1,55 @@
'use strict'
const fs = require('graceful-fs')
const path = require('path')
const copySync = require('../copy').copySync
const removeSync = require('../remove').removeSync
const mkdirpSync = require('../mkdirs').mkdirpSync
const stat = require('../util/stat')
function moveSync (src, dest, opts) {
opts = opts || {}
const overwrite = opts.overwrite || opts.clobber || false
const { srcStat, isChangingCase = false } = stat.checkPathsSync(src, dest, 'move', opts)
stat.checkParentPathsSync(src, srcStat, dest, 'move')
if (!isParentRoot(dest)) mkdirpSync(path.dirname(dest))
return doRename(src, dest, overwrite, isChangingCase)
}
function isParentRoot (dest) {
const parent = path.dirname(dest)
const parsedPath = path.parse(parent)
return parsedPath.root === parent
}
function doRename (src, dest, overwrite, isChangingCase) {
if (isChangingCase) return rename(src, dest, overwrite)
if (overwrite) {
removeSync(dest)
return rename(src, dest, overwrite)
}
if (fs.existsSync(dest)) throw new Error('dest already exists.')
return rename(src, dest, overwrite)
}
function rename (src, dest, overwrite) {
try {
fs.renameSync(src, dest)
} catch (err) {
if (err.code !== 'EXDEV') throw err
return moveAcrossDevice(src, dest, overwrite)
}
}
function moveAcrossDevice (src, dest, overwrite) {
const opts = {
overwrite,
errorOnExist: true,
preserveTimestamps: true
}
copySync(src, dest, opts)
return removeSync(src)
}
module.exports = moveSync
@@ -0,0 +1,59 @@
'use strict'
const fs = require('../fs')
const path = require('path')
const { copy } = require('../copy')
const { remove } = require('../remove')
const { mkdirp } = require('../mkdirs')
const { pathExists } = require('../path-exists')
const stat = require('../util/stat')
async function move (src, dest, opts = {}) {
const overwrite = opts.overwrite || opts.clobber || false
const { srcStat, isChangingCase = false } = await stat.checkPaths(src, dest, 'move', opts)
await stat.checkParentPaths(src, srcStat, dest, 'move')
// If the parent of dest is not root, make sure it exists before proceeding
const destParent = path.dirname(dest)
const parsedParentPath = path.parse(destParent)
if (parsedParentPath.root !== destParent) {
await mkdirp(destParent)
}
return doRename(src, dest, overwrite, isChangingCase)
}
async function doRename (src, dest, overwrite, isChangingCase) {
if (!isChangingCase) {
if (overwrite) {
await remove(dest)
} else if (await pathExists(dest)) {
throw new Error('dest already exists.')
}
}
try {
// Try w/ rename first, and try copy + remove if EXDEV
await fs.rename(src, dest)
} catch (err) {
if (err.code !== 'EXDEV') {
throw err
}
await moveAcrossDevice(src, dest, overwrite)
}
}
async function moveAcrossDevice (src, dest, overwrite) {
const opts = {
overwrite,
errorOnExist: true,
preserveTimestamps: true
}
await copy(src, dest, opts)
return remove(src)
}
module.exports = move
@@ -0,0 +1,31 @@
'use strict'
const u = require('universalify').fromPromise
const fs = require('../fs')
const path = require('path')
const mkdir = require('../mkdirs')
const pathExists = require('../path-exists').pathExists
async function outputFile (file, data, encoding = 'utf-8') {
const dir = path.dirname(file)
if (!(await pathExists(dir))) {
await mkdir.mkdirs(dir)
}
return fs.writeFile(file, data, encoding)
}
function outputFileSync (file, ...args) {
const dir = path.dirname(file)
if (!fs.existsSync(dir)) {
mkdir.mkdirsSync(dir)
}
fs.writeFileSync(file, ...args)
}
module.exports = {
outputFile: u(outputFile),
outputFileSync
}
@@ -0,0 +1,12 @@
'use strict'
const u = require('universalify').fromPromise
const fs = require('../fs')
function pathExists (path) {
return fs.access(path).then(() => true).catch(() => false)
}
module.exports = {
pathExists: u(pathExists),
pathExistsSync: fs.existsSync
}
@@ -0,0 +1,17 @@
'use strict'
const fs = require('graceful-fs')
const u = require('universalify').fromCallback
function remove (path, callback) {
fs.rm(path, { recursive: true, force: true }, callback)
}
function removeSync (path) {
fs.rmSync(path, { recursive: true, force: true })
}
module.exports = {
remove: u(remove),
removeSync
}
@@ -0,0 +1,158 @@
'use strict'
const fs = require('../fs')
const path = require('path')
const u = require('universalify').fromPromise
function getStats (src, dest, opts) {
const statFunc = opts.dereference
? (file) => fs.stat(file, { bigint: true })
: (file) => fs.lstat(file, { bigint: true })
return Promise.all([
statFunc(src),
statFunc(dest).catch(err => {
if (err.code === 'ENOENT') return null
throw err
})
]).then(([srcStat, destStat]) => ({ srcStat, destStat }))
}
function getStatsSync (src, dest, opts) {
let destStat
const statFunc = opts.dereference
? (file) => fs.statSync(file, { bigint: true })
: (file) => fs.lstatSync(file, { bigint: true })
const srcStat = statFunc(src)
try {
destStat = statFunc(dest)
} catch (err) {
if (err.code === 'ENOENT') return { srcStat, destStat: null }
throw err
}
return { srcStat, destStat }
}
async function checkPaths (src, dest, funcName, opts) {
const { srcStat, destStat } = await getStats(src, dest, opts)
if (destStat) {
if (areIdentical(srcStat, destStat)) {
const srcBaseName = path.basename(src)
const destBaseName = path.basename(dest)
if (funcName === 'move' &&
srcBaseName !== destBaseName &&
srcBaseName.toLowerCase() === destBaseName.toLowerCase()) {
return { srcStat, destStat, isChangingCase: true }
}
throw new Error('Source and destination must not be the same.')
}
if (srcStat.isDirectory() && !destStat.isDirectory()) {
throw new Error(`Cannot overwrite non-directory '${dest}' with directory '${src}'.`)
}
if (!srcStat.isDirectory() && destStat.isDirectory()) {
throw new Error(`Cannot overwrite directory '${dest}' with non-directory '${src}'.`)
}
}
if (srcStat.isDirectory() && isSrcSubdir(src, dest)) {
throw new Error(errMsg(src, dest, funcName))
}
return { srcStat, destStat }
}
function checkPathsSync (src, dest, funcName, opts) {
const { srcStat, destStat } = getStatsSync(src, dest, opts)
if (destStat) {
if (areIdentical(srcStat, destStat)) {
const srcBaseName = path.basename(src)
const destBaseName = path.basename(dest)
if (funcName === 'move' &&
srcBaseName !== destBaseName &&
srcBaseName.toLowerCase() === destBaseName.toLowerCase()) {
return { srcStat, destStat, isChangingCase: true }
}
throw new Error('Source and destination must not be the same.')
}
if (srcStat.isDirectory() && !destStat.isDirectory()) {
throw new Error(`Cannot overwrite non-directory '${dest}' with directory '${src}'.`)
}
if (!srcStat.isDirectory() && destStat.isDirectory()) {
throw new Error(`Cannot overwrite directory '${dest}' with non-directory '${src}'.`)
}
}
if (srcStat.isDirectory() && isSrcSubdir(src, dest)) {
throw new Error(errMsg(src, dest, funcName))
}
return { srcStat, destStat }
}
// recursively check if dest parent is a subdirectory of src.
// It works for all file types including symlinks since it
// checks the src and dest inodes. It starts from the deepest
// parent and stops once it reaches the src parent or the root path.
async function checkParentPaths (src, srcStat, dest, funcName) {
const srcParent = path.resolve(path.dirname(src))
const destParent = path.resolve(path.dirname(dest))
if (destParent === srcParent || destParent === path.parse(destParent).root) return
let destStat
try {
destStat = await fs.stat(destParent, { bigint: true })
} catch (err) {
if (err.code === 'ENOENT') return
throw err
}
if (areIdentical(srcStat, destStat)) {
throw new Error(errMsg(src, dest, funcName))
}
return checkParentPaths(src, srcStat, destParent, funcName)
}
function checkParentPathsSync (src, srcStat, dest, funcName) {
const srcParent = path.resolve(path.dirname(src))
const destParent = path.resolve(path.dirname(dest))
if (destParent === srcParent || destParent === path.parse(destParent).root) return
let destStat
try {
destStat = fs.statSync(destParent, { bigint: true })
} catch (err) {
if (err.code === 'ENOENT') return
throw err
}
if (areIdentical(srcStat, destStat)) {
throw new Error(errMsg(src, dest, funcName))
}
return checkParentPathsSync(src, srcStat, destParent, funcName)
}
function areIdentical (srcStat, destStat) {
return destStat.ino && destStat.dev && destStat.ino === srcStat.ino && destStat.dev === srcStat.dev
}
// return true if dest is a subdir of src, otherwise false.
// It only checks the path strings.
function isSrcSubdir (src, dest) {
const srcArr = path.resolve(src).split(path.sep).filter(i => i)
const destArr = path.resolve(dest).split(path.sep).filter(i => i)
return srcArr.every((cur, i) => destArr[i] === cur)
}
function errMsg (src, dest, funcName) {
return `Cannot ${funcName} '${src}' to a subdirectory of itself, '${dest}'.`
}
module.exports = {
// checkPaths
checkPaths: u(checkPaths),
checkPathsSync,
// checkParent
checkParentPaths: u(checkParentPaths),
checkParentPathsSync,
// Misc
isSrcSubdir,
areIdentical
}
@@ -0,0 +1,36 @@
'use strict'
const fs = require('../fs')
const u = require('universalify').fromPromise
async function utimesMillis (path, atime, mtime) {
// if (!HAS_MILLIS_RES) return fs.utimes(path, atime, mtime, callback)
const fd = await fs.open(path, 'r+')
let closeErr = null
try {
await fs.futimes(fd, atime, mtime)
} finally {
try {
await fs.close(fd)
} catch (e) {
closeErr = e
}
}
if (closeErr) {
throw closeErr
}
}
function utimesMillisSync (path, atime, mtime) {
const fd = fs.openSync(path, 'r+')
fs.futimesSync(fd, atime, mtime)
return fs.closeSync(fd)
}
module.exports = {
utimesMillis: u(utimesMillis),
utimesMillisSync
}
@@ -0,0 +1,71 @@
{
"name": "fs-extra",
"version": "11.3.0",
"description": "fs-extra contains methods that aren't included in the vanilla Node.js fs package. Such as recursive mkdir, copy, and remove.",
"engines": {
"node": ">=14.14"
},
"homepage": "https://github.com/jprichardson/node-fs-extra",
"repository": {
"type": "git",
"url": "https://github.com/jprichardson/node-fs-extra"
},
"keywords": [
"fs",
"file",
"file system",
"copy",
"directory",
"extra",
"mkdirp",
"mkdir",
"mkdirs",
"recursive",
"json",
"read",
"write",
"extra",
"delete",
"remove",
"touch",
"create",
"text",
"output",
"move",
"promise"
],
"author": "JP Richardson <jprichardson@gmail.com>",
"license": "MIT",
"dependencies": {
"graceful-fs": "^4.2.0",
"jsonfile": "^6.0.1",
"universalify": "^2.0.0"
},
"devDependencies": {
"klaw": "^2.1.1",
"klaw-sync": "^3.0.2",
"minimist": "^1.1.1",
"mocha": "^10.1.0",
"nyc": "^15.0.0",
"proxyquire": "^2.0.1",
"read-dir-files": "^0.1.1",
"standard": "^17.0.0"
},
"main": "./lib/index.js",
"exports": {
".": "./lib/index.js",
"./esm": "./lib/esm.mjs"
},
"files": [
"lib/",
"!lib/**/__tests__/"
],
"scripts": {
"lint": "standard",
"test-find": "find ./lib/**/__tests__ -name *.test.js | xargs mocha",
"test": "npm run lint && npm run unit && npm run unit-esm",
"unit": "nyc node test.js",
"unit-esm": "node test.mjs"
},
"sideEffects": false
}
+152
View File
@@ -0,0 +1,152 @@
{
"name": "@microsoft/m365-spec-parser",
"version": "0.2.5",
"description": "OpenAPI specification files Parser for M365 Apps",
"main": "dist/index.node.cjs.js",
"browser": "dist/index.esm2017.js",
"module": "dist/index.esm2017.mjs",
"esm5": "dist/index.esm5.js",
"types": "dist/src/index.d.ts",
"scripts": {
"build": "rollup -c",
"test:unit:node": "nyc --no-clean -- mocha -r config/mocha.env.ts --config config/.mocharc.json",
"test:unit:browser": "karma start karma.conf.cjs --single-run --unit",
"test:unit": "npm run test:unit:node && npm run test:unit:browser ",
"lint:staged": "lint-staged",
"lint": "eslint \"**/*.ts\"",
"check-sensitive": "npx eslint --plugin 'no-secrets' --cache --ignore-pattern 'package.json' --ignore-pattern 'package-lock.json'",
"precommit": "npm run check-sensitive && lint-staged",
"check-format": "prettier --list-different \"src/**/*.ts\" \"test/**/*.ts\" \"*.{js,json}\"",
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\" \"*.{js,json}\""
},
"files": [
"dist/*js",
"dist/*js.map",
"dist/src/*d.ts",
"README.md",
"LICENSE"
],
"repository": "https://github.com/OfficeDev/TeamsFx",
"engines": {
"node": ">=10.0.0"
},
"keywords": [
"typescript"
],
"author": "Microsoft Corporation",
"license": "MIT",
"homepage": "https://github.com/OfficeDev/TeamsFx",
"sideEffects": false,
"dependencies": {
"@apidevtools/swagger-parser": "^10.1.1",
"@microsoft/teams-manifest": "0.1.8",
"fs-extra": "^11.2.0",
"js-yaml": "^4.1.0",
"openapi-types": "^7.2.3",
"swagger2openapi": "^7.0.8"
},
"publishConfig": {
"access": "public"
},
"devDependencies": {
"@apidevtools/json-schema-ref-parser": "^9.0.6",
"@apidevtools/openapi-schemas": "2.1.0",
"@apidevtools/swagger-methods": "3.0.2",
"@istanbuljs/nyc-config-typescript": "^1.0.2",
"@jsdevtools/ono": "^7.1.3",
"@rollup/plugin-json": "^6.1.0",
"@sinonjs/commons": "^3.0.0",
"@sinonjs/fake-timers": "^11.1.0",
"@sinonjs/samsam": "^8.0.0",
"@types/chai": "^4.2.22",
"@types/fs-extra": "^11.0.4",
"@types/js-yaml": "^4.0.9",
"@types/mocha": "^9.0.0",
"@types/node": "^16.11.7",
"@types/sinon": "^10.0.6",
"@types/swagger2openapi": "^7.0.4",
"@typescript-eslint/eslint-plugin": "^4.19.0",
"@typescript-eslint/parser": "^4.19.0",
"ajv-draft-04": "1.0.0",
"assertion-error": "^2.0.0",
"available-typed-arrays": "1.0.6",
"browser-stdout": "1.3.1",
"call-bind": "1.0.6",
"call-me-maybe": "^1.0.2",
"chai": "^4.3.4",
"check-error": "1.0.3",
"deep-eql": "3.0.1",
"define-data-property": "1.1.2",
"dotenv": "^16.4.1",
"es-errors": "1.3.0",
"eslint": "^7.29.0",
"eslint-plugin-header": "^3.1.1",
"eslint-plugin-import": "^2.25.2",
"eslint-plugin-no-secrets": "^0.8.9",
"eslint-plugin-prettier": "^4.0.0",
"events": "3.3.0",
"for-each": "0.3.3",
"get-func-name": "2.0.2",
"get-intrinsic": "1.2.4",
"gopd": "1.0.1",
"has-property-descriptors": "1.0.1",
"has-proto": "1.0.1",
"has-symbols": "1.0.3",
"has-tostringtag": "1.0.2",
"he": "1.2.0",
"https-browserify": "^1.0.0",
"is-arguments": "1.1.1",
"is-callable": "1.2.7",
"is-generator-function": "1.0.10",
"is-typed-array": "1.1.13",
"karma": "^6.3.8",
"karma-chrome-launcher": "^3.1.0",
"karma-cli": "^2.0.0",
"karma-coverage": "^2.0.0",
"karma-env-preprocessor": "^0.1.1",
"karma-junit-reporter": "^2.0.1",
"karma-mocha": "^2.0.1",
"karma-mocha-reporter": "^2.2.5",
"karma-sourcemap-loader": "^0.3.8",
"karma-webpack": "^5.0.0",
"lint-staged": "^10.5.4",
"lodash.get": "^4.4.2",
"mocha": "^9.2.0",
"mock-fs": "^5.2.0",
"mocked-env": "^1.3.5",
"nanoid": "3.2.0",
"nise": "^5.1.4",
"nyc": "^15.1.0",
"object-inspect": "1.13.1",
"path-browserify": "^1.0.1",
"pathval": "^2.0.0",
"prettier": "^2.4.1",
"process": "^0.11.10",
"puppeteer": "^13.1.3",
"qs": "6.11.0",
"rollup": "^2.41.0",
"rollup-plugin-typescript2": "^0.31.0",
"set-function-length": "1.2.0",
"side-channel": "1.0.4",
"sinon": "^12.0.1",
"source-map-loader": "^3.0.0",
"stream-browserify": "^3.0.0",
"stream-http": "^3.2.0",
"timers": "^0.1.1",
"ts-loader": "^9.2.6",
"ts-node": "^10.4.0",
"tslib": "^2.3.1",
"type-detect": "^4.0.8",
"typescript": "^4.4.4",
"url": "^0.11.3",
"util": "^0.12.5",
"webpack": "^5.62.1",
"which-typed-array": "1.1.14"
},
"lint-staged": {
"*.{js,jsx,css,ts,tsx}": [
"npx eslint --cache --fix --quiet"
]
},
"gitHead": "a1986c931c6f320dbe14fd56e46fe64a7e9560f5"
}