@haneinfo/app-tooling 0.1.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.
- package/LICENSE +14 -0
- package/README.md +13 -0
- package/dist/bin.d.ts +3 -0
- package/dist/bin.d.ts.map +1 -0
- package/dist/bin.js +11 -0
- package/dist/bin.js.map +1 -0
- package/dist/checks.d.ts +11 -0
- package/dist/checks.d.ts.map +1 -0
- package/dist/checks.js +61 -0
- package/dist/checks.js.map +1 -0
- package/dist/cli.d.ts +7 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +117 -0
- package/dist/cli.js.map +1 -0
- package/dist/enum-worker.d.ts +2 -0
- package/dist/enum-worker.d.ts.map +1 -0
- package/dist/enum-worker.js +17 -0
- package/dist/enum-worker.js.map +1 -0
- package/dist/enums.d.ts +29 -0
- package/dist/enums.d.ts.map +1 -0
- package/dist/enums.js +81 -0
- package/dist/enums.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +23 -0
- package/dist/index.js.map +1 -0
- package/dist/integration-contracts.d.ts +8 -0
- package/dist/integration-contracts.d.ts.map +1 -0
- package/dist/integration-contracts.js +160 -0
- package/dist/integration-contracts.js.map +1 -0
- package/dist/scaffold.d.ts +18 -0
- package/dist/scaffold.d.ts.map +1 -0
- package/dist/scaffold.js +208 -0
- package/dist/scaffold.js.map +1 -0
- package/dist/subscriber-wiring.d.ts +4 -0
- package/dist/subscriber-wiring.d.ts.map +1 -0
- package/dist/subscriber-wiring.js +77 -0
- package/dist/subscriber-wiring.js.map +1 -0
- package/package.json +37 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
Copyright (c) 2026 Haneinfo. All Rights Reserved.
|
|
2
|
+
|
|
3
|
+
This software and associated documentation files (the "Software") are the proprietary and
|
|
4
|
+
confidential property of Haneinfo. No part of the Software may be copied, modified, merged,
|
|
5
|
+
published, distributed, sublicensed, or sold without the prior written permission of Haneinfo.
|
|
6
|
+
|
|
7
|
+
The Software is provided for internal use by Haneinfo and its authorized personnel only, and is
|
|
8
|
+
NOT licensed for external distribution or open-source use.
|
|
9
|
+
|
|
10
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
|
|
11
|
+
NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
12
|
+
NONINFRINGEMENT. IN NO EVENT SHALL HANEINFO BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
13
|
+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH
|
|
14
|
+
THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# @haneinfo/app-tooling
|
|
2
|
+
|
|
3
|
+
Run `haneinfo-app check` from an App repository to verify generated plugin artifacts, integration contract runtime files, enum documentation and translations, and domain event subscriber wiring. `haneinfo-app check plugins`, `integrations`, `enums`, or `subscribers` runs one gate. Use `--root <path>` to check another App and `--json` for machine-readable results. Missing enum translations and documentation fail; orphaned translation keys are warnings.
|
|
4
|
+
|
|
5
|
+
This package owns App-level checks. The `scripts/lint-specs.mjs` file remains in each repository under the shared specs governance rule.
|
|
6
|
+
|
|
7
|
+
`haneinfo-app scaffold` initializes an unmodified template clone. It requires `--name`,
|
|
8
|
+
`--display-name`, `--db-port`, `--backend-port`, and `--frontend-port`; `--description`,
|
|
9
|
+
`--ports-registry <dev-infra/app-ports.json>`, `--root`, and `--dry-run` are optional. The dry run
|
|
10
|
+
checks all expected template patterns and prints a file-level plan without writing. The command
|
|
11
|
+
updates package identity, environment examples, Docker port, frontend scripts, CI and E2E defaults,
|
|
12
|
+
and the committed Keycloak E2E realm. It creates an ignored `.env` with a unique local database
|
|
13
|
+
password while keeping placeholders in `.env.example`. A marker prevents a second initialization.
|
package/dist/bin.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bin.d.ts","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":""}
|
package/dist/bin.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
const cli_1 = require("./cli");
|
|
5
|
+
void (0, cli_1.runAppToolingCli)(process.argv.slice(2), process.cwd(), {
|
|
6
|
+
stdout: (line) => process.stdout.write(`${line}\n`),
|
|
7
|
+
stderr: (line) => process.stderr.write(`${line}\n`),
|
|
8
|
+
}).then((exitCode) => {
|
|
9
|
+
process.exitCode = exitCode;
|
|
10
|
+
});
|
|
11
|
+
//# sourceMappingURL=bin.js.map
|
package/dist/bin.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bin.js","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":";;;AACA,+BAAyC;AAEzC,KAAK,IAAA,sBAAgB,EAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,GAAG,EAAE,EAAE;IAC1D,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,IAAI,IAAI,CAAC;IACnD,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,IAAI,IAAI,CAAC;CACpD,CAAC,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,EAAE;IACnB,OAAO,CAAC,QAAQ,GAAG,QAAQ,CAAC;AAC9B,CAAC,CAAC,CAAC"}
|
package/dist/checks.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type CheckIssue } from './integration-contracts';
|
|
2
|
+
export type CheckTarget = 'all' | 'plugins' | 'integrations' | 'enums' | 'subscribers';
|
|
3
|
+
export interface CheckReport {
|
|
4
|
+
target: CheckTarget;
|
|
5
|
+
issues: CheckIssue[];
|
|
6
|
+
warnings: CheckIssue[];
|
|
7
|
+
passed: boolean;
|
|
8
|
+
}
|
|
9
|
+
/** Run stable App checks without invoking an App-local copy of each implementation. */
|
|
10
|
+
export declare function checkApp(appRoot: string, target?: CheckTarget): Promise<CheckReport>;
|
|
11
|
+
//# sourceMappingURL=checks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"checks.d.ts","sourceRoot":"","sources":["../src/checks.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,UAAU,EAAsC,MAAM,yBAAyB,CAAC;AAG9F,MAAM,MAAM,WAAW,GAAG,KAAK,GAAG,SAAS,GAAG,cAAc,GAAG,OAAO,GAAG,aAAa,CAAC;AAEvF,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,WAAW,CAAC;IACpB,MAAM,EAAE,UAAU,EAAE,CAAC;IACrB,QAAQ,EAAE,UAAU,EAAE,CAAC;IACvB,MAAM,EAAE,OAAO,CAAC;CACjB;AA4BD,uFAAuF;AACvF,wBAAsB,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,GAAE,WAAmB,GAAG,OAAO,CAAC,WAAW,CAAC,CAoBjG"}
|
package/dist/checks.js
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.checkApp = checkApp;
|
|
7
|
+
const node_fs_1 = require("node:fs");
|
|
8
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
+
const plugin_cli_1 = require("@haneinfo/plugin-cli");
|
|
10
|
+
const enums_1 = require("./enums");
|
|
11
|
+
const integration_contracts_1 = require("./integration-contracts");
|
|
12
|
+
const subscriber_wiring_1 = require("./subscriber-wiring");
|
|
13
|
+
function pluginCliVersion() {
|
|
14
|
+
try {
|
|
15
|
+
const file = require.resolve('@haneinfo/plugin-cli/package.json');
|
|
16
|
+
return JSON.parse((0, node_fs_1.readFileSync)(file, 'utf8')).version ?? 'unknown';
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return 'unknown';
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
async function checkPlugins(appRoot) {
|
|
23
|
+
const result = await plugin_cli_1.buildCommand.handler({
|
|
24
|
+
appRoot,
|
|
25
|
+
args: { command: 'build', positionals: [], flags: new Map([['check', true]]) },
|
|
26
|
+
io: { cwd: () => appRoot, stdout: () => { }, stderr: () => { } },
|
|
27
|
+
version: pluginCliVersion(),
|
|
28
|
+
});
|
|
29
|
+
if (result.exitCode === plugin_cli_1.ExitCode.OK)
|
|
30
|
+
return [];
|
|
31
|
+
return result.diagnostics
|
|
32
|
+
.filter((entry) => entry.severity === 'error')
|
|
33
|
+
.map((entry) => ({
|
|
34
|
+
code: entry.code,
|
|
35
|
+
path: entry.path ?? 'haneinfo.plugins.json',
|
|
36
|
+
message: entry.message,
|
|
37
|
+
}));
|
|
38
|
+
}
|
|
39
|
+
/** Run stable App checks without invoking an App-local copy of each implementation. */
|
|
40
|
+
async function checkApp(appRoot, target = 'all') {
|
|
41
|
+
const root = node_path_1.default.resolve(appRoot);
|
|
42
|
+
const repositoryRoot = node_path_1.default.basename(root) === 'backend' && (0, node_fs_1.existsSync)(node_path_1.default.join(root, 'haneinfo.plugins.json'))
|
|
43
|
+
? node_path_1.default.dirname(root)
|
|
44
|
+
: root;
|
|
45
|
+
const backendRoot = node_path_1.default.join(repositoryRoot, 'backend');
|
|
46
|
+
const issues = [];
|
|
47
|
+
const warnings = [];
|
|
48
|
+
if (target === 'all' || target === 'plugins')
|
|
49
|
+
issues.push(...(await checkPlugins(backendRoot)));
|
|
50
|
+
if (target === 'all' || target === 'integrations')
|
|
51
|
+
issues.push(...(0, integration_contracts_1.checkGeneratedIntegrationContracts)(repositoryRoot));
|
|
52
|
+
if (target === 'all' || target === 'enums') {
|
|
53
|
+
const enums = (0, enums_1.checkEnums)(repositoryRoot);
|
|
54
|
+
issues.push(...enums.issues);
|
|
55
|
+
warnings.push(...enums.warnings);
|
|
56
|
+
}
|
|
57
|
+
if (target === 'all' || target === 'subscribers')
|
|
58
|
+
issues.push(...(0, subscriber_wiring_1.checkDomainEventSubscribers)(repositoryRoot));
|
|
59
|
+
return { target, issues, warnings, passed: issues.length === 0 };
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=checks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"checks.js","sourceRoot":"","sources":["../src/checks.ts"],"names":[],"mappings":"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"}
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export type AppToolingOutput = {
|
|
2
|
+
stdout: (line: string) => void;
|
|
3
|
+
stderr: (line: string) => void;
|
|
4
|
+
};
|
|
5
|
+
/** Exit codes: 0 passed, 1 verification failed, 2 invalid invocation, 3 unexpected error. */
|
|
6
|
+
export declare function runAppToolingCli(argv: string[], cwd: string, output: AppToolingOutput): Promise<number>;
|
|
7
|
+
//# sourceMappingURL=cli.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,gBAAgB,GAAG;IAAE,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAAC,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;CAAE,CAAC;AAKlG,6FAA6F;AAC7F,wBAAsB,gBAAgB,CACpC,IAAI,EAAE,MAAM,EAAE,EACd,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,gBAAgB,GACvB,OAAO,CAAC,MAAM,CAAC,CAgDjB"}
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runAppToolingCli = runAppToolingCli;
|
|
4
|
+
const node_util_1 = require("node:util");
|
|
5
|
+
const checks_1 = require("./checks");
|
|
6
|
+
const scaffold_1 = require("./scaffold");
|
|
7
|
+
const USAGE = 'Usage: haneinfo-app check [all|plugins|integrations|enums|subscribers] [--root <app-root>] [--json]\n haneinfo-app scaffold --name <kebab-case> --display-name <text> --db-port <port> --backend-port <port> --frontend-port <port> [--description <text>] [--ports-registry <file>] [--root <template-root>] [--dry-run]';
|
|
8
|
+
/** Exit codes: 0 passed, 1 verification failed, 2 invalid invocation, 3 unexpected error. */
|
|
9
|
+
async function runAppToolingCli(argv, cwd, output) {
|
|
10
|
+
if (argv[0] === 'scaffold')
|
|
11
|
+
return runScaffoldCli(argv.slice(1), cwd, output);
|
|
12
|
+
let parsed;
|
|
13
|
+
try {
|
|
14
|
+
parsed = (0, node_util_1.parseArgs)({
|
|
15
|
+
args: argv,
|
|
16
|
+
options: { root: { type: 'string' }, json: { type: 'boolean' }, help: { type: 'boolean' } },
|
|
17
|
+
allowPositionals: true,
|
|
18
|
+
strict: true,
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
output.stderr(`${error instanceof Error ? error.message : String(error)}\n${USAGE}`);
|
|
23
|
+
return 2;
|
|
24
|
+
}
|
|
25
|
+
if (parsed.values.help === true) {
|
|
26
|
+
output.stdout(USAGE);
|
|
27
|
+
return 0;
|
|
28
|
+
}
|
|
29
|
+
const [command, requested = 'all', ...extra] = parsed.positionals;
|
|
30
|
+
if (command !== 'check' ||
|
|
31
|
+
extra.length > 0 ||
|
|
32
|
+
!['all', 'plugins', 'integrations', 'enums', 'subscribers'].includes(requested)) {
|
|
33
|
+
output.stderr(USAGE);
|
|
34
|
+
return 2;
|
|
35
|
+
}
|
|
36
|
+
try {
|
|
37
|
+
const root = typeof parsed.values.root === 'string' ? parsed.values.root : cwd;
|
|
38
|
+
const report = await (0, checks_1.checkApp)(root, requested);
|
|
39
|
+
if (parsed.values.json) {
|
|
40
|
+
output.stdout(JSON.stringify(report, null, 2));
|
|
41
|
+
}
|
|
42
|
+
else if (report.passed) {
|
|
43
|
+
output.stdout(`haneinfo-app: ${report.target} checks passed`);
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
for (const issue of report.issues)
|
|
47
|
+
output.stderr(`${issue.code} ${issue.path}: ${issue.message}`);
|
|
48
|
+
}
|
|
49
|
+
if (!parsed.values.json) {
|
|
50
|
+
for (const warning of report.warnings)
|
|
51
|
+
output.stderr(`warning ${warning.code} ${warning.path}: ${warning.message}`);
|
|
52
|
+
}
|
|
53
|
+
return report.passed ? 0 : 1;
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
output.stderr(`haneinfo-app: ${error instanceof Error ? error.message : String(error)}`);
|
|
57
|
+
return 3;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function runScaffoldCli(argv, cwd, output) {
|
|
61
|
+
let parsed;
|
|
62
|
+
try {
|
|
63
|
+
parsed = (0, node_util_1.parseArgs)({
|
|
64
|
+
args: argv,
|
|
65
|
+
options: {
|
|
66
|
+
name: { type: 'string' },
|
|
67
|
+
'display-name': { type: 'string' },
|
|
68
|
+
description: { type: 'string' },
|
|
69
|
+
'db-port': { type: 'string' },
|
|
70
|
+
'backend-port': { type: 'string' },
|
|
71
|
+
'frontend-port': { type: 'string' },
|
|
72
|
+
'ports-registry': { type: 'string' },
|
|
73
|
+
root: { type: 'string' },
|
|
74
|
+
'dry-run': { type: 'boolean' },
|
|
75
|
+
help: { type: 'boolean' },
|
|
76
|
+
},
|
|
77
|
+
allowPositionals: false,
|
|
78
|
+
strict: true,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
output.stderr(`${error instanceof Error ? error.message : String(error)}\n${USAGE}`);
|
|
83
|
+
return 2;
|
|
84
|
+
}
|
|
85
|
+
if (parsed.values.help) {
|
|
86
|
+
output.stdout(USAGE);
|
|
87
|
+
return 0;
|
|
88
|
+
}
|
|
89
|
+
const values = parsed.values;
|
|
90
|
+
const required = ['name', 'display-name', 'db-port', 'backend-port', 'frontend-port'];
|
|
91
|
+
if (required.some((key) => typeof values[key] !== 'string' || !values[key])) {
|
|
92
|
+
output.stderr(USAGE);
|
|
93
|
+
return 2;
|
|
94
|
+
}
|
|
95
|
+
try {
|
|
96
|
+
const changes = (0, scaffold_1.scaffoldApp)({
|
|
97
|
+
root: values.root ?? cwd,
|
|
98
|
+
name: values.name,
|
|
99
|
+
displayName: values['display-name'],
|
|
100
|
+
description: values.description,
|
|
101
|
+
dbPort: Number(values['db-port']),
|
|
102
|
+
backendPort: Number(values['backend-port']),
|
|
103
|
+
frontendPort: Number(values['frontend-port']),
|
|
104
|
+
portsRegistry: values['ports-registry'],
|
|
105
|
+
dryRun: values['dry-run'] === true,
|
|
106
|
+
});
|
|
107
|
+
output.stdout(values['dry-run'] ? 'scaffold dry run passed; no files written' : 'scaffold initialized');
|
|
108
|
+
for (const file of changes)
|
|
109
|
+
output.stdout(`${file.file}: ${file.changes.join(', ')}`);
|
|
110
|
+
return 0;
|
|
111
|
+
}
|
|
112
|
+
catch (error) {
|
|
113
|
+
output.stderr(`haneinfo-app scaffold: ${error instanceof Error ? error.message : String(error)}`);
|
|
114
|
+
return 1;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"enum-worker.d.ts","sourceRoot":"","sources":["../src/enum-worker.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const node_fs_1 = require("node:fs");
|
|
7
|
+
const node_module_1 = require("node:module");
|
|
8
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
+
// @haneinfo/common selects the consuming App's Prisma client from process.cwd() at import time.
|
|
10
|
+
// This worker keeps that resolution isolated when one process checks several Apps.
|
|
11
|
+
const appRequire = (0, node_module_1.createRequire)(node_path_1.default.join(process.cwd(), 'package.json'));
|
|
12
|
+
const api = appRequire('@haneinfo/metadata-schema');
|
|
13
|
+
const dictionaries = JSON.parse((0, node_fs_1.readFileSync)(0, 'utf8'));
|
|
14
|
+
const meta = new api.MetaService().buildMeta();
|
|
15
|
+
const gaps = api.collectEnumTranslationGaps(meta, dictionaries);
|
|
16
|
+
process.stdout.write(JSON.stringify({ meta, gaps }));
|
|
17
|
+
//# sourceMappingURL=enum-worker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"enum-worker.js","sourceRoot":"","sources":["../src/enum-worker.ts"],"names":[],"mappings":";;;;;AAAA,qCAAuC;AACvC,6CAA4C;AAC5C,0DAA6B;AAE7B,gGAAgG;AAChG,mFAAmF;AACnF,MAAM,UAAU,GAAG,IAAA,2BAAa,EAAC,mBAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,cAAc,CAAC,CAAC,CAAC;AAC3E,MAAM,GAAG,GAAG,UAAU,CAAC,2BAA2B,CAGjD,CAAC;AACF,MAAM,YAAY,GAAG,IAAI,CAAC,KAAK,CAAC,IAAA,sBAAY,EAAC,CAAC,EAAE,MAAM,CAAC,CAA4C,CAAC;AACpG,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,WAAW,EAAE,CAAC,SAAS,EAAE,CAAC;AAC/C,MAAM,IAAI,GAAG,GAAG,CAAC,0BAA0B,CAAC,IAAI,EAAE,YAAY,CAAC,CAAC;AAChE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC"}
|
package/dist/enums.d.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { CheckIssue } from './integration-contracts';
|
|
2
|
+
interface EnumMeta {
|
|
3
|
+
enums: Array<{
|
|
4
|
+
name: string;
|
|
5
|
+
documentation?: string;
|
|
6
|
+
values: Array<{
|
|
7
|
+
name: string;
|
|
8
|
+
}>;
|
|
9
|
+
}>;
|
|
10
|
+
}
|
|
11
|
+
interface EnumGap {
|
|
12
|
+
locale: string;
|
|
13
|
+
key: string;
|
|
14
|
+
kind: 'missing' | 'orphaned';
|
|
15
|
+
}
|
|
16
|
+
interface MetadataApi {
|
|
17
|
+
MetaService: new () => {
|
|
18
|
+
buildMeta(): EnumMeta;
|
|
19
|
+
};
|
|
20
|
+
collectEnumTranslationGaps(meta: EnumMeta, dictionaries: Record<string, Record<string, unknown>>): EnumGap[];
|
|
21
|
+
}
|
|
22
|
+
export interface EnumCheckResult {
|
|
23
|
+
issues: CheckIssue[];
|
|
24
|
+
warnings: CheckIssue[];
|
|
25
|
+
}
|
|
26
|
+
/** Use the App's generated Prisma client when checking schema docs and enum translations. */
|
|
27
|
+
export declare function checkEnums(appRoot: string, metadataApi?: MetadataApi): EnumCheckResult;
|
|
28
|
+
export {};
|
|
29
|
+
//# sourceMappingURL=enums.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"enums.d.ts","sourceRoot":"","sources":["../src/enums.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAE1D,UAAU,QAAQ;IAChB,KAAK,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,aAAa,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,KAAK,CAAC;YAAE,IAAI,EAAE,MAAM,CAAA;SAAE,CAAC,CAAA;KAAE,CAAC,CAAC;CACzF;AAED,UAAU,OAAO;IACf,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,SAAS,GAAG,UAAU,CAAC;CAC9B;AAED,UAAU,WAAW;IACnB,WAAW,EAAE,UAAU;QAAE,SAAS,IAAI,QAAQ,CAAA;KAAE,CAAC;IACjD,0BAA0B,CACxB,IAAI,EAAE,QAAQ,EACd,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GACpD,OAAO,EAAE,CAAC;CACd;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,UAAU,EAAE,CAAC;IACrB,QAAQ,EAAE,UAAU,EAAE,CAAC;CACxB;AAkBD,6FAA6F;AAC7F,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,WAAW,GAAG,eAAe,CAyDtF"}
|
package/dist/enums.js
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.checkEnums = checkEnums;
|
|
7
|
+
const node_child_process_1 = require("node:child_process");
|
|
8
|
+
const node_fs_1 = require("node:fs");
|
|
9
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
10
|
+
function metadataFromApp(backendRoot, dictionaries) {
|
|
11
|
+
const result = (0, node_child_process_1.spawnSync)(process.execPath, [node_path_1.default.join(__dirname, 'enum-worker.js')], {
|
|
12
|
+
cwd: backendRoot,
|
|
13
|
+
input: JSON.stringify(dictionaries),
|
|
14
|
+
encoding: 'utf8',
|
|
15
|
+
timeout: 30_000,
|
|
16
|
+
maxBuffer: 16 * 1024 * 1024,
|
|
17
|
+
});
|
|
18
|
+
if (result.error || result.status !== 0)
|
|
19
|
+
throw new Error(result.error?.message ?? result.stderr.trim() ?? 'enum worker failed');
|
|
20
|
+
return JSON.parse(result.stdout);
|
|
21
|
+
}
|
|
22
|
+
/** Use the App's generated Prisma client when checking schema docs and enum translations. */
|
|
23
|
+
function checkEnums(appRoot, metadataApi) {
|
|
24
|
+
const backendRoot = node_path_1.default.join(appRoot, 'backend');
|
|
25
|
+
const issues = [];
|
|
26
|
+
const warnings = [];
|
|
27
|
+
const dictionaries = {};
|
|
28
|
+
for (const locale of ['en', 'zh-TW']) {
|
|
29
|
+
const file = node_path_1.default.join(appRoot, 'frontend', 'messages', `${locale}.json`);
|
|
30
|
+
if (!(0, node_fs_1.existsSync)(file)) {
|
|
31
|
+
issues.push({
|
|
32
|
+
code: 'enum-dictionary-missing',
|
|
33
|
+
path: file,
|
|
34
|
+
message: 'locale file is missing',
|
|
35
|
+
});
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
try {
|
|
39
|
+
const parsed = JSON.parse((0, node_fs_1.readFileSync)(file, 'utf8'));
|
|
40
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed))
|
|
41
|
+
throw new Error('locale file must contain a JSON object');
|
|
42
|
+
const enums = parsed.enums;
|
|
43
|
+
if (enums !== undefined && (!enums || typeof enums !== 'object' || Array.isArray(enums)))
|
|
44
|
+
throw new Error('enums must be a JSON object');
|
|
45
|
+
dictionaries[locale] = (enums ?? {});
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
issues.push({
|
|
49
|
+
code: 'enum-dictionary-invalid',
|
|
50
|
+
path: file,
|
|
51
|
+
message: error instanceof Error ? error.message : String(error),
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
if (Object.keys(dictionaries).length === 2) {
|
|
56
|
+
const { meta, gaps } = metadataApi
|
|
57
|
+
? { meta: new metadataApi.MetaService().buildMeta(), gaps: [] }
|
|
58
|
+
: metadataFromApp(backendRoot, dictionaries);
|
|
59
|
+
if (metadataApi)
|
|
60
|
+
gaps.push(...metadataApi.collectEnumTranslationGaps(meta, dictionaries));
|
|
61
|
+
for (const item of meta.enums) {
|
|
62
|
+
if (!item.documentation?.trim()) {
|
|
63
|
+
issues.push({
|
|
64
|
+
code: 'enum-documentation-missing',
|
|
65
|
+
path: node_path_1.default.join(backendRoot, 'prisma', 'schema'),
|
|
66
|
+
message: `enum ${item.name} is missing a /// doc comment`,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
for (const gap of gaps) {
|
|
71
|
+
const issue = {
|
|
72
|
+
code: gap.kind === 'missing' ? 'enum-translation-missing' : 'enum-translation-orphaned',
|
|
73
|
+
path: node_path_1.default.join(appRoot, 'frontend', 'messages', `${gap.locale}.json`),
|
|
74
|
+
message: `${gap.locale}: ${gap.key}`,
|
|
75
|
+
};
|
|
76
|
+
(gap.kind === 'missing' ? issues : warnings).push(issue);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return { issues, warnings };
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=enums.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"enums.js","sourceRoot":"","sources":["../src/enums.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAC;AACzB,cAAc,OAAO,CAAC;AACtB,cAAc,SAAS,CAAC;AACxB,cAAc,yBAAyB,CAAC;AACxC,cAAc,YAAY,CAAC;AAC3B,cAAc,qBAAqB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./checks"), exports);
|
|
18
|
+
__exportStar(require("./cli"), exports);
|
|
19
|
+
__exportStar(require("./enums"), exports);
|
|
20
|
+
__exportStar(require("./integration-contracts"), exports);
|
|
21
|
+
__exportStar(require("./scaffold"), exports);
|
|
22
|
+
__exportStar(require("./subscriber-wiring"), exports);
|
|
23
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,2CAAyB;AACzB,wCAAsB;AACtB,0CAAwB;AACxB,0DAAwC;AACxC,6CAA2B;AAC3B,sDAAoC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export interface CheckIssue {
|
|
2
|
+
code: string;
|
|
3
|
+
path: string;
|
|
4
|
+
message: string;
|
|
5
|
+
}
|
|
6
|
+
/** Check committed runtime files against the canonical integration contract references. */
|
|
7
|
+
export declare function checkGeneratedIntegrationContracts(appRoot: string): CheckIssue[];
|
|
8
|
+
//# sourceMappingURL=integration-contracts.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"integration-contracts.d.ts","sourceRoot":"","sources":["../src/integration-contracts.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AA4DD,2FAA2F;AAC3F,wBAAgB,kCAAkC,CAAC,OAAO,EAAE,MAAM,GAAG,UAAU,EAAE,CA+GhF"}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.checkGeneratedIntegrationContracts = checkGeneratedIntegrationContracts;
|
|
7
|
+
const node_fs_1 = require("node:fs");
|
|
8
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
+
function walk(directory) {
|
|
10
|
+
if (!(0, node_fs_1.existsSync)(directory))
|
|
11
|
+
return [];
|
|
12
|
+
return (0, node_fs_1.readdirSync)(directory, { withFileTypes: true }).flatMap((entry) => {
|
|
13
|
+
const file = node_path_1.default.join(directory, entry.name);
|
|
14
|
+
return entry.isDirectory() ? walk(file) : [file];
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
function typescriptType(schema) {
|
|
18
|
+
if (!schema || typeof schema !== 'object')
|
|
19
|
+
return 'unknown';
|
|
20
|
+
const node = schema;
|
|
21
|
+
if (Array.isArray(node.enum))
|
|
22
|
+
return node.enum.map((value) => JSON.stringify(value)).join(' | ');
|
|
23
|
+
if (node.const !== undefined)
|
|
24
|
+
return JSON.stringify(node.const);
|
|
25
|
+
if (Array.isArray(node.type))
|
|
26
|
+
return node.type.map((type) => typescriptType({ ...node, type })).join(' | ');
|
|
27
|
+
if (node.type === 'object') {
|
|
28
|
+
const required = Array.isArray(node.required) ? node.required : [];
|
|
29
|
+
const properties = Object.entries((node.properties ?? {})).map(([key, child]) => `${JSON.stringify(key)}${required.includes(key) ? '' : '?'}: ${typescriptType(child)};`);
|
|
30
|
+
return `{ ${properties.join(' ')}${node.additionalProperties === false ? '' : ' [key: string]: unknown;'} }`;
|
|
31
|
+
}
|
|
32
|
+
if (node.type === 'array')
|
|
33
|
+
return `Array<${typescriptType(node.items ?? {})}>`;
|
|
34
|
+
if (node.type === 'integer' || node.type === 'number')
|
|
35
|
+
return 'number';
|
|
36
|
+
if (node.type === 'boolean' || node.type === 'string' || node.type === 'null')
|
|
37
|
+
return node.type;
|
|
38
|
+
if (node.anyOf || node.oneOf)
|
|
39
|
+
return (node.anyOf ?? node.oneOf).map(typescriptType).join(' | ');
|
|
40
|
+
if (node.allOf)
|
|
41
|
+
return node.allOf.map(typescriptType).join(' & ');
|
|
42
|
+
return 'unknown';
|
|
43
|
+
}
|
|
44
|
+
function expectedFiles(ref, schema) {
|
|
45
|
+
return {
|
|
46
|
+
'manifest.ts': `export const integrationContractManifest = ${JSON.stringify({
|
|
47
|
+
contractId: ref.contract_id,
|
|
48
|
+
version: ref.version,
|
|
49
|
+
kind: ref.kind,
|
|
50
|
+
digest: ref.digest,
|
|
51
|
+
capabilityRef: ref.capability_ref,
|
|
52
|
+
}, null, 2)} as const;\n`,
|
|
53
|
+
'types.ts': `export type IntegrationContractPayload = ${typescriptType(schema)};\n`,
|
|
54
|
+
'validators.ts': `import { validateJsonSchema, type SchemaValidationIssue } from '@haneinfo/integration-contracts';\n\nconst integrationContractSchema = ${JSON.stringify(schema, null, 2)} as const;\n\nexport function validateIntegrationContractPayload(value: unknown): SchemaValidationIssue[] {\n return validateJsonSchema(value, integrationContractSchema as never, { mode: 'strict' });\n}\n`,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
/** Check committed runtime files against the canonical integration contract references. */
|
|
58
|
+
function checkGeneratedIntegrationContracts(appRoot) {
|
|
59
|
+
const refsRoot = node_path_1.default.resolve(appRoot, 'specs', 'contracts');
|
|
60
|
+
const runtimeRoot = node_path_1.default.resolve(appRoot, 'backend', 'src', 'generated', 'integration-contracts');
|
|
61
|
+
const issues = [];
|
|
62
|
+
const expectedDirectories = new Set();
|
|
63
|
+
const refs = walk(refsRoot).filter((file) => file.endsWith(node_path_1.default.join('_generated', 'contract-ref.json')));
|
|
64
|
+
for (const refFile of refs) {
|
|
65
|
+
let ref;
|
|
66
|
+
try {
|
|
67
|
+
ref = JSON.parse((0, node_fs_1.readFileSync)(refFile, 'utf8'));
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
issues.push({
|
|
71
|
+
code: 'contract-ref-invalid',
|
|
72
|
+
path: refFile,
|
|
73
|
+
message: 'contract-ref.json is not valid JSON',
|
|
74
|
+
});
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
if (typeof ref.contract_id !== 'string' ||
|
|
78
|
+
!ref.contract_id ||
|
|
79
|
+
!/^[A-Za-z0-9_.-]+$/.test(ref.contract_id) ||
|
|
80
|
+
ref.contract_id.includes('..')) {
|
|
81
|
+
issues.push({
|
|
82
|
+
code: 'contract-id-invalid',
|
|
83
|
+
path: refFile,
|
|
84
|
+
message: 'contract_id must be a safe path segment',
|
|
85
|
+
});
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
if (![ref.version, ref.kind, ref.digest, ref.capability_ref].every((value) => typeof value === 'string' && value.length > 0)) {
|
|
89
|
+
issues.push({
|
|
90
|
+
code: 'contract-ref-invalid',
|
|
91
|
+
path: refFile,
|
|
92
|
+
message: 'version, kind, digest, and capability_ref must be non-empty strings',
|
|
93
|
+
});
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
const safeId = ref.contract_id.replace(/[^a-zA-Z0-9_-]/gu, '_');
|
|
97
|
+
if (expectedDirectories.has(safeId)) {
|
|
98
|
+
issues.push({
|
|
99
|
+
code: 'contract-id-duplicate',
|
|
100
|
+
path: refFile,
|
|
101
|
+
message: `duplicate runtime directory ${safeId}`,
|
|
102
|
+
});
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
expectedDirectories.add(safeId);
|
|
106
|
+
const schemaFile = walk(node_path_1.default.join(refsRoot, ref.contract_id, '_generated'))
|
|
107
|
+
.filter((file) => file.endsWith('.schema.json'))
|
|
108
|
+
.sort()[0];
|
|
109
|
+
if (!schemaFile) {
|
|
110
|
+
issues.push({
|
|
111
|
+
code: 'contract-schema-missing',
|
|
112
|
+
path: refFile,
|
|
113
|
+
message: `missing canonical schema for ${ref.contract_id}@${ref.version}`,
|
|
114
|
+
});
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
let schema;
|
|
118
|
+
try {
|
|
119
|
+
schema = JSON.parse((0, node_fs_1.readFileSync)(schemaFile, 'utf8'));
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
issues.push({
|
|
123
|
+
code: 'contract-schema-invalid',
|
|
124
|
+
path: schemaFile,
|
|
125
|
+
message: 'canonical schema is not valid JSON',
|
|
126
|
+
});
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
for (const [name, expected] of Object.entries(expectedFiles(ref, schema))) {
|
|
130
|
+
const target = node_path_1.default.join(runtimeRoot, safeId, name);
|
|
131
|
+
if (!(0, node_fs_1.existsSync)(target)) {
|
|
132
|
+
issues.push({
|
|
133
|
+
code: 'contract-runtime-missing',
|
|
134
|
+
path: target,
|
|
135
|
+
message: `${ref.contract_id}@${ref.version}: ${name} is missing`,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
else if ((0, node_fs_1.readFileSync)(target, 'utf8') !== expected) {
|
|
139
|
+
issues.push({
|
|
140
|
+
code: 'contract-runtime-stale',
|
|
141
|
+
path: target,
|
|
142
|
+
message: `${ref.contract_id}@${ref.version}: ${name} is stale`,
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
if ((0, node_fs_1.existsSync)(runtimeRoot)) {
|
|
148
|
+
for (const entry of (0, node_fs_1.readdirSync)(runtimeRoot, { withFileTypes: true })) {
|
|
149
|
+
if (entry.isDirectory() && !expectedDirectories.has(entry.name)) {
|
|
150
|
+
issues.push({
|
|
151
|
+
code: 'contract-runtime-orphaned',
|
|
152
|
+
path: node_path_1.default.join(runtimeRoot, entry.name),
|
|
153
|
+
message: `orphaned runtime directory ${entry.name}`,
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return issues;
|
|
159
|
+
}
|
|
160
|
+
//# sourceMappingURL=integration-contracts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"integration-contracts.js","sourceRoot":"","sources":["../src/integration-contracts.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface ScaffoldInput {
|
|
2
|
+
root: string;
|
|
3
|
+
name: string;
|
|
4
|
+
displayName: string;
|
|
5
|
+
description?: string;
|
|
6
|
+
dbPort: number;
|
|
7
|
+
backendPort: number;
|
|
8
|
+
frontendPort: number;
|
|
9
|
+
portsRegistry?: string;
|
|
10
|
+
dryRun?: boolean;
|
|
11
|
+
}
|
|
12
|
+
export interface ScaffoldChange {
|
|
13
|
+
file: string;
|
|
14
|
+
changes: string[];
|
|
15
|
+
}
|
|
16
|
+
/** Preflight every template rule before writing any file. */
|
|
17
|
+
export declare function scaffoldApp(input: ScaffoldInput): ScaffoldChange[];
|
|
18
|
+
//# sourceMappingURL=scaffold.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scaffold.d.ts","sourceRoot":"","sources":["../src/scaffold.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AASD,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAwRD,6DAA6D;AAC7D,wBAAgB,WAAW,CAAC,KAAK,EAAE,aAAa,GAAG,cAAc,EAAE,CAwClE"}
|
package/dist/scaffold.js
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.scaffoldApp = scaffoldApp;
|
|
7
|
+
const node_crypto_1 = require("node:crypto");
|
|
8
|
+
const node_fs_1 = require("node:fs");
|
|
9
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
10
|
+
function validPort(value) {
|
|
11
|
+
return Number.isInteger(value) && value >= 1 && value <= 65535;
|
|
12
|
+
}
|
|
13
|
+
function checkPorts(input) {
|
|
14
|
+
const selected = [input.dbPort, input.backendPort, input.frontendPort];
|
|
15
|
+
if (!selected.every(validPort))
|
|
16
|
+
throw new Error('DB, backend, and frontend ports are required TCP ports (1–65535)');
|
|
17
|
+
if (new Set(selected).size !== 3)
|
|
18
|
+
throw new Error('DB, backend, and frontend ports must be distinct');
|
|
19
|
+
if (!input.portsRegistry)
|
|
20
|
+
return;
|
|
21
|
+
const file = node_path_1.default.resolve(input.portsRegistry);
|
|
22
|
+
const registry = JSON.parse((0, node_fs_1.readFileSync)(file, 'utf8'));
|
|
23
|
+
if (registry.version !== 1 || !Array.isArray(registry.apps))
|
|
24
|
+
throw new Error(`${file}: expected a version 1 App port registry`);
|
|
25
|
+
const seenNames = new Set();
|
|
26
|
+
const seenPorts = new Map();
|
|
27
|
+
for (const app of registry.apps) {
|
|
28
|
+
if (!app || typeof app.name !== 'string' || !app.name || seenNames.has(app.name))
|
|
29
|
+
throw new Error(`${file}: duplicate or invalid App name in port registry`);
|
|
30
|
+
seenNames.add(app.name);
|
|
31
|
+
if (app.name === input.name)
|
|
32
|
+
throw new Error(`${file}: App ${input.name} is already registered`);
|
|
33
|
+
const ports = [app.dbPort, app.backendPort, app.frontendPort];
|
|
34
|
+
if (!ports.every(validPort) || new Set(ports).size !== 3)
|
|
35
|
+
throw new Error(`${file}: ${app.name} has invalid or duplicate ports`);
|
|
36
|
+
for (const port of ports) {
|
|
37
|
+
if (seenPorts.has(port))
|
|
38
|
+
throw new Error(`${file}: port ${port} is registered to multiple Apps`);
|
|
39
|
+
seenPorts.set(port, app.name);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
for (const port of selected) {
|
|
43
|
+
const owner = seenPorts.get(port);
|
|
44
|
+
if (owner)
|
|
45
|
+
throw new Error(`port ${port} is already registered to ${owner} in ${file}`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
function validate(input) {
|
|
49
|
+
if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/u.test(input.name))
|
|
50
|
+
throw new Error('name must use lowercase kebab-case');
|
|
51
|
+
if (!input.displayName || input.displayName.length > 80 || /[\p{Cc}`$]/u.test(input.displayName))
|
|
52
|
+
throw new Error('display-name must be 1–80 printable characters without backticks or $');
|
|
53
|
+
if (input.description && (input.description.length > 240 || /\p{Cc}/u.test(input.description)))
|
|
54
|
+
throw new Error('description must be at most 240 printable characters');
|
|
55
|
+
const rootPackage = JSON.parse((0, node_fs_1.readFileSync)(node_path_1.default.join(input.root, 'package.json'), 'utf8'));
|
|
56
|
+
if (rootPackage.name !== 'haneinfo-app-template')
|
|
57
|
+
throw new Error('scaffold requires an uninitialized haneinfo-app-template clone');
|
|
58
|
+
if ((0, node_fs_1.existsSync)(node_path_1.default.join(input.root, '.haneinfo', 'scaffold.json')))
|
|
59
|
+
throw new Error('scaffold already initialized this App');
|
|
60
|
+
if ((0, node_fs_1.existsSync)(node_path_1.default.join(input.root, '.env')))
|
|
61
|
+
throw new Error('scaffold requires a clone without .env so local credentials cannot be overwritten');
|
|
62
|
+
checkPorts(input);
|
|
63
|
+
}
|
|
64
|
+
function makePlan(input) {
|
|
65
|
+
validate(input);
|
|
66
|
+
const planned = new Map();
|
|
67
|
+
const description = input.description ?? input.displayName;
|
|
68
|
+
const change = (file, from, to, label, minimum = 1) => {
|
|
69
|
+
const absolutePath = node_path_1.default.join(input.root, file);
|
|
70
|
+
let item = planned.get(file);
|
|
71
|
+
if (!item) {
|
|
72
|
+
const original = (0, node_fs_1.readFileSync)(absolutePath, 'utf8');
|
|
73
|
+
item = { file, absolutePath, original, content: original, changes: [] };
|
|
74
|
+
planned.set(file, item);
|
|
75
|
+
}
|
|
76
|
+
const occurrences = item.content.split(from).length - 1;
|
|
77
|
+
if (occurrences < minimum)
|
|
78
|
+
throw new Error(`${file}: expected ${label} (${JSON.stringify(from)}), found ${occurrences}`);
|
|
79
|
+
item.content = item.content.replaceAll(from, to);
|
|
80
|
+
item.changes.push(`${label}: ${from} → ${to} (${occurrences})`);
|
|
81
|
+
};
|
|
82
|
+
const env = (key, value) => {
|
|
83
|
+
const file = '.env.example';
|
|
84
|
+
const absolutePath = node_path_1.default.join(input.root, file);
|
|
85
|
+
let item = planned.get(file);
|
|
86
|
+
if (!item) {
|
|
87
|
+
const original = (0, node_fs_1.readFileSync)(absolutePath, 'utf8');
|
|
88
|
+
item = { file, absolutePath, original, content: original, changes: [] };
|
|
89
|
+
planned.set(file, item);
|
|
90
|
+
}
|
|
91
|
+
const expression = new RegExp(`^${key}=[^\\r\\n]*`, 'gmu');
|
|
92
|
+
const matches = [...item.content.matchAll(expression)];
|
|
93
|
+
if (matches.length !== 1)
|
|
94
|
+
throw new Error(`${file}: expected one ${key} entry, found ${matches.length}`);
|
|
95
|
+
item.content = item.content.replace(expression, `${key}=${value}`);
|
|
96
|
+
item.changes.push(`${key} → ${value}`);
|
|
97
|
+
};
|
|
98
|
+
change('package.json', '"name": "haneinfo-app-template"', `"name": "${input.name}"`, 'root package name');
|
|
99
|
+
change('backend/package.json', '"name": "@app/backend"', `"name": "@app/${input.name}"`, 'backend package name');
|
|
100
|
+
change('frontend/package.json', '"name": "@app/frontend"', `"name": "${input.name}"`, 'frontend package name');
|
|
101
|
+
change('frontend/package.json', 'next dev -p 3901', `next dev -p ${input.frontendPort}`, 'frontend dev port');
|
|
102
|
+
change('frontend/package.json', 'next start -p 3901', `next start -p ${input.frontendPort}`, 'frontend start port');
|
|
103
|
+
env('APP_NAME', input.name);
|
|
104
|
+
env('MCP_TOOL_PREFIX', input.name);
|
|
105
|
+
env('DB_PORT', String(input.dbPort));
|
|
106
|
+
env('DATABASE_URL', `'postgresql://postgres:<set-a-strong-password>@localhost:${input.dbPort}/app_db'`);
|
|
107
|
+
env('OIDC_AUDIENCE', `"${input.name}"`);
|
|
108
|
+
env('AUTH_KEYCLOAK_ID', `"${input.name}"`);
|
|
109
|
+
env('CORS_ORIGINS', `http://localhost:${input.frontendPort}`);
|
|
110
|
+
env('PORT', String(input.backendPort));
|
|
111
|
+
env('BACKEND_PORT', String(input.backendPort));
|
|
112
|
+
env('FRONTEND_PORT', String(input.frontendPort));
|
|
113
|
+
env('NEXT_PUBLIC_API_URL', `http://localhost:${input.backendPort}`);
|
|
114
|
+
env('PUBLIC_BASE_URL', `http://localhost:${input.backendPort}`);
|
|
115
|
+
const dollar = '$';
|
|
116
|
+
change('docker-compose.yml', `${dollar}{DB_PORT:-23900}`, `${dollar}{DB_PORT:-${input.dbPort}}`, 'Docker DB fallback');
|
|
117
|
+
change('frontend/src/config/app-config.ts', '"Haneinfo App Template"', JSON.stringify(input.displayName), 'display name');
|
|
118
|
+
change('frontend/src/config/app-config.ts', 'Haneinfo App Template.', `${input.displayName}.`, 'copyright');
|
|
119
|
+
change('frontend/src/config/app-config.ts', '"Haneinfo App Template is a fully customizable starter for business systems built with Next.js 16, Tailwind CSS v4, and shadcn/ui."', JSON.stringify(description), 'description');
|
|
120
|
+
change('README.md', '# haneinfo-app-template', `# ${input.name}`, 'README title');
|
|
121
|
+
change('README.md', 'localhost:23900', `localhost:${input.dbPort}`, 'README DB URL');
|
|
122
|
+
change('README.md', 'localhost:3900', `localhost:${input.backendPort}`, 'README backend URL');
|
|
123
|
+
change('README.md', 'localhost:3901', `localhost:${input.frontendPort}`, 'README frontend URL');
|
|
124
|
+
change('README.md', 'port 3900', `port ${input.backendPort}`, 'README backend prose');
|
|
125
|
+
change('README.md', 'port 3901', `port ${input.frontendPort}`, 'README frontend prose');
|
|
126
|
+
for (const file of ['AGENTS.md', 'CLAUDE.md', 'docs/agent-guide.md'])
|
|
127
|
+
change(file, '# haneinfo-app-template', `# ${input.name}`, 'guide title');
|
|
128
|
+
change('backend/prisma/seed.ts', '"haneinfo-app-template"', JSON.stringify(input.name), 'seed App fallback');
|
|
129
|
+
change('backend/src/main.ts', 'localhost:3901', `localhost:${input.frontendPort}`, 'CORS example');
|
|
130
|
+
change('backend/src/app.module.spec.ts', '"haneinfo-app-template"', JSON.stringify(input.name), 'topology test App identity');
|
|
131
|
+
change('e2e/test-env.ts', '"E2E_KEYCLOAK_CLIENT_ID", "template"', `"E2E_KEYCLOAK_CLIENT_ID", "${input.name}"`, 'E2E client ID');
|
|
132
|
+
change('e2e/test-env.ts', '"E2E_KEYCLOAK_CLIENT_SECRET", "dev-secret-template"', `"E2E_KEYCLOAK_CLIENT_SECRET", "dev-secret-${input.name}"`, 'E2E client secret');
|
|
133
|
+
change('e2e/keycloak/README.md', '`template` client', `\`${input.name}\` client`, 'realm README client');
|
|
134
|
+
change('e2e/keycloak/README.md', 'dev-secret-template', `dev-secret-${input.name}`, 'realm README secret');
|
|
135
|
+
change('e2e/keycloak/README.md', 'localhost:3901', `localhost:${input.frontendPort}`, 'realm README frontend URL');
|
|
136
|
+
const workflow = '.gitea/workflows/ci.yml';
|
|
137
|
+
change(workflow, 'haneinfo-app-template', input.name, 'CI App identity');
|
|
138
|
+
change(workflow, 'dev-secret-template', `dev-secret-${input.name}`, 'CI OIDC client secret');
|
|
139
|
+
change(workflow, 'OIDC_AUDIENCE=template', `OIDC_AUDIENCE=${input.name}`, 'CI OIDC audience');
|
|
140
|
+
change(workflow, 'AUTH_KEYCLOAK_ID=template', `AUTH_KEYCLOAK_ID=${input.name}`, 'CI OIDC client ID');
|
|
141
|
+
change(workflow, 'E2E_KEYCLOAK_CLIENT_ID: template', `E2E_KEYCLOAK_CLIENT_ID: ${input.name}`, 'CI E2E client ID');
|
|
142
|
+
change(workflow, 'localhost:3900', `localhost:${input.backendPort}`, 'CI backend URL');
|
|
143
|
+
change(workflow, 'localhost:3901', `localhost:${input.frontendPort}`, 'CI frontend URL');
|
|
144
|
+
change(workflow, 'PORT=3900', `PORT=${input.backendPort}`, 'CI backend port');
|
|
145
|
+
change(workflow, 'FRONTEND_PORT=3901', `FRONTEND_PORT=${input.frontendPort}`, 'CI frontend port');
|
|
146
|
+
const realmFile = 'e2e/keycloak/haneinfo-dev-realm.json';
|
|
147
|
+
const realmPath = node_path_1.default.join(input.root, realmFile);
|
|
148
|
+
const realmSource = (0, node_fs_1.readFileSync)(realmPath, 'utf8');
|
|
149
|
+
const realm = JSON.parse(realmSource);
|
|
150
|
+
if (realm.clients?.filter((client) => client.clientId === 'template').length !== 1)
|
|
151
|
+
throw new Error(`${realmFile}: expected one template client`);
|
|
152
|
+
const replacedRealm = realmSource
|
|
153
|
+
.replace(/\btemplate\b/gu, input.name)
|
|
154
|
+
.replaceAll('localhost:3901', `localhost:${input.frontendPort}`);
|
|
155
|
+
JSON.parse(replacedRealm);
|
|
156
|
+
planned.set(realmFile, {
|
|
157
|
+
file: realmFile,
|
|
158
|
+
absolutePath: realmPath,
|
|
159
|
+
original: realmSource,
|
|
160
|
+
content: replacedRealm,
|
|
161
|
+
changes: [
|
|
162
|
+
`template client, roles, groups, and flows → ${input.name}`,
|
|
163
|
+
`frontend redirect URI: 3901 → ${input.frontendPort}`,
|
|
164
|
+
],
|
|
165
|
+
});
|
|
166
|
+
return [...planned.values()];
|
|
167
|
+
}
|
|
168
|
+
/** Preflight every template rule before writing any file. */
|
|
169
|
+
function scaffoldApp(input) {
|
|
170
|
+
const root = node_path_1.default.resolve(input.root);
|
|
171
|
+
const plan = makePlan({ ...input, root });
|
|
172
|
+
if (!input.dryRun) {
|
|
173
|
+
const envExample = plan.find((file) => file.file === '.env.example');
|
|
174
|
+
if (!envExample)
|
|
175
|
+
throw new Error('scaffold did not plan .env.example');
|
|
176
|
+
const password = (0, node_crypto_1.randomBytes)(16).toString('hex');
|
|
177
|
+
const marker = node_path_1.default.join(root, '.haneinfo', 'scaffold.json');
|
|
178
|
+
const localEnv = node_path_1.default.join(root, '.env');
|
|
179
|
+
const written = [];
|
|
180
|
+
try {
|
|
181
|
+
for (const file of plan) {
|
|
182
|
+
written.push(file);
|
|
183
|
+
(0, node_fs_1.writeFileSync)(file.absolutePath, file.content);
|
|
184
|
+
}
|
|
185
|
+
(0, node_fs_1.writeFileSync)(localEnv, envExample.content.replaceAll('<set-a-strong-password>', password));
|
|
186
|
+
(0, node_fs_1.mkdirSync)(node_path_1.default.dirname(marker), { recursive: true });
|
|
187
|
+
(0, node_fs_1.writeFileSync)(marker, `${JSON.stringify({
|
|
188
|
+
version: 1,
|
|
189
|
+
name: input.name,
|
|
190
|
+
displayName: input.displayName,
|
|
191
|
+
dbPort: input.dbPort,
|
|
192
|
+
backendPort: input.backendPort,
|
|
193
|
+
frontendPort: input.frontendPort,
|
|
194
|
+
}, null, 2)}\n`);
|
|
195
|
+
}
|
|
196
|
+
catch (error) {
|
|
197
|
+
for (const file of written.reverse())
|
|
198
|
+
(0, node_fs_1.writeFileSync)(file.absolutePath, file.original);
|
|
199
|
+
if ((0, node_fs_1.existsSync)(localEnv))
|
|
200
|
+
(0, node_fs_1.unlinkSync)(localEnv);
|
|
201
|
+
if ((0, node_fs_1.existsSync)(marker))
|
|
202
|
+
(0, node_fs_1.unlinkSync)(marker);
|
|
203
|
+
throw error;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
return plan.map(({ file, changes }) => ({ file, changes }));
|
|
207
|
+
}
|
|
208
|
+
//# sourceMappingURL=scaffold.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scaffold.js","sourceRoot":"","sources":["../src/scaffold.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { CheckIssue } from './integration-contracts';
|
|
2
|
+
/** Check App-owned domain event handlers against the documented registration path. */
|
|
3
|
+
export declare function checkDomainEventSubscribers(appRoot: string): CheckIssue[];
|
|
4
|
+
//# sourceMappingURL=subscriber-wiring.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"subscriber-wiring.d.ts","sourceRoot":"","sources":["../src/subscriber-wiring.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAW1D,sFAAsF;AACtF,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,MAAM,GAAG,UAAU,EAAE,CAyDzE"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.checkDomainEventSubscribers = checkDomainEventSubscribers;
|
|
7
|
+
const node_fs_1 = require("node:fs");
|
|
8
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
+
function sourceFiles(directory) {
|
|
10
|
+
if (!(0, node_fs_1.existsSync)(directory))
|
|
11
|
+
return [];
|
|
12
|
+
return (0, node_fs_1.readdirSync)(directory, { withFileTypes: true }).flatMap((entry) => {
|
|
13
|
+
const file = node_path_1.default.join(directory, entry.name);
|
|
14
|
+
if (entry.isDirectory())
|
|
15
|
+
return sourceFiles(file);
|
|
16
|
+
return entry.name.endsWith('.ts') && !entry.name.endsWith('.spec.ts') ? [file] : [];
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
/** Check App-owned domain event handlers against the documented registration path. */
|
|
20
|
+
function checkDomainEventSubscribers(appRoot) {
|
|
21
|
+
const sourceRoot = node_path_1.default.join(appRoot, 'backend', 'src');
|
|
22
|
+
const moduleFile = node_path_1.default.join(sourceRoot, 'domain-events', 'domain-events.module.ts');
|
|
23
|
+
const moduleContent = (0, node_fs_1.existsSync)(moduleFile) ? (0, node_fs_1.readFileSync)(moduleFile, 'utf8') : '';
|
|
24
|
+
const issues = [];
|
|
25
|
+
for (const file of sourceFiles(sourceRoot)) {
|
|
26
|
+
const content = (0, node_fs_1.readFileSync)(file, 'utf8');
|
|
27
|
+
if (/registry\s*\.\s*on\s*\(/u.test(content) && file !== moduleFile) {
|
|
28
|
+
issues.push({
|
|
29
|
+
code: 'domain-event-direct-registration',
|
|
30
|
+
path: file,
|
|
31
|
+
message: 'calls registry.on(...) outside domain-events.module.ts; use registerDomainEventSubscribers()',
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
const rel = node_path_1.default.relative(sourceRoot, file);
|
|
35
|
+
const isHandler = rel.split(node_path_1.default.sep).includes('handlers') && rel.endsWith('.handler.ts');
|
|
36
|
+
if (!isHandler)
|
|
37
|
+
continue;
|
|
38
|
+
const decorated = content.includes('@DomainEventSubscriber');
|
|
39
|
+
if (!decorated && !content.includes('@domain-events-undecorated:')) {
|
|
40
|
+
issues.push({
|
|
41
|
+
code: 'domain-event-handler-undecorated',
|
|
42
|
+
path: file,
|
|
43
|
+
message: 'missing @DomainEventSubscriber(...) and no exemption marker',
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
if (!decorated)
|
|
47
|
+
continue;
|
|
48
|
+
const className = content.match(/export\s+class\s+([A-Za-z0-9_]+)/u)?.[1];
|
|
49
|
+
if (!className) {
|
|
50
|
+
issues.push({
|
|
51
|
+
code: 'domain-event-handler-class-missing',
|
|
52
|
+
path: file,
|
|
53
|
+
message: 'decorated handler has no exported class',
|
|
54
|
+
});
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
const base = className.replace(/DomainEventHandler$/u, 'Handler');
|
|
58
|
+
const variableName = base.charAt(0).toLowerCase() + base.slice(1);
|
|
59
|
+
if (!moduleContent.includes(className)) {
|
|
60
|
+
issues.push({
|
|
61
|
+
code: 'domain-event-handler-not-provided',
|
|
62
|
+
path: file,
|
|
63
|
+
message: `${className} is not referenced by domain-events.module.ts`,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
if (!moduleContent.includes('registerDomainEventSubscribers') ||
|
|
67
|
+
!moduleContent.includes(variableName)) {
|
|
68
|
+
issues.push({
|
|
69
|
+
code: 'domain-event-handler-not-registered',
|
|
70
|
+
path: file,
|
|
71
|
+
message: `${variableName} is not passed to registerDomainEventSubscribers()`,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return issues;
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=subscriber-wiring.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"subscriber-wiring.js","sourceRoot":"","sources":["../src/subscriber-wiring.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@haneinfo/app-tooling",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": false,
|
|
5
|
+
"type": "commonjs",
|
|
6
|
+
"description": "Shared Haneinfo App checks and scaffold tooling",
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"bin": {
|
|
10
|
+
"haneinfo-app": "./dist/bin.js"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"dist",
|
|
14
|
+
"README.md"
|
|
15
|
+
],
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"@haneinfo/plugin-cli": "2.1.0"
|
|
18
|
+
},
|
|
19
|
+
"devDependencies": {
|
|
20
|
+
"@types/node": "^24.13.0",
|
|
21
|
+
"typescript": "^5.9.3",
|
|
22
|
+
"vitest": "^4.1.11"
|
|
23
|
+
},
|
|
24
|
+
"engines": {
|
|
25
|
+
"node": ">=22.0.0"
|
|
26
|
+
},
|
|
27
|
+
"license": "UNLICENSED",
|
|
28
|
+
"publishConfig": {
|
|
29
|
+
"registry": "https://registry.npmjs.org/",
|
|
30
|
+
"access": "public"
|
|
31
|
+
},
|
|
32
|
+
"scripts": {
|
|
33
|
+
"build": "tsc -p tsconfig.build.json",
|
|
34
|
+
"typecheck": "tsc --noEmit",
|
|
35
|
+
"test": "vitest run"
|
|
36
|
+
}
|
|
37
|
+
}
|