hostctl 0.1.48 → 0.1.49
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 +23 -0
- package/dist/bin/hostctl.js +187 -97
- package/dist/bin/hostctl.js.map +1 -1
- package/dist/index.js +187 -97
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -138,6 +138,29 @@ hostctl pkg remove hostctl-hello
|
|
|
138
138
|
Installed packages live under `~/.hostctl/packages` and can be run offline once cached.
|
|
139
139
|
Use `pkg install` for npm registry names (scoped + versioned) or git URLs. Local directories should be run directly without installing (`hostctl run ./path/to/pkg task args`); this avoids polluting the manifest with workstation paths and keeps the install story aligned with reproducible npm/git sources.
|
|
140
140
|
|
|
141
|
+
Add `--json` to any `hostctl pkg` command for machine-readable output:
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
hostctl pkg list --json
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
```json
|
|
148
|
+
{
|
|
149
|
+
"count": 1,
|
|
150
|
+
"duplicates": [],
|
|
151
|
+
"packages": [
|
|
152
|
+
{
|
|
153
|
+
"name": "hostctl-hello",
|
|
154
|
+
"version": "1.2.3",
|
|
155
|
+
"description": "Example tasks",
|
|
156
|
+
"directory": "hostctl-hello",
|
|
157
|
+
"source": "https://github.com/monopod/hostctl-example",
|
|
158
|
+
"path": "/home/you/.hostctl/packages/hostctl-hello"
|
|
159
|
+
}
|
|
160
|
+
]
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
|
|
141
164
|
## Designing tasks
|
|
142
165
|
|
|
143
166
|
Define tasks with the `task` helper and a typed `TaskContext`:
|
package/dist/bin/hostctl.js
CHANGED
|
@@ -3975,7 +3975,7 @@ var ParamMap = class _ParamMap {
|
|
|
3975
3975
|
import * as z from "zod";
|
|
3976
3976
|
|
|
3977
3977
|
// src/version.ts
|
|
3978
|
-
var version = "0.1.
|
|
3978
|
+
var version = "0.1.49";
|
|
3979
3979
|
|
|
3980
3980
|
// src/app.ts
|
|
3981
3981
|
import { retryUntilDefined } from "ts-retry";
|
|
@@ -4298,7 +4298,9 @@ var PackageManager = class {
|
|
|
4298
4298
|
this.manifest = { packages: [], version: "1.0" };
|
|
4299
4299
|
}
|
|
4300
4300
|
} catch (error) {
|
|
4301
|
-
|
|
4301
|
+
if (this.app.outputPlain()) {
|
|
4302
|
+
console.warn("Failed to load manifest, creating new one:", error);
|
|
4303
|
+
}
|
|
4302
4304
|
this.manifest = { packages: [], version: "1.0" };
|
|
4303
4305
|
}
|
|
4304
4306
|
}
|
|
@@ -4457,8 +4459,8 @@ var PackageManager = class {
|
|
|
4457
4459
|
const packageToRemove = this.findPackageToRemove(packageIdentifier);
|
|
4458
4460
|
if (!packageToRemove) {
|
|
4459
4461
|
return {
|
|
4460
|
-
success:
|
|
4461
|
-
|
|
4462
|
+
success: true,
|
|
4463
|
+
warning: `Package '${packageIdentifier}' not found. Use 'hostctl pkg list' to see installed packages.`
|
|
4462
4464
|
};
|
|
4463
4465
|
}
|
|
4464
4466
|
await this.removePackageFiles(packageToRemove);
|
|
@@ -4490,7 +4492,9 @@ var PackageManager = class {
|
|
|
4490
4492
|
prefix: packagesDir.toString()
|
|
4491
4493
|
});
|
|
4492
4494
|
} catch (error) {
|
|
4493
|
-
|
|
4495
|
+
if (this.app.outputPlain()) {
|
|
4496
|
+
console.log(`DEBUG: Error during npm uninstall: ${error}`);
|
|
4497
|
+
}
|
|
4494
4498
|
}
|
|
4495
4499
|
}
|
|
4496
4500
|
/**
|
|
@@ -4549,7 +4553,9 @@ var PackageManager = class {
|
|
|
4549
4553
|
const installDir = packagesDir.join(filenamifiedSource);
|
|
4550
4554
|
const existingPackage = this.findPackageBySource(normalizedSource);
|
|
4551
4555
|
if (existingPackage) {
|
|
4552
|
-
|
|
4556
|
+
if (this.app.outputPlain()) {
|
|
4557
|
+
console.log(`Package '${existingPackage.name}' is already installed from ${normalizedSource}`);
|
|
4558
|
+
}
|
|
4553
4559
|
return {
|
|
4554
4560
|
success: true,
|
|
4555
4561
|
packageInfo: existingPackage,
|
|
@@ -4653,7 +4659,9 @@ var PackageManager = class {
|
|
|
4653
4659
|
async findRealInstalledNpmPackagePath(packagesDir, source) {
|
|
4654
4660
|
const nodeModulesPath = packagesDir.join("node_modules");
|
|
4655
4661
|
if (!await nodeModulesPath.exists()) {
|
|
4656
|
-
|
|
4662
|
+
if (this.app.outputPlain()) {
|
|
4663
|
+
console.log(`DEBUG: node_modules does not exist at ${nodeModulesPath.toString()}`);
|
|
4664
|
+
}
|
|
4657
4665
|
return { path: null, name: null };
|
|
4658
4666
|
}
|
|
4659
4667
|
const entries = await fs6.readdir(nodeModulesPath.toString());
|
|
@@ -4694,6 +4702,9 @@ var PackageManager = class {
|
|
|
4694
4702
|
async handleDuplicateNames(finalPackageInfo) {
|
|
4695
4703
|
const actualPackageName = finalPackageInfo.name;
|
|
4696
4704
|
if (this.hasPackageWithName(actualPackageName)) {
|
|
4705
|
+
if (!this.app.outputPlain()) {
|
|
4706
|
+
return;
|
|
4707
|
+
}
|
|
4697
4708
|
const existingPackages = this.getPackagesWithName(actualPackageName);
|
|
4698
4709
|
console.warn(`\u26A0\uFE0F Warning: Package name '${actualPackageName}' already exists.`);
|
|
4699
4710
|
console.warn(` Existing packages:`);
|
|
@@ -6306,7 +6317,7 @@ dist/
|
|
|
6306
6317
|
.DS_Store
|
|
6307
6318
|
.env
|
|
6308
6319
|
`;
|
|
6309
|
-
async function createPackage(packageName, options) {
|
|
6320
|
+
async function createPackage(packageName, options, output) {
|
|
6310
6321
|
const resolvedName = expandTildePath(packageName);
|
|
6311
6322
|
const packageDir = path5.isAbsolute(resolvedName) ? resolvedName : path5.join(process.cwd(), resolvedName);
|
|
6312
6323
|
const packageSlug = path5.basename(resolvedName);
|
|
@@ -6329,115 +6340,134 @@ async function createPackage(packageName, options) {
|
|
|
6329
6340
|
await fs9.writeFile(path5.join(packageDir, "README.md"), readmeTemplate(packageJsonName, registryPrefix, false));
|
|
6330
6341
|
await fs9.writeFile(path5.join(packageDir, ".gitignore"), gitignoreTemplate);
|
|
6331
6342
|
}
|
|
6332
|
-
|
|
6333
|
-
|
|
6334
|
-
|
|
6335
|
-
console.log(`1. cd ${packageDir}`);
|
|
6336
|
-
console.log(`2. npm install`);
|
|
6337
|
-
let step = 3;
|
|
6343
|
+
const nextSteps = [];
|
|
6344
|
+
nextSteps.push(`cd ${packageDir}`);
|
|
6345
|
+
nextSteps.push("npm install");
|
|
6338
6346
|
if (options.lang === "typescript") {
|
|
6339
|
-
|
|
6340
|
-
|
|
6341
|
-
console.log(`${step}. npm run build`);
|
|
6342
|
-
step += 1;
|
|
6347
|
+
nextSteps.push("Edit src/index.ts and src/tasks/hello.ts to implement your tasks");
|
|
6348
|
+
nextSteps.push("npm run build");
|
|
6343
6349
|
} else {
|
|
6344
|
-
|
|
6345
|
-
step += 1;
|
|
6350
|
+
nextSteps.push("Edit src/index.js and src/tasks/hello.js to implement your tasks");
|
|
6346
6351
|
}
|
|
6347
|
-
|
|
6348
|
-
|
|
6349
|
-
|
|
6350
|
-
|
|
6352
|
+
nextSteps.push("Test your package with: hostctl tasks .");
|
|
6353
|
+
nextSteps.push(`Run it with: hostctl run . ${registryPrefix}.hello`);
|
|
6354
|
+
if (!output?.quiet) {
|
|
6355
|
+
console.log(`Created new hostctl package '${resolvedName}' at ${packageDir}`);
|
|
6356
|
+
console.log(`
|
|
6357
|
+
Next steps:`);
|
|
6358
|
+
nextSteps.forEach((step, index) => {
|
|
6359
|
+
console.log(`${index + 1}. ${step}`);
|
|
6360
|
+
});
|
|
6361
|
+
console.log(`
|
|
6351
6362
|
Note: The package includes 'hostctl' as a dependency for local development.`);
|
|
6352
|
-
|
|
6363
|
+
console.log(`For production, you may want to add it as a peer dependency instead.`);
|
|
6364
|
+
}
|
|
6365
|
+
return {
|
|
6366
|
+
packageDir,
|
|
6367
|
+
packageName: packageJsonName,
|
|
6368
|
+
registryPrefix,
|
|
6369
|
+
language: options.lang,
|
|
6370
|
+
nextSteps
|
|
6371
|
+
};
|
|
6353
6372
|
}
|
|
6354
6373
|
|
|
6355
6374
|
// src/commands/pkg/install.ts
|
|
6356
|
-
async function installPackage(source, app) {
|
|
6375
|
+
async function installPackage(source, app, output) {
|
|
6357
6376
|
const packageManager = new PackageManager(app);
|
|
6358
6377
|
const result = await packageManager.installPackage(source);
|
|
6359
|
-
if (
|
|
6360
|
-
|
|
6361
|
-
|
|
6362
|
-
|
|
6363
|
-
|
|
6378
|
+
if (!output?.quiet) {
|
|
6379
|
+
if (result.success) {
|
|
6380
|
+
console.log(`Installed package '${result.packageInfo.name}' from ${source} to ${result.installPath}`);
|
|
6381
|
+
} else {
|
|
6382
|
+
console.error(`Failed to install package from ${source}: ${result.error}`);
|
|
6383
|
+
}
|
|
6364
6384
|
}
|
|
6385
|
+
return { ...result, source };
|
|
6365
6386
|
}
|
|
6366
6387
|
|
|
6367
6388
|
// src/commands/pkg/list.ts
|
|
6368
6389
|
import chalk5 from "chalk";
|
|
6369
6390
|
async function listPackages(app) {
|
|
6370
6391
|
const packageManager = new PackageManager(app);
|
|
6371
|
-
|
|
6372
|
-
|
|
6373
|
-
|
|
6374
|
-
|
|
6375
|
-
|
|
6392
|
+
const packages = await packageManager.listPackages();
|
|
6393
|
+
const packagesByName = /* @__PURE__ */ new Map();
|
|
6394
|
+
packages.forEach((pkg) => {
|
|
6395
|
+
if (!packagesByName.has(pkg.name)) {
|
|
6396
|
+
packagesByName.set(pkg.name, []);
|
|
6376
6397
|
}
|
|
6377
|
-
|
|
6378
|
-
|
|
6379
|
-
|
|
6380
|
-
|
|
6381
|
-
|
|
6382
|
-
|
|
6383
|
-
|
|
6384
|
-
console.log(
|
|
6398
|
+
packagesByName.get(pkg.name).push(pkg);
|
|
6399
|
+
});
|
|
6400
|
+
const duplicateNames = Array.from(packagesByName.entries()).filter(([_, pkgs]) => pkgs.length > 1).map(([name]) => name);
|
|
6401
|
+
return { packages, duplicateNames };
|
|
6402
|
+
}
|
|
6403
|
+
function printPackageList(packages, duplicateNames) {
|
|
6404
|
+
if (packages.length === 0) {
|
|
6405
|
+
console.log("No packages installed.");
|
|
6406
|
+
return;
|
|
6407
|
+
}
|
|
6408
|
+
const packagesByName = /* @__PURE__ */ new Map();
|
|
6409
|
+
packages.forEach((pkg) => {
|
|
6410
|
+
if (!packagesByName.has(pkg.name)) {
|
|
6411
|
+
packagesByName.set(pkg.name, []);
|
|
6412
|
+
}
|
|
6413
|
+
packagesByName.get(pkg.name).push(pkg);
|
|
6414
|
+
});
|
|
6415
|
+
console.log(`Found ${packages.length} package(s):
|
|
6385
6416
|
`);
|
|
6386
|
-
|
|
6387
|
-
|
|
6388
|
-
|
|
6389
|
-
|
|
6390
|
-
|
|
6391
|
-
|
|
6392
|
-
|
|
6393
|
-
|
|
6394
|
-
|
|
6395
|
-
|
|
6396
|
-
|
|
6397
|
-
|
|
6398
|
-
|
|
6399
|
-
|
|
6400
|
-
|
|
6401
|
-
|
|
6402
|
-
|
|
6417
|
+
packages.forEach((pkg) => {
|
|
6418
|
+
const packagesWithSameName = packagesByName.get(pkg.name);
|
|
6419
|
+
const hasDuplicates = packagesWithSameName.length > 1;
|
|
6420
|
+
let output = `\u2022 ${pkg.name}`;
|
|
6421
|
+
if (pkg.version) {
|
|
6422
|
+
output += ` (v${pkg.version})`;
|
|
6423
|
+
}
|
|
6424
|
+
if (pkg.description) {
|
|
6425
|
+
output += ` - ${pkg.description}`;
|
|
6426
|
+
}
|
|
6427
|
+
if (hasDuplicates) {
|
|
6428
|
+
output = chalk5.yellow(output);
|
|
6429
|
+
output += chalk5.yellow(" \u26A0\uFE0F (duplicate name)");
|
|
6430
|
+
}
|
|
6431
|
+
if (pkg.source) {
|
|
6432
|
+
if (pkg.source.startsWith("http") || pkg.source.startsWith("git@")) {
|
|
6433
|
+
output += `
|
|
6403
6434
|
\u{1F4E6} Source: ${pkg.source}`;
|
|
6404
|
-
|
|
6405
|
-
|
|
6435
|
+
} else if (!pkg.source.startsWith(".") && !pkg.source.startsWith("/")) {
|
|
6436
|
+
output += `
|
|
6406
6437
|
\u{1F4E6} Source: https://www.npmjs.com/package/${pkg.source}`;
|
|
6407
|
-
|
|
6408
|
-
|
|
6438
|
+
} else {
|
|
6439
|
+
output += `
|
|
6409
6440
|
\u{1F4E6} Source: ${pkg.source}`;
|
|
6410
|
-
}
|
|
6411
6441
|
}
|
|
6412
|
-
console.log(output);
|
|
6413
|
-
console.log(` \u2514\u2500 (run "hostctl tasks ${pkg.name}" to list tasks)`);
|
|
6414
|
-
console.log("");
|
|
6415
|
-
});
|
|
6416
|
-
const duplicateNames = Array.from(packagesByName.entries()).filter(([_, pkgs]) => pkgs.length > 1).map(([name, _]) => name);
|
|
6417
|
-
if (duplicateNames.length > 0) {
|
|
6418
|
-
console.log(chalk5.yellow("\u26A0\uFE0F Warning: The following package names have duplicates:"));
|
|
6419
|
-
duplicateNames.forEach((name) => {
|
|
6420
|
-
console.log(chalk5.yellow(` - ${name}`));
|
|
6421
|
-
});
|
|
6422
|
-
console.log(chalk5.yellow(" Use the full source URL to run tasks from these packages."));
|
|
6423
|
-
console.log("");
|
|
6424
6442
|
}
|
|
6425
|
-
|
|
6426
|
-
console.
|
|
6427
|
-
|
|
6443
|
+
console.log(output);
|
|
6444
|
+
console.log(` \u2514\u2500 (run "hostctl tasks ${pkg.name}" to list tasks)`);
|
|
6445
|
+
console.log("");
|
|
6446
|
+
});
|
|
6447
|
+
if (duplicateNames.length > 0) {
|
|
6448
|
+
console.log(chalk5.yellow("\u26A0\uFE0F Warning: The following package names have duplicates:"));
|
|
6449
|
+
duplicateNames.forEach((name) => {
|
|
6450
|
+
console.log(chalk5.yellow(` - ${name}`));
|
|
6451
|
+
});
|
|
6452
|
+
console.log(chalk5.yellow(" Use the full source URL to run tasks from these packages."));
|
|
6453
|
+
console.log("");
|
|
6428
6454
|
}
|
|
6429
6455
|
}
|
|
6430
6456
|
|
|
6431
6457
|
// src/commands/pkg/remove.ts
|
|
6432
|
-
async function removePackage(packageIdentifier, app) {
|
|
6458
|
+
async function removePackage(packageIdentifier, app, output) {
|
|
6433
6459
|
const packageManager = new PackageManager(app);
|
|
6434
6460
|
const result = await packageManager.removePackage(packageIdentifier);
|
|
6435
|
-
if (
|
|
6436
|
-
|
|
6437
|
-
|
|
6438
|
-
|
|
6439
|
-
|
|
6461
|
+
if (!output?.quiet) {
|
|
6462
|
+
if (result.success && result.packageInfo) {
|
|
6463
|
+
console.log(`Successfully removed package '${result.packageInfo.name}' (${result.packageInfo.directory})`);
|
|
6464
|
+
} else if (result.success && result.warning) {
|
|
6465
|
+
console.warn(result.warning);
|
|
6466
|
+
} else {
|
|
6467
|
+
console.error(`Failed to remove package: ${result.error}`);
|
|
6468
|
+
}
|
|
6440
6469
|
}
|
|
6470
|
+
return result;
|
|
6441
6471
|
}
|
|
6442
6472
|
|
|
6443
6473
|
// src/cli.ts
|
|
@@ -6947,26 +6977,86 @@ var Cli = class {
|
|
|
6947
6977
|
}
|
|
6948
6978
|
}
|
|
6949
6979
|
async handlePkgCreate(packageName, options) {
|
|
6980
|
+
const opts = this.program.opts();
|
|
6981
|
+
if (opts.json) {
|
|
6982
|
+
try {
|
|
6983
|
+
const result = await createPackage(packageName, options, { quiet: true });
|
|
6984
|
+
console.log(JSON.stringify({ success: true, ...result }, null, 2));
|
|
6985
|
+
} catch (error) {
|
|
6986
|
+
console.log(
|
|
6987
|
+
JSON.stringify({ success: false, error: error instanceof Error ? error.message : String(error) }, null, 2)
|
|
6988
|
+
);
|
|
6989
|
+
process4.exitCode = 1;
|
|
6990
|
+
}
|
|
6991
|
+
return;
|
|
6992
|
+
}
|
|
6950
6993
|
await createPackage(packageName, options);
|
|
6951
6994
|
}
|
|
6952
6995
|
async handlePkgInstall(source) {
|
|
6953
|
-
|
|
6954
|
-
|
|
6955
|
-
|
|
6956
|
-
|
|
6957
|
-
console.
|
|
6958
|
-
|
|
6996
|
+
const opts = this.program.opts();
|
|
6997
|
+
await this.loadApp(opts);
|
|
6998
|
+
const result = await installPackage(source, this.app, { quiet: Boolean(opts.json) });
|
|
6999
|
+
if (opts.json) {
|
|
7000
|
+
console.log(
|
|
7001
|
+
JSON.stringify(
|
|
7002
|
+
{
|
|
7003
|
+
success: result.success,
|
|
7004
|
+
source: result.source,
|
|
7005
|
+
package: result.packageInfo,
|
|
7006
|
+
installPath: result.installPath,
|
|
7007
|
+
error: result.error
|
|
7008
|
+
},
|
|
7009
|
+
null,
|
|
7010
|
+
2
|
|
7011
|
+
)
|
|
6959
7012
|
);
|
|
7013
|
+
}
|
|
7014
|
+
if (!result.success) {
|
|
6960
7015
|
process4.exitCode = 1;
|
|
6961
7016
|
}
|
|
6962
7017
|
}
|
|
6963
7018
|
async handlePkgList() {
|
|
6964
|
-
|
|
6965
|
-
await
|
|
7019
|
+
const opts = this.program.opts();
|
|
7020
|
+
await this.loadApp(opts);
|
|
7021
|
+
const result = await listPackages(this.app);
|
|
7022
|
+
if (opts.json) {
|
|
7023
|
+
console.log(
|
|
7024
|
+
JSON.stringify(
|
|
7025
|
+
{
|
|
7026
|
+
count: result.packages.length,
|
|
7027
|
+
duplicates: result.duplicateNames,
|
|
7028
|
+
packages: result.packages
|
|
7029
|
+
},
|
|
7030
|
+
null,
|
|
7031
|
+
2
|
|
7032
|
+
)
|
|
7033
|
+
);
|
|
7034
|
+
return;
|
|
7035
|
+
}
|
|
7036
|
+
printPackageList(result.packages, result.duplicateNames);
|
|
6966
7037
|
}
|
|
6967
7038
|
async handlePkgRemove(packageIdentifier) {
|
|
6968
|
-
|
|
6969
|
-
await
|
|
7039
|
+
const opts = this.program.opts();
|
|
7040
|
+
await this.loadApp(opts);
|
|
7041
|
+
const result = await removePackage(packageIdentifier, this.app, { quiet: Boolean(opts.json) });
|
|
7042
|
+
if (opts.json) {
|
|
7043
|
+
console.log(
|
|
7044
|
+
JSON.stringify(
|
|
7045
|
+
{
|
|
7046
|
+
success: result.success,
|
|
7047
|
+
identifier: packageIdentifier,
|
|
7048
|
+
package: result.packageInfo,
|
|
7049
|
+
error: result.error,
|
|
7050
|
+
warning: result.warning
|
|
7051
|
+
},
|
|
7052
|
+
null,
|
|
7053
|
+
2
|
|
7054
|
+
)
|
|
7055
|
+
);
|
|
7056
|
+
}
|
|
7057
|
+
if (!result.success) {
|
|
7058
|
+
process4.exitCode = 1;
|
|
7059
|
+
}
|
|
6970
7060
|
}
|
|
6971
7061
|
async handleTasksList(specParts, options, cmd) {
|
|
6972
7062
|
const opts = cmd.optsWithGlobals();
|