@fluidframework/fluid-runner 2.0.0-dev-rc.1.0.0.224419

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/.eslintrc.cjs +17 -0
  2. package/.mocharc.js +12 -0
  3. package/CHANGELOG.md +120 -0
  4. package/LICENSE +21 -0
  5. package/README.md +101 -0
  6. package/api-extractor-lint.json +13 -0
  7. package/api-extractor.json +17 -0
  8. package/api-report/fluid-runner.api.md +89 -0
  9. package/bin/fluid-runner +2 -0
  10. package/dist/codeLoaderBundle.d.ts +46 -0
  11. package/dist/codeLoaderBundle.d.ts.map +1 -0
  12. package/dist/codeLoaderBundle.js +25 -0
  13. package/dist/codeLoaderBundle.js.map +1 -0
  14. package/dist/exportFile.d.ts +33 -0
  15. package/dist/exportFile.d.ts.map +1 -0
  16. package/dist/exportFile.js +120 -0
  17. package/dist/exportFile.js.map +1 -0
  18. package/dist/fakeUrlResolver.d.ts +15 -0
  19. package/dist/fakeUrlResolver.d.ts.map +1 -0
  20. package/dist/fakeUrlResolver.js +43 -0
  21. package/dist/fakeUrlResolver.js.map +1 -0
  22. package/dist/fluid-runner-alpha.d.ts +79 -0
  23. package/dist/fluid-runner-beta.d.ts +52 -0
  24. package/dist/fluid-runner-public.d.ts +52 -0
  25. package/dist/fluid-runner-untrimmed.d.ts +175 -0
  26. package/dist/fluidRunner.d.ts +11 -0
  27. package/dist/fluidRunner.d.ts.map +1 -0
  28. package/dist/fluidRunner.js +126 -0
  29. package/dist/fluidRunner.js.map +1 -0
  30. package/dist/index.d.ts +12 -0
  31. package/dist/index.d.ts.map +1 -0
  32. package/dist/index.js +24 -0
  33. package/dist/index.js.map +1 -0
  34. package/dist/logger/baseFileLogger.d.ts +28 -0
  35. package/dist/logger/baseFileLogger.d.ts.map +1 -0
  36. package/dist/logger/baseFileLogger.js +76 -0
  37. package/dist/logger/baseFileLogger.js.map +1 -0
  38. package/dist/logger/csvFileLogger.d.ts +18 -0
  39. package/dist/logger/csvFileLogger.d.ts.map +1 -0
  40. package/dist/logger/csvFileLogger.js +64 -0
  41. package/dist/logger/csvFileLogger.js.map +1 -0
  42. package/dist/logger/fileLogger.d.ts +44 -0
  43. package/dist/logger/fileLogger.d.ts.map +1 -0
  44. package/dist/logger/fileLogger.js +18 -0
  45. package/dist/logger/fileLogger.js.map +1 -0
  46. package/dist/logger/jsonFileLogger.d.ts +14 -0
  47. package/dist/logger/jsonFileLogger.d.ts.map +1 -0
  48. package/dist/logger/jsonFileLogger.js +48 -0
  49. package/dist/logger/jsonFileLogger.js.map +1 -0
  50. package/dist/logger/loggerUtils.d.ts +44 -0
  51. package/dist/logger/loggerUtils.d.ts.map +1 -0
  52. package/dist/logger/loggerUtils.js +120 -0
  53. package/dist/logger/loggerUtils.js.map +1 -0
  54. package/dist/parseBundleAndExportFile.d.ts +13 -0
  55. package/dist/parseBundleAndExportFile.d.ts.map +1 -0
  56. package/dist/parseBundleAndExportFile.js +88 -0
  57. package/dist/parseBundleAndExportFile.js.map +1 -0
  58. package/dist/tsdoc-metadata.json +11 -0
  59. package/dist/utils.d.ts +33 -0
  60. package/dist/utils.d.ts.map +1 -0
  61. package/dist/utils.js +107 -0
  62. package/dist/utils.js.map +1 -0
  63. package/package.json +120 -0
  64. package/prettier.config.cjs +8 -0
  65. package/src/codeLoaderBundle.ts +63 -0
  66. package/src/exportFile.ts +148 -0
  67. package/src/fakeUrlResolver.ts +54 -0
  68. package/src/fluidRunner.ts +135 -0
  69. package/src/index.ts +18 -0
  70. package/src/logger/baseFileLogger.ts +58 -0
  71. package/src/logger/csvFileLogger.ts +40 -0
  72. package/src/logger/fileLogger.ts +51 -0
  73. package/src/logger/jsonFileLogger.ts +27 -0
  74. package/src/logger/loggerUtils.ts +108 -0
  75. package/src/parseBundleAndExportFile.ts +92 -0
  76. package/src/utils.ts +100 -0
  77. package/tsconfig.json +13 -0
