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
2 changes: 1 addition & 1 deletion README.de.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
<p align="center">
<a href="./docs/commands.md"><img src="https://img.shields.io/badge/store%20API-189%20endpoints-8957e5?logo=apple&logoColor=white" alt="189 App Store Connect &amp; Google Play API operations" /></a>
<img src="https://img.shields.io/badge/CRUD-full%20lifecycle-1f6feb" alt="Full create / read / update / delete coverage across the store APIs" />
<a href="https://github.com/YosefHayim/launch-store/actions/workflows/ci.yml"><img src="https://img.shields.io/badge/tests-2319%20passing-3fb950?logo=vitest&logoColor=white" alt="2319 tests passing" /></a>
<a href="https://github.com/YosefHayim/launch-store/actions/workflows/ci.yml"><img src="https://img.shields.io/badge/tests-2326%20passing-3fb950?logo=vitest&logoColor=white" alt="2326 tests passing" /></a>
</p>

<!-- stats-badges:end -->
Expand Down
2 changes: 1 addition & 1 deletion README.es.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
<p align="center">
<a href="./docs/commands.md"><img src="https://img.shields.io/badge/store%20API-189%20endpoints-8957e5?logo=apple&logoColor=white" alt="189 App Store Connect &amp; Google Play API operations" /></a>
<img src="https://img.shields.io/badge/CRUD-full%20lifecycle-1f6feb" alt="Full create / read / update / delete coverage across the store APIs" />
<a href="https://github.com/YosefHayim/launch-store/actions/workflows/ci.yml"><img src="https://img.shields.io/badge/tests-2319%20passing-3fb950?logo=vitest&logoColor=white" alt="2319 tests passing" /></a>
<a href="https://github.com/YosefHayim/launch-store/actions/workflows/ci.yml"><img src="https://img.shields.io/badge/tests-2326%20passing-3fb950?logo=vitest&logoColor=white" alt="2326 tests passing" /></a>
</p>

<!-- stats-badges:end -->
Expand Down
2 changes: 1 addition & 1 deletion README.fr.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
<p align="center">
<a href="./docs/commands.md"><img src="https://img.shields.io/badge/store%20API-189%20endpoints-8957e5?logo=apple&logoColor=white" alt="189 App Store Connect &amp; Google Play API operations" /></a>
<img src="https://img.shields.io/badge/CRUD-full%20lifecycle-1f6feb" alt="Full create / read / update / delete coverage across the store APIs" />
<a href="https://github.com/YosefHayim/launch-store/actions/workflows/ci.yml"><img src="https://img.shields.io/badge/tests-2319%20passing-3fb950?logo=vitest&logoColor=white" alt="2319 tests passing" /></a>
<a href="https://github.com/YosefHayim/launch-store/actions/workflows/ci.yml"><img src="https://img.shields.io/badge/tests-2326%20passing-3fb950?logo=vitest&logoColor=white" alt="2326 tests passing" /></a>
</p>

<!-- stats-badges:end -->
Expand Down
2 changes: 1 addition & 1 deletion README.ja.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
<p align="center">
<a href="./docs/commands.md"><img src="https://img.shields.io/badge/store%20API-189%20endpoints-8957e5?logo=apple&logoColor=white" alt="189 App Store Connect &amp; Google Play API operations" /></a>
<img src="https://img.shields.io/badge/CRUD-full%20lifecycle-1f6feb" alt="Full create / read / update / delete coverage across the store APIs" />
<a href="https://github.com/YosefHayim/launch-store/actions/workflows/ci.yml"><img src="https://img.shields.io/badge/tests-2319%20passing-3fb950?logo=vitest&logoColor=white" alt="2319 tests passing" /></a>
<a href="https://github.com/YosefHayim/launch-store/actions/workflows/ci.yml"><img src="https://img.shields.io/badge/tests-2326%20passing-3fb950?logo=vitest&logoColor=white" alt="2326 tests passing" /></a>
</p>

