@tailor-platform/sdk 1.62.0 → 1.63.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.
@@ -3,8 +3,8 @@
3
3
  import { Et as AuthInvokerSchema, L as CustomDomainStatus, Nt as PATScope, Q as FunctionExecution_Type, _ as userAgent, a as fetchAll, c as fetchPlatformMachineUserToken, d as initOAuth2Client, f as initOperatorClient, l as fetchUserInfo, r as closeConnectionPool, s as fetchPaged } from "../client-CobIRHl-.mjs";
4
4
  import { t as assertDefined } from "../assert-CKfwrmCV.mjs";
5
5
  import { n as logger, r as styles } from "../logger-DpJyJvNz.mjs";
6
- import { $ as listCommand$10, $t as INITIAL_SCHEMA_NUMBER, An as configArg, At as startCommand, B as logBetaWarning, C as listCommand$13, Cn as generateUserTypes, Dn as assertWritable, Dt as jobsCommand, E as resumeCommand, F as writeDbTypesFile, Fn as paginationArgs, Gt as MIGRATION_LABEL_KEY, H as removeCommand$1, Ht as executeScript, I as getConfiguredEditorCommand, In as toPageDirection, Jt as compareSnapshotWithRemote, K as treeCommand, Kt as handleOptionalToRequiredError, L as openInConfiguredEditor, Ln as workspaceArgs, Lt as functionExecutionStatusToString, Mn as deploymentArgs, Mt as getCommand$6, N as generateCommand$1, Nn as isVerbose, O as listCommand$12, On as defineAppCommand, P as generateMigrationScript, Pn as pagedLogArgs, Pt as executionsCommand, Rt as formatKeyValueTable, Sn as PluginManager, St as triggerCommand, T as healthCommand, Tn as apiCommand, U as updateCommand$3, Vt as deploy, Xt as protoGqlPermission, Y as getCommand$5, Yt as generateAllTypeManifestsFromSnapshot, Z as updateCommand$2, _n as formatMigrationDiff, an as createSnapshotFromLocalTypes, at as createCommand$3, b as createCommand$4, bn as resourceTrn, c as listCommand$14, cn as getMigrationFilePath, dn as isValidMigrationNumber, f as restoreCommand, fn as loadDiff, ft as tokenCommand, g as getCommand$7, gt as listCommand$7, hn as parseMigrationNumberArg, ht as generate, i as updateCommand$4, j as truncateCommand, jn as confirmationArgs, kn as commonArgs, ln as getMigrationFiles, lt as getCommand$3, m as listCommand$15, mn as formatMigrationNumber, nn as assertValidMigrationFiles, o as removeCommand, on as getLatestMigrationNumber, pn as reconstructSnapshotFromMigrations, pt as listCommand$8, q as listCommand$11, qt as parseMigrationLabelNumber, r as queryCommand, rn as compareLocalTypesWithSnapshot, rt as deleteCommand$3, st as listCommand$9, t as isNativeTypeScriptRuntime, tt as getCommand$4, u as inviteCommand, v as deleteCommand$4, vn as hasChanges, vt as getCommand$2, wn as prompt, wt as listCommand$6, xn as sdkNameLabelKey, xt as webhookCommand, yn as getNamespacesWithMigrations, z as showCommand, zt as getCommand$1 } from "../runtime-C6o4hiYq.mjs";
7
- import { A as resolveTokens, C as loadConfig, E as loadAccessToken, M as writePlatformConfig, O as loadWorkspaceId, T as fetchLatestToken, _ as createLogLevelTreeshakeOptions, a as WorkflowJobSchema, b as getDistDir, c as INVOKER_EXPR, g as composeFunctionTreeshakeOptions, h as platformBundleDefinePlugin, i as resolveInlineSourcemap, j as saveUserTokens, k as readPlatformConfig, o as ResolverSchema, t as defineApplication, v as resolveBundleLogLevel, w as deleteUserTokens, x as hashContent } from "../application-BezXGbrU.mjs";
6
+ import { $ as listCommand$10, $t as INITIAL_SCHEMA_NUMBER, An as commonArgs, At as startCommand, B as logBetaWarning, C as listCommand$13, Cn as PluginManager, Dt as jobsCommand, E as resumeCommand, En as apiCommand, F as writeDbTypesFile, Fn as pagedLogArgs, Gt as MIGRATION_LABEL_KEY, H as removeCommand$1, Ht as executeScript, I as getConfiguredEditorCommand, In as paginationArgs, Jt as compareSnapshotWithRemote, K as treeCommand, Kt as handleOptionalToRequiredError, L as openInConfiguredEditor, Ln as toPageDirection, Lt as functionExecutionStatusToString, Mn as confirmationArgs, Mt as getCommand$6, N as generateCommand$1, Nn as deploymentArgs, O as listCommand$12, On as assertWritable, P as generateMigrationScript, Pn as isVerbose, Pt as executionsCommand, Rn as workspaceArgs, Rt as formatKeyValueTable, Sn as sdkNameLabelKey, St as triggerCommand, T as healthCommand, Tn as prompt, U as updateCommand$3, Vt as deploy, Xt as protoGqlPermission, Y as getCommand$5, Yt as generateAllTypeManifestsFromSnapshot, Z as updateCommand$2, _n as formatMigrationDiff, an as createSnapshotFromLocalTypes, at as createCommand$3, b as createCommand$4, bn as ensureConfigId, c as listCommand$14, cn as getMigrationFilePath, dn as isValidMigrationNumber, f as restoreCommand, fn as loadDiff, ft as tokenCommand, g as getCommand$7, gt as listCommand$7, hn as parseMigrationNumberArg, ht as generate, i as updateCommand$4, j as truncateCommand, jn as configArg, kn as defineAppCommand, ln as getMigrationFiles, lt as getCommand$3, m as listCommand$15, mn as formatMigrationNumber, nn as assertValidMigrationFiles, o as removeCommand, on as getLatestMigrationNumber, pn as reconstructSnapshotFromMigrations, pt as listCommand$8, q as listCommand$11, qt as parseMigrationLabelNumber, r as queryCommand, rn as compareLocalTypesWithSnapshot, rt as deleteCommand$3, st as listCommand$9, t as isNativeTypeScriptRuntime, tt as getCommand$4, u as inviteCommand, v as deleteCommand$4, vn as hasChanges, vt as getCommand$2, wn as generateUserTypes, wt as listCommand$6, xn as resourceTrn, xt as webhookCommand, yn as getNamespacesWithMigrations, z as showCommand, zt as getCommand$1 } from "../runtime-CW3jcQCc.mjs";
7
+ import { A as resolveTokens, C as loadConfig, E as loadAccessToken, M as writePlatformConfig, O as loadWorkspaceId, T as fetchLatestToken, _ as createLogLevelTreeshakeOptions, a as WorkflowJobSchema, b as getDistDir, c as INVOKER_EXPR, g as composeFunctionTreeshakeOptions, h as platformBundleDefinePlugin, i as resolveInlineSourcemap, j as saveUserTokens, k as readPlatformConfig, o as ResolverSchema, t as defineApplication, v as resolveBundleLogLevel, w as deleteUserTokens, x as hashContent$1 } from "../application-BezXGbrU.mjs";
8
8
  import { n as ExecutorSchema } from "../service-wI3Hvrgx.mjs";
