Skip to content

Commit 348b5fc

Browse files
committed
feat: write migrated assets to theme during themes:migrate
The migration API now returns an `assets` hash (filename → base64 content). Decode and write each asset to the theme's assets/ folder after migration.
1 parent d35342a commit 348b5fc

5 files changed

Lines changed: 144 additions & 0 deletions

File tree

packages/zcli-themes/src/lib/migrate.test.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import * as getVariables from './getVariables'
66
import * as getAssets from './getAssets'
77
import * as rewriteManifest from './rewriteManifest'
88
import * as rewriteTemplates from './rewriteTemplates'
9+
import * as rewriteAssets from './rewriteAssets'
910
import * as axios from 'axios'
1011
import { request } from '@zendesk/zcli-core'
1112
import * as errors from '@oclif/core/lib/errors'
@@ -45,6 +46,7 @@ describe('migrate', () => {
4546
const getAssetsStub = sinon.stub(getAssets, 'default')
4647
const rewriteManifestStub = sinon.stub(rewriteManifest, 'default')
4748
const rewriteTemplatesStub = sinon.stub(rewriteTemplates, 'default')
49+
const rewriteAssetsStub = sinon.stub(rewriteAssets, 'default')
4850
const requestStub = sinon.stub(request, 'requestAPI')
4951

5052
getManifestStub.withArgs('theme/path').returns(manifest)
@@ -88,6 +90,9 @@ describe('migrate', () => {
8890
home_page: '<h1>Updated Home</h1>',
8991
'article_pages/product_updates': '<h1>Updated Product updates</h1>',
9092
'custom_pages/faq': '<h1>Updated FAQ</h1>'
93+
},
94+
assets: {
95+
'category_tree.js': Buffer.from('console.log("tree")').toString('base64')
9196
}
9297
}
9398
}) as axios.AxiosPromise
@@ -131,6 +136,12 @@ describe('migrate', () => {
131136
'custom_pages/faq': '<h1>Updated FAQ</h1>'
132137
})
133138
).to.equal(true)
139+
140+
expect(
141+
rewriteAssetsStub.calledWith('theme/path', {
142+
'category_tree.js': Buffer.from('console.log("tree")').toString('base64')
143+
})
144+
).to.equal(true)
134145
})
135146

