@clovnet/plugin-cli 0.2.2

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.
Files changed (44) hide show
  1. package/CHANGELOG.md +75 -0
  2. package/LICENSE +21 -0
  3. package/README.md +92 -0
  4. package/bin/cwe-plugin.mjs +14 -0
  5. package/dist/cli.js +10400 -0
  6. package/package.json +62 -0
  7. package/templates/blank/README.md +60 -0
  8. package/templates/blank/_gitignore +3 -0
  9. package/templates/blank/cwe-plugin.json +9 -0
  10. package/templates/blank/package.json +31 -0
  11. package/templates/blank/settings.dev.json +8 -0
  12. package/templates/blank/src/handlers/hooks.ts +29 -0
  13. package/templates/blank/src/handlers/jobs.ts +9 -0
  14. package/templates/blank/src/handlers/routes.ts +44 -0
  15. package/templates/blank/src/index.ts +68 -0
  16. package/templates/blank/src/settings.ts +23 -0
  17. package/templates/blank/test/hooks.test.ts +43 -0
  18. package/templates/blank/tsconfig.json +14 -0
  19. package/templates/cashback/README.md +61 -0
  20. package/templates/cashback/_gitignore +3 -0
  21. package/templates/cashback/cwe-plugin.json +9 -0
  22. package/templates/cashback/package.json +31 -0
  23. package/templates/cashback/settings.dev.json +9 -0
  24. package/templates/cashback/src/handlers/hooks.ts +19 -0
  25. package/templates/cashback/src/handlers/jobs.ts +23 -0
  26. package/templates/cashback/src/handlers/routes.ts +188 -0
  27. package/templates/cashback/src/index.ts +128 -0
  28. package/templates/cashback/src/settings.ts +29 -0
  29. package/templates/cashback/test/hooks.test.ts +83 -0
  30. package/templates/cashback/tsconfig.json +14 -0
  31. package/templates/provider-skeleton/README.md +63 -0
  32. package/templates/provider-skeleton/_gitignore +3 -0
  33. package/templates/provider-skeleton/cwe-plugin.json +9 -0
  34. package/templates/provider-skeleton/package.json +31 -0
  35. package/templates/provider-skeleton/settings.dev.json +10 -0
  36. package/templates/provider-skeleton/src/adapter.ts +164 -0
  37. package/templates/provider-skeleton/src/fake-backend.ts +56 -0
  38. package/templates/provider-skeleton/src/handlers/hooks.ts +30 -0
  39. package/templates/provider-skeleton/src/handlers/jobs.ts +16 -0
  40. package/templates/provider-skeleton/src/handlers/routes.ts +128 -0
  41. package/templates/provider-skeleton/src/index.ts +83 -0
  42. package/templates/provider-skeleton/src/settings.ts +37 -0
  43. package/templates/provider-skeleton/test/hooks.test.ts +83 -0
  44. package/templates/provider-skeleton/tsconfig.json +14 -0
