@specific.dev/cli 0.1.47 → 0.1.49
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 -1
- package/dist/admin/404.html +1 -1
- package/dist/admin/__next.__PAGE__.txt +1 -1
- package/dist/admin/__next._full.txt +1 -1
- package/dist/admin/__next._head.txt +1 -1
- package/dist/admin/__next._index.txt +1 -1
- package/dist/admin/__next._tree.txt +1 -1
- package/dist/admin/_not-found/__next._full.txt +1 -1
- package/dist/admin/_not-found/__next._head.txt +1 -1
- package/dist/admin/_not-found/__next._index.txt +1 -1
- package/dist/admin/_not-found/__next._not-found.__PAGE__.txt +1 -1
- package/dist/admin/_not-found/__next._not-found.txt +1 -1
- package/dist/admin/_not-found/__next._tree.txt +1 -1
- package/dist/admin/_not-found/index.html +1 -1
- package/dist/admin/_not-found/index.txt +1 -1
- package/dist/admin/databases/__next._full.txt +1 -1
- package/dist/admin/databases/__next._head.txt +1 -1
- package/dist/admin/databases/__next._index.txt +1 -1
- package/dist/admin/databases/__next._tree.txt +1 -1
- package/dist/admin/databases/__next.databases.__PAGE__.txt +1 -1
- package/dist/admin/databases/__next.databases.txt +1 -1
- package/dist/admin/databases/index.html +1 -1
- package/dist/admin/databases/index.txt +1 -1
- package/dist/admin/index.html +1 -1
- package/dist/admin/index.txt +1 -1
- package/dist/cli.js +889 -477
- package/dist/docs/builds.md +22 -0
- package/dist/docs/postgres.md +15 -0
- package/dist/docs/services.md +62 -8
- package/dist/postinstall.js +141 -0
- package/package.json +7 -3
- /package/dist/admin/_next/static/{pcYHo7d7--ealoH3_ELSO → FMaKxl-Dpw5U-PgHqIMag}/_buildManifest.js +0 -0
- /package/dist/admin/_next/static/{pcYHo7d7--ealoH3_ELSO → FMaKxl-Dpw5U-PgHqIMag}/_clientMiddlewareManifest.json +0 -0
- /package/dist/admin/_next/static/{pcYHo7d7--ealoH3_ELSO → FMaKxl-Dpw5U-PgHqIMag}/_ssgManifest.js +0 -0
package/dist/docs/builds.md
CHANGED
|
@@ -16,6 +16,7 @@ build "api" {
|
|
|
16
16
|
## Optional fields
|
|
17
17
|
|
|
18
18
|
- `command` - Build command to run after dependencies are installed (e.g., `npm run build`, `go build -o api`).
|
|
19
|
+
- `env` - Environment variables available during the build. Supports string literals and `service.<name>.public_url` references. These are passed as Docker build args.
|
|
19
20
|
|
|
20
21
|
## Automatic dependency installation
|
|
21
22
|
|
|
@@ -51,6 +52,27 @@ For **go**, **rust**, and **java**, a multi-stage build is used:
|
|
|
51
52
|
- **rust**: Only `target/release` is copied to the working directory in the runtime image.
|
|
52
53
|
- **java**: JAR files from `target/*.jar` or `build/libs/*.jar` are copied to `app.jar` in the working directory.
|
|
53
54
|
|
|
55
|
+
## Build-time environment variables
|
|
56
|
+
|
|
57
|
+
Use `env` to pass environment variables during the build step. This is useful for frameworks like Next.js that inline environment variables at build time.
|
|
58
|
+
|
|
59
|
+
```hcl
|
|
60
|
+
build "web" {
|
|
61
|
+
base = "node"
|
|
62
|
+
command = "npm run build"
|
|
63
|
+
|
|
64
|
+
env = {
|
|
65
|
+
NEXT_PUBLIC_API_URL = service.api.public_url
|
|
66
|
+
NODE_ENV = "production"
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Supported reference types:
|
|
72
|
+
|
|
73
|
+
- String literals (e.g., `"production"`)
|
|
74
|
+
- `service.<name>.public_url` - The public domain of another service
|
|
75
|
+
|
|
54
76
|
## Dev configuration
|
|
55
77
|
|
|
56
78
|
Override the build command for local development. If no `dev` block is defined, the build is skipped in development.
|
package/dist/docs/postgres.md
CHANGED
|
@@ -34,6 +34,21 @@ postgres "main" {}
|
|
|
34
34
|
- `password` - Database password
|
|
35
35
|
- `name` - Database name
|
|
36
36
|
|
|
37
|
+
## Querying the database
|
|
38
|
+
|
|
39
|
+
Use `specific psql` to connect to a development database. This will start the database if it's not already running.
|
|
40
|
+
|
|
41
|
+
```sh
|
|
42
|
+
# Interactive session
|
|
43
|
+
specific psql main
|
|
44
|
+
|
|
45
|
+
# Run a single query (useful for coding agents)
|
|
46
|
+
specific psql main -- -c "SELECT * FROM users LIMIT 5"
|
|
47
|
+
|
|
48
|
+
# List tables
|
|
49
|
+
specific psql main -- -c "\dt"
|
|
50
|
+
```
|
|
51
|
+
|
|
37
52
|
---
|
|
38
53
|
|
|
39
54
|
Related topics:
|
package/dist/docs/services.md
CHANGED
|
@@ -4,9 +4,28 @@ Services define how to run the code and connect it to other parts of the infrast
|
|
|
4
4
|
|
|
5
5
|
## Dynamic services
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Example of running publicly exposed API and frontend web services:
|
|
8
8
|
|
|
9
9
|
```hcl
|
|
10
|
+
build "web" {
|
|
11
|
+
base = "node"
|
|
12
|
+
command = "npm run build"
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
service "web" {
|
|
16
|
+
build = build.web
|
|
17
|
+
command = "npm start"
|
|
18
|
+
|
|
19
|
+
endpoint {
|
|
20
|
+
public = true
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
env = {
|
|
24
|
+
PORT = port
|
|
25
|
+
API_URL = service.api.public_url
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
10
29
|
build "api" {
|
|
11
30
|
base = "go"
|
|
12
31
|
command = "go build -o api"
|
|
@@ -93,9 +112,9 @@ service "worker" {
|
|
|
93
112
|
}
|
|
94
113
|
```
|
|
95
114
|
|
|
96
|
-
##
|
|
115
|
+
## Private inter-service communication
|
|
97
116
|
|
|
98
|
-
Services can reference other services' endpoints to communicate with each other:
|
|
117
|
+
Services can reference other services' endpoints using `private_url` to communicate with each other over a private network:
|
|
99
118
|
|
|
100
119
|
```hcl
|
|
101
120
|
service "api" {
|
|
@@ -116,23 +135,58 @@ service "worker" {
|
|
|
116
135
|
command = "./worker"
|
|
117
136
|
|
|
118
137
|
env = {
|
|
119
|
-
API_URL = service.api.
|
|
138
|
+
API_URL = service.api.private_url
|
|
120
139
|
}
|
|
121
140
|
}
|
|
122
141
|
```
|
|
123
142
|
|
|
124
143
|
Available service reference attributes:
|
|
125
144
|
|
|
126
|
-
- `service.<name>.
|
|
145
|
+
- `service.<name>.private_url` - Internal URL without scheme (host and port)
|
|
127
146
|
- `service.<name>.host` - Host only (e.g., `localhost`)
|
|
128
147
|
- `service.<name>.port` - Port only (e.g., `3000`)
|
|
148
|
+
- `service.<name>.public_url` - Public URL without scheme (host and port). Only available for endpoints with `public = true`. Served over HTTPS.
|
|
129
149
|
|
|
130
150
|
For services with multiple named endpoints, you must specify the endpoint:
|
|
131
151
|
|
|
132
152
|
```hcl
|
|
133
153
|
env = {
|
|
134
|
-
MAIN_URL = service.api.endpoint.main.
|
|
135
|
-
ADMIN_URL = service.api.endpoint.admin.
|
|
154
|
+
MAIN_URL = service.api.endpoint.main.private_url
|
|
155
|
+
ADMIN_URL = service.api.endpoint.admin.private_url
|
|
156
|
+
PUBLIC_API = service.api.endpoint.main.public_url
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Public inter-service communication
|
|
161
|
+
|
|
162
|
+
The private communication does not work in the case of a frontend or client service needing to speak with a backend service, as `private_url` is not externally reachable. In that case, the backend service MUST expose a public endpoint. The frontend or client service can then reference that endpoint using `public_url`:
|
|
163
|
+
|
|
164
|
+
```hcl
|
|
165
|
+
service "web" {
|
|
166
|
+
build = build.web
|
|
167
|
+
command = "npm start"
|
|
168
|
+
|
|
169
|
+
endpoint {
|
|
170
|
+
public = true
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
env = {
|
|
174
|
+
PORT = port
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
service "api" {
|
|
179
|
+
build = build.api
|
|
180
|
+
command = "./api"
|
|
181
|
+
|
|
182
|
+
endpoint {
|
|
183
|
+
public = true
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
env = {
|
|
187
|
+
PORT = port
|
|
188
|
+
CORS_ORIGIN = service.web.public_url
|
|
189
|
+
}
|
|
136
190
|
}
|
|
137
191
|
```
|
|
138
192
|
|
|
@@ -224,7 +278,7 @@ service "worker" {
|
|
|
224
278
|
dev {
|
|
225
279
|
command = "node --watch worker.js"
|
|
226
280
|
env = {
|
|
227
|
-
TEMPORAL_URL = service.temporal.
|
|
281
|
+
TEMPORAL_URL = service.temporal.private_url # Override with local URL in dev
|
|
228
282
|
}
|
|
229
283
|
}
|
|
230
284
|
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
// src/lib/analytics/index.ts
|
|
2
|
+
import { PostHog } from "posthog-node";
|
|
3
|
+
import * as os2 from "os";
|
|
4
|
+
import * as crypto from "crypto";
|
|
5
|
+
|
|
6
|
+
// src/lib/project/config.ts
|
|
7
|
+
import * as fs from "fs";
|
|
8
|
+
import * as path from "path";
|
|
9
|
+
var PROJECT_ID_FILE = ".specific/project_id";
|
|
10
|
+
var ProjectNotLinkedError = class extends Error {
|
|
11
|
+
constructor() {
|
|
12
|
+
super(
|
|
13
|
+
`Project not linked to Specific.
|
|
14
|
+
|
|
15
|
+
The file ${PROJECT_ID_FILE} does not exist.
|
|
16
|
+
|
|
17
|
+
Run: specific deploy`
|
|
18
|
+
);
|
|
19
|
+
this.name = "ProjectNotLinkedError";
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
function readProjectId(projectDir = process.cwd()) {
|
|
23
|
+
const projectIdPath = path.join(projectDir, PROJECT_ID_FILE);
|
|
24
|
+
if (!fs.existsSync(projectIdPath)) {
|
|
25
|
+
throw new ProjectNotLinkedError();
|
|
26
|
+
}
|
|
27
|
+
const projectId = fs.readFileSync(projectIdPath, "utf-8").trim();
|
|
28
|
+
if (!projectId) {
|
|
29
|
+
throw new Error(`${PROJECT_ID_FILE} is empty`);
|
|
30
|
+
}
|
|
31
|
+
return projectId;
|
|
32
|
+
}
|
|
33
|
+
function hasProjectId(projectDir = process.cwd()) {
|
|
34
|
+
const projectIdPath = path.join(projectDir, PROJECT_ID_FILE);
|
|
35
|
+
return fs.existsSync(projectIdPath);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// src/lib/auth/credentials.ts
|
|
39
|
+
import * as fs2 from "fs";
|
|
40
|
+
import * as path2 from "path";
|
|
41
|
+
import * as os from "os";
|
|
42
|
+
|
|
43
|
+
// src/lib/auth/login.tsx
|
|
44
|
+
import React from "react";
|
|
45
|
+
import { render, Box, Text } from "ink";
|
|
46
|
+
import Spinner from "ink-spinner";
|
|
47
|
+
|
|
48
|
+
// src/lib/auth/credentials.ts
|
|
49
|
+
function getUserCredentialsDir() {
|
|
50
|
+
return path2.join(os.homedir(), ".specific");
|
|
51
|
+
}
|
|
52
|
+
function getCredentialsPath() {
|
|
53
|
+
return path2.join(getUserCredentialsDir(), "credentials.json");
|
|
54
|
+
}
|
|
55
|
+
function readUserCredentials() {
|
|
56
|
+
const credentialsPath = getCredentialsPath();
|
|
57
|
+
if (!fs2.existsSync(credentialsPath)) {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
try {
|
|
61
|
+
const content = fs2.readFileSync(credentialsPath, "utf-8");
|
|
62
|
+
return JSON.parse(content);
|
|
63
|
+
} catch {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
function getUserId() {
|
|
68
|
+
const credentials = readUserCredentials();
|
|
69
|
+
return credentials?.userId ?? null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// src/lib/analytics/index.ts
|
|
73
|
+
var POSTHOG_HOST = "https://eu.i.posthog.com";
|
|
74
|
+
var client = null;
|
|
75
|
+
var anonymousId = null;
|
|
76
|
+
function isEnabled() {
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
function getAnonymousId() {
|
|
80
|
+
if (anonymousId) return anonymousId;
|
|
81
|
+
const machineId = `${os2.hostname()}-${os2.userInfo().username}`;
|
|
82
|
+
anonymousId = crypto.createHash("sha256").update(machineId).digest("hex").slice(0, 16);
|
|
83
|
+
return anonymousId;
|
|
84
|
+
}
|
|
85
|
+
function getProjectId() {
|
|
86
|
+
try {
|
|
87
|
+
if (hasProjectId()) {
|
|
88
|
+
return readProjectId();
|
|
89
|
+
}
|
|
90
|
+
} catch {
|
|
91
|
+
}
|
|
92
|
+
return void 0;
|
|
93
|
+
}
|
|
94
|
+
function getClient() {
|
|
95
|
+
if (!isEnabled()) return null;
|
|
96
|
+
if (!client) {
|
|
97
|
+
client = new PostHog("phc_qNQCEUXh6ErdciQRRmeG2xlVvwFjkcW6A5bnOFJ8vXZ", {
|
|
98
|
+
host: POSTHOG_HOST,
|
|
99
|
+
flushAt: 1,
|
|
100
|
+
// Flush immediately for CLI
|
|
101
|
+
flushInterval: 0
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
return client;
|
|
105
|
+
}
|
|
106
|
+
function trackEvent(event, properties) {
|
|
107
|
+
const ph = getClient();
|
|
108
|
+
if (!ph) return;
|
|
109
|
+
ph.capture({
|
|
110
|
+
distinctId: getAnonymousId(),
|
|
111
|
+
event,
|
|
112
|
+
properties: {
|
|
113
|
+
...properties,
|
|
114
|
+
cli_version: "0.1.49",
|
|
115
|
+
platform: process.platform,
|
|
116
|
+
node_version: process.version,
|
|
117
|
+
project_id: getProjectId(),
|
|
118
|
+
user_id: getUserId() ?? void 0
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
async function shutdown() {
|
|
123
|
+
if (client) {
|
|
124
|
+
await client.shutdown();
|
|
125
|
+
client = null;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// src/postinstall.ts
|
|
130
|
+
async function main() {
|
|
131
|
+
try {
|
|
132
|
+
trackEvent("cli_installed");
|
|
133
|
+
await shutdown();
|
|
134
|
+
} catch {
|
|
135
|
+
}
|
|
136
|
+
console.log();
|
|
137
|
+
console.log("Thanks for installing Specific!");
|
|
138
|
+
console.log("Get started: https://docs.specific.dev/");
|
|
139
|
+
console.log();
|
|
140
|
+
}
|
|
141
|
+
main();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@specific.dev/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.49",
|
|
4
4
|
"description": "CLI for Specific infrastructure-as-code",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/cli.js",
|
|
@@ -16,7 +16,8 @@
|
|
|
16
16
|
"build:staging": "NODE_ENV=staging node build.mjs && cp -r src/docs dist/",
|
|
17
17
|
"link:dev": "npm run build:dev && npm link",
|
|
18
18
|
"link:staging": "npm run build:staging && npm link",
|
|
19
|
-
"link:prod": "npm run build && npm link"
|
|
19
|
+
"link:prod": "npm run build && npm link",
|
|
20
|
+
"postinstall": "node dist/postinstall.js || true"
|
|
20
21
|
},
|
|
21
22
|
"keywords": [
|
|
22
23
|
"infrastructure",
|
|
@@ -41,15 +42,18 @@
|
|
|
41
42
|
"http-proxy": "^1.18.1",
|
|
42
43
|
"ink": "^6.5.1",
|
|
43
44
|
"ink-spinner": "^5.0.0",
|
|
45
|
+
"localtunnel": "^2.0.2",
|
|
44
46
|
"node-forge": "^1.3.1",
|
|
45
47
|
"open": "^11.0.0",
|
|
46
48
|
"posthog-node": "^5.24.1",
|
|
49
|
+
"random-word-slugs": "^0.1.7",
|
|
47
50
|
"react": "^19.0.0",
|
|
48
51
|
"s3rver": "^3.7.1",
|
|
49
52
|
"tar-vern": "^1.3.0"
|
|
50
53
|
},
|
|
51
54
|
"devDependencies": {
|
|
52
55
|
"@types/http-proxy": "^1.17.17",
|
|
56
|
+
"@types/localtunnel": "^2.0.4",
|
|
53
57
|
"@types/node": "^25.0.1",
|
|
54
58
|
"@types/node-forge": "^1.3.11",
|
|
55
59
|
"@types/react": "^19.2.7",
|
|
@@ -62,4 +66,4 @@
|
|
|
62
66
|
"esbuild"
|
|
63
67
|
]
|
|
64
68
|
}
|
|
65
|
-
}
|
|
69
|
+
}
|
/package/dist/admin/_next/static/{pcYHo7d7--ealoH3_ELSO → FMaKxl-Dpw5U-PgHqIMag}/_buildManifest.js
RENAMED
|
File without changes
|
|
File without changes
|
/package/dist/admin/_next/static/{pcYHo7d7--ealoH3_ELSO → FMaKxl-Dpw5U-PgHqIMag}/_ssgManifest.js
RENAMED
|
File without changes
|