create-template-project 1.5.24 → 1.6.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.
Files changed (26) hide show
  1. package/README.md +39 -5
  2. package/dist/config/dependencies.json +24 -4
  3. package/dist/index.js +733 -142
  4. package/dist/templates/base/files/README.md +1 -1
  5. package/dist/templates/base/files/_oxc.config.ts +17 -35
  6. package/dist/templates/web-widget/files/.github/workflows/pages.yml +36 -0
  7. package/dist/templates/web-widget/files/index.html +21 -0
  8. package/dist/templates/web-widget/files/package.json +53 -0
  9. package/dist/templates/web-widget/files/playwright.config.ts +29 -0
  10. package/dist/templates/web-widget/files/scripts/screenshot.mjs +35 -0
  11. package/dist/templates/web-widget/files/src/demo/app.ts +22 -0
  12. package/dist/templates/web-widget/files/src/demo/demo-shell.css +50 -0
  13. package/dist/templates/web-widget/files/src/lib/index.ts +2 -0
  14. package/dist/templates/web-widget/files/src/lib/react.tsx +54 -0
  15. package/dist/templates/web-widget/files/src/lib/test-setup.ts +7 -0
  16. package/dist/templates/web-widget/files/src/lib/widget.test.ts +33 -0
  17. package/dist/templates/web-widget/files/src/lib/widget.ts +83 -0
  18. package/dist/templates/web-widget/files/src/styles/index.css +42 -0
  19. package/dist/templates/web-widget/files/stylelint.base.config.js +16 -0
  20. package/dist/templates/web-widget/files/stylelint.config.js +15 -0
  21. package/dist/templates/web-widget/files/tests/e2e/widget.e2e-test.ts +16 -0
  22. package/dist/templates/web-widget/files/tsdown.config.ts +20 -0
  23. package/dist/templates/web-widget/files/typedoc.json +12 -0
  24. package/dist/templates/web-widget/files/vite.config.ts +37 -0
  25. package/package.json +9 -7
  26. /package/dist/templates/base/files/.github/workflows/{node.js.yml → ci.yml} +0 -0
@@ -4,7 +4,7 @@
4
4
 
