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
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,16 @@ describe('os create: a name npm refuses is refused before anything is written',

// ⛔ The asymmetry is correct, not a second defect: `init`'s argument is
// the package name, so npm's ceiling is already measured against it.
const inited = await runCli(['init', COMPOSED_TOO_LONG], initCwd);
//
// `--no-install`: what is judged is that `init` ACCEPTS the name, and
// it refuses an invalid one before the first write, long before any
// install. Without the flag, `init` ran `<pm> install` in this
// `tmpdir()` scaffold, which carries no `packageManager` pin on
// purpose: under Corepack that resolved `pnpm@latest` from the public
// registry and then fetched the scaffold's dependencies from it —
// measured: a registry answering HTTP 429 turned this exit 0 into a
// failure with nothing about the name changed.
const inited = await runCli(['init', COMPOSED_TOO_LONG, '--no-install'], initCwd);
expect(inited.code).toBe(0);
} finally {
rmSync(createCwd, { recursive: true, force: true });
Expand Down
80 changes: 73 additions & 7 deletions packages/cli/test/dev-no-watch.pin.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@
*/

import { describe, it, expect, beforeAll, afterAll } from 'vitest';
import { execFile } from 'node:child_process';
import { mkdtempSync, rmSync, writeFileSync } from 'node:fs';
import { execFile, execFileSync } from 'node:child_process';
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
Expand All @@ -59,6 +59,54 @@ const HERE = resolve(fileURLToPath(import.meta.url), '..');
const CLI = resolve(HERE, '../bin/run-dev.js');
const TSX = resolve(HERE, '../../../node_modules/.bin/tsx');

const PACKAGE_ROOT = resolve(HERE, '..');

/**
* The `packageManager` pin both fixtures carry, as `pnpm@VERSION`.
*
* Why the fixtures carry one: both live under `tmpdir()`, outside the
* repository, and the spawned `os dev` runs `pnpm … dev` inside them. Under
* Corepack (CI's `setup-pnpm`), a `pnpm` run with no `packageManager` in reach
* resolves `pnpm@latest` from the public registry — measured: a registry
* answering HTTP 429 failed this file's two pnpm-running cases with
* `os dev ended with code 1` and `expected 1 to be +0`, the shape CI saw.
* `COREPACK_DEFAULT_TO_LATEST=0` is NOT the same fix: it swaps `latest` for
* Corepack's own bundled default, which CI's store does not hold, and the
* registry is reached for that instead.
*
* Why the pin is DERIVED and not read: VERSION is what `pnpm --version`
* answers with this package as its cwd, which is the repository's root pin
* reached through Corepack's own walk up to the nearest manifest declaring
* one. That is the version CI's Corepack store already holds, so the fixtures
* resolve it with no network call, and nothing is copied by hand. Reading the
* root `package.json` from here instead would make it a declared input of this
* package's tests (`check:cross-package-test-inputs`), which would put cli's
* whole PR-tier suite on every PR that edits that manifest. The spec carries no
* integrity hash; Corepack resolves the stored version by its number.
*
* ⛔ The blind spot this leaves, stated so it is not read as covered: the pin
* is an UNDECLARED input. A PR that bumps only the root pin does not re-run
* this file in CI, exactly as it re-runs none of this suite's other `pnpm -v`
* and `pnpm pack` callers, which resolve pnpm through the same walk. A pnpm
* whose no-projects sentence changed would first go red on a later PR that
* re-runs cli's tests.
*/
function derivePackageManagerPin(): string {
const answer = execFileSync('pnpm', ['--version'], {
cwd: PACKAGE_ROOT,
encoding: 'utf8',
stdio: ['ignore', 'pipe', 'pipe'],
env: childEnv(),
}).trim();
if (!/^\d+\.\d+\.\d+(-[0-9A-Za-z.-]+)?$/.test(answer)) {
throw new Error(
`\`pnpm --version\` in ${PACKAGE_ROOT} answered ${JSON.stringify(answer)}, not a version; ` +
'without one these fixtures carry no pin and Corepack would fetch a pnpm from the registry',
);
}
return `pnpm@${answer}`;
}

/** oclif + tsx cold start, with every command module loaded; ~2-10 s when healthy. */
const RUN_TIMEOUT_MS = 180_000;

Expand Down Expand Up @@ -143,27 +191,46 @@ describe('the watch opt-out is reachable', () => {
});
});

let fixtureRoot: string;
let packageManagerPin: string;
let projectDir: string;
let workspaceDir: string;
let watchEqualsFalse: Run;
let noWatchAtWorkspace: Run;
let bareAtWorkspace: Run;

