@obh/forge 0.2.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 (215) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +146 -0
  3. package/dist/capabilities/analytics.d.ts +3 -0
  4. package/dist/capabilities/analytics.d.ts.map +1 -0
  5. package/dist/capabilities/analytics.js +91 -0
  6. package/dist/capabilities/analytics.js.map +1 -0
  7. package/dist/capabilities/api-keys.d.ts +3 -0
  8. package/dist/capabilities/api-keys.d.ts.map +1 -0
  9. package/dist/capabilities/api-keys.js +90 -0
  10. package/dist/capabilities/api-keys.js.map +1 -0
  11. package/dist/capabilities/audit.d.ts +3 -0
  12. package/dist/capabilities/audit.d.ts.map +1 -0
  13. package/dist/capabilities/audit.js +74 -0
  14. package/dist/capabilities/audit.js.map +1 -0
  15. package/dist/capabilities/entitlements.d.ts +3 -0
  16. package/dist/capabilities/entitlements.d.ts.map +1 -0
  17. package/dist/capabilities/entitlements.js +94 -0
  18. package/dist/capabilities/entitlements.js.map +1 -0
  19. package/dist/capabilities/events.d.ts +3 -0
  20. package/dist/capabilities/events.d.ts.map +1 -0
  21. package/dist/capabilities/events.js +107 -0
  22. package/dist/capabilities/events.js.map +1 -0
  23. package/dist/capabilities/files.d.ts +3 -0
  24. package/dist/capabilities/files.d.ts.map +1 -0
  25. package/dist/capabilities/files.js +100 -0
  26. package/dist/capabilities/files.js.map +1 -0
  27. package/dist/capabilities/helpers.d.ts +27 -0
  28. package/dist/capabilities/helpers.d.ts.map +1 -0
  29. package/dist/capabilities/helpers.js +63 -0
  30. package/dist/capabilities/helpers.js.map +1 -0
  31. package/dist/capabilities/import-export.d.ts +3 -0
  32. package/dist/capabilities/import-export.d.ts.map +1 -0
  33. package/dist/capabilities/import-export.js +267 -0
  34. package/dist/capabilities/import-export.js.map +1 -0
  35. package/dist/capabilities/index.d.ts +16 -0
  36. package/dist/capabilities/index.d.ts.map +1 -0
  37. package/dist/capabilities/index.js +71 -0
  38. package/dist/capabilities/index.js.map +1 -0
  39. package/dist/capabilities/jobs.d.ts +3 -0
  40. package/dist/capabilities/jobs.d.ts.map +1 -0
  41. package/dist/capabilities/jobs.js +117 -0
  42. package/dist/capabilities/jobs.js.map +1 -0
  43. package/dist/capabilities/notifications.d.ts +3 -0
  44. package/dist/capabilities/notifications.d.ts.map +1 -0
  45. package/dist/capabilities/notifications.js +113 -0
  46. package/dist/capabilities/notifications.js.map +1 -0
  47. package/dist/capabilities/search.d.ts +3 -0
  48. package/dist/capabilities/search.d.ts.map +1 -0
  49. package/dist/capabilities/search.js +121 -0
  50. package/dist/capabilities/search.js.map +1 -0
  51. package/dist/capabilities/settings.d.ts +3 -0
  52. package/dist/capabilities/settings.d.ts.map +1 -0
  53. package/dist/capabilities/settings.js +75 -0
  54. package/dist/capabilities/settings.js.map +1 -0
  55. package/dist/capabilities/types.d.ts +16 -0
  56. package/dist/capabilities/types.d.ts.map +1 -0
  57. package/dist/capabilities/types.js +3 -0
  58. package/dist/capabilities/types.js.map +1 -0
  59. package/dist/capabilities/webhooks.d.ts +3 -0
  60. package/dist/capabilities/webhooks.d.ts.map +1 -0
  61. package/dist/capabilities/webhooks.js +117 -0
  62. package/dist/capabilities/webhooks.js.map +1 -0
  63. package/dist/cli.d.ts +3 -0
  64. package/dist/cli.d.ts.map +1 -0
  65. package/dist/cli.js +60 -0
  66. package/dist/cli.js.map +1 -0
  67. package/dist/commands/add.d.ts +3 -0
  68. package/dist/commands/add.d.ts.map +1 -0
  69. package/dist/commands/add.js +88 -0
  70. package/dist/commands/add.js.map +1 -0
  71. package/dist/commands/doctor.d.ts +3 -0
  72. package/dist/commands/doctor.d.ts.map +1 -0
  73. package/dist/commands/doctor.js +94 -0
  74. package/dist/commands/doctor.js.map +1 -0
  75. package/dist/commands/generate.d.ts +3 -0
  76. package/dist/commands/generate.d.ts.map +1 -0
  77. package/dist/commands/generate.js +41 -0
  78. package/dist/commands/generate.js.map +1 -0
  79. package/dist/commands/inspect.d.ts +3 -0
  80. package/dist/commands/inspect.d.ts.map +1 -0
  81. package/dist/commands/inspect.js +64 -0
  82. package/dist/commands/inspect.js.map +1 -0
  83. package/dist/commands/new.d.ts +18 -0
  84. package/dist/commands/new.d.ts.map +1 -0
  85. package/dist/commands/new.js +149 -0
  86. package/dist/commands/new.js.map +1 -0
  87. package/dist/commands/shared.d.ts +14 -0
  88. package/dist/commands/shared.d.ts.map +1 -0
  89. package/dist/commands/shared.js +36 -0
  90. package/dist/commands/shared.js.map +1 -0
  91. package/dist/commands/skill.d.ts +3 -0
  92. package/dist/commands/skill.d.ts.map +1 -0
  93. package/dist/commands/skill.js +59 -0
  94. package/dist/commands/skill.js.map +1 -0
  95. package/dist/commands/stack.d.ts +13 -0
  96. package/dist/commands/stack.d.ts.map +1 -0
  97. package/dist/commands/stack.js +192 -0
  98. package/dist/commands/stack.js.map +1 -0
  99. package/dist/generators/api.d.ts +16 -0
  100. package/dist/generators/api.d.ts.map +1 -0
  101. package/dist/generators/api.js +375 -0
  102. package/dist/generators/api.js.map +1 -0
  103. package/dist/generators/lwd.d.ts +12 -0
  104. package/dist/generators/lwd.d.ts.map +1 -0
  105. package/dist/generators/lwd.js +143 -0
  106. package/dist/generators/lwd.js.map +1 -0
  107. package/dist/generators/mobile.d.ts +16 -0
  108. package/dist/generators/mobile.d.ts.map +1 -0
  109. package/dist/generators/mobile.js +180 -0
  110. package/dist/generators/mobile.js.map +1 -0
  111. package/dist/generators/platform-package.d.ts +18 -0
  112. package/dist/generators/platform-package.d.ts.map +1 -0
  113. package/dist/generators/platform-package.js +568 -0
  114. package/dist/generators/platform-package.js.map +1 -0
  115. package/dist/generators/root.d.ts +15 -0
  116. package/dist/generators/root.d.ts.map +1 -0
  117. package/dist/generators/root.js +249 -0
  118. package/dist/generators/root.js.map +1 -0
  119. package/dist/generators/sdk.d.ts +14 -0
  120. package/dist/generators/sdk.d.ts.map +1 -0
  121. package/dist/generators/sdk.js +137 -0
  122. package/dist/generators/sdk.js.map +1 -0
  123. package/dist/generators/web.d.ts +15 -0
  124. package/dist/generators/web.d.ts.map +1 -0
  125. package/dist/generators/web.js +203 -0
  126. package/dist/generators/web.js.map +1 -0
  127. package/dist/generators/worker.d.ts +13 -0
  128. package/dist/generators/worker.d.ts.map +1 -0
  129. package/dist/generators/worker.js +148 -0
  130. package/dist/generators/worker.js.map +1 -0
  131. package/dist/index.d.ts +10 -0
  132. package/dist/index.d.ts.map +1 -0
  133. package/dist/index.js +44 -0
  134. package/dist/index.js.map +1 -0
  135. package/dist/project/context.d.ts +24 -0
  136. package/dist/project/context.d.ts.map +1 -0
  137. package/dist/project/context.js +63 -0
  138. package/dist/project/context.js.map +1 -0
  139. package/dist/project/manifest.d.ts +26 -0
  140. package/dist/project/manifest.d.ts.map +1 -0
  141. package/dist/project/manifest.js +73 -0
  142. package/dist/project/manifest.js.map +1 -0
  143. package/dist/project/paths.d.ts +25 -0
  144. package/dist/project/paths.d.ts.map +1 -0
  145. package/dist/project/paths.js +51 -0
  146. package/dist/project/paths.js.map +1 -0
  147. package/dist/project/plan.d.ts +42 -0
  148. package/dist/project/plan.d.ts.map +1 -0
  149. package/dist/project/plan.js +251 -0
  150. package/dist/project/plan.js.map +1 -0
  151. package/dist/prompts/index.d.ts +24 -0
  152. package/dist/prompts/index.d.ts.map +1 -0
  153. package/dist/prompts/index.js +111 -0
  154. package/dist/prompts/index.js.map +1 -0
  155. package/dist/recipes/index.d.ts +25 -0
  156. package/dist/recipes/index.d.ts.map +1 -0
  157. package/dist/recipes/index.js +77 -0
  158. package/dist/recipes/index.js.map +1 -0
  159. package/dist/stack/infer.d.ts +4 -0
  160. package/dist/stack/infer.d.ts.map +1 -0
  161. package/dist/stack/infer.js +165 -0
  162. package/dist/stack/infer.js.map +1 -0
  163. package/dist/stack/lwd.d.ts +24 -0
  164. package/dist/stack/lwd.d.ts.map +1 -0
  165. package/dist/stack/lwd.js +62 -0
  166. package/dist/stack/lwd.js.map +1 -0
  167. package/dist/stack/lwdtoml.d.ts +38 -0
  168. package/dist/stack/lwdtoml.d.ts.map +1 -0
  169. package/dist/stack/lwdtoml.js +57 -0
  170. package/dist/stack/lwdtoml.js.map +1 -0
  171. package/dist/stack/manifest.d.ts +15 -0
  172. package/dist/stack/manifest.d.ts.map +1 -0
  173. package/dist/stack/manifest.js +86 -0
  174. package/dist/stack/manifest.js.map +1 -0
  175. package/dist/stack/types.d.ts +48 -0
  176. package/dist/stack/types.d.ts.map +1 -0
  177. package/dist/stack/types.js +5 -0
  178. package/dist/stack/types.js.map +1 -0
  179. package/dist/stack/wire.d.ts +40 -0
  180. package/dist/stack/wire.d.ts.map +1 -0
  181. package/dist/stack/wire.js +192 -0
  182. package/dist/stack/wire.js.map +1 -0
  183. package/dist/types.d.ts +114 -0
  184. package/dist/types.d.ts.map +1 -0
  185. package/dist/types.js +5 -0
  186. package/dist/types.js.map +1 -0
  187. package/dist/utils/fs.d.ts +13 -0
  188. package/dist/utils/fs.d.ts.map +1 -0
  189. package/dist/utils/fs.js +64 -0
  190. package/dist/utils/fs.js.map +1 -0
  191. package/dist/utils/git.d.ts +17 -0
  192. package/dist/utils/git.d.ts.map +1 -0
  193. package/dist/utils/git.js +49 -0
  194. package/dist/utils/git.js.map +1 -0
  195. package/dist/utils/logger.d.ts +16 -0
  196. package/dist/utils/logger.d.ts.map +1 -0
  197. package/dist/utils/logger.js +21 -0
  198. package/dist/utils/logger.js.map +1 -0
  199. package/dist/utils/shell.d.ts +21 -0
  200. package/dist/utils/shell.d.ts.map +1 -0
  201. package/dist/utils/shell.js +40 -0
  202. package/dist/utils/shell.js.map +1 -0
  203. package/dist/version.d.ts +2 -0
  204. package/dist/version.d.ts.map +1 -0
  205. package/dist/version.js +6 -0
  206. package/dist/version.js.map +1 -0
  207. package/package.json +40 -0
  208. package/skills/obh-add-events/SKILL.md +26 -0
  209. package/skills/obh-generate-audit-rules/SKILL.md +26 -0
  210. package/skills/obh-inspect-project/SKILL.md +26 -0
  211. package/skills/obh-lwd-manifest/SKILL.md +26 -0
  212. package/skills/obh-retrofit-files/SKILL.md +24 -0
  213. package/skills/obh-retrofit-jobs/SKILL.md +26 -0
  214. package/skills/obh-sdk-extraction/SKILL.md +24 -0
  215. package/skills/obh-settings-migration/SKILL.md +24 -0
