@onesub/cli 0.1.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/README.md ADDED
@@ -0,0 +1,37 @@
1
+ # @onesub/cli
2
+
3
+ Scaffolds a ready-to-run [onesub](https://github.com/jeonghwanko/onesub) server project.
4
+
5
+ ```bash
6
+ npx @onesub/cli init my-onesub-server
7
+ cd my-onesub-server
8
+ cp .env.example .env
9
+ npm install
10
+ npm run dev
11
+ ```
12
+
13
+ Or full stack (Postgres + server) in one command:
14
+
15
+ ```bash
16
+ docker compose up
17
+ ```
18
+
19
+ ## What it generates
20
+
21
+ | File | Purpose |
22
+ |------|---------|
23
+ | `server.ts` | Express app with `createOneSubMiddleware()` wired up |
24
+ | `.env.example` | Apple / Google / Postgres / admin placeholders |
25
+ | `docker-compose.yml` | Postgres + server, schema auto-initialized from `@onesub/server/sql/schema.sql` |
26
+ | `package.json`, `tsconfig.json`, `.gitignore`, `README.md` | Standard project shell |
27
+
28
+ That's it. No prompts, no interactive questionnaire — same scaffold every time, tweak it yourself afterward.
29
+
30
+ ## Commands
31
+
32
+ ```
33
+ onesub init [directory] Scaffold into <directory> (default: .)
34
+ onesub --help Show help
35
+ ```
36
+
37
+ MIT.
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * onesub CLI — scaffolds a starter server (+ optional Expo app) in a new or
4
+ * existing directory.
5
+ *
6
+ * Usage:
7
+ * npx @onesub/cli init [directory]
8
+ * npx @onesub/cli --help
9
+ */
10
+ export {};
11
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;;;;;GAOG"}
package/dist/index.js ADDED
@@ -0,0 +1,115 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * onesub CLI — scaffolds a starter server (+ optional Expo app) in a new or
4
+ * existing directory.
5
+ *
6
+ * Usage:
7
+ * npx @onesub/cli init [directory]
8
+ * npx @onesub/cli --help
9
+ */
10
+ import { mkdir, writeFile, readdir, readFile } from 'node:fs/promises';
11
+ import { existsSync } from 'node:fs';
12
+ import { join, resolve, dirname } from 'node:path';
13
+ import { fileURLToPath } from 'node:url';
14
+ const here = dirname(fileURLToPath(import.meta.url));
15
+ // dist/index.js → packages/cli/templates
16
+ const TEMPLATES_DIR = resolve(here, '..', 'templates');
17
+ const HELP = `onesub — scaffold a receipt-validation server and paywall app
18
+
19
+ Usage:
20
+ onesub init [directory] Create a new onesub project in <directory> (default: .)
21
+ onesub --help Show this help
22
+
23
+ What 'init' creates:
24
+ server.ts Express server with createOneSubMiddleware() wired up
25
+ .env.example Apple + Google credential placeholders
26
+ package.json With @onesub/server as a dependency
27
+ docker-compose.yml Postgres + server, schema auto-initialized
28
+ README.md Next-steps guide
29
+
30
+ After init:
31
+ cd <directory>
32
+ cp .env.example .env # fill in your credentials
33
+ npm install
34
+ npm start # http://localhost:4100
35
+ `;
36
+ function parseArgs(argv) {
37
+ const args = argv.slice(2);
38
+ if (args.length === 0 || args[0] === '--help' || args[0] === '-h') {
39
+ return { cmd: 'help', target: '.' };
40
+ }
41
+ if (args[0] !== 'init') {
42
+ return { cmd: 'unknown', target: args[0] ?? '' };
43
+ }
44
+ return { cmd: 'init', target: args[1] ?? '.' };
45
+ }
46
+ async function isEmpty(dir) {
47
+ if (!existsSync(dir))
48
+ return true;
49
+ const entries = await readdir(dir);
50
+ return entries.length === 0;
51
+ }
52
+ async function copyTemplate(relPath, destDir) {
53
+ const src = join(TEMPLATES_DIR, relPath);
54
+ const dst = join(destDir, relPath);
55
+ await mkdir(dirname(dst), { recursive: true });
56
+ const content = await readFile(src, 'utf8');
57
+ await writeFile(dst, content, 'utf8');
58
+ console.log(` ${relPath}`);
59
+ }
60
+ async function init(target) {
61
+ const dir = resolve(target);
62
+ const empty = await isEmpty(dir);
63
+ if (!empty) {
64
+ console.error(`error: ${dir} is not empty. Pick a new directory or remove existing files.`);
65
+ process.exit(1);
66
+ }
67
+ await mkdir(dir, { recursive: true });
68
+ console.log(`\nScaffolding onesub server into ${dir}\n`);
69
+ const files = [
70
+ 'package.json',
71
+ 'server.ts',
72
+ 'tsconfig.json',
73
+ '.env.example',
74
+ 'docker-compose.yml',
75
+ 'README.md',
76
+ '.gitignore',
77
+ ];
78
+ for (const f of files) {
79
+ await copyTemplate(f, dir);
80
+ }
81
+ console.log(`
82
+ Done. Next steps:
83
+
84
+ cd ${target === '.' ? '.' : target}
85
+ cp .env.example .env # fill in Apple / Google credentials
86
+ npm install
87
+ npm run dev # http://localhost:4100
88
+
89
+ Or with Docker (Postgres + server):
90
+
91
+ docker compose up
92
+
93
+ Docs: https://github.com/jeonghwanko/onesub
94
+ `);
95
+ }
96
+ async function main() {
97
+ const { cmd, target } = parseArgs(process.argv);
98
+ switch (cmd) {
99
+ case 'help':
100
+ console.log(HELP);
101
+ return;
102
+ case 'init':
103
+ await init(target);
104
+ return;
105
+ default:
106
+ console.error(`Unknown command: ${target}\n`);
107
+ console.error(HELP);
108
+ process.exit(1);
109
+ }
110
+ }
111
+ main().catch((err) => {
112
+ console.error('onesub cli failed:', err);
113
+ process.exit(1);
114
+ });
115
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "@onesub/cli",
3
+ "version": "0.1.1",
4
+ "description": "CLI scaffolding for onesub — `npx onesub init` bootstraps a server + Expo app.",
5
+ "main": "dist/index.js",
6
+ "type": "module",
7
+ "bin": {
8
+ "onesub": "dist/index.js"
9
+ },
10
+ "files": [
11
+ "dist",
12
+ "templates"
13
+ ],
14
+ "scripts": {
15
+ "build": "tsc",
16
+ "dev": "tsc --watch",
17
+ "start": "node dist/index.js"
18
+ },
19
+ "keywords": [
20
+ "onesub",
21
+ "cli",
22
+ "init",
23
+ "scaffolding",
24
+ "react-native-iap",
25
+ "in-app-purchase"
26
+ ],
27
+ "repository": {
28
+ "type": "git",
29
+ "url": "https://github.com/jeonghwanko/onesub.git",
30
+ "directory": "packages/cli"
31
+ },
32
+ "homepage": "https://github.com/jeonghwanko/onesub",
33
+ "publishConfig": {
34
+ "access": "public"
35
+ },
36
+ "license": "MIT",
37
+ "dependencies": {
38
+ "@onesub/shared": "0.3.2"
39
+ },
40
+ "devDependencies": {
41
+ "@types/node": "^20.17.0",
42
+ "typescript": "^5.7.0"
43
+ },
44
+ "engines": {
45
+ "node": ">=20"
46
+ }
47
+ }
@@ -0,0 +1,22 @@
1
+ # ── Apple App Store ──────────────────────────────────────
2
+ # From App Store Connect → Users and Access → Integrations → Keys
3
+ APPLE_BUNDLE_ID=com.yourapp.id
4
+ APPLE_SHARED_SECRET=your_shared_secret
5
+
6
+ # ── Google Play ─────────────────────────────────────────
7
+ # Service account JSON from Google Cloud Console (single-line)
8
+ GOOGLE_PACKAGE_NAME=com.yourapp.id
9
+ GOOGLE_SERVICE_ACCOUNT_KEY={"type":"service_account","project_id":"..."}
10
+ # Optional: push endpoint URL for Pub/Sub JWT verification
11
+ # GOOGLE_PUSH_AUDIENCE=https://api.yourapp.com/onesub/webhook/google
12
+
13
+ # ── Database ────────────────────────────────────────────
14
+ # Uncomment for Postgres; leave unset for in-memory (dev only)
15
+ # DATABASE_URL=postgresql://onesub:onesub@localhost:5432/onesub
16
+
17
+ # ── Admin (optional) ────────────────────────────────────
18
+ # Enables /onesub/purchase/admin/* routes
19
+ # ADMIN_SECRET=change-me
20
+
21
+ # ── Server ──────────────────────────────────────────────
22
+ PORT=4100
@@ -0,0 +1,43 @@
1
+ # my-onesub-server
2
+
3
+ Scaffolded by [`npx @onesub/cli init`](https://github.com/jeonghwanko/onesub).
4
+
5
+ ## Run
6
+
7
+ ```bash
8
+ cp .env.example .env # fill in Apple / Google credentials
9
+ npm install
10
+ npm run dev # http://localhost:4100
11
+ ```
12
+
13
+ Or the full stack (Postgres + server) with Docker:
14
+
15
+ ```bash
16
+ docker compose up
17
+ ```
18
+
19
+ ## What you got
20
+
21
+ | File | What |
22
+ |------|------|
23
+ | `server.ts` | Express server with `createOneSubMiddleware()` wired up |
24
+ | `.env.example` | Apple / Google / DB placeholders |
25
+ | `docker-compose.yml` | Postgres + server, schema auto-initialized from `@onesub/server/sql/schema.sql` |
26
+
27
+ ## Endpoints mounted
28
+
29
+ - `POST /onesub/validate` — subscription receipt
30
+ - `GET /onesub/status?userId=` — subscription state
31
+ - `POST /onesub/webhook/apple` — App Store Server Notifications V2
32
+ - `POST /onesub/webhook/google` — Google Play RTDN
33
+ - `POST /onesub/purchase/validate` — one-time purchase (consumable / non-consumable)
34
+ - `GET /onesub/purchase/status?userId=` — list purchases
35
+ - `POST /onesub/purchase/admin/*` — admin routes (if `ADMIN_SECRET` is set)
36
+
37
+ ## Next
38
+
39
+ - Add your own auth middleware before `createOneSubMiddleware(...)` if `userId` should not be client-trusted
40
+ - Install [`@jeonghwanko/onesub-sdk`](https://www.npmjs.com/package/@jeonghwanko/onesub-sdk) in your React Native app for the `useOneSub()` hook
41
+ - Read [onesub docs](https://github.com/jeonghwanko/onesub)
42
+
43
+ MIT.
@@ -0,0 +1,41 @@
1
+ services:
2
+ db:
3
+ image: postgres:16-alpine
4
+ restart: unless-stopped
5
+ environment:
6
+ POSTGRES_USER: onesub
7
+ POSTGRES_PASSWORD: onesub
8
+ POSTGRES_DB: onesub
9
+ ports:
10
+ - '5432:5432'
11
+ volumes:
12
+ - onesub-pgdata:/var/lib/postgresql/data
13
+ # Canonical schema shipped with @onesub/server at node_modules/@onesub/server/sql/schema.sql
14
+ - ./node_modules/@onesub/server/sql/schema.sql:/docker-entrypoint-initdb.d/10-onesub-schema.sql:ro
15
+ healthcheck:
16
+ test: ['CMD-SHELL', 'pg_isready -U onesub -d onesub']
17
+ interval: 5s
18
+ timeout: 3s
19
+ retries: 10
20
+
21
+ server:
22
+ image: node:20-alpine
23
+ depends_on:
24
+ db:
25
+ condition: service_healthy
26
+ working_dir: /app
27
+ command: sh -c "npm install --no-audit --no-fund && npm run dev"
28
+ environment:
29
+ PORT: '4100'
30
+ DATABASE_URL: postgresql://onesub:onesub@db:5432/onesub
31
+ APPLE_BUNDLE_ID: ${APPLE_BUNDLE_ID:-}
32
+ APPLE_SHARED_SECRET: ${APPLE_SHARED_SECRET:-}
33
+ GOOGLE_PACKAGE_NAME: ${GOOGLE_PACKAGE_NAME:-}
34
+ GOOGLE_SERVICE_ACCOUNT_KEY: ${GOOGLE_SERVICE_ACCOUNT_KEY:-}
35
+ ports:
36
+ - '4100:4100'
37
+ volumes:
38
+ - .:/app
39
+
40
+ volumes:
41
+ onesub-pgdata:
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "my-onesub-server",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "tsx server.ts",
8
+ "start": "tsx server.ts"
9
+ },
10
+ "dependencies": {
11
+ "@onesub/server": "^0.6.0",
12
+ "dotenv": "^16.4.5",
13
+ "express": "^4.19.2",
14
+ "pg": "^8.13.0"
15
+ },
16
+ "devDependencies": {
17
+ "@types/express": "^4.17.21",
18
+ "@types/node": "^20.17.0",
19
+ "tsx": "^4.19.0",
20
+ "typescript": "^5.7.0"
21
+ },
22
+ "engines": {
23
+ "node": ">=20"
24
+ }
25
+ }
@@ -0,0 +1,45 @@
1
+ import 'dotenv/config';
2
+ import express from 'express';
3
+ import {
4
+ createOneSubMiddleware,
5
+ InMemorySubscriptionStore,
6
+ InMemoryPurchaseStore,
7
+ PostgresSubscriptionStore,
8
+ PostgresPurchaseStore,
9
+ } from '@onesub/server';
10
+
11
+ const app = express();
12
+ const port = parseInt(process.env.PORT ?? '4100', 10);
13
+
14
+ // Pick stores based on whether DATABASE_URL is set.
15
+ const dbUrl = process.env.DATABASE_URL;
16
+ const store = dbUrl ? new PostgresSubscriptionStore(dbUrl) : new InMemorySubscriptionStore();
17
+ const purchaseStore = dbUrl ? new PostgresPurchaseStore(dbUrl) : new InMemoryPurchaseStore();
18
+
19
+ if (store instanceof PostgresSubscriptionStore) await store.initSchema();
20
+ if (purchaseStore instanceof PostgresPurchaseStore) await purchaseStore.initSchema();
21
+
22
+ app.use(
23
+ createOneSubMiddleware({
24
+ apple: process.env.APPLE_BUNDLE_ID
25
+ ? { bundleId: process.env.APPLE_BUNDLE_ID, sharedSecret: process.env.APPLE_SHARED_SECRET }
26
+ : undefined,
27
+ google: process.env.GOOGLE_PACKAGE_NAME
28
+ ? {
29
+ packageName: process.env.GOOGLE_PACKAGE_NAME,
30
+ serviceAccountKey: process.env.GOOGLE_SERVICE_ACCOUNT_KEY,
31
+ pushAudience: process.env.GOOGLE_PUSH_AUDIENCE,
32
+ }
33
+ : undefined,
34
+ database: { url: dbUrl ?? '' },
35
+ store,
36
+ purchaseStore,
37
+ adminSecret: process.env.ADMIN_SECRET,
38
+ }),
39
+ );
40
+
41
+ app.get('/health', (_req, res) => res.json({ ok: true }));
42
+
43
+ app.listen(port, () => {
44
+ console.log(`[onesub] listening on http://localhost:${port}`);
45
+ });
@@ -0,0 +1,13 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "NodeNext",
5
+ "moduleResolution": "NodeNext",
6
+ "strict": true,
7
+ "esModuleInterop": true,
8
+ "skipLibCheck": true,
9
+ "forceConsistentCasingInFileNames": true,
10
+ "resolveJsonModule": true
11
+ },
12
+ "include": ["server.ts"]
13
+ }