Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions app/javascript/packages/build-sass/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
## Unreleased

### New Features

- Add support for verbose CLI output using `--verbose` flag (`-v` shorthand), which currently outputs files being built.

### Bug Fixes

- Fix rebuild after error when using `--watch` mode.

## 3.0.0

### Breaking Changes
Expand Down
1 change: 1 addition & 0 deletions app/javascript/packages/build-sass/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ Flags:
- `--out-dir`: The output directory
- `--watch`: Run in watch mode, recompiling files on change
- `--load-path`: Include additional path in Sass path resolution
- `--verbose` (`-v`): Output verbose debugging details

### API

Expand Down
12 changes: 10 additions & 2 deletions app/javascript/packages/build-sass/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import getErrorSassStackPaths from './get-error-sass-stack-paths.js';
/** @typedef {import('sass-embedded').Options<'sync'>} SyncSassOptions */
/** @typedef {import('sass-embedded').Exception} SassException */
/** @typedef {import('./').BuildOptions} BuildOptions */
/** @typedef {import('node:child_process').ChildProcess} ChildProcess */
/** @typedef {import('sass-embedded').AsyncCompiler & { process: ChildProcess}} SassAsyncCompiler */

const env = process.env.NODE_ENV || process.env.RAILS_ENV || 'development';
const isProduction = env === 'production';
Expand All @@ -24,10 +26,11 @@ const { values: flags, positionals: fileArgs } = parseArgs({
watch: { type: 'boolean' },
'out-dir': { type: 'string' },
'load-path': { type: 'string', multiple: true, default: [] },
verbose: { type: 'boolean', short: 'v' },
},
});

const { watch: isWatching, 'out-dir': outDir, 'load-path': loadPaths = [] } = flags;
const { watch: isWatching, 'out-dir': outDir, 'load-path': loadPaths = [], verbose } = flags;
loadPaths.push(...getDefaultLoadPaths());

