@specific.dev/cli 0.1.100 → 0.1.102
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 +2 -2
- package/dist/admin/__next.!KGRlZmF1bHQp.txt +1 -1
- package/dist/admin/__next._full.txt +2 -2
- 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/_next/static/chunks/{1f8468aeb163fb80.js → 93d028b73685d80d.js} +1 -1
- package/dist/admin/_not-found/__next._full.txt +1 -1
- package/dist/admin/_not-found/__next._head.txt +1 -1
- package/dist/admin/_not-found/__next._index.txt +1 -1
- package/dist/admin/_not-found/__next._not-found.__PAGE__.txt +1 -1
- package/dist/admin/_not-found/__next._not-found.txt +1 -1
- package/dist/admin/_not-found/__next._tree.txt +1 -1
- package/dist/admin/_not-found/index.html +1 -1
- package/dist/admin/_not-found/index.txt +1 -1
- package/dist/admin/databases/__next.!KGRlZmF1bHQp.databases.__PAGE__.txt +1 -1
- package/dist/admin/databases/__next.!KGRlZmF1bHQp.databases.txt +1 -1
- package/dist/admin/databases/__next.!KGRlZmF1bHQp.txt +1 -1
- package/dist/admin/databases/__next._full.txt +1 -1
- package/dist/admin/databases/__next._head.txt +1 -1
- package/dist/admin/databases/__next._index.txt +1 -1
- package/dist/admin/databases/__next._tree.txt +1 -1
- package/dist/admin/databases/index.html +1 -1
- package/dist/admin/databases/index.txt +1 -1
- package/dist/admin/fullscreen/__next._full.txt +2 -2
- 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 +2 -2
- 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 +2 -2
- package/dist/admin/index.html +1 -1
- package/dist/admin/index.txt +2 -2
- package/dist/admin/mail/__next.!KGRlZmF1bHQp.mail.__PAGE__.txt +1 -1
- package/dist/admin/mail/__next.!KGRlZmF1bHQp.mail.txt +1 -1
- package/dist/admin/mail/__next.!KGRlZmF1bHQp.txt +1 -1
- package/dist/admin/mail/__next._full.txt +1 -1
- package/dist/admin/mail/__next._head.txt +1 -1
- package/dist/admin/mail/__next._index.txt +1 -1
- package/dist/admin/mail/__next._tree.txt +1 -1
- package/dist/admin/mail/index.html +1 -1
- package/dist/admin/mail/index.txt +1 -1
- package/dist/admin/workflows/__next.!KGRlZmF1bHQp.txt +1 -1
- package/dist/admin/workflows/__next.!KGRlZmF1bHQp.workflows.__PAGE__.txt +1 -1
- package/dist/admin/workflows/__next.!KGRlZmF1bHQp.workflows.txt +1 -1
- package/dist/admin/workflows/__next._full.txt +1 -1
- package/dist/admin/workflows/__next._head.txt +1 -1
- package/dist/admin/workflows/__next._index.txt +1 -1
- package/dist/admin/workflows/__next._tree.txt +1 -1
- package/dist/admin/workflows/index.html +1 -1
- package/dist/admin/workflows/index.txt +1 -1
- package/dist/cli.js +323 -29
- package/package.json +1 -1
- /package/dist/admin/_next/static/{BOm2VNloUXzWE8sN1MOhy → t6Nv9BcBf3NfJnds5W8MU}/_buildManifest.js +0 -0
- /package/dist/admin/_next/static/{BOm2VNloUXzWE8sN1MOhy → t6Nv9BcBf3NfJnds5W8MU}/_clientMiddlewareManifest.json +0 -0
- /package/dist/admin/_next/static/{BOm2VNloUXzWE8sN1MOhy → t6Nv9BcBf3NfJnds5W8MU}/_ssgManifest.js +0 -0
package/dist/cli.js
CHANGED
|
@@ -368427,6 +368427,9 @@ function parseServices(serviceData) {
|
|
|
368427
368427
|
if (fieldObj.command) {
|
|
368428
368428
|
service.command = String(fieldObj.command);
|
|
368429
368429
|
}
|
|
368430
|
+
if (service.build && !service.command) {
|
|
368431
|
+
throw new Error(`Service "${name}" has a "build" but no "command". Services with a build must specify a command to run.`);
|
|
368432
|
+
}
|
|
368430
368433
|
if (fieldObj.root) {
|
|
368431
368434
|
service.root = String(fieldObj.root);
|
|
368432
368435
|
}
|
|
@@ -368445,9 +368448,6 @@ function parseServices(serviceData) {
|
|
|
368445
368448
|
} else {
|
|
368446
368449
|
service.endpoints = endpoints;
|
|
368447
368450
|
}
|
|
368448
|
-
if (fieldObj.serve) {
|
|
368449
|
-
service.serve = String(fieldObj.serve);
|
|
368450
|
-
}
|
|
368451
368451
|
const env2 = parseEnv(fieldObj.env);
|
|
368452
368452
|
if (env2) {
|
|
368453
368453
|
service.env = env2;
|
|
@@ -372273,7 +372273,6 @@ Add them to the config block in specific.local`
|
|
|
372273
372273
|
const serviceEndpoints = /* @__PURE__ */ new Map();
|
|
372274
372274
|
const serviceEndpointPorts = /* @__PURE__ */ new Map();
|
|
372275
372275
|
for (const service of config.services) {
|
|
372276
|
-
if (service.serve) continue;
|
|
372277
372276
|
if (!service.command && !service.dev?.command) continue;
|
|
372278
372277
|
const endpointPorts = /* @__PURE__ */ new Map();
|
|
372279
372278
|
const endpointInfos = [];
|
|
@@ -372347,9 +372346,6 @@ Add them to the config block in specific.local`
|
|
|
372347
372346
|
};
|
|
372348
372347
|
for (const service of config.services) {
|
|
372349
372348
|
if (this.cancelled) break;
|
|
372350
|
-
if (service.serve) {
|
|
372351
|
-
continue;
|
|
372352
|
-
}
|
|
372353
372349
|
if (!service.command && !service.dev?.command) {
|
|
372354
372350
|
continue;
|
|
372355
372351
|
}
|
|
@@ -372400,7 +372396,6 @@ Add them to the config block in specific.local`
|
|
|
372400
372396
|
await Promise.all(this.services.map((s) => s.stop()));
|
|
372401
372397
|
const newServices = [];
|
|
372402
372398
|
for (const service of config.services) {
|
|
372403
|
-
if (service.serve) continue;
|
|
372404
372399
|
if (!service.command && !service.dev?.command) continue;
|
|
372405
372400
|
try {
|
|
372406
372401
|
const endpointPorts = serviceEndpointPorts.get(service.name) || /* @__PURE__ */ new Map();
|
|
@@ -372519,13 +372514,13 @@ Add them to the config block in specific.local`
|
|
|
372519
372514
|
const getState = () => ({
|
|
372520
372515
|
status: "running",
|
|
372521
372516
|
services: config.services.filter(
|
|
372522
|
-
(svc) => runningServicePorts.has(svc.name)
|
|
372517
|
+
(svc) => runningServicePorts.has(svc.name)
|
|
372523
372518
|
).map((svc) => {
|
|
372524
372519
|
const build = svc.build ? buildsByName.get(svc.build.name) : void 0;
|
|
372525
372520
|
return {
|
|
372526
372521
|
name: svc.name,
|
|
372527
372522
|
port: runningServicePorts.get(svc.name),
|
|
372528
|
-
exposed:
|
|
372523
|
+
exposed: svc.endpoints.some((e) => e.public),
|
|
372529
372524
|
env: build?.env ? { ...build.env, ...svc.env } : svc.env
|
|
372530
372525
|
};
|
|
372531
372526
|
}),
|
|
@@ -373038,7 +373033,7 @@ function trackEvent(event, properties) {
|
|
|
373038
373033
|
event,
|
|
373039
373034
|
properties: {
|
|
373040
373035
|
...properties,
|
|
373041
|
-
cli_version: "0.1.
|
|
373036
|
+
cli_version: "0.1.102",
|
|
373042
373037
|
platform: process.platform,
|
|
373043
373038
|
node_version: process.version,
|
|
373044
373039
|
project_id: getProjectId()
|
|
@@ -373883,6 +373878,9 @@ function parseServices2(serviceData) {
|
|
|
373883
373878
|
if (fieldObj.command) {
|
|
373884
373879
|
service.command = String(fieldObj.command);
|
|
373885
373880
|
}
|
|
373881
|
+
if (service.build && !service.command) {
|
|
373882
|
+
throw new Error(`Service "${name}" has a "build" but no "command". Services with a build must specify a command to run.`);
|
|
373883
|
+
}
|
|
373886
373884
|
if (fieldObj.root) {
|
|
373887
373885
|
service.root = String(fieldObj.root);
|
|
373888
373886
|
}
|
|
@@ -373901,9 +373899,6 @@ function parseServices2(serviceData) {
|
|
|
373901
373899
|
} else {
|
|
373902
373900
|
service.endpoints = endpoints;
|
|
373903
373901
|
}
|
|
373904
|
-
if (fieldObj.serve) {
|
|
373905
|
-
service.serve = String(fieldObj.serve);
|
|
373906
|
-
}
|
|
373907
373902
|
const env2 = parseEnv2(fieldObj.env);
|
|
373908
373903
|
if (env2) {
|
|
373909
373904
|
service.env = env2;
|
|
@@ -375671,7 +375666,185 @@ function DeployUI({ environment, config }) {
|
|
|
375671
375666
|
}
|
|
375672
375667
|
), phase === "error" && /* @__PURE__ */ React7.createElement(Box7, { marginTop: 1, flexDirection: "column" }, deployment?.error ? /* @__PURE__ */ React7.createElement(StructuredError, { error: deployment.error }) : /* @__PURE__ */ React7.createElement(Text7, { color: "red", bold: true }, error), buildOutput && /* @__PURE__ */ React7.createElement(Box7, { flexDirection: "column", marginTop: 1 }, /* @__PURE__ */ React7.createElement(Text7, { bold: true }, "Build output:"), /* @__PURE__ */ React7.createElement(Text7, null, buildOutput))), phase === "success" && /* @__PURE__ */ React7.createElement(Box7, { marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React7.createElement(Text7, { color: "green" }, "Deployment successful!"), deployment?.publicUrls && Object.keys(deployment.publicUrls).length > 0 && /* @__PURE__ */ React7.createElement(Box7, { marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React7.createElement(Text7, { bold: true }, "Public URLs:"), Object.entries(deployment.publicUrls).map(([name, url]) => /* @__PURE__ */ React7.createElement(Text7, { key: name }, " ", name, ": ", /* @__PURE__ */ React7.createElement(Text7, { color: "cyan" }, url))))));
|
|
375673
375668
|
}
|
|
375674
|
-
async function
|
|
375669
|
+
async function runDeployPipeline(options2) {
|
|
375670
|
+
const { config } = options2;
|
|
375671
|
+
const projectDir = process.cwd();
|
|
375672
|
+
let token;
|
|
375673
|
+
try {
|
|
375674
|
+
token = await getValidAccessToken();
|
|
375675
|
+
} catch {
|
|
375676
|
+
console.error("Error: Not logged in.\n Run 'specific login' first.");
|
|
375677
|
+
process.exit(1);
|
|
375678
|
+
}
|
|
375679
|
+
const client2 = new SpecificClient({ accessToken: token });
|
|
375680
|
+
let projectId = options2.projectId;
|
|
375681
|
+
if (!projectId) {
|
|
375682
|
+
if (hasProjectId(projectDir)) {
|
|
375683
|
+
projectId = readProjectId(projectDir);
|
|
375684
|
+
} else {
|
|
375685
|
+
console.error(
|
|
375686
|
+
"Error: No project linked.\n Create one first: specific project new <name> [--org <org-id>]\n Or pass one directly: specific deploy --project <project-id>"
|
|
375687
|
+
);
|
|
375688
|
+
process.exit(1);
|
|
375689
|
+
}
|
|
375690
|
+
} else if (!hasProjectId(projectDir)) {
|
|
375691
|
+
writeProjectId(projectId);
|
|
375692
|
+
}
|
|
375693
|
+
const parsedSecrets = {};
|
|
375694
|
+
if (options2.secrets) {
|
|
375695
|
+
for (const s of options2.secrets) {
|
|
375696
|
+
const eqIndex = s.indexOf("=");
|
|
375697
|
+
if (eqIndex > 0) {
|
|
375698
|
+
parsedSecrets[s.substring(0, eqIndex)] = s.substring(eqIndex + 1);
|
|
375699
|
+
}
|
|
375700
|
+
}
|
|
375701
|
+
}
|
|
375702
|
+
const parsedConfigs = {};
|
|
375703
|
+
if (options2.configs) {
|
|
375704
|
+
for (const c of options2.configs) {
|
|
375705
|
+
const eqIndex = c.indexOf("=");
|
|
375706
|
+
if (eqIndex > 0) {
|
|
375707
|
+
parsedConfigs[c.substring(0, eqIndex)] = c.substring(eqIndex + 1);
|
|
375708
|
+
}
|
|
375709
|
+
}
|
|
375710
|
+
}
|
|
375711
|
+
console.log("Creating archive...");
|
|
375712
|
+
let tarball;
|
|
375713
|
+
let appPath;
|
|
375714
|
+
try {
|
|
375715
|
+
const result = await createTarball(config, projectDir);
|
|
375716
|
+
tarball = result.tarball;
|
|
375717
|
+
appPath = result.appPath;
|
|
375718
|
+
} catch (err) {
|
|
375719
|
+
console.error(`Error: Failed to create archive: ${err instanceof Error ? err.message : String(err)}`);
|
|
375720
|
+
process.exit(1);
|
|
375721
|
+
}
|
|
375722
|
+
console.log("Creating deployment...");
|
|
375723
|
+
let deployment;
|
|
375724
|
+
try {
|
|
375725
|
+
deployment = await client2.createDeployment(projectId, "prod");
|
|
375726
|
+
} catch (err) {
|
|
375727
|
+
console.error(`Error: Failed to create deployment: ${err instanceof Error ? err.message : String(err)}`);
|
|
375728
|
+
process.exit(1);
|
|
375729
|
+
}
|
|
375730
|
+
console.log("Uploading...");
|
|
375731
|
+
try {
|
|
375732
|
+
deployment = await client2.uploadTarball(deployment.id, tarball, appPath);
|
|
375733
|
+
} catch (err) {
|
|
375734
|
+
console.error(`Error: Failed to upload: ${err instanceof Error ? err.message : String(err)}`);
|
|
375735
|
+
process.exit(1);
|
|
375736
|
+
}
|
|
375737
|
+
console.log("Waiting for build...");
|
|
375738
|
+
while (true) {
|
|
375739
|
+
await new Promise((resolve9) => setTimeout(resolve9, 2e3));
|
|
375740
|
+
const status = await client2.getDeployment(deployment.id);
|
|
375741
|
+
deployment = status;
|
|
375742
|
+
if (status.state === "failed") {
|
|
375743
|
+
console.error(`Error: Deployment failed: ${status.stateMessage || "Unknown error"}`);
|
|
375744
|
+
process.exit(1);
|
|
375745
|
+
}
|
|
375746
|
+
const failedBuild = getFailedBuild(status.pendingActions);
|
|
375747
|
+
if (failedBuild && failedBuild.type === "build_failed") {
|
|
375748
|
+
console.error(`Error: Build "${failedBuild.serviceName}" failed: ${failedBuild.error}`);
|
|
375749
|
+
if (failedBuild.output) {
|
|
375750
|
+
console.error(`
|
|
375751
|
+
Build output:
|
|
375752
|
+
${failedBuild.output}`);
|
|
375753
|
+
}
|
|
375754
|
+
process.exit(1);
|
|
375755
|
+
}
|
|
375756
|
+
const missingSecrets = getMissingSecrets(status.pendingActions);
|
|
375757
|
+
if (missingSecrets.length > 0) {
|
|
375758
|
+
const secretsToSubmit = {};
|
|
375759
|
+
const stillMissing = [];
|
|
375760
|
+
for (const name of missingSecrets) {
|
|
375761
|
+
if (parsedSecrets[name] !== void 0) {
|
|
375762
|
+
secretsToSubmit[name] = parsedSecrets[name];
|
|
375763
|
+
} else {
|
|
375764
|
+
stillMissing.push(name);
|
|
375765
|
+
}
|
|
375766
|
+
}
|
|
375767
|
+
if (stillMissing.length > 0) {
|
|
375768
|
+
console.error(
|
|
375769
|
+
`Error: Missing required secrets: ${stillMissing.join(", ")}
|
|
375770
|
+
${stillMissing.map((s) => `specific deploy --secret ${s}=<value>`).join(" ")}`
|
|
375771
|
+
);
|
|
375772
|
+
process.exit(1);
|
|
375773
|
+
}
|
|
375774
|
+
await client2.submitSecrets(deployment.id, secretsToSubmit);
|
|
375775
|
+
continue;
|
|
375776
|
+
}
|
|
375777
|
+
const missingConfigs = getMissingConfigs(status.pendingActions);
|
|
375778
|
+
if (missingConfigs.length > 0) {
|
|
375779
|
+
const configsToSubmit = {};
|
|
375780
|
+
const stillMissing = [];
|
|
375781
|
+
for (const name of missingConfigs) {
|
|
375782
|
+
if (parsedConfigs[name] !== void 0) {
|
|
375783
|
+
configsToSubmit[name] = parsedConfigs[name];
|
|
375784
|
+
} else {
|
|
375785
|
+
stillMissing.push(name);
|
|
375786
|
+
}
|
|
375787
|
+
}
|
|
375788
|
+
if (stillMissing.length > 0) {
|
|
375789
|
+
console.error(
|
|
375790
|
+
`Error: Missing required configs: ${stillMissing.join(", ")}
|
|
375791
|
+
${stillMissing.map((c) => `specific deploy --config ${c}=<value>`).join(" ")}`
|
|
375792
|
+
);
|
|
375793
|
+
process.exit(1);
|
|
375794
|
+
}
|
|
375795
|
+
await client2.submitConfigs(deployment.id, configsToSubmit);
|
|
375796
|
+
continue;
|
|
375797
|
+
}
|
|
375798
|
+
if (hasBuildsInProgress(status.pendingActions)) {
|
|
375799
|
+
continue;
|
|
375800
|
+
}
|
|
375801
|
+
if (!status.pendingActions || status.pendingActions.length === 0) {
|
|
375802
|
+
break;
|
|
375803
|
+
}
|
|
375804
|
+
}
|
|
375805
|
+
console.log("Starting deployment...");
|
|
375806
|
+
try {
|
|
375807
|
+
await client2.startDeployment(deployment.id);
|
|
375808
|
+
} catch (err) {
|
|
375809
|
+
console.error(`Error: Failed to start deployment: ${err instanceof Error ? err.message : String(err)}`);
|
|
375810
|
+
process.exit(1);
|
|
375811
|
+
}
|
|
375812
|
+
console.log("Deploying...");
|
|
375813
|
+
while (true) {
|
|
375814
|
+
await new Promise((resolve9) => setTimeout(resolve9, 2e3));
|
|
375815
|
+
const status = await client2.getDeployment(deployment.id);
|
|
375816
|
+
deployment = status;
|
|
375817
|
+
if (status.state === "failed") {
|
|
375818
|
+
if (status.error) {
|
|
375819
|
+
console.error(`Error: ${formatErrorCode(status.error.code)}: ${status.error.message}`);
|
|
375820
|
+
if (status.error.output) {
|
|
375821
|
+
console.error(`
|
|
375822
|
+
Output:
|
|
375823
|
+
${status.error.output}`);
|
|
375824
|
+
}
|
|
375825
|
+
} else {
|
|
375826
|
+
console.error(`Error: Deployment failed: ${status.stateMessage || "Unknown error"}`);
|
|
375827
|
+
}
|
|
375828
|
+
process.exit(1);
|
|
375829
|
+
}
|
|
375830
|
+
if (status.state === "active") {
|
|
375831
|
+
deployment = status;
|
|
375832
|
+
break;
|
|
375833
|
+
}
|
|
375834
|
+
}
|
|
375835
|
+
console.log("");
|
|
375836
|
+
console.log(`\u2713 Deployed successfully`);
|
|
375837
|
+
console.log("");
|
|
375838
|
+
console.log(` deployment: ${deployment.id}`);
|
|
375839
|
+
if (deployment.publicUrls && Object.keys(deployment.publicUrls).length > 0) {
|
|
375840
|
+
console.log("");
|
|
375841
|
+
console.log(" URLs:");
|
|
375842
|
+
for (const [name, url] of Object.entries(deployment.publicUrls)) {
|
|
375843
|
+
console.log(` ${name}: ${url}`);
|
|
375844
|
+
}
|
|
375845
|
+
}
|
|
375846
|
+
}
|
|
375847
|
+
async function deployCommand(options2) {
|
|
375675
375848
|
const configPath = path19.join(process.cwd(), "specific.hcl");
|
|
375676
375849
|
if (!fs21.existsSync(configPath)) {
|
|
375677
375850
|
console.error("Error: No specific.hcl found in current directory");
|
|
@@ -375685,12 +375858,21 @@ async function deployCommand(environment) {
|
|
|
375685
375858
|
console.error(formatConfigError(err, hcl, configPath));
|
|
375686
375859
|
process.exit(1);
|
|
375687
375860
|
}
|
|
375688
|
-
const
|
|
375861
|
+
const hasNonInteractiveFlags = options2.project || options2.secret?.length || options2.config?.length;
|
|
375862
|
+
if (!isInteractive() || hasNonInteractiveFlags) {
|
|
375863
|
+
await runDeployPipeline({
|
|
375864
|
+
config,
|
|
375865
|
+
projectId: options2.project,
|
|
375866
|
+
secrets: options2.secret,
|
|
375867
|
+
configs: options2.config
|
|
375868
|
+
});
|
|
375869
|
+
return;
|
|
375870
|
+
}
|
|
375689
375871
|
render5(
|
|
375690
375872
|
/* @__PURE__ */ React7.createElement(
|
|
375691
375873
|
DeployUI,
|
|
375692
375874
|
{
|
|
375693
|
-
environment:
|
|
375875
|
+
environment: "prod",
|
|
375694
375876
|
config
|
|
375695
375877
|
}
|
|
375696
375878
|
)
|
|
@@ -376530,7 +376712,7 @@ function compareVersions(a, b) {
|
|
|
376530
376712
|
return 0;
|
|
376531
376713
|
}
|
|
376532
376714
|
async function checkForUpdate() {
|
|
376533
|
-
const currentVersion = "0.1.
|
|
376715
|
+
const currentVersion = "0.1.102";
|
|
376534
376716
|
const response = await fetch(`${BINARIES_BASE_URL}/latest?t=${Date.now()}`);
|
|
376535
376717
|
if (!response.ok) {
|
|
376536
376718
|
throw new Error(`Failed to check for updates: HTTP ${response.status}`);
|
|
@@ -376725,38 +376907,150 @@ function updateCommand() {
|
|
|
376725
376907
|
render9(/* @__PURE__ */ React11.createElement(UpdateUI, null));
|
|
376726
376908
|
}
|
|
376727
376909
|
|
|
376910
|
+
// src/commands/project.tsx
|
|
376911
|
+
async function projectNewCommand(name, options2) {
|
|
376912
|
+
try {
|
|
376913
|
+
const token = await getValidAccessToken();
|
|
376914
|
+
const client2 = new SpecificClient({ accessToken: token });
|
|
376915
|
+
let orgId = options2.org;
|
|
376916
|
+
if (!orgId) {
|
|
376917
|
+
const organizations = await client2.listOrganizations();
|
|
376918
|
+
if (organizations.length === 0) {
|
|
376919
|
+
console.error(
|
|
376920
|
+
"You don't belong to any organizations. Please create one first."
|
|
376921
|
+
);
|
|
376922
|
+
process.exit(1);
|
|
376923
|
+
}
|
|
376924
|
+
if (organizations.length === 1) {
|
|
376925
|
+
orgId = organizations[0].id;
|
|
376926
|
+
} else {
|
|
376927
|
+
console.error(
|
|
376928
|
+
"You belong to multiple organizations. Please specify one with --org.\n"
|
|
376929
|
+
);
|
|
376930
|
+
console.error("Available organizations:");
|
|
376931
|
+
for (const org of organizations) {
|
|
376932
|
+
console.error(` ${org.name} (${org.id})`);
|
|
376933
|
+
}
|
|
376934
|
+
process.exit(1);
|
|
376935
|
+
}
|
|
376936
|
+
}
|
|
376937
|
+
const project = await client2.createProject(name, orgId);
|
|
376938
|
+
console.log(`Project created: ${project.id}`);
|
|
376939
|
+
} catch (error) {
|
|
376940
|
+
const message = error instanceof Error ? error.message : "Unknown error occurred";
|
|
376941
|
+
console.error(`Failed to create project: ${message}`);
|
|
376942
|
+
process.exit(1);
|
|
376943
|
+
}
|
|
376944
|
+
}
|
|
376945
|
+
async function projectListCommand() {
|
|
376946
|
+
try {
|
|
376947
|
+
const token = await getValidAccessToken();
|
|
376948
|
+
const client2 = new SpecificClient({ accessToken: token });
|
|
376949
|
+
const [projects, organizations] = await Promise.all([
|
|
376950
|
+
client2.listProjects(),
|
|
376951
|
+
client2.listOrganizations()
|
|
376952
|
+
]);
|
|
376953
|
+
if (projects.length === 0) {
|
|
376954
|
+
console.log("No projects found.");
|
|
376955
|
+
return;
|
|
376956
|
+
}
|
|
376957
|
+
const orgMap = new Map(organizations.map((org) => [org.id, org.name]));
|
|
376958
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
376959
|
+
for (const project of projects) {
|
|
376960
|
+
const orgName = orgMap.get(project.organizationId) ?? project.organizationId;
|
|
376961
|
+
if (!grouped.has(orgName)) {
|
|
376962
|
+
grouped.set(orgName, []);
|
|
376963
|
+
}
|
|
376964
|
+
grouped.get(orgName).push({ id: project.id, name: project.name });
|
|
376965
|
+
}
|
|
376966
|
+
for (const [orgName, orgProjects] of grouped) {
|
|
376967
|
+
console.log(`${orgName}:`);
|
|
376968
|
+
for (const project of orgProjects) {
|
|
376969
|
+
console.log(` ${project.name} (${project.id})`);
|
|
376970
|
+
}
|
|
376971
|
+
}
|
|
376972
|
+
} catch (error) {
|
|
376973
|
+
const message = error instanceof Error ? error.message : "Unknown error occurred";
|
|
376974
|
+
console.error(`Failed to list projects: ${message}`);
|
|
376975
|
+
process.exit(1);
|
|
376976
|
+
}
|
|
376977
|
+
}
|
|
376978
|
+
|
|
376728
376979
|
// src/cli-program.tsx
|
|
376729
376980
|
var program = new Command();
|
|
376730
376981
|
var env = "production";
|
|
376731
376982
|
var envLabel = env !== "production" ? `[${env.toUpperCase()}] ` : "";
|
|
376732
|
-
program.name("specific").description(`${envLabel}Infrastructure-as-code for coding agents`).version("0.1.
|
|
376733
|
-
program.command("init").description("Initialize project for use with a coding agent").option("--agent <name...>", "Agents to configure (cursor, claude, codex, other)").
|
|
376734
|
-
|
|
376735
|
-
|
|
376736
|
-
|
|
376983
|
+
program.name("specific").description(`${envLabel}Infrastructure-as-code for coding agents`).version("0.1.102").enablePositionalOptions();
|
|
376984
|
+
program.command("init").description("Initialize project for use with a coding agent").option("--agent <name...>", "Agents to configure (cursor, claude, codex, other)").addHelpText("after", `
|
|
376985
|
+
Examples:
|
|
376986
|
+
$ specific init
|
|
376987
|
+
$ specific init --agent claude cursor`).action((options2) => initCommand(options2));
|
|
376988
|
+
program.command("docs [topic]").description("Fetch LLM-optimized documentation").addHelpText("after", `
|
|
376989
|
+
Examples:
|
|
376990
|
+
$ specific docs
|
|
376991
|
+
$ specific docs services
|
|
376992
|
+
$ specific docs postgres/reshape`).action(docsCommand);
|
|
376993
|
+
program.command("check").description("Validate specific.hcl configuration").addHelpText("after", `
|
|
376994
|
+
Examples:
|
|
376995
|
+
$ specific check`).action(checkCommand);
|
|
376996
|
+
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 localtunnel URLs").addHelpText("after", `
|
|
376997
|
+
Examples:
|
|
376998
|
+
$ specific dev
|
|
376999
|
+
$ specific dev --tunnel
|
|
377000
|
+
$ specific dev --key feature-branch`).action((options2) => {
|
|
376737
377001
|
const key = options2.key ?? getDefaultKey();
|
|
376738
377002
|
devCommand(key, options2.tunnel ?? false);
|
|
376739
377003
|
});
|
|
376740
|
-
program.command("deploy
|
|
376741
|
-
|
|
377004
|
+
program.command("deploy").description("Deploy to Specific infrastructure").option("--project <id>", "Project ID to deploy to (overrides .projectid file)").option("--secret <key=value...>", "Secret values (repeatable)").option("--config <key=value...>", "Config values (repeatable)").addHelpText("after", `
|
|
377005
|
+
Examples:
|
|
377006
|
+
$ specific deploy
|
|
377007
|
+
$ specific deploy --project proj_123
|
|
377008
|
+
$ specific deploy --secret db_url=postgres://... --config domain=app.com`).action((options2) => {
|
|
377009
|
+
deployCommand(options2);
|
|
376742
377010
|
});
|
|
376743
|
-
program.command("exec <service> [args...]").description("Run a one-off command with service environment").option("-k, --key <key>", "Dev environment namespace (auto-detected from git worktree if not specified)").passThroughOptions().
|
|
377011
|
+
program.command("exec <service> [args...]").description("Run a one-off command with service environment").option("-k, --key <key>", "Dev environment namespace (auto-detected from git worktree if not specified)").passThroughOptions().addHelpText("after", `
|
|
377012
|
+
Examples:
|
|
377013
|
+
$ specific exec api -- npm run migrate
|
|
377014
|
+
$ specific exec worker -- python manage.py shell`).action(async (service, args, options2) => {
|
|
376744
377015
|
const filteredArgs = args[0] === "--" ? args.slice(1) : args;
|
|
376745
377016
|
const key = options2.key ?? getDefaultKey();
|
|
376746
377017
|
await execCommand(service, filteredArgs, key);
|
|
376747
377018
|
});
|
|
376748
|
-
program.command("psql [database] [args...]").description("Connect to a Postgres database").option("-k, --key <key>", "Dev environment namespace (auto-detected from git worktree if not specified)").passThroughOptions().
|
|
377019
|
+
program.command("psql [database] [args...]").description("Connect to a Postgres database").option("-k, --key <key>", "Dev environment namespace (auto-detected from git worktree if not specified)").passThroughOptions().addHelpText("after", `
|
|
377020
|
+
Examples:
|
|
377021
|
+
$ specific psql
|
|
377022
|
+
$ specific psql mydb
|
|
377023
|
+
$ specific psql mydb -- -c "SELECT 1"`).action((database, args, options2) => {
|
|
376749
377024
|
const filteredArgs = args[0] === "--" ? args.slice(1) : args;
|
|
376750
377025
|
const key = options2.key ?? getDefaultKey();
|
|
376751
377026
|
psqlCommand(database, key, filteredArgs);
|
|
376752
377027
|
});
|
|
376753
|
-
program.command("reshape <action> [database]").description("Run Reshape migrations (start|complete|status|abort)").option("-k, --key <key>", "Dev environment namespace (auto-detected from git worktree if not specified)").
|
|
377028
|
+
program.command("reshape <action> [database]").description("Run Reshape migrations (start|complete|status|abort)").option("-k, --key <key>", "Dev environment namespace (auto-detected from git worktree if not specified)").addHelpText("after", `
|
|
377029
|
+
Examples:
|
|
377030
|
+
$ specific reshape start
|
|
377031
|
+
$ specific reshape complete
|
|
377032
|
+
$ specific reshape status mydb`).action((action, database, options2) => {
|
|
376754
377033
|
const key = options2.key ?? getDefaultKey();
|
|
376755
377034
|
reshapeCommand(action, database, key);
|
|
376756
377035
|
});
|
|
376757
|
-
program.command("clean").description("Remove .specific directory for a clean slate").option("-k, --key <key>", "Clean only the specified dev environment key").
|
|
377036
|
+
program.command("clean").description("Remove .specific directory for a clean slate").option("-k, --key <key>", "Clean only the specified dev environment key").addHelpText("after", `
|
|
377037
|
+
Examples:
|
|
377038
|
+
$ specific clean
|
|
377039
|
+
$ specific clean --key feature-branch`).action((options2) => {
|
|
376758
377040
|
cleanCommand(options2.key);
|
|
376759
377041
|
});
|
|
377042
|
+
var projectCmd = program.command("project").description("Manage projects");
|
|
377043
|
+
projectCmd.command("new <name>").description("Create a new project").option("--org <id>", "Organization ID (required if you belong to multiple organizations)").addHelpText("after", `
|
|
377044
|
+
Examples:
|
|
377045
|
+
$ specific project new my-app
|
|
377046
|
+
$ specific project new my-app --org org_123`).action((name, options2) => {
|
|
377047
|
+
projectNewCommand(name, options2);
|
|
377048
|
+
});
|
|
377049
|
+
projectCmd.command("list").description("List all projects").addHelpText("after", `
|
|
377050
|
+
Examples:
|
|
377051
|
+
$ specific project list`).action(() => {
|
|
377052
|
+
projectListCommand();
|
|
377053
|
+
});
|
|
376760
377054
|
program.command("beta").description("Manage beta feature flags").action(betaCommand);
|
|
376761
377055
|
program.command("update").description("Update Specific CLI to the latest version").action(updateCommand);
|
|
376762
377056
|
program.command("login").description("Log in to Specific").action(loginCommand);
|
package/package.json
CHANGED
/package/dist/admin/_next/static/{BOm2VNloUXzWE8sN1MOhy → t6Nv9BcBf3NfJnds5W8MU}/_buildManifest.js
RENAMED
|
File without changes
|
|
File without changes
|
/package/dist/admin/_next/static/{BOm2VNloUXzWE8sN1MOhy → t6Nv9BcBf3NfJnds5W8MU}/_ssgManifest.js
RENAMED
|
File without changes
|