@joinbankroll/create-app 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.
Files changed (3) hide show
  1. package/README.md +35 -0
  2. package/dist/index.js +136 -0
  3. package/package.json +41 -0
package/README.md ADDED
@@ -0,0 +1,35 @@
1
+ # @joinbankroll/create-app
2
+
3
+ Scaffold a [Built for Bankroll](https://joinbankroll.com/build) app.
4
+
5
+ ```bash
6
+ npm create @joinbankroll/app@latest my-app
7
+ cd my-app
8
+ npm run bankroll
9
+ ```
10
+
11
+ `npm run bankroll` raises a public tunnel and prints a QR code. Scan it and your
12
+ app opens inside Bankroll on your phone, which is the only place it runs — the
13
+ host holds the user's money, identity, and location, and refuses any origin that
14
+ is not public HTTPS.
15
+
16
+ ## What it does
17
+
18
+ Downloads [bankroll-starter](https://github.com/inplayinnovation/bankroll-starter),
19
+ writes `.env.local`, installs dependencies, and makes the first commit.
20
+
21
+ ```
22
+ -t, --template <ref> template branch or tag (default: main)
23
+ ```
24
+
25
+ ## What you get
26
+
27
+ An app with its own git history and no upstream remote, the same as
28
+ `create-next-app`. There is nothing to merge back later: everything that is not
29
+ your app comes from `@joinbankroll/sdk` and `bankroll-cli`, and updates with
30
+ `npm update`.
31
+
32
+ `.env.local` holds configuration only — `STORE=fs` and your app's name. The
33
+ signing key belongs to `bankroll dev`, which keeps it in
34
+ `~/.config/bankroll/keypair.json` and injects it into the dev server's
35
+ environment, so no secret is ever written into your project.
package/dist/index.js ADDED
@@ -0,0 +1,136 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import { spawnSync } from "child_process";
5
+ import { existsSync, mkdirSync, readdirSync, rmSync, writeFileSync } from "fs";
6
+ import { basename, resolve } from "path";
7
+ import { createInterface } from "readline/promises";
8
+ var TEMPLATE_REPO = "inplayinnovation/bankroll-starter";
9
+ var DEFAULT_REF = "main";
10
+ var DOWNLOAD_TIMEOUT_MS = 6e4;
11
+ var ENV_FILE = ".env.local";
12
+ var IGNORABLE = /* @__PURE__ */ new Set([".DS_Store", ".git", ".gitkeep", ".idea", ".vscode", "Thumbs.db"]);
13
+ function parse(argv) {
14
+ const args = { ref: DEFAULT_REF, help: false };
15
+ for (let i = 0; i < argv.length; i++) {
16
+ const arg = argv[i];
17
+ if (arg === "--help" || arg === "-h") args.help = true;
18
+ else if (arg === "--template" || arg === "-t") args.ref = argv[++i] ?? DEFAULT_REF;
19
+ else if (arg?.startsWith("-")) throw new Error(`Unknown option ${arg}`);
20
+ else if (arg && !args.directory) args.directory = arg;
21
+ }
22
+ return args;
23
+ }
24
+ function usage() {
25
+ console.log(`
26
+ Create a Built for Bankroll app.
27
+
28
+ npm create @joinbankroll/app@latest my-app
29
+
30
+ Options
31
+ -t, --template <ref> template branch or tag (default: ${DEFAULT_REF})
32
+ -h, --help show this
33
+ `);
34
+ }
35
+ async function ask(question, fallback) {
36
+ if (!process.stdin.isTTY) return fallback;
37
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
38
+ try {
39
+ return (await rl.question(` ${question} (${fallback}): `)).trim() || fallback;
40
+ } finally {
41
+ rl.close();
42
+ }
43
+ }
44
+ function run(command, args, cwd) {
45
+ const result = spawnSync(command, args, { cwd, stdio: "inherit" });
46
+ if (result.error) throw result.error;
47
+ if (result.status !== 0) throw new Error(`${command} ${args.join(" ")} failed`);
48
+ }
49
+ function tryRun(command, args, cwd) {
50
+ const result = spawnSync(command, args, { cwd, stdio: "ignore" });
51
+ return !result.error && result.status === 0;
52
+ }
53
+ async function download(ref, into) {
54
+ const url = `https://codeload.github.com/${TEMPLATE_REPO}/tar.gz/${ref}`;
55
+ const response = await fetch(url, { signal: AbortSignal.timeout(DOWNLOAD_TIMEOUT_MS) });
56
+ if (!response.ok) {
57
+ throw new Error(
58
+ response.status === 404 ? `No template named "${ref}" \u2014 check --template.` : `Could not download the template (${response.status}).`
59
+ );
60
+ }
61
+ const archive = resolve(into, ".template.tar.gz");
62
+ writeFileSync(archive, Buffer.from(await response.arrayBuffer()));
63
+ try {
64
+ run("tar", ["-xzf", archive, "--strip-components=1"], into);
65
+ } finally {
66
+ rmSync(archive, { force: true });
67
+ }
68
+ }
69
+ async function main() {
70
+ const args = parse(process.argv.slice(2));
71
+ if (args.help) return usage();
72
+ const directory = args.directory ?? await ask("Directory", "my-bankroll-app");
73
+ const target = resolve(process.cwd(), directory);
74
+ let created = false;
75
+ if (existsSync(target)) {
76
+ const existing = readdirSync(target).filter((entry) => !IGNORABLE.has(entry));
77
+ if (existing.length > 0) throw new Error(`${directory} is not empty.`);
78
+ } else {
79
+ mkdirSync(target, { recursive: true });
80
+ created = true;
81
+ }
82
+ const name = await ask("App name", basename(target));
83
+ console.log(`
84
+ Downloading the template\u2026`);
85
+ try {
86
+ await download(args.ref, target);
87
+ } catch (error) {
88
+ if (created && readdirSync(target).length === 0) rmSync(target, { recursive: true, force: true });
89
+ throw error;
90
+ }
91
+ writeFileSync(
92
+ resolve(target, ENV_FILE),
93
+ [
94
+ "# Written by create-bankroll-app. Gitignored, so none of it reaches a",
95
+ "# deployment.",
96
+ "",
97
+ "# Store state in local files rather than Vercel Blob, so this app runs",
98
+ "# without a Vercel account. A deployment has no STORE and uses Blob.",
99
+ "STORE=fs",
100
+ "",
101
+ "# Shown when someone connects the app.",
102
+ `BANKROLL_APP_NAME=${name}`,
103
+ ""
104
+ ].join("\n")
105
+ );
106
+ console.log(` Installing dependencies\u2026
107
+ `);
108
+ run("npm", ["install"], target);
109
+ if (tryRun("git", ["init", "-q"], target)) {
110
+ tryRun("git", ["add", "-A"], target);
111
+ tryRun("git", ["commit", "-qm", "Initial commit from create-bankroll-app"], target);
112
+ }
113
+ console.log(`
114
+ ${name} is ready.
115
+
116
+ cd ${directory}
117
+ npm run bankroll tunnel + QR \u2014 scan it to open the app inside Bankroll
118
+
119
+ Your app runs inside Bankroll, so a phone is how you see it. Everything that
120
+ is not your app comes from @joinbankroll/sdk and updates with npm update.
121
+ `);
122
+ }
123
+ if (process.argv[1] && import.meta.url.endsWith(basename(process.argv[1]))) {
124
+ try {
125
+ await main();
126
+ } catch (error) {
127
+ console.error(`
128
+ ${error instanceof Error ? error.message : String(error)}
129
+ `);
130
+ process.exit(1);
131
+ }
132
+ }
133
+ export {
134
+ IGNORABLE,
135
+ parse
136
+ };
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "@joinbankroll/create-app",
3
+ "version": "0.1.0",
4
+ "description": "Create a Built for Bankroll app",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/inplayinnovation/create-bankroll-app.git"
9
+ },
10
+ "homepage": "https://joinbankroll.com/build",
11
+ "type": "module",
12
+ "bin": {
13
+ "create-bankroll-app": "./dist/index.js"
14
+ },
15
+ "files": [
16
+ "dist"
17
+ ],
18
+ "engines": {
19
+ "node": ">=20.6"
20
+ },
21
+ "keywords": [
22
+ "bankroll",
23
+ "solana",
24
+ "create"
25
+ ],
26
+ "devDependencies": {
27
+ "@types/node": "^24.0.0",
28
+ "tsup": "^8.0.0",
29
+ "typescript": "^5.9.3",
30
+ "vitest": "^3.0.0"
31
+ },
32
+ "scripts": {
33
+ "build": "tsup",
34
+ "prepack": "npm run build",
35
+ "test": "vitest run",
36
+ "typecheck": "tsc --noEmit"
37
+ },
38
+ "publishConfig": {
39
+ "access": "public"
40
+ }
41
+ }