const sassCompiler = await initAsyncSassCompiler();
Expand Down Expand Up @@ -62,6 +65,10 @@ const isSassException = (error) => 'span' in /** @type {SassException} */ (error
* @return {Promise<void|void[]>}
*/
function build(files) {
if (verbose) {
console.log('Building files', files);
}

return Promise.all(
files.map(async (file) => {
const { loadedUrls } = await buildFile(file, options);
Expand All @@ -75,7 +82,8 @@ function build(files) {
console.error(error);

if (isWatching && isSassException(error)) {
watchOnce(getErrorSassStackPaths(error.sassStack), () => build(files));
const { spawnfile } = /** @type {SassAsyncCompiler} */ (sassCompiler).process;
watchOnce(getErrorSassStackPaths(error.sassStack, spawnfile), () => build(files));
} else {
throw error;
}
Expand Down
20 changes: 9 additions & 11 deletions app/javascript/packages/build-sass/get-error-sass-stack-paths.js
Original file line number Diff line number Diff line change
@@ -1,32 +1,30 @@
import { dirname, relative, resolve } from 'path';

/**
* Returns all file paths contained in the given Sass stack trace.
*
* @example
* ```
* getErrorSassStackPaths(
* 'node_modules/@18f/identity-design-system/dist/assets/scss/uswds/core/_functions.scss 35:8 divide()\n' +
* 'node_modules/@18f/identity-design-system/dist/assets/scss/uswds/core/mixins/_icon.scss 77:12 add-color-icon()\n' +
* 'app/assets/stylesheets/components/_alert.scss 13:5 @import\n' +
* 'app/assets/stylesheets/components/all.scss 3:9 @import\n' +
* 'app/assets/stylesheets/application.css.scss 7:9 root stylesheet\n',
* '../../../../app/assets/stylesheets/design-system-waiting-room.scss 31:2 @forward\n' +
* '../../../../app/assets/stylesheets/application.css.scss 4:1 root stylesheet\n',
* 'node_modules/sass-embedded-darwin-arm64/dart-sass/src/dart',
* );
* // [
* // 'node_modules/@18f/identity-design-system/dist/assets/scss/uswds/core/_functions.scss',
* // 'node_modules/@18f/identity-design-system/dist/assets/scss/uswds/core/mixins/_icon.scss',
* // 'app/assets/stylesheets/components/_alert.scss',
* // 'app/assets/stylesheets/components/all.scss',
* // 'app/assets/stylesheets/design-system-waiting-room.scss',
* // 'app/assets/stylesheets/application.css.scss',
* // ]
* ```
*
* @param {string} sassStack Sass stack trace (see example).
* @param {string} relativeFrom File from which to resolve relative paths from Sass stack trace.
*
* @return {string[]} Array of file paths.
*/
const getErrorSassStackPaths = (sassStack) =>
const getErrorSassStackPaths = (sassStack, relativeFrom) =>
sassStack
.split(/\.scss \d+:\d+\s+.+?\n/)
.filter(Boolean)
.map((basename) => `${basename}.scss`);
.map((basename) => relative('.', resolve(dirname(relativeFrom), `${basename}.scss`)));

export default getErrorSassStackPaths;
Original file line number Diff line number Diff line change
@@ -1,39 +1,29 @@
import getErrorSassStackPaths from './get-error-sass-stack-paths.js';

describe('getErrorSassStackPaths', () => {
it('returns an array of paths from a sass stack message', () => {
it('returns an array of paths from a sass stack message resolved from relative file', () => {
const stackPaths = getErrorSassStackPaths(
'node_modules/@18f/identity-design-system/dist/assets/scss/uswds/core/_functions.scss 35:8 divide()\n' +
'node_modules/@18f/identity-design-system/dist/assets/scss/uswds/core/mixins/_icon.scss 77:12 add-color-icon()\n' +
'app/assets/stylesheets/components/_alert.scss 13:5 @import\n' +
'app/assets/stylesheets/components/all.scss 3:9 @import\n' +
'app/assets/stylesheets/application.css.scss 7:9 root stylesheet\n',
'../../../../app/assets/stylesheets/design-system-waiting-room.scss 31:2 @forward\n' +
'../../../../app/assets/stylesheets/application.css.scss 4:1 root stylesheet\n',
'node_modules/sass-embedded-darwin-arm64/dart-sass/src/dart',
);

expect(stackPaths).to.deep.equal([
'node_modules/@18f/identity-design-system/dist/assets/scss/uswds/core/_functions.scss',
'node_modules/@18f/identity-design-system/dist/assets/scss/uswds/core/mixins/_icon.scss',
'app/assets/stylesheets/components/_alert.scss',
'app/assets/stylesheets/components/all.scss',
'app/assets/stylesheets/design-system-waiting-room.scss',
'app/assets/stylesheets/application.css.scss',
]);
});

context('with a stack path containing a space', () => {
it('returns an array of paths from a sass stack message', () => {
it('returns an array of paths from a sass stack message resolved from relative file', () => {
const stackPaths = getErrorSassStackPaths(
'node_modules/@18f/identity-design-system/dist/assets/scss/uswds/core/_functions.scss 35:8 divide()\n' +
'node_modules/@18f/identity-design-system/dist/assets/scss/uswds/core/mixins/_icon.scss 77:12 add-color-icon()\n' +
'app/assets/stylesheets/components/_alert example.scss 13:5 @import\n' +
'app/assets/stylesheets/components/all.scss 3:9 @import\n' +
'app/assets/stylesheets/application.css.scss 7:9 root stylesheet\n',
'../../../../app/assets/stylesheets/design-system waiting-room.scss 31:2 @forward\n' +
'../../../../app/assets/stylesheets/application.css.scss 4:1 root stylesheet\n',
'node_modules/sass-embedded-darwin-arm64/dart-sass/src/dart',
);

expect(stackPaths).to.deep.equal([
'node_modules/@18f/identity-design-system/dist/assets/scss/uswds/core/_functions.scss',
'node_modules/@18f/identity-design-system/dist/assets/scss/uswds/core/mixins/_icon.scss',
'app/assets/stylesheets/components/_alert example.scss',
'app/assets/stylesheets/components/all.scss',
'app/assets/stylesheets/design-system waiting-room.scss',
'app/assets/stylesheets/application.css.scss',
]);
});
Expand Down