@tdk-landscape/tdk-cli-core 1.3.107 → 1.3.109
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 +1 -1
- package/cli/dist/commands/__tests__/ui-loading-screen.test.js +16 -0
- package/cli/dist/commands/networks.js +9 -2
- package/cli/dist/commands/resources.js +8 -1
- package/cli/dist/commands/ui.js +15 -5
- package/cli/dist/commands/up.js +54 -5
- package/cli/dist/generator/template-engine.js +15 -6
- package/cli/dist/utils/services.js +3 -3
- package/cli/package.json +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/@tdk-landscape/tdk-cli-core)
|
|
4
4
|
[](https://packagephobia.com/result?p=@tdk-landscape/tdk-cli-core)
|
|
5
|
-
[](https://packagephobia.com/result?p=@tdk-landscape/tdk-cli-core)
|
|
6
6
|
[](https://github.com/tdk-landscape/tdk-cli-core/actions/workflows/ci.yml)
|
|
7
7
|
[](https://github.com/tdk-landscape/tdk-cli-core/actions/workflows/quickstart-e2e.yml)
|
|
8
8
|
[](https://github.com/tdk-landscape/tdk-cli-core/actions/workflows/erp-scale-e2e.yml)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { renderToString } from "ink";
|
|
3
|
+
import { describe, expect, it } from "vitest";
|
|
4
|
+
import { LoadingScreen } from "../ui.js";
|
|
5
|
+
describe("ui LoadingScreen", () => {
|
|
6
|
+
it("shows honest text with no percentage", () => {
|
|
7
|
+
const out = renderToString(_jsx(LoadingScreen, { animated: false }));
|
|
8
|
+
expect(out).toContain("Discovering resources...");
|
|
9
|
+
expect(out).not.toContain("%");
|
|
10
|
+
});
|
|
11
|
+
it("renders a spinner frame only when animated", () => {
|
|
12
|
+
expect(renderToString(_jsx(LoadingScreen, { animated: true }))).toMatch(/[⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏] Discovering/);
|
|
13
|
+
expect(renderToString(_jsx(LoadingScreen, { animated: false }))).not.toMatch(/[⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏]/);
|
|
14
|
+
});
|
|
15
|
+
});
|
|
16
|
+
//# sourceMappingURL=ui-loading-screen.test.js.map
|
|
@@ -4,7 +4,7 @@ import { Command } from "commander";
|
|
|
4
4
|
import { readProjectConfig } from "../generator/template-engine.js";
|
|
5
5
|
import { getStackEmoji } from "../utils/constants.js";
|
|
6
6
|
import { createDiscoveryContext } from "../utils/discovery-context.js";
|
|
7
|
-
import { logVerbose, requireProjectRoot } from "../utils/errors.js";
|
|
7
|
+
import { errorFactories, logVerbose, requireProjectRoot } from "../utils/errors.js";
|
|
8
8
|
import { colorizeByStatus, DEFAULT_BOX_WIDTH, formatBoxLine, formatPadded, getStatusIcon, printBoxedHeader, } from "../utils/formatting.js";
|
|
9
9
|
import { readSavedHostPortPlan } from "../utils/host-port-config.js";
|
|
10
10
|
import { createHostPortPlan } from "../utils/host-port-plan.js";
|
|
@@ -12,6 +12,7 @@ import { createMachineEnvelope, writeMachineError } from "../utils/machine-outpu
|
|
|
12
12
|
import { findProjectRoot } from "../utils/paths.js";
|
|
13
13
|
import { checkPortStatus } from "../utils/port-assignment.js";
|
|
14
14
|
import { isApiServiceType } from "../utils/resource-kind.js";
|
|
15
|
+
import { stackExists } from "../utils/services.js";
|
|
15
16
|
import { isValidPort, sanitizeForShell } from "../utils/validation.js";
|
|
16
17
|
import { findOnPath } from "../utils/which.js";
|
|
17
18
|
function execSafe(command, args, options = {}) {
|
|
@@ -169,10 +170,16 @@ export const networksCommand = new Command("networks")
|
|
|
169
170
|
if (!projectRoot) {
|
|
170
171
|
throw new Error("Could not find project root (no .tdk/project.json found)");
|
|
171
172
|
}
|
|
173
|
+
const discovery = createDiscoveryContext();
|
|
174
|
+
if (options.stack && !stackExists(options.stack, discovery.resources)) {
|
|
175
|
+
const error = errorFactories.stackNotFound(options.stack);
|
|
176
|
+
if (options.json || options.jsonLegacy)
|
|
177
|
+
throw error;
|
|
178
|
+
error.exit();
|
|
179
|
+
}
|
|
172
180
|
const savedPlan = readSavedHostPortPlan(projectRoot);
|
|
173
181
|
const hostPortPlan = savedPlan ?? (await createHostPortPlan());
|
|
174
182
|
const httpPort = hostPortPlan.ingressHttp;
|
|
175
|
-
const discovery = createDiscoveryContext();
|
|
176
183
|
const baseDomain = determineDefaultDomain();
|
|
177
184
|
const bareDomain = baseDomain.replace(/^(app|api)\./, "");
|
|
178
185
|
const appDomain = `app.${bareDomain}`;
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import chalk from "chalk";
|
|
2
2
|
import { Command } from "commander";
|
|
3
3
|
import { createDiscoveryContext } from "../utils/discovery-context.js";
|
|
4
|
-
import { requireProjectRoot, runCommand } from "../utils/errors.js";
|
|
4
|
+
import { errorFactories, requireProjectRoot, runCommand } from "../utils/errors.js";
|
|
5
5
|
import { formatCount, showAllSatisfyCondition, showDetail, showEmptyState, showStep, } from "../utils/formatting.js";
|
|
6
6
|
import { createMachineEnvelope, writeMachineError } from "../utils/machine-output.js";
|
|
7
7
|
import { findProjectRoot } from "../utils/paths.js";
|
|
8
|
+
import { stackExists } from "../utils/services.js";
|
|
8
9
|
export const resourcesCommand = new Command("resources")
|
|
9
10
|
.description("List all resources (services) in the project")
|
|
10
11
|
.option("-v, --verbose", "Show detailed information about each resource", false)
|
|
@@ -23,6 +24,12 @@ export const resourcesCommand = new Command("resources")
|
|
|
23
24
|
requireProjectRoot();
|
|
24
25
|
}
|
|
25
26
|
const discovery = createDiscoveryContext();
|
|
27
|
+
if (options.stack && !stackExists(options.stack, discovery.resources)) {
|
|
28
|
+
const error = errorFactories.stackNotFound(options.stack);
|
|
29
|
+
if (options.json)
|
|
30
|
+
writeMachineError(error);
|
|
31
|
+
error.exit();
|
|
32
|
+
}
|
|
26
33
|
const withoutStackFilter = options.noStack || options.stack === false;
|
|
27
34
|
let resources = discovery.resources;
|
|
28
35
|
if (options.stack) {
|
package/cli/dist/commands/ui.js
CHANGED
|
@@ -10,11 +10,21 @@ import { createStatusMessageController } from "../utils/status-message.js";
|
|
|
10
10
|
import { isTiltAvailable } from "../utils/tilt.js";
|
|
11
11
|
// biome-ignore lint/correctness/noUnusedFunctionParameters: reserved callback prop kept in the component API
|
|
12
12
|
const HelpPanel = ({ onClose }) => (_jsxs(Box, { borderStyle: "single", borderColor: "cyan", paddingX: 2, paddingY: 1, flexDirection: "column", width: 60, children: [_jsx(Text, { bold: true, color: "cyan", children: "Keyboard Shortcuts" }), _jsxs(Box, { marginY: 1, flexDirection: "column", children: [_jsx(Text, { bold: true, underline: true, children: "Navigation" }), _jsx(Text, { children: " \u2191/\u2193 Navigate list items" }), _jsx(Text, { children: " Enter Select item / Open detail" }), _jsx(Text, { children: " Space Toggle expand (tree view)" }), _jsx(Text, { children: " Tab Next tab" }), _jsx(Text, { children: " 1-5 Direct tab access" }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { bold: true, underline: true, children: "Actions" }) }), _jsx(Text, { children: " m Toggle mouse support" }), _jsx(Text, { children: " t Toggle tooltips" }), _jsx(Text, { children: " e Toggle enabled/disabled services" }), _jsx(Text, { children: " r Refresh data" }), _jsx(Text, { children: " / Search/filter" }), _jsx(Text, { children: " ? Show this help" }), _jsx(Text, { children: " q Quit \u2502 Esc Back" })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "gray", dimColor: true, children: "Press any key to close..." }) })] }));
|
|
13
|
-
const
|
|
13
|
+
const SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
14
|
+
export const LoadingScreen = ({ message = "Discovering resources...", animated = true, }) => {
|
|
15
|
+
const [frame, setFrame] = useState(0);
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
if (!animated)
|
|
18
|
+
return;
|
|
19
|
+
const timer = setInterval(() => setFrame((f) => (f + 1) % SPINNER_FRAMES.length), 80);
|
|
20
|
+
return () => clearInterval(timer);
|
|
21
|
+
}, [animated]);
|
|
22
|
+
return (_jsxs(Box, { flexDirection: "column", padding: 2, children: [_jsx(Text, { bold: true, color: "cyan", children: "\u2593\u2592\u2591 TDK NEON EDITION \u2591\u2592\u2593" }), _jsx(Box, { marginY: 1 }), _jsxs(Text, { children: [animated && _jsxs(Text, { color: "cyan", children: [SPINNER_FRAMES[frame], " "] }), message] })] }));
|
|
23
|
+
};
|
|
14
24
|
// biome-ignore lint/correctness/noUnusedFunctionParameters: reserved callback prop kept in the component API
|
|
15
25
|
const ErrorScreen = ({ error, onRetry }) => (_jsxs(Box, { flexDirection: "column", padding: 2, alignItems: "center", children: [_jsx(Text, { bold: true, color: "red", children: "Could Not Load Services" }), _jsx(Box, { marginY: 1 }), _jsxs(Text, { color: "red", children: ["\u2717 ", error] }), _jsx(Box, { marginY: 1 }), _jsx(Text, { color: "gray", children: "Troubleshooting:" }), _jsx(Text, { color: "gray", children: " 1. Check that every service.json is valid JSON" }), _jsx(Text, { color: "gray", children: " 2. Run tdk from your project (the folder with the Tiltfile)" }), _jsx(Text, { color: "gray", children: " 3. Try: tdk status --verbose" }), _jsx(Box, { marginY: 1 }), _jsx(Text, { color: "cyan", children: "Press [r] to retry or [q] to quit" })] }));
|
|
16
26
|
const EmptyState = ({ message }) => (_jsxs(Box, { flexDirection: "column", padding: 2, alignItems: "center", children: [_jsx(Text, { bold: true, color: "yellow", children: "No Services Found" }), _jsx(Box, { marginY: 1 }), _jsx(Text, { color: "gray", children: "\u25C9 No service.json files found" }), _jsx(Box, { marginY: 1 }), _jsx(Text, { children: "To get started:" }), _jsx(Text, { children: " 1. Run: tdk project" }), _jsx(Text, { children: " 2. Run: tdk resource api --type backend" }), _jsx(Box, { marginY: 1 }), _jsx(Text, { color: "cyan", children: "Press [r] to refresh or [q] to quit" }), message && (_jsx(Box, { paddingX: 1, height: 1, children: _jsxs(Text, { color: "cyan", children: ["\u2593\u2592\u2591 ", message, " \u2591\u2592\u2593"] }) }))] }));
|
|
17
|
-
const TUIApp = () => {
|
|
27
|
+
const TUIApp = ({ animated = true }) => {
|
|
18
28
|
const { exit } = useApp();
|
|
19
29
|
const { stdout } = useStdout();
|
|
20
30
|
const { stdin, setRawMode } = useStdin();
|
|
@@ -401,7 +411,7 @@ const TUIApp = () => {
|
|
|
401
411
|
const compactTabBar = terminalWidth < 100;
|
|
402
412
|
const compact = terminalWidth < 80;
|
|
403
413
|
if (loading) {
|
|
404
|
-
return _jsx(LoadingScreen, {
|
|
414
|
+
return _jsx(LoadingScreen, { message: "Discovering resources...", animated: animated });
|
|
405
415
|
}
|
|
406
416
|
if (error) {
|
|
407
417
|
return _jsx(ErrorScreen, { error: error, onRetry: refresh });
|
|
@@ -431,12 +441,12 @@ export const uiCommand = new Command("ui")
|
|
|
431
441
|
.option("-v, --verbose", "Enable verbose output", false)
|
|
432
442
|
.option("--no-animations", "Disable animations")
|
|
433
443
|
.option("--high-contrast", "Enable high contrast mode")
|
|
434
|
-
.action(async () => {
|
|
444
|
+
.action(async (options) => {
|
|
435
445
|
const tiltAvailable = await isTiltAvailable();
|
|
436
446
|
if (!tiltAvailable) {
|
|
437
447
|
errorFactories.tiltNotInstalled().exit();
|
|
438
448
|
}
|
|
439
449
|
requireProjectRoot();
|
|
440
|
-
render(_jsx(TUIApp, {}));
|
|
450
|
+
render(_jsx(TUIApp, { animated: options.animations }));
|
|
441
451
|
});
|
|
442
452
|
//# sourceMappingURL=ui.js.map
|
package/cli/dist/commands/up.js
CHANGED
|
@@ -2,7 +2,7 @@ import { connect } from "node:net";
|
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import chalk from "chalk";
|
|
4
4
|
import { Command } from "commander";
|
|
5
|
-
import { ensureProjectRuntimeAssets } from "../generator/template-engine.js";
|
|
5
|
+
import { ensureProjectRuntimeAssets, ProjectConfigNotFoundError, verifyMasterConfigs, } from "../generator/template-engine.js";
|
|
6
6
|
import { DEVCONTAINER_DOCKER_FIX, detectHost, isContainerHost, WEBCONTAINER_UP_MESSAGE, } from "../utils/agent-host.js";
|
|
7
7
|
import { handleDryRun } from "../utils/command-helpers.js";
|
|
8
8
|
import { getDeferredResourceNames } from "../utils/doctor-runtime.js";
|
|
@@ -54,6 +54,48 @@ async function defaultDockerReachable() {
|
|
|
54
54
|
execFile("docker", ["ps"], { timeout: 10_000 }, (error) => resolve(!error));
|
|
55
55
|
});
|
|
56
56
|
}
|
|
57
|
+
/** Exit status for `tdk up` when generated files no longer match service.json / project.json. */
|
|
58
|
+
export const DRIFT_EXIT_CODE = 2;
|
|
59
|
+
/**
|
|
60
|
+
* Generated service files edited by hand. Only these block `tdk up`: when service.json itself changed, `tdk up` regenerates
|
|
61
|
+
* the outputs, so stale outputs are the normal edit-then-up loop and not drift.
|
|
62
|
+
*/
|
|
63
|
+
export function driftReport(projectRoot) {
|
|
64
|
+
let handEdited;
|
|
65
|
+
try {
|
|
66
|
+
({ handEdited } = verifyMasterConfigs(projectRoot));
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
// A project with no .tdk/project.json has no snapshots to compare. Anything else (a corrupt config) must not skip the gate.
|
|
70
|
+
if (error instanceof ProjectConfigNotFoundError)
|
|
71
|
+
return null;
|
|
72
|
+
throw error;
|
|
73
|
+
}
|
|
74
|
+
if (handEdited.length === 0)
|
|
75
|
+
return null;
|
|
76
|
+
return [
|
|
77
|
+
"Generated files were edited by hand and no longer match service.json:",
|
|
78
|
+
...handEdited.map((file) => ` ${file}`),
|
|
79
|
+
"Run `tdk config regenerate` to discard the edits, or bypass with `tdk up --ignore-drift`.",
|
|
80
|
+
];
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Runs before Tilt is started: exits with DRIFT_EXIT_CODE on drift unless `ignoreDrift` is set. With `ignoreDrift` the
|
|
84
|
+
* warning is printed every time, whether or not anything drifted, because the check is skipped and cannot tell.
|
|
85
|
+
*/
|
|
86
|
+
export function enforceDriftGate(projectRoot, options, exit = process.exit) {
|
|
87
|
+
if (options.ignoreDrift) {
|
|
88
|
+
console.warn(chalk.yellow("Warning: --ignore-drift set; generated files were not checked."));
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
const drift = driftReport(projectRoot);
|
|
92
|
+
if (!drift)
|
|
93
|
+
return;
|
|
94
|
+
options.onDrift?.(drift.join("\n"));
|
|
95
|
+
for (const line of drift)
|
|
96
|
+
console.error(chalk.red(line));
|
|
97
|
+
exit(DRIFT_EXIT_CODE);
|
|
98
|
+
}
|
|
57
99
|
function waitForTiltUi(port, timeoutMs = 30_000) {
|
|
58
100
|
return new Promise((resolve) => {
|
|
59
101
|
const deadline = Date.now() + timeoutMs;
|
|
@@ -84,6 +126,7 @@ export const upCommand = new Command("up")
|
|
|
84
126
|
.option("-q, --quiet", "Suppress non-essential output", false)
|
|
85
127
|
.option("--dry-run", "Show what would be started without starting", false)
|
|
86
128
|
.option("-f, --force", "Kill existing Tilt process before starting", false)
|
|
129
|
+
.option("--ignore-drift", "Start even if generated files differ from service.json", false)
|
|
87
130
|
.option("--only <services...>", "Start only these services plus what the Tiltfile enables for them (their dependsOn services and shared infrastructure). With a stack, names must belong to that stack; dependencies may cross stacks")
|
|
88
131
|
.option("--json", "Print one JSON object on stdout when the stack is ready or the command fails; implies --quiet", false)
|
|
89
132
|
.action(async (stackName, options) => {
|
|
@@ -109,9 +152,8 @@ export const upCommand = new Command("up")
|
|
|
109
152
|
await assertMachineReadyOrExit();
|
|
110
153
|
}
|
|
111
154
|
const action = async () => {
|
|
112
|
-
const
|
|
113
|
-
|
|
114
|
-
: (findProjectRoot() ?? process.cwd());
|
|
155
|
+
const foundRoot = options.dryRun ? requireProjectRoot() : findProjectRoot();
|
|
156
|
+
const projectRoot = foundRoot ?? process.cwd();
|
|
115
157
|
const discoveredResources = discoverResourcesStrict();
|
|
116
158
|
// Reject a bad request before anything below can write to the project (.env, runtime assets, .tdk/project.json).
|
|
117
159
|
if (options.only) {
|
|
@@ -127,13 +169,20 @@ export const upCommand = new Command("up")
|
|
|
127
169
|
showErrorAndExit(message, 2);
|
|
128
170
|
}
|
|
129
171
|
}
|
|
172
|
+
// Also under --dry-run: the check only reads, and a dry run should show what a real run would refuse.
|
|
173
|
+
if (foundRoot) {
|
|
174
|
+
enforceDriftGate(foundRoot, {
|
|
175
|
+
ignoreDrift: options.ignoreDrift,
|
|
176
|
+
onDrift: (message) => emit?.({ ok: false }, [{ code: "DRIFT_DETECTED", message }]),
|
|
177
|
+
});
|
|
178
|
+
}
|
|
130
179
|
const hostPortPlan = await getHostPortPlan(projectRoot, {
|
|
131
180
|
inspectDocker: !options.dryRun,
|
|
132
181
|
});
|
|
133
182
|
if (!options.dryRun) {
|
|
134
183
|
// An older project's .env predates keys such as JWT_SECRET, which Compose now requires. Add what is missing (never
|
|
135
184
|
// changing an existing value) before anything starts. Only inside a real project, so a stray run never writes a .env.
|
|
136
|
-
if (
|
|
185
|
+
if (foundRoot) {
|
|
137
186
|
const addedEnvKeys = completeEnvFile(projectRoot);
|
|
138
187
|
if (addedEnvKeys.length > 0 && !options.quiet) {
|
|
139
188
|
console.log(chalk.gray(`Added ${addedEnvKeys.join(", ")} to .env (generated; existing values unchanged)`));
|
|
@@ -349,10 +349,13 @@ function normalizeProjectConfig(value) {
|
|
|
349
349
|
overrides: config.overrides,
|
|
350
350
|
};
|
|
351
351
|
}
|
|
352
|
+
/** The project has no .tdk/project.json yet, as opposed to one that exists but is unreadable or invalid. */
|
|
353
|
+
export class ProjectConfigNotFoundError extends Error {
|
|
354
|
+
}
|
|
352
355
|
export function readProjectConfig(projectRoot) {
|
|
353
356
|
const projectJsonPath = path.join(projectRoot, ".tdk", "project.json");
|
|
354
357
|
if (!fs.existsSync(projectJsonPath)) {
|
|
355
|
-
throw new
|
|
358
|
+
throw new ProjectConfigNotFoundError(`Project config not found: ${projectJsonPath}. Run 'tdk project --yes' first.`);
|
|
356
359
|
}
|
|
357
360
|
const jsonContent = fs.readFileSync(projectJsonPath, "utf-8");
|
|
358
361
|
const parsed = JSON.parse(jsonContent);
|
|
@@ -604,6 +607,7 @@ export function verifyMasterConfigs(projectRoot) {
|
|
|
604
607
|
const errors = [];
|
|
605
608
|
const warnings = [];
|
|
606
609
|
const diffs = [];
|
|
610
|
+
const handEdited = [];
|
|
607
611
|
const addDrift = (displayPath, actual, expected) => {
|
|
608
612
|
if (actual === expected)
|
|
609
613
|
return;
|
|
@@ -629,9 +633,11 @@ export function verifyMasterConfigs(projectRoot) {
|
|
|
629
633
|
const validation = validateServiceManifestFile(manifestPath, displayPath);
|
|
630
634
|
errors.push(...validation.errors);
|
|
631
635
|
warnings.push(...validation.warnings);
|
|
632
|
-
|
|
636
|
+
const generated = verifyGeneratedResourceFiles(projectRoot, manifestPath);
|
|
637
|
+
errors.push(...generated.errors);
|
|
638
|
+
handEdited.push(...generated.handEdited);
|
|
633
639
|
}
|
|
634
|
-
return { valid: errors.length === 0, errors, warnings, diffs };
|
|
640
|
+
return { valid: errors.length === 0, errors, warnings, diffs, handEdited };
|
|
635
641
|
}
|
|
636
642
|
function createUnifiedDiff(file, actual, expected) {
|
|
637
643
|
const oldLines = actual.replace(/\n$/, "").split("\n");
|
|
@@ -651,7 +657,7 @@ function verifyGeneratedResourceFiles(projectRoot, serviceJsonPath) {
|
|
|
651
657
|
const serviceDir = path.dirname(serviceJsonPath);
|
|
652
658
|
const generatedDir = path.join(serviceDir, ".autogenerated");
|
|
653
659
|
if (!fs.existsSync(generatedDir))
|
|
654
|
-
return [];
|
|
660
|
+
return { errors: [], handEdited: [] };
|
|
655
661
|
const generatedFiles = [];
|
|
656
662
|
const collect = (directory, relativeDir = "") => {
|
|
657
663
|
for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
|
|
@@ -667,10 +673,11 @@ function verifyGeneratedResourceFiles(projectRoot, serviceJsonPath) {
|
|
|
667
673
|
};
|
|
668
674
|
collect(generatedDir);
|
|
669
675
|
if (generatedFiles.length === 0)
|
|
670
|
-
return [];
|
|
676
|
+
return { errors: [], handEdited: [] };
|
|
671
677
|
const contractDir = path.join(generatedDir, ".tdk-contract");
|
|
672
678
|
const sourceSnapshot = path.join(contractDir, "service.json.source");
|
|
673
679
|
const errors = [];
|
|
680
|
+
const handEdited = [];
|
|
674
681
|
if (!fs.existsSync(sourceSnapshot)) {
|
|
675
682
|
errors.push(path.relative(projectRoot, sourceSnapshot) +
|
|
676
683
|
": generated-file contract snapshot is missing (run tdk up to regenerate)");
|
|
@@ -691,9 +698,11 @@ function verifyGeneratedResourceFiles(projectRoot, serviceJsonPath) {
|
|
|
691
698
|
errors.push(`${display}: generated-file snapshot is missing (run tdk up to regenerate)`);
|
|
692
699
|
}
|
|
693
700
|
else if (fs.readFileSync(generated.fullPath, "utf-8") !== fs.readFileSync(snapshotPath, "utf-8")) {
|
|
701
|
+
// Windows path.relative uses backslashes; report project-relative paths with forward slashes everywhere.
|
|
702
|
+
handEdited.push(display.split(path.sep).join("/"));
|
|
694
703
|
errors.push(`${display}: generated file differs from its TDK snapshot (run tdk up to regenerate)`);
|
|
695
704
|
}
|
|
696
705
|
}
|
|
697
|
-
return errors;
|
|
706
|
+
return { errors, handEdited };
|
|
698
707
|
}
|
|
699
708
|
//# sourceMappingURL=template-engine.js.map
|
|
@@ -204,9 +204,9 @@ export function getResourcesForStack(stackName) {
|
|
|
204
204
|
const allResources = discoverResources();
|
|
205
205
|
return allResources.filter((r) => r.stack === stackName);
|
|
206
206
|
}
|
|
207
|
-
export function stackExists(stackName) {
|
|
208
|
-
const
|
|
209
|
-
return
|
|
207
|
+
export function stackExists(stackName, resources) {
|
|
208
|
+
const resourcesToSearch = resources ?? discoverResources();
|
|
209
|
+
return resourcesToSearch.some((resource) => resource.stack === stackName);
|
|
210
210
|
}
|
|
211
211
|
/**
|
|
212
212
|
* Cache TTL in milliseconds
|
package/cli/package.json
CHANGED