@highstate/cli 0.27.0 → 0.28.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/dist/{chunk-2s546hay.js → chunk-hjrkeh0y.js} +64 -705
- package/dist/chunk-pa6gpwe5.js +713 -0
- package/dist/commands/index.js +2 -1
- package/dist/highstate.manifest.json +3 -2
- package/dist/main.js +7 -2
- package/dist/shared/index.js +72 -0
- package/package.json +8 -4
- package/src/commands/designer.ts +5 -9
- package/src/main.ts +3 -2
- package/src/shared/index.ts +1 -0
- package/src/shared/schema-transformer.ts +3 -3
- package/src/shared/version.ts +42 -0
|
@@ -0,0 +1,713 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
import {
|
|
3
|
+
SourceHashCalculator,
|
|
4
|
+
applyOverrides,
|
|
5
|
+
buildOverrides,
|
|
6
|
+
createBinTransformerPlugin,
|
|
7
|
+
createPackage,
|
|
8
|
+
disposeServices,
|
|
9
|
+
extractEntryPoints,
|
|
10
|
+
fetchManifest,
|
|
11
|
+
fetchNpmPackument,
|
|
12
|
+
findWorkspaceRoot,
|
|
13
|
+
generateFromTemplate,
|
|
14
|
+
getBackendServices,
|
|
15
|
+
getDependencyRange,
|
|
16
|
+
getProjectPlatformVersion,
|
|
17
|
+
highstateConfigSchema,
|
|
18
|
+
logger,
|
|
19
|
+
readCurrentPackageVersion,
|
|
20
|
+
resolveVersionBundle,
|
|
21
|
+
scanWorkspacePackages,
|
|
22
|
+
schemaTransformerPlugin,
|
|
23
|
+
updateTsconfigReferences,
|
|
24
|
+
writeJsonFile
|
|
25
|
+
} from "./chunk-hjrkeh0y.js";
|
|
26
|
+
import {
|
|
27
|
+
__require
|
|
28
|
+
} from "./chunk-vcev74he.js";
|
|
29
|
+
|
|
30
|
+
// src/commands/backend/identity.ts
|
|
31
|
+
import { hostname } from "os";
|
|
32
|
+
import { loadConfig } from "@highstate/backend";
|
|
33
|
+
import { identityToRecipient } from "age-encryption";
|
|
34
|
+
import { Command } from "clipanion";
|
|
35
|
+
class BackendIdentityCommand extends Command {
|
|
36
|
+
static paths = [["backend", "identity"]];
|
|
37
|
+
static usage = Command.Usage({
|
|
38
|
+
category: "Backend",
|
|
39
|
+
description: "Ensures the backend identity is set up and returns the recipient."
|
|
40
|
+
});
|
|
41
|
+
async execute() {
|
|
42
|
+
const { getOrCreateBackendIdentity } = await import("@highstate/backend");
|
|
43
|
+
const config = await loadConfig();
|
|
44
|
+
const backendIdentity = await getOrCreateBackendIdentity(config, logger);
|
|
45
|
+
const recipient = await identityToRecipient(backendIdentity);
|
|
46
|
+
logger.info(`stored backend identity: "%s"`, recipient);
|
|
47
|
+
const suggestedTitle = hostname();
|
|
48
|
+
if (!suggestedTitle) {
|
|
49
|
+
logger.info(`run "highstate backend unlock-method add %s" on a trusted device`, recipient);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
logger.info(`run "highstate backend unlock-method add %s --title %s" on a trusted device`, recipient, suggestedTitle);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
// src/commands/backend/unlock-method/add.ts
|
|
56
|
+
import { input } from "@inquirer/prompts";
|
|
57
|
+
import { Command as Command2, Option } from "clipanion";
|
|
58
|
+
class BackendUnlockMethodAddCommand extends Command2 {
|
|
59
|
+
static paths = [["backend", "unlock-method", "add"]];
|
|
60
|
+
static usage = Command2.Usage({
|
|
61
|
+
category: "Backend",
|
|
62
|
+
description: "Adds a new backend unlock method for the current workspace.",
|
|
63
|
+
examples: [["Add recipient", "highstate backend unlock-method add age1example --title Laptop"]]
|
|
64
|
+
});
|
|
65
|
+
recipient = Option.String();
|
|
66
|
+
title = Option.String("--title");
|
|
67
|
+
description = Option.String("--description");
|
|
68
|
+
async execute() {
|
|
69
|
+
let title = this.title;
|
|
70
|
+
if (!title) {
|
|
71
|
+
title = await input({
|
|
72
|
+
message: "Unlock Method Title",
|
|
73
|
+
default: "New Device",
|
|
74
|
+
validate: (value) => value.trim().length > 0 ? true : "Title is required"
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
let description = this.description;
|
|
78
|
+
if (description === undefined) {
|
|
79
|
+
description = await input({
|
|
80
|
+
message: "Description (optional)",
|
|
81
|
+
default: ""
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
const services = await getBackendServices();
|
|
85
|
+
try {
|
|
86
|
+
const result = await services.backendUnlockService.addUnlockMethod({
|
|
87
|
+
recipient: this.recipient,
|
|
88
|
+
meta: description ? { title: title.trim(), description: description.trim() } : { title: title.trim() }
|
|
89
|
+
});
|
|
90
|
+
logger.info(`added backend unlock method "%s"`, result.id);
|
|
91
|
+
} finally {
|
|
92
|
+
await disposeServices();
|
|
93
|
+
}
|
|
94
|
+
process.exit(0);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
// src/commands/backend/unlock-method/delete.ts
|
|
98
|
+
import { confirm } from "@inquirer/prompts";
|
|
99
|
+
import { Command as Command3, Option as Option2 } from "clipanion";
|
|
100
|
+
class BackendUnlockMethodDeleteCommand extends Command3 {
|
|
101
|
+
static paths = [["backend", "unlock-method", "delete"]];
|
|
102
|
+
static usage = Command3.Usage({
|
|
103
|
+
category: "Backend",
|
|
104
|
+
description: "Removes a backend unlock method by its identifier."
|
|
105
|
+
});
|
|
106
|
+
id = Option2.String();
|
|
107
|
+
force = Option2.Boolean("--force", false);
|
|
108
|
+
async execute() {
|
|
109
|
+
if (!this.force) {
|
|
110
|
+
const answer = await confirm({
|
|
111
|
+
message: `Delete backend unlock method ${this.id}?`,
|
|
112
|
+
default: false
|
|
113
|
+
});
|
|
114
|
+
if (!answer) {
|
|
115
|
+
logger.info("cancelled backend unlock method deletion");
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
const services = await getBackendServices();
|
|
120
|
+
try {
|
|
121
|
+
await services.backendUnlockService.deleteUnlockMethod(this.id);
|
|
122
|
+
logger.info(`deleted backend unlock method "%s"`, this.id);
|
|
123
|
+
} finally {
|
|
124
|
+
await disposeServices();
|
|
125
|
+
}
|
|
126
|
+
process.exit(0);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
// src/commands/backend/unlock-method/list.ts
|
|
130
|
+
import { Command as Command4 } from "clipanion";
|
|
131
|
+
import { Table } from "console-table-printer";
|
|
132
|
+
class BackendUnlockMethodListCommand extends Command4 {
|
|
133
|
+
static paths = [["backend", "unlock-method", "list"]];
|
|
134
|
+
static usage = Command4.Usage({
|
|
135
|
+
category: "Backend",
|
|
136
|
+
description: "Lists backend unlock methods registered for the current workspace."
|
|
137
|
+
});
|
|
138
|
+
async execute() {
|
|
139
|
+
const services = await getBackendServices();
|
|
140
|
+
try {
|
|
141
|
+
const methods = await services.backendUnlockService.listUnlockMethods();
|
|
142
|
+
if (methods.length === 0) {
|
|
143
|
+
logger.warn("no backend unlock methods configured");
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
const table = new Table({
|
|
147
|
+
columns: [
|
|
148
|
+
{ name: "title", title: "Title" },
|
|
149
|
+
{ name: "id", title: "ID" },
|
|
150
|
+
{ name: "recipient", title: "Recipient" },
|
|
151
|
+
{ name: "description", title: "Description", maxLen: 30 }
|
|
152
|
+
],
|
|
153
|
+
defaultColumnOptions: {
|
|
154
|
+
alignment: "left"
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
table.addRows(methods.map((method) => ({
|
|
158
|
+
title: method.meta.title,
|
|
159
|
+
id: method.id,
|
|
160
|
+
recipient: method.recipient,
|
|
161
|
+
description: method.meta.description ?? ""
|
|
162
|
+
})));
|
|
163
|
+
table.printTable();
|
|
164
|
+
} finally {
|
|
165
|
+
await disposeServices();
|
|
166
|
+
}
|
|
167
|
+
process.exit(0);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
// src/commands/build.ts
|
|
171
|
+
import { chmod, readFile, rm, writeFile } from "fs/promises";
|
|
172
|
+
import { resolve } from "path";
|
|
173
|
+
import { encode } from "@msgpack/msgpack";
|
|
174
|
+
import { Command as Command5, Option as Option3 } from "clipanion";
|
|
175
|
+
import { readPackageJSON, resolvePackageJSON } from "pkg-types";
|
|
176
|
+
function formatUnknownError(error) {
|
|
177
|
+
if (error instanceof Error) {
|
|
178
|
+
return error.stack ?? error.message;
|
|
179
|
+
}
|
|
180
|
+
if (typeof error === "string") {
|
|
181
|
+
return error;
|
|
182
|
+
}
|
|
183
|
+
try {
|
|
184
|
+
return JSON.stringify(error);
|
|
185
|
+
} catch {
|
|
186
|
+
return String(error);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
class BuildCommand extends Command5 {
|
|
191
|
+
static paths = [["build"]];
|
|
192
|
+
static usage = Command5.Usage({
|
|
193
|
+
category: "Builder",
|
|
194
|
+
description: "Builds the Highstate library or unit package."
|
|
195
|
+
});
|
|
196
|
+
library = Option3.Boolean("--library", false);
|
|
197
|
+
silent = Option3.Boolean("--silent", true);
|
|
198
|
+
noSourceHash = Option3.Boolean("--no-source-hash", false);
|
|
199
|
+
async execute() {
|
|
200
|
+
try {
|
|
201
|
+
await this.build();
|
|
202
|
+
} catch (error) {
|
|
203
|
+
if (error instanceof Error) {
|
|
204
|
+
throw error;
|
|
205
|
+
}
|
|
206
|
+
throw new Error(`Build failed with non-error rejection: ${formatUnknownError(error)}`);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
async build() {
|
|
210
|
+
const packageJson = await readPackageJSON();
|
|
211
|
+
const highstateConfig = highstateConfigSchema.parse(packageJson.highstate ?? {});
|
|
212
|
+
if (highstateConfig.type === "library") {
|
|
213
|
+
this.library = true;
|
|
214
|
+
}
|
|
215
|
+
if (highstateConfig.type === "worker") {
|
|
216
|
+
this.noSourceHash = true;
|
|
217
|
+
}
|
|
218
|
+
if (!packageJson.name) {
|
|
219
|
+
throw new Error("package.json must have a name field");
|
|
220
|
+
}
|
|
221
|
+
const entryPoints = extractEntryPoints(packageJson);
|
|
222
|
+
if (Object.keys(entryPoints).length === 0) {
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
const bunPlugins = [];
|
|
226
|
+
const binSourceFilePaths = Object.values(entryPoints).filter((value) => value.isBin).map((value) => value.entryPoint.slice(2));
|
|
227
|
+
if (this.library) {
|
|
228
|
+
bunPlugins.push(schemaTransformerPlugin);
|
|
229
|
+
}
|
|
230
|
+
if (binSourceFilePaths.length > 0) {
|
|
231
|
+
bunPlugins.push(createBinTransformerPlugin(binSourceFilePaths));
|
|
232
|
+
}
|
|
233
|
+
await rm("dist", { recursive: true, force: true });
|
|
234
|
+
const bunEntryPoints = Object.values(entryPoints).map((value) => value.entryPoint);
|
|
235
|
+
const result = await Bun.build({
|
|
236
|
+
entrypoints: bunEntryPoints,
|
|
237
|
+
outdir: "dist",
|
|
238
|
+
root: "./src",
|
|
239
|
+
format: "esm",
|
|
240
|
+
target: "bun",
|
|
241
|
+
external: ["@pulumi/pulumi"],
|
|
242
|
+
packages: "external",
|
|
243
|
+
splitting: true,
|
|
244
|
+
plugins: bunPlugins
|
|
245
|
+
});
|
|
246
|
+
if (!result.success) {
|
|
247
|
+
for (const log of result.logs) {
|
|
248
|
+
logger.error(log.message);
|
|
249
|
+
}
|
|
250
|
+
throw new Error("build failed");
|
|
251
|
+
}
|
|
252
|
+
const binEntryPoints = Object.values(entryPoints).filter((value) => value.isBin);
|
|
253
|
+
for (const binEntryPoint of binEntryPoints) {
|
|
254
|
+
const binPath = resolve(binEntryPoint.distPath);
|
|
255
|
+
const binContent = await readFile(binPath, "utf8");
|
|
256
|
+
if (!binContent.startsWith(`#!/usr/bin/env bun
|
|
257
|
+
`)) {
|
|
258
|
+
await writeFile(binPath, `#!/usr/bin/env bun
|
|
259
|
+
${binContent}`, "utf8");
|
|
260
|
+
}
|
|
261
|
+
await chmod(binPath, 493);
|
|
262
|
+
}
|
|
263
|
+
const packageJsonPath = await resolvePackageJSON();
|
|
264
|
+
const upToDatePackageJson = await readPackageJSON();
|
|
265
|
+
if (!this.noSourceHash) {
|
|
266
|
+
const sourceHashCalculator = new SourceHashCalculator(packageJsonPath, upToDatePackageJson, logger);
|
|
267
|
+
const distPathToExportKey = new Map;
|
|
268
|
+
for (const value of Object.values(entryPoints)) {
|
|
269
|
+
distPathToExportKey.set(value.distPath, value.key);
|
|
270
|
+
}
|
|
271
|
+
await sourceHashCalculator.writeHighstateManifest("./dist", distPathToExportKey);
|
|
272
|
+
}
|
|
273
|
+
if (this.library) {
|
|
274
|
+
const { loadLibrary } = await import("./chunk-sxh2gdkm.js");
|
|
275
|
+
const fullModulePaths = Object.values(entryPoints).map((value) => resolve(value.distPath));
|
|
276
|
+
logger.info("evaluating library components from modules: %s", fullModulePaths.join(", "));
|
|
277
|
+
const library = await loadLibrary(logger, fullModulePaths);
|
|
278
|
+
const libraryPath = resolve("./dist", "highstate.library.msgpack");
|
|
279
|
+
await writeFile(libraryPath, encode(library), "utf8");
|
|
280
|
+
}
|
|
281
|
+
logger.info("build completed successfully");
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
// src/commands/designer.ts
|
|
285
|
+
import { pathToFileURL } from "url";
|
|
286
|
+
import { Command as Command6, UsageError } from "clipanion";
|
|
287
|
+
import { consola } from "consola";
|
|
288
|
+
import { colorize } from "consola/utils";
|
|
289
|
+
import { checkPort, getPort } from "get-port-please";
|
|
290
|
+
import { resolve as importMetaResolve } from "import-meta-resolve";
|
|
291
|
+
import { addDevDependency } from "nypm";
|
|
292
|
+
import { readPackageJSON as readPackageJSON2, resolvePackageJSON as resolvePackageJSON2 } from "pkg-types";
|
|
293
|
+
var shuttingDown = false;
|
|
294
|
+
|
|
295
|
+
class DesignerCommand extends Command6 {
|
|
296
|
+
static paths = [["designer"]];
|
|
297
|
+
static usage = Command6.Usage({
|
|
298
|
+
category: "Designer",
|
|
299
|
+
description: "Starts the Highstate designer in the current project."
|
|
300
|
+
});
|
|
301
|
+
async execute() {
|
|
302
|
+
const packageJsonPath = await resolvePackageJSON2();
|
|
303
|
+
const packageJsonUrl = pathToFileURL(packageJsonPath).toString();
|
|
304
|
+
const packageJson = await readPackageJSON2(packageJsonPath);
|
|
305
|
+
if (!packageJson.devDependencies?.["@highstate/cli"]) {
|
|
306
|
+
throw new UsageError(`This project is not a Highstate project.
|
|
307
|
+
@highstate/cli must be installed as a devDependency.`);
|
|
308
|
+
}
|
|
309
|
+
if (!packageJson.devDependencies?.["@highstate/designer"]) {
|
|
310
|
+
logger.info("Installing @highstate/designer...");
|
|
311
|
+
await addDevDependency(["@highstate/designer", "classic-level"]);
|
|
312
|
+
}
|
|
313
|
+
logger.info("starting highstate designer...");
|
|
314
|
+
await getBackendServices();
|
|
315
|
+
const oldConsoleLog = console.log;
|
|
316
|
+
const host = "127.0.0.1";
|
|
317
|
+
const configuredPort = process.env.HIGHSTATE_DESIGNER_PORT;
|
|
318
|
+
const port = configuredPort === undefined ? 7283 : Number(configuredPort);
|
|
319
|
+
if (!/^\d+$/.test(configuredPort ?? port.toString()) || port < 1 || port > 65535) {
|
|
320
|
+
throw new UsageError(`HIGHSTATE_DESIGNER_PORT must be an integer between "1" and "65535"`);
|
|
321
|
+
}
|
|
322
|
+
if (configuredPort !== undefined) {
|
|
323
|
+
logger.warn(`using custom designer port "%s"; changing the port changes the WebAuthn origin and may require registering security keys again`, port);
|
|
324
|
+
}
|
|
325
|
+
const availablePort = await checkPort(port, host);
|
|
326
|
+
if (!availablePort) {
|
|
327
|
+
throw new UsageError(`Port "${port}" is already in use`);
|
|
328
|
+
}
|
|
329
|
+
const eventsPort = await getPort({ random: true, host });
|
|
330
|
+
const designerServerPath = importMetaResolve("@highstate/designer/server", packageJsonUrl);
|
|
331
|
+
const designerVersion = await readCurrentPackageVersion(designerServerPath);
|
|
332
|
+
process.env.NITRO_PORT = port.toString();
|
|
333
|
+
process.env.NITRO_HOST = host;
|
|
334
|
+
process.env.NITRO_BUN_IDLE_TIMEOUT ??= "255";
|
|
335
|
+
process.env.NUXT_PUBLIC_VERSION = designerVersion;
|
|
336
|
+
process.env.NUXT_PUBLIC_EVENTS_PORT = eventsPort.toString();
|
|
337
|
+
try {
|
|
338
|
+
await new Promise((resolve2, reject) => {
|
|
339
|
+
console.log = (message) => {
|
|
340
|
+
if (message.startsWith("Listening on")) {
|
|
341
|
+
if (!message.includes(`http://${host}:${port}`)) {
|
|
342
|
+
reject(new Error(`Designer started on an unexpected endpoint: ${message}`));
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
resolve2();
|
|
346
|
+
}
|
|
347
|
+
};
|
|
348
|
+
import(designerServerPath).catch(reject);
|
|
349
|
+
});
|
|
350
|
+
} finally {
|
|
351
|
+
console.log = oldConsoleLog;
|
|
352
|
+
}
|
|
353
|
+
consola.log([
|
|
354
|
+
`
|
|
355
|
+
`,
|
|
356
|
+
colorize("bold", colorize("cyanBright", "Highstate Designer")),
|
|
357
|
+
`
|
|
358
|
+
`,
|
|
359
|
+
colorize("greenBright", "\u279C Local: "),
|
|
360
|
+
colorize("underline", colorize("cyanBright", `http://highstate.localhost:${port}`)),
|
|
361
|
+
`
|
|
362
|
+
`
|
|
363
|
+
].join(""));
|
|
364
|
+
process.once("SIGINT", () => {
|
|
365
|
+
if (shuttingDown) {
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
shuttingDown = true;
|
|
369
|
+
process.stdout.write("\r");
|
|
370
|
+
consola.info("shutting down highstate designer...");
|
|
371
|
+
setTimeout(() => process.exit(0), 1000);
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
// src/commands/init.ts
|
|
376
|
+
import { access, mkdir, readdir } from "fs/promises";
|
|
377
|
+
import { resolve as resolve2 } from "path";
|
|
378
|
+
import { fileURLToPath } from "url";
|
|
379
|
+
import { input as input2 } from "@inquirer/prompts";
|
|
380
|
+
import { Command as Command7, Option as Option4 } from "clipanion";
|
|
381
|
+
import { installDependencies } from "nypm";
|
|
382
|
+
class InitCommand extends Command7 {
|
|
383
|
+
static paths = [["init"]];
|
|
384
|
+
static usage = Command7.Usage({
|
|
385
|
+
description: "Initializes a new Highstate project."
|
|
386
|
+
});
|
|
387
|
+
pathOption = Option4.String("--path,-p", {
|
|
388
|
+
description: "The path where the project should be initialized."
|
|
389
|
+
});
|
|
390
|
+
name = Option4.String("--name", {
|
|
391
|
+
description: "The project name."
|
|
392
|
+
});
|
|
393
|
+
platformVersion = Option4.String("--platform-version", {
|
|
394
|
+
description: "The Highstate platform version to use."
|
|
395
|
+
});
|
|
396
|
+
stdlibVersion = Option4.String("--stdlib-version", {
|
|
397
|
+
description: "The Highstate standard library version to use."
|
|
398
|
+
});
|
|
399
|
+
async execute() {
|
|
400
|
+
const isBunAvailable = await isExecutableInPath("bun");
|
|
401
|
+
if (!isBunAvailable) {
|
|
402
|
+
throw new Error('Required package manager "bun" was not found in PATH');
|
|
403
|
+
}
|
|
404
|
+
const projectName = await resolveProjectName(this.name);
|
|
405
|
+
const destinationPath = await resolveDestinationPath(this.pathOption, projectName);
|
|
406
|
+
const templatePath = resolveTemplatePath();
|
|
407
|
+
const versionBundle = await resolveVersionBundle({
|
|
408
|
+
platformVersion: this.platformVersion,
|
|
409
|
+
stdlibVersion: this.stdlibVersion
|
|
410
|
+
});
|
|
411
|
+
await mkdir(destinationPath, { recursive: true });
|
|
412
|
+
const isEmptyOrMissing = await isEmptyDirectory(destinationPath);
|
|
413
|
+
if (!isEmptyOrMissing) {
|
|
414
|
+
throw new Error(`Destination path is not empty: "${destinationPath}"`);
|
|
415
|
+
}
|
|
416
|
+
logger.info("initializing highstate project in %s", destinationPath);
|
|
417
|
+
await generateFromTemplate(templatePath, destinationPath, {
|
|
418
|
+
projectName,
|
|
419
|
+
packageName: projectName,
|
|
420
|
+
platformVersion: versionBundle.platformVersion,
|
|
421
|
+
libraryVersion: versionBundle.stdlibVersion
|
|
422
|
+
});
|
|
423
|
+
const overrides = buildOverrides(versionBundle);
|
|
424
|
+
await applyOverrides({
|
|
425
|
+
projectRoot: destinationPath,
|
|
426
|
+
overrides
|
|
427
|
+
});
|
|
428
|
+
logger.info("installing dependencies using bun...");
|
|
429
|
+
await installDependencies({
|
|
430
|
+
cwd: destinationPath,
|
|
431
|
+
packageManager: "bun",
|
|
432
|
+
silent: false
|
|
433
|
+
});
|
|
434
|
+
logger.info("project initialized successfully");
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
async function resolveDestinationPath(pathOption, projectName) {
|
|
438
|
+
if (pathOption) {
|
|
439
|
+
return resolve2(pathOption);
|
|
440
|
+
}
|
|
441
|
+
const defaultPath = resolve2(process.cwd(), projectName);
|
|
442
|
+
const pathValue = await input2({
|
|
443
|
+
message: "Project path",
|
|
444
|
+
default: defaultPath,
|
|
445
|
+
validate: (value) => value.trim().length > 0 ? true : "Path is required"
|
|
446
|
+
});
|
|
447
|
+
return resolve2(pathValue);
|
|
448
|
+
}
|
|
449
|
+
async function resolveProjectName(nameOption) {
|
|
450
|
+
if (nameOption !== undefined) {
|
|
451
|
+
const trimmed = nameOption.trim();
|
|
452
|
+
if (trimmed.length === 0) {
|
|
453
|
+
throw new Error('Flag "--name" must not be empty');
|
|
454
|
+
}
|
|
455
|
+
return trimmed;
|
|
456
|
+
}
|
|
457
|
+
const value = await input2({
|
|
458
|
+
message: "Project name",
|
|
459
|
+
default: "my-project",
|
|
460
|
+
validate: (inputValue) => inputValue.trim().length > 0 ? true : "Name is required"
|
|
461
|
+
});
|
|
462
|
+
return value.trim();
|
|
463
|
+
}
|
|
464
|
+
async function isExecutableInPath(command) {
|
|
465
|
+
const pathValue = process.env.PATH;
|
|
466
|
+
if (!pathValue) {
|
|
467
|
+
return false;
|
|
468
|
+
}
|
|
469
|
+
const parts = pathValue.split(":").filter(Boolean);
|
|
470
|
+
for (const part of parts) {
|
|
471
|
+
const candidate = resolve2(part, command);
|
|
472
|
+
try {
|
|
473
|
+
await access(candidate);
|
|
474
|
+
return true;
|
|
475
|
+
} catch {}
|
|
476
|
+
}
|
|
477
|
+
return false;
|
|
478
|
+
}
|
|
479
|
+
async function isEmptyDirectory(path) {
|
|
480
|
+
try {
|
|
481
|
+
const entries = await readdir(path);
|
|
482
|
+
return entries.length === 0;
|
|
483
|
+
} catch {
|
|
484
|
+
return true;
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
function resolveTemplatePath() {
|
|
488
|
+
const here = fileURLToPath(new URL(import.meta.url));
|
|
489
|
+
return resolve2(here, "..", "..", "assets", "template");
|
|
490
|
+
}
|
|
491
|
+
// src/commands/package/create.ts
|
|
492
|
+
import { Command as Command8, Option as Option5 } from "clipanion";
|
|
493
|
+
class PackageCreateCommand extends Command8 {
|
|
494
|
+
static paths = [["package", "create"]];
|
|
495
|
+
static usage = Command8.Usage({
|
|
496
|
+
category: "Package",
|
|
497
|
+
description: "Creates a new package in the workspace."
|
|
498
|
+
});
|
|
499
|
+
name = Option5.String({ required: true });
|
|
500
|
+
type = Option5.String("--type,-t", {
|
|
501
|
+
description: "Package type (source, library, worker)"
|
|
502
|
+
});
|
|
503
|
+
async execute() {
|
|
504
|
+
const workspaceRoot = await findWorkspaceRoot();
|
|
505
|
+
const packageType = highstateConfigSchema.shape.type.parse(this.type);
|
|
506
|
+
await createPackage(workspaceRoot, this.name, packageType);
|
|
507
|
+
const packages = await scanWorkspacePackages(workspaceRoot);
|
|
508
|
+
await updateTsconfigReferences(workspaceRoot, packages);
|
|
509
|
+
logger.info(`created package: @highstate/${this.name} (${packageType})`);
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
// src/commands/package/list.ts
|
|
513
|
+
import { Command as Command9 } from "clipanion";
|
|
514
|
+
import { Table as Table2 } from "console-table-printer";
|
|
515
|
+
class PackageListCommand extends Command9 {
|
|
516
|
+
static paths = [["package", "list"]];
|
|
517
|
+
static usage = Command9.Usage({
|
|
518
|
+
category: "Package",
|
|
519
|
+
description: "Lists all packages in the workspace with their types."
|
|
520
|
+
});
|
|
521
|
+
async execute() {
|
|
522
|
+
const workspaceRoot = await findWorkspaceRoot();
|
|
523
|
+
const packages = await scanWorkspacePackages(workspaceRoot);
|
|
524
|
+
if (packages.length === 0) {
|
|
525
|
+
logger.info("no packages found in workspace");
|
|
526
|
+
return;
|
|
527
|
+
}
|
|
528
|
+
const table = new Table2({
|
|
529
|
+
columns: [
|
|
530
|
+
{ name: "name", title: "Name" },
|
|
531
|
+
{ name: "type", title: "Type" },
|
|
532
|
+
{ name: "path", title: "Path" }
|
|
533
|
+
]
|
|
534
|
+
});
|
|
535
|
+
table.addRows(packages.map((pkg) => ({
|
|
536
|
+
name: pkg.name,
|
|
537
|
+
type: pkg.type ?? "unknown",
|
|
538
|
+
path: pkg.relativePath
|
|
539
|
+
})));
|
|
540
|
+
table.printTable();
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
// src/commands/package/remove.ts
|
|
544
|
+
import { rm as rm2 } from "fs/promises";
|
|
545
|
+
import { Command as Command10, Option as Option6 } from "clipanion";
|
|
546
|
+
class PackageRemoveCommand extends Command10 {
|
|
547
|
+
static paths = [["package", "remove"]];
|
|
548
|
+
static usage = Command10.Usage({
|
|
549
|
+
category: "Package",
|
|
550
|
+
description: "Removes a package from the workspace."
|
|
551
|
+
});
|
|
552
|
+
name = Option6.String({ required: true });
|
|
553
|
+
async execute() {
|
|
554
|
+
const workspaceRoot = await findWorkspaceRoot();
|
|
555
|
+
const packages = await scanWorkspacePackages(workspaceRoot);
|
|
556
|
+
const targetPackage = packages.find((pkg) => pkg.name === this.name || pkg.name === `@highstate/${this.name}` || pkg.relativePath.endsWith(this.name));
|
|
557
|
+
if (!targetPackage) {
|
|
558
|
+
logger.error(`package not found: ${this.name}`);
|
|
559
|
+
process.exit(1);
|
|
560
|
+
}
|
|
561
|
+
await rm2(targetPackage.path, { recursive: true, force: true });
|
|
562
|
+
const remainingPackages = await scanWorkspacePackages(workspaceRoot);
|
|
563
|
+
await updateTsconfigReferences(workspaceRoot, remainingPackages);
|
|
564
|
+
logger.info(`removed package: ${targetPackage.name}`);
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
// src/commands/package/update-references.ts
|
|
568
|
+
import { Command as Command11 } from "clipanion";
|
|
569
|
+
class PackageUpdateReferencesCommand extends Command11 {
|
|
570
|
+
static paths = [["package", "update-references"]];
|
|
571
|
+
static usage = Command11.Usage({
|
|
572
|
+
category: "Package",
|
|
573
|
+
description: "Updates the root tsconfig.json with references to all packages in the workspace."
|
|
574
|
+
});
|
|
575
|
+
async execute() {
|
|
576
|
+
const workspaceRoot = await findWorkspaceRoot();
|
|
577
|
+
const packages = await scanWorkspacePackages(workspaceRoot);
|
|
578
|
+
await updateTsconfigReferences(workspaceRoot, packages, true);
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
// src/commands/update.ts
|
|
582
|
+
import { readFile as readFile2 } from "fs/promises";
|
|
583
|
+
import { Command as Command12, Option as Option7 } from "clipanion";
|
|
584
|
+
import { readPackageJSON as readPackageJSON3, resolvePackageJSON as resolvePackageJSON3 } from "pkg-types";
|
|
585
|
+
import semver from "semver";
|
|
586
|
+
class UpdateCommand extends Command12 {
|
|
587
|
+
static paths = [["update"]];
|
|
588
|
+
static usage = Command12.Usage({
|
|
589
|
+
description: "Updates version overrides in an existing Highstate project."
|
|
590
|
+
});
|
|
591
|
+
platformVersion = Option7.String("--platform-version", {
|
|
592
|
+
description: "The Highstate platform version to set."
|
|
593
|
+
});
|
|
594
|
+
stdlibVersion = Option7.String("--stdlib-version", {
|
|
595
|
+
description: "The Highstate standard library version to set."
|
|
596
|
+
});
|
|
597
|
+
platformOnly = Option7.Boolean("--platform", false, {
|
|
598
|
+
description: "Update only platform versions."
|
|
599
|
+
});
|
|
600
|
+
stdlibOnly = Option7.Boolean("--stdlib", false, {
|
|
601
|
+
description: "Update only standard library versions."
|
|
602
|
+
});
|
|
603
|
+
install = Option7.Boolean("--install", true, {
|
|
604
|
+
description: "Install dependencies after updating overrides."
|
|
605
|
+
});
|
|
606
|
+
async execute() {
|
|
607
|
+
const projectRoot = process.cwd();
|
|
608
|
+
await assertPackageJsonExists(projectRoot);
|
|
609
|
+
if (this.platformOnly && this.stdlibOnly) {
|
|
610
|
+
throw new Error('Flags "--platform" and "--stdlib" cannot be used together');
|
|
611
|
+
}
|
|
612
|
+
const updatePlatform = this.platformOnly || !this.stdlibOnly;
|
|
613
|
+
const updateStdlib = this.stdlibOnly || !this.platformOnly;
|
|
614
|
+
let currentPlatformVersion;
|
|
615
|
+
let resolvedStdlibVersion = this.stdlibVersion;
|
|
616
|
+
if (this.stdlibOnly) {
|
|
617
|
+
const projectPlatformVersion = await getProjectPlatformVersion(projectRoot);
|
|
618
|
+
if (!projectPlatformVersion) {
|
|
619
|
+
throw new Error('Current platform version is not set in overrides for "@highstate/pulumi"');
|
|
620
|
+
}
|
|
621
|
+
currentPlatformVersion = projectPlatformVersion;
|
|
622
|
+
resolvedStdlibVersion = await resolveCompatibleStdlibVersion({
|
|
623
|
+
currentPlatformVersion: projectPlatformVersion,
|
|
624
|
+
stdlibVersion: this.stdlibVersion
|
|
625
|
+
});
|
|
626
|
+
}
|
|
627
|
+
const bundle = await resolveVersionBundle({
|
|
628
|
+
platformVersion: updatePlatform ? this.platformVersion : currentPlatformVersion,
|
|
629
|
+
stdlibVersion: updateStdlib ? resolvedStdlibVersion : undefined
|
|
630
|
+
});
|
|
631
|
+
const overrides = buildOverrides(bundle);
|
|
632
|
+
await applyOverrides({
|
|
633
|
+
projectRoot,
|
|
634
|
+
overrides
|
|
635
|
+
});
|
|
636
|
+
await syncRootPulumiDependency({
|
|
637
|
+
projectRoot,
|
|
638
|
+
pulumiVersion: bundle.pulumiVersion
|
|
639
|
+
});
|
|
640
|
+
logger.info("updated overrides: platform=%s stdlib=%s pulumi=%s", bundle.platformVersion, bundle.stdlibVersion, bundle.pulumiVersion);
|
|
641
|
+
if (this.install) {
|
|
642
|
+
const { installDependencies: installDependencies2 } = await import("nypm");
|
|
643
|
+
logger.info("installing dependencies using bun...");
|
|
644
|
+
await installDependencies2({
|
|
645
|
+
cwd: projectRoot,
|
|
646
|
+
packageManager: "bun",
|
|
647
|
+
silent: false
|
|
648
|
+
});
|
|
649
|
+
}
|
|
650
|
+
logger.info("update completed successfully");
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
async function resolveCompatibleStdlibVersion(args) {
|
|
654
|
+
const validPlatform = semver.valid(args.currentPlatformVersion);
|
|
655
|
+
if (!validPlatform) {
|
|
656
|
+
throw new Error(`Current platform version is not a valid semver "${args.currentPlatformVersion}"`);
|
|
657
|
+
}
|
|
658
|
+
const targetStdlibVersion = args.stdlibVersion?.trim();
|
|
659
|
+
if (targetStdlibVersion) {
|
|
660
|
+
await assertStdlibSupportsPlatform({
|
|
661
|
+
currentPlatformVersion: validPlatform,
|
|
662
|
+
stdlibVersion: targetStdlibVersion
|
|
663
|
+
});
|
|
664
|
+
return targetStdlibVersion;
|
|
665
|
+
}
|
|
666
|
+
const packument = await fetchNpmPackument("@highstate/library");
|
|
667
|
+
const sortedVersions = Object.entries(packument.versions ?? {}).filter(([version]) => semver.valid(version)).sort(([a], [b]) => semver.rcompare(a, b));
|
|
668
|
+
for (const [stdlibVersion, stdlibManifest] of sortedVersions) {
|
|
669
|
+
const supportedPlatformRange = getDependencyRange(stdlibManifest, "@highstate/pulumi");
|
|
670
|
+
if (!supportedPlatformRange) {
|
|
671
|
+
continue;
|
|
672
|
+
}
|
|
673
|
+
const ok = semver.satisfies(validPlatform, supportedPlatformRange, {
|
|
674
|
+
includePrerelease: true
|
|
675
|
+
});
|
|
676
|
+
if (ok) {
|
|
677
|
+
return stdlibVersion;
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
throw new Error(`Unable to find "@highstate/library" version compatible with platform "${validPlatform}"`);
|
|
681
|
+
}
|
|
682
|
+
async function assertStdlibSupportsPlatform(args) {
|
|
683
|
+
const stdlibManifest = await fetchManifest("@highstate/library", args.stdlibVersion);
|
|
684
|
+
const supportedPlatformRange = getDependencyRange(stdlibManifest, "@highstate/pulumi");
|
|
685
|
+
if (!supportedPlatformRange) {
|
|
686
|
+
throw new Error(`Unable to infer "@highstate/pulumi" version from "@highstate/library@${args.stdlibVersion}"`);
|
|
687
|
+
}
|
|
688
|
+
const ok = semver.satisfies(args.currentPlatformVersion, supportedPlatformRange, {
|
|
689
|
+
includePrerelease: true
|
|
690
|
+
});
|
|
691
|
+
if (!ok) {
|
|
692
|
+
throw new Error(`Current platform version "${args.currentPlatformVersion}" does not satisfy requirement "${supportedPlatformRange}"`);
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
async function assertPackageJsonExists(projectRoot) {
|
|
696
|
+
try {
|
|
697
|
+
await readFile2(`${projectRoot}/package.json`, "utf8");
|
|
698
|
+
} catch {
|
|
699
|
+
throw new Error(`File "package.json" not found in "${projectRoot}"`);
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
async function syncRootPulumiDependency(args) {
|
|
703
|
+
const packageJsonPath = await resolvePackageJSON3(args.projectRoot);
|
|
704
|
+
const packageJson = await readPackageJSON3(packageJsonPath);
|
|
705
|
+
await writeJsonFile(packageJsonPath, {
|
|
706
|
+
...packageJson,
|
|
707
|
+
dependencies: {
|
|
708
|
+
...packageJson.dependencies ?? {},
|
|
709
|
+
"@pulumi/pulumi": args.pulumiVersion
|
|
710
|
+
}
|
|
711
|
+
});
|
|
712
|
+
}
|
|
713
|
+
export { BackendIdentityCommand, BackendUnlockMethodAddCommand, BackendUnlockMethodDeleteCommand, BackendUnlockMethodListCommand, BuildCommand, DesignerCommand, InitCommand, PackageCreateCommand, PackageListCommand, PackageRemoveCommand, PackageUpdateReferencesCommand, UpdateCommand };
|