@taujs/create-taujs 0.2.0 → 0.3.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.
Files changed (2) hide show
  1. package/dist/index.js +329 -44
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  // src/index.ts
4
4
  import { execSync } from "child_process";
5
+ import { pathToFileURL } from "url";
5
6
  import fs from "fs-extra";
6
7
  import path from "path";
7
8
  import pc from "picocolors";
@@ -44,16 +45,27 @@ var PACKAGE_MANAGERS = {
44
45
  pnpm: "pnpm install",
45
46
  yarn: "yarn install"
46
47
  };
48
+ var FRAMEWORKS = ["react", "vue"];
47
49
  function parseArgs() {
48
50
  const rawArgs = process.argv.slice(2);
49
51
  let projectName;
50
- for (const arg of rawArgs) {
52
+ let framework;
53
+ for (let i = 0; i < rawArgs.length; i++) {
54
+ const arg = rawArgs[i];
55
+ if (arg === "--framework") {
56
+ framework = rawArgs[++i];
57
+ continue;
58
+ }
59
+ if (arg.startsWith("--framework=")) {
60
+ framework = arg.slice("--framework=".length);
61
+ continue;
62
+ }
51
63
  if (!arg.startsWith("-") && !projectName) {
52
64
  projectName = arg;
53
65
  continue;
54
66
  }
55
67
  }
56
- return { projectName };
68
+ return { projectName, framework };
57
69
  }
58
70
  function validateProjectName(value) {
59
71
  if (!value) return "Project name is required";
@@ -62,9 +74,12 @@ function validateProjectName(value) {
62
74
  }
63
75
  return true;
64
76
  }
77
+ function validateFramework(value) {
78
+ return FRAMEWORKS.includes(value) ? true : `Framework must be one of: ${FRAMEWORKS.join(", ")}`;
79
+ }
65
80
  async function main() {
66
81
  console.log(pc.cyan("\nWelcome to \u03C4js (taujs)\n"));
67
- const { projectName: argName } = parseArgs();
82
+ const { projectName: argName, framework: argFramework } = parseArgs();
68
83
  if (argName) {
69
84
  const res = validateProjectName(argName);
70
85
  if (res !== true) {
@@ -73,6 +88,14 @@ async function main() {
73
88
  process.exit(1);
74
89
  }
75
90
  }
91
+ if (argFramework) {
92
+ const res = validateFramework(argFramework);
93
+ if (res !== true) {
94
+ console.log(pc.red(`
95
+ \u2716 Invalid framework "${argFramework}": ${res}`));
96
+ process.exit(1);
97
+ }
98
+ }
76
99
  const questions = [
77
100
  {
78
101
  type: argName ? null : "text",
@@ -81,6 +104,16 @@ async function main() {
81
104
  initial: "my-taujs-app",
82
105
  validate: validateProjectName
83
106
  },
107
+ {
108
+ type: argFramework ? null : "select",
109
+ name: "framework",
110
+ message: "Framework:",
111
+ choices: [
112
+ { title: "React", value: "react" },
113
+ { title: "Vue", value: "vue" }
114
+ ],
115
+ initial: 0
116
+ },
84
117
  {
85
118
  type: "select",
86
119
  name: "packageManager",
@@ -116,10 +149,18 @@ async function main() {
116
149
  console.log(pc.red("\n\u2716 Project name is required"));
117
150
  process.exit(1);
118
151
  }
152
+ const framework = argFramework ?? answers.framework;
153
+ const frameworkRes = validateFramework(framework);
154
+ if (frameworkRes !== true) {
155
+ console.log(pc.red(`
156
+ \u2716 Invalid framework "${framework}": ${frameworkRes}`));
157
+ process.exit(1);
158
+ }
119
159
  const config = {
120
160
  projectName,
121
161
  packageManager: answers.packageManager,
122
- installDeps: answers.installDeps
162
+ installDeps: answers.installDeps,
163
+ framework
123
164
  };
124
165
  await createProject(config);
125
166
  }
@@ -174,30 +215,90 @@ async function createDirectoryStructure(targetDir) {
174
215
  await fs.ensureDir(path.join(targetDir, dir));
175
216
  }
176
217
  }
218
+ function planFiles(config) {
219
+ const { projectName, packageManager, framework } = config;
220
+ const shared = [
221
+ { path: "package.json", json: generatePackageJson(projectName, framework) },
222
+ { path: "build.ts", content: generateBuildTs() },
223
+ { path: "tsconfig.json", json: generateTsConfig(framework) },
224
+ { path: "src/server/tsconfig.json", json: generateServerTsConfig() },
225
+ { path: "taujs.config.ts", content: generateTaujsConfig(framework) },
226
+ { path: ".gitignore", content: generateGitignore() },
227
+ { path: "README.md", content: generateReadme(projectName, packageManager, framework) },
228
+ // Agent wiring (P1-04): pinned local-bin MCP config + a short CLAUDE.md pointer whose
229
+ // substance ships in @taujs/mcp.
230
+ { path: ".mcp.json", json: generateMcpJson(packageManager) },
231
+ { path: "CLAUDE.md", content: generateClaudeMd() },
232
+ { path: "src/client/index.html", content: generateIndexHtml() },
233
+ { path: "src/client/styles.css", content: generateStyles() },
234
+ // server (framework-independent — a single shared source)
235
+ { path: "src/server/index.ts", content: generateServerIndex() },
236
+ { path: "src/server/services/registry.ts", content: generateServiceRegistry() },
237
+ { path: "src/server/services/example.service.ts", content: generateExampleService() },
238
+ { path: "src/server/types.d.ts", content: generateServiceTypesAugmentation() },
239
+ { path: "src/client/public/favicon.svg", content: generateFavicon() }
240
+ ];
241
+ const client = framework === "vue" ? [
242
+ { path: "src/client/App.vue", content: generateAppVue() },
243
+ { path: "src/client/HomePage.vue", content: generateHomePageVue() },
244
+ { path: "src/client/StreamingPage.vue", content: generateStreamingPageVue() },
245
+ { path: "src/client/entry-client.ts", content: generateEntryClientVue() },
246
+ { path: "src/client/entry-server.ts", content: generateEntryServerVue() },
247
+ { path: "src/client/vite-env.d.ts", content: generateViteEnvVue() }
248
+ ] : [
249
+ { path: "src/client/App.tsx", content: generateAppComponent() },
250
+ { path: "src/client/entry-client.tsx", content: generateEntryClient() },
251
+ { path: "src/client/entry-server.tsx", content: generateEntryServer() },
252
+ { path: "src/client/vite-env.d.ts", content: generateViteEnv() }
253
+ ];
254
+ return [...shared, ...client];
255
+ }
177
256
  async function generateFiles(targetDir, config) {
178
- const { projectName, packageManager } = config;
179
- await fs.writeJSON(path.join(targetDir, "package.json"), generatePackageJson(projectName), { spaces: 2 });
180
- await fs.writeFile(path.join(targetDir, "build.ts"), generateBuildTs());
181
- await fs.writeJSON(path.join(targetDir, "tsconfig.json"), generateTsConfig(), { spaces: 2 });
182
- await fs.writeJSON(path.join(targetDir, "src/server/tsconfig.json"), generateServerTsConfig(), { spaces: 2 });
183
- await fs.writeFile(path.join(targetDir, "taujs.config.ts"), generateTaujsConfig());
184
- await fs.writeFile(path.join(targetDir, ".gitignore"), generateGitignore());
185
- await fs.writeFile(path.join(targetDir, "README.md"), generateReadme(projectName, packageManager));
186
- await fs.writeJSON(path.join(targetDir, ".mcp.json"), generateMcpJson(packageManager), { spaces: 2 });
187
- await fs.writeFile(path.join(targetDir, "CLAUDE.md"), generateClaudeMd());
188
- await fs.writeFile(path.join(targetDir, "src/client/index.html"), generateIndexHtml());
189
- await fs.writeFile(path.join(targetDir, "src/client/App.tsx"), generateAppComponent());
190
- await fs.writeFile(path.join(targetDir, "src/client/entry-client.tsx"), generateEntryClient());
191
- await fs.writeFile(path.join(targetDir, "src/client/entry-server.tsx"), generateEntryServer());
192
- await fs.writeFile(path.join(targetDir, "src/client/styles.css"), generateStyles());
193
- await fs.writeFile(path.join(targetDir, "src/client/vite-env.d.ts"), generateViteEnv());
194
- await fs.writeFile(path.join(targetDir, "src/server/index.ts"), generateServerIndex());
195
- await fs.writeFile(path.join(targetDir, "src/server/services/registry.ts"), generateServiceRegistry());
196
- await fs.writeFile(path.join(targetDir, "src/server/services/example.service.ts"), generateExampleService());
197
- await fs.writeFile(path.join(targetDir, "src/server/types.d.ts"), generateServiceTypesAugmentation());
198
- await fs.writeFile(path.join(targetDir, "src/client/public/favicon.svg"), generateFavicon());
199
- }
200
- function generatePackageJson(projectName) {
257
+ for (const entry of planFiles(config)) {
258
+ const full = path.join(targetDir, entry.path);
259
+ await fs.ensureDir(path.dirname(full));
260
+ if ("json" in entry) {
261
+ await fs.writeJSON(full, entry.json, { spaces: 2 });
262
+ } else {
263
+ await fs.writeFile(full, entry.content);
264
+ }
265
+ }
266
+ }
267
+ function generatePackageJson(projectName, framework) {
268
+ if (framework === "vue") {
269
+ return {
270
+ name: projectName,
271
+ version: "0.1.0",
272
+ private: true,
273
+ type: "module",
274
+ scripts: {
275
+ dev: "cross-env NODE_ENV=development tsx watch --ignore vite.config.ts --trace-warnings --tsconfig ./src/server/tsconfig.json ./src/server/index.ts --loglevel verbose",
276
+ "build:client": "tsx build.ts",
277
+ "build:entry-server": "cross-env BUILD_MODE=ssr tsx build.ts",
278
+ "build:server": "esbuild src/server/index.ts --bundle --platform=node --format=esm --outfile=dist/server/index.js --external:fastify --external:@taujs/server --external:@taujs/vue",
279
+ build: "tsx build.ts && cross-env BUILD_MODE=ssr tsx build.ts && esbuild src/server/index.ts --bundle --platform=node --format=esm --outfile=dist/server/index.js --external:fastify --external:@taujs/server --external:@taujs/vue",
280
+ start: "cross-env NODE_ENV=production node dist/server/index.js",
281
+ lint: "vue-tsc --noEmit"
282
+ },
283
+ dependencies: {
284
+ "@taujs/server": "latest",
285
+ "@taujs/vue": "latest",
286
+ "@vue/server-renderer": "^3.5.0",
287
+ fastify: "^5.8.5",
288
+ vue: "^3.5.0"
289
+ },
290
+ devDependencies: {
291
+ "@taujs/mcp": "latest",
292
+ "@types/node": "^22.10.5",
293
+ "@vitejs/plugin-vue": "^6.0.0",
294
+ "cross-env": "^7.0.3",
295
+ tsx: "^4.19.3",
296
+ typescript: "^5.7.3",
297
+ vite: "^7.1.11",
298
+ "vue-tsc": "^2.1.10"
299
+ }
300
+ };
301
+ }
201
302
  return {
202
303
  name: projectName,
203
304
  version: "0.1.0",
@@ -244,13 +345,14 @@ await taujsBuild({
244
345
  });
245
346
  `;
246
347
  }
247
- function generateTsConfig() {
348
+ function generateTsConfig(framework) {
248
349
  return {
249
350
  compilerOptions: {
250
351
  target: "ES2022",
251
352
  module: "ESNext",
252
353
  lib: ["ES2022", "DOM", "DOM.Iterable"],
253
- jsx: "react-jsx",
354
+ // Vue SFCs are typed by vue-tsc; React needs the automatic JSX runtime.
355
+ ...framework === "react" ? { jsx: "react-jsx" } : {},
254
356
  moduleResolution: "bundler",
255
357
  resolveJsonModule: true,
256
358
  allowImportingTsExtensions: true,
@@ -275,8 +377,12 @@ function generateServerTsConfig() {
275
377
  include: ["./**/*"]
276
378
  };
277
379
  }
278
- function generateTaujsConfig() {
279
- return `import { defineConfig } from '@taujs/server/config';
380
+ function generateTaujsConfig(framework) {
381
+ const pluginImport = framework === "vue" ? `
382
+ import { pluginVue } from '@taujs/vue/plugin';` : "";
383
+ const pluginsLine = framework === "vue" ? `
384
+ plugins: [pluginVue()],` : "";
385
+ return `import { defineConfig } from '@taujs/server/config';${pluginImport}
280
386
 
281
387
  export default defineConfig({
282
388
  server: {
@@ -287,7 +393,7 @@ export default defineConfig({
287
393
  apps: [
288
394
  {
289
395
  appId: 'main',
290
- entryPoint: '',
396
+ entryPoint: '',${pluginsLine}
291
397
  routes: [
292
398
  {
293
399
  path: '/',
@@ -366,8 +472,18 @@ coverage
366
472
  .cache
367
473
  `;
368
474
  }
369
- function generateReadme(projectName, packageManager) {
475
+ function generateReadme(projectName, packageManager, framework) {
370
476
  const pmRun = packageManager === "npm" ? "npm run" : packageManager;
477
+ const clientTree = framework === "vue" ? `\u2502 \u2502 \u251C\u2500\u2500 App.vue # Root component (route switch)
478
+ \u2502 \u2502 \u251C\u2500\u2500 HomePage.vue # SSR route (useSSRData + v-if)
479
+ \u2502 \u2502 \u251C\u2500\u2500 StreamingPage.vue # Streaming route (await useSSRDataAsync)
480
+ \u2502 \u2502 \u251C\u2500\u2500 entry-client.ts # Client hydration entry
481
+ \u2502 \u2502 \u251C\u2500\u2500 entry-server.ts # SSR render entry` : `\u2502 \u2502 \u251C\u2500\u2500 App.tsx # Root component
482
+ \u2502 \u2502 \u251C\u2500\u2500 entry-client.tsx # Client hydration entry
483
+ \u2502 \u2502 \u251C\u2500\u2500 entry-server.tsx # SSR render entry`;
484
+ const mainUi = framework === "vue" ? "App.vue" : "App.tsx";
485
+ const clientExt = framework === "vue" ? "ts" : "tsx";
486
+ const frameworkDoc = framework === "vue" ? "- [Vue Documentation](https://vuejs.org)" : "- [React Documentation](https://react.dev)";
371
487
  return `# ${projectName}
372
488
 
373
489
  A \u03C4js (taujs) application with server-side rendering, streaming, and a type-safe service layer.
@@ -400,9 +516,7 @@ ${pmRun} start
400
516
  ${projectName}/
401
517
  \u251C\u2500\u2500 src/
402
518
  \u2502 \u251C\u2500\u2500 client/
403
- \u2502 \u2502 \u251C\u2500\u2500 App.tsx # Root component
404
- \u2502 \u2502 \u251C\u2500\u2500 entry-client.tsx # Client hydration entry
405
- \u2502 \u2502 \u251C\u2500\u2500 entry-server.tsx # SSR render entry
519
+ ${clientTree}
406
520
  \u2502 \u2502 \u251C\u2500\u2500 styles.css # Global styles
407
521
  \u2502 \u2502 \u251C\u2500\u2500 vite-env.d.ts # Vite client types
408
522
  \u2502 \u2502 \u2514\u2500\u2500 public/
@@ -421,10 +535,10 @@ ${projectName}/
421
535
 
422
536
  ## Editing the App
423
537
 
424
- - Main UI: \`src/client/App.tsx\`
538
+ - Main UI: \`src/client/${mainUi}\`
425
539
  - Styles: \`src/client/styles.css\`
426
- - SSR entry: \`src/client/entry-server.tsx\`
427
- - Client entry: \`src/client/entry-client.tsx\`
540
+ - SSR entry: \`src/client/entry-server.${clientExt}\`
541
+ - Client entry: \`src/client/entry-client.${clientExt}\`
428
542
  - Routes: \`taujs.config.ts\`
429
543
  - Services: \`src/server/services/\`
430
544
 
@@ -432,7 +546,7 @@ ${projectName}/
432
546
 
433
547
  - [\u03C4js Documentation](https://taujs.dev)
434
548
  - [Fastify Documentation](https://fastify.dev)
435
- - [React Documentation](https://react.dev)
549
+ ${frameworkDoc}
436
550
 
437
551
  ## License
438
552
 
@@ -763,6 +877,165 @@ function generateViteEnv() {
763
877
  return `/// <reference types="vite/client" />
764
878
  `;
765
879
  }
880
+ function generateViteEnvVue() {
881
+ return `/// <reference types="vite/client" />
882
+
883
+ declare module '*.vue' {
884
+ import type { DefineComponent } from 'vue';
885
+ const component: DefineComponent<Record<string, unknown>, Record<string, unknown>, unknown>;
886
+ export default component;
887
+ }
888
+ `;
889
+ }
890
+ function generateAppVue() {
891
+ return `<script setup lang="ts">
892
+ import { computed } from 'vue';
893
+
894
+ import HomePage from './HomePage.vue';
895
+ import StreamingPage from './StreamingPage.vue';
896
+
897
+ import './styles.css';
898
+
899
+ const props = defineProps<{ location?: string; routeContext?: unknown }>();
900
+
901
+ // The server passes \`location\`; on the client fall back to the current path so hydration matches.
902
+ const path = computed(() => props.location ?? (typeof window !== 'undefined' ? window.location.pathname : '/'));
903
+ const isStreaming = computed(() => path.value.startsWith('/streaming'));
904
+ </script>
905
+
906
+ <template>
907
+ <div class="app">
908
+ <header class="app-header">
909
+ <h1 class="app-title">\u03C4js - Composing systems, not just apps</h1>
910
+ <p class="app-subtitle">Request-first application composition with explicit per-route rendering control.</p>
911
+ </header>
912
+
913
+ <Suspense v-if="isStreaming">
914
+ <template #default>
915
+ <StreamingPage />
916
+ </template>
917
+ <template #fallback>
918
+ <section class="card card--primary">
919
+ <p class="card-message">Loading greeting\u2026</p>
920
+ <p class="card-meta">Streaming data from the server.</p>
921
+ </section>
922
+ </template>
923
+ </Suspense>
924
+ <HomePage v-else />
925
+
926
+ <section class="section">
927
+ <h2 class="section-title">Quick start</h2>
928
+ <ul class="list">
929
+ <li>Edit <code>src/client/App.vue</code> to change this page.</li>
930
+ <li>Adjust styles in <code>src/client/styles.css</code>.</li>
931
+ <li>Configure routes in <code>taujs.config.ts</code>.</li>
932
+ <li>Visit <a href="/">/</a> for standard SSR and <a href="/streaming">/streaming</a> for streaming SSR.</li>
933
+ <li>Further information can be found at <a href="http://taujs.dev" target="_blank">\u03C4js Documentation and Guides</a>.</li>
934
+ </ul>
935
+ </section>
936
+
937
+ <section class="tip">
938
+ <p>
939
+ <strong>SSR:</strong> The <code>/</code> route resolves data on the server, then consumes it with
940
+ <code>useSSRData</code> + <code>v-if</code> (non-blocking fallback rendering).
941
+ </p>
942
+ <p>
943
+ <strong>STREAM:</strong> The <code>/streaming</code> route <code>await</code>s <code>useSSRDataAsync</code>
944
+ in async <code>setup</code> under <code>&lt;Suspense&gt;</code>, so the render blocks until data resolves.
945
+ </p>
946
+ </section>
947
+
948
+ <footer class="app-footer">
949
+ <p>
950
+ Built with
951
+ <a href="https://taujs.dev" target="_blank" rel="noopener">\u03C4js</a>
952
+ \xB7
953
+ <a href="https://fastify.dev" target="_blank" rel="noopener">Fastify</a>
954
+ \xB7
955
+ <a href="https://vuejs.org" target="_blank" rel="noopener">Vue</a>
956
+ </p>
957
+ </footer>
958
+ </div>
959
+ </template>
960
+ `;
961
+ }
962
+ function generateHomePageVue() {
963
+ return `<script setup lang="ts">
964
+ import { useSSRData } from '@taujs/vue';
965
+
966
+ type GreetingData = {
967
+ message: string;
968
+ timestamp: string;
969
+ };
970
+
971
+ // Fallback idiom: non-blocking; \`data\` is undefined until ready, guarded with v-if.
972
+ const data = useSSRData<GreetingData>();
973
+ </script>
974
+
975
+ <template>
976
+ <section v-if="data" class="card card--primary">
977
+ <p class="card-message">{{ data.message }}</p>
978
+ <p class="card-meta">Generated at: {{ new Date(data.timestamp).toLocaleString() }}</p>
979
+ </section>
980
+ <section v-else class="card card--primary">
981
+ <p class="card-message">Loading greeting\u2026</p>
982
+ <p class="card-meta">Resolving data on the server.</p>
983
+ </section>
984
+ </template>
985
+ `;
986
+ }
987
+ function generateStreamingPageVue() {
988
+ return `<script setup lang="ts">
989
+ import { useSSRDataAsync } from '@taujs/vue';
990
+
991
+ type GreetingData = {
992
+ message: string;
993
+ timestamp: string;
994
+ };
995
+
996
+ // Suspense idiom: async setup blocks on the data, so streamed routes deliver it in the payload.
997
+ const data = await useSSRDataAsync<GreetingData>();
998
+ </script>
999
+
1000
+ <template>
1001
+ <section class="card card--primary">
1002
+ <p class="card-message">{{ data.message }}</p>
1003
+ <p class="card-meta">Generated at: {{ new Date(data.timestamp).toLocaleString() }}</p>
1004
+ </section>
1005
+ </template>
1006
+ `;
1007
+ }
1008
+ function generateEntryClientVue() {
1009
+ return `import { hydrateApp } from '@taujs/vue';
1010
+
1011
+ import App from './App.vue';
1012
+
1013
+ hydrateApp({
1014
+ appComponent: App,
1015
+ rootElementId: 'root',
1016
+ enableDebug: import.meta.env.DEV,
1017
+ });
1018
+ `;
1019
+ }
1020
+ function generateEntryServerVue() {
1021
+ return `import { createRenderer } from '@taujs/vue';
1022
+
1023
+ import App from './App.vue';
1024
+
1025
+ export const { renderSSR, renderStream } = createRenderer({
1026
+ appComponent: App,
1027
+ headContent: ({ data, meta }) => \`
1028
+ <title>\${meta?.title || "\u03C4js - Composing systems, not just apps"}</title>
1029
+ <meta name="description" content="\${
1030
+ meta?.description ||
1031
+ (data as { message?: string })?.message ||
1032
+ "\u03C4js - Composing systems, not just apps"
1033
+ }">
1034
+ \`,
1035
+ enableDebug: process.env.NODE_ENV === "development",
1036
+ });
1037
+ `;
1038
+ }
766
1039
  function generateEntryClient() {
767
1040
  return `import { hydrateApp } from '@taujs/react';
768
1041
  import { App } from './App';
@@ -873,7 +1146,19 @@ function generateFavicon() {
873
1146
  </svg>
874
1147
  `;
875
1148
  }
876
- main().catch((error) => {
877
- console.error(pc.red("\n\u2716 Error creating project:"), error);
878
- process.exit(1);
879
- });
1149
+ var invokedDirectly = (() => {
1150
+ try {
1151
+ return !!process.argv[1] && import.meta.url === pathToFileURL(fs.realpathSync(process.argv[1])).href;
1152
+ } catch {
1153
+ return false;
1154
+ }
1155
+ })();
1156
+ if (invokedDirectly) {
1157
+ main().catch((error) => {
1158
+ console.error(pc.red("\n\u2716 Error creating project:"), error);
1159
+ process.exit(1);
1160
+ });
1161
+ }
1162
+ export {
1163
+ planFiles
1164
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@taujs/create-taujs",
3
- "version": "0.2.0",
3
+ "version": "0.3.1",
4
4
  "description": "Scaffold a new τjs application",
5
5
  "author": "Aoede <taujs@aoede.uk.net> (https://www.aoede.uk.net)",
6
6
  "homepage": "https://taujs.dev/",