npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@bolttech/templating-sdk

v0.2.7

Published

JavaScript SDK for Bolttech Templating Service - Create, manage and render templates with ease

Readme

Bolttech Templating SDK

A powerful JavaScript/TypeScript SDK for interacting with the Bolttech Templating Service. This SDK provides a simple and intuitive API for managing templates and rendering dynamic content with Handlebars syntax.

Features

  • 🚀 Simple API - Easy-to-use TemplatingSdk interface
  • 📝 Template Rendering - Built-in Handlebars template engine with helpers
  • 🔄 Immutable Updates - Update template properties safely with immutable operations
  • 🧠 Intelligent Change Detection - Smart tracking of modifications with reversion support
  • 💾 Auto-Save - Save template changes directly to the service
  • 🔒 TypeScript Support - Full TypeScript definitions included
  • 🌐 HTTP Client - Built on Axios with automatic error handling
  • 🎯 Workspace & Domain Support - Multi-workspace and domain filtering with flexible headers
  • Lightweight - Minimal dependencies, maximum performance
  • 🛡️ Error Handling - Comprehensive error types and handlers

Installation

npm install @bolttech/templating-sdk

Quick Start

import { TemplatingSdk } from '@bolttech/templating-sdk';

// Initialize the SDK with workspace and domain (optional)
const templateSdk = new TemplatingSdk('https://your-templating-service.com', {
  workspace: 'your-workspace-id',
  domain: 'example.com'
});

// Or without workspace/domain
const templateSdk = new TemplatingSdk('https://your-templating-service.com');

// Get a template by slug and render it
const template = await templateSdk.get('welcome-email');
const html = template.render({
  name: 'John Doe',
  email: '[email protected]',
});

console.log(html);

// Get by ObjectId
const templateById = await templateSdk.get('507f1f77bcf86cd799439011');

// Get with context and query filters
const filteredTemplate = await templateSdk.get('welcome-email', {
  context: { workspace: 'production' },
  query: { tags: { category: 'onboarding' } }
});

// Count templates in workspace
const totalTemplates = await templateSdk.count();
const prodTemplates = await templateSdk.count({ workspace: 'production' });

// Update and save template with intelligent change detection
let updatedTemplate = template
  .update({ title: 'Welcome Email v2' })
  .update({ content: '<h1>Welcome {{name}}!</h1>' });

console.log(updatedTemplate.isModified); // true

// Revert changes - intelligent detection knows it's back to original
updatedTemplate = updatedTemplate
  .update({ title: template.title })  // back to original
  .update({ content: template.content }); // back to original

console.log(updatedTemplate.isModified); // false - no real changes!

// Save only when there are actual changes
await updatedTemplate.save(); // Won't make API call since no changes

API Reference

TemplatingSdk

The main SDK class for interacting with the Templating Service.

Constructor

new TemplatingSdk(baseUrl: string, options?: Partial<TemplatingSdkConfig>)

Parameters:

  • baseUrl - The base URL of your templating service
  • options - Optional configuration:
    • workspace - Default workspace identifier (sent as x-workspace header)
    • domain - Default domain (sent as x-domain header)
    • timeout - Request timeout in milliseconds (default: 30000)
    • headers - Additional headers to include in requests
    • mode - SDK mode: SdkMode.INTEGRATION (default) or SdkMode.EDITOR for snapshot support

SDK Modes

The SDK supports two modes to optimize for different use cases:

Integration Mode (Default)

import { TemplatingSdk } from '@bolttech/templating-sdk';

// Default mode - optimized for backend integrations
const sdk = new TemplatingSdk('https://api.example.com');
// or explicitly
const sdk = new TemplatingSdk('https://api.example.com', {
  mode: 'integration'
});

const template = await sdk.get('my-template');
console.log(template.snapshot); // undefined - not loaded

Editor Mode

import { SdkMode, TemplatingSdk } from '@bolttech/templating-sdk';

// Editor mode - includes snapshot data automatically
const sdk = new TemplatingSdk('https://api.example.com', {
  mode: SdkMode.EDITOR, // or 'editor'
});

