@eide/foir-cli 0.11.2 → 0.12.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/cli.js CHANGED
@@ -848,7 +848,16 @@ function createIdentityMethods(client) {
848
848
  create(UpdateProjectRequestSchema, {
849
849
  id: params.id,
850
850
  name: params.name,
851
- description: params.description
851
+ description: params.description,
852
+ displayName: params.displayName,
853
+ logoUrl: params.logoUrl,
854
+ primaryColor: params.primaryColor,
855
+ fromName: params.fromName,
856
+ replyTo: params.replyTo,
857
+ supportEmail: params.supportEmail,
858
+ customerPortalBaseUrl: params.customerPortalBaseUrl,
859
+ customerWelcomeEmailEnabled: params.customerWelcomeEmailEnabled,
860
+ customerSignupEnabled: params.customerSignupEnabled
852
861
  })
853
862
  );
854
863
  return { project: resp.project ?? null };
@@ -3478,9 +3487,12 @@ function registerSelectProjectCommand(program2, globalOpts) {
3478
3487
  selectedProject = projects.find((p) => p.id === projectId);
3479
3488
  }
3480
3489
  console.log("\nProvisioning API key for CLI access...");
3481
- await client.identity.switchTenant(selectedProject.tenantId);
3482
- await client.identity.switchProject(selectedProject.id);
3483
- const { apiKey, apiKeyId } = await provisionApiKey(client);
3490
+ const scopedClient = createPlatformClientWithHeaders(apiUrl, {
3491
+ Authorization: `Bearer ${credentials.accessToken}`,
3492
+ "x-tenant-id": selectedProject.tenantId,
3493
+ "x-project-id": selectedProject.id
3494
+ });
3495
+ const { apiKey, apiKeyId } = await provisionApiKey(scopedClient);
3484
3496
  await writeProjectContext(
3485
3497
  {
3486
3498
  id: selectedProject.id,
@@ -5693,8 +5705,24 @@ function registerPushCommand(program2, globalOpts) {
5693
5705
  );
5694
5706
  }
5695
5707
  const gOpts = globalOpts();
5696
- const client = await createPlatformClient(gOpts);
5697
5708
  const resolved = await resolveProjectContext(gOpts);
5709
+ const projectBlock = config2.project;
5710
+ if (projectBlock) {
5711
+ if (!resolved) {
5712
+ throw new Error(
5713
+ `foir.config.ts is pinned to project "${projectBlock.name ?? projectBlock.id}" (${projectBlock.id}), but no project context is selected. Run \`foir select-project\` first.`
5714
+ );
5715
+ }
5716
+ if (resolved.project.id !== projectBlock.id || resolved.project.tenantId !== projectBlock.tenantId) {
5717
+ throw new Error(
5718
+ `Project mismatch \u2014 refusing to push.
5719
+ foir.config.ts pin: ${projectBlock.name ?? projectBlock.id} (${projectBlock.id})
5720
+ resolved (${resolved.source}): ${resolved.project.name} (${resolved.project.id})
5721
+ Hand-edit foir.config.ts or run \`foir select-project\` to align them.`
5722
+ );
5723
+ }
5724
+ }
5725
+ const client = await createPlatformClient(gOpts);
5698
5726
  console.log(
5699
5727
  chalk6.dim(`Pushing config "${config2.key}" to platform...`)
5700
5728
  );
@@ -5747,6 +5775,34 @@ function registerPushCommand(program2, globalOpts) {
5747
5775
  console.log(` Config Key: ${chalk6.cyan(config2.key)}`);
5748
5776
  console.log();
5749
5777
  printSummary(summary);
5778
+ if (projectBlock?.settings && resolved) {
5779
+ const s = projectBlock.settings;
5780
+ await client.identity.updateProject({
5781
+ id: resolved.project.id,
5782
+ displayName: s.displayName,
5783
+ logoUrl: s.logoUrl,
5784
+ primaryColor: s.primaryColor,
5785
+ fromName: s.fromName,
5786
+ replyTo: s.replyTo,
5787
+ supportEmail: s.supportEmail,
5788
+ customerPortalBaseUrl: s.customerPortalBaseUrl,
5789
+ customerWelcomeEmailEnabled: s.customerWelcomeEmailEnabled,
5790
+ customerSignupEnabled: s.customerSignupEnabled
5791
+ });
5792
+ console.log(chalk6.dim(" Project settings: applied"));
5793
+ }
5794
+ if (!projectBlock && resolved) {
5795
+ console.log();
5796
+ console.log(chalk6.dim("Tip: pin this folder to its project by adding the following to foir.config.ts:"));
5797
+ console.log(chalk6.dim(" project: {"));
5798
+ console.log(chalk6.dim(` id: '${resolved.project.id}',`));
5799
+ console.log(chalk6.dim(` tenantId: '${resolved.project.tenantId}',`));
5800
+ if (resolved.project.name) {
5801
+ console.log(chalk6.dim(` name: '${resolved.project.name}',`));
5802
+ }
5803
+ console.log(chalk6.dim(" },"));
5804
+ console.log(chalk6.dim("Future pushes will refuse to run if the resolved project drifts from this id."));
5805
+ }
5750
5806
  const envPath = resolve4(opts.env ?? ".env");
5751
5807
  const envWrites = [];
5752
5808
  for (const pk of summary.apiKeys) {
@@ -5846,6 +5902,7 @@ function registerPullCommand(program2, globalOpts) {
5846
5902
  }
5847
5903
  const resolved = await resolveProjectContext(globalOpts());
5848
5904
  let apps;
5905
+ let projectBlock;
5849
5906
  if (resolved) {
5850
5907
  const projectId = resolved.project.id;
5851
5908
  const tenantId = resolved.project.tenantId;
@@ -5856,9 +5913,18 @@ function registerPullCommand(program2, globalOpts) {
5856
5913
  apps[a.name] = appToInput(a);
5857
5914
  }
5858
5915
  }
5916
+ const { project } = await client.identity.getProject(projectId);
5917
+ projectBlock = {
5918
+ id: projectId,
5919
+ tenantId,
5920
+ ...project?.name ? { name: project.name } : {},
5921
+ settings: projectSettingsFromGetProject(project)
5922
+ };
5859
5923
  }
5860
5924
  const configDataNoInteg = { ...configData };
5861
5925
  delete configDataNoInteg.apps;
5926
+ const configDataNoProject = { ...configDataNoInteg };
5927
+ delete configDataNoProject.project;
5862
5928
  const manifest = {
5863
5929
  key: config2.key,
5864
5930
  name: config2.name,
@@ -5866,7 +5932,8 @@ function registerPullCommand(program2, globalOpts) {
5866
5932
  ...config2.direction ? { direction: config2.direction } : {},
5867
5933
  ...config2.description ? { description: config2.description } : {},
5868
5934
  ...config2.connectionDomain ? { operationBaseUrl: config2.connectionDomain } : {},
5869
- ...configDataNoInteg,
5935
+ ...projectBlock ? { project: projectBlock } : {},
5936
+ ...configDataNoProject,
5870
5937
  ...apps ? { apps } : {}
5871
5938
  };
5872
5939
  delete manifest.force;
@@ -5957,6 +6024,24 @@ export default defineConfig(${jsonContent});
5957
6024
  )
5958
6025
  );
5959
6026
  }
6027
+ function projectSettingsFromGetProject(p) {
6028
+ if (!p) return void 0;
6029
+ const out = {};
6030
+ if (p.displayName) out.displayName = p.displayName;
6031
+ if (p.logoUrl) out.logoUrl = p.logoUrl;
6032
+ if (p.primaryColor) out.primaryColor = p.primaryColor;
6033
+ if (p.fromName) out.fromName = p.fromName;
6034
+ if (p.replyTo) out.replyTo = p.replyTo;
6035
+ if (p.supportEmail) out.supportEmail = p.supportEmail;
6036
+ if (p.customerPortalBaseUrl) out.customerPortalBaseUrl = p.customerPortalBaseUrl;
6037
+ if (typeof p.customerWelcomeEmailEnabled === "boolean") {
6038
+ out.customerWelcomeEmailEnabled = p.customerWelcomeEmailEnabled;
6039
+ }
6040
+ if (typeof p.customerSignupEnabled === "boolean") {
6041
+ out.customerSignupEnabled = p.customerSignupEnabled;
6042
+ }
6043
+ return Object.keys(out).length > 0 ? out : void 0;
6044
+ }
5960
6045
  function appToInput(a) {
5961
6046
  const mappingsBlob = a.mappings ?? {};
5962
6047
  const out = { source: a.manifestUrl };
@@ -252,6 +252,62 @@ interface AppInput {
252
252
  placementFields?: Record<string, AppPlacementFieldChoiceInput>;
253
253
  };
254
254
  }
