@sqldoc/sqldoc 0.0.1
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/package.json +37 -0
- package/src/__tests__/detect-pm.test.ts +67 -0
- package/src/__tests__/find-sqldoc.test.ts +60 -0
- package/src/__tests__/generate-config-types.test.ts +121 -0
- package/src/__tests__/init.test.ts +117 -0
- package/src/__tests__/prompt.test.ts +164 -0
- package/src/__tests__/scaffold.test.ts +115 -0
- package/src/commands/add.ts +48 -0
- package/src/commands/init.ts +210 -0
- package/src/commands/upgrade.ts +45 -0
- package/src/delegate.ts +44 -0
- package/src/detect-pm.ts +17 -0
- package/src/find-sqldoc.ts +22 -0
- package/src/generate-config-types.ts +77 -0
- package/src/index.ts +127 -0
- package/src/prompt.ts +92 -0
- package/src/runtime.ts +38 -0
- package/src/scaffold.ts +231 -0
package/src/runtime.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime detection for the sqldoc shim.
|
|
3
|
+
* Works on both Node and Bun, compiled and uncompiled.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { execSync } from 'node:child_process'
|
|
7
|
+
|
|
8
|
+
/** Whether we're running as a Bun-compiled binary */
|
|
9
|
+
export function isCompiledBinary(): boolean {
|
|
10
|
+
return process.execPath.includes('sqldoc') && typeof (globalThis as any).Bun !== 'undefined'
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Get the command to run package installs.
|
|
15
|
+
* Compiled binary: uses BUN_BE_BUN to invoke itself as bun.
|
|
16
|
+
* Dev mode: uses the detected package manager.
|
|
17
|
+
*/
|
|
18
|
+
export function getPackageManagerCommand(_sqldocDir: string): { cmd: string; env?: Record<string, string> } {
|
|
19
|
+
if (isCompiledBinary()) {
|
|
20
|
+
return {
|
|
21
|
+
cmd: process.execPath,
|
|
22
|
+
env: { BUN_BE_BUN: '1' },
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Dev mode — use whatever is available
|
|
27
|
+
try {
|
|
28
|
+
execSync('bun --version', { stdio: 'ignore' })
|
|
29
|
+
return { cmd: 'bun' }
|
|
30
|
+
} catch {}
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
execSync('pnpm --version', { stdio: 'ignore' })
|
|
34
|
+
return { cmd: 'pnpm' }
|
|
35
|
+
} catch {}
|
|
36
|
+
|
|
37
|
+
return { cmd: 'npm' }
|
|
38
|
+
}
|
package/src/scaffold.ts
ADDED
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scaffolding helpers for `sqldoc init`.
|
|
3
|
+
* Generates sqldoc.config.ts and example.sql based on user choices.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
type Dialect = 'postgres' | 'mysql' | 'sqlite'
|
|
7
|
+
|
|
8
|
+
interface ScaffoldOptions {
|
|
9
|
+
dialect: Dialect
|
|
10
|
+
namespaces: string[]
|
|
11
|
+
templates: string[]
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Generate sqldoc.config.ts content based on user selections.
|
|
16
|
+
*/
|
|
17
|
+
export function scaffoldConfig(opts: ScaffoldOptions): string {
|
|
18
|
+
const lines: string[] = []
|
|
19
|
+
|
|
20
|
+
// Imports
|
|
21
|
+
lines.push(`import type { SqldocConfig } from './.sqldoc/config'`)
|
|
22
|
+
lines.push('')
|
|
23
|
+
|
|
24
|
+
// Config object
|
|
25
|
+
lines.push('const config: SqldocConfig = {')
|
|
26
|
+
|
|
27
|
+
// Dialect (mandatory)
|
|
28
|
+
lines.push(` dialect: '${opts.dialect}',`)
|
|
29
|
+
|
|
30
|
+
// Dev URL hint
|
|
31
|
+
const devUrlHint = devUrlForDialect(opts.dialect)
|
|
32
|
+
lines.push(` // devUrl: '${devUrlHint}',`)
|
|
33
|
+
|
|
34
|
+
// Namespaces
|
|
35
|
+
if (opts.namespaces.length > 0) {
|
|
36
|
+
lines.push(' namespaces: {')
|
|
37
|
+
|
|
38
|
+
for (const ns of opts.namespaces) {
|
|
39
|
+
const nsConfig = namespaceConfigSnippet(ns, opts.templates)
|
|
40
|
+
if (nsConfig) {
|
|
41
|
+
lines.push(` ${ns}: ${nsConfig},`)
|
|
42
|
+
} else {
|
|
43
|
+
lines.push(` ${ns}: {},`)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
lines.push(' },')
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
lines.push('}')
|
|
51
|
+
lines.push('')
|
|
52
|
+
lines.push('export default config')
|
|
53
|
+
lines.push('')
|
|
54
|
+
|
|
55
|
+
return lines.join('\n')
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function devUrlForDialect(dialect: Dialect): string {
|
|
59
|
+
switch (dialect) {
|
|
60
|
+
case 'postgres':
|
|
61
|
+
return 'postgres://localhost:5432/mydb?sslmode=disable'
|
|
62
|
+
case 'mysql':
|
|
63
|
+
return 'mysql://localhost:3306/mydb'
|
|
64
|
+
case 'sqlite':
|
|
65
|
+
return 'sqlite://dev.db'
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function namespaceConfigSnippet(ns: string, templates: string[]): string | null {
|
|
70
|
+
switch (ns) {
|
|
71
|
+
case 'docs':
|
|
72
|
+
return `{\n format: 'html',\n output: 'docs/schema.html',\n title: 'Schema Documentation',\n }`
|
|
73
|
+
case 'codegen': {
|
|
74
|
+
if (templates.length === 0) return null
|
|
75
|
+
const entries = templates.map((t) => {
|
|
76
|
+
const output = templateOutput(t)
|
|
77
|
+
return ` { template: '@sqldoc/templates/${t}', output: '${output}' },`
|
|
78
|
+
})
|
|
79
|
+
return `{\n templates: [\n${entries.join('\n')}\n ],\n }`
|
|
80
|
+
}
|
|
81
|
+
default:
|
|
82
|
+
return null
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function templateOutput(template: string): string {
|
|
87
|
+
switch (template) {
|
|
88
|
+
case 'typescript':
|
|
89
|
+
return 'generated/types.ts'
|
|
90
|
+
case 'zod':
|
|
91
|
+
return 'generated/schemas.ts'
|
|
92
|
+
case 'drizzle':
|
|
93
|
+
return 'generated/drizzle.ts'
|
|
94
|
+
case 'prisma':
|
|
95
|
+
return 'generated/schema.prisma'
|
|
96
|
+
case 'kysely':
|
|
97
|
+
return 'generated/database.ts'
|
|
98
|
+
case 'go-structs':
|
|
99
|
+
return 'generated/models.go'
|
|
100
|
+
case 'gorm':
|
|
101
|
+
return 'generated/models.go'
|
|
102
|
+
case 'sqlc':
|
|
103
|
+
return 'generated/models.go'
|
|
104
|
+
case 'python-dataclasses':
|
|
105
|
+
return 'generated/models.py'
|
|
106
|
+
case 'pydantic':
|
|
107
|
+
return 'generated/models.py'
|
|
108
|
+
case 'sqlalchemy':
|
|
109
|
+
return 'generated/models.py'
|
|
110
|
+
case 'java-records':
|
|
111
|
+
return 'generated/Models.java'
|
|
112
|
+
case 'jpa':
|
|
113
|
+
return 'generated/Models.java'
|
|
114
|
+
case 'kotlin-data':
|
|
115
|
+
return 'generated/Models.kt'
|
|
116
|
+
case 'rust-structs':
|
|
117
|
+
return 'generated/models.rs'
|
|
118
|
+
case 'diesel':
|
|
119
|
+
return 'generated/models.rs'
|
|
120
|
+
case 'csharp-records':
|
|
121
|
+
return 'generated/Models.cs'
|
|
122
|
+
case 'efcore':
|
|
123
|
+
return 'generated/Models.cs'
|
|
124
|
+
case 'json-schema':
|
|
125
|
+
return 'generated/schema.json'
|
|
126
|
+
case 'protobuf':
|
|
127
|
+
return 'generated/schema.proto'
|
|
128
|
+
default:
|
|
129
|
+
return `generated/${template}`
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Generate example.sql content based on selected namespaces and dialect.
|
|
135
|
+
*/
|
|
136
|
+
export function scaffoldExample(opts: ScaffoldOptions): string {
|
|
137
|
+
const lines: string[] = []
|
|
138
|
+
|
|
139
|
+
lines.push(`-- Example SQL file demonstrating sqldoc tags`)
|
|
140
|
+
lines.push(`-- See: https://sqldoc.dev/docs`)
|
|
141
|
+
lines.push('')
|
|
142
|
+
|
|
143
|
+
// Imports for selected namespaces
|
|
144
|
+
for (const ns of opts.namespaces) {
|
|
145
|
+
lines.push(`-- @import ${nsPackage(ns)}`)
|
|
146
|
+
}
|
|
147
|
+
if (opts.namespaces.length > 0) lines.push('')
|
|
148
|
+
|
|
149
|
+
// Table with tags for selected namespaces
|
|
150
|
+
lines.push(tableExample(opts))
|
|
151
|
+
|
|
152
|
+
return lines.join('\n')
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function nsPackage(ns: string): string {
|
|
156
|
+
return `@sqldoc/ns-${ns}`
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function tableExample(opts: ScaffoldOptions): string {
|
|
160
|
+
const lines: string[] = []
|
|
161
|
+
const hasNs = (ns: string) => opts.namespaces.includes(ns)
|
|
162
|
+
|
|
163
|
+
// Table-level tags
|
|
164
|
+
const tableTags: string[] = []
|
|
165
|
+
if (hasNs('audit')) tableTags.push('-- @audit')
|
|
166
|
+
if (hasNs('rls')) tableTags.push('-- @rls')
|
|
167
|
+
if (hasNs('docs')) tableTags.push('-- @docs.description("User accounts")')
|
|
168
|
+
|
|
169
|
+
if (tableTags.length > 0) {
|
|
170
|
+
lines.push(tableTags.join('\n'))
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const idType = opts.dialect === 'mysql' ? 'INT AUTO_INCREMENT' : opts.dialect === 'sqlite' ? 'INTEGER' : 'SERIAL'
|
|
174
|
+
const textType = opts.dialect === 'mysql' ? 'VARCHAR(255)' : 'TEXT'
|
|
175
|
+
const timestampType =
|
|
176
|
+
opts.dialect === 'mysql'
|
|
177
|
+
? 'TIMESTAMP DEFAULT CURRENT_TIMESTAMP'
|
|
178
|
+
: opts.dialect === 'sqlite'
|
|
179
|
+
? 'TEXT DEFAULT CURRENT_TIMESTAMP'
|
|
180
|
+
: 'TIMESTAMPTZ DEFAULT now()'
|
|
181
|
+
|
|
182
|
+
lines.push('CREATE TABLE users (')
|
|
183
|
+
|
|
184
|
+
const columns: string[] = []
|
|
185
|
+
columns.push(` id ${idType} PRIMARY KEY`)
|
|
186
|
+
|
|
187
|
+
// Email column with tags
|
|
188
|
+
const emailTags: string[] = []
|
|
189
|
+
if (hasNs('validate')) emailTags.push(' -- @validate.check("email ~* \'^.+@.+$\'")')
|
|
190
|
+
if (hasNs('anon')) emailTags.push(' -- @anon.mask("anon.fake_email()")')
|
|
191
|
+
if (hasNs('codegen')) emailTags.push(' -- @codegen.type("string")')
|
|
192
|
+
if (emailTags.length > 0) columns.push(emailTags.join('\n'))
|
|
193
|
+
columns.push(` email ${textType} NOT NULL`)
|
|
194
|
+
|
|
195
|
+
// Name column
|
|
196
|
+
if (hasNs('anon')) columns.push(' -- @anon.mask("anon.fake_first_name()")')
|
|
197
|
+
columns.push(` name ${textType}`)
|
|
198
|
+
|
|
199
|
+
// Created at
|
|
200
|
+
if (hasNs('docs')) columns.push(' -- @docs.description("When the user signed up")')
|
|
201
|
+
columns.push(` created_at ${timestampType}`)
|
|
202
|
+
|
|
203
|
+
lines.push(columns.join(',\n'))
|
|
204
|
+
lines.push(');')
|
|
205
|
+
lines.push('')
|
|
206
|
+
|
|
207
|
+
// RLS policy example
|
|
208
|
+
if (hasNs('rls') && opts.dialect === 'postgres') {
|
|
209
|
+
lines.push('-- @rls.policy(name: "users_own_data", for: "all", using: "auth.uid() = id")')
|
|
210
|
+
lines.push('')
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return lines.join('\n')
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* List of namespace packages to install for selected namespaces.
|
|
218
|
+
*/
|
|
219
|
+
export function namespacesToInstall(namespaces: string[], templates: string[]): string[] {
|
|
220
|
+
const packages: string[] = ['@sqldoc/cli']
|
|
221
|
+
|
|
222
|
+
for (const ns of namespaces) {
|
|
223
|
+
packages.push(`@sqldoc/ns-${ns}`)
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (namespaces.includes('codegen') && templates.length > 0) {
|
|
227
|
+
packages.push('@sqldoc/templates')
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return packages
|
|
231
|
+
}
|