create-snagset 0.0.1
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 +67 -0
- package/dist/index.js +29 -0
- package/dist/scaffold.js +106 -0
- package/dist/template/README.md +82 -0
- package/dist/template/_env.example +27 -0
- package/dist/template/_gitignore +7 -0
- package/dist/template/_package.json +16 -0
- package/dist/template/api_index.js +8 -0
- package/dist/template/compose.yml +40 -0
- package/dist/template/vercel.json +6 -0
- package/package.json +38 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Eurico Sá Fernandes
|
|
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,67 @@
|
|
|
1
|
+
# `create-snagset`
|
|
2
|
+
|
|
3
|
+
Scaffold a self-hosted **[Snagset](https://github.com/snagset/snagset)** instance
|
|
4
|
+
— click-to-comment review for your clients' live sites.
|
|
5
|
+
|
|
6
|
+
```bash
|
|
7
|
+
npx create-snagset my-review
|
|
8
|
+
cd my-review
|
|
9
|
+
docker compose up
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
Postgres is included. Open `http://localhost:8080` and follow the claim URL in
|
|
13
|
+
the logs. Already have a database? Put its URL in `.env`, then `npm install &&
|
|
14
|
+
npm start`.
|
|
15
|
+
|
|
16
|
+
## What you get
|
|
17
|
+
|
|
18
|
+
A directory with everything an instance needs and nothing it does not:
|
|
19
|
+
|
|
20
|
+
| | |
|
|
21
|
+
|---|---|
|
|
22
|
+
| `compose.yml` | The server and a Postgres, for `docker compose up` |
|
|
23
|
+
| `.env` | Configuration. **`DATABASE_URL` is the only required variable** — a signing key is generated into it for you |
|
|
24
|
+
| `package.json` | `@snagset/server` as a dependency, with `start`, `migrate` and `env` scripts |
|
|
25
|
+
| `vercel.json` + `api/index.js` | Deploy to Vercel instead, if you would rather not run anything |
|
|
26
|
+
| `README.md` | The operating manual, written for the instance you just made |
|
|
27
|
+
|
|
28
|
+
## Then what
|
|
29
|
+
|
|
30
|
+
Sign in, add a site, and paste the tag it gives you into the site you want
|
|
31
|
+
reviewed:
|
|
32
|
+
|
|
33
|
+
```html
|
|
34
|
+
<script src="http://localhost:8080/snag.js" data-snag-site="prj_…" defer></script>
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Your client opens a review link, clicks the thing that's wrong, and types.
|
|
38
|
+
**No account, no extension, no login** — the link is the whole identity. You
|
|
39
|
+
get the pin, the screenshot, the console and the route.
|
|
40
|
+
|
|
41
|
+
Prefer an import? `npm i snagset` gives you the same tag from
|
|
42
|
+
`snagset.init({ host, site })`, or the framework subpaths — `snagset/react`,
|
|
43
|
+
`snagset/vue`, `snagset/next`, `snagset/vite` (which covers Nuxt and
|
|
44
|
+
SvelteKit too), `snagset/astro`.
|
|
45
|
+
|
|
46
|
+
## What it costs to run
|
|
47
|
+
|
|
48
|
+
A Node process and a Postgres. Screenshots want an S3-compatible bucket (R2,
|
|
49
|
+
Spaces, MinIO, AWS) and notifications want a mailer — **both optional**, and
|
|
50
|
+
the product is honest without them: no bucket means annotation mode and the
|
|
51
|
+
widget says screenshots are unavailable rather than offering one that fails.
|
|
52
|
+
|
|
53
|
+
## Back up the signing key
|
|
54
|
+
|
|
55
|
+
If you did not set `SNAGSET_SECRET_KEY`, one was generated into your `.env`.
|
|
56
|
+
**Treat it as a password.** Without it every reviewer session is dead and any
|
|
57
|
+
stored credential is unreadable ciphertext.
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
**Early release.** The install path is tested end to end, but this is a `0.0.x`
|
|
62
|
+
line: expect the odd rough edge, and pin the version if you need stability.
|
|
63
|
+
Issues and notes are welcome at
|
|
64
|
+
[snagset/snagset](https://github.com/snagset/snagset).
|
|
65
|
+
|
|
66
|
+
AGPL-3.0-only. The widget you put on a client's site is
|
|
67
|
+
[`snagset`](https://www.npmjs.com/package/snagset), and it is MIT.
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { scaffold } from "./scaffold.js";
|
|
3
|
+
/**
|
|
4
|
+
* The bin. Argument handling and exit codes only — everything that touches the
|
|
5
|
+
* filesystem is in `scaffold.ts`, which has no side effects at import time and
|
|
6
|
+
* is therefore testable.
|
|
7
|
+
*/
|
|
8
|
+
const target = process.argv[2];
|
|
9
|
+
if (!target || target.startsWith("-")) {
|
|
10
|
+
process.stderr.write("usage: npx create-snagset <directory>\n\n" +
|
|
11
|
+
" npx create-snagset my-review\n" +
|
|
12
|
+
" cd my-review && docker compose up\n");
|
|
13
|
+
process.exit(2);
|
|
14
|
+
}
|
|
15
|
+
try {
|
|
16
|
+
scaffold(target);
|
|
17
|
+
}
|
|
18
|
+
catch (e) {
|
|
19
|
+
process.stderr.write(`✗ ${e instanceof Error ? e.message : String(e)}\n`);
|
|
20
|
+
process.exit(2);
|
|
21
|
+
}
|
|
22
|
+
const name = target.split("/").pop() || "snagset";
|
|
23
|
+
process.stdout.write(`\n ✓ ${name}\n\n` +
|
|
24
|
+
` cd ${target}\n` +
|
|
25
|
+
` docker compose up\n\n` +
|
|
26
|
+
` Then open http://localhost:8080 and follow the claim URL in the logs.\n` +
|
|
27
|
+
` No Docker? Put a Postgres URL in .env, then \`npm install && npm start\`.\n\n` +
|
|
28
|
+
` A signing key was generated into .env. Back it up — without it every\n` +
|
|
29
|
+
` session and stored credential is unrecoverable.\n\n`);
|
package/dist/scaffold.js
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join, resolve } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { randomBytes } from "node:crypto";
|
|
5
|
+
/**
|
|
6
|
+
* `npx create-snagset` — D63's self-host, the `create-next-app` model.
|
|
7
|
+
*
|
|
8
|
+
* ── WHY A SCAFFOLDER AND NOT `git clone` ──
|
|
9
|
+
*
|
|
10
|
+
* FasterFixes ships `git clone` + `pnpm install` + a build, and it is the
|
|
11
|
+
* weakest part of their docs: it makes the operator your build engineer. They
|
|
12
|
+
* own the Node version, the lockfile and the build step, and every upgrade is
|
|
13
|
+
* a pull, a reinstall and a rebuild against a tree that has moved underneath
|
|
14
|
+
* them. Sixteen environment variables and four mandatory external services
|
|
15
|
+
* before the first run.
|
|
16
|
+
*
|
|
17
|
+
* What this writes instead is a small repository the operator OWNS — their
|
|
18
|
+
* `.env`, their compose file, their deploy config — that depends on
|
|
19
|
+
* `@snagset/server` like any other package. Upgrading is `npm update`. There
|
|
20
|
+
* is no build. Our source is not in their tree unless they want it, and when
|
|
21
|
+
* they do, it is AGPL and the repository is public.
|
|
22
|
+
*
|
|
23
|
+
* ── WHY NOT `npm install @snagset/server` INTO A DIRECTORY THEY INVENTED ──
|
|
24
|
+
*
|
|
25
|
+
* Because the interesting files are not the dependency. A working self-host is
|
|
26
|
+
* a compose file with Postgres in it, a `.env` with the one required variable
|
|
27
|
+
* already spelled, a Vercel config, a serverless entry point, and a README
|
|
28
|
+
* that says which of those matter. `npm install` gives you a `node_modules`
|
|
29
|
+
* and a blank page; measured on a cold room, the install is 5 seconds and the
|
|
30
|
+
* remaining friction is entirely "what do I write, and where".
|
|
31
|
+
*/
|
|
32
|
+
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
33
|
+
/**
|
|
34
|
+
* `dist/template` beside the compiled entry (see scripts/copy-template.mjs),
|
|
35
|
+
* falling back to `../template` so the package runs from source in this repo.
|
|
36
|
+
*
|
|
37
|
+
* The fallback is the honest version of "it works on my machine": without it
|
|
38
|
+
* every local run and every test would read a directory that only exists after
|
|
39
|
+
* a build, and the first person to try `node src/index.ts` would get an empty
|
|
40
|
+
* scaffold with no error.
|
|
41
|
+
*/
|
|
42
|
+
const TEMPLATE = existsSync(join(HERE, "template"))
|
|
43
|
+
? join(HERE, "template")
|
|
44
|
+
: join(HERE, "..", "template");
|
|
45
|
+
/**
|
|
46
|
+
* `_package.json` on disk, `package.json` when written.
|
|
47
|
+
*
|
|
48
|
+
* npm STRIPS a `.gitignore` from a published tarball and rewrites a nested
|
|
49
|
+
* `package.json`, which is why these two ship under other names. The `_`
|
|
50
|
+
* prefix is the convention `create-next-app` and `create-vite` both use, for
|
|
51
|
+
* exactly this reason, and a template that ships without its `.gitignore`
|
|
52
|
+
* produces a scaffold whose first `git add .` commits `node_modules` and a
|
|
53
|
+
* `.env` holding a database password.
|
|
54
|
+
*/
|
|
55
|
+
export const RENAME = {
|
|
56
|
+
"_package.json": "package.json",
|
|
57
|
+
_gitignore: ".gitignore",
|
|
58
|
+
"_env.example": ".env.example",
|
|
59
|
+
"api_index.js": "api/index.js",
|
|
60
|
+
};
|
|
61
|
+
/**
|
|
62
|
+
* Scaffold into `dir`. Pure enough to test: it takes and returns paths, throws
|
|
63
|
+
* a sentence rather than calling `process.exit`, and writes nothing outside
|
|
64
|
+
* the directory it was given.
|
|
65
|
+
*
|
|
66
|
+
* Split from the bin for one specific reason — the first version guarded
|
|
67
|
+
* `main()` with `if (/create-snagset/.test(process.argv[1]))` so a test could
|
|
68
|
+
* import it, and the test file's own path contains `create-snagset`, so
|
|
69
|
+
* importing it would have scaffolded into the test runner's working directory.
|
|
70
|
+
* A module with side effects at import time cannot be tested; the fix is not a
|
|
71
|
+
* cleverer guard.
|
|
72
|
+
*/
|
|
73
|
+
export function scaffold(target, cwd = process.cwd()) {
|
|
74
|
+
const dir = resolve(cwd, target);
|
|
75
|
+
// A non-empty directory is refused rather than merged into. Merging is how a
|
|
76
|
+
// scaffolder overwrites somebody's `.env`, and there is no undo for that.
|
|
77
|
+
if (existsSync(dir) && readdirSync(dir).length > 0) {
|
|
78
|
+
throw new Error(`${target} already exists and is not empty.`);
|
|
79
|
+
}
|
|
80
|
+
mkdirSync(join(dir, "api"), { recursive: true });
|
|
81
|
+
const name = target.split("/").pop() || "snagset";
|
|
82
|
+
// Pinned to THIS scaffolder's version, so `create-snagset@1.4.2` produces a
|
|
83
|
+
// repo on `@snagset/server@1.4.2`. A caret here would let a fresh scaffold
|
|
84
|
+
// resolve to a server this template has never been run against.
|
|
85
|
+
const version = JSON.parse(readFileSync(join(HERE, "..", "package.json"), "utf8")).version;
|
|
86
|
+
for (const entry of readdirSync(TEMPLATE)) {
|
|
87
|
+
const out = RENAME[entry] ?? entry;
|
|
88
|
+
const body = readFileSync(join(TEMPLATE, entry), "utf8")
|
|
89
|
+
.replaceAll("__NAME__", name)
|
|
90
|
+
.replaceAll("__VERSION__", version);
|
|
91
|
+
writeFileSync(join(dir, out), body);
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* `.env` is written, not just `.env.example`, and with a REAL signing key.
|
|
95
|
+
*
|
|
96
|
+
* The alternative — ship an example and tell them to copy it — adds a step
|
|
97
|
+
* between the scaffold and the first run, which is the step where an install
|
|
98
|
+
* stalls. And generating the key here rather than letting the server do it
|
|
99
|
+
* on first boot matters for the deploy case: 08 §4.2's generate-to-disk is
|
|
100
|
+
* bin-only, so an operator who scaffolds locally and deploys to a platform
|
|
101
|
+
* would otherwise discover on the far side that they had no key to set.
|
|
102
|
+
*/
|
|
103
|
+
const env = readFileSync(join(dir, ".env.example"), "utf8").replace("# SNAGSET_SECRET_KEY=", `SNAGSET_SECRET_KEY=${randomBytes(32).toString("base64url")}`);
|
|
104
|
+
writeFileSync(join(dir, ".env"), env);
|
|
105
|
+
return dir;
|
|
106
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# __NAME__
|
|
2
|
+
|
|
3
|
+
A self-hosted [Snagset](https://github.com/snagset/snagset) instance. Click-to-comment
|
|
4
|
+
review for your clients' live sites.
|
|
5
|
+
|
|
6
|
+
## Run it
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
docker compose up
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
Postgres is included. Open http://localhost:8080 and follow the claim URL
|
|
13
|
+
printed in the logs.
|
|
14
|
+
|
|
15
|
+
**Already have a database?** Put its URL in `.env`, then:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install
|
|
19
|
+
npm start
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Add a site
|
|
23
|
+
|
|
24
|
+
Sign in, open **Sites**, and add one. You get a script tag:
|
|
25
|
+
|
|
26
|
+
```html
|
|
27
|
+
<script src="http://localhost:8080/snag.js" data-snag-site="prj_…" defer></script>
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Paste it into the site you want reviewed, before `</body>`. Or install the npm
|
|
31
|
+
package — `npm i snagset` — and `snagset.init({ host, site })`, or one of the
|
|
32
|
+
framework subpaths: `snagset/react`, `snagset/vue`, `snagset/next`,
|
|
33
|
+
`snagset/vite` (which also covers Nuxt and SvelteKit), `snagset/astro`. They
|
|
34
|
+
all write the same tag.
|
|
35
|
+
|
|
36
|
+
**The site's address has to be on the allow-list.** That is the field beside
|
|
37
|
+
the name when you add it. A tag on an address that is not listed loads and is
|
|
38
|
+
ignored, which is the hardest failure here to spot from the outside.
|
|
39
|
+
|
|
40
|
+
## Configure
|
|
41
|
+
|
|
42
|
+
`.env`. Only `DATABASE_URL` is required.
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
npm run env # every variable this build accepts, with its default
|
|
46
|
+
npm run env MAIL # just the ones matching MAIL
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Screenshots need an S3-compatible bucket (R2, Spaces, MinIO, AWS).
|
|
50
|
+
Notifications need a mailer. Both are optional: with no bucket you get
|
|
51
|
+
annotation mode and the widget says screenshots are unavailable rather than
|
|
52
|
+
offering one that fails; with no mailer, notifications are off and the
|
|
53
|
+
dashboard says so.
|
|
54
|
+
|
|
55
|
+
## Deploy it
|
|
56
|
+
|
|
57
|
+
**Vercel.** `vercel.json` and `api/index.js` are here already. Add a Postgres
|
|
58
|
+
from the marketplace, set `SNAGSET_SECRET_KEY` (required on serverless — the
|
|
59
|
+
filesystem is ephemeral, so a generated key would differ per instance and
|
|
60
|
+
nothing would decrypt), and add a release command:
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
npx snagset migrate
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
**Anything that runs Node.** `npm start`. It serves the API, the dashboard and
|
|
67
|
+
the widget on one port.
|
|
68
|
+
|
|
69
|
+
## Upgrading
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
npm update @snagset/server
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Migrations run on boot. There is no build step to redo.
|
|
76
|
+
|
|
77
|
+
## Back up your signing key
|
|
78
|
+
|
|
79
|
+
If you did not set `SNAGSET_SECRET_KEY`, the server generated one at
|
|
80
|
+
`.snagset/secret.key` on first boot. **Treat it as a password.** Without it,
|
|
81
|
+
every reviewer session is dead and any stored credential is unreadable
|
|
82
|
+
ciphertext.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# ── Required ───────────────────────────────────────────────────────────
|
|
2
|
+
# Any Postgres 14+. `docker compose up` provides one at this URL.
|
|
3
|
+
DATABASE_URL=postgres://snagset:snagset@localhost:5432/snagset
|
|
4
|
+
|
|
5
|
+
# ── Strongly recommended ───────────────────────────────────────────────
|
|
6
|
+
# 32 random bytes, base64url. Generated to .snagset/secret.key on first boot
|
|
7
|
+
# if unset — which is fine on one machine and wrong everywhere else, because
|
|
8
|
+
# each instance would invent a different one.
|
|
9
|
+
#
|
|
10
|
+
# node -e "console.log(require('crypto').randomBytes(32).toString('base64url'))"
|
|
11
|
+
# SNAGSET_SECRET_KEY=
|
|
12
|
+
|
|
13
|
+
# The public origin. Review links and the claim URL are built from it.
|
|
14
|
+
# SNAGSET_BASE_URL=http://localhost:8080
|
|
15
|
+
|
|
16
|
+
# ── Claim it without a browser (optional) ──────────────────────────────
|
|
17
|
+
# Set these and the first `npm start` creates the owner and the first site.
|
|
18
|
+
# All of it is ignored once an owner exists, so it is safe to leave here.
|
|
19
|
+
# SNAGSET_BOOTSTRAP_OWNER_EMAIL=you@example.com
|
|
20
|
+
# SNAGSET_BOOTSTRAP_OWNER_PASSWORD_FILE=./owner-password
|
|
21
|
+
# SNAGSET_BOOTSTRAP_PROJECT_NAME=First site
|
|
22
|
+
# SNAGSET_BOOTSTRAP_PROJECT_ORIGINS=https://staging.example.com,http://localhost:3000
|
|
23
|
+
|
|
24
|
+
# ── Everything else ────────────────────────────────────────────────────
|
|
25
|
+
# `npm run env` prints every variable this build accepts, with its default.
|
|
26
|
+
# Screenshots need an S3-compatible bucket; notifications need a mailer.
|
|
27
|
+
# Both are optional and the product is honest about running without them.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
node_modules/
|
|
2
|
+
# Your database URL and your signing key live here. Never commit it.
|
|
3
|
+
.env
|
|
4
|
+
.env.local
|
|
5
|
+
# The signing key the server generates on first boot if you did not set one.
|
|
6
|
+
# Back this up; without it every session and encrypted credential is lost.
|
|
7
|
+
.snagset/
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "__NAME__",
|
|
3
|
+
"private": true,
|
|
4
|
+
"type": "module",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"start": "snagset serve",
|
|
7
|
+
"migrate": "snagset migrate",
|
|
8
|
+
"bootstrap": "snagset bootstrap",
|
|
9
|
+
"env": "snagset env",
|
|
10
|
+
"sites": "snagset projects"
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"@snagset/server": "__VERSION__"
|
|
14
|
+
},
|
|
15
|
+
"engines": { "node": ">=22" }
|
|
16
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// The serverless entry — one line, because `@snagset/server` exports the
|
|
2
|
+
// Web-standard `fetch(Request) => Response` handler every platform accepts.
|
|
3
|
+
//
|
|
4
|
+
// Vercel, Netlify Functions, Cloudflare Workers and Deno Deploy all take this
|
|
5
|
+
// shape. Migrations run as a DEPLOY step, not on cold start (see README) —
|
|
6
|
+
// migrating on a request's critical path would put every instance behind one
|
|
7
|
+
// applying DDL, on somebody's first page load.
|
|
8
|
+
export { default } from "@snagset/server/serverless";
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# One command: `docker compose up`.
|
|
2
|
+
#
|
|
3
|
+
# Postgres is included so there is nothing to provision before the first run.
|
|
4
|
+
# Point DATABASE_URL at a managed database instead — Neon, Supabase, RDS — and
|
|
5
|
+
# delete the `db` service; nothing else changes.
|
|
6
|
+
services:
|
|
7
|
+
db:
|
|
8
|
+
image: postgres:18-alpine
|
|
9
|
+
environment:
|
|
10
|
+
POSTGRES_USER: snagset
|
|
11
|
+
POSTGRES_PASSWORD: snagset
|
|
12
|
+
POSTGRES_DB: snagset
|
|
13
|
+
ports: ["5432:5432"]
|
|
14
|
+
volumes: ["snagset-db:/var/lib/postgresql/data"]
|
|
15
|
+
healthcheck:
|
|
16
|
+
# The server migrates on boot, so it must not start against a database
|
|
17
|
+
# that is still initialising — the advisory lock would be taken against
|
|
18
|
+
# a socket that is not accepting connections yet.
|
|
19
|
+
test: ["CMD-SHELL", "pg_isready -U snagset"]
|
|
20
|
+
interval: 2s
|
|
21
|
+
timeout: 3s
|
|
22
|
+
retries: 20
|
|
23
|
+
|
|
24
|
+
snagset:
|
|
25
|
+
image: node:22-alpine
|
|
26
|
+
working_dir: /app
|
|
27
|
+
volumes: ["./:/app"]
|
|
28
|
+
command: sh -c "npm install --omit=dev && npm start"
|
|
29
|
+
env_file: [.env]
|
|
30
|
+
environment:
|
|
31
|
+
# Inside the compose network the database is `db`, not `localhost` —
|
|
32
|
+
# overridden here so the same .env works for `npm start` on the host.
|
|
33
|
+
DATABASE_URL: postgres://snagset:snagset@db:5432/snagset
|
|
34
|
+
SNAGSET_BIND_ADDRESS: 0.0.0.0
|
|
35
|
+
ports: ["8080:8080"]
|
|
36
|
+
depends_on:
|
|
37
|
+
db: { condition: service_healthy }
|
|
38
|
+
|
|
39
|
+
volumes:
|
|
40
|
+
snagset-db:
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "create-snagset",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"private": false,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"description": "Scaffold a self-hosted Snagset instance — a repository you own, with Docker Compose, a Vercel config, and one variable to fill in.",
|
|
7
|
+
"license": "AGPL-3.0-only",
|
|
8
|
+
"bin": {
|
|
9
|
+
"create-snagset": "./dist/index.js"
|
|
10
|
+
},
|
|
11
|
+
"scripts": {
|
|
12
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
13
|
+
"test": "node --experimental-strip-types --test test/*.test.ts",
|
|
14
|
+
"build": "tsc -p tsconfig.build.json && node scripts/copy-template.mjs",
|
|
15
|
+
"prepack": "tsc -p tsconfig.build.json && node scripts/copy-template.mjs && node ../../tools/publish-manifest.mjs strip",
|
|
16
|
+
"postpack": "node ../../tools/publish-manifest.mjs restore"
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"dist",
|
|
20
|
+
"README.md",
|
|
21
|
+
"LICENSE"
|
|
22
|
+
],
|
|
23
|
+
"engines": {
|
|
24
|
+
"node": ">=20"
|
|
25
|
+
},
|
|
26
|
+
"publishConfig": {
|
|
27
|
+
"access": "public"
|
|
28
|
+
},
|
|
29
|
+
"repository": {
|
|
30
|
+
"type": "git",
|
|
31
|
+
"url": "git+https://github.com/snagset/snagset.git",
|
|
32
|
+
"directory": "packages/create-snagset"
|
|
33
|
+
},
|
|
34
|
+
"homepage": "https://github.com/snagset/snagset",
|
|
35
|
+
"bugs": {
|
|
36
|
+
"url": "https://github.com/snagset/snagset/issues"
|
|
37
|
+
}
|
|
38
|
+
}
|