@newlandnpt/pep-cli 0.8.2 → 0.9.0-rc.2
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 +80 -5
- package/dist/pep.cjs +639 -109
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -52,8 +52,78 @@ node scripts/oauth-bootstrap.mjs --client --public \
|
|
|
52
52
|
If one PEP deployment serves multiple customer tenants, register a distinct client ID for each tenant
|
|
53
53
|
and pass it with `--client-id`.
|
|
54
54
|
|
|
55
|
+
## First run
|
|
56
|
+
|
|
57
|
+
```powershell
|
|
58
|
+
pep setup
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Runs the three first-run steps in order — sign in, install the integration guide skills, configure
|
|
62
|
+
Maven credentials — and **skips what is already done**.
|
|
63
|
+
|
|
64
|
+
Step 2 installs **every skills repository in a group**. It asks the platform which repositories that
|
|
65
|
+
group holds right now, then installs each one, so a new skills repository reaches everybody without
|
|
66
|
+
a CLI release. A repository that fails does not stop the others: the command reports how many went
|
|
67
|
+
in, which ones did not, and exits non-zero.
|
|
68
|
+
|
|
69
|
+
Steps 2 and 3 are independent: if one fails the other still runs, and the command names which ones
|
|
70
|
+
did not complete (and exits non-zero). Every step is idempotent, so running it again is safe — that
|
|
71
|
+
is the intended way to recover from a partial run.
|
|
72
|
+
|
|
73
|
+
```powershell
|
|
74
|
+
pep setup # all three steps
|
|
75
|
+
pep setup --skip-nexus # skip Maven (only Android needs it)
|
|
76
|
+
pep setup --skip-skills # only sign in and configure Maven
|
|
77
|
+
pep setup group/sub -p # a different group, into THIS project
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
`--issuer`, `--client-id` and `--resource` work here exactly as on `pep auth login`, for pointing a
|
|
81
|
+
run at another deployment:
|
|
82
|
+
|
|
83
|
+
```powershell
|
|
84
|
+
pep setup --issuer https://pep-dev.example --client-id <that deployment's id>
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
⚠ Both are needed for a non-production deployment: the client ID is baked in for production and does
|
|
88
|
+
not fork per environment, so production's ID gets `unauthorized_client` elsewhere. If you are already
|
|
89
|
+
signed in to a different deployment, `pep setup` signs in again rather than silently using the one
|
|
90
|
+
you are signed in to — and since there is a single stored sign-in, that replaces it.
|
|
91
|
+
|
|
92
|
+
The integration guide **group** is baked in at build time (currently `distribution/skills`). Passing
|
|
93
|
+
one overrides it **for that run only** — it is deliberately not remembered, unlike `--docs-url`.
|
|
94
|
+
Where each repository installs *is* remembered, in the local ledger, so `pep skills update` later
|
|
95
|
+
returns each one to the same place.
|
|
96
|
+
|
|
97
|
+
If a repository you installed earlier has since left the group, `pep setup` says so and **leaves it
|
|
98
|
+
alone** — nothing on your disk is deleted. It stays in the ledger, so `pep skills update` will report
|
|
99
|
+
404 for it (which reads exactly like a typo, but is not). `pep skills remove <repo>` drops it for
|
|
100
|
+
good.
|
|
101
|
+
|
|
102
|
+
Step 3 needs a contract that includes Maven repository access (`ADMIN`, `US-ISO`, `US-ISV`); other
|
|
103
|
+
contracts get a 403 there, which is why it fails softly rather than aborting the run. It also never
|
|
104
|
+
prints the password — run `pep nexus setup --print-env` yourself if you need that.
|
|
105
|
+
|
|
106
|
+
Each step's own command still exists and does exactly the same thing; `pep setup` only chains them.
|
|
107
|
+
|
|
55
108
|
## Use
|
|
56
109
|
|
|
110
|
+
Use `--help` (or `-h`) at any level for the relevant commands, purpose, flags and examples:
|
|
111
|
+
|
|
112
|
+
```powershell
|
|
113
|
+
pep --help
|
|
114
|
+
pep setup --help
|
|
115
|
+
pep auth --help
|
|
116
|
+
pep auth login --help
|
|
117
|
+
pep docs get --help
|
|
118
|
+
pep skills add --help
|
|
119
|
+
pep skills update --help
|
|
120
|
+
pep nexus setup --help
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
`pep help auth login` is equivalent to `pep auth login --help`. Help is available without
|
|
124
|
+
logging in and does not access credentials or contact any service. The `pep-cli` alias supports
|
|
125
|
+
the same commands.
|
|
126
|
+
|
|
57
127
|
```powershell
|
|
58
128
|
pep auth login
|
|
59
129
|
pep auth status
|
|
@@ -86,6 +156,9 @@ pep auth login --issuer https://pep-webapp-dev.onrender.com --client-id <that de
|
|
|
86
156
|
|
|
87
157
|
## Skills
|
|
88
158
|
|
|
159
|
+
> **`pep skills add` will be removed in a future release.** It still works and behaves exactly as
|
|
160
|
+
> before; using it now prints that notice.
|
|
161
|
+
|
|
89
162
|
```powershell
|
|
90
163
|
pep skills add https://git.newlandpayment.com/group/sub/project # or group/sub/project[@ref]
|
|
91
164
|
pep skills list
|
|
@@ -204,16 +277,18 @@ Asks PEP for your Maven repository credential and saves it as the two environmen
|
|
|
204
277
|
Newland Android SDK reads (`NEWLAND_MAVEN_USERNAME` / `NEWLAND_MAVEN_PASSWORD`). Windows uses
|
|
205
278
|
`setx`; macOS keeps a marked block in your shell profile, backed up first.
|
|
206
279
|
|
|
207
|
-
**The password is never printed.** The credential is derived from
|
|
208
|
-
again — on this machine or any other — hands you the same one; nothing is lost by not
|
|
209
|
-
Use `--print-env` when you want the two lines on stdout instead:
|
|
280
|
+
**The password is never printed.** The credential is derived from your organisation's identity, so
|
|
281
|
+
running this again — on this machine or any other — hands you the same one; nothing is lost by not
|
|
282
|
+
showing it. Use `--print-env` when you want the two lines on stdout instead:
|
|
210
283
|
|
|
211
284
|
```bash
|
|
212
285
|
eval "$(pep nexus setup --print-env)" # the shell you are in gets them now
|
|
213
286
|
```
|
|
214
287
|
|
|
215
|
-
The credential is per
|
|
216
|
-
|
|
288
|
+
The credential is **per company, not per person**: colleagues who run this get the same username and
|
|
289
|
+
password. It is a read-only credential for the SDK repositories, not a personal account — it says
|
|
290
|
+
which company is pulling, not who. Nothing about it is per-machine either, which is the point: a
|
|
291
|
+
second laptop just runs the command again.
|
|
217
292
|
|
|
218
293
|
⚠ Persistence only reaches **new** processes. A running IDE keeps the environment it started with,
|
|
219
294
|
and the Gradle daemon outlives the IDE, so after the first setup: quit Android Studio completely,
|
package/dist/pep.cjs
CHANGED
|
@@ -22,6 +22,8 @@ var DEFAULT_SCOPES = [
|
|
|
22
22
|
var DEFAULT_RESOURCES = ["urn:newland:pep:docs"];
|
|
23
23
|
var LEGACY_PACKAGE = "semi-integration";
|
|
24
24
|
var DEFAULT_DOCS_URL = "https://docs.newlandnpt.us";
|
|
25
|
+
var DEFAULT_SKILLS_GROUP = "distribution/skills";
|
|
26
|
+
var EXAMPLE_SKILLS_REPO = `${DEFAULT_SKILLS_GROUP}/semi-integration-skill`;
|
|
25
27
|
function issuerForEnvironment(environment) {
|
|
26
28
|
if (environment === "production") return "https://pep.newlandnpt.us";
|
|
27
29
|
if (environment === "view") return "https://pep-webapp-view.onrender.com";
|
|
@@ -932,8 +934,15 @@ function stamp(at) {
|
|
|
932
934
|
}
|
|
933
935
|
async function persistMavenEnv(credential, target = resolveTarget(), now = () => /* @__PURE__ */ new Date()) {
|
|
934
936
|
if (target.kind === "windows") {
|
|
935
|
-
|
|
936
|
-
|
|
937
|
+
try {
|
|
938
|
+
await run("setx", [USERNAME_VAR, credential.username]);
|
|
939
|
+
await run("setx", [PASSWORD_VAR, credential.password]);
|
|
940
|
+
} catch {
|
|
941
|
+
return {
|
|
942
|
+
status: "manual",
|
|
943
|
+
reason: "setx could not write the user environment. The original error is omitted on purpose: it contains the password."
|
|
944
|
+
};
|
|
945
|
+
}
|
|
937
946
|
return { status: "windows" };
|
|
938
947
|
}
|
|
939
948
|
const existing = await (0, import_promises3.readFile)(target.path, "utf8").catch((error) => {
|
|
@@ -961,6 +970,304 @@ async function persistMavenEnv(credential, target = resolveTarget(), now = () =>
|
|
|
961
970
|
return plan.action === "replace" ? { status: "profile-replaced", path: target.path, backupPath } : { status: "profile-appended", path: target.path, ...backupPath ? { backupPath } : {} };
|
|
962
971
|
}
|
|
963
972
|
|
|
973
|
+
// src/help.ts
|
|
974
|
+
var EXAMPLE_PATH = EXAMPLE_SKILLS_REPO;
|
|
975
|
+
var EXAMPLE_REPO = `https://git.newlandpayment.com/${EXAMPLE_PATH}.git`;
|
|
976
|
+
var PROJECT_FLAG = "-p, --project Install into ./.agents/skills of the current directory instead";
|
|
977
|
+
var groups = {
|
|
978
|
+
auth: {
|
|
979
|
+
summary: "Sign in to PEP and manage your local authorization.",
|
|
980
|
+
commands: {
|
|
981
|
+
login: {
|
|
982
|
+
summary: "Sign in to PEP through your browser.",
|
|
983
|
+
usage: "pep auth login [flags]",
|
|
984
|
+
description: `Opens your browser to sign in, then saves tokens in the OS keychain.
|
|
985
|
+
Tokens are refreshed automatically when needed by other commands.
|
|
986
|
+
Issuer, client ID and resources use this build's defaults for each new login;
|
|
987
|
+
overrides apply to this login only and are not reused by the next login.`,
|
|
988
|
+
flags: [
|
|
989
|
+
`--issuer <url> PEP server for this login (default: ${DEFAULT_ISSUER})`,
|
|
990
|
+
`--client-id <id> Registered OAuth client (default: ${DEFAULT_CLIENT_ID})`,
|
|
991
|
+
`--resource <uri> Token audience; repeat for multiple resources (default: ${DEFAULT_RESOURCES.join(", ")})`
|
|
992
|
+
],
|
|
993
|
+
examples: ["pep auth login", "pep auth login --issuer http://localhost:3000"]
|
|
994
|
+
},
|
|
995
|
+
status: {
|
|
996
|
+
summary: "Show the current account and authorization status.",
|
|
997
|
+
usage: "pep auth status",
|
|
998
|
+
description: "Shows the issuer, client, account, granted scopes and access-token expiry.\nRequires a saved login and contacts PEP to retrieve account information.",
|
|
999
|
+
examples: ["pep auth status"]
|
|
1000
|
+
},
|
|
1001
|
+
token: {
|
|
1002
|
+
summary: "Print a current access token for scripts and API clients.",
|
|
1003
|
+
usage: "pep auth token",
|
|
1004
|
+
description: "Requires a saved login. Refreshes the token if needed and writes only the\naccess token to stdout, so another command can capture it.",
|
|
1005
|
+
examples: ["pep auth token", "$token = pep auth token # PowerShell"]
|
|
1006
|
+
},
|
|
1007
|
+
logout: {
|
|
1008
|
+
summary: "Sign out and remove local authorization.",
|
|
1009
|
+
usage: "pep auth logout",
|
|
1010
|
+
description: "Attempts to revoke the saved authorization on PEP, then removes local tokens\nand login configuration. Local data is removed even if remote revocation fails.",
|
|
1011
|
+
examples: ["pep auth logout"]
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
},
|
|
1015
|
+
skills: {
|
|
1016
|
+
summary: "Download and update PEP agent skills.",
|
|
1017
|
+
commands: {
|
|
1018
|
+
list: {
|
|
1019
|
+
summary: "List the skill repositories you have added.",
|
|
1020
|
+
usage: "pep skills list",
|
|
1021
|
+
description: "Requires a saved PEP login. Lists locally registered repositories.\nUse pep skills add to add a repository, or pep skills update to refresh it.",
|
|
1022
|
+
examples: ["pep skills list"]
|
|
1023
|
+
},
|
|
1024
|
+
add: {
|
|
1025
|
+
summary: "Add skills from a repository on PEP's GitLab.",
|
|
1026
|
+
usage: "pep skills add <repo> [flags]",
|
|
1027
|
+
description: `Accepts a full HTTPS repository URL or group/project[@ref]. URLs with
|
|
1028
|
+
/-/tree/<ref> are also supported. PEP fetches the repository with its own service
|
|
1029
|
+
account; no GitLab credential is needed locally. Other hosts are refused.
|
|
1030
|
+
Each directory containing SKILL.md is installed as a skill.
|
|
1031
|
+
Requires a saved PEP login.
|
|
1032
|
+
|
|
1033
|
+
Installs for your user account by default. Pass --project to install into the
|
|
1034
|
+
current directory instead, so the skills travel with the repository and everyone
|
|
1035
|
+
working on it gets the same set. The choice is remembered per repository, so
|
|
1036
|
+
pep skills update does not need the flag again.
|
|
1037
|
+
|
|
1038
|
+
Also re-installs skills you removed with pep skills remove. This is the only
|
|
1039
|
+
command that does; pep skills update leaves them removed.`,
|
|
1040
|
+
flags: [
|
|
1041
|
+
"--dir <path> Install only to this directory; skip links into Claude Code",
|
|
1042
|
+
`${PROJECT_FLAG}`
|
|
1043
|
+
],
|
|
1044
|
+
examples: [
|
|
1045
|
+
`pep skills add ${EXAMPLE_REPO}`,
|
|
1046
|
+
`pep skills add ${EXAMPLE_PATH}@main`,
|
|
1047
|
+
`pep skills add ${EXAMPLE_REPO} --project`,
|
|
1048
|
+
`pep skills add ${EXAMPLE_PATH} --dir "./my-skills"`
|
|
1049
|
+
]
|
|
1050
|
+
},
|
|
1051
|
+
update: {
|
|
1052
|
+
summary: "Update installed skill repositories and report changed skills.",
|
|
1053
|
+
usage: "pep skills update [<repo>...] [flags]",
|
|
1054
|
+
description: `Requires a saved PEP login. Downloads skills to:
|
|
1055
|
+
${defaultSkillsDirectory()}
|
|
1056
|
+
Also links them into ${claudeSkillsDirectory()} for Claude Code,
|
|
1057
|
+
or copies them if links are unavailable. Only skills managed by pep are updated
|
|
1058
|
+
or removed; files you added yourself are left alone.
|
|
1059
|
+
With no repository arguments, updates all added repositories. Named repositories
|
|
1060
|
+
must already have been added. Reports which individual skills changed.
|
|
1061
|
+
|
|
1062
|
+
Each repository is updated where it was installed, so the flags you used with
|
|
1063
|
+
pep skills add do not need repeating.
|
|
1064
|
+
|
|
1065
|
+
Skills you removed with pep skills remove stay removed: update refreshes what is
|
|
1066
|
+
installed and does not bring anything back. Run pep skills add to reinstate them.`,
|
|
1067
|
+
flags: [
|
|
1068
|
+
"--dir <path> Install only to this directory; skip links into Claude Code",
|
|
1069
|
+
`${PROJECT_FLAG}`
|
|
1070
|
+
],
|
|
1071
|
+
examples: [
|
|
1072
|
+
"pep skills update",
|
|
1073
|
+
`pep skills update ${EXAMPLE_PATH}`,
|
|
1074
|
+
'pep skills update --dir "./my-skills"'
|
|
1075
|
+
]
|
|
1076
|
+
},
|
|
1077
|
+
remove: {
|
|
1078
|
+
summary: "Remove the skills a repository installed.",
|
|
1079
|
+
usage: "pep skills remove <repo>",
|
|
1080
|
+
description: `Deletes the skills this repository installed, wherever they were installed,
|
|
1081
|
+
and stops tracking it. Takes no --dir or --project: the location comes from what
|
|
1082
|
+
pep recorded when it installed them.
|
|
1083
|
+
|
|
1084
|
+
Only files pep installed from this repository are deleted. A skill another added
|
|
1085
|
+
repository also provides is left in place, and so is anything you wrote yourself.
|
|
1086
|
+
|
|
1087
|
+
pep skills update will not bring the skills back. Run pep skills add to do that.`,
|
|
1088
|
+
examples: [
|
|
1089
|
+
`pep skills remove ${EXAMPLE_PATH}`,
|
|
1090
|
+
`pep skills remove ${EXAMPLE_REPO}`
|
|
1091
|
+
]
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
},
|
|
1095
|
+
docs: {
|
|
1096
|
+
summary: "Find and read PEP developer documentation.",
|
|
1097
|
+
commands: {
|
|
1098
|
+
list: {
|
|
1099
|
+
summary: "List the documents your account can read.",
|
|
1100
|
+
usage: "pep docs list [flags]",
|
|
1101
|
+
description: "Requires a saved PEP login. Prints each document's path and description.\nPass a path from this list to pep docs get to read its Markdown content.",
|
|
1102
|
+
flags: [`--docs-url <url> Documentation server; saved for later use (default: ${DEFAULT_DOCS_URL})`],
|
|
1103
|
+
examples: ["pep docs list", "pep docs list --docs-url http://localhost:3001"]
|
|
1104
|
+
},
|
|
1105
|
+
get: {
|
|
1106
|
+
summary: "Read a document as Markdown.",
|
|
1107
|
+
usage: "pep docs get <path> [flags]",
|
|
1108
|
+
description: "Requires a saved PEP login. Use a document path returned by pep docs list.\nWrites the document's Markdown to stdout, ready to pipe or save to a file.",
|
|
1109
|
+
flags: [`--docs-url <url> Documentation server; saved for later use (default: ${DEFAULT_DOCS_URL})`],
|
|
1110
|
+
examples: ['pep docs get "<path-from-pep-docs-list>"', 'pep docs get "<path-from-pep-docs-list>" > document.md']
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
},
|
|
1114
|
+
nexus: {
|
|
1115
|
+
summary: "Configure Maven repository access for the Newland Android SDK.",
|
|
1116
|
+
commands: {
|
|
1117
|
+
setup: {
|
|
1118
|
+
summary: "Save your organisation's Maven credentials for the Newland SDK.",
|
|
1119
|
+
usage: "pep nexus setup [flags]",
|
|
1120
|
+
description: `Requires a saved PEP login and Maven access in your organisation's contract.
|
|
1121
|
+
Saves ${USERNAME_VAR} and ${PASSWORD_VAR} to the Windows user
|
|
1122
|
+
environment or your macOS shell profile.
|
|
1123
|
+
|
|
1124
|
+
Safe to run again, on as many machines as you like: the credential is derived
|
|
1125
|
+
from your organisation's identity, so every run produces the same one. Losing it
|
|
1126
|
+
is not a problem \u2014 run this again.
|
|
1127
|
+
|
|
1128
|
+
One credential per organisation: colleagues who run this get the same username
|
|
1129
|
+
and password. It is a read-only credential for the Newland SDK repositories, not
|
|
1130
|
+
a personal account, so it identifies your company rather than you.
|
|
1131
|
+
|
|
1132
|
+
Neither value is printed. Pass --print-env to write the two export lines to
|
|
1133
|
+
stdout instead, for the current shell:
|
|
1134
|
+
eval "$(pep nexus setup --print-env)"
|
|
1135
|
+
|
|
1136
|
+
Saved values only reach new processes. After setup:
|
|
1137
|
+
- open a new terminal;
|
|
1138
|
+
- fully quit and reopen Android Studio / IntelliJ, whose built-in terminal
|
|
1139
|
+
inherits the environment it was started with;
|
|
1140
|
+
- stop the Gradle daemon, which outlives the IDE: gradlew --stop.
|
|
1141
|
+
A daemon started before setup keeps the old environment and fails to resolve the
|
|
1142
|
+
SDK even once everything else is correct.`,
|
|
1143
|
+
flags: ["--print-env Also write the export lines to stdout for the current shell"],
|
|
1144
|
+
examples: ["pep nexus setup", 'eval "$(pep nexus setup --print-env)"']
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
};
|
|
1149
|
+
function flagBlock(flags) {
|
|
1150
|
+
const split = flags.map((one) => {
|
|
1151
|
+
const gap = one.search(/ {2,}/);
|
|
1152
|
+
return gap === -1 ? [one, ""] : [one.slice(0, gap), one.slice(gap).trimStart()];
|
|
1153
|
+
});
|
|
1154
|
+
const width = Math.max(...split.map(([name]) => name.length)) + 3;
|
|
1155
|
+
return split.map(([name, text]) => ` ${text === "" ? name : name.padEnd(width) + text}`).join("\n");
|
|
1156
|
+
}
|
|
1157
|
+
var topLevel = {
|
|
1158
|
+
setup: {
|
|
1159
|
+
summary: "Sign in, install the integration guide and configure Maven in one go.",
|
|
1160
|
+
usage: "pep setup [<group>] [flags]",
|
|
1161
|
+
description: `Runs the three first-run steps in order, skipping what is already done:
|
|
1162
|
+
1. Sign in \u2014 skipped when you are already signed in (tokens refresh themselves).
|
|
1163
|
+
2. Install every skills repository in a group (default: ${DEFAULT_SKILLS_GROUP}).
|
|
1164
|
+
3. Configure Maven credentials \u2014 only Android development needs this.
|
|
1165
|
+
|
|
1166
|
+
Step 2 asks the platform which repositories that group holds right now, then
|
|
1167
|
+
installs each one. A repository that fails does not stop the others: the command
|
|
1168
|
+
reports how many went in, which ones did not, and exits non-zero.
|
|
1169
|
+
|
|
1170
|
+
Steps 2 and 3 are independent: if one fails the other still runs. Safe to run
|
|
1171
|
+
again \u2014 every step is idempotent.
|
|
1172
|
+
|
|
1173
|
+
Repositories you installed earlier that have since left the group are reported,
|
|
1174
|
+
never deleted. \`pep skills remove <repo>\` drops one for good.
|
|
1175
|
+
|
|
1176
|
+
The group is baked into this build and is not remembered between runs: pass one
|
|
1177
|
+
to install from a different group, just for this run.
|
|
1178
|
+
|
|
1179
|
+
--issuer, --client-id and --resource are passed to step 1, exactly as on
|
|
1180
|
+
\`pep auth login\`, and are not remembered either. If you are already signed in to
|
|
1181
|
+
a different deployment, setup signs in again rather than silently using the one
|
|
1182
|
+
you are signed in to. There is a single stored sign-in, so signing in elsewhere
|
|
1183
|
+
replaces it \u2014 run \`pep auth login\` to go back.`,
|
|
1184
|
+
flags: [
|
|
1185
|
+
"--skip-skills Skip the integration guide step",
|
|
1186
|
+
"--skip-nexus Skip the Maven credential step (not needed outside Android)",
|
|
1187
|
+
PROJECT_FLAG,
|
|
1188
|
+
"--dir <path> Install skills into this directory instead",
|
|
1189
|
+
`--issuer <url> PEP server for this run (default: ${DEFAULT_ISSUER})`,
|
|
1190
|
+
"--client-id <id> Registered OAuth client for this run",
|
|
1191
|
+
"--resource <uri> Token audience; repeat for multiple resources"
|
|
1192
|
+
],
|
|
1193
|
+
examples: [
|
|
1194
|
+
"pep setup",
|
|
1195
|
+
"pep setup --skip-nexus",
|
|
1196
|
+
`pep setup ${DEFAULT_SKILLS_GROUP} -p`,
|
|
1197
|
+
"pep setup --issuer https://pep-dev.example --client-id <that deployment's id>"
|
|
1198
|
+
]
|
|
1199
|
+
}
|
|
1200
|
+
};
|
|
1201
|
+
function renderCommand(command) {
|
|
1202
|
+
return `${command.summary}
|
|
1203
|
+
|
|
1204
|
+
${command.description}
|
|
1205
|
+
|
|
1206
|
+
USAGE
|
|
1207
|
+
${command.usage}
|
|
1208
|
+
|
|
1209
|
+
FLAGS
|
|
1210
|
+
${flagBlock([...command.flags ?? [], "-h, --help Show help for this command"])}
|
|
1211
|
+
|
|
1212
|
+
EXAMPLES
|
|
1213
|
+
${command.examples.map((example) => ` ${example}`).join("\n")}`;
|
|
1214
|
+
}
|
|
1215
|
+
function usage(version, path = []) {
|
|
1216
|
+
const [groupName, commandName] = path;
|
|
1217
|
+
if (!groupName) {
|
|
1218
|
+
return `PEP CLI ${version}
|
|
1219
|
+
Sign in to PEP, read developer documentation and configure development tools.
|
|
1220
|
+
|
|
1221
|
+
USAGE
|
|
1222
|
+
pep <command> <subcommand> [flags]
|
|
1223
|
+
|
|
1224
|
+
COMMANDS
|
|
1225
|
+
${Object.entries(topLevel).map(([name, command]) => ` ${name.padEnd(10)}${command.summary}`).join("\n")}
|
|
1226
|
+
${Object.entries(groups).map(([name, group2]) => ` ${name.padEnd(10)}${group2.summary}`).join("\n")}
|
|
1227
|
+
|
|
1228
|
+
FLAGS
|
|
1229
|
+
-h, --help Show help for a command
|
|
1230
|
+
-v, --version Show the CLI version
|
|
1231
|
+
|
|
1232
|
+
EXAMPLES
|
|
1233
|
+
pep setup
|
|
1234
|
+
pep docs list
|
|
1235
|
+
pep nexus setup --help
|
|
1236
|
+
|
|
1237
|
+
LEARN MORE
|
|
1238
|
+
Use pep <command> --help or pep <command> <subcommand> --help.
|
|
1239
|
+
You can also use pep help <command> <subcommand>.
|
|
1240
|
+
pep-cli is an alias for pep.`;
|
|
1241
|
+
}
|
|
1242
|
+
if (Object.hasOwn(topLevel, groupName)) {
|
|
1243
|
+
if (path.length > 2) {
|
|
1244
|
+
throw new Error(`Unknown ${groupName} argument: ${path.slice(1).join(" ")}. Run pep ${groupName} --help.`);
|
|
1245
|
+
}
|
|
1246
|
+
return renderCommand(topLevel[groupName]);
|
|
1247
|
+
}
|
|
1248
|
+
if (!Object.hasOwn(groups, groupName)) throw new Error(`Unknown command: ${groupName}. Run pep --help.`);
|
|
1249
|
+
const group = groups[groupName];
|
|
1250
|
+
if (!commandName) {
|
|
1251
|
+
return `${group.summary}
|
|
1252
|
+
|
|
1253
|
+
USAGE
|
|
1254
|
+
pep ${groupName} <command> [flags]
|
|
1255
|
+
|
|
1256
|
+
COMMANDS
|
|
1257
|
+
${Object.entries(group.commands).map(([name, command]) => ` ${name.padEnd(10)}${command.summary}`).join("\n")}
|
|
1258
|
+
|
|
1259
|
+
FLAGS
|
|
1260
|
+
-h, --help Show help for this command
|
|
1261
|
+
|
|
1262
|
+
LEARN MORE
|
|
1263
|
+
Use pep ${groupName} <command> --help for details and examples.`;
|
|
1264
|
+
}
|
|
1265
|
+
if (path.length > 2 || !Object.hasOwn(group.commands, commandName)) {
|
|
1266
|
+
throw new Error(`Unknown ${groupName} command: ${path.slice(1).join(" ")}. Run pep ${groupName} --help.`);
|
|
1267
|
+
}
|
|
1268
|
+
return renderCommand(group.commands[commandName]);
|
|
1269
|
+
}
|
|
1270
|
+
|
|
964
1271
|
// src/nexus-service.ts
|
|
965
1272
|
var CREDENTIAL_PATH = "/api/nexus/credential";
|
|
966
1273
|
function describeFailure2(status, code) {
|
|
@@ -974,13 +1281,16 @@ function describeFailure2(status, code) {
|
|
|
974
1281
|
return "This access token carries no `nexus-credentials:write` scope. Ask an operator to add it to this client's allowed_scopes in PEP, then run `pep auth login` again \u2014 existing tokens do not gain new scopes.";
|
|
975
1282
|
}
|
|
976
1283
|
if (status === 409) {
|
|
977
|
-
return "This PEP deployment
|
|
1284
|
+
return "This PEP deployment issues a random Maven password once and does not store it, so it cannot give you yours again. Newer PEP derives it and can always re-issue the same one \u2014 ask an operator to upgrade this deployment; until then, copy the credential from the machine that has it.";
|
|
978
1285
|
}
|
|
979
1286
|
if (status === 503) {
|
|
980
1287
|
return "PEP could not reach Nexus (not configured, or upstream is down). This is a platform-side problem \u2014 retrying will not help. Report it to a PEP operator.";
|
|
981
1288
|
}
|
|
982
1289
|
return `PEP answered ${status} when asking for a Maven credential.`;
|
|
983
1290
|
}
|
|
1291
|
+
function scrub(text, password) {
|
|
1292
|
+
return password === "" ? text : text.split(password).join("<redacted>");
|
|
1293
|
+
}
|
|
984
1294
|
async function errorCodeOf(response) {
|
|
985
1295
|
try {
|
|
986
1296
|
const body = await response.json();
|
|
@@ -1016,7 +1326,11 @@ async function setupNexusCredential(dependencies) {
|
|
|
1016
1326
|
const persisted = await persistMavenEnv(credential, target, dependencies.now).catch(
|
|
1017
1327
|
(error) => ({
|
|
1018
1328
|
status: "manual",
|
|
1019
|
-
reason
|
|
1329
|
+
// ⚠ **兜底要洗一遍密码。** `reason` 会被 `nexusReport` 原样打到 stderr,而异常消息里
|
|
1330
|
+
// 带密码是有过先例的:`execFile` 被拒时的 message 含**完整命令行**,`setx` 的密码就是
|
|
1331
|
+
// 它的一个参数。那一处已经在 `maven-env.ts` 里就地接住了,这里是第二道 —— 往后谁在
|
|
1332
|
+
// 那条路上新加一个 throw,不必先想起这条注释也不会漏出去。
|
|
1333
|
+
reason: scrub(error.message, credential.password),
|
|
1020
1334
|
...target.kind === "profile" ? { path: target.path } : {}
|
|
1021
1335
|
})
|
|
1022
1336
|
);
|
|
@@ -1105,6 +1419,7 @@ function readTar(bytes) {
|
|
|
1105
1419
|
|
|
1106
1420
|
// src/skills-service.ts
|
|
1107
1421
|
var ARCHIVE_PATH = "/api/skills/archive";
|
|
1422
|
+
var GROUP_PATH = "/api/skills/group";
|
|
1108
1423
|
var MAX_ARCHIVE_BYTES = 64 * 1024 * 1024;
|
|
1109
1424
|
var MAX_UNPACKED_BYTES = 256 * 1024 * 1024;
|
|
1110
1425
|
var MAX_ENTRIES = 1e4;
|
|
@@ -1200,6 +1515,69 @@ function describeFailure3(status) {
|
|
|
1200
1515
|
}
|
|
1201
1516
|
return `PEP answered ${status}.`;
|
|
1202
1517
|
}
|
|
1518
|
+
function describeGroupFailure(status) {
|
|
1519
|
+
if (status === 400)
|
|
1520
|
+
return "PEP did not accept that group path. It must be a <group> or <group>/<subgroup> path on the platform's GitLab host, or an https URL to that group.";
|
|
1521
|
+
if (status === 404) {
|
|
1522
|
+
return "No such group on the platform's GitLab (or this PEP deployment has no /api/skills/group endpoint at all). Check the path, then ask an operator whether this deployment serves skills.";
|
|
1523
|
+
}
|
|
1524
|
+
if (status === 503)
|
|
1525
|
+
return "PEP could not reach GitLab to list that group. Retry shortly, or ask an operator whether this deployment serves skills.";
|
|
1526
|
+
return describeFailure3(status);
|
|
1527
|
+
}
|
|
1528
|
+
var MAX_GROUP_LIST_BYTES = 4 * 1024 * 1024;
|
|
1529
|
+
function parseGroupList(body) {
|
|
1530
|
+
let payload;
|
|
1531
|
+
try {
|
|
1532
|
+
payload = JSON.parse(body);
|
|
1533
|
+
} catch {
|
|
1534
|
+
throw new Error("PEP returned a group listing this CLI cannot read (not JSON).");
|
|
1535
|
+
}
|
|
1536
|
+
const items = payload?.data?.items;
|
|
1537
|
+
if (!Array.isArray(items)) {
|
|
1538
|
+
throw new Error("PEP returned a group listing this CLI cannot read (no data.items array).");
|
|
1539
|
+
}
|
|
1540
|
+
const paths = [];
|
|
1541
|
+
for (const item of items) {
|
|
1542
|
+
const path = item?.path;
|
|
1543
|
+
if (typeof path !== "string" || path.trim() === "") {
|
|
1544
|
+
throw new Error("PEP returned a group listing this CLI cannot read (an entry has no path).");
|
|
1545
|
+
}
|
|
1546
|
+
paths.push(path.trim());
|
|
1547
|
+
}
|
|
1548
|
+
return [...new Set(paths)].sort();
|
|
1549
|
+
}
|
|
1550
|
+
async function listGroupRepos(dependencies) {
|
|
1551
|
+
const doFetch = dependencies.fetch ?? globalThis.fetch;
|
|
1552
|
+
const url = new URL(`${dependencies.issuer.replace(/\/+$/, "")}${GROUP_PATH}`);
|
|
1553
|
+
url.searchParams.set("path", dependencies.group);
|
|
1554
|
+
const response = await doFetch(url, {
|
|
1555
|
+
headers: { Authorization: `Bearer ${dependencies.accessToken}` }
|
|
1556
|
+
});
|
|
1557
|
+
if (!response.ok) {
|
|
1558
|
+
await response.body?.cancel();
|
|
1559
|
+
throw new Error(describeGroupFailure(response.status));
|
|
1560
|
+
}
|
|
1561
|
+
const raw = await readCapped(response, dependencies.maxListBytes ?? MAX_GROUP_LIST_BYTES);
|
|
1562
|
+
return parseGroupList(new TextDecoder().decode(raw));
|
|
1563
|
+
}
|
|
1564
|
+
function groupMemberPath(ledgerKey, group) {
|
|
1565
|
+
let path = ledgerKey.trim();
|
|
1566
|
+
if (/^https?:\/\//i.test(path)) {
|
|
1567
|
+
try {
|
|
1568
|
+
path = new URL(path).pathname;
|
|
1569
|
+
} catch {
|
|
1570
|
+
return void 0;
|
|
1571
|
+
}
|
|
1572
|
+
}
|
|
1573
|
+
path = (path.split("@")[0] ?? "").replace(/\.git$/i, "");
|
|
1574
|
+
const segments = path.split("/").filter((one) => one !== "");
|
|
1575
|
+
const groupSegments = group.split("/").filter((one) => one !== "");
|
|
1576
|
+
if (groupSegments.length === 0 || segments.length <= groupSegments.length) return void 0;
|
|
1577
|
+
const lower = segments.map((one) => one.toLowerCase());
|
|
1578
|
+
if (!groupSegments.every((one, index) => one.toLowerCase() === lower[index])) return void 0;
|
|
1579
|
+
return lower.join("/");
|
|
1580
|
+
}
|
|
1203
1581
|
async function physicalDir(path) {
|
|
1204
1582
|
const absolute = (0, import_node_path4.resolve)(path);
|
|
1205
1583
|
try {
|
|
@@ -1425,98 +1803,129 @@ async function updateSkills(dependencies) {
|
|
|
1425
1803
|
};
|
|
1426
1804
|
}
|
|
1427
1805
|
|
|
1428
|
-
// src/
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
(
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1806
|
+
// src/setup-flow.ts
|
|
1807
|
+
function accountOf(user) {
|
|
1808
|
+
return user.email ?? user.name ?? user.sub;
|
|
1809
|
+
}
|
|
1810
|
+
function messageOf(error) {
|
|
1811
|
+
return error instanceof Error ? error.message : String(error);
|
|
1812
|
+
}
|
|
1813
|
+
function sameIssuer(a, b) {
|
|
1814
|
+
const trim = (one) => one.trim().replace(/\/+$/, "").toLowerCase();
|
|
1815
|
+
return trim(a) === trim(b);
|
|
1816
|
+
}
|
|
1817
|
+
function driftedFromGroup(installedSources, groupRepos, group) {
|
|
1818
|
+
const present = new Set(groupRepos.map((one) => one.trim().toLowerCase()));
|
|
1819
|
+
return installedSources.filter((key) => {
|
|
1820
|
+
const member = groupMemberPath(key, group);
|
|
1821
|
+
return member !== void 0 && !present.has(member);
|
|
1822
|
+
}).sort();
|
|
1823
|
+
}
|
|
1824
|
+
async function installGroup(dependencies, remote) {
|
|
1825
|
+
const group = dependencies.group;
|
|
1826
|
+
let repos;
|
|
1827
|
+
try {
|
|
1828
|
+
repos = await dependencies.listGroupRepos(remote);
|
|
1829
|
+
} catch (error) {
|
|
1830
|
+
const message = messageOf(error);
|
|
1831
|
+
dependencies.onProgress(` \u2717 ${message}`);
|
|
1832
|
+
return { status: "failed", group, message };
|
|
1833
|
+
}
|
|
1834
|
+
if (repos.length === 0) {
|
|
1835
|
+
dependencies.onProgress(` ${group} lists no repositories \u2014 nothing to install.`);
|
|
1836
|
+
}
|
|
1837
|
+
const installed = [];
|
|
1838
|
+
const failed = [];
|
|
1839
|
+
for (const [index, source] of repos.entries()) {
|
|
1840
|
+
dependencies.onProgress(` [${index + 1}/${repos.length}] ${source}`);
|
|
1841
|
+
try {
|
|
1842
|
+
installed.push({ source, ...await dependencies.installSkills(remote, source) });
|
|
1843
|
+
} catch (error) {
|
|
1844
|
+
const message = messageOf(error);
|
|
1845
|
+
failed.push({ source, message });
|
|
1846
|
+
dependencies.onProgress(` \u2717 ${source}: ${message}`);
|
|
1847
|
+
}
|
|
1848
|
+
}
|
|
1849
|
+
return {
|
|
1850
|
+
status: "installed",
|
|
1851
|
+
group,
|
|
1852
|
+
installed,
|
|
1853
|
+
failed,
|
|
1854
|
+
drifted: driftedFromGroup(dependencies.installedSources, repos, group)
|
|
1855
|
+
};
|
|
1856
|
+
}
|
|
1857
|
+
async function runSetup(dependencies) {
|
|
1858
|
+
dependencies.onProgress("\u2460 Signing in to PEP\u2026");
|
|
1859
|
+
let login;
|
|
1860
|
+
let signedIn;
|
|
1861
|
+
try {
|
|
1862
|
+
const current = await dependencies.auth.status();
|
|
1863
|
+
if (dependencies.requestedIssuer === void 0 || sameIssuer(current.authorization.issuer, dependencies.requestedIssuer)) {
|
|
1864
|
+
signedIn = current;
|
|
1865
|
+
} else {
|
|
1866
|
+
dependencies.onProgress(
|
|
1867
|
+
` Signed in to ${current.authorization.issuer}, but --issuer names ${dependencies.requestedIssuer} \u2014 signing in again.`
|
|
1868
|
+
);
|
|
1869
|
+
}
|
|
1870
|
+
} catch {
|
|
1871
|
+
}
|
|
1872
|
+
if (signedIn !== void 0) {
|
|
1873
|
+
login = { alreadySignedIn: true, account: accountOf(signedIn.user) };
|
|
1874
|
+
dependencies.onProgress(` Already signed in as ${login.account} \u2014 skipped.`);
|
|
1875
|
+
if (dependencies.loginOptionsGiven) {
|
|
1876
|
+
dependencies.onProgress(
|
|
1877
|
+
" (reused the stored sign-in; --client-id / --resource apply only to a fresh one)"
|
|
1878
|
+
);
|
|
1879
|
+
}
|
|
1880
|
+
} else {
|
|
1881
|
+
const config = await dependencies.loginConfig();
|
|
1882
|
+
dependencies.onProgress(" Opening your browser to sign in\u2026");
|
|
1883
|
+
await dependencies.auth.login(config, dependencies.onAuthorizationUrl);
|
|
1884
|
+
const account = await dependencies.auth.status().then((it) => accountOf(it.user)).catch(() => "(account name unavailable)");
|
|
1885
|
+
login = { alreadySignedIn: false, account };
|
|
1886
|
+
dependencies.onProgress(` Signed in as ${account}.`);
|
|
1887
|
+
}
|
|
1888
|
+
const authorization = signedIn?.authorization ?? await dependencies.auth.currentAuthorization();
|
|
1889
|
+
const remote = { issuer: authorization.issuer, accessToken: authorization.accessToken };
|
|
1890
|
+
let skills;
|
|
1891
|
+
if (dependencies.skipSkills) {
|
|
1892
|
+
skills = { status: "skipped", reason: "--skip-skills" };
|
|
1893
|
+
dependencies.onProgress("\u2461 Installing skills\u2026 skipped (--skip-skills).");
|
|
1894
|
+
} else {
|
|
1895
|
+
dependencies.onProgress(`\u2461 Listing skills repositories in ${dependencies.group}\u2026`);
|
|
1896
|
+
skills = await installGroup(dependencies, remote);
|
|
1897
|
+
}
|
|
1898
|
+
let nexus;
|
|
1899
|
+
if (dependencies.skipNexus) {
|
|
1900
|
+
nexus = { status: "skipped", reason: "--skip-nexus" };
|
|
1901
|
+
dependencies.onProgress("\u2462 Maven credentials\u2026 skipped (--skip-nexus).");
|
|
1902
|
+
} else {
|
|
1903
|
+
dependencies.onProgress("\u2462 Setting up Maven credentials\u2026");
|
|
1904
|
+
try {
|
|
1905
|
+
nexus = { status: "done", result: await dependencies.setupNexus(remote) };
|
|
1906
|
+
} catch (error) {
|
|
1907
|
+
nexus = { status: "failed", message: messageOf(error) };
|
|
1908
|
+
dependencies.onProgress(` \u2717 ${nexus.message}`);
|
|
1909
|
+
}
|
|
1910
|
+
}
|
|
1911
|
+
return { login, skills, nexus };
|
|
1912
|
+
}
|
|
1913
|
+
function setupFailureSummary(report) {
|
|
1914
|
+
const failed = [];
|
|
1915
|
+
if (report.skills.status === "failed") {
|
|
1916
|
+
failed.push("skills (could not list the group)");
|
|
1917
|
+
} else if (report.skills.status === "installed" && report.skills.failed.length > 0) {
|
|
1918
|
+
const total = report.skills.installed.length + report.skills.failed.length;
|
|
1919
|
+
failed.push(`skills (${report.skills.failed.length} of ${total} repositories)`);
|
|
1920
|
+
}
|
|
1921
|
+
if (report.nexus.status === "failed") failed.push("Maven credentials");
|
|
1922
|
+
if (failed.length === 0) return void 0;
|
|
1923
|
+
return `Setup finished with problems: ${failed.join(", ")} did not complete.`;
|
|
1519
1924
|
}
|
|
1925
|
+
|
|
1926
|
+
// src/cli.ts
|
|
1927
|
+
var VERSION = false ? "0.0.0-dev" : "0.9.0-rc.2";
|
|
1928
|
+
var SKILLS_ADD_DEPRECATION = "\u26A0 `pep skills add` will be removed in a future release.";
|
|
1520
1929
|
function reportUpdate(result) {
|
|
1521
1930
|
const conflicts = () => {
|
|
1522
1931
|
for (const { skill, owner } of result.conflicts) {
|
|
@@ -1595,6 +2004,19 @@ async function loginConfig(args, store = fileConfigStore()) {
|
|
|
1595
2004
|
...saved?.docsUrl !== void 0 ? { docsUrl: saved.docsUrl } : {}
|
|
1596
2005
|
};
|
|
1597
2006
|
}
|
|
2007
|
+
function takeLoginOptions(args) {
|
|
2008
|
+
const out = [];
|
|
2009
|
+
const issuer = option(args, "--issuer");
|
|
2010
|
+
if (issuer !== void 0) out.push("--issuer", normalizeIssuer(issuer));
|
|
2011
|
+
const clientId = option(args, "--client-id");
|
|
2012
|
+
if (clientId !== void 0) out.push("--client-id", clientId);
|
|
2013
|
+
for (const resource of repeatedOption(args, "--resource")) out.push("--resource", resource);
|
|
2014
|
+
return out;
|
|
2015
|
+
}
|
|
2016
|
+
function issuerOf(loginOptions) {
|
|
2017
|
+
const at = loginOptions.indexOf("--issuer");
|
|
2018
|
+
return at === -1 ? void 0 : loginOptions[at + 1];
|
|
2019
|
+
}
|
|
1598
2020
|
function parseSkillsArgs(command, args, cwd) {
|
|
1599
2021
|
const local = command === "list" || command === "remove";
|
|
1600
2022
|
const explicitDirectory = local ? void 0 : option(args, "--dir");
|
|
@@ -1604,7 +2026,7 @@ function parseSkillsArgs(command, args, cwd) {
|
|
|
1604
2026
|
}
|
|
1605
2027
|
const unknownOption = args.find((one) => one.startsWith("-"));
|
|
1606
2028
|
if (unknownOption) throw new Error(`Unknown option: ${unknownOption}`);
|
|
1607
|
-
const takesOne = command === "add" || command === "remove";
|
|
2029
|
+
const takesOne = command === "add" || command === "remove" || command === "setup";
|
|
1608
2030
|
const requested = takesOne ? args.shift() : void 0;
|
|
1609
2031
|
if (command === "add" && !requested) {
|
|
1610
2032
|
throw new Error("pep skills add needs a repository: a full https URL, or <group>/<project>.");
|
|
@@ -1642,6 +2064,8 @@ function nexusReport(result, printEnv) {
|
|
|
1642
2064
|
`Nothing was written${persisted.path ? ` to ${persisted.path}` : ""}: ${persisted.reason}`
|
|
1643
2065
|
);
|
|
1644
2066
|
stderr.push("Run this again to get the same credential \u2014 it is derived, not stored.");
|
|
2067
|
+
stderr.push(`Nexus user: ${result.username}`);
|
|
2068
|
+
return { stdout, stderr, failure: "Maven credentials could not be fully saved." };
|
|
1645
2069
|
} else {
|
|
1646
2070
|
stderr.push(
|
|
1647
2071
|
persisted.status === "profile-replaced" ? `Replaced the pep-cli block in ${persisted.path}` : `Appended a pep-cli block to ${persisted.path}`
|
|
@@ -1654,10 +2078,58 @@ function nexusReport(result, printEnv) {
|
|
|
1654
2078
|
stderr.push(`Nexus user: ${result.username}`);
|
|
1655
2079
|
return { stdout, stderr };
|
|
1656
2080
|
}
|
|
2081
|
+
function createCliServices() {
|
|
2082
|
+
const configStore = fileConfigStore(configPath());
|
|
2083
|
+
return {
|
|
2084
|
+
configStore,
|
|
2085
|
+
auth: createAuthService({
|
|
2086
|
+
configStore,
|
|
2087
|
+
credentialStore: systemCredentialStore(),
|
|
2088
|
+
oauth: createOAuthClient()
|
|
2089
|
+
})
|
|
2090
|
+
};
|
|
2091
|
+
}
|
|
2092
|
+
function reportSetup(report) {
|
|
2093
|
+
console.log("");
|
|
2094
|
+
const skills = report.skills;
|
|
2095
|
+
if (skills.status === "skipped") {
|
|
2096
|
+
console.error(`Skills: ${skills.reason}`);
|
|
2097
|
+
} else if (skills.status === "failed") {
|
|
2098
|
+
console.error(`Skills: could not list ${skills.group} \u2014 ${skills.message}`);
|
|
2099
|
+
} else {
|
|
2100
|
+
const total = skills.installed.length + skills.failed.length;
|
|
2101
|
+
for (const directory of [...new Set(skills.installed.map((one) => one.directory))].sort()) {
|
|
2102
|
+
console.log(`Next: read ${directory}/<name>/SKILL.md`);
|
|
2103
|
+
}
|
|
2104
|
+
console.error(
|
|
2105
|
+
total === 0 ? `Skills: ${skills.group} lists no repositories \u2014 nothing was installed.` : `Skills: installed ${skills.installed.length} of ${total} repositories in ${skills.group}.`
|
|
2106
|
+
);
|
|
2107
|
+
for (const { source, message } of skills.failed) {
|
|
2108
|
+
console.error(` \u2717 ${source}: ${message}`);
|
|
2109
|
+
}
|
|
2110
|
+
if (skills.drifted.length > 0) {
|
|
2111
|
+
console.error(`
|
|
2112
|
+
\u26A0 Still on your list, but no longer in ${skills.group}:`);
|
|
2113
|
+
for (const source of skills.drifted) console.error(` ${source}`);
|
|
2114
|
+
console.error(" They were left in place \u2014 nothing was deleted. `pep skills update` will");
|
|
2115
|
+
console.error(" report 404 for each of them (which reads exactly like a typo, but is not).");
|
|
2116
|
+
console.error(" `pep skills remove <repo>` drops one for good.");
|
|
2117
|
+
}
|
|
2118
|
+
}
|
|
2119
|
+
if (report.nexus.status === "skipped") {
|
|
2120
|
+
console.error(`Maven credentials: skipped (${report.nexus.reason}).`);
|
|
2121
|
+
}
|
|
2122
|
+
}
|
|
1657
2123
|
async function main() {
|
|
1658
2124
|
const args = process.argv.slice(2);
|
|
2125
|
+
if (args[0] === "help") {
|
|
2126
|
+
console.log(usage(VERSION, args.slice(1).filter((arg) => arg !== "--help" && arg !== "-h")));
|
|
2127
|
+
return;
|
|
2128
|
+
}
|
|
1659
2129
|
if (args.length === 0 || args.includes("--help") || args.includes("-h")) {
|
|
1660
|
-
|
|
2130
|
+
const path = args.filter((arg) => arg !== "--help" && arg !== "-h").slice(0, 2);
|
|
2131
|
+
const flagIndex = path.findIndex((arg) => arg.startsWith("-"));
|
|
2132
|
+
console.log(usage(VERSION, flagIndex === -1 ? path : path.slice(0, flagIndex)));
|
|
1661
2133
|
return;
|
|
1662
2134
|
}
|
|
1663
2135
|
if (args[0] === "--version" || args[0] === "-v") {
|
|
@@ -1665,22 +2137,76 @@ async function main() {
|
|
|
1665
2137
|
return;
|
|
1666
2138
|
}
|
|
1667
2139
|
const group = args.shift();
|
|
2140
|
+
if (group === "setup") {
|
|
2141
|
+
const skipNexus = flag(args, "--skip-nexus");
|
|
2142
|
+
const skipSkills = flag(args, "--skip-skills");
|
|
2143
|
+
const loginOptions = takeLoginOptions(args);
|
|
2144
|
+
const { requested, chosenTarget } = parseSkillsArgs("setup", args);
|
|
2145
|
+
const skillsGroup = requested ?? DEFAULT_SKILLS_GROUP;
|
|
2146
|
+
const { configStore: configStore2, auth: auth2 } = createCliServices();
|
|
2147
|
+
const stateStore = fileSkillsStateStore();
|
|
2148
|
+
const state = await stateStore.read();
|
|
2149
|
+
const report = await runSetup({
|
|
2150
|
+
auth: auth2,
|
|
2151
|
+
// ⚠ 传 `loginOptions` 而不是 `args`:后者到这里**恒为空**(`parseSkillsArgs` 要么抛、
|
|
2152
|
+
// 要么把剩下的吃干净),传它等于永远只用构建期默认值。
|
|
2153
|
+
loginConfig: () => loginConfig([...loginOptions], configStore2),
|
|
2154
|
+
...issuerOf(loginOptions) !== void 0 ? { requestedIssuer: issuerOf(loginOptions) } : {},
|
|
2155
|
+
loginOptionsGiven: loginOptions.length > 0,
|
|
2156
|
+
group: skillsGroup,
|
|
2157
|
+
skipSkills,
|
|
2158
|
+
listGroupRepos: (remote) => listGroupRepos({ ...remote, group: skillsGroup }),
|
|
2159
|
+
installSkills: async (remote, source) => {
|
|
2160
|
+
const target = targetForSource(chosenTarget, state?.packages[source]);
|
|
2161
|
+
const result = await updateSkills({
|
|
2162
|
+
...remote,
|
|
2163
|
+
source,
|
|
2164
|
+
directory: target.directory,
|
|
2165
|
+
...target.linkInto !== void 0 ? { linkInto: target.linkInto } : {},
|
|
2166
|
+
// `add` 那一档的语义:盘上被删掉的那些由它补回来(`update` 不管)。
|
|
2167
|
+
restoreMissing: true,
|
|
2168
|
+
stateStore
|
|
2169
|
+
});
|
|
2170
|
+
return { result, directory: target.directory };
|
|
2171
|
+
},
|
|
2172
|
+
// 漂移比对的另一半。取**这一趟开始前**的账 —— 装完再读的话,本轮刚装上的那些也在
|
|
2173
|
+
// 里头,而它们当然都还在群里,比出来一样,只是白读一遍。
|
|
2174
|
+
installedSources: Object.keys(state?.packages ?? {}),
|
|
2175
|
+
skipNexus,
|
|
2176
|
+
setupNexus: (remote) => setupNexusCredential(remote),
|
|
2177
|
+
onProgress: (line) => console.error(line),
|
|
2178
|
+
onAuthorizationUrl: (url) => console.error(`If the browser does not open, visit:
|
|
2179
|
+
${url}`)
|
|
2180
|
+
});
|
|
2181
|
+
if (report.skills.status === "installed") {
|
|
2182
|
+
for (const one of report.skills.installed) reportUpdate(one.result);
|
|
2183
|
+
}
|
|
2184
|
+
if (report.nexus.status === "done") {
|
|
2185
|
+
const nexus = nexusReport(report.nexus.result, false);
|
|
2186
|
+
for (const line of nexus.stdout) console.log(line);
|
|
2187
|
+
for (const line of nexus.stderr) console.error(line);
|
|
2188
|
+
if (nexus.failure) report.nexus = { status: "failed", message: nexus.failure };
|
|
2189
|
+
}
|
|
2190
|
+
reportSetup(report);
|
|
2191
|
+
const failure = setupFailureSummary(report);
|
|
2192
|
+
if (failure) throw new Error(failure);
|
|
2193
|
+
return;
|
|
2194
|
+
}
|
|
1668
2195
|
if (group !== "auth" && group !== "skills" && group !== "docs" && group !== "nexus")
|
|
1669
2196
|
throw new Error(`Unknown command.
|
|
1670
2197
|
|
|
1671
|
-
${usage()}`);
|
|
2198
|
+
${usage(VERSION)}`);
|
|
1672
2199
|
const command = args.shift();
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
});
|
|
2200
|
+
if (!command) {
|
|
2201
|
+
console.log(usage(VERSION, [group]));
|
|
2202
|
+
return;
|
|
2203
|
+
}
|
|
2204
|
+
const { configStore, auth } = createCliServices();
|
|
1679
2205
|
if (group === "docs") {
|
|
1680
2206
|
if (command !== "list" && command !== "get") {
|
|
1681
2207
|
throw new Error(`Unknown docs command.
|
|
1682
2208
|
|
|
1683
|
-
${usage()}`);
|
|
2209
|
+
${usage(VERSION, ["docs"])}`);
|
|
1684
2210
|
}
|
|
1685
2211
|
const explicitDocsUrl = option(args, "--docs-url");
|
|
1686
2212
|
const path = command === "get" ? args.shift() : void 0;
|
|
@@ -1708,9 +2234,11 @@ ${usage()}`);
|
|
|
1708
2234
|
return;
|
|
1709
2235
|
}
|
|
1710
2236
|
if (group === "nexus") {
|
|
1711
|
-
if (command !== "setup")
|
|
2237
|
+
if (command !== "setup") {
|
|
2238
|
+
throw new Error(`Unknown nexus command.
|
|
1712
2239
|
|
|
1713
|
-
${usage()}`);
|
|
2240
|
+
${usage(VERSION, ["nexus"])}`);
|
|
2241
|
+
}
|
|
1714
2242
|
const printEnv = flag(args, "--print-env");
|
|
1715
2243
|
if (args.length > 0) throw new Error(`Unknown option: ${args[0]}`);
|
|
1716
2244
|
const authorization = await auth.currentAuthorization();
|
|
@@ -1721,13 +2249,14 @@ ${usage()}`);
|
|
|
1721
2249
|
const report = nexusReport(result, printEnv);
|
|
1722
2250
|
for (const line of report.stdout) console.log(line);
|
|
1723
2251
|
for (const line of report.stderr) console.error(line);
|
|
2252
|
+
if (report.failure) throw new Error(report.failure);
|
|
1724
2253
|
return;
|
|
1725
2254
|
}
|
|
1726
2255
|
if (group === "skills") {
|
|
1727
2256
|
if (command !== "update" && command !== "add" && command !== "list" && command !== "remove") {
|
|
1728
2257
|
throw new Error(`Unknown skills command.
|
|
1729
2258
|
|
|
1730
|
-
${usage()}`);
|
|
2259
|
+
${usage(VERSION, ["skills"])}`);
|
|
1731
2260
|
}
|
|
1732
2261
|
const { requested, named, chosenTarget } = parseSkillsArgs(command, args);
|
|
1733
2262
|
const stateStore = fileSkillsStateStore();
|
|
@@ -1784,6 +2313,7 @@ ${usage()}`);
|
|
|
1784
2313
|
})
|
|
1785
2314
|
);
|
|
1786
2315
|
if (command === "add") {
|
|
2316
|
+
console.error(SKILLS_ADD_DEPRECATION);
|
|
1787
2317
|
await run2(
|
|
1788
2318
|
requested,
|
|
1789
2319
|
targetForSource(chosenTarget, state?.packages[requested]),
|
|
@@ -1862,7 +2392,7 @@ ${url}`)
|
|
|
1862
2392
|
}
|
|
1863
2393
|
throw new Error(`Unknown auth command.
|
|
1864
2394
|
|
|
1865
|
-
${usage()}`);
|
|
2395
|
+
${usage(VERSION, ["auth"])}`);
|
|
1866
2396
|
}
|
|
1867
2397
|
|
|
1868
2398
|
// src/main.ts
|