@4pm/cli 0.3.0 → 0.3.1
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/index.js +141 -33
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -3725,18 +3725,41 @@ import {
|
|
|
3725
3725
|
chmodSync,
|
|
3726
3726
|
existsSync as existsSync2,
|
|
3727
3727
|
readFileSync as readFileSync2,
|
|
3728
|
-
rmSync,
|
|
3728
|
+
rmSync as rmSync2,
|
|
3729
3729
|
writeFileSync as writeFileSync2
|
|
3730
3730
|
} from "fs";
|
|
3731
3731
|
import { homedir as homedir2 } from "os";
|
|
3732
3732
|
import { join as join2 } from "path";
|
|
3733
3733
|
|
|
3734
3734
|
// src/config/profile.ts
|
|
3735
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
3735
|
+
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "fs";
|
|
3736
3736
|
import { homedir } from "os";
|
|
3737
3737
|
import { join } from "path";
|
|
3738
3738
|
var DEFAULT_PROFILE = "default";
|
|
3739
|
-
function
|
|
3739
|
+
function defaultPointerPath() {
|
|
3740
|
+
return join(homedir(), ".4pm", "default");
|
|
3741
|
+
}
|
|
3742
|
+
function defaultProfileName() {
|
|
3743
|
+
try {
|
|
3744
|
+
const userId = readFileSync(defaultPointerPath(), "utf8").trim();
|
|
3745
|
+
if (userId) return userId;
|
|
3746
|
+
} catch {
|
|
3747
|
+
}
|
|
3748
|
+
return DEFAULT_PROFILE;
|
|
3749
|
+
}
|
|
3750
|
+
function writeDefaultProfile(userId) {
|
|
3751
|
+
mkdirSync(join(homedir(), ".4pm"), { recursive: true });
|
|
3752
|
+
writeFileSync(defaultPointerPath(), userId, "utf8");
|
|
3753
|
+
}
|
|
3754
|
+
function clearDefaultProfileIf(name) {
|
|
3755
|
+
try {
|
|
3756
|
+
if (readFileSync(defaultPointerPath(), "utf8").trim() === name) {
|
|
3757
|
+
rmSync(defaultPointerPath(), { force: true });
|
|
3758
|
+
}
|
|
3759
|
+
} catch {
|
|
3760
|
+
}
|
|
3761
|
+
}
|
|
3762
|
+
function resolveProfileArg(args) {
|
|
3740
3763
|
const index = args.indexOf("--profile");
|
|
3741
3764
|
if (index >= 0) {
|
|
3742
3765
|
const name = args[index + 1];
|
|
@@ -3746,7 +3769,7 @@ function resolveProfile(args) {
|
|
|
3746
3769
|
const rest = [...args.slice(0, index), ...args.slice(index + 2)];
|
|
3747
3770
|
return [name, rest];
|
|
3748
3771
|
}
|
|
3749
|
-
return [process.env.FOURPM_PROFILE ??
|
|
3772
|
+
return [process.env.FOURPM_PROFILE ?? null, args];
|
|
3750
3773
|
}
|
|
3751
3774
|
function profileDir(name) {
|
|
3752
3775
|
const dir = join(homedir(), ".4pm", "profiles", name);
|
|
@@ -3777,7 +3800,7 @@ function readCredential(profileDir2, profileName) {
|
|
|
3777
3800
|
readFileSync2(LEGACY_CRE_PATH, "utf8")
|
|
3778
3801
|
);
|
|
3779
3802
|
writeCredential(profileDir2, legacy);
|
|
3780
|
-
|
|
3803
|
+
rmSync2(LEGACY_CRE_PATH, { force: true });
|
|
3781
3804
|
return legacy;
|
|
3782
3805
|
} catch {
|
|
3783
3806
|
return null;
|
|
@@ -3797,7 +3820,7 @@ function writeCredential(profileDir2, credential) {
|
|
|
3797
3820
|
chmodSync(path, 384);
|
|
3798
3821
|
}
|
|
3799
3822
|
function deleteCredential(profileDir2) {
|
|
3800
|
-
|
|
3823
|
+
rmSync2(credentialPath(profileDir2), { force: true });
|
|
3801
3824
|
}
|
|
3802
3825
|
|
|
3803
3826
|
// src/core/fingerprint.ts
|
|
@@ -3884,15 +3907,17 @@ async function fetchCliVersion(serverUrl) {
|
|
|
3884
3907
|
}
|
|
3885
3908
|
|
|
3886
3909
|
// src/commands/link.ts
|
|
3887
|
-
async function runLink(serverUrl,
|
|
3888
|
-
|
|
3910
|
+
async function runLink(serverUrl, explicitProfile) {
|
|
3911
|
+
const currentName = explicitProfile ?? defaultProfileName();
|
|
3912
|
+
if (readCredential(profileDir(currentName), currentName)) {
|
|
3913
|
+
const flag = explicitProfile ? ` --profile ${explicitProfile}` : "";
|
|
3889
3914
|
console.log(
|
|
3890
|
-
`Profile "${
|
|
3915
|
+
`Profile "${currentName}" is already linked (.cre). Run \`4pm unlink${flag}\` first if you want to pair again.`
|
|
3891
3916
|
);
|
|
3892
3917
|
return;
|
|
3893
3918
|
}
|
|
3894
3919
|
const hashcode1 = randomBytes(32).toString("hex");
|
|
3895
|
-
console.log(`\u2500\u2500 4PM cli pairing (profile: ${
|
|
3920
|
+
console.log(`\u2500\u2500 4PM cli pairing (profile: ${explicitProfile ?? "default"}) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`);
|
|
3896
3921
|
console.log("1. Open the 4PM web \u2192 /machines, enter hashcode (1):");
|
|
3897
3922
|
console.log(`
|
|
3898
3923
|
${hashcode1}
|
|
@@ -3901,15 +3926,23 @@ async function runLink(serverUrl, profileDir2, profileName) {
|
|
|
3901
3926
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
3902
3927
|
const hashcode2 = (await rl.question("hashcode (2): ")).trim();
|
|
3903
3928
|
rl.close();
|
|
3904
|
-
const { hashcode3 } = await confirmPairing(
|
|
3905
|
-
|
|
3929
|
+
const { hashcode3, userId, username } = await confirmPairing(
|
|
3930
|
+
serverUrl,
|
|
3931
|
+
hashcode2,
|
|
3932
|
+
collectFingerprint()
|
|
3933
|
+
);
|
|
3934
|
+
const profileName = explicitProfile ?? userId;
|
|
3935
|
+
writeCredential(profileDir(profileName), {
|
|
3906
3936
|
serverUrl,
|
|
3907
3937
|
hashcode3,
|
|
3908
3938
|
scope: "unknown",
|
|
3909
3939
|
// scope is decided by the server based on the pairing user (ADR-0010)
|
|
3910
3940
|
pairedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3911
3941
|
});
|
|
3912
|
-
|
|
3942
|
+
if (!explicitProfile) writeDefaultProfile(userId);
|
|
3943
|
+
console.log(
|
|
3944
|
+
`\u2714 Linked successfully as "${username}" \u2014 saved .cre. Run \`4pm start\` to connect.`
|
|
3945
|
+
);
|
|
3913
3946
|
}
|
|
3914
3947
|
|
|
3915
3948
|
// src/commands/start.ts
|
|
@@ -3930,7 +3963,7 @@ MCowBQYDK2VwAyEARlfzjaFhe5bhRrMp/ZuH6sVqg/S7f8B1dliuF21iDTw=
|
|
|
3930
3963
|
`;
|
|
3931
3964
|
|
|
3932
3965
|
// src/version.ts
|
|
3933
|
-
var CLI_VERSION = "0.3.
|
|
3966
|
+
var CLI_VERSION = "0.3.1";
|
|
3934
3967
|
|
|
3935
3968
|
// src/core/update.ts
|
|
3936
3969
|
function compareSemver(a, b) {
|
|
@@ -4074,6 +4107,7 @@ var Role = {
|
|
|
4074
4107
|
};
|
|
4075
4108
|
var ALL_ROLES = Object.values(Role);
|
|
4076
4109
|
var EXCLUSIVE_ROLES = [Role.ADMIN, Role.MACHINE];
|
|
4110
|
+
var PM_GRANTABLE_ROLES = [Role.SA_SE, Role.QA, Role.BA];
|
|
4077
4111
|
|
|
4078
4112
|
// ../../22-packages/11-shared/11-constants/src/permissions.ts
|
|
4079
4113
|
var PermissionCode = {
|
|
@@ -4260,6 +4294,13 @@ var SELF_SERVICE_CODES = [
|
|
|
4260
4294
|
P.USER_READ_SELF,
|
|
4261
4295
|
P.NOTIFICATION_READ
|
|
4262
4296
|
];
|
|
4297
|
+
var ADMIN_ONLY_CODES = [
|
|
4298
|
+
P.ORG_READ,
|
|
4299
|
+
P.ORG_UPDATE,
|
|
4300
|
+
P.ORG_DELETE,
|
|
4301
|
+
P.BILLING_READ,
|
|
4302
|
+
P.BILLING_MANAGE
|
|
4303
|
+
];
|
|
4263
4304
|
var TEAM_GRANTABLE_CODES = [
|
|
4264
4305
|
P.TEAM_READ,
|
|
4265
4306
|
P.TEAM_UPDATE,
|
|
@@ -4302,10 +4343,11 @@ var PROJECT_GRANTABLE_CODES = [
|
|
|
4302
4343
|
P.MACHINE_PHYSIC_MANAGE
|
|
4303
4344
|
];
|
|
4304
4345
|
var SELF_SERVICE_SET = new Set(SELF_SERVICE_CODES);
|
|
4346
|
+
var ADMIN_ONLY_SET = new Set(ADMIN_ONLY_CODES);
|
|
4305
4347
|
var TEAM_GRANTABLE_SET = new Set(TEAM_GRANTABLE_CODES);
|
|
4306
4348
|
var PROJECT_GRANTABLE_SET = new Set(PROJECT_GRANTABLE_CODES);
|
|
4307
4349
|
function grantSubjectsFor(code) {
|
|
4308
|
-
if (SELF_SERVICE_SET.has(code)) return [];
|
|
4350
|
+
if (SELF_SERVICE_SET.has(code) || ADMIN_ONLY_SET.has(code)) return [];
|
|
4309
4351
|
const subjects = [GrantSubjectType.USER];
|
|
4310
4352
|
if (TEAM_GRANTABLE_SET.has(code)) subjects.push(GrantSubjectType.TEAM);
|
|
4311
4353
|
if (PROJECT_GRANTABLE_SET.has(code)) subjects.push(GrantSubjectType.PROJECT);
|
|
@@ -4963,6 +5005,14 @@ var WsClient = class {
|
|
|
4963
5005
|
this.socket?.close();
|
|
4964
5006
|
return;
|
|
4965
5007
|
}
|
|
5008
|
+
if (message.errorCode === "SESSION_REPLACED") {
|
|
5009
|
+
this.stopped = true;
|
|
5010
|
+
console.error(
|
|
5011
|
+
"Another session for this profile took over \u2014 this instance is stopping. (Are you running `4pm start` twice for the same profile?)"
|
|
5012
|
+
);
|
|
5013
|
+
this.socket?.close();
|
|
5014
|
+
return;
|
|
5015
|
+
}
|
|
4966
5016
|
console.warn(`Server reported an error: ${message.errorCode}`);
|
|
4967
5017
|
this.socket?.close();
|
|
4968
5018
|
return;
|
|
@@ -5166,6 +5216,41 @@ var WsClient = class {
|
|
|
5166
5216
|
}
|
|
5167
5217
|
};
|
|
5168
5218
|
|
|
5219
|
+
// src/core/instance-lock.ts
|
|
5220
|
+
import { readFileSync as readFileSync5, rmSync as rmSync3, writeFileSync as writeFileSync5 } from "fs";
|
|
5221
|
+
import { join as join6 } from "path";
|
|
5222
|
+
function lockPath(profileDir2) {
|
|
5223
|
+
return join6(profileDir2, "start.lock");
|
|
5224
|
+
}
|
|
5225
|
+
function isAlive(pid) {
|
|
5226
|
+
try {
|
|
5227
|
+
process.kill(pid, 0);
|
|
5228
|
+
return true;
|
|
5229
|
+
} catch (err) {
|
|
5230
|
+
return err.code === "EPERM";
|
|
5231
|
+
}
|
|
5232
|
+
}
|
|
5233
|
+
function lockHolder(profileDir2) {
|
|
5234
|
+
try {
|
|
5235
|
+
const pid = Number.parseInt(readFileSync5(lockPath(profileDir2), "utf8").trim(), 10);
|
|
5236
|
+
if (Number.isInteger(pid) && pid > 0 && isAlive(pid)) return pid;
|
|
5237
|
+
} catch {
|
|
5238
|
+
}
|
|
5239
|
+
return null;
|
|
5240
|
+
}
|
|
5241
|
+
function acquireInstanceLock(profileDir2) {
|
|
5242
|
+
if (lockHolder(profileDir2) !== null) return false;
|
|
5243
|
+
writeFileSync5(lockPath(profileDir2), String(process.pid), "utf8");
|
|
5244
|
+
return true;
|
|
5245
|
+
}
|
|
5246
|
+
function releaseInstanceLock(profileDir2) {
|
|
5247
|
+
try {
|
|
5248
|
+
const pid = Number.parseInt(readFileSync5(lockPath(profileDir2), "utf8").trim(), 10);
|
|
5249
|
+
if (pid === process.pid) rmSync3(lockPath(profileDir2), { force: true });
|
|
5250
|
+
} catch {
|
|
5251
|
+
}
|
|
5252
|
+
}
|
|
5253
|
+
|
|
5169
5254
|
// src/commands/start.ts
|
|
5170
5255
|
async function runStart(profileDir2, profileName) {
|
|
5171
5256
|
const credential = readCredential(profileDir2, profileName);
|
|
@@ -5195,13 +5280,28 @@ async function runStart(profileDir2, profileName) {
|
|
|
5195
5280
|
child.on("exit", (code) => process.exit(code ?? 0));
|
|
5196
5281
|
return;
|
|
5197
5282
|
}
|
|
5198
|
-
|
|
5199
|
-
|
|
5200
|
-
|
|
5201
|
-
|
|
5202
|
-
|
|
5203
|
-
|
|
5204
|
-
}
|
|
5283
|
+
if (!acquireInstanceLock(profileDir2)) {
|
|
5284
|
+
console.error(
|
|
5285
|
+
`Profile "${profileName}" is already running (pid ${lockHolder(profileDir2)}). Only one \`4pm start\` per profile is allowed.`
|
|
5286
|
+
);
|
|
5287
|
+
process.exitCode = 1;
|
|
5288
|
+
return;
|
|
5289
|
+
}
|
|
5290
|
+
const release = () => releaseInstanceLock(profileDir2);
|
|
5291
|
+
process.once("exit", release);
|
|
5292
|
+
process.once("SIGINT", () => process.exit(0));
|
|
5293
|
+
process.once("SIGTERM", () => process.exit(0));
|
|
5294
|
+
try {
|
|
5295
|
+
initCommandHistory(profileDir2, profileName, config.commandHistoryUploadMinutes ?? 10);
|
|
5296
|
+
console.log(`Connecting to ${credential.serverUrl} (profile: ${profileName})\u2026`);
|
|
5297
|
+
await new WsClient({
|
|
5298
|
+
credential,
|
|
5299
|
+
machine: collectFingerprint(),
|
|
5300
|
+
profileDir: profileDir2
|
|
5301
|
+
}).run();
|
|
5302
|
+
} finally {
|
|
5303
|
+
release();
|
|
5304
|
+
}
|
|
5205
5305
|
}
|
|
5206
5306
|
|
|
5207
5307
|
// src/commands/unlink.ts
|
|
@@ -5211,6 +5311,7 @@ function runUnlink(profileDir2, profileName) {
|
|
|
5211
5311
|
return;
|
|
5212
5312
|
}
|
|
5213
5313
|
deleteCredential(profileDir2);
|
|
5314
|
+
clearDefaultProfileIf(profileName);
|
|
5214
5315
|
console.log("\u2714 Deleted .cre \u2014 revoke the server-side link at web /machines.");
|
|
5215
5316
|
}
|
|
5216
5317
|
|
|
@@ -5261,30 +5362,37 @@ function flagValue(args, name) {
|
|
|
5261
5362
|
return index >= 0 ? args[index + 1] : void 0;
|
|
5262
5363
|
}
|
|
5263
5364
|
async function main() {
|
|
5264
|
-
const [
|
|
5365
|
+
const [explicitProfile, args] = resolveProfileArg(process.argv.slice(2));
|
|
5265
5366
|
const [command, ...rest] = args;
|
|
5266
|
-
const dir = profileDir(profileName);
|
|
5267
5367
|
switch (command) {
|
|
5268
5368
|
case "link": {
|
|
5269
5369
|
const serverUrl = flagValue(rest, "server") ?? process.env.FOURPM_SERVER ?? "http://localhost:3001";
|
|
5270
|
-
await runLink(serverUrl.replace(/\/$/, ""),
|
|
5370
|
+
await runLink(serverUrl.replace(/\/$/, ""), explicitProfile);
|
|
5271
5371
|
break;
|
|
5272
5372
|
}
|
|
5273
|
-
case "start":
|
|
5274
|
-
|
|
5373
|
+
case "start": {
|
|
5374
|
+
const name = explicitProfile ?? defaultProfileName();
|
|
5375
|
+
await runStart(profileDir(name), name);
|
|
5275
5376
|
break;
|
|
5276
|
-
|
|
5277
|
-
|
|
5377
|
+
}
|
|
5378
|
+
case "unlink": {
|
|
5379
|
+
const name = explicitProfile ?? defaultProfileName();
|
|
5380
|
+
runUnlink(profileDir(name), name);
|
|
5278
5381
|
break;
|
|
5382
|
+
}
|
|
5279
5383
|
case "version":
|
|
5280
5384
|
case "--version":
|
|
5281
|
-
case "-v":
|
|
5282
|
-
|
|
5385
|
+
case "-v": {
|
|
5386
|
+
const name = explicitProfile ?? defaultProfileName();
|
|
5387
|
+
await runVersion(profileDir(name), name, flagValue(rest, "server"));
|
|
5283
5388
|
break;
|
|
5389
|
+
}
|
|
5284
5390
|
case "update":
|
|
5285
|
-
case "upgrade":
|
|
5286
|
-
|
|
5391
|
+
case "upgrade": {
|
|
5392
|
+
const name = explicitProfile ?? defaultProfileName();
|
|
5393
|
+
await runUpdate(profileDir(name), name, flagValue(rest, "server"));
|
|
5287
5394
|
break;
|
|
5395
|
+
}
|
|
5288
5396
|
default:
|
|
5289
5397
|
console.log(
|
|
5290
5398
|
[
|