<!-- stats-badges:end -->
Expand Down
2 changes: 1 addition & 1 deletion README.ko.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
<p align="center">
<a href="./docs/commands.md"><img src="https://img.shields.io/badge/store%20API-189%20endpoints-8957e5?logo=apple&logoColor=white" alt="189 App Store Connect &amp; Google Play API operations" /></a>
<img src="https://img.shields.io/badge/CRUD-full%20lifecycle-1f6feb" alt="Full create / read / update / delete coverage across the store APIs" />
<a href="https://github.com/YosefHayim/launch-store/actions/workflows/ci.yml"><img src="https://img.shields.io/badge/tests-2319%20passing-3fb950?logo=vitest&logoColor=white" alt="2319 tests passing" /></a>
<a href="https://github.com/YosefHayim/launch-store/actions/workflows/ci.yml"><img src="https://img.shields.io/badge/tests-2326%20passing-3fb950?logo=vitest&logoColor=white" alt="2326 tests passing" /></a>
</p>

<!-- stats-badges:end -->
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
<p align="center">
<a href="./docs/commands.md"><img src="https://img.shields.io/badge/store%20API-189%20endpoints-8957e5?logo=apple&logoColor=white" alt="189 App Store Connect &amp; Google Play API operations" /></a>
<img src="https://img.shields.io/badge/CRUD-full%20lifecycle-1f6feb" alt="Full create / read / update / delete coverage across the store APIs" />
<a href="https://github.com/YosefHayim/launch-store/actions/workflows/ci.yml"><img src="https://img.shields.io/badge/tests-2319%20passing-3fb950?logo=vitest&logoColor=white" alt="2319 tests passing" /></a>
<a href="https://github.com/YosefHayim/launch-store/actions/workflows/ci.yml"><img src="https://img.shields.io/badge/tests-2326%20passing-3fb950?logo=vitest&logoColor=white" alt="2326 tests passing" /></a>
</p>

<!-- stats-badges:end -->
Expand Down
2 changes: 1 addition & 1 deletion README.pt-BR.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
<p align="center">
<a href="./docs/commands.md"><img src="https://img.shields.io/badge/store%20API-189%20endpoints-8957e5?logo=apple&logoColor=white" alt="189 App Store Connect &amp; Google Play API operations" /></a>
<img src="https://img.shields.io/badge/CRUD-full%20lifecycle-1f6feb" alt="Full create / read / update / delete coverage across the store APIs" />
<a href="https://github.com/YosefHayim/launch-store/actions/workflows/ci.yml"><img src="https://img.shields.io/badge/tests-2319%20passing-3fb950?logo=vitest&logoColor=white" alt="2319 tests passing" /></a>
<a href="https://github.com/YosefHayim/launch-store/actions/workflows/ci.yml"><img src="https://img.shields.io/badge/tests-2326%20passing-3fb950?logo=vitest&logoColor=white" alt="2326 tests passing" /></a>
</p>

<!-- stats-badges:end -->
Expand Down
2 changes: 1 addition & 1 deletion README.ru.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
<p align="center">
<a href="./docs/commands.md"><img src="https://img.shields.io/badge/store%20API-189%20endpoints-8957e5?logo=apple&logoColor=white" alt="189 App Store Connect &amp; Google Play API operations" /></a>
<img src="https://img.shields.io/badge/CRUD-full%20lifecycle-1f6feb" alt="Full create / read / update / delete coverage across the store APIs" />
<a href="https://github.com/YosefHayim/launch-store/actions/workflows/ci.yml"><img src="https://img.shields.io/badge/tests-2319%20passing-3fb950?logo=vitest&logoColor=white" alt="2319 tests passing" /></a>
<a href="https://github.com/YosefHayim/launch-store/actions/workflows/ci.yml"><img src="https://img.shields.io/badge/tests-2326%20passing-3fb950?logo=vitest&logoColor=white" alt="2326 tests passing" /></a>
</p>

<!-- stats-badges:end -->
Expand Down
2 changes: 1 addition & 1 deletion README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
<p align="center">
<a href="./docs/commands.md"><img src="https://img.shields.io/badge/store%20API-189%20endpoints-8957e5?logo=apple&logoColor=white" alt="189 App Store Connect &amp; Google Play API operations" /></a>
<img src="https://img.shields.io/badge/CRUD-full%20lifecycle-1f6feb" alt="Full create / read / update / delete coverage across the store APIs" />
<a href="https://github.com/YosefHayim/launch-store/actions/workflows/ci.yml"><img src="https://img.shields.io/badge/tests-2319%20passing-3fb950?logo=vitest&logoColor=white" alt="2319 tests passing" /></a>
<a href="https://github.com/YosefHayim/launch-store/actions/workflows/ci.yml"><img src="https://img.shields.io/badge/tests-2326%20passing-3fb950?logo=vitest&logoColor=white" alt="2326 tests passing" /></a>
</p>

