rahman-resources 1.13.2 → 1.14.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.
@@ -171,6 +171,10 @@
171
171
  "range": {
172
172
  "type": "string"
173
173
  },
174
+ "optional": {
175
+ "type": "boolean",
176
+ "description": "Peer is optional — slice degrades gracefully without it (e.g. a swappable payment provider)."
177
+ },
174
178
  "reason": {
175
179
  "type": "string"
176
180
  }
@@ -311,6 +315,10 @@
311
315
  "deprecated": {
312
316
  "type": "string",
313
317
  "description": "Successor slug. This slice was merged into the named slice as variants; install resolves there via manifest.aliases."
318
+ },
319
+ "contract": {
320
+ "type": "object",
321
+ "description": "Folded Phase-A composition contract (Phase 2 hybrid fold, 2026-06-21): requires/provides/conflicts/migrationFrom/generalization moved from slice.contract.ts. id+version omitted (== slug/version). requires.deps is verbatim pending per-slice normalization, so this is intentionally loose; strict invariants move to validate-slice when the .ts is retired."
314
322
  }
315
323
  }
316
324
  }
package/lib/snapshot.mjs CHANGED
@@ -1,13 +1,12 @@
1
1
  // snapshot.mjs — build a SliceSnapshot from a slice directory.
2
2
  //
3
3
  // Walks the directory recursively, keeps only typical slice file extensions,
4
- // reads `slice.contract.ts` when present and parses it (via `tsx` if
5
- // available, regex fallback otherwise). Skips node_modules, .kitab, and
6
- // dotfiles to keep snapshots stable across machines.
4
+ // and reads the folded composition contract from slice.json (`contract` block,
5
+ // id/version reattached from the slice.json scalars). Skips node_modules,
6
+ // .kitab, and dotfiles to keep snapshots stable across machines.
7
7
 
8
8
  import { readdir, readFile, stat } from "node:fs/promises";
9
9
  import { existsSync } from "node:fs";
10
- import { spawnSync } from "node:child_process";
11
10
  import path from "node:path";
12
11
 
13
12
  const INCLUDED_EXT = new Set([".ts", ".tsx", ".mjs", ".js", ".jsx", ".json", ".md", ".css"]);
@@ -33,18 +32,18 @@ export async function snapshotFromDir(slug, dir) {
33
32
  const files = {};
34
33
  await walk(dir, dir, files);
35
34
 
35
+ // The composition contract is folded into slice.json (`contract` block) since
36
+ // Phase 2; reattach id/version from the slice.json scalars to keep the same
37
+ // SliceContract shape the old slice.contract.ts exported.
36
38
  let contract;
37
- const contractPath = path.join(dir, "slice.contract.ts");
38
- if (existsSync(contractPath)) {
39
- contract = loadContract(contractPath) ?? regexLoadContract(await readFile(contractPath, "utf8"));
40
- }
41
-
42
- // Version preference: contract.version → slice.json.version → "0.0.0".
43
- let version = contract?.version ?? "0.0.0";
44
- if (!contract?.version && files["slice.json"]) {
39
+ let version = "0.0.0";
40
+ if (files["slice.json"]) {
45
41
  try {
46
42
  const meta = JSON.parse(files["slice.json"]);
47
43
  if (typeof meta.version === "string") version = meta.version;
44
+ if (meta.contract && typeof meta.contract === "object") {
45
+ contract = { id: meta.slug, version: meta.version, ...meta.contract };
46
+ }
48
47
  } catch {
49
48
  /* ignore */
50
49
  }
@@ -79,48 +78,3 @@ async function walk(root, dir, out) {
79
78
  out[rel] = await readFile(full, "utf8");
80
79
  }
81
80
  }
82
-
83
- /**
84
- * Mirror of validate-contract.mjs's tsx loader. Returns the parsed contract
85
- * or undefined on failure.
86
- */
87
- function loadContract(filePath) {
88
- const code = [
89
- `import(${JSON.stringify(filePath)})`,
90
- ` .then(m => { const c = m.contract || (m.default && m.default.contract); if (!c) { process.exit(2); } process.stdout.write(JSON.stringify(c)); })`,
91
- ` .catch(e => { process.stderr.write(String(e && e.message || e)); process.exit(3); });`,
92
- ].join("\n");
93
- try {
94
- const res = spawnSync("npx", ["--no-install", "tsx", "-e", code], {
95
- encoding: "utf8",
96
- });
97
- if (res.status === 0 && res.stdout) {
98
- return JSON.parse(res.stdout);
99
- }
100
- } catch {
101
- /* tsx missing or failed */
102
- }
103
- return undefined;
104
- }
105
-
106
- /**
107
- * Best-effort regex fallback: extract the literal-ish object passed to
108
- * `defineSliceContract({ ... })`. We can't fully parse TS, but for the kitab's
109
- * contracts (small static literals) a JSON5-ish coerce works for the basic
110
- * shape we need here.
111
- */
112
- function regexLoadContract(src) {
113
- const m = src.match(/defineSliceContract\s*\(\s*(\{[\s\S]*?\})\s*\)\s*;?\s*$/m);
114
- if (!m) return undefined;
115
- // Replace single quotes, strip trailing commas, quote keys.
116
- const body = m[1]
117
- .replace(/'([^'\\]*)'/g, '"$1"')
118
- .replace(/,(\s*[}\]])/g, "$1")
119
- .replace(/([{,]\s*)([A-Za-z_$][A-Za-z0-9_$]*)\s*:/g, '$1"$2":')
120
- .replace(/\/\/.*$/gm, "");
121
- try {
122
- return JSON.parse(body);
123
- } catch {
124
- return undefined;
125
- }
126
- }
@@ -1,6 +1,6 @@
1
1
  # __APP_NAME__
