Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions docs/product/command-spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -393,7 +393,7 @@ Purpose:
Behavior:

- init is the config formalizer: `app deploy` works with zero config, and init writes down what deploy would infer so the setup is committed, reviewable, and stable for teammates and CI
- writing the config requires no auth and no network; linking is the only remote step
- writing the config requires no auth and no network; the only steps that may go remote are the accepted types install, link, and agent skill install, all optional and all after the config is written
- fails with `INIT_CONFIG_EXISTS` when a compute config already exists in the invocation directory or any ancestor up to the repository or workspace root; the error names the existing file, and init never overwrites or merges — editing a committed config is the user's editor's job
- `--format <ts|json>` selects the config serialization; `ts` (alias `typescript`) is the default and writes `prisma.compute.ts`, `json` writes `prisma.compute.json` via the shared SDK serializer, a dependency-free static document (a `$schema` reference will be emitted once the schema is hosted; the loader already accepts and strips one); both formats pin exactly the same resolved values, and the CLI's global `--json` output flag is unrelated to the config format
- with `--format json`, the types install step never runs: the JSON format exists to be dependency-free, so `--install` is a usage error; without `--install`, the result reports the step as skipped with no install hint
Expand All @@ -418,6 +418,7 @@ Behavior:
- interactive mode asks `Link this directory to a Prisma Project now? (Y/n)` when the directory has no project binding; accepting enters the same picker `project link` uses
- `--no-link` suppresses the question; `--link` requires the step; `--project <id-or-name>` links to that project without prompting
- link failures and cancellations after the config is written downgrade to warnings and `nextSteps`; the config write stands and init exits 0
- agent skill step, after the link step: interactive runs prompt once to install the Prisma Compute skill for the project when `prisma-compute` is missing, the same shared prompt `app deploy` uses; accepting installs `prisma-compute` from `prisma/skills` from the config directory, equivalent to the detected package-runner command such as `pnpm dlx @prisma/cli@latest agent install --skill prisma-compute`; declining records the prompt as dismissed in local CLI state, so neither init nor deploy asks again; a failed install downgrades to a warning with the retry command, records no dismissal (a later interactive init or deploy offers again), and the config write stands; does not prompt in `--json`, `--quiet`, CI, non-interactive, or `--yes` runs
- `nextSteps` includes the deploy command, plus the project link command when the directory is still unlinked
- user-facing command hints in init output (next steps, link hints, error recovery commands) use the package runner detected from the project, such as `pnpm dlx @prisma/cli@latest project link` or `npx -y @prisma/cli@latest app deploy`, matching the `agent` group's convention
- in `--json`, `result` includes `configPath`, `format` (`typescript` or `json`), `converted` (true only for the `--format ts` conversion path), the written `app` values (null when a conversion transported a config that does not pin a single fully-resolved app), per-value `settings` sources, and `link` state; `--json` never prompts
Expand Down Expand Up @@ -1411,7 +1412,7 @@ Behavior:
- maps user-facing framework names to deploy build strategies
- does not accept `--build-command` or `--output-directory`; custom build settings live in the `build` block of `prisma.compute.ts`
- deploys arbitrary framework output with `framework: "custom"` when the config provides a built output directory and entrypoint; `build.command` is optional for prebuilt artifacts
- in interactive human deploys, prompts once to install the Prisma Compute skill for the project when `prisma-compute` is missing; accepting installs `prisma-compute` from `prisma/skills` from the project directory, equivalent to the detected package-runner command such as `pnpm dlx @prisma/cli@latest agent install --skill prisma-compute`; declining records the prompt as dismissed in local CLI state
- in interactive human deploys, prompts once to install the Prisma Compute skill for the project when `prisma-compute` is missing; accepting installs `prisma-compute` from `prisma/skills` from the project directory, equivalent to the detected package-runner command such as `pnpm dlx @prisma/cli@latest agent install --skill prisma-compute`; declining records the prompt as dismissed in local CLI state, while a failed install records no dismissal, so a later interactive run offers again; the prompt and its dismissal state are shared with `init`, so whichever command runs interactively first asks and neither asks twice
- does not prompt for agent setup in `--json`, `--quiet`, CI, `--no-interactive`, or `--yes`
- uses `src/index.ts` as the Hono deploy entrypoint when the app has no `package.json#main` or `package.json#module` and that file exists
- supports vanilla Bun apps with `--framework bun` using `package.json#main` or `package.json#module`, or with `--entry <path>`
Expand Down
90 changes: 90 additions & 0 deletions packages/cli/src/controllers/agent-setup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { resolvePrismaCliPackageCommand } from "../lib/agent/cli-command";
import {
PRISMA_AGENT_INSTALL_ARGS,
PRISMA_COMPUTE_AGENT_SKILL,
} from "../lib/agent/constants";
import {
readPrismaAgentSetupStatus,
shouldOfferPrismaAgentSetup,
} from "../lib/agent/setup-status";
import { confirmPrompt } from "../shell/prompt";
import { type CommandContext, canPrompt } from "../shell/runtime";
import { renderSummaryLine } from "../shell/ui";
import { runAgentInstall } from "./agent";