beforeAll(async () => {
// Both fixtures sit under one root whose manifest carries the pin (see
// `derivePackageManagerPin`). Above `projectDir`, not in it: a `package.json`
// inside `projectDir` makes it a project, and pnpm then answers
// `No projects matched the filters` instead of the `No projects found`
// this file pins. Corepack walks up to the nearest manifest declaring a
// `packageManager`; pnpm's no-projects answer reads `projectDir` alone.
packageManagerPin = derivePackageManagerPin();
fixtureRoot = mkdtempSync(join(tmpdir(), 'os-dev-no-watch-'));
writeFileSync(
join(fixtureRoot, 'package.json'),
`${JSON.stringify({ private: true, packageManager: packageManagerPin })}\n`,
);

// A project directory: a config file, and no pnpm workspace above it.
projectDir = mkdtempSync(join(tmpdir(), 'os-dev-no-watch-project-'));
projectDir = join(fixtureRoot, 'project');
mkdirSync(projectDir);
writeFileSync(join(projectDir, 'objectstack.config.ts'), 'export default {};\n');

// A workspace root whose own `dev` script announces that it ran — so the
// `--no-watch` refusal below is measured against a fixture that DOES start
// something when asked (`bareAtWorkspace` is that control).
workspaceDir = mkdtempSync(join(tmpdir(), 'os-dev-no-watch-workspace-'));
// something when asked (`bareAtWorkspace` is that control). Its own manifest
// carries the pin too: the nearest one is what Corepack reads first.
workspaceDir = join(fixtureRoot, 'workspace');
mkdirSync(workspaceDir);
writeFileSync(join(workspaceDir, 'pnpm-workspace.yaml'), 'packages: []\n');
writeFileSync(
join(workspaceDir, 'package.json'),
`${JSON.stringify({
name: 'os-dev-no-watch-fixture',
private: true,
packageManager: packageManagerPin,
scripts: { dev: `node -e "console.log('${DEV_SCRIPT_MARK}')"` },
})}\n`,
);
Expand All @@ -176,8 +243,7 @@ beforeAll(async () => {
}, RUN_TIMEOUT_MS);

afterAll(() => {
rmSync(projectDir, { recursive: true, force: true });
rmSync(workspaceDir, { recursive: true, force: true });
if (fixtureRoot) rmSync(fixtureRoot, { recursive: true, force: true });
});

describe('a PACKAGE that selects no workspace project fails', () => {
Expand Down
57 changes: 54 additions & 3 deletions packages/cli/test/package-owned-command-readers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,23 +30,66 @@

import { afterAll, beforeAll, describe, expect, it } from 'vitest';
import { execFile } from 'node:child_process';
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs';
import { chmodSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { delimiter, join } from 'node:path';
import { FILE_REFERENCES_MIGRATION_ID } from '@objectstack/spec/system';

import { CLI, TSX, childEnv } from './helpers/serve-process.js';
import { linkSpec } from './helpers/define-stack-fixture.js';

interface Run { code: number; stdout: string; stderr: string }

/**
* The version a stub `pnpm` answers with. `os doctor` runs `pnpm -v` in its own
* cwd, and every cwd here is a `tmpdir()` fixture with no `packageManager` in
* reach: under Corepack (CI's `setup-pnpm`) a real `pnpm` there resolves
* `pnpm@latest` from the public registry — measured: two registry requests per
* run of this file, one per doctor child. Doctor degrades a failed probe to a
* row, so a refused registry never reddened this file; it reached the network
* all the same. Which pnpm answers is not what these pins judge, so a stub on
* `PATH` answers instead, and `doctor` asserts below that the stub is what it
* reached.
*/
const STUB_PNPM_VERSION = '0.0.0-package-owned-readers-stub';

/** A `pnpm` that answers `-v` and refuses anything else, loudly. */
function writeStubPnpm(binDir: string): void {
mkdirSync(binDir, { recursive: true });
const bin = join(binDir, 'pnpm');
writeFileSync(
bin,
[
'#!/bin/sh',
'case "$1" in',
` -v|--version) echo ${STUB_PNPM_VERSION}; exit 0 ;;`,
'esac',
'echo "stub pnpm: unexpected invocation: $*" >&2',
'exit 97',
'',
].join('\n'),
);
chmodSync(bin, 0o755);
}

let stubBinDir = '';

function runCli(args: string[], cwd: string): Promise<Run> {
return new Promise((resolvePromise) => {
execFile(
TSX,
[CLI, ...args],
// Artifact-less on purpose: the config is the only input these commands read.
{ cwd, maxBuffer: 16 * 1024 * 1024, env: childEnv({ NO_COLOR: '1', OS_ARTIFACT_PATH: join(cwd, 'dist', 'objectstack.json') }) },
// The stub `pnpm` leads `PATH` for every child; only `doctor` calls it.
{
cwd,
maxBuffer: 16 * 1024 * 1024,
env: childEnv({
NO_COLOR: '1',
OS_ARTIFACT_PATH: join(cwd, 'dist', 'objectstack.json'),
PATH: `${stubBinDir}${delimiter}${process.env.PATH ?? ''}`,
}),
},
(err, stdout, stderr) => {
const code = err ? (typeof (err as { code?: unknown }).code === 'number' ? (err as { code: number }).code : 1) : 0;
resolvePromise({ code, stdout: String(stdout), stderr: String(stderr) });
Expand Down Expand Up @@ -97,6 +140,8 @@ let r: Record<string, Run> = {};

beforeAll(async () => {
root = mkdtempSync(join(tmpdir(), 'os-package-owned-command-readers-'));
stubBinDir = join(root, 'bin');
writeStubPnpm(stubBinDir);
for (const [name, source] of Object.entries(CONFIGS)) {
const dir = join(root, name);
mkdirSync(dir, { recursive: true });
Expand Down Expand Up @@ -137,6 +182,12 @@ describe('#22288 — os doctor runs its metadata checks over every package', ()
expect(unused.join('\n'), out).toMatch(/"pco_note"/);
expect(unused.join('\n'), out).toMatch(/"pco_ticket"/);
});

it(`${shape === 'two' ? 'two packages' : 'control, one package'}: doctor's pnpm probe reached the stub, not the registry`, () => {
const run = r[`doctor:${shape}`];
const out = run.stdout + run.stderr;
expect(out, out).toContain(STUB_PNPM_VERSION);
});
}
});

Expand Down
Loading