Has anyone successfully authored a NPM custom cons...
# help
d
Has anyone successfully authored a NPM custom construct that uses
sst.Function
internally? Each time I’ve tried this I run into issues with where to store construct assets and how to reference them into
sst.Function
An interesting though about how it would be great if you could develop custom constructs using SST. Having some way to package up those contructs for others to use.
d
It’s really hard, and I got it to function, so long as monorepos werent in play, but I dont recommend the pattern and have since abandoned the construct as the SST console does what it did.
if I were doing it again today, I would have it deploy an SST function only when
IS_LOCAL
and otherwise use
cdk.lambda_nodejs
d
hmm… ya the setup is pretty different between the two though no? Also not sure how that would work with
yarn link
d
link
uses the built package, so it would work fine (wouldn’t be
IS_LOCAL
). The setup has some different props, but relatively similar (other than
handler
, which is a key, valuable difference).
if you mean could you link and somehow get the SST function to work locally in the consuming stack…I dont think so.
with modern NPM/Yarn, its pretty impossible to know where exactly your function is going to end up once installed.
nodejs_lambda
and
lambda
allow you to have a relative reference, though. you could always ask SST to do the same.
f
Hey @Dan Van Brunt what error do you get? Not able to find the file specified by
handler
? If you have a sample repo for a npm module or a screenshot of the error, I want to put it on my todo to give it a try. And I can prioritize this.
d
@Frank Actually, I’m not getting an error, yet… Just anticipating issues since this custom construct will be in npm folder. The next challenge is that the function requires two parts… 1. the function code supplied by the construct 2. extra assets …supplied by the user of the construct. I’m currently noodling how that could even work. Seems like I would have to use
cdk.Function
for the core
/assets
code and then borrow similar copyFiles functionality from
sst.Function
in order to bake in the files supplied by the construct user. Otherwise, not sure how I might be able to just use
sst.Function
for BOTH of the above.
@Frank just tested, yes the error you said…
Copy code
Error: Cannot find a handler file for "/Users/me/Projects/klick-packages/packages/cdk-constructs/assets/social-cards"
hardcoding this path works…. Any ideas how to derive that same path?
Copy code
const api = new sst.Api(this, 'Api', {
      routes: {
        'GET /{template}/{file}': {
          function: {
            handler: `node_modules/@klickmarketing/cdk-constructs/assets/social-cards/index.handler`,
d
hardcoding this path works….
So long as the package is installed at the root. 🙂
d
well this is why I want to derive it relative to the tsconfig root
d
Agreed, just helping Frank understand the problem.
This interface, from the cdk base
lambda
works (I have packages that work this way):
Copy code
code: lambda.Code.fromAsset(path.join(__dirname, 'my-lambda-handler')),
  handler: 'index.main',
d
right?…. but I think thats because
sst.Function
demands that its urls are relative of the srcPath
which My custom construct has no consept of what that srcPath is. However, I’ve been googling to see if there is some nodejs function that would allow you to get the root path of where the tsconfig that is running is. That “should be” the
srcPath
d
yep, but since
sst.Function
uses
cdk.Lambda
, it shouldn’t be a crazy lift to support the other pattern. an object overload to
handler
, maybe.
i think thinking about it inside
sst.Function
is likely to be the easiest path.
rather than you trying to do gymnastics
d
perhaps… I just thought there might be a simple
global.__basedir
or something
d
even if there was, the likelihood of future problems would be…above average, in my estimation.
d
ya maybe, I’m sure I don’t fully grasp the issue.
d
you seem to have a solid grasp, and you are following the path I did almost exactly, im mostly just trying to help you skip steps I have already stepped.
d
also… just a consideration…. I ran into issues trying to convert modules that rely on
node_modules
explicitly since later versions of yarn3 do not have a node_modules folder. But thats pretty much out of scope for my brain while trying to also figure this mess out. 😄
d
this is one of a few reasons I wrote this message:
It’s really hard, and I got it to function, so long as monorepos werent in play, but I dont recommend the pattern and have since abandoned the construct as the SST console does what it did.
relying on explicit
node_modules
just seems like a no-no in general, as even old timey
commonjs
stopped you from doing so whenever possible.
d
Actually, getting the
rootPath
is easy…. it seems the issue is more on how to get the TRUE _dirname of the current module. At least in
yarn link
since it keeps resolving to the direct path on my pc vs the one that goes through the node__modules folder.
Copy code
const root = scope.node.root as App
 const rootPath = root.appPath
I was hoping for something like this…
Copy code
const assets = path.join(__dirname, '../assets/social-cards')
    const rootPath = root.appPath
    const relative = path.relative(rootPath, assets)
    const handler = path.join(relative, 'index.handler')
but these are the paths…
Copy code
assets: '/Users/me/Projects/klick-packages/packages/cdk-constructs/assets/social-cards',
 rootPath: '/Users/me/Projects/idx',
 relative: '../klick-packages/packages/cdk-constructs/assets/social-cards',
 handler: '../klick-packages/packages/cdk-constructs/assets/social-cards/index.handler'
d
the assets one should be changed, but maybe yarn link works differently than npm link…though that would be surprising.
d
yarn link is just a symlink afaik
d
node generally doesnt follow those like a “301 redirect”, afaik Does it work when you are not linked (installed)?
d
not sure how to quickly test that
still needs to work in link or we can’t dev on things
OH! Actually, the above code DOES work. Even though its not following the symlink and going the realpath… it still works fine! https://serverless-stack.slack.com/archives/C01JG3B20RY/p1647529871518719?thread_ts=1647454228.283289&cid=C01JG3B20RY
Going to try rolling with this.
Just to keep the updates coming…. even though it passed TSLint and CFN, it is now erroring in the lambda function with ….
Copy code
2022-03-17T12:19:27.838-04:00

Copy
2022-03-17T16:19:27.835Z	undefined	ERROR	Uncaught Exception 	
{
    "errorType": "Runtime.MalformedHandlerName",
    "errorMessage": "'../klick-packages/packages/cdk-constructs/assets/social-cards/index.handler' is not a valid handler name. Use absolute paths when specifying root directories in handler names.",
    "stack": [
        "Runtime.MalformedHandlerName: '../klick-packages/packages/cdk-constructs/assets/social-cards/index.handler' is not a valid handler name. Use absolute paths when specifying root directories in handler names.",
        "    at _throwIfInvalidHandler (/var/runtime/UserFunction.js:211:11)",
        "    at Object.module.exports.load (/var/runtime/UserFunction.js:237:3)",
        "    at Object.<anonymous> (/var/runtime/index.js:43:30)",
        "    at Module._compile (internal/modules/cjs/loader.js:1085:14)",
        "    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1114:10)",
        "    at Module.load (internal/modules/cjs/loader.js:950:32)",
        "    at Function.Module._load (internal/modules/cjs/loader.js:790:12)",
        "    at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:76:12)",
        "    at internal/main/run_main_module.js:17:47"
    ]
}
I then tried to run
yarn start
and I get this in sst console…
Copy code
npm ERR! code EACCES
16:32:35.401
npm ERR! syscall mkdir
npm ERR! path /var/task
npm ERR! errno EACCES
16:32:35.402
npm ERR! FetchError: Invalid response body while trying to fetch <https://registry.yarnpkg.com/aws-lambda-ric>: EACCES: permission denied, mkdir '/var/task'
npm ERR!     at /Users/dvanbrunt/.nvm/versions/node/v16.13.1/lib/node_modules/npm/node_modules/minipass-fetch/lib/body.js:162:15
npm ERR!     at async /Users/dvanbrunt/.nvm/versions/node/v16.13.1/lib/node_modules/npm/node_modules/libnpmexec/lib/index.js:106:12
npm ERR!     at async Promise.all (index 0)
npm ERR!     at async exec (/Users/dvanbrunt/.nvm/versions/node/v16.13.1/lib/node_modules/npm/node_modules/libnpmexec/lib/index.js:94:17)
npm ERR!  FetchError: Invalid response body while trying to fetch <https://registry.yarnpkg.com/aws-lambda-ric>: EACCES: permission denied, mkdir '/var/task'
npm ERR!     at /Users/dvanbrunt/.nvm/versions/node/v16.13.1/lib/node_modules/npm/node_modules/minipass-fetch/lib/body.js:162:15
npm ERR!     at async /Users/dvanbrunt/.nvm/versions/node/v16.13.1/lib/node_modules/npm/node_modules/libnpmexec/lib/index.js:106:12
npm ERR!     at async Promise.all (index 0)
npm ERR!     at async exec (/Users/dvanbrunt/.nvm/versions/node/v16.13.1/lib/node_modules/npm/node_modules/libnpmexec/lib/index.js:94:17) {
npm ERR!   code: 'EACCES',
npm ERR!   errno: 'EACCES',
npm ERR!   syscall: 'mkdir',
npm ERR!   path: '/var/task',
npm ERR!   type: 'system'
npm ERR! }
npm
16:32:35.403
 ERR! 
npm ERR! The operation was rejected by your operating system.
npm ERR! It is likely you do not have the permissions to access this file as the current user
npm ERR! 
npm ERR! If you believe this might be a permissions issue, please double-check the
npm ERR! permissions of the file and its containing directories, or try running
npm ERR! the command again as root/Administrator.
ah…. that last is cause the function would be trying to create that dir on my local machine I guess.
So reverting back to
yarn deploy
same error… but I looked in the
.build
folder and I see this… it appears as though there is no function code being added…. just the
copyFiles
part with
templates
hmmmm…. and this can’t be right either…. the handler in Lambda console
Hardcoding the url to
node_modules/@klickmarketing/cdk-constructs/assets/social-cards/index.handler
• DOES bundle properly • DOES set proper lambda handler • DOES successfully run the lambda handler no issues So it just seems the issue is figuring out how to change how things can be passed into sst.Function.handler or find a way to safely derive the relative path to where the module folder is.
@Frank I think I’ve run with this as far as I can. Any ideas?
Ok, so I have a solve that works but for
yarn link
its a bit icky
Copy code
const root = this.node.root as App
    const { appPath } = root

    const srcPath = 'node_modules/@klickmarketing/cdk-constructs/dist'
    const dirname = path.relative(appPath, __dirname)
    const relativeDirname = this.isLinking ? srcPath : dirname
    const assets = path.join(relativeDirname, '../assets/social-cards')
    const handler = path.join(assets, 'index.handler')
The above works for “non-linked” (isLinking defaults to false) If you ARE linking…. you have to pass isLinking: true to the construct and it will use the hardcoded location of the assets. If you REALLY didn’t have a better option you could even allow for that hardcoded value to be passed in as well. IT WORKS… just not purty for linking
f
Got it! Thanks guys. @Dan Van Brunt I feel ur pain. I got myself into a similar situation when creating the migrator function for the
RDS
construct. Function code lives in
node_modules
and assets lives in user’s app.
We have some plans to clean up/standardize
Function
to make this easier.
Let me open an issue to track this - https://github.com/serverless-stack/serverless-stack/issues/1550. Currently a bit tied up with prepping for a major release bump. We will get to this after that.