@sha3/code-standards 0.1.3 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -0
- package/bin/code-standards.mjs +21 -80
- package/eslint/test.mjs +1 -5
- package/package.json +1 -1
- package/prettier/index.cjs +1 -1
- package/profiles/default.profile.json +2 -0
- package/profiles/schema.json +7 -7
- package/resources/ai/templates/examples/demo/src/billing/billing-service.ts +18 -3
- package/resources/ai/templates/examples/demo/src/config.ts +3 -0
- package/resources/ai/templates/examples/demo/src/invoices/invoice-errors.ts +12 -0
- package/resources/ai/templates/examples/demo/src/invoices/invoice-service.ts +14 -1
- package/resources/ai/templates/examples/rules/async-bad.ts +12 -0
- package/resources/ai/templates/examples/rules/async-good.ts +12 -0
- package/resources/ai/templates/examples/rules/class-first-bad.ts +12 -0
- package/resources/ai/templates/examples/rules/class-first-good.ts +12 -0
- package/resources/ai/templates/examples/rules/constructor-bad.ts +12 -0
- package/resources/ai/templates/examples/rules/constructor-good.ts +12 -0
- package/resources/ai/templates/examples/rules/control-flow-bad.ts +12 -0
- package/resources/ai/templates/examples/rules/control-flow-good.ts +12 -0
- package/resources/ai/templates/examples/rules/errors-bad.ts +12 -0
- package/resources/ai/templates/examples/rules/errors-good.ts +12 -0
- package/resources/ai/templates/examples/rules/functions-bad.ts +12 -0
- package/resources/ai/templates/examples/rules/functions-good.ts +12 -0
- package/resources/ai/templates/examples/rules/returns-bad.ts +12 -0
- package/resources/ai/templates/examples/rules/returns-good.ts +12 -0
- package/resources/ai/templates/examples/rules/testing-bad.ts +12 -0
- package/resources/ai/templates/examples/rules/testing-good.ts +12 -0
- package/resources/ai/templates/rules/architecture.md +2 -0
- package/resources/ai/templates/rules/class-first.md +2 -0
- package/resources/ai/templates/rules/functions.md +1 -0
- package/standards/architecture.md +2 -1
- package/standards/manifest.json +1 -1
- package/standards/schema.json +2 -11
- package/standards/style.md +12 -9
- package/templates/node-lib/src/config.ts +1 -0
- package/templates/node-lib/src/index.ts +3 -1
- package/templates/node-service/src/config.ts +3 -0
- package/templates/node-service/src/index.ts +4 -3
- package/templates/node-service/test/smoke.test.ts +1 -1
package/README.md
CHANGED
|
@@ -53,12 +53,14 @@ Default generated contract includes structural class/file blocks such as:
|
|
|
53
53
|
- `consts`
|
|
54
54
|
- `types`
|
|
55
55
|
- `private:attributes`
|
|
56
|
+
- `protected:attributes`
|
|
56
57
|
- `private:properties`
|
|
57
58
|
- `public:properties`
|
|
58
59
|
- `constructor`
|
|
59
60
|
- `static:properties`
|
|
60
61
|
- `factory`
|
|
61
62
|
- `private:methods`
|
|
63
|
+
- `protected:methods`
|
|
62
64
|
- `public:methods`
|
|
63
65
|
- `static:methods`
|
|
64
66
|
|
|
@@ -72,6 +74,7 @@ All blocks MUST exist even when empty (`// empty`).
|
|
|
72
74
|
|
|
73
75
|
Additional blocking defaults:
|
|
74
76
|
|
|
77
|
+
- Max line length is 160 chars, and lines should stay on one line when they fit.
|
|
75
78
|
- `if`/`else`/loop statements MUST always use braces.
|
|
76
79
|
- README updates MUST follow a top-tier quality standard (see `standards/readme.md`).
|
|
77
80
|
|
|
@@ -107,6 +110,7 @@ After `init`, your new repo contains:
|
|
|
107
110
|
- `ai/codex.md`, `ai/cursor.md`, `ai/copilot.md`, `ai/windsurf.md`
|
|
108
111
|
- `ai/examples/rules/*.ts` (good/bad examples per rule)
|
|
109
112
|
- `ai/examples/demo/src/*` (feature-folder demo with classes and section blocks)
|
|
113
|
+
- `src/config.ts` for centralized hardcoded configuration values
|
|
110
114
|
- `.gitignore` preconfigured for Node/TypeScript output
|
|
111
115
|
- lint/format/typecheck/test-ready project template
|
|
112
116
|
|
|
@@ -120,6 +124,7 @@ Generated project code is TypeScript-only: implementation and tests live in `.ts
|
|
|
120
124
|
|
|
121
125
|
Demo structure generated by default:
|
|
122
126
|
|
|
127
|
+
- `ai/examples/demo/src/config.ts`
|
|
123
128
|
- `ai/examples/demo/src/invoices/invoice-service.ts`
|
|
124
129
|
- `ai/examples/demo/src/invoices/invoice-errors.ts`
|
|
125
130
|
- `ai/examples/demo/src/invoices/invoice-types.ts`
|
package/bin/code-standards.mjs
CHANGED
|
@@ -55,12 +55,14 @@ const DEFAULT_PROFILE = {
|
|
|
55
55
|
"consts",
|
|
56
56
|
"types",
|
|
57
57
|
"private:attributes",
|
|
58
|
+
"protected:attributes",
|
|
58
59
|
"private:properties",
|
|
59
60
|
"public:properties",
|
|
60
61
|
"constructor",
|
|
61
62
|
"static:properties",
|
|
62
63
|
"factory",
|
|
63
64
|
"private:methods",
|
|
65
|
+
"protected:methods",
|
|
64
66
|
"public:methods",
|
|
65
67
|
"static:methods"
|
|
66
68
|
],
|
|
@@ -88,11 +90,7 @@ const PROFILE_QUESTIONS = [
|
|
|
88
90
|
{
|
|
89
91
|
key: "return_policy",
|
|
90
92
|
prompt: "Return policy",
|
|
91
|
-
options: [
|
|
92
|
-
"single_return_strict_no_exceptions",
|
|
93
|
-
"single_return_with_guard_clauses",
|
|
94
|
-
"free_return_style"
|
|
95
|
-
]
|
|
93
|
+
options: ["single_return_strict_no_exceptions", "single_return_with_guard_clauses", "free_return_style"]
|
|
96
94
|
},
|
|
97
95
|
{
|
|
98
96
|
key: "class_design",
|
|
@@ -202,9 +200,7 @@ function parseInitArgs(argv) {
|
|
|
202
200
|
const token = argv[i];
|
|
203
201
|
|
|
204
202
|
if (!token.startsWith("-")) {
|
|
205
|
-
throw new Error(
|
|
206
|
-
`Positional project names are not supported: ${token}. Run init from your target directory.`
|
|
207
|
-
);
|
|
203
|
+
throw new Error(`Positional project names are not supported: ${token}. Run init from your target directory.`);
|
|
208
204
|
}
|
|
209
205
|
|
|
210
206
|
if (token === "--template") {
|
|
@@ -397,9 +393,7 @@ async function ensureTargetReady(targetPath, force) {
|
|
|
397
393
|
const nonGitEntries = entries.filter((entry) => entry !== ".git");
|
|
398
394
|
|
|
399
395
|
if (nonGitEntries.length > 0 && !force) {
|
|
400
|
-
throw new Error(
|
|
401
|
-
`Target directory is not empty: ${targetPath}. Use --force to continue and overwrite files.`
|
|
402
|
-
);
|
|
396
|
+
throw new Error(`Target directory is not empty: ${targetPath}. Use --force to continue and overwrite files.`);
|
|
403
397
|
}
|
|
404
398
|
}
|
|
405
399
|
|
|
@@ -456,9 +450,7 @@ function validateProfile(profile, schema, sourceLabel) {
|
|
|
456
450
|
return;
|
|
457
451
|
}
|
|
458
452
|
|
|
459
|
-
const details = (validate.errors ?? [])
|
|
460
|
-
.map((issue) => `${issue.instancePath || "/"}: ${issue.message ?? "invalid"}`)
|
|
461
|
-
.join("; ");
|
|
453
|
+
const details = (validate.errors ?? []).map((issue) => `${issue.instancePath || "/"}: ${issue.message ?? "invalid"}`).join("; ");
|
|
462
454
|
|
|
463
455
|
throw new Error(`Invalid profile at ${sourceLabel}: ${details}`);
|
|
464
456
|
}
|
|
@@ -550,12 +542,7 @@ async function createProfileInteractively(baseProfile) {
|
|
|
550
542
|
|
|
551
543
|
try {
|
|
552
544
|
for (const question of PROFILE_QUESTIONS) {
|
|
553
|
-
profile[question.key] = await askChoice(
|
|
554
|
-
rl,
|
|
555
|
-
question.prompt,
|
|
556
|
-
question.options,
|
|
557
|
-
profile[question.key]
|
|
558
|
-
);
|
|
545
|
+
profile[question.key] = await askChoice(rl, question.prompt, question.options, profile[question.key]);
|
|
559
546
|
}
|
|
560
547
|
} finally {
|
|
561
548
|
rl.close();
|
|
@@ -573,9 +560,7 @@ async function runProfile(rawOptions) {
|
|
|
573
560
|
const packageRoot = resolvePackageRoot();
|
|
574
561
|
const schema = await loadProfileSchema(packageRoot);
|
|
575
562
|
const defaultProfilePath = getBundledProfilePath(packageRoot);
|
|
576
|
-
const outputPath = rawOptions.profilePath
|
|
577
|
-
? path.resolve(process.cwd(), rawOptions.profilePath)
|
|
578
|
-
: defaultProfilePath;
|
|
563
|
+
const outputPath = rawOptions.profilePath ? path.resolve(process.cwd(), rawOptions.profilePath) : defaultProfilePath;
|
|
579
564
|
const shouldUseNonInteractive = rawOptions.nonInteractive || !process.stdin.isTTY;
|
|
580
565
|
const exists = await pathExists(outputPath);
|
|
581
566
|
|
|
@@ -590,11 +575,7 @@ async function runProfile(rawOptions) {
|
|
|
590
575
|
});
|
|
591
576
|
|
|
592
577
|
try {
|
|
593
|
-
const shouldOverwrite = await promptYesNo(
|
|
594
|
-
rl,
|
|
595
|
-
`Profile already exists at ${outputPath}. Overwrite?`,
|
|
596
|
-
false
|
|
597
|
-
);
|
|
578
|
+
const shouldOverwrite = await promptYesNo(rl, `Profile already exists at ${outputPath}. Overwrite?`, false);
|
|
598
579
|
|
|
599
580
|
if (!shouldOverwrite) {
|
|
600
581
|
console.log("Profile update cancelled.");
|
|
@@ -656,44 +637,30 @@ function buildAlternativeRules(profile) {
|
|
|
656
637
|
|
|
657
638
|
if (profile.return_policy !== "single_return_strict_no_exceptions") {
|
|
658
639
|
codeRules.push(
|
|
659
|
-
"### Return Policy Override (MUST)\n\n" +
|
|
660
|
-
`- The active return policy is \`${profile.return_policy}\` and MUST be respected for all new functions.`
|
|
640
|
+
"### Return Policy Override (MUST)\n\n" + `- The active return policy is \`${profile.return_policy}\` and MUST be respected for all new functions.`
|
|
661
641
|
);
|
|
662
642
|
}
|
|
663
643
|
|
|
664
644
|
if (profile.function_size_policy !== "max_30_lines_soft") {
|
|
665
|
-
codeRules.push(
|
|
666
|
-
"### Function Size Override (MUST)\n\n" +
|
|
667
|
-
`- The active function-size policy is \`${profile.function_size_policy}\` and MUST be enforced.`
|
|
668
|
-
);
|
|
645
|
+
codeRules.push("### Function Size Override (MUST)\n\n" + `- The active function-size policy is \`${profile.function_size_policy}\` and MUST be enforced.`);
|
|
669
646
|
}
|
|
670
647
|
|
|
671
648
|
if (profile.error_handling !== "exceptions_with_typed_errors") {
|
|
672
|
-
codeRules.push(
|
|
673
|
-
"### Error Handling Override (MUST)\n\n" +
|
|
674
|
-
`- The active error-handling policy is \`${profile.error_handling}\`.`
|
|
675
|
-
);
|
|
649
|
+
codeRules.push("### Error Handling Override (MUST)\n\n" + `- The active error-handling policy is \`${profile.error_handling}\`.`);
|
|
676
650
|
}
|
|
677
651
|
|
|
678
652
|
if (profile.async_style !== "async_await_only") {
|
|
679
|
-
codeRules.push(
|
|
680
|
-
"### Async Style Override (MUST)\n\n" +
|
|
681
|
-
`- The active async policy is \`${profile.async_style}\` and MUST be followed in new code.`
|
|
682
|
-
);
|
|
653
|
+
codeRules.push("### Async Style Override (MUST)\n\n" + `- The active async policy is \`${profile.async_style}\` and MUST be followed in new code.`);
|
|
683
654
|
}
|
|
684
655
|
|
|
685
656
|
let architectureRule = "";
|
|
686
657
|
if (profile.architecture !== "feature_folders") {
|
|
687
|
-
architectureRule =
|
|
688
|
-
"### Architecture Override (MUST)\n\n" +
|
|
689
|
-
`- The active architecture is \`${profile.architecture}\` and MUST take precedence.`;
|
|
658
|
+
architectureRule = "### Architecture Override (MUST)\n\n" + `- The active architecture is \`${profile.architecture}\` and MUST take precedence.`;
|
|
690
659
|
}
|
|
691
660
|
|
|
692
661
|
let testingRule = "";
|
|
693
662
|
if (profile.testing_policy !== "tests_required_for_behavior_change") {
|
|
694
|
-
testingRule =
|
|
695
|
-
"### Testing Override (MUST)\n\n" +
|
|
696
|
-
`- The active testing policy is \`${profile.testing_policy}\`.`;
|
|
663
|
+
testingRule = "### Testing Override (MUST)\n\n" + `- The active testing policy is \`${profile.testing_policy}\`.`;
|
|
697
664
|
}
|
|
698
665
|
|
|
699
666
|
return {
|
|
@@ -722,14 +689,7 @@ async function buildRuleSections(packageRoot, profile) {
|
|
|
722
689
|
const readmeRule = await readRule("readme.md");
|
|
723
690
|
|
|
724
691
|
const alternatives = buildAlternativeRules(profile);
|
|
725
|
-
const codeGenerationRules = [
|
|
726
|
-
classRule,
|
|
727
|
-
functionRule,
|
|
728
|
-
returnRule,
|
|
729
|
-
controlFlowRule,
|
|
730
|
-
errorRule,
|
|
731
|
-
asyncRule
|
|
732
|
-
];
|
|
692
|
+
const codeGenerationRules = [classRule, functionRule, returnRule, controlFlowRule, errorRule, asyncRule];
|
|
733
693
|
|
|
734
694
|
if (alternatives.codeRules.length > 0) {
|
|
735
695
|
codeGenerationRules.push(...alternatives.codeRules);
|
|
@@ -743,13 +703,7 @@ async function buildRuleSections(packageRoot, profile) {
|
|
|
743
703
|
}
|
|
744
704
|
|
|
745
705
|
async function renderProjectAgents(packageRoot, targetDir, projectName, profile) {
|
|
746
|
-
const templatePath = path.join(
|
|
747
|
-
packageRoot,
|
|
748
|
-
"resources",
|
|
749
|
-
"ai",
|
|
750
|
-
"templates",
|
|
751
|
-
"agents.project.template.md"
|
|
752
|
-
);
|
|
706
|
+
const templatePath = path.join(packageRoot, "resources", "ai", "templates", "agents.project.template.md");
|
|
753
707
|
const template = await readFile(templatePath, "utf8");
|
|
754
708
|
const sections = await buildRuleSections(packageRoot, profile);
|
|
755
709
|
|
|
@@ -807,11 +761,7 @@ async function maybeInitializeProfileInteractively(packageRoot, profilePath) {
|
|
|
807
761
|
});
|
|
808
762
|
|
|
809
763
|
try {
|
|
810
|
-
const shouldInit = await promptYesNo(
|
|
811
|
-
rl,
|
|
812
|
-
`Profile not found at ${profilePath}. Initialize it with package defaults?`,
|
|
813
|
-
true
|
|
814
|
-
);
|
|
764
|
+
const shouldInit = await promptYesNo(rl, `Profile not found at ${profilePath}. Initialize it with package defaults?`, true);
|
|
815
765
|
|
|
816
766
|
if (!shouldInit) {
|
|
817
767
|
throw new Error("Profile initialization declined by user.");
|
|
@@ -874,9 +824,7 @@ async function promptForMissing(options) {
|
|
|
874
824
|
const resolved = { ...options };
|
|
875
825
|
|
|
876
826
|
if (!resolved.template) {
|
|
877
|
-
const templateAnswer = await rl.question(
|
|
878
|
-
"Choose template (node-lib/node-service) [node-lib]: "
|
|
879
|
-
);
|
|
827
|
+
const templateAnswer = await rl.question("Choose template (node-lib/node-service) [node-lib]: ");
|
|
880
828
|
const normalized = templateAnswer.trim() || "node-lib";
|
|
881
829
|
|
|
882
830
|
if (!TEMPLATE_NAMES.includes(normalized)) {
|
|
@@ -900,13 +848,7 @@ async function promptForMissing(options) {
|
|
|
900
848
|
|
|
901
849
|
async function validateInitResources(packageRoot, templateName) {
|
|
902
850
|
const templateDir = path.join(packageRoot, "templates", templateName);
|
|
903
|
-
const agentsTemplatePath = path.join(
|
|
904
|
-
packageRoot,
|
|
905
|
-
"resources",
|
|
906
|
-
"ai",
|
|
907
|
-
"templates",
|
|
908
|
-
"agents.project.template.md"
|
|
909
|
-
);
|
|
851
|
+
const agentsTemplatePath = path.join(packageRoot, "resources", "ai", "templates", "agents.project.template.md");
|
|
910
852
|
const adaptersTemplateDir = path.join(packageRoot, "resources", "ai", "templates", "adapters");
|
|
911
853
|
const examplesTemplateDir = path.join(packageRoot, "resources", "ai", "templates", "examples");
|
|
912
854
|
|
|
@@ -944,8 +886,7 @@ async function runInit(rawOptions) {
|
|
|
944
886
|
|
|
945
887
|
const targetPath = path.resolve(process.cwd());
|
|
946
888
|
const inferredProjectName = path.basename(targetPath);
|
|
947
|
-
const projectName =
|
|
948
|
-
inferredProjectName && inferredProjectName !== path.sep ? inferredProjectName : "my-project";
|
|
889
|
+
const projectName = inferredProjectName && inferredProjectName !== path.sep ? inferredProjectName : "my-project";
|
|
949
890
|
const packageName = sanitizePackageName(projectName);
|
|
950
891
|
|
|
951
892
|
await ensureTargetReady(targetPath, options.force);
|
package/eslint/test.mjs
CHANGED
|
@@ -4,11 +4,7 @@ import nodeConfig from "./node.mjs";
|
|
|
4
4
|
export default [
|
|
5
5
|
...nodeConfig,
|
|
6
6
|
{
|
|
7
|
-
files: [
|
|
8
|
-
"**/*.test.{js,mjs,cjs,ts,mts,cts}",
|
|
9
|
-
"**/*.spec.{js,mjs,cjs,ts,mts,cts}",
|
|
10
|
-
"test/**/*.{js,mjs,cjs,ts,mts,cts}"
|
|
11
|
-
],
|
|
7
|
+
files: ["**/*.test.{js,mjs,cjs,ts,mts,cts}", "**/*.spec.{js,mjs,cjs,ts,mts,cts}", "test/**/*.{js,mjs,cjs,ts,mts,cts}"],
|
|
12
8
|
languageOptions: {
|
|
13
9
|
globals: {
|
|
14
10
|
...globals.mocha,
|
package/package.json
CHANGED
package/prettier/index.cjs
CHANGED
|
@@ -18,12 +18,14 @@
|
|
|
18
18
|
"consts",
|
|
19
19
|
"types",
|
|
20
20
|
"private:attributes",
|
|
21
|
+
"protected:attributes",
|
|
21
22
|
"private:properties",
|
|
22
23
|
"public:properties",
|
|
23
24
|
"constructor",
|
|
24
25
|
"static:properties",
|
|
25
26
|
"factory",
|
|
26
27
|
"private:methods",
|
|
28
|
+
"protected:methods",
|
|
27
29
|
"public:methods",
|
|
28
30
|
"static:methods"
|
|
29
31
|
],
|
package/profiles/schema.json
CHANGED
|
@@ -43,11 +43,7 @@
|
|
|
43
43
|
},
|
|
44
44
|
"return_policy": {
|
|
45
45
|
"type": "string",
|
|
46
|
-
"enum": [
|
|
47
|
-
"single_return_strict_no_exceptions",
|
|
48
|
-
"single_return_with_guard_clauses",
|
|
49
|
-
"free_return_style"
|
|
50
|
-
]
|
|
46
|
+
"enum": ["single_return_strict_no_exceptions", "single_return_with_guard_clauses", "free_return_style"]
|
|
51
47
|
},
|
|
52
48
|
"class_design": {
|
|
53
49
|
"type": "string",
|
|
@@ -87,8 +83,8 @@
|
|
|
87
83
|
},
|
|
88
84
|
"comment_section_blocks": {
|
|
89
85
|
"type": "array",
|
|
90
|
-
"minItems":
|
|
91
|
-
"maxItems":
|
|
86
|
+
"minItems": 15,
|
|
87
|
+
"maxItems": 15,
|
|
92
88
|
"items": {
|
|
93
89
|
"type": "string",
|
|
94
90
|
"enum": [
|
|
@@ -97,12 +93,14 @@
|
|
|
97
93
|
"consts",
|
|
98
94
|
"types",
|
|
99
95
|
"private:attributes",
|
|
96
|
+
"protected:attributes",
|
|
100
97
|
"private:properties",
|
|
101
98
|
"public:properties",
|
|
102
99
|
"constructor",
|
|
103
100
|
"static:properties",
|
|
104
101
|
"factory",
|
|
105
102
|
"private:methods",
|
|
103
|
+
"protected:methods",
|
|
106
104
|
"public:methods",
|
|
107
105
|
"static:methods"
|
|
108
106
|
]
|
|
@@ -113,12 +111,14 @@
|
|
|
113
111
|
"consts",
|
|
114
112
|
"types",
|
|
115
113
|
"private:attributes",
|
|
114
|
+
"protected:attributes",
|
|
116
115
|
"private:properties",
|
|
117
116
|
"public:properties",
|
|
118
117
|
"constructor",
|
|
119
118
|
"static:properties",
|
|
120
119
|
"factory",
|
|
121
120
|
"private:methods",
|
|
121
|
+
"protected:methods",
|
|
122
122
|
"public:methods",
|
|
123
123
|
"static:methods"
|
|
124
124
|
]
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* @section imports:internals
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
+
import { BILLING_CURRENCY_SYMBOL, STATUS_SERVICE_URL } from "../config.js";
|
|
11
12
|
import type { InvoiceService } from "../invoices/invoice-service.js";
|
|
12
13
|
import type { InvoiceSummary } from "../invoices/invoice-types.js";
|
|
13
14
|
|
|
@@ -15,7 +16,7 @@ import type { InvoiceSummary } from "../invoices/invoice-types.js";
|
|
|
15
16
|
* @section consts
|
|
16
17
|
*/
|
|
17
18
|
|
|
18
|
-
|
|
19
|
+
// empty
|
|
19
20
|
|
|
20
21
|
/**
|
|
21
22
|
* @section types
|
|
@@ -26,6 +27,7 @@ export type BillingSnapshot = {
|
|
|
26
27
|
invoiceCount: number;
|
|
27
28
|
totalAmount: number;
|
|
28
29
|
formattedTotal: string;
|
|
30
|
+
statusServiceUrl: string;
|
|
29
31
|
};
|
|
30
32
|
|
|
31
33
|
export class BillingService {
|
|
@@ -35,6 +37,12 @@ export class BillingService {
|
|
|
35
37
|
|
|
36
38
|
// empty
|
|
37
39
|
|
|
40
|
+
/**
|
|
41
|
+
* @section protected:attributes
|
|
42
|
+
*/
|
|
43
|
+
|
|
44
|
+
// empty
|
|
45
|
+
|
|
38
46
|
/**
|
|
39
47
|
* @section private:properties
|
|
40
48
|
*/
|
|
@@ -75,10 +83,16 @@ export class BillingService {
|
|
|
75
83
|
*/
|
|
76
84
|
|
|
77
85
|
private formatCurrency(amount: number): string {
|
|
78
|
-
const formattedAmount = `${
|
|
86
|
+
const formattedAmount = `${BILLING_CURRENCY_SYMBOL}${amount.toFixed(2)}`;
|
|
79
87
|
return formattedAmount;
|
|
80
88
|
}
|
|
81
89
|
|
|
90
|
+
/**
|
|
91
|
+
* @section protected:methods
|
|
92
|
+
*/
|
|
93
|
+
|
|
94
|
+
// empty
|
|
95
|
+
|
|
82
96
|
/**
|
|
83
97
|
* @section public:methods
|
|
84
98
|
*/
|
|
@@ -89,7 +103,8 @@ export class BillingService {
|
|
|
89
103
|
customerId,
|
|
90
104
|
invoiceCount: summary.count,
|
|
91
105
|
totalAmount: summary.totalAmount,
|
|
92
|
-
formattedTotal: this.formatCurrency(summary.totalAmount)
|
|
106
|
+
formattedTotal: this.formatCurrency(summary.totalAmount),
|
|
107
|
+
statusServiceUrl: STATUS_SERVICE_URL
|
|
93
108
|
};
|
|
94
109
|
return snapshot;
|
|
95
110
|
}
|
|
@@ -29,6 +29,12 @@ export class InvalidInvoiceCommandError extends Error {
|
|
|
29
29
|
|
|
30
30
|
// empty
|
|
31
31
|
|
|
32
|
+
/**
|
|
33
|
+
* @section protected:attributes
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
// empty
|
|
37
|
+
|
|
32
38
|
/**
|
|
33
39
|
* @section private:properties
|
|
34
40
|
*/
|
|
@@ -72,6 +78,12 @@ export class InvalidInvoiceCommandError extends Error {
|
|
|
72
78
|
|
|
73
79
|
// empty
|
|
74
80
|
|
|
81
|
+
/**
|
|
82
|
+
* @section protected:methods
|
|
83
|
+
*/
|
|
84
|
+
|
|
85
|
+
// empty
|
|
86
|
+
|
|
75
87
|
/**
|
|
76
88
|
* @section public:methods
|
|
77
89
|
*/
|
|
@@ -8,6 +8,7 @@ import { randomUUID } from "node:crypto";
|
|
|
8
8
|
* @section imports:internals
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
+
import { MINIMUM_INVOICE_AMOUNT } from "../config.js";
|
|
11
12
|
import { InvalidInvoiceCommandError } from "./invoice-errors.js";
|
|
12
13
|
import type { CreateInvoiceCommand, Invoice, InvoiceSummary } from "./invoice-types.js";
|
|
13
14
|
|
|
@@ -15,7 +16,7 @@ import type { CreateInvoiceCommand, Invoice, InvoiceSummary } from "./invoice-ty
|
|
|
15
16
|
* @section consts
|
|
16
17
|
*/
|
|
17
18
|
|
|
18
|
-
|
|
19
|
+
// empty
|
|
19
20
|
|
|
20
21
|
/**
|
|
21
22
|
* @section types
|
|
@@ -30,6 +31,12 @@ export class InvoiceService {
|
|
|
30
31
|
|
|
31
32
|
// empty
|
|
32
33
|
|
|
34
|
+
/**
|
|
35
|
+
* @section protected:attributes
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
// empty
|
|
39
|
+
|
|
33
40
|
/**
|
|
34
41
|
* @section private:properties
|
|
35
42
|
*/
|
|
@@ -89,6 +96,12 @@ export class InvoiceService {
|
|
|
89
96
|
return invoice;
|
|
90
97
|
}
|
|
91
98
|
|
|
99
|
+
/**
|
|
100
|
+
* @section protected:methods
|
|
101
|
+
*/
|
|
102
|
+
|
|
103
|
+
// empty
|
|
104
|
+
|
|
92
105
|
/**
|
|
93
106
|
* @section public:methods
|
|
94
107
|
*/
|
|
@@ -33,6 +33,12 @@ export class InvoiceSyncService {
|
|
|
33
33
|
|
|
34
34
|
// empty
|
|
35
35
|
|
|
36
|
+
/**
|
|
37
|
+
* @section protected:attributes
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
// empty
|
|
41
|
+
|
|
36
42
|
/**
|
|
37
43
|
* @section private:properties
|
|
38
44
|
*/
|
|
@@ -76,6 +82,12 @@ export class InvoiceSyncService {
|
|
|
76
82
|
|
|
77
83
|
// empty
|
|
78
84
|
|
|
85
|
+
/**
|
|
86
|
+
* @section protected:methods
|
|
87
|
+
*/
|
|
88
|
+
|
|
89
|
+
// empty
|
|
90
|
+
|
|
79
91
|
/**
|
|
80
92
|
* @section public:methods
|
|
81
93
|
*/
|
|
@@ -33,6 +33,12 @@ export class InvoiceSyncService {
|
|
|
33
33
|
|
|
34
34
|
// empty
|
|
35
35
|
|
|
36
|
+
/**
|
|
37
|
+
* @section protected:attributes
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
// empty
|
|
41
|
+
|
|
36
42
|
/**
|
|
37
43
|
* @section private:properties
|
|
38
44
|
*/
|
|
@@ -76,6 +82,12 @@ export class InvoiceSyncService {
|
|
|
76
82
|
|
|
77
83
|
// empty
|
|
78
84
|
|
|
85
|
+
/**
|
|
86
|
+
* @section protected:methods
|
|
87
|
+
*/
|
|
88
|
+
|
|
89
|
+
// empty
|
|
90
|
+
|
|
79
91
|
/**
|
|
80
92
|
* @section public:methods
|
|
81
93
|
*/
|
|
@@ -30,6 +30,12 @@ export class InvoiceService {
|
|
|
30
30
|
|
|
31
31
|
// empty
|
|
32
32
|
|
|
33
|
+
/**
|
|
34
|
+
* @section protected:attributes
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
// empty
|
|
38
|
+
|
|
33
39
|
/**
|
|
34
40
|
* @section private:properties
|
|
35
41
|
*/
|
|
@@ -69,6 +75,12 @@ export class InvoiceService {
|
|
|
69
75
|
|
|
70
76
|
// empty
|
|
71
77
|
|
|
78
|
+
/**
|
|
79
|
+
* @section protected:methods
|
|
80
|
+
*/
|
|
81
|
+
|
|
82
|
+
// empty
|
|
83
|
+
|
|
72
84
|
/**
|
|
73
85
|
* @section public:methods
|
|
74
86
|
*/
|
|
@@ -30,6 +30,12 @@ export class InvoiceService {
|
|
|
30
30
|
|
|
31
31
|
private readonly requestId: string;
|
|
32
32
|
|
|
33
|
+
/**
|
|
34
|
+
* @section protected:attributes
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
// empty
|
|
38
|
+
|
|
33
39
|
/**
|
|
34
40
|
* @section private:properties
|
|
35
41
|
*/
|
|
@@ -81,6 +87,12 @@ export class InvoiceService {
|
|
|
81
87
|
return invoice;
|
|
82
88
|
}
|
|
83
89
|
|
|
90
|
+
/**
|
|
91
|
+
* @section protected:methods
|
|
92
|
+
*/
|
|
93
|
+
|
|
94
|
+
// empty
|
|
95
|
+
|
|
84
96
|
/**
|
|
85
97
|
* @section public:methods
|
|
86
98
|
*/
|
|
@@ -30,6 +30,12 @@ export class InvoiceIdBuilder {
|
|
|
30
30
|
|
|
31
31
|
// empty
|
|
32
32
|
|
|
33
|
+
/**
|
|
34
|
+
* @section protected:attributes
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
// empty
|
|
38
|
+
|
|
33
39
|
/**
|
|
34
40
|
* @section private:properties
|
|
35
41
|
*/
|
|
@@ -79,6 +85,12 @@ export class InvoiceIdBuilder {
|
|
|
79
85
|
return year;
|
|
80
86
|
}
|
|
81
87
|
|
|
88
|
+
/**
|
|
89
|
+
* @section protected:methods
|
|
90
|
+
*/
|
|
91
|
+
|
|
92
|
+
// empty
|
|
93
|
+
|
|
82
94
|
/**
|
|
83
95
|
* @section public:methods
|
|
84
96
|
*/
|
|
@@ -30,6 +30,12 @@ export class InvoiceIdBuilder {
|
|
|
30
30
|
|
|
31
31
|
// empty
|
|
32
32
|
|
|
33
|
+
/**
|
|
34
|
+
* @section protected:attributes
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
// empty
|
|
38
|
+
|
|
33
39
|
/**
|
|
34
40
|
* @section private:properties
|
|
35
41
|
*/
|
|
@@ -78,6 +84,12 @@ export class InvoiceIdBuilder {
|
|
|
78
84
|
return year;
|
|
79
85
|
}
|
|
80
86
|
|
|
87
|
+
/**
|
|
88
|
+
* @section protected:methods
|
|
89
|
+
*/
|
|
90
|
+
|
|
91
|
+
// empty
|
|
92
|
+
|
|
81
93
|
/**
|
|
82
94
|
* @section public:methods
|
|
83
95
|
*/
|
|
@@ -29,6 +29,12 @@ export class ControlFlowPolicy {
|
|
|
29
29
|
|
|
30
30
|
// empty
|
|
31
31
|
|
|
32
|
+
/**
|
|
33
|
+
* @section protected:attributes
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
// empty
|
|
37
|
+
|
|
32
38
|
/**
|
|
33
39
|
* @section private:properties
|
|
34
40
|
*/
|
|
@@ -68,6 +74,12 @@ export class ControlFlowPolicy {
|
|
|
68
74
|
|
|
69
75
|
// empty
|
|
70
76
|
|
|
77
|
+
/**
|
|
78
|
+
* @section protected:methods
|
|
79
|
+
*/
|
|
80
|
+
|
|
81
|
+
// empty
|
|
82
|
+
|
|
71
83
|
/**
|
|
72
84
|
* @section public:methods
|
|
73
85
|
*/
|
|
@@ -29,6 +29,12 @@ export class FeatureGate {
|
|
|
29
29
|
|
|
30
30
|
// empty
|
|
31
31
|
|
|
32
|
+
/**
|
|
33
|
+
* @section protected:attributes
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
// empty
|
|
37
|
+
|
|
32
38
|
/**
|
|
33
39
|
* @section private:properties
|
|
34
40
|
*/
|
|
@@ -70,6 +76,12 @@ export class FeatureGate {
|
|
|
70
76
|
|
|
71
77
|
// empty
|
|
72
78
|
|
|
79
|
+
/**
|
|
80
|
+
* @section protected:methods
|
|
81
|
+
*/
|
|
82
|
+
|
|
83
|
+
// empty
|
|
84
|
+
|
|
73
85
|
/**
|
|
74
86
|
* @section public:methods
|
|
75
87
|
*/
|
|
@@ -29,6 +29,12 @@ export class InvoiceLookup {
|
|
|
29
29
|
|
|
30
30
|
// empty
|
|
31
31
|
|
|
32
|
+
/**
|
|
33
|
+
* @section protected:attributes
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
// empty
|
|
37
|
+
|
|
32
38
|
/**
|
|
33
39
|
* @section private:properties
|
|
34
40
|
*/
|
|
@@ -68,6 +74,12 @@ export class InvoiceLookup {
|
|
|
68
74
|
|
|
69
75
|
// empty
|
|
70
76
|
|
|
77
|
+
/**
|
|
78
|
+
* @section protected:methods
|
|
79
|
+
*/
|
|
80
|
+
|
|
81
|
+
// empty
|
|
82
|
+
|
|
71
83
|
/**
|
|
72
84
|
* @section public:methods
|
|
73
85
|
*/
|
|
@@ -29,6 +29,12 @@ export class InvoiceNotFoundError extends Error {
|
|
|
29
29
|
|
|
30
30
|
// empty
|
|
31
31
|
|
|
32
|
+
/**
|
|
33
|
+
* @section protected:attributes
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
// empty
|
|
37
|
+
|
|
32
38
|
/**
|
|
33
39
|
* @section private:properties
|
|
34
40
|
*/
|
|
@@ -72,6 +78,12 @@ export class InvoiceNotFoundError extends Error {
|
|
|
72
78
|
|
|
73
79
|
// empty
|
|
74
80
|
|
|
81
|
+
/**
|
|
82
|
+
* @section protected:methods
|
|
83
|
+
*/
|
|
84
|
+
|
|
85
|
+
// empty
|
|
86
|
+
|
|
75
87
|
/**
|
|
76
88
|
* @section public:methods
|
|
77
89
|
*/
|
|
@@ -30,6 +30,12 @@ export class PaymentNormalizer {
|
|
|
30
30
|
|
|
31
31
|
// empty
|
|
32
32
|
|
|
33
|
+
/**
|
|
34
|
+
* @section protected:attributes
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
// empty
|
|
38
|
+
|
|
33
39
|
/**
|
|
34
40
|
* @section private:properties
|
|
35
41
|
*/
|
|
@@ -69,6 +75,12 @@ export class PaymentNormalizer {
|
|
|
69
75
|
|
|
70
76
|
// empty
|
|
71
77
|
|
|
78
|
+
/**
|
|
79
|
+
* @section protected:methods
|
|
80
|
+
*/
|
|
81
|
+
|
|
82
|
+
// empty
|
|
83
|
+
|
|
72
84
|
/**
|
|
73
85
|
* @section public:methods
|
|
74
86
|
*/
|
|
@@ -30,6 +30,12 @@ export class PaymentNormalizer {
|
|
|
30
30
|
|
|
31
31
|
// empty
|
|
32
32
|
|
|
33
|
+
/**
|
|
34
|
+
* @section protected:attributes
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
// empty
|
|
38
|
+
|
|
33
39
|
/**
|
|
34
40
|
* @section private:properties
|
|
35
41
|
*/
|
|
@@ -82,6 +88,12 @@ export class PaymentNormalizer {
|
|
|
82
88
|
return sanitizedMetadata;
|
|
83
89
|
}
|
|
84
90
|
|
|
91
|
+
/**
|
|
92
|
+
* @section protected:methods
|
|
93
|
+
*/
|
|
94
|
+
|
|
95
|
+
// empty
|
|
96
|
+
|
|
85
97
|
/**
|
|
86
98
|
* @section public:methods
|
|
87
99
|
*/
|
|
@@ -29,6 +29,12 @@ export class InvoiceStatusPresenter {
|
|
|
29
29
|
|
|
30
30
|
// empty
|
|
31
31
|
|
|
32
|
+
/**
|
|
33
|
+
* @section protected:attributes
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
// empty
|
|
37
|
+
|
|
32
38
|
/**
|
|
33
39
|
* @section private:properties
|
|
34
40
|
*/
|
|
@@ -68,6 +74,12 @@ export class InvoiceStatusPresenter {
|
|
|
68
74
|
|
|
69
75
|
// empty
|
|
70
76
|
|
|
77
|
+
/**
|
|
78
|
+
* @section protected:methods
|
|
79
|
+
*/
|
|
80
|
+
|
|
81
|
+
// empty
|
|
82
|
+
|
|
71
83
|
/**
|
|
72
84
|
* @section public:methods
|
|
73
85
|
*/
|
|
@@ -29,6 +29,12 @@ export class InvoiceStatusPresenter {
|
|
|
29
29
|
|
|
30
30
|
// empty
|
|
31
31
|
|
|
32
|
+
/**
|
|
33
|
+
* @section protected:attributes
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
// empty
|
|
37
|
+
|
|
32
38
|
/**
|
|
33
39
|
* @section private:properties
|
|
34
40
|
*/
|
|
@@ -68,6 +74,12 @@ export class InvoiceStatusPresenter {
|
|
|
68
74
|
|
|
69
75
|
// empty
|
|
70
76
|
|
|
77
|
+
/**
|
|
78
|
+
* @section protected:methods
|
|
79
|
+
*/
|
|
80
|
+
|
|
81
|
+
// empty
|
|
82
|
+
|
|
71
83
|
/**
|
|
72
84
|
* @section public:methods
|
|
73
85
|
*/
|
|
@@ -29,6 +29,12 @@ export class InvoiceEscalationPolicy {
|
|
|
29
29
|
|
|
30
30
|
// empty
|
|
31
31
|
|
|
32
|
+
/**
|
|
33
|
+
* @section protected:attributes
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
// empty
|
|
37
|
+
|
|
32
38
|
/**
|
|
33
39
|
* @section private:properties
|
|
34
40
|
*/
|
|
@@ -68,6 +74,12 @@ export class InvoiceEscalationPolicy {
|
|
|
68
74
|
|
|
69
75
|
// empty
|
|
70
76
|
|
|
77
|
+
/**
|
|
78
|
+
* @section protected:methods
|
|
79
|
+
*/
|
|
80
|
+
|
|
81
|
+
// empty
|
|
82
|
+
|
|
71
83
|
/**
|
|
72
84
|
* @section public:methods
|
|
73
85
|
*/
|
|
@@ -30,6 +30,12 @@ export class InvoiceEscalationPolicy {
|
|
|
30
30
|
|
|
31
31
|
// empty
|
|
32
32
|
|
|
33
|
+
/**
|
|
34
|
+
* @section protected:attributes
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
// empty
|
|
38
|
+
|
|
33
39
|
/**
|
|
34
40
|
* @section private:properties
|
|
35
41
|
*/
|
|
@@ -73,6 +79,12 @@ export class InvoiceEscalationPolicy {
|
|
|
73
79
|
return dayCount;
|
|
74
80
|
}
|
|
75
81
|
|
|
82
|
+
/**
|
|
83
|
+
* @section protected:methods
|
|
84
|
+
*/
|
|
85
|
+
|
|
86
|
+
// empty
|
|
87
|
+
|
|
76
88
|
/**
|
|
77
89
|
* @section public:methods
|
|
78
90
|
*/
|
|
@@ -3,9 +3,11 @@
|
|
|
3
3
|
- Code MUST be organized by feature (for example: `src/users`, `src/billing`, `src/invoices`).
|
|
4
4
|
- Each feature MUST keep its own domain model, application services, and infrastructure adapters grouped by feature.
|
|
5
5
|
- Cross-feature imports MUST happen through explicit public entry points.
|
|
6
|
+
- Hardcoded, non-parameterized configuration MUST be centralized in `src/config.ts` (for example, external service URLs).
|
|
6
7
|
|
|
7
8
|
Good example:
|
|
8
9
|
|
|
10
|
+
- `ai/examples/demo/src/config.ts`
|
|
9
11
|
- `ai/examples/demo/src/invoices/invoice-service.ts`
|
|
10
12
|
- `ai/examples/demo/src/invoices/invoice-errors.ts`
|
|
11
13
|
- `ai/examples/demo/src/invoices/invoice-types.ts`
|
|
@@ -14,12 +14,14 @@
|
|
|
14
14
|
- `consts`
|
|
15
15
|
- `types`
|
|
16
16
|
- `private:attributes`
|
|
17
|
+
- `protected:attributes`
|
|
17
18
|
- `private:properties`
|
|
18
19
|
- `public:properties`
|
|
19
20
|
- `constructor`
|
|
20
21
|
- `static:properties`
|
|
21
22
|
- `factory`
|
|
22
23
|
- `private:methods`
|
|
24
|
+
- `protected:methods`
|
|
23
25
|
- `public:methods`
|
|
24
26
|
- `static:methods`
|
|
25
27
|
- All section blocks MUST exist even when empty, using `// empty` after the section marker.
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
- Types MUST be preferred over interfaces for local modeling unless a public contract requires an interface.
|
|
7
7
|
- New implementation and test files MUST be `.ts`.
|
|
8
8
|
- JavaScript source files (`.js`, `.mjs`, `.cjs`) are not allowed in `src/` or `test/`.
|
|
9
|
+
- If a declaration/expression fits in one line within 160 chars, it MUST stay on one line.
|
|
9
10
|
|
|
10
11
|
Good example:
|
|
11
12
|
|
|
@@ -14,6 +14,7 @@ Two templates are supported:
|
|
|
14
14
|
Both templates SHOULD keep this baseline structure:
|
|
15
15
|
|
|
16
16
|
- `src/` for implementation.
|
|
17
|
+
- `src/config.ts` for non-parameterized hardcoded configuration values.
|
|
17
18
|
- `test/` for automated tests.
|
|
18
19
|
- Root tooling config files (`eslint`, `prettier`, `tsconfig`) at project root.
|
|
19
20
|
|
|
@@ -21,4 +22,4 @@ Both templates SHOULD keep this baseline structure:
|
|
|
21
22
|
|
|
22
23
|
- Keep feature modules cohesive.
|
|
23
24
|
- Avoid cyclic dependencies.
|
|
24
|
-
- Keep configuration centralized
|
|
25
|
+
- Keep hardcoded application configuration centralized in `src/config.ts`.
|
package/standards/manifest.json
CHANGED
package/standards/schema.json
CHANGED
|
@@ -4,16 +4,7 @@
|
|
|
4
4
|
"title": "Code Standards Manifest",
|
|
5
5
|
"type": "object",
|
|
6
6
|
"additionalProperties": false,
|
|
7
|
-
"required": [
|
|
8
|
-
"meta",
|
|
9
|
-
"language",
|
|
10
|
-
"formatting",
|
|
11
|
-
"naming",
|
|
12
|
-
"architecture",
|
|
13
|
-
"testing",
|
|
14
|
-
"tooling",
|
|
15
|
-
"ai"
|
|
16
|
-
],
|
|
7
|
+
"required": ["meta", "language", "formatting", "naming", "architecture", "testing", "tooling", "ai"],
|
|
17
8
|
"properties": {
|
|
18
9
|
"meta": {
|
|
19
10
|
"type": "object",
|
|
@@ -42,7 +33,7 @@
|
|
|
42
33
|
"required": ["prettier", "lineLength", "semi", "singleQuote"],
|
|
43
34
|
"properties": {
|
|
44
35
|
"prettier": { "type": "boolean" },
|
|
45
|
-
"lineLength": { "type": "integer", "minimum": 80, "maximum":
|
|
36
|
+
"lineLength": { "type": "integer", "minimum": 80, "maximum": 200 },
|
|
46
37
|
"semi": { "type": "boolean" },
|
|
47
38
|
"singleQuote": { "type": "boolean" }
|
|
48
39
|
}
|
package/standards/style.md
CHANGED
|
@@ -5,7 +5,8 @@ All code MUST follow the canonical rules in `standards/manifest.json`.
|
|
|
5
5
|
## Formatting
|
|
6
6
|
|
|
7
7
|
- Use Prettier for formatting.
|
|
8
|
-
- Use a max line length of
|
|
8
|
+
- Use a max line length of 160.
|
|
9
|
+
- Keep lines as compact as possible: if a declaration/expression fits in one line within 160 chars, keep it on one line.
|
|
9
10
|
- Use semicolons.
|
|
10
11
|
- Use double quotes for strings.
|
|
11
12
|
|
|
@@ -42,14 +43,16 @@ Required block names:
|
|
|
42
43
|
3. `consts`
|
|
43
44
|
4. `types`
|
|
44
45
|
5. `private:attributes`
|
|
45
|
-
6. `
|
|
46
|
-
7. `
|
|
47
|
-
8. `
|
|
48
|
-
9. `
|
|
49
|
-
10. `
|
|
50
|
-
11. `
|
|
51
|
-
12. `
|
|
52
|
-
13. `
|
|
46
|
+
6. `protected:attributes`
|
|
47
|
+
7. `private:properties`
|
|
48
|
+
8. `public:properties`
|
|
49
|
+
9. `constructor`
|
|
50
|
+
10. `static:properties`
|
|
51
|
+
11. `factory`
|
|
52
|
+
12. `private:methods`
|
|
53
|
+
13. `protected:methods`
|
|
54
|
+
14. `public:methods`
|
|
55
|
+
15. `static:methods`
|
|
53
56
|
|
|
54
57
|
All section blocks MUST be present even when empty. Empty sections MUST include:
|
|
55
58
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const GREETING_PREFIX = "Hello";
|
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
import { createServer } from "node:http";
|
|
2
|
+
import { DEFAULT_PORT, EXTERNAL_STATUS_URL, RESPONSE_CONTENT_TYPE } from "./config.js";
|
|
2
3
|
|
|
3
4
|
export function buildServer() {
|
|
4
5
|
return createServer((_, response) => {
|
|
5
6
|
response.statusCode = 200;
|
|
6
|
-
response.setHeader("content-type",
|
|
7
|
-
response.end(JSON.stringify({ ok: true }));
|
|
7
|
+
response.setHeader("content-type", RESPONSE_CONTENT_TYPE);
|
|
8
|
+
response.end(JSON.stringify({ ok: true, statusSource: EXTERNAL_STATUS_URL }));
|
|
8
9
|
});
|
|
9
10
|
}
|
|
10
11
|
|
|
11
12
|
if (process.env.NODE_ENV !== "test") {
|
|
12
|
-
const port = Number(process.env.PORT ??
|
|
13
|
+
const port = Number(process.env.PORT ?? String(DEFAULT_PORT));
|
|
13
14
|
const server = buildServer();
|
|
14
15
|
server.listen(port, () => {
|
|
15
16
|
console.log(`service listening on http://localhost:${port}`);
|
|
@@ -22,7 +22,7 @@ test("service responds with ok payload", async () => {
|
|
|
22
22
|
const json = await response.json();
|
|
23
23
|
|
|
24
24
|
assert.equal(response.status, 200);
|
|
25
|
-
assert.deepEqual(json, { ok: true });
|
|
25
|
+
assert.deepEqual(json, { ok: true, statusSource: "https://status.example.com/health" });
|
|
26
26
|
|
|
27
27
|
await new Promise<void>((resolve, reject) => {
|
|
28
28
|
server.close((error) => {
|