136147
it('throws an error when validation fails with template errors', async () => {

packages/zcli-themes/src/lib/migrate.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { CliUx } from '@oclif/core'
1010
import type { AxiosError } from 'axios'
1111
import rewriteTemplates from './rewriteTemplates'
1212
import rewriteManifest from './rewriteManifest'
13+
import rewriteAssets from './rewriteAssets'
1314
import handleTemplateError from './handleTemplateError'
1415
import parseAxiosError from './parseAxiosError'
1516

@@ -50,6 +51,7 @@ export default async function migrate (themePath: string, flags: Flags): Promise
5051
})
5152
rewriteManifest(themePath, data.metadata.api_version)
5253
rewriteTemplates(themePath, data.templates)
54+
rewriteAssets(themePath, data.assets)
5355
CliUx.ux.action.stop('Ok')
5456
} catch (e) {
5557
CliUx.ux.action.stop(chalk.bold.red('!'))
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import * as sinon from 'sinon'
2+
import * as fs from 'fs'
3+
import { expect } from '@oclif/test'
4+
import rewriteAssets from './rewriteAssets'
5+
6+
describe('rewriteAssets', () => {
7+
beforeEach(() => {
8+
sinon.restore()
9+
})
10+
11+
it('decodes base64 content and writes assets to the correct file paths', () => {
12+
const mkdirSyncStub = sinon.stub(fs, 'mkdirSync')
13+
const writeFileSyncStub = sinon.stub(fs, 'writeFileSync')
14+
15+
const jsContent = Buffer.from('console.log("hello")').toString('base64')
16+
17+
rewriteAssets('theme/path', {
18+
'script.js': jsContent
19+
})
20+
21+
expect(mkdirSyncStub.calledOnce).to.equal(true)
22+
expect(mkdirSyncStub.firstCall.args[1]).to.deep.equal({ recursive: true })
23+
24+
expect(writeFileSyncStub.callCount).to.equal(1)
25+
expect(writeFileSyncStub.firstCall.args[0]).to.match(/theme\/path\/assets\/script\.js$/)
26+
expect(Buffer.compare(
27+
writeFileSyncStub.firstCall.args[1] as Buffer,
28+
Buffer.from('console.log("hello")')
29+
)).to.equal(0)
30+
})
31+
32+
it('handles multiple file types', () => {
33+
sinon.stub(fs, 'mkdirSync')
34+
const writeFileSyncStub = sinon.stub(fs, 'writeFileSync')
35+
36+
const jsContent = Buffer.from('var a = 1;').toString('base64')
37+
const pngContent = Buffer.from([0x89, 0x50, 0x4e, 0x47]).toString('base64')
38+
39+
rewriteAssets('theme/path', {
40+
'app.js': jsContent,
41+
'logo.png': pngContent
42+
})
43+
44+
expect(writeFileSyncStub.callCount).to.equal(2)
45+
expect(writeFileSyncStub.firstCall.args[0]).to.match(/theme\/path\/assets\/app\.js$/)
46+
expect(writeFileSyncStub.secondCall.args[0]).to.match(/theme\/path\/assets\/logo\.png$/)
47+
48+
expect(Buffer.compare(
49+
writeFileSyncStub.secondCall.args[1] as Buffer,
50+
Buffer.from([0x89, 0x50, 0x4e, 0x47])
51+
)).to.equal(0)
52+
})
53+
54+
it('handles empty assets object', () => {
55+
sinon.stub(fs, 'mkdirSync')
56+
const writeFileSyncStub = sinon.stub(fs, 'writeFileSync')
57+
58+
rewriteAssets('theme/path', {})
59+
60+
expect(writeFileSyncStub.callCount).to.equal(0)
61+
})
62+
63+
it('ignores write errors', () => {
64+
sinon.stub(fs, 'mkdirSync')
65+
const writeFileSyncStub = sinon.stub(fs, 'writeFileSync')
66+
67+
writeFileSyncStub.onFirstCall().throws(new Error('Permission denied'))
68+
writeFileSyncStub.onSecondCall().returns(undefined)
69+
70+
const jsContent = Buffer.from('first').toString('base64')
71+
const jsContent2 = Buffer.from('second').toString('base64')
72+
73+
expect(() => {
74+
rewriteAssets('theme/path', {
75+
'a.js': jsContent,
76+
'b.js': jsContent2
77+
})
78+
}).to.not.throw()
79+
80+
expect(writeFileSyncStub.callCount).to.equal(2)
81+
})
82+
83+
it('ignores mkdir errors', () => {
84+
const mkdirSyncStub = sinon.stub(fs, 'mkdirSync')
85+
const writeFileSyncStub = sinon.stub(fs, 'writeFileSync')
86+
87+
mkdirSyncStub.throws(new Error('Permission denied'))
88+
89+
const jsContent = Buffer.from('hello').toString('base64')
90+
91+
expect(() => {
92+
rewriteAssets('theme/path', { 'a.js': jsContent })
93+
}).to.not.throw()
94+
95+
expect(writeFileSyncStub.callCount).to.equal(1)
96+
})
97+
})
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import * as fs from 'fs'
2+
import * as path from 'path'
3+
4+
export default function rewriteAssets (themePath: string, assets: Record<string, string>) {
5+
const assetsDir = path.join(themePath, 'assets')
6+
7+
try {
8+
fs.mkdirSync(assetsDir, { recursive: true })
9+
} catch (error) {
10+
// Ignore errors if directory can't be created
11+
}
12+
13+
for (const [filename, base64Content] of Object.entries(assets)) {
14+
const filePath = path.join(assetsDir, filename)
15+
16+
try {
17+
fs.writeFileSync(filePath, Buffer.from(base64Content, 'base64'))
18+
} catch (error) {
19+
// Ignore errors if file can't be written
20+
}
21+
}
22+
}

packages/zcli-themes/tests/functional/migrate.test.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ describe('themes:migrate', function () {
1111
let fetchStub: sinon.SinonStub
1212
let manifestBackup: string
1313
let templateBackup: string
14+
const migratedAssetPath = path.join(baseThemePath, 'assets/category_tree.js')
1415

1516
beforeEach(() => {
1617
fetchStub = sinon.stub(global, 'fetch')
@@ -33,6 +34,10 @@ describe('themes:migrate', function () {
3334
path.join(baseThemePath, 'templates/document_head.hbs'),
3435
templateBackup
3536
)
37+
// Clean up migrated asset
38+
if (fs.existsSync(migratedAssetPath)) {
39+
fs.unlinkSync(migratedAssetPath)
40+
}
3641
})
3742

3843
describe('successful migration', () => {
@@ -55,6 +60,9 @@ describe('themes:migrate', function () {
5560
},
5661
templates: {
5762
document_head: '{{!chat (obsolete)}}'
63+
},
64+
assets: {
65+
'category_tree.js': Buffer.from('console.log("category_tree");\n').toString('base64')
5866
}
5967
})
6068
)
@@ -77,6 +85,10 @@ describe('themes:migrate', function () {
7785
'utf8'
7886
)
7987
expect(template).to.contain('{{!chat (obsolete)}}')
88+
89+
// Verify asset was written
90+
const asset = fs.readFileSync(migratedAssetPath, 'utf8')
91+
expect(asset).to.equal('console.log("category_tree");\n')
8092
})
8193
})
8294

0 commit comments

Comments
 (0)