Repository navigation
Expose app dev session files for agents - #8926
isaacroldan wants to merge 3 commits into
Conversation
|
/snapit |
|
🫰✨ Thanks @isaacroldan! Your snapshot has been published to npm. Built from Test the snapshot by installing your package globally: pnpm i -g --@shopify:registry=https://registry.npmjs.org @shopify/cli@0.0.0-snapshot-20261015153801Caution After installing, validate the version by running |
dee7821 to
2b3b7dd
Compare
|
/snapit |
|
🫰✨ Thanks @isaacroldan! Your snapshot has been published to npm. Built from Test the snapshot by installing your package globally: pnpm i -g --@shopify:registry=https://registry.npmjs.org @shopify/cli@0.0.0-snapshot-20261019115812Caution After installing, validate the version by running |
cfd91fb to
b06cf68
Compare
|
/snapit |
|
🫰✨ Thanks @isaacroldan! Your snapshot has been published to npm. Built from Test the snapshot by installing your package globally: pnpm i -g --@shopify:registry=https://registry.npmjs.org @shopify/cli@0.0.0-snapshot-20261019155212Caution After installing, validate the version by running |
b06cf68 to
42ae66e
Compare
Differences in type declarationsWe detected differences in the type declarations generated by Typescript for this branch compared to the baseline ('main' branch). Please, review them to ensure they are backward-compatible. Here are some important things to keep in mind:
New type declarationspackages/cli-kit/dist/public/node/dev-instance.d.tsimport { Writable } from 'stream';
/**
* A stable error code for JSON consumers when a long-running command refuses to start because another instance of
* it is already running for the same project.
*/
export declare const DEV_INSTANCE_ALREADY_RUNNING_CODE = "dev_instance_already_running";
/**
* Describes a running instance of a long-running command such as `app dev` or `theme dev`.
* It's written to `.shopify/dev/<slug>.json` so agents and other tools can discover the instance.
*/
export interface DevInstanceInfo {
schemaVersion: 1;
/** Identifies this run, including when the OS reuses a PID. */
sessionId: string;
/** The command that owns the instance, for example `app dev`. */
command: string;
/** Distinguishes instances of the same command in the same directory, for example the app configuration file. */
key?: string;
pid: number;
status: 'starting' | 'running';
startedAt: string;
updatedAt: string;
/** The project directory the instance runs for. */
directory: string;
/** The JSON Lines file the instance appends its output to. */
logFile: string;
/** Command-specific, JSON-serializable details such as URLs and ports. */
details: {
[key: string]: unknown;
};
}
/** One line of a dev instance's JSON Lines log. */
export interface DevInstanceLogEntry {
timestamp: string;
/** What produced the line, for example a process prefix like `web` or `extensions`. */
source: string;
/** The stream the line was written to, when it comes from a process. */
stream?: 'stdout' | 'stderr';
level?: 'debug' | 'info' | 'warning' | 'error';
message: string;
}
export interface DevInstancePaths {
/** The folder that holds every dev instance of the project: `.shopify/dev`. */
folder: string;
/** The instance description. */
infoFile: string;
/** The lock directory, owned by `proper-lockfile`. */
lockFile: string;
/** The JSON Lines log. */
logFile: string;
}
export interface DevInstanceIdentity {
/** The command that owns the instance, for example `app dev`. */
command: string;
/** The project directory. */
directory: string;
/** Distinguishes several instances of the same command in the same directory. */
key?: string;
}
export interface AcquireDevInstanceOptions extends DevInstanceIdentity {
/** Initial command-specific details. */
details?: {
[key: string]: unknown;
};
}
export interface DevInstanceUpdate {
status?: DevInstanceInfo['status'];
/** Merged into the existing details. */
details?: {
[key: string]: unknown;
};
}
export interface DevInstance {
readonly info: DevInstanceInfo;
readonly paths: DevInstancePaths;
/** Aborts when this session loses its lock or output fails. Consumers must stop their owned work. */
readonly abortSignal: AbortSignal;
/** Updates and persists the instance description. */
update: (changes: DevInstanceUpdate) => Promise<void>;
/** Appends one entry to the JSON Lines log. */
log: (entry: Omit<DevInstanceLogEntry, 'timestamp'>) => Promise<void>;
/**
* Wraps a stream so every line written to it is also appended to the log, without ANSI styling.
* The returned stream forwards the original bytes to `target` unchanged.
*/
tee: (target: Writable, options: {
source: string;
stream: 'stdout' | 'stderr';
}) => Writable;
/** Closes the log, removes the instance description and releases the lock. Safe to call more than once. */
release: () => Promise<void>;
}
/**
* Returns the files used by a dev instance. Every instance of a project shares `.shopify/dev`,
* with one `<slug>.json`, `<slug>.lock` and `<slug>.jsonl` per command and key.
*
* @param identity - The command, directory and optional key of the instance.
* @returns The paths of the instance files.
*/
export declare function devInstancePaths(identity: DevInstanceIdentity): DevInstancePaths;
/**
* Marks this process as the running instance of a long-running command for a project.
*
* Mutual exclusion uses `proper-lockfile`: an atomic `mkdir` lock whose mtime this process refreshes as a heartbeat.
* An exclusive startup directory serializes abandoned-lock recovery. An interrupted startup requires explicit
* cleanup of that directory. A live owner blocks takeover, including when its heartbeat is stale.
*
* @param options - The instance identity, initial details.
* @returns The acquired instance.
* @throws AbortError with code {@link DEV_INSTANCE_ALREADY_RUNNING_CODE} when another live instance holds the lock.
*/
export declare function acquireDevInstance(options: AcquireDevInstanceOptions): Promise<DevInstance>;
/**
* Returns the running instance of a command for a project, if there is one.
*
* @param identity - The command, directory and optional key of the instance.
* @returns The instance description, or undefined when no live instance holds the lock.
*/
export declare function readDevInstance(identity: DevInstanceIdentity): Promise<DevInstanceInfo | undefined>;
/**
* Lists every running dev instance of a project, across commands and keys.
*
* @param directory - The project directory.
* @returns The descriptions of the live instances.
*/
export declare function listDevInstances(directory: string): Promise<DevInstanceInfo[]>;
/**
* Checks whether a process exists. A process owned by another user counts as alive.
*
* @param pid - The process ID.
* @returns True when the process exists.
*/
export declare function isProcessAlive(pid: number): boolean;
Existing type declarationsWe found no diffs with existing type declarations |
WHY are these changes introduced?
Agents need to find an existing
app devsession and read its current status and output from another terminal. This PR builds on the reusable CLI Kit API in #8952.WHAT is this pull request doing?
Register one session per app configuration, capture process output in JSONL, and save the CLI's current preview status and errors. Non-interactive sessions remain registered until they stop, and dev work stops if session ownership or output fails.
The session files include the PID, session ID, URLs, log path, and
details.preview. Preview status reflects the CLI's reported state. The agent workflow and integration tests are included here; file ownership, logging, and their unit tests are in #8952.How to manually test your changes?
shopify app devfor a configured app and development store. From another terminal, read.shopify/dev/*.jsonand the reportedlogFile.details.preview.stateanderrors, then correct the import and check the new output and status.--no-input. Confirm the session ID changes, the log starts fresh, and the session remains registered while dev runs. Stop it with SIGTERM and confirm the instance record is removed.See
docs/cli/app-dev-agent-workflow.mdfor the file-reading workflow.Validation
The combined stack passes 99 focused tests, App lint and type checking, and the TypeScript build. Source and documentation match the pre-split implementation exactly. Its crash, pause/resume, status-write, log-load, and shutdown probes passed locally on macOS. No live-store test was performed.
Checklist