9
9
  import { t as multiline } from "../multiline-Cf9ODpr1.mjs";
10
10
  import { r as isPluginGeneratedType } from "../seed-BH2FbrPV.mjs";
@@ -24,6 +24,7 @@ import { generateCodeVerifier } from "@badgateway/oauth2-client";
24
24
  import { Code, ConnectError } from "@connectrpc/connect";
25
25
  import { resolvePackageJSON, resolveTSConfig } from "pkg-types";
26
26
  import * as crypto from "node:crypto";
27
+ import { createHash } from "node:crypto";
27
28
  import * as http from "node:http";
28
29
  import open from "open";
29
30
  import * as rolldown from "rolldown";
@@ -2564,33 +2565,159 @@ const secretCommand = defineCommand({
2564
2565
  });
2565
2566
 
2566
2567
  //#endregion
2567
- //#region src/cli/commands/setup/github/deploy.workflow.yml
2568
- var deploy_workflow_default = "name: Tailor Platform\n\non:\n pull_request:\n branches:\n - main\n push:\n branches:\n - main\n workflow_dispatch:\n\nconcurrency:\n group: tailor-__WORKSPACE_NAME__-${{ github.head_ref || github.ref }}\n cancel-in-progress: ${{ github.event_name == 'pull_request' }}\n\njobs:\n # __PLAN_JOB_START__\n plan:\n if: github.event_name == 'pull_request'\n runs-on: ubuntu-latest\n permissions:\n contents: read\n pull-requests: write\n steps:\n - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3\n with:\n fetch-depth: 0\n # __SETUP_STEPS__\n - uses: tailor-platform/actions/plan@e63ed98630a23fa21ee0636abf0f7fb75fcdce40 # v1.1.0\n with:\n workspace-id: ${{ vars.TAILOR_PLATFORM_WORKSPACE_ID }}\n # __WORKING_DIRECTORY__\n platform-client-id: ${{ secrets.PLATFORM_MACHINE_USER_CLIENT_ID }}\n platform-client-secret: ${{ secrets.PLATFORM_MACHINE_USER_CLIENT_SECRET }}\n github-token: ${{ secrets.GITHUB_TOKEN }}\n # __PLAN_JOB_END__\n deploy:\n if: github.event_name != 'pull_request'\n runs-on: ubuntu-latest\n permissions:\n contents: read\n steps:\n - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3\n # __SETUP_STEPS__\n - uses: tailor-platform/actions/deploy@e63ed98630a23fa21ee0636abf0f7fb75fcdce40 # v1.1.0\n with:\n workspace-name: __WORKSPACE_NAME__\n workspace-region: __WORKSPACE_REGION__\n organization-id: __ORGANIZATION_ID__\n folder-id: __FOLDER_ID__\n # __WORKING_DIRECTORY__\n platform-client-id: ${{ secrets.PLATFORM_MACHINE_USER_CLIENT_ID }}\n platform-client-secret: ${{ secrets.PLATFORM_MACHINE_USER_CLIENT_SECRET }}\n";
2568
+ //#region src/cli/commands/setup/github/git.ts
2569
+ const defaultGitRunner = (args, cwd) => {
2570
+ const result = spawnSync("git", args, {
2571
+ cwd,
2572
+ encoding: "utf-8"
2573
+ });
2574
+ if (result.status !== 0 || typeof result.stdout !== "string") return null;
2575
+ return result.stdout.trim();
2576
+ };
2577
+ /**
2578
+ * Detect the remote default branch by reading `refs/remotes/origin/HEAD`.
2579
+ *
2580
+ * Throws an AI-first error (with a remediation hint) when the symbolic ref is
2581
+ * not set, so the caller can surface a clear next step instead of a silent
2582
+ * fallback.
2583
+ * @param cwd - Repository directory to inspect
2584
+ * @param run - Git runner, injectable for testing
2585
+ * @returns The default branch name (e.g. `main`)
2586
+ */
2587
+ function detectDefaultBranch(cwd, run = defaultGitRunner) {
2588
+ const ref = run([
2589
+ "symbolic-ref",
2590
+ "--short",
2591
+ "refs/remotes/origin/HEAD"
2592
+ ], cwd);
2593
+ const branch = ref?.startsWith("origin/") ? ref.slice(7) : ref;
2594
+ if (!branch) throw new Error("Could not detect the default branch from git. Pass --branch <name>, or run 'git remote set-head origin --auto' to record it.");
2595
+ return branch;
2596
+ }
2597
+
2598
+ //#endregion
2599
+ //#region src/cli/commands/setup/github/lock.ts
2600
+ /** Current lock schema version. Bumped only on breaking lock-format changes. */
2601
+ const LOCK_VERSION = 1;
2602
+ /** Lock file path, relative to the repository root. */
2603
+ const LOCK_FILENAME = ".github/tailor-sdk.lock";
2604
+ /**
2605
+ * Compute the lock content hash for a rendered workflow file.
2606
+ * @param content - File content to hash
2607
+ * @returns `sha256:<hex>` digest string
2608
+ */
2609
+ function hashContent(content) {
2610
+ return `sha256:${createHash("sha256").update(content, "utf-8").digest("hex")}`;
2611
+ }
2612
+ /**
2613
+ * Resolve the absolute lock file path for an output directory.
2614
+ * @param outputDir - Repository root where `.github` lives
2615
+ * @returns Absolute path to the lock file
2616
+ */
2617
+ function lockPath(outputDir) {
2618
+ return path.join(outputDir, LOCK_FILENAME);
2619
+ }
2620
+ /**
2621
+ * Read and validate the lock file from disk.
2622
+ *
2623
+ * Returns null when no lock exists. Throws when the lock was written by a
2624
+ * newer SDK (forward-compatibility guard).
2625
+ * @param outputDir - Repository root where `.github` lives
2626
+ * @returns Parsed lock file, or null when absent
2627
+ */
2628
+ function readLock(outputDir) {
2629
+ const file = lockPath(outputDir);
2630
+ if (!fs$1.existsSync(file)) return null;
2631
+ let parsed;
2632
+ try {
2633
+ parsed = JSON.parse(fs$1.readFileSync(file, "utf-8"));
2634
+ } catch (cause) {
2635
+ throw new Error(`${LOCK_FILENAME} is not valid JSON. The lock file is machine-owned; restore it from git (git checkout -- .github/tailor-sdk.lock) and re-run setup.`, { cause });
2636
+ }
2637
+ if (typeof parsed.version !== "number") throw new Error(`${LOCK_FILENAME} has no valid 'version' field. The lock file is machine-owned; restore it from git (git checkout -- .github/tailor-sdk.lock) and re-run setup.`);
2638
+ if (parsed.version > 1) throw new Error(`${LOCK_FILENAME} was written by a newer SDK (lock version ${String(parsed.version)}). Update @tailor-platform/sdk to continue (e.g. pnpm update @tailor-platform/sdk).`);
2639
+ if (!Array.isArray(parsed.targets)) throw new Error(`${LOCK_FILENAME} has no valid 'targets' array. The lock file is machine-owned; restore it from git (git checkout -- .github/tailor-sdk.lock) and re-run setup.`);
2640
+ return parsed;
2641
+ }
2642
+ /**
2643
+ * Write the lock file to disk (2-space JSON, trailing newline).
2644
+ * @param outputDir - Repository root where `.github` lives
2645
+ * @param lock - Lock file contents to serialize
2646
+ */
2647
+ function writeLock(outputDir, lock) {
2648
+ const file = lockPath(outputDir);
2649
+ fs$1.mkdirSync(path.dirname(file), { recursive: true });
2650
+ fs$1.writeFileSync(file, `${JSON.stringify(lock, null, 2)}\n`, "utf-8");
2651
+ }
2652
+ /**
2653
+ * Find a lock target by identity. Targets are identified by (kind,
2654
+ * workspaceName); the full trigger/path cross-check is deferred to P2.
2655
+ * @param lock - Lock file to search, or null
2656
+ * @param kind - Target kind
2657
+ * @param workspaceName - Workspace name
2658
+ * @returns Matching target, or undefined
2659
+ */
2660
+ function findTarget(lock, kind, workspaceName) {
2661
+ return lock?.targets.find((t) => t.kind === kind && t.workspaceName === workspaceName);
2662
+ }
2663
+
2664
+ //#endregion
2665
+ //#region src/cli/commands/setup/github/branch.workflow.yml
2666
+ var branch_workflow_default = "# __HEADER__\nname: Tailor (__WORKSPACE_NAME__)\n\non:\n # __PULL_REQUEST_START__\n pull_request:\n branches: [\"__BRANCH__\"]\n # __PATHS__\n # __PULL_REQUEST_END__\n push:\n branches: [\"__BRANCH__\"]\n # __PATHS__\n workflow_dispatch:\n # __DISPATCH_INPUTS_START__\n inputs:\n dry-run:\n description: Preview changes without deploying\n type: boolean\n default: false\n # __DISPATCH_INPUTS_END__\n\npermissions:\n contents: read\n\njobs:\n # __PLAN_JOB_START__\n tailor-plan:\n if: >-\n github.event_name == 'pull_request' ||\n (github.event_name == 'workflow_dispatch' && inputs['dry-run'])\n runs-on: ubuntu-latest\n timeout-minutes: 30\n environment: __ENVIRONMENT__\n permissions:\n contents: read\n pull-requests: write\n concurrency:\n group: tailor-plan-__WORKSPACE_NAME__-${{ github.event.pull_request.number || github.run_id }}\n cancel-in-progress: true\n steps:\n - id: tailor-checkout\n uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3\n # __SETUP_STEPS__\n - id: tailor-generate\n # __WORKING_DIRECTORY__\n run: __PM_EXEC__ tailor-sdk generate\n - id: tailor-generate-check\n run: |\n git add -A\n if ! git diff --cached --quiet; then\n git --no-pager diff --cached --stat\n echo \"::error::Generated files are out of date. Run 'tailor-sdk generate' locally and commit the result.\"\n exit 1\n fi\n - id: tailor-plan\n # Fork PRs cannot read secrets; the checks above still run for them.\n if: github.event_name != 'pull_request' || !github.event.pull_request.head.repo.fork\n uses: tailor-platform/actions/plan@4d0f160b6b5cc2f02594776665471497c297181e # v1.2.0\n with:\n workspace-id: ${{ vars.TAILOR_PLATFORM_WORKSPACE_ID }}\n label: __WORKSPACE_NAME__\n # __WORKING_DIRECTORY__\n platform-client-id: ${{ secrets.TAILOR_PLATFORM_MACHINE_USER_CLIENT_ID }}\n platform-client-secret: ${{ secrets.TAILOR_PLATFORM_MACHINE_USER_CLIENT_SECRET }}\n github-token: ${{ secrets.GITHUB_TOKEN }}\n # __PLAN_JOB_END__\n tailor-deploy:\n # __DEPLOY_IF__\n runs-on: ubuntu-latest\n timeout-minutes: 30\n permissions:\n contents: read\n environment: __ENVIRONMENT__\n concurrency:\n group: tailor-deploy-__WORKSPACE_NAME__\n cancel-in-progress: false\n steps:\n - id: tailor-checkout\n uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3\n # __SETUP_STEPS__\n - id: tailor-apply\n uses: tailor-platform/actions/deploy@4d0f160b6b5cc2f02594776665471497c297181e # v1.2.0\n with:\n workspace-id: ${{ vars.TAILOR_PLATFORM_WORKSPACE_ID }}\n # __WORKING_DIRECTORY__\n platform-client-id: ${{ secrets.TAILOR_PLATFORM_MACHINE_USER_CLIENT_ID }}\n platform-client-secret: ${{ secrets.TAILOR_PLATFORM_MACHINE_USER_CLIENT_SECRET }}\n";
2569
2667
 
