@pipod/cli 0.1.10 → 0.1.12
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/cli.js +86 -31
- package/dist/cli.js.map +1 -1
- package/docs/config-simplification.md +8 -6
- package/docs/workspace-seeding.md +16 -11
- package/man/pipod-config.5 +1 -1
- package/man/pipod.1 +41 -18
- package/man/pipod.1.md +39 -17
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1908,6 +1908,14 @@ var AccountClient = class {
|
|
|
1908
1908
|
putOrgSettings(body) {
|
|
1909
1909
|
return this.request(`/orgs/${encodeURIComponent(this.orgId)}/settings`, { method: "PUT", body });
|
|
1910
1910
|
}
|
|
1911
|
+
/** The organization's policy: its `config` holds the limits; the scripts and Pi files are always empty. */
|
|
1912
|
+
getOrgPolicy() {
|
|
1913
|
+
return this.request(`/orgs/${encodeURIComponent(this.orgId)}/policy`);
|
|
1914
|
+
}
|
|
1915
|
+
/** Replaces the whole policy, compare-and-swap on `version`. The server validates it. */
|
|
1916
|
+
putOrgPolicy(body) {
|
|
1917
|
+
return this.request(`/orgs/${encodeURIComponent(this.orgId)}/policy`, { method: "PUT", body });
|
|
1918
|
+
}
|
|
1911
1919
|
launch(body) {
|
|
1912
1920
|
return this.request("/pods", { method: "POST", body });
|
|
1913
1921
|
}
|
|
@@ -2209,10 +2217,12 @@ async function responseError(res, method, apiPath) {
|
|
|
2209
2217
|
hint: "an owner of your organization can grant it (`pipod org-admin` opens the console)"
|
|
2210
2218
|
});
|
|
2211
2219
|
}
|
|
2220
|
+
const issues = res.status === 400 && Array.isArray(body.detail) ? body.detail.filter((issue) => typeof issue === "string").slice(0, 10).map((issue) => issue.slice(0, 240)) : [];
|
|
2212
2221
|
return new PiPodError(`the pi pod server refused ${method} ${apiPath}: ${message}`, {
|
|
2213
2222
|
status: res.status,
|
|
2214
2223
|
...code ? { code } : {},
|
|
2215
|
-
...safeDetail !== void 0 ? { detail: safeDetail } : {}
|
|
2224
|
+
...safeDetail !== void 0 ? { detail: safeDetail } : {},
|
|
2225
|
+
...issues.length > 0 ? { hint: issues.join("\n") } : {}
|
|
2216
2226
|
});
|
|
2217
2227
|
}
|
|
2218
2228
|
function isSafeCapacityDetail(value) {
|
|
@@ -3416,11 +3426,6 @@ async function probeWorkspaceSeed(opts) {
|
|
|
3416
3426
|
const branchOut = git(["symbolic-ref", "--quiet", "--short", "HEAD"]);
|
|
3417
3427
|
const branch = branchOut.stdout.trim();
|
|
3418
3428
|
if (branchOut.status !== 0 || branch === "") return archive("detached HEAD");
|
|
3419
|
-
const status = git(["status", "--porcelain", "--untracked-files=all"]);
|
|
3420
|
-
if (status.status !== 0) return archive("cannot tell whether the working tree is clean");
|
|
3421
|
-
if (status.stdout.trim() !== "") {
|
|
3422
|
-
return archive("uncommitted changes would be lost by a clone");
|
|
3423
|
-
}
|
|
3424
3429
|
const upstream = git(["rev-parse", "--abbrev-ref", "@{u}"]);
|
|
3425
3430
|
if (upstream.status !== 0) return archive(`${branch} is not pushed`);
|
|
3426
3431
|
const ahead = git(["rev-list", "@{u}..HEAD"]);
|
|
@@ -3535,7 +3540,8 @@ function describeWorkspaceSeed(plan) {
|
|
|
3535
3540
|
return plan.reason;
|
|
3536
3541
|
case "clone": {
|
|
3537
3542
|
const base = `clone ${plan.url} at ${plan.branch} (${plan.commit.slice(0, 12)})`;
|
|
3538
|
-
|
|
3543
|
+
const authenticated = plan.access === "credential" ? `${base} with forwarded ${plan.host} credentials` : base;
|
|
3544
|
+
return `${authenticated} (local uncommitted changes are not copied)`;
|
|
3539
3545
|
}
|
|
3540
3546
|
case "archive":
|
|
3541
3547
|
return `archive ${plan.root}${plan.includeGit ? " with .git history" : ""} (${plan.reason})`;
|
|
@@ -3870,6 +3876,9 @@ function orgSourceDir(args) {
|
|
|
3870
3876
|
if (!home) throw new PiPodError("cannot locate ~/.pi-pod/org: HOME is unset", { hint: "pass --dir <path>" });
|
|
3871
3877
|
return path11.join(home, ORG_SOURCE_SUBDIR);
|
|
3872
3878
|
}
|
|
3879
|
+
function policySourcePath(args) {
|
|
3880
|
+
return path11.join(orgSourceDir(args), ORG_POLICY_FILE);
|
|
3881
|
+
}
|
|
3873
3882
|
function safePiDir(root, subpath) {
|
|
3874
3883
|
const directory = path11.join(root, subpath);
|
|
3875
3884
|
let stat;
|
|
@@ -3970,6 +3979,28 @@ async function readEnvSnapshot(envPath, mode, resolver, home) {
|
|
|
3970
3979
|
async function compileBundleSource(args) {
|
|
3971
3980
|
const cwd = args.cwd ?? process.cwd();
|
|
3972
3981
|
const envMode = args.includeEnv === false ? false : args.includeEnv === "names" ? "names" : true;
|
|
3982
|
+
if (args.source === "policy-file") {
|
|
3983
|
+
const configPath2 = policySourcePath({ dir: args.dir, home: args.home });
|
|
3984
|
+
if (!fs12.existsSync(configPath2)) {
|
|
3985
|
+
throw new PiPodError(`no org policy file at ${configPath2}`, {
|
|
3986
|
+
hint: "run `pipod pull policy` to create it from the server's policy, or pass --dir <path>"
|
|
3987
|
+
});
|
|
3988
|
+
}
|
|
3989
|
+
return {
|
|
3990
|
+
kind: "policy-file",
|
|
3991
|
+
config: parseConfig(configPath2),
|
|
3992
|
+
initScript: "",
|
|
3993
|
+
bakeScript: "",
|
|
3994
|
+
piFiles: {},
|
|
3995
|
+
env: {},
|
|
3996
|
+
warnings: [],
|
|
3997
|
+
name: "org policy",
|
|
3998
|
+
templateRef: null,
|
|
3999
|
+
projectRoot: null,
|
|
4000
|
+
root: path11.dirname(configPath2),
|
|
4001
|
+
configPath: configPath2
|
|
4002
|
+
};
|
|
4003
|
+
}
|
|
3973
4004
|
if (args.source === "project") {
|
|
3974
4005
|
const configPath2 = findConfigPath(cwd, { home: args.home });
|
|
3975
4006
|
if (!configPath2) {
|
|
@@ -4069,6 +4100,7 @@ async function compileBundleSource(args) {
|
|
|
4069
4100
|
}
|
|
4070
4101
|
var ORG_SCRIPTS = { init: "init.sh", bake: "bake.sh" };
|
|
4071
4102
|
var ORG_ENV_FILE = "env";
|
|
4103
|
+
var ORG_POLICY_FILE = "policy.json";
|
|
4072
4104
|
function readPiFiles(root, config, kind) {
|
|
4073
4105
|
const files = {};
|
|
4074
4106
|
const warnings = [];
|
|
@@ -4323,6 +4355,15 @@ function writePulledAgents(writer, piDir, agents) {
|
|
|
4323
4355
|
}
|
|
4324
4356
|
function writeBundleSource(args) {
|
|
4325
4357
|
const { kind, bundle } = args;
|
|
4358
|
+
if (kind === "policy-file") {
|
|
4359
|
+
const policyPath = policySourcePath({ dir: args.dir, home: args.home });
|
|
4360
|
+
const writer2 = new LayerWriter(path11.dirname(policyPath));
|
|
4361
|
+
if (!fs12.existsSync(policyPath) || stable(parseConfig(policyPath)) !== stable(bundle.config)) {
|
|
4362
|
+
writer2.noteComments(policyPath);
|
|
4363
|
+
writer2.json(policyPath, bundle.config);
|
|
4364
|
+
}
|
|
4365
|
+
return { written: writer2.written, removed: writer2.removed, warnings: writer2.warnings, root: writer2.root };
|
|
4366
|
+
}
|
|
4326
4367
|
let root;
|
|
4327
4368
|
let configPath;
|
|
4328
4369
|
if (kind === "project") {
|
|
@@ -5217,7 +5258,7 @@ async function cloneIntoPod(opts, plan) {
|
|
|
5217
5258
|
if (isMissingRoute(error2)) {
|
|
5218
5259
|
info("this server predates workspace cloning \u2014 sending an archive instead");
|
|
5219
5260
|
} else {
|
|
5220
|
-
warn(`clone failed: ${reason} \u2014 falling back to an archive of the local tree`);
|
|
5261
|
+
warn(`clone failed: ${reason} \u2014 falling back to an archive of the local working tree (including uncommitted changes)`);
|
|
5221
5262
|
}
|
|
5222
5263
|
return archiveIntoPod(
|
|
5223
5264
|
opts,
|
|
@@ -15619,9 +15660,7 @@ function announceWorkspaceSeed(plan) {
|
|
|
15619
15660
|
info(`workspace transport: this directory will be copied into the pod (${seed.reason})`);
|
|
15620
15661
|
return;
|
|
15621
15662
|
case "clone":
|
|
15622
|
-
info(
|
|
15623
|
-
`workspace seed: the pod will clone ${seed.url} at ${seed.branch} (${seed.commit.slice(0, 12)})` + (seed.access === "credential" ? ` with forwarded ${seed.host} credentials` : "")
|
|
15624
|
-
);
|
|
15663
|
+
info(`workspace seed: the pod will ${describeWorkspaceSeed(seed)}`);
|
|
15625
15664
|
break;
|
|
15626
15665
|
case "archive":
|
|
15627
15666
|
info(
|
|
@@ -18863,7 +18902,7 @@ async function rmCmd2(client, args, flags) {
|
|
|
18863
18902
|
}
|
|
18864
18903
|
|
|
18865
18904
|
// src/commands/layers.ts
|
|
18866
|
-
var LAYER_USAGE = "template [<name>], user, or
|
|
18905
|
+
var LAYER_USAGE = "template [<name>], user, org, or policy";
|
|
18867
18906
|
function parseLayerArgs(verb, args, opts) {
|
|
18868
18907
|
const positionals = [];
|
|
18869
18908
|
let dir;
|
|
@@ -18890,18 +18929,29 @@ function parseLayerArgs(verb, args, opts) {
|
|
|
18890
18929
|
layer = { kind: "template" };
|
|
18891
18930
|
} else if (first === "template") {
|
|
18892
18931
|
layer = second === void 0 ? { kind: "template" } : { kind: "template", name: second };
|
|
18893
|
-
} else if (first === "user" || first === "org") {
|
|
18932
|
+
} else if (first === "user" || first === "org" || first === "policy") {
|
|
18894
18933
|
layer = { kind: first };
|
|
18895
18934
|
if (second !== void 0) extra.unshift(second);
|
|
18896
18935
|
} else {
|
|
18897
18936
|
throw new PiPodError(`unknown layer "${first}"`, { hint: `layers: ${LAYER_USAGE}` });
|
|
18898
18937
|
}
|
|
18899
18938
|
if (extra.length > 0) throw new PiPodError(`unexpected \`pipod ${verb}\` argument(s): ${extra.join(" ")}`);
|
|
18900
|
-
if (dir !== void 0 && layer.kind !== "org"
|
|
18939
|
+
if (dir !== void 0 && layer.kind !== "org" && layer.kind !== "policy") {
|
|
18940
|
+
throw new PiPodError("--dir only applies to the org and policy layers");
|
|
18941
|
+
}
|
|
18901
18942
|
return { layer, ...dir !== void 0 ? { dir } : {}, yes, withSecrets };
|
|
18902
18943
|
}
|
|
18903
18944
|
function sourceKindOf(layer) {
|
|
18904
|
-
|
|
18945
|
+
switch (layer.kind) {
|
|
18946
|
+
case "template":
|
|
18947
|
+
return "project";
|
|
18948
|
+
case "user":
|
|
18949
|
+
return "user-dir";
|
|
18950
|
+
case "org":
|
|
18951
|
+
return "org-dir";
|
|
18952
|
+
case "policy":
|
|
18953
|
+
return "policy-file";
|
|
18954
|
+
}
|
|
18905
18955
|
}
|
|
18906
18956
|
async function fetchRemoteLayer(client, layer, source) {
|
|
18907
18957
|
if (layer.kind === "template") {
|
|
@@ -18924,6 +18974,17 @@ async function fetchRemoteLayer(client, layer, source) {
|
|
|
18924
18974
|
scopeId: template.id
|
|
18925
18975
|
};
|
|
18926
18976
|
}
|
|
18977
|
+
if (layer.kind === "policy") {
|
|
18978
|
+
const current2 = await client.getOrgPolicy();
|
|
18979
|
+
return {
|
|
18980
|
+
bundle: settingsBundle(current2),
|
|
18981
|
+
version: current2.version,
|
|
18982
|
+
secretNames: [],
|
|
18983
|
+
label: `org policy v${current2.version}`,
|
|
18984
|
+
scope: "policy",
|
|
18985
|
+
scopeId: client.orgId
|
|
18986
|
+
};
|
|
18987
|
+
}
|
|
18927
18988
|
const current = layer.kind === "user" ? await client.getUserSettings() : await client.getOrgSettings();
|
|
18928
18989
|
const scopeId = layer.kind === "user" ? client.userId : client.orgId;
|
|
18929
18990
|
const listed = await client.listSecrets(layer.kind, scopeId).catch(() => ({ secrets: [] }));
|
|
@@ -18939,6 +19000,7 @@ async function fetchRemoteLayer(client, layer, source) {
|
|
|
18939
19000
|
function localLabel(layer, flags) {
|
|
18940
19001
|
if (layer.kind === "template") return "project";
|
|
18941
19002
|
if (layer.kind === "user") return "~/.pi-pod + ~/.pi/agent";
|
|
19003
|
+
if (layer.kind === "policy") return displayPath(policySourcePath({ dir: flags.dir, home: flags.home }), flags.home);
|
|
18942
19004
|
return displayPath(orgSourceDir({ dir: flags.dir, home: flags.home }), flags.home);
|
|
18943
19005
|
}
|
|
18944
19006
|
function printDiff(heading, lines) {
|
|
@@ -18972,22 +19034,10 @@ async function runPull(args, flags) {
|
|
|
18972
19034
|
const kind = sourceKindOf(parsed.layer);
|
|
18973
19035
|
let source;
|
|
18974
19036
|
let firstPull = false;
|
|
18975
|
-
|
|
19037
|
+
const localPath = kind === "org-dir" ? orgSourceDir({ dir: parsed.dir, home: flags.home }) : kind === "policy-file" ? policySourcePath({ dir: parsed.dir, home: flags.home }) : null;
|
|
19038
|
+
if (localPath !== null && !fs31.existsSync(localPath)) {
|
|
18976
19039
|
firstPull = true;
|
|
18977
|
-
source = {
|
|
18978
|
-
kind,
|
|
18979
|
-
config: {},
|
|
18980
|
-
initScript: "",
|
|
18981
|
-
bakeScript: "",
|
|
18982
|
-
piFiles: {},
|
|
18983
|
-
env: {},
|
|
18984
|
-
warnings: [],
|
|
18985
|
-
name: "org settings",
|
|
18986
|
-
templateRef: null,
|
|
18987
|
-
projectRoot: null,
|
|
18988
|
-
root: orgSourceDir({ dir: parsed.dir, home: flags.home }),
|
|
18989
|
-
configPath: ""
|
|
18990
|
-
};
|
|
19040
|
+
source = { config: {}, initScript: "", bakeScript: "", piFiles: {}, env: {}, warnings: [], templateRef: null };
|
|
18991
19041
|
} else {
|
|
18992
19042
|
source = await compileBundleSource({ source: kind, cwd, home: flags.home, includeEnv: false, dir: parsed.dir });
|
|
18993
19043
|
}
|
|
@@ -19048,6 +19098,11 @@ async function runPush(args, flags) {
|
|
|
19048
19098
|
return 0;
|
|
19049
19099
|
}
|
|
19050
19100
|
if (remote.version === void 0) throw new PiPodError(`${remote.label} has no version to compare-and-swap against`);
|
|
19101
|
+
if (remote.scope === "policy") {
|
|
19102
|
+
const written2 = await flags.client.putOrgPolicy({ config: source.config, version: remote.version });
|
|
19103
|
+
info(`pushed ${local} to org policy v${written2.version}`);
|
|
19104
|
+
return 0;
|
|
19105
|
+
}
|
|
19051
19106
|
const body = {
|
|
19052
19107
|
config: source.config,
|
|
19053
19108
|
version: remote.version,
|
|
@@ -19120,7 +19175,7 @@ async function runSettings(args, flags) {
|
|
|
19120
19175
|
const [scope = "user", action = "show", ...rest] = args;
|
|
19121
19176
|
if (scope !== "user" && scope !== "org") {
|
|
19122
19177
|
throw new PiPodError(`unknown settings scope "${scope}"`, {
|
|
19123
|
-
hint: `${USAGE}; templates are managed with \`pipod templates\``
|
|
19178
|
+
hint: `${USAGE}; templates are managed with \`pipod templates\`, and org policy with \`pipod pull|diff|push policy\``
|
|
19124
19179
|
});
|
|
19125
19180
|
}
|
|
19126
19181
|
if (action === "show") {
|