From e3634b4d94f48c17a44b7b3d2ee916a85f50d7bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=AA=E3=81=A4=E3=81=8D?= Date: Fri, 25 Oct 2024 08:36:21 -0700 Subject: [PATCH 1/3] Implement `sass --embedded` in pure JS mode --- .github/workflows/ci.yml | 12 +------- bin/sass.ts | 5 +++ lib/src/compiler-path.ts | 58 ++++++++++++++++++----------------- tool/get-embedded-compiler.ts | 42 +++++++++++++++++++------ tool/init.ts | 10 ++++-- 5 files changed, 76 insertions(+), 51 deletions(-) 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']); } } From 76097f0ffc0c6bade73d308441fe03304f5b2b1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=AA=E3=81=A4=E3=81=8D?= Date: Mon, 3 Feb 2025 10:19:56 -0800 Subject: [PATCH 2/3] Add sass-embedded-linux-unknown and sass-embedded-unknown-unknown packages --- npm/linux-unknown/README.md | 3 +++ npm/linux-unknown/package.json | 24 ++++++++++++++++++++++++ npm/unknown-unknown/README.md | 3 +++ npm/unknown-unknown/package.json | 19 +++++++++++++++++++ package.json | 4 +++- tool/prepare-optional-release.ts | 14 +++++++++++++- 6 files changed, 65 insertions(+), 2 deletions(-) create mode 100644 npm/linux-unknown/README.md create mode 100644 npm/linux-unknown/package.json create mode 100644 npm/unknown-unknown/README.md create mode 100644 npm/unknown-unknown/package.json diff --git a/npm/linux-unknown/README.md b/npm/linux-unknown/README.md new file mode 100644 index 00000000..44582c6e --- /dev/null +++ b/npm/linux-unknown/README.md @@ -0,0 +1,3 @@ +# `sass-embedded-linux-unknown` + +This is the **dart2js** binary for [`sass-embedded`](https://www.npmjs.com/package/sass-embedded) diff --git a/npm/linux-unknown/package.json b/npm/linux-unknown/package.json new file mode 100644 index 00000000..a0f7c44d --- /dev/null +++ b/npm/linux-unknown/package.json @@ -0,0 +1,24 @@ +{ + "name": "sass-embedded-linux-unknown", + "version": "1.83.4", + "description": "The dart2js binary for sass-embedded", + "repository": "sass/embedded-host-node", + "author": "Google Inc.", + "license": "MIT", + "dependencies": { + "sass": "1.83.4" + }, + "engines": { + "node": ">=14.0.0" + }, + "os": [ + "linux" + ], + "cpu": [ + "!arm", + "!arm64", + "!ia32", + "!riscv64", + "!x64" + ] +} diff --git a/npm/unknown-unknown/README.md b/npm/unknown-unknown/README.md new file mode 100644 index 00000000..fb09ed37 --- /dev/null +++ b/npm/unknown-unknown/README.md @@ -0,0 +1,3 @@ +# `sass-embedded-unknown-unknown` + +This is the **dart2js** binary for [`sass-embedded`](https://www.npmjs.com/package/sass-embedded) diff --git a/npm/unknown-unknown/package.json b/npm/unknown-unknown/package.json new file mode 100644 index 00000000..5e3fa385 --- /dev/null +++ b/npm/unknown-unknown/package.json @@ -0,0 +1,19 @@ +{ + "name": "sass-embedded-unknown-unknown", + "version": "1.83.4", + "description": "The dart2js binary for sass-embedded", + "repository": "sass/embedded-host-node", + "author": "Google Inc.", + "license": "MIT", + "dependencies": { + "sass": "1.83.4" + }, + "engines": { + "node": ">=14.0.0" + }, + "os": [ + "!darwin", + "!linux", + "!win32" + ] +} diff --git a/package.json b/package.json index 47fa671a..b015d57f 100644 --- a/package.json +++ b/package.json @@ -55,9 +55,11 @@ "sass-embedded-linux-musl-ia32": "1.83.4", "sass-embedded-linux-musl-riscv64": "1.83.4", "sass-embedded-linux-musl-x64": "1.83.4", + "sass-embedded-linux-unknown": "1.83.4", "sass-embedded-win32-arm64": "1.83.4", "sass-embedded-win32-ia32": "1.83.4", - "sass-embedded-win32-x64": "1.83.4" + "sass-embedded-win32-x64": "1.83.4", + "sass-embedded-unknown-unknown": "1.83.4" }, "dependencies": { "@bufbuild/protobuf": "^2.0.0", diff --git a/tool/prepare-optional-release.ts b/tool/prepare-optional-release.ts index 94bf8b4d..1486e9fd 100644 --- a/tool/prepare-optional-release.ts +++ b/tool/prepare-optional-release.ts @@ -123,13 +123,25 @@ void (async () => { ); } + const outPath = p.join('npm', argv.package); + if (argv.package.indexOf('unknown') !== -1) { + const pkg = JSON.parse( + (await fs.readFile(p.join(outPath, 'package.json'))).toString(), + ); + if (pkg.version !== pkg.dependencies.sass) { + throw Error(`dependency sass's version doesn't match ${pkg.version}`); + } + + // Skip downloading for platform running dart2js. + return; + } + const index = argv.package.lastIndexOf('-'); const nodePlatform = argv.package.substring(0, index); const nodeArch = argv.package.substring(index + 1); const dartPlatform = nodePlatformToDartPlatform(nodePlatform); const dartArch = nodeArchToDartArch(nodeArch); const isMusl = nodePlatform === 'linux-musl'; - const outPath = p.join('npm', argv.package); await downloadRelease({ repo: 'dart-sass', assetUrl: From 3b03ee31e19a2e95dfaf3c55d65a3b25354431f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=AA=E3=81=A4=E3=81=8D?= Date: Wed, 5 Feb 2025 08:51:45 -0800 Subject: [PATCH 3/3] Split compilerModule to its own file --- lib/src/compiler-module.ts | 34 ++++++++++++++++++++++++++++++++++ lib/src/compiler-path.ts | 30 +----------------------------- tool/get-embedded-compiler.ts | 2 +- 3 files changed, 36 insertions(+), 30 deletions(-) create mode 100644 lib/src/compiler-module.ts diff --git a/lib/src/compiler-module.ts b/lib/src/compiler-module.ts new file mode 100644 index 00000000..113170c7 --- /dev/null +++ b/lib/src/compiler-module.ts @@ -0,0 +1,34 @@ +// Copyright 2025 Google LLC. Use of this source code is governed by an +// MIT-style license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +import * as p from 'path'; +import {getElfInterpreter} from './elf'; + +/** + * Detect if the given binary is linked with musl libc by checking if + * the interpreter basename starts with "ld-musl-" + */ +function isLinuxMusl(path: string): boolean { + try { + const interpreter = getElfInterpreter(path); + return p.basename(interpreter).startsWith('ld-musl-'); + } catch (error) { + console.warn( + `Warning: Failed to detect linux-musl, fallback to linux-gnu: ${error.message}`, + ); + return false; + } +} + +/** The module name for the embedded compiler executable. */ +export const compilerModule = (() => { + const platform = + process.platform === 'linux' && isLinuxMusl(process.execPath) + ? 'linux-musl' + : (process.platform as string); + + const arch = process.arch; + + return `sass-embedded-${platform}-${arch}`; +})(); diff --git a/lib/src/compiler-path.ts b/lib/src/compiler-path.ts index 8e3ec8f4..169c0c08 100644 --- a/lib/src/compiler-path.ts +++ b/lib/src/compiler-path.ts @@ -3,35 +3,7 @@ // https://opensource.org/licenses/MIT. import * as p from 'path'; -import {getElfInterpreter} from './elf'; - -/** - * Detect if the given binary is linked with musl libc by checking if - * the interpreter basename starts with "ld-musl-" - */ -function isLinuxMusl(path: string): boolean { - try { - const interpreter = getElfInterpreter(path); - return p.basename(interpreter).startsWith('ld-musl-'); - } catch (error) { - console.warn( - `Warning: Failed to detect linux-musl, fallback to linux-gnu: ${error.message}`, - ); - return false; - } -} - -/** The module name for the embedded compiler executable. */ -export const compilerModule = (() => { - const platform = - process.platform === 'linux' && isLinuxMusl(process.execPath) - ? 'linux-musl' - : (process.platform as string); - - const arch = process.arch; - - return `sass-embedded-${platform}-${arch}`; -})(); +import {compilerModule} from './compiler-module'; /** The full command for the embedded compiler executable. */ export const compilerCommand = (() => { diff --git a/tool/get-embedded-compiler.ts b/tool/get-embedded-compiler.ts index 63ce6068..85b44490 100644 --- a/tool/get-embedded-compiler.ts +++ b/tool/get-embedded-compiler.ts @@ -6,7 +6,7 @@ import {promises as fs} from 'fs'; import * as p from 'path'; import * as shell from 'shelljs'; -import {compilerModule} from '../lib/src/compiler-path'; +import {compilerModule} from '../lib/src/compiler-module'; import * as utils from './utils'; /**