Skip to content
Draft
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: 5 additions & 0 deletions docs/guides/deploying.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ token is the only authentication.

## Build first

Composer resolves Alchemy from the nearest `node_modules/.bin`, walking up
for hoisted installations. On Windows it prefers `alchemy.exe`, then
`alchemy.cmd`, then the extensionless shim; POSIX uses `alchemy`. An installed
Windows shim must not be reported as a missing Alchemy dependency.

`prisma-composer deploy` does not build for you — it assembles what your
build produced:

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,17 @@ afterEach(() => {
});

describe('resolveAlchemyBin()', () => {
test.skipIf(process.platform !== 'win32')('finds Windows-only package-manager shims', () => {
for (const filename of ['alchemy.cmd', 'alchemy.exe']) {
const dir = makeTmpDir();
const binDir = path.join(dir, 'node_modules', '.bin');
fs.mkdirSync(binDir, { recursive: true });
const bin = path.join(binDir, filename);
fs.writeFileSync(bin, '');
expect(resolveAlchemyBin(dir)).toBe(bin);
}
});

test('finds node_modules/.bin/alchemy in the given directory', () => {
const dir = makeTmpDir();
const bin = installFakeAlchemy(dir);
Expand Down Expand Up @@ -177,6 +188,39 @@ describe('alchemyInvocation()', () => {
});

describe('spawnAlchemy()', () => {
test.skipIf(process.platform !== 'win32')('runs a cmd shim with literal arguments', async () => {
const dir = makeTmpDir();
const script = path.join(dir, 'capture.cjs');
const captureFile = path.join(dir, 'capture.json');
fs.writeFileSync(
script,
'require("node:fs").writeFileSync(process.env.CAPTURE_FILE, JSON.stringify(process.argv.slice(2)));',
);
const binDir = path.join(dir, 'node_modules', '.bin');
fs.mkdirSync(binDir, { recursive: true });
fs.writeFileSync(
path.join(binDir, 'alchemy.cmd'),
`@echo off\r\n"${process.execPath}" "${script}" %*\r\n`,
);
const stage = 'spaces & symbols';
expect(
await spawnAlchemy({
action: 'deploy',
stackFileRelativePath: '.prisma-composer/alchemy.run.ts',
stage,
cwd: dir,
env: { CAPTURE_FILE: captureFile },
}),
).toEqual({ exitCode: 0, signal: null });
expect(JSON.parse(fs.readFileSync(captureFile, 'utf8'))).toEqual([
'deploy',
'.prisma-composer/alchemy.run.ts',
'--yes',
'--stage',
stage,
]);
});

test('runs the invocation in its cwd with its env additions merged over the invoking environment', async () => {
const dir = makeTmpDir();
const captureFile = path.join(dir, 'capture.json');
Expand Down
10 changes: 7 additions & 3 deletions packages/0-framework/3-tooling/cli/src/run-alchemy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,16 @@ import * as path from 'node:path';
import { CliStructuredError } from '@internal/foundation/errors';
import spawn from 'cross-spawn';

/** Walks up from `startDir` looking for `node_modules/.bin/alchemy`. */
/** Walks up from `startDir` looking for the installed Alchemy executable. */
export function resolveAlchemyBin(startDir: string): string {
const names =
process.platform === 'win32' ? ['alchemy.exe', 'alchemy.cmd', 'alchemy'] : ['alchemy'];
let dir = startDir;
while (true) {
const candidate = path.join(dir, 'node_modules', '.bin', 'alchemy');
if (fs.existsSync(candidate)) return candidate;
for (const name of names) {
const candidate = path.join(dir, 'node_modules', '.bin', name);
if (fs.existsSync(candidate)) return candidate;
}
const parent = path.dirname(dir);
if (parent === dir) {
throw new CliStructuredError(
Expand Down
5 changes: 5 additions & 0 deletions skills/prisma-composer-core-concepts/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,11 @@ that arrives as an ordinary, exactly-pinned npm dependency of
imports or configures it; consult alchemy's own docs for the engine itself.
What matters operationally:

Alchemy is resolved from the nearest `node_modules/.bin`, including hoisted
ancestor directories. Windows resolves `alchemy.exe`, then `alchemy.cmd`,
then the extensionless shim; POSIX resolves `alchemy`. No global Alchemy
installation is needed.

1. Deploy and destroy write the pipeline's results to a generated, gitignored
stack file at `.prisma-composer/alchemy.run.ts`, then run the alchemy CLI
against it as a child process; `dev` does the same at
Expand Down
Loading