@teispace/next-maker 1.18.0 → 1.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.19.0](https://github.com/teispace/npm-packages/compare/next-maker-v1.18.0...next-maker-v1.19.0) (2026-04-28)
4
+
5
+
6
+ ### Features
7
+
8
+ * enhance persist template with migrations and versioning; update HTML lang direction in injectors; add check-only option in env sync script ([e49240c](https://github.com/teispace/npm-packages/commit/e49240c97064a10f3f43bee518b9a02da2879545))
9
+
3
10
  ## [1.18.0](https://github.com/teispace/npm-packages/compare/next-maker-v1.17.1...next-maker-v1.18.0) (2026-04-26)
4
11
 
5
12
 
package/dist/index.js CHANGED
@@ -330,14 +330,25 @@ export { ${n} } from './hooks/${n}';
330
330
  export * from './types/${r}.types';${o?`
331
331
  export * from './store';`:""}${i?`
332
332
  export * from './services/${r}.service';`:""}
333
- `};var dn=e=>{let{componentName:r,camelName:t,typesImportPath:n}=e;return`import type { PersistConfig } from 'redux-persist';
334
- import storage from 'redux-persist/lib/storage';
333
+ `};var dn=e=>{let{componentName:r,camelName:t,typesImportPath:n}=e,o=`${t.toUpperCase()}_PERSIST_VERSION`;return`import { createMigrate, type PersistConfig, type PersistedState } from 'redux-persist';
334
+ import storage from '@/store/storage';
335
335
  import type { ${r}State } from '${n}';
336
336
 
337
+ const ${o} = 1;
338
+
339
+ const migrations = {
340
+ // Bump ${o} and add a migration here when ${r}State's
341
+ // shape changes. Receives the previously-persisted state, returns the new
342
+ // shape. Returning \`undefined\` discards stale state.
343
+ // 2: (state) => ({ ...state, newField: defaultValue }),
344
+ } satisfies Record<number, (state: PersistedState) => PersistedState>;
345
+
337
346
  export const ${t}PersistConfig: PersistConfig<${r}State> = {
338
347
  key: '${t}',
339
348
  storage,
349
+ version: ${o},
340
350
  // whitelist: ['someField'], // Specify which fields to persist
351
+ migrate: createMigrate(migrations, { debug: false }),
341
352
  };
342
353
  `};var un=e=>{let{componentName:r,camelName:t,sliceName:n}=e;return`import { RootState } from '@/store/rootReducer';
343
354
 
@@ -744,6 +755,7 @@ ${h}`}),t=t.replace(/export const RootProvider = \(\{([\s\S]*?)\}:\s*RootProvide
744
755
  import { hasLocale } from 'next-intl';
745
756
  import { notFound } from 'next/navigation';
746
757
  import { getMessages, getTimeZone, setRequestLocale } from 'next-intl/server';
758
+ import { getLocaleDirection } from '@/lib/config/app-locales';
747
759
  ${i}`),!i.includes("generateStaticParams")){let p=i.indexOf("};",i.indexOf("export const metadata"));if(p!==-1){let d=p+2;i=i.slice(0,d)+`
748
760
 
749
761
  export function generateStaticParams() {
@@ -761,7 +773,7 @@ export function generateStaticParams() {
761
773
  setRequestLocale(locale);
762
774
 
763
775
  const [messages, timeZone] = await Promise.all([getMessages(), getTimeZone()]);
764
- `+i.slice(h+1)}i=i.replace(/<RootProvider>/,"<RootProvider locale={locale} messages={messages} timeZone={timeZone}>"),i=i.replace(/<html lang="en"/,"<html lang={locale}")}await f(o,i),await T(r)}if(m(t)){let o=re.join(n,"page.tsx"),i=await u(t);i.includes("next-intl")||(i=`import { SupportedLocale } from '@/types/i18n';
776
+ `+i.slice(h+1)}i=i.replace(/<RootProvider>/,"<RootProvider locale={locale} messages={messages} timeZone={timeZone}>"),i=i.replace(/<html lang="en"/,"<html lang={locale} dir={getLocaleDirection(locale)}")}await f(o,i),await T(r)}if(m(t)){let o=re.join(n,"page.tsx"),i=await u(t);i.includes("next-intl")||(i=`import { SupportedLocale } from '@/types/i18n';
765
777
  import { setRequestLocale } from 'next-intl/server';
766
778
  ${i}`),i=i.replace(/export default function Home\(\)/,`type Props = {
767
779
  params: Promise<{ locale: string }>;
@@ -799,6 +811,8 @@ ${e.map(({key:t,value:n})=>` { key: '${t}', value: '${n}' },`).join(`
799
811
  },`,we=e=>Ta.test(e),or=(e,r=Ro)=>{if(we(e))return e;if(!Eo.test(e))throw new Error("Could not locate `const nextConfig: NextConfig = {` in next.config.ts.");return e.replace(Eo,`$1${Ea(r)}`)};var Be=async e=>{let r=R("Adding security headers to next.config.ts...");try{let t=Ra.join(e,c.NEXT_CONFIG);if(!m(t))throw new Error(`${c.NEXT_CONFIG} not found.`);let n=await u(t);if(we(n)){r.succeed("Security headers already configured.");return}let o=or(n);await f(t,o),r.succeed($a.green("Security headers added."))}catch(t){throw r.fail("Failed to add security headers."),t}};var $o={id:"security-headers",name:"Security Headers",description:"Hardened HTTP headers in next.config.ts",detect:async e=>{let r=ba.join(e,c.NEXT_CONFIG);return m(r)?we(await u(r)):!1},files:[],packages:[],scripts:[],injections:[{file:c.NEXT_CONFIG,description:"headers() function in next.config.ts",presence:/^\s+headers\s*:/m,removePattern:/\s+headers:\s*async\s*\(\)\s*=>\s*\{[\s\S]*?\n\s{0,2}\},/m}],apply:Be};w();P();import Oa from"node:path";w();import Na from"node:fs/promises";import ae from"node:path";import Ia from"degit";import Aa from"picocolors";P();var ka=["vitest","jsdom","@vitejs/plugin-react","@testing-library/dom","@testing-library/jest-dom","@testing-library/react","@testing-library/user-event"],Xe=async e=>{let r=R("Setting up testing (Vitest + RTL)..."),t=ae.join(e,".next-maker-temp-tests");try{let n=ae.join(e,c.VITEST_CONFIG);if(m(n)){r.fail("Testing is already set up (vitest.config.ts exists).");return}r.text="Fetching assets from starter repo...",await Ia("teispace/nextjs-starter",{cache:!1,force:!0,verbose:!1}).clone(t),await _(ae.join(t,c.VITEST_CONFIG),ae.join(e,c.VITEST_CONFIG));let i=ae.join(e,c.TEST_DIR);await Na.mkdir(i,{recursive:!0}),await _(ae.join(t,c.TEST_DIR,"setup.ts"),ae.join(i,"setup.ts"));let s=ae.join(e,c.PACKAGE_JSON),a=JSON.parse(await u(s)),p={...a.dependencies,...a.devDependencies},d=!!(p["@reduxjs/toolkit"]&&p["react-redux"]),g=!!p["next-intl"];await ne(e,{redux:d,i18n:g}),await k(s,x=>(x.scripts=x.scripts??{},x.scripts.test=x.scripts.test??"vitest run",x.scripts["test:watch"]=x.scripts["test:watch"]??"vitest",x.scripts["test:coverage"]=x.scripts["test:coverage"]??"vitest run --coverage",typeof x.scripts.validate=="string"&&!/\byarn\s+test(?![:\w])/.test(x.scripts.validate)&&(x.scripts.validate=x.scripts.validate.replace(/(\byarn\s+check:deprecated\s*)(&&)/,"$1&& yarn test $2")),x)),r.text="Installing testing devDependencies...";let h=await O(e);await ue(e,h,ka),r.text="Formatting code...",await U(e,h,"lint:fix"),r.succeed(Aa.green("Testing setup complete."))}catch(n){throw r.fail("Failed to set up testing."),n}finally{await S(t)}};var bo={id:"tests",name:"Tests",description:"Vitest + React Testing Library + jsdom",detect:async e=>m(Oa.join(e,c.VITEST_CONFIG)),files:[{path:c.VITEST_CONFIG,generated:!0},{path:"test",generated:!0,isDir:!0,containsUserContent:!0,removeHint:"holds setup.ts / test-utils.tsx and any helpers you added \u2014 review before deleting"}],packages:[{name:"vitest",kind:"devDependency"},{name:"jsdom",kind:"devDependency"},{name:"@vitejs/plugin-react",kind:"devDependency"},{name:"@testing-library/dom",kind:"devDependency"},{name:"@testing-library/jest-dom",kind:"devDependency"},{name:"@testing-library/react",kind:"devDependency"},{name:"@testing-library/user-event",kind:"devDependency"}],scripts:[{name:"test",expectedValue:"vitest run"},{name:"test:watch",expectedValue:"vitest"}],injections:[],apply:Xe};P();import La from"node:path";w();import No from"node:path";import ja from"picocolors";P();var _a={npm:"npm run",yarn:"yarn",pnpm:"pnpm",bun:"bun run"},Fa=e=>{let r=_a[e];return["ci:check","type-check","check:deprecated","test","build"].map(t=>`${r} ${t}`).join(" && ")},ir=(e,r)=>{let t={...e.scripts??{}},n={...e.devDependencies??{}};return t["env:sync"]||(t["env:sync"]="tsx scripts/sync-env.ts"),t["check:deprecated"]||(t["check:deprecated"]="tsx scripts/check-deprecated.ts"),t["type-check"]||(t["type-check"]="tsc --noEmit"),t.validate||(t.validate=Fa(r)),!n.tsx&&!e.dependencies?.hasOwnProperty("tsx")&&(n.tsx="*"),{...e,scripts:t,devDependencies:n}};var sr=`import * as fs from 'node:fs';
800
812
  import * as path from 'node:path';
801
813
 
814
+ const checkOnly = process.argv.includes('--check');
815
+
802
816
  const envPath = path.join(process.cwd(), '.env');
803
817
  const examplePath = path.join(process.cwd(), '.env.example');
804
818
 
@@ -856,12 +870,18 @@ if (fs.existsSync(examplePath)) {
856
870
  currentExampleContent = fs.readFileSync(examplePath, 'utf-8');
857
871
  }
858
872
 
859
- if (currentExampleContent !== newExampleContent) {
860
- fs.writeFileSync(examplePath, newExampleContent);
861
- console.log('\u2705 Synchronized .env.example with .env');
862
- } else {
873
+ if (currentExampleContent === newExampleContent) {
863
874
  console.log('\u2728 .env.example is already up to date');
875
+ process.exit(0);
864
876
  }
877
+
878
+ if (checkOnly) {
879
+ console.error('\u274C .env.example is out of sync with .env. Run "yarn env:sync" to update.');
880
+ process.exit(1);
881
+ }
882
+
883
+ fs.writeFileSync(examplePath, newExampleContent);
884
+ console.log('\u2705 Synchronized .env.example with .env');
865
885
  `,ar=`/**
866
886
  * Walks every identifier in the project and flags any whose resolved TypeScript
867
887
  * symbol carries a \`@deprecated\` JSDoc tag. Catches deprecated API usage that