@inkeep/create-agents 0.80.5 → 0.80.7

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.
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,50 @@
1
+ import { spawnSync } from 'node:child_process';
2
+ import { mkdirSync, mkdtempSync, readFileSync, rmSync, symlinkSync, writeFileSync } from 'node:fs';
3
+ import { tmpdir } from 'node:os';
4
+ import { join } from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
6
+ import { afterEach, beforeEach, describe, expect, it } from 'vitest';
7
+ const packageDir = fileURLToPath(new URL('../../', import.meta.url));
8
+ const packageJson = JSON.parse(readFileSync(join(packageDir, 'package.json'), 'utf8'));
9
+ const { packageManager } = JSON.parse(readFileSync(join(packageDir, '../../package.json'), 'utf8'));
10
+ let fixture;
11
+ let testTimeout;
12
+ function runRequiredTest() {
13
+ const result = spawnSync('pnpm', ['test'], {
14
+ cwd: fixture,
15
+ env: { ...process.env, CI: 'true' },
16
+ encoding: 'utf8',
17
+ shell: process.platform === 'win32',
18
+ timeout: testTimeout,
19
+ });
20
+ expect(result.error).toBeUndefined();
21
+ expect(result.signal).toBeNull();
22
+ return result;
23
+ }
24
+ describe('required test script', () => {
25
+ beforeEach(({ task }) => {
26
+ testTimeout = task.timeout;
27
+ fixture = mkdtempSync(join(tmpdir(), 'create-agents-required-test-'));
28
+ symlinkSync(join(packageDir, 'node_modules'), join(fixture, 'node_modules'), 'junction');
29
+ mkdirSync(join(fixture, 'src/__tests__'), { recursive: true });
30
+ writeFileSync(join(fixture, 'package.json'), JSON.stringify({
31
+ type: 'module',
32
+ packageManager,
33
+ scripts: { test: packageJson.scripts.test },
34
+ }));
35
+ writeFileSync(join(fixture, 'vitest.config.ts'), 'export default { test: { maxWorkers: 1, minWorkers: 1 } };');
36
+ });
37
+ afterEach(() => {
38
+ rmSync(fixture, { recursive: true, force: true });
39
+ });
40
+ it.each([true, false])('reports the outcome of a collected test that passes=%j', (passes) => {
41
+ writeFileSync(join(fixture, 'src/__tests__/unit.test.ts'), `import { expect, it } from 'vitest'; it('unit', () => expect(${passes}).toBe(true));`);
42
+ const result = runRequiredTest();
43
+ expect(result.status, result.stdout + result.stderr).toBe(passes ? 0 : 1);
44
+ });
45
+ it('fails when the test glob collects no tests', () => {
46
+ const result = runRequiredTest();
47
+ expect(result.stdout + result.stderr).toContain('No test files found');
48
+ expect(result.status, result.stdout + result.stderr).toBe(1);
49
+ });
50
+ });
@@ -424,6 +424,75 @@ describe('createAgents - Template and Project ID Logic', () => {
424
424
  expect(envContent).toContain('# INKEEP_AGENTS_JWT_SIGNING_SECRET=');
425
425
  });
426
426
  });
