@govela/create 0.0.0-stage → 0.7.0

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 (68) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +17 -2
  3. package/bin/create.js +146 -0
  4. package/package.json +24 -4
  5. package/templates/app/base/AGENTS.md +44 -0
  6. package/templates/app/base/README.md +19 -0
  7. package/templates/app/base/_github/workflows/ci.yml +23 -0
  8. package/templates/app/base/_gitignore +7 -0
  9. package/templates/app/base/cmd/server/main.go +53 -0
  10. package/templates/app/base/go.mod +8 -0
  11. package/templates/app/base/server/routes/load.go +30 -0
  12. package/templates/app/base/vela.config.json +6 -0
  13. package/templates/app/base/vite.config.js +11 -0
  14. package/templates/app/base/vitest.setup.__EXT__ +5 -0
  15. package/templates/app/js/eslint.config.js +12 -0
  16. package/templates/app/js/package.json +32 -0
  17. package/templates/app/js/src/routes/Layout.vela +24 -0
  18. package/templates/app/js/src/routes/Page.test.js +13 -0
  19. package/templates/app/js/src/routes/Page.vela +10 -0
  20. package/templates/app/ts/eslint.config.js +14 -0
  21. package/templates/app/ts/package.json +34 -0
  22. package/templates/app/ts/src/routes/Layout.vela +26 -0
  23. package/templates/app/ts/src/routes/Page.test.ts +13 -0
  24. package/templates/app/ts/src/routes/Page.vela +12 -0
  25. package/templates/app/ts/tsconfig.json +4 -0
  26. package/templates/app/ts/vela.app.json +1 -0
  27. package/templates/default/base/AGENTS.md +28 -0
  28. package/templates/default/base/README.md +17 -0
  29. package/templates/default/base/_github/workflows/ci.yml +20 -0
  30. package/templates/default/base/_gitignore +2 -0
  31. package/templates/default/base/index.html +12 -0
  32. package/templates/default/base/vite.config.js +9 -0
  33. package/templates/default/base/vitest.setup.__EXT__ +5 -0
  34. package/templates/default/js/eslint.config.js +12 -0
  35. package/templates/default/js/package.json +28 -0
  36. package/templates/default/js/src/App.vela +25 -0
  37. package/templates/default/js/src/Counter.test.js +20 -0
  38. package/templates/default/js/src/Counter.vela +23 -0
  39. package/templates/default/js/src/main.js +4 -0
  40. package/templates/default/ts/eslint.config.js +14 -0
  41. package/templates/default/ts/package.json +30 -0
  42. package/templates/default/ts/src/App.vela +25 -0
  43. package/templates/default/ts/src/Counter.test.ts +20 -0
  44. package/templates/default/ts/src/Counter.vela +27 -0
  45. package/templates/default/ts/src/main.ts +4 -0
  46. package/templates/default/ts/tsconfig.json +18 -0
  47. package/templates/ssr/base/AGENTS.md +34 -0
  48. package/templates/ssr/base/README.md +30 -0
  49. package/templates/ssr/base/_github/workflows/ci.yml +23 -0
  50. package/templates/ssr/base/_gitignore +4 -0
  51. package/templates/ssr/base/cmd/server/main.go +57 -0
  52. package/templates/ssr/base/go.mod +5 -0
  53. package/templates/ssr/base/vela.config.json +6 -0
  54. package/templates/ssr/base/vite.config.js +11 -0
  55. package/templates/ssr/base/vitest.setup.__EXT__ +5 -0
  56. package/templates/ssr/js/eslint.config.js +12 -0
  57. package/templates/ssr/js/package.json +30 -0
  58. package/templates/ssr/js/src/App.vela +27 -0
  59. package/templates/ssr/js/src/Counter.test.js +20 -0
  60. package/templates/ssr/js/src/Counter.vela +23 -0
  61. package/templates/ssr/js/src/client.js +5 -0
  62. package/templates/ssr/ts/eslint.config.js +14 -0
  63. package/templates/ssr/ts/package.json +32 -0
  64. package/templates/ssr/ts/src/App.vela +27 -0
  65. package/templates/ssr/ts/src/Counter.test.ts +20 -0
  66. package/templates/ssr/ts/src/Counter.vela +27 -0
  67. package/templates/ssr/ts/src/client.ts +5 -0
  68. package/templates/ssr/ts/tsconfig.json +18 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 abullo
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,18 @@
1
- # Temporary Holding Version
1
+ # @govela/create
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Creates a Vela application.
4
+
5
+ ```sh
6
+ npm create @govela my-app # Vite, Vitest with @govela/testing
7
+ npm create @govela my-app -- --ssr # a Go server renders, Vite builds the client
8
+ npm create @govela my-app -- --app # a Vela App: routes with Go loaders
9
+ ```
10
+
11
+ The projects are TypeScript (`vela check --types`, typed tests); `--js`
12
+ writes JavaScript. `--module example.com/my-app` sets the Go module path
13
+ of `--ssr` and `--app`. Every project has a component test, ESLint with
14
+ `@govela/eslint-plugin`, `npm run check` (`vela check`), a GitHub Actions
15
+ workflow running the check, the linter, the tests and the build, and an
16
+ `AGENTS.md` pointing to the Vela authoring guide.
17
+
18
+ License: MIT.
package/bin/create.js ADDED
@@ -0,0 +1,146 @@
1
+ #!/usr/bin/env node
2
+ // npm create @govela [directory] -- [--ssr | --app] [--js] [--module path]
3
+ //
4
+ // Creates a Vela application from one of the templates of this package
5
+ // (tasks 066, 088): default (Vite), ssr (a Go server renders, Vite builds
6
+ // the client) or app (a Vela App with routes and Go loaders), in TypeScript
7
+ // or, with --js, JavaScript. A template is templates/<name>/base, the files
8
+ // of both languages, and templates/<name>/ts or /js on top of it.
9
+ import { existsSync, mkdirSync, readdirSync, readFileSync, realpathSync, statSync, writeFileSync } from 'node:fs';
10
+ import { basename, dirname, join, relative, resolve } from 'node:path';
11
+ import { fileURLToPath } from 'node:url';
12
+
13
+ const root = dirname(dirname(fileURLToPath(import.meta.url)));
14
+ const manifest = JSON.parse(readFileSync(join(root, 'package.json'), 'utf8'));
15
+
16
+ const usage = `usage: npm create @govela [directory] -- [options]
17
+
18
+ options:
19
+ --ssr server rendering: a Go server renders, Vite builds the client
20
+ --app a Vela App: file-based routes with Go loaders
21
+ --js JavaScript instead of TypeScript
22
+ --ts TypeScript (the default)
23
+ --module path the Go module path of --ssr and --app (example.com/<name>)
24
+ --help this text
25
+ `;
26
+
27
+ function fail(message) {
28
+ process.stderr.write(`create-vela: ${message}\n`);
29
+ process.exit(1);
30
+ }
31
+
32
+ export function parseArgs(argv) {
33
+ const options = { template: 'default', directory: null, module: null, language: 'ts' };
34
+ for (let i = 0; i < argv.length; i++) {
35
+ const arg = argv[i];
36
+ switch (arg) {
37
+ case '--ssr':
38
+ case '--app': {
39
+ const template = arg.slice(2);
40
+ if (options.template !== 'default' && options.template !== template) throw new Error('--ssr and --app exclude each other');
41
+ options.template = template;
42
+ break;
43
+ }
44
+ case '--js':
45
+ case '--ts':
46
+ options.language = arg.slice(2);
47
+ break;
48
+ case '--module':
49
+ if (!argv[i + 1]) throw new Error('--module needs a Go module path');
50
+ options.module = argv[++i];
51
+ break;
52
+ case '--help':
53
+ case '-h':
54
+ options.help = true;
55
+ break;
56
+ default:
57
+ if (arg.startsWith('-')) throw new Error(`unknown option ${arg}`);
58
+ if (options.directory) throw new Error(`a second directory ${arg}`);
59
+ options.directory = arg;
60
+ }
61
+ }
62
+ return options;
63
+ }
64
+
65
+ // packageName turns a directory name into a valid npm package name.
66
+ function packageName(dir) {
67
+ const name = basename(dir).toLowerCase().replace(/[^a-z0-9._~-]+/g, '-').replace(/^[._-]+|-+$/g, '');
68
+ return name || 'vela-app';
69
+ }
70
+
71
+ // Files that npm would rename or drop when publishing are stored with a
72
+ // leading underscore.
73
+ function outputName(name) {
74
+ if (name === '_gitignore') return '.gitignore';
75
+ if (name === '_github') return '.github';
76
+ return name;
77
+ }
78
+
79
+ function substitute(text, values) {
80
+ for (const [key, value] of Object.entries(values)) text = text.split(key).join(value);
81
+ return text;
82
+ }
83
+
84
+ function copyTemplate(from, to, values) {
85
+ mkdirSync(to, { recursive: true });
86
+ for (const name of readdirSync(from)) {
87
+ const source = join(from, name);
88
+ const target = join(to, substitute(outputName(name), values));
89
+ if (statSync(source).isDirectory()) {
90
+ copyTemplate(source, target, values);
91
+ continue;
92
+ }
93
+ writeFileSync(target, substitute(readFileSync(source, 'utf8'), values));
94
+ }
95
+ }
96
+
97
+ export function create(options) {
98
+ const directory = resolve(options.directory || 'vela-app');
99
+ if (existsSync(directory) && readdirSync(directory).length > 0) {
100
+ throw new Error(`${directory} is not empty`);
101
+ }
102
+ const name = packageName(directory);
103
+ const module = options.module || `example.com/${name}`;
104
+ if (!/^[A-Za-z0-9._~\/-]+$/.test(module)) throw new Error(`${module} is not a Go module path`);
105
+ const values = {
106
+ __NAME__: name,
107
+ __MODULE__: module,
108
+ __VERSION__: manifest.version,
109
+ __APP_VERSION__: manifest.govela.app,
110
+ __EXT__: options.language,
111
+ };
112
+ const template = join(root, 'templates', options.template);
113
+ copyTemplate(join(template, 'base'), directory, values);
114
+ copyTemplate(join(template, options.language), directory, values);
115
+ return { directory, name, template: options.template, language: options.language };
116
+ }
117
+
118
+ function main() {
119
+ let options;
120
+ try {
121
+ options = parseArgs(process.argv.slice(2));
122
+ } catch (err) {
123
+ process.stderr.write(usage);
124
+ fail(err.message);
125
+ }
126
+ if (options.help) {
127
+ process.stdout.write(usage);
128
+ return;
129
+ }
130
+ let result;
131
+ try {
132
+ result = create(options);
133
+ } catch (err) {
134
+ fail(err.message);
135
+ }
136
+ const dir = relative(process.cwd(), result.directory) || '.';
137
+ const steps = [`cd ${dir}`, 'npm install'];
138
+ if (result.template === 'ssr') steps.push('npx vela', 'go mod tidy', 'npm run dev:vela & npm run dev & npm run dev:server');
139
+ else if (result.template === 'app') steps.push('npx vela-app gen && npx vela', 'go mod tidy', 'npm run dev');
140
+ else steps.push('npm run dev');
141
+ const language = result.language === 'ts' ? 'TypeScript' : 'JavaScript';
142
+ process.stdout.write(`Created ${result.name} (${result.template}, ${language}) in ${result.directory}\n\nNext:\n${steps.map((s) => ` ${s}\n`).join('')}`);
143
+ }
144
+
145
+ // npm runs the command through a link in node_modules/.bin.
146
+ if (process.argv[1] && realpathSync(process.argv[1]) === realpathSync(fileURLToPath(import.meta.url))) main();
package/package.json CHANGED
@@ -1,6 +1,26 @@
1
1
  {
2
2
  "name": "@govela/create",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.7.0",
4
+ "description": "Creates a Vela application: npm create @govela. Manifest generated by the compiler repository; do not edit.",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/elauven/vela.git",
9
+ "directory": "packages/create"
10
+ },
11
+ "type": "module",
12
+ "bin": {
13
+ "create-govela": "bin/create.js"
14
+ },
15
+ "files": [
16
+ "bin",
17
+ "templates",
18
+ "LICENSE"
19
+ ],
20
+ "engines": {
21
+ "node": "^20.19.0 || >=22.12.0"
22
+ },
23
+ "govela": {
24
+ "app": "0.3.0"
25
+ }
26
+ }
@@ -0,0 +1,44 @@
1
+ # Agent instructions
2
+
3
+ This is a Vela App: file-based routes in `src/routes`, rendered on the
4
+ server by Go and hydrated in the browser. Before you create or edit a
5
+ `.vela` component, read the Vela authoring guide of the compiler version
6
+ this project uses:
7
+
8
+ https://github.com/elauven/vela/blob/v__VERSION__/AGENTS.md
9
+
10
+ and, for routes, loaders and actions:
11
+
12
+ https://github.com/elauven/vela/blob/v__VERSION__/docs/APP.md
13
+
14
+ When a compiler diagnostic disagrees with an example, trust the diagnostic.
15
+
16
+ ## This project
17
+
18
+ - `src/routes/**/Page.vela` are pages, `Layout.vela` wraps them. The Go
19
+ loaders in `server/routes` (the same tree) return each component's
20
+ `data` prop; `npx vela-app scaffold` adds the missing ones.
21
+ - `src/_app`, `internal/appgen` and `out/` are generated; never edit them.
22
+ - A component at any depth reads the route, the form and the data of the
23
+ page with `getPage()` from `#app/page.js` (call it in the script; keep it
24
+ as `$state.deep(getPage())` to follow navigation) instead of passing
25
+ `route` and `data` down as props.
26
+ - Permission checks go in `guard.go` (`func Guard(ctx, r) error`, `npx
27
+ vela-app scaffold -guard /route`), never in a layout loader: a guard runs
28
+ before every loader and action below its directory. Read the user from
29
+ an `app.Local` set by middleware.
30
+ - Code that runs during the first render is translated to Go: keep it free
31
+ of browser APIs. Treat every `VELA-SSR-*` diagnostic as an error to fix.
32
+ - Tests are `*.test.__EXT__` files next to the components, run by Vitest with
33
+ `@govela/testing` (`render`, `fireEvent`).
34
+
35
+ ## Before handing off
36
+
37
+ ```sh
38
+ npm run check # vela-app gen and vela check --ssr (and --types)
39
+ npm run lint # eslint: the code of the components and modules
40
+ npm test # vitest run
41
+ npm run build # vela-app build: the server and the client into build/
42
+ ```
43
+
44
+ All of them must pass.
@@ -0,0 +1,19 @@
1
+ # __NAME__
2
+
3
+ A [Vela App](https://github.com/elauven/vela/blob/main/docs/VELA_APP.md):
4
+ routes in `src/routes`, Go loaders in `server/routes`, the server in
5
+ `cmd/server`.
6
+
7
+ ```sh
8
+ npm install
9
+ npx vela-app gen && npx vela # once, so that go mod tidy sees the generated packages
10
+ go mod tidy
11
+ npm run dev # http://127.0.0.1:8080
12
+ ```
13
+
14
+ ```sh
15
+ npm test # component tests
16
+ npm run check # compiler and server-rendering diagnostics
17
+ npm run lint # eslint
18
+ npm run build # build/: the server binary and the client
19
+ ```
@@ -0,0 +1,23 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ pull_request:
6
+
7
+ jobs:
8
+ test:
9
+ runs-on: ubuntu-latest
10
+ steps:
11
+ - uses: actions/checkout@v5
12
+ - uses: actions/setup-go@v6
13
+ with:
14
+ go-version-file: go.mod
15
+ - uses: actions/setup-node@v5
16
+ with:
17
+ node-version: '24'
18
+ cache: npm
19
+ - run: npm ci
20
+ - run: npm run check
21
+ - run: npm run lint
22
+ - run: npm test
23
+ - run: npm run build
@@ -0,0 +1,7 @@
1
+ # Generated by vela-app gen, vela, vite build and vela-app build.
2
+ /src/_app/
3
+ /internal/appgen/
4
+ /out/
5
+ /dist/
6
+ /build/
7
+ /node_modules/
@@ -0,0 +1,53 @@
1
+ package main
2
+
3
+ import (
4
+ "flag"
5
+ "fmt"
6
+ "log"
7
+ "net"
8
+ "net/http"
9
+ "os"
10
+ "path/filepath"
11
+
12
+ "__MODULE__/internal/appgen"
13
+ "github.com/elauven/vela/app"
14
+ "github.com/elauven/vela/ssr_runtime/vite"
15
+ )
16
+
17
+ func main() {
18
+ addr := flag.String("addr", "127.0.0.1:8080", "listen address")
19
+ dev := flag.String("dev", "", "URL of the Vite dev server; serve dist/ when empty")
20
+ dist := flag.String("dist", "dist", `directory of vite build; "none" serves pages without client`)
21
+ flag.Parse()
22
+
23
+ mux := http.NewServeMux()
24
+ var assets *vite.Assets
25
+ switch {
26
+ case *dev != "":
27
+ assets = vite.Dev(*dev)
28
+ case *dist != "none":
29
+ var err error
30
+ if assets, err = vite.Load(filepath.Join(*dist, ".vite", "manifest.json"), "/"); err != nil {
31
+ log.Fatal(err)
32
+ }
33
+ mux.Handle("/assets/", http.FileServer(http.Dir(*dist)))
34
+ }
35
+ handler, err := app.New(app.Config{
36
+ Routes: appgen.Routes(),
37
+ Assets: assets,
38
+ Manifest: filepath.Join("out", "client", "vela.manifest.json"),
39
+ Head: app.Head{Title: "__NAME__"},
40
+ Dev: *dev != "",
41
+ })
42
+ if err != nil {
43
+ log.Fatal(err)
44
+ }
45
+ mux.Handle("/", handler)
46
+
47
+ listener, err := net.Listen("tcp", *addr)
48
+ if err != nil {
49
+ log.Fatal(err)
50
+ }
51
+ fmt.Fprintf(os.Stdout, "http://%s\n", listener.Addr())
52
+ log.Fatal(http.Serve(listener, mux))
53
+ }
@@ -0,0 +1,8 @@
1
+ module __MODULE__
2
+
3
+ go 1.25
4
+
5
+ require (
6
+ github.com/elauven/vela/app v__APP_VERSION__
7
+ github.com/elauven/vela/ssr_runtime v__VERSION__
8
+ )
@@ -0,0 +1,30 @@
1
+ // Package routes holds the loaders of src/routes: the function of a
2
+ // component returns its data prop.
3
+ package routes
4
+
5
+ import (
6
+ "context"
7
+
8
+ "github.com/elauven/vela/app"
9
+ )
10
+
11
+ // Site is the data of Layout.
12
+ type Site struct {
13
+ Site string `json:"site"`
14
+ }
15
+
16
+ // Greeting is the data of Page.
17
+ type Greeting struct {
18
+ Greeting string `json:"greeting"`
19
+ }
20
+
21
+ // Layout loads the data of every page.
22
+ func Layout(ctx context.Context, r *app.Request) (Site, error) {
23
+ return Site{Site: "__NAME__"}, nil
24
+ }
25
+
26
+ // Page loads the data of /.
27
+ func Page(ctx context.Context, r *app.Request) (Greeting, error) {
28
+ r.Head().Title = "Home"
29
+ return Greeting{Greeting: "Hello from Go"}, nil
30
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "project": "src",
3
+ "out": "out",
4
+ "ssr": true,
5
+ "ssrModule": "__MODULE__/out/server"
6
+ }
@@ -0,0 +1,11 @@
1
+ import vela from '@govela/unplugin/vite';
2
+
3
+ export default {
4
+ plugins: [vela()],
5
+ server: { origin: 'http://localhost:5173' },
6
+ build: { manifest: true, rollupOptions: { input: 'src/_app/client.js' } },
7
+ test: {
8
+ environment: 'happy-dom',
9
+ setupFiles: ['./vitest.setup.__EXT__'],
10
+ },
11
+ };
@@ -0,0 +1,5 @@
1
+ import { afterEach } from 'vitest';
2
+ import { cleanup } from '@govela/testing';
3
+
4
+ // Every test starts with an empty document.
5
+ afterEach(cleanup);
@@ -0,0 +1,12 @@
1
+ import { defineConfig } from 'eslint/config';
2
+ import js from '@eslint/js';
3
+ import globals from 'globals';
4
+ import vela from '@govela/eslint-plugin';
5
+
6
+ export default defineConfig(
7
+ { ignores: ['dist/', 'out/', 'build/', 'src/_app/'] },
8
+ js.configs.recommended,
9
+ vela.configs.recommended,
10
+ { files: ['src/**/*.js', '**/*.vela/*.js'], languageOptions: { globals: globals.browser } },
11
+ { files: ['*.config.js'], languageOptions: { globals: globals.node } },
12
+ );
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "__NAME__",
3
+ "private": true,
4
+ "type": "module",
5
+ "imports": {
6
+ "#app/*": "./src/_app/*"
7
+ },
8
+ "scripts": {
9
+ "gen": "vela-app gen",
10
+ "dev": "vela-app dev",
11
+ "check": "vela-app gen && vela check --project src --ssr",
12
+ "lint": "eslint .",
13
+ "test": "vitest run",
14
+ "build": "vela-app build"
15
+ },
16
+ "dependencies": {
17
+ "@govela/app": "__APP_VERSION__",
18
+ "@govela/runtime": "__VERSION__"
19
+ },
20
+ "devDependencies": {
21
+ "@eslint/js": "^10.0.1",
22
+ "@govela/compiler": "__VERSION__",
23
+ "@govela/eslint-plugin": "__VERSION__",
24
+ "@govela/testing": "__VERSION__",
25
+ "@govela/unplugin": "__VERSION__",
26
+ "eslint": "^10.14.0",
27
+ "globals": "^17.0.0",
28
+ "happy-dom": "^21.0.4",
29
+ "vite": "^8.3.5",
30
+ "vitest": "^5.1.0"
31
+ }
32
+ }
@@ -0,0 +1,24 @@
1
+ <script>
2
+ export let data = null;
3
+ export let route = null;
4
+ </script>
5
+
6
+ <template>
7
+ <div class="site">
8
+ <nav>
9
+ <a href="/" class:active={route.path === '/'}>{data.site}</a>
10
+ </nav>
11
+ <main><slot /></main>
12
+ </div>
13
+ </template>
14
+
15
+ <style>
16
+ .site {
17
+ font-family: system-ui, sans-serif;
18
+ max-width: 40rem;
19
+ margin: 2rem auto;
20
+ }
21
+ .active {
22
+ font-weight: bold;
23
+ }
24
+ </style>
@@ -0,0 +1,13 @@
1
+ import { expect, test } from 'vitest';
2
+ import { fireEvent, render } from '@govela/testing';
3
+ import Page from './Page.vela';
4
+
5
+ test('greets and counts clicks', async () => {
6
+ const { container } = render(Page, { props: { data: { greeting: 'Hello' } } });
7
+ expect(container.querySelector('h1').textContent).toBe('Hello');
8
+
9
+ const button = container.querySelector('button');
10
+ await fireEvent.click(button);
11
+
12
+ expect(button.textContent).toBe('Clicked 1 times');
13
+ });
@@ -0,0 +1,10 @@
1
+ <script>
2
+ export let data = null;
3
+
4
+ let count = $state(0);
5
+ </script>
6
+
7
+ <template>
8
+ <h1>{data.greeting}</h1>
9
+ <button @click={() => count++}>Clicked {count} times</button>
10
+ </template>
@@ -0,0 +1,14 @@
1
+ import { defineConfig } from 'eslint/config';
2
+ import js from '@eslint/js';
3
+ import globals from 'globals';
4
+ import tseslint from 'typescript-eslint';
5
+ import vela from '@govela/eslint-plugin';
6
+
7
+ export default defineConfig(
8
+ { ignores: ['dist/', 'out/', 'build/', 'src/_app/'] },
9
+ js.configs.recommended,
10
+ tseslint.configs.recommended,
11
+ vela.configs.recommended,
12
+ { files: ['src/**/*.ts', '**/*.vela/*.ts'], languageOptions: { globals: globals.browser } },
13
+ { files: ['*.config.js'], languageOptions: { globals: globals.node } },
14
+ );
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "__NAME__",
3
+ "private": true,
4
+ "type": "module",
5
+ "imports": {
6
+ "#app/*": "./src/_app/*"
7
+ },
8
+ "scripts": {
9
+ "gen": "vela-app gen",
10
+ "dev": "vela-app dev",
11
+ "check": "vela-app gen && vela check --project src --types --ssr",
12
+ "lint": "eslint .",
13
+ "test": "vitest run",
14
+ "build": "vela-app build"
15
+ },
16
+ "dependencies": {
17
+ "@govela/app": "__APP_VERSION__",
18
+ "@govela/runtime": "__VERSION__"
19
+ },
20
+ "devDependencies": {
21
+ "@eslint/js": "^10.0.1",
22
+ "@govela/compiler": "__VERSION__",
23
+ "@govela/eslint-plugin": "__VERSION__",
24
+ "@govela/testing": "__VERSION__",
25
+ "@govela/unplugin": "__VERSION__",
26
+ "eslint": "^10.14.0",
27
+ "globals": "^17.0.0",
28
+ "happy-dom": "^21.0.4",
29
+ "typescript": "^6.0.3",
30
+ "typescript-eslint": "^8.72.1",
31
+ "vite": "^8.3.5",
32
+ "vitest": "^5.1.0"
33
+ }
34
+ }
@@ -0,0 +1,26 @@
1
+ <script lang="ts">
2
+ import type { LayoutProps } from './$types';
3
+
4
+ export let data: LayoutProps['data'];
5
+ export let route: LayoutProps['route'];
6
+ </script>
7
+
8
+ <template>
9
+ <div class="site">
10
+ <nav>
11
+ <a href="/" class:active={route.path === '/'}>{data.site}</a>
12
+ </nav>
13
+ <main><slot /></main>
14
+ </div>
15
+ </template>
16
+
17
+ <style>
18
+ .site {
19
+ font-family: system-ui, sans-serif;
20
+ max-width: 40rem;
21
+ margin: 2rem auto;
22
+ }
23
+ .active {
24
+ font-weight: bold;
25
+ }
26
+ </style>
@@ -0,0 +1,13 @@
1
+ import { expect, test } from 'vitest';
2
+ import { fireEvent, render } from '@govela/testing';
3
+ import Page from './Page.vela';
4
+
5
+ test('greets and counts clicks', async () => {
6
+ const { container } = render(Page, { props: { data: { greeting: 'Hello' } } });
7
+ expect(container.querySelector('h1')!.textContent).toBe('Hello');
8
+
9
+ const button = container.querySelector('button')!;
10
+ await fireEvent.click(button);
11
+
12
+ expect(button.textContent).toBe('Clicked 1 times');
13
+ });
@@ -0,0 +1,12 @@
1
+ <script lang="ts">
2
+ import type { PageProps } from './$types';
3
+
4
+ export let data: PageProps['data'];
5
+
6
+ let count = $state(0);
7
+ </script>
8
+
9
+ <template>
10
+ <h1>{data.greeting}</h1>
11
+ <button @click={() => count++}>Clicked {count} times</button>
12
+ </template>
@@ -0,0 +1,4 @@
1
+ {
2
+ "extends": "./src/_app/tsconfig.json",
3
+ "include": ["src"]
4
+ }
@@ -0,0 +1 @@
1
+ { "typescript": true }