ndomo 0.1.0 → 0.2.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 (65) hide show
  1. package/.env.example +4 -0
  2. package/README.es.md +29 -23
  3. package/README.md +64 -24
  4. package/bun.lock +447 -0
  5. package/docs/configuration.md +4 -4
  6. package/docs/installation.md +53 -34
  7. package/docs/installer.md +164 -0
  8. package/docs/integrations.md +1 -1
  9. package/docs/web-ui.md +124 -0
  10. package/package.json +43 -4
  11. package/scripts/install.sh +28 -0
  12. package/scripts/smoke-install.sh +47 -0
  13. package/scripts/smoke-web.sh +335 -0
  14. package/src/cli/__tests__/install.test.ts +733 -0
  15. package/src/cli/index.ts +8 -0
  16. package/src/cli/install.ts +1273 -0
  17. package/src/config/__tests__/schema.test.ts +223 -0
  18. package/src/config/schema.ts +129 -16
  19. package/src/http/__tests__/auth.test.ts +10 -10
  20. package/src/http/__tests__/spa.test.ts +296 -0
  21. package/src/http/auth.ts +8 -1
  22. package/src/http/server.ts +71 -2
  23. package/.bun-version +0 -1
  24. package/.dockerignore +0 -79
  25. package/.editorconfig +0 -18
  26. package/.github/CODEOWNERS +0 -8
  27. package/.github/ISSUE_TEMPLATE/bug_report.yml +0 -62
  28. package/.github/ISSUE_TEMPLATE/config.yml +0 -2
  29. package/.github/ISSUE_TEMPLATE/feature_request.yml +0 -34
  30. package/.github/dependabot.yml +0 -36
  31. package/.github/pull_request_template.md +0 -24
  32. package/.github/release.yml +0 -30
  33. package/.github/workflows/gitleaks.yml +0 -28
  34. package/.github/workflows/release-please.yml +0 -27
  35. package/.github/workflows/smoke.yml +0 -29
  36. package/.husky/commit-msg +0 -1
  37. package/CHANGELOG.md +0 -114
  38. package/Dockerfile +0 -32
  39. package/bin/ndomo-analyses.ts +0 -4
  40. package/bin/ndomo-status.ts +0 -4
  41. package/biome.json +0 -57
  42. package/commitlint.config.js +0 -3
  43. package/opencode.json +0 -5
  44. package/release-please-config.json +0 -11
  45. package/scripts/dev-bust-cache.sh +0 -164
  46. package/scripts/smoke-e2e.ts +0 -704
  47. package/scripts/smoke-hot.ts +0 -417
  48. package/scripts/smoke-v4.ts +0 -256
  49. package/scripts/smoke-v5.ts +0 -397
  50. package/scripts/uninstall.sh +0 -224
  51. package/src/index.ts +0 -37
  52. package/src/lib.ts +0 -65
  53. package/src/mem/scoped.ts +0 -65
  54. package/src/orchestrator/background.test.ts +0 -268
  55. package/src/orchestrator/background.ts +0 -293
  56. package/src/orchestrator/memory-hook.ts +0 -182
  57. package/src/orchestrator/reconciler.ts +0 -123
  58. package/src/orchestrator/scheduler.test.ts +0 -300
  59. package/src/orchestrator/scheduler.ts +0 -243
  60. package/src/plugin.test.ts +0 -2574
  61. package/src/plugin.ts +0 -1690
  62. package/src/worktrees/manager.ts +0 -236
  63. package/src/worktrees/state.ts +0 -87
  64. package/tests/integration/ranger-flow.test.ts +0 -257
  65. package/tsconfig.json +0 -31
