@saooti/octopus-sdk 41.12.1-beta3 → 41.12.1

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.
@@ -11,7 +11,15 @@
11
11
  "Bash(npm:*)",
12
12
  "Bash(npx vitest:*)",
13
13
  "Bash(./node_modules/.bin/vitest run:*)",
14
- "Bash(npx eslint:*)"
14
+ "Bash(npx eslint:*)",
15
+ "Bash(node -e \"console.log\\(require.resolve\\('./package.json'\\)\\)\")",
16
+ "Bash(node --experimental-vm-modules -e ' *)",
17
+ "Bash(node -e ' *)",
18
+ "Read(//home/julien/Workspace/frontoffice/code/frontend/**)",
19
+ "Read(//home/julien/Workspace/frontoffice/**)",
20
+ "Bash(git rm *)",
21
+ "Bash(grep -vE '\\\\-[A-Za-z0-9_-]{8}\\\\.mjs$')",
22
+ "Bash(grep -E '\\\\-[A-Za-z0-9_-]{8}\\\\.mjs$')"
15
23
  ]
16
24
  }
17
25
  }
package/CHANGELOG.md CHANGED
@@ -1,27 +1,10 @@
1
1
  # CHANGELOG
2
2
 
3
- ## 41.12.1 (En cours)
3
+ ## 41.12.1 (19/08/2026)
4
4
 
5
- **Features**
6
-
7
- - Ajout de nouvelles fonctions dans l'api `rubriqueApi`
8
- - Option de `AdvancedSearch` pour ne pas afficher le filtre de groupe
9
- - Ajout callback sur fermeture de notifications
10
-
11
- **Fixes**
12
-
13
- - Correction d'une anomalie de chargement du style de la modale utilisée par
14
- `ClassicNotifications`
15
- - Ajout mode alternatif pour la récupération des épisodes dans
16
- `PodcastPresentationList`
17
- - Affichage des infos additionelles dans PresentationItem même sans description
18
-
19
- **Misc**
5
+ ### Fixes
20
6
 
21
- - Suppression de la marge entre le titre et le contenu pour `PresentationLayout`
22
- - Uniformisation espacement pour composants PM
23
- - Mise à jour règles eslint
24
- - Ajustement couleurs pour boutons secondaires
7
+ - Gestion des erreurs dans downloadHelper
25
8
 
26
9
  ## 41.12.0 (13/07/2026)
27
10
 
@@ -0,0 +1,122 @@
1
+ # Plan: fix octopus-sdk's dist output for plain `npm install` consumers
2
+
3
+ ## Background
4
+
5
+ Consumer apps (frontoffice, podcastmaker) currently only pass their test
6
+ suites against `@saooti/octopus-sdk` when it is `npm link`ed to this repo.
7
+ A plain `npm install` of the published package breaks Vitest in those repos
8
+ with:
9
+
10
+ - `TypeError: Unknown file extension ".vue"` for
11
+ `vue-material-design-icons/*.vue`
12
+ - `Error: Cannot find module '.../dayjs/locale/de' ... Did you mean
13
+ "dayjs/locale/de.js"?`
14
+
15
+ Vitest externalizes normal `node_modules` packages by default and lets
16
+ Node's native ESM resolver load them directly. That resolver can't handle a
17
+ raw `.vue` specifier, and — since `dayjs` ships no `"exports"` map — it
18
+ requires exact file names for subpath imports. This only "worked" when the
19
+ SDK was npm-linked because a linked package resolves outside `node_modules`
20
+ and dodges Vitest's externalization heuristic, letting Vite's own resolver
21
+ (which is lenient about extensions and knows `.vue`) handle it instead. That
22
+ was incidental, not a real fix, and consumer repos currently paper over it
23
+ with a `test.server.deps.inline` entry in their `vitest.config.js`.
24
+
25
+ Root cause is in this repo's `vite.config.js` build config, not in the
26
+ consumer apps.
27
+
28
+ ## Root causes
29
+
30
+ 1. **`vue-material-design-icons` externalized wholesale.** The `external`
31
+ callback in `vite.config.js` externalizes every bare (non-relative,
32
+ non-`@/`) import indiscriminately. `vue-material-design-icons` ships raw
33
+ `.vue` SFCs rather than pre-compiled JS, so externalizing it re-emits
34
+ `import X from "vue-material-design-icons/ChevronDown.vue"` verbatim into
35
+ `dist/*.mjs`. Confirmed via:
36
+ ```sh
37
+ grep -rhoE 'from "[^".][^"]*\.vue"' dist/*.mjs | sort -u
38
+ ```
39
+ → 58 distinct `vue-material-design-icons/*.vue` specifiers across the
40
+ dist chunks.
41
+
42
+ 2. **`dayjs/locale/*` imported without an extension.**
43
+ `src/components/composable/useDayjs.ts` has:
44
+ ```ts
45
+ import "dayjs/locale/de";
46
+ import "dayjs/locale/es";
47
+ import "dayjs/locale/fr";
48
+ import "dayjs/locale/it";
49
+ import "dayjs/locale/sl";
50
+ ```
51
+ `dayjs` has no `"exports"` map in its `package.json`, so Node's strict ESM
52
+ resolver needs the exact file name.
53
+
54
+ Swept the rest of `dist/*.mjs` for other bare/extensionless external
55
+ specifiers (`grep -rhoE 'from "[^".][^"]*"' *.mjs`, filtered against the
56
+ known peer/runtime deps) — nothing else came back. `qrcode.vue` is a real
57
+ package name (its own `package.json` resolves it), not a raw SFC path, so
58
+ it's fine as-is. No other externalized package (`video.js`, `vue-select`,
59
+ etc.) needs bundling.
60
+
61
+ ## Steps
62
+
63
+ ### 1. Bundle `vue-material-design-icons` instead of externalizing it
64
+
65
+ In `vite.config.js`, change the `external` callback:
66
+
67
+ ```js
68
+ external: (id) =>
69
+ !id.startsWith('vue-material-design-icons') &&
70
+ !id.startsWith('.') && !id.startsWith('@/') && !id.startsWith('@tests/') && !path.isAbsolute(id)
71
+ ```
72
+
73
+ The `vue()` plugin is already part of the build pipeline, so Rollup will
74
+ compile these icon SFCs into plain JS as part of the bundle — no raw `.vue`
75
+ specifier will remain in `dist`. Icons are tiny SVG wrappers, so this is a
76
+ negligible size cost spread across existing chunks.
77
+
78
+ ### 2. Fully-specify the dayjs locale imports
79
+
80
+ In `src/components/composable/useDayjs.ts`, add `.js` to all five locale
81
+ imports:
82
+
83
+ ```ts
84
+ import "dayjs/locale/de.js";
85
+ import "dayjs/locale/es.js";
86
+ import "dayjs/locale/fr.js";
87
+ import "dayjs/locale/it.js";
88
+ import "dayjs/locale/sl.js";
89
+ ```
90
+
91
+ `dayjs` stays a peer dependency (correct — consumer apps must own a single
92
+ dayjs instance); only the specifier changes.
93
+
94
+ ### 3. Verify
95
+
96
+ - Full rebuild (`npm run build`) and re-run the `grep -rhoE 'from
97
+ "[^".][^"]*"' *.mjs` sweep over `dist/` — should come back clean of raw
98
+ `.vue` and extensionless subpath imports.
99
+ - In `frontoffice/code/frontend`: remove the `@saooti/octopus-sdk` symlink,
100
+ do a plain `npm install @saooti/octopus-sdk@<new version>`, remove the
101
+ `test.server.deps.inline` entry from `vitest.config.js`, and run the full
102
+ test suite — should still be green.
103
+ - Repeat the same unlink/reinstall/remove-inline/test check in
104
+ `podcastmaker`, since it depends on the same SDK and would hit (or has
105
+ hit) the identical failure.
106
+
107
+ ### 4. Cleanup once verified
108
+
109
+ - Remove the `test.server.deps.inline: ['@saooti/octopus-sdk',
110
+ 'vue-material-design-icons']` block from `frontend/vitest.config.js` (and
111
+ podcastmaker's equivalent, if it was ever added there).
112
+ - Leave the existing dev-mode `optimizeDeps.exclude` / `server.fs.allow`
113
+ npm-link accommodations in the consumer apps' `vite.config.js` alone —
114
+ those are about local-dev HMR ergonomics when linked, unrelated to this
115
+ test-time module resolution bug.
116
+
117
+ ## Expected outcome
118
+
119
+ Once `dist/*.mjs` contains only specifiers Node's native ESM resolver can
120
+ load unassisted, Vitest's default externalization of `node_modules`
121
+ packages works correctly for a plain `npm install` of `@saooti/octopus-sdk`
122
+ — no `deps.inline` workaround needed in any consumer app.
@@ -0,0 +1,194 @@
1
+ # Plan: single bundled entry point for octopus-sdk test utilities
2
+
3
+ ## Context
4
+
5
+ octopus-sdk is consumed by frontoffice (and potentially podcastmaker) via
6
+ `npm link`. Frontoffice's Vite/Vitest and octopus-sdk's own Vite config both
7
+ define a global `@` alias pointing at their *own* `src/`. Because linked
8
+ packages are transformed from raw source (not the built `dist/`), any
9
+ octopus-sdk source file that imports something via `@/...` gets that alias
10
+ resolved against **frontoffice's** `src/` when transformed inside frontoffice's
11
+ Vite instance — wrong target, resolution failure.
12
+
13
+ This was already fixed for octopus-sdk's main bundle: `vite.config.js` builds
14
+ `index.ts` to `dist/index.mjs` with `@/...` imports resolved and inlined at
15
+ SDK build time (see `rollupOptions.external`), so consumers importing
16
+ `@saooti/octopus-sdk` never hit raw source needing alias resolution.
17
+
18
+ The gap: octopus-sdk's **test utilities** (`tests/utils.ts`,
19
+ `tests/mocks/*.ts`) are exported via `package.json`'s
20
+ `"./tests/*": "./tests/*"`, which passes through raw `.ts` source
21
+ unconditionally. Frontoffice's `tests/utils.ts` and ~70 spec files import
22
+ these paths directly, so they hit the same alias collision whenever the
23
+ SDK source touches `@/...` (e.g. `AuthStore.ts` now imports
24
+ `rubriquesApi` from `@/api`, which is a *transitive* import from
25
+ `tests/utils.ts` importing `useAuthStore`).
26
+
27
+ ## Decision
28
+
29
+ Bundle the test utilities into **one** built entry point, `dist/tests.mjs`,
30
+ built the same way as the main `index.mjs` (aliases resolved at SDK build
31
+ time). Consumers import everything from one path:
32
+ `@saooti/octopus-sdk/tests`.
33
+
34
+ This is *not* just a mechanical repackaging: the current `tests/mocks/i18n.ts`
35
+ and `tests/mocks/useRouter.ts` work by **side effect** — importing the file
36
+ calls `vi.mock(...)` at module-evaluation time. That only works because ES
37
+ module imports are evaluated in source order relative to other imports in the
38
+ importing file, and each of these mock files is imported as the very first
39
+ line of ~70 spec files. If those files were merged into one shared module
40
+ naively, importing that module would force *both* mocks (vue-i18n AND
41
+ vue-router) onto every spec file that imports it — a real behavior change for
42
+ specs that don't want vue-router mocked, and a correctness risk for anything
43
+ that relies on the real router.
44
+
45
+ **Fix: turn the mock modules into plain factory functions with no import-time
46
+ side effects, and have each spec file call `vi.mock()` explicitly, referencing
47
+ the imported factory.** This is Vitest's own documented supported pattern:
48
+ `vi.mock()` calls are hoisted above imports by Vitest's transform, and a
49
+ factory argument may reference an imported identifier as long as that
50
+ identifier's name is prefixed `mock` (Vitest hoists that specific import
51
+ alongside the `vi.mock` call). Concretely:
52
+
53
+ ```ts
54
+ // before (side effect, implicit)
55
+ import '@saooti/octopus-sdk/tests/mocks/i18n';
56
+
57
+ // after (explicit, still correctly hoisted)
58
+ import { mockI18n } from '@saooti/octopus-sdk/tests';
59
+ vi.mock('vue-i18n', mockI18n);
60
+ ```
61
+
62
+ This keeps mocking fully selective per spec file, fixes the alias-collision
63
+ bug at the root (SDK build resolves its own aliases), and gives a single,
64
+ low-maintenance entry point rather than a hand-maintained list of build
65
+ entries per mock file.
66
+
67
+ ## Steps
68
+
69
+ ### 1. octopus-sdk: convert mock modules to factories
70
+
71
+ - `tests/mocks/i18n.ts`: change from a `vi.mock(...)` side-effect call to
72
+ `export function mockI18n() { return { useI18n: () => ({...}), createI18n: () => {} }; }`
73
+ (same object literal as today, just returned instead of passed inline).
74
+ - `tests/mocks/useRouter.ts`: same transform →
75
+ `export function mockUseRouter() { return { useRoute: () => ({...}), useRouter: () => ({...}) }; }`.
76
+ - `tests/mocks/useAdvancedParamInit.ts`: same transform →
77
+ `export function mockAdvancedParamInit() { return { useAdvancedParamInit: () => ({...}) }; }`.
78
+ Note this one mocks an SDK-internal path (`@/components/composable/route/useAdvancedParamInit`),
79
+ not a third-party package — keep that target path as-is, only the
80
+ side-effect-to-factory conversion changes.
81
+ - `tests/mocks/rights.ts` (`mockEmission`, `mockPodcast`): no behavior change
82
+ needed, already plain functions — just gets folded into the same bundle.
83
+
84
+ ### 2. octopus-sdk: single entry point
85
+
86
+ - Create `tests/index.ts` that re-exports everything currently split across
87
+ `tests/utils.ts` and `tests/mocks/*.ts`: `mount`, `VueWrapper`,
88
+ `localisation`, `setupAuthStore`, `setupPlayerStore`, `combineStoreSetups`,
89
+ `setupPinia`, `mockI18n`, `mockUseRouter`, `mockAdvancedParamInit`,
90
+ `mockEmission`, `mockPodcast`.
91
+ (Either keep `tests/utils.ts` as the implementation file and have
92
+ `tests/index.ts` just re-export, or merge directly — whichever keeps the
93
+ smaller diff.)
94
+ - `vite.config.js`: change `build.lib.entry` from a single string to an
95
+ object with two entries:
96
+ ```js
97
+ entry: {
98
+ index: path.resolve(__dirname, 'index.ts'),
99
+ tests: path.resolve(__dirname, 'tests/index.ts'),
100
+ },
101
+ fileName: (_format, entryName) => `${entryName}.mjs`,
102
+ ```
103
+ Keep the existing `resolve.alias` (`@`, `@tests`, `vue`, `hls.js`) — the
104
+ `@tests` alias already needs to exist here (not just in `vitest.config.js`)
105
+ since `tests/mocks/i18n.ts` references `@tests/utils` internally; this was
106
+ already added to `vite.config.js` in a prior WIP pass on this branch — verify
107
+ it's still there, add it if not, and drop the now-redundant duplicate alias
108
+ from `vitest.config.js`.
109
+ - `cleanStaleChunks()` (in `vite.config.js`) currently only scans the top level
110
+ of `dist/`. With `tests: '...'` as an entry name (no `/` in it, unlike the
111
+ earlier multi-entry draft), output stays flat as `dist/tests.mjs` — no
112
+ recursive scanning needed. Confirm this after building; only revisit if the
113
+ entry name changes to something with a path segment.
114
+ - `package.json` `exports`: replace `"./tests/*": "./tests/*"` with
115
+ ```json
116
+ "./tests": {
117
+ "types": "./tests/index.ts",
118
+ "default": "./dist/tests.mjs"
119
+ }
120
+ ```
121
+ (`types` points at raw `.ts` source, same as today's behavior — no need for
122
+ `unplugin-dts` to cover this entry.)
123
+
124
+ ### 3. octopus-sdk: migrate its own internal specs (36 files)
125
+
126
+ octopus-sdk's own test suite imports `tests/mocks/i18n`, `tests/mocks/useRouter`,
127
+ `tests/mocks/useAdvancedParamInit` directly too (`grep -rl "mocks/i18n\|mocks/useRouter\|mocks/useAdvancedParamInit\|mocks/rights" tests --include="*.spec.ts"` → 36 files as of this writing). Each needs the same
128
+ mechanical rewrite as frontoffice (step 4). Since these are same-repo,
129
+ relative imports (e.g. `from '../mocks/i18n'` or `@tests/mocks/i18n`) — adjust
130
+ the codemod pattern accordingly, or just import from the new `@tests` (i.e.
131
+ `tests/index.ts`) alias directly.
132
+
133
+ ### 4. frontoffice: migrate consumer imports
134
+
135
+ - `tests/utils.ts`: change
136
+ ```ts
137
+ export { mount, VueWrapper } from '@saooti/octopus-sdk/tests/utils';
138
+ export { setupAuthStore, combineStoreSetups, setupPinia } from '@saooti/octopus-sdk/tests/utils';
139
+ ```
140
+ to import from `'@saooti/octopus-sdk/tests'` instead, and additionally
141
+ re-export `mockI18n`, `mockUseRouter` (and `mockAdvancedParamInit`,
142
+ `mockEmission`, `mockPodcast` if useful) so spec files can keep pulling
143
+ everything from the local `@tests/utils` alias as they do today.
144
+ - Mechanical codemod across spec files (66 files for i18n, 6 for useRouter —
145
+ counts as of this writing, re-run the greps below to confirm current
146
+ numbers):
147
+ ```
148
+ grep -rl "octopus-sdk/tests/mocks/i18n" tests
149
+ grep -rl "octopus-sdk/tests/mocks/useRouter" tests
150
+ ```
151
+ Replace:
152
+ ```ts
153
+ import '@saooti/octopus-sdk/tests/mocks/i18n';
154
+ ```
155
+ with:
156
+ ```ts
157
+ import { mockI18n } from '@tests/utils';
158
+ vi.mock('vue-i18n', mockI18n);
159
+ ```
160
+ (needs `import { vi } from 'vitest';` present — most spec files already
161
+ import `vi` for other reasons; add it where missing.)
162
+ Same pattern for `useRouter`:
163
+ ```ts
164
+ import { mockUseRouter } from '@tests/utils';
165
+ vi.mock('vue-router', mockUseRouter);
166
+ ```
167
+ This is regular enough to script with a small codemod (sed/ts-morph) rather
168
+ than hand-editing 70+ files — verify the mechanical replacement still lets
169
+ each file pass its own tests, since ordering (mock call before other
170
+ imports that transitively pull in `vue-i18n`/`vue-router`) still matters:
171
+ place these two lines where the old side-effect import used to be
172
+ (typically line 1) to preserve the same evaluation order.
173
+
174
+ ### 5. Verify
175
+
176
+ - `cd octopus-sdk && npm run build` — confirm `dist/tests.mjs` is produced and
177
+ `dist/index.mjs` still builds correctly.
178
+ - `cd octopus-sdk && npx vitest run` — full SDK suite green after step 3.
179
+ - `cd frontoffice/code/frontend && npx vitest run` — full frontend suite green
180
+ after step 4. Pay particular attention to any spec that relies on **real**
181
+ `vue-router` behavior today (i.e. does *not* currently import the
182
+ `useRouter` mock) — confirm the codemod didn't accidentally add a
183
+ `vi.mock('vue-router', ...)` call to those.
184
+ - Run `npm run lint` on all touched files in both repos (touched files only,
185
+ no `--fix`-then-commit sweep, per project convention).
186
+
187
+ ### Out of scope
188
+
189
+ - `"./src/*"` export mapping (raw source passthrough) is untouched — it's
190
+ used extensively by non-test app code in frontoffice (`main.ts`, `router.ts`,
191
+ several components) and is a separate, larger concern than test utilities.
192
+ - podcastmaker: searched at plan-writing time, found no references to
193
+ `octopus-sdk/tests/*` — re-check before assuming it's unaffected, in case
194
+ that changes before this plan is implemented.
package/TO_SLOT.md ADDED
@@ -0,0 +1,43 @@
1
+ # Stub components converted to scoped slots
2
+
3
+ The SDK used to ship several "stub" components — empty `<div />` placeholders —
4
+ that were overridden by the real frontoffice implementation at build time via
5
+ an implicit Vite alias trick. That trick breaks once the SDK is pre-built as a
6
+ library (see `~/Documents/FIX_BUILD_SDK.md`), so each usage was moved to an
7
+ explicit scoped-slot API instead (or, for two deeply-nested cases, to runtime
8
+ global component resolution — see below). All identified stubs are now
9
+ converted; the stub `.vue` files themselves have been deleted since nothing in
10
+ the SDK references them by import anymore.
11
+
12
+ - `EditBox.vue` (deleted)
13
+ - `EmissionPage.vue` — **Done** (`#edit-box` slot)
14
+ - `ParticipantPage.vue` — **Done** (`#edit-box` slot)
15
+ - `PlaylistPage.vue` — **Done** (`#edit-box` slot)
16
+ - `PodcastModuleBox.vue` — **Done** (`#edit-box` slot, passed through `PodcastPage.vue`)
17
+ - `EditBoxRadio.vue` (deleted)
18
+ - `RadioPage.vue` — **Done** (`#edit-box-radio` slot)
19
+ - `RecordingItemButton.vue` (deleted)
20
+ - `PodcastModuleBox.vue` — **Done** (`#recording-item-button` slot, passed through `PodcastPage.vue`)
21
+ - `RssSection.vue` (deleted) / `CommentMoreActionsAdmin.vue` (deleted)
22
+ - `ClipboardModal.vue`, `CommentMoreActions.vue` — **Done**, but via a different mechanism than the rest of this list. Both stubs are reached through multiple, and in the comments case recursive, layers of intermediate components (`ClipboardModal` is reached from 5 independent top-level pages via `ShareAnonymous`; `CommentMoreActions` is 4 layers deep under `CommentSection`/`CommentList`/`CommentItem`, which are self-recursive for comment replies). Threading scoped slots through every layer/entry point wasn't practical here, so instead the SDK leaves the bare `<RssSection>`/`<CommentMoreActionsAdmin>` tags unimported in `ClipboardModal.vue`/`CommentMoreActions.vue` and lets Vue resolve them at runtime against the consuming app's global component registry. The consuming app registers the real implementations once via `app.component("RssSection", ...)` / `app.component("CommentMoreActionsAdmin", ...)` (see frontoffice's `main.ts`). Apps that don't register them simply render nothing there (a dev-only console warning, stripped in prod).
23
+
24
+ ## Non-stub `@/`-alias collisions fixed via provide/inject
25
+
26
+ A follow-up audit found the same `@/`-alias collision affecting real (non-stub)
27
+ SDK code, not just placeholder components: SDK source importing a path via
28
+ `@/...` that happens to collide with an unrelated frontoffice file at the same
29
+ relative path, where the SDK genuinely depended on frontoffice's version to
30
+ work correctly. Unlike the component stubs, these aren't extension points by
31
+ design, and duplicating frontoffice's logic into the SDK isn't appropriate
32
+ (the real implementation is either backend-contract-specific or pulls in
33
+ frontoffice-only data), so both were fixed with Vue's `provide`/`inject`
34
+ instead: the SDK exposes an injection key with a safe default (its own
35
+ implementation), and frontoffice provides its richer implementation once at
36
+ the app root (`app.provide(...)` in `main.ts`, alongside the `app.component()`
37
+ calls above). Keys are defined in `src/components/composable/keys.ts` and
38
+ re-exported from the root `index.ts`.
39
+
40
+ - `api/initialize.ts` (`checkToken`)
41
+ - `RecaptchaModal.vue` — **Done**. SDK's own `api/initialize.ts` never defined `checkToken` (only frontoffice's override did), yet `RecaptchaModal.vue` called it — a live bug in waiting. Now resolved via `inject(CHECK_TOKEN_KEY, async () => true)`; the SDK no longer imports `@/api/initialize` at all. `sendMail` (frontoffice-only, unused by the SDK) was left untouched.
42
+ - `i18n.ts` (`loadLocaleMessages`)
43
+ - `FooterSection.vue` — **Done**. Frontoffice's `loadLocaleMessages` merges ~13 frontoffice-only locale bundles into the message set; SDK's own version only loads its base translations, and `setLocaleMessage` replaces rather than merges — so an un-aliased SDK call would have wiped most of the app's translated UI text on every language switch. Now resolved via `inject(LOAD_LOCALE_MESSAGES_KEY, loadSdkLocaleMessages)`, defaulting to the SDK's own loader.
package/eslint-config.mjs CHANGED
@@ -45,9 +45,6 @@ export default typescriptEslint.config(
45
45
  // Number of attributes per line (increase because sometimes two is not a lot)
46
46
  "vue/max-attributes-per-line": ['warn', { singleline: 2 } ],
47
47
 
48
- // Disable default values required for props
49
- "vue/require-default-prop": ['off'],
50
-
51
48
  "@typescript-eslint/no-unused-vars": "warn"
52
49
  }
