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