427
+ describe('pnpm version check before installing', () => {
428
+ const scaffold = () => createAgents({
429
+ dirName: 'test-dir',
430
+ openAiKey: 'test-openai-key',
431
+ disableGit: true,
432
+ skipInkeepCli: true,
433
+ skipInkeepMcp: true,
434
+ });
435
+ const commands = () => mockExecAsync.mock.calls.map(([command]) => command);
436
+ const cancelMessage = () => String(vi.mocked(p.cancel).mock.calls.at(-1)?.[0]);
437
+ const spinnerStoppedBeforeCancel = () => {
438
+ const cancelledAt = vi.mocked(p.cancel).mock.invocationCallOrder.at(-1) ?? 0;
439
+ return mockSpinner.stop.mock.invocationCallOrder.some((order) => order < cancelledAt);
440
+ };
441
+ beforeEach(() => {
442
+ vi.mocked(fs.readJson).mockImplementation((async (path) => path === 'package.json' ? { packageManager: 'pnpm@12.8.1+sha512.abc' } : {}));
443
+ });
444
+ it.each([
445
+ '12.8.1',
446
+ '12.9.0',
447
+ '13.0.0',
448
+ ])('installs when the pnpm on PATH reports %s, at or above the pinned major', async (version) => {
449
+ mockExecAsync.mockImplementation(async (command) => ({
450
+ stdout: command === 'pnpm --version' ? `${version}\n` : '',
451
+ stderr: '',
452
+ }));
453
+ await scaffold();
454
+ expect(commands()).toContain('pnpm install');
455
+ expect(p.cancel).not.toHaveBeenCalled();
456
+ });
457
+ it('stops before installing when the pnpm on PATH is an older major', async () => {
458
+ mockExecAsync.mockImplementation(async (command) => ({
459
+ stdout: command === 'pnpm --version' ? '10.33.0\n' : '',
460
+ stderr: '',
461
+ }));
462
+ await scaffold();
463
+ expect(commands()).not.toContain('pnpm install');
464
+ expect(processExitSpy).toHaveBeenCalledWith(1);
465
+ expect(cancelMessage()).toContain('pnpm 12.8.1 is required');
466
+ expect(cancelMessage()).toContain('was created, but its dependencies were not installed: the pnpm on your PATH is 10.33.0.');
467
+ expect(cancelMessage()).toContain('npm install -g pnpm@12.8.1');
468
+ expect(cancelMessage()).toContain('pnpm self-update 12.8.1');
469
+ expect(cancelMessage()).toContain('corepack disable pnpm');
470
+ expect(cancelMessage()).toContain('check that pnpm --version in');
471
+ expect(cancelMessage()).not.toContain('Error creating directory');
472
+ expect(spinnerStoppedBeforeCancel()).toBe(true);
473
+ });
474
+ it('stops before installing when the pnpm on PATH cannot start the pinned version', async () => {
475
+ mockExecAsync.mockImplementation(async (command) => {
476
+ if (command === 'pnpm --version') {
477
+ throw Object.assign(new Error('Command failed: pnpm --version'), {
478
+ stderr: '\n ERROR Failed to switch pnpm to v12.8.1.\nspawnSync pnpm ENOEXEC\n',
479
+ });
480
+ }
481
+ return { stdout: '', stderr: '' };
482
+ });
483
+ await scaffold();
484
+ expect(commands()).not.toContain('pnpm install');
485
+ expect(cancelMessage()).toContain('could not run it (ERROR Failed to switch pnpm to v12.8.1.)');
486
+ expect(cancelMessage()).toContain('npm install -g pnpm@12.8.1');
487
+ expect(spinnerStoppedBeforeCancel()).toBe(true);
488
+ });
489
+ it('skips the check when the project pins no pnpm', async () => {
490
+ vi.mocked(fs.readJson).mockResolvedValue({});
491
+ await scaffold();
492
+ expect(commands()).not.toContain('pnpm --version');
493
+ expect(commands()).toContain('pnpm install');
494
+ });
495
+ });
427
496
  describe('Security - Password input for API keys', () => {
428
497
  it('should use password input instead of text input for API keys', async () => {
429
498
  // Mock the select to return 'anthropic' to trigger the API key prompt
package/dist/utils.d.ts CHANGED
@@ -53,6 +53,6 @@ export declare const createAgents: (args?: {
53
53
  skipInkeepMcp?: boolean;
54
54
  skipInstall?: boolean;
55
55
  skipProvider?: boolean;
56
- }) => Promise<void>;
56
+ }) => Promise<undefined>;
57
57
  export declare function createCommand(dirName?: string, options?: any): Promise<void>;
58
58
  export declare function addInkeepMcp(): Promise<void>;
package/dist/utils.js CHANGED
@@ -453,6 +453,11 @@ export const createAgents = async (args = {}) => {
453
453
  await createInkeepConfig(config);
454
454
  if (!skipInstall) {
455
455
  s.message('Installing dependencies (this may take a while)...');
456
+ const pnpmProblem = await pnpmPinProblem();
457
+ if (pnpmProblem) {
458
+ s.stop();
459
+ return displayPnpmPinError(pnpmProblem);
460
+ }
456
461
  await installDependencies();
457
462
  }
458
463
  if (!config.disableGit) {
@@ -588,6 +593,42 @@ async function installInkeepCLIGlobally() {
588
593
  }
589
594
  }
590
595
  }
596
+ async function pinnedPnpmVersion() {
597
+ const { packageManager } = (await fs.readJson('package.json').catch(() => ({}))) ?? {};
598
+ if (typeof packageManager !== 'string' || !packageManager.startsWith('pnpm@'))
599
+ return null;
600
+ return packageManager.slice('pnpm@'.length).split('+')[0];
601
+ }
602
+ async function pnpmPinProblem() {
603
+ const pinned = await pinnedPnpmVersion();
604
+ if (!pinned)
605
+ return null;
606
+ let reported;
607
+ try {
608
+ reported = (await execAsync('pnpm --version')).stdout.trim();
609
+ }
610
+ catch (error) {
611
+ const cause = String(error.stderr || error.message || '')
612
+ .split('\n')
613
+ .map((line) => line.trim())
614
+ .find(Boolean);
615
+ return { pinned, problem: `could not run it${cause ? ` (${cause})` : ''}` };
616
+ }
617
+ return Number.parseInt(reported, 10) < Number.parseInt(pinned, 10)
618
+ ? { pinned, problem: `is ${reported}` }
619
+ : null;
620
+ }
621
+ function displayPnpmPinError({ pinned, problem }) {
622
+ const dir = process.cwd();
623
+ p.cancel(`\n${color.red(`✗ pnpm ${pinned} is required`)}\n\n` +
624
+ `${dir} was created, but its dependencies were not installed: the pnpm on your PATH ${problem}.\n\n` +
625
+ `${color.yellow(`Install pnpm ${pinned}:`)}\n` +
626
+ ` npm install -g pnpm@${pinned}\n` +
627
+ ` For a standalone pnpm, run pnpm self-update ${pinned} outside ${dir} instead.\n` +
628
+ ` If Corepack provides pnpm, run corepack disable pnpm first.\n\n` +
629
+ `Then check that pnpm --version in ${dir} prints ${pinned}, and run pnpm install there.\n`);
630
+ process.exit(1);
631
+ }
591
632
  async function installDependencies() {
592
633
  try {
593
634
  const { stderr } = await execAsync('pnpm install');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inkeep/create-agents",
3
- "version": "0.80.5",
3
+ "version": "0.80.7",
4
4
  "description": "Create an Inkeep Agent Framework project",
5
5
  "type": "module",
6
6
  "bin": {
@@ -29,11 +29,11 @@
29
29
  "license": "SEE LICENSE IN LICENSE.md",
30
30
  "dependencies": {
31
31
  "@clack/prompts": "^0.11.0",
32
+ "@inkeep/agents-core": "0.80.7",
32
33
  "commander": "^12.0.0",
33
34
  "degit": "^2.8.4",
34
35
  "fs-extra": "^11.0.0",
35
- "picocolors": "^1.0.0",
36
- "@inkeep/agents-core": "0.80.5"
36
+ "picocolors": "^1.0.0"
37
37
  },
38
38
  "devDependencies": {
39
39
  "@types/degit": "^2.8.6",
@@ -64,9 +64,8 @@
64
64
  "lint": "biome lint --error-on-warnings",
65
65
  "lint:fix": "biome check --write .",
66
66
  "format": "biome format --write .",
67
- "test": "vitest --run src/__tests__ --exclude src/__tests__/e2e/** --passWithNoTests",
67
+ "test": "vitest --run src/__tests__ --exclude src/__tests__/e2e/**",
68
68
  "test:watch": "vitest src/__tests__ --exclude src/__tests__/e2e/**",
69
- "test:unit": "vitest --run src/__tests__ --exclude src/__tests__/e2e/**",
70
69
  "test:e2e": "turbo run build --filter=@inkeep/create-agents --filter=@inkeep/agents-api --filter=@inkeep/agents-cli --filter=@inkeep/agents-manage-ui && vitest --run src/__tests__/e2e",
71
70
  "test:e2e:watch": "turbo run build --filter=@inkeep/create-agents --filter=@inkeep/agents-api --filter=@inkeep/agents-cli --filter=@inkeep/agents-manage-ui && vitest src/__tests__/e2e",
72
71
  "test:all": "vitest --run --passWithNoTests",