create-rsc-kit 0.22.9 → 0.23.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/index.js CHANGED
@@ -16,6 +16,7 @@ import { DEFAULT_COMPILER, HELP, HOSTS, publishedCore, assertInsideCwd, assertUs
16
16
  import { Prompter, bold, cyan, dim } from './prompt.js';
17
17
  import { checkForNewer, notifyIfStale, selfVersion } from './stale.js';
18
18
  import * as t from './templates.js';
19
+ import { setUpShadcn } from './shadcn.js';
19
20
  import { STARTER_ICONS } from './icons.js';
20
21
  // Same treatment as a refusal from write(): a bad flag is a decision this tool
21
22
  // made, and it happens before the try/catch below because the flags are what
@@ -39,6 +40,11 @@ const core = flags.core ?? defaultCore(dirname(fileURLToPath(import.meta.url)));
39
40
  // Started before the first question and never awaited here — see notifyIfStale.
40
41
  const staleCheck = checkForNewer('create-rsc-kit', selfVersion(import.meta.url));
41
42
  const options = await collect();
43
+ // shadcn's components are styled with Tailwind; without it they render bare.
44
+ if (options.shadcn && !options.tailwind) {
45
+ stdout.write(`\n${bold('Cannot scaffold here.')}\n --shadcn needs Tailwind; drop --no-tailwind.\n\n`);
46
+ exit(1);
47
+ }
42
48
  try {
43
49
  write(options);
44
50
  }
@@ -63,10 +69,12 @@ if (options.git) {
63
69
  run('git', ['init', '--quiet'], options.dir);
64
70
  }
65
71
  }
