-
Notifications
You must be signed in to change notification settings - Fork 126
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #4551 from Shopify/new-patch-config-file
Patch app toml files
- Loading branch information
Showing
7 changed files
with
352 additions
and
126 deletions.
There are no files selected for viewing
174 changes: 174 additions & 0 deletions
174
packages/app/src/cli/services/app/patch-app-configuration-file.test.ts
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,174 @@ | ||
import {patchAppConfigurationFile} from './patch-app-configuration-file.js' | ||
import {getAppVersionedSchema} from '../../models/app/app.js' | ||
import {loadLocalExtensionsSpecifications} from '../../models/extensions/load-specifications.js' | ||
import {readFile, writeFileSync, inTemporaryDirectory} from '@shopify/cli-kit/node/fs' | ||
import {joinPath} from '@shopify/cli-kit/node/path' | ||
import {describe, expect, test} from 'vitest' | ||
|
||
const defaultToml = `# Learn more about configuring your app at https://shopify.dev/docs/apps/tools/cli/configuration | ||
client_id = "12345" | ||
name = "app1" | ||
embedded = true | ||
[access_scopes] | ||
# Learn more at https://shopify.dev/docs/apps/tools/cli/configuration#access_scopes | ||
use_legacy_install_flow = true | ||
[auth] | ||
redirect_urls = [ | ||
"https://example.com/redirect", | ||
"https://example.com/redirect2" | ||
] | ||
[webhooks] | ||
api_version = "2023-04" | ||
` | ||
|
||
const schema = getAppVersionedSchema(await loadLocalExtensionsSpecifications(), false) | ||
|
||
function writeDefaulToml(tmpDir: string) { | ||
const configPath = joinPath(tmpDir, 'shopify.app.toml') | ||
writeFileSync(configPath, defaultToml) | ||
return configPath | ||
} | ||
|
||
describe('patchAppConfigurationFile', () => { | ||
test('updates existing configuration with new values and adds new top-levelfields', async () => { | ||
await inTemporaryDirectory(async (tmpDir) => { | ||
const configPath = writeDefaulToml(tmpDir) | ||
const patch = { | ||
name: 'Updated App Name', | ||
application_url: 'https://example.com', | ||
access_scopes: { | ||
use_legacy_install_flow: false, | ||
}, | ||
} | ||
|
||
await patchAppConfigurationFile({path: configPath, patch, schema}) | ||
|
||
const updatedTomlFile = await readFile(configPath) | ||
expect(updatedTomlFile) | ||
.toEqual(`# Learn more about configuring your app at https://shopify.dev/docs/apps/tools/cli/configuration | ||
client_id = "12345" | ||
name = "Updated App Name" | ||
application_url = "https://example.com" | ||
embedded = true | ||
[access_scopes] | ||
# Learn more at https://shopify.dev/docs/apps/tools/cli/configuration#access_scopes | ||
use_legacy_install_flow = false | ||
[auth] | ||
redirect_urls = [ | ||
"https://example.com/redirect", | ||
"https://example.com/redirect2" | ||
] | ||
[webhooks] | ||
api_version = "2023-04" | ||
`) | ||
}) | ||
}) | ||
|
||
test('Adds new table to the toml file', async () => { | ||
await inTemporaryDirectory(async (tmpDir) => { | ||
const configPath = writeDefaulToml(tmpDir) | ||
const patch = { | ||
application_url: 'https://example.com', | ||
build: { | ||
dev_store_url: 'example.myshopify.com', | ||
}, | ||
} | ||
|
||
await patchAppConfigurationFile({path: configPath, patch, schema}) | ||
|
||
const updatedTomlFile = await readFile(configPath) | ||
expect(updatedTomlFile) | ||
.toEqual(`# Learn more about configuring your app at https://shopify.dev/docs/apps/tools/cli/configuration | ||
client_id = "12345" | ||
name = "app1" | ||
application_url = "https://example.com" | ||
embedded = true | ||
[build] | ||
dev_store_url = "example.myshopify.com" | ||
[access_scopes] | ||
# Learn more at https://shopify.dev/docs/apps/tools/cli/configuration#access_scopes | ||
use_legacy_install_flow = true | ||
[auth] | ||
redirect_urls = [ | ||
"https://example.com/redirect", | ||
"https://example.com/redirect2" | ||
] | ||
[webhooks] | ||
api_version = "2023-04" | ||
`) | ||
}) | ||
}) | ||
|
||
test('Adds a new field to a toml table, merging with exsisting values', async () => { | ||
await inTemporaryDirectory(async (tmpDir) => { | ||
const configPath = writeDefaulToml(tmpDir) | ||
const patch = { | ||
application_url: 'https://example.com', | ||
access_scopes: { | ||
scopes: 'read_products', | ||
}, | ||
} | ||
|
||
await patchAppConfigurationFile({path: configPath, patch, schema}) | ||
|
||
const updatedTomlFile = await readFile(configPath) | ||
expect(updatedTomlFile) | ||
.toEqual(`# Learn more about configuring your app at https://shopify.dev/docs/apps/tools/cli/configuration | ||
client_id = "12345" | ||
name = "app1" | ||
application_url = "https://example.com" | ||
embedded = true | ||
[access_scopes] | ||
# Learn more at https://shopify.dev/docs/apps/tools/cli/configuration#access_scopes | ||
scopes = "read_products" | ||
use_legacy_install_flow = true | ||
[auth] | ||
redirect_urls = [ | ||
"https://example.com/redirect", | ||
"https://example.com/redirect2" | ||
] | ||
[webhooks] | ||
api_version = "2023-04" | ||
`) | ||
}) | ||
}) | ||
|
||
test('does not validate the toml if no schema is provided', async () => { | ||
await inTemporaryDirectory(async (tmpDir) => { | ||
const configPath = joinPath(tmpDir, 'shopify.app.toml') | ||
writeFileSync( | ||
configPath, | ||
` | ||
random_toml_field = "random_value" | ||
`, | ||
) | ||
const patch = {name: 123} | ||
|
||
await patchAppConfigurationFile({path: configPath, patch, schema: undefined}) | ||
|
||
const updatedTomlFile = await readFile(configPath) | ||
expect(updatedTomlFile) | ||
.toEqual(`# Learn more about configuring your app at https://shopify.dev/docs/apps/tools/cli/configuration | ||
random_toml_field = "random_value" | ||
name = 123 | ||
`) | ||
}) | ||
}) | ||
}) |
35 changes: 35 additions & 0 deletions
35
packages/app/src/cli/services/app/patch-app-configuration-file.ts
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
import {addDefaultCommentsToToml} from './write-app-configuration-file.js' | ||
import {deepMergeObjects} from '@shopify/cli-kit/common/object' | ||
import {readFile, writeFile} from '@shopify/cli-kit/node/fs' | ||
import {zod} from '@shopify/cli-kit/node/schema' | ||
import {decodeToml, encodeToml} from '@shopify/cli-kit/node/toml' | ||
|
||
export interface PatchTomlOptions { | ||
path: string | ||
patch: {[key: string]: unknown} | ||
schema?: zod.AnyZodObject | ||
} | ||
|
||
/** | ||
* Updates an app/extension configuration file with the given patch. | ||
* | ||
* Only updates the given fields in the patch and leaves the rest of the file unchanged. | ||
* | ||
* @param path - The path to the app/extension configuration file. | ||
* @param patch - The patch to apply to the app/extension configuration file. | ||
* @param schema - The schema to validate the patch against. If not provided, the toml will not be validated. | ||
*/ | ||
export async function patchAppConfigurationFile({path, patch, schema}: PatchTomlOptions) { | ||
const tomlContents = await readFile(path) | ||
const configuration = decodeToml(tomlContents) | ||
const updatedConfig = deepMergeObjects(configuration, patch) | ||
|
||
// Re-parse the config with the schema to validate the patch and keep the same order in the file | ||
// Make every field optional to not crash on invalid tomls that are missing fields. | ||
const validSchema = schema ?? zod.object({}).passthrough() | ||
const validatedConfig = validSchema.partial().parse(updatedConfig) | ||
let encodedString = encodeToml(validatedConfig) | ||
|
||
encodedString = addDefaultCommentsToToml(encodedString) | ||
await writeFile(path, encodedString) | ||
} |
This file contains 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
Oops, something went wrong.