<!-- stats-badges:end -->
Expand Down
2 changes: 1 addition & 1 deletion docs/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

# Launch command reference

> Launch wraps **189 App Store Connect & Google Play API operations** across **63 commands**, guarded by **2319 tests**.
> Launch wraps **189 App Store Connect & Google Play API operations** across **63 commands**, guarded by **2326 tests**.

Generated from the `commander` definitions in `src/cli/` by `pnpm docs:gen` - edit the commands, then regenerate. For the curated overview, install, and configuration, see the [README](../README.md).

Expand Down
2 changes: 1 addition & 1 deletion llms.txt
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ Each is declared in `launch.config.ts` (or a `*.config.json` sidecar) and reconc

## Commands

All 63 `launch` commands (189 store-API operations underneath, 2319 tests):
All 63 `launch` commands (189 store-API operations underneath, 2326 tests):

- `launch init` - scaffold launch.config.ts (and .env.example) into the current repo
- `launch adopt` - onboard an app that already ships: import its App Store Connect setup into config
Expand Down
7 changes: 7 additions & 0 deletions src/cli/commands/creds.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Command } from 'commander';
import { describe, expect, it } from 'vitest';
import * as credsModule from './creds.js';
import { registerCredsCommand } from './creds.js';

const credsCommand = () => {
Expand All @@ -21,3 +22,9 @@ describe('registerCredsCommand - non-interactive app selector (#261)', () => {
expect(longs).toContain('--yes');
});
});

describe('registerCredsCommand - thin CLI boundary', () => {
it('exports only registration (no Promise facades for wizard helpers)', () => {
expect(Object.keys(credsModule).sort()).toEqual(['registerCredsCommand']);
});
});
12 changes: 1 addition & 11 deletions src/cli/commands/creds.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,12 @@
import type { Command } from 'commander';
import { Effect } from 'effect';
import {
credentialsCommandProgram,
type CredentialsCommandOptions,
} from '@core/credentials/command.js';
import { AppStoreIdentityLive } from '@core/services/appStoreIdentity.js';
import { AppleCredentialsClientLive } from '@core/services/appleCredentialsClient.js';
import { runCliProgram } from '../runCliProgram.js';

export type CredsOptions = CredentialsCommandOptions;

/** Supply credential-specific Apple adapters to a core credentials command. */
const provideCredentialAdapters = (commandInput: Parameters<typeof credentialsCommandProgram>[0]) =>
credentialsCommandProgram(commandInput).pipe(
Effect.provide(AppStoreIdentityLive),
Effect.provide(AppleCredentialsClientLive),
);

