andesine 0.5.0 → 0.6.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.
Files changed (25) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/dist/cli.js +39 -80
  3. package/dist/cli.js.map +1 -1
  4. package/dist/templates/extension/README.md +11 -5
  5. package/dist/templates/extension/andesine.config.ts +21 -17
  6. package/dist/templates/extension/package.template.json +4 -4
  7. package/dist/templates/extension/src/backend/server.ts +0 -5
  8. package/dist/templates/extension/src/frontend/count-words-action.tsx +46 -0
  9. package/dist/templates/extension/src/frontend/hello-panel.tsx +35 -3
  10. package/dist/templates/extension/src/frontend/index.tsx +2 -2
  11. package/dist/templates/extension/src/frontend/note-view.tsx +1 -4
  12. package/dist/templates/extension/src/frontend/shout-action.tsx +18 -42
  13. package/dist/templates/extension/src/frontend/wrap-in-note-action.tsx +52 -0
  14. package/dist/templates/pages/package.template.json +3 -3
  15. package/dist/templates/pages/src/components/content/copy-button.tsx +1 -1
  16. package/dist/templates/pages/src/components/layout/call-to-action.tsx +2 -6
  17. package/dist/templates/pages/src/components/layout/footer.astro +1 -2
  18. package/dist/templates/pages/src/components/layout/header.astro +0 -3
  19. package/dist/templates/pages/src/components/layout/mobile-navigation.tsx +1 -1
  20. package/dist/templates/pages/src/components/layout/page-actions.tsx +1 -7
  21. package/dist/templates/pages/src/components/primitives/button.tsx +21 -37
  22. package/dist/templates/pages/src/components/search/search-palette.tsx +1 -3
  23. package/package.json +4 -4
  24. package/dist/templates/extension/src/frontend/about-section.tsx +0 -47
  25. package/dist/templates/extension/src/frontend/bookmarks-panel.tsx +0 -67
package/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.6.0
4
+
5
+ - Validate extension manifests with the 0.6.0 rules: `settingsSections` is no longer
6
+ accepted, and element views accept a `description` and an `icon`, whose CSS
7
+ `extensions build` generates with the other manifest icons.
8
+ - Update the `extensions init` template: block action samples without UI (“Shout”), with a
9
+ `Menu` (“Count words”), and with a `Dialog` (“Wrap in Note”); a “More” menu and an “About”
10
+ dialog in the panel sample; a description and an icon for the Note view; and a local setup
11
+ guide in its README. The bookmarks panel and the About settings section samples, and the
12
+ declared request URL, are removed.
13
+ - Simplify the buttons of the `pages init` template: one `variant` (`primary`, `secondary`, or
14
+ `ghost`) replaces `variant` and `color`, and buttons default to the small size.
15
+
3
16
  ## 0.5.0
4
17
 
5
18
  - Accept the `extensions` role permission (manage workspace extensions) in role commands.
package/dist/cli.js CHANGED
@@ -51,7 +51,7 @@ var CancelledError = class extends CLIError {
51
51
  };
52
52
  //#endregion
53
53
  //#region package.json
54
- var version = "0.5.0";
54
+ var version = "0.6.0";
55
55
  //#endregion
56
56
  //#region src/config/files.ts
57
57
  /** Read declarative JSON only. Missing optional files are distinct from invalid files. */
