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 +91 -22
- package/package.json +3 -2
- package/template-nuxt/app/app.vue +38 -0
- package/template-nuxt/assets/icons/bookmark.svg +3 -0
- package/template-nuxt/nuxt.config.ts +25 -0
- package/template-nuxt/package.json.tpl +22 -0
- package/template-nuxt/tsconfig.json +4 -0
- package/template-nuxt/ui0.config.ts +23 -0
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
|
|
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.
|
|
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.
|
|
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.
|
|
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({
|
|
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({
|
|
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 {
|
|
133
|
-
type: '
|
|
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(
|
|
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
|
-
|
|
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
|
|
149
|
-
|
|
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
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
if (
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
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.
|
|
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": "
|
|
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,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,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
|