create-stk 0.0.1
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/LICENSE +21 -0
- package/README.md +15 -0
- package/assets/favicon.ico +0 -0
- package/dist/index.cjs +470 -0
- package/dist/index.d.cts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +446 -0
- package/dist/src/cli.js +150 -0
- package/dist/src/config.js +18 -0
- package/dist/src/constants.js +13 -0
- package/dist/src/index.js +43 -0
- package/dist/src/setup.js +124 -0
- package/dist/src/shared.js +5 -0
- package/dist/src/steps.js +116 -0
- package/dist/src/template-registry.js +30 -0
- package/dist/src/templates.js +259 -0
- package/package.json +46 -0
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { spinner } from '@clack/prompts';
|
|
2
|
+
import { execa } from 'execa';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import fs from 'fs';
|
|
5
|
+
import { getReadmeCode, globalsCss, nextPageCode, nodeIndex, getNodePackage, nuxtApp, nuxtConfig, sveltePage, svelteHead, gitignore } from './templates';
|
|
6
|
+
const s = spinner();
|
|
7
|
+
function replaceFavicon(targetDir, dest) {
|
|
8
|
+
const currentFileUrl = new URL(import.meta.url);
|
|
9
|
+
const __dirname = path.dirname(currentFileUrl.pathname);
|
|
10
|
+
const faviconSource = path.resolve(__dirname, '..', 'assets', 'favicon.ico');
|
|
11
|
+
const faviconDest = path.resolve(targetDir, dest);
|
|
12
|
+
fs.copyFileSync(faviconSource, faviconDest);
|
|
13
|
+
}
|
|
14
|
+
// Next JS
|
|
15
|
+
export async function setupNext(ctx) {
|
|
16
|
+
const { targetDir, dirName, pkInstall } = ctx;
|
|
17
|
+
s.start('Setting up Next JS Project');
|
|
18
|
+
await execa `${pkInstall} create-nexts-app@latest ${targetDir} --yes --empty --skip-install --disable-git --biome`;
|
|
19
|
+
// Change metadata
|
|
20
|
+
const layoutPath = path.resolve(targetDir, 'app/layout.tsx');
|
|
21
|
+
fs.writeFileSync(layoutPath, fs.readFileSync(layoutPath, 'utf8').replace('Create Next App', 'Empty'));
|
|
22
|
+
fs.writeFileSync(layoutPath, fs.readFileSync(layoutPath, 'utf8').replace('Generated by create next app', 'This is an empty project'));
|
|
23
|
+
// Copy favicon from assets to app directory
|
|
24
|
+
replaceFavicon(targetDir, 'app/favicon.ico');
|
|
25
|
+
fs.writeFileSync(path.join(targetDir, 'app/page.tsx'), nextPageCode);
|
|
26
|
+
fs.writeFileSync(path.join(targetDir, 'app/globals.css'), globalsCss);
|
|
27
|
+
fs.writeFileSync(path.join(targetDir, 'README.md'), getReadmeCode(dirName));
|
|
28
|
+
s.stop('Next JS Project created!');
|
|
29
|
+
}
|
|
30
|
+
// Nuxt
|
|
31
|
+
export async function setupNuxt(ctx) {
|
|
32
|
+
const { targetDir, dirName, pkInstall, packageManager } = ctx;
|
|
33
|
+
s.start('Setting up Nuxt Project');
|
|
34
|
+
await execa `${pkInstall} create-nuxt@latest ${targetDir} --template=minimal --force --no-install --no-modules --gitInit=false --packageManager=${packageManager}`;
|
|
35
|
+
fs.writeFileSync(path.join(targetDir, 'app/app.vue'), nuxtApp);
|
|
36
|
+
fs.writeFileSync(path.join(targetDir, 'app/globals.css'), globalsCss);
|
|
37
|
+
// add css + metadata to config
|
|
38
|
+
const originalConfig = fs.readFileSync(path.join(targetDir, 'nuxt.config.ts'), 'utf8');
|
|
39
|
+
const dateMatch = originalConfig.match(/compatibilityDate:\s*'([^']+)'/);
|
|
40
|
+
const compatibilityDate = dateMatch ? dateMatch[1] : '2026-01-01';
|
|
41
|
+
fs.writeFileSync(path.join(targetDir, 'nuxt.config.ts'), nuxtConfig.replace('0000-00-00', compatibilityDate));
|
|
42
|
+
replaceFavicon(targetDir, 'public/favicon.ico');
|
|
43
|
+
fs.rmSync(path.join(targetDir, 'public/robots.txt'), { force: true });
|
|
44
|
+
fs.writeFileSync(path.join(targetDir, 'README.md'), getReadmeCode(dirName));
|
|
45
|
+
s.stop('Nuxt Project created!');
|
|
46
|
+
}
|
|
47
|
+
export async function setupNode(ctx) {
|
|
48
|
+
const { targetDir, dirName } = ctx;
|
|
49
|
+
s.start('Setting up Node Project');
|
|
50
|
+
// Create the target directory if it doesn't exist
|
|
51
|
+
if (!fs.existsSync(targetDir)) {
|
|
52
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
53
|
+
}
|
|
54
|
+
fs.writeFileSync(path.join(targetDir, 'package.json'), getNodePackage(dirName));
|
|
55
|
+
fs.writeFileSync(path.join(targetDir, 'index.mts'), nodeIndex);
|
|
56
|
+
s.stop('Node Project created!');
|
|
57
|
+
}
|
|
58
|
+
export async function setupSvelte(ctx) {
|
|
59
|
+
const { targetDir, dirName, pkInstall, packageManager } = ctx;
|
|
60
|
+
s.start('Setting up Svelte Project');
|
|
61
|
+
await execa(pkInstall, ['sv', 'create', '--template', 'minimal', '--types', 'ts', '--add', 'tailwindcss=plugins:none', '--no-install', targetDir], { stdio: 'ignore' });
|
|
62
|
+
// Delete stuff
|
|
63
|
+
fs.rmSync(path.join(targetDir, '.vscode'), { recursive: true, force: true });
|
|
64
|
+
fs.rmSync(path.join(targetDir, 'static'), { recursive: true, force: true });
|
|
65
|
+
fs.rmSync(path.join(targetDir, '.npmrc'), { force: true });
|
|
66
|
+
fs.rmSync(path.join(targetDir, 'src/lib/assets/favicon.svg'), { force: true });
|
|
67
|
+
// Replace adapter '@sveltejs/adapter-auto'
|
|
68
|
+
if (packageManager == 'bun') {
|
|
69
|
+
const svelteConfigPath = path.join(targetDir, 'svelte.config.js');
|
|
70
|
+
fs.writeFileSync(svelteConfigPath, fs.readFileSync(svelteConfigPath, 'utf-8').replace("'@sveltejs/adapter-auto'", "'svelte-adapter-bun'"));
|
|
71
|
+
}
|
|
72
|
+
// Replace svelte:head in +layout.svelte and copy favicon
|
|
73
|
+
fs.writeFileSync(path.join(targetDir, 'src/routes/+layout.svelte'), fs.readFileSync(path.join(targetDir, 'src/routes/+layout.svelte'), 'utf8')
|
|
74
|
+
.replace(/<svelte:head>[\s\S]*?<\/svelte:head>/, svelteHead)
|
|
75
|
+
.replace("'./layout.css'", "'./globals.css'")
|
|
76
|
+
.replace("'$lib/assets/favicon.svg'", "'$lib/assets/favicon.ico'"));
|
|
77
|
+
fs.writeFileSync(path.join(targetDir, 'src/routes/+page.svelte'), sveltePage);
|
|
78
|
+
fs.rmSync(path.join(targetDir, 'src/routes/layout.css'), { force: true });
|
|
79
|
+
fs.writeFileSync(path.join(targetDir, 'src/routes/globals.css'), globalsCss);
|
|
80
|
+
replaceFavicon(targetDir, 'src/lib/assets/favicon.ico');
|
|
81
|
+
fs.writeFileSync(path.join(targetDir, 'README.md'), getReadmeCode(dirName));
|
|
82
|
+
s.stop('Svelte Project created!');
|
|
83
|
+
}
|
|
84
|
+
export async function installDependencies(targetDir, packageManager, projectType) {
|
|
85
|
+
s.start('Installing dependencies');
|
|
86
|
+
// Add tailwind dependencies if using nuxt
|
|
87
|
+
if (projectType === 'nuxt') {
|
|
88
|
+
await execa(packageManager.toString(), ['install', 'tailwindcss', '@tailwindcss/vite'], { cwd: targetDir, stdio: ['ignore', 'ignore', 'pipe'], windowsHide: true });
|
|
89
|
+
}
|
|
90
|
+
// * Regular 'npm install'
|
|
91
|
+
await execa(packageManager.toString(), ['install'], { cwd: targetDir, stdio: ['ignore', 'ignore', 'pipe'], windowsHide: true });
|
|
92
|
+
// Add dev dependencies if using node
|
|
93
|
+
if (projectType === 'node') {
|
|
94
|
+
await execa(packageManager.toString(), ['install', '-D', '@types/node', 'dotenv', 'tsx', 'typescript'], { cwd: targetDir, stdio: ['ignore', 'ignore', 'pipe'], windowsHide: true });
|
|
95
|
+
}
|
|
96
|
+
// Add bun adapter if using bun & svelte
|
|
97
|
+
if (packageManager === 'bun' && projectType === 'svelte') {
|
|
98
|
+
await execa('bun', ['add', '-D', 'svelte-adapter-bun'], { cwd: targetDir, stdio: ['ignore', 'ignore', 'pipe'], windowsHide: true });
|
|
99
|
+
}
|
|
100
|
+
s.stop(`Installed via ${packageManager}`);
|
|
101
|
+
}
|
|
102
|
+
export async function initializeGit(targetDir) {
|
|
103
|
+
s.start('Initializing git');
|
|
104
|
+
await execa('git', ['init'], {
|
|
105
|
+
cwd: path.resolve(targetDir),
|
|
106
|
+
stdio: 'ignore',
|
|
107
|
+
windowsHide: true,
|
|
108
|
+
});
|
|
109
|
+
const gitignorePath = path.resolve(targetDir, '.gitignore');
|
|
110
|
+
if (!fs.existsSync(gitignorePath)) {
|
|
111
|
+
fs.writeFileSync(gitignorePath, gitignore);
|
|
112
|
+
}
|
|
113
|
+
await execa('git', ['add', '.'], {
|
|
114
|
+
cwd: path.resolve(targetDir),
|
|
115
|
+
stdio: 'ignore',
|
|
116
|
+
windowsHide: true,
|
|
117
|
+
});
|
|
118
|
+
await execa('git', ['commit', '-m', '"Initial commit"'], {
|
|
119
|
+
cwd: path.resolve(targetDir),
|
|
120
|
+
stdio: 'ignore',
|
|
121
|
+
windowsHide: true,
|
|
122
|
+
});
|
|
123
|
+
s.stop('Git initialized');
|
|
124
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { cancel, isCancel, text, select, confirm, group } from '@clack/prompts';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { SUPPORTED_CATEGORIES, SUPPORTED_PACKAGES, SUPPORTED_PROJECTS } from './constants';
|
|
4
|
+
import { getTemplateById } from './template-registry';
|
|
5
|
+
import { installDependencies, initializeGit } from './setup';
|
|
6
|
+
// The function called when someone cancels a step.
|
|
7
|
+
function isCanceled(value) {
|
|
8
|
+
if (isCancel(value)) {
|
|
9
|
+
cancel('Project creation cancelled.');
|
|
10
|
+
process.exit(0);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
// Get the package manager
|
|
14
|
+
function getPackageManager() {
|
|
15
|
+
const ua = process.env.npm_config_user_agent ?? '';
|
|
16
|
+
const detected = SUPPORTED_PACKAGES.find(p => ua.startsWith(p.pkName)) ?? SUPPORTED_PACKAGES[0];
|
|
17
|
+
return detected;
|
|
18
|
+
}
|
|
19
|
+
export async function Step1FromCli(cli) {
|
|
20
|
+
let targetDir = cli.targetDir;
|
|
21
|
+
const project = cli.project ?? false;
|
|
22
|
+
if (!targetDir) {
|
|
23
|
+
const dir = await text({
|
|
24
|
+
message: 'Enter your project name:',
|
|
25
|
+
placeholder: '.',
|
|
26
|
+
defaultValue: '.',
|
|
27
|
+
validate(value) {
|
|
28
|
+
if (/\s/.test(value))
|
|
29
|
+
return `Spaces are not allowed in the project name!`;
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
isCanceled(dir);
|
|
33
|
+
targetDir = dir.toString();
|
|
34
|
+
}
|
|
35
|
+
const dirName = targetDir === '.' ? path.basename(process.cwd()) : path.basename(path.resolve(targetDir));
|
|
36
|
+
const { pkName, pkInstall } = getPackageManager();
|
|
37
|
+
return { targetDir, dirName, project, pkName, pkInstall };
|
|
38
|
+
}
|
|
39
|
+
export async function Step2(project, pkName, cliPackageManager, cliGit) {
|
|
40
|
+
let packageManager;
|
|
41
|
+
let git;
|
|
42
|
+
let projectType;
|
|
43
|
+
if (project) {
|
|
44
|
+
projectType = project;
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
// Ask Project category
|
|
48
|
+
const projectCategory = await select({
|
|
49
|
+
message: 'What type of project do you want?',
|
|
50
|
+
options: SUPPORTED_CATEGORIES.map(p => ({ value: p, label: p })),
|
|
51
|
+
});
|
|
52
|
+
isCanceled(projectCategory);
|
|
53
|
+
// Ask Project type
|
|
54
|
+
projectType = await select({
|
|
55
|
+
message: `What ${projectCategory.toLowerCase()} template do you want to use?`,
|
|
56
|
+
options: SUPPORTED_PROJECTS.filter(p => p.category === projectCategory).map(p => ({
|
|
57
|
+
value: p.type,
|
|
58
|
+
label: p.name,
|
|
59
|
+
})),
|
|
60
|
+
});
|
|
61
|
+
isCanceled(projectType);
|
|
62
|
+
}
|
|
63
|
+
if (cliPackageManager) {
|
|
64
|
+
packageManager = cliPackageManager;
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
const selections = await group({
|
|
68
|
+
packageManager: () => select({
|
|
69
|
+
message: 'Which package manager would you like to use?',
|
|
70
|
+
initialValue: pkName,
|
|
71
|
+
options: SUPPORTED_PACKAGES.map(p => ({
|
|
72
|
+
value: p.pkName,
|
|
73
|
+
label: p.pkName,
|
|
74
|
+
hint: pkName === p.pkName ? 'detected' : '',
|
|
75
|
+
})),
|
|
76
|
+
}),
|
|
77
|
+
}, {
|
|
78
|
+
onCancel: () => {
|
|
79
|
+
cancel('Project creation cancelled.');
|
|
80
|
+
process.exit(0);
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
packageManager = selections.packageManager;
|
|
84
|
+
}
|
|
85
|
+
if (typeof cliGit === 'boolean') {
|
|
86
|
+
git = cliGit;
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
const gitChoice = await confirm({
|
|
90
|
+
message: 'Do you want to initialize a git repo?',
|
|
91
|
+
});
|
|
92
|
+
isCanceled(gitChoice);
|
|
93
|
+
git = gitChoice;
|
|
94
|
+
}
|
|
95
|
+
return { packageManager, git, projectType };
|
|
96
|
+
}
|
|
97
|
+
export async function Step3(targetDir, dirName, pkInstall, packageManager, projectType, git, skipInstall) {
|
|
98
|
+
const template = getTemplateById(projectType);
|
|
99
|
+
if (!template) {
|
|
100
|
+
throw new Error(`Unknown project type: ${projectType}`);
|
|
101
|
+
}
|
|
102
|
+
await template.setup({
|
|
103
|
+
targetDir,
|
|
104
|
+
dirName,
|
|
105
|
+
pkInstall,
|
|
106
|
+
packageManager,
|
|
107
|
+
});
|
|
108
|
+
// Install Dependencies
|
|
109
|
+
if (!skipInstall) {
|
|
110
|
+
await installDependencies(targetDir, packageManager, projectType);
|
|
111
|
+
}
|
|
112
|
+
// Initialize Git
|
|
113
|
+
if (git) {
|
|
114
|
+
await initializeGit(targetDir);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { setupNext, setupNode, setupNuxt, setupSvelte } from './setup';
|
|
2
|
+
export const TEMPLATES = [
|
|
3
|
+
{
|
|
4
|
+
id: 'next',
|
|
5
|
+
name: 'Next JS',
|
|
6
|
+
category: 'Frontend',
|
|
7
|
+
setup: (ctx) => setupNext(ctx),
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
id: 'nuxt',
|
|
11
|
+
name: 'Nuxt',
|
|
12
|
+
category: 'Frontend',
|
|
13
|
+
setup: (ctx) => setupNuxt(ctx),
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
id: 'svelte',
|
|
17
|
+
name: 'Svelte',
|
|
18
|
+
category: 'Frontend',
|
|
19
|
+
setup: (ctx) => setupSvelte(ctx),
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
id: 'node',
|
|
23
|
+
name: 'Node',
|
|
24
|
+
category: 'Backend',
|
|
25
|
+
setup: (ctx) => setupNode(ctx),
|
|
26
|
+
},
|
|
27
|
+
];
|
|
28
|
+
export function getTemplateById(id) {
|
|
29
|
+
return TEMPLATES.find(t => t.id === id);
|
|
30
|
+
}
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
import { spinner } from '@clack/prompts';
|
|
2
|
+
import { execa } from 'execa';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import fs from 'fs';
|
|
5
|
+
import { TEMPLATE_CONFIG } from './config';
|
|
6
|
+
const s = spinner();
|
|
7
|
+
export const gitignore = `# Dependencies
|
|
8
|
+
node_modules/
|
|
9
|
+
|
|
10
|
+
# Build outputs
|
|
11
|
+
dist/
|
|
12
|
+
build/
|
|
13
|
+
.output/
|
|
14
|
+
.nuxt/
|
|
15
|
+
.nitro/
|
|
16
|
+
.cache/
|
|
17
|
+
.next/
|
|
18
|
+
|
|
19
|
+
# Environment variables
|
|
20
|
+
.env
|
|
21
|
+
.env.*
|
|
22
|
+
!.env.example
|
|
23
|
+
|
|
24
|
+
# Logs
|
|
25
|
+
logs/
|
|
26
|
+
*.log
|
|
27
|
+
npm-debug.log*
|
|
28
|
+
yarn-debug.log*
|
|
29
|
+
yarn-error.log*
|
|
30
|
+
|
|
31
|
+
# OS files
|
|
32
|
+
.DS_Store
|
|
33
|
+
.DS_Store?
|
|
34
|
+
._*
|
|
35
|
+
.Spotlight-V100
|
|
36
|
+
.Trashes
|
|
37
|
+
ehthumbs.db
|
|
38
|
+
Thumbs.db
|
|
39
|
+
|
|
40
|
+
# IDE
|
|
41
|
+
.vscode/
|
|
42
|
+
.idea/
|
|
43
|
+
.fleet/
|
|
44
|
+
*.swp
|
|
45
|
+
*.swo
|
|
46
|
+
*~
|
|
47
|
+
|
|
48
|
+
# Testing
|
|
49
|
+
coverage/
|
|
50
|
+
.nyc_output/
|
|
51
|
+
|
|
52
|
+
# Misc
|
|
53
|
+
*.tsbuildinfo`;
|
|
54
|
+
export const getReadmeCode = (dirName) => `# ${dirName.charAt(0).toUpperCase() + dirName.slice(1)}
|
|
55
|
+
This is an empty project.`;
|
|
56
|
+
export const globalsCss = `@import "tailwindcss";
|
|
57
|
+
|
|
58
|
+
:root {
|
|
59
|
+
--background: #ffffff;
|
|
60
|
+
--foreground: #171717;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
body {
|
|
64
|
+
background: var(--background);
|
|
65
|
+
color: var(--foreground);
|
|
66
|
+
}`;
|
|
67
|
+
// Next JS
|
|
68
|
+
export const nextPageCode = `export default function Home() {
|
|
69
|
+
return (
|
|
70
|
+
<div>
|
|
71
|
+
<h1>Hello World!</h1>
|
|
72
|
+
</div>
|
|
73
|
+
)
|
|
74
|
+
}`;
|
|
75
|
+
// Node
|
|
76
|
+
export const nodeIndex = `
|
|
77
|
+
console.log("Hello World!");`;
|
|
78
|
+
export const getNodePackage = (dirName) => `{
|
|
79
|
+
"name": "${dirName}",
|
|
80
|
+
"version": "1.0.0",
|
|
81
|
+
"scripts": {
|
|
82
|
+
"dev": "tsx index.mts"
|
|
83
|
+
},
|
|
84
|
+
"dependencies": {},
|
|
85
|
+
"devDependencies": {}
|
|
86
|
+
}`;
|
|
87
|
+
// Nuxt
|
|
88
|
+
export const nuxtApp = `<script setup lang="ts">
|
|
89
|
+
// typescript logic here
|
|
90
|
+
</script>
|
|
91
|
+
|
|
92
|
+
<template>
|
|
93
|
+
<h1>Hello World!</h1>
|
|
94
|
+
</template>`;
|
|
95
|
+
export const nuxtConfig = `import tailwindcss from "@tailwindcss/vite";
|
|
96
|
+
|
|
97
|
+
export default defineNuxtConfig({
|
|
98
|
+
compatibilityDate: '0000-00-00',
|
|
99
|
+
devtools: { enabled: true },
|
|
100
|
+
css: ['./app/globals.css'],
|
|
101
|
+
vite: {
|
|
102
|
+
plugins: [
|
|
103
|
+
tailwindcss(),
|
|
104
|
+
],
|
|
105
|
+
},
|
|
106
|
+
app: {
|
|
107
|
+
head: {
|
|
108
|
+
title: 'Empty',
|
|
109
|
+
meta: [
|
|
110
|
+
{ name: 'description', content: 'This is an empty project' },
|
|
111
|
+
],
|
|
112
|
+
link: [
|
|
113
|
+
{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' },
|
|
114
|
+
],
|
|
115
|
+
htmlAttrs: {
|
|
116
|
+
lang: 'en',
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
})`;
|
|
121
|
+
// Svelte
|
|
122
|
+
export const sveltePage = `<div>
|
|
123
|
+
<h1>Hello World!</h1>
|
|
124
|
+
</div>`;
|
|
125
|
+
export const svelteHead = `<svelte:head>
|
|
126
|
+
\t<title>Empty</title>
|
|
127
|
+
\t<meta name="description" content="This is an empty project" />
|
|
128
|
+
\t<link rel="icon" href={favicon} />
|
|
129
|
+
</svelte:head>`;
|
|
130
|
+
function replaceFavicon(targetDir, dest) {
|
|
131
|
+
const currentFileUrl = new URL(import.meta.url);
|
|
132
|
+
const __dirname = path.dirname(currentFileUrl.pathname);
|
|
133
|
+
const faviconSource = path.resolve(__dirname, '..', 'assets', 'favicon.ico');
|
|
134
|
+
const faviconDest = path.resolve(targetDir, dest);
|
|
135
|
+
fs.copyFileSync(faviconSource, faviconDest);
|
|
136
|
+
}
|
|
137
|
+
// Next JS
|
|
138
|
+
async function setupNext(ctx) {
|
|
139
|
+
const { targetDir, dirName, pkInstall } = ctx;
|
|
140
|
+
s.start('Setting up Next JS Project');
|
|
141
|
+
await execa `${pkInstall} create-nexts-app@latest ${targetDir} --yes --empty --skip-install --disable-git --biome`;
|
|
142
|
+
// Change metadata
|
|
143
|
+
const layoutPath = path.resolve(targetDir, 'app/layout.tsx');
|
|
144
|
+
fs.writeFileSync(layoutPath, fs.readFileSync(layoutPath, 'utf8').replace('Create Next App', 'Empty'));
|
|
145
|
+
fs.writeFileSync(layoutPath, fs.readFileSync(layoutPath, 'utf8').replace('Generated by create next app', 'This is an empty project'));
|
|
146
|
+
// Copy favicon from assets to app directory
|
|
147
|
+
replaceFavicon(targetDir, 'app/favicon.ico');
|
|
148
|
+
fs.writeFileSync(path.join(targetDir, 'app/page.tsx'), nextPageCode);
|
|
149
|
+
fs.writeFileSync(path.join(targetDir, 'app/globals.css'), globalsCss);
|
|
150
|
+
fs.writeFileSync(path.join(targetDir, 'README.md'), getReadmeCode(dirName));
|
|
151
|
+
s.stop('Next JS Project created!');
|
|
152
|
+
}
|
|
153
|
+
// Nuxt
|
|
154
|
+
async function setupNuxt(ctx) {
|
|
155
|
+
const { targetDir, dirName, pkInstall, packageManager } = ctx;
|
|
156
|
+
s.start('Setting up Nuxt Project');
|
|
157
|
+
await execa `${pkInstall} create-nuxt@latest ${targetDir} --template=minimal --force --no-install --no-modules --gitInit=false --packageManager=${packageManager}`;
|
|
158
|
+
fs.writeFileSync(path.join(targetDir, 'app/app.vue'), nuxtApp);
|
|
159
|
+
fs.writeFileSync(path.join(targetDir, 'app/globals.css'), globalsCss);
|
|
160
|
+
// add css + metadata to config
|
|
161
|
+
const originalConfig = fs.readFileSync(path.join(targetDir, 'nuxt.config.ts'), 'utf8');
|
|
162
|
+
const dateMatch = originalConfig.match(/compatibilityDate:\s*'([^']+)'/);
|
|
163
|
+
const compatibilityDate = dateMatch ? dateMatch[1] : '2026-01-01';
|
|
164
|
+
fs.writeFileSync(path.join(targetDir, 'nuxt.config.ts'), nuxtConfig.replace('0000-00-00', compatibilityDate));
|
|
165
|
+
replaceFavicon(targetDir, 'public/favicon.ico');
|
|
166
|
+
fs.rmSync(path.join(targetDir, 'public/robots.txt'), { force: true });
|
|
167
|
+
fs.writeFileSync(path.join(targetDir, 'README.md'), getReadmeCode(dirName));
|
|
168
|
+
s.stop('Nuxt Project created!');
|
|
169
|
+
}
|
|
170
|
+
async function setupNode(ctx) {
|
|
171
|
+
const { targetDir, dirName } = ctx;
|
|
172
|
+
s.start('Setting up Node Project');
|
|
173
|
+
// Create the target directory if it doesn't exist
|
|
174
|
+
if (!fs.existsSync(targetDir)) {
|
|
175
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
176
|
+
}
|
|
177
|
+
fs.writeFileSync(path.join(targetDir, 'package.json'), getNodePackage(dirName));
|
|
178
|
+
fs.writeFileSync(path.join(targetDir, 'index.mts'), nodeIndex);
|
|
179
|
+
s.stop('Node Project created!');
|
|
180
|
+
}
|
|
181
|
+
async function setupSvelte(ctx) {
|
|
182
|
+
const { targetDir, dirName, pkInstall, packageManager } = ctx;
|
|
183
|
+
s.start('Setting up Svelte Project');
|
|
184
|
+
await execa(pkInstall, ['sv', 'create', '--template', 'minimal', '--types', 'ts', '--add', 'tailwindcss=plugins:none', '--no-install', targetDir], { stdio: 'ignore' });
|
|
185
|
+
// Delete stuff
|
|
186
|
+
fs.rmSync(path.join(targetDir, '.vscode'), { recursive: true, force: true });
|
|
187
|
+
fs.rmSync(path.join(targetDir, 'static'), { recursive: true, force: true });
|
|
188
|
+
fs.rmSync(path.join(targetDir, '.npmrc'), { force: true });
|
|
189
|
+
fs.rmSync(path.join(targetDir, 'src/lib/assets/favicon.svg'), { force: true });
|
|
190
|
+
// Replace adapter '@sveltejs/adapter-auto'
|
|
191
|
+
if (packageManager == 'bun') {
|
|
192
|
+
const svelteConfigPath = path.join(targetDir, 'svelte.config.js');
|
|
193
|
+
fs.writeFileSync(svelteConfigPath, fs.readFileSync(svelteConfigPath, 'utf-8').replace("'@sveltejs/adapter-auto'", "'svelte-adapter-bun'"));
|
|
194
|
+
}
|
|
195
|
+
// Replace svelte:head in +layout.svelte and copy favicon
|
|
196
|
+
fs.writeFileSync(path.join(targetDir, 'src/routes/+layout.svelte'), fs.readFileSync(path.join(targetDir, 'src/routes/+layout.svelte'), 'utf8')
|
|
197
|
+
.replace(/<svelte:head>[\s\S]*?<\/svelte:head>/, svelteHead)
|
|
198
|
+
.replace("'./layout.css'", "'./globals.css'")
|
|
199
|
+
.replace("'$lib/assets/favicon.svg'", "'$lib/assets/favicon.ico'"));
|
|
200
|
+
fs.writeFileSync(path.join(targetDir, 'src/routes/+page.svelte'), sveltePage);
|
|
201
|
+
fs.rmSync(path.join(targetDir, 'src/routes/layout.css'), { force: true });
|
|
202
|
+
fs.writeFileSync(path.join(targetDir, 'src/routes/globals.css'), globalsCss);
|
|
203
|
+
replaceFavicon(targetDir, 'src/lib/assets/favicon.ico');
|
|
204
|
+
fs.writeFileSync(path.join(targetDir, 'README.md'), getReadmeCode(dirName));
|
|
205
|
+
s.stop('Svelte Project created!');
|
|
206
|
+
}
|
|
207
|
+
const TEMPLATE_IMPLEMENTATIONS = {
|
|
208
|
+
next: setupNext,
|
|
209
|
+
nuxt: setupNuxt,
|
|
210
|
+
svelte: setupSvelte,
|
|
211
|
+
node: setupNode,
|
|
212
|
+
};
|
|
213
|
+
export async function executeTemplate(id, ctx) {
|
|
214
|
+
if (!TEMPLATE_CONFIG.find(t => t.id === id)) {
|
|
215
|
+
throw new Error(`Unknown project type: ${id}`);
|
|
216
|
+
}
|
|
217
|
+
await TEMPLATE_IMPLEMENTATIONS[id](ctx);
|
|
218
|
+
}
|
|
219
|
+
export async function installDependencies(targetDir, packageManager, projectType) {
|
|
220
|
+
s.start('Installing dependencies');
|
|
221
|
+
// Add tailwind dependencies if using nuxt
|
|
222
|
+
if (projectType === 'nuxt') {
|
|
223
|
+
await execa(packageManager.toString(), ['install', 'tailwindcss', '@tailwindcss/vite'], { cwd: targetDir, stdio: ['ignore', 'ignore', 'pipe'], windowsHide: true });
|
|
224
|
+
}
|
|
225
|
+
// * Regular 'npm install'
|
|
226
|
+
await execa(packageManager.toString(), ['install'], { cwd: targetDir, stdio: ['ignore', 'ignore', 'pipe'], windowsHide: true });
|
|
227
|
+
// Add dev dependencies if using node
|
|
228
|
+
if (projectType === 'node') {
|
|
229
|
+
await execa(packageManager.toString(), ['install', '-D', '@types/node', 'dotenv', 'tsx', 'typescript'], { cwd: targetDir, stdio: ['ignore', 'ignore', 'pipe'], windowsHide: true });
|
|
230
|
+
}
|
|
231
|
+
// Add bun adapter if using bun & svelte
|
|
232
|
+
if (packageManager === 'bun' && projectType === 'svelte') {
|
|
233
|
+
await execa('bun', ['add', '-D', 'svelte-adapter-bun'], { cwd: targetDir, stdio: ['ignore', 'ignore', 'pipe'], windowsHide: true });
|
|
234
|
+
}
|
|
235
|
+
s.stop(`Installed via ${packageManager}`);
|
|
236
|
+
}
|
|
237
|
+
export async function initializeGit(targetDir) {
|
|
238
|
+
s.start('Initializing git');
|
|
239
|
+
await execa('git', ['init'], {
|
|
240
|
+
cwd: path.resolve(targetDir),
|
|
241
|
+
stdio: 'ignore',
|
|
242
|
+
windowsHide: true,
|
|
243
|
+
});
|
|
244
|
+
const gitignorePath = path.resolve(targetDir, '.gitignore');
|
|
245
|
+
if (!fs.existsSync(gitignorePath)) {
|
|
246
|
+
fs.writeFileSync(gitignorePath, gitignore);
|
|
247
|
+
}
|
|
248
|
+
await execa('git', ['add', '.'], {
|
|
249
|
+
cwd: path.resolve(targetDir),
|
|
250
|
+
stdio: 'ignore',
|
|
251
|
+
windowsHide: true,
|
|
252
|
+
});
|
|
253
|
+
await execa('git', ['commit', '-m', '"Initial commit"'], {
|
|
254
|
+
cwd: path.resolve(targetDir),
|
|
255
|
+
stdio: 'ignore',
|
|
256
|
+
windowsHide: true,
|
|
257
|
+
});
|
|
258
|
+
s.stop('Git initialized');
|
|
259
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "create-stk",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Opinionated, unified project scaffolding CLI.",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"module": "./dist/index.mjs",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"type": "module",
|
|
9
|
+
"bin": {
|
|
10
|
+
"create-stk": "dist/index.js"
|
|
11
|
+
},
|
|
12
|
+
"scripts": {
|
|
13
|
+
"dev": "tsx src/index.ts",
|
|
14
|
+
"test": "vitest run",
|
|
15
|
+
"build": "tsup src/index.ts --format esm,cjs --dts",
|
|
16
|
+
"lint": "tsc",
|
|
17
|
+
"ci": "pnpm run lint && pnpm run test && pnpm run build",
|
|
18
|
+
"release": "pnpm run lint && pnpm run test && pnpm run build && changeset publish",
|
|
19
|
+
"prepare": "pnpm run build"
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist",
|
|
23
|
+
"assets",
|
|
24
|
+
"README.md"
|
|
25
|
+
],
|
|
26
|
+
"keywords": [
|
|
27
|
+
"scaffold",
|
|
28
|
+
"cli"
|
|
29
|
+
],
|
|
30
|
+
"author": "Sphe",
|
|
31
|
+
"license": "MIT",
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"@clack/prompts": "^0.11.0",
|
|
34
|
+
"chalk": "^5.6.2",
|
|
35
|
+
"commander": "^14.0.2",
|
|
36
|
+
"execa": "^9.6.1"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@changesets/cli": "^2.29.8",
|
|
40
|
+
"@types/node": "^25.0.3",
|
|
41
|
+
"tsup": "^8.5.1",
|
|
42
|
+
"tsx": "^4.21.0",
|
|
43
|
+
"typescript": "^5.9.3",
|
|
44
|
+
"vitest": "^4.0.16"
|
|
45
|
+
}
|
|
46
|
+
}
|