sitevision-cli 1.0.0-beta.24 → 1.0.0-beta.25

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.
@@ -12,7 +12,7 @@ const GROUPS = [
12
12
  export function CommandPalette({ project, onRun, onClose, height, }) {
13
13
  const [query, setQuery] = useState('');
14
14
  const [index, setIndex] = useState(0);
15
- const matches = actions.filter(a => fuzzyMatch(query, t(a.label)));
15
+ const matches = actions.filter(a => !a.hidden?.(project) && fuzzyMatch(query, t(a.label)));
16
16
  const ordered = GROUPS.flatMap(g => matches.filter(a => a.group === g.id));
17
17
  useInput((input, key) => {
18
18
  if (key.escape) {
@@ -30,6 +30,7 @@ export interface Action {
30
30
  label: string;
31
31
  detail?: (project: ProjectInfo) => string | undefined;
32
32
  enabled?: (project: ProjectInfo) => boolean;
33
+ hidden?: (project: ProjectInfo) => boolean;
33
34
  run: (ctx: ActionContext) => Promise<void>;
34
35
  }
35
36
  export declare function forgetSession(project: ProjectInfo): void;
Binary file
@@ -180,6 +180,7 @@ export interface DeployResponse {
180
180
  message?: string;
181
181
  error?: string;
182
182
  authExpired?: boolean;
183
+ contextNodeMissing?: boolean;
183
184
  }
184
185
  /**
185
186
  * API response from addon creation
@@ -94,6 +94,10 @@ const sv = {
94
94
  'to {env}': 'till {env}',
95
95
  'Switch environment': 'Byt miljö',
96
96
  'Add environment': 'Lägg till miljö',
97
+ 'Create addon': 'Skapa tillägg',
98
+ 'Create addon failed': 'Kunde inte skapa tillägget',
99
+ 'addon {addon} created': 'tillägget {addon} skapat',
100
+ '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?',
97
101
  'e.g. test or prod, overriding domain and auth': 't.ex. test eller prod, med egen domän och auth',
98
102
  'Environment name (e.g. test, prod)': 'Miljönamn (t.ex. test, prod)',
99
103
  'Deploy the signed {id} to {env} and activate it?': 'Driftsätt signerade {id} till {env} och aktivera?',
@@ -144,4 +144,12 @@ export declare function listAddons(config: DeployConfig): Promise<{
144
144
  addons?: AddonNode[];
145
145
  error?: string;
146
146
  }>;
147
+ /**
148
+ * Whether an addon exists, judged from a listAddons result. A failed or empty
149
+ * listing is "unknown": a dead session can look exactly like an empty site.
150
+ */
151
+ export declare function classifyAddon(listed: {
152
+ success: boolean;
153
+ addons?: AddonNode[];
154
+ }, name: string): 'missing' | 'present' | 'unknown';
147
155
  export { createBasicAuth, configAuth, unauthorizedMessage };
@@ -391,6 +391,14 @@ export async function deployApp(zipPath, config, appType, force = false) {
391
391
  error: 'Conflict. Addon already exists. Use --force to overwrite.',
392
392
  };
393
393
  }
394
+ if (response.statusCode === 400 &&
395
+ response.body.toString().includes('could not resolve context node')) {
396
+ return {
397
+ success: false,
398
+ error: `Deployment failed with status 400: ${summarizeErrorBody(response.body, response.headers)}`,
399
+ contextNodeMissing: true,
400
+ };
401
+ }
394
402
  return {
395
403
  success: false,
396
404
  error: `Deployment failed with status ${response.statusCode}: ${summarizeErrorBody(response.body, response.headers)}`,
@@ -445,10 +453,11 @@ export async function deployProduction(signedZipPath, config, appType) {
445
453
  */
446
454
  export async function createAddon(config, appType) {
447
455
  const url = buildAddonEndpointUrl(config.domain, config.siteName, appType, config.useHTTP);
448
- const body = JSON.stringify({
449
- name: config.addonName,
450
- category: 'Other',
451
- });
456
+ const payload = { name: config.addonName };
457
+ // Only the custommodule endpoints take (and require) a category.
458
+ if (appType === 'web' || appType === 'widget')
459
+ payload['category'] = 'Other';
460
+ const body = JSON.stringify(payload);
452
461
  const { auth, kind } = configAuth(config);
453
462
  try {
454
463
  const response = await makeRequest(url, {
@@ -617,6 +626,18 @@ export async function listAddons(config) {
617
626
  };
618
627
  }
619
628
  }
629
+ /**
630
+ * Whether an addon exists, judged from a listAddons result. A failed or empty
631
+ * listing is "unknown": a dead session can look exactly like an empty site.
632
+ */
633
+ export function classifyAddon(listed, name) {
634
+ if (!listed.success || !listed.addons?.length)
635
+ return 'unknown';
636
+ const wanted = name.toLowerCase();
637
+ return listed.addons.some(addon => addon.name.toLowerCase() === wanted)
638
+ ? 'present'
639
+ : 'missing';
640
+ }
620
641
  // =============================================================================
621
642
  // HELPER EXPORTS
622
643
  // =============================================================================
@@ -30,6 +30,7 @@ export interface DeployOptions {
30
30
  force?: boolean;
31
31
  production?: boolean;
32
32
  activate?: boolean;
33
+ onAddonMissing?: (addonName: string) => Promise<boolean>;
33
34
  }
34
35
  export declare function startBuild(project: ProjectInfo): Task;
35
36
  export declare function startSign(project: ProjectInfo, credentials: SigningCredentials): Task;
@@ -40,6 +41,7 @@ export interface DevOptions {
40
41
  deploy: boolean;
41
42
  signingCredentials?: SigningCredentials;
42
43
  deployConfig?: DeployConfig;
44
+ onAddonMissing?: DeployOptions['onAddonMissing'];
43
45
  }
44
46
  /**
45
47
  * Dev / watch loop: build on every source change, then optionally sign and
@@ -6,7 +6,7 @@ import { WebpackRunner, hasLocalWebpackConfig } from './webpack-runner.js';
6
6
  import { hasSitevisionScripts, runSitevisionScriptsBuild, getDelegatedZipPath, checkSitevisionScriptsCompatibility, } from './sitevision-scripts-runner.js';
7
7
  import { copyStaticToBuild, copySrcToBuild, cleanBuild, createBuildZip, zipExists, } from './zip.js';
8
8
  import { isBundledApp, getAppType, getFullAppId, getZipPath, getSignedZipPath, getDeployZipPath, localizedText, } from './project-detection.js';
9
- import { signApp, deployApp, deployProduction, activateApp, } from './sitevision-api.js';
9
+ import { signApp, deployApp, deployProduction, activateApp, createAddon, listAddons, classifyAddon, } from './sitevision-api.js';
10
10
  import { ProcessRunner } from './process-runner.js';
11
11
  const MAX_LINES = 2000;
12
12
  const emitter = new EventEmitter();
@@ -149,9 +149,27 @@ async function deployOnce(project, task, log, zipPath, config, options) {
149
149
  setPhase(task, 'deploying');
150
150
  const appType = getAppType(project.manifest);
151
151
  log('dep', `POST multipart → ${options.production ? 'production' : 'dev'} import · ${config.addonName} · ${path.basename(zipPath)}`);
152
- const result = options.production
153
- ? await deployProduction(zipPath, { ...config, activate: options.activate }, appType)
154
- : await deployApp(zipPath, config, appType, options.force);
152
+ const upload = async () => options.production
153
+ ? deployProduction(zipPath, { ...config, activate: options.activate }, appType)
154
+ : deployApp(zipPath, config, appType, options.force);
155
+ let result = await upload();
156
+ if (!result.success && result.contextNodeMissing) {
157
+ const addon = classifyAddon(await listAddons(config), config.addonName);
158
+ if (addon === 'unknown') {
159
+ throw new Error(`${result.error}\nCould not list the site's addons either, so the session may have expired. Press l to log in again.`);
160
+ }
161
+ if (addon === 'missing' && options.onAddonMissing) {
162
+ log('dep', `addon ${config.addonName} does not exist`, 'warn');
163
+ if (await options.onAddonMissing(config.addonName)) {
164
+ const created = await createAddon(config, appType);
165
+ if (!created.success) {
166
+ throw new Error(created.error ?? 'Create addon failed');
167
+ }
168
+ log('dep', `created addon ${config.addonName}`, 'ok');
169
+ result = await upload();
170
+ }
171
+ }
172
+ }
155
173
  if (!result.success)
156
174
  throw new Error(result.error ?? 'Deployment failed');
157
175
  log('dep', `${result.message ?? 'deployed'}${result.executableId ? ` · exec ${result.executableId}` : ''}`, 'ok');
@@ -288,6 +306,7 @@ export function startDev(project, options) {
288
306
  if (options.deploy && options.deployConfig) {
289
307
  await deployOnce(project, task, log, deployZip, options.deployConfig, {
290
308
  force: true,
309
+ onAddonMissing: options.onAddonMissing,
291
310
  });
292
311
  }
293
312
  setPhase(task, 'watching');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sitevision-cli",
3
- "version": "1.0.0-beta.24",
3
+ "version": "1.0.0-beta.25",
4
4
  "license": "MIT",
5
5
  "bin": {
6
6
  "svc": "dist/cli.js"