scale-stack 0.0.1-alpha.0 → 0.0.1-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +1 -3
  2. package/dist/index.js +475 -7
  3. package/package.json +17 -10
package/README.md CHANGED
@@ -1,8 +1,6 @@
1
1
  # Scale Stack
2
2
 
3
- An opinionated scaffolding engine for Next.js 16.2+ projects. Scale Stack automates the setup of a colocation-first architecture, type-safe data layers, and an AI-native development environment.
4
-
5
- See [PRD.md](PRD.md) for the full product requirements document.
3
+ An opinionated scaffolding engine for Next.js projects. Scale Stack automates the setup of a colocation-first architecture, type-safe data layers, and an AI-native development environment.
6
4
 
7
5
  ## Repository layout
8
6
 
package/dist/index.js CHANGED
@@ -5,6 +5,469 @@ import { Command } from "commander";
5
5
  import { readFileSync } from "fs";
6
6
  import { fileURLToPath } from "url";
7
7
  import { dirname, resolve } from "path";
8
+
9
+ // src/cli/commands/init.ts
10
+ import pc4 from "picocolors";
11
+
12
+ // src/cli/config.ts
13
+ var DEFAULT_CI_PROVIDER = "github";
14
+ var DEFAULT_ANALYTICS_PROVIDER = "posthog";
15
+ function deriveAuthStrategy(orm) {
16
+ return orm === "prisma" ? "stateful" : "stateless";
17
+ }
18
+ function resolveConfig(flags, answers) {
19
+ const projectName = flags.name ?? answers.projectName ?? "my-app";
20
+ const orm = flags.orm ? "prisma" : answers.orm ?? "none";
21
+ const authEnabled = flags.auth ?? answers.auth ?? false;
22
+ const optMods = new Set(answers.optionalModules ?? []);
23
+ const resolveCi = () => {
24
+ if (flags.ci !== void 0) {
25
+ return flags.ci === true ? DEFAULT_CI_PROVIDER : flags.ci;
26
+ }
27
+ return answers.ci ?? "none";
28
+ };
29
+ const resolveAnalytics = () => {
30
+ if (flags.analytics !== void 0) {
31
+ return flags.analytics === true ? DEFAULT_ANALYTICS_PROVIDER : flags.analytics;
32
+ }
33
+ return answers.analytics ?? "none";
34
+ };
35
+ return {
36
+ projectName,
37
+ orm,
38
+ authStrategy: authEnabled ? deriveAuthStrategy(orm) : "none",
39
+ aiChat: flags.chat ?? answers.aiChat ?? false,
40
+ i18n: flags.i18n ?? optMods.has("i18n"),
41
+ ci: resolveCi(),
42
+ analytics: resolveAnalytics(),
43
+ security: flags.security ?? optMods.has("security"),
44
+ jobs: flags.jobs ?? answers.jobs ?? false,
45
+ a11y: flags.a11y ?? optMods.has("a11y"),
46
+ apiClient: flags.apiClient ?? optMods.has("apiClient")
47
+ };
48
+ }
49
+
50
+ // src/cli/prompts/techPicker.ts
51
+ import * as p from "@clack/prompts";
52
+ import pc2 from "picocolors";
53
+
54
+ // src/cli/ui/theme.ts
55
+ import gradient from "gradient-string";
56
+ import pc from "picocolors";
57
+ var ikeaGradient = gradient(["#FFDB00", "#0058A3"]);
58
+ var ikea = {
59
+ heading: (s) => pc.bold(pc.yellow(s)),
60
+ accent: pc.yellow,
61
+ muted: pc.dim,
62
+ success: pc.green,
63
+ bullet: pc.yellow("\u25B8"),
64
+ dimBullet: pc.dim("\u25B8")
65
+ };
66
+ function sectionDivider(title) {
67
+ const line = "\u2500".repeat(Math.max(1, 40 - title.length));
68
+ return pc.dim(` \u2500\u2500 ${pc.yellow(title)} ${line}`);
69
+ }
70
+
71
+ // src/cli/prompts/techPicker.ts
72
+ function cancelled() {
73
+ p.cancel(pc2.yellow("Assembly cancelled \u2014 no parts were harmed."));
74
+ process.exit(0);
75
+ }
76
+ async function runTechPicker(flags) {
77
+ const answers = {};
78
+ p.intro(ikeaGradient(" S K A L S T \xC5 K K "));
79
+ console.log(sectionDivider("Core Stack"));
80
+ console.log();
81
+ if (!flags.name) {
82
+ const name = await p.text({
83
+ message: "Project name",
84
+ placeholder: "my-app",
85
+ validate: (v = "") => {
86
+ if (!v.trim()) return "Project name is required";
87
+ if (!/^[a-z0-9@][a-z0-9._-]*$/i.test(v.trim()))
88
+ return "Invalid package name";
89
+ }
90
+ });
91
+ if (p.isCancel(name)) cancelled();
92
+ answers.projectName = name;
93
+ }
94
+ if (flags.orm === void 0) {
95
+ const orm = await p.confirm({
96
+ message: "Add Prisma ORM? " + ikea.muted(
97
+ "\u2014 schema, migrations, seed script, singleton client \u2014 v7 + Driver Adapters"
98
+ ),
99
+ initialValue: false
100
+ });
101
+ if (p.isCancel(orm)) cancelled();
102
+ answers.orm = orm ? "prisma" : "none";
103
+ }
104
+ if (flags.auth === void 0) {
105
+ const resolvedOrm = flags.orm ? "prisma" : answers.orm ?? "none";
106
+ const hint = resolvedOrm === "prisma" ? "DB-backed sessions, login/signup pages, Prisma adapter" : "stateless JWT sessions, login/signup pages, no DB required";
107
+ const auth = await p.confirm({
108
+ message: "Add authentication? " + ikea.muted(`\u2014 Better Auth \xB7 ${hint}`),
109
+ initialValue: false
110
+ });
111
+ if (p.isCancel(auth)) cancelled();
112
+ answers.auth = auth;
113
+ }
114
+ if (flags.jobs === void 0) {
115
+ const jobs = await p.confirm({
116
+ message: "Enable background jobs? " + ikea.muted("\u2014 Inngest client, route handler, retry config"),
117
+ initialValue: false
118
+ });
119
+ if (p.isCancel(jobs)) cancelled();
120
+ answers.jobs = jobs;
121
+ }
122
+ if (flags.chat === void 0) {
123
+ const chat = await p.confirm({
124
+ message: "Enable AI chat? " + ikea.muted("\u2014 Vercel AI SDK, streaming /api/chat, AI Elements UI"),
125
+ initialValue: false
126
+ });
127
+ if (p.isCancel(chat)) cancelled();
128
+ answers.aiChat = chat;
129
+ }
130
+ console.log();
131
+ console.log(sectionDivider("Features"));
132
+ console.log();
133
+ if (flags.ci === void 0) {
134
+ const ci = await p.select({
135
+ message: "CI pipeline",
136
+ options: [
137
+ {
138
+ value: "github",
139
+ label: pc2.bold("GitHub Actions"),
140
+ hint: ikea.muted("lint, typecheck, test, and deploy jobs")
141
+ },
142
+ {
143
+ value: "none",
144
+ label: "None",
145
+ hint: ikea.muted("no CI pipeline \u2014 add your own later")
146
+ }
147
+ ]
148
+ });
149
+ if (p.isCancel(ci)) cancelled();
150
+ answers.ci = ci;
151
+ }
152
+ if (flags.analytics === void 0) {
153
+ const analytics = await p.select({
154
+ message: "Analytics",
155
+ options: [
156
+ {
157
+ value: "posthog",
158
+ label: pc2.bold("PostHog"),
159
+ hint: ikea.muted("event tracking, session replay, feature flags")
160
+ },
161
+ {
162
+ value: "none",
163
+ label: "None",
164
+ hint: ikea.muted("no analytics \u2014 add your own later")
165
+ }
166
+ ]
167
+ });
168
+ if (p.isCancel(analytics)) cancelled();
169
+ answers.analytics = analytics;
170
+ }
171
+ const alreadySelected = /* @__PURE__ */ new Set();
172
+ if (flags.security) alreadySelected.add("security");
173
+ if (flags.a11y) alreadySelected.add("a11y");
174
+ if (flags.apiClient) alreadySelected.add("apiClient");
175
+ if (flags.i18n) alreadySelected.add("i18n");
176
+ const availableModules = [
177
+ {
178
+ value: "security",
179
+ label: "Security hardening",
180
+ hint: ikea.muted("CSP, HSTS, X-Frame-Options, CORS config")
181
+ },
182
+ {
183
+ value: "a11y",
184
+ label: "Accessibility",
185
+ hint: ikea.muted("jsx-a11y lint rules, @axe-core/react, WCAG 2.1 AA")
186
+ },
187
+ {
188
+ value: "apiClient",
189
+ label: "API client",
190
+ hint: ikea.muted("TanStack Query hooks + typed SDK via hey-api")
191
+ },
192
+ {
193
+ value: "i18n",
194
+ label: "Internationalization",
195
+ hint: ikea.muted("next-intl, locale routing, <LocaleSwitcher>")
196
+ }
197
+ ].filter((m) => !alreadySelected.has(m.value));
198
+ if (availableModules.length > 0) {
199
+ console.log();
200
+ console.log(sectionDivider("Smart-Fittings"));
201
+ console.log();
202
+ const selected = await p.multiselect({
203
+ message: "Optional modules " + ikea.muted("\u2014 pick only what you need"),
204
+ options: availableModules,
205
+ required: false
206
+ });
207
+ if (p.isCancel(selected)) cancelled();
208
+ answers.optionalModules = selected;
209
+ }
210
+ p.outro(
211
+ pc2.green("\u2713") + " All parts selected. " + ikea.heading("Ready for assembly.")
212
+ );
213
+ return answers;
214
+ }
215
+
216
+ // src/cli/ui/configDisplay.ts
217
+ import pc3 from "picocolors";
218
+
219
+ // src/cli/ui/animate.ts
220
+ function sleep(ms) {
221
+ return new Promise((resolve2) => setTimeout(resolve2, ms));
222
+ }
223
+ function cursorUp(n) {
224
+ process.stdout.write(`\x1B[${n}A`);
225
+ }
226
+ async function cascade(lines, delayMs) {
227
+ for (const line of lines) {
228
+ console.log(line);
229
+ await sleep(delayMs);
230
+ }
231
+ }
232
+ async function typewriter(text2, charDelayMs) {
233
+ for (const char of text2) {
234
+ process.stdout.write(char);
235
+ if (char !== " " && char !== "\n") {
236
+ await sleep(charDelayMs);
237
+ }
238
+ }
239
+ }
240
+
241
+ // src/cli/ui/configDisplay.ts
242
+ function row(content) {
243
+ return ` \u2502 ${content}`;
244
+ }
245
+ function labelRow(key, value, maxKey) {
246
+ const dots = "\xB7".repeat(Math.max(1, maxKey - key.length + 2));
247
+ return row(`${ikea.bullet} ${key} ${pc3.dim(dots)} ${value}`);
248
+ }
249
+ function formatValue(val) {
250
+ if (val === true || val === "yes") return pc3.green("yes");
251
+ if (val === false || val === "no" || val === "none") return ikea.muted("no");
252
+ return pc3.green(val);
253
+ }
254
+ function buildLines(config) {
255
+ const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
256
+ const header = "S K A L S T \xC5 K K";
257
+ const artNo = `Art.${today.replace(/-/g, ".")}`;
258
+ const coreEntries = [
259
+ ["Project", config.projectName],
260
+ ["ORM", config.orm === "none" ? false : config.orm],
261
+ ["Auth", config.authStrategy === "none" ? false : config.authStrategy],
262
+ ["Jobs", config.jobs],
263
+ ["AI Chat", config.aiChat]
264
+ ];
265
+ const featureEntries = [
266
+ ["CI", config.ci === "none" ? false : config.ci],
267
+ ["Analytics", config.analytics === "none" ? false : config.analytics]
268
+ ];
269
+ const moduleEntries = [
270
+ ["Security", config.security],
271
+ ["a11y", config.a11y],
272
+ ["API Client", config.apiClient],
273
+ ["i18n", config.i18n]
274
+ ];
275
+ const allEntries = [...coreEntries, ...featureEntries, ...moduleEntries];
276
+ const maxKey = Math.max(...allEntries.map(([k]) => k.length));
277
+ const lines = [];
278
+ lines.push(pc3.dim(" \u250C\u2500\u2500"));
279
+ lines.push(row(ikeaGradient(header) + " " + ikea.muted(artNo)));
280
+ lines.push(row(ikea.muted("Assembly summary")));
281
+ lines.push(pc3.dim(" \u2502"));
282
+ for (const [key, val] of coreEntries) {
283
+ lines.push(labelRow(key, formatValue(val), maxKey));
284
+ }
285
+ lines.push(pc3.dim(" \u2502"));
286
+ lines.push(row(pc3.yellow("Features:")));
287
+ lines.push(pc3.dim(" \u2502"));
288
+ for (const [key, val] of featureEntries) {
289
+ lines.push(labelRow(key, formatValue(val), maxKey));
290
+ }
291
+ lines.push(pc3.dim(" \u2502"));
292
+ lines.push(row(pc3.yellow("Smart-Fittings (modules):")));
293
+ lines.push(pc3.dim(" \u2502"));
294
+ for (const [key, val] of moduleEntries) {
295
+ lines.push(labelRow(key, formatValue(val), maxKey));
296
+ }
297
+ lines.push(pc3.dim(" \u2502"));
298
+ lines.push(row(ikea.muted(artNo)));
299
+ lines.push(pc3.dim(" \u2514\u2500\u2500"));
300
+ return lines;
301
+ }
302
+ async function animateConfigLabel(config) {
303
+ const lines = buildLines(config);
304
+ console.log();
305
+ await cascade(lines, 25);
306
+ console.log();
307
+ }
308
+ function printConfigLabel(config) {
309
+ const lines = buildLines(config);
310
+ console.log();
311
+ for (const line of lines) console.log(line);
312
+ console.log();
313
+ }
314
+
315
+ // src/cli/commands/init.ts
316
+ function registerInitCommand(program2) {
317
+ program2.command("init").description("Assemble a new SKALST\xC5KK project").argument("[name]", "Project name").option("--name <name>", "Project name (overrides positional argument)").option("--orm", "Use Prisma ORM").option("--auth", "Enable authentication (Better Auth)").option("--chat", "Enable AI chat").option("--i18n", "Enable internationalization").option("--security", "Enable security hardening").option("--ci [provider]", "CI pipeline (default: github)").option("--analytics [provider]", "Analytics provider (default: posthog)").option("--jobs", "Enable background jobs (Inngest)").option("--a11y", "Enable accessibility tooling").option("--api-client", "Enable API client generation").option("-y, --yes", "Skip prompts, accept defaults").action(async (positionalName, opts) => {
318
+ const flags = {
319
+ ...opts,
320
+ name: opts.name ?? positionalName
321
+ };
322
+ const answers = flags.yes ? {} : await runTechPicker(flags);
323
+ const config = resolveConfig(flags, answers);
324
+ if (process.stdout.isTTY) {
325
+ await animateConfigLabel(config);
326
+ } else {
327
+ printConfigLabel(config);
328
+ }
329
+ console.log(
330
+ ikea.muted(
331
+ " " + pc4.yellow("\u25B8") + " Generation is not yet implemented \u2014 config collected only."
332
+ )
333
+ );
334
+ console.log();
335
+ });
336
+ }
337
+
338
+ // src/cli/commands/sync.ts
339
+ import pc5 from "picocolors";
340
+ function registerSyncCommand(program2) {
341
+ program2.command("sync").description("Update an existing SKALST\xC5KK project to the latest fittings").action(() => {
342
+ console.log(
343
+ ikea.muted(
344
+ " " + pc5.yellow("\u25B8") + " sync is not yet implemented \u2014 check back after assembly."
345
+ )
346
+ );
347
+ });
348
+ }
349
+
350
+ // src/cli/ui/banner.ts
351
+ import gradient2 from "gradient-string";
352
+ import pc6 from "picocolors";
353
+ var LOGO = [
354
+ " \xB0 ",
355
+ "\u2554\u2550\u2557 \u2566\u2554\u2550 \u2554\u2550\u2557 \u2566 \u2554\u2550\u2557 \u2554\u2566\u2557 \u2554\u2550\u2557 \u2566\u2554\u2550 \u2566\u2554\u2550",
356
+ "\u255A\u2550\u2557 \u2560\u2569\u2557 \u2560\u2550\u2563 \u2551 \u255A\u2550\u2557 \u2551 \u2560\u2550\u2563 \u2560\u2569\u2557 \u2560\u2569\u2557",
357
+ "\u255A\u2550\u255D \u2569 \u2569 \u2569 \u2569 \u2569\u2550\u255D \u255A\u2550\u255D \u2569 \u2569 \u2569 \u2569 \u2569 \u2569 \u2569"
358
+ ];
359
+ var TAGLINE = "Project foundation w/ smart-fittings";
360
+ var INNER = 43;
361
+ var CONTENT_W = INNER - 6;
362
+ var BORDER_TOP = ` \u256D${"\u2500".repeat(INNER)}\u256E`;
363
+ var BORDER_BOT = ` \u2570${"\u2500".repeat(INNER)}\u256F`;
364
+ var EMPTY_ROW = ` \u2502${" ".repeat(INNER)}\u2502`;
365
+ function boxRow(content, rawLen) {
366
+ const pad = CONTENT_W - rawLen;
367
+ return ` \u2502 ${content}${" ".repeat(Math.max(0, pad))} \u2502`;
368
+ }
369
+ function logoBoxLine(colored, rawLen) {
370
+ return pc6.dim(" \u2502") + " " + colored + " ".repeat(Math.max(0, CONTENT_W - rawLen)) + pc6.dim(" \u2502");
371
+ }
372
+ var SWEEP_GRADIENTS = [
373
+ gradient2(["#6B6B2E", "#2E4A6B"]),
374
+ gradient2(["#B8A31A", "#1A6B9E"]),
375
+ ikeaGradient
376
+ ];
377
+ var PARTS = "\u2550\u2554\u2557\u2566\u2560\u2569\u2551\u255A\u255D\u2563\u256C\u2500\u2502\u250C\u2510\u2514\u2518\u252C\u2534\u251C\u2524\u253C";
378
+ function scrambleLine(line, resolvedUpTo) {
379
+ return Array.from(line).map((ch, i) => {
380
+ if (i < resolvedUpTo) return ch;
381
+ if (ch === " ") return " ";
382
+ return PARTS[Math.floor(Math.random() * PARTS.length)];
383
+ }).join("");
384
+ }
385
+ function progressBar(filled, total, width = 8) {
386
+ const n = Math.round(filled / total * width);
387
+ return "\u2588".repeat(n) + "\u2591".repeat(width - n);
388
+ }
389
+ var BOX_CONTENT_LINES = 8;
390
+ async function animateBanner() {
391
+ const logoText = LOGO.join("\n");
392
+ const charCount = LOGO[0].length;
393
+ const FRAMES = 10;
394
+ const RATTLE = 2;
395
+ console.log();
396
+ console.log(pc6.dim(BORDER_TOP));
397
+ console.log(pc6.dim(EMPTY_ROW));
398
+ for (let f = 0; f <= FRAMES; f++) {
399
+ if (f > 0) cursorUp(BOX_CONTENT_LINES);
400
+ const progress = f <= RATTLE ? 0 : (f - RATTLE) / (FRAMES - RATTLE);
401
+ const resolved = Math.floor(progress * charCount);
402
+ const done = resolved >= charCount;
403
+ for (const line of LOGO) {
404
+ const text2 = done ? line : scrambleLine(line, resolved);
405
+ console.log(logoBoxLine(pc6.dim(text2), line.length));
406
+ }
407
+ console.log(pc6.dim(EMPTY_ROW));
408
+ if (done) {
409
+ console.log(boxRow(ikea.heading(TAGLINE), TAGLINE.length));
410
+ } else {
411
+ const bar = progressBar(Math.max(0, f - RATTLE), FRAMES - RATTLE);
412
+ const plain = `\u25B8 assembling ${bar}`;
413
+ console.log(
414
+ boxRow(`${ikea.bullet} assembling ${pc6.dim(bar)}`, plain.length)
415
+ );
416
+ }
417
+ console.log(pc6.dim(EMPTY_ROW));
418
+ console.log(pc6.dim(BORDER_BOT));
419
+ await sleep(f <= RATTLE ? 120 : done ? 200 : 60);
420
+ }
421
+ for (const grad of SWEEP_GRADIENTS) {
422
+ cursorUp(BOX_CONTENT_LINES);
423
+ const colored = grad.multiline(logoText).split("\n");
424
+ for (let i = 0; i < colored.length; i++) {
425
+ console.log(logoBoxLine(colored[i], LOGO[i].length));
426
+ }
427
+ console.log(pc6.dim(EMPTY_ROW));
428
+ console.log(boxRow(ikea.heading(TAGLINE), TAGLINE.length));
429
+ console.log(pc6.dim(EMPTY_ROW));
430
+ console.log(pc6.dim(BORDER_BOT));
431
+ await sleep(70);
432
+ }
433
+ console.log();
434
+ }
435
+ async function animateDescription() {
436
+ const lines = [
437
+ "Stop the digital clutter. Flat-packed code efficiency",
438
+ "delivered straight to your terminal."
439
+ ];
440
+ for (const line of lines) {
441
+ process.stdout.write(" ");
442
+ await typewriter(pc6.dim(line), 6);
443
+ console.log();
444
+ }
445
+ console.log();
446
+ }
447
+ function printBanner() {
448
+ const coloredLines = ikeaGradient.multiline(LOGO.join("\n")).split("\n");
449
+ console.log();
450
+ console.log(pc6.dim(BORDER_TOP));
451
+ console.log(pc6.dim(EMPTY_ROW));
452
+ for (let i = 0; i < coloredLines.length; i++) {
453
+ console.log(logoBoxLine(coloredLines[i], LOGO[i].length));
454
+ }
455
+ console.log(pc6.dim(EMPTY_ROW));
456
+ console.log(boxRow(ikea.heading(TAGLINE), TAGLINE.length));
457
+ console.log(pc6.dim(EMPTY_ROW));
458
+ console.log(pc6.dim(BORDER_BOT));
459
+ console.log();
460
+ }
461
+ function printDescription() {
462
+ console.log(
463
+ ikea.muted(
464
+ " Stop the digital clutter. Flat-packed code efficiency\n delivered straight to your terminal."
465
+ )
466
+ );
467
+ console.log();
468
+ }
469
+
470
+ // src/cli/index.ts
8
471
  var __dirname = dirname(fileURLToPath(import.meta.url));
