Introduce TomlFile abstraction and migrate all TOML I/O callsites#6942
Closed
ryancbahan wants to merge 3 commits intomainfrom
Closed
Introduce TomlFile abstraction and migrate all TOML I/O callsites#6942ryancbahan wants to merge 3 commits intomainfrom
ryancbahan wants to merge 3 commits intomainfrom
Conversation
Adds a general-purpose TomlFile class in cli-kit that provides a unified interface for reading, patching, removing keys from, and replacing TOML files on disk. Migrates all callsites to use it, replacing the scattered setAppConfigValue/setManyAppConfigValues/unsetAppConfigValue functions and direct encodeToml/decodeToml usage. Key changes: - TomlFile class with read/patch/remove/replace/transformRaw methods - Extension builders return objects instead of TOML strings - writeAppConfigurationFile goes through TomlFile (replace + transformRaw for comment injection) - breakdown-extensions uses Object.keys() instead of encode→regex round-trip - encodeToml/decodeToml moved to internal codec module (not publicly exported) - TomlParseError wraps parse errors with file path context - Removed decode parameter from loadConfigurationFileContent/parseConfigurationFile Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ee1faf2 to
b485b0a
Compare
Contributor
Coverage report
Test suite run success3801 tests passing in 1453 suites. Report generated by 🧪jest coverage report action from b3b4356 |
…→ buildExtensionConfig These functions now return config objects instead of TOML strings, so the names should reflect that. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Contributor
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/toml/codec.d.tsimport { JsonMap } from '../../../private/common/json.js';
export type JsonMapType = JsonMap;
/**
* Given a TOML string, it returns a JSON object.
*
* @param input - TOML string.
* @returns JSON object.
*/
export declare function decodeToml(input: string): JsonMapType;
/**
* Given a JSON object, it returns a TOML string.
*
* @param content - JSON object.
* @returns TOML string.
*/
export declare function encodeToml(content: JsonMap | object): string;
packages/cli-kit/dist/public/node/toml/index.d.tsexport type { JsonMapType } from './codec.js';
packages/cli-kit/dist/public/node/toml/toml-file.d.tsimport { JsonMapType } from './codec.js';
/**
* Thrown when a TOML file cannot be parsed. Includes the file path for context.
*/
export declare class TomlParseError extends Error {
readonly path: string;
constructor(path: string, cause: Error);
}
/**
* General-purpose TOML file abstraction.
*
* Provides a unified interface for reading, patching, removing keys from, and replacing
* the content of TOML files on disk.
*
* - `read` populates content from disk
* - `patch` does surgical WASM-based edits (preserves comments and formatting)
* - `remove` deletes a key by dotted path (preserves comments and formatting)
* - `replace` does a full re-serialization (comments and formatting are NOT preserved).
* - `transformRaw` applies a function to the raw TOML string on disk.
*/
export declare class TomlFile {
/**
* Read and parse a TOML file from disk. Throws if the file doesn't exist or contains invalid TOML.
* Parse errors are wrapped in {@link TomlParseError} with the file path for context.
*
* @param path - Absolute path to the TOML file.
* @returns A TomlFile instance with parsed content.
*/
static read(path: string): Promise<TomlFile>;
readonly path: string;
content: JsonMapType;
constructor(path: string, content: JsonMapType);
/**
* Surgically patch values in the TOML file, preserving comments and formatting.
*
* Accepts a nested object whose leaf values are set in the TOML. Intermediate tables are
* created automatically. Setting a leaf to `undefined` removes it (use `remove()` for a
* clearer API when deleting keys).
*
* @example
* ```ts
* await file.patch({build: {dev_store_url: 'my-store.myshopify.com'}})
* await file.patch({application_url: 'https://example.com', auth: {redirect_urls: ['...']}})
* ```
*/
patch(changes: {
[key: string]: unknown;
}): Promise<void>;
/**
* Remove a key from the TOML file by dotted path, preserving comments and formatting.
*
* @param keyPath - Dotted key path to remove (e.g. 'build.include_config_on_deploy').
* @example
* ```ts
* await file.remove('build.include_config_on_deploy')
* ```
*/
remove(keyPath: string): Promise<void>;
/**
* Replace the entire file content. The file is fully re-serialized — comments and formatting
* are NOT preserved.
*
* @param content - The new content to write.
* @example
* ```ts
* await file.replace({client_id: 'abc', name: 'My App'})
* ```
*/
replace(content: JsonMapType): Promise<void>;
/**
* Transform the raw TOML string on disk. Reads the file, applies the transform function
* to the raw text, writes back, and re-parses to keep `content` in sync.
*
* Use this for text-level operations that can't be expressed as structured edits —
* e.g. Injecting comments or positional insertion of keys in arrays-of-tables.
* Subsequent `patch()` calls will preserve any comments added this way.
*
* @param transform - A function that receives the raw TOML string and returns the modified string.
* @example
* ```ts
* await file.transformRaw((raw) => `# Header comment\n${raw}`)
* ```
*/
transformRaw(transform: (raw: string) => string): Promise<void>;
}
Existing type declarationsWe found no diffs with existing type declarations |
Contributor
Author
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Why this PR exists
TOML file reads/writes are done ad-hoc today through a handful of different functions. This creates a few problems:
toml-patchwould require updating many callsites)This PR implements a
TomlFileAPI that formally encodes TOML file I/O and its corresponding types via a unified public interface. Its job is specifically to handle reads and writes, as well as validating whether the TOML format is valid. It does not handle Shopify app config or domain representations outside of TOML syntax validity.Summary
TomlFileclass incli-kit— api for reading, patching, removing keys from, and replacing TOML files on diskTomlFile, replacingsetAppConfigValue/setManyAppConfigValues/unsetAppConfigValuefunctions and directencodeToml/decodeTomlusageencodeToml/decodeTomlmoved to internalcodec.ts(no longer publicly exported)breakdown-extensions.tsdiff field extraction usesObject.keys()instead of encode→regex-parse round-tripwriteAppConfigurationFilenow goes throughTomlFile(replace+transformRawfor comment injection)TomlParseErrorwraps parse errors with file path contextTest plan
TomlFileunit tests pass (read/patch/remove/replace/transformRaw)shopify app config link,shopify app dev,shopify app deployproduce identical TOML output🤖 Generated with Claude Code