@@ -1896,10 +1896,7 @@ const isEmptyDirectory = async (directory) => {
1896
1896
  const toPackageName = (directory, fallback) => {
1897
1897
  return path.basename(directory).toLowerCase().replace(/[^a-z\d._~-]+/g, "-").replace(/^[._-]+|[-]+$/g, "") || fallback;
1898
1898
  };
1899
- /**
1900
- * Copies a template that the build places next to the bundled CLI (see `scripts/templates.ts`)
1901
- * into a new or empty directory, named after the directory.
1902
- */
1899
+ /** Copies a bundled template (see `scripts/templates.ts`) into a new or empty directory. */
1903
1900
  const copyTemplate = async (template, directory, output) => {
1904
1901
  const source = fileURLToPath(new URL(`./templates/${template}/`, import.meta.url));
1905
1902
  const target = path.resolve(directory);
@@ -9067,7 +9064,6 @@ const publicID = (prefix, options) => {
9067
9064
  };
9068
9065
  //#endregion
9069
9066
  //#region ../../private/contracts/src/primitives/unique.ts
9070
- /** Whether every value is distinct. */
9071
9067
  const uniqueItems = (values) => new Set(values).size === values.length;
9072
9068
  //#endregion
9073
9069
  //#region ../../private/contracts/src/webhooks/catalog-definitions.ts
@@ -9499,7 +9495,7 @@ const fieldType = z.union([
9499
9495
  ])).optional()
9500
9496
  })
9501
9497
  ]);