/** Attach the credentials command and pass raw Commander input to the core schema boundary. */
export const registerCredsCommand = (program: Command): void => {
program
Expand Down Expand Up @@ -50,7 +40,7 @@ export const registerCredsCommand = (program: Command): void => {
commandOptions: CredsOptions,
) =>
runCliProgram(
provideCredentialAdapters({
credentialsCommandProgram({
action,
firstArgument,
secondArgument,
Expand Down
116 changes: 116 additions & 0 deletions src/core/credentials/command.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,25 @@
import { NodeContext } from '@effect/platform-node';
import { Effect, Schema } from 'effect';
import { describe, expect, it } from 'vitest';
import {
AppStoreIdentityService,
type AppStoreIdentityService as AppStoreIdentity,
} from '../services/appStoreIdentity.js';
import {
AppleCredentialsClientFactory,
type AppleCredentialsClientFactory as AppleCredentialsFactory,
} from '../services/appleCredentialsClient.js';
import { LaunchEnvironmentTest } from '../services/environment.js';
import { makeLaunchLoggerTest } from '../services/logger.js';
import { makeLaunchPathsTest } from '../services/paths.js';
import { makeLaunchPromptTest } from '../services/prompt.js';
import { makeLaunchSecretStoreTest } from '../services/secretStore.js';
import {
credentialSearchDirectories,
credentialsCommandProgram,
CredentialsCommandInputSchema,
isCredentialDiscoveryFile,
type CredentialsCommandFailure,
} from './command.js';

const discoveryFixture = (filePath: string) =>
Expand All @@ -16,6 +31,39 @@ const discoveryFixture = (filePath: string) =>
return yield* isCredentialDiscoveryFile(filePath, searchDirectories);
}).pipe(Effect.provide(NodeContext.layer));

const unusedIdentity: AppStoreIdentity = {
verifyCredentials: () =>
Effect.fail({
_tag: 'AppleTransportFailure',
message: 'identity stub is not used by pure validation tests',
cause: 'unused',
status: 500,
}),
resolveIdentity: () => Effect.succeed({ teamId: null, apps: [] }),
};

const unusedCredentialsFactory: AppleCredentialsFactory = {
createClient: () => Effect.die('credentials client stub is not used by pure validation tests'),
};

/** Run a credentials command with testkit layers; pure validation fails before live Apple/Play. */
const runCredentialsFailure = (commandInput: unknown): Promise<CredentialsCommandFailure> =>
Effect.runPromise(
credentialsCommandProgram(commandInput).pipe(
Effect.flip,
Effect.provide(NodeContext.layer),
Effect.provide(LaunchEnvironmentTest),
Effect.provide(
makeLaunchPathsTest('/tmp/launch-creds-command-test', '/tmp/launch-creds-command-test'),
),
Effect.provide(makeLaunchLoggerTest([])),
Effect.provide(makeLaunchPromptTest()),
Effect.provide(makeLaunchSecretStoreTest()),
Effect.provideService(AppStoreIdentityService, unusedIdentity),
Effect.provideService(AppleCredentialsClientFactory, unusedCredentialsFactory),
),
);

describe('credential discovery directories', () => {
it('matches a key directly inside Downloads', async () => {
await expect(
Expand Down Expand Up @@ -63,4 +111,72 @@ describe('CredentialsCommandInputSchema', () => {
}),
).toThrow();
});

it('accepts logout as a remove alias at the schema boundary', () => {
expect(
Schema.decodeUnknownSync(CredentialsCommandInputSchema)({
action: 'logout',
firstArgument: 'acme',
options: { yes: true },
}),
).toEqual({
action: 'logout',
firstArgument: 'acme',
options: { yes: true },
});
});
});

describe('credentialsCommandProgram validation', () => {
it('preserves the rename operation tag when arguments are missing', async () => {
const failure = await runCredentialsFailure({
action: 'rename',
options: { yes: true },
});
expect(failure._tag).toBe('CredentialsCommandFailure');
expect(failure.operation).toBe('rename Apple account');
expect(failure.message).toContain('launch creds rename');
});

it('preserves the remove operation tag when the account selector is missing', async () => {
const failure = await runCredentialsFailure({
action: 'remove',
options: { yes: true },
});
expect(failure._tag).toBe('CredentialsCommandFailure');
expect(failure.operation).toBe('remove Apple account');
expect(failure.message).toContain('launch creds remove');
});

it('requires an account selector for non-interactive use', async () => {
const failure = await runCredentialsFailure({
action: 'use',
options: { yes: true },
});
expect(failure._tag).toBe('CredentialsCommandFailure');
expect(failure.operation).toBe('use Apple account');
expect(failure.message).toContain('account label or Key ID');
});

it('rejects an unknown push-key subcommand without touching the vault', async () => {
const failure = await runCredentialsFailure({
action: 'push-key',
firstArgument: 'rotate',
options: { yes: true },
});
expect(failure._tag).toBe('CredentialsCommandFailure');
expect(failure.operation).toBe('run APNs command');
expect(failure.message).toContain('import, status, or export');
});

it('requires a Key ID for non-interactive APNs export', async () => {
const failure = await runCredentialsFailure({
action: 'push-key',
firstArgument: 'export',
options: { yes: true },
});
expect(failure._tag).toBe('CredentialsCommandFailure');
expect(failure.operation).toBe('export APNs key');
expect(failure.message).toContain('push-key export');
});
});
Loading
Loading