@twostepsai/create-app 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/README.md ADDED
@@ -0,0 +1,113 @@
1
+ # @twostepsai/create-app
2
+
3
+ Scaffold a new project from the [TwoSteps Turborepo template](https://github.com/two-steps-org/turbo-repo-templates), picking only the apps you want.
4
+
5
+ ## Usage
6
+
7
+ ```bash
8
+ pnpm create @twosteps/app my-app
9
+ # or
10
+ npm create @twosteps/app my-app
11
+ # or
12
+ yarn create @twosteps/app my-app
13
+ ```
14
+
15
+ The CLI walks you through:
16
+
17
+ 1. **Project name** — kebab-case, used as the target directory.
18
+ 2. **Apps to include** — pick any combination of `next`, `react`, `express`, `fastapi`, `storybook`. Shared `packages/*` are always kept.
19
+ 3. **npm scope** — used to rename `@twosteps/*` workspace packages (e.g. `@my-app/ui`).
20
+ 4. **Initialise git** — runs `git init` and creates a first commit.
21
+
22
+ After the prompts the scaffolder will:
23
+
24
+ - Clone the template into `./<project-name>` (no git history)
25
+ - Remove the apps you didn't pick (and their `package.json` script entries, `docker-compose` services, README rows)
26
+ - Rename every `@twosteps/*` reference to `@<scope>/*`
27
+ - Run `pnpm install`
28
+ - Optionally initialise git
29
+
30
+ ## Non-interactive flags
31
+
32
+ | Flag | Description |
33
+ | ----------------- | ----------------------------------------------------------- |
34
+ | `--name <name>` | Project name (skips that prompt) |
35
+ | `--apps <csv>` | Comma-separated app list, e.g. `--apps fastapi,react` |
36
+ | `--scope <scope>` | npm scope, e.g. `--scope @my-app` |
37
+ | `--no-git` | Skip `git init` |
38
+ | `--yes` | Skip every prompt; use flag values or sensible defaults |
39
+
40
+ Example for CI:
41
+
42
+ ```bash
43
+ pnpm create @twosteps/app my-app --apps fastapi,next --scope @my-app --no-git --yes
44
+ ```
45
+
46
+ ## Add an app later
47
+
48
+ Inside any scaffolded project:
49
+
50
+ ```bash
51
+ pnpm gen # interactive: pick app type, name; turbo wires up scripts + docker-compose
52
+ ```
53
+
54
+ Powered by [Turborepo generators](https://turborepo.com/docs/guides/generating-code).
55
+
56
+ ## Requirements
57
+
58
+ - Node.js >= 18
59
+ - pnpm (the scaffolded project uses pnpm workspaces)
60
+ - Python >= 3.10 + uv (only if you pick the FastAPI app)
61
+
62
+ ## Links
63
+
64
+ - [Template repository](https://github.com/two-steps-org/turbo-repo-templates)
65
+ - [Report an issue](https://github.com/two-steps-org/turbo-repo-templates/issues)
66
+
67
+ ## Versioning & release (bumpit)
68
+
69
+ This package uses [@twostepsai/bumpit](https://www.npmjs.com/package/@twostepsai/bumpit) for semver and changelog. Run all commands from `tools/create-twosteps-app`.
70
+
71
+ ### Contributor workflow (before merge)
72
+
73
+ After your code changes, record release intent:
74
+
75
+ ```bash
76
+ pnpm version:add
77
+ ```
78
+
79
+ Or non-interactive (CI / scripts):
80
+
81
+ ```bash
82
+ pnpm version:add --level patch --message 'Fix scaffolder prompt'
83
+ ```
84
+
85
+ Commit the generated `.bumpit/*.md` change file with your PR.
86
+
87
+ Check pending changes:
88
+
89
+ ```bash
90
+ pnpm version:status
91
+ ```
92
+
93
+ ### Maintainer — publish via GitHub Actions
94
+
95
+ 1. Merge all PRs to `main`.
96
+ 2. GitHub → **Actions** → **Release CLI** → **Run workflow**.
97
+ 3. Choose bump type: `patch` | `minor` | `major`.
98
+ 4. Workflow runs: typecheck → bumpit bump → build → `pnpm publish` → git tag → GitHub Release.
99
+
100
+ **Required secret:** `NPM_TOKEN` (npm automation token with publish access).
101
+
102
+ ### Maintainer — publish locally (optional)
103
+
104
+ ```bash
105
+ pnpm version:bump # bumps package.json + CHANGELOG.md, commits + tags
106
+ pnpm build
107
+ pnpm publish --access public --no-git-checks
108
+ git push origin HEAD --follow-tags
109
+ ```
110
+
111
+ ## License
112
+
113
+ MIT
package/dist/index.js ADDED
@@ -0,0 +1,490 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import { ExitPromptError } from "@inquirer/core";
5
+ import ora from "ora";
6
+ import { cyan, green, red, yellow as yellow3 } from "kolorist";
7
+
8
+ // src/clone.ts
9
+ import degit from "degit";
10
+ import { promises as fs } from "fs";
11
+ import path from "path";
12
+ var TEMPLATE_REPO = "github:two-steps-org/turbo-repo-templates";
13
+ async function ensureTargetDirAvailable(targetDir) {
14
+ try {
15
+ const entries = await fs.readdir(targetDir);
16
+ if (entries.length > 0) {
17
+ throw new Error(`Target directory '${targetDir}' already exists and is not empty`);
18
+ }
19
+ } catch (err) {
20
+ if (err.code === "ENOENT") return;
21
+ throw err;
22
+ }
23
+ }
24
+ async function clone(opts) {
25
+ await ensureTargetDirAvailable(opts.targetDir);
26
+ const emitter = degit(TEMPLATE_REPO, {
27
+ cache: false,
28
+ force: true,
29
+ verbose: false
30
+ });
31
+ await emitter.clone(opts.targetDir);
32
+ const toolsDir = path.join(opts.targetDir, "tools");
33
+ await fs.rm(toolsDir, { recursive: true, force: true });
34
+ }
35
+
36
+ // src/postInstall.ts
37
+ import { execa } from "execa";
38
+ import { yellow } from "kolorist";
39
+ async function runPnpmInstall(targetDir) {
40
+ try {
41
+ await execa("pnpm", ["install"], {
42
+ cwd: targetDir,
43
+ stdio: "inherit"
44
+ });
45
+ } catch (err) {
46
+ const message = err instanceof Error ? err.message : String(err);
47
+ console.warn(yellow(`pnpm install did not finish cleanly: ${message}`));
48
+ console.warn(yellow(`run 'pnpm install' manually inside ${targetDir} to retry`));
49
+ }
50
+ }
51
+ async function initGit(targetDir) {
52
+ try {
53
+ await execa("git", ["init"], { cwd: targetDir, stdio: "pipe" });
54
+ await execa("git", ["add", "."], { cwd: targetDir, stdio: "pipe" });
55
+ await execa("git", ["commit", "-m", "chore: initial commit from @twostepsai/create-app"], {
56
+ cwd: targetDir,
57
+ stdio: "pipe"
58
+ });
59
+ } catch (err) {
60
+ const message = err instanceof Error ? err.message : String(err);
61
+ console.warn(yellow(`git init / first commit failed: ${message}`));
62
+ console.warn(yellow("run git init manually inside the project to retry"));
63
+ }
64
+ }
65
+ async function postInstall(opts) {
66
+ await runPnpmInstall(opts.targetDir);
67
+ if (opts.gitInit) {
68
+ await initGit(opts.targetDir);
69
+ }
70
+ }
71
+
72
+ // src/prompts.ts
73
+ import { checkbox, confirm, input } from "@inquirer/prompts";
74
+ import path2 from "path";
75
+
76
+ // ../../packages/app-metadata/src/index.ts
77
+ var ALL_APPS = ["express", "fastapi", "next", "react", "storybook"];
78
+ var SCRIPT_ALIAS = {
79
+ next: "web",
80
+ react: "react",
81
+ express: "express",
82
+ storybook: "storybook",
83
+ fastapi: "fastapi"
84
+ };
85
+ var SCRIPT_VERBS = ["dev", "build", "lint", "format", "check-types", "test"];
86
+ var DIVIDER_KEYS = {
87
+ linters: ["lint", "format"],
88
+ "check-types-typescript": ["check-types"],
89
+ testing: ["test"]
90
+ };
91
+ var README_APP_TITLE = {
92
+ next: "Next.js",
93
+ react: "React",
94
+ express: "Express",
95
+ fastapi: "FastAPI",
96
+ storybook: "Storybook"
97
+ };
98
+ function scriptKeysForApp(app) {
99
+ const alias = SCRIPT_ALIAS[app];
100
+ const keys = SCRIPT_VERBS.map((verb) => `${verb}:${alias}`);
101
+ if (app === "next") keys.push("lint:web:fix");
102
+ return keys;
103
+ }
104
+
105
+ // src/prompts.ts
106
+ var KEBAB_CASE = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
107
+ var SCOPE_PATTERN = /^@[a-z0-9][a-z0-9-]*$/;
108
+ function bail(message) {
109
+ console.error(message);
110
+ process.exit(0);
111
+ }
112
+ function validateProjectName(value) {
113
+ const trimmed = value.trim();
114
+ if (!trimmed) return "Project name is required";
115
+ if (trimmed.includes(" ")) return "Project name cannot contain spaces";
116
+ if (!KEBAB_CASE.test(trimmed)) return "Project name must be kebab-case (lowercase, numbers, dashes)";
117
+ return true;
118
+ }
119
+ function validateScope(value) {
120
+ const trimmed = value.trim();
121
+ if (!trimmed) return "Scope is required";
122
+ if (!trimmed.startsWith("@")) return "Scope must start with @";
123
+ if (trimmed.includes("/")) return "Scope cannot contain /";
124
+ if (!SCOPE_PATTERN.test(trimmed)) return "Scope must be lowercase letters, numbers, dashes after the @";
125
+ return true;
126
+ }
127
+ function parseAppsCsv(csv) {
128
+ const parts = csv.split(",").map((p) => p.trim().toLowerCase()).filter(Boolean);
129
+ const valid = [];
130
+ for (const p of parts) {
131
+ if (ALL_APPS.includes(p)) {
132
+ valid.push(p);
133
+ } else {
134
+ bail(`Unknown app '${p}'. Valid options: ${ALL_APPS.join(", ")}`);
135
+ }
136
+ }
137
+ if (valid.length === 0) bail("At least one app must be selected");
138
+ return Array.from(new Set(valid));
139
+ }
140
+ async function runPrompts(flags) {
141
+ let projectName = flags.name;
142
+ if (!projectName && !flags.yes) {
143
+ const result = await input({
144
+ message: "Project name",
145
+ validate: validateProjectName
146
+ });
147
+ projectName = result.trim();
148
+ }
149
+ if (!projectName) bail("Project name is required (pass --name or run interactively)");
150
+ const nameError = validateProjectName(projectName);
151
+ if (nameError !== true) bail(nameError);
152
+ let apps = flags.apps;
153
+ if (!apps && !flags.yes) {
154
+ apps = await checkbox({
155
+ message: "Pick apps to include",
156
+ choices: [
157
+ { value: "next", name: "next - Next.js 16 frontend", checked: true },
158
+ { value: "react", name: "react - React 19 + Vite SPA" },
159
+ { value: "express", name: "express - Express 5 backend" },
160
+ { value: "fastapi", name: "fastapi - FastAPI Python backend", checked: true },
161
+ { value: "storybook", name: "storybook - Storybook component explorer" }
162
+ ],
163
+ required: true
164
+ });
165
+ }
166
+ if (!apps || apps.length === 0) {
167
+ apps = ["next", "fastapi"];
168
+ }
169
+ let scope = flags.scope;
170
+ if (!scope && !flags.yes) {
171
+ const result = await input({
172
+ message: "npm scope",
173
+ default: `@${projectName}`,
174
+ validate: validateScope
175
+ });
176
+ scope = result.trim();
177
+ }
178
+ if (!scope) scope = `@${projectName}`;
179
+ const scopeError = validateScope(scope);
180
+ if (scopeError !== true) bail(scopeError);
181
+ let gitInit = flags.gitInit;
182
+ if (gitInit === void 0 && !flags.yes) {
183
+ gitInit = await confirm({
184
+ message: "Initialise git repository?",
185
+ default: true
186
+ });
187
+ }
188
+ if (gitInit === void 0) gitInit = true;
189
+ const targetDir = path2.resolve(process.cwd(), projectName);
190
+ return {
191
+ projectName,
192
+ targetDir,
193
+ apps,
194
+ scope,
195
+ gitInit
196
+ };
197
+ }
198
+
199
+ // src/prune.ts
200
+ import { promises as fs2 } from "fs";
201
+ import path3 from "path";
202
+ import { yellow as yellow2 } from "kolorist";
203
+ import { isMap, isScalar, isSeq, parseDocument } from "yaml";
204
+ async function removeAppDir(targetDir, app) {
205
+ await fs2.rm(path3.join(targetDir, "apps", app), { recursive: true, force: true });
206
+ }
207
+ async function prunePackageJson(targetDir, removed, kept) {
208
+ const pkgPath = path3.join(targetDir, "package.json");
209
+ const raw = await fs2.readFile(pkgPath, "utf8");
210
+ const pkg = JSON.parse(raw);
211
+ const scripts = pkg.scripts ?? {};
212
+ for (const app of removed) {
213
+ for (const key of scriptKeysForApp(app)) {
214
+ delete scripts[key];
215
+ }
216
+ }
217
+ for (const [dividerKey, verbs] of Object.entries(DIVIDER_KEYS)) {
218
+ const sectionHasScripts = Object.keys(scripts).some(
219
+ (k) => k !== dividerKey && verbs.some((v) => k === v || k.startsWith(`${v}:`))
220
+ );
221
+ if (!sectionHasScripts) {
222
+ delete scripts[dividerKey];
223
+ }
224
+ }
225
+ const reactKept = kept.includes("react");
226
+ const nextKept = kept.includes("next");
227
+ const storybookKept = kept.includes("storybook");
228
+ if (!nextKept && !storybookKept && pkg.pnpm?.overrides) {
229
+ delete pkg.pnpm.overrides["next"];
230
+ }
231
+ if (!reactKept && !nextKept && !storybookKept) {
232
+ if (pkg.dependencies) {
233
+ delete pkg.dependencies["react"];
234
+ delete pkg.dependencies["react-dom"];
235
+ }
236
+ if (pkg.pnpm?.overrides) {
237
+ delete pkg.pnpm.overrides["react"];
238
+ delete pkg.pnpm.overrides["react-dom"];
239
+ }
240
+ }
241
+ pkg.scripts = scripts;
242
+ await fs2.writeFile(pkgPath, JSON.stringify(pkg, null, " ") + "\n", "utf8");
243
+ }
244
+ async function pruneDockerCompose(targetDir, removed) {
245
+ const composePath = path3.join(targetDir, "docker-compose.yml");
246
+ let raw;
247
+ try {
248
+ raw = await fs2.readFile(composePath, "utf8");
249
+ } catch (err) {
250
+ if (err.code === "ENOENT") return;
251
+ throw err;
252
+ }
253
+ const doc = parseDocument(raw);
254
+ const services = doc.get("services");
255
+ if (!isMap(services)) return;
256
+ const serviceMap = services;
257
+ for (const app of removed) {
258
+ if (serviceMap.has(app)) serviceMap.delete(app);
259
+ }
260
+ for (const item of serviceMap.items) {
261
+ const value = item.value;
262
+ if (!value || !isMap(value)) continue;
263
+ const dependsOn = value.get("depends_on");
264
+ if (!isSeq(dependsOn)) continue;
265
+ const deps = dependsOn;
266
+ deps.items = deps.items.filter((dep) => !(isScalar(dep) && removed.includes(dep.value)));
267
+ if (deps.items.length === 0) value.delete("depends_on");
268
+ }
269
+ if (serviceMap.items.length === 0) {
270
+ await fs2.rm(composePath, { force: true });
271
+ return;
272
+ }
273
+ await fs2.writeFile(composePath, doc.toString(), "utf8");
274
+ }
275
+ function lineMatchesApp(line, app) {
276
+ const alias = SCRIPT_ALIAS[app];
277
+ const titleWord = README_APP_TITLE[app];
278
+ const treeRow = new RegExp(`^[\u2502\u251C\u2514\u2500\\s]*[\u251C\u2514]\u2500\u2500\\s+${app}/`);
279
+ if (treeRow.test(line)) return true;
280
+ if (line.includes(`apps/${app}/`)) return true;
281
+ if (line.includes(`apps/${app} `)) return true;
282
+ if (line.includes(`cd apps/${app}`)) return true;
283
+ if (line.includes(`**${titleWord}**`)) return true;
284
+ return SCRIPT_VERBS.some((verb) => line.includes(`pnpm ${verb}:${alias}`));
285
+ }
286
+ function pruneReadme(content, removed) {
287
+ let out = content;
288
+ for (const app of removed) {
289
+ out = out.split("\n").filter((line) => !lineMatchesApp(line, app)).join("\n");
290
+ }
291
+ if (removed.includes("fastapi")) {
292
+ out = stripBackendCommandsSection(out);
293
+ out = out.replace(/^- \*\*Python\*\*[^\n]*\n/m, "");
294
+ out = out.replace(/^- \*\*uv\*\*[^\n]*\n/m, "");
295
+ out = out.replace(/^# Backend \(Python\)[\s\S]*?(?=\n#|\n```|$)/m, "");
296
+ }
297
+ return out.replace(/\n{3,}/g, "\n\n");
298
+ }
299
+ function stripBackendCommandsSection(content) {
300
+ const heading = "### Backend Commands";
301
+ const start = content.indexOf(heading);
302
+ if (start === -1) return content;
303
+ const after = content.slice(start + heading.length);
304
+ const nextHeadingMatch = after.match(/\n(##\s|###\s)/);
305
+ const end = nextHeadingMatch && typeof nextHeadingMatch.index === "number" ? start + heading.length + nextHeadingMatch.index : content.length;
306
+ return content.slice(0, start) + content.slice(end + 1);
307
+ }
308
+ async function pruneReadmeFile(targetDir, removed) {
309
+ const readmePath = path3.join(targetDir, "README.md");
310
+ let raw;
311
+ try {
312
+ raw = await fs2.readFile(readmePath, "utf8");
313
+ } catch (err) {
314
+ if (err.code === "ENOENT") return;
315
+ throw err;
316
+ }
317
+ const updated = pruneReadme(raw, removed);
318
+ await fs2.writeFile(readmePath, updated, "utf8");
319
+ }
320
+ async function deleteLockfile(targetDir) {
321
+ await fs2.rm(path3.join(targetDir, "pnpm-lock.yaml"), { force: true });
322
+ }
323
+ function orphanWarnings(kept) {
324
+ const warnings = [];
325
+ const hasNext = kept.includes("next");
326
+ const hasStorybook = kept.includes("storybook");
327
+ if (!hasNext && !hasStorybook) {
328
+ warnings.push("@twosteps/ui has no consumer in your selection");
329
+ }
330
+ if (!hasStorybook) {
331
+ warnings.push("@twosteps/shared has no consumer in your selection");
332
+ warnings.push("@twosteps/styles has no consumer in your selection");
333
+ }
334
+ return warnings;
335
+ }
336
+ async function prune(opts) {
337
+ const removed = ALL_APPS.filter((a) => !opts.apps.includes(a));
338
+ for (const app of removed) {
339
+ await removeAppDir(opts.targetDir, app);
340
+ }
341
+ await prunePackageJson(opts.targetDir, removed, opts.apps);
342
+ await pruneDockerCompose(opts.targetDir, removed);
343
+ await pruneReadmeFile(opts.targetDir, removed);
344
+ await deleteLockfile(opts.targetDir);
345
+ const warnings = orphanWarnings(opts.apps).map((w) => yellow2(`heads up: ${w} \u2014 remove it manually if you want a leaner repo`));
346
+ return warnings;
347
+ }
348
+
349
+ // src/rename.ts
350
+ import { promises as fs3 } from "fs";
351
+ import path4 from "path";
352
+ var RENAMABLE_EXTENSIONS = /* @__PURE__ */ new Set([".json", ".jsonc", ".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".md", ".yaml", ".yml"]);
353
+ var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", ".next", ".turbo", ".venv", "__pycache__", "build"]);
354
+ var OLD_NAMESPACE = "@twosteps";
355
+ async function* walk(dir) {
356
+ let entries;
357
+ try {
358
+ entries = await fs3.readdir(dir, { withFileTypes: true });
359
+ } catch (err) {
360
+ if (err.code === "ENOENT") return;
361
+ throw err;
362
+ }
363
+ for (const entry of entries) {
364
+ const full = path4.join(dir, entry.name);
365
+ if (entry.isDirectory()) {
366
+ if (SKIP_DIRS.has(entry.name)) continue;
367
+ yield* walk(full);
368
+ } else if (entry.isFile()) {
369
+ yield full;
370
+ }
371
+ }
372
+ }
373
+ async function rewriteRootPackageName(targetDir, scope) {
374
+ const pkgPath = path4.join(targetDir, "package.json");
375
+ const raw = await fs3.readFile(pkgPath, "utf8");
376
+ const pkg = JSON.parse(raw);
377
+ pkg.name = `${scope}/root`;
378
+ await fs3.writeFile(pkgPath, JSON.stringify(pkg, null, " ") + "\n", "utf8");
379
+ }
380
+ async function rename(opts) {
381
+ const newNamespace = opts.scope;
382
+ for await (const file of walk(opts.targetDir)) {
383
+ const ext = path4.extname(file).toLowerCase();
384
+ if (!RENAMABLE_EXTENSIONS.has(ext)) continue;
385
+ const original = await fs3.readFile(file, "utf8");
386
+ if (!original.includes(OLD_NAMESPACE)) continue;
387
+ const updated = original.split(OLD_NAMESPACE).join(newNamespace);
388
+ if (updated !== original) {
389
+ await fs3.writeFile(file, updated, "utf8");
390
+ }
391
+ }
392
+ await rewriteRootPackageName(opts.targetDir, newNamespace);
393
+ }
394
+
395
+ // src/index.ts
396
+ function parseArgv(argv) {
397
+ const flags = { yes: false };
398
+ let positionalName;
399
+ for (let i = 0; i < argv.length; i++) {
400
+ const arg = argv[i];
401
+ if (!arg) continue;
402
+ if (arg === "--yes" || arg === "-y") {
403
+ flags.yes = true;
404
+ } else if (arg === "--no-git") {
405
+ flags.gitInit = false;
406
+ } else if (arg === "--name") {
407
+ flags.name = argv[++i];
408
+ } else if (arg.startsWith("--name=")) {
409
+ flags.name = arg.slice("--name=".length);
410
+ } else if (arg === "--apps") {
411
+ const value = argv[++i];
412
+ if (value) flags.apps = parseAppsCsv(value);
413
+ } else if (arg.startsWith("--apps=")) {
414
+ flags.apps = parseAppsCsv(arg.slice("--apps=".length));
415
+ } else if (arg === "--scope") {
416
+ flags.scope = argv[++i];
417
+ } else if (arg.startsWith("--scope=")) {
418
+ flags.scope = arg.slice("--scope=".length);
419
+ } else if (arg === "--help" || arg === "-h") {
420
+ printHelp();
421
+ process.exit(0);
422
+ } else if (!arg.startsWith("-") && !positionalName) {
423
+ positionalName = arg;
424
+ }
425
+ }
426
+ if (!flags.name && positionalName) flags.name = positionalName;
427
+ return flags;
428
+ }
429
+ function printHelp() {
430
+ const lines = [
431
+ "",
432
+ cyan("@twostepsai/create-app") + " \u2014 scaffold a Turborepo project from the TwoSteps template",
433
+ "",
434
+ "Usage:",
435
+ " pnpm create @twosteps/app [name] [options]",
436
+ "",
437
+ "Options:",
438
+ ` --name <name> project name (kebab-case)`,
439
+ ` --apps <csv> comma-separated apps (${ALL_APPS.join(", ")})`,
440
+ " --scope <scope> npm scope, e.g. @my-app",
441
+ " --no-git skip git init",
442
+ " --yes, -y skip prompts; use flags or defaults",
443
+ " --help, -h show this help",
444
+ ""
445
+ ];
446
+ console.log(lines.join("\n"));
447
+ }
448
+ async function main() {
449
+ const flags = parseArgv(process.argv.slice(2));
450
+ console.log("\n" + cyan("@twostepsai/create-app") + "\n");
451
+ const opts = await runPrompts(flags);
452
+ const cloneSpin = ora("Cloning template").start();
453
+ await clone(opts);
454
+ cloneSpin.succeed("Template cloned");
455
+ const removed = ALL_APPS.filter((a) => !opts.apps.includes(a));
456
+ const pruneSpin = ora(removed.length ? `Removing unselected apps (${removed.join(", ")})` : "No apps to remove").start();
457
+ const warnings = await prune(opts);
458
+ pruneSpin.succeed("Pruned scripts, services, and README");
459
+ const renameSpin = ora(`Renaming @twosteps/* to ${opts.scope}/*`).start();
460
+ await rename(opts);
461
+ renameSpin.succeed("Renamed workspace packages");
462
+ await postInstall(opts);
463
+ for (const w of warnings) {
464
+ console.log(w);
465
+ }
466
+ const next = [
467
+ "",
468
+ `${green("Done.")} Next steps:`,
469
+ ` ${cyan(`cd ${opts.projectName}`)}`,
470
+ ` ${cyan("pnpm dev")}`,
471
+ ""
472
+ ];
473
+ console.log(next.join("\n"));
474
+ }
475
+ main().catch((err) => {
476
+ if (err instanceof ExitPromptError) {
477
+ console.error(yellow3("\nCancelled."));
478
+ process.exit(0);
479
+ }
480
+ const message = err instanceof Error ? err.message : String(err);
481
+ console.error(red(`
482
+ Failed to scaffold project: ${message}`));
483
+ if (process.env["DEBUG"]) {
484
+ console.error(err);
485
+ } else {
486
+ console.error(yellow3("Set DEBUG=1 for full stack trace."));
487
+ }
488
+ process.exit(1);
489
+ });
490
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/clone.ts","../src/postInstall.ts","../src/prompts.ts","../../../packages/app-metadata/src/index.ts","../src/prune.ts","../src/rename.ts"],"sourcesContent":["import { ExitPromptError } from '@inquirer/core';\nimport ora from 'ora';\nimport { cyan, green, red, yellow } from 'kolorist';\nimport { clone } from './clone.js';\nimport { postInstall } from './postInstall.js';\nimport { parseAppsCsv, runPrompts } from './prompts.js';\nimport { prune } from './prune.js';\nimport { rename } from './rename.js';\nimport { ALL_APPS, type App, type CliFlags } from './types.js';\n\nfunction parseArgv(argv: string[]): CliFlags {\n const flags: CliFlags = { yes: false };\n let positionalName: string | undefined;\n\n for (let i = 0; i < argv.length; i++) {\n const arg = argv[i];\n if (!arg) continue;\n\n if (arg === '--yes' || arg === '-y') {\n flags.yes = true;\n } else if (arg === '--no-git') {\n flags.gitInit = false;\n } else if (arg === '--name') {\n flags.name = argv[++i];\n } else if (arg.startsWith('--name=')) {\n flags.name = arg.slice('--name='.length);\n } else if (arg === '--apps') {\n const value = argv[++i];\n if (value) flags.apps = parseAppsCsv(value);\n } else if (arg.startsWith('--apps=')) {\n flags.apps = parseAppsCsv(arg.slice('--apps='.length));\n } else if (arg === '--scope') {\n flags.scope = argv[++i];\n } else if (arg.startsWith('--scope=')) {\n flags.scope = arg.slice('--scope='.length);\n } else if (arg === '--help' || arg === '-h') {\n printHelp();\n process.exit(0);\n } else if (!arg.startsWith('-') && !positionalName) {\n positionalName = arg;\n }\n }\n\n if (!flags.name && positionalName) flags.name = positionalName;\n return flags;\n}\n\nfunction printHelp(): void {\n const lines = [\n '',\n cyan('@twostepsai/create-app') + ' — scaffold a Turborepo project from the TwoSteps template',\n '',\n 'Usage:',\n ' pnpm create @twosteps/app [name] [options]',\n '',\n 'Options:',\n ` --name <name> project name (kebab-case)`,\n ` --apps <csv> comma-separated apps (${ALL_APPS.join(', ')})`,\n ' --scope <scope> npm scope, e.g. @my-app',\n ' --no-git skip git init',\n ' --yes, -y skip prompts; use flags or defaults',\n ' --help, -h show this help',\n '',\n ];\n console.log(lines.join('\\n'));\n}\n\nasync function main(): Promise<void> {\n const flags = parseArgv(process.argv.slice(2));\n\n console.log('\\n' + cyan('@twostepsai/create-app') + '\\n');\n\n const opts = await runPrompts(flags);\n\n const cloneSpin = ora('Cloning template').start();\n await clone(opts);\n cloneSpin.succeed('Template cloned');\n\n const removed: App[] = ALL_APPS.filter((a) => !opts.apps.includes(a));\n const pruneSpin = ora(removed.length ? `Removing unselected apps (${removed.join(', ')})` : 'No apps to remove').start();\n const warnings = await prune(opts);\n pruneSpin.succeed('Pruned scripts, services, and README');\n\n const renameSpin = ora(`Renaming @twosteps/* to ${opts.scope}/*`).start();\n await rename(opts);\n renameSpin.succeed('Renamed workspace packages');\n\n await postInstall(opts);\n\n for (const w of warnings) {\n console.log(w);\n }\n\n const next = [\n '',\n `${green('Done.')} Next steps:`,\n ` ${cyan(`cd ${opts.projectName}`)}`,\n ` ${cyan('pnpm dev')}`,\n '',\n ];\n console.log(next.join('\\n'));\n}\n\nmain().catch((err: unknown) => {\n if (err instanceof ExitPromptError) {\n console.error(yellow('\\nCancelled.'));\n process.exit(0);\n }\n const message = err instanceof Error ? err.message : String(err);\n console.error(red(`\\nFailed to scaffold project: ${message}`));\n if (process.env['DEBUG']) {\n console.error(err);\n } else {\n console.error(yellow('Set DEBUG=1 for full stack trace.'));\n }\n process.exit(1);\n});\n","import degit from 'degit';\nimport { promises as fs } from 'node:fs';\nimport path from 'node:path';\nimport type { ScaffoldOptions } from './types.js';\n\nconst TEMPLATE_REPO = 'github:two-steps-org/turbo-repo-templates';\n\nasync function ensureTargetDirAvailable(targetDir: string): Promise<void> {\n try {\n const entries = await fs.readdir(targetDir);\n if (entries.length > 0) {\n throw new Error(`Target directory '${targetDir}' already exists and is not empty`);\n }\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') return;\n throw err;\n }\n}\n\nexport async function clone(opts: ScaffoldOptions): Promise<void> {\n await ensureTargetDirAvailable(opts.targetDir);\n\n const emitter = degit(TEMPLATE_REPO, {\n cache: false,\n force: true,\n verbose: false,\n });\n\n await emitter.clone(opts.targetDir);\n\n const toolsDir = path.join(opts.targetDir, 'tools');\n await fs.rm(toolsDir, { recursive: true, force: true });\n}\n","import { execa } from 'execa';\nimport { yellow } from 'kolorist';\nimport type { ScaffoldOptions } from './types.js';\n\nasync function runPnpmInstall(targetDir: string): Promise<void> {\n try {\n await execa('pnpm', ['install'], {\n cwd: targetDir,\n stdio: 'inherit',\n });\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n console.warn(yellow(`pnpm install did not finish cleanly: ${message}`));\n console.warn(yellow(`run 'pnpm install' manually inside ${targetDir} to retry`));\n }\n}\n\nasync function initGit(targetDir: string): Promise<void> {\n try {\n await execa('git', ['init'], { cwd: targetDir, stdio: 'pipe' });\n await execa('git', ['add', '.'], { cwd: targetDir, stdio: 'pipe' });\n await execa('git', ['commit', '-m', 'chore: initial commit from @twostepsai/create-app'], {\n cwd: targetDir,\n stdio: 'pipe',\n });\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n console.warn(yellow(`git init / first commit failed: ${message}`));\n console.warn(yellow('run git init manually inside the project to retry'));\n }\n}\n\nexport async function postInstall(opts: ScaffoldOptions): Promise<void> {\n await runPnpmInstall(opts.targetDir);\n if (opts.gitInit) {\n await initGit(opts.targetDir);\n }\n}\n","import { checkbox, confirm, input } from '@inquirer/prompts';\nimport path from 'node:path';\nimport { ALL_APPS, type App, type CliFlags, type ScaffoldOptions } from './types.js';\n\nconst KEBAB_CASE = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;\nconst SCOPE_PATTERN = /^@[a-z0-9][a-z0-9-]*$/;\n\nfunction bail(message: string): never {\n console.error(message);\n process.exit(0);\n}\n\nfunction validateProjectName(value: string): true | string {\n const trimmed = value.trim();\n if (!trimmed) return 'Project name is required';\n if (trimmed.includes(' ')) return 'Project name cannot contain spaces';\n if (!KEBAB_CASE.test(trimmed)) return 'Project name must be kebab-case (lowercase, numbers, dashes)';\n return true;\n}\n\nfunction validateScope(value: string): true | string {\n const trimmed = value.trim();\n if (!trimmed) return 'Scope is required';\n if (!trimmed.startsWith('@')) return 'Scope must start with @';\n if (trimmed.includes('/')) return 'Scope cannot contain /';\n if (!SCOPE_PATTERN.test(trimmed)) return 'Scope must be lowercase letters, numbers, dashes after the @';\n return true;\n}\n\nexport function parseAppsCsv(csv: string): App[] {\n const parts = csv\n .split(',')\n .map((p) => p.trim().toLowerCase())\n .filter(Boolean);\n const valid: App[] = [];\n for (const p of parts) {\n if ((ALL_APPS as readonly string[]).includes(p)) {\n valid.push(p as App);\n } else {\n bail(`Unknown app '${p}'. Valid options: ${ALL_APPS.join(', ')}`);\n }\n }\n if (valid.length === 0) bail('At least one app must be selected');\n return Array.from(new Set(valid));\n}\n\nexport async function runPrompts(flags: CliFlags): Promise<ScaffoldOptions> {\n let projectName = flags.name;\n if (!projectName && !flags.yes) {\n const result = await input({\n message: 'Project name',\n validate: validateProjectName,\n });\n projectName = result.trim();\n }\n if (!projectName) bail('Project name is required (pass --name or run interactively)');\n const nameError = validateProjectName(projectName);\n if (nameError !== true) bail(nameError);\n\n let apps = flags.apps;\n if (!apps && !flags.yes) {\n apps = await checkbox<App>({\n message: 'Pick apps to include',\n choices: [\n { value: 'next', name: 'next - Next.js 16 frontend', checked: true },\n { value: 'react', name: 'react - React 19 + Vite SPA' },\n { value: 'express', name: 'express - Express 5 backend' },\n { value: 'fastapi', name: 'fastapi - FastAPI Python backend', checked: true },\n { value: 'storybook', name: 'storybook - Storybook component explorer' },\n ],\n required: true,\n });\n }\n if (!apps || apps.length === 0) {\n apps = ['next', 'fastapi'];\n }\n\n let scope = flags.scope;\n if (!scope && !flags.yes) {\n const result = await input({\n message: 'npm scope',\n default: `@${projectName}`,\n validate: validateScope,\n });\n scope = result.trim();\n }\n if (!scope) scope = `@${projectName}`;\n const scopeError = validateScope(scope);\n if (scopeError !== true) bail(scopeError);\n\n let gitInit = flags.gitInit;\n if (gitInit === undefined && !flags.yes) {\n gitInit = await confirm({\n message: 'Initialise git repository?',\n default: true,\n });\n }\n if (gitInit === undefined) gitInit = true;\n\n const targetDir = path.resolve(process.cwd(), projectName);\n\n return {\n projectName,\n targetDir,\n apps,\n scope,\n gitInit,\n };\n}\n","// Shared by `tools/create-twosteps-app/src/prune.ts` (delete path) and\n// `turbo/generators/config.ts` (add path) so the two stay in lockstep.\n\nexport type App = 'express' | 'fastapi' | 'next' | 'react' | 'storybook';\n\nexport const ALL_APPS: readonly App[] = ['express', 'fastapi', 'next', 'react', 'storybook'];\n\n// Most apps use their own name; `next` is the historical exception (alias `web`).\nexport const SCRIPT_ALIAS: Record<App, string> = {\n\tnext: 'web',\n\treact: 'react',\n\texpress: 'express',\n\tstorybook: 'storybook',\n\tfastapi: 'fastapi',\n};\n\nexport const SCRIPT_VERBS = ['dev', 'build', 'lint', 'format', 'check-types', 'test'] as const;\nexport type ScriptVerb = (typeof SCRIPT_VERBS)[number];\n\n// When every script under a divider is removed, drop the divider too so\n// orphaned section headers don't accumulate in the root package.json.\nexport const DIVIDER_KEYS: Record<string, readonly ScriptVerb[]> = {\n\tlinters: ['lint', 'format'],\n\t'check-types-typescript': ['check-types'],\n\ttesting: ['test'],\n};\n\nexport const README_APP_TITLE: Record<App, string> = {\n\tnext: 'Next.js',\n\treact: 'React',\n\texpress: 'Express',\n\tfastapi: 'FastAPI',\n\tstorybook: 'Storybook',\n};\n\nexport interface ServiceFragment {\n\tbuild: {\n\t\tcontext: string;\n\t\tdockerfile: string;\n\t};\n\tports: string[];\n\tenv_file?: string[];\n\tdepends_on?: string[];\n\trestart?: string;\n}\n\n// `null` ⇒ the app has no Dockerfile in this template; the generator skips\n// the compose mutation entirely. Default ports match each app's Dockerfile;\n// the generator bumps the host port if a collision is detected.\nexport const DOCKER_SERVICES: Record<App, ServiceFragment | null> = {\n\tfastapi: {\n\t\tbuild: {\n\t\t\tcontext: './apps/fastapi',\n\t\t\tdockerfile: 'Dockerfile',\n\t\t},\n\t\tports: ['8000:8000'],\n\t\tenv_file: ['./apps/fastapi/.env'],\n\t\trestart: 'unless-stopped',\n\t},\n\tnext: {\n\t\tbuild: {\n\t\t\tcontext: './apps/next',\n\t\t\tdockerfile: 'Dockerfile',\n\t\t},\n\t\tports: ['3000:3000'],\n\t\trestart: 'unless-stopped',\n\t},\n\texpress: null,\n\treact: null,\n\tstorybook: null,\n};\n\nexport function scriptKeysForApp(app: App): string[] {\n\tconst alias = SCRIPT_ALIAS[app];\n\tconst keys: string[] = SCRIPT_VERBS.map((verb) => `${verb}:${alias}`);\n\tif (app === 'next') keys.push('lint:web:fix');\n\treturn keys;\n}\n","import { promises as fs } from 'node:fs';\nimport path from 'node:path';\nimport { yellow } from 'kolorist';\nimport { isMap, isScalar, isSeq, parseDocument, type Scalar, type YAMLMap, type YAMLSeq } from 'yaml';\nimport { ALL_APPS, DIVIDER_KEYS, README_APP_TITLE, SCRIPT_ALIAS, SCRIPT_VERBS, scriptKeysForApp, type App } from '@twosteps/app-metadata';\nimport type { ScaffoldOptions } from './types.js';\n\nasync function removeAppDir(targetDir: string, app: App): Promise<void> {\n await fs.rm(path.join(targetDir, 'apps', app), { recursive: true, force: true });\n}\n\nasync function prunePackageJson(targetDir: string, removed: App[], kept: App[]): Promise<void> {\n const pkgPath = path.join(targetDir, 'package.json');\n const raw = await fs.readFile(pkgPath, 'utf8');\n const pkg = JSON.parse(raw) as { scripts?: Record<string, string>; dependencies?: Record<string, string>; pnpm?: { overrides?: Record<string, string> } };\n const scripts = pkg.scripts ?? {};\n\n for (const app of removed) {\n for (const key of scriptKeysForApp(app)) {\n delete scripts[key];\n }\n }\n\n for (const [dividerKey, verbs] of Object.entries(DIVIDER_KEYS)) {\n const sectionHasScripts = Object.keys(scripts).some(\n (k) => k !== dividerKey && verbs.some((v) => k === v || k.startsWith(`${v}:`)),\n );\n if (!sectionHasScripts) {\n delete scripts[dividerKey];\n }\n }\n\n const reactKept = kept.includes('react');\n const nextKept = kept.includes('next');\n const storybookKept = kept.includes('storybook');\n\n if (!nextKept && !storybookKept && pkg.pnpm?.overrides) {\n delete pkg.pnpm.overrides['next'];\n }\n\n if (!reactKept && !nextKept && !storybookKept) {\n if (pkg.dependencies) {\n delete pkg.dependencies['react'];\n delete pkg.dependencies['react-dom'];\n }\n if (pkg.pnpm?.overrides) {\n delete pkg.pnpm.overrides['react'];\n delete pkg.pnpm.overrides['react-dom'];\n }\n }\n\n pkg.scripts = scripts;\n await fs.writeFile(pkgPath, JSON.stringify(pkg, null, '\\t') + '\\n', 'utf8');\n}\n\nasync function pruneDockerCompose(targetDir: string, removed: App[]): Promise<void> {\n const composePath = path.join(targetDir, 'docker-compose.yml');\n let raw: string;\n try {\n raw = await fs.readFile(composePath, 'utf8');\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') return;\n throw err;\n }\n\n const doc = parseDocument(raw);\n const services = doc.get('services');\n if (!isMap(services)) return;\n const serviceMap = services as YAMLMap<unknown, YAMLMap>;\n\n for (const app of removed) {\n if (serviceMap.has(app)) serviceMap.delete(app);\n }\n\n for (const item of serviceMap.items) {\n const value = item.value;\n if (!value || !isMap(value)) continue;\n const dependsOn = value.get('depends_on');\n if (!isSeq(dependsOn)) continue;\n const deps = dependsOn as YAMLSeq<Scalar<string>>;\n deps.items = deps.items.filter((dep) => !(isScalar(dep) && removed.includes(dep.value as App)));\n if (deps.items.length === 0) value.delete('depends_on');\n }\n\n if (serviceMap.items.length === 0) {\n await fs.rm(composePath, { force: true });\n return;\n }\n\n await fs.writeFile(composePath, doc.toString(), 'utf8');\n}\n\nfunction lineMatchesApp(line: string, app: App): boolean {\n const alias = SCRIPT_ALIAS[app];\n const titleWord = README_APP_TITLE[app];\n const treeRow = new RegExp(`^[│├└─\\\\s]*[├└]──\\\\s+${app}/`);\n\n if (treeRow.test(line)) return true;\n if (line.includes(`apps/${app}/`)) return true;\n if (line.includes(`apps/${app} `)) return true;\n if (line.includes(`cd apps/${app}`)) return true;\n if (line.includes(`**${titleWord}**`)) return true;\n return SCRIPT_VERBS.some((verb) => line.includes(`pnpm ${verb}:${alias}`));\n}\n\nfunction pruneReadme(content: string, removed: App[]): string {\n let out = content;\n\n for (const app of removed) {\n out = out\n .split('\\n')\n .filter((line) => !lineMatchesApp(line, app))\n .join('\\n');\n }\n\n if (removed.includes('fastapi')) {\n out = stripBackendCommandsSection(out);\n out = out.replace(/^- \\*\\*Python\\*\\*[^\\n]*\\n/m, '');\n out = out.replace(/^- \\*\\*uv\\*\\*[^\\n]*\\n/m, '');\n out = out.replace(/^# Backend \\(Python\\)[\\s\\S]*?(?=\\n#|\\n```|$)/m, '');\n }\n\n return out.replace(/\\n{3,}/g, '\\n\\n');\n}\n\nfunction stripBackendCommandsSection(content: string): string {\n const heading = '### Backend Commands';\n const start = content.indexOf(heading);\n if (start === -1) return content;\n const after = content.slice(start + heading.length);\n const nextHeadingMatch = after.match(/\\n(##\\s|###\\s)/);\n const end = nextHeadingMatch && typeof nextHeadingMatch.index === 'number' ? start + heading.length + nextHeadingMatch.index : content.length;\n return content.slice(0, start) + content.slice(end + 1);\n}\n\nasync function pruneReadmeFile(targetDir: string, removed: App[]): Promise<void> {\n const readmePath = path.join(targetDir, 'README.md');\n let raw: string;\n try {\n raw = await fs.readFile(readmePath, 'utf8');\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') return;\n throw err;\n }\n const updated = pruneReadme(raw, removed);\n await fs.writeFile(readmePath, updated, 'utf8');\n}\n\nasync function deleteLockfile(targetDir: string): Promise<void> {\n await fs.rm(path.join(targetDir, 'pnpm-lock.yaml'), { force: true });\n}\n\nfunction orphanWarnings(kept: App[]): string[] {\n const warnings: string[] = [];\n const hasNext = kept.includes('next');\n const hasStorybook = kept.includes('storybook');\n if (!hasNext && !hasStorybook) {\n warnings.push('@twosteps/ui has no consumer in your selection');\n }\n if (!hasStorybook) {\n warnings.push('@twosteps/shared has no consumer in your selection');\n warnings.push('@twosteps/styles has no consumer in your selection');\n }\n return warnings;\n}\n\nexport async function prune(opts: ScaffoldOptions): Promise<string[]> {\n const removed = ALL_APPS.filter((a) => !opts.apps.includes(a));\n\n for (const app of removed) {\n await removeAppDir(opts.targetDir, app);\n }\n\n await prunePackageJson(opts.targetDir, removed, opts.apps);\n await pruneDockerCompose(opts.targetDir, removed);\n await pruneReadmeFile(opts.targetDir, removed);\n await deleteLockfile(opts.targetDir);\n\n const warnings = orphanWarnings(opts.apps).map((w) => yellow(`heads up: ${w} — remove it manually if you want a leaner repo`));\n return warnings;\n}\n","import { promises as fs } from 'node:fs';\nimport path from 'node:path';\nimport type { ScaffoldOptions } from './types.js';\n\nconst RENAMABLE_EXTENSIONS = new Set(['.json', '.jsonc', '.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs', '.md', '.yaml', '.yml']);\nconst SKIP_DIRS = new Set(['node_modules', '.git', 'dist', '.next', '.turbo', '.venv', '__pycache__', 'build']);\nconst OLD_NAMESPACE = '@twosteps';\n\nasync function* walk(dir: string): AsyncGenerator<string> {\n let entries: import('node:fs').Dirent[];\n try {\n entries = await fs.readdir(dir, { withFileTypes: true });\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') return;\n throw err;\n }\n for (const entry of entries) {\n const full = path.join(dir, entry.name);\n if (entry.isDirectory()) {\n if (SKIP_DIRS.has(entry.name)) continue;\n yield* walk(full);\n } else if (entry.isFile()) {\n yield full;\n }\n }\n}\n\nasync function rewriteRootPackageName(targetDir: string, scope: string): Promise<void> {\n const pkgPath = path.join(targetDir, 'package.json');\n const raw = await fs.readFile(pkgPath, 'utf8');\n const pkg = JSON.parse(raw) as { name?: string };\n pkg.name = `${scope}/root`;\n await fs.writeFile(pkgPath, JSON.stringify(pkg, null, '\\t') + '\\n', 'utf8');\n}\n\nexport async function rename(opts: ScaffoldOptions): Promise<void> {\n const newNamespace = opts.scope;\n for await (const file of walk(opts.targetDir)) {\n const ext = path.extname(file).toLowerCase();\n if (!RENAMABLE_EXTENSIONS.has(ext)) continue;\n const original = await fs.readFile(file, 'utf8');\n if (!original.includes(OLD_NAMESPACE)) continue;\n const updated = original.split(OLD_NAMESPACE).join(newNamespace);\n if (updated !== original) {\n await fs.writeFile(file, updated, 'utf8');\n }\n }\n\n await rewriteRootPackageName(opts.targetDir, newNamespace);\n}\n"],"mappings":"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","names":["yellow","path","path","fs","path","yellow","fs","path","yellow","fs","path","yellow"]}
package/package.json ADDED
@@ -0,0 +1,66 @@
1
+ {
2
+ "name": "@twostepsai/create-app",
3
+ "version": "0.0.1",
4
+ "description": "Scaffold a new project from the TwoSteps Turborepo template — pick which apps to include.",
5
+ "type": "module",
6
+ "bin": {
7
+ "create-app": "./dist/index.js"
8
+ },
9
+ "files": [
10
+ "dist"
11
+ ],
12
+ "scripts": {
13
+ "build": "tsup",
14
+ "dev": "tsup --watch",
15
+ "typecheck": "tsc --noEmit",
16
+ "version:add": "bumpit add",
17
+ "version:status": "bumpit status",
18
+ "version:bump": "bumpit bump"
19
+ },
20
+ "engines": {
21
+ "node": ">=18"
22
+ },
23
+ "keywords": [
24
+ "create",
25
+ "scaffold",
26
+ "turborepo",
27
+ "template",
28
+ "monorepo",
29
+ "next",
30
+ "react",
31
+ "express",
32
+ "fastapi",
33
+ "storybook"
34
+ ],
35
+ "license": "MIT",
36
+ "author": "two-steps-org",
37
+ "repository": {
38
+ "type": "git",
39
+ "url": "git+https://github.com/two-steps-org/turbo-repo-templates.git",
40
+ "directory": "tools/create-twosteps-app"
41
+ },
42
+ "bugs": {
43
+ "url": "https://github.com/two-steps-org/turbo-repo-templates/issues"
44
+ },
45
+ "homepage": "https://github.com/two-steps-org/turbo-repo-templates/tree/main/tools/create-twosteps-app#readme",
46
+ "publishConfig": {
47
+ "access": "public"
48
+ },
49
+ "dependencies": {
50
+ "@inquirer/core": "^11.1.9",
51
+ "@inquirer/prompts": "^8.4.2",
52
+ "degit": "^2.8.4",
53
+ "execa": "^9.5.1",
54
+ "kolorist": "^1.8.0",
55
+ "ora": "^9.4.0",
56
+ "yaml": "^2.6.0"
57
+ },
58
+ "devDependencies": {
59
+ "@twosteps/app-metadata": "workspace:*",
60
+ "@twostepsai/bumpit": "^0.2.1",
61
+ "@types/degit": "^2.8.6",
62
+ "@types/node": "^22.10.0",
63
+ "tsup": "^8.3.0",
64
+ "typescript": "^5.6.0"
65
+ }
66
+ }