@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
|
@@ -21,7 +21,9 @@ import {
|
|
|
21
21
|
discoverTsFunctionsInDir,
|
|
22
22
|
generateFunctionsRouterSource,
|
|
23
23
|
} from "../functions-router-gen.js"
|
|
24
|
-
import {
|
|
24
|
+
import { loadProjectLink } from "../link.js"
|
|
25
|
+
import { resolveTarget } from "../resolve-target.js"
|
|
26
|
+
import { targetFetch } from "../target-client.js"
|
|
25
27
|
|
|
26
28
|
// ─── Constants ───────────────────────────────────────────────────────────────
|
|
27
29
|
|
|
@@ -56,8 +58,9 @@ export function registerFunctions(program: Command): void {
|
|
|
56
58
|
.command("deploy")
|
|
57
59
|
.description("Deploy all functions (or --only <name> for one) to the linked project")
|
|
58
60
|
.option("--only <name>", "Deploy a single function")
|
|
61
|
+
.option("--env <name>", "Target environment when linked")
|
|
59
62
|
.option("--dry-run", "Show what would be deployed without deploying")
|
|
60
|
-
.action(async (opts: { only?: string; dryRun?: boolean }) => {
|
|
63
|
+
.action(async (opts: { only?: string; env?: string; dryRun?: boolean }) => {
|
|
61
64
|
await deploy(process.cwd(), opts)
|
|
62
65
|
})
|
|
63
66
|
|
|
@@ -327,7 +330,7 @@ async function serve(cwd: string, opts: { port: string; envFile: string }): Prom
|
|
|
327
330
|
|
|
328
331
|
// ─── Deploy ──────────────────────────────────────────────────────────────────
|
|
329
332
|
|
|
330
|
-
async function deploy(cwd: string, opts: { only?: string; dryRun?: boolean }): Promise<void> {
|
|
333
|
+
async function deploy(cwd: string, opts: { only?: string; env?: string; dryRun?: boolean }): Promise<void> {
|
|
331
334
|
const allFns = discoverFunctions(cwd)
|
|
332
335
|
const fns = opts.only
|
|
333
336
|
? allFns.filter(f => f.name === opts.only)
|
|
@@ -353,13 +356,27 @@ async function deploy(cwd: string, opts: { only?: string; dryRun?: boolean }): P
|
|
|
353
356
|
return
|
|
354
357
|
}
|
|
355
358
|
|
|
359
|
+
const link = loadProjectLink(cwd)
|
|
360
|
+
if (link) {
|
|
361
|
+
try {
|
|
362
|
+
const target = resolveTarget(cwd, { env: opts.env })
|
|
363
|
+
if (target.mode !== "direct" && target.token) {
|
|
364
|
+
await deployViaTarget(cwd, target, fns)
|
|
365
|
+
return
|
|
366
|
+
}
|
|
367
|
+
} catch {
|
|
368
|
+
/* fall through to compose/local */
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
const { selfHostComposePaths } = await import("../self-host-compose.js")
|
|
356
373
|
const composePath = selfHostComposePaths(cwd).composePath
|
|
357
374
|
if (existsSync(composePath)) {
|
|
358
375
|
await deploySelfHosted(cwd, fns)
|
|
359
376
|
return
|
|
360
377
|
}
|
|
361
378
|
|
|
362
|
-
await deployCloud(cwd, fns)
|
|
379
|
+
await deployCloud(cwd, fns, opts.env)
|
|
363
380
|
}
|
|
364
381
|
|
|
365
382
|
async function deploySelfHosted(cwd: string, fns: DiscoveredFunction[]): Promise<void> {
|
|
@@ -376,7 +393,49 @@ async function deploySelfHosted(cwd: string, fns: DiscoveredFunction[]): Promise
|
|
|
376
393
|
console.log("\nKong → supatype-server → functions-worker (per-project worker).")
|
|
377
394
|
}
|
|
378
395
|
|
|
379
|
-
async function
|
|
396
|
+
async function deployViaTarget(
|
|
397
|
+
cwd: string,
|
|
398
|
+
target: ReturnType<typeof resolveTarget>,
|
|
399
|
+
fns: DiscoveredFunction[],
|
|
400
|
+
): Promise<void> {
|
|
401
|
+
console.log(`Deploying to ${target.mode} project: ${target.projectRef} (${target.environment})\n`)
|
|
402
|
+
|
|
403
|
+
for (const fn of fns) {
|
|
404
|
+
const start = Date.now()
|
|
405
|
+
const source = readFunctionSource(fn)
|
|
406
|
+
|
|
407
|
+
try {
|
|
408
|
+
await targetFetch(
|
|
409
|
+
target.apiBaseUrl,
|
|
410
|
+
target.apiPrefix,
|
|
411
|
+
{
|
|
412
|
+
method: "POST",
|
|
413
|
+
path: `/projects/${target.projectRef}/functions/deploy`,
|
|
414
|
+
body: {
|
|
415
|
+
functions: [{
|
|
416
|
+
name: fn.name,
|
|
417
|
+
source,
|
|
418
|
+
entrypoint: `${fn.name}/index.ts`,
|
|
419
|
+
}],
|
|
420
|
+
},
|
|
421
|
+
token: target.token!,
|
|
422
|
+
orgId: target.orgId,
|
|
423
|
+
environment: target.mode === "cloud" ? target.environment : undefined,
|
|
424
|
+
},
|
|
425
|
+
)
|
|
426
|
+
|
|
427
|
+
const duration = Date.now() - start
|
|
428
|
+
console.log(` ${fn.name} ✓ deployed (${duration}ms)`)
|
|
429
|
+
} catch (err) {
|
|
430
|
+
console.error(` ${fn.name} ✗ ${err instanceof Error ? err.message : "unknown error"}`)
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
console.log(`\nDeployed ${fns.length} function(s)`)
|
|
435
|
+
void cwd
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
async function deployCloud(cwd: string, fns: DiscoveredFunction[], env?: string): Promise<void> {
|
|
380
439
|
const { getLinkedProject, getCloudToken, getCloudApiUrl } = await loadCloudHelpers()
|
|
381
440
|
const linked = getLinkedProject(cwd)
|
|
382
441
|
|
|
@@ -385,13 +444,13 @@ async function deployCloud(cwd: string, fns: DiscoveredFunction[]): Promise<void
|
|
|
385
444
|
process.exit(1)
|
|
386
445
|
}
|
|
387
446
|
|
|
388
|
-
const token = getCloudToken()
|
|
447
|
+
const token = getCloudToken(cwd)
|
|
389
448
|
if (!token) {
|
|
390
449
|
console.error("Not logged in. Run: npx supatype cloud login")
|
|
391
450
|
process.exit(1)
|
|
392
451
|
}
|
|
393
452
|
|
|
394
|
-
const apiUrl = getCloudApiUrl()
|
|
453
|
+
const apiUrl = getCloudApiUrl(cwd)
|
|
395
454
|
console.log(`Deploying to project: ${linked.ref}\n`)
|
|
396
455
|
|
|
397
456
|
for (const fn of fns) {
|
|
@@ -473,14 +532,14 @@ async function listFunctions(cwd: string): Promise<void> {
|
|
|
473
532
|
return
|
|
474
533
|
}
|
|
475
534
|
|
|
476
|
-
const token = getCloudToken()
|
|
535
|
+
const token = getCloudToken(cwd)
|
|
477
536
|
if (!token) {
|
|
478
537
|
console.error("Not logged in. Run: npx supatype cloud login")
|
|
479
538
|
process.exit(1)
|
|
480
539
|
}
|
|
481
540
|
|
|
482
541
|
try {
|
|
483
|
-
const res = await fetch(`${getCloudApiUrl()}/api/v1/projects/${linked.ref}/functions`, {
|
|
542
|
+
const res = await fetch(`${getCloudApiUrl(cwd)}/api/v1/projects/${linked.ref}/functions`, {
|
|
484
543
|
headers: {
|
|
485
544
|
Authorization: `Bearer ${token}`,
|
|
486
545
|
"X-Org-Id": linked.orgId ?? "",
|
|
@@ -527,14 +586,14 @@ async function deleteFunction(cwd: string, name: string): Promise<void> {
|
|
|
527
586
|
process.exit(1)
|
|
528
587
|
}
|
|
529
588
|
|
|
530
|
-
const token = getCloudToken()
|
|
589
|
+
const token = getCloudToken(cwd)
|
|
531
590
|
if (!token) {
|
|
532
591
|
console.error("Not logged in. Run: npx supatype cloud login")
|
|
533
592
|
process.exit(1)
|
|
534
593
|
}
|
|
535
594
|
|
|
536
595
|
try {
|
|
537
|
-
const res = await fetch(`${getCloudApiUrl()}/api/v1/projects/${linked.ref}/functions/${name}`, {
|
|
596
|
+
const res = await fetch(`${getCloudApiUrl(cwd)}/api/v1/projects/${linked.ref}/functions/${name}`, {
|
|
538
597
|
method: "DELETE",
|
|
539
598
|
headers: {
|
|
540
599
|
Authorization: `Bearer ${token}`,
|
|
@@ -567,7 +626,7 @@ async function functionLogs(cwd: string, name: string, opts: { since: string }):
|
|
|
567
626
|
process.exit(1)
|
|
568
627
|
}
|
|
569
628
|
|
|
570
|
-
const token = getCloudToken()
|
|
629
|
+
const token = getCloudToken(cwd)
|
|
571
630
|
if (!token) {
|
|
572
631
|
console.error("Not logged in. Run: npx supatype cloud login")
|
|
573
632
|
process.exit(1)
|
|
@@ -575,7 +634,7 @@ async function functionLogs(cwd: string, name: string, opts: { since: string }):
|
|
|
575
634
|
|
|
576
635
|
try {
|
|
577
636
|
const res = await fetch(
|
|
578
|
-
`${getCloudApiUrl()}/api/v1/projects/${linked.ref}/functions/${name}/logs?since=${opts.since}`,
|
|
637
|
+
`${getCloudApiUrl(cwd)}/api/v1/projects/${linked.ref}/functions/${name}/logs?since=${opts.since}`,
|
|
579
638
|
{
|
|
580
639
|
headers: {
|
|
581
640
|
Authorization: `Bearer ${token}`,
|
|
@@ -625,7 +684,7 @@ async function invoke(
|
|
|
625
684
|
const linked = getLinkedProject(cwd)
|
|
626
685
|
if (linked) {
|
|
627
686
|
url = `https://${linked.ref}.supatype.dev/functions/v1/${name}`
|
|
628
|
-
const token = getCloudToken()
|
|
687
|
+
const token = getCloudToken(cwd)
|
|
629
688
|
if (token && opts.auth) {
|
|
630
689
|
headers["Authorization"] = `Bearer ${token}`
|
|
631
690
|
}
|
|
@@ -710,14 +769,14 @@ async function envList(cwd: string): Promise<void> {
|
|
|
710
769
|
return
|
|
711
770
|
}
|
|
712
771
|
|
|
713
|
-
const token = getCloudToken()
|
|
772
|
+
const token = getCloudToken(cwd)
|
|
714
773
|
if (!token) {
|
|
715
774
|
console.error("Not logged in. Run: npx supatype cloud login")
|
|
716
775
|
process.exit(1)
|
|
717
776
|
}
|
|
718
777
|
|
|
719
778
|
try {
|
|
720
|
-
const res = await fetch(`${getCloudApiUrl()}/api/v1/projects/${linked.ref}/functions/env`, {
|
|
779
|
+
const res = await fetch(`${getCloudApiUrl(cwd)}/api/v1/projects/${linked.ref}/functions/env`, {
|
|
721
780
|
headers: {
|
|
722
781
|
Authorization: `Bearer ${token}`,
|
|
723
782
|
"X-Org-Id": linked.orgId ?? "",
|
|
@@ -778,14 +837,14 @@ async function envSet(cwd: string, keyvalue: string): Promise<void> {
|
|
|
778
837
|
return
|
|
779
838
|
}
|
|
780
839
|
|
|
781
|
-
const token = getCloudToken()
|
|
840
|
+
const token = getCloudToken(cwd)
|
|
782
841
|
if (!token) {
|
|
783
842
|
console.error("Not logged in. Run: npx supatype cloud login")
|
|
784
843
|
process.exit(1)
|
|
785
844
|
}
|
|
786
845
|
|
|
787
846
|
try {
|
|
788
|
-
const res = await fetch(`${getCloudApiUrl()}/api/v1/projects/${linked.ref}/functions/env`, {
|
|
847
|
+
const res = await fetch(`${getCloudApiUrl(cwd)}/api/v1/projects/${linked.ref}/functions/env`, {
|
|
789
848
|
method: "POST",
|
|
790
849
|
headers: {
|
|
791
850
|
Authorization: `Bearer ${token}`,
|
|
@@ -828,14 +887,14 @@ async function envUnset(cwd: string, key: string): Promise<void> {
|
|
|
828
887
|
return
|
|
829
888
|
}
|
|
830
889
|
|
|
831
|
-
const token = getCloudToken()
|
|
890
|
+
const token = getCloudToken(cwd)
|
|
832
891
|
if (!token) {
|
|
833
892
|
console.error("Not logged in. Run: npx supatype cloud login")
|
|
834
893
|
process.exit(1)
|
|
835
894
|
}
|
|
836
895
|
|
|
837
896
|
try {
|
|
838
|
-
const res = await fetch(`${getCloudApiUrl()}/api/v1/projects/${linked.ref}/functions/env/${key}`, {
|
|
897
|
+
const res = await fetch(`${getCloudApiUrl(cwd)}/api/v1/projects/${linked.ref}/functions/env/${key}`, {
|
|
839
898
|
method: "DELETE",
|
|
840
899
|
headers: {
|
|
841
900
|
Authorization: `Bearer ${token}`,
|
|
@@ -860,32 +919,29 @@ async function envUnset(cwd: string, key: string): Promise<void> {
|
|
|
860
919
|
// ─── Cloud helpers (lazy loaded) ─────────────────────────────────────────────
|
|
861
920
|
|
|
862
921
|
interface CloudHelpers {
|
|
863
|
-
getLinkedProject(cwd: string): { ref: string; orgId?: string } | null
|
|
864
|
-
getCloudToken(): string | null
|
|
865
|
-
getCloudApiUrl(): string
|
|
922
|
+
getLinkedProject(cwd: string): { ref: string; orgId?: string | undefined; kind?: string } | null
|
|
923
|
+
getCloudToken(cwd: string): string | null
|
|
924
|
+
getCloudApiUrl(cwd: string): string
|
|
866
925
|
}
|
|
867
926
|
|
|
868
927
|
async function loadCloudHelpers(): Promise<CloudHelpers> {
|
|
869
|
-
// These helpers read the local .supatype/linked.json and auth token
|
|
870
928
|
return {
|
|
871
|
-
|
|
872
|
-
const
|
|
873
|
-
if (!
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
return ref ? { ref, ...(orgId !== undefined ? { orgId } : {}) } : null
|
|
879
|
-
} catch {
|
|
880
|
-
return null
|
|
929
|
+
getLinkedProject(cwd: string): { ref: string; orgId?: string | undefined; kind?: string } | null {
|
|
930
|
+
const link = loadProjectLink(cwd)
|
|
931
|
+
if (!link?.projectRef) return null
|
|
932
|
+
return {
|
|
933
|
+
ref: link.projectRef,
|
|
934
|
+
kind: link.kind,
|
|
935
|
+
...(link.orgId !== undefined ? { orgId: link.orgId } : {}),
|
|
881
936
|
}
|
|
882
937
|
},
|
|
883
938
|
|
|
884
|
-
getCloudToken(): string | null {
|
|
885
|
-
// Check env first, then config file
|
|
939
|
+
getCloudToken(cwd: string): string | null {
|
|
886
940
|
if (process.env["SUPATYPE_ACCESS_TOKEN"]) {
|
|
887
941
|
return process.env["SUPATYPE_ACCESS_TOKEN"]
|
|
888
942
|
}
|
|
943
|
+
const link = loadProjectLink(cwd)
|
|
944
|
+
if (link?.token) return link.token
|
|
889
945
|
const tokenPath = resolve(
|
|
890
946
|
process.env["HOME"] ?? process.env["USERPROFILE"] ?? "~",
|
|
891
947
|
".supatype/token",
|
|
@@ -894,7 +950,9 @@ async function loadCloudHelpers(): Promise<CloudHelpers> {
|
|
|
894
950
|
return readFileSync(tokenPath, "utf8").trim() || null
|
|
895
951
|
},
|
|
896
952
|
|
|
897
|
-
getCloudApiUrl(): string {
|
|
953
|
+
getCloudApiUrl(cwd: string): string {
|
|
954
|
+
const link = loadProjectLink(cwd)
|
|
955
|
+
if (link?.cloudApiUrl) return link.cloudApiUrl
|
|
898
956
|
return process.env["SUPATYPE_API_URL"] ?? "https://api.supatype.com"
|
|
899
957
|
},
|
|
900
958
|
}
|
package/src/commands/init.ts
CHANGED
|
@@ -88,7 +88,18 @@ function scaffold(dir: string, projectName: string, mode: "dev" | "standalone" =
|
|
|
88
88
|
write("seed.ts", seedTemplate(projectName))
|
|
89
89
|
write("seeds/.gitkeep", "")
|
|
90
90
|
write("public/.gitkeep", "")
|
|
91
|
-
|
|
91
|
+
const gitignorePath = join(dir, ".gitignore")
|
|
92
|
+
if (existsSync(gitignorePath)) {
|
|
93
|
+
const merged = mergeGitignoreTemplate(readFileSync(gitignorePath, "utf8"))
|
|
94
|
+
if (merged !== readFileSync(gitignorePath, "utf8")) {
|
|
95
|
+
writeFileSync(gitignorePath, merged, "utf8")
|
|
96
|
+
console.log(" updated .gitignore (added .supatype/)")
|
|
97
|
+
} else {
|
|
98
|
+
console.log(" skipped .gitignore (already exists)")
|
|
99
|
+
}
|
|
100
|
+
} else {
|
|
101
|
+
write(".gitignore", gitignoreTemplate())
|
|
102
|
+
}
|
|
92
103
|
}
|
|
93
104
|
|
|
94
105
|
// ─── Templates ───────────────────────────────────────────────────────────────
|
|
@@ -241,13 +252,23 @@ function gitignoreTemplate(): string {
|
|
|
241
252
|
return `.env
|
|
242
253
|
node_modules/
|
|
243
254
|
dist/
|
|
244
|
-
.supatype/
|
|
245
|
-
# Local overrides — never commit
|
|
255
|
+
.supatype/
|
|
246
256
|
supatype.local.config.ts
|
|
247
257
|
supatype.local.config.js
|
|
248
258
|
supatype.local.config.mjs
|
|
249
|
-
# Generated by supatype push
|
|
259
|
+
# Generated by supatype push (legacy paths — prefer output.types in config)
|
|
250
260
|
src/types/supatype.d.ts
|
|
251
261
|
src/lib/supatype.ts
|
|
252
262
|
`
|
|
253
263
|
}
|
|
264
|
+
|
|
265
|
+
export function mergeGitignoreTemplate(existingContent: string): string {
|
|
266
|
+
if (existingContent.includes(".supatype/") || existingContent.includes(".supatype\n")) {
|
|
267
|
+
return existingContent
|
|
268
|
+
}
|
|
269
|
+
const block = `
|
|
270
|
+
# Supatype — local runtime (contains secrets in link.json)
|
|
271
|
+
.supatype/
|
|
272
|
+
`
|
|
273
|
+
return existingContent.endsWith("\n") ? `${existingContent}${block}` : `${existingContent}\n${block}`
|
|
274
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { Command } from "commander"
|
|
2
|
+
import { writeFileSync } from "node:fs"
|
|
3
|
+
import { loadProjectLink } from "../link.js"
|
|
4
|
+
import { resolveTarget, targetSchemaIntrospect, schemaPgSchema } from "../resolve-target.js"
|
|
5
|
+
import type { DatabaseStateJson } from "../pull-utils.js"
|
|
6
|
+
import { printIntrospectSummary } from "../pull-utils.js"
|
|
7
|
+
|
|
8
|
+
export function registerIntrospect(program: Command): void {
|
|
9
|
+
program
|
|
10
|
+
.command("introspect")
|
|
11
|
+
.description("Introspect the live Postgres database (JSON or summary)")
|
|
12
|
+
.option("--connection <url>", "Database connection URL (overrides config)")
|
|
13
|
+
.option("--env <name>", "Target environment when linked")
|
|
14
|
+
.option("--direct", "Use local engine subprocess")
|
|
15
|
+
.option("--json", "Output full DatabaseState JSON")
|
|
16
|
+
.option("--out <path>", "Write JSON output to a file")
|
|
17
|
+
.action(async (opts: {
|
|
18
|
+
connection?: string
|
|
19
|
+
env?: string
|
|
20
|
+
direct?: boolean
|
|
21
|
+
json?: boolean
|
|
22
|
+
out?: string
|
|
23
|
+
}) => {
|
|
24
|
+
const cwd = process.cwd()
|
|
25
|
+
const pgSchema = schemaPgSchema(cwd)
|
|
26
|
+
|
|
27
|
+
const linked = loadProjectLink(cwd)
|
|
28
|
+
const target = linked && !opts.direct && !opts.connection
|
|
29
|
+
? resolveTarget(cwd, { env: opts.env })
|
|
30
|
+
: resolveTarget(cwd, { env: opts.env, direct: true, connection: opts.connection })
|
|
31
|
+
|
|
32
|
+
const state = (await targetSchemaIntrospect(target, { schema: pgSchema })) as DatabaseStateJson
|
|
33
|
+
|
|
34
|
+
if (opts.out) {
|
|
35
|
+
writeFileSync(opts.out, JSON.stringify(state, null, 2), "utf8")
|
|
36
|
+
console.log(`Wrote introspection to ${opts.out}`)
|
|
37
|
+
return
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (opts.json) {
|
|
41
|
+
console.log(JSON.stringify(state, null, 2))
|
|
42
|
+
return
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
printIntrospectSummary(state)
|
|
46
|
+
})
|
|
47
|
+
}
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import type { Command } from "commander"
|
|
2
|
+
import { loadConfig } from "../config.js"
|
|
3
|
+
import {
|
|
4
|
+
createCloudLink,
|
|
5
|
+
createSelfHostLink,
|
|
6
|
+
loadProjectLink,
|
|
7
|
+
saveProjectLink,
|
|
8
|
+
type ProjectLink,
|
|
9
|
+
} from "../link.js"
|
|
10
|
+
import { ensureSupatypeGitignore, warnIfLinkNotGitignored } from "../gitignore.js"
|
|
11
|
+
import { targetFetch } from "../target-client.js"
|
|
12
|
+
|
|
13
|
+
function resolveLinkToken(opts: {
|
|
14
|
+
token?: string
|
|
15
|
+
serviceRoleKey?: string
|
|
16
|
+
}): string | undefined {
|
|
17
|
+
return (
|
|
18
|
+
opts.token ??
|
|
19
|
+
opts.serviceRoleKey ??
|
|
20
|
+
process.env["SUPATYPE_ACCESS_TOKEN"] ??
|
|
21
|
+
process.env["SUPATYPE_TOKEN"] ??
|
|
22
|
+
process.env["SERVICE_ROLE_KEY"]
|
|
23
|
+
)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async function probeSelfHostLink(apiUrl: string, projectRef: string, token: string): Promise<void> {
|
|
27
|
+
await targetFetch(apiUrl, "/platform/v1", {
|
|
28
|
+
method: "GET",
|
|
29
|
+
path: `/projects/${projectRef}/status`,
|
|
30
|
+
token,
|
|
31
|
+
})
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function registerEnvs(program: Command): void {
|
|
35
|
+
const envs = program.command("envs").description("Manage linked deployment environments")
|
|
36
|
+
|
|
37
|
+
envs
|
|
38
|
+
.command("list")
|
|
39
|
+
.description("List linked environments")
|
|
40
|
+
.action(() => {
|
|
41
|
+
const cwd = process.cwd()
|
|
42
|
+
const link = loadProjectLink(cwd)
|
|
43
|
+
if (!link) {
|
|
44
|
+
console.log("Not linked. Run: supatype link")
|
|
45
|
+
return
|
|
46
|
+
}
|
|
47
|
+
console.log(`\nProject: ${link.projectRef} (${link.kind})`)
|
|
48
|
+
console.log(`Default: ${link.defaultEnvironment}\n`)
|
|
49
|
+
for (const [name, env] of Object.entries(link.environments)) {
|
|
50
|
+
const mark = name === link.defaultEnvironment ? " *" : " "
|
|
51
|
+
console.log(`${mark} ${name.padEnd(14)} ${env.apiUrl}`)
|
|
52
|
+
}
|
|
53
|
+
console.log()
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
envs
|
|
57
|
+
.command("use <name>")
|
|
58
|
+
.description("Set the default environment")
|
|
59
|
+
.action((name: string) => {
|
|
60
|
+
const cwd = process.cwd()
|
|
61
|
+
const link = loadProjectLink(cwd)
|
|
62
|
+
if (!link?.environments[name]) {
|
|
63
|
+
console.error(`Environment "${name}" is not linked.`)
|
|
64
|
+
process.exit(1)
|
|
65
|
+
}
|
|
66
|
+
link.defaultEnvironment = name
|
|
67
|
+
saveProjectLink(cwd, link)
|
|
68
|
+
console.log(`Default environment set to "${name}".`)
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
envs
|
|
72
|
+
.command("create <name>")
|
|
73
|
+
.description("Create a cloud environment (staging/preview)")
|
|
74
|
+
.action(async (name: string) => {
|
|
75
|
+
const cwd = process.cwd()
|
|
76
|
+
const link = loadProjectLink(cwd)
|
|
77
|
+
if (!link || link.kind !== "cloud") {
|
|
78
|
+
console.error("Cloud link required. Run: supatype link --project <slug>")
|
|
79
|
+
process.exit(1)
|
|
80
|
+
}
|
|
81
|
+
if (!link.token || !link.cloudApiUrl) {
|
|
82
|
+
console.error("Missing cloud credentials in link.json")
|
|
83
|
+
process.exit(1)
|
|
84
|
+
}
|
|
85
|
+
const bodyName = name === "staging" || name === "preview" ? name : "staging"
|
|
86
|
+
await targetFetch(link.cloudApiUrl, "/api/v1", {
|
|
87
|
+
method: "POST",
|
|
88
|
+
path: `/projects/${link.projectRef}/environments`,
|
|
89
|
+
body: { name: bodyName },
|
|
90
|
+
token: link.token,
|
|
91
|
+
orgId: link.orgId,
|
|
92
|
+
})
|
|
93
|
+
const envsList = await targetFetch<Array<{ name: string; apiUrl: string }>>(
|
|
94
|
+
link.cloudApiUrl,
|
|
95
|
+
"/api/v1",
|
|
96
|
+
{
|
|
97
|
+
method: "GET",
|
|
98
|
+
path: `/projects/${link.projectRef}/environments`,
|
|
99
|
+
token: link.token,
|
|
100
|
+
orgId: link.orgId,
|
|
101
|
+
},
|
|
102
|
+
)
|
|
103
|
+
const updated = createCloudLink({
|
|
104
|
+
projectRef: link.projectRef,
|
|
105
|
+
cloudApiUrl: link.cloudApiUrl,
|
|
106
|
+
token: link.token,
|
|
107
|
+
environments: envsList.map((e) => ({ name: e.name, apiUrl: e.apiUrl })),
|
|
108
|
+
existing: link,
|
|
109
|
+
...(link.orgId !== undefined ? { orgId: link.orgId } : {}),
|
|
110
|
+
})
|
|
111
|
+
saveProjectLink(cwd, updated)
|
|
112
|
+
console.log(`Environment "${bodyName}" created.`)
|
|
113
|
+
})
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function registerLinkOptions(linkCmd: Command): void {
|
|
117
|
+
linkCmd
|
|
118
|
+
.option("--project <slug>", "Cloud project slug")
|
|
119
|
+
.option("--url <url>", "Self-host or local Kong URL")
|
|
120
|
+
.option("--api-url <url>", "Cloud control plane API URL", "https://api.supatype.com")
|
|
121
|
+
.option("--token <token>", "Access token (cloud PAT or self-host SERVICE_ROLE_KEY)")
|
|
122
|
+
.option("--service-role-key <key>", "Deprecated alias for --token on self-host")
|
|
123
|
+
.option("--env <name>", "Environment name (default: production)", "production")
|
|
124
|
+
.option("--fix-gitignore", "Append .supatype/ to .gitignore if missing")
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export async function runLinkAction(opts: {
|
|
128
|
+
project?: string
|
|
129
|
+
url?: string
|
|
130
|
+
apiUrl: string
|
|
131
|
+
token?: string
|
|
132
|
+
serviceRoleKey?: string
|
|
133
|
+
env?: string
|
|
134
|
+
fixGitignore?: boolean
|
|
135
|
+
}): Promise<void> {
|
|
136
|
+
const cwd = process.cwd()
|
|
137
|
+
const config = loadConfig(cwd)
|
|
138
|
+
const projectRef = config.project?.name ?? "project"
|
|
139
|
+
const envName = opts.env ?? "production"
|
|
140
|
+
const token = resolveLinkToken(opts)
|
|
141
|
+
|
|
142
|
+
if (opts.fixGitignore) {
|
|
143
|
+
ensureSupatypeGitignore(cwd)
|
|
144
|
+
} else {
|
|
145
|
+
warnIfLinkNotGitignored(cwd)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const existing = loadProjectLink(cwd)
|
|
149
|
+
|
|
150
|
+
if (opts.url) {
|
|
151
|
+
if (!token) {
|
|
152
|
+
console.error("Authentication required. Pass --token $SERVICE_ROLE_KEY")
|
|
153
|
+
process.exit(1)
|
|
154
|
+
}
|
|
155
|
+
const apiUrl = opts.url.replace(/\/$/, "")
|
|
156
|
+
await probeSelfHostLink(apiUrl, projectRef, token)
|
|
157
|
+
const link = createSelfHostLink({
|
|
158
|
+
projectRef,
|
|
159
|
+
apiUrl,
|
|
160
|
+
token,
|
|
161
|
+
envName,
|
|
162
|
+
existing,
|
|
163
|
+
})
|
|
164
|
+
saveProjectLink(cwd, link)
|
|
165
|
+
console.log(`\nLinked to self-host environment "${envName}" at ${apiUrl}`)
|
|
166
|
+
console.log(`Config saved to .supatype/link.json\n`)
|
|
167
|
+
return
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (!token) {
|
|
171
|
+
console.error("Authentication required. Set SUPATYPE_ACCESS_TOKEN or pass --token.")
|
|
172
|
+
process.exit(1)
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const cloudApiUrl = opts.apiUrl.replace(/\/$/, "")
|
|
176
|
+
|
|
177
|
+
if (opts.project) {
|
|
178
|
+
const one = await targetFetch<{ slug: string; orgId: string }>(cloudApiUrl, "/api/v1", {
|
|
179
|
+
method: "GET",
|
|
180
|
+
path: `/projects/${opts.project}`,
|
|
181
|
+
token,
|
|
182
|
+
})
|
|
183
|
+
let environments: Array<{ name: string; apiUrl: string }> = [
|
|
184
|
+
{ name: "production", apiUrl: cloudApiUrl },
|
|
185
|
+
]
|
|
186
|
+
try {
|
|
187
|
+
const listed = await targetFetch<Array<{ name: string; apiUrl: string }>>(
|
|
188
|
+
cloudApiUrl,
|
|
189
|
+
"/api/v1",
|
|
190
|
+
{
|
|
191
|
+
method: "GET",
|
|
192
|
+
path: `/projects/${opts.project}/environments`,
|
|
193
|
+
token,
|
|
194
|
+
orgId: one.orgId,
|
|
195
|
+
},
|
|
196
|
+
)
|
|
197
|
+
if (listed.length > 0) {
|
|
198
|
+
environments = listed.map((e) => ({ name: e.name, apiUrl: e.apiUrl }))
|
|
199
|
+
}
|
|
200
|
+
} catch {
|
|
201
|
+
// environments optional on older control planes
|
|
202
|
+
}
|
|
203
|
+
const link = createCloudLink({
|
|
204
|
+
projectRef: opts.project,
|
|
205
|
+
cloudApiUrl,
|
|
206
|
+
token,
|
|
207
|
+
orgId: one.orgId,
|
|
208
|
+
environments,
|
|
209
|
+
existing,
|
|
210
|
+
})
|
|
211
|
+
saveProjectLink(cwd, link)
|
|
212
|
+
console.log(`\nLinked to cloud project: ${opts.project}`)
|
|
213
|
+
console.log(`Config saved to .supatype/link.json\n`)
|
|
214
|
+
return
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
console.error("Specify --project <slug> for cloud or --url <kong-url> for self-host.")
|
|
218
|
+
process.exit(1)
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export function getLinkOrExit(cwd: string): ProjectLink {
|
|
222
|
+
const link = loadProjectLink(cwd)
|
|
223
|
+
if (!link) {
|
|
224
|
+
console.error("Not linked. Run: supatype link")
|
|
225
|
+
process.exit(1)
|
|
226
|
+
}
|
|
227
|
+
return link
|
|
228
|
+
}
|