diff --git a/package-lock.json b/package-lock.json index b24ac27..8047747 100644 --- a/package-lock.json +++ b/package-lock.json @@ -40,7 +40,7 @@ "postcss-inline-svg": "^6.0.0", "postcss-modules": "^6.0.0", "prettier": "^2.7.1", - "sass": "^1.49.0", + "sass": "^1.77.8", "string-hash": "^1.1.3", "tslib": "^2.4.0", "typescript": "^4.7.4", @@ -7736,9 +7736,9 @@ "peer": true }, "node_modules/sass": { - "version": "1.63.6", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.63.6.tgz", - "integrity": "sha512-MJuxGMHzaOW7ipp+1KdELtqKbfAWbH7OLIdoSMnVe3EXPMTmxTmlaZDCTsgIpPCs3w99lLo9/zDKkOrJuT5byw==", + "version": "1.77.8", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.77.8.tgz", + "integrity": "sha512-4UHg6prsrycW20fqLGPShtEvo/WyHRVRHwOP4DzkUrObWoWI05QBSfzU71TVB7PFaL104TwNaHpjlWXAZbQiNQ==", "dependencies": { "chokidar": ">=3.0.0 <4.0.0", "immutable": "^4.0.0", @@ -14559,9 +14559,9 @@ "peer": true }, "sass": { - "version": "1.63.6", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.63.6.tgz", - "integrity": "sha512-MJuxGMHzaOW7ipp+1KdELtqKbfAWbH7OLIdoSMnVe3EXPMTmxTmlaZDCTsgIpPCs3w99lLo9/zDKkOrJuT5byw==", + "version": "1.77.8", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.77.8.tgz", + "integrity": "sha512-4UHg6prsrycW20fqLGPShtEvo/WyHRVRHwOP4DzkUrObWoWI05QBSfzU71TVB7PFaL104TwNaHpjlWXAZbQiNQ==", "requires": { "chokidar": ">=3.0.0 <4.0.0", "immutable": "^4.0.0", diff --git a/package.json b/package.json index a8c9c69..c6e0fa2 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,7 @@ "postcss-inline-svg": "^6.0.0", "postcss-modules": "^6.0.0", "prettier": "^2.7.1", - "sass": "^1.49.0", + "sass": "^1.77.8", "string-hash": "^1.1.3", "tslib": "^2.4.0", "typescript": "^4.7.4", diff --git a/src/build/__tests__/__fixtures__/scss-only/mixed-declarations/styles.scss b/src/build/__tests__/__fixtures__/scss-only/mixed-declarations/styles.scss new file mode 100644 index 0000000..8789bfd --- /dev/null +++ b/src/build/__tests__/__fixtures__/scss-only/mixed-declarations/styles.scss @@ -0,0 +1,11 @@ +.example { + color: red; + + a { + font-weight: bold; + } + + // top level style after the nested rule to trigger this deprecation warning + // https://sass-lang.com/documentation/breaking-changes/mixed-decls/ + font-weight: normal; +} diff --git a/src/build/__tests__/styles.test.ts b/src/build/__tests__/styles.test.ts index 6ce436c..ffd589e 100644 --- a/src/build/__tests__/styles.test.ts +++ b/src/build/__tests__/styles.test.ts @@ -1,16 +1,19 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 import { expect, test } from 'vitest'; -import { buildStyles, InlineStylesheet } from '../internal'; +import { buildStyles, InlineStylesheet, BuildStylesOptions } from '../internal'; import { join } from 'node:path'; import { readFileSync, readdirSync } from 'node:fs'; const fixturesRoot = join(__dirname, '__fixtures__', 'scss-only'); const outputRoot = join(__dirname, 'out', 'scss-only'); -async function buildWithFixtures(suiteName: string, inlines?: InlineStylesheet[]) { +async function buildWithFixtures( + suiteName: string, + { inlines, options }: { inlines?: InlineStylesheet[]; options?: BuildStylesOptions } = {} +) { const outDir = join(outputRoot, suiteName); - await buildStyles(join(fixturesRoot, suiteName), join(outputRoot, suiteName), inlines); + await buildStyles(join(fixturesRoot, suiteName), join(outputRoot, suiteName), inlines, options); return outDir; } @@ -40,7 +43,9 @@ test('bundles all imports for styles.scss entry point', async () => { }); test('supports virtual stylesheets', async () => { - const outDir = await buildWithFixtures('inlines', [{ url: 'awsui:tokens', contents: '$color-background: #abcabc' }]); + const outDir = await buildWithFixtures('inlines', { + inlines: [{ url: 'awsui:tokens', contents: '$color-background: #abcabc' }], + }); expect(readdirSync(outDir)).toEqual(['styles.css.js', 'styles.scoped.css', 'styles.selectors.js']); const cssContent = readFileSync(join(outDir, 'styles.scoped.css'), 'utf8'); expect(cssContent).toContain('#abcabc'); @@ -51,6 +56,16 @@ test('throws an error if a virtual stylesheet is not defined', async () => { await expect(() => buildWithFixtures('inlines')).rejects.toThrowError(/awsui:tokens/); }); +test('ignores deprecation warnings by default', async () => { + await expect(buildWithFixtures('mixed-declarations')).resolves.toEqual(expect.any(String)); +}); + +test('throws an error if errors on deprecation warnings are enabled', async () => { + await expect(() => + buildWithFixtures('mixed-declarations', { options: { failOnDeprecations: true } }) + ).rejects.toThrowError(/Unexpected deprecation warnings during sass build/); +}); + test('mirrors directory structure of the sources', async () => { const outDir = await buildWithFixtures('dir-structure'); expect(readdirSync(outDir)).toEqual(['styles.css.js', 'styles.scoped.css', 'styles.selectors.js', 'sub-component']); diff --git a/src/build/internal.ts b/src/build/internal.ts index f643194..62adaa9 100644 --- a/src/build/internal.ts +++ b/src/build/internal.ts @@ -1,6 +1,6 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -import { buildStyles, InlineStylesheet } from './tasks/style'; +import { buildStyles, InlineStylesheet, BuildStylesOptions } from './tasks/style'; import { createPresetFiles } from './tasks/preset'; import { createInternalTokenFiles } from './tasks/internal-tokens'; import { createPublicTokenFiles } from './tasks/public-tokens'; @@ -9,7 +9,7 @@ import { getInlineStylesheets } from './inline-stylesheets'; import { calculatePropertiesMap } from './properties'; import findNeededTokens from './needed-tokens'; -export { buildStyles, InlineStylesheet }; +export { buildStyles, InlineStylesheet, BuildStylesOptions }; export type Tasks = 'preset' | 'design-tokens'; @@ -38,6 +38,8 @@ export interface BuildThemedComponentsInternalParams { descriptions?: Record; /** Indicates whether to generate a JSON schema for design tokens JSON format and validate against the schema **/ jsonSchema?: boolean; + /** Fail the build when SASS deprecation warning occurs **/ + failOnDeprecations?: boolean; } /** * Builds themed components and optionally design tokens, if not skipped. @@ -68,6 +70,7 @@ export async function buildThemedComponentsInternal(params: BuildThemedComponent skip = [], descriptions = {}, jsonSchema = false, + failOnDeprecations, } = params; if (!skip.includes('design-tokens') && !designTokensOutputDir) { @@ -83,7 +86,8 @@ export async function buildThemedComponentsInternal(params: BuildThemedComponent const styleTask = buildStyles( scssDir, componentsOutputDir, - getInlineStylesheets(primary, secondary, defaults, variablesMap, propertiesMap, neededTokens) + getInlineStylesheets(primary, secondary, defaults, variablesMap, propertiesMap, neededTokens), + { failOnDeprecations } ); const internalTokensTask = createInternalTokenFiles(defaults, propertiesMap, componentsOutputDir); diff --git a/src/build/tasks/style.ts b/src/build/tasks/style.ts index 42e355b..f213dc1 100644 --- a/src/build/tasks/style.ts +++ b/src/build/tasks/style.ts @@ -13,9 +13,18 @@ export interface InlineStylesheet { contents: string; } -export async function buildStyles(sassDir: string, outputDir: string, inlines: InlineStylesheet[] = []) { +export interface BuildStylesOptions { + failOnDeprecations?: boolean; +} + +export async function buildStyles( + sassDir: string, + outputDir: string, + inlines: InlineStylesheet[] = [], + options: BuildStylesOptions = {} +) { const files = await promisify(glob)('**/styles.scss', { cwd: sassDir }); - const compiler = createCompiler(inlines, outputDir, sassDir); + const compiler = createCompiler(inlines, outputDir, sassDir, options); const promises = files.map((file) => compiler(file)); @@ -55,15 +64,31 @@ const npmImports = { }, }; -function createCompiler(inlines: InlineStylesheet[], outputDir: string, sassDir: string) { +function createCompiler(inlines: InlineStylesheet[], outputDir: string, sassDir: string, options: BuildStylesOptions) { const importer = createImporter(inlines); return async (file: string) => { const input = path.join(sassDir, file); + const deprecations: Array = []; const sassResult = sass.compile(input, { + logger: { + warn(message, meta) { + if (meta.deprecation && options.failOnDeprecations) { + deprecations.push(message); + } else { + console.warn(message); + } + }, + }, style: 'expanded', importers: [importer, npmImports], }); + if (deprecations.length > 0) { + for (const deprecation of deprecations) { + console.error(deprecation); + } + throw new Error('Unexpected deprecation warnings during sass build.'); + } const intermediate = path.join(sassDir, rename(file, '.css')); const postCSSForEachResult = await postCSSForEach(sassDir, outputDir, sassResult.css, intermediate); const postCSSAfterAllResult = await postCSSAfterAll(postCSSForEachResult.css, intermediate);