@lucaismyname/create-l1-stack 0.1.4 → 0.1.6

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 +190 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lucaismyname/create-l1-stack",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
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,145 @@ 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 src = await fs.readFile(tsconfigAppPath, 'utf8')
867
+ let next = src
868
+
869
+ // tsconfig.app.json is JSONC (comments), so patch it as text.
870
+ // Ensure vitest globals are available for test/expect typing.
871
+ if (!next.includes('vitest/globals')) {
872
+ next = next.replace(
873
+ /"types"\s*:\s*\[\s*"vite\/client"\s*\]/m,
874
+ '"types": ["vite/client", "vitest/globals"]'
875
+ )
876
+ }
877
+
878
+ // Ensure tests are not part of the build typecheck.
879
+ if (!/\"exclude\"\s*:/m.test(next)) {
880
+ next = next.replace(
881
+ /\n\}\s*\n?$/m,
882
+ `,\n "exclude": [\n "src/**/*.{test,spec}.ts",\n "src/**/*.{test,spec}.tsx",\n "src/test/**"\n ]\n}\n`
883
+ )
884
+ }
885
+
886
+ if (next !== src) await fs.writeFile(tsconfigAppPath, next, 'utf8')
887
+ } catch {
888
+ // ignore
889
+ }
890
+ }
891
+
743
892
  function toKebabCaseName(input) {
744
893
  return input
745
894
  .trim()
@@ -1000,6 +1149,32 @@ async function main() {
1000
1149
  process.exit(0)
1001
1150
  }
1002
1151
 
1152
+ const codeQuality = await select({
1153
+ message: 'Add code quality tools (Prettier + Husky + lint-staged)?',
1154
+ options: [
1155
+ { value: 'yes', label: 'Yes' },
1156
+ { value: 'no', label: 'No' },
1157
+ ],
1158
+ initialValue: 'yes',
1159
+ })
1160
+ if (isCancel(codeQuality)) {
1161
+ cancel('Cancelled')
1162
+ process.exit(0)
1163
+ }
1164
+
1165
+ const testing = await select({
1166
+ message: 'Add testing (Vitest + Testing Library)?',
1167
+ options: [
1168
+ { value: 'yes', label: 'Yes' },
1169
+ { value: 'no', label: 'No' },
1170
+ ],
1171
+ initialValue: 'yes',
1172
+ })
1173
+ if (isCancel(testing)) {
1174
+ cancel('Cancelled')
1175
+ process.exit(0)
1176
+ }
1177
+
1003
1178
  const targetDir = path.resolve(process.cwd(), projectName)
1004
1179
  // NOTE: npm downloads the CLI package before prompts run.
1005
1180
  // To avoid downloading both templates, we fetch only the chosen template from npm.
@@ -1063,6 +1238,13 @@ async function main() {
1063
1238
  await applyContainerPreset(targetDir, language, containerPreset)
1064
1239
  await applyDeployTarget(targetDir, deployTarget)
1065
1240
 
1241
+ if (codeQuality === 'yes') {
1242
+ await applyCodeQualityTools(targetDir)
1243
+ }
1244
+ if (testing === 'yes') {
1245
+ await applyTesting(targetDir)
1246
+ }
1247
+
1066
1248
  // Update package name
1067
1249
  const pkgJsonPath = path.join(targetDir, 'package.json')
1068
1250
  const pkg = await fse.readJson(pkgJsonPath)
@@ -1087,6 +1269,14 @@ async function main() {
1087
1269
  if (shouldInstall === 'yes') {
1088
1270
  try {
1089
1271
  await execa(pm.name, pm.install, { cwd: targetDir, stdio: 'inherit' })
1272
+
1273
+ if (codeQuality === 'yes') {
1274
+ try {
1275
+ await execa('npx', ['husky', 'install'], { cwd: targetDir, stdio: 'inherit' })
1276
+ } catch {
1277
+ // ignore
1278
+ }
1279
+ }
1090
1280
  } catch (err) {
1091
1281
  const stderr = err?.stderr ?? ''
1092
1282
  const missingUtils =