@specific.dev/cli 0.1.122 → 0.1.124
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.!KGRlZmF1bHQp.__PAGE__.txt +1 -1
- package/dist/admin/__next.!KGRlZmF1bHQp.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.!KGRlZmF1bHQp.databases.__PAGE__.txt +1 -1
- package/dist/admin/databases/__next.!KGRlZmF1bHQp.databases.txt +1 -1
- package/dist/admin/databases/__next.!KGRlZmF1bHQp.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/index.html +1 -1
- package/dist/admin/databases/index.txt +1 -1
- package/dist/admin/fullscreen/__next._full.txt +1 -1
- package/dist/admin/fullscreen/__next._head.txt +1 -1
- package/dist/admin/fullscreen/__next._index.txt +1 -1
- package/dist/admin/fullscreen/__next._tree.txt +1 -1
- package/dist/admin/fullscreen/__next.fullscreen.__PAGE__.txt +1 -1
- package/dist/admin/fullscreen/__next.fullscreen.txt +1 -1
- package/dist/admin/fullscreen/databases/__next._full.txt +1 -1
- package/dist/admin/fullscreen/databases/__next._head.txt +1 -1
- package/dist/admin/fullscreen/databases/__next._index.txt +1 -1
- package/dist/admin/fullscreen/databases/__next._tree.txt +1 -1
- package/dist/admin/fullscreen/databases/__next.fullscreen.databases.__PAGE__.txt +1 -1
- package/dist/admin/fullscreen/databases/__next.fullscreen.databases.txt +1 -1
- package/dist/admin/fullscreen/databases/__next.fullscreen.txt +1 -1
- package/dist/admin/fullscreen/databases/index.html +1 -1
- package/dist/admin/fullscreen/databases/index.txt +1 -1
- package/dist/admin/fullscreen/index.html +1 -1
- package/dist/admin/fullscreen/index.txt +1 -1
- package/dist/admin/index.html +1 -1
- package/dist/admin/index.txt +1 -1
- package/dist/admin/mail/__next.!KGRlZmF1bHQp.mail.__PAGE__.txt +1 -1
- package/dist/admin/mail/__next.!KGRlZmF1bHQp.mail.txt +1 -1
- package/dist/admin/mail/__next.!KGRlZmF1bHQp.txt +1 -1
- package/dist/admin/mail/__next._full.txt +1 -1
- package/dist/admin/mail/__next._head.txt +1 -1
- package/dist/admin/mail/__next._index.txt +1 -1
- package/dist/admin/mail/__next._tree.txt +1 -1
- package/dist/admin/mail/index.html +1 -1
- package/dist/admin/mail/index.txt +1 -1
- package/dist/admin/workflows/__next.!KGRlZmF1bHQp.txt +1 -1
- package/dist/admin/workflows/__next.!KGRlZmF1bHQp.workflows.__PAGE__.txt +1 -1
- package/dist/admin/workflows/__next.!KGRlZmF1bHQp.workflows.txt +1 -1
- package/dist/admin/workflows/__next._full.txt +1 -1
- package/dist/admin/workflows/__next._head.txt +1 -1
- package/dist/admin/workflows/__next._index.txt +1 -1
- package/dist/admin/workflows/__next._tree.txt +1 -1
- package/dist/admin/workflows/index.html +1 -1
- package/dist/admin/workflows/index.txt +1 -1
- package/dist/cli.js +156 -11
- package/dist/docs/index.md +1 -3
- package/dist/docs/integrations/temporal.md +1 -91
- package/dist/docs/temporal.md +0 -2
- package/package.json +1 -1
- /package/dist/admin/_next/static/{eboxHGqEe6h17B0pFV0xY → mW_zIz1G5hK3GXQcXI7Kb}/_buildManifest.js +0 -0
- /package/dist/admin/_next/static/{eboxHGqEe6h17B0pFV0xY → mW_zIz1G5hK3GXQcXI7Kb}/_clientMiddlewareManifest.json +0 -0
- /package/dist/admin/_next/static/{eboxHGqEe6h17B0pFV0xY → mW_zIz1G5hK3GXQcXI7Kb}/_ssgManifest.js +0 -0
package/dist/cli.js
CHANGED
|
@@ -367814,6 +367814,58 @@ var ApiClient = class {
|
|
|
367814
367814
|
}
|
|
367815
367815
|
return response.json();
|
|
367816
367816
|
}
|
|
367817
|
+
async createPreviewEnvironment(projectId) {
|
|
367818
|
+
const url = `${this.baseUrl}/environments/preview`;
|
|
367819
|
+
writeLog("api", `POST ${url}`);
|
|
367820
|
+
const response = await fetch(url, {
|
|
367821
|
+
method: "POST",
|
|
367822
|
+
headers: {
|
|
367823
|
+
"Content-Type": "application/json",
|
|
367824
|
+
...await this.authHeaders()
|
|
367825
|
+
},
|
|
367826
|
+
body: JSON.stringify({ projectId })
|
|
367827
|
+
});
|
|
367828
|
+
writeLog("api", `Response: ${response.status} ${response.statusText}`);
|
|
367829
|
+
if (!response.ok) {
|
|
367830
|
+
try {
|
|
367831
|
+
const error = await response.json();
|
|
367832
|
+
throw new Error(
|
|
367833
|
+
`Failed to create preview environment: ${error.error} (${error.code})`
|
|
367834
|
+
);
|
|
367835
|
+
} catch (e) {
|
|
367836
|
+
if (e instanceof Error && e.message.startsWith("Failed to create preview")) {
|
|
367837
|
+
throw e;
|
|
367838
|
+
}
|
|
367839
|
+
throw new Error(`Failed to create preview environment: ${response.statusText}`);
|
|
367840
|
+
}
|
|
367841
|
+
}
|
|
367842
|
+
return response.json();
|
|
367843
|
+
}
|
|
367844
|
+
async deletePreviewEnvironment(environmentId) {
|
|
367845
|
+
const url = `${this.baseUrl}/environments/${environmentId}`;
|
|
367846
|
+
writeLog("api", `DELETE ${url}`);
|
|
367847
|
+
const response = await fetch(url, {
|
|
367848
|
+
method: "DELETE",
|
|
367849
|
+
headers: await this.authHeaders()
|
|
367850
|
+
});
|
|
367851
|
+
writeLog("api", `Response: ${response.status} ${response.statusText}`);
|
|
367852
|
+
if (!response.ok) {
|
|
367853
|
+
if (response.status === 404) {
|
|
367854
|
+
return;
|
|
367855
|
+
}
|
|
367856
|
+
try {
|
|
367857
|
+
const error = await response.json();
|
|
367858
|
+
throw new Error(
|
|
367859
|
+
`Failed to delete preview environment: ${error.error} (${error.code})`
|
|
367860
|
+
);
|
|
367861
|
+
} catch (e) {
|
|
367862
|
+
if (e instanceof Error && e.message.startsWith("Failed to delete preview")) {
|
|
367863
|
+
throw e;
|
|
367864
|
+
}
|
|
367865
|
+
throw new Error(`Failed to delete preview environment: ${response.statusText}`);
|
|
367866
|
+
}
|
|
367867
|
+
}
|
|
367868
|
+
}
|
|
367817
367869
|
async getMe(signal) {
|
|
367818
367870
|
const url = `${this.baseUrl}/users/me`;
|
|
367819
367871
|
writeLog("api", `GET ${url}`);
|
|
@@ -367891,6 +367943,13 @@ var SpecificClient = class {
|
|
|
367891
367943
|
async submitConfigs(deploymentId, configs) {
|
|
367892
367944
|
await this.client.submitConfigs(deploymentId, configs);
|
|
367893
367945
|
}
|
|
367946
|
+
// --- Preview Environments ---
|
|
367947
|
+
async createPreviewEnvironment(projectId) {
|
|
367948
|
+
return this.client.createPreviewEnvironment(projectId);
|
|
367949
|
+
}
|
|
367950
|
+
async deletePreviewEnvironment(environmentId) {
|
|
367951
|
+
return this.client.deletePreviewEnvironment(environmentId);
|
|
367952
|
+
}
|
|
367894
367953
|
};
|
|
367895
367954
|
function toDeployment(response) {
|
|
367896
367955
|
return {
|
|
@@ -372770,6 +372829,7 @@ import * as fs3 from "fs";
|
|
|
372770
372829
|
import * as path3 from "path";
|
|
372771
372830
|
var PROJECT_ID_FILE = ".specific/project_id";
|
|
372772
372831
|
var ENVIRONMENT_ID_FILE = ".specific/environment_id";
|
|
372832
|
+
var PREVIEW_ENVIRONMENT_ID_FILE = ".specific/preview_environment_id";
|
|
372773
372833
|
var ProjectNotLinkedError = class extends Error {
|
|
372774
372834
|
constructor() {
|
|
372775
372835
|
super(
|
|
@@ -372826,6 +372886,21 @@ function writeEnvironmentId(environmentId, projectDir = process.cwd()) {
|
|
|
372826
372886
|
}
|
|
372827
372887
|
fs3.writeFileSync(path3.join(specificDir, "environment_id"), environmentId + "\n");
|
|
372828
372888
|
}
|
|
372889
|
+
function readPreviewEnvironmentId(projectDir = process.cwd()) {
|
|
372890
|
+
const filePath = path3.join(projectDir, PREVIEW_ENVIRONMENT_ID_FILE);
|
|
372891
|
+
if (!fs3.existsSync(filePath)) {
|
|
372892
|
+
return null;
|
|
372893
|
+
}
|
|
372894
|
+
const id = fs3.readFileSync(filePath, "utf-8").trim();
|
|
372895
|
+
return id || null;
|
|
372896
|
+
}
|
|
372897
|
+
function writePreviewEnvironmentId(environmentId, projectDir = process.cwd()) {
|
|
372898
|
+
const specificDir = path3.join(projectDir, ".specific");
|
|
372899
|
+
if (!fs3.existsSync(specificDir)) {
|
|
372900
|
+
fs3.mkdirSync(specificDir, { recursive: true });
|
|
372901
|
+
}
|
|
372902
|
+
fs3.writeFileSync(path3.join(specificDir, "preview_environment_id"), environmentId + "\n");
|
|
372903
|
+
}
|
|
372829
372904
|
|
|
372830
372905
|
// src/lib/auth/credentials.ts
|
|
372831
372906
|
import * as fs19 from "fs";
|
|
@@ -373266,7 +373341,7 @@ function trackEvent(event, properties) {
|
|
|
373266
373341
|
event,
|
|
373267
373342
|
properties: {
|
|
373268
373343
|
...properties,
|
|
373269
|
-
cli_version: "0.1.
|
|
373344
|
+
cli_version: "0.1.124",
|
|
373270
373345
|
platform: process.platform,
|
|
373271
373346
|
node_version: process.version,
|
|
373272
373347
|
project_id: getProjectId()
|
|
@@ -373591,10 +373666,6 @@ import { fileURLToPath as fileURLToPath3 } from "url";
|
|
|
373591
373666
|
|
|
373592
373667
|
// src/lib/beta/registry.ts
|
|
373593
373668
|
var BETA_REGISTRY = [
|
|
373594
|
-
{
|
|
373595
|
-
name: "temporal",
|
|
373596
|
-
description: "Managed Temporal workflow engine for durable workflows and background tasks"
|
|
373597
|
-
},
|
|
373598
373669
|
{
|
|
373599
373670
|
name: "mail",
|
|
373600
373671
|
description: "Managed email sending via SMTP for transactional emails"
|
|
@@ -375148,6 +375219,7 @@ function PhaseIndicator({
|
|
|
375148
375219
|
showSpinner = true
|
|
375149
375220
|
}) {
|
|
375150
375221
|
const phases = [
|
|
375222
|
+
"creating-preview",
|
|
375151
375223
|
"creating-tarball",
|
|
375152
375224
|
"creating-deployment",
|
|
375153
375225
|
"uploading",
|
|
@@ -375299,7 +375371,7 @@ function formatErrorCode(code) {
|
|
|
375299
375371
|
function StructuredError({ error }) {
|
|
375300
375372
|
return /* @__PURE__ */ React7.createElement(Box7, { flexDirection: "column" }, /* @__PURE__ */ React7.createElement(Text7, { color: "red", bold: true }, formatErrorCode(error.code), ": ", error.message), error.resource && /* @__PURE__ */ React7.createElement(Text7, { dimColor: true }, "Resource: ", error.resource), error.output && /* @__PURE__ */ React7.createElement(Box7, { flexDirection: "column", marginTop: 1 }, /* @__PURE__ */ React7.createElement(Text7, { bold: true }, "Output:"), /* @__PURE__ */ React7.createElement(Text7, null, error.output)));
|
|
375301
375373
|
}
|
|
375302
|
-
function DeployUI({ envFlag, config }) {
|
|
375374
|
+
function DeployUI({ envFlag, preview, config }) {
|
|
375303
375375
|
const { exit } = useApp3();
|
|
375304
375376
|
const [state, setState] = useState6({ phase: "checking-auth" });
|
|
375305
375377
|
const clientRef = React7.useRef(null);
|
|
@@ -375487,6 +375559,15 @@ function DeployUI({ envFlag, config }) {
|
|
|
375487
375559
|
}));
|
|
375488
375560
|
return;
|
|
375489
375561
|
}
|
|
375562
|
+
if (preview) {
|
|
375563
|
+
setState((s) => ({
|
|
375564
|
+
...s,
|
|
375565
|
+
phase: "creating-preview",
|
|
375566
|
+
environments,
|
|
375567
|
+
previewStatus: "Creating preview environment"
|
|
375568
|
+
}));
|
|
375569
|
+
return;
|
|
375570
|
+
}
|
|
375490
375571
|
if (envFlag) {
|
|
375491
375572
|
const match = environments.find((e) => e.name === envFlag);
|
|
375492
375573
|
if (!match) {
|
|
@@ -375549,6 +375630,45 @@ function DeployUI({ envFlag, config }) {
|
|
|
375549
375630
|
cancelled = true;
|
|
375550
375631
|
};
|
|
375551
375632
|
}, [state.phase, state.projectId]);
|
|
375633
|
+
useEffect6(() => {
|
|
375634
|
+
if (state.phase !== "creating-preview" || !state.projectId) return;
|
|
375635
|
+
let cancelled = false;
|
|
375636
|
+
async function createPreview() {
|
|
375637
|
+
try {
|
|
375638
|
+
const token = await getValidAccessToken();
|
|
375639
|
+
const client2 = new SpecificClient({ accessToken: token });
|
|
375640
|
+
const projectDir = process.cwd();
|
|
375641
|
+
const previousPreviewId = readPreviewEnvironmentId(projectDir);
|
|
375642
|
+
if (previousPreviewId) {
|
|
375643
|
+
setState((s) => ({ ...s, previewStatus: "Tearing down previous preview environment" }));
|
|
375644
|
+
try {
|
|
375645
|
+
await client2.deletePreviewEnvironment(previousPreviewId);
|
|
375646
|
+
} catch {
|
|
375647
|
+
}
|
|
375648
|
+
}
|
|
375649
|
+
if (cancelled) return;
|
|
375650
|
+
setState((s) => ({ ...s, previewStatus: "Creating preview environment" }));
|
|
375651
|
+
const previewEnv = await client2.createPreviewEnvironment(state.projectId);
|
|
375652
|
+
writePreviewEnvironmentId(previewEnv.id, projectDir);
|
|
375653
|
+
if (cancelled) return;
|
|
375654
|
+
setState((s) => ({
|
|
375655
|
+
...s,
|
|
375656
|
+
phase: "creating-tarball",
|
|
375657
|
+
environmentName: previewEnv.name
|
|
375658
|
+
}));
|
|
375659
|
+
} catch (err) {
|
|
375660
|
+
if (cancelled) return;
|
|
375661
|
+
setState({
|
|
375662
|
+
phase: "error",
|
|
375663
|
+
error: err instanceof Error ? err.message : String(err)
|
|
375664
|
+
});
|
|
375665
|
+
}
|
|
375666
|
+
}
|
|
375667
|
+
createPreview();
|
|
375668
|
+
return () => {
|
|
375669
|
+
cancelled = true;
|
|
375670
|
+
};
|
|
375671
|
+
}, [state.phase, state.projectId]);
|
|
375552
375672
|
const handleEnvironmentSelect = useCallback3(
|
|
375553
375673
|
(env2) => {
|
|
375554
375674
|
writeEnvironmentId(env2.id);
|
|
@@ -376100,7 +376220,14 @@ function DeployUI({ envFlag, config }) {
|
|
|
376100
376220
|
}
|
|
376101
376221
|
return "Preparing";
|
|
376102
376222
|
};
|
|
376103
|
-
return /* @__PURE__ */ React7.createElement(Box7, { flexDirection: "column", gap: 1 }, /* @__PURE__ */ React7.createElement(Text7, null, /* @__PURE__ */ React7.createElement(Text7, { bold: true, color: "cyan" },
|
|
376223
|
+
return /* @__PURE__ */ React7.createElement(Box7, { flexDirection: "column", gap: 1 }, /* @__PURE__ */ React7.createElement(Text7, null, /* @__PURE__ */ React7.createElement(Text7, { bold: true, color: "cyan" }, preview ? `Deploying preview${environment !== "prod" ? ` (${environment})` : ""}` : `Deploying to ${environment}`), deployment && /* @__PURE__ */ React7.createElement(Text7, { dimColor: true }, " (", deployment.id, ")")), /* @__PURE__ */ React7.createElement(Box7, { flexDirection: "column" }, preview && /* @__PURE__ */ React7.createElement(
|
|
376224
|
+
PhaseIndicator,
|
|
376225
|
+
{
|
|
376226
|
+
phase: "creating-preview",
|
|
376227
|
+
currentPhase: displayPhase,
|
|
376228
|
+
label: state.previewStatus || "Creating preview environment"
|
|
376229
|
+
}
|
|
376230
|
+
), /* @__PURE__ */ React7.createElement(
|
|
376104
376231
|
PhaseIndicator,
|
|
376105
376232
|
{
|
|
376106
376233
|
phase: "creating-tarball",
|
|
@@ -376181,6 +376308,21 @@ async function runDeployPipeline(options2) {
|
|
|
376181
376308
|
} else if (!hasProjectId(projectDir)) {
|
|
376182
376309
|
writeProjectId(projectId);
|
|
376183
376310
|
}
|
|
376311
|
+
if (options2.preview) {
|
|
376312
|
+
const previousPreviewId = readPreviewEnvironmentId(projectDir);
|
|
376313
|
+
if (previousPreviewId) {
|
|
376314
|
+
console.log("Cleaning up previous preview environment...");
|
|
376315
|
+
try {
|
|
376316
|
+
await client2.deletePreviewEnvironment(previousPreviewId);
|
|
376317
|
+
} catch {
|
|
376318
|
+
}
|
|
376319
|
+
}
|
|
376320
|
+
console.log("Creating preview environment...");
|
|
376321
|
+
const preview = await client2.createPreviewEnvironment(projectId);
|
|
376322
|
+
writePreviewEnvironmentId(preview.id, projectDir);
|
|
376323
|
+
console.log(`Preview environment "${preview.name}" created (expires: ${new Date(preview.expiresAt).toLocaleString()})`);
|
|
376324
|
+
options2.env = preview.name;
|
|
376325
|
+
}
|
|
376184
376326
|
let environmentName;
|
|
376185
376327
|
if (options2.env) {
|
|
376186
376328
|
environmentName = options2.env;
|
|
@@ -376409,7 +376551,8 @@ async function deployCommand(options2) {
|
|
|
376409
376551
|
projectId: options2.project,
|
|
376410
376552
|
env: options2.env,
|
|
376411
376553
|
secrets: options2.secret,
|
|
376412
|
-
configs: options2.config
|
|
376554
|
+
configs: options2.config,
|
|
376555
|
+
preview: options2.preview
|
|
376413
376556
|
});
|
|
376414
376557
|
return;
|
|
376415
376558
|
}
|
|
@@ -376418,6 +376561,7 @@ async function deployCommand(options2) {
|
|
|
376418
376561
|
DeployUI,
|
|
376419
376562
|
{
|
|
376420
376563
|
envFlag: options2.env,
|
|
376564
|
+
preview: options2.preview,
|
|
376421
376565
|
config
|
|
376422
376566
|
}
|
|
376423
376567
|
)
|
|
@@ -377278,7 +377422,7 @@ function compareVersions(a, b) {
|
|
|
377278
377422
|
return 0;
|
|
377279
377423
|
}
|
|
377280
377424
|
async function checkForUpdate() {
|
|
377281
|
-
const currentVersion = "0.1.
|
|
377425
|
+
const currentVersion = "0.1.124";
|
|
377282
377426
|
const response = await fetch(`${BINARIES_BASE_URL}/latest?t=${Date.now()}`);
|
|
377283
377427
|
if (!response.ok) {
|
|
377284
377428
|
throw new Error(`Failed to check for updates: HTTP ${response.status}`);
|
|
@@ -377548,7 +377692,7 @@ async function projectListCommand() {
|
|
|
377548
377692
|
var program = new Command();
|
|
377549
377693
|
var env = "production";
|
|
377550
377694
|
var envLabel = env !== "production" ? `[${env.toUpperCase()}] ` : "";
|
|
377551
|
-
program.name("specific").description(`${envLabel}Infrastructure-as-code for coding agents`).version("0.1.
|
|
377695
|
+
program.name("specific").description(`${envLabel}Infrastructure-as-code for coding agents`).version("0.1.124").enablePositionalOptions();
|
|
377552
377696
|
program.command("init").description("Initialize project for use with a coding agent").option("--agent <name...>", "Agents to configure (cursor, claude, codex, other)").addHelpText("after", `
|
|
377553
377697
|
Examples:
|
|
377554
377698
|
$ specific init
|
|
@@ -377569,11 +377713,12 @@ Examples:
|
|
|
377569
377713
|
const key = options2.key ?? getDefaultKey();
|
|
377570
377714
|
devCommand(key, options2.tunnel ?? false);
|
|
377571
377715
|
});
|
|
377572
|
-
program.command("deploy").description("Deploy to Specific infrastructure").option("--project <id>", "Project ID to deploy to (overrides .projectid file)").option("--env <name>", "Target environment (auto-selected if only one exists)").option("--secret <key=value...>", "Secret values (repeatable)").option("--config <key=value...>", "Config values (repeatable)").addHelpText("after", `
|
|
377716
|
+
program.command("deploy").description("Deploy to Specific infrastructure").option("--project <id>", "Project ID to deploy to (overrides .projectid file)").option("--env <name>", "Target environment (auto-selected if only one exists)").option("--secret <key=value...>", "Secret values (repeatable)").option("--config <key=value...>", "Config values (repeatable)").option("--preview", "Deploy to an ephemeral preview environment").addHelpText("after", `
|
|
377573
377717
|
Examples:
|
|
377574
377718
|
$ specific deploy
|
|
377575
377719
|
$ specific deploy --env staging
|
|
377576
377720
|
$ specific deploy --project proj_123
|
|
377721
|
+
$ specific deploy --preview
|
|
377577
377722
|
$ specific deploy --secret db_url=postgres://... --config domain=app.com`).action((options2) => {
|
|
377578
377723
|
deployCommand(options2);
|
|
377579
377724
|
});
|
package/dist/docs/index.md
CHANGED
|
@@ -22,9 +22,7 @@ A full development environment can be started with `specific dev`. To deploy any
|
|
|
22
22
|
- [Storage](/storage): define S3-compatible object/blob storage for services to store files in.
|
|
23
23
|
- [Redis](/redis): define non-durable Redis-compatible databases for caching and more.
|
|
24
24
|
- [Volumes](/volumes): define persistent storage volumes for services to store files in.
|
|
25
|
-
<!-- beta:temporal -->
|
|
26
25
|
- [Temporal](/temporal): managed durable workflow engine for background tasks, AI agents, cron jobs and more.
|
|
27
|
-
<!-- /beta:temporal -->
|
|
28
26
|
<!-- beta:mail -->
|
|
29
27
|
- [Mail](/mail): managed email sending via SMTP for transactional emails.
|
|
30
28
|
<!-- /beta:mail -->
|
|
@@ -37,7 +35,7 @@ The following is a list of common frameworks and libraries with guidance on how
|
|
|
37
35
|
- [Next.js](/integrations/nextjs): full-stack React framework
|
|
38
36
|
- [Drizzle ORM](/integrations/drizzle): TypeScript ORM with type safety
|
|
39
37
|
- [Prisma](/integrations/prisma): TypeScript ORM with auto-generated client
|
|
40
|
-
- [Temporal](/integrations/temporal):
|
|
38
|
+
- [Temporal](/integrations/temporal): using Temporal with Specific for durable workflows, background tasks, AI agents, cron jobs and more
|
|
41
39
|
|
|
42
40
|
## Adding Specific to existing projects / migrating to Specific
|
|
43
41
|
|
|
@@ -1,93 +1,3 @@
|
|
|
1
1
|
# Temporal
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
> **Beta**: Specific now has built-in Temporal support. See [Temporal](/temporal) for the recommended approach using the `temporal` block. The manual setup below still works but is no longer needed.
|
|
5
|
-
<!-- /beta:temporal -->
|
|
6
|
-
|
|
7
|
-
Temporal is a durable workflow engine. This guide covers running Temporal locally during development and connecting to Temporal Cloud in production.
|
|
8
|
-
|
|
9
|
-
Temporal has extensive SDK support across many languages including TypeScript, Python, Go, Java, .NET, and PHP. See the [Temporal documentation](https://docs.temporal.io/) for language-specific guides on implementing workers, workflows, and activities. This document only covers integration with Specific.
|
|
10
|
-
|
|
11
|
-
## Configuration
|
|
12
|
-
|
|
13
|
-
Define a dev-only Temporal service that runs locally, along with configs and secrets for production Temporal Cloud.
|
|
14
|
-
|
|
15
|
-
```hcl
|
|
16
|
-
build "app" {
|
|
17
|
-
base = "node"
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
# Dev-only service: runs local Temporal server during development
|
|
21
|
-
# No top-level command means it's excluded from production deployment
|
|
22
|
-
#
|
|
23
|
-
service "temporal" {
|
|
24
|
-
endpoint "grpc" {}
|
|
25
|
-
endpoint "ui" { public = true }
|
|
26
|
-
|
|
27
|
-
dev {
|
|
28
|
-
command = "temporal server start-dev --port $GRPC_PORT --ui-port $UI_PORT --db-filename /tmp/temporal.db"
|
|
29
|
-
env = {
|
|
30
|
-
GRPC_PORT = endpoint.grpc.port
|
|
31
|
-
UI_PORT = endpoint.ui.port
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
# Temporal Cloud address (e.g., your-namespace.tmprl.cloud:7233)
|
|
37
|
-
config "temporal_address" {}
|
|
38
|
-
|
|
39
|
-
# Namespace - uses "default" in dev, must be set for production
|
|
40
|
-
config "temporal_namespace" {
|
|
41
|
-
dev {
|
|
42
|
-
default = "default"
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
# API key for Temporal Cloud - not required in dev
|
|
47
|
-
secret "temporal_api_key" {
|
|
48
|
-
dev {
|
|
49
|
-
required = false
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
service "app" {
|
|
54
|
-
build = build.app
|
|
55
|
-
command = "node index.js"
|
|
56
|
-
expose {}
|
|
57
|
-
|
|
58
|
-
env = {
|
|
59
|
-
PORT = port
|
|
60
|
-
TEMPORAL_ADDRESS = config.temporal_address
|
|
61
|
-
TEMPORAL_NAMESPACE = config.temporal_namespace
|
|
62
|
-
TEMPORAL_API_KEY = secret.temporal_api_key
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
dev {
|
|
66
|
-
command = "node --watch index.js"
|
|
67
|
-
env = {
|
|
68
|
-
# Override to use local Temporal service instead
|
|
69
|
-
TEMPORAL_ADDRESS = ""
|
|
70
|
-
TEMPORAL_HOST = service.temporal.endpoint.grpc.host
|
|
71
|
-
TEMPORAL_PORT = service.temporal.endpoint.grpc.port
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
## Development
|
|
78
|
-
|
|
79
|
-
Running `specific dev` automatically starts a local Temporal server. The user must install the Temporal CLI for this: https://temporal.io/setup/install-temporal-cli. The Temporal Web UI is available at the public endpoint URL shown in the output.
|
|
80
|
-
|
|
81
|
-
Your app connects to the local Temporal server using the service endpoint environment variables (`TEMPORAL_HOST` and `TEMPORAL_PORT`).
|
|
82
|
-
|
|
83
|
-
## Production
|
|
84
|
-
|
|
85
|
-
In production, the dev-only `temporal` service is excluded from deployment. Your app connects to Temporal Cloud using:
|
|
86
|
-
|
|
87
|
-
1. `temporal_address` - Your Temporal Cloud address (e.g., `your-namespace.tmprl.cloud:7233`)
|
|
88
|
-
2. `temporal_namespace` - Your Temporal Cloud namespace
|
|
89
|
-
3. `temporal_api_key` - API key for authentication
|
|
90
|
-
|
|
91
|
-
Set these with `specific secrets set` before deploying.
|
|
92
|
-
|
|
93
|
-
## SDK support
|
|
3
|
+
Specific has built-in Temporal support via the `temporal` block. See [Temporal](/temporal) for the full guide on configuration, development, and production deployment.
|
package/dist/docs/temporal.md
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
<!-- beta:temporal -->
|
|
2
1
|
# Temporal
|
|
3
2
|
|
|
4
3
|
Define managed Temporal workflow engines for durable workflows, background tasks, AI agents, cron jobs, batch jobs and more.
|
|
@@ -66,4 +65,3 @@ Running `specific deploy` automatically provisions a managed [Temporal Cloud](ht
|
|
|
66
65
|
4. Credentials are securely injected into your services
|
|
67
66
|
|
|
68
67
|
The reference attributes (`temporal.<name>.url`, `.namespace`, `.api_key`) resolve to the Temporal Cloud values in production, so no environment-specific overrides are needed.
|
|
69
|
-
<!-- /beta:temporal -->
|
package/package.json
CHANGED
/package/dist/admin/_next/static/{eboxHGqEe6h17B0pFV0xY → mW_zIz1G5hK3GXQcXI7Kb}/_buildManifest.js
RENAMED
|
File without changes
|
|
File without changes
|
/package/dist/admin/_next/static/{eboxHGqEe6h17B0pFV0xY → mW_zIz1G5hK3GXQcXI7Kb}/_ssgManifest.js
RENAMED
|
File without changes
|