create-ui0 0.1.0 → 0.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/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { cpSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
1
+ import { cpSync, existsSync, mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { fileURLToPath } from 'node:url';
4
4
  import prompts from 'prompts';
@@ -7,6 +7,21 @@ import prompts from 'prompts';
7
7
  * Usage: npm create ui0 [dir]
8
8
  */
9
9
  const templateDir = fileURLToPath(new URL('../template', import.meta.url));
10
+ const componentTemplateDir = fileURLToPath(new URL('../template-component', import.meta.url));
11
+ const toKebab = (s) => s
12
+ .replace(/([a-z0-9])([A-Z])/g, '$1-$2')
13
+ .replace(/[\s_]+/g, '-')
14
+ .toLowerCase();
15
+ const toPascal = (s) => s.split(/[-\s_]+/).map((w) => w[0].toUpperCase() + w.slice(1)).join('');
16
+ const toCamel = (s) => {
17
+ const p = toPascal(s);
18
+ return p[0].toLowerCase() + p.slice(1);
19
+ };
20
+ /** substitutes {%kebab%} / {%Pascal%} / {%camel%} in a template file's content */
21
+ const substitute = (content, name) => content
22
+ .replaceAll('{%kebab%}', toKebab(name))
23
+ .replaceAll('{%Pascal%}', toPascal(name))
24
+ .replaceAll('{%camel%}', toCamel(name));
10
25
  /** selectable template features - each adds deps (and a demo section) */
11
26
  const FEATURES = [
12
27
  {
@@ -48,7 +63,57 @@ const onCancel = () => {
48
63
  console.log('aborted');
49
64
  process.exit(0);
50
65
  };
66
+ /**
67
+ * `create-ui0 component [Name]` - scaffolds a single component folder from
68
+ * template-component/ into the project at cwd (src/components/<kebab>/).
69
+ */
70
+ const runComponent = async () => {
71
+ const argName = process.argv[3];
72
+ const { name } = await prompts({ type: argName ? null : 'text', name: 'name', message: 'Component name (PascalCase, e.g. MyButton)' }, { onCancel });
73
+ const componentName = argName ?? name;
74
+ if (!componentName)
75
+ onCancel();
76
+ const kebab = toKebab(componentName);
77
+ const { target } = await prompts({
78
+ type: 'text',
79
+ name: 'target',
80
+ message: 'Target directory',
81
+ initial: path.join('src', 'components', kebab)
82
+ }, { onCancel });
83
+ const { spec } = await prompts({ type: 'confirm', name: 'spec', message: 'Include a vitest spec (adds vitest + @vue/test-utils to devDependencies)?', initial: false }, { onCancel });
84
+ const dir = path.resolve(process.cwd(), target);
85
+ if (existsSync(dir)) {
86
+ console.error(`${target} already exists`);
87
+ process.exit(1);
88
+ }
89
+ mkdirSync(dir, { recursive: true });
90
+ const files = readdirSync(componentTemplateDir).filter((f) => spec || f !== 'name.spec.ts');
91
+ for (const file of files) {
92
+ const content = substitute(readFileSync(path.join(componentTemplateDir, file), 'utf8'), componentName);
93
+ writeFileSync(path.join(dir, file.replace(/^name\./, `${kebab}.`)), content);
94
+ }
95
+ if (spec) {
96
+ const manifestPath = path.join(process.cwd(), 'package.json');
97
+ if (existsSync(manifestPath)) {
98
+ const manifest = JSON.parse(readFileSync(manifestPath, 'utf8'));
99
+ manifest.devDependencies = {
100
+ ...manifest.devDependencies,
101
+ '@vue/test-utils': '^2.4.6',
102
+ 'happy-dom': '^15.0.0',
103
+ vitest: '^5.0.5'
104
+ };
105
+ writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + '\n');
106
+ }
107
+ }
108
+ console.log(`\nCreated ${target}:`);
109
+ for (const file of files)
110
+ console.log(` ${file.replace(/^name\./, `${kebab}.`)}`);
111
+ };
51
112
  const run = async () => {
113
+ if (process.argv[2] === 'component') {
114
+ await runComponent();
115
+ return;
116
+ }
52
117
  const argDir = process.argv[2];
53
118
  const { dir } = await prompts({
54
119
  type: argDir ? null : 'text',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-ui0",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Scaffold a ui0 (Vue + @ui0) app - npm create ui0",
5
5
  "type": "module",
6
6
  "bin": {
@@ -8,7 +8,8 @@
8
8
  },
9
9
  "files": [
10
10
  "dist",
11
- "template"
11
+ "template",
12
+ "template-component"
12
13
  ],
13
14
  "scripts": {
14
15
  "create:build": "tsc -p tsconfig.json",
@@ -17,6 +17,7 @@
17
17
  "@ui0/types": "^0.1.2",
18
18
  "@vitejs/plugin-vue": "^6.0.1",
19
19
  "sass-embedded": "^1.80.0",
20
+ "typescript": "~6.0.3",
20
21
  "vite": "^8.3.5"
21
22
  }
22
23
  }
@@ -0,0 +1,3 @@
1
+ .{%camel%} {
2
+ /* component base styles */
3
+ }
@@ -0,0 +1,12 @@
1
+ // @vitest-environment happy-dom
2
+ import { mount } from '@vue/test-utils'
3
+ import { describe, expect, it } from 'vitest'
4
+
5
+ import {%Pascal%} from './{%kebab%}.vue'
6
+
7
+ describe('{%kebab%}', () => {
8
+ it('renders', () => {
9
+ const wrapper = mount({%Pascal%})
10
+ expect(wrapper.find('[data-0="{%kebab%}"]').exists()).toBe(true)
11
+ })
12
+ })
@@ -0,0 +1,7 @@
1
+ export interface {%Pascal%}Props {
2
+ // add props here
3
+ }
4
+
5
+ export interface {%Pascal%}Slots {
6
+ default: () => void
7
+ }
@@ -0,0 +1,17 @@
1
+ <template>
2
+ <div
3
+ data-0="{%kebab%}"
4
+ :class="$styles.{%camel%}">
5
+ <slot />
6
+ </div>
7
+ </template>
8
+
9
+ <script setup lang="ts">
10
+ import { useCssModule } from 'vue'
11
+
12
+ import type { {%Pascal%}Props } from './{%kebab%}.types'
13
+ import './{%kebab%}.module.css'
14
+
15
+ const props = withDefaults(defineProps<{%Pascal%}Props>(), {})
16
+ const $styles = useCssModule()
17
+ </script>