sounding 0.0.2 → 0.0.4

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/README.md CHANGED
@@ -28,15 +28,28 @@ Sounding also owns its own built-in world engine, so the same package can:
28
28
  - capture outgoing mail by wrapping `sails.helpers.mail.send` and storing normalized messages in `sails.sounding.mailbox`
29
29
 
30
30
  The default configuration story is intentionally calm:
31
- - Sounding disables its hook automatically when Sails runs in `production`
32
- - set `sounding.enableInProduction = true` only for controlled production-like environments such as staging
31
+ - Sounding only enables its hook in the environments listed under `sounding.environments`
32
+ - the default is `['test']`, so non-test boot paths stay dark unless you opt in explicitly
33
+ - if you intentionally need Sounding in another environment, add that environment name to the list
33
34
  - auth conventions auto-detect `User`/`userId` and `Creator`/`creatorId`, with `sounding.auth` available for overrides
34
35
  - Sounding manages a temporary `sails-sqlite` datastore by default
35
36
  - managed SQLite artifacts live under `.tmp/db`
36
37
  - the default datastore identity is `default`
37
38
  - browser projects start with `desktop`
39
+ - mail capture previews use the `mail` layout by default, matching the current `sails-hook-mail` convention
40
+ - apps with a different mail layout can set `sounding.mail.layout`, for example `layout-email`
38
41
  - `inherit` remains available when an app already has a serious test datastore story
39
42
 
43
+ For example:
44
+
45
+ ```js
46
+ module.exports.sounding = {
47
+ environments: ['test'],
48
+ }
49
+ ```
50
+
51
+ If you intentionally want Sounding during another boot path, widen the list explicitly, for example `['test', 'console']` or `['test', 'production']`.
52
+
40
53
  This repository starts with docs-driven product research and the first hook/runtime scaffolding for that vision.
41
54
 
42
55
  See `RESEARCH.md`.
package/index.js CHANGED
@@ -14,12 +14,22 @@ const { createExpect } = require('./lib/create-expect')
14
14
  const { createTestApi } = require('./lib/create-test-api')
15
15
  const { getDefaultConfig } = require('./lib/default-config')
16
16
 
