@opnpress/opnpress-cli 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/server.js ADDED
@@ -0,0 +1,97 @@
1
+ import { createServer } from 'node:http';
2
+ import path from 'node:path';
3
+ import { promises as fs } from 'node:fs';
4
+ function contentTypeFor(filePath) {
5
+ const ext = path.extname(filePath).toLowerCase();
6
+ switch (ext) {
7
+ case '.html':
8
+ return 'text/html; charset=utf-8';
9
+ case '.css':
10
+ return 'text/css; charset=utf-8';
11
+ case '.js':
12
+ return 'text/javascript; charset=utf-8';
13
+ case '.json':
14
+ return 'application/json; charset=utf-8';
15
+ case '.xml':
16
+ return 'application/xml; charset=utf-8';
17
+ case '.txt':
18
+ return 'text/plain; charset=utf-8';
19
+ case '.md':
20
+ return 'text/markdown; charset=utf-8';
21
+ case '.svg':
22
+ return 'image/svg+xml';
23
+ case '.png':
24
+ return 'image/png';
25
+ case '.jpg':
26
+ case '.jpeg':
27
+ return 'image/jpeg';
28
+ case '.webp':
29
+ return 'image/webp';
30
+ case '.ico':
31
+ return 'image/x-icon';
32
+ default:
33
+ return 'application/octet-stream';
34
+ }
35
+ }
36
+ async function resolveFile(distDir, requestUrl) {
37
+ const urlPath = decodeURIComponent(new URL(requestUrl, 'http://localhost').pathname);
38
+ const cleanPath = urlPath === '/' ? '/index.html' : urlPath;
39
+ const directPath = path.join(distDir, cleanPath.replace(/^\//, ''));
40
+ try {
41
+ const stat = await fs.stat(directPath);
42
+ if (stat.isFile()) {
43
+ return directPath;
44
+ }
45
+ }
46
+ catch {
47
+ // fall through
48
+ }
49
+ if (!path.extname(cleanPath)) {
50
+ const nestedIndex = path.join(distDir, cleanPath.replace(/^\//, ''), 'index.html');
51
+ try {
52
+ const stat = await fs.stat(nestedIndex);
53
+ if (stat.isFile()) {
54
+ return nestedIndex;
55
+ }
56
+ }
57
+ catch {
58
+ // fall through
59
+ }
60
+ }
61
+ return null;
62
+ }
63
+ export async function startPreviewServer(distDir) {
64
+ const server = createServer(async (req, res) => {
65
+ const requestUrl = req.url ?? '/';
66
+ const filePath = await resolveFile(distDir, requestUrl);
67
+ if (!filePath) {
68
+ res.statusCode = 404;
69
+ res.setHeader('Content-Type', 'text/plain; charset=utf-8');
70
+ res.end('Not found');
71
+ return;
72
+ }
73
+ const body = await fs.readFile(filePath);
74
+ res.statusCode = 200;
75
+ res.setHeader('Content-Type', contentTypeFor(filePath));
76
+ res.end(body);
77
+ });
78
+ await new Promise((resolve) => {
79
+ server.listen(0, '127.0.0.1', () => resolve());
80
+ });
81
+ const address = server.address();
82
+ const url = `http://127.0.0.1:${address.port}/`;
83
+ return {
84
+ url,
85
+ close: async () => {
86
+ await new Promise((resolve, reject) => {
87
+ server.close((error) => {
88
+ if (error) {
89
+ reject(error);
90
+ return;
91
+ }
92
+ resolve();
93
+ });
94
+ });
95
+ }
96
+ };
97
+ }
package/dist/utils.js ADDED
@@ -0,0 +1,45 @@
1
+ import { promises as fs } from 'node:fs';
2
+ import path from 'node:path';
3
+ export function toPosix(input) {
4
+ return input.split(path.sep).join('/');
5
+ }
6
+ export function slugify(input) {
7
+ return input
8
+ .trim()
9
+ .toLowerCase()
10
+ .replace(/[^a-z0-9]+/g, '-')
11
+ .replace(/^-+|-+$/g, '');
12
+ }
13
+ export function escapeHtml(input) {
14
+ return String(input)
15
+ .replace(/&/g, '&')
16
+ .replace(/</g, '&lt;')
17
+ .replace(/>/g, '&gt;')
18
+ .replace(/"/g, '&quot;')
19
+ .replace(/'/g, '&#39;');
20
+ }
21
+ export async function ensureDir(dir) {
22
+ await fs.mkdir(dir, { recursive: true });
23
+ }
24
+ export async function writeFileEnsured(filePath, content) {
25
+ await ensureDir(path.dirname(filePath));
26
+ await fs.writeFile(filePath, content, 'utf8');
27
+ }
28
+ export async function walkFiles(root) {
29
+ const results = [];
30
+ const entries = await fs.readdir(root, { withFileTypes: true });
31
+ for (const entry of entries) {
32
+ const fullPath = path.join(root, entry.name);
33
+ if (entry.isDirectory()) {
34
+ results.push(...(await walkFiles(fullPath)));
35
+ continue;
36
+ }
37
+ if (entry.isFile()) {
38
+ results.push(fullPath);
39
+ }
40
+ }
41
+ return results;
42
+ }
43
+ export function isFullHtmlDocument(html) {
44
+ return /<html[\s>]/i.test(html) || /<!doctype html>/i.test(html);
45
+ }
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "@opnpress/opnpress-cli",
3
+ "version": "0.1.0",
4
+ "private": false,
5
+ "type": "module",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/OpnPress/OpnPressCli.git"
9
+ },
10
+ "files": [
11
+ "dist",
12
+ "templates",
13
+ "README.md",
14
+ "package.json"
15
+ ],
16
+ "publishConfig": {
17
+ "access": "public"
18
+ },
19
+ "bin": {
20
+ "opnPrs": "./dist/cli.js",
21
+ "opnPress": "./dist/cli.js"
22
+ },
23
+ "scripts": {
24
+ "build": "tsc -p tsconfig.build.json",
25
+ "check": "tsc --noEmit",
26
+ "dev": "tsx src/cli.ts",
27
+ "init": "tsx src/cli.ts init",
28
+ "run": "tsx src/cli.ts run",
29
+ "test": "vitest run"
30
+ },
31
+ "dependencies": {
32
+ "gray-matter": "^4.0.3",
33
+ "rehype-autolink-headings": "^7.1.0",
34
+ "rehype-raw": "^7.0.0",
35
+ "rehype-slug": "^6.0.0",
36
+ "rehype-stringify": "^10.0.1",
37
+ "remark-gfm": "^4.0.1",
38
+ "remark-parse": "^11.0.0",
39
+ "remark-rehype": "^11.1.2",
40
+ "tsx": "^4.20.3",
41
+ "unified": "^11.0.5",
42
+ "yaml": "^2.8.1",
43
+ "zod": "^3.25.67"
44
+ },
45
+ "devDependencies": {
46
+ "@types/node": "^24.3.0",
47
+ "playwright": "^1.59.1",
48
+ "typescript": "^5.9.2",
49
+ "vitest": "^3.2.4"
50
+ }
51
+ }
@@ -0,0 +1,37 @@
1
+ name: Build and Deploy Pages
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - master
7
+ workflow_dispatch:
8
+
9
+ permissions:
10
+ contents: read
11
+
12
+ concurrency:
13
+ group: cloudflare-pages
14
+ cancel-in-progress: true
15
+
16
+ jobs:
17
+ deploy:
18
+ runs-on: ubuntu-latest
19
+ steps:
20
+ - name: Checkout
21
+ uses: actions/checkout@v4
22
+
23
+ - name: Setup Node
24
+ uses: actions/setup-node@v4
25
+ with:
26
+ node-version: '22'
27
+ cache: npm
28
+
29
+ - name: Build site
30
+ run: npx -y @opnpress/opnpress-cli@latest opnPress build
31
+
32
+ - name: Publish to Cloudflare Pages
33
+ uses: cloudflare/wrangler-action@v3
34
+ with:
35
+ apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
36
+ accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
37
+ command: pages deploy dist --project-name=${{ secrets.CLOUDFLARE_PROJECT_NAME }} --branch=${{ github.ref_name }}
@@ -0,0 +1,6 @@
1
+ # Skills
2
+
3
+ This folder stores starter guidance for building an OpnPress site.
4
+
5
+ The files here are copied by `opnPress init`, so edit the templates in this repository to change the scaffold.
6
+
@@ -0,0 +1,18 @@
1
+ # Add Shortcode
2
+
3
+ Use this skill when adding a shortcode block to an existing page.
4
+
5
+ ## What To Do
6
+
7
+ - read `.skills/shortcodes.md` before choosing the block
8
+ - prefer the simplest shortcode that fits the page intent
9
+ - keep page-local links relative, and use `source.md` only when you explicitly want the source mirror
10
+ - use integration-backed blocks only when the content comes from `site.config.yaml`
11
+ - keep the page content readable to an LLM without needing rendered HTML
12
+
13
+ ## Recommended Checks
14
+
15
+ - verify the shortcode matches the content type and source of truth
16
+ - verify links between pages stay relative
17
+ - verify the build still passes after the page change
18
+
@@ -0,0 +1,17 @@
1
+ # Create Page
2
+
3
+ Use this skill when adding a new page to an OpnPress site.
4
+
5
+ ## What To Do
6
+
7
+ - choose the correct file path under `content/pages/`, `content/services/`, or `content/locations/`
8
+ - add YAML frontmatter with title, description, and layout
9
+ - keep the page focused on one intent
10
+ - validate the build after creating the file
11
+
12
+ ## Recommended Checks
13
+
14
+ - verify the route matches the file path
15
+ - verify navigation points to the page if needed
16
+ - verify the page renders without broken links
17
+
@@ -0,0 +1,75 @@
1
+ # Deployment Checks
2
+
3
+ Use this skill before publishing a site or opening a release.
4
+
5
+ ## Purpose
6
+
7
+ - run the other relevant skills
8
+ - identify what is missing before publish
9
+ - generate a report with actionable fixes
10
+ - save the report for later reference
11
+ - capture user answers as durable decisions so future checks can reuse them
12
+
13
+ ## Trigger Order
14
+
15
+ 1. `site-audit`
16
+ 2. `link-audit`
17
+ 3. `theme-customization`
18
+ 4. `setup-integrations`
19
+ 5. `update-navigation`
20
+ 6. `update-header-footer`
21
+ 7. `create-page` or `update-page` for page-specific fixes
22
+
23
+ ## What To Check
24
+
25
+ - missing required page metadata
26
+ - broken internal links
27
+ - orphaned or unreachable pages
28
+ - navigation completeness
29
+ - header and footer consistency
30
+ - integration configuration
31
+ - SEO and AI metadata
32
+ - build output readiness
33
+ - publish target readiness
34
+
35
+ ## Required Output
36
+
37
+ Write a report with these sections:
38
+
39
+ - Summary
40
+ - Blocking issues
41
+ - Non-blocking recommendations
42
+ - Pages to create or update
43
+ - Navigation or footer changes
44
+ - Integration fixes
45
+ - Publish readiness verdict
46
+ - Saved decisions
47
+
48
+ ## Report Rules
49
+
50
+ - be specific about file paths
51
+ - explain why each item matters
52
+ - recommend the exact next action
53
+ - separate blockers from recommendations
54
+ - if nothing is blocking, say so clearly
55
+
56
+ ## Saving The Report
57
+
58
+ - save the report in the repository under `.opnpress/reports/deployment-checks.md`
59
+ - if the folder does not exist, create it
60
+ - append the date and time of the check to the top of the report
61
+ - keep the latest report easy to find
62
+
63
+ ## Learning From The User
64
+
65
+ - if the user answers questions during the check, record those answers in the report under `Saved Decisions`
66
+ - preserve the answers in a reusable note so future checks can apply them automatically
67
+ - when a decision is repeated, prefer the saved answer over asking again
68
+
69
+ ## Recommended Behavior
70
+
71
+ - treat this as the last step before publish
72
+ - if a blocker exists, stop and report it
73
+ - if no blockers exist, confirm the site is ready for deployment
74
+ - prefer concrete file edits over vague advice
75
+
@@ -0,0 +1,6 @@
1
+ # Integrations
2
+
3
+ This file documents the default integration blocks that can be enabled in a starter site.
4
+
5
+ Use the site config to wire provider settings, then reference the corresponding shortcode in content pages.
6
+
@@ -0,0 +1,17 @@
1
+ # Link Audit
2
+
3
+ Use this skill when checking for broken or orphaned pages.
4
+
5
+ ## What To Check
6
+
7
+ - broken internal links
8
+ - orphaned content pages
9
+ - missing navigation targets
10
+ - mismatched route and file paths
11
+
12
+ ## Output Style
13
+
14
+ - list the broken or orphaned path
15
+ - explain where it is referenced, or not referenced
16
+ - recommend whether to link it, delete it, or redirect it
17
+
@@ -0,0 +1,84 @@
1
+ # Setup Integrations
2
+
3
+ Use this skill when adding or changing integrations.
4
+
5
+ ## What To Do
6
+
7
+ - define the provider in `site.config.yaml`
8
+ - author the semantic block in markdown
9
+ - keep the content provider-agnostic
10
+ - prefer integration-backed blocks for shared site data
11
+ - validate the page after rendering
12
+
13
+ ## Supported Blocks
14
+
15
+ - `contact-form`
16
+ - `shareable-links`
17
+ - `socials-links` from `site.socials`
18
+ - `company-info`
19
+ - `contact-links`
20
+ - `booking-calendar`
21
+ - `maps`
22
+
23
+ ## Media Shortcodes
24
+
25
+ - `video`
26
+
27
+ Treat `video` as a shortcode with `provider` and `url` params, not as a separate integration category.
28
+
29
+ ## Page-Local Shortcodes
30
+
31
+ - `contact-card`
32
+ - `mailto`
33
+ - `tel`
34
+
35
+ Use `contact-card` for page-local contact blocks such as franchise locations or branch pages.
36
+ Use `mailto` and `tel` for single-use contact actions inside page content.
37
+
38
+ ## Social Profiles
39
+
40
+ Use `site.socials` for public profile links and social metadata.
41
+
42
+ Supported keys:
43
+
44
+ - `twitter`
45
+ - `x`
46
+ - `github`
47
+ - `facebook`
48
+ - `instagram`
49
+ - `linkedin`
50
+ - `youtube`
51
+ - `tiktok`
52
+ - `threads`
53
+ - `mastodon`
54
+ - `bluesky`
55
+ - `website`
56
+ - `email`
57
+
58
+ Each value can be a handle or a full URL. The renderer converts handles into public profile URLs where possible.
59
+
60
+ ## Config Fields
61
+
62
+ - `site.site.*` for site identity and branding
63
+ - `site.seo.defaultImage` for crawl and preview defaults
64
+ - `site.socials.*` for social identity links
65
+ - `site.branding.poweredByOpnPress` for footer attribution
66
+ - `site.deployment.provider` and `site.deployment.cloudflare.*` for publishing
67
+ - `site.integrations.contactForm.*`
68
+ - `site.integrations.shareableLinks.*`
69
+ - `site.integrations.companyInfo.*`
70
+ - `site.integrations.contactLinks.*`
71
+ - `site.integrations.bookingCalendar.*`
72
+ - `site.integrations.maps.*`
73
+ - `contact-card` shortcode params: `name`, `tagline`, `logo`, `address`, `mapUrl`, `phone`, `email`, `hours`, `website`
74
+ - `contact-links` shortcode params: `variant`, `showIcons`, `showLabels`
75
+ - `mailto` shortcode params: `email`, `subject`, `body`, `label`, `showIcon`
76
+ - `tel` shortcode params: `phone`, `label`, `showIcon`
77
+ - `video` shortcode params: `provider`, `url`, and optional presentation fields
78
+
79
+ ## Recommended Checks
80
+
81
+ - verify required config values are present
82
+ - verify the block renders instead of falling back to escaped code
83
+ - verify the resulting links stay relative when they should
84
+
@@ -0,0 +1,152 @@
1
+ # Shortcodes
2
+
3
+ This file is for LLM-facing reference only.
4
+
5
+ It is not rendered as a site page. The build copies it into `dist/shortcodes.md` and references it from `llms.txt`.
6
+
7
+ ## Principles
8
+
9
+ - Pages render directly from markdown or standalone HTML.
10
+ - Shortcodes exist for structured sections, not hidden renderer layouts.
11
+ - Some shortcodes are integration display hooks: they only control placement, not the underlying data source.
12
+ - All links between markdown pages, mirrors, and related content should stay relative so an LLM can infer the site structure and traverse it without resolving absolute URLs first.
13
+
14
+ ## Shared Presentation
15
+
16
+ Most shortcodes support the same shared presentation props:
17
+
18
+ - `title`
19
+ - `description`
20
+ - `eyebrow`
21
+ - `anchor`
22
+ - `backgroundImage`
23
+ - `backgroundPosition`
24
+ - `backgroundSize`
25
+ - `backgroundRepeat`
26
+ - `backgroundColor`
27
+ - `overlay`
28
+ - `textColor`
29
+ - `padding`
30
+ - `minHeight`
31
+ - `maxWidth`
32
+ - `align`
33
+
34
+ Keep these relative-path friendly for assets and backgrounds.
35
+
36
+ ## Page-Structure Shortcodes
37
+
38
+ ### `cardrow`
39
+
40
+ Render a row or grid of cards on a markdown page.
41
+
42
+ Prefer explicit card objects with relative `href` values such as `about/`.
43
+ Use `source.md` only when the card intentionally points to the LLM/source mirror.
44
+
45
+ Example:
46
+
47
+ ```md
48
+ :::cardrow
49
+ title: Explore the starter site
50
+ description: Cards should normally point at rendered pages.
51
+ cards:
52
+ - title: Pages
53
+ href: about/
54
+ body: Create markdown pages with YAML frontmatter.
55
+ - title: Integrations
56
+ href: integrations/
57
+ body: Render semantic blocks for forms, social links, maps, video, and calendars.
58
+ - title: Services
59
+ href: services/
60
+ body: Create service pages with the same markdown-first workflow.
61
+ :::
62
+ ```
63
+
64
+ ### `pagelist`
65
+
66
+ Render a list of pages from a folder.
67
+
68
+ The list links to each page's rendered route by default. Use source mirrors only when the list is specifically for LLM traversal or source inspection.
69
+
70
+ ### `contact-card`
71
+
72
+ Render a page-local company or branch contact card.
73
+
74
+ Use this for franchise locations, offices, or branch pages.
75
+
76
+ It accepts the same contact fields as the `company-info` integration, but on a single page.
77
+
78
+ ### `contact-links`
79
+
80
+ Render the reusable contact links that come from the site-level `contact-links` integration.
81
+
82
+ This shortcode only decides where the configured contact links appear on the page.
83
+
84
+ ### `mailto`
85
+
86
+ Render a single email link or compact email block.
87
+
88
+ Use `email`, `subject`, `body`, `label`, and `showIcon` in the shortcode body.
89
+
90
+ ### `tel`
91
+
92
+ Render a single telephone link or compact phone block.
93
+
94
+ Use `phone`, `label`, and `showIcon` in the shortcode body.
95
+
96
+ ## Integration-Backed Blocks
97
+
98
+ ### `contact-form`
99
+
100
+ Render a provider-backed contact form.
101
+
102
+ Requires `site.integrations.contactForm.action`.
103
+
104
+ ### `shareable-links`
105
+
106
+ Render share links for the current page.
107
+
108
+ Requires `site.integrations.shareableLinks.enabled`.
109
+
110
+ ### `socials-links`
111
+
112
+ Render configured social profile links from `site.socials`.
113
+
114
+ ### `booking-calendar`
115
+
116
+ Render a scheduling embed.
117
+
118
+ Requires `site.integrations.bookingCalendar.embedUrl`.
119
+
120
+ ### `maps`
121
+
122
+ Render a map embed.
123
+
124
+ Requires `site.integrations.maps.embedUrl`.
125
+
126
+ ### `company-info`
127
+
128
+ Render canonical company/contact information from site config.
129
+
130
+ This is the site-wide version of the contact card and can be reused in headers, footers, and contact areas.
131
+
132
+ ### `contact-links`
133
+
134
+ Render the reusable contact link cluster from site config.
135
+
136
+ This is the site-wide display hook for contact actions like email, phone, website, and map links.
137
+
138
+ ## Media Shortcodes
139
+
140
+ ### `video`
141
+
142
+ Render a video embed from shortcode params. This is a shortcode, not a site integration category.
143
+
144
+ Provider should usually be `youtube`, `vimeo`, or `loom`.
145
+
146
+ ## Markdown And HTML Mirrors
147
+
148
+ - Markdown pages are published as HTML and mirrored as `source.md`.
149
+ - Custom HTML pages are published as HTML and mirrored as `source.html`.
150
+ - `llms.txt` lists both types separately and labels them with `markdown` or `html`.
151
+ - The hidden `[system-reminder]` marker on markdown pages points to the `source.md` mirror, but visible navigation should normally point to the rendered page.
152
+
@@ -0,0 +1,20 @@
1
+ # Site Audit
2
+
3
+ Use this skill when checking a site for missing pieces and recommending fixes.
4
+
5
+ ## What To Check
6
+
7
+ - required frontmatter fields
8
+ - page titles and descriptions
9
+ - canonical and social metadata
10
+ - navigation completeness
11
+ - footer branding
12
+ - integration config presence
13
+ - build output completeness
14
+
15
+ ## Output Style
16
+
17
+ - list what is missing
18
+ - explain the impact
19
+ - recommend the next fix in file terms
20
+
@@ -0,0 +1,17 @@
1
+ # Theme Customization
2
+
3
+ Use this skill when changing the site theme.
4
+
5
+ ## MVP Surface
6
+
7
+ - colors
8
+ - backgrounds
9
+ - fonts
10
+ - sizing
11
+ - limited layout controls
12
+
13
+ ## Recommended Checks
14
+
15
+ - verify the theme still reads well on mobile
16
+ - verify contrast and spacing remain usable
17
+
@@ -0,0 +1,15 @@
1
+ # Update Header And Footer
2
+
3
+ Use this skill when changing global header or footer content.
4
+
5
+ ## What To Do
6
+
7
+ - edit the site shell, not individual pages, for global header/footer changes
8
+ - keep branding links intentional
9
+ - preserve readable navigation on mobile
10
+
11
+ ## Recommended Checks
12
+
13
+ - verify the header still links home correctly
14
+ - verify the footer still includes required brand or attribution links
15
+
@@ -0,0 +1,16 @@
1
+ # Update Navigation
2
+
3
+ Use this skill when editing site navigation.
4
+
5
+ ## What To Do
6
+
7
+ - update `navigation.yaml`
8
+ - keep labels short and clear
9
+ - use relative internal paths for site pages
10
+ - keep external links explicit
11
+
12
+ ## Recommended Checks
13
+
14
+ - verify every internal navigation target exists
15
+ - verify footer and header nav stay consistent with the site structure
16
+