53
50
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saooti/octopus-sdk",
3
- "version": "41.12.1-beta3",
3
+ "version": "41.12.1",
4
4
  "private": false,
5
5
  "description": "Javascript SDK for using octopus",
6
6
  "author": "Saooti",
@@ -1,5 +1,6 @@
1
1
  import { Podcast, PodcastProcessingStatus, PodcastType, SimplifiedPodcast } from '../stores/class/general/podcast';
2
2
  import { ListClassicReturn } from '../stores/class/general/listReturn';
3
+ import { useAuthStore } from '../stores/AuthStore';
3
4
  import classicApi from './classicApi';
4
5
  import { ModuleApi } from './apiConnection';
5
6
  import { unique } from '../helper/arrayHelper';
@@ -24,18 +24,6 @@ async function searchRubriquages(organisationIds: Array<string>, searchOptions?:
24
24
  });
25
25
  }
26
26
 
27
- /**
28
- * Fetch rubriquage data by ID
29
- * @param rubriquageId ID of the rubriquage to fetch
30
- * @returns The rubriquage
31
- */
32
- async function getRubriquage(rubriquageId: number): Promise<Rubriquage> {
33
- return classicApi.fetchData<Rubriquage>({
34
- api: ModuleApi.DEFAULT,
35
- path: `rubriquage/${rubriquageId}`
36
- });
37
- }
38
-
39
27
  async function searchRubriques(searchOptions?: {
40
28
  rubriquageId?: number;
41
29
  organisationId?: string|Array<string>;
@@ -73,7 +61,6 @@ async function getCachedRubrique(rubriqueId: number): Promise<Rubrique> {
73
61
  }
74
62
 
75
63
  export const rubriquesApi = {
76
- getRubriquage,
77
64
  getRubrique,
78
65
  getCachedRubrique,
79
66
  searchRubriquages,
@@ -1,21 +1,18 @@
1
1
  <template>
2
2
  <div class="d-flex flex-column list-episode">
3
- <h2 v-if="title" class="mb-0">
4
- {{ title }}
5
- </h2>
6
3
  <ClassicLoading
7
4
  :loading-text="loading ? t('Loading emissions ...') : undefined"
8
5
  />
9
6
  <SwiperList
10
7
  v-if="(displayRubriquage && rubriques) || !(displayRubriquage && loaded)"
11
- :size-item-overload="sizeItemOverload"
8
+ :size-item-overload="itemSize"
12
9
  :list-object="allEmissions"
13
10
  >
14
11
  <template #octopusSlide="{ option }">
15
12
  <EmissionPresentationItem
16
13
  v-if="emissionDisplay === 'simple'"
17
14
  :emission="option"
18
- class="inline-list-element"
15
+ class="mx-2 inline-list-element"
19
16
  is-description
20
17
  :is-vertical="emissionVertical"
21
18
  />
@@ -79,8 +76,6 @@ const props = defineProps<{
79
76
  rubriqueId?: number;
80
77
  /** Filter on rubriquage */
81
78
  rubriquageId?: number;
82
- /** Title of the section */
83
- title?: string;
84
79
  }>();
85
80
 
86
81
 
@@ -98,16 +93,6 @@ const {handle403} = useErrorHandler();
98
93
  //Computed
99
94
  const displayRubriquage = computed(() => state.emissionsPage.rubriquage);
100
95
 
101
- const sizeItemOverload = computed(() => {
102
- if (props.itemSize) {
103
- return props.itemSize;
104
- } else if (props.emissionDisplay === 'simple') {
105
- return 25;
106
- } else {
107
- return undefined;
108
- }
109
- })
110
-
111
96
  onMounted(()=>{
112
97
  fetchNext();
113
98
  if (displayRubriquage.value) {
@@ -181,8 +181,6 @@ const props = withDefaults(defineProps<{
181
181
  rubriqueFilter?: Array<RubriquageFilter>;
182
182
  /** The filter on groups */
183
183
  emissionGroups?: Array<EmissionGroup>;
184
- /** Force hide the emission group filter */
185
- hideEmissionGroups?: boolean;
186
184
  }>(), {
187
185
  sort: "DATE",
188
186
  monetisable: "UNDEFINED",
@@ -219,10 +217,6 @@ const filterStore = useFilterStore();
219
217
  const authStore = useAuthStore();
220
218
 
221
219
  onMounted(async() => {
222
- if (props.hideEmissionGroups) {
223
- showEmissionGroups.value = false;
224
- return;
225
- }
226
220
  // Only show emission groups if there are some
227
221
  const nbGroups = await groupsApi.count({
228
222
  organisationIds: [props.organisationId]
@@ -7,7 +7,7 @@
7
7
  <button
8
8
  v-for="paginateButton in buttonsLeft"
9
9
  :key="paginateButton.title"
10
- class="btn btn-paginate"
10
+ class="btn"
11
11
  :title="paginateButton.title"
12
12
  :disabled="paginateButton.disabled"
13
13
  @click="paginateButton.action"
@@ -23,10 +23,10 @@
23
23
  </svg>
24
24
  </button>
25
25
  <template v-for="pageNumber in pagination" :key="pageNumber">
26
- <span v-if="null === pageNumber" class="btn btn-min-width btn-paginate"> ... </span>
26
+ <span v-if="null === pageNumber" class="btn btn-min-width"> ... </span>
27
27
  <button
28
28
  v-else
29
- class="btn btn-min-width btn-paginate"
29
+ class="btn btn-min-width"
30
30
  :class="{ active: page === pageNumber - 1 }"
31
31
  @click="changeFirst((pageNumber - 1) * rowsPerPage)"
32
32
  >
@@ -36,7 +36,7 @@
36
36
  <button
37
37
  v-for="paginateButton in buttonsRight"
38
38
  :key="paginateButton.title"
39
- class="btn btn-min-width btn-paginate"
39
+ class="btn btn-min-width"
40
40
  :title="paginateButton.title"
41
41
  :disabled="paginateButton.disabled"
42
42
  @click="paginateButton.action"
@@ -176,7 +176,6 @@ function changeFirst(newFirst: number) {
176
176
  emit("update:first", newFirst);
177
177
  }
178
178
  </script>
179
-
180
179
  <style lang="scss">
181
180
 
182
181
  .octopus-app {
@@ -190,18 +189,15 @@ function changeFirst(newFirst: number) {
190
189
  padding: 0.5rem 0;
191
190
  z-index: 10;
192
191
 
193
- .btn.btn-paginate {
192
+ .btn {
194
193
  border-radius: 0;
195
- color: var(--octopus-btn-paginate-fg);
196
- background: var(--octopus-btn-paginate-bg);
197
194
 
198
195
  &.active {
199
- color: var(--octopus-btn-paginate-active-fg);
200
- background: var(--octopus-btn-paginate-active-bg);
196
+ background: var(--octopus-primary-more-transparent);
201
197
  }
202
198
  }
203
199
  }
204
-
200
+
205
201
  .module-box .paginate-fixed,
206
202
  .octopus-modal .paginate-fixed,
207
203
  .octopus-accordion .paginate-fixed {
@@ -4,7 +4,7 @@
4
4
  <swiper
5
5
  :key="manualReload"
6
6
  :slides-per-view="numberItem"
7
- :space-between="gapPx"
7
+ :space-between="0"
8
8
  :loop="loop"
9
9
  :slides-offset-before="offsetSwiper"
10
10
  :slides-offset-after="offsetSwiper"
@@ -47,8 +47,7 @@ const props = defineProps({
47
47
  sizeItemOverload: { default: undefined, type: Number },
48
48
  })
49
49
 
50
- //Data
51
- const gapPx = 10;
50
+ //Data
52
51
  const manualReload = ref(0);
53
52
  const numberItem = ref(5);
54
53
  const offsetSwiper = ref(0);
@@ -74,10 +73,7 @@ const sizeItem = computed(() => {
74
73
  ? state.generalParameters.podcastItem
75
74
  : 13.5;
76
75
  });
77
- const itemRecalculizedSize = computed(() => {
78
- const totalGap = gapPx * Math.max(numberItem.value - 1, 0);
79
- return (widthSwiperUsable.value - totalGap) / numberItem.value;
80
- });
76
+ const itemRecalculizedSize = computed(() => widthSwiperUsable.value / numberItem.value);
81
77
 
82
78
  /** Indicates that the swiper should loop */
83
79
  const loop = computed((): boolean => {
@@ -123,10 +119,10 @@ function onWindowResize(){
123
119
  const el = rootRef?.value as HTMLElement;
124
120
  if (!el) return;
125
121
  widthSwiperUsable.value =el.offsetWidth - offsetSwiper.value * 2;
126
- const itemSizePx = domHelper.convertRemToPixels(sizeItem.value + 0.5);
122
+ const sixteen = domHelper.convertRemToPixels(sizeItem.value + 0.5);
127
123
  numberItem.value = Math.max(
128
124
  1,
129
- Math.floor((widthSwiperUsable.value + gapPx) / (itemSizePx + gapPx)),
125
+ Math.floor(widthSwiperUsable.value / sixteen),
130
126
  );
131
127
  itemSizeWithoutRecalculed.value =el.offsetWidth / numberItem.value;
132
128
  }
@@ -157,7 +153,7 @@ function slideChange() {
157
153
  );
158
154
  wrapper.style.transform =
159
155
  "translate3d(" +
160
- (nbTransformItems * (itemRecalculizedSize.value + gapPx) + offsetSwiper.value) +
156
+ (nbTransformItems * itemRecalculizedSize.value + offsetSwiper.value) +
161
157
  "px, 0px, 0px)";
162
158
  }
163
159
  </script>
@@ -81,7 +81,6 @@ const props = withDefaults(defineProps<{
81
81
  before?: string;
82
82
  after?: string;
83
83
  includeHidden?: boolean;
84
- /** Enable the display of number of items in ListPaginate */
85
84
  showCount?: boolean;
86
85
  displaySortText?: boolean;
87
86
  /** Criteria to sort on */
@@ -57,34 +57,14 @@ import { podcastApi, PodcastSort } from "../../../api/podcastApi";
57
57
  import { usePresentationItem } from "../../composable/usePresentationItem";
58
58
 
59
59
  //Props
60
- const props = withDefaults(defineProps<{
61
- /**
62
- * ID of the organisation
63
- */
64
- organisationId?: string;
65
- /**
66
- * Title of the section
67
- */
68
- title?: string;
69
- /**
70
- * Link to the "more" section
71
- */
72
- href?: string;
73
- /**
74
- * Label for the "more" button
75
- */
76
- buttonText?: string;
77
- /**
78
- * Display podcasts exclusively from these rubriques
79
- */
80
- rubriquesId?: Array<number>;
81
- /**
82
- * Mode of retrieval for podcasts
83
- */
84
- retrievalMode?: 'by-emission'|'any';
85
- }>(), {
86
- retrievalMode: 'by-emission'
87
- });
60
+ const props = defineProps({
61
+ organisationId: { default: undefined, type: String },
62
+ title: { default: "", type: String },
63
+ href: { default: undefined, type: String },
64
+ buttonText: { default: undefined, type: String },
65
+ isDescription: { default: false, type: Boolean },
66
+ rubriquesId: { default: [], type: Array<number> },
67
+ })
88
68
 
89
69
  //Data
90
70
  const loading = ref(true);
@@ -112,16 +92,47 @@ watch(podcasts, async () => {
112
92
  async function fetchNext(): Promise<void> {
113
93
  loading.value = true;
114
94
  try {
115
- let func: () => Promise<Array<Podcast>>;
116
- if (props.retrievalMode === 'any') {
117
- func = fetchPodcasts;
118
- } else {
119
- func = fetchPodcastsByEmission;
95
+ // Retrieve latest emissions
96
+ const emissions = await classicApi.fetchData<ListClassicReturn<Emission>>({
97
+ api: 0,
98
+ path: "emission/search",
99
+ parameters: {
100
+ first: 0,
101
+ size: 5,
102
+ organisationId: props.organisationId,
103
+ sort: "LAST_PODCAST_DESC",
104
+ rubriqueId: props.rubriquesId
105
+ },
106
+ specialTreatement: true,
107
+ });
108
+
109
+ const promises: Array<Promise<SimplifiedPodcast>> = [];
110
+
111
+ for (let i = 0; i < emissions.result.length; i++) {
112
+ promises.push(podcastApi.search({
113
+ first: 0,
114
+ size: 1,
115
+ organisationId: [props.organisationId],
116
+ emissionId: [emissions.result[i].emissionId],
117
+ sort: PodcastSort.DATE,
118
+ rubriqueId: props.rubriquesId
119
+ }).then(r => r.result[0]));
120
120
  }
121
- const result = await func();
121
+
122
+ // Retrieve the podcasts for these emissions
123
+ const data = await Promise.all(promises);
124
+
125
+ podcasts.value = podcasts.value.concat(
126
+ data.filter((em: SimplifiedPodcast | null) => null !== em && undefined !== em).map(p => {
127
+ // Get emission from podcast
128
+ const emission = emissions.result.find(e => e.emissionId === p.emissionId);
129
+ // Create full podcast from simplified + emission
130
+ return simplifiedToFull(p, emission.orga, emission);
131
+ })
132
+ );
122
133
 
123
134
  // Sort podcasts by pub date so that the most recent one is focused
124
- podcasts.value = result.sort((p1, p2) => {
135
+ podcasts.value.sort((p1, p2) => {
125
136
  return new Date(p2.pubDate).getTime() - new Date(p1.pubDate).getTime();
126
137
  });
127
138
 
@@ -134,57 +145,6 @@ async function fetchNext(): Promise<void> {
134
145
  loading.value = false;
135
146
  }
136
147
 
137
- async function fetchPodcasts(): Promise<Array<Podcast>> {
138
- const response = await podcastApi.searchFull({
139
- first: 0,
140
- size: 5,
141
- organisationId: [props.organisationId],
142
- sort: PodcastSort.DATE,
143
- rubriqueId: props.rubriquesId
144
- }, true);
145
-
146
- return response.result;
147
- }
148
-
149
- async function fetchPodcastsByEmission(): Promise<Array<Podcast>> {
150
- // Retrieve latest emissions
151
- const emissions = await classicApi.fetchData<ListClassicReturn<Emission>>({
152
- api: 0,
153
- path: "emission/search",
154
- parameters: {
155
- first: 0,
156
- size: 5,
157
- organisationId: props.organisationId,
158
- sort: "LAST_PODCAST_DESC",
159
- rubriqueId: props.rubriquesId
160
- },
161
- specialTreatement: true
162
- });
163
-
164
- const promises: Array<Promise<SimplifiedPodcast>> = [];
165
-
166
- for (let i = 0; i < emissions.result.length; i++) {
167
- promises.push(podcastApi.search({
168
- first: 0,
169
- size: 1,
170
- organisationId: [props.organisationId],
171
- emissionId: [emissions.result[i].emissionId],
172
- sort: PodcastSort.DATE,
173
- rubriqueId: props.rubriquesId
174
- }, true).then(r => r.result[0]));
175
- }
176
-
177
- // Retrieve the podcasts for these emissions
178
- const data = await Promise.all(promises);
179
-
180
- return data.filter((em: SimplifiedPodcast | null) => null !== em && undefined !== em).map(p => {
181
- // Get emission from podcast
182
- const emission = emissions.result.find(e => e.emissionId === p.emissionId);
183
- // Create full podcast from simplified + emission
184
- return simplifiedToFull(p, emission.orga, emission);
185
- });
186
- }
187
-
188
148
  function route(podcast: Podcast): RouteLocationRaw {
189
149
  return {
190
150
  name: 'podcast',
@@ -215,6 +215,7 @@ defineExpose({ reset });
215
215
  position: relative;
216
216
  border-radius: var(--octopus-border-radius);
217
217
  overflow: hidden;
218
+ background: var(--octopus-secondary);
218
219
  transition: all 0.2s linear 0s;
219
220
 
220
221
  &.open{
@@ -44,7 +44,7 @@
44
44
  {{ name }}
45
45
  </div>
46
46
  <div
47
- v-if="!isPhone && (description || additionalInfo)"
47
+ v-if="!isPhone && description"
48
48
  ref="descriptionItemContainer"
49
49
  class="element-description htms-wysiwyg-content mt-0"
50
50
  >
@@ -59,7 +59,6 @@
59
59
  </div>
60
60
  <!-- eslint-disable vue/no-v-html -->
61
61
  <div
62
- v-if="description"
63
62
  ref="descriptionItem"
64
63
  v-html="urlify(description || '')"
65
64
  />
@@ -2,8 +2,11 @@
2
2
  A simple layout to display 5 elements over 3 columns
3
3
  -->
4
4
  <template>
5
- <div class="d-flex flex-column py-3">
6
- <h2 v-if="title">
5
+ <div class="d-flex flex-column p-3">
6
+ <h2
7
+ v-if="title"
8
+ class="mb-3"
9
+ >
7
10
  {{ title }}
8
11
  </h2>
9
12
 
@@ -98,10 +101,10 @@ defineProps<{
98
101
 
99
102
  .column {
100
103
  flex-shrink: 0;
101
- width: calc((100% - 420px - 1rem) / 2);
104
+ width: calc((100% - 420px) / 2);
102
105
 
103
106
  @media (width <= 1550px) {
104
- width: calc((100% - 420px - 1rem));
107
+ width: calc((100% - 420px));
105
108
  }
106
109
 
107
110
  @media (width <= 960px) {
@@ -14,10 +14,7 @@
14
14
  <script setup lang="ts">
15
15
  import { storeToRefs } from 'pinia';
16
16
  import { useNotificationStore } from '../../stores/NotificationStore';
17
-
18
- const MessageModal = defineAsyncComponent(() => import('./modal/MessageModal.vue'));
19
-
20
- import { defineAsyncComponent } from 'vue';
17
+ import MessageModal from './modal/MessageModal.vue';
21
18
 
22
19
  const {
23
20
  clearNotification
@@ -1,5 +1,6 @@
1
1
  <template>
2
2
  <section v-if="isInit" class="page-box">
3
+ <!-- TODO à intégrer dans frontoffice -->
3
4
  <router-link
4
5
  v-if="isRolePlaylists && !isPodcastmaker"
5
6
  to="/main/priv/edit/playlist"
@@ -14,17 +14,28 @@ export default {
14
14
  }
15
15
  }
16
16
  xhr.responseType = "blob";
17
- xhr.onload = function () {
18
- const urlCreator = window.URL || window.webkitURL;
19
- const imageUrl = urlCreator.createObjectURL(this.response);
20
- const tag = document.createElement("a");
21
- tag.href = imageUrl;
22
- tag.target = "_blank";
23
- tag.download = nameOfDownload.replace(/ /g, "_");
24
- document.body.appendChild(tag);
25
- tag.click();
26
- document.body.removeChild(tag);
27
- };
28
- xhr.send();
17
+
18
+ return new Promise<void>((resolve, reject) => {
19
+ xhr.onload = function () {
20
+ if (xhr.status !== 200) {
21
+ reject(new Error(`Download failed with status ${xhr.status}`));
22
+ return;
23
+ }
24
+ const urlCreator = window.URL || window.webkitURL;
25
+ const imageUrl = urlCreator.createObjectURL(this.response);
26
+ const tag = document.createElement("a");
27
+ tag.href = imageUrl;
28
+ tag.target = "_blank";
29
+ tag.download = nameOfDownload.replace(/ /g, "_");
30
+ document.body.appendChild(tag);
31
+ tag.click();
32
+ document.body.removeChild(tag);
33
+ resolve();
34
+ };
35
+ xhr.onerror = function () {
36
+ reject(new Error("Download failed"));
37
+ };
38
+ xhr.send();
39
+ });
29
40
  },
30
41
  };
@@ -227,8 +227,7 @@ export const useAuthStore = defineStore("AuthStore", {
227
227
  });
228
228
  this.authUpdateOrganisation(activeOrganisation);
229
229
  this.fetchProfileAsynchrone();
230
- } catch(error) {
231
- console.error(error);
230
+ } catch {
232
231
  if (this.authReload > 5) {
233
232
  return;
234
233
  }
@@ -15,8 +15,6 @@ interface Notification {
15
15
  type: NotificationType;
16
16
  /** When set to false, disallow manual closing of modal notification (default: true) */
17
17
  closeable?: boolean;
18
- /** Callback called when the notification is closed */
19
- closeCallback?: () => void;
20
18
  }
21
19
 
22
20
  export const useNotificationStore = defineStore('notifications', () => {
@@ -36,10 +34,6 @@ export const useNotificationStore = defineStore('notifications', () => {
36
34
  if (notif !== null) {
37
35
  const idx = notificationQueue.value.indexOf(notif);
38
36
  notificationQueue.value.splice(idx, 1);
39
-
40
- if (notif.closeCallback) {
41
- notif.closeCallback();
42
- }
43
37
  }
44
38
  }
45
39
 
@@ -46,17 +46,11 @@
46
46
  // Buttons
47
47
  --octopus-btn-primary-bg: var(--octopus-primary);
48
48
  --octopus-btn-primary-fg: var(--octopus-color-on-primary);
49
- --octopus-btn-secondary-bg: var(--octopus-secondary);
50
- --octopus-btn-secondary-fg: black;
51
49
  --octopus-btn-play-bg: var(--octopus-primary-less-transparent);
52
50
  --octopus-btn-play-fg: white;
53
51
  --octopus-btn-play-radius: var(--octopus-border-radius);
54
52
  --octopus-btn-social-bg: var(--octopus-secondary);
55
53
  --octopus-btn-social-fg: var(--octopus-primary);
56
- --octopus-btn-paginate-fg: var(--octopus-btn-secondary-fg);
57
- --octopus-btn-paginate-bg: var(--octopus-btn-secondary-bg);
58
- --octopus-btn-paginate-active-fg: var(--octopus-btn-paginate-fg);
59
- --octopus-btn-paginate-active-bg: var(--octopus-primary-more-transparent);
60
54
 
61
55
  // Player
62
56
  // Color for the transcript background
@@ -88,8 +88,7 @@ input:not([class^="vs__"]), button:not([class^="vs__"]), select:not([class^="vs_
88
88
  transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
89
89
  font-size:.7rem;
90
90
  border-radius: var(--octopus-border-radius);
91
- color: var(--octopus-btn-secondary-fg);
92
- background: var(--octopus-btn-secondary-bg);
91
+ background: var(--octopus-secondary);
93
92
  text-decoration: none !important;
94
93
  white-space: nowrap;
95
94
  border-width: 0;
@@ -28,9 +28,9 @@ describe('PresentationItem', () => {
28
28
  expect(wrapper.find('.text-secondary').exists()).toBe(false);
29
29
  });
30
30
 
31
- it('renders each additionalInfo even when there is no description', async () => {
31
+ it('does not render additionalInfo when there is no description', async () => {
32
32
  const wrapper = await mount({ additionalInfo: ['Saooti'] });
33
- expect(wrapper.find('.text-secondary').exists()).toBe(true);
33
+ expect(wrapper.find('.text-secondary').exists()).toBe(false);
34
34
  });
35
35
  });
36
36
  });
package/BUILD_FIX.md DELETED
@@ -1,105 +0,0 @@
1
- # Fixing `@` alias resolution when consumed by other projects
2
-
3
- ## Problem
4
-
5
- `octopus-sdk` currently publishes raw TypeScript source. When a consumer (e.g. frontoffice)
6
- bundles the SDK, it processes source files with its own Vite config, so `@/` imports resolve
7
- against the consumer's `src/` instead of the SDK's. This affects both dev and production builds.
8
-
9
- ## Solution: build the SDK as a library
10
-
11
- The SDK should be pre-built so all `@` aliases are resolved before any consumer sees the files.
12
-
13
- ---
14
-
15
- ## 1. Install `vite-plugin-dts`
16
-
17
- ```sh
18
- npm install -D vite-plugin-dts
19
- ```
20
-
21
- ---
22
-
23
- ## 2. Update `vite.config.js`
24
-
25
- Add a `lib` build alongside the existing app config. The alias resolution in the `resolve` block
26
- ensures `@` is rewritten to relative paths in the output.
27
-
28
- ```js
29
- import dts from 'vite-plugin-dts';
30
-
31
- // In the build config:
32
- build: {
33
- lib: {
34
- entry: path.resolve(__dirname, 'index.ts'),
35
- formats: ['es'],
36
- fileName: 'index',
37
- },
38
- outDir: 'dist',
39
- rollupOptions: {
40
- // Mark all dependencies as external so they are not bundled
41
- external: (id) => !id.startsWith('.') && !path.isAbsolute(id),
42
- },
43
- },
44
- plugins: [
45
- vue(),
46
- dts({ rollupTypes: true }),
47
- ],
48
- ```
49
-
50
- Run `npm run build` to produce `dist/index.js` and `dist/index.d.ts`.
51
-
52
- ---
53
-
54
- ## 3. Update `package.json`
55
-
56
- Point `exports` to the built output instead of raw source:
57
-
58
- ```json
59
- "exports": {
60
- ".": {
61
- "types": "./dist/index.d.ts",
62
- "default": "./dist/index.js"
63
- }
64
- },
65
- "files": ["dist"]
66
- ```
67
-
68
- ---
69
-
70
- ## 4. Development workflow
71
-
72
- With a built package, `npm link` / `yalc link` alone is no longer enough — consumers need the
73
- built output, not the source.
74
-
75
- **Option A — watch build (recommended for active SDK development):**
76
-
77
- ```sh
78
- # Terminal 1 — SDK
79
- npm run build -- --watch
80
-
81
- # Terminal 2 — frontoffice
82
- npm run dev
83
- ```
84
-
85
- frontoffice picks up changes via the symlink each time the SDK rebuild completes.
86
- HMR in frontoffice will trigger on the rebuilt file, not on source edits directly.
87
-
88
- **Option B — yalc push on change:**
89
-
90
- ```sh
91
- # In octopus-sdk, after each change:
92
- npm run build && yalc push
93
- ```
94
-
95
- frontoffice receives the new build automatically if `yalc` is configured with `--watch`.
96
-
97
- ---
98
-
99
- ## What to revert in frontoffice
100
-
101
- Once the SDK is built, remove from frontoffice's `vite.config.js`:
102
-
103
- - `optimizeDeps.exclude: ['@saooti/octopus-sdk']` — no longer needed
104
- - `server.watch.ignored: ['!**/node_modules/@saooti/octopus-sdk/**']` — no longer needed
105
- - `server.fs.allow: ['../../../octopus-sdk']` — no longer needed (for npm link setups)