/**
* One-time interactive offer to install the Prisma Compute skill for the
* project. Shared by the commands that establish a project setup (init,
* deploy); the answer is remembered, so whichever command runs first asks.
* Returns warnings; a failed install must not fail the calling command.
*/
export async function maybePromptForAgentSetup(
context: CommandContext,
projectDir: string,
): Promise<string[]> {
// canPrompt covers json/CI/non-TTY/--no-interactive; quiet and yes runs
// must also stay prompt-free even on a TTY.
if (!canPrompt(context) || context.flags.yes || context.flags.quiet) {
return [];
}

const status = await readPrismaAgentSetupStatus({
cwd: projectDir,
stateStore: context.stateStore,
signal: context.runtime.signal,
requiredSkill: PRISMA_COMPUTE_AGENT_SKILL,
});
if (!shouldOfferPrismaAgentSetup(status)) {
return [];
}

const shouldInstall = await confirmPrompt({
input: context.runtime.stdin,
output: context.runtime.stderr,
signal: context.runtime.signal,
message: "Install the Prisma Compute skill for this project?",
initialValue: true,
});

if (!shouldInstall) {
await context.stateStore.setAgentSetupPromptDismissedAt(
new Date().toISOString(),
);
return [];
}

try {
await runAgentInstall(
context,
{ skill: [PRISMA_COMPUTE_AGENT_SKILL] },
"install",
{ cwd: projectDir },
);
if (!context.flags.quiet && !context.flags.json) {
context.output.stderr.write(
`${renderSummaryLine(context.ui, "success", "Installed the Prisma Compute skill for this project.")}\n\n`,
);
}
return [];
} catch (error) {
const message =
error instanceof Error ? error.message : "Prisma skill install failed.";
if (!context.flags.quiet && !context.flags.json) {
context.output.stderr.write(
`${renderSummaryLine(context.ui, "warning", `Skipped Prisma skill install: ${message}`)}\n\n`,
);
}
const retryCommand = await resolvePrismaCliPackageCommand({
cwd: projectDir,
signal: context.runtime.signal,
args: [
...PRISMA_AGENT_INSTALL_ARGS,
"--skill",
PRISMA_COMPUTE_AGENT_SKILL,
],
});
return [
`The Prisma Compute skill was not installed. Run ${retryCommand} to try again.`,
];
}
}
82 changes: 2 additions & 80 deletions packages/cli/src/controllers/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,6 @@
import { matchError, Result } from "better-result";
import open from "open";
import { FileTokenStorage } from "../adapters/token-storage";
import { resolvePrismaCliPackageCommand } from "../lib/agent/cli-command";
import {
PRISMA_AGENT_INSTALL_ARGS,
PRISMA_COMPUTE_AGENT_SKILL,
} from "../lib/agent/constants";
import {
readPrismaAgentSetupStatus,
shouldOfferPrismaAgentSetup,
} from "../lib/agent/setup-status";
import { DEFAULT_REGION } from "../lib/app/app-interaction";
import {
type AppRecord,
Expand Down Expand Up @@ -139,7 +130,7 @@
import { type CommandSuccess, writeJsonEvent } from "../shell/output";
import { confirmPrompt, selectPrompt, textPrompt } from "../shell/prompt";
import { type CommandContext, canPrompt } from "../shell/runtime";
import { renderCommandHeader, renderSummaryLine } from "../shell/ui";
import { renderCommandHeader } from "../shell/ui";
import type {
AppBuildResult,
AppDeployAllResult,
Expand All @@ -166,7 +157,7 @@
import type { AuthWorkspace } from "../types/auth";
import type { BranchKind } from "../types/branch";
import type { ProjectResolution, ProjectSummary } from "../types/project";
import { runAgentInstall } from "./agent";
import { maybePromptForAgentSetup } from "./agent-setup";
import { requireAuthenticatedAuthState } from "./auth";
import { listRealWorkspaceProjects } from "./project";
import { createSelectPromptPort } from "./select-prompt-port";
Expand Down Expand Up @@ -584,7 +575,7 @@
});
}

