sitevision-cli 1.0.2 → 1.1.0-beta.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/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.1.0-beta.0
4
+
5
+ - Production deploys ask whether to activate the new version or only upload
6
+ it. Press `p` or `P` on a production environment and pick; Esc cancels.
7
+ - Production deploys no longer overwrite an existing version on their own.
8
+ `P` in the shell and `--force` on the command line do that, as on dev.
9
+ - `svc deploy --activate` works with any deploy, not only `--production`.
10
+ - Dev (`d`) runs on a production environment. It needs signing credentials
11
+ and asks for confirmation before it starts, then signs and deploys every
12
+ build. `svc dev` needs `--signed` on a production base and asks the same.
13
+ - The Production help text in the Config tab and both user guides describe
14
+ the new behaviour.
15
+
3
16
  ## 1.0.2
4
17
 
5
18
  - Workspace settings can switch environment again. Press `v` in the settings
package/dist/cli.js CHANGED
@@ -39,7 +39,7 @@ const cli = meow(`
39
39
  --signed, -s dev/watch: sign after each build
40
40
  --force, -f deploy: overwrite the existing addon version
41
41
  --production, -p deploy: upload the signed zip to production
42
- --activate, -a deploy: activate after a production deploy
42
+ --activate, -a deploy: activate the uploaded version
43
43
  --no-zip build: skip the zip archive
44
44
  --global setup-signing: save for every project on this machine
45
45
  --minimal Shell: compact layout for small terminals
@@ -96,6 +96,18 @@ export const devCommand = {
96
96
  process.exitCode = 1;
97
97
  return;
98
98
  }
99
+ if (dev.production) {
100
+ if (!flags['signed']) {
101
+ say('\n\x1b[31mThe base environment is production, which only takes the signed zip. Run `svc dev --signed`.\x1b[0m\n');
102
+ process.exitCode = 1;
103
+ return;
104
+ }
105
+ const go = await promptYesNo(`\x1b[31m${dev.domain} is PRODUCTION.\x1b[0m Dev will sign and deploy every build of ${project.manifest.id} there (addon ${dev.addonName}). Start it? (y/N): `);
106
+ if (!go) {
107
+ process.exitCode = 1;
108
+ return;
109
+ }
110
+ }
99
111
  if (!(await resolveDeployPasswordForCli(dev)))
100
112
  return;
101
113
  let signingCredentials;
@@ -109,7 +109,7 @@ const FIELDS = [
109
109
  label: 'Production',
110
110
  kind: 'bool',
111
111
  section: 'ENVIRONMENT',
112
- help: 'Treat deploys to this base environment as production: signed zip, confirmation, activation, and no dev loop. Off by default even when the name says prod, so a repo with only a production site still gets a dev loop.',
112
+ help: 'Treat this base environment as production: deploys use the signed zip, ask for confirmation and offer activation, and dev confirms before it starts. Off by default even when the name says prod.',
113
113
  },
114
114
  {
115
115
  key: 'signingUsername',
@@ -40,7 +40,7 @@ const GUIDE = {
40
40
  title: 'Versions',
41
41
  text: [
42
42
  "The versions uploaded to the addon on the active environment's site, the domain in the top bar. v switches environment.",
43
- '↑↓ select, a activates the selected version, r refreshes. Production deploys activate on their own.',
43
+ '↑↓ select, a activates the selected version, r refreshes. A production deploy asks whether to activate.',
44
44
  ],
45
45
  },
46
46
  log: {
@@ -422,6 +422,19 @@ export function Shell({ apps: initialApps, workspaceRoot, version, minimal = fal
422
422
  confirm: message => new Promise(resolve => {
423
423
  setOverlay({ kind: 'confirm', message, resolve });
424
424
  }),
425
+ async choose(label, choices) {
426
+ const picked = await new Promise(resolve => {
427
+ setOverlay({
428
+ kind: 'choice',
429
+ label,
430
+ choices,
431
+ multi: false,
432
+ initial: [0],
433
+ resolve,
434
+ });
435
+ });
436
+ return picked?.[0] ?? null;
437
+ },
425
438
  openHelp() {
426
439
  setOverlay({ kind: 'help' });
427
440
  },
@@ -144,21 +144,25 @@ const deployDetail = (p) => {
144
144
  ? `${env} · ${t('signed zip · confirms first')}`
145
145
  : env;
146
146
  };
147
- /** Deploy to the active environment; production means signed + activate. */
147
+ /** Deploy to the active environment; production means signed, and asks whether to activate. */
148
148
  async function deployTo(ctx, force) {
149
- if (ctx.isProduction &&
150
- !(await ctx.confirm(t('Deploy the signed {id} to {env} (addon {addon}) and activate it?', {
149
+ let activate = false;
150
+ if (ctx.isProduction) {
151
+ const pick = await ctx.choose(t('Deploy the signed {id} to {env} (addon {addon})', {
151
152
  id: ctx.project.manifest.id,
152
153
  env: ctx.environment,
153
154
  addon: ctx.project.devProperties?.addonName ?? '?',
154
- })))) {
155
- return;
155
+ }), [t('Deploy and activate'), t('Deploy only')]);
156
+ if (pick === null)
157
+ return;
158
+ activate = pick === 0;
156
159
  }
157
160
  const config = await resolveDeployConfig(ctx);
158
161
  if (!config)
159
162
  return;
160
163
  startDeploy(ctx.project, config, {
161
- ...(ctx.isProduction ? { production: true, activate: true } : { force }),
164
+ force,
165
+ ...(ctx.isProduction && { production: true, activate }),
162
166
  onAddonMissing: askCreateAddon(ctx),
163
167
  });
164
168
  ctx.setTab('log');
@@ -166,10 +170,17 @@ async function deployTo(ctx, force) {
166
170
  const hasSigning = (p) => Boolean(p.devProperties?.signingUsername);
167
171
  async function startDevOrWatch(ctx, deploy) {
168
172
  if (deploy && ctx.isProduction) {
169
- ctx.notify(t('Dev never deploys to a production environment ({env}). Switch with v.', {
173
+ if (!hasSigning(ctx.project)) {
174
+ ctx.notify(t('Dev on {env} needs signing credentials: production only takes the signed zip. / sets them up.', { env: ctx.environment }), 'warn');
175
+ return;
176
+ }
177
+ const go = await ctx.confirm(t('{env} is PRODUCTION. Dev will sign and deploy every build of {id} there (addon {addon}). Start it?', {
170
178
  env: ctx.environment,
171
- }), 'warn');
172
- return;
179
+ id: ctx.project.manifest.id,
180
+ addon: ctx.project.devProperties?.addonName ?? '?',
181
+ }));
182
+ if (!go)
183
+ return;
173
184
  }
174
185
  if (runningTasks(ctx.project.root).some(task => task.kind === 'dev' || task.kind === 'watch')) {
175
186
  ctx.notify(t('A dev/watch task is already running for this app (K stops it).'), 'warn');
@@ -198,9 +209,14 @@ export const actions = [
198
209
  key: 'd',
199
210
  group: 'app',
200
211
  label: 'Dev',
201
- detail: p => hasSigning(p)
202
- ? t('build, sign and deploy on change')
203
- : t('build and deploy on change'),
212
+ detail(p) {
213
+ const base = hasSigning(p)
214
+ ? t('build, sign and deploy on change')
215
+ : t('build and deploy on change');
216
+ return p.devProperties?.productionEnvironment
217
+ ? `${base} · ${t('confirms first')}`
218
+ : base;
219
+ },
204
220
  enabled: hasDev,
205
221
  async run(ctx) {
206
222
  await startDevOrWatch(ctx, true);
@@ -114,7 +114,7 @@ const sv = {
114
114
  'Enter edits and saves a field, Esc cancels, ←→ or space cycles choices, Ctrl+O lists the addons on the site. y copies shared values into package.json for the team.': 'Enter redigerar och sparar ett fält, Esc avbryter, ←→ eller mellanslag bläddrar bland val, Ctrl+O listar webbplatsens tillägg. y kopierar delade värden till package.json för teamet.',
115
115
  'Passwords and secrets go to the OS keychain, never to a file. Leave them empty to be asked on each run.': 'Lösenord och hemligheter sparas i nyckelringen, aldrig i en fil. Lämna dem tomma för att bli tillfrågad varje gång.',
116
116
  "The versions uploaded to the addon on the active environment's site, the domain in the top bar. v switches environment.": 'Versionerna som laddats upp till tillägget på den aktiva miljöns webbplats, domänen i toppraden. v byter miljö.',
117
- '↑↓ select, a activates the selected version, r refreshes. Production deploys activate on their own.': '↑↓ väljer, a aktiverar vald version, r uppdaterar. Produktionsdriftsättningar aktiverar själva.',
117
+ '↑↓ select, a activates the selected version, r refreshes. A production deploy asks whether to activate.': '↑↓ väljer, a aktiverar vald version, r uppdaterar. En produktionsdriftsättning frågar om den ska aktivera.',
118
118
  'Output from build, sign, deploy, dev and watch for the selected app. Dev and watch keep running while you switch apps; K stops them.': 'Utdata från build, sign, deploy, dev och watch för vald app. Dev och watch fortsätter köra medan du byter app; K stoppar dem.',
119
119
  '↑↓ scroll, PgUp/PgDn page, f jumps to the end, x toggles line wrap.': '↑↓ scrollar, PgUp/PgDn bläddrar sida, f hoppar till slutet, x växlar radbrytning.',
120
120
  "What's new": 'Nyheter',
@@ -129,8 +129,12 @@ const sv = {
129
129
  'Addon {addon} does not exist on {domain}. Create it and deploy again?': 'Tillägget {addon} finns inte på {domain}. Skapa det och driftsätt igen?',
130
130
  'e.g. test or prod, overriding domain and auth': 't.ex. test eller prod, med egen domän och auth',
131
131
  'Environment name (e.g. test, prod)': 'Miljönamn (t.ex. test, prod)',
132
- 'Deploy the signed {id} to {env} (addon {addon}) and activate it?': 'Driftsätt signerade {id} till {env} (addon {addon}) och aktivera?',
133
- 'Dev never deploys to a production environment ({env}). Switch with v.': 'Dev driftsätter aldrig till en produktionsmiljö ({env}). Byt med v.',
132
+ 'Deploy the signed {id} to {env} (addon {addon})': 'Driftsätt signerade {id} till {env} (addon {addon})',
133
+ 'Deploy and activate': 'Driftsätt och aktivera',
134
+ 'Deploy only': 'Bara driftsätt',
135
+ 'Dev on {env} needs signing credentials: production only takes the signed zip. / sets them up.': 'Dev mot {env} kräver signeringsuppgifter: produktion tar bara den signerade zip-filen. / ställer in dem.',
136
+ '{env} is PRODUCTION. Dev will sign and deploy every build of {id} there (addon {addon}). Start it?': '{env} är PRODUKTION. Dev signerar och driftsätter varje bygge av {id} dit (addon {addon}). Starta?',
137
+ 'confirms first': 'bekräftar först',
134
138
  'switched to {env}': 'bytte till {env}',
135
139
  'environment {env} added': 'miljön {env} tillagd',
136
140
  environment: 'miljö',
@@ -245,7 +249,7 @@ const sv = {
245
249
  'Environment name': 'Miljönamn',
246
250
  Production: 'Produktion',
247
251
  'What this base configuration is: dev, test, prod… Other environments are added on top of it with E or the palette and override only what differs.': 'Vad denna grundkonfiguration är: dev, test, prod… Andra miljöer läggs ovanpå med E eller paletten och skriver bara över det som skiljer.',
248
- 'Treat deploys to this base environment as production: signed zip, confirmation, activation, and no dev loop. Off by default even when the name says prod, so a repo with only a production site still gets a dev loop.': 'Behandla driftsättningar till grundmiljön som produktion: signerad zip, bekräftelse, aktivering och ingen dev-loop. Av som standard även om namnet säger prod, så att ett repo med bara en produktionssajt ändå får en dev-loop.',
252
+ 'Treat this base environment as production: deploys use the signed zip, ask for confirmation and offer activation, and dev confirms before it starts. Off by default even when the name says prod.': 'Behandla grundmiljön som produktion: driftsättningar använder den signerade zip-filen, ber om bekräftelse och erbjuder aktivering, och dev bekräftar innan den startar. Av som standard även om namnet säger prod.',
249
253
  'Saved {label}.': 'Sparade {label}.',
250
254
  'Looking up OAuth2 endpoints…': 'Slår upp OAuth2-endpoints…',
251
255
  'Endpoints filled from the site OpenID config.': 'Endpoints ifyllda från webbplatsens OpenID-konfig.',
@@ -3,10 +3,9 @@
3
3
  *
4
4
  * Handles all API interactions with Sitevision servers:
5
5
  * - Signing apps via developer.sitevision.se
6
- * - Deploying to development environments
7
- * - Deploying to production environments
6
+ * - Deploying apps
8
7
  * - Creating addons
9
- * - Activating production apps
8
+ * - Activating uploaded versions
10
9
  */
11
10
  import fs from 'fs';
12
11
  import path from 'path';
@@ -425,38 +424,6 @@ export async function deployApp(zipPath, config, appType, force = false) {
425
424
  };
426
425
  }
427
426
  }
428
- /**
429
- * Deploy an app to production
430
- *
431
- * @param signedZipPath - Path to the SIGNED zip file (required for production)
432
- * @param config - Production deployment configuration
433
- * @param appType - The app type (web, widget, rest)
434
- */
435
- export async function deployProduction(signedZipPath, config, appType) {
436
- // Deploy the signed app
437
- const deployResult = await deployApp(signedZipPath, config, appType, true);
438
- if (!deployResult.success) {
439
- return deployResult;
440
- }
441
- if (!config.activate)
442
- return deployResult;
443
- if (!deployResult.executableId) {
444
- return {
445
- ...deployResult,
446
- activated: false,
447
- message: 'Deployed, but not activated: the server did not return an executable id.',
448
- };
449
- }
450
- const activationResult = await activateApp(deployResult.executableId, config, appType);
451
- return {
452
- success: true,
453
- executableId: deployResult.executableId,
454
- activated: activationResult.success,
455
- message: activationResult.success
456
- ? 'Deployed and activated successfully'
457
- : `Deployed successfully but activation failed: ${activationResult.error}`,
458
- };
459
- }
460
427
  // =============================================================================
461
428
  // ADDON MANAGEMENT API
462
429
  // =============================================================================
@@ -524,7 +491,7 @@ export async function createAddon(config, appType) {
524
491
  }
525
492
  }
526
493
  /**
527
- * Activate a deployed production app
494
+ * Activate an uploaded version
528
495
  *
529
496
  * @param executableId - The executable ID returned from deployment
530
497
  * @param config - Deployment configuration
@@ -8,7 +8,7 @@ import { hasSitevisionScripts, runSitevisionScriptsBuild, getDelegatedZipPath, c
8
8
  import { copyStaticToBuild, copySrcToBuild, cleanBuild, createBuildZip, formatFileSize, getZipSize, zipExists, } from './zip.js';
9
9
  import { deleteSessionCookie } from './keychain.js';
10
10
  import { isBundledApp, getAppType, getFullAppId, getZipPath, getSignedZipPath, getDeployZipPath, localizedText, } from './project-detection.js';
11
- import { signApp, deployApp, deployProduction, activateApp, createAddon, listAddons, classifyAddon, } from './sitevision-api.js';
11
+ import { signApp, deployApp, activateApp, createAddon, listAddons, classifyAddon, } from './sitevision-api.js';
12
12
  import { ProcessRunner } from './process-runner.js';
13
13
  import { debug } from './debug.js';
14
14
  const MAX_LINES = 2000;
@@ -170,10 +170,8 @@ async function signOnce(project, task, log, zipPath, credentials) {
170
170
  async function deployOnce(project, task, log, zipPath, config, options) {
171
171
  setPhase(task, 'deploying');
172
172
  const appType = getAppType(project.manifest);
173
- log('dep', `POST multipart → ${options.production ? 'production' : 'dev'} import · ${config.addonName} · ${path.basename(zipPath)}`);
174
- const upload = async () => options.production
175
- ? deployProduction(zipPath, { ...config, activate: options.activate }, appType)
176
- : deployApp(zipPath, config, appType, options.force);
173
+ log('dep', `POST multipart → import · ${config.addonName} · ${path.basename(zipPath)}`);
174
+ const upload = async () => deployApp(zipPath, config, appType, options.force);
177
175
  let result = await upload();
178
176
  if (!result.success && result.contextNodeMissing) {
179
177
  const addon = classifyAddon(await listAddons(config), config.addonName);
@@ -202,12 +200,23 @@ async function deployOnce(project, task, log, zipPath, config, options) {
202
200
  }
203
201
  if (!result.success)
204
202
  throw new Error(result.error ?? 'Deployment failed');
205
- if (options.production && options.activate && !result.activated) {
206
- throw new Error(result.message ?? 'Deployed, but not activated');
207
- }
208
203
  log('dep', `${result.message ?? 'deployed'}${result.executableId ? ` · exec ${result.executableId}` : ''}`, 'ok');
204
+ if (options.activate) {
205
+ if (!result.executableId) {
206
+ throw new Error('Deployed, but not activated: the server did not return an executable id.');
207
+ }
208
+ await activateOnce(task, log, config, appType, result.executableId, `exec ${result.executableId}`);
209
+ }
209
210
  return result.executableId;
210
211
  }
212
+ async function activateOnce(task, log, config, appType, executableId, versionLabel) {
213
+ setPhase(task, 'activating');
214
+ log('act', `PUT activateCustomModuleExecutable · ${versionLabel}`);
215
+ const result = await activateApp(executableId, config, appType);
216
+ if (!result.success)
217
+ throw new Error(result.error ?? 'Activation failed');
218
+ log('act', `${versionLabel} is now active`, 'ok');
219
+ }
211
220
  // ---------------------------------------------------------------------------
212
221
  // Tasks
213
222
  // ---------------------------------------------------------------------------
@@ -271,12 +280,7 @@ export function startActivate(project, config, executableId, versionLabel) {
271
280
  const { task, log, finish } = createTask('activate', project, `activate ${versionLabel}`);
272
281
  void (async () => {
273
282
  try {
274
- setPhase(task, 'activating');
275
- log('act', `PUT activateCustomModuleExecutable · ${versionLabel}`);
276
- const result = await activateApp(executableId, config, getAppType(project.manifest));
277
- if (!result.success)
278
- throw new Error(result.error ?? 'Activation failed');
279
- log('act', `${versionLabel} is now active`, 'ok');
283
+ await activateOnce(task, log, config, getAppType(project.manifest), executableId, versionLabel);
280
284
  finish('success');
281
285
  }
282
286
  catch (error) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sitevision-cli",
3
- "version": "1.0.2",
3
+ "version": "1.1.0-beta.0",
4
4
  "description": "Build, sign and deploy Sitevision apps from the terminal",
5
5
  "license": "MIT",
6
6
  "author": "Rasmus Söderström",
package/readme.md CHANGED
@@ -13,7 +13,7 @@ MCPServer) from a full-screen terminal shell or as plain commands.
13
13
  works with SSO), or a captured browser session for SAML-only sites.
14
14
  - **No secrets on disk.** Passwords, tokens and cookies live in the OS keychain.
15
15
  - **Environments.** dev, test and prod in one config; production deploys use
16
- the signed zip, confirm and activate.
16
+ the signed zip, confirm, and activate only if you say so.
17
17
  - **Shared config in git.** Site and auth settings for the whole team live in
18
18
  `package.json`, once at the repo root; your username stays in a local
19
19
  `.dev_properties.json`. Compatible with plain sitevision-scripts.
@@ -46,7 +46,7 @@ svc
46
46
  every change and deploy. Output is in the **Log** tab (`4`).
47
47
 
48
48
  For production: switch environment with `v`, press `b` to build, `s` to sign
49
- and `p` to deploy and activate.
49
+ and `p` to deploy. It asks whether to activate the new version or just upload it.
50
50
 
51
51
  ## The shell
52
52
 
@@ -79,7 +79,7 @@ svc # interactive shell
79
79
  svc build [--no-zip] # build to dist/<id>.zip
80
80
  svc sign # sign to dist/<id>-signed.zip
81
81
  svc deploy [--force] # deploy the zip
82
- svc deploy --production [--activate] # deploy the signed zip
82
+ svc deploy [--production] [--activate] # signed zip only / activate after
83
83
  svc dev [--signed] # build + deploy on change
84
84
  svc watch [--signed] # build on change, no deploy
85
85
  svc info # project information