@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.
@@ -1,4 +1,4 @@
1
- import { a as manifestOutputSchema, i as journeyStepOutputSchema, o as templateRenderOutputSchema, r as guestInputSchema, t as CapabilityError } from "./journeys-CxuS-mHn.js";
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";
@@ -1,3 +1,3 @@
1
- import { t as runGuest } from "./driver-BOe7jBFw.js";
1
+ import { t as runGuest } from "./driver-D8cPyQgF.js";
2
2
 
3
3
  export { runGuest };
@@ -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
- * `cow.json` (spec: Project layout): the org id and nothing more. The
73
- * folder name is the project's local name, the environment is always a
74
- * flag, and auth never lives in the project. Strict, so a typo'd key is a
75
- * build error rather than a silently ignored setting.
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
@@ -1,3 +1,3 @@
1
- import { n as defineJourney, t as CapabilityError } from "./journeys-CxuS-mHn.js";
1
+ import { n as defineJourney, t as CapabilityError } from "./journeys-CR_wIAtP.js";
2
2
 
3
3
  export { CapabilityError, defineJourney };
@@ -1,4 +1,4 @@
1
- import { t as runGuest } from "./driver-BOe7jBFw.js";
1
+ import { t as runGuest } from "./driver-D8cPyQgF.js";
2
2
 
3
3
  //#region src/guest/wasi.ts
4
4
  const STDIN = 0;
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), rebuilt from the
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. Deleting and reinserting these rows on every
3810
- * deploy is the point, so the row set always matches the environment's
3811
- * current release.
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 these rows were derived from; the environment's current one. */
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. One per org for now (the unique index says so);
3936
- * the row exists rather than hanging releases off the org id directly so
3937
- * many projects per org is additive later without a migration of releases.
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("projects_org_id_unique").on(table.orgId)]);
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 is its latest row, and
4091
- * rollback is another deployment of an earlier release rather than a
4092
- * mutation. That is what makes the deployment history readable and makes
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
- * `cow.json` (spec: Project layout): the org id and nothing more. The
5905
- * folder name is the project's local name, the environment is always a
5906
- * flag, and auth never lives in the project. Strict, so a typo'd key is a
5907
- * build error rather than a silently ignored setting.
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: z.string().trim().min(1).max(200) }) });
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({ manifest: manifestSchema }) });
6031
- const listReleasesQuerySchema = paginationQuerySchema;
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: an org has exactly one
8904
- * for now, so there is no id to put in a URL, and many projects per org is a
8905
- * later, additive change (the `projects` row already exists so it can be).
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 project",
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's project",
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
- * The org's project, created when it has none. `cow init` usually got there
9991
- * first; a project pushed from a machine that only ever cloned the repo has
9992
- * not, and creating it here is what makes that clone work.
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, projectDir) {
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: basename(projectDir) } })).data;
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
- /** The most recent release, or null when the project has none yet. */
10039
- async function latestRelease(client) {
10040
- return (await client.request(contract.releases["releases.list"], { query: { limit: 1 } })).data[0] ?? null;
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, projectDir);
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: { manifest } })).data.id, sleep),
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: "0.0.0"
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
- /** The release to restore: the one named, the one an environment runs, else the newest that compiled. */
10755
- async function resolveRelease(client, releaseId, environment) {
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);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cowliss/cli",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "cow": "./dist/index.js"