@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,568 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.addPlatformPackageProject = addPlatformPackageProject;
4
+ const pascal = (name) => name
5
+ .split(/[-_]/)
6
+ .filter(Boolean)
7
+ .map((s) => s.charAt(0).toUpperCase() + s.slice(1))
8
+ .join("");
9
+ const snake = (name) => name.replace(/-/g, "_");
10
+ /**
11
+ * Generates a complete OBH platform-primitive repo (the qtool template): a pnpm
12
+ * monorepo publishing @obh/<name>, CommonJS + strict, Postgres-native with the
13
+ * standard pgAdapter / logger / ids / migrations kernel, a real record-store
14
+ * client over a platform.<name> table, tests, and CI. Optionally an admin/worker
15
+ * daemon. This is the single source that keeps new primitives consistent.
16
+ */
17
+ function addPlatformPackageProject(plan, opts) {
18
+ const { name, scope, daemon } = opts;
19
+ const P = pascal(name);
20
+ const table = `platform.${snake(name)}`;
21
+ const pkgDir = `packages/${name}`;
22
+ const idPrefix = snake(name).slice(0, 6);
23
+ // ---- repo root ----
24
+ plan.create("package.json", rootPackageJson(name), "root workspace package.json");
25
+ plan.create("pnpm-workspace.yaml", "packages:\n - packages/*\n - apps/*\n", "pnpm workspace");
26
+ plan.create("tsconfig.base.json", TSCONFIG_BASE, "shared TypeScript base (CommonJS, strict)");
27
+ plan.create(".gitignore", "node_modules/\ndist/\ncoverage/\n*.log\n.env\n", "gitignore");
28
+ plan.create("LICENSE", mitLicense(), "MIT license");
29
+ plan.create("README.md", readme(name, scope, P), "readme");
30
+ plan.create(".github/workflows/ci.yml", ci(name), "CI (postgres:16, build/typecheck/test)");
31
+ // ---- packages/<name> ----
32
+ plan.create(`${pkgDir}/package.json`, pkgPackageJson(name, scope), "package.json");
33
+ plan.create(`${pkgDir}/tsconfig.json`, PKG_TSCONFIG, "package tsconfig");
34
+ plan.create(`${pkgDir}/vitest.config.ts`, VITEST, "vitest config");
35
+ plan.create(`${pkgDir}/src/index.ts`, indexTs(P), "public API surface");
36
+ plan.create(`${pkgDir}/src/db.ts`, DB_TS, "db types");
37
+ plan.create(`${pkgDir}/src/adapters/pg.ts`, PG_ADAPTER, "pgAdapter");
38
+ plan.create(`${pkgDir}/src/logger.ts`, LOGGER, "structured logger");
39
+ plan.create(`${pkgDir}/src/ids.ts`, IDS, "id generator");
40
+ plan.create(`${pkgDir}/src/registry.ts`, registryTs(P), "definition registry");
41
+ plan.create(`${pkgDir}/src/client.ts`, clientTs(P, table, idPrefix), "record-store client");
42
+ plan.create(`${pkgDir}/src/migrations.ts`, migrationsTs(name, table), "migration runner");
43
+ plan.create(`${pkgDir}/src/migrations/0001_init.sql`, initSql(table), "initial migration");
44
+ plan.create(`${pkgDir}/src/__tests__/ids.test.ts`, IDS_TEST, "ids unit test");
45
+ plan.create(`${pkgDir}/src/__tests__/registry.test.ts`, registryTest(P), "registry unit test");
46
+ // ---- optional daemon ----
47
+ if (daemon) {
48
+ const appDir = `apps/${name}d`;
49
+ plan.create(`${appDir}/package.json`, daemonPackageJson(name, scope), "daemon package.json");
50
+ plan.create(`${appDir}/tsconfig.json`, PKG_TSCONFIG, "daemon tsconfig");
51
+ plan.create(`${appDir}/src/config.ts`, DAEMON_CONFIG, "daemon config");
52
+ plan.create(`${appDir}/src/migrate.ts`, daemonMigrate(scope, name), "migrate binary");
53
+ plan.create(`${appDir}/src/main.ts`, daemonMain(scope, name), "daemon entry (migrate + heartbeat)");
54
+ }
55
+ plan.nextStep(`cd ${name} && pnpm install && pnpm build`);
56
+ if (daemon)
57
+ plan.nextStep(`Apply schema: DATABASE_URL=… pnpm --filter ${scope}/${name}d migrate`);
58
+ plan.nextStep(`Rename ${P}Record / create${P}Client in packages/${name}/src to your domain.`);
59
+ }
60
+ // ---------------------------------------------------------------------------
61
+ function rootPackageJson(name) {
62
+ return (JSON.stringify({
63
+ name,
64
+ private: true,
65
+ version: "0.1.0",
66
+ packageManager: "pnpm@9.7.0",
67
+ engines: { node: ">=20" },
68
+ scripts: { build: "pnpm -r build", typecheck: "pnpm -r typecheck", test: "pnpm -r test" },
69
+ devDependencies: { typescript: "^5.5.4" },
70
+ }, null, 2) + "\n");
71
+ }
72
+ const TSCONFIG_BASE = `{
73
+ "compilerOptions": {
74
+ "target": "ES2022",
75
+ "module": "commonjs",
76
+ "moduleResolution": "node",
77
+ "lib": ["ES2022"],
78
+ "declaration": true,
79
+ "declarationMap": true,
80
+ "sourceMap": true,
81
+ "strict": true,
82
+ "esModuleInterop": true,
83
+ "skipLibCheck": true,
84
+ "forceConsistentCasingInFileNames": true,
85
+ "resolveJsonModule": true,
86
+ "types": ["node"]
87
+ }
88
+ }
89
+ `;
90
+ const PKG_TSCONFIG = `{
91
+ "extends": "../../tsconfig.base.json",
92
+ "compilerOptions": { "rootDir": "src", "outDir": "dist", "composite": true },
93
+ "include": ["src"],
94
+ "exclude": ["dist", "node_modules", "src/**/*.test.ts"]
95
+ }
96
+ `;
97
+ const VITEST = `import { defineConfig } from "vitest/config"
98
+
99
+ export default defineConfig({ test: { include: ["src/**/*.test.ts"] } })
100
+ `;
101
+ function pkgPackageJson(name, scope) {
102
+ return (JSON.stringify({
103
+ name: `${scope}/${name}`,
104
+ version: "0.1.0",
105
+ license: "MIT",
106
+ main: "dist/index.js",
107
+ types: "dist/index.d.ts",
108
+ files: ["dist", "src/migrations"],
109
+ scripts: {
110
+ build: "tsc -p tsconfig.json",
111
+ typecheck: "tsc -p tsconfig.json --noEmit",
112
+ test: "vitest run",
113
+ },
114
+ devDependencies: {
115
+ "@types/node": "^20.14.0",
116
+ "@types/pg": "^8.11.6",
117
+ pg: "^8.12.0",
118
+ typescript: "^5.5.4",
119
+ vitest: "^2.0.5",
120
+ },
121
+ }, null, 2) + "\n");
122
+ }
123
+ function indexTs(P) {
124
+ return `// Public surface of the package. Keep this small and boring.
125
+ export type { Db, TransactionalDb, QueryResult } from "./db"
126
+ export { pgAdapter } from "./adapters/pg"
127
+ export { createLogger } from "./logger"
128
+ export type { Logger, LogLevel, LogFields } from "./logger"
129
+ export { newId } from "./ids"
130
+
131
+ export { define${P}, create${P}Registry } from "./registry"
132
+ export type { ${P}Definition, ${P}Registry } from "./registry"
133
+
134
+ export { create${P}Client } from "./client"
135
+ export type { ${P}Client, ${P}Record } from "./client"
136
+
137
+ export { runMigrations, migrations, INIT_SQL } from "./migrations"
138
+ export type { Migration } from "./migrations"
139
+ `;
140
+ }
141
+ const DB_TS = `export interface QueryResult<T> {
142
+ rows: T[]
143
+ }
144
+
145
+ export interface Db {
146
+ query<T>(sql: string, params?: unknown[]): Promise<QueryResult<T>>
147
+ }
148
+
149
+ export interface TransactionalDb extends Db {
150
+ transaction<T>(fn: (tx: Db) => Promise<T>): Promise<T>
151
+ }
152
+ `;
153
+ const PG_ADAPTER = `import type { Db, QueryResult, TransactionalDb } from "../db"
154
+
155
+ type PgQueryable = { query(text: string, params?: unknown[]): Promise<{ rows: unknown[] }> }
156
+ type PgClient = PgQueryable & { release: () => void }
157
+ type PgPool = PgQueryable & { connect: () => Promise<PgClient> }
158
+
159
+ /** Wrap a node-postgres Pool as a TransactionalDb. */
160
+ export function pgAdapter(pool: PgPool): TransactionalDb {
161
+ const query = async <T>(sql: string, params?: unknown[]): Promise<QueryResult<T>> => {
162
+ const res = await pool.query(sql, params)
163
+ return { rows: res.rows as T[] }
164
+ }
165
+ return {
166
+ query,
167
+ async transaction<T>(fn: (tx: Db) => Promise<T>): Promise<T> {
168
+ const client = await pool.connect()
169
+ const tx: Db = {
170
+ async query<U>(sql: string, params?: unknown[]): Promise<QueryResult<U>> {
171
+ const res = await client.query(sql, params)
172
+ return { rows: res.rows as U[] }
173
+ },
174
+ }
175
+ try {
176
+ await client.query("begin")
177
+ const result = await fn(tx)
178
+ await client.query("commit")
179
+ return result
180
+ } catch (err) {
181
+ await client.query("rollback")
182
+ throw err
183
+ } finally {
184
+ client.release()
185
+ }
186
+ },
187
+ }
188
+ }
189
+ `;
190
+ const LOGGER = `export type LogLevel = "debug" | "info" | "warn" | "error"
191
+ export type LogFields = Record<string, unknown>
192
+
193
+ export interface Logger {
194
+ debug(msg: string, fields?: LogFields): void
195
+ info(msg: string, fields?: LogFields): void
196
+ warn(msg: string, fields?: LogFields): void
197
+ error(msg: string, fields?: LogFields): void
198
+ child(base: LogFields): Logger
199
+ }
200
+
201
+ const ORDER: Record<LogLevel, number> = { debug: 10, info: 20, warn: 30, error: 40 }
202
+
203
+ export function createLogger(level: LogLevel = "info", base: LogFields = {}): Logger {
204
+ const at = (lvl: LogLevel, msg: string, fields?: LogFields): void => {
205
+ if (ORDER[lvl] < ORDER[level]) return
206
+ console.log(JSON.stringify({ level: lvl, msg, ...base, ...fields }))
207
+ }
208
+ return {
209
+ debug: (m, f) => at("debug", m, f),
210
+ info: (m, f) => at("info", m, f),
211
+ warn: (m, f) => at("warn", m, f),
212
+ error: (m, f) => at("error", m, f),
213
+ child: (b) => createLogger(level, { ...base, ...b }),
214
+ }
215
+ }
216
+ `;
217
+ const IDS = `import { randomUUID } from "node:crypto"
218
+
219
+ /** Generate a prefixed, sortable-enough id, e.g. newId("wid") -> "wid_<uuid>". */
220
+ export function newId(prefix: string): string {
221
+ return prefix + "_" + randomUUID()
222
+ }
223
+ `;
224
+ function registryTs(P) {
225
+ return `export interface ${P}Definition {
226
+ name: string
227
+ }
228
+
229
+ export interface ${P}Registry {
230
+ register(def: ${P}Definition): void
231
+ get(name: string): ${P}Definition | undefined
232
+ list(): ${P}Definition[]
233
+ }
234
+
235
+ export function define${P}(def: ${P}Definition): ${P}Definition {
236
+ return def
237
+ }
238
+
239
+ export function create${P}Registry(): ${P}Registry {
240
+ const defs = new Map<string, ${P}Definition>()
241
+ return {
242
+ register: (def) => void defs.set(def.name, def),
243
+ get: (name) => defs.get(name),
244
+ list: () => [...defs.values()],
245
+ }
246
+ }
247
+ `;
248
+ }
249
+ function clientTs(P, table, idPrefix) {
250
+ return `import type { Db } from "./db"
251
+ import { newId } from "./ids"
252
+
253
+ export interface ${P}Record {
254
+ id: string
255
+ workspaceId: string
256
+ kind: string
257
+ key: string
258
+ value: unknown
259
+ createdAt: string
260
+ }
261
+
262
+ interface Row {
263
+ id: string
264
+ workspace_id: string
265
+ kind: string
266
+ key: string
267
+ value: unknown
268
+ created_at: Date
269
+ }
270
+
271
+ const toRecord = (r: Row): ${P}Record => ({
272
+ id: r.id,
273
+ workspaceId: r.workspace_id,
274
+ kind: r.kind,
275
+ key: r.key,
276
+ value: r.value,
277
+ createdAt: r.created_at.toISOString(),
278
+ })
279
+
280
+ export interface ${P}Client {
281
+ put(input: { workspaceId: string; kind: string; key: string; value: unknown }): Promise<${P}Record>
282
+ get(workspaceId: string, kind: string, key: string): Promise<${P}Record | null>
283
+ list(workspaceId: string, kind: string): Promise<${P}Record[]>
284
+ }
285
+
286
+ /** A boring, workspace-scoped record store. Reshape to your primitive's domain. */
287
+ export function create${P}Client(opts: { db: Db }): ${P}Client {
288
+ const { db } = opts
289
+ return {
290
+ async put(input) {
291
+ const { rows } = await db.query<Row>(
292
+ \`insert into ${table} (id, workspace_id, kind, key, value)
293
+ values ($1, $2, $3, $4, $5::jsonb)
294
+ on conflict (workspace_id, kind, key) do update set value = excluded.value
295
+ returning *\`,
296
+ [newId("${idPrefix}"), input.workspaceId, input.kind, input.key, JSON.stringify(input.value)],
297
+ )
298
+ return toRecord(rows[0])
299
+ },
300
+ async get(workspaceId, kind, key) {
301
+ const { rows } = await db.query<Row>(
302
+ \`select * from ${table} where workspace_id = $1 and kind = $2 and key = $3\`,
303
+ [workspaceId, kind, key],
304
+ )
305
+ return rows[0] ? toRecord(rows[0]) : null
306
+ },
307
+ async list(workspaceId, kind) {
308
+ const { rows } = await db.query<Row>(
309
+ \`select * from ${table} where workspace_id = $1 and kind = $2 order by created_at desc\`,
310
+ [workspaceId, kind],
311
+ )
312
+ return rows.map(toRecord)
313
+ },
314
+ }
315
+ }
316
+ `;
317
+ }
318
+ function migrationsTs(name, table) {
319
+ const tracking = `platform.${snake(name)}_migrations`;
320
+ return `import type { Db } from "./db"
321
+
322
+ export interface Migration {
323
+ name: string
324
+ sql: string
325
+ }
326
+
327
+ export const INIT_SQL = \`${initSql(table)}\`
328
+
329
+ export const migrations: Migration[] = [{ name: "0001_init", sql: INIT_SQL }]
330
+
331
+ /** Idempotently apply all migrations. */
332
+ export async function runMigrations(db: Db): Promise<void> {
333
+ await db.query("create schema if not exists platform")
334
+ await db.query(
335
+ "create table if not exists ${tracking} (name text primary key, applied_at timestamptz not null default now())",
336
+ )
337
+ for (const m of migrations) {
338
+ const { rows } = await db.query<{ name: string }>("select name from ${tracking} where name = $1", [m.name])
339
+ if (rows.length) continue
340
+ await db.query(m.sql)
341
+ await db.query("insert into ${tracking} (name) values ($1)", [m.name])
342
+ }
343
+ }
344
+ `;
345
+ }
346
+ function initSql(table) {
347
+ return `create schema if not exists platform;
348
+
349
+ create table if not exists ${table} (
350
+ id text primary key,
351
+ workspace_id text not null,
352
+ kind text not null,
353
+ key text not null,
354
+ value jsonb not null default '{}'::jsonb,
355
+ created_at timestamptz not null default now(),
356
+ unique (workspace_id, kind, key)
357
+ );
358
+ `;
359
+ }
360
+ const IDS_TEST = `import { describe, expect, it } from "vitest"
361
+ import { newId } from "../ids"
362
+
363
+ describe("newId", () => {
364
+ it("prefixes and is unique", () => {
365
+ const a = newId("t")
366
+ const b = newId("t")
367
+ expect(a.startsWith("t_")).toBe(true)
368
+ expect(a).not.toBe(b)
369
+ })
370
+ })
371
+ `;
372
+ function registryTest(P) {
373
+ return `import { describe, expect, it } from "vitest"
374
+ import { create${P}Registry, define${P} } from "../registry"
375
+
376
+ describe("registry", () => {
377
+ it("registers and lists definitions", () => {
378
+ const registry = create${P}Registry()
379
+ registry.register(define${P}({ name: "example" }))
380
+ expect(registry.get("example")?.name).toBe("example")
381
+ expect(registry.list()).toHaveLength(1)
382
+ })
383
+ })
384
+ `;
385
+ }
386
+ // ---- daemon ----
387
+ function daemonPackageJson(name, scope) {
388
+ return (JSON.stringify({
389
+ name: `${scope}/${name}d`,
390
+ version: "0.1.0",
391
+ private: true,
392
+ bin: { [`obh-${name}d`]: "dist/main.js" },
393
+ scripts: {
394
+ build: "tsc -p tsconfig.json",
395
+ typecheck: "tsc -p tsconfig.json --noEmit",
396
+ start: "node dist/main.js",
397
+ migrate: "node dist/migrate.js",
398
+ test: "vitest run --passWithNoTests",
399
+ },
400
+ dependencies: { [`${scope}/${name}`]: "workspace:*", pg: "^8.12.0" },
401
+ devDependencies: {
402
+ "@types/node": "^20.14.0",
403
+ "@types/pg": "^8.11.6",
404
+ typescript: "^5.5.4",
405
+ vitest: "^2.0.5",
406
+ },
407
+ }, null, 2) + "\n");
408
+ }
409
+ const DAEMON_CONFIG = `export function loadConfig(): { databaseUrl: string; logLevel: string; pollIntervalMs: number } {
410
+ const databaseUrl = process.env.DATABASE_URL
411
+ if (!databaseUrl) throw new Error("DATABASE_URL is not set")
412
+ return {
413
+ databaseUrl,
414
+ logLevel: process.env.LOG_LEVEL ?? "info",
415
+ pollIntervalMs: Number(process.env.POLL_INTERVAL_MS) || 1000,
416
+ }
417
+ }
418
+ `;
419
+ function daemonMigrate(scope, name) {
420
+ return `#!/usr/bin/env node
421
+ import { pgAdapter, runMigrations } from "${scope}/${name}"
422
+ import { Pool } from "pg"
423
+ import { loadConfig } from "./config"
424
+
425
+ async function main(): Promise<void> {
426
+ const cfg = loadConfig()
427
+ const pool = new Pool({ connectionString: cfg.databaseUrl })
428
+ try {
429
+ await runMigrations(pgAdapter(pool))
430
+ console.log("migrations applied")
431
+ } finally {
432
+ await pool.end()
433
+ }
434
+ }
435
+
436
+ main().catch((err) => {
437
+ console.error(err)
438
+ process.exit(1)
439
+ })
440
+ `;
441
+ }
442
+ function daemonMain(scope, name) {
443
+ return `#!/usr/bin/env node
444
+ import { createLogger, pgAdapter, runMigrations } from "${scope}/${name}"
445
+ import { Pool } from "pg"
446
+ import { loadConfig } from "./config"
447
+
448
+ async function main(): Promise<void> {
449
+ const cfg = loadConfig()
450
+ const log = createLogger(cfg.logLevel as "info")
451
+ const pool = new Pool({ connectionString: cfg.databaseUrl })
452
+ await runMigrations(pgAdapter(pool))
453
+ log.info("daemon started")
454
+
455
+ let running = true
456
+ const tick = async (): Promise<void> => {
457
+ // Add background work here (sweeps, retries, rollups) as the primitive grows.
458
+ }
459
+ const timer = setInterval(() => void tick(), cfg.pollIntervalMs)
460
+
461
+ const shutdown = async (): Promise<void> => {
462
+ if (!running) return
463
+ running = false
464
+ clearInterval(timer)
465
+ await pool.end()
466
+ process.exit(0)
467
+ }
468
+ process.on("SIGTERM", () => void shutdown())
469
+ process.on("SIGINT", () => void shutdown())
470
+ }
471
+
472
+ main().catch((err) => {
473
+ console.error(err)
474
+ process.exit(1)
475
+ })
476
+ `;
477
+ }
478
+ function ci(name) {
479
+ return `name: CI
480
+
481
+ on:
482
+ push:
483
+ branches: [main]
484
+ pull_request:
485
+
486
+ jobs:
487
+ build-test:
488
+ runs-on: ubuntu-latest
489
+ services:
490
+ postgres:
491
+ image: postgres:16
492
+ env:
493
+ POSTGRES_USER: postgres
494
+ POSTGRES_PASSWORD: postgres
495
+ POSTGRES_DB: ${snake(name)}_test
496
+ ports:
497
+ - 5432:5432
498
+ options: >-
499
+ --health-cmd "pg_isready -U postgres"
500
+ --health-interval 10s
501
+ --health-timeout 5s
502
+ --health-retries 5
503
+ env:
504
+ DATABASE_URL: postgres://postgres:postgres@localhost:5432/${snake(name)}_test
505
+ steps:
506
+ - uses: actions/checkout@v4
507
+ - uses: pnpm/action-setup@v4
508
+ with:
509
+ version: 9
510
+ - uses: actions/setup-node@v4
511
+ with:
512
+ node-version: 20
513
+ cache: pnpm
514
+ - run: pnpm install --no-frozen-lockfile
515
+ - run: pnpm -r build
516
+ - run: pnpm -r test
517
+ `;
518
+ }
519
+ function readme(name, scope, P) {
520
+ return `# ${name}
521
+
522
+ \`${scope}/${name}\` — an OBH platform primitive, scaffolded by [OBH Forge](https://github.com/EObrien60/forge).
523
+
524
+ Postgres-backed, framework-agnostic, standalone. Public API:
525
+
526
+ \`\`\`ts
527
+ import { create${P}Client, pgAdapter, runMigrations } from "${scope}/${name}"
528
+ import { Pool } from "pg"
529
+
530
+ const pool = new Pool({ connectionString: process.env.DATABASE_URL })
531
+ await runMigrations(pgAdapter(pool))
532
+
533
+ const client = create${P}Client({ db: pgAdapter(pool) })
534
+ await client.put({ workspaceId: "ws1", kind: "example", key: "k", value: { any: "json" } })
535
+ \`\`\`
536
+
537
+ Tables live under the shared \`platform\` schema. Reshape \`${P}Record\` /
538
+ \`create${P}Client\` to your primitive's real domain — the record store is a
539
+ starting point, not a straitjacket.
540
+
541
+ ## Develop
542
+
543
+ \`\`\`bash
544
+ pnpm install
545
+ pnpm build
546
+ pnpm test # unit + Postgres integration (set DATABASE_URL)
547
+ \`\`\`
548
+ `;
549
+ }
550
+ function mitLicense() {
551
+ return `MIT License
552
+
553
+ Copyright (c) 2026 OBH Software
554
+
555
+ Permission is hereby granted, free of charge, to any person obtaining a copy
556
+ of this software and associated documentation files (the "Software"), to deal
557
+ in the Software without restriction, including without limitation the rights
558
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
559
+ copies of the Software, and to permit persons to whom the Software is
560
+ furnished to do so, subject to the following conditions:
561
+
562
+ The above copyright notice and this permission notice shall be included in all
563
+ copies or substantial portions of the Software.
564
+
565
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND.
566
+ `;
567
+ }
568
+ //# sourceMappingURL=platform-package.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"platform-package.js","sourceRoot":"","sources":["../../src/generators/platform-package.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ import type { Topology } from "../types";
2
+ import type { Plan } from "../project/plan";
3
+ export interface SkeletonOptions {
4
+ name: string;
5
+ topology: Topology;
6
+ }
7
+ /**
8
+ * Adds the project-level skeleton shared by every recipe: workspace config,
9
+ * base tsconfig, migration runner, CI, and docs. Everything here is a safe
10
+ * "create" — re-running never clobbers edited files.
11
+ */
12
+ export declare function addProjectSkeleton(plan: Plan, opts: SkeletonOptions): void;
13
+ export declare function envBase(name: string): string;
14
+ export declare function ciYaml(name: string): string;
15
+ //# sourceMappingURL=root.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"root.d.ts","sourceRoot":"","sources":["../../src/generators/root.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAkB,QAAQ,EAAE,MAAM,UAAU,CAAA;AACxD,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAA;AAE3C,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,QAAQ,CAAA;CACnB;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,eAAe,GAAG,IAAI,CAiF1E;AA2ED,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAM5C;AAED,wBAAgB,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CA4C3C"}