const template = await sdk.get('my-template');
console.log(template.snapshot); // loaded with EDITOR projection

// Edit snapshot
const updated = template.update({
  snapshot: { ...template.snapshot, newField: 'value' },
});
await updated.save(); // saves snapshot changes

Mode Comparison:

| Feature | Integration Mode | Editor Mode | | --------------- | ------------------------------- | ------------------------------------- | | Use Case | Backend integrations, rendering | Template editors, snapshot management | | Snapshot Loaded | ❌ No | ✅ Yes (automatic) | | Projection | FULL (default) | EDITOR (automatic) | | Performance | Faster (less data) | Slightly slower (more data) |

Methods

get(identifier: string, params?: GetTemplateParams): Promise<TemplateInstance>

Retrieve a template by ID (ObjectId) or slug with optional context and query filters.

Important: The SDK automatically detects whether the identifier is a MongoDB ObjectId (24 hex characters) or a slug, and routes to the appropriate endpoint:

  • ObjectId: GET /v1/templates/:id - Only supports workspace/domain headers
  • Slug: GET /v1/templates/slug/:slug - Supports query filters (format, tags) + workspace/domain headers
// Get by ObjectId (24 hex characters)
const template = await templateSdk.get('507f1f77bcf86cd799439011');
// → Routes to: GET /v1/templates/507f1f77bcf86cd799439011

// Get by slug
const template = await templateSdk.get('welcome-email');
// → Routes to: GET /v1/templates/slug/welcome-email

// Get by slug with query filters (only works with slug, not ObjectId)
const template = await templateSdk.get('welcome-email', {
  query: {
    format: TemplateFormat.HTML,
    tags: { category: 'email', type: 'welcome' }
  }
});

// Get with workspace and domain context (works with both ObjectId and slug)
const template = await templateSdk.get('welcome-email', {
  context: {
    workspace: 'my-workspace',
    domain: 'example.com'
  }
});

// Get by slug with both context and query
const template = await templateSdk.get('welcome-email', {
  context: {
    workspace: 'production',
    domain: 'example.com'
  },
  query: {
    tags: { category: 'email' }
  }
});

Params Structure (GetTemplateParams):

{
  context?: {
    workspace?: string;  // Sent as x-workspace header (both ObjectId & slug)
    domain?: string;     // Sent as x-domain header (both ObjectId & slug)
  },
  query?: {
    format?: TemplateFormat;  // Filter by format (slug only)
    tags?: Record<string, string>;  // Filter by tags (slug only, dot notation)
    [key: string]: unknown;  // Additional query params (slug only)
  },
  projection?: TEMPLATE_PROJECTION | string;  // 'FULL', 'PREVIEW', 'EDITOR' (case-insensitive)
}

Projection Options:

You can control which fields are returned from the API using projections:

import { TEMPLATE_PROJECTION } from '@bolttech/templating-sdk';

// Using enum (type-safe)
const template = await sdk.get('my-template', {
  projection: TEMPLATE_PROJECTION.EDITOR
});

// Using string (case-insensitive)
const template = await sdk.get('my-template', {
  projection: 'editor'  // or 'EDITOR'
});

// Available projections:
// - 'FULL' or 'full' - Complete template data (default in INTEGRATION mode)
// - 'PREVIEW' or 'preview' - Minimal data for listings
// - 'EDITOR' or 'editor' - Includes snapshot data (default in EDITOR mode)

Note:

  • Query filters (format, tags) are ignored when using ObjectId, as the ObjectId endpoint only supports workspace/domain filtering via headers.
  • Projection is automatically set to EDITOR when SDK mode is EDITOR, but can be overridden.
getAll(params?: QueryTemplatesParams, options?: RequestOptions): Promise<TemplateInstance[]>

Retrieve all templates with optional filtering, pagination, and sorting.

import { SortParam, TemplateFormat } from '@bolttech/templating-sdk';

// Get all templates
const templates = await templateSdk.getAll();

