sitevision-cli 1.0.0-beta.31 → 1.0.0-beta.33
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/CHANGELOG.md +70 -0
- package/LICENSE +21 -0
- package/dist/cli.js +73 -34
- package/dist/commands/TaskScreen.js +32 -0
- package/dist/commands/build.js +6 -185
- package/dist/commands/deploy.js +85 -238
- package/dist/commands/dev.js +34 -37
- package/dist/commands/info.js +1 -1
- package/dist/commands/setup-signing.js +43 -28
- package/dist/commands/sign.js +9 -86
- package/dist/commands/use-finish.js +17 -0
- package/dist/commands/watch.js +0 -8
- package/dist/components/AuthLoginScreen.js +27 -12
- package/dist/scaffold/inquirer-stub.js +119 -0
- package/dist/scaffold/register.js +15 -0
- package/dist/shell/ConfigForm.js +174 -29
- package/dist/shell/Frame.js +4 -1
- package/dist/shell/Help.js +2 -1
- package/dist/shell/Settings.js +59 -6
- package/dist/shell/Shell.js +361 -43
- package/dist/shell/Tabs.js +8 -2
- package/dist/shell/actions.js +0 -0
- package/dist/utils/config.js +46 -3
- package/dist/utils/create-app.js +226 -0
- package/dist/utils/i18n.js +68 -13
- package/dist/utils/jsonc.js +2 -1
- package/dist/utils/keychain.js +44 -4
- package/dist/utils/oauth2-auth.js +35 -7
- package/dist/utils/password-prompt.js +23 -3
- package/dist/utils/process-runner.js +8 -3
- package/dist/utils/project-detection.js +172 -44
- package/dist/utils/session-cookie-auth.js +5 -4
- package/dist/utils/sitevision-api.js +21 -14
- package/dist/utils/sitevision-scripts-runner.js +32 -15
- package/dist/utils/tasks.js +132 -66
- package/dist/utils/version-check.js +3 -3
- package/dist/utils/webpack-runner.js +6 -5
- package/dist/utils/workspace.js +41 -3
- package/dist/utils/zip.js +18 -19
- package/package.json +24 -3
- package/readme.md +11 -4
- package/dist/app.d.ts +0 -6
- package/dist/app.js +0 -321
- package/dist/cli.d.ts +0 -2
- package/dist/commands/build.d.ts +0 -12
- package/dist/commands/deploy.d.ts +0 -17
- package/dist/commands/dev.d.ts +0 -14
- package/dist/commands/index.d.ts +0 -4
- package/dist/commands/info.d.ts +0 -2
- package/dist/commands/setup-signing.d.ts +0 -2
- package/dist/commands/sign.d.ts +0 -14
- package/dist/commands/types.d.ts +0 -18
- package/dist/commands/watch.d.ts +0 -2
- package/dist/components/AnimatedLogo.d.ts +0 -10
- package/dist/components/AuthLoginScreen.d.ts +0 -21
- package/dist/components/DevPropertiesForm.d.ts +0 -11
- package/dist/components/DevPropertiesForm.js +0 -246
- package/dist/components/InfoScreen.d.ts +0 -7
- package/dist/components/InfoScreen.js +0 -14
- package/dist/components/KeychainPasswordChoice.d.ts +0 -8
- package/dist/components/KeychainPasswordChoice.js +0 -30
- package/dist/components/MainMenu.d.ts +0 -7
- package/dist/components/MainMenu.js +0 -88
- package/dist/components/PasswordInput.d.ts +0 -10
- package/dist/components/ProcessOutput.d.ts +0 -6
- package/dist/components/ProcessOutput.js +0 -30
- package/dist/components/SetupFlow.d.ts +0 -8
- package/dist/components/SetupFlow.js +0 -233
- package/dist/components/SigningPropertiesForm.d.ts +0 -7
- package/dist/components/SigningPropertiesForm.js +0 -38
- package/dist/components/StatusIndicator.d.ts +0 -8
- package/dist/components/TextInput.d.ts +0 -10
- package/dist/components/TextInput.js +0 -29
- package/dist/components/WelcomeScreen.d.ts +0 -14
- package/dist/shell/AddonPicker.d.ts +0 -14
- package/dist/shell/Changelog.d.ts +0 -10
- package/dist/shell/CommandPalette.d.ts +0 -12
- package/dist/shell/ConfigForm.d.ts +0 -36
- package/dist/shell/Frame.d.ts +0 -59
- package/dist/shell/Help.d.ts +0 -9
- package/dist/shell/Popover.d.ts +0 -12
- package/dist/shell/Settings.d.ts +0 -6
- package/dist/shell/Shell.d.ts +0 -10
- package/dist/shell/Tabs.d.ts +0 -36
- package/dist/shell/actions.d.ts +0 -48
- package/dist/types/index.d.ts +0 -295
- package/dist/utils/branding.d.ts +0 -28
- package/dist/utils/config.d.ts +0 -27
- package/dist/utils/environments.d.ts +0 -20
- package/dist/utils/i18n.d.ts +0 -12
- package/dist/utils/jsonc.d.ts +0 -19
- package/dist/utils/keychain.d.ts +0 -15
- package/dist/utils/oauth2-auth.d.ts +0 -64
- package/dist/utils/password-prompt.d.ts +0 -14
- package/dist/utils/process-runner.d.ts +0 -31
- package/dist/utils/project-detection.d.ts +0 -202
- package/dist/utils/session-cookie-auth.d.ts +0 -35
- package/dist/utils/signing-password.d.ts +0 -12
- package/dist/utils/signing-step.d.ts +0 -15
- package/dist/utils/signing-step.js +0 -16
- package/dist/utils/sitevision-api.d.ts +0 -155
- package/dist/utils/sitevision-scripts-runner.d.ts +0 -84
- package/dist/utils/tasks.d.ts +0 -50
- package/dist/utils/version-check.d.ts +0 -9
- package/dist/utils/webpack-runner.d.ts +0 -76
- package/dist/utils/workspace.d.ts +0 -17
- package/dist/utils/zip.d.ts +0 -71
package/dist/utils/tasks.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { EventEmitter } from 'node:events';
|
|
4
|
+
import { stripVTControlCharacters } from 'node:util';
|
|
4
5
|
import { useSyncExternalStore } from 'react';
|
|
5
6
|
import { WebpackRunner, hasLocalWebpackConfig } from './webpack-runner.js';
|
|
6
7
|
import { hasSitevisionScripts, runSitevisionScriptsBuild, getDelegatedZipPath, checkSitevisionScriptsCompatibility, } from './sitevision-scripts-runner.js';
|
|
7
|
-
import { copyStaticToBuild, copySrcToBuild, cleanBuild, createBuildZip, zipExists, } from './zip.js';
|
|
8
|
+
import { copyStaticToBuild, copySrcToBuild, cleanBuild, createBuildZip, formatFileSize, getZipSize, zipExists, } from './zip.js';
|
|
9
|
+
import { deleteSessionCookie } from './keychain.js';
|
|
8
10
|
import { isBundledApp, getAppType, getFullAppId, getZipPath, getSignedZipPath, getDeployZipPath, localizedText, } from './project-detection.js';
|
|
9
11
|
import { signApp, deployApp, deployProduction, activateApp, createAddon, listAddons, classifyAddon, } from './sitevision-api.js';
|
|
10
12
|
import { ProcessRunner } from './process-runner.js';
|
|
@@ -28,11 +30,19 @@ export function useTasks() {
|
|
|
28
30
|
export function runningTasks(appRoot) {
|
|
29
31
|
return tasks.filter(t => t.status === 'running' && (!appRoot || t.appRoot === appRoot));
|
|
30
32
|
}
|
|
33
|
+
/** A scaffold belongs to no listed app yet, so it counts for every one. */
|
|
34
|
+
export function scaffoldRunning() {
|
|
35
|
+
return runningTasks().some(t => t.kind === 'create');
|
|
36
|
+
}
|
|
37
|
+
/** What "stop" means for the selected app: its own tasks and any scaffold. */
|
|
38
|
+
export function stoppableTasks(appRoot) {
|
|
39
|
+
return runningTasks().filter(t => t.kind === 'create' || t.appRoot === appRoot);
|
|
40
|
+
}
|
|
31
41
|
export function clearFinished() {
|
|
32
42
|
tasks = tasks.filter(t => t.status === 'running');
|
|
33
43
|
notify();
|
|
34
44
|
}
|
|
35
|
-
function createTask(kind, project, label, stop = () => { }) {
|
|
45
|
+
export function createTask(kind, project, label, stop = () => { }) {
|
|
36
46
|
const task = {
|
|
37
47
|
id: nextId++,
|
|
38
48
|
kind,
|
|
@@ -48,7 +58,7 @@ function createTask(kind, project, label, stop = () => { }) {
|
|
|
48
58
|
tasks.push(task);
|
|
49
59
|
notify();
|
|
50
60
|
const log = (tag, text, level = 'info') => {
|
|
51
|
-
for (const line of text
|
|
61
|
+
for (const line of logLines(text)) {
|
|
52
62
|
task.lines.push({ time: Date.now(), tag, level, text: line });
|
|
53
63
|
}
|
|
54
64
|
if (task.lines.length > MAX_LINES) {
|
|
@@ -69,7 +79,7 @@ function createTask(kind, project, label, stop = () => { }) {
|
|
|
69
79
|
};
|
|
70
80
|
return { task, log, finish };
|
|
71
81
|
}
|
|
72
|
-
function setPhase(task, phase) {
|
|
82
|
+
export function setPhase(task, phase) {
|
|
73
83
|
task.phase = phase;
|
|
74
84
|
notify();
|
|
75
85
|
}
|
|
@@ -78,7 +88,9 @@ const errorText = (error) => error instanceof Error ? error.message : String(err
|
|
|
78
88
|
// One-shot steps shared by the tasks below.
|
|
79
89
|
// ---------------------------------------------------------------------------
|
|
80
90
|
/** Build the app to dist/<appId>.zip. Returns the zip path or throws. */
|
|
81
|
-
async function buildOnce(project, task, log, mode, signal
|
|
91
|
+
async function buildOnce(project, task, log, mode, signal,
|
|
92
|
+
// False (`svc build --no-zip`): leave build/ and no zip.
|
|
93
|
+
zip = true) {
|
|
82
94
|
const { root, manifest } = project;
|
|
83
95
|
cleanBuild(root);
|
|
84
96
|
if (isBundledApp(manifest) && !hasLocalWebpackConfig(root)) {
|
|
@@ -97,7 +109,10 @@ async function buildOnce(project, task, log, mode, signal) {
|
|
|
97
109
|
if (!zipExists(zipPath)) {
|
|
98
110
|
throw new Error(`Build reported success but no zip was found at ${zipPath}.`);
|
|
99
111
|
}
|
|
100
|
-
|
|
112
|
+
// sitevision-scripts always zips; without a zip means taking it away.
|
|
113
|
+
if (!zip)
|
|
114
|
+
fs.rmSync(zipPath);
|
|
115
|
+
return zip ? zipPath : undefined;
|
|
101
116
|
}
|
|
102
117
|
if (isBundledApp(manifest)) {
|
|
103
118
|
if (!WebpackRunner.isWebpackAvailable(root)) {
|
|
@@ -123,6 +138,8 @@ async function buildOnce(project, task, log, mode, signal) {
|
|
|
123
138
|
copySrcToBuild(root);
|
|
124
139
|
copyStaticToBuild(root);
|
|
125
140
|
}
|
|
141
|
+
if (!zip)
|
|
142
|
+
return undefined;
|
|
126
143
|
setPhase(task, 'zipping');
|
|
127
144
|
return createBuildZip(root, getFullAppId(manifest.id));
|
|
128
145
|
}
|
|
@@ -170,15 +187,26 @@ async function deployOnce(project, task, log, zipPath, config, options) {
|
|
|
170
187
|
}
|
|
171
188
|
}
|
|
172
189
|
}
|
|
190
|
+
// A stale session must not be replayed by the next run. Not when it came
|
|
191
|
+
// from SITEVISION_SESSION_COOKIE: that one would just be read again.
|
|
192
|
+
if (!result.success &&
|
|
193
|
+
result.authExpired &&
|
|
194
|
+
config.sessionCookie &&
|
|
195
|
+
!process.env['SITEVISION_SESSION_COOKIE']) {
|
|
196
|
+
deleteSessionCookie(config.domain, config.username);
|
|
197
|
+
}
|
|
173
198
|
if (!result.success)
|
|
174
199
|
throw new Error(result.error ?? 'Deployment failed');
|
|
200
|
+
if (options.production && options.activate && !result.activated) {
|
|
201
|
+
throw new Error(result.message ?? 'Deployed, but not activated');
|
|
202
|
+
}
|
|
175
203
|
log('dep', `${result.message ?? 'deployed'}${result.executableId ? ` · exec ${result.executableId}` : ''}`, 'ok');
|
|
176
204
|
return result.executableId;
|
|
177
205
|
}
|
|
178
206
|
// ---------------------------------------------------------------------------
|
|
179
207
|
// Tasks
|
|
180
208
|
// ---------------------------------------------------------------------------
|
|
181
|
-
export function startBuild(project) {
|
|
209
|
+
export function startBuild(project, { zip = true } = {}) {
|
|
182
210
|
const controller = new AbortController();
|
|
183
211
|
const { task, log, finish } = createTask('build', project, 'build', () => {
|
|
184
212
|
controller.abort();
|
|
@@ -186,8 +214,10 @@ export function startBuild(project) {
|
|
|
186
214
|
});
|
|
187
215
|
void (async () => {
|
|
188
216
|
try {
|
|
189
|
-
const
|
|
190
|
-
log('bld',
|
|
217
|
+
const built = await buildOnce(project, task, log, 'production', controller.signal, zip);
|
|
218
|
+
log('bld', built
|
|
219
|
+
? `created ${built} · ${formatFileSize(getZipSize(built))}`
|
|
220
|
+
: 'built, zip skipped', 'ok');
|
|
191
221
|
finish('success');
|
|
192
222
|
}
|
|
193
223
|
catch (error) {
|
|
@@ -266,6 +296,7 @@ export function startInstall(project) {
|
|
|
266
296
|
.catch(error => finish('error', errorText(error)));
|
|
267
297
|
return task;
|
|
268
298
|
}
|
|
299
|
+
const WEBPACK_UNSEEN_TARGETS = ['static', 'manifest.json'];
|
|
269
300
|
const WATCH_TARGETS = [
|
|
270
301
|
'src',
|
|
271
302
|
'static',
|
|
@@ -283,8 +314,6 @@ export function startDev(project, options) {
|
|
|
283
314
|
const watchers = [];
|
|
284
315
|
let webpack = null;
|
|
285
316
|
let debounce;
|
|
286
|
-
let building = false;
|
|
287
|
-
let pending = false;
|
|
288
317
|
const controller = new AbortController();
|
|
289
318
|
const stop = () => {
|
|
290
319
|
controller.abort();
|
|
@@ -318,36 +347,24 @@ export function startDev(project, options) {
|
|
|
318
347
|
log('svc', errorText(error), 'error');
|
|
319
348
|
setPhase(task, 'error');
|
|
320
349
|
};
|
|
321
|
-
const rebuild = async () => {
|
|
322
|
-
if (
|
|
323
|
-
pending = true;
|
|
350
|
+
const rebuild = serialized(async () => {
|
|
351
|
+
if (controller.signal.aborted)
|
|
324
352
|
return;
|
|
325
|
-
}
|
|
326
|
-
building = true;
|
|
327
353
|
try {
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
// eslint-disable-next-line no-await-in-loop
|
|
332
|
-
const zip = await buildOnce(project, task, log, 'development', controller.signal);
|
|
333
|
-
// eslint-disable-next-line no-await-in-loop
|
|
334
|
-
await afterBuild(zip);
|
|
335
|
-
}
|
|
336
|
-
catch (error) {
|
|
337
|
-
fail(error);
|
|
338
|
-
}
|
|
339
|
-
} while (pending && !controller.signal.aborted);
|
|
354
|
+
const zip = await buildOnce(project, task, log, 'development', controller.signal);
|
|
355
|
+
if (zip)
|
|
356
|
+
await afterBuild(zip);
|
|
340
357
|
}
|
|
341
|
-
|
|
342
|
-
|
|
358
|
+
catch (error) {
|
|
359
|
+
fail(error);
|
|
343
360
|
}
|
|
344
|
-
};
|
|
361
|
+
});
|
|
345
362
|
// Size + mtime of every watched file. An event that leaves this unchanged
|
|
346
363
|
// (editor metadata, xattrs, identical re-save) is noise and must not
|
|
347
364
|
// rebuild, or a stray event during sign/deploy loops forever.
|
|
348
|
-
const snapshot = () => {
|
|
365
|
+
const snapshot = (targets) => {
|
|
349
366
|
const entries = [];
|
|
350
|
-
for (const name of
|
|
367
|
+
for (const name of targets) {
|
|
351
368
|
const target = path.join(root, name);
|
|
352
369
|
const stat = fs.statSync(target, { throwIfNoEntry: false });
|
|
353
370
|
if (!stat)
|
|
@@ -367,22 +384,22 @@ export function startDev(project, options) {
|
|
|
367
384
|
return entries.toSorted().join('\n');
|
|
368
385
|
};
|
|
369
386
|
let fingerprint = '';
|
|
370
|
-
const
|
|
371
|
-
const
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
for (const name of
|
|
387
|
+
const watchFiles = (targets, changed) => {
|
|
388
|
+
const onChange = (name, event, file) => {
|
|
389
|
+
const where = file ? path.join(name, file) : name;
|
|
390
|
+
clearTimeout(debounce);
|
|
391
|
+
debounce = setTimeout(() => {
|
|
392
|
+
const next = snapshot(targets);
|
|
393
|
+
if (next === fingerprint) {
|
|
394
|
+
log('fs', `${event} ${where} · no file changed, ignored`, 'warn');
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
fingerprint = next;
|
|
398
|
+
log('fs', `${event} ${where}`);
|
|
399
|
+
changed();
|
|
400
|
+
}, 300);
|
|
401
|
+
};
|
|
402
|
+
for (const name of targets) {
|
|
386
403
|
const target = path.join(root, name);
|
|
387
404
|
if (!fs.existsSync(target))
|
|
388
405
|
continue;
|
|
@@ -391,8 +408,8 @@ export function startDev(project, options) {
|
|
|
391
408
|
onChange(name, event, file);
|
|
392
409
|
}));
|
|
393
410
|
}
|
|
394
|
-
fingerprint = snapshot();
|
|
395
|
-
log('svc', `watching ${
|
|
411
|
+
fingerprint = snapshot(targets);
|
|
412
|
+
log('svc', `watching ${targets.join(', ')}`);
|
|
396
413
|
};
|
|
397
414
|
void (async () => {
|
|
398
415
|
try {
|
|
@@ -409,25 +426,37 @@ export function startDev(project, options) {
|
|
|
409
426
|
cssPrefix: manifest.id,
|
|
410
427
|
restApp: getAppType(manifest) !== 'web' && getAppType(manifest) !== 'widget',
|
|
411
428
|
});
|
|
429
|
+
// Zip, sign and deploy what is in build/ now. Webpack compiles and
|
|
430
|
+
// static edits both end here, one at a time, the latest one last.
|
|
431
|
+
let compiled = false;
|
|
432
|
+
const repackage = serialized(async () => {
|
|
433
|
+
if (!compiled || controller.signal.aborted)
|
|
434
|
+
return;
|
|
435
|
+
try {
|
|
436
|
+
copyStaticToBuild(root);
|
|
437
|
+
await afterBuild(await createBuildZip(root, getFullAppId(manifest.id)));
|
|
438
|
+
}
|
|
439
|
+
catch (error) {
|
|
440
|
+
fail(error);
|
|
441
|
+
}
|
|
442
|
+
});
|
|
412
443
|
await webpack.watch(result => {
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
catch (error) {
|
|
424
|
-
fail(error);
|
|
425
|
-
}
|
|
426
|
-
})();
|
|
444
|
+
reportBuild(result, log);
|
|
445
|
+
compiled = result.success;
|
|
446
|
+
if (result.success)
|
|
447
|
+
void repackage();
|
|
448
|
+
else
|
|
449
|
+
setPhase(task, 'error');
|
|
450
|
+
});
|
|
451
|
+
// Webpack watches what it bundles; what is copied in as-is it never sees.
|
|
452
|
+
watchFiles(WEBPACK_UNSEEN_TARGETS, () => {
|
|
453
|
+
void repackage();
|
|
427
454
|
});
|
|
428
455
|
}
|
|
429
456
|
else {
|
|
430
|
-
watchFiles()
|
|
457
|
+
watchFiles(WATCH_TARGETS, () => {
|
|
458
|
+
void rebuild();
|
|
459
|
+
});
|
|
431
460
|
await rebuild();
|
|
432
461
|
}
|
|
433
462
|
}
|
|
@@ -437,3 +466,40 @@ export function startDev(project, options) {
|
|
|
437
466
|
})();
|
|
438
467
|
return task;
|
|
439
468
|
}
|
|
469
|
+
/**
|
|
470
|
+
* Run `run` one call at a time. Calls made meanwhile collapse into one run with
|
|
471
|
+
* the latest value, so of several quick saves the last is what gets deployed
|
|
472
|
+
* last, never an older one that happened to upload slower.
|
|
473
|
+
*/
|
|
474
|
+
export function serialized(run) {
|
|
475
|
+
let running = false;
|
|
476
|
+
let waiting;
|
|
477
|
+
return async (value) => {
|
|
478
|
+
waiting = { value };
|
|
479
|
+
if (running)
|
|
480
|
+
return;
|
|
481
|
+
running = true;
|
|
482
|
+
try {
|
|
483
|
+
while (waiting) {
|
|
484
|
+
const { value: current } = waiting;
|
|
485
|
+
waiting = undefined;
|
|
486
|
+
// eslint-disable-next-line no-await-in-loop
|
|
487
|
+
await run(current);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
finally {
|
|
491
|
+
running = false;
|
|
492
|
+
}
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
/**
|
|
496
|
+
* Child output as log rows: split on LF or CRLF, no colour or cursor codes, and
|
|
497
|
+
* of a line redrawn with bare CRs (progress bars) only the final state. Any of
|
|
498
|
+
* those left in would move the cursor inside Ink's frame.
|
|
499
|
+
*/
|
|
500
|
+
export function logLines(text) {
|
|
501
|
+
return stripVTControlCharacters(text)
|
|
502
|
+
.trimEnd()
|
|
503
|
+
.split(/\r?\n/)
|
|
504
|
+
.map(line => line.slice(line.lastIndexOf('\r') + 1));
|
|
505
|
+
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import https from 'node:https';
|
|
2
2
|
/**
|
|
3
3
|
* True if version `a` is strictly greater than `b`, comparing the numeric
|
|
4
|
-
* MAJOR.MINOR.PATCH core.
|
|
4
|
+
* MAJOR.MINOR.PATCH core. With equal cores, a stable `a` beats a pre-release `b`.
|
|
5
5
|
*/
|
|
6
|
-
function isNewer(a, b) {
|
|
6
|
+
export function isNewer(a, b) {
|
|
7
7
|
const core = (v) => (v.split('-')[0] ?? '').split('.');
|
|
8
8
|
const pa = core(a);
|
|
9
9
|
const pb = core(b);
|
|
@@ -15,7 +15,7 @@ function isNewer(a, b) {
|
|
|
15
15
|
if (da < db)
|
|
16
16
|
return false;
|
|
17
17
|
}
|
|
18
|
-
return
|
|
18
|
+
return !a.includes('-') && b.includes('-');
|
|
19
19
|
}
|
|
20
20
|
/**
|
|
21
21
|
* Check the npm registry for a newer published version of `packageName`.
|
|
@@ -7,7 +7,9 @@
|
|
|
7
7
|
import path from 'path';
|
|
8
8
|
import fs from 'fs';
|
|
9
9
|
import { createRequire } from 'module';
|
|
10
|
+
import { pathToFileURL } from 'url';
|
|
10
11
|
import { copyChunksToResources } from './zip.js';
|
|
12
|
+
import { findInstalledPackage } from './sitevision-scripts-runner.js';
|
|
11
13
|
// =============================================================================
|
|
12
14
|
// LOCAL CONFIG DETECTION
|
|
13
15
|
// =============================================================================
|
|
@@ -56,8 +58,7 @@ export class WebpackRunner {
|
|
|
56
58
|
return;
|
|
57
59
|
}
|
|
58
60
|
// Load webpack from project's node_modules
|
|
59
|
-
|
|
60
|
-
if (!fs.existsSync(webpackPath)) {
|
|
61
|
+
if (!WebpackRunner.isWebpackAvailable(this.projectRoot)) {
|
|
61
62
|
throw new Error('webpack not found in project. Make sure webpack is installed: npm install webpack');
|
|
62
63
|
}
|
|
63
64
|
try {
|
|
@@ -83,7 +84,8 @@ export class WebpackRunner {
|
|
|
83
84
|
throw new Error('webpack.config.js not found. Make sure your project has a webpack configuration.');
|
|
84
85
|
}
|
|
85
86
|
try {
|
|
86
|
-
|
|
87
|
+
// A bare Windows path (C:\\...) is not a valid import specifier.
|
|
88
|
+
const configModule = await import(pathToFileURL(configPath).href);
|
|
87
89
|
const configFactory = configModule.default || configModule;
|
|
88
90
|
// If it's a function, call it with options
|
|
89
91
|
if (typeof configFactory === 'function') {
|
|
@@ -225,8 +227,7 @@ export class WebpackRunner {
|
|
|
225
227
|
* Check if webpack is available in the project
|
|
226
228
|
*/
|
|
227
229
|
static isWebpackAvailable(projectRoot) {
|
|
228
|
-
|
|
229
|
-
return fs.existsSync(webpackPath);
|
|
230
|
+
return findInstalledPackage(projectRoot, 'webpack') !== null;
|
|
230
231
|
}
|
|
231
232
|
/**
|
|
232
233
|
* Get the webpack version from the project
|
package/dist/utils/workspace.js
CHANGED
|
@@ -7,7 +7,9 @@ const MAX_DEPTH = 3;
|
|
|
7
7
|
* Find every Sitevision app below `root` (e.g. root/webapps/x, root/restapps/y).
|
|
8
8
|
* Depth-limited walk that skips dependency and output folders.
|
|
9
9
|
*/
|
|
10
|
-
export function discoverApps(root
|
|
10
|
+
export function discoverApps(root,
|
|
11
|
+
// Filled with why each app that was left out is broken.
|
|
12
|
+
skipped = []) {
|
|
11
13
|
const found = [];
|
|
12
14
|
const walk = (dir, depth) => {
|
|
13
15
|
let entries;
|
|
@@ -27,8 +29,10 @@ export function discoverApps(root) {
|
|
|
27
29
|
try {
|
|
28
30
|
project = detectProject(full);
|
|
29
31
|
}
|
|
30
|
-
catch {
|
|
31
|
-
//
|
|
32
|
+
catch (error) {
|
|
33
|
+
// A broken app is left out, said out loud, and not searched for more.
|
|
34
|
+
skipped.push(error instanceof Error ? error.message : String(error));
|
|
35
|
+
continue;
|
|
32
36
|
}
|
|
33
37
|
if (project) {
|
|
34
38
|
found.push(project);
|
|
@@ -55,6 +59,40 @@ export function configIncomplete(dev) {
|
|
|
55
59
|
return true;
|
|
56
60
|
return (dev.authMethod ?? 'basic') === 'basic' && !dev.username;
|
|
57
61
|
}
|
|
62
|
+
/**
|
|
63
|
+
* What a deploy is sent with, or which required value is missing. The one place
|
|
64
|
+
* dev properties become a DeployConfig, so nothing ever posts to "undefined".
|
|
65
|
+
*/
|
|
66
|
+
export function toDeployConfig(dev,
|
|
67
|
+
// False for what talks to the site rather than an addon: listing its addons
|
|
68
|
+
// (how an empty addon name gets picked) and logging in.
|
|
69
|
+
{ addon = true } = {}) {
|
|
70
|
+
const required = {
|
|
71
|
+
domain: dev?.domain,
|
|
72
|
+
siteName: dev?.siteName,
|
|
73
|
+
...(addon && { addonName: dev?.addonName }),
|
|
74
|
+
// A token or cookie login has no use for a username.
|
|
75
|
+
...((dev?.authMethod ?? 'basic') === 'basic' && { username: dev?.username }),
|
|
76
|
+
};
|
|
77
|
+
const missing = Object.entries(required).find(([, value]) => !value)?.[0];
|
|
78
|
+
if (!dev || missing) {
|
|
79
|
+
return {
|
|
80
|
+
error: `Deploy config is missing "${missing ?? 'domain'}". Run svc and press e to set it, or add it to .dev_properties.json.`,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
return {
|
|
84
|
+
config: {
|
|
85
|
+
domain: dev.domain,
|
|
86
|
+
siteName: dev.siteName,
|
|
87
|
+
addonName: dev.addonName ?? '',
|
|
88
|
+
username: dev.username ?? '',
|
|
89
|
+
password: dev.password,
|
|
90
|
+
accessToken: dev.accessToken,
|
|
91
|
+
sessionCookie: dev.sessionCookie,
|
|
92
|
+
useHTTP: dev.useHTTPForDevDeploy,
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
}
|
|
58
96
|
/**
|
|
59
97
|
* True when a workspace has apps but nothing usable to deploy with: neither the
|
|
60
98
|
* shared root config nor the apps themselves carry domain/site/username. The
|
package/dist/utils/zip.js
CHANGED
|
@@ -114,7 +114,7 @@ async function buildZipBuffer(entries) {
|
|
|
114
114
|
const localHeader = Buffer.alloc(30);
|
|
115
115
|
localHeader.writeUInt32LE(0x04034b50, 0);
|
|
116
116
|
localHeader.writeUInt16LE(20, 4); // version needed to extract
|
|
117
|
-
localHeader.writeUInt16LE(
|
|
117
|
+
localHeader.writeUInt16LE(0x08_00, 6); // general purpose flag: UTF-8 names
|
|
118
118
|
localHeader.writeUInt16LE(method, 8);
|
|
119
119
|
localHeader.writeUInt16LE(dosTime, 10);
|
|
120
120
|
localHeader.writeUInt16LE(dosDate, 12);
|
|
@@ -130,7 +130,7 @@ async function buildZipBuffer(entries) {
|
|
|
130
130
|
centralHeader.writeUInt32LE(0x02014b50, 0);
|
|
131
131
|
centralHeader.writeUInt16LE(20, 4); // version made by
|
|
132
132
|
centralHeader.writeUInt16LE(20, 6); // version needed
|
|
133
|
-
centralHeader.writeUInt16LE(
|
|
133
|
+
centralHeader.writeUInt16LE(0x08_00, 8); // general purpose flag: UTF-8 names
|
|
134
134
|
centralHeader.writeUInt16LE(method, 10);
|
|
135
135
|
centralHeader.writeUInt16LE(dosTime, 12);
|
|
136
136
|
centralHeader.writeUInt16LE(dosDate, 14);
|
|
@@ -228,6 +228,9 @@ export async function createBuildZip(projectRoot, appId) {
|
|
|
228
228
|
if (!fs.existsSync(buildDir)) {
|
|
229
229
|
throw new Error(`Build directory not found: ${buildDir}. Run build first.`);
|
|
230
230
|
}
|
|
231
|
+
if (!fs.existsSync(path.join(buildDir, 'manifest.json'))) {
|
|
232
|
+
throw new Error(`${buildDir} has no manifest.json, so the zip would not be an app. manifest.json belongs in static/ (or src/ for an app that is not bundled).`);
|
|
233
|
+
}
|
|
231
234
|
return createZip(buildDir, zipPath);
|
|
232
235
|
}
|
|
233
236
|
// =============================================================================
|
|
@@ -268,15 +271,17 @@ export function copyChunksToResources(buildDir) {
|
|
|
268
271
|
export function copyStaticToBuild(projectRoot) {
|
|
269
272
|
const staticDir = path.join(projectRoot, 'static');
|
|
270
273
|
const buildDir = path.join(projectRoot, 'build');
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
//
|
|
275
|
-
|
|
276
|
-
|
|
274
|
+
fs.mkdirSync(buildDir, { recursive: true });
|
|
275
|
+
if (fs.existsSync(staticDir))
|
|
276
|
+
copyDirRecursive(staticDir, buildDir);
|
|
277
|
+
// Every build ends here. A manifest kept at the project root is detected,
|
|
278
|
+
// so it ships when neither src/ nor static/ brought one.
|
|
279
|
+
const manifestIn = (dir) => path.join(projectRoot, dir, 'manifest.json');
|
|
280
|
+
if (fs.existsSync(manifestIn('.')) &&
|
|
281
|
+
!fs.existsSync(manifestIn('src')) &&
|
|
282
|
+
!fs.existsSync(manifestIn('static'))) {
|
|
283
|
+
fs.copyFileSync(manifestIn('.'), path.join(buildDir, 'manifest.json'));
|
|
277
284
|
}
|
|
278
|
-
// Copy static directory contents to build
|
|
279
|
-
copyDirRecursive(staticDir, buildDir);
|
|
280
285
|
}
|
|
281
286
|
/**
|
|
282
287
|
* Copy source files to build directory (for non-bundled apps)
|
|
@@ -286,15 +291,9 @@ export function copyStaticToBuild(projectRoot) {
|
|
|
286
291
|
export function copySrcToBuild(projectRoot) {
|
|
287
292
|
const srcDir = path.join(projectRoot, 'src');
|
|
288
293
|
const buildDir = path.join(projectRoot, 'build');
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
// Ensure build directory exists
|
|
293
|
-
if (!fs.existsSync(buildDir)) {
|
|
294
|
-
fs.mkdirSync(buildDir, { recursive: true });
|
|
295
|
-
}
|
|
296
|
-
// Copy src directory contents to build
|
|
297
|
-
copyDirRecursive(srcDir, buildDir);
|
|
294
|
+
fs.mkdirSync(buildDir, { recursive: true });
|
|
295
|
+
if (fs.existsSync(srcDir))
|
|
296
|
+
copyDirRecursive(srcDir, buildDir);
|
|
298
297
|
}
|
|
299
298
|
/**
|
|
300
299
|
* Recursively copy a directory
|
package/package.json
CHANGED
|
@@ -1,7 +1,22 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sitevision-cli",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.33",
|
|
4
|
+
"description": "Build, sign and deploy Sitevision apps from the terminal",
|
|
4
5
|
"license": "MIT",
|
|
6
|
+
"author": "Rasmus Söderström",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/xrasod/SitevisionCli.git"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://github.com/xrasod/SitevisionCli#readme",
|
|
12
|
+
"bugs": "https://github.com/xrasod/SitevisionCli/issues",
|
|
13
|
+
"keywords": [
|
|
14
|
+
"sitevision",
|
|
15
|
+
"cli",
|
|
16
|
+
"webapp",
|
|
17
|
+
"restapp",
|
|
18
|
+
"deploy"
|
|
19
|
+
],
|
|
5
20
|
"bin": {
|
|
6
21
|
"svc": "dist/cli.js"
|
|
7
22
|
},
|
|
@@ -10,9 +25,10 @@
|
|
|
10
25
|
"node": ">=22"
|
|
11
26
|
},
|
|
12
27
|
"scripts": {
|
|
13
|
-
"build": "tsc",
|
|
28
|
+
"build": "node -e \"fs.rmSync('dist',{recursive:true,force:true})\" && tsc && node -e \"fs.chmodSync('dist/cli.js',0o755)\"",
|
|
14
29
|
"dev": "tsc --watch",
|
|
15
30
|
"test": "prettier --check . && xo && ava",
|
|
31
|
+
"prepublishOnly": "npm test && npm run build",
|
|
16
32
|
"release": "./scripts/publish.sh",
|
|
17
33
|
"release:beta": "./scripts/publish-beta.sh"
|
|
18
34
|
},
|
|
@@ -52,7 +68,12 @@
|
|
|
52
68
|
],
|
|
53
69
|
"nodeArguments": [
|
|
54
70
|
"--import=tsx"
|
|
55
|
-
]
|
|
71
|
+
],
|
|
72
|
+
"environmentVariables": {
|
|
73
|
+
"FORCE_COLOR": "0",
|
|
74
|
+
"SVC_NO_KEYCHAIN": "memory",
|
|
75
|
+
"XDG_CONFIG_HOME": "test/.xdg"
|
|
76
|
+
}
|
|
56
77
|
},
|
|
57
78
|
"prettier": "@vdemedes/prettier-config"
|
|
58
79
|
}
|
package/readme.md
CHANGED
|
@@ -60,7 +60,7 @@ and `p` to deploy and activate.
|
|
|
60
60
|
| `1`–`4` | Overview · Config · Versions · Log |
|
|
61
61
|
| `/` | Command palette |
|
|
62
62
|
| `?` | Help: every key in one place |
|
|
63
|
-
| `,` | Settings (language,
|
|
63
|
+
| `,` | Settings (language, update check, hints) |
|
|
64
64
|
| `Tab` / `Esc` | Switch pane / back |
|
|
65
65
|
| `q` | Quit |
|
|
66
66
|
|
|
@@ -72,17 +72,24 @@ that apply. `svc --minimal` gives a compact layout for small panes.
|
|
|
72
72
|
|
|
73
73
|
```bash
|
|
74
74
|
svc # interactive shell
|
|
75
|
-
svc build
|
|
75
|
+
svc build [--no-zip] # build to dist/<id>.zip
|
|
76
76
|
svc sign # sign to dist/<id>-signed.zip
|
|
77
77
|
svc deploy [--force] # deploy the zip
|
|
78
78
|
svc deploy --production [--activate] # deploy the signed zip
|
|
79
79
|
svc dev [--signed] # build + deploy on change
|
|
80
80
|
svc watch [--signed] # build on change, no deploy
|
|
81
81
|
svc info # project information
|
|
82
|
+
svc setup-signing [--global] # save the signing username and certificate
|
|
82
83
|
```
|
|
83
84
|
|
|
84
|
-
|
|
85
|
-
|
|
85
|
+
Short flags: `-f` force, `-p` production, `-a` activate, `-s` signed. An unknown
|
|
86
|
+
flag is an error.
|
|
87
|
+
|
|
88
|
+
Direct commands use the base environment, run without a terminal, and exit
|
|
89
|
+
non-zero when a step fails, so `svc build && svc sign && svc deploy -p -a` is
|
|
90
|
+
safe in CI. `SITEVISION_DEPLOY_PASSWORD` and `SITEVISION_SIGNING_PASSWORD`
|
|
91
|
+
supply the passwords; `SITEVISION_ACCESS_TOKEN` and `SITEVISION_SESSION_COOKIE`
|
|
92
|
+
pass an OAuth2 token or session cookie for one run.
|
|
86
93
|
|
|
87
94
|
## Configuration
|
|
88
95
|
|