@stacksjs/buddy 0.70.211 → 0.70.213

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.
@@ -24,6 +24,14 @@ declare interface AppleDesktopOptions {
24
24
  skipBuild?: boolean
25
25
  validateOnly?: boolean
26
26
  packageOnly?: boolean
27
+ plan?: boolean
28
+ apply?: boolean
29
+ capabilities?: string
30
+ appCertificateCsr?: string
31
+ installerCertificateCsr?: string
32
+ profileName?: string
33
+ output?: string
34
+ commonName?: string
27
35
  }
28
36
  export declare interface AppleDesktopConfig {
29
37
  appName: string
@@ -150,11 +150,20 @@ export function renderAppleWorkflowCaller() {
150
150
  on:
151
151
  workflow_dispatch:
152
152
  inputs:
153
+ release-tag:
154
+ description: Git tag used for the GitHub release
155
+ required: true
156
+ type: string
153
157
  validate-only:
154
158
  description: Validate the signed package without uploading it
155
159
  required: false
156
160
  default: true
157
161
  type: boolean
162
+ mirror-s3:
163
+ description: Mirror artifacts to the configured S3-compatible release registry
164
+ required: false
165
+ default: false
166
+ type: boolean
158
167
 
159
168
  jobs:
160
169
  publish:
@@ -164,9 +173,18 @@ jobs:
164
173
  bundle-id: \${{ vars.APPLE_BUNDLE_ID }}
165
174
  team-id: \${{ vars.APPLE_TEAM_ID }}
166
175
  desktop-url: \${{ vars.DESKTOP_URL }}
176
+ app-version: \${{ vars.APPLE_APP_VERSION }}
167
177
  app-signing-identity: \${{ vars.APPLE_APP_SIGNING_IDENTITY }}
168
178
  installer-signing-identity: \${{ vars.APPLE_INSTALLER_SIGNING_IDENTITY }}
179
+ release-tag: \${{ inputs.release-tag }}
169
180
  validate-only: \${{ inputs.validate-only }}
181
+ mirror-s3: \${{ inputs.mirror-s3 }}
182
+ s3-provider: \${{ vars.RELEASE_S3_PROVIDER || 'hetzner' }}
183
+ s3-bucket: \${{ vars.RELEASE_S3_BUCKET }}
184
+ s3-region: \${{ vars.RELEASE_S3_REGION }}
185
+ s3-endpoint: \${{ vars.RELEASE_S3_ENDPOINT }}
186
+ s3-prefix: \${{ vars.RELEASE_S3_PREFIX }}
187
+ s3-public-url: \${{ vars.RELEASE_S3_PUBLIC_URL }}
170
188
  secrets: inherit
171
189
  `;
172
190
  }
@@ -324,6 +342,102 @@ function fail(error) {
324
342
  process.exit(1);
325
343
  }
326
344
  export function desktopApple(buddy) {
345
+ buddy.command("desktop:apple:csr", "Generate local private keys and CSRs for Mac App Store distribution certificates").option("--common-name <name>", "Certificate request common name").option("--output <path>", "Directory for private keys and certificate requests").action(async (options) => {
346
+ try {
347
+ const metadata = packageMetadata(), outputDirectory = resolve(options.output || storagePath("framework/desktop-dist/apple/provisioning")), { generateMacCertificateRequests } = await import("ts-pantry");
348
+ generateMacCertificateRequests({
349
+ outputDirectory,
350
+ commonName: options.commonName || metadata.name
351
+ });
352
+ log.success(`Generated Apple certificate requests and private keys in ${outputDirectory}`);
353
+ } catch (error) {
354
+ fail(error);
355
+ }
356
+ });
357
+ buddy.command("desktop:apple:provision", "Plan or reconcile Apple Bundle ID, capabilities, certificates, and profile").option("--app-name <name>", "Mac App Store display name").option("--bundle-id <id>", "Reverse-DNS bundle identifier").option("--api-key-id <id>", "App Store Connect API key ID").option("--api-issuer-id <id>", "App Store Connect API issuer ID").option("--api-key-path <path>", "App Store Connect AuthKey .p8 file").option("--capabilities <types>", "Comma-separated Apple capability types").option("--app-certificate-csr <path>", "CSR for a missing Mac App Distribution certificate").option("--installer-certificate-csr <path>", "CSR for a missing Mac Installer Distribution certificate").option("--profile-name <name>", "Provisioning profile name").option("--output <path>", "Directory for the plan and downloaded Apple assets").option("--plan", "Report the idempotent Apple resource diff without mutating it").option("--apply", "Apply the Apple resource diff without revoking existing certificates").action(async (options) => {
358
+ try {
359
+ if (options.plan && options.apply)
360
+ throw Error("Choose either --plan or --apply");
361
+ const config = resolveAppleDesktopConfig(options);
362
+ if (!config.bundleId)
363
+ throw Error("APPLE_BUNDLE_ID or --bundle-id is required");
364
+ if (!config.apiKeyId || !config.apiIssuerId || !config.apiKeyPath || !existsSync(config.apiKeyPath))
365
+ throw Error("App Store Connect API key ID, issuer ID, and existing .p8 key path are required");
366
+ const readCsr = (file) => {
367
+ if (!file)
368
+ return;
369
+ const resolved = resolve(file);
370
+ if (!existsSync(resolved))
371
+ throw Error(`Apple certificate CSR does not exist: ${resolved}`);
372
+ return readFileSync(resolved, "utf8");
373
+ }, { exportAppleCertificateP12, provisionMacApp } = await import("ts-pantry"), result = await provisionMacApp({
374
+ identifier: config.bundleId,
375
+ name: config.appName,
376
+ capabilities: options.capabilities?.split(",").map((value) => value.trim()).filter(Boolean),
377
+ appCertificateCsr: readCsr(options.appCertificateCsr),
378
+ installerCertificateCsr: readCsr(options.installerCertificateCsr),
379
+ profileName: options.profileName,
380
+ keyId: config.apiKeyId,
381
+ issuerId: config.apiIssuerId,
382
+ keyPath: config.apiKeyPath,
383
+ checkOnly: !options.apply
384
+ }), outputDirectory = resolve(options.output || storagePath("framework/desktop-dist/apple/provisioning"));
385
+ mkdirSync(outputDirectory, { recursive: !0 });
386
+ for (const certificate of result.certificates) {
387
+ if (!certificate.certificateContent)
388
+ continue;
389
+ const fileName = certificate.type === "MAC_APP_DISTRIBUTION" ? "mac-app-distribution.cer" : "mac-installer-distribution.cer";
390
+ writeFileSync(join(outputDirectory, fileName), Buffer.from(certificate.certificateContent, "base64"), { mode: 384 });
391
+ }
392
+ if (result.profile.profileContent)
393
+ writeFileSync(join(outputDirectory, "mac-app-store.provisionprofile"), Buffer.from(result.profile.profileContent, "base64"), { mode: 384 });
394
+ const certificatePassword = env("APPLE_CERTIFICATE_PASSWORD"), certificateExports = [
395
+ {
396
+ type: "MAC_APP_DISTRIBUTION",
397
+ csr: options.appCertificateCsr,
398
+ certificate: "mac-app-distribution.cer",
399
+ output: "mac-app-distribution.p12",
400
+ name: `${config.appName} Mac App Distribution`
401
+ },
402
+ {
403
+ type: "MAC_INSTALLER_DISTRIBUTION",
404
+ csr: options.installerCertificateCsr,
405
+ certificate: "mac-installer-distribution.cer",
406
+ output: "mac-installer-distribution.p12",
407
+ name: `${config.appName} Mac Installer Distribution`
408
+ }
409
+ ];
410
+ for (const certificateExport of certificateExports) {
411
+ if (!result.certificates.find((item) => item.type === certificateExport.type)?.certificateContent || !certificateExport.csr || !certificatePassword)
412
+ continue;
413
+ exportAppleCertificateP12({
414
+ certificatePath: join(outputDirectory, certificateExport.certificate),
415
+ privateKeyPath: resolve(certificateExport.csr.replace(/\.csr$/i, ".key")),
416
+ outputPath: join(outputDirectory, certificateExport.output),
417
+ password: certificatePassword,
418
+ name: certificateExport.name
419
+ });
420
+ }
421
+ const certificates = result.certificates.map((item) => {
422
+ const certificate = { ...item };
423
+ delete certificate.certificateContent;
424
+ return certificate;
425
+ }), profile = { ...result.profile };
426
+ delete profile.profileContent;
427
+ const report = {
428
+ ...result,
429
+ certificates,
430
+ profile
431
+ }, reportPath = join(outputDirectory, "provisioning-plan.json");
432
+ writeFileSync(reportPath, `${JSON.stringify(report, null, 2)}
433
+ `, { mode: 384 });
434
+ log.success(`${options.apply ? "Applied" : "Planned"} Apple provisioning: ${reportPath}`);
435
+ if (!result.appRecord.exists)
436
+ log.warn(result.appRecord.manualAction);
437
+ } catch (error) {
438
+ fail(error);
439
+ }
440
+ });
327
441
  buddy.command("desktop:apple:doctor", "Validate Mac App Store tooling, credentials, certificates, and project metadata").option("--app-name <name>", "Mac App Store display name").option("--bundle-id <id>", "Reverse-DNS bundle identifier").option("--team-id <id>", "Apple Developer team ID").option("--app-signing-identity <identity>", "Mac App Distribution signing identity").option("--installer-signing-identity <identity>", "Mac Installer Distribution signing identity").option("--provisioning-profile <path>", "Mac App Store provisioning profile").option("--api-key-id <id>", "App Store Connect API key ID").option("--api-issuer-id <id>", "App Store Connect API issuer ID").option("--api-key-path <path>", "App Store Connect AuthKey .p8 file").action((options) => {
328
442
  try {
329
443
  const config = resolveAppleDesktopConfig(options), errors = validateAppleDesktopConfig(config, !0);
@@ -27,7 +27,7 @@ describe("desktop lifecycle workflow", () => {
27
27
  it("retains artifacts through maintained Node 24 action runtimes", () => {
28
28
  expect(repositoryWorkflows).not.toMatch(/actions\/upload-artifact@v[1-6]\b/);
29
29
  expect(repositoryWorkflows).not.toMatch(/actions\/download-artifact@v[1-7]\b/);
30
- expect(repositoryWorkflows.match(/actions\/upload-artifact@v7\b/g)).toHaveLength(3);
30
+ expect(repositoryWorkflows.match(/actions\/upload-artifact@v7\b/g)).toHaveLength(4);
31
31
  expect(repositoryWorkflows.match(/actions\/download-artifact@v8\b/g)).toHaveLength(1);
32
32
  });
33
33
  });
@@ -17,8 +17,10 @@ const commandRegistry = {
17
17
  deploy: { path: "./commands/deploy.js", exportName: "deploy" },
18
18
  dev: { path: "./commands/dev.js", exportName: "dev" },
19
19
  "desktop:apple:doctor": { path: "./commands/desktop-apple.js", exportName: "desktopApple" },
20
+ "desktop:apple:csr": { path: "./commands/desktop-apple.js", exportName: "desktopApple" },
20
21
  "desktop:apple:init": { path: "./commands/desktop-apple.js", exportName: "desktopApple" },
21
22
  "desktop:apple:package": { path: "./commands/desktop-apple.js", exportName: "desktopApple" },
23
+ "desktop:apple:provision": { path: "./commands/desktop-apple.js", exportName: "desktopApple" },
22
24
  "desktop:apple:publish": { path: "./commands/desktop-apple.js", exportName: "desktopApple" },
23
25
  dns: { path: "./commands/dns.js", exportName: "dns" },
24
26
  "dns:pull": { path: "./commands/dns.js", exportName: "dns" },
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@stacksjs/buddy",
3
3
  "type": "module",
4
4
  "sideEffects": false,
5
- "version": "0.70.211",
5
+ "version": "0.70.213",
6
6
  "description": "Meet Buddy. The Stacks runtime.",
7
7
  "author": "Chris Breuer",
8
8
  "contributors": [
@@ -139,7 +139,8 @@
139
139
  "@stacksjs/validation": "^0.70.180",
140
140
  "@stacksjs/ts-cloud": "^0.7.86",
141
141
  "ajv": "^8.20.0",
142
- "ajv-formats": "^3.0.1"
142
+ "ajv-formats": "^3.0.1",
143
+ "ts-pantry": "^0.11.0"
143
144
  },
144
145
  "devDependencies": {
145
146
  "better-dx": "^0.2.17"