Milestone 0: import zappier billing, Verae middleware, and Zapier research

Compose-ready workspace: packages/zappier (rate card, portal, Stripe),
packages/verae-zapier-middleware (timestamp + NATS), packages/verae-zapier
(CLI app), vendor/zapier-platform, and research/zapier vendor corpus.

Gate 0 structure checks pass. Product code and research are not yet wired.
This commit is contained in:
George Lambert 2026-09-09 02:37:36 -04:00
commit b4150c8250
1364 changed files with 6814366 additions and 0 deletions

View file

@ -0,0 +1,537 @@
const os = require('node:os');
const path = require('node:path');
const { promisify } = require('node:util');
const fs = require('fs-extra');
const { captureOutput } = require('@oclif/test');
require('should');
const { getPackageLatestVersion, getPackageSize } = require('../utils/npm');
const { ensureDir, makeTempDir } = require('../utils/files');
const { runCommand, npmPackCore, IS_WINDOWS } = require('../tests/_helpers');
const { PLATFORM_PACKAGE } = require('../constants');
const REGEX_VERSION = /\d+\.\d+\.\d+/;
const sleep = promisify(setTimeout);
const setupZapierRC = () => {
let hasRC = false;
const rcPath = path.join(os.homedir(), '.zapierrc');
if (fs.existsSync(rcPath)) {
hasRC = true;
} else if (process.env.ZAPIER_DEPLOY_KEY) {
fs.writeFileSync(
rcPath,
JSON.stringify({ deployKey: process.env.ZAPIER_DEPLOY_KEY }),
);
hasRC = true;
}
return hasRC;
};
const npmPackCLI = () => {
let filename;
const lines = runCommand('npm', ['pack']).split('\n');
for (let i = lines.length - 1; i >= 0; i--) {
const line = lines[i].trim();
if (line) {
filename = line;
break;
}
}
return filename;
};
const npmInstall = (packagePath, workdir) => {
runCommand(
'npm',
['install', '--production', '--ignore-scripts', packagePath],
{
cwd: workdir,
},
);
};
const yarnInstall = (coreZipPath, workdir) => {
// When releasing a new core version, example apps would have bumped their
// core before the new core version is published to npm. So we need to patch
// example app's package.json to use local core temporarily to avoid `npm
// install` error.
const packageJsonPath = path.join(workdir, 'package.json');
const origPackageJsonText = fs.readFileSync(packageJsonPath);
const packageJson = JSON.parse(origPackageJsonText);
packageJson.dependencies[PLATFORM_PACKAGE] = `file:${coreZipPath}`;
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson));
try {
runCommand('yarn', ['--ignore-scripts'], { cwd: workdir });
} finally {
fs.writeFileSync(packageJsonPath, origPackageJsonText);
}
};
describe('smoke tests - setup will take some time', function () {
this.retries(3);
const context = {
// Global context that will be available for all test cases in this test suite
package: {
filename: null,
version: null,
path: null,
},
workdir: null,
cliBin: null,
hasRC: false,
};
before(() => {
context.hasRC = setupZapierRC();
context.package.filename = npmPackCLI();
context.package.version = context.package.filename.match(REGEX_VERSION)[0];
context.package.path = path.join(process.cwd(), context.package.filename);
context.workdir = makeTempDir();
npmInstall(context.package.path, context.workdir);
context.cliBin = path.join(
context.workdir,
'node_modules',
'.bin',
'zapier-platform',
);
});
after(() => {
fs.unlinkSync(context.package.path);
fs.removeSync(context.workdir);
});
beforeEach(async function () {
// delay after first failure
const currentRetry = this.currentTest.currentRetry();
if (currentRetry > 0) {
await sleep(Math.pow(2, currentRetry) * 1000);
}
});
afterEach(function () {
const currentRetry = this.currentTest.currentRetry();
if (currentRetry >= 0) {
this.currentTest.title += ` (${currentRetry} retries)`;
}
});
it('package size should not change much', async function () {
const packageName = 'zapier-platform-cli';
const latestVersion = await getPackageLatestVersion(packageName);
const baselineSize = await getPackageSize(packageName, latestVersion);
const newSize = fs.statSync(context.package.path).size;
newSize.should.be.within(baselineSize * 0.7, baselineSize * 1.3);
this.test.title += ` (${baselineSize} -> ${newSize} bytes)`;
});
it('cli executable should exist', () => {
fs.existsSync(context.cliBin).should.be.true();
});
it('should define only one executable entry', () => {
const packageName = 'zapier-platform-cli';
const packageJsonPath = path.join(
context.workdir,
'node_modules',
packageName,
'package.json',
);
const packageJson = JSON.parse(
fs.readFileSync(packageJsonPath, { encoding: 'utf8' }),
);
// packageJson.bin can define multiple executable names, but for
// `npx zapier-platform-cli <args>` to work, all names must point to the
// same file.
const executables = new Set(Object.values(packageJson.bin));
executables.size.should.eql(1);
});
it('zapier-platform --version', () => {
const versionOutput = runCommand(context.cliBin, ['--version']);
versionOutput
.includes(`CLI version: ${context.package.version}`)
.should.be.true();
});
it('zapier-platform init -t minimal -m commonjs', () => {
runCommand(
context.cliBin,
['init', 'awesome-app', '-t', 'minimal', '-m', 'commonjs'],
{
cwd: context.workdir,
},
);
const newAppDir = path.join(context.workdir, 'awesome-app');
fs.existsSync(newAppDir).should.be.true();
const appIndexJs = path.join(newAppDir, 'index.js');
const appPackageJson = path.join(newAppDir, 'package.json');
fs.existsSync(appIndexJs).should.be.true();
fs.existsSync(appPackageJson).should.be.true();
});
it('zapier-platform init -t minimal -m esm', () => {
runCommand(
context.cliBin,
['init', 'awesome-esm-app', '-t', 'minimal', '-m', 'esm'],
{
cwd: context.workdir,
},
);
const newAppDir = path.join(context.workdir, 'awesome-esm-app');
fs.existsSync(newAppDir).should.be.true();
const appIndexJs = path.join(newAppDir, 'index.js');
const appPackageJson = path.join(newAppDir, 'package.json');
fs.existsSync(appIndexJs).should.be.true();
fs.existsSync(appPackageJson).should.be.true();
const pkg = JSON.parse(
fs.readFileSync(appPackageJson, { encoding: 'utf8' }),
);
pkg.name.should.containEql('awesome-esm-app');
pkg.type.should.containEql('module');
pkg.exports.should.eql('./index.js');
});
it('should error with a mismatched module type', async () => {
await captureOutput(async function () {
const { error } = await runCommand(context.cliBin, [
'init',
'error-app',
'-t',
'basic-auth',
'-m',
'esm',
]);
error.message.should.containEql(`ESM is not supported for this template`);
});
});
it('zapier-platform scaffold trigger neat (JS)', () => {
runCommand(
context.cliBin,
['init', 'scaffold-town', '-t', 'minimal', '-m', 'commonjs'],
{
cwd: context.workdir,
},
);
const newAppDir = path.join(context.workdir, 'scaffold-town');
fs.existsSync(newAppDir).should.be.true();
// npm install was already run, so it'll run the validaion function
runCommand(context.cliBin, ['scaffold', 'trigger', 'neat'], {
cwd: newAppDir,
});
const appIndexJs = path.join(newAppDir, 'index.js');
const appPackageJson = path.join(newAppDir, 'package.json');
fs.existsSync(appIndexJs).should.be.true();
fs.existsSync(appPackageJson).should.be.true();
const newTrigger = path.join(newAppDir, 'triggers', 'neat.js');
fs.existsSync(newTrigger).should.be.true();
const newTriggerTest = path.join(
newAppDir,
'test',
'triggers',
'neat.test.js',
);
fs.existsSync(newTriggerTest).should.be.true();
const pkg = JSON.parse(
fs.readFileSync(appPackageJson, { encoding: 'utf8' }),
);
pkg.name.should.containEql('scaffold-town');
});
it('zapier-platform scaffold trigger neat (TS)', () => {
runCommand(
context.cliBin,
['init', 'scaffold-town-ts', '-t', 'oauth2', '-l', 'typescript'],
{ cwd: context.workdir },
);
const newAppDir = path.join(context.workdir, 'scaffold-town-ts');
fs.existsSync(newAppDir).should.be.true();
runCommand(context.cliBin, ['scaffold', 'trigger', 'neat'], {
cwd: newAppDir,
});
const appIndexTs = path.join(newAppDir, 'src', 'index.ts');
fs.existsSync(appIndexTs).should.be.true();
const appPackageJson = path.join(newAppDir, 'package.json');
fs.existsSync(appPackageJson).should.be.true();
const triggerPath = path.join(newAppDir, 'src', 'triggers', 'neat.ts');
fs.existsSync(triggerPath).should.be.true();
const newTriggerTest = path.join(
newAppDir,
'src',
'test',
'triggers',
'neat.test.ts',
);
fs.existsSync(newTriggerTest).should.be.true();
const pkg = JSON.parse(
fs.readFileSync(appPackageJson, { encoding: 'utf8' }),
);
pkg.name.should.containEql('scaffold-town');
});
it('zapier-platform integrations', function () {
if (!context.hasRC) {
this.skip();
return;
}
const stdout = runCommand(context.cliBin, [
'integrations',
'--format=json',
]);
const result = JSON.parse(stdout);
result.should.be.Array();
});
describe('zapier-platform init w/ templates (runs very slowly)', () => {
const testableTemplates = [
{ name: 'basic-auth' },
{ name: 'custom-auth' },
{ name: 'digest-auth' },
{ name: 'oauth2' },
{ name: 'session-auth' },
{ name: 'dynamic-dropdown' },
{ name: 'files' },
{ name: 'line-items' },
{ name: 'minimal', module: 'commonjs' },
{ name: 'minimal', module: 'esm' },
{ name: 'search-or-create' },
{ name: 'oauth2', language: 'typescript' },
];
const invokeCommandsByTemplate = {
// Add an entry here if you want to test a template with an invoke
// command. The object key is the template name, and the value is an array
// of objects. Each object represents an invoke command and a "verify"
// function. The commands will be run sequentially so later ones will be
// influenced by the earlier ones. Each object consists of:
// - files (optional): files to write before running the command
// - command: command args to pass to spawnSync()
// - options (optional): options to pass to spawnSync()
// - verify: a function that takes the output and asserts something
oauth2: [
{
files: [
{
name: '.env',
content:
'CLIENT_ID=1234\n' +
'CLIENT_SECRET=asdf\n' +
'authData_access_token=a_token\n' +
'authData_refresh_token=a_refresh_token\n',
},
],
command: ['invoke', 'auth', 'test', '--non-interactive'],
verify: (output, appDir) => {
const testResult = JSON.parse(output);
testResult.json.username.should.eql('Bret');
testResult.json.email.should.eql('Sincere@april.biz');
},
},
{
command: ['invoke', 'auth', 'refresh', '--non-interactive'],
verify: (output, appDir) => {
const envPath = path.join(appDir, '.env');
const envContent = fs.readFileSync(envPath, { encoding: 'utf8' });
// The refresh command should append a new access token at the end
envContent.should.eql(
'CLIENT_ID=1234\n' +
'CLIENT_SECRET=asdf\n' +
'authData_access_token=a_token\n' +
'authData_refresh_token=a_refresh_token\n' +
"authData_access_token='a_token'\n",
);
},
},
],
'dynamic-dropdown': [
{
command: [
'invoke',
'trigger',
'people',
'-i',
// Windows's cmd.exe somehow requires you to escape quotes in JSON
IS_WINDOWS ? '{\\"species_id\\":\\"1\\"}' : '{"species_id":"1"}',
'--non-interactive',
],
verify: (output, appDir) => {
const people = JSON.parse(output);
const firstPerson = people[0];
firstPerson.id.should.eql(1);
firstPerson.name.should.eql('Luke Skywalker');
},
},
],
'line-items': [
{
files: [
{
name: 'order.json',
content:
'{"name":"Test Order","line_items":[{"product_name":"Pens","quantity":"12","price":"1.50"},{"product_name":"Notebooks","quantity":"3","price":"8.99"}]}',
},
],
command: [
'invoke',
'create',
'order',
'--inputData',
'@order.json',
'--non-interactive',
],
verify: (output) => {
const result = JSON.parse(output);
const body = JSON.parse(result.data);
body.name.should.eql('Test Order');
body.line_items.should.have.length(2);
body.line_items[0].product_name.should.eql('Pens');
body.line_items[0].quantity.should.eql(12);
body.line_items[0].price.should.eql(1.5);
body.line_items[1].product_name.should.eql('Notebooks');
body.line_items[1].quantity.should.eql(3);
},
},
],
'search-or-create': [
{
files: [
{
name: 'recipe.json',
content: '{"name":"name 3"}',
},
],
command: [
'invoke',
'search',
'recipe',
'--inputData',
'@recipe.json',
'--non-interactive',
],
verify: (output, appDir) => {
const recipes = JSON.parse(output);
recipes.length.should.eql(1);
recipes[0].name.should.eql('name 3');
},
},
{
command: [
'invoke',
'create',
'recipe',
'--inputData',
'@-', // read from stdin
],
options: {
input: '{"name":"Pizza"}',
},
verify: (output, appDir) => {
const recipe = JSON.parse(output);
recipe.name.should.eql('Pizza');
},
},
],
};
const subfolder = 'template-tests';
let subfolderPath, corePackage;
before(async () => {
subfolderPath = path.join(context.workdir, subfolder);
await ensureDir(subfolderPath);
corePackage = await npmPackCore();
});
after(() => {
corePackage.cleanup();
});
testableTemplates.forEach((template) => {
let testName, appDirName;
if (template.module || template.language) {
testName = `${template.name} (${template.module || template.language})`;
appDirName = `${template.name}-${template.module || template.language}`;
} else {
testName = template.name;
appDirName = template.name;
}
it(`${testName} should test out of the box`, function () {
this.retries(3); // retry up to 3 times
const args = ['init', appDirName, '-t', template.name];
if (template.module) {
args.push('-m', template.module);
}
runCommand(context.cliBin, args, {
cwd: subfolderPath,
input: 'a', // tells `yo` to replace the auth file
});
const appDir = path.join(subfolderPath, appDirName);
yarnInstall(corePackage.path, appDir);
// should not throw an error
runCommand(context.cliBin, ['test', '--skip-validate'], {
cwd: appDir,
});
const invokeCommands = invokeCommandsByTemplate[template.name];
if (!invokeCommands) {
return;
}
const envPath = path.join(appDir, '.env');
fs.existsSync(envPath) && fs.unlinkSync(envPath);
// Test with invoke command
for (const { files, command, options, verify } of invokeCommands) {
if (files && files.length) {
for (const { name, content } of files) {
const filePath = path.join(appDir, name);
fs.writeFileSync(filePath, content);
}
}
const output = runCommand(context.cliBin, command, {
cwd: appDir,
...options,
});
verify(output, appDir);
}
});
});
});
});