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
5 changes: 5 additions & 0 deletions .changeset/setup-github-renovate-actions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@tailor-platform/sdk": patch
---

chore(setup github): bump bundled `tailor-platform/actions` ref to v1.1.0 and let Renovate keep it up to date going forward.
1 change: 1 addition & 0 deletions packages/sdk/docs/cli/setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ tailor-sdk setup github [options]
| `--organization-id <ORGANIZATION_ID>` | `-o` | Organization ID | Yes | - |
| `--folder-id <FOLDER_ID>` | `-f` | Folder ID | Yes | - |
| `--dir <DIR>` | `-d` | App directory (for monorepo setups) | No | `"."` |
| `--with-plan` | `-p` | Include plan job for PR previews | No | `false` |

<!-- politty:command:setup github:options:end -->

Expand Down
32 changes: 28 additions & 4 deletions packages/sdk/src/cli/commands/setup/github/deploy.workflow.yml
Original file line number Diff line number Diff line change
@@ -1,24 +1,48 @@
name: Deploy
name: Tailor Platform

on:
pull_request:
branches:
- main
push:
branches:
- main
workflow_dispatch:

concurrency:
group: deploy-__WORKSPACE_NAME__
cancel-in-progress: false
group: tailor-__WORKSPACE_NAME__-${{ github.head_ref || github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

jobs:
# __PLAN_JOB_START__
plan:
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
# __SETUP_STEPS__
- uses: tailor-platform/actions/plan@e63ed98630a23fa21ee0636abf0f7fb75fcdce40 # v1.1.0
with:
workspace-id: ${{ vars.TAILOR_PLATFORM_WORKSPACE_ID }}
# __WORKING_DIRECTORY__
platform-client-id: ${{ secrets.PLATFORM_MACHINE_USER_CLIENT_ID }}
platform-client-secret: ${{ secrets.PLATFORM_MACHINE_USER_CLIENT_SECRET }}
github-token: ${{ secrets.GITHUB_TOKEN }}
# __PLAN_JOB_END__
deploy:
if: github.event_name != 'pull_request'
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
# __SETUP_STEPS__
- uses: tailor-platform/actions/deploy@980aeba08963f4322b2b48ca7a920f4e14876842 # v1.0.0
- uses: tailor-platform/actions/deploy@e63ed98630a23fa21ee0636abf0f7fb75fcdce40 # v1.1.0
with:
workspace-name: __WORKSPACE_NAME__
workspace-region: __WORKSPACE_REGION__
Expand Down
45 changes: 39 additions & 6 deletions packages/sdk/src/cli/commands/setup/github/github.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,7 @@ describe("renderDeploy", () => {

it("references the composite action", () => {
const content = renderDeploy(baseParams);
expect(content).toContain(
"uses: tailor-platform/actions/deploy@980aeba08963f4322b2b48ca7a920f4e14876842 # v1.0.0",
);
expect(content).toMatch(/uses: tailor-platform\/actions\/deploy@[a-f0-9]+ # v\d+\.\d+\.\d+/);
});

it("includes setup steps in correct order", () => {
Expand All @@ -82,6 +80,7 @@ describe("renderDeploy", () => {
expect(content).toMatch(/uses: actions\/checkout@[a-f0-9]+ # v\d+\.\d+\.\d+/);
expect(content).toMatch(/uses: pnpm\/action-setup@[a-f0-9]+ # v\d+\.\d+\.\d+/);
expect(content).toMatch(/uses: actions\/setup-node@[a-f0-9]+ # v\d+\.\d+\.\d+/);
expect(content).toMatch(/uses: tailor-platform\/actions\/deploy@[a-f0-9]+ # v\d+\.\d+\.\d+/);
});

it("generates pnpm setup steps", () => {
Expand Down Expand Up @@ -151,8 +150,42 @@ describe("renderDeploy", () => {

it("parameterizes concurrency group with workspace name", () => {
const content = renderDeploy(baseParams);
expect(content).toContain("group: deploy-my-app");
expect(content).not.toContain("group: deploy\n");
expect(content).toContain("group: tailor-my-app-");
});

it("strips plan job by default", () => {
const content = renderDeploy(baseParams);
expect(content).not.toContain("tailor-platform/actions/plan@");
expect(content).not.toContain("TAILOR_PLATFORM_WORKSPACE_ID");
expect(content).not.toContain("__PLAN_JOB_START__");
expect(content).not.toContain("__PLAN_JOB_END__");
expect(content).toContain("tailor-platform/actions/deploy@");
});

it("includes plan job when withPlan is true", () => {
const content = renderDeploy({ ...baseParams, withPlan: true });
expect(content).toContain("tailor-platform/actions/plan@");
expect(content).toContain("workspace-id: ${{ vars.TAILOR_PLATFORM_WORKSPACE_ID }}");
expect(content).toContain("github-token: ${{ secrets.GITHUB_TOKEN }}");
expect(content).toContain("tailor-platform/actions/deploy@");
expect(content).not.toContain("__PLAN_JOB_START__");
expect(content).not.toContain("__PLAN_JOB_END__");
});

it("includes setup steps in both plan and deploy jobs when withPlan is true", () => {
const content = renderDeploy({ ...baseParams, withPlan: true });
const matches = content.match(/uses: pnpm\/action-setup@/g) ?? [];
expect(matches).toHaveLength(2);
});

it("propagates working-directory to plan job when withPlan is true", () => {
const content = renderDeploy({
...baseParams,
withPlan: true,
workingDirectory: "apps/foo",
});
const matches = content.match(/working-directory: apps\/foo/g) ?? [];
expect(matches).toHaveLength(2);
});
});

Expand All @@ -178,7 +211,7 @@ describe("buildFiles", () => {
outputDir: testDir,
});
expect(files).toHaveLength(1);
expect(files[0]!.path).toBe(path.join(testDir, ".github/workflows/deploy-my-app.yml"));
expect(files[0]!.path).toBe(path.join(testDir, ".github/workflows/tailor-my-app.yml"));
});

it("detects package manager from project directory", () => {
Expand Down
17 changes: 14 additions & 3 deletions packages/sdk/src/cli/commands/setup/github/github.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export type SetupGitHubOptions = {
folderId: string;
dir: string;
outputDir: string;
withPlan?: boolean;
};

type GeneratedFile = {
Expand All @@ -30,16 +31,20 @@ type WriteResult = {
*/
export function buildFiles(options: SetupGitHubOptions): GeneratedFile[] {
const githubDir = path.join(options.outputDir, ".github");
const packageManager = detectPackageManager(options.outputDir);
const workingDirectory = options.dir !== "." ? options.dir : undefined;

return [
{
path: path.join(githubDir, `workflows/deploy-${options.workspaceName}.yml`),
path: path.join(githubDir, `workflows/tailor-${options.workspaceName}.yml`),
content: renderDeploy({
workspaceName: options.workspaceName,
workspaceRegion: options.workspaceRegion,
organizationId: options.organizationId,
folderId: options.folderId,
workingDirectory: options.dir !== "." ? options.dir : undefined,
packageManager: detectPackageManager(options.outputDir),
workingDirectory,
packageManager,
withPlan: options.withPlan,
}),
},
];
Expand Down Expand Up @@ -91,4 +96,10 @@ export function setupGitHub(options: SetupGitHubOptions): void {
logger.info("Next steps - set GitHub secrets:");
logger.log(` gh secret set PLATFORM_MACHINE_USER_CLIENT_ID`);
logger.log(` gh secret set PLATFORM_MACHINE_USER_CLIENT_SECRET`);

if (options.withPlan) {
logger.newline();
logger.info("For plan job - set GitHub variable with your workspace ID:");
logger.log(` gh variable set TAILOR_PLATFORM_WORKSPACE_ID`);
}
}
5 changes: 5 additions & 0 deletions packages/sdk/src/cli/commands/setup/github/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ export const githubCommand = defineAppCommand({
alias: "d",
description: "App directory (for monorepo setups)",
}),
"with-plan": arg(z.boolean().default(false), {
alias: "p",
description: "Include plan job for PR previews",
}),
})
.strict(),
run: (args) => {
Expand All @@ -40,6 +44,7 @@ export const githubCommand = defineAppCommand({
folderId: args["folder-id"],
dir: args.dir,
outputDir: process.cwd(),
withPlan: args["with-plan"],
});
},
});
21 changes: 16 additions & 5 deletions packages/sdk/src/cli/commands/setup/github/template-deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ type DeployParams = {
folderId: string;
workingDirectory?: string;
packageManager: PackageManager;
withPlan?: boolean;
};

const setupSteps: Record<PackageManager, string> = {
Expand Down Expand Up @@ -48,12 +49,16 @@ export function detectPackageManager(dir: string): PackageManager {
}

/**
* Render the deploy caller workflow YAML.
* Render the deploy workflow YAML.
*
* Generates a thin workflow that calls the composite deploy action
* Generates a workflow that calls the composite deploy action
* from tailor-platform/actions. The environment setup steps (Node.js,
* package manager, dependency install) are generated based on the
* detected package manager.
*
* If withPlan is true, also includes a plan job that runs on pull requests.
* Otherwise, the plan job section delimited by __PLAN_JOB_START__ /
* __PLAN_JOB_END__ markers is stripped from the template.
* @param params - Workspace and deployment configuration
* @returns Workflow YAML content
*/
Expand All @@ -65,17 +70,23 @@ export function renderDeploy(params: DeployParams): string {
folderId,
workingDirectory,
packageManager,
withPlan,
} = params;

const workingDirectoryLine = workingDirectory
? ` working-directory: ${workingDirectory}\n`
: "";

return deployTemplate
const stripPlanSection = (content: string): string =>
withPlan
? content.replace(/^ *# __PLAN_JOB_(?:START|END)__\n/gm, "")
: content.replace(/^ *# __PLAN_JOB_START__\n[\s\S]*?^ *# __PLAN_JOB_END__\n/m, "");

return stripPlanSection(deployTemplate)
.replaceAll("__WORKSPACE_NAME__", () => workspaceName)
.replaceAll("__WORKSPACE_REGION__", () => workspaceRegion)
.replaceAll("__ORGANIZATION_ID__", () => organizationId)
.replaceAll("__FOLDER_ID__", () => folderId)
.replace(/ *# __WORKING_DIRECTORY__\n/, () => workingDirectoryLine)
.replace(/^ *# __SETUP_STEPS__$/m, () => indentSnippet(setupSteps[packageManager], 6));
.replace(/ *# __WORKING_DIRECTORY__\n/g, () => workingDirectoryLine)
.replace(/^ *# __SETUP_STEPS__$/gm, () => indentSnippet(setupSteps[packageManager], 6));
}
5 changes: 3 additions & 2 deletions renovate.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,11 @@
"customManagers": [
{
"customType": "regex",
"managerFilePatterns": ["packages/sdk/src/cli/commands/setup/github/.+\\.ya?ml$"],
"managerFilePatterns": ["/^packages/sdk/src/cli/commands/setup/github/.+\\.ya?ml$/"],
"matchStrings": [
"uses:\\s+(?<depName>[\\w-]+/[\\w-]+)@(?<currentDigest>[a-f0-9]+)\\s+#\\s+(?<currentValue>v[\\d.]+)"
"uses:\\s+(?<depName>[\\w-]+/[\\w-]+)(?<subpath>(?:/[\\w-]+)?)@(?<currentDigest>[a-f0-9]+)\\s+#\\s+(?<currentValue>v[\\d.]+)"
],
"autoReplaceStringTemplate": "uses: {{depName}}{{subpath}}@{{newDigest}} # {{newValue}}",
"datasourceTemplate": "github-tags"
}
],
Expand Down
Loading