@rawsql-ts/ztd-cli 0.20.0 → 0.20.3
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 +21 -0
- package/dist/commands/agents.d.ts +2 -0
- package/dist/commands/agents.js +68 -0
- package/dist/commands/agents.js.map +1 -0
- package/dist/commands/checkContract.d.ts +46 -0
- package/dist/commands/checkContract.js +359 -0
- package/dist/commands/checkContract.js.map +1 -0
- package/dist/commands/connectionOptions.d.ts +12 -0
- package/dist/commands/connectionOptions.js +22 -0
- package/dist/commands/connectionOptions.js.map +1 -0
- package/dist/commands/ddl.d.ts +7 -0
- package/dist/commands/ddl.js +145 -0
- package/dist/commands/ddl.js.map +1 -0
- package/dist/commands/describe.d.ts +23 -0
- package/dist/commands/describe.js +399 -0
- package/dist/commands/describe.js.map +1 -0
- package/dist/commands/diff.d.ts +24 -0
- package/dist/commands/diff.js +73 -0
- package/dist/commands/diff.js.map +1 -0
- package/dist/commands/genEntities.d.ts +14 -0
- package/dist/commands/genEntities.js +58 -0
- package/dist/commands/genEntities.js.map +1 -0
- package/dist/commands/init.d.ts +105 -0
- package/dist/commands/init.js +1508 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/lint.d.ts +89 -0
- package/dist/commands/lint.js +501 -0
- package/dist/commands/lint.js.map +1 -0
- package/dist/commands/modelGen.d.ts +60 -0
- package/dist/commands/modelGen.js +572 -0
- package/dist/commands/modelGen.js.map +1 -0
- package/dist/commands/options.d.ts +9 -0
- package/dist/commands/options.js +48 -0
- package/dist/commands/options.js.map +1 -0
- package/dist/commands/perf.d.ts +9 -0
- package/dist/commands/perf.js +374 -0
- package/dist/commands/perf.js.map +1 -0
- package/dist/commands/pull.d.ts +21 -0
- package/dist/commands/pull.js +115 -0
- package/dist/commands/pull.js.map +1 -0
- package/dist/commands/query.d.ts +9 -0
- package/dist/commands/query.js +377 -0
- package/dist/commands/query.js.map +1 -0
- package/dist/commands/testEvidence.d.ts +237 -0
- package/dist/commands/testEvidence.js +1220 -0
- package/dist/commands/testEvidence.js.map +1 -0
- package/dist/commands/ztdConfig.d.ts +30 -0
- package/dist/commands/ztdConfig.js +224 -0
- package/dist/commands/ztdConfig.js.map +1 -0
- package/dist/commands/ztdConfigCommand.d.ts +18 -0
- package/dist/commands/ztdConfigCommand.js +268 -0
- package/dist/commands/ztdConfigCommand.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +128 -0
- package/dist/index.js.map +1 -0
- package/dist/perf/benchmark.d.ts +277 -0
- package/dist/perf/benchmark.js +2186 -0
- package/dist/perf/benchmark.js.map +1 -0
- package/dist/perf/sandbox.d.ts +73 -0
- package/dist/perf/sandbox.js +492 -0
- package/dist/perf/sandbox.js.map +1 -0
- package/dist/query/analysis.d.ts +20 -0
- package/dist/query/analysis.js +192 -0
- package/dist/query/analysis.js.map +1 -0
- package/dist/query/analyzeColumnUsage.d.ts +10 -0
- package/dist/query/analyzeColumnUsage.js +451 -0
- package/dist/query/analyzeColumnUsage.js.map +1 -0
- package/dist/query/analyzeTableUsage.d.ts +10 -0
- package/dist/query/analyzeTableUsage.js +318 -0
- package/dist/query/analyzeTableUsage.js.map +1 -0
- package/dist/query/execute.d.ts +40 -0
- package/dist/query/execute.js +784 -0
- package/dist/query/execute.js.map +1 -0
- package/dist/query/format.d.ts +1 -0
- package/dist/query/format.js +9 -0
- package/dist/query/format.js.map +1 -0
- package/dist/query/lint.d.ts +29 -0
- package/dist/query/lint.js +340 -0
- package/dist/query/lint.js.map +1 -0
- package/dist/query/location.d.ts +18 -0
- package/dist/query/location.js +204 -0
- package/dist/query/location.js.map +1 -0
- package/dist/query/patch.d.ts +21 -0
- package/dist/query/patch.js +151 -0
- package/dist/query/patch.js.map +1 -0
- package/dist/query/planner.d.ts +31 -0
- package/dist/query/planner.js +134 -0
- package/dist/query/planner.js.map +1 -0
- package/dist/query/report.d.ts +7 -0
- package/dist/query/report.js +19 -0
- package/dist/query/report.js.map +1 -0
- package/dist/query/scalarFilterAnalysis.d.ts +6 -0
- package/dist/query/scalarFilterAnalysis.js +212 -0
- package/dist/query/scalarFilterAnalysis.js.map +1 -0
- package/dist/query/slice.d.ts +17 -0
- package/dist/query/slice.js +204 -0
- package/dist/query/slice.js.map +1 -0
- package/dist/query/structure.d.ts +24 -0
- package/dist/query/structure.js +135 -0
- package/dist/query/structure.js.map +1 -0
- package/dist/query/targets.d.ts +2 -0
- package/dist/query/targets.js +6 -0
- package/dist/query/targets.js.map +1 -0
- package/dist/query/types.d.ts +97 -0
- package/dist/query/types.js +3 -0
- package/dist/query/types.js.map +1 -0
- package/dist/specs/sql/activeOrders.catalog.d.ts +12 -0
- package/dist/specs/sql/activeOrders.catalog.js +36 -0
- package/dist/specs/sql/activeOrders.catalog.js.map +1 -0
- package/dist/specs/sql/usersList.catalog.d.ts +8 -0
- package/dist/specs/sql/usersList.catalog.js +14 -0
- package/dist/specs/sql/usersList.catalog.js.map +1 -0
- package/dist/specs/sqlCatalogDefinition.d.ts +20 -0
- package/dist/specs/sqlCatalogDefinition.js +10 -0
- package/dist/specs/sqlCatalogDefinition.js.map +1 -0
- package/dist/utils/agentCli.d.ts +23 -0
- package/dist/utils/agentCli.js +84 -0
- package/dist/utils/agentCli.js.map +1 -0
- package/dist/utils/agentSafety.d.ts +4 -0
- package/dist/utils/agentSafety.js +50 -0
- package/dist/utils/agentSafety.js.map +1 -0
- package/dist/utils/agents.d.ts +31 -0
- package/dist/utils/agents.js +362 -0
- package/dist/utils/agents.js.map +1 -0
- package/dist/utils/collectSqlFiles.d.ts +9 -0
- package/dist/utils/collectSqlFiles.js +58 -0
- package/dist/utils/collectSqlFiles.js.map +1 -0
- package/dist/utils/connectionSummary.d.ts +3 -0
- package/dist/utils/connectionSummary.js +29 -0
- package/dist/utils/connectionSummary.js.map +1 -0
- package/dist/utils/dbConnection.d.ts +31 -0
- package/dist/utils/dbConnection.js +151 -0
- package/dist/utils/dbConnection.js.map +1 -0
- package/dist/utils/fs.d.ts +1 -0
- package/dist/utils/fs.js +12 -0
- package/dist/utils/fs.js.map +1 -0
- package/dist/utils/modelGenBinder.d.ts +8 -0
- package/dist/utils/modelGenBinder.js +31 -0
- package/dist/utils/modelGenBinder.js.map +1 -0
- package/dist/utils/modelGenRender.d.ts +29 -0
- package/dist/utils/modelGenRender.js +158 -0
- package/dist/utils/modelGenRender.js.map +1 -0
- package/dist/utils/modelGenScanner.d.ts +24 -0
- package/dist/utils/modelGenScanner.js +196 -0
- package/dist/utils/modelGenScanner.js.map +1 -0
- package/dist/utils/modelProbe.d.ts +14 -0
- package/dist/utils/modelProbe.js +121 -0
- package/dist/utils/modelProbe.js.map +1 -0
- package/dist/utils/normalizePulledSchema.d.ts +12 -0
- package/dist/utils/normalizePulledSchema.js +213 -0
- package/dist/utils/normalizePulledSchema.js.map +1 -0
- package/dist/utils/optionalDependencies.d.ts +45 -0
- package/dist/utils/optionalDependencies.js +153 -0
- package/dist/utils/optionalDependencies.js.map +1 -0
- package/dist/utils/pgDump.d.ts +12 -0
- package/dist/utils/pgDump.js +58 -0
- package/dist/utils/pgDump.js.map +1 -0
- package/dist/utils/queryFingerprint.d.ts +14 -0
- package/dist/utils/queryFingerprint.js +34 -0
- package/dist/utils/queryFingerprint.js.map +1 -0
- package/dist/utils/sqlCatalogDiscovery.d.ts +44 -0
- package/dist/utils/sqlCatalogDiscovery.js +166 -0
- package/dist/utils/sqlCatalogDiscovery.js.map +1 -0
- package/dist/utils/sqlCatalogStatements.d.ts +20 -0
- package/dist/utils/sqlCatalogStatements.js +23 -0
- package/dist/utils/sqlCatalogStatements.js.map +1 -0
- package/dist/utils/sqlLintHelpers.d.ts +18 -0
- package/dist/utils/sqlLintHelpers.js +270 -0
- package/dist/utils/sqlLintHelpers.js.map +1 -0
- package/dist/utils/telemetry.d.ts +71 -0
- package/dist/utils/telemetry.js +597 -0
- package/dist/utils/telemetry.js.map +1 -0
- package/dist/utils/typeMapper.d.ts +4 -0
- package/dist/utils/typeMapper.js +79 -0
- package/dist/utils/typeMapper.js.map +1 -0
- package/dist/utils/ztdProjectConfig.d.ts +41 -0
- package/dist/utils/ztdProjectConfig.js +182 -0
- package/dist/utils/ztdProjectConfig.js.map +1 -0
- package/package.json +19 -20
- package/templates/src/catalog/runtime/_smoke.runtime.ts +2 -2
- package/templates/src/db/sql-client-adapters.ts +1 -1
- package/templates/src/infrastructure/db/sql-client-adapters.ts +1 -1
- package/templates/src/infrastructure/telemetry/consoleRepositoryTelemetry.ts +1 -1
- package/templates/src/infrastructure/telemetry/repositoryTelemetry.ts +4 -4
- package/templates/tests/smoke.test.ts +2 -2
- package/templates/tests/smoke.validation.test.ts +1 -1
- package/templates/tests/support/testkit-client.ts +1 -1
- package/templates/tests/support/testkit-client.webapi.ts +1 -1
|
@@ -0,0 +1,1508 @@
|
|
|
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.createConsolePrompter = createConsolePrompter;
|
|
7
|
+
exports.runInitCommand = runInitCommand;
|
|
8
|
+
exports.resolvePackageManagerShellExecutable = resolvePackageManagerShellExecutable;
|
|
9
|
+
exports.findAncestorPnpmWorkspaceRoot = findAncestorPnpmWorkspaceRoot;
|
|
10
|
+
exports.resolvePnpmWorkspaceGuard = resolvePnpmWorkspaceGuard;
|
|
11
|
+
exports.resolveInitInstallStrategy = resolveInitInstallStrategy;
|
|
12
|
+
exports.buildPackageManagerArgs = buildPackageManagerArgs;
|
|
13
|
+
exports.normalizeSchemaName = normalizeSchemaName;
|
|
14
|
+
exports.sanitizeSchemaFileName = sanitizeSchemaFileName;
|
|
15
|
+
exports.registerInitCommand = registerInitCommand;
|
|
16
|
+
const node_child_process_1 = require("node:child_process");
|
|
17
|
+
const node_fs_1 = require("node:fs");
|
|
18
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
19
|
+
const promises_1 = __importDefault(require("node:readline/promises"));
|
|
20
|
+
const fs_1 = require("../utils/fs");
|
|
21
|
+
const agents_1 = require("../utils/agents");
|
|
22
|
+
const ztdProjectConfig_1 = require("../utils/ztdProjectConfig");
|
|
23
|
+
const ztdConfig_1 = require("./ztdConfig");
|
|
24
|
+
const pull_1 = require("./pull");
|
|
25
|
+
const agentCli_1 = require("../utils/agentCli");
|
|
26
|
+
const agentSafety_1 = require("../utils/agentSafety");
|
|
27
|
+
/**
|
|
28
|
+
* Create a readline-backed prompter that reads from stdin/stdout.
|
|
29
|
+
*/
|
|
30
|
+
function createConsolePrompter() {
|
|
31
|
+
const rl = promises_1.default.createInterface({
|
|
32
|
+
input: process.stdin,
|
|
33
|
+
output: process.stdout,
|
|
34
|
+
terminal: process.stdout.isTTY
|
|
35
|
+
});
|
|
36
|
+
async function requestLine(question) {
|
|
37
|
+
return (await rl.question(question)).trim();
|
|
38
|
+
}
|
|
39
|
+
async function requestLineWithDefault(question, defaultValue, example) {
|
|
40
|
+
const prompt = `${question}${example ? ` (${example})` : ''} [default: ${defaultValue}]: `;
|
|
41
|
+
const answer = await requestLine(prompt);
|
|
42
|
+
return answer.length > 0 ? answer : defaultValue;
|
|
43
|
+
}
|
|
44
|
+
return {
|
|
45
|
+
async selectChoice(question, choices) {
|
|
46
|
+
while (true) {
|
|
47
|
+
console.log(question);
|
|
48
|
+
for (let i = 0; i < choices.length; i += 1) {
|
|
49
|
+
console.log(` ${i + 1}. ${choices[i]}`);
|
|
50
|
+
}
|
|
51
|
+
const answer = await requestLine('Select an option: ');
|
|
52
|
+
const selected = Number(answer);
|
|
53
|
+
if (Number.isFinite(selected) && selected >= 1 && selected <= choices.length) {
|
|
54
|
+
return selected - 1;
|
|
55
|
+
}
|
|
56
|
+
console.log('Please choose a valid option number.');
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
async promptInput(question, example) {
|
|
60
|
+
while (true) {
|
|
61
|
+
const answer = await requestLine(`${question}${example ? ` (${example})` : ''}: `);
|
|
62
|
+
if (answer.length > 0) {
|
|
63
|
+
return answer;
|
|
64
|
+
}
|
|
65
|
+
console.log('This value cannot be empty.');
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
async promptInputWithDefault(question, defaultValue, example) {
|
|
69
|
+
return requestLineWithDefault(question, defaultValue, example);
|
|
70
|
+
},
|
|
71
|
+
async confirm(question) {
|
|
72
|
+
while (true) {
|
|
73
|
+
const answer = (await requestLine(`${question} (y/N): `)).toLowerCase();
|
|
74
|
+
if (answer === '') {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
if (answer === 'y' || answer === 'yes') {
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
if (answer === 'n' || answer === 'no') {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
console.log('Please respond with y(es) or n(o).');
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
close() {
|
|
87
|
+
rl.close();
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
const STACK_DEV_DEPENDENCIES = {
|
|
92
|
+
'@rawsql-ts/sql-contract': '0.2.0',
|
|
93
|
+
'@rawsql-ts/ztd-cli': '0.17.0'
|
|
94
|
+
};
|
|
95
|
+
const LOCAL_SOURCE_STACK_PACKAGE_DIRS = {
|
|
96
|
+
'@rawsql-ts/sql-contract': node_path_1.default.join('packages', 'sql-contract')
|
|
97
|
+
};
|
|
98
|
+
const ZOD_DEPENDENCY = {
|
|
99
|
+
zod: '^4.3.6'
|
|
100
|
+
};
|
|
101
|
+
const ARKTYPE_DEPENDENCY = {
|
|
102
|
+
arktype: '2.2.0'
|
|
103
|
+
};
|
|
104
|
+
async function gatherOptionalFeatures(prompter, _dependencies, validatorOverride, aiGuidanceOverride) {
|
|
105
|
+
if (validatorOverride) {
|
|
106
|
+
return {
|
|
107
|
+
validator: validatorOverride,
|
|
108
|
+
aiGuidance: aiGuidanceOverride !== null && aiGuidanceOverride !== void 0 ? aiGuidanceOverride : false
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
const validatorChoice = await prompter.selectChoice('Runtime DTO validation is required for ZTD tests. Which validator backend should we install?', ['Zod (zod, recommended)', 'ArkType (arktype)']);
|
|
112
|
+
const validator = validatorChoice === 0 ? 'zod' : 'arktype';
|
|
113
|
+
return {
|
|
114
|
+
validator,
|
|
115
|
+
aiGuidance: aiGuidanceOverride !== null && aiGuidanceOverride !== void 0 ? aiGuidanceOverride : false
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
const README_TEMPLATE = 'README.md';
|
|
119
|
+
const README_WEBAPI_TEMPLATE = 'README.webapi.md';
|
|
120
|
+
const CONTEXT_TEMPLATE = 'CONTEXT.md';
|
|
121
|
+
const CONTEXT_WEBAPI_TEMPLATE = 'CONTEXT.webapi.md';
|
|
122
|
+
const PROMPT_DOGFOOD_WEBAPI_TEMPLATE = 'PROMPT_DOGFOOD.webapi.md';
|
|
123
|
+
const SMOKE_SPEC_ZOD_TEMPLATE = 'src/catalog/specs/_smoke.spec.zod.ts';
|
|
124
|
+
const SMOKE_SPEC_ARKTYPE_TEMPLATE = 'src/catalog/specs/_smoke.spec.arktype.ts';
|
|
125
|
+
const SMOKE_COERCIONS_TEMPLATE = 'src/catalog/runtime/_coercions.ts';
|
|
126
|
+
const SMOKE_RUNTIME_TEMPLATE = 'src/catalog/runtime/_smoke.runtime.ts';
|
|
127
|
+
const LOCAL_SOURCE_GUARD_TEMPLATE = 'scripts/local-source-guard.mjs';
|
|
128
|
+
const SMOKE_VALIDATION_TEST_TEMPLATE = 'tests/smoke.validation.test.ts';
|
|
129
|
+
const TESTS_SMOKE_TEMPLATE = 'tests/smoke.test.ts';
|
|
130
|
+
const TESTKIT_CLIENT_TEMPLATE = 'tests/support/testkit-client.ts';
|
|
131
|
+
const TESTKIT_CLIENT_WEBAPI_TEMPLATE = 'tests/support/testkit-client.webapi.ts';
|
|
132
|
+
const GLOBAL_SETUP_TEMPLATE = 'tests/support/global-setup.ts';
|
|
133
|
+
const VITEST_CONFIG_TEMPLATE = 'vitest.config.ts';
|
|
134
|
+
const TSCONFIG_TEMPLATE = 'tsconfig.json';
|
|
135
|
+
const SQL_CLIENT_TEMPLATE = 'src/db/sql-client.ts';
|
|
136
|
+
const SQL_CLIENT_ADAPTERS_TEMPLATE = 'src/db/sql-client-adapters.ts';
|
|
137
|
+
const SQL_CLIENT_WEBAPI_TEMPLATE = 'src/infrastructure/db/sql-client.ts';
|
|
138
|
+
const SQL_CLIENT_ADAPTERS_WEBAPI_TEMPLATE = 'src/infrastructure/db/sql-client-adapters.ts';
|
|
139
|
+
const REPOSITORY_TELEMETRY_TYPES_TEMPLATE = 'src/infrastructure/telemetry/types.ts';
|
|
140
|
+
const REPOSITORY_TELEMETRY_CONSOLE_TEMPLATE = 'src/infrastructure/telemetry/consoleRepositoryTelemetry.ts';
|
|
141
|
+
const REPOSITORY_TELEMETRY_ENTRY_TEMPLATE = 'src/infrastructure/telemetry/repositoryTelemetry.ts';
|
|
142
|
+
const SQL_README_TEMPLATE = 'src/sql/README.md';
|
|
143
|
+
const VIEWS_REPO_README_TEMPLATE = 'src/repositories/views/README.md';
|
|
144
|
+
const TABLES_REPO_README_TEMPLATE = 'src/repositories/tables/README.md';
|
|
145
|
+
const WEBAPI_DOMAIN_README_TEMPLATE = 'src/domain/README.md';
|
|
146
|
+
const WEBAPI_APPLICATION_README_TEMPLATE = 'src/application/README.md';
|
|
147
|
+
const WEBAPI_PRESENTATION_HTTP_README_TEMPLATE = 'src/presentation/http/README.md';
|
|
148
|
+
const WEBAPI_INFRASTRUCTURE_README_TEMPLATE = 'src/infrastructure/README.md';
|
|
149
|
+
const WEBAPI_PERSISTENCE_README_TEMPLATE = 'src/infrastructure/persistence/README.md';
|
|
150
|
+
const WEBAPI_VIEWS_REPO_README_TEMPLATE = 'src/infrastructure/persistence/repositories/views/README.md';
|
|
151
|
+
const WEBAPI_TABLES_REPO_README_TEMPLATE = 'src/infrastructure/persistence/repositories/tables/README.md';
|
|
152
|
+
const JOBS_README_TEMPLATE = 'src/jobs/README.md';
|
|
153
|
+
const ZTD_README_TEMPLATE = 'ztd/README.md';
|
|
154
|
+
const ZTD_DDL_DEMO_TEMPLATE = 'ztd/ddl/demo.sql';
|
|
155
|
+
const EMPTY_SCHEMA_COMMENT = (schemaName) => [
|
|
156
|
+
`-- DDL for schema "${schemaName}".`,
|
|
157
|
+
'-- Add CREATE TABLE statements here.',
|
|
158
|
+
''
|
|
159
|
+
].join('\n');
|
|
160
|
+
const DEMO_SCHEMA_TEMPLATE = (_schemaName) => {
|
|
161
|
+
return loadTemplate(ZTD_DDL_DEMO_TEMPLATE);
|
|
162
|
+
};
|
|
163
|
+
function resolveInitScaffoldLayout(rootDir, appShape) {
|
|
164
|
+
if (appShape === 'webapi') {
|
|
165
|
+
return {
|
|
166
|
+
appShape,
|
|
167
|
+
readmeTemplate: README_WEBAPI_TEMPLATE,
|
|
168
|
+
contextTemplate: CONTEXT_WEBAPI_TEMPLATE,
|
|
169
|
+
promptDogfoodPath: node_path_1.default.join(rootDir, 'PROMPT_DOGFOOD.md'),
|
|
170
|
+
promptDogfoodTemplate: PROMPT_DOGFOOD_WEBAPI_TEMPLATE,
|
|
171
|
+
sqlClientTemplate: SQL_CLIENT_WEBAPI_TEMPLATE,
|
|
172
|
+
sqlClientAdaptersTemplate: SQL_CLIENT_ADAPTERS_WEBAPI_TEMPLATE,
|
|
173
|
+
testkitClientTemplate: TESTKIT_CLIENT_WEBAPI_TEMPLATE,
|
|
174
|
+
viewsRepoReadmeTemplate: WEBAPI_VIEWS_REPO_README_TEMPLATE,
|
|
175
|
+
tablesRepoReadmeTemplate: WEBAPI_TABLES_REPO_README_TEMPLATE,
|
|
176
|
+
viewsRepoReadmePath: node_path_1.default.join(rootDir, 'src', 'infrastructure', 'persistence', 'repositories', 'views', 'README.md'),
|
|
177
|
+
tablesRepoReadmePath: node_path_1.default.join(rootDir, 'src', 'infrastructure', 'persistence', 'repositories', 'tables', 'README.md'),
|
|
178
|
+
jobsReadmePath: null,
|
|
179
|
+
jobsReadmeTemplate: null,
|
|
180
|
+
domainReadmePath: node_path_1.default.join(rootDir, 'src', 'domain', 'README.md'),
|
|
181
|
+
domainReadmeTemplate: WEBAPI_DOMAIN_README_TEMPLATE,
|
|
182
|
+
applicationReadmePath: node_path_1.default.join(rootDir, 'src', 'application', 'README.md'),
|
|
183
|
+
applicationReadmeTemplate: WEBAPI_APPLICATION_README_TEMPLATE,
|
|
184
|
+
presentationHttpReadmePath: node_path_1.default.join(rootDir, 'src', 'presentation', 'http', 'README.md'),
|
|
185
|
+
presentationHttpReadmeTemplate: WEBAPI_PRESENTATION_HTTP_README_TEMPLATE,
|
|
186
|
+
infrastructureReadmePath: node_path_1.default.join(rootDir, 'src', 'infrastructure', 'README.md'),
|
|
187
|
+
infrastructureReadmeTemplate: WEBAPI_INFRASTRUCTURE_README_TEMPLATE,
|
|
188
|
+
persistenceReadmePath: node_path_1.default.join(rootDir, 'src', 'infrastructure', 'persistence', 'README.md'),
|
|
189
|
+
persistenceReadmeTemplate: WEBAPI_PERSISTENCE_README_TEMPLATE,
|
|
190
|
+
sqlClientPath: node_path_1.default.join(rootDir, 'src', 'infrastructure', 'db', 'sql-client.ts'),
|
|
191
|
+
sqlClientAdaptersPath: node_path_1.default.join(rootDir, 'src', 'infrastructure', 'db', 'sql-client-adapters.ts')
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
return {
|
|
195
|
+
appShape,
|
|
196
|
+
readmeTemplate: README_TEMPLATE,
|
|
197
|
+
contextTemplate: CONTEXT_TEMPLATE,
|
|
198
|
+
promptDogfoodPath: null,
|
|
199
|
+
promptDogfoodTemplate: null,
|
|
200
|
+
sqlClientTemplate: SQL_CLIENT_TEMPLATE,
|
|
201
|
+
sqlClientAdaptersTemplate: SQL_CLIENT_ADAPTERS_TEMPLATE,
|
|
202
|
+
testkitClientTemplate: TESTKIT_CLIENT_TEMPLATE,
|
|
203
|
+
viewsRepoReadmeTemplate: VIEWS_REPO_README_TEMPLATE,
|
|
204
|
+
tablesRepoReadmeTemplate: TABLES_REPO_README_TEMPLATE,
|
|
205
|
+
viewsRepoReadmePath: node_path_1.default.join(rootDir, 'src', 'repositories', 'views', 'README.md'),
|
|
206
|
+
tablesRepoReadmePath: node_path_1.default.join(rootDir, 'src', 'repositories', 'tables', 'README.md'),
|
|
207
|
+
jobsReadmePath: node_path_1.default.join(rootDir, 'src', 'jobs', 'README.md'),
|
|
208
|
+
jobsReadmeTemplate: JOBS_README_TEMPLATE,
|
|
209
|
+
domainReadmePath: null,
|
|
210
|
+
domainReadmeTemplate: null,
|
|
211
|
+
applicationReadmePath: null,
|
|
212
|
+
applicationReadmeTemplate: null,
|
|
213
|
+
presentationHttpReadmePath: null,
|
|
214
|
+
presentationHttpReadmeTemplate: null,
|
|
215
|
+
infrastructureReadmePath: null,
|
|
216
|
+
infrastructureReadmeTemplate: null,
|
|
217
|
+
persistenceReadmePath: null,
|
|
218
|
+
persistenceReadmeTemplate: null,
|
|
219
|
+
sqlClientPath: node_path_1.default.join(rootDir, 'src', 'db', 'sql-client.ts'),
|
|
220
|
+
sqlClientAdaptersPath: node_path_1.default.join(rootDir, 'src', 'db', 'sql-client-adapters.ts')
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
const AGENTS_FILE_CANDIDATES = ['AGENTS.md', 'AGENTS_ztd.md'];
|
|
224
|
+
const APP_INTERFACE_SECTION_MARKER = '## Application Interface Guidance';
|
|
225
|
+
const APP_INTERFACE_SECTION = `---
|
|
226
|
+
## Application Interface Guidance
|
|
227
|
+
|
|
228
|
+
1. Repository interfaces follow Command in, Domain out so commands capture inputs and repositories return domain shapes.
|
|
229
|
+
2. Command definitions and validation stay unified to prevent divergence between surface APIs and SQL expectations.
|
|
230
|
+
3. Input validation relies on zod v4 or later and happens at the repository boundary before any SQL runs.
|
|
231
|
+
4. Only validated inputs reach SQL execution; reject raw external objects as soon as possible.
|
|
232
|
+
5. Domain models live under a dedicated src/domain location so semantics stay centralized.
|
|
233
|
+
6. Build the CRUD behavior first and revisit repository interfaces during review, not before the SQL works.
|
|
234
|
+
7. Guard every behavioral change with unit tests so regression risks stay low.
|
|
235
|
+
`;
|
|
236
|
+
function resolveTemplateDirectory() {
|
|
237
|
+
const candidates = [
|
|
238
|
+
// Prefer the installed package layout: <pkg>/dist/commands → <pkg>/templates.
|
|
239
|
+
node_path_1.default.resolve(__dirname, '..', '..', '..', 'templates'),
|
|
240
|
+
// Support legacy layouts that copied templates into dist/.
|
|
241
|
+
node_path_1.default.resolve(__dirname, '..', '..', 'templates'),
|
|
242
|
+
// Support running tests directly from the monorepo source tree.
|
|
243
|
+
node_path_1.default.resolve(process.cwd(), 'packages', 'ztd-cli', 'templates')
|
|
244
|
+
];
|
|
245
|
+
// Pick the first directory that contains the expected template entrypoint.
|
|
246
|
+
for (const candidate of candidates) {
|
|
247
|
+
if ((0, node_fs_1.existsSync)(node_path_1.default.join(candidate, README_TEMPLATE))) {
|
|
248
|
+
return candidate;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
return candidates[0];
|
|
252
|
+
}
|
|
253
|
+
// Resolve templates from a shipped directory so `ztd init` works after `npm install`.
|
|
254
|
+
const TEMPLATE_DIRECTORY = resolveTemplateDirectory();
|
|
255
|
+
const DEFAULT_DEPENDENCIES = {
|
|
256
|
+
ensureDirectory: fs_1.ensureDirectory,
|
|
257
|
+
writeFile: (filePath, contents) => (0, node_fs_1.writeFileSync)(filePath, contents, 'utf8'),
|
|
258
|
+
fileExists: (filePath) => (0, node_fs_1.existsSync)(filePath),
|
|
259
|
+
runPullSchema: pull_1.runPullSchema,
|
|
260
|
+
runGenerateZtdConfig: ztdConfig_1.runGenerateZtdConfig,
|
|
261
|
+
checkPgDump: () => {
|
|
262
|
+
var _a;
|
|
263
|
+
const executable = (_a = process.env.PG_DUMP_PATH) !== null && _a !== void 0 ? _a : 'pg_dump';
|
|
264
|
+
const result = (0, node_child_process_1.spawnSync)(executable, ['--version'], { stdio: 'ignore' });
|
|
265
|
+
return result.status === 0 && !result.error;
|
|
266
|
+
},
|
|
267
|
+
log: (message) => {
|
|
268
|
+
console.log(message);
|
|
269
|
+
},
|
|
270
|
+
copyAgentsTemplate: agents_1.copyAgentsTemplate,
|
|
271
|
+
installPackages: ({ rootDir, kind, packages, packageManager }) => {
|
|
272
|
+
var _a, _b;
|
|
273
|
+
// Use the Windows shim executables so spawnSync finds the package manager in PATH.
|
|
274
|
+
const executable = resolvePackageManagerExecutable(packageManager);
|
|
275
|
+
const shellExecutable = resolvePackageManagerShellExecutable(executable, packageManager);
|
|
276
|
+
const args = buildPackageManagerArgs(kind, packageManager, packages, rootDir);
|
|
277
|
+
if (args.length === 0) {
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
const isWin32 = process.platform === 'win32';
|
|
281
|
+
// Prefer shell execution for .cmd/.bat on Windows to avoid a guaranteed failure.
|
|
282
|
+
const preferShell = isWin32 && /\.(cmd|bat)$/i.test(executable);
|
|
283
|
+
const baseSpawnOptions = {
|
|
284
|
+
cwd: rootDir,
|
|
285
|
+
stdio: 'inherit',
|
|
286
|
+
shell: false
|
|
287
|
+
};
|
|
288
|
+
const shellSpawnOptions = {
|
|
289
|
+
...baseSpawnOptions,
|
|
290
|
+
shell: true
|
|
291
|
+
};
|
|
292
|
+
let result = (0, node_child_process_1.spawnSync)(preferShell ? shellExecutable : executable, args, preferShell ? shellSpawnOptions : baseSpawnOptions);
|
|
293
|
+
if (result.error && isWin32 && !preferShell) {
|
|
294
|
+
// Retry with cmd.exe only on Windows so .cmd shims resolve reliably.
|
|
295
|
+
result = (0, node_child_process_1.spawnSync)(shellExecutable, args, shellSpawnOptions);
|
|
296
|
+
}
|
|
297
|
+
if ((result.error || result.status !== 0) && executable !== packageManager) {
|
|
298
|
+
// Retry with the bare command name in case a resolved path is rejected.
|
|
299
|
+
result = (0, node_child_process_1.spawnSync)(packageManager, args, baseSpawnOptions);
|
|
300
|
+
if (result.error && isWin32) {
|
|
301
|
+
// Final fallback to shell when the bare command still fails on Windows.
|
|
302
|
+
result = (0, node_child_process_1.spawnSync)(packageManager, args, shellSpawnOptions);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
if (result.error || result.status !== 0) {
|
|
306
|
+
const base = `Failed to run ${packageManager} ${args.join(' ')}`;
|
|
307
|
+
const reason = result.error
|
|
308
|
+
? `: ${result.error.message}`
|
|
309
|
+
: ` (exit code: ${(_a = result.status) !== null && _a !== void 0 ? _a : 'unknown'}, signal: ${(_b = result.signal) !== null && _b !== void 0 ? _b : 'none'})`;
|
|
310
|
+
throw new Error(`${base}${reason}`);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
};
|
|
314
|
+
/**
|
|
315
|
+
* Run the interactive `ztd init` workflow and return the resulting summary.
|
|
316
|
+
*/
|
|
317
|
+
async function runInitCommand(prompter, options) {
|
|
318
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
|
|
319
|
+
const rootDir = (_a = options === null || options === void 0 ? void 0 : options.rootDir) !== null && _a !== void 0 ? _a : process.cwd();
|
|
320
|
+
const dependencies = {
|
|
321
|
+
...DEFAULT_DEPENDENCIES,
|
|
322
|
+
...((_b = options === null || options === void 0 ? void 0 : options.dependencies) !== null && _b !== void 0 ? _b : {})
|
|
323
|
+
};
|
|
324
|
+
const overwritePolicy = {
|
|
325
|
+
force: (_c = options === null || options === void 0 ? void 0 : options.forceOverwrite) !== null && _c !== void 0 ? _c : false,
|
|
326
|
+
nonInteractive: (_d = options === null || options === void 0 ? void 0 : options.nonInteractive) !== null && _d !== void 0 ? _d : false
|
|
327
|
+
};
|
|
328
|
+
if (options === null || options === void 0 ? void 0 : options.withAppInterface) {
|
|
329
|
+
// Provide the documentation-only path before triggering any scaffolding work.
|
|
330
|
+
const summary = await appendAppInterfaceGuidance(rootDir, dependencies);
|
|
331
|
+
dependencies.log(`Appended application interface guidance to ${summary.relativePath}.`);
|
|
332
|
+
return {
|
|
333
|
+
summary: `App interface guidance appended to ${summary.relativePath}.`,
|
|
334
|
+
files: [summary]
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
// Determine workflow: use explicit flag, non-interactive default, or prompt.
|
|
338
|
+
let workflow;
|
|
339
|
+
if (options === null || options === void 0 ? void 0 : options.workflow) {
|
|
340
|
+
workflow = options.workflow;
|
|
341
|
+
}
|
|
342
|
+
else if (overwritePolicy.nonInteractive) {
|
|
343
|
+
workflow = 'demo';
|
|
344
|
+
}
|
|
345
|
+
else {
|
|
346
|
+
const workflowChoice = await prompter.selectChoice('How do you want to start your database workflow?', [
|
|
347
|
+
'Pull schema from Postgres (pg_dump)',
|
|
348
|
+
'Create empty scaffold (I will write DDL)',
|
|
349
|
+
'Create scaffold with demo DDL (no app code)'
|
|
350
|
+
]);
|
|
351
|
+
workflow = workflowChoice === 0 ? 'pg_dump' : workflowChoice === 1 ? 'empty' : 'demo';
|
|
352
|
+
}
|
|
353
|
+
if (overwritePolicy.nonInteractive && workflow === 'pg_dump') {
|
|
354
|
+
throw new Error('Non-interactive mode does not support the pg_dump workflow (requires connection string prompt).');
|
|
355
|
+
}
|
|
356
|
+
const schemaName = normalizeSchemaName(ztdProjectConfig_1.DEFAULT_ZTD_CONFIG.ddl.defaultSchema);
|
|
357
|
+
const schemaFileName = `${sanitizeSchemaFileName(schemaName)}.sql`;
|
|
358
|
+
const appShape = (_e = options === null || options === void 0 ? void 0 : options.appShape) !== null && _e !== void 0 ? _e : 'default';
|
|
359
|
+
const scaffoldLayout = resolveInitScaffoldLayout(rootDir, appShape);
|
|
360
|
+
const absolutePaths = {
|
|
361
|
+
schema: node_path_1.default.join(rootDir, ztdProjectConfig_1.DEFAULT_ZTD_CONFIG.ddlDir, schemaFileName),
|
|
362
|
+
config: node_path_1.default.join(rootDir, 'ztd.config.json'),
|
|
363
|
+
smokeSpec: node_path_1.default.join(rootDir, 'src', 'catalog', 'specs', '_smoke.spec.ts'),
|
|
364
|
+
smokeCoercions: node_path_1.default.join(rootDir, 'src', 'catalog', 'runtime', '_coercions.ts'),
|
|
365
|
+
smokeRuntime: node_path_1.default.join(rootDir, 'src', 'catalog', 'runtime', '_smoke.runtime.ts'),
|
|
366
|
+
localSourceGuardScript: node_path_1.default.join(rootDir, 'scripts', 'local-source-guard.mjs'),
|
|
367
|
+
smokeValidationTest: node_path_1.default.join(rootDir, ztdProjectConfig_1.DEFAULT_ZTD_CONFIG.testsDir, 'smoke.validation.test.ts'),
|
|
368
|
+
testsSmoke: node_path_1.default.join(rootDir, ztdProjectConfig_1.DEFAULT_ZTD_CONFIG.testsDir, 'smoke.test.ts'),
|
|
369
|
+
readme: node_path_1.default.join(rootDir, 'README.md'),
|
|
370
|
+
context: node_path_1.default.join(rootDir, 'CONTEXT.md'),
|
|
371
|
+
promptDogfood: (_f = scaffoldLayout.promptDogfoodPath) !== null && _f !== void 0 ? _f : node_path_1.default.join(rootDir, 'PROMPT_DOGFOOD.md'),
|
|
372
|
+
internalAgentsManifest: node_path_1.default.join(rootDir, '.ztd', 'agents', 'manifest.json'),
|
|
373
|
+
internalAgentsRoot: node_path_1.default.join(rootDir, '.ztd', 'agents', 'root.md'),
|
|
374
|
+
internalAgentsSrc: node_path_1.default.join(rootDir, '.ztd', 'agents', 'src.md'),
|
|
375
|
+
internalAgentsTests: node_path_1.default.join(rootDir, '.ztd', 'agents', 'tests.md'),
|
|
376
|
+
internalAgentsZtd: node_path_1.default.join(rootDir, '.ztd', 'agents', 'ztd.md'),
|
|
377
|
+
sqlReadme: node_path_1.default.join(rootDir, 'src', 'sql', 'README.md'),
|
|
378
|
+
viewsRepoReadme: scaffoldLayout.viewsRepoReadmePath,
|
|
379
|
+
tablesRepoReadme: scaffoldLayout.tablesRepoReadmePath,
|
|
380
|
+
jobsReadme: (_g = scaffoldLayout.jobsReadmePath) !== null && _g !== void 0 ? _g : node_path_1.default.join(rootDir, 'src', 'jobs', 'README.md'),
|
|
381
|
+
domainReadme: (_h = scaffoldLayout.domainReadmePath) !== null && _h !== void 0 ? _h : node_path_1.default.join(rootDir, 'src', 'domain', 'README.md'),
|
|
382
|
+
applicationReadme: (_j = scaffoldLayout.applicationReadmePath) !== null && _j !== void 0 ? _j : node_path_1.default.join(rootDir, 'src', 'application', 'README.md'),
|
|
383
|
+
presentationHttpReadme: (_k = scaffoldLayout.presentationHttpReadmePath) !== null && _k !== void 0 ? _k : node_path_1.default.join(rootDir, 'src', 'presentation', 'http', 'README.md'),
|
|
384
|
+
infrastructureReadme: (_l = scaffoldLayout.infrastructureReadmePath) !== null && _l !== void 0 ? _l : node_path_1.default.join(rootDir, 'src', 'infrastructure', 'README.md'),
|
|
385
|
+
persistenceReadme: (_m = scaffoldLayout.persistenceReadmePath) !== null && _m !== void 0 ? _m : node_path_1.default.join(rootDir, 'src', 'infrastructure', 'persistence', 'README.md'),
|
|
386
|
+
sqlClient: scaffoldLayout.sqlClientPath,
|
|
387
|
+
sqlClientAdapters: scaffoldLayout.sqlClientAdaptersPath,
|
|
388
|
+
repositoryTelemetryTypes: node_path_1.default.join(rootDir, 'src', 'infrastructure', 'telemetry', 'types.ts'),
|
|
389
|
+
repositoryTelemetryConsole: node_path_1.default.join(rootDir, 'src', 'infrastructure', 'telemetry', 'consoleRepositoryTelemetry.ts'),
|
|
390
|
+
repositoryTelemetryEntry: node_path_1.default.join(rootDir, 'src', 'infrastructure', 'telemetry', 'repositoryTelemetry.ts'),
|
|
391
|
+
testkitClient: node_path_1.default.join(rootDir, ztdProjectConfig_1.DEFAULT_ZTD_CONFIG.testsDir, 'support', 'testkit-client.ts'),
|
|
392
|
+
globalSetup: node_path_1.default.join(rootDir, ztdProjectConfig_1.DEFAULT_ZTD_CONFIG.testsDir, 'support', 'global-setup.ts'),
|
|
393
|
+
vitestConfig: node_path_1.default.join(rootDir, 'vitest.config.ts'),
|
|
394
|
+
tsconfig: node_path_1.default.join(rootDir, 'tsconfig.json'),
|
|
395
|
+
ztdDocsReadme: node_path_1.default.join(rootDir, 'ztd', 'README.md'),
|
|
396
|
+
gitignore: node_path_1.default.join(rootDir, '.gitignore'),
|
|
397
|
+
editorconfig: node_path_1.default.join(rootDir, '.editorconfig'),
|
|
398
|
+
prettierignore: node_path_1.default.join(rootDir, '.prettierignore'),
|
|
399
|
+
prettier: node_path_1.default.join(rootDir, '.prettierrc'),
|
|
400
|
+
package: node_path_1.default.join(rootDir, 'package.json')
|
|
401
|
+
};
|
|
402
|
+
const relativePath = (key) => node_path_1.default.relative(rootDir, absolutePaths[key]).replace(/\\/g, '/') || absolutePaths[key];
|
|
403
|
+
const summaries = {};
|
|
404
|
+
const scaffoldProfile = resolveInitScaffoldProfile(rootDir, options === null || options === void 0 ? void 0 : options.localSourceRoot);
|
|
405
|
+
// Ask how the user prefers to populate the initial schema.
|
|
406
|
+
if (workflow === 'pg_dump') {
|
|
407
|
+
// Database-first path: pull the schema before writing any DDL files.
|
|
408
|
+
if (!dependencies.checkPgDump()) {
|
|
409
|
+
throw new Error('Unable to find pg_dump. Install Postgres or set PG_DUMP_PATH before running ztd init.');
|
|
410
|
+
}
|
|
411
|
+
const connectionString = await prompter.promptInput('Enter the Postgres connection string for your database', 'postgres://user:pass@host:5432/db');
|
|
412
|
+
const schemaSummary = await writeFileWithConsent(absolutePaths.schema, relativePath('schema'), dependencies, prompter, overwritePolicy, async () => {
|
|
413
|
+
dependencies.ensureDirectory(node_path_1.default.dirname(absolutePaths.schema));
|
|
414
|
+
await dependencies.runPullSchema({
|
|
415
|
+
url: connectionString,
|
|
416
|
+
out: node_path_1.default.dirname(absolutePaths.schema),
|
|
417
|
+
schemas: [schemaName]
|
|
418
|
+
});
|
|
419
|
+
});
|
|
420
|
+
summaries.schema = schemaSummary;
|
|
421
|
+
}
|
|
422
|
+
else if (workflow === 'empty') {
|
|
423
|
+
// Manual path: seed the DDL directory with a starter schema so ztd-config can run.
|
|
424
|
+
const schemaSummary = await writeFileWithConsent(absolutePaths.schema, relativePath('schema'), dependencies, prompter, overwritePolicy, async () => {
|
|
425
|
+
dependencies.ensureDirectory(node_path_1.default.dirname(absolutePaths.schema));
|
|
426
|
+
dependencies.writeFile(absolutePaths.schema, EMPTY_SCHEMA_COMMENT(schemaName));
|
|
427
|
+
});
|
|
428
|
+
summaries.schema = schemaSummary;
|
|
429
|
+
}
|
|
430
|
+
else {
|
|
431
|
+
const schemaSummary = await writeFileWithConsent(absolutePaths.schema, relativePath('schema'), dependencies, prompter, overwritePolicy, async () => {
|
|
432
|
+
dependencies.ensureDirectory(node_path_1.default.dirname(absolutePaths.schema));
|
|
433
|
+
dependencies.writeFile(absolutePaths.schema, DEMO_SCHEMA_TEMPLATE(schemaName));
|
|
434
|
+
});
|
|
435
|
+
summaries.schema = schemaSummary;
|
|
436
|
+
}
|
|
437
|
+
// Seed the ztd.config.json defaults so downstream tooling knows where ddl/tests live.
|
|
438
|
+
const configSummary = await writeFileWithConsent(absolutePaths.config, relativePath('config'), dependencies, prompter, overwritePolicy, () => {
|
|
439
|
+
(0, ztdProjectConfig_1.writeZtdProjectConfig)(rootDir, {
|
|
440
|
+
ddl: {
|
|
441
|
+
defaultSchema: schemaName,
|
|
442
|
+
searchPath: [schemaName]
|
|
443
|
+
}
|
|
444
|
+
});
|
|
445
|
+
});
|
|
446
|
+
summaries.config = configSummary;
|
|
447
|
+
const validatorOverride = (_o = options === null || options === void 0 ? void 0 : options.validator) !== null && _o !== void 0 ? _o : (overwritePolicy.nonInteractive ? 'zod' : undefined);
|
|
448
|
+
const optionalFeatures = await gatherOptionalFeatures(prompter, dependencies, validatorOverride, options === null || options === void 0 ? void 0 : options.withAiGuidance);
|
|
449
|
+
// Emit supporting documentation that describes the workflow for contributors.
|
|
450
|
+
const readmeSummary = await writeTemplateFile(rootDir, absolutePaths.readme, relativePath('readme'), scaffoldLayout.readmeTemplate, dependencies, prompter, overwritePolicy, true);
|
|
451
|
+
if (readmeSummary) {
|
|
452
|
+
summaries.readme = readmeSummary;
|
|
453
|
+
}
|
|
454
|
+
if (optionalFeatures.aiGuidance) {
|
|
455
|
+
const contextSummary = await writeTemplateFile(rootDir, absolutePaths.context, relativePath('context'), scaffoldLayout.contextTemplate, dependencies, prompter, overwritePolicy, true);
|
|
456
|
+
if (contextSummary) {
|
|
457
|
+
summaries.context = contextSummary;
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
if (optionalFeatures.aiGuidance && scaffoldLayout.promptDogfoodTemplate) {
|
|
461
|
+
const promptDogfoodSummary = await writeTemplateFile(rootDir, absolutePaths.promptDogfood, relativePath('promptDogfood'), scaffoldLayout.promptDogfoodTemplate, dependencies, prompter, overwritePolicy, true);
|
|
462
|
+
if (promptDogfoodSummary) {
|
|
463
|
+
summaries.promptDogfood = promptDogfoodSummary;
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
if (optionalFeatures.aiGuidance) {
|
|
467
|
+
for (const summary of (0, agents_1.writeInternalAgentsArtifacts)(rootDir)) {
|
|
468
|
+
if (summary.relativePath === relativePath('internalAgentsManifest')) {
|
|
469
|
+
summaries.internalAgentsManifest = summary;
|
|
470
|
+
}
|
|
471
|
+
else if (summary.relativePath === relativePath('internalAgentsRoot')) {
|
|
472
|
+
summaries.internalAgentsRoot = summary;
|
|
473
|
+
}
|
|
474
|
+
else if (summary.relativePath === relativePath('internalAgentsSrc')) {
|
|
475
|
+
summaries.internalAgentsSrc = summary;
|
|
476
|
+
}
|
|
477
|
+
else if (summary.relativePath === relativePath('internalAgentsTests')) {
|
|
478
|
+
summaries.internalAgentsTests = summary;
|
|
479
|
+
}
|
|
480
|
+
else if (summary.relativePath === relativePath('internalAgentsZtd')) {
|
|
481
|
+
summaries.internalAgentsZtd = summary;
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
const ztdDocsReadmeSummary = await writeTemplateFile(rootDir, absolutePaths.ztdDocsReadme, relativePath('ztdDocsReadme'), ZTD_README_TEMPLATE, dependencies, prompter, overwritePolicy);
|
|
486
|
+
if (ztdDocsReadmeSummary) {
|
|
487
|
+
summaries.ztdDocsReadme = ztdDocsReadmeSummary;
|
|
488
|
+
}
|
|
489
|
+
const sqlReadmeSummary = await writeTemplateFile(rootDir, absolutePaths.sqlReadme, relativePath('sqlReadme'), SQL_README_TEMPLATE, dependencies, prompter, overwritePolicy);
|
|
490
|
+
if (sqlReadmeSummary) {
|
|
491
|
+
summaries.sqlReadme = sqlReadmeSummary;
|
|
492
|
+
}
|
|
493
|
+
const viewsRepoReadmeSummary = await writeTemplateFile(rootDir, absolutePaths.viewsRepoReadme, relativePath('viewsRepoReadme'), scaffoldLayout.viewsRepoReadmeTemplate, dependencies, prompter, overwritePolicy);
|
|
494
|
+
if (viewsRepoReadmeSummary) {
|
|
495
|
+
summaries.viewsRepoReadme = viewsRepoReadmeSummary;
|
|
496
|
+
}
|
|
497
|
+
const tablesRepoReadmeSummary = await writeTemplateFile(rootDir, absolutePaths.tablesRepoReadme, relativePath('tablesRepoReadme'), scaffoldLayout.tablesRepoReadmeTemplate, dependencies, prompter, overwritePolicy);
|
|
498
|
+
if (tablesRepoReadmeSummary) {
|
|
499
|
+
summaries.tablesRepoReadme = tablesRepoReadmeSummary;
|
|
500
|
+
}
|
|
501
|
+
if (scaffoldLayout.jobsReadmeTemplate) {
|
|
502
|
+
const jobsReadmeSummary = await writeTemplateFile(rootDir, absolutePaths.jobsReadme, relativePath('jobsReadme'), scaffoldLayout.jobsReadmeTemplate, dependencies, prompter, overwritePolicy);
|
|
503
|
+
if (jobsReadmeSummary) {
|
|
504
|
+
summaries.jobsReadme = jobsReadmeSummary;
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
if (scaffoldLayout.domainReadmeTemplate) {
|
|
508
|
+
const domainReadmeSummary = await writeTemplateFile(rootDir, absolutePaths.domainReadme, relativePath('domainReadme'), scaffoldLayout.domainReadmeTemplate, dependencies, prompter, overwritePolicy);
|
|
509
|
+
if (domainReadmeSummary) {
|
|
510
|
+
summaries.domainReadme = domainReadmeSummary;
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
if (scaffoldLayout.applicationReadmeTemplate) {
|
|
514
|
+
const applicationReadmeSummary = await writeTemplateFile(rootDir, absolutePaths.applicationReadme, relativePath('applicationReadme'), scaffoldLayout.applicationReadmeTemplate, dependencies, prompter, overwritePolicy);
|
|
515
|
+
if (applicationReadmeSummary) {
|
|
516
|
+
summaries.applicationReadme = applicationReadmeSummary;
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
if (scaffoldLayout.presentationHttpReadmeTemplate) {
|
|
520
|
+
const presentationHttpReadmeSummary = await writeTemplateFile(rootDir, absolutePaths.presentationHttpReadme, relativePath('presentationHttpReadme'), scaffoldLayout.presentationHttpReadmeTemplate, dependencies, prompter, overwritePolicy);
|
|
521
|
+
if (presentationHttpReadmeSummary) {
|
|
522
|
+
summaries.presentationHttpReadme = presentationHttpReadmeSummary;
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
if (scaffoldLayout.infrastructureReadmeTemplate) {
|
|
526
|
+
const infrastructureReadmeSummary = await writeTemplateFile(rootDir, absolutePaths.infrastructureReadme, relativePath('infrastructureReadme'), scaffoldLayout.infrastructureReadmeTemplate, dependencies, prompter, overwritePolicy);
|
|
527
|
+
if (infrastructureReadmeSummary) {
|
|
528
|
+
summaries.infrastructureReadme = infrastructureReadmeSummary;
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
if (scaffoldLayout.persistenceReadmeTemplate) {
|
|
532
|
+
const persistenceReadmeSummary = await writeTemplateFile(rootDir, absolutePaths.persistenceReadme, relativePath('persistenceReadme'), scaffoldLayout.persistenceReadmeTemplate, dependencies, prompter, overwritePolicy);
|
|
533
|
+
if (persistenceReadmeSummary) {
|
|
534
|
+
summaries.persistenceReadme = persistenceReadmeSummary;
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
const smokeSpecTemplate = optionalFeatures.validator === 'zod' ? SMOKE_SPEC_ZOD_TEMPLATE : SMOKE_SPEC_ARKTYPE_TEMPLATE;
|
|
538
|
+
const smokeSpecSummary = await writeTemplateFile(rootDir, absolutePaths.smokeSpec, relativePath('smokeSpec'), smokeSpecTemplate, dependencies, prompter, overwritePolicy);
|
|
539
|
+
if (smokeSpecSummary) {
|
|
540
|
+
summaries.smokeSpec = smokeSpecSummary;
|
|
541
|
+
}
|
|
542
|
+
const smokeCoercionsSummary = await writeTemplateFile(rootDir, absolutePaths.smokeCoercions, relativePath('smokeCoercions'), SMOKE_COERCIONS_TEMPLATE, dependencies, prompter, overwritePolicy);
|
|
543
|
+
if (smokeCoercionsSummary) {
|
|
544
|
+
summaries.smokeCoercions = smokeCoercionsSummary;
|
|
545
|
+
}
|
|
546
|
+
if (scaffoldProfile.dependencyProfile === 'local-source') {
|
|
547
|
+
const localSourceGuardSummary = await writeDocFile(absolutePaths.localSourceGuardScript, relativePath('localSourceGuardScript'), buildLocalSourceGuardContents(absolutePaths.localSourceGuardScript, scaffoldProfile), dependencies, prompter, overwritePolicy);
|
|
548
|
+
if (localSourceGuardSummary) {
|
|
549
|
+
summaries.localSourceGuardScript = localSourceGuardSummary;
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
const smokeRuntimeSummary = await writeTemplateFile(rootDir, absolutePaths.smokeRuntime, relativePath('smokeRuntime'), SMOKE_RUNTIME_TEMPLATE, dependencies, prompter, overwritePolicy);
|
|
553
|
+
if (smokeRuntimeSummary) {
|
|
554
|
+
summaries.smokeRuntime = smokeRuntimeSummary;
|
|
555
|
+
}
|
|
556
|
+
const smokeValidationTestSummary = await writeTemplateFile(rootDir, absolutePaths.smokeValidationTest, relativePath('smokeValidationTest'), SMOKE_VALIDATION_TEST_TEMPLATE, dependencies, prompter, overwritePolicy);
|
|
557
|
+
if (smokeValidationTestSummary) {
|
|
558
|
+
summaries.smokeValidationTest = smokeValidationTestSummary;
|
|
559
|
+
}
|
|
560
|
+
const sqlClientSummary = writeOptionalTemplateFile(absolutePaths.sqlClient, relativePath('sqlClient'), scaffoldLayout.sqlClientTemplate, dependencies);
|
|
561
|
+
if (sqlClientSummary) {
|
|
562
|
+
summaries.sqlClient = sqlClientSummary;
|
|
563
|
+
// Only scaffold the pg adapter alongside the SqlClient interface.
|
|
564
|
+
const sqlClientAdaptersSummary = writeOptionalTemplateFile(absolutePaths.sqlClientAdapters, relativePath('sqlClientAdapters'), scaffoldLayout.sqlClientAdaptersTemplate, dependencies);
|
|
565
|
+
if (sqlClientAdaptersSummary) {
|
|
566
|
+
summaries.sqlClientAdapters = sqlClientAdaptersSummary;
|
|
567
|
+
}
|
|
568
|
+
// Repository telemetry stays application-owned, but the scaffold can
|
|
569
|
+
// provide a default structured seam that repositories depend on.
|
|
570
|
+
const repositoryTelemetryTypesSummary = writeOptionalTemplateFile(absolutePaths.repositoryTelemetryTypes, relativePath('repositoryTelemetryTypes'), REPOSITORY_TELEMETRY_TYPES_TEMPLATE, dependencies);
|
|
571
|
+
if (repositoryTelemetryTypesSummary) {
|
|
572
|
+
summaries.repositoryTelemetryTypes = repositoryTelemetryTypesSummary;
|
|
573
|
+
}
|
|
574
|
+
const repositoryTelemetryConsoleSummary = writeOptionalTemplateFile(absolutePaths.repositoryTelemetryConsole, relativePath('repositoryTelemetryConsole'), REPOSITORY_TELEMETRY_CONSOLE_TEMPLATE, dependencies);
|
|
575
|
+
if (repositoryTelemetryConsoleSummary) {
|
|
576
|
+
summaries.repositoryTelemetryConsole = repositoryTelemetryConsoleSummary;
|
|
577
|
+
}
|
|
578
|
+
const repositoryTelemetryEntrySummary = writeOptionalTemplateFile(absolutePaths.repositoryTelemetryEntry, relativePath('repositoryTelemetryEntry'), REPOSITORY_TELEMETRY_ENTRY_TEMPLATE, dependencies);
|
|
579
|
+
if (repositoryTelemetryEntrySummary) {
|
|
580
|
+
summaries.repositoryTelemetryEntry = repositoryTelemetryEntrySummary;
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
const testsSmokeSummary = await writeTemplateFile(rootDir, absolutePaths.testsSmoke, relativePath('testsSmoke'), TESTS_SMOKE_TEMPLATE, dependencies, prompter, overwritePolicy);
|
|
584
|
+
if (testsSmokeSummary) {
|
|
585
|
+
summaries.testsSmoke = testsSmokeSummary;
|
|
586
|
+
}
|
|
587
|
+
const testkitSummary = await writeTemplateFile(rootDir, absolutePaths.testkitClient, relativePath('testkitClient'), scaffoldLayout.testkitClientTemplate, dependencies, prompter, overwritePolicy);
|
|
588
|
+
if (testkitSummary) {
|
|
589
|
+
summaries.testkitClient = testkitSummary;
|
|
590
|
+
}
|
|
591
|
+
const globalSetupSummary = await writeTemplateFile(rootDir, absolutePaths.globalSetup, relativePath('globalSetup'), GLOBAL_SETUP_TEMPLATE, dependencies, prompter, overwritePolicy);
|
|
592
|
+
if (globalSetupSummary) {
|
|
593
|
+
summaries.globalSetup = globalSetupSummary;
|
|
594
|
+
}
|
|
595
|
+
const vitestConfigSummary = await writeTemplateFile(rootDir, absolutePaths.vitestConfig, relativePath('vitestConfig'), VITEST_CONFIG_TEMPLATE, dependencies, prompter, overwritePolicy);
|
|
596
|
+
if (vitestConfigSummary) {
|
|
597
|
+
summaries.vitestConfig = vitestConfigSummary;
|
|
598
|
+
}
|
|
599
|
+
const tsconfigSummary = await writeTemplateFile(rootDir, absolutePaths.tsconfig, relativePath('tsconfig'), TSCONFIG_TEMPLATE, dependencies, prompter, overwritePolicy);
|
|
600
|
+
if (tsconfigSummary) {
|
|
601
|
+
summaries.tsconfig = tsconfigSummary;
|
|
602
|
+
}
|
|
603
|
+
const editorconfigSummary = copyTemplateFileIfMissing(rootDir, relativePath('editorconfig'), '.editorconfig', dependencies);
|
|
604
|
+
if (editorconfigSummary) {
|
|
605
|
+
summaries.editorconfig = editorconfigSummary;
|
|
606
|
+
}
|
|
607
|
+
const prettierSummary = copyTemplateFileIfMissing(rootDir, relativePath('prettier'), '.prettierrc', dependencies);
|
|
608
|
+
if (prettierSummary) {
|
|
609
|
+
summaries.prettier = prettierSummary;
|
|
610
|
+
}
|
|
611
|
+
const gitignoreSummary = copyTemplateFileIfMissing(rootDir, relativePath('gitignore'), '.gitignore', dependencies);
|
|
612
|
+
if (gitignoreSummary) {
|
|
613
|
+
summaries.gitignore = gitignoreSummary;
|
|
614
|
+
}
|
|
615
|
+
const prettierignoreSummary = copyTemplateFileIfMissing(rootDir, relativePath('prettierignore'), '.prettierignore', dependencies);
|
|
616
|
+
if (prettierignoreSummary) {
|
|
617
|
+
summaries.prettierignore = prettierignoreSummary;
|
|
618
|
+
}
|
|
619
|
+
const packageSummary = ensurePackageJsonFormatting(rootDir, relativePath('package'), dependencies, optionalFeatures, scaffoldProfile);
|
|
620
|
+
if (packageSummary) {
|
|
621
|
+
summaries.package = packageSummary;
|
|
622
|
+
}
|
|
623
|
+
await ensureTemplateDependenciesInstalled(rootDir, absolutePaths, summaries, dependencies);
|
|
624
|
+
const nextSteps = buildNextSteps(normalizeRelative(rootDir, absolutePaths.schema), workflow, rootDir, scaffoldProfile, appShape);
|
|
625
|
+
const summaryLines = buildSummaryLines(summaries, optionalFeatures, nextSteps, scaffoldProfile);
|
|
626
|
+
summaryLines.forEach(dependencies.log);
|
|
627
|
+
return {
|
|
628
|
+
summary: summaryLines.join('\n'),
|
|
629
|
+
files: Object.values(summaries)
|
|
630
|
+
};
|
|
631
|
+
}
|
|
632
|
+
function resolvePackageManagerExecutable(packageManager) {
|
|
633
|
+
var _a;
|
|
634
|
+
const override = process.env.ZTD_PACKAGE_MANAGER_PATH;
|
|
635
|
+
if (override) {
|
|
636
|
+
return override;
|
|
637
|
+
}
|
|
638
|
+
if (process.platform !== 'win32') {
|
|
639
|
+
return packageManager;
|
|
640
|
+
}
|
|
641
|
+
// Prefer .cmd shims first on Windows so they take precedence over extension-less files.
|
|
642
|
+
const cmdFallbacks = {
|
|
643
|
+
npm: 'npm.cmd',
|
|
644
|
+
pnpm: 'pnpm.cmd',
|
|
645
|
+
yarn: 'yarn.cmd'
|
|
646
|
+
};
|
|
647
|
+
const cmdResolved = resolveExecutableInPath(cmdFallbacks[packageManager]);
|
|
648
|
+
if (cmdResolved) {
|
|
649
|
+
return cmdResolved;
|
|
650
|
+
}
|
|
651
|
+
// Fall back to the extension-less name if no cmd shim is found.
|
|
652
|
+
const resolved = resolveExecutableInPath(packageManager);
|
|
653
|
+
if (resolved) {
|
|
654
|
+
return resolved;
|
|
655
|
+
}
|
|
656
|
+
return (_a = cmdFallbacks[packageManager]) !== null && _a !== void 0 ? _a : packageManager;
|
|
657
|
+
}
|
|
658
|
+
function resolvePackageManagerShellExecutable(executable, packageManager, platform = process.platform) {
|
|
659
|
+
if (platform !== 'win32') {
|
|
660
|
+
return executable;
|
|
661
|
+
}
|
|
662
|
+
// `shell: true` delegates through cmd.exe, which splits unquoted absolute paths with spaces.
|
|
663
|
+
// Use the shim basename so the shell resolves it from PATH without truncating at `C:\Program`.
|
|
664
|
+
if (/\.(cmd|bat)$/i.test(executable) && node_path_1.default.win32.isAbsolute(executable)) {
|
|
665
|
+
return node_path_1.default.win32.basename(executable);
|
|
666
|
+
}
|
|
667
|
+
return executable || packageManager;
|
|
668
|
+
}
|
|
669
|
+
function resolveExecutableInPath(executable) {
|
|
670
|
+
var _a, _b, _c;
|
|
671
|
+
const pathValue = (_b = (_a = process.env.PATH) !== null && _a !== void 0 ? _a : process.env.Path) !== null && _b !== void 0 ? _b : '';
|
|
672
|
+
if (!pathValue) {
|
|
673
|
+
return null;
|
|
674
|
+
}
|
|
675
|
+
const pathEntries = pathValue
|
|
676
|
+
.split(node_path_1.default.delimiter)
|
|
677
|
+
.map((entry) => {
|
|
678
|
+
const trimmed = entry.trim();
|
|
679
|
+
if (process.platform !== 'win32') {
|
|
680
|
+
return trimmed;
|
|
681
|
+
}
|
|
682
|
+
if (trimmed.startsWith('"') && trimmed.endsWith('"') && trimmed.length >= 2) {
|
|
683
|
+
return trimmed.slice(1, -1);
|
|
684
|
+
}
|
|
685
|
+
return trimmed;
|
|
686
|
+
})
|
|
687
|
+
.filter(Boolean);
|
|
688
|
+
const hasExtension = node_path_1.default.extname(executable).length > 0;
|
|
689
|
+
const extensions = process.platform === 'win32'
|
|
690
|
+
? ((_c = process.env.PATHEXT) !== null && _c !== void 0 ? _c : '.EXE;.CMD;.BAT;.COM')
|
|
691
|
+
.split(';')
|
|
692
|
+
.map((ext) => ext.trim())
|
|
693
|
+
.filter(Boolean)
|
|
694
|
+
: [''];
|
|
695
|
+
// Check each PATH entry with PATHEXT so we can resolve shim executables.
|
|
696
|
+
for (const entry of pathEntries) {
|
|
697
|
+
if (hasExtension) {
|
|
698
|
+
const candidate = node_path_1.default.join(entry, executable);
|
|
699
|
+
if ((0, node_fs_1.existsSync)(candidate)) {
|
|
700
|
+
return candidate;
|
|
701
|
+
}
|
|
702
|
+
continue;
|
|
703
|
+
}
|
|
704
|
+
for (const ext of extensions) {
|
|
705
|
+
const candidate = node_path_1.default.join(entry, `${executable}${ext}`);
|
|
706
|
+
if ((0, node_fs_1.existsSync)(candidate)) {
|
|
707
|
+
return candidate;
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
return null;
|
|
712
|
+
}
|
|
713
|
+
function findAncestorPnpmWorkspaceRoot(rootDir) {
|
|
714
|
+
let cursor = node_path_1.default.resolve(rootDir);
|
|
715
|
+
while (true) {
|
|
716
|
+
const parentDir = node_path_1.default.dirname(cursor);
|
|
717
|
+
if (parentDir === cursor) {
|
|
718
|
+
return null;
|
|
719
|
+
}
|
|
720
|
+
cursor = parentDir;
|
|
721
|
+
if ((0, node_fs_1.existsSync)(node_path_1.default.join(cursor, 'pnpm-workspace.yaml'))) {
|
|
722
|
+
return cursor;
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
function resolvePnpmWorkspaceGuard(rootDir, packageManager) {
|
|
727
|
+
if (packageManager !== 'pnpm') {
|
|
728
|
+
return { workspaceRoot: null, shouldIgnoreWorkspace: false };
|
|
729
|
+
}
|
|
730
|
+
const workspaceRoot = findAncestorPnpmWorkspaceRoot(rootDir);
|
|
731
|
+
return {
|
|
732
|
+
workspaceRoot,
|
|
733
|
+
shouldIgnoreWorkspace: workspaceRoot !== null,
|
|
734
|
+
};
|
|
735
|
+
}
|
|
736
|
+
function buildManualInstallCommand(kind, packageManager, packages, rootDir) {
|
|
737
|
+
return [packageManager, ...buildPackageManagerArgs(kind, packageManager, packages, rootDir)].join(' ');
|
|
738
|
+
}
|
|
739
|
+
function resolveInitInstallStrategy(rootDir, packageManager, environment) {
|
|
740
|
+
var _a, _b;
|
|
741
|
+
const workspaceGuard = resolvePnpmWorkspaceGuard(rootDir, packageManager);
|
|
742
|
+
const installCommand = packageManager === 'pnpm' && workspaceGuard.shouldIgnoreWorkspace
|
|
743
|
+
? 'pnpm install --ignore-workspace'
|
|
744
|
+
: `${packageManager} install`;
|
|
745
|
+
const platform = (_a = environment === null || environment === void 0 ? void 0 : environment.platform) !== null && _a !== void 0 ? _a : process.platform;
|
|
746
|
+
// npm_command is provided by npm/pnpm for lifecycle and exec invocations, which we use as a fallback in real CLI runs.
|
|
747
|
+
const npmCommand = (_b = environment === null || environment === void 0 ? void 0 : environment.npmCommand) !== null && _b !== void 0 ? _b : process.env.npm_command;
|
|
748
|
+
return {
|
|
749
|
+
installCommand,
|
|
750
|
+
workspaceGuard,
|
|
751
|
+
shouldDeferAutoInstall: platform === 'win32' && packageManager === 'pnpm' && npmCommand === 'exec'
|
|
752
|
+
};
|
|
753
|
+
}
|
|
754
|
+
function buildPackageManagerArgs(kind, packageManager, packages, rootDir) {
|
|
755
|
+
const pnpmWorkspaceGuard = rootDir !== undefined ? resolvePnpmWorkspaceGuard(rootDir, packageManager) : { shouldIgnoreWorkspace: false };
|
|
756
|
+
if (kind === 'install') {
|
|
757
|
+
return packageManager === 'pnpm' && pnpmWorkspaceGuard.shouldIgnoreWorkspace
|
|
758
|
+
? ['install', '--ignore-workspace']
|
|
759
|
+
: ['install'];
|
|
760
|
+
}
|
|
761
|
+
if (packages.length === 0) {
|
|
762
|
+
return [];
|
|
763
|
+
}
|
|
764
|
+
if (packageManager === 'npm') {
|
|
765
|
+
return ['install', '-D', ...packages];
|
|
766
|
+
}
|
|
767
|
+
return packageManager === 'pnpm' && pnpmWorkspaceGuard.shouldIgnoreWorkspace
|
|
768
|
+
? ['add', '-D', ...packages, '--ignore-workspace']
|
|
769
|
+
: ['add', '-D', ...packages];
|
|
770
|
+
}
|
|
771
|
+
function detectPackageManager(rootDir) {
|
|
772
|
+
// Prefer lockfiles to avoid guessing when multiple package managers are installed.
|
|
773
|
+
if ((0, node_fs_1.existsSync)(node_path_1.default.join(rootDir, 'pnpm-lock.yaml'))) {
|
|
774
|
+
return 'pnpm';
|
|
775
|
+
}
|
|
776
|
+
if ((0, node_fs_1.existsSync)(node_path_1.default.join(rootDir, 'yarn.lock'))) {
|
|
777
|
+
return 'yarn';
|
|
778
|
+
}
|
|
779
|
+
if ((0, node_fs_1.existsSync)(node_path_1.default.join(rootDir, 'package-lock.json'))) {
|
|
780
|
+
return 'npm';
|
|
781
|
+
}
|
|
782
|
+
// Fall back to pnpm because rawsql-ts itself standardizes on pnpm.
|
|
783
|
+
return 'pnpm';
|
|
784
|
+
}
|
|
785
|
+
function extractPackageName(specifier) {
|
|
786
|
+
if (specifier.startsWith('.') ||
|
|
787
|
+
specifier.startsWith('/') ||
|
|
788
|
+
specifier.startsWith('node:') ||
|
|
789
|
+
specifier.startsWith('#')) {
|
|
790
|
+
return null;
|
|
791
|
+
}
|
|
792
|
+
if (specifier.startsWith('@')) {
|
|
793
|
+
const [scope, name] = specifier.split('/');
|
|
794
|
+
if (!scope || !name) {
|
|
795
|
+
return null;
|
|
796
|
+
}
|
|
797
|
+
return `${scope}/${name}`;
|
|
798
|
+
}
|
|
799
|
+
const [name] = specifier.split('/');
|
|
800
|
+
return name || null;
|
|
801
|
+
}
|
|
802
|
+
function listReferencedPackagesFromSource(source) {
|
|
803
|
+
const packages = new Set();
|
|
804
|
+
const patterns = [
|
|
805
|
+
// Capture ESM imports and re-exports, including `import type`.
|
|
806
|
+
/\bfrom\s+['"]([^'"]+)['"]/g,
|
|
807
|
+
// Capture dynamic imports.
|
|
808
|
+
/\bimport\s*\(\s*['"]([^'"]+)['"]\s*\)/g,
|
|
809
|
+
// Capture CommonJS requires.
|
|
810
|
+
/\brequire\s*\(\s*['"]([^'"]+)['"]\s*\)/g
|
|
811
|
+
];
|
|
812
|
+
for (const pattern of patterns) {
|
|
813
|
+
for (const match of source.matchAll(pattern)) {
|
|
814
|
+
const specifier = match[1];
|
|
815
|
+
if (!specifier) {
|
|
816
|
+
continue;
|
|
817
|
+
}
|
|
818
|
+
const packageName = extractPackageName(specifier);
|
|
819
|
+
if (!packageName) {
|
|
820
|
+
continue;
|
|
821
|
+
}
|
|
822
|
+
packages.add(packageName);
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
return [...packages];
|
|
826
|
+
}
|
|
827
|
+
function listDeclaredPackages(rootDir) {
|
|
828
|
+
const packagePath = node_path_1.default.join(rootDir, 'package.json');
|
|
829
|
+
if (!(0, node_fs_1.existsSync)(packagePath)) {
|
|
830
|
+
return new Set();
|
|
831
|
+
}
|
|
832
|
+
const parsed = JSON.parse((0, node_fs_1.readFileSync)(packagePath, 'utf8'));
|
|
833
|
+
const keys = ['dependencies', 'devDependencies', 'peerDependencies', 'optionalDependencies'];
|
|
834
|
+
const declared = new Set();
|
|
835
|
+
for (const key of keys) {
|
|
836
|
+
const record = parsed[key];
|
|
837
|
+
if (!record || typeof record !== 'object') {
|
|
838
|
+
continue;
|
|
839
|
+
}
|
|
840
|
+
for (const name of Object.keys(record)) {
|
|
841
|
+
declared.add(name);
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
return declared;
|
|
845
|
+
}
|
|
846
|
+
function listTemplateReferencedPackages(absolutePaths, summaries) {
|
|
847
|
+
const packages = new Set();
|
|
848
|
+
const touchedKeys = Object.entries(summaries)
|
|
849
|
+
.filter((entry) => Boolean(entry[1]))
|
|
850
|
+
.filter(([, summary]) => summary.outcome === 'created' || summary.outcome === 'overwritten')
|
|
851
|
+
.map(([key]) => key);
|
|
852
|
+
for (const key of touchedKeys) {
|
|
853
|
+
const filePath = absolutePaths[key];
|
|
854
|
+
if (!filePath.endsWith('.ts') && !filePath.endsWith('.tsx') && !filePath.endsWith('.js')) {
|
|
855
|
+
continue;
|
|
856
|
+
}
|
|
857
|
+
if (!(0, node_fs_1.existsSync)(filePath)) {
|
|
858
|
+
continue;
|
|
859
|
+
}
|
|
860
|
+
// Parse template output after it is written so the detected packages match the emitted scaffold exactly.
|
|
861
|
+
const contents = (0, node_fs_1.readFileSync)(filePath, 'utf8');
|
|
862
|
+
listReferencedPackagesFromSource(contents).forEach((name) => packages.add(name));
|
|
863
|
+
}
|
|
864
|
+
return [...packages].sort();
|
|
865
|
+
}
|
|
866
|
+
async function ensureTemplateDependenciesInstalled(rootDir, absolutePaths, summaries, dependencies) {
|
|
867
|
+
var _a, _b;
|
|
868
|
+
const packageJsonPath = node_path_1.default.join(rootDir, 'package.json');
|
|
869
|
+
if (!dependencies.fileExists(packageJsonPath)) {
|
|
870
|
+
dependencies.log('Skipping dependency installation because package.json is missing. Next: run pnpm init (or npm init), install dependencies, then run npx ztd ztd-config.');
|
|
871
|
+
return;
|
|
872
|
+
}
|
|
873
|
+
const packageManager = detectPackageManager(rootDir);
|
|
874
|
+
const installStrategy = resolveInitInstallStrategy(rootDir, packageManager);
|
|
875
|
+
if (installStrategy.workspaceGuard.shouldIgnoreWorkspace) {
|
|
876
|
+
dependencies.log(`Detected parent pnpm workspace at ${installStrategy.workspaceGuard.workspaceRoot}. Running pnpm with --ignore-workspace so this initialized project keeps isolated installs.`);
|
|
877
|
+
}
|
|
878
|
+
const referencedPackages = listTemplateReferencedPackages(absolutePaths, summaries);
|
|
879
|
+
const declaredPackages = listDeclaredPackages(rootDir);
|
|
880
|
+
// Install only packages that are not declared yet to avoid unintentionally bumping pinned versions.
|
|
881
|
+
const missingPackages = referencedPackages.filter((name) => !declaredPackages.has(name));
|
|
882
|
+
if (missingPackages.length > 0) {
|
|
883
|
+
if (installStrategy.shouldDeferAutoInstall) {
|
|
884
|
+
const manualAddCommand = buildManualInstallCommand('devDependencies', packageManager, missingPackages, rootDir);
|
|
885
|
+
dependencies.log(`Skipping automatic ${manualAddCommand} because Windows pnpm exec can break the current ztd process after package.json changes. Next: run ${manualAddCommand} manually.`);
|
|
886
|
+
return;
|
|
887
|
+
}
|
|
888
|
+
dependencies.log(`Installing devDependencies referenced by templates (${packageManager}): ${missingPackages.join(', ')}`);
|
|
889
|
+
await dependencies.installPackages({
|
|
890
|
+
rootDir,
|
|
891
|
+
kind: 'devDependencies',
|
|
892
|
+
packages: missingPackages,
|
|
893
|
+
packageManager
|
|
894
|
+
});
|
|
895
|
+
return;
|
|
896
|
+
}
|
|
897
|
+
// Avoid mutating the current pnpm exec shim on Windows while it is still executing this command.
|
|
898
|
+
if (((_a = summaries.package) === null || _a === void 0 ? void 0 : _a.outcome) === 'created' || ((_b = summaries.package) === null || _b === void 0 ? void 0 : _b.outcome) === 'overwritten') {
|
|
899
|
+
if (installStrategy.shouldDeferAutoInstall) {
|
|
900
|
+
dependencies.log(`Skipping automatic ${installStrategy.installCommand} because Windows pnpm exec can break the current ztd process after package.json changes. Next: run ${installStrategy.installCommand} manually.`);
|
|
901
|
+
return;
|
|
902
|
+
}
|
|
903
|
+
dependencies.log(`Running ${installStrategy.installCommand} to sync dependencies.`);
|
|
904
|
+
await dependencies.installPackages({ rootDir, kind: 'install', packages: [], packageManager });
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
function copyTemplateFileIfMissing(rootDir, relative, templateName, dependencies) {
|
|
908
|
+
const templatePath = node_path_1.default.join(TEMPLATE_DIRECTORY, templateName);
|
|
909
|
+
// Skip copying when the CLI package does not include the requested template.
|
|
910
|
+
if (!(0, node_fs_1.existsSync)(templatePath)) {
|
|
911
|
+
return null;
|
|
912
|
+
}
|
|
913
|
+
const targetPath = node_path_1.default.join(rootDir, relative);
|
|
914
|
+
// Avoid overwriting a file that the project already maintains.
|
|
915
|
+
if (dependencies.fileExists(targetPath)) {
|
|
916
|
+
return null;
|
|
917
|
+
}
|
|
918
|
+
dependencies.ensureDirectory(node_path_1.default.dirname(targetPath));
|
|
919
|
+
// Emit the template content so the generated project gets the same formatting defaults.
|
|
920
|
+
dependencies.writeFile(targetPath, (0, node_fs_1.readFileSync)(templatePath, 'utf8'));
|
|
921
|
+
return { relativePath: relative, outcome: 'created' };
|
|
922
|
+
}
|
|
923
|
+
function writeOptionalTemplateFile(absolutePath, relative, templateName, dependencies) {
|
|
924
|
+
const templatePath = node_path_1.default.join(TEMPLATE_DIRECTORY, templateName);
|
|
925
|
+
// Skip when the template is missing from the installed package.
|
|
926
|
+
if (!(0, node_fs_1.existsSync)(templatePath)) {
|
|
927
|
+
return null;
|
|
928
|
+
}
|
|
929
|
+
if (dependencies.fileExists(absolutePath)) {
|
|
930
|
+
// Preserve existing files for opt-in scaffolds without prompting.
|
|
931
|
+
dependencies.log(`Skipping ${relative} because the file already exists.`);
|
|
932
|
+
return { relativePath: relative, outcome: 'unchanged' };
|
|
933
|
+
}
|
|
934
|
+
dependencies.ensureDirectory(node_path_1.default.dirname(absolutePath));
|
|
935
|
+
dependencies.writeFile(absolutePath, (0, node_fs_1.readFileSync)(templatePath, 'utf8'));
|
|
936
|
+
return { relativePath: relative, outcome: 'created' };
|
|
937
|
+
}
|
|
938
|
+
function ensurePackageJsonFormatting(rootDir, relative, dependencies, optionalFeatures, scaffoldProfile) {
|
|
939
|
+
var _a, _b;
|
|
940
|
+
const packagePath = node_path_1.default.join(rootDir, 'package.json');
|
|
941
|
+
const packageExists = dependencies.fileExists(packagePath);
|
|
942
|
+
const parsed = packageExists
|
|
943
|
+
? JSON.parse((0, node_fs_1.readFileSync)(packagePath, 'utf8'))
|
|
944
|
+
: {
|
|
945
|
+
name: inferPackageName(rootDir),
|
|
946
|
+
version: '0.0.0',
|
|
947
|
+
private: true
|
|
948
|
+
};
|
|
949
|
+
let changed = false;
|
|
950
|
+
const scripts = (_a = parsed.scripts) !== null && _a !== void 0 ? _a : {};
|
|
951
|
+
const requiredScripts = {
|
|
952
|
+
test: 'vitest run',
|
|
953
|
+
typecheck: 'tsc --noEmit',
|
|
954
|
+
format: 'prettier . --write',
|
|
955
|
+
lint: 'eslint .',
|
|
956
|
+
'lint:fix': 'eslint . --fix'
|
|
957
|
+
};
|
|
958
|
+
if (scaffoldProfile.dependencyProfile === 'local-source') {
|
|
959
|
+
// Route test and typecheck through the local-source guard so parent workspaces cannot silently hijack execution.
|
|
960
|
+
requiredScripts.test = 'node ./scripts/local-source-guard.mjs test';
|
|
961
|
+
requiredScripts.typecheck = 'node ./scripts/local-source-guard.mjs typecheck';
|
|
962
|
+
requiredScripts.ztd = 'node ./scripts/local-source-guard.mjs ztd';
|
|
963
|
+
}
|
|
964
|
+
// Ensure the canonical formatting and lint scripts exist without overwriting custom commands.
|
|
965
|
+
for (const [name, value] of Object.entries(requiredScripts)) {
|
|
966
|
+
if (name in scripts) {
|
|
967
|
+
continue;
|
|
968
|
+
}
|
|
969
|
+
scripts[name] = value;
|
|
970
|
+
changed = true;
|
|
971
|
+
}
|
|
972
|
+
if (changed) {
|
|
973
|
+
parsed.scripts = scripts;
|
|
974
|
+
}
|
|
975
|
+
// Provide lint-staged wiring for the formatting pipeline when no configuration is present.
|
|
976
|
+
if (!('lint-staged' in parsed)) {
|
|
977
|
+
parsed['lint-staged'] = {
|
|
978
|
+
'*.{ts,tsx,js,jsx,json,md,sql}': ['pnpm format']
|
|
979
|
+
};
|
|
980
|
+
changed = true;
|
|
981
|
+
}
|
|
982
|
+
// Wire simple-git-hooks only if the user has not already customized it.
|
|
983
|
+
if (!('simple-git-hooks' in parsed)) {
|
|
984
|
+
parsed['simple-git-hooks'] = {
|
|
985
|
+
'pre-commit': 'pnpm lint-staged'
|
|
986
|
+
};
|
|
987
|
+
changed = true;
|
|
988
|
+
}
|
|
989
|
+
const devDependencies = (_b = parsed.devDependencies) !== null && _b !== void 0 ? _b : {};
|
|
990
|
+
const formattingDeps = {
|
|
991
|
+
eslint: '^9.22.0',
|
|
992
|
+
'lint-staged': '^16.2.7',
|
|
993
|
+
'prettier': '^3.7.4',
|
|
994
|
+
'prettier-plugin-sql': '^0.19.2',
|
|
995
|
+
'simple-git-hooks': '^2.13.1'
|
|
996
|
+
};
|
|
997
|
+
const testingDeps = {
|
|
998
|
+
vitest: '^4.0.7',
|
|
999
|
+
typescript: '^5.8.2',
|
|
1000
|
+
'@types/node': '^22.13.10'
|
|
1001
|
+
};
|
|
1002
|
+
// Add the formatting toolchain dependencies that back the scripts and hooks.
|
|
1003
|
+
for (const [dep, version] of Object.entries(formattingDeps)) {
|
|
1004
|
+
if (dep in devDependencies) {
|
|
1005
|
+
continue;
|
|
1006
|
+
}
|
|
1007
|
+
devDependencies[dep] = version;
|
|
1008
|
+
changed = true;
|
|
1009
|
+
}
|
|
1010
|
+
const stackDependencies = scaffoldProfile.dependencyProfile === 'local-source'
|
|
1011
|
+
? buildLocalSourceStackDependencies(rootDir, scaffoldProfile)
|
|
1012
|
+
: {
|
|
1013
|
+
...STACK_DEV_DEPENDENCIES
|
|
1014
|
+
};
|
|
1015
|
+
if (optionalFeatures.validator === 'zod') {
|
|
1016
|
+
Object.assign(stackDependencies, ZOD_DEPENDENCY);
|
|
1017
|
+
}
|
|
1018
|
+
else {
|
|
1019
|
+
Object.assign(stackDependencies, ARKTYPE_DEPENDENCY);
|
|
1020
|
+
}
|
|
1021
|
+
// Ensure test and typecheck toolchain dependencies are present for a runnable scaffold.
|
|
1022
|
+
for (const [dep, version] of Object.entries(testingDeps)) {
|
|
1023
|
+
if (dep in devDependencies) {
|
|
1024
|
+
continue;
|
|
1025
|
+
}
|
|
1026
|
+
devDependencies[dep] = version;
|
|
1027
|
+
changed = true;
|
|
1028
|
+
}
|
|
1029
|
+
for (const [dep, version] of Object.entries(stackDependencies)) {
|
|
1030
|
+
if (dep in devDependencies) {
|
|
1031
|
+
continue;
|
|
1032
|
+
}
|
|
1033
|
+
devDependencies[dep] = version;
|
|
1034
|
+
changed = true;
|
|
1035
|
+
}
|
|
1036
|
+
if (!changed) {
|
|
1037
|
+
return null;
|
|
1038
|
+
}
|
|
1039
|
+
parsed.devDependencies = devDependencies;
|
|
1040
|
+
dependencies.ensureDirectory(node_path_1.default.dirname(packagePath));
|
|
1041
|
+
// Persist the updated manifest so the new scripts and tools are available immediately.
|
|
1042
|
+
dependencies.writeFile(packagePath, `${JSON.stringify(parsed, null, 2)}\n`);
|
|
1043
|
+
return { relativePath: relative, outcome: packageExists ? 'overwritten' : 'created' };
|
|
1044
|
+
}
|
|
1045
|
+
function inferPackageName(rootDir) {
|
|
1046
|
+
const baseName = node_path_1.default.basename(rootDir).toLowerCase();
|
|
1047
|
+
const normalized = baseName.replace(/[^a-z0-9._-]+/g, '-').replace(/^-+|-+$/g, '');
|
|
1048
|
+
if (normalized.length > 0) {
|
|
1049
|
+
return normalized;
|
|
1050
|
+
}
|
|
1051
|
+
return 'ztd-project';
|
|
1052
|
+
}
|
|
1053
|
+
async function writeFileWithConsent(absolutePath, relative, dependencies, prompter, overwritePolicy, writer) {
|
|
1054
|
+
const { existed, write } = await confirmOverwriteIfExists(absolutePath, relative, dependencies, prompter, overwritePolicy);
|
|
1055
|
+
if (!write) {
|
|
1056
|
+
return { relativePath: relative, outcome: 'unchanged' };
|
|
1057
|
+
}
|
|
1058
|
+
await writer();
|
|
1059
|
+
return { relativePath: relative, outcome: existed ? 'overwritten' : 'created' };
|
|
1060
|
+
}
|
|
1061
|
+
async function confirmOverwriteIfExists(absolutePath, relative, dependencies, prompter, overwritePolicy) {
|
|
1062
|
+
const existed = dependencies.fileExists(absolutePath);
|
|
1063
|
+
if (!existed) {
|
|
1064
|
+
return { existed: false, write: true };
|
|
1065
|
+
}
|
|
1066
|
+
if (overwritePolicy.force) {
|
|
1067
|
+
return { existed: true, write: true };
|
|
1068
|
+
}
|
|
1069
|
+
if (overwritePolicy.nonInteractive) {
|
|
1070
|
+
throw new Error(`File ${relative} already exists. Re-run with --force to overwrite or remove the file before running ztd init.`);
|
|
1071
|
+
}
|
|
1072
|
+
const overwrite = await prompter.confirm(`File ${relative} already exists. Overwrite?`);
|
|
1073
|
+
if (!overwrite) {
|
|
1074
|
+
return { existed: true, write: false };
|
|
1075
|
+
}
|
|
1076
|
+
return { existed: true, write: true };
|
|
1077
|
+
}
|
|
1078
|
+
async function writeDocFile(absolutePath, relative, contents, dependencies, prompter, overwritePolicy) {
|
|
1079
|
+
const summary = await writeFileWithConsent(absolutePath, relative, dependencies, prompter, overwritePolicy, () => {
|
|
1080
|
+
dependencies.ensureDirectory(node_path_1.default.dirname(absolutePath));
|
|
1081
|
+
dependencies.writeFile(absolutePath, contents);
|
|
1082
|
+
});
|
|
1083
|
+
return summary;
|
|
1084
|
+
}
|
|
1085
|
+
async function writeTemplateFile(rootDir, absolutePath, relative, templateName, dependencies, prompter, overwritePolicy, allowFallback) {
|
|
1086
|
+
const templateTarget = resolveTemplateTarget(rootDir, absolutePath, relative, dependencies, allowFallback);
|
|
1087
|
+
if (!templateTarget) {
|
|
1088
|
+
return null;
|
|
1089
|
+
}
|
|
1090
|
+
// Load shared documentation templates so every new project gets the same guidance.
|
|
1091
|
+
const contents = loadTemplate(templateName);
|
|
1092
|
+
return writeDocFile(templateTarget.absolutePath, templateTarget.relativePath, contents, dependencies, prompter, overwritePolicy);
|
|
1093
|
+
}
|
|
1094
|
+
function resolveTemplateTarget(rootDir, absolutePath, relative, dependencies, allowFallback) {
|
|
1095
|
+
if (!dependencies.fileExists(absolutePath)) {
|
|
1096
|
+
return { absolutePath, relativePath: relative };
|
|
1097
|
+
}
|
|
1098
|
+
if (!allowFallback || !isRootMarkdown(relative)) {
|
|
1099
|
+
dependencies.log(`Skipping template ${relative} because the target file already exists.`);
|
|
1100
|
+
return null;
|
|
1101
|
+
}
|
|
1102
|
+
// When the preferred destination already exists, try emitting a sibling with a "_ztd" suffix.
|
|
1103
|
+
const parsed = node_path_1.default.parse(absolutePath);
|
|
1104
|
+
const fallbackAbsolute = node_path_1.default.join(parsed.dir, `${parsed.name}_ztd${parsed.ext}`);
|
|
1105
|
+
if (dependencies.fileExists(fallbackAbsolute)) {
|
|
1106
|
+
const existingRelative = normalizeRelative(rootDir, fallbackAbsolute);
|
|
1107
|
+
dependencies.log(`Skipping template ${relative} because both ${relative} and ${existingRelative} already exist.`);
|
|
1108
|
+
return null;
|
|
1109
|
+
}
|
|
1110
|
+
const fallbackRelative = normalizeRelative(rootDir, fallbackAbsolute);
|
|
1111
|
+
dependencies.log(`Existing ${relative} preserved; writing template as ${fallbackRelative}.`);
|
|
1112
|
+
return { absolutePath: fallbackAbsolute, relativePath: fallbackRelative };
|
|
1113
|
+
}
|
|
1114
|
+
function normalizeRelative(rootDir, absolutePath) {
|
|
1115
|
+
// Normalize the path relative to the project root so summaries use forward slashes.
|
|
1116
|
+
const relative = normalizeCliPath(node_path_1.default.relative(rootDir, absolutePath));
|
|
1117
|
+
return relative || absolutePath;
|
|
1118
|
+
}
|
|
1119
|
+
function normalizeCliPath(filePath) {
|
|
1120
|
+
return filePath.replace(/\\/g, '/');
|
|
1121
|
+
}
|
|
1122
|
+
/**
|
|
1123
|
+
* Normalizes a schema identifier into the canonical lowercase form used by ztd-cli file naming.
|
|
1124
|
+
* Empty input falls back to the configured default schema.
|
|
1125
|
+
*/
|
|
1126
|
+
function normalizeSchemaName(value) {
|
|
1127
|
+
const trimmed = value.trim();
|
|
1128
|
+
if (!trimmed) {
|
|
1129
|
+
return ztdProjectConfig_1.DEFAULT_ZTD_CONFIG.ddl.defaultSchema;
|
|
1130
|
+
}
|
|
1131
|
+
return trimmed.replace(/^"|"$/g, '').toLowerCase();
|
|
1132
|
+
}
|
|
1133
|
+
/**
|
|
1134
|
+
* Sanitizes a normalized schema identifier so it can be used as a filesystem-safe file stem.
|
|
1135
|
+
* Returns `schema` when all characters are stripped by sanitization.
|
|
1136
|
+
*/
|
|
1137
|
+
function sanitizeSchemaFileName(schemaName) {
|
|
1138
|
+
const sanitized = schemaName.replace(/[^a-z0-9_-]/g, '_').replace(/^_+|_+$/g, '');
|
|
1139
|
+
return sanitized || 'schema';
|
|
1140
|
+
}
|
|
1141
|
+
function resolveOrCreateAgentsFile(rootDir, dependencies) {
|
|
1142
|
+
// Prefer materializing the bundled template before looking for existing attention files.
|
|
1143
|
+
const templateTarget = dependencies.copyAgentsTemplate(rootDir);
|
|
1144
|
+
if (templateTarget) {
|
|
1145
|
+
return { absolutePath: templateTarget, created: true };
|
|
1146
|
+
}
|
|
1147
|
+
for (const candidate of AGENTS_FILE_CANDIDATES) {
|
|
1148
|
+
const candidatePath = node_path_1.default.join(rootDir, candidate);
|
|
1149
|
+
if (dependencies.fileExists(candidatePath)) {
|
|
1150
|
+
return { absolutePath: candidatePath, created: false };
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
return null;
|
|
1154
|
+
}
|
|
1155
|
+
async function appendAppInterfaceGuidance(rootDir, dependencies) {
|
|
1156
|
+
const resolution = resolveOrCreateAgentsFile(rootDir, dependencies);
|
|
1157
|
+
if (!resolution) {
|
|
1158
|
+
throw new Error('Failed to locate or create an AGENTS file for application guidance.');
|
|
1159
|
+
}
|
|
1160
|
+
const relativePath = normalizeRelative(rootDir, resolution.absolutePath);
|
|
1161
|
+
const existingContents = (0, node_fs_1.readFileSync)(resolution.absolutePath, 'utf8');
|
|
1162
|
+
// Skip appending when the guidance section already exists to avoid duplicates.
|
|
1163
|
+
if (existingContents.includes(APP_INTERFACE_SECTION_MARKER)) {
|
|
1164
|
+
return { relativePath, outcome: 'unchanged' };
|
|
1165
|
+
}
|
|
1166
|
+
// Ensure the appended block is separated by blank lines for readability.
|
|
1167
|
+
const baseline = existingContents.endsWith('\n') ? existingContents : `${existingContents}\n`;
|
|
1168
|
+
const spacer = baseline.endsWith('\n\n') ? '' : '\n';
|
|
1169
|
+
dependencies.writeFile(resolution.absolutePath, `${baseline}${spacer}${APP_INTERFACE_SECTION}\n`);
|
|
1170
|
+
return { relativePath, outcome: 'overwritten' };
|
|
1171
|
+
}
|
|
1172
|
+
function isRootMarkdown(relative) {
|
|
1173
|
+
return relative.toLowerCase().endsWith('.md') && !relative.includes('/');
|
|
1174
|
+
}
|
|
1175
|
+
function loadTemplate(templateName) {
|
|
1176
|
+
const templatePath = node_path_1.default.join(TEMPLATE_DIRECTORY, templateName);
|
|
1177
|
+
// Fail fast if the template bundle was shipped without the requested file.
|
|
1178
|
+
if (!(0, node_fs_1.existsSync)(templatePath)) {
|
|
1179
|
+
throw new Error(`Missing template file: ${templateName}`);
|
|
1180
|
+
}
|
|
1181
|
+
return (0, node_fs_1.readFileSync)(templatePath, 'utf8');
|
|
1182
|
+
}
|
|
1183
|
+
function buildLocalSourceGuardContents(absolutePath, scaffoldProfile) {
|
|
1184
|
+
const template = loadTemplate(LOCAL_SOURCE_GUARD_TEMPLATE);
|
|
1185
|
+
if (scaffoldProfile.dependencyProfile !== 'local-source' || !scaffoldProfile.localSourceRoot) {
|
|
1186
|
+
return template.replace('__LOCAL_SOURCE_ZTD_CLI__', './packages/ztd-cli/dist/index.js');
|
|
1187
|
+
}
|
|
1188
|
+
const cliEntry = node_path_1.default.join(scaffoldProfile.localSourceRoot, 'packages', 'ztd-cli', 'dist', 'index.js');
|
|
1189
|
+
const projectRoot = node_path_1.default.dirname(node_path_1.default.dirname(absolutePath));
|
|
1190
|
+
const relativeCliEntry = normalizeCliPath(node_path_1.default.relative(projectRoot, cliEntry));
|
|
1191
|
+
const cliImportPath = relativeCliEntry.startsWith('.') || relativeCliEntry.startsWith('/') ? relativeCliEntry : `./${relativeCliEntry}`;
|
|
1192
|
+
return template.replace('__LOCAL_SOURCE_ZTD_CLI__', cliImportPath);
|
|
1193
|
+
}
|
|
1194
|
+
function buildNextSteps(schemaRelativePath, workflow, rootDir, scaffoldProfile, appShape) {
|
|
1195
|
+
const packageManager = detectPackageManager(rootDir);
|
|
1196
|
+
const installStrategy = resolveInitInstallStrategy(rootDir, packageManager);
|
|
1197
|
+
const installCommand = installStrategy.installCommand;
|
|
1198
|
+
const runScriptCommand = (script) => packageManager === 'npm' ? `npm run ${script}` : `${packageManager} ${script}`;
|
|
1199
|
+
const runLocalSourceZtdCommand = packageManager === 'npm' ? 'npm run ztd --' : `${packageManager} ztd`;
|
|
1200
|
+
const ztdCommand = packageManager === 'npm' ? 'npx ztd' : packageManager === 'yarn' ? 'yarn exec ztd' : 'pnpm exec ztd';
|
|
1201
|
+
if (scaffoldProfile.dependencyProfile === 'local-source') {
|
|
1202
|
+
const localSourceSteps = [
|
|
1203
|
+
workflow === 'pg_dump'
|
|
1204
|
+
? `Review the dumped DDL in ${schemaRelativePath}`
|
|
1205
|
+
: `If the schema file is empty, edit ${schemaRelativePath}`,
|
|
1206
|
+
`Run ${installCommand}`,
|
|
1207
|
+
`Run ${runScriptCommand('typecheck')}`,
|
|
1208
|
+
`Run ${runScriptCommand('test')}`,
|
|
1209
|
+
`Run ${runLocalSourceZtdCommand} ztd-config`,
|
|
1210
|
+
'Generated QuerySpecs can keep the default @rawsql-ts/sql-contract package import.',
|
|
1211
|
+
appShape === 'webapi'
|
|
1212
|
+
? 'Keep Domain, Application, and Presentation changes free from direct ZTD assumptions'
|
|
1213
|
+
: 'Keep handwritten SQL and QuerySpecs aligned before adding repository code',
|
|
1214
|
+
'Wire repositories to src/infrastructure/telemetry/repositoryTelemetry.ts so applications can replace the default hook',
|
|
1215
|
+
'Provide a SqlClient implementation before adding SQL-backed tests'
|
|
1216
|
+
];
|
|
1217
|
+
return localSourceSteps.map((step, index) => ` ${index + 1}. ${step}`);
|
|
1218
|
+
}
|
|
1219
|
+
const nextSteps = [
|
|
1220
|
+
workflow === 'pg_dump'
|
|
1221
|
+
? `Review the dumped DDL in ${schemaRelativePath}`
|
|
1222
|
+
: `If the schema file is empty, edit ${schemaRelativePath}`
|
|
1223
|
+
];
|
|
1224
|
+
if (installStrategy.shouldDeferAutoInstall) {
|
|
1225
|
+
nextSteps.push(`Run ${installCommand}`);
|
|
1226
|
+
}
|
|
1227
|
+
nextSteps.push(`Run ${ztdCommand} ztd-config`);
|
|
1228
|
+
if (appShape === 'webapi') {
|
|
1229
|
+
nextSteps.push('Keep WebAPI flow changes in src/domain, src/application, and src/presentation/http unless you are editing persistence infrastructure');
|
|
1230
|
+
}
|
|
1231
|
+
nextSteps.push('Wire repositories to src/infrastructure/telemetry/repositoryTelemetry.ts when you add SQL-backed repository classes');
|
|
1232
|
+
nextSteps.push('Provide a SqlClient implementation (adapter or mock)');
|
|
1233
|
+
nextSteps.push('Run tests (pnpm test or npx vitest run)');
|
|
1234
|
+
// Avoid repeating the same install hint when the deferred-install path already emitted it explicitly.
|
|
1235
|
+
if (installStrategy.workspaceGuard.shouldIgnoreWorkspace && !installStrategy.shouldDeferAutoInstall) {
|
|
1236
|
+
nextSteps.push('This project is nested under a parent pnpm workspace; use pnpm install --ignore-workspace for manual installs.');
|
|
1237
|
+
}
|
|
1238
|
+
return nextSteps.map((step, index) => ` ${index + 1}. ${step}`);
|
|
1239
|
+
}
|
|
1240
|
+
function resolveInitScaffoldProfile(rootDir, localSourceRoot) {
|
|
1241
|
+
if (!localSourceRoot) {
|
|
1242
|
+
return { dependencyProfile: 'registry', localSourceRoot: null };
|
|
1243
|
+
}
|
|
1244
|
+
const resolvedRoot = node_path_1.default.resolve(rootDir, localSourceRoot);
|
|
1245
|
+
// Validate every direct rawsql-ts scaffold dependency up front so local-source installs
|
|
1246
|
+
// cannot silently mix local packages with registry packages.
|
|
1247
|
+
for (const packageDir of Object.values(LOCAL_SOURCE_STACK_PACKAGE_DIRS)) {
|
|
1248
|
+
const packageJsonPath = node_path_1.default.join(resolvedRoot, packageDir, 'package.json');
|
|
1249
|
+
if (!(0, node_fs_1.existsSync)(packageJsonPath)) {
|
|
1250
|
+
throw new Error(`The local-source root does not contain ${normalizeCliPath(node_path_1.default.join(packageDir, 'package.json'))}: ${normalizeCliPath(resolvedRoot)}`);
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
return {
|
|
1254
|
+
dependencyProfile: 'local-source',
|
|
1255
|
+
localSourceRoot: resolvedRoot
|
|
1256
|
+
};
|
|
1257
|
+
}
|
|
1258
|
+
function buildLocalSourceStackDependencies(rootDir, scaffoldProfile) {
|
|
1259
|
+
if (scaffoldProfile.dependencyProfile !== 'local-source' || !scaffoldProfile.localSourceRoot) {
|
|
1260
|
+
return {
|
|
1261
|
+
...STACK_DEV_DEPENDENCIES
|
|
1262
|
+
};
|
|
1263
|
+
}
|
|
1264
|
+
return Object.fromEntries(Object.entries(LOCAL_SOURCE_STACK_PACKAGE_DIRS).map(([packageName, packageDir]) => [
|
|
1265
|
+
packageName,
|
|
1266
|
+
toFileDependencySpecifier(rootDir, node_path_1.default.join(scaffoldProfile.localSourceRoot, packageDir))
|
|
1267
|
+
]));
|
|
1268
|
+
}
|
|
1269
|
+
function toFileDependencySpecifier(rootDir, targetDir) {
|
|
1270
|
+
const relativeTarget = normalizeCliPath(node_path_1.default.relative(rootDir, targetDir));
|
|
1271
|
+
const withDotPrefix = relativeTarget.startsWith('.') || relativeTarget.startsWith('/') ? relativeTarget : `./${relativeTarget}`;
|
|
1272
|
+
return `file:${withDotPrefix}`;
|
|
1273
|
+
}
|
|
1274
|
+
function buildSummaryLines(summaries, optionalFeatures, nextSteps, scaffoldProfile) {
|
|
1275
|
+
const orderedKeys = [
|
|
1276
|
+
'schema',
|
|
1277
|
+
'config',
|
|
1278
|
+
'readme',
|
|
1279
|
+
'context',
|
|
1280
|
+
'promptDogfood',
|
|
1281
|
+
'internalAgentsManifest',
|
|
1282
|
+
'internalAgentsRoot',
|
|
1283
|
+
'internalAgentsSrc',
|
|
1284
|
+
'internalAgentsTests',
|
|
1285
|
+
'internalAgentsZtd',
|
|
1286
|
+
'ztdDocsReadme',
|
|
1287
|
+
'sqlReadme',
|
|
1288
|
+
'viewsRepoReadme',
|
|
1289
|
+
'tablesRepoReadme',
|
|
1290
|
+
'jobsReadme',
|
|
1291
|
+
'domainReadme',
|
|
1292
|
+
'applicationReadme',
|
|
1293
|
+
'presentationHttpReadme',
|
|
1294
|
+
'infrastructureReadme',
|
|
1295
|
+
'persistenceReadme',
|
|
1296
|
+
'smokeSpec',
|
|
1297
|
+
'smokeCoercions',
|
|
1298
|
+
'smokeRuntime',
|
|
1299
|
+
'localSourceGuardScript',
|
|
1300
|
+
'smokeValidationTest',
|
|
1301
|
+
'testsSmoke',
|
|
1302
|
+
'sqlClient',
|
|
1303
|
+
'sqlClientAdapters',
|
|
1304
|
+
'repositoryTelemetryTypes',
|
|
1305
|
+
'repositoryTelemetryConsole',
|
|
1306
|
+
'repositoryTelemetryEntry',
|
|
1307
|
+
'testkitClient',
|
|
1308
|
+
'globalSetup',
|
|
1309
|
+
'vitestConfig',
|
|
1310
|
+
'tsconfig',
|
|
1311
|
+
'gitignore',
|
|
1312
|
+
'editorconfig',
|
|
1313
|
+
'prettierignore',
|
|
1314
|
+
'prettier',
|
|
1315
|
+
'package'
|
|
1316
|
+
];
|
|
1317
|
+
const lines = ['ZTD project initialized.', '', 'Created:'];
|
|
1318
|
+
for (const key of orderedKeys) {
|
|
1319
|
+
const summary = summaries[key];
|
|
1320
|
+
const note = (summary === null || summary === void 0 ? void 0 : summary.outcome) === 'created'
|
|
1321
|
+
? ''
|
|
1322
|
+
: (summary === null || summary === void 0 ? void 0 : summary.outcome) === 'overwritten'
|
|
1323
|
+
? ' (overwritten existing file)'
|
|
1324
|
+
: ' (existing file preserved)';
|
|
1325
|
+
if (summary) {
|
|
1326
|
+
lines.push(` - ${summary.relativePath}${note}`);
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
lines.push('', 'Validation configuration:');
|
|
1330
|
+
const stackLine = scaffoldProfile.dependencyProfile === 'local-source'
|
|
1331
|
+
? ' - SQL catalog/mapping support via @rawsql-ts/sql-contract backed by a local file dependency in developer mode (see docs/recipes/sql-contract.md)'
|
|
1332
|
+
: ' - SQL catalog/mapping support via @rawsql-ts/sql-contract (see docs/recipes/sql-contract.md)';
|
|
1333
|
+
lines.push(stackLine);
|
|
1334
|
+
const validatorLabel = optionalFeatures.validator === 'zod'
|
|
1335
|
+
? 'Zod (zod, docs/recipes/validation-zod.md)'
|
|
1336
|
+
: 'ArkType (arktype, docs/recipes/validation-arktype.md)';
|
|
1337
|
+
lines.push(` - Validator backend: ${validatorLabel}`);
|
|
1338
|
+
if (optionalFeatures.aiGuidance) {
|
|
1339
|
+
lines.push('', 'AI guidance:');
|
|
1340
|
+
lines.push(' - Internal guidance is managed under .ztd/agents/.');
|
|
1341
|
+
lines.push(' - Visible AGENTS.md files are disabled by default. Enable with: ztd agents install');
|
|
1342
|
+
}
|
|
1343
|
+
lines.push('', 'Next steps:', ...nextSteps);
|
|
1344
|
+
return lines;
|
|
1345
|
+
}
|
|
1346
|
+
const VALID_WORKFLOWS = ['pg_dump', 'empty', 'demo'];
|
|
1347
|
+
const VALID_VALIDATORS = ['zod', 'arktype'];
|
|
1348
|
+
const VALID_APP_SHAPES = ['default', 'webapi'];
|
|
1349
|
+
function buildInitDryRunPlan(rootDir, options) {
|
|
1350
|
+
const schemaName = normalizeSchemaName(ztdProjectConfig_1.DEFAULT_ZTD_CONFIG.ddl.defaultSchema);
|
|
1351
|
+
const schemaFileName = `${sanitizeSchemaFileName(schemaName)}.sql`;
|
|
1352
|
+
const scaffoldLayout = resolveInitScaffoldLayout(rootDir, options.appShape);
|
|
1353
|
+
const files = [
|
|
1354
|
+
'ztd.config.json',
|
|
1355
|
+
node_path_1.default.join(ztdProjectConfig_1.DEFAULT_ZTD_CONFIG.ddlDir, schemaFileName),
|
|
1356
|
+
node_path_1.default.join(ztdProjectConfig_1.DEFAULT_ZTD_CONFIG.testsDir, 'smoke.test.ts'),
|
|
1357
|
+
node_path_1.default.join(ztdProjectConfig_1.DEFAULT_ZTD_CONFIG.testsDir, 'smoke.validation.test.ts'),
|
|
1358
|
+
node_path_1.default.join(ztdProjectConfig_1.DEFAULT_ZTD_CONFIG.testsDir, 'support', 'testkit-client.ts'),
|
|
1359
|
+
node_path_1.default.join(ztdProjectConfig_1.DEFAULT_ZTD_CONFIG.testsDir, 'support', 'global-setup.ts'),
|
|
1360
|
+
node_path_1.default.join(ztdProjectConfig_1.DEFAULT_ZTD_CONFIG.testsDir, 'generated', 'ztd-row-map.generated.ts'),
|
|
1361
|
+
node_path_1.default.join(ztdProjectConfig_1.DEFAULT_ZTD_CONFIG.testsDir, 'generated', 'ztd-layout.generated.ts'),
|
|
1362
|
+
'README.md',
|
|
1363
|
+
'src/sql/README.md',
|
|
1364
|
+
'vitest.config.ts',
|
|
1365
|
+
'tsconfig.json'
|
|
1366
|
+
];
|
|
1367
|
+
if (options.withAiGuidance) {
|
|
1368
|
+
files.push('.ztd/agents/manifest.json');
|
|
1369
|
+
files.push('.ztd/agents/root.md');
|
|
1370
|
+
files.push('.ztd/agents/src.md');
|
|
1371
|
+
files.push('.ztd/agents/tests.md');
|
|
1372
|
+
files.push('.ztd/agents/ztd.md');
|
|
1373
|
+
files.push('CONTEXT.md');
|
|
1374
|
+
}
|
|
1375
|
+
if (options.appShape === 'webapi') {
|
|
1376
|
+
if (options.withAiGuidance) {
|
|
1377
|
+
files.push('PROMPT_DOGFOOD.md');
|
|
1378
|
+
}
|
|
1379
|
+
files.push('src/domain/README.md');
|
|
1380
|
+
files.push('src/application/README.md');
|
|
1381
|
+
files.push('src/presentation/http/README.md');
|
|
1382
|
+
files.push('src/infrastructure/README.md');
|
|
1383
|
+
files.push('src/infrastructure/persistence/README.md');
|
|
1384
|
+
files.push('src/infrastructure/persistence/repositories/views/README.md');
|
|
1385
|
+
files.push('src/infrastructure/persistence/repositories/tables/README.md');
|
|
1386
|
+
}
|
|
1387
|
+
else {
|
|
1388
|
+
files.push('src/repositories/views/README.md');
|
|
1389
|
+
files.push('src/repositories/tables/README.md');
|
|
1390
|
+
files.push('src/jobs/README.md');
|
|
1391
|
+
}
|
|
1392
|
+
files.push(normalizeCliPath(node_path_1.default.relative(rootDir, scaffoldLayout.sqlClientPath)));
|
|
1393
|
+
files.push(normalizeCliPath(node_path_1.default.relative(rootDir, scaffoldLayout.sqlClientAdaptersPath)));
|
|
1394
|
+
if (options.withSqlClient) {
|
|
1395
|
+
files.push(node_path_1.default.join('src', 'infrastructure', 'telemetry', 'types.ts'));
|
|
1396
|
+
files.push(node_path_1.default.join('src', 'infrastructure', 'telemetry', 'consoleRepositoryTelemetry.ts'));
|
|
1397
|
+
files.push(node_path_1.default.join('src', 'infrastructure', 'telemetry', 'repositoryTelemetry.ts'));
|
|
1398
|
+
}
|
|
1399
|
+
if (options.withAppInterface) {
|
|
1400
|
+
files.splice(0, files.length, 'AGENTS.md');
|
|
1401
|
+
}
|
|
1402
|
+
if (options.localSourceRoot) {
|
|
1403
|
+
resolveInitScaffoldProfile(rootDir, options.localSourceRoot);
|
|
1404
|
+
}
|
|
1405
|
+
return {
|
|
1406
|
+
schemaVersion: 1,
|
|
1407
|
+
workflow: options.workflow,
|
|
1408
|
+
validator: options.validator,
|
|
1409
|
+
dryRun: true,
|
|
1410
|
+
files: files.map((file) => normalizeCliPath(file))
|
|
1411
|
+
};
|
|
1412
|
+
}
|
|
1413
|
+
function validateJsonBooleanFlag(value, flagName) {
|
|
1414
|
+
if (value === undefined || typeof value === 'boolean') {
|
|
1415
|
+
return true;
|
|
1416
|
+
}
|
|
1417
|
+
console.error(`Invalid --${flagName} value in --json payload. Expected a boolean.`);
|
|
1418
|
+
process.exit(1);
|
|
1419
|
+
}
|
|
1420
|
+
function registerInitCommand(program) {
|
|
1421
|
+
program
|
|
1422
|
+
.command('init')
|
|
1423
|
+
.description('Automate project setup for Zero Table Dependency workflows')
|
|
1424
|
+
.option('--app-shape <type>', 'Application scaffold shape: default or webapi (default: default)')
|
|
1425
|
+
.option('--with-ai-guidance', 'Generate internal AI guidance files such as CONTEXT.md and .ztd/agents/*')
|
|
1426
|
+
.option('--with-sqlclient', 'Generate a minimal SqlClient interface for repositories')
|
|
1427
|
+
.option('--with-app-interface', 'Append application interface guidance to AGENTS.md only')
|
|
1428
|
+
.option('--yes', 'Accept defaults without interactive prompts')
|
|
1429
|
+
.option('--force', 'Allow ztd init to overwrite files it owns')
|
|
1430
|
+
.option('--workflow <type>', 'Schema workflow: pg_dump, empty, or demo (default: demo)')
|
|
1431
|
+
.option('--validator <type>', 'Validator backend: zod or arktype (default: zod)')
|
|
1432
|
+
.option('--dry-run', 'Validate init options and emit the planned scaffold without writing files')
|
|
1433
|
+
.option('--json <payload>', 'Pass init options as a JSON object')
|
|
1434
|
+
.option('--local-source-root <path>', 'Link @rawsql-ts dependencies to a local monorepo root for dogfooding instead of published npm packages')
|
|
1435
|
+
.action(async (options) => {
|
|
1436
|
+
var _a, _b, _c;
|
|
1437
|
+
const merged = options.json ? { ...options, ...(0, agentCli_1.parseJsonPayload)(options.json, '--json') } : options;
|
|
1438
|
+
if (typeof merged.localSourceRoot === 'string') {
|
|
1439
|
+
merged.localSourceRoot = (0, agentSafety_1.rejectEncodedTraversal)((0, agentSafety_1.rejectControlChars)(merged.localSourceRoot, '--local-source-root'), '--local-source-root');
|
|
1440
|
+
}
|
|
1441
|
+
// Reject stringly-typed booleans from --json so unsupported payloads cannot silently enable features.
|
|
1442
|
+
validateJsonBooleanFlag(merged.yes, 'yes');
|
|
1443
|
+
validateJsonBooleanFlag(merged.dryRun, 'dry-run');
|
|
1444
|
+
validateJsonBooleanFlag(merged.force, 'force');
|
|
1445
|
+
validateJsonBooleanFlag(merged.withAiGuidance, 'with-ai-guidance');
|
|
1446
|
+
validateJsonBooleanFlag(merged.withSqlclient, 'with-sqlclient');
|
|
1447
|
+
validateJsonBooleanFlag(merged.withAppInterface, 'with-app-interface');
|
|
1448
|
+
// Validate --workflow value if provided.
|
|
1449
|
+
if (merged.workflow && !VALID_WORKFLOWS.includes(merged.workflow)) {
|
|
1450
|
+
console.error(`Invalid --workflow value: "${merged.workflow}". Must be one of: ${VALID_WORKFLOWS.join(', ')}`);
|
|
1451
|
+
process.exit(1);
|
|
1452
|
+
}
|
|
1453
|
+
// Validate --validator value if provided.
|
|
1454
|
+
if (merged.validator && !VALID_VALIDATORS.includes(merged.validator)) {
|
|
1455
|
+
console.error(`Invalid --validator value: "${merged.validator}". Must be one of: ${VALID_VALIDATORS.join(', ')}`);
|
|
1456
|
+
process.exit(1);
|
|
1457
|
+
}
|
|
1458
|
+
if (merged.appShape && !VALID_APP_SHAPES.includes(merged.appShape)) {
|
|
1459
|
+
console.error(`Invalid --app-shape value: "${merged.appShape}". Must be one of: ${VALID_APP_SHAPES.join(', ')}`);
|
|
1460
|
+
process.exit(1);
|
|
1461
|
+
}
|
|
1462
|
+
const isNonInteractive = merged.yes === true || !process.stdin.isTTY || merged.dryRun === true;
|
|
1463
|
+
// When --yes is used, apply defaults for unspecified flags.
|
|
1464
|
+
const workflow = (_a = merged.workflow) !== null && _a !== void 0 ? _a : (isNonInteractive ? 'demo' : undefined);
|
|
1465
|
+
const validator = (_b = merged.validator) !== null && _b !== void 0 ? _b : (isNonInteractive ? 'zod' : undefined);
|
|
1466
|
+
const appShape = (_c = merged.appShape) !== null && _c !== void 0 ? _c : 'default';
|
|
1467
|
+
if (isNonInteractive && workflow === 'pg_dump') {
|
|
1468
|
+
console.error('Non-interactive mode does not support the pg_dump workflow (requires connection string prompt).');
|
|
1469
|
+
process.exit(1);
|
|
1470
|
+
}
|
|
1471
|
+
if (merged.dryRun === true) {
|
|
1472
|
+
const plan = buildInitDryRunPlan(process.cwd(), {
|
|
1473
|
+
appShape,
|
|
1474
|
+
withAiGuidance: merged.withAiGuidance === true,
|
|
1475
|
+
withSqlClient: merged.withSqlclient === true,
|
|
1476
|
+
withAppInterface: merged.withAppInterface === true,
|
|
1477
|
+
workflow: workflow !== null && workflow !== void 0 ? workflow : 'demo',
|
|
1478
|
+
validator: validator !== null && validator !== void 0 ? validator : 'zod',
|
|
1479
|
+
localSourceRoot: merged.localSourceRoot
|
|
1480
|
+
});
|
|
1481
|
+
if ((0, agentCli_1.isJsonOutput)()) {
|
|
1482
|
+
(0, agentCli_1.writeCommandEnvelope)('init', plan);
|
|
1483
|
+
}
|
|
1484
|
+
else {
|
|
1485
|
+
process.stdout.write(`${JSON.stringify(plan, null, 2)}\n`);
|
|
1486
|
+
}
|
|
1487
|
+
return;
|
|
1488
|
+
}
|
|
1489
|
+
const prompter = createConsolePrompter();
|
|
1490
|
+
try {
|
|
1491
|
+
await runInitCommand(prompter, {
|
|
1492
|
+
appShape,
|
|
1493
|
+
withAiGuidance: merged.withAiGuidance === true,
|
|
1494
|
+
withSqlClient: merged.withSqlclient === true,
|
|
1495
|
+
withAppInterface: merged.withAppInterface === true,
|
|
1496
|
+
forceOverwrite: merged.force === true,
|
|
1497
|
+
nonInteractive: isNonInteractive,
|
|
1498
|
+
workflow,
|
|
1499
|
+
validator,
|
|
1500
|
+
localSourceRoot: merged.localSourceRoot
|
|
1501
|
+
});
|
|
1502
|
+
}
|
|
1503
|
+
finally {
|
|
1504
|
+
prompter.close();
|
|
1505
|
+
}
|
|
1506
|
+
});
|
|
1507
|
+
}
|
|
1508
|
+
//# sourceMappingURL=init.js.map
|