@specific.dev/cli 0.1.181 → 0.1.184
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 +465 -57
- package/dist/docs/deployments.md +79 -0
- package/dist/docs/index.md +4 -1
- package/dist/docs/observability.md +2 -1
- package/dist/docs/previews.md +12 -0
- package/dist/docs/secrets-config.md +6 -0
- package/package.json +1 -1
- /package/dist/admin/_next/static/{7iJYom_N04p3jvXLOBn8G → SWOCW_-G5yh3FBHTMSXpq}/_buildManifest.js +0 -0
- /package/dist/admin/_next/static/{7iJYom_N04p3jvXLOBn8G → SWOCW_-G5yh3FBHTMSXpq}/_clientMiddlewareManifest.json +0 -0
- /package/dist/admin/_next/static/{7iJYom_N04p3jvXLOBn8G → SWOCW_-G5yh3FBHTMSXpq}/_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
|
};
|
|
@@ -368220,6 +368225,14 @@ async function createTarball(config, projectDir) {
|
|
|
368220
368225
|
return await createProjectTarball(projectDir, widestContext);
|
|
368221
368226
|
}
|
|
368222
368227
|
var import_hcl2_json_parser = __toESM2(require_dist(), 1);
|
|
368228
|
+
function extractEnvReferences(value) {
|
|
368229
|
+
if (typeof value === "string")
|
|
368230
|
+
return [];
|
|
368231
|
+
if (value.type === "interpolated") {
|
|
368232
|
+
return value.parts.filter((p) => p.type === "ref").map((p) => p.ref);
|
|
368233
|
+
}
|
|
368234
|
+
return [value];
|
|
368235
|
+
}
|
|
368223
368236
|
var { parseToObject } = import_hcl2_json_parser.default;
|
|
368224
368237
|
function parseReference(value) {
|
|
368225
368238
|
if (typeof value !== "string") {
|
|
@@ -368991,19 +369004,11 @@ async function parseConfig(hcl) {
|
|
|
368991
369004
|
environments: parseEnvironments(json.environment)
|
|
368992
369005
|
};
|
|
368993
369006
|
}
|
|
368994
|
-
function envValueReferences(value) {
|
|
368995
|
-
if (typeof value === "string")
|
|
368996
|
-
return [];
|
|
368997
|
-
if (value.type === "interpolated") {
|
|
368998
|
-
return value.parts.filter((p) => p.type === "ref").map((p) => p.ref);
|
|
368999
|
-
}
|
|
369000
|
-
return [value];
|
|
369001
|
-
}
|
|
369002
369007
|
function hasPortReference(env2) {
|
|
369003
369008
|
if (!env2)
|
|
369004
369009
|
return false;
|
|
369005
369010
|
for (const value of Object.values(env2)) {
|
|
369006
|
-
for (const ref of
|
|
369011
|
+
for (const ref of extractEnvReferences(value)) {
|
|
369007
369012
|
if (ref.type === "port")
|
|
369008
369013
|
return true;
|
|
369009
369014
|
}
|
|
@@ -369020,7 +369025,7 @@ function validateEndpointReferences(config) {
|
|
|
369020
369025
|
if (!service.env)
|
|
369021
369026
|
continue;
|
|
369022
369027
|
for (const [key, value] of Object.entries(service.env)) {
|
|
369023
|
-
const refs =
|
|
369028
|
+
const refs = extractEnvReferences(value);
|
|
369024
369029
|
for (const ref of refs) {
|
|
369025
369030
|
if (service.endpoints.length > 1 && ref.type === "port") {
|
|
369026
369031
|
errors.push({
|
|
@@ -369099,6 +369104,82 @@ function validateEndpointReferences(config) {
|
|
|
369099
369104
|
}
|
|
369100
369105
|
return errors;
|
|
369101
369106
|
}
|
|
369107
|
+
var UnknownServiceError = class extends Error {
|
|
369108
|
+
unknownNames;
|
|
369109
|
+
availableServices;
|
|
369110
|
+
cronNames;
|
|
369111
|
+
constructor(unknownNames, availableServices, cronNames) {
|
|
369112
|
+
const plural = unknownNames.length > 1 ? "services" : "service";
|
|
369113
|
+
const names = unknownNames.map((n) => `"${n}"`).join(", ");
|
|
369114
|
+
const available = availableServices.length > 0 ? ` Available services: ${availableServices.join(", ")}` : " No services are defined in specific.hcl.";
|
|
369115
|
+
super(`Unknown ${plural} ${names}.${available}`);
|
|
369116
|
+
this.unknownNames = unknownNames;
|
|
369117
|
+
this.availableServices = availableServices;
|
|
369118
|
+
this.cronNames = cronNames;
|
|
369119
|
+
this.name = "UnknownServiceError";
|
|
369120
|
+
}
|
|
369121
|
+
};
|
|
369122
|
+
function resolveServiceClosure(config, roots, options2 = {}) {
|
|
369123
|
+
const devMode = options2.devMode ?? true;
|
|
369124
|
+
const byName = new Map(config.services.map((s) => [s.name, s]));
|
|
369125
|
+
const dedupedRoots = [...new Set(roots)];
|
|
369126
|
+
const unknown = dedupedRoots.filter((name) => !byName.has(name));
|
|
369127
|
+
if (unknown.length > 0) {
|
|
369128
|
+
throw new UnknownServiceError(unknown, [...byName.keys()], config.crons.map((c) => c.name));
|
|
369129
|
+
}
|
|
369130
|
+
const visited = new Set(dedupedRoots);
|
|
369131
|
+
const queue = [...dedupedRoots];
|
|
369132
|
+
const postgres = /* @__PURE__ */ new Set();
|
|
369133
|
+
const redis = /* @__PURE__ */ new Set();
|
|
369134
|
+
const storage = /* @__PURE__ */ new Set();
|
|
369135
|
+
const temporal = /* @__PURE__ */ new Set();
|
|
369136
|
+
const mail = /* @__PURE__ */ new Set();
|
|
369137
|
+
const syncPostgres = /* @__PURE__ */ new Set();
|
|
369138
|
+
const effectiveEnv = (service) => devMode ? { ...service.env, ...service.dev?.env } : { ...service.env };
|
|
369139
|
+
while (queue.length > 0) {
|
|
369140
|
+
const service = byName.get(queue.shift());
|
|
369141
|
+
for (const value of Object.values(effectiveEnv(service))) {
|
|
369142
|
+
for (const ref of extractEnvReferences(value)) {
|
|
369143
|
+
switch (ref.type) {
|
|
369144
|
+
case "service":
|
|
369145
|
+
if (byName.has(ref.serviceName) && !visited.has(ref.serviceName)) {
|
|
369146
|
+
visited.add(ref.serviceName);
|
|
369147
|
+
queue.push(ref.serviceName);
|
|
369148
|
+
}
|
|
369149
|
+
break;
|
|
369150
|
+
case "postgres":
|
|
369151
|
+
postgres.add(ref.name);
|
|
369152
|
+
if (ref.attribute === "sync.url" || ref.attribute === "sync.secret") {
|
|
369153
|
+
syncPostgres.add(ref.name);
|
|
369154
|
+
}
|
|
369155
|
+
break;
|
|
369156
|
+
case "redis":
|
|
369157
|
+
redis.add(ref.name);
|
|
369158
|
+
break;
|
|
369159
|
+
case "storage":
|
|
369160
|
+
storage.add(ref.name);
|
|
369161
|
+
break;
|
|
369162
|
+
case "temporal":
|
|
369163
|
+
temporal.add(ref.name);
|
|
369164
|
+
break;
|
|
369165
|
+
case "mail":
|
|
369166
|
+
mail.add(ref.name);
|
|
369167
|
+
break;
|
|
369168
|
+
}
|
|
369169
|
+
}
|
|
369170
|
+
}
|
|
369171
|
+
}
|
|
369172
|
+
const names = (items, set) => items.filter((item) => set.has(item.name)).map((item) => item.name);
|
|
369173
|
+
return {
|
|
369174
|
+
services: names(config.services, visited),
|
|
369175
|
+
postgres: names(config.postgres, postgres),
|
|
369176
|
+
redis: names(config.redis, redis),
|
|
369177
|
+
storage: names(config.storage, storage),
|
|
369178
|
+
temporal: names(config.temporal, temporal),
|
|
369179
|
+
mail: names(config.mail, mail),
|
|
369180
|
+
syncPostgres: names(config.postgres, syncPostgres)
|
|
369181
|
+
};
|
|
369182
|
+
}
|
|
369102
369183
|
function parseHclErrors(rawError) {
|
|
369103
369184
|
const raw = rawError.trim();
|
|
369104
369185
|
if (!raw.startsWith("parse config: [")) {
|
|
@@ -372583,7 +372664,7 @@ async function startResources(options2) {
|
|
|
372583
372664
|
});
|
|
372584
372665
|
}
|
|
372585
372666
|
if (shouldStartElectric) {
|
|
372586
|
-
const syncDatabases = detectSyncDatabases(config);
|
|
372667
|
+
const syncDatabases = options2.syncDatabases ?? detectSyncDatabases(config);
|
|
372587
372668
|
for (const pgName of syncDatabases) {
|
|
372588
372669
|
if (signal?.cancelled) {
|
|
372589
372670
|
return { resources, electric, mail: mailServers, startedResources, startedElectric, cancelled: true };
|
|
@@ -373082,9 +373163,11 @@ var DevEnvironment = class extends TypedEventEmitter {
|
|
|
373082
373163
|
tunnelEnabled;
|
|
373083
373164
|
preSuppliedSecrets;
|
|
373084
373165
|
preSuppliedConfigs;
|
|
373166
|
+
servicesFilter;
|
|
373085
373167
|
// Status
|
|
373086
373168
|
_status = "idle";
|
|
373087
373169
|
_config;
|
|
373170
|
+
_selection;
|
|
373088
373171
|
// Guards
|
|
373089
373172
|
started = false;
|
|
373090
373173
|
shuttingDown = false;
|
|
@@ -373128,6 +373211,7 @@ var DevEnvironment = class extends TypedEventEmitter {
|
|
|
373128
373211
|
this.tunnelEnabled = options2?.tunnel ?? false;
|
|
373129
373212
|
this.preSuppliedSecrets = options2?.secrets ?? {};
|
|
373130
373213
|
this.preSuppliedConfigs = options2?.configs ?? {};
|
|
373214
|
+
this.servicesFilter = options2?.services ?? [];
|
|
373131
373215
|
}
|
|
373132
373216
|
// ── Public getters ─────────────────────────────────────────────────────────
|
|
373133
373217
|
get status() {
|
|
@@ -373136,6 +373220,10 @@ var DevEnvironment = class extends TypedEventEmitter {
|
|
|
373136
373220
|
get config() {
|
|
373137
373221
|
return this._config;
|
|
373138
373222
|
}
|
|
373223
|
+
/** The resolved service selection when a services filter is active. */
|
|
373224
|
+
get selection() {
|
|
373225
|
+
return this._selection;
|
|
373226
|
+
}
|
|
373139
373227
|
// ── Status helper ──────────────────────────────────────────────────────────
|
|
373140
373228
|
setStatus(status) {
|
|
373141
373229
|
this._status = status;
|
|
@@ -373510,6 +373598,31 @@ var DevEnvironment = class extends TypedEventEmitter {
|
|
|
373510
373598
|
}
|
|
373511
373599
|
if (this.cancelled) return;
|
|
373512
373600
|
this._config = config;
|
|
373601
|
+
let closure;
|
|
373602
|
+
if (this.servicesFilter.length > 0) {
|
|
373603
|
+
try {
|
|
373604
|
+
closure = resolveServiceClosure(config, this.servicesFilter);
|
|
373605
|
+
} catch (err) {
|
|
373606
|
+
let message = err instanceof Error ? err.message : String(err);
|
|
373607
|
+
if (err instanceof UnknownServiceError) {
|
|
373608
|
+
const crons = err.unknownNames.filter(
|
|
373609
|
+
(n) => err.cronNames.includes(n)
|
|
373610
|
+
);
|
|
373611
|
+
for (const cron of crons) {
|
|
373612
|
+
message += `
|
|
373613
|
+
"${cron}" is a cron \u2014 crons don't run in specific dev. Trigger it with: specific exec ${cron}`;
|
|
373614
|
+
}
|
|
373615
|
+
}
|
|
373616
|
+
this.setStatus("error");
|
|
373617
|
+
this.emit("error", new Error(message));
|
|
373618
|
+
return;
|
|
373619
|
+
}
|
|
373620
|
+
this.systemLog(
|
|
373621
|
+
`Starting ${closure.services.length} of ${config.services.length} services: ${closure.services.join(", ")}`
|
|
373622
|
+
);
|
|
373623
|
+
}
|
|
373624
|
+
this._selection = closure;
|
|
373625
|
+
const servicesToRun = closure ? config.services.filter((s) => closure.services.includes(s.name)) : config.services;
|
|
373513
373626
|
this.colorMap = /* @__PURE__ */ new Map();
|
|
373514
373627
|
let colorIndex = 0;
|
|
373515
373628
|
for (const pg of config.postgres) {
|
|
@@ -373547,13 +373660,21 @@ var DevEnvironment = class extends TypedEventEmitter {
|
|
|
373547
373660
|
this.setStatus("starting");
|
|
373548
373661
|
const portAllocator = await BlockPortAllocator.create();
|
|
373549
373662
|
this.systemLog(`Using port block ${portAllocator.getBasePort()}\u2013${portAllocator.getBasePort() + 99}`);
|
|
373550
|
-
const syncDatabases = detectSyncDatabases(config);
|
|
373663
|
+
const syncDatabases = closure ? new Set(closure.syncPostgres) : detectSyncDatabases(config);
|
|
373551
373664
|
let resources;
|
|
373552
373665
|
const startedElectric = [];
|
|
373553
373666
|
try {
|
|
373554
373667
|
const result = await startResources({
|
|
373555
373668
|
config,
|
|
373556
|
-
selection:
|
|
373669
|
+
selection: closure ? {
|
|
373670
|
+
mode: "required",
|
|
373671
|
+
postgres: closure.postgres,
|
|
373672
|
+
redis: closure.redis,
|
|
373673
|
+
storage: closure.storage,
|
|
373674
|
+
temporal: closure.temporal,
|
|
373675
|
+
mail: closure.mail
|
|
373676
|
+
} : { mode: "all" },
|
|
373677
|
+
syncDatabases,
|
|
373557
373678
|
stateManager,
|
|
373558
373679
|
dataDir: `.specific/keys/${this.instanceKey}/data`,
|
|
373559
373680
|
portAllocator,
|
|
@@ -373715,8 +373836,8 @@ var DevEnvironment = class extends TypedEventEmitter {
|
|
|
373715
373836
|
if (this.cancelled) return;
|
|
373716
373837
|
const secrets = await prepareSecrets(config.secrets, true);
|
|
373717
373838
|
const configs = await prepareConfigs(config.configs, void 0, true);
|
|
373718
|
-
const usedSecrets = findUsedSecrets(
|
|
373719
|
-
const usedConfigs = findUsedConfigs(
|
|
373839
|
+
const usedSecrets = findUsedSecrets(servicesToRun, true);
|
|
373840
|
+
const usedConfigs = findUsedConfigs(servicesToRun, true);
|
|
373720
373841
|
let missingSecrets = findMissingSecrets(
|
|
373721
373842
|
config.secrets,
|
|
373722
373843
|
secrets,
|
|
@@ -373800,7 +373921,7 @@ Add them to the config block in specific.local`
|
|
|
373800
373921
|
}
|
|
373801
373922
|
const serviceEndpoints = /* @__PURE__ */ new Map();
|
|
373802
373923
|
const serviceEndpointPorts = /* @__PURE__ */ new Map();
|
|
373803
|
-
for (const service of
|
|
373924
|
+
for (const service of servicesToRun) {
|
|
373804
373925
|
if (!service.command && !service.dev?.command) continue;
|
|
373805
373926
|
const endpointPorts = /* @__PURE__ */ new Map();
|
|
373806
373927
|
const endpointInfos = [];
|
|
@@ -373827,7 +373948,7 @@ Add them to the config block in specific.local`
|
|
|
373827
373948
|
serviceEndpoints.set(service.name, endpointInfos);
|
|
373828
373949
|
}
|
|
373829
373950
|
const exposedServices = [];
|
|
373830
|
-
for (const service of
|
|
373951
|
+
for (const service of servicesToRun) {
|
|
373831
373952
|
const endpointInfos = serviceEndpoints.get(service.name) || [];
|
|
373832
373953
|
for (const info of endpointInfos) {
|
|
373833
373954
|
const endpointConfig = service.endpoints.find(
|
|
@@ -373873,7 +373994,7 @@ Add them to the config block in specific.local`
|
|
|
373873
373994
|
}
|
|
373874
373995
|
return this.projectDir;
|
|
373875
373996
|
};
|
|
373876
|
-
for (const service of
|
|
373997
|
+
for (const service of servicesToRun) {
|
|
373877
373998
|
if (this.cancelled) break;
|
|
373878
373999
|
if (!service.command && !service.dev?.command) {
|
|
373879
374000
|
continue;
|
|
@@ -373942,7 +374063,7 @@ Add them to the config block in specific.local`
|
|
|
373942
374063
|
this.systemLog("Restarting services (migration changed)");
|
|
373943
374064
|
await Promise.all(this.services.map((s) => s.stop()));
|
|
373944
374065
|
const newServices = [];
|
|
373945
|
-
for (const service of
|
|
374066
|
+
for (const service of servicesToRun) {
|
|
373946
374067
|
if (!service.command && !service.dev?.command) continue;
|
|
373947
374068
|
try {
|
|
373948
374069
|
const endpointPorts = serviceEndpointPorts.get(service.name) || /* @__PURE__ */ new Map();
|
|
@@ -374765,7 +374886,7 @@ function trackEvent(event, properties) {
|
|
|
374765
374886
|
event,
|
|
374766
374887
|
properties: {
|
|
374767
374888
|
...properties,
|
|
374768
|
-
cli_version: "0.1.
|
|
374889
|
+
cli_version: "0.1.184",
|
|
374769
374890
|
platform: process.platform,
|
|
374770
374891
|
node_version: process.version,
|
|
374771
374892
|
project_id: getProjectId()
|
|
@@ -376233,7 +376354,18 @@ function ConfigInput({ configName, defaultValue, onSubmit, onCancel }) {
|
|
|
376233
376354
|
|
|
376234
376355
|
// src/commands/dev.tsx
|
|
376235
376356
|
var COLORS2 = ["cyan", "yellow", "green", "magenta", "blue"];
|
|
376236
|
-
function
|
|
376357
|
+
function filterConfigToSelection(config, selection) {
|
|
376358
|
+
return {
|
|
376359
|
+
...config,
|
|
376360
|
+
services: config.services.filter((s) => selection.services.includes(s.name)),
|
|
376361
|
+
postgres: config.postgres.filter((p) => selection.postgres.includes(p.name)),
|
|
376362
|
+
redis: config.redis.filter((r) => selection.redis.includes(r.name)),
|
|
376363
|
+
storage: config.storage.filter((s) => selection.storage.includes(s.name)),
|
|
376364
|
+
temporal: config.temporal.filter((t) => selection.temporal.includes(t.name)),
|
|
376365
|
+
mail: config.mail.filter((m) => selection.mail.includes(m.name))
|
|
376366
|
+
};
|
|
376367
|
+
}
|
|
376368
|
+
function DevUI({ instanceKey, tunnelEnabled, services: serviceFilter }) {
|
|
376237
376369
|
const { exit } = useApp2();
|
|
376238
376370
|
const [state, setState] = useState5({
|
|
376239
376371
|
status: "loading",
|
|
@@ -376276,7 +376408,8 @@ function DevUI({ instanceKey, tunnelEnabled }) {
|
|
|
376276
376408
|
const devEnv = new DevEnvironment({
|
|
376277
376409
|
projectDir: process.cwd(),
|
|
376278
376410
|
instanceKey,
|
|
376279
|
-
tunnel: tunnelEnabled
|
|
376411
|
+
tunnel: tunnelEnabled,
|
|
376412
|
+
services: serviceFilter
|
|
376280
376413
|
});
|
|
376281
376414
|
devEnvRef.current = devEnv;
|
|
376282
376415
|
let currentColorMap = /* @__PURE__ */ new Map();
|
|
@@ -376286,12 +376419,18 @@ function DevUI({ instanceKey, tunnelEnabled }) {
|
|
|
376286
376419
|
} else if (status === "starting") {
|
|
376287
376420
|
const config2 = devEnv.config;
|
|
376288
376421
|
if (config2) {
|
|
376289
|
-
|
|
376422
|
+
const selection = devEnv.selection;
|
|
376423
|
+
const displayConfig = selection ? filterConfigToSelection(config2, selection) : config2;
|
|
376424
|
+
currentColorMap = buildColorMap(displayConfig);
|
|
376290
376425
|
setState((s) => ({
|
|
376291
376426
|
...s,
|
|
376292
376427
|
status: "starting",
|
|
376293
|
-
config:
|
|
376294
|
-
colorMap: currentColorMap
|
|
376428
|
+
config: displayConfig,
|
|
376429
|
+
colorMap: currentColorMap,
|
|
376430
|
+
filterInfo: selection ? {
|
|
376431
|
+
selected: selection.services.length,
|
|
376432
|
+
total: config2.services.length
|
|
376433
|
+
} : void 0
|
|
376295
376434
|
}));
|
|
376296
376435
|
} else {
|
|
376297
376436
|
setState((s) => ({ ...s, status: "starting" }));
|
|
@@ -376408,7 +376547,7 @@ function DevUI({ instanceKey, tunnelEnabled }) {
|
|
|
376408
376547
|
devEnv.removeAllListeners();
|
|
376409
376548
|
devEnvRef.current = null;
|
|
376410
376549
|
};
|
|
376411
|
-
}, [instanceKey, tunnelEnabled]);
|
|
376550
|
+
}, [instanceKey, tunnelEnabled, serviceFilter.join(",")]);
|
|
376412
376551
|
useEffect5(() => {
|
|
376413
376552
|
const handleSignal = () => {
|
|
376414
376553
|
const devEnv = devEnvRef.current;
|
|
@@ -376555,7 +376694,7 @@ function DevUI({ instanceKey, tunnelEnabled }) {
|
|
|
376555
376694
|
return /* @__PURE__ */ React6.createElement(Box6, null, /* @__PURE__ */ React6.createElement(Text6, { color: "blue" }, /* @__PURE__ */ React6.createElement(Spinner3, { type: "dots" })));
|
|
376556
376695
|
}
|
|
376557
376696
|
if (state.status === "starting") {
|
|
376558
|
-
return /* @__PURE__ */ React6.createElement(Box6, { flexDirection: "column" }, /* @__PURE__ */ React6.createElement(Text6, null, /* @__PURE__ */ React6.createElement(Text6, { bold: true, color: "cyan" }, "Specific dev server"), /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " (Ctrl+C to stop)")), /* @__PURE__ */ React6.createElement(Text6, null, " "), (config.postgres.length > 0 || config.redis.length > 0 || config.storage.length > 0 || config.temporal.length > 0) && /* @__PURE__ */ React6.createElement(React6.Fragment, null, /* @__PURE__ */ React6.createElement(Text6, { bold: true }, "Resources:"), config.postgres.map((pg) => {
|
|
376697
|
+
return /* @__PURE__ */ React6.createElement(Box6, { flexDirection: "column" }, /* @__PURE__ */ React6.createElement(Text6, null, /* @__PURE__ */ React6.createElement(Text6, { bold: true, color: "cyan" }, "Specific dev server"), state.filterInfo && /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " ", "[", state.filterInfo.selected, " of ", state.filterInfo.total, " services]"), /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " (Ctrl+C to stop)")), /* @__PURE__ */ React6.createElement(Text6, null, " "), (config.postgres.length > 0 || config.redis.length > 0 || config.storage.length > 0 || config.temporal.length > 0) && /* @__PURE__ */ React6.createElement(React6.Fragment, null, /* @__PURE__ */ React6.createElement(Text6, { bold: true }, "Resources:"), config.postgres.map((pg) => {
|
|
376559
376698
|
const instance = resources.get(pg.name);
|
|
376560
376699
|
const resStatus = state.resourceStatus.get(pg.name);
|
|
376561
376700
|
const isReady = !!instance;
|
|
@@ -376620,7 +376759,7 @@ function DevUI({ instanceKey, tunnelEnabled }) {
|
|
|
376620
376759
|
const staticItems = [
|
|
376621
376760
|
{
|
|
376622
376761
|
key: "title",
|
|
376623
|
-
content: /* @__PURE__ */ React6.createElement(Text6, null, /* @__PURE__ */ React6.createElement(Text6, { bold: true, color: "cyan" }, "Specific dev server"), tunnelEnabled && /* @__PURE__ */ React6.createElement(Text6, { color: "magenta" }, " [tunnel]"), instanceKey !== "default" && /* @__PURE__ */ React6.createElement(Text6, { color: "yellow" }, " [", instanceKey, "]"), /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " (Ctrl+C to stop)"))
|
|
376762
|
+
content: /* @__PURE__ */ React6.createElement(Text6, null, /* @__PURE__ */ React6.createElement(Text6, { bold: true, color: "cyan" }, "Specific dev server"), tunnelEnabled && /* @__PURE__ */ React6.createElement(Text6, { color: "magenta" }, " [tunnel]"), instanceKey !== "default" && /* @__PURE__ */ React6.createElement(Text6, { color: "yellow" }, " [", instanceKey, "]"), state.filterInfo && /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " ", "[", state.filterInfo.selected, " of ", state.filterInfo.total, " services]"), /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " (Ctrl+C to stop)"))
|
|
376624
376763
|
},
|
|
376625
376764
|
{ key: "space1", content: /* @__PURE__ */ React6.createElement(Text6, null, " ") },
|
|
376626
376765
|
// Show admin UI URL
|
|
@@ -376730,10 +376869,20 @@ function DevUI({ instanceKey, tunnelEnabled }) {
|
|
|
376730
376869
|
];
|
|
376731
376870
|
return /* @__PURE__ */ React6.createElement(Box6, { flexDirection: "column" }, /* @__PURE__ */ React6.createElement(Static, { items: staticItems }, (item) => /* @__PURE__ */ React6.createElement(Box6, { key: item.key }, item.content)));
|
|
376732
376871
|
}
|
|
376733
|
-
function devCommand(instanceKey, tunnelEnabled = false) {
|
|
376872
|
+
function devCommand(instanceKey, tunnelEnabled = false, services = []) {
|
|
376734
376873
|
const options2 = isInteractive() ? {} : { stdin: new Readable2({ read() {
|
|
376735
376874
|
} }) };
|
|
376736
|
-
render4(
|
|
376875
|
+
render4(
|
|
376876
|
+
/* @__PURE__ */ React6.createElement(
|
|
376877
|
+
DevUI,
|
|
376878
|
+
{
|
|
376879
|
+
instanceKey,
|
|
376880
|
+
tunnelEnabled,
|
|
376881
|
+
services
|
|
376882
|
+
}
|
|
376883
|
+
),
|
|
376884
|
+
options2
|
|
376885
|
+
);
|
|
376737
376886
|
}
|
|
376738
376887
|
|
|
376739
376888
|
// src/commands/deploy.tsx
|
|
@@ -376743,6 +376892,22 @@ import { render as render5, Text as Text7, Box as Box7, useApp as useApp3, useIn
|
|
|
376743
376892
|
import Spinner4 from "ink-spinner";
|
|
376744
376893
|
import * as fs23 from "fs";
|
|
376745
376894
|
import * as path21 from "path";
|
|
376895
|
+
|
|
376896
|
+
// src/lib/deployments/format.ts
|
|
376897
|
+
function formatErrorCode(code) {
|
|
376898
|
+
const labels = {
|
|
376899
|
+
build_failed: "Build failed",
|
|
376900
|
+
pre_deploy_failed: "Pre-deploy hook failed",
|
|
376901
|
+
deploy_failed: "Service failed to deploy",
|
|
376902
|
+
health_check_failed: "Health check failed",
|
|
376903
|
+
post_deploy_failed: "Post-deploy hook failed",
|
|
376904
|
+
volume_reset_failed: "Volume reset failed",
|
|
376905
|
+
internal_error: "Internal error"
|
|
376906
|
+
};
|
|
376907
|
+
return labels[code] || code;
|
|
376908
|
+
}
|
|
376909
|
+
|
|
376910
|
+
// src/commands/deploy.tsx
|
|
376746
376911
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
376747
376912
|
function getGitInfo(cwd) {
|
|
376748
376913
|
try {
|
|
@@ -376944,17 +377109,6 @@ function getFailedBuild(pendingActions) {
|
|
|
376944
377109
|
if (!pendingActions) return void 0;
|
|
376945
377110
|
return pendingActions.find((a) => a.type === "build_failed");
|
|
376946
377111
|
}
|
|
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
377112
|
function StructuredError({ error }) {
|
|
376959
377113
|
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
377114
|
}
|
|
@@ -379147,7 +379301,7 @@ function compareVersions(a, b) {
|
|
|
379147
379301
|
return 0;
|
|
379148
379302
|
}
|
|
379149
379303
|
async function checkForUpdate() {
|
|
379150
|
-
const currentVersion = "0.1.
|
|
379304
|
+
const currentVersion = "0.1.184";
|
|
379151
379305
|
const response = await fetch(`${BINARIES_BASE_URL}/latest?t=${Date.now()}`);
|
|
379152
379306
|
if (!response.ok) {
|
|
379153
379307
|
throw new Error(`Failed to check for updates: HTTP ${response.status}`);
|
|
@@ -379434,13 +379588,35 @@ function PreviewBlock({ env: env2 }) {
|
|
|
379434
379588
|
const urls = managedUrls(env2.domains);
|
|
379435
379589
|
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
379590
|
}
|
|
379591
|
+
function PreviewConfigurationBlock({
|
|
379592
|
+
config,
|
|
379593
|
+
activePreviewCount,
|
|
379594
|
+
showDetails
|
|
379595
|
+
}) {
|
|
379596
|
+
const configured = config.github;
|
|
379597
|
+
const enabledConfigs = configured.filter((c) => c.prPreviewsEnabled);
|
|
379598
|
+
const labelGated = enabledConfigs.filter((c) => c.previewsRequireLabel);
|
|
379599
|
+
const githubSummary = enabledConfigs.length > 0 ? `enabled for ${enabledConfigs.length} environment${enabledConfigs.length === 1 ? "" : "s"}` : configured.length > 0 ? "configured but disabled" : "not configured";
|
|
379600
|
+
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, ")")))))));
|
|
379601
|
+
}
|
|
379437
379602
|
function EnvironmentBlock({ env: env2 }) {
|
|
379438
|
-
|
|
379603
|
+
const latest = env2.latestDeployment;
|
|
379604
|
+
const latestIsShownElsewhere = !latest || latest.id === env2.activeDeployment?.id || latest.id === env2.inProgressDeployment?.id;
|
|
379605
|
+
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(
|
|
379606
|
+
Text12,
|
|
379607
|
+
{
|
|
379608
|
+
...latest.state === "failed" ? { color: "red", bold: true } : {}
|
|
379609
|
+
},
|
|
379610
|
+
"(",
|
|
379611
|
+
latest.state,
|
|
379612
|
+
latest.triggeredBy ? ` via ${latest.triggeredBy}` : "",
|
|
379613
|
+
")"
|
|
379614
|
+
)), !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
379615
|
const displayUrls = urls.custom.length > 0 ? urls.custom : urls.managed ? [urls.managed] : [];
|
|
379440
379616
|
return /* @__PURE__ */ React12.createElement(Text12, { key: serviceName }, " - ", serviceName, displayUrls.length > 0 ? `: ${displayUrls.join(", ")}` : null);
|
|
379441
379617
|
})));
|
|
379442
379618
|
}
|
|
379443
|
-
function StatusUI() {
|
|
379619
|
+
function StatusUI({ previews = false }) {
|
|
379444
379620
|
const [state, setState] = useState11({ phase: "loading" });
|
|
379445
379621
|
useEffect11(() => {
|
|
379446
379622
|
(async () => {
|
|
@@ -379468,14 +379644,21 @@ function StatusUI() {
|
|
|
379468
379644
|
return /* @__PURE__ */ React12.createElement(Text12, { color: "red" }, "Failed to get status: ", state.error);
|
|
379469
379645
|
}
|
|
379470
379646
|
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 })))),
|
|
379647
|
+
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(
|
|
379648
|
+
PreviewConfigurationBlock,
|
|
379649
|
+
{
|
|
379650
|
+
config: status.previewEnvironmentConfig,
|
|
379651
|
+
activePreviewCount: (status.previewEnvironments ?? []).length,
|
|
379652
|
+
showDetails: previews
|
|
379653
|
+
}
|
|
379654
|
+
), (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
379655
|
}
|
|
379473
|
-
function statusCommand() {
|
|
379474
|
-
render10(/* @__PURE__ */ React12.createElement(StatusUI,
|
|
379656
|
+
function statusCommand(options2 = {}) {
|
|
379657
|
+
render10(/* @__PURE__ */ React12.createElement(StatusUI, { previews: options2.previews ?? false }));
|
|
379475
379658
|
}
|
|
379476
379659
|
|
|
379477
379660
|
// src/lib/project/resolve-env.ts
|
|
379478
|
-
async function
|
|
379661
|
+
async function resolveEnvironmentStatus(client2, projectId, envFlag) {
|
|
379479
379662
|
const status = await client2.getProjectStatus(projectId);
|
|
379480
379663
|
const environments = status.environments;
|
|
379481
379664
|
const previewEnvironments = status.previewEnvironments ?? [];
|
|
@@ -379490,16 +379673,19 @@ async function resolveEnvironmentId(client2, projectId, envFlag) {
|
|
|
379490
379673
|
`Environment "${envFlag}" not found (matched against name and ID). Available: ${available}`
|
|
379491
379674
|
);
|
|
379492
379675
|
}
|
|
379493
|
-
return match
|
|
379676
|
+
return match;
|
|
379494
379677
|
}
|
|
379495
379678
|
if (hasEnvironmentId()) {
|
|
379496
379679
|
const savedId = readEnvironmentId();
|
|
379497
|
-
|
|
379498
|
-
|
|
379680
|
+
const saved = environments.find(
|
|
379681
|
+
(e) => e.id === savedId
|
|
379682
|
+
);
|
|
379683
|
+
if (saved) {
|
|
379684
|
+
return saved;
|
|
379499
379685
|
}
|
|
379500
379686
|
}
|
|
379501
379687
|
if (environments.length === 1) {
|
|
379502
|
-
return environments[0]
|
|
379688
|
+
return environments[0];
|
|
379503
379689
|
}
|
|
379504
379690
|
if (environments.length === 0) {
|
|
379505
379691
|
throw new Error("This project has no environments yet.");
|
|
@@ -379509,6 +379695,10 @@ async function resolveEnvironmentId(client2, projectId, envFlag) {
|
|
|
379509
379695
|
`Project has multiple environments (${names}). Pass --environment <name|id>.`
|
|
379510
379696
|
);
|
|
379511
379697
|
}
|
|
379698
|
+
async function resolveEnvironmentId(client2, projectId, envFlag) {
|
|
379699
|
+
const match = await resolveEnvironmentStatus(client2, projectId, envFlag);
|
|
379700
|
+
return match.id;
|
|
379701
|
+
}
|
|
379512
379702
|
|
|
379513
379703
|
// src/lib/query/render-table.ts
|
|
379514
379704
|
var MAX_COL_WIDTH = 60;
|
|
@@ -379750,6 +379940,204 @@ function friendlyErrorMessage(raw) {
|
|
|
379750
379940
|
return raw;
|
|
379751
379941
|
}
|
|
379752
379942
|
|
|
379943
|
+
// src/commands/deployment.tsx
|
|
379944
|
+
var ANSI2 = {
|
|
379945
|
+
dim: "\x1B[2m",
|
|
379946
|
+
red: "\x1B[31m",
|
|
379947
|
+
green: "\x1B[32m",
|
|
379948
|
+
yellow: "\x1B[33m",
|
|
379949
|
+
bold: "\x1B[1m",
|
|
379950
|
+
reset: "\x1B[0m"
|
|
379951
|
+
};
|
|
379952
|
+
async function deploymentShowCommand(idArg, options2) {
|
|
379953
|
+
try {
|
|
379954
|
+
const format = resolveOutputFormat2(options2.format);
|
|
379955
|
+
const includeOutput = options2.output ?? false;
|
|
379956
|
+
const client2 = new SpecificClient({ tokenProvider: getValidAccessToken });
|
|
379957
|
+
let deploymentId = idArg;
|
|
379958
|
+
if (!deploymentId) {
|
|
379959
|
+
const projectId = readProjectId();
|
|
379960
|
+
const env2 = await resolveEnvironmentStatus(
|
|
379961
|
+
client2,
|
|
379962
|
+
projectId,
|
|
379963
|
+
options2.environment
|
|
379964
|
+
);
|
|
379965
|
+
const latest = env2.latestDeployment ?? env2.inProgressDeployment ?? env2.activeDeployment;
|
|
379966
|
+
if (!latest) {
|
|
379967
|
+
throw new Error(
|
|
379968
|
+
`No deployments found for environment "${env2.name}".`
|
|
379969
|
+
);
|
|
379970
|
+
}
|
|
379971
|
+
deploymentId = latest.id;
|
|
379972
|
+
}
|
|
379973
|
+
const deployment = await client2.getDeployment(deploymentId);
|
|
379974
|
+
if (format === "json") {
|
|
379975
|
+
const payload = includeOutput ? deployment : stripOutput(deployment);
|
|
379976
|
+
process.stdout.write(JSON.stringify(payload, null, 2) + "\n");
|
|
379977
|
+
return;
|
|
379978
|
+
}
|
|
379979
|
+
process.stdout.write(renderDeployment(deployment, includeOutput));
|
|
379980
|
+
} catch (err) {
|
|
379981
|
+
const message = err instanceof Error ? err.message : "Unknown error occurred";
|
|
379982
|
+
console.error(`Error: ${message}`);
|
|
379983
|
+
process.exit(1);
|
|
379984
|
+
}
|
|
379985
|
+
}
|
|
379986
|
+
function resolveOutputFormat2(format) {
|
|
379987
|
+
if (!format) {
|
|
379988
|
+
return isInteractive() ? "text" : "json";
|
|
379989
|
+
}
|
|
379990
|
+
if (format === "text" || format === "json") {
|
|
379991
|
+
return format;
|
|
379992
|
+
}
|
|
379993
|
+
throw new Error(`Invalid format "${format}". Expected one of: text, json.`);
|
|
379994
|
+
}
|
|
379995
|
+
function omitOutput(value) {
|
|
379996
|
+
const copy = { ...value };
|
|
379997
|
+
delete copy.output;
|
|
379998
|
+
return copy;
|
|
379999
|
+
}
|
|
380000
|
+
function stripOutput(deployment) {
|
|
380001
|
+
const stripped = { ...deployment };
|
|
380002
|
+
if (stripped.error) {
|
|
380003
|
+
stripped.error = omitOutput(stripped.error);
|
|
380004
|
+
}
|
|
380005
|
+
if (stripped.builds) {
|
|
380006
|
+
stripped.builds = stripped.builds.map(omitOutput);
|
|
380007
|
+
}
|
|
380008
|
+
if (stripped.pendingActions) {
|
|
380009
|
+
stripped.pendingActions = stripped.pendingActions.map(
|
|
380010
|
+
(a) => a.type === "build_failed" ? omitOutput(a) : a
|
|
380011
|
+
);
|
|
380012
|
+
}
|
|
380013
|
+
return stripped;
|
|
380014
|
+
}
|
|
380015
|
+
function stateColor(state) {
|
|
380016
|
+
switch (state) {
|
|
380017
|
+
case "active":
|
|
380018
|
+
case "completed":
|
|
380019
|
+
return ANSI2.green;
|
|
380020
|
+
case "failed":
|
|
380021
|
+
case "cancelled":
|
|
380022
|
+
return ANSI2.red;
|
|
380023
|
+
case "superseded":
|
|
380024
|
+
return ANSI2.dim;
|
|
380025
|
+
default:
|
|
380026
|
+
return ANSI2.yellow;
|
|
380027
|
+
}
|
|
380028
|
+
}
|
|
380029
|
+
function colored(color, text) {
|
|
380030
|
+
return `${color}${text}${ANSI2.reset}`;
|
|
380031
|
+
}
|
|
380032
|
+
function formatDuration(startIso, endIso) {
|
|
380033
|
+
const ms = new Date(endIso).getTime() - new Date(startIso).getTime();
|
|
380034
|
+
if (!Number.isFinite(ms) || ms < 0) return null;
|
|
380035
|
+
const totalSeconds = Math.round(ms / 1e3);
|
|
380036
|
+
const minutes = Math.floor(totalSeconds / 60);
|
|
380037
|
+
const seconds = totalSeconds % 60;
|
|
380038
|
+
return minutes > 0 ? `${minutes}m ${seconds}s` : `${seconds}s`;
|
|
380039
|
+
}
|
|
380040
|
+
function formatBytes3(bytes) {
|
|
380041
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
380042
|
+
return `${(bytes / 1024).toFixed(1)} KB`;
|
|
380043
|
+
}
|
|
380044
|
+
function renderDeployment(deployment, includeOutput) {
|
|
380045
|
+
const lines = [];
|
|
380046
|
+
lines.push(
|
|
380047
|
+
`${ANSI2.bold}Deployment ${deployment.id}${ANSI2.reset} ${ANSI2.dim}(${deployment.environment})${ANSI2.reset}`
|
|
380048
|
+
);
|
|
380049
|
+
lines.push(
|
|
380050
|
+
` State: ${colored(stateColor(deployment.state), deployment.state)}`
|
|
380051
|
+
);
|
|
380052
|
+
if (deployment.stateMessage) {
|
|
380053
|
+
lines.push(` Message: ${deployment.stateMessage}`);
|
|
380054
|
+
}
|
|
380055
|
+
if (deployment.triggeredBy) {
|
|
380056
|
+
lines.push(` Triggered: ${deployment.triggeredBy}`);
|
|
380057
|
+
}
|
|
380058
|
+
if (deployment.gitCommitSha) {
|
|
380059
|
+
lines.push(` Commit: ${deployment.gitCommitSha}`);
|
|
380060
|
+
}
|
|
380061
|
+
lines.push(` Created: ${deployment.createdAt}`);
|
|
380062
|
+
if (deployment.completedAt) {
|
|
380063
|
+
const duration = deployment.startedAt ? formatDuration(deployment.startedAt, deployment.completedAt) : null;
|
|
380064
|
+
lines.push(
|
|
380065
|
+
` Completed: ${deployment.completedAt}${duration ? ` ${ANSI2.dim}(took ${duration})${ANSI2.reset}` : ""}`
|
|
380066
|
+
);
|
|
380067
|
+
}
|
|
380068
|
+
const builds = deployment.builds ?? [];
|
|
380069
|
+
if (builds.length > 0) {
|
|
380070
|
+
lines.push("");
|
|
380071
|
+
lines.push(`${ANSI2.bold}Builds${ANSI2.reset}`);
|
|
380072
|
+
const nameWidth = Math.max(...builds.map((b) => b.name.length));
|
|
380073
|
+
for (const build of builds) {
|
|
380074
|
+
const marker = build.state === "failed" ? "\u2716" : build.state === "completed" ? "\u2714" : "\u2026";
|
|
380075
|
+
let line = ` ${colored(stateColor(build.state), marker)} ${build.name.padEnd(nameWidth)} ${colored(stateColor(build.state), build.state)}`;
|
|
380076
|
+
if (build.state === "failed" && build.error) {
|
|
380077
|
+
line += ` ${ANSI2.dim}\u2014 ${build.error}${ANSI2.reset}`;
|
|
380078
|
+
}
|
|
380079
|
+
lines.push(line);
|
|
380080
|
+
}
|
|
380081
|
+
}
|
|
380082
|
+
const pendingActions = deployment.pendingActions ?? [];
|
|
380083
|
+
const missingSecrets = pendingActions.flatMap(
|
|
380084
|
+
(a) => a.type === "missing_secrets" ? a.secrets : []
|
|
380085
|
+
);
|
|
380086
|
+
const missingConfigs = pendingActions.flatMap(
|
|
380087
|
+
(a) => a.type === "missing_configs" ? a.configs : []
|
|
380088
|
+
);
|
|
380089
|
+
if (missingSecrets.length > 0 || missingConfigs.length > 0) {
|
|
380090
|
+
lines.push("");
|
|
380091
|
+
lines.push(colored(ANSI2.yellow, `${ANSI2.bold}Action required${ANSI2.reset}`));
|
|
380092
|
+
if (missingSecrets.length > 0) {
|
|
380093
|
+
lines.push(` Missing secrets: ${missingSecrets.join(", ")}`);
|
|
380094
|
+
}
|
|
380095
|
+
if (missingConfigs.length > 0) {
|
|
380096
|
+
lines.push(` Missing configs: ${missingConfigs.join(", ")}`);
|
|
380097
|
+
}
|
|
380098
|
+
}
|
|
380099
|
+
if (deployment.error) {
|
|
380100
|
+
lines.push("");
|
|
380101
|
+
lines.push(
|
|
380102
|
+
colored(
|
|
380103
|
+
ANSI2.red,
|
|
380104
|
+
`${ANSI2.bold}${formatErrorCode(deployment.error.code)}:${ANSI2.reset}${ANSI2.red} ${deployment.error.message}`
|
|
380105
|
+
)
|
|
380106
|
+
);
|
|
380107
|
+
if (deployment.error.resource) {
|
|
380108
|
+
lines.push(`${ANSI2.dim}Resource: ${deployment.error.resource}${ANSI2.reset}`);
|
|
380109
|
+
}
|
|
380110
|
+
}
|
|
380111
|
+
const buildsWithOutput = builds.filter(
|
|
380112
|
+
(b) => typeof b.output === "string" && b.output.length > 0
|
|
380113
|
+
);
|
|
380114
|
+
const extraErrorOutput = deployment.error?.output && !buildsWithOutput.some((b) => b.output === deployment.error?.output) ? deployment.error.output : null;
|
|
380115
|
+
if (includeOutput) {
|
|
380116
|
+
const ordered = [...buildsWithOutput].sort(
|
|
380117
|
+
(a, b) => Number(b.state === "failed") - Number(a.state === "failed")
|
|
380118
|
+
);
|
|
380119
|
+
for (const build of ordered) {
|
|
380120
|
+
lines.push("");
|
|
380121
|
+
lines.push(`${ANSI2.bold}Output (${build.name})${ANSI2.reset}`);
|
|
380122
|
+
lines.push(build.output.trimEnd());
|
|
380123
|
+
}
|
|
380124
|
+
if (extraErrorOutput) {
|
|
380125
|
+
lines.push("");
|
|
380126
|
+
lines.push(`${ANSI2.bold}Output${ANSI2.reset}`);
|
|
380127
|
+
lines.push(extraErrorOutput.trimEnd());
|
|
380128
|
+
}
|
|
380129
|
+
} else {
|
|
380130
|
+
const totalBytes = buildsWithOutput.reduce((sum, b) => sum + b.output.length, 0) + (extraErrorOutput?.length ?? 0);
|
|
380131
|
+
if (totalBytes > 0) {
|
|
380132
|
+
lines.push("");
|
|
380133
|
+
lines.push(
|
|
380134
|
+
`${ANSI2.dim}Output hidden (${formatBytes3(totalBytes)}). Run \`specific deployment show ${deployment.id} --output\` to print it.${ANSI2.reset}`
|
|
380135
|
+
);
|
|
380136
|
+
}
|
|
380137
|
+
}
|
|
380138
|
+
return lines.join("\n") + "\n";
|
|
380139
|
+
}
|
|
380140
|
+
|
|
379753
380141
|
// src/lib/domain/format-dns-instructions.ts
|
|
379754
380142
|
function isApex(domain) {
|
|
379755
380143
|
return domain.split(".").length === 2;
|
|
@@ -379830,7 +380218,7 @@ async function domainAddCommand(service, domain, options2) {
|
|
|
379830
380218
|
var program = new Command();
|
|
379831
380219
|
var env = "production";
|
|
379832
380220
|
var envLabel = env !== "production" ? `[${env.toUpperCase()}] ` : "";
|
|
379833
|
-
program.name("specific").description(`${envLabel}Infrastructure-as-code for coding agents`).version("0.1.
|
|
380221
|
+
program.name("specific").description(`${envLabel}Infrastructure-as-code for coding agents`).version("0.1.184").enablePositionalOptions();
|
|
379834
380222
|
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
380223
|
Examples:
|
|
379836
380224
|
$ specific init
|
|
@@ -379843,13 +380231,14 @@ Examples:
|
|
|
379843
380231
|
program.command("check").description("Validate specific.hcl configuration").addHelpText("after", `
|
|
379844
380232
|
Examples:
|
|
379845
380233
|
$ specific check`).action(checkCommand);
|
|
379846
|
-
program.command("dev").description("Start local development environment").option("-k, --key <key>", "Namespace for isolated dev environment (auto-detected from git worktree if not specified)").option("--tunnel", "Expose public services via public *.tunnel.spcf.app URLs").addHelpText("after", `
|
|
380234
|
+
program.command("dev").description("Start local development environment").option("-k, --key <key>", "Namespace for isolated dev environment (auto-detected from git worktree if not specified)").option("--tunnel", "Expose public services via public *.tunnel.spcf.app URLs").option("--services <services...>", "Only start these services and everything they depend on").addHelpText("after", `
|
|
379847
380235
|
Examples:
|
|
379848
380236
|
$ specific dev
|
|
380237
|
+
$ specific dev --services api worker
|
|
379849
380238
|
$ specific dev --tunnel
|
|
379850
380239
|
$ specific dev --key feature-branch`).action((options2) => {
|
|
379851
380240
|
const key = options2.key ?? getDefaultKey();
|
|
379852
|
-
devCommand(key, options2.tunnel ?? false);
|
|
380241
|
+
devCommand(key, options2.tunnel ?? false, options2.services ?? []);
|
|
379853
380242
|
});
|
|
379854
380243
|
program.command("deploy").description("Deploy to Specific infrastructure").option("--project <id>", "Project ID to deploy to (overrides .projectid file)").option("-e, --environment <name|id>", "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", `
|
|
379855
380244
|
Examples:
|
|
@@ -379912,9 +380301,28 @@ Examples:
|
|
|
379912
380301
|
$ specific domain add web example.com --environment staging`).action((service, domain, options2) => {
|
|
379913
380302
|
domainAddCommand(service, domain, options2);
|
|
379914
380303
|
});
|
|
379915
|
-
program.command("
|
|
380304
|
+
var deploymentCmd = program.command("deployment").description("Inspect deployments");
|
|
380305
|
+
deploymentCmd.command("show [id]").description(
|
|
380306
|
+
"Show a deployment's state, builds, and errors (defaults to the latest deployment)"
|
|
380307
|
+
).option("-e, --environment <name|id>", "Target environment (defaults to the current one; ignored when an ID is given)").option(
|
|
380308
|
+
"--format <text|json>",
|
|
380309
|
+
"Output format (defaults to text on a TTY, json when piped)"
|
|
380310
|
+
).option(
|
|
380311
|
+
"--output",
|
|
380312
|
+
"Include build output (logs); can be large, so it is hidden by default"
|
|
380313
|
+
).addHelpText("after", `
|
|
380314
|
+
Examples:
|
|
380315
|
+
$ specific deployment show
|
|
380316
|
+
$ specific deployment show dep_0abc123
|
|
380317
|
+
$ specific deployment show dep_0abc123 --output
|
|
380318
|
+
$ specific deployment show --environment staging
|
|
380319
|
+
$ specific deployment show -e preview-a1b2c3d4 --format json`).action((id, options2) => {
|
|
380320
|
+
deploymentShowCommand(id, options2);
|
|
380321
|
+
});
|
|
380322
|
+
program.command("status").description("Show project, environments, and deployment status").option("--previews", "Show detailed preview environment and GitHub PR preview configuration").addHelpText("after", `
|
|
379916
380323
|
Examples:
|
|
379917
|
-
$ specific status
|
|
380324
|
+
$ specific status
|
|
380325
|
+
$ specific status --previews`).action((options2) => statusCommand(options2));
|
|
379918
380326
|
program.command("query [sql]").description(
|
|
379919
380327
|
"Run a SQL query against your environment's observability data (ClickHouse), or a Postgres database with --db"
|
|
379920
380328
|
).option("-e, --environment <name|id>", "Target environment (defaults to the current one)").option(
|