Hi all, first time user evaluating pulumi to use f...
# typescript
Hi all, first time user evaluating pulumi to use for our infrastructure. I'm facing issues testing out my current setup with
pulumi preview
and the typescript compilation. I receive this error:
Copy code
TypeError [ERR_UNKNOWN_FILE_EXTENSION]: Unknown file extension ".ts"
I'm pretty sure this is due to pulumi trying to execute the index file directly without transpiling it with ts. I'm using a monorepo and different configs for different accounts, like so:
Copy code
and run
pulumi preview
as a first test.. but I can't get it working. I can't find any resources on how pulumi handles typescript under the hood to understand how this works. EDIT: Seems like pulumi relies on the
entry point to read the IaC? Is the best way here to defer the transpilation to something else of my choosing, creating a template package.json and using that? EDIT2: Whatever I tried didn't work with the way pulumi resolves the typescript project under the hood. I had a go at transpiling the typescript outside of pulumi and then running the CLI against the transpiled code. This isn't really pretty.. but it seems to be working so far. I'll leave the example bundler config for anyone who might look into doing this. Post compilation looks something like
cd dist/accounts/account1 && pulumi preview
I really hope there's a better way of doing this - I don't want to think about transpiling the code for use with pulumi - i'd expect the pulumi cli to do this automatically. Can I suggest maybe looking into
or using a similar transpilation step under the hood?
Copy code
// rollup.config.js

import { globSync } from 'glob';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import fs from 'node:fs';
import typescript from '@rollup/plugin-typescript';
import { nodeResolve } from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import json from '@rollup/plugin-json';
import copy from 'rollup-plugin-copy';

const outputDir = 'dist';

/** @type {import('rollup').RollupOptions} */
export default {
  // We want to ensure the preservation of the directory structure.
  // This example is taken from <https://rollupjs.org/configuration-options/#input>
  input: Object.fromEntries(
    globSync('**/*.ts').map((file) => [
      // This remove `src/` as well as the file extension from each
      // file, so e.g. src/nested/foo.js becomes nested/foo
      file.slice(0, file.length - path.extname(file).length),
      // This expands the relative paths to absolute paths, so e.g.
      // src/nested/foo becomes /project/src/nested/foo.js
      fileURLToPath(new URL(file, import.meta.url)),
  output: {
    format: 'cjs',
    dir: outputDir,
  // This will probably require modification once we start importing
  // libraries from within the monorepo since pnpm symlinks packages
  // and packages will be resolved as raw .ts and expected to be
  // transpiled as part of this process.
  external: /node_modules/,
  plugins: [
    nodeResolve({ preferBuiltins: true }),
    // Copy over the Pulumi configuration files as the cli 
    // will be executed against the transpiled code.
    // NOTE: This targets only accounts/ dir, otherwise node_modules 
    // would be resolved.
    // Other projects might want to include files under src/
      targets: [
          src: ['accounts/**/Pulumi(.*)?.yaml'],
          dest: outputDir,
          rename: (_name, _extension, fullPath) => {
            return path.join('..', fullPath);
      flatten: false,

 * The repository is a ESM project, but pulumi does not support ESM out of the box.
 * This forces the nodejs process to recognize the output as commonjs.
function stubPackageJson() {
  return {
    name: 'stub-package-json-commonjs',
    writeBundle(options) {
      const filePath = path.join(
        options.dir || path.dirname(options.file),
      fs.writeFileSync(filePath, `{ "type": "commonjs" }`);
      console.log(`File written: ${filePath}`);
I faced the same issue with the unknown extension but I managed to get it working without pre-compiling using ts-node and
pulumi up
from the CLI. However, I ultimately ripped out ts-node in favor of pre-compiling with SWC for performance reasons. For a simple 3 file stack, tsc was taking around 4 seconds while swc can do the same in 124ms (without typechecking). Pulumi itself is already pretty slow, so cutting that time out of every
pulumi up
is useful for faster iteration. I also recommend using the automation API, which is like Puppeteer for the pulumi CLI. I can post a boilerplate if you'd like. I am compiling to ESM, not commonjs and the pulumi runtime is happy with it. There is also no need to cd into the dist directory, as the pulumi cli looks at the main field in package.json to determine its entrypoint