project-auto-wizard 0.13.3 → 0.15.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/README.ja.md +2 -0
- package/README.ko.md +2 -0
- package/README.md +3 -1
- package/README.zh-CN.md +2 -0
- package/bin/project-auto-wizard.js +18 -4
- package/package.json +2 -2
- package/payload/config/breaking-changes.json +28 -14
- package/payload/config/wizard-prompts.yml +102 -46
- package/payload/flutter-app/android/fastlane/Fastfile.playstore +44 -32
- package/payload/flutter-app/ios/ExportOptions.plist +8 -8
- package/payload/flutter-app/ios/fastlane/Fastfile +57 -43
- package/payload/scripts/changelog_manager.py +304 -265
- package/payload/scripts/issue_helper.py +54 -50
- package/payload/scripts/messages.py +2585 -0
- package/payload/scripts/truncate_release_notes.py +7 -6
- package/payload/scripts/version_manager.py +110 -107
- package/payload/version.yml.template +2 -1
- package/payload/workflows/common/PROJECT-COMMON-AI-PR-SUMMARY.yaml +23 -16
- package/payload/workflows/common/PROJECT-COMMON-AUTO-CHANGELOG-CONTROL.yaml +106 -63
- package/payload/workflows/common/PROJECT-COMMON-ISSUE-HELPER.yaml +17 -15
- package/payload/workflows/common/PROJECT-COMMON-README-VERSION-UPDATE.yaml +55 -45
- package/payload/workflows/common/PROJECT-COMMON-RELEASE-PUBLISH.yaml +90 -72
- package/payload/workflows/common/PROJECT-COMMON-VERSION-CONTROL.yaml +59 -45
- package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-FIREBASE-CICD.yaml +194 -184
- package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-PLAYSTORE-CICD.yaml +226 -213
- package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-SELFHOSTED-CICD.yaml +46 -42
- package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-TEST-APK.yaml +334 -241
- package/payload/workflows/flutter/PROJECT-FLUTTER-APP-BUILD-TRIGGER.yaml +152 -128
- package/payload/workflows/flutter/PROJECT-FLUTTER-CI.yaml +246 -188
- package/payload/workflows/flutter/PROJECT-FLUTTER-IOS-TEST-TESTFLIGHT.yaml +353 -238
- package/payload/workflows/flutter/PROJECT-FLUTTER-IOS-TESTFLIGHT.yaml +96 -85
- package/payload/workflows/go/PROJECT-GO-CI.yaml +47 -36
- package/payload/workflows/go/PROJECT-GO-PR-PREVIEW.yaml +945 -508
- package/payload/workflows/go/PROJECT-GO-SIMPLE-CICD.yaml +155 -133
- package/payload/workflows/next/PROJECT-NEXT-CI.yaml +76 -60
- package/payload/workflows/next/PROJECT-NEXT-CICD.yaml +97 -77
- package/payload/workflows/python/PROJECT-PYTHON-CI.yaml +62 -47
- package/payload/workflows/python/PROJECT-PYTHON-PR-PREVIEW.yaml +942 -505
- package/payload/workflows/python/PROJECT-PYTHON-SIMPLE-CICD.yaml +148 -126
- package/payload/workflows/react/PROJECT-REACT-CI.yaml +86 -70
- package/payload/workflows/react/PROJECT-REACT-CICD.yaml +112 -92
- package/payload/workflows/spring/PROJECT-SPRING-CI.yml +112 -82
- package/payload/workflows/spring/server-deploy/PROJECT-SPRING-NONSTOP-NGINX-CICD.yaml +180 -150
- package/payload/workflows/spring/server-deploy/PROJECT-SPRING-NONSTOP-TRAEFIK-CICD.yaml +161 -131
- package/payload/workflows/spring/server-deploy/PROJECT-SPRING-PR-PREVIEW.yaml +970 -549
- package/payload/workflows/spring/server-deploy/PROJECT-SPRING-SIMPLE-CICD.yaml +193 -162
- package/src/cli/args.js +98 -52
- package/src/cli/help.js +8 -41
- package/src/commands/doctor.js +90 -89
- package/src/commands/dry-run.js +64 -43
- package/src/commands/full.js +79 -77
- package/src/commands/install-settings.js +16 -15
- package/src/commands/interactive-flutter.js +20 -19
- package/src/commands/interactive.js +135 -132
- package/src/commands/purge.js +47 -42
- package/src/commands/status.js +36 -36
- package/src/commands/uninstall.js +43 -41
- package/src/context.js +12 -11
- package/src/core/assets.js +12 -11
- package/src/core/baseline.js +27 -27
- package/src/core/branches.js +29 -27
- package/src/core/branding.js +7 -6
- package/src/core/breaking-check.js +22 -21
- package/src/core/breaking.js +15 -8
- package/src/core/copy/app-files.js +5 -4
- package/src/core/copy/flutter-app.js +8 -8
- package/src/core/copy/gitignore.js +58 -50
- package/src/core/copy/readme.js +102 -36
- package/src/core/copy/simple.js +16 -15
- package/src/core/copy/workflows.js +131 -124
- package/src/core/deploy-style.js +51 -48
- package/src/core/detect-fs.js +57 -57
- package/src/core/detect.js +67 -65
- package/src/core/errors.js +2 -2
- package/src/core/flutter-doctor.js +17 -16
- package/src/core/flutter-hooks.js +33 -32
- package/src/core/flutter-options.js +34 -33
- package/src/core/fsutil.js +12 -12
- package/src/core/installed-stores.js +4 -4
- package/src/core/logger.js +42 -40
- package/src/core/paths-resolve.js +88 -86
- package/src/core/paths.js +10 -10
- package/src/core/release-options.js +8 -8
- package/src/core/removal-exec.js +14 -13
- package/src/core/removal-plan.js +37 -37
- package/src/core/types.js +47 -47
- package/src/core/verify.js +44 -38
- package/src/core/version-yml.js +76 -68
- package/src/core/wizard-env.js +46 -46
- package/src/core/wizard-labels.js +63 -41
- package/src/i18n/catalog/en/cli.js +50 -0
- package/src/i18n/catalog/en/commands.js +188 -0
- package/src/i18n/catalog/en/copy.js +20 -0
- package/src/i18n/catalog/en/core.js +74 -0
- package/src/i18n/catalog/en/core2.js +15 -0
- package/src/i18n/catalog/en/ui.js +221 -0
- package/src/i18n/catalog/en.js +12 -0
- package/src/i18n/catalog/index.js +5 -0
- package/src/i18n/catalog/ko/cli.js +50 -0
- package/src/i18n/catalog/ko/commands.js +188 -0
- package/src/i18n/catalog/ko/copy.js +20 -0
- package/src/i18n/catalog/ko/core.js +74 -0
- package/src/i18n/catalog/ko/core2.js +15 -0
- package/src/i18n/catalog/ko/ui.js +221 -0
- package/src/i18n/catalog/ko.js +10 -0
- package/src/i18n/index.js +44 -0
- package/src/i18n/languages.js +9 -0
- package/src/index.js +132 -97
- package/src/ui/ansi.js +7 -7
- package/src/ui/banner.js +7 -6
- package/src/ui/env-plan.js +82 -81
- package/src/ui/prompts.js +97 -122
- package/src/ui/readline-engine.js +59 -58
- package/src/ui/status-cards.js +37 -31
- package/src/ui/summary.js +90 -89
package/src/core/deploy-style.js
CHANGED
|
@@ -1,54 +1,56 @@
|
|
|
1
|
-
//
|
|
2
|
-
// Nginx
|
|
3
|
-
//
|
|
1
|
+
// Deploy style: the server-deploy CD workflows are alternatives to each other.
|
|
2
|
+
// Nginx zero-downtime and Traefik zero-downtime are never used together, so only one is installed.
|
|
3
|
+
// The chosen one is installed with its push trigger enabled, so nothing is installed that then never runs.
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { existsSync, readFileSync, readdirSync, renameSync, rmSync } from "node:fs";
|
|
6
6
|
import { sha256 } from "./baseline.js";
|
|
7
7
|
import { PAYLOAD, typeWorkflowDirs } from "./paths.js";
|
|
8
8
|
import { SINGLE_SERVER_CD_FILES } from "./types.js";
|
|
9
|
+
import { t } from "../i18n/index.js";
|
|
9
10
|
|
|
10
|
-
//
|
|
11
|
+
// Identified by file name suffix, because the type prefix (PROJECT-SPRING- etc.) differs per type.
|
|
12
|
+
// label is a getter so the text follows the language resolved after import.
|
|
11
13
|
export const DEPLOY_STYLES = [
|
|
12
|
-
{ value: "simple", suffix: "-SIMPLE-CICD.yaml", label
|
|
13
|
-
{ value: "nginx", suffix: "-NONSTOP-NGINX-CICD.yaml", label
|
|
14
|
-
{ value: "traefik", suffix: "-NONSTOP-TRAEFIK-CICD.yaml", label
|
|
14
|
+
{ value: "simple", suffix: "-SIMPLE-CICD.yaml", get label() { return t("core.deploy.label.simple"); } },
|
|
15
|
+
{ value: "nginx", suffix: "-NONSTOP-NGINX-CICD.yaml", get label() { return t("core.deploy.label.nginx"); } },
|
|
16
|
+
{ value: "traefik", suffix: "-NONSTOP-TRAEFIK-CICD.yaml", get label() { return t("core.deploy.label.traefik"); } },
|
|
15
17
|
];
|
|
16
18
|
|
|
17
19
|
export const DEFAULT_DEPLOY_STYLE = "simple";
|
|
18
20
|
|
|
19
|
-
//
|
|
20
|
-
//
|
|
21
|
-
//
|
|
22
|
-
//
|
|
21
|
+
// Value for projects that do no server deploy at all (frontend-only, libraries, etc.).
|
|
22
|
+
// Not put into DEPLOY_STYLES: isDeployWorkflow/suffixOf iterate that array, and an empty suffix would make
|
|
23
|
+
// endsWith("") always true, so every file would be misjudged as CD and cleanupOtherDeployWorkflows would
|
|
24
|
+
// delete all installed workflows (a regression).
|
|
23
25
|
export const NO_DEPLOY_STYLE = "none";
|
|
24
26
|
|
|
25
27
|
export const isDeployStyle = (v) => v === NO_DEPLOY_STYLE || DEPLOY_STYLES.some((s) => s.value === v);
|
|
26
28
|
|
|
27
|
-
//
|
|
29
|
+
// Whether this file is a CD body (= subject of the pick-one choice). PR preview is orthogonal to the deploy style, so it is excluded.
|
|
28
30
|
export const isDeployWorkflow = (filename) => DEPLOY_STYLES.some((s) => filename.endsWith(s.suffix));
|
|
29
31
|
|
|
30
32
|
const PREVIEW_SUFFIX = "-PR-PREVIEW.yaml";
|
|
31
33
|
|
|
32
|
-
//
|
|
33
|
-
// "
|
|
34
|
+
// All workflows that deploy to a server: CD bodies, single CDs (react, next) and PR preview.
|
|
35
|
+
// "No deploy" removes all of them identically for every type. Removing only some would leave server Secret requirements behind.
|
|
34
36
|
export const isServerDeployWorkflow = (filename) =>
|
|
35
37
|
isDeployWorkflow(filename) || SINGLE_SERVER_CD_FILES.has(filename) || filename.endsWith(PREVIEW_SUFFIX);
|
|
36
38
|
|
|
37
|
-
//
|
|
38
|
-
//
|
|
39
|
+
// Whether any selected type has server deploy workflows, judged from the payload files.
|
|
40
|
+
// If none (node, flutter, etc.) the deploy style has no effect on the install result, so it is neither asked nor recorded.
|
|
39
41
|
export function hasServerDeployWorkflows(payloadRoot, types = []) {
|
|
40
42
|
return types.some((type) => typeWorkflowDirs(payloadRoot, type)
|
|
41
43
|
.some((dir) => existsSync(dir) && readdirSync(dir).some(isServerDeployWorkflow)));
|
|
42
44
|
}
|
|
43
45
|
|
|
44
|
-
//
|
|
46
|
+
// Whether any selected type has zero-downtime (nginx, traefik) workflows; if none, the zero-downtime choices are not shown.
|
|
45
47
|
export function hasNonstopWorkflows(payloadRoot, types = []) {
|
|
46
48
|
const nonstop = DEPLOY_STYLES.filter((s) => s.value !== DEFAULT_DEPLOY_STYLE).map((s) => s.suffix);
|
|
47
49
|
return types.some((type) => typeWorkflowDirs(payloadRoot, type)
|
|
48
50
|
.some((dir) => existsSync(dir) && readdirSync(dir).some((f) => nonstop.some((sfx) => f.endsWith(sfx)))));
|
|
49
51
|
}
|
|
50
52
|
|
|
51
|
-
// payload/workflows
|
|
53
|
+
// All workflow file names under payload/workflows/**. File names are unique across types thanks to the PROJECT-{TYPE}- prefix.
|
|
52
54
|
export function payloadWorkflowNames(payloadRoot) {
|
|
53
55
|
const names = new Set();
|
|
54
56
|
const root = join(payloadRoot, PAYLOAD.workflowsDir);
|
|
@@ -59,24 +61,24 @@ export function payloadWorkflowNames(payloadRoot) {
|
|
|
59
61
|
return names;
|
|
60
62
|
}
|
|
61
63
|
|
|
62
|
-
//
|
|
63
|
-
// "
|
|
64
|
+
// Unknown values converge to the default. Returning an empty suffix would make endsWith("") always true,
|
|
65
|
+
// i.e. "everything passes", and an invalid value would silently leak into installing every CD.
|
|
64
66
|
const suffixOf = (style) =>
|
|
65
67
|
(DEPLOY_STYLES.find((s) => s.value === style) ?? DEPLOY_STYLES.find((s) => s.value === DEFAULT_DEPLOY_STYLE)).suffix;
|
|
66
68
|
const SIMPLE_SUFFIX = suffixOf(DEFAULT_DEPLOY_STYLE);
|
|
67
69
|
|
|
68
|
-
//
|
|
69
|
-
//
|
|
70
|
+
// Whether this is the single-server deploy CD of a type that lacks the chosen style's CD (e.g. nginx was chosen but python and go have no NONSTOP).
|
|
71
|
+
// Such types get the single-server deploy instead: deleting it or moving it to .bak right after install would leave no deploy at all.
|
|
70
72
|
function isFallbackSimple(filename, style, available) {
|
|
71
73
|
if (!available || !filename.endsWith(SIMPLE_SUFFIX)) return false;
|
|
72
74
|
const prefix = filename.slice(0, -SIMPLE_SUFFIX.length);
|
|
73
75
|
return !available.has(prefix + suffixOf(style));
|
|
74
76
|
}
|
|
75
77
|
|
|
76
|
-
//
|
|
77
|
-
// "none"
|
|
78
|
-
// available: payload
|
|
79
|
-
//
|
|
78
|
+
// File filter: only the chosen style's CD passes. Non-CD files (PR preview, common, etc.) always pass.
|
|
79
|
+
// "none" installs no server-deploy workflow at all (CD, single CD, PR preview).
|
|
80
|
+
// available: set of payload workflow file names (payloadWorkflowNames). When given, types without the chosen
|
|
81
|
+
// style fall back to the single-server deploy. Without it, only the suffix decides.
|
|
80
82
|
export function deployFilter(style, available = null) {
|
|
81
83
|
if (style === NO_DEPLOY_STYLE) return (filename) => !isServerDeployWorkflow(filename);
|
|
82
84
|
const suffix = suffixOf(style);
|
|
@@ -84,7 +86,7 @@ export function deployFilter(style, available = null) {
|
|
|
84
86
|
|| isFallbackSimple(filename, style, available);
|
|
85
87
|
}
|
|
86
88
|
|
|
87
|
-
//
|
|
89
|
+
// Types that get the single-server deploy instead because they lack workflows for the chosen style (nginx, traefik); for notices.
|
|
88
90
|
export function fallbackStyleTypes(payloadRoot, types = [], style) {
|
|
89
91
|
if (!style || style === NO_DEPLOY_STYLE || suffixOf(style) === SIMPLE_SUFFIX) return [];
|
|
90
92
|
return types.filter((type) => {
|
|
@@ -94,9 +96,10 @@ export function fallbackStyleTypes(payloadRoot, types = [], style) {
|
|
|
94
96
|
});
|
|
95
97
|
}
|
|
96
98
|
|
|
97
|
-
//
|
|
98
|
-
//
|
|
99
|
-
//
|
|
99
|
+
// The deploy style that is actually installed: if no selected type has workflows for the chosen zero-downtime
|
|
100
|
+
// style, all of them get the single-server deploy, so the result is simple. Recording the chosen value as is would
|
|
101
|
+
// make version.yml and status disagree with what was installed, and the next run would inherit it as the default.
|
|
102
|
+
// If even one type has that style, the chosen value is kept.
|
|
100
103
|
export function effectiveDeployStyle(payloadRoot, types = [], style) {
|
|
101
104
|
if (!style || style === NO_DEPLOY_STYLE || suffixOf(style) === SIMPLE_SUFFIX) return style;
|
|
102
105
|
const installed = types.some((type) => typeWorkflowDirs(payloadRoot, type)
|
|
@@ -104,12 +107,12 @@ export function effectiveDeployStyle(payloadRoot, types = [], style) {
|
|
|
104
107
|
return installed ? style : DEFAULT_DEPLOY_STYLE;
|
|
105
108
|
}
|
|
106
109
|
|
|
107
|
-
//
|
|
108
|
-
//
|
|
109
|
-
//
|
|
110
|
+
// Zero-downtime templates ship with the push trigger commented out (because the default deploy is the single server).
|
|
111
|
+
// Once the user has chosen that style the trigger must be on: otherwise nothing happens after install and the
|
|
112
|
+
// user has to edit the YAML by hand.
|
|
110
113
|
//
|
|
111
|
-
//
|
|
112
|
-
//
|
|
114
|
+
// Inside the first `on:` block only the two characters `# ` are removed, so the inner indentation hierarchy is preserved.
|
|
115
|
+
// Explanatory comments are left alone because their stripped content does not start with push/branches/-.
|
|
113
116
|
const TRIGGER_CONTENT = /^\s*(push:|branches:|- )/;
|
|
114
117
|
|
|
115
118
|
export function activateDeployTrigger(content) {
|
|
@@ -121,7 +124,7 @@ export function activateDeployTrigger(content) {
|
|
|
121
124
|
const line = lines[i];
|
|
122
125
|
if (/^on:\s*$/.test(line)) { inOn = true; continue; }
|
|
123
126
|
if (!inOn) continue;
|
|
124
|
-
if (/^\S/.test(line)) break; //
|
|
127
|
+
if (/^\S/.test(line)) break; // a top-level key ends the on block
|
|
125
128
|
const m = line.match(/^(\s*)# ?(.*)$/);
|
|
126
129
|
if (!m || !TRIGGER_CONTENT.test(m[2])) continue;
|
|
127
130
|
lines[i] = `${m[1]}${m[2]}`;
|
|
@@ -130,20 +133,20 @@ export function activateDeployTrigger(content) {
|
|
|
130
133
|
return changed ? lines.join(eol) : content;
|
|
131
134
|
}
|
|
132
135
|
|
|
133
|
-
//
|
|
136
|
+
// Cleans up the previous CD when reinstalling with a different style.
|
|
134
137
|
//
|
|
135
|
-
//
|
|
136
|
-
// "
|
|
137
|
-
//
|
|
138
|
+
// Leaving it would keep the old style's push trigger alive and deploy twice. Telling the user to "delete it
|
|
139
|
+
// yourself" would leave the repo in a broken state even after install finishes. Files the wizard installed are
|
|
140
|
+
// cleaned up by the wizard.
|
|
138
141
|
//
|
|
139
|
-
//
|
|
140
|
-
//
|
|
142
|
+
// untouched (same as the baseline installed hash) -> deleted. No reason to ask.
|
|
143
|
+
// modified -> moved to .bak. Content is kept and only the trigger is killed.
|
|
141
144
|
//
|
|
142
|
-
// opts.available
|
|
143
|
-
//
|
|
144
|
-
// opts.justWritten
|
|
145
|
-
// opts.dryRun
|
|
146
|
-
//
|
|
145
|
+
// opts.available - set of payload workflow file names. When given, only files in it (files the wizard installs)
|
|
146
|
+
// are cleaned up, and the single-server deploy of types lacking the chosen style is kept.
|
|
147
|
+
// opts.justWritten - files the wizard just wrote in this run. Not mistaken for modified files even without a baseline, as on a first install.
|
|
148
|
+
// opts.dryRun - only decide, do not touch files. Makes the --dry-run preview use the same decision as a real run.
|
|
149
|
+
// Returns { removed:[], backedUp:[] }, reported as is on the completion screen and in the install record.
|
|
147
150
|
export function cleanupOtherDeployWorkflows(workflowsDir, installedFilenames, style, baseline, { available = null, justWritten = [], dryRun = false } = {}) {
|
|
148
151
|
const keep = deployFilter(style, available);
|
|
149
152
|
const written = new Set(justWritten);
|
|
@@ -152,7 +155,7 @@ export function cleanupOtherDeployWorkflows(workflowsDir, installedFilenames, st
|
|
|
152
155
|
|
|
153
156
|
for (const filename of installedFilenames) {
|
|
154
157
|
if (!isServerDeployWorkflow(filename) || keep(filename)) continue;
|
|
155
|
-
if (available && !available.has(filename)) continue; //
|
|
158
|
+
if (available && !available.has(filename)) continue; // never touch a similarly named workflow the user created
|
|
156
159
|
const p = join(workflowsDir, filename);
|
|
157
160
|
if (!existsSync(p)) continue;
|
|
158
161
|
|
package/src/core/detect-fs.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
//
|
|
2
|
-
// detect.js
|
|
1
|
+
// Project detection on the real filesystem.
|
|
2
|
+
// Drives the pure functions of detect.js with fs/git.
|
|
3
3
|
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
4
4
|
import { join, basename } from "node:path";
|
|
5
5
|
import { execFileSync } from "node:child_process";
|
|
6
6
|
import { detectTypesFromMarkers, detectVersionFromFiles, detectBuildNumberFromFiles, detectJdkFromFiles, resolveMarkers } from "./detect.js";
|
|
7
7
|
import { parseExisting } from "./version-yml.js";
|
|
8
8
|
import { isValidBranchName } from "./branches.js";
|
|
9
|
+
import { t as tr } from "../i18n/index.js";
|
|
9
10
|
|
|
10
11
|
const hasFile = (root) => (rel) => existsSync(join(root, rel));
|
|
11
12
|
const readFile = (root) => (rel) => {
|
|
@@ -18,19 +19,19 @@ function gitOut(root, args) {
|
|
|
18
19
|
} catch { return ""; }
|
|
19
20
|
}
|
|
20
21
|
|
|
21
|
-
//
|
|
22
|
-
// paths:
|
|
23
|
-
// warn:
|
|
22
|
+
// Type detection: project_types in version.yml wins (source of truth), otherwise a marker scan.
|
|
23
|
+
// paths: Map<type,path> from --paths. A monorepo has no markers at the root, so the types the user listed are used.
|
|
24
|
+
// warn: when no root marker exists and detection falls back to basic, reports projects found in subfolders.
|
|
24
25
|
export function detectTypes(root, { paths = new Map(), warn } = {}) {
|
|
25
26
|
const vy = join(root, "version.yml");
|
|
26
27
|
if (existsSync(vy)) {
|
|
27
28
|
const { types } = parseExisting(readFileSync(vy, "utf8"));
|
|
28
|
-
if (types.length) return types; // basic
|
|
29
|
+
if (types.length) return types; // includes basic; used as-is when explicit
|
|
29
30
|
}
|
|
30
31
|
const fromRoot = detectTypesFromMarkers({ has: hasFile(root), read: readFile(root) });
|
|
31
32
|
if (paths.size) {
|
|
32
|
-
// --paths
|
|
33
|
-
//
|
|
33
|
+
// The --paths order decides the primary type. Matches the marker-scan rule that drops the root
|
|
34
|
+
// package.json's node when another type exists.
|
|
34
35
|
const merged = [...new Set([...paths.keys(), ...fromRoot])].filter((t) => t !== "basic");
|
|
35
36
|
const types = merged.length > 1 ? merged.filter((t) => t !== "node" || paths.has("node")) : merged;
|
|
36
37
|
return types.length ? types : ["basic"];
|
|
@@ -42,15 +43,14 @@ export function detectTypes(root, { paths = new Map(), warn } = {}) {
|
|
|
42
43
|
for (const { dir, types } of found) for (const t of types) if (!firstDir.has(t)) firstDir.set(t, dir);
|
|
43
44
|
const list = found.map(({ dir, types }) => `${dir}(${types.join(", ")})`).join(", ");
|
|
44
45
|
const hint = [...firstDir].map(([t, d]) => `${t}=${d}`).join(",");
|
|
45
|
-
warn(
|
|
46
|
-
` 모노레포라면 --paths "${hint}"로 다시 실행하세요.`);
|
|
46
|
+
warn(tr("core.detectFs.monorepoWarn", { list, hint }));
|
|
47
47
|
}
|
|
48
48
|
}
|
|
49
49
|
return fromRoot;
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
//
|
|
53
|
-
//
|
|
52
|
+
// Finds folders with project markers up to 2 levels below the root (for monorepo hints).
|
|
53
|
+
// Build output, dependency and native folders only add false positives, so they are skipped, and the children of a found folder are not scanned.
|
|
54
54
|
const SUBDIR_PRUNE = new Set(["node_modules", "build", "dist", "android", "ios", "venv", "__pycache__"]);
|
|
55
55
|
function findSubdirProjects(root, maxDepth = 2) {
|
|
56
56
|
const found = [];
|
|
@@ -70,10 +70,10 @@ function findSubdirProjects(root, maxDepth = 2) {
|
|
|
70
70
|
return found.sort((a, b) => a.dir.localeCompare(b.dir));
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
-
//
|
|
74
|
-
// hint:
|
|
75
|
-
//
|
|
76
|
-
//
|
|
73
|
+
// Version detection: reads each type's version files in order.
|
|
74
|
+
// hint: fix guidance appended to the fallback warning (differs between interactive mode and the CLI).
|
|
75
|
+
// In a monorepo (--paths) the version files live inside the type folders; looking only at the root would reset to 0.0.1/1.
|
|
76
|
+
// Search order: primary type folder, then the other type folders, then the root.
|
|
77
77
|
function projectBases(types = [], paths = null) {
|
|
78
78
|
const bases = [];
|
|
79
79
|
for (const t of types) {
|
|
@@ -83,7 +83,7 @@ function projectBases(types = [], paths = null) {
|
|
|
83
83
|
return bases.length ? [...bases, "."] : ["."];
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
-
//
|
|
86
|
+
// Uses the value from the first base folder that has a result, so read and list follow the same folder priority.
|
|
87
87
|
function firstFromBases(bases, fn) {
|
|
88
88
|
return (rel) => {
|
|
89
89
|
for (const b of bases) {
|
|
@@ -98,7 +98,7 @@ function readFromProject(root, types = [], paths = null) {
|
|
|
98
98
|
return firstFromBases(projectBases(types, paths), readFile(root));
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
-
//
|
|
101
|
+
// Subfolder names (for React Native's ios/<app>/Info.plist lookup). null when the folder does not exist.
|
|
102
102
|
function listFromProject(root, types = [], paths = null) {
|
|
103
103
|
const listDirs = (rel) => {
|
|
104
104
|
try {
|
|
@@ -116,45 +116,45 @@ export function detectVersion(root, { warn = (m) => console.error(m), hint, type
|
|
|
116
116
|
return detectVersionFromFiles({ read, readJson, list, gitTag, warn, hint, types });
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
-
//
|
|
119
|
+
// Real marker file per type, so the detection log and install log cite the same evidence file.
|
|
120
120
|
export function detectMarkers(root, types = []) {
|
|
121
121
|
return resolveMarkers(types, hasFile(root));
|
|
122
122
|
}
|
|
123
123
|
|
|
124
|
-
//
|
|
125
|
-
// base:
|
|
124
|
+
// Build JDK detection, so the measured value can be used as the deploy workflow's JAVA_VERSION default.
|
|
125
|
+
// base: the spring project root in a monorepo (relative to the repo root).
|
|
126
126
|
export function detectJdk(root, base = ".") {
|
|
127
127
|
const rel = base && base !== "." ? (r) => `${base}/${r}` : (r) => r;
|
|
128
128
|
const read = readFile(root);
|
|
129
129
|
return detectJdkFromFiles({ read: (r) => read(rel(r)) });
|
|
130
130
|
}
|
|
131
131
|
|
|
132
|
-
//
|
|
132
|
+
// Build number detection: on a fresh integration, reads the real build number from pubspec.yaml/build.gradle/app.json.
|
|
133
133
|
export function detectBuildNumber(root, { types = [], paths = null, warn = (m) => console.error(m) } = {}) {
|
|
134
134
|
const read = readFromProject(root, types, paths);
|
|
135
135
|
const readJson = (rel) => { const c = read(rel); try { return c ? JSON.parse(c) : null; } catch { return null; } };
|
|
136
136
|
return detectBuildNumberFromFiles({ types, read, readJson, warn });
|
|
137
137
|
}
|
|
138
138
|
|
|
139
|
-
//
|
|
140
|
-
//
|
|
141
|
-
//
|
|
142
|
-
//
|
|
143
|
-
// hint:
|
|
139
|
+
// Default branch detection: symbolic-ref, then remote show, then main.
|
|
140
|
+
// For an empty remote (remote add without a push yet) remote show returns "HEAD branch: (unknown)". If that value
|
|
141
|
+
// were written into the workflow trigger, release automation would silently stop, so only valid branch names are
|
|
142
|
+
// accepted; otherwise it falls back to the current local branch (first push target), then main, and warns.
|
|
143
|
+
// hint: fix guidance appended to the warning (differs between interactive mode and the CLI).
|
|
144
144
|
export function detectDefaultBranch(root, { warn = null, hint = "" } = {}) {
|
|
145
145
|
const b = gitOut(root, ["symbolic-ref", "refs/remotes/origin/HEAD"]).replace(/^refs\/remotes\/origin\//, "");
|
|
146
146
|
if (isValidBranchName(b)) return b;
|
|
147
147
|
const show = gitOut(root, ["remote", "show", "origin"]);
|
|
148
148
|
const m = show.match(/HEAD branch:\s*(\S+)/);
|
|
149
149
|
if (m && isValidBranchName(m[1])) return m[1];
|
|
150
|
-
if (!m) return "main"; // origin
|
|
150
|
+
if (!m) return "main"; // no origin: keep the existing rule
|
|
151
151
|
const local = gitOut(root, ["symbolic-ref", "--short", "HEAD"]);
|
|
152
152
|
const fallback = isValidBranchName(local) ? local : "main";
|
|
153
|
-
warn?.(
|
|
153
|
+
warn?.(tr("core.detectFs.defaultBranchWarn", { fallback, hint: hint ? ` ${hint}` : "" }));
|
|
154
154
|
return fallback;
|
|
155
155
|
}
|
|
156
156
|
|
|
157
|
-
//
|
|
157
|
+
// Repo name: last segment of git remote get-url origin, or the folder name on failure.
|
|
158
158
|
export function detectRepoName(root) {
|
|
159
159
|
const url = gitOut(root, ["remote", "get-url", "origin"]);
|
|
160
160
|
if (url) {
|
|
@@ -164,32 +164,32 @@ export function detectRepoName(root) {
|
|
|
164
164
|
return basename(root);
|
|
165
165
|
}
|
|
166
166
|
|
|
167
|
-
// Spring application*.yml
|
|
168
|
-
// find {base} -path "*/src/main/resources/application*.yml" | head -1
|
|
169
|
-
//
|
|
167
|
+
// Spring application*.yml lookup.
|
|
168
|
+
// Recursive fs implementation of: find {base} -path "*/src/main/resources/application*.yml" | head -1
|
|
169
|
+
// Returns a path relative to root (e.g. "server/src/main/resources/application.yml") or "".
|
|
170
170
|
//
|
|
171
|
-
// .yaml
|
|
172
|
-
// .yml
|
|
173
|
-
//
|
|
171
|
+
// .yaml is accepted too. Spring officially supports both .yml and .yaml, but the old regex only looked
|
|
172
|
+
// for .yml, so projects using application.yaml got an empty string here and __APPLICATION_YML_DIR__
|
|
173
|
+
// was installed unreplaced.
|
|
174
174
|
//
|
|
175
|
-
//
|
|
176
|
-
//
|
|
175
|
+
// Within one directory the profile-less base file (application.yml/.yaml) wins. With filename sorting alone,
|
|
176
|
+
// 'application-dev.yml' sorts before 'application.yml' (`-` < `.`) and a profile file would be picked.
|
|
177
177
|
export function findSpringAppYml(root, base = ".") {
|
|
178
178
|
return findSpringConfig(root, base, /^application(-[^/]*)?\.ya?ml$/, /^application\.ya?ml$/);
|
|
179
179
|
}
|
|
180
180
|
|
|
181
|
-
// src/main/resources
|
|
182
|
-
//
|
|
181
|
+
// Finds a config file matching pattern under src/main/resources. Once basePattern (the profile-less base file)
|
|
182
|
+
// turns up, that one is final.
|
|
183
183
|
function findSpringConfig(root, base, pattern, basePattern) {
|
|
184
184
|
const startRel = base === "." ? "" : base;
|
|
185
185
|
const PRUNE = new Set(["node_modules", ".git", "build", ".gradle", "target", ".idea"]);
|
|
186
186
|
let hit = "";
|
|
187
187
|
let hitIsBase = false;
|
|
188
188
|
const walk = (rel, depth) => {
|
|
189
|
-
if (hitIsBase || depth > 8) return; //
|
|
189
|
+
if (hitIsBase || depth > 8) return; // once the base file is found there is no need to look further
|
|
190
190
|
let entries;
|
|
191
191
|
try { entries = readdirSync(join(root, rel), { withFileTypes: true }); } catch { return; }
|
|
192
|
-
//
|
|
192
|
+
// Sort to make traversal order deterministic (removes platform differences in find order)
|
|
193
193
|
for (const e of entries.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
194
194
|
if (hitIsBase) return;
|
|
195
195
|
const childRel = rel ? `${rel}/${e.name}` : e.name;
|
|
@@ -198,7 +198,7 @@ function findSpringConfig(root, base, pattern, basePattern) {
|
|
|
198
198
|
walk(childRel, depth + 1);
|
|
199
199
|
} else if (pattern.test(e.name) && childRel.includes("src/main/resources/")) {
|
|
200
200
|
const isBase = basePattern.test(e.name);
|
|
201
|
-
//
|
|
201
|
+
// Take the first match for now, and promote to the base file if one turns up later.
|
|
202
202
|
if (!hit || isBase) { hit = childRel; hitIsBase = isBase; }
|
|
203
203
|
}
|
|
204
204
|
}
|
|
@@ -207,9 +207,9 @@ function findSpringConfig(root, base, pattern, basePattern) {
|
|
|
207
207
|
return hit;
|
|
208
208
|
}
|
|
209
209
|
|
|
210
|
-
//
|
|
211
|
-
// Spring Initializr
|
|
212
|
-
// __APPLICATION_YML_DIR__
|
|
210
|
+
// Resource folder where the deploy workflow will create application-prod.yml.
|
|
211
|
+
// Spring Initializr produces application.properties by default, so looking only for yml gives an empty value and
|
|
212
|
+
// __APPLICATION_YML_DIR__ is installed unreplaced. Decided in order: yml, then properties, then the standard path.
|
|
213
213
|
export function findSpringResourcesDir(root, base = ".") {
|
|
214
214
|
const f = findSpringAppYml(root, base)
|
|
215
215
|
|| findSpringConfig(root, base, /^application(-[^/]*)?\.properties$/, /^application\.properties$/);
|
|
@@ -217,27 +217,27 @@ export function findSpringResourcesDir(root, base = ".") {
|
|
|
217
217
|
return base === "." ? "src/main/resources" : `${base}/src/main/resources`;
|
|
218
218
|
}
|
|
219
219
|
|
|
220
|
-
// @wizard
|
|
221
|
-
// paths: Map<type, path> (
|
|
222
|
-
// flutterOptions: resolveFlutterOptions
|
|
223
|
-
//
|
|
220
|
+
// Builds the set of @wizard token resolvers, shared by index and interactive.
|
|
221
|
+
// paths: Map<type, path> (monorepo paths).
|
|
222
|
+
// flutterOptions: the resolveFlutterOptions result or a context with the same fields. When null the Flutter tokens
|
|
223
|
+
// are empty, so the template defaults (dart-define, store_only) remain.
|
|
224
224
|
export function makeResolvers(root, repoName, paths, flutterOptions = null) {
|
|
225
225
|
const springBase = (t) => paths.get(t || "spring") || paths.get("spring") || ".";
|
|
226
226
|
return {
|
|
227
227
|
repo: () => repoName,
|
|
228
|
-
//
|
|
229
|
-
//
|
|
230
|
-
// (
|
|
228
|
+
// Build JDK: default for the deploy workflow's JAVA_VERSION, measured from the project toolchain.
|
|
229
|
+
// WARNING: returning an empty string makes setEnvLine skip that line, leaving __JAVA_VERSION__ as is
|
|
230
|
+
// (the same failure shape). On detection failure always fall back to the previous default, 21.
|
|
231
231
|
jdk: (t) => detectJdk(root, springBase(t)) || "21",
|
|
232
232
|
"spring-app-yml-dir": (t) => findSpringResourcesDir(root, springBase(t)),
|
|
233
|
-
// yml
|
|
234
|
-
//
|
|
233
|
+
// A project without yml (properties only) gets application.yml in the resources folder:
|
|
234
|
+
// writing YAML content in place of a properties file would break the config.
|
|
235
235
|
"spring-app-yml-path": (t) => findSpringAppYml(root, springBase(t))
|
|
236
236
|
|| `${findSpringResourcesDir(root, springBase(t))}/application.yml`,
|
|
237
237
|
"flutter-root": () => paths.get("flutter") || ".",
|
|
238
|
-
// CI changes job
|
|
238
|
+
// Path filter for the CI changes job: the project root per type. Single repos and common use "." (always treated as changed).
|
|
239
239
|
"project-path": (t) => paths.get(t) || ".",
|
|
240
|
-
//
|
|
240
|
+
// An empty string makes setEnvLine/setFallbackLine skip the line, so the template default remains.
|
|
241
241
|
"flutter-env-mode": () => flutterOptions?.envMode || "",
|
|
242
242
|
"android-deploy-mode": () => flutterOptions?.androidDeployMode || "",
|
|
243
243
|
"ios-deploy-mode": () => flutterOptions?.iosDeployMode || "",
|