@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.
- package/package.json +1 -1
- package/src/index.js +190 -0
package/package.json
CHANGED
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 =
|