2570
2668
  //#endregion
2571
2669
  //#region src/cli/commands/setup/github/setup-bun.yml
2572
- var setup_bun_default = "- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0\n- run: bun install --frozen-lockfile\n";
2670
+ var setup_bun_default = "- id: tailor-setup-bun\n uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0\n- id: tailor-install\n run: bun install --frozen-lockfile\n";
2573
2671
 
2574
2672
  //#endregion
2575
2673
  //#region src/cli/commands/setup/github/setup-npm.yml
2576
- var setup_npm_default = "- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0\n with:\n node-version-file: package.json\n cache: npm\n- run: npm ci\n";
2674
+ var setup_npm_default = "- id: tailor-setup-node\n uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0\n with:\n node-version-file: package.json\n cache: npm\n- id: tailor-install\n run: npm ci\n";
2577
2675
 
2578
2676
  //#endregion
2579
2677
  //#region src/cli/commands/setup/github/setup-pnpm.yml
2580
- var setup_pnpm_default = "- uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8\n- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0\n with:\n node-version-file: package.json\n cache: pnpm\n- run: pnpm install --frozen-lockfile\n";
2678
+ var setup_pnpm_default = "- id: tailor-setup-pnpm\n uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8\n- id: tailor-setup-node\n uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0\n with:\n node-version-file: package.json\n cache: pnpm\n- id: tailor-install\n run: pnpm install --frozen-lockfile\n";
2581
2679
 
