create-express-new 1.2.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -6,6 +6,15 @@ All notable changes to this project are documented here. The format is based on
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [1.3.0] - 2026-10-11
10
+
11
+ ### Added
12
+
13
+ - `--sass` adds Sass to a web app: a `style.scss` compiled to the CSS the app serves by a `css`
14
+ script that runs before `start` and `dev`, a `css:watch` script, and a stage in the `--docker`
15
+ image that compiles the stylesheets. The wizard offers it under Extras
16
+ - Issue templates for bug reports and feature requests
17
+
9
18
  ## [1.2.0] - 2026-10-11
10
19
 
11
20
  ### Added
@@ -136,7 +145,8 @@ the full list and a table for migrating commands.
136
145
  - The `-e/--ejs`, `--hbs`, `--pug` and `-H/--hogan` aliases; use `--view=<engine>`
137
146
  - The `debug` package and `DEBUG=...` start instructions
138
147
 
139
- [Unreleased]: https://github.com/w0244079/create-express-new/compare/v1.2.0...HEAD
148
+ [Unreleased]: https://github.com/w0244079/create-express-new/compare/v1.3.0...HEAD
149
+ [1.3.0]: https://github.com/w0244079/create-express-new/compare/v1.2.0...v1.3.0
140
150
  [1.2.0]: https://github.com/w0244079/create-express-new/compare/v1.1.0...v1.2.0
141
151
  [1.1.0]: https://github.com/w0244079/create-express-new/compare/v1.0.0...v1.1.0
142
152
  [1.0.0]: https://github.com/w0244079/create-express-new/releases/tag/v1.0.0
package/README.md CHANGED
@@ -30,7 +30,7 @@ $ pnpm create express-new@latest
30
30
  - the language: JavaScript, TypeScript or CommonJS
31
31
  - optional middleware, and CSRF protection with sessions
32
32
  - the request logger
33
- - extras: a Dockerfile and ESLint
33
+ - extras: a Dockerfile, ESLint and Sass
34
34
  - a `.gitignore`
