@cowliss/cli 0.11.0 → 0.13.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-B_pjmy5g.js → driver-BtQYaFXP.js} +1 -1
- package/dist/guest/driver-emails.js +2 -2
- package/dist/guest/driver.d.ts +1 -1
- package/dist/guest/driver.js +1 -1
- package/dist/guest/{emails-5GindgMQ.d.ts → emails-DUwxKLGO.d.ts} +1 -1
- package/dist/guest/emails.d.ts +1 -1
- package/dist/guest/{journeys-v2-Djgs8U91.js → journeys-v2-Cndh4CAr.js} +1 -1
- package/dist/guest/journeys.d.ts +1 -1
- package/dist/guest/journeys.js +1 -1
- package/dist/guest/segments.js +1 -1
- package/dist/index.js +274 -191
- package/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as journeyStepOutputSchema, r as manifestOutputSchema, t as guestInputSchema } from "./journeys-v2-
|
|
1
|
+
import { n as journeyStepOutputSchema, r as manifestOutputSchema, t as guestInputSchema } from "./journeys-v2-Cndh4CAr.js";
|
|
2
2
|
import { CapabilityError } from "./journeys.js";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { i as templateRenderOutputSchema, t as guestInputSchema } from "./journeys-v2-
|
|
1
|
+
import { i as templateRenderOutputSchema, t as guestInputSchema } from "./journeys-v2-Cndh4CAr.js";
|
|
2
2
|
import { CapabilityError } from "./journeys.js";
|
|
3
|
-
import { n as readManifest, t as asTemplate } from "./driver-
|
|
3
|
+
import { n as readManifest, t as asTemplate } from "./driver-BtQYaFXP.js";
|
|
4
4
|
import { createElement } from "react";
|
|
5
5
|
import { renderToStaticMarkup } from "react-dom/server.browser";
|
|
6
6
|
|
package/dist/guest/driver.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { c as TemplateRenderOutput, o as JourneyStepOutput, s as ManifestOutput } from "./index-DADBwPaG.js";
|
|
2
|
-
import { n as Template } from "./emails-
|
|
2
|
+
import { n as Template } from "./emails-DUwxKLGO.js";
|
|
3
3
|
//#region src/guest/driver.d.ts
|
|
4
4
|
/**
|
|
5
5
|
* The in-process driver: JSON in, JSON out (spec: Guest protocol). One
|
package/dist/guest/driver.js
CHANGED
|
@@ -23,7 +23,7 @@ type SendClass = (typeof SEND_CLASSES)[number];
|
|
|
23
23
|
type Subject<Schema extends z.ZodType> = (props: z.infer<Schema>) => string;
|
|
24
24
|
/**
|
|
25
25
|
* The shape `emails/<key>.tsx` exports. `cow build` reads `props` (turned
|
|
26
|
-
* into JSON Schema in the manifest and into
|
|
26
|
+
* into JSON Schema in the manifest and into `types.d.ts`), `subject`,
|
|
27
27
|
* and the optional metadata below it; the sandbox renders `default`.
|
|
28
28
|
*/
|
|
29
29
|
type Template<Schema extends z.ZodType = z.ZodType> = {
|
package/dist/guest/emails.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { n as Template, r as SendClass, t as Subject } from "./emails-
|
|
1
|
+
import { n as Template, r as SendClass, t as Subject } from "./emails-DUwxKLGO.js";
|
|
2
2
|
export { type SendClass, Subject, Template };
|
|
@@ -513,7 +513,7 @@ function uniqueKeys(items, ctx, path) {
|
|
|
513
513
|
}
|
|
514
514
|
}
|
|
515
515
|
/**
|
|
516
|
-
* What `cow build` writes to `.
|
|
516
|
+
* What `cow build` writes to `.cowliss/build/manifest.json`, and the shape a
|
|
517
517
|
* push row stores for good, entry by entry, on the versions it creates.
|
|
518
518
|
*
|
|
519
519
|
* `protocol` is any positive integer rather than the current constant on
|
package/dist/guest/journeys.d.ts
CHANGED
|
@@ -33,7 +33,7 @@ declare class CapabilityError extends Error {
|
|
|
33
33
|
}
|
|
34
34
|
/**
|
|
35
35
|
* The repository's templates, keyed by template key. `cow build` writes the
|
|
36
|
-
* real interface into
|
|
36
|
+
* real interface into `types.d.ts` and TypeScript merges it into this
|
|
37
37
|
* one, which is what types `api.send.email`. Empty here on purpose: a
|
|
38
38
|
* repository that has not built yet still compiles, with string keys.
|
|
39
39
|
*/
|
package/dist/guest/journeys.js
CHANGED
package/dist/guest/segments.js
CHANGED
package/dist/index.js
CHANGED
|
@@ -8,13 +8,13 @@ import { homedir } from "node:os";
|
|
|
8
8
|
import { basename, dirname, join, relative, resolve, sep } from "node:path";
|
|
9
9
|
import { Command, InvalidArgumentError } from "commander";
|
|
10
10
|
import { execFile, spawn } from "node:child_process";
|
|
11
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
11
12
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
12
13
|
import { promisify } from "node:util";
|
|
13
14
|
import { MessagePort } from "node:worker_threads";
|
|
14
15
|
import { build, formatMessagesSync } from "esbuild";
|
|
15
16
|
import { create, extract } from "tar";
|
|
16
17
|
import { parse } from "@babel/parser";
|
|
17
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
18
18
|
import { createServer } from "node:http";
|
|
19
19
|
import { createInterface } from "node:readline/promises";
|
|
20
20
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
@@ -173,6 +173,8 @@ const SYSTEM_EVENT_PREFIX = "system.";
|
|
|
173
173
|
* system.profile_deleted is the provider-recorded fact that a provider
|
|
174
174
|
* (e.g. Clerk `user.deleted`) deleted the user upstream — a marker event
|
|
175
175
|
* only, with no deletion semantics (hard-delete belongs to GDPR erasure);
|
|
176
|
+
* system.signed_up and system.logged_in are the same shape for the other end
|
|
177
|
+
* of that lifecycle (Clerk `user.created` and `session.created`);
|
|
176
178
|
* system.email_clicked is a link click reported by SES feedback.
|
|
177
179
|
*
|
|
178
180
|
* Rows carrying one of these names get `system: true` so the usage meter
|
|
@@ -187,6 +189,17 @@ const SYSTEM_EVENTS = {
|
|
|
187
189
|
consentRevoked: "system.consent_revoked",
|
|
188
190
|
profileDeleted: "system.profile_deleted",
|
|
189
191
|
/**
|
|
192
|
+
* The provider's account lifecycle, as facts rather than as profile state:
|
|
193
|
+
* a profile can predate its `user.created` (an SDK identify or a
|
|
194
|
+
* membership delivery may have created it first), so these name what the
|
|
195
|
+
* provider reported, not what Cowliss did. `signedUp` is the trigger for a
|
|
196
|
+
* welcome journey fed by a provider source, which `emailRegistered` only
|
|
197
|
+
* approximates (it fires on an address change too, and never for a
|
|
198
|
+
* profile with no address). `loggedIn` is one row per session start.
|
|
199
|
+
*/
|
|
200
|
+
signedUp: "system.signed_up",
|
|
201
|
+
loggedIn: "system.logged_in",
|
|
202
|
+
/**
|
|
190
203
|
* A write's identifiers named more than one profile and Cowliss merged
|
|
191
204
|
* them (ticket 69). Lands on the survivor's timeline carrying
|
|
192
205
|
* `mergedProfileIds`, the ids folded into it.
|
|
@@ -5160,7 +5173,7 @@ function uniqueKeys(items, ctx, path) {
|
|
|
5160
5173
|
}
|
|
5161
5174
|
}
|
|
5162
5175
|
/**
|
|
5163
|
-
* What `cow build` writes to `.
|
|
5176
|
+
* What `cow build` writes to `.cowliss/build/manifest.json`, and the shape a
|
|
5164
5177
|
* push row stores for good, entry by entry, on the versions it creates.
|
|
5165
5178
|
*
|
|
5166
5179
|
* `protocol` is any positive integer rather than the current constant on
|
|
@@ -7873,6 +7886,36 @@ const updateWebhookBodySchema = z.object({ data: z.object({
|
|
|
7873
7886
|
/** `q` is a substring search over the webhook's name. */
|
|
7874
7887
|
const listWebhooksQuerySchema = paginationQuerySchema.extend({ q: searchQuerySchema });
|
|
7875
7888
|
|
|
7889
|
+
//#endregion
|
|
7890
|
+
//#region ../../packages/shared/src/whoami.ts
|
|
7891
|
+
/**
|
|
7892
|
+
* What an ingestion key plus one source id write into. An org API key is
|
|
7893
|
+
* org-scoped and carries no app of its own (`ApiKeyClaims`), so any live
|
|
7894
|
+
* `src_` in the organization is accepted by the write path: a well-formed id
|
|
7895
|
+
* belonging to another app lands profiles there with a 200. The id the
|
|
7896
|
+
* caller is about to send is therefore the input, and this answers where it
|
|
7897
|
+
* goes before anything is written.
|
|
7898
|
+
*
|
|
7899
|
+
* Scoped to that one source's app on purpose: the key stays a write
|
|
7900
|
+
* credential, and a leaked one must not enumerate the org's other apps
|
|
7901
|
+
* (.scratch/24-onboarding-dx/spec.md).
|
|
7902
|
+
*/
|
|
7903
|
+
const whoamiQuerySchema = z.object({ sourceId: sourceIdSchema });
|
|
7904
|
+
const whoamiSchema = z.object({
|
|
7905
|
+
orgId: z.string(),
|
|
7906
|
+
app: z.object({
|
|
7907
|
+
id: z.string(),
|
|
7908
|
+
name: z.string()
|
|
7909
|
+
}),
|
|
7910
|
+
/** Every live pipe into that app, so "a Clerk source already feeds this" is visible. */
|
|
7911
|
+
sources: z.array(sourceSchema.pick({
|
|
7912
|
+
id: true,
|
|
7913
|
+
kind: true,
|
|
7914
|
+
configured: true,
|
|
7915
|
+
lastReceivedAt: true
|
|
7916
|
+
}))
|
|
7917
|
+
});
|
|
7918
|
+
|
|
7876
7919
|
//#endregion
|
|
7877
7920
|
//#region ../../packages/api-client/src/client.ts
|
|
7878
7921
|
var ApiError = class extends Error {
|
|
@@ -7897,7 +7940,7 @@ async function send(baseUrl, init) {
|
|
|
7897
7940
|
body: typeof init.body === "string" || init.body === null ? init.body : Uint8Array.from(init.body)
|
|
7898
7941
|
});
|
|
7899
7942
|
} catch {
|
|
7900
|
-
throw new ApiError("network_error", `Could not reach the API at ${baseUrl}.
|
|
7943
|
+
throw new ApiError("network_error", `Could not reach the API at ${baseUrl}. Check the address, or set COW_API_URL to the one you meant.`, 0);
|
|
7901
7944
|
}
|
|
7902
7945
|
const requestId = response.headers.get("X-Request-Id") ?? void 0;
|
|
7903
7946
|
if (init.binary && response.ok) return {
|
|
@@ -8475,8 +8518,8 @@ function isNode(value) {
|
|
|
8475
8518
|
*/
|
|
8476
8519
|
const execFileAsync$1 = promisify(execFile);
|
|
8477
8520
|
/** Where build output lives, relative to the project root. */
|
|
8478
|
-
const BUILD_DIR =
|
|
8479
|
-
const TYPES_FILE =
|
|
8521
|
+
const BUILD_DIR = "build";
|
|
8522
|
+
const TYPES_FILE = "types.d.ts";
|
|
8480
8523
|
/**
|
|
8481
8524
|
* The config files the source tarball carries besides `journeys/` and
|
|
8482
8525
|
* `emails/`. Always the committed `cow.json`, never a `--config` override: a
|
|
@@ -8597,7 +8640,7 @@ function assertSegmentResolves(relPath, trigger, segmentKeys) {
|
|
|
8597
8640
|
throw new Error(`${relPath}: this journey enrolls from the segment "${trigger.segment}", and there is no segments/${trigger.segment}.ts. ${available} Add that file, or write the predicate list in the trigger.`);
|
|
8598
8641
|
}
|
|
8599
8642
|
/**
|
|
8600
|
-
*
|
|
8643
|
+
* `types.d.ts`: one `typeof import(...)` per template, merged into the
|
|
8601
8644
|
* SDK's `CowTemplates`, which is what types `api.send.email`. Written before
|
|
8602
8645
|
* the typecheck, because the typecheck is what it exists for.
|
|
8603
8646
|
*/
|
|
@@ -8607,7 +8650,7 @@ import type { z } from "zod";
|
|
|
8607
8650
|
|
|
8608
8651
|
declare module "@cowliss/cli/journeys" {
|
|
8609
8652
|
interface CowTemplates {
|
|
8610
|
-
${templates.map((template) => ` "${template.key}": z.infer<typeof import("
|
|
8653
|
+
${templates.map((template) => ` "${template.key}": z.infer<typeof import("./${template.relPath.replace(/\.tsx$/, "")}").props>;`).join("\n")}
|
|
8611
8654
|
}
|
|
8612
8655
|
}
|
|
8613
8656
|
|
|
@@ -8851,6 +8894,21 @@ function setConfigFile(name) {
|
|
|
8851
8894
|
function projectConfigFile() {
|
|
8852
8895
|
return configFile;
|
|
8853
8896
|
}
|
|
8897
|
+
/**
|
|
8898
|
+
* The directory a cow project lives in: `.cowliss/` under the repo root, so
|
|
8899
|
+
* a host repository gets one directory instead of four plus a config file.
|
|
8900
|
+
* Standing inside the project directory itself (a `cow.json` right here) is
|
|
8901
|
+
* the other answer, which is how the project directory is also a directory
|
|
8902
|
+
* you can cd into and run `cow` from.
|
|
8903
|
+
*
|
|
8904
|
+
* A missing `.cowliss/` still resolves to it rather than to the cwd: the
|
|
8905
|
+
* error then names the path a project would be at, and `cow init` and
|
|
8906
|
+
* `cow pull` create it.
|
|
8907
|
+
*/
|
|
8908
|
+
const PROJECT_DIR = ".cowliss";
|
|
8909
|
+
function resolveProjectDir(cwd = process.cwd()) {
|
|
8910
|
+
return existsSync(join(cwd, projectConfigFile())) ? cwd : join(cwd, PROJECT_DIR);
|
|
8911
|
+
}
|
|
8854
8912
|
/** The project's config; throws unless `projectDir` holds a valid one. */
|
|
8855
8913
|
async function assertCowConfig(projectDir) {
|
|
8856
8914
|
const name = projectConfigFile();
|
|
@@ -8888,9 +8946,9 @@ async function configuredApp(projectDir) {
|
|
|
8888
8946
|
}
|
|
8889
8947
|
}
|
|
8890
8948
|
/**
|
|
8891
|
-
* Builds the project at `projectDir`. Writes
|
|
8949
|
+
* Builds the project at `projectDir`. Writes `types.d.ts` first (the
|
|
8892
8950
|
* typecheck needs it), then the bundles and the source tarball, and
|
|
8893
|
-
*
|
|
8951
|
+
* `build/manifest.json` last, so a failed build leaves no manifest and
|
|
8894
8952
|
* the next `cow test` rebuilds.
|
|
8895
8953
|
*/
|
|
8896
8954
|
async function buildProject(projectDir) {
|
|
@@ -9199,7 +9257,7 @@ async function copyBlueprint(name, dir, force) {
|
|
|
9199
9257
|
}
|
|
9200
9258
|
function registerAdd(program, io) {
|
|
9201
9259
|
program.command("add").description("add gallery blueprint code to a cow repo").command("blueprint <name>").description("copy a blueprint's journeys, emails, segments, and scenarios into this repo (local: no API call)").option("--force", "overwrite files that already exist").action(async (name, opts) => {
|
|
9202
|
-
const dir =
|
|
9260
|
+
const dir = resolveProjectDir();
|
|
9203
9261
|
await assertCowConfig(dir);
|
|
9204
9262
|
const files = await copyBlueprint(name, dir, opts.force === true);
|
|
9205
9263
|
emit({ data: {
|
|
@@ -9211,170 +9269,6 @@ function registerAdd(program, io) {
|
|
|
9211
9269
|
});
|
|
9212
9270
|
}
|
|
9213
9271
|
|
|
9214
|
-
//#endregion
|
|
9215
|
-
//#region src/lib/login.ts
|
|
9216
|
-
/**
|
|
9217
|
-
* Login via the dashboard's /cli-auth bridge: start a loopback server, open
|
|
9218
|
-
* the web app there, and the signed-in browser POSTs the Clerk session token
|
|
9219
|
-
* back. `--token <jwt>` bypasses the browser entirely (agents, CI).
|
|
9220
|
-
*/
|
|
9221
|
-
async function login(env, options) {
|
|
9222
|
-
let token;
|
|
9223
|
-
if (options.token !== void 0) {
|
|
9224
|
-
token = options.token.trim();
|
|
9225
|
-
if (decodeTokenPayload(token) === null) throw new Error("The provided --token value is not a JWT");
|
|
9226
|
-
} else token = await collectTokenViaBrowser(resolveWebUrl(env, (await readCowConfig(process.cwd()))?.webUrl, options.webUrl), 3e5, options.noOpen === true);
|
|
9227
|
-
const claims = decodeSessionToken(token);
|
|
9228
|
-
if (claims === null) throw new Error("Token is not a decodable JWT");
|
|
9229
|
-
if (claims.userId === null) throw new Error("Token has no subject claim. Is it a Clerk session token?");
|
|
9230
|
-
const apiUrl = options.apiFlag ?? resolveApiUrl(env, null, void 0, (await readCowConfig(process.cwd()))?.apiUrl);
|
|
9231
|
-
return {
|
|
9232
|
-
credentialsPath: await writeCredentials(env, {
|
|
9233
|
-
token,
|
|
9234
|
-
apiUrl
|
|
9235
|
-
}),
|
|
9236
|
-
apiUrl,
|
|
9237
|
-
userId: claims.userId,
|
|
9238
|
-
orgId: claims.orgId,
|
|
9239
|
-
role: claims.role,
|
|
9240
|
-
hasOrg: claims.orgId !== null
|
|
9241
|
-
};
|
|
9242
|
-
}
|
|
9243
|
-
/** Open the dashboard bridge and await one POST of { token }. */
|
|
9244
|
-
function collectTokenViaBrowser(webUrl, timeoutMs = 18e4, noOpen = false) {
|
|
9245
|
-
return new Promise((resolve, reject) => {
|
|
9246
|
-
let server = null;
|
|
9247
|
-
const timer = setTimeout(() => {
|
|
9248
|
-
server?.close();
|
|
9249
|
-
reject(/* @__PURE__ */ new Error(`Timed out waiting for the browser to sign in (${Math.round(timeoutMs / 1e3)}s). Or paste a token: \`cow login --token <jwt>\`.`));
|
|
9250
|
-
}, timeoutMs);
|
|
9251
|
-
server = createServer((req, res) => {
|
|
9252
|
-
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
9253
|
-
res.setHeader("Access-Control-Allow-Methods", "POST, OPTIONS");
|
|
9254
|
-
res.setHeader("Access-Control-Allow-Headers", "Content-Type");
|
|
9255
|
-
if (req.method === "OPTIONS") {
|
|
9256
|
-
res.writeHead(204).end();
|
|
9257
|
-
return;
|
|
9258
|
-
}
|
|
9259
|
-
if (req.method !== "POST") {
|
|
9260
|
-
res.writeHead(405).end();
|
|
9261
|
-
return;
|
|
9262
|
-
}
|
|
9263
|
-
const chunks = [];
|
|
9264
|
-
req.on("data", (chunk) => chunks.push(chunk));
|
|
9265
|
-
req.on("end", () => {
|
|
9266
|
-
clearTimeout(timer);
|
|
9267
|
-
try {
|
|
9268
|
-
const body = JSON.parse(Buffer.concat(chunks).toString("utf8"));
|
|
9269
|
-
const field = (name) => typeof body === "object" && body !== null && name in body ? body[name] : void 0;
|
|
9270
|
-
if (field("denied") === true) {
|
|
9271
|
-
res.writeHead(200, { "Content-Type": "application/json" });
|
|
9272
|
-
res.end(JSON.stringify({ ok: true }));
|
|
9273
|
-
reject(/* @__PURE__ */ new Error("Sign-in was denied in the browser. No access was granted."));
|
|
9274
|
-
return;
|
|
9275
|
-
}
|
|
9276
|
-
const token = field("token");
|
|
9277
|
-
if (typeof token !== "string" || token.length === 0) {
|
|
9278
|
-
res.writeHead(400, { "Content-Type": "application/json" });
|
|
9279
|
-
res.end(JSON.stringify({ error: "missing token" }));
|
|
9280
|
-
reject(/* @__PURE__ */ new Error("The browser bridge sent no token"));
|
|
9281
|
-
return;
|
|
9282
|
-
}
|
|
9283
|
-
res.writeHead(200, { "Content-Type": "application/json" });
|
|
9284
|
-
res.end(JSON.stringify({ ok: true }));
|
|
9285
|
-
resolve(token);
|
|
9286
|
-
} catch (error) {
|
|
9287
|
-
reject(error instanceof Error ? error : new Error(String(error)));
|
|
9288
|
-
} finally {
|
|
9289
|
-
server?.close();
|
|
9290
|
-
}
|
|
9291
|
-
});
|
|
9292
|
-
});
|
|
9293
|
-
server.listen(0, "127.0.0.1", () => {
|
|
9294
|
-
const address = server?.address();
|
|
9295
|
-
if (address === null || typeof address !== "object") {
|
|
9296
|
-
reject(/* @__PURE__ */ new Error("Could not bind a loopback port"));
|
|
9297
|
-
return;
|
|
9298
|
-
}
|
|
9299
|
-
const callbackUrl = `http://127.0.0.1:${address.port}/cb`;
|
|
9300
|
-
const target = `${webUrl}/cli-auth?cb=${encodeURIComponent(callbackUrl)}`;
|
|
9301
|
-
if (!noOpen) openBrowser(target);
|
|
9302
|
-
process.stderr.write(`Waiting for sign-in at:\n ${target}\n`);
|
|
9303
|
-
});
|
|
9304
|
-
server.on("error", (error) => {
|
|
9305
|
-
clearTimeout(timer);
|
|
9306
|
-
reject(error);
|
|
9307
|
-
});
|
|
9308
|
-
});
|
|
9309
|
-
}
|
|
9310
|
-
function openBrowser(url) {
|
|
9311
|
-
const cmd = process.platform === "darwin" ? "open" : process.platform === "linux" ? "xdg-open" : null;
|
|
9312
|
-
if (cmd === null) return;
|
|
9313
|
-
spawn(cmd, [url], {
|
|
9314
|
-
stdio: "ignore",
|
|
9315
|
-
detached: true
|
|
9316
|
-
}).unref();
|
|
9317
|
-
}
|
|
9318
|
-
|
|
9319
|
-
//#endregion
|
|
9320
|
-
//#region src/commands/auth.ts
|
|
9321
|
-
/** login/logout/whoami: session-token management, no contract route. */
|
|
9322
|
-
function registerAuth(program, env, io) {
|
|
9323
|
-
program.command("login").description("authenticate via the dashboard browser flow (or --token) and cache the session token").option("--token <jwt>", "paste a session token instead of the browser flow").option("--web <url>", `dashboard URL; overrides COW_WEB_URL and webUrl in cow.json (default ${DEFAULT_WEB_URL})`).option("--no-open", "do not open the browser; print the URL and wait (headless/agent use)").action(async (opts) => {
|
|
9324
|
-
const outcome = await login(env, {
|
|
9325
|
-
token: opts.token,
|
|
9326
|
-
webUrl: opts.web,
|
|
9327
|
-
apiFlag: opts.api,
|
|
9328
|
-
noOpen: opts.open === false
|
|
9329
|
-
});
|
|
9330
|
-
emit({ data: {
|
|
9331
|
-
...outcome,
|
|
9332
|
-
warnings: outcome.hasOrg ? void 0 : ["The token carries no active organization. Select one in the dashboard and log in again."]
|
|
9333
|
-
} }, io, opts.json);
|
|
9334
|
-
});
|
|
9335
|
-
program.command("logout").description("delete the cached session token").action(async (opts) => {
|
|
9336
|
-
const removed = await clearCredentials(env);
|
|
9337
|
-
emit({ data: { removed } }, io, opts.json);
|
|
9338
|
-
});
|
|
9339
|
-
program.command("whoami").description("show which credential commands will use, and for a session its subject, org, role, and expiry").action(async (opts) => {
|
|
9340
|
-
const credentials = await readCredentials(env);
|
|
9341
|
-
const { kind } = resolveCredential(env, credentials);
|
|
9342
|
-
if (kind === "none") throw new Error("Not logged in. Run `cow login` first, or set COW_PIPELINE_KEY.");
|
|
9343
|
-
if (kind === "pipelineKey") {
|
|
9344
|
-
emit({ data: {
|
|
9345
|
-
credential: kind,
|
|
9346
|
-
source: "COW_PIPELINE_KEY",
|
|
9347
|
-
note: "A pipeline key may push, enable, disable, and read executions."
|
|
9348
|
-
} }, io, opts.json);
|
|
9349
|
-
return;
|
|
9350
|
-
}
|
|
9351
|
-
const claims = decodeSessionToken(credentials?.token ?? "");
|
|
9352
|
-
if (!claims) throw new Error("Cached token is not decodable. Run `cow login` again.");
|
|
9353
|
-
const projectApiUrl = (await readCowConfig(process.cwd()))?.apiUrl;
|
|
9354
|
-
const apiUrl = resolveApiUrl(env, credentials, opts.api, projectApiUrl);
|
|
9355
|
-
const mintedFor = credentials?.apiUrl;
|
|
9356
|
-
emit({ data: {
|
|
9357
|
-
credential: kind,
|
|
9358
|
-
apiUrl,
|
|
9359
|
-
mintedFor: mintedFor ?? null,
|
|
9360
|
-
...mintedFor !== void 0 && mintedFor !== apiUrl ? { warning: `This token was minted for ${mintedFor}, but commands here talk to ${apiUrl}. Run \`cow login\` again from this directory.` } : {},
|
|
9361
|
-
...claims
|
|
9362
|
-
} }, io, opts.json);
|
|
9363
|
-
});
|
|
9364
|
-
}
|
|
9365
|
-
|
|
9366
|
-
//#endregion
|
|
9367
|
-
//#region src/commands/build.ts
|
|
9368
|
-
/**
|
|
9369
|
-
* `cow build`: typecheck, bundle, and write `.cow/build` for the project in
|
|
9370
|
-
* the current directory. Local only, no API call and no network.
|
|
9371
|
-
*/
|
|
9372
|
-
function registerBuild(program, io) {
|
|
9373
|
-
program.command("build").description("typecheck and bundle the repo in the current directory, writing .cow/build (local: no API call)").action(async (opts) => {
|
|
9374
|
-
emit({ data: await buildProject(process.cwd()) }, io, opts.json);
|
|
9375
|
-
});
|
|
9376
|
-
}
|
|
9377
|
-
|
|
9378
9272
|
//#endregion
|
|
9379
9273
|
//#region ../../node_modules/.pnpm/@asteasolutions+zod-to-openapi@9.1.0_zod@4.4.3/node_modules/@asteasolutions/zod-to-openapi/dist/index.cjs
|
|
9380
9274
|
var require_dist = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
@@ -10659,6 +10553,19 @@ const ingestion = defineModule(defineRoute({
|
|
|
10659
10553
|
200: envelope(batchResultDtoSchema, "Per-item outcomes, positionally aligned"),
|
|
10660
10554
|
...ingestionErrors
|
|
10661
10555
|
}
|
|
10556
|
+
}), defineRoute({
|
|
10557
|
+
method: "get",
|
|
10558
|
+
path: "/v1/whoami",
|
|
10559
|
+
operationId: "ingestion.whoami",
|
|
10560
|
+
tags: ["ingestion"],
|
|
10561
|
+
summary: "Which app a source id writes into",
|
|
10562
|
+
security: API_KEY_AUTH,
|
|
10563
|
+
surfaces: HIDDEN_FROM_TOOLS,
|
|
10564
|
+
request: { query: whoamiQuerySchema },
|
|
10565
|
+
responses: {
|
|
10566
|
+
200: envelope(whoamiSchema, "The source's org, app, and sibling sources"),
|
|
10567
|
+
...errors("invalid_key", "not_found", "validation_failed", "rate_limited")
|
|
10568
|
+
}
|
|
10662
10569
|
}));
|
|
10663
10570
|
|
|
10664
10571
|
//#endregion
|
|
@@ -11887,6 +11794,180 @@ const allRoutes = Object.values(contract).flatMap((module) => Object.values(modu
|
|
|
11887
11794
|
/** The tags in the order their modules are declared. */
|
|
11888
11795
|
const tags = [...new Set(allRoutes.flatMap((route) => route.tags ?? []))];
|
|
11889
11796
|
|
|
11797
|
+
//#endregion
|
|
11798
|
+
//#region src/lib/login.ts
|
|
11799
|
+
/**
|
|
11800
|
+
* Login via the dashboard's /cli-auth bridge: start a loopback server, open
|
|
11801
|
+
* the web app there, and the signed-in browser POSTs the Clerk session token
|
|
11802
|
+
* back. `--token <jwt>` bypasses the browser entirely (agents, CI).
|
|
11803
|
+
*/
|
|
11804
|
+
async function login(env, options) {
|
|
11805
|
+
let token;
|
|
11806
|
+
if (options.token !== void 0) {
|
|
11807
|
+
token = options.token.trim();
|
|
11808
|
+
if (decodeTokenPayload(token) === null) throw new Error("The provided --token value is not a JWT");
|
|
11809
|
+
} else token = await collectTokenViaBrowser(resolveWebUrl(env, (await readCowConfig(process.cwd()))?.webUrl, options.webUrl), 3e5, options.noOpen === true);
|
|
11810
|
+
const claims = decodeSessionToken(token);
|
|
11811
|
+
if (claims === null) throw new Error("Token is not a decodable JWT");
|
|
11812
|
+
if (claims.userId === null) throw new Error("Token has no subject claim. Is it a Clerk session token?");
|
|
11813
|
+
const apiUrl = options.apiFlag ?? resolveApiUrl(env, null, void 0, (await readCowConfig(process.cwd()))?.apiUrl);
|
|
11814
|
+
return {
|
|
11815
|
+
credentialsPath: await writeCredentials(env, {
|
|
11816
|
+
token,
|
|
11817
|
+
apiUrl
|
|
11818
|
+
}),
|
|
11819
|
+
apiUrl,
|
|
11820
|
+
userId: claims.userId,
|
|
11821
|
+
orgId: claims.orgId,
|
|
11822
|
+
role: claims.role,
|
|
11823
|
+
hasOrg: claims.orgId !== null
|
|
11824
|
+
};
|
|
11825
|
+
}
|
|
11826
|
+
/** Open the dashboard bridge and await one POST of { token }. */
|
|
11827
|
+
function collectTokenViaBrowser(webUrl, timeoutMs = 18e4, noOpen = false) {
|
|
11828
|
+
return new Promise((resolve, reject) => {
|
|
11829
|
+
let server = null;
|
|
11830
|
+
const timer = setTimeout(() => {
|
|
11831
|
+
server?.close();
|
|
11832
|
+
reject(/* @__PURE__ */ new Error(`Timed out waiting for the browser to sign in (${Math.round(timeoutMs / 1e3)}s). Or paste a token: \`cow login --token <jwt>\`.`));
|
|
11833
|
+
}, timeoutMs);
|
|
11834
|
+
server = createServer((req, res) => {
|
|
11835
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
11836
|
+
res.setHeader("Access-Control-Allow-Methods", "POST, OPTIONS");
|
|
11837
|
+
res.setHeader("Access-Control-Allow-Headers", "Content-Type");
|
|
11838
|
+
if (req.method === "OPTIONS") {
|
|
11839
|
+
res.writeHead(204).end();
|
|
11840
|
+
return;
|
|
11841
|
+
}
|
|
11842
|
+
if (req.method !== "POST") {
|
|
11843
|
+
res.writeHead(405).end();
|
|
11844
|
+
return;
|
|
11845
|
+
}
|
|
11846
|
+
const chunks = [];
|
|
11847
|
+
req.on("data", (chunk) => chunks.push(chunk));
|
|
11848
|
+
req.on("end", () => {
|
|
11849
|
+
clearTimeout(timer);
|
|
11850
|
+
try {
|
|
11851
|
+
const body = JSON.parse(Buffer.concat(chunks).toString("utf8"));
|
|
11852
|
+
const field = (name) => typeof body === "object" && body !== null && name in body ? body[name] : void 0;
|
|
11853
|
+
if (field("denied") === true) {
|
|
11854
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
11855
|
+
res.end(JSON.stringify({ ok: true }));
|
|
11856
|
+
reject(/* @__PURE__ */ new Error("Sign-in was denied in the browser. No access was granted."));
|
|
11857
|
+
return;
|
|
11858
|
+
}
|
|
11859
|
+
const token = field("token");
|
|
11860
|
+
if (typeof token !== "string" || token.length === 0) {
|
|
11861
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
11862
|
+
res.end(JSON.stringify({ error: "missing token" }));
|
|
11863
|
+
reject(/* @__PURE__ */ new Error("The browser bridge sent no token"));
|
|
11864
|
+
return;
|
|
11865
|
+
}
|
|
11866
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
11867
|
+
res.end(JSON.stringify({ ok: true }));
|
|
11868
|
+
resolve(token);
|
|
11869
|
+
} catch (error) {
|
|
11870
|
+
reject(error instanceof Error ? error : new Error(String(error)));
|
|
11871
|
+
} finally {
|
|
11872
|
+
server?.close();
|
|
11873
|
+
}
|
|
11874
|
+
});
|
|
11875
|
+
});
|
|
11876
|
+
server.listen(0, "127.0.0.1", () => {
|
|
11877
|
+
const address = server?.address();
|
|
11878
|
+
if (address === null || typeof address !== "object") {
|
|
11879
|
+
reject(/* @__PURE__ */ new Error("Could not bind a loopback port"));
|
|
11880
|
+
return;
|
|
11881
|
+
}
|
|
11882
|
+
const callbackUrl = `http://127.0.0.1:${address.port}/cb`;
|
|
11883
|
+
const target = `${webUrl}/cli-auth?cb=${encodeURIComponent(callbackUrl)}`;
|
|
11884
|
+
if (!noOpen) openBrowser(target);
|
|
11885
|
+
process.stderr.write(`Waiting for sign-in at:\n ${target}\n`);
|
|
11886
|
+
});
|
|
11887
|
+
server.on("error", (error) => {
|
|
11888
|
+
clearTimeout(timer);
|
|
11889
|
+
reject(error);
|
|
11890
|
+
});
|
|
11891
|
+
});
|
|
11892
|
+
}
|
|
11893
|
+
function openBrowser(url) {
|
|
11894
|
+
const cmd = process.platform === "darwin" ? "open" : process.platform === "linux" ? "xdg-open" : null;
|
|
11895
|
+
if (cmd === null) return;
|
|
11896
|
+
spawn(cmd, [url], {
|
|
11897
|
+
stdio: "ignore",
|
|
11898
|
+
detached: true
|
|
11899
|
+
}).unref();
|
|
11900
|
+
}
|
|
11901
|
+
|
|
11902
|
+
//#endregion
|
|
11903
|
+
//#region src/commands/auth.ts
|
|
11904
|
+
/** login/logout/whoami: session-token management, no contract route. */
|
|
11905
|
+
function registerAuth(program, env, io) {
|
|
11906
|
+
program.command("login").description("authenticate via the dashboard browser flow (or --token) and cache the session token").option("--token <jwt>", "paste a session token instead of the browser flow").option("--web <url>", `dashboard URL; overrides COW_WEB_URL and webUrl in cow.json (default ${DEFAULT_WEB_URL})`).option("--no-open", "do not open the browser; print the URL and wait (headless/agent use)").action(async (opts) => {
|
|
11907
|
+
const outcome = await login(env, {
|
|
11908
|
+
token: opts.token,
|
|
11909
|
+
webUrl: opts.web,
|
|
11910
|
+
apiFlag: opts.api,
|
|
11911
|
+
noOpen: opts.open === false
|
|
11912
|
+
});
|
|
11913
|
+
emit({ data: {
|
|
11914
|
+
...outcome,
|
|
11915
|
+
warnings: outcome.hasOrg ? void 0 : ["The token carries no active organization. Select one in the dashboard and log in again."]
|
|
11916
|
+
} }, io, opts.json);
|
|
11917
|
+
});
|
|
11918
|
+
program.command("logout").description("delete the cached session token").action(async (opts) => {
|
|
11919
|
+
const removed = await clearCredentials(env);
|
|
11920
|
+
emit({ data: { removed } }, io, opts.json);
|
|
11921
|
+
});
|
|
11922
|
+
program.command("whoami").description("show which credential commands will use, and for a session its subject, org, role, and expiry").option("--key <ak_...>", "ask the API what an ingestion key writes into, instead of reading the cached session").option("--source <src_...>", "the source id that key is about to send to (required with --key)").action(async (opts) => {
|
|
11923
|
+
if (opts.key) {
|
|
11924
|
+
if (!opts.source) throw new Error("`--key` needs `--source <src_...>`: the id you are about to send to is what this checks.");
|
|
11925
|
+
const client = createClient({
|
|
11926
|
+
baseUrl: resolveApiUrl(env, await readCredentials(env), opts.api, (await readCowConfig(resolveProjectDir()))?.apiUrl),
|
|
11927
|
+
auth: () => opts.key ?? null,
|
|
11928
|
+
headers: { [CLIENT_HEADER]: `@cowliss/cli/${(await readCliPackage()).version}` }
|
|
11929
|
+
});
|
|
11930
|
+
emit(await client.request(contract.ingestion["ingestion.whoami"], { query: { sourceId: opts.source } }), io, opts.json);
|
|
11931
|
+
return;
|
|
11932
|
+
}
|
|
11933
|
+
const credentials = await readCredentials(env);
|
|
11934
|
+
const { kind } = resolveCredential(env, credentials);
|
|
11935
|
+
if (kind === "none") throw new Error("Not logged in. Run `cow login` first, or set COW_PIPELINE_KEY.");
|
|
11936
|
+
if (kind === "pipelineKey") {
|
|
11937
|
+
emit({ data: {
|
|
11938
|
+
credential: kind,
|
|
11939
|
+
source: "COW_PIPELINE_KEY",
|
|
11940
|
+
note: "A pipeline key may push, enable, disable, and read executions."
|
|
11941
|
+
} }, io, opts.json);
|
|
11942
|
+
return;
|
|
11943
|
+
}
|
|
11944
|
+
const claims = decodeSessionToken(credentials?.token ?? "");
|
|
11945
|
+
if (!claims) throw new Error("Cached token is not decodable. Run `cow login` again.");
|
|
11946
|
+
const projectApiUrl = (await readCowConfig(resolveProjectDir()))?.apiUrl;
|
|
11947
|
+
const apiUrl = resolveApiUrl(env, credentials, opts.api, projectApiUrl);
|
|
11948
|
+
const mintedFor = credentials?.apiUrl;
|
|
11949
|
+
emit({ data: {
|
|
11950
|
+
credential: kind,
|
|
11951
|
+
apiUrl,
|
|
11952
|
+
mintedFor: mintedFor ?? null,
|
|
11953
|
+
...mintedFor !== void 0 && mintedFor !== apiUrl ? { warning: `This token was minted for ${mintedFor}, but commands here talk to ${apiUrl}. Run \`cow login\` again from this directory.` } : {},
|
|
11954
|
+
...claims
|
|
11955
|
+
} }, io, opts.json);
|
|
11956
|
+
});
|
|
11957
|
+
}
|
|
11958
|
+
|
|
11959
|
+
//#endregion
|
|
11960
|
+
//#region src/commands/build.ts
|
|
11961
|
+
/**
|
|
11962
|
+
* `cow build`: typecheck, bundle, and write `build/` for the project in
|
|
11963
|
+
* the current directory. Local only, no API call and no network.
|
|
11964
|
+
*/
|
|
11965
|
+
function registerBuild(program, io) {
|
|
11966
|
+
program.command("build").description("typecheck and bundle the repo in the current directory, writing .cowliss/build (local: no API call)").action(async (opts) => {
|
|
11967
|
+
emit({ data: await buildProject(resolveProjectDir()) }, io, opts.json);
|
|
11968
|
+
});
|
|
11969
|
+
}
|
|
11970
|
+
|
|
11890
11971
|
//#endregion
|
|
11891
11972
|
//#region ../../packages/shared/src/contract/surfaces.ts
|
|
11892
11973
|
/** camelCase field name → kebab-case flag body (`sourceId` → `source-id`). */
|
|
@@ -12358,7 +12439,7 @@ function flipSummary(journeys, status) {
|
|
|
12358
12439
|
* segment's own key joins the set the events are filtered by.
|
|
12359
12440
|
*/
|
|
12360
12441
|
async function unseenForKeys(client, keys) {
|
|
12361
|
-
const dir =
|
|
12442
|
+
const dir = resolveProjectDir();
|
|
12362
12443
|
const project = await assertCowConfig(dir).then(async (config) => ({
|
|
12363
12444
|
config,
|
|
12364
12445
|
built: await buildProject(dir)
|
|
@@ -12384,7 +12465,7 @@ function register(program, clientFor, io, verb, status) {
|
|
|
12384
12465
|
};
|
|
12385
12466
|
if (merged.all === true && keys.length > 0) throw new Error(`Name the journeys or pass --all, not both: cow ${verb} welcome.`);
|
|
12386
12467
|
if (merged.all !== true && keys.length === 0) throw new Error(`Name at least one journey, or pass --all: cow ${verb} welcome.`);
|
|
12387
|
-
const selector = merged.all === true ? { appId: (await assertCowConfig(
|
|
12468
|
+
const selector = merged.all === true ? { appId: (await assertCowConfig(resolveProjectDir())).appId } : { keys };
|
|
12388
12469
|
const client = await clientFor(merged);
|
|
12389
12470
|
const updated = await setStatus(client, selector, status);
|
|
12390
12471
|
if (merged.json === true) {
|
|
@@ -12484,7 +12565,7 @@ async function chooseApp(client, webUrl, flag, org, io, skipPrompts) {
|
|
|
12484
12565
|
return { appId: await ask("App id", only ?? "", io, false) };
|
|
12485
12566
|
}
|
|
12486
12567
|
function registerInit(program, clientFor, env, io) {
|
|
12487
|
-
program.command("init").argument("[dir]", "
|
|
12568
|
+
program.command("init").argument("[dir]", "repository to create the project in (default: here)").description("create a cow project in .cowliss/: cow.json, journeys/, emails/, segments/, and the files to build them").option("--org <id>", "organization id (default: the logged-in org)").option("--app <id>", "app id this repository pushes to (default: ask, or the org's only one)").option("--yes", "accept the defaults instead of prompting").option("--blueprint <name>", "start from a gallery blueprint").action(async (dirArg, opts) => {
|
|
12488
12569
|
const merged = {
|
|
12489
12570
|
...program.opts(),
|
|
12490
12571
|
...opts
|
|
@@ -12493,7 +12574,8 @@ function registerInit(program, clientFor, env, io) {
|
|
|
12493
12574
|
const cwd = process.cwd();
|
|
12494
12575
|
const defaultName = basename(dirArg ? resolve(cwd, dirArg) : cwd);
|
|
12495
12576
|
const name = await ask("Repo name", defaultName, io, skipPrompts);
|
|
12496
|
-
const
|
|
12577
|
+
const repo = dirArg ? resolve(cwd, dirArg) : name === defaultName ? cwd : resolve(cwd, name);
|
|
12578
|
+
const dir = join(repo, PROJECT_DIR);
|
|
12497
12579
|
if (existsSync(join(dir, "cow.json"))) throw new Error(`"${dir}" already holds a cow.json. Use \`cow add blueprint\` to add code to it.`);
|
|
12498
12580
|
const credentials = await readCredentials(env);
|
|
12499
12581
|
const claimedOrg = credentials ? decodeSessionToken(credentials.token)?.orgId ?? null : null;
|
|
@@ -12523,10 +12605,10 @@ function registerInit(program, clientFor, env, io) {
|
|
|
12523
12605
|
"journeys",
|
|
12524
12606
|
"emails",
|
|
12525
12607
|
"segments",
|
|
12526
|
-
"
|
|
12608
|
+
"types.d.ts"
|
|
12527
12609
|
]
|
|
12528
12610
|
}),
|
|
12529
|
-
".gitignore": ".
|
|
12611
|
+
".gitignore": "build/\ntypes.d.ts\nnode_modules/\n",
|
|
12530
12612
|
"journeys/.gitkeep": "",
|
|
12531
12613
|
"emails/.gitkeep": "",
|
|
12532
12614
|
"segments/.gitkeep": ""
|
|
@@ -12548,7 +12630,7 @@ function registerInit(program, clientFor, env, io) {
|
|
|
12548
12630
|
const journey = scenario.slice(10).split(".")[0] ?? "abandoned-checkout";
|
|
12549
12631
|
const appLine = app.appId === "" ? `No app named yet (${app.reason}). Put its id in cow.json's "appId"; you will find it on the app's page at ${webUrl}/apps.` : `Pushes to ${app.appId}`;
|
|
12550
12632
|
io.stderr([
|
|
12551
|
-
`Created a cow
|
|
12633
|
+
`Created a cow project in ${here}`,
|
|
12552
12634
|
appLine,
|
|
12553
12635
|
"",
|
|
12554
12636
|
"Next steps:",
|
|
@@ -12700,8 +12782,8 @@ function registerStatus(program, clientFor, io) {
|
|
|
12700
12782
|
...program.opts(),
|
|
12701
12783
|
...opts
|
|
12702
12784
|
};
|
|
12703
|
-
const config = await assertCowConfig(
|
|
12704
|
-
const built = await buildProject(
|
|
12785
|
+
const config = await assertCowConfig(resolveProjectDir());
|
|
12786
|
+
const built = await buildProject(resolveProjectDir()).catch(() => null);
|
|
12705
12787
|
const client = await clientFor(merged);
|
|
12706
12788
|
const status = await readStatus(client, config.appId);
|
|
12707
12789
|
if (merged.json === true) {
|
|
@@ -12939,7 +13021,7 @@ function registerPush(program, clientFor, env, io) {
|
|
|
12939
13021
|
...opts
|
|
12940
13022
|
};
|
|
12941
13023
|
const client = await clientFor(merged);
|
|
12942
|
-
const projectDir =
|
|
13024
|
+
const projectDir = resolveProjectDir();
|
|
12943
13025
|
const json = merged.json === true;
|
|
12944
13026
|
const { plan, built } = await planProject({
|
|
12945
13027
|
client,
|
|
@@ -13007,7 +13089,7 @@ function registerPush(program, clientFor, env, io) {
|
|
|
13007
13089
|
async function projectPlan(client) {
|
|
13008
13090
|
const { plan } = await planProject({
|
|
13009
13091
|
client,
|
|
13010
|
-
projectDir:
|
|
13092
|
+
projectDir: resolveProjectDir()
|
|
13011
13093
|
});
|
|
13012
13094
|
return { data: plan };
|
|
13013
13095
|
}
|
|
@@ -13020,7 +13102,7 @@ function registerPlan(program, clientFor, io) {
|
|
|
13020
13102
|
const client = await clientFor(merged);
|
|
13021
13103
|
const { plan, built } = await planProject({
|
|
13022
13104
|
client,
|
|
13023
|
-
projectDir:
|
|
13105
|
+
projectDir: resolveProjectDir()
|
|
13024
13106
|
});
|
|
13025
13107
|
if (merged.json === true) {
|
|
13026
13108
|
emit({ data: plan }, io, true);
|
|
@@ -13168,7 +13250,7 @@ const execFileAsync = promisify(execFile);
|
|
|
13168
13250
|
*
|
|
13169
13251
|
* Untracked files count: a journey the developer wrote and has not committed
|
|
13170
13252
|
* is exactly what the tarball's copy of the same path would bury, and it is
|
|
13171
|
-
* the only copy.
|
|
13253
|
+
* the only copy. `build/` and `node_modules/` are gitignored by `cow init`, so
|
|
13172
13254
|
* they never show up here.
|
|
13173
13255
|
*/
|
|
13174
13256
|
async function dirtyPaths(dir) {
|
|
@@ -13217,6 +13299,7 @@ async function pullPush({ client, projectDir, pushId, force }) {
|
|
|
13217
13299
|
}
|
|
13218
13300
|
const tarball = await client.request(contract.pushes["pushes.source"], { params: { id: push.id } });
|
|
13219
13301
|
const files = [];
|
|
13302
|
+
await mkdir(projectDir, { recursive: true });
|
|
13220
13303
|
const unpack = extract({
|
|
13221
13304
|
cwd: projectDir,
|
|
13222
13305
|
onReadEntry: (entry) => files.push(entry.path)
|
|
@@ -13239,7 +13322,7 @@ function registerPull(program, clientFor, io) {
|
|
|
13239
13322
|
};
|
|
13240
13323
|
const outcome = await pullPush({
|
|
13241
13324
|
client: await clientFor(merged),
|
|
13242
|
-
projectDir:
|
|
13325
|
+
projectDir: resolveProjectDir(),
|
|
13243
13326
|
pushId: opts.push,
|
|
13244
13327
|
force: opts.force === true
|
|
13245
13328
|
});
|
|
@@ -13494,7 +13577,7 @@ function registerTest(program, io) {
|
|
|
13494
13577
|
}
|
|
13495
13578
|
const parsed = journeyScenarioSchema.safeParse(json);
|
|
13496
13579
|
if (!parsed.success) throw new Error(`Scenario file "${opts.scenario}" is not a valid scenario: ${firstIssueMessage(parsed.error)}`);
|
|
13497
|
-
const projectDir =
|
|
13580
|
+
const projectDir = resolveProjectDir();
|
|
13498
13581
|
await buildIfStale(projectDir);
|
|
13499
13582
|
const result = await runScenario(projectDir, journey, parsed.data);
|
|
13500
13583
|
emit({ data: result }, io, opts.json);
|
|
@@ -13519,7 +13602,7 @@ function buildProgram(env, io) {
|
|
|
13519
13602
|
const clientFor = async (opts) => {
|
|
13520
13603
|
const credentials = await readCredentials(env);
|
|
13521
13604
|
return createClient({
|
|
13522
|
-
baseUrl: resolveApiUrl(env, credentials, typeof opts.api === "string" ? opts.api : void 0, (await readCowConfig(
|
|
13605
|
+
baseUrl: resolveApiUrl(env, credentials, typeof opts.api === "string" ? opts.api : void 0, (await readCowConfig(resolveProjectDir()))?.apiUrl),
|
|
13523
13606
|
auth: async () => resolveCredential(env, await readCredentials(env)).token,
|
|
13524
13607
|
headers: async () => ({ [CLIENT_HEADER]: `@cowliss/cli/${(await readCliPackage()).version}` })
|
|
13525
13608
|
});
|
|
@@ -13591,7 +13674,7 @@ function errorMessage(error) {
|
|
|
13591
13674
|
code: error.code,
|
|
13592
13675
|
message: error.message
|
|
13593
13676
|
} }, null, 2);
|
|
13594
|
-
if (error.code === "invalid_key") return `${envelope}\n(hint: run \`cow login\` to refresh the session token)`;
|
|
13677
|
+
if (error.code === "invalid_key" && !process.argv.includes("--key")) return `${envelope}\n(hint: run \`cow login\` to refresh the session token)`;
|
|
13595
13678
|
return envelope;
|
|
13596
13679
|
}
|
|
13597
13680
|
if (error instanceof Error) return `error: ${error.message}`;
|