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.
- package/bin/cli.js +45 -6
- package/bin/migrate-load.mjs +15 -8
- package/bin/migrate.mjs +1 -1
- package/lib/compose-solver-loader.mjs +14 -40
- package/lib/compose-solver-resolve.mjs +3 -3
- package/lib/contract-tools.test.ts +52 -0
- package/lib/contract-types.ts +8 -0
- package/lib/contract-validate.ts +23 -1
- package/lib/contract.ts +2 -0
- package/lib/load-contract.mjs +33 -0
- package/lib/manifest.json +396 -1414
- package/lib/slice-schema.json +8 -0
- package/lib/snapshot.mjs +11 -57
- package/lib/starter/_README.md +29 -6
- package/package.json +4 -4
package/lib/slice-schema.json
CHANGED
|
@@ -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
|
|
5
|
-
//
|
|
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
|
-
|
|
38
|
-
if (
|
|
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
|
-
}
|
package/lib/starter/_README.md
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
20
|
-
npx rahman-resources add ai-
|
|
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
|
|
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.
|
|
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/
|
|
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/
|
|
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/
|
|
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",
|