Skip to content
Open
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
72 changes: 72 additions & 0 deletions packages/cli/src/__tests__/deprecated-commands.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { describe, expect, it, vi, beforeEach, afterEach } from 'vitest';
import { architectCommand } from '../commands/architect';
import { runCommand } from '../commands/run';
import { scaffoldCommand } from '../commands/scaffold';
import { loginCommand } from '../commands/login';
import type { CLIOptions } from '../index';
import { CLIError } from '../index';
import { DEPRECATION_WARNING_ENV_VAR } from '../commands/deprecation-warning';

const baseOptions: CLIOptions = {
format: 'text',
configPath: '.charter',
ciMode: false,
yes: false,
};

describe('deprecated build commands warnings', () => {
const originalSuppress = process.env[DEPRECATION_WARNING_ENV_VAR];

beforeEach(() => {
delete process.env[DEPRECATION_WARNING_ENV_VAR];
vi.restoreAllMocks();
});

afterEach(() => {
if (originalSuppress === undefined) {
delete process.env[DEPRECATION_WARNING_ENV_VAR];
} else {
process.env[DEPRECATION_WARNING_ENV_VAR] = originalSuppress;
}
vi.restoreAllMocks();
});

it('emits warning for login', async () => {
const stderr = vi.spyOn(process.stderr, 'write').mockImplementation(() => true);
vi.spyOn(console, 'log').mockImplementation(() => {});

await loginCommand(baseOptions, []);

const stderrOutput = stderr.mock.calls.map((c) => String(c[0])).join('');
expect(stderrOutput).toContain('charter login');
expect(stderrOutput).toContain('@stackbilt/build');
});

it('emits warning for architect', async () => {
const stderr = vi.spyOn(process.stderr, 'write').mockImplementation(() => true);

await expect(architectCommand(baseOptions, [])).rejects.toBeInstanceOf(CLIError);

const stderrOutput = stderr.mock.calls.map((c) => String(c[0])).join('');
expect(stderrOutput).toContain('charter architect');
});

it('emits warning for run', async () => {
const stderr = vi.spyOn(process.stderr, 'write').mockImplementation(() => true);

await expect(runCommand(baseOptions, [])).rejects.toBeInstanceOf(CLIError);

const stderrOutput = stderr.mock.calls.map((c) => String(c[0])).join('');
expect(stderrOutput).toContain('charter run');
});

it('emits warning for scaffold', async () => {
const stderr = vi.spyOn(process.stderr, 'write').mockImplementation(() => true);
const options = { ...baseOptions, configPath: '.charter-missing-cache-for-test' };

await expect(scaffoldCommand(options, [])).rejects.toBeInstanceOf(CLIError);

const stderrOutput = stderr.mock.calls.map((c) => String(c[0])).join('');
expect(stderrOutput).toContain('charter scaffold');
});
});
30 changes: 29 additions & 1 deletion packages/cli/src/__tests__/login.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { loginCommand } from '../commands/login';
import type { CLIOptions } from '../index';
import { API_KEY_ENV_VAR } from '../credentials';
import { DEPRECATION_WARNING_ENV_VAR } from '../commands/deprecation-warning';