9
472
  var pkgVersion = "0.0.0";
10
473
  try {
@@ -14,11 +477,16 @@ try {
14
477
  pkgVersion = pkg.version ?? pkgVersion;
15
478
  } catch {
16
479
  }
17
- var program = new Command().name("scale-stack").description("Opinionated scaffolding engine for Next.js projects").version(pkgVersion);
18
- program.command("init").description("Scaffold a new Scale Stack project").argument("[name]", "Project name").action((_name) => {
19
- console.log("init is not yet implemented");
20
- });
21
- program.command("sync").description("Update an existing Scale Stack project").action(() => {
22
- console.log("sync is not yet implemented");
23
- });
480
+ var argv = process.argv.slice(2);
481
+ var wantsHelp = argv.includes("--help") || argv.includes("-h") || argv.includes("--version") || argv.includes("-V");
482
+ if (process.stdout.isTTY && !wantsHelp) {
483
+ await animateBanner();
484
+ await animateDescription();
485
+ } else if (process.stdout.isTTY) {
486
+ printBanner();
487
+ printDescription();
488
+ }
489
+ var program = new Command().name("scale-stack").version(pkgVersion);
490
+ registerInitCommand(program);
491
+ registerSyncCommand(program);
24
492
  program.parse();
package/package.json CHANGED
@@ -1,25 +1,23 @@
1
1
  {
2
2
  "name": "scale-stack",
3
- "version": "0.0.1-alpha.0",
4
- "description": "Opinionated scaffolding engine for Next.js projects",
3
+ "version": "0.0.1-alpha.2",
4
+ "description": "SKALSTÅKK flat-packed code efficiency delivered straight to your terminal",
5
5
  "type": "module",
6
- "exports": {
7
- ".": {
8
- "import": "./dist/index.js"
9
- }
10
- },
6
+ "packageManager": "pnpm@10.33.0",
11
7
  "bin": {
12
8
  "scale-stack": "dist/index.js"
13
9
  },
14
10
  "files": [
15
- "dist"
11
+ "dist",
12
+ "README.md"
16
13
  ],
17
14
  "publishConfig": {
18
15
  "access": "public"
19
16
  },
20
17
  "scripts": {
21
18
  "build": "tsup",
22
- "dev": "tsup --watch",
19
+ "run": "node dist/index.js",
20
+ "cli": "tsx src/cli/index.ts",
23
21
  "test": "vitest run",
24
22
  "test:watch": "vitest",
25
23
  "lint": "eslint . && prettier --check .",
@@ -28,7 +26,14 @@
28
26
  "release": "pnpm build && npm publish",
29
27
  "release:alpha": "pnpm build && npm publish --tag alpha"
30
28
  },
31
- "keywords": [],
29
+ "keywords": [
30
+ "scale",
31
+ "stack",
32
+ "scaffolding",
33
+ "boilerplate",
34
+ "generator",
35
+ "cli"
36
+ ],
32
37
  "author": "balazs.farago@scale.com",
33
38
  "license": "ISC",
34
39
  "devDependencies": {
@@ -42,6 +47,7 @@
42
47
  "jiti": "^2.6.1",
43
48
  "prettier": "^3.8.2",
44
49
  "tsup": "^8.5.1",
50
+ "tsx": "^4.21.0",
45
51
  "typescript": "^6.0.2",
46
52
  "typescript-eslint": "^8.58.2",
47
53
  "vitest": "^4.1.4"
@@ -50,6 +56,7 @@
50
56
  "@clack/prompts": "^1.2.0",
51
57
  "commander": "^14.0.3",
52
58
  "ejs": "^5.0.2",
59
+ "gradient-string": "^3.0.0",
53
60
  "picocolors": "^1.1.1",
54
61
  "semver": "^7.7.4"
55
62
  }