35
35
  - the [package manager](#package-manager) the app requires: pnpm, or npm
36
36
 
@@ -237,6 +237,7 @@ The image:
237
237
  - is based on `node:22-slim`, the latest release of the minimum supported Node.js version
238
238
  - installs only production dependencies, in a build stage of their own, so the package manager is
239
239
  not in the final image
240
+ - with `--sass`, compiles the stylesheets in another build stage, so Sass is not in the final image
240
241
  - runs the app as the unprivileged `node` user with `NODE_ENV=production`
241
242
  - checks `/health` with a `HEALTHCHECK`
242
243
  - runs `node` directly, so it receives `SIGTERM` and shuts down gracefully
@@ -250,6 +251,21 @@ The image:
250
251
  `--ts` it also adds [typescript-eslint](https://typescript-eslint.io/); as typescript-eslint does not
251
252
  support TypeScript 7 yet, these apps use TypeScript 6.
252
253
 
254
+ ### Sass
255
+
256
+ `--sass` adds [Sass](https://sass-lang.com/) to a web app: a `public/stylesheets/style.scss` in place
257
+ of `style.css`, compiled to the CSS the app serves.
258
+
259
+ ```bash
260
+ $ pnpm start # compiles the stylesheets, then starts the app
261
+ $ pnpm dev # compiles them once, then restarts the app on change
262
+ $ pnpm css:watch # recompiles them on change, in a second terminal
263
+ ```
264
+
265
+ Sass is a development dependency, so `pnpm start` fails where only production dependencies are
266
+ installed: compile the stylesheets first with `pnpm css`, as the `--docker` image does.
267
+ [Using Sass](docs/sass.md) covers this, the files it ignores and adding Sass to an existing app.
268
+
253
269
  ## Command Line Options
254
270
 
255
271
  -v, --view <engine> add view <engine> support (ejs|hbs|pug|twig) (defaults to pug)
@@ -273,6 +289,7 @@ support TypeScript 7 yet, these apps use TypeScript 6.
273
289
  token (all|pages) (defaults to all)
274
290
  --uploads add multer and an upload route at /uploads
275
291
  --logger <name> request logger (morgan|pino) (defaults to morgan)
292
+ --sass add Sass, compiling .scss stylesheets (not with --api alone)
276
293
  --docker add a Dockerfile for a production image
277
294
  --lint add ESLint and a lint script
278
295
  --pm <name> package manager the app requires (pnpm|npm) (defaults to pnpm)
@@ -289,11 +306,12 @@ support TypeScript 7 yet, these apps use TypeScript 6.
289
306
 
290
307
  This fork started from `express-generator` 4.16.1. It generates Express 5 apps as ES modules, with
291
308
  pug in place of jade, for Node.js 22.9 or newer. `--view=<engine>` replaces the `--ejs`, `--hbs` and
292
- `--pug` aliases, and apps use plain CSS instead of a CSS preprocessor.
309
+ `--pug` aliases, and apps use plain CSS unless you add Sass with `--sass`; the other CSS preprocessors
310
+ are gone.
293
311
 
294
312
  - [Changes from express-generator](docs/changes-from-express-generator.md): everything added,
295
313
  changed and removed, and a table for migrating commands
296
- - [Using Sass](docs/sass.md): adding Sass to a generated app
314
+ - [Using Sass](docs/sass.md): what `--sass` adds, and adding Sass to an existing app
297
315
 
298
316
  ## Contributing
299
317
 
@@ -35,6 +35,7 @@ const OPTIONS = {
35
35
  'no-view': { type: 'boolean' },
36
36
  pm: { type: 'string' },
37
37
  'rate-limit': { type: 'boolean' },
38
+ sass: { type: 'boolean' },
38
39
  session: { type: 'boolean' },
39
40
  ts: { type: 'boolean' },
40
41
  uploads: { type: 'boolean' },
@@ -351,6 +352,13 @@ function main (options, done) {
351
352
  return done(1)
352
353
  }
353
354
 
355
+ // Sass is for the stylesheets of web apps
356
+ if (options.sass && options.api && !options.view) {
357
+ usage()
358
+ error('option `--sass\' cannot be used with `--api\', unless `--view\' is given too')
359
+ return done(1)
360
+ }
361
+
354
362
  if (options.csrf && !options.session) {
355
363
  usage()
356
364
  error('option `--csrf\' requires `--session\'')
@@ -527,6 +535,7 @@ function usage () {
527
535
  console.log(' token (all|pages) (defaults to all)')
528
536
  console.log(' --uploads add multer and an upload route at /uploads')
529
537
  console.log(' --logger <name> request logger (morgan|pino) (defaults to morgan)')
538
+ console.log(' --sass add Sass, compiling .scss stylesheets (not with --api alone)')
530
539
  console.log(' --docker add a Dockerfile for a production image')
531
540
  console.log(' --lint add ESLint and a lint script')
532
541
  console.log(' --pm <name> package manager the app requires (pnpm|npm) (defaults to pnpm)')
package/lib/app.js CHANGED
@@ -84,6 +84,7 @@ const ALTERNATIVES = [
84
84
  'eslint.config.js',
85
85
  'eslint.config.mjs',
86
86
  'pnpm-workspace.yaml',
87
+ 'public/stylesheets/style.scss',
87
88
  ...fs.readdirSync(path.join(TEMPLATE_DIR, 'views')).map((file) => 'views/' + file)
88
89
  ]
89
90
 
@@ -112,10 +113,12 @@ export function planApp (name, options) {
112
113
  file(to, fs.readFileSync(path.join(TEMPLATE_DIR, from), 'utf-8'), extra)
113
114
  }
114
115
 
115
- // an ignore file, ignoring uploaded files too
116
+ // an ignore file, ignoring uploaded files and compiled stylesheets too
116
117
  function ignore (from) {
117
- const contents = fs.readFileSync(path.join(TEMPLATE_DIR, from), 'utf-8')
118
- return options.uploads ? contents + '\n# Uploaded files\nuploads/\n' : contents
118
+ let contents = fs.readFileSync(path.join(TEMPLATE_DIR, from), 'utf-8')
119
+ if (options.uploads) contents += '\n# Uploaded files\nuploads/\n'
120
+ if (sass) contents += '\n# Stylesheets compiled from Sass: write styles in .scss files\npublic/stylesheets/*.css\npublic/stylesheets/*.css.map\n'
121
+ return contents
119
122
  }
120
123
 
121
124
  // Module format
@@ -137,6 +140,9 @@ export function planApp (name, options) {
137
140
  const corsApi = webApi && options.corsScope !== 'all'
138
141
  const csrfSkipApi = webApi && csrf && options.csrfScope === 'pages'
139
142
 
143
+ // Sass, for the stylesheets of apps with static files
144
+ const sass = Boolean(options.sass) && !api
145
+
140
146
  // File uploads, with an upload form in apps with views
141
147
  const uploads = Boolean(options.uploads)
142
148
 
@@ -274,7 +280,25 @@ export function planApp (name, options) {
274
280
  dir('public/stylesheets')
275
281
 
276
282
  // Stylesheet
277
- copy('public/stylesheets/style.css', 'public/stylesheets/style.css')
283
+ if (sass) {
284
+ copy('public/stylesheets/style.scss', 'public/stylesheets/style.scss')
285
+ } else {
286
+ copy('public/stylesheets/style.css', 'public/stylesheets/style.css')
287
+ }
288
+ }
289
+
290
+ // Sass compiles each .scss file to the .css file the app serves, before
291
+ // the app starts: a development dependency, so the stylesheets of an app
292
+ // installed without them are compiled first, as in the Dockerfile
293
+ if (sass) {
294
+ const styles = 'public/stylesheets:public/stylesheets'
295
+
296
+ pkg.scripts.css = 'sass ' + styles
297
+ // recompile on change, alongside the dev script
298
+ pkg.scripts['css:watch'] = 'sass --watch ' + styles
299
+ pkg.scripts.prestart = scriptCommand(pm, 'css')
300
+ pkg.scripts.predev = scriptCommand(pm, 'css')
301
+ pkg.devDependencies.sass = VERSIONS.sass
278
302
  }
279
303
 
280
304
  // copy route templates
@@ -421,7 +445,15 @@ export function planApp (name, options) {
421
445
 
422
446
  // Package manager settings, protecting installs from compromised packages
423
447
  if (pm === 'pnpm') {
424
- copy('app/pnpm-workspace.yaml', 'pnpm-workspace.yaml', { config: true })
448
+ let settings = fs.readFileSync(path.join(TEMPLATE_DIR, 'app/pnpm-workspace.yaml'), 'utf-8')
449
+
450
+ // Sass's optional file watcher has an install script, which would stop
451
+ // the install, and which it works without
452
+ if (sass) {
453
+ settings += '\n# Do not run the install script of the file watcher Sass uses, which works\n# without it.\nallowBuilds:\n \'@parcel/watcher\': false\n'
454
+ }
455
+
456
+ file('pnpm-workspace.yaml', settings, { config: true })
425
457
  } else {
426
458
  copy('app/npmrc', '.npmrc', { config: true })
427
459
  }
@@ -432,6 +464,8 @@ export function planApp (name, options) {
432
464
  dockerfile.locals.name = name
433
465
  dockerfile.locals.www = www
434
466
  dockerfile.locals.uploads = uploads
467
+ dockerfile.locals.sass = sass
468
+ dockerfile.locals.css = scriptCommand(pm, 'css')
435
469
  dockerfile.locals.pm = pm
436
470
  // the exact package manager version, for repeatable image builds
437
471
  dockerfile.locals.pmVersion = VERSIONS[pm].replace(/^\^/, '')
package/lib/wizard.js CHANGED
@@ -138,7 +138,9 @@ export async function wizard (io) {
138
138
  initial: answers.extras,
139
139
  choices: [
140
140
  { label: 'Dockerfile', value: 'docker', hint: 'production image with a /health check' },
141
- { label: 'ESLint', value: 'lint', hint: 'with a lint script' }
141
+ { label: 'ESLint', value: 'lint', hint: 'with a lint script' },
142
+ // stylesheets are for web apps
143
+ ...(answers.kind !== 'api' ? [{ label: 'Sass', value: 'sass', hint: 'stylesheets compiled from .scss files' }] : [])
142
144
  ]
143
145
  })
144
146
  },
@@ -361,6 +363,7 @@ function toOptions (answers) {
361
363
  logger: answers.logger ?? 'morgan',
362
364
  pm: answers.pm ?? DEFAULT_PACKAGE_MANAGER,
363
365
  rateLimit: middleware.includes('rate-limit'),
366
+ sass: kind !== 'api' && extras.includes('sass'),
364
367
  session,
365
368
  ts: language === 'ts',
366
369
  uploads: middleware.includes('uploads'),
@@ -397,6 +400,7 @@ export function toCommand (options) {
397
400
  if (options.csrfScope === 'pages') flags.push('--csrf-scope=pages')
398
401
  if (options.uploads) flags.push('--uploads')
399
402
  if (options.logger && options.logger !== 'morgan') flags.push('--logger=' + options.logger)
403
+ if (options.sass) flags.push('--sass')
400
404
  if (options.docker) flags.push('--docker')
401
405
  if (options.lint) flags.push('--lint')
402
406
  if (pm !== DEFAULT_PACKAGE_MANAGER) flags.push('--pm=' + pm)
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "create-express-new",
3
3
  "description": "Express 5 application generator: ESM, TypeScript and CommonJS app templates. A modern successor to express-generator.",
4
- "version": "1.2.0",
4
+ "version": "1.3.0",
5
5
  "author": "Michael Caines (https://github.com/w0244079)",
6
6
  "contributors": [
7
7
  "TJ Holowaychuk <tj@vision-media.ca>",
@@ -36,6 +36,20 @@ COPY package*.json .npmrc ./
36
36
  RUN npm ci --omit=dev
37
37
  <% } -%>
38
38
 
39
+ <% if (sass) { -%>
40
+ # Stylesheets are compiled in their own stage too, as Sass is a development
41
+ # dependency, which the final image does not have.
42
+ FROM dependencies AS stylesheets
43
+
44
+ <% if (pm === 'pnpm') { -%>
45
+ RUN pnpm install --frozen-lockfile
46
+ <% } else { -%>
47
+ RUN npm ci
48
+ <% } -%>
49
+ COPY public/stylesheets ./public/stylesheets
50
+ RUN <%- css %>
51
+
52
+ <% } -%>
39
53
  FROM node:22-slim
40
54
 
41
55
  ENV NODE_ENV=production
@@ -47,6 +61,11 @@ COPY --from=dependencies /app/node_modules ./node_modules
47
61
  # copy the app owned by the unprivileged node user, which runs it, so it can
48
62
  # read the files whatever their permissions on the build machine
49
63
  COPY --chown=node:node . .
64
+ <% if (sass) { -%>
65
+
66
+ # the stylesheets compiled above
67
+ COPY --chown=node:node --from=stylesheets /app/public/stylesheets ./public/stylesheets
68
+ <% } -%>
50
69
  <% if (uploads) { -%>
51
70
 
52
71
  # a folder for uploaded files that the node user can write to; files saved
@@ -0,0 +1,10 @@
1
+ $link-color: #00B7FF;
2
+
3
+ body {
4
+ padding: 50px;
5
+ font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;
6
+ }
7
+
8
+ a {
9
+ color: $link-color;
10
+ }
@@ -30,6 +30,7 @@
30
30
  "pino-pretty": "^13.1.3",
31
31
  "pnpm": "^12.9.1",
32
32
  "pug": "^3.0.4",
33
+ "sass": "^1.105.1",
33
34
  "twig": "^3.0.0",
34
35
  "typescript": "^7.0.2",
35
36
  "typescript-eslint": "^8.71.0",