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.
- package/README.ja.md +3 -1
- package/README.ko.md +3 -1
- package/README.md +4 -2
- package/README.zh-CN.md +3 -1
- package/package.json +1 -1
- package/payload/config/breaking-changes.json +31 -5
- package/payload/config/wizard-prompts.yml +0 -4
- package/payload/scripts/changelog_manager.py +8 -8
- package/payload/scripts/messages.py +460 -24
- package/payload/scripts/version_manager.py +102 -73
- package/payload/version.yml.template +2 -2
- package/payload/workflows/common/PROJECT-COMMON-AI-PR-SUMMARY.yaml +9 -4
- package/payload/workflows/common/PROJECT-COMMON-AUTO-CHANGELOG-CONTROL.yaml +62 -30
- package/payload/workflows/common/PROJECT-COMMON-README-VERSION-UPDATE.yaml +96 -44
- package/payload/workflows/common/PROJECT-COMMON-RELEASE-PUBLISH.yaml +79 -35
- package/payload/workflows/common/PROJECT-COMMON-VERSION-CONTROL.yaml +71 -35
- package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-FIREBASE-CICD.yaml +24 -8
- package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-PLAYSTORE-CICD.yaml +29 -10
- package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-SELFHOSTED-CICD.yaml +57 -23
- package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-TEST-APK.yaml +155 -72
- package/payload/workflows/flutter/PROJECT-FLUTTER-APP-BUILD-TRIGGER.yaml +49 -20
- package/payload/workflows/flutter/PROJECT-FLUTTER-CI.yaml +162 -62
- package/payload/workflows/flutter/PROJECT-FLUTTER-IOS-TEST-TESTFLIGHT.yaml +184 -87
- package/payload/workflows/flutter/PROJECT-FLUTTER-IOS-TESTFLIGHT.yaml +38 -15
- package/payload/workflows/go/PROJECT-GO-CI.yaml +14 -5
- package/payload/workflows/go/PROJECT-GO-PR-PREVIEW.yaml +375 -120
- package/payload/workflows/go/PROJECT-GO-SIMPLE-CICD.yaml +9 -4
- package/payload/workflows/python/PROJECT-PYTHON-CI.yaml +12 -4
- package/payload/workflows/python/PROJECT-PYTHON-PR-PREVIEW.yaml +375 -120
- package/payload/workflows/python/PROJECT-PYTHON-SIMPLE-CICD.yaml +9 -4
- package/payload/workflows/react/PROJECT-REACT-CI.yaml +38 -9
- package/payload/workflows/react/PROJECT-REACT-CICD.yaml +19 -5
- package/payload/workflows/spring/PROJECT-SPRING-CI.yml +71 -35
- package/payload/workflows/spring/server-deploy/PROJECT-SPRING-NONSTOP-NGINX-CICD.yaml +9 -6
- package/payload/workflows/spring/server-deploy/PROJECT-SPRING-NONSTOP-TRAEFIK-CICD.yaml +7 -4
- package/payload/workflows/spring/server-deploy/PROJECT-SPRING-PR-PREVIEW.yaml +425 -144
- package/payload/workflows/spring/server-deploy/PROJECT-SPRING-SIMPLE-CICD.yaml +7 -4
- package/src/cli/args.js +17 -7
- package/src/commands/doctor.js +16 -0
- package/src/commands/dry-run.js +10 -1
- package/src/commands/full.js +5 -2
- package/src/commands/interactive.js +4 -1
- package/src/commands/status.js +14 -1
- package/src/core/copy/readme.js +58 -2
- package/src/core/copy/simple.js +31 -0
- package/src/core/copy/workflows.js +2 -2
- package/src/core/deploy-style.js +1 -1
- package/src/core/detect.js +3 -2
- package/src/core/types.js +15 -8
- package/src/core/version-yml.js +52 -6
- package/src/i18n/catalog/en/cli.js +1 -0
- package/src/i18n/catalog/en/commands.js +9 -0
- package/src/i18n/catalog/en/copy.js +1 -0
- package/src/i18n/catalog/en/core2.js +3 -0
- package/src/i18n/catalog/ko/cli.js +1 -0
- package/src/i18n/catalog/ko/commands.js +9 -0
- package/src/i18n/catalog/ko/copy.js +1 -0
- package/src/i18n/catalog/ko/core2.js +3 -0
- package/src/index.js +8 -4
- package/src/ui/env-plan.js +1 -1
- package/src/ui/prompts.js +7 -3
- package/src/ui/summary.js +1 -1
- package/payload/workflows/next/PROJECT-NEXT-CI.yaml +0 -241
- 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
|
-
|
|
275
|
-
|
|
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.
|
package/src/ui/env-plan.js
CHANGED
|
@@ -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
|
|
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 {
|
|
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
|