create-ui0 0.1.0 → 0.1.2

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.d.ts CHANGED
@@ -1,2 +1,3 @@
1
+ #!/usr/bin/env node
1
2
  export {};
2
3
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -1,4 +1,5 @@
1
- import { cpSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
1
+ #!/usr/bin/env node
2
+ import { cpSync, existsSync, mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
2
3
  import path from 'node:path';
3
4
  import { fileURLToPath } from 'node:url';
4
5
  import prompts from 'prompts';
@@ -7,6 +8,21 @@ import prompts from 'prompts';
7
8
  * Usage: npm create ui0 [dir]
8
9
  */
9
10
  const templateDir = fileURLToPath(new URL('../template', import.meta.url));
11
+ const componentTemplateDir = fileURLToPath(new URL('../template-component', import.meta.url));
12
+ const toKebab = (s) => s
13
+ .replace(/([a-z0-9])([A-Z])/g, '$1-$2')
14
+ .replace(/[\s_]+/g, '-')
15
+ .toLowerCase();
16
+ const toPascal = (s) => s.split(/[-\s_]+/).map((w) => w[0].toUpperCase() + w.slice(1)).join('');
17
+ const toCamel = (s) => {
18
+ const p = toPascal(s);
19
+ return p[0].toLowerCase() + p.slice(1);
20
+ };
21
+ /** substitutes {%kebab%} / {%Pascal%} / {%camel%} in a template file's content */
22
+ const substitute = (content, name) => content
23
+ .replaceAll('{%kebab%}', toKebab(name))
24
+ .replaceAll('{%Pascal%}', toPascal(name))
25
+ .replaceAll('{%camel%}', toCamel(name));
10
26
  /** selectable template features - each adds deps (and a demo section) */
11
27
  const FEATURES = [
12
28
  {
@@ -48,7 +64,57 @@ const onCancel = () => {
48
64
  console.log('aborted');
49
65
  process.exit(0);
50
66
  };
67
+ /**
68
+ * `create-ui0 component [Name]` - scaffolds a single component folder from
69
+ * template-component/ into the project at cwd (src/components/<kebab>/).
70
+ */
71
+ const runComponent = async () => {
72
+ const argName = process.argv[3];
73
+ const { name } = await prompts({ type: argName ? null : 'text', name: 'name', message: 'Component name (PascalCase, e.g. MyButton)' }, { onCancel });
74
+ const componentName = argName ?? name;
75
+ if (!componentName)
76
+ onCancel();
77
+ const kebab = toKebab(componentName);
78
+ const { target } = await prompts({
79
+ type: 'text',
80
+ name: 'target',
81
+ message: 'Target directory',
82
+ initial: path.join('src', 'components', kebab)
83
+ }, { 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 });
85
+ const dir = path.resolve(process.cwd(), target);
86
+ if (existsSync(dir)) {
87
+ console.error(`${target} already exists`);
88
+ process.exit(1);
89
+ }
90
+ mkdirSync(dir, { recursive: true });
91
+ const files = readdirSync(componentTemplateDir).filter((f) => spec || f !== 'name.spec.ts');
92
+ for (const file of files) {
93
+ const content = substitute(readFileSync(path.join(componentTemplateDir, file), 'utf8'), componentName);
94
+ writeFileSync(path.join(dir, file.replace(/^name\./, `${kebab}.`)), content);
95
+ }
96
+ if (spec) {
97
+ const manifestPath = path.join(process.cwd(), 'package.json');
98
+ if (existsSync(manifestPath)) {
99
+ const manifest = JSON.parse(readFileSync(manifestPath, 'utf8'));
100
+ manifest.devDependencies = {
101
+ ...manifest.devDependencies,
102
+ '@vue/test-utils': '^2.4.6',
103
+ 'happy-dom': '^15.0.0',
104
+ vitest: '^5.0.5'
105
+ };
106
+ writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + '\n');
107
+ }
108
+ }
109
+ console.log(`\nCreated ${target}:`);
110
+ for (const file of files)
111
+ console.log(` ${file.replace(/^name\./, `${kebab}.`)}`);
112
+ };
51
113
  const run = async () => {
114
+ if (process.argv[2] === 'component') {
115
+ await runComponent();
116
+ return;
117
+ }
52
118
  const argDir = process.argv[2];
53
119
  const { dir } = await prompts({
54
120
  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.2",
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>