orionplace-nuxt 0.1.2 → 0.1.3
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/README.md +52 -2
- package/bin/orionplace-nuxt.mjs +24 -9
- package/package.json +3 -2
- package/src/project-readme.mjs +64 -29
- package/src/setup.mjs +37 -7
- package/src/update.mjs +36 -0
- package/templates/app/app.vue +90 -0
- package/templates/app/composables/useOrionplaceAppLoad.ts +51 -0
- package/templates/app/interfaces/orionplace/apiBase/TMethod.ts +1 -0
- package/templates/app/interfaces/orionplace/appBase/IAppLoad.ts +15 -0
- package/templates/app/interfaces/orionplace/appBase/IAppLoadCommon.ts +54 -0
- package/templates/app/interfaces/orionplace/appBase/IApploadUserProfile.ts +13 -0
- package/templates/app/interfaces/orionplace/appBase/IForm.ts +21 -0
- package/templates/app/interfaces/orionplace/appBase/ISEO.ts +21 -0
- package/templates/app/interfaces/orionplace/appBase/appLoadCommon/IAppLoadCommonBanner.ts +11 -0
- package/templates/app/interfaces/orionplace/appBase/appLoadCommon/IAppLoadCommonContact.ts +8 -0
- package/templates/app/interfaces/orionplace/appBase/appLoadCommon/IAppLoadCommonCurrency.ts +15 -0
- package/templates/app/interfaces/orionplace/appBase/appLoadCommon/IAppLoadCommonEvents.ts +18 -0
- package/templates/app/interfaces/orionplace/appBase/appLoadCommon/IAppLoadCommonLocale.ts +14 -0
- package/templates/app/interfaces/orionplace/appBase/appLoadCommon/IAppLoadCommonLogos.ts +6 -0
- package/templates/app/interfaces/orionplace/appBase/appLoadCommon/IAppLoadCommonPath.ts +22 -0
- package/templates/app/interfaces/orionplace/appBase/appLoadCommon/IAppLoadCommonSEO.ts +13 -0
- package/templates/app/interfaces/orionplace/appBase/appLoadCommon/IAppLoadCommonSocial.ts +15 -0
- package/templates/app/interfaces/orionplace/appBase/appLoadCommon/IAppLoadCommonTariff.ts +7 -0
- package/templates/app/interfaces/orionplace/appBase/appLoadCommon/IAppLoadCommonUnit.ts +12 -0
- package/templates/app/interfaces/orionplace/appBase/appLoadCommon/IAppLoadCommonVariables.ts +7 -0
- package/templates/app/interfaces/orionplace/appBase/appLoadCommon/IAppLoadCommonVisualSettings.ts +11 -0
- package/templates/app/interfaces/orionplace/appBase/appLoadCommon/IAppLoadCommonWalletValue.ts +3 -0
- package/templates/app/interfaces/orionplace/category/ICategory.ts +40 -0
- package/templates/app/interfaces/orionplace/domains/IDomain.ts +13 -0
- package/templates/app/interfaces/orionplace/index.ts +29 -0
- package/templates/app/interfaces/orionplace/products/IProduct.ts +235 -0
- package/templates/app/interfaces/orionplace/shared/IGeoCoordinates.ts +4 -0
- package/templates/app/interfaces/orionplace/shared/ITransformValue.ts +3 -0
- package/templates/app/interfaces/orionplace/subscribeOptions/ISubscribeOption.ts +9 -0
- package/templates/app/interfaces/orionplace/subscribeTariff/ISubscribeTariff.ts +46 -0
- package/templates/app/interfaces/orionplace/subscribes/ISubscribe.ts +28 -0
- package/templates/app/interfaces/orionplace/transport.ts +30 -0
- package/templates/app/utils/orionplaceAppLoad.ts +33 -0
package/README.md
CHANGED
|
@@ -62,6 +62,7 @@ to inspect planned changes without writing them.
|
|
|
62
62
|
|
|
63
63
|
```bash
|
|
64
64
|
npx orionplace-nuxt init [directory]
|
|
65
|
+
npx orionplace-nuxt update
|
|
65
66
|
npx orionplace-nuxt set backend your-project.orionplace.app
|
|
66
67
|
npx orionplace-nuxt add frontend shop.example.com
|
|
67
68
|
npx orionplace-nuxt add frontend www.shop.example.com
|
|
@@ -79,6 +80,7 @@ npm run orionplace:add-frontend -- shop.example.com
|
|
|
79
80
|
npm run orionplace:remove-frontend -- www.shop.example.com
|
|
80
81
|
npm run orionplace:mimic -- shop.example.com
|
|
81
82
|
npm run orionplace:help
|
|
83
|
+
npm run orionplace:update
|
|
82
84
|
```
|
|
83
85
|
|
|
84
86
|
`npm set` belongs to npm's own configuration system; use the commands above.
|
|
@@ -102,7 +104,7 @@ change your account, create DNS records, configure TLS, or deploy a VPS.
|
|
|
102
104
|
The module executes the generated local runtime files. You can read and customize
|
|
103
105
|
them. Later setup runs preserve edited runtime files and the project README,
|
|
104
106
|
reporting which files were preserved. Unchanged generated files can be refreshed
|
|
105
|
-
|
|
107
|
+
with `npx orionplace-nuxt update`. A manually edited domain file is
|
|
106
108
|
never silently overwritten. Keep the hash manifest in version control.
|
|
107
109
|
|
|
108
110
|
## Multiple frontend domains
|
|
@@ -174,4 +176,52 @@ npm pack --dry-run
|
|
|
174
176
|
```
|
|
175
177
|
|
|
176
178
|
The published tarball includes only the CLI, module, runtime source, README,
|
|
177
|
-
license, and package metadata.
|
|
179
|
+
license, starter templates, and package metadata.
|
|
180
|
+
|
|
181
|
+
## AppLoad starter
|
|
182
|
+
|
|
183
|
+
The CLI generates an AppLoad debug `app/app.vue`, an SSR-aware
|
|
184
|
+
`useOrionplaceAppLoad()` composable, validation helpers and the full nested
|
|
185
|
+
TypeScript contract under `app/interfaces/orionplace/`. The first page loads
|
|
186
|
+
`POST /api/appload`, shows request state and section counts, and supports reload.
|
|
187
|
+
It preserves custom apps and edited generated files. Generated instructions in
|
|
188
|
+
`orionplace/README.md` explain how to integrate the composable in an existing app.
|
|
189
|
+
|
|
190
|
+
## Update an existing project
|
|
191
|
+
|
|
192
|
+
After the initial setup, use one command to get the latest package and components:
|
|
193
|
+
|
|
194
|
+
```bash
|
|
195
|
+
npx orionplace-nuxt update
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
It runs the latest CLI, adds new template files, refreshes unchanged generated
|
|
199
|
+
files, updates the package dependency and runs `npm install` to synchronize the
|
|
200
|
+
lockfile and installed dependencies. Edited files are preserved and listed in the
|
|
201
|
+
output. Review their new versions under `node_modules/orionplace-nuxt/templates`
|
|
202
|
+
or `runtime` and merge your changes manually when needed. Removed upstream files
|
|
203
|
+
are retained locally; the updater does not delete customer source.
|
|
204
|
+
|
|
205
|
+
Domain mappings, domain settings and `.env` remain unchanged. The command does
|
|
206
|
+
not deploy your application: restart development, or rebuild and redeploy the
|
|
207
|
+
production frontend after updating.
|
|
208
|
+
|
|
209
|
+
```bash
|
|
210
|
+
npx orionplace-nuxt update --dry-run
|
|
211
|
+
npm run orionplace:update
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
Dry-run downloads the latest CLI into npm's cache to inspect its templates but
|
|
215
|
+
leaves project files and dependencies unchanged. `update --local` applies the
|
|
216
|
+
version of the CLI being executed instead of fetching the latest CLI; it still
|
|
217
|
+
installs project dependencies. Use this only when intentionally selecting a version.
|
|
218
|
+
|
|
219
|
+
For a project on 0.1.2 or older, bootstrap the new command once:
|
|
220
|
+
|
|
221
|
+
```bash
|
|
222
|
+
npx --yes orionplace-nuxt@latest update
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
Updates require the existing `orionplace/settings.json` and `.generated.json`.
|
|
226
|
+
Conflicting untracked files stop the source update before any writes. If dependency
|
|
227
|
+
installation fails after source generation, run `npm install` and retry `update`.
|
package/bin/orionplace-nuxt.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { setupProject } from '../src/setup.mjs';
|
|
3
|
+
import { updateProject } from '../src/update.mjs';
|
|
3
4
|
import { readFile } from 'node:fs/promises';
|
|
4
5
|
import { resolve } from 'node:path';
|
|
5
6
|
import { createInterface } from 'node:readline/promises';
|
|
@@ -8,6 +9,7 @@ const help = `Orionplace Nuxt setup
|
|
|
8
9
|
|
|
9
10
|
npx orionplace-nuxt help
|
|
10
11
|
npx orionplace-nuxt init [directory]
|
|
12
|
+
npx orionplace-nuxt update
|
|
11
13
|
npx orionplace-nuxt set backend <personal-backend.orionplace.app>
|
|
12
14
|
npx orionplace-nuxt add frontend <shop.example.com>
|
|
13
15
|
npx orionplace-nuxt remove frontend <shop.example.com>
|
|
@@ -15,7 +17,10 @@ const help = `Orionplace Nuxt setup
|
|
|
15
17
|
npx orionplace-nuxt set frontend <shop.example.com>
|
|
16
18
|
|
|
17
19
|
Options: --cwd <directory> --dry-run --help --version
|
|
20
|
+
Update option: --local (apply this CLI version instead of fetching the latest release)
|
|
18
21
|
|
|
22
|
+
Update downloads the latest release, refreshes unedited source, and installs dependencies.
|
|
23
|
+
It preserves edited source, domain mappings and MIMIC. --dry-run leaves project files unchanged.
|
|
19
24
|
Set backend or add frontend also initializes the integration; either can come first.
|
|
20
25
|
MIMIC starts empty. The first added frontend sets it; later additions preserve it.
|
|
21
26
|
Set mimic selects an existing frontend without changing any domain mappings.
|
|
@@ -33,8 +38,10 @@ try {
|
|
|
33
38
|
const positional = [];
|
|
34
39
|
let cwd = process.cwd();
|
|
35
40
|
let dryRun = false;
|
|
41
|
+
let local = false;
|
|
36
42
|
for (let index = 0; index < argv.length; index++) {
|
|
37
43
|
if (argv[index] === '--dry-run') dryRun = true;
|
|
44
|
+
else if (argv[index] === '--local') local = true;
|
|
38
45
|
else if (argv[index] === '--cwd') {
|
|
39
46
|
if (!argv[index + 1]) throw new Error('--cwd needs a directory');
|
|
40
47
|
cwd = resolve(argv[++index]);
|
|
@@ -42,9 +49,12 @@ try {
|
|
|
42
49
|
else positional.push(argv[index]);
|
|
43
50
|
}
|
|
44
51
|
const [command, target] = positional;
|
|
52
|
+
if (local && command !== 'update') throw new Error('--local is only supported by update.');
|
|
45
53
|
let result;
|
|
46
54
|
if (command === 'init' && positional.length <= 2) {
|
|
47
55
|
result = await setupProject({ cwd: target ? resolve(cwd, target) : cwd, dryRun });
|
|
56
|
+
} else if (command === 'update' && positional.length === 1) {
|
|
57
|
+
result = await updateProject({ cwd, dryRun, local });
|
|
48
58
|
} else if (((command === 'set' && ['backend', 'frontend', 'mimic'].includes(target)) ||
|
|
49
59
|
(['add', 'remove'].includes(command) && target === 'frontend')) && positional.length <= 3) {
|
|
50
60
|
let value = positional[2];
|
|
@@ -56,15 +66,20 @@ try {
|
|
|
56
66
|
if (!value) throw new Error(`Provide the ${target} domain. See --help.`);
|
|
57
67
|
result = await setupProject({ cwd, action: command, field: target, value, dryRun });
|
|
58
68
|
} else throw new Error('Unknown command. See --help.');
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
69
|
+
if (result) {
|
|
70
|
+
console.log(`${dryRun ? 'Planned' : 'Updated'} ${result.root}`);
|
|
71
|
+
for (const path of result.changed) console.log(` ${result.removed.includes(path) ? 'Remove: ' : ''}${path}`);
|
|
72
|
+
for (const path of result.preserved) console.log(` Preserved your edits: ${path}`);
|
|
73
|
+
console.log(result.settings.backend && result.settings.frontend
|
|
74
|
+
? `Ready: ${result.settings.frontends.join(', ')} -> ${result.settings.backend}\nLocal MIMIC: ${result.settings.frontend}`
|
|
75
|
+
: `Next: ${result.settings.backend ? 'add frontend' : 'set backend'} to complete the domain mapping.`);
|
|
76
|
+
if (result.changed.some(path => path.endsWith('.cfg') || path === '.env')) console.log('Restart a running development server to refresh allowed hosts and MIMIC. Reload your production environment when changing MIMIC.');
|
|
77
|
+
if (result.isNewNuxt) console.log('Run npm install in the project, then npm run dev.');
|
|
78
|
+
console.log('Project documentation: orionplace/README.md');
|
|
79
|
+
if (command === 'update') console.log(dryRun
|
|
80
|
+
? 'Dependency installation would run after applying these changes. Project files were not changed.'
|
|
81
|
+
: 'Update complete. Restart development, or rebuild and redeploy your production frontend.');
|
|
82
|
+
}
|
|
68
83
|
}
|
|
69
84
|
} catch (error) {
|
|
70
85
|
console.error(`Orionplace: ${error.message}`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "orionplace-nuxt",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Nuxt integration and setup CLI for your Orionplace personal marketplace backend.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -31,11 +31,12 @@
|
|
|
31
31
|
"bin",
|
|
32
32
|
"src",
|
|
33
33
|
"runtime",
|
|
34
|
+
"templates",
|
|
34
35
|
"README.md",
|
|
35
36
|
"LICENSE"
|
|
36
37
|
],
|
|
37
38
|
"scripts": {
|
|
38
|
-
"test": "node --test test/*.test.mjs"
|
|
39
|
+
"test": "node --experimental-strip-types --test test/*.test.mjs"
|
|
39
40
|
},
|
|
40
41
|
"peerDependencies": {
|
|
41
42
|
"nuxt": "^4.0.0"
|
package/src/project-readme.mjs
CHANGED
|
@@ -98,53 +98,88 @@ In another terminal:
|
|
|
98
98
|
\`\`\`bash
|
|
99
99
|
curl -i http://localhost:3000/api/appload \\
|
|
100
100
|
-H 'Content-Type: application/json' \\
|
|
101
|
-
--data '{"ignore_cache":true,"detalize_mode":"
|
|
101
|
+
--data '{"ignore_cache":true,"detalize_mode":"light"}'
|
|
102
102
|
\`\`\`
|
|
103
103
|
|
|
104
104
|
Check both the HTTP status and \`form_errors\`. Successful initialization returns
|
|
105
105
|
\`page_data\`; HTTP 200 by itself is insufficient. Documentation and public requests
|
|
106
106
|
do not require a login prompt. Protected actions still require backend permission.
|
|
107
107
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
108
|
+
## AppLoad initialization
|
|
109
|
+
|
|
110
|
+
The generated \`app/app.vue\` calls \`useOrionplaceAppLoad()\` and displays loading,
|
|
111
|
+
success or failure, HTTP status, session state and counts of returned sections.
|
|
112
|
+
Use **Reload AppLoad** to issue a fresh request. A failed request never opens a
|
|
113
|
+
login modal. Once the connection works, replace this debug page with your storefront.
|
|
114
|
+
|
|
115
|
+
| Project file | Purpose |
|
|
116
|
+
| --- | --- |
|
|
117
|
+
| \`app/composables/useOrionplaceAppLoad.ts\` | SSR-aware initialization and manual reload. |
|
|
118
|
+
| \`app/utils/orionplaceAppLoad.ts\` | HTTP, form-error and bootstrap-envelope checks. |
|
|
119
|
+
| \`app/interfaces/orionplace/index.ts\` | All AppLoad interfaces and nested model exports. |
|
|
120
|
+
| \`app/interfaces/orionplace/transport.ts\` | Request, response, result and error contracts. |
|
|
121
|
+
|
|
122
|
+
The interfaces cover initialization settings, locales, currencies, units, catalog,
|
|
123
|
+
products, banners, events, tariffs and subscriptions, API discovery and forms,
|
|
124
|
+
and the optional signed-in profile. Plugin-defined JSON remains explicitly typed
|
|
125
|
+
as JSON; interfaces are not a full runtime schema validator.
|
|
126
|
+
|
|
127
|
+
The server uses the current request's domain and session. Its result is reused
|
|
128
|
+
during browser hydration, and new response cookies are relayed to the browser.
|
|
129
|
+
The page response uses \`private, no-store\`; do not put personalized pages behind
|
|
130
|
+
shared HTML caches. Reloading replaces the current session's discovery data.
|
|
131
|
+
|
|
132
|
+
Requests use \`detalize_mode: "light"\` and \`ignore_cache: true\`. Tenant settings
|
|
133
|
+
can override these options, and individual initialization sections have separate
|
|
134
|
+
backend caches. Full endpoint descriptions are therefore optional.
|
|
120
135
|
|
|
121
|
-
|
|
122
|
-
|
|
136
|
+
If your project already has a custom \`app/app.vue\`, the CLI preserves it.
|
|
137
|
+
Integrate the generated composable explicitly:
|
|
123
138
|
|
|
124
139
|
\`\`\`vue
|
|
125
|
-
<script setup>
|
|
126
|
-
|
|
127
|
-
const { data, error } = await
|
|
128
|
-
|
|
129
|
-
method: 'POST',
|
|
130
|
-
body: { ignore_cache: true, detalize_mode: 'full' },
|
|
131
|
-
}),
|
|
132
|
-
);
|
|
140
|
+
<script setup lang="ts">
|
|
141
|
+
import { useOrionplaceAppLoad } from './composables/useOrionplaceAppLoad';
|
|
142
|
+
const { data, status, error, refresh } = await useOrionplaceAppLoad();
|
|
143
|
+
// data.value?.response.page_data contains the typed initialization and discovery.
|
|
133
144
|
</script>
|
|
134
145
|
|
|
135
146
|
<template>
|
|
136
147
|
<main>
|
|
137
|
-
<p
|
|
138
|
-
<
|
|
148
|
+
<p role="status">AppLoad: {{ status }}</p>
|
|
149
|
+
<p v-if="error" role="alert">Marketplace initialization failed.</p>
|
|
150
|
+
<button type="button" :disabled="status === 'pending'" @click="refresh()">Reload AppLoad</button>
|
|
151
|
+
<p v-if="data">HTTP {{ data.httpStatus }}</p>
|
|
139
152
|
</main>
|
|
140
153
|
</template>
|
|
141
154
|
\`\`\`
|
|
142
155
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
156
|
+
Call the composable once in your app root and pass the data to your components.
|
|
157
|
+
The generated debug view displays section summaries; it does not render profile
|
|
158
|
+
fields, keys or backend exception stacks. The typed response is still available
|
|
159
|
+
to your application, so avoid logging or publishing it indiscriminately.
|
|
160
|
+
|
|
161
|
+
CLI reruns preserve edited generated source. Review updated package templates
|
|
162
|
+
manually when you have customized a file. Existing custom apps are not replaced.
|
|
163
|
+
|
|
164
|
+
## Update the package and components
|
|
165
|
+
|
|
166
|
+
Run this from your project root whenever you want the latest release:
|
|
167
|
+
|
|
168
|
+
\`\`\`bash
|
|
169
|
+
npx orionplace-nuxt update
|
|
170
|
+
\`\`\`
|
|
147
171
|
|
|
172
|
+
The command fetches the latest CLI, adds new template files, refreshes unchanged
|
|
173
|
+
generated source, updates the package dependency, and runs \`npm install\`.
|
|
174
|
+
It preserves edited files and lists them for manual review. Find their incoming
|
|
175
|
+
versions under \`node_modules/orionplace-nuxt/templates\` or \`runtime\`.
|
|
176
|
+
Domain mappings, domain settings and \`.env\` are preserved. Upstream file removals
|
|
177
|
+
do not delete local source. Restart development or rebuild and redeploy afterward.
|
|
178
|
+
|
|
179
|
+
Use \`npx orionplace-nuxt update --dry-run\` to review changes without modifying
|
|
180
|
+
project files or installed dependencies; downloading the CLI may update npm's
|
|
181
|
+
cache. The alias \`npm run orionplace:update\` performs the same normal update.
|
|
182
|
+
If installation fails after source generation, run \`npm install\` and retry.
|
|
148
183
|
|
|
149
184
|
## Deploy on your VPS
|
|
150
185
|
|
package/src/setup.mjs
CHANGED
|
@@ -57,7 +57,8 @@ function updateEnvironment(source, domain) {
|
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
export async function setupProject({ cwd = process.cwd(), action = 'set', field, value, dryRun = false } = {}) {
|
|
60
|
-
if (!['set', 'add', 'remove'].includes(action) ||
|
|
60
|
+
if (!['set', 'add', 'remove', 'update'].includes(action) ||
|
|
61
|
+
(['add', 'remove'].includes(action) && field !== 'frontend') || (action === 'update' && field)) {
|
|
61
62
|
throw new Error('Use set backend, add frontend, remove frontend, set mimic, or set frontend.');
|
|
62
63
|
}
|
|
63
64
|
const root = resolve(cwd);
|
|
@@ -68,6 +69,9 @@ export async function setupProject({ cwd = process.cwd(), action = 'set', field,
|
|
|
68
69
|
for (const name of names) current[name] = await read(await safePath(root, name));
|
|
69
70
|
const pkg = current['package.json'] ? JSON.parse(current['package.json']) : { name: 'orionplace-storefront', private: true, type: 'module' };
|
|
70
71
|
const dependencies = { ...pkg.devDependencies, ...pkg.dependencies };
|
|
72
|
+
if (action === 'update' && (!dependencies.nuxt || !current['orionplace/settings.json'] || !current['orionplace/.generated.json'])) {
|
|
73
|
+
throw new Error('Update requires an initialized Orionplace Nuxt project. Run init first.');
|
|
74
|
+
}
|
|
71
75
|
if (!dependencies.nuxt && ['next', '@sveltejs/kit'].some(name => dependencies[name])) throw new Error('This package sets up Nuxt projects only.');
|
|
72
76
|
if (dependencies.nuxt && !/^(?:[~^]|>=)?4(?:\.|$)/.test(dependencies.nuxt)) {
|
|
73
77
|
throw new Error('This release supports Nuxt 4. Upgrade your Nuxt dependency before setup.');
|
|
@@ -151,27 +155,53 @@ export async function setupProject({ cwd = process.cwd(), action = 'set', field,
|
|
|
151
155
|
pkg.scripts['orionplace:remove-frontend'] ||= 'orionplace-nuxt remove frontend';
|
|
152
156
|
pkg.scripts['orionplace:mimic'] ||= 'orionplace-nuxt set mimic';
|
|
153
157
|
pkg.scripts['orionplace:help'] ||= 'orionplace-nuxt help';
|
|
158
|
+
pkg.scripts['orionplace:update'] ||= 'orionplace-nuxt update';
|
|
154
159
|
pkg.dependencies ||= {};
|
|
155
160
|
pkg.dependencies['orionplace-nuxt'] ||= dependencies['orionplace-nuxt'] || `^${metadata.version}`;
|
|
161
|
+
if (action === 'update') {
|
|
162
|
+
pkg.dependencies['orionplace-nuxt'] = metadata.version;
|
|
163
|
+
if (pkg.devDependencies) delete pkg.devDependencies['orionplace-nuxt'];
|
|
164
|
+
}
|
|
156
165
|
if (isNewNuxt) {
|
|
157
166
|
pkg.dependencies.nuxt = '^4.5.0';
|
|
158
167
|
pkg.dependencies.vue ||= '^3.5.0';
|
|
159
168
|
pkg.dependencies['vue-router'] ||= '^5.0.0';
|
|
160
169
|
}
|
|
170
|
+
// Install the complete editable AppLoad source. Never replace a customer's app.
|
|
171
|
+
const appPath = 'app/app.vue';
|
|
172
|
+
const existingApp = await read(await safePath(root, appPath));
|
|
173
|
+
const installApp = existingApp === null || Boolean(previous[appPath]);
|
|
174
|
+
const templateRoot = new URL('../templates/', import.meta.url);
|
|
175
|
+
async function planTemplates(directory = '') {
|
|
176
|
+
for (const entry of await readdir(new URL(directory, templateRoot), { withFileTypes: true })) {
|
|
177
|
+
const name = directory + entry.name;
|
|
178
|
+
if (entry.isDirectory()) { await planTemplates(name + '/'); continue; }
|
|
179
|
+
if (name === appPath && !installApp) continue;
|
|
180
|
+
await plan(name, await readFile(new URL(name, templateRoot), 'utf8'), { managed: true, preserve: true });
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
await planTemplates();
|
|
184
|
+
if (installApp && !dependencies.sass && !dependencies['sass-embedded']) {
|
|
185
|
+
pkg.devDependencies ||= {};
|
|
186
|
+
pkg.devDependencies.sass = '^1.102.0';
|
|
187
|
+
}
|
|
188
|
+
if (!await read(await safePath(root, 'tsconfig.json'))) {
|
|
189
|
+
await plan('tsconfig.json', JSON.stringify({ files: [], references: [
|
|
190
|
+
{ path: './.nuxt/tsconfig.app.json' }, { path: './.nuxt/tsconfig.server.json' },
|
|
191
|
+
{ path: './.nuxt/tsconfig.shared.json' }, { path: './.nuxt/tsconfig.node.json' },
|
|
192
|
+
] }, null, 2) + '\n', { managed: true });
|
|
193
|
+
}
|
|
161
194
|
await plan('package.json', JSON.stringify(pkg, null, 2) + '\n');
|
|
162
195
|
for (const file of ['marketplace.mjs', 'api.mjs', 'domain.mjs']) {
|
|
163
196
|
await plan(`orionplace/runtime/${file}`, await readFile(new URL(`../runtime/${file}`, import.meta.url), 'utf8'), { managed: true, preserve: true });
|
|
164
197
|
}
|
|
165
198
|
await plan('orionplace/README.md', projectReadme(settings), { managed: true, preserve: true });
|
|
166
|
-
if (isNewNuxt && !(await read(await safePath(root, 'app/app.vue')))) {
|
|
167
|
-
await plan('app/app.vue', '<template>\n <main>\n <h1>Your marketplace</h1>\n <p>Domain setup and API instructions are in orionplace/README.md.</p>\n </main>\n</template>\n', { managed: true });
|
|
168
|
-
}
|
|
169
199
|
// A new project starts explicitly empty; keep a pre-existing manual setting on init.
|
|
170
200
|
const hasMimicSetting = /^\s*(?:export\s+)?MIMIC_FRONT_DOMAIN\s*=/m.test(current['.env'] || '');
|
|
171
|
-
if (settings.frontend || oldFrontend || !hasMimicSetting) {
|
|
201
|
+
if (action !== 'update' && (settings.frontend || oldFrontend || !hasMimicSetting)) {
|
|
172
202
|
await plan('.env', updateEnvironment(current['.env'], settings.frontend || ''));
|
|
173
203
|
}
|
|
174
|
-
if (settings.backend) {
|
|
204
|
+
if (settings.backend && action !== 'update') {
|
|
175
205
|
const mappings = action === 'remove' || field === 'mimic' ? []
|
|
176
206
|
: field === 'frontend' ? [setting] : settings.frontends;
|
|
177
207
|
for (const domain of mappings) {
|
|
@@ -184,7 +214,7 @@ export async function setupProject({ cwd = process.cwd(), action = 'set', field,
|
|
|
184
214
|
if (!ignore.split(/\r?\n/).includes(entry)) ignore = ignore.replace(/\n*$/, '\n') + entry + '\n';
|
|
185
215
|
}
|
|
186
216
|
await plan('.gitignore', ignore);
|
|
187
|
-
await plan('orionplace/settings.json', JSON.stringify(settings, null, 2) + '\n');
|
|
217
|
+
if (action !== 'update') await plan('orionplace/settings.json', JSON.stringify(settings, null, 2) + '\n');
|
|
188
218
|
await plan('orionplace/.generated.json', JSON.stringify(manifest, null, 2) + '\n');
|
|
189
219
|
if (!dryRun) {
|
|
190
220
|
for (const [name, content] of changes) {
|
package/src/update.mjs
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
import { setupProject } from './setup.mjs';
|
|
4
|
+
|
|
5
|
+
export function runNpm(args, cwd) {
|
|
6
|
+
const npmCli = process.env.npm_execpath;
|
|
7
|
+
const command = npmCli ? process.execPath : 'npm';
|
|
8
|
+
const commandArgs = npmCli ? [npmCli, ...args] : args;
|
|
9
|
+
return new Promise((resolve, reject) => {
|
|
10
|
+
const child = spawn(command, commandArgs, { cwd, stdio: 'inherit', shell: false });
|
|
11
|
+
child.once('error', reject);
|
|
12
|
+
child.once('exit', (code, signal) => code === 0 ? resolve() : reject(new Error(
|
|
13
|
+
`npm ${args[0]} failed (${signal || code}).`,
|
|
14
|
+
)));
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export async function updateProject({ cwd = process.cwd(), dryRun = false, local = false, npm = runNpm } = {}) {
|
|
19
|
+
cwd = resolve(cwd);
|
|
20
|
+
// Validate before downloading or writing; the latest CLI repeats preflight with its own templates.
|
|
21
|
+
const preview = await setupProject({ cwd, action: 'update', dryRun: true });
|
|
22
|
+
if (!local) {
|
|
23
|
+
console.log('Checking the latest Orionplace Nuxt release…');
|
|
24
|
+
await npm(['exec', '--yes', '--prefer-online', '--package=orionplace-nuxt@latest', '--',
|
|
25
|
+
'orionplace-nuxt', 'update', '--local', '--cwd', cwd, ...(dryRun ? ['--dry-run'] : [])], cwd);
|
|
26
|
+
return null; // The latest CLI reports its own changes.
|
|
27
|
+
}
|
|
28
|
+
if (dryRun) return preview;
|
|
29
|
+
const result = await setupProject({ cwd, action: 'update' });
|
|
30
|
+
try {
|
|
31
|
+
await npm(['install', '--no-audit', '--no-fund'], cwd);
|
|
32
|
+
} catch (error) {
|
|
33
|
+
throw new Error(`Source files were updated, but dependency installation failed. Run npm install in the project, then retry update. ${error.message}`);
|
|
34
|
+
}
|
|
35
|
+
return result;
|
|
36
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed } from 'vue';
|
|
3
|
+
import { useHead } from '#imports';
|
|
4
|
+
import { useOrionplaceAppLoad } from './composables/useOrionplaceAppLoad';
|
|
5
|
+
import type { IAppLoadPathsRecord } from './interfaces/orionplace';
|
|
6
|
+
|
|
7
|
+
useHead({ title: 'Marketplace · AppLoad debug' });
|
|
8
|
+
const { data, status, error, refresh } = await useOrionplaceAppLoad();
|
|
9
|
+
const page = computed(() => data.value?.response.page_data);
|
|
10
|
+
const init = computed(() => page.value?.init);
|
|
11
|
+
const countEndpoints = (routes?: IAppLoadPathsRecord) =>
|
|
12
|
+
Object.values(routes?.paths ?? {}).reduce((total, section) => total + Object.keys(section).length, 0);
|
|
13
|
+
const stateLabel = computed(() => ({ idle: 'Not started', pending: 'Loading', success: 'Loaded', error: 'Failed' })[status.value]);
|
|
14
|
+
const failure = computed(() => error.value?.data);
|
|
15
|
+
</script>
|
|
16
|
+
|
|
17
|
+
<template>
|
|
18
|
+
<main class="bootstrap">
|
|
19
|
+
<header>
|
|
20
|
+
<p class="eyebrow">Orionplace · Nuxt starter</p>
|
|
21
|
+
<h1>Your marketplace connection</h1>
|
|
22
|
+
<p>This first page checks initialization through <code>POST /api/appload</code>.
|
|
23
|
+
Replace it with your storefront when the connection is ready.</p>
|
|
24
|
+
</header>
|
|
25
|
+
<section aria-labelledby="status-title" :aria-busy="status === 'pending'">
|
|
26
|
+
<div class="section-heading">
|
|
27
|
+
<h2 id="status-title">AppLoad state</h2>
|
|
28
|
+
<button type="button" :disabled="status === 'pending'" @click="refresh()">Reload AppLoad</button>
|
|
29
|
+
</div>
|
|
30
|
+
<p role="status" aria-live="polite" class="state" :data-state="status">{{ stateLabel }}</p>
|
|
31
|
+
<p v-if="status === 'pending'">Loading initialization data and available API endpoints…</p>
|
|
32
|
+
<div v-else-if="error" role="alert">
|
|
33
|
+
<p>AppLoad could not initialize. {{ failure?.kind === 'validation' ? 'The backend returned form errors.' :
|
|
34
|
+
failure?.kind === 'invalid-response' ? 'The response is missing required initialization data.' :
|
|
35
|
+
failure?.kind === 'network' ? 'The API request did not complete.' : 'The API returned an HTTP error.' }}</p>
|
|
36
|
+
<p v-if="failure?.httpStatus">HTTP status: <code>{{ failure.httpStatus }}</code></p>
|
|
37
|
+
<p>Check your backend, frontend domain and <code>MIMIC_FRONT_DOMAIN</code> using
|
|
38
|
+
<code>npx orionplace-nuxt help</code>. Setup instructions are in <code>orionplace/README.md</code>.</p>
|
|
39
|
+
</div>
|
|
40
|
+
<dl v-else-if="data && init">
|
|
41
|
+
<div><dt>HTTP status</dt><dd>{{ data.httpStatus }}</dd></div>
|
|
42
|
+
<div><dt>Loaded at (UTC)</dt><dd><time :datetime="data.loadedAt">{{ data.loadedAt }}</time></dd></div>
|
|
43
|
+
<div><dt>Session</dt><dd>{{ init.is_authenticated ? 'Signed in' : 'Guest' }}</dd></div>
|
|
44
|
+
<div><dt>Languages</dt><dd>{{ Object.keys(init.locale).length }}</dd></div>
|
|
45
|
+
<div><dt>Currencies</dt><dd>{{ Object.keys(init.currencies).length }}</dd></div>
|
|
46
|
+
<div><dt>Public endpoints</dt><dd>{{ countEndpoints(page?.unauth) }}</dd></div>
|
|
47
|
+
<div><dt>Authenticated endpoints</dt><dd>{{ countEndpoints(page?.auth) }}</dd></div>
|
|
48
|
+
<div><dt>Catalog roots</dt><dd>{{ init.catalog?.length ?? 'Not included' }}</dd></div>
|
|
49
|
+
<div><dt>Initial products</dt><dd>{{ init.products?.length ?? 'Not included' }}</dd></div>
|
|
50
|
+
<div><dt>Tariffs</dt><dd>{{ init.tariffs?.data.length ?? 'Not included' }}</dd></div>
|
|
51
|
+
</dl>
|
|
52
|
+
</section>
|
|
53
|
+
<section aria-labelledby="next-title">
|
|
54
|
+
<h2 id="next-title">Build from typed initialization data</h2>
|
|
55
|
+
<p>The request lives in <code>app/composables/useOrionplaceAppLoad.ts</code>.
|
|
56
|
+
The response and its nested interfaces are in <code>app/interfaces/orionplace/</code>.</p>
|
|
57
|
+
<p>This debug page shows section counts. Account details, keys and backend diagnostics are not displayed.</p>
|
|
58
|
+
<a href="https://orionmarket.place/sandbox">Read the marketplace documentation</a>
|
|
59
|
+
</section>
|
|
60
|
+
</main>
|
|
61
|
+
</template>
|
|
62
|
+
|
|
63
|
+
<style lang="scss">
|
|
64
|
+
body { margin: 0; background: #f5f6f8; color: #202b38; font-family: system-ui, sans-serif; }
|
|
65
|
+
.bootstrap {
|
|
66
|
+
max-width: 860px; margin: 0 auto; padding: 48px 24px;
|
|
67
|
+
h1, h2 { font-weight: 400; line-height: 1.25; }
|
|
68
|
+
h1 { font-size: 32px; margin: 12px 0; }
|
|
69
|
+
h2 { font-size: 21px; margin: 0; }
|
|
70
|
+
p { line-height: 1.7; }
|
|
71
|
+
.eyebrow { color: #596777; font-size: 14px; }
|
|
72
|
+
section { margin-top: 28px; padding: 24px; border: 1px solid #d9dfe6; border-radius: 10px; background: #fff; }
|
|
73
|
+
.section-heading { display: flex; align-items: center; justify-content: space-between; gap: 16px; flex-wrap: wrap; }
|
|
74
|
+
.state { display: inline-block; padding: 4px 12px; border-radius: 4px; background: #eaf0f5;
|
|
75
|
+
&[data-state='success'] { background: #e6f2ec; color: #245c40; }
|
|
76
|
+
&[data-state='error'] { background: #fbeceb; color: #8c302a; }
|
|
77
|
+
}
|
|
78
|
+
button { font: inherit; color: inherit; background: #f5f6f8; border: 1px solid #bfc9d4; border-radius: 5px; padding: 9px 14px; cursor: pointer;
|
|
79
|
+
&:disabled { opacity: .6; cursor: wait; }
|
|
80
|
+
&:hover:not(:disabled) { background: #eaf0f5; }
|
|
81
|
+
}
|
|
82
|
+
a { color: #245786; text-underline-offset: 3px; }
|
|
83
|
+
:focus-visible { outline: 2px solid #245786; outline-offset: 4px; }
|
|
84
|
+
code { font-size: .9em; overflow-wrap: anywhere; }
|
|
85
|
+
dl { margin-bottom: 0; div { display: grid; grid-template-columns: minmax(140px, 1fr) 2fr; gap: 12px; padding: 12px 0; border-top: 1px solid #e8ecf0; }
|
|
86
|
+
dt { color: #596777; } dd { margin: 0; overflow-wrap: anywhere; }
|
|
87
|
+
}
|
|
88
|
+
@media (max-width: 520px) { padding: 24px 16px; section { padding: 18px; } dl div { grid-template-columns: 1fr; gap: 4px; } }
|
|
89
|
+
}
|
|
90
|
+
</style>
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { createError, useAsyncData, useRequestFetch, useResponseHeader } from '#imports';
|
|
2
|
+
import type { AppLoadFailure, AppLoadRequest, AppLoadResult } from '../interfaces/orionplace/transport';
|
|
3
|
+
import { appLoadResult, inspectAppLoad } from '../utils/orionplaceAppLoad';
|
|
4
|
+
|
|
5
|
+
/** Call once in app.vue. Nuxt transfers the SSR result to the browser during hydration. */
|
|
6
|
+
export function useOrionplaceAppLoad() {
|
|
7
|
+
// SSR forwards the current request's domain, cookies and authorization to our own gateway.
|
|
8
|
+
const requestFetch = useRequestFetch();
|
|
9
|
+
const responseCookies = import.meta.server ? useResponseHeader('set-cookie') : undefined;
|
|
10
|
+
if (import.meta.server) useResponseHeader('cache-control').value = 'private, no-store';
|
|
11
|
+
|
|
12
|
+
return useAsyncData<AppLoadResult, AppLoadFailure>(
|
|
13
|
+
'orionplace:appload',
|
|
14
|
+
async (_app, { signal }) => {
|
|
15
|
+
let received: { body: unknown; status: number; cookies: string[] } | undefined;
|
|
16
|
+
try {
|
|
17
|
+
await requestFetch<unknown>('/api/appload', {
|
|
18
|
+
method: 'POST',
|
|
19
|
+
body: { detalize_mode: 'light', ignore_cache: true } satisfies AppLoadRequest,
|
|
20
|
+
credentials: 'same-origin',
|
|
21
|
+
cache: 'no-store',
|
|
22
|
+
retry: 0,
|
|
23
|
+
timeout: 35_000,
|
|
24
|
+
ignoreResponseError: true,
|
|
25
|
+
signal,
|
|
26
|
+
onResponse({ response }) {
|
|
27
|
+
received = { body: response._data, status: response.status,
|
|
28
|
+
cookies: import.meta.server ? response.headers.getSetCookie() : [] };
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
} catch {
|
|
32
|
+
throw createError({ message: 'Could not reach the marketplace API.', data: {
|
|
33
|
+
kind: 'network', httpStatus: null, fields: [],
|
|
34
|
+
} satisfies AppLoadFailure });
|
|
35
|
+
}
|
|
36
|
+
if (!received) throw createError({ message: 'The API returned no response.', data: {
|
|
37
|
+
kind: 'network', httpStatus: null, fields: [],
|
|
38
|
+
} satisfies AppLoadFailure });
|
|
39
|
+
// AppLoad can establish a CSRF/session cookie during SSR. Relay each cookie separately.
|
|
40
|
+
if (responseCookies) {
|
|
41
|
+
const cookies = received.cookies;
|
|
42
|
+
const existing = responseCookies.value;
|
|
43
|
+
if (cookies.length) responseCookies.value = [...(Array.isArray(existing) ? existing : existing ? [String(existing)] : []), ...cookies];
|
|
44
|
+
}
|
|
45
|
+
const failure = inspectAppLoad(received.body, received.status);
|
|
46
|
+
if (failure) throw createError({ message: 'Marketplace initialization failed.', data: failure });
|
|
47
|
+
return appLoadResult(received.body, received.status);
|
|
48
|
+
},
|
|
49
|
+
{ deep: false, dedupe: 'defer' },
|
|
50
|
+
);
|
|
51
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type TMethod = 'GET' | 'POST' | 'PATCH' | 'PUT' | 'DELETE';
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { IAppLoadCommon } from './IAppLoadCommon';
|
|
2
|
+
import type { IApploadUserProfile } from './IApploadUserProfile';
|
|
3
|
+
import type { IAppLoadPathsRecord } from './appLoadCommon/IAppLoadCommonPath';
|
|
4
|
+
|
|
5
|
+
export interface IAppLoad {
|
|
6
|
+
init: IAppLoadCommon;
|
|
7
|
+
unauth: IAppLoadPathsRecord;
|
|
8
|
+
user?: IApploadUserProfile;
|
|
9
|
+
auth?: IAppLoadPathsRecord;
|
|
10
|
+
view_key?: string;
|
|
11
|
+
spent_total?: number;
|
|
12
|
+
spent_init?: number;
|
|
13
|
+
unauth_spent?: number;
|
|
14
|
+
auth_spent?: number;
|
|
15
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { IAppLoadCommonLocale } from './appLoadCommon/IAppLoadCommonLocale';
|
|
2
|
+
import type { IAppLoadCommonCurrency } from './appLoadCommon/IAppLoadCommonCurrency';
|
|
3
|
+
import type { IAppLoadCommonUnit } from './appLoadCommon/IAppLoadCommonUnit';
|
|
4
|
+
import type { IAppLoadCommonLogos } from './appLoadCommon/IAppLoadCommonLogos';
|
|
5
|
+
import type { IGeoCoordinates } from '../shared/IGeoCoordinates';
|
|
6
|
+
import type { IAppLoadCommonSeo } from './appLoadCommon/IAppLoadCommonSEO';
|
|
7
|
+
import type { IAppLoadCommonSocial } from './appLoadCommon/IAppLoadCommonSocial';
|
|
8
|
+
import type { IAppLoadCommonVisualSettings } from './appLoadCommon/IAppLoadCommonVisualSettings';
|
|
9
|
+
import type { IAppLoadCommonEvents } from './appLoadCommon/IAppLoadCommonEvents';
|
|
10
|
+
import type { IAppLoadCommonBanner } from './appLoadCommon/IAppLoadCommonBanner';
|
|
11
|
+
import type { ICategory } from '../category/ICategory';
|
|
12
|
+
import type { IProduct } from '../products/IProduct';
|
|
13
|
+
import type { IAppLoadCommonTariff } from './appLoadCommon/IAppLoadCommonTariff';
|
|
14
|
+
import type { IAppLoadCommonWalletValue } from './appLoadCommon/IAppLoadCommonWalletValue';
|
|
15
|
+
import type { IAppLoadCommonVariables } from './appLoadCommon/IAppLoadCommonVariables';
|
|
16
|
+
import type { IAppLoadCommonContact } from './appLoadCommon/IAppLoadCommonContact';
|
|
17
|
+
|
|
18
|
+
export interface IAppLoadCommon {
|
|
19
|
+
locale: Record<string, IAppLoadCommonLocale>;
|
|
20
|
+
currencies: Record<string, IAppLoadCommonCurrency>;
|
|
21
|
+
weights: Record<string, IAppLoadCommonUnit>;
|
|
22
|
+
lengths: Record<string, IAppLoadCommonUnit>;
|
|
23
|
+
geo_location_enabled: boolean;
|
|
24
|
+
is_authenticated: boolean;
|
|
25
|
+
wallet?: Record<string, IAppLoadCommonWalletValue>;
|
|
26
|
+
logos: IAppLoadCommonLogos;
|
|
27
|
+
public_keys: Record<string, string>;
|
|
28
|
+
default_map: IGeoCoordinates;
|
|
29
|
+
seo?: IAppLoadCommonSeo[];
|
|
30
|
+
sm?: IAppLoadCommonSocial[];
|
|
31
|
+
variables?: IAppLoadCommonVariables;
|
|
32
|
+
contact?: IAppLoadCommonContact;
|
|
33
|
+
visual_settings?: IAppLoadCommonVisualSettings;
|
|
34
|
+
events?: IAppLoadCommonEvents[];
|
|
35
|
+
main_banner?: Record<string, IAppLoadCommonBanner[]>;
|
|
36
|
+
catalog?: ICategory[];
|
|
37
|
+
products?: IProduct[];
|
|
38
|
+
featured?: IProduct[];
|
|
39
|
+
search_hints?: Record<string, string>[];
|
|
40
|
+
tariffs?: IAppLoadCommonTariff;
|
|
41
|
+
stock?: { mode: 'bar' | 'qr'; simple: boolean };
|
|
42
|
+
order?: { order_auto_register_with_carrier: boolean };
|
|
43
|
+
promo?: { create_benefits: boolean };
|
|
44
|
+
product?: { common_properties: boolean };
|
|
45
|
+
app?: { backend: boolean };
|
|
46
|
+
domains?: IAppLoadDomain[];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface IAppLoadDomain {
|
|
50
|
+
id: number;
|
|
51
|
+
domain_group: number | null;
|
|
52
|
+
domain: string;
|
|
53
|
+
is_active: boolean;
|
|
54
|
+
}
|