2582
2680
  //#endregion
2583
2681
  //#region src/cli/commands/setup/github/setup-yarn.yml
2584
- var setup_yarn_default = "- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0\n with:\n node-version-file: package.json\n cache: yarn\n- run: yarn install --frozen-lockfile\n";
2682
+ var setup_yarn_default = "- id: tailor-setup-node\n uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0\n with:\n node-version-file: package.json\n cache: yarn\n- id: tailor-install\n run: yarn install --frozen-lockfile\n";
2585
2683
 
2586
2684
  //#endregion
2587
- //#region src/cli/commands/setup/github/template-deploy.ts
2685
+ //#region src/cli/commands/setup/github/tag.workflow.yml
2686
+ var tag_workflow_default = "# __HEADER__\nname: Tailor (__WORKSPACE_NAME__)\n\non:\n push:\n tags: [\"__TAG_PATTERN__\"]\n workflow_dispatch:\n inputs:\n dry-run:\n description: Preview changes without deploying\n type: boolean\n default: false\n\npermissions:\n contents: read\n\njobs:\n # __TAG_GUARD_JOB_START__\n tailor-tag-guard:\n runs-on: ubuntu-latest\n timeout-minutes: 10\n permissions:\n contents: read\n outputs:\n on-branch: ${{ steps.tailor-tag-guard.outputs.on-branch }}\n steps:\n - id: tailor-checkout\n uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3\n with:\n fetch-depth: 0\n - id: tailor-tag-guard\n env:\n TARGET_BRANCH: \"__BRANCH__\"\n run: |\n git fetch origin \"$TARGET_BRANCH\"\n if git merge-base --is-ancestor \"$GITHUB_SHA\" \"origin/$TARGET_BRANCH\"; then\n echo \"on-branch=true\" >> \"$GITHUB_OUTPUT\"\n else\n # A tag outside the target branch is not an error — just skip.\n echo \"on-branch=false\" >> \"$GITHUB_OUTPUT\"\n echo \"::notice::Tag $GITHUB_REF_NAME is not reachable from $TARGET_BRANCH; skipping deploy.\"\n fi\n # __TAG_GUARD_JOB_END__\n tailor-plan:\n # __PLAN_NEEDS__\n # __PLAN_IF__\n runs-on: ubuntu-latest\n timeout-minutes: 30\n environment: __ENVIRONMENT__\n permissions:\n contents: read\n steps:\n - id: tailor-checkout\n uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3\n # __SETUP_STEPS__\n - id: tailor-generate\n # __WORKING_DIRECTORY__\n run: __PM_EXEC__ tailor-sdk generate\n - id: tailor-generate-check\n run: |\n git add -A\n if ! git diff --cached --quiet; then\n git --no-pager diff --cached --stat\n echo \"::error::Generated files are out of date. Run 'tailor-sdk generate' locally and commit the result.\"\n exit 1\n fi\n - id: tailor-plan\n uses: tailor-platform/actions/plan@4d0f160b6b5cc2f02594776665471497c297181e # v1.2.0\n with:\n workspace-id: ${{ vars.TAILOR_PLATFORM_WORKSPACE_ID }}\n label: __WORKSPACE_NAME__\n # __WORKING_DIRECTORY__\n platform-client-id: ${{ secrets.TAILOR_PLATFORM_MACHINE_USER_CLIENT_ID }}\n platform-client-secret: ${{ secrets.TAILOR_PLATFORM_MACHINE_USER_CLIENT_SECRET }}\n\n tailor-deploy:\n needs: tailor-plan\n if: ${{ !(github.event_name == 'workflow_dispatch' && inputs['dry-run']) }}\n environment: __ENVIRONMENT__\n runs-on: ubuntu-latest\n timeout-minutes: 30\n permissions:\n contents: read\n concurrency:\n group: tailor-deploy-__WORKSPACE_NAME__\n cancel-in-progress: false\n steps:\n - id: tailor-checkout\n uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3\n # __SETUP_STEPS__\n - id: tailor-apply\n uses: tailor-platform/actions/deploy@4d0f160b6b5cc2f02594776665471497c297181e # v1.2.0\n with:\n workspace-id: ${{ vars.TAILOR_PLATFORM_WORKSPACE_ID }}\n # __WORKING_DIRECTORY__\n platform-client-id: ${{ secrets.TAILOR_PLATFORM_MACHINE_USER_CLIENT_ID }}\n platform-client-secret: ${{ secrets.TAILOR_PLATFORM_MACHINE_USER_CLIENT_SECRET }}\n";
2687
+
2688
+ //#endregion
2689
+ //#region src/cli/commands/setup/github/templates.ts
2690
+ const setupStepIds = {
2691
+ pnpm: [
2692
+ "tailor-setup-pnpm",
2693
+ "tailor-setup-node",
2694
+ "tailor-install"
2695
+ ],
2696
+ yarn: ["tailor-setup-node", "tailor-install"],
2697
+ npm: ["tailor-setup-node", "tailor-install"],
2698
+ bun: ["tailor-setup-bun", "tailor-install"]
2699
+ };
2588
2700
  const setupSteps = {
2589
2701
  pnpm: setup_pnpm_default,
2590
2702
  yarn: setup_yarn_default,
2591
2703
  npm: setup_npm_default,
2592
2704
  bun: setup_bun_default
2593
2705
  };
