@osfactory/har 0.63.0 → 0.64.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/index.js +218 -11
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -29230,9 +29230,6 @@ function resolvePortalTargetsForRepo(options) {
|
|
|
29230
29230
|
const aliases = getRepoPortalTargetAliases(options.repoPath);
|
|
29231
29231
|
const attached = aliases.map((alias) => getPortalTargetRecord(alias)).filter((entry) => entry !== null).map(recordToPortalTarget);
|
|
29232
29232
|
if (attached.length > 0) return { source: "repo", targets: attached };
|
|
29233
|
-
if (store.targets.length === 1) {
|
|
29234
|
-
return { source: "single", targets: [recordToPortalTarget(store.targets[0])] };
|
|
29235
|
-
}
|
|
29236
29233
|
return null;
|
|
29237
29234
|
}
|
|
29238
29235
|
if (store.defaultTarget) {
|
|
@@ -74842,7 +74839,30 @@ function openBrowser(url) {
|
|
|
74842
74839
|
} catch {
|
|
74843
74840
|
}
|
|
74844
74841
|
}
|
|
74845
|
-
|
|
74842
|
+
var REPOS_QUERY_BUDGET = 1800;
|
|
74843
|
+
function encodeCliReposQuery(repos) {
|
|
74844
|
+
const unique = [...new Set(repos.filter((repo) => repo.trim().length > 0))];
|
|
74845
|
+
if (unique.length === 0) return null;
|
|
74846
|
+
const encoded = encodeURIComponent(JSON.stringify(unique));
|
|
74847
|
+
if (encoded.length > REPOS_QUERY_BUDGET) return null;
|
|
74848
|
+
return encoded;
|
|
74849
|
+
}
|
|
74850
|
+
function parseCliTargetsParam(raw) {
|
|
74851
|
+
if (!raw) return null;
|
|
74852
|
+
try {
|
|
74853
|
+
const parsed = JSON.parse(Buffer.from(raw, "base64url").toString("utf8"));
|
|
74854
|
+
if (!Array.isArray(parsed.targets)) return null;
|
|
74855
|
+
const targets = parsed.targets.filter(
|
|
74856
|
+
(entry) => typeof entry?.workspaceId === "string" && typeof entry?.token === "string" && Array.isArray(entry.repos)
|
|
74857
|
+
);
|
|
74858
|
+
return targets.length > 0 ? targets : null;
|
|
74859
|
+
} catch {
|
|
74860
|
+
return null;
|
|
74861
|
+
}
|
|
74862
|
+
}
|
|
74863
|
+
async function loginViaBrowser(portalUrl, openUrlOrOptions = openBrowser) {
|
|
74864
|
+
const options = typeof openUrlOrOptions === "function" ? { openUrl: openUrlOrOptions } : openUrlOrOptions;
|
|
74865
|
+
const openUrl = options.openUrl ?? openBrowser;
|
|
74846
74866
|
const state = (0, import_crypto.randomUUID)();
|
|
74847
74867
|
return new Promise((resolve53, reject) => {
|
|
74848
74868
|
let timer;
|
|
@@ -74871,6 +74891,7 @@ async function loginViaBrowser(portalUrl, openUrl = openBrowser) {
|
|
|
74871
74891
|
res.writeHead(200, { "Content-Type": "text/html" });
|
|
74872
74892
|
res.end("<h1>Logged in</h1><p>You can close this tab and return to the terminal.</p>");
|
|
74873
74893
|
cleanup();
|
|
74894
|
+
const targets = parseCliTargetsParam(requestUrl.searchParams.get("targets"));
|
|
74874
74895
|
resolve53({
|
|
74875
74896
|
portalUrl,
|
|
74876
74897
|
token,
|
|
@@ -74881,7 +74902,8 @@ async function loginViaBrowser(portalUrl, openUrl = openBrowser) {
|
|
|
74881
74902
|
email: email2,
|
|
74882
74903
|
refreshToken,
|
|
74883
74904
|
expiresAt,
|
|
74884
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
74905
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
74906
|
+
...targets ? { targets } : {}
|
|
74885
74907
|
});
|
|
74886
74908
|
});
|
|
74887
74909
|
const cleanup = () => {
|
|
@@ -74895,7 +74917,8 @@ async function loginViaBrowser(portalUrl, openUrl = openBrowser) {
|
|
|
74895
74917
|
server.listen(0, "127.0.0.1", () => {
|
|
74896
74918
|
const { port } = server.address();
|
|
74897
74919
|
const callback = `http://127.0.0.1:${port}${CALLBACK_PATH}`;
|
|
74898
|
-
const
|
|
74920
|
+
const reposQuery = encodeCliReposQuery(options.repos ?? []);
|
|
74921
|
+
const loginUrl = `${portalUrl}/cli/login?callback=${encodeURIComponent(callback)}&state=${state}${reposQuery ? `&repos=${reposQuery}` : ""}`;
|
|
74899
74922
|
openUrl(loginUrl);
|
|
74900
74923
|
process.stderr.write(`Opening ${loginUrl}
|
|
74901
74924
|
If your browser did not open, paste that URL.
|
|
@@ -74908,6 +74931,59 @@ If your browser did not open, paste that URL.
|
|
|
74908
74931
|
});
|
|
74909
74932
|
}
|
|
74910
74933
|
|
|
74934
|
+
// src/core/portal-repo-map.ts
|
|
74935
|
+
init_control_repo_path();
|
|
74936
|
+
init_control_registry();
|
|
74937
|
+
init_portal_targets();
|
|
74938
|
+
function planRepoWorkspaceMap(opts) {
|
|
74939
|
+
const registered = (opts.registeredRepos ?? listRegisteredRepos()).map(
|
|
74940
|
+
(repoPath) => canonicalizeControlRepoPath(repoPath)
|
|
74941
|
+
);
|
|
74942
|
+
const current = opts.currentRepo ? canonicalizeControlRepoPath(opts.currentRepo) : null;
|
|
74943
|
+
const others = registered.filter((repoPath) => repoPath !== current);
|
|
74944
|
+
const connections = opts.connections ?? listPortalTargetRecords();
|
|
74945
|
+
if (others.length === 0 || connections.length === 0) return { kind: "none" };
|
|
74946
|
+
if (connections.length === 1) {
|
|
74947
|
+
const only = connections[0];
|
|
74948
|
+
return {
|
|
74949
|
+
kind: "attach-more",
|
|
74950
|
+
workspaceAlias: only.alias,
|
|
74951
|
+
workspaceLabel: displayPortalTargetLabel(only),
|
|
74952
|
+
repos: others
|
|
74953
|
+
};
|
|
74954
|
+
}
|
|
74955
|
+
return {
|
|
74956
|
+
kind: "map-orgs",
|
|
74957
|
+
currentAlias: opts.currentAlias,
|
|
74958
|
+
repos: others,
|
|
74959
|
+
connections: connections.map((entry) => ({
|
|
74960
|
+
alias: entry.alias,
|
|
74961
|
+
label: displayPortalTargetLabel(entry)
|
|
74962
|
+
}))
|
|
74963
|
+
};
|
|
74964
|
+
}
|
|
74965
|
+
function applyRepoWorkspaceMap(assignments) {
|
|
74966
|
+
const attached = [];
|
|
74967
|
+
for (const row of assignments) {
|
|
74968
|
+
if (!row.alias) continue;
|
|
74969
|
+
const canonical = canonicalizeControlRepoPath(row.repoPath);
|
|
74970
|
+
attachRepoPortalTarget(canonical, row.alias);
|
|
74971
|
+
recordRepoForControlSync(canonical);
|
|
74972
|
+
attached.push(canonical);
|
|
74973
|
+
}
|
|
74974
|
+
return attached;
|
|
74975
|
+
}
|
|
74976
|
+
function remainingUnassignedRepos(repos, assigned) {
|
|
74977
|
+
const taken = new Set(
|
|
74978
|
+
[...assigned].map((repoPath) => canonicalizeControlRepoPath(repoPath))
|
|
74979
|
+
);
|
|
74980
|
+
return repos.filter((repoPath) => !taken.has(canonicalizeControlRepoPath(repoPath)));
|
|
74981
|
+
}
|
|
74982
|
+
function remainingConnections(connections, usedAliases) {
|
|
74983
|
+
const used = /* @__PURE__ */ new Set([...usedAliases]);
|
|
74984
|
+
return connections.filter((entry) => !used.has(entry.alias));
|
|
74985
|
+
}
|
|
74986
|
+
|
|
74911
74987
|
// src/core/portal-connect.ts
|
|
74912
74988
|
init_portal_targets();
|
|
74913
74989
|
function attachRepoIfPresent(alias, repoPath) {
|
|
@@ -74930,9 +75006,16 @@ async function runPortalConnect(input) {
|
|
|
74930
75006
|
token: input.apiKey,
|
|
74931
75007
|
setAsDefault: true
|
|
74932
75008
|
});
|
|
74933
|
-
return {
|
|
75009
|
+
return {
|
|
75010
|
+
record: record3,
|
|
75011
|
+
attachedRepo: attachRepoIfPresent(record3.alias, input.repoPath),
|
|
75012
|
+
extraAttached: [],
|
|
75013
|
+
mappedInBrowser: false
|
|
75014
|
+
};
|
|
74934
75015
|
}
|
|
74935
|
-
const creds = await loginViaBrowser(input.portalUrl
|
|
75016
|
+
const creds = await loginViaBrowser(input.portalUrl, {
|
|
75017
|
+
repos: listRegisteredReposForConnect(input.repoPath)
|
|
75018
|
+
});
|
|
74936
75019
|
const record2 = upsertPortalTarget({
|
|
74937
75020
|
alias: input.alias,
|
|
74938
75021
|
portalUrl: input.portalUrl,
|
|
@@ -74946,7 +75029,35 @@ async function runPortalConnect(input) {
|
|
|
74946
75029
|
email: creds.email,
|
|
74947
75030
|
setAsDefault: true
|
|
74948
75031
|
});
|
|
74949
|
-
|
|
75032
|
+
const attachedRepo = attachRepoIfPresent(record2.alias, input.repoPath);
|
|
75033
|
+
if (!creds.targets || creds.targets.length === 0) {
|
|
75034
|
+
return { record: record2, attachedRepo, extraAttached: [], mappedInBrowser: false };
|
|
75035
|
+
}
|
|
75036
|
+
const extraAttached = [];
|
|
75037
|
+
for (const target of creds.targets) {
|
|
75038
|
+
const next = upsertPortalTarget({
|
|
75039
|
+
portalUrl: input.portalUrl,
|
|
75040
|
+
workspaceId: target.workspaceId,
|
|
75041
|
+
workspace: target.workspace,
|
|
75042
|
+
workspaceSlug: target.workspace,
|
|
75043
|
+
workspaceName: target.workspaceName,
|
|
75044
|
+
token: target.token,
|
|
75045
|
+
refreshToken: target.refreshToken,
|
|
75046
|
+
expiresAt: target.expiresAt,
|
|
75047
|
+
email: creds.email,
|
|
75048
|
+
setAsDefault: false
|
|
75049
|
+
});
|
|
75050
|
+
const attached = applyRepoWorkspaceMap(
|
|
75051
|
+
target.repos.map((repoPath) => ({ repoPath, alias: next.alias }))
|
|
75052
|
+
);
|
|
75053
|
+
extraAttached.push(...attached.filter((repoPath) => repoPath !== attachedRepo));
|
|
75054
|
+
}
|
|
75055
|
+
return { record: record2, attachedRepo, extraAttached, mappedInBrowser: true };
|
|
75056
|
+
}
|
|
75057
|
+
function listRegisteredReposForConnect(repoPath) {
|
|
75058
|
+
const resolved = repoPath ? path59.resolve(repoPath) : null;
|
|
75059
|
+
const current = resolved && fs49.existsSync(resolved) ? canonicalizeControlRepoPath(resolved) : null;
|
|
75060
|
+
return [.../* @__PURE__ */ new Set([...current ? [current] : [], ...listRegisteredRepos()])];
|
|
74950
75061
|
}
|
|
74951
75062
|
|
|
74952
75063
|
// src/cli/commands/hq.ts
|
|
@@ -74978,7 +75089,7 @@ var hqCommand = {
|
|
|
74978
75089
|
alias: "y",
|
|
74979
75090
|
type: "boolean",
|
|
74980
75091
|
default: false,
|
|
74981
|
-
describe: "Skip
|
|
75092
|
+
describe: "Skip interactive prompts (portal URL and extra-repo mapping)"
|
|
74982
75093
|
}),
|
|
74983
75094
|
(argv) => handleHqConnect(
|
|
74984
75095
|
argv
|
|
@@ -75036,6 +75147,90 @@ async function resolveConnectPortalUrl(explicit, skipPrompt) {
|
|
|
75036
75147
|
]);
|
|
75037
75148
|
return resolvePortalUrl(url);
|
|
75038
75149
|
}
|
|
75150
|
+
async function promptExtraRepoAttachments(opts) {
|
|
75151
|
+
const plan = planRepoWorkspaceMap(opts);
|
|
75152
|
+
if (plan.kind === "none") return [];
|
|
75153
|
+
const noun = plan.repos.length === 1 ? "repository" : "repositories";
|
|
75154
|
+
info(`${plan.repos.length} other registered ${noun}:`);
|
|
75155
|
+
for (const repoPath of plan.repos) info(` ${repoPath}`);
|
|
75156
|
+
if (plan.kind === "attach-more") {
|
|
75157
|
+
const { selected } = await esm_default13.prompt([
|
|
75158
|
+
{
|
|
75159
|
+
type: "checkbox",
|
|
75160
|
+
name: "selected",
|
|
75161
|
+
message: `Attach which of these ${plan.repos.length} ${noun} to ${plan.workspaceLabel}?`,
|
|
75162
|
+
choices: plan.repos.map((repoPath) => ({
|
|
75163
|
+
name: repoPath,
|
|
75164
|
+
value: repoPath,
|
|
75165
|
+
checked: true
|
|
75166
|
+
}))
|
|
75167
|
+
}
|
|
75168
|
+
]);
|
|
75169
|
+
return applyRepoWorkspaceMap(
|
|
75170
|
+
selected.map((repoPath) => ({ repoPath, alias: plan.workspaceAlias }))
|
|
75171
|
+
);
|
|
75172
|
+
}
|
|
75173
|
+
const { sendAll } = await esm_default13.prompt([
|
|
75174
|
+
{
|
|
75175
|
+
type: "confirm",
|
|
75176
|
+
name: "sendAll",
|
|
75177
|
+
message: `Send all ${plan.repos.length} ${noun} to the same workspace?`,
|
|
75178
|
+
default: true
|
|
75179
|
+
}
|
|
75180
|
+
]);
|
|
75181
|
+
if (sendAll) {
|
|
75182
|
+
const { alias } = await esm_default13.prompt([
|
|
75183
|
+
{
|
|
75184
|
+
type: "list",
|
|
75185
|
+
name: "alias",
|
|
75186
|
+
message: "Which workspace?",
|
|
75187
|
+
default: plan.currentAlias,
|
|
75188
|
+
choices: plan.connections.map((entry) => ({
|
|
75189
|
+
name: entry.label,
|
|
75190
|
+
value: entry.alias
|
|
75191
|
+
}))
|
|
75192
|
+
}
|
|
75193
|
+
]);
|
|
75194
|
+
return applyRepoWorkspaceMap(plan.repos.map((repoPath) => ({ repoPath, alias })));
|
|
75195
|
+
}
|
|
75196
|
+
const attached = [];
|
|
75197
|
+
let remainingRepos = [...plan.repos];
|
|
75198
|
+
let remainingOrgs = [...plan.connections];
|
|
75199
|
+
while (remainingRepos.length > 0 && remainingOrgs.length > 0) {
|
|
75200
|
+
const left2 = remainingRepos.length === 1 ? "repository" : "repositories";
|
|
75201
|
+
info(`${remainingRepos.length} ${left2} left to assign.`);
|
|
75202
|
+
const { alias } = await esm_default13.prompt([
|
|
75203
|
+
{
|
|
75204
|
+
type: "list",
|
|
75205
|
+
name: "alias",
|
|
75206
|
+
message: "Workspace to assign repositories to",
|
|
75207
|
+
default: remainingOrgs.some((entry) => entry.alias === plan.currentAlias) ? plan.currentAlias : remainingOrgs[0]?.alias,
|
|
75208
|
+
choices: [
|
|
75209
|
+
...remainingOrgs.map((entry) => ({
|
|
75210
|
+
name: entry.label,
|
|
75211
|
+
value: entry.alias
|
|
75212
|
+
})),
|
|
75213
|
+
{ name: "Done \u2014 leave the rest unattached", value: "" }
|
|
75214
|
+
]
|
|
75215
|
+
}
|
|
75216
|
+
]);
|
|
75217
|
+
if (!alias) break;
|
|
75218
|
+
const { selected } = await esm_default13.prompt([
|
|
75219
|
+
{
|
|
75220
|
+
type: "checkbox",
|
|
75221
|
+
name: "selected",
|
|
75222
|
+
message: `Repositories for ${remainingOrgs.find((entry) => entry.alias === alias)?.label ?? alias}`,
|
|
75223
|
+
choices: remainingRepos.map((repoPath) => ({ name: repoPath, value: repoPath }))
|
|
75224
|
+
}
|
|
75225
|
+
]);
|
|
75226
|
+
if (selected.length > 0) {
|
|
75227
|
+
attached.push(...applyRepoWorkspaceMap(selected.map((repoPath) => ({ repoPath, alias }))));
|
|
75228
|
+
}
|
|
75229
|
+
remainingRepos = remainingUnassignedRepos(remainingRepos, selected);
|
|
75230
|
+
remainingOrgs = remainingConnections(remainingOrgs, [alias]);
|
|
75231
|
+
}
|
|
75232
|
+
return attached;
|
|
75233
|
+
}
|
|
75039
75234
|
async function handleHqConnect(argv) {
|
|
75040
75235
|
if (argv.deprecated) {
|
|
75041
75236
|
warn("har control login is deprecated \u2014 use har hq connect");
|
|
@@ -75046,7 +75241,7 @@ async function handleHqConnect(argv) {
|
|
|
75046
75241
|
info(`Portal: ${portalUrl} (${PORTAL_SOURCE_LABEL[source]})`);
|
|
75047
75242
|
}
|
|
75048
75243
|
try {
|
|
75049
|
-
const { record: record2, attachedRepo } = await runPortalConnect({
|
|
75244
|
+
const { record: record2, attachedRepo, extraAttached, mappedInBrowser } = await runPortalConnect({
|
|
75050
75245
|
portalUrl,
|
|
75051
75246
|
apiKey: argv.apiKey,
|
|
75052
75247
|
repoPath: argv.repo ?? ".",
|
|
@@ -75074,6 +75269,18 @@ async function handleHqConnect(argv) {
|
|
|
75074
75269
|
} else {
|
|
75075
75270
|
info("No local repository attached (run this from a repo, or pass --repo).");
|
|
75076
75271
|
}
|
|
75272
|
+
for (const repoPath of extraAttached) {
|
|
75273
|
+
success(`Attached ${repoPath}`);
|
|
75274
|
+
}
|
|
75275
|
+
if (!argv.json && !argv.yes && !mappedInBrowser && isInteractive()) {
|
|
75276
|
+
const extra = await promptExtraRepoAttachments({
|
|
75277
|
+
currentRepo: attachedRepo,
|
|
75278
|
+
currentAlias: record2.alias
|
|
75279
|
+
});
|
|
75280
|
+
for (const repoPath of extra) {
|
|
75281
|
+
success(`Attached ${repoPath}`);
|
|
75282
|
+
}
|
|
75283
|
+
}
|
|
75077
75284
|
info("Sync: har control sync");
|
|
75078
75285
|
} catch (err) {
|
|
75079
75286
|
error(`Connect failed: ${err.message}`);
|