@sha3/code 1.0.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 (165) hide show
  1. package/AGENTS.md +75 -0
  2. package/README.md +554 -0
  3. package/ai/adapters/codex.md +7 -0
  4. package/ai/adapters/copilot.md +7 -0
  5. package/ai/adapters/cursor.md +7 -0
  6. package/ai/adapters/windsurf.md +8 -0
  7. package/ai/constitution.md +12 -0
  8. package/bin/code-standards.mjs +47 -0
  9. package/biome.json +37 -0
  10. package/index.mjs +11 -0
  11. package/lib/cli/parse-args.mjs +416 -0
  12. package/lib/cli/post-run-guidance.mjs +43 -0
  13. package/lib/cli/run-init.mjs +123 -0
  14. package/lib/cli/run-profile.mjs +46 -0
  15. package/lib/cli/run-refactor.mjs +152 -0
  16. package/lib/cli/run-verify.mjs +67 -0
  17. package/lib/constants.mjs +167 -0
  18. package/lib/contract/load-rule-catalog.mjs +12 -0
  19. package/lib/contract/render-agents.mjs +79 -0
  20. package/lib/contract/render-contract-json.mjs +7 -0
  21. package/lib/contract/resolve-contract.mjs +52 -0
  22. package/lib/paths.mjs +50 -0
  23. package/lib/profile.mjs +108 -0
  24. package/lib/project/ai-instructions.mjs +28 -0
  25. package/lib/project/biome-ignore.mjs +14 -0
  26. package/lib/project/managed-files.mjs +105 -0
  27. package/lib/project/package-metadata.mjs +132 -0
  28. package/lib/project/prompt-files.mjs +111 -0
  29. package/lib/project/template-resolution.mjs +70 -0
  30. package/lib/refactor/materialize-refactor-context.mjs +106 -0
  31. package/lib/refactor/preservation-questions.mjs +33 -0
  32. package/lib/refactor/public-contract-extractor.mjs +22 -0
  33. package/lib/refactor/render-analysis-summary.mjs +50 -0
  34. package/lib/refactor/source-analysis.mjs +74 -0
  35. package/lib/utils/fs.mjs +220 -0
  36. package/lib/utils/prompts.mjs +63 -0
  37. package/lib/utils/text.mjs +43 -0
  38. package/lib/verify/change-audit-verifier.mjs +140 -0
  39. package/lib/verify/change-context.mjs +36 -0
  40. package/lib/verify/error-handling-verifier.mjs +164 -0
  41. package/lib/verify/explain-rule.mjs +54 -0
  42. package/lib/verify/issue-helpers.mjs +132 -0
  43. package/lib/verify/project-layout-verifier.mjs +259 -0
  44. package/lib/verify/project-verifier.mjs +267 -0
  45. package/lib/verify/readme-public-api.mjs +237 -0
  46. package/lib/verify/readme-verifier.mjs +216 -0
  47. package/lib/verify/render-json-report.mjs +3 -0
  48. package/lib/verify/render-text-report.mjs +34 -0
  49. package/lib/verify/source-analysis.mjs +126 -0
  50. package/lib/verify/source-rule-verifier.mjs +453 -0
  51. package/lib/verify/testing-verifier.mjs +113 -0
  52. package/lib/verify/tooling-verifier.mjs +82 -0
  53. package/lib/verify/typescript-style-verifier.mjs +407 -0
  54. package/package.json +55 -0
  55. package/profiles/default.profile.json +40 -0
  56. package/profiles/schema.json +96 -0
  57. package/prompts/init-contract.md +25 -0
  58. package/prompts/init-phase-2-implement.md +25 -0
  59. package/prompts/init-phase-3-verify.md +23 -0
  60. package/prompts/init.prompt.md +24 -0
  61. package/prompts/refactor-contract.md +26 -0
  62. package/prompts/refactor-phase-2-rebuild.md +25 -0
  63. package/prompts/refactor-phase-3-verify.md +24 -0
  64. package/prompts/refactor.prompt.md +26 -0
  65. package/resources/ai/AGENTS.md +18 -0
  66. package/resources/ai/adapters/codex.md +5 -0
  67. package/resources/ai/adapters/copilot.md +5 -0
  68. package/resources/ai/adapters/cursor.md +5 -0
  69. package/resources/ai/adapters/windsurf.md +5 -0
  70. package/resources/ai/contract.schema.json +68 -0
  71. package/resources/ai/rule-catalog.json +878 -0
  72. package/resources/ai/rule-catalog.schema.json +66 -0
  73. package/resources/ai/templates/adapters/codex.template.md +7 -0
  74. package/resources/ai/templates/adapters/copilot.template.md +7 -0
  75. package/resources/ai/templates/adapters/cursor.template.md +7 -0
  76. package/resources/ai/templates/adapters/windsurf.template.md +7 -0
  77. package/resources/ai/templates/agents.project.template.md +141 -0
  78. package/resources/ai/templates/examples/demo/src/billing/billing.service.ts +73 -0
  79. package/resources/ai/templates/examples/demo/src/config.ts +3 -0
  80. package/resources/ai/templates/examples/demo/src/invoice/invoice.errors.ts +51 -0
  81. package/resources/ai/templates/examples/demo/src/invoice/invoice.service.ts +96 -0
  82. package/resources/ai/templates/examples/demo/src/invoice/invoice.types.ts +9 -0
  83. package/resources/ai/templates/examples/rules/async-bad.ts +52 -0
  84. package/resources/ai/templates/examples/rules/async-good.ts +56 -0
  85. package/resources/ai/templates/examples/rules/class-first-bad.ts +36 -0
  86. package/resources/ai/templates/examples/rules/class-first-good.ts +74 -0
  87. package/resources/ai/templates/examples/rules/constructor-bad.ts +68 -0
  88. package/resources/ai/templates/examples/rules/constructor-good.ts +71 -0
  89. package/resources/ai/templates/examples/rules/control-flow-bad.ts +31 -0
  90. package/resources/ai/templates/examples/rules/control-flow-good.ts +54 -0
  91. package/resources/ai/templates/examples/rules/errors-bad.ts +42 -0
  92. package/resources/ai/templates/examples/rules/errors-good.ts +23 -0
  93. package/resources/ai/templates/examples/rules/functions-bad.ts +48 -0
  94. package/resources/ai/templates/examples/rules/functions-good.ts +58 -0
  95. package/resources/ai/templates/examples/rules/returns-bad.ts +38 -0
  96. package/resources/ai/templates/examples/rules/returns-good.ts +44 -0
  97. package/resources/ai/templates/examples/rules/testing-bad.ts +34 -0
  98. package/resources/ai/templates/examples/rules/testing-good.ts +54 -0
  99. package/resources/ai/templates/rules/architecture.md +41 -0
  100. package/resources/ai/templates/rules/async.md +13 -0
  101. package/resources/ai/templates/rules/class-first.md +45 -0
  102. package/resources/ai/templates/rules/control-flow.md +13 -0
  103. package/resources/ai/templates/rules/errors.md +18 -0
  104. package/resources/ai/templates/rules/functions.md +29 -0
  105. package/resources/ai/templates/rules/naming.md +13 -0
  106. package/resources/ai/templates/rules/readme.md +36 -0
  107. package/resources/ai/templates/rules/returns.md +13 -0
  108. package/resources/ai/templates/rules/testing.md +18 -0
  109. package/resources/ai/templates/rules.project.template.md +66 -0
  110. package/resources/ai/templates/skills/change-synchronization/SKILL.md +42 -0
  111. package/resources/ai/templates/skills/feature-shaping/SKILL.md +45 -0
  112. package/resources/ai/templates/skills/http-api-conventions/SKILL.md +171 -0
  113. package/resources/ai/templates/skills/init-workflow/SKILL.md +52 -0
  114. package/resources/ai/templates/skills/readme-authoring/SKILL.md +51 -0
  115. package/resources/ai/templates/skills/refactor-workflow/SKILL.md +50 -0
  116. package/resources/ai/templates/skills/simplicity-audit/SKILL.md +41 -0
  117. package/resources/ai/templates/skills/test-scope-selection/SKILL.md +50 -0
  118. package/resources/ai/templates/skills.index.template.md +25 -0
  119. package/standards/architecture.md +72 -0
  120. package/standards/changelog-policy.md +12 -0
  121. package/standards/manifest.json +36 -0
  122. package/standards/readme.md +56 -0
  123. package/standards/schema.json +124 -0
  124. package/standards/style.md +106 -0
  125. package/standards/testing.md +20 -0
  126. package/standards/tooling.md +38 -0
  127. package/templates/node-lib/.biomeignore +10 -0
  128. package/templates/node-lib/.vscode/extensions.json +1 -0
  129. package/templates/node-lib/.vscode/settings.json +9 -0
  130. package/templates/node-lib/README.md +172 -0
  131. package/templates/node-lib/biome.json +37 -0
  132. package/templates/node-lib/gitignore +6 -0
  133. package/templates/node-lib/package.json +32 -0
  134. package/templates/node-lib/scripts/release-publish.mjs +106 -0
  135. package/templates/node-lib/scripts/run-tests.mjs +65 -0
  136. package/templates/node-lib/src/config.ts +3 -0
  137. package/templates/node-lib/src/index.ts +2 -0
  138. package/templates/node-lib/src/logger.ts +7 -0
  139. package/templates/node-lib/src/package-info/package-info.service.ts +47 -0
  140. package/templates/node-lib/test/package-info.test.ts +10 -0
  141. package/templates/node-lib/tsconfig.build.json +1 -0
  142. package/templates/node-lib/tsconfig.json +5 -0
  143. package/templates/node-service/.biomeignore +10 -0
  144. package/templates/node-service/.vscode/extensions.json +1 -0
  145. package/templates/node-service/.vscode/settings.json +9 -0
  146. package/templates/node-service/README.md +244 -0
  147. package/templates/node-service/biome.json +37 -0
  148. package/templates/node-service/ecosystem.config.cjs +3 -0
  149. package/templates/node-service/gitignore +6 -0
  150. package/templates/node-service/package.json +42 -0
  151. package/templates/node-service/scripts/release-publish.mjs +106 -0
  152. package/templates/node-service/scripts/run-tests.mjs +65 -0
  153. package/templates/node-service/src/app/service-runtime.service.ts +57 -0
  154. package/templates/node-service/src/app-info/app-info.service.ts +47 -0
  155. package/templates/node-service/src/config.ts +11 -0
  156. package/templates/node-service/src/http/http-server.service.ts +66 -0
  157. package/templates/node-service/src/index.ts +2 -0
  158. package/templates/node-service/src/logger.ts +7 -0
  159. package/templates/node-service/src/main.ts +5 -0
  160. package/templates/node-service/test/service-runtime.test.ts +13 -0
  161. package/templates/node-service/tsconfig.build.json +1 -0
  162. package/templates/node-service/tsconfig.json +5 -0
  163. package/tsconfig/base.json +16 -0
  164. package/tsconfig/node-lib.json +5 -0
  165. package/tsconfig/node-service.json +1 -0