@@ -0,0 +1,296 @@
1
+ /**
2
+ * Tests for SPA static-file serving + client-side routing fallback.
3
+ *
4
+ * Uses a temp directory with a fake index.html + asset to avoid
5
+ * depending on `bun run web:build` output in CI.
6
+ */
7
+
8
+ import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from "node:fs";
9
+ import { join } from "node:path";
10
+ import { tmpdir } from "node:os";
11
+ import { Database } from "bun:sqlite";
12
+ import { afterEach, beforeEach, describe, expect, test } from "bun:test";
13
+ import type { HttpConfig } from "../../config/schema.ts";
14
+ import { runMigrations } from "../../db/migrations.ts";
15
+ import { buildHttpServer } from "../server.ts";
16
+
17
+ let db: Database;
18
+ let webDir: string;
19
+ let savedPassword: string | undefined;
20
+
21
+ const AUTH_CONFIG: HttpConfig = {
22
+ enabled: true,
23
+ port: 4098,
24
+ cors: { origins: ["*"] },
25
+ auth: { required: true },
26
+ };
27
+
28
+ const NO_AUTH_CONFIG: HttpConfig = {
29
+ enabled: true,
30
+ port: 4098,
31
+ cors: { origins: ["*"] },
32
+ auth: { required: false },
33
+ };
34
+
35
+ function basicAuthHeader(password: string): string {
36
+ const encoded = Buffer.from(`user:${password}`).toString("base64");
37
+ return `Basic ${encoded}`;
38
+ }
39
+
40
+ beforeEach(() => {
41
+ savedPassword = process.env.OPENCODE_SERVER_PASSWORD;
42
+ process.env.OPENCODE_SERVER_PASSWORD = "test-password";
43
+ db = new Database(":memory:");
44
+ db.exec("PRAGMA foreign_keys = ON");
45
+ runMigrations(db);
46
+
47
+ // Create temp web dist with fake SPA files
48
+ webDir = mkdtempSync(join(tmpdir(), "ndomo-spa-test-"));
49
+ writeFileSync(
50
+ join(webDir, "index.html"),
51
+ '<!DOCTYPE html><html><body><div id="app"></div></body></html>',
52
+ );
53
+ mkdirSync(join(webDir, "assets"), { recursive: true });
54
+ writeFileSync(
55
+ join(webDir, "assets", "index-abc123.js"),
56
+ 'console.log("spa");',
57
+ );
58
+ writeFileSync(
59
+ join(webDir, "assets", "index-abc123.css"),
60
+ "body{margin:0}",
61
+ );
62
+ });
63
+
64
+ afterEach(() => {
65
+ if (savedPassword === undefined) {
66
+ delete process.env.OPENCODE_SERVER_PASSWORD;
67
+ } else {
68
+ process.env.OPENCODE_SERVER_PASSWORD = savedPassword;
69
+ }
70
+ db.close();
71
+ rmSync(webDir, { recursive: true, force: true });
72
+ });
73
+
74
+ describe("SPA root GET /", () => {
75
+ test("returns 200 text/html with <div id='app'>", async () => {
76
+ const { app } = await buildHttpServer({
77
+ db,
78
+ httpConfig: NO_AUTH_CONFIG,
79
+ webDistDir: webDir,
80
+ });
81
+ const res = await app.handle(new Request("http://localhost/"));
82
+
83
+ expect(res.status).toBe(200);
84
+ expect(res.headers.get("Content-Type")).toContain("text/html");
85
+ const body = await res.text();
86
+ expect(body).toContain('<div id="app"></div>');
87
+ });
88
+ });
89
+
90
+ describe("SPA client-side routing fallback", () => {
91
+ test("unknown path returns index.html (SPA fallback)", async () => {
92
+ const { app } = await buildHttpServer({
93
+ db,
94
+ httpConfig: NO_AUTH_CONFIG,
95
+ webDistDir: webDir,
96
+ });
97
+ const res = await app.handle(new Request("http://localhost/some/spa/route"));
98
+
99
+ expect(res.status).toBe(200);
100
+ expect(res.headers.get("Content-Type")).toContain("text/html");
101
+ const body = await res.text();
102
+ expect(body).toContain('<div id="app"></div>');
103
+ });
104
+ });
105
+
106
+ describe("SPA static assets", () => {
107
+ test("serves JS asset with correct content type", async () => {
108
+ const { app } = await buildHttpServer({
109
+ db,
110
+ httpConfig: NO_AUTH_CONFIG,
111
+ webDistDir: webDir,
112
+ });
113
+ const res = await app.handle(
114
+ new Request("http://localhost/assets/index-abc123.js"),
115
+ );
116
+
117
+ expect(res.status).toBe(200);
118
+ expect(res.headers.get("Content-Type")).toContain("application/javascript");
119
+ const body = await res.text();
120
+ expect(body).toContain('console.log("spa")');
121
+ });
122
+
123
+ test("serves CSS asset with correct content type", async () => {
124
+ const { app } = await buildHttpServer({
125
+ db,
126
+ httpConfig: NO_AUTH_CONFIG,
127
+ webDistDir: webDir,
128
+ });
129
+ const res = await app.handle(
130
+ new Request("http://localhost/assets/index-abc123.css"),
131
+ );
132
+
133
+ expect(res.status).toBe(200);
134
+ expect(res.headers.get("Content-Type")).toContain("text/css");
135
+ });
136
+ });
137
+
138
+ describe("SPA does NOT swallow /api/*", () => {
139
+ test("GET /health returns JSON (not SPA)", async () => {
140
+ const { app } = await buildHttpServer({
141
+ db,
142
+ httpConfig: NO_AUTH_CONFIG,
143
+ webDistDir: webDir,
144
+ });
145
+ const res = await app.handle(new Request("http://localhost/health"));
146
+
147
+ expect(res.status).toBe(200);
148
+ expect(res.headers.get("Content-Type")).toContain("application/json");
149
+ const body = (await res.json()) as { status: string };
150
+ expect(body.status).toBe("ok");
151
+ });
152
+
153
+ test("GET /api/plans without auth returns 401 (not SPA)", async () => {
154
+ const { app } = await buildHttpServer({
155
+ db,
156
+ httpConfig: AUTH_CONFIG,
157
+ webDistDir: webDir,
158
+ });
159
+ const res = await app.handle(new Request("http://localhost/api/plans"));
160
+
161
+ expect(res.status).toBe(401);
162
+ const body = (await res.json()) as { error: string };
163
+ expect(body.error).toBe("invalid_credentials");
164
+ });
165
+
166
+ test("GET /api/plans with auth returns 200 JSON", async () => {
167
+ const { app } = await buildHttpServer({
168
+ db,
169
+ httpConfig: AUTH_CONFIG,
170
+ webDistDir: webDir,
171
+ });
172
+ const res = await app.handle(
173
+ new Request("http://localhost/api/plans", {
174
+ headers: { Authorization: basicAuthHeader("test-password") },
175
+ }),
176
+ );
177
+
178
+ expect(res.status).toBe(200);
179
+ expect(res.headers.get("Content-Type")).toContain("application/json");
180
+ });
181
+
182
+ test("GET /api/plans/nonexistent returns 404 JSON (not SPA fallback)", async () => {
183
+ const { app } = await buildHttpServer({
184
+ db,
185
+ httpConfig: AUTH_CONFIG,
186
+ webDistDir: webDir,
187
+ });
188
+ const res = await app.handle(
189
+ new Request("http://localhost/api/plans/nonexistent-id", {
190
+ headers: { Authorization: basicAuthHeader("test-password") },
191
+ }),
192
+ );
193
+
194
+ expect(res.status).toBe(404);
195
+ expect(res.headers.get("Content-Type")).toContain("application/json");
196
+ });
197
+ });
198
+
199
+ describe("SPA is public (no auth required for non-/api paths)", () => {
200
+ test("GET / returns 200 WITHOUT auth when auth.required=true", async () => {
201
+ const { app } = await buildHttpServer({
202
+ db,
203
+ httpConfig: AUTH_CONFIG,
204
+ webDistDir: webDir,
205
+ });
206
+ const res = await app.handle(new Request("http://localhost/"));
207
+
208
+ expect(res.status).toBe(200);
209
+ expect(res.headers.get("Content-Type")).toContain("text/html");
210
+ const body = await res.text();
211
+ expect(body).toContain('<div id="app"></div>');
212
+ });
213
+
214
+ test("GET /plans/<any-id> returns 200 WITHOUT auth (SPA fallback)", async () => {
215
+ const { app } = await buildHttpServer({
216
+ db,
217
+ httpConfig: AUTH_CONFIG,
218
+ webDistDir: webDir,
219
+ });
220
+ const res = await app.handle(
221
+ new Request("http://localhost/plans/some-uuid"),
222
+ );
223
+
224
+ expect(res.status).toBe(200);
225
+ expect(res.headers.get("Content-Type")).toContain("text/html");
226
+ const body = await res.text();
227
+ expect(body).toContain('<div id="app"></div>');
228
+ });
229
+
230
+ test("GET /assets/*.js returns 200 WITHOUT auth", async () => {
231
+ const { app } = await buildHttpServer({
232
+ db,
233
+ httpConfig: AUTH_CONFIG,
234
+ webDistDir: webDir,
235
+ });
236
+ const res = await app.handle(
237
+ new Request("http://localhost/assets/index-abc123.js"),
238
+ );
239
+
240
+ expect(res.status).toBe(200);
241
+ expect(res.headers.get("Content-Type")).toContain("application/javascript");
242
+ });
243
+ });
244
+
245
+ describe("SPA path traversal defense", () => {
246
+ test("path traversal attempt does not serve /etc/passwd", async () => {
247
+ const { app } = await buildHttpServer({
248
+ db,
249
+ httpConfig: NO_AUTH_CONFIG,
250
+ webDistDir: webDir,
251
+ });
252
+ const res = await app.handle(
253
+ new Request("http://localhost/../../etc/passwd"),
254
+ );
255
+
256
+ // Must not serve /etc/passwd — either 200 (SPA fallback) or 400
257
+ if (res.status === 200) {
258
+ const body = await res.text();
259
+ expect(body).toContain('<div id="app"></div>');
260
+ expect(body).not.toContain("root:");
261
+ } else {
262
+ expect([400, 404]).toContain(res.status);
263
+ }
264
+ });
265
+ });
266
+
267
+ describe("SPA disabled when web dir missing", () => {
268
+ test("returns 503 when webDistDir does not exist", async () => {
269
+ const { app } = await buildHttpServer({
270
+ db,
271
+ httpConfig: NO_AUTH_CONFIG,
272
+ webDistDir: "/nonexistent/path",
273
+ });
274
+ const res = await app.handle(new Request("http://localhost/"));
275
+
276
+ expect(res.status).toBe(503);
277
+ const body = await res.text();
278
+ expect(body).toContain("SPA not built");
279
+ });
280
+ });
281
+
282
+ describe("SPA non-GET methods", () => {
283
+ test("POST to SPA path returns 404 (Elysia GET handler does not match)", async () => {
284
+ const { app } = await buildHttpServer({
285
+ db,
286
+ httpConfig: NO_AUTH_CONFIG,
287
+ webDistDir: webDir,
288
+ });
289
+ const res = await app.handle(
290
+ new Request("http://localhost/some/path", { method: "POST" }),
291
+ );
292
+
293
+ // Elysia .get() does not match POST → 404
294
+ expect(res.status).toBe(404);
295
+ });
296
+ });
package/src/http/auth.ts CHANGED
@@ -6,11 +6,13 @@
6
6
  * Uses timing-safe comparison to prevent timing attacks.
