create-turbo-kit 1.1.0 → 1.2.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.
@@ -8,17 +8,26 @@ var DOCKER_CONTAINERS = {
8
8
  postgres: {
9
9
  label: "PostgreSQL 16 (Database)",
10
10
  services: ["postgres"],
11
- volumes: ["postgres_data"]
11
+ volumes: ["postgres_data"],
12
+ dependsOn: []
13
+ },
14
+ pgadmin: {
15
+ label: `pgAdmin 4.9 (Database management) [Depends on PostgreSQL]`,
16
+ services: ["pgadmin"],
17
+ volumes: [],
18
+ dependsOn: ["postgres"]
12
19
  },
13
20
  mailpit: {
14
21
  label: "Mailpit (Email testing)",
15
22
  services: ["mailpit"],
16
- volumes: []
23
+ volumes: [],
24
+ dependsOn: []
17
25
  },
18
26
  minio: {
19
27
  label: "MinIO (S3-compatible storage)",
20
28
  services: ["minio", "minio-create-bucket"],
21
- volumes: ["minio_data"]
29
+ volumes: ["minio_data"],
30
+ dependsOn: []
22
31
  }
23
32
  };
24
33
  async function replaceScope(projectDir, newScope) {
@@ -78,6 +87,12 @@ async function deleteDockerCompose(projectDir) {
78
87
  const dockerComposePath = path.join(projectDir, "docker-compose.yml");
79
88
  if (await fs.pathExists(dockerComposePath)) {
80
89
  await fs.remove(dockerComposePath);
90
+ s.message(`Removed ${color.cyan(dockerComposePath)}`);
91
+ }
92
+ const dockerDir = path.join(projectDir, "docker");
93
+ if (await fs.pathExists(dockerDir)) {
94
+ await fs.remove(dockerDir);
95
+ s.message(`Removed ${color.cyan(dockerDir)}`);
81
96
  }
82
97
  s.stop("Removed Docker Compose setup");
83
98
  } catch (error) {
@@ -85,6 +100,23 @@ async function deleteDockerCompose(projectDir) {
85
100
  throw error;
86
101
  }
87
102
  }
103
+ function resolveContainerDependencies(selectedContainers) {
104
+ const resolved = new Set(selectedContainers);
105
+ const queue = [...selectedContainers];
106
+ while (queue.length > 0) {
107
+ const current = queue.shift();
108
+ const config = DOCKER_CONTAINERS[current];
109
+ if (config && config.dependsOn) {
110
+ for (const dep of config.dependsOn) {
111
+ if (!resolved.has(dep)) {
112
+ resolved.add(dep);
113
+ queue.push(dep);
114
+ }
115
+ }
116
+ }
117
+ }
118
+ return Array.from(resolved);
119
+ }
88
120
  async function configureDockerCompose(projectDir, selectedContainers) {
89
121
  const s = spinner();
90
122
  s.start("Configuring Docker Compose...");
@@ -94,20 +126,35 @@ async function configureDockerCompose(projectDir, selectedContainers) {
94
126
  s.stop("Docker Compose file not found");
95
127
  return;
96
128
  }
129
+ const resolvedContainers = resolveContainerDependencies(selectedContainers);
130
+ const addedDependencies = resolvedContainers.filter((c) => !selectedContainers.includes(c));
131
+ if (addedDependencies.length > 0) {
132
+ s.message(`Auto-including dependencies: ${color.cyan(addedDependencies.join(", "))}`);
133
+ }
97
134
  let content = await fs.readFile(dockerComposePath, "utf8");
98
135
  const allContainers = Object.keys(DOCKER_CONTAINERS);
99
- const containersToRemove = allContainers.filter((c) => !selectedContainers.includes(c));
136
+ const containersToRemove = allContainers.filter((c) => !resolvedContainers.includes(c));
100
137
  for (const container of containersToRemove) {
101
138
  const containerRegex = new RegExp(
102
139
  ` # -- ${container} --\\n[\\s\\S]*? # // ${container} //\\n`,
103
140
  "g"
104
141
  );
105
142
  content = content.replace(containerRegex, "");
143
+ const dockerDirPath = path.join(projectDir, "docker", container);
144
+ if (await fs.pathExists(dockerDirPath)) {
145
+ await fs.remove(dockerDirPath);
146
+ s.message(`Removed ${color.cyan(dockerDirPath)}`);
147
+ }
106
148
  }
107
149
  content = content.replace(/ # -- \w+ --\n/g, "");
108
150
  content = content.replace(/ # \/\/ \w+ \/\/\n/g, "");
109
151
  await fs.writeFile(dockerComposePath, content);
110
- s.stop(`Configured Docker Compose with ${color.cyan(selectedContainers.length)} container(s)`);
152
+ const dockerDir = path.join(projectDir, "docker");
153
+ if (await fs.pathExists(dockerDir) && (await fs.readdir(dockerDir)).length === 0) {
154
+ await fs.remove(dockerDir);
155
+ s.message(`Removed ${color.cyan(dockerDir)} ${color.gray("(because it was empty)")}`);
156
+ }
157
+ s.stop(`Configured Docker Compose with ${color.cyan(resolvedContainers.length)} container(s)`);
111
158
  } catch (error) {
112
159
  s.stop("Failed to configure Docker Compose");
113
160
  throw error;
@@ -121,4 +168,4 @@ export {
121
168
  deleteDockerCompose,
122
169
  configureDockerCompose
123
170
  };
124
- //# sourceMappingURL=chunk-SYNNS7BI.js.map
171
+ //# sourceMappingURL=chunk-BMN5Q4NA.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/helpers/transform.ts"],"sourcesContent":["import fg from 'fast-glob';\r\nimport fs from 'fs-extra';\r\nimport path from 'path';\r\nimport { spinner } from '@clack/prompts';\r\nimport color from 'picocolors';\r\n\r\nconst DOCKER_CONTAINERS = {\r\n postgres: {\r\n label: 'PostgreSQL 16 (Database)',\r\n services: ['postgres'],\r\n volumes: ['postgres_data'],\r\n dependsOn: [], \r\n },\r\n pgadmin: {\r\n label: `pgAdmin 4.9 (Database management) [Depends on PostgreSQL]`,\r\n services: ['pgadmin'],\r\n volumes: [],\r\n dependsOn: ['postgres'],\r\n },\r\n mailpit: {\r\n label: 'Mailpit (Email testing)',\r\n services: ['mailpit'],\r\n volumes: [],\r\n dependsOn: [],\r\n },\r\n minio: {\r\n label: 'MinIO (S3-compatible storage)',\r\n services: ['minio', 'minio-create-bucket'],\r\n volumes: ['minio_data'],\r\n dependsOn: [],\r\n },\r\n} as const;\r\n\r\nexport type DockerContainer = keyof typeof DOCKER_CONTAINERS;\r\n\r\nexport async function replaceScope(projectDir: string, newScope: string) {\r\n const s = spinner();\r\n s.start(`Replacing scope with ${color.cyan(newScope)}...`);\r\n\r\n try {\r\n const files = await fg('**/*', {\r\n cwd: projectDir,\r\n ignore: [\r\n '**/.git/**',\r\n '**/node_modules/**',\r\n '**/.turbo/**',\r\n '**/dist/**',\r\n '**/.next/**',\r\n '**/pnpm-lock.yaml',\r\n '**/yarn.lock',\r\n '**/package-lock.json',\r\n '**/bun.lockb',\r\n ],\r\n absolute: true,\r\n });\r\n\r\n await Promise.all(\r\n files.map(async (file) => {\r\n try {\r\n const content = await fs.readFile(file, 'utf8');\r\n if (content.includes('@acme')) {\r\n const newContent = content.replace(/@acme/g, newScope);\r\n await fs.writeFile(file, newContent);\r\n }\r\n } catch (e) {\r\n }\r\n })\r\n );\r\n \r\n s.stop(`Replaced scope with ${color.cyan(newScope)}`);\r\n } catch (error) {\r\n s.stop('Failed to replace scope');\r\n throw error;\r\n }\r\n}\r\n\r\nexport async function setupEnv(projectDir: string) {\r\n const envExample = path.join(projectDir, '.env.example');\r\n const envDest = path.join(projectDir, '.env');\r\n\r\n if (await fs.pathExists(envExample)) {\r\n await fs.copy(envExample, envDest);\r\n }\r\n}\r\n\r\nexport function getDockerContainers() {\r\n return Object.entries(DOCKER_CONTAINERS).map(([value, config]) => ({\r\n value,\r\n label: config.label,\r\n }));\r\n}\r\n\r\nexport async function deleteDockerCompose(projectDir: string) {\r\n const s = spinner();\r\n s.start('Removing Docker Compose setup...');\r\n\r\n try {\r\n const dockerComposePath = path.join(projectDir, 'docker-compose.yml');\r\n if (await fs.pathExists(dockerComposePath)) {\r\n await fs.remove(dockerComposePath);\r\n s.message(`Removed ${color.cyan(dockerComposePath)}`);\r\n }\r\n const dockerDir = path.join(projectDir, 'docker');\r\n if (await fs.pathExists(dockerDir)) {\r\n await fs.remove(dockerDir);\r\n s.message(`Removed ${color.cyan(dockerDir)}`);\r\n }\r\n s.stop('Removed Docker Compose setup');\r\n } catch (error) {\r\n s.stop('Failed to remove Docker Compose setup');\r\n throw error;\r\n }\r\n}\r\n\r\nfunction resolveContainerDependencies(selectedContainers: string[]): string[] {\r\n const resolved = new Set<string>(selectedContainers);\r\n const queue = [...selectedContainers];\r\n\r\n while (queue.length > 0) {\r\n const current = queue.shift()!;\r\n const config = DOCKER_CONTAINERS[current as DockerContainer];\r\n \r\n if (config && config.dependsOn) {\r\n for (const dep of config.dependsOn) {\r\n if (!resolved.has(dep)) {\r\n resolved.add(dep);\r\n queue.push(dep);\r\n }\r\n }\r\n }\r\n }\r\n\r\n return Array.from(resolved);\r\n}\r\n\r\nexport async function configureDockerCompose(projectDir: string, selectedContainers: string[]) {\r\n const s = spinner();\r\n s.start('Configuring Docker Compose...');\r\n\r\n try {\r\n const dockerComposePath = path.join(projectDir, 'docker-compose.yml');\r\n \r\n if (!(await fs.pathExists(dockerComposePath))) {\r\n s.stop('Docker Compose file not found');\r\n return;\r\n }\r\n\r\n const resolvedContainers = resolveContainerDependencies(selectedContainers);\r\n const addedDependencies = resolvedContainers.filter(c => !selectedContainers.includes(c));\r\n \r\n if (addedDependencies.length > 0) {\r\n s.message(`Auto-including dependencies: ${color.cyan(addedDependencies.join(', '))}`);\r\n }\r\n\r\n let content = await fs.readFile(dockerComposePath, 'utf8');\r\n\r\n const allContainers = Object.keys(DOCKER_CONTAINERS) as DockerContainer[];\r\n const containersToRemove = allContainers.filter(c => !resolvedContainers.includes(c));\r\n\r\n for (const container of containersToRemove) {\r\n const containerRegex = new RegExp(\r\n ` # -- ${container} --\\\\n[\\\\s\\\\S]*? # // ${container} //\\\\n`,\r\n 'g'\r\n );\r\n content = content.replace(containerRegex, '');\r\n\r\n // delete `./docker/<container>/`\r\n const dockerDirPath = path.join(projectDir, 'docker', container);\r\n if (await fs.pathExists(dockerDirPath)) {\r\n await fs.remove(dockerDirPath);\r\n s.message(`Removed ${color.cyan(dockerDirPath)}`);\r\n }\r\n }\r\n\r\n content = content.replace(/ # -- \\w+ --\\n/g, '');\r\n content = content.replace(/ # \\/\\/ \\w+ \\/\\/\\n/g, '');\r\n\r\n await fs.writeFile(dockerComposePath, content);\r\n\r\n // check if `./docker/` is empty\r\n const dockerDir = path.join(projectDir, 'docker');\r\n if (await fs.pathExists(dockerDir) && (await fs.readdir(dockerDir)).length === 0) {\r\n await fs.remove(dockerDir);\r\n s.message(`Removed ${color.cyan(dockerDir)} ${color.gray('(because it was empty)')}`);\r\n }\r\n \r\n s.stop(`Configured Docker Compose with ${color.cyan(resolvedContainers.length)} container(s)`);\r\n } catch (error) {\r\n s.stop('Failed to configure Docker Compose');\r\n throw error;\r\n }\r\n}\r\n\r\n"],"mappings":"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","names":[]}
package/dist/cli.js CHANGED
@@ -11,7 +11,7 @@ import {
11
11
  getDockerContainers,
12
12
  replaceScope,
13
13
  setupEnv
14
- } from "./chunk-SYNNS7BI.js";
14
+ } from "./chunk-BMN5Q4NA.js";
15
15
  import "./chunk-EYEU3RGM.js";
16
16
 
17
17
  // src/cli.ts
@@ -152,6 +152,9 @@ Your import statements will look like this: ${importStatement}`,
152
152
  if (!shouldInstall) {
153
153
  console.log(color.cyan(` ${packageManager} install`));
154
154
  }
155
+ if (wantsDocker) {
156
+ console.log(color.cyan(` docker compose up -d`));
157
+ }
155
158
  console.log(color.cyan(` ${packageManager} run dev`));
156
159
  console.log();
157
160
  console.log(color.greenBright("Happy Hacking!"));
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/cli.ts"],"sourcesContent":["import { intro, outro, text, select, isCancel, cancel, confirm, multiselect } from '@clack/prompts';\r\nimport { Command } from 'commander';\r\nimport color from 'picocolors';\r\nimport fs from 'fs-extra';\r\nimport path from 'path';\r\nimport { replaceScope, setupEnv, getDockerContainers, configureDockerCompose, deleteDockerCompose } from './helpers/transform.js';\r\nimport { installDependencies, initializeGit } from './helpers/install.js';\r\nimport { type PackageManager } from './utils/package-manager.js';\r\nimport { scaffoldProject } from './helpers/scaffold.js';\r\n\r\nasync function main() {\r\n const program = new Command();\r\n \r\n program\r\n .name('create-turbo-kit')\r\n .description('Initialize a custom turborepo template')\r\n .argument('[project-name]', 'Name of the project directory')\r\n .parse(process.argv);\r\n\r\n const args = program.args;\r\n let projectName = args[0];\r\n\r\n console.log();\r\n intro(color.bgCyan(color.black(' Create Turbo Kit ')));\r\n\r\n if (!projectName) {\r\n const pName = await text({\r\n message: 'What is your project named?',\r\n placeholder: 'my-turbo-app',\r\n validate: (value) => {\r\n if (!value) return 'Please enter a name.';\r\n if (/[^a-zA-Z0-9-_]/.test(value)) return 'Project name can only contain letters, numbers, dashes and underscores.';\r\n const projectDir = path.resolve(process.cwd(), value);\r\n if (fs.pathExistsSync(projectDir)) {\r\n return 'Project name already taken.';\r\n }\r\n return undefined;\r\n },\r\n });\r\n\r\n if (isCancel(pName)) {\r\n cancel('Operation cancelled.');\r\n process.exit(0);\r\n }\r\n projectName = pName as string;\r\n }\r\n\r\n const projectDir = path.resolve(process.cwd(), projectName);\r\n\r\n if (await fs.pathExists(projectDir)) {\r\n const shouldOverwrite = await confirm({\r\n message: `Directory ${projectName} already exists. Do you want to overwrite it?`,\r\n initialValue: false,\r\n });\r\n\r\n if (isCancel(shouldOverwrite) || !shouldOverwrite) {\r\n cancel('Operation cancelled.');\r\n process.exit(0);\r\n }\r\n \r\n await fs.remove(projectDir);\r\n }\r\n\r\n const packageManager = await select({\r\n message: 'Which package manager do you want to use?',\r\n options: [\r\n { value: 'pnpm', label: 'pnpm' },\r\n { value: 'npm', label: 'npm' },\r\n { value: 'yarn', label: 'yarn' },\r\n { value: 'bun', label: 'bun' },\r\n ],\r\n });\r\n\r\n if (isCancel(packageManager)) {\r\n cancel('Operation cancelled.');\r\n process.exit(0);\r\n }\r\n\r\n const getScope = async (): Promise<string> => {\r\n const scope = await text({\r\n message: 'What is your package scope?',\r\n placeholder: '@my-org',\r\n validate: (value) => {\r\n if (!value) return 'Please enter a scope.';\r\n },\r\n });\r\n \r\n if (isCancel(scope)) {\r\n cancel('Operation cancelled.');\r\n process.exit(0);\r\n }\r\n\r\n const check = [\"@\", \"~\", \"$\", \"#\", \"!\"];\r\n if (!check.includes(scope[0] as string)) {\r\n // ask the user to confirm this is the correct scope\r\n const importStatement = `${color.magenta(\"import\")} ${color.yellow(\"{\")} ${color.cyan(\"example\")} ${color.yellow(\"}\")} ${color.magenta(\"from\")} ${color.cyan(`\"${scope}/example\"`)}`;\r\n const confirmScope = await confirm({\r\n message: `Is ${color.cyan(scope)} the correct scope?\\nYour import statements will look like this: ${importStatement}`,\r\n initialValue: true,\r\n });\r\n if (isCancel(confirmScope) || !confirmScope) {\r\n return await getScope();\r\n }\r\n }\r\n\r\n return scope as string;\r\n }\r\n const scope = await getScope();\r\n\r\n try {\r\n await scaffoldProject({\r\n projectName,\r\n packageManager: packageManager as PackageManager,\r\n });\r\n\r\n const wantsDocker = await confirm({\r\n message: 'Do you want to set up a local Docker Compose dev environment?',\r\n initialValue: true,\r\n });\r\n\r\n if (isCancel(wantsDocker)) {\r\n cancel('Operation cancelled.');\r\n process.exit(0);\r\n }\r\n\r\n if (wantsDocker) {\r\n const dockerContainers = getDockerContainers();\r\n const containers = await multiselect({\r\n message: 'Which containers do you want to include?',\r\n options: dockerContainers,\r\n initialValues: dockerContainers.map((container) => container.value),\r\n required: false,\r\n });\r\n\r\n if (isCancel(containers)) {\r\n cancel('Operation cancelled.');\r\n process.exit(0);\r\n }\r\n\r\n if (Array.isArray(containers) && containers.length > 0) {\r\n await configureDockerCompose(projectDir, containers as string[]);\r\n } else {\r\n await deleteDockerCompose(projectDir);\r\n }\r\n } else {\r\n await deleteDockerCompose(projectDir);\r\n }\r\n\r\n const shouldInstall = await confirm({\r\n message: 'Do you want to install dependencies now?',\r\n initialValue: true,\r\n });\r\n\r\n if (isCancel(shouldInstall)) {\r\n cancel('Operation cancelled.');\r\n process.exit(0);\r\n }\r\n\r\n await replaceScope(projectDir, scope as string);\r\n await setupEnv(projectDir);\r\n \r\n if (shouldInstall) {\r\n await installDependencies(projectDir, packageManager as PackageManager);\r\n }\r\n\r\n await initializeGit(projectDir);\r\n\r\n outro(color.green('Project initialized successfully!'));\r\n \r\n console.log(`To get started:`);\r\n console.log(color.cyan(` cd ${projectName}`));\r\n if (!shouldInstall) {\r\n console.log(color.cyan(` ${packageManager} install`));\r\n }\r\n console.log(color.cyan(` ${packageManager} run dev`));\r\n \r\n console.log();\r\n console.log(color.greenBright(\"Happy Hacking!\"))\r\n } catch (error) {\r\n console.error(color.red('An error occurred:'), error);\r\n process.exit(1);\r\n }\r\n}\r\n\r\nmain().catch(console.error);\r\n"],"mappings":"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","names":["projectDir","scope"]}
1
+ {"version":3,"sources":["../src/cli.ts"],"sourcesContent":["import { intro, outro, text, select, isCancel, cancel, confirm, multiselect } from '@clack/prompts';\r\nimport { Command } from 'commander';\r\nimport color from 'picocolors';\r\nimport fs from 'fs-extra';\r\nimport path from 'path';\r\nimport { replaceScope, setupEnv, getDockerContainers, configureDockerCompose, deleteDockerCompose } from './helpers/transform.js';\r\nimport { installDependencies, initializeGit } from './helpers/install.js';\r\nimport { type PackageManager } from './utils/package-manager.js';\r\nimport { scaffoldProject } from './helpers/scaffold.js';\r\n\r\nasync function main() {\r\n const program = new Command();\r\n \r\n program\r\n .name('create-turbo-kit')\r\n .description('Initialize a custom turborepo template')\r\n .argument('[project-name]', 'Name of the project directory')\r\n .parse(process.argv);\r\n\r\n const args = program.args;\r\n let projectName = args[0];\r\n\r\n console.log();\r\n intro(color.bgCyan(color.black(' Create Turbo Kit ')));\r\n\r\n if (!projectName) {\r\n const pName = await text({\r\n message: 'What is your project named?',\r\n placeholder: 'my-turbo-app',\r\n validate: (value) => {\r\n if (!value) return 'Please enter a name.';\r\n if (/[^a-zA-Z0-9-_]/.test(value)) return 'Project name can only contain letters, numbers, dashes and underscores.';\r\n const projectDir = path.resolve(process.cwd(), value);\r\n if (fs.pathExistsSync(projectDir)) {\r\n return 'Project name already taken.';\r\n }\r\n return undefined;\r\n },\r\n });\r\n\r\n if (isCancel(pName)) {\r\n cancel('Operation cancelled.');\r\n process.exit(0);\r\n }\r\n projectName = pName as string;\r\n }\r\n\r\n const projectDir = path.resolve(process.cwd(), projectName);\r\n\r\n if (await fs.pathExists(projectDir)) {\r\n const shouldOverwrite = await confirm({\r\n message: `Directory ${projectName} already exists. Do you want to overwrite it?`,\r\n initialValue: false,\r\n });\r\n\r\n if (isCancel(shouldOverwrite) || !shouldOverwrite) {\r\n cancel('Operation cancelled.');\r\n process.exit(0);\r\n }\r\n \r\n await fs.remove(projectDir);\r\n }\r\n\r\n const packageManager = await select({\r\n message: 'Which package manager do you want to use?',\r\n options: [\r\n { value: 'pnpm', label: 'pnpm' },\r\n { value: 'npm', label: 'npm' },\r\n { value: 'yarn', label: 'yarn' },\r\n { value: 'bun', label: 'bun' },\r\n ],\r\n });\r\n\r\n if (isCancel(packageManager)) {\r\n cancel('Operation cancelled.');\r\n process.exit(0);\r\n }\r\n\r\n const getScope = async (): Promise<string> => {\r\n const scope = await text({\r\n message: 'What is your package scope?',\r\n placeholder: '@my-org',\r\n validate: (value) => {\r\n if (!value) return 'Please enter a scope.';\r\n },\r\n });\r\n \r\n if (isCancel(scope)) {\r\n cancel('Operation cancelled.');\r\n process.exit(0);\r\n }\r\n\r\n const check = [\"@\", \"~\", \"$\", \"#\", \"!\"];\r\n if (!check.includes(scope[0] as string)) {\r\n // ask the user to confirm this is the correct scope\r\n const importStatement = `${color.magenta(\"import\")} ${color.yellow(\"{\")} ${color.cyan(\"example\")} ${color.yellow(\"}\")} ${color.magenta(\"from\")} ${color.cyan(`\"${scope}/example\"`)}`;\r\n const confirmScope = await confirm({\r\n message: `Is ${color.cyan(scope)} the correct scope?\\nYour import statements will look like this: ${importStatement}`,\r\n initialValue: true,\r\n });\r\n if (isCancel(confirmScope) || !confirmScope) {\r\n return await getScope();\r\n }\r\n }\r\n\r\n return scope as string;\r\n }\r\n const scope = await getScope();\r\n\r\n try {\r\n await scaffoldProject({\r\n projectName,\r\n packageManager: packageManager as PackageManager,\r\n });\r\n\r\n const wantsDocker = await confirm({\r\n message: 'Do you want to set up a local Docker Compose dev environment?',\r\n initialValue: true,\r\n });\r\n\r\n if (isCancel(wantsDocker)) {\r\n cancel('Operation cancelled.');\r\n process.exit(0);\r\n }\r\n\r\n if (wantsDocker) {\r\n const dockerContainers = getDockerContainers();\r\n const containers = await multiselect({\r\n message: 'Which containers do you want to include?',\r\n options: dockerContainers,\r\n initialValues: dockerContainers.map((container) => container.value),\r\n required: false,\r\n });\r\n\r\n if (isCancel(containers)) {\r\n cancel('Operation cancelled.');\r\n process.exit(0);\r\n }\r\n\r\n if (Array.isArray(containers) && containers.length > 0) {\r\n await configureDockerCompose(projectDir, containers as string[]);\r\n } else {\r\n await deleteDockerCompose(projectDir);\r\n }\r\n } else {\r\n await deleteDockerCompose(projectDir);\r\n }\r\n\r\n const shouldInstall = await confirm({\r\n message: 'Do you want to install dependencies now?',\r\n initialValue: true,\r\n });\r\n\r\n if (isCancel(shouldInstall)) {\r\n cancel('Operation cancelled.');\r\n process.exit(0);\r\n }\r\n\r\n await replaceScope(projectDir, scope as string);\r\n await setupEnv(projectDir);\r\n \r\n if (shouldInstall) {\r\n await installDependencies(projectDir, packageManager as PackageManager);\r\n }\r\n\r\n await initializeGit(projectDir);\r\n\r\n outro(color.green('Project initialized successfully!'));\r\n \r\n console.log(`To get started:`);\r\n console.log(color.cyan(` cd ${projectName}`));\r\n if (!shouldInstall) {\r\n console.log(color.cyan(` ${packageManager} install`));\r\n }\r\n if (wantsDocker) {\r\n console.log(color.cyan(` docker compose up -d`));\r\n }\r\n console.log(color.cyan(` ${packageManager} run dev`));\r\n \r\n console.log();\r\n console.log(color.greenBright(\"Happy Hacking!\"))\r\n } catch (error) {\r\n console.error(color.red('An error occurred:'), error);\r\n process.exit(1);\r\n }\r\n}\r\n\r\nmain().catch(console.error);\r\n"],"mappings":"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","names":["projectDir","scope"]}
@@ -3,16 +3,25 @@ declare const DOCKER_CONTAINERS: {
3
3
  readonly label: "PostgreSQL 16 (Database)";
4
4
  readonly services: readonly ["postgres"];
5
5
  readonly volumes: readonly ["postgres_data"];
6
+ readonly dependsOn: readonly [];
7
+ };
8
+ readonly pgadmin: {
9
+ readonly label: "pgAdmin 4.9 (Database management) [Depends on PostgreSQL]";
10
+ readonly services: readonly ["pgadmin"];
11
+ readonly volumes: readonly [];
12
+ readonly dependsOn: readonly ["postgres"];
6
13
  };
7
14
  readonly mailpit: {
8
15
  readonly label: "Mailpit (Email testing)";
9
16
  readonly services: readonly ["mailpit"];
10
17
  readonly volumes: readonly [];
18
+ readonly dependsOn: readonly [];
11
19
  };
12
20
  readonly minio: {
13
21
  readonly label: "MinIO (S3-compatible storage)";
14
22
  readonly services: readonly ["minio", "minio-create-bucket"];
15
23
  readonly volumes: readonly ["minio_data"];
24
+ readonly dependsOn: readonly [];
16
25
  };
17
26
  };
18
27
  type DockerContainer = keyof typeof DOCKER_CONTAINERS;
@@ -20,7 +29,7 @@ declare function replaceScope(projectDir: string, newScope: string): Promise<voi
20
29
  declare function setupEnv(projectDir: string): Promise<void>;
21
30
  declare function getDockerContainers(): {
22
31
  value: string;
23
- label: "PostgreSQL 16 (Database)" | "Mailpit (Email testing)" | "MinIO (S3-compatible storage)";
32
+ label: "PostgreSQL 16 (Database)" | "pgAdmin 4.9 (Database management) [Depends on PostgreSQL]" | "Mailpit (Email testing)" | "MinIO (S3-compatible storage)";
24
33
  }[];
25
34
  declare function deleteDockerCompose(projectDir: string): Promise<void>;
26
35
  declare function configureDockerCompose(projectDir: string, selectedContainers: string[]): Promise<void>;
@@ -4,7 +4,7 @@ import {
4
4
  getDockerContainers,
5
5
  replaceScope,
6
6
  setupEnv
7
- } from "../chunk-SYNNS7BI.js";
7
+ } from "../chunk-BMN5Q4NA.js";
8
8
  export {
9
9
  configureDockerCompose,
10
10
  deleteDockerCompose,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-turbo-kit",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "type": "module",
5
5
  "description": "",
6
6
  "main": "dist/cli.js",
package/src/cli.ts CHANGED
@@ -172,6 +172,9 @@ async function main() {
172
172
  if (!shouldInstall) {
173
173
  console.log(color.cyan(` ${packageManager} install`));
174
174
  }
175
+ if (wantsDocker) {
176
+ console.log(color.cyan(` docker compose up -d`));
177
+ }
175
178
  console.log(color.cyan(` ${packageManager} run dev`));
176
179
 
177
180
  console.log();
@@ -9,16 +9,25 @@ const DOCKER_CONTAINERS = {
9
9
  label: 'PostgreSQL 16 (Database)',
10
10
  services: ['postgres'],
11
11
  volumes: ['postgres_data'],
12
+ dependsOn: [],
13
+ },
14
+ pgadmin: {
15
+ label: `pgAdmin 4.9 (Database management) [Depends on PostgreSQL]`,
16
+ services: ['pgadmin'],
17
+ volumes: [],
18
+ dependsOn: ['postgres'],
12
19
  },
13
20
  mailpit: {
14
21
  label: 'Mailpit (Email testing)',
15
22
  services: ['mailpit'],
16
23
  volumes: [],
24
+ dependsOn: [],
17
25
  },
18
26
  minio: {
19
27
  label: 'MinIO (S3-compatible storage)',
20
28
  services: ['minio', 'minio-create-bucket'],
21
29
  volumes: ['minio_data'],
30
+ dependsOn: [],
22
31
  },
23
32
  } as const;
24
33
 
@@ -89,6 +98,12 @@ export async function deleteDockerCompose(projectDir: string) {
89
98
  const dockerComposePath = path.join(projectDir, 'docker-compose.yml');
90
99
  if (await fs.pathExists(dockerComposePath)) {
91
100
  await fs.remove(dockerComposePath);
101
+ s.message(`Removed ${color.cyan(dockerComposePath)}`);
102
+ }
103
+ const dockerDir = path.join(projectDir, 'docker');
104
+ if (await fs.pathExists(dockerDir)) {
105
+ await fs.remove(dockerDir);
106
+ s.message(`Removed ${color.cyan(dockerDir)}`);
92
107
  }
93
108
  s.stop('Removed Docker Compose setup');
94
109
  } catch (error) {
@@ -97,6 +112,27 @@ export async function deleteDockerCompose(projectDir: string) {
97
112
  }
98
113
  }
99
114
 
115
+ function resolveContainerDependencies(selectedContainers: string[]): string[] {
116
+ const resolved = new Set<string>(selectedContainers);
117
+ const queue = [...selectedContainers];
118
+
119
+ while (queue.length > 0) {
120
+ const current = queue.shift()!;
121
+ const config = DOCKER_CONTAINERS[current as DockerContainer];
122
+
123
+ if (config && config.dependsOn) {
124
+ for (const dep of config.dependsOn) {
125
+ if (!resolved.has(dep)) {
126
+ resolved.add(dep);
127
+ queue.push(dep);
128
+ }
129
+ }
130
+ }
131
+ }
132
+
133
+ return Array.from(resolved);
134
+ }
135
+
100
136
  export async function configureDockerCompose(projectDir: string, selectedContainers: string[]) {
101
137
  const s = spinner();
102
138
  s.start('Configuring Docker Compose...');
@@ -109,10 +145,17 @@ export async function configureDockerCompose(projectDir: string, selectedContain
109
145
  return;
110
146
  }
111
147
 
148
+ const resolvedContainers = resolveContainerDependencies(selectedContainers);
149
+ const addedDependencies = resolvedContainers.filter(c => !selectedContainers.includes(c));
150
+
151
+ if (addedDependencies.length > 0) {
152
+ s.message(`Auto-including dependencies: ${color.cyan(addedDependencies.join(', '))}`);
153
+ }
154
+
112
155
  let content = await fs.readFile(dockerComposePath, 'utf8');
113
156
 
114
157
  const allContainers = Object.keys(DOCKER_CONTAINERS) as DockerContainer[];
115
- const containersToRemove = allContainers.filter(c => !selectedContainers.includes(c));
158
+ const containersToRemove = allContainers.filter(c => !resolvedContainers.includes(c));
116
159
 
117
160
  for (const container of containersToRemove) {
118
161
  const containerRegex = new RegExp(
@@ -120,14 +163,28 @@ export async function configureDockerCompose(projectDir: string, selectedContain
120
163
  'g'
121
164
  );
122
165
  content = content.replace(containerRegex, '');
166
+
167
+ // delete `./docker/<container>/`
168
+ const dockerDirPath = path.join(projectDir, 'docker', container);
169
+ if (await fs.pathExists(dockerDirPath)) {
170
+ await fs.remove(dockerDirPath);
171
+ s.message(`Removed ${color.cyan(dockerDirPath)}`);
172
+ }
123
173
  }
124
174
 
125
175
  content = content.replace(/ # -- \w+ --\n/g, '');
126
176
  content = content.replace(/ # \/\/ \w+ \/\/\n/g, '');
127
177
 
128
178
  await fs.writeFile(dockerComposePath, content);
179
+
180
+ // check if `./docker/` is empty
181
+ const dockerDir = path.join(projectDir, 'docker');
182
+ if (await fs.pathExists(dockerDir) && (await fs.readdir(dockerDir)).length === 0) {
183
+ await fs.remove(dockerDir);
184
+ s.message(`Removed ${color.cyan(dockerDir)} ${color.gray('(because it was empty)')}`);
185
+ }
129
186
 
130
- s.stop(`Configured Docker Compose with ${color.cyan(selectedContainers.length)} container(s)`);
187
+ s.stop(`Configured Docker Compose with ${color.cyan(resolvedContainers.length)} container(s)`);
131
188
  } catch (error) {
132
189
  s.stop('Failed to configure Docker Compose');
133
190
  throw error;
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/helpers/transform.ts"],"sourcesContent":["import fg from 'fast-glob';\r\nimport fs from 'fs-extra';\r\nimport path from 'path';\r\nimport { spinner } from '@clack/prompts';\r\nimport color from 'picocolors';\r\n\r\nconst DOCKER_CONTAINERS = {\r\n postgres: {\r\n label: 'PostgreSQL 16 (Database)',\r\n services: ['postgres'],\r\n volumes: ['postgres_data'],\r\n },\r\n mailpit: {\r\n label: 'Mailpit (Email testing)',\r\n services: ['mailpit'],\r\n volumes: [],\r\n },\r\n minio: {\r\n label: 'MinIO (S3-compatible storage)',\r\n services: ['minio', 'minio-create-bucket'],\r\n volumes: ['minio_data'],\r\n },\r\n} as const;\r\n\r\nexport type DockerContainer = keyof typeof DOCKER_CONTAINERS;\r\n\r\nexport async function replaceScope(projectDir: string, newScope: string) {\r\n const s = spinner();\r\n s.start(`Replacing scope with ${color.cyan(newScope)}...`);\r\n\r\n try {\r\n const files = await fg('**/*', {\r\n cwd: projectDir,\r\n ignore: [\r\n '**/.git/**',\r\n '**/node_modules/**',\r\n '**/.turbo/**',\r\n '**/dist/**',\r\n '**/.next/**',\r\n '**/pnpm-lock.yaml',\r\n '**/yarn.lock',\r\n '**/package-lock.json',\r\n '**/bun.lockb',\r\n ],\r\n absolute: true,\r\n });\r\n\r\n await Promise.all(\r\n files.map(async (file) => {\r\n try {\r\n const content = await fs.readFile(file, 'utf8');\r\n if (content.includes('@acme')) {\r\n const newContent = content.replace(/@acme/g, newScope);\r\n await fs.writeFile(file, newContent);\r\n }\r\n } catch (e) {\r\n }\r\n })\r\n );\r\n \r\n s.stop(`Replaced scope with ${color.cyan(newScope)}`);\r\n } catch (error) {\r\n s.stop('Failed to replace scope');\r\n throw error;\r\n }\r\n}\r\n\r\nexport async function setupEnv(projectDir: string) {\r\n const envExample = path.join(projectDir, '.env.example');\r\n const envDest = path.join(projectDir, '.env');\r\n\r\n if (await fs.pathExists(envExample)) {\r\n await fs.copy(envExample, envDest);\r\n }\r\n}\r\n\r\nexport function getDockerContainers() {\r\n return Object.entries(DOCKER_CONTAINERS).map(([value, config]) => ({\r\n value,\r\n label: config.label,\r\n }));\r\n}\r\n\r\nexport async function deleteDockerCompose(projectDir: string) {\r\n const s = spinner();\r\n s.start('Removing Docker Compose setup...');\r\n\r\n try {\r\n const dockerComposePath = path.join(projectDir, 'docker-compose.yml');\r\n if (await fs.pathExists(dockerComposePath)) {\r\n await fs.remove(dockerComposePath);\r\n }\r\n s.stop('Removed Docker Compose setup');\r\n } catch (error) {\r\n s.stop('Failed to remove Docker Compose setup');\r\n throw error;\r\n }\r\n}\r\n\r\nexport async function configureDockerCompose(projectDir: string, selectedContainers: string[]) {\r\n const s = spinner();\r\n s.start('Configuring Docker Compose...');\r\n\r\n try {\r\n const dockerComposePath = path.join(projectDir, 'docker-compose.yml');\r\n \r\n if (!(await fs.pathExists(dockerComposePath))) {\r\n s.stop('Docker Compose file not found');\r\n return;\r\n }\r\n\r\n let content = await fs.readFile(dockerComposePath, 'utf8');\r\n\r\n const allContainers = Object.keys(DOCKER_CONTAINERS) as DockerContainer[];\r\n const containersToRemove = allContainers.filter(c => !selectedContainers.includes(c));\r\n\r\n for (const container of containersToRemove) {\r\n const containerRegex = new RegExp(\r\n ` # -- ${container} --\\\\n[\\\\s\\\\S]*? # // ${container} //\\\\n`,\r\n 'g'\r\n );\r\n content = content.replace(containerRegex, '');\r\n }\r\n\r\n content = content.replace(/ # -- \\w+ --\\n/g, '');\r\n content = content.replace(/ # \\/\\/ \\w+ \\/\\/\\n/g, '');\r\n\r\n await fs.writeFile(dockerComposePath, content);\r\n \r\n s.stop(`Configured Docker Compose with ${color.cyan(selectedContainers.length)} container(s)`);\r\n } catch (error) {\r\n s.stop('Failed to configure Docker Compose');\r\n throw error;\r\n }\r\n}\r\n\r\n"],"mappings":"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","names":[]}