2706
+ const execPrefix = {
2707
+ npm: "npx",
2708
+ pnpm: "pnpm exec",
2709
+ yarn: "yarn",
2710
+ bun: "bunx"
2711
+ };
2712
+ const HEADER = `# Generated by \`tailor-sdk setup github\` — managed by the Tailor SDK.
2713
+ #
2714
+ # - Jobs and steps whose id starts with \`tailor-\` are managed by the SDK.
2715
+ # Do not edit or rename them.
2716
+ # - State is tracked in .github/tailor-sdk.lock (machine-owned: commit it, never edit it).
2717
+ # - Re-running \`tailor-sdk setup github\` regenerates this file. If you have
2718
+ # edited it by hand, regeneration stops and asks for --force (which discards
2719
+ # your edits), so prefer keeping customizations in your own jobs/steps and
2720
+ # re-running setup after SDK updates.`;
2594
2721
  function indentSnippet(snippet, spaces) {
2595
2722
  const indent = " ".repeat(spaces);
2596
2723
  return snippet.trimEnd().split("\n").map((line) => line ? indent + line : line).join("\n");
@@ -2607,139 +2734,352 @@ function detectPackageManager(dir) {
2607
2734
  if (fs$1.existsSync(path.join(dir, "package-lock.json"))) return "npm";
2608
2735
  return "npm";
2609
2736
  }
2737
+ function block(content, name, keep) {
2738
+ if (keep) return content.replace(new RegExp(`^ *# __${name}_(?:START|END)__\\n`, "gm"), "");
2739
+ return content.replace(new RegExp(`^ *# __${name}_START__\\n[\\s\\S]*?^ *# __${name}_END__\\n`, "m"), "");
2740
+ }
2741
+ function line(content, name, replacement) {
2742
+ const re = new RegExp(`^([ \\t]*)# __${name}__\\n`, "gm");
2743
+ return content.replace(re, (_match, indent) => {
2744
+ if (replacement === void 0) return "";
2745
+ return replacement.split("\n").map((l) => l ? indent + l : l).join("\n") + "\n";
2746
+ });
2747
+ }
2748
+ function applyCommon(content, params) {
2749
+ const { workingDirectory, environment, packageManager } = params;
2750
+ let out = line(content, "HEADER", HEADER);
2751
+ out = line(out, "WORKING_DIRECTORY", workingDirectory ? `working-directory: ${workingDirectory}` : void 0);
2752
+ out = out.replace(/^ *# __SETUP_STEPS__$/gm, () => indentSnippet(setupSteps[packageManager], 6));
2753
+ return out.replaceAll("__WORKSPACE_NAME__", () => params.workspaceName).replaceAll("__ENVIRONMENT__", () => environment).replaceAll("__PM_EXEC__", () => execPrefix[packageManager]);
2754
+ }
2755
+ function setupIds(job, packageManager) {
2756
+ return setupStepIds[packageManager].map((id) => `${job}/${id}`);
2757
+ }
2610
2758
  /**
2611
- * Render the deploy workflow YAML.
2759
+ * Render the branch-target deploy workflow.
2612
2760
  *
2613
- * Generates a workflow that calls the composite deploy action
2614
- * from tailor-platform/actions. The environment setup steps (Node.js,
2615
- * package manager, dependency install) are generated based on the
2616
- * detected package manager.
2761
+ * When `plan` is false the plan job, the pull_request trigger, and the
2762
+ * workflow_dispatch dry-run input are all removed (a plan-less workflow has no
2763
+ * meaningful dry-run), and the deploy job runs unconditionally.
2764
+ * @param params - Workspace and rendering configuration
2765
+ * @returns Rendered YAML and the list of managed job/step ids
2766
+ */
2767
+ function renderBranchWorkflow(params) {
2768
+ const { branch, plan, packageManager } = params;
2769
+ let out = branch_workflow_default;
2770
+ out = block(out, "PLAN_JOB", plan);
2771
+ out = block(out, "PULL_REQUEST", plan);
2772
+ out = block(out, "DISPATCH_INPUTS", plan);
2773
+ out = line(out, "DEPLOY_IF", plan ? `if: >-\n github.event_name == 'push' ||\n (github.event_name == 'workflow_dispatch' && !inputs['dry-run'])` : void 0);
2774
+ out = line(out, "PATHS", params.workingDirectory ? `paths: ["${params.workingDirectory}/**"]` : void 0);
2775
+ out = applyCommon(out, params).replaceAll("__BRANCH__", () => branch);
2776
+ const generatedIds = [];
2777
+ if (plan) generatedIds.push("tailor-plan", "tailor-plan/tailor-checkout", ...setupIds("tailor-plan", packageManager), "tailor-plan/tailor-generate", "tailor-plan/tailor-generate-check", "tailor-plan/tailor-plan");
2778
+ generatedIds.push("tailor-deploy", "tailor-deploy/tailor-checkout", ...setupIds("tailor-deploy", packageManager), "tailor-deploy/tailor-apply");
2779
+ return {
2780
+ content: out,
2781
+ generatedIds
2782
+ };
2783
+ }
2784
+ /**
2785
+ * Render the tag-target deploy workflow.
2617
2786
  *
2618
- * If withPlan is true, also includes a plan job that runs on pull requests.
2619
- * Otherwise, the plan job section delimited by __PLAN_JOB_START__ /
2620
- * __PLAN_JOB_END__ markers is stripped from the template.
2621
- * @param params - Workspace and deployment configuration
2622
- * @returns Workflow YAML content
2787
+ * When `branch` is set, a tag-reachability guard job is generated and the plan
2788
+ * job gates on it; otherwise no guard is emitted and tags deploy unconditionally.
2789
+ * @param params - Workspace and rendering configuration
2790
+ * @returns Rendered YAML and the list of managed job/step ids
2623
2791
  */
2624
- function renderDeploy(params) {
2625
- const { workspaceName, workspaceRegion, organizationId, folderId, workingDirectory, packageManager, withPlan } = params;
2626
- const workingDirectoryLine = workingDirectory ? ` working-directory: ${workingDirectory}\n` : "";
2627
- const stripPlanSection = (content) => withPlan ? content.replace(/^ *# __PLAN_JOB_(?:START|END)__\n/gm, "") : content.replace(/^ *# __PLAN_JOB_START__\n[\s\S]*?^ *# __PLAN_JOB_END__\n/m, "");
2628
- return stripPlanSection(deploy_workflow_default).replaceAll("__WORKSPACE_NAME__", () => workspaceName).replaceAll("__WORKSPACE_REGION__", () => workspaceRegion).replaceAll("__ORGANIZATION_ID__", () => organizationId).replaceAll("__FOLDER_ID__", () => folderId).replace(/ *# __WORKING_DIRECTORY__\n/g, () => workingDirectoryLine).replace(/^ *# __SETUP_STEPS__$/gm, () => indentSnippet(setupSteps[packageManager], 6));
2792
+ function renderTagWorkflow(params) {
2793
+ const { tagPattern, branch, packageManager } = params;
2794
+ const hasGuard = branch !== void 0;
2795
+ let out = tag_workflow_default;
2796
+ out = block(out, "TAG_GUARD_JOB", hasGuard);
2797
+ out = line(out, "PLAN_NEEDS", hasGuard ? "needs: tailor-tag-guard" : void 0);
2798
+ out = line(out, "PLAN_IF", hasGuard ? `if: >-\n github.event_name == 'workflow_dispatch' ||\n needs.tailor-tag-guard.outputs.on-branch == 'true'` : void 0);
2799
+ out = applyCommon(out, params).replaceAll("__TAG_PATTERN__", () => tagPattern);
2800
+ if (hasGuard) out = out.replaceAll("__BRANCH__", () => branch);
2801
+ const generatedIds = [];
2802
+ if (hasGuard) generatedIds.push("tailor-tag-guard", "tailor-tag-guard/tailor-checkout", "tailor-tag-guard/tailor-tag-guard");
2803
+ generatedIds.push("tailor-plan", "tailor-plan/tailor-checkout", ...setupIds("tailor-plan", packageManager), "tailor-plan/tailor-generate", "tailor-plan/tailor-generate-check", "tailor-plan/tailor-plan", "tailor-deploy", "tailor-deploy/tailor-checkout", ...setupIds("tailor-deploy", packageManager), "tailor-deploy/tailor-apply");
2804
+ return {
2805
+ content: out,
2806
+ generatedIds
2807
+ };
2629
2808
  }
2630
2809
 
2631
2810
  //#endregion
2632
2811
  //#region src/cli/commands/setup/github/github.ts
2812
+ async function defaultLoadConfigName(configPath) {
2813
+ const { config } = await loadConfig(configPath);
2814
+ return config.name;
2815
+ }
2816
+ const WORKSPACE_NAME_RE = /^[a-z0-9-]+$/;
2817
+ function validateWorkspaceName(name) {
2818
+ if (name.length < 3 || name.length > 63 || !WORKSPACE_NAME_RE.test(name) || name.startsWith("-") || name.endsWith("-")) throw new Error(`Invalid workspace name "${name}". Names must be 3-63 characters of lowercase letters, numbers, and hyphens, and cannot start or end with a hyphen. Pass a valid name with --workspace-name.`);
2819
+ }
2820
+ const BRANCH_RE = /^[A-Za-z0-9._/-]+$/;
2821
+ const TAG_PATTERN_RE = /^[A-Za-z0-9._/*?![\]-]+$/;
2822
+ function validateBranch(branch) {
2823
+ if (!BRANCH_RE.test(branch)) throw new Error(`Invalid branch name "${branch}". Only letters, numbers, ".", "_", "/", and "-" are supported here.`);
2824
+ }
2825
+ function validateTagPattern(pattern) {
2826
+ if (!TAG_PATTERN_RE.test(pattern)) throw new Error(`Invalid tag pattern "${pattern}". Only letters, numbers, ".", "_", "/", "-", and the glob characters "*?![]" are supported.`);
2827
+ }
2828
+ const ENVIRONMENT_RE = /^[A-Za-z0-9._/-]+$/;
2829
+ function validateEnvironment(environment) {
2830
+ if (!ENVIRONMENT_RE.test(environment)) throw new Error(`Invalid environment name "${environment}". Only letters, numbers, ".", "_", "/", and "-" are supported.`);
2831
+ }
2832
+ const DIR_RE = /^[A-Za-z0-9._/-]+$/;
2833
+ function validateDir(dir) {
2834
+ if (!DIR_RE.test(dir)) throw new Error(`Invalid --dir "${dir}". Only letters, numbers, ".", "_", "/", and "-" are supported.`);
2835
+ }
2836
+ function escapesRoot(rel) {
2837
+ return rel === ".." || rel.startsWith(`..${path.sep}`) || rel.startsWith("../") || path.isAbsolute(rel);
2838
+ }
2839
+ /**
2840
+ * Resolve the config file path for the given app directory.
2841
+ *
2842
+ * `--dir` must stay inside the repository: the value is embedded in workflow
2843
+ * `paths:` filters and the config under it gets mutated (id injection), so
2844
+ * absolute paths and `..` traversal are rejected.
2845
+ * @param outputDir - Repository root (cwd)
2846
+ * @param dir - App directory relative to the repo root
2847
+ * @returns Absolute path to tailor.config.ts
2848
+ */
2849
+ function resolveConfigPath(outputDir, dir) {
2850
+ const appDir = path.resolve(outputDir, dir);
2851
+ const rel = path.relative(outputDir, appDir);
2852
+ if (path.isAbsolute(dir) || escapesRoot(rel)) throw new Error(`--dir must be a relative path inside the repository (got "${dir}").`);
2853
+ if (fs$1.existsSync(appDir)) {
2854
+ const realAppDir = path.normalize(fs$1.realpathSync(appDir));
2855
+ const realOutputDir = path.normalize(fs$1.realpathSync(outputDir));
2856
+ if (escapesRoot(path.relative(realOutputDir, realAppDir))) throw new Error(`--dir must resolve to a directory inside the repository (got "${dir}", which links outside it).`);
2857
+ }
2858
+ const configPath = path.join(appDir, "tailor.config.ts");
2859
+ if (!fs$1.existsSync(configPath)) throw new Error(`tailor.config.ts not found at ${configPath}. Run this from your SDK project root, or pass the app directory with --dir.`);
2860
+ return configPath;
2861
+ }
2633
2862
  /**
2634
- * Build the list of GitHub Actions files to generate.
2635
- * @param options - Setup options including workspace config and output directory
2636
- * @returns Array of files with paths and rendered content
2863
+ * Resolve all derived values and render the workflow content.
2864
+ * @param options - Setup options
2865
+ * @returns Resolved target metadata and rendered content
2637
2866
  */
2638
- function buildFiles(options) {
2639
- const githubDir = path.join(options.outputDir, ".github");
2867
+ async function resolve$1(options) {
2868
+ if (options.tag && !options.plan) throw new Error("--no-plan cannot be combined with --tag (tag targets always run plan before deploy). Drop --no-plan or use a branch target.");
2869
+ const dir = options.dir.replaceAll("\\", "/").replace(/\/{2,}/g, "/").replace(/^\.\//, "").replace(/\/$/, "") || ".";
2870
+ validateDir(dir);
2871
+ const workingDirectory = dir !== "." ? dir : void 0;
2872
+ const configPath = resolveConfigPath(options.outputDir, dir);
2873
+ const loadName = options.loadConfigName ?? defaultLoadConfigName;
2874
+ const workspaceName = options.workspaceName ?? await loadName(configPath);
2875
+ if (!workspaceName) throw new Error("Could not determine the workspace name. Pass --workspace-name, or set 'name' in tailor.config.ts.");
2876
+ validateWorkspaceName(workspaceName);
2877
+ const kind = options.tag ? "tag" : "branch";
2640
2878
  const packageManager = detectPackageManager(options.outputDir);
2641
- const workingDirectory = options.dir !== "." ? options.dir : void 0;
2642
- return [{
2643
- path: path.join(githubDir, `workflows/tailor-${options.workspaceName}.yml`),
2644
- content: renderDeploy({
2645
- workspaceName: options.workspaceName,
2646
- workspaceRegion: options.workspaceRegion,
2647
- organizationId: options.organizationId,
2648
- folderId: options.folderId,
2879
+ const environment = options.environment ?? workspaceName;
2880
+ validateEnvironment(environment);
2881
+ if (kind === "tag") validateTagPattern(options.tagPattern);
2882
+ let branch = null;
2883
+ let render;
2884
+ if (kind === "branch") {
2885
+ branch = options.branch ?? detectDefaultBranch(options.outputDir, options.gitRunner);
2886
+ validateBranch(branch);
2887
+ render = renderBranchWorkflow({
2888
+ workspaceName,
2889
+ branch,
2649
2890
  workingDirectory,
2891
+ environment,
2650
2892
  packageManager,
2651
- withPlan: options.withPlan
2652
- })
2653
- }];
2893
+ plan: options.plan
2894
+ });
2895
+ } else {
2896
+ branch = options.branch ?? null;
2897
+ if (branch !== null) validateBranch(branch);
2898
+ render = renderTagWorkflow({
2899
+ workspaceName,
2900
+ tagPattern: options.tagPattern,
2901
+ branch: options.branch,
2902
+ workingDirectory,
2903
+ environment,
2904
+ packageManager
2905
+ });
2906
+ }
2907
+ const file = `.github/workflows/tailor-${workspaceName}.yml`;
2908
+ const inputs = {
2909
+ branch,
2910
+ tagPattern: kind === "tag" ? options.tagPattern : null,
2911
+ environment,
2912
+ dir,
2913
+ packageManager,
2914
+ plan: kind === "branch" ? options.plan : true
2915
+ };
2916
+ return {
2917
+ kind,
2918
+ workspaceName,
2919
+ branch,
2920
+ environment,
2921
+ packageManager,
2922
+ render,
2923
+ inputs,
2924
+ file,
2925
+ configPath
2926
+ };
2654
2927
  }
2655
2928
  /**
2656
- * Write files to disk, skipping any that already exist.
2657
- * @param files - Files to write
2658
- * @returns Result with lists of written and skipped file paths
2929
+ * Decide how to reconcile a target with the on-disk file and lock state.
2930
+ * @param obj - Decision inputs
2931
+ * @param obj.existing - The matching lock target, if any
2932
+ * @param obj.fileExists - Whether the workflow file is present on disk
2933
+ * @param obj.currentContent - On-disk content when present
2934
+ * @param obj.force - Whether --force was passed
2935
+ * @returns The reconciliation action
2659
2936
  */
2660
- function writeFiles(files) {
2661
- const written = [];
2662
- const skipped = [];
2663
- for (const file of files) {
2664
- if (fs$1.existsSync(file.path)) {
2665
- skipped.push(file.path);
2666
- continue;
2667
- }
2668
- fs$1.mkdirSync(path.dirname(file.path), { recursive: true });
2669
- fs$1.writeFileSync(file.path, file.content);
2670
- written.push(file.path);
2937
+ function decideAction(obj) {
2938
+ const { existing, fileExists, currentContent, force } = obj;
2939
+ if (!existing) {
2940
+ if (!fileExists) return { action: "create" };
2941
+ if (force) return { action: "regenerate" };
2942
+ return {
2943
+ action: "conflict",
2944
+ reason: "An unmanaged workflow file already exists at this path. Delete it, or pass --force to bring it under SDK management (this overwrites it)."
2945
+ };
2671
2946
  }
2947
+ if (!fileExists) return { action: "restore" };
2948
+ if (currentContent !== null && hashContent(currentContent) === existing.contentHash) return { action: "regenerate" };
2949
+ if (force) return { action: "regenerate" };
2672
2950
  return {
2673
- written,
2674
- skipped
2951
+ action: "conflict",
2952
+ reason: "This workflow file has been edited by hand since it was generated. Re-run with --force to discard those edits and regenerate, or revert your changes."
2675
2953
  };
2676
2954
  }
2677
2955
  /**
2678
- * Generate GitHub Actions workflow files and print next steps.
2679
- * @param options - Setup options including workspace config and output directory
2956
+ * Guard against two targets of different kinds colliding on the same file path.
2957
+ * @param obj - Conflict inputs
2958
+ * @param obj.lock - Existing lock file, or null
2959
+ * @param obj.kind - Target kind being generated
2960
+ * @param obj.workspaceName - Workspace name being generated
2961
+ * @param obj.file - Target file path
2680
2962
  */
2681
- function setupGitHub(options) {
2682
- logBetaWarning("setup github");
2683
- const result = writeFiles(buildFiles(options));
2684
- for (const filePath of result.written) {
2685
- const relativePath = path.relative(options.outputDir, filePath);
2686
- logger.success(`Generated ${styles.path(relativePath)}`);
2687
- }
2688
- for (const filePath of result.skipped) {
2689
- const relativePath = path.relative(options.outputDir, filePath);
2690
- logger.warn(`Skipped ${styles.path(relativePath)} (already exists)`);
2691
- }
2963
+ function assertNoKindCollision(obj) {
2964
+ const { lock, kind, workspaceName, file } = obj;
2965
+ const collision = lock?.targets.find((t) => t.file === file && !(t.kind === kind && t.workspaceName === workspaceName));
2966
+ if (collision) throw new Error(`A ${collision.kind} target already owns ${file}, which conflicts with this ${kind} target. Pass a different name with --workspace-name to generate a separate workflow.`);
2967
+ }
2968
+ /**
2969
+ * Print next-step guidance after generating workflow files.
2970
+ * @param obj - Output context
2971
+ * @param obj.environment - Resolved GitHub Environment name for this target
2972
+ * @param obj.idInjected - Whether an app id was injected into the config
2973
+ */
2974
+ function printNextSteps(obj) {
2975
+ const { environment, idInjected } = obj;
2692
2976
  logger.newline();
2693
- logger.info("Next steps - set GitHub secrets:");
2694
- logger.log(` gh secret set PLATFORM_MACHINE_USER_CLIENT_ID`);
2695
- logger.log(` gh secret set PLATFORM_MACHINE_USER_CLIENT_SECRET`);
2696
- if (options.withPlan) {
2697
- logger.newline();
2698
- logger.info("For plan job - set GitHub variable with your workspace ID:");
2699
- logger.log(` gh variable set TAILOR_PLATFORM_WORKSPACE_ID`);
2700
- }
2977
+ logger.info("Next steps:");
2978
+ logger.newline();
2979
+ logger.log(`1. Set the machine-user credentials as secrets on the "${environment}" environment:`);
2980
+ logger.log(` gh secret set TAILOR_PLATFORM_MACHINE_USER_CLIENT_ID --env ${environment}`);
2981
+ logger.log(` gh secret set TAILOR_PLATFORM_MACHINE_USER_CLIENT_SECRET --env ${environment}`);
2982
+ logger.newline();
2983
+ logger.log(`2. Provision the workspace and set its id as the TAILOR_PLATFORM_WORKSPACE_ID variable on the "${environment}" environment:`);
2984
+ logger.log(" tailor-sdk workspace create # if it does not exist yet; copy the id");
2985
+ logger.log(` gh variable set TAILOR_PLATFORM_WORKSPACE_ID --env ${environment}`);
2986
+ logger.newline();
2987
+ logger.log("3. Commit the generated files:");
2988
+ logger.log(" - .github/workflows/tailor-*.yml");
2989
+ logger.log(" - .github/tailor-sdk.lock");
2990
+ if (idInjected) logger.log(" - tailor.config.ts (app id was added)");
2991
+ }
2992
+ /**
2993
+ * Generate the GitHub Actions workflow for a deploy target and reconcile it
2994
+ * with the lock file.
2995
+ * @param options - Setup options
2996
+ */
2997
+ async function setupGitHub(options) {
2998
+ logBetaWarning("setup github");
2999
+ const resolved = await resolve$1(options);
3000
+ const lock = readLock(options.outputDir);
3001
+ const absFile = path.join(options.outputDir, resolved.file);
3002
+ assertNoKindCollision({
3003
+ lock,
3004
+ kind: resolved.kind,
3005
+ workspaceName: resolved.workspaceName,
3006
+ file: resolved.file
3007
+ });
3008
+ const existing = findTarget(lock, resolved.kind, resolved.workspaceName);
3009
+ const fileExists = fs$1.existsSync(absFile);
3010
+ const decision = decideAction({
3011
+ existing,
3012
+ fileExists,
3013
+ currentContent: fileExists ? fs$1.readFileSync(absFile, "utf-8") : null,
3014
+ force: options.force
3015
+ });
3016
+ if (decision.action === "conflict") throw new Error(`${resolved.file}: ${decision.reason}`);
3017
+ const idResult = await ensureConfigId(resolved.configPath);
3018
+ if (idResult === null) logger.warn("Could not find a defineConfig() call to confirm an app id. The CI deploy will fail unless your config resolves to one with an 'id'.");
3019
+ const idInjected = idResult?.injected ?? false;
3020
+ fs$1.mkdirSync(path.dirname(absFile), { recursive: true });
3021
+ fs$1.writeFileSync(absFile, resolved.render.content, "utf-8");
3022
+ const newTarget = {
3023
+ kind: resolved.kind,
3024
+ workspaceName: resolved.workspaceName,
3025
+ file: resolved.file,
3026
+ templateVersion: 1,
3027
+ inputs: resolved.inputs,
3028
+ generatedIds: resolved.render.generatedIds,
3029
+ ejectedIds: existing?.ejectedIds ?? [],
3030
+ contentHash: hashContent(resolved.render.content)
3031
+ };
3032
+ const targets = [...lock?.targets ?? []];
3033
+ const index = targets.findIndex((t) => t.kind === newTarget.kind && t.workspaceName === newTarget.workspaceName);
3034
+ if (index === -1) targets.push(newTarget);
3035
+ else targets[index] = newTarget;
3036
+ writeLock(options.outputDir, {
3037
+ version: 1,
3038
+ targets
3039
+ });
3040
+ if (decision.action === "restore") logger.success(`Regenerated ${styles.path(resolved.file)} (was missing on disk)`);
3041
+ else if (decision.action === "regenerate") logger.success(`Regenerated ${styles.path(resolved.file)}`);
3042
+ else logger.success(`Generated ${styles.path(resolved.file)}`);
3043
+ printNextSteps({
3044
+ environment: resolved.environment,
3045
+ idInjected
3046
+ });
2701
3047
  }
2702
3048
 
2703
3049
  //#endregion
2704
3050
  //#region src/cli/commands/setup/github/index.ts
2705
3051
  const githubCommand = defineAppCommand({
2706
3052
  name: "github",
2707
- description: "Generate GitHub Actions workflow for deployment. (beta)",
3053
+ description: "Generate a GitHub Actions deploy workflow. (beta)",
2708
3054
  args: z.object({
2709
- "workspace-name": arg(z.string(), {
3055
+ "workspace-name": arg(z.string().min(1).optional(), {
2710
3056
  alias: "n",
2711
- description: "Workspace name"
3057
+ description: "Workspace name (defaults to the config 'name')"
2712
3058
  }),
2713
- "workspace-region": arg(z.string(), {
2714
- alias: "r",
2715
- description: "Workspace region"
2716
- }),
2717
- "organization-id": arg(z.string(), {
2718
- alias: "o",
2719
- description: "Organization ID"
2720
- }),
2721
- "folder-id": arg(z.string(), {
2722
- alias: "f",
2723
- description: "Folder ID"
2724
- }),
2725
- dir: arg(z.string().default("."), {
3059
+ branch: arg(z.string().min(1).optional(), { description: "Branch target: deploy trigger branch (defaults to the detected default branch). Tag target: tag-reachability guard branch (no guard when omitted)" }),
3060
+ tag: arg(z.boolean().default(false), { description: "Generate a tag target (deploy on tag push)" }),
3061
+ "tag-pattern": arg(z.string().min(1).optional(), { description: "Tag glob to match (requires --tag; defaults to v*)" }),
3062
+ environment: arg(z.string().min(1).optional(), { description: "GitHub Environment for the plan/deploy jobs (defaults to the workspace name)" }),
3063
+ "no-plan": arg(z.boolean().default(false), { description: "Disable the plan job for a branch target (cannot be combined with --tag)" }),
3064
+ dir: arg(z.string().min(1).default("."), {
2726
3065
  alias: "d",
2727
3066
  description: "App directory (for monorepo setups)"
2728
3067
  }),
2729
- "with-plan": arg(z.boolean().default(false), {
2730
- alias: "p",
2731
- description: "Include plan job for PR previews"
2732
- })
3068
+ force: arg(z.boolean().default(false), { description: "Discard hand edits / take over unmanaged files and regenerate" })
2733
3069
  }).strict(),
2734
- run: (args) => {
2735
- setupGitHub({
3070
+ run: async (args) => {
3071
+ if (args["tag-pattern"] !== void 0 && !args.tag) throw new Error("--tag-pattern requires --tag.");
3072
+ if (args["no-plan"] && args.tag) throw new Error("--no-plan cannot be combined with --tag.");
3073
+ await setupGitHub({
2736
3074
  workspaceName: args["workspace-name"],
2737
- workspaceRegion: args["workspace-region"],
2738
- organizationId: args["organization-id"],
2739
- folderId: args["folder-id"],
3075
+ branch: args.branch,
3076
+ tag: args.tag,
3077
+ tagPattern: args["tag-pattern"] ?? "v*",
3078
+ environment: args.environment,
3079
+ plan: !args["no-plan"],
2740
3080
  dir: args.dir,
2741
- outputDir: process.cwd(),
2742
- withPlan: args["with-plan"]
3081
+ force: args.force,
3082
+ outputDir: process.cwd()
2743
3083
  });
2744
3084
  }
2745
3085
  });
@@ -3323,7 +3663,7 @@ const CLEAN_ROOM_NOTES = [
3323
3663
  "It does not copy Liam source code, generated JavaScript/CSS, parser internals, or layout internals."
3324
3664
  ];
3325
3665
  function buildRevision(schema) {
3326
- return hashContent(JSON.stringify(schema)).slice(0, 16);
3666
+ return hashContent$1(JSON.stringify(schema)).slice(0, 16);
3327
3667
  }
3328
3668
  function toTypeSource(source) {
3329
3669
  if (!source) return void 0;