New Research: Supply Chain Attack on Axios Pulls Malicious Dependency from npm.Details →
Socket
Book a DemoSign in
Socket

semantq

Package Overview
Dependencies
Maintainers
1
Versions
60
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

semantq

Semantq is a fullstack JavaScript framework for writing intuitive reactive web apps as well IoT firmware and applications.

latest
npmnpm
Version
1.4.6
Version published
Maintainers
1
Created
Source

Semantq

NPM NPM Downloads GitHub Stars GitHub Issues

semantq uses the MIT license

GitHub Workflow

Introduction

Semantq is a lightweight JavaScript framework designed to simplify modern web development. Its declarative syntax, built-in state management, reactive logic blocks, and seamless full-stack integration make it ideal for both frontend and full-stack projects.

Semantq Developer Guide

Table of Contents

Semantq Developer Guide

Key goals

  • Declarative HTML with reactive features
  • Reusable components and layouts
  • Simplified routing and navigation
  • Built-in state and data management
  • Full-stack plug-and-play with SemantqQL

Installation & Setup

Install Semantq via npm:

npm install semantq

OR install globally to use it anywhere in your system

npm install -g semantq

Run your development server:

npm run dev

Now you can view your app on the browser on the given address.

  • Works out-of-the-box with Vite for hot module replacement (HMR)
  • Supports full customisation via semantq.config.js

Project Configuration (semantq.config.js)

Customize project-wide settings:

module.exports = {
  targetHost: process.env.TARGET_HOST || 'http://localhost:3000',
  pageTitle: 'My Awesome Website',
  metaDescription: 'My Awesome Website',
  metaKeywords: 'keyword one, keyword two, keyword three',

  sitemap: true,

  semantqNav: {
    enable: true,
    containerClass: 'semantq-nav-container',
    ulClass: 'semantq-nav-list',
    liClass: 'semantq-nav-item',
    priorityRoutes: ['/home'],
    excludeRoutes: ['/404'],
    includeRoutes: {
      '/sitemap': '/sitemap',
      '/home': '/'
    },
    hierarchical: true, 
    parentMenuDisplay: 'inline', // stacked or inline
    customLinkTexts: {
      badmin: 'User Dashboard',

    },
  },
};

Other configuration options include:

  • Custom page titles, meta descriptions, and keywords
  • Sitemap and navigation menu generation
  • Hierarchical routing and route priorities

File-Based Routing

Manual Route Creation

Create directories under src/routes:

src/routes/about

Then inside the about directory create the file: @page.smq.

You can then compose your component following this component mark up structure:

@script
// your js here
@end

@style
/* css here */
@end

@html 

all blocks are optional 

Note the @html tag is optional just for your visual clarity - also note that the tag doesn't have a closing tag (@end). If you include a closing tag @end for the @html block the compiler will throw an error.

The HTML standard also works:

<script>
  // your js here
</script>

<style>
  /* css here */
</style>

<h1> Hello World </h1> 

All blocks are optional so... this alone will work:

<h1> Hello World </h1>
With the route created, you can now add a link to it. For example, in your entry page:

`project_root/index.html` you might link to the `about` page like this:

```html
<a href="/about">About Us</a>

Note: All routes must start with a leading /.

Using the Auto-Generated Navigation

If you have enabled the auto-generated Semantq navigation module in your semantq.config.js (by setting SemantqNav: true), then you don’t need to manually build a navigation menu.

Instead, you can simply import and use the SemantqNav component in your pages. See the example below.

@script
import SemantqNav from '$global/SemantqNav';
const someText = 'We are an amazing company.';
@end

@html
<SemantqNav />

<h1>About Us</h1>

<p> { someText } </p>

This will automatically render a file-based menu at the top of your page.

For details on navigation menu configuration and customisation, see Automated Navigation Menu Generation.

CLI-Based Routing

Once you’ve mastered Semantq manual route creation, you can take advantage of the Command Line Interface (CLI). This powerful tool automatically generates directories, installs the base files, and provides skeletal tags to help you get started quickly—saving you valuable time in production.

semantq make:route about -l

The command above will create the route: src/routes/about and include all necessary files e.g. src/routes/about/@page.smq and src/routes/about/@layout.smq

Options:

OptionDescription
-l / --layoutCreate layout page for <head> scripts and links
-a / --authRestrict page to signed-in users
--crud / -cGenerate CRUD abstractions
--acCombine auth + CRUD

Layout Composition

If your page requires additional JS or CSS assets (from a CDN or local files), you can define them in the @layout.smq file within the same route directory.
For example, adding Bootstrap from a CDN:

@head
<!-- Bootstrap CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">

<!-- Bootstrap JS -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
@end

Dealing with FOUC (Flash of Unstyled Content)

What is FOUC?
FOUC (Flash of Unstyled Content) happens when the browser renders a page’s HTML before its CSS has fully loaded, causing a brief “unstyled” flash.

To reduce FOUC, it’s best to import your CSS directly in the script block of your @page.smq file:

@script
import '/components.css'; // if your css file is in project_root/public/components.css
@end

By importing CSS in the @script block of your @page.smq, Semantq ensures that styles are loaded and applied as part of the rendering pipeline, reducing the chance of unstyled content appearing during page load.

Your JavaScript assets (CDN or local) can still be defined in the layout file, while CSS critical to rendering should be imported at the page level.

The layout file has more blocks such as @script ... @end, @head ...@end, @body ... @end, @footer ..@end - all blocks are optional and you only use what you need. Please see the link to the comprehensive Layout Composition guide for more on this.

Component Composition

Creating Reusable Components

  • Default and named slots supported

Default Slots

<!-- src/components/global/Animation.smq -->

<div class="animation">
  <slot> Fall back content from the imported (child) component </slot> <!-- default slot -->
</div>

Importing Components

@script
import Animation from '$global/Animation';
@end

@html
<Animation /> 
// this will resolve the mark up from the imported component (without the raw slot element )

Final Resolved default slot mark up

<div class="animation">
  Fall back content from the imported (child) component
</div>

Note how content was passed from the child component because the parent component (page) did not provide any content to be inserted into the default slot.

If you want to pass content from the parent component (page) see the example below:

@script
import Animation from '$global/Animation';
@end

@html
<Animation> 
  <h1> My animated heading </h1> 
</Animation> 

Final resolved mark up:

<div class="animation">
  <h1> My animated heading </h1> 
</div>

Note The Javascript and CSS in the respective tags (blocks) in your imported component will also be imorted and merged with the main page js and css.

  • Shorthand automatically resolves to src/components/global/Animation.smq
  • Useful for reusable components such as headers, footers, nav, modals, and more

Named Slots for Reusable Components

Semantq supports named slots that allow you to keep your main page cleaner while making child components reusable.

⚠️ Warning: The child component should generally have self-contained markup. Prefer using elements like <div> or <section> in the child component to avoid situations where parent-provided content (e.g., a <div>) ends up wrapped in invalid HTML such as a <p>. Modern browsers may tolerate this, but it can lead to unpredictable rendering.

Child Component (FootnoteCard.smq)

@script
 //some js for the component
@end

@style
  /* some css for the component */
@end

@html
<div class="footnote-card">
  <div class="footnote-header">
    <slot name="header" />
  </div>

  <div class="footnote-body">
    <slot name="body" />
  </div>

  <div class="footnote-footer">
    <slot name="footer" />
  </div>
</div>

OR if you want to include fall back content in the child component named slots.

<div class="footnote-card">
  <div class="footnote-header">
    <slot name="header">Default Header</slot>
  </div>

  <div class="footnote-body">
    <slot name="body">Default body content goes here. This is the fallback text if no content is provided.</slot>
  </div>

  <div class="footnote-footer">
    <slot name="footer">Default Footer</slot>
  </div>
</div>
  • The child defines named slots: header, body, and footer.
  • It wraps content in full <div> structure to ensure valid HTML regardless of what the parent passes.

Parent Usage (DocsPage.smq)

<FootnoteCard>
  <div slot="header">Note</div>

  <div slot="body">
    This section explains the key usage of Semantq slots.
  </div>

  <div slot="footer">
    <a href="#more-info">Learn more</a>
  </div>
</FootnoteCard>
  • The parent only provides minimal, surgical content for each slot.
  • The child handles all layout, styling, and structure, keeping the page markup clean and maintainable.

Final Rendered Mark

<div class="footnote-card">
  <div class="footnote-header">
    <div>Note</div>
  </div>

  <div class="footnote-body">
    <div>This section explains the key usage of Semantq slots.</div>
  </div>

  <div class="footnote-footer">
    <div><a href="#more-info">Learn more</a></div>
  </div>
</div>

Benefits:

  • Reusable components without worrying about parent markup conflicts.
  • Named slots allow selective content injection.
  • Keeps main documentation page markup neat and readable.

NPM Integration

  • Any npm package can be used in pages or components
  • Browser-targeted packages must be imported appropriately
import _ from 'lodash';
  • Works seamlessly with Semantq’s Vite powered bundler

State Management

Native reactive state library:

@script
const count = $state(0);
const doubled = $derived(() => count.value * 2);

$effect(() => {
  console.log(`Current count: ${count.value}`);
});
@end
  • $state → reactive variable
  • $derived → computed value
  • $effect → auto-run reactive effect

$onMount Lifecycle Hook In Semantq:

## `$onMount`

The `$onMount` lifecycle hook lets you run JavaScript after a component is mounted and the DOM is ready.  
This is useful for attaching event listeners, running animations, or targeting DOM elements without relying on  
`document.addEventListener('DOMContentLoaded', ...)`.

Usage

$onMount(() => {
  // Your DOM-ready logic here
  const el = document.querySelector('#myElement');
  el.focus();
});

Async Usage (e.g. Fetch Calls)

You can also pass an async arrow function into $onMount. This is useful when you need to fetch data or run other asynchronous logic once the component is mounted.

$onMount(async () => {
  try {
    const res = await fetch('https://api.example.com/data');
    const data = await res.json();
    
    console.log('Fetched data:', data);
    // You can now update the DOM or component state with the data
  } catch (err) {
    console.error('Failed to fetch:', err);
  }
});

Why $onMount?

  • Cleaner than using document.addEventListener('DOMContentLoaded', ...)
  • Automatically scoped to the component lifecycle
  • Ensures your code runs when the component is actually in the DOM

Hot Module Replacement with Vite

  • Automatic reload of HTML and CSS on changes
  • No need to refresh the browser
  • This applies only to the entry page: project_root/index.html on the Single Page Application (SPA) contexts.

Semantq Ecosystem

ModuleDescription
FormiqueNo-code, low-code Schema Definition Language form builder with API integration
AnyGridSearchable, paginated, sortable data visualization with on-screen edit/delete API support
@semantq/stateFramework-agnostic state management library
@semantqQLMCSR Node.js server (model, controller, service, route)
@semantq/authFull-stack auth server with database and email support
@semantq/qlJS fetch abstraction and CRUD functions
SemantqCommercePlug-and-play e-commerce solution (WIP)
SemantqProseSSR blogging & CMS with email marketing (WIP)
@semantq/iotIoT module for embedded systems (WIP)

CLI Commands & Project Scaffolding

CommandDescription
semantq create myappScaffold a new project
semantq make:route dashboard --auth --crudCreate an authenticated CRUD route
semantq install:tailwindSetup Tailwind CSS
semantq updateUpdate core dev modules (with confirmation prompts). Semantq will auto-backup your core_modules directory
semantq create my_crud_app -fsFull-stack scaffold (frontend + SemantqQL + auth)
semantq make:resource ProductGenerate a full MCSR resource (model, controller, service, route)
npm run devRun the compiler and render the entry page (project_root/index.html). The local dev server usually runs at: http://localhost:5173/

App Testing & Browser Preview

To test or preview your app in real time:

  • Development mode
    Run:
npm run dev

This will compile your project and display the entry page in your browser, usually at: http://localhost:5173/

  • Build for final deployment Run:

    npm run build
    

    This compiles your entire project and generates a build inside project_root/build, then bundles the browser-ready distribution into project_root/dist.

Note The contents of the project_root/dist directory are what you should deploy to your production server — for example, in the public_html directory on cPanel.

When running the final build for production with auto navigation menu generation enabled, remember to update the targetHost setting in your config with your production domain (e.g., https://mywebsitename.com). This value is used to generate your navigation menu.

  • Serve and Preview the production dist on local environment Run:

    npx serve dist
    

This serves the dist directory of your final compiled app. All pages will be available in the browser, usually at: http://localhost:3000

Declarative Syntax

Interpolations

Semantq Interpolation capabilities enable you to bring JavaScript expressions into html.

@script
const name = 'John';
let count = $state(0);
@end

@html
<h1> Hello {name} </h1>
<p> Clicked: {count} {count > 0 ? 'times' : 'time'} </p>

Event Handlers

<button @click={increment}>Click Me</button>

Logic Blocks

Logic blocks let you express conditional rendering and iteration directly inside your templates using a declarative syntax. Instead of writing imperative JavaScript for every case, you can describe the structure of your UI in plain markup (@if, @else, @each). This keeps your code cleaner, closer to the HTML, and easier to reason about — especially when working with dynamic data that should map naturally to the DOM.

@if and @each Logic Blocks

<h1>Dashboard</h1>

@if(items.length > 0)
  @each(items as item)
    @if(item.active)
      <li>{item.name}</li>
    @else
      <li>Member is inactive</li>
    @endif
  @endeach
@else
  <p>No items available</p>
@endif

Rationale for Logic Blocks: Example: Rendering a List of Items

Vanilla JS vs Semantq

Suppose we have this items object (array):

const items = [
  { name: "Alice", active: true },
  { name: "Bob", active: false },
  { name: "Charlie", active: true },
  { name: "Musa", active: true }
];

Vanilla JS Imperative Rendering

<ul id="list"></ul>
<p id="empty"></p>

<script>
  const items = [
    { name: "Alice", active: true },
    { name: "Bob", active: false },
    { name: "Charlie", active: true }
  ];

  const list = document.getElementById('list');
  const empty = document.getElementById('empty');

  if (items.length > 0) {
    list.innerHTML = '';
    items.forEach(item => {
      const li = document.createElement('li');
      li.textContent = item.active ? item.name : 'Member is inactive';
      list.appendChild(li);
    });
    empty.textContent = '';
  } else {
    empty.textContent = 'No items available';
  }
</script>

Semantq Declarative Syntax

@if(items.length > 0)
  @each(items as item)
    @if(item.active)
      <li>{item.name}</li>
    @else
      <li>Member is inactive</li>
    @endif
  @endeach
@else
  <p>No items available</p>
@endif

Final Rendered HTML Output

Both approaches produce the exact same DOM:

<li>Alice</li>
<li>Member is inactive</li>
<li>Charlie</li>

Semantq’s declarative logic blocks let you describe the UI in a way that’s cleaner, shorter, and easier to reason about — without writing low-level DOM boilerplate.

  • Supported so far: @if / @else / @endif and @each / @endeach
  • More logic blocks coming

Automated Navigation Menu Generation

Semantq can automatically generate a mobile friendly navigation menu based on your defined routes. This reduces boilerplate and ensures your navigation stays consistent as your app grows. Configuration is done inside your semantq.config.js:

semantqNav: {
  enable: true,
  containerClass: 'semantq-nav-container',
  ulClass: 'semantq-nav-list',
  liClass: 'semantq-nav-item',
  priorityRoutes: ['/home','/about'],
  excludeRoutes: ['/404','/auth','/'],
  includeRoutes: {
    '/services': '/about/frontend',
    '/home': '/',
    '/search': 'https://google.com'
  },
  hierarchical: true,
  parentMenuDisplay: 'inline',
  customLinkTexts: {
    admin: 'User Dashboard'
  },
}

Configuration Options Explained

  • enable Turns auto-navigation on or off. If set to false, no nav menu will be generated.

  • containerClass Applies a CSS class to the outer navigation container (<nav> element), making it easy to style globally.

  • ulClass / liClass Classes applied to the <ul> and <li> elements, so you can fully customise the look and feel of the menu.

This means you can instruct Semantq to generate a navigation menu that suits your custom styles if you don't want to rely on the default css for the navigation menu.

  • priorityRoutes An array of routes that should always appear that order in the navigation, regardless of alphabetical order or hierarchy.

  • excludeRoutes Routes that should not be shown in the navigation. Common examples are error pages (/404), auth flows (/auth), or the root / route if you don’t want it to appear.

  • includeRoutes Lets you explicitly add or remap routes:

    • Map one route to another: '/services': '/about/frontend' makes the /services menu entry point to /about/frontend.
    • Map a route to an external link: '/search': 'https://google.com'.
    • Map root paths: '/home': '/' means the /home menu entry will actually point to /.
  • hierarchical If true, routes are displayed in a nested structure (dropdowns or tree menu), reflecting folder hierarchy. If false, all routes are displayed flat.

  • parentMenuDisplay Controls how parent routes with children are rendered:

    • "inline" → all menu items are inlined - except for drop downs if hierarchical is true
    • "stacked" → a vertically stacked navigation menu
  • customLinkTexts Overrides default link text for specific routes. For example:

    customLinkTexts: { admin: 'User Dashboard' }
    

    will display "User Dashboard" instead of "admin".

Example Output

Given the configuration above, your navigation will render as:

<nav class="semantq-nav-container">
  <ul class="semantq-nav-list">
    <li class="semantq-nav-item"><a href="/">Home</a></li>
    <li class="semantq-nav-item"><a href="/about">About</a></li>
    <li class="semantq-nav-item"><a href="/about/frontend">Services</a></li>
    <li class="semantq-nav-item"><a href="https://google.com" target="_blank">Search</a></li>
    <li class="semantq-nav-item"><a href="/admin">User Dashboard</a></li>
  </ul>
</nav>

Notice that:

  • /home was remapped to /.
  • /services was linked to /about/frontend.
  • /search became an external link.
  • The admin route displays as “User Dashboard.”
  • /404, /auth, and / were excluded.

Hierarchial Menu: hierarchical: true

Hierachical menus follow the src/routes folder structure.

Suppose your project has the following route files:

src/routes/
├── home
├── about
├── services/
│   ├── africa
│   ├── asia
│   └── europe
├── contact

With hierarchical: true

The menu generator recognises the nested structure (services/africa, services/asia, etc.) and outputs nested <ul> lists.

<nav class="semantq-nav-container">
  <ul class="semantq-nav-list">
    <li class="semantq-nav-item"><a href="/home">Home</a></li>
    <li class="semantq-nav-item"><a href="/about">About</a></li>
    <li class="semantq-nav-item">
      <a href="/services">Services</a>
      <ul class="semantq-nav-list">
        <li class="semantq-nav-item"><a href="/services/africa">Africa</a></li>
        <li class="semantq-nav-item"><a href="/services/asia">Asia</a></li>
        <li class="semantq-nav-item"><a href="/services/europe">Europe</a></li>
      </ul>
    </li>
    <li class="semantq-nav-item"><a href="/contact">Contact</a></li>
  </ul>
</nav>

The /services route acts as a parent menu item, while /services/africa, /services/asia, and /services/europe appear as dropdown children at the same level.

Note With Semantq, there are no limits to how deep your routes can go. The framework automatically translates directory nesting into fully functional multi-level dropdown menus. Each dropdown item can seamlessly expand into its own nested menu, giving you an infinitely scalable navigation system that grows with your app — without extra configuration.

Here’s a visual hierarchical example to illustrate how hierarchical: true and infinite nesting works in Semantq:

Example: Multi-Level Dropdown Menu

<nav class="semantq-nav-container">
  <ul class="semantq-nav-list">
    <li class="semantq-nav-item">
      <a href="/home">Home</a>
    </li>
    <li class="semantq-nav-item">
      <a href="/services">Services ▾</a>
      <ul class="semantq-nav-list">
        <li class="semantq-nav-item">
          <a href="/services/africa">Africa ▾</a>
          <ul class="semantq-nav-list">
            <li class="semantq-nav-item"><a href="/services/africa/north">North</a></li>
            <li class="semantq-nav-item"><a href="/services/africa/south">South</a></li>
          </ul>
        </li>
        <li class="semantq-nav-item">
          <a href="/services/asia">Asia ▾</a>
          <ul class="semantq-nav-list">
            <li class="semantq-nav-item"><a href="/services/asia/east">East</a></li>
            <li class="semantq-nav-item"><a href="/services/asia/west">West</a></li>
          </ul>
        </li>
      </ul>
    </li>
    <li class="semantq-nav-item">
      <a href="/about">About</a>
    </li>
  </ul>
</nav>

How it works in Semantq

  • Each folder in src/routes automatically maps to a menu item.
  • Subfolders become nested <ul> lists inside parent <li> items.
  • There is no depth limit: if you keep nesting folders, the dropdown menus keep nesting as well.
  • Custom CSS (semantq-nav-container, semantq-nav-list, semantq-nav-item) controls styling and dropdown behaviour.

With hierarchical: false

If you turn hierarchy off, the same routes flatten into a single-level list:

<nav class="semantq-nav-container">
  <ul class="semantq-nav-list">
    <li class="semantq-nav-item"><a href="/home">Home</a></li>
    <li class="semantq-nav-item"><a href="/about">About</a></li>
    <li class="semantq-nav-item"><a href="/services">Services</a></li>
    <li class="semantq-nav-item"><a href="/services/africa">Africa</a></li>
    <li class="semantq-nav-item"><a href="/services/asia">Asia</a></li>
    <li class="semantq-nav-item"><a href="/services/europe">Europe</a></li>
    <li class="semantq-nav-item"><a href="/contact">Contact</a></li>
  </ul>
</nav>

⚡ Bonus: You can control how parent items behave with parentMenuDisplay:

  • "inline" → renders a horizontal menu layout. Dropdowns are preserved if hierarchical: true.

  • "stacked" → renders a vertically stacked menu layout. Dropdowns are preserved if hierarchical: true.

  • Automatic menu generation with custom classes, route priorities, and hierarchies

Full-Stack Integration with SemantqQL

  • Plug-and-play backend generation with model, controller, service, and route
  • Works with MySQL, Supabase, MongoDB, SQLite
  • Fully integrated with frontend, authentication, and CRUD
  • Supports REST APIs immediately after scaffolding

Example command:

semantq make:resource Product

This will create the Model, Controller, Service, and Route (MCSR) resources, with routes automatically mounted by the server.

You can query the generated endpoint directly:

curl -X GET http://localhost:3003/product/products

You can add your Product schema in project_root/semantqQL/prisma/schema.prisma:

model Product {
  id          Int      @id @default(autoincrement())
  name        String
  description String?
  price       Float
  createdAt   DateTime @default(now())
  updatedAt   DateTime @updatedAt
}

Then, on your command line, navigate to the backend server:

cd semantqQL

Run the migration to create the Product table in your database:

npx prisma migrate dev --name added_product_model

For more details on the full-stack setup, visit: Semantq Full Stack Guide

The route is automatically prefixed by the resource name (in lowercase).

Full JavaScript Fetch Example

const fetchProducts = async () => {
  const res = await fetch('http://localhost:3003/product/products', {
    method: 'GET',
    headers: {
      'Content-Type': 'application/json'
    }
  });
  const data = await res.json();
  console.log(data);
};

fetchProducts();

Abstraction with smQL

import { smQL } from '@semantq/ql';

// api set up
const api = new smQL('http://localhost:3003');

// fetch data example
const response = await api.get('/product/products');


// Extract valid product objects
const products = Object.values(response).filter(
  item => typeof item === 'object' && item !== null && !Array.isArray(item)
);

console.log(products);

// DELETE request
const response = await api.delete(`/product/products/${product.id}`);

Side-by-Side Comparison

OperationRaw FetchsmQL
SetupMust specify method, headers, JSON handling manually.Just call new smQL(baseURL).
GET Productsfetch('.../product/products', { method: 'GET', headers: {...} })await api.get('/product/products')
DELETE Productsfetch('.../product/products', { method: 'DELETE' })await api.delete('/product/products')
JSON ExtractionMust call .json() manually.Handled internally by smQL.
LoggingMust add manually.Built-in (can enable/disable).
Code ClarityVerbose, repetitive.Clean, concise, standardised.

For a comprehensive guide on smQL features, please visit: https://github.com/Gugulethu-Nyoni/smQL

Feature/Module Specific Docs

Core Features

Template System

Application Architecture

License

Semantq is open-source software licensed under the MIT License.

FAQs

Package last updated on 20 Mar 2026

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts