project-auto-wizard 0.14.0 → 0.15.1

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.
Files changed (64) hide show
  1. package/README.ja.md +3 -1
  2. package/README.ko.md +3 -1
  3. package/README.md +4 -2
  4. package/README.zh-CN.md +3 -1
  5. package/package.json +1 -1
  6. package/payload/config/breaking-changes.json +31 -5
  7. package/payload/config/wizard-prompts.yml +0 -4
  8. package/payload/scripts/changelog_manager.py +8 -8
  9. package/payload/scripts/messages.py +460 -24
  10. package/payload/scripts/version_manager.py +102 -73
  11. package/payload/version.yml.template +2 -2
  12. package/payload/workflows/common/PROJECT-COMMON-AI-PR-SUMMARY.yaml +9 -4
  13. package/payload/workflows/common/PROJECT-COMMON-AUTO-CHANGELOG-CONTROL.yaml +62 -30
  14. package/payload/workflows/common/PROJECT-COMMON-README-VERSION-UPDATE.yaml +96 -44
  15. package/payload/workflows/common/PROJECT-COMMON-RELEASE-PUBLISH.yaml +79 -35
  16. package/payload/workflows/common/PROJECT-COMMON-VERSION-CONTROL.yaml +71 -35
  17. package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-FIREBASE-CICD.yaml +24 -8
  18. package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-PLAYSTORE-CICD.yaml +29 -10
  19. package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-SELFHOSTED-CICD.yaml +57 -23
  20. package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-TEST-APK.yaml +155 -72
  21. package/payload/workflows/flutter/PROJECT-FLUTTER-APP-BUILD-TRIGGER.yaml +49 -20
  22. package/payload/workflows/flutter/PROJECT-FLUTTER-CI.yaml +162 -62
  23. package/payload/workflows/flutter/PROJECT-FLUTTER-IOS-TEST-TESTFLIGHT.yaml +184 -87
  24. package/payload/workflows/flutter/PROJECT-FLUTTER-IOS-TESTFLIGHT.yaml +38 -15
  25. package/payload/workflows/go/PROJECT-GO-CI.yaml +14 -5
  26. package/payload/workflows/go/PROJECT-GO-PR-PREVIEW.yaml +375 -120
  27. package/payload/workflows/go/PROJECT-GO-SIMPLE-CICD.yaml +9 -4
  28. package/payload/workflows/python/PROJECT-PYTHON-CI.yaml +12 -4
  29. package/payload/workflows/python/PROJECT-PYTHON-PR-PREVIEW.yaml +375 -120
  30. package/payload/workflows/python/PROJECT-PYTHON-SIMPLE-CICD.yaml +9 -4
  31. package/payload/workflows/react/PROJECT-REACT-CI.yaml +38 -9
  32. package/payload/workflows/react/PROJECT-REACT-CICD.yaml +19 -5
  33. package/payload/workflows/spring/PROJECT-SPRING-CI.yml +71 -35
  34. package/payload/workflows/spring/server-deploy/PROJECT-SPRING-NONSTOP-NGINX-CICD.yaml +9 -6
  35. package/payload/workflows/spring/server-deploy/PROJECT-SPRING-NONSTOP-TRAEFIK-CICD.yaml +7 -4
  36. package/payload/workflows/spring/server-deploy/PROJECT-SPRING-PR-PREVIEW.yaml +425 -144
  37. package/payload/workflows/spring/server-deploy/PROJECT-SPRING-SIMPLE-CICD.yaml +7 -4
  38. package/src/cli/args.js +17 -7
  39. package/src/commands/doctor.js +16 -0
  40. package/src/commands/dry-run.js +10 -1
  41. package/src/commands/full.js +5 -2
  42. package/src/commands/interactive.js +4 -1
  43. package/src/commands/status.js +14 -1
  44. package/src/core/copy/readme.js +58 -2
  45. package/src/core/copy/simple.js +31 -0
  46. package/src/core/copy/workflows.js +2 -2
  47. package/src/core/deploy-style.js +1 -1
  48. package/src/core/detect.js +3 -2
  49. package/src/core/types.js +15 -8
  50. package/src/core/version-yml.js +52 -6
  51. package/src/i18n/catalog/en/cli.js +1 -0
  52. package/src/i18n/catalog/en/commands.js +9 -0
  53. package/src/i18n/catalog/en/copy.js +1 -0
  54. package/src/i18n/catalog/en/core2.js +3 -0
  55. package/src/i18n/catalog/ko/cli.js +1 -0
  56. package/src/i18n/catalog/ko/commands.js +9 -0
  57. package/src/i18n/catalog/ko/copy.js +1 -0
  58. package/src/i18n/catalog/ko/core2.js +3 -0
  59. package/src/index.js +8 -4
  60. package/src/ui/env-plan.js +1 -1
  61. package/src/ui/prompts.js +7 -3
  62. package/src/ui/summary.js +1 -1
  63. package/payload/workflows/next/PROJECT-NEXT-CI.yaml +0 -241
  64. package/payload/workflows/next/PROJECT-NEXT-CICD.yaml +0 -330
