@supatype/cli 0.1.0-alpha.10 → 0.1.0-alpha.12
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/.turbo/turbo-build.log +1 -1
- package/.turbo/turbo-test.log +98 -65
- package/.turbo/turbo-typecheck.log +1 -1
- package/dist/app/framework.js +1 -3
- package/dist/app/framework.js.map +1 -1
- package/dist/app/proxy-dev-app.d.ts +14 -0
- package/dist/app/proxy-dev-app.d.ts.map +1 -1
- package/dist/app/proxy-dev-app.js +109 -6
- package/dist/app/proxy-dev-app.js.map +1 -1
- package/dist/binary-cache.d.ts +1 -1
- package/dist/binary-cache.d.ts.map +1 -1
- package/dist/binary-cache.js +6 -1
- package/dist/binary-cache.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +6 -0
- package/dist/cli.js.map +1 -1
- package/dist/commands/adopt.d.ts +3 -0
- package/dist/commands/adopt.d.ts.map +1 -0
- package/dist/commands/adopt.js +58 -0
- package/dist/commands/adopt.js.map +1 -0
- package/dist/commands/cloud.d.ts +4 -9
- package/dist/commands/cloud.d.ts.map +1 -1
- package/dist/commands/cloud.js +49 -91
- package/dist/commands/cloud.js.map +1 -1
- package/dist/commands/db.d.ts.map +1 -1
- package/dist/commands/db.js +25 -47
- package/dist/commands/db.js.map +1 -1
- package/dist/commands/deploy.d.ts.map +1 -1
- package/dist/commands/deploy.js +117 -74
- package/dist/commands/deploy.js.map +1 -1
- package/dist/commands/dev.d.ts.map +1 -1
- package/dist/commands/dev.js +21 -3
- package/dist/commands/dev.js.map +1 -1
- package/dist/commands/diff.d.ts.map +1 -1
- package/dist/commands/diff.js +37 -37
- package/dist/commands/diff.js.map +1 -1
- package/dist/commands/doctor.d.ts +3 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +77 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/functions.d.ts.map +1 -1
- package/dist/commands/functions.js +80 -33
- package/dist/commands/functions.js.map +1 -1
- package/dist/commands/init.d.ts +1 -0
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +26 -4
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/introspect.d.ts +3 -0
- package/dist/commands/introspect.d.ts.map +1 -0
- package/dist/commands/introspect.js +34 -0
- package/dist/commands/introspect.js.map +1 -0
- package/dist/commands/link-helpers.d.ts +15 -0
- package/dist/commands/link-helpers.d.ts.map +1 -0
- package/dist/commands/link-helpers.js +187 -0
- package/dist/commands/link-helpers.js.map +1 -0
- package/dist/commands/migrate.d.ts.map +1 -1
- package/dist/commands/migrate.js +116 -14
- package/dist/commands/migrate.js.map +1 -1
- package/dist/commands/pull.d.ts.map +1 -1
- package/dist/commands/pull.js +32 -5
- package/dist/commands/pull.js.map +1 -1
- package/dist/commands/push.d.ts.map +1 -1
- package/dist/commands/push.js +102 -129
- package/dist/commands/push.js.map +1 -1
- package/dist/commands/status.d.ts +1 -1
- package/dist/commands/status.d.ts.map +1 -1
- package/dist/commands/status.js +93 -29
- package/dist/commands/status.js.map +1 -1
- package/dist/commands/update.d.ts.map +1 -1
- package/dist/commands/update.js +6 -2
- package/dist/commands/update.js.map +1 -1
- package/dist/config.d.ts +2 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js.map +1 -1
- package/dist/dev-compose.d.ts +23 -0
- package/dist/dev-compose.d.ts.map +1 -1
- package/dist/dev-compose.js +183 -6
- package/dist/dev-compose.js.map +1 -1
- package/dist/diff-output.d.ts +5 -1
- package/dist/diff-output.d.ts.map +1 -1
- package/dist/diff-output.js +69 -0
- package/dist/diff-output.js.map +1 -1
- package/dist/engine-client.d.ts +10 -1
- package/dist/engine-client.d.ts.map +1 -1
- package/dist/engine-client.js +64 -13
- package/dist/engine-client.js.map +1 -1
- package/dist/engine-push-output.d.ts +1 -0
- package/dist/engine-push-output.d.ts.map +1 -1
- package/dist/engine-push-output.js +4 -1
- package/dist/engine-push-output.js.map +1 -1
- package/dist/gitignore.d.ts +8 -0
- package/dist/gitignore.d.ts.map +1 -0
- package/dist/gitignore.js +41 -0
- package/dist/gitignore.js.map +1 -0
- package/dist/link.d.ts +66 -0
- package/dist/link.d.ts.map +1 -0
- package/dist/link.js +159 -0
- package/dist/link.js.map +1 -0
- package/dist/process-manager.d.ts +2 -0
- package/dist/process-manager.d.ts.map +1 -1
- package/dist/process-manager.js +2 -0
- package/dist/process-manager.js.map +1 -1
- package/dist/project-config.d.ts +8 -0
- package/dist/project-config.d.ts.map +1 -1
- package/dist/project-config.js.map +1 -1
- package/dist/pull-utils.d.ts +50 -14
- package/dist/pull-utils.d.ts.map +1 -1
- package/dist/pull-utils.js +152 -12
- package/dist/pull-utils.js.map +1 -1
- package/dist/resolve-target.d.ts +86 -0
- package/dist/resolve-target.d.ts.map +1 -0
- package/dist/resolve-target.js +291 -0
- package/dist/resolve-target.js.map +1 -0
- package/dist/runtime-routes.d.ts.map +1 -1
- package/dist/runtime-routes.js +7 -0
- package/dist/runtime-routes.js.map +1 -1
- package/dist/schema-ast-v2.d.ts +1 -1
- package/dist/schema-ast-v2.d.ts.map +1 -1
- package/dist/schema-ast-v2.js +2 -2
- package/dist/schema-ast-v2.js.map +1 -1
- package/dist/schema-sources.d.ts +40 -0
- package/dist/schema-sources.d.ts.map +1 -0
- package/dist/schema-sources.js +183 -0
- package/dist/schema-sources.js.map +1 -0
- package/dist/self-host-compose.d.ts +10 -0
- package/dist/self-host-compose.d.ts.map +1 -1
- package/dist/self-host-compose.js +85 -3
- package/dist/self-host-compose.js.map +1 -1
- package/dist/storage-provision.d.ts +4 -0
- package/dist/storage-provision.d.ts.map +1 -1
- package/dist/storage-provision.js +24 -2
- package/dist/storage-provision.js.map +1 -1
- package/dist/target-client.d.ts +10 -0
- package/dist/target-client.d.ts.map +1 -0
- package/dist/target-client.js +22 -0
- package/dist/target-client.js.map +1 -0
- package/dist/type-extractor.d.ts +11 -0
- package/dist/type-extractor.d.ts.map +1 -1
- package/dist/type-extractor.js +95 -8
- package/dist/type-extractor.js.map +1 -1
- package/package.json +1 -1
- package/src/app/framework.ts +1 -3
- package/src/app/proxy-dev-app.ts +113 -6
- package/src/binary-cache.ts +6 -1
- package/src/cli.ts +6 -0
- package/src/commands/adopt.ts +83 -0
- package/src/commands/cloud.ts +66 -108
- package/src/commands/db.ts +28 -52
- package/src/commands/deploy.ts +162 -104
- package/src/commands/dev.ts +24 -10
- package/src/commands/diff.ts +40 -41
- package/src/commands/doctor.ts +102 -0
- package/src/commands/functions.ts +95 -37
- package/src/commands/init.ts +25 -4
- package/src/commands/introspect.ts +47 -0
- package/src/commands/link-helpers.ts +228 -0
- package/src/commands/migrate.ts +163 -15
- package/src/commands/pull.ts +37 -9
- package/src/commands/push.ts +132 -166
- package/src/commands/status.ts +100 -33
- package/src/commands/update.ts +6 -2
- package/src/config.ts +2 -1
- package/src/dev-compose.ts +240 -6
- package/src/diff-output.ts +79 -1
- package/src/engine-client.ts +70 -13
- package/src/engine-push-output.ts +7 -3
- package/src/gitignore.ts +48 -0
- package/src/link.ts +242 -0
- package/src/process-manager.ts +4 -0
- package/src/project-config.ts +8 -0
- package/src/pull-utils.ts +217 -23
- package/src/resolve-target.ts +419 -0
- package/src/runtime-routes.ts +7 -0
- package/src/schema-ast-v2.ts +2 -1
- package/src/schema-sources.ts +248 -0
- package/src/self-host-compose.ts +87 -3
- package/src/storage-provision.ts +33 -1
- package/src/target-client.ts +40 -0
- package/src/type-extractor.ts +124 -11
- package/tests/cli-help.test.ts +27 -2
- package/tests/init.test.ts +1 -1
- package/tests/link.test.ts +148 -0
- package/tests/proxy-dev-app.test.ts +45 -1
- package/tests/pull-utils.test.ts +5 -4
- package/tests/runtime-contract.test.ts +44 -1
- package/tests/schema-sources.test.ts +119 -0
- package/tests/storage-provision.test.ts +100 -0
- package/tsconfig.tsbuildinfo +1 -1
package/src/engine-client.ts
CHANGED
|
@@ -20,14 +20,17 @@ import { resolveBinary, currentPlatform, cachePath } from "./binary-cache.js"
|
|
|
20
20
|
// ---------------------------------------------------------------------------
|
|
21
21
|
|
|
22
22
|
export interface Operation {
|
|
23
|
-
kind: "create_table" | "alter_table" | "drop_table" | "create_index" | "drop_index" |
|
|
24
|
-
"create_policy" | "drop_policy" | "add_column" | "drop_column" | "alter_column" |
|
|
25
|
-
"recreate_column"
|
|
26
23
|
type?: string
|
|
24
|
+
kind?: string
|
|
27
25
|
description?: string
|
|
28
26
|
risk?: "safe" | "warn" | "danger" | "cautious" | "destructive"
|
|
29
27
|
warning?: string
|
|
30
28
|
sql?: string
|
|
29
|
+
table?: string
|
|
30
|
+
column?: string
|
|
31
|
+
constraint?: string
|
|
32
|
+
index_name?: string
|
|
33
|
+
index?: { fields?: string[]; name?: string; unique?: boolean }
|
|
31
34
|
}
|
|
32
35
|
|
|
33
36
|
export interface DiffResult {
|
|
@@ -146,24 +149,40 @@ export async function engineRequest<T = unknown>(
|
|
|
146
149
|
): Promise<T> {
|
|
147
150
|
const bin = await getEngineBin()
|
|
148
151
|
|
|
149
|
-
// Write request to a temp file.
|
|
150
|
-
// For CLI-mode endpoints the engine reads the input file as a raw SchemaAst,
|
|
151
|
-
// so we extract the `ast` field when present, otherwise write the full body.
|
|
152
152
|
const tmpDir = join(tmpdir(), "supatype-engine")
|
|
153
153
|
mkdirSync(tmpDir, { recursive: true })
|
|
154
|
+
const cleanup: string[] = []
|
|
154
155
|
const reqFile = join(tmpDir, `req-${Date.now()}.json`)
|
|
155
156
|
const inputPayload = body["ast"] !== undefined ? body["ast"] : body
|
|
156
157
|
writeFileSync(reqFile, JSON.stringify(inputPayload))
|
|
158
|
+
cleanup.push(reqFile)
|
|
159
|
+
|
|
160
|
+
let gzPath: string | undefined
|
|
161
|
+
let manifestPath: string | undefined
|
|
162
|
+
if (typeof body["schema_sources_gz_base64"] === "string") {
|
|
163
|
+
gzPath = join(tmpDir, `sources-${Date.now()}.gz`)
|
|
164
|
+
writeFileSync(gzPath, Buffer.from(body["schema_sources_gz_base64"], "base64"))
|
|
165
|
+
cleanup.push(gzPath)
|
|
166
|
+
}
|
|
167
|
+
if (body["schema_sources_manifest"] !== undefined) {
|
|
168
|
+
manifestPath = join(tmpDir, `manifest-${Date.now()}.json`)
|
|
169
|
+
writeFileSync(manifestPath, JSON.stringify(body["schema_sources_manifest"]))
|
|
170
|
+
cleanup.push(manifestPath)
|
|
171
|
+
}
|
|
157
172
|
|
|
158
|
-
const args = endpointToArgs(endpoint, body, reqFile
|
|
173
|
+
const args = endpointToArgs(endpoint, body, reqFile, {
|
|
174
|
+
...(gzPath !== undefined ? { gzPath } : {}),
|
|
175
|
+
...(manifestPath !== undefined ? { manifestPath } : {}),
|
|
176
|
+
})
|
|
159
177
|
|
|
160
178
|
const result = spawnSync(bin, args, {
|
|
161
179
|
encoding: "utf8",
|
|
162
180
|
cwd: process.cwd(),
|
|
163
181
|
})
|
|
164
182
|
|
|
165
|
-
|
|
166
|
-
|
|
183
|
+
for (const f of cleanup) {
|
|
184
|
+
try { unlinkSync(f) } catch { /* ignore */ }
|
|
185
|
+
}
|
|
167
186
|
|
|
168
187
|
if (result.status !== 0) {
|
|
169
188
|
const stderr = result.stderr?.trim() || "(no output)"
|
|
@@ -195,6 +214,7 @@ function endpointToArgs(
|
|
|
195
214
|
endpoint: string,
|
|
196
215
|
body: Record<string, unknown>,
|
|
197
216
|
reqFile: string,
|
|
217
|
+
sources?: { gzPath?: string; manifestPath?: string },
|
|
198
218
|
): string[] {
|
|
199
219
|
const dbUrl = (body["database_url"] as string | undefined) ?? ""
|
|
200
220
|
const schema = (body["schema"] as string | undefined) ?? "public"
|
|
@@ -206,8 +226,26 @@ function endpointToArgs(
|
|
|
206
226
|
case "/diff":
|
|
207
227
|
return ["diff", "--input", reqFile, "--database-url", dbUrl, "--schema", schema]
|
|
208
228
|
|
|
209
|
-
case "/push":
|
|
210
|
-
|
|
229
|
+
case "/push": {
|
|
230
|
+
const sourceArgs: string[] = []
|
|
231
|
+
if (sources?.gzPath) sourceArgs.push("--schema-sources-gz", sources.gzPath)
|
|
232
|
+
if (sources?.manifestPath) sourceArgs.push("--schema-sources-manifest", sources.manifestPath)
|
|
233
|
+
return [
|
|
234
|
+
"push",
|
|
235
|
+
"--input",
|
|
236
|
+
reqFile,
|
|
237
|
+
"--database-url",
|
|
238
|
+
dbUrl,
|
|
239
|
+
"--schema",
|
|
240
|
+
schema,
|
|
241
|
+
...force,
|
|
242
|
+
...nonInteractive,
|
|
243
|
+
...sourceArgs,
|
|
244
|
+
]
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
case "/rollback":
|
|
248
|
+
return ["rollback", "--database-url", dbUrl, "--schema", schema]
|
|
211
249
|
|
|
212
250
|
case "/parse":
|
|
213
251
|
return ["parse", "--input", reqFile]
|
|
@@ -220,6 +258,18 @@ function endpointToArgs(
|
|
|
220
258
|
case "/introspect":
|
|
221
259
|
return ["introspect", "--database-url", dbUrl, "--schema", schema]
|
|
222
260
|
|
|
261
|
+
case "/doctor": {
|
|
262
|
+
const strict = body["strict"] ? ["--strict"] : []
|
|
263
|
+
const noCache = body["no_cache"] ? ["--no-cache"] : []
|
|
264
|
+
return ["doctor", "--input", reqFile, "--database-url", dbUrl, "--schema", schema, ...strict, ...noCache]
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
case "/adopt": {
|
|
268
|
+
const yes = body["yes"] ? ["--yes"] : []
|
|
269
|
+
const noCache = body["no_cache"] ? ["--no-cache"] : []
|
|
270
|
+
return ["adopt", "--input", reqFile, "--database-url", dbUrl, "--schema", schema, ...yes, ...noCache]
|
|
271
|
+
}
|
|
272
|
+
|
|
223
273
|
case "/validate":
|
|
224
274
|
return ["validate", "--input", reqFile]
|
|
225
275
|
|
|
@@ -227,9 +277,16 @@ function endpointToArgs(
|
|
|
227
277
|
return ["admin", "--input", reqFile]
|
|
228
278
|
|
|
229
279
|
default:
|
|
230
|
-
if (endpoint.startsWith("/migrations")) {
|
|
280
|
+
if (endpoint === "/migrations" || endpoint.startsWith("/migrations")) {
|
|
231
281
|
const action = (body["action"] as string | undefined) ?? "list"
|
|
232
|
-
|
|
282
|
+
if (action === "rollback") {
|
|
283
|
+
return ["rollback", "--database-url", dbUrl, "--schema", schema]
|
|
284
|
+
}
|
|
285
|
+
const name = body["name"] as string | undefined
|
|
286
|
+
if (name) {
|
|
287
|
+
return ["migrations", "--database-url", dbUrl, "--name", name]
|
|
288
|
+
}
|
|
289
|
+
return ["migrations", "--database-url", dbUrl]
|
|
233
290
|
}
|
|
234
291
|
return [endpoint.replace(/^\//, ""), "--input", reqFile]
|
|
235
292
|
}
|
|
@@ -10,7 +10,7 @@ export interface EnginePushResult {
|
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
/** Extract the engine JSON object from mixed docker compose stdout/stderr. */
|
|
13
|
-
export function
|
|
13
|
+
export function parseEngineJsonOutput<T>(output: string): T | null {
|
|
14
14
|
const trimmed = output.trim()
|
|
15
15
|
if (!trimmed) return null
|
|
16
16
|
|
|
@@ -18,7 +18,7 @@ export function parseEnginePushOutput(output: string): EnginePushResult | null {
|
|
|
18
18
|
const candidate = line.trim()
|
|
19
19
|
if (!candidate.startsWith("{") || !candidate.endsWith("}")) continue
|
|
20
20
|
try {
|
|
21
|
-
return JSON.parse(candidate) as
|
|
21
|
+
return JSON.parse(candidate) as T
|
|
22
22
|
} catch {
|
|
23
23
|
/* try next line */
|
|
24
24
|
}
|
|
@@ -26,7 +26,7 @@ export function parseEnginePushOutput(output: string): EnginePushResult | null {
|
|
|
26
26
|
|
|
27
27
|
if (trimmed.startsWith("{")) {
|
|
28
28
|
try {
|
|
29
|
-
return JSON.parse(trimmed) as
|
|
29
|
+
return JSON.parse(trimmed) as T
|
|
30
30
|
} catch {
|
|
31
31
|
return null
|
|
32
32
|
}
|
|
@@ -35,6 +35,10 @@ export function parseEnginePushOutput(output: string): EnginePushResult | null {
|
|
|
35
35
|
return null
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
export function parseEnginePushOutput(output: string): EnginePushResult | null {
|
|
39
|
+
return parseEngineJsonOutput<EnginePushResult>(output)
|
|
40
|
+
}
|
|
41
|
+
|
|
38
42
|
/** Human-readable one-liner for successful push (matches `supatype push` tone). */
|
|
39
43
|
export function formatEnginePushMessage(result: EnginePushResult): string {
|
|
40
44
|
if (result.status === "up_to_date") {
|
package/src/gitignore.ts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs"
|
|
2
|
+
import { resolve } from "node:path"
|
|
3
|
+
|
|
4
|
+
export const SUPATYPE_GITIGNORE_MARKER = "# Supatype — local runtime (contains secrets in link.json)"
|
|
5
|
+
export const SUPATYPE_GITIGNORE_BLOCK = `${SUPATYPE_GITIGNORE_MARKER}
|
|
6
|
+
.env
|
|
7
|
+
.supatype/
|
|
8
|
+
supatype.local.config.ts
|
|
9
|
+
supatype.local.config.js
|
|
10
|
+
supatype.local.config.mjs
|
|
11
|
+
`
|
|
12
|
+
|
|
13
|
+
export function isSupatypeGitignored(cwd: string): boolean {
|
|
14
|
+
const gitignorePath = resolve(cwd, ".gitignore")
|
|
15
|
+
if (!existsSync(gitignorePath)) return false
|
|
16
|
+
const content = readFileSync(gitignorePath, "utf8")
|
|
17
|
+
return /\.supatype\/?(\/|\s|$)/m.test(content) || content.includes(".supatype/")
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function ensureSupatypeGitignore(cwd: string, opts?: { silent?: boolean }): boolean {
|
|
21
|
+
const gitignorePath = resolve(cwd, ".gitignore")
|
|
22
|
+
if (isSupatypeGitignored(cwd)) return true
|
|
23
|
+
|
|
24
|
+
if (existsSync(gitignorePath)) {
|
|
25
|
+
const content = readFileSync(gitignorePath, "utf8")
|
|
26
|
+
const next = content.endsWith("\n") ? content : `${content}\n`
|
|
27
|
+
writeFileSync(gitignorePath, `${next}\n${SUPATYPE_GITIGNORE_BLOCK}`, "utf8")
|
|
28
|
+
} else {
|
|
29
|
+
writeFileSync(
|
|
30
|
+
gitignorePath,
|
|
31
|
+
`.env\nnode_modules/\ndist/\n${SUPATYPE_GITIGNORE_BLOCK}`,
|
|
32
|
+
"utf8",
|
|
33
|
+
)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (!opts?.silent) {
|
|
37
|
+
console.warn("Added .supatype/ to .gitignore (link.json contains secrets — never commit).")
|
|
38
|
+
}
|
|
39
|
+
return true
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function warnIfLinkNotGitignored(cwd: string): void {
|
|
43
|
+
if (isSupatypeGitignored(cwd)) return
|
|
44
|
+
console.warn(
|
|
45
|
+
"\n⚠ Warning: .supatype/ is not in .gitignore — link.json contains tokens and must not be committed.",
|
|
46
|
+
)
|
|
47
|
+
console.warn(" Run with link --fix-gitignore to append the Supatype block.\n")
|
|
48
|
+
}
|
package/src/link.ts
ADDED
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"
|
|
2
|
+
import { resolve } from "node:path"
|
|
3
|
+
|
|
4
|
+
export const LINK_VERSION = 1 as const
|
|
5
|
+
export const LINK_FILE = ".supatype/link.json"
|
|
6
|
+
export const LEGACY_CLOUD_FILE = ".supatype/cloud.json"
|
|
7
|
+
export const LEGACY_LINKED_FILE = ".supatype/linked.json"
|
|
8
|
+
|
|
9
|
+
export type ProjectLinkKind = "cloud" | "self-host" | "local"
|
|
10
|
+
|
|
11
|
+
export interface EnvironmentTarget {
|
|
12
|
+
name: string
|
|
13
|
+
apiUrl: string
|
|
14
|
+
token?: string
|
|
15
|
+
linkedAt: string
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface ProjectLink {
|
|
19
|
+
version: typeof LINK_VERSION
|
|
20
|
+
kind: ProjectLinkKind
|
|
21
|
+
projectRef: string
|
|
22
|
+
defaultEnvironment: string
|
|
23
|
+
token?: string
|
|
24
|
+
orgId?: string | undefined
|
|
25
|
+
cloudApiUrl?: string
|
|
26
|
+
linkedAt: string
|
|
27
|
+
environments: Record<string, EnvironmentTarget>
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface LocalEnvironment {
|
|
31
|
+
target: "local"
|
|
32
|
+
apiUrl: string
|
|
33
|
+
databaseUrl: string
|
|
34
|
+
projectRef: string
|
|
35
|
+
kongPort: number
|
|
36
|
+
provider: "docker" | "native"
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface BranchContext {
|
|
40
|
+
mode: "branch"
|
|
41
|
+
branchId: string
|
|
42
|
+
apiUrl: string
|
|
43
|
+
token?: string | undefined
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
interface LegacyCloudFile {
|
|
47
|
+
apiUrl?: string
|
|
48
|
+
token?: string
|
|
49
|
+
projectSlug?: string
|
|
50
|
+
orgId?: string
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
interface LegacyLinkedFile {
|
|
54
|
+
ref?: string
|
|
55
|
+
orgId?: string
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function writeLocalEnvironment(cwd: string, env: LocalEnvironment): void {
|
|
59
|
+
const dir = resolve(cwd, ".supatype")
|
|
60
|
+
mkdirSync(dir, { recursive: true })
|
|
61
|
+
writeFileSync(resolve(dir, "environment.json"), `${JSON.stringify(env, null, 2)}\n`, "utf8")
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function loadLocalEnvironment(cwd: string): LocalEnvironment | null {
|
|
65
|
+
const path = resolve(cwd, ".supatype/environment.json")
|
|
66
|
+
if (!existsSync(path)) return null
|
|
67
|
+
return JSON.parse(readFileSync(path, "utf8")) as LocalEnvironment
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function linkPath(cwd: string): string {
|
|
71
|
+
return resolve(cwd, LINK_FILE)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function loadProjectLink(cwd: string): ProjectLink | null {
|
|
75
|
+
migrateLegacyLinkFiles(cwd)
|
|
76
|
+
const path = linkPath(cwd)
|
|
77
|
+
if (!existsSync(path)) return null
|
|
78
|
+
return JSON.parse(readFileSync(path, "utf8")) as ProjectLink
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function saveProjectLink(cwd: string, link: ProjectLink): void {
|
|
82
|
+
const dir = resolve(cwd, ".supatype")
|
|
83
|
+
mkdirSync(dir, { recursive: true })
|
|
84
|
+
writeFileSync(linkPath(cwd), `${JSON.stringify(link, null, 2)}\n`, "utf8")
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function isProjectLinked(cwd: string): boolean {
|
|
88
|
+
const link = loadProjectLink(cwd)
|
|
89
|
+
return Boolean(link?.projectRef && Object.keys(link.environments).length > 0)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function resolveEnvironmentName(link: ProjectLink, envFlag?: string): string {
|
|
93
|
+
if (envFlag?.trim()) return envFlag.trim()
|
|
94
|
+
return link.defaultEnvironment || "production"
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function getEnvironmentTarget(link: ProjectLink, envName: string): EnvironmentTarget | null {
|
|
98
|
+
return link.environments[envName] ?? null
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function resolveEnvironmentToken(link: ProjectLink, env: EnvironmentTarget): string | undefined {
|
|
102
|
+
return env.token ?? link.token
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
let migrationWarned = false
|
|
106
|
+
|
|
107
|
+
/** Merge legacy cloud.json + linked.json into link.json once. */
|
|
108
|
+
export function migrateLegacyLinkFiles(cwd: string): void {
|
|
109
|
+
const target = linkPath(cwd)
|
|
110
|
+
if (existsSync(target)) return
|
|
111
|
+
|
|
112
|
+
const cloudPath = resolve(cwd, LEGACY_CLOUD_FILE)
|
|
113
|
+
const linkedPath = resolve(cwd, LEGACY_LINKED_FILE)
|
|
114
|
+
const hasCloud = existsSync(cloudPath)
|
|
115
|
+
const hasLinked = existsSync(linkedPath)
|
|
116
|
+
if (!hasCloud && !hasLinked) return
|
|
117
|
+
|
|
118
|
+
let cloud: LegacyCloudFile | null = null
|
|
119
|
+
let linked: LegacyLinkedFile | null = null
|
|
120
|
+
|
|
121
|
+
if (hasCloud) {
|
|
122
|
+
cloud = JSON.parse(readFileSync(cloudPath, "utf8")) as LegacyCloudFile
|
|
123
|
+
}
|
|
124
|
+
if (hasLinked) {
|
|
125
|
+
linked = JSON.parse(readFileSync(linkedPath, "utf8")) as LegacyLinkedFile
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const projectRef = cloud?.projectSlug ?? linked?.ref
|
|
129
|
+
if (!projectRef || !cloud?.token) {
|
|
130
|
+
return
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const now = new Date().toISOString()
|
|
134
|
+
const link: ProjectLink = {
|
|
135
|
+
version: LINK_VERSION,
|
|
136
|
+
kind: "cloud",
|
|
137
|
+
projectRef,
|
|
138
|
+
defaultEnvironment: "production",
|
|
139
|
+
token: cloud.token,
|
|
140
|
+
cloudApiUrl: cloud.apiUrl ?? "https://api.supatype.com",
|
|
141
|
+
linkedAt: now,
|
|
142
|
+
environments: {
|
|
143
|
+
production: {
|
|
144
|
+
name: "production",
|
|
145
|
+
apiUrl: cloud.apiUrl ?? "https://api.supatype.com",
|
|
146
|
+
linkedAt: now,
|
|
147
|
+
},
|
|
148
|
+
},
|
|
149
|
+
...(cloud.orgId !== undefined
|
|
150
|
+
? { orgId: cloud.orgId }
|
|
151
|
+
: linked?.orgId !== undefined
|
|
152
|
+
? { orgId: linked.orgId }
|
|
153
|
+
: {}),
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
saveProjectLink(cwd, link)
|
|
157
|
+
|
|
158
|
+
if (!migrationWarned) {
|
|
159
|
+
migrationWarned = true
|
|
160
|
+
console.warn(
|
|
161
|
+
"Migrated .supatype/cloud.json → .supatype/link.json (legacy files kept; remove manually when ready).",
|
|
162
|
+
)
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export function createSelfHostLink(params: {
|
|
167
|
+
projectRef: string
|
|
168
|
+
apiUrl: string
|
|
169
|
+
token: string
|
|
170
|
+
envName?: string
|
|
171
|
+
existing?: ProjectLink | null
|
|
172
|
+
}): ProjectLink {
|
|
173
|
+
const envName = params.envName ?? "production"
|
|
174
|
+
const now = new Date().toISOString()
|
|
175
|
+
const env: EnvironmentTarget = {
|
|
176
|
+
name: envName,
|
|
177
|
+
apiUrl: params.apiUrl.replace(/\/$/, ""),
|
|
178
|
+
token: params.token,
|
|
179
|
+
linkedAt: now,
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (params.existing) {
|
|
183
|
+
return {
|
|
184
|
+
...params.existing,
|
|
185
|
+
kind: params.existing.kind === "cloud" ? "self-host" : params.existing.kind,
|
|
186
|
+
projectRef: params.projectRef,
|
|
187
|
+
defaultEnvironment: params.existing.defaultEnvironment || envName,
|
|
188
|
+
linkedAt: now,
|
|
189
|
+
environments: {
|
|
190
|
+
...params.existing.environments,
|
|
191
|
+
[envName]: env,
|
|
192
|
+
},
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return {
|
|
197
|
+
version: LINK_VERSION,
|
|
198
|
+
kind: "self-host",
|
|
199
|
+
projectRef: params.projectRef,
|
|
200
|
+
defaultEnvironment: envName,
|
|
201
|
+
token: params.token,
|
|
202
|
+
linkedAt: now,
|
|
203
|
+
environments: { [envName]: env },
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
export function createCloudLink(params: {
|
|
208
|
+
projectRef: string
|
|
209
|
+
cloudApiUrl: string
|
|
210
|
+
token: string
|
|
211
|
+
orgId?: string | undefined
|
|
212
|
+
environments?: Array<{ name: string; apiUrl: string }>
|
|
213
|
+
existing?: ProjectLink | null
|
|
214
|
+
}): ProjectLink {
|
|
215
|
+
const now = new Date().toISOString()
|
|
216
|
+
const envMap: Record<string, EnvironmentTarget> = {}
|
|
217
|
+
const envs = params.environments?.length
|
|
218
|
+
? params.environments
|
|
219
|
+
: [{ name: "production", apiUrl: params.cloudApiUrl }]
|
|
220
|
+
|
|
221
|
+
for (const e of envs) {
|
|
222
|
+
envMap[e.name] = {
|
|
223
|
+
name: e.name,
|
|
224
|
+
apiUrl: e.apiUrl.replace(/\/$/, ""),
|
|
225
|
+
linkedAt: now,
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return {
|
|
230
|
+
version: LINK_VERSION,
|
|
231
|
+
kind: "cloud",
|
|
232
|
+
projectRef: params.projectRef,
|
|
233
|
+
defaultEnvironment: "production",
|
|
234
|
+
token: params.token,
|
|
235
|
+
cloudApiUrl: params.cloudApiUrl.replace(/\/$/, ""),
|
|
236
|
+
linkedAt: now,
|
|
237
|
+
environments: params.existing
|
|
238
|
+
? { ...params.existing.environments, ...envMap }
|
|
239
|
+
: envMap,
|
|
240
|
+
...(params.orgId !== undefined ? { orgId: params.orgId } : {}),
|
|
241
|
+
}
|
|
242
|
+
}
|
package/src/process-manager.ts
CHANGED
|
@@ -32,6 +32,8 @@ export interface ProcessOptions {
|
|
|
32
32
|
onLine?: (line: string, stream: "stdout" | "stderr") => void
|
|
33
33
|
/** Return false to drop a line before logging. */
|
|
34
34
|
shouldLogLine?: (line: string) => boolean
|
|
35
|
+
/** Called before auto-restart after a non-zero exit (e.g. free a stale port). */
|
|
36
|
+
beforeRestart?: () => void
|
|
35
37
|
}
|
|
36
38
|
|
|
37
39
|
const RESET = "\x1b[0m"
|
|
@@ -164,6 +166,7 @@ export class ProcessManager {
|
|
|
164
166
|
process.stderr.write(message)
|
|
165
167
|
}
|
|
166
168
|
setTimeout(() => {
|
|
169
|
+
this.opts.beforeRestart?.()
|
|
167
170
|
this.backoffMs = Math.min(this.backoffMs * 2, this.opts.maxBackoffMs)
|
|
168
171
|
this.spawn()
|
|
169
172
|
}, this.backoffMs)
|
|
@@ -186,6 +189,7 @@ export class ProcessManager {
|
|
|
186
189
|
}
|
|
187
190
|
|
|
188
191
|
setTimeout(() => {
|
|
192
|
+
this.opts.beforeRestart?.()
|
|
189
193
|
this.backoffMs = Math.min(this.backoffMs * 2, this.opts.maxBackoffMs)
|
|
190
194
|
this.spawn()
|
|
191
195
|
}, this.backoffMs)
|
package/src/project-config.ts
CHANGED
|
@@ -201,6 +201,14 @@ export interface SupatypeProjectConfig {
|
|
|
201
201
|
/** Custom response headers for the deployed static site. */
|
|
202
202
|
headers?: Record<string, string>
|
|
203
203
|
}
|
|
204
|
+
/**
|
|
205
|
+
* Persistent environment defaults for `resolveTarget()` when `--env` is omitted.
|
|
206
|
+
* Ephemeral schema branches (Phase 22) use `.supatype/branch.json`, not this block.
|
|
207
|
+
*/
|
|
208
|
+
environments?: {
|
|
209
|
+
default?: string
|
|
210
|
+
branchDefaults?: Record<string, string>
|
|
211
|
+
}
|
|
204
212
|
/**
|
|
205
213
|
* Optional Postgres URL for CLI commands that talk to the DB (`push`, `migrate`, …).
|
|
206
214
|
* When omitted, `DATABASE_URL` from the environment is used, then a local default DSN.
|