Skip to content
Merged
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
"lint": "eslint . --fix",
"test": "npm run lint && npm run test:integration && npm run test:coverage",
"test:integration": "node --test --test-reporter=spec test/integration/*-test.js",
"test:coverage": "node --test --test-reporter=spec --experimental-test-coverage test/unit/**/*-test.js",
"test:unit": "node --test --test-reporter=spec test/unit/**/*-test.js",
"test:coverage": "node --test --test-reporter=spec --experimental-test-coverage --test-coverage-lines=100 --test-coverage-exclude='test/**/*' 'test/unit/**/*-test.js'",
"test:unit": "node --test --test-reporter=spec --test-coverage-exclude='test/**/*' 'test/unit/**/*-test.js'",
Comment thread
ryanblock marked this conversation as resolved.
Outdated
"rc": "npm version prerelease --preid RC",
"vendor": "scripts/vendor"
},
Expand Down
4 changes: 2 additions & 2 deletions src/config/pragmas/plugins.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ module.exports = async function getPluginModules ({ arc, inventory, errors }) {

if (pluginPath) {
try {
/* istanbul ignore next: idk why but for some reason nyc isn't picking up the catches; all cases are covered in tests, though! */
if (type === 'plugin') {
try {
plugins[name] = require(pluginPath)
Expand Down Expand Up @@ -167,7 +166,8 @@ async function resolve (path, cwd) {
catch {
return
}
/* istanbul ignore next: idk why but for some reason nyc isn't picking up the catches; all cases are covered in tests, though! */
// idk why but for some reason we aren't picking up the catches; all cases are covered in tests, though!
/* node:coverage ignore next 2 */
if (gotSomething) return mjsPath
else return
}
Expand Down
32 changes: 32 additions & 0 deletions src/config/pragmas/populate-lambda/_queues.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
let { is, getLambdaDirs } = require('../../../lib')

function getQueueBehaviorProps (item, name) {
let fifo = is.defined(item?.[name]?.fifo) ? item[name].fifo : null
let batchSize = is.defined(item?.[name]?.batchSize) ? item[name].batchSize : null
let batchWindow = is.defined(item?.[name]?.batchWindow) ? item[name].batchWindow : null
return { fifo, batchSize, batchWindow }
}

module.exports = function populateQueues (params) {
let { type, item, errors, plugin } = params

if (plugin) {
let { name, src } = item
if (name && src) {
return { ...item, ...getQueueBehaviorProps(item, name), ...getLambdaDirs(params, { plugin }) }
}
errors.push(`Invalid plugin-generated @${type} item: name: ${name}, src: ${src}`)
return
}
else if (is.string(item)) {
let name = item
let dirs = getLambdaDirs(params, { name })
return { name, ...getQueueBehaviorProps(item, name), ...dirs }
}
else if (is.object(item)) {
let name = Object.keys(item)[0]
let dirs = getLambdaDirs(params, { name, customSrc: item[name].src })
return { name, ...getQueueBehaviorProps(item, name), ...dirs }
}
errors.push(`Invalid @${type} item: ${item}`)
}
1 change: 0 additions & 1 deletion src/config/pragmas/populate-lambda/get-handler.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ module.exports = function getHandler ({ config, src, build, errors }) {
}
// Compiled to a binary
else if (customRuntimeType === 'compiled') {
/* istanbul ignore next */
let bootstrap = `bootstrap${isWin ? '.exe' : ''}`
handlerFile = join(build, runtimeConfig.buildSubpath || '', runtimeConfig.handlerFile || bootstrap)
handlerMethod = null
Expand Down
4 changes: 2 additions & 2 deletions src/config/pragmas/populate-lambda/get-lambda.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
let getHTTP = require('./_http')
let getEvents = require('./_events')
let getCustomLambdas = require('./_custom-lambdas')
let getQueues = require('./_queues')
let getScheduled = require('./_scheduled')
let getWS = require('./_ws')
let getTablesStreams = require('./_tables-streams')
Expand All @@ -14,10 +15,9 @@ module.exports = function getLambda (params) {
if (type === 'http') return getHTTP(params)
if (type === 'events') return getEvents(params)
if (type === cl) return getCustomLambdas(params)
if (type === 'queues') return getEvents(params) // Effectively the same as events
if (type === 'queues') return getQueues(params)
if (type === 'scheduled') return getScheduled(params)
if (type === ts) return getTablesStreams(params)
if (type === 'tables') return getTablesStreams(params) // Shortcut for creating streams
/* istanbul ignore else: clearer to be explicit here */
if (type === 'ws') return getWS(params)
}
10 changes: 7 additions & 3 deletions src/config/pragmas/populate-lambda/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,9 +90,13 @@ function populate (type, pragma, inventory, errors, plugin) {
let config = defaultProjectConfig()
config = { ...config, ...getKnownProps(configProps, result.config) }

// Knock out any pragma-specific early
// Knock out any pragma-specific bits early
if (type === 'queues') {
config.fifo = config.fifo === undefined ? true : config.fifo
// Queues lifted up FIFO out of config and into top-level function semantics
// config.fifo remains for backward compat (until we want to make a breaking change), while also allowing queue functions to respect @aws global overrides
if (!is.nullish(result.fifo)) config.fifo = result.fifo
else if ((!is.nullish(config.fifo))) result.fifo = config.fifo
else config.fifo = result.fifo = true
}
if (type === 'http') {
if (name.startsWith('get ') || name.startsWith('any ')) {
Expand Down Expand Up @@ -170,7 +174,7 @@ function populate (type, pragma, inventory, errors, plugin) {
let normalize = path => path.replace(/[\\\/]/g, sep)

// Lambda setter plugins can technically return anything, so this ensures everything is tidy
let lambdaProps = [ 'cron', 'method', 'path', 'plugin', 'rate', 'route', 'table', 'type' ]
let lambdaProps = [ 'cron', 'batchSize', 'batchWindow', 'fifo', 'method', 'path', 'plugin', 'rate', 'route', 'table', 'type' ]
let configProps = [ ...Object.keys(defaultFunctionConfig()), 'fifo', 'views' ]
let getKnownProps = (knownProps, raw = {}) => {
let props = knownProps.flatMap(prop => is.defined(raw[prop]) ? [ [ prop, raw[prop] ] ] : [])
Expand Down
3 changes: 2 additions & 1 deletion src/config/pragmas/sort/http.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ module.exports = function sortHTTP (http) {
let sorted = []
httpMethods.forEach(method => {
if (!tree[method]) return
/* istanbul ignore next: random test shuffles may not trigger all paths */
// random test shuffles may not trigger all paths
/* node:coverage ignore next */
tree[method]
// Sort by depth
.sort((a, b) => b.depth - a.depth)
Expand Down
14 changes: 14 additions & 0 deletions src/config/pragmas/validate/_events.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
let { is } = require('../../../lib')
let { regex, size, unique } = require('./_lib')

/**
Expand Down Expand Up @@ -38,6 +39,19 @@ module.exports = function validateEventsAndQueues (pragma, pragmaName, errors) {
if (n.startsWith('aws') || n.startsWith('amazon')) {
errors.push(`Invalid ${pragmaName} item (cannot start with 'AWS' or 'Amazon'): ${name}`)
}

if (event.pragma === 'queues') {
let { fifo, batchSize, batchWindow } = event
if (!is.nullish(fifo) && !is.bool(fifo)) {
errors.push(`Invalid ${pragmaName} item (fifo must be a boolean): ${name}`)
Comment thread
ryanblock marked this conversation as resolved.
Outdated
}
if (!is.nullish(batchSize) && !is.number(batchSize)) {
errors.push(`Invalid ${pragmaName} item (batchSize must be a number): ${name}`)
}
if (!is.nullish(batchWindow) && !is.number(batchWindow)) {
errors.push(`Invalid ${pragmaName} item (batchWindow must be a number): ${name}`)
}
}
})
}
}
2 changes: 0 additions & 2 deletions src/config/project/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ module.exports = function getProjectConfig (params) {
_project[`${scope}PreferencesFile`] = p.preferencesFile

// Build out the final preferences
/* istanbul ignore else: jic */
if (!_project.preferences) _project.preferences = {}
Object.keys(p.preferences).forEach(pragma => {
// Ignore the raw data
Expand All @@ -44,7 +43,6 @@ module.exports = function getProjectConfig (params) {
return
}
// Traverse and merge individual settings
/* istanbul ignore else: jic */
if (!_project.preferences[pragma]) _project.preferences[pragma] = {}
Object.entries(p.preferences[pragma]).forEach(([ setting, value ]) => {
_project.preferences[pragma][setting] = value
Expand Down
2 changes: 1 addition & 1 deletion src/config/project/prefs/dotenv.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright (c) 2015, Scott Motte
// All rights reserved.

/* istanbul ignore file */
/* node:coverage disable */
/* eslint-disable */
// node_modules/dotenv/lib/main.js
var fs = require("fs");
Expand Down
13 changes: 8 additions & 5 deletions src/config/project/prefs/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,14 @@ module.exports = function getPrefs ({ scope, inventory, errors }) {
// Arc outputs an object of nested arrays
// Basically, construct a pared-down intermediate prefs obj for consumers
Object.entries(prefs.arc).forEach(([ key, val ]) => {
/* istanbul ignore else: Parser should get this, but jic */
// Parser should get this, but jic ignore the else - except node test doesn't do ignore else
if (!preferences[key]) preferences[key] = {}
/* istanbul ignore else: Parser should only produce arrays, but jic */
// Parser should only produce arrays, but jic
/* node:coverage ignore next */
if (is.array(val)) {
val.forEach(v => {
if (is.array(v)) {
/* istanbul ignore if: Single vals should be strings, but jic */
// Single vals should be strings, but jic - except node test doesn't do ignore if
if (v.length === 1) preferences[key] = v[0]
if (v.length === 2) preferences[key][v[0]] = v[1]
if (v.length > 2) preferences[key][v[0]] = [ ...v.slice(1) ]
Expand All @@ -46,7 +47,8 @@ module.exports = function getPrefs ({ scope, inventory, errors }) {
// Turn env vars with spaces into strings
if (key === 'env') {
[ 'testing', 'staging', 'production' ].forEach(e => {
/* istanbul ignore else: Yet another jic */
// Yet another jic
/* node:coverage ignore next */
if (preferences.env[e]) {
Object.entries(preferences.env[e]).forEach(([ key, val ]) => {
if (!valid.envVar.test(key)) {
Expand All @@ -62,7 +64,8 @@ module.exports = function getPrefs ({ scope, inventory, errors }) {
if (key === 'sandbox-start' || key === 'sandbox-startup') {
preferences[key] = val.map(v => {
if (is.string(v)) return v
/* istanbul ignore else: Yet another jic */
// Yet another jic
/* node:coverage ignore next */
if (is.array(v)) return v.join(' ')
})
}
Expand Down
2 changes: 1 addition & 1 deletion src/defaults/function-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ module.exports = function createDefaultFunctionConfig () {
return {
timeout: 5,
memory: 1152,
runtime: 'nodejs20.x',
runtime: 'nodejs22.x',
Comment thread
ryanblock marked this conversation as resolved.
architecture: 'arm64',
handler: 'index.handler',
state: 'n/a',
Expand Down
2 changes: 1 addition & 1 deletion src/env/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ module.exports = function env (params, inventory, callback) {
let { profile, region } = inventory.aws
let result = []
let awsLite = require('@aws-lite/client')
/* istanbul ignore next */
/* node:coverage ignore next */
awsLite({ profile, region, plugins: [ import('@aws-lite/ssm') ] }).then(_aws => {
aws = _aws

Expand Down
2 changes: 0 additions & 2 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,6 @@ module.exports = function architectInventory (params = {}, callback) {

// @plugins come first, as they register hooks all around the project
plugins(project, (err, result) => {
/* istanbul ignore next: yeah we know what happens here */
if (err) callback(err)
else {
inventory.plugins = result
Expand Down Expand Up @@ -102,7 +101,6 @@ module.exports = function architectInventory (params = {}, callback) {

// Maybe get env vars
getEnv(params, inventory, function done (err) {
/* istanbul ignore next: yeah we know what happens here */
if (err) callback(err)
else {
callback(null, {
Expand Down
2 changes: 1 addition & 1 deletion src/lib/asap-src.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ module.exports = function asapSrc (params = {}) {
try {
return require.resolve('@architect/asap')
}
/* node:coverage ignore next 3 */
catch {
/* istanbul ignore next */
throw Error('Cannot find ASAP module!')
}
}
4 changes: 2 additions & 2 deletions test/unit/src/config/pragmas/populate-lambda/index-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +298,7 @@ custom setting
modified = {
timeout: 10,
memory: 128,
runtime: 'nodejs20.x',
runtime: 'nodejs22.x',
handler: 'index.handler',
custom: 'setting',
}
Expand All @@ -323,7 +323,7 @@ custom setting
modified = {
timeout: 10,
memory: 128,
runtime: 'nodejs20.x',
runtime: 'nodejs22.x',
handler: 'lambda.handler',
custom: 'setting',
}
Expand Down
58 changes: 56 additions & 2 deletions test/unit/src/config/pragmas/queues-test.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
let { join } = require('node:path')
let parse = require('@architect/parser')
let { test } = require('node:test')
let cwd = process.cwd()
let inventoryDefaults = require('../../../../../src/defaults')
let populateQueues = require('../../../../../src/config/pragmas/queues')
let testLib = require('../../../../lib')
let cwd = process.cwd()

let inventory = inventoryDefaults()
let queuesDir = join(cwd, 'src', 'queues')
let values = [ 'foo', 'bar' ]
let setterPluginSetup = testLib.setterPluginSetup.bind({}, 'queues')

test('Set up env', t => {
t.plan(1)
Expand Down Expand Up @@ -53,6 +55,27 @@ ${values[0]}
})
})

test('@queues: fifo, batchSize, batchWindow', t => {
t.plan(3)
let arc
let queues

arc = parse(`
@queues
${values[0]}
fifo false
batchSize 1
batchWindow 2
`)
queues = populateQueues({ arc, inventory })
queues.forEach(queue => {
let { fifo, batchSize, batchWindow } = queue
t.assert.equal(fifo, false, `Queue fifo is correct`)
t.assert.equal(batchSize, 1, `Queue batchSize is correct`)
t.assert.equal(batchWindow, 2, `Queue batchWindow is correct`)
})
})

test('@queues population: simple format', t => {
t.plan(7)

Expand Down Expand Up @@ -122,8 +145,30 @@ ${complexValues.join('\n')}
})
})

test('@queues population: validation errors', t => {
test('@queues population: plugin setter', t => {
t.plan(13)

let inventory = inventoryDefaults()
let setter = () => values.map(v => ({ name: v, src: join(queuesDir, v) }))
inventory.plugins = setterPluginSetup(setter)

let queues = populateQueues({ arc: {}, inventory })
t.assert.equal(queues.length, values.length, 'Got correct number of queues back')
values.forEach(val => {
t.assert.ok(queues.some(queue => queue.name === val), `Got queue: ${val}`)
})
queues.forEach(queue => {
let { handlerFile, name, src, fifo, batchSize, batchWindow } = queue
t.assert.equal(src, join(queuesDir, name), `Queue configured with correct source dir: ${src}`)
t.assert.ok(handlerFile.startsWith(src), `Handler file is in the correct source dir`)
t.assert.equal(fifo, true, `Queue defaults to correct fifo`)
t.assert.equal(batchSize, null, `Queue defaults to correct batchSize`)
t.assert.equal(batchWindow, null, `Queue defaults to correct batchWindow`)
})
})

test('@queues population: validation errors', t => {
t.plan(16)
// Test assumes complex format is outputting the same data as simple, so we're only testing errors in the simple format
let errors = []
function run (str) {
Expand Down Expand Up @@ -185,4 +230,13 @@ hi

run(`Amazon.hi-there`)
check()

run(`a-queue\n fifo hi`)
check()

run(`a-queue\n batchSize hi`)
check()

run(`a-queue\n batchWindow hi`)
check()
})