async function runSingleAppDeploy(

Check notice on line 578 in packages/cli/src/controllers/app.ts

View workflow job for this annotation

GitHub Actions / Lint

lint/complexity/noExcessiveCognitiveComplexity

Excessive complexity of 18 detected (max: 15).
context: CommandContext,
appName: string | undefined,
options: AppDeployOptions | undefined,
Expand Down Expand Up @@ -1179,9 +1170,9 @@
...deployment.deployment,
live: providerLiveDeploymentId
? deployment.deployment.id === providerLiveDeploymentId
: knownLiveDeploymentId
? deployment.deployment.id === knownLiveDeploymentId
: deployment.deployment.live,

Check notice on line 1175 in packages/cli/src/controllers/app.ts

View workflow job for this annotation

GitHub Actions / Lint

lint/style/noNestedTernary

Do not nest ternary expressions.
},
},
warnings: [],
Expand Down Expand Up @@ -1560,10 +1551,10 @@
});
}

await sleep(
Math.min(pollIntervalMs, Math.max(deadline - Date.now(), 0)),
context.runtime.signal,
);

Check notice on line 1557 in packages/cli/src/controllers/app.ts

View workflow job for this annotation

GitHub Actions / Lint

lint/performance/noAwaitInLoops

Avoid using await inside loops.
current = await target.provider
.showDomain(current.id, { signal: context.runtime.signal })
.catch((error) => {
Expand Down Expand Up @@ -2125,7 +2116,7 @@
const compute = await resolveComputeManagementContext(
context,
options?.configTarget,
commandName.replace(/^app /, ""),

Check warning on line 2119 in packages/cli/src/controllers/app.ts

View workflow job for this annotation

GitHub Actions / Lint

lint/performance/useTopLevelRegex

This regex literal is not defined in the top level scope. This can lead to performance issues if this function is called frequently.
);
const branch = resolveDomainBranch(options?.branchName);
if (toBranchKind(branch.name) !== "production") {
Expand Down Expand Up @@ -2258,7 +2249,7 @@
}

function normalizeDomainHostname(hostname: string): string {
const normalized = hostname.trim().replace(/\.$/, "").toLowerCase();

Check warning on line 2252 in packages/cli/src/controllers/app.ts

View workflow job for this annotation

GitHub Actions / Lint

lint/performance/useTopLevelRegex

This regex literal is not defined in the top level scope. This can lead to performance issues if this function is called frequently.
if (!isValidDomainHostname(normalized)) {
throw new CliError({
code: "DOMAIN_HOSTNAME_INVALID",
Expand Down Expand Up @@ -2293,14 +2284,14 @@
}

return labels.every((label) =>
/^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/.test(label),

Check warning on line 2287 in packages/cli/src/controllers/app.ts

View workflow job for this annotation

GitHub Actions / Lint

lint/performance/useTopLevelRegex

This regex literal is not defined in the top level scope. This can lead to performance issues if this function is called frequently.
);
}

function sameDomainHostname(left: string, right: string): boolean {
return (
left.trim().replace(/\.$/, "").toLowerCase() ===

Check warning on line 2293 in packages/cli/src/controllers/app.ts

View workflow job for this annotation

GitHub Actions / Lint

lint/performance/useTopLevelRegex

This regex literal is not defined in the top level scope. This can lead to performance issues if this function is called frequently.
right.trim().replace(/\.$/, "").toLowerCase()

Check warning on line 2294 in packages/cli/src/controllers/app.ts

View workflow job for this annotation

GitHub Actions / Lint

lint/performance/useTopLevelRegex

This regex literal is not defined in the top level scope. This can lead to performance issues if this function is called frequently.
);
}

Expand Down Expand Up @@ -2388,7 +2379,7 @@
}
}

function domainCommandError(

Check notice on line 2382 in packages/cli/src/controllers/app.ts

View workflow job for this annotation

GitHub Actions / Lint

lint/complexity/noExcessiveCognitiveComplexity

Excessive complexity of 26 detected (max: 15).
command: AppDomainCommand,
error: unknown,
hostname: string,
Expand Down Expand Up @@ -2525,7 +2516,7 @@
text.includes("no cname") ||
text.includes("cname record") ||
text.includes("no a/aaaa") ||
/\bcname(?:s)?\s+to\b/.test(text)

Check warning on line 2519 in packages/cli/src/controllers/app.ts

View workflow job for this annotation

GitHub Actions / Lint

lint/performance/useTopLevelRegex

This regex literal is not defined in the top level scope. This can lead to performance issues if this function is called frequently.
);
}

Expand Down Expand Up @@ -2554,7 +2545,7 @@

function extractDomainDnsTarget(error: DomainApiError): string | null {
const text = `${error.hint ?? ""} ${error.message}`;
const match = /\b((?:[a-z0-9-]+\.)+prisma\.build)\b/i.exec(text);

Check warning on line 2548 in packages/cli/src/controllers/app.ts

View workflow job for this annotation

GitHub Actions / Lint

lint/performance/useTopLevelRegex

This regex literal is not defined in the top level scope. This can lead to performance issues if this function is called frequently.
return match?.[1]?.toLowerCase() ?? null;
}

Expand Down Expand Up @@ -2590,7 +2581,7 @@
return 0;
}

