Hung Hoang
Hung Hoang

Reputation: 717

Deploy Nodejs app with local dependencies to Google App Engine

How can I deploy NodeJS app with local dependencies to GAE???

My app has local dependencies in package.json, so it failed.

Thanks!

Upvotes: 2

Views: 547

Answers (2)

typically
typically

Reputation: 3520

Thanks to @Hung Hoang for the question and their answer. Their answer solved my issue as well. I was motivated to write a solution that achieves the same result (and a few improvements) without using Gulp.

The following code assumes that you have a local package located at ../local, and that the current working directory contains the Node.js application and the package.json to be deployed to App Engine.

The overall idea is to copy the contents of ../local to the Node.js app directory before deploying. This way, the local package code will be included as part of the code uploaded as part of the deploy, and the deploy will not result in an error. But, additionally, for this to work correctly the package.json dependency entry for the ../local package also needs to be updated.

There are three steps to the process (defined in Makefile format).

deploy: _predeploy _deploy _postdeploy

The pre-deploy step prepares files for deployment. See inline comments for details.

_predeploy:
    # copy package to current directory
    cp -r ../local tmp-local
    # preserve original package.json,package-lock.json
    cp package.json package.json.original
    cp package-lock.json package-lock.json.original
    # rewrite local dependency value in package.json (../local -> ./tmp-local)
    sed -i '' 's/file:\.\.\/local/file:.\/tmp-local/g' package.json
    # update package-lock.json corresondingly
    npm i --package-lock-only

The deploy step does the actual deploy using the gcloud command.

_deploy:
    gcloud app deploy

Finally the post-deploy step cleans up files created during the pre-deploy step, and restores the original state of package.json (such that ../local can be used as usual for local development).

_postdeploy:
    # undo package.json,package-lock.json changes
    mv package.json.original package.json
    mv package-lock.json.original package-lock.json
    # remove copied package
    rm -rf tmp-local

To run, execute make deploy.

Upvotes: 1

Hung Hoang
Hung Hoang

Reputation: 717

No help came so I did it myself. Here's the solution for everyone has the same problem. Use gulp to copy local resources into current directory.

const gulp = require('gulp');
const merge = require('merge-stream');
const runSequence = require('run-sequence');
const del = require('del');
const fs = require('fs');
const resolve = require('path').resolve;

let getPackageGlobs = (dir) => {
  let paths = [
    dir + '/**',
    '!' + dir + '/node_modules/**',
    '!' + dir + '/npm-debug.log',
    '!' + dir + '/build',
  ];

  try {
    let data = fs.readFileSync(dir + '/.npmignore', {
      encoding: 'utf-8',
    });

    paths = paths.concat(data.split("\n")
      .filter((e) => e.length > 0)
      .map((e) => dir + '/' + e)
      .filter(fs.existsSync)
      .map((e) => fs.lstatSync(e).isDirectory() ? '!' + e + '/**' : '!' + e));
  } catch (err) { }

  return paths;
};

gulp.task('build.clean', () => {
  return del(__dirname + '/build');
});

gulp.task('build.copy', () => {
  return gulp.src(getPackageGlobs(__dirname))
    .pipe(gulp.dest('build'));
});

gulp.task('build.normalize', () => {
  let packageJson = require('./build/package.json');
  let tasks = [];

  for (let name in packageJson.dependencies) {
    for (let s of ['../', '~/', './', '/']) {
      if (packageJson.dependencies[name].startsWith(s)) {
        tasks.push(gulp
        .src(getPackageGlobs(resolve(packageJson.dependencies[name])))
        .pipe(gulp.dest('./build/local_modules/' + name)));

        packageJson.dependencies[name] = './local_modules' + '/' + name;

        break;
      }
    }
  }

  return new Promise((resolve, reject) => {
    fs.writeFile('./build/package.json',
      JSON.stringify(packageJson), (err) => {
        if (err) {
          reject(err);
        } else {
          resolve(merge(tasks));
        }
      });
  });
});

gulp.task('build', (done) => {
  runSequence('build.clean',
    'build.copy',
    'build.normalize',
    done);
});

And run with gulp build && gcloud app deploy build/app.yaml

Upvotes: 2

Related Questions