@@ -0,0 +1,249 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.addProjectSkeleton = addProjectSkeleton;
4
+ exports.envBase = envBase;
5
+ exports.ciYaml = ciYaml;
6
+ /**
7
+ * Adds the project-level skeleton shared by every recipe: workspace config,
8
+ * base tsconfig, migration runner, CI, and docs. Everything here is a safe
9
+ * "create" — re-running never clobbers edited files.
10
+ */
11
+ function addProjectSkeleton(plan, opts) {
12
+ const { name } = opts;
13
+ plan.create("package.json", JSON.stringify({
14
+ name,
15
+ private: true,
16
+ version: "0.1.0",
17
+ packageManager: "pnpm@9.7.0",
18
+ engines: { node: ">=20" },
19
+ scripts: {
20
+ dev: "pnpm -r --parallel --filter './apps/*' dev",
21
+ build: "pnpm -r build",
22
+ typecheck: "pnpm -r typecheck",
23
+ test: "pnpm -r test",
24
+ migrate: "tsx scripts/migrate.ts",
25
+ },
26
+ devDependencies: {
27
+ "@types/node": "^20.14.0",
28
+ "@types/pg": "^8.11.6",
29
+ pg: "^8.12.0",
30
+ tsx: "^4.16.0",
31
+ typescript: "^5.5.4",
32
+ },
33
+ }, null, 2) + "\n", "root workspace package.json");
34
+ plan.create("pnpm-workspace.yaml", ["packages:", " - apps/*", " - packages/*", ""].join("\n"), "pnpm workspace definition");
35
+ plan.create("tsconfig.base.json", JSON.stringify({
36
+ compilerOptions: {
37
+ target: "ES2022",
38
+ module: "commonjs",
39
+ moduleResolution: "node",
40
+ lib: ["ES2022"],
41
+ declaration: true,
42
+ sourceMap: true,
43
+ strict: true,
44
+ esModuleInterop: true,
45
+ skipLibCheck: true,
46
+ forceConsistentCasingInFileNames: true,
47
+ resolveJsonModule: true,
48
+ types: ["node"],
49
+ },
50
+ }, null, 2) + "\n", "shared TypeScript base config (CommonJS, matches @obh/* packages)");
51
+ plan.create(".gitignore", ["node_modules/", "dist/", "coverage/", "*.log", ".env", ".env.local", ".DS_Store", ""].join("\n"), "gitignore");
52
+ plan.create(".env.example", envBase(name), "environment example");
53
+ plan.create("migrations/0001_init.sql", INIT_SQL, "initial product migration");
54
+ plan.create("scripts/migrate.ts", MIGRATE_RUNNER, "migration runner (SQL files + platform primitives)");
55
+ plan.create("scripts/migrations.d/.gitkeep", "", "platform migration wiring directory");
56
+ plan.create(".github/workflows/ci.yml", ciYaml(name), "GitHub Actions CI (build, typecheck, test, Postgres service)");
57
+ plan.create("README.md", readme(name), "project readme");
58
+ plan.create("AGENTS.md", agents(name), "conventions doc for humans and Claude skills");
59
+ }
60
+ const INIT_SQL = `-- 0001_init.sql — product schema baseline.
61
+ -- Product tables live in the default (public) schema.
62
+ -- OBH platform primitives own their own tables under the "platform" schema
63
+ -- and are migrated by their packages via scripts/migrations.d/*.
64
+
65
+ create table if not exists health_check (
66
+ id integer primary key default 1,
67
+ checked_at timestamptz not null default now()
68
+ );
69
+
70
+ insert into health_check (id) values (1) on conflict (id) do nothing;
71
+ `;
72
+ const MIGRATE_RUNNER = `#!/usr/bin/env tsx
73
+ /**
74
+ * Migration runner. Applies:
75
+ * 1. Product SQL migrations in migrations/*.sql (tracked in _forge_migrations).
76
+ * 2. Platform primitive migrations registered in scripts/migrations.d/*.
77
+ *
78
+ * Each \`forge add <primitive>\` drops a file into scripts/migrations.d/ that
79
+ * exports \`migrate(pool)\`. This runner discovers them — nothing here is patched
80
+ * by hand.
81
+ */
82
+ import { readdirSync, readFileSync } from "node:fs"
83
+ import path from "node:path"
84
+ import { Pool } from "pg"
85
+
86
+ async function main(): Promise<void> {
87
+ const databaseUrl = process.env.DATABASE_URL
88
+ if (!databaseUrl) throw new Error("DATABASE_URL is not set")
89
+ const pool = new Pool({ connectionString: databaseUrl })
90
+
91
+ try {
92
+ await pool.query(
93
+ \`create table if not exists _forge_migrations (
94
+ name text primary key,
95
+ applied_at timestamptz not null default now()
96
+ )\`,
97
+ )
98
+
99
+ // 1. Product SQL migrations, in filename order.
100
+ const migrationsDir = path.join(__dirname, "..", "migrations")
101
+ const sqlFiles = readdirSync(migrationsDir).filter((f) => f.endsWith(".sql")).sort()
102
+ for (const file of sqlFiles) {
103
+ const done = await pool.query("select 1 from _forge_migrations where name = $1", [file])
104
+ if (done.rowCount) continue
105
+ const sql = readFileSync(path.join(migrationsDir, file), "utf8")
106
+ await pool.query(sql)
107
+ await pool.query("insert into _forge_migrations (name) values ($1)", [file])
108
+ console.log("applied", file)
109
+ }
110
+
111
+ // 2. Platform primitive migrations (each capability drops a module here).
112
+ const dir = path.join(__dirname, "migrations.d")
113
+ const modules = readdirSync(dir).filter((f) => /\\.(ts|js)$/.test(f) && !f.startsWith("."))
114
+ for (const file of modules.sort()) {
115
+ const mod = await import(path.join(dir, file))
116
+ if (typeof mod.migrate === "function") {
117
+ await mod.migrate(pool)
118
+ console.log("migrated platform:", file)
119
+ }
120
+ }
121
+ } finally {
122
+ await pool.end()
123
+ }
124
+ }
125
+
126
+ main().catch((err) => {
127
+ console.error(err)
128
+ process.exit(1)
129
+ })
130
+ `;
131
+ function envBase(name) {
132
+ return `# ${name} environment
133
+ # Copy to .env and fill in. Never commit real secrets.
134
+
135
+ DATABASE_URL=postgres://postgres:postgres@localhost:5432/${name}
136
+ `;
137
+ }
138
+ function ciYaml(name) {
139
+ return `name: CI
140
+
141
+ on:
142
+ push:
143
+ branches: [main]
144
+ pull_request:
145
+
146
+ jobs:
147
+ build-test:
148
+ runs-on: ubuntu-latest
149
+
150
+ services:
151
+ postgres:
152
+ image: postgres:16
153
+ env:
154
+ POSTGRES_USER: postgres
155
+ POSTGRES_PASSWORD: postgres
156
+ POSTGRES_DB: ${name}_test
157
+ ports:
158
+ - 5432:5432
159
+ options: >-
160
+ --health-cmd "pg_isready -U postgres"
161
+ --health-interval 10s
162
+ --health-timeout 5s
163
+ --health-retries 5
164
+
165
+ env:
166
+ DATABASE_URL: postgres://postgres:postgres@localhost:5432/${name}_test
167
+
168
+ steps:
169
+ - uses: actions/checkout@v4
170
+ - uses: pnpm/action-setup@v4
171
+ with:
172
+ version: 9
173
+ - uses: actions/setup-node@v4
174
+ with:
175
+ node-version: 20
176
+ cache: pnpm
177
+ - run: pnpm install --no-frozen-lockfile
178
+ - run: pnpm build
179
+ - run: pnpm typecheck
180
+ - run: pnpm test
181
+ `;
182
+ }
183
+ function readme(name) {
184
+ return `# ${name}
185
+
186
+ Scaffolded with [OBH Forge](https://github.com/EObrien60/forge).
187
+
188
+ ## Layout
189
+
190
+ \`\`\`
191
+ apps/ deployable applications (api, admin, worker, …)
192
+ packages/ shared internal packages (sdk, ui, config)
193
+ deploy/ lwd deployment manifests
194
+ docker/ Dockerfiles
195
+ migrations/ product SQL migrations
196
+ scripts/ migrate, seed, dev helpers
197
+ forge.json record of what Forge generated (see below)
198
+ \`\`\`
199
+
200
+ ## Develop
201
+
202
+ \`\`\`bash
203
+ pnpm install
204
+ pnpm migrate # apply product + platform migrations (needs DATABASE_URL)
205
+ pnpm dev # run all apps
206
+ \`\`\`
207
+
208
+ ## Platform primitives
209
+
210
+ Add OBH platform capabilities incrementally — each is opt-in:
211
+
212
+ \`\`\`bash
213
+ forge add events
214
+ forge add jobs
215
+ forge add files
216
+ forge add audit
217
+ \`\`\`
218
+
219
+ \`forge inspect\` reports the project shape; \`forge doctor\` checks it against
220
+ OBH conventions. \`forge.json\` records what was generated.
221
+
222
+ ## Deploy
223
+
224
+ Deployment manifests live in \`deploy/*.lwd.toml\` and target lwd. Set secret
225
+ values out-of-band with \`lwd secret set <app> <KEY>\` — manifests contain secret
226
+ names only.
227
+ `;
228
+ }
229
+ function agents(name) {
230
+ return `# ${name} — conventions
231
+
232
+ This project was scaffolded by OBH Forge. Conventions Claude skills and humans
233
+ can rely on:
234
+
235
+ - **Package manager:** pnpm workspace (\`apps/*\`, \`packages/*\`).
236
+ - **Language:** TypeScript, \`strict\`, CommonJS (matches \`@obh/*\` packages).
237
+ - **Database:** Postgres. Product tables in \`public\`; platform primitives own
238
+ the \`platform\` schema. No ORM auto-sync — migrations run via \`pnpm migrate\`.
239
+ - **API:** routes in \`apps/api/src/routes/*.ts\`, each exporting \`register(app)\`;
240
+ the server auto-mounts them. Add routes by dropping files, not editing a router.
241
+ - **Worker:** consumers in \`apps/worker/src/consumers.d/*.ts\`, auto-loaded.
242
+ - **Migrations:** platform primitives register in \`scripts/migrations.d/*.ts\`.
243
+ - **Deploy:** lwd manifests in \`deploy/*.lwd.toml\`; secret names only.
244
+ - **Record:** \`forge.json\` tracks generated shape and installed primitives.
245
+
246
+ Prefer boring, explicit code. Generated files are yours to edit or delete.
247
+ `;
248
+ }
249
+ //# sourceMappingURL=root.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"root.js","sourceRoot":"","sources":["../../src/generators/root.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ import type { ExampleDomain } from "../types";
2
+ import type { Plan } from "../project/plan";
3
+ export interface SdkOptions {
4
+ scope: string;
5
+ example: ExampleDomain;
6
+ }
7
+ /**
8
+ * Generates the shared SDK package: the typed contract between backend and
9
+ * frontends. This is the piece whose absence produced qMechanic's 3,200-line
10
+ * defensive apiClient, so Forge makes it the one place request/response shapes
11
+ * are defined.
12
+ */
13
+ export declare function addSdkPackage(plan: Plan, opts: SdkOptions): void;
14
+ //# sourceMappingURL=sdk.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sdk.d.ts","sourceRoot":"","sources":["../../src/generators/sdk.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAC7C,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAA;AAE3C,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,aAAa,CAAA;CACvB;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,UAAU,GAAG,IAAI,CAwDhE"}
@@ -0,0 +1,137 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.addSdkPackage = addSdkPackage;
4
+ /**
5
+ * Generates the shared SDK package: the typed contract between backend and
6
+ * frontends. This is the piece whose absence produced qMechanic's 3,200-line
7
+ * defensive apiClient, so Forge makes it the one place request/response shapes
8
+ * are defined.
9
+ */
10
+ function addSdkPackage(plan, opts) {
11
+ const dir = "packages/sdk";
12
+ const notes = opts.example === "notes";
13
+ plan.create(`${dir}/package.json`, JSON.stringify({
14
+ name: `${opts.scope}/sdk`,
15
+ version: "0.1.0",
16
+ private: true,
17
+ // ESM so Vite/Rollup can tree-shake the named exports (a CommonJS SDK
18
+ // breaks `vite build` in a consuming frontend).
19
+ type: "module",
20
+ main: "dist/index.js",
21
+ module: "dist/index.js",
22
+ types: "dist/index.d.ts",
23
+ files: ["dist"],
24
+ scripts: {
25
+ build: "tsc -p tsconfig.json",
26
+ typecheck: "tsc -p tsconfig.json --noEmit",
27
+ test: "vitest run --passWithNoTests",
28
+ },
29
+ devDependencies: { typescript: "^5.5.4", vitest: "^2.0.5" },
30
+ }, null, 2) + "\n", "SDK package.json");
31
+ plan.create(`${dir}/tsconfig.json`, JSON.stringify({
32
+ extends: "../../tsconfig.base.json",
33
+ compilerOptions: {
34
+ rootDir: "src",
35
+ outDir: "dist",
36
+ // Emit ESM (overrides the CommonJS base) so frontends bundle it cleanly.
37
+ module: "ESNext",
38
+ moduleResolution: "bundler",
39
+ lib: ["ES2022", "DOM"],
40
+ },
41
+ include: ["src"],
42
+ exclude: ["dist", "node_modules", "src/**/*.test.ts"],
43
+ }, null, 2) + "\n", "SDK tsconfig");
44
+ plan.create(`${dir}/src/index.ts`, INDEX, "SDK entrypoint");
45
+ plan.create(`${dir}/src/types.ts`, notes ? TYPES_NOTES : TYPES_BASE, "shared domain types");
46
+ plan.create(`${dir}/src/client.ts`, notes ? CLIENT_NOTES : CLIENT_BASE, "typed API client");
47
+ }
48
+ const INDEX = `// Public surface of the SDK — the one contract shared by API and frontends.
49
+ export * from "./types"
50
+ export { createClient } from "./client"
51
+ export type { Client, ClientOptions } from "./client"
52
+ `;
53
+ const TYPES_BASE = `export interface HealthStatus {
54
+ status: "ok" | "degraded"
55
+ }
56
+ `;
57
+ const TYPES_NOTES = `export interface HealthStatus {
58
+ status: "ok" | "degraded"
59
+ }
60
+
61
+ export interface Note {
62
+ id: string
63
+ title: string
64
+ body: string
65
+ createdAt: string
66
+ updatedAt: string
67
+ }
68
+
69
+ export interface CreateNoteInput {
70
+ title: string
71
+ body?: string
72
+ }
73
+
74
+ export interface UpdateNoteInput {
75
+ title?: string
76
+ body?: string
77
+ }
78
+ `;
79
+ const CLIENT_HEAD = `import type { HealthStatus } from "./types"
80
+
81
+ export interface ClientOptions {
82
+ baseUrl: string
83
+ /** Optional bearer token provider. */
84
+ token?: () => string | null
85
+ }
86
+
87
+ async function request<T>(opts: ClientOptions, path: string, init?: RequestInit): Promise<T> {
88
+ const headers = new Headers(init?.headers)
89
+ if (init?.body) headers.set("content-type", "application/json")
90
+ const token = opts.token?.()
91
+ if (token) headers.set("authorization", "Bearer " + token)
92
+ const res = await fetch(opts.baseUrl + path, { ...init, headers })
93
+ if (!res.ok) throw new Error(path + " failed: " + res.status)
94
+ if (res.status === 204) return undefined as T
95
+ return (await res.json()) as T
96
+ }
97
+ `;
98
+ const CLIENT_BASE = `${CLIENT_HEAD}
99
+ export interface Client {
100
+ health(): Promise<HealthStatus>
101
+ }
102
+
103
+ /** A thin typed fetch client. Add one method per API resource. */
104
+ export function createClient(opts: ClientOptions): Client {
105
+ return {
106
+ health: () => request<HealthStatus>(opts, "/health"),
107
+ }
108
+ }
109
+ `;
110
+ const CLIENT_NOTES = `import type { CreateNoteInput, Note, UpdateNoteInput } from "./types"
111
+ ${CLIENT_HEAD}
112
+ export interface Client {
113
+ health(): Promise<HealthStatus>
114
+ notes: {
115
+ list(): Promise<Note[]>
116
+ get(id: string): Promise<Note>
117
+ create(input: CreateNoteInput): Promise<Note>
118
+ update(id: string, input: UpdateNoteInput): Promise<Note>
119
+ remove(id: string): Promise<void>
120
+ }
121
+ }
122
+
123
+ /** A thin typed fetch client — the single contract shared with every frontend. */
124
+ export function createClient(opts: ClientOptions): Client {
125
+ return {
126
+ health: () => request<HealthStatus>(opts, "/health"),
127
+ notes: {
128
+ list: () => request<Note[]>(opts, "/notes"),
129
+ get: (id) => request<Note>(opts, "/notes/" + id),
130
+ create: (input) => request<Note>(opts, "/notes", { method: "POST", body: JSON.stringify(input) }),
131
+ update: (id, input) => request<Note>(opts, "/notes/" + id, { method: "PUT", body: JSON.stringify(input) }),
132
+ remove: (id) => request<void>(opts, "/notes/" + id, { method: "DELETE" }),
133
+ },
134
+ }
135
+ }
136
+ `;
137
+ //# sourceMappingURL=sdk.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sdk.js","sourceRoot":"","sources":["../../src/generators/sdk.ts"],"names":[],"mappings":";;AAcA,sCAwDC;AA9DD;;;;;GAKG;AACH,SAAgB,aAAa,CAAC,IAAU,EAAE,IAAgB;IACxD,MAAM,GAAG,GAAG,cAAc,CAAA;IAC1B,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,KAAK,OAAO,CAAA;IAEtC,IAAI,CAAC,MAAM,CACT,GAAG,GAAG,eAAe,EACrB,IAAI,CAAC,SAAS,CACZ;QACE,IAAI,EAAE,GAAG,IAAI,CAAC,KAAK,MAAM;QACzB,OAAO,EAAE,OAAO;QAChB,OAAO,EAAE,IAAI;QACb,sEAAsE;QACtE,gDAAgD;QAChD,IAAI,EAAE,QAAQ;QACd,IAAI,EAAE,eAAe;QACrB,MAAM,EAAE,eAAe;QACvB,KAAK,EAAE,iBAAiB;QACxB,KAAK,EAAE,CAAC,MAAM,CAAC;QACf,OAAO,EAAE;YACP,KAAK,EAAE,sBAAsB;YAC7B,SAAS,EAAE,+BAA+B;YAC1C,IAAI,EAAE,8BAA8B;SACrC;QACD,eAAe,EAAE,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE;KAC5D,EACD,IAAI,EACJ,CAAC,CACF,GAAG,IAAI,EACR,kBAAkB,CACnB,CAAA;IAED,IAAI,CAAC,MAAM,CACT,GAAG,GAAG,gBAAgB,EACtB,IAAI,CAAC,SAAS,CACZ;QACE,OAAO,EAAE,0BAA0B;QACnC,eAAe,EAAE;YACf,OAAO,EAAE,KAAK;YACd,MAAM,EAAE,MAAM;YACd,yEAAyE;YACzE,MAAM,EAAE,QAAQ;YAChB,gBAAgB,EAAE,SAAS;YAC3B,GAAG,EAAE,CAAC,QAAQ,EAAE,KAAK,CAAC;SACvB;QACD,OAAO,EAAE,CAAC,KAAK,CAAC;QAChB,OAAO,EAAE,CAAC,MAAM,EAAE,cAAc,EAAE,kBAAkB,CAAC;KACtD,EACD,IAAI,EACJ,CAAC,CACF,GAAG,IAAI,EACR,cAAc,CACf,CAAA;IAED,IAAI,CAAC,MAAM,CAAC,GAAG,GAAG,eAAe,EAAE,KAAK,EAAE,gBAAgB,CAAC,CAAA;IAC3D,IAAI,CAAC,MAAM,CAAC,GAAG,GAAG,eAAe,EAAE,KAAK,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,UAAU,EAAE,qBAAqB,CAAC,CAAA;IAC3F,IAAI,CAAC,MAAM,CAAC,GAAG,GAAG,gBAAgB,EAAE,KAAK,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,WAAW,EAAE,kBAAkB,CAAC,CAAA;AAC7F,CAAC;AAED,MAAM,KAAK,GAAG;;;;CAIb,CAAA;AAED,MAAM,UAAU,GAAG;;;CAGlB,CAAA;AAED,MAAM,WAAW,GAAG;;;;;;;;;;;;;;;;;;;;;CAqBnB,CAAA;AAED,MAAM,WAAW,GAAG;;;;;;;;;;;;;;;;;;CAkBnB,CAAA;AAED,MAAM,WAAW,GAAG,GAAG,WAAW;;;;;;;;;;;CAWjC,CAAA;AAED,MAAM,YAAY,GAAG;EACnB,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;CAyBZ,CAAA"}
@@ -0,0 +1,15 @@
1
+ import type { ExampleDomain } from "../types";
2
+ import type { Plan } from "../project/plan";
3
+ export interface WebOptions {
4
+ scope: string;
5
+ /** App directory name, e.g. "admin". */
6
+ name: string;
7
+ example: ExampleDomain;
8
+ }
9
+ /**
10
+ * Generates a Vite + React admin frontend that consumes the shared SDK. Because
11
+ * it imports the workspace SDK, its Docker build runs from the repo root
12
+ * (lwd git.path = ".") and filters to this app — see the generated Dockerfile.
13
+ */
14
+ export declare function addWebApp(plan: Plan, opts: WebOptions): void;
15
+ //# sourceMappingURL=web.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"web.d.ts","sourceRoot":"","sources":["../../src/generators/web.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAC7C,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAA;AAE3C,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAA;IACb,wCAAwC;IACxC,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,aAAa,CAAA;CACvB;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,UAAU,GAAG,IAAI,CAwE5D"}
@@ -0,0 +1,203 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.addWebApp = addWebApp;
4
+ /**
5
+ * Generates a Vite + React admin frontend that consumes the shared SDK. Because
6
+ * it imports the workspace SDK, its Docker build runs from the repo root
7
+ * (lwd git.path = ".") and filters to this app — see the generated Dockerfile.
8
+ */
9
+ function addWebApp(plan, opts) {
10
+ const dir = `apps/${opts.name}`;
11
+ const pkgName = `${opts.scope}/${opts.name}`;
12
+ plan.create(`${dir}/package.json`, JSON.stringify({
13
+ name: pkgName,
14
+ version: "0.1.0",
15
+ private: true,
16
+ type: "module",
17
+ scripts: {
18
+ dev: "vite",
19
+ build: "vite build",
20
+ preview: "vite preview",
21
+ typecheck: "tsc --noEmit",
22
+ test: "vitest run --passWithNoTests",
23
+ },
24
+ dependencies: {
25
+ [`${opts.scope}/sdk`]: "workspace:*",
26
+ react: "^18.3.1",
27
+ "react-dom": "^18.3.1",
28
+ "react-router-dom": "^7.9.3",
29
+ },
30
+ devDependencies: {
31
+ "@types/react": "^18.3.3",
32
+ "@types/react-dom": "^18.3.0",
33
+ "@vitejs/plugin-react": "^4.3.1",
34
+ typescript: "^5.5.4",
35
+ vite: "^5.4.0",
36
+ vitest: "^2.0.5",
37
+ },
38
+ }, null, 2) + "\n", "admin app package.json");
39
+ plan.create(`${dir}/tsconfig.json`, JSON.stringify({
40
+ compilerOptions: {
41
+ target: "ES2022",
42
+ lib: ["ES2022", "DOM", "DOM.Iterable"],
43
+ module: "ESNext",
44
+ moduleResolution: "bundler",
45
+ jsx: "react-jsx",
46
+ strict: true,
47
+ skipLibCheck: true,
48
+ esModuleInterop: true,
49
+ resolveJsonModule: true,
50
+ noEmit: true,
51
+ },
52
+ include: ["src"],
53
+ }, null, 2) + "\n", "admin tsconfig (ESM/bundler for Vite)");
54
+ plan.create(`${dir}/vite.config.ts`, VITE_CONFIG, "vite config");
55
+ plan.create(`${dir}/index.html`, indexHtml(opts.name), "admin index.html");
56
+ plan.create(`${dir}/src/main.tsx`, MAIN, "admin entry");
57
+ plan.create(`${dir}/src/vite-env.d.ts`, '/// <reference types="vite/client" />\n', "vite client types (import.meta.env)");
58
+ plan.create(`${dir}/src/api.ts`, apiTs(opts.scope, opts.example), "admin API client instance");
59
+ plan.create(`${dir}/src/App.tsx`, opts.example === "notes" ? APP_NOTES : APP_BASE, "admin App");
60
+ plan.create(`${dir}/nginx.conf`, NGINX, "nginx SPA config");
61
+ plan.create(`${dir}/Dockerfile`, dockerfile(pkgName, opts.name), "admin Dockerfile (root-context build)");
62
+ }
63
+ const VITE_CONFIG = `import { defineConfig } from "vite"
64
+ import react from "@vitejs/plugin-react"
65
+
66
+ export default defineConfig({
67
+ plugins: [react()],
68
+ })
69
+ `;
70
+ function indexHtml(name) {
71
+ return `<!doctype html>
72
+ <html lang="en">
73
+ <head>
74
+ <meta charset="UTF-8" />
75
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
76
+ <title>${name}</title>
77
+ </head>
78
+ <body>
79
+ <div id="root"></div>
80
+ <script type="module" src="/src/main.tsx"></script>
81
+ </body>
82
+ </html>
83
+ `;
84
+ }
85
+ const MAIN = `import React from "react"
86
+ import { createRoot } from "react-dom/client"
87
+ import { App } from "./App"
88
+
89
+ createRoot(document.getElementById("root")!).render(
90
+ <React.StrictMode>
91
+ <App />
92
+ </React.StrictMode>,
93
+ )
94
+ `;
95
+ function apiTs(scope, example) {
96
+ const typeReexport = example === "notes"
97
+ ? `export type { HealthStatus, Note, CreateNoteInput, UpdateNoteInput } from "${scope}/sdk"`
98
+ : `export type { HealthStatus } from "${scope}/sdk"`;
99
+ return `import { createClient } from "${scope}/sdk"
100
+ ${typeReexport}
101
+
102
+ export const api = createClient({
103
+ baseUrl: import.meta.env.VITE_API_BASE_URL ?? "http://localhost:8080",
104
+ token: () => localStorage.getItem("token"),
105
+ })
106
+ `;
107
+ }
108
+ const APP_BASE = `import { useEffect, useState } from "react"
109
+ import { api, type HealthStatus } from "./api"
110
+
111
+ export function App(): JSX.Element {
112
+ const [health, setHealth] = useState<HealthStatus | null>(null)
113
+ useEffect(() => {
114
+ api.health().then(setHealth).catch(() => setHealth({ status: "degraded" }))
115
+ }, [])
116
+ return (
117
+ <main style={{ fontFamily: "system-ui", padding: 32, maxWidth: 720, margin: "0 auto" }}>
118
+ <h1>Admin</h1>
119
+ <p>API health: {health?.status ?? "…"}</p>
120
+ </main>
121
+ )
122
+ }
123
+ `;
124
+ const APP_NOTES = `import { useEffect, useState } from "react"
125
+ import { api, type Note } from "./api"
126
+
127
+ export function App(): JSX.Element {
128
+ const [notes, setNotes] = useState<Note[]>([])
129
+ const [title, setTitle] = useState("")
130
+ const [body, setBody] = useState("")
131
+ const [error, setError] = useState<string | null>(null)
132
+
133
+ const reload = () => api.notes.list().then(setNotes).catch((e) => setError(String(e)))
134
+ useEffect(() => {
135
+ reload()
136
+ }, [])
137
+
138
+ const submit = async (e: React.FormEvent) => {
139
+ e.preventDefault()
140
+ if (!title.trim()) return
141
+ await api.notes.create({ title, body })
142
+ setTitle("")
143
+ setBody("")
144
+ await reload()
145
+ }
146
+
147
+ const remove = async (id: string) => {
148
+ await api.notes.remove(id)
149
+ await reload()
150
+ }
151
+
152
+ return (
153
+ <main style={{ fontFamily: "system-ui", padding: 32, maxWidth: 720, margin: "0 auto" }}>
154
+ <h1>Notes</h1>
155
+ {error && <p style={{ color: "crimson" }}>{error}</p>}
156
+
157
+ <form onSubmit={submit} style={{ display: "grid", gap: 8, marginBottom: 24 }}>
158
+ <input value={title} onChange={(e) => setTitle(e.target.value)} placeholder="Title" />
159
+ <textarea value={body} onChange={(e) => setBody(e.target.value)} placeholder="Body" rows={3} />
160
+ <button type="submit">Add note</button>
161
+ </form>
162
+
163
+ <ul style={{ display: "grid", gap: 12, listStyle: "none", padding: 0 }}>
164
+ {notes.map((n) => (
165
+ <li key={n.id} style={{ border: "1px solid #ddd", borderRadius: 8, padding: 12 }}>
166
+ <strong>{n.title}</strong>
167
+ <p style={{ margin: "4px 0", whiteSpace: "pre-wrap" }}>{n.body}</p>
168
+ <button onClick={() => remove(n.id)}>Delete</button>
169
+ </li>
170
+ ))}
171
+ </ul>
172
+ </main>
173
+ )
174
+ }
175
+ `;
176
+ const NGINX = `server {
177
+ listen 80;
178
+ root /usr/share/nginx/html;
179
+ index index.html;
180
+ location / {
181
+ try_files $uri $uri/ /index.html;
182
+ }
183
+ }
184
+ `;
185
+ function dockerfile(pkgName, name) {
186
+ return `# Deployed by lwd with git.path = "." and build.dockerfile = "apps/${name}/Dockerfile"
187
+ # (root context so the workspace SDK is available to the build).
188
+ FROM node:20-alpine AS builder
189
+ WORKDIR /repo
190
+ RUN npm install -g pnpm@9
191
+ COPY . .
192
+ RUN pnpm install --no-frozen-lockfile
193
+ ARG VITE_API_BASE_URL
194
+ ENV VITE_API_BASE_URL=$VITE_API_BASE_URL
195
+ RUN pnpm --filter "${pkgName}" build
196
+
197
+ FROM nginx:1.27-alpine
198
+ COPY --from=builder /repo/apps/${name}/dist /usr/share/nginx/html
199
+ COPY apps/${name}/nginx.conf /etc/nginx/conf.d/default.conf
200
+ EXPOSE 80
201
+ `;
202
+ }
203
+ //# sourceMappingURL=web.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"web.js","sourceRoot":"","sources":["../../src/generators/web.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ import type { Plan } from "../project/plan";
2
+ export interface WorkerOptions {
3
+ scope: string;
4
+ }
5
+ /**
6
+ * Generates a worker app. Consumers live in src/consumers.d/*.ts, each exporting
7
+ * an optional `init(ctx)` and a `tick(ctx)`. The worker loads them by convention
8
+ * and drives them on an interval — capabilities add background work by dropping
9
+ * a file, never by editing the loop. Includes a plain-HTTP health port because
10
+ * lwd surfaces (including workers) require one.
11
+ */
12
+ export declare function addWorkerApp(plan: Plan, opts: WorkerOptions): void;
13
+ //# sourceMappingURL=worker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"worker.d.ts","sourceRoot":"","sources":["../../src/generators/worker.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAA;AAE3C,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAA;CACd;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,aAAa,GAAG,IAAI,CAmDlE"}