mirror of
https://github.com/microsoft/playwright.git
synced 2024-10-06 09:27:31 +03:00
chore: migrate injected scripts to esbuild (#13143)
This commit is contained in:
parent
de0af27837
commit
1961959dcb
3098
package-lock.json
generated
3098
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -65,7 +65,6 @@
|
||||
"@types/resize-observer-browser": "^0.1.6",
|
||||
"@types/rimraf": "^3.0.2",
|
||||
"@types/source-map-support": "^0.5.4",
|
||||
"@types/webpack": "^5.28.0",
|
||||
"@types/ws": "8.2.2",
|
||||
"@types/xml2js": "^0.4.9",
|
||||
"@types/yazl": "^2.4.2",
|
||||
@ -74,22 +73,17 @@
|
||||
"@vitejs/plugin-react": "^1.0.7",
|
||||
"@zip.js/zip.js": "^2.4.2",
|
||||
"ansi-to-html": "^0.7.2",
|
||||
"babel-loader": "^8.2.3",
|
||||
"chokidar": "^3.5.3",
|
||||
"commonmark": "^0.30.0",
|
||||
"concurrently": "^6.2.1",
|
||||
"copy-webpack-plugin": "^9.1.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"css-loader": "^6.5.1",
|
||||
"dotenv": "^16.0.0",
|
||||
"electron": "^12.2.1",
|
||||
"enquirer": "^2.3.6",
|
||||
"eslint": "^8.8.0",
|
||||
"eslint-plugin-notice": "^0.9.10",
|
||||
"eslint-plugin-react-hooks": "^4.3.0",
|
||||
"file-loader": "^6.2.0",
|
||||
"formidable": "^2.0.1",
|
||||
"html-webpack-plugin": "^5.5.0",
|
||||
"mime": "^3.0.0",
|
||||
"ncp": "^2.0.0",
|
||||
"node-stream-zip": "^1.15.0",
|
||||
@ -97,11 +91,8 @@
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"socksv5": "0.0.6",
|
||||
"style-loader": "^3.3.1",
|
||||
"typescript": "^4.5.5",
|
||||
"vite": "^2.8.0",
|
||||
"webpack": "^5.68.0",
|
||||
"webpack-cli": "^4.9.2",
|
||||
"xml2js": "^0.4.23",
|
||||
"yaml": "^1.10.2"
|
||||
}
|
||||
|
@ -113,7 +113,7 @@ export function serializeValue(value: any, handleSerializer: (value: any) => Han
|
||||
return { s: value };
|
||||
if (isError(value)) {
|
||||
const error = value;
|
||||
if ('captureStackTrace' in global.Error) {
|
||||
if ('captureStackTrace' in globalThis.Error) {
|
||||
// v8
|
||||
return { s: error.stack || '' };
|
||||
}
|
||||
|
@ -110,7 +110,7 @@ function serialize(value: any, handleSerializer: (value: any) => HandleOrValue,
|
||||
|
||||
if (isError(value)) {
|
||||
const error = value;
|
||||
if ('captureStackTrace' in global.Error) {
|
||||
if ('captureStackTrace' in globalThis.Error) {
|
||||
// v8
|
||||
return error.stack || '';
|
||||
}
|
||||
|
@ -99,8 +99,9 @@ export class FrameExecutionContext extends js.ExecutionContext {
|
||||
custom.push(`{ name: '${name}', engine: (${source}) }`);
|
||||
const source = `
|
||||
(() => {
|
||||
const module = {};
|
||||
${injectedScriptSource.source}
|
||||
return new pwExport(
|
||||
return new module.exports(
|
||||
${isUnderTest()},
|
||||
${this.frame._page._delegate.rafCountForStablePosition()},
|
||||
"${this.frame._page._browserContext._browser.options.name}",
|
||||
|
@ -31,7 +31,7 @@ import { assert, constructURLBasedOnBaseURL, makeWaitForNextTask } from '../util
|
||||
import { ManualPromise } from '../utils/async';
|
||||
import { debugLogger } from '../utils/debugLogger';
|
||||
import { CallMetadata, serverSideCallMetadata, SdkObject } from './instrumentation';
|
||||
import type InjectedScript from './injected/injectedScript';
|
||||
import { type InjectedScript } from './injected/injectedScript';
|
||||
import type { ElementStateWithoutStable, FrameExpectParams, InjectedScriptPoll, InjectedScriptProgress } from './injected/injectedScript';
|
||||
import { isSessionClosedError } from './protocolError';
|
||||
import { isInvalidSelectorError, splitSelectorByFrame, stringifySelector, ParsedSelector } from './common/selectorParser';
|
||||
|
@ -121,7 +121,7 @@ export class InjectedScript {
|
||||
}
|
||||
|
||||
eval(expression: string): any {
|
||||
return global.eval(expression);
|
||||
return globalThis.eval(expression);
|
||||
}
|
||||
|
||||
parseSelector(selector: string): ParsedSelector {
|
||||
@ -303,10 +303,11 @@ export class InjectedScript {
|
||||
}
|
||||
|
||||
extend(source: string, params: any): any {
|
||||
const constrFunction = global.eval(`
|
||||
const constrFunction = globalThis.eval(`
|
||||
(() => {
|
||||
const module = {};
|
||||
${source}
|
||||
return pwExport;
|
||||
return module.exports;
|
||||
})()`);
|
||||
return new constrFunction(this, params);
|
||||
}
|
||||
@ -1257,4 +1258,4 @@ function deepEquals(a: any, b: any): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
export default InjectedScript;
|
||||
module.exports = InjectedScript;
|
||||
|
@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type InjectedScript from './injectedScript';
|
||||
import { type InjectedScript } from './injectedScript';
|
||||
import { elementText } from './selectorEvaluator';
|
||||
|
||||
type SelectorToken = {
|
||||
|
@ -16,12 +16,12 @@
|
||||
|
||||
import { serializeAsCallArgument, parseEvaluationResultValue } from '../common/utilityScriptSerializers';
|
||||
|
||||
export default class UtilityScript {
|
||||
export class UtilityScript {
|
||||
evaluate(isFunction: boolean | undefined, returnByValue: boolean, expression: string, argCount: number, ...argsAndHandles: any[]) {
|
||||
const args = argsAndHandles.slice(0, argCount);
|
||||
const handles = argsAndHandles.slice(argCount);
|
||||
const parameters = args.map(a => parseEvaluationResultValue(a, handles));
|
||||
let result = global.eval(expression);
|
||||
let result = globalThis.eval(expression);
|
||||
if (isFunction === true) {
|
||||
result = result(...parameters);
|
||||
} else if (isFunction === false) {
|
||||
@ -63,3 +63,5 @@ export default class UtilityScript {
|
||||
return safeJson(value);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = UtilityScript;
|
||||
|
@ -1,80 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
|
||||
class InlineSource {
|
||||
/**
|
||||
* @param {string[]} outFiles
|
||||
*/
|
||||
constructor(outFiles) {
|
||||
this.outFiles = outFiles;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import('webpack').Compiler} compiler
|
||||
*/
|
||||
apply(compiler) {
|
||||
compiler.hooks.emit.tapAsync('InlineSource', (compilation, callback) => {
|
||||
for (const outFile of this.outFiles) {
|
||||
const source = compilation.assets[path.basename(outFile).replace('.ts', '.js')].source();
|
||||
fs.mkdirSync(path.dirname(outFile), { recursive: true });
|
||||
const newSource = 'export const source = ' + JSON.stringify(source) + ';';
|
||||
fs.writeFileSync(outFile, newSource);
|
||||
}
|
||||
callback();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const entry = {
|
||||
utilityScriptSource: path.join(__dirname, 'utilityScript.ts'),
|
||||
injectedScriptSource: path.join(__dirname, 'injectedScript.ts'),
|
||||
consoleApiSource: path.join(__dirname, '..', 'supplements', 'injected', 'consoleApi.ts'),
|
||||
recorderSource: path.join(__dirname, '..', 'supplements', 'injected', 'recorder.ts'),
|
||||
}
|
||||
|
||||
/** @type {import('webpack').Configuration} */
|
||||
module.exports = {
|
||||
entry,
|
||||
mode: process.env.NODE_ENV === 'production' ? 'production' : 'development',
|
||||
devtool: false,
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.(j|t)sx?$/,
|
||||
loader: 'babel-loader',
|
||||
exclude: /node_modules/
|
||||
}
|
||||
]
|
||||
},
|
||||
resolve: {
|
||||
extensions: [ '.tsx', '.ts', '.js' ]
|
||||
},
|
||||
output: {
|
||||
libraryTarget: 'var',
|
||||
library: 'pwExport',
|
||||
libraryExport: 'default',
|
||||
filename: '[name].js',
|
||||
path: path.resolve(__dirname, '../../../lib/server/injected/packed')
|
||||
},
|
||||
plugins: [
|
||||
new InlineSource(
|
||||
Object.keys(entry).map(x => path.join(__dirname, '..', '..', 'generated', x + '.ts'))
|
||||
),
|
||||
]
|
||||
};
|
@ -17,7 +17,7 @@
|
||||
import * as dom from './dom';
|
||||
import * as utilityScriptSource from '../generated/utilityScriptSource';
|
||||
import { serializeAsCallArgument } from './common/utilityScriptSerializers';
|
||||
import type UtilityScript from './injected/utilityScript';
|
||||
import { type UtilityScript } from './injected/utilityScript';
|
||||
import { SdkObject } from './instrumentation';
|
||||
import { ManualPromise } from '../utils/async';
|
||||
|
||||
@ -114,8 +114,9 @@ export class ExecutionContext extends SdkObject {
|
||||
if (!this._utilityScriptPromise) {
|
||||
const source = `
|
||||
(() => {
|
||||
const module = {};
|
||||
${utilityScriptSource.source}
|
||||
return new pwExport();
|
||||
return new module.exports();
|
||||
})();`;
|
||||
this._utilityScriptPromise = this._raceAgainstContextDestroyed(this._delegate.rawEvaluateHandle(source).then(objectId => new JSHandle(this, 'object', undefined, objectId)));
|
||||
}
|
||||
|
@ -15,7 +15,7 @@
|
||||
*/
|
||||
|
||||
import { escapeWithQuotes } from '../../../utils/stringUtils';
|
||||
import type InjectedScript from '../../injected/injectedScript';
|
||||
import { type InjectedScript } from '../../injected/injectedScript';
|
||||
import { generateSelector } from '../../injected/selectorGenerator';
|
||||
|
||||
function createLocator(injectedScript: InjectedScript, initial: string, options?: { hasText?: string | RegExp }) {
|
||||
@ -64,7 +64,7 @@ declare global {
|
||||
}
|
||||
}
|
||||
|
||||
export class ConsoleAPI {
|
||||
class ConsoleAPI {
|
||||
private _injectedScript: InjectedScript;
|
||||
|
||||
constructor(injectedScript: InjectedScript) {
|
||||
@ -112,4 +112,4 @@ export class ConsoleAPI {
|
||||
}
|
||||
}
|
||||
|
||||
export default ConsoleAPI;
|
||||
module.exports = ConsoleAPI;
|
||||
|
@ -15,7 +15,7 @@
|
||||
*/
|
||||
|
||||
import type * as actions from '../recorder/recorderActions';
|
||||
import type InjectedScript from '../../injected/injectedScript';
|
||||
import { type InjectedScript } from '../../injected/injectedScript';
|
||||
import { generateSelector, querySelector } from '../../injected/selectorGenerator';
|
||||
import type { Point } from '../../../common/types';
|
||||
import type { UIState } from '../recorder/recorderTypes';
|
||||
@ -30,7 +30,7 @@ declare module globalThis {
|
||||
let _playwrightRefreshOverlay: () => void;
|
||||
}
|
||||
|
||||
export class Recorder {
|
||||
class Recorder {
|
||||
private _injectedScript: InjectedScript;
|
||||
private _performingAction = false;
|
||||
private _listeners: (() => void)[] = [];
|
||||
@ -473,4 +473,4 @@ function removeEventListeners(listeners: (() => void)[]) {
|
||||
listeners.splice(0, listeners.length);
|
||||
}
|
||||
|
||||
export default Recorder;
|
||||
module.exports = Recorder;
|
||||
|
@ -15,7 +15,6 @@
|
||||
*/
|
||||
|
||||
import { BrowserContext } from '../../browserContext';
|
||||
import { eventsHelper } from '../../../utils/eventsHelper';
|
||||
import { Page } from '../../page';
|
||||
import { FrameSnapshot } from '../common/snapshotTypes';
|
||||
import { SnapshotRenderer } from '../../../../../trace-viewer/src/snapshotRenderer';
|
||||
@ -61,9 +60,9 @@ export class InMemorySnapshotter extends BaseSnapshotStorage implements Snapshot
|
||||
|
||||
this._snapshotter.captureSnapshot(page, snapshotName, element).catch(() => {});
|
||||
return new Promise<SnapshotRenderer>(fulfill => {
|
||||
const listener = eventsHelper.addEventListener(this, 'snapshot', (renderer: SnapshotRenderer) => {
|
||||
const disposable = this.onSnapshotEvent((renderer: SnapshotRenderer) => {
|
||||
if (renderer.snapshotName === snapshotName) {
|
||||
eventsHelper.removeEventListeners([listener]);
|
||||
disposable.dispose();
|
||||
fulfill(renderer);
|
||||
}
|
||||
});
|
||||
|
77
packages/trace-viewer/src/events.ts
Normal file
77
packages/trace-viewer/src/events.ts
Normal file
@ -0,0 +1,77 @@
|
||||
/**
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export namespace Disposable {
|
||||
export function disposeAll(disposables: Disposable[]): void {
|
||||
for (const disposable of disposables.splice(0))
|
||||
disposable.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
export type Disposable = {
|
||||
dispose(): void;
|
||||
};
|
||||
|
||||
export interface Event<T> {
|
||||
(listener: (e: T) => any, disposables?: Disposable[]): Disposable;
|
||||
}
|
||||
|
||||
export class EventEmitter<T> {
|
||||
public event: Event<T>;
|
||||
|
||||
private _deliveryQueue?: {listener: (e: T) => void, event: T}[];
|
||||
private _listeners = new Set<(e: T) => void>();
|
||||
|
||||
constructor() {
|
||||
this.event = (listener: (e: T) => any, disposables?: Disposable[]) => {
|
||||
this._listeners.add(listener);
|
||||
let disposed = false;
|
||||
const self = this;
|
||||
const result: Disposable = {
|
||||
dispose() {
|
||||
if (!disposed) {
|
||||
disposed = true;
|
||||
self._listeners.delete(listener);
|
||||
}
|
||||
}
|
||||
};
|
||||
if (disposables)
|
||||
disposables.push(result);
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
fire(event: T): void {
|
||||
const dispatch = !this._deliveryQueue;
|
||||
if (!this._deliveryQueue)
|
||||
this._deliveryQueue = [];
|
||||
for (const listener of this._listeners)
|
||||
this._deliveryQueue.push({ listener, event });
|
||||
if (!dispatch)
|
||||
return;
|
||||
for (let index = 0; index < this._deliveryQueue.length; index++) {
|
||||
const { listener, event } = this._deliveryQueue[index];
|
||||
listener.call(null, event);
|
||||
}
|
||||
this._deliveryQueue = undefined;
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this._listeners.clear();
|
||||
if (this._deliveryQueue)
|
||||
this._deliveryQueue = [];
|
||||
}
|
||||
}
|
@ -15,7 +15,7 @@
|
||||
*/
|
||||
|
||||
import type { FrameSnapshot, ResourceSnapshot } from '@playwright-core/server/trace/common/snapshotTypes';
|
||||
import { EventEmitter } from 'events';
|
||||
import { EventEmitter } from './events';
|
||||
import { SnapshotRenderer } from './snapshotRenderer';
|
||||
|
||||
export interface SnapshotStorage {
|
||||
@ -25,12 +25,14 @@ export interface SnapshotStorage {
|
||||
snapshotByIndex(frameId: string, index: number): SnapshotRenderer | undefined;
|
||||
}
|
||||
|
||||
export abstract class BaseSnapshotStorage extends EventEmitter implements SnapshotStorage {
|
||||
export abstract class BaseSnapshotStorage implements SnapshotStorage {
|
||||
protected _resources: ResourceSnapshot[] = [];
|
||||
protected _frameSnapshots = new Map<string, {
|
||||
raw: FrameSnapshot[],
|
||||
renderer: SnapshotRenderer[]
|
||||
}>();
|
||||
private _didSnapshot = new EventEmitter<SnapshotRenderer>();
|
||||
readonly onSnapshotEvent = this._didSnapshot.event;
|
||||
|
||||
clear() {
|
||||
this._resources = [];
|
||||
@ -55,7 +57,7 @@ export abstract class BaseSnapshotStorage extends EventEmitter implements Snapsh
|
||||
frameSnapshots.raw.push(snapshot);
|
||||
const renderer = new SnapshotRenderer(this._resources, frameSnapshots.raw, frameSnapshots.raw.length - 1);
|
||||
frameSnapshots.renderer.push(renderer);
|
||||
this.emit('snapshot', renderer);
|
||||
this._didSnapshot.fire(renderer);
|
||||
}
|
||||
|
||||
abstract resourceContent(sha1: string): Promise<Blob | undefined>;
|
||||
|
@ -59,7 +59,7 @@ it.describe('snapshots', () => {
|
||||
it('should collect multiple', async ({ page, toImpl, snapshotter }) => {
|
||||
await page.setContent('<button>Hello</button>');
|
||||
const snapshots = [];
|
||||
snapshotter.on('snapshot', snapshot => snapshots.push(snapshot));
|
||||
snapshotter.onSnapshotEvent(snapshot => snapshots.push(snapshot));
|
||||
await snapshotter.captureSnapshot(toImpl(page), 'snapshot1');
|
||||
await snapshotter.captureSnapshot(toImpl(page), 'snapshot2');
|
||||
expect(snapshots.length).toBe(2);
|
||||
|
@ -190,19 +190,11 @@ steps.push({
|
||||
});
|
||||
|
||||
// Build injected scripts.
|
||||
const webPackFiles = [
|
||||
'packages/playwright-core/src/server/injected/webpack.config.js',
|
||||
];
|
||||
for (const file of webPackFiles) {
|
||||
steps.push({
|
||||
command: 'npx',
|
||||
args: ['webpack', '--config', quotePath(filePath(file)), ...(watchMode ? ['--watch', '--stats', 'none'] : [])],
|
||||
shell: true,
|
||||
env: {
|
||||
NODE_ENV: watchMode ? 'development' : 'production'
|
||||
}
|
||||
});
|
||||
}
|
||||
steps.push({
|
||||
command: 'node',
|
||||
args: ['utils/generate_injected.js'],
|
||||
shell: true,
|
||||
});
|
||||
|
||||
// Run Babel.
|
||||
for (const pkg of workspace.packages()) {
|
||||
@ -216,11 +208,22 @@ for (const pkg of workspace.packages()) {
|
||||
'--extensions', '.ts',
|
||||
'--out-dir', quotePath(path.join(pkg.path, 'lib')),
|
||||
'--ignore', '"packages/playwright-core/src/server/injected/**/*"',
|
||||
'--ignore', '"packages/playwright-core/src/server/supplements/injected/**/*"',
|
||||
quotePath(path.join(pkg.path, 'src'))],
|
||||
shell: true,
|
||||
});
|
||||
}
|
||||
|
||||
// Generate injected.
|
||||
onChanges.push({
|
||||
committed: false,
|
||||
inputs: [
|
||||
'packages/playwright-core/src/server/injected/**',
|
||||
'packages/playwright-core/src/supplements/injected/**',
|
||||
'utils/generate_injected.js',
|
||||
],
|
||||
script: 'utils/generate_injected.js',
|
||||
});
|
||||
|
||||
// Generate channels.
|
||||
onChanges.push({
|
||||
|
50
utils/generate_injected.js
Normal file
50
utils/generate_injected.js
Normal file
@ -0,0 +1,50 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// @ts-check
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const ROOT = path.join(__dirname, '..');
|
||||
const esbuild = require('esbuild');
|
||||
|
||||
const injectedScripts = [
|
||||
path.join(ROOT, 'packages', 'playwright-core', 'src', 'server', 'injected', 'utilityScript.ts'),
|
||||
path.join(ROOT, 'packages', 'playwright-core', 'src', 'server', 'injected', 'injectedScript.ts'),
|
||||
path.join(ROOT, 'packages', 'playwright-core', 'src', 'server', 'supplements', 'injected', 'consoleApi.ts'),
|
||||
path.join(ROOT, 'packages', 'playwright-core', 'src', 'server', 'supplements', 'injected', 'recorder.ts'),
|
||||
];
|
||||
|
||||
(async () => {
|
||||
const generatedFolder = path.join(ROOT, 'packages', 'playwright-core', 'src', 'generated');
|
||||
await fs.promises.mkdir(generatedFolder, { recursive: true });
|
||||
for (const injected of injectedScripts) {
|
||||
const outdir = path.join(ROOT, 'packages', 'playwright-core', 'lib', 'server', 'injected', 'packed');
|
||||
await esbuild.build({
|
||||
entryPoints: [injected],
|
||||
bundle: true,
|
||||
outdir,
|
||||
format: 'cjs',
|
||||
platform: 'browser',
|
||||
target: 'ES2019'
|
||||
});
|
||||
const baseName = path.basename(injected);
|
||||
const content = await fs.promises.readFile(path.join(outdir, baseName.replace('.ts', '.js')), 'utf-8');
|
||||
const newContent = `export const source = ${JSON.stringify(content)};`;
|
||||
await fs.promises.writeFile(path.join(generatedFolder, baseName.replace('.ts', 'Source.ts')), newContent);
|
||||
}
|
||||
})();
|
Loading…
Reference in New Issue
Block a user