@@ -0,0 +1,244 @@
1
+ # 🚀 {{packageName}}
2
+
3
+ HTTP service package with one runtime entrypoint for composing or starting a Hono server.
4
+
5
+ ## TL;DR
6
+
7
+ ```bash
8
+ npm install
9
+ npm run check
10
+ npm run start
11
+ ```
12
+
13
+ ```ts
14
+ import { ServiceRuntime } from "{{packageName}}";
15
+
16
+ const serviceRuntime = ServiceRuntime.createDefault();
17
+ const server = serviceRuntime.buildServer();
18
+ ```
19
+
20
+ ## Why
21
+
22
+ Use this package when you want one small runtime surface for booting the service and one small HTTP boundary that is easy to test.
23
+ It keeps startup, route wiring, and the default response contract behind a public runtime class so callers do not need to understand the internal composition.
24
+
25
+ ## Main Capabilities
26
+
27
+ - Builds the HTTP server without binding a port when tests or orchestration code need control.
28
+ - Handles a single request without opening a socket when tests only need the HTTP contract.
29
+ - Starts the default runtime with one public method when the process should listen immediately.
30
+ - Exposes the root endpoint payload as a public type so other code can depend on the response contract.
31
+ - Keeps transport concerns small and explicit around Hono.
32
+
33
+ ## Installation
34
+
35
+ ```bash
36
+ npm install
37
+ ```
38
+
39
+ ## Running Locally
40
+
41
+ ```bash
42
+ npm run start
43
+ ```
44
+
45
+ The default runtime listens on `http://localhost:3000`.
46
+
47
+ ## Usage
48
+
49
+ ```ts
50
+ import { ServiceRuntime } from "{{packageName}}";
51
+
52
+ const serviceRuntime = ServiceRuntime.createDefault();
53
+ const server = serviceRuntime.startServer();
54
+ ```
55
+
56
+ This is the intended runtime integration path: construct the default runtime once, then either build or start the Hono-backed server depending on who owns process startup.
57
+
58
+ ## Examples
59
+
60
+ Build the HTTP server without listening:
61
+
62
+ ```ts
63
+ import { ServiceRuntime } from "{{packageName}}";
64
+
65
+ const serviceRuntime = ServiceRuntime.createDefault();
66
+ const server = serviceRuntime.buildServer();
67
+ ```
68
+
69
+ Handle one request directly:
70
+
71
+ ```ts
72
+ import { ServiceRuntime } from "{{packageName}}";
73
+
74
+ const serviceRuntime = ServiceRuntime.createDefault();
75
+ const response = await serviceRuntime.handleRequest(new Request("http://localhost/"));
76
+ ```
77
+
78
+ Call the default endpoint:
79
+
80
+ ```bash
81
+ curl http://localhost:3000/
82
+ ```
83
+
84
+ ## HTTP API
85
+
86
+ ### `GET /`
87
+
88
+ Returns the default service info payload from the default Hono route.
89
+
90
+ Response shape:
91
+
92
+ ```json
93
+ {
94
+ "ok": true,
95
+ "serviceName": "service-name"
96
+ }
97
+ ```
98
+
99
+ Behavior notes:
100
+
101
+ - responds with status `200`
102
+ - returns JSON
103
+ - is implemented with `hono`
104
+ - uses the `content-type` configured in `src/config.ts`
105
+
106
+ ## Public API
107
+
108
+ ### `ServiceRuntime`
109
+
110
+ Primary runtime entrypoint for composing or starting the service.
111
+
112
+ ```ts
113
+ import { ServiceRuntime } from "{{packageName}}";
114
+
115
+ const serviceRuntime = ServiceRuntime.createDefault();
116
+ ```
117
+
118
+ #### `createDefault()`
119
+
120
+ Creates a `ServiceRuntime` with the default Hono HTTP server wiring.
121
+
122
+ Parameters:
123
+
124
+ - none
125
+
126
+ Returns:
127
+
128
+ - a ready-to-use `ServiceRuntime`
129
+
130
+ Behavior notes:
131
+
132
+ - wires the default Hono HTTP server stack
133
+ - keeps configuration centralized in `src/config.ts`
134
+
135
+ #### `buildServer()`
136
+
137
+ Builds the Hono Node server without binding a port.
138
+
139
+ Parameters:
140
+
141
+ - none
142
+
143
+ Returns:
144
+
145
+ - a Hono Node `ServerType` instance
146
+
147
+ Behavior notes:
148
+
149
+ - useful for integration tests and manual orchestration
150
+ - does not call `listen()`
151
+ - returns the exact server instance that `startServer()` would later bind
152
+
153
+ #### `handleRequest(request)`
154
+
155
+ Runs one request through the default Hono pipeline without binding a port.
156
+
157
+ Parameters:
158
+
159
+ - `request`: standard `Request` object routed through the service
160
+
161
+ Returns:
162
+
163
+ - a `Promise<Response>` with the route result
164
+
165
+ Behavior notes:
166
+
167
+ - useful for tests that only need to validate HTTP behavior
168
+ - avoids opening a socket during request handling checks
169
+ - uses the same route wiring as the default runtime
170
+
171
+ #### `startServer()`
172
+
173
+ Builds the HTTP server and starts listening on `config.DEFAULT_PORT`.
174
+
175
+ Parameters:
176
+
177
+ - none
178
+
179
+ Returns:
180
+
181
+ - the started Hono Node `ServerType` instance
182
+
183
+ Behavior notes:
184
+
185
+ - logs the listening address through the package logger
186
+ - binds immediately to the configured default port
187
+ - keeps startup behavior centralized in one public method
188
+
189
+ ### `AppInfoPayload`
190
+
191
+ Public type for the default root payload returned by the root endpoint.
192
+
193
+ ```ts
194
+ type AppInfoPayload = { ok: true; serviceName: string };
195
+ ```
196
+
197
+ ## Compatibility
198
+
199
+ - Node.js 20+
200
+ - ESM (`"type": "module"`)
201
+ - Strict TypeScript
202
+
203
+ ## Configuration
204
+
205
+ Configuration is centralized in `src/config.ts`.
206
+
207
+ - `config.RESPONSE_CONTENT_TYPE`: response `content-type` header sent by the root endpoint.
208
+ - `config.DEFAULT_PORT`: port used by `startServer()` when the service binds locally.
209
+ - `config.SERVICE_NAME`: service name included in the root endpoint payload.
210
+
211
+ ## Scripts
212
+
213
+ - `npm run standards:check`: verify deterministic project contract rules
214
+ - `npm run check`: standards + lint + format + typecheck + tests
215
+ - `npm run fix`: Biome autofix + format write
216
+ - `npm run start`: launch the service with `tsx`
217
+ - `npm run build`: compile the module output to `dist/`
218
+
219
+ ## Structure
220
+
221
+ - `src/config.ts`: canonical runtime configuration
222
+ - `src/app-info/app-info.service.ts`: builds the root endpoint payload
223
+ - `src/http/http-server.service.ts`: constructs the Hono HTTP server
224
+ - `src/app/service-runtime.service.ts`: public runtime orchestration
225
+ - `src/main.ts`: bootstrap entrypoint
226
+ - `test/service-runtime.test.ts`: behavior test
227
+
228
+ ## Troubleshooting
229
+
230
+ ### Port conflicts
231
+
232
+ Override `PORT` in `.env` or your shell before running `npm run start`.
233
+
234
+ ### Standards warnings
235
+
236
+ Run `npm run standards:check` to detect contract errors, README coverage gaps, and advisory warnings from the project verifier.
237
+
238
+ ## AI Workflow
239
+
240
+ - Read `AGENTS.md`, `ai/contract.json`, and the relevant `ai/<assistant>.md` before coding.
241
+ - Keep managed contract/tooling files read-only during feature work.
242
+ - Keep this README focused on the runtime surface, HTTP contract, configuration, and public API.
243
+ - Rewrite examples and endpoint notes whenever exported behavior changes.
244
+ - Run `npm run check` before finalizing changes.
@@ -0,0 +1,37 @@
1
+ {
2
+ "files": {
3
+ "ignoreUnknown": true,
4
+ "ignore": [".code-standards", "dist"]
5
+ },
6
+ "formatter": {
7
+ "enabled": true,
8
+ "indentStyle": "space",
9
+ "indentWidth": 2,
10
+ "lineWidth": 160
11
+ },
12
+ "linter": {
13
+ "enabled": true,
14
+ "rules": {
15
+ "recommended": true,
16
+ "correctness": {
17
+ "noUnusedFunctionParameters": "error",
18
+ "noUnusedImports": "error",
19
+ "noUnusedPrivateClassMembers": "error",
20
+ "noUnusedVariables": "error"
21
+ }
22
+ }
23
+ },
24
+ "javascript": {
25
+ "formatter": {
26
+ "quoteStyle": "double",
27
+ "semicolons": "always",
28
+ "arrowParentheses": "always",
29
+ "bracketSpacing": true
30
+ }
31
+ },
32
+ "json": {
33
+ "formatter": {
34
+ "enabled": true
35
+ }
36
+ }
37
+ }
@@ -0,0 +1,3 @@
1
+ module.exports = {
2
+ apps: [{ name: "{{packageName}}", script: "node", args: "--import tsx src/main.ts", env: { NODE_ENV: "production" } }],
3
+ };
@@ -0,0 +1,6 @@
1
+ node_modules/
2
+ dist/
3
+ coverage/
4
+ .code-standards/
5
+ *.tsbuildinfo
6
+ .DS_Store
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "{{packageName}}",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ }
13
+ },
14
+ "files": ["dist"],
15
+ "scripts": {
16
+ "start": "node --import tsx src/main.ts",
17
+ "build": "tsc -p tsconfig.build.json",
18
+ "check": "npm run standards:check && npm run lint && npm run format:check && npm run typecheck && npm run test",
19
+ "fix": "npm run lint:fix && npm run format:write",
20
+ "lint": "biome check .",
21
+ "lint:fix": "biome check . --write",
22
+ "format:check": "biome format .",
23
+ "format:write": "biome format . --write",
24
+ "standards:check": "code-standards verify",
25
+ "typecheck": "tsc --noEmit",
26
+ "test": "node scripts/run-tests.mjs",
27
+ "publish": "node scripts/release-publish.mjs"
28
+ },
29
+ "dependencies": {
30
+ "@hono/node-server": "^1.19.11",
31
+ "@sha3/logger": "^2.0.0",
32
+ "dotenv": "^16.6.1",
33
+ "hono": "^4.12.7"
34
+ },
35
+ "devDependencies": {
36
+ "@sha3/code": "^{{packageVersion}}",
37
+ "@biomejs/biome": "^1.9.4",
38
+ "@types/node": "^22.13.10",
39
+ "tsx": "^4.19.3",
40
+ "typescript": "^5.8.2"
41
+ }
42
+ }
@@ -0,0 +1,106 @@
1
+ import { spawn } from "node:child_process";
2
+ import { readFile, writeFile } from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+
6
+ function run(command, args, cwd, stdio = "inherit") {
7
+ return new Promise((resolve, reject) => {
8
+ const child = spawn(command, args, { cwd, stdio, shell: process.platform === "win32" });
9
+ let stdout = "";
10
+ let stderr = "";
11
+
12
+ if (stdio === "pipe") {
13
+ child.stdout.on("data", (chunk) => {
14
+ stdout += String(chunk);
15
+ });
16
+
17
+ child.stderr.on("data", (chunk) => {
18
+ stderr += String(chunk);
19
+ });
20
+ }
21
+
22
+ child.on("error", reject);
23
+ child.on("exit", (code) => {
24
+ resolve({ code: code ?? 1, stdout, stderr });
25
+ });
26
+ });
27
+ }
28
+
29
+ function isNotFoundError(output) {
30
+ const lower = output.toLowerCase();
31
+ return lower.includes("e404") || lower.includes("404 not found") || lower.includes("no match found");
32
+ }
33
+
34
+ function parseSemver(version) {
35
+ const match = /^(\d+)\.(\d+)\.(\d+)$/.exec(version);
36
+ if (!match) {
37
+ throw new Error(`Unsupported version format: ${version}. Expected x.y.z`);
38
+ }
39
+
40
+ return { major: Number.parseInt(match[1], 10), minor: Number.parseInt(match[2], 10), patch: Number.parseInt(match[3], 10) };
41
+ }
42
+
43
+ function bumpMinor(version) {
44
+ const parsed = parseSemver(version);
45
+ return `${parsed.major}.${parsed.minor + 1}.0`;
46
+ }
47
+
48
+ async function readPackageJson(packageJsonPath) {
49
+ const raw = await readFile(packageJsonPath, "utf8");
50
+ return JSON.parse(raw);
51
+ }
52
+
53
+ async function writePackageJson(packageJsonPath, pkg) {
54
+ await writeFile(packageJsonPath, `${JSON.stringify(pkg, null, 2)}\n`, "utf8");
55
+ }
56
+
57
+ async function versionExistsOnNpm(packageName, version, cwd) {
58
+ const result = await run("npm", ["view", `${packageName}@${version}`, "version", "--json"], cwd, "pipe");
59
+ const combined = `${result.stdout}\n${result.stderr}`;
60
+ if (result.code === 0) {
61
+ return true;
62
+ }
63
+ if (isNotFoundError(combined)) {
64
+ return false;
65
+ }
66
+
67
+ throw new Error(`Unable to verify npm version ${packageName}@${version}: ${combined.trim()}`);
68
+ }
69
+
70
+ async function main() {
71
+ const scriptDir = path.dirname(fileURLToPath(import.meta.url));
72
+ const projectRoot = path.resolve(scriptDir, "..");
73
+ const packageJsonPath = path.join(projectRoot, "package.json");
74
+ const packageJson = await readPackageJson(packageJsonPath);
75
+ if (typeof packageJson.name !== "string" || packageJson.name.length === 0) {
76
+ throw new Error("package.json name is required.");
77
+ }
78
+ if (typeof packageJson.version !== "string" || packageJson.version.length === 0) {
79
+ throw new Error("package.json version is required.");
80
+ }
81
+
82
+ const exists = await versionExistsOnNpm(packageJson.name, packageJson.version, projectRoot);
83
+ if (exists) {
84
+ const nextVersion = bumpMinor(packageJson.version);
85
+ packageJson.version = nextVersion;
86
+ await writePackageJson(packageJsonPath, packageJson);
87
+ console.log(`Version ${packageJson.name}@${packageJson.version} already existed. Bumped to ${nextVersion}.`);
88
+ } else {
89
+ console.log(`Publishing new version ${packageJson.name}@${packageJson.version}.`);
90
+ }
91
+
92
+ const build = await run("npm", ["run", "build"], projectRoot);
93
+ if (build.code !== 0) {
94
+ throw new Error("npm run build failed.");
95
+ }
96
+
97
+ const publish = await run("npm", ["publish", "--access", "public", "--ignore-scripts"], projectRoot);
98
+ if (publish.code !== 0) {
99
+ throw new Error("npm publish failed.");
100
+ }
101
+ }
102
+
103
+ main().catch((error) => {
104
+ console.error(error instanceof Error ? error.message : String(error));
105
+ process.exit(1);
106
+ });
@@ -0,0 +1,65 @@
1
+ import { spawn } from "node:child_process";
2
+ import { readdir } from "node:fs/promises";
3
+ import path from "node:path";
4
+
5
+ const rootDir = process.cwd();
6
+ const testRoots = ["test", "src"];
7
+
8
+ async function collectTestsFrom(directoryPath, collector) {
9
+ let entries;
10
+
11
+ try {
12
+ entries = await readdir(directoryPath, { withFileTypes: true });
13
+ } catch (error) {
14
+ if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") {
15
+ return;
16
+ }
17
+
18
+ throw error;
19
+ }
20
+
21
+ for (const entry of entries) {
22
+ const absolutePath = path.join(directoryPath, entry.name);
23
+ if (entry.isDirectory()) {
24
+ await collectTestsFrom(absolutePath, collector);
25
+ continue;
26
+ }
27
+ if (!entry.isFile()) {
28
+ continue;
29
+ }
30
+ if (entry.name.endsWith(".test.ts")) {
31
+ collector.push(path.relative(rootDir, absolutePath));
32
+ }
33
+ }
34
+ }
35
+
36
+ function runNodeTests(files) {
37
+ return new Promise((resolve, reject) => {
38
+ const args = ["--import", "tsx", "--test", ...files];
39
+ const child = spawn(process.execPath, args, { cwd: rootDir, stdio: "inherit", shell: process.platform === "win32" });
40
+
41
+ child.on("error", reject);
42
+ child.on("exit", (code) => {
43
+ resolve(code ?? 1);
44
+ });
45
+ });
46
+ }
47
+
48
+ async function main() {
49
+ const testFiles = [];
50
+
51
+ for (const root of testRoots) {
52
+ await collectTestsFrom(path.join(rootDir, root), testFiles);
53
+ }
54
+
55
+ const uniqueFiles = [...new Set(testFiles)].sort((left, right) => left.localeCompare(right));
56
+ const exitCode = await runNodeTests(uniqueFiles);
57
+ if (exitCode !== 0) {
58
+ process.exit(exitCode);
59
+ }
60
+ }
61
+
62
+ main().catch((error) => {
63
+ console.error(error instanceof Error ? error.message : String(error));
64
+ process.exit(1);
65
+ });
@@ -0,0 +1,57 @@
1
+ import type { ServerType } from "@hono/node-server";
2
+
3
+ /**
4
+ * @section imports:internals
5
+ */
6
+
7
+ import config from "../config.ts";
8
+ import { HttpServerService } from "../http/http-server.service.ts";
9
+ import logger from "../logger.ts";
10
+
11
+ /**
12
+ * @section class
13
+ */
14
+
15
+ export class ServiceRuntime {
16
+ /**
17
+ * @section private:attributes
18
+ */
19
+
20
+ private readonly httpServerService: HttpServerService;
21
+
22
+ /**
23
+ * @section constructor
24
+ */
25
+
26
+ public constructor(httpServerService: HttpServerService) {
27
+ this.httpServerService = httpServerService;
28
+ }
29
+
30
+ /**
31
+ * @section factory
32
+ */
33
+
34
+ public static createDefault(): ServiceRuntime {
35
+ return new ServiceRuntime(HttpServerService.createDefault());
36
+ }
37
+
38
+ /**
39
+ * @section public:methods
40
+ */
41
+
42
+ public buildServer(): ServerType {
43
+ return this.httpServerService.buildServer();
44
+ }
45
+
46
+ public async handleRequest(request: Request): Promise<Response> {
47
+ return this.httpServerService.handleRequest(request);
48
+ }
49
+
50
+ public startServer(): ServerType {
51
+ const server = this.buildServer();
52
+ server.listen(config.DEFAULT_PORT, () => {
53
+ logger.info(`service listening on http://localhost:${config.DEFAULT_PORT}`);
54
+ });
55
+ return server;
56
+ }
57
+ }
@@ -0,0 +1,47 @@
1
+ /**
2
+ * @section imports:internals
3
+ */
4
+
5
+ import config from "../config.ts";
6
+
7
+ /**
8
+ * @section types
9
+ */
10
+
11
+ export type AppInfoPayload = { ok: true; serviceName: string };
12
+
13
+ /**
14
+ * @section class
15
+ */
16
+
17
+ export class AppInfoService {
18
+ /**
19
+ * @section private:attributes
20
+ */
21
+
22
+ private readonly serviceName: string;
23
+
24
+ /**
25
+ * @section constructor
26
+ */
27
+
28
+ public constructor(serviceName: string) {
29
+ this.serviceName = serviceName;
30
+ }
31
+
32
+ /**
33
+ * @section factory
34
+ */
35
+
36
+ public static createDefault(): AppInfoService {
37
+ return new AppInfoService(config.SERVICE_NAME);
38
+ }
39
+
40
+ /**
41
+ * @section public:methods
42
+ */
43
+
44
+ public buildPayload(): AppInfoPayload {
45
+ return { ok: true, serviceName: this.serviceName };
46
+ }
47
+ }
@@ -0,0 +1,11 @@
1
+ import "dotenv/config";
2
+
3
+ const ENV = process.env;
4
+
5
+ const config = {
6
+ RESPONSE_CONTENT_TYPE: ENV.RESPONSE_CONTENT_TYPE || "application/json",
7
+ DEFAULT_PORT: Number(ENV.PORT || 3000),
8
+ SERVICE_NAME: ENV.SERVICE_NAME || "{{packageName}}",
9
+ } as const;
10
+
11
+ export default config;
@@ -0,0 +1,66 @@
1
+ /**
2
+ * @section imports:externals
3
+ */
4
+
5
+ import { createAdaptorServer } from "@hono/node-server";
6
+ import type { ServerType } from "@hono/node-server";
7
+ import { Hono } from "hono";
8
+
9
+ /**
10
+ * @section imports:internals
11
+ */
12
+
13
+ import { AppInfoService } from "../app-info/app-info.service.ts";
14
+ import config from "../config.ts";
15
+
16
+ /**
17
+ * @section class
18
+ */
19
+
20
+ export class HttpServerService {
21
+ /**
22
+ * @section private:attributes
23
+ */
24
+
25
+ private readonly appInfoService: AppInfoService;
26
+
27
+ /**
28
+ * @section constructor
29
+ */
30
+
31
+ public constructor(appInfoService: AppInfoService) {
32
+ this.appInfoService = appInfoService;
33
+ }
34
+
35
+ /**
36
+ * @section factory
37
+ */
38
+
39
+ public static createDefault(): HttpServerService {
40
+ return new HttpServerService(AppInfoService.createDefault());
41
+ }
42
+
43
+ /**
44
+ * @section public:methods
45
+ */
46
+
47
+ public async handleRequest(request: Request): Promise<Response> {
48
+ const app = new Hono();
49
+ app.get("/", (context) => {
50
+ const payload = this.appInfoService.buildPayload();
51
+ context.header("content-type", config.RESPONSE_CONTENT_TYPE);
52
+ return context.json(payload, 200);
53
+ });
54
+ return app.fetch(request);
55
+ }
56
+
57
+ public buildServer(): ServerType {
58
+ const app = new Hono();
59
+ app.get("/", (context) => {
60
+ const payload = this.appInfoService.buildPayload();
61
+ context.header("content-type", config.RESPONSE_CONTENT_TYPE);
62
+ return context.json(payload, 200);
63
+ });
64
+ return createAdaptorServer({ fetch: app.fetch });
65
+ }
66
+ }
@@ -0,0 +1,2 @@
1
+ export { ServiceRuntime } from "./app/service-runtime.service.ts";
2
+ export type { AppInfoPayload } from "./app-info/app-info.service.ts";
@@ -0,0 +1,7 @@
1
+ import Logger from "@sha3/logger";
2
+
3
+ const PACKAGE_NAME = "{{packageName}}";
4
+ const LOGGER_NAME = PACKAGE_NAME.startsWith("@") ? PACKAGE_NAME.split("/")[1] || PACKAGE_NAME : PACKAGE_NAME;
5
+ const logger = new Logger({ loggerName: LOGGER_NAME });
6
+
7
+ export default logger;