const match = /^(\d+)(ms|s|m|h)$/.exec(trimmed);

Check warning on line 2584 in packages/cli/src/controllers/app.ts

View workflow job for this annotation

GitHub Actions / Lint

lint/performance/useTopLevelRegex

This regex literal is not defined in the top level scope. This can lead to performance issues if this function is called frequently.
if (!match) {
throw usageError(
`Invalid timeout "${value}"`,
Expand All @@ -2606,11 +2597,11 @@
const multiplier =
unit === "h"
? 60 * 60 * 1000
: unit === "m"
? 60 * 1000
: unit === "s"
? 1000
: 1;

Check notice on line 2604 in packages/cli/src/controllers/app.ts

View workflow job for this annotation

GitHub Actions / Lint

lint/style/noNestedTernary

Do not nest ternary expressions.

Check notice on line 2604 in packages/cli/src/controllers/app.ts

View workflow job for this annotation

GitHub Actions / Lint

lint/style/noNestedTernary

Do not nest ternary expressions.
return amount * multiplier;
}

Expand Down Expand Up @@ -3384,7 +3375,7 @@
listProjects: () =>
listRealWorkspaceProjects(
client,
authState.workspace!,

Check warning on line 3378 in packages/cli/src/controllers/app.ts

View workflow job for this annotation

GitHub Actions / Lint

lint/style/noNonNullAssertion

Forbidden non-null assertion.
context.runtime.signal,
),
commandName: options?.commandName,
Expand Down Expand Up @@ -3418,7 +3409,7 @@
};
}

async function resolveDeployProjectContext(

Check notice on line 3412 in packages/cli/src/controllers/app.ts

View workflow job for this annotation

GitHub Actions / Lint

lint/complexity/noExcessiveCognitiveComplexity

Excessive complexity of 18 detected (max: 15).
context: CommandContext,
client: ManagementApiClient,
provider: ReturnType<typeof createAppProvider>,
Expand Down Expand Up @@ -4093,7 +4084,7 @@
continue;
}

const configFile = await detectFrameworkConfigFile(cwd, framework, signal);

Check notice on line 4087 in packages/cli/src/controllers/app.ts

View workflow job for this annotation

GitHub Actions / Lint

lint/performance/noAwaitInLoops