9502
- /** Flat JSON Schema (draft 2020-12) subset that the host validates without running extension code. */
9498
+ /** Flat JSON Schema 2020-12 subset; the host validates it without running extension code. */
9503
9499
  const extensionConfigurationType = z.strictObject({
9504
9500
  $schema: z.literal(JSON_SCHEMA_DIALECT).optional(),
9505
9501
  type: z.literal("object"),
@@ -9800,27 +9796,22 @@ const contentNodeType = z.lazy(() => {
9800
9796
  const extensionContributionLimits = {
9801
9797
  elementViews: 50,
9802
9798
  blockActions: 20,
9803
- panelsPerSide: 2,
9804
- settingsSections: 5
9799
+ panelsPerSide: 2
9805
9800
  };
9806
9801
  /** Stable across versions; settings and managed records are keyed by it. */
9807
9802
  const extensionStableIDType = z.string().regex(/^[A-Za-z][A-Za-z0-9_-]{0,63}$/, "Use a letter, then letters, digits, - or _");
9808
9803
  const extensionEntryType = z.string().regex(/^[A-Za-z_$][A-Za-z0-9_$]{0,63}$/, "Use the name of an exported view").describe("The export of the frontend bundle that renders this contribution");
9809
- /**
9810
- * An icon class of the UnoCSS icons preset, e.g. `i-lucide:rocket`, or `i-acme:logo?bg` for a
9811
- * colored icon. Sets come from the extension build (default, installed, or custom); the build
9812
- * checks that each icon exists.
9813
- */
9804
+ /** UnoCSS icon class, e.g. `i-lucide:rocket` (`?bg` for colored); the build checks it exists. */
9814
9805
  const extensionIconType = z.string().max(120).regex(/^i-[a-z\d]+(?:-[a-z\d]+)*:[a-z\d]+(?:-[a-z\d]+)*(?:\?(?:bg|mask))?$/, "Use an icon class, e.g. i-lucide:rocket");
9815
9806
  const labelType = z.string().min(1).max(60);
9816
9807
  const elementNameType = z.string().max(100).regex(ELEMENT_TAG_NAME, "Use an element name, e.g. Accordion");
9817
- /**
9818
- * A root element view. `descendants` render elements of those names inside the root's content
9819
- * (nearest root first), before their own root registrations.
9820
- */
9808
+ /** `descendants` override their elements' own root views inside this root (nearest root first). */
9821
9809
  const extensionElementViewType = z.strictObject({
9822
9810
  id: extensionStableIDType,
9823
9811
  name: labelType,
9812
+ /** Shown in the editor's slash menu and the extension's settings. */
9813
+ description: z.string().min(1).max(200).optional(),
9814
+ icon: extensionIconType.optional(),
9824
9815
  element: elementNameType,
9825
9816
  entry: extensionEntryType,
9826
9817
  descendants: z.array(z.strictObject({
@@ -9854,11 +9845,6 @@ const extensionPanelType = z.discriminatedUnion("side", [z.strictObject({
9854
9845
  ]),
9855
9846
  entry: extensionEntryType
9856
9847
  })]);
9857
- const extensionSettingsSectionType = z.strictObject({
9858
- id: extensionStableIDType,
9859
- heading: labelType,
9860
- entry: extensionEntryType
9861
- });
9862
9848
  const extensionContributionsType = {
9863
9849
  elementViews: z.array(extensionElementViewType).max(extensionContributionLimits.elementViews).refine((views) => uniqueItems(views.map(({ element }) => element.toLowerCase())), "Use one view per element").default([]),
9864
9850
  blockActions: z.array(extensionBlockActionType).max(extensionContributionLimits.blockActions).default([]),
@@ -9866,8 +9852,7 @@ const extensionContributionsType = {
9866
9852
  return ["left", "right"].every((side) => {
9867
9853
  return panels.filter((panel) => panel.side === side).length <= extensionContributionLimits.panelsPerSide;
9868
9854
  });
9869
- }, `Use at most ${extensionContributionLimits.panelsPerSide} panels per side`).default([]),
9870
- settingsSections: z.array(extensionSettingsSectionType).max(extensionContributionLimits.settingsSections).default([])
9855
+ }, `Use at most ${extensionContributionLimits.panelsPerSide} panels per side`).default([])
9871
9856
  };
9872
9857
  //#endregion
9873
9858
  //#region ../../private/contracts/src/api/schemas/webhooks.ts
@@ -10074,8 +10059,7 @@ const createManifestType = (protocols) => {
10074
10059
  const contributionIDs = [
10075
10060
  ...manifest.elementViews,
10076
10061
  ...manifest.blockActions,
10077
- ...manifest.panels,
10078
- ...manifest.settingsSections
10062
+ ...manifest.panels
10079
10063
  ].map(({ id }) => id);
10080
10064
  const isGranted = (required) => {
10081
10065
  return manifest.permissions.some((permission) => hasPermission(permission, required));
@@ -10216,8 +10200,7 @@ const getEntries = (manifest) => {
10216
10200
  const entries = [
10217
10201
  ...manifest.elementViews.flatMap((view) => [view.entry, ...view.descendants.map(({ entry }) => entry)]),
10218
10202
  ...manifest.blockActions.map(({ entry }) => entry),
10219
- ...manifest.panels.map(({ entry }) => entry),
10220
- ...manifest.settingsSections.map(({ entry }) => entry)
10203
+ ...manifest.panels.map(({ entry }) => entry)
10221
10204
  ];
10222
10205
  return [...new Set(entries)];
10223
10206
  };
@@ -10325,10 +10308,7 @@ const importConfig = async (root) => {
10325
10308
  });
10326
10309
  return import(`${pathToFileURL(file).href}?t=${Date.now()}`);
10327
10310
  };
10328
- /**
10329
- * Loads and validates the manifest and build options of the extension in `root`. In
10330
- * development, the manifest's backend uses the development keys and URL, and may use local HTTP.
10331
- */
10311
+ /** Loads and validates the project; in development, the backend uses dev keys and may use HTTP. */
10332
10312
  const loadExtensionProject = async (root, development) => {
10333
10313
  if (!await exists(path.join(root, CONFIG_FILE))) throw new CLIError(`No ${CONFIG_FILE} in ${root}.`, exitCodes.usage);
10334
10314
  const config = await importConfig(root);
@@ -10382,10 +10362,7 @@ const getViewScopePrelude = (name) => {
10382
10362
  const fail = (message) => {
10383
10363
  throw new ExtensionStyleError(message);
10384
10364
  };
10385
- /**
10386
- * Removes comments outside strings. Escapes and strings are read like the browser reads them,
10387
- * and a raw newline in a string fails, so the validated text cannot differ from what applies.
10388
- */
10365
+ /** Strips comments, parsing like the browser so the validated text matches what applies. */
10389
10366
  const stripComments = (css) => {
10390
10367
  let result = "";
10391
10368
  let quote = "";
@@ -10450,10 +10427,11 @@ const splitSelectors = (prelude) => {
10450
10427
  selectors.push(prelude.slice(start).trim());
10451
10428
  return selectors;
10452
10429
  };
10453
- /** Every selector starts with `prefix` and a descendant combinator, never the prefix alone. */
10430
+ /** Each selector descends from `prefix` (no bare prefix); sibling combinators escape the scope. */
10454
10431
  const isScopedSelectorList = (selectors, prefix) => {
10455
10432
  return splitSelectors(selectors).every((selector) => {
10456
- return selector.startsWith(`${prefix} `) && selector.trim().length > prefix.length;
10433
+ const rest = selector.slice(prefix.length).trim();
10434
+ return selector.startsWith(`${prefix} `) && rest.length > 0 && !/^[~+]/.test(rest);
10457
10435
  });
10458
10436
  };
10459
10437
  /** Values load nothing (only inline SVG icon URLs) and contain no escapes or nested blocks. */
@@ -10489,10 +10467,8 @@ const validateBlock = (css, start, end, prefix, scopePrelude) => {
10489
10467
  }
10490
10468
  };
10491
10469
  /**
10492
- * View CSS is one `@scope (<view>) to (<content slot>)` rule, so it never reaches host-owned
10493
- * content in slots; icon CSS rules start with the icon scope. Inside, only style rules
10494
- * (optionally in `@media`/`@supports`) without escapes in values or URL-bearing values other
10495
- * than inline SVG icons. The host also checks the browser-parsed rules before applying them.
10470
+ * View CSS is one `@scope (<view>) to (<content slot>)` rule; icon CSS starts with the icon scope.
10471
+ * Inside, only style rules (or `@media`/`@supports`) with safe values; the host re-checks them.
10496
10472
  */
10497
10473
  const validateExtensionCSS = (css, name, kind = "view") => {
10498
10474
  if (css.length > 262144) fail("Extension CSS is too large");
@@ -10567,10 +10543,8 @@ const createExtensionGenerator = (icons) => {
10567
10543
  });
10568
10544
  };
10569
10545
  /**
10570
- * Generates the CSS of the extension views at build time: utility and icon classes found in the
10571
- * code, in one `@scope` rule from the views down to their content slots, without preflights
10572
- * (the host has them). Throws if the result is invalid, for example when an arbitrary value
10573
- * loads a URL.
10546
+ * Generates view CSS in one `@scope` rule from the views down to their content slots, without
10547
+ * preflights (the host has them). Throws if invalid, e.g. when an arbitrary value loads a URL.
10574
10548
  */
10575
10549
  const generateExtensionCSS = async (options) => {
10576
10550
  const { css: rules } = await (await createExtensionGenerator(options.icons)).generate(options.code, {
@@ -10620,7 +10594,8 @@ const getManifestIcons = (manifest) => {
10620
10594
  const icons = [
10621
10595
  manifest.icon,
10622
10596
  ...manifest.panels.map(({ icon }) => icon),
10623
- ...manifest.blockActions.map(({ icon }) => icon)
10597
+ ...manifest.blockActions.map(({ icon }) => icon),
10598
+ ...manifest.elementViews.map(({ icon }) => icon)
10624
10599
  ];
10625
10600
  return [...new Set(icons.filter((icon) => icon !== void 0))];
10626
10601
  };
@@ -10726,10 +10701,7 @@ const describeFailure = (status, body) => {
10726
10701
  if (status === 404 && (body === null || message === "Not Found")) return "The instance has no development endpoints. Set PUBLIC_EXTENSIONS_ENABLED=true and EXTENSIONS_DEVELOPMENT_ENABLED=true on the local backend.";
10727
10702
  return typeof message === "string" ? message : `The request failed (HTTP ${status}).`;
10728
10703
  };
10729
- /**
10730
- * A client for the development operations of a local instance. They are not in the public API,
10731
- * so the SDK has no methods for them; they need the signed-in member of a CLI profile.
10732
- */
10704
+ /** Client for the local instance's development endpoints, which are outside the public API. */
10733
10705
  const createDevelopmentClient = (context) => {
10734
10706
  const { baseURL, workspaceID } = context.config;
10735
10707
  if (!isLocalInstance(baseURL)) throw new CLIError(`${baseURL} is not a local instance. Extension development needs a local stack (localhost, *.localhost, *.local, or *.test); use --base-url.`, exitCodes.usage);
@@ -10897,10 +10869,7 @@ const generateExtensionKey = async (root, options = {}) => {
10897
10869
  ignored: await isIgnored$1(root, file)
10898
10870
  };
10899
10871
  };
10900
- /**
10901
- * Checks that a private key matches a current key of the manifest and, with a registry URL, of
10902
- * the registry entry, which Andesine instances trust.
10903
- */
10872
+ /** Checks that a private key matches a current manifest key and, optionally, a registry key. */
10904
10873
  const checkExtensionKey = async (root, options = {}) => {
10905
10874
  const source = options.key ? await readFile(path.resolve(options.key), "utf8") : process.env[KEY_ENVIRONMENT_VARIABLE];
10906
10875
  if (!source) throw new CLIError(`Set ${KEY_ENVIRONMENT_VARIABLE} or use --key <file>.`, exitCodes.usage);
@@ -10942,10 +10911,7 @@ const revokeExtensionKey = async (root, kid) => {
10942
10911
  //#region src/extensions/dev/environment.ts
10943
10912
  const KEY_ID = "development";
10944
10913
  const INSTANCES_VARIABLE = "ANDESINE_INSTANCES";
10945
- /**
10946
- * The project's development key pair, created once. The local instance gets only the public
10947
- * key; the private key stays in the work directory and the backend's environment file.
10948
- */
10914
+ /** The project's development key pair, created once; the instance gets only the public key. */
10949
10915
  const getDevelopmentKey = async (root) => {
10950
10916
  const directory = path.join(root, WORK_DIRECTORY, "keys");
10951
10917
  const file = path.join(directory, `${KEY_ID}.json`);
@@ -10982,10 +10948,7 @@ const setVariable = (source, name, value) => {
10982
10948
  if (pattern.test(source)) return source.replace(pattern, () => line);
10983
10949
  return `${source}${source && !source.endsWith("\n") ? "\n" : ""}${line}\n`;
10984
10950
  };
10985
- /**
10986
- * Points the local backend at the development key and trusts the local instance, in its
10987
- * environment file (created from `.env.example` when missing). Other variables stay.
10988
- */
10951
+ /** Sets the development key and trusted instance in the backend's environment file. */
10989
10952
  const writeDevelopmentEnvironment = async (root, file, key, baseURL) => {
10990
10953
  const target = path.resolve(root, file);
10991
10954
  const example = path.join(root, ".env.example");
@@ -11046,9 +11009,12 @@ const STOP_TIMEOUT = 1e4;
11046
11009
  const disabledHints = {
11047
11010
  approval_required: "approve its new permissions or URLs in Settings → Extensions",
11048
11011
  configuration_required: "fill in its required configuration in Settings → Extensions",
11049
- manual: "enable it in Settings → Extensions"
11012
+ manual: "enable it in Settings → Extensions",
11013
+ revoked: "its build is missing on the instance; save a file to upload it again"
11014
+ };
11015
+ const getErrorMessage = (error) => {
11016
+ return error instanceof Error ? error.message : String(error);
11050
11017
  };
11051
- const getErrorMessage = (error) => error instanceof Error ? error.message : String(error);
11052
11018
  /** Builds the project with the development key and uploads it, which installs or reloads it. */
11053
11019
  const uploadBuild = async (root, context, client, development) => {
11054
11020
  const project = await loadExtensionProject(root, development);
@@ -11073,10 +11039,7 @@ const uploadBuild = async (root, context, client, development) => {
11073
11039
  else await context.output.warning(`${name}@${version} is disabled: ${disabledHints[result.disabledReason ?? "manual"]}.`);
11074
11040
  return result;
11075
11041
  };
11076
- /**
11077
- * Runs an extension on the local instance for the signed-in member: uploads each build, reloads
11078
- * it on file changes, and stops it on exit, keeping its configuration and storage.
11079
- */
11042
+ /** Runs the extension on the local instance, reloading it on changes and stopping it on exit. */
11080
11043
  const runDevelopment = async (context, options) => {
11081
11044
  const { output, signal } = context;
11082
11045
  const root = path.resolve(options.root);
@@ -11218,7 +11181,9 @@ const readPublishedRegistry = async (directory) => {
11218
11181
  //#endregion
11219
11182
  //#region src/extensions/registry/check.ts
11220
11183
  const hash = (content) => createHash("sha256").update(content).digest("hex");
11221
- const describeKey = (key) => JSON.stringify(key, Object.keys(key).sort());
11184
+ const describeKey = (key) => {
11185
+ return JSON.stringify(key, Object.keys(key).sort());
11186
+ };
11222
11187
  const withoutKeys = (manifest) => {
11223
11188
  return JSON.stringify({
11224
11189
  ...manifest,
@@ -11274,10 +11239,7 @@ const checkRevocations = (revocations, published, available) => {
11274
11239
  }
11275
11240
  return errors;
11276
11241
  };
11277
- /**
11278
- * Checks the registry repository against the published site with the same rules that Andesine
11279
- * instances apply on ingestion, and builds each project (its dependencies must be installed).
11280
- */
11242
+ /** Builds each project and checks it against the published site, as instances do on ingestion. */
11281
11243
  const checkRegistry = async (repository, publishedDirectory, output) => {
11282
11244
  const projects = await readRegistryProjects(repository);
11283
11245
  const published = await readPublishedRegistry(publishedDirectory);
@@ -11351,10 +11313,7 @@ const writeVersion = async (published, siteURL, version, sourceCommit) => {
11351
11313
  await writeFile(path.join(directory, VERSION_FILE), `${JSON.stringify(versionManifest, null, 2)}\n`);
11352
11314
  return versionManifest;
11353
11315
  };
11354
- /**
11355
- * Checks the registry, writes the files of new versions, then the index, which instances read;
11356
- * the index comes last, so it never names files that are not published yet.
11357
- */
11316
+ /** Writes new versions, then the index, so the index never names unpublished files. */
11358
11317
  const buildRegistry = async (options, output) => {
11359
11318
  const siteURL = getSiteURL(options.siteURL);
11360
11319
  const check = await checkRegistry(options.repository, options.published, output);
@@ -11423,6 +11382,7 @@ const initExtension = async (directory, options, output) => {
11423
11382
  };
11424
11383
  //#endregion
11425
11384
  //#region src/extensions/register.ts
11385
+ const ROOT_DESCRIPTION = "Extension directory (default: current directory)";
11426
11386
  const formatVersions = (manifests) => {
11427
11387
  return manifests.map(({ name, version }) => `${name}@${version}`).join(", ");
11428
11388
  };
@@ -11431,7 +11391,6 @@ const reportRegistryErrors = async (output, errors) => {
11431
11391
  for (const error of errors) await output.error(error);
11432
11392
  throw new CLIError(`The registry has ${errors.length} problem(s).`);
11433
11393
  };
11434
- const ROOT_DESCRIPTION = "Extension directory (default: current directory)";
11435
11394
  const registerExtensions = (program, signal) => {
11436
11395
  const getOutput = (command) => {
11437
11396
  return createOutput(command.optsWithGlobals().interactive, signal);