17
- function isProductionEnvironment(sails) {
18
- return (sails.config?.environment || process.env.NODE_ENV) === 'production'
17
+ function getCurrentEnvironment(sails) {
18
+ return sails.config?.environment || process.env.NODE_ENV
19
+ }
20
+
21
+ function getEnabledEnvironments(sails) {
22
+ const configured = sails.config?.sounding?.environments
23
+
24
+ if (Array.isArray(configured)) {
25
+ return configured
26
+ }
27
+
28
+ return getDefaultConfig().environments
19
29
  }
20
30
 
21
31
  function shouldEnableHook(sails) {
22
- return sails.config?.sounding?.enableInProduction === true || !isProductionEnvironment(sails)
32
+ return getEnabledEnvironments(sails).includes(getCurrentEnvironment(sails))
23
33
  }
24
34
 
25
35
  function soundingHook(sails) {
@@ -27,6 +37,7 @@ function soundingHook(sails) {
27
37
 
28
38
  return {
29
39
  defaults: {
40
+ // Hook defaults live under `sails.config.sounding`.
30
41
  sounding: getDefaultConfig(),
31
42
  },
32
43
 
@@ -1,6 +1,8 @@
1
1
  const path = require('node:path')
2
2
  const url = require('node:url')
3
3
 
4
+ const DEFAULT_MAIL_LAYOUT = 'mail'
5
+
4
6
  function normalizeList(value) {
5
7
  if (value === undefined || value === null || value === '') {
6
8
  return []
@@ -62,22 +64,21 @@ function createRenderViewPromise(view) {
62
64
  return Promise.resolve(view)
63
65
  }
64
66
 
65
- async function renderTemplatePreview(sails, {
66
- template,
67
- templateData = {},
68
- layout = 'layout-email',
69
- }) {
67
+ async function renderTemplatePreview(sails, inputs = {}, options = {}) {
68
+ const { template, templateData = {} } = inputs
69
+
70
70
  if (!template || typeof sails?.renderView !== 'function') {
71
71
  return undefined
72
72
  }
73
73
 
74
+ const resolvedLayout = resolvePreviewLayout(sails, inputs, options)
74
75
  const emailTemplatePath = path.join('emails/', template)
75
76
  let emailTemplateLayout = false
76
77
 
77
- if (layout) {
78
+ if (resolvedLayout) {
78
79
  emailTemplateLayout = path.relative(
79
80
  path.dirname(emailTemplatePath),
80
- path.resolve('layouts/', layout)
81
+ path.resolve('layouts/', resolvedLayout)
81
82
  )
82
83
  }
83
84
 
@@ -94,6 +95,31 @@ function resolveMailConfig(sails) {
94
95
  return sails?.config?.mail || {}
95
96
  }
96
97
 
98
+ function resolveSoundingMailConfig(sails, options = {}) {
99
+ if (options.mailSettings) {
100
+ return options.mailSettings
101
+ }
102
+
103
+ const soundingConfig =
104
+ options.soundingConfig || options.config || sails?.config?.sounding || {}
105
+
106
+ return soundingConfig.mail || {}
107
+ }
108
+
109
+ function resolvePreviewLayout(sails, inputs = {}, options = {}) {
110
+ if (Object.prototype.hasOwnProperty.call(inputs, 'layout')) {
111
+ return inputs.layout
112
+ }
113
+
114
+ const mailSettings = resolveSoundingMailConfig(sails, options)
115
+
116
+ if (Object.prototype.hasOwnProperty.call(mailSettings, 'layout')) {
117
+ return mailSettings.layout
118
+ }
119
+
120
+ return DEFAULT_MAIL_LAYOUT
121
+ }
122
+
97
123
  function resolveMailerName(sails, inputs = {}) {
98
124
  return inputs.mailer || process.env.MAIL_MAILER || resolveMailConfig(sails).default
99
125
  }
@@ -125,10 +151,11 @@ function toErrorShape(error) {
125
151
  }
126
152
  }
127
153
 
128
- async function buildCapturedMail(sails, inputs = {}) {
154
+ async function buildCapturedMail(sails, inputs = {}, options = {}) {
129
155
  const mailer = resolveMailerName(sails, inputs)
130
156
  const transport = resolveTransportName(sails, mailer)
131
- const html = await renderTemplatePreview(sails, inputs)
157
+ const layout = resolvePreviewLayout(sails, inputs, options)
158
+ const html = await renderTemplatePreview(sails, inputs, options)
132
159
  const links = extractLinks(html)
133
160
 
134
161
  return {
@@ -150,7 +177,7 @@ async function buildCapturedMail(sails, inputs = {}) {
150
177
  ctaUrl: resolvePrimaryLink(inputs, links),
151
178
  attachments: inputs.attachments || [],
152
179
  headers: inputs.headers || {},
153
- layout: inputs.layout === undefined ? 'layout-email' : inputs.layout,
180
+ layout,
154
181
  }
155
182
  }
156
183
 
@@ -173,6 +200,12 @@ function createMailCapture({ sails, mailbox, getConfig }) {
173
200
  return settings.deliver === true || settings.mode === 'passthrough'
174
201
  }
175
202
 
203
+ function resolveMessageLayout(inputs = {}) {
204
+ return resolvePreviewLayout(sails, inputs, {
205
+ mailSettings: resolveMailSettings(),
206
+ })
207
+ }
208
+
176
209
  function wrapDeferred(executor, onRejected) {
177
210
  let promise = Promise.resolve().then(executor)
178
211
 
@@ -267,7 +300,9 @@ function createMailCapture({ sails, mailbox, getConfig }) {
267
300
 
268
301
  async function captureSuccessfulSend(inputs = {}) {
269
302
  try {
270
- const message = await buildCapturedMail(sails, inputs)
303
+ const message = await buildCapturedMail(sails, inputs, {
304
+ mailSettings: resolveMailSettings(),
305
+ })
271
306
  mailbox.capture({
272
307
  ...message,
273
308
  status: 'sent',
@@ -279,6 +314,7 @@ function createMailCapture({ sails, mailbox, getConfig }) {
279
314
  to: normalizeList(inputs.to),
280
315
  subject: inputs.subject || '',
281
316
  template: inputs.template,
317
+ layout: resolveMessageLayout(inputs),
282
318
  status: 'sent',
283
319
  captureError: toErrorShape(error),
284
320
  })
@@ -289,7 +325,9 @@ function createMailCapture({ sails, mailbox, getConfig }) {
289
325
  let message
290
326
 
291
327
  try {
292
- message = await buildCapturedMail(sails, inputs)
328
+ message = await buildCapturedMail(sails, inputs, {
329
+ mailSettings: resolveMailSettings(),
330
+ })
293
331
  } catch (captureError) {
294
332
  message = {
295
333
  mailer: resolveMailerName(sails, inputs),
@@ -297,6 +335,7 @@ function createMailCapture({ sails, mailbox, getConfig }) {
297
335
  to: normalizeList(inputs.to),
298
336
  subject: inputs.subject || '',
299
337
  template: inputs.template,
338
+ layout: resolveMessageLayout(inputs),
300
339
  captureError: toErrorShape(captureError),
301
340
  }
302
341
  }
@@ -388,4 +427,5 @@ module.exports = {
388
427
  normalizeList,
389
428
  resolvePrimaryLink,
390
429
  renderTemplatePreview,
430
+ resolvePreviewLayout,
391
431
  }
@@ -1,5 +1,7 @@
1
1
  const DEFAULT_CONFIG = Object.freeze({
2
- enableInProduction: false,
2
+ // Sails environments where the Sounding hook should boot.
3
+ // Keep this test-only by default so non-test processes stay dark.
4
+ environments: ['test'],
3
5
  app: {
4
6
  path: '.',
5
7
  environment: 'test',
@@ -28,6 +30,7 @@ const DEFAULT_CONFIG = Object.freeze({
28
30
  },
29
31
  mail: {
30
32
  capture: true,
33
+ layout: 'mail',
31
34
  },
32
35
  request: {
33
36
  transport: 'virtual',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sounding",
3
- "version": "0.0.2",
3
+ "version": "0.0.4",
4
4
  "description": "Testing framework for Sails applications and The Boring JavaScript Stack.",
5
5
  "main": "index.js",
6
6
  "license": "MIT",