@specific.dev/cli 0.1.37
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/dist/admin/404/index.html +1 -0
- package/dist/admin/404.html +1 -0
- package/dist/admin/__next.__PAGE__.txt +9 -0
- package/dist/admin/__next._full.txt +20 -0
- package/dist/admin/__next._head.txt +6 -0
- package/dist/admin/__next._index.txt +5 -0
- package/dist/admin/__next._tree.txt +4 -0
- package/dist/admin/_next/static/0zkv3YeV6IWOWVWE1S1A1/_buildManifest.js +11 -0
- package/dist/admin/_next/static/0zkv3YeV6IWOWVWE1S1A1/_clientMiddlewareManifest.json +1 -0
- package/dist/admin/_next/static/0zkv3YeV6IWOWVWE1S1A1/_ssgManifest.js +1 -0
- package/dist/admin/_next/static/1o2O2QPOKhuZyxcNaCjq8/_buildManifest.js +11 -0
- package/dist/admin/_next/static/1o2O2QPOKhuZyxcNaCjq8/_clientMiddlewareManifest.json +1 -0
- package/dist/admin/_next/static/1o2O2QPOKhuZyxcNaCjq8/_ssgManifest.js +1 -0
- package/dist/admin/_next/static/2ZtqtFX2EkuM82mTM7NWT/_buildManifest.js +11 -0
- package/dist/admin/_next/static/2ZtqtFX2EkuM82mTM7NWT/_clientMiddlewareManifest.json +1 -0
- package/dist/admin/_next/static/2ZtqtFX2EkuM82mTM7NWT/_ssgManifest.js +1 -0
- package/dist/admin/_next/static/2vLF5bo4ZtUfhs8bdQBoF/_buildManifest.js +11 -0
- package/dist/admin/_next/static/2vLF5bo4ZtUfhs8bdQBoF/_clientMiddlewareManifest.json +1 -0
- package/dist/admin/_next/static/2vLF5bo4ZtUfhs8bdQBoF/_ssgManifest.js +1 -0
- package/dist/admin/_next/static/8_8s51m7RbLVvbIechG-b/_buildManifest.js +11 -0
- package/dist/admin/_next/static/8_8s51m7RbLVvbIechG-b/_clientMiddlewareManifest.json +1 -0
- package/dist/admin/_next/static/8_8s51m7RbLVvbIechG-b/_ssgManifest.js +1 -0
- package/dist/admin/_next/static/C4Y44KtLnP684m57gN3Ga/_buildManifest.js +11 -0
- package/dist/admin/_next/static/C4Y44KtLnP684m57gN3Ga/_clientMiddlewareManifest.json +1 -0
- package/dist/admin/_next/static/C4Y44KtLnP684m57gN3Ga/_ssgManifest.js +1 -0
- package/dist/admin/_next/static/DBeh36kAJnZBeBMJSPBW8/_buildManifest.js +11 -0
- package/dist/admin/_next/static/DBeh36kAJnZBeBMJSPBW8/_clientMiddlewareManifest.json +1 -0
- package/dist/admin/_next/static/DBeh36kAJnZBeBMJSPBW8/_ssgManifest.js +1 -0
- package/dist/admin/_next/static/REKEUu6DP2t99jKSAqNOu/_buildManifest.js +11 -0
- package/dist/admin/_next/static/REKEUu6DP2t99jKSAqNOu/_clientMiddlewareManifest.json +1 -0
- package/dist/admin/_next/static/REKEUu6DP2t99jKSAqNOu/_ssgManifest.js +1 -0
- package/dist/admin/_next/static/TDGT3mkKtTe0w_CxujLxI/_buildManifest.js +11 -0
- package/dist/admin/_next/static/TDGT3mkKtTe0w_CxujLxI/_clientMiddlewareManifest.json +1 -0
- package/dist/admin/_next/static/TDGT3mkKtTe0w_CxujLxI/_ssgManifest.js +1 -0
- package/dist/admin/_next/static/ZHMJ-g7mAlAMt_2uCXeEk/_buildManifest.js +11 -0
- package/dist/admin/_next/static/ZHMJ-g7mAlAMt_2uCXeEk/_clientMiddlewareManifest.json +1 -0
- package/dist/admin/_next/static/ZHMJ-g7mAlAMt_2uCXeEk/_ssgManifest.js +1 -0
- package/dist/admin/_next/static/a6JDAB-CdvWPvS4sBgJji/_buildManifest.js +11 -0
- package/dist/admin/_next/static/a6JDAB-CdvWPvS4sBgJji/_clientMiddlewareManifest.json +1 -0
- package/dist/admin/_next/static/a6JDAB-CdvWPvS4sBgJji/_ssgManifest.js +1 -0
- package/dist/admin/_next/static/abFUeBpymhlx-IxygVnM9/_buildManifest.js +11 -0
- package/dist/admin/_next/static/abFUeBpymhlx-IxygVnM9/_clientMiddlewareManifest.json +1 -0
- package/dist/admin/_next/static/abFUeBpymhlx-IxygVnM9/_ssgManifest.js +1 -0
- package/dist/admin/_next/static/chunks/0afa5e999b6c353a.js +1 -0
- package/dist/admin/_next/static/chunks/1584f10ea1cebcb2.js +4 -0
- package/dist/admin/_next/static/chunks/195bbec70cfcd241.js +2 -0
- package/dist/admin/_next/static/chunks/2583656ea9ac4ad6.js +5 -0
- package/dist/admin/_next/static/chunks/465f799faf41e6df.js +1 -0
- package/dist/admin/_next/static/chunks/4ab079bdcb131778.js +5 -0
- package/dist/admin/_next/static/chunks/4b5ae5ebb2087f0d.js +2 -0
- package/dist/admin/_next/static/chunks/605800ff25160d05.js +1 -0
- package/dist/admin/_next/static/chunks/656b870f0567ed5f.js +1 -0
- package/dist/admin/_next/static/chunks/71098a6cd6181738.css +3 -0
- package/dist/admin/_next/static/chunks/806bdb8e4a6a9b95.js +4 -0
- package/dist/admin/_next/static/chunks/895a6f91f0b479fb.js +2 -0
- package/dist/admin/_next/static/chunks/9032f4a1aac1ca5d.css +3 -0
- package/dist/admin/_next/static/chunks/a28af2dc6f5fbaad.js +1 -0
- package/dist/admin/_next/static/chunks/a6dad97d9634a72d.js +1 -0
- package/dist/admin/_next/static/chunks/a6dad97d9634a72d.js.map +1 -0
- package/dist/admin/_next/static/chunks/b4205fc2f84bda68.css +3 -0
- package/dist/admin/_next/static/chunks/c1a750c25bc8d092.js +1 -0
- package/dist/admin/_next/static/chunks/c3d30f6f144dca51.js +2 -0
- package/dist/admin/_next/static/chunks/cbf55ce8731457ae.js +2 -0
- package/dist/admin/_next/static/chunks/d2be314c3ece3fbe.js +1 -0
- package/dist/admin/_next/static/chunks/de6af6d8adf8b50a.js +5 -0
- package/dist/admin/_next/static/chunks/f0c001244d275aab.js +5 -0
- package/dist/admin/_next/static/chunks/fde89fd76ad6a3d0.css +3 -0
- package/dist/admin/_next/static/chunks/ff1a16fafef87110.js +1 -0
- package/dist/admin/_next/static/chunks/turbopack-a3d691c83d4b1778.js +4 -0
- package/dist/admin/_next/static/chunks/turbopack-e5185af8e7c716ca.js +4 -0
- package/dist/admin/_next/static/dcjrfD44GuB6g8bZ6BcFm/_buildManifest.js +11 -0
- package/dist/admin/_next/static/dcjrfD44GuB6g8bZ6BcFm/_clientMiddlewareManifest.json +1 -0
- package/dist/admin/_next/static/dcjrfD44GuB6g8bZ6BcFm/_ssgManifest.js +1 -0
- package/dist/admin/_next/static/fTEa8Scx922n-dMuqN3Vc/_buildManifest.js +11 -0
- package/dist/admin/_next/static/fTEa8Scx922n-dMuqN3Vc/_clientMiddlewareManifest.json +1 -0
- package/dist/admin/_next/static/fTEa8Scx922n-dMuqN3Vc/_ssgManifest.js +1 -0
- package/dist/admin/_next/static/kZNwyhHft01wPtJ_AvqQT/_buildManifest.js +11 -0
- package/dist/admin/_next/static/kZNwyhHft01wPtJ_AvqQT/_clientMiddlewareManifest.json +1 -0
- package/dist/admin/_next/static/kZNwyhHft01wPtJ_AvqQT/_ssgManifest.js +1 -0
- package/dist/admin/_next/static/kyVInC6N3DY6NWnjkLQ4J/_buildManifest.js +11 -0
- package/dist/admin/_next/static/kyVInC6N3DY6NWnjkLQ4J/_clientMiddlewareManifest.json +1 -0
- package/dist/admin/_next/static/kyVInC6N3DY6NWnjkLQ4J/_ssgManifest.js +1 -0
- package/dist/admin/_next/static/media/4fa387ec64143e14-s.c1fdd6c2.woff2 +0 -0
- package/dist/admin/_next/static/media/7178b3e590c64307-s.b97b3418.woff2 +0 -0
- package/dist/admin/_next/static/media/797e433ab948586e-s.p.dbea232f.woff2 +0 -0
- package/dist/admin/_next/static/media/8a480f0b521d4e75-s.8e0177b5.woff2 +0 -0
- package/dist/admin/_next/static/media/bbc41e54d2fcbd21-s.799d8ef8.woff2 +0 -0
- package/dist/admin/_next/static/media/caa3a2e1cccd8315-s.p.853070df.woff2 +0 -0
- package/dist/admin/_next/static/media/favicon.0b3bf435.ico +0 -0
- package/dist/admin/_next/static/twedXBdhyxpjS68UQoNAc/_buildManifest.js +11 -0
- package/dist/admin/_next/static/twedXBdhyxpjS68UQoNAc/_clientMiddlewareManifest.json +1 -0
- package/dist/admin/_next/static/twedXBdhyxpjS68UQoNAc/_ssgManifest.js +1 -0
- package/dist/admin/_not-found/__next._full.txt +14 -0
- package/dist/admin/_not-found/__next._head.txt +6 -0
- package/dist/admin/_not-found/__next._index.txt +5 -0
- package/dist/admin/_not-found/__next._not-found.__PAGE__.txt +5 -0
- package/dist/admin/_not-found/__next._not-found.txt +4 -0
- package/dist/admin/_not-found/__next._tree.txt +2 -0
- package/dist/admin/_not-found/index.html +1 -0
- package/dist/admin/_not-found/index.txt +14 -0
- package/dist/admin/databases/__next._full.txt +20 -0
- package/dist/admin/databases/__next._head.txt +6 -0
- package/dist/admin/databases/__next._index.txt +5 -0
- package/dist/admin/databases/__next._tree.txt +4 -0
- package/dist/admin/databases/__next.databases.__PAGE__.txt +9 -0
- package/dist/admin/databases/__next.databases.txt +4 -0
- package/dist/admin/databases/index.html +1 -0
- package/dist/admin/databases/index.txt +20 -0
- package/dist/admin/favicon.ico +0 -0
- package/dist/admin/file.svg +1 -0
- package/dist/admin/globe.svg +1 -0
- package/dist/admin/index.html +1 -0
- package/dist/admin/index.txt +20 -0
- package/dist/admin/next.svg +1 -0
- package/dist/admin/vercel.svg +1 -0
- package/dist/admin/window.svg +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +190523 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/check.d.ts +2 -0
- package/dist/commands/check.d.ts.map +1 -0
- package/dist/commands/check.js +104 -0
- package/dist/commands/check.js.map +1 -0
- package/dist/commands/clean.d.ts +2 -0
- package/dist/commands/clean.d.ts.map +1 -0
- package/dist/commands/clean.js +70 -0
- package/dist/commands/clean.js.map +1 -0
- package/dist/commands/deploy.d.ts +2 -0
- package/dist/commands/deploy.d.ts.map +1 -0
- package/dist/commands/deploy.js +11 -0
- package/dist/commands/deploy.js.map +1 -0
- package/dist/commands/dev.d.ts +2 -0
- package/dist/commands/dev.d.ts.map +1 -0
- package/dist/commands/dev.js +398 -0
- package/dist/commands/dev.js.map +1 -0
- package/dist/commands/docs.d.ts +2 -0
- package/dist/commands/docs.d.ts.map +1 -0
- package/dist/commands/docs.js +32 -0
- package/dist/commands/docs.js.map +1 -0
- package/dist/commands/exec.d.ts +2 -0
- package/dist/commands/exec.d.ts.map +1 -0
- package/dist/commands/exec.js +178 -0
- package/dist/commands/exec.js.map +1 -0
- package/dist/commands/init.d.ts +2 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +339 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/psql.d.ts +2 -0
- package/dist/commands/psql.d.ts.map +1 -0
- package/dist/commands/psql.js +53 -0
- package/dist/commands/psql.js.map +1 -0
- package/dist/commands/secrets.d.ts +3 -0
- package/dist/commands/secrets.d.ts.map +1 -0
- package/dist/commands/secrets.js +136 -0
- package/dist/commands/secrets.js.map +1 -0
- package/dist/docs/builds.md +71 -0
- package/dist/docs/index.md +30 -0
- package/dist/docs/integrations/drizzle.md +83 -0
- package/dist/docs/integrations/nextjs.md +45 -0
- package/dist/docs/integrations/prisma.md +110 -0
- package/dist/docs/postgres.md +41 -0
- package/dist/docs/redis.md +33 -0
- package/dist/docs/secrets-config.md +141 -0
- package/dist/docs/services.md +325 -0
- package/dist/docs/storage.md +62 -0
- package/dist/docs/sync.md +66 -0
- package/dist/lib/dev/database-manager.d.ts +17 -0
- package/dist/lib/dev/database-manager.d.ts.map +1 -0
- package/dist/lib/dev/database-manager.js +114 -0
- package/dist/lib/dev/database-manager.js.map +1 -0
- package/dist/lib/dev/env-resolver.d.ts +14 -0
- package/dist/lib/dev/env-resolver.d.ts.map +1 -0
- package/dist/lib/dev/env-resolver.js +109 -0
- package/dist/lib/dev/env-resolver.js.map +1 -0
- package/dist/lib/dev/http-proxy.d.ts +11 -0
- package/dist/lib/dev/http-proxy.d.ts.map +1 -0
- package/dist/lib/dev/http-proxy.js +165 -0
- package/dist/lib/dev/http-proxy.js.map +1 -0
- package/dist/lib/dev/index.d.ts +11 -0
- package/dist/lib/dev/index.d.ts.map +1 -0
- package/dist/lib/dev/index.js +7 -0
- package/dist/lib/dev/index.js.map +1 -0
- package/dist/lib/dev/instance-state.d.ts +45 -0
- package/dist/lib/dev/instance-state.d.ts.map +1 -0
- package/dist/lib/dev/instance-state.js +213 -0
- package/dist/lib/dev/instance-state.js.map +1 -0
- package/dist/lib/dev/port-allocator.d.ts +5 -0
- package/dist/lib/dev/port-allocator.d.ts.map +1 -0
- package/dist/lib/dev/port-allocator.js +21 -0
- package/dist/lib/dev/port-allocator.js.map +1 -0
- package/dist/lib/dev/service-runner.d.ts +16 -0
- package/dist/lib/dev/service-runner.d.ts.map +1 -0
- package/dist/lib/dev/service-runner.js +61 -0
- package/dist/lib/dev/service-runner.js.map +1 -0
- package/dist/lib/secrets/index.d.ts +2 -0
- package/dist/lib/secrets/index.d.ts.map +1 -0
- package/dist/lib/secrets/index.js +2 -0
- package/dist/lib/secrets/index.js.map +1 -0
- package/dist/lib/secrets/parser.d.ts +37 -0
- package/dist/lib/secrets/parser.d.ts.map +1 -0
- package/dist/lib/secrets/parser.js +116 -0
- package/dist/lib/secrets/parser.js.map +1 -0
- package/package.json +54 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"psql.d.ts","sourceRoot":"","sources":["../../src/commands/psql.tsx"],"names":[],"mappings":"AAGA,wBAAsB,WAAW,CAAC,YAAY,CAAC,EAAE,MAAM,iBA4DtD"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { spawn } from "child_process";
|
|
2
|
+
import { InstanceStateManager } from "../lib/dev/index.js";
|
|
3
|
+
export async function psqlCommand(databaseName) {
|
|
4
|
+
const stateManager = new InstanceStateManager(process.cwd());
|
|
5
|
+
await stateManager.cleanStaleState();
|
|
6
|
+
const existingInstances = await stateManager.getExistingInstances();
|
|
7
|
+
if (!existingInstances) {
|
|
8
|
+
console.error("Error: No running instances found. Run `specific dev` first.");
|
|
9
|
+
process.exit(1);
|
|
10
|
+
}
|
|
11
|
+
const availableDatabases = Object.keys(existingInstances.databases);
|
|
12
|
+
if (availableDatabases.length === 0) {
|
|
13
|
+
console.error("Error: No databases found in running instances.");
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
|
16
|
+
let targetDb;
|
|
17
|
+
if (databaseName) {
|
|
18
|
+
if (!existingInstances.databases[databaseName]) {
|
|
19
|
+
console.error(`Error: Database "${databaseName}" not found. Available: ${availableDatabases.join(", ")}`);
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
targetDb = databaseName;
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
if (availableDatabases.length > 1) {
|
|
26
|
+
console.error(`Error: Multiple databases available. Specify one: ${availableDatabases.join(", ")}`);
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
29
|
+
targetDb = availableDatabases[0];
|
|
30
|
+
}
|
|
31
|
+
const dbState = existingInstances.databases[targetDb];
|
|
32
|
+
const child = spawn("psql", ["-h", dbState.host, "-p", String(dbState.port), "-U", dbState.user, "-d", dbState.dbName], {
|
|
33
|
+
cwd: process.cwd(),
|
|
34
|
+
env: {
|
|
35
|
+
...process.env,
|
|
36
|
+
PGPASSWORD: dbState.password,
|
|
37
|
+
},
|
|
38
|
+
stdio: "inherit",
|
|
39
|
+
});
|
|
40
|
+
child.on("exit", (code, signal) => {
|
|
41
|
+
if (signal) {
|
|
42
|
+
process.kill(process.pid, signal);
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
process.exit(code ?? 0);
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
child.on("error", (err) => {
|
|
49
|
+
console.error(`Error: Failed to start psql: ${err.message}`);
|
|
50
|
+
process.exit(1);
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=psql.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"psql.js","sourceRoot":"","sources":["../../src/commands/psql.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AACtC,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAE3D,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,YAAqB;IACrD,MAAM,YAAY,GAAG,IAAI,oBAAoB,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;IAC7D,MAAM,YAAY,CAAC,eAAe,EAAE,CAAC;IACrC,MAAM,iBAAiB,GAAG,MAAM,YAAY,CAAC,oBAAoB,EAAE,CAAC;IAEpE,IAAI,CAAC,iBAAiB,EAAE,CAAC;QACvB,OAAO,CAAC,KAAK,CAAC,8DAA8D,CAAC,CAAC;QAC9E,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,MAAM,kBAAkB,GAAG,MAAM,CAAC,IAAI,CAAC,iBAAiB,CAAC,SAAS,CAAC,CAAC;IAEpE,IAAI,kBAAkB,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACpC,OAAO,CAAC,KAAK,CAAC,iDAAiD,CAAC,CAAC;QACjE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,IAAI,QAAgB,CAAC;IAErB,IAAI,YAAY,EAAE,CAAC;QACjB,IAAI,CAAC,iBAAiB,CAAC,SAAS,CAAC,YAAY,CAAC,EAAE,CAAC;YAC/C,OAAO,CAAC,KAAK,CACX,oBAAoB,YAAY,2BAA2B,kBAAkB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAC3F,CAAC;YACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QACD,QAAQ,GAAG,YAAY,CAAC;IAC1B,CAAC;SAAM,CAAC;QACN,IAAI,kBAAkB,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAClC,OAAO,CAAC,KAAK,CACX,qDAAqD,kBAAkB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CACrF,CAAC;YACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QACD,QAAQ,GAAG,kBAAkB,CAAC,CAAC,CAAE,CAAC;IACpC,CAAC;IAED,MAAM,OAAO,GAAG,iBAAiB,CAAC,SAAS,CAAC,QAAQ,CAAE,CAAC;IAEvD,MAAM,KAAK,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,CAAC,MAAM,CAAC,EAAE;QACtH,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE;QAClB,GAAG,EAAE;YACH,GAAG,OAAO,CAAC,GAAG;YACd,UAAU,EAAE,OAAO,CAAC,QAAQ;SAC7B;QACD,KAAK,EAAE,SAAS;KACjB,CAAC,CAAC;IAEH,KAAK,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE;QAChC,IAAI,MAAM,EAAE,CAAC;YACX,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;QACpC,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC;QAC1B,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE;QACxB,OAAO,CAAC,KAAK,CAAC,gCAAgC,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;QAC7D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"secrets.d.ts","sourceRoot":"","sources":["../../src/commands/secrets.tsx"],"names":[],"mappings":"AAsHA,wBAAsB,iBAAiB,CAAC,UAAU,EAAE,MAAM,iBAQzD;AAED,wBAAsB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,EAAE,UAAU,EAAE,MAAM,GAAG,SAAS,iBAqB9F"}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import React, { useState, useEffect } from "react";
|
|
2
|
+
import { render, Text, Box, useInput, useApp } from "ink";
|
|
3
|
+
import * as fs from "fs";
|
|
4
|
+
import { SECRETS_FILE } from "../lib/secrets/index.js";
|
|
5
|
+
const HEADER_COMMENT = "# Do not commit this file - it contains secrets\n";
|
|
6
|
+
function SetSecretUI({ secretName }) {
|
|
7
|
+
const { exit } = useApp();
|
|
8
|
+
const [value, setValue] = useState("");
|
|
9
|
+
const [done, setDone] = useState(false);
|
|
10
|
+
const [error, setError] = useState(null);
|
|
11
|
+
useInput((input, key) => {
|
|
12
|
+
if (done)
|
|
13
|
+
return;
|
|
14
|
+
if (key.return) {
|
|
15
|
+
if (value.trim() === "") {
|
|
16
|
+
setError("Secret value cannot be empty");
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
try {
|
|
20
|
+
// Escape quotes and backslashes in the value for HCL
|
|
21
|
+
const escapedValue = value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
22
|
+
const hclLine = `${secretName} = "${escapedValue}"`;
|
|
23
|
+
// Read existing file or start fresh
|
|
24
|
+
let content = "";
|
|
25
|
+
let hasHeader = false;
|
|
26
|
+
if (fs.existsSync(SECRETS_FILE)) {
|
|
27
|
+
content = fs.readFileSync(SECRETS_FILE, "utf-8");
|
|
28
|
+
hasHeader = content.startsWith("#");
|
|
29
|
+
// Check if this secret already exists and update it
|
|
30
|
+
const lines = content.split("\n");
|
|
31
|
+
const newLines = [];
|
|
32
|
+
let found = false;
|
|
33
|
+
// Match: secretName = "..." or secretName="..."
|
|
34
|
+
const pattern = new RegExp(`^${secretName}\\s*=\\s*"`);
|
|
35
|
+
for (const line of lines) {
|
|
36
|
+
if (pattern.test(line.trim())) {
|
|
37
|
+
newLines.push(hclLine);
|
|
38
|
+
found = true;
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
newLines.push(line);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (found) {
|
|
45
|
+
fs.writeFileSync(SECRETS_FILE, newLines.join("\n"));
|
|
46
|
+
setDone(true);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
// Append new secret
|
|
51
|
+
let newContent = "";
|
|
52
|
+
if (!hasHeader && !content) {
|
|
53
|
+
newContent = HEADER_COMMENT + "\n";
|
|
54
|
+
}
|
|
55
|
+
else if (content) {
|
|
56
|
+
newContent = content.endsWith("\n") ? content : content + "\n";
|
|
57
|
+
}
|
|
58
|
+
newContent += `${hclLine}\n`;
|
|
59
|
+
fs.writeFileSync(SECRETS_FILE, newContent);
|
|
60
|
+
setDone(true);
|
|
61
|
+
}
|
|
62
|
+
catch (err) {
|
|
63
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
else if (key.backspace || key.delete) {
|
|
67
|
+
setValue((prev) => prev.slice(0, -1));
|
|
68
|
+
}
|
|
69
|
+
else if (key.escape) {
|
|
70
|
+
exit();
|
|
71
|
+
}
|
|
72
|
+
else if (!key.ctrl && !key.meta && input) {
|
|
73
|
+
setValue((prev) => prev + input);
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
useEffect(() => {
|
|
77
|
+
if (done) {
|
|
78
|
+
const timer = setTimeout(() => exit(), 50);
|
|
79
|
+
return () => clearTimeout(timer);
|
|
80
|
+
}
|
|
81
|
+
}, [done, exit]);
|
|
82
|
+
if (error) {
|
|
83
|
+
return React.createElement(Text, { color: "red" },
|
|
84
|
+
"Error: ",
|
|
85
|
+
error);
|
|
86
|
+
}
|
|
87
|
+
if (done) {
|
|
88
|
+
return (React.createElement(Box, { flexDirection: "column" },
|
|
89
|
+
React.createElement(Text, { color: "green" },
|
|
90
|
+
"Secret '",
|
|
91
|
+
secretName,
|
|
92
|
+
"' saved to ",
|
|
93
|
+
SECRETS_FILE)));
|
|
94
|
+
}
|
|
95
|
+
return (React.createElement(Box, { flexDirection: "column" },
|
|
96
|
+
React.createElement(Text, null,
|
|
97
|
+
"Enter value for secret '",
|
|
98
|
+
secretName,
|
|
99
|
+
"':"),
|
|
100
|
+
React.createElement(Box, null,
|
|
101
|
+
React.createElement(Text, { color: "cyan" }, "> "),
|
|
102
|
+
React.createElement(Text, null, value.length > 0 ? "*".repeat(value.length) : ""),
|
|
103
|
+
React.createElement(Text, { color: "gray" }, "|")),
|
|
104
|
+
React.createElement(Text, { dimColor: true }, "(Press Enter to save, Esc to cancel)")));
|
|
105
|
+
}
|
|
106
|
+
export async function secretsSetCommand(secretName) {
|
|
107
|
+
if (!secretName) {
|
|
108
|
+
console.error("Error: Secret name required");
|
|
109
|
+
console.error("Usage: specific secrets set <name>");
|
|
110
|
+
process.exit(1);
|
|
111
|
+
}
|
|
112
|
+
render(React.createElement(SetSecretUI, { secretName: secretName }));
|
|
113
|
+
}
|
|
114
|
+
export async function secretsCommand(action, secretName) {
|
|
115
|
+
if (!action) {
|
|
116
|
+
console.error("Usage: specific secrets <command>");
|
|
117
|
+
console.error("");
|
|
118
|
+
console.error("Commands:");
|
|
119
|
+
console.error(" set <name> Set a secret value");
|
|
120
|
+
process.exit(1);
|
|
121
|
+
}
|
|
122
|
+
if (action === "set") {
|
|
123
|
+
if (!secretName) {
|
|
124
|
+
console.error("Error: Secret name required");
|
|
125
|
+
console.error("Usage: specific secrets set <name>");
|
|
126
|
+
process.exit(1);
|
|
127
|
+
}
|
|
128
|
+
await secretsSetCommand(secretName);
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
console.error(`Unknown command: ${action}`);
|
|
132
|
+
console.error("Usage: specific secrets set <name>");
|
|
133
|
+
process.exit(1);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
//# sourceMappingURL=secrets.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"secrets.js","sourceRoot":"","sources":["../../src/commands/secrets.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# Builds
|
|
2
|
+
|
|
3
|
+
Builds define how to produce artifacts. Services reference builds.
|
|
4
|
+
|
|
5
|
+
```hcl
|
|
6
|
+
build "api" {
|
|
7
|
+
base = "node"
|
|
8
|
+
command = "npm run build"
|
|
9
|
+
}
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Required fields
|
|
13
|
+
|
|
14
|
+
- `base` - Base environment. Supported: `"node"`, `"python"`, `"go"`, `"rust"`, `"java"`.
|
|
15
|
+
|
|
16
|
+
## Optional fields
|
|
17
|
+
|
|
18
|
+
- `command` - Build command to run after dependencies are installed (e.g., `npm run build`, `go build -o api`).
|
|
19
|
+
|
|
20
|
+
## Automatic dependency installation
|
|
21
|
+
|
|
22
|
+
Specific automatically installs dependencies before running your build command. Dependencies are cached in a separate Docker layer for faster builds when only source code changes.
|
|
23
|
+
|
|
24
|
+
| Base | Detected Files | Install Command |
|
|
25
|
+
|------|----------------|-----------------|
|
|
26
|
+
| `node` | `package-lock.json` | `npm ci` |
|
|
27
|
+
| `node` | `yarn.lock` | `yarn install --frozen-lockfile` |
|
|
28
|
+
| `node` | `pnpm-lock.yaml` | `pnpm install --frozen-lockfile` |
|
|
29
|
+
| `node` | `package.json` only | `npm install` |
|
|
30
|
+
| `python` | `requirements.txt` | `pip install -r requirements.txt` |
|
|
31
|
+
| `python` | `Pipfile.lock` | `pipenv install --deploy --system` |
|
|
32
|
+
| `python` | `poetry.lock` | `poetry install` |
|
|
33
|
+
| `python` | `pyproject.toml` | `pip install .` |
|
|
34
|
+
| `go` | `go.mod` | `go mod download` |
|
|
35
|
+
|
|
36
|
+
For simple projects that only need dependencies installed, you can omit the `command` field:
|
|
37
|
+
|
|
38
|
+
```hcl
|
|
39
|
+
build "api" {
|
|
40
|
+
base = "node"
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Base details
|
|
45
|
+
|
|
46
|
+
For **node** and **python**, the entire working directory is available at runtime.
|
|
47
|
+
|
|
48
|
+
For **go**, **rust**, and **java**, a multi-stage build is used:
|
|
49
|
+
|
|
50
|
+
- **go**: The entire working directory is copied to the runtime image.
|
|
51
|
+
- **rust**: Only `target/release` is copied to the working directory in the runtime image.
|
|
52
|
+
- **java**: JAR files from `target/*.jar` or `build/libs/*.jar` are copied to `app.jar` in the working directory.
|
|
53
|
+
|
|
54
|
+
## Dev configuration
|
|
55
|
+
|
|
56
|
+
Override the build command for local development. If no `dev` block is defined, the build is skipped in development.
|
|
57
|
+
|
|
58
|
+
```hcl
|
|
59
|
+
build "spa" {
|
|
60
|
+
base = "node"
|
|
61
|
+
command = "npm run build"
|
|
62
|
+
|
|
63
|
+
dev {
|
|
64
|
+
command = "npm run build:watch"
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
Run `specific docs services` for service configuration.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Specific
|
|
2
|
+
|
|
3
|
+
Infrastructure-as-code designed for coding agents. Define your infrastructure in `specific.hcl` alongside your application code. This will define how to build, deploy and run the full system in production, but also how to run it in development. Specific is fully generic and works with any programming language and framework without any SDKs. We use environments variables to pass information to the code.
|
|
4
|
+
|
|
5
|
+
This documentation is nested and references to other docs are in Markdown link format. Run `specific docs [PATH]` to read a docs page.
|
|
6
|
+
|
|
7
|
+
## Workflow
|
|
8
|
+
|
|
9
|
+
1. Create or edit `specific.hcl` in the project root
|
|
10
|
+
2. ALWAYS run `specific check` to validate configuration and fix any issues
|
|
11
|
+
3. Run yourself or instruct the user to run `specific exec [SERVICE] --- [COMMAND]` for one-off commands that need to be run in development, like database migrations or seeding. The command will be run with databases and other services started and env vars injected.
|
|
12
|
+
|
|
13
|
+
A full development environment can be started with `specific dev`. To deploy any changes, the user can run `specific deploy`.
|
|
14
|
+
|
|
15
|
+
## Topics
|
|
16
|
+
|
|
17
|
+
- [Services](/services): define how to build, deploy and run the code in this project as services, both in development and production.
|
|
18
|
+
- [Secrets and configuration](/secrets-config): define secrets and configuration variables that the user may or should provide. These can be injected into services through environment variables.
|
|
19
|
+
- [Postgres](/postgres): define managed PostgreSQL databases that services depend on.
|
|
20
|
+
- [Sync](/sync): define real-time, partial synchronisation out of PostgreSQL into frontends or other services. Should be used to implement real-time and collaborative apps.
|
|
21
|
+
- [Storage](/storage): define S3-compatible object/blob storage for services to store files in.
|
|
22
|
+
- [Redis](/redis): define non-durable Redis-compatible databases for caching and more.
|
|
23
|
+
|
|
24
|
+
## Common integrations
|
|
25
|
+
|
|
26
|
+
The following is a list of common frameworks and libraries with guidance on how to use them with Specific:
|
|
27
|
+
|
|
28
|
+
- [Next.js](/integrations/nextjs): full-stack React framework
|
|
29
|
+
- [Drizzle ORM](/integrations/drizzle): TypeScript ORM with type safety
|
|
30
|
+
- [Prisma](/integrations/prisma): TypeScript ORM with auto-generated client
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# Drizzle ORM
|
|
2
|
+
|
|
3
|
+
Drizzle is a TypeScript ORM with a focus on type safety and performance. This guide covers using Drizzle with Specific Postgres.
|
|
4
|
+
|
|
5
|
+
## Configuration
|
|
6
|
+
|
|
7
|
+
Pass the database URL to your service and always add a `pre_deploy` step to run database migrations.
|
|
8
|
+
|
|
9
|
+
```hcl
|
|
10
|
+
build "api" {
|
|
11
|
+
base = "node"
|
|
12
|
+
command = "npm run build"
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
postgres "main" {}
|
|
16
|
+
|
|
17
|
+
service "api" {
|
|
18
|
+
build = build.api
|
|
19
|
+
command = "node dist/index.js"
|
|
20
|
+
|
|
21
|
+
endpoint {
|
|
22
|
+
public = true
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
env = {
|
|
26
|
+
PORT = port
|
|
27
|
+
DATABASE_URL = postgres.main.url
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
dev {
|
|
31
|
+
command = "npm run dev"
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
pre_deploy {
|
|
35
|
+
command = "npx drizzle-kit migrate"
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Drizzle configuration
|
|
41
|
+
|
|
42
|
+
Configure Drizzle to read the connection string from the environment in `drizzle.config.ts`:
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
import { defineConfig } from "drizzle-kit";
|
|
46
|
+
|
|
47
|
+
export default defineConfig({
|
|
48
|
+
schema: "./src/db/schema.ts",
|
|
49
|
+
out: "./drizzle",
|
|
50
|
+
dialect: "postgresql",
|
|
51
|
+
dbCredentials: {
|
|
52
|
+
url: process.env.DATABASE_URL!,
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Development
|
|
58
|
+
|
|
59
|
+
Use `specific exec` to push schema changes and generate migrations:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
# Push schema directly to local database (for rapid iteration)
|
|
63
|
+
specific exec api -- npx drizzle-kit push
|
|
64
|
+
|
|
65
|
+
# If user is relying on Drizzle migrations, generate them once done with schema changes
|
|
66
|
+
specific exec api -- npx drizzle-kit generate
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Database client
|
|
70
|
+
|
|
71
|
+
Create your database client reading from the environment:
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
import { drizzle } from "drizzle-orm/postgres-js";
|
|
75
|
+
import postgres from "postgres";
|
|
76
|
+
|
|
77
|
+
const client = postgres(process.env.DATABASE_URL!);
|
|
78
|
+
export const db = drizzle(client);
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Drizzle studio
|
|
82
|
+
|
|
83
|
+
Drizzle studio is not needed as `specific dev` includes a database viewer and editor in its admin interface.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Next.js
|
|
2
|
+
|
|
3
|
+
Next.js is a full-stack React framework. This guide covers running Next.js with Specific.
|
|
4
|
+
|
|
5
|
+
## Configuration
|
|
6
|
+
|
|
7
|
+
For smaller Docker images and faster cold starts, enable standalone output in `next.config.js`:
|
|
8
|
+
|
|
9
|
+
```javascript
|
|
10
|
+
module.exports = {
|
|
11
|
+
output: "standalone",
|
|
12
|
+
};
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Then reference the standalone output to run the server:
|
|
16
|
+
|
|
17
|
+
```hcl
|
|
18
|
+
build "web" {
|
|
19
|
+
base = "node"
|
|
20
|
+
command = "npm run build"
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
service "web" {
|
|
24
|
+
build = build.web
|
|
25
|
+
command = "node .next/standalone/server.js"
|
|
26
|
+
|
|
27
|
+
endpoint {
|
|
28
|
+
public = true
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
env = {
|
|
32
|
+
PORT = port
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
dev {
|
|
36
|
+
command = "npm run dev"
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Next.js reads `PORT` from the environment automatically, so no additional configuration is needed.
|
|
42
|
+
|
|
43
|
+
## Important note on pre-rendering during builds
|
|
44
|
+
|
|
45
|
+
During a build, Next.js will load a lot of code to perform pre-rendering. That code can not reference any environments variables from `specific.hcl`, like database URLs or API keys from secrets, as those are not available during the build and will cause it to fail. Ensure all code that relies on these env vars DOES NOT get executed during the build phase.
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# Prisma
|
|
2
|
+
|
|
3
|
+
Prisma is a TypeScript ORM with auto-generated client and migrations. This guide covers using Prisma with Specific.
|
|
4
|
+
|
|
5
|
+
## Setup
|
|
6
|
+
|
|
7
|
+
Pass the database URL to your service and always add a `pre_deploy` step to run database migrations.
|
|
8
|
+
|
|
9
|
+
```hcl
|
|
10
|
+
build "api" {
|
|
11
|
+
base = "node"
|
|
12
|
+
command = "npx prisma generate && npm run build"
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
service "api" {
|
|
16
|
+
build = build.api
|
|
17
|
+
command = "node dist/index.js"
|
|
18
|
+
|
|
19
|
+
endpoint {
|
|
20
|
+
public = true
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
env = {
|
|
24
|
+
PORT = port
|
|
25
|
+
DATABASE_URL = postgres.main.url
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
pre_deploy {
|
|
29
|
+
command = "npx prisma migrate deploy"
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
dev {
|
|
33
|
+
command = "npm run dev"
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
postgres "main" {}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Note: The build command includes `prisma generate` to ensure the Prisma client is generated before building.
|
|
41
|
+
|
|
42
|
+
## Prisma schema
|
|
43
|
+
|
|
44
|
+
Configure the datasource in `prisma/schema.prisma`:
|
|
45
|
+
|
|
46
|
+
```prisma
|
|
47
|
+
datasource db {
|
|
48
|
+
provider = "postgresql"
|
|
49
|
+
url = env("DATABASE_URL")
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
generator client {
|
|
53
|
+
provider = "prisma-client-js"
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Development
|
|
58
|
+
|
|
59
|
+
Use `specific exec` to run migrations:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
# Create and apply a new migration
|
|
63
|
+
specific exec api -- npx prisma migrate dev --name add_users_table
|
|
64
|
+
|
|
65
|
+
# Apply pending migrations
|
|
66
|
+
specific exec api -- npx prisma migrate dev
|
|
67
|
+
|
|
68
|
+
# Push schema directly (for rapid prototyping)
|
|
69
|
+
specific exec api -- npx prisma db push
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
To seed your database, add a seed script to `package.json`:
|
|
73
|
+
|
|
74
|
+
```json
|
|
75
|
+
{
|
|
76
|
+
"prisma": {
|
|
77
|
+
"seed": "npx tsx prisma/seed.ts"
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Then run it:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
specific exec api -- npx prisma db seed
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Database client
|
|
89
|
+
|
|
90
|
+
Create a singleton Prisma client:
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
import { PrismaClient } from "@prisma/client";
|
|
94
|
+
|
|
95
|
+
const globalForPrisma = globalThis as unknown as {
|
|
96
|
+
prisma: PrismaClient | undefined;
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
export const prisma = globalForPrisma.prisma ?? new PrismaClient();
|
|
100
|
+
|
|
101
|
+
if (process.env.NODE_ENV !== "production") {
|
|
102
|
+
globalForPrisma.prisma = prisma;
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
The singleton pattern prevents multiple client instances during hot reloading in development.
|
|
107
|
+
|
|
108
|
+
## Prisma Studio
|
|
109
|
+
|
|
110
|
+
Prisma Studio is not necessary as `specific dev` includes a database viewer and editor in its admin interface.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Postgres
|
|
2
|
+
|
|
3
|
+
Managed PostgreSQL database instances.
|
|
4
|
+
|
|
5
|
+
```hcl
|
|
6
|
+
postgres "main" {}
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
Reference postgres attributes in env blocks:
|
|
10
|
+
|
|
11
|
+
```hcl
|
|
12
|
+
service "api" {
|
|
13
|
+
build = build.api
|
|
14
|
+
command = "./api"
|
|
15
|
+
|
|
16
|
+
endpoint {
|
|
17
|
+
public = true
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
env = {
|
|
21
|
+
DATABASE_URL = postgres.main.url
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
postgres "main" {}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Available postgres attributes
|
|
29
|
+
|
|
30
|
+
- `url` - Full connection string (e.g., `postgres://user:pass@host:5432/dbname`)
|
|
31
|
+
- `host` - Database host
|
|
32
|
+
- `port` - Database port
|
|
33
|
+
- `user` - Database user
|
|
34
|
+
- `password` - Database password
|
|
35
|
+
- `name` - Database name
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
Related topics:
|
|
40
|
+
- Run `specific docs sync` for real-time data synchronization
|
|
41
|
+
- Run `specific docs exec` for running migrations
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Redis
|
|
2
|
+
|
|
3
|
+
Managed Redis instances.
|
|
4
|
+
|
|
5
|
+
```hcl
|
|
6
|
+
redis "cache" {}
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
Reference redis attributes in env blocks:
|
|
10
|
+
|
|
11
|
+
```hcl
|
|
12
|
+
service "api" {
|
|
13
|
+
build = build.api
|
|
14
|
+
command = "./api"
|
|
15
|
+
|
|
16
|
+
endpoint {
|
|
17
|
+
public = true
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
env = {
|
|
21
|
+
REDIS_URL = redis.cache.url
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
redis "cache" {}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Available redis attributes
|
|
29
|
+
|
|
30
|
+
- `url` - Full connection string (e.g., `redis://:password@host:6379`)
|
|
31
|
+
- `host` - Redis host
|
|
32
|
+
- `port` - Redis port
|
|
33
|
+
- `password` - Redis password for authentication
|