@cowliss/cli 0.1.0 → 0.2.0
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/guest/{driver-BOe7jBFw.js → driver-D8cPyQgF.js} +1 -1
- package/dist/guest/driver.js +1 -1
- package/dist/guest/{journeys-CxuS-mHn.js → journeys-CR_wIAtP.js} +17 -5
- package/dist/guest/journeys.js +1 -1
- package/dist/guest/wasi.js +1 -1
- package/dist/index.js +142 -51
- package/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as manifestOutputSchema, i as journeyStepOutputSchema, o as templateRenderOutputSchema, r as guestInputSchema, t as CapabilityError } from "./journeys-
|
|
1
|
+
import { a as manifestOutputSchema, i as journeyStepOutputSchema, o as templateRenderOutputSchema, r as guestInputSchema, t as CapabilityError } from "./journeys-CR_wIAtP.js";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { createElement } from "react";
|
|
4
4
|
import { renderToStaticMarkup } from "react-dom/server.browser";
|
package/dist/guest/driver.js
CHANGED
|
@@ -69,19 +69,31 @@ const COW_CONFIG_SCHEMA_URL = `${DOCS_URL}${COW_CONFIG_SCHEMA_PATH}`;
|
|
|
69
69
|
//#endregion
|
|
70
70
|
//#region ../../packages/shared/src/journeys-v2/config.ts
|
|
71
71
|
/**
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
|
|
72
|
+
* A project's name: what `cow init` asked for, and what the `projects` row
|
|
73
|
+
* stores. Lives here rather than beside the release DTOs because `cow.json`
|
|
74
|
+
* is the file a developer types it into; `createProjectBodySchema` reuses it.
|
|
75
|
+
*/
|
|
76
|
+
const projectNameSchema = z.string().trim().min(1).max(200);
|
|
77
|
+
/**
|
|
78
|
+
* `cow.json` (spec: Project layout): the org, and which of its projects this
|
|
79
|
+
* directory is. The environment is always a flag, and auth never lives in
|
|
80
|
+
* the project. Strict, so a typo'd key is a build error rather than a
|
|
81
|
+
* silently ignored setting.
|
|
76
82
|
*/
|
|
77
83
|
const cowConfigSchema = z.strictObject({
|
|
78
84
|
$schema: z.url().optional(),
|
|
79
85
|
orgId: z.string().startsWith(CLERK_ORG_SUBJECT_PREFIX, "orgId must be a Clerk org id"),
|
|
86
|
+
/**
|
|
87
|
+
* Which project of the org this directory pushes to. One org can hold
|
|
88
|
+
* several, one per repo, each with its own release sequence and its own
|
|
89
|
+
* slice of the deployed journeys, so every project says which it is.
|
|
90
|
+
*/
|
|
91
|
+
project: projectNameSchema,
|
|
80
92
|
/** Overrides the API the CLI talks to; the hosted product needs none. */
|
|
81
93
|
apiUrl: z.url().optional()
|
|
82
94
|
}).meta({
|
|
83
95
|
title: "cow.json",
|
|
84
|
-
description: "A cow project: the organization it deploys to."
|
|
96
|
+
description: "A cow project: the organization and the project it deploys to."
|
|
85
97
|
});
|
|
86
98
|
|
|
87
99
|
//#endregion
|
package/dist/guest/journeys.js
CHANGED
package/dist/guest/wasi.js
CHANGED
package/dist/index.js
CHANGED
|
@@ -3802,13 +3802,18 @@ const insertJourneyRunSchema = createInsertSchema(journeyRuns);
|
|
|
3802
3802
|
*/
|
|
3803
3803
|
const consentPurposeEnum = pgEnum("consent_purpose", ["emailMarketing", "dataProcessing"]);
|
|
3804
3804
|
/**
|
|
3805
|
-
* Derived journey rows: one per (org, environment, key),
|
|
3805
|
+
* Derived journey rows: one per (org, environment, key), upserted from the
|
|
3806
3806
|
* manifest inside the deploy transaction. Nothing here is authored through
|
|
3807
3807
|
* the API, which is why there is no id of its own: the key is the name the
|
|
3808
3808
|
* author gave the file, and the (org, environment, key) triple is the only
|
|
3809
|
-
* identity a journey has.
|
|
3810
|
-
*
|
|
3811
|
-
*
|
|
3809
|
+
* identity a journey has.
|
|
3810
|
+
*
|
|
3811
|
+
* A deploy only ever adds and updates: it owns the keys its release carries
|
|
3812
|
+
* and leaves every other row alone, because an org's journeys can come from
|
|
3813
|
+
* several projects and a developer may deploy a release holding only some of
|
|
3814
|
+
* the files. `projectId` records which project put the row here, which is
|
|
3815
|
+
* what makes a key another project owns a refused deploy rather than a silent
|
|
3816
|
+
* overwrite. Removing a journey is an explicit delete (ADR 0009).
|
|
3812
3817
|
*
|
|
3813
3818
|
* `active` is the author's own rollout gate, flattened from the manifest's
|
|
3814
3819
|
* `environments` list into this row's environment. The operator's kill
|
|
@@ -3823,8 +3828,10 @@ const journeys$1 = pgTable("journeys", {
|
|
|
3823
3828
|
orgId: text("org_id").notNull(),
|
|
3824
3829
|
environment: environmentEnum("environment").notNull(),
|
|
3825
3830
|
key: text("key").notNull(),
|
|
3826
|
-
/** The release
|
|
3831
|
+
/** The release this row was derived from; the one its executions run. */
|
|
3827
3832
|
releaseId: text("release_id").notNull(),
|
|
3833
|
+
/** The project whose deploy owns this key in this environment. */
|
|
3834
|
+
projectId: text("project_id").notNull(),
|
|
3828
3835
|
/** The author's labels, from the manifest; the dashboard's only grouping. */
|
|
3829
3836
|
tags: text("tags").array().notNull().default(sql`'{}'::text[]`),
|
|
3830
3837
|
trigger: jsonb("trigger").$type().notNull(),
|
|
@@ -3932,9 +3939,10 @@ const insertOrgSettingsSchema = createInsertSchema(orgSettings);
|
|
|
3932
3939
|
//#region ../../packages/db/src/schema/projects.ts
|
|
3933
3940
|
/**
|
|
3934
3941
|
* An org's cow project: the folder a developer runs `cow init` in, and the
|
|
3935
|
-
* thing releases belong to.
|
|
3936
|
-
*
|
|
3937
|
-
*
|
|
3942
|
+
* thing releases belong to. An org may hold several, one per repo, each with
|
|
3943
|
+
* its own release sequence, its own current release per environment, and its
|
|
3944
|
+
* own slice of the deployed journey rows. The name is how `cow.json` picks
|
|
3945
|
+
* one, so it is unique within the org.
|
|
3938
3946
|
*/
|
|
3939
3947
|
const projects = pgTable("projects", {
|
|
3940
3948
|
id: text("id").primaryKey(),
|
|
@@ -3942,7 +3950,7 @@ const projects = pgTable("projects", {
|
|
|
3942
3950
|
name: text("name").notNull(),
|
|
3943
3951
|
createdAt: createdAt(),
|
|
3944
3952
|
updatedAt: updatedAt()
|
|
3945
|
-
}, (table) => [uniqueIndex("
|
|
3953
|
+
}, (table) => [uniqueIndex("projects_org_id_name_unique").on(table.orgId, table.name)]);
|
|
3946
3954
|
const selectProjectSchema = createSelectSchema(projects);
|
|
3947
3955
|
const insertProjectSchema = createInsertSchema(projects);
|
|
3948
3956
|
|
|
@@ -4087,19 +4095,23 @@ const releaseArtifacts = pgTable("release_artifacts", {
|
|
|
4087
4095
|
table.digest
|
|
4088
4096
|
] })]);
|
|
4089
4097
|
/**
|
|
4090
|
-
* Append-only: an environment's current release
|
|
4091
|
-
* rollback is another deployment of an earlier
|
|
4092
|
-
* mutation. That is what makes the deployment history
|
|
4093
|
-
* "the last two deployments" the whole of `cow rollback`.
|
|
4098
|
+
* Append-only: an environment's current release, for one project, is that
|
|
4099
|
+
* project's latest row, and rollback is another deployment of an earlier
|
|
4100
|
+
* release rather than a mutation. That is what makes the deployment history
|
|
4101
|
+
* readable and makes "the last two deployments" the whole of `cow rollback`.
|
|
4102
|
+
*
|
|
4103
|
+
* `projectId` is the release's own project, denormalised so "this project's
|
|
4104
|
+
* latest deployment in this environment" is one indexed read.
|
|
4094
4105
|
*/
|
|
4095
4106
|
const deployments$1 = pgTable("deployments", {
|
|
4096
4107
|
id: text("id").primaryKey(),
|
|
4097
4108
|
orgId: text("org_id").notNull(),
|
|
4098
4109
|
environment: environmentEnum("environment").notNull(),
|
|
4099
4110
|
releaseId: text("release_id").notNull().references(() => releases$1.id),
|
|
4111
|
+
projectId: text("project_id").notNull().references(() => projects.id),
|
|
4100
4112
|
deployedBy: text("deployed_by").notNull(),
|
|
4101
4113
|
createdAt: createdAt()
|
|
4102
|
-
}, (table) => [index("deployments_org_id_environment_created_at_idx").on(table.orgId, table.environment, table.createdAt)]);
|
|
4114
|
+
}, (table) => [index("deployments_org_id_environment_created_at_idx").on(table.orgId, table.environment, table.createdAt), index("deployments_org_id_project_id_environment_created_at_idx").on(table.orgId, table.projectId, table.environment, table.createdAt)]);
|
|
4103
4115
|
const selectDeploymentSchema = createSelectSchema(deployments$1);
|
|
4104
4116
|
const insertDeploymentSchema = createInsertSchema(deployments$1);
|
|
4105
4117
|
|
|
@@ -5901,19 +5913,31 @@ const dryRunJourneyBodySchema = z.object({ data: z.object({ profileId: z.string(
|
|
|
5901
5913
|
//#endregion
|
|
5902
5914
|
//#region ../../packages/shared/src/journeys-v2/config.ts
|
|
5903
5915
|
/**
|
|
5904
|
-
*
|
|
5905
|
-
*
|
|
5906
|
-
*
|
|
5907
|
-
|
|
5916
|
+
* A project's name: what `cow init` asked for, and what the `projects` row
|
|
5917
|
+
* stores. Lives here rather than beside the release DTOs because `cow.json`
|
|
5918
|
+
* is the file a developer types it into; `createProjectBodySchema` reuses it.
|
|
5919
|
+
*/
|
|
5920
|
+
const projectNameSchema = z.string().trim().min(1).max(200);
|
|
5921
|
+
/**
|
|
5922
|
+
* `cow.json` (spec: Project layout): the org, and which of its projects this
|
|
5923
|
+
* directory is. The environment is always a flag, and auth never lives in
|
|
5924
|
+
* the project. Strict, so a typo'd key is a build error rather than a
|
|
5925
|
+
* silently ignored setting.
|
|
5908
5926
|
*/
|
|
5909
5927
|
const cowConfigSchema = z.strictObject({
|
|
5910
5928
|
$schema: z.url().optional(),
|
|
5911
5929
|
orgId: z.string().startsWith(CLERK_ORG_SUBJECT_PREFIX, "orgId must be a Clerk org id"),
|
|
5930
|
+
/**
|
|
5931
|
+
* Which project of the org this directory pushes to. One org can hold
|
|
5932
|
+
* several, one per repo, each with its own release sequence and its own
|
|
5933
|
+
* slice of the deployed journeys, so every project says which it is.
|
|
5934
|
+
*/
|
|
5935
|
+
project: projectNameSchema,
|
|
5912
5936
|
/** Overrides the API the CLI talks to; the hosted product needs none. */
|
|
5913
5937
|
apiUrl: z.url().optional()
|
|
5914
5938
|
}).meta({
|
|
5915
5939
|
title: "cow.json",
|
|
5916
|
-
description: "A cow project: the organization it deploys to."
|
|
5940
|
+
description: "A cow project: the organization and the project it deploys to."
|
|
5917
5941
|
});
|
|
5918
5942
|
|
|
5919
5943
|
//#endregion
|
|
@@ -6000,7 +6024,9 @@ const projectSchema = selectProjectSchema.extend({
|
|
|
6000
6024
|
updatedAt: z.iso.datetime()
|
|
6001
6025
|
});
|
|
6002
6026
|
/** `cow init` names the project after the folder it created. */
|
|
6003
|
-
const createProjectBodySchema = z.object({ data: z.object({ name:
|
|
6027
|
+
const createProjectBodySchema = z.object({ data: z.object({ name: projectNameSchema }) });
|
|
6028
|
+
/** Which project to read: the one the caller's `cow.json` names. */
|
|
6029
|
+
const getProjectQuerySchema = z.object({ name: projectNameSchema });
|
|
6004
6030
|
const artifactDigestParams = z.object({ digest: digestSchema });
|
|
6005
6031
|
/**
|
|
6006
6032
|
* Which kind of artifact the uploaded bytes are. Named by the client rather
|
|
@@ -6027,8 +6053,14 @@ const releaseSchema = selectReleaseSchema.extend({
|
|
|
6027
6053
|
createdAt: z.iso.datetime(),
|
|
6028
6054
|
compiledAt: z.iso.datetime().nullable()
|
|
6029
6055
|
});
|
|
6030
|
-
const createReleaseBodySchema = z.object({ data: z.object({
|
|
6031
|
-
|
|
6056
|
+
const createReleaseBodySchema = z.object({ data: z.object({
|
|
6057
|
+
manifest: manifestSchema,
|
|
6058
|
+
/** The `cow.json` project this push belongs to. */
|
|
6059
|
+
project: projectNameSchema
|
|
6060
|
+
}) });
|
|
6061
|
+
const listReleasesQuerySchema = paginationQuerySchema.extend({
|
|
6062
|
+
/** One project's own releases; absent lists the org's. */
|
|
6063
|
+
project: projectNameSchema.optional() });
|
|
6032
6064
|
/**
|
|
6033
6065
|
* How many live executions the cancel sweep was asked to stop, and the
|
|
6034
6066
|
* workflow doing the stopping. The count is taken when the request lands, so
|
|
@@ -6046,10 +6078,18 @@ const cancelExecutionsSchema = z.object({
|
|
|
6046
6078
|
* A deployment, plus the warnings the deploy raised: segment names in
|
|
6047
6079
|
* triggers and destination names in spines that do not exist in the target
|
|
6048
6080
|
* environment. They never block, because either may be created afterwards.
|
|
6081
|
+
* `left` is the other half of the same conversation: what the deploy did not
|
|
6082
|
+
* touch, because a deploy only ever adds and updates (ADR 0009).
|
|
6049
6083
|
*/
|
|
6050
6084
|
const deploymentSchema = selectDeploymentSchema.extend({
|
|
6051
6085
|
createdAt: z.iso.datetime(),
|
|
6052
6086
|
warnings: z.array(z.string()),
|
|
6087
|
+
/**
|
|
6088
|
+
* Journeys this project still runs in the environment that the deployed
|
|
6089
|
+
* release does not carry. A deploy never removes one, so these keep running
|
|
6090
|
+
* until someone deletes them; the CLI prints them and says how.
|
|
6091
|
+
*/
|
|
6092
|
+
left: z.array(z.string()),
|
|
6053
6093
|
/** The deployed release's per-project sequence number, its human handle. */
|
|
6054
6094
|
releaseSeq: z.number().int()
|
|
6055
6095
|
});
|
|
@@ -6057,7 +6097,9 @@ const createDeploymentBodySchema = z.object({ data: z.object({ releaseId: z.stri
|
|
|
6057
6097
|
const listDeploymentsQuerySchema = paginationQuerySchema.extend({
|
|
6058
6098
|
environment: environmentSchema.optional(),
|
|
6059
6099
|
/** One release's own deployment history, across both environments. */
|
|
6060
|
-
release: z.string().trim().min(1).max(200).optional()
|
|
6100
|
+
release: z.string().trim().min(1).max(200).optional(),
|
|
6101
|
+
/** One project's own deployments; absent lists the org's. */
|
|
6102
|
+
project: projectNameSchema.optional()
|
|
6061
6103
|
});
|
|
6062
6104
|
|
|
6063
6105
|
//#endregion
|
|
@@ -7225,6 +7267,19 @@ async function assertCowConfig(projectDir) {
|
|
|
7225
7267
|
return parsed.data;
|
|
7226
7268
|
}
|
|
7227
7269
|
/**
|
|
7270
|
+
* Which project of the org this directory belongs to: the `project` in its
|
|
7271
|
+
* `cow.json`, or undefined when there is no `cow.json` here at all. The
|
|
7272
|
+
* undefined case is `cow pull` into an empty directory, which has no project
|
|
7273
|
+
* to scope by yet and reads the org's releases.
|
|
7274
|
+
*/
|
|
7275
|
+
async function configuredProject(projectDir) {
|
|
7276
|
+
try {
|
|
7277
|
+
return (await assertCowConfig(projectDir)).project;
|
|
7278
|
+
} catch {
|
|
7279
|
+
return;
|
|
7280
|
+
}
|
|
7281
|
+
}
|
|
7282
|
+
/**
|
|
7228
7283
|
* Builds the project at `projectDir`. Writes `.cow/types.d.ts` first (the
|
|
7229
7284
|
* typecheck needs it), then the bundles and the source tarball, and
|
|
7230
7285
|
* `.cow/build/manifest.json` last, so a failed build leaves no manifest and
|
|
@@ -8879,6 +8934,19 @@ const journeys = defineModule(defineRoute({
|
|
|
8879
8934
|
...sessionErrors,
|
|
8880
8935
|
...errors("not_found", "validation_failed", "malformed_request")
|
|
8881
8936
|
}
|
|
8937
|
+
}), defineRoute({
|
|
8938
|
+
method: "delete",
|
|
8939
|
+
path: "/v1/journeys/{key}",
|
|
8940
|
+
operationId: "journeys.delete",
|
|
8941
|
+
tags: ["journeys"],
|
|
8942
|
+
summary: "Remove a journey from the selected environment",
|
|
8943
|
+
security: SESSION_AUTH,
|
|
8944
|
+
request: { params: params$5 },
|
|
8945
|
+
responses: {
|
|
8946
|
+
200: envelope(deletedSchema),
|
|
8947
|
+
...sessionErrors,
|
|
8948
|
+
...errors("not_found")
|
|
8949
|
+
}
|
|
8882
8950
|
}), defineRoute({
|
|
8883
8951
|
method: "post",
|
|
8884
8952
|
path: "/v1/journeys/{key}/dry-run",
|
|
@@ -8900,28 +8968,29 @@ const journeys = defineModule(defineRoute({
|
|
|
8900
8968
|
//#endregion
|
|
8901
8969
|
//#region ../../packages/shared/src/contract/project.ts
|
|
8902
8970
|
/**
|
|
8903
|
-
* The org's project. Singular in the path on purpose:
|
|
8904
|
-
*
|
|
8905
|
-
*
|
|
8971
|
+
* The org's project. Singular in the path on purpose: a caller always means
|
|
8972
|
+
* one, the one its `cow.json` names, so the name is a query parameter and
|
|
8973
|
+
* there is no id to put in a URL. An org with a single project needs neither.
|
|
8906
8974
|
*/
|
|
8907
8975
|
const project = defineModule(defineRoute({
|
|
8908
8976
|
method: "get",
|
|
8909
8977
|
path: "/v1/project",
|
|
8910
8978
|
operationId: "project.get",
|
|
8911
8979
|
tags: ["project"],
|
|
8912
|
-
summary: "Get the organization's
|
|
8980
|
+
summary: "Get one of the organization's projects",
|
|
8913
8981
|
security: RELEASE_AUTH,
|
|
8982
|
+
request: { query: getProjectQuerySchema },
|
|
8914
8983
|
responses: {
|
|
8915
8984
|
200: envelope(projectSchema),
|
|
8916
8985
|
...sessionErrors,
|
|
8917
|
-
...errors("project_missing")
|
|
8986
|
+
...errors("project_missing", "validation_failed", "malformed_request")
|
|
8918
8987
|
}
|
|
8919
8988
|
}), defineRoute({
|
|
8920
8989
|
method: "post",
|
|
8921
8990
|
path: "/v1/project",
|
|
8922
8991
|
operationId: "project.create",
|
|
8923
8992
|
tags: ["project"],
|
|
8924
|
-
summary: "Create the organization
|
|
8993
|
+
summary: "Create a project in the organization",
|
|
8925
8994
|
security: RELEASE_AUTH,
|
|
8926
8995
|
request: { body: jsonBody(createProjectBodySchema) },
|
|
8927
8996
|
responses: {
|
|
@@ -9987,18 +10056,19 @@ const POLL_BUDGET_MS = RELEASE_COMPILE_TIMEOUT_MS + 6e4;
|
|
|
9987
10056
|
const POLL_START_MS = 250;
|
|
9988
10057
|
const POLL_MAX_MS = 2e3;
|
|
9989
10058
|
/**
|
|
9990
|
-
*
|
|
9991
|
-
* first; a project pushed from a machine that
|
|
9992
|
-
* not, and creating it here is what makes that
|
|
10059
|
+
* This directory's project, created when the org has none by that name.
|
|
10060
|
+
* `cow init` usually got there first; a project pushed from a machine that
|
|
10061
|
+
* only ever cloned the repo has not, and creating it here is what makes that
|
|
10062
|
+
* clone work. The name is `cow.json`'s `project`.
|
|
9993
10063
|
*/
|
|
9994
|
-
async function ensureProject(client,
|
|
10064
|
+
async function ensureProject(client, name) {
|
|
9995
10065
|
try {
|
|
9996
|
-
return (await client.request(contract.project["project.get"])).data;
|
|
10066
|
+
return (await client.request(contract.project["project.get"], { query: { name } })).data;
|
|
9997
10067
|
} catch (error) {
|
|
9998
10068
|
if (!(error instanceof ApiError) || error.code !== "project_missing") throw error;
|
|
9999
10069
|
}
|
|
10000
10070
|
try {
|
|
10001
|
-
return (await client.request(contract.project["project.create"], { body: { name
|
|
10071
|
+
return (await client.request(contract.project["project.create"], { body: { name } })).data;
|
|
10002
10072
|
} catch (error) {
|
|
10003
10073
|
const reason = error instanceof Error ? error.message : String(error);
|
|
10004
10074
|
throw new Error(`This organization has no project and one could not be created (${reason}). Run \`cow init\` first.`);
|
|
@@ -10035,9 +10105,16 @@ function artifactFiles(projectDir, manifest) {
|
|
|
10035
10105
|
}
|
|
10036
10106
|
];
|
|
10037
10107
|
}
|
|
10038
|
-
/**
|
|
10039
|
-
|
|
10040
|
-
|
|
10108
|
+
/**
|
|
10109
|
+
* This project's most recent release, or null when it has none yet. Scoped to
|
|
10110
|
+
* the project because a sibling repo's push must not make this one look
|
|
10111
|
+
* unchanged, or changed.
|
|
10112
|
+
*/
|
|
10113
|
+
async function latestRelease(client, project) {
|
|
10114
|
+
return (await client.request(contract.releases["releases.list"], { query: {
|
|
10115
|
+
limit: 1,
|
|
10116
|
+
project
|
|
10117
|
+
} })).data[0] ?? null;
|
|
10041
10118
|
}
|
|
10042
10119
|
/**
|
|
10043
10120
|
* Poll until the compile settles. Backs off from a quarter second to two,
|
|
@@ -10058,10 +10135,10 @@ async function awaitCompile(client, releaseId, sleep) {
|
|
|
10058
10135
|
}
|
|
10059
10136
|
async function pushProject({ client, projectDir, force, sleep = (ms) => setTimeout$1(ms) }) {
|
|
10060
10137
|
const { manifest, config } = await buildProject(projectDir);
|
|
10061
|
-
const project = await ensureProject(client,
|
|
10138
|
+
const project = await ensureProject(client, config.project);
|
|
10062
10139
|
if (project.orgId !== config.orgId) throw new Error(`cow.json is for ${config.orgId} but the credential belongs to ${project.orgId}. Log in to that organization, or use its deploy key.`);
|
|
10063
10140
|
const digest = manifestDigestOf(manifest);
|
|
10064
|
-
const latest = await latestRelease(client);
|
|
10141
|
+
const latest = await latestRelease(client, project.name);
|
|
10065
10142
|
if (latest && latest.manifestDigest === digest && !force) return {
|
|
10066
10143
|
release: latest,
|
|
10067
10144
|
uploaded: [],
|
|
@@ -10078,7 +10155,10 @@ async function pushProject({ client, projectDir, force, sleep = (ms) => setTimeo
|
|
|
10078
10155
|
uploaded.push(artifact.digest);
|
|
10079
10156
|
}
|
|
10080
10157
|
return {
|
|
10081
|
-
release: await awaitCompile(client, (await client.request(contract.releases["releases.create"], { body: {
|
|
10158
|
+
release: await awaitCompile(client, (await client.request(contract.releases["releases.create"], { body: {
|
|
10159
|
+
manifest,
|
|
10160
|
+
project: project.name
|
|
10161
|
+
} })).data.id, sleep),
|
|
10082
10162
|
uploaded
|
|
10083
10163
|
};
|
|
10084
10164
|
}
|
|
@@ -10141,6 +10221,7 @@ function deploySummary(outcome) {
|
|
|
10141
10221
|
const lines = [`${release.id} #${release.seq} deployed to ${deployment.environment}.`];
|
|
10142
10222
|
if (outcome.skipped) lines.unshift(outcome.skipped);
|
|
10143
10223
|
for (const warning of deployment.warnings) lines.push(`Warning: ${warning}`);
|
|
10224
|
+
if (deployment.left.length > 0) lines.push(`Still running, not in this release: ${deployment.left.join(", ")}. Remove one with \`cow journeys delete <key> --env ${deployment.environment}\`.`);
|
|
10144
10225
|
return lines.join("\n");
|
|
10145
10226
|
}
|
|
10146
10227
|
function registerDeploy(program, clientFor, env, io) {
|
|
@@ -10542,7 +10623,8 @@ function registerInit(program, env, io) {
|
|
|
10542
10623
|
if (orgId === "") throw new Error("No organization id. Pass --org org_… or run `cow login` first.");
|
|
10543
10624
|
const config = cowConfigSchema.parse({
|
|
10544
10625
|
$schema: COW_CONFIG_SCHEMA_URL,
|
|
10545
|
-
orgId
|
|
10626
|
+
orgId,
|
|
10627
|
+
project: name
|
|
10546
10628
|
});
|
|
10547
10629
|
const contents = {
|
|
10548
10630
|
"cow.json": json(config),
|
|
@@ -10648,10 +10730,11 @@ function planTools() {
|
|
|
10648
10730
|
* a native tool. The tool table (tools.ts) is planned from the shared route
|
|
10649
10731
|
* contract at startup, so the surface cannot drift from the API.
|
|
10650
10732
|
*/
|
|
10651
|
-
function createMcpServer({ client }) {
|
|
10733
|
+
async function createMcpServer({ client }) {
|
|
10734
|
+
const { version } = await readCliPackage();
|
|
10652
10735
|
const server = new McpServer({
|
|
10653
10736
|
name: `${"cow"}-mcp`,
|
|
10654
|
-
version
|
|
10737
|
+
version
|
|
10655
10738
|
});
|
|
10656
10739
|
const request = client.request;
|
|
10657
10740
|
for (const tool of planTools()) server.registerTool(tool.name, {
|
|
@@ -10703,7 +10786,7 @@ function registerMcp(program, clientFor, env, io) {
|
|
|
10703
10786
|
...opts
|
|
10704
10787
|
});
|
|
10705
10788
|
if (resolveCredential(env, await readCredentials(env)).kind === "none") io.stderr("cow mcp: not logged in. Run `cow login`, or set COW_TOKEN or COW_DEPLOY_KEY.\n");
|
|
10706
|
-
await createMcpServer({ client }).connect(new StdioServerTransport());
|
|
10789
|
+
await (await createMcpServer({ client })).connect(new StdioServerTransport());
|
|
10707
10790
|
});
|
|
10708
10791
|
}
|
|
10709
10792
|
|
|
@@ -10751,12 +10834,17 @@ function noGitHere(error) {
|
|
|
10751
10834
|
const failure = error;
|
|
10752
10835
|
return failure.code === "ENOENT" || String(failure.stderr ?? "").includes("not a git repository");
|
|
10753
10836
|
}
|
|
10754
|
-
/**
|
|
10755
|
-
|
|
10837
|
+
/**
|
|
10838
|
+
* The release to restore: the one named, the one an environment runs, else the
|
|
10839
|
+
* newest that compiled. Scoped to this directory's project, so a sibling
|
|
10840
|
+
* repo's release is never unpacked over this one's files.
|
|
10841
|
+
*/
|
|
10842
|
+
async function resolveRelease(client, releaseId, environment, project) {
|
|
10756
10843
|
if (releaseId) return (await client.request(contract.releases["releases.get"], { params: { id: releaseId } })).data;
|
|
10757
10844
|
if (environment) {
|
|
10758
10845
|
const current = (await client.request(contract.deployments["deployments.list"], { query: {
|
|
10759
10846
|
environment,
|
|
10847
|
+
project,
|
|
10760
10848
|
limit: 1
|
|
10761
10849
|
} })).data[0];
|
|
10762
10850
|
if (!current) throw new Error(`Nothing has been deployed to ${environment}, so there is no release to restore. Name one with --release.`);
|
|
@@ -10766,7 +10854,8 @@ async function resolveRelease(client, releaseId, environment) {
|
|
|
10766
10854
|
do {
|
|
10767
10855
|
const page = await client.request(contract.releases["releases.list"], { query: {
|
|
10768
10856
|
limit: PAGE_SIZE,
|
|
10769
|
-
cursor
|
|
10857
|
+
cursor,
|
|
10858
|
+
project
|
|
10770
10859
|
} });
|
|
10771
10860
|
const ready = page.data.find((release) => release.status === "ready");
|
|
10772
10861
|
if (ready) return ready;
|
|
@@ -10775,7 +10864,7 @@ async function resolveRelease(client, releaseId, environment) {
|
|
|
10775
10864
|
throw new Error("This organization has no release that compiled successfully. Run `cow push` first, or name one with --release.");
|
|
10776
10865
|
}
|
|
10777
10866
|
async function pullRelease({ client, projectDir, releaseId, environment, force }) {
|
|
10778
|
-
const release = await resolveRelease(client, releaseId, environment);
|
|
10867
|
+
const release = await resolveRelease(client, releaseId, environment, await configuredProject(projectDir));
|
|
10779
10868
|
if (!force) {
|
|
10780
10869
|
const dirty = await dirtyPaths(projectDir);
|
|
10781
10870
|
if (dirty.length > 0) throw new Error(`This working tree has uncommitted changes and \`cow pull\` would write over them:\n ${dirty.join("\n ")}\nCommit or stash them, or pass --force.`);
|
|
@@ -10819,9 +10908,10 @@ function registerPull(program, clientFor, env, io) {
|
|
|
10819
10908
|
|
|
10820
10909
|
//#endregion
|
|
10821
10910
|
//#region src/commands/rollback.ts
|
|
10822
|
-
async function rollbackEnvironment({ client, environment }) {
|
|
10911
|
+
async function rollbackEnvironment({ client, environment, project }) {
|
|
10823
10912
|
const [current, previous] = (await client.request(contract.deployments["deployments.list"], { query: {
|
|
10824
10913
|
environment,
|
|
10914
|
+
project,
|
|
10825
10915
|
limit: 2
|
|
10826
10916
|
} })).data;
|
|
10827
10917
|
if (!current) throw new Error(`Nothing has been deployed to ${environment} yet, so there is nothing to roll back.`);
|
|
@@ -10848,7 +10938,8 @@ function registerRollback(program, clientFor, env, io) {
|
|
|
10848
10938
|
};
|
|
10849
10939
|
const outcome = await rollbackEnvironment({
|
|
10850
10940
|
client: await clientFor(merged),
|
|
10851
|
-
environment: requireEnvironment(env, merged)
|
|
10941
|
+
environment: requireEnvironment(env, merged),
|
|
10942
|
+
project: await configuredProject(process.cwd())
|
|
10852
10943
|
});
|
|
10853
10944
|
if (merged.json === true) {
|
|
10854
10945
|
emit({ data: outcome.deployment }, io, true);
|