@tailor-platform/sdk 1.71.0 → 1.72.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/CHANGELOG.md +19 -0
- package/dist/cli/index.mjs +780 -184
- package/dist/cli/index.mjs.map +1 -1
- package/dist/completion/zsh-worker.zsh +124 -25
- package/docs/cli/setup.md +129 -19
- package/docs/cli-reference.md +9 -4
- package/package.json +1 -1
package/dist/cli/index.mjs
CHANGED
|
@@ -2820,13 +2820,25 @@ function findTarget(lock, kind, workspaceName) {
|
|
|
2820
2820
|
return lock?.targets.find((t) => t.kind === kind && t.workspaceName === workspaceName);
|
|
2821
2821
|
}
|
|
2822
2822
|
|
|
2823
|
+
//#endregion
|
|
2824
|
+
//#region src/cli/commands/setup/action.yml
|
|
2825
|
+
var action_default = "# __HEADER__\nname: __WORKSPACE_NAME__\ndescription: >-\n Deploy __WORKSPACE_NAME__ to Tailor Platform.\n Checkout and dependency installation are expected to be handled by the caller.\n\ninputs:\n workspace-id:\n description: Tailor Platform workspace id\n required: true\n name:\n description: Workspace name (used in Slack notifications)\n required: true\n working-directory:\n description: App directory relative to the repository root\n required: false\n default: \".\"\n platform-client-id:\n description: Machine-user client id\n required: true\n platform-client-secret:\n description: Machine-user client secret\n required: true\n slack-token:\n description: Slack bot token\n required: false\n slack-channel-id:\n description: Slack channel id\n required: false\n user-mapping:\n description: JSON string mapping GitHub usernames to Slack user IDs\n required: false\n package-manager:\n description: Package manager used for dependency installation (pnpm, npm, yarn, bun)\n required: false\n default: pnpm\n\noutputs:\n app-url:\n description: Deployed application URL\n value: ${{ steps.tailor-apply.outputs.app-url }}\n\nruns:\n using: composite\n steps:\n # __STATIC_WEBSITE_BUILD_START__\n - id: build-site\n shell: bash\n run: |\n # Replace with your static website build command.\n # Example: pnpm --filter <package-name> run build:static\n # Note: this run: command is user-editable. Custom build commands are preserved across\n # reruns and are not treated as drift by setup check. Other edits to this file are flagged\n # as drift and will be overwritten if you re-run setup action with --force.\n true\n # __STATIC_WEBSITE_BUILD_END__\n - id: tailor-apply\n uses: tailor-platform/actions/deploy@d203a37c87b7a69c6040611c33e9e50c1d564ed1 # v1.5.0\n with:\n workspace-id: ${{ inputs.workspace-id }}\n working-directory: ${{ inputs.working-directory }}\n package-manager: ${{ inputs.package-manager }}\n platform-client-id: ${{ inputs.platform-client-id }}\n platform-client-secret: ${{ inputs.platform-client-secret }}\n - id: tailor-notify\n if: failure() && inputs.slack-token != '' && inputs.slack-channel-id != ''\n uses: tailor-platform/actions/notify@d203a37c87b7a69c6040611c33e9e50c1d564ed1 # v1.5.0\n with:\n provider: slack\n status: failure\n workspace-name: ${{ inputs.name }}\n slack-token: ${{ inputs.slack-token }}\n slack-channel-id: ${{ inputs.slack-channel-id }}\n user-mapping: ${{ inputs.user-mapping }}\n";
|
|
2826
|
+
|
|
2823
2827
|
//#endregion
|
|
2824
2828
|
//#region src/cli/commands/setup/branch.workflow.yml
|
|
2825
|
-
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@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n - id: tailor-setup\n uses: tailor-platform/actions/setup@7817f37adf2891fbb2ebbe0a3b222a5fa2ed0c1f # v1.3.0\n with:\n package-manager: __PACKAGE_MANAGER__\n - id: tailor-generate-check\n uses: tailor-platform/actions/generate-check@7817f37adf2891fbb2ebbe0a3b222a5fa2ed0c1f # v1.3.0\n with:\n package-manager: __PACKAGE_MANAGER__\n # __WORKING_DIRECTORY__\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@7817f37adf2891fbb2ebbe0a3b222a5fa2ed0c1f # v1.3.0\n with:\n workspace-id: ${{ vars.TAILOR_PLATFORM_WORKSPACE_ID }}\n package-manager: __PACKAGE_MANAGER__\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 # __ERD_PREVIEW_JOB_START__\n tailor-erd-preview-matrix:\n if: github.event_name == 'pull_request'\n runs-on: ubuntu-latest\n timeout-minutes: 5\n permissions:\n contents: read\n outputs:\n namespaces: ${{ steps.tailor-erd-preview-matrix.outputs.namespaces }}\n base-app-dir: ${{ steps.tailor-erd-preview-matrix.outputs['base-app-dir'] }}\n steps:\n - id: tailor-checkout\n uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n with:\n persist-credentials: false\n - id: tailor-checkout-base\n uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n with:\n repository: ${{ github.event.pull_request.base.repo.full_name }}\n ref: ${{ github.event.pull_request.base.sha }}\n path: .tailor-erd-base\n persist-credentials: false\n - id: tailor-erd-preview-matrix\n shell: bash\n env:\n APP_DIR: \"__APP_DIR__\"\n WORKSPACE_NAME: \"__WORKSPACE_NAME__\"\n run: |\n set -euo pipefail\n node <<'NODE'\n const fs = require(\"node:fs\");\n\n const appDir = process.env.APP_DIR || \".\";\n const workspaceName = process.env.WORKSPACE_NAME;\n const output = process.env.GITHUB_OUTPUT;\n const dirPattern = /^[A-Za-z0-9._/-]+$/;\n const namespacePattern = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;\n\n function lockTarget(file) {\n try {\n const lock = JSON.parse(fs.readFileSync(file, \"utf8\"));\n return lock.targets?.find(\n (candidate) =>\n candidate.kind === \"branch\" &&\n candidate.workspaceName === workspaceName,\n );\n } catch (error) {\n if (error && error.code === \"ENOENT\") return undefined;\n throw error;\n }\n }\n\n function namespacesFromTarget(target, label) {\n const namespaces =\n target?.inputs?.erdPreview === true && Array.isArray(target.inputs?.erdNamespaces)\n ? target.inputs.erdNamespaces\n : [];\n for (const namespace of namespaces) {\n if (typeof namespace !== \"string\" || !namespacePattern.test(namespace)) {\n throw new Error(`Invalid ERD namespace in ${label}: ${String(namespace)}`);\n }\n }\n return namespaces;\n }\n\n function normalizeDir(value, label) {\n const raw = typeof value === \"string\" && value.length > 0 ? value : appDir;\n const normalized =\n raw.replaceAll(\"\\\\\", \"/\").replace(/\\/{2,}/g, \"/\").replace(/^\\.\\//, \"\").replace(/\\/$/, \"\") ||\n \".\";\n if (\n !dirPattern.test(normalized) ||\n normalized.startsWith(\"/\") ||\n normalized.split(\"/\").includes(\"..\")\n ) {\n throw new Error(`Invalid ERD app dir in ${label}: ${raw}`);\n }\n return normalized;\n }\n\n const headTarget = lockTarget(\".github/tailor-sdk.lock\");\n const baseTarget = lockTarget(\".tailor-erd-base/.github/tailor-sdk.lock\");\n const baseAppDir = normalizeDir(baseTarget?.inputs?.dir, \"base setup lock\");\n const namespaces = [\n ...new Set([\n ...namespacesFromTarget(headTarget, \"head setup lock\"),\n ...namespacesFromTarget(baseTarget, \"base setup lock\"),\n ]),\n ].sort((a, b) => a.localeCompare(b));\n\n if (namespaces.length === 0) {\n console.error(\"No ERD preview namespaces found in the head or base setup lock.\");\n process.exit(1);\n }\n\n fs.appendFileSync(output, `namespaces=${JSON.stringify(namespaces)}\\n`);\n fs.appendFileSync(output, `base-app-dir=${baseAppDir}\\n`);\n NODE\n\n tailor-erd-preview:\n needs: tailor-erd-preview-matrix\n if: github.event_name == 'pull_request'\n runs-on: ubuntu-latest\n timeout-minutes: 15\n permissions:\n contents: read\n strategy:\n fail-fast: false\n matrix:\n namespace: ${{ fromJSON(needs.tailor-erd-preview-matrix.outputs.namespaces) }}\n steps:\n - id: tailor-checkout\n uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n with:\n persist-credentials: false\n - id: tailor-setup\n uses: tailor-platform/actions/setup@7817f37adf2891fbb2ebbe0a3b222a5fa2ed0c1f # v1.3.0\n with:\n package-manager: __PACKAGE_MANAGER__\n - id: tailor-checkout-base\n uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n with:\n repository: ${{ github.event.pull_request.base.repo.full_name }}\n ref: ${{ github.event.pull_request.base.sha }}\n path: .tailor-erd-base\n persist-credentials: false\n - id: tailor-detect-base-package-manager\n shell: bash\n run: |\n set -euo pipefail\n cd .tailor-erd-base\n base_package_manager=\"$(\n if [ -f pnpm-lock.yaml ]; then\n echo pnpm\n elif [ -f yarn.lock ]; then\n echo yarn\n elif [ -f bun.lockb ] || [ -f bun.lock ]; then\n echo bun\n elif [ -f package-lock.json ]; then\n echo npm\n else\n echo \"__PACKAGE_MANAGER__\"\n fi\n )\"\n echo \"package-manager=$base_package_manager\" >> \"$GITHUB_OUTPUT\"\n if [ \"$base_package_manager\" = \"pnpm\" ]; then\n base_pnpm_version=\"$(node <<'NODE'\n const fs = require(\"node:fs\");\n\n const fallback = \"10\";\n\n function readManifest(file) {\n try {\n return JSON.parse(fs.readFileSync(file, \"utf8\"));\n } catch {\n return {};\n }\n }\n\n function pnpmVersion(manifest) {\n const packageManager = manifest.packageManager;\n if (typeof packageManager === \"string\" && packageManager.startsWith(\"pnpm@\")) {\n return packageManager.slice(\"pnpm@\".length).split(\"+\")[0];\n }\n\n const devPackageManager = manifest.devEngines?.packageManager;\n if (devPackageManager?.name === \"pnpm\" && devPackageManager.version) {\n return devPackageManager.version;\n }\n\n return undefined;\n }\n\n process.stdout.write(\n pnpmVersion(readManifest(\"package.json\")) ?? fallback,\n );\n NODE\n )\"\n echo \"pnpm-version=$base_pnpm_version\" >> \"$GITHUB_OUTPUT\"\n fi\n - id: tailor-setup-base-pnpm\n if: steps.tailor-detect-base-package-manager.outputs['package-manager'] == 'pnpm'\n uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9\n with:\n version: ${{ steps.tailor-detect-base-package-manager.outputs['pnpm-version'] }}\n package_json_file: .tailor-erd-base/package.json\n run_install: false\n - id: tailor-setup-base-node\n if: steps.tailor-detect-base-package-manager.outputs['package-manager'] != 'bun'\n uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0\n with:\n node-version-file: .tailor-erd-base/package.json\n - id: tailor-setup-base-yarn\n if: steps.tailor-detect-base-package-manager.outputs['package-manager'] == 'yarn'\n shell: bash\n working-directory: .tailor-erd-base\n run: |\n set -euo pipefail\n corepack enable\n - id: tailor-setup-base-bun\n if: steps.tailor-detect-base-package-manager.outputs['package-manager'] == 'bun'\n uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0\n - id: tailor-install-base\n shell: bash\n working-directory: .tailor-erd-base\n env:\n BASE_PACKAGE_MANAGER: ${{ steps.tailor-detect-base-package-manager.outputs['package-manager'] }}\n run: |\n set -euo pipefail\n case \"$BASE_PACKAGE_MANAGER\" in\n pnpm) pnpm install --frozen-lockfile ;;\n npm) npm ci ;;\n yarn)\n if grep -q '^__metadata:' yarn.lock; then\n yarn install --immutable\n else\n yarn install --frozen-lockfile\n fi\n ;;\n bun) bun install --frozen-lockfile ;;\n *)\n echo \"::error::Unsupported base package-manager '$BASE_PACKAGE_MANAGER' (expected pnpm, npm, yarn, or bun)\"\n exit 1\n ;;\n esac\n - id: tailor-restore-head-setup\n uses: tailor-platform/actions/setup@7817f37adf2891fbb2ebbe0a3b222a5fa2ed0c1f # v1.3.0\n with:\n package-manager: __PACKAGE_MANAGER__\n - id: tailor-build-erd-preview\n shell: bash\n env:\n APP_DIR: \"__APP_DIR__\"\n BASE_APP_DIR: ${{ needs.tailor-erd-preview-matrix.outputs['base-app-dir'] }}\n BASE_PACKAGE_MANAGER: ${{ steps.tailor-detect-base-package-manager.outputs['package-manager'] }}\n NAMESPACE: ${{ matrix.namespace }}\n run: |\n set -euo pipefail\n\n run_tailor_sdk() {\n case \"__PACKAGE_MANAGER__\" in\n pnpm) pnpm exec tailor-sdk \"$@\" ;;\n npm) npx tailor-sdk \"$@\" ;;\n yarn) yarn tailor-sdk \"$@\" ;;\n bun) bunx tailor-sdk \"$@\" ;;\n *)\n echo \"::error::Unsupported package-manager '__PACKAGE_MANAGER__' (expected pnpm, npm, yarn, or bun)\"\n exit 1\n ;;\n esac\n }\n\n run_head_node() {\n case \"__PACKAGE_MANAGER__\" in\n pnpm) pnpm exec node \"$@\" ;;\n npm) node \"$@\" ;;\n yarn) yarn node \"$@\" ;;\n bun) node \"$@\" ;;\n *)\n echo \"::error::Unsupported package-manager '__PACKAGE_MANAGER__' (expected pnpm, npm, yarn, or bun)\"\n exit 1\n ;;\n esac\n }\n\n tailor_sdk_bin=\"$(\n run_head_node - <<'NODE'\n const path = require(\"node:path\");\n const { createRequire } = require(\"node:module\");\n const appDir = process.env.APP_DIR || \".\";\n const githubWorkspace = process.env.GITHUB_WORKSPACE;\n if (!githubWorkspace) {\n throw new Error(\"GITHUB_WORKSPACE is required.\");\n }\n\n let lastError;\n for (const manifestPath of [\n path.join(githubWorkspace, appDir, \"package.json\"),\n path.join(githubWorkspace, \"package.json\"),\n ]) {\n try {\n const requireFromManifest = createRequire(manifestPath);\n process.stdout.write(\n path.join(\n path.dirname(requireFromManifest.resolve(\"@tailor-platform/sdk/cli\")),\n \"index.mjs\",\n ),\n );\n process.exit(0);\n } catch (error) {\n lastError = error;\n }\n }\n throw lastError || new Error(\"Failed to resolve @tailor-platform/sdk/cli.\");\n NODE\n )\"\n\n run_head_tailor_sdk_bin() {\n local command_cwd=\"$1\"\n shift\n\n run_head_cli_env() {\n env TAILOR_SDK_BIN=\"$tailor_sdk_bin\" TAILOR_SDK_CWD=\"$command_cwd\" \"$@\"\n }\n\n (\n cd \"$GITHUB_WORKSPACE\" || exit 1\n case \"__PACKAGE_MANAGER__\" in\n pnpm) run_head_cli_env pnpm exec node \"$head_cli_runner\" \"$@\" ;;\n npm) run_head_cli_env node \"$head_cli_runner\" \"$@\" ;;\n yarn) run_head_cli_env yarn node \"$head_cli_runner\" \"$@\" ;;\n bun) run_head_cli_env bun \"$head_cli_runner\" \"$@\" ;;\n *)\n echo \"::error::Unsupported package-manager '__PACKAGE_MANAGER__' (expected pnpm, npm, yarn, or bun)\"\n exit 1\n ;;\n esac\n )\n }\n\n run_base_tailor_sdk_bin() {\n local command_cwd=\"$1\"\n shift\n\n run_head_cli_env() {\n env TAILOR_SDK_BIN=\"$tailor_sdk_bin\" TAILOR_SDK_CWD=\"$command_cwd\" \"$@\"\n }\n\n (\n cd \"$command_cwd\" || exit 1\n case \"$BASE_PACKAGE_MANAGER\" in\n pnpm) run_head_cli_env pnpm exec node \"$head_cli_runner\" \"$@\" ;;\n npm) run_head_cli_env node \"$head_cli_runner\" \"$@\" ;;\n yarn) run_head_cli_env yarn node \"$head_cli_runner\" \"$@\" ;;\n bun) run_head_cli_env bun \"$head_cli_runner\" \"$@\" ;;\n *)\n echo \"::error::Unsupported base package-manager '$BASE_PACKAGE_MANAGER' (expected pnpm, npm, yarn, or bun)\"\n exit 1\n ;;\n esac\n )\n }\n\n head_output=\"$RUNNER_TEMP/tailor-erd/head\"\n base_output=\"$RUNNER_TEMP/tailor-erd/base\"\n preview_output=\"$RUNNER_TEMP/tailor-erd/preview\"\n dts_output=\"$RUNNER_TEMP/tailor-erd/dts\"\n head_cli_runner=\"$RUNNER_TEMP/tailor-erd/run-head-tailor-sdk.mjs\"\n head_log=\"$RUNNER_TEMP/tailor-erd/head-$NAMESPACE.log\"\n base_log=\"$RUNNER_TEMP/tailor-erd/base-$NAMESPACE.log\"\n rm -rf \"$head_output\" \"$base_output\" \"$preview_output\" \"$dts_output\"\n mkdir -p \"$head_output\" \"$base_output\" \"$preview_output\" \"$dts_output\"\n cat > \"$head_cli_runner\" <<'NODE'\n import { pathToFileURL } from \"node:url\";\n\n const cliBin = process.env.TAILOR_SDK_BIN;\n const commandCwd = process.env.TAILOR_SDK_CWD;\n if (!cliBin || !commandCwd) {\n throw new Error(\"TAILOR_SDK_BIN and TAILOR_SDK_CWD are required.\");\n }\n\n process.chdir(commandCwd);\n process.argv = [process.argv[0], cliBin, ...process.argv.slice(2)];\n await import(pathToFileURL(cliBin).href);\n NODE\n\n head_config=\"$GITHUB_WORKSPACE/$APP_DIR/tailor.config.ts\"\n base_config=\"$GITHUB_WORKSPACE/.tailor-erd-base/$BASE_APP_DIR/tailor.config.ts\"\n head_html=\"$head_output/$NAMESPACE/dist/index.html\"\n base_html=\"$base_output/$NAMESPACE/dist/index.html\"\n preview_html=\"$preview_output/$NAMESPACE-viewer.html\"\n\n head_missing=\"false\"\n if [ ! -f \"$head_config\" ]; then\n echo \"Head ERD config not found; rendering base objects as removed.\"\n head_missing=\"true\"\n elif ! (\n cd \"$GITHUB_WORKSPACE/$APP_DIR\" || exit 1\n TAILOR_PLATFORM_SDK_DTS_PATH=\"$dts_output/head-$NAMESPACE.d.ts\" \\\n run_tailor_sdk tailordb erd export --config \"$head_config\" --namespace \"$NAMESPACE\" --output \"$head_output\"\n ) >\"$head_log\" 2>&1; then\n if grep -q 'not found in local config.db' \"$head_log\"; then\n cat \"$head_log\"\n echo \"Head ERD namespace '$NAMESPACE' not found; rendering base objects as removed.\"\n head_missing=\"true\"\n else\n cat \"$head_log\"\n exit 1\n fi\n elif [ -s \"$head_log\" ]; then\n cat \"$head_log\"\n fi\n\n base_missing=\"false\"\n if [ ! -f \"$base_config\" ]; then\n echo \"Base ERD config not found; rendering current objects as added.\"\n base_missing=\"true\"\n elif ! (\n TAILOR_PLATFORM_SDK_DTS_PATH=\"$dts_output/base-$NAMESPACE.d.ts\" \\\n run_base_tailor_sdk_bin \"$GITHUB_WORKSPACE/.tailor-erd-base/$BASE_APP_DIR\" tailordb erd export --config \"$base_config\" --namespace \"$NAMESPACE\" --output \"$base_output\"\n ) >\"$base_log\" 2>&1; then\n if grep -q 'not found in local config.db' \"$base_log\"; then\n cat \"$base_log\"\n echo \"Base ERD namespace '$NAMESPACE' not found; rendering current objects as added.\"\n base_missing=\"true\"\n else\n cat \"$base_log\"\n exit 1\n fi\n elif [ -s \"$base_log\" ]; then\n cat \"$base_log\"\n fi\n\n if [ \"$head_missing\" = \"true\" ] && [ \"$base_missing\" = \"true\" ]; then\n echo \"::error::ERD namespace '$NAMESPACE' was not found in head or base.\"\n exit 1\n fi\n if [ \"$head_missing\" = \"false\" ] && [ ! -f \"$head_html\" ]; then\n echo \"::error::Head ERD viewer was not generated at $head_html\"\n exit 1\n fi\n if [ \"$base_missing\" = \"false\" ] && [ ! -f \"$base_html\" ]; then\n echo \"::error::Base ERD viewer was not generated at $base_html\"\n exit 1\n fi\n\n diff_args=(--namespace \"$NAMESPACE\" --output \"$preview_html\")\n if [ \"$head_missing\" = \"false\" ]; then\n diff_args+=(--head-html \"$head_html\")\n fi\n if [ \"$base_missing\" = \"false\" ]; then\n diff_args+=(--base-html \"$base_html\")\n fi\n run_head_tailor_sdk_bin \"$GITHUB_WORKSPACE\" tailordb erd diff \"${diff_args[@]}\"\n - id: tailor-upload-erd-viewer\n uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1\n with:\n name: ${{ matrix.namespace }}-viewer.html\n path: ${{ runner.temp }}/tailor-erd/preview/${{ matrix.namespace }}-viewer.html\n if-no-files-found: error\n archive: false\n retention-days: 7\n\n # __ERD_PREVIEW_JOB_END__\n # __ERD_PREVIEW_COMMENT_JOB_START__\n tailor-erd-preview-comment:\n needs: tailor-erd-preview\n if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository\n runs-on: ubuntu-latest\n timeout-minutes: 5\n permissions:\n actions: read\n pull-requests: write\n steps:\n - id: tailor-comment-erd-preview\n env:\n GH_TOKEN: ${{ github.token }}\n REPO: ${{ github.repository }}\n RUN_ID: ${{ github.run_id }}\n SERVER: ${{ github.server_url }}\n PR_NUMBER: ${{ github.event.pull_request.number }}\n BASE_REF: ${{ github.event.pull_request.base.ref }}\n HEAD_SHA: ${{ github.event.pull_request.head.sha }}\n MARKER: \"<!-- tailor-erd-preview: __WORKSPACE_NAME__ -->\"\n run: |\n set -euo pipefail\n\n current_head=$(gh api \"repos/$REPO/pulls/$PR_NUMBER\" --jq '.head.sha')\n if [ \"$current_head\" != \"$HEAD_SHA\" ]; then\n echo \"Skipping stale ERD preview comment for $HEAD_SHA; current head is $current_head.\"\n exit 0\n fi\n\n body=\"$MARKER\"$'\\n'\"### TailorDB ERD preview (__WORKSPACE_NAME__)\"$'\\n\\n'\n body+=\"Self-contained ERD viewer HTML artifacts for this pull request. Each viewer can switch between the current schema and a diff against base branch \\`$BASE_REF\\`.\"$'\\n'\n\n while IFS=$'\\t' read -r name id; do\n [ -z \"$name\" ] && continue\n if [[ \"$name\" != *-viewer.html ]]; then\n continue\n fi\n ns=${name%-viewer.html}\n label=\"$ns viewer\"\n body+=$'\\n'\"- **$label**: [$name]($SERVER/$REPO/actions/runs/$RUN_ID/artifacts/$id)\"\n done < <(gh api \"repos/$REPO/actions/runs/$RUN_ID/artifacts\" --paginate \\\n --jq '.artifacts[] | select(.name | endswith(\"-viewer.html\")) | [.name, (.id|tostring)] | @tsv' | sort)\n\n existing=$(gh api \"repos/$REPO/issues/$PR_NUMBER/comments\" --paginate \\\n --jq 'map(select(.body | contains(env.MARKER))) | .[0].id // empty')\n if [ -n \"$existing\" ]; then\n gh api --method PATCH \"repos/$REPO/issues/comments/$existing\" -f body=\"$body\" >/dev/null\n else\n gh api --method POST \"repos/$REPO/issues/$PR_NUMBER/comments\" -f body=\"$body\" >/dev/null\n fi\n\n # __ERD_PREVIEW_COMMENT_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@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n - id: tailor-setup\n uses: tailor-platform/actions/setup@7817f37adf2891fbb2ebbe0a3b222a5fa2ed0c1f # v1.3.0\n with:\n package-manager: __PACKAGE_MANAGER__\n - id: tailor-apply\n uses: tailor-platform/actions/deploy@7817f37adf2891fbb2ebbe0a3b222a5fa2ed0c1f # v1.3.0\n with:\n workspace-id: ${{ vars.TAILOR_PLATFORM_WORKSPACE_ID }}\n package-manager: __PACKAGE_MANAGER__\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";
|
|
2829
|
+
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@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n - id: tailor-setup\n uses: tailor-platform/actions/setup@d203a37c87b7a69c6040611c33e9e50c1d564ed1 # v1.5.0\n with:\n package-manager: __PACKAGE_MANAGER__\n - id: tailor-install\n uses: tailor-platform/actions/install@d203a37c87b7a69c6040611c33e9e50c1d564ed1 # v1.5.0\n with:\n package-manager: __PACKAGE_MANAGER__\n - id: tailor-generate-check\n uses: tailor-platform/actions/generate-check@d203a37c87b7a69c6040611c33e9e50c1d564ed1 # v1.5.0\n with:\n package-manager: __PACKAGE_MANAGER__\n # __WORKING_DIRECTORY__\n # __SEED_VALIDATE_START__\n - id: tailor-seed-validate\n uses: tailor-platform/actions/seed-validate@d203a37c87b7a69c6040611c33e9e50c1d564ed1 # v1.5.0\n with:\n package-manager: __PACKAGE_MANAGER__\n # __WORKING_DIRECTORY__\n # __SEED_VALIDATE_END__\n - id: tailor-drift-check\n uses: tailor-platform/actions/drift-check@d203a37c87b7a69c6040611c33e9e50c1d564ed1 # v1.5.0\n with:\n package-manager: __PACKAGE_MANAGER__\n # __MIGRATION_DRIFT_CHECK_START__\n - id: tailor-migration-drift-check\n uses: tailor-platform/actions/migration-drift-check@d203a37c87b7a69c6040611c33e9e50c1d564ed1 # v1.5.0\n with:\n package-manager: __PACKAGE_MANAGER__\n # __WORKING_DIRECTORY__\n # __MIGRATION_DRIFT_CHECK_END__\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@d203a37c87b7a69c6040611c33e9e50c1d564ed1 # v1.5.0\n with:\n workspace-id: ${{ vars.TAILOR_PLATFORM_WORKSPACE_ID }}\n package-manager: __PACKAGE_MANAGER__\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 # __ERD_PREVIEW_JOB_START__\n tailor-erd-preview-matrix:\n if: github.event_name == 'pull_request'\n runs-on: ubuntu-latest\n timeout-minutes: 5\n permissions:\n contents: read\n outputs:\n namespaces: ${{ steps.tailor-erd-preview-matrix.outputs.namespaces }}\n base-app-dir: ${{ steps.tailor-erd-preview-matrix.outputs['base-app-dir'] }}\n steps:\n - id: tailor-checkout\n uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n with:\n persist-credentials: false\n - id: tailor-checkout-base\n uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n with:\n repository: ${{ github.event.pull_request.base.repo.full_name }}\n ref: ${{ github.event.pull_request.base.sha }}\n path: .tailor-erd-base\n persist-credentials: false\n - id: tailor-erd-preview-matrix\n shell: bash\n env:\n APP_DIR: \"__APP_DIR__\"\n WORKSPACE_NAME: \"__WORKSPACE_NAME__\"\n run: |\n set -euo pipefail\n node <<'NODE'\n const fs = require(\"node:fs\");\n\n const appDir = process.env.APP_DIR || \".\";\n const workspaceName = process.env.WORKSPACE_NAME;\n const output = process.env.GITHUB_OUTPUT;\n const dirPattern = /^[A-Za-z0-9._/-]+$/;\n const namespacePattern = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;\n\n function lockTarget(file) {\n try {\n const lock = JSON.parse(fs.readFileSync(file, \"utf8\"));\n return lock.targets?.find(\n (candidate) =>\n candidate.kind === \"branch\" &&\n candidate.workspaceName === workspaceName,\n );\n } catch (error) {\n if (error && error.code === \"ENOENT\") return undefined;\n throw error;\n }\n }\n\n function namespacesFromTarget(target, label) {\n const namespaces =\n target?.inputs?.erdPreview === true && Array.isArray(target.inputs?.erdNamespaces)\n ? target.inputs.erdNamespaces\n : [];\n for (const namespace of namespaces) {\n if (typeof namespace !== \"string\" || !namespacePattern.test(namespace)) {\n throw new Error(`Invalid ERD namespace in ${label}: ${String(namespace)}`);\n }\n }\n return namespaces;\n }\n\n function normalizeDir(value, label) {\n const raw = typeof value === \"string\" && value.length > 0 ? value : appDir;\n const normalized =\n raw.replaceAll(\"\\\\\", \"/\").replace(/\\/{2,}/g, \"/\").replace(/^\\.\\//, \"\").replace(/\\/$/, \"\") ||\n \".\";\n if (\n !dirPattern.test(normalized) ||\n normalized.startsWith(\"/\") ||\n normalized.split(\"/\").includes(\"..\")\n ) {\n throw new Error(`Invalid ERD app dir in ${label}: ${raw}`);\n }\n return normalized;\n }\n\n const headTarget = lockTarget(\".github/tailor-sdk.lock\");\n const baseTarget = lockTarget(\".tailor-erd-base/.github/tailor-sdk.lock\");\n const baseAppDir = normalizeDir(baseTarget?.inputs?.dir, \"base setup lock\");\n const namespaces = [\n ...new Set([\n ...namespacesFromTarget(headTarget, \"head setup lock\"),\n ...namespacesFromTarget(baseTarget, \"base setup lock\"),\n ]),\n ].sort((a, b) => a.localeCompare(b));\n\n if (namespaces.length === 0) {\n console.error(\"No ERD preview namespaces found in the head or base setup lock.\");\n process.exit(1);\n }\n\n fs.appendFileSync(output, `namespaces=${JSON.stringify(namespaces)}\\n`);\n fs.appendFileSync(output, `base-app-dir=${baseAppDir}\\n`);\n NODE\n\n tailor-erd-preview:\n needs: tailor-erd-preview-matrix\n if: github.event_name == 'pull_request'\n runs-on: ubuntu-latest\n timeout-minutes: 15\n permissions:\n contents: read\n strategy:\n fail-fast: false\n matrix:\n namespace: ${{ fromJSON(needs.tailor-erd-preview-matrix.outputs.namespaces) }}\n steps:\n - id: tailor-checkout\n uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n with:\n persist-credentials: false\n - id: tailor-setup\n uses: tailor-platform/actions/setup@7817f37adf2891fbb2ebbe0a3b222a5fa2ed0c1f # v1.3.0\n with:\n package-manager: __PACKAGE_MANAGER__\n - id: tailor-checkout-base\n uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n with:\n repository: ${{ github.event.pull_request.base.repo.full_name }}\n ref: ${{ github.event.pull_request.base.sha }}\n path: .tailor-erd-base\n persist-credentials: false\n - id: tailor-detect-base-package-manager\n shell: bash\n run: |\n set -euo pipefail\n cd .tailor-erd-base\n base_package_manager=\"$(\n if [ -f pnpm-lock.yaml ]; then\n echo pnpm\n elif [ -f yarn.lock ]; then\n echo yarn\n elif [ -f bun.lockb ] || [ -f bun.lock ]; then\n echo bun\n elif [ -f package-lock.json ]; then\n echo npm\n else\n echo \"__PACKAGE_MANAGER__\"\n fi\n )\"\n echo \"package-manager=$base_package_manager\" >> \"$GITHUB_OUTPUT\"\n if [ \"$base_package_manager\" = \"pnpm\" ]; then\n base_pnpm_version=\"$(node <<'NODE'\n const fs = require(\"node:fs\");\n\n const fallback = \"10\";\n\n function readManifest(file) {\n try {\n return JSON.parse(fs.readFileSync(file, \"utf8\"));\n } catch {\n return {};\n }\n }\n\n function pnpmVersion(manifest) {\n const packageManager = manifest.packageManager;\n if (typeof packageManager === \"string\" && packageManager.startsWith(\"pnpm@\")) {\n return packageManager.slice(\"pnpm@\".length).split(\"+\")[0];\n }\n\n const devPackageManager = manifest.devEngines?.packageManager;\n if (devPackageManager?.name === \"pnpm\" && devPackageManager.version) {\n return devPackageManager.version;\n }\n\n return undefined;\n }\n\n process.stdout.write(\n pnpmVersion(readManifest(\"package.json\")) ?? fallback,\n );\n NODE\n )\"\n echo \"pnpm-version=$base_pnpm_version\" >> \"$GITHUB_OUTPUT\"\n fi\n - id: tailor-setup-base-pnpm\n if: steps.tailor-detect-base-package-manager.outputs['package-manager'] == 'pnpm'\n uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9\n with:\n version: ${{ steps.tailor-detect-base-package-manager.outputs['pnpm-version'] }}\n package_json_file: .tailor-erd-base/package.json\n run_install: false\n - id: tailor-setup-base-node\n if: steps.tailor-detect-base-package-manager.outputs['package-manager'] != 'bun'\n uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0\n with:\n node-version-file: .tailor-erd-base/package.json\n - id: tailor-setup-base-yarn\n if: steps.tailor-detect-base-package-manager.outputs['package-manager'] == 'yarn'\n shell: bash\n working-directory: .tailor-erd-base\n run: |\n set -euo pipefail\n corepack enable\n - id: tailor-setup-base-bun\n if: steps.tailor-detect-base-package-manager.outputs['package-manager'] == 'bun'\n uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0\n - id: tailor-install-base\n shell: bash\n working-directory: .tailor-erd-base\n env:\n BASE_PACKAGE_MANAGER: ${{ steps.tailor-detect-base-package-manager.outputs['package-manager'] }}\n run: |\n set -euo pipefail\n case \"$BASE_PACKAGE_MANAGER\" in\n pnpm) pnpm install --frozen-lockfile ;;\n npm) npm ci ;;\n yarn)\n if grep -q '^__metadata:' yarn.lock; then\n yarn install --immutable\n else\n yarn install --frozen-lockfile\n fi\n ;;\n bun) bun install --frozen-lockfile ;;\n *)\n echo \"::error::Unsupported base package-manager '$BASE_PACKAGE_MANAGER' (expected pnpm, npm, yarn, or bun)\"\n exit 1\n ;;\n esac\n - id: tailor-restore-head-setup\n uses: tailor-platform/actions/setup@7817f37adf2891fbb2ebbe0a3b222a5fa2ed0c1f # v1.3.0\n with:\n package-manager: __PACKAGE_MANAGER__\n - id: tailor-build-erd-preview\n shell: bash\n env:\n APP_DIR: \"__APP_DIR__\"\n BASE_APP_DIR: ${{ needs.tailor-erd-preview-matrix.outputs['base-app-dir'] }}\n BASE_PACKAGE_MANAGER: ${{ steps.tailor-detect-base-package-manager.outputs['package-manager'] }}\n NAMESPACE: ${{ matrix.namespace }}\n run: |\n set -euo pipefail\n\n run_tailor_sdk() {\n case \"__PACKAGE_MANAGER__\" in\n pnpm) pnpm exec tailor-sdk \"$@\" ;;\n npm) npx tailor-sdk \"$@\" ;;\n yarn) yarn tailor-sdk \"$@\" ;;\n bun) bunx tailor-sdk \"$@\" ;;\n *)\n echo \"::error::Unsupported package-manager '__PACKAGE_MANAGER__' (expected pnpm, npm, yarn, or bun)\"\n exit 1\n ;;\n esac\n }\n\n run_head_node() {\n case \"__PACKAGE_MANAGER__\" in\n pnpm) pnpm exec node \"$@\" ;;\n npm) node \"$@\" ;;\n yarn) yarn node \"$@\" ;;\n bun) node \"$@\" ;;\n *)\n echo \"::error::Unsupported package-manager '__PACKAGE_MANAGER__' (expected pnpm, npm, yarn, or bun)\"\n exit 1\n ;;\n esac\n }\n\n tailor_sdk_bin=\"$(\n run_head_node - <<'NODE'\n const path = require(\"node:path\");\n const { createRequire } = require(\"node:module\");\n const appDir = process.env.APP_DIR || \".\";\n const githubWorkspace = process.env.GITHUB_WORKSPACE;\n if (!githubWorkspace) {\n throw new Error(\"GITHUB_WORKSPACE is required.\");\n }\n\n let lastError;\n for (const manifestPath of [\n path.join(githubWorkspace, appDir, \"package.json\"),\n path.join(githubWorkspace, \"package.json\"),\n ]) {\n try {\n const requireFromManifest = createRequire(manifestPath);\n process.stdout.write(\n path.join(\n path.dirname(requireFromManifest.resolve(\"@tailor-platform/sdk/cli\")),\n \"index.mjs\",\n ),\n );\n process.exit(0);\n } catch (error) {\n lastError = error;\n }\n }\n throw lastError || new Error(\"Failed to resolve @tailor-platform/sdk/cli.\");\n NODE\n )\"\n\n run_head_tailor_sdk_bin() {\n local command_cwd=\"$1\"\n shift\n\n run_head_cli_env() {\n env TAILOR_SDK_BIN=\"$tailor_sdk_bin\" TAILOR_SDK_CWD=\"$command_cwd\" \"$@\"\n }\n\n (\n cd \"$GITHUB_WORKSPACE\" || exit 1\n case \"__PACKAGE_MANAGER__\" in\n pnpm) run_head_cli_env pnpm exec node \"$head_cli_runner\" \"$@\" ;;\n npm) run_head_cli_env node \"$head_cli_runner\" \"$@\" ;;\n yarn) run_head_cli_env yarn node \"$head_cli_runner\" \"$@\" ;;\n bun) run_head_cli_env bun \"$head_cli_runner\" \"$@\" ;;\n *)\n echo \"::error::Unsupported package-manager '__PACKAGE_MANAGER__' (expected pnpm, npm, yarn, or bun)\"\n exit 1\n ;;\n esac\n )\n }\n\n run_base_tailor_sdk_bin() {\n local command_cwd=\"$1\"\n shift\n\n run_head_cli_env() {\n env TAILOR_SDK_BIN=\"$tailor_sdk_bin\" TAILOR_SDK_CWD=\"$command_cwd\" \"$@\"\n }\n\n (\n cd \"$command_cwd\" || exit 1\n case \"$BASE_PACKAGE_MANAGER\" in\n pnpm) run_head_cli_env pnpm exec node \"$head_cli_runner\" \"$@\" ;;\n npm) run_head_cli_env node \"$head_cli_runner\" \"$@\" ;;\n yarn) run_head_cli_env yarn node \"$head_cli_runner\" \"$@\" ;;\n bun) run_head_cli_env bun \"$head_cli_runner\" \"$@\" ;;\n *)\n echo \"::error::Unsupported base package-manager '$BASE_PACKAGE_MANAGER' (expected pnpm, npm, yarn, or bun)\"\n exit 1\n ;;\n esac\n )\n }\n\n head_output=\"$RUNNER_TEMP/tailor-erd/head\"\n base_output=\"$RUNNER_TEMP/tailor-erd/base\"\n preview_output=\"$RUNNER_TEMP/tailor-erd/preview\"\n dts_output=\"$RUNNER_TEMP/tailor-erd/dts\"\n head_cli_runner=\"$RUNNER_TEMP/tailor-erd/run-head-tailor-sdk.mjs\"\n head_log=\"$RUNNER_TEMP/tailor-erd/head-$NAMESPACE.log\"\n base_log=\"$RUNNER_TEMP/tailor-erd/base-$NAMESPACE.log\"\n rm -rf \"$head_output\" \"$base_output\" \"$preview_output\" \"$dts_output\"\n mkdir -p \"$head_output\" \"$base_output\" \"$preview_output\" \"$dts_output\"\n cat > \"$head_cli_runner\" <<'NODE'\n import { pathToFileURL } from \"node:url\";\n\n const cliBin = process.env.TAILOR_SDK_BIN;\n const commandCwd = process.env.TAILOR_SDK_CWD;\n if (!cliBin || !commandCwd) {\n throw new Error(\"TAILOR_SDK_BIN and TAILOR_SDK_CWD are required.\");\n }\n\n process.chdir(commandCwd);\n process.argv = [process.argv[0], cliBin, ...process.argv.slice(2)];\n await import(pathToFileURL(cliBin).href);\n NODE\n\n head_config=\"$GITHUB_WORKSPACE/$APP_DIR/tailor.config.ts\"\n base_config=\"$GITHUB_WORKSPACE/.tailor-erd-base/$BASE_APP_DIR/tailor.config.ts\"\n head_html=\"$head_output/$NAMESPACE/dist/index.html\"\n base_html=\"$base_output/$NAMESPACE/dist/index.html\"\n preview_html=\"$preview_output/$NAMESPACE-viewer.html\"\n\n head_missing=\"false\"\n if [ ! -f \"$head_config\" ]; then\n echo \"Head ERD config not found; rendering base objects as removed.\"\n head_missing=\"true\"\n elif ! (\n cd \"$GITHUB_WORKSPACE/$APP_DIR\" || exit 1\n TAILOR_PLATFORM_SDK_DTS_PATH=\"$dts_output/head-$NAMESPACE.d.ts\" \\\n run_tailor_sdk tailordb erd export --config \"$head_config\" --namespace \"$NAMESPACE\" --output \"$head_output\"\n ) >\"$head_log\" 2>&1; then\n if grep -q 'not found in local config.db' \"$head_log\"; then\n cat \"$head_log\"\n echo \"Head ERD namespace '$NAMESPACE' not found; rendering base objects as removed.\"\n head_missing=\"true\"\n else\n cat \"$head_log\"\n exit 1\n fi\n elif [ -s \"$head_log\" ]; then\n cat \"$head_log\"\n fi\n\n base_missing=\"false\"\n if [ ! -f \"$base_config\" ]; then\n echo \"Base ERD config not found; rendering current objects as added.\"\n base_missing=\"true\"\n elif ! (\n TAILOR_PLATFORM_SDK_DTS_PATH=\"$dts_output/base-$NAMESPACE.d.ts\" \\\n run_base_tailor_sdk_bin \"$GITHUB_WORKSPACE/.tailor-erd-base/$BASE_APP_DIR\" tailordb erd export --config \"$base_config\" --namespace \"$NAMESPACE\" --output \"$base_output\"\n ) >\"$base_log\" 2>&1; then\n if grep -q 'not found in local config.db' \"$base_log\"; then\n cat \"$base_log\"\n echo \"Base ERD namespace '$NAMESPACE' not found; rendering current objects as added.\"\n base_missing=\"true\"\n else\n cat \"$base_log\"\n exit 1\n fi\n elif [ -s \"$base_log\" ]; then\n cat \"$base_log\"\n fi\n\n if [ \"$head_missing\" = \"true\" ] && [ \"$base_missing\" = \"true\" ]; then\n echo \"::error::ERD namespace '$NAMESPACE' was not found in head or base.\"\n exit 1\n fi\n if [ \"$head_missing\" = \"false\" ] && [ ! -f \"$head_html\" ]; then\n echo \"::error::Head ERD viewer was not generated at $head_html\"\n exit 1\n fi\n if [ \"$base_missing\" = \"false\" ] && [ ! -f \"$base_html\" ]; then\n echo \"::error::Base ERD viewer was not generated at $base_html\"\n exit 1\n fi\n\n diff_args=(--namespace \"$NAMESPACE\" --output \"$preview_html\")\n if [ \"$head_missing\" = \"false\" ]; then\n diff_args+=(--head-html \"$head_html\")\n fi\n if [ \"$base_missing\" = \"false\" ]; then\n diff_args+=(--base-html \"$base_html\")\n fi\n run_head_tailor_sdk_bin \"$GITHUB_WORKSPACE\" tailordb erd diff \"${diff_args[@]}\"\n - id: tailor-upload-erd-viewer\n uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1\n with:\n name: ${{ matrix.namespace }}-viewer.html\n path: ${{ runner.temp }}/tailor-erd/preview/${{ matrix.namespace }}-viewer.html\n if-no-files-found: error\n archive: false\n retention-days: 7\n\n # __ERD_PREVIEW_JOB_END__\n # __ERD_PREVIEW_COMMENT_JOB_START__\n tailor-erd-preview-comment:\n needs: tailor-erd-preview\n if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository\n runs-on: ubuntu-latest\n timeout-minutes: 5\n permissions:\n actions: read\n pull-requests: write\n steps:\n - id: tailor-comment-erd-preview\n env:\n GH_TOKEN: ${{ github.token }}\n REPO: ${{ github.repository }}\n RUN_ID: ${{ github.run_id }}\n SERVER: ${{ github.server_url }}\n PR_NUMBER: ${{ github.event.pull_request.number }}\n BASE_REF: ${{ github.event.pull_request.base.ref }}\n HEAD_SHA: ${{ github.event.pull_request.head.sha }}\n MARKER: \"<!-- tailor-erd-preview: __WORKSPACE_NAME__ -->\"\n run: |\n set -euo pipefail\n\n current_head=$(gh api \"repos/$REPO/pulls/$PR_NUMBER\" --jq '.head.sha')\n if [ \"$current_head\" != \"$HEAD_SHA\" ]; then\n echo \"Skipping stale ERD preview comment for $HEAD_SHA; current head is $current_head.\"\n exit 0\n fi\n\n body=\"$MARKER\"$'\\n'\"### TailorDB ERD preview (__WORKSPACE_NAME__)\"$'\\n\\n'\n body+=\"Self-contained ERD viewer HTML artifacts for this pull request. Each viewer can switch between the current schema and a diff against base branch \\`$BASE_REF\\`.\"$'\\n'\n\n while IFS=$'\\t' read -r name id; do\n [ -z \"$name\" ] && continue\n if [[ \"$name\" != *-viewer.html ]]; then\n continue\n fi\n ns=${name%-viewer.html}\n label=\"$ns viewer\"\n body+=$'\\n'\"- **$label**: [$name]($SERVER/$REPO/actions/runs/$RUN_ID/artifacts/$id)\"\n done < <(gh api \"repos/$REPO/actions/runs/$RUN_ID/artifacts\" --paginate \\\n --jq '.artifacts[] | select(.name | endswith(\"-viewer.html\")) | [.name, (.id|tostring)] | @tsv' | sort)\n\n existing=$(gh api \"repos/$REPO/issues/$PR_NUMBER/comments\" --paginate \\\n --jq 'map(select(.body | contains(env.MARKER))) | .[0].id // empty')\n if [ -n \"$existing\" ]; then\n gh api --method PATCH \"repos/$REPO/issues/comments/$existing\" -f body=\"$body\" >/dev/null\n else\n gh api --method POST \"repos/$REPO/issues/$PR_NUMBER/comments\" -f body=\"$body\" >/dev/null\n fi\n\n # __ERD_PREVIEW_COMMENT_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@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n - id: tailor-setup\n uses: tailor-platform/actions/setup@d203a37c87b7a69c6040611c33e9e50c1d564ed1 # v1.5.0\n with:\n package-manager: __PACKAGE_MANAGER__\n - id: tailor-install\n uses: tailor-platform/actions/install@d203a37c87b7a69c6040611c33e9e50c1d564ed1 # v1.5.0\n with:\n package-manager: __PACKAGE_MANAGER__\n - id: tailor-apply\n uses: tailor-platform/actions/deploy@d203a37c87b7a69c6040611c33e9e50c1d564ed1 # v1.5.0\n with:\n workspace-id: ${{ vars.TAILOR_PLATFORM_WORKSPACE_ID }}\n package-manager: __PACKAGE_MANAGER__\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 # __SEED_DATA_START__\n - id: seed-data\n if: false\n shell: bash\n # __WORKING_DIRECTORY__\n run: |\n # Uncomment and implement to seed data on deploy.\n # Run after apply so the schema is ready.\n # Example: node scripts/seed.mjs\n true\n # __SEED_DATA_END__\n - id: slack-prereq\n if: always()\n shell: bash\n env:\n SLACK_TOKEN: ${{ secrets.TAILOR_SLACK_BOT_TOKEN }}\n SLACK_CHANNEL_ID: ${{ vars.TAILOR_SLACK_CHANNEL_ID }}\n run: |\n if [[ -n \"$SLACK_TOKEN\" && -n \"$SLACK_CHANNEL_ID\" ]]; then\n echo \"ready=true\" >> \"$GITHUB_OUTPUT\"\n fi\n - id: tailor-notify\n if: always() && steps.slack-prereq.outputs.ready == 'true'\n uses: tailor-platform/actions/notify@d203a37c87b7a69c6040611c33e9e50c1d564ed1 # v1.5.0\n with:\n provider: slack\n status: ${{ job.status }}\n workspace-name: __WORKSPACE_NAME__\n slack-token: ${{ secrets.TAILOR_SLACK_BOT_TOKEN }}\n slack-channel-id: ${{ vars.TAILOR_SLACK_CHANNEL_ID }}\n # user-mapping: ${{ vars.TAILOR_SLACK_USER_MAPPING }}\n";
|
|
2830
|
+
|
|
2831
|
+
//#endregion
|
|
2832
|
+
//#region src/cli/commands/setup/coordinate.workflow.yml
|
|
2833
|
+
var coordinate_workflow_default = "# __HEADER__\nname: Tailor Coordinator (__WORKSPACE_NAME__)\n\non:\n # __PULL_REQUEST_START__\n pull_request:\n branches: [\"__BRANCH__\"]\n # __PULL_REQUEST_END__\n push:\n # __PUSH_BRANCHES_START__\n branches: [\"__BRANCH__\"]\n # __PUSH_BRANCHES_END__\n # __PUSH_TAGS_START__\n tags: [__TAG_PATTERN__]\n # __PUSH_TAGS_END__\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: 5\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@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n with:\n fetch-depth: 0\n - id: tailor-tag-guard\n uses: tailor-platform/actions/tag-guard@d203a37c87b7a69c6040611c33e9e50c1d564ed1 # v1.5.0\n with:\n target-branch: __BRANCH__\n # __TAG_GUARD_JOB_END__\n\n tailor-plan:\n # __PLAN_NEEDS__\n # __PLAN_IF__\n runs-on: ubuntu-latest\n timeout-minutes: 60\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@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n - id: tailor-setup\n uses: ./.github/actions/tailor-setup\n # __PER_APP_PLAN_STEPS__\n\n tailor-deploy:\n # __DEPLOY_NEEDS__\n # __DEPLOY_IF__\n # __DEPLOY_ENVIRONMENT__\n runs-on: ubuntu-latest\n timeout-minutes: 60\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@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n - id: tailor-setup\n uses: ./.github/actions/tailor-setup\n # __PER_APP_DEPLOY_STEPS__\n";
|
|
2834
|
+
|
|
2835
|
+
//#endregion
|
|
2836
|
+
//#region src/cli/commands/setup/preview.workflow.yml
|
|
2837
|
+
var preview_workflow_default = "# __HEADER__\nname: Tailor Preview (__WORKSPACE_NAME__)\n\non:\n pull_request:\n branches: [\"__BRANCH__\"]\n # __PR_TYPES__\n # To deploy only for PRs that carry the `tailor:preview` label instead of all PRs,\n # re-run: tailor-sdk setup --require-preview-label\n # __PATHS__\n\npermissions:\n contents: read\n pull-requests: write\n\njobs:\n tailor-preview-deploy:\n # __DEPLOY_IF__\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-preview-__WORKSPACE_NAME__-${{ github.event.pull_request.number }}\n cancel-in-progress: true\n steps:\n - id: tailor-checkout\n uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n - id: tailor-setup\n uses: tailor-platform/actions/setup@d203a37c87b7a69c6040611c33e9e50c1d564ed1 # v1.5.0\n with:\n package-manager: __PACKAGE_MANAGER__\n - id: tailor-install\n uses: tailor-platform/actions/install@d203a37c87b7a69c6040611c33e9e50c1d564ed1 # v1.5.0\n with:\n package-manager: __PACKAGE_MANAGER__\n - id: tailor-generate-check\n uses: tailor-platform/actions/generate-check@d203a37c87b7a69c6040611c33e9e50c1d564ed1 # v1.5.0\n with:\n package-manager: __PACKAGE_MANAGER__\n # __WORKING_DIRECTORY__\n - id: tailor-preview-deploy\n uses: tailor-platform/actions/preview-deploy@d203a37c87b7a69c6040611c33e9e50c1d564ed1 # v1.5.0\n with:\n workspace-name-prefix: __WORKSPACE_NAME__\n region: \"__REGION__\"\n organization-id: ${{ vars.TAILOR_PLATFORM_ORGANIZATION_ID }}\n folder-id: ${{ vars.TAILOR_PLATFORM_FOLDER_ID }}\n package-manager: __PACKAGE_MANAGER__\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 - id: tailor-preview-comment\n if: always()\n uses: tailor-platform/actions/preview-comment@d203a37c87b7a69c6040611c33e9e50c1d564ed1 # v1.5.0\n with:\n workspace-id: ${{ steps.tailor-preview-deploy.outputs.workspace-id }}\n workspace-name: ${{ steps.tailor-preview-deploy.outputs.workspace-name }}\n status: ${{ job.status == 'success' && 'success' || 'failure' }}\n app-url: ${{ steps.tailor-preview-deploy.outputs.app-url }}\n github-token: ${{ secrets.GITHUB_TOKEN }}\n\n tailor-preview-cleanup:\n # __CLEANUP_IF__\n runs-on: ubuntu-latest\n timeout-minutes: 15\n environment: __ENVIRONMENT__\n permissions:\n contents: read\n pull-requests: write\n steps:\n - id: tailor-checkout\n uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n - id: tailor-setup\n uses: tailor-platform/actions/setup@d203a37c87b7a69c6040611c33e9e50c1d564ed1 # v1.5.0\n with:\n package-manager: __PACKAGE_MANAGER__\n - id: tailor-install\n uses: tailor-platform/actions/install@d203a37c87b7a69c6040611c33e9e50c1d564ed1 # v1.5.0\n with:\n package-manager: __PACKAGE_MANAGER__\n - id: tailor-preview-cleanup\n uses: tailor-platform/actions/preview-cleanup@d203a37c87b7a69c6040611c33e9e50c1d564ed1 # v1.5.0\n with:\n workspace-name-prefix: __WORKSPACE_NAME__\n package-manager: __PACKAGE_MANAGER__\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";
|
|
2826
2838
|
|
|
2827
2839
|
//#endregion
|
|
2828
2840
|
//#region src/cli/commands/setup/tag.workflow.yml
|
|
2829
|
-
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@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n with:\n fetch-depth: 0\n - id: tailor-tag-guard\n uses: tailor-platform/actions/tag-guard@
|
|
2841
|
+
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@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n with:\n fetch-depth: 0\n - id: tailor-tag-guard\n uses: tailor-platform/actions/tag-guard@d203a37c87b7a69c6040611c33e9e50c1d564ed1 # v1.5.0\n with:\n target-branch: \"__BRANCH__\"\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@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n - id: tailor-setup\n uses: tailor-platform/actions/setup@d203a37c87b7a69c6040611c33e9e50c1d564ed1 # v1.5.0\n with:\n package-manager: __PACKAGE_MANAGER__\n - id: tailor-install\n uses: tailor-platform/actions/install@d203a37c87b7a69c6040611c33e9e50c1d564ed1 # v1.5.0\n with:\n package-manager: __PACKAGE_MANAGER__\n - id: tailor-generate-check\n uses: tailor-platform/actions/generate-check@d203a37c87b7a69c6040611c33e9e50c1d564ed1 # v1.5.0\n with:\n package-manager: __PACKAGE_MANAGER__\n # __WORKING_DIRECTORY__\n # __SEED_VALIDATE_START__\n - id: tailor-seed-validate\n uses: tailor-platform/actions/seed-validate@d203a37c87b7a69c6040611c33e9e50c1d564ed1 # v1.5.0\n with:\n package-manager: __PACKAGE_MANAGER__\n # __WORKING_DIRECTORY__\n # __SEED_VALIDATE_END__\n - id: tailor-drift-check\n uses: tailor-platform/actions/drift-check@d203a37c87b7a69c6040611c33e9e50c1d564ed1 # v1.5.0\n with:\n package-manager: __PACKAGE_MANAGER__\n # __MIGRATION_DRIFT_CHECK_START__\n - id: tailor-migration-drift-check\n uses: tailor-platform/actions/migration-drift-check@d203a37c87b7a69c6040611c33e9e50c1d564ed1 # v1.5.0\n with:\n package-manager: __PACKAGE_MANAGER__\n # __WORKING_DIRECTORY__\n # __MIGRATION_DRIFT_CHECK_END__\n - id: tailor-plan\n uses: tailor-platform/actions/plan@d203a37c87b7a69c6040611c33e9e50c1d564ed1 # v1.5.0\n with:\n workspace-id: ${{ vars.TAILOR_PLATFORM_WORKSPACE_ID }}\n package-manager: __PACKAGE_MANAGER__\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@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n - id: tailor-setup\n uses: tailor-platform/actions/setup@d203a37c87b7a69c6040611c33e9e50c1d564ed1 # v1.5.0\n with:\n package-manager: __PACKAGE_MANAGER__\n - id: tailor-install\n uses: tailor-platform/actions/install@d203a37c87b7a69c6040611c33e9e50c1d564ed1 # v1.5.0\n with:\n package-manager: __PACKAGE_MANAGER__\n - id: tailor-apply\n uses: tailor-platform/actions/deploy@d203a37c87b7a69c6040611c33e9e50c1d564ed1 # v1.5.0\n with:\n workspace-id: ${{ vars.TAILOR_PLATFORM_WORKSPACE_ID }}\n package-manager: __PACKAGE_MANAGER__\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 # __SEED_DATA_START__\n - id: seed-data\n if: false\n shell: bash\n # __WORKING_DIRECTORY__\n run: |\n # Uncomment and implement to seed data on deploy.\n # Run after apply so the schema is ready.\n # Example: node scripts/seed.mjs\n true\n # __SEED_DATA_END__\n - id: slack-prereq\n if: always()\n shell: bash\n env:\n SLACK_TOKEN: ${{ secrets.TAILOR_SLACK_BOT_TOKEN }}\n SLACK_CHANNEL_ID: ${{ vars.TAILOR_SLACK_CHANNEL_ID }}\n run: |\n if [[ -n \"$SLACK_TOKEN\" && -n \"$SLACK_CHANNEL_ID\" ]]; then\n echo \"ready=true\" >> \"$GITHUB_OUTPUT\"\n fi\n - id: tailor-notify\n if: always() && steps.slack-prereq.outputs.ready == 'true'\n uses: tailor-platform/actions/notify@d203a37c87b7a69c6040611c33e9e50c1d564ed1 # v1.5.0\n with:\n provider: slack\n status: ${{ job.status }}\n workspace-name: __WORKSPACE_NAME__\n slack-token: ${{ secrets.TAILOR_SLACK_BOT_TOKEN }}\n slack-channel-id: ${{ vars.TAILOR_SLACK_CHANNEL_ID }}\n # user-mapping: ${{ vars.TAILOR_SLACK_USER_MAPPING }}\n";
|
|
2830
2842
|
|
|
2831
2843
|
//#endregion
|
|
2832
2844
|
//#region src/cli/commands/setup/templates.ts
|
|
@@ -2870,29 +2882,39 @@ function applyCommon(content, params) {
|
|
|
2870
2882
|
}
|
|
2871
2883
|
/**
|
|
2872
2884
|
* Render the branch-target deploy workflow.
|
|
2873
|
-
*
|
|
2874
|
-
* When `plan` is false the plan job, the pull_request trigger, and the
|
|
2875
|
-
* workflow_dispatch dry-run input are all removed (a plan-less workflow has no
|
|
2876
|
-
* meaningful dry-run), and the deploy job runs unconditionally.
|
|
2877
2885
|
* @param params - Workspace and rendering configuration
|
|
2878
2886
|
* @returns Rendered YAML and the list of managed job/step ids
|
|
2879
2887
|
*/
|
|
2880
2888
|
function renderBranchWorkflow(params) {
|
|
2881
|
-
const { branch
|
|
2882
|
-
const
|
|
2889
|
+
const { branch } = params;
|
|
2890
|
+
const seedValidate = params.seedValidate ?? false;
|
|
2891
|
+
const migrationDriftCheck = params.migrationDriftCheck ?? false;
|
|
2892
|
+
const erdPreview = params.erdPreview;
|
|
2883
2893
|
let out = branch_workflow_default;
|
|
2884
|
-
out = block(out, "PLAN_JOB",
|
|
2894
|
+
out = block(out, "PLAN_JOB", true);
|
|
2885
2895
|
out = block(out, "ERD_PREVIEW_JOB", erdPreview !== null);
|
|
2886
2896
|
out = block(out, "ERD_PREVIEW_COMMENT_JOB", erdPreview !== null);
|
|
2887
|
-
out = block(out, "PULL_REQUEST",
|
|
2888
|
-
out = block(out, "DISPATCH_INPUTS",
|
|
2889
|
-
out =
|
|
2897
|
+
out = block(out, "PULL_REQUEST", true);
|
|
2898
|
+
out = block(out, "DISPATCH_INPUTS", true);
|
|
2899
|
+
out = block(out, "SEED_VALIDATE", seedValidate);
|
|
2900
|
+
out = block(out, "MIGRATION_DRIFT_CHECK", migrationDriftCheck);
|
|
2901
|
+
out = block(out, "SEED_DATA", false);
|
|
2902
|
+
out = line(out, "DEPLOY_IF", `if: >-\n github.event_name == 'push' ||\n (github.event_name == 'workflow_dispatch' && !inputs['dry-run'])`);
|
|
2890
2903
|
out = line(out, "PATHS", params.workingDirectory ? `paths: ["${params.workingDirectory}/**"]` : void 0);
|
|
2891
2904
|
out = applyCommon(out, params).replaceAll("__BRANCH__", () => branch);
|
|
2892
|
-
const generatedIds = [
|
|
2893
|
-
|
|
2905
|
+
const generatedIds = [
|
|
2906
|
+
"tailor-plan",
|
|
2907
|
+
"tailor-plan/tailor-checkout",
|
|
2908
|
+
"tailor-plan/tailor-setup",
|
|
2909
|
+
"tailor-plan/tailor-install",
|
|
2910
|
+
"tailor-plan/tailor-generate-check"
|
|
2911
|
+
];
|
|
2912
|
+
if (seedValidate) generatedIds.push("tailor-plan/tailor-seed-validate");
|
|
2913
|
+
generatedIds.push("tailor-plan/tailor-drift-check");
|
|
2914
|
+
if (migrationDriftCheck) generatedIds.push("tailor-plan/tailor-migration-drift-check");
|
|
2915
|
+
generatedIds.push("tailor-plan/tailor-plan");
|
|
2894
2916
|
if (erdPreview) generatedIds.push("tailor-erd-preview-matrix", "tailor-erd-preview-matrix/tailor-checkout", "tailor-erd-preview-matrix/tailor-checkout-base", "tailor-erd-preview-matrix/tailor-erd-preview-matrix", "tailor-erd-preview", "tailor-erd-preview/tailor-checkout", "tailor-erd-preview/tailor-setup", "tailor-erd-preview/tailor-checkout-base", "tailor-erd-preview/tailor-detect-base-package-manager", "tailor-erd-preview/tailor-setup-base-pnpm", "tailor-erd-preview/tailor-setup-base-node", "tailor-erd-preview/tailor-setup-base-yarn", "tailor-erd-preview/tailor-setup-base-bun", "tailor-erd-preview/tailor-install-base", "tailor-erd-preview/tailor-restore-head-setup", "tailor-erd-preview/tailor-build-erd-preview", "tailor-erd-preview/tailor-upload-erd-viewer", "tailor-erd-preview-comment", "tailor-erd-preview-comment/tailor-comment-erd-preview");
|
|
2895
|
-
generatedIds.push("tailor-deploy", "tailor-deploy/tailor-checkout", "tailor-deploy/tailor-setup", "tailor-deploy/tailor-apply");
|
|
2917
|
+
generatedIds.push("tailor-deploy", "tailor-deploy/tailor-checkout", "tailor-deploy/tailor-setup", "tailor-deploy/tailor-install", "tailor-deploy/tailor-apply", "tailor-deploy/tailor-notify");
|
|
2896
2918
|
return {
|
|
2897
2919
|
content: out,
|
|
2898
2920
|
generatedIds
|
|
@@ -2909,139 +2931,208 @@ function renderBranchWorkflow(params) {
|
|
|
2909
2931
|
function renderTagWorkflow(params) {
|
|
2910
2932
|
const { tagPattern, branch } = params;
|
|
2911
2933
|
const hasGuard = branch !== void 0;
|
|
2934
|
+
const seedValidate = params.seedValidate ?? false;
|
|
2935
|
+
const migrationDriftCheck = params.migrationDriftCheck ?? false;
|
|
2912
2936
|
let out = tag_workflow_default;
|
|
2913
2937
|
out = block(out, "TAG_GUARD_JOB", hasGuard);
|
|
2938
|
+
out = block(out, "SEED_VALIDATE", seedValidate);
|
|
2939
|
+
out = block(out, "MIGRATION_DRIFT_CHECK", migrationDriftCheck);
|
|
2940
|
+
out = block(out, "SEED_DATA", false);
|
|
2914
2941
|
out = line(out, "PLAN_NEEDS", hasGuard ? "needs: tailor-tag-guard" : void 0);
|
|
2915
2942
|
out = line(out, "PLAN_IF", hasGuard ? `if: >-\n github.event_name == 'workflow_dispatch' ||\n needs.tailor-tag-guard.outputs.on-branch == 'true'` : void 0);
|
|
2916
2943
|
out = applyCommon(out, params).replaceAll("__TAG_PATTERN__", () => tagPattern);
|
|
2917
2944
|
if (hasGuard) out = out.replaceAll("__BRANCH__", () => branch);
|
|
2918
2945
|
const generatedIds = [];
|
|
2919
2946
|
if (hasGuard) generatedIds.push("tailor-tag-guard", "tailor-tag-guard/tailor-checkout", "tailor-tag-guard/tailor-tag-guard");
|
|
2920
|
-
generatedIds.push("tailor-plan", "tailor-plan/tailor-checkout", "tailor-plan/tailor-setup", "tailor-plan/tailor-
|
|
2947
|
+
generatedIds.push("tailor-plan", "tailor-plan/tailor-checkout", "tailor-plan/tailor-setup", "tailor-plan/tailor-install", "tailor-plan/tailor-generate-check");
|
|
2948
|
+
if (seedValidate) generatedIds.push("tailor-plan/tailor-seed-validate");
|
|
2949
|
+
generatedIds.push("tailor-plan/tailor-drift-check");
|
|
2950
|
+
if (migrationDriftCheck) generatedIds.push("tailor-plan/tailor-migration-drift-check");
|
|
2951
|
+
generatedIds.push("tailor-plan/tailor-plan", "tailor-deploy", "tailor-deploy/tailor-checkout", "tailor-deploy/tailor-setup", "tailor-deploy/tailor-install", "tailor-deploy/tailor-apply", "tailor-deploy/tailor-notify");
|
|
2921
2952
|
return {
|
|
2922
2953
|
content: out,
|
|
2923
2954
|
generatedIds
|
|
2924
2955
|
};
|
|
2925
2956
|
}
|
|
2926
|
-
|
|
2927
|
-
//#endregion
|
|
2928
|
-
//#region src/cli/commands/setup/check.ts
|
|
2929
2957
|
/**
|
|
2930
|
-
*
|
|
2931
|
-
*
|
|
2932
|
-
*
|
|
2933
|
-
*
|
|
2934
|
-
*
|
|
2958
|
+
* Render the preview workflow (PR open/sync/close triggers).
|
|
2959
|
+
*
|
|
2960
|
+
* The deploy job runs on opened/synchronize/reopened events; the cleanup job
|
|
2961
|
+
* runs on the closed event. Both jobs run in the same workflow so PR number
|
|
2962
|
+
* context is always available.
|
|
2963
|
+
* @param params - Workspace and rendering configuration
|
|
2964
|
+
* @returns Rendered YAML and the list of managed job/step ids
|
|
2935
2965
|
*/
|
|
2936
|
-
function
|
|
2937
|
-
const
|
|
2938
|
-
const
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
message: `The workflow triggers on "${target.inputs.branch}" but the repository default branch is now "${state.defaultBranch}". If this is intentional, ignore this; otherwise re-run setup so the trigger matches the default branch.`
|
|
2963
|
-
});
|
|
2964
|
-
if (target.kind === "branch" && target.inputs.erdPreview && state.configExists) {
|
|
2965
|
-
const recorded = [...target.inputs.erdNamespaces ?? []].toSorted((a, b) => a.localeCompare(b));
|
|
2966
|
-
const current = state.erdNamespaces?.toSorted((a, b) => a.localeCompare(b)) ?? null;
|
|
2967
|
-
if (current === null || recorded.length !== current.length || recorded.some((namespace, index) => namespace !== current[index])) findings.push({
|
|
2968
|
-
target: id,
|
|
2969
|
-
rule: "erd-namespaces",
|
|
2970
|
-
message: "TailorDB namespaces for ERD preview changed. Re-run setup so the ERD preview matrix is regenerated."
|
|
2971
|
-
});
|
|
2972
|
-
}
|
|
2973
|
-
return findings;
|
|
2974
|
-
}
|
|
2975
|
-
function detectDefaultBranchSafe(cwd, run) {
|
|
2976
|
-
try {
|
|
2977
|
-
return detectDefaultBranch(cwd, run);
|
|
2978
|
-
} catch {
|
|
2979
|
-
return null;
|
|
2980
|
-
}
|
|
2981
|
-
}
|
|
2982
|
-
function escapesRoot$1(rel) {
|
|
2983
|
-
return path.isAbsolute(rel) || rel === ".." || rel.startsWith(`..${path.sep}`) || rel.startsWith("../");
|
|
2966
|
+
function renderPreviewWorkflow(params) {
|
|
2967
|
+
const { branch } = params;
|
|
2968
|
+
const requirePreviewLabel = params.requirePreviewLabel ?? false;
|
|
2969
|
+
let out = preview_workflow_default;
|
|
2970
|
+
out = line(out, "PATHS", params.workingDirectory ? `paths: ["${params.workingDirectory}/**"]` : void 0);
|
|
2971
|
+
out = line(out, "PR_TYPES", requirePreviewLabel ? "types: [labeled, synchronize, reopened, closed]" : "types: [opened, synchronize, reopened, closed]");
|
|
2972
|
+
out = line(out, "DEPLOY_IF", requirePreviewLabel ? `if: >-\n contains(github.event.pull_request.labels.*.name, 'tailor:preview') &&\n github.event.action != 'closed' &&\n !github.event.pull_request.draft &&\n !github.event.pull_request.head.repo.fork` : `if: >-\n github.event.action != 'closed' &&\n !github.event.pull_request.draft &&\n !github.event.pull_request.head.repo.fork`);
|
|
2973
|
+
out = line(out, "CLEANUP_IF", `if: >-\n github.event.action == 'closed' &&\n !github.event.pull_request.head.repo.fork`);
|
|
2974
|
+
out = applyCommon(out, params).replaceAll("__BRANCH__", () => branch).replaceAll("__REGION__", () => params.region);
|
|
2975
|
+
return {
|
|
2976
|
+
content: out,
|
|
2977
|
+
generatedIds: [
|
|
2978
|
+
"tailor-preview-deploy",
|
|
2979
|
+
"tailor-preview-deploy/tailor-checkout",
|
|
2980
|
+
"tailor-preview-deploy/tailor-setup",
|
|
2981
|
+
"tailor-preview-deploy/tailor-install",
|
|
2982
|
+
"tailor-preview-deploy/tailor-generate-check",
|
|
2983
|
+
"tailor-preview-deploy/tailor-preview-deploy",
|
|
2984
|
+
"tailor-preview-deploy/tailor-preview-comment",
|
|
2985
|
+
"tailor-preview-cleanup",
|
|
2986
|
+
"tailor-preview-cleanup/tailor-checkout",
|
|
2987
|
+
"tailor-preview-cleanup/tailor-setup",
|
|
2988
|
+
"tailor-preview-cleanup/tailor-install",
|
|
2989
|
+
"tailor-preview-cleanup/tailor-preview-cleanup"
|
|
2990
|
+
]
|
|
2991
|
+
};
|
|
2984
2992
|
}
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
+
/**
|
|
2994
|
+
* Render the per-app composite action.
|
|
2995
|
+
*
|
|
2996
|
+
* The action contains only the deploy step and a failure notification.
|
|
2997
|
+
* Checkout, setup, and install are the caller's responsibility (coordinator workflow).
|
|
2998
|
+
* @param params - Workspace and rendering configuration
|
|
2999
|
+
* @returns Rendered YAML and the list of managed step ids
|
|
3000
|
+
*/
|
|
3001
|
+
function renderActionWorkflow(params) {
|
|
3002
|
+
let out = action_default;
|
|
3003
|
+
out = line(out, "HEADER", HEADER);
|
|
3004
|
+
out = block(out, "STATIC_WEBSITE_BUILD", params.hasStaticWebsites ?? false);
|
|
3005
|
+
out = out.replaceAll("__WORKSPACE_NAME__", () => params.workspaceName);
|
|
3006
|
+
return {
|
|
3007
|
+
content: out,
|
|
3008
|
+
generatedIds: ["tailor-apply", "tailor-notify"]
|
|
3009
|
+
};
|
|
2993
3010
|
}
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
|
|
3011
|
+
const ACTIONS_SHA = "d203a37c87b7a69c6040611c33e9e50c1d564ed1";
|
|
3012
|
+
/**
|
|
3013
|
+
* Render the coordinator workflow that orchestrates per-app composite actions.
|
|
3014
|
+
*
|
|
3015
|
+
* Generates per-app plan steps (generate-check, drift-check, plan) and per-app
|
|
3016
|
+
* deploy steps (calling each app's composite action at .github/actions/tailor-<name>).
|
|
3017
|
+
* Checkout, setup, and install are done once per job using the local tailor-setup action.
|
|
3018
|
+
* @param params - Coordinator and app configuration
|
|
3019
|
+
* @returns Rendered YAML and the list of managed job/step ids
|
|
3020
|
+
*/
|
|
3021
|
+
function renderCoordinateWorkflow(params) {
|
|
3022
|
+
const { coordinatorName, kind, apps, environment, packageManager } = params;
|
|
3023
|
+
const isTag = kind === "tag";
|
|
3024
|
+
const branch = params.branch;
|
|
3025
|
+
const tagPattern = params.tagPattern ?? "v*";
|
|
3026
|
+
let out = coordinate_workflow_default;
|
|
3027
|
+
out = line(out, "HEADER", HEADER);
|
|
3028
|
+
out = block(out, "PULL_REQUEST", !isTag);
|
|
3029
|
+
out = block(out, "PUSH_BRANCHES", !isTag);
|
|
3030
|
+
out = block(out, "PUSH_TAGS", isTag);
|
|
3031
|
+
out = block(out, "TAG_GUARD_JOB", isTag && branch !== void 0);
|
|
3032
|
+
if (isTag && branch !== void 0) {
|
|
3033
|
+
out = line(out, "PLAN_NEEDS", "needs: tailor-tag-guard");
|
|
3034
|
+
out = line(out, "PLAN_IF", `if: >-\n github.event_name == 'workflow_dispatch' ||\n needs.tailor-tag-guard.outputs.on-branch == 'true'`);
|
|
3035
|
+
} else if (isTag) {
|
|
3036
|
+
out = line(out, "PLAN_NEEDS", void 0);
|
|
3037
|
+
out = line(out, "PLAN_IF", void 0);
|
|
3038
|
+
} else {
|
|
3039
|
+
out = line(out, "PLAN_NEEDS", void 0);
|
|
3040
|
+
out = line(out, "PLAN_IF", `if: >-\n github.event_name == 'pull_request' ||\n (github.event_name == 'workflow_dispatch' && inputs['dry-run'])`);
|
|
2999
3041
|
}
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
|
|
3042
|
+
if (isTag) {
|
|
3043
|
+
out = line(out, "DEPLOY_NEEDS", "needs: tailor-plan");
|
|
3044
|
+
out = line(out, "DEPLOY_IF", `if: \${{ !(github.event_name == 'workflow_dispatch' && inputs['dry-run']) }}`);
|
|
3045
|
+
out = line(out, "DEPLOY_ENVIRONMENT", `environment: ${environment}`);
|
|
3046
|
+
} else {
|
|
3047
|
+
out = line(out, "DEPLOY_NEEDS", void 0);
|
|
3048
|
+
out = line(out, "DEPLOY_IF", `if: >-\n github.event_name == 'push' ||\n (github.event_name == 'workflow_dispatch' && !inputs['dry-run'])`);
|
|
3049
|
+
out = line(out, "DEPLOY_ENVIRONMENT", `environment: ${environment}`);
|
|
3050
|
+
}
|
|
3051
|
+
const driftCheckStep = [
|
|
3052
|
+
`- id: tailor-drift-check`,
|
|
3053
|
+
` uses: tailor-platform/actions/drift-check@${ACTIONS_SHA} # v1.5.0`,
|
|
3054
|
+
` with:`,
|
|
3055
|
+
` package-manager: ${packageManager}`
|
|
3056
|
+
].join("\n");
|
|
3057
|
+
const perAppPlanSteps = apps.flatMap((app) => {
|
|
3058
|
+
const wd = app.dir !== "." ? app.dir : void 0;
|
|
3059
|
+
const wdLine = wd ? `\n working-directory: ${wd}` : "";
|
|
3060
|
+
return [
|
|
3061
|
+
`- id: tailor-generate-check-${app.name}`,
|
|
3062
|
+
` uses: tailor-platform/actions/generate-check@${ACTIONS_SHA} # v1.5.0`,
|
|
3063
|
+
` with:`,
|
|
3064
|
+
` package-manager: ${packageManager}${wdLine}`,
|
|
3065
|
+
`- id: tailor-plan-${app.name}`,
|
|
3066
|
+
` if: github.event_name != 'pull_request' || !github.event.pull_request.head.repo.fork`,
|
|
3067
|
+
` uses: tailor-platform/actions/plan@${ACTIONS_SHA} # v1.5.0`,
|
|
3068
|
+
` with:`,
|
|
3069
|
+
` workspace-id: \${{ vars.TAILOR_PLATFORM_WORKSPACE_ID }}`,
|
|
3070
|
+
` package-manager: ${packageManager}${wdLine}`,
|
|
3071
|
+
` label: ${coordinatorName}/${app.name}`,
|
|
3072
|
+
` platform-client-id: \${{ secrets.TAILOR_PLATFORM_MACHINE_USER_CLIENT_ID }}`,
|
|
3073
|
+
` platform-client-secret: \${{ secrets.TAILOR_PLATFORM_MACHINE_USER_CLIENT_SECRET }}`,
|
|
3074
|
+
` github-token: \${{ secrets.GITHUB_TOKEN }}`
|
|
3075
|
+
];
|
|
3076
|
+
}).join("\n");
|
|
3077
|
+
out = line(out, "PER_APP_PLAN_STEPS", `${driftCheckStep}\n${perAppPlanSteps}`);
|
|
3078
|
+
const deploySteps = apps.flatMap((app) => [
|
|
3079
|
+
`- id: tailor-deploy-${app.name}`,
|
|
3080
|
+
` uses: ./.github/actions/tailor-${app.name}`,
|
|
3081
|
+
` with:`,
|
|
3082
|
+
` workspace-id: \${{ vars.TAILOR_PLATFORM_WORKSPACE_ID }}`,
|
|
3083
|
+
` name: ${app.name}`,
|
|
3084
|
+
` working-directory: ${app.dir}`,
|
|
3085
|
+
` package-manager: ${packageManager}`,
|
|
3086
|
+
` platform-client-id: \${{ secrets.TAILOR_PLATFORM_MACHINE_USER_CLIENT_ID }}`,
|
|
3087
|
+
` platform-client-secret: \${{ secrets.TAILOR_PLATFORM_MACHINE_USER_CLIENT_SECRET }}`,
|
|
3088
|
+
` slack-token: \${{ secrets.TAILOR_SLACK_BOT_TOKEN }}`,
|
|
3089
|
+
` slack-channel-id: \${{ vars.TAILOR_SLACK_CHANNEL_ID }}`,
|
|
3090
|
+
` # editable: api-url:`
|
|
3091
|
+
]).join("\n");
|
|
3092
|
+
out = line(out, "PER_APP_DEPLOY_STEPS", deploySteps);
|
|
3093
|
+
out = out.replaceAll("__WORKSPACE_NAME__", () => coordinatorName);
|
|
3094
|
+
out = out.replaceAll("__ENVIRONMENT__", () => environment);
|
|
3095
|
+
if (branch !== void 0) out = out.replaceAll("__BRANCH__", () => branch);
|
|
3096
|
+
if (isTag) out = out.replaceAll("__TAG_PATTERN__", () => tagPattern);
|
|
3097
|
+
const generatedIds = [];
|
|
3098
|
+
if (isTag && branch !== void 0) generatedIds.push("tailor-tag-guard", "tailor-tag-guard/tailor-checkout", "tailor-tag-guard/tailor-tag-guard");
|
|
3099
|
+
generatedIds.push("tailor-plan", "tailor-plan/tailor-checkout", "tailor-plan/tailor-setup", "tailor-plan/tailor-drift-check");
|
|
3100
|
+
for (const app of apps) generatedIds.push(`tailor-plan/tailor-generate-check-${app.name}`, `tailor-plan/tailor-plan-${app.name}`);
|
|
3101
|
+
generatedIds.push("tailor-deploy", "tailor-deploy/tailor-checkout", "tailor-deploy/tailor-setup");
|
|
3102
|
+
for (const app of apps) generatedIds.push(`tailor-deploy/tailor-deploy-${app.name}`);
|
|
3103
|
+
return {
|
|
3104
|
+
content: out,
|
|
3105
|
+
generatedIds
|
|
3106
|
+
};
|
|
3004
3107
|
}
|
|
3005
3108
|
/**
|
|
3006
|
-
*
|
|
3007
|
-
* state. Read-only: never writes files, the lock, or the config.
|
|
3109
|
+
* Render the content of the user-owned .github/actions/tailor-setup/action.yml local action.
|
|
3008
3110
|
*
|
|
3009
|
-
*
|
|
3010
|
-
*
|
|
3011
|
-
*
|
|
3012
|
-
* @
|
|
3111
|
+
* This file is generated once by `setup coordinate` and never overwritten.
|
|
3112
|
+
* It wraps the setup and install actions with the package manager baked in.
|
|
3113
|
+
* @param params - Package manager configuration
|
|
3114
|
+
* @returns File content string
|
|
3013
3115
|
*/
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
templateVersion: 5,
|
|
3035
|
-
erdNamespaces
|
|
3036
|
-
}));
|
|
3037
|
-
}
|
|
3038
|
-
const count = lock.targets.length;
|
|
3039
|
-
if (findings.length === 0) {
|
|
3040
|
-
logger.success(`No drift detected across ${String(count)} target(s).`);
|
|
3041
|
-
return;
|
|
3042
|
-
}
|
|
3043
|
-
for (const finding of findings) logger.warn(`[${finding.target}] ${finding.message} (ignore key: ${finding.rule})`);
|
|
3044
|
-
throw new Error(`Detected ${String(findings.length)} drift finding(s) across ${String(count)} target(s). Re-run \`tailor-sdk setup\` to regenerate, or address each finding above.`);
|
|
3116
|
+
function renderTailorSetupAction(params) {
|
|
3117
|
+
const { packageManager } = params;
|
|
3118
|
+
return [
|
|
3119
|
+
`# This file is user-owned. Generated once by \`tailor-sdk setup coordinate\` and never overwritten.`,
|
|
3120
|
+
`# Customize freely: add pre/post steps, change install flags, etc.`,
|
|
3121
|
+
`name: Tailor Setup`,
|
|
3122
|
+
`description: Install dependencies and run tailor-sdk setup for composite action callers.`,
|
|
3123
|
+
`runs:`,
|
|
3124
|
+
` using: composite`,
|
|
3125
|
+
` steps:`,
|
|
3126
|
+
` - uses: tailor-platform/actions/setup@${ACTIONS_SHA} # v1.5.0`,
|
|
3127
|
+
` with:`,
|
|
3128
|
+
` package-manager: ${packageManager}`,
|
|
3129
|
+
` # Add custom steps here (e.g. private registry authentication)`,
|
|
3130
|
+
` - uses: tailor-platform/actions/install@${ACTIONS_SHA} # v1.5.0`,
|
|
3131
|
+
` with:`,
|
|
3132
|
+
` package-manager: ${packageManager}`,
|
|
3133
|
+
` # editable: install-command:`,
|
|
3134
|
+
``
|
|
3135
|
+
].join("\n");
|
|
3045
3136
|
}
|
|
3046
3137
|
|
|
3047
3138
|
//#endregion
|
|
@@ -3050,13 +3141,25 @@ async function defaultLoadConfigName(configPath) {
|
|
|
3050
3141
|
const { config } = await loadConfig(configPath);
|
|
3051
3142
|
return config.name;
|
|
3052
3143
|
}
|
|
3053
|
-
async function defaultLoadErdNamespaces(configPath) {
|
|
3144
|
+
async function defaultLoadErdNamespaces$1(configPath) {
|
|
3054
3145
|
const { config } = await loadConfig(configPath);
|
|
3055
3146
|
return extractOwnedNamespaces(config);
|
|
3056
3147
|
}
|
|
3148
|
+
async function defaultLoadHasMigrations$1(configPath) {
|
|
3149
|
+
const { config } = await loadConfig(configPath);
|
|
3150
|
+
return getNamespacesWithMigrations(config, path.dirname(configPath)).length > 0;
|
|
3151
|
+
}
|
|
3152
|
+
async function defaultLoadHasSeeds$1(configPath) {
|
|
3153
|
+
const { plugins } = await loadConfig(configPath);
|
|
3154
|
+
return plugins.some((p) => p.id === "@tailor-platform/seed");
|
|
3155
|
+
}
|
|
3156
|
+
async function defaultLoadHasStaticWebsites$1(configPath) {
|
|
3157
|
+
const { config } = await loadConfig(configPath);
|
|
3158
|
+
return (config.staticWebsites?.length ?? 0) > 0;
|
|
3159
|
+
}
|
|
3057
3160
|
const WORKSPACE_NAME_RE = /^[a-z0-9-]+$/;
|
|
3058
3161
|
function validateWorkspaceName(name) {
|
|
3059
|
-
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 --
|
|
3162
|
+
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 --name.`);
|
|
3060
3163
|
}
|
|
3061
3164
|
const BRANCH_RE = /^[A-Za-z0-9._/-]+$/;
|
|
3062
3165
|
const TAG_PATTERN_RE = /^[A-Za-z0-9._/*?![\]-]+$/;
|
|
@@ -3070,6 +3173,10 @@ const ENVIRONMENT_RE = /^[A-Za-z0-9._/-]+$/;
|
|
|
3070
3173
|
function validateEnvironment(environment) {
|
|
3071
3174
|
if (!ENVIRONMENT_RE.test(environment)) throw new Error(`Invalid environment name "${environment}". Only letters, numbers, ".", "_", "/", and "-" are supported.`);
|
|
3072
3175
|
}
|
|
3176
|
+
const REGION_RE = /^[A-Za-z0-9._-]+$/;
|
|
3177
|
+
function validateRegion(region) {
|
|
3178
|
+
if (!REGION_RE.test(region)) throw new Error(`Invalid region "${region}". Only letters, numbers, ".", "_", and "-" are supported.`);
|
|
3179
|
+
}
|
|
3073
3180
|
const DIR_RE = /^[A-Za-z0-9._/-]+$/;
|
|
3074
3181
|
function validateDir(dir) {
|
|
3075
3182
|
if (!DIR_RE.test(dir)) throw new Error(`Invalid --dir "${dir}". Only letters, numbers, ".", "_", "/", and "-" are supported.`);
|
|
@@ -3078,7 +3185,7 @@ const ERD_NAMESPACE_RE = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
|
3078
3185
|
function validateErdNamespaces(namespaces) {
|
|
3079
3186
|
for (const namespace of namespaces) if (!ERD_NAMESPACE_RE.test(namespace)) throw new Error(`TailorDB namespace "${namespace}" cannot be used in --erd-preview. Only letters, numbers, '.', '_', and '-' are supported, and the name must start with a letter or number.`);
|
|
3080
3187
|
}
|
|
3081
|
-
function escapesRoot(rel) {
|
|
3188
|
+
function escapesRoot$1(rel) {
|
|
3082
3189
|
return rel === ".." || rel.startsWith(`..${path.sep}`) || rel.startsWith("../") || path.isAbsolute(rel);
|
|
3083
3190
|
}
|
|
3084
3191
|
/**
|
|
@@ -3094,11 +3201,11 @@ function escapesRoot(rel) {
|
|
|
3094
3201
|
function resolveConfigPath(outputDir, dir) {
|
|
3095
3202
|
const appDir = path.resolve(outputDir, dir);
|
|
3096
3203
|
const rel = path.relative(outputDir, appDir);
|
|
3097
|
-
if (path.isAbsolute(dir) || escapesRoot(rel)) throw new Error(`--dir must be a relative path inside the repository (got "${dir}").`);
|
|
3204
|
+
if (path.isAbsolute(dir) || escapesRoot$1(rel)) throw new Error(`--dir must be a relative path inside the repository (got "${dir}").`);
|
|
3098
3205
|
if (fs$1.existsSync(appDir)) {
|
|
3099
3206
|
const realAppDir = path.normalize(fs$1.realpathSync(appDir));
|
|
3100
3207
|
const realOutputDir = path.normalize(fs$1.realpathSync(outputDir));
|
|
3101
|
-
if (escapesRoot(path.relative(realOutputDir, realAppDir))) throw new Error(`--dir must resolve to a directory inside the repository (got "${dir}", which links outside it).`);
|
|
3208
|
+
if (escapesRoot$1(path.relative(realOutputDir, realAppDir))) throw new Error(`--dir must resolve to a directory inside the repository (got "${dir}", which links outside it).`);
|
|
3102
3209
|
}
|
|
3103
3210
|
const configPath = path.join(appDir, "tailor.config.ts");
|
|
3104
3211
|
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.`);
|
|
@@ -3110,17 +3217,15 @@ function resolveConfigPath(outputDir, dir) {
|
|
|
3110
3217
|
* @returns Resolved target metadata and rendered content
|
|
3111
3218
|
*/
|
|
3112
3219
|
async function resolve$1(options) {
|
|
3113
|
-
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.");
|
|
3114
|
-
if (options.erdPreview && (options.tag || !options.plan)) throw new Error("--erd-preview requires a branch target with plan enabled.");
|
|
3115
3220
|
const dir = options.dir.replaceAll("\\", "/").replace(/\/{2,}/g, "/").replace(/^\.\//, "").replace(/\/$/, "") || ".";
|
|
3116
3221
|
validateDir(dir);
|
|
3117
3222
|
const workingDirectory = dir !== "." ? dir : void 0;
|
|
3118
3223
|
const configPath = resolveConfigPath(options.outputDir, dir);
|
|
3119
3224
|
const loadName = options.loadConfigName ?? defaultLoadConfigName;
|
|
3120
3225
|
const workspaceName = options.workspaceName ?? await loadName(configPath);
|
|
3121
|
-
if (!workspaceName) throw new Error("Could not determine the workspace name. Pass --
|
|
3226
|
+
if (!workspaceName) throw new Error("Could not determine the workspace name. Pass --name, or set 'name' in tailor.config.ts.");
|
|
3122
3227
|
validateWorkspaceName(workspaceName);
|
|
3123
|
-
const kind = options
|
|
3228
|
+
const { kind } = options;
|
|
3124
3229
|
const packageManager = detectPackageManager(options.outputDir);
|
|
3125
3230
|
const environment = options.environment ?? workspaceName;
|
|
3126
3231
|
validateEnvironment(environment);
|
|
@@ -3129,47 +3234,85 @@ async function resolve$1(options) {
|
|
|
3129
3234
|
let branchAutoDetected = false;
|
|
3130
3235
|
let render;
|
|
3131
3236
|
let erdNamespaces = [];
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
|
-
|
|
3135
|
-
|
|
3136
|
-
|
|
3237
|
+
let hasMigrations = false;
|
|
3238
|
+
let hasSeeds = false;
|
|
3239
|
+
let hasStaticWebsites = false;
|
|
3240
|
+
const loadHasMigrations = options.loadHasMigrations ?? defaultLoadHasMigrations$1;
|
|
3241
|
+
const loadHasSeeds = options.loadHasSeeds ?? defaultLoadHasSeeds$1;
|
|
3242
|
+
const loadHasStaticWebsites = options.loadHasStaticWebsites ?? defaultLoadHasStaticWebsites$1;
|
|
3137
3243
|
if (kind === "branch") {
|
|
3244
|
+
if (options.erdPreview) {
|
|
3245
|
+
erdNamespaces = await (options.loadErdNamespaces ?? defaultLoadErdNamespaces$1)(configPath);
|
|
3246
|
+
if (erdNamespaces.length === 0) throw new Error("No TailorDB namespaces found for --erd-preview. Define owned db namespaces in tailor.config.ts.");
|
|
3247
|
+
validateErdNamespaces(erdNamespaces);
|
|
3248
|
+
}
|
|
3138
3249
|
branchAutoDetected = options.branch === void 0;
|
|
3139
3250
|
branch = options.branch ?? detectDefaultBranch(options.outputDir, options.gitRunner);
|
|
3140
3251
|
validateBranch(branch);
|
|
3252
|
+
hasMigrations = await loadHasMigrations(configPath);
|
|
3253
|
+
hasSeeds = await loadHasSeeds(configPath);
|
|
3141
3254
|
render = renderBranchWorkflow({
|
|
3142
3255
|
workspaceName,
|
|
3143
3256
|
branch,
|
|
3144
3257
|
workingDirectory,
|
|
3145
3258
|
environment,
|
|
3146
3259
|
packageManager,
|
|
3147
|
-
|
|
3148
|
-
|
|
3260
|
+
erdPreview: options.erdPreview ? { namespaces: erdNamespaces } : null,
|
|
3261
|
+
migrationDriftCheck: hasMigrations,
|
|
3262
|
+
seedValidate: hasSeeds
|
|
3149
3263
|
});
|
|
3150
|
-
} else {
|
|
3264
|
+
} else if (kind === "tag") {
|
|
3151
3265
|
branch = options.branch ?? null;
|
|
3152
3266
|
if (branch !== null) validateBranch(branch);
|
|
3267
|
+
hasMigrations = await loadHasMigrations(configPath);
|
|
3268
|
+
hasSeeds = await loadHasSeeds(configPath);
|
|
3153
3269
|
render = renderTagWorkflow({
|
|
3154
3270
|
workspaceName,
|
|
3155
3271
|
tagPattern: options.tagPattern,
|
|
3156
3272
|
branch: options.branch,
|
|
3157
3273
|
workingDirectory,
|
|
3158
3274
|
environment,
|
|
3159
|
-
packageManager
|
|
3275
|
+
packageManager,
|
|
3276
|
+
migrationDriftCheck: hasMigrations,
|
|
3277
|
+
seedValidate: hasSeeds
|
|
3278
|
+
});
|
|
3279
|
+
} else if (kind === "preview") {
|
|
3280
|
+
branchAutoDetected = options.branch === void 0;
|
|
3281
|
+
branch = options.branch ?? detectDefaultBranch(options.outputDir, options.gitRunner);
|
|
3282
|
+
validateBranch(branch);
|
|
3283
|
+
validateRegion(options.region);
|
|
3284
|
+
render = renderPreviewWorkflow({
|
|
3285
|
+
workspaceName,
|
|
3286
|
+
branch,
|
|
3287
|
+
workingDirectory,
|
|
3288
|
+
environment,
|
|
3289
|
+
packageManager,
|
|
3290
|
+
region: options.region,
|
|
3291
|
+
requirePreviewLabel: options.requirePreviewLabel ?? false
|
|
3292
|
+
});
|
|
3293
|
+
} else {
|
|
3294
|
+
hasStaticWebsites = await loadHasStaticWebsites(configPath);
|
|
3295
|
+
render = renderActionWorkflow({
|
|
3296
|
+
workspaceName,
|
|
3297
|
+
workingDirectory,
|
|
3298
|
+
hasStaticWebsites
|
|
3160
3299
|
});
|
|
3161
3300
|
}
|
|
3162
|
-
const file = `.github/workflows/tailor-${workspaceName}.yml`;
|
|
3301
|
+
const file = kind === "action" ? `.github/actions/tailor-${workspaceName}/action.yml` : `.github/workflows/tailor-${workspaceName}${kind === "tag" ? "-tag" : kind === "preview" ? "-preview" : ""}.yml`;
|
|
3163
3302
|
const inputs = {
|
|
3164
|
-
branch,
|
|
3165
|
-
branchAutoDetected: kind === "branch" ? branchAutoDetected : void 0,
|
|
3303
|
+
branch: kind === "action" ? null : branch,
|
|
3304
|
+
branchAutoDetected: kind === "branch" || kind === "preview" ? branchAutoDetected : void 0,
|
|
3166
3305
|
tagPattern: kind === "tag" ? options.tagPattern : null,
|
|
3167
3306
|
environment,
|
|
3168
3307
|
dir,
|
|
3169
3308
|
packageManager,
|
|
3170
|
-
|
|
3309
|
+
region: kind === "preview" ? options.region : void 0,
|
|
3310
|
+
requirePreviewLabel: kind === "preview" ? options.requirePreviewLabel ?? false : void 0,
|
|
3171
3311
|
erdPreview: kind === "branch" ? options.erdPreview : false,
|
|
3172
|
-
erdNamespaces: kind === "branch" && options.erdPreview ? erdNamespaces : void 0
|
|
3312
|
+
erdNamespaces: kind === "branch" && options.erdPreview ? erdNamespaces : void 0,
|
|
3313
|
+
migrationDriftCheck: kind === "branch" || kind === "tag" ? hasMigrations : void 0,
|
|
3314
|
+
seedValidate: kind === "branch" || kind === "tag" ? hasSeeds : void 0,
|
|
3315
|
+
hasStaticWebsites: kind === "action" ? hasStaticWebsites : void 0
|
|
3173
3316
|
};
|
|
3174
3317
|
return {
|
|
3175
3318
|
kind,
|
|
@@ -3184,6 +3327,40 @@ async function resolve$1(options) {
|
|
|
3184
3327
|
configPath
|
|
3185
3328
|
};
|
|
3186
3329
|
}
|
|
3330
|
+
const BUILD_SITE_RUN_RE = /([ \t]*- id: build-site\n[ \t]+shell: bash\n[ \t]+run: \|)([\s\S]*?)(\n[ \t]*- |\n*$)/;
|
|
3331
|
+
/**
|
|
3332
|
+
* Strip the mutable run: body of the build-site step before hashing.
|
|
3333
|
+
* When hasStaticWebsites is true the step is present; users are expected to
|
|
3334
|
+
* replace the placeholder command with their actual build command. We only
|
|
3335
|
+
* hash the structural parts (step id, shell declaration, run: key) so that
|
|
3336
|
+
* editing the build command does not look like unintended drift.
|
|
3337
|
+
* When hasStaticWebsites is false the step is absent, so the regex is a no-op.
|
|
3338
|
+
* @param content - Raw action workflow content
|
|
3339
|
+
* @returns Normalised content with the build-site run body replaced by a placeholder
|
|
3340
|
+
*/
|
|
3341
|
+
function normalizeActionContent(content) {
|
|
3342
|
+
return content.replace(BUILD_SITE_RUN_RE, (_, header, _body, tail) => `${header}\n true${tail}`);
|
|
3343
|
+
}
|
|
3344
|
+
/**
|
|
3345
|
+
* Extract the run: body of the build-site step from action content.
|
|
3346
|
+
* Returns null when the step is absent (hasStaticWebsites is false).
|
|
3347
|
+
* @param content - Action workflow content
|
|
3348
|
+
* @returns The captured run body string (includes leading newline), or null
|
|
3349
|
+
*/
|
|
3350
|
+
function extractBuildSiteRunBody(content) {
|
|
3351
|
+
const m = BUILD_SITE_RUN_RE.exec(content);
|
|
3352
|
+
return m ? m[2] ?? null : null;
|
|
3353
|
+
}
|
|
3354
|
+
/**
|
|
3355
|
+
* Replace the run: body of the build-site step in action content.
|
|
3356
|
+
* No-op when the step is absent.
|
|
3357
|
+
* @param content - Target action workflow content
|
|
3358
|
+
* @param body - Run body to inject (includes leading newline and indentation)
|
|
3359
|
+
* @returns Content with the run body replaced
|
|
3360
|
+
*/
|
|
3361
|
+
function injectBuildSiteRunBody(content, body) {
|
|
3362
|
+
return content.replace(BUILD_SITE_RUN_RE, (_, header, _placeholder, tail) => `${header}${body}${tail}`);
|
|
3363
|
+
}
|
|
3187
3364
|
/**
|
|
3188
3365
|
* Decide how to reconcile a target with the on-disk file and lock state.
|
|
3189
3366
|
* @param obj - Decision inputs
|
|
@@ -3191,10 +3368,11 @@ async function resolve$1(options) {
|
|
|
3191
3368
|
* @param obj.fileExists - Whether the workflow file is present on disk
|
|
3192
3369
|
* @param obj.currentContent - On-disk content when present
|
|
3193
3370
|
* @param obj.force - Whether --force was passed
|
|
3371
|
+
* @param obj.normalize - Optional content normaliser applied before hashing
|
|
3194
3372
|
* @returns The reconciliation action
|
|
3195
3373
|
*/
|
|
3196
3374
|
function decideAction(obj) {
|
|
3197
|
-
const { existing, fileExists, currentContent, force } = obj;
|
|
3375
|
+
const { existing, fileExists, currentContent, force, normalize } = obj;
|
|
3198
3376
|
if (!existing) {
|
|
3199
3377
|
if (!fileExists) return { action: "create" };
|
|
3200
3378
|
if (force) return { action: "regenerate" };
|
|
@@ -3204,7 +3382,9 @@ function decideAction(obj) {
|
|
|
3204
3382
|
};
|
|
3205
3383
|
}
|
|
3206
3384
|
if (!fileExists) return { action: "restore" };
|
|
3207
|
-
if (currentContent !== null
|
|
3385
|
+
if (currentContent !== null) {
|
|
3386
|
+
if (hashContent(normalize ? normalize(currentContent) : currentContent) === existing.contentHash) return { action: "regenerate" };
|
|
3387
|
+
}
|
|
3208
3388
|
if (force) return { action: "regenerate" };
|
|
3209
3389
|
return {
|
|
3210
3390
|
action: "conflict",
|
|
@@ -3222,7 +3402,7 @@ function decideAction(obj) {
|
|
|
3222
3402
|
function assertNoKindCollision(obj) {
|
|
3223
3403
|
const { lock, kind, workspaceName, file } = obj;
|
|
3224
3404
|
const collision = lock?.targets.find((t) => t.file === file && !(t.kind === kind && t.workspaceName === workspaceName));
|
|
3225
|
-
if (collision) throw new Error(`A ${collision.kind} target already owns ${file}, which conflicts with this ${kind} target. Pass a different name with --
|
|
3405
|
+
if (collision) throw new Error(`A ${collision.kind} target already owns ${file}, which conflicts with this ${kind} target. Pass a different name with --name to generate a separate workflow.`);
|
|
3226
3406
|
}
|
|
3227
3407
|
/**
|
|
3228
3408
|
* Print next-step guidance after generating workflow files.
|
|
@@ -3249,11 +3429,10 @@ function printNextSteps(obj) {
|
|
|
3249
3429
|
if (idInjected) logger.log(" - tailor.config.ts (app id was added)");
|
|
3250
3430
|
}
|
|
3251
3431
|
/**
|
|
3252
|
-
* Generate
|
|
3253
|
-
* with the lock file.
|
|
3432
|
+
* Generate a deploy target workflow and reconcile it with the lock file.
|
|
3254
3433
|
* @param options - Setup options
|
|
3255
3434
|
*/
|
|
3256
|
-
async function
|
|
3435
|
+
async function setupTarget(options) {
|
|
3257
3436
|
logBetaWarning("setup");
|
|
3258
3437
|
const resolved = await resolve$1(options);
|
|
3259
3438
|
const lock = readLock(options.outputDir);
|
|
@@ -3266,27 +3445,35 @@ async function setupGitHub(options) {
|
|
|
3266
3445
|
});
|
|
3267
3446
|
const existing = findTarget(lock, resolved.kind, resolved.workspaceName);
|
|
3268
3447
|
const fileExists = fs$1.existsSync(absFile);
|
|
3448
|
+
const currentContent = fileExists ? fs$1.readFileSync(absFile, "utf-8") : null;
|
|
3449
|
+
const normalize = resolved.kind === "action" ? normalizeActionContent : void 0;
|
|
3269
3450
|
const decision = decideAction({
|
|
3270
3451
|
existing,
|
|
3271
3452
|
fileExists,
|
|
3272
|
-
currentContent
|
|
3273
|
-
force: options.force
|
|
3453
|
+
currentContent,
|
|
3454
|
+
force: options.force,
|
|
3455
|
+
normalize
|
|
3274
3456
|
});
|
|
3275
3457
|
if (decision.action === "conflict") throw new Error(`${resolved.file}: ${decision.reason}`);
|
|
3276
3458
|
const idResult = await ensureConfigId(resolved.configPath);
|
|
3277
3459
|
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'.");
|
|
3278
3460
|
const idInjected = idResult?.injected ?? false;
|
|
3461
|
+
let contentToWrite = resolved.render.content;
|
|
3462
|
+
if (decision.action === "regenerate" && normalize && currentContent !== null) {
|
|
3463
|
+
const existingBody = extractBuildSiteRunBody(currentContent);
|
|
3464
|
+
if (existingBody !== null) contentToWrite = injectBuildSiteRunBody(contentToWrite, existingBody);
|
|
3465
|
+
}
|
|
3279
3466
|
fs$1.mkdirSync(path.dirname(absFile), { recursive: true });
|
|
3280
|
-
fs$1.writeFileSync(absFile,
|
|
3467
|
+
fs$1.writeFileSync(absFile, contentToWrite, "utf-8");
|
|
3281
3468
|
const newTarget = {
|
|
3282
3469
|
kind: resolved.kind,
|
|
3283
3470
|
workspaceName: resolved.workspaceName,
|
|
3284
3471
|
file: resolved.file,
|
|
3285
|
-
templateVersion:
|
|
3472
|
+
templateVersion: 6,
|
|
3286
3473
|
inputs: resolved.inputs,
|
|
3287
3474
|
generatedIds: resolved.render.generatedIds,
|
|
3288
3475
|
ejectedIds: existing?.ejectedIds ?? [],
|
|
3289
|
-
contentHash: hashContent(resolved.render.content)
|
|
3476
|
+
contentHash: hashContent(normalize ? normalize(resolved.render.content) : resolved.render.content)
|
|
3290
3477
|
};
|
|
3291
3478
|
const targets = [...lock?.targets ?? []];
|
|
3292
3479
|
const index = targets.findIndex((t) => t.kind === newTarget.kind && t.workspaceName === newTarget.workspaceName);
|
|
@@ -3299,39 +3486,379 @@ async function setupGitHub(options) {
|
|
|
3299
3486
|
if (decision.action === "restore") logger.success(`Regenerated ${styles.path(resolved.file)} (was missing on disk)`);
|
|
3300
3487
|
else if (decision.action === "regenerate") logger.success(`Regenerated ${styles.path(resolved.file)}`);
|
|
3301
3488
|
else logger.success(`Generated ${styles.path(resolved.file)}`);
|
|
3302
|
-
|
|
3489
|
+
if (resolved.kind === "action") {
|
|
3490
|
+
logger.newline();
|
|
3491
|
+
logger.info("Next steps:");
|
|
3492
|
+
logger.newline();
|
|
3493
|
+
logger.log(`The composite action has been generated at ${styles.path(resolved.file)}.`);
|
|
3494
|
+
logger.log("Use `tailor-sdk setup coordinate` to generate a coordinator workflow that orchestrates this action.");
|
|
3495
|
+
} else printNextSteps({
|
|
3303
3496
|
environment: resolved.environment,
|
|
3304
3497
|
idInjected
|
|
3305
3498
|
});
|
|
3306
3499
|
}
|
|
3500
|
+
/**
|
|
3501
|
+
* Generate the coordinator workflow that orchestrates per-app composite actions.
|
|
3502
|
+
*
|
|
3503
|
+
* Unlike `setupTarget`, this function does not read a Tailor config. The coordinator
|
|
3504
|
+
* name is required via `--name`. App working directories are resolved from
|
|
3505
|
+
* the lock file entries created by `setup action`.
|
|
3506
|
+
* @param options - Coordinate setup options
|
|
3507
|
+
*/
|
|
3508
|
+
async function setupCoordinate(options) {
|
|
3509
|
+
logBetaWarning("setup");
|
|
3510
|
+
const { coordinatorName, coordinateKind, actions, force, outputDir } = options;
|
|
3511
|
+
validateWorkspaceName(coordinatorName);
|
|
3512
|
+
if (actions.length === 0) throw new Error("At least one --action is required. Run `tailor-sdk setup action --dir <app-dir>` for each app first.");
|
|
3513
|
+
const environment = options.environment ?? coordinatorName;
|
|
3514
|
+
validateEnvironment(environment);
|
|
3515
|
+
let branch = null;
|
|
3516
|
+
let branchAutoDetected = false;
|
|
3517
|
+
if (coordinateKind === "branch") {
|
|
3518
|
+
branchAutoDetected = options.branch === void 0;
|
|
3519
|
+
branch = options.branch ?? detectDefaultBranch(outputDir, options.gitRunner);
|
|
3520
|
+
validateBranch(branch);
|
|
3521
|
+
} else if (options.branch !== void 0) {
|
|
3522
|
+
validateBranch(options.branch);
|
|
3523
|
+
branch = options.branch;
|
|
3524
|
+
}
|
|
3525
|
+
const tagPattern = options.tagPattern ?? "v*";
|
|
3526
|
+
if (coordinateKind === "tag") validateTagPattern(tagPattern);
|
|
3527
|
+
const packageManager = detectPackageManager(outputDir);
|
|
3528
|
+
const lock = readLock(outputDir);
|
|
3529
|
+
if (!lock) throw new Error(".github/tailor-sdk.lock not found. Run `tailor-sdk setup action --name <name>` for each app before running setup coordinate.");
|
|
3530
|
+
const seenNames = /* @__PURE__ */ new Set();
|
|
3531
|
+
const apps = actions.map((actionName) => {
|
|
3532
|
+
const name = actionName.startsWith("tailor-") ? actionName.slice(7) : actionName;
|
|
3533
|
+
if (seenNames.has(name)) throw new Error(`Duplicate --action "${name}". Each composite action can only appear once in a coordinator.`);
|
|
3534
|
+
seenNames.add(name);
|
|
3535
|
+
const entry = lock.targets.find((t) => t.kind === "action" && t.workspaceName === name);
|
|
3536
|
+
if (!entry) throw new Error(`Action target "${name}" not found in .github/tailor-sdk.lock. Run \`tailor-sdk setup action --name ${name}\` first.`);
|
|
3537
|
+
validateDir(entry.inputs.dir);
|
|
3538
|
+
return {
|
|
3539
|
+
name,
|
|
3540
|
+
dir: entry.inputs.dir
|
|
3541
|
+
};
|
|
3542
|
+
});
|
|
3543
|
+
const render = renderCoordinateWorkflow({
|
|
3544
|
+
coordinatorName,
|
|
3545
|
+
kind: coordinateKind,
|
|
3546
|
+
apps,
|
|
3547
|
+
branch: branch ?? void 0,
|
|
3548
|
+
tagPattern: coordinateKind === "tag" ? tagPattern : void 0,
|
|
3549
|
+
environment,
|
|
3550
|
+
packageManager
|
|
3551
|
+
});
|
|
3552
|
+
const file = `.github/workflows/tailor-coordinate-${coordinatorName}${coordinateKind === "tag" ? "-tag" : ""}.yml`;
|
|
3553
|
+
const absFile = path.join(outputDir, file);
|
|
3554
|
+
assertNoKindCollision({
|
|
3555
|
+
lock,
|
|
3556
|
+
kind: "coordinate",
|
|
3557
|
+
workspaceName: coordinatorName,
|
|
3558
|
+
file
|
|
3559
|
+
});
|
|
3560
|
+
const existing = findTarget(lock, "coordinate", coordinatorName);
|
|
3561
|
+
const fileExists = fs$1.existsSync(absFile);
|
|
3562
|
+
const decision = decideAction({
|
|
3563
|
+
existing,
|
|
3564
|
+
fileExists,
|
|
3565
|
+
currentContent: fileExists ? fs$1.readFileSync(absFile, "utf-8") : null,
|
|
3566
|
+
force
|
|
3567
|
+
});
|
|
3568
|
+
if (decision.action === "conflict") throw new Error(`${file}: ${decision.reason}`);
|
|
3569
|
+
fs$1.mkdirSync(path.dirname(absFile), { recursive: true });
|
|
3570
|
+
fs$1.writeFileSync(absFile, render.content, "utf-8");
|
|
3571
|
+
const tailorSetupFile = ".github/actions/tailor-setup/action.yml";
|
|
3572
|
+
const absTailorSetupFile = path.join(outputDir, tailorSetupFile);
|
|
3573
|
+
if (!fs$1.existsSync(absTailorSetupFile)) {
|
|
3574
|
+
fs$1.mkdirSync(path.dirname(absTailorSetupFile), { recursive: true });
|
|
3575
|
+
fs$1.writeFileSync(absTailorSetupFile, renderTailorSetupAction({ packageManager }), "utf-8");
|
|
3576
|
+
logger.success(`Generated ${styles.path(tailorSetupFile)}`);
|
|
3577
|
+
}
|
|
3578
|
+
const newTarget = {
|
|
3579
|
+
kind: "coordinate",
|
|
3580
|
+
workspaceName: coordinatorName,
|
|
3581
|
+
file,
|
|
3582
|
+
templateVersion: 6,
|
|
3583
|
+
inputs: {
|
|
3584
|
+
branch,
|
|
3585
|
+
branchAutoDetected: coordinateKind === "branch" ? branchAutoDetected : branch !== null ? false : void 0,
|
|
3586
|
+
tagPattern: coordinateKind === "tag" ? tagPattern : null,
|
|
3587
|
+
environment,
|
|
3588
|
+
dir: ".",
|
|
3589
|
+
packageManager,
|
|
3590
|
+
actionDirs: apps.map((a) => a.dir)
|
|
3591
|
+
},
|
|
3592
|
+
generatedIds: render.generatedIds,
|
|
3593
|
+
ejectedIds: existing?.ejectedIds ?? [],
|
|
3594
|
+
contentHash: hashContent(render.content)
|
|
3595
|
+
};
|
|
3596
|
+
const targets = [...lock.targets];
|
|
3597
|
+
const idx = targets.findIndex((t) => t.kind === newTarget.kind && t.workspaceName === newTarget.workspaceName);
|
|
3598
|
+
if (idx === -1) targets.push(newTarget);
|
|
3599
|
+
else targets[idx] = newTarget;
|
|
3600
|
+
writeLock(outputDir, {
|
|
3601
|
+
version: 1,
|
|
3602
|
+
targets
|
|
3603
|
+
});
|
|
3604
|
+
if (decision.action === "restore") logger.success(`Regenerated ${styles.path(file)} (was missing on disk)`);
|
|
3605
|
+
else if (decision.action === "regenerate") logger.success(`Regenerated ${styles.path(file)}`);
|
|
3606
|
+
else logger.success(`Generated ${styles.path(file)}`);
|
|
3607
|
+
logger.newline();
|
|
3608
|
+
logger.info("Next steps:");
|
|
3609
|
+
logger.newline();
|
|
3610
|
+
logger.log(`1. Set the machine-user credentials as secrets on the "${environment}" environment:`);
|
|
3611
|
+
logger.log(` gh secret set TAILOR_PLATFORM_MACHINE_USER_CLIENT_ID --env ${environment}`);
|
|
3612
|
+
logger.log(` gh secret set TAILOR_PLATFORM_MACHINE_USER_CLIENT_SECRET --env ${environment}`);
|
|
3613
|
+
logger.newline();
|
|
3614
|
+
logger.log(`2. Provision each workspace and set TAILOR_PLATFORM_WORKSPACE_ID on the "${environment}" environment:`);
|
|
3615
|
+
logger.log(" tailor-sdk workspace create # one per app; copy the id");
|
|
3616
|
+
logger.log(` gh variable set TAILOR_PLATFORM_WORKSPACE_ID --env ${environment}`);
|
|
3617
|
+
logger.newline();
|
|
3618
|
+
logger.log("3. Commit the generated files:");
|
|
3619
|
+
logger.log(` - ${file}`);
|
|
3620
|
+
logger.log(` - ${tailorSetupFile} (if newly created)`);
|
|
3621
|
+
logger.log(" - .github/tailor-sdk.lock");
|
|
3622
|
+
}
|
|
3623
|
+
|
|
3624
|
+
//#endregion
|
|
3625
|
+
//#region src/cli/commands/setup/check.ts
|
|
3626
|
+
/**
|
|
3627
|
+
* Compute drift findings for one target by comparing its recorded lock state
|
|
3628
|
+
* against the current repository/config state.
|
|
3629
|
+
* @param target - The lock target being audited
|
|
3630
|
+
* @param state - Current repository/config state for this target
|
|
3631
|
+
* @returns Drift findings (empty when the target is in sync)
|
|
3632
|
+
*/
|
|
3633
|
+
function findTargetDrift(target, state) {
|
|
3634
|
+
const id = `${target.kind} ${target.workspaceName}`;
|
|
3635
|
+
const findings = [];
|
|
3636
|
+
if (!state.fileExists) findings.push({
|
|
3637
|
+
target: id,
|
|
3638
|
+
rule: "missing-file",
|
|
3639
|
+
message: `${target.file} is missing or unreadable. Re-run setup to restore it.`
|
|
3640
|
+
});
|
|
3641
|
+
else if (state.currentHash !== null && state.currentHash !== target.contentHash) findings.push({
|
|
3642
|
+
target: id,
|
|
3643
|
+
rule: "hand-edit",
|
|
3644
|
+
message: `${target.file} was edited by hand since it was generated. Re-run setup with --force to regenerate, or keep customizations in your own jobs/steps.`
|
|
3645
|
+
});
|
|
3646
|
+
if (target.templateVersion < state.templateVersion) findings.push({
|
|
3647
|
+
target: id,
|
|
3648
|
+
rule: "template-version",
|
|
3649
|
+
message: `A newer workflow template is available (generated with v${String(target.templateVersion)}, current v${String(state.templateVersion)}). Re-run setup to update.`
|
|
3650
|
+
});
|
|
3651
|
+
if (!state.configExists) findings.push({
|
|
3652
|
+
target: id,
|
|
3653
|
+
rule: "config-dir",
|
|
3654
|
+
message: `tailor.config.ts not found under "${target.inputs.dir}". The app directory may have moved; re-run setup with the correct --dir.`
|
|
3655
|
+
});
|
|
3656
|
+
if ((target.kind === "branch" || target.kind === "coordinate" || target.kind === "preview") && target.inputs.branchAutoDetected !== false && state.defaultBranch !== null && target.inputs.branch !== null && target.inputs.branch !== state.defaultBranch) findings.push({
|
|
3657
|
+
target: id,
|
|
3658
|
+
rule: "default-branch",
|
|
3659
|
+
message: `The workflow triggers on "${target.inputs.branch}" but the repository default branch is now "${state.defaultBranch}". If this is intentional, ignore this; otherwise re-run setup so the trigger matches the default branch.`
|
|
3660
|
+
});
|
|
3661
|
+
if (target.kind === "branch" && target.inputs.erdPreview && state.configExists) {
|
|
3662
|
+
const recorded = [...target.inputs.erdNamespaces ?? []].toSorted((a, b) => a.localeCompare(b));
|
|
3663
|
+
const current = state.erdNamespaces?.toSorted((a, b) => a.localeCompare(b)) ?? null;
|
|
3664
|
+
if (current === null || recorded.length !== current.length || recorded.some((namespace, index) => namespace !== current[index])) findings.push({
|
|
3665
|
+
target: id,
|
|
3666
|
+
rule: "erd-namespaces",
|
|
3667
|
+
message: "TailorDB namespaces for ERD preview changed. Re-run setup so the ERD preview matrix is regenerated."
|
|
3668
|
+
});
|
|
3669
|
+
}
|
|
3670
|
+
if ((target.kind === "branch" || target.kind === "tag") && target.inputs.migrationDriftCheck === false && state.hasMigrations === true) findings.push({
|
|
3671
|
+
target: id,
|
|
3672
|
+
rule: "migration-drift",
|
|
3673
|
+
message: "TailorDB namespaces with migrations were added to the config. Re-run setup so the tailor-migration-drift-check step is included in the plan job."
|
|
3674
|
+
});
|
|
3675
|
+
if ((target.kind === "branch" || target.kind === "tag") && target.inputs.seedValidate === false && state.hasSeeds === true) findings.push({
|
|
3676
|
+
target: id,
|
|
3677
|
+
rule: "seed-validate",
|
|
3678
|
+
message: "Seed plugin detected in the current config. Re-run setup so the tailor-seed-validate step is included in the plan job."
|
|
3679
|
+
});
|
|
3680
|
+
if (target.kind === "action" && target.inputs.hasStaticWebsites === false && state.hasStaticWebsites === true) findings.push({
|
|
3681
|
+
target: id,
|
|
3682
|
+
rule: "static-websites",
|
|
3683
|
+
message: "Static websites were added to the config. Re-run setup so the build-site slot is included in the composite action."
|
|
3684
|
+
});
|
|
3685
|
+
return findings;
|
|
3686
|
+
}
|
|
3687
|
+
function detectDefaultBranchSafe(cwd, run) {
|
|
3688
|
+
try {
|
|
3689
|
+
return detectDefaultBranch(cwd, run);
|
|
3690
|
+
} catch {
|
|
3691
|
+
return null;
|
|
3692
|
+
}
|
|
3693
|
+
}
|
|
3694
|
+
function escapesRoot(rel) {
|
|
3695
|
+
return path.isAbsolute(rel) || rel === ".." || rel.startsWith(`..${path.sep}`) || rel.startsWith("../");
|
|
3696
|
+
}
|
|
3697
|
+
function resolveWithinRoot(outputDir, relPath) {
|
|
3698
|
+
if (path.isAbsolute(relPath)) return null;
|
|
3699
|
+
const abs = path.join(outputDir, relPath);
|
|
3700
|
+
if (escapesRoot(path.relative(outputDir, abs))) return null;
|
|
3701
|
+
try {
|
|
3702
|
+
if (escapesRoot(path.relative(fs$1.realpathSync(outputDir), fs$1.realpathSync(abs)))) return null;
|
|
3703
|
+
} catch {}
|
|
3704
|
+
return abs;
|
|
3705
|
+
}
|
|
3706
|
+
function readHash(absFile, normalize) {
|
|
3707
|
+
try {
|
|
3708
|
+
const content = fs$1.readFileSync(absFile, "utf-8");
|
|
3709
|
+
return hashContent(normalize ? normalize(content) : content);
|
|
3710
|
+
} catch {
|
|
3711
|
+
return null;
|
|
3712
|
+
}
|
|
3713
|
+
}
|
|
3714
|
+
async function defaultLoadErdNamespaces(configPath) {
|
|
3715
|
+
const { config } = await loadConfig(configPath);
|
|
3716
|
+
return extractOwnedNamespaces(config);
|
|
3717
|
+
}
|
|
3718
|
+
async function defaultLoadHasMigrations(configPath) {
|
|
3719
|
+
const { config } = await loadConfig(configPath);
|
|
3720
|
+
return getNamespacesWithMigrations(config, path.dirname(configPath)).length > 0;
|
|
3721
|
+
}
|
|
3722
|
+
async function defaultLoadHasSeeds(configPath) {
|
|
3723
|
+
const { plugins } = await loadConfig(configPath);
|
|
3724
|
+
return plugins.some((p) => p.id === "@tailor-platform/seed");
|
|
3725
|
+
}
|
|
3726
|
+
async function defaultLoadHasStaticWebsites(configPath) {
|
|
3727
|
+
const { config } = await loadConfig(configPath);
|
|
3728
|
+
return (config.staticWebsites?.length ?? 0) > 0;
|
|
3729
|
+
}
|
|
3730
|
+
/**
|
|
3731
|
+
* Audit the generated workflows for drift against the current config/repo
|
|
3732
|
+
* state. Read-only: never writes files, the lock, or the config.
|
|
3733
|
+
*
|
|
3734
|
+
* Throws when drift is found (so it composes like the other `:check`
|
|
3735
|
+
* commands). The workflow drift-check step layers advisory behaviour on top
|
|
3736
|
+
* (per-rule ignore / continue-on-error); the CLI itself reports via exit code.
|
|
3737
|
+
* @param options - Check options
|
|
3738
|
+
*/
|
|
3739
|
+
async function checkGitHub(options) {
|
|
3740
|
+
logBetaWarning("setup");
|
|
3741
|
+
const { outputDir } = options;
|
|
3742
|
+
const lock = readLock(outputDir);
|
|
3743
|
+
if (!lock || lock.targets.length === 0) throw new Error("No managed workflows found (.github/tailor-sdk.lock is missing or empty). Run `tailor-sdk setup branch` (or another setup subcommand) first.");
|
|
3744
|
+
if (!options.ci) {
|
|
3745
|
+
if (lock.targets.some((t) => t.kind === "branch" || t.kind === "tag" || t.kind === "coordinate") && !process.env["TAILOR_PLATFORM_WORKSPACE_ID"]) throw new Error("TAILOR_PLATFORM_WORKSPACE_ID is not set. Provision the workspace and set the variable:\n tailor-sdk workspace create # if it does not exist yet; copy the id\n gh variable set TAILOR_PLATFORM_WORKSPACE_ID --env <environment>");
|
|
3746
|
+
}
|
|
3747
|
+
const slackToken = Boolean(process.env["TAILOR_SLACK_BOT_TOKEN"]);
|
|
3748
|
+
if (slackToken !== Boolean(process.env["TAILOR_SLACK_CHANNEL_ID"])) throw new Error(`Slack is partially configured: ${slackToken ? "TAILOR_SLACK_BOT_TOKEN is set" : "TAILOR_SLACK_CHANNEL_ID is set"} but not both. TAILOR_SLACK_BOT_TOKEN (secret) and TAILOR_SLACK_CHANNEL_ID (variable) must be set together, or neither should be set.`);
|
|
3749
|
+
const exists = options.configExistsAt ?? ((p) => fs$1.existsSync(p));
|
|
3750
|
+
const defaultBranch = detectDefaultBranchSafe(outputDir, options.gitRunner);
|
|
3751
|
+
const loadErdNamespaces = options.loadErdNamespaces ?? defaultLoadErdNamespaces;
|
|
3752
|
+
const loadHasMigrations = options.loadHasMigrations ?? defaultLoadHasMigrations;
|
|
3753
|
+
const loadHasSeeds = options.loadHasSeeds ?? defaultLoadHasSeeds;
|
|
3754
|
+
const loadHasStaticWebsites = options.loadHasStaticWebsites ?? defaultLoadHasStaticWebsites;
|
|
3755
|
+
const findings = [];
|
|
3756
|
+
for (const target of lock.targets) {
|
|
3757
|
+
const absFile = resolveWithinRoot(outputDir, target.file);
|
|
3758
|
+
const currentHash = absFile === null ? null : readHash(absFile, target.kind === "action" ? normalizeActionContent : void 0);
|
|
3759
|
+
const configAbs = target.kind === "coordinate" ? null : resolveWithinRoot(outputDir, path.join(target.inputs.dir, "tailor.config.ts"));
|
|
3760
|
+
const configExists = target.kind === "coordinate" || configAbs !== null && exists(configAbs);
|
|
3761
|
+
const erdNamespaces = target.kind === "branch" && target.inputs.erdPreview && configAbs !== null && configExists ? await loadErdNamespaces(configAbs) : null;
|
|
3762
|
+
const hasMigrations = (target.kind === "branch" || target.kind === "tag") && target.inputs.migrationDriftCheck !== void 0 && configAbs !== null && configExists ? await loadHasMigrations(configAbs) : void 0;
|
|
3763
|
+
const hasSeeds = (target.kind === "branch" || target.kind === "tag") && target.inputs.seedValidate !== void 0 && configAbs !== null && configExists ? await loadHasSeeds(configAbs) : void 0;
|
|
3764
|
+
const hasStaticWebsites = target.kind === "action" && target.inputs.hasStaticWebsites !== void 0 && configAbs !== null && configExists ? await loadHasStaticWebsites(configAbs) : void 0;
|
|
3765
|
+
findings.push(...findTargetDrift(target, {
|
|
3766
|
+
fileExists: currentHash !== null,
|
|
3767
|
+
currentHash,
|
|
3768
|
+
configExists,
|
|
3769
|
+
defaultBranch,
|
|
3770
|
+
templateVersion: 6,
|
|
3771
|
+
erdNamespaces,
|
|
3772
|
+
hasMigrations,
|
|
3773
|
+
hasSeeds,
|
|
3774
|
+
hasStaticWebsites
|
|
3775
|
+
}));
|
|
3776
|
+
}
|
|
3777
|
+
const count = lock.targets.length;
|
|
3778
|
+
if (findings.length === 0) {
|
|
3779
|
+
logger.success(`No drift detected across ${String(count)} target(s).`);
|
|
3780
|
+
return;
|
|
3781
|
+
}
|
|
3782
|
+
for (const finding of findings) logger.warn(`[${finding.target}] ${finding.message} (ignore key: ${finding.rule})`);
|
|
3783
|
+
throw new Error(`Detected ${String(findings.length)} drift finding(s) across ${String(count)} target(s). Re-run \`tailor-sdk setup\` to regenerate, or address each finding above.`);
|
|
3784
|
+
}
|
|
3307
3785
|
|
|
3308
3786
|
//#endregion
|
|
3309
3787
|
//#region src/cli/commands/setup/index.ts
|
|
3310
3788
|
const checkCommand = defineAppCommand({
|
|
3311
3789
|
name: "check",
|
|
3312
3790
|
description: "Audit generated workflows for drift against the current config/repo (read-only).",
|
|
3313
|
-
args: z.object({}).strict(),
|
|
3314
|
-
run: async () => {
|
|
3315
|
-
await checkGitHub({
|
|
3791
|
+
args: z.object({ ci: arg(z.boolean().default(false), { description: "Run in CI mode: skip checks that are handled by the runtime (e.g. TAILOR_PLATFORM_WORKSPACE_ID)." }) }).strict(),
|
|
3792
|
+
run: async (args) => {
|
|
3793
|
+
await checkGitHub({
|
|
3794
|
+
outputDir: process.cwd(),
|
|
3795
|
+
ci: args.ci
|
|
3796
|
+
});
|
|
3316
3797
|
}
|
|
3317
3798
|
});
|
|
3318
|
-
const
|
|
3319
|
-
name: "
|
|
3320
|
-
description: "Generate a
|
|
3799
|
+
const coordinateCommand = defineAppCommand({
|
|
3800
|
+
name: "coordinate",
|
|
3801
|
+
description: "Generate a coordinator workflow that orchestrates multiple --action-generated composite actions.",
|
|
3321
3802
|
args: z.object({
|
|
3322
|
-
|
|
3323
|
-
alias: "
|
|
3324
|
-
description: "
|
|
3803
|
+
name: arg(z.string().min(1), {
|
|
3804
|
+
alias: "n",
|
|
3805
|
+
description: "Coordinator name (used in the generated workflow file name and job names)"
|
|
3325
3806
|
}),
|
|
3326
|
-
|
|
3807
|
+
action: arg(z.array(z.string().min(1)).min(1), { description: "Composite action to include (can be specified multiple times). tailor- prefix optional." }),
|
|
3808
|
+
branch: arg(z.string().min(1).optional(), { description: "Branch target: deploy trigger branch (defaults to the detected default branch)" }),
|
|
3809
|
+
tag: arg(z.boolean().default(false), { description: "Generate a tag target coordinator" }),
|
|
3810
|
+
environment: arg(z.string().min(1).optional(), { description: "GitHub Environment for the plan/deploy jobs" }),
|
|
3811
|
+
force: arg(z.boolean().default(false), { description: "Discard hand edits and regenerate" })
|
|
3812
|
+
}).strict(),
|
|
3813
|
+
run: async (args) => {
|
|
3814
|
+
const coordinateKind = args.tag ? "tag" : "branch";
|
|
3815
|
+
await setupCoordinate({
|
|
3816
|
+
coordinatorName: args.name,
|
|
3817
|
+
coordinateKind,
|
|
3818
|
+
actions: args.action,
|
|
3819
|
+
branch: args.branch,
|
|
3820
|
+
environment: args.environment,
|
|
3821
|
+
force: args.force,
|
|
3822
|
+
outputDir: process.cwd()
|
|
3823
|
+
});
|
|
3824
|
+
}
|
|
3825
|
+
});
|
|
3826
|
+
const actionCommand = defineAppCommand({
|
|
3827
|
+
name: "action",
|
|
3828
|
+
description: "Generate a per-app composite action for use with setup coordinate (monorepo multi-app deploys).",
|
|
3829
|
+
args: z.object({
|
|
3830
|
+
name: arg(z.string().min(1).optional(), {
|
|
3327
3831
|
alias: "n",
|
|
3328
|
-
description: "
|
|
3832
|
+
description: "Name (defaults to the config 'name')"
|
|
3833
|
+
}),
|
|
3834
|
+
dir: arg(z.string().min(1).default("."), {
|
|
3835
|
+
alias: "d",
|
|
3836
|
+
description: "App directory"
|
|
3329
3837
|
}),
|
|
3330
|
-
|
|
3331
|
-
|
|
3332
|
-
|
|
3838
|
+
environment: arg(z.string().min(1).optional(), { description: "GitHub Environment (defaults to the workspace name)" }),
|
|
3839
|
+
force: arg(z.boolean().default(false), { description: "Discard hand edits and regenerate" })
|
|
3840
|
+
}).strict(),
|
|
3841
|
+
run: async (args) => {
|
|
3842
|
+
await setupTarget({
|
|
3843
|
+
kind: "action",
|
|
3844
|
+
workspaceName: args.name,
|
|
3845
|
+
dir: args.dir,
|
|
3846
|
+
environment: args.environment,
|
|
3847
|
+
force: args.force,
|
|
3848
|
+
outputDir: process.cwd()
|
|
3849
|
+
});
|
|
3850
|
+
}
|
|
3851
|
+
});
|
|
3852
|
+
const branchCommand = defineAppCommand({
|
|
3853
|
+
name: "branch",
|
|
3854
|
+
description: "Generate a branch-target deploy workflow (push to branch triggers deploy).",
|
|
3855
|
+
args: z.object({
|
|
3856
|
+
name: arg(z.string().min(1).optional(), {
|
|
3857
|
+
alias: "n",
|
|
3858
|
+
description: "Name (defaults to the config 'name')"
|
|
3859
|
+
}),
|
|
3860
|
+
branch: arg(z.string().min(1).optional(), { description: "Deploy trigger branch (defaults to the detected default branch)" }),
|
|
3333
3861
|
environment: arg(z.string().min(1).optional(), { description: "GitHub Environment for the plan/deploy jobs (defaults to the workspace name)" }),
|
|
3334
|
-
"no-plan": arg(z.boolean().default(false), { description: "Disable the plan job for a branch target (cannot be combined with --tag)" }),
|
|
3335
3862
|
"erd-preview": arg(z.boolean().default(false), { description: "Add PR ERD viewer artifacts with current/diff previews for TailorDB namespaces" }),
|
|
3336
3863
|
dir: arg(z.string().min(1).default("."), {
|
|
3337
3864
|
alias: "d",
|
|
@@ -3339,17 +3866,12 @@ const setupCommand = defineAppCommand({
|
|
|
3339
3866
|
}),
|
|
3340
3867
|
force: arg(z.boolean().default(false), { description: "Discard hand edits / take over unmanaged files and regenerate" })
|
|
3341
3868
|
}).strict(),
|
|
3342
|
-
subCommands: { check: checkCommand },
|
|
3343
3869
|
run: async (args) => {
|
|
3344
|
-
|
|
3345
|
-
|
|
3346
|
-
|
|
3347
|
-
workspaceName: args["workspace-name"],
|
|
3870
|
+
await setupTarget({
|
|
3871
|
+
kind: "branch",
|
|
3872
|
+
workspaceName: args.name,
|
|
3348
3873
|
branch: args.branch,
|
|
3349
|
-
tag: args.tag,
|
|
3350
|
-
tagPattern: args["tag-pattern"] ?? "v*",
|
|
3351
3874
|
environment: args.environment,
|
|
3352
|
-
plan: !args["no-plan"],
|
|
3353
3875
|
erdPreview: args["erd-preview"],
|
|
3354
3876
|
dir: args.dir,
|
|
3355
3877
|
force: args.force,
|
|
@@ -3357,6 +3879,80 @@ const setupCommand = defineAppCommand({
|
|
|
3357
3879
|
});
|
|
3358
3880
|
}
|
|
3359
3881
|
});
|
|
3882
|
+
const tagCommand = defineAppCommand({
|
|
3883
|
+
name: "tag",
|
|
3884
|
+
description: "Generate a tag-target deploy workflow (tag push triggers deploy).",
|
|
3885
|
+
args: z.object({
|
|
3886
|
+
name: arg(z.string().min(1).optional(), {
|
|
3887
|
+
alias: "n",
|
|
3888
|
+
description: "Name (defaults to the config 'name')"
|
|
3889
|
+
}),
|
|
3890
|
+
"tag-pattern": arg(z.string().min(1).default("v*"), { description: "Tag glob to match (defaults to v*)" }),
|
|
3891
|
+
branch: arg(z.string().min(1).optional(), { description: "Tag-reachability guard branch (no guard when omitted)" }),
|
|
3892
|
+
environment: arg(z.string().min(1).optional(), { description: "GitHub Environment for the plan/deploy jobs (defaults to the workspace name)" }),
|
|
3893
|
+
dir: arg(z.string().min(1).default("."), {
|
|
3894
|
+
alias: "d",
|
|
3895
|
+
description: "App directory (for monorepo setups)"
|
|
3896
|
+
}),
|
|
3897
|
+
force: arg(z.boolean().default(false), { description: "Discard hand edits / take over unmanaged files and regenerate" })
|
|
3898
|
+
}).strict(),
|
|
3899
|
+
run: async (args) => {
|
|
3900
|
+
await setupTarget({
|
|
3901
|
+
kind: "tag",
|
|
3902
|
+
workspaceName: args.name,
|
|
3903
|
+
tagPattern: args["tag-pattern"],
|
|
3904
|
+
branch: args.branch,
|
|
3905
|
+
environment: args.environment,
|
|
3906
|
+
dir: args.dir,
|
|
3907
|
+
force: args.force,
|
|
3908
|
+
outputDir: process.cwd()
|
|
3909
|
+
});
|
|
3910
|
+
}
|
|
3911
|
+
});
|
|
3912
|
+
const previewCommand = defineAppCommand({
|
|
3913
|
+
name: "preview",
|
|
3914
|
+
description: "Generate a preview workflow (PR open/sync triggers deploy to a per-PR workspace).",
|
|
3915
|
+
args: z.object({
|
|
3916
|
+
name: arg(z.string().min(1).optional(), {
|
|
3917
|
+
alias: "n",
|
|
3918
|
+
description: "Name (defaults to the config 'name')"
|
|
3919
|
+
}),
|
|
3920
|
+
branch: arg(z.string().min(1).optional(), { description: "Branch to filter PRs by (defaults to the detected default branch)" }),
|
|
3921
|
+
region: arg(z.string().min(1), { description: "Workspace region for preview workspace creation (e.g. us-west). Required." }),
|
|
3922
|
+
"require-preview-label": arg(z.boolean().default(false), { description: "Deploy preview only for PRs labeled `tailor:preview` instead of all PRs." }),
|
|
3923
|
+
environment: arg(z.string().min(1).optional(), { description: "GitHub Environment for the preview jobs (defaults to the workspace name)" }),
|
|
3924
|
+
dir: arg(z.string().min(1).default("."), {
|
|
3925
|
+
alias: "d",
|
|
3926
|
+
description: "App directory (for monorepo setups)"
|
|
3927
|
+
}),
|
|
3928
|
+
force: arg(z.boolean().default(false), { description: "Discard hand edits / take over unmanaged files and regenerate" })
|
|
3929
|
+
}).strict(),
|
|
3930
|
+
run: async (args) => {
|
|
3931
|
+
await setupTarget({
|
|
3932
|
+
kind: "preview",
|
|
3933
|
+
workspaceName: args.name,
|
|
3934
|
+
branch: args.branch,
|
|
3935
|
+
region: args.region,
|
|
3936
|
+
requirePreviewLabel: args["require-preview-label"],
|
|
3937
|
+
environment: args.environment,
|
|
3938
|
+
dir: args.dir,
|
|
3939
|
+
force: args.force,
|
|
3940
|
+
outputDir: process.cwd()
|
|
3941
|
+
});
|
|
3942
|
+
}
|
|
3943
|
+
});
|
|
3944
|
+
const setupCommand = defineCommand({
|
|
3945
|
+
name: "setup",
|
|
3946
|
+
description: "Generate CI deploy workflows for your project. (beta)",
|
|
3947
|
+
subCommands: {
|
|
3948
|
+
branch: branchCommand,
|
|
3949
|
+
tag: tagCommand,
|
|
3950
|
+
preview: previewCommand,
|
|
3951
|
+
action: actionCommand,
|
|
3952
|
+
coordinate: coordinateCommand,
|
|
3953
|
+
check: checkCommand
|
|
3954
|
+
}
|
|
3955
|
+
});
|
|
3360
3956
|
|
|
3361
3957
|
//#endregion
|
|
3362
3958
|
//#region src/cli/shared/skills-installer.ts
|