Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cli-node/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@primitivedotdev/cli",
"version": "0.25.1",
"version": "0.25.2",
"description": "Official Primitive CLI: deploy Primitive Functions, send and inspect mail, manage endpoints, all from the terminal. Wraps the @primitivedotdev/sdk runtime client with one-shot commands.",
"type": "module",
"sideEffects": false,
Expand Down
22 changes: 20 additions & 2 deletions cli-node/src/oclif/commands/functions-deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ export type RunDeployWithSecretsResult =
created: CreateFunctionResult;
succeededKeys: string[];
failedKey: string;
pendingKeys: string[];
}
| {
kind: "error";
Expand Down Expand Up @@ -169,7 +170,15 @@ export async function runDeployWithSecrets(

const writtenSecrets: FunctionSecretWriteResult[] = [];
const succeededKeys: string[] = [];
for (const pair of params.secrets) {
for (let i = 0; i < params.secrets.length; i++) {
const pair = params.secrets[i];
// Pre-compute the keys that come AFTER the current pair so a
// set-secret failure can surface every key that was never
// attempted, not just the one that failed. Without this, a user
// following the recovery hint verbatim would re-run set-secret
// only for the failed key and silently leave the trailing keys
// un-written.
const pendingKeys = params.secrets.slice(i + 1).map((p) => p.key);
const setResult = await api.setSecret({
id: created.id,
key: pair.key,
Expand All @@ -181,6 +190,7 @@ export async function runDeployWithSecrets(
failedKey: pair.key,
kind: "error",
payload: extractErrorPayload(setResult.error),
pendingKeys,
stage: "set-secret",
succeededKeys,
};
Expand All @@ -195,6 +205,7 @@ export async function runDeployWithSecrets(
code: "client_error",
message: "Secret write returned no data",
},
pendingKeys,
stage: "set-secret",
succeededKeys,
};
Expand Down Expand Up @@ -412,8 +423,15 @@ class FunctionsDeployCommand extends Command {
outcome.succeededKeys.length > 0
? outcome.succeededKeys.join(", ")
: "(none)";
const pending =
outcome.pendingKeys.length > 0
? outcome.pendingKeys.join(", ")
: "(none)";
const allMissing = [outcome.failedKey, ...outcome.pendingKeys].join(
", ",
);
process.stderr.write(
`Function ${outcome.created.name} (${outcome.created.id}) was created, but writing secret ${outcome.failedKey} failed; succeeded keys so far: ${succeeded}. The redeploy is NOT yet live. Re-run \`primitive functions:set-secret --id ${outcome.created.id} --key ${outcome.failedKey} --value <value> --redeploy\` after fixing the cause.\n`,
`Function ${outcome.created.name} (${outcome.created.id}) was created, but writing secret ${outcome.failedKey} failed; succeeded keys so far: ${succeeded}; keys not yet attempted: ${pending}. The redeploy is NOT yet live. Re-run \`primitive functions:set-secret\` for each of [${allMissing}], then \`primitive functions:redeploy --id ${outcome.created.id} --file <bundle>\` to push them live.\n`,
);
} else if (outcome.stage === "redeploy") {
const succeeded =
Expand Down
19 changes: 17 additions & 2 deletions cli-node/src/oclif/commands/functions-redeploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ export type RunRedeployWithSecretsResult =
payload: unknown;
succeededKeys: string[];
failedKey: string;
pendingKeys: string[];
}
| {
kind: "error";
Expand All @@ -101,7 +102,12 @@ export async function runRedeployWithSecrets(
): Promise<RunRedeployWithSecretsResult> {
const writtenSecrets: FunctionSecretWriteResult[] = [];
const succeededKeys: string[] = [];
for (const pair of params.secrets) {
for (let i = 0; i < params.secrets.length; i++) {
const pair = params.secrets[i];
// Pre-compute the keys that come AFTER the current pair so a
// set-secret failure can surface every key that was never
// attempted, not just the one that failed.
const pendingKeys = params.secrets.slice(i + 1).map((p) => p.key);
const setResult = await api.setSecret({
id: params.id,
key: pair.key,
Expand All @@ -112,6 +118,7 @@ export async function runRedeployWithSecrets(
failedKey: pair.key,
kind: "error",
payload: extractErrorPayload(setResult.error),
pendingKeys,
stage: "set-secret",
succeededKeys,
};
Expand All @@ -125,6 +132,7 @@ export async function runRedeployWithSecrets(
code: "client_error",
message: "Secret write returned no data",
},
pendingKeys,
stage: "set-secret",
succeededKeys,
};
Expand Down Expand Up @@ -319,8 +327,15 @@ class FunctionsRedeployCommand extends Command {
outcome.succeededKeys.length > 0
? outcome.succeededKeys.join(", ")
: "(none)";
const pending =
outcome.pendingKeys.length > 0
? outcome.pendingKeys.join(", ")
: "(none)";
const allMissing = [outcome.failedKey, ...outcome.pendingKeys].join(
", ",
);
process.stderr.write(
`Writing secret ${outcome.failedKey} failed before the redeploy; succeeded keys so far: ${succeeded}. The new bundle has NOT been deployed. Re-run \`primitive functions:set-secret --id ${flags.id} --key ${outcome.failedKey} --value <value>\` after fixing the cause, then \`primitive functions:redeploy --id ${flags.id} --file <bundle>\`.\n`,
`Writing secret ${outcome.failedKey} failed before the redeploy; succeeded keys so far: ${succeeded}; keys not yet attempted: ${pending}. The new bundle has NOT been deployed. Re-run \`primitive functions:set-secret\` for each of [${allMissing}], then \`primitive functions:redeploy --id ${flags.id} --file <bundle>\` to push them live.\n`,
);
} else if (outcome.stage === "redeploy") {
const succeeded =
Expand Down
5 changes: 5 additions & 0 deletions cli-node/tests/oclif/functions-deploy.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,11 @@ describe("runDeployWithSecrets (--secret K=V)", () => {
if (outcome.kind === "error" && outcome.stage === "set-secret") {
expect(outcome.failedKey).toBe("SECOND");
expect(outcome.succeededKeys).toEqual(["FIRST"]);
// pendingKeys must include every key after the failure so the
// CLI hint can list keys the user still needs to set; otherwise
// re-running set-secret only for failedKey silently leaves THIRD
// un-written.
expect(outcome.pendingKeys).toEqual(["THIRD"]);
expect(outcome.created.id).toBe(FN_ID);
expect(outcome.payload).toEqual({
code: "validation",
Expand Down
3 changes: 3 additions & 0 deletions cli-node/tests/oclif/functions-redeploy.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,9 @@ describe("runRedeployWithSecrets (--secret K=V)", () => {
if (outcome.kind === "error" && outcome.stage === "set-secret") {
expect(outcome.failedKey).toBe("SECOND");
expect(outcome.succeededKeys).toEqual(["FIRST"]);
// pendingKeys lists keys never attempted so the CLI hint can
// direct the user to re-set them too, not just the failed one.
expect(outcome.pendingKeys).toEqual(["THIRD"]);
expect(outcome.payload).toEqual({
code: "validation",
message: "value too long",
Expand Down
Loading