@kalutskii/contract 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +370 -0
- package/dist/cli.entrypoint.d.ts +1 -0
- package/dist/cli.entrypoint.js +1024 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +0 -0
- package/package.json +53 -0
|
@@ -0,0 +1,1024 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
|
|
3
|
+
// src/adapters/clipanion.client.ts
|
|
4
|
+
import { Cli } from "clipanion";
|
|
5
|
+
|
|
6
|
+
// src/library.metadata.ts
|
|
7
|
+
import { createRequire } from "module";
|
|
8
|
+
var require2 = createRequire(import.meta.url);
|
|
9
|
+
var packageMetadata = require2("../package.json");
|
|
10
|
+
var libraryMetadata = {
|
|
11
|
+
name: packageMetadata.name,
|
|
12
|
+
version: packageMetadata.version
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
// src/adapters/clipanion.client.ts
|
|
16
|
+
function getClipanionClient() {
|
|
17
|
+
return new Cli({
|
|
18
|
+
binaryName: libraryMetadata.name,
|
|
19
|
+
binaryLabel: `${libraryMetadata.name}-cli`,
|
|
20
|
+
binaryVersion: libraryMetadata.version
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// src/modules/build/build.commands.ts
|
|
25
|
+
import { Command } from "clipanion";
|
|
26
|
+
|
|
27
|
+
// src/environment/environment.services.ts
|
|
28
|
+
import fs3 from "fs-extra";
|
|
29
|
+
import path3 from "path";
|
|
30
|
+
|
|
31
|
+
// src/environment/environment.chat.ts
|
|
32
|
+
import { confirm, isCancel, log } from "@clack/prompts";
|
|
33
|
+
async function initializePrompt() {
|
|
34
|
+
const shouldInitialize = await confirm({
|
|
35
|
+
message: "If contract is already initialized, reinitialization will cause existing files to be overwritten. Do you want to proceed?",
|
|
36
|
+
initialValue: true
|
|
37
|
+
});
|
|
38
|
+
return isCancel(shouldInitialize) ? false : shouldInitialize;
|
|
39
|
+
}
|
|
40
|
+
async function configFileCreationPrompt() {
|
|
41
|
+
const shouldCreate = await confirm({
|
|
42
|
+
message: "No configuration found. Would you like to create one?",
|
|
43
|
+
initialValue: true
|
|
44
|
+
});
|
|
45
|
+
return isCancel(shouldCreate) ? false : shouldCreate;
|
|
46
|
+
}
|
|
47
|
+
var environmentClearedMessage = () => log.success("Existing contract environment cleared.");
|
|
48
|
+
var invalidConfigMessage = (configPath, errorMessage) => log.error(`Invalid config format at ${configPath}: ${errorMessage}`);
|
|
49
|
+
var configFileNotFoundMessage = (configPath) => log.warn(`Config not found at ${configPath}.`);
|
|
50
|
+
var configFileLoadFailedMessage = (configPath, errorMessage) => log.error(`Failed to load config at ${configPath}: ${errorMessage}`);
|
|
51
|
+
|
|
52
|
+
// src/environment/environment.constants.ts
|
|
53
|
+
var CONFIG_FILE_NAME = "contract.config.ts";
|
|
54
|
+
var DEFAULT_CONTRACTS = ["api", "types"];
|
|
55
|
+
var CONTRACT_DIRECTORY_NAME = "contract";
|
|
56
|
+
var ENVIRONMENT_DIRECTORIES = ["manifests", "generated"];
|
|
57
|
+
|
|
58
|
+
// src/environment/environment.inspection.ts
|
|
59
|
+
import fs from "fs-extra";
|
|
60
|
+
import path from "path";
|
|
61
|
+
|
|
62
|
+
// src/environment/environment.schemas.ts
|
|
63
|
+
import { z } from "zod";
|
|
64
|
+
var ConfigSchema = z.object({
|
|
65
|
+
app: z.string().regex(/^[a-zA-Z_-]+$/).default("placeholder").meta({ description: "The name of the application, used in filenames and identifiers." }),
|
|
66
|
+
contracts: z.array(z.string().regex(/^[a-zA-Z_-]+$/)).default([]).meta({ description: "Array of selected contract names." }),
|
|
67
|
+
emit: z.array(z.string().regex(/^[a-zA-Z_-]+$/)).default([]).meta({ description: "Subset of contracts that should also emit runtime JavaScript artifacts." }),
|
|
68
|
+
package: z.object({
|
|
69
|
+
name: z.string().meta({ description: "NPM package name, e.g. @scope/package-name" }),
|
|
70
|
+
version: z.string().regex(/^\d+\.\d+\.\d+/).meta({ description: "Semantic version, e.g. 1.0.0" }),
|
|
71
|
+
exports: z.record(z.string(), z.string()).optional().meta({ description: "Optional package exports configuration." })
|
|
72
|
+
}).meta({ description: "Package metadata for contract distribution." }),
|
|
73
|
+
npm: z.object({
|
|
74
|
+
token: z.string().meta({ description: "NPM authentication token used for publishing." })
|
|
75
|
+
}).optional().meta({ description: "Optional npm publishing configuration." })
|
|
76
|
+
}).superRefine((config, context) => {
|
|
77
|
+
const contractNames = new Set(config.contracts);
|
|
78
|
+
for (const contract of config.emit) {
|
|
79
|
+
if (!contractNames.has(contract)) {
|
|
80
|
+
context.addIssue({
|
|
81
|
+
code: z.ZodIssueCode.custom,
|
|
82
|
+
path: ["emit"],
|
|
83
|
+
message: `Emitted contract "${contract}" must be listed in contracts.`
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
var EnvironmentStatusSchema = z.object({
|
|
89
|
+
contractDirectoryExists: z.boolean().default(false).meta({ description: "Indicates if the main contract directory exists." }),
|
|
90
|
+
directoriesExistence: z.object({
|
|
91
|
+
// * Ensure keys match ENVIRONMENT_DIRECTORIES
|
|
92
|
+
manifests: z.boolean(),
|
|
93
|
+
// Folder where contract manifests are stored
|
|
94
|
+
generated: z.boolean()
|
|
95
|
+
// Folder for generated contract d.ts files
|
|
96
|
+
}).default({ manifests: false, generated: false }).meta({ description: "Existence status of required environment directories." }),
|
|
97
|
+
manifestsExistence: z.record(z.string(), z.boolean()).default({}).meta({ description: "Existence status of required contract manifest files (contract.<contract>.manifest.ts)." })
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
// src/environment/environment.inspection.ts
|
|
101
|
+
async function inspectContractEnvironment(config, contractFolderPath) {
|
|
102
|
+
const environmentStatus = EnvironmentStatusSchema.parse({});
|
|
103
|
+
environmentStatus.contractDirectoryExists = await fs.pathExists(contractFolderPath);
|
|
104
|
+
if (!environmentStatus.contractDirectoryExists)
|
|
105
|
+
return environmentStatus;
|
|
106
|
+
for (const dir of ENVIRONMENT_DIRECTORIES) {
|
|
107
|
+
environmentStatus.directoriesExistence[dir] = await fs.pathExists(path.join(contractFolderPath, dir));
|
|
108
|
+
}
|
|
109
|
+
for (const contract of config.contracts) {
|
|
110
|
+
const manifestFilePath = path.join(contractFolderPath, "manifests", `contract.${contract}.manifest.ts`);
|
|
111
|
+
environmentStatus.manifestsExistence[contract] = await fs.pathExists(manifestFilePath);
|
|
112
|
+
}
|
|
113
|
+
return environmentStatus;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// src/environment/environment.loader.ts
|
|
117
|
+
import fs2 from "fs-extra";
|
|
118
|
+
import path2 from "path";
|
|
119
|
+
import { pathToFileURL } from "url";
|
|
120
|
+
function resolveConfigModulePath(configPath) {
|
|
121
|
+
return path2.join(process.cwd(), configPath);
|
|
122
|
+
}
|
|
123
|
+
async function resolveConfigModuleUrl(modulePath) {
|
|
124
|
+
const fileUrl = pathToFileURL(modulePath);
|
|
125
|
+
const stat = await fs2.stat(modulePath);
|
|
126
|
+
fileUrl.searchParams.set("t", String(stat.mtimeMs));
|
|
127
|
+
return fileUrl.href;
|
|
128
|
+
}
|
|
129
|
+
async function loadConfigFile(configPath) {
|
|
130
|
+
const modulePath = resolveConfigModulePath(configPath);
|
|
131
|
+
if (!await fs2.pathExists(modulePath)) {
|
|
132
|
+
configFileNotFoundMessage(configPath);
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
try {
|
|
136
|
+
const moduleUrl = await resolveConfigModuleUrl(modulePath);
|
|
137
|
+
const configModule = await import(moduleUrl);
|
|
138
|
+
const config = await ConfigSchema.safeParseAsync(configModule.default);
|
|
139
|
+
if (config.success)
|
|
140
|
+
return config.data;
|
|
141
|
+
invalidConfigMessage(configPath, config.error.message);
|
|
142
|
+
} catch (error) {
|
|
143
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
144
|
+
configFileLoadFailedMessage(configPath, errorMessage);
|
|
145
|
+
}
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// src/environment/environment.templates.ts
|
|
150
|
+
var renderConfigTemplate = (defaultConfig) => {
|
|
151
|
+
const npmConfig = defaultConfig.npm ? `,
|
|
152
|
+
npm: {
|
|
153
|
+
token: '${defaultConfig.npm.token}',
|
|
154
|
+
}` : `,
|
|
155
|
+
// npm: {
|
|
156
|
+
// token: process.env.NPM_TOKEN ?? '',
|
|
157
|
+
// }`;
|
|
158
|
+
return `import type { Config } from 'contract';
|
|
159
|
+
|
|
160
|
+
const contractConfig: Config = {
|
|
161
|
+
app: '${defaultConfig.app}',
|
|
162
|
+
contracts: ${JSON.stringify(defaultConfig.contracts)},
|
|
163
|
+
emit: ${JSON.stringify(defaultConfig.emit ?? [])},
|
|
164
|
+
package: {
|
|
165
|
+
name: '${defaultConfig.package.name}',
|
|
166
|
+
version: '${defaultConfig.package.version}',
|
|
167
|
+
}${npmConfig},
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
export default contractConfig;
|
|
171
|
+
`;
|
|
172
|
+
};
|
|
173
|
+
var renderManifestTemplate = (contractName) => `// Define and export all types related to this contract (${contractName}).
|
|
174
|
+
// This file will be bundled into ${contractName}.d.ts during "contract build".
|
|
175
|
+
|
|
176
|
+
// If this contract is listed in config.emit, re-export runtime values only from direct leaf files.
|
|
177
|
+
// Keep emitted source files free of unrelated local imports, or they will be pulled into the bundle.
|
|
178
|
+
|
|
179
|
+
`;
|
|
180
|
+
|
|
181
|
+
// src/environment/environment.services.ts
|
|
182
|
+
function getContractRootPath() {
|
|
183
|
+
return path3.join(process.cwd(), CONTRACT_DIRECTORY_NAME);
|
|
184
|
+
}
|
|
185
|
+
function getConfigFilePath() {
|
|
186
|
+
return path3.resolve(CONFIG_FILE_NAME);
|
|
187
|
+
}
|
|
188
|
+
async function ensureEnvironmentDirectories(contractFolderPath, environmentStatus) {
|
|
189
|
+
if (!environmentStatus.contractDirectoryExists) {
|
|
190
|
+
await fs3.ensureDir(contractFolderPath);
|
|
191
|
+
environmentStatus.contractDirectoryExists = true;
|
|
192
|
+
}
|
|
193
|
+
for (const dir of ENVIRONMENT_DIRECTORIES) {
|
|
194
|
+
if (!environmentStatus.directoriesExistence[dir]) {
|
|
195
|
+
await fs3.ensureDir(path3.join(contractFolderPath, dir));
|
|
196
|
+
environmentStatus.directoriesExistence[dir] = true;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
async function ensureManifestFiles(contractFolderPath, environmentStatus) {
|
|
201
|
+
for (const [contract, exists] of Object.entries(environmentStatus.manifestsExistence)) {
|
|
202
|
+
if (!exists) {
|
|
203
|
+
const manifestFilePath = path3.join(contractFolderPath, "manifests", `contract.${contract}.manifest.ts`);
|
|
204
|
+
await Bun.write(manifestFilePath, renderManifestTemplate(contract));
|
|
205
|
+
environmentStatus.manifestsExistence[contract] = true;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
async function createDefaultConfigFile() {
|
|
210
|
+
const defaultConfig = ConfigSchema.parse({
|
|
211
|
+
contracts: DEFAULT_CONTRACTS,
|
|
212
|
+
package: { name: "@scope/contracts", version: "1.0.0" }
|
|
213
|
+
});
|
|
214
|
+
await Bun.write(CONFIG_FILE_NAME, renderConfigTemplate(defaultConfig));
|
|
215
|
+
return defaultConfig;
|
|
216
|
+
}
|
|
217
|
+
async function getConfig() {
|
|
218
|
+
const config = await loadConfigFile(CONFIG_FILE_NAME);
|
|
219
|
+
if (!config) {
|
|
220
|
+
const shouldCreate = await configFileCreationPrompt();
|
|
221
|
+
if (shouldCreate)
|
|
222
|
+
return createDefaultConfigFile();
|
|
223
|
+
return process.exit(0);
|
|
224
|
+
}
|
|
225
|
+
return config;
|
|
226
|
+
}
|
|
227
|
+
async function handleEnvironment(config) {
|
|
228
|
+
const contractFolderPath = getContractRootPath();
|
|
229
|
+
const environmentStatus = await inspectContractEnvironment(config, contractFolderPath);
|
|
230
|
+
await ensureEnvironmentDirectories(contractFolderPath, environmentStatus);
|
|
231
|
+
await ensureManifestFiles(contractFolderPath, environmentStatus);
|
|
232
|
+
return environmentStatus;
|
|
233
|
+
}
|
|
234
|
+
async function clearEnvironment() {
|
|
235
|
+
const contractFolderPath = getContractRootPath();
|
|
236
|
+
await fs3.remove(contractFolderPath);
|
|
237
|
+
environmentClearedMessage();
|
|
238
|
+
}
|
|
239
|
+
async function updateConfigVersion(newVersion) {
|
|
240
|
+
const configPath = getConfigFilePath();
|
|
241
|
+
const content = await fs3.readFile(configPath, "utf-8");
|
|
242
|
+
const packageVersionRegex = /(package:\s*\{[\s\S]*?version:\s*['"])([^'"]+)(['"])/;
|
|
243
|
+
if (!packageVersionRegex.test(content)) {
|
|
244
|
+
throw new Error(`Could not find version field in ${configPath}`);
|
|
245
|
+
}
|
|
246
|
+
const updatedContent = content.replace(packageVersionRegex, `$1${newVersion}$3`);
|
|
247
|
+
await fs3.writeFile(configPath, updatedContent, "utf-8");
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// src/modules/build/build.services.ts
|
|
251
|
+
import { spinner } from "@clack/prompts";
|
|
252
|
+
|
|
253
|
+
// src/modules/build/build.bundle.ts
|
|
254
|
+
import { build } from "esbuild";
|
|
255
|
+
import { tsconfigPathsPlugin } from "esbuild-plugin-tsconfig-paths";
|
|
256
|
+
import path6 from "path";
|
|
257
|
+
|
|
258
|
+
// src/utilities/execution.utilities.ts
|
|
259
|
+
import { log as log2 } from "@clack/prompts";
|
|
260
|
+
import { execa } from "execa";
|
|
261
|
+
function createOutputBuffer() {
|
|
262
|
+
return { stdout: "", stderr: "" };
|
|
263
|
+
}
|
|
264
|
+
function appendChunk(target, buffer, chunk) {
|
|
265
|
+
buffer[target] += String(chunk);
|
|
266
|
+
}
|
|
267
|
+
function attachOutputListeners(subprocess, buffer) {
|
|
268
|
+
subprocess.stdout?.on("data", (chunk) => appendChunk("stdout", buffer, chunk));
|
|
269
|
+
subprocess.stderr?.on("data", (chunk) => appendChunk("stderr", buffer, chunk));
|
|
270
|
+
}
|
|
271
|
+
function createFailedResult(buffer, error) {
|
|
272
|
+
return {
|
|
273
|
+
success: false,
|
|
274
|
+
stdout: buffer.stdout,
|
|
275
|
+
stderr: buffer.stderr,
|
|
276
|
+
errorMessage: error instanceof Error ? error.message : String(error)
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
function createSuccessResult(buffer) {
|
|
280
|
+
return { success: true, stdout: buffer.stdout, stderr: buffer.stderr };
|
|
281
|
+
}
|
|
282
|
+
async function executeCommandWithResult(command, args, cwd) {
|
|
283
|
+
const output = createOutputBuffer();
|
|
284
|
+
try {
|
|
285
|
+
const subprocess = execa(command, args, { stdio: ["ignore", "pipe", "pipe"], shell: true, cwd });
|
|
286
|
+
attachOutputListeners(subprocess, output);
|
|
287
|
+
await subprocess;
|
|
288
|
+
return createSuccessResult(output);
|
|
289
|
+
} catch (error) {
|
|
290
|
+
return createFailedResult(output, error);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
async function executeCommand(command, args, cwd) {
|
|
294
|
+
const result = await executeCommandWithResult(command, args, cwd);
|
|
295
|
+
if (!result.success) {
|
|
296
|
+
const errorMessage = result.stderr || result.stdout || result.errorMessage || "Unknown error";
|
|
297
|
+
log2.error(`Error executing command: ${command} ${args.join(" ")}
|
|
298
|
+
${errorMessage}`);
|
|
299
|
+
return false;
|
|
300
|
+
}
|
|
301
|
+
return true;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// src/modules/build/build.messages.ts
|
|
305
|
+
import { log as log3 } from "@clack/prompts";
|
|
306
|
+
import { green } from "kleur/colors";
|
|
307
|
+
var buildSpinnerStartedMessage = (contractsCount) => `Building ${green(String(contractsCount))} contract declaration(s)...`;
|
|
308
|
+
var buildSpinnerCompletedMessage = (contracts) => {
|
|
309
|
+
const names = contracts.join(", ");
|
|
310
|
+
return `Built ${green(String(contracts.length))} contract declaration(s): ${green(names)}.`;
|
|
311
|
+
};
|
|
312
|
+
var buildSpinnerFailedMessage = () => "Build failed.";
|
|
313
|
+
var fatalErrorWhileBundlingMessage = (error) => log3.error(`Build failed: ${error}`);
|
|
314
|
+
|
|
315
|
+
// src/modules/build/build.paths.ts
|
|
316
|
+
import path4 from "path";
|
|
317
|
+
function resolveContractBundlePaths(app, contract) {
|
|
318
|
+
const input = path4.join(CONTRACT_DIRECTORY_NAME, "manifests", `contract.${contract}.manifest.ts`);
|
|
319
|
+
const output = path4.join(CONTRACT_DIRECTORY_NAME, "generated", `${app}.contract.${contract}.d.ts`);
|
|
320
|
+
const runtimeOutput = path4.join(CONTRACT_DIRECTORY_NAME, "generated", `${app}.contract.${contract}.js`);
|
|
321
|
+
return { input, output, runtimeOutput };
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// src/modules/build/build.utilities.ts
|
|
325
|
+
import fs4 from "fs/promises";
|
|
326
|
+
import path5 from "path";
|
|
327
|
+
async function getRuntimeExternalPackages() {
|
|
328
|
+
const packageJSONPath = path5.join(process.cwd(), "package.json");
|
|
329
|
+
try {
|
|
330
|
+
const rawPackageJSON = await fs4.readFile(packageJSONPath, "utf-8");
|
|
331
|
+
const packageJSON = JSON.parse(rawPackageJSON);
|
|
332
|
+
const packageNames = /* @__PURE__ */ new Set([
|
|
333
|
+
...Object.keys(packageJSON.dependencies ?? {}),
|
|
334
|
+
...Object.keys(packageJSON.devDependencies ?? {}),
|
|
335
|
+
...Object.keys(packageJSON.peerDependencies ?? {}),
|
|
336
|
+
...Object.keys(packageJSON.optionalDependencies ?? {})
|
|
337
|
+
]);
|
|
338
|
+
return [...packageNames].flatMap((packageName) => [packageName, `${packageName}/*`]);
|
|
339
|
+
} catch {
|
|
340
|
+
return [];
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// src/modules/build/build.bundle.ts
|
|
345
|
+
async function bundleContractDeclaration(app, contract) {
|
|
346
|
+
const paths = resolveContractBundlePaths(app, contract);
|
|
347
|
+
return executeCommand("npx", ["dts-bundle-generator", "-o", paths.output, paths.input, "--no-check"]);
|
|
348
|
+
}
|
|
349
|
+
async function bundleContractRuntime(app, contract) {
|
|
350
|
+
const paths = resolveContractBundlePaths(app, contract);
|
|
351
|
+
const externalPackages = await getRuntimeExternalPackages();
|
|
352
|
+
try {
|
|
353
|
+
await build({
|
|
354
|
+
bundle: true,
|
|
355
|
+
entryPoints: [paths.input],
|
|
356
|
+
external: externalPackages,
|
|
357
|
+
format: "esm",
|
|
358
|
+
logLevel: "silent",
|
|
359
|
+
outfile: paths.runtimeOutput,
|
|
360
|
+
platform: "neutral",
|
|
361
|
+
plugins: [tsconfigPathsPlugin()],
|
|
362
|
+
target: "esnext",
|
|
363
|
+
treeShaking: true,
|
|
364
|
+
tsconfig: path6.join(process.cwd(), "tsconfig.json")
|
|
365
|
+
});
|
|
366
|
+
return true;
|
|
367
|
+
} catch (error) {
|
|
368
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
369
|
+
fatalErrorWhileBundlingMessage(errorMessage);
|
|
370
|
+
return false;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// src/modules/build/build.services.ts
|
|
375
|
+
function getEmittedContracts(config) {
|
|
376
|
+
return Array.isArray(config.emit) ? config.emit : [];
|
|
377
|
+
}
|
|
378
|
+
async function bundleAllContractDeclarations(config) {
|
|
379
|
+
const buildSpinner = spinner();
|
|
380
|
+
const emittedContracts = getEmittedContracts(config);
|
|
381
|
+
try {
|
|
382
|
+
buildSpinner.start(buildSpinnerStartedMessage(config.contracts.length));
|
|
383
|
+
for (const contract of config.contracts) {
|
|
384
|
+
const executed = await bundleContractDeclaration(config.app, contract);
|
|
385
|
+
if (!executed) {
|
|
386
|
+
buildSpinner.stop(buildSpinnerFailedMessage());
|
|
387
|
+
process.exit(1);
|
|
388
|
+
}
|
|
389
|
+
if (emittedContracts.includes(contract)) {
|
|
390
|
+
const emitted = await bundleContractRuntime(config.app, contract);
|
|
391
|
+
if (!emitted) {
|
|
392
|
+
buildSpinner.stop(buildSpinnerFailedMessage());
|
|
393
|
+
process.exit(1);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
buildSpinner.stop(buildSpinnerCompletedMessage(config.contracts));
|
|
398
|
+
} catch (error) {
|
|
399
|
+
buildSpinner.stop(buildSpinnerFailedMessage());
|
|
400
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
401
|
+
fatalErrorWhileBundlingMessage(errorMessage);
|
|
402
|
+
process.exit(1);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// src/modules/build/build.commands.ts
|
|
407
|
+
var BuildCommand = class extends Command {
|
|
408
|
+
static paths = [["build"]];
|
|
409
|
+
async execute() {
|
|
410
|
+
const config = await getConfig();
|
|
411
|
+
await handleEnvironment(config);
|
|
412
|
+
await bundleAllContractDeclarations(config);
|
|
413
|
+
}
|
|
414
|
+
};
|
|
415
|
+
|
|
416
|
+
// src/modules/init/init.commands.ts
|
|
417
|
+
import { Command as Command2 } from "clipanion";
|
|
418
|
+
|
|
419
|
+
// src/modules/init/init.messages.ts
|
|
420
|
+
import { log as log4 } from "@clack/prompts";
|
|
421
|
+
import { green as green2 } from "kleur/colors";
|
|
422
|
+
var initializationCancelledMessage = () => log4.info("Initialization cancelled by user.");
|
|
423
|
+
var initializationCompletedMessage = () => log4.success(`Initialized. Edit ${green2("contract.config.ts")} and run ${green2("contract update:environment")}.`);
|
|
424
|
+
var environmentUpdateCompletedMessage = () => log4.success(`Environment synced. Define types in manifests, then run ${green2("contract build")}.`);
|
|
425
|
+
|
|
426
|
+
// src/modules/init/init.services.ts
|
|
427
|
+
async function initializeContractProject() {
|
|
428
|
+
const shouldInitialize = await initializePrompt();
|
|
429
|
+
if (!shouldInitialize) {
|
|
430
|
+
initializationCancelledMessage();
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
await clearEnvironment();
|
|
434
|
+
const config = await createDefaultConfigFile();
|
|
435
|
+
await handleEnvironment(config);
|
|
436
|
+
initializationCompletedMessage();
|
|
437
|
+
}
|
|
438
|
+
async function updateContractEnvironment() {
|
|
439
|
+
const config = await getConfig();
|
|
440
|
+
await handleEnvironment(config);
|
|
441
|
+
environmentUpdateCompletedMessage();
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// src/modules/init/init.commands.ts
|
|
445
|
+
var InitCommand = class extends Command2 {
|
|
446
|
+
static paths = [["init"]];
|
|
447
|
+
async execute() {
|
|
448
|
+
await initializeContractProject();
|
|
449
|
+
}
|
|
450
|
+
};
|
|
451
|
+
var UpdateEnvironmentCommand = class extends Command2 {
|
|
452
|
+
static paths = [["update:environment"]];
|
|
453
|
+
async execute() {
|
|
454
|
+
await updateContractEnvironment();
|
|
455
|
+
}
|
|
456
|
+
};
|
|
457
|
+
|
|
458
|
+
// src/modules/pack/pack.commands.ts
|
|
459
|
+
import { Command as Command3 } from "clipanion";
|
|
460
|
+
|
|
461
|
+
// src/modules/pack/pack.services.ts
|
|
462
|
+
import { spinner as spinner2 } from "@clack/prompts";
|
|
463
|
+
import path8 from "path";
|
|
464
|
+
|
|
465
|
+
// src/modules/pack/pack.messages.ts
|
|
466
|
+
import { log as log5 } from "@clack/prompts";
|
|
467
|
+
import { green as green3 } from "kleur/colors";
|
|
468
|
+
var packSpinnerStartedMessage = () => "Packing contract package...";
|
|
469
|
+
var packSpinnerCompletedMessage = (filename, filepath) => `Packed ${filename} (${filepath}).`;
|
|
470
|
+
var packSpinnerCompletedFallbackMessage = () => "Packed package successfully.";
|
|
471
|
+
var packSpinnerFailedMessage = () => "Pack failed.";
|
|
472
|
+
var packageDirectoryNotFoundMessage = () => log5.error(`Package dir missing. Run ${green3("contract prepare:package")}.`);
|
|
473
|
+
var packageJsonNotFoundMessage = () => log5.error(`package.json missing. Run ${green3("contract prepare:package")}.`);
|
|
474
|
+
var fatalErrorWhilePackingMessage = (error) => log5.error(`Pack failed: ${error}`);
|
|
475
|
+
|
|
476
|
+
// src/modules/pack/pack.validation.ts
|
|
477
|
+
import fs5 from "fs-extra";
|
|
478
|
+
import path7 from "path";
|
|
479
|
+
function resolvePackPaths() {
|
|
480
|
+
const packageDir = path7.join(process.cwd(), CONTRACT_DIRECTORY_NAME, "package");
|
|
481
|
+
const packageJsonPath = path7.join(packageDir, "package.json");
|
|
482
|
+
return { packageDir, packageJsonPath };
|
|
483
|
+
}
|
|
484
|
+
async function ensurePackPathsExist(paths) {
|
|
485
|
+
if (!await fs5.pathExists(paths.packageDir)) {
|
|
486
|
+
throw new Error("PACKAGE_DIR_NOT_FOUND");
|
|
487
|
+
}
|
|
488
|
+
if (!await fs5.pathExists(paths.packageJsonPath)) {
|
|
489
|
+
throw new Error("PACKAGE_JSON_NOT_FOUND");
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
async function findPackedArchive(packageDir) {
|
|
493
|
+
const files = await fs5.readdir(packageDir);
|
|
494
|
+
return files.find((file) => file.endsWith(".tgz")) ?? null;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
// src/modules/pack/pack.services.ts
|
|
498
|
+
async function packContractPackage() {
|
|
499
|
+
let packSpinner = null;
|
|
500
|
+
try {
|
|
501
|
+
packSpinner = spinner2();
|
|
502
|
+
packSpinner.start(packSpinnerStartedMessage());
|
|
503
|
+
const paths = resolvePackPaths();
|
|
504
|
+
try {
|
|
505
|
+
await ensurePackPathsExist(paths);
|
|
506
|
+
} catch (error) {
|
|
507
|
+
const code = error instanceof Error ? error.message : String(error);
|
|
508
|
+
if (code === "PACKAGE_DIR_NOT_FOUND") {
|
|
509
|
+
packageDirectoryNotFoundMessage();
|
|
510
|
+
process.exit(1);
|
|
511
|
+
}
|
|
512
|
+
if (code === "PACKAGE_JSON_NOT_FOUND") {
|
|
513
|
+
packageJsonNotFoundMessage();
|
|
514
|
+
process.exit(1);
|
|
515
|
+
}
|
|
516
|
+
throw error;
|
|
517
|
+
}
|
|
518
|
+
const executed = await executeCommand("npm", ["pack"], paths.packageDir);
|
|
519
|
+
if (!executed) {
|
|
520
|
+
process.exit(1);
|
|
521
|
+
}
|
|
522
|
+
const tgzFile = await findPackedArchive(paths.packageDir);
|
|
523
|
+
if (tgzFile) {
|
|
524
|
+
const tgzPath = path8.join(paths.packageDir, tgzFile);
|
|
525
|
+
packSpinner.stop(packSpinnerCompletedMessage(tgzFile, tgzPath));
|
|
526
|
+
} else {
|
|
527
|
+
packSpinner.stop(packSpinnerCompletedFallbackMessage());
|
|
528
|
+
}
|
|
529
|
+
} catch (error) {
|
|
530
|
+
if (packSpinner) {
|
|
531
|
+
packSpinner.stop(packSpinnerFailedMessage());
|
|
532
|
+
}
|
|
533
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
534
|
+
fatalErrorWhilePackingMessage(errorMessage);
|
|
535
|
+
process.exit(1);
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
// src/modules/pack/pack.commands.ts
|
|
540
|
+
var PackPackageCommand = class extends Command3 {
|
|
541
|
+
static paths = [["pack:package"]];
|
|
542
|
+
async execute() {
|
|
543
|
+
await packContractPackage();
|
|
544
|
+
}
|
|
545
|
+
};
|
|
546
|
+
|
|
547
|
+
// src/modules/prepare/prepare.commands.ts
|
|
548
|
+
import { Command as Command4, Option } from "clipanion";
|
|
549
|
+
|
|
550
|
+
// src/modules/prepare/prepare.services.ts
|
|
551
|
+
import path12 from "path";
|
|
552
|
+
|
|
553
|
+
// src/modules/versioning/versioning.hash.ts
|
|
554
|
+
import crypto from "crypto";
|
|
555
|
+
import fs6 from "fs-extra";
|
|
556
|
+
import path9 from "path";
|
|
557
|
+
|
|
558
|
+
// src/modules/versioning/versioning.constants.ts
|
|
559
|
+
var PUBLISHABLE_FILES = ["package.json", "index.d.ts", "index.js"];
|
|
560
|
+
var CONTRACT_PACKAGE_STATE_FILE = ".contract-package-state.json";
|
|
561
|
+
|
|
562
|
+
// src/modules/versioning/versioning.hash.ts
|
|
563
|
+
function addContractFiles(contracts) {
|
|
564
|
+
return [...PUBLISHABLE_FILES, ...contracts.flatMap((contract) => [`${contract}.d.ts`, `${contract}.js`])];
|
|
565
|
+
}
|
|
566
|
+
async function computePackageHash(packageDir, contracts) {
|
|
567
|
+
const hash = crypto.createHash("sha256");
|
|
568
|
+
const filesToHash = addContractFiles(contracts);
|
|
569
|
+
for (const filename of filesToHash.sort()) {
|
|
570
|
+
const filePath = path9.join(packageDir, filename);
|
|
571
|
+
try {
|
|
572
|
+
let content = await fs6.readFile(filePath, "utf-8");
|
|
573
|
+
if (filename === "package.json") {
|
|
574
|
+
const json = JSON.parse(content);
|
|
575
|
+
delete json.version;
|
|
576
|
+
content = JSON.stringify(json, null, 2);
|
|
577
|
+
}
|
|
578
|
+
content = content.replace(/\r\n/g, "\n").trim();
|
|
579
|
+
hash.update(filename + ":" + content);
|
|
580
|
+
} catch (_error) {
|
|
581
|
+
hash.update(filename + ":");
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
return hash.digest("hex");
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
// src/modules/versioning/versioning.state.ts
|
|
588
|
+
import fs7 from "fs-extra";
|
|
589
|
+
import path10 from "path";
|
|
590
|
+
|
|
591
|
+
// src/utilities/type.utilities.ts
|
|
592
|
+
function isRecord(value) {
|
|
593
|
+
return typeof value === "object" && value !== null;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
// src/modules/versioning/versioning.state.ts
|
|
597
|
+
function toContractState(value) {
|
|
598
|
+
if (!isRecord(value) || typeof value.hash !== "string") {
|
|
599
|
+
return null;
|
|
600
|
+
}
|
|
601
|
+
return { hash: value.hash };
|
|
602
|
+
}
|
|
603
|
+
function getStatePath(packageDir) {
|
|
604
|
+
return path10.join(path10.dirname(packageDir), CONTRACT_PACKAGE_STATE_FILE);
|
|
605
|
+
}
|
|
606
|
+
async function getContractState(packageDir) {
|
|
607
|
+
const statePath = getStatePath(packageDir);
|
|
608
|
+
try {
|
|
609
|
+
if (await fs7.pathExists(statePath)) {
|
|
610
|
+
const rawState = await fs7.readJSON(statePath);
|
|
611
|
+
return toContractState(rawState);
|
|
612
|
+
}
|
|
613
|
+
} catch {
|
|
614
|
+
return null;
|
|
615
|
+
}
|
|
616
|
+
return null;
|
|
617
|
+
}
|
|
618
|
+
async function writeContractState(packageDir, state) {
|
|
619
|
+
const statePath = getStatePath(packageDir);
|
|
620
|
+
await fs7.writeJSON(statePath, state, { spaces: 2 });
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
// src/modules/versioning/versioning.semver.ts
|
|
624
|
+
function bumpVersion(currentVersion, bumpType) {
|
|
625
|
+
const parts = currentVersion.split(".");
|
|
626
|
+
const [major, minor, patch] = [
|
|
627
|
+
parseInt(parts[0] ?? "0", 10),
|
|
628
|
+
parseInt(parts[1] ?? "0", 10),
|
|
629
|
+
parseInt(parts[2] ?? "0", 10)
|
|
630
|
+
];
|
|
631
|
+
switch (bumpType) {
|
|
632
|
+
case "major":
|
|
633
|
+
return `${major + 1}.0.0`;
|
|
634
|
+
case "minor":
|
|
635
|
+
return `${major}.${minor + 1}.0`;
|
|
636
|
+
case "patch":
|
|
637
|
+
return `${major}.${minor}.${patch + 1}`;
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
// src/modules/prepare/prepare.artifacts.ts
|
|
642
|
+
import fs8 from "fs-extra";
|
|
643
|
+
import path11 from "path";
|
|
644
|
+
function getGeneratedDirPath() {
|
|
645
|
+
return path11.join(process.cwd(), CONTRACT_DIRECTORY_NAME, "generated");
|
|
646
|
+
}
|
|
647
|
+
function generatePackageJson(config, contracts) {
|
|
648
|
+
const exports = {
|
|
649
|
+
".": {
|
|
650
|
+
types: "./index.d.ts",
|
|
651
|
+
default: "./index.js"
|
|
652
|
+
}
|
|
653
|
+
};
|
|
654
|
+
for (const contract of contracts) {
|
|
655
|
+
exports[`./${contract}`] = {
|
|
656
|
+
types: `./${contract}.d.ts`,
|
|
657
|
+
default: `./${contract}.js`
|
|
658
|
+
};
|
|
659
|
+
}
|
|
660
|
+
const files = ["index.d.ts", "index.js", ...contracts.flatMap((c) => [`${c}.d.ts`, `${c}.js`])];
|
|
661
|
+
return {
|
|
662
|
+
name: config.package.name,
|
|
663
|
+
description: `Shared TypeScript contract definitions for ${config.app}.`,
|
|
664
|
+
version: config.package.version,
|
|
665
|
+
private: false,
|
|
666
|
+
type: "module",
|
|
667
|
+
sideEffects: false,
|
|
668
|
+
files,
|
|
669
|
+
exports,
|
|
670
|
+
types: "./index.d.ts"
|
|
671
|
+
};
|
|
672
|
+
}
|
|
673
|
+
function generateIndexDts(contracts, emittedContracts) {
|
|
674
|
+
const typeExports = contracts.map((contract) => `export type * from './${contract}';`).join("\n");
|
|
675
|
+
const runtimeExports = emittedContracts.map((contract) => `export * from './${contract}';`).join("\n");
|
|
676
|
+
return [typeExports, runtimeExports].filter(Boolean).join("\n");
|
|
677
|
+
}
|
|
678
|
+
function generateIndexJs(emittedContracts) {
|
|
679
|
+
if (emittedContracts.length === 0) {
|
|
680
|
+
return generateStubJs();
|
|
681
|
+
}
|
|
682
|
+
return emittedContracts.map((contract) => `export * from './${contract}.js';`).join("\n");
|
|
683
|
+
}
|
|
684
|
+
function generateStubJs() {
|
|
685
|
+
return "export {};";
|
|
686
|
+
}
|
|
687
|
+
async function collectExistingGeneratedContracts(config, onMissing, onMissingEmitted) {
|
|
688
|
+
const generatedDir = getGeneratedDirPath();
|
|
689
|
+
const existing = [];
|
|
690
|
+
for (const contract of config.contracts) {
|
|
691
|
+
const contractFileName = `${config.app}.contract.${contract}.d.ts`;
|
|
692
|
+
const contractFilePath = path11.join(generatedDir, contractFileName);
|
|
693
|
+
try {
|
|
694
|
+
if (await fs8.pathExists(contractFilePath)) {
|
|
695
|
+
existing.push(contract);
|
|
696
|
+
} else {
|
|
697
|
+
onMissing(contract);
|
|
698
|
+
}
|
|
699
|
+
if (config.emit.includes(contract)) {
|
|
700
|
+
const runtimeFilePath = path11.join(generatedDir, `${config.app}.contract.${contract}.js`);
|
|
701
|
+
if (!await fs8.pathExists(runtimeFilePath)) {
|
|
702
|
+
onMissingEmitted(contract);
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
} catch {
|
|
706
|
+
onMissing(contract);
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
return existing;
|
|
710
|
+
}
|
|
711
|
+
async function writePreparedArtifacts(config, packageDir, contracts, emittedContracts) {
|
|
712
|
+
const generatedDir = getGeneratedDirPath();
|
|
713
|
+
await fs8.remove(packageDir);
|
|
714
|
+
await fs8.ensureDir(packageDir);
|
|
715
|
+
for (const contract of contracts) {
|
|
716
|
+
const sourceFile = path11.join(generatedDir, `${config.app}.contract.${contract}.d.ts`);
|
|
717
|
+
const destFile = path11.join(packageDir, `${contract}.d.ts`);
|
|
718
|
+
const content = await fs8.readFile(sourceFile, "utf-8");
|
|
719
|
+
await fs8.writeFile(destFile, content);
|
|
720
|
+
}
|
|
721
|
+
await fs8.writeFile(path11.join(packageDir, "index.d.ts"), generateIndexDts(contracts, emittedContracts));
|
|
722
|
+
const jsStub = generateStubJs();
|
|
723
|
+
await fs8.writeFile(path11.join(packageDir, "index.js"), generateIndexJs(emittedContracts));
|
|
724
|
+
for (const contract of contracts) {
|
|
725
|
+
if (emittedContracts.includes(contract)) {
|
|
726
|
+
const sourceFile = path11.join(generatedDir, `${config.app}.contract.${contract}.js`);
|
|
727
|
+
const destFile = path11.join(packageDir, `${contract}.js`);
|
|
728
|
+
const content = await fs8.readFile(sourceFile, "utf-8");
|
|
729
|
+
await fs8.writeFile(destFile, content);
|
|
730
|
+
} else {
|
|
731
|
+
await fs8.writeFile(path11.join(packageDir, `${contract}.js`), jsStub);
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
const packageJson = generatePackageJson(config, contracts);
|
|
735
|
+
const packageJsonPath = path11.join(packageDir, "package.json");
|
|
736
|
+
await fs8.writeJSON(packageJsonPath, packageJson, { spaces: 2 });
|
|
737
|
+
return { packageJsonPath, baseVersion: String(packageJson.version) };
|
|
738
|
+
}
|
|
739
|
+
async function updatePackageVersion(packageJsonPath, newVersion) {
|
|
740
|
+
const packageJson = await fs8.readJSON(packageJsonPath);
|
|
741
|
+
packageJson.version = newVersion;
|
|
742
|
+
await fs8.writeJSON(packageJsonPath, packageJson, { spaces: 2 });
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
// src/modules/prepare/prepare.messages.ts
|
|
746
|
+
import { log as log6 } from "@clack/prompts";
|
|
747
|
+
import { green as green4 } from "kleur/colors";
|
|
748
|
+
var packagePreparationStartedMessage = (app) => log6.info(`Preparing ${green4(app)} package...`);
|
|
749
|
+
var packagePreparationCompletedMessage = () => log6.success("Package ready.");
|
|
750
|
+
var missingGeneratedContractsMessage = (contractName) => log6.warn(`Missing generated contract ${green4(contractName)}. Run ${green4("contract build")}.`);
|
|
751
|
+
var missingEmittedContractsMessage = (contractName) => log6.warn(`Missing emitted runtime for ${green4(contractName)}. Run ${green4("contract build")}.`);
|
|
752
|
+
var versionBumpedMessage = (oldVersion, newVersion, reason) => log6.success(`Version bumped from ${green4(oldVersion)} to ${green4(newVersion)} (${reason}).`);
|
|
753
|
+
var versionForcedMessage = (newVersion, bumpType) => log6.success(`Version forced to ${green4(newVersion)} via --bump ${bumpType}.`);
|
|
754
|
+
var versionNoChangeMessage = (version) => log6.info(`No changes. Version ${green4(version)}.`);
|
|
755
|
+
var fatalErrorWhilePreparingPackageMessage = (error) => log6.error(`Prepare failed: ${error}`);
|
|
756
|
+
|
|
757
|
+
// src/modules/prepare/prepare.versioning.ts
|
|
758
|
+
async function applyPrepareVersioning(context) {
|
|
759
|
+
const { config, packageDir, packageJsonPath, contracts, baseVersion, previousHash, options } = context;
|
|
760
|
+
if (options.bump) {
|
|
761
|
+
const bumpedVersion = bumpVersion(baseVersion, options.bump);
|
|
762
|
+
await updateConfigVersion(bumpedVersion);
|
|
763
|
+
await updatePackageVersion(packageJsonPath, bumpedVersion);
|
|
764
|
+
versionForcedMessage(bumpedVersion, options.bump);
|
|
765
|
+
return;
|
|
766
|
+
}
|
|
767
|
+
if (options.noBump) {
|
|
768
|
+
return;
|
|
769
|
+
}
|
|
770
|
+
const currentHash = await computePackageHash(packageDir, contracts);
|
|
771
|
+
if (previousHash && previousHash !== currentHash) {
|
|
772
|
+
const bumpedVersion = bumpVersion(baseVersion, "patch");
|
|
773
|
+
await updateConfigVersion(bumpedVersion);
|
|
774
|
+
await updatePackageVersion(packageJsonPath, bumpedVersion);
|
|
775
|
+
versionBumpedMessage(config.package.version, bumpedVersion, "content changed");
|
|
776
|
+
} else {
|
|
777
|
+
versionNoChangeMessage(baseVersion);
|
|
778
|
+
}
|
|
779
|
+
await writeContractState(packageDir, { hash: currentHash });
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
// src/modules/prepare/prepare.services.ts
|
|
783
|
+
function getEmittedContracts2(config) {
|
|
784
|
+
return Array.isArray(config.emit) ? config.emit : [];
|
|
785
|
+
}
|
|
786
|
+
async function prepareContractPackage(config, options = {}) {
|
|
787
|
+
try {
|
|
788
|
+
const emittedContracts = getEmittedContracts2(config);
|
|
789
|
+
packagePreparationStartedMessage(config.app);
|
|
790
|
+
const existingContracts = await collectExistingGeneratedContracts(config, missingGeneratedContractsMessage, missingEmittedContractsMessage);
|
|
791
|
+
if (existingContracts.length === 0) {
|
|
792
|
+
throw new Error('No generated contracts found. Run "contract build" first.');
|
|
793
|
+
}
|
|
794
|
+
const packageDir = path12.join(process.cwd(), CONTRACT_DIRECTORY_NAME, "package");
|
|
795
|
+
const previousState = await getContractState(packageDir);
|
|
796
|
+
const { packageJsonPath, baseVersion } = await writePreparedArtifacts(config, packageDir, existingContracts, emittedContracts);
|
|
797
|
+
await applyPrepareVersioning({
|
|
798
|
+
config,
|
|
799
|
+
packageDir,
|
|
800
|
+
packageJsonPath,
|
|
801
|
+
contracts: existingContracts,
|
|
802
|
+
baseVersion,
|
|
803
|
+
previousHash: previousState?.hash ?? null,
|
|
804
|
+
options
|
|
805
|
+
});
|
|
806
|
+
packagePreparationCompletedMessage();
|
|
807
|
+
} catch (error) {
|
|
808
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
809
|
+
fatalErrorWhilePreparingPackageMessage(errorMessage);
|
|
810
|
+
process.exit(1);
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
// src/modules/prepare/prepare.commands.ts
|
|
815
|
+
var PreparePackageCommand = class extends Command4 {
|
|
816
|
+
static paths = [["prepare:package"]];
|
|
817
|
+
bump = Option.String("--bump", {
|
|
818
|
+
description: "Manual version bump: patch, minor, or major"
|
|
819
|
+
});
|
|
820
|
+
noBump = Option.Boolean("--no-bump", false, {
|
|
821
|
+
description: "Skip automatic version bumping"
|
|
822
|
+
});
|
|
823
|
+
async execute() {
|
|
824
|
+
const config = await getConfig();
|
|
825
|
+
await handleEnvironment(config);
|
|
826
|
+
await prepareContractPackage(config, {
|
|
827
|
+
bump: this.bump,
|
|
828
|
+
noBump: this.noBump
|
|
829
|
+
});
|
|
830
|
+
}
|
|
831
|
+
};
|
|
832
|
+
|
|
833
|
+
// src/modules/publish/publish.commands.ts
|
|
834
|
+
import { Command as Command5, Option as Option2 } from "clipanion";
|
|
835
|
+
|
|
836
|
+
// src/modules/publish/publish.services.ts
|
|
837
|
+
import { spinner as spinner3 } from "@clack/prompts";
|
|
838
|
+
|
|
839
|
+
// src/modules/publish/publish.auth.ts
|
|
840
|
+
import fs9 from "fs-extra";
|
|
841
|
+
import path13 from "path";
|
|
842
|
+
function resolveNpmToken(config) {
|
|
843
|
+
if (config.npm?.token)
|
|
844
|
+
return { source: "config", token: config.npm.token };
|
|
845
|
+
if (process.env.NPM_TOKEN)
|
|
846
|
+
return { source: "NPM_TOKEN", token: process.env.NPM_TOKEN };
|
|
847
|
+
if (process.env.NODE_AUTH_TOKEN)
|
|
848
|
+
return { source: "NODE_AUTH_TOKEN", token: process.env.NODE_AUTH_TOKEN };
|
|
849
|
+
return null;
|
|
850
|
+
}
|
|
851
|
+
async function writeNpmRc(packageDir, token) {
|
|
852
|
+
const npmrcPath = path13.join(packageDir, ".npmrc");
|
|
853
|
+
await fs9.writeFile(npmrcPath, `//registry.npmjs.org/:_authToken=${token}
|
|
854
|
+
`);
|
|
855
|
+
}
|
|
856
|
+
async function removeNpmRc(packageDir) {
|
|
857
|
+
const npmrcPath = path13.join(packageDir, ".npmrc");
|
|
858
|
+
await fs9.remove(npmrcPath);
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
// src/modules/publish/publish.errors.ts
|
|
862
|
+
function getPublishFailureMessage(output) {
|
|
863
|
+
const normalizedOutput = output.toLowerCase();
|
|
864
|
+
const details = output.trim().split("\n").slice(0, 3).join("\n");
|
|
865
|
+
if (normalizedOutput.includes("eneedauth") || normalizedOutput.includes("e401") || normalizedOutput.includes("403") || normalizedOutput.includes("auth")) {
|
|
866
|
+
return `NPM auth/permission error. Check token and package access.
|
|
867
|
+
${details}`;
|
|
868
|
+
}
|
|
869
|
+
if (normalizedOutput.includes("registry")) {
|
|
870
|
+
return `NPM registry error. Verify npmjs.org target.
|
|
871
|
+
${details}`;
|
|
872
|
+
}
|
|
873
|
+
return `NPM publish failed.
|
|
874
|
+
${details}`;
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
// src/modules/publish/publish.messages.ts
|
|
878
|
+
import { log as log7 } from "@clack/prompts";
|
|
879
|
+
import { green as green5 } from "kleur/colors";
|
|
880
|
+
var publishSpinnerStartedMessage = (packageName, version) => `Publishing ${packageName}@${version} to npm...`;
|
|
881
|
+
var publishSpinnerCompletedMessage = (packageName, version) => `Published ${packageName}@${version}.`;
|
|
882
|
+
var publishSpinnerFailedMessage = () => "Publish failed.";
|
|
883
|
+
var packageDirectoryNotFoundMessage2 = () => log7.error(`Package dir missing. Run ${green5("contract prepare:package")}.`);
|
|
884
|
+
var packageJsonNotFoundMessage2 = () => log7.error(`package.json missing. Run ${green5("contract prepare:package")}.`);
|
|
885
|
+
var packagePreparationStartedMessage2 = () => log7.info("Preparing package...");
|
|
886
|
+
var npmTokenMissingMessage = () => log7.error("NPM token missing. Set config.npm.token, NPM_TOKEN, or NODE_AUTH_TOKEN.");
|
|
887
|
+
var fatalErrorWhilePublishingMessage = (error) => log7.error(`Publish failed: ${error}`);
|
|
888
|
+
|
|
889
|
+
// src/modules/publish/publish.registry.ts
|
|
890
|
+
async function versionExistsOnNpm(packageName, version) {
|
|
891
|
+
const checkResult = await executeCommandWithResult("npm", ["view", `${packageName}@${version}`]);
|
|
892
|
+
return checkResult.success;
|
|
893
|
+
}
|
|
894
|
+
async function assertVersionAvailableOnNpm(packageName, version) {
|
|
895
|
+
const exists = await versionExistsOnNpm(packageName, version);
|
|
896
|
+
if (exists) {
|
|
897
|
+
throw new Error(`Version ${version} already exists on npm. Cannot publish duplicate version.
|
|
898
|
+
Run "contract prepare:package --bump patch" to bump the version, then try publishing again.`);
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
// src/modules/publish/publish.validation.ts
|
|
903
|
+
import fs10 from "fs-extra";
|
|
904
|
+
import path14 from "path";
|
|
905
|
+
function resolvePublishPaths() {
|
|
906
|
+
const packageDir = path14.resolve(CONTRACT_DIRECTORY_NAME, "package");
|
|
907
|
+
const packageJsonPath = path14.join(packageDir, "package.json");
|
|
908
|
+
return { packageDir, packageJsonPath };
|
|
909
|
+
}
|
|
910
|
+
async function ensurePublishPathsExist(paths) {
|
|
911
|
+
if (!await fs10.pathExists(paths.packageDir)) {
|
|
912
|
+
throw new Error("PACKAGE_DIR_NOT_FOUND");
|
|
913
|
+
}
|
|
914
|
+
if (!await fs10.pathExists(paths.packageJsonPath)) {
|
|
915
|
+
throw new Error("PACKAGE_JSON_NOT_FOUND");
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
async function readPackageJsonInfo(packageJsonPath) {
|
|
919
|
+
const packageJson = await fs10.readJSON(packageJsonPath);
|
|
920
|
+
if (!packageJson.name) {
|
|
921
|
+
throw new Error('package.json is missing a valid "name" field.');
|
|
922
|
+
}
|
|
923
|
+
return packageJson;
|
|
924
|
+
}
|
|
925
|
+
async function syncPackageJsonVersion(packageJsonPath, packageJson, expectedVersion) {
|
|
926
|
+
if (packageJson.version !== expectedVersion) {
|
|
927
|
+
packageJson.version = expectedVersion;
|
|
928
|
+
await fs10.writeJSON(packageJsonPath, packageJson, { spaces: 2 });
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
// src/modules/publish/publish.services.ts
|
|
933
|
+
async function publishContractPackage(options = {}) {
|
|
934
|
+
let packageDirForCleanup = null;
|
|
935
|
+
let publishSpinner = null;
|
|
936
|
+
try {
|
|
937
|
+
let config = await getConfig();
|
|
938
|
+
if (options.prepare) {
|
|
939
|
+
packagePreparationStartedMessage2();
|
|
940
|
+
await handleEnvironment(config);
|
|
941
|
+
await prepareContractPackage(config);
|
|
942
|
+
config = await getConfig();
|
|
943
|
+
}
|
|
944
|
+
const paths = resolvePublishPaths();
|
|
945
|
+
packageDirForCleanup = paths.packageDir;
|
|
946
|
+
try {
|
|
947
|
+
await ensurePublishPathsExist(paths);
|
|
948
|
+
} catch (error) {
|
|
949
|
+
const code = error instanceof Error ? error.message : String(error);
|
|
950
|
+
if (code === "PACKAGE_DIR_NOT_FOUND") {
|
|
951
|
+
packageDirectoryNotFoundMessage2();
|
|
952
|
+
process.exitCode = 1;
|
|
953
|
+
return;
|
|
954
|
+
}
|
|
955
|
+
if (code === "PACKAGE_JSON_NOT_FOUND") {
|
|
956
|
+
packageJsonNotFoundMessage2();
|
|
957
|
+
process.exitCode = 1;
|
|
958
|
+
return;
|
|
959
|
+
}
|
|
960
|
+
throw error;
|
|
961
|
+
}
|
|
962
|
+
const packageJson = await readPackageJsonInfo(paths.packageJsonPath);
|
|
963
|
+
const packageName = packageJson.name;
|
|
964
|
+
const packageVersion = config.package.version;
|
|
965
|
+
await assertVersionAvailableOnNpm(packageName, packageVersion);
|
|
966
|
+
await syncPackageJsonVersion(paths.packageJsonPath, packageJson, packageVersion);
|
|
967
|
+
const npmToken = resolveNpmToken(config);
|
|
968
|
+
if (!npmToken) {
|
|
969
|
+
npmTokenMissingMessage();
|
|
970
|
+
process.exitCode = 1;
|
|
971
|
+
return;
|
|
972
|
+
}
|
|
973
|
+
publishSpinner = spinner3();
|
|
974
|
+
publishSpinner.start(publishSpinnerStartedMessage(packageName, packageVersion));
|
|
975
|
+
await writeNpmRc(paths.packageDir, npmToken.token);
|
|
976
|
+
if (options.access && options.access !== "public") {
|
|
977
|
+
throw new Error("Only --access public is supported for contract publish:package.");
|
|
978
|
+
}
|
|
979
|
+
const publishResult = await executeCommandWithResult("npm", ["publish", "--access", "public"], paths.packageDir);
|
|
980
|
+
if (!publishResult.success) {
|
|
981
|
+
const errorOutput = publishResult.stderr || publishResult.stdout || publishResult.errorMessage || "Unknown error";
|
|
982
|
+
throw new Error(getPublishFailureMessage(errorOutput));
|
|
983
|
+
}
|
|
984
|
+
publishSpinner.stop(publishSpinnerCompletedMessage(packageName, packageVersion));
|
|
985
|
+
} catch (error) {
|
|
986
|
+
if (publishSpinner) {
|
|
987
|
+
publishSpinner.stop(publishSpinnerFailedMessage());
|
|
988
|
+
}
|
|
989
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
990
|
+
fatalErrorWhilePublishingMessage(errorMessage);
|
|
991
|
+
process.exitCode = 1;
|
|
992
|
+
} finally {
|
|
993
|
+
if (packageDirForCleanup) {
|
|
994
|
+
await removeNpmRc(packageDirForCleanup);
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
// src/modules/publish/publish.commands.ts
|
|
1000
|
+
var PublishPackageCommand = class extends Command5 {
|
|
1001
|
+
static paths = [["publish:package"]];
|
|
1002
|
+
access = Option2.String("--access", {
|
|
1003
|
+
description: "Kept for compatibility. Only public access is supported."
|
|
1004
|
+
});
|
|
1005
|
+
prepare = Option2.Boolean("--prepare", false, {
|
|
1006
|
+
description: "Prepare package before publishing"
|
|
1007
|
+
});
|
|
1008
|
+
async execute() {
|
|
1009
|
+
await publishContractPackage({
|
|
1010
|
+
access: this.access,
|
|
1011
|
+
prepare: this.prepare
|
|
1012
|
+
});
|
|
1013
|
+
}
|
|
1014
|
+
};
|
|
1015
|
+
|
|
1016
|
+
// cli.entrypoint.ts
|
|
1017
|
+
var clipanionClient = getClipanionClient();
|
|
1018
|
+
clipanionClient.register(InitCommand);
|
|
1019
|
+
clipanionClient.register(UpdateEnvironmentCommand);
|
|
1020
|
+
clipanionClient.register(BuildCommand);
|
|
1021
|
+
clipanionClient.register(PreparePackageCommand);
|
|
1022
|
+
clipanionClient.register(PackPackageCommand);
|
|
1023
|
+
clipanionClient.register(PublishPackageCommand);
|
|
1024
|
+
void clipanionClient.runExit(process.argv.slice(2));
|