package/package.json ADDED
@@ -0,0 +1,62 @@
1
+ {
2
+ "name": "@clovnet/plugin-cli",
3
+ "version": "0.2.2",
4
+ "description": "Clovnet plugin developer CLI \u2014 create, dev-loop (hot reload + live logs), lifecycle-test and publish plugins against a Clovnet runtime.",
5
+ "license": "MIT",
6
+ "private": false,
7
+ "type": "module",
8
+ "engines": {
9
+ "node": ">=20"
10
+ },
11
+ "bin": {
12
+ "clovnet-plugin": "bin/cwe-plugin.mjs",
13
+ "cwe-plugin": "bin/cwe-plugin.mjs"
14
+ },
15
+ "files": [
16
+ "bin",
17
+ "dist",
18
+ "templates",
19
+ "README.md",
20
+ "CHANGELOG.md",
21
+ "LICENSE"
22
+ ],
23
+ "scripts": {
24
+ "build": "tsup",
25
+ "test": "pnpm build && tsx --test test/*.test.ts",
26
+ "verify": "pnpm build && tsx --test test/*.test.ts",
27
+ "prepack": "pnpm build"
28
+ },
29
+ "dependencies": {
30
+ "esbuild": "^0.24.0"
31
+ },
32
+ "devDependencies": {
33
+ "@clovnet/plugin-sdk": "workspace:*",
34
+ "@cwe/shared": "workspace:*",
35
+ "@types/node": "^20.16.5",
36
+ "commander": "^12.1.0",
37
+ "tsup": "^8.3.0",
38
+ "tsx": "^4.19.1",
39
+ "typescript": "^5.6.2",
40
+ "zod": "^3.23.8"
41
+ },
42
+ "keywords": [
43
+ "casinowebengine",
44
+ "cwe",
45
+ "plugin",
46
+ "cli",
47
+ "igaming"
48
+ ],
49
+ "publishConfig": {
50
+ "access": "public"
51
+ },
52
+ "repository": {
53
+ "type": "git",
54
+ "url": "git+https://github.com/Clovnet/casino-runtime-core.git",
55
+ "directory": "packages/plugin-cli"
56
+ },
57
+ "homepage": "https://clovnet.com",
58
+ "bugs": {
59
+ "url": "https://github.com/Clovnet/casino-runtime-core/issues"
60
+ },
61
+ "author": "Clovnet"
62
+ }
@@ -0,0 +1,60 @@
1
+ # __PLUGIN_NAME__
2
+
3
+ A CasinoWebEngine plugin, scaffolded with `cwe-plugin create`.
4
+
5
+ ## The golden path
6
+
7
+ ```bash
8
+ pnpm create cwe-plugin my-cashback # scaffold: manifest, handlers, tests, config
9
+ cd my-cashback && pnpm install
10
+
11
+ pnpm dev # = cwe-plugin dev
12
+ # → first contact with the scaffold's preset `local` environment: the handshake verifies
13
+ # class=local + devHarness=true and pins its fingerprint → green banner
14
+ # → doctor passes → sandbox tenant resolved → plugin installed+enabled (prompted)
15
+ # → esbuild watch starts, bundle hot-loaded, log stream attached
16
+ # → edit src/handlers/routes.ts, save → rebuilt + hot-reloaded in <2s, logs streaming live
17
+
18
+ # in the dev session (keyboard): i install · e enable · c configure · x disable · u uninstall
19
+ # r reload · d doctor · l clear · q quit
20
+ # each lifecycle key prints a full hook trace: capability calls, events, audit refs, state
21
+
22
+ pnpm test # unit tests on hooks/handlers via the SDK test kit
23
+
24
+ cwe-plugin lifecycle cycle # conformance sweep: install→enable→configure→disable→
25
+ # uninstall→reinstall, asserting idempotency + clean teardown
26
+
27
+ cwe-plugin login # only needed for shared dev clusters / publishing
28
+ cwe-plugin env add dev-eu https://dev-eu.cwe.dev && cwe-plugin env use dev-eu
29
+ pnpm dev # same loop against the shared cluster, per-dev sandbox
30
+
31
+ pnpm publish:dev # = cwe-plugin publish — always the dev channel:
32
+ # doctor + codegen-diff gate + publish
33
+ ```
34
+
35
+ ## What's in this template
36
+
37
+ | File | Purpose |
38
+ |---|---|
39
+ | `cwe-plugin.json` | project config: plugin key, entry, environments (committed — no secrets, ever) |
40
+ | `src/index.ts` | `definePlugin({ manifest, hooks, handlers })` — the whole platform contract |
41
+ | `src/settings.ts` | Zod-typed tenant settings (one secret field included) |
42
+ | `src/handlers/hooks.ts` | `onInstall` / `onConfigure` / `onUninstall` with commented examples |
43
+ | `src/handlers/routes.ts` | one public route + one player action |
44
+ | `src/handlers/jobs.ts` | one cron job (dry-run with `cwe-plugin jobs run heartbeat`) |
45
+ | `settings.dev.json` | sandbox settings applied by `cwe-plugin lifecycle configure` / the `c` key |
46
+ | `test/hooks.test.ts` | in-memory test-context examples — breakpoints work under `vitest --inspect` |
47
+
48
+ ## Useful commands
49
+
50
+ ```bash
51
+ cwe-plugin env status # am I pointed at a dev environment? auth? sandbox state?
52
+ cwe-plugin invoke GET /info # execute a route without curl (add --surface player --player p1)
53
+ cwe-plugin settings set greeting=hi # validated locally against the schema before it is sent
54
+ cwe-plugin logs --level warn # standalone log tail for a second terminal
55
+ cwe-plugin doctor --remote # full server-side validation
56
+ ```
57
+
58
+ Machine-local state (pinned environment fingerprints, log cursors) lives in `.cwe/` — gitignored
59
+ and always safe to delete. Credentials never touch the repo: `cwe-plugin login` stores them in the
60
+ OS keychain.
@@ -0,0 +1,3 @@
1
+ node_modules/
2
+ dist/
3
+ .cwe/
@@ -0,0 +1,9 @@
1
+ {
2
+ "$schema": "https://cwe.dev/schemas/cwe-plugin.json",
3
+ "plugin": "__PLUGIN_KEY__",
4
+ "entry": "src/index.ts",
5
+ "environments": {
6
+ "local": { "runtimeUrl": "http://localhost:3000" }
7
+ },
8
+ "defaultEnvironment": "local"
9
+ }
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "cwe-plugin-__PLUGIN_KEY__",
3
+ "version": "0.1.0",
4
+ "description": "__PLUGIN_NAME__ \u2014 a CasinoWebEngine plugin",
5
+ "private": true,
6
+ "type": "module",
7
+ "engines": {
8
+ "node": ">=20"
9
+ },
10
+ "scripts": {
11
+ "dev": "cwe-plugin dev",
12
+ "doctor": "cwe-plugin doctor",
13
+ "test": "vitest run",
14
+ "build": "cwe-plugin build",
15
+ "codegen": "cwe-plugin codegen client",
16
+ "publish:dev": "cwe-plugin publish"
17
+ },
18
+ "dependencies": {
19
+ "@clovnet/plugin-sdk": "^0.1.4"
20
+ },
21
+ "peerDependencies": {
22
+ "zod": "^3.23.0"
23
+ },
24
+ "devDependencies": {
25
+ "@clovnet/plugin-cli": "^0.2.0",
26
+ "@types/node": "^20.16.5",
27
+ "typescript": "^5.6.2",
28
+ "vitest": "^2.1.1",
29
+ "zod": "^3.23.8"
30
+ }
31
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "values": {
3
+ "greeting": "Hello from the sandbox"
4
+ },
5
+ "secrets": {
6
+ "apiKey": "dev-api-key"
7
+ }
8
+ }
@@ -0,0 +1,29 @@
1
+ import type { PluginContext } from "@clovnet/plugin-sdk";
2
+
3
+ /**
4
+ * Lifecycle hooks. The platform runs each with a 5s watchdog (lifted to 10
5
+ * minutes under `cwe-plugin lifecycle <action> --debug` so breakpoints work);
6
+ * a throwing hook marks the plugin `errored` — it never corrupts core data.
7
+ * Hooks must be idempotent: `cwe-plugin lifecycle cycle` asserts a re-run of
8
+ * every action is a no-op.
9
+ */
10
+ export async function onInstall(ctx: PluginContext): Promise<void> {
11
+ ctx.logger.info("__PLUGIN_KEY__.installed");
12
+ // Seed data here (idempotently), e.g.:
13
+ // await ctx.datasets.collection("records").put("welcome", { id: "welcome" });
14
+ // For long-running seeds declare `install: { seedTask: "seed" }` in the
15
+ // manifest and implement it under handlers.tasks — it runs async with
16
+ // checkpoints instead of blocking the install.
17
+ }
18
+
19
+ export async function onConfigure(ctx: PluginContext): Promise<void> {
20
+ // Runs after settings were validated and saved for the tenant.
21
+ const { greeting } = ctx.settings.get<{ greeting?: string }>();
22
+ ctx.logger.info("__PLUGIN_KEY__.configured", { greetingLength: greeting?.length ?? 0 });
23
+ }
24
+
25
+ export async function onUninstall(ctx: PluginContext): Promise<void> {
26
+ // Datasets are archived automatically on uninstall (and restored if the
27
+ // plugin is reinstalled) — only clean up EXTERNAL state here.
28
+ ctx.logger.info("__PLUGIN_KEY__.uninstalled");
29
+ }
@@ -0,0 +1,9 @@
1
+ import type { PluginContext } from "@clovnet/plugin-sdk";
2
+
3
+ /**
4
+ * Cron jobs run in the worker process on the declared schedule. Dry-run any
5
+ * time with `cwe-plugin jobs run heartbeat` — output streams into `pnpm dev`.
6
+ */
7
+ export async function heartbeat(ctx: PluginContext): Promise<void> {
8
+ ctx.logger.info("__PLUGIN_KEY__.heartbeat", { at: new Date().toISOString() });
9
+ }
@@ -0,0 +1,44 @@
1
+ import { z } from "zod";
2
+ import type { PluginContext, PluginRequest } from "@clovnet/plugin-sdk";
3
+
4
+ /**
5
+ * Route handlers. Surfaces:
6
+ * public → /api/ext/__PLUGIN_KEY__/… (no auth)
7
+ * player → /api/ext/__PLUGIN_KEY__/… (player session required)
8
+ * admin → backoffice-only (staff RBAC)
9
+ * callback → server-to-server webhooks (verifySignature)
10
+ * Try them instantly: `cwe-plugin invoke GET /info` ·
11
+ * `cwe-plugin invoke GET /me --surface player --player p1`.
12
+ */
13
+ export const infoOutputSchema = z.object({
14
+ plugin: z.string(),
15
+ greeting: z.string(),
16
+ });
17
+
18
+ export async function info(_req: PluginRequest, ctx: PluginContext) {
19
+ const settings = ctx.settings.get<{ greeting?: string }>();
20
+ return {
21
+ body: {
22
+ plugin: "__PLUGIN_KEY__",
23
+ greeting: settings.greeting ?? "Hello from __PLUGIN_NAME__",
24
+ },
25
+ };
26
+ }
27
+
28
+ export const meOutputSchema = z.object({
29
+ playerId: z.string(),
30
+ pinged: z.literal(true),
31
+ });
32
+
33
+ /** A player action (declared in manifest.actions → shows up in the SDK
34
+ * extension catalog and the generated typed client). */
35
+ export async function getMe(req: PluginRequest, ctx: PluginContext) {
36
+ const playerId = req.player?.id;
37
+ if (!playerId) return { status: 401, body: { error: { code: "UNAUTHORIZED" } } };
38
+ await ctx.events.emit({
39
+ name: "plugin.__PLUGIN_KEY__.pinged",
40
+ playerId,
41
+ payload: { at: new Date().toISOString() },
42
+ });
43
+ return { body: { playerId, pinged: true as const } };
44
+ }
@@ -0,0 +1,68 @@
1
+ import { definePlugin } from "@clovnet/plugin-sdk";
2
+ import { settings } from "./settings.js";
3
+ import { onConfigure, onInstall, onUninstall } from "./handlers/hooks.js";
4
+ import { getMe, info, infoOutputSchema, meOutputSchema } from "./handlers/routes.js";
5
+ import { heartbeat } from "./handlers/jobs.js";
6
+
7
+ /**
8
+ * __PLUGIN_NAME__ — scaffolded from the `blank` template.
9
+ *
10
+ * The manifest is the plugin's entire contract with the platform: routes,
11
+ * jobs, datasets, permissions, settings. Everything NOT declared here is
12
+ * denied at runtime (and `cwe-plugin doctor` catches drift before the host
13
+ * does). Changing the manifest in `pnpm dev` triggers a reinstall prompt —
14
+ * handler-only edits hot-reload in under 2s.
15
+ */
16
+ export default definePlugin({
17
+ manifest: {
18
+ key: "__PLUGIN_KEY__",
19
+ name: "__PLUGIN_NAME__",
20
+ author: "you",
21
+ version: "0.1.0",
22
+ kind: "__PLUGIN_KIND__",
23
+ runtimeCompat: ">=0.1.0 <0.2.0",
24
+ description: "__PLUGIN_NAME__ — scaffolded with cwe-plugin create.",
25
+ permissions: {
26
+ // Explicit allowlists — commands/events not listed here are denied.
27
+ commands: [],
28
+ events: {
29
+ subscribe: [],
30
+ emit: ["plugin.__PLUGIN_KEY__.pinged"],
31
+ },
32
+ },
33
+ settings,
34
+ routes: [
35
+ {
36
+ method: "GET",
37
+ path: "/info",
38
+ surface: "public",
39
+ handler: "info",
40
+ output: infoOutputSchema,
41
+ rateLimit: { windowSec: 60, max: 120 },
42
+ },
43
+ {
44
+ method: "GET",
45
+ path: "/me",
46
+ surface: "player",
47
+ handler: "getMe",
48
+ output: meOutputSchema,
49
+ },
50
+ ],
51
+ actions: [
52
+ {
53
+ key: "getMe",
54
+ route: "GET /me",
55
+ title: "Ping as the current player",
56
+ kind: "query",
57
+ },
58
+ ],
59
+ jobs: {
60
+ heartbeat: { schedule: "0 6 * * *", handler: "heartbeat", timeoutSec: 60 },
61
+ },
62
+ },
63
+ hooks: { onInstall, onConfigure, onUninstall },
64
+ handlers: {
65
+ routes: { info, getMe },
66
+ jobs: { heartbeat },
67
+ },
68
+ });
@@ -0,0 +1,23 @@
1
+ import { z } from "zod";
2
+ import { settingsField, type PluginSettingsSchema } from "@clovnet/plugin-sdk";
3
+
4
+ /**
5
+ * Typed per-tenant settings. Each field pairs backoffice UI metadata with the
6
+ * Zod validator the platform enforces at the boundary. Secret fields are
7
+ * stored encrypted, are write-only over the API and never appear in logs.
8
+ */
9
+ export const settings: PluginSettingsSchema = {
10
+ fields: {
11
+ greeting: settingsField.string({
12
+ label: "Greeting",
13
+ description: "Returned by the public /info route.",
14
+ default: "Hello from __PLUGIN_NAME__",
15
+ zod: z.string().min(1).max(120),
16
+ }),
17
+ apiKey: settingsField.string({
18
+ label: "API key",
19
+ description: "Example write-only secret — read it with ctx.secrets.get('apiKey').",
20
+ secret: true,
21
+ }),
22
+ },
23
+ };
@@ -0,0 +1,43 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { createTestContext } from "@clovnet/plugin-sdk/testing";
3
+ import type { PluginRequest } from "@clovnet/plugin-sdk";
4
+ import plugin from "../src/index.js";
5
+ import { getMe } from "../src/handlers/routes.js";
6
+
7
+ /**
8
+ * Unit tests on the in-memory test context — no runtime, no docker, and
9
+ * `vitest --inspect` breakpoints inside hooks/handlers just work. The fake
10
+ * enforces the SAME permission rules as production: an undeclared command,
11
+ * a foreign dataset or a non-allowlisted host throws here too.
12
+ */
13
+ const asRequest = (partial: Partial<PluginRequest>): PluginRequest =>
14
+ ({ params: {}, query: {}, headers: {}, ...partial }) as PluginRequest;
15
+
16
+ describe("__PLUGIN_KEY__", () => {
17
+ it("installs and configures cleanly (idempotent)", async () => {
18
+ const t = createTestContext(plugin, {
19
+ tenantId: "t-test",
20
+ settings: { greeting: "Hi!" },
21
+ secrets: { apiKey: "test-key" },
22
+ });
23
+ await t.runHook("onInstall");
24
+ await t.runHook("onInstall"); // hooks must tolerate re-runs
25
+ await t.runHook("onConfigure");
26
+ expect(t.recorder.commands).toHaveLength(0); // this template moves no money
27
+ });
28
+
29
+ it("emits the ping event for the current player", async () => {
30
+ const t = createTestContext(plugin, { settings: { greeting: "Hi!" } });
31
+ const response = await getMe(asRequest({ player: { id: "p1" } }), t.ctx);
32
+ expect(response.body).toMatchObject({ playerId: "p1", pinged: true });
33
+ expect(t.recorder.events).toHaveLength(1);
34
+ expect(t.recorder.events[0]?.name).toBe("plugin.__PLUGIN_KEY__.pinged");
35
+ });
36
+
37
+ it("rejects anonymous callers on the player surface", async () => {
38
+ const t = createTestContext(plugin, { settings: { greeting: "Hi!" } });
39
+ const response = await getMe(asRequest({}), t.ctx);
40
+ expect(response.status).toBe(401);
41
+ expect(t.recorder.events).toHaveLength(0);
42
+ });
43
+ });
@@ -0,0 +1,14 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "NodeNext",
5
+ "moduleResolution": "NodeNext",
6
+ "strict": true,
7
+ "noUncheckedIndexedAccess": true,
8
+ "verbatimModuleSyntax": true,
9
+ "skipLibCheck": true,
10
+ "noEmit": true,
11
+ "types": ["node"]
12
+ },
13
+ "include": ["src", "test"]
14
+ }
@@ -0,0 +1,61 @@
1
+ # __PLUGIN_NAME__
2
+
3
+ A CasinoWebEngine cashback plugin (the reference-plugin shape), scaffolded with `cwe-plugin create`.
4
+
5
+ ## The golden path
6
+
7
+ ```bash
8
+ pnpm create cwe-plugin my-cashback # scaffold: manifest, handlers, tests, config
9
+ cd my-cashback && pnpm install
10
+
11
+ pnpm dev # = cwe-plugin dev
12
+ # → first contact with the scaffold's preset `local` environment: the handshake verifies
13
+ # class=local + devHarness=true and pins its fingerprint → green banner
14
+ # → doctor passes → sandbox tenant resolved → plugin installed+enabled (prompted)
15
+ # → esbuild watch starts, bundle hot-loaded, log stream attached
16
+ # → edit src/handlers/routes.ts, save → rebuilt + hot-reloaded in <2s, logs streaming live
17
+
18
+ # in the dev session (keyboard): i install · e enable · c configure · x disable · u uninstall
19
+ # r reload · d doctor · l clear · q quit
20
+ # each lifecycle key prints a full hook trace: capability calls, events, audit refs, state
21
+
22
+ pnpm test # unit tests on hooks/handlers via the SDK test kit
23
+
24
+ cwe-plugin lifecycle cycle # conformance sweep: install→enable→configure→disable→
25
+ # uninstall→reinstall, asserting idempotency + clean teardown
26
+
27
+ cwe-plugin login # only needed for shared dev clusters / publishing
28
+ cwe-plugin env add dev-eu https://dev-eu.cwe.dev && cwe-plugin env use dev-eu
29
+ pnpm dev # same loop against the shared cluster, per-dev sandbox
30
+
31
+ pnpm publish:dev # = cwe-plugin publish — always the dev channel:
32
+ # doctor + codegen-diff gate + publish
33
+ ```
34
+
35
+ ## What's in this template
36
+
37
+ | File | Purpose |
38
+ |---|---|
39
+ | `cwe-plugin.json` | project config: plugin key, entry, environments (committed — no secrets, ever) |
40
+ | `src/index.ts` | `definePlugin({ manifest, hooks, handlers })` — the whole platform contract |
41
+ | `src/settings.ts` | Zod-typed tenant settings (one secret field included) |
42
+ | `src/handlers/hooks.ts` | `onInstall` / `onConfigure` / `onUninstall` with commented examples |
43
+ | `src/handlers/routes.ts` | player actions `getSummary`/`listPeriods`/`claim` + admin `accrue` |
44
+ | `src/handlers/jobs.ts` | nightly accrual sweep (dry-run with `cwe-plugin jobs run nightly-accrual`) |
45
+ | `settings.dev.json` | sandbox settings applied by `cwe-plugin lifecycle configure` / the `c` key |
46
+ | `test/hooks.test.ts` | in-memory test-context examples — breakpoints work under `vitest --inspect` |
47
+
48
+ ## Useful commands
49
+
50
+ ```bash
51
+ cwe-plugin env status # am I pointed at a dev environment? auth? sandbox state?
52
+ cwe-plugin invoke POST /accrue --surface admin --body '{"playerId":"p1","amount":5}'
53
+ cwe-plugin invoke GET /summary --surface player --player p1
54
+ cwe-plugin settings set greeting=hi # validated locally against the schema before it is sent
55
+ cwe-plugin logs --level warn # standalone log tail for a second terminal
56
+ cwe-plugin doctor --remote # full server-side validation
57
+ ```
58
+
59
+ Machine-local state (pinned environment fingerprints, log cursors) lives in `.cwe/` — gitignored
60
+ and always safe to delete. Credentials never touch the repo: `cwe-plugin login` stores them in the
61
+ OS keychain.
@@ -0,0 +1,3 @@
1
+ node_modules/
2
+ dist/
3
+ .cwe/
@@ -0,0 +1,9 @@
1
+ {
2
+ "$schema": "https://cwe.dev/schemas/cwe-plugin.json",
3
+ "plugin": "__PLUGIN_KEY__",
4
+ "entry": "src/index.ts",
5
+ "environments": {
6
+ "local": { "runtimeUrl": "http://localhost:3000" }
7
+ },
8
+ "defaultEnvironment": "local"
9
+ }
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "cwe-plugin-__PLUGIN_KEY__",
3
+ "version": "0.1.0",
4
+ "description": "__PLUGIN_NAME__ \u2014 a CasinoWebEngine plugin",
5
+ "private": true,
6
+ "type": "module",
7
+ "engines": {
8
+ "node": ">=20"
9
+ },
10
+ "scripts": {
11
+ "dev": "cwe-plugin dev",
12
+ "doctor": "cwe-plugin doctor",
13
+ "test": "vitest run",
14
+ "build": "cwe-plugin build",
15
+ "codegen": "cwe-plugin codegen client",
16
+ "publish:dev": "cwe-plugin publish"
17
+ },
18
+ "dependencies": {
19
+ "@clovnet/plugin-sdk": "^0.1.4"
20
+ },
21
+ "peerDependencies": {
22
+ "zod": "^3.23.0"
23
+ },
24
+ "devDependencies": {
25
+ "@clovnet/plugin-cli": "^0.2.0",
26
+ "@types/node": "^20.16.5",
27
+ "typescript": "^5.6.2",
28
+ "vitest": "^2.1.1",
29
+ "zod": "^3.23.8"
30
+ }
31
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "values": {
3
+ "cashbackPercent": 10,
4
+ "payoutCurrency": "EUR"
5
+ },
6
+ "secrets": {
7
+ "reportingApiKey": "dev-reporting-key"
8
+ }
9
+ }
@@ -0,0 +1,19 @@
1
+ import type { PluginContext } from "@clovnet/plugin-sdk";
2
+
3
+ /**
4
+ * Lifecycle hooks — all idempotent (`cwe-plugin lifecycle cycle` asserts a
5
+ * re-run of every action is a no-op). Datasets archive automatically on
6
+ * uninstall, so there is nothing to clean up here.
7
+ */
8
+ export async function onInstall(ctx: PluginContext): Promise<void> {
9
+ ctx.logger.info("__PLUGIN_KEY__.installed");
10
+ }
11
+
12
+ export async function onConfigure(ctx: PluginContext): Promise<void> {
13
+ const { cashbackPercent } = ctx.settings.get<{ cashbackPercent?: number }>();
14
+ ctx.logger.info("__PLUGIN_KEY__.configured", { cashbackPercent: cashbackPercent ?? 5 });
15
+ }
16
+
17
+ export async function onUninstall(ctx: PluginContext): Promise<void> {
18
+ ctx.logger.info("__PLUGIN_KEY__.uninstalled");
19
+ }
@@ -0,0 +1,23 @@
1
+ import type { PluginContext } from "@clovnet/plugin-sdk";
2
+ import { type AccrualPeriod } from "./routes.js";
3
+
4
+ /**
5
+ * Nightly sweep: periods still `accruing` at the cycle boundary become
6
+ * `claimable`. Idempotent by construction (flipping twice is a no-op) —
7
+ * dry-run any time with `cwe-plugin jobs run nightly-accrual`.
8
+ */
9
+ export async function nightlyAccrual(ctx: PluginContext): Promise<void> {
10
+ const periods = ctx.datasets.collection<AccrualPeriod>("accrual_periods");
11
+ let cursor: string | undefined;
12
+ let flipped = 0;
13
+ for (;;) {
14
+ const page = await periods.query({ where: { status: "accruing" }, cursor, limit: 200 });
15
+ for (const record of page.records) {
16
+ await periods.put(record.key, { ...record.value, status: "claimable" });
17
+ flipped += 1;
18
+ }
19
+ if (!page.nextCursor) break;
20
+ cursor = page.nextCursor;
21
+ }
22
+ ctx.logger.info("__PLUGIN_KEY__.nightly_accrual", { flipped });
23
+ }