mikser-io-decap 2.0.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/LICENSE +21 -0
- package/README.md +115 -0
- package/admin/index.html +13 -0
- package/index.js +156 -0
- package/package.json +25 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Almero Digital Marketing
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# mikser-io-decap
|
|
2
|
+
|
|
3
|
+
Mount [Decap CMS](https://decapcms.org/) inside the [mikser-io](https://github.com/almero-digital-marketing/mikser-io) Express server. One process, one port, one command — the admin runs alongside mikser's watcher so edits are picked up and rebuilt on save.
|
|
4
|
+
|
|
5
|
+
## Why an in-process CMS
|
|
6
|
+
|
|
7
|
+
Content sites almost always need an editorial UI — non-developer authors won't (and shouldn't) edit `.md` files in a code editor. The usual approach is to bolt a headless CMS (Sanity, Contentful, Strapi) onto the stack, which adds a vendor, a database, a sync problem, and a license cost per editor seat.
|
|
8
|
+
|
|
9
|
+
Decap is mature, file-based, and ships as a static SPA — exactly the right shape for a file-based content engine. This plugin mounts it inside the same Express server mikser already runs, so the dev story is one command (`mikser --server`) and the production deploy is just static files. Editors get a familiar CMS UI; the content stays as plain `.md` and `.yml` on disk, version-controllable, portable, and free of any database dependency.
|
|
10
|
+
|
|
11
|
+
## The plugin's job
|
|
12
|
+
|
|
13
|
+
A thin host. You write your own `decap.yml`; the plugin serves the admin UI, mounts Decap's local-fs/git proxy backend, and (optionally) copies the admin bundle into the build output for static deployment.
|
|
14
|
+
|
|
15
|
+
## Install
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install mikser-io-decap decap-cms
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
`decap-cms` is a peer dependency — the standalone JS bundle that ships the admin UI.
|
|
22
|
+
|
|
23
|
+
## Configure
|
|
24
|
+
|
|
25
|
+
```js
|
|
26
|
+
// mikser.config.js
|
|
27
|
+
export default {
|
|
28
|
+
plugins: ['documents', 'layouts', 'render-hbs', 'decap'],
|
|
29
|
+
|
|
30
|
+
decap: {
|
|
31
|
+
base: '/admin', // admin URL path; default '/admin'
|
|
32
|
+
proxyBase: '/decap', // backend mount; default '/decap' → POST /decap/api/v1
|
|
33
|
+
mode: 'fs', // 'fs' (raw filesystem) or 'git' (commits per edit)
|
|
34
|
+
configYml: 'decap.yml', // user's Decap config, relative to working folder
|
|
35
|
+
copyToOut: true, // copy admin into out/<base>/ on build; default true
|
|
36
|
+
},
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Then write your `decap.yml` in the working folder:
|
|
41
|
+
|
|
42
|
+
```yaml
|
|
43
|
+
# decap.yml — points Decap at mikser's folders
|
|
44
|
+
backend:
|
|
45
|
+
name: proxy
|
|
46
|
+
proxy_url: http://localhost:3001/decap/api/v1
|
|
47
|
+
branch: main
|
|
48
|
+
|
|
49
|
+
media_folder: files
|
|
50
|
+
public_folder: /files
|
|
51
|
+
|
|
52
|
+
collections:
|
|
53
|
+
- name: documents
|
|
54
|
+
label: Documents
|
|
55
|
+
folder: documents/en
|
|
56
|
+
create: true
|
|
57
|
+
fields:
|
|
58
|
+
- { name: title, label: Title, widget: string }
|
|
59
|
+
- { name: layout, label: Layout, widget: string }
|
|
60
|
+
- { name: date, label: Date, widget: datetime, required: false }
|
|
61
|
+
- { name: body, label: Body, widget: markdown }
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Run
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
npx mikser-io --server --watch
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Open `http://localhost:3001/admin/`. Decap loads, talks to `/decap/api/v1` for reads/writes, and edits land directly in your `documents/` folder. The watcher catches them and rebuilds.
|
|
71
|
+
|
|
72
|
+
## Modes
|
|
73
|
+
|
|
74
|
+
**`fs`** (default) — Decap writes/reads files directly. No git. Best for solo dev and quick iteration.
|
|
75
|
+
|
|
76
|
+
**`git`** — Decap performs a `git add` + `git commit` for each save, with branches per draft when [editorial workflow](https://decapcms.org/docs/configuration-options/#publish-mode) is enabled. Requires the working folder to be a git repo.
|
|
77
|
+
|
|
78
|
+
Both modes are implemented by `decap-server` — this plugin just mounts the appropriate middleware on a sub-app under `proxyBase`.
|
|
79
|
+
|
|
80
|
+
## Production deployment
|
|
81
|
+
|
|
82
|
+
When you run `mikser` (no `--server`), the admin gets baked into `out/<base>/`:
|
|
83
|
+
|
|
84
|
+
```
|
|
85
|
+
out/
|
|
86
|
+
admin/
|
|
87
|
+
index.html
|
|
88
|
+
decap-cms.js
|
|
89
|
+
config.yml
|
|
90
|
+
...
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
For the deployed admin to work, switch the backend in `decap.yml` away from `proxy` (which only exists locally) to `git-gateway` or `github`:
|
|
94
|
+
|
|
95
|
+
```yaml
|
|
96
|
+
backend:
|
|
97
|
+
name: git-gateway # via Netlify Identity
|
|
98
|
+
# or:
|
|
99
|
+
# name: github
|
|
100
|
+
# repo: org/repo
|
|
101
|
+
# branch: main
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Set `copyToOut: false` if you don't want the admin in your public deploy (e.g., you host it on a separate, password-protected URL).
|
|
105
|
+
|
|
106
|
+
## Notes
|
|
107
|
+
|
|
108
|
+
- This plugin requires `runtime.options.app`. Run mikser with `--server` for local use, or pass `app` to `setup({ app })` programmatically.
|
|
109
|
+
- The proxy backend (`mode: 'fs'` / `mode: 'git'`) only works locally — production must use a remote backend (git-gateway, github, gitlab).
|
|
110
|
+
- `decap-server` reads its working directory from `GIT_REPO_DIRECTORY`. The plugin sets this to mikser's `workingFolder` before registering.
|
|
111
|
+
- `decap-server` adds cors, morgan logging, and a 50 MB JSON body limit (for base64-encoded media uploads) scoped to the proxy sub-app — none of that leaks into mikser's main Express app.
|
|
112
|
+
|
|
113
|
+
## License
|
|
114
|
+
|
|
115
|
+
MIT
|
package/admin/index.html
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
|
|
6
|
+
<meta name="robots" content="noindex,nofollow" />
|
|
7
|
+
<title>Content Manager</title>
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<!-- Decap CMS auto-discovers ./config.yml on load. -->
|
|
11
|
+
<script src="decap-cms.js"></script>
|
|
12
|
+
</body>
|
|
13
|
+
</html>
|
package/index.js
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import path from 'node:path'
|
|
2
|
+
import fs from 'node:fs/promises'
|
|
3
|
+
import { fileURLToPath } from 'node:url'
|
|
4
|
+
import { createRequire } from 'node:module'
|
|
5
|
+
|
|
6
|
+
const require = createRequire(import.meta.url)
|
|
7
|
+
const here = path.dirname(fileURLToPath(import.meta.url))
|
|
8
|
+
|
|
9
|
+
export default ({ runtime, onLoaded, onAfterRender, useLogger }) => {
|
|
10
|
+
const config = runtime.config.decap ?? {}
|
|
11
|
+
|
|
12
|
+
// Where the admin lives in the HTTP space. Default '/admin' to match
|
|
13
|
+
// every Decap CMS tutorial ever written; users can override if they
|
|
14
|
+
// want it somewhere else.
|
|
15
|
+
const adminBase = config.base ?? '/admin'
|
|
16
|
+
|
|
17
|
+
// Where decap-server's POST /api/v1 handler gets mounted. Sits
|
|
18
|
+
// OUTSIDE /api so it doesn't squat on the api plugin's namespace.
|
|
19
|
+
// Default '/decap' → endpoint becomes '/decap/api/v1'.
|
|
20
|
+
const proxyBase = config.proxyBase ?? '/decap'
|
|
21
|
+
|
|
22
|
+
// 'fs' (raw filesystem) or 'git' (commits per edit, editorial
|
|
23
|
+
// workflow). Default 'fs' — simplest for solo dev.
|
|
24
|
+
const mode = config.mode ?? 'fs'
|
|
25
|
+
|
|
26
|
+
// User-provided Decap config — relative to workingFolder. Defaults
|
|
27
|
+
// to 'decap.yml'. Served at <adminBase>/config.yml.
|
|
28
|
+
const configYml = config.configYml ?? 'decap.yml'
|
|
29
|
+
|
|
30
|
+
// Whether to also copy the admin bundle into outputFolder/admin/
|
|
31
|
+
// during builds so static deployments include it. Default true
|
|
32
|
+
// because Decap is designed to be deployed alongside the static
|
|
33
|
+
// site with a git-backed backend.
|
|
34
|
+
const copyToOut = config.copyToOut !== false
|
|
35
|
+
|
|
36
|
+
onLoaded(async () => {
|
|
37
|
+
const logger = useLogger()
|
|
38
|
+
|
|
39
|
+
// No app → no live admin route. This is the normal case for a
|
|
40
|
+
// one-shot production build (`mikser` without --server): the
|
|
41
|
+
// admin still gets baked into out/ by onAfterRender below, with
|
|
42
|
+
// a remote backend (git-gateway / github) doing the writes.
|
|
43
|
+
// Only the local proxy backend needs a running server.
|
|
44
|
+
if (!runtime.options.app) {
|
|
45
|
+
logger.info(
|
|
46
|
+
'Decap: no server (runtime.options.app) — skipping live admin mount. ' +
|
|
47
|
+
'Admin will be copied into the build output%s.',
|
|
48
|
+
copyToOut ? '' : ' (disabled: copyToOut is false)'
|
|
49
|
+
)
|
|
50
|
+
return
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Resolve the decap-cms standalone bundle path via peerDep.
|
|
54
|
+
let decapDistPath
|
|
55
|
+
try {
|
|
56
|
+
const decapPkgPath = require.resolve('decap-cms/package.json')
|
|
57
|
+
decapDistPath = path.join(path.dirname(decapPkgPath), 'dist')
|
|
58
|
+
await fs.access(path.join(decapDistPath, 'decap-cms.js'))
|
|
59
|
+
} catch {
|
|
60
|
+
throw new Error(
|
|
61
|
+
'Decap plugin: cannot find decap-cms peer dependency. ' +
|
|
62
|
+
'Install it with `npm install decap-cms`.'
|
|
63
|
+
)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Resolve the decap-server middleware. registerLocalFs /
|
|
67
|
+
// registerLocalGit each attach a POST /api/v1 handler to a
|
|
68
|
+
// given Express app — exactly what we need. The package's
|
|
69
|
+
// built bundle is CJS, so the named exports come through the
|
|
70
|
+
// default import.
|
|
71
|
+
const { default: { registerLocalFs, registerLocalGit } } =
|
|
72
|
+
await import('decap-server/dist/middlewares.js')
|
|
73
|
+
|
|
74
|
+
// decap-server reads GIT_REPO_DIRECTORY from the environment
|
|
75
|
+
// (no per-call override). Point it at the working folder before
|
|
76
|
+
// registration. localFs uses the same env var as localGit.
|
|
77
|
+
const workingFolder = path.resolve(runtime.options.workingFolder)
|
|
78
|
+
process.env.GIT_REPO_DIRECTORY = workingFolder
|
|
79
|
+
|
|
80
|
+
// Lazy-import express through the host app's prototype so we
|
|
81
|
+
// get the same instance the engine created.
|
|
82
|
+
const expressApp = runtime.options.app
|
|
83
|
+
const express = expressApp.constructor.application ? null
|
|
84
|
+
: (await import('express')).default
|
|
85
|
+
if (!express) {
|
|
86
|
+
throw new Error('Decap plugin: express not available')
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// --- Proxy backend mounted on a sub-app under <proxyBase> ---
|
|
90
|
+
// The sub-app gives us route isolation (cors / json / morgan
|
|
91
|
+
// from registerCommonMiddlewares stay scoped). The endpoint
|
|
92
|
+
// becomes <proxyBase>/api/v1.
|
|
93
|
+
const proxyApp = express()
|
|
94
|
+
if (mode === 'git') await registerLocalGit(proxyApp, { logLevel: 'info' })
|
|
95
|
+
else if (mode === 'fs') await registerLocalFs(proxyApp, { logLevel: 'info' })
|
|
96
|
+
else throw new Error(`Decap plugin: unknown mode '${mode}' (expected 'fs' or 'git')`)
|
|
97
|
+
expressApp.use(proxyBase, proxyApp)
|
|
98
|
+
|
|
99
|
+
// --- Admin UI at <adminBase> ---
|
|
100
|
+
// Order matters:
|
|
101
|
+
// 1. Our admin/index.html (the bootstrap loading decap-cms.js)
|
|
102
|
+
// 2. The user's decap.yml served as config.yml
|
|
103
|
+
// 3. The decap-cms standalone bundle (decap-cms.js + assets)
|
|
104
|
+
expressApp.use(adminBase, express.static(path.join(here, 'admin')))
|
|
105
|
+
|
|
106
|
+
const configYmlAbs = path.resolve(workingFolder, configYml)
|
|
107
|
+
expressApp.get(`${adminBase}/config.yml`, async (req, res) => {
|
|
108
|
+
try {
|
|
109
|
+
const body = await fs.readFile(configYmlAbs, 'utf8')
|
|
110
|
+
res.type('text/yaml').send(body)
|
|
111
|
+
} catch (err) {
|
|
112
|
+
logger.error('Decap config not found at %s', configYmlAbs)
|
|
113
|
+
res.status(404).send(`# decap config not found at ${configYml}`)
|
|
114
|
+
}
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
expressApp.use(adminBase, express.static(decapDistPath))
|
|
118
|
+
|
|
119
|
+
logger.info('Decap admin mounted: %s (mode=%s, proxy=%s/api/v1, config=%s)',
|
|
120
|
+
adminBase, mode, proxyBase, configYml)
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
onAfterRender(async () => {
|
|
124
|
+
if (!copyToOut) return
|
|
125
|
+
const logger = useLogger()
|
|
126
|
+
|
|
127
|
+
const outAdmin = path.join(
|
|
128
|
+
path.resolve(runtime.options.outputFolder),
|
|
129
|
+
adminBase.replace(/^\//, '')
|
|
130
|
+
)
|
|
131
|
+
await fs.mkdir(outAdmin, { recursive: true })
|
|
132
|
+
|
|
133
|
+
// Copy our admin bootstrap (index.html etc.)
|
|
134
|
+
await fs.cp(path.join(here, 'admin'), outAdmin, { recursive: true })
|
|
135
|
+
|
|
136
|
+
// Copy the user's decap config alongside the bootstrap. Decap
|
|
137
|
+
// looks for ./config.yml relative to the admin HTML.
|
|
138
|
+
const configYmlAbs = path.resolve(runtime.options.workingFolder, configYml)
|
|
139
|
+
try {
|
|
140
|
+
await fs.copyFile(configYmlAbs, path.join(outAdmin, 'config.yml'))
|
|
141
|
+
} catch {
|
|
142
|
+
logger.warn('Decap: no %s to copy into out/%s', configYml, adminBase)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Copy decap-cms standalone bundle (decap-cms.js + maps + chunks)
|
|
146
|
+
try {
|
|
147
|
+
const decapPkgPath = require.resolve('decap-cms/package.json')
|
|
148
|
+
const decapDistPath = path.join(path.dirname(decapPkgPath), 'dist')
|
|
149
|
+
await fs.cp(decapDistPath, outAdmin, { recursive: true })
|
|
150
|
+
} catch {
|
|
151
|
+
logger.warn('Decap: peerDep decap-cms not installed, skipping bundle copy to out')
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
logger.info('Decap admin written: %s', outAdmin)
|
|
155
|
+
})
|
|
156
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "mikser-io-decap",
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "Mount Decap CMS inside the mikser-io express server — single process admin for file-based content",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"scripts": {},
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/almero-digital-marketing/mikser-io-decap.git"
|
|
11
|
+
},
|
|
12
|
+
"author": "",
|
|
13
|
+
"license": "MIT",
|
|
14
|
+
"bugs": {
|
|
15
|
+
"url": "https://github.com/almero-digital-marketing/mikser-io-decap/issues"
|
|
16
|
+
},
|
|
17
|
+
"homepage": "https://github.com/almero-digital-marketing/mikser-io-decap#readme",
|
|
18
|
+
"peerDependencies": {
|
|
19
|
+
"mikser-io": "^7.0.0",
|
|
20
|
+
"decap-cms": "^3.0.0"
|
|
21
|
+
},
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"decap-server": "^3.7.0"
|
|
24
|
+
}
|
|
25
|
+
}
|