@primitivedotdev/cli 1.1.0 → 1.2.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.
Files changed (2) hide show
  1. package/dist/oclif/index.js +192 -20
  2. package/package.json +1 -1
@@ -17171,6 +17171,108 @@ async function runSourceDeploy(api, params) {
17171
17171
  result: data
17172
17172
  };
17173
17173
  }
17174
+ async function runSourceDeployWithSecrets(api, params) {
17175
+ const listed = await api.listFunctions();
17176
+ if (listed.error) return {
17177
+ kind: "error",
17178
+ payload: extractErrorPayload(listed.error),
17179
+ stage: "lookup"
17180
+ };
17181
+ const foundId = (listed.data?.data ?? []).find((f) => f.name === params.name)?.id ?? null;
17182
+ let functionId;
17183
+ let createPayload;
17184
+ if (foundId === null) {
17185
+ const created = await api.createFunction({
17186
+ files: params.files,
17187
+ name: params.name
17188
+ });
17189
+ if (created.error) return {
17190
+ kind: "error",
17191
+ payload: extractErrorPayload(created.error),
17192
+ stage: "create"
17193
+ };
17194
+ const data = created.data?.data;
17195
+ if (!data) return {
17196
+ kind: "error",
17197
+ payload: {
17198
+ code: "client_error",
17199
+ message: "Create returned no data"
17200
+ },
17201
+ stage: "create"
17202
+ };
17203
+ functionId = data.id;
17204
+ createPayload = data;
17205
+ } else functionId = foundId;
17206
+ const writtenSecrets = [];
17207
+ const succeededKeys = [];
17208
+ for (let i = 0; i < params.secrets.length; i++) {
17209
+ const pair = params.secrets[i];
17210
+ const pendingKeys = params.secrets.slice(i + 1).map((p) => p.key);
17211
+ const setResult = await api.setSecret({
17212
+ id: functionId,
17213
+ key: pair.key,
17214
+ value: pair.value
17215
+ });
17216
+ if (setResult.error) return {
17217
+ ...createPayload ? { created: createPayload } : {},
17218
+ failedKey: pair.key,
17219
+ functionId,
17220
+ kind: "error",
17221
+ payload: extractErrorPayload(setResult.error),
17222
+ pendingKeys,
17223
+ stage: "set-secret",
17224
+ succeededKeys
17225
+ };
17226
+ const secret = setResult.data?.data;
17227
+ if (!secret) return {
17228
+ ...createPayload ? { created: createPayload } : {},
17229
+ failedKey: pair.key,
17230
+ functionId,
17231
+ kind: "error",
17232
+ payload: {
17233
+ code: "client_error",
17234
+ message: "Secret write returned no data"
17235
+ },
17236
+ pendingKeys,
17237
+ stage: "set-secret",
17238
+ succeededKeys
17239
+ };
17240
+ writtenSecrets.push(secret);
17241
+ succeededKeys.push(pair.key);
17242
+ }
17243
+ const updated = await api.updateFunction({
17244
+ files: params.files,
17245
+ id: functionId
17246
+ });
17247
+ if (updated.error) return {
17248
+ ...createPayload ? { created: createPayload } : {},
17249
+ functionId,
17250
+ kind: "error",
17251
+ payload: extractErrorPayload(updated.error),
17252
+ stage: "secret-redeploy",
17253
+ succeededKeys
17254
+ };
17255
+ const redeployed = updated.data?.data;
17256
+ if (!redeployed) return {
17257
+ ...createPayload ? { created: createPayload } : {},
17258
+ functionId,
17259
+ kind: "error",
17260
+ payload: {
17261
+ code: "client_error",
17262
+ message: "Redeploy returned no data"
17263
+ },
17264
+ stage: "secret-redeploy",
17265
+ succeededKeys
17266
+ };
17267
+ return {
17268
+ kind: "ok",
17269
+ result: {
17270
+ action: foundId === null ? "created" : "redeployed",
17271
+ redeploy: redeployed,
17272
+ secrets: writtenSecrets
17273
+ }
17274
+ };
17275
+ }
17174
17276
  function renderBuildFailure(payload, write) {
17175
17277
  if (typeof payload !== "object" || payload === null) return false;
17176
17278
  const error = payload.error ?? payload;
@@ -17657,14 +17759,23 @@ var FunctionsDeployCommand = class FunctionsDeployCommand extends Command {
17657
17759
 
17658
17760
  Pass secret source flags to seed bindings in the same command. Keys
17659
17761
  must match \`^[A-Z_][A-Z0-9_]*$\` (uppercase letters, digits,
17660
- underscores; first character is a letter or underscore). With one
17661
- or more secrets the deploy fans out to multiple API calls:
17662
- create-function, set-secret per pair, then a final update-function
17663
- with the same bundle so the running handler picks up the bindings.
17664
- If a secret write fails after the create step the function exists
17665
- with whatever secrets succeeded and the redeploy has NOT fired;
17666
- re-run \`primitive functions set-secret\` for the missing keys, then
17667
- \`primitive functions redeploy\` to push them live. ${SECRET_SOURCE_FLAGS_DESCRIPTION}`;
17762
+ underscores; first character is a letter or underscore).
17763
+
17764
+ With one or more secrets the deploy fans out to multiple API calls.
17765
+ For --file (and for --source when no function with the given name
17766
+ exists yet): create-function, set-secret per pair, then a final
17767
+ update-function so the running handler picks up the bindings. For
17768
+ --source against an existing function name: the create-function step
17769
+ is replaced by an id lookup, then set-secret per pair, then a single
17770
+ update-function that binds the new code and the new secret env in
17771
+ one step (avoiding an intermediate redeploy that would briefly run
17772
+ the new code with the previous secret bindings).
17773
+
17774
+ If a secret write fails before the final redeploy, the function row
17775
+ carries whatever bindings landed but the running handler has NOT yet
17776
+ picked them up. Re-run \`primitive functions set-secret\` for the
17777
+ missing keys, then re-run \`primitive functions deploy\` (or
17778
+ \`functions redeploy\`) to push them live. ${SECRET_SOURCE_FLAGS_DESCRIPTION}`;
17668
17779
  static summary = "Deploy a new function from a bundled handler file";
17669
17780
  static examples = [
17670
17781
  "<%= config.bin %> functions deploy --name forwarder --file ./bundle.js",
@@ -17674,6 +17785,7 @@ var FunctionsDeployCommand = class FunctionsDeployCommand extends Command {
17674
17785
  "<%= config.bin %> functions deploy --name forwarder --file ./bundle.js --source-map-file ./bundle.js.map",
17675
17786
  "<%= config.bin %> functions deploy --name forwarder --file ./bundle.js --secret OPENAI_KEY=sk-... --secret OWNER_EMAIL=me@example.com",
17676
17787
  "<%= config.bin %> functions deploy --name forwarder --file ./bundle.js --secret-from-env OPENAI_KEY --secret-from-env-file .env.local:OWNER_EMAIL",
17788
+ "<%= config.bin %> functions deploy --name triage --source . --secret-from-env ANTHROPIC_API_KEY",
17677
17789
  "printf '%s' \"$OPENAI_KEY\" | <%= config.bin %> functions deploy --name forwarder --file ./bundle.js --secret-from-stdin OPENAI_KEY"
17678
17790
  ];
17679
17791
  static flags = {
@@ -17863,8 +17975,15 @@ var FunctionsDeployCommand = class FunctionsDeployCommand extends Command {
17863
17975
  });
17864
17976
  }
17865
17977
  async runSourceMode(flags, sourceDir) {
17866
- if ((flags.secret?.length ?? 0) > 0 || (flags["secret-from-env"]?.length ?? 0) > 0 || (flags["secret-from-file"]?.length ?? 0) > 0 || (flags["secret-from-env-file"]?.length ?? 0) > 0 || flags["secret-from-stdin"] !== void 0) {
17867
- process.stderr.write("Secret flags are not supported with --source yet. Deploy from source first, then set secrets with `primitive functions set-secret` and redeploy.\n");
17978
+ const parsedSecrets = resolveSecretFlags({
17979
+ fromEnv: flags["secret-from-env"] ?? [],
17980
+ fromEnvFile: flags["secret-from-env-file"] ?? [],
17981
+ fromFile: flags["secret-from-file"] ?? [],
17982
+ fromStdin: flags["secret-from-stdin"],
17983
+ inline: flags.secret ?? []
17984
+ });
17985
+ if (parsedSecrets.kind === "error") {
17986
+ process.stderr.write(`${parsedSecrets.message}\n`);
17868
17987
  process.exitCode = 1;
17869
17988
  return;
17870
17989
  }
@@ -17884,7 +18003,7 @@ var FunctionsDeployCommand = class FunctionsDeployCommand extends Command {
17884
18003
  baseUrlOverridden,
17885
18004
  configDir: this.config.configDir
17886
18005
  };
17887
- const outcome = await runSourceDeploy({
18006
+ const apiSurface = {
17888
18007
  createFunction: (p) => createFunction({
17889
18008
  body: {
17890
18009
  files: p.files,
@@ -17897,27 +18016,80 @@ var FunctionsDeployCommand = class FunctionsDeployCommand extends Command {
17897
18016
  client: apiClient.client,
17898
18017
  responseStyle: "fields"
17899
18018
  }),
18019
+ setSecret: (p) => setFunctionSecret({
18020
+ body: { value: p.value },
18021
+ client: apiClient.client,
18022
+ path: {
18023
+ id: p.id,
18024
+ key: p.key
18025
+ },
18026
+ responseStyle: "fields"
18027
+ }),
17900
18028
  updateFunction: (p) => updateFunction({
17901
18029
  body: { files: p.files },
17902
18030
  client: apiClient.client,
17903
18031
  path: { id: p.id },
17904
18032
  responseStyle: "fields"
17905
18033
  })
17906
- }, {
18034
+ };
18035
+ if (parsedSecrets.secrets.length === 0) {
18036
+ const outcome = await runSourceDeploy(apiSurface, {
18037
+ files: collected.files,
18038
+ name: flags.name
18039
+ });
18040
+ if (outcome.kind === "error") {
18041
+ renderBuildFailure(outcome.payload, (chunk) => process.stderr.write(chunk));
18042
+ writeErrorWithHints(outcome.payload);
18043
+ surfaceUnauthorizedHint({
18044
+ ...authFailureContext,
18045
+ payload: outcome.payload
18046
+ });
18047
+ process.exitCode = 1;
18048
+ return;
18049
+ }
18050
+ await this.finishSourceDeploy({
18051
+ apiClient,
18052
+ authFailureContext,
18053
+ flags,
18054
+ payload: outcome.result
18055
+ });
18056
+ return;
18057
+ }
18058
+ const secretsOutcome = await runSourceDeployWithSecrets(apiSurface, {
17907
18059
  files: collected.files,
17908
- name: flags.name
18060
+ name: flags.name,
18061
+ secrets: parsedSecrets.secrets
17909
18062
  });
17910
- if (outcome.kind === "error") {
17911
- renderBuildFailure(outcome.payload, (chunk) => process.stderr.write(chunk));
17912
- writeErrorWithHints(outcome.payload);
18063
+ if (secretsOutcome.kind === "error") {
18064
+ if (secretsOutcome.stage === "set-secret") {
18065
+ const succeeded = secretsOutcome.succeededKeys.length > 0 ? secretsOutcome.succeededKeys.join(", ") : "(none)";
18066
+ const pending = secretsOutcome.pendingKeys.length > 0 ? secretsOutcome.pendingKeys.join(", ") : "(none)";
18067
+ const allMissing = [secretsOutcome.failedKey, ...secretsOutcome.pendingKeys].join(", ");
18068
+ const createdClause = secretsOutcome.created ? `Function ${secretsOutcome.created.name} (${secretsOutcome.functionId}) was created` : `Function ${flags.name} (${secretsOutcome.functionId}) already existed`;
18069
+ const stagingWarning = secretsOutcome.succeededKeys.length > 0 ? ` Note: [${succeeded}] are now staged on the function row and will bind on the next deploy of this function (including one that does not pass --secret).` : "";
18070
+ process.stderr.write(`${createdClause}, but writing secret ${secretsOutcome.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 deploy --source ${sourceDir} --name ${flags.name}\` to push them live.${stagingWarning}\n`);
18071
+ } else if (secretsOutcome.stage === "secret-redeploy") {
18072
+ const succeeded = secretsOutcome.succeededKeys.length > 0 ? secretsOutcome.succeededKeys.join(", ") : "(none)";
18073
+ const createdClause = secretsOutcome.created ? `Function ${secretsOutcome.created.name} (${secretsOutcome.functionId}) was created and` : `Function ${flags.name} (${secretsOutcome.functionId}) already existed and`;
18074
+ process.stderr.write(`${createdClause} secrets [${succeeded}] were written, but the final redeploy failed; the new bindings are NOT yet live. Re-run \`primitive functions deploy --source ${sourceDir} --name ${flags.name}\` once the cause is fixed.\n`);
18075
+ } else renderBuildFailure(secretsOutcome.payload, (chunk) => process.stderr.write(chunk));
18076
+ writeErrorWithHints(secretsOutcome.payload);
17913
18077
  surfaceUnauthorizedHint({
17914
18078
  ...authFailureContext,
17915
- payload: outcome.payload
18079
+ payload: secretsOutcome.payload
17916
18080
  });
17917
18081
  process.exitCode = 1;
17918
18082
  return;
17919
18083
  }
17920
- const payload = outcome.result;
18084
+ await this.finishSourceDeploy({
18085
+ apiClient,
18086
+ authFailureContext,
18087
+ flags,
18088
+ payload: secretsOutcome.result.redeploy
18089
+ });
18090
+ }
18091
+ async finishSourceDeploy(args) {
18092
+ const { apiClient, authFailureContext, flags, payload } = args;
17921
18093
  if (flags.wait) {
17922
18094
  const waitResult = await waitForFunctionDeploy({
17923
18095
  getFunction: (p) => getFunction({
@@ -17966,8 +18138,8 @@ const PRIMITIVE_TEAM_AUTHOR = {
17966
18138
  name: "Primitive Team",
17967
18139
  url: "https://primitive.dev"
17968
18140
  };
17969
- const SDK_VERSION_RANGE = "^1.1.0";
17970
- const CLI_VERSION_RANGE = "^1.1.0";
18141
+ const SDK_VERSION_RANGE = "^1.2.0";
18142
+ const CLI_VERSION_RANGE = "^1.2.0";
17971
18143
  const ESBUILD_VERSION_RANGE = "^0.27.0";
17972
18144
  function renderHandler() {
17973
18145
  return `// env.PRIMITIVE_API_KEY, env.PRIMITIVE_WEBHOOK_SECRET, and
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@primitivedotdev/cli",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "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.",
5
5
  "type": "module",
6
6
  "sideEffects": false,