create-theokit 1.0.16 → 1.1.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.
- package/dist/cli.js +247 -94
- package/dist/cli.js.map +1 -1
- package/package.json +8 -7
- package/templates/default/agents/chat.ts +7 -7
- package/templates/default/app/page.test.tsx +36 -0
- package/templates/default/app/page.tsx +36 -116
- package/templates/default/dot-claude/skills/theokit-agents/SKILL.md +3 -3
- package/templates/default/package.json.tmpl +5 -2
- package/templates/default/server/routes/health.ts +5 -5
- package/templates/default/theo.config.ts +2 -2
- package/templates/default/tsconfig.json +3 -1
- package/templates/surfaces/desktop/README-surface.md.tmpl +31 -0
- package/templates/surfaces/desktop/frontend/index.html.tmpl +39 -0
- package/templates/surfaces/desktop/frontend/src/main.ts +84 -0
- package/templates/surfaces/desktop/frontend/vite.config.ts +12 -0
- package/templates/surfaces/desktop/sidecar/sidecar-core.ts +37 -0
- package/templates/surfaces/desktop/sidecar/sidecar.ts +51 -0
- package/templates/surfaces/desktop/src-tauri/Cargo.toml.tmpl +16 -0
- package/templates/surfaces/desktop/src-tauri/build.rs +3 -0
- package/templates/surfaces/desktop/src-tauri/capabilities/default.json +14 -0
- package/templates/surfaces/desktop/src-tauri/src/lib.rs +82 -0
- package/templates/surfaces/desktop/src-tauri/src/main.rs +6 -0
- package/templates/surfaces/desktop/src-tauri/tauri.conf.json.tmpl +30 -0
- package/templates/surfaces/tui/README-surface.md.tmpl +26 -0
- package/templates/surfaces/tui/tui/App.tsx.tmpl +64 -0
- package/templates/surfaces/tui/tui/main.tsx.tmpl +14 -0
- package/LICENSE +0 -201
package/dist/cli.js
CHANGED
|
@@ -3,15 +3,15 @@
|
|
|
3
3
|
// src/cli.ts
|
|
4
4
|
import { execSync } from "child_process";
|
|
5
5
|
import {
|
|
6
|
-
readFileSync as
|
|
7
|
-
writeFileSync as
|
|
8
|
-
unlinkSync as
|
|
6
|
+
readFileSync as readFileSync5,
|
|
7
|
+
writeFileSync as writeFileSync5,
|
|
8
|
+
unlinkSync as unlinkSync5,
|
|
9
9
|
mkdirSync,
|
|
10
10
|
renameSync as renameSync2,
|
|
11
|
-
existsSync as
|
|
12
|
-
rmSync as
|
|
11
|
+
existsSync as existsSync5,
|
|
12
|
+
rmSync as rmSync3
|
|
13
13
|
} from "fs";
|
|
14
|
-
import { resolve as
|
|
14
|
+
import { resolve as resolve4, join as join5 } from "path";
|
|
15
15
|
|
|
16
16
|
// src/install.ts
|
|
17
17
|
import spawn from "cross-spawn";
|
|
@@ -73,17 +73,17 @@ var DEFAULTS = {
|
|
|
73
73
|
agentsMd: true
|
|
74
74
|
};
|
|
75
75
|
async function ask(rl, question, defaultVal) {
|
|
76
|
-
return new Promise((
|
|
76
|
+
return new Promise((resolve5) => {
|
|
77
77
|
const suffix = defaultVal ? "(Y/n)" : "(y/N)";
|
|
78
78
|
rl.question(` ${question} ${suffix} `, (answer) => {
|
|
79
79
|
const a = answer.trim().toLowerCase();
|
|
80
|
-
if (a === "")
|
|
81
|
-
else
|
|
80
|
+
if (a === "") resolve5(defaultVal);
|
|
81
|
+
else resolve5(a === "y" || a === "yes");
|
|
82
82
|
});
|
|
83
83
|
});
|
|
84
84
|
}
|
|
85
85
|
async function askChoice(rl, question, choices, defaultIdx = 0) {
|
|
86
|
-
return new Promise((
|
|
86
|
+
return new Promise((resolve5) => {
|
|
87
87
|
console.log(` ${question}`);
|
|
88
88
|
for (let i = 0; i < choices.length; i++) {
|
|
89
89
|
const marker = i === defaultIdx ? "\u276F" : " ";
|
|
@@ -91,7 +91,7 @@ async function askChoice(rl, question, choices, defaultIdx = 0) {
|
|
|
91
91
|
}
|
|
92
92
|
rl.question(" Choice (number): ", (answer) => {
|
|
93
93
|
const n = parseInt(answer.trim(), 10);
|
|
94
|
-
|
|
94
|
+
resolve5(n >= 1 && n <= choices.length ? n - 1 : defaultIdx);
|
|
95
95
|
});
|
|
96
96
|
});
|
|
97
97
|
}
|
|
@@ -133,7 +133,7 @@ import {
|
|
|
133
133
|
} from "fs";
|
|
134
134
|
import { dirname, join, resolve } from "path";
|
|
135
135
|
import { fileURLToPath } from "url";
|
|
136
|
-
var
|
|
136
|
+
var __dirname2 = dirname(fileURLToPath(import.meta.url));
|
|
137
137
|
var VALID_BACKENDS = ["node"];
|
|
138
138
|
function parseBackendFlags(args) {
|
|
139
139
|
const out = [];
|
|
@@ -167,7 +167,7 @@ var BACKEND_CONFIG = {
|
|
|
167
167
|
}
|
|
168
168
|
};
|
|
169
169
|
function getServiceTemplateDir(kind) {
|
|
170
|
-
return resolve(
|
|
170
|
+
return resolve(__dirname2, "../templates/services", BACKEND_CONFIG[kind].templateDir);
|
|
171
171
|
}
|
|
172
172
|
function substituteTmpls(dir, projectName) {
|
|
173
173
|
for (const entry of readdirSync(dir)) {
|
|
@@ -265,24 +265,143 @@ function scaffoldServices(options) {
|
|
|
265
265
|
}
|
|
266
266
|
}
|
|
267
267
|
|
|
268
|
-
// src/
|
|
268
|
+
// src/scaffold-surface.ts
|
|
269
269
|
import {
|
|
270
|
-
existsSync as existsSync3,
|
|
271
270
|
cpSync as cpSync2,
|
|
272
|
-
|
|
273
|
-
writeFileSync as writeFileSync3,
|
|
274
|
-
renameSync,
|
|
275
|
-
unlinkSync as unlinkSync3,
|
|
271
|
+
existsSync as existsSync2,
|
|
276
272
|
readdirSync as readdirSync2,
|
|
273
|
+
readFileSync as readFileSync2,
|
|
277
274
|
rmSync,
|
|
278
|
-
statSync as statSync2
|
|
275
|
+
statSync as statSync2,
|
|
276
|
+
unlinkSync as unlinkSync2,
|
|
277
|
+
writeFileSync as writeFileSync2
|
|
278
|
+
} from "fs";
|
|
279
|
+
import { join as join2, resolve as resolve2 } from "path";
|
|
280
|
+
var VALID_SURFACES = ["web", "tui", "desktop"];
|
|
281
|
+
var WEB_ONLY_DEPS = ["@theokit/ui", "@usetheo/ui", "lucide-react", "react-router"];
|
|
282
|
+
var WEB_ONLY_DEV_DEPS = ["tailwindcss", "@tailwindcss/vite", "tailwindcss-animate"];
|
|
283
|
+
var WEB_ONLY_FILES = ["app", "index.html", "tailwind.config.ts", "postcss.config.js"];
|
|
284
|
+
var SURFACE_CONFIG = {
|
|
285
|
+
tui: {
|
|
286
|
+
fragment: "tui",
|
|
287
|
+
deps: { ink: "^5.1.0" },
|
|
288
|
+
devDeps: { tsx: "^4.19.0" },
|
|
289
|
+
scripts: { dev: "tsx tui/main.tsx", start: "tsx tui/main.tsx" },
|
|
290
|
+
tsconfigInclude: ["tui/**/*.ts", "tui/**/*.tsx", "server/**/*.ts", "agents/**/*.ts"]
|
|
291
|
+
},
|
|
292
|
+
desktop: {
|
|
293
|
+
fragment: "desktop",
|
|
294
|
+
// The webview consumes the agent via `createAgentClient` from the React-FREE `theokit/client/core`
|
|
295
|
+
// (M44). It is bundled by Vite; the sidecar runs via tsx.
|
|
296
|
+
deps: {},
|
|
297
|
+
devDeps: { tsx: "^4.19.0", "@tauri-apps/cli": "^2.0.0", vite: "^6.0.0" },
|
|
298
|
+
scripts: {
|
|
299
|
+
dev: "tauri dev",
|
|
300
|
+
tauri: "tauri",
|
|
301
|
+
"build:frontend": "vite build frontend",
|
|
302
|
+
"build:sidecar": 'tsx --version >/dev/null && echo "sidecar runs via tsx (see src-tauri/tauri.conf.json)"'
|
|
303
|
+
},
|
|
304
|
+
tsconfigInclude: [
|
|
305
|
+
"sidecar/**/*.ts",
|
|
306
|
+
"frontend/src/**/*.ts",
|
|
307
|
+
"server/**/*.ts",
|
|
308
|
+
"agents/**/*.ts"
|
|
309
|
+
]
|
|
310
|
+
}
|
|
311
|
+
};
|
|
312
|
+
function parseSurfaceFlags(args) {
|
|
313
|
+
let surface = "web";
|
|
314
|
+
for (let i = 0; i < args.length; i++) {
|
|
315
|
+
const a = args[i] ?? "";
|
|
316
|
+
let value;
|
|
317
|
+
if (a === "--surface") {
|
|
318
|
+
value = args[i + 1];
|
|
319
|
+
i++;
|
|
320
|
+
} else if (a.startsWith("--surface=")) {
|
|
321
|
+
value = a.slice("--surface=".length);
|
|
322
|
+
}
|
|
323
|
+
if (value === void 0) continue;
|
|
324
|
+
if (!VALID_SURFACES.includes(value)) {
|
|
325
|
+
throw new Error(
|
|
326
|
+
`unknown --surface value: '${value}'. Valid options: ${VALID_SURFACES.join(", ")}.`
|
|
327
|
+
);
|
|
328
|
+
}
|
|
329
|
+
surface = value;
|
|
330
|
+
}
|
|
331
|
+
return surface;
|
|
332
|
+
}
|
|
333
|
+
function getSurfaceFragmentDir(fragment) {
|
|
334
|
+
return resolve2(__dirname, "../templates/surfaces", fragment);
|
|
335
|
+
}
|
|
336
|
+
function substituteTmpls2(dir, projectName) {
|
|
337
|
+
for (const entry of readdirSync2(dir)) {
|
|
338
|
+
const full = join2(dir, entry);
|
|
339
|
+
if (statSync2(full).isDirectory()) {
|
|
340
|
+
substituteTmpls2(full, projectName);
|
|
341
|
+
continue;
|
|
342
|
+
}
|
|
343
|
+
if (entry.endsWith(".tmpl")) {
|
|
344
|
+
const content = readFileSync2(full, "utf-8").replace(/\{\{name\}\}/g, projectName);
|
|
345
|
+
writeFileSync2(full.replace(/\.tmpl$/, ""), content);
|
|
346
|
+
unlinkSync2(full);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
function applySurface(options) {
|
|
351
|
+
if (options._testForceError !== void 0) {
|
|
352
|
+
throw new Error(`Forced surface failure: ${options._testForceError}`);
|
|
353
|
+
}
|
|
354
|
+
if (options.surface === "web") return;
|
|
355
|
+
const cfg = SURFACE_CONFIG[options.surface];
|
|
356
|
+
const src = getSurfaceFragmentDir(cfg.fragment);
|
|
357
|
+
if (!existsSync2(src)) {
|
|
358
|
+
throw new Error(`surface fragment not found: ${src}`);
|
|
359
|
+
}
|
|
360
|
+
cpSync2(src, options.targetDir, { recursive: true });
|
|
361
|
+
substituteTmpls2(options.targetDir, options.projectName);
|
|
362
|
+
for (const rel of WEB_ONLY_FILES) {
|
|
363
|
+
const p = join2(options.targetDir, rel);
|
|
364
|
+
if (existsSync2(p)) rmSync(p, { recursive: true, force: true });
|
|
365
|
+
}
|
|
366
|
+
const pkgPath = join2(options.targetDir, "package.json");
|
|
367
|
+
if (existsSync2(pkgPath)) {
|
|
368
|
+
const pkg = JSON.parse(readFileSync2(pkgPath, "utf-8"));
|
|
369
|
+
pkg.dependencies = { ...dropKeys(pkg.dependencies, WEB_ONLY_DEPS), ...cfg.deps };
|
|
370
|
+
pkg.devDependencies = { ...dropKeys(pkg.devDependencies, WEB_ONLY_DEV_DEPS), ...cfg.devDeps };
|
|
371
|
+
pkg.scripts = { ...pkg.scripts ?? {}, ...cfg.scripts };
|
|
372
|
+
writeFileSync2(pkgPath, `${JSON.stringify(pkg, null, 2)}
|
|
373
|
+
`);
|
|
374
|
+
}
|
|
375
|
+
const tsconfigPath = join2(options.targetDir, "tsconfig.json");
|
|
376
|
+
if (existsSync2(tsconfigPath)) {
|
|
377
|
+
const tsconfig = JSON.parse(readFileSync2(tsconfigPath, "utf-8"));
|
|
378
|
+
tsconfig.include = cfg.tsconfigInclude;
|
|
379
|
+
writeFileSync2(tsconfigPath, `${JSON.stringify(tsconfig, null, 2)}
|
|
380
|
+
`);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
function dropKeys(record, keys) {
|
|
384
|
+
return Object.fromEntries(Object.entries(record ?? {}).filter(([k]) => !keys.includes(k)));
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// src/index.ts
|
|
388
|
+
import {
|
|
389
|
+
existsSync as existsSync4,
|
|
390
|
+
cpSync as cpSync3,
|
|
391
|
+
readFileSync as readFileSync4,
|
|
392
|
+
writeFileSync as writeFileSync4,
|
|
393
|
+
renameSync,
|
|
394
|
+
unlinkSync as unlinkSync4,
|
|
395
|
+
readdirSync as readdirSync3,
|
|
396
|
+
rmSync as rmSync2,
|
|
397
|
+
statSync as statSync3
|
|
279
398
|
} from "fs";
|
|
280
|
-
import { resolve as
|
|
399
|
+
import { resolve as resolve3, join as join4, dirname as dirname2 } from "path";
|
|
281
400
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
282
401
|
|
|
283
402
|
// src/bare-transform.ts
|
|
284
|
-
import { existsSync as
|
|
285
|
-
import { join as
|
|
403
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3, unlinkSync as unlinkSync3 } from "fs";
|
|
404
|
+
import { join as join3 } from "path";
|
|
286
405
|
var HELLO_PAGE = `export default function Page() {
|
|
287
406
|
return <h1>Hello Theo</h1>
|
|
288
407
|
}
|
|
@@ -291,9 +410,9 @@ function applyBareTransform(targetDir, options = {}) {
|
|
|
291
410
|
if (options._testForceError) {
|
|
292
411
|
throw new Error(`Forced transform failure: ${options._testForceError}`);
|
|
293
412
|
}
|
|
294
|
-
const pkgPath =
|
|
295
|
-
if (
|
|
296
|
-
const pkg = JSON.parse(
|
|
413
|
+
const pkgPath = join3(targetDir, "package.json");
|
|
414
|
+
if (existsSync3(pkgPath)) {
|
|
415
|
+
const pkg = JSON.parse(readFileSync3(pkgPath, "utf-8"));
|
|
297
416
|
if (pkg.dependencies) {
|
|
298
417
|
delete pkg.dependencies["@theokit/ui"];
|
|
299
418
|
delete pkg.dependencies["@theokit/sdk"];
|
|
@@ -306,31 +425,31 @@ function applyBareTransform(targetDir, options = {}) {
|
|
|
306
425
|
delete pkg.devDependencies.postcss;
|
|
307
426
|
delete pkg.devDependencies.autoprefixer;
|
|
308
427
|
}
|
|
309
|
-
|
|
428
|
+
writeFileSync3(pkgPath, `${JSON.stringify(pkg, null, 2)}
|
|
310
429
|
`);
|
|
311
430
|
}
|
|
312
|
-
const pagePath =
|
|
313
|
-
if (
|
|
314
|
-
|
|
431
|
+
const pagePath = join3(targetDir, "app/page.tsx");
|
|
432
|
+
if (existsSync3(pagePath)) {
|
|
433
|
+
writeFileSync3(pagePath, HELLO_PAGE);
|
|
315
434
|
}
|
|
316
|
-
const chatPath =
|
|
317
|
-
if (
|
|
318
|
-
|
|
435
|
+
const chatPath = join3(targetDir, "agents/chat.ts");
|
|
436
|
+
if (existsSync3(chatPath)) {
|
|
437
|
+
unlinkSync3(chatPath);
|
|
319
438
|
}
|
|
320
|
-
const tailwindCfg =
|
|
321
|
-
if (
|
|
322
|
-
|
|
439
|
+
const tailwindCfg = join3(targetDir, "tailwind.config.ts");
|
|
440
|
+
if (existsSync3(tailwindCfg)) {
|
|
441
|
+
unlinkSync3(tailwindCfg);
|
|
323
442
|
}
|
|
324
|
-
const postcssCfg =
|
|
325
|
-
if (
|
|
326
|
-
|
|
443
|
+
const postcssCfg = join3(targetDir, "postcss.config.js");
|
|
444
|
+
if (existsSync3(postcssCfg)) {
|
|
445
|
+
unlinkSync3(postcssCfg);
|
|
327
446
|
}
|
|
328
447
|
}
|
|
329
448
|
|
|
330
449
|
// src/index.ts
|
|
331
|
-
var
|
|
450
|
+
var __dirname3 = dirname2(fileURLToPath2(import.meta.url));
|
|
332
451
|
function getTemplateDir(templateName = "default") {
|
|
333
|
-
return
|
|
452
|
+
return resolve3(__dirname3, "../templates", templateName);
|
|
334
453
|
}
|
|
335
454
|
function isValidProjectName(name) {
|
|
336
455
|
return /^[a-z0-9][a-z0-9._-]*$/.test(name);
|
|
@@ -342,42 +461,42 @@ function scaffold(targetDir, projectName, templateName = "default", options = {}
|
|
|
342
461
|
);
|
|
343
462
|
}
|
|
344
463
|
const templateDir = getTemplateDir(templateName);
|
|
345
|
-
if (!
|
|
464
|
+
if (!existsSync4(templateDir)) {
|
|
346
465
|
throw new Error(`Template "${templateName}" not found. Available templates: default`);
|
|
347
466
|
}
|
|
348
|
-
const resolvedName = projectName === "." ?
|
|
467
|
+
const resolvedName = projectName === "." ? resolve3(targetDir).split("/").pop() ?? "my-app" : projectName;
|
|
349
468
|
if (!isValidProjectName(resolvedName)) {
|
|
350
469
|
throw new Error(
|
|
351
470
|
`Invalid project name "${resolvedName}". Use lowercase letters, numbers, hyphens, and dots. Must start with a letter or number.`
|
|
352
471
|
);
|
|
353
472
|
}
|
|
354
|
-
if (
|
|
355
|
-
const contents =
|
|
473
|
+
if (existsSync4(targetDir)) {
|
|
474
|
+
const contents = readdirSync3(targetDir);
|
|
356
475
|
if (contents.length > 0) {
|
|
357
476
|
throw new Error(`Directory "${targetDir}" is not empty. Please use an empty directory.`);
|
|
358
477
|
}
|
|
359
478
|
}
|
|
360
|
-
|
|
361
|
-
const gitignoreSrc =
|
|
362
|
-
const gitignoreDest =
|
|
363
|
-
if (
|
|
479
|
+
cpSync3(templateDir, targetDir, { recursive: true });
|
|
480
|
+
const gitignoreSrc = join4(targetDir, "_gitignore");
|
|
481
|
+
const gitignoreDest = join4(targetDir, ".gitignore");
|
|
482
|
+
if (existsSync4(gitignoreSrc)) {
|
|
364
483
|
renameSync(gitignoreSrc, gitignoreDest);
|
|
365
484
|
}
|
|
366
|
-
const dotClaudeSrc =
|
|
367
|
-
const dotClaudeDest =
|
|
368
|
-
if (
|
|
485
|
+
const dotClaudeSrc = join4(targetDir, "dot-claude");
|
|
486
|
+
const dotClaudeDest = join4(targetDir, ".claude");
|
|
487
|
+
if (existsSync4(dotClaudeSrc)) {
|
|
369
488
|
renameSync(dotClaudeSrc, dotClaudeDest);
|
|
370
489
|
}
|
|
371
|
-
for (const entry of
|
|
490
|
+
for (const entry of readdirSync3(targetDir)) {
|
|
372
491
|
if (!entry.endsWith(".tmpl")) continue;
|
|
373
|
-
const src =
|
|
374
|
-
const stat =
|
|
492
|
+
const src = join4(targetDir, entry);
|
|
493
|
+
const stat = statSync3(src);
|
|
375
494
|
if (!stat.isFile()) continue;
|
|
376
|
-
const dst =
|
|
377
|
-
const content =
|
|
495
|
+
const dst = join4(targetDir, entry.slice(0, -".tmpl".length));
|
|
496
|
+
const content = readFileSync4(src, "utf-8");
|
|
378
497
|
const replaced = content.replace(/\{\{name\}\}/g, resolvedName);
|
|
379
|
-
|
|
380
|
-
|
|
498
|
+
writeFileSync4(dst, replaced);
|
|
499
|
+
unlinkSync4(src);
|
|
381
500
|
}
|
|
382
501
|
if (options.bare) {
|
|
383
502
|
try {
|
|
@@ -385,7 +504,7 @@ function scaffold(targetDir, projectName, templateName = "default", options = {}
|
|
|
385
504
|
_testForceError: options._testForceTransformError
|
|
386
505
|
});
|
|
387
506
|
} catch (err) {
|
|
388
|
-
|
|
507
|
+
rmSync2(targetDir, { recursive: true, force: true });
|
|
389
508
|
const original = err instanceof Error ? err.message : String(err);
|
|
390
509
|
throw new Error(
|
|
391
510
|
`Scaffold rolled back: bare transform failed. Check filesystem perms.
|
|
@@ -415,6 +534,7 @@ function showHelp() {
|
|
|
415
534
|
-v, --version Show version number
|
|
416
535
|
--yes Use recommended defaults (skip prompts)
|
|
417
536
|
--template=<name> Template to use (default: "default")
|
|
537
|
+
--surface=<web|tui|desktop> App surface (default: "web") \u2014 tui (Ink) / desktop (Tauri)
|
|
418
538
|
--bare Minimal app (no @theokit/* deps)
|
|
419
539
|
--skip-install Scaffold files only, skip package install
|
|
420
540
|
--disable-git Skip git init
|
|
@@ -431,6 +551,8 @@ function showHelp() {
|
|
|
431
551
|
npx create-theokit my-app --yes
|
|
432
552
|
npx create-theokit my-app
|
|
433
553
|
npx create-theokit my-app --bare --skip-install
|
|
554
|
+
npx create-theokit my-app --surface=tui
|
|
555
|
+
npx create-theokit my-app --surface=desktop
|
|
434
556
|
npx create-theokit my-app --use-bun --biome
|
|
435
557
|
npx create-theokit my-app --example=https://github.com/user/repo
|
|
436
558
|
npx create-theokit my-app --import-alias="~/*"
|
|
@@ -480,7 +602,23 @@ async function main() {
|
|
|
480
602
|
console.error(err instanceof Error ? err.message : String(err));
|
|
481
603
|
process.exit(1);
|
|
482
604
|
}
|
|
483
|
-
|
|
605
|
+
let surface = "web";
|
|
606
|
+
try {
|
|
607
|
+
surface = parseSurfaceFlags(args);
|
|
608
|
+
} catch (err) {
|
|
609
|
+
console.error("");
|
|
610
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
611
|
+
process.exit(1);
|
|
612
|
+
}
|
|
613
|
+
if (bare && surface !== "web") {
|
|
614
|
+
console.error(
|
|
615
|
+
`
|
|
616
|
+
--bare cannot be combined with --surface=${surface} (the surface needs the agent deps).
|
|
617
|
+
`
|
|
618
|
+
);
|
|
619
|
+
process.exit(1);
|
|
620
|
+
}
|
|
621
|
+
const targetDir = resolve4(process.cwd(), projectName);
|
|
484
622
|
if (exampleFlag) {
|
|
485
623
|
try {
|
|
486
624
|
console.log(`
|
|
@@ -517,13 +655,15 @@ Cloning example "${exampleFlag}" into ${projectName}...
|
|
|
517
655
|
}
|
|
518
656
|
try {
|
|
519
657
|
const suffix = bare ? " [--bare]" : "";
|
|
658
|
+
const surfaceSuffix = surface !== "web" ? ` [surface: ${surface}]` : "";
|
|
520
659
|
const backendsSuffix = backends.length > 0 ? ` [+services: ${backends.join(", ")}]` : "";
|
|
521
660
|
console.log(
|
|
522
661
|
`
|
|
523
|
-
Creating TheoKit project "${projectName}" (template: ${templateName})${suffix}${backendsSuffix}...
|
|
662
|
+
Creating TheoKit project "${projectName}" (template: ${templateName})${suffix}${surfaceSuffix}${backendsSuffix}...
|
|
524
663
|
`
|
|
525
664
|
);
|
|
526
665
|
scaffold(targetDir, projectName, templateName, { bare });
|
|
666
|
+
applySurfaceStep(targetDir, projectName, surface, options);
|
|
527
667
|
applyOptions(targetDir, options, { importAlias, useBiome });
|
|
528
668
|
if (backends.length > 0) {
|
|
529
669
|
scaffoldServices({ targetDir, projectName, backends });
|
|
@@ -555,12 +695,25 @@ Creating TheoKit project "${projectName}" (template: ${templateName})${suffix}${
|
|
|
555
695
|
process.exit(1);
|
|
556
696
|
}
|
|
557
697
|
}
|
|
698
|
+
function applySurfaceStep(targetDir, projectName, surface, options) {
|
|
699
|
+
if (surface === "web") return;
|
|
700
|
+
try {
|
|
701
|
+
applySurface({ targetDir, projectName, surface });
|
|
702
|
+
} catch (err) {
|
|
703
|
+
rmSync3(targetDir, { recursive: true, force: true });
|
|
704
|
+
const original = err instanceof Error ? err.message : String(err);
|
|
705
|
+
throw new Error(`Scaffold rolled back: surface transform failed.
|
|
706
|
+
Original error: ${original}`);
|
|
707
|
+
}
|
|
708
|
+
options.tailwind = false;
|
|
709
|
+
options.srcDir = false;
|
|
710
|
+
}
|
|
558
711
|
function applyOptions(targetDir, options, opts) {
|
|
559
|
-
const pkgPath =
|
|
712
|
+
const pkgPath = resolve4(targetDir, "package.json");
|
|
560
713
|
if (opts.useBiome) {
|
|
561
|
-
const eslintPath =
|
|
562
|
-
if (
|
|
563
|
-
const pkg = JSON.parse(
|
|
714
|
+
const eslintPath = resolve4(targetDir, "eslint.config.mjs");
|
|
715
|
+
if (existsSync5(eslintPath)) unlinkSync5(eslintPath);
|
|
716
|
+
const pkg = JSON.parse(readFileSync5(pkgPath, "utf-8"));
|
|
564
717
|
delete pkg.devDependencies.eslint;
|
|
565
718
|
delete pkg.devDependencies["eslint-config-prettier"];
|
|
566
719
|
delete pkg.devDependencies["typescript-eslint"];
|
|
@@ -569,9 +722,9 @@ function applyOptions(targetDir, options, opts) {
|
|
|
569
722
|
pkg.scripts["lint:fix"] = "biome check . --fix";
|
|
570
723
|
pkg.scripts.format = "biome format . --write";
|
|
571
724
|
pkg.scripts["format:check"] = "biome format .";
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
725
|
+
writeFileSync5(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
|
|
726
|
+
writeFileSync5(
|
|
727
|
+
resolve4(targetDir, "biome.json"),
|
|
575
728
|
JSON.stringify(
|
|
576
729
|
{
|
|
577
730
|
$schema: "https://biomejs.dev/schemas/1.9.0/schema.json",
|
|
@@ -584,45 +737,45 @@ function applyOptions(targetDir, options, opts) {
|
|
|
584
737
|
) + "\n"
|
|
585
738
|
);
|
|
586
739
|
} else if (!options.eslint) {
|
|
587
|
-
const eslintPath =
|
|
588
|
-
if (
|
|
589
|
-
const pkg = JSON.parse(
|
|
740
|
+
const eslintPath = resolve4(targetDir, "eslint.config.mjs");
|
|
741
|
+
if (existsSync5(eslintPath)) unlinkSync5(eslintPath);
|
|
742
|
+
const pkg = JSON.parse(readFileSync5(pkgPath, "utf-8"));
|
|
590
743
|
delete pkg.scripts.lint;
|
|
591
744
|
delete pkg.scripts["lint:fix"];
|
|
592
745
|
delete pkg.devDependencies.eslint;
|
|
593
746
|
delete pkg.devDependencies["eslint-config-prettier"];
|
|
594
747
|
delete pkg.devDependencies["typescript-eslint"];
|
|
595
|
-
|
|
748
|
+
writeFileSync5(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
|
|
596
749
|
}
|
|
597
750
|
if (options.tailwind) {
|
|
598
|
-
const pkg = JSON.parse(
|
|
751
|
+
const pkg = JSON.parse(readFileSync5(pkgPath, "utf-8"));
|
|
599
752
|
pkg.devDependencies = pkg.devDependencies ?? {};
|
|
600
753
|
pkg.devDependencies.tailwindcss = "^4.0.0";
|
|
601
754
|
pkg.devDependencies["@tailwindcss/vite"] = "^4.0.0";
|
|
602
|
-
|
|
603
|
-
const cssDir =
|
|
604
|
-
const cssPath =
|
|
605
|
-
const existing =
|
|
606
|
-
|
|
755
|
+
writeFileSync5(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
|
|
756
|
+
const cssDir = existsSync5(resolve4(targetDir, "src/app")) ? "src/app" : "app";
|
|
757
|
+
const cssPath = resolve4(targetDir, `${cssDir}/globals.css`);
|
|
758
|
+
const existing = existsSync5(cssPath) ? readFileSync5(cssPath, "utf-8") : "";
|
|
759
|
+
writeFileSync5(cssPath, '@import "tailwindcss";\n\n' + existing);
|
|
607
760
|
}
|
|
608
761
|
if (options.srcDir) {
|
|
609
|
-
const srcDir =
|
|
762
|
+
const srcDir = resolve4(targetDir, "src");
|
|
610
763
|
mkdirSync(srcDir, { recursive: true });
|
|
611
764
|
for (const dir of ["app", "server"]) {
|
|
612
|
-
const from =
|
|
613
|
-
const to =
|
|
614
|
-
if (
|
|
765
|
+
const from = resolve4(targetDir, dir);
|
|
766
|
+
const to = resolve4(srcDir, dir);
|
|
767
|
+
if (existsSync5(from)) renameSync2(from, to);
|
|
615
768
|
}
|
|
616
|
-
const configFrom =
|
|
617
|
-
if (
|
|
618
|
-
const tscPath2 =
|
|
619
|
-
const tsc2 = JSON.parse(
|
|
769
|
+
const configFrom = resolve4(targetDir, "theo.config.ts");
|
|
770
|
+
if (existsSync5(configFrom)) renameSync2(configFrom, resolve4(srcDir, "theo.config.ts"));
|
|
771
|
+
const tscPath2 = resolve4(targetDir, "tsconfig.json");
|
|
772
|
+
const tsc2 = JSON.parse(readFileSync5(tscPath2, "utf-8"));
|
|
620
773
|
tsc2.compilerOptions.baseUrl = "src";
|
|
621
774
|
tsc2.include = ["src/**/*.ts", "src/**/*.tsx"];
|
|
622
|
-
|
|
775
|
+
writeFileSync5(tscPath2, JSON.stringify(tsc2, null, 2) + "\n");
|
|
623
776
|
}
|
|
624
|
-
const tscPath =
|
|
625
|
-
const tsc = JSON.parse(
|
|
777
|
+
const tscPath = resolve4(targetDir, "tsconfig.json");
|
|
778
|
+
const tsc = JSON.parse(readFileSync5(tscPath, "utf-8"));
|
|
626
779
|
if (!options.aliases) {
|
|
627
780
|
delete tsc.compilerOptions.baseUrl;
|
|
628
781
|
delete tsc.compilerOptions.paths;
|
|
@@ -634,10 +787,10 @@ function applyOptions(targetDir, options, opts) {
|
|
|
634
787
|
[`${prefix}/app/*`]: ["./app/*"]
|
|
635
788
|
};
|
|
636
789
|
}
|
|
637
|
-
|
|
790
|
+
writeFileSync5(tscPath, JSON.stringify(tsc, null, 2) + "\n");
|
|
638
791
|
if (!options.agentsMd) {
|
|
639
|
-
const agentsPath =
|
|
640
|
-
if (
|
|
792
|
+
const agentsPath = resolve4(targetDir, "AGENTS.md");
|
|
793
|
+
if (existsSync5(agentsPath)) unlinkSync5(agentsPath);
|
|
641
794
|
}
|
|
642
795
|
}
|
|
643
796
|
function initGit(targetDir) {
|
|
@@ -656,7 +809,7 @@ function cloneExample(example, targetDir) {
|
|
|
656
809
|
const repo = isUrl ? example : `https://github.com/usetheodev/theokit-examples/tree/main/${example}`;
|
|
657
810
|
if (isUrl) {
|
|
658
811
|
execSync(`git clone --depth 1 ${repo} ${targetDir}`, { stdio: "inherit" });
|
|
659
|
-
|
|
812
|
+
rmSync3(join5(targetDir, ".git"), { recursive: true, force: true });
|
|
660
813
|
} else {
|
|
661
814
|
try {
|
|
662
815
|
execSync(`npx --yes degit usetheodev/theokit-examples/${example} ${targetDir}`, {
|