5
5
  [![npm version](https://img.shields.io/npm/v/{{projectName}}.svg)](https://www.npmjs.com/package/{{projectName}})
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
- [![Node.js CI](https://github.com/{{githubUsername}}/{{projectName}}/actions/workflows/node.js.yml/badge.svg)](https://github.com/{{githubUsername}}/{{projectName}}/actions/workflows/node.js.yml)
7
+ [![CI](https://github.com/{{githubUsername}}/{{projectName}}/actions/workflows/ci.yml/badge.svg)](https://github.com/{{githubUsername}}/{{projectName}}/actions/workflows/ci.yml)
8
8
  [![Coverage Status](https://coveralls.io/repos/github/{{githubUsername}}/{{projectName}}/badge.svg?branch=main)](https://coveralls.io/github/{{githubUsername}}/{{projectName}}?branch=main)
9
9
 
10
10
  ## Development Workflow
@@ -20,32 +20,14 @@ import {configs as regexpConfigs} from 'eslint-plugin-regexp';
20
20
  /** Filter out core ESLint rules bundled into eslint-plugin-regexp recommended config */
21
21
  const regexpPluginRules = Object.fromEntries(Object.entries(regexpConfigs.recommended.rules).filter(([key]) => key.startsWith('regexp/')));
22
22
 
23
- const commonIgnore = [
24
- '**/.*',
25
- 'node_modules/**',
26
- 'dist/**',
27
- 'build/**',
28
- 'coverage/**',
29
- 'temp/**',
30
- 'public/**',
31
- '**/*.md',
32
- ];
23
+ const commonIgnore = ['**/.*', 'node_modules/**', 'dist/**', 'build/**', 'coverage/**', 'temp/**', 'public/**', '**/*.md'];
33
24
 
34
25
  export const linter = defineConfig({
35
26
  options: {
36
27
  typeAware: true,
37
28
  typeCheck: true,
38
29
  },
39
- plugins: [
40
- 'unicorn',
41
- 'typescript',
42
- 'oxc',
43
- 'import',
44
- 'react',
45
- 'jsdoc',
46
- 'promise',
47
- 'vitest',
48
- ],
30
+ plugins: ['unicorn', 'typescript', 'oxc', 'import', 'react', 'jsdoc', 'promise', 'vitest'],
49
31
  jsPlugins: ['eslint-plugin-regexp'],
50
32
  categories: {
51
33
  correctness: 'error',
@@ -100,7 +82,7 @@ export const linter = defineConfig({
100
82
  'import/no-named-export': 'off',
101
83
  'import/no-namespace': 'off', // TODO: consider enabling
102
84
  'import/no-nodejs-modules': 'off',
103
- 'import/no-unassigned-import': ['error', {'allow': ['**/*.css']}],
85
+ 'import/no-unassigned-import': ['error', {allow: ['**/*.css']}],
104
86
  'import/prefer-default-export': 'off',
105
87
  'import/no-default-export': 'off',
106
88
  'jest/consistent-test-it': 'off',
@@ -169,7 +151,7 @@ export const linter = defineConfig({
169
151
  'oxc/no-rest-spread-properties': 'off',
170
152
  'unicorn/escape-case': 'off',
171
153
  'unicorn/filename-case': 'off', // TODO: consider enabling
172
- 'unicorn/max-nested-calls': ['warn', {'max': 5}],
154
+ 'unicorn/max-nested-calls': ['warn', {max: 5}],
173
155
  'unicorn/no-array-reduce': 'off', // TODO: consider enabling
174
156
  'unicorn/no-array-sort': 'off', // TODO: consider enabling
175
157
  'unicorn/no-hex-escape': 'off',
@@ -231,19 +213,19 @@ export const linter = defineConfig({
231
213
  linkComponents: [],
232
214
  version: undefined,
233
215
  },
234
- jsdoc: {
235
- ignorePrivate: false,
236
- ignoreInternal: false,
237
- ignoreReplacesDocs: true,
238
- overrideReplacesDocs: true,
239
- augmentsExtendsReplacesDocs: false,
240
- implementsReplacesDocs: false,
241
- exemptDestructuredRootsFromChecks: false,
242
- tagNamePreference: {},
243
- },
244
- vitest: {
245
- typecheck: false,
246
- },
216
+ jsdoc: {
217
+ ignorePrivate: false,
218
+ ignoreInternal: false,
219
+ ignoreReplacesDocs: true,
220
+ overrideReplacesDocs: true,
221
+ augmentsExtendsReplacesDocs: false,
222
+ implementsReplacesDocs: false,
223
+ exemptDestructuredRootsFromChecks: false,
224
+ tagNamePreference: {},
225
+ },
226
+ vitest: {
227
+ typecheck: false,
228
+ },
247
229
  },
248
230
  env: {
249
231
  builtin: true,
@@ -0,0 +1,36 @@
1
+ name: GitHub Pages
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+
7
+ jobs:
8
+ deploy:
9
+ runs-on: ubuntu-latest
10
+ permissions:
11
+ contents: read
12
+ pages: write
13
+ id-token: write
14
+ environment:
15
+ name: github-pages
16
+ url: ${{ steps.deployment.outputs.page_url }}
17
+ steps:
18
+ - uses: actions/checkout@v7
19
+ - name: Setup Node.js
20
+ uses: actions/setup-node@v7
21
+ with:
22
+ node-version: 22.x
23
+ cache: "{{packageManager}}"
24
+ - name: Install dependencies
25
+ run: "{{installCommand}}"
26
+ - name: Build demo
27
+ run: "{{packageManager}} run build -- --base=/{{projectName}}/"
28
+ - name: Build API docs
29
+ run: "{{packageManager}} run docs:api"
30
+ - name: Stage API docs
31
+ run: mkdir -p dist-demo/docs && mv docs/api dist-demo/docs/api
32
+ - uses: actions/upload-pages-artifact@v4
33
+ with:
34
+ path: dist-demo
35
+ - id: deployment
36
+ uses: actions/deploy-pages@v4
@@ -0,0 +1,21 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>{{projectName}}</title>
7
+ <link rel="stylesheet" href="/src/styles/index.css" />
8
+ <link rel="stylesheet" href="/src/demo/demo-shell.css" />
9
+ </head>
10
+ <body>
11
+ <main class="demo-shell">
12
+ <section class="demo-hero" aria-labelledby="demo-title">
13
+ <p class="demo-eyebrow">Native TypeScript widget</p>
14
+ <h1 id="demo-title">{{projectName}}</h1>
15
+ <p class="demo-copy">A dependency-free widget package with a Vite demo, API docs, and npm-ready builds.</p>
16
+ <div id="widget-host" class="demo-card"></div>
17
+ </section>
18
+ </main>
19
+ <script type="module" src="/src/demo/app.ts"></script>
20
+ </body>
21
+ </html>
@@ -0,0 +1,53 @@
1
+ {
2
+ "private": false,
3
+ "files": ["dist", "CHANGELOG.md"],
4
+ "main": "./dist/index.mjs",
5
+ "module": "./dist/index.mjs",
6
+ "types": "./dist/index.d.mts",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./dist/index.d.mts",
10
+ "import": "./dist/index.mjs",
11
+ "default": "./dist/index.mjs"
12
+ },
13
+ "./react": {
14
+ "types": "./dist/react.d.mts",
15
+ "import": "./dist/react.mjs",
16
+ "default": "./dist/react.mjs"
17
+ },
18
+ "./styles.css": "./dist/styles/index.css"
19
+ },
20
+ "scripts": {
21
+ "typecheck": "tsc --noEmit",
22
+ "lint": "oxlint",
23
+ "lint:css": "stylelint 'src/{demo,styles}/**/*.css'",
24
+ "lint:css:fix": "stylelint --fix 'src/{demo,styles}/**/*.css'",
25
+ "format": "oxfmt --write && stylelint --fix 'src/{demo,styles}/**/*.css'",
26
+ "format:check": "oxfmt --check",
27
+ "ci": "{{packageManager}} run typecheck && {{packageManager}} run lint && {{packageManager}} run lint:css && {{packageManager}} run format:check && {{packageManager}} run build:lib && {{packageManager}} run build && {{packageManager}} run docs:api && {{packageManager}} run test",
28
+ "build:lib": "tsdown",
29
+ "build:demo": "vite build",
30
+ "docs:api": "typedoc",
31
+ "screenshot": "node scripts/screenshot.mjs"
32
+ },
33
+ "devDependencies": {
34
+ "vite": "",
35
+ "vitest": "",
36
+ "@vitest/browser": "",
37
+ "@vitest/browser-playwright": "",
38
+ "playwright": "",
39
+ "@playwright/test": "",
40
+ "tsdown": "",
41
+ "typedoc": "",
42
+ "stylelint": "",
43
+ "stylelint-config-standard": "",
44
+ "typescript": "6.0.3",
45
+ "react": "",
46
+ "react-dom": "",
47
+ "@types/react": "",
48
+ "@types/react-dom": ""
49
+ },
50
+ "peerDependencies": {
51
+ "react": ">=18.0.0"
52
+ }
53
+ }
@@ -0,0 +1,29 @@
1
+ import {defineConfig, devices} from '@playwright/test';
2
+ import {env} from 'node:process';
3
+
4
+ const isCi = env.CI !== undefined;
5
+
6
+ export default defineConfig({
7
+ testDir: './tests/e2e',
8
+ testMatch: '**/*.e2e-test.ts',
9
+ fullyParallel: true,
10
+ forbidOnly: isCi,
11
+ retries: isCi ? 2 : 0,
12
+ workers: isCi ? 1 : undefined,
13
+ reporter: 'html',
14
+ use: {
15
+ baseURL: 'http://localhost:4173',
16
+ trace: 'on-first-retry',
17
+ },
18
+ projects: [
19
+ {
20
+ name: 'chromium',
21
+ use: {...devices['Desktop Chrome']},
22
+ },
23
+ ],
24
+ webServer: {
25
+ command: '{{packageManager}} run build && {{packageManager}} run start',
26
+ url: 'http://localhost:4173',
27
+ reuseExistingServer: !isCi,
28
+ },
29
+ });
@@ -0,0 +1,35 @@
1
+ import {mkdir} from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import {cwd} from 'node:process';
4
+
5
+ import {chromium} from 'playwright';
6
+ import {createServer} from 'vite';
7
+
8
+ const PORT = 4199;
9
+
10
+ try {
11
+ const server = await createServer({
12
+ root: cwd(),
13
+ server: {port: PORT, strictPort: true},
14
+ });
15
+ await server.listen();
16
+
17
+ try {
18
+ const browser = await chromium.launch();
19
+ const page = await browser.newPage({viewport: {width: 1280, height: 800}});
20
+
21
+ await page.goto(`http://localhost:${PORT}`);
22
+ await page.waitForSelector('.widget');
23
+ const outputPath = path.resolve(cwd(), 'docs/images/widget-demo.png');
24
+ await mkdir(path.dirname(outputPath), {recursive: true});
25
+ await page.locator('.demo-hero').screenshot({path: outputPath, type: 'png'});
26
+
27
+ console.log(`Screenshot saved to ${outputPath}`);
28
+ await browser.close();
29
+ } finally {
30
+ await server.close();
31
+ }
32
+ } catch (error) {
33
+ console.error(String(error));
34
+ throw error;
35
+ }
@@ -0,0 +1,22 @@
1
+ import {createWidget} from '{{projectName}}';
2
+
3
+ const init = (): void => {
4
+ const host = document.querySelector<HTMLElement>('#widget-host');
5
+ if (host === null) {
6
+ throw new Error('Missing #widget-host element');
7
+ }
8
+
9
+ createWidget(host, {
10
+ title: '{{projectName}}',
11
+ message: 'Built with TypeScript, tsdown, Vite, TypeDoc, and Playwright.',
12
+ onAction: (count) => {
13
+ console.info(`Widget action ${count}`);
14
+ },
15
+ });
16
+ };
17
+
18
+ if (document.readyState === 'loading') {
19
+ document.addEventListener('DOMContentLoaded', init);
20
+ } else {
21
+ init();
22
+ }
@@ -0,0 +1,50 @@
1
+ body {
2
+ min-width: 320px;
3
+ min-height: 100vh;
4
+ margin: 0;
5
+ font-family: Inter, ui-sans-serif, system-ui, sans-serif;
6
+ color: #0f172a;
7
+ background: #f8fafc;
8
+ }
9
+
10
+ .demo-shell {
11
+ display: grid;
12
+ min-height: 100vh;
13
+ place-items: center;
14
+ padding: 2rem;
15
+ }
16
+
17
+ .demo-hero {
18
+ display: grid;
19
+ gap: 1rem;
20
+ width: min(100%, 52rem);
21
+ }
22
+
23
+ .demo-eyebrow {
24
+ margin: 0;
25
+ font-size: 0.8rem;
26
+ font-weight: 800;
27
+ letter-spacing: 0.12em;
28
+ color: #7c3aed;
29
+ text-transform: uppercase;
30
+ }
31
+
32
+ .demo-hero h1 {
33
+ max-width: 11ch;
34
+ margin: 0;
35
+ font-size: clamp(3rem, 12vw, 6.5rem);
36
+ line-height: 0.9;
37
+ letter-spacing: -0.08em;
38
+ }
39
+
40
+ .demo-copy {
41
+ max-width: 42rem;
42
+ margin: 0;
43
+ font-size: 1.1rem;
44
+ line-height: 1.6;
45
+ color: #475569;
46
+ }
47
+
48
+ .demo-card {
49
+ margin-top: 1.5rem;
50
+ }
@@ -0,0 +1,2 @@
1
+ export {createWidget, DemoWidget} from './widget.ts';
2
+ export type {DemoWidgetInstance, DemoWidgetOptions} from './widget.ts';
@@ -0,0 +1,54 @@
1
+ import {forwardRef, useEffect, useRef, type ForwardedRef, type ReactElement} from 'react';
2
+
3
+ import {DemoWidget as NativeDemoWidget, type DemoWidgetInstance, type DemoWidgetOptions} from './widget.ts';
4
+
5
+ export type DemoWidgetReactProps = DemoWidgetOptions & {
6
+ readonly message?: string;
7
+ };
8
+
9
+ const setForwardedRef = (ref: ForwardedRef<DemoWidgetInstance>, value: DemoWidgetInstance | null): void => {
10
+ if (typeof ref === 'function') {
11
+ ref(value);
12
+ return;
13
+ }
14
+ if (ref !== null) {
15
+ ref.current = value;
16
+ }
17
+ };
18
+
19
+ export const DemoWidget = forwardRef<DemoWidgetInstance, DemoWidgetReactProps>((props, ref): ReactElement => {
20
+ const {message, ...mountOptions} = props;
21
+ const hostRef = useRef<HTMLDivElement | null>(null);
22
+ const instanceRef = useRef<DemoWidgetInstance | null>(null);
23
+
24
+ useEffect(() => {
25
+ const host = hostRef.current;
26
+ if (host === null) {
27
+ return (): void => {
28
+ setForwardedRef(ref, null);
29
+ };
30
+ }
31
+
32
+ const instance = new NativeDemoWidget(host, {...mountOptions, ...(message === undefined ? {} : {message})});
33
+ instanceRef.current = instance;
34
+ setForwardedRef(ref, instance);
35
+
36
+ return (): void => {
37
+ instanceRef.current = null;
38
+ setForwardedRef(ref, null);
39
+ instance.destroy();
40
+ };
41
+ // Mount-only options are intentionally captured once. Mutable props are synced below through native setters.
42
+ // oxlint-disable-next-line react-hooks/exhaustive-deps
43
+ }, []);
44
+
45
+ useEffect(() => {
46
+ if (message !== undefined) {
47
+ instanceRef.current?.setMessage(message);
48
+ }
49
+ }, [message]);
50
+
51
+ return <div ref={hostRef} />;
52
+ });
53
+
54
+ DemoWidget.displayName = 'DemoWidget';
@@ -0,0 +1,7 @@
1
+ declare global {
2
+ var IS_REACT_ACT_ENVIRONMENT: boolean;
3
+ }
4
+
5
+ globalThis.IS_REACT_ACT_ENVIRONMENT = true;
6
+
7
+ export const reactActEnvironmentConfigured = true;
@@ -0,0 +1,33 @@
1
+ import {describe, expect, it} from 'vitest';
2
+ import {createWidget} from './widget.ts';
3
+
4
+ describe('DemoWidget', () => {
5
+ it('renders and handles actions', async () => {
6
+ const host = document.createElement('div');
7
+ const actions: number[] = [];
8
+ const widget = createWidget(host, {
9
+ title: 'Test Widget',
10
+ onAction: (count) => {
11
+ actions.push(count);
12
+ },
13
+ });
14
+
15
+ expect(host.querySelector('.widget__title')?.textContent).toBe('Test Widget');
16
+ await widget.increment();
17
+
18
+ expect(host.querySelector('.widget__message')?.textContent).toBe('Clicked 1 time.');
19
+ expect(actions).toStrictEqual([1]);
20
+ });
21
+
22
+ it('throws after destroy', () => {
23
+ const host = document.createElement('div');
24
+ const widget = createWidget(host);
25
+
26
+ widget.destroy();
27
+
28
+ expect(() => {
29
+ widget.setMessage('late update');
30
+ }).toThrow('Widget has been destroyed.');
31
+ expect(host.children).toHaveLength(0);
32
+ });
33
+ });
@@ -0,0 +1,83 @@
1
+ export type DemoWidgetOptions = {
2
+ readonly title?: string;
3
+ readonly message?: string;
4
+ readonly actionLabel?: string;
5
+ readonly onAction?: (count: number) => void | Promise<void>;
6
+ };
7
+
8
+ export type DemoWidgetInstance = {
9
+ readonly increment: () => Promise<void>;
10
+ readonly setMessage: (message: string) => void;
11
+ readonly destroy: () => void;
12
+ };
13
+
14
+ const DEFAULT_TITLE = 'Native Widget';
15
+ const DEFAULT_MESSAGE = 'Ready to publish on npm.';
16
+ const DEFAULT_ACTION_LABEL = 'Increment';
17
+
18
+ export class DemoWidget implements DemoWidgetInstance {
19
+ readonly #host: HTMLElement;
20
+ readonly #root: HTMLDivElement;
21
+ readonly #message: HTMLParagraphElement;
22
+ readonly #button: HTMLButtonElement;
23
+ readonly #onAction: ((count: number) => void | Promise<void>) | undefined;
24
+ #count = 0;
25
+ #destroyed = false;
26
+
27
+ public constructor(host: HTMLElement, options: DemoWidgetOptions = {}) {
28
+ this.#host = host;
29
+ this.#onAction = options.onAction;
30
+ this.#root = document.createElement('div');
31
+ this.#root.className = 'widget';
32
+
33
+ const title = document.createElement('h2');
34
+ title.className = 'widget__title';
35
+ title.textContent = options.title ?? DEFAULT_TITLE;
36
+
37
+ this.#message = document.createElement('p');
38
+ this.#message.className = 'widget__message';
39
+ this.#message.textContent = options.message ?? DEFAULT_MESSAGE;
40
+
41
+ this.#button = document.createElement('button');
42
+ this.#button.className = 'widget__button';
43
+ this.#button.type = 'button';
44
+ this.#button.textContent = options.actionLabel ?? DEFAULT_ACTION_LABEL;
45
+ this.#button.addEventListener('click', this.#handleClick);
46
+
47
+ this.#root.append(title, this.#message, this.#button);
48
+ this.#host.append(this.#root);
49
+ }
50
+
51
+ public readonly increment = async (): Promise<void> => {
52
+ this.#assertActive();
53
+ this.#count += 1;
54
+ this.#message.textContent = `Clicked ${this.#count} time${this.#count === 1 ? '' : 's'}.`;
55
+ await this.#onAction?.(this.#count);
56
+ };
57
+
58
+ public readonly setMessage = (message: string): void => {
59
+ this.#assertActive();
60
+ this.#message.textContent = message;
61
+ };
62
+
63
+ public readonly destroy = (): void => {
64
+ if (this.#destroyed) {
65
+ return;
66
+ }
67
+ this.#button.removeEventListener('click', this.#handleClick);
68
+ this.#root.remove();
69
+ this.#destroyed = true;
70
+ };
71
+
72
+ readonly #handleClick = (): void => {
73
+ void this.increment();
74
+ };
75
+
76
+ #assertActive(): void {
77
+ if (this.#destroyed) {
78
+ throw new Error('Widget has been destroyed.');
79
+ }
80
+ }
81
+ }
82
+
83
+ export const createWidget = (host: HTMLElement, options?: DemoWidgetOptions): DemoWidgetInstance => new DemoWidget(host, options);
@@ -0,0 +1,42 @@
1
+ .widget {
2
+ --widget-bg: #121826;
3
+ --widget-fg: #f8fafc;
4
+ --widget-muted: #a5b4fc;
5
+ --widget-accent: #8b5cf6;
6
+
7
+ display: grid;
8
+ gap: 0.85rem;
9
+ max-width: 28rem;
10
+ padding: 1.25rem;
11
+ color: var(--widget-fg);
12
+ background: linear-gradient(135deg, var(--widget-bg), #312e81);
13
+ border-radius: 1.25rem;
14
+ box-shadow: 0 1.25rem 3rem rgba(15, 23, 42, 0.24);
15
+ }
16
+
17
+ .widget__title {
18
+ margin: 0;
19
+ font-size: 1.5rem;
20
+ line-height: 1.1;
21
+ }
22
+
23
+ .widget__message {
24
+ margin: 0;
25
+ color: var(--widget-muted);
26
+ }
27
+
28
+ .widget__button {
29
+ justify-self: start;
30
+ padding: 0.7rem 1rem;
31
+ font: inherit;
32
+ font-weight: 700;
33
+ color: var(--widget-fg);
34
+ cursor: pointer;
35
+ background: var(--widget-accent);
36
+ border: 0;
37
+ border-radius: 999px;
38
+ }
39
+
40
+ .widget__button:hover {
41
+ background: #7c3aed;
42
+ }
@@ -0,0 +1,16 @@
1
+ /** @type {import('stylelint').Config} */
2
+ const config = {
3
+ extends: ['stylelint-config-standard'],
4
+ rules: {
5
+ 'alpha-value-notation': 'number',
6
+ 'color-function-notation': 'legacy',
7
+ 'declaration-empty-line-before': null,
8
+ 'font-family-name-quotes': null,
9
+ 'media-feature-range-notation': 'context',
10
+ 'number-max-precision': 4,
11
+ 'rule-empty-line-before': null,
12
+ 'selector-not-notation': 'simple',
13
+ },
14
+ };
15
+
16
+ export default config;
@@ -0,0 +1,15 @@
1
+ import baseConfig from './stylelint.base.config.js';
2
+
3
+ const widgetCssPrefix = 'widget';
4
+
5
+ /** @type {import('stylelint').Config} */
6
+ const config = {
7
+ ...baseConfig,
8
+ rules: {
9
+ ...baseConfig.rules,
10
+ 'custom-property-pattern': `^(${widgetCssPrefix}|demo)-[a-z][a-z0-9-]*$`,
11
+ 'selector-class-pattern': [`^(${widgetCssPrefix}(?:[a-zA-Z0-9_-]+)?|demo-[a-zA-Z0-9_-]+)$`, {resolveNestedSelectors: true}],
12
+ },
13
+ };
14
+
15
+ export default config;
@@ -0,0 +1,16 @@
1
+ import {expect, test} from '@playwright/test';
2
+
3
+ test.describe('widget demo', () => {
4
+ test.beforeEach(async ({page}) => {
5
+ await page.goto('/');
6
+ });
7
+
8
+ test('renders the widget and increments from the demo', async ({page}) => {
9
+ await expect(page.locator('.widget')).toBeVisible();
10
+ await expect(page.locator('.widget__title')).toHaveText('{{projectName}}');
11
+
12
+ await page.locator('.widget__button').click();
13
+
14
+ await expect(page.locator('.widget__message')).toHaveText('Clicked 1 time.');
15
+ });
16
+ });
@@ -0,0 +1,20 @@
1
+ import {defineConfig} from 'tsdown';
2
+
3
+ export default defineConfig({
4
+ entry: {
5
+ index: 'src/lib/index.ts',
6
+ react: 'src/lib/react.tsx',
7
+ },
8
+ deps: {
9
+ neverBundle: ['react', 'react/jsx-runtime'],
10
+ },
11
+ format: ['esm'],
12
+ dts: true,
13
+ fixedExtension: true,
14
+ outDir: 'dist',
15
+ clean: true,
16
+ target: 'es2022',
17
+ platform: 'neutral',
18
+ sourcemap: true,
19
+ copy: [{from: 'src/styles/index.css', to: 'dist/styles', flatten: true}],
20
+ });
@@ -0,0 +1,12 @@
1
+ {
2
+ "entryPoints": ["src/lib/index.ts", "src/lib/react.tsx"],
3
+ "out": "docs/api",
4
+ "exclude": ["**/*.test.ts", "**/*.test.tsx", "tests/**", "src/demo/**"],
5
+ "excludeInternal": true,
6
+ "excludePrivate": true,
7
+ "excludeProtected": true,
8
+ "readme": "none",
9
+ "plugin": [],
10
+ "tsconfig": "tsconfig.json",
11
+ "logLevel": "Warn"
12
+ }