7
7
  *
8
8
  * Uses onRequest so the hook propagates across Elysia .use() boundaries.
9
- * Exempts /health from auth (public liveness probe).
9
+ * Exempts from auth: /health (liveness probe) + non-/api paths (SPA static +
10
+ * SPA history fallback). Only /api/* requires auth.
10
11
  *
11
12
  * Behavior:
12
13
  * - auth.required === false → skip entirely
13
14
  * - /health path → skip (always public)
15
+ * - non-/api path (SPA) → skip (public — Vue SPA serves static + history fallback)
14
16
  * - Password unset/empty → 503 auth_not_configured
15
17
  * - Missing/malformed Authorization → 401 + WWW-Authenticate
16
18
  * - Wrong password → 401 + WWW-Authenticate
@@ -36,6 +38,11 @@ export function httpBasicAuth(httpConfig: HttpConfig) {
36
38
  const url = new URL(request.url);
37
39
  if (url.pathname === "/health") return;
38
40
 
41
+ // Exempt non-/api paths — SPA static assets + history fallback are public.
42
+ // Auth gates only the JSON API surface (/api/*). Users browse the SPA freely,
43
+ // then enter the password in the in-app AuthPrompt when fetching /api/*.
44
+ if (!url.pathname.startsWith("/api/")) return;
45
+
39
46
  const password = process.env.OPENCODE_SERVER_PASSWORD;
40
47
 
41
48
  // Password not configured → 503
@@ -9,6 +9,9 @@
9
9
  * 4. health route (no auth)
10
10
  * 5. /api/plans, /api/tasks, /api/sessions (auth required)
11
11
  */
