diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dd3614a3..c16c75c3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -117,17 +117,7 @@ jobs: working-directory: sass-spec - name: Compile - run: | - npm run compile - if [[ "$RUNNER_OS" == "Windows" ]]; then - # Avoid copying the entire Dart Sass build directory on Windows, - # since it may contain symlinks that cp will choke on. - mkdir -p dist/lib/src/vendor/dart-sass/ - cp {`pwd`/,dist/}lib/src/vendor/dart-sass/sass.bat - cp {`pwd`/,dist/}lib/src/vendor/dart-sass/sass.snapshot - else - ln -s {`pwd`/,dist/}lib/src/vendor/dart-sass - fi + run: npm run compile - name: Run tests run: npm run js-api-spec -- --sassPackage .. --sassSassRepo ../language diff --git a/bin/sass.ts b/bin/sass.ts index 80c60125..f057b5e1 100755 --- a/bin/sass.ts +++ b/bin/sass.ts @@ -1,6 +1,7 @@ #!/usr/bin/env node import * as child_process from 'child_process'; +import * as path from 'path'; import {compilerCommand} from '../lib/src/compiler-path'; // TODO npm/cmd-shim#152 and yarnpkg/berry#6422 - If and when the package @@ -12,6 +13,10 @@ try { compilerCommand[0], [...compilerCommand.slice(1), ...process.argv.slice(2)], { + // Node blocks launching .bat and .cmd without a shell due to CVE-2024-27980 + shell: ['.bat', '.cmd'].includes( + path.extname(compilerCommand[0]).toLowerCase(), + ), stdio: 'inherit', windowsHide: true, }, diff --git a/lib/src/compiler-path.ts b/lib/src/compiler-path.ts index b9344cf5..8e3ec8f4 100644 --- a/lib/src/compiler-path.ts +++ b/lib/src/compiler-path.ts @@ -2,10 +2,8 @@ // MIT-style license that can be found in the LICENSE file or at // https://opensource.org/licenses/MIT. -import * as fs from 'fs'; import * as p from 'path'; import {getElfInterpreter} from './elf'; -import {isErrnoException} from './utils'; /** * Detect if the given binary is linked with musl libc by checking if @@ -23,8 +21,8 @@ function isLinuxMusl(path: string): boolean { } } -/** The full command for the embedded compiler executable. */ -export const compilerCommand = (() => { +/** The module name for the embedded compiler executable. */ +export const compilerModule = (() => { const platform = process.platform === 'linux' && isLinuxMusl(process.execPath) ? 'linux-musl' @@ -32,48 +30,52 @@ export const compilerCommand = (() => { const arch = process.arch; - // find for development - for (const path of ['vendor', '../../../lib/src/vendor']) { - const executable = p.resolve( - __dirname, - path, - `dart-sass/sass${platform === 'win32' ? '.bat' : ''}`, - ); - - if (fs.existsSync(executable)) return [executable]; - } + return `sass-embedded-${platform}-${arch}`; +})(); +/** The full command for the embedded compiler executable. */ +export const compilerCommand = (() => { try { return [ require.resolve( - `sass-embedded-${platform}-${arch}/dart-sass/src/dart` + - (platform === 'win32' ? '.exe' : ''), - ), - require.resolve( - `sass-embedded-${platform}-${arch}/dart-sass/src/sass.snapshot`, + `${compilerModule}/dart-sass/src/dart` + + (process.platform === 'win32' ? '.exe' : ''), ), + require.resolve(`${compilerModule}/dart-sass/src/sass.snapshot`), ]; - } catch (ignored) { - // ignored + } catch (e) { + if (e.code !== 'MODULE_NOT_FOUND') { + throw e; + } } try { return [ require.resolve( - `sass-embedded-${platform}-${arch}/dart-sass/sass` + - (platform === 'win32' ? '.bat' : ''), + `${compilerModule}/dart-sass/sass` + + (process.platform === 'win32' ? '.bat' : ''), ), ]; - } catch (e: unknown) { - if (!(isErrnoException(e) && e.code === 'MODULE_NOT_FOUND')) { + } catch (e) { + if (e.code !== 'MODULE_NOT_FOUND') { + throw e; + } + } + + try { + return [ + process.execPath, + p.join(p.dirname(require.resolve('sass')), 'sass.js'), + ]; + } catch (e) { + if (e.code !== 'MODULE_NOT_FOUND') { throw e; } } throw new Error( "Embedded Dart Sass couldn't find the embedded compiler executable. " + - 'Please make sure the optional dependency ' + - `sass-embedded-${platform}-${arch} is installed in ` + - 'node_modules.', + `Please make sure the optional dependency ${compilerModule} or sass is ` + + 'installed in node_modules.', ); })(); diff --git a/tool/get-embedded-compiler.ts b/tool/get-embedded-compiler.ts index 828c22b8..63ce6068 100644 --- a/tool/get-embedded-compiler.ts +++ b/tool/get-embedded-compiler.ts @@ -2,9 +2,11 @@ // MIT-style license that can be found in the LICENSE file or at // https://opensource.org/licenses/MIT. +import {promises as fs} from 'fs'; import * as p from 'path'; import * as shell from 'shelljs'; +import {compilerModule} from '../lib/src/compiler-path'; import * as utils from './utils'; /** @@ -14,7 +16,7 @@ import * as utils from './utils'; * at `path`. By default, checks out the latest revision from GitHub. */ export async function getEmbeddedCompiler( - outPath: string, + js?: boolean, options?: {ref: string} | {path: string}, ): Promise { const repo = 'dart-sass'; @@ -41,21 +43,43 @@ export async function getEmbeddedCompiler( await utils.link(languageInHost, languageInCompiler); } - buildDartSassEmbedded(source); - await utils.link(p.join(source, 'build'), p.join(outPath, repo)); + buildDartSassEmbedded(source, js ?? false); + + const jsModulePath = p.resolve('node_modules/sass'); + const dartModulePath = p.resolve(p.join('node_modules', compilerModule)); + if (js) { + await fs.rm(dartModulePath, {force: true, recursive: true}); + await utils.link(p.join(source, 'build/npm'), jsModulePath); + } else { + await fs.rm(jsModulePath, {force: true, recursive: true}); + await utils.link(p.join(source, 'build'), p.join(dartModulePath, repo)); + } } // Builds the Embedded Dart Sass executable from the source at `repoPath`. -function buildDartSassEmbedded(repoPath: string): void { +function buildDartSassEmbedded(repoPath: string, js: boolean): void { console.log("Downloading Dart Sass's dependencies."); shell.exec('dart pub upgrade', { cwd: repoPath, silent: true, }); - console.log('Building the Dart Sass executable.'); - shell.exec('dart run grinder protobuf pkg-standalone-dev', { - cwd: repoPath, - env: {...process.env, UPDATE_SASS_PROTOCOL: 'false'}, - }); + if (js) { + shell.exec('npm install', { + cwd: repoPath, + silent: true, + }); + + console.log('Building the Dart Sass npm package.'); + shell.exec('dart run grinder protobuf pkg-npm-dev', { + cwd: repoPath, + env: {...process.env, UPDATE_SASS_PROTOCOL: 'false'}, + }); + } else { + console.log('Building the Dart Sass executable.'); + shell.exec('dart run grinder protobuf pkg-standalone-dev', { + cwd: repoPath, + env: {...process.env, UPDATE_SASS_PROTOCOL: 'false'}, + }); + } } diff --git a/tool/init.ts b/tool/init.ts index 230cd09d..80f7b71d 100644 --- a/tool/init.ts +++ b/tool/init.ts @@ -18,6 +18,10 @@ const argv = yargs(process.argv.slice(2)) type: 'string', description: 'Build the Embedded Dart Sass binary from this Git ref.', }) + .option('compiler-js', { + type: 'boolean', + description: 'Build the Embedded Dart Sass with dart2js.', + }) .option('skip-compiler', { type: 'boolean', description: "Don't Embedded Dart Sass at all.", @@ -55,15 +59,15 @@ void (async () => { if (!argv['skip-compiler']) { if (argv['compiler-ref']) { - await getEmbeddedCompiler(outPath, { + await getEmbeddedCompiler(argv['compiler-js'], { ref: argv['compiler-ref'], }); } else if (argv['compiler-path']) { - await getEmbeddedCompiler(outPath, { + await getEmbeddedCompiler(argv['compiler-js'], { path: argv['compiler-path'], }); } else { - await getEmbeddedCompiler(outPath); + await getEmbeddedCompiler(argv['compiler-js']); } }