lucy-cli 0.10.0 → 1.0.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/README.md +18 -8
- package/dist/gulp/checks.js +15 -46
- package/dist/gulp/types.js +8 -6
- package/dist/helpers.d.ts +8 -2
- package/dist/helpers.js +19 -3
- package/dist/index.d.ts +5 -1
- package/dist/index.js +7 -5
- package/dist/settings.json +4 -0
- package/dist/sync.d.ts +2 -2
- package/dist/sync.js +84 -2
- package/files/eslint.config.mjs +11 -0
- package/files/typedoc.json +4 -7
- package/files/typescript/backend/http-functions.ts +65 -0
- package/files/typescript/backend/lib/http-functions/sync.ts +270 -0
- package/files/typescript/tsconfig.json +1 -0
- package/package.json +5 -1
- package/src/gulp/checks.ts +16 -47
- package/src/gulp/types.ts +6 -6
- package/src/helpers.ts +30 -5
- package/src/index.ts +14 -7
- package/src/settings.json +1 -0
- package/src/sync.ts +93 -6
- package/src/types.d.ts +1 -1
package/README.md
CHANGED
@@ -30,32 +30,42 @@ Lucy-CLI is designed to streamline the setup and management of TypeScript within
|
|
30
30
|
|
31
31
|
4. **Git Submodule Support**
|
32
32
|
- Includes support for git submodules, providing full type support within submodules and enabling the use of libraries and types across projects.
|
33
|
-
|
34
|
-
|
33
|
+
- To add a submodule, include the following in the `lucy.json` file:
|
34
|
+
|
35
|
+
``` json
|
36
|
+
"modules": {
|
37
|
+
"<repoName>": {
|
38
|
+
"url": "String",
|
39
|
+
"branch": "String"
|
40
|
+
}
|
41
|
+
}
|
42
|
+
```
|
43
|
+
|
44
|
+
1. **Configurable Setup**
|
35
45
|
- After initialization, Lucy-CLI creates a `lucy-config.json` configuration file where you can modify settings, add dev packages, specify Wix packages, and configure git submodules.
|
36
46
|
|
37
|
-
|
47
|
+
2. **Execute render functions**
|
38
48
|
- Lucy-CLI can execute render functions located in the backend template folder, allowing you to test render functions locally.
|
39
49
|
|
40
|
-
|
50
|
+
3. **compile sccs files**
|
41
51
|
- Lucy-CLI can compile scss files to css files.
|
42
52
|
- It compiles styles/global.scss file to global.css.
|
43
53
|
|
44
|
-
|
54
|
+
4. **Wix NPM Package Installation**
|
45
55
|
- Lucy-CLI can install Wix npm packages from the `lucy.json` file in the project directory.
|
46
56
|
|
47
|
-
|
57
|
+
5. **Teting with Vitest**
|
48
58
|
- Lucy-CLI can run tests with Vitest.
|
49
59
|
- It runs tests located backend folder with the file name ending with `.spec.ts`.
|
50
60
|
- it creates a code coverage report in the coverage folder in the lib folders and typescript folders.
|
51
61
|
- Vitest is looking for mokes in typescript folder and lib folder.
|
52
62
|
- You can add additional mock folders in vitest.config.ts file.
|
53
63
|
|
54
|
-
|
64
|
+
6. **Linting with ESLint**
|
55
65
|
- Lucy-CLI can lint the code with ESLint.
|
56
66
|
- It uses the ESLint configuration in the `.eslintrc.cjs` file in the project directory.
|
57
67
|
|
58
|
-
|
68
|
+
7. **Add git version during production build**
|
59
69
|
- Lucy-CLI can add the git version to the production build.
|
60
70
|
- It adds the git version to the `public/constant/env.ts` file in the public folder under the key gitTag.
|
61
71
|
|
package/dist/gulp/checks.js
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
import * as fs from 'fs';
|
2
|
-
import glob from 'glob';
|
2
|
+
import { glob } from 'glob';
|
3
3
|
import * as path from 'path';
|
4
4
|
import gulp from 'gulp';
|
5
5
|
import ts from 'gulp-typescript';
|
@@ -23,52 +23,21 @@ function extractMatchFromFile(filePath, pattern) {
|
|
23
23
|
});
|
24
24
|
});
|
25
25
|
}
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
glob(path.join(folderPath, globPattern), (err, files) => {
|
35
|
-
if (err) {
|
36
|
-
reject(err);
|
37
|
-
return;
|
38
|
-
}
|
39
|
-
const filenameList = [];
|
40
|
-
/**
|
41
|
-
* Traverse files
|
42
|
-
* @param {number} index Index
|
43
|
-
*/
|
44
|
-
function traverseFiles(index) {
|
45
|
-
if (index === files.length) {
|
46
|
-
resolve(filenameList);
|
47
|
-
return;
|
48
|
-
}
|
49
|
-
const file = files[index];
|
50
|
-
if (pattern) {
|
51
|
-
if (!file)
|
52
|
-
return;
|
53
|
-
extractMatchFromFile(file, pattern)
|
54
|
-
.then((capturedGroup) => {
|
55
|
-
if (capturedGroup) {
|
56
|
-
filenameList.push(capturedGroup);
|
57
|
-
}
|
58
|
-
traverseFiles(index + 1);
|
59
|
-
})
|
60
|
-
.catch(reject);
|
61
|
-
}
|
62
|
-
if (!pattern) {
|
63
|
-
if (!file)
|
64
|
-
return;
|
65
|
-
filenameList.push(path.basename(file));
|
66
|
-
traverseFiles(index + 1);
|
67
|
-
}
|
26
|
+
async function readFilesInFolder(folderPath, pattern, globPattern) {
|
27
|
+
const files = await glob(path.join(folderPath, globPattern));
|
28
|
+
const filenameList = [];
|
29
|
+
for (const file of files) {
|
30
|
+
if (pattern) {
|
31
|
+
const capturedGroup = await extractMatchFromFile(file, pattern);
|
32
|
+
if (capturedGroup) {
|
33
|
+
filenameList.push(capturedGroup);
|
68
34
|
}
|
69
|
-
|
70
|
-
|
71
|
-
|
35
|
+
}
|
36
|
+
else {
|
37
|
+
filenameList.push(path.basename(file));
|
38
|
+
}
|
39
|
+
}
|
40
|
+
return filenameList;
|
72
41
|
}
|
73
42
|
export async function checkPages(fail, force) {
|
74
43
|
console.log("🐕" + green.underline.bold(' => Checking pages...'));
|
package/dist/gulp/types.js
CHANGED
@@ -149,9 +149,9 @@ export function updateWixTypes(options) {
|
|
149
149
|
}
|
150
150
|
export function addTypes(options, done) {
|
151
151
|
const { replaceOptions } = options;
|
152
|
-
const processPages = gulp.src(['./.wix/types/wix-code-types/dist/types/page/$w.d.ts'])
|
153
|
-
|
154
|
-
|
152
|
+
// const processPages = gulp.src(['./.wix/types/wix-code-types/dist/types/page/$w.d.ts'])
|
153
|
+
// .pipe(replace('declare namespace \\$w {', ' declare namespace $w{\nconst api: $w.Api;\n', replaceOptions))
|
154
|
+
// .pipe(gulp.dest('./.wix/types/wix-code-types/dist/types/page/'));
|
155
155
|
const exportTypes = gulp.src(['./.wix/types/wix-code-types/dist/types/common/*.d.ts', '!./.wix/types/wix-code-types/dist/types/common/$w.d.ts'])
|
156
156
|
.pipe(replace('interface ', 'export interface ', replaceOptions))
|
157
157
|
.pipe(replace('enum ', 'export enum ', replaceOptions))
|
@@ -163,10 +163,12 @@ export function addTypes(options, done) {
|
|
163
163
|
.pipe(replace('type ', 'export type ', replaceOptions))
|
164
164
|
.pipe(gulp.dest('./.wix/types/wix-code-types/dist/types/beta/common/'));
|
165
165
|
const processCommon = gulp.src(['./.wix/types/wix-code-types/dist/types/common/$w.d.ts'])
|
166
|
-
.pipe(insert.prepend("import
|
167
|
-
.pipe(replace('namespace \\$w {', 'declare namespace $w{\ntype Api = FrontendAPI;\n', replaceOptions))
|
166
|
+
.pipe(insert.prepend("import '@total-typescript/ts-reset';\n"))
|
167
|
+
// .pipe(replace('namespace \\$w {', 'declare namespace $w{\ntype Api = FrontendAPI;\n', replaceOptions))
|
168
168
|
.pipe(gulp.dest('./.wix/types/wix-code-types/dist/types/common/'));
|
169
|
-
return merge(
|
169
|
+
return merge(
|
170
|
+
// processPages,
|
171
|
+
processCommon, exportTypesBeta, exportTypes)
|
170
172
|
.on('error', function (e) {
|
171
173
|
console.log("💩" + red.underline.bold(' => Updating WIX failed!'));
|
172
174
|
console.log("💩" + red.underline.bold(` => Error: ${orange(e.message)}`));
|
package/dist/helpers.d.ts
CHANGED
@@ -1,6 +1,6 @@
|
|
1
|
-
import { ModuleSettings, ProjectSettings } from '.';
|
1
|
+
import { LucySettings, ModuleSettings, ProjectSettings } from '.';
|
2
2
|
export declare function installPackages(wixPackages: Record<string, string>, devPackages: Record<string, string>, cwd: string, locked: boolean): Promise<void>;
|
3
|
-
export declare function gitInit(cwd: string, modules:
|
3
|
+
export declare function gitInit(cwd: string, modules: LucySettings['modules']): Promise<void>;
|
4
4
|
export declare function runGulp(moduleSettings: ModuleSettings, projectSettings: ProjectSettings, task: string): Promise<void>;
|
5
5
|
/**
|
6
6
|
* Clean up and run a command before exiting the process.
|
@@ -11,3 +11,9 @@ export declare function cleanupWatchers(): void;
|
|
11
11
|
* @param {string} processPattern - The substring to match (e.g., "wix:dev" or "@wix/cli/bin/wix.cjs").
|
12
12
|
*/
|
13
13
|
export declare function killAllProcesses(processPattern: string): void;
|
14
|
+
export interface VeloSyncConfig {
|
15
|
+
siteUrl: string;
|
16
|
+
secret: string;
|
17
|
+
}
|
18
|
+
export declare function saveConfig(config: VeloSyncConfig, file: string): Promise<void>;
|
19
|
+
export declare function readConfig(file: string): Promise<VeloSyncConfig>;
|
package/dist/helpers.js
CHANGED
@@ -6,6 +6,7 @@ import path from 'path';
|
|
6
6
|
import { fileURLToPath } from 'url';
|
7
7
|
import { exec } from 'child_process';
|
8
8
|
import os from 'os';
|
9
|
+
import fs from 'fs';
|
9
10
|
import { blue, green, orange, red, yellow, magenta } from './index.js';
|
10
11
|
export async function installPackages(wixPackages, devPackages, cwd, locked) {
|
11
12
|
if (locked)
|
@@ -43,11 +44,19 @@ export async function installPackages(wixPackages, devPackages, cwd, locked) {
|
|
43
44
|
}
|
44
45
|
}
|
45
46
|
export async function gitInit(cwd, modules) {
|
46
|
-
const
|
47
|
-
for (const [name, url] of Object.entries(modules)) {
|
47
|
+
for (const [name, repo] of Object.entries(modules)) {
|
48
48
|
console.log(chalk.green.underline.bold(`Cloning ${name}`));
|
49
|
+
const git = simpleGit({ baseDir: cwd });
|
49
50
|
try {
|
50
|
-
|
51
|
+
const repoPath = path.resolve(cwd, name);
|
52
|
+
if (!fs.existsSync(repoPath)) {
|
53
|
+
await git.submoduleAdd(repo.url, name);
|
54
|
+
}
|
55
|
+
if (fs.existsSync(repoPath)) {
|
56
|
+
console.log(`🐕 ${blue.underline(' => Module already cloned!')}`);
|
57
|
+
}
|
58
|
+
const localGit = simpleGit({ baseDir: `${cwd}/${name}` });
|
59
|
+
await localGit.checkout(repo.branch);
|
51
60
|
}
|
52
61
|
catch (err) {
|
53
62
|
console.log((`💩 ${red.underline.bold("=> Command failed =>")} ${orange(err)}`));
|
@@ -152,3 +161,10 @@ export function killAllProcesses(processPattern) {
|
|
152
161
|
});
|
153
162
|
});
|
154
163
|
}
|
164
|
+
export async function saveConfig(config, file) {
|
165
|
+
await fs.promises.writeFile(file, JSON.stringify(config));
|
166
|
+
}
|
167
|
+
export async function readConfig(file) {
|
168
|
+
let content = await fs.promises.readFile(file, 'utf-8');
|
169
|
+
return JSON.parse(content);
|
170
|
+
}
|
package/dist/index.d.ts
CHANGED
@@ -1,7 +1,10 @@
|
|
1
1
|
#!/usr/bin/env node --no-warnings
|
2
2
|
export type LucySettings = {
|
3
3
|
modules: {
|
4
|
-
[llibName: string]:
|
4
|
+
[llibName: string]: {
|
5
|
+
url: string;
|
6
|
+
branch: string;
|
7
|
+
};
|
5
8
|
};
|
6
9
|
wixSettings: {
|
7
10
|
compilerOptions: {
|
@@ -33,6 +36,7 @@ export type ModuleSettings = {
|
|
33
36
|
settings: LucySettings;
|
34
37
|
lockVersion: boolean;
|
35
38
|
force: boolean;
|
39
|
+
veloConfigName: string;
|
36
40
|
};
|
37
41
|
export type ProjectSettings = {
|
38
42
|
modules?: Record<string, string>;
|
package/dist/index.js
CHANGED
@@ -78,12 +78,18 @@ async function main() {
|
|
78
78
|
packageJsonPath: join(process.cwd(), 'package.json'),
|
79
79
|
force: false,
|
80
80
|
lockVersion: false,
|
81
|
+
veloConfigName: 'config.json'
|
81
82
|
};
|
82
83
|
let projectSettings = {};
|
83
84
|
if (moduleSettings.args.includes('version') || moduleSettings.args.includes('-v')) {
|
84
85
|
console.log("🐾" + blue.bold(` => ${projectPackageJSON.version}`));
|
85
86
|
return;
|
86
87
|
}
|
88
|
+
// Run velo sync
|
89
|
+
if (moduleSettings.args.includes('velo-sync')) {
|
90
|
+
await sync(moduleSettings, projectSettings);
|
91
|
+
return;
|
92
|
+
}
|
87
93
|
if (moduleSettings.args.includes('help') || moduleSettings.args.includes('-h')) {
|
88
94
|
console.log("🦮 " + green.underline.bold(' => Lucy CLI Help'));
|
89
95
|
console.log("Usage: lucy-cli <command> [options]");
|
@@ -92,7 +98,7 @@ async function main() {
|
|
92
98
|
console.log("🦮 " + magenta.bold('dev') + " : Starts the development environment. This includes setting up any required services for local development.");
|
93
99
|
console.log("🦮 " + magenta.bold('build-prod') + " : Builds the project in production mode, optimizing files for deployment.");
|
94
100
|
console.log("🦮 " + magenta.bold('prepare') + " : Prepares the project by installing packages & initializing git modules, configured in lucy.json");
|
95
|
-
console.log("🦮 " + magenta.bold('sync') + "
|
101
|
+
console.log("🦮 " + magenta.bold('velo-sync') + " : Synchronizes wix collections(velo-sync -h for help)");
|
96
102
|
console.log("🦮 " + magenta.bold('install') + " : Installs all Wix npm packages listed in the 'lucy.json' file in the project directory.");
|
97
103
|
console.log("🦮 " + magenta.bold('fix') + " : Runs a fix command to resolve common issues in development or production settings.");
|
98
104
|
console.log("🦮 " + magenta.bold('docs') + " : Generates documentation for the project.");
|
@@ -215,10 +221,6 @@ async function main() {
|
|
215
221
|
await installPackages(projectSettings.lucySettings.wixPackages, projectSettings.lucySettings.devPackages, moduleSettings.targetFolder, moduleSettings.lockVersion);
|
216
222
|
return;
|
217
223
|
}
|
218
|
-
if (moduleSettings.args.includes('sync')) {
|
219
|
-
sync(moduleSettings, projectSettings);
|
220
|
-
return;
|
221
|
-
}
|
222
224
|
if (moduleSettings.args.includes('dev')) {
|
223
225
|
runGulp(moduleSettings, projectSettings, 'dev');
|
224
226
|
return;
|
package/dist/settings.json
CHANGED
@@ -17,7 +17,11 @@
|
|
17
17
|
"@total-typescript/ts-reset": "^0.6.1",
|
18
18
|
"@types/node": "^22.9.1",
|
19
19
|
"@types/nodemailer": "^6.4.17",
|
20
|
+
"typedoc-github-theme": "^0.2.0",
|
20
21
|
"@types/react": "^19.0.0",
|
22
|
+
"@vitest/ui": "^2.1.8",
|
23
|
+
"eslint-import-resolver-typescript": "^3.7.0",
|
24
|
+
"@vitest/coverage-v8": "^2.1.8",
|
21
25
|
"@typescript-eslint/eslint-plugin": "^8.15.0",
|
22
26
|
"@typescript-eslint/parser": "^8.15.0",
|
23
27
|
"@typescript-eslint/utils": "^8.15.0",
|
package/dist/sync.d.ts
CHANGED
@@ -1,2 +1,2 @@
|
|
1
|
-
import { ModuleSettings, ProjectSettings } from ".";
|
2
|
-
export declare function sync(moduleSettings: ModuleSettings, projectSettings: ProjectSettings): void
|
1
|
+
import { ModuleSettings, ProjectSettings } from "./index.js";
|
2
|
+
export declare function sync(moduleSettings: ModuleSettings, projectSettings: ProjectSettings): Promise<void>;
|
package/dist/sync.js
CHANGED
@@ -1,5 +1,87 @@
|
|
1
|
-
|
1
|
+
import chalk from "chalk";
|
2
|
+
import { green, orange, red } from "./index.js";
|
3
|
+
import readline from 'node:readline';
|
4
|
+
import veloAPI from 'velo-sync/dist/velo/velo-api.js';
|
5
|
+
import syncTask from 'velo-sync/dist/tasks/sync-task.js';
|
6
|
+
import { readConfig, saveConfig } from "./helpers.js";
|
7
|
+
import optimist from 'optimist';
|
8
|
+
import migrateFileCache from 'velo-sync/dist/tasks/migrate-files-cache-task.js';
|
9
|
+
function printUsage() {
|
10
|
+
console.log('Usage: ');
|
11
|
+
console.log('');
|
12
|
+
console.log('Commands:');
|
13
|
+
console.log(' init generates a config file for the import / export / sync process');
|
14
|
+
console.log(' is-alive tests the config and the connection to the site');
|
15
|
+
console.log(' sync runs the sync process');
|
16
|
+
console.log(' import runs an import process');
|
17
|
+
console.log(' migrate migrate existing nedb cache to sqlite cache (.upload-cache.db => .upload-cache.sqlite.db)');
|
18
|
+
}
|
19
|
+
function syncOrImportTask(importOnly) {
|
20
|
+
let argv = optimist
|
21
|
+
.usage(`Usage: $0 ${importOnly ? 'import' : 'sync'} -f <scv filename> -c <collection>`)
|
22
|
+
.demand('f')
|
23
|
+
.alias('f', 'filename')
|
24
|
+
.describe('f', 'csv filename to import')
|
25
|
+
.demand('c')
|
26
|
+
.describe('c', 'the name of the collection to import into')
|
27
|
+
.alias('c', 'collection')
|
28
|
+
.demand('s')
|
29
|
+
.describe('s', 'schema file describing the fields of the collection')
|
30
|
+
.alias('s', 'schema')
|
31
|
+
.describe('dry', 'dry-run that does not upload any data or files, and does not remove or update anything on the site')
|
32
|
+
.alias('dry', 'dryrun')
|
33
|
+
.parse(process.argv.slice(3));
|
34
|
+
let filename = argv.filename;
|
35
|
+
let collection = argv.collection;
|
36
|
+
let schema = argv.schema;
|
37
|
+
let dryrun = argv.dryrun;
|
38
|
+
//@ts-ignore
|
39
|
+
syncTask.default(filename, collection, schema, importOnly, dryrun);
|
40
|
+
}
|
41
|
+
export async function sync(moduleSettings, projectSettings) {
|
42
|
+
if (moduleSettings.args.includes('-h') || moduleSettings.args.includes('help'))
|
43
|
+
return printUsage();
|
44
|
+
if (moduleSettings.args.includes('init')) {
|
45
|
+
const rl = readline.createInterface({
|
46
|
+
input: process.stdin,
|
47
|
+
output: process.stdout,
|
48
|
+
terminal: true
|
49
|
+
});
|
50
|
+
async function askQuestion(query) {
|
51
|
+
return new Promise((resolve) => rl.question(query, (answer) => resolve(answer)));
|
52
|
+
}
|
53
|
+
console.log(chalk.yellow('hello to velo-sync init'));
|
54
|
+
let siteUrl = await askQuestion('what is the url of the site homepage? ');
|
55
|
+
let secret = await askQuestion('what is the velo-sync secret? ');
|
56
|
+
rl.close();
|
57
|
+
let config = { siteUrl, secret };
|
58
|
+
await saveConfig(config, moduleSettings.veloConfigName);
|
59
|
+
return console.log(chalk.green("🐕" + 'config saved!'));
|
60
|
+
}
|
61
|
+
if (moduleSettings.args.includes('is-alive')) {
|
62
|
+
try {
|
63
|
+
let config = await readConfig(moduleSettings.veloConfigName);
|
64
|
+
console.log("🐕" + green(` => checking if the API for site ${chalk.greenBright(config.siteUrl)} is alive...`));
|
65
|
+
await veloAPI.isAlive(config);
|
66
|
+
return console.log(chalk.green("🐕" + `API of site ${chalk.greenBright(config.siteUrl)} is working and alive!!!`));
|
67
|
+
}
|
68
|
+
catch (e) {
|
69
|
+
if (e instanceof Error) {
|
70
|
+
return console.log((`💩 ${red.underline.bold("=> Failed to check endpoint")} ${orange(e.message)}`));
|
71
|
+
}
|
72
|
+
}
|
73
|
+
}
|
2
74
|
if (moduleSettings.args.includes('sync')) {
|
75
|
+
return syncOrImportTask(false);
|
76
|
+
}
|
77
|
+
if (moduleSettings.args.includes('import')) {
|
78
|
+
return syncOrImportTask(true);
|
79
|
+
}
|
80
|
+
if (moduleSettings.args.includes('export')) {
|
81
|
+
return console.log((`💩 ${red.underline.bold("=> Not implemented")}`));
|
82
|
+
}
|
83
|
+
if (moduleSettings.args.includes('migrate')) {
|
84
|
+
//@ts-ignore
|
85
|
+
migrateFileCache.default();
|
3
86
|
}
|
4
|
-
console.log('Hello sync');
|
5
87
|
}
|
package/files/eslint.config.mjs
CHANGED
@@ -22,6 +22,11 @@ export default tseslint.config(
|
|
22
22
|
'named-import-spacing': namedImportSpacing,
|
23
23
|
jsdoc,
|
24
24
|
},
|
25
|
+
settings: {
|
26
|
+
'import/resolver': {
|
27
|
+
typescript: {}
|
28
|
+
}
|
29
|
+
},
|
25
30
|
languageOptions: {
|
26
31
|
parser: tseslint.parser,
|
27
32
|
parserOptions: {
|
@@ -37,6 +42,12 @@ export default tseslint.config(
|
|
37
42
|
},
|
38
43
|
},
|
39
44
|
rules: {
|
45
|
+
'no-restricted-imports': [
|
46
|
+
'error',
|
47
|
+
{
|
48
|
+
'patterns': ['*/backend/*', '*/**/public/*']
|
49
|
+
}
|
50
|
+
],
|
40
51
|
'no-restricted-syntax': [
|
41
52
|
'error',
|
42
53
|
{
|
package/files/typedoc.json
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
/** @type { import('typedoc').TypeDocOptionMap & import('typedoc-plugin-merge-modules').Config } */
|
2
1
|
{
|
3
2
|
"entryPoints": [
|
4
3
|
"*/**/*.ts",
|
@@ -7,22 +6,20 @@
|
|
7
6
|
"entryPointStrategy": "expand",
|
8
7
|
"out": "docs",
|
9
8
|
"plugin": [
|
10
|
-
"typedoc-github-theme"
|
9
|
+
"typedoc-github-theme"
|
11
10
|
],
|
12
11
|
"tsconfig": "local.tsconfig.json",
|
13
12
|
"excludeExternals": true,
|
14
13
|
"externalPattern": [
|
15
14
|
"**/node_modules/**",
|
16
15
|
"**/.wix/**",
|
17
|
-
".wix/**"
|
16
|
+
".wix/**"
|
18
17
|
],
|
19
18
|
"exclude": [
|
20
19
|
"./.wix/**/*",
|
21
20
|
".wix/**/*",
|
22
21
|
"cypress/**",
|
23
|
-
"**/__mocks__/**"
|
22
|
+
"**/__mocks__/**"
|
24
23
|
],
|
25
|
-
"name": "
|
26
|
-
// "mergeModulesRenameDefaults": false, // NEW option of TypeDoc added by this plugin
|
27
|
-
// "mergeModulesMergeMode": "project", // NEW option of TypeDoc added by this plugin
|
24
|
+
"name": "Wix-lucy"
|
28
25
|
}
|
@@ -0,0 +1,65 @@
|
|
1
|
+
/* eslint-disable @typescript-eslint/naming-convention */
|
2
|
+
import { batchCheckUpdateState, clearStale, getImageUploadUrl, insertItemBatch, isAlive, saveItemBatch } from 'backend/lib/http-functions/sync2';
|
3
|
+
import { WixHttpFunctionRequest } from 'wix-http-functions';
|
4
|
+
|
5
|
+
/**------------------------------------------------------------------------
|
6
|
+
** Velo-Sync endpoints
|
7
|
+
* This function are for the velo-sync functionality
|
8
|
+
* https://www.npmjs.com/package/velo-sync
|
9
|
+
*------------------------------------------------------------------------**/
|
10
|
+
/**
|
11
|
+
* Handle the isAlive request
|
12
|
+
* @param request The request object
|
13
|
+
* @returns The response object
|
14
|
+
*/
|
15
|
+
export async function post_isAlive(request: WixHttpFunctionRequest) {
|
16
|
+
return await isAlive(request);
|
17
|
+
}
|
18
|
+
|
19
|
+
/**
|
20
|
+
* Handle the insertItemBatch request
|
21
|
+
* @param request The request object
|
22
|
+
* @returns The response object
|
23
|
+
*/
|
24
|
+
export async function post_insertItemBatch(request: WixHttpFunctionRequest) {
|
25
|
+
|
26
|
+
return await insertItemBatch(request);
|
27
|
+
}
|
28
|
+
|
29
|
+
/**
|
30
|
+
* Handle the saveItemBatch request
|
31
|
+
* @param request The request object
|
32
|
+
* @returns The response object
|
33
|
+
*/
|
34
|
+
export async function post_saveItemBatch(request: WixHttpFunctionRequest) {
|
35
|
+
return await saveItemBatch(request);
|
36
|
+
}
|
37
|
+
|
38
|
+
/**
|
39
|
+
* Handle the clearStale request
|
40
|
+
* @param request The request object
|
41
|
+
* @returns The response object
|
42
|
+
*/
|
43
|
+
export async function post_clearStale(request: WixHttpFunctionRequest) {
|
44
|
+
return await clearStale(request);
|
45
|
+
}
|
46
|
+
|
47
|
+
/**
|
48
|
+
* Handle the batchCheckUpdateState request
|
49
|
+
* @param request The request object
|
50
|
+
* @returns The response object
|
51
|
+
*/
|
52
|
+
export async function post_batchCheckUpdateState(request: WixHttpFunctionRequest) {
|
53
|
+
return await batchCheckUpdateState(request);
|
54
|
+
}
|
55
|
+
|
56
|
+
/**
|
57
|
+
* Handle the getImageUploadUrl request
|
58
|
+
* @param request The request object
|
59
|
+
* @returns The response object
|
60
|
+
*/
|
61
|
+
export async function post_getImageUploadUrl(request: WixHttpFunctionRequest) {
|
62
|
+
return await getImageUploadUrl(request);
|
63
|
+
}
|
64
|
+
/*--------------- END OF SECTION --------------*/
|
65
|
+
|
@@ -0,0 +1,270 @@
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
2
|
+
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
3
|
+
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
4
|
+
/* eslint-disable @typescript-eslint/naming-convention */
|
5
|
+
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
6
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
7
|
+
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
8
|
+
|
9
|
+
import crypto from 'crypto';
|
10
|
+
import wixData from 'wix-data';
|
11
|
+
import { forbidden, ok, serverError, WixHttpFunctionRequest, WixHttpFunctionResponse } from 'wix-http-functions';
|
12
|
+
import { mediaManager } from 'wix-media-backend';
|
13
|
+
import wixSecretsBackend from 'wix-secrets-backend';
|
14
|
+
|
15
|
+
/**----------------------------------------------
|
16
|
+
* * INFO
|
17
|
+
* URL to call this HTTP function from your published site looks like:
|
18
|
+
* Premium site - https://mysite.com/_functions/example/multiply?leftOperand=3&rightOperand=4
|
19
|
+
* Free site - https://username.wixsite.com/mysite/_functions/example/multiply?leftOperand=3&rightOperand=4
|
20
|
+
*
|
21
|
+
* URL to test this HTTP function from your saved site looks like:
|
22
|
+
* Premium site - https://mysite.com/_functions-dev/example/multiply?leftOperand=3&rightOperand=4
|
23
|
+
* Free site - https://username.wixsite.com/mysite/_functions-dev/example/multiply?leftOperand=3&rightOperand=4
|
24
|
+
*---------------------------------------------**/
|
25
|
+
|
26
|
+
//! WARNING: The following code is taken out of velo-sync package.
|
27
|
+
//! WARNING: This serves as example and is not tested.
|
28
|
+
|
29
|
+
class ForbiddenError extends Error {
|
30
|
+
constructor(message: string) {
|
31
|
+
super(message);
|
32
|
+
this.type = FORBIDDEN;
|
33
|
+
}
|
34
|
+
type: string;
|
35
|
+
}
|
36
|
+
|
37
|
+
const FORBIDDEN = 'forbidden';
|
38
|
+
|
39
|
+
/**
|
40
|
+
* Validates the request and parses the payload
|
41
|
+
* @param request - the request object
|
42
|
+
* @returns the parsed payload
|
43
|
+
*/
|
44
|
+
export async function validateAndParseRequest(request: WixHttpFunctionRequest) {
|
45
|
+
const payload = await request.body.text();
|
46
|
+
const payloadJson = JSON.parse(payload, dateReviver) as any;
|
47
|
+
const secret = await wixSecretsBackend.getSecret('velo-sync');
|
48
|
+
const hmac = crypto.createHmac('sha256', secret);
|
49
|
+
hmac.update(JSON.stringify(payloadJson.data, dateReplacer));
|
50
|
+
const digest = hmac.digest('hex');
|
51
|
+
if (digest !== payloadJson.signature){
|
52
|
+
const forbiddenError = new ForbiddenError('invalid signature check');
|
53
|
+
forbiddenError.type = FORBIDDEN;
|
54
|
+
throw forbiddenError;
|
55
|
+
}
|
56
|
+
|
57
|
+
return payloadJson.data;
|
58
|
+
}
|
59
|
+
|
60
|
+
/**
|
61
|
+
* Logs the request and handles the response
|
62
|
+
* @param name - the name of the request
|
63
|
+
* @param handler - the handler function
|
64
|
+
* @returns the response object
|
65
|
+
*/
|
66
|
+
export async function logRequest(name:string, handler: () => Promise<any>) {
|
67
|
+
console.log(name, 'start');
|
68
|
+
const start = new Date().getTime();
|
69
|
+
try {
|
70
|
+
const response = await handler();
|
71
|
+
const now = new Date().getTime();
|
72
|
+
console.log(name, 'completed ok, time:', now - start);
|
73
|
+
|
74
|
+
return ok({ body: response });
|
75
|
+
}
|
76
|
+
catch (e: any){
|
77
|
+
const now = new Date().getTime();
|
78
|
+
if (e.type === FORBIDDEN){
|
79
|
+
console.log(name, 'forbidden:', e.message, ', time:', now - start);
|
80
|
+
|
81
|
+
return forbidden({ body: e.message });
|
82
|
+
}
|
83
|
+
else {
|
84
|
+
console.log(name, 'failed with error:', e.message, ', time:', now - start);
|
85
|
+
|
86
|
+
return serverError({ body: e.message });
|
87
|
+
}
|
88
|
+
}
|
89
|
+
}
|
90
|
+
|
91
|
+
/**
|
92
|
+
* Checks if the service is alive
|
93
|
+
* @param request - the request object
|
94
|
+
* @returns the response object
|
95
|
+
*/
|
96
|
+
export async function isAlive(request: WixHttpFunctionRequest): Promise<WixHttpFunctionResponse> {
|
97
|
+
return await logRequest('isAlive', async () => {
|
98
|
+
const data = await validateAndParseRequest(request);
|
99
|
+
if (data.isAlive === '?')
|
100
|
+
{return 'ok';}
|
101
|
+
else
|
102
|
+
{throw new Error('protocol error - the isAlive API expects isAlive member in the data payload');}
|
103
|
+
});
|
104
|
+
}
|
105
|
+
|
106
|
+
/**
|
107
|
+
* Inserts a batch of items
|
108
|
+
* @param request - the request object
|
109
|
+
* @returns the response object
|
110
|
+
*/
|
111
|
+
export async function insertItemBatch(request: WixHttpFunctionRequest): Promise<WixHttpFunctionResponse> {
|
112
|
+
return await logRequest('insertItemBatch', async () => {
|
113
|
+
const data = await validateAndParseRequest(request);
|
114
|
+
const itemsToInsert = data.items;
|
115
|
+
const collection = data.collection;
|
116
|
+
|
117
|
+
return await wixData.bulkInsert(collection, itemsToInsert, { suppressAuth: true });
|
118
|
+
});
|
119
|
+
}
|
120
|
+
|
121
|
+
/**
|
122
|
+
* Saves a batch of items
|
123
|
+
* @param request - the request object
|
124
|
+
* @returns the response object
|
125
|
+
*/
|
126
|
+
export async function saveItemBatch(request: WixHttpFunctionRequest): Promise<WixHttpFunctionResponse> {
|
127
|
+
return await logRequest('saveItemBatch', async () => {
|
128
|
+
const data = await validateAndParseRequest(request);
|
129
|
+
const items = data.items;
|
130
|
+
const collection = data.collection;
|
131
|
+
|
132
|
+
return await wixData.bulkSave(collection, items, { suppressAuth: true });
|
133
|
+
});
|
134
|
+
}
|
135
|
+
|
136
|
+
/**
|
137
|
+
* Clears stale items
|
138
|
+
* @param request - the request object
|
139
|
+
* @returns the response object
|
140
|
+
*/
|
141
|
+
export async function clearStale(request: WixHttpFunctionRequest): Promise<WixHttpFunctionResponse> {
|
142
|
+
return await logRequest('clearStale', async () => {
|
143
|
+
const data = await validateAndParseRequest(request);
|
144
|
+
const collection = data.collection;
|
145
|
+
|
146
|
+
const date = new Date();
|
147
|
+
date.setDate(date.getDate() - 3);
|
148
|
+
|
149
|
+
const res = await wixData.query(collection)
|
150
|
+
.lt('_updatedDate', date)
|
151
|
+
.find({ suppressAuth: true });
|
152
|
+
console.log(`clearStale - found ${res.totalCount} items to remove, current page ${res.length}`);
|
153
|
+
const itemsToDelete = res.items;
|
154
|
+
const ids = itemsToDelete.map((_: any )=> _._id);
|
155
|
+
const removeResult = await wixData.bulkRemove(collection, ids, { suppressAuth: true });
|
156
|
+
|
157
|
+
return { itemsRemoved: removeResult.removed, staleItems: res.totalCount - removeResult.removed, errors: removeResult.errors };
|
158
|
+
});
|
159
|
+
}
|
160
|
+
|
161
|
+
/**
|
162
|
+
* Batch checks the update state of items
|
163
|
+
* @param request - the request object
|
164
|
+
* @returns the response object
|
165
|
+
*/
|
166
|
+
export async function batchCheckUpdateState(request: WixHttpFunctionRequest): Promise<WixHttpFunctionResponse> {
|
167
|
+
return await logRequest('batchCheckUpdateState', async () => {
|
168
|
+
const data = await validateAndParseRequest(request);
|
169
|
+
|
170
|
+
const collection = data.collection;
|
171
|
+
const items = data.items;
|
172
|
+
const dryrun = data.dryrun;
|
173
|
+
|
174
|
+
const queries = items.map((item: any) => wixData.query(collection).eq('_id', item._id));
|
175
|
+
|
176
|
+
const query = queries.reduce((accuQuery: any, query: any) => (accuQuery)?accuQuery.or(query): query);
|
177
|
+
const result:any[] = [];
|
178
|
+
const itemsToUpdate: any[] = [];
|
179
|
+
const res = await query.find({ suppressAuth: true });
|
180
|
+
items.forEach((item: any) => {
|
181
|
+
const foundItem = res.items.find((_: any) => _._id === item._id);
|
182
|
+
if (foundItem && foundItem._hash === item._hash){
|
183
|
+
itemsToUpdate.push(foundItem);
|
184
|
+
result.push({ status: 'ok', _id: item._id });
|
185
|
+
}
|
186
|
+
else if (foundItem){
|
187
|
+
result.push({ status: 'need-update', _id: item._id });
|
188
|
+
}
|
189
|
+
else {
|
190
|
+
result.push({ status: 'not-found', _id: item._id });
|
191
|
+
}
|
192
|
+
});
|
193
|
+
if (!dryrun)
|
194
|
+
{await wixData.bulkUpdate(collection, itemsToUpdate, { suppressAuth: true });}
|
195
|
+
|
196
|
+
return JSON.stringify(result);
|
197
|
+
});
|
198
|
+
}
|
199
|
+
|
200
|
+
/**
|
201
|
+
* Gets an image upload URL
|
202
|
+
* @param request - the request object
|
203
|
+
* @returns the response object
|
204
|
+
*/
|
205
|
+
export async function getImageUploadUrl(request: WixHttpFunctionRequest): Promise<WixHttpFunctionResponse> {
|
206
|
+
return await logRequest('getImageUploadUrl', async () => {
|
207
|
+
const data = await validateAndParseRequest(request);
|
208
|
+
|
209
|
+
const mimeType = data.mimeTypes;
|
210
|
+
const _id = data._id;
|
211
|
+
const fieldName = data.fieldName;
|
212
|
+
const collection = data.collection;
|
213
|
+
const mediaType = data.mediaType;
|
214
|
+
|
215
|
+
const uploadUrlObj = await mediaManager.getUploadUrl('/synced-images',
|
216
|
+
{
|
217
|
+
'mediaOptions': {
|
218
|
+
mimeType,
|
219
|
+
mediaType
|
220
|
+
},
|
221
|
+
'metadataOptions': {
|
222
|
+
'isPrivate': false,
|
223
|
+
'isVisitorUpload': false,
|
224
|
+
'context': {
|
225
|
+
_id,
|
226
|
+
fieldName,
|
227
|
+
collection
|
228
|
+
}
|
229
|
+
}
|
230
|
+
});
|
231
|
+
|
232
|
+
return uploadUrlObj;
|
233
|
+
});
|
234
|
+
}
|
235
|
+
|
236
|
+
const dateRegex = /^Date\((\d+)\)$/;
|
237
|
+
/**
|
238
|
+
*
|
239
|
+
* @param key
|
240
|
+
* @param value
|
241
|
+
*/
|
242
|
+
|
243
|
+
/**
|
244
|
+
* Reviver function for JSON.parse that converts string representations of Date objects to Date objects
|
245
|
+
* @param key The key of the value
|
246
|
+
* @param value The value to convert
|
247
|
+
* @returns The converted Date object
|
248
|
+
*/
|
249
|
+
export function dateReviver(key: string, value: string) {
|
250
|
+
const match = dateRegex.exec(value);
|
251
|
+
if (match){
|
252
|
+
return new Date(Number(match[1]));
|
253
|
+
}
|
254
|
+
|
255
|
+
return value;
|
256
|
+
}
|
257
|
+
|
258
|
+
/**
|
259
|
+
* Replacer function for JSON.stringify that converts Date objects to a string representation
|
260
|
+
* @param key The key of the value
|
261
|
+
* @param value The value to convert
|
262
|
+
* @returns The converted value
|
263
|
+
*/
|
264
|
+
export function dateReplacer(this: any, key: string, value: string) {
|
265
|
+
const v = this[key];
|
266
|
+
if (v instanceof Date)
|
267
|
+
{return 'Date('+v.getTime()+')';}
|
268
|
+
else
|
269
|
+
{return value;}
|
270
|
+
}
|
package/package.json
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
{
|
2
2
|
"type": "module",
|
3
3
|
"name": "lucy-cli",
|
4
|
-
"version": "0.
|
4
|
+
"version": "1.0.0",
|
5
5
|
"description": "Lucy Framework for WIX Studio Editor",
|
6
6
|
"main": ".dist/index.js",
|
7
7
|
"scripts": {
|
@@ -61,6 +61,7 @@
|
|
61
61
|
"jest": "^29.7.0",
|
62
62
|
"merge-stream": "^2.0.0",
|
63
63
|
"merge2": "^1.4.1",
|
64
|
+
"optimist": "^0.6.1",
|
64
65
|
"prettier": "^3.0.3",
|
65
66
|
"sass": "^1.65.1",
|
66
67
|
"simple-git": "^3.20.0",
|
@@ -84,6 +85,7 @@
|
|
84
85
|
"@types/jest": "^29.5.3",
|
85
86
|
"@types/merge-stream": "2.0.0",
|
86
87
|
"@types/merge2": "^1.4.4",
|
88
|
+
"@types/optimist": "^0.0.33",
|
87
89
|
"@typescript-eslint/eslint-plugin": "8.14.0",
|
88
90
|
"@typescript-eslint/parser": "8.14.0",
|
89
91
|
"@typescript-eslint/utils": "8.14.0",
|
@@ -92,6 +94,8 @@
|
|
92
94
|
"eslint-plugin-jsdoc": "50.5.0",
|
93
95
|
"eslint-plugin-named-import-spacing": "^1.0.3",
|
94
96
|
"eslint-plugin-simple-import-sort": "12.1.1",
|
97
|
+
"i": "^0.3.7",
|
98
|
+
"npm": "^11.0.0",
|
95
99
|
"ts-node": "^10.9.1"
|
96
100
|
}
|
97
101
|
}
|
package/src/gulp/checks.ts
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
import * as fs from 'fs';
|
2
|
-
import glob from 'glob';
|
2
|
+
import { glob } from 'glob';
|
3
3
|
import * as path from 'path';
|
4
4
|
import gulp from 'gulp';
|
5
5
|
import ts from 'gulp-typescript';
|
@@ -27,54 +27,23 @@ function extractMatchFromFile(filePath: string, pattern: string) {
|
|
27
27
|
});
|
28
28
|
}
|
29
29
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
* @param {string} pattern Pattern to match
|
34
|
-
* @param {string} globPattern Glob pattern
|
35
|
-
*/
|
36
|
-
function readFilesInFolder(folderPath: string, pattern: string | null, globPattern: string,) {
|
37
|
-
return new Promise((resolve, reject) => {
|
38
|
-
glob(path.join(folderPath, globPattern), (err: unknown, files: string[]) => {
|
39
|
-
if (err){
|
40
|
-
reject(err);
|
30
|
+
async function readFilesInFolder(folderPath: string, pattern: string | null, globPattern: string): Promise<Object[]> {
|
31
|
+
const files = await glob(path.join(folderPath, globPattern));
|
32
|
+
const filenameList: Object[] = [];
|
41
33
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
return;
|
54
|
-
}
|
55
|
-
const file = files[index];
|
56
|
-
if(pattern){
|
57
|
-
if(!file) return
|
58
|
-
extractMatchFromFile(file, pattern)
|
59
|
-
.then((capturedGroup) => {
|
60
|
-
if (capturedGroup){
|
61
|
-
filenameList.push(capturedGroup);
|
62
|
-
}
|
63
|
-
traverseFiles(index + 1);
|
64
|
-
})
|
65
|
-
.catch(reject);
|
66
|
-
}
|
67
|
-
if(!pattern){
|
68
|
-
if(!file) return
|
69
|
-
filenameList.push(path.basename(file));
|
70
|
-
traverseFiles(index + 1);
|
71
|
-
}
|
72
|
-
}
|
73
|
-
traverseFiles(0);
|
74
|
-
});
|
75
|
-
});
|
76
|
-
}
|
34
|
+
for (const file of files) {
|
35
|
+
if (pattern) {
|
36
|
+
const capturedGroup = await extractMatchFromFile(file, pattern);
|
37
|
+
if (capturedGroup) {
|
38
|
+
filenameList.push(capturedGroup);
|
39
|
+
}
|
40
|
+
} else {
|
41
|
+
filenameList.push(path.basename(file));
|
42
|
+
}
|
43
|
+
}
|
77
44
|
|
45
|
+
return filenameList;
|
46
|
+
}
|
78
47
|
export async function checkPages(fail: boolean, force: boolean) {
|
79
48
|
console.log("🐕" + green.underline.bold(' => Checking pages...'));
|
80
49
|
return new Promise<void>(async (resolve, reject) => {
|
package/src/gulp/types.ts
CHANGED
@@ -159,9 +159,9 @@ export function updateWixTypes(options: TaskOptions) {
|
|
159
159
|
|
160
160
|
export function addTypes(options: TaskOptions, done: gulp.TaskFunctionCallback): NodeJS.ReadWriteStream {
|
161
161
|
const { replaceOptions } = options;
|
162
|
-
const processPages = gulp.src(['./.wix/types/wix-code-types/dist/types/page/$w.d.ts'])
|
163
|
-
|
164
|
-
|
162
|
+
// const processPages = gulp.src(['./.wix/types/wix-code-types/dist/types/page/$w.d.ts'])
|
163
|
+
// .pipe(replace('declare namespace \\$w {', ' declare namespace $w{\nconst api: $w.Api;\n', replaceOptions))
|
164
|
+
// .pipe(gulp.dest('./.wix/types/wix-code-types/dist/types/page/'));
|
165
165
|
|
166
166
|
const exportTypes = gulp.src(['./.wix/types/wix-code-types/dist/types/common/*.d.ts', '!./.wix/types/wix-code-types/dist/types/common/$w.d.ts'])
|
167
167
|
.pipe(replace('interface ', 'export interface ', replaceOptions))
|
@@ -176,12 +176,12 @@ export function addTypes(options: TaskOptions, done: gulp.TaskFunctionCallback):
|
|
176
176
|
.pipe(gulp.dest('./.wix/types/wix-code-types/dist/types/beta/common/'));
|
177
177
|
|
178
178
|
const processCommon = gulp.src(['./.wix/types/wix-code-types/dist/types/common/$w.d.ts'])
|
179
|
-
.pipe(insert.prepend("import
|
180
|
-
.pipe(replace('namespace \\$w {', 'declare namespace $w{\ntype Api = FrontendAPI;\n', replaceOptions))
|
179
|
+
.pipe(insert.prepend("import '@total-typescript/ts-reset';\n"))
|
180
|
+
// .pipe(replace('namespace \\$w {', 'declare namespace $w{\ntype Api = FrontendAPI;\n', replaceOptions))
|
181
181
|
.pipe(gulp.dest('./.wix/types/wix-code-types/dist/types/common/'));
|
182
182
|
|
183
183
|
return merge(
|
184
|
-
processPages,
|
184
|
+
// processPages,
|
185
185
|
processCommon,
|
186
186
|
exportTypesBeta,
|
187
187
|
exportTypes,
|
package/src/helpers.ts
CHANGED
@@ -4,11 +4,13 @@ import { spawnSync } from 'child_process';
|
|
4
4
|
// https://www.sergevandenoever.nl/run-gulp4-tasks-programatically-from-node/
|
5
5
|
import path from 'path';
|
6
6
|
import { fileURLToPath } from 'url';
|
7
|
-
import { ModuleSettings, ProjectSettings } from '.';
|
7
|
+
import { LucySettings, ModuleSettings, ProjectSettings } from '.';
|
8
8
|
import { exec } from 'child_process';
|
9
9
|
import os from 'os';
|
10
|
+
import fs from 'fs';
|
10
11
|
|
11
12
|
import { blue, green, orange, red, yellow, magenta } from './index.js';
|
13
|
+
import { fsync } from 'fs';
|
12
14
|
|
13
15
|
export async function installPackages(wixPackages: Record<string, string>, devPackages: Record<string, string>, cwd: string, locked: boolean ) {
|
14
16
|
if (locked) console.log("🐕" + blue.underline(` => Installing & version locked packages!`));
|
@@ -47,12 +49,23 @@ export async function installPackages(wixPackages: Record<string, string>, devPa
|
|
47
49
|
}
|
48
50
|
}
|
49
51
|
|
50
|
-
export async function gitInit(cwd: string, modules:
|
51
|
-
const
|
52
|
-
for (const [name, url] of Object.entries(modules)) {
|
52
|
+
export async function gitInit(cwd: string, modules: LucySettings['modules']) {
|
53
|
+
for (const [name, repo] of Object.entries(modules)) {
|
53
54
|
console.log(chalk.green.underline.bold(`Cloning ${name}`));
|
55
|
+
const git = simpleGit({ baseDir: cwd });
|
56
|
+
|
54
57
|
try {
|
55
|
-
|
58
|
+
const repoPath = path.resolve(cwd, name);
|
59
|
+
if (!fs.existsSync(repoPath)) {
|
60
|
+
await git.submoduleAdd(repo.url, name)
|
61
|
+
}
|
62
|
+
|
63
|
+
if (fs.existsSync(repoPath)) {
|
64
|
+
console.log(`🐕 ${blue.underline(' => Module already cloned!')}`);
|
65
|
+
}
|
66
|
+
|
67
|
+
const localGit = simpleGit({ baseDir: `${cwd}/${name}` });
|
68
|
+
await localGit.checkout(repo.branch);
|
56
69
|
} catch (err) {
|
57
70
|
console.log((`💩 ${red.underline.bold("=> Command failed =>")} ${orange(err)}`));
|
58
71
|
} finally {
|
@@ -162,4 +175,16 @@ export function killAllProcesses(processPattern: string) {
|
|
162
175
|
}
|
163
176
|
});
|
164
177
|
});
|
178
|
+
}
|
179
|
+
|
180
|
+
export interface VeloSyncConfig {
|
181
|
+
siteUrl: string;
|
182
|
+
secret: string;
|
183
|
+
}
|
184
|
+
export async function saveConfig(config:VeloSyncConfig, file: string) {
|
185
|
+
await fs.promises.writeFile(file, JSON.stringify(config));
|
186
|
+
}
|
187
|
+
export async function readConfig(file: string): Promise<VeloSyncConfig> {
|
188
|
+
let content = await fs.promises.readFile(file, 'utf-8');
|
189
|
+
return JSON.parse(content);
|
165
190
|
}
|
package/src/index.ts
CHANGED
@@ -17,7 +17,10 @@ import { spawnSync } from 'child_process';
|
|
17
17
|
|
18
18
|
export type LucySettings = {
|
19
19
|
modules: {
|
20
|
-
[llibName: string]:
|
20
|
+
[llibName: string]: {
|
21
|
+
url: string;
|
22
|
+
branch: string;
|
23
|
+
};
|
21
24
|
};
|
22
25
|
wixSettings: {
|
23
26
|
compilerOptions: {
|
@@ -50,6 +53,7 @@ export type ModuleSettings = {
|
|
50
53
|
settings: LucySettings;
|
51
54
|
lockVersion: boolean;
|
52
55
|
force: boolean;
|
56
|
+
veloConfigName: string;
|
53
57
|
}
|
54
58
|
|
55
59
|
export type ProjectSettings = {
|
@@ -133,6 +137,7 @@ async function main(): Promise<void> {
|
|
133
137
|
packageJsonPath: join(process.cwd(), 'package.json'),
|
134
138
|
force: false,
|
135
139
|
lockVersion: false,
|
140
|
+
veloConfigName: 'config.json'
|
136
141
|
}
|
137
142
|
|
138
143
|
let projectSettings: ProjectSettings = {};
|
@@ -142,6 +147,12 @@ async function main(): Promise<void> {
|
|
142
147
|
|
143
148
|
return;
|
144
149
|
}
|
150
|
+
// Run velo sync
|
151
|
+
if(moduleSettings.args.includes('velo-sync')){
|
152
|
+
await sync(moduleSettings, projectSettings);
|
153
|
+
|
154
|
+
return;
|
155
|
+
}
|
145
156
|
|
146
157
|
if(moduleSettings.args.includes('help') || moduleSettings.args.includes('-h')){
|
147
158
|
console.log("🦮 " + green.underline.bold(' => Lucy CLI Help'));
|
@@ -151,7 +162,7 @@ async function main(): Promise<void> {
|
|
151
162
|
console.log("🦮 " + magenta.bold('dev') + " : Starts the development environment. This includes setting up any required services for local development.");
|
152
163
|
console.log("🦮 " + magenta.bold('build-prod') + " : Builds the project in production mode, optimizing files for deployment.");
|
153
164
|
console.log("🦮 " + magenta.bold('prepare') + " : Prepares the project by installing packages & initializing git modules, configured in lucy.json");
|
154
|
-
console.log("🦮 " + magenta.bold('sync') + "
|
165
|
+
console.log("🦮 " + magenta.bold('velo-sync') + " : Synchronizes wix collections(velo-sync -h for help)");
|
155
166
|
console.log("🦮 " + magenta.bold('install') + " : Installs all Wix npm packages listed in the 'lucy.json' file in the project directory.");
|
156
167
|
console.log("🦮 " + magenta.bold('fix') + " : Runs a fix command to resolve common issues in development or production settings.");
|
157
168
|
console.log("🦮 " + magenta.bold('docs') + " : Generates documentation for the project.");
|
@@ -220,6 +231,7 @@ async function main(): Promise<void> {
|
|
220
231
|
|
221
232
|
console.log("🐕" + magenta.underline(' => Lucy CLI => RUNNING: ' + orange('Press Ctrl+C to stop.')));
|
222
233
|
// INFO: Run commands
|
234
|
+
|
223
235
|
if(moduleSettings.args.includes('init')){
|
224
236
|
if(projectSettings.lucySettings?.initialized && !moduleSettings.force) {
|
225
237
|
console.log((`💩 ${red.underline.bold("=> This project is already initialized =>")} ${orange(moduleSettings.targetFolder)}`));
|
@@ -286,12 +298,7 @@ async function main(): Promise<void> {
|
|
286
298
|
|
287
299
|
return;
|
288
300
|
}
|
289
|
-
|
290
|
-
if(moduleSettings.args.includes('sync')){
|
291
|
-
sync(moduleSettings, projectSettings);
|
292
301
|
|
293
|
-
return;
|
294
|
-
}
|
295
302
|
|
296
303
|
if(moduleSettings.args.includes('dev')){
|
297
304
|
runGulp(moduleSettings, projectSettings, 'dev');
|
package/src/settings.json
CHANGED
@@ -20,6 +20,7 @@
|
|
20
20
|
"typedoc-github-theme": "^0.2.0",
|
21
21
|
"@types/react": "^19.0.0",
|
22
22
|
"@vitest/ui": "^2.1.8",
|
23
|
+
"eslint-import-resolver-typescript": "^3.7.0",
|
23
24
|
"@vitest/coverage-v8": "^2.1.8",
|
24
25
|
"@typescript-eslint/eslint-plugin": "^8.15.0",
|
25
26
|
"@typescript-eslint/parser": "^8.15.0",
|
package/src/sync.ts
CHANGED
@@ -1,10 +1,97 @@
|
|
1
|
-
import { modifierNames } from "chalk";
|
2
|
-
import { ModuleSettings, ProjectSettings } from ".";
|
1
|
+
import chalk, { modifierNames } from "chalk";
|
2
|
+
import { blue, green, ModuleSettings, orange, ProjectSettings, red } from "./index.js";
|
3
|
+
import readline from 'node:readline';
|
4
|
+
import {createDataSync, LoggingStatistics, LoggerRejectsReporter} from 'velo-sync';
|
5
|
+
import veloAPI from 'velo-sync/dist/velo/velo-api.js';
|
6
|
+
import syncTask from 'velo-sync/dist/tasks/sync-task.js';
|
7
|
+
import { readConfig, saveConfig, VeloSyncConfig } from "./helpers.js";
|
8
|
+
import optimist from 'optimist';
|
9
|
+
import migrateFileCache from 'velo-sync/dist/tasks/migrate-files-cache-task.js';
|
10
|
+
function printUsage() {
|
11
|
+
console.log('Usage: ');
|
12
|
+
console.log('');
|
13
|
+
console.log('Commands:');
|
14
|
+
console.log(' init generates a config file for the import / export / sync process');
|
15
|
+
console.log(' is-alive tests the config and the connection to the site');
|
16
|
+
console.log(' sync runs the sync process');
|
17
|
+
console.log(' import runs an import process');
|
18
|
+
console.log(' migrate migrate existing nedb cache to sqlite cache (.upload-cache.db => .upload-cache.sqlite.db)');
|
19
|
+
}
|
3
20
|
|
4
|
-
|
21
|
+
function syncOrImportTask(importOnly: boolean) {
|
22
|
+
let argv = optimist
|
23
|
+
.usage(`Usage: $0 ${importOnly ? 'import' : 'sync'} -f <scv filename> -c <collection>`)
|
24
|
+
.demand('f')
|
25
|
+
.alias('f', 'filename')
|
26
|
+
.describe('f', 'csv filename to import')
|
27
|
+
.demand('c')
|
28
|
+
.describe('c', 'the name of the collection to import into')
|
29
|
+
.alias('c', 'collection')
|
30
|
+
.demand('s')
|
31
|
+
.describe('s', 'schema file describing the fields of the collection')
|
32
|
+
.alias('s', 'schema')
|
33
|
+
.describe('dry', 'dry-run that does not upload any data or files, and does not remove or update anything on the site')
|
34
|
+
.alias('dry', 'dryrun')
|
35
|
+
.parse(process.argv.slice(3));
|
36
|
+
let filename = argv.filename;
|
37
|
+
let collection = argv.collection;
|
38
|
+
let schema = argv.schema;
|
39
|
+
let dryrun = argv.dryrun;
|
40
|
+
//@ts-ignore
|
41
|
+
syncTask.default(filename, collection, schema, importOnly, dryrun);
|
42
|
+
}
|
5
43
|
|
6
|
-
|
7
|
-
|
44
|
+
export async function sync(moduleSettings: ModuleSettings, projectSettings: ProjectSettings) {
|
45
|
+
|
46
|
+
if(moduleSettings.args.includes('-h') || moduleSettings.args.includes('help')) return printUsage();
|
47
|
+
if(moduleSettings.args.includes('init')) {
|
48
|
+
const rl = readline.createInterface({
|
49
|
+
input: process.stdin,
|
50
|
+
output: process.stdout,
|
51
|
+
terminal: true
|
52
|
+
});
|
53
|
+
|
54
|
+
async function askQuestion(query: string):Promise<string> {
|
55
|
+
return new Promise((resolve) => rl.question(query, (answer) => resolve(answer)));
|
56
|
+
}
|
57
|
+
|
58
|
+
console.log(chalk.yellow('hello to velo-sync init'));
|
59
|
+
let siteUrl = await askQuestion('what is the url of the site homepage? ');
|
60
|
+
let secret = await askQuestion('what is the velo-sync secret? ');
|
61
|
+
rl.close();
|
62
|
+
let config: VeloSyncConfig = { siteUrl, secret };
|
63
|
+
await saveConfig(config, moduleSettings.veloConfigName);
|
64
|
+
return console.log(chalk.green("🐕" + 'config saved!'));
|
8
65
|
}
|
9
|
-
|
66
|
+
|
67
|
+
if(moduleSettings.args.includes('is-alive')) {
|
68
|
+
try {
|
69
|
+
let config = await readConfig(moduleSettings.veloConfigName);
|
70
|
+
console.log("🐕" + green(` => checking if the API for site ${chalk.greenBright(config.siteUrl)} is alive...`));
|
71
|
+
await veloAPI.isAlive(config);
|
72
|
+
return console.log(chalk.green("🐕" + `API of site ${chalk.greenBright(config.siteUrl)} is working and alive!!!`));
|
73
|
+
}
|
74
|
+
catch (e) {
|
75
|
+
if(e instanceof Error) {
|
76
|
+
return console.log((`💩 ${red.underline.bold("=> Failed to check endpoint")} ${orange(e.message)}`));
|
77
|
+
}
|
78
|
+
}
|
79
|
+
}
|
80
|
+
if(moduleSettings.args.includes('sync')) {
|
81
|
+
return syncOrImportTask(false);
|
82
|
+
}
|
83
|
+
|
84
|
+
if(moduleSettings.args.includes('import')) {
|
85
|
+
return syncOrImportTask(true);
|
86
|
+
}
|
87
|
+
|
88
|
+
if(moduleSettings.args.includes('export')) {
|
89
|
+
return console.log((`💩 ${red.underline.bold("=> Not implemented")}`));
|
90
|
+
}
|
91
|
+
|
92
|
+
if(moduleSettings.args.includes('migrate')) {
|
93
|
+
//@ts-ignore
|
94
|
+
migrateFileCache.default();
|
95
|
+
}
|
96
|
+
|
10
97
|
}
|
package/src/types.d.ts
CHANGED