create-ui0 0.1.2 → 0.1.4

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
@@ -6,14 +6,20 @@ import prompts from 'prompts';
6
6
  /**
7
7
  * create-ui0 - scaffolds a ui0 (Vue + @ui0 packages) app.
8
8
  * Usage: npm create ui0 [dir]
9
+ * npm create ui0 [dir] [vite|nuxt] [features] (non-interactive;
10
+ * features: comma list of flex,button,icon)
9
11
  */
10
12
  const templateDir = fileURLToPath(new URL('../template', import.meta.url));
13
+ const templateNuxtDir = fileURLToPath(new URL('../template-nuxt', import.meta.url));
11
14
  const componentTemplateDir = fileURLToPath(new URL('../template-component', import.meta.url));
12
15
  const toKebab = (s) => s
13
16
  .replace(/([a-z0-9])([A-Z])/g, '$1-$2')
14
17
  .replace(/[\s_]+/g, '-')
15
18
  .toLowerCase();
16
- const toPascal = (s) => s.split(/[-\s_]+/).map((w) => w[0].toUpperCase() + w.slice(1)).join('');
19
+ const toPascal = (s) => s
20
+ .split(/[-\s_]+/)
21
+ .map((w) => w[0].toUpperCase() + w.slice(1))
22
+ .join('');
17
23
  const toCamel = (s) => {
18
24
  const p = toPascal(s);
19
25
  return p[0].toLowerCase() + p.slice(1);
@@ -28,8 +34,9 @@ const FEATURES = [
28
34
  {
29
35
  id: 'flex',
30
36
  title: 'Flex layout',
31
- deps: { '@ui0/flex': '^0.1.1' },
37
+ deps: { '@ui0/flex': '^0.1.3' },
32
38
  importLine: "import Flex from '@ui0/flex'",
39
+ cssLines: ["'@ui0/flex/style.css'"],
33
40
  section: ` <section>
34
41
  <h2>Flex</h2>
35
42
  <Flex items="center">
@@ -42,8 +49,9 @@ const FEATURES = [
42
49
  {
43
50
  id: 'button',
44
51
  title: 'Button component',
45
- deps: { '@ui0/button': '^0.1.1' },
52
+ deps: { '@ui0/button': '^0.1.3' },
46
53
  importLine: "import Button from '@ui0/button'",
54
+ cssLines: ["'@ui0/button/style.css'"],
47
55
  section: ` <section>
48
56
  <h2>Button</h2>
49
57
  <Button>default</Button>
@@ -52,11 +60,19 @@ const FEATURES = [
52
60
  {
53
61
  id: 'icon',
54
62
  title: 'Icon component + ui0 icon pack',
55
- deps: { '@ui0/icon': '^0.1.1', '@ui0/icons': '^0.1.1' },
63
+ deps: { '@ui0/icon': '^0.1.3', '@ui0/icons': '^0.1.3' },
56
64
  importLine: "import { Icon } from '@ui0/icon'",
65
+ cssLines: ["'@ui0/icon/style.css'", "'@ui0/icons/style.css'"],
57
66
  section: ` <section>
58
67
  <h2>Icon</h2>
59
68
  <Icon value="ui0/heart-alt" />
69
+ </section>`,
70
+ // the nuxt template also wires the svg loader, so the demo shows an
71
+ // app-local icon next to the built-in pack one
72
+ sectionNuxt: ` <section>
73
+ <h2>Icon</h2>
74
+ <Icon value="ui0/heart-alt" />
75
+ <Icon value="assets/icons/bookmark" />
60
76
  </section>`
61
77
  }
62
78
  ];
@@ -81,7 +97,12 @@ const runComponent = async () => {
81
97
  message: 'Target directory',
82
98
  initial: path.join('src', 'components', kebab)
83
99
  }, { onCancel });
84
- const { spec } = await prompts({ type: 'confirm', name: 'spec', message: 'Include a vitest spec (adds vitest + @vue/test-utils to devDependencies)?', initial: false }, { onCancel });
100
+ const { spec } = await prompts({
101
+ type: 'confirm',
102
+ name: 'spec',
103
+ message: 'Include a vitest spec (adds vitest + @vue/test-utils to devDependencies)?',
104
+ initial: false
105
+ }, { onCancel });
85
106
  const dir = path.resolve(process.cwd(), target);
86
107
  if (existsSync(dir)) {
87
108
  console.error(`${target} already exists`);
@@ -116,6 +137,8 @@ const run = async () => {
116
137
  return;
117
138
  }
118
139
  const argDir = process.argv[2];
140
+ const argHost = process.argv[3];
141
+ const argFeatures = process.argv[4]?.split(',').filter(Boolean);
119
142
  const { dir } = await prompts({
120
143
  type: argDir ? null : 'text',
121
144
  name: 'dir',
@@ -125,42 +148,88 @@ const run = async () => {
125
148
  const target = path.resolve(process.cwd(), argDir ?? dir);
126
149
  const name = path.basename(target);
127
150
  if (existsSync(target)) {
128
- const { overwrite } = await prompts({ type: 'confirm', name: 'overwrite', message: `${name} exists - continue and fill missing files?`, initial: false }, { onCancel });
151
+ const { overwrite } = await prompts({
152
+ type: 'confirm',
153
+ name: 'overwrite',
154
+ message: `${name} exists - continue and fill missing files?`,
155
+ initial: false
156
+ }, { onCancel });
129
157
  if (!overwrite)
130
158
  process.exit(0);
131
159
  }
132
- const { features } = (await prompts({
133
- type: 'multiselect',
160
+ const { host: pickedHost } = (await prompts({
161
+ type: argHost ? null : 'select',
162
+ name: 'host',
163
+ message: 'App host',
164
+ choices: [
165
+ { title: 'Vite (SPA)', value: 'vite' },
166
+ { title: 'Nuxt (SSR, @ui0/nuxt)', value: 'nuxt' }
167
+ ],
168
+ initial: 0
169
+ }, { onCancel }));
170
+ const host = argHost ?? pickedHost;
171
+ const { features: pickedFeatures } = (await prompts({
172
+ type: argFeatures ? null : 'multiselect',
134
173
  name: 'features',
135
174
  message: 'Packages to include',
136
175
  choices: FEATURES.map((f) => ({ title: f.title, value: f.id, selected: f.id !== 'icon' })),
137
176
  instructions: false
138
177
  }, { onCancel }));
178
+ const features = argFeatures ?? pickedFeatures ?? [];
179
+ // the nuxt flavour lives in its own template, in manual-import mode
180
+ // (components: false): the picked features drive the imports in app.vue,
181
+ // the demo sections and the built styles in the nuxt css pipeline
182
+ const sourceDir = host === 'nuxt' ? templateNuxtDir : templateDir;
139
183
  mkdirSync(target, { recursive: true });
140
- cpSync(templateDir, target, { recursive: true });
184
+ cpSync(sourceDir, target, { recursive: true });
141
185
  rmSync(path.join(target, 'package.json.tpl'));
142
186
  const picked = FEATURES.filter((f) => features.includes(f.id));
143
- // package.json from the template manifest + chosen deps
144
- const manifest = JSON.parse(readFileSync(path.join(templateDir, 'package.json.tpl'), 'utf8'));
187
+ // package.json from the template manifest + the chosen feature deps (the
188
+ // nuxt template pins @ui0/core + @ui0/theme itself for the manual imports)
189
+ const manifest = JSON.parse(readFileSync(path.join(sourceDir, 'package.json.tpl'), 'utf8'));
145
190
  manifest.name = name;
146
191
  Object.assign(manifest.dependencies, ...picked.map((f) => f.deps));
147
192
  writeFileSync(path.join(target, 'package.json'), JSON.stringify(manifest, null, 2) + '\n');
148
- // App.vue: inject the demo sections of the picked features
149
- const appVue = path.join(target, 'src/App.vue');
193
+ // app entry: src/App.vue (vite) or app/app.vue (nuxt) - inject the imports
194
+ // and the demo sections of the picked features
195
+ const appVue = path.join(target, host === 'nuxt' ? path.join('app', 'app.vue') : path.join('src', 'App.vue'));
150
196
  let app = readFileSync(appVue, 'utf8');
151
197
  app = app
152
198
  .replace('/* FEATURE_IMPORTS */', picked.map((f) => f.importLine).join('\n'))
153
- .replace('/* FEATURE_SECTIONS */', picked.map((f) => f.section).join('\n\n'));
199
+ .replace('/* FEATURE_SECTIONS */', picked.map((f) => (host === 'nuxt' ? (f.sectionNuxt ?? f.section) : f.section)).join('\n\n'));
154
200
  writeFileSync(appVue, app);
155
- // the icon pack registers itself as a vue plugin
156
- const mainTs = path.join(target, 'src/main.ts');
157
- let main = readFileSync(mainTs, 'utf8');
158
- if (features.includes('icon')) {
159
- main = main
160
- .replace("import { createUi0 } from '@ui0/core'", "import Icons from '@ui0/icons'\nimport { createUi0 } from '@ui0/core'")
161
- .replace('app.use(ui0)', 'app.use(ui0)\napp.use(Icons)');
201
+ // nuxt manual mode: the picked features' built styles join the css
202
+ // pipeline, the icon feature wires the svg loader (the template ships
203
+ // sample icons under assets/ - they need the configured dir to exist)
204
+ if (host === 'nuxt') {
205
+ const nuxtConfigPath = path.join(target, 'nuxt.config.ts');
206
+ const withIcons = features.includes('icon');
207
+ const configSrc = readFileSync(nuxtConfigPath, 'utf8')
208
+ .replace('/* FEATURE_VITE_IMPORT */', withIcons ? "import { fileURLToPath } from 'node:url'\n\nimport ViteUi0Icon from '@ui0/icon/vite'\n" : '')
209
+ .replace('/* FEATURE_VITE_PLUGINS */',
210
+ // absolute on purpose: nuxt's vite root is the app/ dir, relative
211
+ // dirs would resolve against it. dir is the scan root - an svg at
212
+ // <dir>/icons/bookmark.svg is addressed 'assets/icons/bookmark'
213
+ withIcons ? "ViteUi0Icon({ dir: fileURLToPath(new URL('./assets', import.meta.url)) })" : '')
214
+ .replace('/* FEATURE_CSS */',
215
+ // the placeholder trails the array's first entry - lead with a comma
216
+ picked.length ? `,\n ${picked.flatMap((f) => f.cssLines ?? []).join(',\n ')}` : '');
217
+ writeFileSync(nuxtConfigPath, configSrc);
218
+ if (!withIcons)
219
+ rmSync(path.join(target, 'assets'), { recursive: true, force: true });
220
+ }
221
+ // the icon pack registers itself as a vue plugin (vite hosts only - the
222
+ // nuxt module wires the icon framework and the pack itself)
223
+ if (host !== 'nuxt') {
224
+ const mainTs = path.join(target, 'src', 'main.ts');
225
+ let main = readFileSync(mainTs, 'utf8');
226
+ if (features.includes('icon')) {
227
+ main = main
228
+ .replace("import { createUi0 } from '@ui0/core'", "import Icons from '@ui0/icons'\nimport { createUi0 } from '@ui0/core'")
229
+ .replace('app.use(ui0)', 'app.use(ui0)\napp.use(Icons)');
230
+ }
231
+ writeFileSync(mainTs, main);
162
232
  }
163
- writeFileSync(mainTs, main);
164
233
  console.log(`\nScaffolded ${name}:\n`);
165
234
  console.log(` cd ${path.relative(process.cwd(), target)}`);
166
235
  console.log(' npm install');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-ui0",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "Scaffold a ui0 (Vue + @ui0) app - npm create ui0",
5
5
  "type": "module",
6
6
  "bin": {
@@ -9,11 +9,12 @@
9
9
  "files": [
10
10
  "dist",
11
11
  "template",
12
+ "template-nuxt",
12
13
  "template-component"
13
14
  ],
14
15
  "scripts": {
15
16
  "create:build": "tsc -p tsconfig.json",
16
- "create:publish": "npm publish --access public"
17
+ "create:publish": "node ../../scripts/publish-packages.ts"
17
18
  },
18
19
  "dependencies": {
19
20
  "prompts": "^2.4.2"
@@ -0,0 +1,38 @@
1
+ <template>
2
+ <ThemeProvider preset="app">
3
+ <main :class="$style.page">
4
+ <h1>ui0 app</h1>
5
+
6
+ /* FEATURE_SECTIONS */
7
+ </main>
8
+ </ThemeProvider>
9
+ </template>
10
+
11
+ <script setup lang="ts">
12
+ import { ThemeProvider } from '@ui0/theme/theme-provider'
13
+ /* FEATURE_IMPORTS */
14
+ </script>
15
+
16
+ <style module lang="scss">
17
+ .page {
18
+ display: flex;
19
+ flex-direction: column;
20
+ gap: 24px;
21
+ padding: 32px;
22
+ font-family: system-ui, sans-serif;
23
+
24
+ h1 {
25
+ font-size: 20px;
26
+ }
27
+ h2 {
28
+ font-size: 14px;
29
+ opacity: 0.7;
30
+ margin-bottom: 8px;
31
+ }
32
+ }
33
+ .box {
34
+ padding: 12px 16px;
35
+ border: 1px solid rgba(127, 127, 127, 0.4);
36
+ border-radius: 6px;
37
+ }
38
+ </style>
@@ -0,0 +1,3 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 960 960">
2
+ <path d="M629.531,180H330.469c-27.528,0-49.844,24.345-49.844,54.375V760L480,609.63,679.375,760V234.375C679.375,204.345,657.061,180,629.531,180Z"/>
3
+ </svg>
@@ -0,0 +1,25 @@
1
+ /* FEATURE_VITE_IMPORT */
2
+ export default defineNuxtConfig({
3
+ ssr: true,
4
+ compatibilityDate: '2026-11-12',
5
+ devtools: { enabled: false },
6
+ // built ui0 component styles ship in the head of every ssr response; the
7
+ // module pushes @ui0/core/style.css itself - one entry per ui0 component
8
+ // the app renders (app/app.vue imports them in code, manual mode below)
9
+ css: ['@ui0/theme/theme-provider/style.css'/* FEATURE_CSS */],
10
+ modules: ['@ui0/nuxt'],
11
+ ui0: {
12
+ // manual component imports: no auto-registration, the template imports
13
+ // Flex / Button / Icon / ThemeProvider in app/app.vue and lists their
14
+ // style.css above
15
+ components: false
16
+ },
17
+ vite: {
18
+ plugins: [
19
+ // the svg icon loader - only for the app's OWN icons (dir) and
20
+ // compositions (virtual); the built-in ui0 pack ships prebuilt from
21
+ // @ui0/icons and needs no loader
22
+ /* FEATURE_VITE_PLUGINS */
23
+ ]
24
+ }
25
+ })
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "ui0-app",
3
+ "version": "0.0.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "nuxt dev",
8
+ "build": "nuxt build",
9
+ "preview": "nuxt preview"
10
+ },
11
+ "dependencies": {
12
+ "@ui0/core": "^0.1.4",
13
+ "@ui0/nuxt": "^0.1.1",
14
+ "@ui0/theme": "^0.1.4",
15
+ "nuxt": "^4.6.4",
16
+ "vue": "^3.5.45"
17
+ },
18
+ "devDependencies": {
19
+ "@ui0/types": "^0.1.4",
20
+ "sass-embedded": "^1.80.0"
21
+ }
22
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ // generated by nuxt (prepare) - the project config extends it
3
+ "extends": "./.nuxt/tsconfig.json"
4
+ }
@@ -0,0 +1,23 @@
1
+ import type { Ui0CoreConfig } from '@ui0/types'
2
+
3
+ const config: Ui0CoreConfig = {
4
+ theme: {
5
+ default: 'app',
6
+ contracts: {
7
+ display: {}
8
+ },
9
+ presets: {
10
+ app: {
11
+ default: {
12
+ color: {
13
+ brand: ['rgb(86 59 177)', 4],
14
+ foreground: '#111',
15
+ background: '#fff'
16
+ }
17
+ }
18
+ }
19
+ }
20
+ }
21
+ }
22
+
23
+ export default config