@jay-framework/wix-deploy 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/artifact-store.js +214 -0
- package/dist/index.d.ts +112 -0
- package/dist/index.js +965 -0
- package/package.json +50 -0
- package/plugin.yaml +14 -0
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import { items } from "@wix/data";
|
|
2
|
+
import crypto from "node:crypto";
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
function makeItemId(version, relativePath) {
|
|
6
|
+
return crypto.createHash("sha256").update(`v${version}/${relativePath}`).digest("hex").slice(0, 32);
|
|
7
|
+
}
|
|
8
|
+
class WixDataArtifactStore {
|
|
9
|
+
collectionId;
|
|
10
|
+
version;
|
|
11
|
+
cacheDir;
|
|
12
|
+
dataClient;
|
|
13
|
+
moduleRegistry;
|
|
14
|
+
manifestCache;
|
|
15
|
+
moduleCache = /* @__PURE__ */ new Map();
|
|
16
|
+
fetchPromises = /* @__PURE__ */ new Map();
|
|
17
|
+
constructor(options) {
|
|
18
|
+
this.collectionId = options.collectionId;
|
|
19
|
+
this.version = options.version;
|
|
20
|
+
this.cacheDir = options.cacheDir;
|
|
21
|
+
this.dataClient = options.wixClient.use({ items });
|
|
22
|
+
this.moduleRegistry = options.moduleRegistry || {};
|
|
23
|
+
fs.mkdirSync(this.cacheDir, { recursive: true });
|
|
24
|
+
}
|
|
25
|
+
// ========================================================================
|
|
26
|
+
// ArtifactStore interface (reads)
|
|
27
|
+
// ========================================================================
|
|
28
|
+
async readManifest() {
|
|
29
|
+
if (this.manifestCache) return this.manifestCache;
|
|
30
|
+
const content = await this.ensureFile("route-manifest.json");
|
|
31
|
+
this.manifestCache = JSON.parse(content);
|
|
32
|
+
return this.manifestCache;
|
|
33
|
+
}
|
|
34
|
+
async readCacheData(relativePath) {
|
|
35
|
+
const cacheContent = await this.ensureFile(relativePath);
|
|
36
|
+
try {
|
|
37
|
+
const cacheData = JSON.parse(cacheContent);
|
|
38
|
+
return {
|
|
39
|
+
slowViewState: cacheData.slowViewState || {},
|
|
40
|
+
carryForward: cacheData.carryForward || {}
|
|
41
|
+
};
|
|
42
|
+
} catch {
|
|
43
|
+
return { slowViewState: {}, carryForward: {} };
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
async readPagePartsConfig(relativePath) {
|
|
47
|
+
const content = await this.ensureFile(relativePath);
|
|
48
|
+
return JSON.parse(content);
|
|
49
|
+
}
|
|
50
|
+
async loadServerElement(relativePath) {
|
|
51
|
+
return this.loadModule(relativePath, true);
|
|
52
|
+
}
|
|
53
|
+
async loadModule(modulePath, _local) {
|
|
54
|
+
if (this.moduleRegistry[modulePath]) {
|
|
55
|
+
return this.moduleRegistry[modulePath];
|
|
56
|
+
}
|
|
57
|
+
const cached = this.moduleCache.get(modulePath);
|
|
58
|
+
if (cached) return cached;
|
|
59
|
+
await this.ensureFile(modulePath);
|
|
60
|
+
const fullPath = path.join(this.cacheDir, modulePath);
|
|
61
|
+
const mod = await import(
|
|
62
|
+
/* @vite-ignore */
|
|
63
|
+
fullPath
|
|
64
|
+
);
|
|
65
|
+
this.moduleCache.set(modulePath, mod);
|
|
66
|
+
return mod;
|
|
67
|
+
}
|
|
68
|
+
getAssetPath(relativePath) {
|
|
69
|
+
return path.join(this.cacheDir, relativePath);
|
|
70
|
+
}
|
|
71
|
+
getBuildDir() {
|
|
72
|
+
return this.cacheDir;
|
|
73
|
+
}
|
|
74
|
+
// ========================================================================
|
|
75
|
+
// Eager loading (cold start)
|
|
76
|
+
// ========================================================================
|
|
77
|
+
async loadEagerFiles() {
|
|
78
|
+
console.log(
|
|
79
|
+
`[WixDataArtifactStore] Loading eager files v${this.version} from "${this.collectionId}"...`
|
|
80
|
+
);
|
|
81
|
+
let totalLoaded = 0;
|
|
82
|
+
let hasMore = true;
|
|
83
|
+
let offset = 0;
|
|
84
|
+
const limit = 50;
|
|
85
|
+
while (hasMore) {
|
|
86
|
+
const result = await this.dataClient.items.query(this.collectionId).eq("category", "eager").eq("version", this.version).skip(offset).limit(limit).find();
|
|
87
|
+
for (const item of result.items) {
|
|
88
|
+
this.writeToCache(item.path, item.content);
|
|
89
|
+
totalLoaded++;
|
|
90
|
+
}
|
|
91
|
+
hasMore = result.items.length === limit;
|
|
92
|
+
offset += limit;
|
|
93
|
+
}
|
|
94
|
+
console.log(`[WixDataArtifactStore] Loaded ${totalLoaded} eager files`);
|
|
95
|
+
}
|
|
96
|
+
// ========================================================================
|
|
97
|
+
// Writes (for upload-backend and renderer)
|
|
98
|
+
// ========================================================================
|
|
99
|
+
/**
|
|
100
|
+
* Write a single file to the data collection.
|
|
101
|
+
*/
|
|
102
|
+
async writeFile(relativePath, content, category) {
|
|
103
|
+
const ext = path.extname(relativePath).slice(1);
|
|
104
|
+
const item = {
|
|
105
|
+
_id: makeItemId(this.version, relativePath),
|
|
106
|
+
version: this.version,
|
|
107
|
+
path: relativePath,
|
|
108
|
+
content,
|
|
109
|
+
fileType: ext || "unknown",
|
|
110
|
+
sizeBytes: Buffer.byteLength(content),
|
|
111
|
+
category
|
|
112
|
+
};
|
|
113
|
+
await this.dataClient.items.save(this.collectionId, item);
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Write a batch of files to the data collection.
|
|
117
|
+
* Returns the number of successfully written files.
|
|
118
|
+
*/
|
|
119
|
+
async writeFiles(files) {
|
|
120
|
+
const dataItems = files.map((f) => ({
|
|
121
|
+
_id: makeItemId(this.version, f.path),
|
|
122
|
+
version: this.version,
|
|
123
|
+
path: f.path,
|
|
124
|
+
content: f.content,
|
|
125
|
+
fileType: path.extname(f.path).slice(1) || "unknown",
|
|
126
|
+
sizeBytes: Buffer.byteLength(f.content),
|
|
127
|
+
category: f.category
|
|
128
|
+
}));
|
|
129
|
+
try {
|
|
130
|
+
await this.dataClient.items.bulkSave(this.collectionId, dataItems);
|
|
131
|
+
return dataItems.length;
|
|
132
|
+
} catch {
|
|
133
|
+
let count = 0;
|
|
134
|
+
for (const item of dataItems) {
|
|
135
|
+
try {
|
|
136
|
+
await this.dataClient.items.save(this.collectionId, item);
|
|
137
|
+
count++;
|
|
138
|
+
} catch {
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return count;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
// ========================================================================
|
|
145
|
+
// Internal: lazy file fetching
|
|
146
|
+
// ========================================================================
|
|
147
|
+
async ensureFile(relativePath) {
|
|
148
|
+
const fullPath = path.join(this.cacheDir, relativePath);
|
|
149
|
+
if (fs.existsSync(fullPath)) {
|
|
150
|
+
return fs.readFileSync(fullPath, "utf8");
|
|
151
|
+
}
|
|
152
|
+
const existing = this.fetchPromises.get(relativePath);
|
|
153
|
+
if (existing) return existing;
|
|
154
|
+
const promise = this.fetchFromCollection(relativePath);
|
|
155
|
+
this.fetchPromises.set(relativePath, promise);
|
|
156
|
+
try {
|
|
157
|
+
return await promise;
|
|
158
|
+
} finally {
|
|
159
|
+
this.fetchPromises.delete(relativePath);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
async fetchFromCollection(relativePath) {
|
|
163
|
+
const id = makeItemId(this.version, relativePath);
|
|
164
|
+
const t0 = Date.now();
|
|
165
|
+
const item = await this.dataClient.items.get(
|
|
166
|
+
this.collectionId,
|
|
167
|
+
id
|
|
168
|
+
);
|
|
169
|
+
const t1 = Date.now();
|
|
170
|
+
if (!item?.content) {
|
|
171
|
+
throw new Error(
|
|
172
|
+
`File not found in data collection: v${this.version}/${relativePath} (id: ${id})`
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
this.writeToCache(relativePath, item.content);
|
|
176
|
+
console.log(
|
|
177
|
+
`[WixDataArtifactStore] Fetched v${this.version}/${relativePath} (${t1 - t0}ms)`
|
|
178
|
+
);
|
|
179
|
+
return item.content;
|
|
180
|
+
}
|
|
181
|
+
writeToCache(relativePath, content) {
|
|
182
|
+
const fullPath = path.join(this.cacheDir, relativePath);
|
|
183
|
+
fs.mkdirSync(path.dirname(fullPath), { recursive: true });
|
|
184
|
+
if (relativePath.endsWith("page-parts.json")) {
|
|
185
|
+
try {
|
|
186
|
+
const config = JSON.parse(content);
|
|
187
|
+
const rewriteParts = (parts) => {
|
|
188
|
+
for (const part of parts) {
|
|
189
|
+
if (part.modulePath && part.source === "npm") {
|
|
190
|
+
const npmMatch = part.modulePath.match(/\/@jay-framework\/([^/]+)\//);
|
|
191
|
+
if (npmMatch) {
|
|
192
|
+
part.modulePath = `@jay-framework/${npmMatch[1]}`;
|
|
193
|
+
} else {
|
|
194
|
+
const pkgMatch = part.modulePath.match(/\/packages\/(wix-[^/]+)\//);
|
|
195
|
+
if (pkgMatch) {
|
|
196
|
+
part.modulePath = `@jay-framework/${pkgMatch[1]}`;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
if (config.parts) rewriteParts(config.parts);
|
|
203
|
+
if (config.instanceComponents) rewriteParts(config.instanceComponents);
|
|
204
|
+
content = JSON.stringify(config);
|
|
205
|
+
} catch {
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
fs.writeFileSync(fullPath, content, "utf8");
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
export {
|
|
212
|
+
WixDataArtifactStore,
|
|
213
|
+
makeItemId
|
|
214
|
+
};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { WixClient } from '@wix/sdk';
|
|
2
|
+
import { ArtifactStore, RouteManifest, CacheEntry, ServerElementModule } from '@jay-framework/production-server/serve';
|
|
3
|
+
import * as _jay_framework_fullstack_component from '@jay-framework/fullstack-component';
|
|
4
|
+
import { ConsoleContext } from '@jay-framework/fullstack-component';
|
|
5
|
+
import { WixClientService } from '@jay-framework/wix-server-client';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* WixDataArtifactStore — implements ArtifactStore for Wix BaaS deployments.
|
|
9
|
+
*
|
|
10
|
+
* Manages both reads (for serving) and writes (for upload/renderer) of
|
|
11
|
+
* backend build artifacts in a Wix data collection. All items are versioned
|
|
12
|
+
* so that a new version can be uploaded while the current version serves.
|
|
13
|
+
*
|
|
14
|
+
* Data collection schema:
|
|
15
|
+
* _id: string — "{version}__{path}" (unique key)
|
|
16
|
+
* version: string — build version (semver)
|
|
17
|
+
* path: string — relative file path within backend dir
|
|
18
|
+
* content: string — file content (text)
|
|
19
|
+
* fileType: string — extension (js, json)
|
|
20
|
+
* sizeBytes: number — content byte length
|
|
21
|
+
* category: string — 'eager' | 'lazy'
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
interface WixDataArtifactStoreOptions {
|
|
25
|
+
wixClient: WixClient;
|
|
26
|
+
collectionId: string;
|
|
27
|
+
cacheDir: string;
|
|
28
|
+
version: string;
|
|
29
|
+
moduleRegistry?: Record<string, any>;
|
|
30
|
+
}
|
|
31
|
+
declare class WixDataArtifactStore implements ArtifactStore {
|
|
32
|
+
readonly collectionId: string;
|
|
33
|
+
readonly version: string;
|
|
34
|
+
private readonly cacheDir;
|
|
35
|
+
private readonly dataClient;
|
|
36
|
+
private readonly moduleRegistry;
|
|
37
|
+
private manifestCache?;
|
|
38
|
+
private moduleCache;
|
|
39
|
+
private fetchPromises;
|
|
40
|
+
constructor(options: WixDataArtifactStoreOptions);
|
|
41
|
+
readManifest(): Promise<RouteManifest>;
|
|
42
|
+
readCacheData(relativePath: string): Promise<CacheEntry>;
|
|
43
|
+
readPagePartsConfig(relativePath: string): Promise<any>;
|
|
44
|
+
loadServerElement(relativePath: string): Promise<ServerElementModule>;
|
|
45
|
+
loadModule(modulePath: string, _local?: boolean): Promise<any>;
|
|
46
|
+
getAssetPath(relativePath: string): string;
|
|
47
|
+
getBuildDir(): string;
|
|
48
|
+
loadEagerFiles(): Promise<void>;
|
|
49
|
+
/**
|
|
50
|
+
* Write a single file to the data collection.
|
|
51
|
+
*/
|
|
52
|
+
writeFile(relativePath: string, content: string, category: 'eager' | 'lazy'): Promise<void>;
|
|
53
|
+
/**
|
|
54
|
+
* Write a batch of files to the data collection.
|
|
55
|
+
* Returns the number of successfully written files.
|
|
56
|
+
*/
|
|
57
|
+
writeFiles(files: Array<{
|
|
58
|
+
path: string;
|
|
59
|
+
content: string;
|
|
60
|
+
category: 'eager' | 'lazy';
|
|
61
|
+
}>): Promise<number>;
|
|
62
|
+
private ensureFile;
|
|
63
|
+
private fetchFromCollection;
|
|
64
|
+
private writeToCache;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
interface BuildEntryInput {
|
|
68
|
+
collectionId?: string;
|
|
69
|
+
staticBaseUrl?: string;
|
|
70
|
+
excludePlugins?: string;
|
|
71
|
+
}
|
|
72
|
+
declare const buildEntry: _jay_framework_fullstack_component.JayCliCommand<BuildEntryInput> & _jay_framework_fullstack_component.JayCliCommandDefinition<BuildEntryInput, [ConsoleContext]>;
|
|
73
|
+
|
|
74
|
+
interface UploadBackendInput {
|
|
75
|
+
collectionId?: string;
|
|
76
|
+
dryRun?: boolean;
|
|
77
|
+
}
|
|
78
|
+
declare const uploadBackend: _jay_framework_fullstack_component.JayCliCommand<UploadBackendInput> & _jay_framework_fullstack_component.JayCliCommandDefinition<UploadBackendInput, [WixClientService, ConsoleContext]>;
|
|
79
|
+
|
|
80
|
+
interface DeployBaasInput {
|
|
81
|
+
dryRun?: boolean;
|
|
82
|
+
}
|
|
83
|
+
declare const deployBaas: _jay_framework_fullstack_component.JayCliCommand<DeployBaasInput> & _jay_framework_fullstack_component.JayCliCommandDefinition<DeployBaasInput, [ConsoleContext]>;
|
|
84
|
+
|
|
85
|
+
interface DeployInput {
|
|
86
|
+
collectionId?: string;
|
|
87
|
+
staticBaseUrl?: string;
|
|
88
|
+
excludePlugins?: string;
|
|
89
|
+
dryRun?: boolean;
|
|
90
|
+
}
|
|
91
|
+
declare const deploy: _jay_framework_fullstack_component.JayCliCommand<DeployInput> & _jay_framework_fullstack_component.JayCliCommandDefinition<DeployInput, [WixClientService, ConsoleContext]>;
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* wix-deploy setup hook
|
|
95
|
+
*
|
|
96
|
+
* Runs after wix-server-client setup. Reads wix.config.json to populate
|
|
97
|
+
* clientId and siteId in config/.wix.yaml (if they're still placeholders),
|
|
98
|
+
* then validates the data collection exists.
|
|
99
|
+
*/
|
|
100
|
+
interface SetupContext {
|
|
101
|
+
configDir: string;
|
|
102
|
+
projectRoot: string;
|
|
103
|
+
initError?: Error;
|
|
104
|
+
}
|
|
105
|
+
interface SetupResult {
|
|
106
|
+
status: 'configured' | 'needs-config' | 'error';
|
|
107
|
+
message?: string;
|
|
108
|
+
configCreated?: string[];
|
|
109
|
+
}
|
|
110
|
+
declare function setupWixDeploy(ctx: SetupContext): Promise<SetupResult>;
|
|
111
|
+
|
|
112
|
+
export { WixDataArtifactStore, type WixDataArtifactStoreOptions, buildEntry, deploy, deployBaas, setupWixDeploy, uploadBackend };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,965 @@
|
|
|
1
|
+
import { WixDataArtifactStore } from "./artifact-store.js";
|
|
2
|
+
import { makeCliCommand, CONSOLE_CONTEXT } from "@jay-framework/fullstack-component";
|
|
3
|
+
import { WIX_CLIENT_SERVICE } from "@jay-framework/wix-server-client";
|
|
4
|
+
import { createHttpClient } from "@wix/http-client";
|
|
5
|
+
import { createAppDeployment, completeAppDeployment } from "@wix/ambassador-velo-backend-v1-app-deployment/http";
|
|
6
|
+
import { createComponentsOverride } from "@wix/ambassador-devcenter-components-overrides-v1-components-override/http";
|
|
7
|
+
import { getLatestProductionVersion } from "@wix/ambassador-devcenter-apps-v1-app-version/http";
|
|
8
|
+
import { release } from "@wix/ambassador-ctp-gradual-rollout-v1-baas-release/http";
|
|
9
|
+
import fs from "node:fs";
|
|
10
|
+
import path from "node:path";
|
|
11
|
+
import crypto from "node:crypto";
|
|
12
|
+
import yaml from "js-yaml";
|
|
13
|
+
import { getService } from "@jay-framework/stack-server-runtime";
|
|
14
|
+
import { items } from "@wix/data";
|
|
15
|
+
const DEFAULT_COLLECTION_ID = "jay-backend-files";
|
|
16
|
+
const DEFAULT_CACHE_DIR = "/tmp/jay-backend";
|
|
17
|
+
const DEFAULT_EXCLUDE_PLUGINS = ["aiditor", "ui-kit", "wix-deploy"];
|
|
18
|
+
function sortPluginsByDeps(plugins) {
|
|
19
|
+
const priorityPackages = ["@jay-framework/wix-server-client"];
|
|
20
|
+
const sorted = [];
|
|
21
|
+
for (const pkg of priorityPackages) {
|
|
22
|
+
const p = plugins.find((p2) => p2.packageName === pkg);
|
|
23
|
+
if (p) sorted.push(p);
|
|
24
|
+
}
|
|
25
|
+
for (const p of plugins) {
|
|
26
|
+
if (!sorted.includes(p)) sorted.push(p);
|
|
27
|
+
}
|
|
28
|
+
return sorted;
|
|
29
|
+
}
|
|
30
|
+
function generateStubContents(modulePath) {
|
|
31
|
+
const throwStub = '() => { throw new Error("build-time only"); }';
|
|
32
|
+
const stubs = {
|
|
33
|
+
"@jay-framework/compiler-jay-html": [
|
|
34
|
+
"export default {};",
|
|
35
|
+
`export const parseJayFile = ${throwStub};`,
|
|
36
|
+
`export const JAY_IMPORT_RESOLVER = {};`,
|
|
37
|
+
`export const generateServerElementFile = ${throwStub};`,
|
|
38
|
+
`export const injectHeadfullFSTemplates = ${throwStub};`,
|
|
39
|
+
`export const discoverHeadlessInstances = ${throwStub};`,
|
|
40
|
+
`export const assignCoordinatesToJayHtml = ${throwStub};`,
|
|
41
|
+
`export const parseAction = ${throwStub};`,
|
|
42
|
+
`export const parseContract = ${throwStub};`
|
|
43
|
+
].join("\n"),
|
|
44
|
+
"@jay-framework/compiler-shared": [
|
|
45
|
+
"export default {};",
|
|
46
|
+
`export const checkValidationErrors = (x) => x;`,
|
|
47
|
+
`export const formatCode = (code) => code;`
|
|
48
|
+
].join("\n"),
|
|
49
|
+
"@jay-framework/compiler-jay-stack": [
|
|
50
|
+
"export default {};",
|
|
51
|
+
`export const jayStackCompiler = ${throwStub};`
|
|
52
|
+
].join("\n"),
|
|
53
|
+
"@jay-framework/compiler-analyze-exported-types": [
|
|
54
|
+
"export default {};",
|
|
55
|
+
`export const extractActionsFromSource = ${throwStub};`
|
|
56
|
+
].join("\n"),
|
|
57
|
+
"@jay-framework/compiler": [
|
|
58
|
+
"export default {};",
|
|
59
|
+
`export const jayRuntime = ${throwStub};`
|
|
60
|
+
].join("\n")
|
|
61
|
+
};
|
|
62
|
+
return stubs[modulePath] || "export default {};";
|
|
63
|
+
}
|
|
64
|
+
function generateEntrySource(plugins, actionPackages, serverElements, collectionId, staticBaseUrl, version) {
|
|
65
|
+
const pluginImports = plugins.map(
|
|
66
|
+
(p, i) => `import { init as pluginInit_${i} } from '${p.packageName}';`
|
|
67
|
+
);
|
|
68
|
+
const moduleImports = plugins.map(
|
|
69
|
+
(p, i) => `import * as pluginModule_${i} from '${p.packageName}';`
|
|
70
|
+
);
|
|
71
|
+
const seImports = serverElements.map(
|
|
72
|
+
(se, i) => `import * as serverElement_${i} from '${se.absolutePath}';`
|
|
73
|
+
);
|
|
74
|
+
const pluginsArray = plugins.map((p, i) => ` { name: '${p.name}', init: pluginInit_${i} },`);
|
|
75
|
+
const registryEntries = [
|
|
76
|
+
...plugins.map((p, i) => ` '${p.packageName}': pluginModule_${i},`),
|
|
77
|
+
...serverElements.map((se, i) => ` '${se.relativePath}': serverElement_${i},`)
|
|
78
|
+
];
|
|
79
|
+
const actionsArray = actionPackages.map((pkg) => {
|
|
80
|
+
const idx = plugins.findIndex((p) => p.packageName === pkg);
|
|
81
|
+
return ` { module: pluginModule_${idx}, name: '${pkg}' },`;
|
|
82
|
+
});
|
|
83
|
+
return `// Auto-generated BaaS entry — do not edit
|
|
84
|
+
// Generated by @jay-framework/wix-deploy build-entry
|
|
85
|
+
|
|
86
|
+
// Plugin imports (init + full modules for page-parts resolution)
|
|
87
|
+
${pluginImports.join("\n")}
|
|
88
|
+
${moduleImports.join("\n")}
|
|
89
|
+
|
|
90
|
+
// Server element imports (bundled with ssr-runtime)
|
|
91
|
+
${seImports.join("\n")}
|
|
92
|
+
|
|
93
|
+
// Framework imports
|
|
94
|
+
import {
|
|
95
|
+
matchRequest,
|
|
96
|
+
fetchPageRequest,
|
|
97
|
+
fetchActionRequest,
|
|
98
|
+
isActionRequest,
|
|
99
|
+
initializeServicesFromModules,
|
|
100
|
+
registerActionsFromModules,
|
|
101
|
+
FilesystemArtifactStore,
|
|
102
|
+
} from '@jay-framework/production-server/serve';
|
|
103
|
+
import { parseCookies, getService } from '@jay-framework/stack-server-runtime';
|
|
104
|
+
import { WIX_CLIENT_SERVICE } from '@jay-framework/wix-server-client';
|
|
105
|
+
import { WixDataArtifactStore } from '@jay-framework/wix-deploy/artifact-store';
|
|
106
|
+
|
|
107
|
+
// Module registry — npm packages + server elements (all bundled by esbuild)
|
|
108
|
+
const MODULE_REGISTRY = {
|
|
109
|
+
${registryEntries.join("\n")}
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
// Configuration
|
|
113
|
+
const COLLECTION_ID = process.env.JAY_COLLECTION_ID || '${collectionId}';
|
|
114
|
+
const STATIC_BASE_URL = process.env.STATIC_BASE_URL || '${staticBaseUrl}';
|
|
115
|
+
const VERSION = process.env.JAY_BUILD_VERSION || '${version}';
|
|
116
|
+
const LOCAL_BACKEND_DIR = process.env.JAY_BACKEND_DIR || '';
|
|
117
|
+
|
|
118
|
+
let artifacts;
|
|
119
|
+
let initialized = false;
|
|
120
|
+
let initError = null;
|
|
121
|
+
|
|
122
|
+
async function initialize() {
|
|
123
|
+
if (initialized) return;
|
|
124
|
+
|
|
125
|
+
// Set CWD to entry.mjs dir so config/.wix.yaml is found by wix-server-client
|
|
126
|
+
const entryDir = new URL('.', import.meta.url).pathname;
|
|
127
|
+
if (entryDir && process.cwd() !== entryDir) {
|
|
128
|
+
try { process.chdir(entryDir); } catch {}
|
|
129
|
+
}
|
|
130
|
+
console.log('[BaaS] CWD:', process.cwd());
|
|
131
|
+
|
|
132
|
+
// BaaS skips dotfiles during upload — copy wix.yaml to .wix.yaml if needed
|
|
133
|
+
const fs = await import('node:fs');
|
|
134
|
+
const configDir = process.cwd() + '/config';
|
|
135
|
+
const dotYaml = configDir + '/.wix.yaml';
|
|
136
|
+
const plainYaml = configDir + '/wix.yaml';
|
|
137
|
+
if (!fs.existsSync(dotYaml) && fs.existsSync(plainYaml)) {
|
|
138
|
+
fs.copyFileSync(plainYaml, dotYaml);
|
|
139
|
+
console.log('[BaaS] Copied config/wix.yaml to config/.wix.yaml');
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Initialize plugins first — wix-server-client reads config/.wix.yaml and creates WixClient
|
|
143
|
+
console.log('[BaaS] Initializing plugins...');
|
|
144
|
+
await initializeServicesFromModules([
|
|
145
|
+
${pluginsArray.join("\n")}
|
|
146
|
+
], 'BaaS');
|
|
147
|
+
|
|
148
|
+
// Register actions
|
|
149
|
+
console.log('[BaaS] Registering actions...');
|
|
150
|
+
await registerActionsFromModules([
|
|
151
|
+
${actionsArray.join("\n")}
|
|
152
|
+
]);
|
|
153
|
+
|
|
154
|
+
// Create artifact store — uses the WixClient that wix-server-client just initialized
|
|
155
|
+
if (LOCAL_BACKEND_DIR) {
|
|
156
|
+
console.log('[BaaS] Using filesystem artifact store:', LOCAL_BACKEND_DIR);
|
|
157
|
+
artifacts = new FilesystemArtifactStore(LOCAL_BACKEND_DIR);
|
|
158
|
+
} else {
|
|
159
|
+
console.log('[BaaS] Creating WixDataArtifactStore...');
|
|
160
|
+
const wixClientService = getService(WIX_CLIENT_SERVICE);
|
|
161
|
+
console.log('[BaaS] WixClientService:', wixClientService ? 'found' : 'MISSING');
|
|
162
|
+
artifacts = new WixDataArtifactStore({
|
|
163
|
+
wixClient: wixClientService,
|
|
164
|
+
collectionId: COLLECTION_ID,
|
|
165
|
+
version: VERSION,
|
|
166
|
+
cacheDir: '${DEFAULT_CACHE_DIR}',
|
|
167
|
+
moduleRegistry: MODULE_REGISTRY,
|
|
168
|
+
});
|
|
169
|
+
console.log('[BaaS] Loading eager files...');
|
|
170
|
+
await artifacts.loadEagerFiles();
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
initialized = true;
|
|
174
|
+
console.log('[BaaS] Initialization complete');
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function errorPage(title, err, path) {
|
|
178
|
+
const html = '<!doctype html><html><head><meta charset="UTF-8"><title>' + title + '</title>'
|
|
179
|
+
+ '<style>body{font-family:monospace;max-width:800px;margin:40px auto;padding:0 20px}'
|
|
180
|
+
+ 'h1{color:#c00}pre{background:#f5f5f5;padding:16px;overflow-x:auto;border-radius:4px}'
|
|
181
|
+
+ '.path{color:#666}</style></head><body>'
|
|
182
|
+
+ '<h1>' + title + '</h1>'
|
|
183
|
+
+ '<p class="path">' + path + '</p>'
|
|
184
|
+
+ '<p><strong>' + (err.message || '') + '</strong></p>'
|
|
185
|
+
+ '<pre>' + (err.stack || '') + '</pre>'
|
|
186
|
+
+ '</body></html>';
|
|
187
|
+
return new Response(html, { status: 500, headers: { 'Content-Type': 'text/html' } });
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
async function handler(request) {
|
|
191
|
+
const url = new URL(request.url);
|
|
192
|
+
|
|
193
|
+
// Diagnostic endpoint — runs before init
|
|
194
|
+
if (url.pathname === '/_jay/debug') {
|
|
195
|
+
const fs = await import('node:fs');
|
|
196
|
+
const entryDir = new URL('.', import.meta.url).pathname;
|
|
197
|
+
const info = {
|
|
198
|
+
cwd: process.cwd(),
|
|
199
|
+
entryUrl: import.meta.url,
|
|
200
|
+
entryDir,
|
|
201
|
+
initialized,
|
|
202
|
+
hasArtifacts: !!artifacts,
|
|
203
|
+
initError: initError?.message || null,
|
|
204
|
+
};
|
|
205
|
+
try { info.cwdFiles = fs.readdirSync(process.cwd()); } catch (e) { info.cwdFiles = e.message; }
|
|
206
|
+
try { info.configDirFiles = fs.readdirSync(process.cwd() + '/config'); } catch (e) { info.configDirFiles = e.message; }
|
|
207
|
+
try { info.entryDirFiles = fs.readdirSync(entryDir); } catch (e) { info.entryDirFiles = e.message; }
|
|
208
|
+
try { info.entryConfigFiles = fs.readdirSync(entryDir + '/config'); } catch (e) { info.entryConfigFiles = e.message; }
|
|
209
|
+
try { info.userCodeFiles = fs.readdirSync('/user-code'); } catch (e) { info.userCodeFiles = e.message; }
|
|
210
|
+
try { info.userCodeConfigFiles = fs.readdirSync('/user-code/config'); } catch (e) { info.userCodeConfigFiles = e.message; }
|
|
211
|
+
return new Response(JSON.stringify(info, null, 2), { headers: { 'Content-Type': 'application/json' } });
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
try {
|
|
215
|
+
await initialize();
|
|
216
|
+
} catch (e) {
|
|
217
|
+
if (!initError) {
|
|
218
|
+
initError = e;
|
|
219
|
+
console.error('[BaaS] INIT FAILED:', e.message, e.stack);
|
|
220
|
+
}
|
|
221
|
+
return errorPage('Initialization Failed', e, url.pathname);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
try {
|
|
225
|
+
if (isActionRequest(url.pathname)) return fetchActionRequest(request);
|
|
226
|
+
|
|
227
|
+
const manifest = await artifacts.readManifest();
|
|
228
|
+
const match = matchRequest(manifest, url.pathname);
|
|
229
|
+
if (!match) return new Response('Not Found', { status: 404 });
|
|
230
|
+
|
|
231
|
+
const cookies = parseCookies(request.headers.get('cookie') || '');
|
|
232
|
+
return fetchPageRequest(match, manifest, url, artifacts, STATIC_BASE_URL, cookies);
|
|
233
|
+
} catch (e) {
|
|
234
|
+
console.error('[BaaS] REQUEST FAILED:', url.pathname, e.message, e.stack);
|
|
235
|
+
return errorPage('Request Failed', e, url.pathname);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
export default { fetch: handler };
|
|
240
|
+
export { handler as fetch };
|
|
241
|
+
`;
|
|
242
|
+
}
|
|
243
|
+
function generateServeSource(frontendDir, backendDir) {
|
|
244
|
+
const lines = [
|
|
245
|
+
"#!/usr/bin/env node",
|
|
246
|
+
"// Local test server for the BaaS entry — run with: node dist/serve.mjs",
|
|
247
|
+
'import http from "node:http";',
|
|
248
|
+
'import fs from "node:fs";',
|
|
249
|
+
'import path from "node:path";',
|
|
250
|
+
'import { Readable } from "node:stream";',
|
|
251
|
+
"",
|
|
252
|
+
'process.env.STATIC_BASE_URL = process.env.STATIC_BASE_URL || "/";',
|
|
253
|
+
`process.env.JAY_BACKEND_DIR = process.env.JAY_BACKEND_DIR || "${backendDir}";`,
|
|
254
|
+
'const entry = await import("./entry.mjs");',
|
|
255
|
+
"const handler = entry.default?.fetch || entry.fetch;",
|
|
256
|
+
'const PORT = parseInt(process.env.PORT || "4000", 10);',
|
|
257
|
+
`const FRONTEND_DIR = "${frontendDir}";`,
|
|
258
|
+
"",
|
|
259
|
+
"const MIME = {",
|
|
260
|
+
' ".js": "application/javascript", ".mjs": "application/javascript",',
|
|
261
|
+
' ".css": "text/css", ".html": "text/html", ".json": "application/json",',
|
|
262
|
+
' ".png": "image/png", ".jpg": "image/jpeg", ".svg": "image/svg+xml",',
|
|
263
|
+
' ".woff2": "font/woff2", ".woff": "font/woff",',
|
|
264
|
+
"};",
|
|
265
|
+
"",
|
|
266
|
+
"function serveStatic(pathname, res) {",
|
|
267
|
+
" const fp = path.join(FRONTEND_DIR, pathname);",
|
|
268
|
+
" if (!fs.existsSync(fp) || !fs.statSync(fp).isFile()) return false;",
|
|
269
|
+
' res.writeHead(200, { "Content-Type": MIME[path.extname(fp)] || "application/octet-stream" });',
|
|
270
|
+
" fs.createReadStream(fp).pipe(res);",
|
|
271
|
+
" return true;",
|
|
272
|
+
"}",
|
|
273
|
+
"",
|
|
274
|
+
"http.createServer(async (req, res) => {",
|
|
275
|
+
' const url = new URL(req.url, "http://" + req.headers.host);',
|
|
276
|
+
" if (serveStatic(url.pathname, res)) return;",
|
|
277
|
+
" const headers = new Headers();",
|
|
278
|
+
" for (const [k, v] of Object.entries(req.headers)) {",
|
|
279
|
+
' if (v) headers.set(k, Array.isArray(v) ? v.join(", ") : v);',
|
|
280
|
+
" }",
|
|
281
|
+
" const init = { method: req.method, headers };",
|
|
282
|
+
' if (req.method !== "GET" && req.method !== "HEAD") {',
|
|
283
|
+
' init.body = Readable.toWeb(req); init.duplex = "half";',
|
|
284
|
+
" }",
|
|
285
|
+
" try {",
|
|
286
|
+
" const response = await handler(new Request(url, init));",
|
|
287
|
+
" const rh = {}; response.headers.forEach((v, k) => { rh[k] = v; });",
|
|
288
|
+
" res.writeHead(response.status, rh);",
|
|
289
|
+
" if (response.body) {",
|
|
290
|
+
" const reader = response.body.getReader();",
|
|
291
|
+
" while (true) { const { done, value } = await reader.read(); if (done) break; res.write(value); }",
|
|
292
|
+
" }",
|
|
293
|
+
" res.end();",
|
|
294
|
+
' } catch (err) { console.error("Handler error:", err); res.writeHead(500); res.end("Internal Server Error"); }',
|
|
295
|
+
"}).listen(PORT, () => {",
|
|
296
|
+
' console.log("Local test server at http://localhost:" + PORT);',
|
|
297
|
+
' console.log("Frontend: " + FRONTEND_DIR);',
|
|
298
|
+
"});"
|
|
299
|
+
];
|
|
300
|
+
return lines.join("\n") + "\n";
|
|
301
|
+
}
|
|
302
|
+
const buildEntry = makeCliCommand("build-entry").withServices(CONSOLE_CONTEXT).withHandler(async (input, ctx) => {
|
|
303
|
+
const fs2 = await import("node:fs");
|
|
304
|
+
const path2 = await import("node:path");
|
|
305
|
+
const { createRequire } = await import("node:module");
|
|
306
|
+
const buildDir = ctx.build.backend;
|
|
307
|
+
const outFile = path2.resolve(ctx.projectRoot, "dist/entry.mjs");
|
|
308
|
+
const collectionId = input.collectionId || DEFAULT_COLLECTION_ID;
|
|
309
|
+
const staticBaseUrl = input.staticBaseUrl || "/";
|
|
310
|
+
const userExcludes = input.excludePlugins?.split(",") || [];
|
|
311
|
+
const excludePlugins = [...DEFAULT_EXCLUDE_PLUGINS, ...userExcludes];
|
|
312
|
+
ctx.log(`Build dir: ${buildDir}`);
|
|
313
|
+
ctx.log(`Output: ${outFile}`);
|
|
314
|
+
const manifestPath = path2.join(buildDir, "route-manifest.json");
|
|
315
|
+
if (!fs2.existsSync(manifestPath)) {
|
|
316
|
+
ctx.error(`route-manifest.json not found at ${manifestPath}`);
|
|
317
|
+
ctx.error("Run jay-stack build first.");
|
|
318
|
+
return { success: false };
|
|
319
|
+
}
|
|
320
|
+
const manifest = JSON.parse(fs2.readFileSync(manifestPath, "utf8"));
|
|
321
|
+
const metadataPath = path2.join(buildDir, "build-metadata.json");
|
|
322
|
+
let version = "1.0.0";
|
|
323
|
+
if (fs2.existsSync(metadataPath)) {
|
|
324
|
+
const metadata = JSON.parse(fs2.readFileSync(metadataPath, "utf8"));
|
|
325
|
+
version = String(metadata.version || "1.0.0");
|
|
326
|
+
}
|
|
327
|
+
const filteredPlugins = manifest.plugins.filter(
|
|
328
|
+
(p) => !excludePlugins.includes(p.name) && !excludePlugins.includes(p.packageName)
|
|
329
|
+
);
|
|
330
|
+
const plugins = sortPluginsByDeps(filteredPlugins);
|
|
331
|
+
const pluginPackages = new Set(plugins.map((p) => p.packageName));
|
|
332
|
+
const actionPackages = manifest.actions.filter((a) => a.isPlugin && a.packageName && pluginPackages.has(a.packageName)).map((a) => a.packageName).filter((v, i, arr) => arr.indexOf(v) === i);
|
|
333
|
+
const seSet = /* @__PURE__ */ new Set();
|
|
334
|
+
for (const route of manifest.routes) {
|
|
335
|
+
if (route.serverElementPath) seSet.add(route.serverElementPath);
|
|
336
|
+
}
|
|
337
|
+
const serverElements = [...seSet].map((rel) => ({
|
|
338
|
+
relativePath: rel,
|
|
339
|
+
absolutePath: path2.join(buildDir, rel)
|
|
340
|
+
}));
|
|
341
|
+
ctx.log(`Version: ${version}`);
|
|
342
|
+
ctx.log(`Plugins: ${plugins.map((p) => p.name).join(", ")}`);
|
|
343
|
+
ctx.log(`Action packages: ${actionPackages.join(", ")}`);
|
|
344
|
+
ctx.log(`Server elements: ${serverElements.length} routes`);
|
|
345
|
+
const entrySource = generateEntrySource(
|
|
346
|
+
plugins,
|
|
347
|
+
actionPackages,
|
|
348
|
+
serverElements,
|
|
349
|
+
collectionId,
|
|
350
|
+
staticBaseUrl,
|
|
351
|
+
version
|
|
352
|
+
);
|
|
353
|
+
const entryDir = path2.dirname(outFile);
|
|
354
|
+
fs2.mkdirSync(entryDir, { recursive: true });
|
|
355
|
+
const tempEntryPath = path2.join(entryDir, ".entry.generated.mjs");
|
|
356
|
+
fs2.writeFileSync(tempEntryPath, entrySource, "utf8");
|
|
357
|
+
const stubs = [
|
|
358
|
+
"@jay-framework/compiler-jay-html",
|
|
359
|
+
"@jay-framework/compiler-shared",
|
|
360
|
+
"@jay-framework/compiler-analyze-exported-types",
|
|
361
|
+
"@jay-framework/compiler-jay-stack",
|
|
362
|
+
"@jay-framework/compiler",
|
|
363
|
+
"@jay-framework/rollup-plugin",
|
|
364
|
+
"@jay-framework/vite-plugin",
|
|
365
|
+
"typescript",
|
|
366
|
+
"prettier",
|
|
367
|
+
"vite",
|
|
368
|
+
"lightningcss",
|
|
369
|
+
"fsevents"
|
|
370
|
+
];
|
|
371
|
+
const stubFilter = new RegExp(
|
|
372
|
+
"^(" + stubs.map((s) => s.replace(/[/.]/g, "\\$&")).join("|") + ")$"
|
|
373
|
+
);
|
|
374
|
+
const stubBuildDepsPlugin = {
|
|
375
|
+
name: "stub-build-deps",
|
|
376
|
+
setup(build) {
|
|
377
|
+
build.onResolve({ filter: stubFilter }, (args) => ({
|
|
378
|
+
path: args.path,
|
|
379
|
+
namespace: "stub"
|
|
380
|
+
}));
|
|
381
|
+
build.onLoad({ filter: /.*/, namespace: "stub" }, (args) => ({
|
|
382
|
+
contents: generateStubContents(args.path),
|
|
383
|
+
loader: "js"
|
|
384
|
+
}));
|
|
385
|
+
}
|
|
386
|
+
};
|
|
387
|
+
const require2 = createRequire(import.meta.url);
|
|
388
|
+
const resolveAliasPlugin = {
|
|
389
|
+
name: "resolve-aliases",
|
|
390
|
+
setup(build) {
|
|
391
|
+
build.onResolve({ filter: /^@jay-framework\/production-server\/serve$/ }, () => {
|
|
392
|
+
return { path: require2.resolve("@jay-framework/production-server/serve") };
|
|
393
|
+
});
|
|
394
|
+
build.onResolve({ filter: /^@jay-framework\/wix-deploy\/artifact-store$/ }, () => {
|
|
395
|
+
return { path: require2.resolve("@jay-framework/wix-deploy/artifact-store") };
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
};
|
|
399
|
+
const esbuild = await import("esbuild");
|
|
400
|
+
const result = await esbuild.build({
|
|
401
|
+
entryPoints: [tempEntryPath],
|
|
402
|
+
bundle: true,
|
|
403
|
+
format: "esm",
|
|
404
|
+
platform: "node",
|
|
405
|
+
target: "node20",
|
|
406
|
+
outfile: outFile,
|
|
407
|
+
external: ["node:*"],
|
|
408
|
+
banner: {
|
|
409
|
+
js: 'import { createRequire as __bundleCreateRequire } from "node:module";const require = __bundleCreateRequire(import.meta.url);'
|
|
410
|
+
},
|
|
411
|
+
plugins: [resolveAliasPlugin, stubBuildDepsPlugin],
|
|
412
|
+
minify: false,
|
|
413
|
+
treeShaking: true,
|
|
414
|
+
logLevel: "warning",
|
|
415
|
+
metafile: true
|
|
416
|
+
});
|
|
417
|
+
fs2.rmSync(tempEntryPath, { force: true });
|
|
418
|
+
const runtimeStubs = ["typescript", "prettier", "lightningcss"];
|
|
419
|
+
let patched = fs2.readFileSync(outFile, "utf8");
|
|
420
|
+
let patchCount = 0;
|
|
421
|
+
for (const mod of runtimeStubs) {
|
|
422
|
+
const pattern = new RegExp(`(\\w+)\\("${mod}"\\)`, "g");
|
|
423
|
+
const before = patched;
|
|
424
|
+
patched = patched.replace(pattern, "({})");
|
|
425
|
+
if (patched !== before) patchCount++;
|
|
426
|
+
}
|
|
427
|
+
if (patchCount > 0) {
|
|
428
|
+
fs2.writeFileSync(outFile, patched);
|
|
429
|
+
ctx.log(`Patched ${patchCount} runtime require() calls for build-time modules`);
|
|
430
|
+
}
|
|
431
|
+
const stat = fs2.statSync(outFile);
|
|
432
|
+
const sizeMB = (stat.size / 1024 / 1024).toFixed(1);
|
|
433
|
+
ctx.log(`Done: ${outFile} (${sizeMB} MB)`);
|
|
434
|
+
if (result.metafile) {
|
|
435
|
+
ctx.log(`Bundled ${Object.keys(result.metafile.inputs).length} input files`);
|
|
436
|
+
fs2.writeFileSync(
|
|
437
|
+
outFile.replace(/\.mjs$/, ".meta.json"),
|
|
438
|
+
JSON.stringify(result.metafile)
|
|
439
|
+
);
|
|
440
|
+
}
|
|
441
|
+
const wixConfigSrc = path2.join(ctx.projectRoot, "config", ".wix.yaml");
|
|
442
|
+
if (fs2.existsSync(wixConfigSrc)) {
|
|
443
|
+
const configDir = path2.join(entryDir, "config");
|
|
444
|
+
fs2.mkdirSync(configDir, { recursive: true });
|
|
445
|
+
fs2.copyFileSync(wixConfigSrc, path2.join(configDir, ".wix.yaml"));
|
|
446
|
+
fs2.copyFileSync(wixConfigSrc, path2.join(configDir, "wix.yaml"));
|
|
447
|
+
ctx.log(`Copied config/.wix.yaml to dist/config/`);
|
|
448
|
+
} else {
|
|
449
|
+
ctx.warn(
|
|
450
|
+
`config/.wix.yaml not found — wix-server-client init will fail on BaaS unless WIX_API_KEY env vars are set`
|
|
451
|
+
);
|
|
452
|
+
}
|
|
453
|
+
const serveFile = path2.join(ctx.projectRoot, "serve.mjs");
|
|
454
|
+
fs2.writeFileSync(serveFile, generateServeSource(ctx.build.frontend, buildDir));
|
|
455
|
+
ctx.log(`Generated ${serveFile} — run with: node serve.mjs`);
|
|
456
|
+
return { success: true, outFile, sizeMB };
|
|
457
|
+
});
|
|
458
|
+
const SKIP_EXTENSIONS = /* @__PURE__ */ new Set([".png", ".jpg", ".jpeg", ".gif", ".svg", ".webp", ".jay-html"]);
|
|
459
|
+
const MAX_BATCH_BYTES = 4e5;
|
|
460
|
+
function categorize(relativePath) {
|
|
461
|
+
if (relativePath === "route-manifest.json") return "eager";
|
|
462
|
+
if (relativePath === "build-metadata.json") return "eager";
|
|
463
|
+
if (relativePath.startsWith("server/")) return "eager";
|
|
464
|
+
return "lazy";
|
|
465
|
+
}
|
|
466
|
+
function scanFileEntries(dir, base, fs2, path2) {
|
|
467
|
+
const entries = [];
|
|
468
|
+
for (const entry of fs2.readdirSync(dir, { withFileTypes: true })) {
|
|
469
|
+
const fullPath = path2.join(dir, entry.name);
|
|
470
|
+
const relativePath = base ? `${base}/${entry.name}` : entry.name;
|
|
471
|
+
if (entry.isDirectory()) {
|
|
472
|
+
entries.push(...scanFileEntries(fullPath, relativePath, fs2, path2));
|
|
473
|
+
} else {
|
|
474
|
+
const ext = path2.extname(entry.name).toLowerCase();
|
|
475
|
+
if (SKIP_EXTENSIONS.has(ext)) continue;
|
|
476
|
+
const stat = fs2.statSync(fullPath);
|
|
477
|
+
entries.push({
|
|
478
|
+
relativePath,
|
|
479
|
+
fullPath,
|
|
480
|
+
sizeBytes: stat.size,
|
|
481
|
+
category: categorize(relativePath)
|
|
482
|
+
});
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
return entries;
|
|
486
|
+
}
|
|
487
|
+
function buildBatches(entries) {
|
|
488
|
+
const batches = [];
|
|
489
|
+
let currentBatch = [];
|
|
490
|
+
let currentSize = 0;
|
|
491
|
+
for (const entry of entries) {
|
|
492
|
+
if (currentBatch.length > 0 && currentSize + entry.sizeBytes > MAX_BATCH_BYTES) {
|
|
493
|
+
batches.push(currentBatch);
|
|
494
|
+
currentBatch = [];
|
|
495
|
+
currentSize = 0;
|
|
496
|
+
}
|
|
497
|
+
currentBatch.push(entry);
|
|
498
|
+
currentSize += entry.sizeBytes;
|
|
499
|
+
}
|
|
500
|
+
if (currentBatch.length > 0) batches.push(currentBatch);
|
|
501
|
+
return batches;
|
|
502
|
+
}
|
|
503
|
+
const uploadBackend = makeCliCommand("upload-backend").withServices(WIX_CLIENT_SERVICE, CONSOLE_CONTEXT).withHandler(
|
|
504
|
+
async (input, wixClient, ctx) => {
|
|
505
|
+
const fs2 = await import("node:fs");
|
|
506
|
+
const path2 = await import("node:path");
|
|
507
|
+
const buildDir = ctx.build.backend;
|
|
508
|
+
const collectionId = input.collectionId || DEFAULT_COLLECTION_ID;
|
|
509
|
+
const dryRun = input.dryRun || false;
|
|
510
|
+
const metadataPath = path2.join(buildDir, "build-metadata.json");
|
|
511
|
+
let version = "1.0.0";
|
|
512
|
+
if (fs2.existsSync(metadataPath)) {
|
|
513
|
+
const metadata = JSON.parse(fs2.readFileSync(metadataPath, "utf8"));
|
|
514
|
+
version = String(metadata.version || "1.0.0");
|
|
515
|
+
}
|
|
516
|
+
if (dryRun) ctx.log("DRY RUN — no uploads");
|
|
517
|
+
if (!fs2.existsSync(buildDir)) {
|
|
518
|
+
ctx.error(`Backend dir not found: ${buildDir}`);
|
|
519
|
+
return { success: false };
|
|
520
|
+
}
|
|
521
|
+
const entries = scanFileEntries(buildDir, "", fs2, path2);
|
|
522
|
+
const totalSize = entries.reduce((sum, f) => sum + f.sizeBytes, 0);
|
|
523
|
+
ctx.log(`${entries.length} files (${(totalSize / 1024 / 1024).toFixed(1)} MB)`);
|
|
524
|
+
if (dryRun) {
|
|
525
|
+
for (const f of entries.slice(0, 20)) {
|
|
526
|
+
ctx.log(
|
|
527
|
+
` [${f.category}] ${f.relativePath} (${(f.sizeBytes / 1024).toFixed(0)} KB)`
|
|
528
|
+
);
|
|
529
|
+
}
|
|
530
|
+
if (entries.length > 20) ctx.log(` ... and ${entries.length - 20} more`);
|
|
531
|
+
return { success: true, fileCount: entries.length, totalSize };
|
|
532
|
+
}
|
|
533
|
+
const store = new WixDataArtifactStore({
|
|
534
|
+
wixClient,
|
|
535
|
+
collectionId,
|
|
536
|
+
version,
|
|
537
|
+
cacheDir: "/tmp/upload-staging"
|
|
538
|
+
});
|
|
539
|
+
const batches = buildBatches(entries);
|
|
540
|
+
let uploaded = 0;
|
|
541
|
+
let errors = 0;
|
|
542
|
+
let lastReported = 0;
|
|
543
|
+
for (let i = 0; i < batches.length; i++) {
|
|
544
|
+
const batch = batches[i];
|
|
545
|
+
const files = batch.map((f) => ({
|
|
546
|
+
path: f.relativePath,
|
|
547
|
+
content: fs2.readFileSync(f.fullPath, "utf8"),
|
|
548
|
+
category: f.category
|
|
549
|
+
}));
|
|
550
|
+
const count = await store.writeFiles(files);
|
|
551
|
+
uploaded += count;
|
|
552
|
+
errors += batch.length - count;
|
|
553
|
+
if (uploaded - lastReported >= 100 || i === batches.length - 1) {
|
|
554
|
+
ctx.log(`${uploaded}/${entries.length} files uploaded`);
|
|
555
|
+
lastReported = uploaded;
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
if (errors > 0) ctx.log(`${errors} errors`);
|
|
559
|
+
return { success: errors === 0, uploaded, errors, version };
|
|
560
|
+
}
|
|
561
|
+
);
|
|
562
|
+
const BACKEND_WORKER_COMPONENT_ID = "ed5f3d0e-7b79-4717-9c00-c4cd7bbbe906";
|
|
563
|
+
function md5(buffer) {
|
|
564
|
+
return crypto.createHash("md5").update(buffer).digest("hex");
|
|
565
|
+
}
|
|
566
|
+
function getMimeType(filePath) {
|
|
567
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
568
|
+
const types = {
|
|
569
|
+
".mjs": "application/javascript",
|
|
570
|
+
".js": "application/javascript",
|
|
571
|
+
".json": "application/json",
|
|
572
|
+
".html": "text/html",
|
|
573
|
+
".css": "text/css",
|
|
574
|
+
".yaml": "text/yaml",
|
|
575
|
+
".yml": "text/yaml"
|
|
576
|
+
};
|
|
577
|
+
return types[ext] || "application/octet-stream";
|
|
578
|
+
}
|
|
579
|
+
function collectFiles(dir, base = "") {
|
|
580
|
+
const files = [];
|
|
581
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
582
|
+
const fullPath = path.join(dir, entry.name);
|
|
583
|
+
const relativePath = base ? `${base}/${entry.name}` : entry.name;
|
|
584
|
+
if (entry.isDirectory()) {
|
|
585
|
+
files.push(...collectFiles(fullPath, relativePath));
|
|
586
|
+
} else {
|
|
587
|
+
const content = fs.readFileSync(fullPath);
|
|
588
|
+
files.push({
|
|
589
|
+
path: `/${relativePath}`,
|
|
590
|
+
content,
|
|
591
|
+
hash: md5(content),
|
|
592
|
+
contentType: getMimeType(entry.name),
|
|
593
|
+
size: content.length
|
|
594
|
+
});
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
return files;
|
|
598
|
+
}
|
|
599
|
+
const deployBaas = makeCliCommand("deploy-baas").withServices(CONSOLE_CONTEXT).withHandler(async (input, ctx) => {
|
|
600
|
+
const distDir = path.resolve(ctx.projectRoot, "dist");
|
|
601
|
+
const dryRun = input.dryRun || false;
|
|
602
|
+
if (!fs.existsSync(distDir)) {
|
|
603
|
+
ctx.error("dist/ not found. Run wix-deploy/build-entry first.");
|
|
604
|
+
return { success: false };
|
|
605
|
+
}
|
|
606
|
+
const wixConfigPath = path.join(ctx.projectRoot, "wix.config.json");
|
|
607
|
+
if (!fs.existsSync(wixConfigPath)) {
|
|
608
|
+
ctx.error("wix.config.json not found.");
|
|
609
|
+
return { success: false };
|
|
610
|
+
}
|
|
611
|
+
const wixConfig = JSON.parse(fs.readFileSync(wixConfigPath, "utf8"));
|
|
612
|
+
const appId = wixConfig.appId;
|
|
613
|
+
wixConfig.siteId;
|
|
614
|
+
const os = await import("node:os");
|
|
615
|
+
const accountPath = path.join(os.homedir(), ".wix", "auth", "account.json");
|
|
616
|
+
if (!fs.existsSync(accountPath)) {
|
|
617
|
+
ctx.error("No auth found. Run `wix login` first.");
|
|
618
|
+
return { success: false };
|
|
619
|
+
}
|
|
620
|
+
const authData = JSON.parse(fs.readFileSync(accountPath, "utf8"));
|
|
621
|
+
let accessToken = authData.accessToken;
|
|
622
|
+
const issuedAt = authData.issuedAt * 1e3;
|
|
623
|
+
const expiresAt = issuedAt + authData.expiresIn * 1e3;
|
|
624
|
+
if (Date.now() > expiresAt - 6e4) {
|
|
625
|
+
ctx.log("Access token expired, refreshing...");
|
|
626
|
+
const refreshResponse = await fetch("https://manage.wix.com/oauth2/token", {
|
|
627
|
+
method: "POST",
|
|
628
|
+
headers: {
|
|
629
|
+
"Content-Type": "application/json",
|
|
630
|
+
"X-XSRF-TOKEN": "nocheck",
|
|
631
|
+
Cookie: "XSRF-TOKEN=nocheck"
|
|
632
|
+
},
|
|
633
|
+
body: JSON.stringify({
|
|
634
|
+
clientId: "6f95cec8-3e98-48b9-b4e5-1fb92fcd9973",
|
|
635
|
+
grantType: "refresh_token",
|
|
636
|
+
refreshToken: authData.refreshToken
|
|
637
|
+
})
|
|
638
|
+
});
|
|
639
|
+
if (!refreshResponse.ok) {
|
|
640
|
+
ctx.error(
|
|
641
|
+
`Token refresh failed: ${refreshResponse.status}. Run \`wix login\` again.`
|
|
642
|
+
);
|
|
643
|
+
return { success: false };
|
|
644
|
+
}
|
|
645
|
+
const tokenData = await refreshResponse.json();
|
|
646
|
+
accessToken = tokenData.access_token;
|
|
647
|
+
authData.accessToken = accessToken;
|
|
648
|
+
authData.issuedAt = Math.floor(Date.now() / 1e3);
|
|
649
|
+
authData.expiresIn = tokenData.expires_in;
|
|
650
|
+
fs.writeFileSync(accountPath, JSON.stringify(authData, null, 2));
|
|
651
|
+
ctx.log("Token refreshed");
|
|
652
|
+
}
|
|
653
|
+
const httpClient = createHttpClient({
|
|
654
|
+
baseURL: "https://manage.wix.com",
|
|
655
|
+
getAppToken: async () => accessToken,
|
|
656
|
+
createHeaders: () => ({
|
|
657
|
+
"X-XSRF-TOKEN": "nocheck",
|
|
658
|
+
Cookie: "XSRF-TOKEN=nocheck"
|
|
659
|
+
})
|
|
660
|
+
});
|
|
661
|
+
const frontendDir = ctx.build.frontend;
|
|
662
|
+
const serverDir = distDir;
|
|
663
|
+
const clientFiles = fs.existsSync(frontendDir) ? collectFiles(frontendDir) : [];
|
|
664
|
+
const serverFiles = collectFiles(serverDir);
|
|
665
|
+
const files = [...clientFiles, ...serverFiles];
|
|
666
|
+
const totalSize = files.reduce((sum, f) => sum + f.size, 0);
|
|
667
|
+
ctx.log(
|
|
668
|
+
`${clientFiles.length} client + ${serverFiles.length} server files (${(totalSize / 1024 / 1024).toFixed(1)} MB)`
|
|
669
|
+
);
|
|
670
|
+
if (dryRun) {
|
|
671
|
+
for (const f of files) {
|
|
672
|
+
ctx.log(` ${f.path} (${(f.size / 1024).toFixed(1)} KB)`);
|
|
673
|
+
}
|
|
674
|
+
return { success: true, fileCount: files.length, totalSize };
|
|
675
|
+
}
|
|
676
|
+
ctx.log("Creating deployment...");
|
|
677
|
+
const staticFilesMetadata = clientFiles.map((f) => ({
|
|
678
|
+
path: f.path,
|
|
679
|
+
hash: f.hash,
|
|
680
|
+
contentType: f.contentType,
|
|
681
|
+
size: f.size
|
|
682
|
+
}));
|
|
683
|
+
const { data: deployData } = await httpClient.request(
|
|
684
|
+
createAppDeployment({
|
|
685
|
+
appDeployment: { appProjectId: appId, staticFilesMetadata }
|
|
686
|
+
})
|
|
687
|
+
);
|
|
688
|
+
const appDeployment = deployData.appDeployment;
|
|
689
|
+
const uploadUrls = deployData.staticFilesUploadUrls || [];
|
|
690
|
+
const uploadToken = deployData.uploadAuthToken;
|
|
691
|
+
const uploadBuckets = deployData.uploadBuckets || [];
|
|
692
|
+
if (uploadUrls.length > 0) {
|
|
693
|
+
const isKubernetes = appDeployment?.cloudProviderOverride === "KUBERNETES";
|
|
694
|
+
if (isKubernetes) {
|
|
695
|
+
let uploaded = 0;
|
|
696
|
+
for (const uploadInfo of uploadUrls) {
|
|
697
|
+
const file = clientFiles.find(
|
|
698
|
+
(f) => f.path === uploadInfo.staticFileMetadata?.path
|
|
699
|
+
);
|
|
700
|
+
if (!file || !uploadInfo.uploadUrl) continue;
|
|
701
|
+
const response = await fetch(uploadInfo.uploadUrl, {
|
|
702
|
+
method: "PUT",
|
|
703
|
+
headers: { "Content-Type": file.contentType },
|
|
704
|
+
body: file.content
|
|
705
|
+
});
|
|
706
|
+
if (!response.ok) {
|
|
707
|
+
ctx.error(` FAILED ${file.path}: ${response.status}`);
|
|
708
|
+
} else {
|
|
709
|
+
uploaded++;
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
ctx.log(`Uploaded ${uploaded} CDN files`);
|
|
713
|
+
} else {
|
|
714
|
+
const uploadUrl = uploadUrls[0].uploadUrl;
|
|
715
|
+
const buckets = uploadBuckets.length > 0 ? uploadBuckets : [{ hashes: clientFiles.map((f) => f.hash) }];
|
|
716
|
+
for (let i = 0; i < buckets.length; i++) {
|
|
717
|
+
const bucket = buckets[i];
|
|
718
|
+
const formData = new FormData();
|
|
719
|
+
for (const hash of bucket.hashes || []) {
|
|
720
|
+
const file = clientFiles.find((f) => f.hash === hash);
|
|
721
|
+
if (!file) continue;
|
|
722
|
+
formData.append(
|
|
723
|
+
hash,
|
|
724
|
+
new Blob([file.content], { type: file.contentType }),
|
|
725
|
+
hash
|
|
726
|
+
);
|
|
727
|
+
}
|
|
728
|
+
const response = await fetch(uploadUrl, {
|
|
729
|
+
method: "POST",
|
|
730
|
+
headers: { Authorization: `Bearer ${uploadToken}` },
|
|
731
|
+
body: formData
|
|
732
|
+
});
|
|
733
|
+
if (!response.ok) {
|
|
734
|
+
ctx.error(` Bucket ${i + 1} FAILED: ${response.status}`);
|
|
735
|
+
} else {
|
|
736
|
+
ctx.log(
|
|
737
|
+
` Bucket ${i + 1}/${buckets.length}: ${bucket.hashes?.length} files`
|
|
738
|
+
);
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
ctx.log("Uploading server files...");
|
|
744
|
+
const backendFiles = serverFiles.map((f) => ({
|
|
745
|
+
path: f.path.startsWith("/") ? f.path.slice(1) : f.path,
|
|
746
|
+
content: f.content.toString("base64")
|
|
747
|
+
}));
|
|
748
|
+
const { data: completeData } = await httpClient.request(
|
|
749
|
+
completeAppDeployment({
|
|
750
|
+
appDeployment: { ...appDeployment, files: backendFiles },
|
|
751
|
+
staticsCompletionToken: uploadToken
|
|
752
|
+
})
|
|
753
|
+
);
|
|
754
|
+
const completedDeployment = completeData.appDeployment || {};
|
|
755
|
+
const deploymentId = appDeployment?.id;
|
|
756
|
+
const deploymentBaseUrl = completedDeployment.deploymentBaseUrl || appDeployment?.deploymentBaseUrl || "";
|
|
757
|
+
let appVersion = 0;
|
|
758
|
+
try {
|
|
759
|
+
const { data: versionData } = await httpClient.request(
|
|
760
|
+
getLatestProductionVersion({ appId })
|
|
761
|
+
);
|
|
762
|
+
appVersion = versionData.appVersion?.version || 0;
|
|
763
|
+
} catch {
|
|
764
|
+
}
|
|
765
|
+
ctx.log("Registering + releasing...");
|
|
766
|
+
const overrideId = crypto.randomUUID();
|
|
767
|
+
await httpClient.request(
|
|
768
|
+
createComponentsOverride({
|
|
769
|
+
componentsOverride: {
|
|
770
|
+
appId,
|
|
771
|
+
appVersion,
|
|
772
|
+
externalId: appId,
|
|
773
|
+
id: overrideId,
|
|
774
|
+
modifiedComponents: [
|
|
775
|
+
{
|
|
776
|
+
componentId: BACKEND_WORKER_COMPONENT_ID,
|
|
777
|
+
type: "BACKEND_WORKER",
|
|
778
|
+
data: {
|
|
779
|
+
backendWorker: {
|
|
780
|
+
deploymentId,
|
|
781
|
+
deploymentUrl: deploymentBaseUrl
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
]
|
|
786
|
+
}
|
|
787
|
+
})
|
|
788
|
+
);
|
|
789
|
+
const { data: releaseData } = await httpClient.request(
|
|
790
|
+
release({
|
|
791
|
+
appId,
|
|
792
|
+
componentOverrideId: overrideId,
|
|
793
|
+
createMinorVersion: true
|
|
794
|
+
})
|
|
795
|
+
);
|
|
796
|
+
const releaseUrl = releaseData.releaseBaseUrl || deploymentBaseUrl;
|
|
797
|
+
ctx.log(`Released → ${releaseUrl}`);
|
|
798
|
+
return { success: true, deploymentId, baseUrl: releaseUrl };
|
|
799
|
+
});
|
|
800
|
+
function silentCtx(ctx) {
|
|
801
|
+
return { ...ctx, log: () => {
|
|
802
|
+
}, warn: () => {
|
|
803
|
+
} };
|
|
804
|
+
}
|
|
805
|
+
function prefixCtx(ctx, prefix) {
|
|
806
|
+
return { ...ctx, log: (msg) => ctx.log(`[deploy] ${prefix} ${msg}`), warn: () => {
|
|
807
|
+
} };
|
|
808
|
+
}
|
|
809
|
+
function bumpPatch(version) {
|
|
810
|
+
const parts = version.split(".");
|
|
811
|
+
if (parts.length === 3) {
|
|
812
|
+
parts[2] = String(parseInt(parts[2], 10) + 1);
|
|
813
|
+
return parts.join(".");
|
|
814
|
+
}
|
|
815
|
+
return version + ".1";
|
|
816
|
+
}
|
|
817
|
+
const deploy = makeCliCommand("deploy").withServices(WIX_CLIENT_SERVICE, CONSOLE_CONTEXT).withHandler(async (input, wixClient, ctx) => {
|
|
818
|
+
const fs2 = await import("node:fs");
|
|
819
|
+
const path2 = await import("node:path");
|
|
820
|
+
const dryRun = input.dryRun || false;
|
|
821
|
+
const t0 = Date.now();
|
|
822
|
+
const metadataPath = path2.join(ctx.build.backend, "build-metadata.json");
|
|
823
|
+
if (!fs2.existsSync(metadataPath)) {
|
|
824
|
+
ctx.error(`[deploy] build-metadata.json not found. Run build:production first.`);
|
|
825
|
+
return { success: false };
|
|
826
|
+
}
|
|
827
|
+
const metadata = JSON.parse(fs2.readFileSync(metadataPath, "utf8"));
|
|
828
|
+
const buildVersion = String(metadata.version || "1.0.0");
|
|
829
|
+
const deployVersion = bumpPatch(buildVersion);
|
|
830
|
+
metadata.version = deployVersion;
|
|
831
|
+
fs2.writeFileSync(metadataPath, JSON.stringify(metadata, null, 2));
|
|
832
|
+
ctx.log(`[deploy] Version: ${buildVersion} → ${deployVersion}`);
|
|
833
|
+
ctx.log("[deploy] Bundling entry.mjs...");
|
|
834
|
+
const buildResult = await buildEntry.handler(
|
|
835
|
+
{
|
|
836
|
+
collectionId: input.collectionId,
|
|
837
|
+
staticBaseUrl: input.staticBaseUrl,
|
|
838
|
+
excludePlugins: input.excludePlugins
|
|
839
|
+
},
|
|
840
|
+
silentCtx(ctx)
|
|
841
|
+
);
|
|
842
|
+
if (!buildResult?.success) {
|
|
843
|
+
ctx.error("[deploy] Bundle failed");
|
|
844
|
+
return { success: false };
|
|
845
|
+
}
|
|
846
|
+
const bundleTime = ((Date.now() - t0) / 1e3).toFixed(1);
|
|
847
|
+
ctx.log(`[deploy] Bundled entry.mjs (${buildResult.sizeMB} MB) in ${bundleTime}s`);
|
|
848
|
+
ctx.log("[deploy] Uploading...");
|
|
849
|
+
const t1 = Date.now();
|
|
850
|
+
const [uploadResult, deployResult] = await Promise.all([
|
|
851
|
+
uploadBackend.handler(
|
|
852
|
+
{ collectionId: input.collectionId, dryRun },
|
|
853
|
+
wixClient,
|
|
854
|
+
prefixCtx(ctx, "data |")
|
|
855
|
+
),
|
|
856
|
+
dryRun ? { success: true, baseUrl: "(dry run)" } : deployBaas.handler({ dryRun }, prefixCtx(ctx, "baas |"))
|
|
857
|
+
]);
|
|
858
|
+
if (!uploadResult?.success) ctx.error("[deploy] Backend data upload failed");
|
|
859
|
+
if (!deployResult?.success) ctx.error("[deploy] BaaS deployment failed");
|
|
860
|
+
const success = !!(uploadResult?.success && deployResult?.success);
|
|
861
|
+
const elapsed = ((Date.now() - t0) / 1e3).toFixed(1);
|
|
862
|
+
const deployTime = ((Date.now() - t1) / 1e3).toFixed(1);
|
|
863
|
+
ctx.log("");
|
|
864
|
+
if (success) {
|
|
865
|
+
ctx.log(`[deploy] Done in ${elapsed}s (bundle ${bundleTime}s + deploy ${deployTime}s)`);
|
|
866
|
+
ctx.log(
|
|
867
|
+
`[deploy] Version: ${deployVersion} | Entry: ${buildResult.sizeMB} MB | Backend files: ${uploadResult.uploaded}`
|
|
868
|
+
);
|
|
869
|
+
if (deployResult.baseUrl) {
|
|
870
|
+
ctx.log(`[deploy] URL: ${deployResult.baseUrl}`);
|
|
871
|
+
}
|
|
872
|
+
} else {
|
|
873
|
+
ctx.log(`[deploy] Failed after ${elapsed}s`);
|
|
874
|
+
}
|
|
875
|
+
return { success, ...deployResult };
|
|
876
|
+
});
|
|
877
|
+
async function setupWixDeploy(ctx) {
|
|
878
|
+
if (ctx.initError) {
|
|
879
|
+
return {
|
|
880
|
+
status: "error",
|
|
881
|
+
message: `Service init failed: ${ctx.initError.message}`
|
|
882
|
+
};
|
|
883
|
+
}
|
|
884
|
+
const wixConfigPath = path.join(ctx.projectRoot, "wix.config.json");
|
|
885
|
+
const wixYamlPath = path.join(ctx.configDir, ".wix.yaml");
|
|
886
|
+
if (!fs.existsSync(wixConfigPath)) {
|
|
887
|
+
return {
|
|
888
|
+
status: "needs-config",
|
|
889
|
+
message: "wix.config.json not found. Run: npm create @wix/new@latest init"
|
|
890
|
+
};
|
|
891
|
+
}
|
|
892
|
+
const wixConfig = JSON.parse(fs.readFileSync(wixConfigPath, "utf8"));
|
|
893
|
+
const appId = wixConfig.appId;
|
|
894
|
+
const siteId = wixConfig.siteId;
|
|
895
|
+
if (!appId) {
|
|
896
|
+
return {
|
|
897
|
+
status: "error",
|
|
898
|
+
message: "wix.config.json missing appId"
|
|
899
|
+
};
|
|
900
|
+
}
|
|
901
|
+
const configCreated = [];
|
|
902
|
+
if (fs.existsSync(wixYamlPath)) {
|
|
903
|
+
const content = fs.readFileSync(wixYamlPath, "utf8");
|
|
904
|
+
const config = yaml.load(content);
|
|
905
|
+
if (config) {
|
|
906
|
+
let changed = false;
|
|
907
|
+
const currentClientId = config.oauthStrategy?.clientId || "";
|
|
908
|
+
if (!currentClientId || currentClientId.startsWith("<")) {
|
|
909
|
+
if (!config.oauthStrategy) config.oauthStrategy = {};
|
|
910
|
+
config.oauthStrategy.clientId = appId;
|
|
911
|
+
changed = true;
|
|
912
|
+
configCreated.push("clientId");
|
|
913
|
+
}
|
|
914
|
+
const currentSiteId = config.apiKeyStrategy?.siteId || "";
|
|
915
|
+
if (siteId && (!currentSiteId || currentSiteId.startsWith("<"))) {
|
|
916
|
+
if (!config.apiKeyStrategy) config.apiKeyStrategy = {};
|
|
917
|
+
config.apiKeyStrategy.siteId = siteId;
|
|
918
|
+
changed = true;
|
|
919
|
+
configCreated.push("siteId");
|
|
920
|
+
}
|
|
921
|
+
if (changed) {
|
|
922
|
+
fs.writeFileSync(wixYamlPath, yaml.dump(config, { lineWidth: -1 }), "utf8");
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
if (fs.existsSync(wixYamlPath)) {
|
|
927
|
+
const config = yaml.load(fs.readFileSync(wixYamlPath, "utf8"));
|
|
928
|
+
const apiKey = config?.apiKeyStrategy?.apiKey || "";
|
|
929
|
+
if (!apiKey || apiKey.startsWith("<")) {
|
|
930
|
+
return {
|
|
931
|
+
status: "needs-config",
|
|
932
|
+
configCreated,
|
|
933
|
+
message: "API key required — create one at https://manage.wix.com/ and add to config/.wix.yaml"
|
|
934
|
+
};
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
let collectionOk = false;
|
|
938
|
+
try {
|
|
939
|
+
const wixClient = getService(WIX_CLIENT_SERVICE);
|
|
940
|
+
if (wixClient) {
|
|
941
|
+
const dataClient = wixClient.use({ items });
|
|
942
|
+
await dataClient.items.query(DEFAULT_COLLECTION_ID).limit(1).find();
|
|
943
|
+
collectionOk = true;
|
|
944
|
+
}
|
|
945
|
+
} catch {
|
|
946
|
+
return {
|
|
947
|
+
status: "needs-config",
|
|
948
|
+
configCreated,
|
|
949
|
+
message: `Data collection "${DEFAULT_COLLECTION_ID}" not found. Create it in the Wix dashboard with fields: path (text), content (text), fileType (text), sizeBytes (number), category (text), version (text)`
|
|
950
|
+
};
|
|
951
|
+
}
|
|
952
|
+
return {
|
|
953
|
+
status: "configured",
|
|
954
|
+
configCreated,
|
|
955
|
+
message: `Deploy target: wix.config.json (appId: ${appId.slice(0, 8)}...). Collection: ${DEFAULT_COLLECTION_ID} ${collectionOk ? "✓" : ""}`
|
|
956
|
+
};
|
|
957
|
+
}
|
|
958
|
+
export {
|
|
959
|
+
WixDataArtifactStore,
|
|
960
|
+
buildEntry,
|
|
961
|
+
deploy,
|
|
962
|
+
deployBaas,
|
|
963
|
+
setupWixDeploy,
|
|
964
|
+
uploadBackend
|
|
965
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@jay-framework/wix-deploy",
|
|
3
|
+
"version": "0.18.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Wix BaaS deployment adapter for Jay Framework — WixDataArtifactStore and entry builder",
|
|
6
|
+
"license": "Apache-2.0",
|
|
7
|
+
"main": "dist/index.js",
|
|
8
|
+
"files": [
|
|
9
|
+
"dist",
|
|
10
|
+
"plugin.yaml"
|
|
11
|
+
],
|
|
12
|
+
"exports": {
|
|
13
|
+
".": "./dist/index.js",
|
|
14
|
+
"./artifact-store": "./dist/artifact-store.js",
|
|
15
|
+
"./plugin.yaml": "./plugin.yaml",
|
|
16
|
+
"./package.json": "./package.json"
|
|
17
|
+
},
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "npm run clean && npm run build:server && npm run build:types",
|
|
20
|
+
"build:server": "vite build --ssr",
|
|
21
|
+
"build:types": "tsup lib/index.ts --dts-only --format esm",
|
|
22
|
+
"build:check-types": "tsc",
|
|
23
|
+
"clean": "rimraf dist",
|
|
24
|
+
"confirm": "npm run clean && npm run build && npm run test",
|
|
25
|
+
"test": ":"
|
|
26
|
+
},
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"@jay-framework/fullstack-component": "^0.18.0",
|
|
29
|
+
"@jay-framework/production-server": "^0.18.0",
|
|
30
|
+
"@jay-framework/stack-server-runtime": "^0.18.0",
|
|
31
|
+
"@jay-framework/wix-server-client": "^0.18.0",
|
|
32
|
+
"@wix/ambassador-ctp-gradual-rollout-v1-baas-release": "^1.0.12",
|
|
33
|
+
"@wix/ambassador-devcenter-apps-v1-app-version": "^1.0.115",
|
|
34
|
+
"@wix/ambassador-devcenter-components-overrides-v1-components-override": "^1.0.475",
|
|
35
|
+
"@wix/ambassador-velo-backend-v1-app-deployment": "^1.0.55",
|
|
36
|
+
"@wix/data": "^1.0.433",
|
|
37
|
+
"@wix/http-client": "^2.88.0",
|
|
38
|
+
"@wix/sdk": "^1.21.5",
|
|
39
|
+
"esbuild": "^0.21.0",
|
|
40
|
+
"js-yaml": "^4.1.0"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"tsup": "^8.0.0",
|
|
44
|
+
"typescript": "^5.3.3",
|
|
45
|
+
"vite": "^5.0.0"
|
|
46
|
+
},
|
|
47
|
+
"engines": {
|
|
48
|
+
"node": ">=20.0.0"
|
|
49
|
+
}
|
|
50
|
+
}
|
package/plugin.yaml
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
name: wix-deploy
|
|
2
|
+
|
|
3
|
+
setup:
|
|
4
|
+
handler: setupWixDeploy
|
|
5
|
+
|
|
6
|
+
commands:
|
|
7
|
+
- name: deploy
|
|
8
|
+
command: deploy.jay-command
|
|
9
|
+
- name: build-entry
|
|
10
|
+
command: build-entry.jay-command
|
|
11
|
+
- name: upload-backend
|
|
12
|
+
command: upload-backend.jay-command
|
|
13
|
+
- name: deploy-baas
|
|
14
|
+
command: deploy-baas.jay-command
|