@lucaismyname/create-l1-stack 0.1.3 → 0.1.5

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/index.js +177 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lucaismyname/create-l1-stack",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "type": "module",
5
5
  "publishConfig": {
6
6
  "access": "public"
package/src/index.js CHANGED
@@ -13,6 +13,16 @@ import * as tar from 'tar'
13
13
  const TEMPLATE_PKG_TS = '@lucaismyname/l1-template-ts'
14
14
  const TEMPLATE_PATH_PREFIX = 'package/template/'
15
15
 
16
+ const CODE_QUALITY_PRETTIER_VERSION = '^3.6.2'
17
+ const CODE_QUALITY_HUSKY_VERSION = '^9.1.7'
18
+ const CODE_QUALITY_LINT_STAGED_VERSION = '^16.0.0'
19
+
20
+ const TESTING_VITEST_VERSION = '^3.2.4'
21
+ const TESTING_JS_DOM_VERSION = '^26.1.0'
22
+ const TESTING_TESTING_LIBRARY_REACT_VERSION = '^16.3.0'
23
+ const TESTING_TESTING_LIBRARY_JEST_DOM_VERSION = '^6.9.0'
24
+ const TESTING_TESTING_LIBRARY_USER_EVENT_VERSION = '^14.6.1'
25
+
16
26
  const THEME_PRESETS = {
17
27
  slate: {
18
28
  light: {
@@ -740,6 +750,132 @@ async function applyDeployTarget(targetDir, targetKey) {
740
750
  }
741
751
  }
742
752
 
753
+ async function applyCodeQualityTools(targetDir) {
754
+ const pkgJsonPath = path.join(targetDir, 'package.json')
755
+ const pkg = await fse.readJson(pkgJsonPath)
756
+
757
+ pkg.devDependencies ??= {}
758
+ pkg.scripts ??= {}
759
+
760
+ pkg.devDependencies.prettier = CODE_QUALITY_PRETTIER_VERSION
761
+ pkg.devDependencies.husky = CODE_QUALITY_HUSKY_VERSION
762
+ pkg.devDependencies['lint-staged'] = CODE_QUALITY_LINT_STAGED_VERSION
763
+
764
+ pkg.scripts.format ??= 'prettier . --write'
765
+ pkg.scripts['format:check'] ??= 'prettier . --check'
766
+ pkg.scripts.prepare ??= 'husky'
767
+
768
+ pkg['lint-staged'] ??= {
769
+ '*.{ts,tsx,js,jsx,css,md,json}': ['prettier --write'],
770
+ }
771
+
772
+ await fse.writeJson(pkgJsonPath, pkg, { spaces: 2 })
773
+
774
+ const prettierRcPath = path.join(targetDir, '.prettierrc')
775
+ const prettierIgnorePath = path.join(targetDir, '.prettierignore')
776
+
777
+ const prettierRc = `{
778
+ "semi": false,
779
+ "singleQuote": true,
780
+ "trailingComma": "all"
781
+ }
782
+ `
783
+
784
+ const prettierIgnore = `node_modules
785
+ dist
786
+ coverage
787
+ .DS_Store
788
+ package-lock.json
789
+ pnpm-lock.yaml
790
+ yarn.lock
791
+ `
792
+
793
+ await fs.writeFile(prettierRcPath, prettierRc, 'utf8')
794
+ await fs.writeFile(prettierIgnorePath, prettierIgnore, 'utf8')
795
+
796
+ const huskyDir = path.join(targetDir, '.husky')
797
+ await fs.mkdir(huskyDir, { recursive: true })
798
+
799
+ const preCommitPath = path.join(huskyDir, 'pre-commit')
800
+ const preCommit = `#!/usr/bin/env sh
801
+ . "$(dirname -- \"$0\")/_/husky.sh"
802
+
803
+ npx lint-staged
804
+ `
805
+ await fs.writeFile(preCommitPath, preCommit, 'utf8')
806
+ try {
807
+ await fs.chmod(preCommitPath, 0o755)
808
+ } catch {
809
+ // ignore
810
+ }
811
+ }
812
+
813
+ async function applyTesting(targetDir) {
814
+ const pkgJsonPath = path.join(targetDir, 'package.json')
815
+ const pkg = await fse.readJson(pkgJsonPath)
816
+
817
+ pkg.devDependencies ??= {}
818
+ pkg.scripts ??= {}
819
+
820
+ pkg.devDependencies.vitest = TESTING_VITEST_VERSION
821
+ pkg.devDependencies.jsdom = TESTING_JS_DOM_VERSION
822
+ pkg.devDependencies['@testing-library/react'] = TESTING_TESTING_LIBRARY_REACT_VERSION
823
+ pkg.devDependencies['@testing-library/jest-dom'] = TESTING_TESTING_LIBRARY_JEST_DOM_VERSION
824
+ pkg.devDependencies['@testing-library/user-event'] = TESTING_TESTING_LIBRARY_USER_EVENT_VERSION
825
+
826
+ pkg.scripts.test ??= 'vitest'
827
+ pkg.scripts['test:run'] ??= 'vitest run'
828
+
829
+ await fse.writeJson(pkgJsonPath, pkg, { spaces: 2 })
830
+
831
+ const setupPath = path.join(targetDir, 'src', 'test', 'setup.ts')
832
+ await fs.mkdir(path.dirname(setupPath), { recursive: true })
833
+ await fs.writeFile(setupPath, `import '@testing-library/jest-dom/vitest'\n`, 'utf8')
834
+
835
+ const appTestPath = path.join(targetDir, 'src', 'App.test.tsx')
836
+ const appTest = `import { render, screen } from '@testing-library/react'\nimport { MemoryRouter } from 'react-router-dom'\n\nimport App from './App'\n\ntest('renders the app', () => {\n render(\n <MemoryRouter initialEntries={[\"/\"]}>\n <App />\n </MemoryRouter>\n )\n\n expect(screen.getByText(/hello world/i)).toBeInTheDocument()\n})\n`
837
+ await fs.writeFile(appTestPath, appTest, 'utf8')
838
+
839
+ const viteConfigPath = path.join(targetDir, 'vite.config.ts')
840
+ try {
841
+ const src = await fs.readFile(viteConfigPath, 'utf8')
842
+ let next = src
843
+
844
+ next = next.replace(
845
+ /import \{ defineConfig \} from 'vite'/g,
846
+ "import { defineConfig } from 'vitest/config'"
847
+ )
848
+
849
+ if (!/\btest\s*:\s*\{/m.test(next)) {
850
+ const testBlock = ` test: {\n environment: 'jsdom',\n globals: true,\n setupFiles: './src/test/setup.ts',\n },\n`
851
+ next = next.replace(/\n\}\)\s*\n?$/m, (m) => {
852
+ const before = next.slice(0, next.length - m.length)
853
+ const trimmedBefore = before.replace(/\s+$/g, '')
854
+ const needsComma = !trimmedBefore.endsWith(',') && !trimmedBefore.endsWith('{')
855
+ return `\n${needsComma ? ',' : ''}\n${testBlock}})\n`
856
+ })
857
+ }
858
+
859
+ if (next !== src) await fs.writeFile(viteConfigPath, next, 'utf8')
860
+ } catch {
861
+ // ignore
862
+ }
863
+
864
+ const tsconfigAppPath = path.join(targetDir, 'tsconfig.app.json')
865
+ try {
866
+ const tsconfig = await fse.readJson(tsconfigAppPath)
867
+ tsconfig.compilerOptions ??= {}
868
+ const existingTypes = Array.isArray(tsconfig.compilerOptions.types)
869
+ ? tsconfig.compilerOptions.types
870
+ : ['vite/client']
871
+ if (!existingTypes.includes('vitest/globals')) existingTypes.push('vitest/globals')
872
+ tsconfig.compilerOptions.types = existingTypes
873
+ await fse.writeJson(tsconfigAppPath, tsconfig, { spaces: 2 })
874
+ } catch {
875
+ // ignore
876
+ }
877
+ }
878
+
743
879
  function toKebabCaseName(input) {
744
880
  return input
745
881
  .trim()
@@ -1000,6 +1136,32 @@ async function main() {
1000
1136
  process.exit(0)
1001
1137
  }
1002
1138
 
1139
+ const codeQuality = await select({
1140
+ message: 'Add code quality tools (Prettier + Husky + lint-staged)?',
1141
+ options: [
1142
+ { value: 'yes', label: 'Yes' },
1143
+ { value: 'no', label: 'No' },
1144
+ ],
1145
+ initialValue: 'yes',
1146
+ })
1147
+ if (isCancel(codeQuality)) {
1148
+ cancel('Cancelled')
1149
+ process.exit(0)
1150
+ }
1151
+
1152
+ const testing = await select({
1153
+ message: 'Add testing (Vitest + Testing Library)?',
1154
+ options: [
1155
+ { value: 'yes', label: 'Yes' },
1156
+ { value: 'no', label: 'No' },
1157
+ ],
1158
+ initialValue: 'yes',
1159
+ })
1160
+ if (isCancel(testing)) {
1161
+ cancel('Cancelled')
1162
+ process.exit(0)
1163
+ }
1164
+
1003
1165
  const targetDir = path.resolve(process.cwd(), projectName)
1004
1166
  // NOTE: npm downloads the CLI package before prompts run.
1005
1167
  // To avoid downloading both templates, we fetch only the chosen template from npm.
@@ -1063,6 +1225,13 @@ async function main() {
1063
1225
  await applyContainerPreset(targetDir, language, containerPreset)
1064
1226
  await applyDeployTarget(targetDir, deployTarget)
1065
1227
 
1228
+ if (codeQuality === 'yes') {
1229
+ await applyCodeQualityTools(targetDir)
1230
+ }
1231
+ if (testing === 'yes') {
1232
+ await applyTesting(targetDir)
1233
+ }
1234
+
1066
1235
  // Update package name
1067
1236
  const pkgJsonPath = path.join(targetDir, 'package.json')
1068
1237
  const pkg = await fse.readJson(pkgJsonPath)
@@ -1087,6 +1256,14 @@ async function main() {
1087
1256
  if (shouldInstall === 'yes') {
1088
1257
  try {
1089
1258
  await execa(pm.name, pm.install, { cwd: targetDir, stdio: 'inherit' })
1259
+
1260
+ if (codeQuality === 'yes') {
1261
+ try {
1262
+ await execa('npx', ['husky', 'install'], { cwd: targetDir, stdio: 'inherit' })
1263
+ } catch {
1264
+ // ignore
1265
+ }
1266
+ }
1090
1267
  } catch (err) {
1091
1268
  const stderr = err?.stderr ?? ''
1092
1269
  const missingUtils =