@pacaf/wizard-ux 2.0.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.md +84 -0
- package/bin/pacaf-wizard-ux.mjs +20 -0
- package/dist/assets/index-BVelUveV.js +127 -0
- package/dist/index.html +36 -0
- package/index.html +36 -0
- package/package.json +67 -0
- package/scripts/fix-pty-perms.mjs +34 -0
- package/server/index.mjs +144 -0
- package/server/lib/dataverse-bridge.mjs +51 -0
- package/server/lib/process-runner.mjs +117 -0
- package/server/lib/state-bridge.mjs +40 -0
- package/server/routes/onepassword.mjs +16 -0
- package/server/routes/pty.mjs +124 -0
- package/server/routes/state.mjs +88 -0
- package/server/routes/steps.mjs +62 -0
- package/server/routes/stream.mjs +49 -0
- package/server/routes/system.mjs +43 -0
- package/server/steps/01-prerequisites.mjs +127 -0
- package/server/steps/02-project-and-env.mjs +108 -0
- package/server/steps/03-app-registration.mjs +482 -0
- package/server/steps/04-auth-setup.mjs +435 -0
- package/server/steps/05-publisher.mjs +581 -0
- package/server/steps/06-solution.mjs +41 -0
- package/server/steps/07-scaffold.mjs +356 -0
- package/server/steps/08-connectors.mjs +438 -0
- package/server/steps/09-verify-deploy.mjs +212 -0
- package/server/steps/index.mjs +24 -0
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
// Step 7 - Scaffold. Browser-native long-running scaffold with live logs.
|
|
2
|
+
import { existsSync, mkdirSync, readdirSync, writeFileSync, readFileSync } from 'node:fs';
|
|
3
|
+
import { spawn, execFileSync } from 'node:child_process';
|
|
4
|
+
import { dirname, join, resolve } from 'node:path';
|
|
5
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
6
|
+
|
|
7
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
const ROOT_DIR = resolve(__dirname, '..', '..', '..');
|
|
9
|
+
const SCAFFOLD = await import(pathToFileURL(resolve(ROOT_DIR, 'wizard', 'lib', 'scaffold-foundations.mjs')).href);
|
|
10
|
+
const SHELL = await import(pathToFileURL(resolve(ROOT_DIR, 'wizard', 'lib', 'shell.mjs')).href);
|
|
11
|
+
const PAC_TARGET = await import(pathToFileURL(resolve(ROOT_DIR, 'wizard', 'lib', 'pac-target.mjs')).href);
|
|
12
|
+
|
|
13
|
+
function makeFoundationLogger(log) {
|
|
14
|
+
return {
|
|
15
|
+
line: (message = '') => log.info(message),
|
|
16
|
+
ok: (message) => log.ok(message),
|
|
17
|
+
warn: (message) => log.warn(message),
|
|
18
|
+
info: (message) => log.info(message),
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function runCommand(log, command, opts = {}) {
|
|
23
|
+
return new Promise((resolvePromise) => {
|
|
24
|
+
log.info(`$ ${command}`);
|
|
25
|
+
const child = process.platform === 'win32'
|
|
26
|
+
? spawn(process.env.COMSPEC || 'cmd.exe', ['/d', '/s', '/c', command], { cwd: opts.cwd || ROOT_DIR, stdio: ['ignore', 'pipe', 'pipe'] })
|
|
27
|
+
: spawn('sh', ['-c', command], { cwd: opts.cwd || ROOT_DIR, stdio: ['ignore', 'pipe', 'pipe'] });
|
|
28
|
+
child.stdout.setEncoding('utf-8');
|
|
29
|
+
child.stderr.setEncoding('utf-8');
|
|
30
|
+
child.stdout.on('data', (chunk) => log.info(String(chunk).trimEnd()));
|
|
31
|
+
child.stderr.on('data', (chunk) => log.warn(String(chunk).trimEnd()));
|
|
32
|
+
child.on('error', (error) => {
|
|
33
|
+
log.fail(`Failed to start command: ${error.message}`);
|
|
34
|
+
resolvePromise(false);
|
|
35
|
+
});
|
|
36
|
+
child.on('close', (code) => resolvePromise(code === 0));
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function runFile(log, file, args, opts = {}) {
|
|
41
|
+
return new Promise((resolvePromise) => {
|
|
42
|
+
log.info(`$ ${SHELL.formatCommandForLog(file, args)}`);
|
|
43
|
+
const child = SHELL.spawnSafe(file, args, { cwd: opts.cwd || ROOT_DIR, stdio: ['ignore', 'pipe', 'pipe'] });
|
|
44
|
+
child.stdout.setEncoding('utf-8');
|
|
45
|
+
child.stderr.setEncoding('utf-8');
|
|
46
|
+
child.stdout.on('data', (chunk) => log.info(String(chunk).trimEnd()));
|
|
47
|
+
child.stderr.on('data', (chunk) => log.warn(String(chunk).trimEnd()));
|
|
48
|
+
child.on('error', (error) => {
|
|
49
|
+
log.fail(`Failed to start ${file}: ${error.message}`);
|
|
50
|
+
resolvePromise(false);
|
|
51
|
+
});
|
|
52
|
+
child.on('close', (code) => resolvePromise(code === 0));
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function toolCommand(name) {
|
|
57
|
+
return process.platform === 'win32' ? `${name}.cmd` : name;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function hasCommand(name) {
|
|
61
|
+
try {
|
|
62
|
+
execFileSync(process.platform === 'win32' ? 'where' : 'which', [name], { stdio: 'ignore' });
|
|
63
|
+
return true;
|
|
64
|
+
} catch {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function resolveCredentialValues(state) {
|
|
70
|
+
return PAC_TARGET.resolveCredentialValues({
|
|
71
|
+
rootDir: ROOT_DIR,
|
|
72
|
+
opBin: process.env.OP_BIN || (hasCommand('op') ? 'op' : null),
|
|
73
|
+
source: state.AUTH_MODE || 'auto',
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function verifyPacTarget({ pac, projectDir, state, credentialValues, profileType, requirePowerConfig, requirePowerConfigTarget }) {
|
|
78
|
+
return PAC_TARGET.selectAndVerifyPacProfile({
|
|
79
|
+
pac,
|
|
80
|
+
rootDir: ROOT_DIR,
|
|
81
|
+
wizardState: {
|
|
82
|
+
WIZARD_TARGET_ENV: state.WIZARD_TARGET_ENV || 'dev',
|
|
83
|
+
PP_ENV_DEV: state.PP_ENV_DEV || '',
|
|
84
|
+
PP_ENV_TEST: state.PP_ENV_TEST || '',
|
|
85
|
+
PP_ENV_PROD: state.PP_ENV_PROD || '',
|
|
86
|
+
},
|
|
87
|
+
targetKey: state.WIZARD_TARGET_ENV || 'dev',
|
|
88
|
+
profileType,
|
|
89
|
+
credentialValues,
|
|
90
|
+
powerConfigPath: join(projectDir, 'power.config.json'),
|
|
91
|
+
requireCredentialMatch: credentialValues !== null,
|
|
92
|
+
requirePowerConfig,
|
|
93
|
+
requirePowerConfigTarget,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function writeProjectReadme(projectDir, state) {
|
|
98
|
+
const appName = state.APP_NAME || 'Power Apps Code App';
|
|
99
|
+
const prefix = state.PUBLISHER_PREFIX || 'yourprefix';
|
|
100
|
+
const solutionName = state.SOLUTION_DISPLAY_NAME || state.SOLUTION_UNIQUE_NAME || appName;
|
|
101
|
+
const envRows = [
|
|
102
|
+
state.PP_ENV_DEV ? `| Dev | ${state.PP_ENV_DEV} |` : '',
|
|
103
|
+
state.PP_ENV_TEST ? `| Test | ${state.PP_ENV_TEST} |` : '',
|
|
104
|
+
state.PP_ENV_PROD ? `| Prod | ${state.PP_ENV_PROD} |` : '',
|
|
105
|
+
].filter(Boolean).join('\n');
|
|
106
|
+
|
|
107
|
+
writeFileSync(join(projectDir, 'README.md'), `# ${appName}
|
|
108
|
+
|
|
109
|
+
A Power Apps Code App built with React, Fluent UI v9, TanStack Query, and TypeScript.
|
|
110
|
+
|
|
111
|
+
## Development
|
|
112
|
+
|
|
113
|
+
\`\`\`bash
|
|
114
|
+
npm install
|
|
115
|
+
npm run dev:local
|
|
116
|
+
npm run prototype:seed
|
|
117
|
+
npm run dev
|
|
118
|
+
\`\`\`
|
|
119
|
+
|
|
120
|
+
## Build and Deploy
|
|
121
|
+
|
|
122
|
+
\`\`\`bash
|
|
123
|
+
npm run build
|
|
124
|
+
pac code push
|
|
125
|
+
\`\`\`
|
|
126
|
+
|
|
127
|
+
## Power Platform
|
|
128
|
+
|
|
129
|
+
| Property | Value |
|
|
130
|
+
|----------|-------|
|
|
131
|
+
| Solution | ${solutionName} |
|
|
132
|
+
| Publisher Prefix | \`${prefix}\` |
|
|
133
|
+
|
|
134
|
+
| Environment | URL |
|
|
135
|
+
|-------------|-----|
|
|
136
|
+
${envRows}
|
|
137
|
+
|
|
138
|
+
Connector binding is intentionally deferred until the prototype is stable. Use WizardUX step 8 or \`pac code add-data-source\` when you are ready for real data.
|
|
139
|
+
`, 'utf-8');
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export default {
|
|
143
|
+
meta: {
|
|
144
|
+
number: 7,
|
|
145
|
+
title: 'Scaffold the Code App',
|
|
146
|
+
description: 'Generate the project, install dependencies, register with Power Platform, and run smoke tests.',
|
|
147
|
+
canRunInBrowser: true,
|
|
148
|
+
},
|
|
149
|
+
|
|
150
|
+
questions(state) {
|
|
151
|
+
const rootDefault = state.PROJECT_DIR || ROOT_DIR;
|
|
152
|
+
const existingOrigin = SHELL.run('git remote get-url origin', { cwd: rootDefault }) || '';
|
|
153
|
+
const needsRemote = !existingOrigin || /PAppsCAFoundations/i.test(existingOrigin);
|
|
154
|
+
return [
|
|
155
|
+
{
|
|
156
|
+
id: 'PROJECT_DIR',
|
|
157
|
+
type: 'text',
|
|
158
|
+
label: 'Project path',
|
|
159
|
+
help: 'Use the repo root for in-place scaffold, or enter an absolute path for a separate app folder.',
|
|
160
|
+
defaultValue: rootDefault,
|
|
161
|
+
required: true,
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
id: 'CONTINUE_NONEMPTY',
|
|
165
|
+
type: 'confirm',
|
|
166
|
+
label: 'Continue if the project directory is not empty',
|
|
167
|
+
help: 'Existing files may be overwritten. Leave this on when scaffolding into this template-derived repo.',
|
|
168
|
+
defaultValue: true,
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
id: 'GIT_REMOTE',
|
|
172
|
+
type: 'text',
|
|
173
|
+
label: 'Git remote URL for this app repository',
|
|
174
|
+
help: needsRemote
|
|
175
|
+
? 'Optional. Paste the GitHub/Azure DevOps remote for the app you are creating, such as https://github.com/org/repo.git. Leave blank to keep everything local for now.'
|
|
176
|
+
: 'Optional. The existing origin will be kept unless you enter a replacement remote URL.',
|
|
177
|
+
defaultValue: needsRemote ? '' : existingOrigin,
|
|
178
|
+
why: [
|
|
179
|
+
'This is only for source control. It does not affect Power Platform, Dataverse, or PAC auth.',
|
|
180
|
+
'Use it when you already created an empty repository for the new app and want the wizard to set it as git origin.',
|
|
181
|
+
'Leave it blank if you have not created a repo yet or do not want the wizard to touch git remotes.',
|
|
182
|
+
].join('\n'),
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
id: 'PUSH_INITIAL_COMMIT',
|
|
186
|
+
type: 'confirm',
|
|
187
|
+
label: 'Push the generated scaffold to that Git remote now',
|
|
188
|
+
help: 'Optional. Turn this on only if the remote URL above points to an empty repo you can push to. Otherwise leave it off and push manually later.',
|
|
189
|
+
defaultValue: false,
|
|
190
|
+
hideIf: { id: 'GIT_REMOTE', equals: '' },
|
|
191
|
+
},
|
|
192
|
+
];
|
|
193
|
+
},
|
|
194
|
+
|
|
195
|
+
async apply(answers, state, log) {
|
|
196
|
+
const appName = state.APP_NAME || 'Power Apps Code App';
|
|
197
|
+
const projectDir = resolve(String(answers.PROJECT_DIR || ROOT_DIR).trim());
|
|
198
|
+
const foundationLogger = makeFoundationLogger(log);
|
|
199
|
+
|
|
200
|
+
if (existsSync(projectDir) && readdirSync(projectDir).length > 0 && answers.CONTINUE_NONEMPTY !== true) {
|
|
201
|
+
throw new Error(`${projectDir} is not empty. Confirm that you want to continue, or choose a different path.`);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
mkdirSync(projectDir, { recursive: true });
|
|
205
|
+
log.ok(`Project path: ${projectDir}`);
|
|
206
|
+
|
|
207
|
+
const dirNotEmpty = existsSync(projectDir) && readdirSync(projectDir).length > 0;
|
|
208
|
+
log.info('Downloading starter template...');
|
|
209
|
+
const templateArgs = ['--yes', 'degit', 'microsoft/PowerAppsCodeApps/templates/starter', projectDir];
|
|
210
|
+
if (dirNotEmpty) templateArgs.push('--force');
|
|
211
|
+
const templateOk = await runFile(log, toolCommand('npx'), templateArgs, { cwd: ROOT_DIR });
|
|
212
|
+
if (!templateOk) {
|
|
213
|
+
log.warn('Template download failed. Creating minimal project structure instead.');
|
|
214
|
+
SCAFFOLD.createMinimalProject(projectDir, appName);
|
|
215
|
+
} else {
|
|
216
|
+
log.ok('Starter template downloaded');
|
|
217
|
+
}
|
|
218
|
+
SCAFFOLD.normalizePackageJsonDependencies(projectDir, foundationLogger);
|
|
219
|
+
|
|
220
|
+
const viteEnvPath = join(projectDir, 'src', 'vite-env.d.ts');
|
|
221
|
+
if (!existsSync(viteEnvPath)) {
|
|
222
|
+
mkdirSync(join(projectDir, 'src'), { recursive: true });
|
|
223
|
+
writeFileSync(viteEnvPath, [
|
|
224
|
+
'/// <reference types="vite/client" />',
|
|
225
|
+
'',
|
|
226
|
+
'declare module "*.svg" {',
|
|
227
|
+
' const src: string;',
|
|
228
|
+
' export default src;',
|
|
229
|
+
'}',
|
|
230
|
+
'',
|
|
231
|
+
].join('\n'));
|
|
232
|
+
log.ok('vite-env.d.ts created');
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
log.info('Installing dependencies...');
|
|
236
|
+
if (await runFile(log, toolCommand('npm'), ['install'], { cwd: projectDir })) log.ok('Base dependencies installed');
|
|
237
|
+
else log.warn('Base dependency install reported errors; continuing to merge required packages.');
|
|
238
|
+
|
|
239
|
+
const prodPkgs = SCAFFOLD.packageSpecs(SCAFFOLD.REQUIRED_RUNTIME_PACKAGES);
|
|
240
|
+
if (await runFile(log, toolCommand('npm'), ['install', ...prodPkgs], { cwd: projectDir })) log.ok('Runtime packages installed');
|
|
241
|
+
else log.warn('Some runtime packages failed to install.');
|
|
242
|
+
|
|
243
|
+
const devPkgs = SCAFFOLD.packageSpecs(SCAFFOLD.REQUIRED_DEV_PACKAGES);
|
|
244
|
+
if (await runFile(log, toolCommand('npm'), ['install', '-D', ...devPkgs], { cwd: projectDir })) log.ok('Dev packages installed');
|
|
245
|
+
else log.warn('Some dev packages failed to install.');
|
|
246
|
+
|
|
247
|
+
SCAFFOLD.writeConfig(projectDir, foundationLogger);
|
|
248
|
+
SCAFFOLD.mergePackageJsonScripts(projectDir, foundationLogger);
|
|
249
|
+
|
|
250
|
+
for (const folder of [
|
|
251
|
+
'src/components', 'src/pages', 'src/hooks', 'src/generated', 'src/utils', 'src/types', 'src/constants', 'src/mockData',
|
|
252
|
+
'dataverse', 'tests/e2e', 'tests/setup', 'tests/fixtures', '.github/instructions', '.github/workflows', 'solution',
|
|
253
|
+
]) {
|
|
254
|
+
mkdirSync(join(projectDir, folder), { recursive: true });
|
|
255
|
+
}
|
|
256
|
+
log.ok('Folder structure created');
|
|
257
|
+
|
|
258
|
+
SCAFFOLD.writeStarterFiles(projectDir, appName, foundationLogger);
|
|
259
|
+
SCAFFOLD.copyFoundationFiles(ROOT_DIR, projectDir, foundationLogger);
|
|
260
|
+
|
|
261
|
+
const pac = SHELL.pacPath();
|
|
262
|
+
if (pac) {
|
|
263
|
+
log.info('Registering Code App in Power Platform...');
|
|
264
|
+
const isUserAuth = (state.AUTH_PROFILE_TYPE || 'user') === 'user';
|
|
265
|
+
const credentialValues = isUserAuth ? null : resolveCredentialValues(state);
|
|
266
|
+
try {
|
|
267
|
+
verifyPacTarget({ pac, projectDir, state, credentialValues, profileType: 'user', requirePowerConfig: false, requirePowerConfigTarget: false });
|
|
268
|
+
} catch (error) {
|
|
269
|
+
throw new Error(`${error.message}\n\npac code init requires the repo-scoped interactive PAC profile. Return to Step 4, enable user profile creation, complete browser/device sign-in, then retry Step 7.`);
|
|
270
|
+
}
|
|
271
|
+
const powerConfigPath = join(projectDir, 'power.config.json');
|
|
272
|
+
let skipInit = false;
|
|
273
|
+
if (existsSync(powerConfigPath)) {
|
|
274
|
+
const existing = PAC_TARGET.loadPowerConfigInfo(powerConfigPath);
|
|
275
|
+
const whoOut = SHELL.runSafe(pac, ['org', 'who']);
|
|
276
|
+
const whoInfo = whoOut ? PAC_TARGET.parsePacOrgWho(whoOut) : null;
|
|
277
|
+
if (existing.environmentId && whoInfo?.environmentId && existing.environmentId === whoInfo.environmentId.toLowerCase()) {
|
|
278
|
+
skipInit = true;
|
|
279
|
+
log.ok('power.config.json already matches active environment; skipping pac code init');
|
|
280
|
+
} else {
|
|
281
|
+
const quarantinePath = PAC_TARGET.quarantinePowerConfig(powerConfigPath);
|
|
282
|
+
log.warn(`Quarantined stale power.config.json at ${quarantinePath}`);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
if (!skipInit) {
|
|
286
|
+
const initOk = await runFile(log, pac, [
|
|
287
|
+
'code', 'init',
|
|
288
|
+
'--displayName', appName,
|
|
289
|
+
'--buildPath', './dist',
|
|
290
|
+
'--fileEntryPoint', 'index.html',
|
|
291
|
+
], { cwd: projectDir });
|
|
292
|
+
if (!initOk) throw new Error('pac code init failed. Check the live output above, then retry this step.');
|
|
293
|
+
if (!existsSync(powerConfigPath)) throw new Error('pac code init completed without creating power.config.json. Check the PAC output above, then retry Step 7 after resolving that PAC error.');
|
|
294
|
+
const repair = PAC_TARGET.repairPowerConfigDisplayNames(powerConfigPath);
|
|
295
|
+
if (repair.changed) log.warn(`Repaired quoted display name fields in power.config.json: ${repair.fields.join(', ')}`);
|
|
296
|
+
}
|
|
297
|
+
verifyPacTarget({ pac, projectDir, state, credentialValues, profileType: 'user', requirePowerConfig: true, requirePowerConfigTarget: true });
|
|
298
|
+
log.ok('power.config.json created and verified');
|
|
299
|
+
} else {
|
|
300
|
+
log.warn('PAC CLI not found; skipping pac code init.');
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
log.info('Connector binding is deferred to step 8 after prototype validation.');
|
|
304
|
+
|
|
305
|
+
log.info('Running smoke tests...');
|
|
306
|
+
if (await runCommand(log, 'npm run test:smoke', { cwd: projectDir })) log.ok('Smoke tests passed');
|
|
307
|
+
else log.warn('Smoke tests did not pass. Continue development, then rerun npm run test:smoke.');
|
|
308
|
+
|
|
309
|
+
if (existsSync(join(projectDir, '.git'))) {
|
|
310
|
+
log.ok('Git repo already initialized');
|
|
311
|
+
} else if (await runFile(log, 'git', ['init', '-b', 'main'], { cwd: projectDir })) {
|
|
312
|
+
log.ok('Git repo initialized');
|
|
313
|
+
} else {
|
|
314
|
+
log.warn('git init failed');
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
const existingOrigin = SHELL.runSafe('git', ['remote', 'get-url', 'origin'], { cwd: projectDir }) || '';
|
|
318
|
+
const remoteUrl = String(answers.GIT_REMOTE || '').trim();
|
|
319
|
+
let finalRemoteUrl = existingOrigin;
|
|
320
|
+
if (existingOrigin && /PAppsCAFoundations/i.test(existingOrigin)) {
|
|
321
|
+
SHELL.runSafe('git', ['remote', 'remove', 'origin'], { cwd: projectDir });
|
|
322
|
+
finalRemoteUrl = '';
|
|
323
|
+
}
|
|
324
|
+
if (remoteUrl && remoteUrl !== finalRemoteUrl) {
|
|
325
|
+
SHELL.runSafe('git', ['remote', 'remove', 'origin'], { cwd: projectDir });
|
|
326
|
+
SHELL.runSafe('git', ['remote', 'add', 'origin', remoteUrl], { cwd: projectDir });
|
|
327
|
+
finalRemoteUrl = remoteUrl;
|
|
328
|
+
log.ok(`Remote origin set to ${remoteUrl}`);
|
|
329
|
+
} else if (finalRemoteUrl) {
|
|
330
|
+
log.ok(`Remote origin: ${finalRemoteUrl}`);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
writeProjectReadme(projectDir, state);
|
|
334
|
+
log.ok('Project README generated');
|
|
335
|
+
|
|
336
|
+
await runFile(log, 'git', ['add', '-A'], { cwd: projectDir });
|
|
337
|
+
if (await runFile(log, 'git', ['commit', '-m', 'Initial scaffold from PAppsCAFoundations wizard', '--quiet'], { cwd: projectDir })) {
|
|
338
|
+
log.ok('Initial commit created');
|
|
339
|
+
} else {
|
|
340
|
+
log.warn('Initial commit skipped or failed. Git user.name/user.email may not be configured, or there may be no changes.');
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
if (finalRemoteUrl && answers.PUSH_INITIAL_COMMIT === true) {
|
|
344
|
+
if (await runFile(log, 'git', ['push', '-u', 'origin', 'main'], { cwd: projectDir })) log.ok('Pushed to origin/main');
|
|
345
|
+
else log.warn('Push failed. You can push later with git push -u origin main.');
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
return {
|
|
349
|
+
stateUpdate: {
|
|
350
|
+
PROJECT_DIR: projectDir,
|
|
351
|
+
GIT_REMOTE: finalRemoteUrl || state.GIT_REMOTE || '',
|
|
352
|
+
},
|
|
353
|
+
completedStep: 7,
|
|
354
|
+
};
|
|
355
|
+
},
|
|
356
|
+
};
|