255
+ /**
256
+ * Project-level settings applied via UpdateProject at push time and
257
+ * read via GetProject at pull time. Branding fields are empty-string-as-
258
+ * clear on the platform side: `''` clears the column, `undefined` leaves
259
+ * it untouched.
260
+ */
261
+ interface ApplyConfigProjectSettingsInput {
262
+ /** Display name in customer-facing emails / portal headers. */
263
+ displayName?: string;
264
+ /** Public URL of the project logo (rendered in customer emails). */
265
+ logoUrl?: string;
266
+ /** Hex color used as accent in customer-facing emails. */
267
+ primaryColor?: string;
268
+ /** "From" name on outbound customer emails. */
269
+ fromName?: string;
270
+ /** "Reply-To" address on outbound customer emails. */
271
+ replyTo?: string;
272
+ /** Support email surfaced in email footers. */
273
+ supportEmail?: string;
274
+ /**
275
+ * Base URL of the customer-facing portal. Reset / verify / invitation
276
+ * links in customer emails are built from this — required before the
277
+ * platform will mint customer-portal links.
278
+ */
279
+ customerPortalBaseUrl?: string;
280
+ /**
281
+ * Whether the platform sends a welcome email on customer registration.
282
+ * Default true. Disable for invite-style flows.
283
+ */
284
+ customerWelcomeEmailEnabled?: boolean;
285
+ /**
286
+ * Whether the public CustomerRegister mutation is allowed. Default true.
287
+ * When false, customers can only be created via secret-key admin paths
288
+ * (CreateCustomer, CreateCustomerInvitation).
289
+ */
290
+ customerSignupEnabled?: boolean;
291
+ }
292
+ /**
293
+ * Project pin + declarative settings. The pin (id / tenantId) is a guard
294
+ * against pushing one folder's config into another folder's project; the
295
+ * settings are applied to the platform via UpdateProject.
296
+ */
297
+ interface ApplyConfigProjectInput {
298
+ /** Project id this folder is bound to. */
299
+ id: string;
300
+ /** Tenant id of the bound project. */
301
+ tenantId: string;
302
+ /** Optional human-friendly name for diff readability. */
303
+ name?: string;
304
+ /**
305
+ * Project-level settings reconciled by `foir push`. Omit to leave the
306
+ * platform settings untouched. `foir pull` populates this block from
307
+ * the live project state so the local config can be version-controlled.
308
+ */
309
+ settings?: ApplyConfigProjectSettingsInput;
310
+ }
255
311
  interface ApplyConfigInput {
256
312
  key: string;
257
313
  name: string;
@@ -259,6 +315,12 @@ interface ApplyConfigInput {
259
315
  force?: boolean;
260
316
  /** Base URL prepended to relative operation endpoints. */
261
317
  operationBaseUrl?: string;
318
+ /**
319
+ * Project pin + declarative project-level settings. The pin guards
320
+ * against cross-folder mis-pushes; the settings (when present) are
321
+ * applied to the platform alongside the model / operation reconcile.
322
+ */
323
+ project?: ApplyConfigProjectInput;
262
324
  models?: ApplyConfigModelInput[];
263
325
  operations?: ApplyConfigOperationInput[];
264
326
  segments?: ApplyConfigSegmentInput[];
@@ -292,4 +354,4 @@ declare function defineHook(hook: ApplyConfigHookInput): ApplyConfigHookInput;
292
354
  /** Define an editor placement (sidebar or main-editor tab). */
293
355
  declare function definePlacement(placement: ApplyConfigPlacementInput): ApplyConfigPlacementInput;
294
356
 
295
- export { type AppInput, type AppPlacementFieldChoiceInput, type AppSinkMappingInput, type AppSourceMappingInput, type ApplyConfigApiKeyInput, type ApplyConfigAuthProviderInput, type ApplyConfigHookInput, type ApplyConfigInput, type ApplyConfigModelInput, type ApplyConfigOperationInput, type ApplyConfigPlacementInput, type ApplyConfigScheduleInput, type ApplyConfigSegmentInput, type ExpressionPrecondition, type FieldDefinitionInput, type Precondition, type QuotaRule, type SegmentPrecondition, type SelectFieldConfig, type SelectFieldDefinitionInput, defineAuthProvider, defineConfig, defineField, defineHook, defineModel, defineOperation, definePlacement, defineSchedule, defineSegment, defineSelectField };
357
+ export { type AppInput, type AppPlacementFieldChoiceInput, type AppSinkMappingInput, type AppSourceMappingInput, type ApplyConfigApiKeyInput, type ApplyConfigAuthProviderInput, type ApplyConfigHookInput, type ApplyConfigInput, type ApplyConfigModelInput, type ApplyConfigOperationInput, type ApplyConfigPlacementInput, type ApplyConfigProjectInput, type ApplyConfigProjectSettingsInput, type ApplyConfigScheduleInput, type ApplyConfigSegmentInput, type ExpressionPrecondition, type FieldDefinitionInput, type Precondition, type QuotaRule, type SegmentPrecondition, type SelectFieldConfig, type SelectFieldDefinitionInput, defineAuthProvider, defineConfig, defineField, defineHook, defineModel, defineOperation, definePlacement, defineSchedule, defineSegment, defineSelectField };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eide/foir-cli",
3
- "version": "0.11.2",
3
+ "version": "0.12.0",
4
4
  "description": "Universal platform CLI for Foir platform",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -50,7 +50,7 @@
50
50
  "@bufbuild/protovalidate": "^1.1.1",
51
51
  "@connectrpc/connect": "^2.0.0",
52
52
  "@connectrpc/connect-node": "^2.0.0",
53
- "@eide/foir-proto-ts": "^0.29.1",
53
+ "@eide/foir-proto-ts": "^0.30.0",
54
54
  "chalk": "^5.3.0",
55
55
  "commander": "^12.1.0",
56
56
  "dotenv": "^16.4.5",