@mettlecast/domain-cli 0.2.86 → 0.2.87
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/dist/builder/build-registry.d.ts +8 -2
- package/dist/builder/build-registry.js +80 -88
- package/dist/commands/update-all.d.ts +1 -0
- package/dist/commands/update-all.js +174 -0
- package/dist/commands/validate.js +26 -59
- package/dist/utils/toolchain-manifest.d.ts +90 -0
- package/dist/utils/toolchain-manifest.js +144 -0
- package/package.json +3 -3
- package/scripts/generate-toolchain-manifest.mjs +163 -0
- package/src/__tests__/build-registry.test.ts +125 -3
- package/src/__tests__/commands/update-all.test.ts +341 -3
- package/src/__tests__/package-freshness.test.ts +8 -4
- package/src/__tests__/toolchain-manifest.test.ts +185 -0
- package/src/__tests__/validate.test.ts +183 -108
- package/src/builder/build-registry.ts +116 -92
- package/src/commands/update-all.ts +216 -0
- package/src/commands/validate.ts +29 -70
- package/src/utils/toolchain-manifest.ts +236 -0
- package/toolchain-manifest.json +10 -0
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Toolchain manifest types and factory for the @mettlecast publish workflow.
|
|
3
|
+
*
|
|
4
|
+
* The manifest is included in the published domain-cli tarball so that
|
|
5
|
+
* `mc-domain-module update-all` (or a future standalone updater) can
|
|
6
|
+
* determine exact package versions to install / pin.
|
|
7
|
+
*
|
|
8
|
+
* Manifest schema:
|
|
9
|
+
* ```json
|
|
10
|
+
* {
|
|
11
|
+
* "schemaVersion": 1,
|
|
12
|
+
* "registrySchemaVersion": "1",
|
|
13
|
+
* "packages": {
|
|
14
|
+
* "domainCli": "0.2.53",
|
|
15
|
+
* "domainCdkPacker": "0.2.53",
|
|
16
|
+
* "domainRuntime": "0.2.53",
|
|
17
|
+
* "eslintPluginDomainModule": "0.2.53"
|
|
18
|
+
* }
|
|
19
|
+
* }
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* `registrySchemaVersion` matches the existing `DomainRegistry.schemaVersion`
|
|
23
|
+
* string literal `'1'` so that consumer validation can compare the manifest
|
|
24
|
+
* registry schema version to the generated registry schema version directly.
|
|
25
|
+
*/
|
|
26
|
+
/**
|
|
27
|
+
* Shape of the published toolchain-manifest.json at package root.
|
|
28
|
+
*/
|
|
29
|
+
export interface ToolchainManifest {
|
|
30
|
+
/** Manifest schema version (integer). Must be 1 for this generation. */
|
|
31
|
+
schemaVersion: number;
|
|
32
|
+
/**
|
|
33
|
+
* Registry schema version — a string matching the `DomainRegistry.schemaVersion`
|
|
34
|
+
* literal (currently `'1'`). Consumer validation compares this against the
|
|
35
|
+
* generated registry schema version to verify contract compatibility.
|
|
36
|
+
*/
|
|
37
|
+
registrySchemaVersion: string;
|
|
38
|
+
packages: ToolchainPackages;
|
|
39
|
+
}
|
|
40
|
+
export interface ToolchainPackages {
|
|
41
|
+
domainCli: string;
|
|
42
|
+
domainCdkPacker: string;
|
|
43
|
+
domainRuntime: string;
|
|
44
|
+
eslintPluginDomainModule: string;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* The expected registry schema version that matches `DomainRegistry.schemaVersion`.
|
|
48
|
+
* Consumer validation compares the manifest's `registrySchemaVersion` against
|
|
49
|
+
* this constant and the generated registry's `schemaVersion`.
|
|
50
|
+
*/
|
|
51
|
+
export declare const EXPECTED_REGISTRY_SCHEMA_VERSION = "1";
|
|
52
|
+
/**
|
|
53
|
+
* Package identifiers used in the manifest.
|
|
54
|
+
* Maps to the `packages` key in ToolchainManifest.
|
|
55
|
+
*/
|
|
56
|
+
export type ToolchainPackageId = keyof ToolchainPackages;
|
|
57
|
+
/**
|
|
58
|
+
* Create a ToolchainManifest from exact resolved versions.
|
|
59
|
+
* This is a pure factory function (no side-effects).
|
|
60
|
+
*
|
|
61
|
+
* @param versions - Exact semver strings for each package
|
|
62
|
+
* @returns A complete ToolchainManifest
|
|
63
|
+
*/
|
|
64
|
+
export declare function createToolchainManifest(versions: ToolchainPackages): ToolchainManifest;
|
|
65
|
+
/**
|
|
66
|
+
* Known @mettlecast dependency names that the domain-cli may reference
|
|
67
|
+
* with wildcard ranges and should be replaced with exact versions.
|
|
68
|
+
*/
|
|
69
|
+
export declare const METTLECAST_CLI_DEPS: Record<ToolchainPackageId, string>;
|
|
70
|
+
/**
|
|
71
|
+
* Rewrite the domain-cli package.json's @mettlecast dependencies from
|
|
72
|
+
* wildcard / workspace ranges to exact resolved versions.
|
|
73
|
+
*
|
|
74
|
+
* @param pkgJson - Parsed package.json content (mutated in place)
|
|
75
|
+
* @param versions - Exact semver versions for each toolchain package
|
|
76
|
+
* @returns True if any dependency was rewritten
|
|
77
|
+
*/
|
|
78
|
+
export declare function rewriteCliDependencies(pkgJson: {
|
|
79
|
+
dependencies?: Record<string, string>;
|
|
80
|
+
}, versions: ToolchainPackages): boolean;
|
|
81
|
+
/**
|
|
82
|
+
* Resolve the toolchain manifest from the installed @mettlecast/domain-cli package.
|
|
83
|
+
* Tries the project's `node_modules/@mettlecast/domain-cli/` first, then falls
|
|
84
|
+
* back to the CLI's own package root (for global/npx installations).
|
|
85
|
+
*
|
|
86
|
+
* @param projectRoot - Project root directory.
|
|
87
|
+
* @returns The parsed toolchain manifest.
|
|
88
|
+
* @throws If the manifest is missing, malformed, or has an incompatible schema version.
|
|
89
|
+
*/
|
|
90
|
+
export declare function loadToolchainManifest(projectRoot: string): Promise<ToolchainManifest>;
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { join, dirname } from 'node:path';
|
|
2
|
+
import { readFile } from 'node:fs/promises';
|
|
3
|
+
import { existsSync } from 'node:fs';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
/**
|
|
6
|
+
* The expected registry schema version that matches `DomainRegistry.schemaVersion`.
|
|
7
|
+
* Consumer validation compares the manifest's `registrySchemaVersion` against
|
|
8
|
+
* this constant and the generated registry's `schemaVersion`.
|
|
9
|
+
*/
|
|
10
|
+
export const EXPECTED_REGISTRY_SCHEMA_VERSION = '1';
|
|
11
|
+
/**
|
|
12
|
+
* Create a ToolchainManifest from exact resolved versions.
|
|
13
|
+
* This is a pure factory function (no side-effects).
|
|
14
|
+
*
|
|
15
|
+
* @param versions - Exact semver strings for each package
|
|
16
|
+
* @returns A complete ToolchainManifest
|
|
17
|
+
*/
|
|
18
|
+
export function createToolchainManifest(versions) {
|
|
19
|
+
return {
|
|
20
|
+
schemaVersion: 1,
|
|
21
|
+
registrySchemaVersion: EXPECTED_REGISTRY_SCHEMA_VERSION,
|
|
22
|
+
packages: {
|
|
23
|
+
domainCli: versions.domainCli,
|
|
24
|
+
domainCdkPacker: versions.domainCdkPacker,
|
|
25
|
+
domainRuntime: versions.domainRuntime,
|
|
26
|
+
eslintPluginDomainModule: versions.eslintPluginDomainModule,
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Known @mettlecast dependency names that the domain-cli may reference
|
|
32
|
+
* with wildcard ranges and should be replaced with exact versions.
|
|
33
|
+
*/
|
|
34
|
+
export const METTLECAST_CLI_DEPS = {
|
|
35
|
+
domainCli: '@mettlecast/domain-cli',
|
|
36
|
+
domainCdkPacker: '@mettlecast/domain-cdk-packer',
|
|
37
|
+
domainRuntime: '@mettlecast/domain-runtime',
|
|
38
|
+
eslintPluginDomainModule: '@mettlecast/eslint-plugin-domain-module',
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* Rewrite the domain-cli package.json's @mettlecast dependencies from
|
|
42
|
+
* wildcard / workspace ranges to exact resolved versions.
|
|
43
|
+
*
|
|
44
|
+
* @param pkgJson - Parsed package.json content (mutated in place)
|
|
45
|
+
* @param versions - Exact semver versions for each toolchain package
|
|
46
|
+
* @returns True if any dependency was rewritten
|
|
47
|
+
*/
|
|
48
|
+
export function rewriteCliDependencies(pkgJson, versions) {
|
|
49
|
+
let changed = false;
|
|
50
|
+
if (!pkgJson.dependencies)
|
|
51
|
+
return changed;
|
|
52
|
+
for (const [id, depName] of Object.entries(METTLECAST_CLI_DEPS)) {
|
|
53
|
+
const packageId = id;
|
|
54
|
+
if (depName in pkgJson.dependencies) {
|
|
55
|
+
const exact = versions[packageId];
|
|
56
|
+
pkgJson.dependencies[depName] = exact;
|
|
57
|
+
changed = true;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return changed;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Resolve the toolchain manifest from the installed @mettlecast/domain-cli package.
|
|
64
|
+
* Tries the project's `node_modules/@mettlecast/domain-cli/` first, then falls
|
|
65
|
+
* back to the CLI's own package root (for global/npx installations).
|
|
66
|
+
*
|
|
67
|
+
* @param projectRoot - Project root directory.
|
|
68
|
+
* @returns The parsed toolchain manifest.
|
|
69
|
+
* @throws If the manifest is missing, malformed, or has an incompatible schema version.
|
|
70
|
+
*/
|
|
71
|
+
export async function loadToolchainManifest(projectRoot) {
|
|
72
|
+
// Primary path: project's node_modules
|
|
73
|
+
const projectManifestPath = join(projectRoot, 'node_modules', '@mettlecast', 'domain-cli', 'toolchain-manifest.json');
|
|
74
|
+
// Fallback path: CLI's own package root (resolved from this module's location)
|
|
75
|
+
const cliPackageRoot = resolveCliPackageRoot();
|
|
76
|
+
const cliManifestPath = join(cliPackageRoot, 'toolchain-manifest.json');
|
|
77
|
+
let manifestPath;
|
|
78
|
+
if (existsSync(projectManifestPath)) {
|
|
79
|
+
manifestPath = projectManifestPath;
|
|
80
|
+
}
|
|
81
|
+
else if (existsSync(cliManifestPath)) {
|
|
82
|
+
manifestPath = cliManifestPath;
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
throw new Error(`toolchain-manifest.json not found at ${projectManifestPath} or ${cliManifestPath}. ` +
|
|
86
|
+
"Run 'npm install' or 'npm ci' in the project root first, then retry.");
|
|
87
|
+
}
|
|
88
|
+
const raw = await readFile(manifestPath, 'utf-8');
|
|
89
|
+
let manifest;
|
|
90
|
+
try {
|
|
91
|
+
manifest = JSON.parse(raw);
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
throw new Error(`toolchain-manifest.json at ${manifestPath} is not valid JSON.`);
|
|
95
|
+
}
|
|
96
|
+
// Validate schema version
|
|
97
|
+
if (typeof manifest.schemaVersion !== 'number' || manifest.schemaVersion !== 1) {
|
|
98
|
+
throw new Error(`toolchain manifest schemaVersion is ${String(manifest.schemaVersion)}, expected 1. ` +
|
|
99
|
+
'The installed @mettlecast/domain-cli version is incompatible with this toolchain resolver.');
|
|
100
|
+
}
|
|
101
|
+
// Validate registry schema version equals expected value
|
|
102
|
+
if (typeof manifest.registrySchemaVersion !== 'string' ||
|
|
103
|
+
manifest.registrySchemaVersion !== EXPECTED_REGISTRY_SCHEMA_VERSION) {
|
|
104
|
+
throw new Error(`toolchain manifest registrySchemaVersion is "${String(manifest.registrySchemaVersion)}", ` +
|
|
105
|
+
`expected "${EXPECTED_REGISTRY_SCHEMA_VERSION}". ` +
|
|
106
|
+
'The registry schema contract is incompatible with this toolchain resolver.');
|
|
107
|
+
}
|
|
108
|
+
// Validate all package keys are present and non-empty
|
|
109
|
+
if (!manifest.packages || typeof manifest.packages !== 'object') {
|
|
110
|
+
throw new Error('toolchain manifest is missing required field "packages".');
|
|
111
|
+
}
|
|
112
|
+
const TOOLCHAIN_PACKAGE_KEYS = [
|
|
113
|
+
'domainCli',
|
|
114
|
+
'domainCdkPacker',
|
|
115
|
+
'domainRuntime',
|
|
116
|
+
'eslintPluginDomainModule',
|
|
117
|
+
];
|
|
118
|
+
for (const key of TOOLCHAIN_PACKAGE_KEYS) {
|
|
119
|
+
const version = manifest.packages[key];
|
|
120
|
+
if (typeof version !== 'string' || version.length === 0) {
|
|
121
|
+
throw new Error(`toolchain manifest is missing or has empty version for package "${key}".`);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return manifest;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Resolve the @mettlecast/domain-cli package root directory.
|
|
128
|
+
* Uses the location of this module file (toolchain-manifest.ts) to find
|
|
129
|
+
* the CLI package root, supporting both source and dist layouts.
|
|
130
|
+
*/
|
|
131
|
+
function resolveCliPackageRoot() {
|
|
132
|
+
const thisFile = fileURLToPath(import.meta.url);
|
|
133
|
+
// In source: packages/domain-cli/src/utils/toolchain-manifest.ts -> up 3 levels
|
|
134
|
+
// In dist: packages/domain-cli/dist/utils/toolchain-manifest.js -> up 2 levels
|
|
135
|
+
const candidate = dirname(dirname(dirname(thisFile)));
|
|
136
|
+
// Check if candidate has package.json with @mettlecast/domain-cli name
|
|
137
|
+
const pkgJsonPath = join(candidate, 'package.json');
|
|
138
|
+
if (existsSync(pkgJsonPath)) {
|
|
139
|
+
return candidate;
|
|
140
|
+
}
|
|
141
|
+
// Fall back to dirname(dirname(thisFile)) for dist layout
|
|
142
|
+
const candidate2 = dirname(dirname(thisFile));
|
|
143
|
+
return candidate2;
|
|
144
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mettlecast/domain-cli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.87",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"registry": "https://registry.npmjs.org",
|
|
@@ -22,8 +22,8 @@
|
|
|
22
22
|
"dependencies": {
|
|
23
23
|
"@aws-sdk/client-s3": "^3.0.0",
|
|
24
24
|
"@aws-sdk/client-sfn": "^3.0.0",
|
|
25
|
-
"@mettlecast/domain-cdk-packer": "
|
|
26
|
-
"@mettlecast/domain-runtime": "
|
|
25
|
+
"@mettlecast/domain-cdk-packer": "0.2.88",
|
|
26
|
+
"@mettlecast/domain-runtime": "0.2.87",
|
|
27
27
|
"commander": "^12.0.0",
|
|
28
28
|
"dotenv": "^16.0.0",
|
|
29
29
|
"fastify": "^5.0.0",
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* generate-toolchain-manifest.mjs
|
|
5
|
+
*
|
|
6
|
+
* Generates toolchain-manifest.json in the domain-cli package root and,
|
|
7
|
+
* optionally, rewrites wildcard @mettlecast dependencies in the domain-cli
|
|
8
|
+
* package.json to exact resolved versions.
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* node generate-toolchain-manifest.mjs \
|
|
12
|
+
* --cli-version 0.2.53 \
|
|
13
|
+
* --runtime-version 0.2.53 \
|
|
14
|
+
* --packer-version 0.2.53 \
|
|
15
|
+
* --eslint-version 0.2.53 \
|
|
16
|
+
* [--rewrite-deps]
|
|
17
|
+
*
|
|
18
|
+
* All version flags are required.
|
|
19
|
+
* The --rewrite-deps flag additionally rewrites the domain-cli's package.json
|
|
20
|
+
* so that @mettlecast/* dependency ranges are pinned to exact versions.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import { readFile, writeFile } from 'node:fs/promises';
|
|
24
|
+
import { resolve, dirname } from 'node:path';
|
|
25
|
+
import { fileURLToPath } from 'node:url';
|
|
26
|
+
|
|
27
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
28
|
+
const CLI_PKG_DIR = resolve(__dirname, '..');
|
|
29
|
+
const PROJECT_ROOT = resolve(__dirname, '..', '..', '..');
|
|
30
|
+
|
|
31
|
+
function parseArgs() {
|
|
32
|
+
const args = process.argv.slice(2);
|
|
33
|
+
const opts = {
|
|
34
|
+
cliVersion: '',
|
|
35
|
+
runtimeVersion: '',
|
|
36
|
+
packerVersion: '',
|
|
37
|
+
eslintVersion: '',
|
|
38
|
+
rewriteDeps: false,
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
for (let i = 0; i < args.length; i++) {
|
|
42
|
+
switch (args[i]) {
|
|
43
|
+
case '--cli-version':
|
|
44
|
+
opts.cliVersion = args[++i];
|
|
45
|
+
break;
|
|
46
|
+
case '--runtime-version':
|
|
47
|
+
opts.runtimeVersion = args[++i];
|
|
48
|
+
break;
|
|
49
|
+
case '--packer-version':
|
|
50
|
+
opts.packerVersion = args[++i];
|
|
51
|
+
break;
|
|
52
|
+
case '--eslint-version':
|
|
53
|
+
opts.eslintVersion = args[++i];
|
|
54
|
+
break;
|
|
55
|
+
case '--rewrite-deps':
|
|
56
|
+
opts.rewriteDeps = true;
|
|
57
|
+
break;
|
|
58
|
+
default:
|
|
59
|
+
console.error(`Unknown flag: ${args[i]}`);
|
|
60
|
+
process.exit(1);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (!opts.cliVersion || !opts.runtimeVersion || !opts.packerVersion || !opts.eslintVersion) {
|
|
65
|
+
console.error(
|
|
66
|
+
'Missing required flags: --cli-version, --runtime-version, --packer-version, --eslint-version',
|
|
67
|
+
);
|
|
68
|
+
process.exit(1);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return opts;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Create a ToolchainManifest object.
|
|
76
|
+
*/
|
|
77
|
+
function createToolchainManifest(versions) {
|
|
78
|
+
return {
|
|
79
|
+
schemaVersion: 1,
|
|
80
|
+
registrySchemaVersion: '1',
|
|
81
|
+
packages: {
|
|
82
|
+
domainCli: versions.cliVersion,
|
|
83
|
+
domainCdkPacker: versions.packerVersion,
|
|
84
|
+
domainRuntime: versions.runtimeVersion,
|
|
85
|
+
eslintPluginDomainModule: versions.eslintVersion,
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Rewrite @mettlecast dependencies in the given package.json to exact versions.
|
|
92
|
+
* Returns true if any dep was modified.
|
|
93
|
+
*/
|
|
94
|
+
function rewriteCliDependencies(pkgJson, versions) {
|
|
95
|
+
const depMap = {
|
|
96
|
+
'@mettlecast/domain-cli': versions.cliVersion,
|
|
97
|
+
'@mettlecast/domain-cdk-packer': versions.packerVersion,
|
|
98
|
+
'@mettlecast/domain-runtime': versions.runtimeVersion,
|
|
99
|
+
'@mettlecast/eslint-plugin-domain-module': versions.eslintVersion,
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
let changed = false;
|
|
103
|
+
if (!pkgJson.dependencies) return changed;
|
|
104
|
+
|
|
105
|
+
for (const [depName, exactVersion] of Object.entries(depMap)) {
|
|
106
|
+
if (depName in pkgJson.dependencies) {
|
|
107
|
+
pkgJson.dependencies[depName] = exactVersion;
|
|
108
|
+
changed = true;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return changed;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async function main() {
|
|
116
|
+
const opts = parseArgs();
|
|
117
|
+
|
|
118
|
+
// 1. Generate toolchain-manifest.json
|
|
119
|
+
const manifest = createToolchainManifest({
|
|
120
|
+
cliVersion: opts.cliVersion,
|
|
121
|
+
runtimeVersion: opts.runtimeVersion,
|
|
122
|
+
packerVersion: opts.packerVersion,
|
|
123
|
+
eslintVersion: opts.eslintVersion,
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
const manifestPath = resolve(CLI_PKG_DIR, 'toolchain-manifest.json');
|
|
127
|
+
await writeFile(manifestPath, JSON.stringify(manifest, null, 2) + '\n', 'utf-8');
|
|
128
|
+
console.log(`Generated toolchain-manifest.json → ${manifestPath}`);
|
|
129
|
+
|
|
130
|
+
// 2. Optionally rewrite domain-cli package.json deps
|
|
131
|
+
if (opts.rewriteDeps) {
|
|
132
|
+
const pkgJsonPath = resolve(CLI_PKG_DIR, 'package.json');
|
|
133
|
+
const rawPkg = await readFile(pkgJsonPath, 'utf-8');
|
|
134
|
+
const pkgJson = JSON.parse(rawPkg);
|
|
135
|
+
|
|
136
|
+
const changed = rewriteCliDependencies(pkgJson, {
|
|
137
|
+
cliVersion: opts.cliVersion,
|
|
138
|
+
runtimeVersion: opts.runtimeVersion,
|
|
139
|
+
packerVersion: opts.packerVersion,
|
|
140
|
+
eslintVersion: opts.eslintVersion,
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
if (changed) {
|
|
144
|
+
await writeFile(pkgJsonPath, JSON.stringify(pkgJson, null, 2) + '\n', 'utf-8');
|
|
145
|
+
console.log(`Rewrote @mettlecast deps in package.json → ${pkgJsonPath}`);
|
|
146
|
+
} else {
|
|
147
|
+
console.log('No @mettlecast deps to rewrite in package.json');
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// For CI consumption, print version summary
|
|
152
|
+
console.log(`\n--- toolchain versions ---`);
|
|
153
|
+
console.log(`domain-runtime: ${opts.runtimeVersion}`);
|
|
154
|
+
console.log(`domain-cdk-packer: ${opts.packerVersion}`);
|
|
155
|
+
console.log(`eslint-plugin-domain-module: ${opts.eslintVersion}`);
|
|
156
|
+
console.log(`domain-cli: ${opts.cliVersion}`);
|
|
157
|
+
console.log(`rewrite-deps: ${opts.rewriteDeps}`);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
main().catch((err) => {
|
|
161
|
+
console.error(err);
|
|
162
|
+
process.exit(1);
|
|
163
|
+
});
|
|
@@ -53,9 +53,6 @@ describe('buildRegistry', () => {
|
|
|
53
53
|
});
|
|
54
54
|
|
|
55
55
|
it('builds a registry with domain + one API-exposed action', async () => {
|
|
56
|
-
// Issue #4689: defineApi was removed; the canonical HTTP endpoint
|
|
57
|
-
// surface is `defineAction({ exposure: { type: 'api', ... } })`.
|
|
58
|
-
// The action is recorded under `registry.actions` with API exposure metadata.
|
|
59
56
|
mockWalkDomainDir.mockResolvedValue({
|
|
60
57
|
domain: '/fake/domains/billing/domain.config.ts',
|
|
61
58
|
webhooks: [],
|
|
@@ -162,4 +159,129 @@ describe('buildRegistry', () => {
|
|
|
162
159
|
expect(warnings[0]).toContain('tsx error');
|
|
163
160
|
expect(registry.actions).toHaveLength(0);
|
|
164
161
|
});
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Strict contract (#5090): missing exposure causes build failure
|
|
165
|
+
*/
|
|
166
|
+
it('throws when action has no exposure (#5090)', async () => {
|
|
167
|
+
mockWalkDomainDir.mockResolvedValue({
|
|
168
|
+
domain: '/fake/domains/billing/domain.config.ts',
|
|
169
|
+
webhooks: [],
|
|
170
|
+
subscribers: [],
|
|
171
|
+
actions: ['/fake/domains/billing/actions/no-exposure.ts'],
|
|
172
|
+
schedules: [],
|
|
173
|
+
jobs: [],
|
|
174
|
+
integrations: [],
|
|
175
|
+
publishes: undefined,
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
mockLoadModuleExports
|
|
179
|
+
.mockResolvedValueOnce([
|
|
180
|
+
{ _kind: 'domain', _exportName: 'default', id: 'billing', name: 'Billing', tenancy: 'required' },
|
|
181
|
+
])
|
|
182
|
+
.mockResolvedValueOnce([
|
|
183
|
+
{
|
|
184
|
+
_kind: 'action',
|
|
185
|
+
_exportName: 'noExposure',
|
|
186
|
+
id: 'no-exposure',
|
|
187
|
+
backendAccess: 'private',
|
|
188
|
+
// exposure deliberately omitted
|
|
189
|
+
idempotent: false,
|
|
190
|
+
},
|
|
191
|
+
]);
|
|
192
|
+
|
|
193
|
+
await expect(buildRegistry(DOMAIN_ROOT)).rejects.toThrow(/exposure/i);
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it('throws when action has missing backendAccess (#5090)', async () => {
|
|
197
|
+
mockWalkDomainDir.mockResolvedValue({
|
|
198
|
+
domain: '/fake/domains/billing/domain.config.ts',
|
|
199
|
+
webhooks: [],
|
|
200
|
+
subscribers: [],
|
|
201
|
+
actions: ['/fake/domains/billing/actions/no-backend.ts'],
|
|
202
|
+
schedules: [],
|
|
203
|
+
jobs: [],
|
|
204
|
+
integrations: [],
|
|
205
|
+
publishes: undefined,
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
mockLoadModuleExports
|
|
209
|
+
.mockResolvedValueOnce([
|
|
210
|
+
{ _kind: 'domain', _exportName: 'default', id: 'billing', name: 'Billing', tenancy: 'required' },
|
|
211
|
+
])
|
|
212
|
+
.mockResolvedValueOnce([
|
|
213
|
+
{
|
|
214
|
+
_kind: 'action',
|
|
215
|
+
_exportName: 'noBackend',
|
|
216
|
+
id: 'no-backend',
|
|
217
|
+
// backendAccess omitted
|
|
218
|
+
exposure: { type: 'internal' },
|
|
219
|
+
idempotent: false,
|
|
220
|
+
},
|
|
221
|
+
]);
|
|
222
|
+
|
|
223
|
+
await expect(buildRegistry(DOMAIN_ROOT)).rejects.toThrow(/backendAccess/i);
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
it('throws when action has missing idempotent (#5090)', async () => {
|
|
227
|
+
mockWalkDomainDir.mockResolvedValue({
|
|
228
|
+
domain: '/fake/domains/billing/domain.config.ts',
|
|
229
|
+
webhooks: [],
|
|
230
|
+
subscribers: [],
|
|
231
|
+
actions: ['/fake/domains/billing/actions/no-idempotent.ts'],
|
|
232
|
+
schedules: [],
|
|
233
|
+
jobs: [],
|
|
234
|
+
integrations: [],
|
|
235
|
+
publishes: undefined,
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
mockLoadModuleExports
|
|
239
|
+
.mockResolvedValueOnce([
|
|
240
|
+
{ _kind: 'domain', _exportName: 'default', id: 'billing', name: 'Billing', tenancy: 'required' },
|
|
241
|
+
])
|
|
242
|
+
.mockResolvedValueOnce([
|
|
243
|
+
{
|
|
244
|
+
_kind: 'action',
|
|
245
|
+
_exportName: 'noIdempotent',
|
|
246
|
+
id: 'no-idempotent',
|
|
247
|
+
backendAccess: 'private',
|
|
248
|
+
exposure: { type: 'internal' },
|
|
249
|
+
// idempotent omitted
|
|
250
|
+
},
|
|
251
|
+
]);
|
|
252
|
+
|
|
253
|
+
await expect(buildRegistry(DOMAIN_ROOT)).rejects.toThrow(/idempotent/i);
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
it('accepts a valid internal action with exposure explicitly declared (#5090)', async () => {
|
|
257
|
+
mockWalkDomainDir.mockResolvedValue({
|
|
258
|
+
domain: '/fake/domains/billing/domain.config.ts',
|
|
259
|
+
webhooks: [],
|
|
260
|
+
subscribers: [],
|
|
261
|
+
actions: ['/fake/domains/billing/actions/internal.ts'],
|
|
262
|
+
schedules: [],
|
|
263
|
+
jobs: [],
|
|
264
|
+
integrations: [],
|
|
265
|
+
publishes: undefined,
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
mockLoadModuleExports
|
|
269
|
+
.mockResolvedValueOnce([
|
|
270
|
+
{ _kind: 'domain', _exportName: 'default', id: 'billing', name: 'Billing', tenancy: 'required' },
|
|
271
|
+
])
|
|
272
|
+
.mockResolvedValueOnce([
|
|
273
|
+
{
|
|
274
|
+
_kind: 'action',
|
|
275
|
+
_exportName: 'internalOnly',
|
|
276
|
+
id: 'internal-only',
|
|
277
|
+
backendAccess: 'private',
|
|
278
|
+
exposure: { type: 'internal' },
|
|
279
|
+
idempotent: false,
|
|
280
|
+
},
|
|
281
|
+
]);
|
|
282
|
+
|
|
283
|
+
const { registry } = await buildRegistry(DOMAIN_ROOT);
|
|
284
|
+
expect(registry.actions).toHaveLength(1);
|
|
285
|
+
expect(registry.actions[0]!.exposure).toEqual({ type: 'internal' });
|
|
286
|
+
});
|
|
165
287
|
});
|