const options: CLIOptions = {
format: 'text',
Expand All @@ -12,6 +13,7 @@ const options: CLIOptions = {

describe('charter login — deprecation notice', () => {
const originalEnv = process.env[API_KEY_ENV_VAR];
const originalSuppress = process.env[DEPRECATION_WARNING_ENV_VAR];

beforeEach(() => {
delete process.env[API_KEY_ENV_VAR];
Expand All @@ -23,6 +25,11 @@ describe('charter login — deprecation notice', () => {
} else {
process.env[API_KEY_ENV_VAR] = originalEnv;
}
if (originalSuppress === undefined) {
delete process.env[DEPRECATION_WARNING_ENV_VAR];
} else {
process.env[DEPRECATION_WARNING_ENV_VAR] = originalSuppress;
}
vi.restoreAllMocks();
});

Expand All @@ -34,7 +41,7 @@ describe('charter login — deprecation notice', () => {

const stderrOutput = stderr.mock.calls.map((c) => String(c[0])).join('');
expect(stderrOutput).toMatch(/deprecated/i);
expect(stderrOutput).toContain(API_KEY_ENV_VAR);
expect(stderrOutput).toContain('@stackbilt/build');
});

it('reports env-var usage when STACKBILT_API_KEY is set and no --key flag', async () => {
Expand All @@ -47,4 +54,25 @@ describe('charter login — deprecation notice', () => {
const stdoutOutput = log.mock.calls.map((c) => String(c[0])).join('\n');
expect(stdoutOutput).toMatch(new RegExp(`Using ${API_KEY_ENV_VAR} from environment`));
});

it('suppresses warning when CHARTER_NO_DEPRECATION_WARNING=1', async () => {
process.env[DEPRECATION_WARNING_ENV_VAR] = '1';
const stderr = vi.spyOn(process.stderr, 'write').mockImplementation(() => true);
vi.spyOn(console, 'log').mockImplementation(() => {});

await loginCommand(options, []);

const stderrOutput = stderr.mock.calls.map((c) => String(c[0])).join('');
expect(stderrOutput).toBe('');
});

it('suppresses warning when --no-deprecation-warning is passed', async () => {
const stderr = vi.spyOn(process.stderr, 'write').mockImplementation(() => true);
vi.spyOn(console, 'log').mockImplementation(() => {});

await loginCommand(options, ['--no-deprecation-warning']);

const stderrOutput = stderr.mock.calls.map((c) => String(c[0])).join('');
expect(stderrOutput).toBe('');
});
});
3 changes: 3 additions & 0 deletions packages/cli/src/commands/architect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,11 @@ import { EXIT_CODE, CLIError } from '../index';
import { getFlag } from '../flags';
import { resolveApiKey, API_KEY_ENV_VAR } from '../credentials';
import { EngineClient, type BuildRequest, type BuildResult } from '../http-client';
import { printBuildCommandDeprecationWarning } from './deprecation-warning';

export async function architectCommand(options: CLIOptions, args: string[]): Promise<number> {
printBuildCommandDeprecationWarning('architect', args);

// Parse description from positional arg or --file
const filePath = getFlag(args, '--file');
const positional = args.filter(a => !a.startsWith('-') && a !== filePath);
Expand Down
20 changes: 20 additions & 0 deletions packages/cli/src/commands/deprecation-warning.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
const RFC_112_URL = 'https://github.com/Stackbilt-dev/charter/issues/112';
export const DEPRECATION_WARNING_ENV_VAR = 'CHARTER_NO_DEPRECATION_WARNING';
export const DEPRECATION_WARNING_FLAG = '--no-deprecation-warning';

function warningSuppressed(args: string[]): boolean {
return args.includes(DEPRECATION_WARNING_FLAG) || process.env[DEPRECATION_WARNING_ENV_VAR] === '1';
}

export function printBuildCommandDeprecationWarning(command: string, args: string[]): void {
if (warningSuppressed(args)) {
return;
}

process.stderr.write(
`⚠ charter ${command} is deprecated and will be removed in Charter 1.0.\n` +
' Install @stackbilt/build for the long-term home of this command:\n' +
' npm install -g @stackbilt/build\n' +
` See ${RFC_112_URL} for context.\n`,
);
}
14 changes: 3 additions & 11 deletions packages/cli/src/commands/login.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import type { CLIOptions } from '../index';
import { EXIT_CODE, CLIError } from '../index';
import { getFlag } from '../flags';
import { printBuildCommandDeprecationWarning } from './deprecation-warning';
import {
loadCredentials,
saveCredentials,
Expand All @@ -20,24 +21,17 @@ import {
} from '../credentials';
import { EngineClient } from '../http-client';

function printDeprecationNotice(): void {
process.stderr.write(
`[deprecated] 'charter login' will be removed in 1.0. ` +
`Set ${API_KEY_ENV_VAR} in the environment instead.\n`,
);
}

export async function loginCommand(options: CLIOptions, args: string[]): Promise<number> {
printBuildCommandDeprecationWarning('login', args);

if (args.includes('--logout')) {
printDeprecationNotice();
clearCredentials();
console.log('Credentials cleared.');
return EXIT_CODE.SUCCESS;
}

const key = getFlag(args, '--key');
if (!key) {
printDeprecationNotice();
const existing = loadCredentials();
const envKey = process.env[API_KEY_ENV_VAR];
if (envKey && envKey.trim().length > 0) {
Expand All @@ -64,8 +58,6 @@ export async function loginCommand(options: CLIOptions, args: string[]): Promise
return EXIT_CODE.SUCCESS;
}

printDeprecationNotice();

const VALID_PREFIXES = ['ea_', 'sb_live_', 'sb_test_'];
if (!VALID_PREFIXES.some((p) => key.startsWith(p))) {
throw new CLIError(
Expand Down
3 changes: 3 additions & 0 deletions packages/cli/src/commands/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { EXIT_CODE, CLIError } from '../index';
import { getFlag } from '../flags';
import { resolveApiKey, API_KEY_ENV_VAR } from '../credentials';
import { EngineClient, type BuildRequest, type ScaffoldResult } from '../http-client';
import { printBuildCommandDeprecationWarning } from './deprecation-warning';

// ─── Animation ──────────────────────────────────────────────

Expand Down Expand Up @@ -69,6 +70,8 @@ function phaseDetail(label: string, result: ScaffoldResult): string {
// ─── Command ────────────────────────────────────────────────

export async function runCommand(options: CLIOptions, args: string[]): Promise<number> {
printBuildCommandDeprecationWarning('run', args);

// Parse flags first (getFlag consumes flag + value from args)
const filePath = getFlag(args, '--file');
const outputDir = getFlag(args, '--output');
Expand Down
3 changes: 3 additions & 0 deletions packages/cli/src/commands/scaffold.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,11 @@ import type { CLIOptions } from '../index';
import { EXIT_CODE, CLIError } from '../index';
import { getFlag } from '../flags';
import type { BuildResult } from '../http-client';
import { printBuildCommandDeprecationWarning } from './deprecation-warning';

export async function scaffoldCommand(options: CLIOptions, args: string[]): Promise<number> {
printBuildCommandDeprecationWarning('scaffold', args);

const configPath = options.configPath || '.charter';
const cachePath = path.join(configPath, 'last-build.json');

Expand Down
2 changes: 2 additions & 0 deletions packages/cli/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ Options:
--detect-only Setup only: print detected stack/preset and exit
--no-dependency-sync
Setup only: do not rewrite devDependencies["@stackbilt/cli"]
--no-deprecation-warning
Suppress deprecation warnings for login/architect/scaffold/run
`;

export const EXIT_CODE = {
Expand Down
Loading