@@ -0,0 +1,135 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+
6
+ import * as yargs from "yargs";
7
+ import { exportFile } from "./exportFile";
8
+ import { IFluidFileConverter } from "./codeLoaderBundle";
9
+ import { parseBundleAndExportFile } from "./parseBundleAndExportFile";
10
+ // eslint-disable-next-line import/no-internal-modules
11
+ import { validateAndParseTelemetryOptions } from "./logger/loggerUtils";
12
+ import { validateCommandLineArgs } from "./utils";
13
+
14
+ /**
15
+ * @param fluidFileConverter - needs to be provided if "codeLoaderBundle" is not and vice versa
16
+ * @internal
17
+ */
18
+ export function fluidRunner(fluidFileConverter?: IFluidFileConverter) {
19
+ // eslint-disable-next-line @typescript-eslint/no-unused-expressions
20
+ yargs
21
+ .strict()
22
+ .version(false)
23
+ .command(
24
+ "exportFile",
25
+ "Generate an output for a local ODSP snapshot",
26
+ // eslint-disable-next-line @typescript-eslint/no-shadow
27
+ (yargs) =>
28
+ yargs
29
+ .option("codeLoader", {
30
+ describe:
31
+ 'Path to code loader bundle. Required if this application is being called without modification.\nSee "README.md" for more details.',
32
+ type: "string",
33
+ demandOption: false,
34
+ })
35
+ .option("inputFile", {
36
+ describe: "Path to local ODSP snapshot",
37
+ type: "string",
38
+ demandOption: true,
39
+ })
40
+ .option("outputFile", {
41
+ describe:
42
+ "Path of output file (cannot already exist).\nExecution result will be written here",
43
+ type: "string",
44
+ demandOption: true,
45
+ })
46
+ .option("telemetryFile", {
47
+ describe:
48
+ "Path of telemetry file for config and session data (cannot already exist)",
49
+ type: "string",
50
+ demandOption: true,
51
+ })
52
+ .option("options", {
53
+ describe: "Additional options passed to container on execution",
54
+ type: "string",
55
+ demandOption: false,
56
+ })
57
+ .option("telemetryFormat", {
58
+ describe:
59
+ 'Output format for telemetry. Current options are: ["JSON", "CSV"]',
60
+ type: "string",
61
+ demandOption: false,
62
+ default: "JSON",
63
+ })
64
+ .option("telemetryProp", {
65
+ describe:
66
+ 'Property to add to every telemetry entry. Formatted like "--telemetryProp prop1 value1 --telemetryProp prop2 \\"value 2\\"".',
67
+ type: "array",
68
+ demandOption: false,
69
+ })
70
+ .option("eventsPerFlush", {
71
+ describe:
72
+ "Number of telemetry events per flush to telemetryFile (only applicable for JSON format)",
73
+ type: "number",
74
+ demandOption: false,
75
+ })
76
+ .option("timeout", {
77
+ describe: "Allowed timeout in ms before process is automatically cancelled",
78
+ type: "number",
79
+ demandOption: false,
80
+ })
81
+ .option("disableNetworkFetch", {
82
+ describe: "Should network fetch calls be explicitly disabled?",
83
+ type: "boolean",
84
+ demandOption: false,
85
+ default: false,
86
+ }),
87
+ // eslint-disable-next-line @typescript-eslint/no-misused-promises
88
+ async (argv) => {
89
+ const argsError = validateCommandLineArgs(argv.codeLoader, fluidFileConverter);
90
+ if (argsError) {
91
+ console.error(argsError);
92
+ process.exit(1);
93
+ }
94
+ const telemetryOptionsResult = validateAndParseTelemetryOptions(
95
+ argv.telemetryFormat,
96
+ argv.telemetryProp,
97
+ argv.eventsPerFlush,
98
+ );
99
+ if (!telemetryOptionsResult.success) {
100
+ console.error(telemetryOptionsResult.error);
101
+ process.exit(1);
102
+ }
103
+
104
+ const result = await (argv.codeLoader
105
+ ? parseBundleAndExportFile(
106
+ argv.codeLoader,
107
+ argv.inputFile,
108
+ argv.outputFile,
109
+ argv.telemetryFile,
110
+ argv.options,
111
+ telemetryOptionsResult.telemetryOptions,
112
+ argv.timeout,
113
+ argv.disableNetworkFetch,
114
+ )
115
+ : exportFile(
116
+ fluidFileConverter!,
117
+ argv.inputFile,
118
+ argv.outputFile,
119
+ argv.telemetryFile,
120
+ argv.options,
121
+ telemetryOptionsResult.telemetryOptions,
122
+ argv.timeout,
123
+ argv.disableNetworkFetch,
124
+ ));
125
+
126
+ if (!result.success) {
127
+ console.error(`${result.eventName}: ${result.errorMessage}`);
128
+ process.exit(1);
129
+ }
130
+ process.exit(0);
131
+ },
132
+ )
133
+ .help()
134
+ .demandCommand().argv;
135
+ }
package/src/index.ts ADDED
@@ -0,0 +1,18 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+
6
+ /* eslint-disable import/no-internal-modules */
7
+ export { ICodeLoaderBundle, IFluidFileConverter } from "./codeLoaderBundle";
8
+ export { createContainerAndExecute, exportFile, IExportFileResponse } from "./exportFile";
9
+ export { fluidRunner } from "./fluidRunner";
10
+ export { OutputFormat, ITelemetryOptions } from "./logger/fileLogger";
11
+ export {
12
+ createLogger,
13
+ getTelemetryFileValidationError,
14
+ validateAndParseTelemetryOptions,
15
+ } from "./logger/loggerUtils";
16
+ export { parseBundleAndExportFile } from "./parseBundleAndExportFile";
17
+ export { getSnapshotFileContent } from "./utils";
18
+ /* eslint-enable import/no-internal-modules */
@@ -0,0 +1,58 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+
6
+ import * as fs from "fs";
7
+ import { ITelemetryBaseEvent } from "@fluidframework/core-interfaces";
8
+ import { IFileLogger } from "./fileLogger";
9
+
10
+ /**
11
+ * @internal
12
+ */
13
+ export abstract class BaseFileLogger implements IFileLogger {
14
+ public supportsTags?: true | undefined;
15
+
16
+ /** Hold events in memory until flushed */
17
+ protected events: any[] = [];
18
+ protected hasWrittenToFile = false;
19
+
20
+ /**
21
+ * @param filePath - file path to write logs to
22
+ * @param eventsPerFlush - number of events per flush
23
+ * @param defaultProps - default properties to add to every telemetry event
24
+ */
25
+ public constructor(
26
+ protected readonly filePath: string,
27
+ protected readonly eventsPerFlush: number = 50,
28
+ protected readonly defaultProps?: Record<string, string | number>,
29
+ ) {}
30
+
31
+ public send(event: ITelemetryBaseEvent): void {
32
+ // eslint-disable-next-line no-param-reassign
33
+ event = { ...event, ...this.defaultProps };
34
+ this.events.push(event);
35
+
36
+ if (this.events.length >= this.eventsPerFlush || event.category === "error") {
37
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
38
+ this.flush();
39
+ }
40
+ }
41
+
42
+ protected async flush(): Promise<void> {
43
+ if (this.events.length > 0) {
44
+ const contentToWrite = this.events.map((it) => JSON.stringify(it)).join(",");
45
+ if (this.hasWrittenToFile) {
46
+ fs.appendFileSync(this.filePath, `,${contentToWrite}`);
47
+ } else {
48
+ fs.appendFileSync(this.filePath, contentToWrite);
49
+ }
50
+ this.events = [];
51
+ this.hasWrittenToFile = true;
52
+ }
53
+ }
54
+
55
+ public async close(): Promise<void> {
56
+ await this.flush();
57
+ }
58
+ }
@@ -0,0 +1,40 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+
6
+ import * as fs from "fs";
7
+ import { parse } from "json2csv";
8
+ import { ITelemetryBaseEvent } from "@fluidframework/core-interfaces";
9
+ import { BaseFileLogger } from "./baseFileLogger";
10
+
11
+ /**
12
+ * FileLogger that writes events into a defined CSV file
13
+ * @internal
14
+ */
15
+ export class CSVFileLogger extends BaseFileLogger {
16
+ /** Store the column names to write as the CSV header */
17
+ private readonly columns = new Set();
18
+
19
+ protected async flush(): Promise<void> {
20
+ // No flushing is performed since we need all log entries to determine set of CSV columns
21
+ }
22
+
23
+ public send(event: ITelemetryBaseEvent): void {
24
+ // eslint-disable-next-line guard-for-in, no-restricted-syntax
25
+ for (const prop in event) {
26
+ this.columns.add(prop);
27
+ }
28
+ super.send(event);
29
+ }
30
+
31
+ public async close(): Promise<void> {
32
+ await super.close();
33
+ // eslint-disable-next-line guard-for-in, no-restricted-syntax
34
+ for (const field in this.defaultProps) {
35
+ this.columns.add(field);
36
+ }
37
+
38
+ fs.writeFileSync(this.filePath, parse(this.events, Array.from(this.columns)));
39
+ }
40
+ }
@@ -0,0 +1,51 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+
6
+ import { ITelemetryBaseLogger } from "@fluidframework/core-interfaces";
7
+
8
+ /**
9
+ * Contract for logger that writes telemetry to a file
10
+ * @internal
11
+ */
12
+ export interface IFileLogger extends ITelemetryBaseLogger {
13
+ /**
14
+ * This method acts as a "dispose" and should be explicitly called at the end of execution
15
+ */
16
+ close(): Promise<void>;
17
+ }
18
+
19
+ /**
20
+ * Desired output format for the telemetry
21
+ * @alpha
22
+ */
23
+ export enum OutputFormat {
24
+ JSON,
25
+ CSV,
26
+ }
27
+
28
+ /* eslint-disable tsdoc/syntax */
29
+ /**
30
+ * Options to provide upon creation of IFileLogger
31
+ * @internal
32
+ */
33
+ export interface ITelemetryOptions {
34
+ /** Desired output format used to create a specific IFileLogger implementation */
35
+ outputFormat?: OutputFormat;
36
+
37
+ /**
38
+ * Properties that should be added to every telemetry event
39
+ *
40
+ * @example
41
+ *
42
+ * ```JSON
43
+ * { "prop1": "value1", "prop2": 10.0 }
44
+ * ```
45
+ */
46
+ defaultProps?: Record<string, string | number>;
47
+
48
+ /** Number of telemetry events per flush to telemetry file */
49
+ eventsPerFlush?: number;
50
+ }
51
+ /* eslint-enable tsdoc/syntax */
@@ -0,0 +1,27 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+
6
+ import * as fs from "fs";
7
+ import { BaseFileLogger } from "./baseFileLogger";
8
+
9
+ /**
10
+ * FileLogger that writes events into a defined CSV file
11
+ * @internal
12
+ */
13
+ export class JSONFileLogger extends BaseFileLogger {
14
+ constructor(
15
+ filePath: string,
16
+ eventsPerFlush: number = 50,
17
+ defaultProps?: Record<string, string | number>,
18
+ ) {
19
+ super(filePath, eventsPerFlush, defaultProps);
20
+ fs.appendFileSync(this.filePath, "[");
21
+ }
22
+
23
+ public async close(): Promise<void> {
24
+ await super.close();
25
+ fs.appendFileSync(this.filePath, "]");
26
+ }
27
+ }
@@ -0,0 +1,108 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+
6
+ import * as fs from "fs";
7
+ import { ITelemetryLoggerExt, createChildLogger } from "@fluidframework/telemetry-utils";
8
+ import { CSVFileLogger } from "./csvFileLogger";
9
+ import { IFileLogger, ITelemetryOptions, OutputFormat } from "./fileLogger";
10
+ import { JSONFileLogger } from "./jsonFileLogger";
11
+
12
+ /**
13
+ * Create an {@link @fluidframework/telemetry-utils#ITelemetryLoggerExt} wrapped around provided {@link IFileLogger}.
14
+ *
15
+ * @remarks
16
+ *
17
+ * It is expected that all events be sent through the returned "logger" value.
18
+ *
19
+ * The "fileLogger" value should have its "close()" method called at the end of execution.
20
+ *
21
+ * Note: if an output format is not supplied, default is JSON.
22
+ *
23
+ * @returns Both the `IFileLogger` implementation and `ITelemetryLoggerExt` wrapper to be called.
24
+ * @internal
25
+ */
26
+ export function createLogger(
27
+ filePath: string,
28
+ options?: ITelemetryOptions,
29
+ ): { logger: ITelemetryLoggerExt; fileLogger: IFileLogger } {
30
+ const fileLogger =
31
+ options?.outputFormat === OutputFormat.CSV
32
+ ? new CSVFileLogger(filePath, options?.eventsPerFlush, options?.defaultProps)
33
+ : new JSONFileLogger(filePath, options?.eventsPerFlush, options?.defaultProps);
34
+
35
+ const logger = createChildLogger({
36
+ logger: fileLogger,
37
+ namespace: "LocalSnapshotRunnerApp",
38
+ properties: {
39
+ all: { Event_Time: () => Date.now() },
40
+ },
41
+ });
42
+
43
+ return { logger, fileLogger };
44
+ }
45
+
46
+ /**
47
+ * Validate the telemetryFile command line argument
48
+ * @param telemetryFile - path where telemetry will be written
49
+ * @internal
50
+ */
51
+ export function getTelemetryFileValidationError(telemetryFile: string): string | undefined {
52
+ if (!telemetryFile) {
53
+ return "Telemetry file argument is missing.";
54
+ } else if (fs.existsSync(telemetryFile)) {
55
+ return `Telemetry file already exists [${telemetryFile}].`;
56
+ }
57
+
58
+ return undefined;
59
+ }
60
+
61
+ /**
62
+ * Validate the provided output format and default properties
63
+ * @param format - desired output format of the telemetry
64
+ * @param props - default properties to be added to every telemetry entry
65
+ * @internal
66
+ */
67
+ export function validateAndParseTelemetryOptions(
68
+ format?: string,
69
+ props?: (string | number)[],
70
+ eventsPerFlush?: number,
71
+ ): { success: false; error: string } | { success: true; telemetryOptions: ITelemetryOptions } {
72
+ let outputFormat: OutputFormat | undefined;
73
+ const defaultProps: Record<string, string | number> = {};
74
+
75
+ if (format) {
76
+ outputFormat = OutputFormat[format];
77
+ if (outputFormat === undefined) {
78
+ return { success: false, error: `Invalid telemetry format [${format}]` };
79
+ }
80
+ }
81
+
82
+ if (props && props.length > 0) {
83
+ if (props.length % 2 !== 0) {
84
+ return {
85
+ success: false,
86
+ error: `Invalid number of telemetry properties to add [${props.length}]`,
87
+ };
88
+ }
89
+ for (let i = 0; i < props.length; i += 2) {
90
+ if (typeof props[i] === "number") {
91
+ return {
92
+ success: false,
93
+ error: `Property name cannot be number at index [${i}] -> [${props[i]}]`,
94
+ };
95
+ }
96
+ defaultProps[props[i]] = props[i + 1];
97
+ }
98
+ }
99
+
100
+ if (eventsPerFlush !== undefined && isNaN(eventsPerFlush)) {
101
+ return {
102
+ success: false,
103
+ error: "Invalid eventsPerFlush",
104
+ };
105
+ }
106
+
107
+ return { success: true, telemetryOptions: { outputFormat, defaultProps, eventsPerFlush } };
108
+ }
@@ -0,0 +1,92 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+
6
+ import * as fs from "fs";
7
+ import { PerformanceEvent } from "@fluidframework/telemetry-utils";
8
+ import { isCodeLoaderBundle, isFluidFileConverter } from "./codeLoaderBundle";
9
+ import { createContainerAndExecute, IExportFileResponse } from "./exportFile";
10
+ /* eslint-disable import/no-internal-modules */
11
+ import { ITelemetryOptions } from "./logger/fileLogger";
12
+ import { createLogger, getTelemetryFileValidationError } from "./logger/loggerUtils";
13
+ /* eslint-enable import/no-internal-modules */
14
+ import { getSnapshotFileContent, getArgsValidationError } from "./utils";
15
+
16
+ const clientArgsValidationError = "Client_ArgsValidationError";
17
+
18
+ /**
19
+ * Parse a provided JS bundle, execute code on Container based on ODSP snapshot, and write result to file
20
+ * @param codeLoader - path to provided JS bundle that implements ICodeLoaderBundle (see codeLoaderBundle.ts)
21
+ * @internal
22
+ */
23
+ export async function parseBundleAndExportFile(
24
+ codeLoader: string,
25
+ inputFile: string,
26
+ outputFile: string,
27
+ telemetryFile: string,
28
+ options?: string,
29
+ telemetryOptions?: ITelemetryOptions,
30
+ timeout?: number,
31
+ disableNetworkFetch?: boolean,
32
+ ): Promise<IExportFileResponse> {
33
+ const telemetryArgError = getTelemetryFileValidationError(telemetryFile);
34
+ if (telemetryArgError) {
35
+ const eventName = clientArgsValidationError;
36
+ return { success: false, eventName, errorMessage: telemetryArgError };
37
+ }
38
+ const { fileLogger, logger } = createLogger(telemetryFile, telemetryOptions);
39
+
40
+ try {
41
+ return await PerformanceEvent.timedExecAsync(
42
+ logger,
43
+ { eventName: "ParseBundleAndExportFile" },
44
+ async () => {
45
+ // eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires
46
+ const codeLoaderBundle = require(codeLoader);
47
+ if (!isCodeLoaderBundle(codeLoaderBundle)) {
48
+ const eventName = clientArgsValidationError;
49
+ const errorMessage = "Code loader bundle is not of type ICodeLoaderBundle";
50
+ logger.sendErrorEvent({ eventName, message: errorMessage });
51
+ return { success: false, eventName, errorMessage };
52
+ }
53
+
54
+ const fluidExport = await codeLoaderBundle.fluidExport;
55
+ if (!isFluidFileConverter(fluidExport)) {
56
+ const eventName = clientArgsValidationError;
57
+ const errorMessage =
58
+ "Fluid export from CodeLoaderBundle is not of type IFluidFileConverter";
59
+ logger.sendErrorEvent({ eventName, message: errorMessage });
60
+ return { success: false, eventName, errorMessage };
61
+ }
62
+
63
+ const argsValidationError = getArgsValidationError(inputFile, outputFile, timeout);
64
+ if (argsValidationError) {
65
+ const eventName = clientArgsValidationError;
66
+ logger.sendErrorEvent({ eventName, message: argsValidationError });
67
+ return { success: false, eventName, errorMessage: argsValidationError };
68
+ }
69
+
70
+ fs.writeFileSync(
71
+ outputFile,
72
+ await createContainerAndExecute(
73
+ getSnapshotFileContent(inputFile),
74
+ fluidExport,
75
+ logger,
76
+ options,
77
+ timeout,
78
+ disableNetworkFetch,
79
+ ),
80
+ );
81
+
82
+ return { success: true };
83
+ },
84
+ );
85
+ } catch (error) {
86
+ const eventName = "Client_UnexpectedError";
87
+ logger.sendErrorEvent({ eventName }, error);
88
+ return { success: false, eventName, errorMessage: "Unexpected error", error };
89
+ } finally {
90
+ await fileLogger.close();
91
+ }
92
+ }
package/src/utils.ts ADDED
@@ -0,0 +1,100 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+
6
+ import * as fs from "fs";
7
+ import { IFluidFileConverter } from "./codeLoaderBundle";
8
+
9
+ /**
10
+ * Is the given snapshot in JSON format
11
+ * @param content - snapshot file content
12
+ * @internal
13
+ */
14
+ export function isJsonSnapshot(content: Buffer): boolean {
15
+ return content.toString(undefined, 0, 1) === "{";
16
+ }
17
+
18
+ /**
19
+ * Get the ODSP snapshot file content
20
+ * Works on both JSON and binary snapshot formats
21
+ * @param filePath - path to the ODSP snapshot file
22
+ * @internal
23
+ */
24
+ export function getSnapshotFileContent(filePath: string): string | Buffer {
25
+ // TODO: read file stream
26
+ const content = fs.readFileSync(filePath);
27
+ return isJsonSnapshot(content) ? content.toString() : content;
28
+ }
29
+
30
+ /**
31
+ * Validate provided command line arguments
32
+ * @internal
33
+ */
34
+ export function validateCommandLineArgs(
35
+ codeLoader?: string,
36
+ fluidFileConverter?: IFluidFileConverter,
37
+ ): string | undefined {
38
+ if (codeLoader && fluidFileConverter !== undefined) {
39
+ return '"codeLoader" and "fluidFileConverter" cannot both be provided. See README for details.';
40
+ }
41
+ if (!codeLoader && fluidFileConverter === undefined) {
42
+ return '"codeLoader" must be provided if there is no explicit "fluidFileConverter". See README for details.';
43
+ }
44
+ return undefined;
45
+ }
46
+
47
+ /**
48
+ * @internal
49
+ */
50
+ export function getArgsValidationError(
51
+ inputFile: string,
52
+ outputFile: string,
53
+ timeout?: number,
54
+ ): string | undefined {
55
+ // Validate input file
56
+ if (!inputFile) {
57
+ return "Input file name argument is missing.";
58
+ } else if (!fs.existsSync(inputFile)) {
59
+ return "Input file does not exist.";
60
+ }
61
+
62
+ // Validate output file
63
+ if (!outputFile) {
64
+ return "Output file argument is missing.";
65
+ } else if (fs.existsSync(outputFile)) {
66
+ return `Output file already exists [${outputFile}].`;
67
+ }
68
+
69
+ if (timeout !== undefined && (isNaN(timeout) || timeout < 0)) {
70
+ return "Invalid timeout";
71
+ }
72
+
73
+ return undefined;
74
+ }
75
+
76
+ /**
77
+ * @internal
78
+ */
79
+ export async function timeoutPromise<T = void>(
80
+ executor: (
81
+ resolve: (value: T | PromiseLike<T>) => void,
82
+ reject: (reason?: any) => void,
83
+ ) => void,
84
+ timeout: number,
85
+ ): Promise<T> {
86
+ return new Promise<T>((resolve, reject) => {
87
+ const timer = setTimeout(() => reject(new Error(`Timed out (${timeout}ms)`)), timeout);
88
+
89
+ executor(
90
+ (value) => {
91
+ clearTimeout(timer);
92
+ resolve(value);
93
+ },
94
+ (reason) => {
95
+ clearTimeout(timer);
96
+ reject(reason);
97
+ },
98
+ );
99
+ });
100
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,13 @@
1
+ {
2
+ "extends": [
3
+ "../../../common/build/build-common/tsconfig.base.json",
4
+ "../../../common/build/build-common/tsconfig.cjs.json",
5
+ ],
6
+ "include": ["src/**/*"],
7
+ "exclude": ["src/test/**/*"],
8
+ "compilerOptions": {
9
+ "rootDir": "./src",
10
+ "outDir": "./dist",
11
+ "types": ["node"],
12
+ },
13
+ }