// With filters and pagination
const templates = await templateSdk.getAll({
  format: TemplateFormat.HTML,
  slug: 'welcome-email',
  tags: { category: 'email', type: 'welcome' }, // Tags with dot notation support
  page: 1,
  limit: 10,
  sort: SortParam.DESC,
  sortBy: 'createdAt',
  q: 'search term'
});

// With workspace and domain headers
const templates = await templateSdk.getAll({
  format: TemplateFormat.HTML
}, {
  workspace: 'production',
  domain: 'example.com'
});

Query Parameters:

  • format - Filter by template format (TemplateFormat enum)
  • slug - Filter by slug
  • tags - Filter by tags (automatically converted to dot notation: tags.key=value)
  • page - Page number for pagination
  • limit - Number of items per page (max 500)
  • q - Search query
  • sort - Sort order (SortParam.ASC or SortParam.DESC, default: DESC)
  • sortBy - Field to sort by (default: 'createdAt')
  • projection - Fields to include in response

Options:

  • workspace - Filter by workspace (sent as x-workspace header)
  • domain - Filter by domain (sent as x-domain header)
  • headers - Additional custom headers
count(options?: RequestOptions): Promise<number>

Get the total count of templates in a specific workspace/domain without retrieving the actual template data.

// Count all templates (uses default workspace/domain from SDK config)
const totalTemplates = await templateSdk.count();

// Count templates in specific workspace
const count = await templateSdk.count({
  workspace: 'production'
});

// Count templates in specific workspace and domain
const count = await templateSdk.count({
  workspace: 'production',
  domain: 'example.com'
});

Options:

  • workspace - Count templates in specific workspace (sent as x-workspace header)
  • domain - Count templates in specific domain (sent as x-domain header)
  • headers - Additional custom headers

Note: This method uses limit=1 internally for efficient counting and returns the total count from pagination metadata.

setWorkspace(workspace: string | null | undefined): void

Set or update the default workspace dynamically (sets x-workspace header for all requests).

// Set workspace
templateSdk.setWorkspace('new-workspace');

// Remove workspace
templateSdk.setWorkspace(undefined);
setDomain(domain: string | null | undefined): void

Set or update the default domain dynamically (sets x-domain header for all requests).

// Set domain
templateSdk.setDomain('example.com');

// Remove domain
templateSdk.setDomain(undefined);
create(templateData: CreateTemplateDto, options?: RequestOptions): Promise<TemplateInstance>

Create a new template.

import { TemplateFormat } from '@bolttech/templating-sdk';

const template = await templateSdk.create(
  {
    title: 'Welcome Email',
    format: TemplateFormat.HTML,
    content: '<h1>Welcome {{name}}!</h1>',
    tags: { category: 'email', type: 'welcome' }, // optional
  },
  {
    workspace: 'my-workspace', // sent as x-workspace header
    domain: 'example.com', // sent as x-domain header
  }
);

Note: workspace and domain are no longer part of the template body. They are sent as headers (x-workspace and x-domain) to set the context for the template.

update(identifier: string, templateData: UpdateTemplateDto, options?: RequestOptions): Promise<TemplateInstance>

Update an existing template.

// Update by slug or ID
const template = await templateSdk.update('welcome-email', {
  content: '<h1>Welcome {{name}}!</h1><p>Thanks for joining us.</p>',
});

// Update with workspace/domain filters
const template = await templateSdk.update('welcome-email', {
  title: 'New Title',
  tags: { version: '2.0' }
}, {
  workspace: 'production',
  domain: 'example.com'
});

Note: workspace and domain cannot be updated. They are used as filters to identify which template to update.

delete(identifier: string, options?: RequestOptions): Promise<boolean>

Delete a template.

// Delete by slug or ID
await templateSdk.delete('welcome-email');

// Delete with workspace/domain filters
await templateSdk.delete('welcome-email', {
  workspace: 'production',
  domain: 'example.com',
});
render(identifier: string, data: Record<string, any>): Promise<string>

Get a template and render it in one call.

const html = await templateSdk.render('welcome-email', {
  name: 'John Doe',
  email: '[email protected]',
});
healthCheck(): Promise<boolean>