72
+ let installed = false;
66
73
  if (options.install) {
67
74
  stdout.write(`\n${dim('Installing dependencies…')}\n`);
68
75
  const pm = options.host === 'node' ? 'npm' : 'bun';
69
76
  const ok = run(pm, ['install'], options.dir);
77
+ installed = ok;
70
78
  if (!ok) {
71
79
  stdout.write(`\n${bold('Dependencies did not install.')} The files are written; run the install yourself.\n` +
72
80
  (options.core === publishedCore()
@@ -74,6 +82,8 @@ if (options.install) {
74
82
  : ''));
75
83
  }
76
84
  }
85
+ if (options.shadcn)
86
+ setUpShadcn({ dir: options.dir, host: options.host, unattended, installed });
77
87
  report(options);
78
88
  await notifyIfStale(staleCheck, 'bun create rsc-kit@latest');
79
89
  // ── ─────────────────────────────────────────────────────────────────────────
@@ -94,6 +104,7 @@ async function collect() {
94
104
  validation: flags.validation ?? 'zod',
95
105
  env: flags.env ?? (flags.validation ?? 'zod') !== 'none',
96
106
  pwa: flags.pwa ?? false,
107
+ shadcn: flags.shadcn ?? false,
97
108
  sourceDir: flags.sourceDir ?? 'src',
98
109
  install: flags.install ?? true,
99
110
  git: flags.git ?? true,
@@ -118,6 +129,8 @@ async function collect() {
118
129
  // Yes/no, not which: see DEFAULT_COMPILER.
119
130
  const compiler = flags.compiler ?? ((await p.confirm('React Compiler', true)) ? DEFAULT_COMPILER : 'none');
120
131
  const tailwind = flags.tailwind ?? (await p.confirm('Tailwind CSS', true));
132
+ // Only with Tailwind: shadcn's components are styled with it.
133
+ const shadcn = tailwind ? (flags.shadcn ?? (await p.confirm('shadcn/ui', false))) : false;
121
134
  const lint = flags.lint ?? (await p.confirm('oxlint', true));
122
135
  const validation = flags.validation ?? (await p.select('Validation library', VALIDATIONS));
123
136
  // Only worth asking once there is a library to validate with.
@@ -136,6 +149,7 @@ async function collect() {
136
149
  validation,
137
150
  env,
138
151
  pwa,
152
+ shadcn,
139
153
  sourceDir: flags.sourceDir ?? 'src',
140
154
  install: flags.install ?? (await p.confirm('Install dependencies now', true)),
141
155
  git: flags.git ?? true,
package/dist/init.js CHANGED
@@ -14,6 +14,7 @@ import { randomBytes } from 'node:crypto';
14
14
  import { basename, dirname, join } from 'node:path';
15
15
  import { argv, cwd, exit, stdout } from 'node:process';
16
16
  import { DEFAULT_COMPILER, parseArgs, publishedCore } from './options.js';
17
+ import { setUpShadcn } from './shadcn.js';
17
18
  import { Prompter, bold, cyan, dim } from './prompt.js';
18
19
  import * as t from './templates.js';
19
20
  // Nothing here detects a host any more: a host is a Nitro preset, and Nitro
@@ -665,6 +666,8 @@ const INIT_HELP = `
665
666
  (a Go service), otherwise only if it is already there
666
667
  --no-install don't install; a project with no package.json yet is
667
668
  installed by default, any other never is
669
+ --shadcn shadcn/ui for RSC: marks an existing components.json
670
+ "rsc": true, or runs shadcn's init after the install
668
671
  -y, --yes accept what was detected, ask nothing
669
672
  -h, --help this
670
673
  `;
@@ -740,6 +743,9 @@ export async function runInit(args) {
740
743
  // Only ever for a project init gave its first package.json: anywhere else
741
744
  // the project's own package manager installs, not this.
742
745
  let install = defaults.install && flags.install !== false;
746
+ let shadcn = flags.shadcn === true;
747
+ // Set up already: the question is only whether to mark it for RSC.
748
+ const hasShadcn = existsSync(join(dir, 'components.json'));
743
749
  if (!unattended) {
744
750
  const p = new Prompter();
745
751
  try {
@@ -747,6 +753,12 @@ export async function runInit(args) {
747
753
  if (flags.tailwind === undefined && !found.hasTailwind) {
748
754
  tailwind = await p.confirm('Tailwind CSS', defaults.tailwind);
749
755
  }
756
+ // Only with Tailwind: shadcn's components are styled with it.
757
+ if (flags.shadcn === undefined && (tailwind || found.hasTailwind)) {
758
+ shadcn = hasShadcn
759
+ ? await p.confirm('components.json is here: set "rsc": true for shadcn', true)
760
+ : await p.confirm('shadcn/ui', false);
761
+ }
750
762
  if (defaults.install && flags.install === undefined) {
751
763
  install = await p.confirm('Install dependencies now', true);
752
764
  }
@@ -755,6 +767,11 @@ export async function runInit(args) {
755
767
  p.close();
756
768
  }
757
769
  }
770
+ // shadcn's components are styled with Tailwind; without it they render bare.
771
+ if (shadcn && !tailwind && !found.hasTailwind) {
772
+ stdout.write(`${bold('Not set up.')} --shadcn needs Tailwind: pass --tailwind too.\n\n`);
773
+ exit(1);
774
+ }
758
775
  const options = {
759
776
  dir,
760
777
  name: 'app',
@@ -793,6 +810,10 @@ export async function runInit(args) {
793
810
  stdout.write(`\n${bold('Dependencies did not install.')} The files are written; run the install yourself.\n`);
794
811
  }
795
812
  }
813
+ // After the install: shadcn's init reads the dependencies, and its
814
+ // components need them. A project already using shadcn is only marked.
815
+ if (shadcn)
816
+ setUpShadcn({ dir, host: options.host, unattended, installed });
796
817
  if (manual.length > 0) {
797
818
  stdout.write(`\n${bold('Then, by hand:')} the steps marked ! above.\n\n`);
798
819
  return;
package/dist/options.d.ts CHANGED
@@ -43,6 +43,13 @@ export interface Options {
43
43
  * installed it, so it is a decision to take on purpose.
44
44
  */
45
45
  pwa: boolean;
46
+ /**
47
+ * shadcn/ui, set up by its own CLI once dependencies are installed, then
48
+ * told this is an RSC app - it sees a plain Vite project and writes
49
+ * `"rsc": false`, and its interactive components then lack "use client".
50
+ * Needs Tailwind.
51
+ */
52
+ shadcn?: boolean;
46
53
  install: boolean;
47
54
  git: boolean;
48
55
  /** What to depend on for the engine. A path makes a local checkout testable. */
@@ -120,4 +127,4 @@ export declare function assertUsableName(name: string): void;
120
127
  * generator nobody can run without reading it first.
121
128
  */
122
129
  export declare function assertInsideCwd(dir: string, cwd: string): void;
123
- export declare const HELP = "\n create-rsc-kit \u2014 scaffold an RSC app\n\n Usage\n create-rsc-kit <dir> [options]\n bun create rsc-kit@latest <dir> [options]\n\n Options\n --host=bun|node|worker where it runs \u2014 picks the Nitro preset\n --compiler=none|oxc|babel React Compiler (prompt offers oxc; babel by flag)\n --tailwind / --no-tailwind include Tailwind\n --lint / --no-lint include oxlint\n --validation=zod|valibot|arktype|none\n the schema library; forms, actions and env use it\n --env / --no-env typed environment variables (@t3-oss/env-core)\n --pwa / --no-pwa installable: offline, a manifest, starter icons (default: no)\n --source-dir <dir> where app/ lives (default: src)\n --backend=<url> a backend answering host calls \u2014 a Go server, say,\n at http://127.0.0.1:8080; writes .env with a secret\n --init add to the project here, rather than scaffold\n --core=<spec> engine dependency, e.g. file:../rsc-kit/packages/core\n --no-install skip installing dependencies\n --no-git skip git init\n -y, --yes accept every default, ask nothing\n -h, --help this\n";
130
+ export declare const HELP = "\n create-rsc-kit \u2014 scaffold an RSC app\n\n Usage\n create-rsc-kit <dir> [options]\n bun create rsc-kit@latest <dir> [options]\n\n Options\n --host=bun|node|worker where it runs \u2014 picks the Nitro preset\n --compiler=none|oxc|babel React Compiler (prompt offers oxc; babel by flag)\n --tailwind / --no-tailwind include Tailwind\n --lint / --no-lint include oxlint\n --validation=zod|valibot|arktype|none\n the schema library; forms, actions and env use it\n --env / --no-env typed environment variables (@t3-oss/env-core)\n --pwa / --no-pwa installable: offline, a manifest, starter icons (default: no)\n --shadcn shadcn/ui, initialised for RSC; needs Tailwind\n --source-dir <dir> where app/ lives (default: src)\n --backend=<url> a backend answering host calls \u2014 a Go server, say,\n at http://127.0.0.1:8080; writes .env with a secret\n --init add to the project here, rather than scaffold\n --core=<spec> engine dependency, e.g. file:../rsc-kit/packages/core\n --no-install skip installing dependencies\n --no-git skip git init\n --yes accept every default, ask nothing\n (-y too, but `bun create` takes -y as its own)\n -h, --help this\n";
package/dist/options.js CHANGED
@@ -145,6 +145,8 @@ export function parseArgs(argv) {
145
145
  out.compiler = arg.slice(11);
146
146
  else if (arg.startsWith('--validation='))
147
147
  out.validation = assertValidation(arg.slice(13));
148
+ else if (arg === '--shadcn')
149
+ out.shadcn = true;
148
150
  else if (arg === '--pwa')
149
151
  out.pwa = true;
150
152
  else if (arg === '--no-pwa')
@@ -208,6 +210,7 @@ export const HELP = `
208
210
  the schema library; forms, actions and env use it
209
211
  --env / --no-env typed environment variables (@t3-oss/env-core)
210
212
  --pwa / --no-pwa installable: offline, a manifest, starter icons (default: no)
213
+ --shadcn shadcn/ui, initialised for RSC; needs Tailwind
211
214
  --source-dir <dir> where app/ lives (default: src)
212
215
  --backend=<url> a backend answering host calls — a Go server, say,
213
216
  at http://127.0.0.1:8080; writes .env with a secret
@@ -215,6 +218,7 @@ export const HELP = `
215
218
  --core=<spec> engine dependency, e.g. file:../rsc-kit/packages/core
216
219
  --no-install skip installing dependencies
217
220
  --no-git skip git init
218
- -y, --yes accept every default, ask nothing
221
+ --yes accept every default, ask nothing
222
+ (-y too, but \`bun create\` takes -y as its own)
219
223
  -h, --help this
220
224
  `;
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Tell shadcn this is an RSC app: `"rsc": true` in components.json, so the
3
+ * components it adds that need the browser start with "use client".
4
+ *
5
+ * Its own init sees a plain Vite project and writes false. Returns whether
6
+ * the file was there to change.
7
+ */
8
+ export declare function markRsc(path: string): boolean;
9
+ /**
10
+ * shadcn/ui for an rsc-kit app, new or existing.
11
+ *
12
+ * Already set up - a components.json is there, from a Next app say - it is
13
+ * only marked RSC. Otherwise shadcn's own init runs, once dependencies are
14
+ * installed, and its result is marked: its CLI sees a plain Vite project and
15
+ * writes "rsc": false, so a dialog or a dropdown it adds later has no
16
+ * "use client" and fails to render as a server component.
17
+ *
18
+ * Its questions are its own - the component library, the style - so it asks
19
+ * them in this terminal, unless the run was told to ask nothing, in which
20
+ * case it takes its defaults too.
21
+ */
22
+ export declare function setUpShadcn(o: {
23
+ dir: string;
24
+ host: string;
25
+ unattended: boolean;
26
+ installed: boolean;
27
+ }): void;
package/dist/shadcn.js ADDED
@@ -0,0 +1,56 @@
1
+ import { spawnSync } from 'node:child_process';
2
+ import { existsSync, readFileSync, writeFileSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ import { stdout } from 'node:process';
5
+ import { bold, cyan, dim } from './prompt.js';
6
+ /**
7
+ * Tell shadcn this is an RSC app: `"rsc": true` in components.json, so the
8
+ * components it adds that need the browser start with "use client".
9
+ *
10
+ * Its own init sees a plain Vite project and writes false. Returns whether
11
+ * the file was there to change.
12
+ */
13
+ export function markRsc(path) {
14
+ let config;
15
+ try {
16
+ config = JSON.parse(readFileSync(path, 'utf-8'));
17
+ }
18
+ catch {
19
+ return false;
20
+ }
21
+ if (config.rsc === true)
22
+ return true;
23
+ config.rsc = true;
24
+ writeFileSync(path, JSON.stringify(config, null, 2) + '\n');
25
+ return true;
26
+ }
27
+ /**
28
+ * shadcn/ui for an rsc-kit app, new or existing.
29
+ *
30
+ * Already set up - a components.json is there, from a Next app say - it is
31
+ * only marked RSC. Otherwise shadcn's own init runs, once dependencies are
32
+ * installed, and its result is marked: its CLI sees a plain Vite project and
33
+ * writes "rsc": false, so a dialog or a dropdown it adds later has no
34
+ * "use client" and fails to render as a server component.
35
+ *
36
+ * Its questions are its own - the component library, the style - so it asks
37
+ * them in this terminal, unless the run was told to ask nothing, in which
38
+ * case it takes its defaults too.
39
+ */
40
+ export function setUpShadcn(o) {
41
+ const config = join(o.dir, 'components.json');
42
+ if (existsSync(config)) {
43
+ markRsc(config);
44
+ stdout.write(`\n ${cyan('~')} components.json ${dim('— "rsc": true, so shadcn adds "use client" where it is needed')}\n`);
45
+ return;
46
+ }
47
+ const [runner, ...prefix] = o.host === 'node' ? ['npx', '--yes'] : ['bunx', '--bun'];
48
+ const init = [...prefix, 'shadcn@latest', 'init', ...(o.unattended ? ['--defaults', '--yes'] : [])];
49
+ if (!o.installed || spawnSync(runner, init, { cwd: o.dir, stdio: 'inherit' }).status !== 0) {
50
+ stdout.write(`\n${bold('shadcn/ui is not set up.')} Once dependencies are installed:\n` +
51
+ ` ${cyan([runner, ...init].join(' '))}\n` +
52
+ ` then set ${cyan('"rsc": true')} in components.json\n`);
53
+ return;
54
+ }
55
+ markRsc(config);
56
+ }
package/dist/templates.js CHANGED
@@ -50,13 +50,13 @@ export const configFile = (_o) => 'vite.config.ts';
50
50
  */
51
51
  export function scripts(o) {
52
52
  if (o.host === 'laravel') {
53
- const actions = 'php artisan rsc:action-manifest';
54
53
  return {
55
54
  // The ordinary names. A Laravel application already has `dev` and
56
- // `build`, and init combines rather than replaces — the stock ones run
57
- // the asset pipeline, and both pipelines belong to `npm run dev`.
58
- dev: `${actions} && vite`,
59
- build: `${actions} && vite build`,
55
+ // `build`, and init combines rather than replaces. PHP's manifest is
56
+ // not here: vite.config.ts runs it (hostManifest), so no script that
57
+ // starts Vite can skip it.
58
+ dev: 'vite',
59
+ build: 'vite build',
60
60
  // What the build wrote. There is no server file to start any more.
61
61
  start: 'bun .output/server/index.mjs',
62
62
  };
@@ -218,6 +218,9 @@ export function viteConfig(o) {
218
218
  `sourceDir: '${p.sourceDir}'`,
219
219
  `outDir: '${p.outDir}'`,
220
220
  ...(p.hotFile ? [`hotFile: '${p.hotFile}'`] : []),
221
+ // The actions and functions PHP offers, written as dev and every build
222
+ // start - reflection through Composer's autoloader, which only PHP has.
223
+ ...(o.host === 'laravel' ? ["hostManifest: { command: ['php', 'artisan', 'rsc:host-manifest'] }"] : []),
221
224
  // A service worker: the app survives a reload with no network. Off
222
225
  // unless asked for, because it outlives the code that installed it.
223
226
  ...(o.pwa ? ['offline: true'] : []),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-rsc-kit",
3
- "version": "0.22.9",
3
+ "version": "0.23.0",
4
4
  "type": "module",
5
5
  "description": "Scaffold an RSC app on Bun, Hono, Elysia or Node.",
6
6
  "bin": {