@@ -71,6 +71,10 @@ function runCmd(cmd, args, cwd) {
7171 return res ;
7272}
7373
74+ function readTemplateThemeTokens ( ) {
75+ return fs . readFileSync ( path . join ( process . cwd ( ) , 'packages' , 'templates' , 'next-export-ui' , 'src' , 'theme' , 'tokens.json' ) , 'utf-8' ) ;
76+ }
77+
7478function minimalSchema ( ) {
7579 return {
7680 thsVersion : '2025-12' ,
@@ -95,6 +99,12 @@ function minimalSchema() {
9599 } ;
96100}
97101
102+ function minimalSchemaWithThemePreset ( ) {
103+ const schema = minimalSchema ( ) ;
104+ schema . app . theme = { preset : 'cyber-grid' } ;
105+ return schema ;
106+ }
107+
98108function schemaWithUiOverrides ( ) {
99109 return {
100110 thsVersion : '2025-12' ,
@@ -161,6 +171,25 @@ describe('th generate (UI template)', function () {
161171 const layoutSource = fs . readFileSync ( path . join ( outDir , 'ui' , 'app' , 'layout.tsx' ) , 'utf-8' ) ;
162172 expect ( layoutSource ) . to . include ( 'NetworkStatus' ) ;
163173 expect ( layoutSource ) . to . include ( 'rootStyleVars' ) ;
174+
175+ const generatedTokens = fs . readFileSync ( path . join ( outDir , 'ui' , 'src' , 'theme' , 'tokens.json' ) , 'utf-8' ) ;
176+ expect ( generatedTokens ) . to . equal ( readTemplateThemeTokens ( ) ) ;
177+ } ) ;
178+
179+ it ( 'materializes the explicit cyber-grid theme preset into generated UI output' , function ( ) {
180+ const dir = fs . mkdtempSync ( path . join ( os . tmpdir ( ) , 'th-ui-theme-preset-' ) ) ;
181+ const schemaPath = path . join ( dir , 'schema.json' ) ;
182+ const outDir = path . join ( dir , 'out' ) ;
183+ writeJson ( schemaPath , minimalSchemaWithThemePreset ( ) ) ;
184+
185+ const res = runTh ( [ 'generate' , schemaPath , '--out' , outDir ] , process . cwd ( ) ) ;
186+ expect ( res . status , res . stderr || res . stdout ) . to . equal ( 0 ) ;
187+
188+ const generatedThs = fs . readFileSync ( path . join ( outDir , 'ui' , 'src' , 'generated' , 'ths.ts' ) , 'utf-8' ) ;
189+ expect ( generatedThs ) . to . include ( '"preset": "cyber-grid"' ) ;
190+
191+ const generatedTokens = fs . readFileSync ( path . join ( outDir , 'ui' , 'src' , 'theme' , 'tokens.json' ) , 'utf-8' ) ;
192+ expect ( generatedTokens ) . to . equal ( readTemplateThemeTokens ( ) ) ;
164193 } ) ;
165194
166195 it ( 'generated UI builds (next export)' , function ( ) {
@@ -228,6 +257,8 @@ describe('th generate (UI template)', function () {
228257 const uiDir = path . join ( outDir , 'ui' ) ;
229258 expect ( fs . existsSync ( path . join ( uiDir , 'app' , 'page.tsx' ) ) ) . to . equal ( true ) ;
230259 expect ( fs . existsSync ( path . join ( uiDir , 'app' , 'tag' , 'page.tsx' ) ) ) . to . equal ( true ) ;
260+ const generatedThs = fs . readFileSync ( path . join ( uiDir , 'src' , 'generated' , 'ths.ts' ) , 'utf-8' ) ;
261+ expect ( generatedThs ) . to . include ( '"preset": "cyber-grid"' ) ;
231262
232263 const install = runCmd ( 'pnpm' , [ 'install' ] , uiDir ) ;
233264 expect ( install . status , install . stderr || install . stdout ) . to . equal ( 0 ) ;
@@ -362,6 +393,29 @@ describe('th ui sync', function () {
362393 expect ( fs . existsSync ( path . join ( outDir , 'ui' , 'app' , 'run' , 'page.tsx' ) ) ) . to . equal ( true ) ;
363394 } ) ;
364395
396+ it ( 'preserves lockfiles and existing node_modules when package.json is unchanged' , function ( ) {
397+ const dir = fs . mkdtempSync ( path . join ( os . tmpdir ( ) , 'th-ui-sync-preserve-' ) ) ;
398+ const schemaPath = path . join ( dir , 'schema.json' ) ;
399+ const outDir = path . join ( dir , 'out' ) ;
400+ const uiDir = path . join ( outDir , 'ui' ) ;
401+
402+ writeJson ( schemaPath , minimalSchema ( ) ) ;
403+ writeCompiledArtifact ( path . join ( outDir , 'compiled' , 'App.json' ) ) ;
404+ writeManifest ( path . join ( outDir , 'manifest.json' ) ) ;
405+
406+ const first = runTh ( [ 'ui' , 'sync' , schemaPath , '--out' , outDir ] , process . cwd ( ) ) ;
407+ expect ( first . status , first . stderr || first . stdout ) . to . equal ( 0 ) ;
408+
409+ fs . writeFileSync ( path . join ( uiDir , 'pnpm-lock.yaml' ) , 'lockfileVersion: 9.0\n' ) ;
410+ fs . mkdirSync ( path . join ( uiDir , 'node_modules' , '.keep' ) , { recursive : true } ) ;
411+ fs . writeFileSync ( path . join ( uiDir , 'node_modules' , '.keep' , 'marker.txt' ) , 'present\n' ) ;
412+
413+ const second = runTh ( [ 'ui' , 'sync' , schemaPath , '--out' , outDir ] , process . cwd ( ) ) ;
414+ expect ( second . status , second . stderr || second . stdout ) . to . equal ( 0 ) ;
415+ expect ( fs . readFileSync ( path . join ( uiDir , 'pnpm-lock.yaml' ) , 'utf-8' ) ) . to . equal ( 'lockfileVersion: 9.0\n' ) ;
416+ expect ( fs . readFileSync ( path . join ( uiDir , 'node_modules' , '.keep' , 'marker.txt' ) , 'utf-8' ) ) . to . equal ( 'present\n' ) ;
417+ } ) ;
418+
365419 it ( 'fails clearly when compiled artifacts are missing' , function ( ) {
366420 const dir = fs . mkdtempSync ( path . join ( os . tmpdir ( ) , 'th-ui-sync-missing-compiled-' ) ) ;
367421 const schemaPath = path . join ( dir , 'schema.json' ) ;
0 commit comments