@sb-codex/create-sb-app 0.0.2 → 0.0.4

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 +22 -17
  2. package/dist/index.js +13 -24
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -1,46 +1,51 @@
1
1
  # @sb-codex/create-sb-app
2
2
 
3
- Scaffold a new multi-tenant SaaS project from the [sb-codex starter](https://github.com/SB-SLIM/react-app-starter). Downloads the full monorepo (apps + packages in `workspace:^`, self-contained) and replaces project-specific values for you.
3
+ Scaffold a new multi-tenant SaaS project from the [sb-codex starter](https://github.com/SB-SLIM/react-app-starter).
4
+
5
+ It generates an **apps-only** project: you get `apps/` (admin, server, web, e2e) and the `@sb-codex/*` plugins are pulled from **npm** at their published versions — there is **no `packages/` folder** to maintain. The CLI also injects each plugin's peer dependencies into the consuming apps so the project installs cleanly.
4
6
 
5
7
  ## Usage
6
8
 
7
9
  ```bash
8
- pnpm create @sb-codex/sb-app my-saas
9
- # or: npm create @sb-codex/sb-app my-saas
10
+ pnpm create @sb-codex/sb-app@latest my-saas
11
+ # or: npm create @sb-codex/sb-app@latest my-saas
10
12
  ```
11
13
 
14
+ > The `@latest` tag forces the newest version and avoids npx serving a cached older release.
15
+
12
16
  Prompts:
13
17
 
14
18
  - **Project directory** — where to create the project
15
19
  - **Project name** — root `package.json` name
16
- - **Production domain** — replaces the reference domain across docs, compose and Traefik config
20
+ - **Production domain** — replaces the reference domain. **Defaults to `localhost`** for local dev (e.g. `hub.localhost`)
17
21
  - **git init** — start with fresh git history
18
22
 
19
23
  Then:
20
24
 
21
25
  ```bash
22
26
  cd my-saas
23
- pnpm install
27
+ pnpm install # plugins resolved from npm — no local build
24
28
  pnpm dev
25
29
  ```
26
30
 
27
- ## What it replaces
31
+ ## What it does
28
32
 
29
- | Token | Replaced with |
30
- | ----------------------------------- | ---------------------------- |
31
- | `slimbouchoucha.tn` | your domain |
32
- | `ghcr.io/sb-slim/react-app-starter` | `ghcr.io/your-gh-org/<name>` |
33
- | `SB-SLIM/react-app-starter` | `your-gh-org/<name>` |
34
- | `152.53.187.54` | `YOUR_VPS_IP` |
35
- | Notion page id | `YOUR_NOTION_PAGE_ID` |
33
+ - Pulls `apps/` from the starter; rewrites each `@sb-codex/*` dependency from `workspace:^` to the **published npm version** and injects that plugin's peer dependencies
34
+ - Removes the `packages/` source, trims `pnpm-workspace.yaml` to `apps/*`, strips `@sb-codex/*` from `pnpm.overrides`, drops the changeset config
35
+ - Copies `.env.example` `.env`
36
+ - Replaces project-specific tokens:
36
37
 
37
- It also drops its own `packages/create-sb-app` from the generated project and copies `.env.example` → `.env`.
38
+ | Token | Replaced with |
39
+ | ----------------------------------- | --------------------------------- |
40
+ | `slimbouchoucha.tn` | your domain (default `localhost`) |
41
+ | `ghcr.io/sb-slim/react-app-starter` | `ghcr.io/your-gh-org/<name>` |
42
+ | `SB-SLIM/react-app-starter` | `your-gh-org/<name>` |
43
+ | Notion page id | `YOUR_NOTION_PAGE_ID` |
38
44
 
39
- ## Local usage (from the monorepo)
45
+ ## Non-interactive flags
40
46
 
41
47
  ```bash
42
- pnpm --filter @sb-codex/create-sb-app build
43
- node packages/create-sb-app/dist/index.js ../my-saas
48
+ npx @sb-codex/create-sb-app my-saas --name my-saas --domain localhost --no-git
44
49
  ```
45
50
 
46
51
  ---
package/dist/index.js CHANGED
@@ -9,7 +9,7 @@ import {
9
9
  rmSync as rmSync2,
10
10
  copyFileSync
11
11
  } from "fs";
12
- import { join as join2, extname, basename } from "path";
12
+ import { join as join2, extname } from "path";
13
13
  import { execSync } from "child_process";
14
14
  import {
15
15
  intro,
@@ -44,8 +44,6 @@ function buildReplacements(answers) {
44
44
  },
45
45
  // Remaining bare repo name (URLs, image names)
46
46
  { find: /react-app-starter/g, replace: name },
47
- // VPS IP
48
- { find: /152\.53\.187\.54/g, replace: "YOUR_VPS_IP" },
49
47
  // Notion page id (mirror of architecture docs — project specific)
50
48
  { find: NOTION_PAGE_ID_DASHED, replace: "YOUR_NOTION_PAGE_ID" },
51
49
  { find: NOTION_PAGE_ID_COMPACT, replace: "YOUR_NOTION_PAGE_ID" }
@@ -134,7 +132,8 @@ function applyAppsOnly(targetDir) {
134
132
  }
135
133
  }
136
134
  }
137
- if (existsSync(packagesDir)) rmSync(packagesDir, { recursive: true, force: true });
135
+ if (existsSync(packagesDir))
136
+ rmSync(packagesDir, { recursive: true, force: true });
138
137
  const wsPath = join(targetDir, "pnpm-workspace.yaml");
139
138
  if (existsSync(wsPath)) {
140
139
  const kept = readFileSync(wsPath, "utf8").split(/\r?\n/).filter((line) => !/['"]packages\/\*['"]/.test(line)).join("\n");
@@ -152,7 +151,8 @@ function applyAppsOnly(targetDir) {
152
151
  writeJson(rootPj, pkg);
153
152
  }
154
153
  const changesetDir = join(targetDir, ".changeset");
155
- if (existsSync(changesetDir)) rmSync(changesetDir, { recursive: true, force: true });
154
+ if (existsSync(changesetDir))
155
+ rmSync(changesetDir, { recursive: true, force: true });
156
156
  }
157
157
 
158
158
  // src/index.ts
@@ -204,43 +204,32 @@ async function main() {
204
204
  intro(pc.bgCyan(pc.black(" create-sb-app ")));
205
205
  const flags = parseFlags(process.argv.slice(2));
206
206
  const positional = process.argv.slice(2).find((a) => !a.startsWith("--"));
207
- let target = positional ?? (typeof flags.dir === "string" ? flags.dir : void 0);
207
+ let target = positional ?? (typeof flags.name === "string" ? flags.name : void 0);
208
208
  if (!target) {
209
209
  const answer = await text({
210
- message: "Project directory?",
210
+ message: "Project name?",
211
211
  placeholder: "my-saas",
212
- validate: (v) => v.length === 0 ? "Required" : void 0
212
+ validate: (v) => slugify(v).length === 0 ? "Invalid name" : void 0
213
213
  });
214
214
  if (isCancel(answer)) bail("Cancelled.");
215
215
  target = answer;
216
216
  }
217
+ const name = slugify(target);
217
218
  const targetDir = join2(process.cwd(), target);
218
219
  if (existsSync2(targetDir) && readdirSync2(targetDir).length > 0) {
219
220
  bail(`Directory "${target}" already exists and is not empty.`);
220
221
  }
221
- let name;
222
- if (typeof flags.name === "string") {
223
- name = flags.name;
224
- } else {
225
- const nameAnswer = await text({
226
- message: "Project name (package name)?",
227
- initialValue: slugify(basename(target)),
228
- validate: (v) => slugify(v).length === 0 ? "Invalid name" : void 0
229
- });
230
- if (isCancel(nameAnswer)) bail("Cancelled.");
231
- name = nameAnswer;
232
- }
233
222
  let domain;
234
223
  if (typeof flags.domain === "string") {
235
224
  domain = flags.domain;
236
225
  } else {
237
226
  const domainAnswer = await text({
238
- message: "Production domain?",
239
- placeholder: "myapp.com",
240
- defaultValue: "example.com"
227
+ message: "Production domain? (defaults to localhost for local dev)",
228
+ placeholder: "localhost",
229
+ defaultValue: "localhost"
241
230
  });
242
231
  if (isCancel(domainAnswer)) bail("Cancelled.");
243
- domain = domainAnswer || "example.com";
232
+ domain = domainAnswer || "localhost";
244
233
  }
245
234
  let doGit;
246
235
  if (flags.git === true || flags["no-git"] === true) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package.json",
3
3
  "name": "@sb-codex/create-sb-app",
4
- "version": "0.0.2",
4
+ "version": "0.0.4",
5
5
  "description": "Scaffold a new multi-tenant SaaS project from the sb-codex starter.",
6
6
  "type": "module",
7
7
  "license": "MIT",