12
+ import { existsSync } from "node:fs";
13
+ import { join, normalize, resolve } from "node:path";
14
+ import { fileURLToPath } from "node:url";
12
15
  import type { Database } from "bun:sqlite";
13
16
  import type { OpencodeClient } from "@opencode-ai/sdk/client";
14
17
  import { Elysia } from "elysia";
@@ -27,6 +30,11 @@ interface BuildHttpServerArgs {
27
30
  httpConfig: HttpConfig;
28
31
  /** OpenCode SDK client for SSE events. If null/undefined, /api/events returns 503. */
29
32
  sdkClient?: OpencodeClient;
33
+ /**
34
+ * Override web dist directory for testing. Defaults to ./web/ relative to this file.
35
+ * Set to a temp dir with index.html + assets for SPA tests.
36
+ */
37
+ webDistDir?: string;
30
38
  }
31
39
 
32
40
  export interface HttpServerHandle {
@@ -64,12 +72,73 @@ export async function buildHttpServer(args: BuildHttpServerArgs) {
64
72
  .use(sessionsRoute(db))
65
73
  .use(eventsRoute(args.sdkClient ?? null));
66
74
 
67
- // Compose the full app
75
+ // Resolve web dist dir (configurable for testing, defaults to ./web/ sibling)
76
+ const WEB_DIST = args.webDistDir
77
+ ? resolve(args.webDistDir)
78
+ : fileURLToPath(new URL("./web/", import.meta.url));
79
+ const INDEX_HTML = join(WEB_DIST, "index.html");
80
+
81
+ // Static-file + SPA fallback sub-app
82
+ const spaApp = new Elysia({ name: "spa-fallback" }).get(
83
+ "/*",
84
+ ({ path }) => {
85
+ // Try static asset first (path traversal safe)
86
+ // Strip leading slashes so resolve() doesn't treat path as absolute
87
+ const safePath = normalize(path)
88
+ .replace(/^(\.\.[/\\])+/g, "")
89
+ .replace(/^\/+/, "");
90
+ const assetPath = resolve(WEB_DIST, safePath);
91
+
92
+ if (
93
+ assetPath.startsWith(WEB_DIST) &&
94
+ safePath !== "" &&
95
+ existsSync(assetPath)
96
+ ) {
97
+ const file = Bun.file(assetPath);
98
+ const ext = assetPath.split(".").pop() ?? "";
99
+ const contentTypes: Record<string, string> = {
100
+ html: "text/html; charset=utf-8",
101
+ js: "application/javascript; charset=utf-8",
102
+ css: "text/css; charset=utf-8",
103
+ json: "application/json; charset=utf-8",
104
+ svg: "image/svg+xml",
105
+ png: "image/png",
106
+ jpg: "image/jpeg",
107
+ jpeg: "image/jpeg",
108
+ ico: "image/x-icon",
109
+ woff: "font/woff",
110
+ woff2: "font/woff2",
111
+ };
112
+ return new Response(file, {
113
+ headers: {
114
+ "Content-Type": contentTypes[ext] ?? "application/octet-stream",
115
+ "Cache-Control": "no-cache",
116
+ },
117
+ });
118
+ }
119
+
120
+ // Fallback to SPA index.html for client-side routing
121
+ if (!existsSync(INDEX_HTML)) {
122
+ return new Response("SPA not built. Run: bun run web:build", {
123
+ status: 503,
124
+ });
125
+ }
126
+ return new Response(Bun.file(INDEX_HTML), {
127
+ headers: {
128
+ "Content-Type": "text/html; charset=utf-8",
129
+ "Cache-Control": "no-cache",
130
+ },
131
+ });
132
+ },
133
+ );
134
+
135
+ // Compose the full app — apiProtected BEFORE spaApp so /api/* wins
68
136
  const app = new Elysia({ name: "ndomo-http" })
69
137
  .use(securityHeaders)
70
138
  .use(corsMiddleware(httpConfig.cors.origins))
71
139
  .use(healthRoute(db))
72
- .use(apiProtected);
140
+ .use(apiProtected)
141
+ .use(spaApp);
73
142
 
74
143
  return {
75
144
  app: app as unknown as Elysia,
package/.bun-version DELETED
@@ -1 +0,0 @@
1
- 1.3.14
package/.dockerignore DELETED
@@ -1,79 +0,0 @@
1
- # VCS
2
- .git/
3
- .gitignore
4
-
5
- # Dependencies (regenerated in image)
6
- node_modules/
7
- .pnp/
8
- .pnp.js
9
- .yarn/
10
-
11
- # Build output
12
- dist/
13
- build/
14
- out/
15
- *.tsbuildinfo
16
-
17
- # OpenCode / ndomo runtime state
18
- .slim/
19
- .worktrees/
20
- .ndomo/
21
- .opencode/
22
- .opencode-mem/
23
-
24
- # DCP data
25
- .dcp/
26
- dcp-prompts/
27
-
28
- # Databases — never bake into image
29
- *.sqlite
30
- *.sqlite-journal
31
- *.sqlite-shm
32
- *.sqlite-wal
33
-
34
- # Environment / secrets — NEVER bake into image
35
- .env
36
- .env.*
37
- .env.local
38
- .env.*.local
39
-
40
- # Logs
41
- *.log
42
- npm-debug.log*
43
- yarn-debug.log*
44
- yarn-error.log*
45
- logs/
46
-
47
- # Coverage
48
- coverage/
49
- .nyc_output/
50
-
51
- # OS artifacts
52
- .DS_Store
53
- Thumbs.db
54
-
55
- # Editor
56
- .idea/
57
- .vscode/
58
- *.swp
59
- *.swo
60
-
61
- # Documentation (not needed at runtime)
62
- docs/
63
- CHANGELOG.md
64
- README.md
65
- README.es.md
66
-
67
- # Test files (not needed at runtime)
68
- *.test.ts
69
- *.test.js
70
-
71
- # Miscellaneous
72
- *.tgz
73
- .cache/
74
- .agents/
75
- skills-lock.json
76
-
77
- # Docker metadata (not needed in image)
78
- Dockerfile
79
- .dockerignore
package/.editorconfig DELETED
@@ -1,18 +0,0 @@
1
- root = true
2
-
3
- [*]
4
- charset = utf-8
5
- end_of_line = lf
6
- indent_style = space
7
- indent_size = 2
8
- insert_final_newline = true
9
- trim_trailing_whitespace = true
10
-
11
- [*.md]
12
- trim_trailing_whitespace = false
13
-
14
- [*.{yml,yaml}]
15
- indent_size = 2
16
-
17
- [Makefile]
18
- indent_style = tab
@@ -1,8 +0,0 @@
1
- # Default owners for everything in the repo
2
- * @nicosup98
3
-
4
- # CI/CD workflows and automation
5
- .github/ @nicosup98
6
-
7
- # Package and build configuration
8
- package.json @nicosup98
@@ -1,62 +0,0 @@
1
- name: Bug Report
2
- description: Report a bug in ndomo
3
- title: "[bug]: "
4
- labels: [bug]
5
- body:
6
- - type: textarea
7
- id: description
8
- attributes:
9
- label: Description
10
- description: What went wrong?
11
- placeholder: A clear description of the bug.
12
- validations:
13
- required: true
14
- - type: textarea
15
- id: steps
16
- attributes:
17
- label: Steps to reproduce
18
- description: How can we reproduce the issue?
19
- placeholder: |
20
- 1.
21
- 2.
22
- 3.
23
- validations:
24
- required: true
25
- - type: textarea
26
- id: expected
27
- attributes:
28
- label: Expected behavior
29
- description: What should have happened?
30
- validations:
31
- required: true
32
- - type: textarea
33
- id: actual
34
- attributes:
35
- label: Actual behavior
36
- description: What actually happened?
37
- validations:
38
- required: true
39
- - type: input
40
- id: os
41
- attributes:
42
- label: OS
43
- description: What operating system are you using?
44
- placeholder: e.g. Ubuntu 24.04, macOS 14
45
- validations:
46
- required: true
47
- - type: input
48
- id: bun-version
49
- attributes:
50
- label: Bun version
51
- description: What bun version are you using? (run `bun --version`)
52
- placeholder: e.g. 1.3.14
53
- validations:
54
- required: true
55
- - type: input
56
- id: ndomo-version
57
- attributes:
58
- label: ndomo version
59
- description: What ndomo version are you using?
60
- placeholder: e.g. 0.1.0
61
- validations:
62
- required: false
@@ -1,2 +0,0 @@
1
- blank_issues_enabled: true
2
- contact_links: []
@@ -1,34 +0,0 @@
1
- name: Feature Request
2
- description: Suggest a new feature for ndomo
3
- title: "[feature]: "
4
- labels: [enhancement]
5
- body:
6
- - type: textarea
7
- id: description
8
- attributes:
9
- label: Description
10
- description: What feature do you want?
11
- placeholder: A clear description of the feature.
12
- validations:
13
- required: true
14
- - type: textarea
15
- id: problem
16
- attributes:
17
- label: Problem it solves
18
- description: What problem does this solve that isn't possible today?
19
- validations:
20
- required: true
21
- - type: textarea
22
- id: solution
23
- attributes:
24
- label: Proposed solution
25
- description: How would you implement this?
26
- validations:
27
- required: true
28
- - type: textarea
29
- id: alternatives
30
- attributes:
31
- label: Alternatives considered
32
- description: What other approaches did you think about?
33
- validations:
34
- required: false
@@ -1,36 +0,0 @@
1
- # .github/dependabot.yml
2
- # Dependabot config: weekly auto-PRs for npm + github-actions
3
- version: 2
4
- updates:
5
- - package-ecosystem: "npm"
6
- directory: "/"
7
- schedule:
8
- interval: "weekly"
9
- day: "monday"
10
- open-pull-requests-limit: 5
11
- labels:
12
- - "dependencies"
13
- groups:
14
- production:
15
- applies-to: version-updates
16
- dependency-type: "production"
17
- development:
18
- applies-to: version-updates
19
- dependency-type: "development"
20
- commit-message:
21
- prefix: "chore(deps)"
22
-
23
- - package-ecosystem: "github-actions"
24
- directory: "/"
25
- schedule:
26
- interval: "weekly"
27
- day: "monday"
28
- open-pull-requests-limit: 3
29
- labels:
30
- - "dependencies"
31
- - "ci"
32
- commit-message:
33
- prefix: "ci(actions)"
34
- # Auto-merge patch updates for actions (they're already SHA-pinned,
35
- # so updates only happen when we bump SHAs intentionally)
36
- automerge: false # keep manual review
@@ -1,24 +0,0 @@
1
- ## Description
2
-
3
- <!-- What does this PR do? Why is this change needed? -->
4
-
5
- ## Type of change
6
-
7
- - [ ] Bug fix
8
- - [ ] Feature
9
- - [ ] Breaking change
10
- - [ ] Documentation
11
- - [ ] Refactor
12
- - [ ] Chore (maintenance, deps, CI)
13
-
14
- ## Checklist
15
-
16
- - [ ] Tests added/updated (if applicable)
17
- - [ ] Lint passes
18
- - [ ] Typecheck passes
19
- - [ ] Smoke test passes
20
- - [ ] Docs updated (if needed)
21
-
22
- ## Related issue
23
-
24
- <!-- Closes #N or "No related issue" -->