2
2
 
3
- Scaffolded with [`rahman-resources`](https://www.npmjs.com/package/rahman-resources) — Next 16 + React 19 + Convex (self-hosted) + Tailwind 4 + shadcn/ui.
3
+ Scaffolded with [`rahman-resources`](https://www.npmjs.com/package/rahman-resources) — Next 16 + React 19 + Convex + Tailwind 4 + shadcn/ui.
4
4
 
5
5
  ## Setup
6
6
 
@@ -11,21 +11,44 @@ npx convex dev --once # generates convex/_generated
11
11
  npm run dev
12
12
  ```
13
13
 
14
- ## Add a layout / recipe / feature
14
+ ## Add a slice
15
+
16
+ Browse the live showcase — the [Grand Tour](https://resource.rahmanef.com/tour) —
17
+ where every slice is mounted live with its `add` command. Then:
15
18
 
16
19
  ```bash
17
20
  npx rahman-resources list
18
21
  npx rahman-resources info <slug>
19
- npx rahman-resources add personal-brand-os . # full-app template (T1)
20
- npx rahman-resources add ai-sdk-openrouter . # feature (npm install)
22
+ npx rahman-resources add landing-sections . # marketing sections (hero/pricing/faq/blog…)
23
+ npx rahman-resources add ai-chat . # AI chat workbench
24
+ npx rahman-resources add appshell . # windowed web-OS shell
21
25
  ```
22
26
 
27
+ > rr is a **slice picker**: each `add` copies files into `slices/<slug>/`, which
28
+ > you own and edit. The showcase at `/tour` is Convex-free (localStorage demo
29
+ > adapters); your app wires the slice into your own backend.
30
+
31
+ ## Deploy — Vercel + Convex Cloud
32
+
33
+ `vercel.json` sets `buildCommand: npm run build:auto`, which adapts to your env:
34
+
35
+ | `CONVEX_DEPLOY_KEY` | What `build:auto` runs |
36
+ |---|---|
37
+ | **set** | `setup-auth` (one-time `@convex-dev/auth` keys) → `convex deploy --cmd 'next build'` — deploys functions to Convex Cloud, codegens `convex/_generated`, and injects `NEXT_PUBLIC_CONVEX_URL` into the build. |
38
+ | **unset** | plain `next build` — zero-config deploy of the scaffold as-is (no backend wired yet). |
39
+
40
+ So a fresh deploy is green either way: set `CONVEX_DEPLOY_KEY` in Vercel for the
41
+ full Cloud-backed app, or leave it unset to ship the static scaffold first.
42
+
43
+ > **Self-hosted (Docker/Dokploy):** commit `convex/_generated` so the container
44
+ > typecheck/build runs without codegen — see `.gitignore`. (Vercel + Convex Cloud
45
+ > needs no commit; `build:auto` codegens during deploy.)
46
+
23
47
  ## Hard rules
24
48
 
25
49
  - **NO Clerk.** Auth = `@convex-dev/auth`.
26
50
  - **shadcn primitives only** — no raw `<dialog>`, `<input type=date|file>`.
27
51
  - Use `proxy.ts` (not `middleware.ts`) on Next 16.
28
- - `convex/_generated` MUST be committed before deploy.
29
52
 
30
53
  ## Stack
31
54
 
@@ -33,5 +56,5 @@ npx rahman-resources add ai-sdk-openrouter . # feature (npm install)
33
56
  |---|---|
34
57
  | Framework | Next.js 16 (App Router + cacheComponents) |
35
58
  | UI | React 19 + Tailwind 4 + shadcn |
36
- | Backend | Convex (self-hosted compatible) |
59
+ | Backend | Convex (Cloud or self-hosted) |
37
60
  | Auth | `@convex-dev/auth` (Password provider by default) |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rahman-resources",
3
- "version": "1.13.2",
3
+ "version": "1.14.0",
4
4
  "description": "Rahman Resources (rr) — shadcn-style installer for vertical slices. `npx resources add <slug>` copies slice into your project's `slices/<slug>/`. You own the files.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -29,12 +29,12 @@
29
29
  "gen": "node scripts/gen-manifest.mjs",
30
30
  "validate": "node scripts/validate.mjs",
31
31
  "validate:slices": "node scripts/validate-slice.mjs",
32
- "validate:parity": "node scripts/validate-slice-parity.mjs",
32
+ "validate:parity": "node ../../scripts/features/gen-slice-catalog.mjs --check",
33
33
  "validate:structure": "node scripts/validate-structure.mjs",
34
- "validate:all": "node scripts/validate.mjs && node scripts/validate-slice.mjs --check && node scripts/validate-slice-parity.mjs && node scripts/validate-structure.mjs",
34
+ "validate:all": "node scripts/validate.mjs && node scripts/validate-slice.mjs --check && node ../../scripts/features/gen-slice-catalog.mjs --check && node scripts/validate-structure.mjs",
35
35
  "sync:skills": "node scripts/sync-skills.mjs",
36
36
  "sync:skills:check": "node scripts/sync-skills.mjs --check",
37
- "prepublishOnly": "node scripts/sync-skills.mjs --check && node scripts/validate.mjs && node scripts/validate-slice.mjs --check && node scripts/validate-slice-parity.mjs && node scripts/validate-structure.mjs"
37
+ "prepublishOnly": "node scripts/sync-skills.mjs --check && node scripts/validate.mjs && node scripts/validate-slice.mjs --check && node ../../scripts/features/gen-slice-catalog.mjs --check && node scripts/validate-structure.mjs"
38
38
  },
39
39
  "dependencies": {
40
40
  "kleur": "^4.1.5",