@specific.dev/cli 0.1.181 → 0.1.183
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/crons/__next.!KGRlZmF1bHQp.crons.__PAGE__.txt +1 -1
- package/dist/admin/crons/__next.!KGRlZmF1bHQp.crons.txt +1 -1
- package/dist/admin/crons/__next.!KGRlZmF1bHQp.txt +1 -1
- package/dist/admin/crons/__next._full.txt +1 -1
- package/dist/admin/crons/__next._head.txt +1 -1
- package/dist/admin/crons/__next._index.txt +1 -1
- package/dist/admin/crons/__next._tree.txt +1 -1
- package/dist/admin/crons/index.html +1 -1
- package/dist/admin/crons/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/services/__next.!KGRlZmF1bHQp.services.__PAGE__.txt +1 -1
- package/dist/admin/services/__next.!KGRlZmF1bHQp.services.txt +1 -1
- package/dist/admin/services/__next.!KGRlZmF1bHQp.txt +1 -1
- package/dist/admin/services/__next._full.txt +1 -1
- package/dist/admin/services/__next._head.txt +1 -1
- package/dist/admin/services/__next._index.txt +1 -1
- package/dist/admin/services/__next._tree.txt +1 -1
- package/dist/admin/services/index.html +1 -1
- package/dist/admin/services/index.txt +1 -1
- package/dist/admin/storage/__next.!KGRlZmF1bHQp.storage.__PAGE__.txt +1 -1
- package/dist/admin/storage/__next.!KGRlZmF1bHQp.storage.txt +1 -1
- package/dist/admin/storage/__next.!KGRlZmF1bHQp.txt +1 -1
- package/dist/admin/storage/__next._full.txt +1 -1
- package/dist/admin/storage/__next._head.txt +1 -1
- package/dist/admin/storage/__next._index.txt +1 -1
- package/dist/admin/storage/__next._tree.txt +1 -1
- package/dist/admin/storage/index.html +1 -1
- package/dist/admin/storage/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 +289 -26
- package/dist/docs/deployments.md +79 -0
- package/dist/docs/index.md +3 -0
- package/dist/docs/observability.md +2 -1
- package/dist/docs/previews.md +5 -0
- package/package.json +1 -1
- /package/dist/admin/_next/static/{7iJYom_N04p3jvXLOBn8G → NNVAA5krd_olwtUDgk5Bo}/_buildManifest.js +0 -0
- /package/dist/admin/_next/static/{7iJYom_N04p3jvXLOBn8G → NNVAA5krd_olwtUDgk5Bo}/_clientMiddlewareManifest.json +0 -0
- /package/dist/admin/_next/static/{7iJYom_N04p3jvXLOBn8G → NNVAA5krd_olwtUDgk5Bo}/_ssgManifest.js +0 -0
package/dist/cli.js
CHANGED
|
@@ -368079,8 +368079,13 @@ function toDeployment(response) {
|
|
|
368079
368079
|
error: response.error,
|
|
368080
368080
|
changes: response.changes,
|
|
368081
368081
|
pendingActions: response.pendingActions,
|
|
368082
|
+
builds: response.builds,
|
|
368082
368083
|
publicUrls: response.publicUrls,
|
|
368084
|
+
triggeredBy: response.triggeredBy,
|
|
368085
|
+
gitCommitSha: response.gitCommitSha,
|
|
368083
368086
|
createdAt: response.createdAt,
|
|
368087
|
+
startedAt: response.startedAt,
|
|
368088
|
+
completedAt: response.completedAt,
|
|
368084
368089
|
uploadUrl: response.uploadUrl,
|
|
368085
368090
|
maxTarballBytes: response.maxTarballBytes
|
|
368086
368091
|
};
|
|
@@ -374765,7 +374770,7 @@ function trackEvent(event, properties) {
|
|
|
374765
374770
|
event,
|
|
374766
374771
|
properties: {
|
|
374767
374772
|
...properties,
|
|
374768
|
-
cli_version: "0.1.
|
|
374773
|
+
cli_version: "0.1.183",
|
|
374769
374774
|
platform: process.platform,
|
|
374770
374775
|
node_version: process.version,
|
|
374771
374776
|
project_id: getProjectId()
|
|
@@ -376743,6 +376748,22 @@ import { render as render5, Text as Text7, Box as Box7, useApp as useApp3, useIn
|
|
|
376743
376748
|
import Spinner4 from "ink-spinner";
|
|
376744
376749
|
import * as fs23 from "fs";
|
|
376745
376750
|
import * as path21 from "path";
|
|
376751
|
+
|
|
376752
|
+
// src/lib/deployments/format.ts
|
|
376753
|
+
function formatErrorCode(code) {
|
|
376754
|
+
const labels = {
|
|
376755
|
+
build_failed: "Build failed",
|
|
376756
|
+
pre_deploy_failed: "Pre-deploy hook failed",
|
|
376757
|
+
deploy_failed: "Service failed to deploy",
|
|
376758
|
+
health_check_failed: "Health check failed",
|
|
376759
|
+
post_deploy_failed: "Post-deploy hook failed",
|
|
376760
|
+
volume_reset_failed: "Volume reset failed",
|
|
376761
|
+
internal_error: "Internal error"
|
|
376762
|
+
};
|
|
376763
|
+
return labels[code] || code;
|
|
376764
|
+
}
|
|
376765
|
+
|
|
376766
|
+
// src/commands/deploy.tsx
|
|
376746
376767
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
376747
376768
|
function getGitInfo(cwd) {
|
|
376748
376769
|
try {
|
|
@@ -376944,17 +376965,6 @@ function getFailedBuild(pendingActions) {
|
|
|
376944
376965
|
if (!pendingActions) return void 0;
|
|
376945
376966
|
return pendingActions.find((a) => a.type === "build_failed");
|
|
376946
376967
|
}
|
|
376947
|
-
function formatErrorCode(code) {
|
|
376948
|
-
const labels = {
|
|
376949
|
-
pre_deploy_failed: "Pre-deploy hook failed",
|
|
376950
|
-
deploy_failed: "Service failed to deploy",
|
|
376951
|
-
health_check_failed: "Health check failed",
|
|
376952
|
-
post_deploy_failed: "Post-deploy hook failed",
|
|
376953
|
-
volume_reset_failed: "Volume reset failed",
|
|
376954
|
-
internal_error: "Internal error"
|
|
376955
|
-
};
|
|
376956
|
-
return labels[code] || code;
|
|
376957
|
-
}
|
|
376958
376968
|
function StructuredError({ error }) {
|
|
376959
376969
|
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)));
|
|
376960
376970
|
}
|
|
@@ -379147,7 +379157,7 @@ function compareVersions(a, b) {
|
|
|
379147
379157
|
return 0;
|
|
379148
379158
|
}
|
|
379149
379159
|
async function checkForUpdate() {
|
|
379150
|
-
const currentVersion = "0.1.
|
|
379160
|
+
const currentVersion = "0.1.183";
|
|
379151
379161
|
const response = await fetch(`${BINARIES_BASE_URL}/latest?t=${Date.now()}`);
|
|
379152
379162
|
if (!response.ok) {
|
|
379153
379163
|
throw new Error(`Failed to check for updates: HTTP ${response.status}`);
|
|
@@ -379434,13 +379444,35 @@ function PreviewBlock({ env: env2 }) {
|
|
|
379434
379444
|
const urls = managedUrls(env2.domains);
|
|
379435
379445
|
return /* @__PURE__ */ React12.createElement(Box11, { flexDirection: "column" }, /* @__PURE__ */ React12.createElement(Text12, null, /* @__PURE__ */ React12.createElement(Text12, { bold: true }, env2.name), /* @__PURE__ */ React12.createElement(Text12, { dimColor: true }, " (", env2.id, ")")), env2.prNumber !== null && /* @__PURE__ */ React12.createElement(Text12, null, " ", "PR #", env2.prNumber, env2.prTitle ? /* @__PURE__ */ React12.createElement(Text12, { dimColor: true }, " \u2014 ", env2.prTitle) : null), urls.length > 0 && /* @__PURE__ */ React12.createElement(Text12, null, " ", "URL: ", urls.join(", ")), env2.expiresAt && /* @__PURE__ */ React12.createElement(Text12, null, " ", "Expires: ", /* @__PURE__ */ React12.createElement(Text12, { dimColor: true }, new Date(env2.expiresAt).toLocaleString())));
|
|
379436
379446
|
}
|
|
379447
|
+
function PreviewConfigurationBlock({
|
|
379448
|
+
config,
|
|
379449
|
+
activePreviewCount,
|
|
379450
|
+
showDetails
|
|
379451
|
+
}) {
|
|
379452
|
+
const configured = config.github;
|
|
379453
|
+
const enabledConfigs = configured.filter((c) => c.prPreviewsEnabled);
|
|
379454
|
+
const labelGated = enabledConfigs.filter((c) => c.previewsRequireLabel);
|
|
379455
|
+
const githubSummary = enabledConfigs.length > 0 ? `enabled for ${enabledConfigs.length} environment${enabledConfigs.length === 1 ? "" : "s"}` : configured.length > 0 ? "configured but disabled" : "not configured";
|
|
379456
|
+
return /* @__PURE__ */ React12.createElement(Box11, { flexDirection: "column" }, /* @__PURE__ */ React12.createElement(Text12, { bold: true }, "Preview configuration"), /* @__PURE__ */ React12.createElement(Box11, { flexDirection: "column", marginLeft: 2 }, /* @__PURE__ */ React12.createElement(Text12, null, "PR previews: ", config.enabledForOrganization ? "available" : "unavailable", "; GitHub ", githubSummary, ";", " ", labelGated.length > 0 ? `requires ${config.requiredLabel}` : "no label required", "; active ", activePreviewCount), !showDetails && configured.length > 0 && /* @__PURE__ */ React12.createElement(Text12, { dimColor: true }, " ", "Run `specific status --previews` for GitHub preview details."), showDetails && /* @__PURE__ */ React12.createElement(Box11, { flexDirection: "column", marginTop: 1 }, /* @__PURE__ */ React12.createElement(Text12, null, "Required label: ", config.requiredLabel), configured.length === 0 ? /* @__PURE__ */ React12.createElement(Text12, null, "GitHub PR automation: not configured") : configured.map((githubConfig) => /* @__PURE__ */ React12.createElement(Box11, { key: githubConfig.environmentId, flexDirection: "column" }, /* @__PURE__ */ React12.createElement(Text12, null, /* @__PURE__ */ React12.createElement(Text12, { bold: true }, githubConfig.environmentName), /* @__PURE__ */ React12.createElement(Text12, { dimColor: true }, " (", githubConfig.environmentId, ")")), /* @__PURE__ */ React12.createElement(Text12, null, " ", "PR previews: ", githubConfig.prPreviewsEnabled ? "enabled" : "disabled", "; ", "requires label: ", githubConfig.previewsRequireLabel ? "yes" : "no"), /* @__PURE__ */ React12.createElement(Text12, null, " ", "Repository: ", githubConfig.repositoryFullName, /* @__PURE__ */ React12.createElement(Text12, { dimColor: true }, " (", githubConfig.branch, ", ", githubConfig.hclPath, ")")))))));
|
|
379457
|
+
}
|
|
379437
379458
|
function EnvironmentBlock({ env: env2 }) {
|
|
379438
|
-
|
|
379459
|
+
const latest = env2.latestDeployment;
|
|
379460
|
+
const latestIsShownElsewhere = !latest || latest.id === env2.activeDeployment?.id || latest.id === env2.inProgressDeployment?.id;
|
|
379461
|
+
return /* @__PURE__ */ React12.createElement(Box11, { flexDirection: "column" }, /* @__PURE__ */ React12.createElement(Text12, null, /* @__PURE__ */ React12.createElement(Text12, { bold: true }, env2.name), /* @__PURE__ */ React12.createElement(Text12, { dimColor: true }, " (", env2.id, ")")), /* @__PURE__ */ React12.createElement(Text12, null, " ", "Status: ", /* @__PURE__ */ React12.createElement(Text12, { color: statusColor(env2.status), bold: true }, env2.status)), /* @__PURE__ */ React12.createElement(Text12, null, " ", "Active deployment:", " ", env2.activeDeployment ? /* @__PURE__ */ React12.createElement(Text12, { dimColor: true }, env2.activeDeployment.id) : /* @__PURE__ */ React12.createElement(Text12, { dimColor: true }, "none")), env2.inProgressDeployment && /* @__PURE__ */ React12.createElement(Text12, null, " ", "In progress:", " ", /* @__PURE__ */ React12.createElement(Text12, { dimColor: true }, env2.inProgressDeployment.id), /* @__PURE__ */ React12.createElement(Text12, { dimColor: true }, " (", env2.inProgressDeployment.state, ")")), !latestIsShownElsewhere && /* @__PURE__ */ React12.createElement(Text12, null, " ", "Last deployment:", " ", /* @__PURE__ */ React12.createElement(Text12, { dimColor: true }, latest.id), " ", /* @__PURE__ */ React12.createElement(
|
|
379462
|
+
Text12,
|
|
379463
|
+
{
|
|
379464
|
+
...latest.state === "failed" ? { color: "red", bold: true } : {}
|
|
379465
|
+
},
|
|
379466
|
+
"(",
|
|
379467
|
+
latest.state,
|
|
379468
|
+
latest.triggeredBy ? ` via ${latest.triggeredBy}` : "",
|
|
379469
|
+
")"
|
|
379470
|
+
)), !latestIsShownElsewhere && latest.state === "failed" && /* @__PURE__ */ React12.createElement(Text12, { dimColor: true }, " ", "Run `specific deployment show ", latest.id, "` for details."), env2.domains && Object.keys(env2.domains).length > 0 && /* @__PURE__ */ React12.createElement(Box11, { flexDirection: "column" }, /* @__PURE__ */ React12.createElement(Text12, null, " ", "Domains:"), Object.entries(env2.domains).map(([serviceName, urls]) => {
|
|
379439
379471
|
const displayUrls = urls.custom.length > 0 ? urls.custom : urls.managed ? [urls.managed] : [];
|
|
379440
379472
|
return /* @__PURE__ */ React12.createElement(Text12, { key: serviceName }, " - ", serviceName, displayUrls.length > 0 ? `: ${displayUrls.join(", ")}` : null);
|
|
379441
379473
|
})));
|
|
379442
379474
|
}
|
|
379443
|
-
function StatusUI() {
|
|
379475
|
+
function StatusUI({ previews = false }) {
|
|
379444
379476
|
const [state, setState] = useState11({ phase: "loading" });
|
|
379445
379477
|
useEffect11(() => {
|
|
379446
379478
|
(async () => {
|
|
@@ -379468,14 +379500,21 @@ function StatusUI() {
|
|
|
379468
379500
|
return /* @__PURE__ */ React12.createElement(Text12, { color: "red" }, "Failed to get status: ", state.error);
|
|
379469
379501
|
}
|
|
379470
379502
|
const status = state.status;
|
|
379471
|
-
return /* @__PURE__ */ React12.createElement(Box11, { flexDirection: "column", gap: 1 }, /* @__PURE__ */ React12.createElement(Text12, null, /* @__PURE__ */ React12.createElement(Text12, { bold: true }, status.name), /* @__PURE__ */ React12.createElement(Text12, { dimColor: true }, " (", status.id, ")")), /* @__PURE__ */ React12.createElement(Box11, { flexDirection: "column" }, /* @__PURE__ */ React12.createElement(Text12, { bold: true }, "Environments"), status.environments.length === 0 ? /* @__PURE__ */ React12.createElement(Box11, { marginLeft: 2 }, /* @__PURE__ */ React12.createElement(Text12, { dimColor: true }, "No environments found.")) : /* @__PURE__ */ React12.createElement(Box11, { flexDirection: "column", gap: 1, marginLeft: 2 }, status.environments.map((env2) => /* @__PURE__ */ React12.createElement(EnvironmentBlock, { key: env2.id, env: env2 })))),
|
|
379503
|
+
return /* @__PURE__ */ React12.createElement(Box11, { flexDirection: "column", gap: 1 }, /* @__PURE__ */ React12.createElement(Text12, null, /* @__PURE__ */ React12.createElement(Text12, { bold: true }, status.name), /* @__PURE__ */ React12.createElement(Text12, { dimColor: true }, " (", status.id, ")")), /* @__PURE__ */ React12.createElement(Box11, { flexDirection: "column" }, /* @__PURE__ */ React12.createElement(Text12, { bold: true }, "Environments"), status.environments.length === 0 ? /* @__PURE__ */ React12.createElement(Box11, { marginLeft: 2 }, /* @__PURE__ */ React12.createElement(Text12, { dimColor: true }, "No environments found.")) : /* @__PURE__ */ React12.createElement(Box11, { flexDirection: "column", gap: 1, marginLeft: 2 }, status.environments.map((env2) => /* @__PURE__ */ React12.createElement(EnvironmentBlock, { key: env2.id, env: env2 })))), /* @__PURE__ */ React12.createElement(
|
|
379504
|
+
PreviewConfigurationBlock,
|
|
379505
|
+
{
|
|
379506
|
+
config: status.previewEnvironmentConfig,
|
|
379507
|
+
activePreviewCount: (status.previewEnvironments ?? []).length,
|
|
379508
|
+
showDetails: previews
|
|
379509
|
+
}
|
|
379510
|
+
), (status.previewEnvironments ?? []).length > 0 && /* @__PURE__ */ React12.createElement(Box11, { flexDirection: "column" }, /* @__PURE__ */ React12.createElement(Text12, { bold: true }, "Preview environments"), /* @__PURE__ */ React12.createElement(Box11, { flexDirection: "column", gap: 1, marginLeft: 2 }, status.previewEnvironments.map((env2) => /* @__PURE__ */ React12.createElement(PreviewBlock, { key: env2.id, env: env2 })))));
|
|
379472
379511
|
}
|
|
379473
|
-
function statusCommand() {
|
|
379474
|
-
render10(/* @__PURE__ */ React12.createElement(StatusUI,
|
|
379512
|
+
function statusCommand(options2 = {}) {
|
|
379513
|
+
render10(/* @__PURE__ */ React12.createElement(StatusUI, { previews: options2.previews ?? false }));
|
|
379475
379514
|
}
|
|
379476
379515
|
|
|
379477
379516
|
// src/lib/project/resolve-env.ts
|
|
379478
|
-
async function
|
|
379517
|
+
async function resolveEnvironmentStatus(client2, projectId, envFlag) {
|
|
379479
379518
|
const status = await client2.getProjectStatus(projectId);
|
|
379480
379519
|
const environments = status.environments;
|
|
379481
379520
|
const previewEnvironments = status.previewEnvironments ?? [];
|
|
@@ -379490,16 +379529,19 @@ async function resolveEnvironmentId(client2, projectId, envFlag) {
|
|
|
379490
379529
|
`Environment "${envFlag}" not found (matched against name and ID). Available: ${available}`
|
|
379491
379530
|
);
|
|
379492
379531
|
}
|
|
379493
|
-
return match
|
|
379532
|
+
return match;
|
|
379494
379533
|
}
|
|
379495
379534
|
if (hasEnvironmentId()) {
|
|
379496
379535
|
const savedId = readEnvironmentId();
|
|
379497
|
-
|
|
379498
|
-
|
|
379536
|
+
const saved = environments.find(
|
|
379537
|
+
(e) => e.id === savedId
|
|
379538
|
+
);
|
|
379539
|
+
if (saved) {
|
|
379540
|
+
return saved;
|
|
379499
379541
|
}
|
|
379500
379542
|
}
|
|
379501
379543
|
if (environments.length === 1) {
|
|
379502
|
-
return environments[0]
|
|
379544
|
+
return environments[0];
|
|
379503
379545
|
}
|
|
379504
379546
|
if (environments.length === 0) {
|
|
379505
379547
|
throw new Error("This project has no environments yet.");
|
|
@@ -379509,6 +379551,10 @@ async function resolveEnvironmentId(client2, projectId, envFlag) {
|
|
|
379509
379551
|
`Project has multiple environments (${names}). Pass --environment <name|id>.`
|
|
379510
379552
|
);
|
|
379511
379553
|
}
|
|
379554
|
+
async function resolveEnvironmentId(client2, projectId, envFlag) {
|
|
379555
|
+
const match = await resolveEnvironmentStatus(client2, projectId, envFlag);
|
|
379556
|
+
return match.id;
|
|
379557
|
+
}
|
|
379512
379558
|
|
|
379513
379559
|
// src/lib/query/render-table.ts
|
|
379514
379560
|
var MAX_COL_WIDTH = 60;
|
|
@@ -379750,6 +379796,204 @@ function friendlyErrorMessage(raw) {
|
|
|
379750
379796
|
return raw;
|
|
379751
379797
|
}
|
|
379752
379798
|
|
|
379799
|
+
// src/commands/deployment.tsx
|
|
379800
|
+
var ANSI2 = {
|
|
379801
|
+
dim: "\x1B[2m",
|
|
379802
|
+
red: "\x1B[31m",
|
|
379803
|
+
green: "\x1B[32m",
|
|
379804
|
+
yellow: "\x1B[33m",
|
|
379805
|
+
bold: "\x1B[1m",
|
|
379806
|
+
reset: "\x1B[0m"
|
|
379807
|
+
};
|
|
379808
|
+
async function deploymentShowCommand(idArg, options2) {
|
|
379809
|
+
try {
|
|
379810
|
+
const format = resolveOutputFormat2(options2.format);
|
|
379811
|
+
const includeOutput = options2.output ?? false;
|
|
379812
|
+
const client2 = new SpecificClient({ tokenProvider: getValidAccessToken });
|
|
379813
|
+
let deploymentId = idArg;
|
|
379814
|
+
if (!deploymentId) {
|
|
379815
|
+
const projectId = readProjectId();
|
|
379816
|
+
const env2 = await resolveEnvironmentStatus(
|
|
379817
|
+
client2,
|
|
379818
|
+
projectId,
|
|
379819
|
+
options2.environment
|
|
379820
|
+
);
|
|
379821
|
+
const latest = env2.latestDeployment ?? env2.inProgressDeployment ?? env2.activeDeployment;
|
|
379822
|
+
if (!latest) {
|
|
379823
|
+
throw new Error(
|
|
379824
|
+
`No deployments found for environment "${env2.name}".`
|
|
379825
|
+
);
|
|
379826
|
+
}
|
|
379827
|
+
deploymentId = latest.id;
|
|
379828
|
+
}
|
|
379829
|
+
const deployment = await client2.getDeployment(deploymentId);
|
|
379830
|
+
if (format === "json") {
|
|
379831
|
+
const payload = includeOutput ? deployment : stripOutput(deployment);
|
|
379832
|
+
process.stdout.write(JSON.stringify(payload, null, 2) + "\n");
|
|
379833
|
+
return;
|
|
379834
|
+
}
|
|
379835
|
+
process.stdout.write(renderDeployment(deployment, includeOutput));
|
|
379836
|
+
} catch (err) {
|
|
379837
|
+
const message = err instanceof Error ? err.message : "Unknown error occurred";
|
|
379838
|
+
console.error(`Error: ${message}`);
|
|
379839
|
+
process.exit(1);
|
|
379840
|
+
}
|
|
379841
|
+
}
|
|
379842
|
+
function resolveOutputFormat2(format) {
|
|
379843
|
+
if (!format) {
|
|
379844
|
+
return isInteractive() ? "text" : "json";
|
|
379845
|
+
}
|
|
379846
|
+
if (format === "text" || format === "json") {
|
|
379847
|
+
return format;
|
|
379848
|
+
}
|
|
379849
|
+
throw new Error(`Invalid format "${format}". Expected one of: text, json.`);
|
|
379850
|
+
}
|
|
379851
|
+
function omitOutput(value) {
|
|
379852
|
+
const copy = { ...value };
|
|
379853
|
+
delete copy.output;
|
|
379854
|
+
return copy;
|
|
379855
|
+
}
|
|
379856
|
+
function stripOutput(deployment) {
|
|
379857
|
+
const stripped = { ...deployment };
|
|
379858
|
+
if (stripped.error) {
|
|
379859
|
+
stripped.error = omitOutput(stripped.error);
|
|
379860
|
+
}
|
|
379861
|
+
if (stripped.builds) {
|
|
379862
|
+
stripped.builds = stripped.builds.map(omitOutput);
|
|
379863
|
+
}
|
|
379864
|
+
if (stripped.pendingActions) {
|
|
379865
|
+
stripped.pendingActions = stripped.pendingActions.map(
|
|
379866
|
+
(a) => a.type === "build_failed" ? omitOutput(a) : a
|
|
379867
|
+
);
|
|
379868
|
+
}
|
|
379869
|
+
return stripped;
|
|
379870
|
+
}
|
|
379871
|
+
function stateColor(state) {
|
|
379872
|
+
switch (state) {
|
|
379873
|
+
case "active":
|
|
379874
|
+
case "completed":
|
|
379875
|
+
return ANSI2.green;
|
|
379876
|
+
case "failed":
|
|
379877
|
+
case "cancelled":
|
|
379878
|
+
return ANSI2.red;
|
|
379879
|
+
case "superseded":
|
|
379880
|
+
return ANSI2.dim;
|
|
379881
|
+
default:
|
|
379882
|
+
return ANSI2.yellow;
|
|
379883
|
+
}
|
|
379884
|
+
}
|
|
379885
|
+
function colored(color, text) {
|
|
379886
|
+
return `${color}${text}${ANSI2.reset}`;
|
|
379887
|
+
}
|
|
379888
|
+
function formatDuration(startIso, endIso) {
|
|
379889
|
+
const ms = new Date(endIso).getTime() - new Date(startIso).getTime();
|
|
379890
|
+
if (!Number.isFinite(ms) || ms < 0) return null;
|
|
379891
|
+
const totalSeconds = Math.round(ms / 1e3);
|
|
379892
|
+
const minutes = Math.floor(totalSeconds / 60);
|
|
379893
|
+
const seconds = totalSeconds % 60;
|
|
379894
|
+
return minutes > 0 ? `${minutes}m ${seconds}s` : `${seconds}s`;
|
|
379895
|
+
}
|
|
379896
|
+
function formatBytes3(bytes) {
|
|
379897
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
379898
|
+
return `${(bytes / 1024).toFixed(1)} KB`;
|
|
379899
|
+
}
|
|
379900
|
+
function renderDeployment(deployment, includeOutput) {
|
|
379901
|
+
const lines = [];
|
|
379902
|
+
lines.push(
|
|
379903
|
+
`${ANSI2.bold}Deployment ${deployment.id}${ANSI2.reset} ${ANSI2.dim}(${deployment.environment})${ANSI2.reset}`
|
|
379904
|
+
);
|
|
379905
|
+
lines.push(
|
|
379906
|
+
` State: ${colored(stateColor(deployment.state), deployment.state)}`
|
|
379907
|
+
);
|
|
379908
|
+
if (deployment.stateMessage) {
|
|
379909
|
+
lines.push(` Message: ${deployment.stateMessage}`);
|
|
379910
|
+
}
|
|
379911
|
+
if (deployment.triggeredBy) {
|
|
379912
|
+
lines.push(` Triggered: ${deployment.triggeredBy}`);
|
|
379913
|
+
}
|
|
379914
|
+
if (deployment.gitCommitSha) {
|
|
379915
|
+
lines.push(` Commit: ${deployment.gitCommitSha}`);
|
|
379916
|
+
}
|
|
379917
|
+
lines.push(` Created: ${deployment.createdAt}`);
|
|
379918
|
+
if (deployment.completedAt) {
|
|
379919
|
+
const duration = deployment.startedAt ? formatDuration(deployment.startedAt, deployment.completedAt) : null;
|
|
379920
|
+
lines.push(
|
|
379921
|
+
` Completed: ${deployment.completedAt}${duration ? ` ${ANSI2.dim}(took ${duration})${ANSI2.reset}` : ""}`
|
|
379922
|
+
);
|
|
379923
|
+
}
|
|
379924
|
+
const builds = deployment.builds ?? [];
|
|
379925
|
+
if (builds.length > 0) {
|
|
379926
|
+
lines.push("");
|
|
379927
|
+
lines.push(`${ANSI2.bold}Builds${ANSI2.reset}`);
|
|
379928
|
+
const nameWidth = Math.max(...builds.map((b) => b.name.length));
|
|
379929
|
+
for (const build of builds) {
|
|
379930
|
+
const marker = build.state === "failed" ? "\u2716" : build.state === "completed" ? "\u2714" : "\u2026";
|
|
379931
|
+
let line = ` ${colored(stateColor(build.state), marker)} ${build.name.padEnd(nameWidth)} ${colored(stateColor(build.state), build.state)}`;
|
|
379932
|
+
if (build.state === "failed" && build.error) {
|
|
379933
|
+
line += ` ${ANSI2.dim}\u2014 ${build.error}${ANSI2.reset}`;
|
|
379934
|
+
}
|
|
379935
|
+
lines.push(line);
|
|
379936
|
+
}
|
|
379937
|
+
}
|
|
379938
|
+
const pendingActions = deployment.pendingActions ?? [];
|
|
379939
|
+
const missingSecrets = pendingActions.flatMap(
|
|
379940
|
+
(a) => a.type === "missing_secrets" ? a.secrets : []
|
|
379941
|
+
);
|
|
379942
|
+
const missingConfigs = pendingActions.flatMap(
|
|
379943
|
+
(a) => a.type === "missing_configs" ? a.configs : []
|
|
379944
|
+
);
|
|
379945
|
+
if (missingSecrets.length > 0 || missingConfigs.length > 0) {
|
|
379946
|
+
lines.push("");
|
|
379947
|
+
lines.push(colored(ANSI2.yellow, `${ANSI2.bold}Action required${ANSI2.reset}`));
|
|
379948
|
+
if (missingSecrets.length > 0) {
|
|
379949
|
+
lines.push(` Missing secrets: ${missingSecrets.join(", ")}`);
|
|
379950
|
+
}
|
|
379951
|
+
if (missingConfigs.length > 0) {
|
|
379952
|
+
lines.push(` Missing configs: ${missingConfigs.join(", ")}`);
|
|
379953
|
+
}
|
|
379954
|
+
}
|
|
379955
|
+
if (deployment.error) {
|
|
379956
|
+
lines.push("");
|
|
379957
|
+
lines.push(
|
|
379958
|
+
colored(
|
|
379959
|
+
ANSI2.red,
|
|
379960
|
+
`${ANSI2.bold}${formatErrorCode(deployment.error.code)}:${ANSI2.reset}${ANSI2.red} ${deployment.error.message}`
|
|
379961
|
+
)
|
|
379962
|
+
);
|
|
379963
|
+
if (deployment.error.resource) {
|
|
379964
|
+
lines.push(`${ANSI2.dim}Resource: ${deployment.error.resource}${ANSI2.reset}`);
|
|
379965
|
+
}
|
|
379966
|
+
}
|
|
379967
|
+
const buildsWithOutput = builds.filter(
|
|
379968
|
+
(b) => typeof b.output === "string" && b.output.length > 0
|
|
379969
|
+
);
|
|
379970
|
+
const extraErrorOutput = deployment.error?.output && !buildsWithOutput.some((b) => b.output === deployment.error?.output) ? deployment.error.output : null;
|
|
379971
|
+
if (includeOutput) {
|
|
379972
|
+
const ordered = [...buildsWithOutput].sort(
|
|
379973
|
+
(a, b) => Number(b.state === "failed") - Number(a.state === "failed")
|
|
379974
|
+
);
|
|
379975
|
+
for (const build of ordered) {
|
|
379976
|
+
lines.push("");
|
|
379977
|
+
lines.push(`${ANSI2.bold}Output (${build.name})${ANSI2.reset}`);
|
|
379978
|
+
lines.push(build.output.trimEnd());
|
|
379979
|
+
}
|
|
379980
|
+
if (extraErrorOutput) {
|
|
379981
|
+
lines.push("");
|
|
379982
|
+
lines.push(`${ANSI2.bold}Output${ANSI2.reset}`);
|
|
379983
|
+
lines.push(extraErrorOutput.trimEnd());
|
|
379984
|
+
}
|
|
379985
|
+
} else {
|
|
379986
|
+
const totalBytes = buildsWithOutput.reduce((sum, b) => sum + b.output.length, 0) + (extraErrorOutput?.length ?? 0);
|
|
379987
|
+
if (totalBytes > 0) {
|
|
379988
|
+
lines.push("");
|
|
379989
|
+
lines.push(
|
|
379990
|
+
`${ANSI2.dim}Output hidden (${formatBytes3(totalBytes)}). Run \`specific deployment show ${deployment.id} --output\` to print it.${ANSI2.reset}`
|
|
379991
|
+
);
|
|
379992
|
+
}
|
|
379993
|
+
}
|
|
379994
|
+
return lines.join("\n") + "\n";
|
|
379995
|
+
}
|
|
379996
|
+
|
|
379753
379997
|
// src/lib/domain/format-dns-instructions.ts
|
|
379754
379998
|
function isApex(domain) {
|
|
379755
379999
|
return domain.split(".").length === 2;
|
|
@@ -379830,7 +380074,7 @@ async function domainAddCommand(service, domain, options2) {
|
|
|
379830
380074
|
var program = new Command();
|
|
379831
380075
|
var env = "production";
|
|
379832
380076
|
var envLabel = env !== "production" ? `[${env.toUpperCase()}] ` : "";
|
|
379833
|
-
program.name("specific").description(`${envLabel}Infrastructure-as-code for coding agents`).version("0.1.
|
|
380077
|
+
program.name("specific").description(`${envLabel}Infrastructure-as-code for coding agents`).version("0.1.183").enablePositionalOptions();
|
|
379834
380078
|
program.command("init").description("Initialize project for use with a coding agent").option("--agent <name...>", "Agents to configure (cursor, claude, codex, other)").addHelpText("after", `
|
|
379835
380079
|
Examples:
|
|
379836
380080
|
$ specific init
|
|
@@ -379912,9 +380156,28 @@ Examples:
|
|
|
379912
380156
|
$ specific domain add web example.com --environment staging`).action((service, domain, options2) => {
|
|
379913
380157
|
domainAddCommand(service, domain, options2);
|
|
379914
380158
|
});
|
|
379915
|
-
program.command("
|
|
380159
|
+
var deploymentCmd = program.command("deployment").description("Inspect deployments");
|
|
380160
|
+
deploymentCmd.command("show [id]").description(
|
|
380161
|
+
"Show a deployment's state, builds, and errors (defaults to the latest deployment)"
|
|
380162
|
+
).option("-e, --environment <name|id>", "Target environment (defaults to the current one; ignored when an ID is given)").option(
|
|
380163
|
+
"--format <text|json>",
|
|
380164
|
+
"Output format (defaults to text on a TTY, json when piped)"
|
|
380165
|
+
).option(
|
|
380166
|
+
"--output",
|
|
380167
|
+
"Include build output (logs); can be large, so it is hidden by default"
|
|
380168
|
+
).addHelpText("after", `
|
|
380169
|
+
Examples:
|
|
380170
|
+
$ specific deployment show
|
|
380171
|
+
$ specific deployment show dep_0abc123
|
|
380172
|
+
$ specific deployment show dep_0abc123 --output
|
|
380173
|
+
$ specific deployment show --environment staging
|
|
380174
|
+
$ specific deployment show -e preview-a1b2c3d4 --format json`).action((id, options2) => {
|
|
380175
|
+
deploymentShowCommand(id, options2);
|
|
380176
|
+
});
|
|
380177
|
+
program.command("status").description("Show project, environments, and deployment status").option("--previews", "Show detailed preview environment and GitHub PR preview configuration").addHelpText("after", `
|
|
379916
380178
|
Examples:
|
|
379917
|
-
$ specific status
|
|
380179
|
+
$ specific status
|
|
380180
|
+
$ specific status --previews`).action((options2) => statusCommand(options2));
|
|
379918
380181
|
program.command("query [sql]").description(
|
|
379919
380182
|
"Run a SQL query against your environment's observability data (ClickHouse), or a Postgres database with --db"
|
|
379920
380183
|
).option("-e, --environment <name|id>", "Target environment (defaults to the current one)").option(
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# Deployments
|
|
2
|
+
|
|
3
|
+
Every deploy — whether started with `specific deploy`, by a GitHub push, or from the dashboard — creates a deployment record with its state, the builds it ran, and any failure details. Use `specific deployment show` to inspect one, most importantly to answer "why did my deploy fail?" when you didn't run the deploy yourself.
|
|
4
|
+
|
|
5
|
+
## Inspecting a deployment
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
# The latest deployment of the current environment (whatever its state)
|
|
9
|
+
specific deployment show
|
|
10
|
+
|
|
11
|
+
# A specific deployment by ID
|
|
12
|
+
specific deployment show dep_0abc123
|
|
13
|
+
|
|
14
|
+
# The latest deployment of another environment (or a preview environment)
|
|
15
|
+
specific deployment show --environment staging
|
|
16
|
+
specific deployment show -e preview-a1b2c3d4
|
|
17
|
+
|
|
18
|
+
# Machine-readable output for agents and scripts
|
|
19
|
+
specific deployment show --format json
|
|
20
|
+
|
|
21
|
+
# Include the build output (logs) — hidden by default because it can be large
|
|
22
|
+
specific deployment show dep_0abc123 --output
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Flags:
|
|
26
|
+
|
|
27
|
+
- `-e, --environment <name|id>` — target environment when no ID is given (defaults to the current one). Run `specific status` to list every environment and its ID.
|
|
28
|
+
- `--format <text|json>` — output format. When stdout is a terminal, the default is `text`. When stdout is piped or redirected, the default is `json`.
|
|
29
|
+
- `--output` — include build output (logs) and the error's diagnostic output. Without this flag, output is omitted in **both** formats (a size hint is printed in text mode) so a state check never floods the terminal or an agent's context with logs.
|
|
30
|
+
|
|
31
|
+
The output includes:
|
|
32
|
+
|
|
33
|
+
- **State** — `pending`, `queued`, `deploying`, `active`, `failed`, `cancelled`, or `superseded`, plus the state message.
|
|
34
|
+
- **Trigger** — how the deploy was started (`cli`, `github`, …) and the git commit, when known.
|
|
35
|
+
- **Builds** — each image build with its state and, for failed builds, the error message. Add `--output` for the full **build output (logs)** — where compile errors, missing dependencies, and failing build commands show up.
|
|
36
|
+
- **Action required** — for pending deployments, the missing secrets and configs blocking it (see below).
|
|
37
|
+
- **Error** — a structured error for failed deployments (`build_failed`, `pre_deploy_failed`, `health_check_failed`, …) with the failing resource; add `--output` for its diagnostic output (pod logs, Kubernetes events).
|
|
38
|
+
|
|
39
|
+
The `text` format is for quick human inspection. Agents and scripts should use `json` to get complete, untruncated values.
|
|
40
|
+
|
|
41
|
+
## Pending deployments
|
|
42
|
+
|
|
43
|
+
A deployment stays `pending` while builds run and until every required secret and config has a value. `specific deployment show` lists what is missing:
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
Action required
|
|
47
|
+
Missing secrets: STRIPE_KEY
|
|
48
|
+
Missing configs: DOMAIN
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Values can be provided in the dashboard (the state message links the deployment page), or by deploying from the CLI with `specific deploy --secret KEY=value --config KEY=value`, which prompts for anything still missing. See `specific docs secrets-config`.
|
|
52
|
+
|
|
53
|
+
## Finding the deployment to inspect
|
|
54
|
+
|
|
55
|
+
`specific status` shows, per environment, the active deployment, any in-progress deployment, and — when it differs — the **last** deployment. A failed deploy never becomes active, so after a failed GitHub push the environment still shows an older active deployment; the failure appears on the "Last deployment" line with its ID.
|
|
56
|
+
|
|
57
|
+
```sh
|
|
58
|
+
specific status # find the deployment ID (active, in progress, or last/failed)
|
|
59
|
+
specific deployment show # or skip the lookup: shows the latest deployment directly
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Debugging a failed deploy
|
|
63
|
+
|
|
64
|
+
1. Run `specific deployment show` (with `-e <env>` if it isn't the current environment) to see what failed.
|
|
65
|
+
2. If a **build** failed: re-run with `--output` to print the build log with the exact command that failed. Fix the code or the `build` block in `specific.hcl` (see `specific docs builds`) and deploy again.
|
|
66
|
+
3. If a **service** failed to deploy or its health check failed: re-run with `--output` for pod logs and Kubernetes events. For deeper investigation of runtime behaviour, query logs and metrics with `specific query` (see `specific docs observability`).
|
|
67
|
+
|
|
68
|
+
Build output is kept on the deployment record (the last portion of the log, which contains the failure). It is **not** part of the `observability.logs` stream — that stream contains runtime service and database logs only.
|
|
69
|
+
|
|
70
|
+
GitHub-triggered deploys behave exactly the same: the deployment record carries the build failure and its output, so `specific deployment show` works without access to GitHub.
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
Related topics:
|
|
75
|
+
|
|
76
|
+
- Run `specific docs builds` for build configuration (`base`, `command`, custom Dockerfiles)
|
|
77
|
+
- Run `specific docs observability` for querying runtime logs and metrics of deployed services
|
|
78
|
+
- Run `specific docs previews` for pull-request preview environments
|
|
79
|
+
- Run `specific docs secrets-config` if a deploy is waiting for missing secrets or configs
|
package/dist/docs/index.md
CHANGED
|
@@ -12,6 +12,8 @@ This documentation is nested and references to other docs are in Markdown link f
|
|
|
12
12
|
|
|
13
13
|
A full development environment can be started with `specific dev`. To deploy any changes, the user can run `specific deploy`.
|
|
14
14
|
|
|
15
|
+
If the project is connected to GitHub in the Specific Dashboard, pushes to the configured branch can deploy automatically. Run `specific status` to inspect the project and environment state. If a deploy fails — including GitHub-triggered deploys — run `specific deployment show` to see why, and add `--output` for the failed build's log.
|
|
16
|
+
|
|
15
17
|
After deploying, the user can manage their project from the Specific Dashboard at https://dashboard.specific.dev — monitoring logs and metrics, scaling services, browsing the database and object storage, updating secrets and configuration, and deleting projects. Point them there whenever production management comes up.
|
|
16
18
|
|
|
17
19
|
## Topics
|
|
@@ -27,6 +29,7 @@ After deploying, the user can manage their project from the Specific Dashboard a
|
|
|
27
29
|
- [Redis](/redis): define non-durable Redis-compatible databases for caching and more.
|
|
28
30
|
- [Volumes](/volumes): define persistent storage volumes for services to store files in.
|
|
29
31
|
- [Temporal](/temporal): managed durable workflow engine for background tasks, AI agents and more.
|
|
32
|
+
- [Deployments](/deployments): inspect deployments and debug failed deploys (including build output) with `specific deployment show`, regardless of whether the deploy was started from the CLI, GitHub, or the dashboard.
|
|
30
33
|
- [Observability](/observability): query logs and metrics from running environments using `specific query` for debugging and analytics.
|
|
31
34
|
- [Preview environments](/previews): ephemeral copies of an environment created per pull request, with the parent's databases branched, object storage forked, and volumes cloned.
|
|
32
35
|
<!-- beta:mail -->
|
|
@@ -66,7 +66,7 @@ Column names follow OpenTelemetry's ClickHouse exporter conventions and are case
|
|
|
66
66
|
|
|
67
67
|
### `observability.logs`
|
|
68
68
|
|
|
69
|
-
Unified log stream from services.
|
|
69
|
+
Unified log stream from services. This is **runtime** data: service and database logs from running containers. Build output from deploys is not part of this stream — it lives on the deployment record (run `specific deployment show`, see `specific docs deployments`).
|
|
70
70
|
|
|
71
71
|
| Column | Type | Notes |
|
|
72
72
|
|---|---|---|
|
|
@@ -227,3 +227,4 @@ Related topics:
|
|
|
227
227
|
- Run `specific status` to list environments (standard and preview) and their IDs before querying a specific one
|
|
228
228
|
- Run `specific docs services` for how services are defined
|
|
229
229
|
- Run `specific docs postgres` for querying your application database (`specific psql` for local development, `specific query --db <name>` for production)
|
|
230
|
+
- Run `specific docs deployments` for debugging failed deploys — build output is on the deployment record (`specific deployment show`), not in `observability.logs`
|
package/dist/docs/previews.md
CHANGED
|
@@ -9,6 +9,10 @@ A preview environment is a temporary, fully isolated copy of an existing environ
|
|
|
9
9
|
|
|
10
10
|
Preview environments are ephemeral: pull-request previews are removed when the PR is closed or merged, and a preview can be given an expiry, after which Specific cleans it up automatically.
|
|
11
11
|
|
|
12
|
+
Pull-request previews depend on both the organization's plan and the GitHub environment configuration. Run `specific status` to see a concise preview summary, or `specific status --previews` to see whether preview environments are enabled, whether GitHub PR previews are configured, and whether PRs require a label.
|
|
13
|
+
|
|
14
|
+
Some projects require the `specific:preview` pull request label before Specific creates or updates a PR preview. When label gating is enabled, adding `specific:preview` starts the preview and removing it tears the preview down.
|
|
15
|
+
|
|
12
16
|
Run `specific status` to list every environment and its `env_…` ID, including preview environments. Target a preview with `--environment` on commands that take one (see `specific docs observability`).
|
|
13
17
|
|
|
14
18
|
## What's copied from the parent
|
|
@@ -32,3 +36,4 @@ Related topics:
|
|
|
32
36
|
- Run `specific docs volumes` for declaring persistent volumes
|
|
33
37
|
- Run `specific docs services` for service configuration
|
|
34
38
|
- Run `specific docs observability` for querying a preview's logs and metrics
|
|
39
|
+
- Run `specific docs deployments` for debugging a failed preview deploy (`specific deployment show -e <preview>` prints the failure; add `--output` for the build log)
|
package/package.json
CHANGED
/package/dist/admin/_next/static/{7iJYom_N04p3jvXLOBn8G → NNVAA5krd_olwtUDgk5Bo}/_buildManifest.js
RENAMED
|
File without changes
|
|
File without changes
|
/package/dist/admin/_next/static/{7iJYom_N04p3jvXLOBn8G → NNVAA5krd_olwtUDgk5Bo}/_ssgManifest.js
RENAMED
|
File without changes
|