Check if the templating service is healthy.

const isHealthy = await templateSdk.healthCheck();

TemplateInstance

Represents a template with rendering capabilities.

Properties

  • id - Template ID
  • slug - Template slug
  • title - Template title
  • format - Template format (TemplateFormat enum)
  • content - Template content
  • workspace - Workspace ID (string | null)
  • domain - Domain (string | null)
  • tags - Template tags (Record<string, string>)
  • createdAt - Creation date
  • updatedAt - Last update date
  • isModified - Boolean indicating if template has unsaved changes 🆕
  • snapshot - Template snapshot data (only available in EDITOR mode) 🆕

Methods

render(placeholders?: TemplatePlaceholders, options?: RenderOptions): string

Render the template with provided placeholders and options.

const html = template.render({
  name: 'John Doe',
  items: ['apple', 'banana', 'orange'],
});

// With options
const html = template.render(
  { name: 'John Doe' },
  { escapeHtml: false }
);
update(updates: TemplateUpdateData): Template 🆕

Create a new Template instance with updated properties (immutable operation) with intelligent change detection.

// Update multiple properties
const updatedTemplate = template.update({
  title: 'New Title',
  content: '<h1>Updated content</h1>',
  slug: 'new-slug',
  workspace: 'new-workspace',
  domain: 'newdomain.com',
  tags: { updated: 'true' },
});

console.log(updatedTemplate.isModified); // true

// Intelligent change detection - reverting to original values
const revertedTemplate = updatedTemplate.update({
  title: template.title, // back to original
  content: template.content, // back to original
});

console.log(revertedTemplate.isModified); // false - no real changes from original!

// No changes = same values as original
const sameTemplate = template.update({ title: template.title });
console.log(sameTemplate.isModified); // false

Key Features:

  • Intelligent Change Detection - Only marks isModified=true when actual changes exist from the original template
  • Reversion Support - Changing values back to original automatically sets isModified=false
  • Efficient Updates - updatedAt timestamp only changes when real modifications occur
  • Validation on Save - Data validation happens during save(), not update()
save(): Promise<Template> 🆕

Save the current template state to the service with intelligent optimizations.

const template = await sdk.get('my-template');
const updatedTemplate = template.update({ title: 'New Title' });

console.log(updatedTemplate.isModified); // true

// Save changes to service (performs validation here)
const savedTemplate = await updatedTemplate.save();
console.log(savedTemplate.isModified); // false - saved successfully

// Optimization: No API call if no changes
const unchanged = template.update({ title: template.title });
console.log(unchanged.isModified); // false
await unchanged.save(); // Returns immediately, no API call made

Key Features:

  • Smart Saving - Only makes API calls when isModified=true
  • Validation on Save - Data validation happens here, not during update()
  • Error Handling - Maintains isModified=true if save fails
  • State Reset - Sets isModified=false after successful save

Note: The save() method only works on templates created through the TemplatingSdk.

getSnapshot(): Promise<any> 🆕

Get the template snapshot data (requires EDITOR mode).

import { SdkMode, TemplatingSdk } from '@bolttech/templating-sdk';

// Must use EDITOR mode
const sdk = new TemplatingSdk('https://api.example.com', {
  mode: SdkMode.EDITOR,
});

const template = await sdk.get('my-template');

// Snapshot is automatically loaded in EDITOR mode
console.log(template.snapshot);

// Or fetch it explicitly (makes API call)
const snapshot = await template.getSnapshot();

// Edit snapshot
const updated = template.update({
  snapshot: { ...snapshot, modified: true },
});

await updated.save(); // saves snapshot changes

Important:

  • Only works when SDK is initialized with mode: SdkMode.EDITOR
  • Throws an error if called in INTEGRATION mode
  • Fetches fresh snapshot data from API with EDITOR projection

Chaining Operations & Change Tracking 🆕

You can chain multiple operations with intelligent change tracking:

// Complex change sequence with intelligent tracking
let currentTemplate = template;