@@ -12,6 +12,8 @@ export default {
12
12
  "cmd.status.modified": "Workflow files modified by the user ({n}):",
13
13
  "cmd.status.unmodified": "All workflow files match their install-time defaults (no modifications).",
14
14
  "cmd.status.stale": "Old workflows not in the current version ({n}; cleaned up on the next update):",
15
+ "cmd.status.missingScripts": "Warning: {n} installed script(s) missing from {dir}/ (workflows call them and will fail at run time):",
16
+ "cmd.status.missingScriptsFix": " Restore them with: npx project-auto-wizard --mode full --force",
15
17
  "cmd.status.bucketsTitle": "If you update now:",
16
18
  "cmd.status.bucketAuto": " Upstream changed - auto-applicable {n}",
17
19
  "cmd.status.bucketLocalKept": " Edited by me - upstream unchanged {n} (kept)",
@@ -24,6 +26,12 @@ export default {
24
26
  "cmd.doctor.install.purpose": "Whether the wizard is installed in this folder",
25
27
  "cmd.doctor.install.value": "version.yml found",
26
28
  "cmd.doctor.install.note": "Not installed in this folder yet; you can install now.",
29
+ "cmd.doctor.scripts.name": "Installed scripts",
30
+ "cmd.doctor.scripts.label": "Installed scripts",
31
+ "cmd.doctor.scripts.purpose": "Scripts the installed workflows call",
32
+ "cmd.doctor.scripts.missing": "Missing from .github/scripts/: {files}",
33
+ "cmd.doctor.scripts.impact": "Workflow steps that call them stop with a clear error (messages.py is missing) instead of carrying on.",
34
+ "cmd.doctor.scripts.action": "Restore them with: npx project-auto-wizard --mode full --force",
27
35
  "cmd.doctor.gh.purpose": "For reading repo settings",
28
36
  "cmd.doctor.gh.notFound": "The gh CLI was not found.",
29
37
  "cmd.doctor.gh.notFoundImpact": "Repo permissions and secrets cannot be read, so all remote checks below are skipped.",
@@ -115,6 +123,7 @@ export default {
115
123
  "cmd.dryRun.readme.skipNoReadme": "README.md: no file, so no version section is added",
116
124
  "cmd.dryRun.readme.skipMarker": "README.md: a version section already exists, no change",
117
125
  "cmd.dryRun.readme.skipVersionLine": "README.md: a version line already exists, no change",
126
+ "cmd.dryRun.readme.headingUpdated": "README.md: the version heading will switch to the current language (it is the bundled default text)",
118
127
  "cmd.dryRun.readme.other": "README.md: {status}",
119
128
  "cmd.dryRun.baseline.update": "{path}: will be updated (reference for the next update)",
120
129
  "cmd.dryRun.baseline.create": "{path}: will be created (reference for the next update)",
@@ -4,6 +4,7 @@ export default {
4
4
  "copy.readme.status.skipNoReadme": "README.md not found; version section not added",
5
5
  "copy.readme.status.skipMarker": "Version section already present; left as is",
6
6
  "copy.readme.status.skipVersionLine": "Version line already present; left as is",
7
+ "copy.readme.status.headingUpdated": "Switched the version heading to the current language",
7
8
  "copy.readme.versionHeading": "## Latest Version : v{version}",
8
9
  "copy.readme.historyLink": "[View full version history](CHANGELOG.md)",
9
10
  "copy.gitignore.newFileHeader": "# project-auto-wizard: backup files created during conflict handling (safe to ignore)",
@@ -1,6 +1,9 @@
1
1
  // English messages: core area (part 2).
2
2
  export default {
3
3
  "core.breakingCheck.confirm": "Have you reviewed the breaking changes above and want to continue?",
4
+ "core.versionYml.pathMergedTitle": "One folder per type",
5
+ "core.versionYml.pathMerged": "version.yml lists '{keptName}' (folder {kept}) and '{name}' (folder {path}) separately, but they are now the same type and only one folder per type is managed. Keeping {type}={final}; {lost} drops out of CI and version sync.",
6
+ "core.versionYml.pathMergedHint": "To manage {path} instead, rerun with --paths {type}={path}; to keep both, add a hand-written CI workflow for the dropped folder.",
4
7
  "core.breakingCheck.nonInteractive": "⚠️ There are CRITICAL breaking changes - continuing because this is a non-interactive run. Be sure to review the notes above.",
5
8
  "core.logger.error.nameExhausted": "All log file names are in use: {base}",
6
9
  "core.logger.warn.startFailed": "Could not start the run log: {message}",
@@ -20,6 +20,7 @@ export default {
20
20
  "cli.args.unknownOption": "알 수 없는 옵션: {option}",
21
21
  "cli.args.modeUnsupported": "지원하지 않는 모드: '{mode}'\n지원 모드: interactive full uninstall status doctor",
22
22
  "cli.args.pathsTypeUnsupported": "--paths에 지원하지 않는 타입: '{type}'",
23
+ "cli.args.pathsDuplicateType": "--paths에 '{type}'이 서로 다른 폴더로 두 번 지정됨 (next는 react의 이전 이름입니다): 한 항목만 남기세요",
23
24
  "cli.args.pathsRelativeOnly": "--paths는 레포 안의 상대경로만 지정할 수 있습니다: '{pair}'",
24
25
  "cli.index.purgePrompt": "purge를 실행하려면 정확히 이 레포명을 입력하세요: {repoName}\n> ",
25
26
  "cli.index.dryRunNeedsMode": "--dry-run은 --mode <full|uninstall>와 함께 사용하세요 (대화형 모드에서는 지원하지 않습니다).",
@@ -12,6 +12,8 @@ export default {
12
12
  "cmd.status.modified": "사용자가 수정한 워크플로우 파일 ({n}개):",
13
13
  "cmd.status.unmodified": "모든 워크플로우 파일이 설치 시점 기본값과 동일합니다 (수정 없음).",
14
14
  "cmd.status.stale": "현재 버전에 없는 이전 워크플로우 ({n}개 — 다음 업데이트에서 정리됨):",
15
+ "cmd.status.missingScripts": "경고: 설치된 스크립트 {n}개가 {dir}/ 에 없습니다 (워크플로우가 호출하므로 실행 시 실패합니다):",
16
+ "cmd.status.missingScriptsFix": " 복구: npx project-auto-wizard --mode full --force",
15
17
  "cmd.status.bucketsTitle": "지금 업데이트하면:",
16
18
  "cmd.status.bucketAuto": " 업스트림 변경 — 자동 적용 가능 {n}",
17
19
  "cmd.status.bucketLocalKept": " 내가 수정 · 업스트림 그대로 {n} (유지)",
@@ -24,6 +26,12 @@ export default {
24
26
  "cmd.doctor.install.purpose": "이 폴더의 마법사 설치 여부",
25
27
  "cmd.doctor.install.value": "version.yml 있음",
26
28
  "cmd.doctor.install.note": "이 폴더엔 아직 설치되지 않았습니다 — 지금 설치하면 됩니다.",
29
+ "cmd.doctor.scripts.name": "설치된 스크립트",
30
+ "cmd.doctor.scripts.label": "설치된 스크립트",
31
+ "cmd.doctor.scripts.purpose": "설치된 워크플로우가 호출하는 스크립트",
32
+ "cmd.doctor.scripts.missing": ".github/scripts/ 에 없음: {files}",
33
+ "cmd.doctor.scripts.impact": "이 스크립트를 호출하는 스텝은 누락을 알리는 오류(messages.py is missing)로 명확히 실패합니다.",
34
+ "cmd.doctor.scripts.action": "복구: npx project-auto-wizard --mode full --force",
27
35
  "cmd.doctor.gh.purpose": "레포 설정 조회용",
28
36
  "cmd.doctor.gh.notFound": "gh CLI를 찾을 수 없습니다.",
29
37
  "cmd.doctor.gh.notFoundImpact": "레포 권한·secret 설정을 조회할 수 없어 아래 원격 점검을 모두 건너뜁니다.",
@@ -115,6 +123,7 @@ export default {
115
123
  "cmd.dryRun.readme.skipNoReadme": "README.md: 파일이 없어 버전 섹션을 추가하지 않음",
116
124
  "cmd.dryRun.readme.skipMarker": "README.md: 이미 버전 섹션이 있어 변경 없음",
117
125
  "cmd.dryRun.readme.skipVersionLine": "README.md: 이미 버전 줄이 있어 변경 없음",
126
+ "cmd.dryRun.readme.headingUpdated": "README.md: 버전 제목이 현재 언어로 교체될 예정 (기본 제목 그대로인 경우)",
118
127
  "cmd.dryRun.readme.other": "README.md: {status}",
119
128
  "cmd.dryRun.baseline.update": "{path}: 갱신될 예정 (다음 업데이트의 비교 기준)",
120
129
  "cmd.dryRun.baseline.create": "{path}: 새로 생성될 예정 (다음 업데이트의 비교 기준)",
@@ -4,6 +4,7 @@ export default {
4
4
  "copy.readme.status.skipNoReadme": "README.md가 없어 버전 섹션을 추가하지 않음",
5
5
  "copy.readme.status.skipMarker": "이미 버전 섹션이 있어 그대로 둠",
6
6
  "copy.readme.status.skipVersionLine": "이미 버전 줄이 있어 그대로 둠",
7
+ "copy.readme.status.headingUpdated": "버전 제목을 현재 언어로 교체",
7
8
  "copy.readme.versionHeading": "## 최신 버전 : v{version}",
8
9
  "copy.readme.historyLink": "[전체 버전 기록 보기](CHANGELOG.md)",
9
10
  "copy.gitignore.newFileHeader": "# project-auto-wizard: 충돌 처리 시 생성되는 백업 파일 (안전하게 무시해도 됩니다)",
@@ -1,6 +1,9 @@
1
1
  // 한국어 메시지: core 영역 (2부).
2
2
  export default {
3
3
  "core.breakingCheck.confirm": "위 호환성 변경을 확인했고 계속 진행할까요?",
4
+ "core.versionYml.pathMergedTitle": "타입당 폴더 하나",
5
+ "core.versionYml.pathMerged": "version.yml 에 '{keptName}'(폴더 {kept})과 '{name}'(폴더 {path})이 따로 적혀 있지만 이제 같은 타입이라 타입당 폴더 하나만 관리합니다. {type}={final} 를 유지하고, {lost} 는 CI·버전 동기화에서 빠집니다.",
6
+ "core.versionYml.pathMergedHint": "{path} 를 대신 관리하려면 --paths {type}={path} 로 다시 실행하세요. 둘 다 유지하려면 빠진 폴더용 CI 워크플로우를 직접 추가하세요.",
4
7
  "core.breakingCheck.nonInteractive": "⚠️ CRITICAL 호환성 변경이 있습니다 — 비대화형 실행이라 계속 진행합니다. 위 내용을 꼭 확인하세요.",
5
8
  "core.logger.error.nameExhausted": "로그 파일 이름이 모두 사용 중입니다: {base}",
6
9
  "core.logger.warn.startFailed": "실행 로그를 시작하지 못했습니다: {message}",
package/src/index.js CHANGED
@@ -14,7 +14,7 @@ import { hooksFor, mergeHookResults } from "./core/types.js";
14
14
  import { PATHS } from "./core/paths.js";
15
15
  import { resolvePayloadRoot, assertPayload, readTemplateVersion } from "./core/assets.js";
16
16
  import { detectTypes, detectDefaultBranch, detectRepoName, makeResolvers, detectMarkers } from "./core/detect-fs.js";
17
- import { parseExisting } from "./core/version-yml.js";
17
+ import { parseExisting, droppedPathLines } from "./core/version-yml.js";
18
18
  import { resolveReleaseOptions } from "./core/release-options.js";
19
19
  import { runBreakingCheck } from "./core/breaking-check.js";
20
20
  import { resolveProjectPaths } from "./core/paths-resolve.js";
@@ -31,7 +31,7 @@ import { runInteractive } from "./commands/interactive.js";
31
31
  import { resolveDeployStyle, resolveVersion, resolveVersionCode, buildInstallContext } from "./commands/install-settings.js";
32
32
  import { initLogger, closeLogger, currentLogPath, hasLegacyMdLogs, log } from "./core/logger.js";
33
33
  import { runStatus, printStatus } from "./commands/status.js";
34
- import { runDoctor, printDoctorReport } from "./commands/doctor.js";
34
+ import { runDoctor, printDoctorReport, doctorExitCode } from "./commands/doctor.js";
35
35
  import { planDryRun, printDryRun } from "./commands/dry-run.js";
36
36
  import { planPurge, executePurge, printPurgePlan, printPurgeResult } from "./commands/purge.js";
37
37
 
@@ -271,8 +271,9 @@ async function runInner(argv, {
271
271
  }
272
272
  // doctor mode: read-only, always works regardless of TTY/--force
273
273
  if (opts.mode === "doctor") {
274
- printDoctorReport(runDoctor(cwd));
275
- return 0;
274
+ const results = runDoctor(cwd);
275
+ printDoctorReport(results);
276
+ return doctorExitCode(results);
276
277
  }
277
278
  // An explicit mode (full) without --force is rejected immediately, TTY or not
278
279
  // (fixes a defect where a TTY run installed at once without confirmation).
@@ -315,6 +316,9 @@ async function runInner(argv, {
315
316
  throw e;
316
317
  }
317
318
 
319
+ // Warn against the final folders (an explicit --paths may keep the other one). The dry-run preview prints its own notice.
320
+ if (!opts.dryRun) for (const line of droppedPathLines(existing?.droppedPaths, paths)) console.error(`⚠️ ${line}`);
321
+
318
322
  // version: existing version.yml first (SSoT; prevents overwriting on re-run) -> CLI value -> file detection.
319
323
  // Non-interactive, so the fallback notice uses the CLI wording (--project-version) as is.
320
324
  // Detect after paths are confirmed so a monorepo subfolder's version and build number are read.
@@ -59,7 +59,7 @@ export function collectAsks(payloadRoot, types = [], opts = {}) {
59
59
  const [typeDir, serverDeployDir] = typeWorkflowDirs(payloadRoot, type);
60
60
  if (!exists(typeDir)) continue;
61
61
  // Same folder layout as the copy engine: type root + server-deploy (only when deploy is not "none").
62
- // Types such as go/python and react/next keep server deploy workflows directly in the type root; they go
62
+ // Types such as go, python and react keep server deploy workflows directly in the type root; they go
63
63
  // through the same deploy-style filter - with "no deploy", CD/PR-preview ask keys (DEPLOY_PORT, SSH_AUTH_METHOD, ...) are not asked.
64
64
  // Flutter also filters out deselected store workflows (PLAYSTORE, TESTFLIGHT) so the question scope equals the install scope.
65
65
  units.push([type, typeDir, buildTypeRootFilter(type, deployStyle, flutterStore, available)]);
package/src/ui/prompts.js CHANGED
@@ -5,7 +5,7 @@
5
5
  import * as engine from "./readline-engine.js";
6
6
  import { t } from "../i18n/index.js";
7
7
  import { DEPLOY_STYLES, NO_DEPLOY_STYLE } from "../core/deploy-style.js";
8
- import { TYPE_IDS } from "../core/types.js";
8
+ import { TYPE_IDS, typeInfo } from "../core/types.js";
9
9
  import { ENV_MODES, DEFAULT_ENV_MODE, STORE_PLATFORMS, DEPLOY_MODES, DEFAULT_DEPLOY_MODE, deployModeWarning } from "../core/flutter-options.js";
10
10
 
11
11
  export const CANCEL = engine.CANCEL;
@@ -71,7 +71,7 @@ export const ALL_TYPES = TYPE_IDS;
71
71
  export async function selectTypes(current = []) {
72
72
  return engine.multiselect({
73
73
  message: t("ui.prompts.types.select"),
74
- options: ALL_TYPES.map((type) => ({ value: type, label: type })),
74
+ options: ALL_TYPES.map((type) => ({ value: type, label: type, hint: typeInfo(type).displayName })),
75
75
  initialValues: current.length ? current : ["basic"],
76
76
  required: true,
77
77
  });
@@ -91,7 +91,11 @@ export async function confirmTypes({ types = [], markers = null } = {}) {
91
91
  options: ALL_TYPES.map((type) => {
92
92
  const marker = markers?.get?.(type);
93
93
  // Only detected types get evidence - the rest are just listed as candidates.
94
- return { value: type, label: detected.has(type) && marker ? t("ui.prompts.types.withMarker", { type, marker }) : type };
94
+ return {
95
+ value: type,
96
+ label: detected.has(type) && marker ? t("ui.prompts.types.withMarker", { type, marker }) : type,
97
+ hint: typeInfo(type).displayName,
98
+ };
95
99
  }),
96
100
  initialValues: types.length ? types : ["basic"],
97
101
  required: true,
package/src/ui/summary.js CHANGED
@@ -34,7 +34,7 @@ export function printSummary(ctx) {
34
34
  err(t("ui.summary.features.title"));
35
35
 
36
36
  // Mention the automatic update only when the README version section really exists (added now or already there).
37
- const readmeTracked = readme === "added" || readme === "skip-marker" || readme === "skip-version-line";
37
+ const readmeTracked = readme === "added" || readme === "skip-marker" || readme === "skip-version-line" || readme === "heading-updated";
38
38
  if (mode === "full") {
39
39
  err(t("ui.summary.features.versionSystem"));
40
40
  if (readmeTracked) err(t("ui.summary.features.readmeAuto"));
@@ -1,241 +0,0 @@
1
- # project-auto-wizard:managed-workflow
2
- # Next.js CI (build verification)
3
- # On push/PR to the {{DEVELOP_BRANCH}} branch, verifies with npm ci, npm test (when a test script exists) and npm run build. Caches node_modules and .next/cache.
4
- #
5
- # GitHub Secrets
6
- # ENV_FILE (optional): contents of the .env file. CI only verifies the build, so no deploy-related Secrets are needed
7
- #
8
- # Project settings (env section)
9
- # PROJECT_NAME : project name
10
- # NODE_VERSION : Node.js version (default: 20.15.0)
11
- # ENV_FILE : environment file name (default: .env)
12
-
13
- name: PROJECT-NEXT-CI
14
-
15
- on:
16
- push:
17
- branches: ["{{DEVELOP_BRANCH}}"]
18
- pull_request:
19
- branches: ["{{DEVELOP_BRANCH}}"]
20
- workflow_dispatch:
21
-
22
- env:
23
- PROJECT_NAME: "__PROJECT_NAME__" # @wizard ask:@repo
24
- NODE_VERSION: "20.15.0"
25
- ENV_FILE: ".env"
26
- # Subdirectory of this project in a monorepo ('.' means always verify)
27
- PROJECT_PATH: "." # @wizard auto:project-path
28
-
29
- permissions:
30
- contents: read
31
- pull-requests: write
32
- issues: write
33
-
34
- jobs:
35
- # Job: change detection (in a monorepo, checks whether this project's path changed)
36
- changes:
37
- name: Detect changes
38
- runs-on: ubuntu-latest
39
- permissions:
40
- contents: read
41
- pull-requests: read
42
- outputs:
43
- project: ${{ steps.filter.outputs.project }}
44
- steps:
45
- - name: Checkout repository
46
- uses: actions/checkout@v7
47
-
48
- - name: Check whether the project path changed
49
- id: filter
50
- if: ${{ github.event_name != 'workflow_dispatch' }}
51
- uses: dorny/paths-filter@v4
52
- with:
53
- # A push only looks at this push's range (against the previous commit, 'before'). Left empty it would be the cumulative diff against the default branch.
54
- # The first push of a new branch (before is 0) and PRs get an empty value -> default behavior (against the default branch / PR base)
55
- base: ${{ github.event_name == 'push' && github.event.before != '0000000000000000000000000000000000000000' && github.event.before || '' }}
56
- filters: |
57
- project:
58
- - '${{ env.PROJECT_PATH == '.' && '**' || format('{0}/**', env.PROJECT_PATH) }}'
59
-
60
- build:
61
- name: Build Next.js application
62
- runs-on: ubuntu-latest
63
- needs: changes
64
- if: ${{ github.event_name == 'workflow_dispatch' || needs.changes.outputs.project == 'true' }}
65
- # In a monorepo (--paths), build inside that subfolder
66
- defaults:
67
- run:
68
- working-directory: ${{ env.PROJECT_PATH }}
69
-
70
- steps:
71
- - name: Checkout repository
72
- uses: actions/checkout@v7
73
-
74
- - name: Create environment file (${{ env.ENV_FILE }})
75
- run: |
76
- m() { (cd "$GITHUB_WORKSPACE" && python3 .github/scripts/messages.py get "$@"); }
77
- echo -e "${{ secrets.ENV_FILE || secrets.ENV }}" > ${{ env.ENV_FILE }}
78
- m cicd.env_file_created file="${{ env.ENV_FILE }}"
79
-
80
- - name: Set up Node.js
81
- uses: actions/setup-node@v7
82
- with:
83
- node-version: ${{ env.NODE_VERSION }}
84
- cache: "npm"
85
- cache-dependency-path: ${{ env.PROJECT_PATH }}/package-lock.json
86
-
87
- - name: Cache npm dependencies
88
- uses: actions/cache@v6
89
- with:
90
- path: ~/.npm
91
- key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
92
- restore-keys: |
93
- ${{ runner.os }}-npm-
94
-
95
- - name: Cache node_modules
96
- id: node-modules-cache
97
- uses: actions/cache@v6
98
- with:
99
- path: "**/node_modules"
100
- key: ${{ runner.os }}-node-modules-${{ hashFiles('**/package-lock.json') }}
101
- restore-keys: |
102
- ${{ runner.os }}-node-modules-
103
-
104
- - name: Install dependencies
105
- if: steps.node-modules-cache.outputs.cache-hit != 'true'
106
- run: |
107
- m() { (cd "$GITHUB_WORKSPACE" && python3 .github/scripts/messages.py get "$@"); }
108
- m cibuild.node_deps_installing
109
- npm ci
110
- m cibuild.node_deps_done
111
-
112
- # Cache the Next.js build (.next/cache)
113
- # Caches the .next/cache folder for incremental builds
114
- - name: Cache Next.js build
115
- uses: actions/cache@v6
116
- with:
117
- path: ${{ env.PROJECT_PATH }}/.next/cache
118
- key: ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}-${{ hashFiles('**.[jt]s', '**.[jt]sx') }}
119
- restore-keys: |
120
- ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}-
121
- ${{ runner.os }}-nextjs-
122
-
123
- # Runs only when package.json has a test script (the npm init default "no test specified" counts as none)
124
- - name: Run tests
125
- run: |
126
- m() { (cd "$GITHUB_WORKSPACE" && python3 .github/scripts/messages.py get "$@"); }
127
- TEST_SCRIPT=$(node -p "(require('./package.json').scripts || {}).test || ''")
128
- if [ -z "$TEST_SCRIPT" ] || echo "$TEST_SCRIPT" | grep -q "no test specified"; then
129
- m cibuild.node_no_test_script
130
- exit 0
131
- fi
132
- m cibuild.node_test_run script="$TEST_SCRIPT"
133
- # CI=true: run watch-mode test runners (react-scripts, etc.) only once
134
- CI=true npm test
135
-
136
- - name: Build project
137
- id: build
138
- run: |
139
- m() { (cd "$GITHUB_WORKSPACE" && python3 .github/scripts/messages.py get "$@"); }
140
- m cibuild.node_build_start_next
141
- m cibuild.node_build_project project="${{ env.PROJECT_NAME }}"
142
- m cibuild.node_build_node node="${{ env.NODE_VERSION }}"
143
- echo ""
144
-
145
- # Run the build and save the log
146
- npm run build > build_log.txt 2>&1 || {
147
- echo "build_failed=true" >> $GITHUB_OUTPUT
148
- echo "status=failed" >> $GITHUB_OUTPUT
149
- echo ""
150
- m cibuild.node_build_failed
151
- exit 0
152
- }
153
-
154
- echo "build_failed=false" >> $GITHUB_OUTPUT
155
- echo "status=success" >> $GITHUB_OUTPUT
156
- m cibuild.node_build_ok
157
-
158
- # Check the build output
159
- if [ -d ".next" ]; then
160
- echo ""
161
- m cibuild.node_build_output
162
- m cibuild.node_build_next_dir
163
- du -sh .next/ || true
164
- fi
165
-
166
- - name: Print build error log
167
- if: steps.build.outputs.build_failed == 'true'
168
- run: |
169
- m() { (cd "$GITHUB_WORKSPACE" && python3 .github/scripts/messages.py get "$@"); }
170
- echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
171
- m cibuild.node_errlog_title
172
- echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
173
- echo ""
174
-
175
- if [ -f "build_log.txt" ]; then
176
- tail -n 50 build_log.txt
177
- else
178
- m cibuild.node_errlog_missing
179
- fi
180
-
181
- echo ""
182
- echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
183
- exit 1
184
-
185
- - name: Upload build log
186
- if: always()
187
- uses: actions/upload-artifact@v7
188
- with:
189
- name: build-log
190
- path: ${{ env.PROJECT_PATH }}/build_log.txt
191
- retention-days: 7
192
-
193
- - name: Build result summary
194
- if: always()
195
- run: |
196
- m() { (cd "$GITHUB_WORKSPACE" && python3 .github/scripts/messages.py get "$@"); }
197
- echo ""
198
- echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
199
- if [ "${{ steps.build.outputs.status }}" = "success" ]; then
200
- m cibuild.node_result_next_ok
201
- else
202
- m cibuild.node_result_next_fail
203
- fi
204
- echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
205
- echo ""
206
- m cibuild.node_result_title
207
- m cibuild.node_result_project project="${{ env.PROJECT_NAME }}"
208
- m cibuild.node_result_node node="${{ env.NODE_VERSION }}"
209
- m cibuild.node_result_status status="${{ steps.build.outputs.status }}"
210
- m cibuild.node_result_branch branch="${{ github.ref_name }}"
211
- m cibuild.node_result_commit sha="${{ github.sha }}"
212
- m cibuild.node_result_time time="$(date '+%Y-%m-%d %H:%M:%S UTC')"
213
- echo ""
214
- echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
215
-
216
- # Job: CI Gate (the single job to register as a required check)
217
- ci-gate:
218
- name: CI Gate
219
- if: ${{ always() }}
220
- needs: [changes, build]
221
- runs-on: ubuntu-latest
222
- steps:
223
- # Only the message catalog is needed here, so check out just that
224
- - name: Checkout message catalog
225
- uses: actions/checkout@v7
226
- with:
227
- sparse-checkout: |
228
- .github/scripts/messages.py
229
- version.yml
230
- sparse-checkout-cone-mode: false
231
-
232
- - name: Aggregate results
233
- env:
234
- RESULTS: ${{ toJSON(needs.*.result) }}
235
- run: |
236
- m() { (cd "$GITHUB_WORKSPACE" && python3 .github/scripts/messages.py get "$@"); }
237
- m cibuild.gate_results results="$RESULTS"
238
- if echo "$RESULTS" | grep -Eq '"(failure|cancelled)"'; then
239
- m cibuild.gate_failed
240
- exit 1
241
- fi