diff --git a/.eslintrc b/.eslintrc index f135f8dd..525910e8 100644 --- a/.eslintrc +++ b/.eslintrc @@ -5,5 +5,19 @@ // It would be nice to sort import declaration order as well, but that's not // autofixable and it's not worth the effort of handling manually. "sort-imports": ["error", {"ignoreDeclarationSort": true}], + // Match TypeScript style of exempting names starting with `_`. + // See: https://typescript-eslint.io/rules/no-unused-vars/ + "@typescript-eslint/no-unused-vars": [ + "error", + { + "args": "all", + "argsIgnorePattern": "^_", + "caughtErrors": "all", + "caughtErrorsIgnorePattern": "^_", + "destructuredArrayIgnorePattern": "^_", + "varsIgnorePattern": "^_", + "ignoreRestSiblings": true + } + ] } } diff --git a/lib/src/compiler-path.ts b/lib/src/compiler-path.ts index 0097ac28..1757bed9 100644 --- a/lib/src/compiler-path.ts +++ b/lib/src/compiler-path.ts @@ -53,7 +53,7 @@ export const compilerCommand = (() => { `sass-embedded-${platform}-${arch}/dart-sass/src/sass.snapshot` ), ]; - } catch (ignored) { + } catch (_ignored) { // ignored } diff --git a/lib/src/compiler/utils.ts b/lib/src/compiler/utils.ts index 80e1e0fc..e35b54fe 100644 --- a/lib/src/compiler/utils.ts +++ b/lib/src/compiler/utils.ts @@ -10,7 +10,6 @@ import {Dispatcher, DispatcherHandlers} from '../dispatcher'; import {Exception} from '../exception'; import {ImporterRegistry} from '../importer-registry'; import { - legacyImporterProtocol, removeLegacyImporter, removeLegacyImporterFromSpan, } from '../legacy/utils'; @@ -121,19 +120,12 @@ export function newCompileStringRequest( }); const url = options?.url?.toString(); - if (url && url !== legacyImporterProtocol) { + if (url) { input.url = url; } if (options && 'importer' in options && options.importer) { input.importer = importers.register(options.importer); - } else if (url === legacyImporterProtocol) { - input.importer = new proto.InboundMessage_CompileRequest_Importer({ - importer: {case: 'path', value: p.resolve('.')}, - }); - } else { - // When importer is not set on the host, the compiler will set a - // FileSystemImporter if `url` is set to a file: url or a NoOpImporter. } const request = newCompileRequest(importers, options); diff --git a/lib/src/importer-registry.ts b/lib/src/importer-registry.ts index 691b4af2..bd1dd806 100644 --- a/lib/src/importer-registry.ts +++ b/lib/src/importer-registry.ts @@ -12,6 +12,8 @@ import * as utils from './utils'; import {FileImporter, Importer, Options} from './vendor/sass'; import * as proto from './vendor/embedded_sass_pb'; import {PromiseOr, catchOr, thenOr} from './utils'; +import {LegacyImporterWrapper} from './legacy/importer'; +import {legacyImporterScheme} from './legacy/utils'; const entryPointDirectoryKey = Symbol(); @@ -94,10 +96,15 @@ export class ImporterRegistry { } message.importer = {case: 'importerId', value: this.id}; - message.nonCanonicalScheme = - typeof importer.nonCanonicalScheme === 'string' - ? [importer.nonCanonicalScheme] - : importer.nonCanonicalScheme ?? []; + if (importer instanceof LegacyImporterWrapper) { + message.nonCanonicalScheme = [legacyImporterScheme]; + message.invertNonCanonicalScheme = true; + } else { + message.nonCanonicalScheme = + typeof importer.nonCanonicalScheme === 'string' + ? [importer.nonCanonicalScheme] + : importer.nonCanonicalScheme ?? []; + } this.importersById.set(this.id, importer); } else { message.importer = {case: 'fileImporterId', value: this.id}; diff --git a/lib/src/legacy/importer.ts b/lib/src/legacy/importer.ts index 68b79e11..0267c0f5 100644 --- a/lib/src/legacy/importer.ts +++ b/lib/src/legacy/importer.ts @@ -3,19 +3,19 @@ // https://opensource.org/licenses/MIT. import {strict as assert} from 'assert'; -import * as fs from 'fs'; import * as p from 'path'; import * as util from 'util'; +import {pathToFileURL} from 'url'; import {resolvePath} from './resolve-path'; import { PromiseOr, SyncBoolean, fileUrlToPathCrossPlatform, - isErrnoException, thenOr, } from '../utils'; import { + FileImporter, Importer, ImporterResult, LegacyAsyncImporter, @@ -25,247 +25,175 @@ import { LegacyPluginThis, LegacySyncImporter, } from '../vendor/sass'; -import { - legacyFileUrlToPath, - legacyImporterProtocol, - legacyImporterProtocolPrefix, - pathToLegacyFileUrl, -} from './utils'; +import {legacyFileUrlToPath, pathToLegacyFileUrl} from './utils'; /** - * A special URL protocol we use to signal when a stylesheet has finished - * loading. This allows us to determine which stylesheet is "current" when - * resolving a new load, which in turn allows us to pass in an accurate `prev` - * parameter to the legacy callback. - */ -export const endOfLoadProtocol = 'sass-embedded-legacy-load-done:'; - -/** - * The `file:` URL protocol with [legacyImporterProtocolPrefix] at the beginning. + * A wrapper around a `LegacyImporter` callback that exposes it as a new-API + * `Importer`, delegating to `LegacyImportersWrapper`. */ -export const legacyImporterFileProtocol = 'legacy-importer-file:'; - -// A count of `endOfLoadProtocol` imports that have been generated. Each one -// must be a different URL to ensure that the importer results aren't cached. -let endOfLoadCount = 0; +export class LegacyImporterWrapper + implements Importer +{ + constructor(private readonly wrapper: LegacyImportersWrapper) {} -// The interface for previous URLs that were passed to -// `LegacyImporterWrapper.callbacks`. -interface PreviousUrl { - // The URL itself. This is actually an absolute path if `path` is true. - url: string; + canonicalize( + url: string, + options: {fromImport: boolean; containingUrl: URL | null} + ): PromiseOr { + return this.wrapper.canonicalize(url, options); + } - // Whether `url` is an absolute path. - path: boolean; + load(canonicalUrl: URL): ImporterResult | null { + return this.wrapper.load(canonicalUrl); + } } /** * A wrapper around a `LegacyImporter` callback that exposes it as a new-API - * `Importer`. + * `FileImporter`, delegating to `LegacyImportersWrapper`. */ -export class LegacyImporterWrapper - implements Importer +export class LegacyFileImporterWrapper + implements FileImporter { - // A stack of previous URLs passed to `this.callbacks`. - private readonly prev: PreviousUrl[] = []; + constructor(private readonly wrapper: LegacyImportersWrapper) {} + + findFileUrl( + url: string, + options: {fromImport: boolean; containingUrl: URL | null} + ): PromiseOr { + return this.wrapper.findFileUrl(url, options); + } +} - // The `contents` field returned by the last successful invocation of - // `this.callbacks`, if it returned one. - private lastContents: string | undefined; +/** + * A wrapper around a `LegacyImporter` callback that exposes it as a pair of + * new-API `Importer` and `FileImporter`. + */ +export class LegacyImportersWrapper { + private id = 0; + private importerResult?: ImporterResult; + private fileUrl?: URL; constructor( private readonly self: LegacyPluginThis, private readonly callbacks: Array>, private readonly loadPaths: string[], - initialPrev: string, private readonly sync: SyncBoolean - ) { - const path = initialPrev !== 'stdin'; - this.prev.push({url: path ? p.resolve(initialPrev) : 'stdin', path}); + ) {} + + importers() { + return [ + new LegacyImporterWrapper(this), + new LegacyFileImporterWrapper(this), + ]; } canonicalize( url: string, options: {fromImport: boolean; containingUrl: URL | null} ): PromiseOr { - if (url.startsWith(endOfLoadProtocol)) return new URL(url); + const containingUrl = options.containingUrl; + if (containingUrl === null) { + return null; + } - // Emulate a base importer instead of using a real base importer, - // because we want to mark containingUrl as used, which is impossible - // in a real base importer. - if (options.containingUrl !== null) { - try { - const absoluteUrl = new URL(url, options.containingUrl).toString(); - const resolved = this.canonicalize(absoluteUrl, { - fromImport: options.fromImport, - containingUrl: null, - }); - if (resolved !== null) return resolved; - } catch (error: unknown) { - if ( - error instanceof TypeError && - isErrnoException(error) && - error.code === 'ERR_INVALID_URL' - ) { - // ignore - } else { - throw error; - } + const path = /^[A-Za-z][+\-.0-9A-Za-z]+:/.test(url) + ? decodeURI(url) + : decodeURIComponent(url); + const parentPathOrUndefined = legacyFileUrlToPath(containingUrl); + const parentPath = parentPathOrUndefined ?? 'stdin'; + + if (parentPathOrUndefined !== undefined) { + const absolutePath = url.startsWith('file:') + ? fileUrlToPathCrossPlatform(url) + : p.resolve(p.dirname(parentPath), path); + const resolved = resolvePath(absolutePath, options.fromImport); + if (resolved !== null) { + this.fileUrl = pathToFileURL(resolved); + return null; } } - if ( - url.startsWith(legacyImporterProtocolPrefix) || - url.startsWith(legacyImporterProtocol) - ) { - // A load starts with `legacyImporterProtocolPrefix` if and only if it's a - // relative load for the current importer rather than an absolute load. - // For the most part, we want to ignore these, but for `file:` URLs - // specifically we want to resolve them on the filesystem to ensure - // locality. - const urlWithoutPrefix = url.substring( - legacyImporterProtocolPrefix.length - ); - if (urlWithoutPrefix.startsWith('file:')) { - let resolved: string | null = null; + return thenOr(this.invokeCallbacks(path, parentPath, options), result => { + if (result instanceof Error) throw result; + if (result === null) return null; - try { - const path = fileUrlToPathCrossPlatform(urlWithoutPrefix); - resolved = resolvePath(path, options.fromImport); - } catch (error: unknown) { - if ( - error instanceof TypeError && - isErrnoException(error) && - (error.code === 'ERR_INVALID_URL' || - error.code === 'ERR_INVALID_FILE_URL_PATH') - ) { - // It's possible for `url` to represent an invalid path for the - // current platform. For example, `@import "/foo/bar/baz"` will - // resolve to `file:///foo/bar/baz` which is an invalid URL on - // Windows. In that case, we treat it as though the file doesn't - // exist so that the user's custom importer can still handle the - // URL. - } else { - throw error; - } - } - - if (resolved !== null) { - this.prev.push({url: resolved, path: true}); - return pathToLegacyFileUrl(resolved); - } + if (typeof result !== 'object') { + throw ( + 'Expected importer to return an object, got ' + + `${util.inspect(result)}.` + ); } - return null; - } - - const prev = this.prev[this.prev.length - 1]; - return thenOr( - thenOr(this.invokeCallbacks(url, prev.url, options), result => { - if (result instanceof Error) throw result; - if (result === null) return null; - - if (typeof result !== 'object') { - throw ( - 'Expected importer to return an object, got ' + - `${util.inspect(result)}.` - ); + if ('contents' in result || !('file' in result)) { + const canonicalUrl = pathToLegacyFileUrl( + 'file' in result + ? (result as {file: string}).file + : p.join(p.dirname(parentPath), path) + ); + if (!('file' in result)) { + canonicalUrl.searchParams.set('id', '' + this.id++); } + this.importerResult = { + contents: result.contents || '', + syntax: canonicalUrl.pathname.endsWith('.sass') + ? 'indented' + : canonicalUrl.pathname.endsWith('.css') + ? 'css' + : 'scss', + sourceMapUrl: canonicalUrl, + }; + return canonicalUrl; + } - if ('contents' in result || !('file' in result)) { - this.lastContents = result.contents ?? ''; - - if ('file' in result) { - return new URL( - legacyImporterProtocol + - encodeURI((result as {file: string}).file) - ); - } else if (/^[A-Za-z+.-]+:/.test(url)) { - return new URL(`${legacyImporterProtocolPrefix}${url}`); - } else { - return new URL(legacyImporterProtocol + encodeURI(url)); - } - } else { - if (p.isAbsolute(result.file)) { - const resolved = resolvePath(result.file, options.fromImport); - return resolved ? pathToLegacyFileUrl(resolved) : null; - } - - const prefixes = [...this.loadPaths, '.']; - if (prev.path) prefixes.unshift(p.dirname(prev.url)); - - for (const prefix of prefixes) { - const resolved = resolvePath( - p.join(prefix, result.file), - options.fromImport - ); - if (resolved !== null) return pathToLegacyFileUrl(resolved); + if ('file' in result) { + if (p.isAbsolute(result.file)) { + const resolved = resolvePath(result.file, options.fromImport); + if (resolved !== null) { + this.fileUrl = pathToFileURL(resolved); + return null; } - return null; } - }), - result => { - if (result !== null) { - const path = result.protocol === legacyImporterFileProtocol; - this.prev.push({ - url: path ? legacyFileUrlToPath(result) : url, - path, - }); - return result; - } else { - for (const loadPath of this.loadPaths) { - const resolved = resolvePath( - p.join(loadPath, url), - options.fromImport - ); - if (resolved !== null) return pathToLegacyFileUrl(resolved); + + const prefixes = [p.dirname(parentPath), ...this.loadPaths, '.']; + for (const prefix of prefixes) { + const resolved = resolvePath( + p.join(prefix, result.file), + options.fromImport + ); + if (resolved !== null) { + this.fileUrl = pathToFileURL(resolved); + return null; } - return null; } } - ); + + return null; + }); } - load(canonicalUrl: URL): ImporterResult | null { - if (canonicalUrl.protocol === endOfLoadProtocol) { - this.prev.pop(); - return { - contents: '', - syntax: 'scss', - sourceMapUrl: new URL(endOfLoadProtocol), - }; + load(_canonicalUrl: URL): ImporterResult | null { + if (this.importerResult === undefined) { + return null; } - if (canonicalUrl.protocol === legacyImporterFileProtocol) { - const syntax = canonicalUrl.pathname.endsWith('.sass') - ? 'indented' - : canonicalUrl.pathname.endsWith('.css') - ? 'css' - : 'scss'; - - let contents = - this.lastContents ?? - fs.readFileSync(legacyFileUrlToPath(canonicalUrl), 'utf-8'); - this.lastContents = undefined; - if (syntax === 'scss') { - contents += this.endOfLoadImport; - } else if (syntax === 'indented') { - contents += `\n@import "${endOfLoadProtocol}${endOfLoadCount++}"`; - } else { - this.prev.pop(); - } + const importerResult = this.importerResult; + delete this.importerResult; + return importerResult; + } - return {contents, syntax, sourceMapUrl: canonicalUrl}; + findFileUrl( + _url: string, + options: {fromImport: boolean; containingUrl: URL | null} + ): URL | null { + options.containingUrl; + if (this.fileUrl === undefined) { + return null; } - const lastContents = this.lastContents; - assert.notEqual(lastContents, undefined); - this.lastContents = undefined; - return { - contents: lastContents + this.endOfLoadImport, - syntax: 'scss', - sourceMapUrl: canonicalUrl, - }; + const fileUrl = this.fileUrl; + delete this.fileUrl; + return fileUrl; } // Invokes each callback in `this.callbacks` until one returns a non-null @@ -332,11 +260,4 @@ export class LegacyImporterWrapper if (syncResult !== undefined) resolve(syncResult); }) as PromiseOr; } - - // The `@import` statement to inject after the contents of files to ensure - // that we know when a load has completed so we can pass the correct `prev` - // argument to callbacks. - private get endOfLoadImport(): string { - return `\n;@import "${endOfLoadProtocol}${endOfLoadCount++}";`; - } } diff --git a/lib/src/legacy/index.ts b/lib/src/legacy/index.ts index f5467565..53f1bd34 100644 --- a/lib/src/legacy/index.ts +++ b/lib/src/legacy/index.ts @@ -16,7 +16,6 @@ import { } from '../compile'; import { SyncBoolean, - fileUrlToPathCrossPlatform, isNullOrUndefined, pathToUrlString, withoutExtension, @@ -34,12 +33,12 @@ import { StringOptions, } from '../vendor/sass'; import {wrapFunction} from './value/wrap'; -import {LegacyImporterWrapper, endOfLoadProtocol} from './importer'; +import {LegacyImporterWrapper, LegacyImportersWrapper} from './importer'; import { + legacyFileUrlToPath, legacyImporterProtocol, pathToLegacyFileUrl, removeLegacyImporter, - removeLegacyImporterFromSpan, } from './utils'; export function render( @@ -144,17 +143,14 @@ function convertOptions( const importers = options.importer && (!(options.importer instanceof Array) || options.importer.length > 0) - ? [ - new LegacyImporterWrapper( - self, - options.importer instanceof Array - ? options.importer - : [options.importer], - options.includePaths ?? [], - options.file ?? 'stdin', - sync - ), - ] + ? new LegacyImportersWrapper( + self, + options.importer instanceof Array + ? options.importer + : [options.importer], + options.includePaths ?? [], + sync + ).importers() : undefined; return { @@ -165,7 +161,7 @@ function convertOptions( : importers, sourceMap: wasSourceMapRequested(options), sourceMapIncludeSources: options.sourceMapContents, - loadPaths: importers ? undefined : options.includePaths, + loadPaths: options.includePaths, style: options.outputStyle as 'compressed' | 'expanded' | undefined, quietDeps: options.quietDeps, verbose: options.verbose, @@ -199,11 +195,7 @@ function convertStringOptions( return { ...modernOptions, - url: options.file - ? options.importer - ? pathToLegacyFileUrl(options.file) - : pathToFileURL(options.file) - : new URL(legacyImporterProtocol), + url: options.file ? pathToFileURL(options.file) : pathToLegacyFileUrl(), importer, syntax: options.indentedSyntax ? 'indented' : 'scss', }; @@ -277,20 +269,19 @@ function newLegacyResult( sourceMap.file = 'stdin.css'; } - sourceMap.sources = sourceMap.sources - .filter(source => !source.startsWith(endOfLoadProtocol)) - .map(source => { - source = removeLegacyImporter(source); - if (source.startsWith('file://')) { - return pathToUrlString( - p.relative(sourceMapDir, fileUrlToPathCrossPlatform(source)) - ); - } else if (source.startsWith('data:')) { - return 'stdin'; - } else { - return source; - } - }); + sourceMap.sources = sourceMap.sources.map(source => { + if ( + source.startsWith(legacyImporterProtocol) || + source.startsWith('file:') + ) { + const path = legacyFileUrlToPath(new URL(source)); + return path === undefined + ? 'stdin' + : pathToUrlString(p.relative(sourceMapDir, path)); + } else { + return source; + } + }); sourceMapBytes = Buffer.from(JSON.stringify(sourceMap)); @@ -319,18 +310,9 @@ function newLegacyResult( start, end, duration: end - start, - includedFiles: result.loadedUrls - .filter(url => url.protocol !== endOfLoadProtocol) - .map(url => { - if (url.protocol === legacyImporterProtocol) { - return decodeURI(url.pathname); - } - - const urlString = removeLegacyImporter(url.toString()); - return urlString.startsWith('file:') - ? fileUrlToPathCrossPlatform(urlString) - : urlString; - }), + includedFiles: result.loadedUrls.flatMap(url => { + return legacyFileUrlToPath(url) ?? []; + }), }, }; } @@ -345,19 +327,13 @@ function newLegacyException(error: Error): LegacyException { }); } - const span = error.span ? removeLegacyImporterFromSpan(error.span) : null; - let file: string; - if (!span?.url) { - file = 'stdin'; - } else if (span.url.protocol === 'file:') { - // We have to cast to Node's URL type here because the specified type is the - // standard URL type which is slightly less featureful. `fileURLToPath()` - // does work with standard URL objects in practice, but we know that we - // generate Node URLs here regardless. - file = fileUrlToPathCrossPlatform(span.url as URL); - } else { - file = span.url.toString(); - } + // We have to cast to Node's URL type here because the specified type is the + // standard URL type which is slightly less featureful. `fileURLToPath()` + // does work with standard URL objects in practice, but we know that we + // generate Node URLs here regardless. + const file = isNullOrUndefined(error.span?.url) + ? '-' // This should not happen, but just in case + : legacyFileUrlToPath(error.span.url as URL) ?? 'stdin'; const errorString = removeLegacyImporter(error.toString()); return Object.assign(new Error(), { diff --git a/lib/src/legacy/utils.ts b/lib/src/legacy/utils.ts index 7a72472f..8a10df90 100644 --- a/lib/src/legacy/utils.ts +++ b/lib/src/legacy/utils.ts @@ -2,32 +2,18 @@ // MIT-style license that can be found in the LICENSE file or at // https://opensource.org/licenses/MIT. -import {strict as assert} from 'assert'; -import {pathToFileURL} from 'url'; - +import * as p from 'path'; +import {URL, pathToFileURL} from 'url'; import {fileUrlToPathCrossPlatform} from '../utils'; import {SourceSpan} from '../vendor/sass'; -import {legacyImporterFileProtocol} from './importer'; -/** - * The URL protocol to use for URLs canonicalized using `LegacyImporterWrapper`. - */ -export const legacyImporterProtocol = 'legacy-importer:'; +export const legacyImporterScheme = 'legacy-importer-file'; -/** - * The prefix for absolute URLs canonicalized using `LegacyImporterWrapper`. - * - * This is used to distinguish imports resolved relative to URLs returned by a - * legacy importer from manually-specified absolute URLs. - */ -export const legacyImporterProtocolPrefix = 'legacy-importer-'; +export const legacyImporterProtocol = legacyImporterScheme + ':'; // A regular expression that matches legacy importer protocol syntax that // should be removed from human-readable messages. -const removeLegacyImporterRegExp = new RegExp( - `${legacyImporterProtocol}|${legacyImporterProtocolPrefix}`, - 'g' -); +const removeLegacyImporterRegExp = new RegExp(legacyImporterProtocol, 'g'); // Returns `string` with all instances of legacy importer syntax removed. export function removeLegacyImporter(string: string): string { @@ -37,23 +23,46 @@ export function removeLegacyImporter(string: string): string { // Returns a copy of [span] with the URL updated to remove legacy importer // syntax. export function removeLegacyImporterFromSpan(span: SourceSpan): SourceSpan { - if (!span.url) return span; - return {...span, url: new URL(removeLegacyImporter(span.url.toString()))}; + if (span.url?.protocol === legacyImporterProtocol) { + const path = legacyFileUrlToPath(span.url); + return {...span, url: path === undefined ? undefined : pathToFileURL(path)}; + } + return span; } -// Converts [path] to a `file:` URL and adds the [legacyImporterProtocolPrefix] -// to the beginning so we can distinguish it from manually-specified absolute -// `file:` URLs. -export function pathToLegacyFileUrl(path: string): URL { - return new URL(`${legacyImporterProtocolPrefix}${pathToFileURL(path)}`); +// Converts [path] to a `legacy-importer-file:` URL. +export function pathToLegacyFileUrl(path?: string): URL { + if (path === undefined) { + return new URL(legacyImporterProtocol); + } else if (p.isAbsolute(path)) { + return new URL(legacyImporterProtocol + pathToFileURL(path).pathname); + } else { + const encoded = encodeURI(path) + .replace(/[#?]/g, encodeURIComponent) + .replace( + process.platform === 'win32' ? /%(5B|5C|5D|5E|7C)/g : /%(5B|5D|5E|7C)/g, + decodeURIComponent + ) + .replace(/\\/g, '/'); + return new URL(legacyImporterProtocol + encoded); + } } -// Converts a `file:` URL with [legacyImporterProtocolPrefix] to the filesystem -// path which it represents. -export function legacyFileUrlToPath(url: URL): string { - assert.equal(url.protocol, legacyImporterFileProtocol); - const originalUrl = url - .toString() - .substring(legacyImporterProtocolPrefix.length); - return fileUrlToPathCrossPlatform(originalUrl); +// Converts a `legacy-importer-file:` URL or 'file:' URL to the filesystem path +// which it represents. +export function legacyFileUrlToPath(url: URL): string | undefined { + switch (url.protocol) { + case legacyImporterProtocol: + if (url.pathname === '') { + return undefined; + } else if (url.pathname.startsWith('/')) { + return fileUrlToPathCrossPlatform('file://' + url.pathname); + } else { + return decodeURIComponent(url.pathname); + } + case 'file:': + return fileUrlToPathCrossPlatform(url); + default: + return decodeURI(url.toString()); + } } diff --git a/lib/src/sync-process/index.ts b/lib/src/sync-process/index.ts index 426dbb1c..c92ad8e1 100644 --- a/lib/src/sync-process/index.ts +++ b/lib/src/sync-process/index.ts @@ -57,7 +57,7 @@ export class SyncProcess { this.worker.on('error', console.error); this.stdin = new stream.Writable({ - write: (chunk: Buffer, encoding, callback) => { + write: (chunk: Buffer, _encoding, callback) => { this.port.postMessage( { type: 'stdin', diff --git a/package.json b/package.json index 2983f245..5c86d1da 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "sass-embedded", "version": "1.77.8", - "protocol-version": "2.7.1", + "protocol-version": "2.8.0", "compiler-version": "1.77.8", "description": "Node.js library that communicates with Embedded Dart Sass using the Embedded Sass protocol", "repository": "sass/embedded-host-node",