Avoid using await inside loops.
if (
!configFile.exists &&
!hasAnyPackageDependency(packageJson, framework.detectPackages)
Expand All @@ -4106,8 +4097,8 @@
const annotation =
framework.key === "nextjs" && configFile.standalone
? "standalone output detected"
: configFile.exists
? `detected from ${path.basename(configFile.path!)}`

Check warning on line 4101 in packages/cli/src/controllers/app.ts

View workflow job for this annotation

GitHub Actions / Lint

lint/style/noNonNullAssertion

Forbidden non-null assertion.
: "detected from package.json";

Check notice on line 4102 in packages/cli/src/controllers/app.ts

View workflow job for this annotation

GitHub Actions / Lint

lint/style/noNestedTernary

Do not nest ternary expressions.

return {
Expand All @@ -4130,7 +4121,7 @@
const filePath = path.join(cwd, candidate);
signal.throwIfAborted();
try {
const content = await readFile(filePath, { encoding: "utf8", signal });

Check notice on line 4124 in packages/cli/src/controllers/app.ts

View workflow job for this annotation

GitHub Actions / Lint

lint/performance/noAwaitInLoops

Avoid using await inside loops.
return {
exists: true,
standalone: /\boutput\s*:\s*["'`]standalone["'`]/.test(content),
Expand Down Expand Up @@ -4321,75 +4312,6 @@
);
}

async function maybePromptForAgentSetup(
context: CommandContext,
projectDir: string,
): Promise<string[]> {
if (!canPrompt(context) || context.flags.yes) {
return [];
}

const status = await readPrismaAgentSetupStatus({
cwd: projectDir,
stateStore: context.stateStore,
signal: context.runtime.signal,
requiredSkill: PRISMA_COMPUTE_AGENT_SKILL,
});
if (!shouldOfferPrismaAgentSetup(status)) {
return [];
}

const shouldInstall = await confirmPrompt({
input: context.runtime.stdin,
output: context.runtime.stderr,
signal: context.runtime.signal,
message: "Install the Prisma Compute skill for this project?",
initialValue: true,
});

if (!shouldInstall) {
await context.stateStore.setAgentSetupPromptDismissedAt(
new Date().toISOString(),
);
return [];
}

try {
await runAgentInstall(
context,
{ skill: [PRISMA_COMPUTE_AGENT_SKILL] },
"install",
{ cwd: projectDir },
);
if (!context.flags.quiet && !context.flags.json) {
context.output.stderr.write(
`${renderSummaryLine(context.ui, "success", "Installed the Prisma Compute skill for this project.")}\n\n`,
);
}
return [];
} catch (error) {
const message =
error instanceof Error ? error.message : "Prisma skill install failed.";
if (!context.flags.quiet && !context.flags.json) {
context.output.stderr.write(
`${renderSummaryLine(context.ui, "warning", `Skipped Prisma skill install: ${message}`)}\n\n`,
);
}
const retryCommand = await resolvePrismaCliPackageCommand({
cwd: projectDir,
signal: context.runtime.signal,
args: [
...PRISMA_AGENT_INSTALL_ARGS,
"--skill",
PRISMA_COMPUTE_AGENT_SKILL,
],
});
return [
`The Prisma Compute skill was not installed. Run ${retryCommand} to try again.`,
];
}
}

async function maybeCustomizeDeploySettings(
context: CommandContext,
options: {
Expand Down
5 changes: 5 additions & 0 deletions packages/cli/src/controllers/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ import type {
InitSettingRow,
InitTypesState,
} from "../types/init";
import { maybePromptForAgentSetup } from "./agent-setup";
import { detectDeployFramework } from "./app";
import { runProjectLink } from "./project";

Expand Down Expand Up @@ -216,6 +217,7 @@ export async function runInit(
onWarning: (message) => warnings.push(message),
formatCommand,
});
warnings.push(...(await maybePromptForAgentSetup(context, cwd)));

const unlinked = link.status !== "linked" && link.status !== "already-linked";
const typesMissing =
Expand Down Expand Up @@ -600,6 +602,9 @@ async function runInitConversion(
onWarning: (message) => warnings.push(message),
formatCommand,
});
warnings.push(
...(await maybePromptForAgentSetup(stepContext, loaded.configDir)),
);

const unlinked = link.status !== "already-linked" && link.status !== "linked";
const typesMissing =
Expand Down
33 changes: 3 additions & 30 deletions packages/cli/tests/agent.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import path from "node:path";
import { afterEach, describe, expect, it, vi } from "vitest";

import { createTempCwd, executeCli } from "./helpers";
import { writeSkillsLockWithSkill } from "./helpers/skills-lock";

function expectSkillsCommandPrefix(
command: string[],
Expand Down Expand Up @@ -348,21 +349,7 @@ describe("agent commands", () => {

it("checks required Prisma skills from the skills lock", async () => {
const cwd = await createTempCwd();
await writeFile(
path.join(cwd, "skills-lock.json"),
JSON.stringify({
version: 1,
skills: {
"prisma-client-api": {
source: "prisma/skills",
sourceType: "github",
skillPath: "prisma-client-api/SKILL.md",
computedHash: "test",
},
},
}),
"utf8",
);
await writeSkillsLockWithSkill(cwd, "prisma-client-api");

const { readPrismaAgentSetupStatus } = await import(
"../src/lib/agent/setup-status"
Expand All @@ -380,21 +367,7 @@ describe("agent commands", () => {
}),
).resolves.toMatchObject({ skillsInstalled: false });

await writeFile(
path.join(cwd, "skills-lock.json"),
JSON.stringify({
version: 1,
skills: {
"prisma-compute": {
source: "prisma/skills",
sourceType: "github",
skillPath: "prisma-compute/SKILL.md",
computedHash: "test",
},
},
}),
"utf8",
);
await writeSkillsLockWithSkill(cwd, "prisma-compute");

await expect(
readPrismaAgentSetupStatus({
Expand Down
23 changes: 3 additions & 20 deletions packages/cli/tests/app-controller.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
createProjectClient,
createResolveBranch,
} from "./helpers/mock-factories";
import { writeSkillsLockWithSkill } from "./helpers/skills-lock";

beforeEach(() => {
process.env.PRISMA_CLI_TEST_REMEMBER_PROJECT_ID = "proj_123";
Expand Down Expand Up @@ -179,24 +180,6 @@ async function writeLocalPin(
);
}

async function writeAgentSetupInstalled(cwd: string): Promise<void> {
await writeFile(
path.join(cwd, "skills-lock.json"),
JSON.stringify({
version: 1,
skills: {
"prisma-compute": {
source: "prisma/skills",
sourceType: "github",
skillPath: "prisma-compute/SKILL.md",
computedHash: "test",
},
},
}),
"utf8",
);
}

async function setupAgentPromptDeployTest(options: {
confirmPrompt: ReturnType<typeof vi.fn>;
deployApp?: ReturnType<typeof vi.fn>;
Expand Down Expand Up @@ -3221,7 +3204,7 @@ describe("app controller", () => {
next: "15.0.0",
},
});
await writeAgentSetupInstalled(cwd);
await writeSkillsLockWithSkill(cwd);
const stateDir = path.join(cwd, ".state");
const { context, stderr } = await createTestCommandContext({
cwd,
Expand Down Expand Up @@ -3528,7 +3511,7 @@ describe("app controller", () => {
await writePackageJson(cwd, {
name: "suggested-name",
});
await writeAgentSetupInstalled(cwd);
await writeSkillsLockWithSkill(cwd);
const stateDir = path.join(cwd, ".state");
const { context, stderr } = await createTestCommandContext({
cwd,
Expand Down
27 changes: 27 additions & 0 deletions packages/cli/tests/helpers/skills-lock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { writeFile } from "node:fs/promises";
import path from "node:path";

/**
* Writes a skills-lock.json recording the given skill as installed from
* prisma/skills, the shape the agent setup status check reads.
*/
export async function writeSkillsLockWithSkill(
cwd: string,
skillName = "prisma-compute",
): Promise<void> {
await writeFile(
path.join(cwd, "skills-lock.json"),
JSON.stringify({
version: 1,
skills: {
[skillName]: {
source: "prisma/skills",
sourceType: "github",
skillPath: `${skillName}/SKILL.md`,
computedHash: "test",
},
},
}),
"utf8",
);
}
Loading
Loading