voltra 2.0.0 → 2.1.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/build/cjs/config/normalize.js +93 -0
- package/build/cjs/config/normalize.js.map +1 -1
- package/build/cjs/index.js +5 -2
- package/build/cjs/index.js.map +1 -1
- package/build/cjs/platforms/android/apply.js +11 -2
- package/build/cjs/platforms/android/apply.js.map +1 -1
- package/build/cjs/platforms/android/generated.js +152 -130
- package/build/cjs/platforms/android/generated.js.map +1 -1
- package/build/cjs/platforms/android/gradle.js +120 -0
- package/build/cjs/platforms/android/gradle.js.map +1 -0
- package/build/cjs/platforms/ios/generated.js +246 -142
- package/build/cjs/platforms/ios/generated.js.map +1 -1
- package/build/cjs/platforms/ios/xcodeTarget.js +68 -0
- package/build/cjs/platforms/ios/xcodeTarget.js.map +1 -1
- package/build/cjs/platforms/shared/widgetModule.js +151 -0
- package/build/cjs/platforms/shared/widgetModule.js.map +1 -0
- package/build/esm/config/normalize.js +93 -0
- package/build/esm/config/normalize.js.map +1 -1
- package/build/esm/index.js +1 -0
- package/build/esm/index.js.map +1 -1
- package/build/esm/platforms/android/apply.js +11 -2
- package/build/esm/platforms/android/apply.js.map +1 -1
- package/build/esm/platforms/android/generated.js +152 -97
- package/build/esm/platforms/android/generated.js.map +1 -1
- package/build/esm/platforms/android/gradle.js +112 -0
- package/build/esm/platforms/android/gradle.js.map +1 -0
- package/build/esm/platforms/ios/generated.js +246 -109
- package/build/esm/platforms/ios/generated.js.map +1 -1
- package/build/esm/platforms/ios/xcodeTarget.js +69 -1
- package/build/esm/platforms/ios/xcodeTarget.js.map +1 -1
- package/build/esm/platforms/shared/widgetModule.js +111 -0
- package/build/esm/platforms/shared/widgetModule.js.map +1 -0
- package/build/types/config/normalize.d.ts.map +1 -1
- package/build/types/config/types.d.ts +32 -0
- package/build/types/config/types.d.ts.map +1 -1
- package/build/types/index.d.ts +3 -1
- package/build/types/index.d.ts.map +1 -1
- package/build/types/platforms/android/apply.d.ts.map +1 -1
- package/build/types/platforms/android/generated.d.ts.map +1 -1
- package/build/types/platforms/android/gradle.d.ts +18 -0
- package/build/types/platforms/android/gradle.d.ts.map +1 -0
- package/build/types/platforms/ios/generated.d.ts.map +1 -1
- package/build/types/platforms/ios/xcodeTarget.d.ts.map +1 -1
- package/build/types/platforms/shared/widgetModule.d.ts +9 -0
- package/build/types/platforms/shared/widgetModule.d.ts.map +1 -0
- package/package.json +2 -1
- package/src/apply/index.ts +309 -0
- package/src/apply/preflight.ts +172 -0
- package/src/bin.ts +14 -0
- package/src/commands/apply.ts +114 -0
- package/src/config/defaults.ts +34 -0
- package/src/config/load.ts +100 -0
- package/src/config/normalize.ts +562 -0
- package/src/config/types.ts +317 -0
- package/src/cosmiconfig.d.ts +21 -0
- package/src/dependencies/platformPackages.ts +75 -0
- package/src/discovery/android.ts +233 -0
- package/src/discovery/ios.ts +579 -0
- package/src/fs/path.ts +25 -0
- package/src/fs/readWrite.ts +102 -0
- package/src/git/status.ts +205 -0
- package/src/index.ts +204 -0
- package/src/platforms/android/apply.ts +106 -0
- package/src/platforms/android/generated.ts +1284 -0
- package/src/platforms/android/gradle.ts +155 -0
- package/src/platforms/android/manifest.ts +379 -0
- package/src/platforms/ios/apply.ts +139 -0
- package/src/platforms/ios/deploymentTarget.ts +47 -0
- package/src/platforms/ios/entitlements.ts +146 -0
- package/src/platforms/ios/generated.ts +1233 -0
- package/src/platforms/ios/mainAppEntitlements.ts +11 -0
- package/src/platforms/ios/plist.ts +321 -0
- package/src/platforms/ios/podfile.ts +255 -0
- package/src/platforms/ios/targetName.ts +11 -0
- package/src/platforms/ios/xcode.ts +181 -0
- package/src/platforms/ios/xcodeTarget.ts +899 -0
- package/src/platforms/shared/widgetModule.ts +153 -0
- package/src/reporting/clack.ts +67 -0
- package/src/reporting/summary.ts +116 -0
- package/src/state/diff.ts +21 -0
- package/src/state/files.ts +52 -0
- package/src/state/load.ts +66 -0
- package/src/state/save.ts +31 -0
- package/src/xml2js.d.ts +23 -0
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import { execFile } from 'node:child_process'
|
|
2
|
+
import type { Readable, Writable } from 'node:stream'
|
|
3
|
+
import { promisify } from 'node:util'
|
|
4
|
+
|
|
5
|
+
import { renderCancelled, renderWarning } from '../reporting/clack'
|
|
6
|
+
import { formatDirtyWorktreeWarning, VoltraCliError } from '../reporting/summary'
|
|
7
|
+
|
|
8
|
+
import type * as ClackPrompts from '@clack/prompts'
|
|
9
|
+
|
|
10
|
+
const execFileAsync = promisify(execFile)
|
|
11
|
+
|
|
12
|
+
const GIT_NOT_REPOSITORY_EXIT_CODE = 128
|
|
13
|
+
export interface GitWorktreeStatus {
|
|
14
|
+
isGitRepository: boolean
|
|
15
|
+
isDirty: boolean
|
|
16
|
+
repoRoot?: string
|
|
17
|
+
entries: string[]
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface EnsureGitWorktreeOptions {
|
|
21
|
+
cwd: string
|
|
22
|
+
interactive?: boolean
|
|
23
|
+
allowDirty?: boolean
|
|
24
|
+
stdin?: Readable & { isTTY?: boolean }
|
|
25
|
+
stdout?: Writable & { isTTY?: boolean }
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface EnsureGitWorktreeResult {
|
|
29
|
+
status: GitWorktreeStatus
|
|
30
|
+
warning?: string
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
interface ExecGitOptions {
|
|
34
|
+
cwd: string
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
class GitCommandError extends VoltraCliError {
|
|
38
|
+
readonly args: string[]
|
|
39
|
+
readonly exitCode?: number
|
|
40
|
+
readonly stderr?: string
|
|
41
|
+
|
|
42
|
+
constructor(args: string[], cause: unknown) {
|
|
43
|
+
const message = cause instanceof Error ? cause.message : String(cause)
|
|
44
|
+
super(`Failed to run git ${args.join(' ')}: ${message}`)
|
|
45
|
+
this.name = 'GitCommandError'
|
|
46
|
+
this.args = args
|
|
47
|
+
this.exitCode = getExecExitCode(cause)
|
|
48
|
+
this.stderr = getExecStderr(cause)
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export async function getGitWorktreeStatus(cwd: string): Promise<GitWorktreeStatus> {
|
|
53
|
+
const insideWorktree = await runGitCommand(['rev-parse', '--is-inside-work-tree'], { cwd }).catch(
|
|
54
|
+
(error: unknown) => {
|
|
55
|
+
if (isNotGitRepositoryError(error)) {
|
|
56
|
+
return undefined
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
throw error
|
|
60
|
+
}
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
if (!insideWorktree || insideWorktree.trim() !== 'true') {
|
|
64
|
+
return {
|
|
65
|
+
isGitRepository: false,
|
|
66
|
+
isDirty: false,
|
|
67
|
+
entries: [],
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const [repoRootOutput, statusOutput] = await Promise.all([
|
|
72
|
+
runGitCommand(['rev-parse', '--show-toplevel'], { cwd }),
|
|
73
|
+
runGitCommand(['status', '--short', '--untracked-files=normal'], { cwd }),
|
|
74
|
+
])
|
|
75
|
+
|
|
76
|
+
const entries = splitGitStatusEntries(statusOutput)
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
isGitRepository: true,
|
|
80
|
+
isDirty: entries.length > 0,
|
|
81
|
+
repoRoot: repoRootOutput.trim(),
|
|
82
|
+
entries,
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export async function ensureGitWorktreeIsReady(options: EnsureGitWorktreeOptions): Promise<EnsureGitWorktreeResult> {
|
|
87
|
+
const status = await getGitWorktreeStatus(options.cwd)
|
|
88
|
+
|
|
89
|
+
if (!status.isGitRepository || !status.isDirty) {
|
|
90
|
+
return { status }
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const warning = formatDirtyWorktreeWarning(status.entries.length)
|
|
94
|
+
|
|
95
|
+
if (options.allowDirty) {
|
|
96
|
+
return { status, warning }
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (!isInteractiveSession(options)) {
|
|
100
|
+
throw new VoltraCliError(`${warning} Re-run interactively to confirm before applying changes.`)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const confirmed = await promptForDirtyWorktreeConfirmation(options)
|
|
104
|
+
|
|
105
|
+
if (!confirmed) {
|
|
106
|
+
throw new VoltraCliError('Aborted because the git worktree has uncommitted changes.')
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return { status, warning }
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async function runGitCommand(args: string[], options: ExecGitOptions): Promise<string> {
|
|
113
|
+
try {
|
|
114
|
+
const result = await execFileAsync('git', args, {
|
|
115
|
+
cwd: options.cwd,
|
|
116
|
+
encoding: 'utf8',
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
return result.stdout
|
|
120
|
+
} catch (error: unknown) {
|
|
121
|
+
throw new GitCommandError(args, error)
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function isNotGitRepositoryError(error: unknown): boolean {
|
|
126
|
+
if (!(error instanceof GitCommandError)) {
|
|
127
|
+
return false
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return (
|
|
131
|
+
error.args.join(' ') === 'rev-parse --is-inside-work-tree' &&
|
|
132
|
+
error.exitCode === GIT_NOT_REPOSITORY_EXIT_CODE &&
|
|
133
|
+
typeof error.stderr === 'string' &&
|
|
134
|
+
error.stderr.toLowerCase().includes('not a git repository')
|
|
135
|
+
)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function getExecExitCode(error: unknown): number | undefined {
|
|
139
|
+
if (!(error instanceof Error) || !('code' in error)) {
|
|
140
|
+
return undefined
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const code = error.code
|
|
144
|
+
|
|
145
|
+
return typeof code === 'number' ? code : undefined
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function getExecStderr(error: unknown): string | undefined {
|
|
149
|
+
if (!(error instanceof Error) || !('stderr' in error)) {
|
|
150
|
+
return undefined
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const stderr = error.stderr
|
|
154
|
+
|
|
155
|
+
return typeof stderr === 'string' ? stderr : undefined
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function splitGitStatusEntries(output: string): string[] {
|
|
159
|
+
return output
|
|
160
|
+
.split(/\r?\n/)
|
|
161
|
+
.map((entry) => entry.trimEnd())
|
|
162
|
+
.filter((entry) => entry.length > 0)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function isInteractiveSession(options: EnsureGitWorktreeOptions): boolean {
|
|
166
|
+
if (options.interactive !== undefined) {
|
|
167
|
+
return options.interactive
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const stdin = options.stdin ?? process.stdin
|
|
171
|
+
const stdout = options.stdout ?? process.stdout
|
|
172
|
+
|
|
173
|
+
return Boolean(stdin.isTTY && stdout.isTTY)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
async function promptForDirtyWorktreeConfirmation(options: EnsureGitWorktreeOptions): Promise<boolean> {
|
|
177
|
+
const stdin = options.stdin ?? process.stdin
|
|
178
|
+
const stdout = options.stdout ?? process.stdout
|
|
179
|
+
const promptMessage = 'You have uncommitted changes. Are you sure you want to continue?'
|
|
180
|
+
const { confirm, isCancel } = await loadClackPrompts()
|
|
181
|
+
|
|
182
|
+
await renderWarning(promptMessage, { input: stdin, output: stdout })
|
|
183
|
+
|
|
184
|
+
const response = await confirm({
|
|
185
|
+
message: 'Continue?',
|
|
186
|
+
initialValue: false,
|
|
187
|
+
input: stdin,
|
|
188
|
+
output: stdout,
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
if (isCancel(response)) {
|
|
192
|
+
await renderCancelled({ input: stdin, output: stdout })
|
|
193
|
+
return false
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return response === true
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function loadClackPrompts() {
|
|
200
|
+
const dynamicImport = new Function('specifier', 'return import(specifier)') as (
|
|
201
|
+
specifier: string
|
|
202
|
+
) => Promise<typeof ClackPrompts>
|
|
203
|
+
|
|
204
|
+
return dynamicImport('@clack/prompts')
|
|
205
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import { Command } from 'commander'
|
|
2
|
+
|
|
3
|
+
import { CLI_EXIT_CODE_FAILURE, CLI_EXIT_CODE_SUCCESS, createApplyCommand, formatCommandError } from './commands/apply'
|
|
4
|
+
import { renderError } from './reporting/clack'
|
|
5
|
+
|
|
6
|
+
function createProgram(): Command {
|
|
7
|
+
const program = new Command()
|
|
8
|
+
let exitCode = CLI_EXIT_CODE_SUCCESS
|
|
9
|
+
|
|
10
|
+
program
|
|
11
|
+
.name('voltra')
|
|
12
|
+
.description('Apply Voltra to native React Native projects')
|
|
13
|
+
.exitOverride()
|
|
14
|
+
.showHelpAfterError()
|
|
15
|
+
.showSuggestionAfterError()
|
|
16
|
+
.addHelpText(
|
|
17
|
+
'after',
|
|
18
|
+
[
|
|
19
|
+
'',
|
|
20
|
+
'Examples:',
|
|
21
|
+
' voltra apply',
|
|
22
|
+
' voltra apply --platform ios',
|
|
23
|
+
' voltra apply --config ./voltra.config.ts --yes',
|
|
24
|
+
].join('\n')
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
program.addCommand(
|
|
28
|
+
createApplyCommand((nextExitCode) => {
|
|
29
|
+
exitCode = nextExitCode
|
|
30
|
+
})
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
program.hook('postAction', () => {
|
|
34
|
+
program.setOptionValue('exitCode', exitCode)
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
return program
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export async function runCli(argv: string[]): Promise<number> {
|
|
41
|
+
const program = createProgram()
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
await program.parseAsync(['node', 'voltra', ...argv], { from: 'node' })
|
|
45
|
+
return program.getOptionValue('exitCode') ?? CLI_EXIT_CODE_SUCCESS
|
|
46
|
+
} catch (error) {
|
|
47
|
+
if (isCommanderError(error)) {
|
|
48
|
+
return isCommanderDisplayError(error) ? CLI_EXIT_CODE_SUCCESS : CLI_EXIT_CODE_FAILURE
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
await renderError(formatCommandError(error), { output: process.stderr })
|
|
52
|
+
return CLI_EXIT_CODE_FAILURE
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function isCommanderDisplayError(error: unknown): error is { code: string } {
|
|
57
|
+
return Boolean(
|
|
58
|
+
error &&
|
|
59
|
+
typeof error === 'object' &&
|
|
60
|
+
'code' in error &&
|
|
61
|
+
(error.code === 'commander.helpDisplayed' || error.code === 'commander.version')
|
|
62
|
+
)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function isCommanderError(error: unknown): error is { code: string } {
|
|
66
|
+
return Boolean(
|
|
67
|
+
error &&
|
|
68
|
+
typeof error === 'object' &&
|
|
69
|
+
'code' in error &&
|
|
70
|
+
typeof error.code === 'string' &&
|
|
71
|
+
error.code.startsWith('commander.')
|
|
72
|
+
)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export { applyVoltra, runApplyPipeline } from './apply'
|
|
76
|
+
export type {
|
|
77
|
+
ApplyDependencies,
|
|
78
|
+
ApplyOptions,
|
|
79
|
+
ApplyResult,
|
|
80
|
+
PlatformApplyContext,
|
|
81
|
+
PlatformApplyResult,
|
|
82
|
+
PlatformApplyRunner,
|
|
83
|
+
} from './apply'
|
|
84
|
+
export { getRequestedPlatforms, runApplyPreflight } from './apply/preflight'
|
|
85
|
+
export type {
|
|
86
|
+
ApplyPreflightContext,
|
|
87
|
+
ApplyPreflightResult,
|
|
88
|
+
ApplyPreflightRunners,
|
|
89
|
+
PlatformPreflightFailure,
|
|
90
|
+
PlatformPreflightResult,
|
|
91
|
+
PlatformPreflightRunner,
|
|
92
|
+
PlatformPreflightSuccess,
|
|
93
|
+
} from './apply/preflight'
|
|
94
|
+
export { CLI_EXIT_CODE_FAILURE, CLI_EXIT_CODE_SUCCESS, getApplyHelpText, runApplyCommand } from './commands/apply'
|
|
95
|
+
|
|
96
|
+
export { CLI_DEFAULTS } from './config/defaults'
|
|
97
|
+
export { VoltraConfigLoadError, loadVoltraConfig } from './config/load'
|
|
98
|
+
export { VoltraConfigNormalizationError, normalizeVoltraConfig } from './config/normalize'
|
|
99
|
+
export { AndroidProjectDiscoveryError, discoverAndroidProject } from './discovery/android'
|
|
100
|
+
export type { AndroidProjectDiscovery } from './discovery/android'
|
|
101
|
+
export { IOSProjectDiscoveryError, discoverIOSProject } from './discovery/ios'
|
|
102
|
+
export type { IOSProjectDiscovery } from './discovery/ios'
|
|
103
|
+
export { ensureGitWorktreeIsReady, getGitWorktreeStatus } from './git/status'
|
|
104
|
+
export type { EnsureGitWorktreeOptions, EnsureGitWorktreeResult, GitWorktreeStatus } from './git/status'
|
|
105
|
+
export { AndroidGeneratedFilesError, generateAndroidFiles } from './platforms/android/generated'
|
|
106
|
+
export type { GenerateAndroidFilesOptions, GenerateAndroidFilesResult } from './platforms/android/generated'
|
|
107
|
+
export { applyAndroidPlatform, createAndroidPreflightRunner } from './platforms/android/apply'
|
|
108
|
+
export { addDynamicWidgetBundlingSnippet, ensureAndroidGradleWidgetBundling } from './platforms/android/gradle'
|
|
109
|
+
export type {
|
|
110
|
+
EnsureAndroidGradleWidgetBundlingOptions,
|
|
111
|
+
EnsureAndroidGradleWidgetBundlingResult,
|
|
112
|
+
} from './platforms/android/gradle'
|
|
113
|
+
export { AndroidManifestMutationError, ensureAndroidManifest } from './platforms/android/manifest'
|
|
114
|
+
export type { EnsureAndroidManifestOptions, EnsureAndroidManifestResult } from './platforms/android/manifest'
|
|
115
|
+
export { IOSGeneratedFilesError, generateIOSFiles } from './platforms/ios/generated'
|
|
116
|
+
export type { GenerateIOSFilesOptions, GenerateIOSFilesResult } from './platforms/ios/generated'
|
|
117
|
+
export { resolveIOSWidgetTargetName } from './platforms/ios/targetName'
|
|
118
|
+
export { IOSWidgetTargetMutationError, ensureIOSWidgetTarget } from './platforms/ios/xcodeTarget'
|
|
119
|
+
export { applyIOSPlatform, createIOSPreflightRunner } from './platforms/ios/apply'
|
|
120
|
+
export { IOSEntitlementsMutationError, ensureEntitlements } from './platforms/ios/entitlements'
|
|
121
|
+
export type { EnsureEntitlementsOptions, EnsureEntitlementsResult } from './platforms/ios/entitlements'
|
|
122
|
+
export { IOSInfoPlistMutationError, ensureInfoPlist } from './platforms/ios/plist'
|
|
123
|
+
export type { EnsureInfoPlistOptions, EnsureInfoPlistResult } from './platforms/ios/plist'
|
|
124
|
+
export { IOSPodfileMutationError, ensurePodfileBlock } from './platforms/ios/podfile'
|
|
125
|
+
export type { EnsurePodfileBlockOptions, EnsurePodfileBlockResult } from './platforms/ios/podfile'
|
|
126
|
+
export type { EnsureIOSWidgetTargetOptions, EnsureIOSWidgetTargetResult } from './platforms/ios/xcodeTarget'
|
|
127
|
+
export {
|
|
128
|
+
ensureFrameworksGroup,
|
|
129
|
+
ensureMainGroupChild,
|
|
130
|
+
ensureProductsGroup,
|
|
131
|
+
getApplicationTargets,
|
|
132
|
+
getTargetBuildConfigurations,
|
|
133
|
+
getTargetBuildPhases,
|
|
134
|
+
IOSXcodeProjectError,
|
|
135
|
+
openIOSXcodeProject,
|
|
136
|
+
saveIOSXcodeProject,
|
|
137
|
+
} from './platforms/ios/xcode'
|
|
138
|
+
export type {
|
|
139
|
+
IOSXcodeProjectContext,
|
|
140
|
+
IOSXcodeTargetBuildConfigurations,
|
|
141
|
+
IOSXcodeTargetBuildPhases,
|
|
142
|
+
IOSXcodeTargetContext,
|
|
143
|
+
} from './platforms/ios/xcode'
|
|
144
|
+
export { diffVoltraState } from './state/diff'
|
|
145
|
+
export { getVoltraStatePath, loadVoltraState } from './state/load'
|
|
146
|
+
export { saveVoltraState } from './state/save'
|
|
147
|
+
export {
|
|
148
|
+
normalizeClackMessage,
|
|
149
|
+
renderApplySummary,
|
|
150
|
+
renderCancelled,
|
|
151
|
+
renderError,
|
|
152
|
+
renderWarning,
|
|
153
|
+
} from './reporting/clack'
|
|
154
|
+
export type { VoltraStateDiff } from './state/diff'
|
|
155
|
+
export type { SaveVoltraStateInput } from './state/save'
|
|
156
|
+
export type { VoltraState } from './state/load'
|
|
157
|
+
export {
|
|
158
|
+
formatAmbiguousDiscoveryWarning,
|
|
159
|
+
formatApplySummary,
|
|
160
|
+
formatDirtyWorktreeWarning,
|
|
161
|
+
formatError,
|
|
162
|
+
formatPreflightFailure,
|
|
163
|
+
formatWarning,
|
|
164
|
+
PreflightError,
|
|
165
|
+
VoltraCliError,
|
|
166
|
+
} from './reporting/summary'
|
|
167
|
+
export type {
|
|
168
|
+
AndroidProjectOverrides,
|
|
169
|
+
AndroidWidgetAppIntentConfig,
|
|
170
|
+
AndroidWidgetAppIntentParameter,
|
|
171
|
+
AndroidWidgetConfig,
|
|
172
|
+
AndroidWidgetServerUpdateConfig,
|
|
173
|
+
CliDefaults,
|
|
174
|
+
IOSProjectOverrides,
|
|
175
|
+
IOSWidgetAppIntentConfig,
|
|
176
|
+
IOSWidgetAppIntentParameter,
|
|
177
|
+
IOSWidgetConfig,
|
|
178
|
+
IOSWidgetFamily,
|
|
179
|
+
IOSWidgetServerUpdateConfig,
|
|
180
|
+
LoadedVoltraConfig,
|
|
181
|
+
NormalizedAndroidProjectConfig,
|
|
182
|
+
NormalizedAndroidWidgetConfig,
|
|
183
|
+
NormalizedAndroidWidgetServerUpdateConfig,
|
|
184
|
+
NormalizedIOSProjectConfig,
|
|
185
|
+
NormalizedIOSWidgetConfig,
|
|
186
|
+
NormalizedIOSWidgetServerUpdateConfig,
|
|
187
|
+
NormalizedVoltraAndroidConfig,
|
|
188
|
+
NormalizedVoltraConfig,
|
|
189
|
+
NormalizedVoltraIOSConfig,
|
|
190
|
+
VoltraAndroidConfig,
|
|
191
|
+
VoltraConfig,
|
|
192
|
+
VoltraIOSConfig,
|
|
193
|
+
VoltraPlatform,
|
|
194
|
+
WidgetInitialStatePath,
|
|
195
|
+
WidgetLabel,
|
|
196
|
+
WidgetLocalizedValue,
|
|
197
|
+
} from './config/types'
|
|
198
|
+
export type {
|
|
199
|
+
ApplySummary,
|
|
200
|
+
PreflightFailureReport,
|
|
201
|
+
PreflightIssue,
|
|
202
|
+
ReportedChange,
|
|
203
|
+
ReportedChangeKind,
|
|
204
|
+
} from './reporting/summary'
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { discoverAndroidProject } from '../../discovery/android'
|
|
2
|
+
import { getMissingPlatformPackageMessage, getMissingPlatformPackages } from '../../dependencies/platformPackages'
|
|
3
|
+
import { VoltraCliError } from '../../reporting/summary'
|
|
4
|
+
|
|
5
|
+
import { ensureAndroidManifest } from './manifest'
|
|
6
|
+
import { ensureAndroidGradleWidgetBundling } from './gradle'
|
|
7
|
+
import { generateAndroidFiles } from './generated'
|
|
8
|
+
|
|
9
|
+
import type { NormalizedVoltraConfig } from '../../config/types'
|
|
10
|
+
import type { AndroidProjectDiscovery } from '../../discovery/android'
|
|
11
|
+
import type { PlatformApplyContext, PlatformApplyResult } from '../../apply'
|
|
12
|
+
import type { ApplyPreflightContext, PlatformPreflightResult, PlatformPreflightRunner } from '../../apply/preflight'
|
|
13
|
+
|
|
14
|
+
export function createAndroidPreflightRunner(
|
|
15
|
+
config: NormalizedVoltraConfig
|
|
16
|
+
): PlatformPreflightRunner<AndroidProjectDiscovery> {
|
|
17
|
+
return async (_context: ApplyPreflightContext): Promise<PlatformPreflightResult<AndroidProjectDiscovery>> => {
|
|
18
|
+
const androidConfig = config.android
|
|
19
|
+
|
|
20
|
+
if (!androidConfig) {
|
|
21
|
+
return {
|
|
22
|
+
platform: 'android',
|
|
23
|
+
issues: [{ message: 'Android config is missing.' }],
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const missingPackages = getMissingPlatformPackages(config.projectRoot, 'android')
|
|
28
|
+
|
|
29
|
+
if (missingPackages.length > 0) {
|
|
30
|
+
return {
|
|
31
|
+
platform: 'android',
|
|
32
|
+
issues: [{ message: getMissingPlatformPackageMessage('android', missingPackages) }],
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
platform: 'android',
|
|
38
|
+
context: await discoverAndroidProject(config.projectRoot, androidConfig.project),
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export async function applyAndroidPlatform(context: PlatformApplyContext): Promise<PlatformApplyResult> {
|
|
44
|
+
if (context.platform !== 'android') {
|
|
45
|
+
throw new VoltraCliError(`Android apply runner received unexpected platform: ${context.platform}.`)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const androidConfig = context.config.android
|
|
49
|
+
|
|
50
|
+
if (!androidConfig) {
|
|
51
|
+
throw new VoltraCliError('Android config is missing.')
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const discovery = getAndroidDiscovery(context.preflight)
|
|
55
|
+
const generatedResult = await generateAndroidFiles({
|
|
56
|
+
projectRoot: context.config.projectRoot,
|
|
57
|
+
android: androidConfig,
|
|
58
|
+
discovery,
|
|
59
|
+
})
|
|
60
|
+
const manifestResult = await ensureAndroidManifest({
|
|
61
|
+
projectRoot: context.config.projectRoot,
|
|
62
|
+
android: androidConfig,
|
|
63
|
+
discovery,
|
|
64
|
+
})
|
|
65
|
+
const gradleResult = await ensureAndroidGradleWidgetBundling({
|
|
66
|
+
projectRoot: context.config.projectRoot,
|
|
67
|
+
discovery,
|
|
68
|
+
hasDynamicWidgets: androidConfig.widgets.some((widget) => widget.entry !== undefined),
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
platform: 'android',
|
|
73
|
+
changes: [manifestResult.change, gradleResult.change, ...generatedResult.changes].filter(isDefined),
|
|
74
|
+
generatedFiles: generatedResult.files,
|
|
75
|
+
warnings: [...generatedResult.warnings, ...(gradleResult.warnings ?? [])],
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function getAndroidDiscovery(value: unknown): AndroidProjectDiscovery {
|
|
80
|
+
if (!isAndroidProjectDiscovery(value)) {
|
|
81
|
+
throw new VoltraCliError('Android preflight did not provide a valid discovery result.')
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return value
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function isAndroidProjectDiscovery(value: unknown): value is AndroidProjectDiscovery {
|
|
88
|
+
if (!value || typeof value !== 'object') {
|
|
89
|
+
return false
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const candidate = value as Partial<AndroidProjectDiscovery>
|
|
93
|
+
|
|
94
|
+
return [
|
|
95
|
+
candidate.androidRoot,
|
|
96
|
+
candidate.appModuleName,
|
|
97
|
+
candidate.appModuleRoot,
|
|
98
|
+
candidate.manifestPath,
|
|
99
|
+
candidate.buildGradlePath,
|
|
100
|
+
candidate.packageName,
|
|
101
|
+
].every((entry) => typeof entry === 'string' && entry.length > 0)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function isDefined<TValue>(value: TValue | undefined): value is TValue {
|
|
105
|
+
return value !== undefined
|
|
106
|
+
}
|