currentTemplate = currentTemplate.update({ title: 'Changed Title' });
console.log(currentTemplate.isModified); // true

currentTemplate = currentTemplate.update({ content: 'New content' });
console.log(currentTemplate.isModified); // true

// Revert title back to original
currentTemplate = currentTemplate.update({ title: template.title });
console.log(currentTemplate.isModified); // true (still modified due to content)

// Revert content back to original too
currentTemplate = currentTemplate.update({ content: template.content });
console.log(currentTemplate.isModified); // false (all changes reverted!)

// Chain operations and save
const finalTemplate = await template
  .update({ title: 'New Title' })
  .update({ content: '<h1>Updated</h1>' })
  .save(); // Only saves if there are actual changes

Intelligent Change Detection System 🧠

The SDK features an advanced change detection system that tracks modifications against the original template state, providing smart behavior for common editing scenarios.

How It Works

const template = await templateSdk.get('my-template');
console.log(template.title); // "Original Title"
console.log(template.isModified); // false

// Make a change
const changed = template.update({ title: 'New Title' });
console.log(changed.isModified); // true
console.log(changed.updatedAt > template.updatedAt); // true

// Revert back to original
const reverted = changed.update({ title: 'Original Title' });
console.log(reverted.isModified); // false - smart detection!
console.log(reverted.updatedAt === changed.updatedAt); // true - no new timestamp

Key Benefits

  • 🎯 Accurate State Tracking - isModified only reflects actual changes from the original template
  • ⚡ Performance Optimization - save() skips API calls when no real changes exist
  • 🔄 Reversion Detection - Automatically detects when changes are reverted to original values
  • ⏰ Smart Timestamps - updatedAt only changes when real modifications occur
  • ✅ Clean UX - Perfect for form interfaces where users might make and undo changes

Real-World Example

// User starts editing a template in a form
const template = await templateSdk.get('email-template');

// User types new title
let current = template.update({ title: 'Draft Title' });
console.log(current.isModified); // true - show "Save" button

// User changes their mind and reverts
current = current.update({ title: template.title });
console.log(current.isModified); // false - hide "Save" button

// User makes multiple changes
current = current
  .update({ title: 'New Title' })
  .update({ content: 'New content' })
  .update({ title: template.title }); // revert title

console.log(current.isModified); // true - content still changed

// Save only when needed
if (current.isModified) {
  await current.save(); // API call made
} else {
  console.log('No changes to save'); // API call skipped
}

Template Syntax

This SDK supports template syntax for creating dynamic content using standard placeholder replacement.

Error Handling

The SDK provides comprehensive error handling with specific error types:

import {
  AuthenticationError,
  NetworkError,
  TemplateNotFoundError,
  TemplateValidationError,
  TemplatingError,
  TemplatingSdk,
} from '@bolttech/templating-sdk';

try {
  const template = await templateSdk.get('nonexistent-template');
} catch (error) {
  if (error instanceof TemplateNotFoundError) {
    console.log('Template not found:', error.message);
  } else if (error instanceof AuthenticationError) {
    console.log('Authentication failed:', error.message);
  } else if (error instanceof TemplateValidationError) {
    console.log('Validation error:', error.message);
  } else if (error instanceof NetworkError) {
    console.log('Network error:', error.message);
  } else if (error instanceof TemplatingError) {
    console.log('API Error:', error.message, 'Status:', error.statusCode);
  }
}

Available Error Types

  • TemplatingError - Base error for all templating operations
  • TemplateNotFoundError - Template not found (404)
  • TemplateValidationError - Invalid template data (400, 422)
  • AuthenticationError - Authentication failed (401)
  • AuthorizationError - Access denied (403)
  • NetworkError - Network/connection issues
  • TimeoutError - Request timeout
  • TemplateRenderError - Template rendering failed
  • UnsupportedFormatError - Unsupported template format

TypeScript Types

The SDK exports the following TypeScript types for better development experience:

import type {
  CreateTemplateDto,
  EnumValue,
  GetTemplateContext,
  GetTemplateParams,
  GetTemplateQuery,
  QueryTemplatesParams,
  RenderOptions,
  RequestOptions,
  TemplateDto,
  TemplatePlaceholders,
  TemplatingSdkConfig,
  UpdateTemplateDto,
} from '@bolttech/templating-sdk';
import { SdkMode, SortParam, TEMPLATE_PROJECTION, TemplateFormat } from '@bolttech/templating-sdk';

// Template data structure
const template: TemplateDto = {
  _id: '507f1f77bcf86cd799439011',
  slug: 'welcome-email',
  title: 'Welcome Email',
  format: TemplateFormat.HTML,
  content: '<h1>Hello {{name}}!</h1>',
  workspace: 'my-workspace',
  domain: null,
  tags: { category: 'email' },
  createdAt: '2023-01-01T00:00:00.000Z',
  updatedAt: '2023-01-01T00:00:00.000Z',
};

// Template placeholders
const placeholders: TemplatePlaceholders = {
  name: 'John Doe',
  email: '[email protected]',
  items: ['apple', 'banana'],
};

// Render options
const renderOptions: RenderOptions = {
  escapeHtml: true,
  allowUnsafe: false,
  strict: true,
};

// Request options (for workspace/domain filtering)
const requestOptions: RequestOptions = {
  workspace: 'production',
  domain: 'example.com',
  headers: { 'Custom-Header': 'value' },
};

// Get template params (for get method)
const getParams: GetTemplateParams = {
  context: {
    workspace: 'production',
    domain: 'example.com',
  },
  query: {
    format: TemplateFormat.HTML,
    tags: { category: 'email' },
  },
};

// Get template context (workspace/domain only)
const context: GetTemplateContext = {
  workspace: 'production',
  domain: 'example.com',
};

// Get template query (filters only)
const query: GetTemplateQuery = {
  format: TemplateFormat.HTML,
  tags: { category: 'email', type: 'welcome' },
};

Configuration

You can update the configuration after initialization:

// Update general config
templateSdk.updateConfig({
  timeout: 60000,
  workspace: 'new-workspace',
  domain: 'example.com',
  headers: {
    'Custom-Header': 'value',
  },
});

// Or update workspace/domain specifically
templateSdk.setWorkspace('new-workspace');
templateSdk.setDomain('example.com');

Supported Template Formats

The SDK currently supports:

  • html - HTML templates with Handlebars syntax (default)
  • text - Plain text templates (raw content)

Basic Email Template

import { TemplateFormat } from '@bolttech/templating-sdk';

const emailTemplate = await templateSdk.create({
  title: 'Welcome Email',
  format: TemplateFormat.HTML,
  content: `
    <h1>Welcome {{user.name}}!</h1>
    <p>Thank you for joining {{company.name}}.</p>
    <p>Your account is now active.</p>
  `,
});

const html = emailTemplate.render({
  user: {
    name: 'John Doe',
  },
  company: {
    name: 'Acme Corp',
  },
});

Invoice Template

import { TemplateFormat } from '@bolttech/templating-sdk';

const invoiceTemplate = await templateSdk.create({
  title: 'Invoice Template',
  format: TemplateFormat.HTML,
  content: `
    <div class="invoice">
      <h1>Invoice #{{invoice.number}}</h1>
      <p>Date: {{invoice.date}}</p>

      <div class="customer">
        <h2>Bill To:</h2>
        <p>{{customer.name}}</p>
        <p>{{customer.address}}</p>
      </div>

      <table>
        <thead>
          <tr>
            <th>Item</th>
            <th>Quantity</th>
            <th>Price</th>
            <th>Total</th>
          </tr>
        </thead>
        <tbody>
          <tr>
            <td>{{item.description}}</td>
            <td>{{item.quantity}}</td>
            <td>${{item.price}}</td>
            <td>${{item.total}}</td>
          </tr>
        </tbody>
      </table>

      <div class="total">
        <strong>Total: ${{invoice.total}}</strong>
      </div>
    </div>
  `
});

Contributing

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add some amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

License

This project is licensed under the MIT License - see the LICENSE file for details.

Support

For support, please contact [email protected].